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.DiagnosticSink;
import p.studio.compiler.source.diagnostics.DiagnosticPhase; import p.studio.compiler.source.diagnostics.DiagnosticPhase;
import p.studio.compiler.source.identifiers.FileId; 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.compiler.source.tables.IntrinsicTable;
import p.studio.utilities.structures.ReadOnlyList; import p.studio.utilities.structures.ReadOnlyList;
@ -121,13 +123,18 @@ public final class PbsFrontendCompiler {
? ReadOnlyList.empty() ? ReadOnlyList.empty()
: lowerFunctions(fileId, ast); : lowerFunctions(fileId, ast);
final var loweringErrorBaseline = diagnostics.errorCount(); final var loweringErrorBaseline = diagnostics.errorCount();
final ReadOnlyList<IRBackendExecutableFunction> executableFunctions = sourceKind == SourceKind.SDK_INTERFACE final var executableLowering = sourceKind == SourceKind.SDK_INTERFACE
? ReadOnlyList.empty() ? new ExecutableLoweringResult(ReadOnlyList.empty(), ReadOnlyList.empty())
: lowerExecutableFunctions(fileId, ast, moduleKey, reservedMetadata, diagnostics); : lowerExecutableFunctions(fileId, ast, moduleKey, reservedMetadata, diagnostics);
if (diagnostics.errorCount() > loweringErrorBaseline) { if (diagnostics.errorCount() > loweringErrorBaseline) {
return IRBackendFile.empty(fileId); 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) { private ReadOnlyList<IRFunction> lowerFunctions(final FileId fileId, final PbsAst.File ast) {
@ -143,12 +150,13 @@ public final class PbsFrontendCompiler {
return ReadOnlyList.wrap(functions); return ReadOnlyList.wrap(functions);
} }
private ReadOnlyList<IRBackendExecutableFunction> lowerExecutableFunctions( private ExecutableLoweringResult lowerExecutableFunctions(
final FileId fileId, final FileId fileId,
final PbsAst.File ast, final PbsAst.File ast,
final String moduleKey, final String moduleKey,
final IRReservedMetadata reservedMetadata, final IRReservedMetadata reservedMetadata,
final DiagnosticSink diagnostics) { final DiagnosticSink diagnostics) {
final var normalizedModuleKey = moduleKey == null ? "" : moduleKey;
final var hostByMethodName = new HashMap<String, IRReservedMetadata.HostMethodBinding>(); final var hostByMethodName = new HashMap<String, IRReservedMetadata.HostMethodBinding>();
for (final var hostBinding : reservedMetadata.hostMethodBindings()) { for (final var hostBinding : reservedMetadata.hostMethodBindings()) {
hostByMethodName.put(hostBinding.sourceMethodName(), hostBinding); hostByMethodName.put(hostBinding.sourceMethodName(), hostBinding);
@ -159,21 +167,32 @@ public final class PbsFrontendCompiler {
intrinsicByMethodName.put(intrinsicSurface.sourceMethodName(), intrinsicSurface); 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(); final var intrinsicIdTable = new IntrinsicTable();
for (final var declaredFn : ast.functions()) { 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); final var retSlots = returnSlotsFor(declaredFn);
if (returnSlotsByCallableAndArity.containsKey(key) returnSlotsByCallableId.put(callableId, retSlots);
&& !returnSlotsByCallableAndArity.get(key).equals(retSlots)) {
returnSlotsByCallableAndArity.put(key, null);
} else {
returnSlotsByCallableAndArity.put(key, retSlots);
}
} }
final var executableFunctions = new ArrayList<IRBackendExecutableFunction>(ast.functions().size()); final var executableFunctions = new ArrayList<IRBackendExecutableFunction>(ast.functions().size());
for (final var fn : ast.functions()) { 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 instructions = new ArrayList<IRBackendExecutableFunction.Instruction>();
final var callsites = new ArrayList<PbsAst.CallExpr>(); final var callsites = new ArrayList<PbsAst.CallExpr>();
collectCallsFromBlock(fn.body(), callsites); collectCallsFromBlock(fn.body(), callsites);
@ -230,14 +249,37 @@ public final class PbsFrontendCompiler {
continue; 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( instructions.add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_FUNC, IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
moduleKey == null ? "" : moduleKey, normalizedModuleKey,
calleeName, calleeName,
calleeCallableId,
null, null,
null, null,
callExpr.arguments().size(), callExpr.arguments().size(),
returnSlotsByCallableAndArity.get(callableArityKey(calleeName, callExpr.arguments().size())), returnSlotsByCallableId.get(calleeCallableId),
callExpr.span())); callExpr.span()));
} }
instructions.add(new IRBackendExecutableFunction.Instruction( instructions.add(new IRBackendExecutableFunction.Instruction(
@ -246,6 +288,7 @@ public final class PbsFrontendCompiler {
"", "",
null, null,
null, null,
null,
fn.span())); fn.span()));
final var returnSlots = returnSlotsFor(fn); final var returnSlots = returnSlotsFor(fn);
@ -253,8 +296,9 @@ public final class PbsFrontendCompiler {
final var end = safeToInt(fn.span().getEnd()); final var end = safeToInt(fn.span().getEnd());
executableFunctions.add(new IRBackendExecutableFunction( executableFunctions.add(new IRBackendExecutableFunction(
fileId, fileId,
moduleKey == null ? "" : moduleKey, normalizedModuleKey,
fn.name(), fn.name(),
functionCallableId,
start, start,
end, end,
fn.parameters().size(), fn.parameters().size(),
@ -264,7 +308,13 @@ public final class PbsFrontendCompiler {
ReadOnlyList.wrap(instructions), ReadOnlyList.wrap(instructions),
fn.span())); 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) { private int returnSlotsFor(final PbsAst.FunctionDecl functionDecl) {
@ -280,6 +330,60 @@ public final class PbsFrontendCompiler {
return callableName + "#" + arity; 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) { private int safeToInt(final long value) {
if (value > Integer.MAX_VALUE) { if (value > Integer.MAX_VALUE) {
return Integer.MAX_VALUE; return Integer.MAX_VALUE;
@ -443,4 +547,9 @@ public final class PbsFrontendCompiler {
default -> null; 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 private static final Comparator<IRBackendExecutableFunction> FUNCTION_ORDER = Comparator
.comparing(IRBackendExecutableFunction::moduleKey) .comparing(IRBackendExecutableFunction::moduleKey)
.thenComparing(IRBackendExecutableFunction::callableName) .thenComparing(IRBackendExecutableFunction::callableName)
.thenComparingInt(IRBackendExecutableFunction::callableId)
.thenComparingInt(IRBackendExecutableFunction::sourceStart); .thenComparingInt(IRBackendExecutableFunction::sourceStart);
private final IRVMValidator validator; private final IRVMValidator validator;
@ -35,11 +36,14 @@ public class LowerToIRVMService {
} }
final var ordered = orderFunctions(backend.getExecutableFunctions()); 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++) { for (var i = 0; i < ordered.size(); i++) {
final var fn = ordered.get(i); final var fn = ordered.get(i);
final var key = callableKey(fn.moduleKey(), fn.callableName()); if (funcIdByCallableId.putIfAbsent(fn.callableId(), i) != null) {
funcIdByModuleAndName.putIfAbsent(key, i); throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE,
"duplicate callable id in executable set: " + fn.callableId());
}
} }
final var loweredFunctions = new ArrayList<IRVMFunction>(ordered.size()); final var loweredFunctions = new ArrayList<IRVMFunction>(ordered.size());
@ -66,12 +70,16 @@ public class LowerToIRVMService {
functionPc += IRVMOp.RET.immediateSize() + 2; functionPc += IRVMOp.RET.immediateSize() + 2;
} }
case CALL_FUNC -> { case CALL_FUNC -> {
final var key = callableKey(instr.calleeModuleKey(), instr.calleeCallableName()); if (instr.calleeCallableId() == null) {
final var calleeId = funcIdByModuleAndName.get(key); throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE,
"missing callee callable id");
}
final var calleeId = funcIdByCallableId.get(instr.calleeCallableId());
if (calleeId == null) { if (calleeId == null) {
throw new IRVMLoweringException( throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE, IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE,
"missing callee function: " + key); "missing callee function for callable_id=" + instr.calleeCallableId());
} }
final var calleeFunction = ordered.get(calleeId); final var calleeFunction = ordered.get(calleeId);
if (instr.expectedArgSlots() != null if (instr.expectedArgSlots() != null
@ -79,7 +87,7 @@ public class LowerToIRVMService {
throw new IRVMLoweringException( throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_CALL_ARG_SLOTS_MISMATCH, IRVMLoweringErrorCode.LOWER_IRVM_CALL_ARG_SLOTS_MISMATCH,
"call arg_slots mismatch for %s. expected=%d actual=%d".formatted( "call arg_slots mismatch for %s. expected=%d actual=%d".formatted(
key, instr.calleeCallableName(),
instr.expectedArgSlots(), instr.expectedArgSlots(),
calleeFunction.paramSlots())); calleeFunction.paramSlots()));
} }
@ -88,7 +96,7 @@ public class LowerToIRVMService {
throw new IRVMLoweringException( throw new IRVMLoweringException(
IRVMLoweringErrorCode.LOWER_IRVM_CALL_RET_SLOTS_MISMATCH, IRVMLoweringErrorCode.LOWER_IRVM_CALL_RET_SLOTS_MISMATCH,
"call ret_slots mismatch for %s. expected=%d actual=%d".formatted( "call ret_slots mismatch for %s. expected=%d actual=%d".formatted(
key, instr.calleeCallableName(),
instr.expectedRetSlots(), instr.expectedRetSlots(),
calleeFunction.returnSlots())); calleeFunction.returnSlots()));
} }
@ -213,12 +221,6 @@ public class LowerToIRVMService {
return ReadOnlyList.wrap(ordered); return ReadOnlyList.wrap(ordered);
} }
private String callableKey(
final String moduleKey,
final String callableName) {
return (moduleKey == null ? "" : moduleKey) + "::" + callableName;
}
private BytecodeModule.SourceSpan toBytecodeSpan( private BytecodeModule.SourceSpan toBytecodeSpan(
final int fallbackFileId, final int fallbackFileId,
final Span span) { final Span span) {

View File

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

View File

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

View File

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

View File

@ -38,6 +38,7 @@ class LowerToIRVMPipelineStageTest {
new FileId(0), new FileId(0),
"app", "app",
"main", "main",
1,
0, 0,
10, 10,
0, 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.Builder;
import lombok.Getter; import lombok.Getter;
import p.studio.compiler.source.tables.CallableSignatureRef;
import p.studio.compiler.source.tables.CallableTable;
import p.studio.utilities.structures.ReadOnlyList; import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList; import java.util.ArrayList;
@ -15,6 +17,8 @@ public class IRBackend {
@Builder.Default @Builder.Default
private final ReadOnlyList<IRBackendExecutableFunction> executableFunctions = ReadOnlyList.empty(); private final ReadOnlyList<IRBackendExecutableFunction> executableFunctions = ReadOnlyList.empty();
@Builder.Default @Builder.Default
private final ReadOnlyList<CallableSignatureRef> callableSignatures = ReadOnlyList.empty();
@Builder.Default
private final IRReservedMetadata reservedMetadata = IRReservedMetadata.empty(); private final IRReservedMetadata reservedMetadata = IRReservedMetadata.empty();
public static IRBackendAggregator aggregator() { public static IRBackendAggregator aggregator() {
@ -24,6 +28,7 @@ public class IRBackend {
public static final class IRBackendAggregator { public static final class IRBackendAggregator {
private final ArrayList<IRFunction> functions = new ArrayList<>(); private final ArrayList<IRFunction> functions = new ArrayList<>();
private final ArrayList<IRBackendExecutableFunction> executableFunctions = 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.HostMethodBinding> hostMethodBindings = new ArrayList<>();
private final ArrayList<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces = new ArrayList<>(); private final ArrayList<IRReservedMetadata.BuiltinTypeSurface> builtinTypeSurfaces = new ArrayList<>();
private final ArrayList<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces = new ArrayList<>(); private final ArrayList<IRReservedMetadata.BuiltinConstSurface> builtinConstSurfaces = new ArrayList<>();
@ -34,7 +39,10 @@ public class IRBackend {
return; return;
} }
functions.addAll(backendFile.functions().asList()); 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(); final var metadata = backendFile.reservedMetadata();
hostMethodBindings.addAll(metadata.hostMethodBindings().asList()); hostMethodBindings.addAll(metadata.hostMethodBindings().asList());
builtinTypeSurfaces.addAll(metadata.builtinTypeSurfaces().asList()); builtinTypeSurfaces.addAll(metadata.builtinTypeSurfaces().asList());
@ -42,11 +50,80 @@ public class IRBackend {
requiredCapabilities.addAll(metadata.requiredCapabilities().asList()); 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() { public IRBackend emit() {
return IRBackend return IRBackend
.builder() .builder()
.functions(ReadOnlyList.wrap(functions)) .functions(ReadOnlyList.wrap(functions))
.executableFunctions(ReadOnlyList.wrap(executableFunctions)) .executableFunctions(ReadOnlyList.wrap(executableFunctions))
.callableSignatures(emitCallableSignatures())
.reservedMetadata(new IRReservedMetadata( .reservedMetadata(new IRReservedMetadata(
ReadOnlyList.wrap(hostMethodBindings), ReadOnlyList.wrap(hostMethodBindings),
ReadOnlyList.wrap(builtinTypeSurfaces), ReadOnlyList.wrap(builtinTypeSurfaces),
@ -61,6 +138,7 @@ public class IRBackend {
final var sb = new StringBuilder(); final var sb = new StringBuilder();
sb.append("IRBackend{functions=").append(functions.size()) sb.append("IRBackend{functions=").append(functions.size())
.append(", executableFunctions=").append(executableFunctions.size()) .append(", executableFunctions=").append(executableFunctions.size())
.append(", callableSignatures=").append(callableSignatures.size())
.append(", hostBindings=").append(reservedMetadata.hostMethodBindings().size()) .append(", hostBindings=").append(reservedMetadata.hostMethodBindings().size())
.append(", builtinTypes=").append(reservedMetadata.builtinTypeSurfaces().size()) .append(", builtinTypes=").append(reservedMetadata.builtinTypeSurfaces().size())
.append(", builtinConsts=").append(reservedMetadata.builtinConstSurfaces().size()) .append(", builtinConsts=").append(reservedMetadata.builtinConstSurfaces().size())

View File

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

View File

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