implements PR-039

This commit is contained in:
bQUARKz 2026-03-07 16:58:21 +00:00
parent 0728637f58
commit 4e2cad60cb
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
3 changed files with 301 additions and 3 deletions

View File

@ -3,6 +3,7 @@ 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.IRReservedMetadata;
import p.studio.compiler.pbs.ast.PbsAst;
@ -16,6 +17,9 @@ import p.studio.compiler.source.identifiers.FileId;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class PbsFrontendCompiler {
private final PbsDeclarationSemanticsValidator declarationSemanticsValidator = new PbsDeclarationSemanticsValidator();
@ -61,7 +65,7 @@ public final class PbsFrontendCompiler {
final FileId fileId,
final PbsAst.File ast,
final DiagnosticSink diagnostics) {
return compileParsedFile(fileId, ast, diagnostics, SourceKind.PROJECT);
return compileParsedFile(fileId, ast, diagnostics, SourceKind.PROJECT, "", HostAdmissionContext.permissiveDefault());
}
public IRBackendFile compileParsedFile(
@ -69,7 +73,7 @@ public final class PbsFrontendCompiler {
final PbsAst.File ast,
final DiagnosticSink diagnostics,
final SourceKind sourceKind) {
return compileParsedFile(fileId, ast, diagnostics, sourceKind, HostAdmissionContext.permissiveDefault());
return compileParsedFile(fileId, ast, diagnostics, sourceKind, "", HostAdmissionContext.permissiveDefault());
}
public IRBackendFile compileParsedFile(
@ -78,6 +82,16 @@ public final class PbsFrontendCompiler {
final DiagnosticSink diagnostics,
final SourceKind sourceKind,
final HostAdmissionContext hostAdmissionContext) {
return compileParsedFile(fileId, ast, diagnostics, sourceKind, "", hostAdmissionContext);
}
public IRBackendFile compileParsedFile(
final FileId fileId,
final PbsAst.File ast,
final DiagnosticSink diagnostics,
final SourceKind sourceKind,
final String moduleKey,
final HostAdmissionContext hostAdmissionContext) {
final var semanticsErrorBaseline = diagnostics.errorCount();
declarationSemanticsValidator.validate(ast, sourceKind, diagnostics);
flowSemanticsValidator.validate(ast, diagnostics);
@ -103,7 +117,10 @@ public final class PbsFrontendCompiler {
final ReadOnlyList<IRFunction> functions = sourceKind == SourceKind.SDK_INTERFACE
? ReadOnlyList.empty()
: lowerFunctions(fileId, ast);
return new IRBackendFile(fileId, functions, reservedMetadata);
final ReadOnlyList<IRBackendExecutableFunction> executableFunctions = sourceKind == SourceKind.SDK_INTERFACE
? ReadOnlyList.empty()
: lowerExecutableFunctions(fileId, ast, moduleKey, reservedMetadata);
return new IRBackendFile(fileId, functions, executableFunctions, reservedMetadata);
}
private ReadOnlyList<IRFunction> lowerFunctions(final FileId fileId, final PbsAst.File ast) {
@ -118,4 +135,258 @@ public final class PbsFrontendCompiler {
}
return ReadOnlyList.wrap(functions);
}
private ReadOnlyList<IRBackendExecutableFunction> lowerExecutableFunctions(
final FileId fileId,
final PbsAst.File ast,
final String moduleKey,
final IRReservedMetadata reservedMetadata) {
final var hostByMethodName = new HashMap<String, IRReservedMetadata.HostMethodBinding>();
for (final var hostBinding : reservedMetadata.hostMethodBindings()) {
hostByMethodName.put(hostBinding.sourceMethodName(), hostBinding);
}
final var intrinsicByMethodName = new HashMap<String, IRReservedMetadata.IntrinsicSurface>();
for (final var builtinType : reservedMetadata.builtinTypeSurfaces()) {
for (final var intrinsicSurface : builtinType.intrinsics()) {
intrinsicByMethodName.put(intrinsicSurface.sourceMethodName(), intrinsicSurface);
}
}
final var executableFunctions = new ArrayList<IRBackendExecutableFunction>(ast.functions().size());
for (final var fn : ast.functions()) {
final var instructions = new ArrayList<IRBackendExecutableFunction.Instruction>();
final var callsites = new ArrayList<PbsAst.CallExpr>();
collectCallsFromBlock(fn.body(), callsites);
for (final var callExpr : callsites) {
final var calleeName = extractSimpleCalleeName(callExpr.callee());
final var host = hostByMethodName.get(calleeName);
if (host != null) {
instructions.add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_HOST,
"",
"",
new IRBackendExecutableFunction.HostCallMetadata(
host.abiModule(),
host.abiMethod(),
host.abiVersion(),
callExpr.arguments().size(),
0),
null,
callExpr.span()));
continue;
}
final var intrinsic = intrinsicByMethodName.get(calleeName);
if (intrinsic != null) {
instructions.add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC,
"",
"",
null,
new IRBackendExecutableFunction.IntrinsicCallMetadata(
intrinsic.canonicalName(),
intrinsic.canonicalVersion(),
intrinsicIdFor(intrinsic.canonicalName(), intrinsic.canonicalVersion())),
callExpr.span()));
continue;
}
instructions.add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
moduleKey == null ? "" : moduleKey,
calleeName,
null,
null,
callExpr.span()));
}
instructions.add(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.RET,
"",
"",
null,
null,
fn.span()));
final var returnSlots = switch (fn.returnKind()) {
case INFERRED_UNIT, EXPLICIT_UNIT -> 0;
case PLAIN, RESULT -> 1;
};
final var start = safeToInt(fn.span().getStart());
final var end = safeToInt(fn.span().getEnd());
executableFunctions.add(new IRBackendExecutableFunction(
fileId,
moduleKey == null ? "" : moduleKey,
fn.name(),
start,
end,
fn.parameters().size(),
0,
returnSlots,
Math.max(4, fn.parameters().size() + 2),
ReadOnlyList.wrap(instructions),
fn.span()));
}
return ReadOnlyList.wrap(executableFunctions);
}
private int intrinsicIdFor(
final String canonicalName,
final long canonicalVersion) {
return (canonicalName + "#" + canonicalVersion).hashCode();
}
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 void collectCallsFromBlock(
final PbsAst.Block block,
final List<PbsAst.CallExpr> output) {
for (final var statement : block.statements()) {
collectCallsFromStatement(statement, output);
}
if (block.tailExpression() != null) {
collectCallsFromExpression(block.tailExpression(), output);
}
}
private void collectCallsFromStatement(
final PbsAst.Statement statement,
final List<PbsAst.CallExpr> output) {
switch (statement) {
case PbsAst.LetStatement letStatement -> collectCallsFromExpression(letStatement.initializer(), output);
case PbsAst.AssignStatement assignStatement -> collectCallsFromExpression(assignStatement.value(), output);
case PbsAst.ReturnStatement returnStatement -> {
if (returnStatement.value() != null) {
collectCallsFromExpression(returnStatement.value(), output);
}
}
case PbsAst.IfStatement ifStatement -> {
collectCallsFromExpression(ifStatement.condition(), output);
collectCallsFromBlock(ifStatement.thenBlock(), output);
if (ifStatement.elseIf() != null) {
collectCallsFromStatement(ifStatement.elseIf(), output);
}
if (ifStatement.elseBlock() != null) {
collectCallsFromBlock(ifStatement.elseBlock(), output);
}
}
case PbsAst.ForStatement forStatement -> {
collectCallsFromExpression(forStatement.fromExpression(), output);
collectCallsFromExpression(forStatement.untilExpression(), output);
if (forStatement.stepExpression() != null) {
collectCallsFromExpression(forStatement.stepExpression(), output);
}
collectCallsFromBlock(forStatement.body(), output);
}
case PbsAst.WhileStatement whileStatement -> {
collectCallsFromExpression(whileStatement.condition(), output);
collectCallsFromBlock(whileStatement.body(), output);
}
case PbsAst.ExpressionStatement expressionStatement ->
collectCallsFromExpression(expressionStatement.expression(), output);
case PbsAst.BreakStatement ignored -> {
}
case PbsAst.ContinueStatement ignored -> {
}
}
}
private void collectCallsFromExpression(
final PbsAst.Expression expression,
final List<PbsAst.CallExpr> output) {
switch (expression) {
case PbsAst.CallExpr callExpr -> {
output.add(callExpr);
collectCallsFromExpression(callExpr.callee(), output);
for (final var arg : callExpr.arguments()) {
collectCallsFromExpression(arg, output);
}
}
case PbsAst.ApplyExpr applyExpr -> {
collectCallsFromExpression(applyExpr.callee(), output);
collectCallsFromExpression(applyExpr.argument(), output);
}
case PbsAst.BinaryExpr binaryExpr -> {
collectCallsFromExpression(binaryExpr.left(), output);
collectCallsFromExpression(binaryExpr.right(), output);
}
case PbsAst.UnaryExpr unaryExpr -> collectCallsFromExpression(unaryExpr.expression(), output);
case PbsAst.ElseExpr elseExpr -> {
collectCallsFromExpression(elseExpr.optionalExpression(), output);
collectCallsFromExpression(elseExpr.fallbackExpression(), output);
}
case PbsAst.IfExpr ifExpr -> {
collectCallsFromExpression(ifExpr.condition(), output);
collectCallsFromBlock(ifExpr.thenBlock(), output);
collectCallsFromExpression(ifExpr.elseExpression(), output);
}
case PbsAst.SwitchExpr switchExpr -> {
collectCallsFromExpression(switchExpr.selector(), output);
for (final var arm : switchExpr.arms()) {
collectCallsFromBlock(arm.block(), output);
}
}
case PbsAst.HandleExpr handleExpr -> {
collectCallsFromExpression(handleExpr.value(), output);
for (final var arm : handleExpr.arms()) {
collectCallsFromBlock(arm.block(), output);
}
}
case PbsAst.AsExpr asExpr -> collectCallsFromExpression(asExpr.expression(), output);
case PbsAst.MemberExpr memberExpr -> collectCallsFromExpression(memberExpr.receiver(), output);
case PbsAst.PropagateExpr propagateExpr -> collectCallsFromExpression(propagateExpr.expression(), output);
case PbsAst.GroupExpr groupExpr -> collectCallsFromExpression(groupExpr.expression(), output);
case PbsAst.NewExpr newExpr -> {
for (final var arg : newExpr.arguments()) {
collectCallsFromExpression(arg, output);
}
}
case PbsAst.BindExpr bindExpr -> collectCallsFromExpression(bindExpr.contextExpression(), output);
case PbsAst.SomeExpr someExpr -> collectCallsFromExpression(someExpr.value(), output);
case PbsAst.OkExpr okExpr -> collectCallsFromExpression(okExpr.value(), output);
case PbsAst.TupleExpr tupleExpr -> {
for (final var item : tupleExpr.items()) {
collectCallsFromExpression(item.expression(), output);
}
}
case PbsAst.BlockExpr blockExpr -> collectCallsFromBlock(blockExpr.block(), output);
case PbsAst.IdentifierExpr ignored -> {
}
case PbsAst.IntLiteralExpr ignored -> {
}
case PbsAst.FloatLiteralExpr ignored -> {
}
case PbsAst.BoundedLiteralExpr ignored -> {
}
case PbsAst.StringLiteralExpr ignored -> {
}
case PbsAst.BoolLiteralExpr ignored -> {
}
case PbsAst.ThisExpr ignored -> {
}
case PbsAst.NoneExpr ignored -> {
}
case PbsAst.ErrExpr ignored -> {
}
case PbsAst.UnitExpr ignored -> {
}
}
}
private String extractSimpleCalleeName(final PbsAst.Expression callee) {
return switch (callee) {
case PbsAst.IdentifierExpr identifierExpr -> identifierExpr.name();
case PbsAst.MemberExpr memberExpr -> memberExpr.memberName();
case PbsAst.BindExpr bindExpr -> bindExpr.functionName();
case PbsAst.GroupExpr groupExpr -> extractSimpleCalleeName(groupExpr.expression());
default -> "<unknown>";
};
}
}

