- * 算法策略:顺序扫描所有 IR 指令,每遇到一个新的虚拟寄存器就分配一个递增编号。
- * 不考虑寄存器生存期重叠、重用或优化,仅做简单映射。
- *
+ * A very simple linear-scan register allocator.
+ *
+ *
Strategy:
+ *
+ *
Assign formal parameters to slots
+ * 0 .. n-1 (in declaration order).
+ *
Scan the instruction list; every unseen virtual register
+ * gets the next free slot number.
+ *
+ *
No liveness analysis, no spilling – demo-grade only.
*/
public final class RegisterAllocator {
- /** 虚拟寄存器 → 槽位编号 的映射表 */
- private final Map map = new HashMap<>();
+ private final Map map = new HashMap<>();
/**
- * 执行寄存器分配:将函数中所有虚拟寄存器映射为连续的整型槽位编号。
+ * Computes a register→slot mapping for the given function.
*
- * @param fn 中间表示形式的函数(IRFunction)
- * @return 映射表(IRVirtualRegister → 槽位编号)
+ * @param fn IRFunction to allocate
+ * @return mapping table (immutable)
*/
- public Map allocate(IRFunction fn) {
- int next = 0; // 当前可分配的下一个槽位编号
+ public Map allocate(IRFunction fn) {
- for (IRInstruction i : fn.body()) {
- // 若该指令有目标寄存器(dest),且尚未分配槽位 → 分配一个
- if (i.dest() != null && !map.containsKey(i.dest())) {
- map.put(i.dest(), next++);
+ int next = 0;
+
+ /* ---------- 1) parameters first ---------- */
+ for (IRVirtualRegister param : fn.parameters()) {
+ map.put(param, next++);
+ }
+
+ /* ---------- 2) then all remaining VRegs ---------- */
+ for (IRInstruction inst : fn.body()) {
+
+ if (inst.dest() != null && !map.containsKey(inst.dest())) {
+ map.put(inst.dest(), next++);
}
- // 遍历指令的所有操作数(operand),若是寄存器也进行分配
- for (IRValue v : i.operands()) {
- if (v instanceof IRVirtualRegister r && !map.containsKey(r)) {
- map.put(r, next++);
+ for (IRValue v : inst.operands()) {
+ if (v instanceof IRVirtualRegister vr &&
+ !map.containsKey(vr)) {
+ map.put(vr, next++);
}
}
}
- return map;
+ return Map.copyOf(map);
}
}
diff --git a/src/main/java/org/jcnc/snow/compiler/ir/builder/FunctionBuilder.java b/src/main/java/org/jcnc/snow/compiler/ir/builder/FunctionBuilder.java
index 1c096af..4125d0b 100644
--- a/src/main/java/org/jcnc/snow/compiler/ir/builder/FunctionBuilder.java
+++ b/src/main/java/org/jcnc/snow/compiler/ir/builder/FunctionBuilder.java
@@ -1,6 +1,7 @@
package org.jcnc.snow.compiler.ir.builder;
import org.jcnc.snow.compiler.ir.core.IRFunction;
+import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import org.jcnc.snow.compiler.parser.ast.FunctionNode;
import org.jcnc.snow.compiler.parser.ast.ParameterNode;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
@@ -31,10 +32,19 @@ public class FunctionBuilder {
// 声明函数参数
for (ParameterNode parameterNode : functionNode.parameters()) {
- // 在当前作用域中声明参数名称对应的虚拟寄存器
- irContext.getScope().declare(parameterNode.name());
+
+ /* 1) 为参数新建一个虚拟寄存器 */
+ IRVirtualRegister paramReg = irFunction.newRegister();
+
+ /* 2) 在当前作用域绑定 变量名 → 寄存器 */
+ irContext.getScope().declare(parameterNode.name(), paramReg);
+
+ /* 3) 记录到 IRFunction 的形参列表
+ —— 供 RegisterAllocator 在 slot 0,1,2… 分配 */
+ irFunction.addParameter(paramReg);
}
+
// 使用 StatementBuilder 遍历并生成函数体所有语句的 IR
StatementBuilder statementBuilder = new StatementBuilder(irContext);
for (StatementNode statementNode : functionNode.body()) {
diff --git a/src/main/java/org/jcnc/snow/compiler/ir/core/IRFunction.java b/src/main/java/org/jcnc/snow/compiler/ir/core/IRFunction.java
index 03138b4..37c6b3e 100644
--- a/src/main/java/org/jcnc/snow/compiler/ir/core/IRFunction.java
+++ b/src/main/java/org/jcnc/snow/compiler/ir/core/IRFunction.java
@@ -6,101 +6,82 @@ import java.util.ArrayList;
import java.util.List;
/**
- * IRFunction 表示一个函数级别的中间表示(IR)实体。
- *
- * 每个 IRFunction 包含:
- *
- *
函数名称
- *
函数体的指令列表
- *
用于分配虚拟寄存器编号的计数器
- *
+ * An intermediate-representation function (IRFunction).
*
- * 该类提供虚拟寄存器分配、指令添加和调试输出等功能。
+ *
For every source-level function we build one IRFunction that
+ * records:
+ *
+ *
the function’s name,
+ *
a list of IR instructions (the body),
+ *
a list of formal parameters (in declaration order),
+ *
a counter that hands out unique register numbers.
+ *
*/
public class IRFunction {
- /** 函数名称 */
+
+ /* ---------- basic info ---------- */
+
+ /** function name */
private final String name;
- /** 存放函数体中所有中间表示指令 */
+ /** linear list of IR instructions */
private final List body = new ArrayList<>();
- /**
- * 虚拟寄存器编号计数器;
- * 每调用一次 newRegister(),计数器自增并分配唯一编号
- */
+ /* ---------- virtual-register management ---------- */
+
+ /** running counter for new virtual registers */
private int regCounter = 0;
- /**
- * 构造函数,初始化 IRFunction 实例并设置函数名称。
- *
- * @param name 函数名称
- */
+ /** list of formal-parameter registers in declaration order */
+ private final List parameters = new ArrayList<>();
+
+ /* ---------- construction ---------- */
+
public IRFunction(String name) {
this.name = name;
}
- /**
- * 创建并返回一个新的虚拟寄存器。
- *
- * @return 分配了唯一编号的 IRVirtualRegister 实例
- */
+ /* ---------- register helpers ---------- */
+
+ /** Allocates and returns a brand-new virtual register. */
public IRVirtualRegister newRegister() {
return new IRVirtualRegister(regCounter++);
}
- /**
- * 向函数体中添加一条中间表示指令。
- *
- * @param irInstruction 要添加的 IRInstruction 对象
- */
- public void add(IRInstruction irInstruction) {
- body.add(irInstruction);
- }
+ /* ---------- parameter helpers ---------- */
/**
- * 获取函数体的所有中间表示指令。
- *
- * @return 包含所有 IRInstruction 的列表
+ * Adds a virtual register to the formal-parameter list.
+ * Call this once for each parameter in declaration order
+ * when building the IR.
*/
- public List body() {
- return body;
+ public void addParameter(IRVirtualRegister vr) {
+ parameters.add(vr);
}
- /**
- * 获取该函数的名称。
- *
- * @return 函数名称字符串
- */
- public String name() {
- return name;
+ /** Returns the immutable list of formal parameters. */
+ public List parameters() {
+ return parameters;
}
- /**
- * 获取当前已分配的虚拟寄存器数量。
- *
- * @return 当前的寄存器计数器值
- */
- public int registerCount() {
- return regCounter;
- }
+ /* ---------- body helpers ---------- */
- /**
- * 将整个 IRFunction 对象格式化为字符串,便于打印和调试。
- * 输出示例:
- *
- * func 方法名 {
- * 指令1
- * 指令2
- * ...
- * }
- *
- *
- * @return 格式化后的函数字符串表示
- */
- @Override
- public String toString() {
+ /** Appends an IR instruction to the function body. */
+ public void add(IRInstruction inst) { body.add(inst); }
+
+ /** Returns all instructions. */
+ public List body() { return body; }
+
+ /* ---------- meta data ---------- */
+
+ public String name() { return name; }
+ public int registerCount() { return regCounter; }
+
+ /* ---------- debugging ---------- */
+
+ @Override public String toString() {
StringBuilder sb = new StringBuilder("func " + name + " {\n");
- body.forEach(i -> sb.append(" ").append(i).append("\n"));
+ body.forEach(i -> sb.append(" ").append(i).append('\n'));
return sb.append('}').toString();
}
-}
\ No newline at end of file
+}
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 6c687e3..e466bc0 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
@@ -5,7 +5,8 @@ import org.jcnc.snow.vm.interfaces.Command;
import org.jcnc.snow.vm.module.*;
/**
- * CALL 指令 —— 创建新栈帧并跳转到目标地址。
+ * CALL 指令 —— 创建新栈帧并跳转到目标地址,同时把实参从操作数栈
+ * 写入被调函数的局部变量表(目前只处理 1 个参数)。
*/
public class CallCommand implements Command {
@@ -17,7 +18,7 @@ public class CallCommand implements Command {
CallStack callStack) {
/* ---------- 解析地址 ---------- */
- if (parts.length < 2) //
+ if (parts.length < 2)
throw new IllegalArgumentException("CALL: missing target address");
int targetAddr;
@@ -28,20 +29,27 @@ public class CallCommand implements Command {
}
/* ---------- 创建新栈帧 ---------- */
- // 为被调函数分配“独立”的局部变量表
LocalVariableStore calleeLVS = new LocalVariableStore(VMMode.RUN);
- // 若有调用约定,可在此把实参从 operandStack 弹入 calleeLVS
- // 例如:for (int i = nArgs-1; i >= 0; i--) calleeLVS.setVariable(i, operandStack.pop());
+ /* ---------- 处理 1 个实参 ---------- */
+ if (operandStack.isEmpty())
+ throw new IllegalStateException("CALL: operand stack empty, missing argument");
+ Object arg0 = operandStack.pop(); // 弹出栈顶实参
+ calleeLVS.setVariable(0, arg0); // 写入槽 0
+
+ /* 若将来有多个参数,可用:
+ for (int i = nArgs - 1; i >= 0; i--) {
+ calleeLVS.setVariable(i, operandStack.pop());
+ }
+ */
MethodContext ctx = new MethodContext("subroutine@" + targetAddr, null);
-
StackFrame newFrame = new StackFrame(currentPC + 1, calleeLVS, ctx);
callStack.pushFrame(newFrame);
System.out.println("Calling function at address: " + targetAddr);
/* ---------- 跳转 ---------- */
- return targetAddr; // 设置 PC = 目标函数入口
+ return targetAddr;
}
}
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 d62291c..af37032 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
@@ -4,37 +4,19 @@ import org.jcnc.snow.vm.interfaces.Command;
import org.jcnc.snow.vm.module.*;
/**
- * The {@code RetCommand} implements the {@code RET} instruction that returns
- * from a method in the virtual-machine execution flow.
+ * Implements the {@code RET} instruction (method return).
*
- *
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}.
- *
- *
Algorithm
- *
- *
Ensure the call stack is not empty (a {@code RET} with no frame is an
- * illegal state).
- *
Pop the current frame; clear its local variables.
- *
If the stack is now empty, return the sentinel value to terminate the
- * VM’s main loop.
- *
Otherwise, resume execution at the return address stored in the popped
- * frame.
- *
- *
- *
The VM’s main loop should exit when the program counter equals
- * {@code Integer.MAX_VALUE}.
+ *
Root-frame protection: The command first inspects the
+ * top frame without popping it. If the frame’s return address is {@code 0}
+ * (the root frame) the VM signals normal termination by returning
+ * {@link #PROGRAM_END}. The frame is not removed and its locals are
+ * kept intact. All other frames are popped and their locals cleared.
*/
public class RetCommand implements Command {
- /** Sentinel value indicating that execution should terminate. */
+ /** Sentinel value that tells the VM loop to terminate gracefully. */
private static final int PROGRAM_END = Integer.MAX_VALUE;
- public RetCommand() {
- /* no-op */
- }
-
@Override
public int execute(String[] parts,
int currentPC,
@@ -42,22 +24,23 @@ public class RetCommand implements Command {
LocalVariableStore localVariableStore,
CallStack callStack) {
- /* Guard: a RET with an empty stack is invalid. */
+ /* ----- Guard: must have at least one frame ----- */
if (callStack.isEmpty()) {
- throw new IllegalStateException("Call stack is empty. No function to return to.");
+ throw new IllegalStateException("RET: call stack is empty.");
}
- /* Pop the current frame and clear its locals. */
+ StackFrame topFrame = callStack.peekFrame();
+
+ /* ----- Root frame: do NOT pop, just end program ----- */
+ if (topFrame.getReturnAddress() == 0) {
+ System.out.println("Return 0");
+ return PROGRAM_END; // VM main loop should break
+ }
+
+ /* ----- Normal frame: pop & clean locals, then resume caller ----- */
StackFrame finished = callStack.popFrame();
finished.getLocalVariableStore().clearVariables();
- /* 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.
- }
-
- /* 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 d0d93a1..d696511 100644
--- a/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java
+++ b/src/main/java/org/jcnc/snow/vm/engine/VirtualMachineEngine.java
@@ -8,37 +8,36 @@ import java.util.List;
/**
* 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:
+ *
Interprets and executes a list of VM instructions while maintaining
+ * a program counter (PC) and the runtime data structures required for
+ * operand manipulation and method invocation.
- * 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.
+ * A root stack frame is pushed once via
+ * {@link #ensureRootFrame()} before the first instruction executes
+ * and is never popped. When a {@code RET} executed in the root frame
+ * returns {@link #PROGRAM_END}, the main loop exits gracefully.
*/
public class VirtualMachineEngine {
/* ---------- Constants ---------- */
- /** Sentinel PC value meaning “terminate the program gracefully”. */
+ /** Sentinel PC value that signals “terminate 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 OperandStack operandStack;
+ private final LocalVariableStore localVariableStore;
+ private final CallStack callStack;
private final CommandExecutionHandler commandExecutionHandler;
private int programCounter;
@@ -46,122 +45,122 @@ public class VirtualMachineEngine {
/* ---------- Construction ---------- */
/**
- * 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()}.
+ * Builds a VM engine with fresh runtime structures.
*
- * @param vmMode Operating mode (affects local-variable store policy).
+ * @param vmMode execution mode (DEBUG / RUN)
*/
public VirtualMachineEngine(VMMode vmMode) {
- this.operandStack = new OperandStack();
- this.callStack = new CallStack();
- this.localVariableStore = new LocalVariableStore(vmMode);
+ this.operandStack = new OperandStack();
+ this.callStack = new CallStack();
+ this.localVariableStore = new LocalVariableStore(vmMode); // shared with root frame
this.commandExecutionHandler =
- new CommandExecutionHandler(operandStack,
- localVariableStore,
- callStack);
+ new CommandExecutionHandler(operandStack, localVariableStore, callStack);
this.programCounter = 0;
}
- /* Package-private getter – used by debug helpers */
+ /* package-private accessor used by debug helpers */
CallStack getCallStack() { return callStack; }
/* ---------- Execution ---------- */
/**
- * Executes the supplied program in place.
+ * Executes the supplied program in place.
*
- * @param program list of textual instructions (opcode and operands
- * separated by spaces)
- * @throws IllegalArgumentException if {@code program} is {@code null}
- * or empty
+ * @param program textual instructions (“opcode arg1 arg2 …”)
+ * @throws IllegalArgumentException if {@code program} is null / empty
*/
public void execute(List program) {
- if (program == null || program.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. */
+ /* Ensure a single root frame is present. */
ensureRootFrame();
/* -------- Main interpreter loop -------- */
while (true) {
- /* ─── graceful termination ─── */
+ /* graceful termination */
if (programCounter == PROGRAM_END) break;
- /* ─── bounds check ─── */
+ /* 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 PC=" +
- 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);
+ int nextPC = commandExecutionHandler.handle(opCode, parts, programCounter);
- /* HALT (-1) or PROGRAM_END → exit VM */
+ /* HALT (-1) or PROGRAM_END → exit */
if (nextPC == -1 || nextPC == PROGRAM_END) break;
programCounter = nextPC;
} catch (IllegalArgumentException e) {
- System.err.println("Command error at PC=" +
- programCounter + " -> " + e.getMessage());
+ System.err.println("Command error at PC=" + programCounter + " -> " +
+ e.getMessage());
break;
}
}
+
+ /* ---------- compact root locals & print debug info ---------- */
+ if (!callStack.isEmpty()) {
+ LocalVariableStore rootLvs = callStack.peekFrame().getLocalVariableStore();
+ rootLvs.compact(); // trim leading / trailing null slots
+ }
+
+ printStack();
+ printLocalVariables();
}
/* ---------- Helper: ensure root frame ---------- */
/**
- * 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}.
+ * Pushes the root frame (returnAddress = 0) iff it isn’t there yet.
+ * This frame is never popped during normal execution.
*/
private void ensureRootFrame() {
- if (!callStack.isEmpty()) return; // already initialised
+ if (!callStack.isEmpty()) return; // already initialised
MethodContext rootCtx = new MethodContext("root", null);
- StackFrame root = new StackFrame(0, localVariableStore, rootCtx);
- callStack.pushFrame(root);
+ StackFrame rootFrame = new StackFrame(0, localVariableStore, rootCtx);
+ callStack.pushFrame(rootFrame);
}
/* ---------- Debug helpers ---------- */
+ /** Prints operand stack + call-stack snapshot. */
public void printStack() {
operandStack.printOperandStack();
callStack.printCallStack();
}
+ /** Prints the local-variable table of the current top frame. */
public void printLocalVariables() {
- localVariableStore.printLv();
+ if (callStack.isEmpty()) {
+ System.out.println("Local variable table is empty");
+ return;
+ }
+ callStack.peekFrame().getLocalVariableStore().printLv();
}
/* ---------- Utility ---------- */
- /** Converts the textual opcode to an integer. */
+ /** Parses textual opcode to 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);
}
}
}
diff --git a/src/main/java/org/jcnc/snow/vm/module/LocalVariableStore.java b/src/main/java/org/jcnc/snow/vm/module/LocalVariableStore.java
index d27b031..8384aff 100644
--- a/src/main/java/org/jcnc/snow/vm/module/LocalVariableStore.java
+++ b/src/main/java/org/jcnc/snow/vm/module/LocalVariableStore.java
@@ -7,96 +7,56 @@ import org.jcnc.snow.vm.engine.VMMode;
import java.util.ArrayList;
/**
- * The LocalVariableStore class represents the local variable store of the virtual machine.
- * This class manages the local variables of the virtual machine and can behave differently based on the specified execution mode (such as debug or run mode).
+ * The {@code LocalVariableStore} represents a simple dynamically-sized
+ * local-variable table (frame locals) of the VM.
*
- *
The local variable table is stored using an {@link ArrayList}, which supports dynamic resizing and setting values at specific index locations.
- *
- *
The local variable store provides basic operations, including setting and getting variable values, ensuring capacity, and printing the current state.
+ *
It supports random access via {@link #setVariable(int, Object)}
+ * / {@link #getVariable(int)} and can compact itself
+ * by trimming trailing {@code null} slots after execution has finished.
*/
public class LocalVariableStore {
+
private final ArrayList