refactoring and reducing complexity

This commit is contained in:
bQUARKz 2026-03-10 08:35:53 +00:00
parent 2288ed75f9
commit 2b4c9488a7
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
11 changed files with 2039 additions and 1509 deletions

View File

@ -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) {

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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;
};
}
}

View File

@ -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);
}
}

View File

@ -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++;
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}
}