修复函数调用
This commit is contained in:
parent
30f6ff7c02
commit
174aafb846
@ -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(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());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 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<String> 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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 = 目标函数入口
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
* VM’s main loop.</li>
|
||||
* <li>Otherwise, resume execution at the return address stored in the popped
|
||||
* frame.</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>The VM’s 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 caller’s saved PC. */
|
||||
int returnAddr = finished.getReturnAddress();
|
||||
System.out.println("Return " + returnAddr);
|
||||
return returnAddr;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
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.
|
||||
* <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.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);
|
||||
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.
|
||||
* <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 = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user