实现函数调用

This commit is contained in:
Luke 2025-05-11 00:13:11 +08:00
parent 174aafb846
commit 07c040b40d
8 changed files with 252 additions and 283 deletions

View File

@ -1,52 +1,57 @@
package org.jcnc.snow.compiler.backend; package org.jcnc.snow.compiler.backend;
import org.jcnc.snow.compiler.ir.core.IRFunction; import org.jcnc.snow.compiler.ir.core.*;
import org.jcnc.snow.compiler.ir.core.IRInstruction;
import org.jcnc.snow.compiler.ir.core.IRValue;
import org.jcnc.snow.compiler.ir.value.IRVirtualRegister; import org.jcnc.snow.compiler.ir.value.IRVirtualRegister;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* RegisterAllocator 简单的线性扫描寄存器分配器演示级实现 * A <em>very</em> simple linear-scan register allocator.
* <p> *
* 该类用于为 IRFunction 中出现的所有虚拟寄存器IRVirtualRegister分配槽位编号 * <p>Strategy:</p>
* 每个槽位编号对应虚拟机中的局部变量位置或栈槽 * <ol>
* </p> * <li>Assign <strong>formal parameters</strong> to slots
* <p> * 0&nbsp;..&nbsp;n-1 (in declaration order).</li>
* 算法策略顺序扫描所有 IR 指令每遇到一个新的虚拟寄存器就分配一个递增编号 * <li>Scan the instruction list; every unseen virtual register
* 不考虑寄存器生存期重叠重用或优化仅做简单映射 * gets the next free slot number.</li>
* </p> * </ol>
* <p>No liveness analysis, no spilling demo-grade only.</p>
*/ */
public final class RegisterAllocator { public final class RegisterAllocator {
/** 虚拟寄存器 → 槽位编号 的映射表 */ private final Map<IRVirtualRegister,Integer> map = new HashMap<>();
private final Map<IRVirtualRegister, Integer> map = new HashMap<>();
/** /**
* 执行寄存器分配将函数中所有虚拟寄存器映射为连续的整型槽位编号 * Computes a registerslot mapping for the given function.
* *
* @param fn 中间表示形式的函数IRFunction * @param fn IRFunction to allocate
* @return 映射表IRVirtualRegister 槽位编号 * @return mapping table (immutable)
*/ */
public Map<IRVirtualRegister, Integer> allocate(IRFunction fn) { public Map<IRVirtualRegister,Integer> allocate(IRFunction fn) {
int next = 0; // 当前可分配的下一个槽位编号
for (IRInstruction i : fn.body()) { int next = 0;
// 若该指令有目标寄存器dest且尚未分配槽位 分配一个
if (i.dest() != null && !map.containsKey(i.dest())) { /* ---------- 1) parameters first ---------- */
map.put(i.dest(), next++); 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 : inst.operands()) {
for (IRValue v : i.operands()) { if (v instanceof IRVirtualRegister vr &&
if (v instanceof IRVirtualRegister r && !map.containsKey(r)) { !map.containsKey(vr)) {
map.put(r, next++); map.put(vr, next++);
} }
} }
} }
return map; return Map.copyOf(map);
} }
} }

View File

