修复函数调用

This commit is contained in:
Luke 2025-05-10 23:43:51 +08:00
parent 30f6ff7c02
commit 174aafb846
6 changed files with 329 additions and 421 deletions

View File

@ -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<IRVirtualRegister, Integer> slotMap;
private final VMProgramBuilder out;
private String currentFn;
/** 存储生成的指令 */
private final List<String> code = new ArrayList<>();
/** 当前正在生成代码的函数名称 */
private String currentFnName;
public VMCodeGenerator(Map<IRVirtualRegister, Integer> slotMap) {
public VMCodeGenerator(Map<IRVirtualRegister, Integer> slotMap,
VMProgramBuilder out) {
this.slotMap = slotMap;
this.out = out;
}
/**
* 主入口接收一个中间表示函数IRFunction输出对应的 VM 指令文本序列
*
* @param fn IRFunction包含该函数的所有 IRInstruction
* @return 指令字符串列表
*/
public List<String> 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("main".equals(currentFn) ? op("HALT") : op("RET"));
}
if (r.value() != null) {
emit(op("I_LOAD"), slot(r.value()) + "");
}
emit(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());
}
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);
}
}
}

View File

@ -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<String> code = new ArrayList<>();
private final Map<String, Integer> labelMap = new HashMap<>();
private final List<Fixup> 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<String> 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<Fixup> 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();
}
}
}
}

View File

@ -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 <source-file>");
@ -38,20 +35,14 @@ public class SnowCompiler {
}
String source = Files.readString(srcPath, StandardCharsets.UTF_8);
/* 1. 词法分析 */
LexerEngine lexer = new LexerEngine(source);
List<Token> tokens = lexer.getAllTokens();
/* 2. 语法分析 */
ParserContext ctx = new ParserContext(tokens);
List<Node> 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 slotMap = new RegisterAllocator().allocate(fn);
new VMCodeGenerator(slotMap, builder).generate(fn);
}
List<String> finalCode = builder.build();
var gen = new VMCodeGenerator(slotM);
var code = gen.generate(fn);
System.out.println("== VM code ==");
finalCode.forEach(System.out::println);
System.out.println("== VM code for " + fn.name() + " ==");
code.forEach(System.out::println);
/* 只执行 main 函数 */
if ("main".equals(fn.name())) {
/* 6. 运行虚拟机 */
VirtualMachineEngine vm = new VirtualMachineEngine(VMMode.RUN);
vm.execute(code); // 运行指令
vm.printStack(); // 打印 Operand-/Call-Stack
vm.printLocalVariables(); // 打印局部变量槽
vm.execute(finalCode);
vm.printStack();
vm.printLocalVariables();
System.out.println("Process has ended");
}
}
}
}

View File

@ -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.
*
* <p>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.</p>
*
* <p>Specific behavior:</p>
* <ul>
* <li>Pops the current program counter (the return address) from the stack and stores it for later use when the method returns.</li>
* <li>Pushes a new stack frame onto the call stack with the return address and the method context (e.g., method name, local variables).</li>
* <li>Updates the program counter to the target function address, effectively transferring control to the function.</li>
* </ul>
*
* <p>This behavior allows the virtual machine to execute functions by managing a call stack that keeps track of method invocations
* and return addresses.</p>
* 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.
*
* <p>This method performs the following steps:</p>
* <ul>
* <li>Validates and extracts the target function address from the provided instruction parameters.</li>
* <li>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).</li>
* <li>Pushes the new stack frame onto the call stack.</li>
* <li>Sets the program counter to the target function address, causing execution to jump to that address.</li>
* </ul>
*
* <p>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.</p>
*
* @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) // <op> <addr>
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 = 目标函数入口
}
}

View File

