implements one-shot PBS boot guard in published wrapper lowering

This commit is contained in:
bQUARKz 2026-03-26 20:05:27 +00:00
parent 64762ca227
commit ce55cf6405
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
9 changed files with 268 additions and 12 deletions

View File

@ -240,9 +240,19 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
&& entryPointCallableName != null && entryPointCallableName != null
&& !entryPointCallableName.isBlank() && !entryPointCallableName.isBlank()
&& !entryPointModuleId.isNone()) { && !entryPointModuleId.isNone()) {
final var existingBootGuard = globals.stream()
.filter(global -> moduleIdsMatch(entryPointModuleId, global.moduleId()))
.filter(global -> global.hiddenKind() == IRHiddenGlobalKind.BOOT_GUARD)
.findFirst()
.orElse(null);
final var bootGuardSlot = existingBootGuard != null
? existingBootGuard.slot()
: nextGlobalSlot(globals, entryPointModuleId);
final var wrapperCallableId = new CallableId(callableSignatures.size()); final var wrapperCallableId = new CallableId(callableSignatures.size());
callableSignatures.add(new CallableSignatureRef(entryPointModuleId, entryPointCallableName, 0, "() -> unit")); callableSignatures.add(new CallableSignatureRef(entryPointModuleId, entryPointCallableName, 0, "() -> unit"));
final var instructions = new ArrayList<IRBackendExecutableFunction.Instruction>(); final var instructions = new ArrayList<IRBackendExecutableFunction.Instruction>();
instructions.add(getGlobalInstruction(bootGuardSlot, frameExecutable.span()));
instructions.add(jumpIfTrueInstruction("bootstrap_done", frameExecutable.span()));
for (final var moduleId : sortedModuleIds) { for (final var moduleId : sortedModuleIds) {
final var moduleInitCallableId = moduleInitCallableIds.get(moduleId); final var moduleInitCallableId = moduleInitCallableIds.get(moduleId);
if (moduleInitCallableId == null) { if (moduleInitCallableId == null) {
@ -271,6 +281,9 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
0, 0,
frameExecutable.span())); frameExecutable.span()));
} }
instructions.add(pushBoolInstruction(true, frameExecutable.span()));
instructions.add(setGlobalInstruction(bootGuardSlot, frameExecutable.span()));
instructions.add(labelInstruction("bootstrap_done", frameExecutable.span()));
instructions.add(callInstruction(frameExecutable, frameExecutable.span())); instructions.add(callInstruction(frameExecutable, frameExecutable.span()));
instructions.add(retInstruction(frameExecutable.span())); instructions.add(retInstruction(frameExecutable.span()));
executableFunctions.add(new IRBackendExecutableFunction( executableFunctions.add(new IRBackendExecutableFunction(
@ -287,15 +300,13 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
ReadOnlyList.wrap(instructions), ReadOnlyList.wrap(instructions),
frameExecutable.span())); frameExecutable.span()));
if (globals.stream().noneMatch(global -> if (existingBootGuard == null) {
moduleIdsMatch(entryPointModuleId, global.moduleId())
&& global.hiddenKind() == IRHiddenGlobalKind.BOOT_GUARD)) {
globals.add(new IRGlobal( globals.add(new IRGlobal(
frameExecutable.fileId(), frameExecutable.fileId(),
entryPointModuleId, entryPointModuleId,
PbsFrontendCompiler.bootGuardGlobalName(entryPointModuleId), PbsFrontendCompiler.bootGuardGlobalName(entryPointModuleId),
"bool", "bool",
0, bootGuardSlot,
IRGlobalVisibility.MODULE, IRGlobalVisibility.MODULE,
true, true,
IRHiddenGlobalKind.BOOT_GUARD, IRHiddenGlobalKind.BOOT_GUARD,
@ -325,6 +336,19 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
.build(); .build();
} }
private int nextGlobalSlot(
final ArrayList<IRGlobal> globals,
final ModuleId moduleId) {
var nextSlot = 0;
for (final var global : globals) {
if (!moduleIdsMatch(moduleId, global.moduleId())) {
continue;
}
nextSlot = Math.max(nextSlot, global.slot() + 1);
}
return nextSlot;
}
private ArrayList<IRGlobal> normalizeGlobalSlots(final ArrayList<IRGlobal> globals) { private ArrayList<IRGlobal> normalizeGlobalSlots(final ArrayList<IRGlobal> globals) {
final var nextSlotByModule = new LinkedHashMap<ModuleId, Integer>(); final var nextSlotByModule = new LinkedHashMap<ModuleId, Integer>();
final var normalized = new ArrayList<IRGlobal>(globals.size()); final var normalized = new ArrayList<IRGlobal>(globals.size());
@ -390,6 +414,82 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
span); span);
} }
private IRBackendExecutableFunction.Instruction getGlobalInstruction(
final int slot,
final Span span) {
return new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.GET_GLOBAL,
"",
null,
null,
null,
slot,
null,
span);
}
private IRBackendExecutableFunction.Instruction setGlobalInstruction(
final int slot,
final Span span) {
return new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.SET_GLOBAL,
"",
null,
null,
null,
slot,
null,
span);
}
private IRBackendExecutableFunction.Instruction pushBoolInstruction(
final boolean value,
final Span span) {
return new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.PUSH_BOOL,
"",
null,
null,
null,
value ? "true" : "false",
null,
null,
null,
span);
}
private IRBackendExecutableFunction.Instruction labelInstruction(
final String label,
final Span span) {
return new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.LABEL,
"",
null,
null,
null,
label,
null,
null,
null,
span);
}
private IRBackendExecutableFunction.Instruction jumpIfTrueInstruction(
final String targetLabel,
final Span span) {
return new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.JMP_IF_TRUE,
"",
null,
null,
null,
null,
targetLabel,
null,
null,
span);
}
private ModuleId normalizeModuleId(final ModuleId moduleId) { private ModuleId normalizeModuleId(final ModuleId moduleId) {
return moduleId == null ? ModuleId.none() : moduleId; return moduleId == null ? ModuleId.none() : moduleId;
} }

