From 2b4c9488a7843847ea6c3a273765c80bd866f377 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 10 Mar 2026 08:35:53 +0000 Subject: [PATCH] refactoring and reducing complexity --- .../compiler/pbs/PbsFrontendCompiler.java | 1483 +---------------- .../pbs/PbsHostAdmissionValidator.java | 149 +- .../PbsCallableShapeSurfaceService.java | 93 ++ .../lowering/PbsExecutableBodyLowerer.java | 655 ++++++++ .../PbsExecutableCallableRegistryFactory.java | 146 ++ .../PbsExecutableCallsiteEmitter.java | 332 ++++ .../PbsExecutableLoweringContext.java | 129 ++ .../lowering/PbsExecutableLoweringModels.java | 88 + .../PbsExecutableLoweringService.java | 126 ++ .../PbsExecutableMetadataIndexFactory.java | 185 ++ .../lowering/PbsExecutableStackAnalyzer.java | 162 ++ 11 files changed, 2039 insertions(+), 1509 deletions(-) create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsCallableShapeSurfaceService.java create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableBodyLowerer.java create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableCallableRegistryFactory.java create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableCallsiteEmitter.java create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringContext.java create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringModels.java create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringService.java create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableMetadataIndexFactory.java create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableStackAnalyzer.java diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java index e5fe2bb4..8877d8c3 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java @@ -1,47 +1,32 @@ package p.studio.compiler.pbs; -import p.studio.compiler.models.IRFunction; -import p.studio.compiler.models.SourceKind; -import p.studio.compiler.models.IRBackendFile; -import p.studio.compiler.models.IRBackendExecutableFunction; import p.studio.compiler.messages.HostAdmissionContext; +import p.studio.compiler.models.IRBackendFile; +import p.studio.compiler.models.IRFunction; import p.studio.compiler.models.IRReservedMetadata; +import p.studio.compiler.models.SourceKind; import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.pbs.lexer.PbsLexer; +import p.studio.compiler.pbs.lowering.PbsExecutableLoweringService; import p.studio.compiler.pbs.metadata.PbsReservedMetadataExtractor; import p.studio.compiler.pbs.parser.PbsParser; import p.studio.compiler.pbs.semantics.PbsDeclarationSemanticsValidator; import p.studio.compiler.pbs.semantics.PbsFlowSemanticsValidator; -import p.studio.compiler.pbs.semantics.PbsSemanticsErrors; import p.studio.compiler.source.diagnostics.DiagnosticSink; -import p.studio.compiler.source.diagnostics.DiagnosticPhase; -import p.studio.compiler.source.identifiers.CallableShapeId; -import p.studio.compiler.source.identifiers.CallableId; import p.studio.compiler.source.identifiers.FileId; import p.studio.compiler.source.identifiers.ModuleId; -import p.studio.compiler.source.identifiers.NameId; -import p.studio.compiler.source.identifiers.TypeSurfaceId; -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.IntrinsicReference; -import p.studio.compiler.source.tables.IntrinsicTable; import p.studio.compiler.source.tables.ModuleReference; import p.studio.compiler.source.tables.NameTable; -import p.studio.compiler.source.tables.TypeSurfaceTable; import p.studio.utilities.structures.ReadOnlyList; import java.util.ArrayList; -import java.util.ArrayDeque; -import java.util.HashMap; import java.util.HashSet; -import java.util.List; -import java.util.Map; public final class PbsFrontendCompiler { private final PbsFlowSemanticsValidator flowSemanticsValidator = new PbsFlowSemanticsValidator(); private final PbsReservedMetadataExtractor reservedMetadataExtractor = new PbsReservedMetadataExtractor(); private final PbsHostAdmissionValidator hostAdmissionValidator = new PbsHostAdmissionValidator(); + private final PbsExecutableLoweringService executableLoweringService = new PbsExecutableLoweringService(); public IRBackendFile compileFile( final FileId fileId, @@ -140,34 +125,25 @@ public final class PbsFrontendCompiler { return IRBackendFile.empty(fileId); } - final var extractedReservedMetadata = reservedMetadataExtractor.extract(ast, sourceKind); - final var hostAdmissionErrorBaseline = diagnostics.errorCount(); - final var requiredCapabilities = hostAdmissionValidator.validate( - extractedReservedMetadata, + final var reservedMetadata = validateAndBuildReservedMetadata( + ast, + sourceKind, hostAdmissionContext, diagnostics); - if (diagnostics.errorCount() > hostAdmissionErrorBaseline) { + if (reservedMetadata == null) { return IRBackendFile.empty(fileId); } - final var reservedMetadata = new IRReservedMetadata( - extractedReservedMetadata.hostMethodBindings(), - extractedReservedMetadata.builtinTypeSurfaces(), - extractedReservedMetadata.builtinConstSurfaces(), - requiredCapabilities); - final var effectiveReservedMetadata = mergeReservedMetadata( - reservedMetadata, - effectiveImportedReservedMetadata); final ReadOnlyList functions = sourceKind == SourceKind.SDK_INTERFACE ? ReadOnlyList.empty() : lowerFunctions(fileId, ast); final var loweringErrorBaseline = diagnostics.errorCount(); - final var executableLowering = lowerExecutableFunctions( + final var executableLowering = executableLoweringService.lower( fileId, ast, effectiveSupplementalTopDecls, effectiveModuleId, - effectiveReservedMetadata, + mergeReservedMetadata(reservedMetadata, effectiveImportedReservedMetadata), effectiveNameTable, diagnostics, effectiveImportedCallables); @@ -184,6 +160,31 @@ public final class PbsFrontendCompiler { executableLowering.intrinsicPool()); } + public String callableShapeSurfaceOf(final PbsAst.FunctionDecl functionDecl) { + return executableLoweringService.callableShapeSurfaceOf(functionDecl); + } + + private IRReservedMetadata validateAndBuildReservedMetadata( + final PbsAst.File ast, + final SourceKind sourceKind, + final HostAdmissionContext hostAdmissionContext, + final DiagnosticSink diagnostics) { + final var extractedReservedMetadata = reservedMetadataExtractor.extract(ast, sourceKind); + final var hostAdmissionErrorBaseline = diagnostics.errorCount(); + final var requiredCapabilities = hostAdmissionValidator.validate( + extractedReservedMetadata, + hostAdmissionContext, + diagnostics); + if (diagnostics.errorCount() > hostAdmissionErrorBaseline) { + return null; + } + return new IRReservedMetadata( + extractedReservedMetadata.hostMethodBindings(), + extractedReservedMetadata.builtinTypeSurfaces(), + extractedReservedMetadata.builtinConstSurfaces(), + requiredCapabilities); + } + private ReadOnlyList lowerFunctions(final FileId fileId, final PbsAst.File ast) { final var functions = new ArrayList(ast.functions().size()); for (final var fn : ast.functions()) { @@ -197,245 +198,6 @@ public final class PbsFrontendCompiler { return ReadOnlyList.wrap(functions); } - private ExecutableLoweringResult lowerExecutableFunctions( - final FileId fileId, - final PbsAst.File ast, - final ReadOnlyList supplementalTopDecls, - final ModuleId moduleId, - final IRReservedMetadata reservedMetadata, - final NameTable nameTable, - final DiagnosticSink diagnostics, - final ReadOnlyList importedCallables) { - final var normalizedModuleId = moduleId == null ? ModuleId.none() : moduleId; - final var hostByMethodName = new HashMap>(); - for (final var hostBinding : reservedMetadata.hostMethodBindings()) { - hostByMethodName - .computeIfAbsent(nameTable.register(hostBinding.sourceMethodName()), ignored -> new ArrayList<>()) - .add(hostBinding); - } - final var builtinCanonicalBySourceType = new HashMap(); - for (final var builtinType : reservedMetadata.builtinTypeSurfaces()) { - builtinCanonicalBySourceType.putIfAbsent(builtinType.sourceTypeName(), builtinType.canonicalTypeName()); - } - final var builtinConstOwnerByNameId = new HashMap(); - for (final var builtinConst : reservedMetadata.builtinConstSurfaces()) { - if (builtinConst.sourceConstName().isBlank() || builtinConst.canonicalTarget().isBlank()) { - continue; - } - builtinConstOwnerByNameId.put(nameTable.register(builtinConst.sourceConstName()), builtinConst.canonicalTarget()); - } - - final var builtinSignatureByOwnerAndMethod = new HashMap(); - final var topDeclsForIntrinsicInference = new ArrayList(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 IntrinsicOwnerMethodKey(ownerCanonical, signature.name()), - signature); - } - } - - final var intrinsicByOwnerAndMethod = new HashMap>(); - final var intrinsicReturnOwnerByCanonical = new HashMap(); - for (final var builtinType : reservedMetadata.builtinTypeSurfaces()) { - final var ownerCanonical = builtinType.canonicalTypeName(); - for (final var intrinsicSurface : builtinType.intrinsics()) { - intrinsicByOwnerAndMethod - .computeIfAbsent( - new IntrinsicOwnerMethodKey(ownerCanonical, intrinsicSurface.sourceMethodName()), - ignored -> new ArrayList<>()) - .add(intrinsicSurface); - - final var signature = builtinSignatureByOwnerAndMethod.get( - new IntrinsicOwnerMethodKey(ownerCanonical, intrinsicSurface.sourceMethodName())); - if (signature == null) { - continue; - } - final var returnOwner = inferIntrinsicReturnOwner( - signature, - ownerCanonical, - builtinCanonicalBySourceType); - if (returnOwner.isBlank()) { - continue; - } - final var key = new IntrinsicCanonicalKey( - 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()); - } - } - } - final var callableIdTable = new CallableTable(); - final var callableIdsByNameAndArity = new HashMap>(); - final var callableIdByDeclaration = new HashMap(); - final var returnSlotsByCallableId = new HashMap(); - final var intrinsicIdTable = new IntrinsicTable(); - final var typeSurfaceTable = new TypeSurfaceTable(); - final var callableShapeTable = new CallableShapeTable(); - final var localCallables = collectLocalLowerableCallables(ast); - for (final var declaredCallable : localCallables) { - final var declaredFn = declaredCallable.functionDecl(); - final var callableShapeId = callableShapeId(declaredFn, typeSurfaceTable, callableShapeTable); - final var callableId = callableIdTable.register( - normalizedModuleId, - declaredCallable.callableName(), - declaredFn.parameters().size(), - callableShapeSurface(callableShapeId, typeSurfaceTable, callableShapeTable)); - callableIdByDeclaration.put(declaredCallable, callableId); - callableIdsByNameAndArity - .computeIfAbsent( - new CallableResolutionKey(nameTable.register(declaredCallable.callableName()), declaredFn.parameters().size()), - ignored -> new ArrayList<>()) - .add(callableId); - final var retSlots = returnSlotsFor(declaredFn); - returnSlotsByCallableId.put(callableId, retSlots); - } - for (final var importedCallable : importedCallables) { - final var callableId = callableIdTable.register( - importedCallable.moduleId(), - importedCallable.callableName(), - importedCallable.arity(), - importedCallable.shapeSurface()); - callableIdsByNameAndArity - .computeIfAbsent( - new CallableResolutionKey( - nameTable.register(importedCallable.callableName()), - importedCallable.arity()), - ignored -> new ArrayList<>()) - .add(callableId); - returnSlotsByCallableId.put(callableId, importedCallable.returnSlots()); - } - final var callableSignatureByCallableId = new HashMap(); - for (final var callableId : callableIdTable.identifiers()) { - callableSignatureByCallableId.put(callableId, callableIdTable.get(callableId)); - } - - final var executableFunctions = new ArrayList(localCallables.size()); - for (final var callable : localCallables) { - final var fn = callable.functionDecl(); - final var functionCallableId = callableIdByDeclaration.get(callable); - if (functionCallableId == null) { - continue; - } - final var localSlotByNameId = new HashMap(); - for (var paramIndex = 0; paramIndex < fn.parameters().size(); paramIndex++) { - localSlotByNameId.put(nameTable.register(fn.parameters().get(paramIndex).name()), paramIndex); - } - final var loweringContext = new ExecutableLoweringContext( - normalizedModuleId, - diagnostics, - nameTable, - hostByMethodName, - intrinsicByOwnerAndMethod, - intrinsicReturnOwnerByCanonical, - builtinConstOwnerByNameId, - callableIdsByNameAndArity, - callableSignatureByCallableId, - returnSlotsByCallableId, - intrinsicIdTable, - localSlotByNameId, - fn.parameters().size()); - final var terminated = lowerBlock(fn.body(), loweringContext); - final var instructions = loweringContext.instructions(); - if (!terminated) { - instructions.add(new IRBackendExecutableFunction.Instruction( - IRBackendExecutableFunction.InstructionKind.RET, - "", - null, - null, - null, - fn.span())); - } - - final var returnSlots = returnSlotsFor(fn); - final int maxStackSlots; - try { - maxStackSlots = analyzeMaxStackSlots(ReadOnlyList.wrap(instructions), returnSlots); - } catch (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(), - fn.span()); - continue; - } - final var start = safeToInt(fn.span().getStart()); - final var end = safeToInt(fn.span().getEnd()); - final var localSlots = Math.max(0, loweringContext.nextLocalSlot() - fn.parameters().size()); - executableFunctions.add(new IRBackendExecutableFunction( - fileId, - normalizedModuleId, - callable.callableName(), - functionCallableId, - start, - end, - fn.parameters().size(), - localSlots, - returnSlots, - maxStackSlots, - ReadOnlyList.wrap(instructions), - fn.span())); - } - final var callableSignatures = new ArrayList(callableIdTable.size()); - for (final var callableId : callableIdTable.identifiers()) { - callableSignatures.add(callableIdTable.get(callableId)); - } - final var intrinsicPool = new ArrayList(intrinsicIdTable.size()); - for (final var intrinsicId : intrinsicIdTable.identifiers()) { - intrinsicPool.add(intrinsicIdTable.get(intrinsicId)); - } - return new ExecutableLoweringResult( - ReadOnlyList.wrap(executableFunctions), - ReadOnlyList.wrap(callableSignatures), - ReadOnlyList.wrap(intrinsicPool)); - } - - private int returnSlotsFor(final PbsAst.FunctionDecl functionDecl) { - return switch (functionDecl.returnKind()) { - case INFERRED_UNIT, EXPLICIT_UNIT -> 0; - case PLAIN, RESULT -> 1; - }; - } - - private ReadOnlyList collectLocalLowerableCallables(final PbsAst.File ast) { - final var callables = new ArrayList(); - for (final var functionDecl : ast.functions()) { - callables.add(new LowerableCallable(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 LowerableCallable(serviceDecl.name() + "." + method.name(), method)); - } - } - return ReadOnlyList.wrap(callables); - } - private IRReservedMetadata mergeReservedMetadata( final IRReservedMetadata primary, final IRReservedMetadata imported) { @@ -462,1173 +224,6 @@ public final class PbsFrontendCompiler { ReadOnlyList.wrap(requiredCapabilities.stream().toList())); } - private CallableShapeId callableShapeId( - final PbsAst.FunctionDecl functionDecl, - final TypeSurfaceTable typeSurfaceTable, - final CallableShapeTable callableShapeTable) { - final var parameterSurfaceIds = new ArrayList(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); - } - - public 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); - } - - private 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 inferIntrinsicReturnOwner( - final PbsAst.FunctionSignature signature, - final String intrinsicOwnerCanonical, - final Map 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 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()); - } - - 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; - } - - private boolean lowerBlock( - final PbsAst.Block block, - final ExecutableLoweringContext 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 ExecutableLoweringContext context) { - if (statement == null) { - return false; - } - switch (statement) { - case PbsAst.LetStatement letStatement -> { - lowerExpression(letStatement.initializer(), context); - final var localSlot = context.declareLocalSlot(letStatement.name()); - emitSetLocal(localSlot, letStatement.span(), context); - } - case PbsAst.AssignStatement assignStatement -> { - 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; - } - switch (assignStatement.operator()) { - case ASSIGN -> { - lowerExpression(assignStatement.value(), context); - emitSetLocal(targetSlot, assignStatement.span(), context); - } - case ADD_ASSIGN, SUB_ASSIGN, MUL_ASSIGN, DIV_ASSIGN, MOD_ASSIGN -> { - emitGetLocal(targetSlot, assignStatement.span(), context); - lowerExpression(assignStatement.value(), context); - emitBinaryOperatorInstruction(compoundAssignBinaryOperator(assignStatement.operator()), assignStatement.span(), context); - emitSetLocal(targetSlot, assignStatement.span(), context); - } - } - } - case PbsAst.ReturnStatement returnStatement -> { - if (returnStatement.value() != null) { - lowerExpression(returnStatement.value(), context); - } - context.instructions().add(new IRBackendExecutableFunction.Instruction( - IRBackendExecutableFunction.InstructionKind.RET, - "", - null, - null, - null, - returnStatement.span())); - return true; - } - case PbsAst.IfStatement ifStatement -> { - 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 boolean elseTerminated; - if (ifStatement.elseIf() != null) { - elseTerminated = lowerStatement(ifStatement.elseIf(), context); - } else if (ifStatement.elseBlock() != null) { - elseTerminated = lowerBlock(ifStatement.elseBlock(), context); - } else { - elseTerminated = false; - } - if (!thenTerminated || !elseTerminated) { - emitLabel(endLabel, ifStatement.span(), context); - } - return (ifStatement.elseIf() != null || ifStatement.elseBlock() != null) - && thenTerminated - && elseTerminated; - } - case PbsAst.ForStatement forStatement -> { - 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); - } - case PbsAst.WhileStatement whileStatement -> { - 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); - } - case PbsAst.ExpressionStatement expressionStatement -> lowerExpression(expressionStatement.expression(), context); - case PbsAst.BreakStatement breakStatement -> { - 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; - } - case PbsAst.ContinueStatement continueStatement -> { - 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; - } - } - return false; - } - - private void lowerExpression( - final PbsAst.Expression expression, - final ExecutableLoweringContext context) { - if (expression == null) { - return; - } - switch (expression) { - case PbsAst.CallExpr callExpr -> { - lowerCallsiteReceiver(callExpr.callee(), context); - for (final var arg : callExpr.arguments()) { - lowerExpression(arg, context); - } - lowerCallsite(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 -> { - 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); - } - } - case PbsAst.SwitchExpr switchExpr -> { - lowerExpression(switchExpr.selector(), context); - reportUnsupportedLowering("switch expression is not lowerable in executable lowering v1", switchExpr.span(), context); - } - case PbsAst.HandleExpr handleExpr -> { - lowerExpression(handleExpr.value(), context); - reportUnsupportedLowering("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 -> { - for (final var arg : newExpr.arguments()) { - lowerExpression(arg, 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 -> { - final var slot = context.localSlotByNameId().get(context.nameTable().register(identifierExpr.name())); - if (slot != null) { - emitGetLocal(slot, identifierExpr.span(), context); - } - } - case PbsAst.IntLiteralExpr intLiteralExpr -> { - 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); - } - 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 lowerCallsiteReceiver( - final PbsAst.Expression callee, - final ExecutableLoweringContext 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 lowerCallsite( - final PbsAst.CallExpr callExpr, - final ExecutableLoweringContext 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 callableCandidates = new ArrayList(); - final var primaryCallableCandidates = context.callableIdsByNameAndArity().get( - new CallableResolutionKey(calleeIdentity.primaryCallableNameId(), callExpr.arguments().size())); - if (primaryCallableCandidates != null) { - callableCandidates.addAll(primaryCallableCandidates); - } - if (calleeIdentity.secondaryCallableNameId() != null) { - final var secondaryCallableCandidates = context.callableIdsByNameAndArity().get( - new CallableResolutionKey(calleeIdentity.secondaryCallableNameId(), callExpr.arguments().size())); - if (secondaryCallableCandidates != null) { - for (final var candidate : secondaryCallableCandidates) { - if (!callableCandidates.contains(candidate)) { - callableCandidates.add(candidate); - } - } - } - } - - final var hostCandidates = context.hostByMethodName().getOrDefault(calleeIdentity.memberNameId(), List.of()); - final var intrinsicCandidates = resolveIntrinsicCandidates(callExpr, calleeIdentity, context); - - var categoryCount = 0; - if (callableCandidates != null && !callableCandidates.isEmpty()) { - categoryCount++; - } - if (!hostCandidates.isEmpty()) { - categoryCount++; - } - if (!intrinsicCandidates.isEmpty()) { - categoryCount++; - } - if (categoryCount == 0) { - reportUnsupportedLowering("executable lowering requires resolvable callee identity", callExpr.span(), context); - return; - } - if (categoryCount > 1) { - reportUnsupportedLowering("executable lowering found ambiguous callsite category", callExpr.span(), context); - return; - } - - if (!hostCandidates.isEmpty()) { - if (hostCandidates.size() > 1) { - reportUnsupportedLowering("executable lowering found ambiguous host binding identity", callExpr.span(), context); - return; - } - final var host = hostCandidates.getFirst(); - 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())); - return; - } - - if (!intrinsicCandidates.isEmpty()) { - if (intrinsicCandidates.size() > 1) { - reportUnsupportedLowering("executable lowering found ambiguous intrinsic identity", callExpr.span(), context); - return; - } - final var intrinsic = intrinsicCandidates.getFirst(); - 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())); - return; - } - - 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 resolveIntrinsicCandidates( - final PbsAst.CallExpr callExpr, - final CalleeIdentity calleeIdentity, - final ExecutableLoweringContext context) { - final var receiverOwner = resolveReceiverOwnerForCallee(callExpr.callee(), context); - if (receiverOwner.isBlank()) { - return List.of(); - } - final var key = new IntrinsicOwnerMethodKey(receiverOwner, calleeIdentity.memberSourceMethodName()); - return context.intrinsicByOwnerAndMethod().getOrDefault(key, List.of()); - } - - private String resolveReceiverOwnerForCallee( - final PbsAst.Expression callee, - final ExecutableLoweringContext 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 ExecutableLoweringContext 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 ExecutableLoweringContext 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 IntrinsicOwnerMethodKey(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 IntrinsicCanonicalKey(intrinsic.canonicalName(), intrinsic.canonicalVersion()), - ""); - } - - private CalleeIdentity resolveCalleeIdentity( - final PbsAst.Expression callee, - final NameTable nameTable) { - return switch (callee) { - case PbsAst.IdentifierExpr identifierExpr -> - new CalleeIdentity( - nameTable.register(identifierExpr.name()), - identifierExpr.name(), - null, - "", - identifierExpr.name(), - nameTable.register(identifierExpr.name())); - case PbsAst.MemberExpr memberExpr -> { - final var memberName = memberExpr.memberName(); - final var rootName = memberRootName(memberExpr.receiver()); - final var qualified = rootName == null || rootName.isBlank() - ? memberName - : rootName + "." + memberName; - final NameId secondaryNameId; - final String secondaryDisplayName; - if (qualified.equals(memberName)) { - secondaryNameId = null; - secondaryDisplayName = ""; - } else { - secondaryNameId = nameTable.register(memberName); - secondaryDisplayName = memberName; - } - yield new CalleeIdentity( - nameTable.register(qualified), - qualified, - secondaryNameId, - secondaryDisplayName, - memberName, - nameTable.register(memberName)); - } - case PbsAst.BindExpr bindExpr -> - new CalleeIdentity( - nameTable.register(bindExpr.functionName()), - bindExpr.functionName(), - null, - "", - bindExpr.functionName(), - nameTable.register(bindExpr.functionName())); - case PbsAst.GroupExpr groupExpr -> resolveCalleeIdentity(groupExpr.expression(), nameTable); - default -> null; - }; - } - - 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 emitJump( - final IRBackendExecutableFunction.InstructionKind jumpKind, - final String targetLabel, - final p.studio.compiler.source.Span span, - final ExecutableLoweringContext 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 ExecutableLoweringContext 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 ExecutableLoweringContext 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 ExecutableLoweringContext 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 ExecutableLoweringContext 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 ExecutableLoweringContext 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 ExecutableLoweringContext 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 ExecutableLoweringContext 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 ExecutableLoweringContext 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 ExecutableLoweringContext 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); - } - - private int analyzeMaxStackSlots( - final ReadOnlyList instructions, - final int returnSlots) { - if (instructions.isEmpty()) { - return Math.max(1, returnSlots); - } - - final var labelToIndex = new HashMap(); - 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(); - final var worklist = new ArrayDeque(); - final var visited = new HashSet(); - 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(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 incomingHeightByInstruction, - final ArrayDeque 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)); - } - } - - private static final class ExecutableLoweringContext { - private final ModuleId moduleId; - private final DiagnosticSink diagnostics; - private final NameTable nameTable; - private final Map> hostByMethodName; - private final Map> intrinsicByOwnerAndMethod; - private final Map intrinsicReturnOwnerByCanonical; - private final Map builtinConstOwnerByNameId; - private final Map> callableIdsByNameAndArity; - private final Map callableSignatureByCallableId; - private final Map returnSlotsByCallableId; - private final IntrinsicTable intrinsicIdTable; - private final Map localSlotByNameId; - private final ArrayList instructions = new ArrayList<>(); - private final ArrayDeque loopTargets = new ArrayDeque<>(); - private int nextLabelId = 0; - private int nextLocalSlot; - - private ExecutableLoweringContext( - final ModuleId moduleId, - final DiagnosticSink diagnostics, - final NameTable nameTable, - final Map> hostByMethodName, - final Map> intrinsicByOwnerAndMethod, - final Map intrinsicReturnOwnerByCanonical, - final Map builtinConstOwnerByNameId, - final Map> callableIdsByNameAndArity, - final Map callableSignatureByCallableId, - final Map returnSlotsByCallableId, - final IntrinsicTable intrinsicIdTable, - final Map localSlotByNameId, - final int initialLocalSlot) { - this.moduleId = moduleId == null ? ModuleId.none() : moduleId; - this.diagnostics = diagnostics; - this.nameTable = nameTable; - this.hostByMethodName = hostByMethodName; - this.intrinsicByOwnerAndMethod = intrinsicByOwnerAndMethod; - this.intrinsicReturnOwnerByCanonical = intrinsicReturnOwnerByCanonical; - this.builtinConstOwnerByNameId = builtinConstOwnerByNameId; - this.callableIdsByNameAndArity = callableIdsByNameAndArity; - this.callableSignatureByCallableId = callableSignatureByCallableId; - this.returnSlotsByCallableId = returnSlotsByCallableId; - this.intrinsicIdTable = intrinsicIdTable; - this.localSlotByNameId = localSlotByNameId == null ? new HashMap<>() : localSlotByNameId; - this.nextLocalSlot = Math.max(0, initialLocalSlot); - } - - private ModuleId moduleId() { - return moduleId; - } - - private DiagnosticSink diagnostics() { - return diagnostics; - } - - private NameTable nameTable() { - return nameTable; - } - - private Map> hostByMethodName() { - return hostByMethodName; - } - - private Map> intrinsicByOwnerAndMethod() { - return intrinsicByOwnerAndMethod; - } - - private Map intrinsicReturnOwnerByCanonical() { - return intrinsicReturnOwnerByCanonical; - } - - private Map builtinConstOwnerByNameId() { - return builtinConstOwnerByNameId; - } - - private Map> callableIdsByNameAndArity() { - return callableIdsByNameAndArity; - } - - private Map callableSignatureByCallableId() { - return callableSignatureByCallableId; - } - - private Map returnSlotsByCallableId() { - return returnSlotsByCallableId; - } - - private IntrinsicTable intrinsicIdTable() { - return intrinsicIdTable; - } - - private Map localSlotByNameId() { - return localSlotByNameId; - } - - private Integer resolveLocalSlot(final String localName) { - if (localName == null || localName.isBlank()) { - return null; - } - return localSlotByNameId.get(nameTable.register(localName)); - } - - private 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; - } - - private int nextLocalSlot() { - return nextLocalSlot; - } - - private ArrayList instructions() { - return instructions; - } - - private ArrayDeque loopTargets() { - return loopTargets; - } - - private void pushLoop( - final String continueLabel, - final String breakLabel) { - loopTargets.push(new LoopTargets(continueLabel, breakLabel)); - } - - private void popLoop() { - loopTargets.pop(); - } - - private String nextLabel(final String prefix) { - return "__" + prefix + "_" + nextLabelId++; - } - } - - private record LoopTargets( - String continueLabel, - String breakLabel) { - } - - private record ExecutableLoweringResult( - ReadOnlyList executableFunctions, - ReadOnlyList callableSignatures, - ReadOnlyList intrinsicPool) { - } - - private record LowerableCallable( - String callableName, - PbsAst.FunctionDecl functionDecl) { - } - - private record CallableResolutionKey( - NameId callableNameId, - int arity) { - } - - private record IntrinsicOwnerMethodKey( - String ownerCanonicalName, - String sourceMethodName) { - } - - private record IntrinsicCanonicalKey( - String canonicalName, - long canonicalVersion) { - } - - private record CalleeIdentity( - NameId primaryCallableNameId, - String primaryCallableDisplayName, - NameId secondaryCallableNameId, - String secondaryCallableDisplayName, - String memberSourceMethodName, - NameId memberNameId) { - } - public record ImportedCallableSurface( ModuleId moduleId, String callableName, @@ -1639,10 +234,4 @@ public final class PbsFrontendCompiler { moduleId = moduleId == null ? ModuleId.none() : moduleId; } } - - private static final class ExecutableLoweringAnalysisException extends RuntimeException { - private ExecutableLoweringAnalysisException(final String message) { - super(message); - } - } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsHostAdmissionValidator.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsHostAdmissionValidator.java index b54da65d..7a9f1361 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsHostAdmissionValidator.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsHostAdmissionValidator.java @@ -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 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 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 normalizedSet(final ReadOnlyList values) { final var normalized = new HashSet(); for (final var value : values) { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsCallableShapeSurfaceService.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsCallableShapeSurfaceService.java new file mode 100644 index 00000000..2a51ceaa --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsCallableShapeSurfaceService.java @@ -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(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()); + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableBodyLowerer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableBodyLowerer.java new file mode 100644 index 00000000..e586f142 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableBodyLowerer.java @@ -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 initialLocalSlots( + final PbsAst.FunctionDecl functionDecl, + final NameTable nameTable) { + final var localSlotByNameId = new HashMap(); + 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 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); + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableCallableRegistryFactory.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableCallableRegistryFactory.java new file mode 100644 index 00000000..688de70d --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableCallableRegistryFactory.java @@ -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 importedCallables) { + final var callableIdTable = new CallableTable(); + final var callableIdsByNameAndArity = new HashMap>(); + final var callableIdByDeclaration = new HashMap(); + final var returnSlotsByCallableId = new HashMap(); + 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(); + final var callableSignatures = new ArrayList(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 collectLocalLowerableCallables(final PbsAst.File ast) { + final var callables = new ArrayList(); + 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> callableIdsByNameAndArity, + final Map callableIdByDeclaration, + final Map returnSlotsByCallableId, + final TypeSurfaceTable typeSurfaceTable, + final CallableShapeTable callableShapeTable, + final ReadOnlyList 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> callableIdsByNameAndArity, + final Map returnSlotsByCallableId, + final ReadOnlyList 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; + }; + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableCallsiteEmitter.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableCallsiteEmitter.java new file mode 100644 index 00000000..80584cf0 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableCallsiteEmitter.java @@ -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 resolveCallableCandidates( + final PbsAst.CallExpr callExpr, + final PbsCalleeIdentity calleeIdentity, + final PbsExecutableLoweringContext context) { + final var callableCandidates = new ArrayList(); + 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 destination, + final List 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 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 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); + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringContext.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringContext.java new file mode 100644 index 00000000..a23e28fb --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringContext.java @@ -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 localSlotByNameId; + private final ArrayList instructions = new ArrayList<>(); + private final ArrayDeque 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 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> hostByMethodName() { + return metadataIndex.hostByMethodName(); + } + + Map builtinConstOwnerByNameId() { + return metadataIndex.builtinConstOwnerByNameId(); + } + + Map> intrinsicByOwnerAndMethod() { + return metadataIndex.intrinsicByOwnerAndMethod(); + } + + Map intrinsicReturnOwnerByCanonical() { + return metadataIndex.intrinsicReturnOwnerByCanonical(); + } + + Map> callableIdsByNameAndArity() { + return callableRegistry.callableIdsByNameAndArity(); + } + + Map callableSignatureByCallableId() { + return callableRegistry.callableSignatureByCallableId(); + } + + Map returnSlotsByCallableId() { + return callableRegistry.returnSlotsByCallableId(); + } + + IntrinsicTable intrinsicIdTable() { + return intrinsicIdTable; + } + + Map 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 instructions() { + return instructions; + } + + ArrayDeque 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++; + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringModels.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringModels.java new file mode 100644 index 00000000..32ce1fb3 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringModels.java @@ -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 executableFunctions, + ReadOnlyList callableSignatures, + ReadOnlyList 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> hostByMethodName, + Map builtinConstOwnerByNameId, + Map> intrinsicByOwnerAndMethod, + Map intrinsicReturnOwnerByCanonical) { + } + + record PbsExecutableCallableRegistry( + ReadOnlyList localCallables, + Map callableIdByDeclaration, + Map> callableIdsByNameAndArity, + Map callableSignatureByCallableId, + Map returnSlotsByCallableId, + ReadOnlyList callableSignatures) { + } + + record PbsLoweredExecutableBody( + ReadOnlyList instructions, + int localSlots, + int maxStackSlots) { + } + + record PbsCalleeIdentity( + NameId primaryCallableNameId, + NameId secondaryCallableNameId, + String memberSourceMethodName, + NameId memberNameId) { + } + + record PbsResolvedCallsiteCandidates( + List callableCandidates, + List hostCandidates, + List intrinsicCandidates) { + } + + record PbsLoopTargets( + String continueLabel, + String breakLabel) { + } + + enum PbsCallsiteCategory { + CALLABLE, + HOST, + INTRINSIC + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringService.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringService.java new file mode 100644 index 00000000..99da2dd6 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableLoweringService.java @@ -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 supplementalTopDecls, + final ModuleId moduleId, + final IRReservedMetadata reservedMetadata, + final NameTable nameTable, + final DiagnosticSink diagnostics, + final ReadOnlyList 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(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(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; + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableMetadataIndexFactory.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableMetadataIndexFactory.java new file mode 100644 index 00000000..05c6a315 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableMetadataIndexFactory.java @@ -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 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>(); + final var intrinsicReturnOwnerByCanonical = new HashMap(); + indexIntrinsicMetadata( + reservedMetadata, + builtinCanonicalBySourceType, + builtinSignatureByOwnerAndMethod, + intrinsicByOwnerAndMethod, + intrinsicReturnOwnerByCanonical, + diagnostics); + return new PbsExecutableMetadataIndex( + hostByMethodName, + builtinConstOwnerByNameId, + intrinsicByOwnerAndMethod, + intrinsicReturnOwnerByCanonical); + } + + private Map> indexHostBindingsByMethodName( + final IRReservedMetadata reservedMetadata, + final NameTable nameTable) { + final var hostByMethodName = new HashMap>(); + for (final var hostBinding : reservedMetadata.hostMethodBindings()) { + hostByMethodName + .computeIfAbsent(nameTable.register(hostBinding.sourceMethodName()), ignored -> new ArrayList<>()) + .add(hostBinding); + } + return hostByMethodName; + } + + private Map indexBuiltinCanonicalBySourceType(final IRReservedMetadata reservedMetadata) { + final var builtinCanonicalBySourceType = new HashMap(); + for (final var builtinType : reservedMetadata.builtinTypeSurfaces()) { + builtinCanonicalBySourceType.putIfAbsent(builtinType.sourceTypeName(), builtinType.canonicalTypeName()); + } + return builtinCanonicalBySourceType; + } + + private Map indexBuiltinConstOwners( + final IRReservedMetadata reservedMetadata, + final NameTable nameTable) { + final var builtinConstOwnerByNameId = new HashMap(); + 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 indexBuiltinSignatures( + final PbsAst.File ast, + final ReadOnlyList supplementalTopDecls, + final Map builtinCanonicalBySourceType) { + final var builtinSignatureByOwnerAndMethod = new HashMap(); + final var topDeclsForIntrinsicInference = new ArrayList(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 builtinCanonicalBySourceType, + final Map builtinSignatureByOwnerAndMethod, + final Map> intrinsicByOwnerAndMethod, + final Map 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 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()); + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableStackAnalyzer.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableStackAnalyzer.java new file mode 100644 index 00000000..33455177 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/lowering/PbsExecutableStackAnalyzer.java @@ -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 instructions, + final int returnSlots) { + if (instructions.isEmpty()) { + return Math.max(1, returnSlots); + } + + final var labelToIndex = new HashMap(); + 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(); + final var worklist = new ArrayDeque(); + final var visited = new HashSet(); + 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(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 incomingHeightByInstruction, + final ArrayDeque 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); + } + } +}