aritmetics

This commit is contained in:
bQUARKz 2026-03-09 17:00:29 +00:00
parent 5575c491a1
commit 2bc99cc901
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
10 changed files with 684 additions and 14 deletions

View File

@ -388,7 +388,8 @@ public final class PbsFrontendCompiler {
callableSignatureByCallableId,
returnSlotsByCallableId,
intrinsicIdTable,
localSlotByNameId);
localSlotByNameId,
fn.parameters().size());
final var terminated = lowerBlock(fn.body(), loweringContext);
final var instructions = loweringContext.instructions();
if (!terminated) {
@ -417,6 +418,7 @@ public final class PbsFrontendCompiler {
}
final var start = safeToInt(fn.span().getStart());
final var end = safeToInt(fn.span().getEnd());
final var localSlots = Math.max(0, loweringContext.nextLocalSlot() - fn.parameters().size());
executableFunctions.add(new IRBackendExecutableFunction(
fileId,
normalizedModuleId,
@ -425,7 +427,7 @@ public final class PbsFrontendCompiler {
start,
end,
fn.parameters().size(),
0,
localSlots,
returnSlots,
maxStackSlots,
ReadOnlyList.wrap(instructions),
@ -629,8 +631,39 @@ public final class PbsFrontendCompiler {
return false;
}
switch (statement) {
case PbsAst.LetStatement letStatement -> lowerExpression(letStatement.initializer(), context);
case PbsAst.AssignStatement assignStatement -> lowerExpression(assignStatement.value(), context);
case PbsAst.LetStatement letStatement -> {
lowerExpression(letStatement.initializer(), context);
final var localSlot = context.declareLocalSlot(letStatement.name());
emitSetLocal(localSlot, letStatement.span(), context);
}
case PbsAst.AssignStatement assignStatement -> {
final var target = assignStatement.target();
if (target == null || target.rootName() == null || target.rootName().isBlank()) {
reportUnsupportedLowering("assignment target requires a local root name", assignStatement.span(), context);
return false;
}
if (target.pathSegments() != null && !target.pathSegments().isEmpty()) {
reportUnsupportedLowering("path assignment lowering is not supported in executable lowering v1", assignStatement.span(), context);
return false;
}
final var targetSlot = context.resolveLocalSlot(target.rootName());
if (targetSlot == null) {
reportUnsupportedLowering("assignment target is not a known local: " + target.rootName(), assignStatement.span(), context);
return false;
}
switch (assignStatement.operator()) {
case ASSIGN -> {
lowerExpression(assignStatement.value(), context);
emitSetLocal(targetSlot, assignStatement.span(), context);
}
case ADD_ASSIGN, SUB_ASSIGN, MUL_ASSIGN, DIV_ASSIGN, MOD_ASSIGN -> {
emitGetLocal(targetSlot, assignStatement.span(), context);
lowerExpression(assignStatement.value(), context);
emitBinaryOperatorInstruction(compoundAssignBinaryOperator(assignStatement.operator()), assignStatement.span(), context);
emitSetLocal(targetSlot, assignStatement.span(), context);
}
}
}
case PbsAst.ReturnStatement returnStatement -> {
if (returnStatement.value() != null) {
lowerExpression(returnStatement.value(), context);
@ -741,8 +774,12 @@ public final class PbsFrontendCompiler {
case PbsAst.BinaryExpr binaryExpr -> {
lowerExpression(binaryExpr.left(), context);
lowerExpression(binaryExpr.right(), context);
emitBinaryOperatorInstruction(binaryExpr.operator(), binaryExpr.span(), context);
}
case PbsAst.UnaryExpr unaryExpr -> {
lowerExpression(unaryExpr.expression(), context);
emitUnaryOperatorInstruction(unaryExpr.operator(), unaryExpr.span(), context);
}
case PbsAst.UnaryExpr unaryExpr -> lowerExpression(unaryExpr.expression(), context);
case PbsAst.ElseExpr elseExpr -> {
lowerExpression(elseExpr.optionalExpression(), context);
lowerExpression(elseExpr.fallbackExpression(), context);
@ -809,8 +846,7 @@ public final class PbsFrontendCompiler {
case PbsAst.BoundedLiteralExpr ignored -> {
}
case PbsAst.StringLiteralExpr stringLiteralExpr -> emitPushConst(stringLiteralExpr.value(), stringLiteralExpr.span(), context);
case PbsAst.BoolLiteralExpr ignored -> {
}
case PbsAst.BoolLiteralExpr boolLiteralExpr -> emitPushBool(boolLiteralExpr.value(), boolLiteralExpr.span(), context);
case PbsAst.ThisExpr ignored -> {
}
case PbsAst.NoneExpr ignored -> {
@ -890,6 +926,7 @@ public final class PbsFrontendCompiler {
return;
}
final var host = hostCandidates.getFirst();
final var effectiveArgSlots = callExpr.arguments().size() + implicitReceiverArgSlots(callExpr.callee());
context.instructions().add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_HOST,
"",
@ -897,7 +934,7 @@ public final class PbsFrontendCompiler {
host.abiModule(),
host.abiMethod(),
host.abiVersion(),
callExpr.arguments().size(),
effectiveArgSlots,
0),
null,
callExpr.span()));
@ -939,6 +976,7 @@ public final class PbsFrontendCompiler {
reportUnsupportedLowering("executable lowering resolved callable without signature metadata", callExpr.span(), context);
return;
}
final var effectiveArgSlots = callExpr.arguments().size() + implicitReceiverArgSlots(callExpr.callee());
context.instructions().add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
calleeSignature.moduleId(),
@ -946,7 +984,7 @@ public final class PbsFrontendCompiler {
calleeCallableId,
null,
null,
callExpr.arguments().size(),
effectiveArgSlots,
context.returnSlotsByCallableId().get(calleeCallableId),
callExpr.span()));
}
@ -1139,6 +1177,24 @@ public final class PbsFrontendCompiler {
span));
}
private void emitPushBool(
final boolean value,
final p.studio.compiler.source.Span span,
final ExecutableLoweringContext context) {
context.instructions().add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.PUSH_BOOL,
ModuleId.none(),
"",
null,
null,
null,
value ? "true" : "false",
"",
null,
null,
span));
}
private void emitPushConst(
final String value,
final p.studio.compiler.source.Span span,
@ -1157,6 +1213,102 @@ public final class PbsFrontendCompiler {
span));
}
private void emitSetLocal(
final int slot,
final p.studio.compiler.source.Span span,
final ExecutableLoweringContext context) {
context.instructions().add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.SET_LOCAL,
ModuleId.none(),
"",
null,
null,
null,
"",
"",
slot,
null,
span));
}
private String compoundAssignBinaryOperator(final PbsAst.AssignOperator operator) {
return switch (operator) {
case ADD_ASSIGN -> "+";
case SUB_ASSIGN -> "-";
case MUL_ASSIGN -> "*";
case DIV_ASSIGN -> "/";
case MOD_ASSIGN -> "%";
case ASSIGN -> "=";
};
}
private void emitUnaryOperatorInstruction(
final String operator,
final p.studio.compiler.source.Span span,
final ExecutableLoweringContext context) {
final var normalized = operator == null ? "" : operator;
final IRBackendExecutableFunction.InstructionKind instructionKind = switch (normalized) {
case "-", "neg" -> IRBackendExecutableFunction.InstructionKind.NEG;
case "!", "not" -> IRBackendExecutableFunction.InstructionKind.NOT;
default -> null;
};
if (instructionKind == null) {
reportUnsupportedLowering("unsupported unary operator in executable lowering: " + normalized, span, context);
return;
}
context.instructions().add(new IRBackendExecutableFunction.Instruction(
instructionKind,
ModuleId.none(),
"",
null,
null,
null,
"",
"",
null,
null,
span));
}
private void emitBinaryOperatorInstruction(
final String operator,
final p.studio.compiler.source.Span span,
final ExecutableLoweringContext context) {
final var normalized = operator == null ? "" : operator;
final IRBackendExecutableFunction.InstructionKind instructionKind = switch (normalized) {
case "+" -> IRBackendExecutableFunction.InstructionKind.ADD;
case "-" -> IRBackendExecutableFunction.InstructionKind.SUB;
case "*" -> IRBackendExecutableFunction.InstructionKind.MUL;
case "/" -> IRBackendExecutableFunction.InstructionKind.DIV;
case "%" -> IRBackendExecutableFunction.InstructionKind.MOD;
case "==" -> IRBackendExecutableFunction.InstructionKind.EQ;
case "!=" -> IRBackendExecutableFunction.InstructionKind.NEQ;
case "<" -> IRBackendExecutableFunction.InstructionKind.LT;
case "<=" -> IRBackendExecutableFunction.InstructionKind.LTE;
case ">" -> IRBackendExecutableFunction.InstructionKind.GT;
case ">=" -> IRBackendExecutableFunction.InstructionKind.GTE;
case "&&", "and" -> IRBackendExecutableFunction.InstructionKind.AND;
case "||", "or" -> IRBackendExecutableFunction.InstructionKind.OR;
default -> null;
};
if (instructionKind == null) {
reportUnsupportedLowering("unsupported binary operator in executable lowering: " + normalized, span, context);
return;
}
context.instructions().add(new IRBackendExecutableFunction.Instruction(
instructionKind,
ModuleId.none(),
"",
null,
null,
null,
"",
"",
null,
null,
span));
}
private void emitGetLocal(
final int slot,
final p.studio.compiler.source.Span span,
@ -1219,7 +1371,27 @@ public final class PbsFrontendCompiler {
var outHeight = incomingHeightByInstruction.get(index);
switch (instruction.kind()) {
case PUSH_I32, PUSH_CONST, GET_LOCAL -> outHeight += 1;
case PUSH_I32, PUSH_BOOL, PUSH_CONST, GET_LOCAL -> outHeight += 1;
case POP, SET_LOCAL -> {
if (outHeight <= 0) {
throw new ExecutableLoweringAnalysisException(
"stack underflow at " + instruction.kind().name().toLowerCase() + ": need=1 have=" + outHeight);
}
outHeight -= 1;
}
case ADD, SUB, MUL, DIV, MOD, EQ, NEQ, LT, LTE, GT, GTE, AND, OR -> {
if (outHeight < 2) {
throw new ExecutableLoweringAnalysisException(
"stack underflow at " + instruction.kind().name().toLowerCase() + ": need=2 have=" + outHeight);
}
outHeight -= 1;
}
case NOT, NEG -> {
if (outHeight <= 0) {
throw new ExecutableLoweringAnalysisException(
"stack underflow at " + instruction.kind().name().toLowerCase() + ": need=1 have=" + outHeight);
}
}
case CALL_FUNC -> {
final var argSlots = instruction.expectedArgSlots() == null ? 0 : instruction.expectedArgSlots();
final var retSlots = instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots();
@ -1327,6 +1499,7 @@ public final class PbsFrontendCompiler {
private final ArrayList<IRBackendExecutableFunction.Instruction> instructions = new ArrayList<>();
private final ArrayDeque<LoopTargets> loopTargets = new ArrayDeque<>();
private int nextLabelId = 0;
private int nextLocalSlot;
private ExecutableLoweringContext(
final ModuleId moduleId,
@ -1340,7 +1513,8 @@ public final class PbsFrontendCompiler {
final Map<CallableId, CallableSignatureRef> callableSignatureByCallableId,
final Map<CallableId, Integer> returnSlotsByCallableId,
final IntrinsicTable intrinsicIdTable,
final Map<NameId, Integer> localSlotByNameId) {
final Map<NameId, Integer> localSlotByNameId,
final int initialLocalSlot) {
this.moduleId = moduleId == null ? ModuleId.none() : moduleId;
this.diagnostics = diagnostics;
this.nameTable = nameTable;
@ -1352,7 +1526,8 @@ public final class PbsFrontendCompiler {
this.callableSignatureByCallableId = callableSignatureByCallableId;
this.returnSlotsByCallableId = returnSlotsByCallableId;
this.intrinsicIdTable = intrinsicIdTable;
this.localSlotByNameId = localSlotByNameId == null ? Map.of() : localSlotByNameId;
this.localSlotByNameId = localSlotByNameId == null ? new HashMap<>() : localSlotByNameId;
this.nextLocalSlot = Math.max(0, initialLocalSlot);
}
private ModuleId moduleId() {
@ -1403,6 +1578,28 @@ public final class PbsFrontendCompiler {
return localSlotByNameId;
}
private Integer resolveLocalSlot(final String localName) {
if (localName == null || localName.isBlank()) {
return null;
}
return localSlotByNameId.get(nameTable.register(localName));
}
private int declareLocalSlot(final String localName) {
final var nameId = nameTable.register(localName);
final var existing = localSlotByNameId.get(nameId);
if (existing != null) {
return existing;
}
final var slot = nextLocalSlot++;
localSlotByNameId.put(nameId, slot);
return slot;
}
private int nextLocalSlot() {
return nextLocalSlot;
}
private ArrayList<IRBackendExecutableFunction.Instruction> instructions() {
return instructions;
}

View File

@ -18,7 +18,25 @@ public class BytecodeEmitter {
private static final int OP_JMP_IF_FALSE = 0x03;
private static final int OP_JMP_IF_TRUE = 0x04;
private static final int OP_PUSH_CONST = 0x10;
private static final int OP_POP = 0x11;
private static final int OP_PUSH_BOOL = 0x16;
private static final int OP_ADD = 0x20;
private static final int OP_SUB = 0x21;
private static final int OP_MUL = 0x22;
private static final int OP_DIV = 0x23;
private static final int OP_MOD = 0x24;
private static final int OP_EQ = 0x30;
private static final int OP_NEQ = 0x31;
private static final int OP_LT = 0x32;
private static final int OP_GT = 0x33;
private static final int OP_AND = 0x34;
private static final int OP_OR = 0x35;
private static final int OP_NOT = 0x36;
private static final int OP_LTE = 0x3C;
private static final int OP_GTE = 0x3D;
private static final int OP_NEG = 0x3E;
private static final int OP_GET_LOCAL = 0x42;
private static final int OP_SET_LOCAL = 0x43;
private static final int OP_PUSH_I32 = 0x17;
private static final int OP_HOSTCALL = 0x71;
private static final int OP_SYSCALL = 0x70;
@ -55,14 +73,86 @@ public class BytecodeEmitter {
writeOpU32(code, OP_PUSH_CONST, op.immediate());
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case POP -> {
writeOpNoImm(code, OP_POP);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case GET_LOCAL -> {
writeOpU32(code, OP_GET_LOCAL, op.immediate());
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case SET_LOCAL -> {
writeOpU32(code, OP_SET_LOCAL, op.immediate());
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case PUSH_BOOL -> {
writeOpU8(code, OP_PUSH_BOOL, op.immediate() == 0 ? 0 : 1);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case PUSH_I32 -> {
writeOpU32(code, OP_PUSH_I32, op.immediate());
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case ADD -> {
writeOpNoImm(code, OP_ADD);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case SUB -> {
writeOpNoImm(code, OP_SUB);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case MUL -> {
writeOpNoImm(code, OP_MUL);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case DIV -> {
writeOpNoImm(code, OP_DIV);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case MOD -> {
writeOpNoImm(code, OP_MOD);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case EQ -> {
writeOpNoImm(code, OP_EQ);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case NEQ -> {
writeOpNoImm(code, OP_NEQ);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case LT -> {
writeOpNoImm(code, OP_LT);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case LTE -> {
writeOpNoImm(code, OP_LTE);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case GT -> {
writeOpNoImm(code, OP_GT);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case GTE -> {
writeOpNoImm(code, OP_GTE);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case AND -> {
writeOpNoImm(code, OP_AND);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case OR -> {
writeOpNoImm(code, OP_OR);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case NOT -> {
writeOpNoImm(code, OP_NOT);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case NEG -> {
writeOpNoImm(code, OP_NEG);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case JMP -> {
writeOpU32(code, OP_JMP, op.immediate());
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
@ -136,6 +226,11 @@ public class BytecodeEmitter {
out.writeBytes(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(immediate).array());
}
private static void writeOpU8(final ByteArrayOutputStream out, final int opcode, final int immediate) {
out.writeBytes(ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort((short) opcode).array());
out.write(immediate & 0xFF);
}
private static BytecodeModule.SourceSpan spanOrUnknown(final BytecodeModule.SourceSpan span) {
if (span == null) {
return new BytecodeModule.SourceSpan(-1, 0, 0);
@ -210,8 +305,26 @@ public class BytecodeEmitter {
HALT,
RET,
PUSH_CONST,
POP,
GET_LOCAL,
SET_LOCAL,
PUSH_BOOL,
PUSH_I32,
ADD,
SUB,
MUL,
DIV,
MOD,
EQ,
NEQ,
LT,
LTE,
GT,
GTE,
AND,
OR,
NOT,
NEG,
CALL_FUNC,
JMP,
JMP_IF_TRUE,
@ -276,14 +389,86 @@ public class BytecodeEmitter {
return new Operation(OperationKind.PUSH_CONST, constPoolIndex, null, null, null, span);
}
public static Operation pop(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.POP, 0, null, null, null, span);
}
public static Operation getLocal(final int slot, final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.GET_LOCAL, slot, null, null, null, span);
}
public static Operation setLocal(final int slot, final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.SET_LOCAL, slot, null, null, null, span);
}
public static Operation pushBool(final boolean value, final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.PUSH_BOOL, value ? 1 : 0, null, null, null, span);
}
public static Operation pushI32(final int value, final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.PUSH_I32, value, null, null, null, span);
}
public static Operation add(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.ADD, 0, null, null, null, span);
}
public static Operation sub(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.SUB, 0, null, null, null, span);
}
public static Operation mul(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.MUL, 0, null, null, null, span);
}
public static Operation div(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.DIV, 0, null, null, null, span);
}
public static Operation mod(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.MOD, 0, null, null, null, span);
}
public static Operation eq(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.EQ, 0, null, null, null, span);
}
public static Operation neq(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.NEQ, 0, null, null, null, span);
}
public static Operation lt(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.LT, 0, null, null, null, span);
}
public static Operation lte(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.LTE, 0, null, null, null, span);
}
public static Operation gt(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.GT, 0, null, null, null, span);
}
public static Operation gte(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.GTE, 0, null, null, null, span);
}
public static Operation and(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.AND, 0, null, null, null, span);
}
public static Operation or(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.OR, 0, null, null, null, span);
}
public static Operation not(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.NOT, 0, null, null, null, span);
}
public static Operation neg(final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.NEG, 0, null, null, null, span);
}
public static Operation hostcall(
final BytecodeModule.SyscallDecl decl,
final Integer expectedArgSlots,

View File

@ -9,11 +9,27 @@ final class BytecodeOpcodeLayout {
Map.entry(0x03, 6),
Map.entry(0x04, 6),
Map.entry(0x10, 6),
Map.entry(0x11, 2),
Map.entry(0x14, 10),
Map.entry(0x15, 10),
Map.entry(0x16, 3),
Map.entry(0x17, 6),
Map.entry(0x18, 6),
Map.entry(0x20, 2),
Map.entry(0x21, 2),
Map.entry(0x22, 2),
Map.entry(0x23, 2),
Map.entry(0x24, 2),
Map.entry(0x30, 2),
Map.entry(0x31, 2),
Map.entry(0x32, 2),
Map.entry(0x33, 2),
Map.entry(0x34, 2),
Map.entry(0x35, 2),
Map.entry(0x36, 2),
Map.entry(0x3C, 2),
Map.entry(0x3D, 2),
Map.entry(0x3E, 2),
Map.entry(0x40, 6),
Map.entry(0x41, 6),
Map.entry(0x42, 6),

View File

@ -13,13 +13,31 @@ public record IRVMOp(
public static final IRVMOp HALT = new IRVMOp("HALT", 0x01, 0, 0, 0, false, true, false);
public static final IRVMOp RET = new IRVMOp("RET", 0x51, 0, 0, 0, false, true, false);
public static final IRVMOp PUSH_CONST = new IRVMOp("PUSH_CONST", 0x10, 4, 0, 1, false, false, false);
public static final IRVMOp POP = new IRVMOp("POP", 0x11, 0, 1, 0, false, false, false);
public static final IRVMOp CALL = new IRVMOp("CALL", 0x50, 4, 0, 0, false, false, false);
public static final IRVMOp JMP = new IRVMOp("JMP", 0x02, 4, 0, 0, true, true, false);
public static final IRVMOp JMP_IF_TRUE = new IRVMOp("JMP_IF_TRUE", 0x04, 4, 1, 0, true, false, false);
public static final IRVMOp JMP_IF_FALSE = new IRVMOp("JMP_IF_FALSE", 0x03, 4, 1, 0, true, false, false);
public static final IRVMOp HOSTCALL = new IRVMOp("HOSTCALL", 0x71, 4, 0, 0, false, false, false);
public static final IRVMOp INTRINSIC = new IRVMOp("INTRINSIC", 0x72, 4, 0, 0, false, false, false);
public static final IRVMOp PUSH_BOOL = new IRVMOp("PUSH_BOOL", 0x16, 1, 0, 1, false, false, false);
public static final IRVMOp PUSH_I32 = new IRVMOp("PUSH_I32", 0x17, 4, 0, 1, false, false, false);
public static final IRVMOp ADD = new IRVMOp("ADD", 0x20, 0, 2, 1, false, false, false);
public static final IRVMOp SUB = new IRVMOp("SUB", 0x21, 0, 2, 1, false, false, false);
public static final IRVMOp MUL = new IRVMOp("MUL", 0x22, 0, 2, 1, false, false, false);
public static final IRVMOp DIV = new IRVMOp("DIV", 0x23, 0, 2, 1, false, false, false);
public static final IRVMOp MOD = new IRVMOp("MOD", 0x24, 0, 2, 1, false, false, false);
public static final IRVMOp EQ = new IRVMOp("EQ", 0x30, 0, 2, 1, false, false, false);
public static final IRVMOp NEQ = new IRVMOp("NEQ", 0x31, 0, 2, 1, false, false, false);
public static final IRVMOp LT = new IRVMOp("LT", 0x32, 0, 2, 1, false, false, false);
public static final IRVMOp GT = new IRVMOp("GT", 0x33, 0, 2, 1, false, false, false);
public static final IRVMOp AND = new IRVMOp("AND", 0x34, 0, 2, 1, false, false, false);
public static final IRVMOp OR = new IRVMOp("OR", 0x35, 0, 2, 1, false, false, false);
public static final IRVMOp NOT = new IRVMOp("NOT", 0x36, 0, 1, 1, false, false, false);
public static final IRVMOp LTE = new IRVMOp("LTE", 0x3C, 0, 2, 1, false, false, false);
public static final IRVMOp GTE = new IRVMOp("GTE", 0x3D, 0, 2, 1, false, false, false);
public static final IRVMOp NEG = new IRVMOp("NEG", 0x3E, 0, 1, 1, false, false, false);
public static final IRVMOp GET_LOCAL = new IRVMOp("GET_LOCAL", 0x42, 4, 0, 1, false, false, false);
public static final IRVMOp SET_LOCAL = new IRVMOp("SET_LOCAL", 0x43, 4, 1, 0, false, false, false);
public static final IRVMOp INTERNAL_EXT = new IRVMOp("IRVM_EXT_NOP", 0xFFFF, 0, 0, 0, false, false, true);
}

View File

@ -12,25 +12,61 @@ public final class IRVMProfileFeatureGate {
IRVMOp.HALT,
IRVMOp.RET,
IRVMOp.PUSH_CONST,
IRVMOp.POP,
IRVMOp.GET_LOCAL,
IRVMOp.SET_LOCAL,
IRVMOp.CALL,
IRVMOp.JMP,
IRVMOp.JMP_IF_TRUE,
IRVMOp.JMP_IF_FALSE,
IRVMOp.HOSTCALL,
IRVMOp.INTRINSIC,
IRVMOp.PUSH_BOOL,
IRVMOp.ADD,
IRVMOp.SUB,
IRVMOp.MUL,
IRVMOp.DIV,
IRVMOp.MOD,
IRVMOp.EQ,
IRVMOp.NEQ,
IRVMOp.LT,
IRVMOp.LTE,
IRVMOp.GT,
IRVMOp.GTE,
IRVMOp.AND,
IRVMOp.OR,
IRVMOp.NOT,
IRVMOp.NEG,
IRVMOp.PUSH_I32),
"experimental-v1", Set.of(
IRVMOp.HALT,
IRVMOp.RET,
IRVMOp.PUSH_CONST,
IRVMOp.POP,
IRVMOp.GET_LOCAL,
IRVMOp.SET_LOCAL,
IRVMOp.CALL,
IRVMOp.JMP,
IRVMOp.JMP_IF_TRUE,
IRVMOp.JMP_IF_FALSE,
IRVMOp.HOSTCALL,
IRVMOp.INTRINSIC,
IRVMOp.PUSH_BOOL,
IRVMOp.ADD,
IRVMOp.SUB,
IRVMOp.MUL,
IRVMOp.DIV,
IRVMOp.MOD,
IRVMOp.EQ,
IRVMOp.NEQ,
IRVMOp.LT,
IRVMOp.LTE,
IRVMOp.GT,
IRVMOp.GTE,
IRVMOp.AND,
IRVMOp.OR,
IRVMOp.NOT,
IRVMOp.NEG,
IRVMOp.PUSH_I32,
IRVMOp.INTERNAL_EXT)));
}

View File

@ -93,8 +93,26 @@ public record IRVMProgram(
case 0x01 -> operation.kind() == BytecodeEmitter.OperationKind.HALT;
case 0x51 -> operation.kind() == BytecodeEmitter.OperationKind.RET;
case 0x10 -> operation.kind() == BytecodeEmitter.OperationKind.PUSH_CONST && operation.immediate() == immediate;
case 0x11 -> operation.kind() == BytecodeEmitter.OperationKind.POP;
case 0x16 -> operation.kind() == BytecodeEmitter.OperationKind.PUSH_BOOL;
case 0x42 -> operation.kind() == BytecodeEmitter.OperationKind.GET_LOCAL && operation.immediate() == immediate;
case 0x43 -> operation.kind() == BytecodeEmitter.OperationKind.SET_LOCAL && operation.immediate() == immediate;
case 0x17 -> operation.kind() == BytecodeEmitter.OperationKind.PUSH_I32 && operation.immediate() == immediate;
case 0x20 -> operation.kind() == BytecodeEmitter.OperationKind.ADD;
case 0x21 -> operation.kind() == BytecodeEmitter.OperationKind.SUB;
case 0x22 -> operation.kind() == BytecodeEmitter.OperationKind.MUL;
case 0x23 -> operation.kind() == BytecodeEmitter.OperationKind.DIV;
case 0x24 -> operation.kind() == BytecodeEmitter.OperationKind.MOD;
case 0x30 -> operation.kind() == BytecodeEmitter.OperationKind.EQ;
case 0x31 -> operation.kind() == BytecodeEmitter.OperationKind.NEQ;
case 0x32 -> operation.kind() == BytecodeEmitter.OperationKind.LT;
case 0x33 -> operation.kind() == BytecodeEmitter.OperationKind.GT;
case 0x34 -> operation.kind() == BytecodeEmitter.OperationKind.AND;
case 0x35 -> operation.kind() == BytecodeEmitter.OperationKind.OR;
case 0x36 -> operation.kind() == BytecodeEmitter.OperationKind.NOT;
case 0x3C -> operation.kind() == BytecodeEmitter.OperationKind.LTE;
case 0x3D -> operation.kind() == BytecodeEmitter.OperationKind.GTE;
case 0x3E -> operation.kind() == BytecodeEmitter.OperationKind.NEG;
case 0x50 -> operation.kind() == BytecodeEmitter.OperationKind.CALL_FUNC && operation.immediate() == immediate;
case 0x02 -> operation.kind() == BytecodeEmitter.OperationKind.JMP && operation.immediate() == immediate;
case 0x04 -> operation.kind() == BytecodeEmitter.OperationKind.JMP_IF_TRUE && operation.immediate() == immediate;
@ -157,8 +175,26 @@ public record IRVMProgram(
case 0x01 -> BytecodeEmitter.Operation.halt();
case 0x51 -> BytecodeEmitter.Operation.ret();
case 0x10 -> BytecodeEmitter.Operation.pushConst(immediate, null);
case 0x11 -> BytecodeEmitter.Operation.pop(null);
case 0x16 -> BytecodeEmitter.Operation.pushBool(immediate != 0, null);
case 0x42 -> BytecodeEmitter.Operation.getLocal(immediate, null);
case 0x43 -> BytecodeEmitter.Operation.setLocal(immediate, null);
case 0x17 -> BytecodeEmitter.Operation.pushI32(immediate, null);
case 0x20 -> BytecodeEmitter.Operation.add(null);
case 0x21 -> BytecodeEmitter.Operation.sub(null);
case 0x22 -> BytecodeEmitter.Operation.mul(null);
case 0x23 -> BytecodeEmitter.Operation.div(null);
case 0x24 -> BytecodeEmitter.Operation.mod(null);
case 0x30 -> BytecodeEmitter.Operation.eq(null);
case 0x31 -> BytecodeEmitter.Operation.neq(null);
case 0x32 -> BytecodeEmitter.Operation.lt(null);
case 0x33 -> BytecodeEmitter.Operation.gt(null);
case 0x34 -> BytecodeEmitter.Operation.and(null);
case 0x35 -> BytecodeEmitter.Operation.or(null);
case 0x36 -> BytecodeEmitter.Operation.not(null);
case 0x3C -> BytecodeEmitter.Operation.lte(null);
case 0x3D -> BytecodeEmitter.Operation.gte(null);
case 0x3E -> BytecodeEmitter.Operation.neg(null);
case 0x50 -> BytecodeEmitter.Operation.callFunc(immediate);
case 0x02 -> BytecodeEmitter.Operation.jmp(immediate, null);
case 0x04 -> BytecodeEmitter.Operation.jmpIfTrue(immediate, null);
@ -211,8 +247,26 @@ public record IRVMProgram(
case HALT -> new IRVMInstruction(IRVMOp.HALT, null);
case RET -> new IRVMInstruction(IRVMOp.RET, null);
case PUSH_CONST -> new IRVMInstruction(IRVMOp.PUSH_CONST, operation.immediate());
case POP -> new IRVMInstruction(IRVMOp.POP, null);
case PUSH_BOOL -> new IRVMInstruction(IRVMOp.PUSH_BOOL, operation.immediate());
case GET_LOCAL -> new IRVMInstruction(IRVMOp.GET_LOCAL, operation.immediate());
case SET_LOCAL -> new IRVMInstruction(IRVMOp.SET_LOCAL, operation.immediate());
case PUSH_I32 -> new IRVMInstruction(IRVMOp.PUSH_I32, operation.immediate());
case ADD -> new IRVMInstruction(IRVMOp.ADD, null);
case SUB -> new IRVMInstruction(IRVMOp.SUB, null);
case MUL -> new IRVMInstruction(IRVMOp.MUL, null);
case DIV -> new IRVMInstruction(IRVMOp.DIV, null);
case MOD -> new IRVMInstruction(IRVMOp.MOD, null);
case EQ -> new IRVMInstruction(IRVMOp.EQ, null);
case NEQ -> new IRVMInstruction(IRVMOp.NEQ, null);
case LT -> new IRVMInstruction(IRVMOp.LT, null);
case LTE -> new IRVMInstruction(IRVMOp.LTE, null);
case GT -> new IRVMInstruction(IRVMOp.GT, null);
case GTE -> new IRVMInstruction(IRVMOp.GTE, null);
case AND -> new IRVMInstruction(IRVMOp.AND, null);
case OR -> new IRVMInstruction(IRVMOp.OR, null);
case NOT -> new IRVMInstruction(IRVMOp.NOT, null);
case NEG -> new IRVMInstruction(IRVMOp.NEG, null);
case CALL_FUNC -> new IRVMInstruction(IRVMOp.CALL, operation.immediate());
case JMP -> new IRVMInstruction(IRVMOp.JMP, operation.immediate());
case JMP_IF_TRUE -> new IRVMInstruction(IRVMOp.JMP_IF_TRUE, operation.immediate());

View File

@ -90,6 +90,24 @@ public class LowerToIRVMService {
operations.add(BytecodeEmitter.Operation.pushConst(constPoolIndex, sourceSpan));
functionPc += IRVMOp.PUSH_CONST.immediateSize() + 2;
}
case PUSH_BOOL -> {
final var raw = instr.label() == null ? "" : instr.label().trim().toLowerCase();
final Integer value = switch (raw) {
case "1", "true" -> 1;
case "0", "false" -> 0;
default -> null;
};
if (value == null) {
throw loweringError(
fn,
instr,
IRVMLoweringErrorCode.LOWER_IRVM_INVALID_INTRINSIC_ID,
"invalid PUSH_BOOL literal payload: " + instr.label());
}
instructions.add(new IRVMInstruction(IRVMOp.PUSH_BOOL, value));
operations.add(BytecodeEmitter.Operation.pushBool(value != 0, sourceSpan));
functionPc += IRVMOp.PUSH_BOOL.immediateSize() + 2;
}
case GET_LOCAL -> {
final var slot = instr.expectedArgSlots();
if (slot == null) {
@ -103,6 +121,99 @@ public class LowerToIRVMService {
operations.add(BytecodeEmitter.Operation.getLocal(slot, sourceSpan));
functionPc += IRVMOp.GET_LOCAL.immediateSize() + 2;
}
case SET_LOCAL -> {
final var slot = instr.expectedArgSlots();
if (slot == null) {
throw loweringError(
fn,
instr,
IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE,
"missing local slot for SET_LOCAL");
}
instructions.add(new IRVMInstruction(IRVMOp.SET_LOCAL, slot));
operations.add(BytecodeEmitter.Operation.setLocal(slot, sourceSpan));
functionPc += IRVMOp.SET_LOCAL.immediateSize() + 2;
}
case POP -> {
instructions.add(new IRVMInstruction(IRVMOp.POP, null));
operations.add(BytecodeEmitter.Operation.pop(sourceSpan));
functionPc += IRVMOp.POP.immediateSize() + 2;
}
case ADD -> {
instructions.add(new IRVMInstruction(IRVMOp.ADD, null));
operations.add(BytecodeEmitter.Operation.add(sourceSpan));
functionPc += IRVMOp.ADD.immediateSize() + 2;
}
case SUB -> {
instructions.add(new IRVMInstruction(IRVMOp.SUB, null));
operations.add(BytecodeEmitter.Operation.sub(sourceSpan));
functionPc += IRVMOp.SUB.immediateSize() + 2;
}
case MUL -> {
instructions.add(new IRVMInstruction(IRVMOp.MUL, null));
operations.add(BytecodeEmitter.Operation.mul(sourceSpan));
functionPc += IRVMOp.MUL.immediateSize() + 2;
}
case DIV -> {
instructions.add(new IRVMInstruction(IRVMOp.DIV, null));
operations.add(BytecodeEmitter.Operation.div(sourceSpan));
functionPc += IRVMOp.DIV.immediateSize() + 2;
}
case MOD -> {
instructions.add(new IRVMInstruction(IRVMOp.MOD, null));
operations.add(BytecodeEmitter.Operation.mod(sourceSpan));
functionPc += IRVMOp.MOD.immediateSize() + 2;
}
case EQ -> {
instructions.add(new IRVMInstruction(IRVMOp.EQ, null));
operations.add(BytecodeEmitter.Operation.eq(sourceSpan));
functionPc += IRVMOp.EQ.immediateSize() + 2;
}
case NEQ -> {
instructions.add(new IRVMInstruction(IRVMOp.NEQ, null));
operations.add(BytecodeEmitter.Operation.neq(sourceSpan));
functionPc += IRVMOp.NEQ.immediateSize() + 2;
}
case LT -> {
instructions.add(new IRVMInstruction(IRVMOp.LT, null));
operations.add(BytecodeEmitter.Operation.lt(sourceSpan));
functionPc += IRVMOp.LT.immediateSize() + 2;
}
case LTE -> {
instructions.add(new IRVMInstruction(IRVMOp.LTE, null));
operations.add(BytecodeEmitter.Operation.lte(sourceSpan));
functionPc += IRVMOp.LTE.immediateSize() + 2;
}
case GT -> {
instructions.add(new IRVMInstruction(IRVMOp.GT, null));
operations.add(BytecodeEmitter.Operation.gt(sourceSpan));
functionPc += IRVMOp.GT.immediateSize() + 2;
}
case GTE -> {
instructions.add(new IRVMInstruction(IRVMOp.GTE, null));
operations.add(BytecodeEmitter.Operation.gte(sourceSpan));
functionPc += IRVMOp.GTE.immediateSize() + 2;
}
case AND -> {
instructions.add(new IRVMInstruction(IRVMOp.AND, null));
operations.add(BytecodeEmitter.Operation.and(sourceSpan));
functionPc += IRVMOp.AND.immediateSize() + 2;
}
case OR -> {
instructions.add(new IRVMInstruction(IRVMOp.OR, null));
operations.add(BytecodeEmitter.Operation.or(sourceSpan));
functionPc += IRVMOp.OR.immediateSize() + 2;
}
case NOT -> {
instructions.add(new IRVMInstruction(IRVMOp.NOT, null));
operations.add(BytecodeEmitter.Operation.not(sourceSpan));
functionPc += IRVMOp.NOT.immediateSize() + 2;
}
case NEG -> {
instructions.add(new IRVMInstruction(IRVMOp.NEG, null));
operations.add(BytecodeEmitter.Operation.neg(sourceSpan));
functionPc += IRVMOp.NEG.immediateSize() + 2;
}
case HALT -> {
instructions.add(new IRVMInstruction(IRVMOp.HALT, null));
operations.add(BytecodeEmitter.Operation.halt(sourceSpan));

View File

@ -232,6 +232,20 @@ public record IRBackendExecutableFunction(
throw new IllegalArgumentException("PUSH_I32 must not carry call metadata");
}
}
case PUSH_BOOL -> {
if (label.isBlank()) {
throw new IllegalArgumentException("PUSH_BOOL requires immediate label payload");
}
if (!targetLabel.isBlank()
|| !calleeCallableName.isBlank()
|| calleeCallableId != null
|| hostCall != null
|| intrinsicCall != null
|| expectedArgSlots != null
|| expectedRetSlots != null) {
throw new IllegalArgumentException("PUSH_BOOL must not carry call metadata");
}
}
case PUSH_CONST -> {
if (!targetLabel.isBlank()
|| !calleeCallableName.isBlank()
@ -257,6 +271,20 @@ public record IRBackendExecutableFunction(
throw new IllegalArgumentException("GET_LOCAL must not carry call metadata");
}
}
case SET_LOCAL -> {
if (expectedArgSlots == null) {
throw new IllegalArgumentException("SET_LOCAL requires local slot in expectedArgSlots");
}
if (!label.isBlank()
|| !targetLabel.isBlank()
|| !calleeCallableName.isBlank()
|| calleeCallableId != null
|| hostCall != null
|| intrinsicCall != null
|| expectedRetSlots != null) {
throw new IllegalArgumentException("SET_LOCAL must not carry call metadata");
}
}
case CALL_FUNC -> {
if (calleeCallableId == null) {
throw new IllegalArgumentException("CALL_FUNC requires calleeCallableId");
@ -287,10 +315,13 @@ public record IRBackendExecutableFunction(
throw new IllegalArgumentException("CALL_INTRINSIC must not carry host metadata");
}
}
case HALT, RET -> {
case HALT, RET, POP, ADD, SUB, MUL, DIV, MOD, EQ, NEQ, LT, LTE, GT, GTE, AND, OR, NOT, NEG -> {
if (!calleeCallableName.isBlank() || calleeCallableId != null || hostCall != null || intrinsicCall != null) {
throw new IllegalArgumentException(kind + " must not carry callsite metadata");
}
if (!label.isBlank() || !targetLabel.isBlank()) {
throw new IllegalArgumentException(kind + " must not carry label metadata");
}
if (expectedArgSlots != null || expectedRetSlots != null) {
throw new IllegalArgumentException(kind + " must not carry expected slot metadata");
}
@ -329,8 +360,26 @@ public record IRBackendExecutableFunction(
public enum InstructionKind {
PUSH_I32,
PUSH_BOOL,
PUSH_CONST,
GET_LOCAL,
SET_LOCAL,
POP,
ADD,
SUB,
MUL,
DIV,
MOD,
EQ,
NEQ,
LT,
LTE,
GT,
GTE,
AND,
OR,
NOT,
NEG,
HALT,
RET,
CALL_FUNC,

View File

@ -15,6 +15,10 @@ fn frame() -> void
{
total += 5;
}
else if (Input.pad().x().pressed())
{
total -= 10;
}
if (total == 30)
{
@ -24,7 +28,7 @@ fn frame() -> void
{
Log.error("50 is the magic number!");
}
else
else if (total == 15)
{
Log.warn("The magic number is neither 30 nor 50!");
}