feat: 支持数组下标访问类型分派

- 扩展 __index_i 函数支持 byte、short、int、long、float、double、boolean等类型
- 新增 __index_b、__index_s、__index_l、__index_f、__index_d、__index_r 函数- 优化数组元素访问的 IR 生成逻辑,根据类型选择合适的函数
- 更新 VM 层的 ARR_GET 子命令处理逻辑,支持多种数据类型
This commit is contained in:
Luke 2025-08-02 10:49:54 +08:00
parent 4a84f37b20
commit b093f8db72
3 changed files with 232 additions and 122 deletions

View File

@ -23,7 +23,7 @@ import java.util.concurrent.ConcurrentHashMap;
* *
* <ul> * <ul>
* <li>syscall: 支持字符串常量与寄存器到子命令的绑定与解析</li> * <li>syscall: 支持字符串常量与寄存器到子命令的绑定与解析</li>
* <li>数组下标访问: 特殊的__index_i__index_r内部调用</li> * <li>数组下标访问: 支持所有主流基础类型 __index_b/s/i/l/f/d/r</li>
* <li>普通函数: 支持自动推断返回值类型与槽位类型</li> * <li>普通函数: 支持自动推断返回值类型与槽位类型</li>
* </ul> * </ul>
*/ */
@ -77,16 +77,36 @@ public class CallGenerator implements InstructionGenerator<CallInstruction> {
return; return;
} }
// 一维数组整型下标访问 // 各种一维数组类型byte/short/int/long/float/double/boolean
if ("__index_i".equals(fn)) { switch (fn) {
generateIndexInstruction(ins, out, slotMap, true); case "__index_b" -> {
return; generateIndexInstruction(ins, out, slotMap, 'B');
} return;
}
// 多维数组返回引用的下标访问 case "__index_s" -> {
if ("__index_r".equals(fn)) { generateIndexInstruction(ins, out, slotMap, 'S');
generateIndexInstruction(ins, out, slotMap, false); return;
return; }
case "__index_i" -> {
generateIndexInstruction(ins, out, slotMap, 'I');
return;
}
case "__index_l" -> {
generateIndexInstruction(ins, out, slotMap, 'L');
return;
}
case "__index_f" -> {
generateIndexInstruction(ins, out, slotMap, 'F');
return;
}
case "__index_d" -> {
generateIndexInstruction(ins, out, slotMap, 'D');
return;
}
case "__index_r" -> {
generateIndexInstruction(ins, out, slotMap, 'R');
return;
}
} }
// 普通函数调用 // 普通函数调用
@ -155,25 +175,38 @@ public class CallGenerator implements InstructionGenerator<CallInstruction> {
} }
/** /**
* 生成一维/多维数组的下标访问指令 * 生成一维/多维数组的下标访问指令支持类型分派
* <p>
* 本方法用于将 IR 层的数组下标访问表达式 arr[idx]生成对应的 VM 指令序列
* 支持 byte/short/int/long/float/double/reference 等所有基础类型的数组元素访问
* </p>
* *
* @param ins 调用指令 * <ul>
* @param out VM 程序构建器 * <li>1. 依次加载数组参数arr和下标参数idx到操作数栈</li>
* @param slotMap 寄存器到槽位映射 * <li>2. 发出 ARR_GET 系统调用指令SYSCALL ARR_GET通过 VM 访问对应元素</li>
* @param isInt 是否是一维整型下标 * <li>3. 根据元素声明类型将结果写入目标槽位支持类型分派B/S/I/L/F/D/R</li>
* <li>4. 若参数或返回寄存器缺失则抛出异常提示</li>
* </ul>
*
* @param ins 下标访问对应的 IR 调用指令函数名通常为 __index_x
* @param out VM 程序构建器用于发出 VM 指令
* @param slotMap IR 虚拟寄存器到 VM 槽位的映射表
* @param retType 元素类型标识'B' byte, 'S' short, 'I' int, 'L' long, 'F' float, 'D' double, 'R' ref/obj
* @throws IllegalStateException 参数个数不符缺少目标寄存器未找到槽位等情况
*/ */
private void generateIndexInstruction(CallInstruction ins, VMProgramBuilder out, Map<IRVirtualRegister, Integer> slotMap, boolean isInt) { private void generateIndexInstruction(CallInstruction ins, VMProgramBuilder out, Map<IRVirtualRegister, Integer> slotMap, char retType) {
String fn = ins.getFunctionName(); String fn = ins.getFunctionName();
List<IRValue> args = ins.getArguments(); List<IRValue> args = ins.getArguments();
if (args.size() != 2) { if (args.size() != 2) {
throw new IllegalStateException( throw new IllegalStateException(
"[CallGenerator] %s 需要两个参数(arr, idx),实际: %s".formatted(fn, args)); "[CallGenerator] %s 需要两个参数(arr, idx),实际: %s".formatted(fn, args));
} }
// 加载数组参数 // 加载数组参数寄存器类型按 R/ref 处理默认对象槽位
loadArgument(out, slotMap, args.get(0), 'R', fn); loadArgument(out, slotMap, args.get(0), 'R', fn);
// 加载下标参数 // 加载下标参数寄存器类型按 I/int 处理
loadArgument(out, slotMap, args.get(1), 'I', fn); loadArgument(out, slotMap, args.get(1), 'I', fn);
// 发出 ARR_GET 系统调用元素访问由 VM 完成类型分派
out.emit(VMOpCode.SYSCALL + " " + "ARR_GET"); out.emit(VMOpCode.SYSCALL + " " + "ARR_GET");
// 保存返回值到目标寄存器 // 保存返回值到目标寄存器
@ -187,12 +220,37 @@ public class CallGenerator implements InstructionGenerator<CallInstruction> {
throw new IllegalStateException( throw new IllegalStateException(
"[CallGenerator] %s 未找到目标寄存器的槽位映射 (dest: %s)".formatted(fn, dest)); "[CallGenerator] %s 未找到目标寄存器的槽位映射 (dest: %s)".formatted(fn, dest));
} }
if (isInt) {
out.emit(OpHelper.opcode("I_STORE") + " " + destSlot); // 按元素类型分派写入 VM 槽位
out.setSlotType(destSlot, 'I'); switch (retType) {
} else { case 'B' -> {
out.emit(OpHelper.opcode("R_STORE") + " " + destSlot); out.emit(OpHelper.opcode("B_STORE") + " " + destSlot);
out.setSlotType(destSlot, 'R'); out.setSlotType(destSlot, 'B');
}
case 'S' -> {
out.emit(OpHelper.opcode("S_STORE") + " " + destSlot);
out.setSlotType(destSlot, 'S');
}
case 'I' -> {
out.emit(OpHelper.opcode("I_STORE") + " " + destSlot);
out.setSlotType(destSlot, 'I');
}
case 'L' -> {
out.emit(OpHelper.opcode("L_STORE") + " " + destSlot);
out.setSlotType(destSlot, 'L');
}
case 'F' -> {
out.emit(OpHelper.opcode("F_STORE") + " " + destSlot);
out.setSlotType(destSlot, 'F');
}
case 'D' -> {
out.emit(OpHelper.opcode("D_STORE") + " " + destSlot);
out.setSlotType(destSlot, 'D');
}
default -> {
out.emit(OpHelper.opcode("R_STORE") + " " + destSlot);
out.setSlotType(destSlot, 'R');
}
} }
} }

