实现函数调用
This commit is contained in:
parent
174aafb846
commit
07c040b40d
@ -1,52 +1,57 @@
|
||||
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.core.IRValue;
|
||||
import org.jcnc.snow.compiler.ir.core.*;
|
||||
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* RegisterAllocator —— 简单的线性扫描寄存器分配器(演示级实现)。
|
||||
* <p>
|
||||
* 该类用于为 IRFunction 中出现的所有虚拟寄存器(IRVirtualRegister)分配槽位编号。
|
||||
* 每个槽位编号对应虚拟机中的局部变量位置或栈槽。
|
||||
* </p>
|
||||
* <p>
|
||||
* 算法策略:顺序扫描所有 IR 指令,每遇到一个新的虚拟寄存器就分配一个递增编号。
|
||||
* 不考虑寄存器生存期重叠、重用或优化,仅做简单映射。
|
||||
* </p>
|
||||
* A <em>very</em> simple linear-scan register allocator.
|
||||
*
|
||||
* <p>Strategy:</p>
|
||||
* <ol>
|
||||
* <li>Assign <strong>formal parameters</strong> to slots
|
||||
* 0 .. n-1 (in declaration order).</li>
|
||||
* <li>Scan the instruction list; every unseen virtual register
|
||||
* gets the next free slot number.</li>
|
||||
* </ol>
|
||||
* <p>No liveness analysis, no spilling – demo-grade only.</p>
|
||||
*/
|
||||
public final class RegisterAllocator {
|
||||
|
||||
/** 虚拟寄存器 → 槽位编号 的映射表 */
|
||||
private final Map<IRVirtualRegister, Integer> map = new HashMap<>();
|
||||
private final Map<IRVirtualRegister,Integer> 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<IRVirtualRegister, Integer> allocate(IRFunction fn) {
|
||||
int next = 0; // 当前可分配的下一个槽位编号
|
||||
public Map<IRVirtualRegister,Integer> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -6,101 +6,82 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IRFunction 表示一个函数级别的中间表示(IR)实体。
|
||||
* <p>
|
||||
* 每个 IRFunction 包含:
|
||||
* <ul>
|
||||
* <li>函数名称</li>
|
||||
* <li>函数体的指令列表</li>
|
||||
* <li>用于分配虚拟寄存器编号的计数器</li>
|
||||
* </ul>
|
||||
* An intermediate-representation function (IRFunction).
|
||||
*
|
||||
* 该类提供虚拟寄存器分配、指令添加和调试输出等功能。
|
||||
* <p>For every source-level function we build one IRFunction that
|
||||
* records:</p>
|
||||
* <ul>
|
||||
* <li>the function’s name,</li>
|
||||
* <li>a list of IR instructions (the <em>body</em>),</li>
|
||||
* <li>a list of <strong>formal parameters</strong> (in declaration order),</li>
|
||||
* <li>a counter that hands out unique register numbers.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class IRFunction {
|
||||
/** 函数名称 */
|
||||
|
||||
/* ---------- basic info ---------- */
|
||||
|
||||
/** function name */
|
||||
private final String name;
|
||||
|
||||
/** 存放函数体中所有中间表示指令 */
|
||||
/** linear list of IR instructions */
|
||||
private final List<IRInstruction> 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<IRVirtualRegister> 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 <em>in declaration order</em>
|
||||
* when building the IR.
|
||||
*/
|
||||
public List<IRInstruction> 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<IRVirtualRegister> parameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前已分配的虚拟寄存器数量。
|
||||
*
|
||||
* @return 当前的寄存器计数器值
|
||||
*/
|
||||
public int registerCount() {
|
||||
return regCounter;
|
||||
}
|
||||
/* ---------- body helpers ---------- */
|
||||
|
||||
/**
|
||||
* 将整个 IRFunction 对象格式化为字符串,便于打印和调试。
|
||||
* 输出示例:
|
||||
* <pre>
|
||||
* func 方法名 {
|
||||
* 指令1
|
||||
* 指令2
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @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<IRInstruction> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) // <op> <addr>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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).
|
||||
*
|
||||
* <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>
|
||||
*
|
||||
* <h2>Algorithm</h2>
|
||||
* <ol>
|
||||
* <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>
|
||||
* <p><strong>Root-frame protection:</strong> 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 <em>not</em> removed and its locals are
|
||||
* kept intact. All other frames are popped and their locals cleared.</p>
|
||||
*/
|
||||
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;
|
||||
|
||||
@ -8,37 +8,36 @@ import java.util.List;
|
||||
/**
|
||||
* 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>
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <ul>
|
||||
* <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>
|
||||
* <li>{@link OperandStack} — stores intermediate values</li>
|
||||
* <li>{@link LocalVariableStore} — holds locals for the <em>current</em>
|
||||
* stack frame</li>
|
||||
* <li>{@link CallStack} — manages stack frames and return addresses</li>
|
||||
* <li>{@link CommandExecutionHandler} — dispatches opcodes</li>
|
||||
* </ul>
|
||||
*
|
||||
* <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.
|
||||
* A <strong>root stack frame</strong> is pushed <em>once</em> 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 <i>in place</i>.
|
||||
* Executes the supplied program <em>in place</em>.
|
||||
*
|
||||
* @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<String> 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
|
||||
* <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}.
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 (<em>frame locals</em>) of the VM.
|
||||
*
|
||||
* <p>The local variable table is stored using an {@link ArrayList}, which supports dynamic resizing and setting values at specific index locations.</p>
|
||||
*
|
||||
* <p>The local variable store provides basic operations, including setting and getting variable values, ensuring capacity, and printing the current state.</p>
|
||||
* <p>It supports random access via {@link #setVariable(int, Object)}
|
||||
* / {@link #getVariable(int)} and can <strong>compact</strong> itself
|
||||
* by trimming trailing {@code null} slots after execution has finished.</p>
|
||||
*/
|
||||
public class LocalVariableStore {
|
||||
|
||||
private final ArrayList<Object> localVariables;
|
||||
private final VMMode vmMode;
|
||||
|
||||
/**
|
||||
* Constructor that initializes the local variable store with a specified initial capacity and execution mode.
|
||||
*
|
||||
* @param vmMode The current virtual machine mode (e.g., debug or run mode).
|
||||
* @param initialCapacity The initial capacity determining the starting size of the local variable table.
|
||||
*/
|
||||
/* ---------- construction ---------- */
|
||||
|
||||
public LocalVariableStore(VMMode vmMode, int initialCapacity) {
|
||||
this.localVariables = new ArrayList<>(initialCapacity);
|
||||
this.vmMode = vmMode;
|
||||
handleMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that initializes the local variable store without specifying initial capacity but with a specified execution mode.
|
||||
*
|
||||
* @param vmMode The current virtual machine mode (e.g., debug or run mode).
|
||||
*/
|
||||
public LocalVariableStore(VMMode vmMode) {
|
||||
this.localVariables = new ArrayList<>(); // Default capacity of 10
|
||||
this.localVariables = new ArrayList<>();
|
||||
this.vmMode = vmMode;
|
||||
handleMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the local variable value at the specified index.
|
||||
*
|
||||
* <p>If the index exceeds the current size of the local variable table, the table is automatically expanded to accommodate the index.</p>
|
||||
*
|
||||
* @param index The index position.
|
||||
* @param value The value to be set for the local variable.
|
||||
*/
|
||||
/* ---------- public API ---------- */
|
||||
|
||||
/** Sets the value at {@code index}, expanding the list if necessary. */
|
||||
public void setVariable(int index, Object value) {
|
||||
// Ensure the local variable table has enough capacity to accommodate the specified index
|
||||
ensureCapacity(index + 1);
|
||||
localVariables.set(index, value); // Set the local variable value
|
||||
localVariables.set(index, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the local variable value at the specified index.
|
||||
*
|
||||
* @param index The index position.
|
||||
* @return The value of the local variable.
|
||||
* @throws IndexOutOfBoundsException If the index is invalid, an exception is thrown.
|
||||
*/
|
||||
/** Returns the value at {@code index}. */
|
||||
public Object getVariable(int index) {
|
||||
if (index < 0 || index >= localVariables.size()) {
|
||||
throw new IndexOutOfBoundsException("Invalid index: " + index + ", current size of table: " + localVariables.size());
|
||||
throw new IndexOutOfBoundsException(
|
||||
"Invalid index: " + index +
|
||||
", current size of table: " + localVariables.size());
|
||||
}
|
||||
return localVariables.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands the capacity of the local variable table to ensure it can hold at least the specified number of elements.
|
||||
*
|
||||
* @param minCapacity The minimum required capacity.
|
||||
*/
|
||||
private void ensureCapacity(int minCapacity) {
|
||||
if (minCapacity > localVariables.size()) {
|
||||
localVariables.ensureCapacity(minCapacity); // Expand the ArrayList capacity
|
||||
while (localVariables.size() < minCapacity) {
|
||||
localVariables.add(null); // Fill remaining space with null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the complete data of the local variable table.
|
||||
*
|
||||
* @return The ArrayList containing the local variables.
|
||||
*/
|
||||
/** Exposes the backing list (read-only preferred). */
|
||||
public ArrayList<Object> getLocalVariables() {
|
||||
return this.localVariables;
|
||||
return localVariables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the current state of the local variable table.
|
||||
*
|
||||
* <p>If the local variable table is empty, a message is logged indicating that. Otherwise, each local variable's index and value are printed.</p>
|
||||
*/
|
||||
/** Prints every slot to the logger. */
|
||||
public void printLv() {
|
||||
if (localVariables.isEmpty()) {
|
||||
LoggingUtils.logInfo("Local variable table is empty", "");
|
||||
@ -104,33 +64,54 @@ public class LocalVariableStore {
|
||||
}
|
||||
LoggingUtils.logInfo("Local Variable Table:", "");
|
||||
for (int i = 0; i < localVariables.size(); i++) {
|
||||
LoggingUtils.logInfo("", String.format("%d: %s", i, localVariables.get(i)));
|
||||
LoggingUtils.logInfo("",
|
||||
String.format("%d: %s", i, localVariables.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the local variable table, removing all local variables.
|
||||
*
|
||||
* <p>This method removes all entries from the local variable table, effectively resetting the table.</p>
|
||||
*/
|
||||
/** Clears all variables (used when a stack frame is popped). */
|
||||
public void clearVariables() {
|
||||
localVariables.clear(); // Clears all elements in the ArrayList
|
||||
localVariables.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes specific operations based on the virtual machine's execution mode.
|
||||
*
|
||||
* <p>This method performs different actions depending on the current mode, such as displaying the local variable table in debug mode.</p>
|
||||
* Compacts the table by <b>removing trailing {@code null} slots</b>.
|
||||
* <p>Call this once after program termination (e.g. in
|
||||
* {@code VirtualMachineEngine.execute()} before printing) to get
|
||||
* cleaner debug output without affecting execution-time indices.</p>
|
||||
*/
|
||||
public void compact() {
|
||||
/* 1) 去掉尾部 null */
|
||||
for (int i = localVariables.size() - 1; i >= 0; i--) {
|
||||
if (localVariables.get(i) != null) break;
|
||||
localVariables.remove(i);
|
||||
}
|
||||
/* 2) 去掉前导 null */
|
||||
while (!localVariables.isEmpty() && localVariables.getFirst() == null) {
|
||||
localVariables.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ---------- internal helpers ---------- */
|
||||
|
||||
/** Ensures backing list can hold {@code minCapacity} slots. */
|
||||
private void ensureCapacity(int minCapacity) {
|
||||
if (minCapacity > localVariables.size()) {
|
||||
localVariables.ensureCapacity(minCapacity);
|
||||
while (localVariables.size() < minCapacity) {
|
||||
localVariables.add(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Mode-specific UI hook (unchanged). */
|
||||
private void handleMode() {
|
||||
switch (vmMode) {
|
||||
case DEBUG:
|
||||
// TODO: "If you need to build a project with graalvm into a native image, you do not need to annotate nether code
|
||||
LocalVariableStoreSwing.display(this, "Local Variable Table"); // Display local variable table in debug mode
|
||||
break;
|
||||
case RUN:
|
||||
default:
|
||||
break;
|
||||
case DEBUG ->
|
||||
LocalVariableStoreSwing.display(this, "Local Variable Table");
|
||||
case RUN -> { /* no-op */ }
|
||||
default -> { /* no-op */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
test
8
test
@ -8,7 +8,7 @@ module: CommonTasks
|
||||
body:
|
||||
num1 = 10
|
||||
num2=1
|
||||
return num1 + CommonTasks.test(1)
|
||||
return num1 + CommonTasks.test(2)
|
||||
end body
|
||||
end function
|
||||
|
||||
@ -17,8 +17,10 @@ module: CommonTasks
|
||||
declare num1:int
|
||||
return_type:int
|
||||
body:
|
||||
num1 =4
|
||||
return num1
|
||||
declare result : int
|
||||
declare num2: int =1
|
||||
result = num2 + num1
|
||||
return result
|
||||
end body
|
||||
end function
|
||||
end module
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user