diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeEmitter.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeEmitter.java index 4bf60375..7a7e415f 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeEmitter.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeEmitter.java @@ -30,14 +30,21 @@ public class BytecodeEmitter { for (final var op : function.operations()) { final var pc = code.size(); switch (op.kind()) { - case HALT -> writeOpNoImm(code, OP_HALT); - case RET -> writeOpNoImm(code, OP_RET); - case CALL_FUNC -> writeOpU32(code, OP_CALL, op.immediate()); + case HALT -> { + writeOpNoImm(code, OP_HALT); + spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span()))); + } + case RET -> { + writeOpNoImm(code, OP_RET); + spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span()))); + } + case CALL_FUNC -> { + writeOpU32(code, OP_CALL, op.immediate()); + spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span()))); + } case INTRINSIC -> { writeOpU32(code, OP_INTRINSIC, op.immediate()); - if (op.span() != null) { - spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, op.span())); - } + spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span()))); } case HOSTCALL -> { final var decl = Objects.requireNonNull(op.syscallDecl(), "syscallDecl"); @@ -59,9 +66,7 @@ public class BytecodeEmitter { } final var index = syscallIndexByIdentity.get(identity); writeOpU32(code, OP_HOSTCALL, index); - if (op.span() != null) { - spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, op.span())); - } + spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span()))); } case RAW_SYSCALL -> throw new BytecodeMarshalingException( BytecodeMarshalingErrorCode.MARSHAL_LINKAGE_RAW_SYSCALL_IN_PRELOAD, @@ -98,6 +103,13 @@ public class BytecodeEmitter { out.writeBytes(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(immediate).array()); } + private static BytecodeModule.SourceSpan spanOrUnknown(final BytecodeModule.SourceSpan span) { + if (span == null) { + return new BytecodeModule.SourceSpan(-1, 0, 0); + } + return span; + } + private record SyscallIdentity( String module, String name, @@ -154,26 +166,50 @@ public class BytecodeEmitter { } public static Operation halt() { - return new Operation(OperationKind.HALT, 0, null, null, null, null); + return halt(null); + } + + public static Operation halt(final BytecodeModule.SourceSpan span) { + return new Operation(OperationKind.HALT, 0, null, null, null, span); } public static Operation ret() { - return new Operation(OperationKind.RET, 0, null, null, null, null); + return ret(null); + } + + public static Operation ret(final BytecodeModule.SourceSpan span) { + return new Operation(OperationKind.RET, 0, null, null, null, span); } public static Operation intrinsic(final int intrinsicId) { - return new Operation(OperationKind.INTRINSIC, intrinsicId, null, null, null, null); + return intrinsic(intrinsicId, null); + } + + public static Operation intrinsic(final int intrinsicId, final BytecodeModule.SourceSpan span) { + return new Operation(OperationKind.INTRINSIC, intrinsicId, null, null, null, span); } public static Operation callFunc(final int funcId) { - return new Operation(OperationKind.CALL_FUNC, funcId, null, null, null, null); + return callFunc(funcId, null); + } + + public static Operation callFunc(final int funcId, final BytecodeModule.SourceSpan span) { + return new Operation(OperationKind.CALL_FUNC, funcId, null, null, null, span); } public static Operation hostcall( final BytecodeModule.SyscallDecl decl, final Integer expectedArgSlots, final Integer expectedRetSlots) { - return new Operation(OperationKind.HOSTCALL, 0, decl, expectedArgSlots, expectedRetSlots, null); + return hostcall(decl, expectedArgSlots, expectedRetSlots, null); + } + + public static Operation hostcall( + final BytecodeModule.SyscallDecl decl, + final Integer expectedArgSlots, + final Integer expectedRetSlots, + final BytecodeModule.SourceSpan span) { + return new Operation(OperationKind.HOSTCALL, 0, decl, expectedArgSlots, expectedRetSlots, span); } public static Operation rawSyscall(final int syscallId) { diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java index 327ed287..5a8d63da 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/LowerToIRVMService.java @@ -1,8 +1,10 @@ package p.studio.compiler.backend.irvm; import p.studio.compiler.backend.bytecode.BytecodeEmitter; +import p.studio.compiler.backend.bytecode.BytecodeModule; import p.studio.compiler.models.IRBackend; import p.studio.compiler.models.IRBackendExecutableFunction; +import p.studio.compiler.source.Span; import p.studio.utilities.structures.ReadOnlyList; import java.util.ArrayList; @@ -48,14 +50,15 @@ public class LowerToIRVMService { final var instructions = new ArrayList(fn.instructions().size()); final var operations = new ArrayList(fn.instructions().size()); for (final var instr : fn.instructions()) { + final var sourceSpan = toBytecodeSpan(fn.fileId().getId(), instr.span()); switch (instr.kind()) { case HALT -> { instructions.add(new IRVMInstruction(IRVMOp.HALT, null)); - operations.add(BytecodeEmitter.Operation.halt()); + operations.add(BytecodeEmitter.Operation.halt(sourceSpan)); } case RET -> { instructions.add(new IRVMInstruction(IRVMOp.RET, null)); - operations.add(BytecodeEmitter.Operation.ret()); + operations.add(BytecodeEmitter.Operation.ret(sourceSpan)); } case CALL_FUNC -> { final var key = callableKey(instr.calleeModuleKey(), instr.calleeCallableName()); @@ -85,7 +88,7 @@ public class LowerToIRVMService { calleeFunction.returnSlots())); } instructions.add(new IRVMInstruction(IRVMOp.CALL, calleeId)); - operations.add(BytecodeEmitter.Operation.callFunc(calleeId)); + operations.add(BytecodeEmitter.Operation.callFunc(calleeId, sourceSpan)); } case CALL_HOST -> { final var host = instr.hostCall(); @@ -98,12 +101,13 @@ public class LowerToIRVMService { host.argSlots(), host.retSlots()), host.argSlots(), - host.retSlots())); + host.retSlots(), + sourceSpan)); } case CALL_INTRINSIC -> { final var intrinsic = instr.intrinsicCall(); instructions.add(new IRVMInstruction(IRVMOp.INTRINSIC, intrinsic.intrinsicId())); - operations.add(BytecodeEmitter.Operation.intrinsic(intrinsic.intrinsicId())); + operations.add(BytecodeEmitter.Operation.intrinsic(intrinsic.intrinsicId(), sourceSpan)); } } } @@ -160,4 +164,27 @@ public class LowerToIRVMService { final String callableName) { return (moduleKey == null ? "" : moduleKey) + "::" + callableName; } + + private BytecodeModule.SourceSpan toBytecodeSpan( + final int fallbackFileId, + final Span span) { + final var sourceSpan = span == null ? Span.none() : span; + final var fileId = sourceSpan.getFileId() == null || sourceSpan.getFileId().isNone() + ? fallbackFileId + : sourceSpan.getFileId().getId(); + return new BytecodeModule.SourceSpan( + fileId, + safeToInt(sourceSpan.getStart()), + safeToInt(sourceSpan.getEnd())); + } + + private int safeToInt(final long value) { + if (value > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + if (value < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + return (int) value; + } } diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodeEmitterTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodeEmitterTest.java index 99502327..c69e7555 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodeEmitterTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodeEmitterTest.java @@ -116,6 +116,33 @@ class BytecodeEmitterTest { assertEquals(0x2000, readU32(module.code(), 2)); } + @Test + void emitMustProducePcToSpanForEveryInstructionStartWhenSpansAreProvided() { + final var emitter = new BytecodeEmitter(); + final var module = emitter.emit(new BytecodeEmitter.EmissionPlan( + 0, + ReadOnlyList.empty(), + ReadOnlyList.empty(), + ReadOnlyList.from( + new BytecodeEmitter.FunctionPlan( + "main", + 0, + 0, + 0, + 4, + ReadOnlyList.from( + BytecodeEmitter.Operation.halt(new BytecodeModule.SourceSpan(1, 0, 1)), + BytecodeEmitter.Operation.callFunc(0, new BytecodeModule.SourceSpan(1, 2, 3)), + BytecodeEmitter.Operation.intrinsic(0x2000, new BytecodeModule.SourceSpan(1, 4, 5)), + BytecodeEmitter.Operation.ret(new BytecodeModule.SourceSpan(1, 6, 7))))))); + + assertEquals(4, module.debugInfo().pcToSpan().size()); + assertEquals(0, module.debugInfo().pcToSpan().get(0).pc()); + assertEquals(2, module.debugInfo().pcToSpan().get(1).pc()); + assertEquals(8, module.debugInfo().pcToSpan().get(2).pc()); + assertEquals(14, module.debugInfo().pcToSpan().get(3).pc()); + } + private static int readU16(final byte[] bytes, final int offset) { return ByteBuffer.wrap(bytes, offset, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF; } @@ -124,4 +151,3 @@ class BytecodeEmitterTest { return ByteBuffer.wrap(bytes, offset, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); } } - diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java index dae3f3f3..4c03fbc3 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/irvm/LowerToIRVMServiceTest.java @@ -52,6 +52,7 @@ class LowerToIRVMServiceTest { final var emissionOps = lowered.emissionPlan().functions().getFirst().operations(); assertEquals(p.studio.compiler.backend.bytecode.BytecodeEmitter.OperationKind.HOSTCALL, emissionOps.get(0).kind()); assertEquals(p.studio.compiler.backend.bytecode.BytecodeEmitter.OperationKind.INTRINSIC, emissionOps.get(1).kind()); + assertTrue(emissionOps.stream().allMatch(op -> op.span() != null)); } @Test