View File

@ -137,7 +137,7 @@ public record ExpressionBuilder(IRContext ctx) {
* <li>否则 * <li>否则
* <ul> * <ul>
* <li>若数组表达式本身是下一个下标的中间值即多维数组链式下标则先用 __index_r 获取引用</li> * <li>若数组表达式本身是下一个下标的中间值即多维数组链式下标则先用 __index_r 获取引用</li>
* <li>最后一级用 __index_i 获取实际整型元素值</li> * <li>最后一级用 __index_b/s/i/l/f/d/r按声明类型智能分派</li>
* </ul> * </ul>
* </li> * </li>
* </ul> * </ul>
@ -182,8 +182,28 @@ public record ExpressionBuilder(IRContext ctx) {
// 非最末层下标取引用 // 非最末层下标取引用
ctx.addInstruction(new CallInstruction(dest, "__index_r", argv)); ctx.addInstruction(new CallInstruction(dest, "__index_r", argv));
} else { } else {
// 最末层下标取实际元素值 // 最末层下标取实际元素值按声明类型分派
ctx.addInstruction(new CallInstruction(dest, "__index_i", argv)); String func = "__index_i"; // 默认整型
if (node.array() instanceof IdentifierNode id) {
String declType = ctx.getScope().lookupType(id.name()); // "double[]""int[]"
if (declType != null) {
String base = declType.toLowerCase();
int p = base.indexOf('[');
if (p > 0) base = base.substring(0, p); // 基本类型
switch (base) {
case "byte" -> func = "__index_b";
case "short" -> func = "__index_s";
case "int" -> func = "__index_i";
case "long" -> func = "__index_l";
case "float" -> func = "__index_f";
case "double" -> func = "__index_d";
case "boolean" -> func = "__index_i"; // 布尔型用 int 通道返回 1/0
case "string" -> func = "__index_r"; // 字符串/其它未识别类型均走引用
default -> func = "__index_r";
}
}
}
ctx.addInstruction(new CallInstruction(dest, func, argv));
} }
return dest; return dest;

