implements PR-O4.1
This commit is contained in:
parent
bbcb65b048
commit
d4f6d96437
@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -178,6 +178,7 @@ class BackendGateIIntegrationTest {
|
||||
new FileId(1),
|
||||
"app/main",
|
||||
name,
|
||||
1,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -38,6 +38,7 @@ class LowerToIRVMPipelineStageTest {
|
||||
new FileId(0),
|
||||
"app",
|
||||
"main",
|
||||
1,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package p.studio.compiler.source.tables;
|
||||
|
||||
import p.studio.compiler.source.identifiers.CallableId;
|
||||
|
||||
public interface CallableTableReader extends InternTableReader<CallableId, CallableSignatureRef> {
|
||||
}
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
@ -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())
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user