diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java new file mode 100644 index 00000000..714c978b --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/IRVMProgram.java @@ -0,0 +1,17 @@ +package p.studio.compiler.backend.irvm; + +import p.studio.compiler.backend.bytecode.BytecodeEmitter; + +public record IRVMProgram( + boolean hasInternalOpcodes, + BytecodeEmitter.EmissionPlan emissionPlan) { + + public IRVMProgram { + emissionPlan = emissionPlan == null ? BytecodeEmitter.EmissionPlan.empty() : emissionPlan; + } + + public static IRVMProgram empty() { + return new IRVMProgram(false, BytecodeEmitter.EmissionPlan.empty()); + } +} + diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/models/BuilderPipelineContext.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/models/BuilderPipelineContext.java index b9ebc302..a8dcdfb3 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/models/BuilderPipelineContext.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/models/BuilderPipelineContext.java @@ -1,5 +1,7 @@ package p.studio.compiler.models; +import p.studio.compiler.backend.bytecode.BytecodeModule; +import p.studio.compiler.backend.irvm.IRVMProgram; import p.studio.compiler.messages.BuilderPipelineConfig; import p.studio.compiler.source.tables.FileTable; import p.studio.compiler.utilities.SourceProviderFactory; @@ -15,6 +17,10 @@ public class BuilderPipelineContext { public FileTable fileTable; public IRBackend irBackend; + public IRVMProgram irvm; + public IRVMProgram optimizedIrvm; + public BytecodeModule bytecodeModule; + public byte[] bytecodeBytes; private BuilderPipelineContext( final BuilderPipelineConfig config, diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/EmitBytecodePipelineStage.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/EmitBytecodePipelineStage.java index 697a1549..cf76ed11 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/EmitBytecodePipelineStage.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/EmitBytecodePipelineStage.java @@ -1,6 +1,8 @@ package p.studio.compiler.workspaces.stages; import lombok.extern.slf4j.Slf4j; +import p.studio.compiler.backend.bytecode.BytecodeEmitter; +import p.studio.compiler.backend.bytecode.BytecodeMarshalingException; import p.studio.compiler.messages.BuildingIssueSink; import p.studio.compiler.models.BuilderPipelineContext; import p.studio.compiler.workspaces.PipelineStage; @@ -8,8 +10,41 @@ import p.studio.utilities.logs.LogAggregator; @Slf4j public class EmitBytecodePipelineStage implements PipelineStage { + private final BytecodeEmitter emitter; + + public EmitBytecodePipelineStage() { + this(new BytecodeEmitter()); + } + + EmitBytecodePipelineStage(final BytecodeEmitter emitter) { + this.emitter = emitter; + } + @Override public BuildingIssueSink run(BuilderPipelineContext ctx, LogAggregator logs) { + if (ctx.optimizedIrvm == null) { + return BuildingIssueSink.empty() + .report(builder -> builder + .error(true) + .message("[BUILD]: optimized IRVM is missing before EmitBytecode stage")); + } + if (ctx.optimizedIrvm.hasInternalOpcodes()) { + return BuildingIssueSink.empty() + .report(builder -> builder + .error(true) + .message("[BUILD]: optimized IRVM still contains internal opcodes")); + } + try { + ctx.bytecodeModule = emitter.emit(ctx.optimizedIrvm.emissionPlan()); + ctx.bytecodeBytes = ctx.bytecodeModule.serialize(); + } catch (BytecodeMarshalingException e) { + return BuildingIssueSink.empty() + .report(builder -> builder + .error(true) + .message("[BUILD]: bytecode marshaling failed (%s): %s" + .formatted(e.code().name(), e.getMessage())) + .exception(e)); + } return BuildingIssueSink.empty(); } } diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/EmitBytecodePipelineStageTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/EmitBytecodePipelineStageTest.java new file mode 100644 index 00000000..43683432 --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/workspaces/stages/EmitBytecodePipelineStageTest.java @@ -0,0 +1,69 @@ +package p.studio.compiler.workspaces.stages; + +import org.junit.jupiter.api.Test; +import p.studio.compiler.backend.bytecode.BytecodeEmitter; +import p.studio.compiler.backend.irvm.IRVMProgram; +import p.studio.compiler.messages.BuilderPipelineConfig; +import p.studio.compiler.models.BuilderPipelineContext; +import p.studio.utilities.logs.LogAggregator; +import p.studio.utilities.structures.ReadOnlyList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class EmitBytecodePipelineStageTest { + + @Test + void runMustFailWhenOptimizedIrvmIsMissing() { + final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, ".")); + final var stage = new EmitBytecodePipelineStage(); + + final var issues = stage.run(ctx, LogAggregator.empty()); + + assertTrue(issues.hasErrors()); + assertEquals(1, issues.size()); + } + + @Test + void runMustFailWhenInternalOpcodesRemain() { + final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, ".")); + ctx.optimizedIrvm = new IRVMProgram(true, BytecodeEmitter.EmissionPlan.empty()); + final var stage = new EmitBytecodePipelineStage(); + + final var issues = stage.run(ctx, LogAggregator.empty()); + + assertTrue(issues.hasErrors()); + assertEquals(1, issues.size()); + } + + @Test + void runMustEmitBytecodeWhenPreconditionsAreSatisfied() { + final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, ".")); + ctx.optimizedIrvm = new IRVMProgram( + false, + new BytecodeEmitter.EmissionPlan( + 0, + ReadOnlyList.empty(), + ReadOnlyList.empty(), + ReadOnlyList.from( + new BytecodeEmitter.FunctionPlan( + "main", + 0, + 0, + 0, + 1, + ReadOnlyList.from(BytecodeEmitter.Operation.halt()))))); + final var stage = new EmitBytecodePipelineStage(); + + final var issues = stage.run(ctx, LogAggregator.empty()); + + assertFalse(issues.hasErrors()); + assertNotNull(ctx.bytecodeModule); + assertNotNull(ctx.bytecodeBytes); + assertTrue(ctx.bytecodeBytes.length > 0); + assertEquals(0, ctx.bytecodeModule.syscalls().size()); + } +} +