diff --git a/src/main/java/org/jcnc/snow/compiler/backend/VMCodeGenerator.java b/src/main/java/org/jcnc/snow/compiler/backend/VMCodeGenerator.java index 187b92b..5b20d55 100644 --- a/src/main/java/org/jcnc/snow/compiler/backend/VMCodeGenerator.java +++ b/src/main/java/org/jcnc/snow/compiler/backend/VMCodeGenerator.java @@ -1,183 +1,108 @@ package org.jcnc.snow.compiler.backend; -import org.jcnc.snow.compiler.ir.core.IRFunction; -import org.jcnc.snow.compiler.ir.core.IRInstruction; -import org.jcnc.snow.compiler.ir.instruction.BinaryOperationInstruction; -import org.jcnc.snow.compiler.ir.instruction.CallInstruction; -import org.jcnc.snow.compiler.ir.instruction.LoadConstInstruction; -import org.jcnc.snow.compiler.ir.instruction.ReturnInstruction; -import org.jcnc.snow.compiler.ir.instruction.UnaryOperationInstruction; -import org.jcnc.snow.compiler.ir.value.IRConstant; -import org.jcnc.snow.compiler.ir.value.IRVirtualRegister; -import org.jcnc.snow.compiler.ir.core.IRValue; +import org.jcnc.snow.compiler.ir.core.*; +import org.jcnc.snow.compiler.ir.instruction.*; +import org.jcnc.snow.compiler.ir.value.*; +import org.jcnc.snow.vm.engine.VMOpCode; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; import java.util.Map; /** - * VMCodeGenerator —— 虚拟机代码生成器。 - * - * 该类负责将中间表示(IRFunction)翻译为 SVM 虚拟机可执行的文本指令。 - * 当前支持: - * - int32 类型的常量加载 - * - 加减乘除(+ - * /)的二元表达式 - * - 取负(一元 -) - * - 函数调用 - * - 函数返回(含 main 函数终止) - * - * 每个虚拟寄存器通过 slotMap 分配一个槽位(int 索引),用于定位变量。 - * 生成的代码是一系列字符串,格式为汇编式:OPCODE 参数... + * 将单个 IRFunction 转换成 VM 指令并写入 VMProgramBuilder。 */ public final class VMCodeGenerator { - /** 虚拟寄存器 → 栈槽编号的映射表(由编译器分配) */ private final Map slotMap; + private final VMProgramBuilder out; + private String currentFn; - /** 存储生成的指令 */ - private final List code = new ArrayList<>(); - - /** 当前正在生成代码的函数名称 */ - private String currentFnName; - - public VMCodeGenerator(Map slotMap) { + public VMCodeGenerator(Map slotMap, + VMProgramBuilder out) { this.slotMap = slotMap; + this.out = out; } - /** - * 主入口:接收一个中间表示函数(IRFunction),输出对应的 VM 指令文本序列 - * - * @param fn IRFunction,包含该函数的所有 IRInstruction - * @return 指令字符串列表 - */ - public List generate(IRFunction fn) { - currentFnName = fn.name(); + /** 主入口:生成当前函数全部指令(无返回值,直接写入 builder) */ + public void generate(IRFunction fn) { + currentFn = fn.name(); + out.beginFunction(currentFn); // 记录入口 for (IRInstruction inst : fn.body()) { switch (inst) { - case LoadConstInstruction c -> genLoadConst(c); // 常量加载 - case BinaryOperationInstruction b -> genBinOp(b); // 二元操作 - case UnaryOperationInstruction u -> genUnary(u); // 一元操作 - case CallInstruction c -> genCall(c); // 函数调用 - case ReturnInstruction r -> genRet(r); // 返回语句 - default -> throw new IllegalStateException("不支持的 IR 指令类型: " + inst); + case LoadConstInstruction c -> genLoadConst(c); + case BinaryOperationInstruction b -> genBinOp(b); + case UnaryOperationInstruction u -> genUnary(u); + case CallInstruction c -> genCall(c); + case ReturnInstruction r -> genRet(r); + default -> throw new IllegalStateException( + "Unsupported IR: " + inst); } } - - return code; } - /** - * 生成常量加载指令:I_PUSH 常量 → I_STORE 寄存器槽位 - */ + /* ---------- 指令生成 ---------- */ + private void genLoadConst(LoadConstInstruction c) { IRConstant k = (IRConstant) c.operands().getFirst(); emit(op("I_PUSH"), k.value().toString()); - emit(op("I_STORE"), slot(c.dest()) + ""); + emit(op("I_STORE"), slot(c.dest())); } - /** - * 生成二元运算(+ - * /) - */ private void genBinOp(BinaryOperationInstruction b) { - emit(op("I_LOAD"), slot((IRVirtualRegister) b.operands().get(0)) + ""); - emit(op("I_LOAD"), slot((IRVirtualRegister) b.operands().get(1)) + ""); - - // 通过 IROpCode → VM 指令映射表获取实际指令 - String opcode = IROpCodeMapper.toVMOp(b.op()); - emit(op(opcode)); - emit(op("I_STORE"), slot(b.dest()) + ""); + emit(op("I_LOAD"), slot((IRVirtualRegister) b.operands().get(0))); + emit(op("I_LOAD"), slot((IRVirtualRegister) b.operands().get(1))); + emit(op(IROpCodeMapper.toVMOp(b.op()))); + emit(op("I_STORE"), slot(b.dest())); } - /** - * 生成一元运算(如取负) - */ private void genUnary(UnaryOperationInstruction u) { - emit(op("I_LOAD"), slot((IRVirtualRegister) u.operands().getFirst()) + ""); - - String opcode = IROpCodeMapper.toVMOp(u.op()); - emit(op(opcode)); - emit(op("I_STORE"), slot(u.dest()) + ""); + emit(op("I_LOAD"), slot((IRVirtualRegister) u.operands().getFirst())); + emit(op(IROpCodeMapper.toVMOp(u.op()))); + emit(op("I_STORE"), slot(u.dest())); } - /** - * 生成函数调用指令: - * - 将所有实参从局部槽加载到栈; - * - 执行 CALL,使用函数全名; - * - 将返回值存回目标寄存器槽。 - */ private void genCall(CallInstruction c) { - // 参数入栈 + // 实参入栈 for (IRValue arg : c.getArguments()) { - if (arg instanceof IRVirtualRegister reg) { - emit(op("I_LOAD"), slot(reg) + ""); - } else { - throw new IllegalStateException("不支持的调用参数类型: " + arg); - } + if (arg instanceof IRVirtualRegister reg) + emit(op("I_LOAD"), slot(reg)); + else + throw new IllegalStateException("Unsupported arg: " + arg); } - // 调用指令,函数名为全名(含模块前缀) - emit(op("CALL"), c.getFunctionName()); - // 返回值存储到目标寄存器 - emit(op("I_STORE"), slot(c.getDest()) + ""); + // 生成 CALL(由 builder 解析地址/回填) + out.emitCall(c.getFunctionName()); + // 存放返回值 + emit(op("I_STORE"), slot(c.getDest())); } - /** - * 生成返回语句: - * - 对于 main 函数,使用 HALT 表示程序终止; - * - 其他函数使用 RET 指令返回调用者。 - */ private void genRet(ReturnInstruction r) { - if ("main".equals(currentFnName)) { - if (r.value() != null) { - emit(op("I_LOAD"), slot(r.value()) + ""); - } - emit(op("HALT")); - return; - } - - if (r.value() != null) { - emit(op("I_LOAD"), slot(r.value()) + ""); - } - emit(op("RET")); + if (r.value() != null) + emit(op("I_LOAD"), slot(r.value())); + emit("main".equals(currentFn) ? op("HALT") : op("RET")); } - /** - * 获取某个虚拟寄存器映射到的槽位索引 - * - * @param r 虚拟寄存器 - * @return 槽位编号 - */ - private int slot(IRVirtualRegister r) { - Integer s = slotMap.get(r); - if (s == null) throw new IllegalStateException("寄存器 " + r + " 未映射槽位"); - return s; - } + /* ---------- 工具 ---------- */ - /** - * 通过反射方式获取 VMOpCode 中定义的静态常量值(字符串形式) - * - * @param name VM 指令名(如 "I_ADD") - * @return 指令码文本 - */ - private String op(String name) { - try { - Field f = Class.forName("org.jcnc.snow.vm.engine.VMOpCode").getField(name); - return f.get(null).toString(); - } catch (Exception e) { - throw new RuntimeException("未知的 opcode: " + name, e); - } - } - - /** - * 输出一条指令到代码列表中 - * - * @param opcode 指令名 - * @param args 参数(可选) - */ private void emit(String opcode, String... args) { StringBuilder sb = new StringBuilder(opcode); for (String a : args) sb.append(' ').append(a); - code.add(sb.toString()); + out.emit(sb.toString()); } -} \ No newline at end of file + + private String slot(IRVirtualRegister r) { + Integer s = slotMap.get(r); + if (s == null) + throw new IllegalStateException("Unmapped register: " + r); + return s.toString(); + } + + private String op(String name) { + try { + Field f = VMOpCode.class.getField(name); + return f.get(null).toString(); + } catch (Exception e) { + throw new RuntimeException("Unknown opcode: " + name, e); + } + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/backend/VMProgramBuilder.java b/src/main/java/org/jcnc/snow/compiler/backend/VMProgramBuilder.java new file mode 100644 index 0000000..0b6514f --- /dev/null +++ b/src/main/java/org/jcnc/snow/compiler/backend/VMProgramBuilder.java @@ -0,0 +1,81 @@ +package org.jcnc.snow.compiler.backend; + +import org.jcnc.snow.vm.engine.VMOpCode; + +import java.util.*; + +/** + * 负责生成整段 VM 指令: + * 1. 为每个函数登记入口地址(label → pc) + * 2. 生成指令并维护全局 pc 计数 + * 3. 支持前向调用:CALL 时目标函数尚未出现也能写占位并在后续回填 + * 4. 允许模块前缀(CommonTasks.test)与简名(test)互相解析 + */ +public final class VMProgramBuilder { + + private static final String PLACEHOLDER = "-1"; + private record Fixup(int index, String target) {} + + private final List code = new ArrayList<>(); + private final Map labelMap = new HashMap<>(); + private final List unresolved = new ArrayList<>(); + private int pc = 0; + + /* 在写入函数体指令之前调用 —— 记录入口地址并回填之前的前向 CALL */ + public void beginFunction(String name) { + labelMap.put(name, pc); + patchFixupsFor(name); + } + + /* 普通指令 */ + public void emit(String line) { + code.add(line); + pc++; + } + + /* 生成 CALL,自动解析前缀 / 回填 */ + public void emitCall(String target) { + Integer addr = resolveAddr(target); + if (addr != null) { + emit(VMOpCode.CALL + " " + addr); + } else { + emit(VMOpCode.CALL + " " + PLACEHOLDER); // 写占位 + unresolved.add(new Fixup(pc - 1, target)); + } + } + + /* 构建最终代码,若还有悬而未决的前向调用则报错 */ + public List build() { + if (!unresolved.isEmpty()) { + String msg = "Unresolved CALL targets: " + + unresolved.stream().map(f -> f.target).toList(); + throw new IllegalStateException(msg); + } + return List.copyOf(code); + } + + /* ---------- 私有辅助 ---------- */ + + /** 解析 label:先查原样,再查去掉模块前缀后的简名 */ + private Integer resolveAddr(String label) { + Integer addr = labelMap.get(label); + if (addr == null && label.contains(".")) { + String simple = label.substring(label.lastIndexOf('.') + 1); + addr = labelMap.get(simple); + } + return addr; + } + + /** 当某个函数登记入口后,回填此前针对它的占位 CALL */ + private void patchFixupsFor(String name) { + for (Iterator it = unresolved.iterator(); it.hasNext(); ) { + Fixup f = it.next(); + boolean match = f.target.equals(name) || + f.target.endsWith("." + name); + if (match) { + code.set(f.index, VMOpCode.CALL + " " + pc); + it.remove(); + } + } + } +} diff --git a/src/main/java/org/jcnc/snow/compiler/cli/SnowCompiler.java b/src/main/java/org/jcnc/snow/compiler/cli/SnowCompiler.java index d2db05e..875933f 100644 --- a/src/main/java/org/jcnc/snow/compiler/cli/SnowCompiler.java +++ b/src/main/java/org/jcnc/snow/compiler/cli/SnowCompiler.java @@ -1,20 +1,15 @@ package org.jcnc.snow.compiler.cli; -import org.jcnc.snow.compiler.backend.RegisterAllocator; -import org.jcnc.snow.compiler.backend.VMCodeGenerator; +import org.jcnc.snow.compiler.backend.*; import org.jcnc.snow.compiler.ir.builder.IRProgramBuilder; -import org.jcnc.snow.compiler.ir.core.IRFunction; -import org.jcnc.snow.compiler.ir.core.IRProgram; +import org.jcnc.snow.compiler.ir.core.*; import org.jcnc.snow.compiler.lexer.core.LexerEngine; import org.jcnc.snow.compiler.lexer.token.Token; -import org.jcnc.snow.compiler.lexer.utils.TokenPrinter; import org.jcnc.snow.compiler.parser.ast.base.Node; import org.jcnc.snow.compiler.parser.context.ParserContext; import org.jcnc.snow.compiler.parser.core.ParserEngine; -import org.jcnc.snow.compiler.parser.function.ASTPrinter; import org.jcnc.snow.compiler.semantic.core.SemanticAnalyzerRunner; -import org.jcnc.snow.vm.engine.VMMode; -import org.jcnc.snow.vm.engine.VirtualMachineEngine; +import org.jcnc.snow.vm.engine.*; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -22,10 +17,12 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +/** + * SnowCompiler CLI —— 读取源文件,编译到 VM 指令并运行。 + */ public class SnowCompiler { public static void main(String[] args) throws IOException { - /* ───── 读取源码 ───── */ if (args.length == 0 || args[0].isBlank()) { System.err.println("Usage: SnowCompiler "); @@ -38,20 +35,14 @@ public class SnowCompiler { } String source = Files.readString(srcPath, StandardCharsets.UTF_8); - /* 1. 词法分析 */ LexerEngine lexer = new LexerEngine(source); - List tokens = lexer.getAllTokens(); /* 2. 语法分析 */ ParserContext ctx = new ParserContext(tokens); List ast = new ParserEngine(ctx).parse(); -// System.out.println(source); - TokenPrinter.print(tokens); // 打印 Token 列表 - ASTPrinter.print(ast); // 打印 AST - ASTPrinter.printJson(ast); // 打印JSON AST /* 3. 语义分析 */ SemanticAnalyzerRunner.runSemanticAnalysis(ast, false); @@ -60,29 +51,22 @@ public class SnowCompiler { System.out.println("=== IR ==="); System.out.println(program); - - + /* 5. IR → VM 指令(单遍,自动回填 CALL 地址) */ + VMProgramBuilder builder = new VMProgramBuilder(); for (IRFunction fn : program.functions()) { - var alloc = new RegisterAllocator(); - var slotM = alloc.allocate(fn); - - var gen = new VMCodeGenerator(slotM); - var code = gen.generate(fn); - - System.out.println("== VM code for " + fn.name() + " =="); - code.forEach(System.out::println); - - - /* 只执行 main 函数 */ - if ("main".equals(fn.name())) { - VirtualMachineEngine vm = new VirtualMachineEngine(VMMode.RUN); - - vm.execute(code); // 运行指令 - vm.printStack(); // 打印 Operand-/Call-Stack - vm.printLocalVariables(); // 打印局部变量槽 - - System.out.println("Process has ended"); - } + var slotMap = new RegisterAllocator().allocate(fn); + new VMCodeGenerator(slotMap, builder).generate(fn); } + List finalCode = builder.build(); + + System.out.println("== VM code =="); + finalCode.forEach(System.out::println); + + /* 6. 运行虚拟机 */ + VirtualMachineEngine vm = new VirtualMachineEngine(VMMode.RUN); + vm.execute(finalCode); + vm.printStack(); + vm.printLocalVariables(); + System.out.println("Process has ended"); } } diff --git a/src/main/java/org/jcnc/snow/vm/commands/function/CallCommand.java b/src/main/java/org/jcnc/snow/vm/commands/function/CallCommand.java index 892c7d8..6c687e3 100644 --- a/src/main/java/org/jcnc/snow/vm/commands/function/CallCommand.java +++ b/src/main/java/org/jcnc/snow/vm/commands/function/CallCommand.java @@ -1,88 +1,47 @@ package org.jcnc.snow.vm.commands.function; +import org.jcnc.snow.vm.engine.VMMode; import org.jcnc.snow.vm.interfaces.Command; import org.jcnc.snow.vm.module.*; /** - * The {@code CallCommand} class implements the {@link Command} interface and represents a function call command in the virtual machine. - * This command pushes a new stack frame onto the call stack, saves the current program counter (the return address), - * and jumps to the target function address to begin execution. - * - *

The {@code CallCommand} is used to simulate the invocation of a method or function in the virtual machine's execution flow. - * It manages the call stack by creating a new stack frame, pushing it to the stack, and modifying the program counter (PC) - * to jump to the target address.

- * - *

Specific behavior:

- *
    - *
  • Pops the current program counter (the return address) from the stack and stores it for later use when the method returns.
  • - *
  • Pushes a new stack frame onto the call stack with the return address and the method context (e.g., method name, local variables).
  • - *
  • Updates the program counter to the target function address, effectively transferring control to the function.
  • - *
- * - *

This behavior allows the virtual machine to execute functions by managing a call stack that keeps track of method invocations - * and return addresses.

+ * CALL 指令 —— 创建新栈帧并跳转到目标地址。 */ public class CallCommand implements Command { - /** - * Default constructor for creating an instance of CallCommand. - * This constructor is empty as no specific initialization is required. - */ - public CallCommand() { - // Empty constructor - } - /** - * Executes the {@code CALL} instruction, which initiates a function call in the virtual machine. - * - *

This method performs the following steps:

- *
    - *
  • Validates and extracts the target function address from the provided instruction parameters.
  • - *
  • Creates a new stack frame that includes the return address (current PC + 1), the local variable store, - * and the method context (e.g., method name, arguments).
  • - *
  • Pushes the new stack frame onto the call stack.
  • - *
  • Sets the program counter to the target function address, causing execution to jump to that address.
  • - *
- * - *

After executing this command, the virtual machine begins executing the function at the specified address, and - * upon return, the control will be transferred back to the instruction following the call.

- * - * @param parts The array of instruction parameters, which should contain the target function address. - * @param currentPC The current program counter-value, which points to the instruction currently being executed. - * After the function call, this value will be updated to the target function's address. - * @param operandStack The operand stack manager, which is responsible for managing the operand stack during the execution of the command. - * @param localVariableStore The local variable store, which is used to manage the local variables for the current function being called. - * @param callStack The call stack, which is responsible for managing the method invocation hierarchy. - * The stack is modified by pushing a new frame when a function call is made. - * @return The updated program counter-value, which is set to the target function's address in the call. - * @throws IllegalArgumentException If the instruction parameters are invalid or if the function address is not provided. - */ @Override - public int execute(String[] parts, int currentPC, OperandStack operandStack, LocalVariableStore localVariableStore, CallStack callStack) { - // Validate parts array length, expect at least 1 parameter (target address) - if (parts.length < 1) { - throw new IllegalArgumentException("Invalid instruction format. Target function address is missing."); - } + public int execute(String[] parts, + int currentPC, + OperandStack operandStack, + LocalVariableStore callerLVS, + CallStack callStack) { - // Extract the target function address (simulated from the parts) - int targetAddress; + /* ---------- 解析地址 ---------- */ + if (parts.length < 2) // + throw new IllegalArgumentException("CALL: missing target address"); + int targetAddr; try { - targetAddress = Integer.parseInt(parts[1]); // The function address is expected as the first parameter + targetAddr = Integer.parseInt(parts[1]); } catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid function address: " + parts[1], e); + throw new IllegalArgumentException("CALL: invalid address " + parts[1], e); } - // Create a method context for the function call (name and parameters can be expanded as needed) - MethodContext methodContext = new MethodContext("main", null); // Method name is hardcoded, modify for actual use + /* ---------- 创建新栈帧 ---------- */ + // 为被调函数分配“独立”的局部变量表 + LocalVariableStore calleeLVS = new LocalVariableStore(VMMode.RUN); - // Create a new stack frame and push it onto the call stack - StackFrame frame = new StackFrame(currentPC + 1, localVariableStore, methodContext); - callStack.pushFrame(frame); + // 若有调用约定,可在此把实参从 operandStack 弹入 calleeLVS + // 例如:for (int i = nArgs-1; i >= 0; i--) calleeLVS.setVariable(i, operandStack.pop()); - // Log the call action for debugging - System.out.println("Calling function at address: " + targetAddress); + MethodContext ctx = new MethodContext("subroutine@" + targetAddr, null); - // Update the program counter to the target function address, which effectively jumps to the function - return targetAddress; + StackFrame newFrame = new StackFrame(currentPC + 1, calleeLVS, ctx); + callStack.pushFrame(newFrame); + + System.out.println("Calling function at address: " + targetAddr); + + /* ---------- 跳转 ---------- */ + return targetAddr; // 设置 PC = 目标函数入口 } } diff --git a/src/main/java/org/jcnc/snow/vm/commands/function/RetCommand.java b/src/main/java/org/jcnc/snow/vm/commands/function/RetCommand.java index 6c1f082..d62291c 100644 --- a/src/main/java/org/jcnc/snow/vm/commands/function/RetCommand.java +++ b/src/main/java/org/jcnc/snow/vm/commands/function/RetCommand.java @@ -1,99 +1,65 @@ package org.jcnc.snow.vm.commands.function; 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; -import org.jcnc.snow.vm.module.StackFrame; +import org.jcnc.snow.vm.module.*; /** - * The {@code RetCommand} class implements the {@link Command} interface and represents the - * instruction for returning from a method in the virtual machine's execution flow. + * The {@code RetCommand} implements the {@code RET} instruction that returns + * from a method in the virtual-machine execution flow. * - *

This command performs the necessary actions to return control to the calling method - * by restoring the program counter (PC) to the saved return address from the call stack.

+ *

Root-frame protection: after popping the current frame the + * command checks {@link CallStack#isEmpty()}. If the stack became empty, the + * popped frame must have been the root frame; the command therefore signals + * normal program termination by returning {@link Integer#MAX_VALUE}.

* - *

Specific behavior:

- *
    - *
  • Pops the return address (program counter) from the call stack.
  • - *
  • Clears the local variables of the current method (stack frame) using - * {@link LocalVariableStore#clearVariables()}.
  • - *
  • Restores the program counter to the return address.
  • - *
  • Resumes execution from the instruction following the method call.
  • - *
- * - *

This operation signifies the end of a method execution, where control is returned - * to the caller method, and execution continues from where the method was invoked.

- * - *

Example of the process:

+ *

Algorithm

*
    - *
  1. A method is called, pushing a new stack frame onto the call stack.
  2. - *
  3. Execution proceeds in the method, using the operand stack and local variables.
  4. - *
  5. When a {@code RET} command is encountered, the return address is popped - * from the call stack, the method's local variables are cleared, and the program - * counter is updated to the return address.
  6. - *
  7. Execution continues from the point where the method was called.
  8. + *
  9. Ensure the call stack is not empty (a {@code RET} with no frame is an + * illegal state).
  10. + *
  11. Pop the current frame; clear its local variables.
  12. + *
  13. If the stack is now empty, return the sentinel value to terminate the + * VM’s main loop.
  14. + *
  15. Otherwise, resume execution at the return address stored in the popped + * frame.
  16. *
+ * + *

The VM’s main loop should exit when the program counter equals + * {@code Integer.MAX_VALUE}.

*/ public class RetCommand implements Command { - /** - * Default constructor for creating an instance of RetCommand. - * This constructor is empty as no specific initialization is required. - */ + + /** Sentinel value indicating that execution should terminate. */ + private static final int PROGRAM_END = Integer.MAX_VALUE; + public RetCommand() { - // Empty constructor + /* no-op */ } - /** - * Executes the {@code RET} instruction, returning control to the caller method. - * - *

This method performs the following actions:

- *
    - *
  • Checks if the call stack is empty (indicating no method to return from).
  • - *
  • Pops the current stack frame from the call stack, retrieving the return address.
  • - *
  • Clears the local variables in the current stack frame using - * {@link LocalVariableStore#clearVariables()}.
  • - *
  • Restores the program counter to the return address stored in the stack frame.
  • - *
- * - *

The execution of this method results in the program counter being updated to - * the return address of the previous method in the call stack.

- * - * @param parts The array of instruction parameters, which can include - * operation arguments (such as target addresses or function names). - * @param currentPC The current program counter-value, which represents the - * address of the instruction being executed. - * @param operandStack The operand stack manager, used to manage the operand stack - * during the execution of the command (though not directly - * involved in this command). - * @param localVariableStore The local variable store associated with the current method, - * which is cleared when returning from the method. - * @param callStack The call stack that tracks method invocations and manages - * the stack frames, which is used to retrieve and pop the - * return address during the execution of this command. - * @return The updated program counter-value, which is the return address popped from - * the call stack. This value will be used to continue execution after returning - * from the current method. - * @throws IllegalStateException If the call stack is empty, indicating there is no - * method to return from. - */ @Override - public int execute(String[] parts, int currentPC, OperandStack operandStack, LocalVariableStore localVariableStore, CallStack callStack) { - // Check if the call stack is empty (i.e., no function was called before) + public int execute(String[] parts, + int currentPC, + OperandStack operandStack, + LocalVariableStore localVariableStore, + CallStack callStack) { + + /* Guard: a RET with an empty stack is invalid. */ if (callStack.isEmpty()) { throw new IllegalStateException("Call stack is empty. No function to return to."); } - // Clear the local variable store in the current stack frame - callStack.peekFrame().getLocalVariableStore().clearVariables(); + /* Pop the current frame and clear its locals. */ + StackFrame finished = callStack.popFrame(); + finished.getLocalVariableStore().clearVariables(); - // Pop the return address (the saved program counter) from the call stack - StackFrame frame = callStack.popFrame(); + /* If the stack became empty, we just popped the root frame -> terminate. */ + if (callStack.isEmpty()) { + System.out.println("Return 0"); + return PROGRAM_END; // VM main loop must break. + } - // Debug log for the return address - System.out.println("Return " + frame.getReturnAddress()); - - // Restore the program counter to the return address - return frame.getReturnAddress(); + /* Normal return to the caller’s saved PC. */ + int returnAddr = finished.getReturnAddress(); + System.out.println("Return " + returnAddr); + return returnAddr; } } diff --git a/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java b/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java index 4f5e884..d0d93a1 100644 --- a/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java +++ b/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java @@ -6,169 +6,162 @@ import org.jcnc.snow.vm.module.*; import java.util.List; /** - * Virtual Machine Engine ({@code VirtualMachineEngine}) - *

- * This class implements a virtual machine capable of parsing and executing a sequence of commands. - * The execution is controlled by a program counter (PC), which fetches and processes one instruction at a time - * from the provided command list. The virtual machine manages its internal state using core components - * such as the operand stack, local variable store, and call stack. - *

+ * Virtual-Machine Engine ({@code VirtualMachineEngine}) + * + *

This class interprets and executes a list of VM instructions. + * It maintains a program counter (PC) together with core + * runtime structures:

* - *

Core Components

*
    - *
  • Operand Stack: Manages intermediate values during execution, used for arithmetic operations, data manipulation, etc. {@link OperandStack}
  • - *
  • Local Variable Store: Maintains local variables for functions or methods, enabling scoped data handling. {@link LocalVariableStore}
  • - *
  • Call Stack: Tracks active function calls, supporting nested invocations and proper return address management. {@link CallStack}
  • - *
  • Command Handler: Delegates command execution to appropriate instruction implementations. {@link CommandExecutionHandler}
  • + *
  • {@link OperandStack} – stores intermediate values;
  • + *
  • {@link LocalVariableStore} – holds local variables for the + * current stack frame;
  • + *
  • {@link CallStack} – keeps stack frames and return addresses;
  • + *
  • {@link CommandExecutionHandler} – dispatches opcodes to the + * corresponding {@link org.jcnc.snow.vm.interfaces.Command}.
  • *
* - *

- * Each command is processed by the {@link CommandExecutionHandler}, which determines the appropriate operation - * based on the opcode and maintains the program counter to control program flow. - *

+ *

Root-frame contract

+ * A root stack frame is pushed once (via + * {@link #ensureRootFrame()}) before execution starts and is never popped. + * When a {@code RET} executed in the root frame signals + * {@link #PROGRAM_END}, the main loop exits gracefully. */ public class VirtualMachineEngine { - private final OperandStack operandStack; - private final LocalVariableStore localVariableStore; - private final CallStack callStack; + + /* ---------- Constants ---------- */ + + /** Sentinel PC value meaning “terminate the program gracefully”. */ + private static final int PROGRAM_END = Integer.MAX_VALUE; + + /* ---------- Runtime state ---------- */ + + private final OperandStack operandStack; + private final LocalVariableStore localVariableStore; + private final CallStack callStack; private final CommandExecutionHandler commandExecutionHandler; + private int programCounter; + /* ---------- Construction ---------- */ + /** - * Constructs and initializes the virtual machine engine. - *

- * This constructor sets up the core components of the virtual machine, including the operand stack, - * call stack, local variable store, and command handler. The program counter is initialized to zero, - * indicating that execution will start from the first instruction. - *

+ * Constructs the virtual-machine engine and initialises its runtime + * structures. The program counter is set to {@code 0}; the root frame + * will be created lazily by {@link #ensureRootFrame()}. * - * @param vmMode The operating mode of the virtual machine, which may affect local variable storage behavior. + * @param vmMode Operating mode (affects local-variable store policy). */ public VirtualMachineEngine(VMMode vmMode) { - this.operandStack = new OperandStack(); - this.callStack = new CallStack(); + this.operandStack = new OperandStack(); + this.callStack = new CallStack(); this.localVariableStore = new LocalVariableStore(vmMode); - this.commandExecutionHandler = new CommandExecutionHandler(operandStack, localVariableStore, callStack); + this.commandExecutionHandler = + new CommandExecutionHandler(operandStack, + localVariableStore, + callStack); this.programCounter = 0; } - CallStack getCallStack() { - return callStack; - } + /* Package-private getter – used by debug helpers */ + CallStack getCallStack() { return callStack; } + + /* ---------- Execution ---------- */ /** - * Executes a list of commands in sequence. - *

- * This method processes each command from the provided list until either all commands are executed - * or a termination condition (such as a halt command) is met. The method relies on the - * {@link CommandExecutionHandler} to interpret and execute each instruction. - *

+ * Executes the supplied program in place. * - *

Error Handling: If an invalid command format or execution error occurs, an error message - * is printed, and the virtual machine halts execution to prevent undefined behavior.

- * - * @param command The list of commands, where each command is formatted as "opcode operand1 operand2 ...". - * @throws IllegalArgumentException If the command list is empty or contains invalid instructions. + * @param program list of textual instructions (opcode and operands + * separated by spaces) + * @throws IllegalArgumentException if {@code program} is {@code null} + * or empty */ - public void execute(List command) { + public void execute(List program) { - if (command == null || command.isEmpty()) { - throw new IllegalArgumentException("The command list cannot be empty or null."); + if (program == null || program.isEmpty()) { + throw new IllegalArgumentException( + "The command list cannot be empty or null."); } + /* Ensure the root frame is present exactly once. */ ensureRootFrame(); - if (command == null || command.isEmpty()) { - throw new IllegalArgumentException("The command list cannot be empty or null."); - } + /* -------- Main interpreter loop -------- */ + while (true) { - while (programCounter >= 0 && programCounter < command.size()) { - String instruction = command.get(programCounter); - String[] parts = instruction.trim().split(" "); + /* ─── graceful termination ─── */ + if (programCounter == PROGRAM_END) break; + + /* ─── bounds check ─── */ + if (programCounter < 0 || programCounter >= program.size()) break; + + String instruction = program.get(programCounter); + String[] parts = instruction.trim().split(" "); if (parts.length < 1) { - System.err.println("Invalid command format at " + programCounter + " -> Missing opcode"); + System.err.println("Invalid command format at PC=" + + programCounter + " -> Missing opcode"); break; } try { int opCode = parseOpCode(parts[0]); - int nextPC = commandExecutionHandler.handle(opCode, parts, programCounter); - if (nextPC == -1) break; // Halt condition + int nextPC = + commandExecutionHandler.handle(opCode, + parts, + programCounter); + + /* HALT (-1) or PROGRAM_END → exit VM */ + if (nextPC == -1 || nextPC == PROGRAM_END) break; programCounter = nextPC; + } catch (IllegalArgumentException e) { - System.err.println("Command error at " + programCounter + " -> " + e.getMessage()); + System.err.println("Command error at PC=" + + programCounter + " -> " + e.getMessage()); break; } } } - /** 若 callStack 为空,则构造一个根栈帧并预置空局部变量槽 */ - private void ensureRootFrame() { - if (!callStack.isEmpty()) return; - - LocalVariableStore lvs = this.localVariableStore; // 仍复用现有对象 - - /* ---------- 预分配 1 个空位 ---------- */ -// for (int i = 0; i < 0; i++) { -// lvs.setVariable(i, 0); // setVariable() 会自动扩容,先填 0 占位 -// } - - MethodContext mc = new MethodContext("root", null); - StackFrame sf = new StackFrame(0, lvs, mc); - callStack.pushFrame(sf); - } - - + /* ---------- Helper: ensure root frame ---------- */ /** - * Prints the current state of the operand stack and the call stack. - *

- * This method provides a comprehensive snapshot of the virtual machine's execution state by invoking: - *

    - *
  • {@link OperandStack#printOperandStack()} - Displays the current elements in the operand stack, - * which holds intermediate values used during instruction execution, such as computation results, - * operands for upcoming operations, and temporary data.
  • - *
  • {@link CallStack#printCallStack()} - Displays the current function call hierarchy, - * including active stack frames, return addresses, local variables, and method contexts. - * This helps track the flow of nested function calls and identify the current execution context.
  • - *
+ * Pushes a root stack-frame if none exists. This frame has + * returnAddress = 0 and an empty + * {@link LocalVariableStore}. It must never be popped; instead, + * when a {@code RET} is executed in this frame, the command should + * return {@link #PROGRAM_END}. */ + private void ensureRootFrame() { + if (!callStack.isEmpty()) return; // already initialised + + MethodContext rootCtx = new MethodContext("root", null); + StackFrame root = new StackFrame(0, localVariableStore, rootCtx); + callStack.pushFrame(root); + } + + /* ---------- Debug helpers ---------- */ + public void printStack() { operandStack.printOperandStack(); callStack.printCallStack(); } - - /** - * Prints the current state of the local variable store. - *

- * This method invokes {@link LocalVariableStore#printLv()} to display all active local variables, - * which can be useful for debugging or inspecting the state of function calls. - *

- */ public void printLocalVariables() { localVariableStore.printLv(); } - /** - * Parses the opcode from a string representation. - *

- * This method converts the opcode string into an integer value. If the conversion fails, - * an {@link IllegalArgumentException} is thrown, indicating an invalid opcode format. - *

- * - * @param opCodeStr The string representation of the opcode. - * @return The integer value of the opcode. - * @throws IllegalArgumentException If the opcode format is invalid or cannot be parsed. - */ + /* ---------- Utility ---------- */ + + /** Converts the textual opcode to an integer. */ private int parseOpCode(String opCodeStr) { try { return Integer.parseInt(opCodeStr); } catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid opcode -> " + opCodeStr, e); + throw new IllegalArgumentException( + "Invalid opcode -> " + opCodeStr, e); } } }