implements PR-O4.4
This commit is contained in:
parent
9e9ce76c6c
commit
0073469555
@ -1,8 +1,11 @@
|
|||||||
package p.studio.compiler.backend.irvm;
|
package p.studio.compiler.backend.irvm;
|
||||||
|
|
||||||
import p.studio.compiler.backend.bytecode.BytecodeEmitter;
|
import p.studio.compiler.backend.bytecode.BytecodeEmitter;
|
||||||
|
import p.studio.compiler.backend.bytecode.BytecodeModule;
|
||||||
import p.studio.utilities.structures.ReadOnlyList;
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public record IRVMProgram(
|
public record IRVMProgram(
|
||||||
IRVMModule module,
|
IRVMModule module,
|
||||||
BytecodeEmitter.EmissionPlan emissionPlan) {
|
BytecodeEmitter.EmissionPlan emissionPlan) {
|
||||||
@ -10,6 +13,9 @@ public record IRVMProgram(
|
|||||||
public IRVMProgram {
|
public IRVMProgram {
|
||||||
module = module == null ? IRVMModule.empty() : module;
|
module = module == null ? IRVMModule.empty() : module;
|
||||||
emissionPlan = emissionPlan == null ? BytecodeEmitter.EmissionPlan.empty() : emissionPlan;
|
emissionPlan = emissionPlan == null ? BytecodeEmitter.EmissionPlan.empty() : emissionPlan;
|
||||||
|
if (!emissionPlan.functions().isEmpty()) {
|
||||||
|
validateCoherence(module, emissionPlan);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IRVMProgram(final IRVMModule module) {
|
public IRVMProgram(final IRVMModule module) {
|
||||||
@ -19,23 +25,21 @@ public record IRVMProgram(
|
|||||||
public IRVMProgram(
|
public IRVMProgram(
|
||||||
final boolean hasInternalOpcodes,
|
final boolean hasInternalOpcodes,
|
||||||
final BytecodeEmitter.EmissionPlan emissionPlan) {
|
final BytecodeEmitter.EmissionPlan emissionPlan) {
|
||||||
this(new IRVMModule(
|
this(moduleFromPlan(hasInternalOpcodes, emissionPlan), emissionPlan);
|
||||||
"core-v1",
|
|
||||||
ReadOnlyList.from(new IRVMFunction(
|
|
||||||
"__synthetic__",
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
ReadOnlyList.from(
|
|
||||||
new IRVMInstruction(hasInternalOpcodes ? IRVMOp.INTERNAL_EXT : IRVMOp.HALT, null))))),
|
|
||||||
emissionPlan);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IRVMProgram empty() {
|
public static IRVMProgram empty() {
|
||||||
return new IRVMProgram(IRVMModule.empty(), BytecodeEmitter.EmissionPlan.empty());
|
return new IRVMProgram(IRVMModule.empty(), BytecodeEmitter.EmissionPlan.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BytecodeEmitter.EmissionPlan coherentEmissionPlan() {
|
||||||
|
if (emissionPlan.functions().isEmpty()) {
|
||||||
|
return deriveEmissionPlan(module);
|
||||||
|
}
|
||||||
|
validateCoherence(module, emissionPlan);
|
||||||
|
return emissionPlan;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasInternalOpcodes() {
|
public boolean hasInternalOpcodes() {
|
||||||
for (final var function : module.functions()) {
|
for (final var function : module.functions()) {
|
||||||
for (final var instruction : function.instructions()) {
|
for (final var instruction : function.instructions()) {
|
||||||
@ -46,4 +50,141 @@ public record IRVMProgram(
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateCoherence(
|
||||||
|
final IRVMModule module,
|
||||||
|
final BytecodeEmitter.EmissionPlan emissionPlan) {
|
||||||
|
if (module.functions().size() != emissionPlan.functions().size()) {
|
||||||
|
throw new IllegalArgumentException("irvm module/function-plan size mismatch");
|
||||||
|
}
|
||||||
|
for (var fnIndex = 0; fnIndex < module.functions().size(); fnIndex++) {
|
||||||
|
final var irvmFunction = module.functions().get(fnIndex);
|
||||||
|
final var planFunction = emissionPlan.functions().get(fnIndex);
|
||||||
|
if (!irvmFunction.name().equals(planFunction.name())
|
||||||
|
|| irvmFunction.paramSlots() != planFunction.paramSlots()
|
||||||
|
|| irvmFunction.localSlots() != planFunction.localSlots()
|
||||||
|
|| irvmFunction.returnSlots() != planFunction.returnSlots()
|
||||||
|
|| irvmFunction.maxStackSlots() != planFunction.maxStackSlots()) {
|
||||||
|
throw new IllegalArgumentException("irvm/emission plan function header mismatch at index " + fnIndex);
|
||||||
|
}
|
||||||
|
if (irvmFunction.instructions().size() != planFunction.operations().size()) {
|
||||||
|
throw new IllegalArgumentException("irvm/emission plan operation count mismatch at index " + fnIndex);
|
||||||
|
}
|
||||||
|
for (var opIndex = 0; opIndex < irvmFunction.instructions().size(); opIndex++) {
|
||||||
|
final var instruction = irvmFunction.instructions().get(opIndex);
|
||||||
|
final var operation = planFunction.operations().get(opIndex);
|
||||||
|
if (!coherentPair(instruction, operation)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"irvm/emission plan opcode mismatch at function=%d op=%d".formatted(fnIndex, opIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean coherentPair(
|
||||||
|
final IRVMInstruction instruction,
|
||||||
|
final BytecodeEmitter.Operation operation) {
|
||||||
|
final var immediate = instruction.immediate() == null ? 0 : instruction.immediate();
|
||||||
|
return switch (instruction.op().opcode()) {
|
||||||
|
case 0x01 -> operation.kind() == BytecodeEmitter.OperationKind.HALT;
|
||||||
|
case 0x51 -> operation.kind() == BytecodeEmitter.OperationKind.RET;
|
||||||
|
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;
|
||||||
|
case 0x03 -> operation.kind() == BytecodeEmitter.OperationKind.JMP_IF_FALSE && operation.immediate() == immediate;
|
||||||
|
case 0x71 -> operation.kind() == BytecodeEmitter.OperationKind.HOSTCALL;
|
||||||
|
case 0x72 -> operation.kind() == BytecodeEmitter.OperationKind.INTRINSIC && operation.immediate() == immediate;
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private BytecodeEmitter.EmissionPlan deriveEmissionPlan(final IRVMModule module) {
|
||||||
|
final var functions = new ArrayList<BytecodeEmitter.FunctionPlan>(module.functions().size());
|
||||||
|
for (final var function : module.functions()) {
|
||||||
|
final var operations = new ArrayList<BytecodeEmitter.Operation>(function.instructions().size());
|
||||||
|
for (final var instruction : function.instructions()) {
|
||||||
|
operations.add(deriveOperation(instruction));
|
||||||
|
}
|
||||||
|
functions.add(new BytecodeEmitter.FunctionPlan(
|
||||||
|
function.name(),
|
||||||
|
function.paramSlots(),
|
||||||
|
function.localSlots(),
|
||||||
|
function.returnSlots(),
|
||||||
|
function.maxStackSlots(),
|
||||||
|
ReadOnlyList.wrap(operations)));
|
||||||
|
}
|
||||||
|
return new BytecodeEmitter.EmissionPlan(
|
||||||
|
0,
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.wrap(functions));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BytecodeEmitter.Operation deriveOperation(final IRVMInstruction instruction) {
|
||||||
|
final var immediate = instruction.immediate() == null ? 0 : instruction.immediate();
|
||||||
|
return switch (instruction.op().opcode()) {
|
||||||
|
case 0x01 -> BytecodeEmitter.Operation.halt();
|
||||||
|
case 0x51 -> BytecodeEmitter.Operation.ret();
|
||||||
|
case 0x50 -> BytecodeEmitter.Operation.callFunc(immediate);
|
||||||
|
case 0x02 -> BytecodeEmitter.Operation.jmp(immediate, null);
|
||||||
|
case 0x04 -> BytecodeEmitter.Operation.jmpIfTrue(immediate, null);
|
||||||
|
case 0x03 -> BytecodeEmitter.Operation.jmpIfFalse(immediate, null);
|
||||||
|
case 0x71 -> BytecodeEmitter.Operation.hostcall(
|
||||||
|
new BytecodeModule.SyscallDecl("__unknown__", "__unknown__", 0, 0, 0),
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
case 0x72 -> BytecodeEmitter.Operation.intrinsic(immediate);
|
||||||
|
default -> throw new IllegalArgumentException("cannot derive emission op for irvm opcode: " + instruction.op().opcode());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRVMModule moduleFromPlan(
|
||||||
|
final boolean hasInternalOpcodes,
|
||||||
|
final BytecodeEmitter.EmissionPlan emissionPlan) {
|
||||||
|
final var inputPlan = emissionPlan == null ? BytecodeEmitter.EmissionPlan.empty() : emissionPlan;
|
||||||
|
if (inputPlan.functions().isEmpty()) {
|
||||||
|
return new IRVMModule(
|
||||||
|
"core-v1",
|
||||||
|
ReadOnlyList.from(new IRVMFunction(
|
||||||
|
"__synthetic__",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(
|
||||||
|
new IRVMInstruction(hasInternalOpcodes ? IRVMOp.INTERNAL_EXT : IRVMOp.HALT, null)))));
|
||||||
|
}
|
||||||
|
final var functions = new ArrayList<IRVMFunction>(inputPlan.functions().size());
|
||||||
|
for (final var functionPlan : inputPlan.functions()) {
|
||||||
|
final var instructions = new ArrayList<IRVMInstruction>(functionPlan.operations().size());
|
||||||
|
for (final var operation : functionPlan.operations()) {
|
||||||
|
instructions.add(operationToInstruction(operation));
|
||||||
|
}
|
||||||
|
if (hasInternalOpcodes) {
|
||||||
|
instructions.add(0, new IRVMInstruction(IRVMOp.INTERNAL_EXT, null));
|
||||||
|
}
|
||||||
|
functions.add(new IRVMFunction(
|
||||||
|
functionPlan.name(),
|
||||||
|
functionPlan.paramSlots(),
|
||||||
|
functionPlan.localSlots(),
|
||||||
|
functionPlan.returnSlots(),
|
||||||
|
functionPlan.maxStackSlots(),
|
||||||
|
ReadOnlyList.wrap(instructions)));
|
||||||
|
}
|
||||||
|
return new IRVMModule("core-v1", ReadOnlyList.wrap(functions));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IRVMInstruction operationToInstruction(final BytecodeEmitter.Operation operation) {
|
||||||
|
return switch (operation.kind()) {
|
||||||
|
case HALT -> new IRVMInstruction(IRVMOp.HALT, null);
|
||||||
|
case RET -> new IRVMInstruction(IRVMOp.RET, null);
|
||||||
|
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());
|
||||||
|
case JMP_IF_FALSE -> new IRVMInstruction(IRVMOp.JMP_IF_FALSE, operation.immediate());
|
||||||
|
case HOSTCALL -> new IRVMInstruction(IRVMOp.HOSTCALL, 0);
|
||||||
|
case INTRINSIC -> new IRVMInstruction(IRVMOp.INTRINSIC, operation.immediate());
|
||||||
|
case RAW_SYSCALL -> throw new IllegalArgumentException("raw syscall is not representable in IRVM preload");
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,8 +39,16 @@ public class EmitBytecodePipelineStage implements PipelineStage {
|
|||||||
.message("[BUILD]: optimized IRVM still contains internal opcodes"));
|
.message("[BUILD]: optimized IRVM still contains internal opcodes"));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ctx.bytecodeModule = emitter.emit(ctx.optimizedIrvm.emissionPlan());
|
ctx.bytecodeModule = emitter.emit(ctx.optimizedIrvm.coherentEmissionPlan());
|
||||||
ctx.bytecodeBytes = ctx.bytecodeModule.serialize();
|
ctx.bytecodeBytes = ctx.bytecodeModule.serialize();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return BuildingIssueSink.empty()
|
||||||
|
.report(builder -> builder
|
||||||
|
.error(true)
|
||||||
|
.phase("BACKEND_EMIT_BYTECODE")
|
||||||
|
.code("MARSHAL_VERIFY_PRECHECK_IRVM_PROGRAM_COHERENCE")
|
||||||
|
.message("[BUILD]: irvm program coherence failed: " + e.getMessage())
|
||||||
|
.exception(e));
|
||||||
} catch (BytecodeMarshalingException e) {
|
} catch (BytecodeMarshalingException e) {
|
||||||
return BuildingIssueSink.empty()
|
return BuildingIssueSink.empty()
|
||||||
.report(builder -> builder
|
.report(builder -> builder
|
||||||
|
|||||||
@ -0,0 +1,55 @@
|
|||||||
|
package p.studio.compiler.backend.irvm;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.compiler.backend.bytecode.BytecodeEmitter;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
class IRVMProgramTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void constructorMustRejectModuleAndEmissionPlanMismatch() {
|
||||||
|
final var module = new IRVMModule(
|
||||||
|
"core-v1",
|
||||||
|
ReadOnlyList.from(new IRVMFunction(
|
||||||
|
"main",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(new IRVMInstruction(IRVMOp.HALT, null)))));
|
||||||
|
final var mismatchedPlan = new BytecodeEmitter.EmissionPlan(
|
||||||
|
0,
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.from(new BytecodeEmitter.FunctionPlan(
|
||||||
|
"main",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(BytecodeEmitter.Operation.ret()))));
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new IRVMProgram(module, mismatchedPlan));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void coherentEmissionPlanMayBeDerivedFromModuleWhenPlanIsEmpty() {
|
||||||
|
final var module = new IRVMModule(
|
||||||
|
"core-v1",
|
||||||
|
ReadOnlyList.from(new IRVMFunction(
|
||||||
|
"main",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(new IRVMInstruction(IRVMOp.HALT, null)))));
|
||||||
|
final var program = new IRVMProgram(module, BytecodeEmitter.EmissionPlan.empty());
|
||||||
|
|
||||||
|
final var plan = program.coherentEmissionPlan();
|
||||||
|
assertEquals(1, plan.functions().size());
|
||||||
|
assertEquals(BytecodeEmitter.OperationKind.HALT, plan.functions().getFirst().operations().getFirst().kind());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user