View File

@ -369,15 +369,21 @@ class PBSFrontendPhaseServiceTest {
.orElseThrow(); .orElseThrow();
assertTrue(bootGuard.isHidden()); assertTrue(bootGuard.isHidden());
assertTrue(bootGuard.name().startsWith("__pbs.boot_guard$m")); assertTrue(bootGuard.name().startsWith("__pbs.boot_guard$m"));
assertEquals(1, bootGuard.slot());
final var wrapper = irBackend.getExecutableFunctions().stream() final var wrapper = irBackend.getExecutableFunctions().stream()
.filter(function -> irBackend.getEntryPointCallableName().equals(function.callableName())) .filter(function -> irBackend.getEntryPointCallableName().equals(function.callableName()))
.findFirst() .findFirst()
.orElseThrow(); .orElseThrow();
assertEquals(4, wrapper.instructions().size()); assertEquals(9, wrapper.instructions().size());
assertTrue(wrapper.instructions().get(0).calleeCallableName().startsWith("__pbs.module_init$m")); assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.GET_GLOBAL, wrapper.instructions().get(0).kind());
assertTrue(wrapper.instructions().get(1).calleeCallableName().startsWith("__pbs.project_init$m")); assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.JMP_IF_TRUE, wrapper.instructions().get(1).kind());
assertEquals("frame", wrapper.instructions().get(2).calleeCallableName()); assertTrue(wrapper.instructions().get(2).calleeCallableName().startsWith("__pbs.module_init$m"));
assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.RET, wrapper.instructions().get(3).kind()); assertTrue(wrapper.instructions().get(3).calleeCallableName().startsWith("__pbs.project_init$m"));
assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.PUSH_BOOL, wrapper.instructions().get(4).kind());
assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.SET_GLOBAL, wrapper.instructions().get(5).kind());
assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.LABEL, wrapper.instructions().get(6).kind());
assertEquals("frame", wrapper.instructions().get(7).calleeCallableName());
assertEquals(p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.RET, wrapper.instructions().get(8).kind());
} }
@Test @Test

View File

