refactoring and reducing complexity
This commit is contained in:
parent
2288ed75f9
commit
2b4c9488a7
File diff suppressed because it is too large
Load Diff
@ -32,44 +32,9 @@ public final class PbsHostAdmissionValidator {
|
||||
|
||||
for (final var hostBinding : metadata.hostMethodBindings()) {
|
||||
final var normalizedCapability = normalize(hostBinding.requiredCapability());
|
||||
if (!hostBinding.capabilityDeclared()) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsHostAdmissionErrors.E_HOST_MISSING_REQUIRED_CAPABILITY.name(),
|
||||
"Host binding '%s.%s' requires Capability(name=...) metadata"
|
||||
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName()),
|
||||
hostBinding.span());
|
||||
continue;
|
||||
}
|
||||
if (normalizedCapability.isBlank()) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsHostAdmissionErrors.E_HOST_MALFORMED_CAPABILITY_METADATA.name(),
|
||||
"Host binding '%s.%s' has malformed Capability(name=...) metadata"
|
||||
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName()),
|
||||
hostBinding.span());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CAPABILITY_NAME.matcher(normalizedCapability).matches()) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsHostAdmissionErrors.E_HOST_MALFORMED_CAPABILITY_METADATA.name(),
|
||||
"Host binding '%s.%s' has malformed capability name '%s'"
|
||||
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName(), normalizedCapability),
|
||||
hostBinding.span());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!knownCapabilities.contains(normalizedCapability)) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsHostAdmissionErrors.E_HOST_UNKNOWN_CAPABILITY.name(),
|
||||
"Host binding '%s.%s' references unknown capability '%s'"
|
||||
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName(), normalizedCapability),
|
||||
hostBinding.span());
|
||||
continue;
|
||||
}
|
||||
if (hostBinding(diagnostics, hostBinding)) continue;
|
||||
if (normalizedCapability(diagnostics, hostBinding, normalizedCapability)) continue;
|
||||
if (knowCapabilities(diagnostics, hostBinding, knownCapabilities, normalizedCapability)) continue;
|
||||
|
||||
final var hostBindingId = hostBindingTable.register(
|
||||
hostBinding.abiModule(),
|
||||
@ -78,31 +43,9 @@ public final class PbsHostAdmissionValidator {
|
||||
final var firstBindingCapability = firstCapabilityByHostBinding.putIfAbsent(
|
||||
hostBindingId,
|
||||
new FirstBindingCapability(normalizedCapability, hostBinding.span()));
|
||||
if (firstBindingCapability != null && !firstBindingCapability.capability().equals(normalizedCapability)) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsHostAdmissionErrors.E_HOST_INCONSISTENT_BINDING_CAPABILITY.name(),
|
||||
"Canonical host binding (%s, %s, %d) has inconsistent capability metadata ('%s' vs '%s')"
|
||||
.formatted(
|
||||
hostBinding.abiModule(),
|
||||
hostBinding.abiMethod(),
|
||||
hostBinding.abiVersion(),
|
||||
firstBindingCapability.capability(),
|
||||
normalizedCapability),
|
||||
hostBinding.span(),
|
||||
List.of(new RelatedSpan("First capability declaration for this binding is here", firstBindingCapability.span())));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (context.enforceDeclaredCapabilities() && !declaredCapabilities.contains(normalizedCapability)) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsHostAdmissionErrors.E_HOST_CAPABILITY_NOT_DECLARED.name(),
|
||||
"Capability '%s' required by host binding '%s.%s' is not declared in host admission context"
|
||||
.formatted(normalizedCapability, hostBinding.ownerName(), hostBinding.sourceMethodName()),
|
||||
hostBinding.span());
|
||||
continue;
|
||||
}
|
||||
if (firstBindingCapability(diagnostics, hostBinding, firstBindingCapability, normalizedCapability)) continue;
|
||||
if (declaredCapabilities(context, diagnostics, hostBinding, declaredCapabilities, normalizedCapability)) continue;
|
||||
|
||||
requiredCapabilities.add(normalizedCapability);
|
||||
}
|
||||
@ -110,6 +53,88 @@ public final class PbsHostAdmissionValidator {
|
||||
return ReadOnlyList.wrap(requiredCapabilities.stream().toList());
|
||||
}
|
||||
|
||||
private static boolean declaredCapabilities(HostAdmissionContext context, DiagnosticSink diagnostics, IRReservedMetadata.HostMethodBinding hostBinding, Set<String> declaredCapabilities, String normalizedCapability) {
|
||||
if (context.enforceDeclaredCapabilities() && !declaredCapabilities.contains(normalizedCapability)) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsHostAdmissionErrors.E_HOST_CAPABILITY_NOT_DECLARED.name(),
|
||||
"Capability '%s' required by host binding '%s.%s' is not declared in host admission context"
|
||||
.formatted(normalizedCapability, hostBinding.ownerName(), hostBinding.sourceMethodName()),
|
||||
hostBinding.span());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean firstBindingCapability(DiagnosticSink diagnostics, IRReservedMetadata.HostMethodBinding hostBinding, FirstBindingCapability firstBindingCapability, String normalizedCapability) {
|
||||
if (firstBindingCapability != null && !firstBindingCapability.capability().equals(normalizedCapability)) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsHostAdmissionErrors.E_HOST_INCONSISTENT_BINDING_CAPABILITY.name(),
|
||||
"Canonical host binding (%s, %s, %d) has inconsistent capability metadata ('%s' vs '%s')"
|
||||
.formatted(
|
||||
hostBinding.abiModule(),
|
||||
hostBinding.abiMethod(),
|
||||
hostBinding.abiVersion(),
|
||||
firstBindingCapability.capability(),
|
||||
normalizedCapability),
|
||||
hostBinding.span(),
|
||||
List.of(new RelatedSpan("First capability declaration for this binding is here", firstBindingCapability.span())));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean knowCapabilities(DiagnosticSink diagnostics, IRReservedMetadata.HostMethodBinding hostBinding, Set<String> knownCapabilities, String normalizedCapability) {
|
||||
if (!knownCapabilities.contains(normalizedCapability)) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsHostAdmissionErrors.E_HOST_UNKNOWN_CAPABILITY.name(),
|
||||
"Host binding '%s.%s' references unknown capability '%s'"
|
||||
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName(), normalizedCapability),
|
||||
hostBinding.span());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean normalizedCapability(DiagnosticSink diagnostics, IRReservedMetadata.HostMethodBinding hostBinding, String normalizedCapability) {
|
||||
if (normalizedCapability.isBlank()) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsHostAdmissionErrors.E_HOST_MALFORMED_CAPABILITY_METADATA.name(),
|
||||
"Host binding '%s.%s' has malformed Capability(name=...) metadata"
|
||||
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName()),
|
||||
hostBinding.span());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!CAPABILITY_NAME.matcher(normalizedCapability).matches()) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsHostAdmissionErrors.E_HOST_MALFORMED_CAPABILITY_METADATA.name(),
|
||||
"Host binding '%s.%s' has malformed capability name '%s'"
|
||||
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName(), normalizedCapability),
|
||||
hostBinding.span());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean hostBinding(DiagnosticSink diagnostics, IRReservedMetadata.HostMethodBinding hostBinding) {
|
||||
if (!hostBinding.capabilityDeclared()) {
|
||||
Diagnostics.error(
|
||||
diagnostics,
|
||||
PbsHostAdmissionErrors.E_HOST_MISSING_REQUIRED_CAPABILITY.name(),
|
||||
"Host binding '%s.%s' requires Capability(name=...) metadata"
|
||||
.formatted(hostBinding.ownerName(), hostBinding.sourceMethodName()),
|
||||
hostBinding.span());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Set<String> normalizedSet(final ReadOnlyList<String> values) {
|
||||
final var normalized = new HashSet<String>();
|
||||
for (final var value : values) {
|
||||
|
||||
@ -0,0 +1,93 @@
|
||||
package p.studio.compiler.pbs.lowering;
|
||||
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.compiler.source.identifiers.CallableShapeId;
|
||||
import p.studio.compiler.source.identifiers.TypeSurfaceId;
|
||||
import p.studio.compiler.source.tables.CallableShapeTable;
|
||||
import p.studio.compiler.source.tables.TypeSurfaceTable;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
final class PbsCallableShapeSurfaceService {
|
||||
|
||||
String callableShapeSurfaceOf(final PbsAst.FunctionDecl functionDecl) {
|
||||
final var typeSurfaceTable = new TypeSurfaceTable();
|
||||
final var callableShapeTable = new CallableShapeTable();
|
||||
final var callableShapeId = callableShapeId(functionDecl, typeSurfaceTable, callableShapeTable);
|
||||
return callableShapeSurface(callableShapeId, typeSurfaceTable, callableShapeTable);
|
||||
}
|
||||
|
||||
CallableShapeId callableShapeId(
|
||||
final PbsAst.FunctionDecl functionDecl,
|
||||
final TypeSurfaceTable typeSurfaceTable,
|
||||
final CallableShapeTable callableShapeTable) {
|
||||
final var parameterSurfaceIds = new ArrayList<TypeSurfaceId>(functionDecl.parameters().size());
|
||||
for (final var parameter : functionDecl.parameters()) {
|
||||
parameterSurfaceIds.add(typeSurfaceTable.register(typeSurfaceKey(parameter.typeRef())));
|
||||
}
|
||||
final var outputSurfaceId = typeSurfaceTable.register(outputShapeSurface(
|
||||
functionDecl.returnKind(),
|
||||
functionDecl.returnType(),
|
||||
functionDecl.resultErrorType()));
|
||||
return callableShapeTable.register(ReadOnlyList.wrap(parameterSurfaceIds), outputSurfaceId);
|
||||
}
|
||||
|
||||
String callableShapeSurface(
|
||||
final CallableShapeId callableShapeId,
|
||||
final TypeSurfaceTable typeSurfaceTable,
|
||||
final CallableShapeTable callableShapeTable) {
|
||||
final var shape = callableShapeTable.get(callableShapeId);
|
||||
final var builder = new StringBuilder();
|
||||
builder.append('(');
|
||||
for (var i = 0; i < shape.parameterTypeSurfaces().size(); i++) {
|
||||
if (i > 0) {
|
||||
builder.append(',');
|
||||
}
|
||||
builder.append(typeSurfaceTable.get(shape.parameterTypeSurfaces().get(i)).surface());
|
||||
}
|
||||
builder.append(")->");
|
||||
builder.append(typeSurfaceTable.get(shape.outputTypeSurface()).surface());
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String outputShapeSurface(
|
||||
final PbsAst.ReturnKind returnKind,
|
||||
final PbsAst.TypeRef returnType,
|
||||
final PbsAst.TypeRef resultErrorType) {
|
||||
return switch (returnKind) {
|
||||
case INFERRED_UNIT, EXPLICIT_UNIT -> "unit";
|
||||
case PLAIN -> typeSurfaceKey(returnType);
|
||||
case RESULT -> "result<" + typeSurfaceKey(resultErrorType) + ">" + typeSurfaceKey(returnType);
|
||||
};
|
||||
}
|
||||
|
||||
private String typeSurfaceKey(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(" + typeSurfaceKey(unwrapped.inner()) + ")";
|
||||
case UNIT -> "unit";
|
||||
case GROUP -> typeSurfaceKey(unwrapped.inner());
|
||||
case NAMED_TUPLE -> "tuple(" + unwrapped.fields().stream()
|
||||
.map(field -> typeSurfaceKey(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());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,655 @@
|
||||
package p.studio.compiler.pbs.lowering;
|
||||
|
||||
import p.studio.compiler.models.IRBackendExecutableFunction;
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticPhase;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.compiler.source.identifiers.NameId;
|
||||
import p.studio.compiler.source.identifiers.ModuleId;
|
||||
import p.studio.compiler.source.tables.IntrinsicTable;
|
||||
import p.studio.compiler.source.tables.NameTable;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import static p.studio.compiler.pbs.lowering.PbsExecutableLoweringModels.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
final class PbsExecutableBodyLowerer {
|
||||
private final PbsExecutableCallsiteEmitter callsiteEmitter = new PbsExecutableCallsiteEmitter();
|
||||
private final PbsExecutableStackAnalyzer stackAnalyzer = new PbsExecutableStackAnalyzer();
|
||||
|
||||
PbsLoweredExecutableBody lower(
|
||||
final PbsAst.FunctionDecl functionDecl,
|
||||
final DiagnosticSink diagnostics,
|
||||
final NameTable nameTable,
|
||||
final PbsExecutableMetadataIndex metadataIndex,
|
||||
final PbsExecutableCallableRegistry callableRegistry,
|
||||
final IntrinsicTable intrinsicIdTable) {
|
||||
final var localSlotByNameId = initialLocalSlots(functionDecl, nameTable);
|
||||
final var context = new PbsExecutableLoweringContext(
|
||||
diagnostics,
|
||||
nameTable,
|
||||
metadataIndex,
|
||||
callableRegistry,
|
||||
intrinsicIdTable,
|
||||
localSlotByNameId,
|
||||
functionDecl.parameters().size());
|
||||
final var terminated = lowerBlock(functionDecl.body(), context);
|
||||
finalizeFunctionInstructions(functionDecl, context, terminated);
|
||||
|
||||
final var returnSlots = returnSlotsFor(functionDecl);
|
||||
final int maxStackSlots;
|
||||
try {
|
||||
maxStackSlots = stackAnalyzer.analyzeMaxStackSlots(ReadOnlyList.wrap(context.instructions()), returnSlots);
|
||||
} catch (PbsExecutableStackAnalyzer.ExecutableLoweringAnalysisException e) {
|
||||
diagnostics.error(
|
||||
DiagnosticPhase.STATIC_SEMANTICS,
|
||||
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
|
||||
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
|
||||
Map.of(),
|
||||
e.getMessage(),
|
||||
functionDecl.span());
|
||||
return null;
|
||||
}
|
||||
final var localSlots = Math.max(0, context.nextLocalSlot() - functionDecl.parameters().size());
|
||||
return new PbsLoweredExecutableBody(
|
||||
ReadOnlyList.wrap(context.instructions()),
|
||||
localSlots,
|
||||
maxStackSlots);
|
||||
}
|
||||
|
||||
private Map<NameId, Integer> initialLocalSlots(
|
||||
final PbsAst.FunctionDecl functionDecl,
|
||||
final NameTable nameTable) {
|
||||
final var localSlotByNameId = new HashMap<NameId, Integer>();
|
||||
for (var paramIndex = 0; paramIndex < functionDecl.parameters().size(); paramIndex++) {
|
||||
localSlotByNameId.put(nameTable.register(functionDecl.parameters().get(paramIndex).name()), paramIndex);
|
||||
}
|
||||
return localSlotByNameId;
|
||||
}
|
||||
|
||||
private int returnSlotsFor(final PbsAst.FunctionDecl functionDecl) {
|
||||
return switch (functionDecl.returnKind()) {
|
||||
case INFERRED_UNIT, EXPLICIT_UNIT -> 0;
|
||||
case PLAIN, RESULT -> 1;
|
||||
};
|
||||
}
|
||||
|
||||
private void finalizeFunctionInstructions(
|
||||
final PbsAst.FunctionDecl functionDecl,
|
||||
final PbsExecutableLoweringContext context,
|
||||
final boolean terminated) {
|
||||
if (terminated) {
|
||||
return;
|
||||
}
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
IRBackendExecutableFunction.InstructionKind.RET,
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
functionDecl.span()));
|
||||
}
|
||||
|
||||
private boolean lowerBlock(
|
||||
final PbsAst.Block block,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
if (block == null) {
|
||||
return false;
|
||||
}
|
||||
var terminated = false;
|
||||
for (final var statement : block.statements()) {
|
||||
if (terminated) {
|
||||
break;
|
||||
}
|
||||
terminated = lowerStatement(statement, context);
|
||||
}
|
||||
if (!terminated && block.tailExpression() != null) {
|
||||
lowerExpression(block.tailExpression(), context);
|
||||
}
|
||||
return terminated;
|
||||
}
|
||||
|
||||
private boolean lowerStatement(
|
||||
final PbsAst.Statement statement,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
if (statement == null) {
|
||||
return false;
|
||||
}
|
||||
return switch (statement) {
|
||||
case PbsAst.LetStatement letStatement -> lowerLetStatement(letStatement, context);
|
||||
case PbsAst.AssignStatement assignStatement -> lowerAssignStatement(assignStatement, context);
|
||||
case PbsAst.ReturnStatement returnStatement -> lowerReturnStatement(returnStatement, context);
|
||||
case PbsAst.IfStatement ifStatement -> lowerIfStatement(ifStatement, context);
|
||||
case PbsAst.ForStatement forStatement -> lowerForStatement(forStatement, context);
|
||||
case PbsAst.WhileStatement whileStatement -> lowerWhileStatement(whileStatement, context);
|
||||
case PbsAst.ExpressionStatement expressionStatement -> {
|
||||
lowerExpression(expressionStatement.expression(), context);
|
||||
yield false;
|
||||
}
|
||||
case PbsAst.BreakStatement breakStatement -> lowerBreakStatement(breakStatement, context);
|
||||
case PbsAst.ContinueStatement continueStatement -> lowerContinueStatement(continueStatement, context);
|
||||
};
|
||||
}
|
||||
|
||||
private boolean lowerLetStatement(
|
||||
final PbsAst.LetStatement letStatement,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
lowerExpression(letStatement.initializer(), context);
|
||||
final var localSlot = context.declareLocalSlot(letStatement.name());
|
||||
emitSetLocal(localSlot, letStatement.span(), context);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean lowerAssignStatement(
|
||||
final PbsAst.AssignStatement assignStatement,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
final var target = assignStatement.target();
|
||||
if (target == null || target.rootName() == null || target.rootName().isBlank()) {
|
||||
reportUnsupportedLowering("assignment target requires a local root name", assignStatement.span(), context);
|
||||
return false;
|
||||
}
|
||||
if (target.pathSegments() != null && !target.pathSegments().isEmpty()) {
|
||||
reportUnsupportedLowering("path assignment lowering is not supported in executable lowering v1", assignStatement.span(), context);
|
||||
return false;
|
||||
}
|
||||
final var targetSlot = context.resolveLocalSlot(target.rootName());
|
||||
if (targetSlot == null) {
|
||||
reportUnsupportedLowering("assignment target is not a known local: " + target.rootName(), assignStatement.span(), context);
|
||||
return false;
|
||||
}
|
||||
if (assignStatement.operator() == PbsAst.AssignOperator.ASSIGN) {
|
||||
lowerExpression(assignStatement.value(), context);
|
||||
emitSetLocal(targetSlot, assignStatement.span(), context);
|
||||
return false;
|
||||
}
|
||||
emitGetLocal(targetSlot, assignStatement.span(), context);
|
||||
lowerExpression(assignStatement.value(), context);
|
||||
emitBinaryOperatorInstruction(compoundAssignBinaryOperator(assignStatement.operator()), assignStatement.span(), context);
|
||||
emitSetLocal(targetSlot, assignStatement.span(), context);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean lowerReturnStatement(
|
||||
final PbsAst.ReturnStatement returnStatement,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
if (returnStatement.value() != null) {
|
||||
lowerExpression(returnStatement.value(), context);
|
||||
}
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
IRBackendExecutableFunction.InstructionKind.RET,
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
returnStatement.span()));
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean lowerIfStatement(
|
||||
final PbsAst.IfStatement ifStatement,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
lowerExpression(ifStatement.condition(), context);
|
||||
final var elseLabel = context.nextLabel("if_else");
|
||||
final var endLabel = context.nextLabel("if_end");
|
||||
emitJump(IRBackendExecutableFunction.InstructionKind.JMP_IF_FALSE, elseLabel, ifStatement.span(), context);
|
||||
final var thenTerminated = lowerBlock(ifStatement.thenBlock(), context);
|
||||
if (!thenTerminated) {
|
||||
emitJump(IRBackendExecutableFunction.InstructionKind.JMP, endLabel, ifStatement.span(), context);
|
||||
}
|
||||
emitLabel(elseLabel, ifStatement.span(), context);
|
||||
final var elseTerminated = lowerElseBranch(ifStatement, context);
|
||||
if (!thenTerminated || !elseTerminated) {
|
||||
emitLabel(endLabel, ifStatement.span(), context);
|
||||
}
|
||||
return hasElseBranch(ifStatement) && thenTerminated && elseTerminated;
|
||||
}
|
||||
|
||||
private boolean lowerElseBranch(
|
||||
final PbsAst.IfStatement ifStatement,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
if (ifStatement.elseIf() != null) {
|
||||
return lowerStatement(ifStatement.elseIf(), context);
|
||||
}
|
||||
if (ifStatement.elseBlock() != null) {
|
||||
return lowerBlock(ifStatement.elseBlock(), context);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasElseBranch(final PbsAst.IfStatement ifStatement) {
|
||||
return ifStatement.elseIf() != null || ifStatement.elseBlock() != null;
|
||||
}
|
||||
|
||||
private boolean lowerForStatement(
|
||||
final PbsAst.ForStatement forStatement,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
lowerExpression(forStatement.fromExpression(), context);
|
||||
final var loopStart = context.nextLabel("for_start");
|
||||
final var loopExit = context.nextLabel("for_exit");
|
||||
final var loopContinue = context.nextLabel("for_continue");
|
||||
emitLabel(loopStart, forStatement.span(), context);
|
||||
lowerExpression(forStatement.untilExpression(), context);
|
||||
emitJump(IRBackendExecutableFunction.InstructionKind.JMP_IF_FALSE, loopExit, forStatement.span(), context);
|
||||
context.pushLoop(loopContinue, loopExit);
|
||||
lowerBlock(forStatement.body(), context);
|
||||
context.popLoop();
|
||||
emitLabel(loopContinue, forStatement.span(), context);
|
||||
if (forStatement.stepExpression() != null) {
|
||||
lowerExpression(forStatement.stepExpression(), context);
|
||||
}
|
||||
emitJump(IRBackendExecutableFunction.InstructionKind.JMP, loopStart, forStatement.span(), context);
|
||||
emitLabel(loopExit, forStatement.span(), context);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean lowerWhileStatement(
|
||||
final PbsAst.WhileStatement whileStatement,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
final var loopStart = context.nextLabel("while_start");
|
||||
final var loopExit = context.nextLabel("while_exit");
|
||||
emitLabel(loopStart, whileStatement.span(), context);
|
||||
lowerExpression(whileStatement.condition(), context);
|
||||
emitJump(IRBackendExecutableFunction.InstructionKind.JMP_IF_FALSE, loopExit, whileStatement.span(), context);
|
||||
context.pushLoop(loopStart, loopExit);
|
||||
lowerBlock(whileStatement.body(), context);
|
||||
context.popLoop();
|
||||
emitJump(IRBackendExecutableFunction.InstructionKind.JMP, loopStart, whileStatement.span(), context);
|
||||
emitLabel(loopExit, whileStatement.span(), context);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean lowerBreakStatement(
|
||||
final PbsAst.BreakStatement breakStatement,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
if (context.loopTargets().isEmpty()) {
|
||||
reportUnsupportedLowering("break statement outside loop", breakStatement.span(), context);
|
||||
return true;
|
||||
}
|
||||
emitJump(IRBackendExecutableFunction.InstructionKind.JMP, context.loopTargets().peek().breakLabel(), breakStatement.span(), context);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean lowerContinueStatement(
|
||||
final PbsAst.ContinueStatement continueStatement,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
if (context.loopTargets().isEmpty()) {
|
||||
reportUnsupportedLowering("continue statement outside loop", continueStatement.span(), context);
|
||||
return true;
|
||||
}
|
||||
emitJump(
|
||||
IRBackendExecutableFunction.InstructionKind.JMP,
|
||||
context.loopTargets().peek().continueLabel(),
|
||||
continueStatement.span(),
|
||||
context);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void lowerExpression(
|
||||
final PbsAst.Expression expression,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
if (expression == null) {
|
||||
return;
|
||||
}
|
||||
switch (expression) {
|
||||
case PbsAst.CallExpr callExpr -> lowerCallExpression(callExpr, context);
|
||||
case PbsAst.ApplyExpr applyExpr -> {
|
||||
lowerExpression(applyExpr.callee(), context);
|
||||
lowerExpression(applyExpr.argument(), context);
|
||||
}
|
||||
case PbsAst.BinaryExpr binaryExpr -> {
|
||||
lowerExpression(binaryExpr.left(), context);
|
||||
lowerExpression(binaryExpr.right(), context);
|
||||
emitBinaryOperatorInstruction(binaryExpr.operator(), binaryExpr.span(), context);
|
||||
}
|
||||
case PbsAst.UnaryExpr unaryExpr -> {
|
||||
lowerExpression(unaryExpr.expression(), context);
|
||||
emitUnaryOperatorInstruction(unaryExpr.operator(), unaryExpr.span(), context);
|
||||
}
|
||||
case PbsAst.ElseExpr elseExpr -> {
|
||||
lowerExpression(elseExpr.optionalExpression(), context);
|
||||
lowerExpression(elseExpr.fallbackExpression(), context);
|
||||
}
|
||||
case PbsAst.IfExpr ifExpr -> lowerIfExpression(ifExpr, context);
|
||||
case PbsAst.SwitchExpr switchExpr -> lowerUnsupportedExpression(
|
||||
switchExpr.selector(),
|
||||
"switch expression is not lowerable in executable lowering v1",
|
||||
switchExpr.span(),
|
||||
context);
|
||||
case PbsAst.HandleExpr handleExpr -> lowerUnsupportedExpression(
|
||||
handleExpr.value(),
|
||||
"handle expression is not lowerable in executable lowering v1",
|
||||
handleExpr.span(),
|
||||
context);
|
||||
case PbsAst.AsExpr asExpr -> lowerExpression(asExpr.expression(), context);
|
||||
case PbsAst.MemberExpr memberExpr -> lowerExpression(memberExpr.receiver(), context);
|
||||
case PbsAst.PropagateExpr propagateExpr -> lowerExpression(propagateExpr.expression(), context);
|
||||
case PbsAst.GroupExpr groupExpr -> lowerExpression(groupExpr.expression(), context);
|
||||
case PbsAst.NewExpr newExpr -> lowerExpressionList(newExpr.arguments(), context);
|
||||
case PbsAst.BindExpr bindExpr -> lowerExpression(bindExpr.contextExpression(), context);
|
||||
case PbsAst.SomeExpr someExpr -> lowerExpression(someExpr.value(), context);
|
||||
case PbsAst.OkExpr okExpr -> lowerExpression(okExpr.value(), context);
|
||||
case PbsAst.TupleExpr tupleExpr -> {
|
||||
for (final var item : tupleExpr.items()) {
|
||||
lowerExpression(item.expression(), context);
|
||||
}
|
||||
}
|
||||
case PbsAst.BlockExpr blockExpr -> lowerBlock(blockExpr.block(), context);
|
||||
case PbsAst.IdentifierExpr identifierExpr -> lowerIdentifierExpression(identifierExpr, context);
|
||||
case PbsAst.IntLiteralExpr intLiteralExpr -> lowerIntLiteral(intLiteralExpr, context);
|
||||
case PbsAst.FloatLiteralExpr ignored -> {
|
||||
}
|
||||
case PbsAst.BoundedLiteralExpr ignored -> {
|
||||
}
|
||||
case PbsAst.StringLiteralExpr stringLiteralExpr -> emitPushConst(stringLiteralExpr.value(), stringLiteralExpr.span(), context);
|
||||
case PbsAst.BoolLiteralExpr boolLiteralExpr -> emitPushBool(boolLiteralExpr.value(), boolLiteralExpr.span(), context);
|
||||
case PbsAst.ThisExpr ignored -> {
|
||||
}
|
||||
case PbsAst.NoneExpr ignored -> {
|
||||
}
|
||||
case PbsAst.ErrExpr ignored -> {
|
||||
}
|
||||
case PbsAst.UnitExpr ignored -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void lowerCallExpression(
|
||||
final PbsAst.CallExpr callExpr,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
lowerCallsiteReceiver(callExpr.callee(), context);
|
||||
lowerExpressionList(callExpr.arguments(), context);
|
||||
callsiteEmitter.emitCallsite(callExpr, context);
|
||||
}
|
||||
|
||||
private void lowerExpressionList(
|
||||
final ReadOnlyList<PbsAst.Expression> expressions,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
for (final var expression : expressions) {
|
||||
lowerExpression(expression, context);
|
||||
}
|
||||
}
|
||||
|
||||
private void lowerIfExpression(
|
||||
final PbsAst.IfExpr ifExpr,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
lowerExpression(ifExpr.condition(), context);
|
||||
final var elseLabel = context.nextLabel("ifexpr_else");
|
||||
final var endLabel = context.nextLabel("ifexpr_end");
|
||||
emitJump(IRBackendExecutableFunction.InstructionKind.JMP_IF_FALSE, elseLabel, ifExpr.span(), context);
|
||||
final var thenTerminated = lowerBlock(ifExpr.thenBlock(), context);
|
||||
if (!thenTerminated) {
|
||||
emitJump(IRBackendExecutableFunction.InstructionKind.JMP, endLabel, ifExpr.span(), context);
|
||||
}
|
||||
emitLabel(elseLabel, ifExpr.span(), context);
|
||||
lowerExpression(ifExpr.elseExpression(), context);
|
||||
if (!thenTerminated) {
|
||||
emitLabel(endLabel, ifExpr.span(), context);
|
||||
}
|
||||
}
|
||||
|
||||
private void lowerUnsupportedExpression(
|
||||
final PbsAst.Expression nestedExpression,
|
||||
final String message,
|
||||
final p.studio.compiler.source.Span span,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
lowerExpression(nestedExpression, context);
|
||||
reportUnsupportedLowering(message, span, context);
|
||||
}
|
||||
|
||||
private void lowerIdentifierExpression(
|
||||
final PbsAst.IdentifierExpr identifierExpr,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
final var slot = context.localSlotByNameId().get(context.nameTable().register(identifierExpr.name()));
|
||||
if (slot != null) {
|
||||
emitGetLocal(slot, identifierExpr.span(), context);
|
||||
}
|
||||
}
|
||||
|
||||
private void lowerIntLiteral(
|
||||
final PbsAst.IntLiteralExpr intLiteralExpr,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
if (intLiteralExpr.value() < Integer.MIN_VALUE || intLiteralExpr.value() > Integer.MAX_VALUE) {
|
||||
reportUnsupportedLowering(
|
||||
"int literal exceeds i32 lowering range: " + intLiteralExpr.value(),
|
||||
intLiteralExpr.span(),
|
||||
context);
|
||||
return;
|
||||
}
|
||||
emitPushI32((int) intLiteralExpr.value(), intLiteralExpr.span(), context);
|
||||
}
|
||||
|
||||
private void lowerCallsiteReceiver(
|
||||
final PbsAst.Expression callee,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
if (callee == null) {
|
||||
return;
|
||||
}
|
||||
switch (callee) {
|
||||
case PbsAst.MemberExpr memberExpr -> lowerExpression(memberExpr.receiver(), context);
|
||||
case PbsAst.GroupExpr groupExpr -> lowerCallsiteReceiver(groupExpr.expression(), context);
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void emitJump(
|
||||
final IRBackendExecutableFunction.InstructionKind jumpKind,
|
||||
final String targetLabel,
|
||||
final p.studio.compiler.source.Span span,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
jumpKind,
|
||||
ModuleId.none(),
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
targetLabel,
|
||||
null,
|
||||
null,
|
||||
span));
|
||||
}
|
||||
|
||||
private void emitLabel(
|
||||
final String label,
|
||||
final p.studio.compiler.source.Span span,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
IRBackendExecutableFunction.InstructionKind.LABEL,
|
||||
ModuleId.none(),
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
label,
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
span));
|
||||
}
|
||||
|
||||
private void emitPushI32(
|
||||
final int value,
|
||||
final p.studio.compiler.source.Span span,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
IRBackendExecutableFunction.InstructionKind.PUSH_I32,
|
||||
ModuleId.none(),
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
Integer.toString(value),
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
span));
|
||||
}
|
||||
|
||||
private void emitPushBool(
|
||||
final boolean value,
|
||||
final p.studio.compiler.source.Span span,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
IRBackendExecutableFunction.InstructionKind.PUSH_BOOL,
|
||||
ModuleId.none(),
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
value ? "true" : "false",
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
span));
|
||||
}
|
||||
|
||||
private void emitPushConst(
|
||||
final String value,
|
||||
final p.studio.compiler.source.Span span,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
IRBackendExecutableFunction.InstructionKind.PUSH_CONST,
|
||||
ModuleId.none(),
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
value == null ? "" : value,
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
span));
|
||||
}
|
||||
|
||||
private void emitSetLocal(
|
||||
final int slot,
|
||||
final p.studio.compiler.source.Span span,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
IRBackendExecutableFunction.InstructionKind.SET_LOCAL,
|
||||
ModuleId.none(),
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"",
|
||||
slot,
|
||||
null,
|
||||
span));
|
||||
}
|
||||
|
||||
private String compoundAssignBinaryOperator(final PbsAst.AssignOperator operator) {
|
||||
return switch (operator) {
|
||||
case ADD_ASSIGN -> "+";
|
||||
case SUB_ASSIGN -> "-";
|
||||
case MUL_ASSIGN -> "*";
|
||||
case DIV_ASSIGN -> "/";
|
||||
case MOD_ASSIGN -> "%";
|
||||
case ASSIGN -> "=";
|
||||
};
|
||||
}
|
||||
|
||||
private void emitUnaryOperatorInstruction(
|
||||
final String operator,
|
||||
final p.studio.compiler.source.Span span,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
final var normalized = operator == null ? "" : operator;
|
||||
final IRBackendExecutableFunction.InstructionKind instructionKind = switch (normalized) {
|
||||
case "-", "neg" -> IRBackendExecutableFunction.InstructionKind.NEG;
|
||||
case "!", "not" -> IRBackendExecutableFunction.InstructionKind.NOT;
|
||||
default -> null;
|
||||
};
|
||||
if (instructionKind == null) {
|
||||
reportUnsupportedLowering("unsupported unary operator in executable lowering: " + normalized, span, context);
|
||||
return;
|
||||
}
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
instructionKind,
|
||||
ModuleId.none(),
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
span));
|
||||
}
|
||||
|
||||
private void emitBinaryOperatorInstruction(
|
||||
final String operator,
|
||||
final p.studio.compiler.source.Span span,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
final var normalized = operator == null ? "" : operator;
|
||||
final IRBackendExecutableFunction.InstructionKind instructionKind = switch (normalized) {
|
||||
case "+" -> IRBackendExecutableFunction.InstructionKind.ADD;
|
||||
case "-" -> IRBackendExecutableFunction.InstructionKind.SUB;
|
||||
case "*" -> IRBackendExecutableFunction.InstructionKind.MUL;
|
||||
case "/" -> IRBackendExecutableFunction.InstructionKind.DIV;
|
||||
case "%" -> IRBackendExecutableFunction.InstructionKind.MOD;
|
||||
case "==" -> IRBackendExecutableFunction.InstructionKind.EQ;
|
||||
case "!=" -> IRBackendExecutableFunction.InstructionKind.NEQ;
|
||||
case "<" -> IRBackendExecutableFunction.InstructionKind.LT;
|
||||
case "<=" -> IRBackendExecutableFunction.InstructionKind.LTE;
|
||||
case ">" -> IRBackendExecutableFunction.InstructionKind.GT;
|
||||
case ">=" -> IRBackendExecutableFunction.InstructionKind.GTE;
|
||||
case "&&", "and" -> IRBackendExecutableFunction.InstructionKind.AND;
|
||||
case "||", "or" -> IRBackendExecutableFunction.InstructionKind.OR;
|
||||
default -> null;
|
||||
};
|
||||
if (instructionKind == null) {
|
||||
reportUnsupportedLowering("unsupported binary operator in executable lowering: " + normalized, span, context);
|
||||
return;
|
||||
}
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
instructionKind,
|
||||
ModuleId.none(),
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
span));
|
||||
}
|
||||
|
||||
private void emitGetLocal(
|
||||
final int slot,
|
||||
final p.studio.compiler.source.Span span,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
IRBackendExecutableFunction.InstructionKind.GET_LOCAL,
|
||||
ModuleId.none(),
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"",
|
||||
slot,
|
||||
null,
|
||||
span));
|
||||
}
|
||||
|
||||
private void reportUnsupportedLowering(
|
||||
final String message,
|
||||
final p.studio.compiler.source.Span span,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
context.diagnostics().error(
|
||||
DiagnosticPhase.STATIC_SEMANTICS,
|
||||
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
|
||||
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
|
||||
Map.of(),
|
||||
message,
|
||||
span);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,146 @@
|
||||
package p.studio.compiler.pbs.lowering;
|
||||
|
||||
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.compiler.source.identifiers.CallableId;
|
||||
import p.studio.compiler.source.identifiers.ModuleId;
|
||||
import p.studio.compiler.source.tables.CallableShapeTable;
|
||||
import p.studio.compiler.source.tables.CallableSignatureRef;
|
||||
import p.studio.compiler.source.tables.CallableTable;
|
||||
import p.studio.compiler.source.tables.NameTable;
|
||||
import p.studio.compiler.source.tables.TypeSurfaceTable;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import static p.studio.compiler.pbs.lowering.PbsExecutableLoweringModels.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
final class PbsExecutableCallableRegistryFactory {
|
||||
private final PbsCallableShapeSurfaceService callableShapeSurfaceService = new PbsCallableShapeSurfaceService();
|
||||
|
||||
PbsExecutableCallableRegistry create(
|
||||
final PbsAst.File ast,
|
||||
final ModuleId moduleId,
|
||||
final NameTable nameTable,
|
||||
final ReadOnlyList<PbsFrontendCompiler.ImportedCallableSurface> importedCallables) {
|
||||
final var callableIdTable = new CallableTable();
|
||||
final var callableIdsByNameAndArity = new HashMap<PbsCallableResolutionKey, List<CallableId>>();
|
||||
final var callableIdByDeclaration = new HashMap<PbsLowerableCallable, CallableId>();
|
||||
final var returnSlotsByCallableId = new HashMap<CallableId, Integer>();
|
||||
final var typeSurfaceTable = new TypeSurfaceTable();
|
||||
final var callableShapeTable = new CallableShapeTable();
|
||||
final var localCallables = collectLocalLowerableCallables(ast);
|
||||
|
||||
registerLocalCallables(
|
||||
moduleId,
|
||||
nameTable,
|
||||
callableIdTable,
|
||||
callableIdsByNameAndArity,
|
||||
callableIdByDeclaration,
|
||||
returnSlotsByCallableId,
|
||||
typeSurfaceTable,
|
||||
callableShapeTable,
|
||||
localCallables);
|
||||
registerImportedCallables(
|
||||
nameTable,
|
||||
callableIdTable,
|
||||
callableIdsByNameAndArity,
|
||||
returnSlotsByCallableId,
|
||||
importedCallables);
|
||||
|
||||
final var callableSignatureByCallableId = new HashMap<CallableId, CallableSignatureRef>();
|
||||
final var callableSignatures = new ArrayList<CallableSignatureRef>(callableIdTable.size());
|
||||
for (final var callableId : callableIdTable.identifiers()) {
|
||||
final var signature = callableIdTable.get(callableId);
|
||||
callableSignatureByCallableId.put(callableId, signature);
|
||||
callableSignatures.add(signature);
|
||||
}
|
||||
return new PbsExecutableCallableRegistry(
|
||||
localCallables,
|
||||
callableIdByDeclaration,
|
||||
callableIdsByNameAndArity,
|
||||
callableSignatureByCallableId,
|
||||
returnSlotsByCallableId,
|
||||
ReadOnlyList.wrap(callableSignatures));
|
||||
}
|
||||
|
||||
private ReadOnlyList<PbsLowerableCallable> collectLocalLowerableCallables(final PbsAst.File ast) {
|
||||
final var callables = new ArrayList<PbsLowerableCallable>();
|
||||
for (final var functionDecl : ast.functions()) {
|
||||
callables.add(new PbsLowerableCallable(functionDecl.name(), functionDecl));
|
||||
}
|
||||
for (final var topDecl : ast.topDecls()) {
|
||||
if (!(topDecl instanceof PbsAst.ServiceDecl serviceDecl)) {
|
||||
continue;
|
||||
}
|
||||
for (final var method : serviceDecl.methods()) {
|
||||
callables.add(new PbsLowerableCallable(serviceDecl.name() + "." + method.name(), method));
|
||||
}
|
||||
}
|
||||
return ReadOnlyList.wrap(callables);
|
||||
}
|
||||
|
||||
private void registerLocalCallables(
|
||||
final ModuleId moduleId,
|
||||
final NameTable nameTable,
|
||||
final CallableTable callableIdTable,
|
||||
final Map<PbsCallableResolutionKey, List<CallableId>> callableIdsByNameAndArity,
|
||||
final Map<PbsLowerableCallable, CallableId> callableIdByDeclaration,
|
||||
final Map<CallableId, Integer> returnSlotsByCallableId,
|
||||
final TypeSurfaceTable typeSurfaceTable,
|
||||
final CallableShapeTable callableShapeTable,
|
||||
final ReadOnlyList<PbsLowerableCallable> localCallables) {
|
||||
for (final var declaredCallable : localCallables) {
|
||||
final var declaredFn = declaredCallable.functionDecl();
|
||||
final var callableShapeId = callableShapeSurfaceService.callableShapeId(
|
||||
declaredFn,
|
||||
typeSurfaceTable,
|
||||
callableShapeTable);
|
||||
final var callableId = callableIdTable.register(
|
||||
moduleId,
|
||||
declaredCallable.callableName(),
|
||||
declaredFn.parameters().size(),
|
||||
callableShapeSurfaceService.callableShapeSurface(callableShapeId, typeSurfaceTable, callableShapeTable));
|
||||
callableIdByDeclaration.put(declaredCallable, callableId);
|
||||
callableIdsByNameAndArity
|
||||
.computeIfAbsent(
|
||||
new PbsCallableResolutionKey(nameTable.register(declaredCallable.callableName()), declaredFn.parameters().size()),
|
||||
ignored -> new ArrayList<>())
|
||||
.add(callableId);
|
||||
returnSlotsByCallableId.put(callableId, returnSlotsFor(declaredFn));
|
||||
}
|
||||
}
|
||||
|
||||
private void registerImportedCallables(
|
||||
final NameTable nameTable,
|
||||
final CallableTable callableIdTable,
|
||||
final Map<PbsCallableResolutionKey, List<CallableId>> callableIdsByNameAndArity,
|
||||
final Map<CallableId, Integer> returnSlotsByCallableId,
|
||||
final ReadOnlyList<PbsFrontendCompiler.ImportedCallableSurface> importedCallables) {
|
||||
for (final var importedCallable : importedCallables) {
|
||||
final var callableId = callableIdTable.register(
|
||||
importedCallable.moduleId(),
|
||||
importedCallable.callableName(),
|
||||
importedCallable.arity(),
|
||||
importedCallable.shapeSurface());
|
||||
callableIdsByNameAndArity
|
||||
.computeIfAbsent(
|
||||
new PbsCallableResolutionKey(
|
||||
nameTable.register(importedCallable.callableName()),
|
||||
importedCallable.arity()),
|
||||
ignored -> new ArrayList<>())
|
||||
.add(callableId);
|
||||
returnSlotsByCallableId.put(callableId, importedCallable.returnSlots());
|
||||
}
|
||||
}
|
||||
|
||||
private int returnSlotsFor(final PbsAst.FunctionDecl functionDecl) {
|
||||
return switch (functionDecl.returnKind()) {
|
||||
case INFERRED_UNIT, EXPLICIT_UNIT -> 0;
|
||||
case PLAIN, RESULT -> 1;
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,332 @@
|
||||
package p.studio.compiler.pbs.lowering;
|
||||
|
||||
import p.studio.compiler.models.IRBackendExecutableFunction;
|
||||
import p.studio.compiler.models.IRReservedMetadata;
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticPhase;
|
||||
import p.studio.compiler.source.identifiers.CallableId;
|
||||
import p.studio.compiler.source.identifiers.NameId;
|
||||
import p.studio.compiler.source.tables.NameTable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static p.studio.compiler.pbs.lowering.PbsExecutableLoweringModels.*;
|
||||
|
||||
final class PbsExecutableCallsiteEmitter {
|
||||
|
||||
void emitCallsite(
|
||||
final PbsAst.CallExpr callExpr,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
final var calleeIdentity = resolveCalleeIdentity(callExpr.callee(), context.nameTable());
|
||||
if (calleeIdentity == null) {
|
||||
reportUnsupportedLowering("executable lowering requires resolvable callee identity", callExpr.span(), context);
|
||||
return;
|
||||
}
|
||||
final var candidates = resolveCallsiteCandidates(callExpr, calleeIdentity, context);
|
||||
final var resolvedCategory = resolveCallsiteCategory(candidates, callExpr, context);
|
||||
if (resolvedCategory == null) {
|
||||
return;
|
||||
}
|
||||
switch (resolvedCategory) {
|
||||
case HOST -> emitHostCall(callExpr, context, candidates.hostCandidates().getFirst());
|
||||
case INTRINSIC -> emitIntrinsicCall(callExpr, context, candidates.intrinsicCandidates().getFirst());
|
||||
case CALLABLE -> emitCallableCall(callExpr, context, candidates.callableCandidates());
|
||||
}
|
||||
}
|
||||
|
||||
private PbsResolvedCallsiteCandidates resolveCallsiteCandidates(
|
||||
final PbsAst.CallExpr callExpr,
|
||||
final PbsCalleeIdentity calleeIdentity,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
return new PbsResolvedCallsiteCandidates(
|
||||
resolveCallableCandidates(callExpr, calleeIdentity, context),
|
||||
context.hostByMethodName().getOrDefault(calleeIdentity.memberNameId(), List.of()),
|
||||
resolveIntrinsicCandidates(callExpr, calleeIdentity, context));
|
||||
}
|
||||
|
||||
private List<CallableId> resolveCallableCandidates(
|
||||
final PbsAst.CallExpr callExpr,
|
||||
final PbsCalleeIdentity calleeIdentity,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
final var callableCandidates = new ArrayList<CallableId>();
|
||||
appendCallableCandidates(
|
||||
callableCandidates,
|
||||
context.callableIdsByNameAndArity().get(
|
||||
new PbsCallableResolutionKey(calleeIdentity.primaryCallableNameId(), callExpr.arguments().size())));
|
||||
if (calleeIdentity.secondaryCallableNameId() != null) {
|
||||
appendCallableCandidates(
|
||||
callableCandidates,
|
||||
context.callableIdsByNameAndArity().get(
|
||||
new PbsCallableResolutionKey(calleeIdentity.secondaryCallableNameId(), callExpr.arguments().size())));
|
||||
}
|
||||
return callableCandidates;
|
||||
}
|
||||
|
||||
private void appendCallableCandidates(
|
||||
final List<CallableId> destination,
|
||||
final List<CallableId> candidates) {
|
||||
if (candidates == null) {
|
||||
return;
|
||||
}
|
||||
for (final var candidate : candidates) {
|
||||
if (!destination.contains(candidate)) {
|
||||
destination.add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PbsCallsiteCategory resolveCallsiteCategory(
|
||||
final PbsResolvedCallsiteCandidates candidates,
|
||||
final PbsAst.CallExpr callExpr,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
final var categoryCount = countResolvedCategories(candidates);
|
||||
if (categoryCount == 0) {
|
||||
reportUnsupportedLowering("executable lowering requires resolvable callee identity", callExpr.span(), context);
|
||||
return null;
|
||||
}
|
||||
if (categoryCount > 1) {
|
||||
reportUnsupportedLowering("executable lowering found ambiguous callsite category", callExpr.span(), context);
|
||||
return null;
|
||||
}
|
||||
if (!candidates.hostCandidates().isEmpty()) {
|
||||
if (candidates.hostCandidates().size() > 1) {
|
||||
reportUnsupportedLowering("executable lowering found ambiguous host binding identity", callExpr.span(), context);
|
||||
return null;
|
||||
}
|
||||
return PbsCallsiteCategory.HOST;
|
||||
}
|
||||
if (!candidates.intrinsicCandidates().isEmpty()) {
|
||||
if (candidates.intrinsicCandidates().size() > 1) {
|
||||
reportUnsupportedLowering("executable lowering found ambiguous intrinsic identity", callExpr.span(), context);
|
||||
return null;
|
||||
}
|
||||
return PbsCallsiteCategory.INTRINSIC;
|
||||
}
|
||||
return PbsCallsiteCategory.CALLABLE;
|
||||
}
|
||||
|
||||
private int countResolvedCategories(final PbsResolvedCallsiteCandidates candidates) {
|
||||
var categoryCount = 0;
|
||||
if (!candidates.callableCandidates().isEmpty()) {
|
||||
categoryCount++;
|
||||
}
|
||||
if (!candidates.hostCandidates().isEmpty()) {
|
||||
categoryCount++;
|
||||
}
|
||||
if (!candidates.intrinsicCandidates().isEmpty()) {
|
||||
categoryCount++;
|
||||
}
|
||||
return categoryCount;
|
||||
}
|
||||
|
||||
private void emitHostCall(
|
||||
final PbsAst.CallExpr callExpr,
|
||||
final PbsExecutableLoweringContext context,
|
||||
final IRReservedMetadata.HostMethodBinding host) {
|
||||
final var effectiveArgSlots = callExpr.arguments().size() + implicitReceiverArgSlots(callExpr.callee());
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
IRBackendExecutableFunction.InstructionKind.CALL_HOST,
|
||||
"",
|
||||
new IRBackendExecutableFunction.HostCallMetadata(
|
||||
host.abiModule(),
|
||||
host.abiMethod(),
|
||||
host.abiVersion(),
|
||||
effectiveArgSlots,
|
||||
0),
|
||||
null,
|
||||
callExpr.span()));
|
||||
}
|
||||
|
||||
private void emitIntrinsicCall(
|
||||
final PbsAst.CallExpr callExpr,
|
||||
final PbsExecutableLoweringContext context,
|
||||
final IRReservedMetadata.IntrinsicSurface intrinsic) {
|
||||
final var effectiveArgSlots = intrinsic.argSlots() + implicitReceiverArgSlots(callExpr.callee());
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC,
|
||||
"",
|
||||
null,
|
||||
new IRBackendExecutableFunction.IntrinsicCallMetadata(
|
||||
intrinsic.canonicalName(),
|
||||
intrinsic.canonicalVersion(),
|
||||
context.intrinsicIdTable().register(intrinsic.canonicalName(), intrinsic.canonicalVersion())),
|
||||
effectiveArgSlots,
|
||||
intrinsic.retSlots(),
|
||||
callExpr.span()));
|
||||
}
|
||||
|
||||
private void emitCallableCall(
|
||||
final PbsAst.CallExpr callExpr,
|
||||
final PbsExecutableLoweringContext context,
|
||||
final List<CallableId> callableCandidates) {
|
||||
if (callableCandidates.isEmpty()) {
|
||||
reportUnsupportedLowering("executable lowering requires resolvable callable identity", callExpr.span(), context);
|
||||
return;
|
||||
}
|
||||
if (callableCandidates.size() > 1) {
|
||||
reportUnsupportedLowering("executable lowering found ambiguous callable identity", callExpr.span(), context);
|
||||
return;
|
||||
}
|
||||
final var calleeCallableId = callableCandidates.getFirst();
|
||||
final var calleeSignature = context.callableSignatureByCallableId().get(calleeCallableId);
|
||||
if (calleeSignature == null) {
|
||||
reportUnsupportedLowering("executable lowering resolved callable without signature metadata", callExpr.span(), context);
|
||||
return;
|
||||
}
|
||||
final var effectiveArgSlots = callExpr.arguments().size() + implicitReceiverArgSlots(callExpr.callee());
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
|
||||
calleeSignature.moduleId(),
|
||||
calleeSignature.callableName(),
|
||||
calleeCallableId,
|
||||
null,
|
||||
null,
|
||||
effectiveArgSlots,
|
||||
context.returnSlotsByCallableId().get(calleeCallableId),
|
||||
callExpr.span()));
|
||||
}
|
||||
|
||||
private List<IRReservedMetadata.IntrinsicSurface> resolveIntrinsicCandidates(
|
||||
final PbsAst.CallExpr callExpr,
|
||||
final PbsCalleeIdentity calleeIdentity,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
final var receiverOwner = resolveReceiverOwnerForCallee(callExpr.callee(), context);
|
||||
if (receiverOwner.isBlank()) {
|
||||
return List.of();
|
||||
}
|
||||
final var key = new PbsIntrinsicOwnerMethodKey(receiverOwner, calleeIdentity.memberSourceMethodName());
|
||||
return context.intrinsicByOwnerAndMethod().getOrDefault(key, List.of());
|
||||
}
|
||||
|
||||
private String resolveReceiverOwnerForCallee(
|
||||
final PbsAst.Expression callee,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
return switch (callee) {
|
||||
case PbsAst.MemberExpr memberExpr -> resolveExpressionOwner(memberExpr.receiver(), context);
|
||||
case PbsAst.GroupExpr groupExpr -> resolveReceiverOwnerForCallee(groupExpr.expression(), context);
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
|
||||
private String resolveExpressionOwner(
|
||||
final PbsAst.Expression expression,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
if (expression == null) {
|
||||
return "";
|
||||
}
|
||||
return switch (expression) {
|
||||
case PbsAst.IdentifierExpr identifierExpr ->
|
||||
context.builtinConstOwnerByNameId().getOrDefault(context.nameTable().register(identifierExpr.name()), "");
|
||||
case PbsAst.CallExpr callExpr -> resolveCallReturnOwner(callExpr, context);
|
||||
case PbsAst.MemberExpr memberExpr -> resolveExpressionOwner(memberExpr.receiver(), context);
|
||||
case PbsAst.GroupExpr groupExpr -> resolveExpressionOwner(groupExpr.expression(), context);
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
|
||||
private String resolveCallReturnOwner(
|
||||
final PbsAst.CallExpr callExpr,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
final var calleeIdentity = resolveCalleeIdentity(callExpr.callee(), context.nameTable());
|
||||
if (calleeIdentity == null) {
|
||||
return "";
|
||||
}
|
||||
final var receiverOwner = resolveReceiverOwnerForCallee(callExpr.callee(), context);
|
||||
if (receiverOwner.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
final var key = new PbsIntrinsicOwnerMethodKey(receiverOwner, calleeIdentity.memberSourceMethodName());
|
||||
final var intrinsicCandidates = context.intrinsicByOwnerAndMethod().getOrDefault(key, List.of());
|
||||
if (intrinsicCandidates.size() != 1) {
|
||||
return "";
|
||||
}
|
||||
final var intrinsic = intrinsicCandidates.getFirst();
|
||||
return context.intrinsicReturnOwnerByCanonical().getOrDefault(
|
||||
new PbsIntrinsicCanonicalKey(intrinsic.canonicalName(), intrinsic.canonicalVersion()),
|
||||
"");
|
||||
}
|
||||
|
||||
private PbsCalleeIdentity resolveCalleeIdentity(
|
||||
final PbsAst.Expression callee,
|
||||
final NameTable nameTable) {
|
||||
return switch (callee) {
|
||||
case PbsAst.IdentifierExpr identifierExpr ->
|
||||
new PbsCalleeIdentity(
|
||||
nameTable.register(identifierExpr.name()),
|
||||
null,
|
||||
identifierExpr.name(),
|
||||
nameTable.register(identifierExpr.name()));
|
||||
case PbsAst.MemberExpr memberExpr -> resolveMemberCalleeIdentity(memberExpr, nameTable);
|
||||
case PbsAst.BindExpr bindExpr ->
|
||||
new PbsCalleeIdentity(
|
||||
nameTable.register(bindExpr.functionName()),
|
||||
null,
|
||||
bindExpr.functionName(),
|
||||
nameTable.register(bindExpr.functionName()));
|
||||
case PbsAst.GroupExpr groupExpr -> resolveCalleeIdentity(groupExpr.expression(), nameTable);
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private PbsCalleeIdentity resolveMemberCalleeIdentity(
|
||||
final PbsAst.MemberExpr memberExpr,
|
||||
final NameTable nameTable) {
|
||||
final var memberName = memberExpr.memberName();
|
||||
final var rootName = memberRootName(memberExpr.receiver());
|
||||
final var qualified = rootName == null || rootName.isBlank()
|
||||
? memberName
|
||||
: rootName + "." + memberName;
|
||||
final NameId secondaryNameId;
|
||||
if (qualified.equals(memberName)) {
|
||||
secondaryNameId = null;
|
||||
} else {
|
||||
secondaryNameId = nameTable.register(memberName);
|
||||
}
|
||||
return new PbsCalleeIdentity(
|
||||
nameTable.register(qualified),
|
||||
secondaryNameId,
|
||||
memberName,
|
||||
nameTable.register(memberName));
|
||||
}
|
||||
|
||||
private String memberRootName(final PbsAst.Expression expression) {
|
||||
return switch (expression) {
|
||||
case PbsAst.IdentifierExpr identifierExpr -> identifierExpr.name();
|
||||
case PbsAst.MemberExpr memberExpr -> memberRootName(memberExpr.receiver());
|
||||
case PbsAst.CallExpr callExpr -> memberRootName(callExpr.callee());
|
||||
case PbsAst.GroupExpr groupExpr -> memberRootName(groupExpr.expression());
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private int implicitReceiverArgSlots(final PbsAst.Expression callee) {
|
||||
if (!(callee instanceof PbsAst.MemberExpr memberExpr)) {
|
||||
return 0;
|
||||
}
|
||||
return receiverProducesRuntimeValue(memberExpr.receiver()) ? 1 : 0;
|
||||
}
|
||||
|
||||
private boolean receiverProducesRuntimeValue(final PbsAst.Expression receiver) {
|
||||
return switch (receiver) {
|
||||
case PbsAst.CallExpr ignored -> true;
|
||||
case PbsAst.GroupExpr groupExpr -> receiverProducesRuntimeValue(groupExpr.expression());
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private void reportUnsupportedLowering(
|
||||
final String message,
|
||||
final p.studio.compiler.source.Span span,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
context.diagnostics().error(
|
||||
DiagnosticPhase.STATIC_SEMANTICS,
|
||||
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
|
||||
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
|
||||
Map.of(),
|
||||
message,
|
||||
span);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,129 @@
|
||||
package p.studio.compiler.pbs.lowering;
|
||||
|
||||
import p.studio.compiler.models.IRBackendExecutableFunction;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.compiler.source.identifiers.NameId;
|
||||
import p.studio.compiler.source.tables.IntrinsicTable;
|
||||
import p.studio.compiler.source.tables.NameTable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static p.studio.compiler.pbs.lowering.PbsExecutableLoweringModels.*;
|
||||
|
||||
final class PbsExecutableLoweringContext {
|
||||
private final DiagnosticSink diagnostics;
|
||||
private final NameTable nameTable;
|
||||
private final PbsExecutableMetadataIndex metadataIndex;
|
||||
private final PbsExecutableCallableRegistry callableRegistry;
|
||||
private final IntrinsicTable intrinsicIdTable;
|
||||
private final Map<NameId, Integer> localSlotByNameId;
|
||||
private final ArrayList<IRBackendExecutableFunction.Instruction> instructions = new ArrayList<>();
|
||||
private final ArrayDeque<PbsLoopTargets> loopTargets = new ArrayDeque<>();
|
||||
private int nextLabelId = 0;
|
||||
private int nextLocalSlot;
|
||||
|
||||
PbsExecutableLoweringContext(
|
||||
final DiagnosticSink diagnostics,
|
||||
final NameTable nameTable,
|
||||
final PbsExecutableMetadataIndex metadataIndex,
|
||||
final PbsExecutableCallableRegistry callableRegistry,
|
||||
final IntrinsicTable intrinsicIdTable,
|
||||
final Map<NameId, Integer> localSlotByNameId,
|
||||
final int initialLocalSlot) {
|
||||
this.diagnostics = diagnostics;
|
||||
this.nameTable = nameTable;
|
||||
this.metadataIndex = metadataIndex;
|
||||
this.callableRegistry = callableRegistry;
|
||||
this.intrinsicIdTable = intrinsicIdTable;
|
||||
this.localSlotByNameId = localSlotByNameId == null ? new HashMap<>() : localSlotByNameId;
|
||||
this.nextLocalSlot = Math.max(0, initialLocalSlot);
|
||||
}
|
||||
|
||||
DiagnosticSink diagnostics() {
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
NameTable nameTable() {
|
||||
return nameTable;
|
||||
}
|
||||
|
||||
Map<NameId, List<p.studio.compiler.models.IRReservedMetadata.HostMethodBinding>> hostByMethodName() {
|
||||
return metadataIndex.hostByMethodName();
|
||||
}
|
||||
|
||||
Map<NameId, String> builtinConstOwnerByNameId() {
|
||||
return metadataIndex.builtinConstOwnerByNameId();
|
||||
}
|
||||
|
||||
Map<PbsIntrinsicOwnerMethodKey, List<p.studio.compiler.models.IRReservedMetadata.IntrinsicSurface>> intrinsicByOwnerAndMethod() {
|
||||
return metadataIndex.intrinsicByOwnerAndMethod();
|
||||
}
|
||||
|
||||
Map<PbsIntrinsicCanonicalKey, String> intrinsicReturnOwnerByCanonical() {
|
||||
return metadataIndex.intrinsicReturnOwnerByCanonical();
|
||||
}
|
||||
|
||||
Map<PbsCallableResolutionKey, List<p.studio.compiler.source.identifiers.CallableId>> callableIdsByNameAndArity() {
|
||||
return callableRegistry.callableIdsByNameAndArity();
|
||||
}
|
||||
|
||||
Map<p.studio.compiler.source.identifiers.CallableId, p.studio.compiler.source.tables.CallableSignatureRef> callableSignatureByCallableId() {
|
||||
return callableRegistry.callableSignatureByCallableId();
|
||||
}
|
||||
|
||||
Map<p.studio.compiler.source.identifiers.CallableId, Integer> returnSlotsByCallableId() {
|
||||
return callableRegistry.returnSlotsByCallableId();
|
||||
}
|
||||
|
||||
IntrinsicTable intrinsicIdTable() {
|
||||
return intrinsicIdTable;
|
||||
}
|
||||
|
||||
Map<NameId, Integer> localSlotByNameId() {
|
||||
return localSlotByNameId;
|
||||
}
|
||||
|
||||
Integer resolveLocalSlot(final String localName) {
|
||||
if (localName == null || localName.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return localSlotByNameId.get(nameTable.register(localName));
|
||||
}
|
||||
|
||||
int declareLocalSlot(final String localName) {
|
||||
final var nameId = nameTable.register(localName);
|
||||
final var existing = localSlotByNameId.get(nameId);
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
final var slot = nextLocalSlot++;
|
||||
localSlotByNameId.put(nameId, slot);
|
||||
return slot;
|
||||
}
|
||||
|
||||
int nextLocalSlot() {
|
||||
return nextLocalSlot;
|
||||
}
|
||||
|
||||
ArrayList<IRBackendExecutableFunction.Instruction> instructions() {
|
||||
return instructions;
|
||||
}
|
||||
|
||||
ArrayDeque<PbsLoopTargets> loopTargets() {
|
||||
return loopTargets;
|
||||
}
|
||||
|
||||
void pushLoop(
|
||||
final String continueLabel,
|
||||
final String breakLabel) {
|
||||
loopTargets.push(new PbsLoopTargets(continueLabel, breakLabel));
|
||||
}
|
||||
|
||||
void popLoop() {
|
||||
loopTargets.pop();
|
||||
}
|
||||
|
||||
String nextLabel(final String prefix) {
|
||||
return "__" + prefix + "_" + nextLabelId++;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
package p.studio.compiler.pbs.lowering;
|
||||
|
||||
import p.studio.compiler.models.IRBackendExecutableFunction;
|
||||
import p.studio.compiler.models.IRReservedMetadata;
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.compiler.source.identifiers.CallableId;
|
||||
import p.studio.compiler.source.identifiers.NameId;
|
||||
import p.studio.compiler.source.tables.CallableSignatureRef;
|
||||
import p.studio.compiler.source.tables.IntrinsicReference;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PbsExecutableLoweringModels {
|
||||
|
||||
public record PbsExecutableLoweringResult(
|
||||
ReadOnlyList<IRBackendExecutableFunction> executableFunctions,
|
||||
ReadOnlyList<CallableSignatureRef> callableSignatures,
|
||||
ReadOnlyList<IntrinsicReference> intrinsicPool) {
|
||||
}
|
||||
|
||||
record PbsLowerableCallable(
|
||||
String callableName,
|
||||
PbsAst.FunctionDecl functionDecl) {
|
||||
}
|
||||
|
||||
record PbsCallableResolutionKey(
|
||||
NameId callableNameId,
|
||||
int arity) {
|
||||
}
|
||||
|
||||
record PbsIntrinsicOwnerMethodKey(
|
||||
String ownerCanonicalName,
|
||||
String sourceMethodName) {
|
||||
}
|
||||
|
||||
record PbsIntrinsicCanonicalKey(
|
||||
String canonicalName,
|
||||
long canonicalVersion) {
|
||||
}
|
||||
|
||||
record PbsExecutableMetadataIndex(
|
||||
Map<NameId, List<IRReservedMetadata.HostMethodBinding>> hostByMethodName,
|
||||
Map<NameId, String> builtinConstOwnerByNameId,
|
||||
Map<PbsIntrinsicOwnerMethodKey, List<IRReservedMetadata.IntrinsicSurface>> intrinsicByOwnerAndMethod,
|
||||
Map<PbsIntrinsicCanonicalKey, String> intrinsicReturnOwnerByCanonical) {
|
||||
}
|
||||
|
||||
record PbsExecutableCallableRegistry(
|
||||
ReadOnlyList<PbsLowerableCallable> localCallables,
|
||||
Map<PbsLowerableCallable, CallableId> callableIdByDeclaration,
|
||||
Map<PbsCallableResolutionKey, List<CallableId>> callableIdsByNameAndArity,
|
||||
Map<CallableId, CallableSignatureRef> callableSignatureByCallableId,
|
||||
Map<CallableId, Integer> returnSlotsByCallableId,
|
||||
ReadOnlyList<CallableSignatureRef> callableSignatures) {
|
||||
}
|
||||
|
||||
record PbsLoweredExecutableBody(
|
||||
ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions,
|
||||
int localSlots,
|
||||
int maxStackSlots) {
|
||||
}
|
||||
|
||||
record PbsCalleeIdentity(
|
||||
NameId primaryCallableNameId,
|
||||
NameId secondaryCallableNameId,
|
||||
String memberSourceMethodName,
|
||||
NameId memberNameId) {
|
||||
}
|
||||
|
||||
record PbsResolvedCallsiteCandidates(
|
||||
List<CallableId> callableCandidates,
|
||||
List<IRReservedMetadata.HostMethodBinding> hostCandidates,
|
||||
List<IRReservedMetadata.IntrinsicSurface> intrinsicCandidates) {
|
||||
}
|
||||
|
||||
record PbsLoopTargets(
|
||||
String continueLabel,
|
||||
String breakLabel) {
|
||||
}
|
||||
|
||||
enum PbsCallsiteCategory {
|
||||
CALLABLE,
|
||||
HOST,
|
||||
INTRINSIC
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,126 @@
|
||||
package p.studio.compiler.pbs.lowering;
|
||||
|
||||
import p.studio.compiler.models.IRBackendExecutableFunction;
|
||||
import p.studio.compiler.models.IRReservedMetadata;
|
||||
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.compiler.source.identifiers.FileId;
|
||||
import p.studio.compiler.source.identifiers.ModuleId;
|
||||
import p.studio.compiler.source.tables.IntrinsicReference;
|
||||
import p.studio.compiler.source.tables.IntrinsicTable;
|
||||
import p.studio.compiler.source.tables.NameTable;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static p.studio.compiler.pbs.lowering.PbsExecutableLoweringModels.*;
|
||||
|
||||
public final class PbsExecutableLoweringService {
|
||||
private final PbsCallableShapeSurfaceService callableShapeSurfaceService = new PbsCallableShapeSurfaceService();
|
||||
private final PbsExecutableMetadataIndexFactory metadataIndexFactory = new PbsExecutableMetadataIndexFactory();
|
||||
private final PbsExecutableCallableRegistryFactory callableRegistryFactory = new PbsExecutableCallableRegistryFactory();
|
||||
private final PbsExecutableBodyLowerer bodyLowerer = new PbsExecutableBodyLowerer();
|
||||
|
||||
public PbsExecutableLoweringResult lower(
|
||||
final FileId fileId,
|
||||
final PbsAst.File ast,
|
||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||
final ModuleId moduleId,
|
||||
final IRReservedMetadata reservedMetadata,
|
||||
final NameTable nameTable,
|
||||
final DiagnosticSink diagnostics,
|
||||
final ReadOnlyList<PbsFrontendCompiler.ImportedCallableSurface> importedCallables) {
|
||||
final var normalizedModuleId = moduleId == null ? ModuleId.none() : moduleId;
|
||||
final var metadataIndex = metadataIndexFactory.create(
|
||||
ast,
|
||||
supplementalTopDecls,
|
||||
reservedMetadata,
|
||||
nameTable,
|
||||
diagnostics);
|
||||
final var callableRegistry = callableRegistryFactory.create(
|
||||
ast,
|
||||
normalizedModuleId,
|
||||
nameTable,
|
||||
importedCallables);
|
||||
final var intrinsicIdTable = new IntrinsicTable();
|
||||
final var executableFunctions = new ArrayList<IRBackendExecutableFunction>(callableRegistry.localCallables().size());
|
||||
|
||||
for (final var callable : callableRegistry.localCallables()) {
|
||||
final var loweredFunction = lowerExecutableFunction(
|
||||
fileId,
|
||||
normalizedModuleId,
|
||||
callable,
|
||||
callableRegistry,
|
||||
diagnostics,
|
||||
nameTable,
|
||||
metadataIndex,
|
||||
intrinsicIdTable);
|
||||
if (loweredFunction != null) {
|
||||
executableFunctions.add(loweredFunction);
|
||||
}
|
||||
}
|
||||
|
||||
final var intrinsicPool = new ArrayList<IntrinsicReference>(intrinsicIdTable.size());
|
||||
for (final var intrinsicId : intrinsicIdTable.identifiers()) {
|
||||
intrinsicPool.add(intrinsicIdTable.get(intrinsicId));
|
||||
}
|
||||
return new PbsExecutableLoweringResult(
|
||||
ReadOnlyList.wrap(executableFunctions),
|
||||
callableRegistry.callableSignatures(),
|
||||
ReadOnlyList.wrap(intrinsicPool));
|
||||
}
|
||||
|
||||
public String callableShapeSurfaceOf(final PbsAst.FunctionDecl functionDecl) {
|
||||
return callableShapeSurfaceService.callableShapeSurfaceOf(functionDecl);
|
||||
}
|
||||
|
||||
private IRBackendExecutableFunction lowerExecutableFunction(
|
||||
final FileId fileId,
|
||||
final ModuleId moduleId,
|
||||
final PbsLowerableCallable callable,
|
||||
final PbsExecutableCallableRegistry callableRegistry,
|
||||
final DiagnosticSink diagnostics,
|
||||
final NameTable nameTable,
|
||||
final PbsExecutableMetadataIndex metadataIndex,
|
||||
final IntrinsicTable intrinsicIdTable) {
|
||||
final var functionDecl = callable.functionDecl();
|
||||
final var functionCallableId = callableRegistry.callableIdByDeclaration().get(callable);
|
||||
if (functionCallableId == null) {
|
||||
return null;
|
||||
}
|
||||
final var loweredBody = bodyLowerer.lower(
|
||||
functionDecl,
|
||||
diagnostics,
|
||||
nameTable,
|
||||
metadataIndex,
|
||||
callableRegistry,
|
||||
intrinsicIdTable);
|
||||
if (loweredBody == null) {
|
||||
return null;
|
||||
}
|
||||
return new IRBackendExecutableFunction(
|
||||
fileId,
|
||||
moduleId,
|
||||
callable.callableName(),
|
||||
functionCallableId,
|
||||
safeToInt(functionDecl.span().getStart()),
|
||||
safeToInt(functionDecl.span().getEnd()),
|
||||
functionDecl.parameters().size(),
|
||||
loweredBody.localSlots(),
|
||||
callableRegistry.returnSlotsByCallableId().getOrDefault(functionCallableId, 0),
|
||||
loweredBody.maxStackSlots(),
|
||||
loweredBody.instructions(),
|
||||
functionDecl.span());
|
||||
}
|
||||
|
||||
private int safeToInt(final long value) {
|
||||
if (value > Integer.MAX_VALUE) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
if (value < Integer.MIN_VALUE) {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
return (int) value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,185 @@
|
||||
package p.studio.compiler.pbs.lowering;
|
||||
|
||||
import p.studio.compiler.models.IRReservedMetadata;
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.compiler.pbs.semantics.PbsSemanticsErrors;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticPhase;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.compiler.source.identifiers.NameId;
|
||||
import p.studio.compiler.source.tables.NameTable;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import static p.studio.compiler.pbs.lowering.PbsExecutableLoweringModels.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
final class PbsExecutableMetadataIndexFactory {
|
||||
|
||||
PbsExecutableMetadataIndex create(
|
||||
final PbsAst.File ast,
|
||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||
final IRReservedMetadata reservedMetadata,
|
||||
final NameTable nameTable,
|
||||
final DiagnosticSink diagnostics) {
|
||||
final var hostByMethodName = indexHostBindingsByMethodName(reservedMetadata, nameTable);
|
||||
final var builtinCanonicalBySourceType = indexBuiltinCanonicalBySourceType(reservedMetadata);
|
||||
final var builtinConstOwnerByNameId = indexBuiltinConstOwners(reservedMetadata, nameTable);
|
||||
final var builtinSignatureByOwnerAndMethod = indexBuiltinSignatures(
|
||||
ast,
|
||||
supplementalTopDecls,
|
||||
builtinCanonicalBySourceType);
|
||||
final var intrinsicByOwnerAndMethod = new HashMap<PbsIntrinsicOwnerMethodKey, List<IRReservedMetadata.IntrinsicSurface>>();
|
||||
final var intrinsicReturnOwnerByCanonical = new HashMap<PbsIntrinsicCanonicalKey, String>();
|
||||
indexIntrinsicMetadata(
|
||||
reservedMetadata,
|
||||
builtinCanonicalBySourceType,
|
||||
builtinSignatureByOwnerAndMethod,
|
||||
intrinsicByOwnerAndMethod,
|
||||
intrinsicReturnOwnerByCanonical,
|
||||
diagnostics);
|
||||
return new PbsExecutableMetadataIndex(
|
||||
hostByMethodName,
|
||||
builtinConstOwnerByNameId,
|
||||
intrinsicByOwnerAndMethod,
|
||||
intrinsicReturnOwnerByCanonical);
|
||||
}
|
||||
|
||||
private Map<NameId, List<IRReservedMetadata.HostMethodBinding>> indexHostBindingsByMethodName(
|
||||
final IRReservedMetadata reservedMetadata,
|
||||
final NameTable nameTable) {
|
||||
final var hostByMethodName = new HashMap<NameId, List<IRReservedMetadata.HostMethodBinding>>();
|
||||
for (final var hostBinding : reservedMetadata.hostMethodBindings()) {
|
||||
hostByMethodName
|
||||
.computeIfAbsent(nameTable.register(hostBinding.sourceMethodName()), ignored -> new ArrayList<>())
|
||||
.add(hostBinding);
|
||||
}
|
||||
return hostByMethodName;
|
||||
}
|
||||
|
||||
private Map<String, String> indexBuiltinCanonicalBySourceType(final IRReservedMetadata reservedMetadata) {
|
||||
final var builtinCanonicalBySourceType = new HashMap<String, String>();
|
||||
for (final var builtinType : reservedMetadata.builtinTypeSurfaces()) {
|
||||
builtinCanonicalBySourceType.putIfAbsent(builtinType.sourceTypeName(), builtinType.canonicalTypeName());
|
||||
}
|
||||
return builtinCanonicalBySourceType;
|
||||
}
|
||||
|
||||
private Map<NameId, String> indexBuiltinConstOwners(
|
||||
final IRReservedMetadata reservedMetadata,
|
||||
final NameTable nameTable) {
|
||||
final var builtinConstOwnerByNameId = new HashMap<NameId, String>();
|
||||
for (final var builtinConst : reservedMetadata.builtinConstSurfaces()) {
|
||||
if (builtinConst.sourceConstName().isBlank() || builtinConst.canonicalTarget().isBlank()) {
|
||||
continue;
|
||||
}
|
||||
builtinConstOwnerByNameId.put(nameTable.register(builtinConst.sourceConstName()), builtinConst.canonicalTarget());
|
||||
}
|
||||
return builtinConstOwnerByNameId;
|
||||
}
|
||||
|
||||
private Map<PbsIntrinsicOwnerMethodKey, PbsAst.FunctionSignature> indexBuiltinSignatures(
|
||||
final PbsAst.File ast,
|
||||
final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
|
||||
final Map<String, String> builtinCanonicalBySourceType) {
|
||||
final var builtinSignatureByOwnerAndMethod = new HashMap<PbsIntrinsicOwnerMethodKey, PbsAst.FunctionSignature>();
|
||||
final var topDeclsForIntrinsicInference = new ArrayList<PbsAst.TopDecl>(ast.topDecls().size() + supplementalTopDecls.size());
|
||||
topDeclsForIntrinsicInference.addAll(ast.topDecls().asList());
|
||||
topDeclsForIntrinsicInference.addAll(supplementalTopDecls.asList());
|
||||
for (final var topDecl : topDeclsForIntrinsicInference) {
|
||||
if (!(topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl)) {
|
||||
continue;
|
||||
}
|
||||
final var ownerCanonical = builtinCanonicalBySourceType.getOrDefault(builtinTypeDecl.name(), "");
|
||||
if (ownerCanonical.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
for (final var signature : builtinTypeDecl.signatures()) {
|
||||
builtinSignatureByOwnerAndMethod.putIfAbsent(
|
||||
new PbsIntrinsicOwnerMethodKey(ownerCanonical, signature.name()),
|
||||
signature);
|
||||
}
|
||||
}
|
||||
return builtinSignatureByOwnerAndMethod;
|
||||
}
|
||||
|
||||
private void indexIntrinsicMetadata(
|
||||
final IRReservedMetadata reservedMetadata,
|
||||
final Map<String, String> builtinCanonicalBySourceType,
|
||||
final Map<PbsIntrinsicOwnerMethodKey, PbsAst.FunctionSignature> builtinSignatureByOwnerAndMethod,
|
||||
final Map<PbsIntrinsicOwnerMethodKey, List<IRReservedMetadata.IntrinsicSurface>> intrinsicByOwnerAndMethod,
|
||||
final Map<PbsIntrinsicCanonicalKey, String> intrinsicReturnOwnerByCanonical,
|
||||
final DiagnosticSink diagnostics) {
|
||||
for (final var builtinType : reservedMetadata.builtinTypeSurfaces()) {
|
||||
final var ownerCanonical = builtinType.canonicalTypeName();
|
||||
for (final var intrinsicSurface : builtinType.intrinsics()) {
|
||||
intrinsicByOwnerAndMethod
|
||||
.computeIfAbsent(
|
||||
new PbsIntrinsicOwnerMethodKey(ownerCanonical, intrinsicSurface.sourceMethodName()),
|
||||
ignored -> new ArrayList<>())
|
||||
.add(intrinsicSurface);
|
||||
|
||||
final var signature = builtinSignatureByOwnerAndMethod.get(
|
||||
new PbsIntrinsicOwnerMethodKey(ownerCanonical, intrinsicSurface.sourceMethodName()));
|
||||
if (signature == null) {
|
||||
continue;
|
||||
}
|
||||
final var returnOwner = inferIntrinsicReturnOwner(
|
||||
signature,
|
||||
ownerCanonical,
|
||||
builtinCanonicalBySourceType);
|
||||
if (returnOwner.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
final var key = new PbsIntrinsicCanonicalKey(
|
||||
intrinsicSurface.canonicalName(),
|
||||
intrinsicSurface.canonicalVersion());
|
||||
final var previous = intrinsicReturnOwnerByCanonical.putIfAbsent(key, returnOwner);
|
||||
if (previous != null && !previous.equals(returnOwner)) {
|
||||
diagnostics.error(
|
||||
DiagnosticPhase.STATIC_SEMANTICS,
|
||||
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
|
||||
PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name(),
|
||||
Map.of(),
|
||||
"inconsistent intrinsic return owner metadata for %s@%d: %s vs %s".formatted(
|
||||
intrinsicSurface.canonicalName(),
|
||||
intrinsicSurface.canonicalVersion(),
|
||||
previous,
|
||||
returnOwner),
|
||||
intrinsicSurface.span());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String inferIntrinsicReturnOwner(
|
||||
final PbsAst.FunctionSignature signature,
|
||||
final String intrinsicOwnerCanonical,
|
||||
final Map<String, String> builtinCanonicalBySourceType) {
|
||||
if (signature.returnKind() == PbsAst.ReturnKind.INFERRED_UNIT
|
||||
|| signature.returnKind() == PbsAst.ReturnKind.EXPLICIT_UNIT) {
|
||||
return "";
|
||||
}
|
||||
final var returnType = unwrapGroup(signature.returnType());
|
||||
if (returnType == null) {
|
||||
return "";
|
||||
}
|
||||
return switch (returnType.kind()) {
|
||||
case SELF -> intrinsicOwnerCanonical == null ? "" : intrinsicOwnerCanonical;
|
||||
case SIMPLE -> builtinCanonicalBySourceType.getOrDefault(returnType.name(), "");
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,162 @@
|
||||
package p.studio.compiler.pbs.lowering;
|
||||
|
||||
import p.studio.compiler.models.IRBackendExecutableFunction;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
final class PbsExecutableStackAnalyzer {
|
||||
|
||||
int analyzeMaxStackSlots(
|
||||
final ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions,
|
||||
final int returnSlots) {
|
||||
if (instructions.isEmpty()) {
|
||||
return Math.max(1, returnSlots);
|
||||
}
|
||||
|
||||
final var labelToIndex = new HashMap<String, Integer>();
|
||||
for (var i = 0; i < instructions.size(); i++) {
|
||||
final var instruction = instructions.get(i);
|
||||
if (instruction.kind() == IRBackendExecutableFunction.InstructionKind.LABEL) {
|
||||
if (labelToIndex.putIfAbsent(instruction.label(), i) != null) {
|
||||
throw new ExecutableLoweringAnalysisException("duplicate label during stack analysis: " + instruction.label());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final var incomingHeightByInstruction = new HashMap<Integer, Integer>();
|
||||
final var worklist = new ArrayDeque<Integer>();
|
||||
final var visited = new HashSet<Integer>();
|
||||
incomingHeightByInstruction.put(0, 0);
|
||||
worklist.add(0);
|
||||
|
||||
var maxHeight = 0;
|
||||
while (!worklist.isEmpty()) {
|
||||
final var index = worklist.removeFirst();
|
||||
visited.add(index);
|
||||
final var instruction = instructions.get(index);
|
||||
var outHeight = incomingHeightByInstruction.get(index);
|
||||
|
||||
switch (instruction.kind()) {
|
||||
case PUSH_I32, PUSH_BOOL, PUSH_CONST, GET_LOCAL -> outHeight += 1;
|
||||
case POP, SET_LOCAL -> {
|
||||
if (outHeight <= 0) {
|
||||
throw new ExecutableLoweringAnalysisException(
|
||||
"stack underflow at " + instruction.kind().name().toLowerCase() + ": need=1 have=" + outHeight);
|
||||
}
|
||||
outHeight -= 1;
|
||||
}
|
||||
case ADD, SUB, MUL, DIV, MOD, EQ, NEQ, LT, LTE, GT, GTE, AND, OR -> {
|
||||
if (outHeight < 2) {
|
||||
throw new ExecutableLoweringAnalysisException(
|
||||
"stack underflow at " + instruction.kind().name().toLowerCase() + ": need=2 have=" + outHeight);
|
||||
}
|
||||
outHeight -= 1;
|
||||
}
|
||||
case NOT, NEG -> {
|
||||
if (outHeight <= 0) {
|
||||
throw new ExecutableLoweringAnalysisException(
|
||||
"stack underflow at " + instruction.kind().name().toLowerCase() + ": need=1 have=" + outHeight);
|
||||
}
|
||||
}
|
||||
case CALL_FUNC -> {
|
||||
final var argSlots = instruction.expectedArgSlots() == null ? 0 : instruction.expectedArgSlots();
|
||||
final var retSlots = instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots();
|
||||
if (outHeight < argSlots) {
|
||||
throw new ExecutableLoweringAnalysisException(
|
||||
"stack underflow at call_func: need=%d have=%d".formatted(argSlots, outHeight));
|
||||
}
|
||||
outHeight = outHeight - argSlots + retSlots;
|
||||
}
|
||||
case CALL_HOST -> {
|
||||
final var argSlots = instruction.hostCall() == null ? 0 : instruction.hostCall().argSlots();
|
||||
final var retSlots = instruction.hostCall() == null ? 0 : instruction.hostCall().retSlots();
|
||||
if (outHeight < argSlots) {
|
||||
throw new ExecutableLoweringAnalysisException(
|
||||
"stack underflow at call_host: need=%d have=%d".formatted(argSlots, outHeight));
|
||||
}
|
||||
outHeight = outHeight - argSlots + retSlots;
|
||||
}
|
||||
case CALL_INTRINSIC -> {
|
||||
final var argSlots = instruction.expectedArgSlots() == null ? 0 : instruction.expectedArgSlots();
|
||||
final var retSlots = instruction.expectedRetSlots() == null ? 0 : instruction.expectedRetSlots();
|
||||
if (outHeight < argSlots) {
|
||||
throw new ExecutableLoweringAnalysisException(
|
||||
"stack underflow at call_intrinsic: need=%d have=%d".formatted(argSlots, outHeight));
|
||||
}
|
||||
outHeight = outHeight - argSlots + retSlots;
|
||||
}
|
||||
case HALT, LABEL, JMP, RET -> {
|
||||
}
|
||||
case JMP_IF_TRUE, JMP_IF_FALSE -> {
|
||||
if (outHeight <= 0) {
|
||||
throw new ExecutableLoweringAnalysisException("conditional jump requires stack height > 0");
|
||||
}
|
||||
outHeight -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
maxHeight = Math.max(maxHeight, outHeight);
|
||||
if (instruction.kind() == IRBackendExecutableFunction.InstructionKind.RET
|
||||
|| instruction.kind() == IRBackendExecutableFunction.InstructionKind.HALT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final var successors = new ArrayList<Integer>(2);
|
||||
if (instruction.kind() == IRBackendExecutableFunction.InstructionKind.JMP
|
||||
|| instruction.kind() == IRBackendExecutableFunction.InstructionKind.JMP_IF_TRUE
|
||||
|| instruction.kind() == IRBackendExecutableFunction.InstructionKind.JMP_IF_FALSE) {
|
||||
final var targetIndex = labelToIndex.get(instruction.targetLabel());
|
||||
if (targetIndex == null) {
|
||||
throw new ExecutableLoweringAnalysisException("missing jump target label: " + instruction.targetLabel());
|
||||
}
|
||||
successors.add(targetIndex);
|
||||
if (instruction.kind() == IRBackendExecutableFunction.InstructionKind.JMP) {
|
||||
mergeIncomingHeights(incomingHeightByInstruction, worklist, targetIndex, outHeight);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
final var nextIndex = index + 1;
|
||||
if (nextIndex >= instructions.size()) {
|
||||
throw new ExecutableLoweringAnalysisException("reachable path ends without terminal instruction");
|
||||
}
|
||||
successors.add(nextIndex);
|
||||
for (final var successor : successors) {
|
||||
mergeIncomingHeights(incomingHeightByInstruction, worklist, successor, outHeight);
|
||||
}
|
||||
}
|
||||
|
||||
if (!visited.contains(instructions.size() - 1)) {
|
||||
throw new ExecutableLoweringAnalysisException("entrypoint does not reach function tail");
|
||||
}
|
||||
return Math.max(1, Math.max(maxHeight, returnSlots));
|
||||
}
|
||||
|
||||
private void mergeIncomingHeights(
|
||||
final Map<Integer, Integer> incomingHeightByInstruction,
|
||||
final ArrayDeque<Integer> worklist,
|
||||
final int targetIndex,
|
||||
final int newHeight) {
|
||||
final var existingHeight = incomingHeightByInstruction.get(targetIndex);
|
||||
if (existingHeight == null) {
|
||||
incomingHeightByInstruction.put(targetIndex, newHeight);
|
||||
worklist.add(targetIndex);
|
||||
return;
|
||||
}
|
||||
if (existingHeight != newHeight) {
|
||||
throw new ExecutableLoweringAnalysisException(
|
||||
"stack mismatch at join: existing=%d new=%d".formatted(existingHeight, newHeight));
|
||||
}
|
||||
}
|
||||
|
||||
static final class ExecutableLoweringAnalysisException extends RuntimeException {
|
||||
private ExecutableLoweringAnalysisException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user