implements PR-035

This commit is contained in:
bQUARKz 2026-03-07 16:29:16 +00:00
parent 69da7a5924
commit 68bd72bb21
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
4 changed files with 127 additions and 0 deletions

View File

@ -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());
}
}

View File

@ -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,

View File

@ -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();
}
}

View File

@ -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());
}
}