a little bit of gfx

This commit is contained in:
bQUARKz 2026-03-10 11:51:21 +00:00
parent 079db44cd1
commit 59e33d6639
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
16 changed files with 378 additions and 35 deletions

View File

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

View File

@ -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<NameId, String> initialLocalOwners(
final PbsAst.FunctionDecl functionDecl,
final NameTable nameTable,
final PbsExecutableMetadataIndex metadataIndex) {
final var localOwnerByNameId = new HashMap<NameId, String>();
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) {

View File

@ -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<IRReservedMetadata.HostMethodBinding> 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<CallableId> 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,6 +229,23 @@ 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) {
@ -219,7 +254,7 @@ final class PbsExecutableCallsiteEmitter {
}
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;
};
}

View File

@ -17,6 +17,7 @@ final class PbsExecutableLoweringContext {
private final PbsExecutableCallableRegistry callableRegistry;
private final IntrinsicTable intrinsicIdTable;
private final Map<NameId, Integer> localSlotByNameId;
private final Map<NameId, String> localOwnerByNameId;
private final ArrayList<IRBackendExecutableFunction.Instruction> instructions = new ArrayList<>();
private final ArrayDeque<PbsLoopTargets> loopTargets = new ArrayDeque<>();
private int nextLabelId = 0;
@ -29,6 +30,7 @@ final class PbsExecutableLoweringContext {
final PbsExecutableCallableRegistry callableRegistry,
final IntrinsicTable intrinsicIdTable,
final Map<NameId, Integer> localSlotByNameId,
final Map<NameId, String> 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<String, String> builtinCanonicalBySourceType() {
return metadataIndex.builtinCanonicalBySourceType();
}
Map<PbsIntrinsicOwnerMethodKey, List<p.studio.compiler.models.IRReservedMetadata.IntrinsicSurface>> 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;

View File

@ -42,6 +42,7 @@ public class PbsExecutableLoweringModels {
record PbsExecutableMetadataIndex(
Map<NameId, List<IRReservedMetadata.HostMethodBinding>> hostByMethodName,
Map<String, String> builtinCanonicalBySourceType,
Map<NameId, String> builtinConstOwnerByNameId,
Map<PbsIntrinsicOwnerMethodKey, List<IRReservedMetadata.IntrinsicSurface>> intrinsicByOwnerAndMethod,
Map<PbsIntrinsicCanonicalKey, String> intrinsicReturnOwnerByCanonical) {

View File

@ -42,6 +42,7 @@ final class PbsExecutableMetadataIndexFactory {
diagnostics);
return new PbsExecutableMetadataIndex(
hostByMethodName,
builtinCanonicalBySourceType,
builtinConstOwnerByNameId,
intrinsicByOwnerAndMethod,
intrinsicReturnOwnerByCanonical);

View File

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

View File

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

View File

@ -1 +1,2 @@
pub host Gfx;
mod host LowGfx;
pub service Gfx;

View File

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

View File

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

View File

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

View File

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

View File

@ -6,5 +6,5 @@
"app_version": "0.1.0",
"app_mode": "Game",
"entrypoint": "0",
"capabilities": ["log"]
"capabilities": ["log", "gfx"]
}

View File

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