@ -35,6 +35,8 @@ public class BytecodeEmitter {
private static final int OP_LTE = 0x3C; private static final int OP_LTE = 0x3C;
private static final int OP_GTE = 0x3D; private static final int OP_GTE = 0x3D;
private static final int OP_NEG = 0x3E; private static final int OP_NEG = 0x3E;
private static final int OP_GET_GLOBAL = 0x40;
private static final int OP_SET_GLOBAL = 0x41;
private static final int OP_GET_LOCAL = 0x42; private static final int OP_GET_LOCAL = 0x42;
private static final int OP_SET_LOCAL = 0x43; private static final int OP_SET_LOCAL = 0x43;
private static final int OP_PUSH_I32 = 0x17; private static final int OP_PUSH_I32 = 0x17;
@ -81,10 +83,18 @@ public class BytecodeEmitter {
writeOpU32(code, OP_GET_LOCAL, op.immediate()); writeOpU32(code, OP_GET_LOCAL, op.immediate());
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span()))); spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
} }
case GET_GLOBAL -> {
writeOpU32(code, OP_GET_GLOBAL, op.immediate());
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case SET_LOCAL -> { case SET_LOCAL -> {
writeOpU32(code, OP_SET_LOCAL, op.immediate()); writeOpU32(code, OP_SET_LOCAL, op.immediate());
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span()))); spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
} }
case SET_GLOBAL -> {
writeOpU32(code, OP_SET_GLOBAL, op.immediate());
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case PUSH_BOOL -> { case PUSH_BOOL -> {
writeOpU8(code, OP_PUSH_BOOL, op.immediate() == 0 ? 0 : 1); writeOpU8(code, OP_PUSH_BOOL, op.immediate() == 0 ? 0 : 1);
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span()))); spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
@ -307,7 +317,9 @@ public class BytecodeEmitter {
PUSH_CONST, PUSH_CONST,
POP, POP,
GET_LOCAL, GET_LOCAL,
GET_GLOBAL,
SET_LOCAL, SET_LOCAL,
SET_GLOBAL,
PUSH_BOOL, PUSH_BOOL,
PUSH_I32, PUSH_I32,
ADD, ADD,
@ -397,10 +409,18 @@ public class BytecodeEmitter {
return new Operation(OperationKind.GET_LOCAL, slot, null, null, null, span); return new Operation(OperationKind.GET_LOCAL, slot, null, null, null, span);
} }
public static Operation getGlobal(final int slot, final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.GET_GLOBAL, slot, null, null, null, span);
}
public static Operation setLocal(final int slot, final BytecodeModule.SourceSpan span) { public static Operation setLocal(final int slot, final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.SET_LOCAL, slot, null, null, null, span); return new Operation(OperationKind.SET_LOCAL, slot, null, null, null, span);
} }
public static Operation setGlobal(final int slot, final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.SET_GLOBAL, slot, null, null, null, span);
}
public static Operation pushBool(final boolean value, final BytecodeModule.SourceSpan 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); return new Operation(OperationKind.PUSH_BOOL, value ? 1 : 0, null, null, null, span);
} }

View File

