new test main with code directly (fixes) runtime runing
This commit is contained in:
parent
5630f8f119
commit
54f0b4b6ba
@ -287,6 +287,10 @@ public final class PbsFrontendCompiler {
|
|||||||
if (functionCallableId == null) {
|
if (functionCallableId == null) {
|
||||||
continue;
|
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(
|
final var loweringContext = new ExecutableLoweringContext(
|
||||||
normalizedModuleKey,
|
normalizedModuleKey,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
@ -295,7 +299,8 @@ public final class PbsFrontendCompiler {
|
|||||||
intrinsicByMethodName,
|
intrinsicByMethodName,
|
||||||
callableIdsByNameAndArity,
|
callableIdsByNameAndArity,
|
||||||
returnSlotsByCallableId,
|
returnSlotsByCallableId,
|
||||||
intrinsicIdTable);
|
intrinsicIdTable,
|
||||||
|
localSlotByNameId);
|
||||||
final var terminated = lowerBlock(fn.body(), loweringContext);
|
final var terminated = lowerBlock(fn.body(), loweringContext);
|
||||||
final var instructions = loweringContext.instructions();
|
final var instructions = loweringContext.instructions();
|
||||||
if (!terminated) {
|
if (!terminated) {
|
||||||
@ -618,7 +623,7 @@ public final class PbsFrontendCompiler {
|
|||||||
}
|
}
|
||||||
switch (expression) {
|
switch (expression) {
|
||||||
case PbsAst.CallExpr callExpr -> {
|
case PbsAst.CallExpr callExpr -> {
|
||||||
lowerExpression(callExpr.callee(), context);
|
lowerCallsiteReceiver(callExpr.callee(), context);
|
||||||
for (final var arg : callExpr.arguments()) {
|
for (final var arg : callExpr.arguments()) {
|
||||||
lowerExpression(arg, context);
|
lowerExpression(arg, context);
|
||||||
}
|
}
|
||||||
@ -678,16 +683,27 @@ public final class PbsFrontendCompiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case PbsAst.BlockExpr blockExpr -> lowerBlock(blockExpr.block(), context);
|
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.FloatLiteralExpr ignored -> {
|
||||||
}
|
}
|
||||||
case PbsAst.BoundedLiteralExpr 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.BoolLiteralExpr ignored -> {
|
||||||
}
|
}
|
||||||
case PbsAst.ThisExpr 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(
|
private void lowerCallsite(
|
||||||
final PbsAst.CallExpr callExpr,
|
final PbsAst.CallExpr callExpr,
|
||||||
final ExecutableLoweringContext context) {
|
final ExecutableLoweringContext context) {
|
||||||
@ -920,6 +950,60 @@ public final class PbsFrontendCompiler {
|
|||||||
span));
|
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(
|
private void reportUnsupportedLowering(
|
||||||
final String message,
|
final String message,
|
||||||
final p.studio.compiler.source.Span span,
|
final p.studio.compiler.source.Span span,
|
||||||
@ -964,9 +1048,34 @@ public final class PbsFrontendCompiler {
|
|||||||
var outHeight = incomingHeightByInstruction.get(index);
|
var outHeight = incomingHeightByInstruction.get(index);
|
||||||
|
|
||||||
switch (instruction.kind()) {
|
switch (instruction.kind()) {
|
||||||
case CALL_FUNC -> outHeight += instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots();
|
case PUSH_I32, PUSH_CONST, GET_LOCAL -> outHeight += 1;
|
||||||
case CALL_HOST -> outHeight += instruction.hostCall() == null ? 0 : instruction.hostCall().retSlots();
|
case CALL_FUNC -> {
|
||||||
case CALL_INTRINSIC -> outHeight += instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots();
|
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 HALT, LABEL, JMP, RET -> {
|
||||||
}
|
}
|
||||||
case JMP_IF_TRUE, JMP_IF_FALSE -> {
|
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<CallableResolutionKey, List<CallableId>> callableIdsByNameAndArity;
|
||||||
private final Map<CallableId, Integer> returnSlotsByCallableId;
|
private final Map<CallableId, Integer> returnSlotsByCallableId;
|
||||||
private final IntrinsicTable intrinsicIdTable;
|
private final IntrinsicTable intrinsicIdTable;
|
||||||
|
private final Map<NameId, Integer> localSlotByNameId;
|
||||||
private final ArrayList<IRBackendExecutableFunction.Instruction> instructions = new ArrayList<>();
|
private final ArrayList<IRBackendExecutableFunction.Instruction> instructions = new ArrayList<>();
|
||||||
private final ArrayDeque<LoopTargets> loopTargets = new ArrayDeque<>();
|
private final ArrayDeque<LoopTargets> loopTargets = new ArrayDeque<>();
|
||||||
private int nextLabelId = 0;
|
private int nextLabelId = 0;
|
||||||
@ -1052,7 +1162,8 @@ public final class PbsFrontendCompiler {
|
|||||||
final Map<NameId, List<IRReservedMetadata.IntrinsicSurface>> intrinsicByMethodName,
|
final Map<NameId, List<IRReservedMetadata.IntrinsicSurface>> intrinsicByMethodName,
|
||||||
final Map<CallableResolutionKey, List<CallableId>> callableIdsByNameAndArity,
|
final Map<CallableResolutionKey, List<CallableId>> callableIdsByNameAndArity,
|
||||||
final Map<CallableId, Integer> returnSlotsByCallableId,
|
final Map<CallableId, Integer> returnSlotsByCallableId,
|
||||||
final IntrinsicTable intrinsicIdTable) {
|
final IntrinsicTable intrinsicIdTable,
|
||||||
|
final Map<NameId, Integer> localSlotByNameId) {
|
||||||
this.moduleKey = moduleKey;
|
this.moduleKey = moduleKey;
|
||||||
this.diagnostics = diagnostics;
|
this.diagnostics = diagnostics;
|
||||||
this.nameTable = nameTable;
|
this.nameTable = nameTable;
|
||||||
@ -1061,6 +1172,7 @@ public final class PbsFrontendCompiler {
|
|||||||
this.callableIdsByNameAndArity = callableIdsByNameAndArity;
|
this.callableIdsByNameAndArity = callableIdsByNameAndArity;
|
||||||
this.returnSlotsByCallableId = returnSlotsByCallableId;
|
this.returnSlotsByCallableId = returnSlotsByCallableId;
|
||||||
this.intrinsicIdTable = intrinsicIdTable;
|
this.intrinsicIdTable = intrinsicIdTable;
|
||||||
|
this.localSlotByNameId = localSlotByNameId == null ? Map.of() : localSlotByNameId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String moduleKey() {
|
private String moduleKey() {
|
||||||
@ -1095,6 +1207,10 @@ public final class PbsFrontendCompiler {
|
|||||||
return intrinsicIdTable;
|
return intrinsicIdTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<NameId, Integer> localSlotByNameId() {
|
||||||
|
return localSlotByNameId;
|
||||||
|
}
|
||||||
|
|
||||||
private ArrayList<IRBackendExecutableFunction.Instruction> instructions() {
|
private ArrayList<IRBackendExecutableFunction.Instruction> instructions() {
|
||||||
return instructions;
|
return instructions;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,9 @@ callables=2
|
|||||||
1:::a/0|()->simple:int
|
1:::a/0|()->simple:int
|
||||||
intrinsics=0
|
intrinsics=0
|
||||||
fn#0:::b id=0
|
fn#0:::b id=0
|
||||||
|
GET_LOCAL calleeId=- intrinsicId=-
|
||||||
RET calleeId=- intrinsicId=-
|
RET calleeId=- intrinsicId=-
|
||||||
fn#1:::a id=1
|
fn#1:::a id=1
|
||||||
|
PUSH_I32 calleeId=- intrinsicId=-
|
||||||
CALL_FUNC calleeId=0 intrinsicId=-
|
CALL_FUNC calleeId=0 intrinsicId=-
|
||||||
RET calleeId=- intrinsicId=-
|
RET calleeId=- intrinsicId=-
|
||||||
|
|||||||
@ -17,6 +17,9 @@ public class BytecodeEmitter {
|
|||||||
private static final int OP_JMP = 0x02;
|
private static final int OP_JMP = 0x02;
|
||||||
private static final int OP_JMP_IF_FALSE = 0x03;
|
private static final int OP_JMP_IF_FALSE = 0x03;
|
||||||
private static final int OP_JMP_IF_TRUE = 0x04;
|
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_HOSTCALL = 0x71;
|
||||||
private static final int OP_SYSCALL = 0x70;
|
private static final int OP_SYSCALL = 0x70;
|
||||||
private static final int OP_INTRINSIC = 0x72;
|
private static final int OP_INTRINSIC = 0x72;
|
||||||
@ -48,6 +51,18 @@ public class BytecodeEmitter {
|
|||||||
writeOpU32(code, OP_CALL, op.immediate());
|
writeOpU32(code, OP_CALL, op.immediate());
|
||||||
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
|
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 -> {
|
case JMP -> {
|
||||||
writeOpU32(code, OP_JMP, op.immediate());
|
writeOpU32(code, OP_JMP, op.immediate());
|
||||||
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
|
spans.add(new BytecodeFunctionLayoutBuilder.InstructionSpan(pc, spanOrUnknown(op.span())));
|
||||||
@ -194,6 +209,9 @@ public class BytecodeEmitter {
|
|||||||
public enum OperationKind {
|
public enum OperationKind {
|
||||||
HALT,
|
HALT,
|
||||||
RET,
|
RET,
|
||||||
|
PUSH_CONST,
|
||||||
|
GET_LOCAL,
|
||||||
|
PUSH_I32,
|
||||||
CALL_FUNC,
|
CALL_FUNC,
|
||||||
JMP,
|
JMP,
|
||||||
JMP_IF_TRUE,
|
JMP_IF_TRUE,
|
||||||
@ -254,6 +272,18 @@ public class BytecodeEmitter {
|
|||||||
return new Operation(OperationKind.CALL_FUNC, funcId, null, null, null, span);
|
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(
|
public static Operation hostcall(
|
||||||
final BytecodeModule.SyscallDecl decl,
|
final BytecodeModule.SyscallDecl decl,
|
||||||
final Integer expectedArgSlots,
|
final Integer expectedArgSlots,
|
||||||
|
|||||||
@ -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 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 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 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 = 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);
|
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 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 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 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);
|
public static final IRVMOp INTERNAL_EXT = new IRVMOp("IRVM_EXT_NOP", 0xFFFF, 0, 0, 0, false, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,8 @@ public final class IRVMProfileFeatureGate {
|
|||||||
"core-v1", Set.of(
|
"core-v1", Set.of(
|
||||||
IRVMOp.HALT,
|
IRVMOp.HALT,
|
||||||
IRVMOp.RET,
|
IRVMOp.RET,
|
||||||
|
IRVMOp.PUSH_CONST,
|
||||||
|
IRVMOp.GET_LOCAL,
|
||||||
IRVMOp.CALL,
|
IRVMOp.CALL,
|
||||||
IRVMOp.JMP,
|
IRVMOp.JMP,
|
||||||
IRVMOp.JMP_IF_TRUE,
|
IRVMOp.JMP_IF_TRUE,
|
||||||
@ -21,6 +23,8 @@ public final class IRVMProfileFeatureGate {
|
|||||||
"experimental-v1", Set.of(
|
"experimental-v1", Set.of(
|
||||||
IRVMOp.HALT,
|
IRVMOp.HALT,
|
||||||
IRVMOp.RET,
|
IRVMOp.RET,
|
||||||
|
IRVMOp.PUSH_CONST,
|
||||||
|
IRVMOp.GET_LOCAL,
|
||||||
IRVMOp.CALL,
|
IRVMOp.CALL,
|
||||||
IRVMOp.JMP,
|
IRVMOp.JMP,
|
||||||
IRVMOp.JMP_IF_TRUE,
|
IRVMOp.JMP_IF_TRUE,
|
||||||
|
|||||||
@ -92,6 +92,9 @@ public record IRVMProgram(
|
|||||||
return switch (instruction.op().opcode()) {
|
return switch (instruction.op().opcode()) {
|
||||||
case 0x01 -> operation.kind() == BytecodeEmitter.OperationKind.HALT;
|
case 0x01 -> operation.kind() == BytecodeEmitter.OperationKind.HALT;
|
||||||
case 0x51 -> operation.kind() == BytecodeEmitter.OperationKind.RET;
|
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 0x50 -> operation.kind() == BytecodeEmitter.OperationKind.CALL_FUNC && operation.immediate() == immediate;
|
||||||
case 0x02 -> operation.kind() == BytecodeEmitter.OperationKind.JMP && 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 0x04 -> operation.kind() == BytecodeEmitter.OperationKind.JMP_IF_TRUE && operation.immediate() == immediate;
|
||||||
@ -153,6 +156,9 @@ public record IRVMProgram(
|
|||||||
return switch (instruction.op().opcode()) {
|
return switch (instruction.op().opcode()) {
|
||||||
case 0x01 -> BytecodeEmitter.Operation.halt();
|
case 0x01 -> BytecodeEmitter.Operation.halt();
|
||||||
case 0x51 -> BytecodeEmitter.Operation.ret();
|
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 0x50 -> BytecodeEmitter.Operation.callFunc(immediate);
|
||||||
case 0x02 -> BytecodeEmitter.Operation.jmp(immediate, null);
|
case 0x02 -> BytecodeEmitter.Operation.jmp(immediate, null);
|
||||||
case 0x04 -> BytecodeEmitter.Operation.jmpIfTrue(immediate, null);
|
case 0x04 -> BytecodeEmitter.Operation.jmpIfTrue(immediate, null);
|
||||||
@ -204,6 +210,9 @@ public record IRVMProgram(
|
|||||||
return switch (operation.kind()) {
|
return switch (operation.kind()) {
|
||||||
case HALT -> new IRVMInstruction(IRVMOp.HALT, null);
|
case HALT -> new IRVMInstruction(IRVMOp.HALT, null);
|
||||||
case RET -> new IRVMInstruction(IRVMOp.RET, 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 CALL_FUNC -> new IRVMInstruction(IRVMOp.CALL, operation.immediate());
|
||||||
case JMP -> new IRVMInstruction(IRVMOp.JMP, 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_TRUE -> new IRVMInstruction(IRVMOp.JMP_IF_TRUE, operation.immediate());
|
||||||
|
|||||||
@ -56,6 +56,8 @@ public class LowerToIRVMService {
|
|||||||
|
|
||||||
final var loweredFunctions = new ArrayList<IRVMFunction>(ordered.size());
|
final var loweredFunctions = new ArrayList<IRVMFunction>(ordered.size());
|
||||||
final var emissionFunctions = new ArrayList<BytecodeEmitter.FunctionPlan>(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++) {
|
for (var i = 0; i < ordered.size(); i++) {
|
||||||
final var fn = ordered.get(i);
|
final var fn = ordered.get(i);
|
||||||
@ -67,6 +69,44 @@ public class LowerToIRVMService {
|
|||||||
for (final var instr : fn.instructions()) {
|
for (final var instr : fn.instructions()) {
|
||||||
final var sourceSpan = toBytecodeSpan(fn.fileId().getId(), instr.span());
|
final var sourceSpan = toBytecodeSpan(fn.fileId().getId(), instr.span());
|
||||||
switch (instr.kind()) {
|
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 -> {
|
case HALT -> {
|
||||||
instructions.add(new IRVMInstruction(IRVMOp.HALT, null));
|
instructions.add(new IRVMInstruction(IRVMOp.HALT, null));
|
||||||
operations.add(BytecodeEmitter.Operation.halt(sourceSpan));
|
operations.add(BytecodeEmitter.Operation.halt(sourceSpan));
|
||||||
@ -246,7 +286,7 @@ public class LowerToIRVMService {
|
|||||||
new IRVMModule(vmProfile, ReadOnlyList.wrap(loweredFunctions)),
|
new IRVMModule(vmProfile, ReadOnlyList.wrap(loweredFunctions)),
|
||||||
new BytecodeEmitter.EmissionPlan(
|
new BytecodeEmitter.EmissionPlan(
|
||||||
0,
|
0,
|
||||||
ReadOnlyList.empty(),
|
ReadOnlyList.wrap(constPool),
|
||||||
ReadOnlyList.empty(),
|
ReadOnlyList.empty(),
|
||||||
ReadOnlyList.wrap(emissionFunctions)));
|
ReadOnlyList.wrap(emissionFunctions)));
|
||||||
validator.validate(program, false);
|
validator.validate(program, false);
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -128,6 +128,45 @@ public record IRBackendExecutableFunction(
|
|||||||
throw new IllegalArgumentException("expectedRetSlots must be non-negative");
|
throw new IllegalArgumentException("expectedRetSlots must be non-negative");
|
||||||
}
|
}
|
||||||
switch (kind) {
|
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 -> {
|
case CALL_FUNC -> {
|
||||||
if (calleeCallableId == null) {
|
if (calleeCallableId == null) {
|
||||||
throw new IllegalArgumentException("CALL_FUNC requires calleeCallableId");
|
throw new IllegalArgumentException("CALL_FUNC requires calleeCallableId");
|
||||||
@ -199,6 +238,9 @@ public record IRBackendExecutableFunction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum InstructionKind {
|
public enum InstructionKind {
|
||||||
|
PUSH_I32,
|
||||||
|
PUSH_CONST,
|
||||||
|
GET_LOCAL,
|
||||||
HALT,
|
HALT,
|
||||||
RET,
|
RET,
|
||||||
CALL_FUNC,
|
CALL_FUNC,
|
||||||
|
|||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user