实现函数调用
This commit is contained in:
parent
174aafb846
commit
07c040b40d
@ -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 .. 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 register→slot 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()) {
|
||||||
|
|||||||
@ -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 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 {
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 frame’s 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
|
|
||||||
* 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 {
|
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 caller’s saved PC. */
|
|
||||||
int returnAddr = finished.getReturnAddress();
|
int returnAddr = finished.getReturnAddress();
|
||||||
System.out.println("Return " + returnAddr);
|
System.out.println("Return " + returnAddr);
|
||||||
return returnAddr;
|
return returnAddr;
|
||||||
|
|||||||
@ -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 isn’t there yet.
|
||||||
* <code>returnAddress = 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
8
test
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user