implements PR-O4.1

This commit is contained in:
bQUARKz 2026-03-07 19:32:45 +00:00
parent bbcb65b048
commit d4f6d96437
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
15 changed files with 404 additions and 56 deletions

View File

@ -16,6 +16,8 @@ import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.diagnostics.DiagnosticPhase;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.tables.CallableSignatureRef;
import p.studio.compiler.source.tables.CallableTable;
import p.studio.compiler.source.tables.IntrinsicTable;
import p.studio.utilities.structures.ReadOnlyList;
@ -121,13 +123,18 @@ public final class PbsFrontendCompiler {
? ReadOnlyList.empty()
: lowerFunctions(fileId, ast);
final var loweringErrorBaseline = diagnostics.errorCount();
final ReadOnlyList<IRBackendExecutableFunction> executableFunctions = sourceKind == SourceKind.SDK_INTERFACE
? ReadOnlyList.empty()
final var executableLowering = sourceKind == SourceKind.SDK_INTERFACE
? new ExecutableLoweringResult(ReadOnlyList.empty(), ReadOnlyList.empty())
: lowerExecutableFunctions(fileId, ast, moduleKey, reservedMetadata, diagnostics);
if (diagnostics.errorCount() > loweringErrorBaseline) {
return IRBackendFile.empty(fileId);
}
return new IRBackendFile(fileId, functions, executableFunctions, reservedMetadata);
return new IRBackendFile(
fileId,
functions,
executableLowering.executableFunctions(),
reservedMetadata,
executableLowering.callableSignatures());
}
private ReadOnlyList<IRFunction> lowerFunctions(final FileId fileId, final PbsAst.File ast) {
@ -143,12 +150,13 @@ public final class PbsFrontendCompiler {
return ReadOnlyList.wrap(functions);
}
private ReadOnlyList<IRBackendExecutableFunction> lowerExecutableFunctions(
private ExecutableLoweringResult lowerExecutableFunctions(
final FileId fileId,
final PbsAst.File ast,
final String moduleKey,
final IRReservedMetadata reservedMetadata,
final DiagnosticSink diagnostics) {
final var normalizedModuleKey = moduleKey == null ? "" : moduleKey;
final var hostByMethodName = new HashMap<String, IRReservedMetadata.HostMethodBinding>();
for (final var hostBinding : reservedMetadata.hostMethodBindings()) {
hostByMethodName.put(hostBinding.sourceMethodName(), hostBinding);
@ -159,21 +167,32 @@ public final class PbsFrontendCompiler {
intrinsicByMethodName.put(intrinsicSurface.sourceMethodName(), intrinsicSurface);
}
}
final var returnSlotsByCallableAndArity = new HashMap<String, Integer>();
final var callableIdTable = new CallableTable();
final var callableIdsByNameAndArity = new HashMap<String, List<Integer>>();
final var callableIdByDeclaration = new HashMap<PbsAst.FunctionDecl, Integer>();
final var returnSlotsByCallableId = new HashMap<Integer, Integer>();
final var intrinsicIdTable = new IntrinsicTable();
for (final var declaredFn : ast.functions()) {
final var key = callableArityKey(declaredFn.name(), declaredFn.parameters().size());
final var callableId = callableIdTable.register(
normalizedModuleKey,
declaredFn.name(),
declaredFn.parameters().size(),
callableShapeKey(declaredFn))
.getId();
callableIdByDeclaration.put(declaredFn, callableId);
callableIdsByNameAndArity
.computeIfAbsent(callableArityKey(declaredFn.name(), declaredFn.parameters().size()), ignored -> new ArrayList<>())
.add(callableId);
final var retSlots = returnSlotsFor(declaredFn);
if (returnSlotsByCallableAndArity.containsKey(key)
&& !returnSlotsByCallableAndArity.get(key).equals(retSlots)) {
returnSlotsByCallableAndArity.put(key, null);
} else {
returnSlotsByCallableAndArity.put(key, retSlots);
}
returnSlotsByCallableId.put(callableId, retSlots);
}
final var executableFunctions = new ArrayList<IRBackendExecutableFunction>(ast.functions().size());
for (final var fn : ast.functions()) {
final var functionCallableId = callableIdByDeclaration.get(fn);
if (functionCallableId == null) {
continue;
}
final var instructions = new ArrayList<IRBackendExecutableFunction.Instruction>();
final var callsites = new ArrayList<PbsAst.CallExpr>();
collectCallsFromBlock(fn.body(), callsites);
@ -230,14 +249,37 @@ public final class PbsFrontendCompiler {
continue;
}
final var candidateCallableIds = callableIdsByNameAndArity.get(callableArityKey(calleeName, callExpr.arguments().size()));
if (candidateCallableIds == null || candidateCallableIds.isEmpty()) {
diagnostics.error(
DiagnosticPhase.STATIC_SEMANTICS,
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
Map.of(),
"executable lowering requires resolvable callee identity",
callExpr.span());
continue;
}
if (candidateCallableIds.size() > 1) {
diagnostics.error(
DiagnosticPhase.STATIC_SEMANTICS,
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
Map.of(),
"executable lowering found ambiguous callable identity",
callExpr.span());
continue;
}
final var calleeCallableId = candidateCallableIds.getFirst();
instructions.add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
moduleKey == null ? "" : moduleKey,
normalizedModuleKey,
calleeName,
calleeCallableId,
null,
null,
callExpr.arguments().size(),
returnSlotsByCallableAndArity.get(callableArityKey(calleeName, callExpr.arguments().size())),
returnSlotsByCallableId.get(calleeCallableId),
callExpr.span()));
}
instructions.add(new IRBackendExecutableFunction.Instruction(
@ -246,6 +288,7 @@ public final class PbsFrontendCompiler {
"",
null,
null,
null,
fn.span()));
final var returnSlots = returnSlotsFor(fn);
@ -253,8 +296,9 @@ public final class PbsFrontendCompiler {
final var end = safeToInt(fn.span().getEnd());
executableFunctions.add(new IRBackendExecutableFunction(
fileId,
moduleKey == null ? "" : moduleKey,
normalizedModuleKey,
fn.name(),
functionCallableId,
start,
end,
fn.parameters().size(),
@ -264,7 +308,13 @@ public final class PbsFrontendCompiler {
ReadOnlyList.wrap(instructions),
fn.span()));
}
return ReadOnlyList.wrap(executableFunctions);
final var callableSignatures = new ArrayList<CallableSignatureRef>(callableIdTable.size());
for (final var callableId : callableIdTable.identifiers()) {
callableSignatures.add(callableIdTable.get(callableId));
}
return new ExecutableLoweringResult(
ReadOnlyList.wrap(executableFunctions),
ReadOnlyList.wrap(callableSignatures));
}
private int returnSlotsFor(final PbsAst.FunctionDecl functionDecl) {
@ -280,6 +330,60 @@ public final class PbsFrontendCompiler {
return callableName + "#" + arity;
}
private String callableShapeKey(final PbsAst.FunctionDecl functionDecl) {
final var builder = new StringBuilder();
builder.append('(');
for (var i = 0; i < functionDecl.parameters().size(); i++) {
if (i > 0) {
builder.append(',');
}
builder.append(typeKey(functionDecl.parameters().get(i).typeRef()));
}
builder.append(")->");
builder.append(outputShapeKey(functionDecl.returnKind(), functionDecl.returnType(), functionDecl.resultErrorType()));
return builder.toString();
}
private String outputShapeKey(
final PbsAst.ReturnKind returnKind,
final PbsAst.TypeRef returnType,
final PbsAst.TypeRef resultErrorType) {
return switch (returnKind) {
case INFERRED_UNIT, EXPLICIT_UNIT -> "unit";
case PLAIN -> typeKey(returnType);
case RESULT -> "result<" + typeKey(resultErrorType) + ">" + typeKey(returnType);
};
}
private String typeKey(final PbsAst.TypeRef typeRef) {
final var unwrapped = unwrapGroup(typeRef);
if (unwrapped == null) {
return "unit";
}
return switch (unwrapped.kind()) {
case SIMPLE -> "simple:" + unwrapped.name();
case SELF -> "self";
case OPTIONAL -> "optional(" + typeKey(unwrapped.inner()) + ")";
case UNIT -> "unit";
case GROUP -> typeKey(unwrapped.inner());
case NAMED_TUPLE -> "tuple(" + unwrapped.fields().stream()
.map(field -> typeKey(field.typeRef()))
.reduce((left, right) -> left + "," + right)
.orElse("") + ")";
case ERROR -> "error";
};
}
private PbsAst.TypeRef unwrapGroup(final PbsAst.TypeRef typeRef) {
if (typeRef == null) {
return null;
}
if (typeRef.kind() != PbsAst.TypeRefKind.GROUP) {
return typeRef;
}
return unwrapGroup(typeRef.inner());
}
private int safeToInt(final long value) {
if (value > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
@ -443,4 +547,9 @@ public final class PbsFrontendCompiler {
default -> null;
};
}
private record ExecutableLoweringResult(
ReadOnlyList<IRBackendExecutableFunction> executableFunctions,
ReadOnlyList<CallableSignatureRef> callableSignatures) {
}
}

View File

@ -15,6 +15,7 @@ public class LowerToIRVMService {
private static final Comparator<IRBackendExecutableFunction> FUNCTION_ORDER = Comparator
.comparing(IRBackendExecutableFunction::moduleKey)
.thenComparing(IRBackendExecutableFunction::callableName)
.thenComparingInt(IRBackendExecutableFunction::callableId)
.thenComparingInt(IRBackendExecutableFunction::sourceStart);
private final IRVMValidator validator;
@ -35,11 +36,14 @@ public class LowerToIRVMService {
}
final var ordered = orderFunctions(backend.getExecutableFunctions());
final var funcIdByModuleAndName = new HashMap<String, Integer>();
final var funcIdByCallableId = new HashMap<Integer, Integer>();
for (var i = 0; i < ordered.size(); i++) {
final var fn = ordered.get(i);
final var key = callableKey(fn.moduleKey(), fn.callableName());
funcIdByModuleAndName.putIfAbsent(key, i);
if (funcIdByCallableId.putIfAbsent(fn.callableId(), i) != null) {
throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE,
"duplicate callable id in executable set: " + fn.callableId());
}
}
final var loweredFunctions = new ArrayList<IRVMFunction>(ordered.size());
@ -66,12 +70,16 @@ public class LowerToIRVMService {
functionPc += IRVMOp.RET.immediateSize() + 2;
}
case CALL_FUNC -> {
final var key = callableKey(instr.calleeModuleKey(), instr.calleeCallableName());
final var calleeId = funcIdByModuleAndName.get(key);
if (instr.calleeCallableId() == null) {
throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE,
"missing callee callable id");
}
final var calleeId = funcIdByCallableId.get(instr.calleeCallableId());
if (calleeId == null) {
throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE,
"missing callee function: " + key);
"missing callee function for callable_id=" + instr.calleeCallableId());
}
final var calleeFunction = ordered.get(calleeId);
if (instr.expectedArgSlots() != null
@ -79,7 +87,7 @@ public class LowerToIRVMService {
throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_CALL_ARG_SLOTS_MISMATCH,
"call arg_slots mismatch for %s. expected=%d actual=%d".formatted(
key,
instr.calleeCallableName(),
instr.expectedArgSlots(),
calleeFunction.paramSlots()));
}
@ -88,7 +96,7 @@ public class LowerToIRVMService {
throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_CALL_RET_SLOTS_MISMATCH,
"call ret_slots mismatch for %s. expected=%d actual=%d".formatted(
key,
instr.calleeCallableName(),
instr.expectedRetSlots(),
calleeFunction.returnSlots()));
}
@ -213,12 +221,6 @@ public class LowerToIRVMService {
return ReadOnlyList.wrap(ordered);
}
private String callableKey(
final String moduleKey,
final String callableName) {
return (moduleKey == null ? "" : moduleKey) + "::" + callableName;
}
private BytecodeModule.SourceSpan toBytecodeSpan(
final int fallbackFileId,
final Span span) {

View File

@ -17,10 +17,10 @@ class LowerToIRVMServiceTest {
void lowerMustAssignEntrypointIdZeroAndSortRemainingDeterministically() {
final var backend = IRBackend.builder()
.executableFunctions(ReadOnlyList.from(
fn("aux", "app", ReadOnlyList.from(
callFunc("app", "main"),
fn("aux", "app", 11, ReadOnlyList.from(
callFunc("app", "main", 10),
ret())),
fn("main", "app", ReadOnlyList.from(
fn("main", "app", 10, ReadOnlyList.from(
ret()))))
.build();
@ -37,7 +37,7 @@ class LowerToIRVMServiceTest {
void lowerMustMapHostAndIntrinsicCallsites() {
final var backend = IRBackend.builder()
.executableFunctions(ReadOnlyList.from(
fn("main", "app", ReadOnlyList.from(
fn("main", "app", 10, ReadOnlyList.from(
callHost("gfx", "draw_pixel", 1, 2, 0),
callIntrinsic("core.color.pack", 1, 0x2001),
ret()))))
@ -59,8 +59,8 @@ class LowerToIRVMServiceTest {
void lowerMustRejectUnterminatedFunction() {
final var backend = IRBackend.builder()
.executableFunctions(ReadOnlyList.from(
fn("main", "app", ReadOnlyList.from(
callFunc("app", "main")))))
fn("main", "app", 10, ReadOnlyList.from(
callFunc("app", "main", 10)))))
.build();
final var thrown = assertThrows(IRVMValidationException.class, () -> new LowerToIRVMService().lower(backend));
@ -71,8 +71,8 @@ class LowerToIRVMServiceTest {
void lowerMustRejectMissingCallee() {
final var backend = IRBackend.builder()
.executableFunctions(ReadOnlyList.from(
fn("main", "app", ReadOnlyList.from(
callFunc("app", "missing"),
fn("main", "app", 10, ReadOnlyList.from(
callFunc("app", "missing", 77),
ret()))))
.build();
@ -84,9 +84,9 @@ class LowerToIRVMServiceTest {
void lowerMustRejectCallArgSlotMismatch() {
final var backend = IRBackend.builder()
.executableFunctions(ReadOnlyList.from(
fn("callee", "app", ReadOnlyList.from(ret())),
fn("main", "app", ReadOnlyList.from(
callFuncWithExpected("app", "callee", 2, 0),
fn("callee", "app", 20, ReadOnlyList.from(ret())),
fn("main", "app", 10, ReadOnlyList.from(
callFuncWithExpected("app", "callee", 20, 2, 0),
ret()))))
.build();
@ -98,7 +98,7 @@ class LowerToIRVMServiceTest {
void lowerMustResolveJumpTargetsFromLabels() {
final var backend = IRBackend.builder()
.executableFunctions(ReadOnlyList.from(
fn("main", "app", ReadOnlyList.from(
fn("main", "app", 10, ReadOnlyList.from(
label("entry"),
jmp("exit"),
label("exit"),
@ -117,7 +117,7 @@ class LowerToIRVMServiceTest {
void lowerMustRejectMissingJumpTargetLabel() {
final var backend = IRBackend.builder()
.executableFunctions(ReadOnlyList.from(
fn("main", "app", ReadOnlyList.from(
fn("main", "app", 10, ReadOnlyList.from(
jmp("missing"),
ret()))))
.build();
@ -129,11 +129,13 @@ class LowerToIRVMServiceTest {
private static IRBackendExecutableFunction fn(
final String name,
final String moduleKey,
final int callableId,
final ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions) {
return new IRBackendExecutableFunction(
new FileId(0),
moduleKey,
name,
callableId,
0,
10,
0,
@ -154,11 +156,15 @@ class LowerToIRVMServiceTest {
Span.none());
}
private static IRBackendExecutableFunction.Instruction callFunc(final String moduleKey, final String name) {
private static IRBackendExecutableFunction.Instruction callFunc(
final String moduleKey,
final String name,
final int calleeCallableId) {
return new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
moduleKey,
name,
calleeCallableId,
null,
null,
Span.none());
@ -167,12 +173,14 @@ class LowerToIRVMServiceTest {
private static IRBackendExecutableFunction.Instruction callFuncWithExpected(
final String moduleKey,
final String name,
final int calleeCallableId,
final int expectedArgSlots,
final int expectedRetSlots) {
return new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
moduleKey,
name,
calleeCallableId,
null,
null,
expectedArgSlots,
@ -215,6 +223,7 @@ class LowerToIRVMServiceTest {
"",
null,
null,
null,
label,
"",
null,
@ -229,6 +238,7 @@ class LowerToIRVMServiceTest {
"",
null,
null,
null,
"",
target,
null,

View File

@ -178,6 +178,7 @@ class BackendGateIIntegrationTest {
new FileId(1),
"app/main",
name,
1,
0,
10,
0,

View File

@ -29,6 +29,7 @@ class BackendSafetyGateSUTest {
new FileId(1),
"app",
"main",
1,
0,
10,
0,
@ -40,6 +41,7 @@ class BackendSafetyGateSUTest {
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
"app",
"missing",
99,
null,
null,
0,
@ -112,6 +114,7 @@ class BackendSafetyGateSUTest {
new FileId(1),
"app",
"main",
1,
0,
10,
0,

View File

@ -38,6 +38,7 @@ class LowerToIRVMPipelineStageTest {
new FileId(0),
"app",
"main",
1,
0,
10,
0,

View File

@ -0,0 +1,13 @@
package p.studio.compiler.source.identifiers;
public class CallableId extends SourceIdentifier {
public static final CallableId NONE = new CallableId(-1);
public CallableId(final int id) {
super(id);
}
public static CallableId none() {
return NONE;
}
}

View File

@ -0,0 +1,22 @@
package p.studio.compiler.source.tables;
import java.util.Objects;
public record CallableSignatureRef(
String moduleKey,
String callableName,
int arity,
String typeShape) {
public CallableSignatureRef {
moduleKey = moduleKey == null ? "" : moduleKey;
callableName = Objects.requireNonNull(callableName, "callableName");
typeShape = typeShape == null ? "" : typeShape;
if (callableName.isBlank()) {
throw new IllegalArgumentException("callableName must not be blank");
}
if (arity < 0) {
throw new IllegalArgumentException("arity must be non-negative");
}
}
}

View File

@ -0,0 +1,18 @@
package p.studio.compiler.source.tables;
import p.studio.compiler.source.identifiers.CallableId;
public class CallableTable extends InternTable<CallableId, CallableSignatureRef> implements CallableTableReader {
public CallableTable() {
super(CallableId::new);
}
public CallableId register(
final String moduleKey,
final String callableName,
final int arity,
final String typeShape) {
return register(new CallableSignatureRef(moduleKey, callableName, arity, typeShape));
}
}

View File

@ -0,0 +1,6 @@
package p.studio.compiler.source.tables;
import p.studio.compiler.source.identifiers.CallableId;
public interface CallableTableReader extends InternTableReader<CallableId, CallableSignatureRef> {
}

View File

@ -0,0 +1,28 @@
package p.studio.compiler.source.tables;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class CallableTableTest {
@Test
void shouldInternEqualCallableSignaturesToSameIdentifier() {
final var table = new CallableTable();
final var first = table.register("app/main", "draw", 2, "(simple:int,simple:int)->unit");
final var second = table.register("app/main", "draw", 2, "(simple:int,simple:int)->unit");
final var third = table.register("app/main", "draw", 1, "(simple:int)->unit");
assertEquals(first, second);
assertEquals(2, table.size());
assertEquals(1, table.get(third).arity());
}
@Test
void shouldRejectInvalidCallableSignatureReferenceContract() {
assertThrows(IllegalArgumentException.class, () -> new CallableSignatureRef("app/main", "", 1, "(unit)->unit"));
assertThrows(IllegalArgumentException.class, () -> new CallableSignatureRef("app/main", "draw", -1, "(unit)->unit"));
}
}

View File

@ -2,6 +2,8 @@ package p.studio.compiler.models;
import lombok.Builder;
import lombok.Getter;
import p.studio.compiler.source.tables.CallableSignatureRef;
import p.studio.compiler.source.tables.CallableTable;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
@ -15,6 +17,8 @@ public class IRBackend {
@Builder.Default
private final ReadOnlyList<IRBackendExecutableFunction> executableFunctions = ReadOnlyList.empty();
@Builder.Default
private final ReadOnlyList<CallableSignatureRef> callableSignatures = ReadOnlyList.empty();
@Builder.Default
private final IRReservedMetadata reservedMetadata = IRReservedMetadata.empty();
public static IRBackendAggregator aggregator() {
@ -24,6 +28,7 @@ public class IRBackend {
public static final class IRBackendAggregator {
private final ArrayList<IRFunction> functions = new ArrayList<>();
private final ArrayList<IRBackendExecutableFunction> executableFunctions = new ArrayList<>();
private final CallableTable callableTable = new CallableTable();
private final ArrayList<IRReservedMetadata.HostMethodBinding> hostMethodBindings = new ArrayList<>();
private final ArrayList<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces = new ArrayList<>();
private final ArrayList<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces = new ArrayList<>();
@ -34,7 +39,10 @@ public class IRBackend {
return;
}
functions.addAll(backendFile.functions().asList());
executableFunctions.addAll(backendFile.executableFunctions().asList());
final var callableRemap = reindexCallables(backendFile.callableSignatures());
for (final var function : backendFile.executableFunctions()) {
executableFunctions.add(remapExecutableFunction(function, callableRemap));
}
final var metadata = backendFile.reservedMetadata();
hostMethodBindings.addAll(metadata.hostMethodBindings().asList());
builtinTypeSurfaces.addAll(metadata.builtinTypeSurfaces().asList());
@ -42,11 +50,80 @@ public class IRBackend {
requiredCapabilities.addAll(metadata.requiredCapabilities().asList());
}
private int[] reindexCallables(final ReadOnlyList<CallableSignatureRef> localCallableSignatures) {
if (localCallableSignatures == null || localCallableSignatures.isEmpty()) {
return new int[0];
}
final var remap = new int[localCallableSignatures.size()];
for (var i = 0; i < localCallableSignatures.size(); i++) {
remap[i] = callableTable.register(localCallableSignatures.get(i)).getId();
}
return remap;
}
private IRBackendExecutableFunction remapExecutableFunction(
final IRBackendExecutableFunction function,
final int[] callableRemap) {
final var remappedInstructions = new ArrayList<IRBackendExecutableFunction.Instruction>(function.instructions().size());
for (final var instruction : function.instructions()) {
final Integer remappedCallee;
if (instruction.calleeCallableId() == null) {
remappedCallee = null;
} else {
remappedCallee = remapCallableId(instruction.calleeCallableId(), callableRemap, "callee");
}
remappedInstructions.add(new IRBackendExecutableFunction.Instruction(
instruction.kind(),
instruction.calleeModuleKey(),
instruction.calleeCallableName(),
remappedCallee,
instruction.hostCall(),
instruction.intrinsicCall(),
instruction.label(),
instruction.targetLabel(),
instruction.expectedArgSlots(),
instruction.expectedRetSlots(),
instruction.span()));
}
return new IRBackendExecutableFunction(
function.fileId(),
function.moduleKey(),
function.callableName(),
remapCallableId(function.callableId(), callableRemap, "function"),
function.sourceStart(),
function.sourceEnd(),
function.paramSlots(),
function.localSlots(),
function.returnSlots(),
function.maxStackSlots(),
ReadOnlyList.wrap(remappedInstructions),
function.span());
}
private int remapCallableId(
final int localCallableId,
final int[] callableRemap,
final String label) {
if (localCallableId < 0 || localCallableId >= callableRemap.length) {
throw new IllegalArgumentException("invalid local " + label + " callable id: " + localCallableId);
}
return callableRemap[localCallableId];
}
private ReadOnlyList<CallableSignatureRef> emitCallableSignatures() {
final var signatures = new ArrayList<CallableSignatureRef>(callableTable.size());
for (final var callableId : callableTable.identifiers()) {
signatures.add(callableTable.get(callableId));
}
return ReadOnlyList.wrap(signatures);
}
public IRBackend emit() {
return IRBackend
.builder()
.functions(ReadOnlyList.wrap(functions))
.executableFunctions(ReadOnlyList.wrap(executableFunctions))
.callableSignatures(emitCallableSignatures())
.reservedMetadata(new IRReservedMetadata(
ReadOnlyList.wrap(hostMethodBindings),
ReadOnlyList.wrap(builtinTypeSurfaces),
@ -61,6 +138,7 @@ public class IRBackend {
final var sb = new StringBuilder();
sb.append("IRBackend{functions=").append(functions.size())
.append(", executableFunctions=").append(executableFunctions.size())
.append(", callableSignatures=").append(callableSignatures.size())
.append(", hostBindings=").append(reservedMetadata.hostMethodBindings().size())
.append(", builtinTypes=").append(reservedMetadata.builtinTypeSurfaces().size())
.append(", builtinConsts=").append(reservedMetadata.builtinConstSurfaces().size())

View File

@ -10,6 +10,7 @@ public record IRBackendExecutableFunction(
FileId fileId,
String moduleKey,
String callableName,
int callableId,
int sourceStart,
int sourceEnd,
int paramSlots,
@ -26,6 +27,9 @@ public record IRBackendExecutableFunction(
if (callableName.isBlank()) {
throw new IllegalArgumentException("callableName must not be blank");
}
if (callableId < 0) {
throw new IllegalArgumentException("callableId must be non-negative");
}
if (sourceStart < 0 || sourceEnd < 0 || sourceEnd < sourceStart) {
throw new IllegalArgumentException("invalid source span bounds");
}
@ -46,6 +50,7 @@ public record IRBackendExecutableFunction(
InstructionKind kind,
String calleeModuleKey,
String calleeCallableName,
Integer calleeCallableId,
HostCallMetadata hostCall,
IntrinsicCallMetadata intrinsicCall,
String label,
@ -57,10 +62,34 @@ public record IRBackendExecutableFunction(
final InstructionKind kind,
final String calleeModuleKey,
final String calleeCallableName,
final Integer calleeCallableId,
final HostCallMetadata hostCall,
final IntrinsicCallMetadata intrinsicCall,
final Span span) {
this(kind, calleeModuleKey, calleeCallableName, hostCall, intrinsicCall, null, null, null, null, span);
this(kind, calleeModuleKey, calleeCallableName, calleeCallableId, hostCall, intrinsicCall, null, null, null, null, span);
}
public Instruction(
final InstructionKind kind,
final String calleeModuleKey,
final String calleeCallableName,
final Integer calleeCallableId,
final HostCallMetadata hostCall,
final IntrinsicCallMetadata intrinsicCall,
final Integer expectedArgSlots,
final Integer expectedRetSlots,
final Span span) {
this(kind, calleeModuleKey, calleeCallableName, calleeCallableId, hostCall, intrinsicCall, null, null, expectedArgSlots, expectedRetSlots, span);
}
public Instruction(
final InstructionKind kind,
final String calleeModuleKey,
final String calleeCallableName,
final HostCallMetadata hostCall,
final IntrinsicCallMetadata intrinsicCall,
final Span span) {
this(kind, calleeModuleKey, calleeCallableName, null, hostCall, intrinsicCall, null, null, null, null, span);
}
public Instruction(
@ -72,7 +101,7 @@ public record IRBackendExecutableFunction(
final Integer expectedArgSlots,
final Integer expectedRetSlots,
final Span span) {
this(kind, calleeModuleKey, calleeCallableName, hostCall, intrinsicCall, null, null, expectedArgSlots, expectedRetSlots, span);
this(kind, calleeModuleKey, calleeCallableName, null, hostCall, intrinsicCall, null, null, expectedArgSlots, expectedRetSlots, span);
}
public Instruction {
@ -82,6 +111,9 @@ public record IRBackendExecutableFunction(
calleeCallableName = calleeCallableName == null ? "" : calleeCallableName;
label = label == null ? "" : label;
targetLabel = targetLabel == null ? "" : targetLabel;
if (calleeCallableId != null && calleeCallableId < 0) {
throw new IllegalArgumentException("calleeCallableId must be non-negative");
}
if (expectedArgSlots != null && expectedArgSlots < 0) {
throw new IllegalArgumentException("expectedArgSlots must be non-negative");
}
@ -90,8 +122,8 @@ public record IRBackendExecutableFunction(
}
switch (kind) {
case CALL_FUNC -> {
if (calleeCallableName.isBlank()) {
throw new IllegalArgumentException("CALL_FUNC requires calleeCallableName");
if (calleeCallableId == null) {
throw new IllegalArgumentException("CALL_FUNC requires calleeCallableId");
}
if (hostCall != null || intrinsicCall != null) {
throw new IllegalArgumentException("CALL_FUNC must not carry host or intrinsic metadata");
@ -101,6 +133,9 @@ public record IRBackendExecutableFunction(
if (hostCall == null) {
throw new IllegalArgumentException("CALL_HOST requires hostCall metadata");
}
if (calleeCallableId != null) {
throw new IllegalArgumentException("CALL_HOST must not carry calleeCallableId");
}
if (intrinsicCall != null) {
throw new IllegalArgumentException("CALL_HOST must not carry intrinsic metadata");
}
@ -109,12 +144,15 @@ public record IRBackendExecutableFunction(
if (intrinsicCall == null) {
throw new IllegalArgumentException("CALL_INTRINSIC requires intrinsic metadata");
}
if (calleeCallableId != null) {
throw new IllegalArgumentException("CALL_INTRINSIC must not carry calleeCallableId");
}
if (hostCall != null) {
throw new IllegalArgumentException("CALL_INTRINSIC must not carry host metadata");
}
}
case HALT, RET -> {
if (!calleeCallableName.isBlank() || hostCall != null || intrinsicCall != null) {
if (!calleeCallableName.isBlank() || calleeCallableId != null || hostCall != null || intrinsicCall != null) {
throw new IllegalArgumentException(kind + " must not carry callsite metadata");
}
if (expectedArgSlots != null || expectedRetSlots != null) {
@ -127,6 +165,7 @@ public record IRBackendExecutableFunction(
}
if (!targetLabel.isBlank()
|| !calleeCallableName.isBlank()
|| calleeCallableId != null
|| hostCall != null
|| intrinsicCall != null
|| expectedArgSlots != null
@ -140,6 +179,7 @@ public record IRBackendExecutableFunction(
}
if (!label.isBlank()
|| !calleeCallableName.isBlank()
|| calleeCallableId != null
|| hostCall != null
|| intrinsicCall != null
|| expectedArgSlots != null

View File

@ -1,6 +1,7 @@
package p.studio.compiler.models;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.tables.CallableSignatureRef;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.Objects;
@ -9,28 +10,30 @@ public record IRBackendFile(
FileId fileId,
ReadOnlyList<IRFunction> functions,
ReadOnlyList<IRBackendExecutableFunction> executableFunctions,
IRReservedMetadata reservedMetadata) {
IRReservedMetadata reservedMetadata,
ReadOnlyList<CallableSignatureRef> callableSignatures) {
public IRBackendFile {
fileId = Objects.requireNonNull(fileId, "fileId");
functions = functions == null ? ReadOnlyList.empty() : functions;
executableFunctions = executableFunctions == null ? ReadOnlyList.empty() : executableFunctions;
reservedMetadata = reservedMetadata == null ? IRReservedMetadata.empty() : reservedMetadata;
callableSignatures = callableSignatures == null ? ReadOnlyList.empty() : callableSignatures;
}
public IRBackendFile(
final FileId fileId,
final ReadOnlyList<IRFunction> functions) {
this(fileId, functions, ReadOnlyList.empty(), IRReservedMetadata.empty());
this(fileId, functions, ReadOnlyList.empty(), IRReservedMetadata.empty(), ReadOnlyList.empty());
}
public static IRBackendFile empty(final FileId fileId) {
return new IRBackendFile(fileId, ReadOnlyList.empty(), ReadOnlyList.empty(), IRReservedMetadata.empty());
return new IRBackendFile(fileId, ReadOnlyList.empty(), ReadOnlyList.empty(), IRReservedMetadata.empty(), ReadOnlyList.empty());
}
public IRBackendFile(
final FileId fileId,
final ReadOnlyList<IRFunction> functions,
final IRReservedMetadata reservedMetadata) {
this(fileId, functions, ReadOnlyList.empty(), reservedMetadata);
this(fileId, functions, ReadOnlyList.empty(), reservedMetadata, ReadOnlyList.empty());
}
}

View File

@ -3,6 +3,7 @@ package p.studio.compiler.models;
import org.junit.jupiter.api.Test;
import p.studio.compiler.source.Span;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.tables.CallableSignatureRef;
import p.studio.utilities.structures.ReadOnlyList;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -25,6 +26,7 @@ class IRBackendExecutableContractTest {
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
"app/main",
"foo",
7,
null,
null,
Span.none());
@ -37,6 +39,7 @@ class IRBackendExecutableContractTest {
new FileId(1),
"app",
"main",
1,
10,
5,
0,
@ -50,6 +53,7 @@ class IRBackendExecutableContractTest {
new FileId(1),
"app",
"main",
1,
0,
10,
0,
@ -67,6 +71,7 @@ class IRBackendExecutableContractTest {
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
"app/main",
"foo",
1,
new IRBackendExecutableFunction.HostCallMetadata("gfx", "draw", 1, 0, 0),
null,
Span.none()));
@ -87,6 +92,7 @@ class IRBackendExecutableContractTest {
"",
null,
null,
null,
"",
"",
null,
@ -99,6 +105,7 @@ class IRBackendExecutableContractTest {
"",
null,
null,
null,
"",
"",
null,
@ -116,6 +123,7 @@ class IRBackendExecutableContractTest {
"app/main",
"entry",
0,
0,
10,
0,
0,
@ -127,9 +135,11 @@ class IRBackendExecutableContractTest {
"",
null,
null,
null,
Span.none())),
Span.none())),
IRReservedMetadata.empty());
IRReservedMetadata.empty(),
ReadOnlyList.from(new CallableSignatureRef("app/main", "entry", 0, "() -> unit")));
final var fileB = new IRBackendFile(
new FileId(2),
ReadOnlyList.empty(),
@ -137,6 +147,7 @@ class IRBackendExecutableContractTest {
new FileId(2),
"app/main",
"aux",
0,
11,
20,
0,
@ -149,9 +160,11 @@ class IRBackendExecutableContractTest {
"",
null,
null,
null,
Span.none())),
Span.none())),
IRReservedMetadata.empty());
IRReservedMetadata.empty(),
ReadOnlyList.from(new CallableSignatureRef("app/main", "aux", 0, "() -> unit")));
final var aggregator = IRBackend.aggregator();
aggregator.merge(fileA);
@ -161,5 +174,6 @@ class IRBackendExecutableContractTest {
assertEquals(2, backend.getExecutableFunctions().size());
assertEquals("entry", backend.getExecutableFunctions().get(0).callableName());
assertEquals("aux", backend.getExecutableFunctions().get(1).callableName());
assertEquals(2, backend.getCallableSignatures().size());
}
}