View File

@ -46,6 +46,97 @@ import static java.nio.file.StandardOpenOption.*;
*/ */
public class SyscallCommand implements Command { public class SyscallCommand implements Command {
/**
* 根据传入的文件打开标志构造 NIO {@link OpenOption} 集合
* <p>
* 本方法负责将底层虚拟机传递的 flags 整数型位域转换为 Java NIO 标准的文件打开选项集合
* 以支持文件读创建截断追加等多种访问场景
* 常用于 SYSCALL OPEN 子命令
* </p>
*
* @param flags 文件打开模式标志各标志可组合使用具体含义请参见虚拟机文档
* @return 转换后的 OpenOption 集合可直接用于 FileChannel.open NIO 方法
*/
private static Set<OpenOption> flagsToOptions(int flags) {
Set<OpenOption> opts = new HashSet<>();
// 如果有写入标志则添加WRITE否则默认为READ
if ((flags & 0x1) != 0) opts.add(WRITE);
else opts.add(READ);
// 如果包含创建标志允许创建文件
if ((flags & 0x40) != 0) opts.add(CREATE);
// 包含截断标志打开时清空内容
if ((flags & 0x200) != 0) opts.add(TRUNCATE_EXISTING);
// 包含追加标志文件写入时追加到末尾
if ((flags & 0x400) != 0) opts.add(APPEND);
return opts;
}
/**
* 捕获所有异常并统一处理操作数栈压入 -1 代表本次系统调用失败
* <p>
* 本方法是全局错误屏障任何命令异常都会转换为虚拟机通用的失败信号
* 保证上层调用逻辑不会被异常打断实际应用中可拓展错误码机制
* </p>
*
* @param stack 操作数栈将失败信号写入此栈
* @param e 抛出的异常对象可在调试时输出日志
*/
private static void pushErr(OperandStack stack, Exception e) {
stack.push(-1);
System.err.println("Syscall exception: " + e);
}
/**
* 控制台输出通用方法支持基本类型字节数组任意数组对象等
* <p>
* 该方法用于 SYSCALL PRINT/PRINTLN将任意类型对象转为易读字符串输出到标准输出流
* 字节数组自动按 UTF-8 解码其它原生数组按格式化字符串输出
* </p>
*
* @param obj 待输出的内容可以为任何类型如基本类型byte[]数组对象等
* @param newline 是否自动换行如果为 true则在输出后换行否则直接输出
*/
private static void output(Object obj, boolean newline) {
String str;
if (obj == null) {
str = "null";
} else if (obj instanceof byte[] bytes) {
// 字节数组作为文本输出
str = new String(bytes);
} else if (obj.getClass().isArray()) {
// 其它数组格式化输出
str = arrayToString(obj);
} else {
str = obj.toString();
}
if (newline) System.out.println(str);
else System.out.print(str);
}
/**
* 将各种原生数组和对象数组转换为可读字符串便于控制台输出和调试
* <p>
* 本方法针对 intlongdoublefloatshortcharbyteboolean 等所有原生数组类型
* 以及对象数组都能正确格式化统一输出格式风格避免显示为类型 hashCode
* 若为不支持的类型返回通用提示字符串
* </p>
*
* @param array 任意原生数组或对象数组
* @return 该数组的可读字符串表示
*/
private static String arrayToString(Object array) {
if (array instanceof int[] a) return Arrays.toString(a);
if (array instanceof long[] a) return Arrays.toString(a);
if (array instanceof double[] a) return Arrays.toString(a);
if (array instanceof float[] a) return Arrays.toString(a);
if (array instanceof short[] a) return Arrays.toString(a);
if (array instanceof char[] a) return Arrays.toString(a);
if (array instanceof byte[] a) return Arrays.toString(a);
if (array instanceof boolean[] a) return Arrays.toString(a);
if (array instanceof Object[] a) return Arrays.deepToString(a);
return "Unsupported array";
}
/** /**
* 分发并执行 SYSCALL 子命令根据子命令类型从操作数栈取出参数操作底层资源并将结果压回栈顶 * 分发并执行 SYSCALL 子命令根据子命令类型从操作数栈取出参数操作底层资源并将结果压回栈顶
* *
@ -226,8 +317,24 @@ public class SyscallCommand implements Command {
sel.close(); sel.close();
} }
// 数组元素访问arr[idx] -> 返回元素当前实现返回 int 或字符串/引用原样 // 数组元素访问arr[idx] 保留所有类型精度byte/short/int/long/float/double/boolean/string/ref
case "ARR_GET" -> { case "ARR_GET" -> {
/**
* 执行数组下标访问操作 arr[idx]并将对应元素以真实类型压入操作数栈
* <ul>
* <li>支持 List 与任意原生数组类型int[]double[] </li>
* <li>idx 参数支持 Number/String 类型自动转 int</li>
* <li>下标越界将抛出异常非数组类型将报错</li>
* <li>返回结果保持类型精度byte/short/int/long/float/double/boolean/string/object;</li>
* <li>boolean 元素以 1/0 压栈string/引用直接压栈</li>
* </ul>
*
* 异常与出错行为
* <ul>
* <li>索引类型非法目标非数组/列表将抛 IllegalArgumentException</li>
* <li>索引越界将抛 IndexOutOfBoundsException</li>
* </ul>
*/
Object idxObj = stack.pop(); Object idxObj = stack.pop();
Object arrObj = stack.pop(); Object arrObj = stack.pop();
int idx; int idx;
@ -249,16 +356,32 @@ public class SyscallCommand implements Command {
throw new IllegalArgumentException("ARR_GET: not an array/list: " + arrObj); throw new IllegalArgumentException("ARR_GET: not an array/list: " + arrObj);
} }
// === 按真实类型压栈byte/short/int/long/float/double/boolean/string/ref===
if (elem instanceof Number n) { if (elem instanceof Number n) {
stack.push(n.intValue()); if (elem instanceof Double) {
stack.push(n.doubleValue());
} else if (elem instanceof Float) {
stack.push(n.floatValue());
} else if (elem instanceof Long) {
stack.push(n.longValue());
} else if (elem instanceof Integer) {
stack.push(n.intValue());
} else if (elem instanceof Short) {
stack.push(n.shortValue());
} else if (elem instanceof Byte) {
stack.push(n.byteValue());
} else {
stack.push(n.intValue()); // 兜底
}
} else if (elem instanceof Boolean b) { } else if (elem instanceof Boolean b) {
stack.push(b ? 1 : 0); stack.push(b ? 1 : 0);
} else { } else {
// 对于字符串或其它引用类型直接返回引用由上层决定如何存储 // string 或其它引用类型直接返回
stack.push(elem); stack.push(elem);
} }
} }
// 控制台输出 // 控制台输出
case "PRINT" -> { case "PRINT" -> {
Object dataObj = stack.pop(); Object dataObj = stack.pop();
@ -279,95 +402,4 @@ public class SyscallCommand implements Command {
return pc + 1; return pc + 1;
} }
/**
* 根据传入的文件打开标志构造 NIO {@link OpenOption} 集合
* <p>
* 本方法负责将底层虚拟机传递的 flags 整数型位域转换为 Java NIO 标准的文件打开选项集合
* 以支持文件读创建截断追加等多种访问场景
* 常用于 SYSCALL OPEN 子命令
* </p>
*
* @param flags 文件打开模式标志各标志可组合使用具体含义请参见虚拟机文档
* @return 转换后的 OpenOption 集合可直接用于 FileChannel.open NIO 方法
*/
private static Set<OpenOption> flagsToOptions(int flags) {
Set<OpenOption> opts = new HashSet<>();
// 如果有写入标志则添加WRITE否则默认为READ
if ((flags & 0x1) != 0) opts.add(WRITE);
else opts.add(READ);
// 如果包含创建标志允许创建文件
if ((flags & 0x40) != 0) opts.add(CREATE);
// 包含截断标志打开时清空内容
if ((flags & 0x200) != 0) opts.add(TRUNCATE_EXISTING);
// 包含追加标志文件写入时追加到末尾
if ((flags & 0x400) != 0) opts.add(APPEND);
return opts;
}
/**
* 捕获所有异常并统一处理操作数栈压入 -1 代表本次系统调用失败
* <p>
* 本方法是全局错误屏障任何命令异常都会转换为虚拟机通用的失败信号
* 保证上层调用逻辑不会被异常打断实际应用中可拓展错误码机制
* </p>
*
* @param stack 操作数栈将失败信号写入此栈
* @param e 抛出的异常对象可在调试时输出日志
*/
private static void pushErr(OperandStack stack, Exception e) {
stack.push(-1);
System.err.println("Syscall exception: " + e);
}
/**
* 控制台输出通用方法支持基本类型字节数组任意数组对象等
* <p>
* 该方法用于 SYSCALL PRINT/PRINTLN将任意类型对象转为易读字符串输出到标准输出流
* 字节数组自动按 UTF-8 解码其它原生数组按格式化字符串输出
* </p>
*
* @param obj 待输出的内容可以为任何类型如基本类型byte[]数组对象等
* @param newline 是否自动换行如果为 true则在输出后换行否则直接输出
*/
private static void output(Object obj, boolean newline) {
String str;
if (obj == null) {
str = "null";
} else if (obj instanceof byte[] bytes) {
// 字节数组作为文本输出
str = new String(bytes);
} else if (obj.getClass().isArray()) {
// 其它数组格式化输出
str = arrayToString(obj);
} else {
str = obj.toString();
}
if (newline) System.out.println(str);
else System.out.print(str);
}
/**
* 将各种原生数组和对象数组转换为可读字符串便于控制台输出和调试
* <p>
* 本方法针对 intlongdoublefloatshortcharbyteboolean 等所有原生数组类型
* 以及对象数组都能正确格式化统一输出格式风格避免显示为类型 hashCode
* 若为不支持的类型返回通用提示字符串
* </p>
*
* @param array 任意原生数组或对象数组
* @return 该数组的可读字符串表示
*/
private static String arrayToString(Object array) {
if (array instanceof int[] a) return Arrays.toString(a);
if (array instanceof long[] a) return Arrays.toString(a);
if (array instanceof double[] a) return Arrays.toString(a);
if (array instanceof float[] a) return Arrays.toString(a);
if (array instanceof short[] a) return Arrays.toString(a);
if (array instanceof char[] a) return Arrays.toString(a);
if (array instanceof byte[] a) return Arrays.toString(a);
if (array instanceof boolean[] a) return Arrays.toString(a);
if (array instanceof Object[] a) return Arrays.deepToString(a);
return "Unsupported array";
}
} }