View File

@ -148,6 +148,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
parsedSource.ast(),
diagnostics,
parsedSource.sourceKind(),
parsedSource.moduleKey(),
ctx.hostAdmissionContext());
if (diagnostics.errorCount() > compileErrorBaseline) {
failedModuleKeys.add(parsedSource.moduleKey());

View File

@ -72,6 +72,32 @@ class PbsFrontendCompilerTest {
assertEquals(2, fileBackend.functions().size());
}
@Test
void shouldLowerExecutableFunctionsWithCallsiteCategories() {
final var source = """
fn b(x: int) -> int {
return x;
}
fn a() -> int {
return b(1);
}
""";
final var diagnostics = DiagnosticSink.empty();
final var compiler = new PbsFrontendCompiler();
final var fileBackend = compiler.compileFile(new FileId(42), source, diagnostics);
assertTrue(diagnostics.isEmpty(), "Valid program should not report diagnostics");
assertEquals(2, fileBackend.executableFunctions().size());
final var executableA = fileBackend.executableFunctions().stream()
.filter(fn -> fn.callableName().equals("a"))
.findFirst()
.orElseThrow();
assertTrue(executableA.instructions().stream().anyMatch(i ->
i.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_FUNC));
}
@Test
void shouldNotLowerWhenSyntaxErrorsExist() {
final var source = """