@ -37,6 +37,8 @@ public record IRVMOp(
public static final IRVMOp LTE = new IRVMOp("LTE", 0x3C, 0, 2, 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 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 NEG = new IRVMOp("NEG", 0x3E, 0, 1, 1, false, false, false);
public static final IRVMOp GET_GLOBAL = new IRVMOp("GET_GLOBAL", 0x40, 4, 0, 1, false, false, false);
public static final IRVMOp SET_GLOBAL = new IRVMOp("SET_GLOBAL", 0x41, 4, 1, 0, false, false, false);
public static final IRVMOp GET_LOCAL = new IRVMOp("GET_LOCAL", 0x42, 4, 0, 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 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); public static final IRVMOp INTERNAL_EXT = new IRVMOp("IRVM_EXT_NOP", 0xFFFF, 0, 0, 0, false, false, true);

View File

@ -13,7 +13,9 @@ public final class IRVMProfileFeatureGate {
IRVMOp.RET, IRVMOp.RET,
IRVMOp.PUSH_CONST, IRVMOp.PUSH_CONST,
IRVMOp.POP, IRVMOp.POP,
IRVMOp.GET_GLOBAL,
IRVMOp.GET_LOCAL, IRVMOp.GET_LOCAL,
IRVMOp.SET_GLOBAL,
IRVMOp.SET_LOCAL, IRVMOp.SET_LOCAL,
IRVMOp.CALL, IRVMOp.CALL,
IRVMOp.JMP, IRVMOp.JMP,
@ -43,7 +45,9 @@ public final class IRVMProfileFeatureGate {
IRVMOp.RET, IRVMOp.RET,
IRVMOp.PUSH_CONST, IRVMOp.PUSH_CONST,
IRVMOp.POP, IRVMOp.POP,
IRVMOp.GET_GLOBAL,
IRVMOp.GET_LOCAL, IRVMOp.GET_LOCAL,
IRVMOp.SET_GLOBAL,
IRVMOp.SET_LOCAL, IRVMOp.SET_LOCAL,
IRVMOp.CALL, IRVMOp.CALL,
IRVMOp.JMP, IRVMOp.JMP,

View File

@ -94,7 +94,9 @@ public record IRVMProgram(
case 0x10 -> operation.kind() == BytecodeEmitter.OperationKind.PUSH_CONST && operation.immediate() == immediate; case 0x10 -> operation.kind() == BytecodeEmitter.OperationKind.PUSH_CONST && operation.immediate() == immediate;
case 0x11 -> operation.kind() == BytecodeEmitter.OperationKind.POP; case 0x11 -> operation.kind() == BytecodeEmitter.OperationKind.POP;
case 0x16 -> operation.kind() == BytecodeEmitter.OperationKind.PUSH_BOOL; case 0x16 -> operation.kind() == BytecodeEmitter.OperationKind.PUSH_BOOL;
case 0x40 -> operation.kind() == BytecodeEmitter.OperationKind.GET_GLOBAL && operation.immediate() == immediate;
case 0x42 -> operation.kind() == BytecodeEmitter.OperationKind.GET_LOCAL && operation.immediate() == immediate; case 0x42 -> operation.kind() == BytecodeEmitter.OperationKind.GET_LOCAL && operation.immediate() == immediate;
case 0x41 -> operation.kind() == BytecodeEmitter.OperationKind.SET_GLOBAL && operation.immediate() == immediate;
case 0x43 -> operation.kind() == BytecodeEmitter.OperationKind.SET_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 0x17 -> operation.kind() == BytecodeEmitter.OperationKind.PUSH_I32 && operation.immediate() == immediate;
case 0x20 -> operation.kind() == BytecodeEmitter.OperationKind.ADD; case 0x20 -> operation.kind() == BytecodeEmitter.OperationKind.ADD;
@ -176,7 +178,9 @@ public record IRVMProgram(
case 0x10 -> BytecodeEmitter.Operation.pushConst(immediate, null); case 0x10 -> BytecodeEmitter.Operation.pushConst(immediate, null);
case 0x11 -> BytecodeEmitter.Operation.pop(null); case 0x11 -> BytecodeEmitter.Operation.pop(null);
case 0x16 -> BytecodeEmitter.Operation.pushBool(immediate != 0, null); case 0x16 -> BytecodeEmitter.Operation.pushBool(immediate != 0, null);
case 0x40 -> BytecodeEmitter.Operation.getGlobal(immediate, null);
case 0x42 -> BytecodeEmitter.Operation.getLocal(immediate, null); case 0x42 -> BytecodeEmitter.Operation.getLocal(immediate, null);
case 0x41 -> BytecodeEmitter.Operation.setGlobal(immediate, null);
case 0x43 -> BytecodeEmitter.Operation.setLocal(immediate, null); case 0x43 -> BytecodeEmitter.Operation.setLocal(immediate, null);
case 0x17 -> BytecodeEmitter.Operation.pushI32(immediate, null); case 0x17 -> BytecodeEmitter.Operation.pushI32(immediate, null);
case 0x20 -> BytecodeEmitter.Operation.add(null); case 0x20 -> BytecodeEmitter.Operation.add(null);
@ -248,7 +252,9 @@ public record IRVMProgram(
case PUSH_CONST -> new IRVMInstruction(IRVMOp.PUSH_CONST, operation.immediate()); case PUSH_CONST -> new IRVMInstruction(IRVMOp.PUSH_CONST, operation.immediate());
case POP -> new IRVMInstruction(IRVMOp.POP, null); case POP -> new IRVMInstruction(IRVMOp.POP, null);
case PUSH_BOOL -> new IRVMInstruction(IRVMOp.PUSH_BOOL, operation.immediate()); case PUSH_BOOL -> new IRVMInstruction(IRVMOp.PUSH_BOOL, operation.immediate());
case GET_GLOBAL -> new IRVMInstruction(IRVMOp.GET_GLOBAL, operation.immediate());
case GET_LOCAL -> new IRVMInstruction(IRVMOp.GET_LOCAL, operation.immediate()); case GET_LOCAL -> new IRVMInstruction(IRVMOp.GET_LOCAL, operation.immediate());
case SET_GLOBAL -> new IRVMInstruction(IRVMOp.SET_GLOBAL, operation.immediate());
case SET_LOCAL -> new IRVMInstruction(IRVMOp.SET_LOCAL, operation.immediate()); case SET_LOCAL -> new IRVMInstruction(IRVMOp.SET_LOCAL, operation.immediate());
case PUSH_I32 -> new IRVMInstruction(IRVMOp.PUSH_I32, operation.immediate()); case PUSH_I32 -> new IRVMInstruction(IRVMOp.PUSH_I32, operation.immediate());
case ADD -> new IRVMInstruction(IRVMOp.ADD, null); case ADD -> new IRVMInstruction(IRVMOp.ADD, null);

View File

@ -124,6 +124,19 @@ public class LowerToIRVMService {
operations.add(BytecodeEmitter.Operation.getLocal(slot, sourceSpan)); operations.add(BytecodeEmitter.Operation.getLocal(slot, sourceSpan));
functionPc += IRVMOp.GET_LOCAL.immediateSize() + 2; functionPc += IRVMOp.GET_LOCAL.immediateSize() + 2;
} }
case GET_GLOBAL -> {
final var slot = instr.expectedArgSlots();
if (slot == null) {
throw loweringError(
fn,
instr,
IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE,
"missing global slot for GET_GLOBAL");
}
instructions.add(new IRVMInstruction(IRVMOp.GET_GLOBAL, slot));
operations.add(BytecodeEmitter.Operation.getGlobal(slot, sourceSpan));
functionPc += IRVMOp.GET_GLOBAL.immediateSize() + 2;
}
case SET_LOCAL -> { case SET_LOCAL -> {
final var slot = instr.expectedArgSlots(); final var slot = instr.expectedArgSlots();
if (slot == null) { if (slot == null) {
@ -137,6 +150,19 @@ public class LowerToIRVMService {
operations.add(BytecodeEmitter.Operation.setLocal(slot, sourceSpan)); operations.add(BytecodeEmitter.Operation.setLocal(slot, sourceSpan));
functionPc += IRVMOp.SET_LOCAL.immediateSize() + 2; functionPc += IRVMOp.SET_LOCAL.immediateSize() + 2;
} }
case SET_GLOBAL -> {
final var slot = instr.expectedArgSlots();
if (slot == null) {
throw loweringError(
fn,
instr,
IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE,
"missing global slot for SET_GLOBAL");
}
instructions.add(new IRVMInstruction(IRVMOp.SET_GLOBAL, slot));
operations.add(BytecodeEmitter.Operation.setGlobal(slot, sourceSpan));
functionPc += IRVMOp.SET_GLOBAL.immediateSize() + 2;
}
case POP -> { case POP -> {
instructions.add(new IRVMInstruction(IRVMOp.POP, null)); instructions.add(new IRVMInstruction(IRVMOp.POP, null));
operations.add(BytecodeEmitter.Operation.pop(sourceSpan)); operations.add(BytecodeEmitter.Operation.pop(sourceSpan));

View File

@ -86,8 +86,13 @@ class LowerToIRVMServiceTest {
fn("boot", "app", 10, ReadOnlyList.from(ret())), fn("boot", "app", 10, ReadOnlyList.from(ret())),
fn("frame", "app", 20, ReadOnlyList.from(ret())), fn("frame", "app", 20, ReadOnlyList.from(ret())),
fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from( fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from(
getGlobal(0),
jmpIfTrue("bootstrap_done"),
callFunc("app", "__pbs.module_init$m0", 31), callFunc("app", "__pbs.module_init$m0", 31),
callFunc("app", "__pbs.project_init$m0", 32), callFunc("app", "__pbs.project_init$m0", 32),
pushBool(true),
setGlobal(0),
label("bootstrap_done"),
callFunc("app", "frame", 20), callFunc("app", "frame", 20),
ret())))) ret()))))
.build(); .build();
@ -95,10 +100,14 @@ class LowerToIRVMServiceTest {
final var lowered = new LowerToIRVMService().lower(backend); final var lowered = new LowerToIRVMService().lower(backend);
assertEquals("__pbs.frame_wrapper$m0", lowered.module().functions().get(0).name()); assertEquals("__pbs.frame_wrapper$m0", lowered.module().functions().get(0).name());
assertEquals(IRVMOp.CALL, lowered.module().functions().get(0).instructions().get(0).op()); assertEquals(IRVMOp.GET_GLOBAL, lowered.module().functions().get(0).instructions().get(0).op());
assertEquals(IRVMOp.CALL, lowered.module().functions().get(0).instructions().get(1).op()); assertEquals(IRVMOp.JMP_IF_TRUE, lowered.module().functions().get(0).instructions().get(1).op());
assertEquals(IRVMOp.CALL, lowered.module().functions().get(0).instructions().get(2).op()); assertEquals(IRVMOp.CALL, lowered.module().functions().get(0).instructions().get(2).op());
assertEquals(IRVMOp.RET, lowered.module().functions().get(0).instructions().get(3).op()); assertEquals(IRVMOp.CALL, lowered.module().functions().get(0).instructions().get(3).op());
assertEquals(IRVMOp.PUSH_BOOL, lowered.module().functions().get(0).instructions().get(4).op());
assertEquals(IRVMOp.SET_GLOBAL, lowered.module().functions().get(0).instructions().get(5).op());
assertEquals(IRVMOp.CALL, lowered.module().functions().get(0).instructions().get(6).op());
assertEquals(IRVMOp.RET, lowered.module().functions().get(0).instructions().get(7).op());
assertEquals(IRVMOp.RET, lowered.module().functions().stream() assertEquals(IRVMOp.RET, lowered.module().functions().stream()
.filter(function -> "frame".equals(function.name())) .filter(function -> "frame".equals(function.name()))
.findFirst() .findFirst()
@ -583,4 +592,57 @@ class LowerToIRVMServiceTest {
null, null,
Span.none()); Span.none());
} }
private static IRBackendExecutableFunction.Instruction jmpIfTrue(final String target) {
return new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.JMP_IF_TRUE,
ModuleId.none(),
"",
null,
null,
null,
"",
target,
null,
null,
Span.none());
}
private static IRBackendExecutableFunction.Instruction getGlobal(final int slot) {
return new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.GET_GLOBAL,
"",
null,
null,
null,
slot,
null,
Span.none());
}
private static IRBackendExecutableFunction.Instruction setGlobal(final int slot) {
return new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.SET_GLOBAL,
"",
null,
null,
null,
slot,
null,
Span.none());
}
private static IRBackendExecutableFunction.Instruction pushBool(final boolean value) {
return new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.PUSH_BOOL,
"",
null,
null,
null,
value ? "true" : "false",
null,
null,
null,
Span.none());
}
} }

