diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsReservedMetadataExtractor.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsReservedMetadataExtractor.java index 34bed80f..46bd9ac5 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsReservedMetadataExtractor.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsReservedMetadataExtractor.java @@ -79,6 +79,10 @@ public final class PbsReservedMetadataExtractor { abiModule, abiMethod, abiVersion, + switch (signature.returnKind()) { + case INFERRED_UNIT, EXPLICIT_UNIT -> 0; + case PLAIN, RESULT -> 1; + }, capabilityAttribute.isPresent(), capability, signature.span())); 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 index e586f142..6e3c0c65 100644 --- 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 @@ -28,6 +28,7 @@ final class PbsExecutableBodyLowerer { final PbsExecutableCallableRegistry callableRegistry, final IntrinsicTable intrinsicIdTable) { final var localSlotByNameId = initialLocalSlots(functionDecl, nameTable); + final var localOwnerByNameId = initialLocalOwners(functionDecl, nameTable, metadataIndex); final var context = new PbsExecutableLoweringContext( diagnostics, nameTable, @@ -35,6 +36,7 @@ final class PbsExecutableBodyLowerer { callableRegistry, intrinsicIdTable, localSlotByNameId, + localOwnerByNameId, functionDecl.parameters().size()); final var terminated = lowerBlock(functionDecl.body(), context); finalizeFunctionInstructions(functionDecl, context, terminated); @@ -70,6 +72,20 @@ final class PbsExecutableBodyLowerer { return localSlotByNameId; } + private Map initialLocalOwners( + final PbsAst.FunctionDecl functionDecl, + final NameTable nameTable, + final PbsExecutableMetadataIndex metadataIndex) { + final var localOwnerByNameId = new HashMap(); + for (final var parameter : functionDecl.parameters()) { + final var ownerCanonical = resolveBuiltinOwnerCanonical(parameter.typeRef(), metadataIndex); + if (!ownerCanonical.isBlank()) { + localOwnerByNameId.put(nameTable.register(parameter.name()), ownerCanonical); + } + } + return localOwnerByNameId; + } + private int returnSlotsFor(final PbsAst.FunctionDecl functionDecl) { return switch (functionDecl.returnKind()) { case INFERRED_UNIT, EXPLICIT_UNIT -> 0; @@ -137,8 +153,10 @@ final class PbsExecutableBodyLowerer { private boolean lowerLetStatement( final PbsAst.LetStatement letStatement, final PbsExecutableLoweringContext context) { + final var localOwner = callsiteEmitter.resolveExpressionOwnerForLowering(letStatement.initializer(), context); lowerExpression(letStatement.initializer(), context); final var localSlot = context.declareLocalSlot(letStatement.name()); + context.bindLocalOwner(letStatement.name(), localOwner); emitSetLocal(localSlot, letStatement.span(), context); return false; } @@ -161,17 +179,40 @@ final class PbsExecutableBodyLowerer { return false; } if (assignStatement.operator() == PbsAst.AssignOperator.ASSIGN) { + final var localOwner = callsiteEmitter.resolveExpressionOwnerForLowering(assignStatement.value(), context); lowerExpression(assignStatement.value(), context); + context.bindLocalOwner(target.rootName(), localOwner); emitSetLocal(targetSlot, assignStatement.span(), context); return false; } emitGetLocal(targetSlot, assignStatement.span(), context); lowerExpression(assignStatement.value(), context); emitBinaryOperatorInstruction(compoundAssignBinaryOperator(assignStatement.operator()), assignStatement.span(), context); + context.bindLocalOwner(target.rootName(), ""); emitSetLocal(targetSlot, assignStatement.span(), context); return false; } + private String resolveBuiltinOwnerCanonical( + final PbsAst.TypeRef typeRef, + final PbsExecutableMetadataIndex metadataIndex) { + final var unwrapped = unwrapGroup(typeRef); + if (unwrapped == null || unwrapped.kind() != PbsAst.TypeRefKind.SIMPLE) { + return ""; + } + return metadataIndex.builtinCanonicalBySourceType().getOrDefault(unwrapped.name(), ""); + } + + 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 boolean lowerReturnStatement( final PbsAst.ReturnStatement returnStatement, final PbsExecutableLoweringContext context) { 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 index 80584cf0..c5b40327 100644 --- 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 @@ -43,10 +43,28 @@ final class PbsExecutableCallsiteEmitter { final PbsExecutableLoweringContext context) { return new PbsResolvedCallsiteCandidates( resolveCallableCandidates(callExpr, calleeIdentity, context), - context.hostByMethodName().getOrDefault(calleeIdentity.memberNameId(), List.of()), + resolveHostCandidates(callExpr, calleeIdentity, context), resolveIntrinsicCandidates(callExpr, calleeIdentity, context)); } + private List resolveHostCandidates( + final PbsAst.CallExpr callExpr, + final PbsCalleeIdentity calleeIdentity, + final PbsExecutableLoweringContext context) { + final var hostCandidates = context.hostByMethodName().getOrDefault(calleeIdentity.memberNameId(), List.of()); + if (hostCandidates.isEmpty()) { + return List.of(); + } + final var ownerHint = hostOwnerHint(callExpr.callee()); + if (ownerHint.isBlank()) { + return hostCandidates; + } + final var filtered = hostCandidates.stream() + .filter(candidate -> ownerHint.equals(candidate.ownerName())) + .toList(); + return filtered; + } + private List resolveCallableCandidates( final PbsAst.CallExpr callExpr, final PbsCalleeIdentity calleeIdentity, @@ -126,7 +144,7 @@ final class PbsExecutableCallsiteEmitter { final PbsAst.CallExpr callExpr, final PbsExecutableLoweringContext context, final IRReservedMetadata.HostMethodBinding host) { - final var effectiveArgSlots = callExpr.arguments().size() + implicitReceiverArgSlots(callExpr.callee()); + final var effectiveArgSlots = callExpr.arguments().size() + implicitReceiverArgSlots(callExpr.callee(), context); context.instructions().add(new IRBackendExecutableFunction.Instruction( IRBackendExecutableFunction.InstructionKind.CALL_HOST, "", @@ -135,7 +153,7 @@ final class PbsExecutableCallsiteEmitter { host.abiMethod(), host.abiVersion(), effectiveArgSlots, - 0), + host.retSlots()), null, callExpr.span())); } @@ -144,7 +162,7 @@ final class PbsExecutableCallsiteEmitter { final PbsAst.CallExpr callExpr, final PbsExecutableLoweringContext context, final IRReservedMetadata.IntrinsicSurface intrinsic) { - final var effectiveArgSlots = intrinsic.argSlots() + implicitReceiverArgSlots(callExpr.callee()); + final var effectiveArgSlots = intrinsic.argSlots() + implicitReceiverArgSlots(callExpr.callee(), context); context.instructions().add(new IRBackendExecutableFunction.Instruction( IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC, "", @@ -176,7 +194,7 @@ final class PbsExecutableCallsiteEmitter { reportUnsupportedLowering("executable lowering resolved callable without signature metadata", callExpr.span(), context); return; } - final var effectiveArgSlots = callExpr.arguments().size() + implicitReceiverArgSlots(callExpr.callee()); + final var effectiveArgSlots = callExpr.arguments().size() + implicitReceiverArgSlots(callExpr.callee(), context); context.instructions().add(new IRBackendExecutableFunction.Instruction( IRBackendExecutableFunction.InstructionKind.CALL_FUNC, calleeSignature.moduleId(), @@ -211,15 +229,32 @@ final class PbsExecutableCallsiteEmitter { }; } + String resolveExpressionOwnerForLowering( + final PbsAst.Expression expression, + final PbsExecutableLoweringContext context) { + return resolveExpressionOwner(expression, context); + } + + private String hostOwnerHint(final PbsAst.Expression callee) { + return switch (callee) { + case PbsAst.MemberExpr memberExpr -> { + final var rootName = memberRootName(memberExpr.receiver()); + yield rootName == null ? "" : rootName; + } + case PbsAst.GroupExpr groupExpr -> hostOwnerHint(groupExpr.expression()); + default -> ""; + }; + } + private String resolveExpressionOwner( final PbsAst.Expression expression, final PbsExecutableLoweringContext context) { if (expression == null) { return ""; } - return switch (expression) { + return switch (expression) { case PbsAst.IdentifierExpr identifierExpr -> - context.builtinConstOwnerByNameId().getOrDefault(context.nameTable().register(identifierExpr.name()), ""); + resolveIdentifierOwner(identifierExpr.name(), context); case PbsAst.CallExpr callExpr -> resolveCallReturnOwner(callExpr, context); case PbsAst.MemberExpr memberExpr -> resolveExpressionOwner(memberExpr.receiver(), context); case PbsAst.GroupExpr groupExpr -> resolveExpressionOwner(groupExpr.expression(), context); @@ -227,6 +262,16 @@ final class PbsExecutableCallsiteEmitter { }; } + private String resolveIdentifierOwner( + final String identifier, + final PbsExecutableLoweringContext context) { + final var localOwner = context.resolveLocalOwner(identifier); + if (!localOwner.isBlank()) { + return localOwner; + } + return context.builtinConstOwnerByNameId().getOrDefault(context.nameTable().register(identifier), ""); + } + private String resolveCallReturnOwner( final PbsAst.CallExpr callExpr, final PbsExecutableLoweringContext context) { @@ -302,17 +347,22 @@ final class PbsExecutableCallsiteEmitter { }; } - private int implicitReceiverArgSlots(final PbsAst.Expression callee) { + private int implicitReceiverArgSlots( + final PbsAst.Expression callee, + final PbsExecutableLoweringContext context) { if (!(callee instanceof PbsAst.MemberExpr memberExpr)) { return 0; } - return receiverProducesRuntimeValue(memberExpr.receiver()) ? 1 : 0; + return receiverProducesRuntimeValue(memberExpr.receiver(), context) ? 1 : 0; } - private boolean receiverProducesRuntimeValue(final PbsAst.Expression receiver) { + private boolean receiverProducesRuntimeValue( + final PbsAst.Expression receiver, + final PbsExecutableLoweringContext context) { return switch (receiver) { case PbsAst.CallExpr ignored -> true; - case PbsAst.GroupExpr groupExpr -> receiverProducesRuntimeValue(groupExpr.expression()); + case PbsAst.IdentifierExpr identifierExpr -> context.resolveLocalSlot(identifierExpr.name()) != null; + case PbsAst.GroupExpr groupExpr -> receiverProducesRuntimeValue(groupExpr.expression(), context); default -> false; }; } 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 index a23e28fb..3551029d 100644 --- 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 @@ -17,6 +17,7 @@ final class PbsExecutableLoweringContext { private final PbsExecutableCallableRegistry callableRegistry; private final IntrinsicTable intrinsicIdTable; private final Map localSlotByNameId; + private final Map localOwnerByNameId; private final ArrayList instructions = new ArrayList<>(); private final ArrayDeque loopTargets = new ArrayDeque<>(); private int nextLabelId = 0; @@ -29,6 +30,7 @@ final class PbsExecutableLoweringContext { final PbsExecutableCallableRegistry callableRegistry, final IntrinsicTable intrinsicIdTable, final Map localSlotByNameId, + final Map localOwnerByNameId, final int initialLocalSlot) { this.diagnostics = diagnostics; this.nameTable = nameTable; @@ -36,6 +38,7 @@ final class PbsExecutableLoweringContext { this.callableRegistry = callableRegistry; this.intrinsicIdTable = intrinsicIdTable; this.localSlotByNameId = localSlotByNameId == null ? new HashMap<>() : localSlotByNameId; + this.localOwnerByNameId = localOwnerByNameId == null ? new HashMap<>() : localOwnerByNameId; this.nextLocalSlot = Math.max(0, initialLocalSlot); } @@ -55,6 +58,10 @@ final class PbsExecutableLoweringContext { return metadataIndex.builtinConstOwnerByNameId(); } + Map builtinCanonicalBySourceType() { + return metadataIndex.builtinCanonicalBySourceType(); + } + Map> intrinsicByOwnerAndMethod() { return metadataIndex.intrinsicByOwnerAndMethod(); } @@ -83,6 +90,27 @@ final class PbsExecutableLoweringContext { return localSlotByNameId; } + String resolveLocalOwner(final String localName) { + if (localName == null || localName.isBlank()) { + return ""; + } + return localOwnerByNameId.getOrDefault(nameTable.register(localName), ""); + } + + void bindLocalOwner( + final String localName, + final String ownerCanonicalName) { + if (localName == null || localName.isBlank()) { + return; + } + final var nameId = nameTable.register(localName); + if (ownerCanonicalName == null || ownerCanonicalName.isBlank()) { + localOwnerByNameId.remove(nameId); + return; + } + localOwnerByNameId.put(nameId, ownerCanonicalName); + } + Integer resolveLocalSlot(final String localName) { if (localName == null || localName.isBlank()) { return null; 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 index 32ce1fb3..e75b7828 100644 --- 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 @@ -42,6 +42,7 @@ public class PbsExecutableLoweringModels { record PbsExecutableMetadataIndex( Map> hostByMethodName, + Map builtinCanonicalBySourceType, Map builtinConstOwnerByNameId, Map> intrinsicByOwnerAndMethod, Map intrinsicReturnOwnerByCanonical) { 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 index 05c6a315..b5ee5e92 100644 --- 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 @@ -42,6 +42,7 @@ final class PbsExecutableMetadataIndexFactory { diagnostics); return new PbsExecutableMetadataIndex( hostByMethodName, + builtinCanonicalBySourceType, builtinConstOwnerByNameId, intrinsicByOwnerAndMethod, intrinsicReturnOwnerByCanonical); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/main.pbs b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/main.pbs index 9bc1c8ac..70c87c0d 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/main.pbs +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/core/color/main.pbs @@ -1,10 +1,5 @@ [BuiltinType(name = "Color", version = 1)] declare builtin type Color( - pub r: int, - pub g: int, - pub b: int, - pub a: int + pub raw: int ) { - [IntrinsicCall(name = "core.color.pack", version = 1)] - fn pack() -> int; } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/main.pbs b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/main.pbs index eb8dc359..a3f6edd9 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/main.pbs +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/main.pbs @@ -1,7 +1,109 @@ import { Color } from @core:color; -declare host Gfx { - [Host(module = "gfx", name = "draw_pixel", version = 1)] +declare host LowGfx { + [Host(module = "gfx", name = "clear", version = 1)] [Capability(name = "gfx")] - fn draw_pixel(x: int, y: int, color: Color) -> void; + fn clear(color: Color) -> void; + + [Host(module = "gfx", name = "fill_rect", version = 1)] + [Capability(name = "gfx")] + fn fill_rect(x: int, y: int, w: int, h: int, color: Color) -> void; + + [Host(module = "gfx", name = "draw_line", version = 1)] + [Capability(name = "gfx")] + fn draw_line(x1: int, y1: int, x2: int, y2: int, color: Color) -> void; + + [Host(module = "gfx", name = "draw_circle", version = 1)] + [Capability(name = "gfx")] + fn draw_circle(x: int, y: int, r: int, color: Color) -> void; + + [Host(module = "gfx", name = "draw_disc", version = 1)] + [Capability(name = "gfx")] + fn draw_disc(x: int, y: int, r: int, border_color: Color, fill_color: Color) -> void; + + [Host(module = "gfx", name = "draw_square", version = 1)] + [Capability(name = "gfx")] + fn draw_square(x: int, y: int, w: int, h: int, border_color: Color, fill_color: Color) -> void; + + [Host(module = "gfx", name = "set_sprite", version = 1)] + [Capability(name = "gfx")] + fn set_sprite( + asset_name: str, + index: int, + x: int, + y: int, + tile_id: int, + palette_id: int, + active: bool, + flip_x: bool, + flip_y: bool, + priority: int + ) -> int; + + [Host(module = "gfx", name = "draw_text", version = 1)] + [Capability(name = "gfx")] + fn draw_text(x: int, y: int, message: str, color: Color) -> void; + + [Host(module = "gfx", name = "clear_565", version = 1)] + [Capability(name = "gfx")] + fn clear_565(color_565: int) -> void; +} + +declare service Gfx +{ + fn clear(color: Color) -> void + { + LowGfx.clear(color); + } + + fn fill_rect(x: int, y: int, w: int, h: int, color: Color) -> void + { + LowGfx.fill_rect(x, y, w, h, color); + } + + fn draw_line(x1: int, y1: int, x2: int, y2: int, color: Color) -> void + { + LowGfx.draw_line(x1, y1, x2, y2, color); + } + + fn draw_circle(x: int, y: int, r: int, color: Color) -> void + { + LowGfx.draw_circle(x, y, r, color); + } + + fn draw_disc(x: int, y: int, r: int, border_color: Color, fill_color: Color) -> void + { + LowGfx.draw_disc(x, y, r, border_color, fill_color); + } + + fn draw_square(x: int, y: int, w: int, h: int, border_color: Color, fill_color: Color) -> void + { + LowGfx.draw_square(x, y, w, h, border_color, fill_color); + } + + fn set_sprite( + asset_name: str, + index: int, + x: int, + y: int, + tile_id: int, + palette_id: int, + active: bool, + flip_x: bool, + flip_y: bool, + priority: int + ) -> int + { + return LowGfx.set_sprite(asset_name, index, x, y, tile_id, palette_id, active, flip_x, flip_y, priority); + } + + fn draw_text(x: int, y: int, message: str, color: Color) -> void + { + LowGfx.draw_text(x, y, message, color); + } + + fn clear_565(color_565: int) -> void + { + LowGfx.clear_565(color_565); + } } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/mod.barrel b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/mod.barrel index b08ba680..01344c23 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/mod.barrel +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/mod.barrel @@ -1 +1,2 @@ -pub host Gfx; +mod host LowGfx; +pub service Gfx; diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java index 74e1c175..e2351072 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsFrontendCompilerTest.java @@ -304,12 +304,8 @@ class PbsFrontendCompilerTest { final var source = """ [BuiltinType(name = "Color", version = 1)] declare builtin type Color( - pub r: int, - pub g: int, - pub b: int + pub raw: int ) { - [IntrinsicCall(name = "core.color.pack")] - fn pack() -> int; } declare host Gfx { [Host(module = "gfx", name = "draw_pixel", version = 1)] @@ -332,7 +328,7 @@ class PbsFrontendCompilerTest { assertEquals(1, fileBackend.reservedMetadata().requiredCapabilities().size()); assertEquals("gfx", fileBackend.reservedMetadata().requiredCapabilities().getFirst()); assertEquals("Color", fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().canonicalTypeName()); - assertEquals(3, fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().fields().size()); + assertEquals(1, fileBackend.reservedMetadata().builtinTypeSurfaces().getFirst().fields().size()); } @Test @@ -453,10 +449,7 @@ class PbsFrontendCompilerTest { final var source = """ [BuiltinType(name = "Color", version = 1)] declare builtin type Color( - pub r: int, - pub g: int, - pub b: int, - pub a: int + pub raw: int ) { } diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsGateUSdkInterfaceConformanceTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsGateUSdkInterfaceConformanceTest.java index 42e4e53d..f736f3b9 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsGateUSdkInterfaceConformanceTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsGateUSdkInterfaceConformanceTest.java @@ -116,7 +116,10 @@ class PbsGateUSdkInterfaceConformanceTest { .anyMatch(t -> t.sourceTypeName().equals("InputButton") && t.intrinsics().stream().anyMatch(i -> i.canonicalName().equals("input.button.hold")))); assertTrue(positive.irBackend().getReservedMetadata().hostMethodBindings().stream() - .anyMatch(h -> h.ownerName().equals("Gfx"))); + .anyMatch(h -> h.ownerName().equals("LowGfx") + && h.abiModule().equals("gfx") + && h.abiMethod().equals("clear") + && h.abiVersion() == 1)); assertTrue(positive.irBackend().getReservedMetadata().hostMethodBindings().stream() .anyMatch(h -> h.ownerName().equals("LowLog") && h.abiModule().equals("log") diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java index c0ccf6a9..45a9401b 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/services/PBSFrontendPhaseServiceTest.java @@ -525,7 +525,7 @@ class PBSFrontendPhaseServiceTest { d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name()))); assertEquals(0, irBackend.getFunctions().size()); assertTrue(irBackend.getReservedMetadata().builtinTypeSurfaces().stream() - .anyMatch(t -> t.sourceTypeName().equals("Color") && t.fields().size() == 4)); + .anyMatch(t -> t.sourceTypeName().equals("Color") && t.fields().size() == 1)); } @Test @@ -578,7 +578,56 @@ class PBSFrontendPhaseServiceTest { d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name()))); assertEquals(0, irBackend.getFunctions().size()); assertTrue(irBackend.getReservedMetadata().hostMethodBindings().stream() - .anyMatch(h -> h.ownerName().equals("Gfx") && h.sourceMethodName().equals("draw_pixel"))); + .anyMatch(h -> h.ownerName().equals("LowGfx") && h.sourceMethodName().equals("clear"))); + } + + @Test + void shouldLowerSdkGfxServiceFacadeCallsWithoutHostCallableAmbiguity() throws IOException { + final var projectRoot = tempDir.resolve("project-bootstrap-sdk-gfx-facade-call"); + final var sourceRoot = projectRoot.resolve("src"); + final var modulePath = sourceRoot.resolve("app"); + Files.createDirectories(modulePath); + + final var sourceFile = modulePath.resolve("source.pbs"); + final var modBarrel = modulePath.resolve("mod.barrel"); + Files.writeString(sourceFile, """ + import { Gfx } from @sdk:gfx; + + fn frame() -> void + { + Gfx.clear_565(6577); + } + """); + Files.writeString(modBarrel, "pub fn frame() -> void;"); + + final var projectTable = new ProjectTable(); + final var fileTable = new FileTable(1); + final var projectId = projectTable.register(ProjectDescriptor.builder() + .rootPath(projectRoot) + .name("app") + .version("1.0.0") + .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot))) + .build()); + + registerFile(projectId, projectRoot, sourceFile, fileTable); + registerFile(projectId, projectRoot, modBarrel, fileTable); + + final var ctx = new FrontendPhaseContext( + projectTable, + fileTable, + new BuildStack(ReadOnlyList.wrap(List.of(projectId))), + 1); + final var diagnostics = DiagnosticSink.empty(); + + final var irBackend = new PBSFrontendPhaseService().compile( + ctx, + diagnostics, + LogAggregator.empty(), + BuildingIssueSink.empty()); + + assertTrue(diagnostics.stream().noneMatch(d -> + d.getCode().equals(PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name()))); + assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function -> "frame".equals(function.callableName()))); } @Test @@ -1008,6 +1057,69 @@ class PBSFrontendPhaseServiceTest { assertTrue(intrinsicCalls.contains("input.touch.x")); } + @Test + void shouldResolveIntrinsicCallsOnBuiltinValuesStoredInLocals() throws IOException { + final var projectRoot = tempDir.resolve("project-intrinsic-local-builtin-receiver"); + final var sourceRoot = projectRoot.resolve("src"); + Files.createDirectories(sourceRoot); + + final var sourceFile = sourceRoot.resolve("main.pbs"); + final var modBarrel = sourceRoot.resolve("mod.barrel"); + Files.writeString(sourceFile, """ + import { Input } from @sdk:input; + + fn frame() -> void + { + let touch = Input.touch(); + touch.x(); + touch.y(); + } + """); + Files.writeString(modBarrel, "pub fn frame() -> void;"); + + final var projectTable = new ProjectTable(); + final var fileTable = new FileTable(1); + final var projectId = projectTable.register(ProjectDescriptor.builder() + .rootPath(projectRoot) + .name("main") + .version("1.0.0") + .sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot))) + .build()); + + registerFile(projectId, projectRoot, sourceFile, fileTable); + registerFile(projectId, projectRoot, modBarrel, fileTable); + + final var ctx = new FrontendPhaseContext( + projectTable, + fileTable, + new BuildStack(ReadOnlyList.wrap(List.of(projectId))), + 1); + final var diagnostics = DiagnosticSink.empty(); + + final var irBackend = new PBSFrontendPhaseService().compile( + ctx, + diagnostics, + LogAggregator.empty(), + BuildingIssueSink.empty()); + + assertTrue(diagnostics.stream().noneMatch(d -> + d.getCode().equals(PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name())), + diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString()); + final var frameExecutable = irBackend.getExecutableFunctions().stream() + .filter(function -> "frame".equals(function.callableName())) + .findFirst() + .orElseThrow(); + final var intrinsicCalls = frameExecutable.instructions().stream() + .filter(instruction -> + instruction.kind() == p.studio.compiler.models.IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC + && instruction.intrinsicCall() != null) + .map(instruction -> instruction.intrinsicCall().canonicalName()) + .toList(); + assertTrue(intrinsicCalls.contains("input.touch")); + assertTrue(intrinsicCalls.contains("input.touch.x")); + assertTrue(intrinsicCalls.contains("input.touch.y")); + } + private void registerFile( final ProjectId projectId, final Path projectRoot, diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRReservedMetadata.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRReservedMetadata.java index 832deae0..e262a8a3 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRReservedMetadata.java +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/models/IRReservedMetadata.java @@ -32,6 +32,7 @@ public record IRReservedMetadata( String abiModule, String abiMethod, long abiVersion, + int retSlots, boolean capabilityDeclared, String requiredCapability, Span span) { @@ -40,6 +41,9 @@ public record IRReservedMetadata( sourceMethodName = Objects.requireNonNull(sourceMethodName, "sourceMethodName"); abiModule = Objects.requireNonNull(abiModule, "abiModule"); abiMethod = Objects.requireNonNull(abiMethod, "abiMethod"); + if (retSlots < 0) { + throw new IllegalArgumentException("host retSlots must be non-negative"); + } requiredCapability = requiredCapability == null ? "" : requiredCapability; span = Objects.requireNonNull(span, "span"); } diff --git a/test-projects/main/cartridge/manifest.json b/test-projects/main/cartridge/manifest.json index f9301734..4f93d84a 100644 --- a/test-projects/main/cartridge/manifest.json +++ b/test-projects/main/cartridge/manifest.json @@ -6,5 +6,5 @@ "app_version": "0.1.0", "app_mode": "Game", "entrypoint": "0", - "capabilities": ["log"] + "capabilities": ["log", "gfx"] } diff --git a/test-projects/main/cartridge/program.pbx b/test-projects/main/cartridge/program.pbx index 32ba652d..a090a149 100644 Binary files a/test-projects/main/cartridge/program.pbx and b/test-projects/main/cartridge/program.pbx differ diff --git a/test-projects/main/src/main.pbs b/test-projects/main/src/main.pbs index f3fea3a7..cd6c7fb9 100644 --- a/test-projects/main/src/main.pbs +++ b/test-projects/main/src/main.pbs @@ -1,8 +1,16 @@ +import { Color } from @core:color; + import { Log } from @sdk:log; import { Input } from @sdk:input; +import { Gfx } from @sdk:gfx; fn frame() -> void { + let touch = Input.touch(); + + Gfx.clear(new Color(6577)); + Gfx.draw_square(touch.x() - 8, touch.y() - 8, 16, 16, new Color(65535), new Color(13271)); + let a = 10; let b = 15; let total = a + b;