!41 feat: 核心引擎与标准库重构及功能增强

Merge pull request !41 from Luke/feature/add-naitve-print
This commit is contained in:
Luke 2025-07-23 13:51:26 +00:00 committed by Gitee
commit e788e9c437
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
25 changed files with 1193 additions and 234 deletions

10
.run/Demo14.run.xml Normal file
View File

@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Demo14" type="Application" factoryName="Application" folderName="Demo">
<option name="MAIN_CLASS_NAME" value="org.jcnc.snow.cli.SnowCLI" />
<module name="Snow" />
<option name="PROGRAM_PARAMETERS" value="compile run -d playground/Demo/Demo14 -o target/Demo14" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@ -3,7 +3,6 @@
<toRun name="Demo1" type="Application" />
<toRun name="Demo10" type="Application" />
<toRun name="Demo11" type="Application" />
<toRun name="Demo11" type="Application" />
<toRun name="Demo12" type="Application" />
<toRun name="Demo13" type="Application" />
<toRun name="Demo2" type="Application" />

11
lib/os/OS.snow Normal file
View File

@ -0,0 +1,11 @@
module: os
import: os
function: print
parameter:
declare i1: int
return_type: void
body:
syscall("PRINT",i1)
end body
end function
end module

View File

@ -2,8 +2,7 @@ module: Main
function: main
return_type: void
body:
declare abc!:int =1
declare abc:int =1
end body
end function
end module

View File

@ -0,0 +1,9 @@
module: Main
import: os
function: main
return_type: void
body:
print(222)
end body
end function
end module

View File

@ -0,0 +1,11 @@
module: os
import: os
function: print
parameter:
declare i1: int
return_type: void
body:
syscall("PRINT",i1)
end body
end function
end module

View File

@ -1,6 +1,7 @@
package org.jcnc.snow.compiler.backend.builder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.backend.utils.OpHelper;
import org.jcnc.snow.compiler.ir.core.IRFunction;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
@ -16,7 +17,7 @@ import java.util.stream.Collectors;
* 仅负责根据指令类型分发到对应的 {@link InstructionGenerator} 子类完成实际生成
* </p>
* <p>
* 工作流程简述:
* 工作流程简述:
* <ol>
* <li>接收一组已注册的 IR 指令生成器并建立类型到生成器的映射表</li>
* <li>遍历 IR 函数体的每条指令根据类型找到对应的生成器调用其 generate 方法生成 VM 指令</li>
@ -74,18 +75,26 @@ public final class VMCodeGenerator {
*/
public void generate(IRFunction fn) {
this.currentFn = fn.name();
out.beginFunction(currentFn); // 输出函数起始
/* 登记函数入口地址 —— 解决 CALL 未解析符号问题 */
out.beginFunction(currentFn);
/* 逐条分发 IR 指令给对应的生成器 */
for (IRInstruction ins : fn.body()) {
@SuppressWarnings("unchecked")
// 取得与当前 IR 指令类型匹配的生成器泛型强转消除类型警告
InstructionGenerator<IRInstruction> gen =
(InstructionGenerator<IRInstruction>) registry.get(ins.getClass());
if (gen == null) {
throw new IllegalStateException("Unsupported IR: " + ins);
}
// 通过多态分发到实际生成器
gen.generate(ins, out, slotMap, currentFn);
}
out.endFunction(); // 输出函数结束
/* 强制补上函数结尾的返回/终止指令 */
String retOpcode = "main".equals(currentFn) ? "HALT" : "RET";
out.emit(OpHelper.opcode(retOpcode));
/* 结束函数 */
out.endFunction();
}
}

View File