View File

@ -271,6 +271,20 @@ public record IRBackendExecutableFunction(
throw new IllegalArgumentException("GET_LOCAL must not carry call metadata"); throw new IllegalArgumentException("GET_LOCAL must not carry call metadata");
} }
} }
case GET_GLOBAL -> {
if (expectedArgSlots == null) {
throw new IllegalArgumentException("GET_GLOBAL requires global slot in expectedArgSlots");
}
if (!label.isBlank()
|| !targetLabel.isBlank()
|| !calleeCallableName.isBlank()
|| calleeCallableId != null
|| hostCall != null
|| intrinsicCall != null
|| expectedRetSlots != null) {
throw new IllegalArgumentException("GET_GLOBAL must not carry call metadata");
}
}
case SET_LOCAL -> { case SET_LOCAL -> {
if (expectedArgSlots == null) { if (expectedArgSlots == null) {
throw new IllegalArgumentException("SET_LOCAL requires local slot in expectedArgSlots"); throw new IllegalArgumentException("SET_LOCAL requires local slot in expectedArgSlots");
@ -285,6 +299,20 @@ public record IRBackendExecutableFunction(
throw new IllegalArgumentException("SET_LOCAL must not carry call metadata"); throw new IllegalArgumentException("SET_LOCAL must not carry call metadata");
} }
} }
case SET_GLOBAL -> {
if (expectedArgSlots == null) {
throw new IllegalArgumentException("SET_GLOBAL requires global slot in expectedArgSlots");
}
if (!label.isBlank()
|| !targetLabel.isBlank()
|| !calleeCallableName.isBlank()
|| calleeCallableId != null
|| hostCall != null
|| intrinsicCall != null
|| expectedRetSlots != null) {
throw new IllegalArgumentException("SET_GLOBAL must not carry call metadata");
}
}
case CALL_FUNC -> { case CALL_FUNC -> {
if (calleeCallableId == null) { if (calleeCallableId == null) {
throw new IllegalArgumentException("CALL_FUNC requires calleeCallableId"); throw new IllegalArgumentException("CALL_FUNC requires calleeCallableId");
@ -363,7 +391,9 @@ public record IRBackendExecutableFunction(
PUSH_BOOL, PUSH_BOOL,
PUSH_CONST, PUSH_CONST,
GET_LOCAL, GET_LOCAL,
GET_GLOBAL,
SET_LOCAL, SET_LOCAL,
SET_GLOBAL,
POP, POP,
ADD, ADD,
SUB, SUB,