@ -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.
*
* <p>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.</p>
* <p><strong>Root-frame protection:</strong> 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}.</p>
*
* <p>Specific behavior:</p>
* <ul>
* <li>Pops the return address (program counter) from the call stack.</li>
* <li>Clears the local variables of the current method (stack frame) using
* {@link LocalVariableStore#clearVariables()}.</li>
* <li>Restores the program counter to the return address.</li>
* <li>Resumes execution from the instruction following the method call.</li>
* </ul>
*
* <p>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.</p>
*
* <p>Example of the process:</p>
* <h2>Algorithm</h2>
* <ol>
* <li>A method is called, pushing a new stack frame onto the call stack.</li>
* <li>Execution proceeds in the method, using the operand stack and local variables.</li>
* <li>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.</li>
* <li>Execution continues from the point where the method was called.</li>
* <li>Ensure the call stack is not empty (a {@code RET} with no frame is an
* illegal state).</li>
* <li>Pop the current frame; clear its local variables.</li>
* <li>If the stack is now empty, return the sentinel value to terminate the
* VMs main loop.</li>
* <li>Otherwise, resume execution at the return address stored in the popped
* frame.</li>
* </ol>
*
* <p>The VMs main loop should exit when the program counter equals
* {@code Integer.MAX_VALUE}.</p>
*/
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.
*
* <p>This method performs the following actions:</p>
* <ul>
* <li>Checks if the call stack is empty (indicating no method to return from).</li>
* <li>Pops the current stack frame from the call stack, retrieving the return address.</li>
* <li>Clears the local variables in the current stack frame using
* {@link LocalVariableStore#clearVariables()}.</li>
* <li>Restores the program counter to the return address stored in the stack frame.</li>
* </ul>
*
* <p>The execution of this method results in the program counter being updated to
* the return address of the previous method in the call stack.</p>
*
* @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 callers saved PC. */
int returnAddr = finished.getReturnAddress();
System.out.println("Return " + returnAddr);
return returnAddr;
}
}

View File

@ -6,169 +6,162 @@ import org.jcnc.snow.vm.module.*;
import java.util.List;
/**
* Virtual Machine Engine ({@code VirtualMachineEngine})
* <p>
* 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.
* </p>
* Virtual-Machine Engine ({@code VirtualMachineEngine})
*
* <p>This class interprets and executes a list of VM instructions.
* It maintains a program counter (PC) together with <em>core
* runtime structures</em>:</p>
*
* <h2>Core Components</h2>
* <ul>
* <li><b>Operand Stack:</b> Manages intermediate values during execution, used for arithmetic operations, data manipulation, etc. {@link OperandStack}</li>
* <li><b>Local Variable Store:</b> Maintains local variables for functions or methods, enabling scoped data handling. {@link LocalVariableStore}</li>
* <li><b>Call Stack:</b> Tracks active function calls, supporting nested invocations and proper return address management. {@link CallStack}</li>
* <li><b>Command Handler:</b> Delegates command execution to appropriate instruction implementations. {@link CommandExecutionHandler}</li>
* <li>{@link OperandStack} stores intermediate values;</li>
* <li>{@link LocalVariableStore} holds local variables for the
* <em>current</em> stack frame;</li>
* <li>{@link CallStack} keeps stack frames and return addresses;</li>
* <li>{@link CommandExecutionHandler} dispatches opcodes to the
* corresponding {@link org.jcnc.snow.vm.interfaces.Command}.</li>
* </ul>
*
* <p>
* 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.
* </p>
* <h2>Root-frame contract</h2>
* A <strong>root stack frame</strong> is pushed <em>once</em> (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 {
/* ---------- 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.
* <p>
* 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.
* </p>
* 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.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.
* <p>
* 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.
* </p>
* Executes the supplied program <i>in place</i>.
*
* <p><b>Error Handling:</b> If an invalid command format or execution error occurs, an error message
* is printed, and the virtual machine halts execution to prevent undefined behavior.</p>
*
* @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<String> command) {
public void execute(List<String> 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);
/* ─── 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.
* <p>
* This method provides a comprehensive snapshot of the virtual machine's execution state by invoking:
* <ul>
* <li>{@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.</li>
* <li>{@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.</li>
* </ul>
* Pushes a root stack-frame if none exists. This frame has
* <code>returnAddress&nbsp;=&nbsp;0</code> 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.
* <p>
* 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.
* </p>
*/
public void printLocalVariables() {
localVariableStore.printLv();
}
/**
* Parses the opcode from a string representation.
* <p>
* This method converts the opcode string into an integer value. If the conversion fails,
* an {@link IllegalArgumentException} is thrown, indicating an invalid opcode format.
* </p>
*
* @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);
}
}
}