new test main with code directly (fixes) runtime runing

This commit is contained in:
bQUARKz 2026-03-09 12:43:55 +00:00
parent 5630f8f119
commit 54f0b4b6ba
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
10 changed files with 309 additions and 12 deletions

View File

@ -287,6 +287,10 @@ public final class PbsFrontendCompiler {
if (functionCallableId == null) {
continue;
}
final var localSlotByNameId = new HashMap<NameId, Integer>();
for (var paramIndex = 0; paramIndex < fn.parameters().size(); paramIndex++) {
localSlotByNameId.put(nameTable.register(fn.parameters().get(paramIndex).name()), paramIndex);
}
final var loweringContext = new ExecutableLoweringContext(
normalizedModuleKey,
diagnostics,
@ -295,7 +299,8 @@ public final class PbsFrontendCompiler {
intrinsicByMethodName,
callableIdsByNameAndArity,
returnSlotsByCallableId,
intrinsicIdTable);
intrinsicIdTable,
localSlotByNameId);
final var terminated = lowerBlock(fn.body(), loweringContext);
final var instructions = loweringContext.instructions();
if (!terminated) {
@ -618,7 +623,7 @@ public final class PbsFrontendCompiler {
}
switch (expression) {
case PbsAst.CallExpr callExpr -> {
lowerExpression(callExpr.callee(), context);
lowerCallsiteReceiver(callExpr.callee(), context);
for (final var arg : callExpr.arguments()) {
lowerExpression(arg, context);
}
@ -678,16 +683,27 @@ public final class PbsFrontendCompiler {
}
}
case PbsAst.BlockExpr blockExpr -> lowerBlock(blockExpr.block(), context);
case PbsAst.IdentifierExpr ignored -> {
case PbsAst.IdentifierExpr identifierExpr -> {
final var slot = context.localSlotByNameId().get(context.nameTable().register(identifierExpr.name()));
if (slot != null) {
emitGetLocal(slot, identifierExpr.span(), context);
}
}
case PbsAst.IntLiteralExpr ignored -> {
case PbsAst.IntLiteralExpr intLiteralExpr -> {
if (intLiteralExpr.value() < Integer.MIN_VALUE || intLiteralExpr.value() > Integer.MAX_VALUE) {
reportUnsupportedLowering(
"int literal exceeds i32 lowering range: " + intLiteralExpr.value(),
intLiteralExpr.span(),
context);
return;
}
emitPushI32((int) intLiteralExpr.value(), intLiteralExpr.span(), context);
}
case PbsAst.FloatLiteralExpr ignored -> {
}
case PbsAst.BoundedLiteralExpr ignored -> {
}
case PbsAst.StringLiteralExpr ignored -> {
}
case PbsAst.StringLiteralExpr stringLiteralExpr -> emitPushConst(stringLiteralExpr.value(), stringLiteralExpr.span(), context);
case PbsAst.BoolLiteralExpr ignored -> {
}
case PbsAst.ThisExpr ignored -> {
@ -701,6 +717,20 @@ public final class PbsFrontendCompiler {
}
}
private void lowerCallsiteReceiver(
final PbsAst.Expression callee,
final ExecutableLoweringContext context) {
if (callee == null) {
return;
}
switch (callee) {
case PbsAst.MemberExpr memberExpr -> lowerExpression(memberExpr.receiver(), context);
case PbsAst.GroupExpr groupExpr -> lowerCallsiteReceiver(groupExpr.expression(), context);
default -> {
}
}
}
private void lowerCallsite(
final PbsAst.CallExpr callExpr,
final ExecutableLoweringContext context) {
@ -920,6 +950,60 @@ public final class PbsFrontendCompiler {
span));
}
private void emitPushI32(
final int value,
final p.studio.compiler.source.Span span,
final ExecutableLoweringContext context) {
context.instructions().add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.PUSH_I32,
"",
"",
null,
null,
null,
Integer.toString(value),
"",
null,
null,
span));
}
private void emitPushConst(
final String value,
final p.studio.compiler.source.Span span,
final ExecutableLoweringContext context) {
context.instructions().add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.PUSH_CONST,
"",
"",
null,
null,
null,
value == null ? "" : value,
"",
null,
null,
span));
}
private void emitGetLocal(
final int slot,
final p.studio.compiler.source.Span span,
final ExecutableLoweringContext context) {
context.instructions().add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.GET_LOCAL,
"",
"",
null,
null,
null,
"",
"",
slot,
null,
span));
}
private void reportUnsupportedLowering(
final String message,
final p.studio.compiler.source.Span span,
@ -964,9 +1048,34 @@ public final class PbsFrontendCompiler {
var outHeight = incomingHeightByInstruction.get(index);
switch (instruction.kind()) {
case CALL_FUNC -> outHeight += instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots();
case CALL_HOST -> outHeight += instruction.hostCall() == null ? 0 : instruction.hostCall().retSlots();
case CALL_INTRINSIC -> outHeight += instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots();
case PUSH_I32, PUSH_CONST, GET_LOCAL -> outHeight += 1;
case CALL_FUNC -> {
final var argSlots = instruction.expectedArgSlots() == null ? 0 : instruction.expectedArgSlots();
final var retSlots = instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots();
if (outHeight < argSlots) {
throw new ExecutableLoweringAnalysisException(
"stack underflow at call_func: need=%d have=%d".formatted(argSlots, outHeight));
}
outHeight = outHeight - argSlots + retSlots;
}
case CALL_HOST -> {
final var argSlots = instruction.hostCall() == null ? 0 : instruction.hostCall().argSlots();
final var retSlots = instruction.hostCall() == null ? 0 : instruction.hostCall().retSlots();
if (outHeight < argSlots) {
throw new ExecutableLoweringAnalysisException(
"stack underflow at call_host: need=%d have=%d".formatted(argSlots, outHeight));
}
outHeight = outHeight - argSlots + retSlots;
}
case CALL_INTRINSIC -> {
final var argSlots = instruction.expectedArgSlots() == null ? 0 : instruction.expectedArgSlots();
final var retSlots = instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots();
if (outHeight < argSlots) {
throw new ExecutableLoweringAnalysisException(
"stack underflow at call_intrinsic: need=%d have=%d".formatted(argSlots, outHeight));
}
outHeight = outHeight - argSlots + retSlots;
}
case HALT, LABEL, JMP, RET -> {
}
case JMP_IF_TRUE, JMP_IF_FALSE -> {
@ -1040,6 +1149,7 @@ public final class PbsFrontendCompiler {
private final Map<CallableResolutionKey, List<CallableId>> callableIdsByNameAndArity;
private final Map<CallableId, Integer> returnSlotsByCallableId;
private final IntrinsicTable intrinsicIdTable;
private final Map<NameId, Integer> localSlotByNameId;
private final ArrayList<IRBackendExecutableFunction.Instruction> instructions = new ArrayList<>();
private final ArrayDeque<LoopTargets> loopTargets = new ArrayDeque<>();
private int nextLabelId = 0;
@ -1052,7 +1162,8 @@ public final class PbsFrontendCompiler {
final Map<NameId, List<IRReservedMetadata.IntrinsicSurface>> intrinsicByMethodName,
final Map<CallableResolutionKey, List<CallableId>> callableIdsByNameAndArity,
final Map<CallableId, Integer> returnSlotsByCallableId,
final IntrinsicTable intrinsicIdTable) {
final IntrinsicTable intrinsicIdTable,
final Map<NameId, Integer> localSlotByNameId) {
this.moduleKey = moduleKey;
this.diagnostics = diagnostics;
this.nameTable = nameTable;
@ -1061,6 +1172,7 @@ public final class PbsFrontendCompiler {
this.callableIdsByNameAndArity = callableIdsByNameAndArity;
this.returnSlotsByCallableId = returnSlotsByCallableId;
this.intrinsicIdTable = intrinsicIdTable;
this.localSlotByNameId = localSlotByNameId == null ? Map.of() : localSlotByNameId;
}
private String moduleKey() {
@ -1095,6 +1207,10 @@ public final class PbsFrontendCompiler {
return intrinsicIdTable;
}
private Map<NameId, Integer> localSlotByNameId() {
return localSlotByNameId;
}
private ArrayList<IRBackendExecutableFunction.Instruction> instructions() {
return instructions;
}

View File

@ -3,7 +3,9 @@ callables=2
1:::a/0|()->simple:int
intrinsics=0
fn#0:::b id=0
GET_LOCAL calleeId=- intrinsicId=-
RET calleeId=- intrinsicId=-
fn#1:::a id=1
PUSH_I32 calleeId=- intrinsicId=-
CALL_FUNC calleeId=0 intrinsicId=-
RET calleeId=- intrinsicId=-

View File

@ -17,6 +17,9 @@ public class BytecodeEmitter {
private static final int OP_JMP = 0x02;
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_GET_LOCAL = 0x42;
private static final int OP_PUSH_I32 = 0x17;
private static final int OP_HOSTCALL = 0x71;
private static final int OP_SYSCALL = 0x70;
private static final int OP_INTRINSIC = 0x72;
@ -48,6 +51,18 @@ public class BytecodeEmitter {
writeOpU32(code, OP_CALL, op.immediate());
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
}
case PUSH_CONST -> {
writeOpU32(code, OP_PUSH_CONST, op.immediate());
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 PUSH_I32 -> {
writeOpU32(code, OP_PUSH_I32, op.immediate());
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())));
@ -194,6 +209,9 @@ public class BytecodeEmitter {
public enum OperationKind {
HALT,
RET,
PUSH_CONST,
GET_LOCAL,
PUSH_I32,
CALL_FUNC,
JMP,
JMP_IF_TRUE,
@ -254,6 +272,18 @@ public class BytecodeEmitter {
return new Operation(OperationKind.CALL_FUNC, funcId, null, null, null, span);
}
public static Operation pushConst(final int constPoolIndex, final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.PUSH_CONST, constPoolIndex, 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 pushI32(final int value, final BytecodeModule.SourceSpan span) {
return new Operation(OperationKind.PUSH_I32, value, null, null, null, span);
}
public static Operation hostcall(
final BytecodeModule.SyscallDecl decl,
final Integer expectedArgSlots,

View File

@ -12,6 +12,7 @@ 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 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);
@ -19,6 +20,6 @@ public record IRVMOp(
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_I32 = new IRVMOp("PUSH_I32", 0x17, 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 INTERNAL_EXT = new IRVMOp("IRVM_EXT_NOP", 0xFFFF, 0, 0, 0, false, false, true);
}

View File

@ -11,6 +11,8 @@ public final class IRVMProfileFeatureGate {
"core-v1", Set.of(
IRVMOp.HALT,
IRVMOp.RET,
IRVMOp.PUSH_CONST,
IRVMOp.GET_LOCAL,
IRVMOp.CALL,
IRVMOp.JMP,
IRVMOp.JMP_IF_TRUE,
@ -21,6 +23,8 @@ public final class IRVMProfileFeatureGate {
"experimental-v1", Set.of(
IRVMOp.HALT,
IRVMOp.RET,
IRVMOp.PUSH_CONST,
IRVMOp.GET_LOCAL,
IRVMOp.CALL,
IRVMOp.JMP,
IRVMOp.JMP_IF_TRUE,

View File

@ -92,6 +92,9 @@ public record IRVMProgram(
return switch (instruction.op().opcode()) {
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 0x42 -> operation.kind() == BytecodeEmitter.OperationKind.GET_LOCAL && operation.immediate() == immediate;
case 0x17 -> operation.kind() == BytecodeEmitter.OperationKind.PUSH_I32 && operation.immediate() == immediate;
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;
@ -153,6 +156,9 @@ public record IRVMProgram(
return switch (instruction.op().opcode()) {
case 0x01 -> BytecodeEmitter.Operation.halt();
case 0x51 -> BytecodeEmitter.Operation.ret();
case 0x10 -> BytecodeEmitter.Operation.pushConst(immediate, null);
case 0x42 -> BytecodeEmitter.Operation.getLocal(immediate, null);
case 0x17 -> BytecodeEmitter.Operation.pushI32(immediate, null);
case 0x50 -> BytecodeEmitter.Operation.callFunc(immediate);
case 0x02 -> BytecodeEmitter.Operation.jmp(immediate, null);
case 0x04 -> BytecodeEmitter.Operation.jmpIfTrue(immediate, null);
@ -204,6 +210,9 @@ public record IRVMProgram(
return switch (operation.kind()) {
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 GET_LOCAL -> new IRVMInstruction(IRVMOp.GET_LOCAL, operation.immediate());
case PUSH_I32 -> new IRVMInstruction(IRVMOp.PUSH_I32, operation.immediate());
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

@ -56,6 +56,8 @@ public class LowerToIRVMService {
final var loweredFunctions = new ArrayList<IRVMFunction>(ordered.size());
final var emissionFunctions = new ArrayList<BytecodeEmitter.FunctionPlan>(ordered.size());
final var constPool = new ArrayList<BytecodeModule.ConstantPoolEntry>();
final var constPoolIndexByString = new HashMap<String, Integer>();
for (var i = 0; i < ordered.size(); i++) {
final var fn = ordered.get(i);
@ -67,6 +69,44 @@ public class LowerToIRVMService {
for (final var instr : fn.instructions()) {
final var sourceSpan = toBytecodeSpan(fn.fileId().getId(), instr.span());
switch (instr.kind()) {
case PUSH_I32 -> {
final int value;
try {
value = Integer.parseInt(instr.label());
} catch (NumberFormatException e) {
throw loweringError(
fn,
instr,
IRVMLoweringErrorCode.LOWER_IRVM_INVALID_INTRINSIC_ID,
"invalid PUSH_I32 literal payload: " + instr.label());
}
instructions.add(new IRVMInstruction(IRVMOp.PUSH_I32, value));
operations.add(BytecodeEmitter.Operation.pushI32(value, sourceSpan));
functionPc += IRVMOp.PUSH_I32.immediateSize() + 2;
}
case PUSH_CONST -> {
final var value = instr.label() == null ? "" : instr.label();
final var constPoolIndex = constPoolIndexByString.computeIfAbsent(value, ignored -> {
constPool.add(new BytecodeModule.StringConstant(value));
return constPool.size() - 1;
});
instructions.add(new IRVMInstruction(IRVMOp.PUSH_CONST, constPoolIndex));
operations.add(BytecodeEmitter.Operation.pushConst(constPoolIndex, sourceSpan));
functionPc += IRVMOp.PUSH_CONST.immediateSize() + 2;
}
case GET_LOCAL -> {
final var slot = instr.expectedArgSlots();
if (slot == null) {
throw loweringError(
fn,
instr,
IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE,
"missing local slot for GET_LOCAL");
}
instructions.add(new IRVMInstruction(IRVMOp.GET_LOCAL, slot));
operations.add(BytecodeEmitter.Operation.getLocal(slot, sourceSpan));
functionPc += IRVMOp.GET_LOCAL.immediateSize() + 2;
}
case HALT -> {
instructions.add(new IRVMInstruction(IRVMOp.HALT, null));
operations.add(BytecodeEmitter.Operation.halt(sourceSpan));
@ -246,7 +286,7 @@ public class LowerToIRVMService {
new IRVMModule(vmProfile, ReadOnlyList.wrap(loweredFunctions)),
new BytecodeEmitter.EmissionPlan(
0,
ReadOnlyList.empty(),
ReadOnlyList.wrap(constPool),
ReadOnlyList.empty(),
ReadOnlyList.wrap(emissionFunctions)));
validator.validate(program, false);

View File

@ -0,0 +1,53 @@
package p.studio.compiler.integration;
import org.junit.jupiter.api.Test;
import p.studio.compiler.messages.BuilderPipelineConfig;
import p.studio.compiler.workspaces.BuilderPipelineService;
import p.studio.utilities.logs.LogAggregator;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
class MainProjectPipelineIntegrationTest {
@Test
void shouldCompileMainProjectAndWriteProgramBytecode() throws IOException {
final var repoRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize());
final var projectRoot = repoRoot.resolve("test-projects").resolve("main");
final var outputPath = projectRoot.resolve("build").resolve("program.pbx");
Files.createDirectories(outputPath.getParent());
Files.deleteIfExists(outputPath);
final var logsOut = new StringBuilder();
final var logs = LogAggregator.with(line -> {
logsOut.append(line);
if (!line.endsWith("\n")) {
logsOut.append('\n');
}
});
assertDoesNotThrow(
() -> BuilderPipelineService.INSTANCE.run(new BuilderPipelineConfig(false, projectRoot.toString()), logs),
logsOut::toString);
assertTrue(Files.exists(outputPath), "pipeline did not write output: " + outputPath + "\n" + logsOut);
assertTrue(Files.size(outputPath) > 0, "pipeline wrote empty bytecode file: " + outputPath + "\n" + logsOut);
}
private Path findRepoRoot(final Path start) {
var current = start;
while (current != null) {
if (Files.exists(current.resolve("settings.gradle.kts"))
&& Files.exists(current.resolve("test-projects").resolve("main").resolve("prometeu.json"))) {
return current;
}
current = current.getParent();
}
fail("unable to locate repository root from " + start);
return start;
}
}

View File

@ -128,6 +128,45 @@ public record IRBackendExecutableFunction(
throw new IllegalArgumentException("expectedRetSlots must be non-negative");
}
switch (kind) {
case PUSH_I32 -> {
if (label.isBlank()) {
throw new IllegalArgumentException("PUSH_I32 requires immediate label payload");
}
if (!targetLabel.isBlank()
|| !calleeCallableName.isBlank()
|| calleeCallableId != null
|| hostCall != null
|| intrinsicCall != null
|| expectedArgSlots != null
|| expectedRetSlots != null) {
throw new IllegalArgumentException("PUSH_I32 must not carry call metadata");
}
}
case PUSH_CONST -> {
if (!targetLabel.isBlank()
|| !calleeCallableName.isBlank()
|| calleeCallableId != null
|| hostCall != null
|| intrinsicCall != null
|| expectedArgSlots != null
|| expectedRetSlots != null) {
throw new IllegalArgumentException("PUSH_CONST must not carry call metadata");
}
}
case GET_LOCAL -> {
if (expectedArgSlots == null) {
throw new IllegalArgumentException("GET_LOCAL requires local slot in expectedArgSlots");
}
if (!label.isBlank()
|| !targetLabel.isBlank()
|| !calleeCallableName.isBlank()
|| calleeCallableId != null
|| hostCall != null
|| intrinsicCall != null
|| expectedRetSlots != null) {
throw new IllegalArgumentException("GET_LOCAL must not carry call metadata");
}
}
case CALL_FUNC -> {
if (calleeCallableId == null) {
throw new IllegalArgumentException("CALL_FUNC requires calleeCallableId");
@ -199,6 +238,9 @@ public record IRBackendExecutableFunction(
}
public enum InstructionKind {
PUSH_I32,
PUSH_CONST,
GET_LOCAL,
HALT,
RET,
CALL_FUNC,