@ -1,6 +1,7 @@
package org.jcnc.snow.compiler.ir.builder; package org.jcnc.snow.compiler.ir.builder;
import org.jcnc.snow.compiler.ir.core.IRFunction; 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.FunctionNode;
import org.jcnc.snow.compiler.parser.ast.ParameterNode; import org.jcnc.snow.compiler.parser.ast.ParameterNode;
import org.jcnc.snow.compiler.parser.ast.base.StatementNode; import org.jcnc.snow.compiler.parser.ast.base.StatementNode;
@ -31,10 +32,19 @@ public class FunctionBuilder {
// 声明函数参数 // 声明函数参数
for (ParameterNode parameterNode : functionNode.parameters()) { 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 遍历并生成函数体所有语句的 IR
StatementBuilder statementBuilder = new StatementBuilder(irContext); StatementBuilder statementBuilder = new StatementBuilder(irContext);
for (StatementNode statementNode : functionNode.body()) { for (StatementNode statementNode : functionNode.body()) {

View File

@ -6,101 +6,82 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* IRFunction 表示一个函数级别的中间表示IR实体 * An intermediate-representation function (IRFunction).
* <p>
* 每个 IRFunction 包含
* <ul>
* <li>函数名称</li>
* <li>函数体的指令列表</li>
* <li>用于分配虚拟寄存器编号的计数器</li>
* </ul>
* *
* 该类提供虚拟寄存器分配指令添加和调试输出等功能 * <p>For every source-level function we build one IRFunction that
* records:</p>
* <ul>
* <li>the functions 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 { public class IRFunction {
/** 函数名称 */
/* ---------- basic info ---------- */
/** function name */
private final String name; private final String name;
/** 存放函数体中所有中间表示指令 */ /** linear list of IR instructions */
private final List<IRInstruction> body = new ArrayList<>(); private final List<IRInstruction> body = new ArrayList<>();
/** /* ---------- virtual-register management ---------- */
* 虚拟寄存器编号计数器
* 每调用一次 newRegister()计数器自增并分配唯一编号 /** running counter for new virtual registers */
*/
private int regCounter = 0; private int regCounter = 0;
/** /** list of formal-parameter registers in declaration order */
* 构造函数初始化 IRFunction 实例并设置函数名称 private final List<IRVirtualRegister> parameters = new ArrayList<>();
*
* @param name 函数名称 /* ---------- construction ---------- */
*/
public IRFunction(String name) { public IRFunction(String name) {
this.name = name; this.name = name;
} }
/** /* ---------- register helpers ---------- */
* 创建并返回一个新的虚拟寄存器
* /** Allocates and returns a brand-new virtual register. */
* @return 分配了唯一编号的 IRVirtualRegister 实例
*/
public IRVirtualRegister newRegister() { public IRVirtualRegister newRegister() {
return new IRVirtualRegister(regCounter++); return new IRVirtualRegister(regCounter++);
} }
/** /* ---------- parameter helpers ---------- */
* 向函数体中添加一条中间表示指令
*
* @param irInstruction 要添加的 IRInstruction 对象
*/
public void add(IRInstruction irInstruction) {
body.add(irInstruction);
}
/** /**
* 获取函数体的所有中间表示指令 * Adds a virtual register to the formal-parameter list.
* * Call this once for each parameter <em>in declaration order</em>
* @return 包含所有 IRInstruction 的列表 * when building the IR.
*/ */
public List<IRInstruction> body() { public void addParameter(IRVirtualRegister vr) {
return body; parameters.add(vr);
} }
/** /** Returns the immutable list of formal parameters. */
* 获取该函数的名称 public List<IRVirtualRegister> parameters() {
* return parameters;
* @return 函数名称字符串
*/
public String name() {
return name;
} }
/** /* ---------- body helpers ---------- */
* 获取当前已分配的虚拟寄存器数量
*
* @return 当前的寄存器计数器值
*/
public int registerCount() {
return regCounter;
}
/** /** Appends an IR instruction to the function body. */
* 将整个 IRFunction 对象格式化为字符串便于打印和调试 public void add(IRInstruction inst) { body.add(inst); }
* 输出示例
* <pre> /** Returns all instructions. */
* func 方法名 { public List<IRInstruction> body() { return body; }
* 指令1
* 指令2 /* ---------- meta data ---------- */
* ...
* } public String name() { return name; }
* </pre> public int registerCount() { return regCounter; }
*
* @return 格式化后的函数字符串表示 /* ---------- debugging ---------- */
*/
@Override @Override public String toString() {
public String toString() {
StringBuilder sb = new StringBuilder("func " + name + " {\n"); 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(); return sb.append('}').toString();
} }
} }

View File

@ -5,7 +5,8 @@ import org.jcnc.snow.vm.interfaces.Command;
import org.jcnc.snow.vm.module.*; import org.jcnc.snow.vm.module.*;
/** /**
* CALL 指令 创建新栈帧并跳转到目标地址 * CALL 指令 创建新栈帧并跳转到目标地址同时把实参从操作数栈
* 写入被调函数的局部变量表目前只处理 1 个参数
*/ */
public class CallCommand implements Command { public class CallCommand implements Command {
@ -17,7 +18,7 @@ public class CallCommand implements Command {
CallStack callStack) { CallStack callStack) {
/* ---------- 解析地址 ---------- */ /* ---------- 解析地址 ---------- */
if (parts.length < 2) // <op> <addr> if (parts.length < 2)
throw new IllegalArgumentException("CALL: missing target address"); throw new IllegalArgumentException("CALL: missing target address");
int targetAddr; int targetAddr;
@ -28,20 +29,27 @@ public class CallCommand implements Command {
} }
/* ---------- 创建新栈帧 ---------- */ /* ---------- 创建新栈帧 ---------- */
// 为被调函数分配独立的局部变量表
LocalVariableStore calleeLVS = new LocalVariableStore(VMMode.RUN); LocalVariableStore calleeLVS = new LocalVariableStore(VMMode.RUN);
// 若有调用约定可在此把实参从 operandStack 弹入 calleeLVS /* ---------- 处理 1 个实参 ---------- */
// 例如for (int i = nArgs-1; i >= 0; i--) calleeLVS.setVariable(i, operandStack.pop()); 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); MethodContext ctx = new MethodContext("subroutine@" + targetAddr, null);
StackFrame newFrame = new StackFrame(currentPC + 1, calleeLVS, ctx); StackFrame newFrame = new StackFrame(currentPC + 1, calleeLVS, ctx);
callStack.pushFrame(newFrame); callStack.pushFrame(newFrame);
System.out.println("Calling function at address: " + targetAddr); System.out.println("Calling function at address: " + targetAddr);
/* ---------- 跳转 ---------- */ /* ---------- 跳转 ---------- */
return targetAddr; // 设置 PC = 目标函数入口 return targetAddr;
} }
} }

View File

@ -4,37 +4,19 @@ import org.jcnc.snow.vm.interfaces.Command;
import org.jcnc.snow.vm.module.*; import org.jcnc.snow.vm.module.*;
/** /**
* The {@code RetCommand} implements the {@code RET} instruction that returns * Implements the {@code RET} instruction (method return).
* from a method in the virtual-machine execution flow.
* *
* <p><strong>Root-frame protection:</strong> after popping the current frame the * <p><strong>Root-frame protection:</strong> The command first inspects the
* command checks {@link CallStack#isEmpty()}. If the stack became empty, the * top frame without popping it. If the frames return address is {@code 0}
* popped frame must have been the root frame; the command therefore signals * (the root frame) the VM signals normal termination by returning
* normal program termination by returning {@link Integer#MAX_VALUE}.</p> * {@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>
* <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
* VMs main loop.</li>
* <li>Otherwise, resume execution at the return address stored in the popped
* frame.</li>
* </ol>
*
* <p>The VMs main loop should exit when the program counter equals
* {@code Integer.MAX_VALUE}.</p>
*/ */
public class RetCommand implements Command { 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; private static final int PROGRAM_END = Integer.MAX_VALUE;
public RetCommand() {
/* no-op */
}
@Override @Override
public int execute(String[] parts, public int execute(String[] parts,
int currentPC, int currentPC,
@ -42,22 +24,23 @@ public class RetCommand implements Command {
LocalVariableStore localVariableStore, LocalVariableStore localVariableStore,
CallStack callStack) { CallStack callStack) {
/* Guard: a RET with an empty stack is invalid. */ /* ----- Guard: must have at least one frame ----- */
if (callStack.isEmpty()) { 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(); StackFrame finished = callStack.popFrame();
finished.getLocalVariableStore().clearVariables(); 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 callers saved PC. */
int returnAddr = finished.getReturnAddress(); int returnAddr = finished.getReturnAddress();
System.out.println("Return " + returnAddr); System.out.println("Return " + returnAddr);
return returnAddr; return returnAddr;

View File

@ -8,37 +8,36 @@ import java.util.List;
/** /**
* Virtual-Machine Engine ({@code VirtualMachineEngine}) * Virtual-Machine Engine ({@code VirtualMachineEngine})
* *
* <p>This class interprets and executes a list of VM instructions. * <p>Interprets and executes a list of VM instructions while maintaining
* It maintains a program counter (PC) together with <em>core * a program counter (PC) and the runtime data structures required for
* runtime structures</em>:</p> * operand manipulation and method invocation.</p>
* *
* <ul> * <ul>
* <li>{@link OperandStack} stores intermediate values;</li> * <li>{@link OperandStack} stores intermediate values</li>
* <li>{@link LocalVariableStore} holds local variables for the * <li>{@link LocalVariableStore} holds locals for the <em>current</em>
* <em>current</em> stack frame;</li> * stack frame</li>
* <li>{@link CallStack} keeps stack frames and return addresses;</li> * <li>{@link CallStack} manages stack frames and return addresses</li>
* <li>{@link CommandExecutionHandler} dispatches opcodes to the * <li>{@link CommandExecutionHandler} dispatches opcodes</li>
* corresponding {@link org.jcnc.snow.vm.interfaces.Command}.</li>
* </ul> * </ul>
* *
* <h2>Root-frame contract</h2> * <h2>Root-frame contract</h2>
* A <strong>root stack frame</strong> is pushed <em>once</em> (via * A <strong>root stack frame</strong> is pushed <em>once</em> via
* {@link #ensureRootFrame()}) before execution starts and is never popped. * {@link #ensureRootFrame()} before the first instruction executes
* When a {@code RET} executed in the root frame signals * and is never popped. When a {@code RET} executed in the root frame
* {@link #PROGRAM_END}, the main loop exits gracefully. * returns {@link #PROGRAM_END}, the main loop exits gracefully.
*/ */
public class VirtualMachineEngine { public class VirtualMachineEngine {
/* ---------- Constants ---------- */ /* ---------- 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; private static final int PROGRAM_END = Integer.MAX_VALUE;
/* ---------- Runtime state ---------- */ /* ---------- Runtime state ---------- */
private final OperandStack operandStack; private final OperandStack operandStack;
private final LocalVariableStore localVariableStore; private final LocalVariableStore localVariableStore;
private final CallStack callStack; private final CallStack callStack;
private final CommandExecutionHandler commandExecutionHandler; private final CommandExecutionHandler commandExecutionHandler;
private int programCounter; private int programCounter;
@ -46,122 +45,122 @@ public class VirtualMachineEngine {
/* ---------- Construction ---------- */ /* ---------- Construction ---------- */
/** /**
* Constructs the virtual-machine engine and initialises its runtime * Builds a VM engine with fresh runtime structures.
* structures. The program counter is set to {@code 0}; the root frame
* will be created lazily by {@link #ensureRootFrame()}.
* *
* @param vmMode Operating mode (affects local-variable store policy). * @param vmMode execution mode (DEBUG / RUN)
*/ */
public VirtualMachineEngine(VMMode vmMode) { public VirtualMachineEngine(VMMode vmMode) {
this.operandStack = new OperandStack(); this.operandStack = new OperandStack();
this.callStack = new CallStack(); this.callStack = new CallStack();
this.localVariableStore = new LocalVariableStore(vmMode); this.localVariableStore = new LocalVariableStore(vmMode); // shared with root frame
this.commandExecutionHandler = this.commandExecutionHandler =
new CommandExecutionHandler(operandStack, new CommandExecutionHandler(operandStack, localVariableStore, callStack);
localVariableStore,
callStack);
this.programCounter = 0; this.programCounter = 0;
} }
/* Package-private getter used by debug helpers */ /* package-private accessor used by debug helpers */
CallStack getCallStack() { return callStack; } CallStack getCallStack() { return callStack; }
/* ---------- Execution ---------- */ /* ---------- 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 * @param program textual instructions (opcode arg1 arg2 )
* separated by spaces) * @throws IllegalArgumentException if {@code program} is null / empty
* @throws IllegalArgumentException if {@code program} is {@code null}
* or empty
*/ */
public void execute(List<String> program) { public void execute(List<String> program) {
if (program == null || program.isEmpty()) { if (program == null || program.isEmpty())
throw new IllegalArgumentException( throw new IllegalArgumentException("The command list cannot be empty or null.");
"The command list cannot be empty or null.");
}
/* Ensure the root frame is present exactly once. */ /* Ensure a single root frame is present. */
ensureRootFrame(); ensureRootFrame();
/* -------- Main interpreter loop -------- */ /* -------- Main interpreter loop -------- */
while (true) { while (true) {
/* ─── graceful termination ─── */ /* graceful termination */
if (programCounter == PROGRAM_END) break; if (programCounter == PROGRAM_END) break;
/* ─── bounds check ─── */ /* bounds check */
if (programCounter < 0 || programCounter >= program.size()) break; if (programCounter < 0 || programCounter >= program.size()) break;
String instruction = program.get(programCounter); String instruction = program.get(programCounter);
String[] parts = instruction.trim().split(" "); String[] parts = instruction.trim().split(" ");
if (parts.length < 1) { if (parts.length < 1) {
System.err.println("Invalid command format at PC=" + System.err.println("Invalid command format at PC=" + programCounter +
programCounter + " -> Missing opcode"); " -> Missing opcode");
break; break;
} }
try { try {
int opCode = parseOpCode(parts[0]); int opCode = parseOpCode(parts[0]);
int nextPC = int nextPC = commandExecutionHandler.handle(opCode, parts, programCounter);
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; if (nextPC == -1 || nextPC == PROGRAM_END) break;
programCounter = nextPC; programCounter = nextPC;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
System.err.println("Command error at PC=" + System.err.println("Command error at PC=" + programCounter + " -> " +
programCounter + " -> " + e.getMessage()); e.getMessage());
break; 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 ---------- */ /* ---------- Helper: ensure root frame ---------- */
/** /**
* Pushes a root stack-frame if none exists. This frame has * Pushes the root frame (returnAddress = 0) iff it isnt there yet.
* <code>returnAddress&nbsp;=&nbsp;0</code> and an empty * This frame is never popped during normal execution.
* {@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() { private void ensureRootFrame() {
if (!callStack.isEmpty()) return; // already initialised if (!callStack.isEmpty()) return; // already initialised
MethodContext rootCtx = new MethodContext("root", null); MethodContext rootCtx = new MethodContext("root", null);
StackFrame root = new StackFrame(0, localVariableStore, rootCtx); StackFrame rootFrame = new StackFrame(0, localVariableStore, rootCtx);
callStack.pushFrame(root); callStack.pushFrame(rootFrame);
} }
/* ---------- Debug helpers ---------- */ /* ---------- Debug helpers ---------- */
/** Prints operand stack + call-stack snapshot. */
public void printStack() { public void printStack() {
operandStack.printOperandStack(); operandStack.printOperandStack();
callStack.printCallStack(); callStack.printCallStack();
} }
/** Prints the local-variable table of the current top frame. */
public void printLocalVariables() { public void printLocalVariables() {
localVariableStore.printLv(); if (callStack.isEmpty()) {
System.out.println("Local variable table is empty");
return;
}
callStack.peekFrame().getLocalVariableStore().printLv();
} }
/* ---------- Utility ---------- */ /* ---------- Utility ---------- */
/** Converts the textual opcode to an integer. */ /** Parses textual opcode to integer. */
private int parseOpCode(String opCodeStr) { private int parseOpCode(String opCodeStr) {
try { try {
return Integer.parseInt(opCodeStr); return Integer.parseInt(opCodeStr);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new IllegalArgumentException( throw new IllegalArgumentException("Invalid opcode -> " + opCodeStr, e);
"Invalid opcode -> " + opCodeStr, e);
} }
} }
} }

View File

@ -7,96 +7,56 @@ import org.jcnc.snow.vm.engine.VMMode;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
* The LocalVariableStore class represents the local variable store of the virtual machine. * The {@code LocalVariableStore} represents a simple dynamically-sized
* 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). * 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>It supports random access via {@link #setVariable(int, Object)}
* * / {@link #getVariable(int)} and can <strong>compact</strong> itself
* <p>The local variable store provides basic operations, including setting and getting variable values, ensuring capacity, and printing the current state.</p> * by trimming trailing {@code null} slots after execution has finished.</p>
*/ */
public class LocalVariableStore { public class LocalVariableStore {
private final ArrayList<Object> localVariables; private final ArrayList<Object> localVariables;
private final VMMode vmMode; private final VMMode vmMode;
/** /* ---------- construction ---------- */
* 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.
*/
public LocalVariableStore(VMMode vmMode, int initialCapacity) { public LocalVariableStore(VMMode vmMode, int initialCapacity) {
this.localVariables = new ArrayList<>(initialCapacity); this.localVariables = new ArrayList<>(initialCapacity);
this.vmMode = vmMode; this.vmMode = vmMode;
handleMode(); 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) { public LocalVariableStore(VMMode vmMode) {
this.localVariables = new ArrayList<>(); // Default capacity of 10 this.localVariables = new ArrayList<>();
this.vmMode = vmMode; this.vmMode = vmMode;
handleMode(); handleMode();
} }
/** /* ---------- public API ---------- */
* Sets the local variable value at the specified index.
* /** Sets the value at {@code index}, expanding the list if necessary. */
* <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 void setVariable(int index, Object value) { public void setVariable(int index, Object value) {
// Ensure the local variable table has enough capacity to accommodate the specified index
ensureCapacity(index + 1); ensureCapacity(index + 1);
localVariables.set(index, value); // Set the local variable value localVariables.set(index, value);
} }
/** /** Returns the value at {@code index}. */
* 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.
*/
public Object getVariable(int index) { public Object getVariable(int index) {
if (index < 0 || index >= localVariables.size()) { 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); return localVariables.get(index);
} }
/** /** Exposes the backing list (read-only preferred). */
* 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.
*/
public ArrayList<Object> getLocalVariables() { public ArrayList<Object> getLocalVariables() {
return this.localVariables; return localVariables;
} }
/** /** Prints every slot to the logger. */
* 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>
*/
public void printLv() { public void printLv() {
if (localVariables.isEmpty()) { if (localVariables.isEmpty()) {
LoggingUtils.logInfo("Local variable table is empty", ""); LoggingUtils.logInfo("Local variable table is empty", "");
@ -104,33 +64,54 @@ public class LocalVariableStore {
} }
LoggingUtils.logInfo("Local Variable Table:", ""); LoggingUtils.logInfo("Local Variable Table:", "");
for (int i = 0; i < localVariables.size(); i++) { 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 all variables (used when a stack frame is popped). */
* 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>
*/
public void clearVariables() { public void clearVariables() {
localVariables.clear(); // Clears all elements in the ArrayList localVariables.clear();
} }
/** /**
* Executes specific operations based on the virtual machine's execution mode. * Compacts the table by <b>removing trailing {@code null} slots</b>.
* * <p>Call this once after program termination (e.g. in
* <p>This method performs different actions depending on the current mode, such as displaying the local variable table in debug mode.</p> * {@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() { private void handleMode() {
switch (vmMode) { switch (vmMode) {
case DEBUG: 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");
LocalVariableStoreSwing.display(this, "Local Variable Table"); // Display local variable table in debug mode case RUN -> { /* no-op */ }
break; default -> { /* no-op */ }
case RUN:
default:
break;
} }
} }
} }

8
test
View File

@ -8,7 +8,7 @@ module: CommonTasks
body: body:
num1 = 10 num1 = 10
num2=1 num2=1
return num1 + CommonTasks.test(1) return num1 + CommonTasks.test(2)
end body end body
end function end function
@ -17,8 +17,10 @@ module: CommonTasks
declare num1:int declare num1:int
return_type:int return_type:int
body: body:
num1 =4 declare result : int
return num1 declare num2: int =1
result = num2 + num1
return result
end body end body
end function end function
end module end module