@ -4,53 +4,142 @@ import org.jcnc.snow.compiler.backend.builder.VMProgramBuilder;
import org.jcnc.snow.compiler.backend.core.InstructionGenerator;
import org.jcnc.snow.compiler.backend.utils.OpHelper;
import org.jcnc.snow.compiler.ir.common.GlobalFunctionTable;
import org.jcnc.snow.compiler.ir.core.IRValue;
import org.jcnc.snow.compiler.ir.instruction.CallInstruction;
import org.jcnc.snow.compiler.ir.value.IRConstant;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import org.jcnc.snow.vm.engine.VMOpCode;
import java.util.Map;
import java.util.*;
/**
* IR CallInstruction 生成 VM 指令
* <b>CallGenerator - IR {@code CallInstruction} 生成 VM 指令</b>
*
* <p>
* 本类负责将中间表示IR中的函数调用指令 {@link CallInstruction} 转换为虚拟机VM指令
* 支持普通函数调用和特殊的 syscall 指令转换
* </p>
*
* <p>
* <b>能力说明</b>
* <ul>
* <li>支持识别 {@code IRConstant} 直接字面量或已绑定到虚拟寄存器的字符串常量直接降级为 {@code SYSCALL &lt;SUBCMD&gt;} 指令</li>
* <li>对普通函数完成参数加载调用返回值保存等指令生成</li>
* </ul>
* </p>
*/
public class CallGenerator implements InstructionGenerator<CallInstruction> {
/**
* <虚拟寄存器 id, 对应的字符串常量>
* <p>用于记录虚拟寄存器与其绑定字符串常量的映射 {@link LoadConstGenerator} 在编译期间填充</p>
*/
private static final Map<Integer, String> STRING_CONST_POOL = new HashMap<>();
/**
* 注册字符串常量到虚拟寄存器
* <p> {@link LoadConstGenerator} 在加载字符串常量时调用</p>
*
* @param regId 虚拟寄存器 id
* @param value 字符串常量值
*/
public static void registerStringConst(int regId, String value) {
STRING_CONST_POOL.put(regId, value);
}
/**
* 返回本生成器支持的 IR 指令类型CallInstruction
*/
@Override
public Class<CallInstruction> supportedClass() {
return CallInstruction.class;
}
/**
* 生成 VM 指令的主逻辑
*
* @param ins 当前 IR 指令函数调用
* @param out 指令输出构建器
* @param slotMap IR 虚拟寄存器与物理槽位映射
* @param currentFn 当前函数名
*/
@Override
public void generate(CallInstruction ins,
VMProgramBuilder out,
Map<IRVirtualRegister, Integer> slotMap,
String currentFn) {
/* 1. 推断返回值类型(用于非 void 情况下的 I/F/D/L_STORE */
char retType = 'I';
/* ========== 特殊处理 syscall 调用 ========== */
if ("syscall".equals(ins.getFunctionName()) ||
ins.getFunctionName().endsWith(".syscall")) {
List<IRValue> args = ins.getArguments();
if (args.isEmpty()) {
throw new IllegalStateException("syscall 需要子命令参数");
}
// ---------- 0. 解析 syscall 子命令 ----------
// 子命令支持 IRConstant直接字面量或虚拟寄存器需已绑定字符串
String subcmd;
IRValue first = args.getFirst();
if (first instanceof IRConstant(Object value)) { // 直接字面量
if (!(value instanceof String s))
throw new IllegalStateException("syscall 第一个参数必须是字符串常量");
subcmd = s.toUpperCase(Locale.ROOT);
} else if (first instanceof IRVirtualRegister vr) { // 虚拟寄存器
// 从常量池中查找是否已绑定字符串
subcmd = Optional.ofNullable(STRING_CONST_POOL.get(vr.id()))
.orElseThrow(() ->
new IllegalStateException("未找到 syscall 字符串常量绑定: " + vr));
subcmd = subcmd.toUpperCase(Locale.ROOT);
} else {
throw new IllegalStateException("syscall 第一个参数必须是字符串常量");
}
// ---------- 1. 压栈其余 syscall 参数index 1 开始 ----------
for (int i = 1; i < args.size(); i++) {
IRVirtualRegister vr = (IRVirtualRegister) args.get(i);
int slotId = slotMap.get(vr);
char t = out.getSlotType(slotId);
if (t == '\0') t = 'I'; // 默认整型
out.emit(OpHelper.opcode(t + "_LOAD") + " " + slotId);
}
// ---------- 2. 生成 SYSCALL 指令 ----------
out.emit(VMOpCode.SYSCALL + " " + subcmd);
return; // syscall 无返回值直接返回
}
/* ========== 普通函数调用 ========== */
// ---------- 1. 推断返回值类型 void 返回时用 ----------
char retType = 'I'; // 默认为整型
if (!ins.getArguments().isEmpty()) {
int firstSlot = slotMap.get((IRVirtualRegister) ins.getArguments().getFirst());
retType = out.getSlotType(firstSlot);
if (retType == '\0') retType = 'I';
}
/* 2. 依次加载实参 */
// ---------- 2. 加载全部实参 ----------
for (var arg : ins.getArguments()) {
int slotId = slotMap.get((IRVirtualRegister) arg);
char t = out.getSlotType(slotId);
if (t == '\0') t = 'I';
if (t == '\0') t = 'I'; // 默认整型
out.emit(OpHelper.opcode(t + "_LOAD") + " " + slotId);
}
/* 3. 发出 CALL 指令 */
// ---------- 3. 发出 CALL 指令 ----------
out.emitCall(ins.getFunctionName(), ins.getArguments().size());
/* 3.5 若被调用函数返回 void则无需保存返回值 */
String rt = GlobalFunctionTable.getReturnType(ins.getFunctionName());
if ("void".equals(rt)) {
return; // 直接结束 _STORE
// ---------- 3.5 如果为 void 返回直接结束 ----------
if ("void".equals(GlobalFunctionTable.getReturnType(ins.getFunctionName()))) {
return;
}
/* 4. 保存返回值到目标槽 */
// ---------- 4. 保存返回值 ----------
int destSlot = slotMap.get(ins.getDest());
out.emit(OpHelper.opcode(retType + "_STORE") + " " + destSlot);
out.setSlotType(destSlot, retType);

View File

@ -10,16 +10,18 @@ import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.Map;
/**
* 常量加载指令生成器
* 该类用于生成将常量加载到虚拟机寄存器的指令包括 PUSH 常量值和 STORE 到指定槽位
* 并为每个槽位设置正确的类型前缀 'I', 'L', 'F'
* <b>LoadConstGenerator - IR {@code LoadConstInstruction} 生成 VM 指令</b>
*
* <p>
* 本类负责将 IR 层的常量加载指令 {@link LoadConstInstruction} 转换为对应的虚拟机指令
* 额外支持如果常量类型为 {@code String}会同步登记到
* {@link CallGenerator} 的字符串常量池方便 syscall 降级场景使用
* </p>
*/
public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruction> {
/**
* 返回本生成器支持的指令类型 LoadConstInstruction
*
* @return 支持的指令类型的 Class 对象
* 指定本生成器支持的 IR 指令类型LoadConstInstruction
*/
@Override
public Class<LoadConstInstruction> supportedClass() {
@ -27,49 +29,49 @@ public class LoadConstGenerator implements InstructionGenerator<LoadConstInstruc
}
/**
* 生成一条常量加载指令的目标虚拟机代码
* 生成 VM 指令主流程
*
* @param ins 当前要生成的 LoadConstInstruction 指令
* @param out VMProgramBuilder用于输出生成的虚拟机指令
* @param slotMap IR 虚拟寄存器到实际槽位编号的映射表
* @param currentFn 当前函数名如有需要可使用
* @param ins 当前常量加载指令
* @param out 指令输出构建器
* @param slotMap 虚拟寄存器与物理槽位映射
* @param currentFn 当前函数名
*/
@Override
public void generate(LoadConstInstruction ins,
VMProgramBuilder out,
Map<IRVirtualRegister, Integer> slotMap,
String currentFn) {
// 1. 获取常量值第一个操作数必为常量
/* 1. 获取常量值 */
IRConstant constant = (IRConstant) ins.operands().getFirst();
Object value = constant.value();
// 2. 生成 PUSH 指令将常量值推入操作数栈
// 通过 OpHelper 辅助方法获取合适的数据类型前缀
String pushOp = OpHelper.pushOpcodeFor(value);
out.emit(pushOp + " " + value);
/* 2. 生成 PUSH 指令,将常量值入栈 */
out.emit(OpHelper.pushOpcodeFor(value) + " " + value);
// 3. 生成 STORE 指令将栈顶的值存入对应槽位寄存器
// 同样通过 OpHelper 获取对应类型的 STORE 指令
String storeOp = OpHelper.storeOpcodeFor(value);
// 获取目标虚拟寄存器对应的槽位编号
/* 3. STORE 到目标槽位 */
int slot = slotMap.get(ins.dest());
out.emit(storeOp + " " + slot);
out.emit(OpHelper.storeOpcodeFor(value) + " " + slot);
// 4. 根据常量的 Java 类型为槽位设置正确的前缀字符
// 这样在后续类型检查/运行时可用常见前缀如 'I', 'L', 'F', 'D', 'S', 'B'
/* 4. 标记槽位数据类型(用于后续类型推断和 LOAD/STORE 指令选择) */
char prefix = switch (value) {
case Integer _ -> 'I'; // 整型
case Long _ -> 'L'; // 长整型
case Short _ -> 'S'; // 短整型
case Byte _ -> 'B'; // 字节型
case Double _ -> 'D'; // 双精度浮点型
case Float _ -> 'F'; // 单精度浮点型
case Boolean _ -> 'I'; // 布尔作为 0/1 整型存储
case null, default ->
throw new IllegalStateException("Unknown const type: " + (value != null ? value.getClass() : null));
case Integer _ -> 'I'; // 整型
case Long _ -> 'L'; // 长整型
case Short _ -> 'S'; // 短整型
case Byte _ -> 'B'; // 字节型
case Double _ -> 'D'; // 双精度
case Float _ -> 'F'; // 单精度
case Boolean _ -> 'I'; // 布尔类型用 I 处理
case String _ -> 'R'; // 字符串常量
case null, default -> // 其它类型异常
throw new IllegalStateException("未知的常量类型: "
+ (value != null ? value.getClass() : null));
};
// 写入槽位类型映射表
out.setSlotType(slot, prefix);
/* 5. 如果是字符串常量,则登记到 CallGenerator 的常量池,便于 syscall 字符串降级使用 */
if (value instanceof String s) {
CallGenerator.registerStringConst(ins.dest().id(), s);
}
}
}

View File

@ -167,6 +167,9 @@ public final class OpHelper {
map.put("D2I", Integer.toString(VMOpCode.D2I));
map.put("D2L", Integer.toString(VMOpCode.D2L));
map.put("D2F", Integer.toString(VMOpCode.D2F));
map.put("R_PUSH", Integer.toString(VMOpCode.R_PUSH));
map.put("R_LOAD", Integer.toString(VMOpCode.R_LOAD));
map.put("R_STORE", Integer.toString(VMOpCode.R_STORE));
map.put("POP", Integer.toString(VMOpCode.POP));
map.put("DUP", Integer.toString(VMOpCode.DUP));
map.put("SWAP", Integer.toString(VMOpCode.SWAP));
@ -176,11 +179,19 @@ public final class OpHelper {
map.put("MOV", Integer.toString(VMOpCode.MOV));
map.put("HALT", Integer.toString(VMOpCode.HALT));
map.put("SYSCALL", Integer.toString(VMOpCode.SYSCALL));
map.put("DEBUG_TRAP", Integer.toString(VMOpCode.DEBUG_TRAP));
// map.put("DEBUG_TRAP", Integer.toString(VMOpCode.DEBUG_TRAP));
OPCODE_MAP = Collections.unmodifiableMap(map);
Map<Integer, String> revmap = new HashMap<>(); // reverse map
OPCODE_MAP.forEach((key, value) -> revmap.put(Integer.parseInt(value), key));
OPCODE_MAP.forEach((key, value) -> {
int codeInt;
if (value.startsWith("0x") || value.startsWith("0X")) {
codeInt = Integer.parseInt(value.substring(2), 16);
} else {
codeInt = Integer.parseInt(value);
}
revmap.put(codeInt, key);
});
OPCODE_NAME_MAP = Collections.unmodifiableMap(revmap);
}
@ -222,11 +233,13 @@ public final class OpHelper {
if (v instanceof Byte) return "B";
if (v instanceof Double) return "D";
if (v instanceof Float) return "F";
if (v instanceof String) return "R"; //引用类型
throw new IllegalStateException("Unknown const type: " + v.getClass());
}
/**
* 根据 opcode 数值的字符串形式获取指令名
*
* @param code 字符串形式的 opcode 数值
* @return opcode 对应的指令名
*/
@ -236,6 +249,7 @@ public final class OpHelper {
/**
* 根据 opcode 获取指令名
*
* @param code opcode
* @return opcode 对应的指令名
*/

View File

@ -5,121 +5,117 @@ import org.jcnc.snow.compiler.ir.instruction.CallInstruction;
import org.jcnc.snow.compiler.ir.instruction.LoadConstInstruction;
import org.jcnc.snow.compiler.ir.instruction.UnaryOperationInstruction;
import org.jcnc.snow.compiler.ir.utils.ComparisonUtils;
import org.jcnc.snow.compiler.ir.utils.ExpressionUtils;
import org.jcnc.snow.compiler.ir.value.IRConstant;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import org.jcnc.snow.compiler.ir.utils.ExpressionUtils;
import org.jcnc.snow.compiler.parser.ast.*;
import org.jcnc.snow.compiler.parser.ast.base.ExpressionNode;
import java.util.*;
/**
* <b>表达式构建器</b>
* <b>ExpressionBuilder - 表达式 IR 构建器</b>
*
* <p>
* 该类负责将抽象语法树AST的表达式节点转换为中间表示IR指令和虚拟寄存器
* 是编译器IR生成阶段的核心工具
* <br/>
* 主要职责包括:
* 负责将 AST 表达式节点递归转换为 IR 虚拟寄存器操作并生成对应的 IR 指令序列
* 支持字面量标识符二元表达式一元表达式函数调用等多种类型表达式
* </p>
*
* <p>
* 主要功能
* <ul>
* <li>将数字字面量标识符二元表达式函数调用等AST表达式节点翻译为对应的IR指令序列</li>
* <li>管理并分配虚拟寄存器保证IR操作的数据流正确</li>
* <li>将表达式节点映射为虚拟寄存器</li>
* <li>为每种表达式类型生成对应 IR 指令</li>
* <li>支持表达式嵌套的递归构建</li>
* <li>支持写入指定目标寄存器避免冗余的 move 指令</li>
* </ul>
* <p>
* </p>
*/
public record ExpressionBuilder(IRContext ctx) {
/**
* 构建并返回某个表达式节点对应的虚拟寄存器
*
* <p>会根据节点的实际类型分别处理:
* <ul>
* <li>数字字面量: 新建常量寄存器</li>
* <li>布尔字面量: 生成值为 0 1 的常量寄存器</li>
* <li>标识符: 查找当前作用域中的寄存器</li>
* <li>二元表达式: 递归处理子表达式并进行相应运算</li>
* <li>一元运算符:
* <ul>
* <li><code>-x</code>取负生成 <code>NEG_I32</code> 指令</li>
* <li>code>!x</code>逻辑非转换为 <code>x == 0</code> 比较指令</li>
* </ul>
* </li>
* <li>函数调用: 生成对应的Call指令</li>
* <li>其它类型不支持抛出异常</li>
* </ul>
*
* @param expr 要转换的表达式AST节点
* @return 该表达式的计算结果寄存器
* @throws IllegalStateException 如果遇到未定义的标识符或不支持的表达式类型
*/
/* ───────────────── 顶层入口 ───────────────── */
/**
* 构建任意 AST 表达式节点自动为其分配一个新的虚拟寄存器并返回该寄存器
*
* <p>
* 这是表达式 IR 生成的核心入口它会根据不同的表达式类型进行分派递归构建 IR 指令
* </p>
*
* @param expr 任意 AST 表达式节点
* @return 存储该表达式结果的虚拟寄存器
* @throws IllegalStateException 遇到不支持的表达式类型或未定义标识符
*/
public IRVirtualRegister build(ExpressionNode expr) {
return switch (expr) {
// 数字字面量
// 数字字面量例如 1233.14
case NumberLiteralNode n -> buildNumberLiteral(n.value());
// 布尔字面量
// 字符串字面量例如 "abc"
case StringLiteralNode s -> buildStringLiteral(s.value());
// 布尔字面量例如 true / false
case BoolLiteralNode b -> buildBoolLiteral(b.getValue());
// 标识符
// 标识符变量名 ab
case IdentifierNode id -> {
// 查找当前作用域中的变量寄存器
IRVirtualRegister reg = ctx.getScope().lookup(id.name());
if (reg == null)
throw new IllegalStateException("未定义标识符: " + id.name());
yield reg;
}
// 二元表达式
// 二元表达式 a+b, x==y
case BinaryExpressionNode bin -> buildBinary(bin);
// 函数调用
case CallExpressionNode call -> buildCall(call);
case UnaryExpressionNode u -> buildUnary(u);
// 函数/方法调用表达式
case CallExpressionNode call -> buildCall(call);
// 一元表达式 -a, !a
case UnaryExpressionNode un -> buildUnary(un);
// 默认分支遇到未知表达式类型则直接抛异常
default -> throw new IllegalStateException(
"不支持的表达式类型: " + expr.getClass().getSimpleName());
};
}
/** 处理一元表达式 */
private IRVirtualRegister buildUnary(UnaryExpressionNode un) {
String op = un.operator();
IRVirtualRegister val = build(un.operand());
// -x NEG_*根据类型自动选择位宽
if (op.equals("-")) {
IRVirtualRegister dest = ctx.newRegister();
IROpCode code = ExpressionUtils.negOp(un.operand());
ctx.addInstruction(new UnaryOperationInstruction(code, dest, val));
return dest;
}
// !x (x == 0)
if (op.equals("!")) {
IRVirtualRegister zero = InstructionFactory.loadConst(ctx, 0);
return InstructionFactory.binOp(ctx, IROpCode.CMP_IEQ, val, zero);
}
throw new IllegalStateException("未知一元运算符: " + op);
}
/* ───────────────── 写入指定寄存器 ───────────────── */
/**
* 直接将表达式计算结果写入指定的目标寄存器dest
* <p>
* {@link #build(ExpressionNode)}类似但支持目标寄存器复用避免不必要的move
* 生成表达式并将其结果直接写入目标寄存器避免冗余的 move 操作
*
* @param node 表达式AST节点
* @param dest 目标寄存器
* @throws IllegalStateException 未定义标识符/不支持的表达式类型时报错
* <p>
* 某些简单表达式如字面量变量名可以直接写入目标寄存器复杂表达式则会先 build 到新寄存器 move 到目标寄存器
* </p>
*
* @param node 要生成的表达式节点
* @param dest 目标虚拟寄存器用于存储结果
*/
public void buildInto(ExpressionNode node, IRVirtualRegister dest) {
switch (node) {
// 数字字面量直接加载到目标寄存器
// 数字字面量生成 loadConst 指令写入目标寄存器
case NumberLiteralNode n ->
InstructionFactory.loadConstInto(ctx, dest, ExpressionUtils.buildNumberConstant(ctx, n.value()));
// 标识符查找并move到目标寄存器
InstructionFactory.loadConstInto(
ctx, dest, ExpressionUtils.buildNumberConstant(ctx, n.value()));
// 字符串字面量生成 loadConst 指令写入目标寄存器
case StringLiteralNode s ->
InstructionFactory.loadConstInto(
ctx, dest, new IRConstant(s.value()));
// 布尔字面量转换为 int 1/0生成 loadConst
case BoolLiteralNode b ->
InstructionFactory.loadConstInto(
ctx, dest, new IRConstant(b.getValue() ? 1 : 0));
// 标识符查表获取原寄存器然后 move 到目标寄存器
case IdentifierNode id -> {
IRVirtualRegister src = ctx.getScope().lookup(id.name());
if (src == null) throw new IllegalStateException("未定义标识符: " + id.name());
if (src == null)
throw new IllegalStateException("未定义标识符: " + id.name());
InstructionFactory.move(ctx, src, dest);
}
// 二元表达式直接写入目标寄存器
// 二元表达式递归生成并写入目标寄存器
case BinaryExpressionNode bin -> buildBinaryInto(bin, dest);
// 其他表达式先递归生成寄存器再move到目标寄存器
// 其它复杂情况 build 到新寄存器 move 到目标寄存器
default -> {
IRVirtualRegister tmp = build(node);
InstructionFactory.move(ctx, tmp, dest);
@ -127,41 +123,100 @@ public record ExpressionBuilder(IRContext ctx) {
}
}
/* ───────────────── 具体表达式类型 ───────────────── */
/**
* 构建二元表达式的IR生成新寄存器存储结果
* <p>
* 先递归构建左右操作数之后根据操作符类别算术或比较决定生成的IR操作码
* 并生成对应的二元运算指令
* 一元表达式构建
*
* @param bin 二元表达式节点
* @return 存放结果的虚拟寄存器
* <p>
* 支持算术取负-a逻辑非!a等一元运算符
* </p>
*
* @param un 一元表达式节点
* @return 结果存储的新分配虚拟寄存器
*/
private IRVirtualRegister buildBinary(BinaryExpressionNode bin) {
String op = bin.operator();
IRVirtualRegister left = build(bin.left());
IRVirtualRegister right = build(bin.right());
private IRVirtualRegister buildUnary(UnaryExpressionNode un) {
// 递归生成操作数的寄存器
IRVirtualRegister src = build(un.operand());
// 分配目标寄存器
IRVirtualRegister dest = ctx.newRegister();
// 1. 比较运算
if (ComparisonUtils.isComparisonOperator(op)) {
return InstructionFactory.binOp(
ctx,
ComparisonUtils.cmpOp(ctx.getScope().getVarTypes(), op, bin.left(), bin.right()),
left, right);
switch (un.operator()) {
// 算术负号生成取负指令
case "-" -> ctx.addInstruction(
new UnaryOperationInstruction(ExpressionUtils.negOp(un.operand()), dest, src));
// 逻辑非等价于 a == 0生成比较指令
case "!" -> {
IRVirtualRegister zero = InstructionFactory.loadConst(ctx, 0);
return InstructionFactory.binOp(ctx, IROpCode.CMP_IEQ, src, zero);
}
// 其它一元运算符不支持抛异常
default -> throw new IllegalStateException("未知一元运算符: " + un.operator());
}
// 2. 其他算术 / 逻辑运算
IROpCode code = ExpressionUtils.resolveOpCode(op, bin.left(), bin.right());
if (code == null) throw new IllegalStateException("不支持的运算符: " + op);
return InstructionFactory.binOp(ctx, code, left, right);
return dest;
}
/**
* 将二元表达式的结果直接写入指定寄存器dest
* <p>
* 结构与{@link #buildBinary(BinaryExpressionNode)}类似但不会新分配寄存器
* 构建函数或方法调用表达式
*
* @param call AST 调用表达式节点
* @return 存储调用结果的虚拟寄存器
*/
private IRVirtualRegister buildCall(CallExpressionNode call) {
// 递归生成所有参数实参对应的寄存器
List<IRVirtualRegister> argv = call.arguments().stream().map(this::build).toList();
// 解析被调用目标名支持普通函数/成员方法
String callee = switch (call.callee()) {
// 成员方法调用例如 obj.foo()
case MemberExpressionNode m when m.object() instanceof IdentifierNode id
-> id.name() + "." + m.member();
// 普通函数调用
case IdentifierNode id -> id.name();
// 其它情况暂不支持
default -> throw new IllegalStateException("不支持的调用目标: " + call.callee().getClass().getSimpleName());
};
// 为返回值分配新寄存器生成 Call 指令
IRVirtualRegister dest = ctx.newRegister();
ctx.addInstruction(new CallInstruction(dest, callee, new ArrayList<>(argv)));
return dest;
}
/**
* 二元表达式构建结果存储到新寄存器
* <br>
* 支持算术位运算与比较==, !=, >, <, ...
*
* @param bin 二元表达式节点
* @param dest 目标寄存器
* @return 存储表达式结果的虚拟寄存器
*/
private IRVirtualRegister buildBinary(BinaryExpressionNode bin) {
// 递归生成左右子表达式的寄存器
IRVirtualRegister a = build(bin.left());
IRVirtualRegister b = build(bin.right());
String op = bin.operator();
// 比较运算符==!=>< 需要生成条件跳转或布尔值寄存器
if (ComparisonUtils.isComparisonOperator(op)) {
return InstructionFactory.binOp(
ctx,
// 通过比较工具获得合适的 IR 操作码
ComparisonUtils.cmpOp(ctx.getScope().getVarTypes(), op, bin.left(), bin.right()),
a, b);
}
// 其它算术/位运算
IROpCode code = ExpressionUtils.resolveOpCode(op, bin.left(), bin.right());
if (code == null) throw new IllegalStateException("不支持的运算符: " + op);
return InstructionFactory.binOp(ctx, code, a, b);
}
/**
* 二元表达式构建结果直接写入目标寄存器用于赋值左值等优化场景
*
* @param bin 二元表达式节点
* @param dest 目标虚拟寄存器
*/
private void buildBinaryInto(BinaryExpressionNode bin, IRVirtualRegister dest) {
IRVirtualRegister a = build(bin.left());
@ -180,54 +235,44 @@ public record ExpressionBuilder(IRContext ctx) {
}
}
/**
* 处理函数调用表达式生成对应的Call指令和目标寄存器
* <p>
* 支持普通标识符调用和成员调用 mod.func会为每个参数依次生成子表达式的寄存器
*
* @param call 调用表达式AST节点
* @return 返回结果存放的寄存器
*/
private IRVirtualRegister buildCall(CallExpressionNode call) {
// 递归构建所有参数的寄存器
List<IRVirtualRegister> argv = call.arguments().stream()
.map(this::build)
.toList();
// 获取完整调用目标名称支持成员/模块调用和普通调用
String fullName = switch (call.callee()) {
case MemberExpressionNode member when member.object() instanceof IdentifierNode _ ->
((IdentifierNode)member.object()).name() + "." + member.member();
case IdentifierNode id -> id.name();
default -> throw new IllegalStateException("不支持的调用目标: " + call.callee().getClass().getSimpleName());
};
// 申请目标寄存器
IRVirtualRegister dest = ctx.newRegister();
// 添加Call指令到IR上下文
ctx.addInstruction(new CallInstruction(dest, fullName, new ArrayList<>(argv)));
return dest;
}
/* ───────────────── 字面量辅助方法 ───────────────── */
/**
* 处理数字字面量生成常量寄存器和加载指令
* <p>
* 会将字符串型字面量 "123", "1.0f"解析为具体的IRConstant
* 并分配一个新的虚拟寄存器来存放该常量
* 构建数字字面量表达式 123分配新寄存器并生成 LoadConst 指令
*
* @param value 字面量字符串
* @return 放该常量的寄存器
* @param value 字面量文本字符串格式
* @return 存储该字面量的寄存器
*/
private IRVirtualRegister buildNumberLiteral(String value) {
IRConstant constant = ExpressionUtils.buildNumberConstant(ctx, value);
IRVirtualRegister reg = ctx.newRegister();
ctx.addInstruction(new LoadConstInstruction(reg, constant));
return reg;
IRConstant c = ExpressionUtils.buildNumberConstant(ctx, value);
IRVirtualRegister r = ctx.newRegister();
ctx.addInstruction(new LoadConstInstruction(r, c));
return r;
}
/** 布尔字面量 → CONST true=1false=0*/
private IRVirtualRegister buildBoolLiteral(boolean value) {
IRConstant constant = new IRConstant(value ? 1 : 0);
IRVirtualRegister reg = ctx.newRegister();
ctx.addInstruction(new LoadConstInstruction(reg, constant));
return reg;
/**
* 构建字符串字面量表达式分配新寄存器并生成 LoadConst 指令
*
* @param value 字符串内容
* @return 存储该字符串的寄存器
*/
private IRVirtualRegister buildStringLiteral(String value) {
IRConstant c = new IRConstant(value);
IRVirtualRegister r = ctx.newRegister();
ctx.addInstruction(new LoadConstInstruction(r, c));
return r;
}
/**
* 构建布尔字面量表达式true/false分配新寄存器并生成 LoadConst 指令1 表示 true0 表示 false
*
* @param v 布尔值
* @return 存储 1/0 的寄存器
*/
private IRVirtualRegister buildBoolLiteral(boolean v) {
IRConstant c = new IRConstant(v ? 1 : 0);
IRVirtualRegister r = ctx.newRegister();
ctx.addInstruction(new LoadConstInstruction(r, c));
return r;
}
}

View File

@ -20,7 +20,7 @@ import java.util.List;
* <p>
* 它负责处理类似 {@code callee(arg1, arg2, ...)} 形式的调用表达式执行如下操作:
* <ul>
* <li>识别调用目标支持模块成员函数调用和当前模块函数调用</li>
* <li>识别调用目标支持模块成员函数调用和当前模块函数调用也支持自动在所有已导入模块中查找唯一同名函数</li>
* <li>根据被调用函数的参数签名检查实参数量和类型的兼容性</li>
* <li>支持数值参数的宽化转换 int double</li>
* <li>支持数值到字符串的隐式转换自动视为调用 {@code to_string}</li>
@ -77,8 +77,38 @@ public class CallExpressionAnalyzer implements ExpressionAnalyzer<CallExpression
return BuiltinType.INT;
}
// 查找目标函数签名
// 查找目标函数签名先在当前模块/显式模块查找
FunctionType ft = target.getFunctions().get(functionName);
// 如果当前模块未找到再自动遍历所有已导入模块寻找唯一同名函数
if (ft == null && target == mi) {
ModuleInfo foundModule = null;
FunctionType foundType = null;
for (String importName : mi.getImports()) {
ModuleInfo imported = ctx.getModules().get(importName);
if (imported == null) continue;
FunctionType candidate = imported.getFunctions().get(functionName);
if (candidate != null) {
if (foundModule != null) {
// 多个导入模块含有同名函数二义性报错
ctx.getErrors().add(new SemanticError(callee,
"函数调用不明确: " + functionName +
" 同时存在于模块 " + foundModule.getName() +
"" + imported.getName()));
ctx.log("错误: 函数调用不明确 " + functionName);
return BuiltinType.INT;
}
foundModule = imported;
foundType = candidate;
}
}
if (foundType != null) {
target = foundModule;
ft = foundType;
}
}
// 最终未找到则报错
if (ft == null) {
ctx.getErrors().add(new SemanticError(callee,
"函数未定义: " + functionName));

View File

@ -1,54 +1,77 @@
package org.jcnc.snow.compiler.semantic.core;
import org.jcnc.snow.compiler.semantic.type.*;
import org.jcnc.snow.compiler.semantic.type.BuiltinType;
import org.jcnc.snow.compiler.semantic.type.FunctionType;
import org.jcnc.snow.compiler.semantic.type.Type;
import java.util.Map;
import java.util.*;
/**
* {@code BuiltinTypeRegistry} 是内置类型和内置模块的集中注册中心
* <b>BuiltinTypeRegistry - 语言全部内置类型/模块/函数注册中心</b>
*
* <p>
* 本类主要负责:
* 该类统一注册编译器需要用到的所有基础类型标准库模块与内核函数供语义分析及类型检查阶段使用
* <ul>
* <li>定义语言中所有可识别的基础类型 intfloatstring </li>
* <li>在语义分析初始化时将内置模块 {@code BuiltinUtils}注册到上下文中</li>
* <li>提供对内置类型的快速查找支持</li>
* <li>所有基础类型byteshortintlongfloatdoublestringbooleanvoid</li>
* <li>标准库模块 <b>BuiltinUtils</b>仅注册函数签名具体实现由 Snow 语言源码实现</li>
* <li>内核函数 <b>syscall</b>供标准库内部实现调用</li>
* </ul>
* 该类为纯工具类所有成员均为静态不可实例化
* </p>
*/
public final class BuiltinTypeRegistry {
/**
* 内置类型映射表: 将类型名称字符串映射到对应的 {@link Type} 实例
* <b>基础类型表</b>类型名称 Type 实例
* <p>
* 用于类型解析过程如解析变量声明或函数返回类型
* 将用户源码中的类型字符串转换为语义类型对象
* Map 静态初始化注册所有 Snow 语言基础类型供类型检查与类型推断使用
* </p>
*/
public static final Map<String, Type> BUILTIN_TYPES = Map.of(
"int", BuiltinType.INT,
"long", BuiltinType.LONG,
"short", BuiltinType.SHORT,
"byte", BuiltinType.BYTE,
"float", BuiltinType.FLOAT,
"double", BuiltinType.DOUBLE,
"string", BuiltinType.STRING,
"boolean", BuiltinType.BOOLEAN,
"void", BuiltinType.VOID
);
public static final Map<String, Type> BUILTIN_TYPES;
static {
Map<String, Type> t = new HashMap<>();
t.put("byte", BuiltinType.BYTE); // 字节型
t.put("short", BuiltinType.SHORT); // 短整型
t.put("int", BuiltinType.INT); // 整型
t.put("long", BuiltinType.LONG); // 长整型
t.put("float", BuiltinType.FLOAT); // 单精度浮点
t.put("double", BuiltinType.DOUBLE); // 双精度浮点
t.put("string", BuiltinType.STRING); // 字符串
t.put("void", BuiltinType.VOID); // 无返回
t.put("boolean", BuiltinType.BOOLEAN); // 布尔类型
BUILTIN_TYPES = Collections.unmodifiableMap(t); // 不可变映射防止被意外更改
}
/**
* 私有构造函数禁止实例化
* 私有构造方法禁止实例化
*/
private BuiltinTypeRegistry() { }
/**
* 初始化语义上下文中与内置模块相关的内容
* <p>
* 当前实现将内置模块 {@code BuiltinUtils} 注册至上下文模块表中
* 使其在用户代码中可被访问 {@code BuiltinUtils.to_string(...)}
* <b>初始化内置模块和函数声明</b>
*
* @param ctx 当前语义分析上下文
* <p>
* 语义分析阶段调用将所有基础模块与函数声明注册到语义上下文中
* - 目前注册 BuiltinUtils 标准库模块仅注册签名不负责具体实现
* - syscall 函数注册到 BuiltinUtils 供标准库内部调用
* </p>
*
* @param ctx 全局语义分析上下文持有模块表
*/
public static void init(Context ctx) {
/* ---------- 注册标准库 os ---------- */
ModuleInfo utils = new ModuleInfo("os");
// syscall(string, int): void 供标准库内部使用的调用接口
utils.getFunctions().put(
"syscall",
new FunctionType(
Arrays.asList(BuiltinType.STRING, BuiltinType.INT),
BuiltinType.VOID
)
);
// 注册 BuiltinUtils 到上下文的模块表若已存在则不重复添加
ctx.getModules().putIfAbsent("os", utils);
}
}

View File

@ -220,6 +220,7 @@ public final class CompileTask implements Task {
if (runAfterCompile) {
System.out.println("\n=== Launching VM ===");
VMLauncher.main(new String[]{outputFile.toString()});
System.out.println("\n=== VM exited ===");
}
return 0;

View File

@ -5,12 +5,48 @@ import org.jcnc.snow.vm.interfaces.Command;
import org.jcnc.snow.vm.module.*;
/**
* CALL addr nArgs pushes a new stack-frame, transfers the specified
* argument count from the operand stack into the callees local slots
* 0n-1 (left-to-right), then jumps to {@code addr}.
* The CallCommand class implements the {@link Command} interface and represents a subroutine/function call
* instruction in the virtual machine.
* <p>
* This command facilitates method invocation by creating a new stack frame, transferring arguments
* from the operand stack to the callee's local variable store, and jumping to the specified target address.
* </p>
*
* <p>Specific behavior:</p>
* <ul>
* <li>Parses the target address and the number of arguments from the instruction parameters.</li>
* <li>Validates the operands and checks for correct argument count.</li>
* <li>Builds a new local variable store for the callee and transfers arguments from the operand stack
* (left-to-right order, where the top of the stack is the last argument).</li>
* <li>Pushes a new stack frame onto the call stack, saving the return address and local variables.</li>
* <li>Jumps to the specified target address to begin execution of the callee function.</li>
* <li>If any error occurs (e.g., malformed operands, stack underflow), an exception is thrown.</li>
* </ul>
*/
public class CallCommand implements Command {
/**
* Executes the CALL instruction, initiating a subroutine/function call within the virtual machine.
* <p>
* This method handles the creation of a new stack frame for the callee, argument passing,
* and control transfer to the target function address.
* </p>
*
* @param parts The instruction parameters. Must include:
* <ul>
* <li>{@code parts[0]}: The "CALL" operator.</li>
* <li>{@code parts[1]}: The target address of the callee function.</li>
* <li>{@code parts[2]}: The number of arguments to pass.</li>
* </ul>
* @param currentPC The current program counter, used to record the return address for after the call.
* @param operandStack The operand stack manager. Arguments are popped from this stack.
* @param callerLVS The local variable store of the caller function (not directly modified here).
* @param callStack The virtual machine's call stack manager, used to push the new stack frame.
* @return The new program counter value, which is the address of the callee function (i.e., jump target).
* The VM should transfer control to this address after setting up the call frame.
* @throws IllegalArgumentException If the instruction parameters are malformed or missing.
* @throws IllegalStateException If the operand stack does not contain enough arguments.
*/
@Override
public int execute(String[] parts,
int currentPC,
@ -44,7 +80,7 @@ public class CallCommand implements Command {
new MethodContext("subroutine@" + targetAddr, null));
callStack.pushFrame(newFrame);
System.out.println("Calling function at address: " + targetAddr);
System.out.println("\nCalling function at address: " + targetAddr);
return targetAddr; // jump
}
}

View File

@ -44,7 +44,7 @@ public class RetCommand implements Command {
finished.getLocalVariableStore().clearVariables();
int returnAddr = finished.getReturnAddress();
System.out.println("Return " + returnAddr);
System.out.println("\nReturn " + returnAddr);
return returnAddr;
}
}

View File

@ -0,0 +1,54 @@
package org.jcnc.snow.vm.commands.ref.control;
import org.jcnc.snow.vm.interfaces.Command;
import org.jcnc.snow.vm.module.CallStack;
import org.jcnc.snow.vm.module.LocalVariableStore;
import org.jcnc.snow.vm.module.OperandStack;
/**
* The {@code RLoadCommand} class implements the {@link Command} interface and represents the
* reference load instruction ({@code R_LOAD}) in the virtual machine.
*
* <p>
* This instruction loads a reference object from the current stack frames local variable store
* at the specified slot and pushes it onto the operand stack.
* </p>
*
* <p>Instruction format: {@code R_LOAD <slot>}</p>
* <ul>
* <li>{@code <slot>}: The index in the local variable table to load the reference from.</li>
* </ul>
*
* <p>Behavior:</p>
* <ul>
* <li>Parses the slot index from the instruction parameters.</li>
* <li>Fetches the reference object stored at the specified slot in the current stack frame's local variable store.</li>
* <li>Pushes the fetched reference onto the operand stack.</li>
* <li>Increments the program counter to the next instruction.</li>
* </ul>
*/
public final class RLoadCommand implements Command {
/**
* Executes the {@code R_LOAD} instruction, loading a reference from the local variable table and pushing it onto the operand stack.
*
* @param parts The instruction parameters. {@code parts[0]} is the operator ("R_LOAD"), {@code parts[1]} is the slot index.
* @param pc The current program counter value, indicating the instruction address being executed.
* @param stack The operand stack manager. The loaded reference will be pushed onto this stack.
* @param lvs The local variable store. (Not used directly, as this command uses the store from the current stack frame.)
* @param cs The call stack manager. The reference will be loaded from the local variable store of the top stack frame.
* @return The next program counter value ({@code pc + 1}), pointing to the next instruction.
* @throws NumberFormatException if the slot parameter cannot be parsed as an integer.
*/
@Override
public int execute(String[] parts, int pc,
OperandStack stack,
LocalVariableStore lvs,
CallStack cs) {
int slot = Integer.parseInt(parts[1]);
Object v = cs.peekFrame().getLocalVariableStore().getVariable(slot);
stack.push(v);
return pc + 1;
}
}

View File

@ -0,0 +1,63 @@
package org.jcnc.snow.vm.commands.ref.control;
import org.jcnc.snow.vm.interfaces.Command;
import org.jcnc.snow.vm.module.CallStack;
import org.jcnc.snow.vm.module.LocalVariableStore;
import org.jcnc.snow.vm.module.OperandStack;
/**
* The {@code RPushCommand} class implements the {@link Command} interface and represents the
* reference push instruction ({@code R_PUSH}) in the virtual machine.
*
* <p>
* This instruction pushes a reference object, such as a String literal, onto the operand stack.
* </p>
*
* <p>Instruction format: {@code R_PUSH <literal>}</p>
* <ul>
* <li>{@code <literal>}: The reference value (e.g., string) to be pushed onto the stack.
* If the literal contains spaces, all parts after {@code R_PUSH} are joined into a single string.</li>
* </ul>
*
* <p>Behavior:</p>
* <ul>
* <li>Checks that the instruction has at least one parameter after the operator.</li>
* <li>Concatenates all parameters after {@code R_PUSH} into a single string (separated by spaces).</li>
* <li>Pushes the resulting string as a reference object onto the operand stack.</li>
* <li>Increments the program counter to the next instruction.</li>
* <li>Throws an {@code IllegalStateException} if the instruction is missing required parameters.</li>
* </ul>
*/
public final class RPushCommand implements Command {
/**
* Executes the {@code R_PUSH} instruction, pushing a reference (such as a string literal)
* onto the operand stack.
*
* @param parts The instruction parameters. {@code parts[0]} is the operator ("R_PUSH"),
* {@code parts[1..]} are the parts of the literal to be concatenated and pushed.
* @param pc The current program counter value, indicating the instruction address being executed.
* @param stack The operand stack manager. The literal will be pushed onto this stack.
* @param lvs The local variable store. (Not used in this instruction.)
* @param cs The call stack manager. (Not used in this instruction.)
* @return The next program counter value ({@code pc + 1}), pointing to the next instruction.
* @throws IllegalStateException if the instruction is missing required parameters.
*/
@Override
public int execute(String[] parts, int pc,
OperandStack stack,
LocalVariableStore lvs,
CallStack cs) {
if (parts.length < 2)
throw new IllegalStateException("R_PUSH missing parameter");
StringBuilder sb = new StringBuilder();
for (int i = 1; i < parts.length; i++) {
if (i > 1) sb.append(' ');
sb.append(parts[i]);
}
stack.push(sb.toString());
return pc + 1;
}
}

View File

@ -0,0 +1,58 @@
package org.jcnc.snow.vm.commands.ref.control;
import org.jcnc.snow.vm.interfaces.Command;
import org.jcnc.snow.vm.module.CallStack;
import org.jcnc.snow.vm.module.LocalVariableStore;
import org.jcnc.snow.vm.module.OperandStack;
/**
* The {@code RStoreCommand} class implements the {@link Command} interface and represents the
* reference store instruction ({@code R_STORE}) in the virtual machine.
*
* <p>
* This instruction pops a reference object from the top of the operand stack
* and stores it in the local variable table at the specified slot index of the current stack frame.
* </p>
*
* <p>Instruction format: {@code R_STORE <slot>}</p>
* <ul>
* <li>{@code <slot>}: The index in the local variable table where the reference will be stored.</li>
* </ul>
*
* <p>Behavior:</p>
* <ul>
* <li>Parses the slot index from the instruction parameters.</li>
* <li>Pops a reference object from the operand stack.</li>
* <li>Stores the popped reference object into the local variable table of the current stack frame at the specified slot.</li>
* <li>Increments the program counter to the next instruction.</li>
* <li>If the operand stack is empty, a {@code java.util.EmptyStackException} may be thrown.</li>
* </ul>
*/
public final class RStoreCommand implements Command {
/**
* Executes the {@code R_STORE} instruction, storing a reference object from the top of the operand stack
* into the local variable table of the current stack frame.
*
* @param parts The instruction parameters. {@code parts[0]} is the operator ("R_STORE"),
* {@code parts[1]} is the slot index.
* @param pc The current program counter value, indicating the instruction address being executed.
* @param stack The operand stack manager. The reference object will be popped from this stack.
* @param lvs The local variable store. (Not used directly, as the store from the current stack frame is used.)
* @param cs The call stack manager. The reference will be stored in the local variable store of the top stack frame.
* @return The next program counter value ({@code pc + 1}), pointing to the next instruction.
* @throws NumberFormatException if the slot parameter cannot be parsed as an integer.
* @throws java.util.EmptyStackException if the operand stack is empty when popping.
*/
@Override
public int execute(String[] parts, int pc,
OperandStack stack,
LocalVariableStore lvs,
CallStack cs) {
int slot = Integer.parseInt(parts[1]);
Object v = stack.pop();
cs.peekFrame().getLocalVariableStore().setVariable(slot, v);
return pc + 1;
}
}

View File

@ -59,7 +59,7 @@ public class HaltCommand implements Command {
@Override
public int execute(String[] parts, int currentPC, OperandStack operandStack, LocalVariableStore localVariableStore, CallStack callStack) {
// Output the termination message
LoggingUtils.logInfo("Process has ended", "\n");
LoggingUtils.logInfo("\nProcess has ended", "");
// Return -1 to indicate the program termination, and the virtual machine will not continue executing subsequent instructions
return -1;

View File

@ -0,0 +1,340 @@
package org.jcnc.snow.vm.commands.system.control;
import org.jcnc.snow.vm.interfaces.Command;
import org.jcnc.snow.vm.io.FDTable;
import org.jcnc.snow.vm.module.CallStack;
import org.jcnc.snow.vm.module.LocalVariableStore;
import org.jcnc.snow.vm.module.OperandStack;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.OpenOption;
import java.nio.file.Paths;
import java.util.*;
import static java.nio.file.StandardOpenOption.*;
/**
* SyscallCommand 虚拟机系统调用SYSCALL指令实现
*
* <p>
* 本类负责将虚拟机指令集中的 SYSCALL 进行分派模拟现实系统常见的文件网络管道标准输出等操作
* 通过操作数栈完成参数返回值传递并借助文件描述符表FDTable进行底层资源管理
* 所有 I/O 相关功能均基于 Java NIO 实现兼容多种 I/O 场景
* </p>
*
* <p>参数与栈约定:</p>
* <ul>
* <li>所有调用参数均按右值先入左值后入顺序压入 {@link OperandStack}</li>
* <li>SYSCALL 指令自动弹出参数并处理结果返回值如描述符读取长度是否成功等压回栈顶</li>
* </ul>
*
* <p>异常与失败处理:</p>
* <ul>
* <li>系统调用失败或遇到异常时均向操作数栈压入 {@code -1}以便调用者统一检测</li>
* </ul>
*
* <p>支持的子命令示例:</p>
* <ul>
* <li>PRINT / PRINTLN 控制台输出</li>
* <li>OPEN / CLOSE / READ / WRITE / SEEK 文件相关操作</li>
* <li>PIPE / DUP 管道与文件描述符复制</li>
* <li>SOCKET / CONNECT / BIND / LISTEN / ACCEPT 网络通信</li>
* <li>SELECT 多通道 I/O 就绪检测</li>
* </ul>
*/
public class SyscallCommand implements Command {
/**
* 分发并执行 SYSCALL 子命令根据子命令类型从操作数栈取出参数操作底层资源并将结果压回栈顶
*
* @param parts 指令及子命令参数分割数组parts[1]为子命令名
* @param pc 当前指令计数器
* @param stack 操作数栈
* @param locals 局部变量表
* @param callStack 调用栈
* @return 下一条指令的 pc 通常为 pc+1
* @throws IllegalArgumentException 缺少子命令参数时抛出
* @throws UnsupportedOperationException 不支持的 SYSCALL 子命令时抛出
*/
@Override
public int execute(String[] parts, int pc,
OperandStack stack,
LocalVariableStore locals,
CallStack callStack) {
if (parts.length < 2) {
throw new IllegalArgumentException("SYSCALL missing subcommand");
}
String cmd = parts[1].toUpperCase(Locale.ROOT);
try {
switch (cmd) {
// 文件相关操作
case "OPEN" -> {
int mode = (Integer) stack.pop();
int flags = (Integer) stack.pop();
String path = String.valueOf(stack.pop());
FileChannel fc = FileChannel.open(Paths.get(path), flagsToOptions(flags));
stack.push(FDTable.register(fc));
}
case "CLOSE" -> {
int fd = (Integer) stack.pop();
FDTable.close(fd);
stack.push(0);
}
case "READ" -> {
int count = (Integer) stack.pop();
int fd = (Integer) stack.pop();
Channel ch = FDTable.get(fd);
if (!(ch instanceof ReadableByteChannel rch)) {
stack.push(new byte[0]);
break;
}
ByteBuffer buf = ByteBuffer.allocate(count);
int n = rch.read(buf);
if (n < 0) n = 0;
buf.flip();
byte[] out = new byte[n];
buf.get(out);
stack.push(out);
}
case "WRITE" -> {
Object dataObj = stack.pop();
int fd = (Integer) stack.pop();
byte[] data = (dataObj instanceof byte[] b)
? b
: String.valueOf(dataObj).getBytes();
Channel ch = FDTable.get(fd);
if (!(ch instanceof WritableByteChannel wch)) {
stack.push(-1);
break;
}
int written = wch.write(ByteBuffer.wrap(data));
stack.push(written);
}
case "SEEK" -> {
int whence = (Integer) stack.pop();
long off = ((Number) stack.pop()).longValue();
int fd = (Integer) stack.pop();
Channel ch = FDTable.get(fd);
if (!(ch instanceof SeekableByteChannel sbc)) {
stack.push(-1);
break;
}
SeekableByteChannel newPos = switch (whence) {
case 0 -> sbc.position(off);
case 1 -> sbc.position(sbc.position() + off);
case 2 -> sbc.position(sbc.size() + off);
default -> throw new IllegalArgumentException("Invalid offset type");
};
stack.push(newPos);
}
// 管道与描述符操作
case "PIPE" -> {
Pipe p = Pipe.open();
stack.push(FDTable.register(p.sink()));
stack.push(FDTable.register(p.source()));
}
case "DUP" -> {
int oldfd = (Integer) stack.pop();
stack.push(FDTable.dup(oldfd));
}
// 网络相关
case "SOCKET" -> {
int proto = (Integer) stack.pop();
int type = (Integer) stack.pop();
int domain = (Integer) stack.pop();
Channel ch = (type == 1)
? SocketChannel.open()
: DatagramChannel.open();
stack.push(FDTable.register(ch));
}
case "CONNECT" -> {
int port = (Integer) stack.pop();
String host = String.valueOf(stack.pop());
int fd = (Integer) stack.pop();
Channel ch = FDTable.get(fd);
if (ch instanceof SocketChannel sc) {
sc.connect(new InetSocketAddress(host, port));
stack.push(0);
} else {
stack.push(-1);
}
}
case "BIND" -> {
int port = (Integer) stack.pop();
String host = String.valueOf(stack.pop());
int fd = (Integer) stack.pop();
Channel ch = FDTable.get(fd);
if (ch instanceof ServerSocketChannel ssc) {
ssc.bind(new InetSocketAddress(host, port));
stack.push(0);
} else {
stack.push(-1);
}
}
case "LISTEN" -> {
int backlog = (Integer) stack.pop();
int fd = (Integer) stack.pop();
Channel ch = FDTable.get(fd);
if (ch instanceof ServerSocketChannel) {
stack.push(0);
} else {
stack.push(-1);
}
}
case "ACCEPT" -> {
int fd = (Integer) stack.pop();
Channel ch = FDTable.get(fd);
if (ch instanceof ServerSocketChannel ssc) {
SocketChannel cli = ssc.accept();
stack.push(FDTable.register(cli));
} else {
stack.push(-1);
}
}
// 多路复用
case "SELECT" -> {
long timeout = ((Number) stack.pop()).longValue();
@SuppressWarnings("unchecked")
List<Integer> fds = (List<Integer>) stack.pop();
Selector sel = Selector.open();
for (int fd : fds) {
Channel c = FDTable.get(fd);
if (c instanceof SelectableChannel sc) {
sc.configureBlocking(false);
int ops = (c instanceof ReadableByteChannel ? SelectionKey.OP_READ : 0)
| (c instanceof WritableByteChannel ? SelectionKey.OP_WRITE : 0);
sc.register(sel, ops, fd);
}
}
int ready = sel.select(timeout);
List<Integer> readyFds = new ArrayList<>();
if (ready > 0) {
for (SelectionKey k : sel.selectedKeys()) {
readyFds.add((Integer) k.attachment());
}
}
stack.push(readyFds);
sel.close();
}
// 控制台输出
case "PRINT" -> {
Object dataObj = stack.pop();
output(dataObj, false);
stack.push(0);
}
case "PRINTLN" -> {
Object dataObj = stack.pop();
output(dataObj, true);
stack.push(0);
}
default -> throw new UnsupportedOperationException("Unsupported SYSCALL subcommand: " + cmd);
}
} catch (Exception e) {
pushErr(stack, e);
}
return pc + 1;
}
/**
* 根据传入的文件打开标志构造 NIO {@link OpenOption} 集合
* <p>
* 本方法负责将底层虚拟机传递的 flags 整数型位域转换为 Java NIO 标准的文件打开选项集合
* 以支持文件读创建截断追加等多种访问场景
* 常用于 SYSCALL OPEN 子命令
* </p>
*
* @param flags 文件打开模式标志各标志可组合使用具体含义请参见虚拟机文档
* @return 转换后的 OpenOption 集合可直接用于 FileChannel.open NIO 方法
*/
private static Set<OpenOption> flagsToOptions(int flags) {
Set<OpenOption> opts = new HashSet<>();
// 如果有写入标志则添加WRITE否则默认为READ
if ((flags & 0x1) != 0) opts.add(WRITE);
else opts.add(READ);
// 如果包含创建标志允许创建文件
if ((flags & 0x40) != 0) opts.add(CREATE);
// 包含截断标志打开时清空内容
if ((flags & 0x200) != 0) opts.add(TRUNCATE_EXISTING);
// 包含追加标志文件写入时追加到末尾
if ((flags & 0x400) != 0) opts.add(APPEND);
return opts;
}
/**
* 捕获所有异常并统一处理操作数栈压入 -1 代表本次系统调用失败
* <p>
* 本方法是全局错误屏障任何命令异常都会转换为虚拟机通用的失败信号
* 保证上层调用逻辑不会被异常打断实际应用中可拓展错误码机制
* </p>
*
* @param stack 操作数栈将失败信号写入此栈
* @param e 抛出的异常对象可在调试时输出日志
*/
private static void pushErr(OperandStack stack, Exception e) {
stack.push(-1);
System.err.println("Syscall exception: " + e);
}
/**
* 控制台输出通用方法支持基本类型字节数组任意数组对象等
* <p>
* 该方法用于 SYSCALL PRINT/PRINTLN将任意类型对象转为易读字符串输出到标准输出流
* 字节数组自动按 UTF-8 解码其它原生数组按格式化字符串输出
* </p>
*
* @param obj 待输出的内容可以为任何类型如基本类型byte[]数组对象等
* @param newline 是否自动换行如果为 true则在输出后换行否则直接输出
*/
private static void output(Object obj, boolean newline) {
String str;
if (obj == null) {
str = "null";
} else if (obj instanceof byte[] bytes) {
// 字节数组作为文本输出
str = new String(bytes);
} else if (obj.getClass().isArray()) {
// 其它数组格式化输出
str = arrayToString(obj);
} else {
str = obj.toString();
}
if (newline) System.out.println(str);
else System.out.print(str);
}
/**
* 将各种原生数组和对象数组转换为可读字符串便于控制台输出和调试
* <p>
* 本方法针对 intlongdoublefloatshortcharbyteboolean 等所有原生数组类型
* 以及对象数组都能正确格式化统一输出格式风格避免显示为类型 hashCode
* 若为不支持的类型返回通用提示字符串
* </p>
*
* @param array 任意原生数组或对象数组
* @return 该数组的可读字符串表示
*/
private static String arrayToString(Object array) {
if (array instanceof int[] a) return Arrays.toString(a);
if (array instanceof long[] a) return Arrays.toString(a);
if (array instanceof double[] a) return Arrays.toString(a);
if (array instanceof float[] a) return Arrays.toString(a);
if (array instanceof short[] a) return Arrays.toString(a);
if (array instanceof char[] a) return Arrays.toString(a);
if (array instanceof byte[] a) return Arrays.toString(a);
if (array instanceof boolean[] a) return Arrays.toString(a);
if (array instanceof Object[] a) return Arrays.deepToString(a);
return "Unsupported array";
}
}

View File

@ -1,5 +1,9 @@
package org.jcnc.snow.vm.engine;
import org.jcnc.snow.vm.commands.ref.control.RLoadCommand;
import org.jcnc.snow.vm.commands.ref.control.RPushCommand;
import org.jcnc.snow.vm.commands.ref.control.RStoreCommand;
import org.jcnc.snow.vm.commands.system.control.SyscallCommand;
import org.jcnc.snow.vm.commands.type.control.byte8.*;
import org.jcnc.snow.vm.commands.type.control.double64.*;
import org.jcnc.snow.vm.commands.type.control.float32.*;
@ -2482,6 +2486,74 @@ public class VMOpCode {
// endregion Double64
// endregion Conversion
// region Reference Control (0x00E0-0x00EF)
/**
* R_PUSH Opcode: Represents an operation that pushes an object reference (such as a String or any reference type)
* onto the operand stack.
* <p>This opcode is implemented by the {@link RPushCommand} class, which defines its specific execution logic.</p>
*
* <p>Execution Steps:</p>
* <ol>
* <li>Parses the object reference literal (e.g., a string) from the instruction parameters.</li>
* <li>Creates or interprets the reference as an object instance if necessary.</li>
* <li>Pushes the reference object onto the operand stack.</li>
* <li>Increments the program counter (PC) to proceed with the next sequential instruction.</li>
* </ol>
*
* <p>This opcode is commonly used for:</p>
* <ul>
* <li>Pushing string literals, objects, or other references needed for subsequent operations.</li>
* <li>Supplying parameters for method calls that expect reference types.</li>
* <li>Initializing the operand stack with reference values for computation or storage.</li>
* </ul>
*/
public static final int R_PUSH = 0x00E0;
/**
* R_LOAD Opcode: Represents an operation that loads an object reference from the local variable table
* and pushes it onto the operand stack.
* <p>This opcode is implemented by the {@link RLoadCommand} class, which defines its specific execution logic.</p>
*
* <p>Execution Steps:</p>
* <ol>
* <li>Parses the target slot index from the instruction parameters.</li>
* <li>Retrieves the reference object stored at the specified slot in the local variable table
* of the current stack frame.</li>
* <li>Pushes the retrieved reference onto the operand stack.</li>
* <li>Increments the program counter (PC) to proceed with the next sequential instruction.</li>
* </ol>
*
* <p>This opcode is commonly used for:</p>
* <ul>
* <li>Accessing local variables (such as function arguments or local object references) during method execution.</li>
* <li>Preparing reference values for operations or method invocations.</li>
* <li>Reusing objects previously stored in local variables.</li>
* </ul>
*/
public static final int R_LOAD = 0x00E1;
/**
* R_STORE Opcode: Represents an operation that pops an object reference from the top of the operand stack
* and stores it into the local variable table at the specified slot index.
* <p>This opcode is implemented by the {@link RStoreCommand} class, which defines its specific execution logic.</p>
*
* <p>Execution Steps:</p>
* <ol>
* <li>Parses the target slot index from the instruction parameters.</li>
* <li>Pops the top reference object from the operand stack.</li>
* <li>Stores the popped reference object into the specified slot in the local variable table
* of the current stack frame.</li>
* <li>Increments the program counter (PC) to proceed with the next sequential instruction.</li>
* </ol>
*
* <p>This opcode is commonly used for:</p>
* <ul>
* <li>Storing computation results or intermediate reference values for later use.</li>
* <li>Passing object references to local variables in preparation for further operations or method calls.</li>
* <li>Managing object lifetimes and ensuring correct referencing in scoped execution contexts.</li>
* </ul>
*/
public static final int R_STORE = 0x00E2;
// endregion
// region Stack Control (0x0100-0x01FF)
/**
* POP Opcode: Represents a stack operation that removes the top element from the operand stack.
@ -2646,8 +2718,18 @@ public class VMOpCode {
* </ul>
*/
public static final int HALT = 0x0400;
/**
* SYSCALL Opcode: Represents a system call operation that invokes a system-level function or service.
* <p>This opcode is implemented by the {@link SyscallCommand} class, which defines its specific execution logic.</p>
*
* <p>Execution Steps:</p>
* <ol>
* <li>Parses the system call identifier from the instruction parameters.</li>
* <li>Invokes the corresponding system-level function or service based on the system call identifier.</li>
* <li>Returns the result of the system call operation.</li>
*/
public static final int SYSCALL = 0x0401;
public static final int DEBUG_TRAP = 0x0402;
// public static final int DEBUG_TRAP = 0x0402;
// endregion
/**

View File

@ -1,10 +1,14 @@
package org.jcnc.snow.vm.factories;
import org.jcnc.snow.vm.commands.system.control.SyscallCommand;
import org.jcnc.snow.vm.commands.type.control.byte8.*;
import org.jcnc.snow.vm.commands.type.control.double64.*;
import org.jcnc.snow.vm.commands.type.control.float32.*;
import org.jcnc.snow.vm.commands.type.control.int32.*;
import org.jcnc.snow.vm.commands.type.control.long64.*;
import org.jcnc.snow.vm.commands.ref.control.RLoadCommand;
import org.jcnc.snow.vm.commands.ref.control.RPushCommand;
import org.jcnc.snow.vm.commands.ref.control.RStoreCommand;
import org.jcnc.snow.vm.commands.type.control.short16.*;
import org.jcnc.snow.vm.commands.type.control.byte8.BAndCommand;
import org.jcnc.snow.vm.commands.type.control.byte8.BOrCommand;
@ -61,6 +65,7 @@ public class CommandFactory {
static {
// region Type Control (0x0000-0x00BF)
// region Byte8 (0x0000-0x001F)
COMMANDS[VMOpCode.B_ADD] = new BAddCommand();
@ -204,6 +209,7 @@ public class CommandFactory {
COMMANDS[VMOpCode.D_CL] = new DCLCommand();
COMMANDS[VMOpCode.D_CLE] = new DCLECommand();
// endregion
// endregion
// region Type Conversion (0x00C0-0x00DF)
@ -244,6 +250,12 @@ public class CommandFactory {
COMMANDS[VMOpCode.D2F] = new D2FCommand();
// endregion
// region Reference Control (0x00E0-0x00EF)
COMMANDS[VMOpCode.R_PUSH] = new RPushCommand();
COMMANDS[VMOpCode.R_LOAD] = new RLoadCommand();
COMMANDS[VMOpCode.R_STORE] = new RStoreCommand();
// endregion
// region Stack Control (0x0100-0x01FF)
COMMANDS[VMOpCode.POP] = new PopCommand();
COMMANDS[VMOpCode.DUP] = new DupCommand();
@ -262,7 +274,7 @@ public class CommandFactory {
// region System Control (0x0400-0x04FF)
COMMANDS[VMOpCode.HALT] = new HaltCommand();
// COMMANDS[VMOpCode.SYSCALL] = new SyscallCommand();
COMMANDS[VMOpCode.SYSCALL] = new SyscallCommand();
// COMMANDS[VMOpCode.DEBUG_TRAP] = new DebugTrapCommand();
// endregion

View File

@ -0,0 +1,62 @@
package org.jcnc.snow.vm.io;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.nio.channels.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 维护 虚拟 fd Java NIO Channel 的全局映射表
*
* <pre>
* 0 stdin (ReadableByteChannel)
* 1 stdout (WritableByteChannel)
* 2 stderr (WritableByteChannel)
* 3+ 运行期动态分配
* </pre>
*/
public final class FDTable {
private FDTable() {}
/** 下一次可用 fd02 保留给标准流) */
private static final AtomicInteger NEXT_FD = new AtomicInteger(3);
/** 主映射表fd → Channel */
private static final ConcurrentHashMap<Integer, Channel> MAP = new ConcurrentHashMap<>();
static {
// JVM 标准流包装成 NIO Channel 后放入表中
MAP.put(0, Channels.newChannel(new BufferedInputStream(System.in)));
MAP.put(1, Channels.newChannel(new BufferedOutputStream(System.out)));
MAP.put(2, Channels.newChannel(new BufferedOutputStream(System.err)));
}
/** 注册新 Channel返回分配到的虚拟 fd */
public static int register(Channel ch) {
int fd = NEXT_FD.getAndIncrement();
MAP.put(fd, ch);
return fd;
}
/** 取得 Channel如果 fd 不存在则返回 null */
public static Channel get(int fd) {
return MAP.get(fd);
}
/** 关闭并移除 fd02 忽略) */
public static void close(int fd) throws IOException {
if (fd <= 2) return; // 标准流交由宿主 JVM 维护
Channel ch = MAP.remove(fd);
if (ch != null && ch.isOpen()) ch.close();
}
/** 类似 dup(oldfd) —— 返回指向同一 Channel 的新 fd */
public static int dup(int oldfd) {
Channel ch = MAP.get(oldfd);
if (ch == null)
throw new IllegalArgumentException("Bad fd: " + oldfd);
return register(ch); // 多个 fd 引用同一 Channel
}
}

View File

@ -84,7 +84,7 @@ public class OperandStack {
* </p>
*/
public void printOperandStack() {
LoggingUtils.logInfo("Operand Stack state:", stack + "\n");
LoggingUtils.logInfo("\n\nOperand Stack state:", stack + "\n");
}
/**