test: group examples into per-category folders

Move examples/*.sx and their expected/ snapshots into per-category
subfolders (examples/<category>/...). Folder = leading filename token,
with ffi-objc/ffi-jni kept whole; filenames are unchanged. The corpus
runner and LSP sweep now discover each category's expected/ dir, while
issues/ stays flat. Example 1058's repo-root-relative companion import
is made file-relative. Path strings embedded in 164 snapshots were
regenerated (path-only changes). Test-layout docs in CLAUDE.md updated.
This commit is contained in:
agra
2026-06-21 14:41:34 +03:00
parent 6d1409bc1f
commit 66bdc70bf1
3357 changed files with 456 additions and 363 deletions

View File

@@ -0,0 +1,19 @@
#import "modules/ffi/raylib.sx";
main :: () {
InitWindow(800, 600, "sx - Triangle");
while !WindowShouldClose() {
BeginDrawing();
ClearBackground(Color.{50, 50, 50, 255});
v1 := Vector2.{400.0, 100.0};
v2 := Vector2.{200.0, 500.0};
v3 := Vector2.{600.0, 500.0};
DrawTriangle(v1, v2, v3, Color.{230, 41, 55, 255});
EndDrawing();
}
CloseWindow();
}

View File

@@ -0,0 +1,280 @@
#import "modules/std.sx";
#import "modules/ffi/sdl3.sx";
#import "modules/ffi/opengl.sx";
#import "modules/math";
WIDTH :f32: 800;
HEIGHT :f32: 600;
vec4 :: (x: f32, y: f32, z: f32, w: f32) -> Vector(4, f32) {
.[x, y, z, w]
}
// ---- Matrix44: column-major 4×4 matrix ----
Matrix44 :: union {
data: [16]f32;
struct { c0, c1, c2, c3: Vector(4, f32); };
}
mat4_multiply :: (a: *Matrix44, b: *Matrix44) -> Matrix44 {
out : Matrix44 = ---;
out.c0 = a.c0 * b.c0.x + a.c1 * b.c0.y + a.c2 * b.c0.z + a.c3 * b.c0.w;
out.c1 = a.c0 * b.c1.x + a.c1 * b.c1.y + a.c2 * b.c1.z + a.c3 * b.c1.w;
out.c2 = a.c0 * b.c2.x + a.c1 * b.c2.y + a.c2 * b.c2.z + a.c3 * b.c2.w;
out.c3 = a.c0 * b.c3.x + a.c1 * b.c3.y + a.c2 * b.c3.z + a.c3 * b.c3.w;
out
}
multiply :: ufcs mat4_multiply;
mat4_perspective :: (fov: f32, aspect: f32, near: f32, far: f32) -> Matrix44 {
half := fov / 2.0;
f := cos(half) / sin(half);
m : Matrix44 = ---;
m.c0 = vec4(f / aspect, 0.0, 0.0, 0.0);
m.c1 = vec4(0.0, f, 0.0, 0.0);
m.c2 = vec4(0.0, 0.0, (far + near) / (near - far), -1.0);
m.c3 = vec4(0.0, 0.0, (2.0 * far * near) / (near - far), 0.0);
m
}
mat4_rotate_y :: (angle: f32) -> Matrix44 {
c := cos(angle);
s := sin(angle);
m : Matrix44 = ---;
m.c0 = vec4(c, 0.0, 0.0 - s, 0.0);
m.c1 = vec4(0.0, 1.0, 0.0, 0.0);
m.c2 = vec4(s, 0.0, c, 0.0);
m.c3 = vec4(0.0, 0.0, 0.0, 1.0);
m
}
mat4_rotate_x :: (angle: f32) -> Matrix44 {
c := cos(angle);
s := sin(angle);
m : Matrix44 = ---;
m.c0 = vec4(1.0, 0.0, 0.0, 0.0);
m.c1 = vec4(0.0, c, s, 0.0);
m.c2 = vec4(0.0, 0.0 - s, c, 0.0);
m.c3 = vec4(0.0, 0.0, 0.0, 1.0);
m
}
mat4_translate :: (tx: f32, ty: f32, tz: f32) -> Matrix44 {
m : Matrix44 = ---;
m.c0 = vec4(1.0, 0.0, 0.0, 0.0);
m.c1 = vec4(0.0, 1.0, 0.0, 0.0);
m.c2 = vec4(0.0, 0.0, 1.0, 0.0);
m.c3 = vec4(tx, ty, tz, 1.0);
m
}
// ---- Main ----
main :: () {
SDL_Init(SDL_INIT_VIDEO);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
window := SDL_CreateWindow("sx GL cube", xx WIDTH, xx HEIGHT, SDL_WINDOW_OPENGL);
gl_ctx := SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, gl_ctx);
SDL_GL_SetSwapInterval(1);
load_gl(SDL_GL_GetProcAddress);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
print("create program: {}\n{}\n", VERT_SHADER_SRC, FRAG_SHADER_SRC);
program := create_program(VERT_SHADER_SRC, FRAG_SHADER_SRC);
glUseProgram(program);
mvp_loc : i32 = glGetUniformLocation(program, "uMVP");
light_loc : i32 = glGetUniformLocation(program, "uLightDir");
wire_loc : i32 = glGetUniformLocation(program, "uWire");
// Cube vertices: pos(vec4 w=1) + normal(vec4 w=0), 36 vertices × 2 vec4s = 72
vertices : []Vector(4, f32) = .[
// Front face (z = +0.5)
.[-0.5, -0.5, 0.5, 1.0], vec4( 0.0, 0.0, 1.0, 0.0),
vec4( 0.5, -0.5, 0.5, 1.0), vec4( 0.0, 0.0, 1.0, 0.0),
vec4( 0.5, 0.5, 0.5, 1.0), vec4( 0.0, 0.0, 1.0, 0.0),
vec4(-0.5, -0.5, 0.5, 1.0), vec4( 0.0, 0.0, 1.0, 0.0),
vec4( 0.5, 0.5, 0.5, 1.0), vec4( 0.0, 0.0, 1.0, 0.0),
vec4(-0.5, 0.5, 0.5, 1.0), vec4( 0.0, 0.0, 1.0, 0.0),
// Back face (z = -0.5)
vec4( 0.5, -0.5, -0.5, 1.0), vec4( 0.0, 0.0, -1.0, 0.0),
vec4(-0.5, -0.5, -0.5, 1.0), vec4( 0.0, 0.0, -1.0, 0.0),
vec4(-0.5, 0.5, -0.5, 1.0), vec4( 0.0, 0.0, -1.0, 0.0),
vec4( 0.5, -0.5, -0.5, 1.0), vec4( 0.0, 0.0, -1.0, 0.0),
vec4(-0.5, 0.5, -0.5, 1.0), vec4( 0.0, 0.0, -1.0, 0.0),
vec4( 0.5, 0.5, -0.5, 1.0), vec4( 0.0, 0.0, -1.0, 0.0),
// Top face (y = +0.5)
vec4(-0.5, 0.5, 0.5, 1.0), vec4( 0.0, 1.0, 0.0, 0.0),
vec4( 0.5, 0.5, 0.5, 1.0), vec4( 0.0, 1.0, 0.0, 0.0),
vec4( 0.5, 0.5, -0.5, 1.0), vec4( 0.0, 1.0, 0.0, 0.0),
vec4(-0.5, 0.5, 0.5, 1.0), vec4( 0.0, 1.0, 0.0, 0.0),
vec4( 0.5, 0.5, -0.5, 1.0), vec4( 0.0, 1.0, 0.0, 0.0),
vec4(-0.5, 0.5, -0.5, 1.0), vec4( 0.0, 1.0, 0.0, 0.0),
// Bottom face (y = -0.5)
vec4(-0.5, -0.5, -0.5, 1.0), vec4( 0.0, -1.0, 0.0, 0.0),
vec4( 0.5, -0.5, -0.5, 1.0), vec4( 0.0, -1.0, 0.0, 0.0),
vec4( 0.5, -0.5, 0.5, 1.0), vec4( 0.0, -1.0, 0.0, 0.0),
vec4(-0.5, -0.5, -0.5, 1.0), vec4( 0.0, -1.0, 0.0, 0.0),
vec4( 0.5, -0.5, 0.5, 1.0), vec4( 0.0, -1.0, 0.0, 0.0),
vec4(-0.5, -0.5, 0.5, 1.0), vec4( 0.0, -1.0, 0.0, 0.0),
// Right face (x = +0.5)
vec4( 0.5, -0.5, 0.5, 1.0), vec4( 1.0, 0.0, 0.0, 0.0),
vec4( 0.5, -0.5, -0.5, 1.0), vec4( 1.0, 0.0, 0.0, 0.0),
vec4( 0.5, 0.5, -0.5, 1.0), vec4( 1.0, 0.0, 0.0, 0.0),
vec4( 0.5, -0.5, 0.5, 1.0), vec4( 1.0, 0.0, 0.0, 0.0),
vec4( 0.5, 0.5, -0.5, 1.0), vec4( 1.0, 0.0, 0.0, 0.0),
vec4( 0.5, 0.5, 0.5, 1.0), vec4( 1.0, 0.0, 0.0, 0.0),
// Left face (x = -0.5)
vec4(-0.5, -0.5, -0.5, 1.0), vec4(-1.0, 0.0, 0.0, 0.0),
vec4(-0.5, -0.5, 0.5, 1.0), vec4(-1.0, 0.0, 0.0, 0.0),
vec4(-0.5, 0.5, 0.5, 1.0), vec4(-1.0, 0.0, 0.0, 0.0),
vec4(-0.5, -0.5, -0.5, 1.0), vec4(-1.0, 0.0, 0.0, 0.0),
vec4(-0.5, 0.5, 0.5, 1.0), vec4(-1.0, 0.0, 0.0, 0.0),
vec4(-0.5, 0.5, -0.5, 1.0), vec4(-1.0, 0.0, 0.0, 0.0)
];
// Create VAO and VBO
vao : u32 = 0;
vbo : u32 = 0;
glGenVertexArrays(1, vao);
glGenBuffers(1, vbo);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, 1152, vertices.ptr, GL_STATIC_DRAW);
// Position attribute (location 0): 3 floats, stride 32 bytes, offset 0
glVertexAttribPointer(0, 3, GL_FLOAT, 0, 32, xx 0);
glEnableVertexAttribArray(0);
// Normal attribute (location 1): 3 floats, stride 32 bytes, offset 16
glVertexAttribPointer(1, 3, GL_FLOAT, 0, 32, xx 16);
glEnableVertexAttribArray(1);
// Set light direction
glUniform3f(light_loc, 0.5, 0.7, 1.0);
glUniform1f(wire_loc, 0.0);
// Render loop
running := true;
event : SDL_Event = .none;
while running {
while SDL_PollEvent(event) {
if event == {
case .quit: running = false;
case .key_up: (e) {
if e.key == {
case .escape: running = false;
//case .
}
}
case .window_exposed: (e) {
}
case .key_down: (e) {
k : u32 = xx e.key;
print("ts={} wid={} sc={} key={}\n",
e.timestamp, e.window_id, e.scancode, k);
}
}
}
// Compute rotation angle from time
ticks := SDL_GetTicks();
ms : f32 = xx ticks;
angle := ms * 0.001;
// Build matrices
proj := mat4_perspective(PI / 4.0, WIDTH / HEIGHT, 0.1, 100.0);
view := mat4_translate(0.0, 0.0, -3.0);
rot_y := mat4_rotate_y(angle);
rot_x := mat4_rotate_x(angle * 0.7);
model := mat4_multiply(rot_y, rot_x);
vm := mat4_multiply(view, model);
mvp := mat4_multiply(proj, vm);
glUniformMatrix4fv(mvp_loc, 1, 0, mvp.data);
glClearColor(0.1, 0.1, 0.15, 1.0);
glClear(GL_COLOR_BUFFER_BIT + GL_DEPTH_BUFFER_BIT);
// Solid pass
glUniform1f(wire_loc, 0.0);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES, 0, 36);
// Wireframe overlay
glDepthFunc(GL_LEQUAL);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glLineWidth(2.0);
glUniform1f(wire_loc, 1.0);
glDrawArrays(GL_TRIANGLES, 0, 36);
// Restore
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDepthFunc(GL_LESS);
glUniform1f(wire_loc, 0.0);
SDL_GL_SwapWindow(window);
}
SDL_GL_DestroyContext(gl_ctx);
SDL_DestroyWindow(window);
SDL_Quit();
}
VERT_SHADER_SRC : string = #string GLSL
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
uniform mat4 uMVP;
out vec3 vNormal;
out vec3 vPos;
void main() {
gl_Position = uMVP * vec4(aPos, 1.0);
vNormal = aNormal;
vPos = aPos;
}
GLSL;
FRAG_SHADER_SRC : string = #string GLSL
#version 330 core
in vec3 vNormal;
in vec3 vPos;
out vec4 FragColor;
uniform vec3 uLightDir;
uniform float uWire;
void main() {
if (uWire > 0.5) {
FragColor = vec4(0.05, 0.05, 0.05, 1.0);
return;
}
vec3 n = normalize(vNormal);
vec3 l = normalize(uLightDir);
float diff = max(dot(n, l), 0.15);
float cx = floor(vPos.x * 2.0 + 0.001);
float cy = floor(vPos.y * 2.0 + 0.001);
float cz = floor(vPos.z * 2.0 + 0.001);
float check = mod(cx + cy + cz, 2.0);
vec3 col1 = vec3(0.9, 0.5, 0.2);
vec3 col2 = vec3(0.2, 0.6, 0.9);
vec3 base = mix(col1, col2, check);
FragColor = vec4(base * diff, 1.0);
}
GLSL;

View File

@@ -0,0 +1,81 @@
// HTTP server example (macOS only)
#import "modules/std.sx";
#import "modules/std/socket.sx";
// --- Logger ---
Logger :: struct {
prefix: string;
count: i64;
}
log :: (logger: *Logger, msg: string) {
logger.count += 1;
print("[{}] {}\n", logger.prefix, msg);
}
main :: () -> i32 {
PORT :: 8080;
fd := socket(AF_INET, SOCK_STREAM, 0);
if fd < 0 {
print("error: socket()\n");
return 1;
}
opt : i32 = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, @opt, 4);
addr := SockAddr.{ sin_len = 16, sin_family = 2, sin_port = htons(PORT) };
if bind(fd, @addr, 16) < 0 {
print("error: bind()\n");
return 1;
}
if listen(fd, 10) < 0 {
print("error: listen()\n");
return 1;
}
print("listening on http://localhost:{}\n", PORT);
arena := Arena.init(context.allocator, 65536);
logger := Logger.{ prefix = "http", count = 0 };
while true {
client := accept(fd, null, null);
if client < 0 { continue; }
push Context.{ allocator = xx arena, data = xx @logger } {
handle(client);
}
arena.reset();
close(client);
}
arena.deinit();
close(fd);
0
}
handle :: (client: i32) {
// Read request
buf : [4096]u8 = ---;
read(client, buf, buf.len);
body :: "<html><body><h1>Hello from sx!</h1></body></html>";
response :: format("HTTP/1.1 200 OK\r
Content-Type: text/html\r
Connection: close\r
Content-Length: {}\r
\r\n{}", body.len, body);
write(client, response, response.len);
logger : *Logger = xx context.data;
log(logger, format("served request #{}", logger.count + 1));
}

View File

@@ -0,0 +1,16 @@
#import "modules/std.sx";
stb :: #import "vendors/stb_image/stb_image.sx";
main :: () -> i32 {
w: i32 = 0;
h: i32 = 0;
ch: i32 = 0;
img := stb.stbi_load("test.png", @w, @h, @ch, 4);
if xx img != 0 {
print("loaded {}x{} ({} channels)\n", w, h, ch);
stb.stbi_image_free(xx img);
} else {
print("no image (expected in test)\n");
}
0
}

View File

@@ -0,0 +1,45 @@
#import "modules/std.sx";
#import "modules/build.sx";
// --- #run build configuration ---
// build_options() returns a BuildOptions struct at compile time.
// Methods on it (add_link_flag, set_output_path) are compiler builtins
// that configure the build without runtime cost.
configure_build :: () abi(.compiler) {
opts := build_options();
// These calls are intercepted by the compiler at compile time.
// On a normal (non-wasm) target, inline if gates them off.
inline if OS == .wasm {
opts.set_output_path("sx-out/wasm/test.html");
opts.add_link_flag("-sUSE_SDL=3");
}
}
#run configure_build();
// --- inline if with compiler constants ---
main :: () {
// Verify #run configure_build() executed without error
print("build config: ok\n");
// Verify compiler constants are available
print("pointer size: {}\n", POINTER_SIZE);
// Verify inline if with OS/ARCH works
inline if OS == {
case .macos: { print("os: macos\n"); }
case .linux: { print("os: linux\n"); }
case .windows: { print("os: windows\n"); }
case .wasm: { print("os: wasm\n"); }
else: { print("os: unknown\n"); }
}
// Verify POINTER_SIZE is usable in inline if
inline if POINTER_SIZE == 8 {
print("64-bit platform\n");
}
inline if POINTER_SIZE == 4 {
print("32-bit platform\n");
}
}

View File

@@ -0,0 +1,12 @@
// `#framework "Name"` top-level directive registers an Apple framework for
// linking; `extern` declarations can omit the library identifier (frameworks
// resolve symbols by global namespace at link time).
#framework "CoreFoundation";
CFAbsoluteTimeGetCurrent :: () -> f64 extern;
main :: () -> i32 {
t := CFAbsoluteTimeGetCurrent();
return if t > 0.0 then 0 else 1;
}

View File

@@ -0,0 +1,121 @@
// iOS-only: bring up UIKitPlatform in Metal mode, clear the screen dark
// blue each frame, then draw a colored triangle via the GPU protocol —
// exercises create_shader (MSL compile + pipeline state), create_buffer
// + update_buffer, set_shader, set_vertex_buffer, and draw_triangles.
// Step 3b will port the UI renderer to use this same surface.
//
// Build for iOS sim:
// /Users/agra/projects/sx/zig-out/bin/sx build --target ios-sim \
// examples/63-metal-clear.sx \
// -o /tmp/MetalClear --bundle /tmp/MetalClear.app \
// --bundle-id co.swipelab.metalclear -F ~/Library/Frameworks
// codesign --force --sign - --timestamp=none /tmp/MetalClear.app
// xcrun simctl install booted /tmp/MetalClear.app
// xcrun simctl launch --terminate-running-process booted co.swipelab.metalclear
//
// This file is iOS-only and not part of the JIT regression suite (no
// tests/expected/63-metal-clear.txt). The test runner skips it on macOS.
#import "modules/std.sx";
#import "modules/ffi/objc.sx";
#import "modules/build.sx";
#import "modules/platform/api.sx";
#import "modules/platform/uikit.sx";
#import "modules/gpu/types.sx";
#import "modules/gpu/api.sx";
#import "modules/gpu/metal.sx";
#framework "UIKit";
#framework "QuartzCore";
#framework "OpenGLES";
// Pass-through vertex + fragment shader for a colored triangle. Vertex
// layout is { packed_float2 pos; packed_float4 color; } = 24 bytes —
// `packed_*` types have 4-byte alignment so the struct doesn't get
// padded between fields (a plain `float4` would force 16-byte alignment
// and pad the struct out to 32 bytes per vertex). Entry-point names
// (vmain / fmain) match what MetalGPU.create_shader looks up.
TRI_MSL :: #string MSL
#include <metal_stdlib>
using namespace metal;
struct Vertex {
packed_float2 pos;
packed_float4 color;
};
struct RasterizerData {
float4 position [[position]];
float4 color;
};
vertex RasterizerData vmain(uint vid [[vertex_id]],
constant Vertex* vertices [[buffer(0)]]) {
RasterizerData out;
out.position = float4(vertices[vid].pos, 0.0, 1.0);
out.color = float4(vertices[vid].color);
return out;
}
fragment float4 fmain(RasterizerData in [[stage_in]]) {
return in.color;
}
MSL;
TRI_VERTS : [18]f32 = .[
-0.6, -0.4, 1.0, 0.0, 0.0, 1.0,
0.6, -0.4, 0.0, 1.0, 0.0, 1.0,
0.0, 0.6, 0.0, 0.0, 1.0, 1.0,
];
g_plat : *UIKitPlatform = null;
g_gpu : *MetalGPU = null;
g_shader : ShaderHandle = 0;
g_vbuf : BufferHandle = 0;
frame :: () {
if g_plat == null { return; }
if g_gpu == null { return; }
// Lazy-init the GPU on the first frame where the layer is available
// (the layer is created during -[SxAppDelegate didFinishLaunching:]
// which fires AFTER our main() returns into UIApplicationMain).
if g_gpu.layer == null {
if g_plat.gl_layer == null { return; }
if !g_gpu.init(g_plat.gl_layer, g_plat.pixel_w, g_plat.pixel_h) { return; }
}
// Compile shader + upload vertex buffer once.
if g_shader == 0 {
g_shader = g_gpu.create_shader(TRI_MSL, "");
if g_shader == 0 { return; }
}
if g_vbuf == 0 {
g_vbuf = g_gpu.create_buffer(72); // 3 verts × 24 bytes
if g_vbuf == 0 { return; }
g_gpu.update_buffer(g_vbuf, xx @TRI_VERTS, 72);
}
bg : ClearColor = .{ r = 0.07, g = 0.10, b = 0.18, a = 1.0 };
if !g_gpu.begin_frame(bg) { return; }
g_gpu.set_shader(g_shader);
g_gpu.set_vertex_buffer(g_vbuf);
g_gpu.draw_triangles(0, 3);
g_gpu.end_frame(0.0);
}
main :: () -> i32 {
inline if OS != .ios { return 0; }
plat : *UIKitPlatform = xx context.allocator.alloc_bytes(size_of(UIKitPlatform));
plat.gpu_mode = .metal;
if !plat.init("Metal Clear", 0, 0) { return 1; }
g_plat = plat;
gpu : *MetalGPU = xx context.allocator.alloc_bytes(size_of(MetalGPU));
g_gpu = gpu;
plat.run_frame_loop(closure(frame));
plat.shutdown();
0
}

View File

@@ -0,0 +1,45 @@
// Minimal iOS app entry point — pure sx, no .m files.
//
// 1. Register a class `SxAppDelegate : UIResponder <UIApplicationDelegate>`
// dynamically, with one method:
// application:didFinishLaunchingWithOptions: returns YES (BOOL 1).
// 2. Call UIApplicationMain(0, null, null, @"SxAppDelegate") to hand off to
// UIKit's run loop. This blocks until the app exits.
//
// After install + launch, the simulator shows the default black screen
// (UIWindow not created — that's 5.8) and the AppDelegate callback fires
// once at startup. The process stays alive because UIApplicationMain
// drives the iOS run loop.
#import "modules/std.sx";
#import "modules/ffi/objc.sx";
#framework "UIKit";
UIApplicationMain :: (argc: i32, argv: *void, principal_class: *NSString, delegate_class: *NSString) -> i32 extern;
// IMP for application:didFinishLaunchingWithOptions:
// Obj-C: -(BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts
// Type encoding: "c@:@@" -- BOOL (signed char), self, _cmd, id, id
did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 abi(.c) {
NSLog(xx "[sx] application:didFinishLaunchingWithOptions: called\n");
return 1; // YES
}
main :: () -> i32 {
// SxAppDelegate : UIResponder. We deliberately don't try
// `class_addProtocol(UIApplicationDelegate)` — the linker dead-strips the
// protocol metadata from UIKit when nothing references it at compile
// time, and the C runtime can't look it up by name. UIApplicationMain
// duck-types on the method name, so this works without formal conformance.
UIResponder := objc_getClass("UIResponder".ptr);
SxAppDelegate := objc_allocateClassPair(UIResponder, "SxAppDelegate".ptr, 0);
sel := sel_registerName("application:didFinishLaunchingWithOptions:".ptr);
class_addMethod(SxAppDelegate, sel, xx did_finish_launching, "c@:@@".ptr);
objc_registerClassPair(SxAppDelegate);
// Hand off to the iOS run loop. Never returns under normal operation.
return UIApplicationMain(0, xx 0, xx 0, xx "SxAppDelegate");
}

View File

@@ -0,0 +1,98 @@
// Show something on screen — a UIWindow with a colored UIViewController root.
//
// Builds on 5.7's AppDelegate. Inside `didFinishLaunching`, we:
// 1. [[UIWindow alloc] initWithFrame:CGRect]
// 2. [[UIViewController alloc] init]; set view.backgroundColor
// 3. window.rootViewController = vc
// 4. [window makeKeyAndVisible]
//
// We hardcode the frame to 1024×1024 to avoid the struct-return ABI of
// `[UIScreen mainScreen].bounds` for now — any frame bigger than the device
// covers the screen, modulo safe-area insets.
//
// `g_window` is a module-level global so the window outlives the IMP scope
// (no ARC; UIKit retains it as the key window anyway).
#import "modules/std.sx";
#import "modules/ffi/objc.sx";
#framework "UIKit";
UIApplicationMain :: (argc: i32, argv: *void, principal_class: *NSString, delegate_class: *NSString) -> i32 extern;
g_window : *void = ---;
// AppDelegate's `window` property. iOS queries this getter to discover the
// app's key window; without it, the legacy code path creates its own empty
// window and ignores anything we configure.
window_getter :: (self: *void, _cmd: *void) -> *void abi(.c) {
return g_window;
}
window_setter :: (self: *void, _cmd: *void, w: *void) abi(.c) {
g_window = w;
}
did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 abi(.c) {
UIWindow := objc_getClass("UIWindow".ptr);
UIViewController := objc_getClass("UIViewController".ptr);
UIColor := objc_getClass("UIColor".ptr);
sel_alloc := sel_registerName("alloc".ptr);
sel_init := sel_registerName("init".ptr);
sel_init_with_scene := sel_registerName("initWithWindowScene:".ptr);
sel_view := sel_registerName("view".ptr);
sel_system_blue := sel_registerName("systemBlueColor".ptr);
sel_set_bg := sel_registerName("setBackgroundColor:".ptr);
sel_set_root_vc := sel_registerName("setRootViewController:".ptr);
sel_make_key_visible := sel_registerName("makeKeyAndVisible".ptr);
sel_connected_scenes := sel_registerName("connectedScenes".ptr);
sel_any_object := sel_registerName("anyObject".ptr);
msg_o : (*void, *void) -> *void abi(.c) = xx objc_msgSend;
msg_v : (*void, *void) -> void abi(.c) = xx objc_msgSend;
msg_oo : (*void, *void, *void) -> void abi(.c) = xx objc_msgSend;
msg_ooo : (*void, *void, *void) -> *void abi(.c) = xx objc_msgSend;
// Modern iOS path: get the connected windowScene, then construct the
// window via initWithWindowScene: so UIKit auto-sizes it and attaches
// it to the display in one step.
scenes := msg_o(app, sel_connected_scenes);
scene := msg_o(scenes, sel_any_object);
if scene == xx 0 { NSLog(xx "[sx] scene NULL\n"); return 1; }
NSLog(xx "[sx] scene: ok\n");
win_raw := msg_o(UIWindow, sel_alloc);
g_window = msg_ooo(win_raw, sel_init_with_scene, scene);
NSLog(xx "[sx] window: ok\n");
vc_raw := msg_o(UIViewController, sel_alloc);
vc := msg_o(vc_raw, sel_init);
msg_oo(g_window, sel_set_root_vc, vc);
blue := msg_o(UIColor, sel_system_blue);
view := msg_o(vc, sel_view);
msg_oo(view, sel_set_bg, blue);
msg_oo(g_window, sel_set_bg, blue);
msg_v(g_window, sel_make_key_visible);
NSLog(xx "[sx] makeKeyAndVisible done\n");
return 1;
}
main :: () -> i32 {
UIResponder := objc_getClass("UIResponder".ptr);
SxAppDelegate := objc_allocateClassPair(UIResponder, "SxAppDelegate".ptr, 0);
class_addMethod(SxAppDelegate,
sel_registerName("application:didFinishLaunchingWithOptions:".ptr),
xx did_finish_launching, "c@:@@".ptr);
class_addMethod(SxAppDelegate,
sel_registerName("window".ptr),
xx window_getter, "@@:".ptr);
class_addMethod(SxAppDelegate,
sel_registerName("setWindow:".ptr),
xx window_setter, "v@:@".ptr);
objc_registerClassPair(SxAppDelegate);
return UIApplicationMain(0, xx 0, xx 0, xx "SxAppDelegate");
}

View File

@@ -0,0 +1,20 @@
// BuildOptions.add_framework registers an Apple framework at comptime,
// equivalent to a top-level `#framework "Name"` directive. The advantage:
// you can gate it with `inline if OS == .ios { ... }` or similar logic,
// keeping the framework off non-Apple builds.
#import "modules/std.sx";
#import "modules/build.sx";
configure_build :: () abi(.compiler) {
opts := build_options();
opts.add_framework("CoreFoundation");
}
#run configure_build();
CFAbsoluteTimeGetCurrent :: () -> f64 extern;
main :: () -> i32 {
t := CFAbsoluteTimeGetCurrent();
return if t > 0.0 then 0 else 1;
}

View File

@@ -0,0 +1,96 @@
// UIKitPlatform end-to-end smoke: boots the AppDelegate, installs an
// SxGLView with a CAEAGLLayer + GLES3 context + CADisplayLink, polls
// UITouch events into ui.Event, and on every vsync clears the screen
// to a color that advances on each tap. Each tap also toggles the
// on-screen keyboard so safe_insets.bottom can be observed growing /
// shrinking under it.
//
// To visualize the safe-area / keyboard inset, the frame draws a red
// bar at the bottom whose height equals `safe_insets.bottom`. The
// platform interpolates `keyboard_height` over the keyboard's own
// animation duration, so the bar slides in lockstep with iOS's
// keyboard.
//
// Build + run:
// sx build --target ios-sim examples/66-uikit-platform.sx \
// -o /tmp/SxUIKitBoot --bundle /tmp/SxUIKitBoot.app \
// --bundle-id co.swipelab.sxuikit -F ~/Library/Frameworks
// xcrun simctl install booted /tmp/SxUIKitBoot.app
// xcrun simctl launch booted co.swipelab.sxuikit
#import "modules/std.sx";
#framework "UIKit";
#framework "OpenGLES";
#framework "QuartzCore";
#import "modules/ffi/opengl.sx";
#import "modules/ui/types.sx";
#import "modules/ui/events.sx";
#import "modules/platform/uikit.sx";
GL_SCISSOR_TEST :u32: 0x0C11;
glEnable_ : (u32) -> void = ---;
glDisable_ : (u32) -> void = ---;
glScissor_ : (i32, i32, i32, i32) -> void = ---;
g_color_index : i64 = 0;
g_keyboard_up : bool = false;
g_loaded : bool = false;
tap_frame :: () {
fc := g_uikit_plat.begin_frame();
if !g_loaded {
// Cache the GL fn-ptrs we use beyond what modules/ffi/opengl.sx loads.
glEnable_ = xx ios_gl_proc("glEnable".ptr);
glDisable_ = xx ios_gl_proc("glDisable".ptr);
glScissor_ = xx ios_gl_proc("glScissor".ptr);
g_loaded = true;
}
events := g_uikit_plat.poll_events();
i : i64 = 0;
while i < events.len {
ev := events.ptr[i];
if ev == {
case .mouse_down: {
g_color_index += 1;
if g_keyboard_up {
g_uikit_plat.hide_keyboard();
g_keyboard_up = false;
} else {
g_uikit_plat.show_keyboard();
g_keyboard_up = true;
}
}
}
i += 1;
}
phase := g_color_index % 3;
r : f32 = if phase == 0 then 0.8 else 0.1;
g : f32 = if phase == 1 then 0.8 else 0.1;
b : f32 = if phase == 2 then 0.8 else 0.1;
glViewport(0, 0, fc.pixel_w, fc.pixel_h);
glClearColor(r, g, b, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
// Bottom bar = the interpolated safe-area bottom inset.
insets := g_uikit_plat.safe_insets();
bar_h_px : i32 = xx (insets.bottom * fc.dpi_scale);
if bar_h_px > 0 {
glEnable_(GL_SCISSOR_TEST);
glScissor_(0, 0, fc.pixel_w, bar_h_px);
glClearColor(0.95, 0.25, 0.25, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glDisable_(GL_SCISSOR_TEST);
}
g_uikit_plat.end_frame();
}
main :: () -> void {
plat : *UIKitPlatform = xx libc_malloc(size_of(UIKitPlatform));
plat.init("SxUIKitPlatform", 0, 0);
plat.run_frame_loop(closure(tap_frame));
}

View File

@@ -0,0 +1,26 @@
#import "modules/std.sx";
#import "modules/build.sx";
// Post-link callback registration. The compiler invokes `post_link`
// after `target.link()` returns (sx build). Under `sx run` (JIT) the
// callback is registered but never invoked because there's no link
// phase — so the only thing this example prints under the test
// runner is `runtime main`. The post-link path is exercised via
// `sx build` separately.
puts :: (s: [:0]u8) -> i32 extern libc;
post_link :: (opt: BuildOptions) -> bool abi(.compiler) {
puts("[post-link] callback fired");
true
}
configure :: () abi(.compiler) {
opts := build_options();
on_build(post_link);
}
#run configure();
main :: () {
print("runtime main\n");
}

View File

@@ -0,0 +1,43 @@
#import "modules/std.sx";
#import "modules/std/fs.sx";
// fs.sx smoke test: every primitive the bundling phase needs.
// Creates a temp tree, writes/reads/copies/renames/chmod/deletes
// through it, then exercises basename/dirname.
main :: () {
if !create_dir_all("/tmp/sx_fs_test/a/b/c") { print("FAIL mkdir_all\n"); return; }
if !exists("/tmp/sx_fs_test/a/b/c") { print("FAIL exists after mkdir_all\n"); return; }
if !write_file("/tmp/sx_fs_test/hello.txt", "hello fs") { print("FAIL write_file\n"); return; }
if r := read_file("/tmp/sx_fs_test/hello.txt") {
if r.len != 8 { print("FAIL read length: got {}\n", r.len); return; }
print("read: {}\n", r);
} else {
print("FAIL read_file\n");
return;
}
if !copy_file("/tmp/sx_fs_test/hello.txt", "/tmp/sx_fs_test/hello.copy") { print("FAIL copy\n"); return; }
if !exists("/tmp/sx_fs_test/hello.copy") { print("FAIL exists after copy\n"); return; }
if !move("/tmp/sx_fs_test/hello.copy", "/tmp/sx_fs_test/renamed.txt") { print("FAIL rename\n"); return; }
if exists("/tmp/sx_fs_test/hello.copy") { print("FAIL old still exists after rename\n"); return; }
if !exists("/tmp/sx_fs_test/renamed.txt") { print("FAIL new missing after rename\n"); return; }
if !set_mode("/tmp/sx_fs_test/hello.txt", 493) { print("FAIL chmod\n"); return; }
if !delete_file("/tmp/sx_fs_test/hello.txt") { print("FAIL delete_file\n"); return; }
if !delete_file("/tmp/sx_fs_test/renamed.txt") { print("FAIL delete renamed\n"); return; }
if !delete_dir("/tmp/sx_fs_test/a/b/c") { print("FAIL delete c\n"); return; }
if !delete_dir("/tmp/sx_fs_test/a/b") { print("FAIL delete b\n"); return; }
if !delete_dir("/tmp/sx_fs_test/a") { print("FAIL delete a\n"); return; }
if !delete_dir("/tmp/sx_fs_test") { print("FAIL delete root\n"); return; }
print("basename(/a/b/c.txt) = {}\n", basename("/a/b/c.txt"));
print("basename(foo) = {}\n", basename("foo"));
print("dirname(/a/b/c.txt) = {}\n", dirname("/a/b/c.txt"));
print("dirname(foo) = {}\n", dirname("foo"));
print("ok\n");
}

View File

@@ -0,0 +1,47 @@
#import "modules/std.sx";
#import "modules/std/process.sx";
// process.sx smoke test: run + env + find_executable, with
// success-path and failure-path coverage.
//
// The PATH-startswith check is stable across machines (PATH always
// begins with an absolute path); `ls` is guaranteed in /bin on every
// POSIX host this targets.
main :: () {
if r := run("echo hello world") {
print("exit={}, stdout={}", r.exit_code, r.stdout);
} else {
print("FAIL run echo\n");
return;
}
if r := run("false") {
if r.exit_code == 0 { print("FAIL: false should not exit 0\n"); return; }
print("false exit={}\n", r.exit_code);
}
if n := env("SX_DEFINITELY_UNSET_VAR") {
print("FAIL: unset var returned: {}\n", n);
return;
}
print("unset var: null (ok)\n");
if w := find_executable("ls") {
// /bin/ls on macOS, /usr/bin/ls on Linux. Either is fine —
// we only assert the result is non-empty and absolute.
if w.len < 2 { print("FAIL: ls path too short\n"); return; }
if w[0] != 47 { print("FAIL: ls path not absolute\n"); return; }
print("ls is absolute (ok)\n");
} else {
print("FAIL find ls\n"); return;
}
if w := find_executable("sx_definitely_no_such_command_12345") {
print("FAIL: bogus exec returned: {}\n", w);
return;
}
print("missing exec: null (ok)\n");
print("ok\n");
}

View File

@@ -0,0 +1,18 @@
#import "modules/std.sx";
#import "modules/build.sx";
#import "modules/platform/bundle.sx";
// Register the sx-side `.app` bundler. Under `sx build` on macOS, the
// post-link callback runs and writes a real `.app` next to the
// binary. Under `sx run` (JIT) the callback is registered but never
// fires — so the test runner only sees `runtime main`.
configure :: () abi(.compiler) {
opts := build_options();
opts.set_bundle_path("HelloApp.app");
opts.set_bundle_id("co.example.hello");
on_build(bundle_main);
}
#run configure();
main :: () { print("runtime main\n"); }

View File

@@ -0,0 +1,20 @@
#import "modules/std.sx";
#import "modules/build.sx";
#import "modules/platform/bundle.sx";
// Cross-compile regression for the iOS-simulator branch of
// `platform.bundle`. On a host with the iPhoneSimulator SDK installed,
// `sx build --target ios-sim` writes a `.app` with the iOS-shaped
// Info.plist (UIDeviceFamily, LSRequiresIPhoneOS,
// UIApplicationSceneManifest, DTPlatformName=iPhoneSimulator). Ad-hoc
// codesign; no provisioning embed needed for the simulator.
configure :: () abi(.compiler) {
opts := build_options();
opts.set_bundle_path("IosSimApp.app");
opts.set_bundle_id("co.example.iossim");
on_build(bundle_main);
}
#run configure();
main :: () { print("ios-sim runtime main\n"); }

View File

@@ -0,0 +1,58 @@
// iOS *device* end-to-end exercise for `platform.bundle`. Distinct from
// 121-ios-sim-bundle.sx because the device path adds three steps that
// don't run on the simulator: provisioning profile embed, entitlements
// extraction (`security cms` + `plutil` pipeline resolving the
// wildcard `<TEAM>.*` → `<TEAM>.<bundle_id>`), and codesign with
// `--entitlements`.
//
// Build:
// sx build --target ios examples/122-ios-device-bundle.sx -o /tmp/SxDeviceProbe
//
// Install + launch (requires the device UDID to be on the profile):
// xcrun devicectl device install app --device <name> /tmp/SxDeviceProbe.app
// xcrun devicectl device process launch --device <name> co.swipelab.sxprobe
//
// The bundle id (`co.swipelab.sxprobe`) and codesign identity below
// match the test team's wildcard `SwipeS32DevProfile.mobileprovision`.
// Update the three set_* values to your own identity / profile / id to
// re-exercise on a different developer account.
#import "modules/std.sx";
#import "modules/ffi/objc.sx";
#framework "UIKit";
UIApplicationMain :: (argc: i32, argv: *void, principal_class: *NSString, delegate_class: *NSString) -> i32 extern;
#import "modules/build.sx";
#import "modules/platform/bundle.sx";
configure :: () abi(.compiler) {
opts := build_options();
opts.set_bundle_path("/tmp/SxDeviceProbe.app");
opts.set_bundle_id("co.swipelab.sxprobe");
opts.set_codesign_identity("Apple Development: Alexandru Agrapine (DC8VVHJ9W4)");
opts.set_provisioning_profile("/Users/agra/Downloads/SwipeS32DevProfile.mobileprovision");
on_build(bundle_main);
}
#run configure();
// IMP for application:didFinishLaunchingWithOptions:
// Obj-C: -(BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts
// Type encoding: "c@:@@" -- BOOL (signed char), self, _cmd, id, id
did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 abi(.c) {
NSLog(xx "[sx-device-probe] launched\n");
return 1; // YES
}
main :: () -> i32 {
UIResponder := objc_getClass("UIResponder".ptr);
SxAppDelegate := objc_allocateClassPair(UIResponder, "SxAppDelegate".ptr, 0);
sel := sel_registerName("application:didFinishLaunchingWithOptions:".ptr);
class_addMethod(SxAppDelegate, sel, xx did_finish_launching, "c@:@@".ptr);
objc_registerClassPair(SxAppDelegate);
// UIApplicationMain blocks driving iOS's run loop.
return UIApplicationMain(0, xx 0, xx 0, xx "SxAppDelegate");
}

View File

@@ -0,0 +1,9 @@
// Phase 0 (ASM stream) test-infra lock: exercises the `<name>.build` JSON
// config + `--target` threading + the host-match EXECUTE path of the corpus
// runner. The companion `.build` pins the HOST target (`{ "target": "macos" }`
// resolves to the host arch+os), so the runner threads `--target` and still
// runs the example natively — its stdout is asserted as usual.
#import "modules/std.sx";
main :: () {
print("target-host ok\n");
}

View File

@@ -0,0 +1,7 @@
// Phase 0 (ASM stream) test-infra lock: exercises the corpus runner's
// CROSS-TARGET ir-only path. The `.build` pins `x86_64-linux`, which does NOT
// match this aarch64 host, so the runner skips run/build/exec and verifies via
// `sx ir --target x86_64-linux` only — asserting exit + the `.ir` snapshot +
// stderr (no `.stdout`). Asm-free on purpose: it locks the harness gating, not
// any inline-asm lowering (that arrives in Phase A+).
main :: () -> i64 { return 0; }

View File

@@ -0,0 +1,20 @@
// ASM stream Phase E — x86_64 multi-output asm: `divq` produces quotient in rax
// and remainder in rdx, returned as a `(quot, rem)` tuple. Two `={rax}`/`={rdx}`
// value outputs ⇒ LLVM returns a `{ i64, i64 }` struct, which IS sx's tuple
// representation (so `q, r := …` destructures it directly). x86-pinned via
// `.build`: ir-only on a non-x86 host (the `.ir` snapshot locks the struct
// return + `%[name]` rewrite); runs natively on x86_64-linux. See 1647 for a
// multi-output example that executes on aarch64.
divmod :: (n: u64, d: u64) -> (quot: u64, rem: u64) {
return asm {
"divq %[d]",
[quot] "={rax}" -> u64,
[rem] "={rdx}" -> u64,
"{rax}" = n, "{rdx}" = 0, [d] "r" = d,
clobbers(.cc),
};
}
main :: () {
q, r := divmod(17, 5);
}

View File

@@ -0,0 +1,6 @@
// ASM stream Phase B — an asm with no value outputs yields no result, so its
// effects could be deleted unless it is marked `volatile`. This omits
// `volatile` ⇒ a compile error. Pins that diagnostic (mirrors Zig's rule).
// Called from `main` so lowering reaches the asm body.
nope :: () { asm { "nop" }; }
main :: () { nope(); }

View File

@@ -0,0 +1,5 @@
// ASM stream — the no-output `volatile` form runs end-to-end: a bare `nop`
// (no operands, no result) assembles and executes cleanly (exit 0). Confirms
// the no-output⇒volatile rule's positive side AND the zero-operand emit path.
nop :: () { asm volatile { "nop" }; }
main :: () { nop(); }

View File

@@ -0,0 +1,6 @@
// ASM stream Phase B — operand naming rule (§II.5): an explicit `[name]` that
// just echoes the register its own constraint pins (`[eax] "={eax}"`) carries
// no information — the operand is already auto-named after the register. Reject
// it. The useful form is a label that DIFFERS (e.g. `[quot] "={rax}"`).
f :: () -> u32 { return asm volatile { "cpuid", [eax] "={eax}" -> u32, "{eax}" = 1 }; }
main :: () { x := f(); }

View File

@@ -0,0 +1,4 @@
// ASM stream Phase B — two asm operands may not share a `[name]`: the `%[name]`
// template reference (and the result tuple field) would be ambiguous.
f :: () -> u64 { return asm volatile { "nop", [x] "=r" -> u64, [x] "r" = 5 }; }
main :: () { v := f(); }

View File

@@ -0,0 +1,10 @@
// ASM stream Phase D — inline assembly that RUNS end-to-end. An aarch64 `add`
// with two register-class inputs (`%[a]`, `%[b]`) and a value output (`%[out]`)
// returned from the function. The `.build` pins aarch64-macOS: on a matching
// host the runner executes it (exit 42); elsewhere it falls to ir-only mode and
// asserts the `.ir` snapshot (the inline_asm op + LLVM `call asm` are target-
// independent in the IR text). Regression for the full lower→emit→JIT path.
add_asm :: (a: i64, b: i64) -> i64 {
return asm { "add %[out], %[a], %[b]", [out] "=r" -> i64, [a] "r" = a, [b] "r" = b };
}
main :: () -> i64 { return add_asm(40, 2); }

View File

@@ -0,0 +1,9 @@
// ASM stream Phase D — a bare `x := asm { … -> T }` binding (not a direct
// `return asm`) types correctly: the value output flows through the local and
// out as the exit code. Regression for the `inferType` `.asm_expr` arm (without
// it the binding inferred `.unresolved` and silently produced 0). aarch64-pinned
// via `.build` → runs on a matching host, ir-only elsewhere.
main :: () -> i64 {
x := asm { "mov %[out], #99", [out] "=r" -> i64 };
return x;
}

View File

@@ -0,0 +1,20 @@
// ASM stream Phase E — multi-output asm that RUNS end-to-end on aarch64. Splits
// a value into low/high bytes via two value outputs, returned + destructured as
// a `(lo, hi)` tuple. The two outputs become an LLVM `{ i64, i64 }` struct =
// sx's tuple. aarch64-pinned via `.build`: executes on a matching host (exit
// reflects lo+hi), ir-only elsewhere.
split :: (x: u64) -> (lo: u64, hi: u64) {
return asm {
#string ASM
and %[l], %[x], #0xff
lsr %[h], %[x], #8
ASM,
[l] "=r" -> u64,
[h] "=r" -> u64,
[x] "r" = x,
};
}
main :: () -> i64 {
lo, hi := split(0x1234);
return xx (lo + hi); // 0x34 + 0x12 = 52 + 18 = 70
}

View File

@@ -0,0 +1,20 @@
// ASM stream Phase F — top-level (global) `asm { … }`: a template-only block at
// module scope, lowered to LLVM `module asm` (LLVMAppendModuleInlineAsm). It
// defines a symbol that a lib-less `extern` declaration calls into — the
// import direction reuses the existing C-FFI extern path, no new surface.
// Built+run via `aot` (a module-asm symbol lives in the final linked binary,
// not the JIT host); aarch64-macos-pinned, so ir-only on a non-matching host.
asm {
#string ASM
.global _my_add
_my_add:
add x0, x0, x1
ret
ASM,
};
my_add :: (a: i64, b: i64) -> i64 extern;
main :: () -> i64 {
return my_add(40, 2); // 42, computed by the global-asm routine
}

View File

@@ -0,0 +1,19 @@
// ASM stream Phase 2 — `-> @place` write-through output. An asm result can be
// STORED through a place (a local / struct field) instead of returned: the
// place output does NOT join the result tuple. Here one value output is
// returned (into `main_val`) while a second is written through `@other`. The
// two are combined to 42. Read-write (`+`) and indirect (`*`) place outputs are
// not yet implemented (rejected at lowering). aarch64-pinned; ir-only elsewhere.
compute :: () -> i64 {
other : i64 = 0;
main_val := asm volatile {
#string ASM
mov %[m], #5
mov %[o], #37
ASM,
[m] "=r" -> i64, // value output → returned
[o] "=r" -> @other, // place output → stored through @other
};
return main_val + other; // 5 + 37 = 42
}
main :: () -> i64 { return compute(); }

View File

@@ -0,0 +1,11 @@
// ASM stream Phase 2 — read-write (`+`) place output. The place is LOADED as a
// seed, the asm both reads and writes the operand register (tied input ↔ output),
// and the (modified) result is STORED back through the place. Increment-in-place:
// the register holds 41 on entry, the asm adds 1, 42 is written back to `x`.
// aarch64-pinned; ir-only elsewhere.
compute :: () -> i64 {
x : i64 = 41;
asm volatile { "add %[v], %[v], #1", [v] "+r" -> @x };
return x; // 42
}
main :: () -> i64 { return compute(); }

View File

@@ -0,0 +1,27 @@
// ASM stream — x86_64 Linux `write(2)` via a raw `syscall`. The canonical inline-
// asm use case: SYS_write (rax=1) with fd/buf/count pinned to rdi/rsi/rdx, the
// `syscall` instruction clobbering rcx + r11 (+ memory), and the byte count
// returned in rax. Demonstrates register-pinned inputs, a pinned value output,
// a pointer input (`*u8` → rsi), and `clobbers(.…)` lowering all at once.
//
// x86-pinned via `.build`: ir-only on a non-x86 host — the `.ir` snapshot locks
// the exact constraint string (`={rax},{rax},{rdi},{rsi},{rdx},~{rcx},~{r11},
// ~{memory}`), which is the §II.11 silent-miscompile risk zone — and runs
// natively on x86_64-linux (printing "ok\n"). See 1640 for an x86 multi-output
// example, 1645/1647/1649/1650 for aarch64 examples that execute on this host.
sys_write :: (fd: i64, buf: *u8, count: i64) -> i64 {
return asm volatile {
"syscall",
[ret] "={rax}" -> i64, // return: bytes written, in rax
"{rax}" = 1, // SYS_write (x86_64 Linux)
"{rdi}" = fd, // fd
"{rsi}" = buf, // buf
"{rdx}" = count, // count
clobbers(.rcx, .r11, .memory),
};
}
main :: () {
msg : [3]u8 = .[111, 107, 10]; // "ok\n"
n := sys_write(1, @msg[0], 3);
}

View File

@@ -0,0 +1,19 @@
// ASM stream — indirect-memory (`=*m`) place output. The place address is passed
// to the asm as a pointer and the asm writes THROUGH it (no return slot): here
// `str x9, %[out]` stores 42 into `x`'s storage directly. Distinct from a
// write-through `=` output (which returns a value that is then stored). Mixes a
// value output and an input below to exercise operand ordering. aarch64-pinned;
// ir-only elsewhere (the `.ir` locks the `=*m` constraint + `elementtype` attr).
poke :: () -> i64 {
x : i64 = 0;
asm volatile {
#string ASM
mov x9, #42
str x9, %[out]
ASM,
[out] "=*m" -> @x,
clobbers(.x9),
};
return x; // 42 — written through the pointer
}
main :: () -> i64 { return poke(); }

View File

@@ -0,0 +1,22 @@
// ASM stream — global (module-scope) `asm { … }` executed via the JIT (`sx run`),
// NOT AOT. `sx run` compiles the whole module to an in-memory object (the
// integrated assembler assembles the `module asm` block into it), then ORC
// relocates and runs it — so a module-asm symbol IS resolvable at JIT main
// execution, just like a normal symbol. The only path that can't see it is a
// COMPILE-TIME `#run` call (the interpreter resolves externs via host dlsym; the
// symbol isn't linked yet — see 1654). Sibling of 1648 (which exercises the same
// feature via AOT). aarch64-macos-pinned; ir-only elsewhere.
asm {
#string ASM
.global _my_sub
_my_sub:
sub x0, x0, x1
ret
ASM,
};
my_sub :: (a: i64, b: i64) -> i64 extern;
main :: () -> i64 {
return my_sub(44, 2); // 42, computed by the global-asm routine, under JIT
}

View File

@@ -0,0 +1,22 @@
// ASM stream — calling a global-asm symbol at COMPILE TIME (`#run`) fails loud.
// A module-asm symbol only exists once the module is assembled+linked; the
// comptime VM resolves `extern` calls via host `dlsym` (RTLD_DEFAULT),
// where the symbol is absent — so `#run my_add(…)` cannot evaluate and reports a
// clear diagnostic instead of silently misfiring. (Calling the same symbol at
// RUNTIME works under both JIT and AOT — see 1648/1653.) The failure is at
// dlsym resolution, before any asm is assembled, so it is arch-independent —
// no `.build` target needed. Regression guard for the comptime boundary.
asm {
#string ASM
.global _my_add
_my_add:
add x0, x0, x1
ret
ASM,
};
my_add :: (a: i64, b: i64) -> i64 extern;
COMPUTED :: #run my_add(40, 2); // compile-time call — module-asm symbol not yet linked
main :: () -> i64 { return COMPUTED; }

View File

@@ -0,0 +1,35 @@
// ASM stream — the round trip: sx → asm → sx. A global-asm routine (`_caller`)
// CALLS BACK into an sx function (`cb`) by its symbol, then returns. For the asm
// `bl _cb` to resolve, the sx callback needs EXTERNAL LINKAGE and a stable C
// symbol — that is exactly what `export` provides (it also implies the C ABI, so
// no hidden context parameter). `abi(.c)` alone is NOT enough: it sets the
// ABI but leaves the function `internal`, so it is dead-code-eliminated (nothing
// in the IR references it — the `bl` is opaque to the optimizer) and `_cb` is
// undefined at link. macOS gives `export "cb"` the symbol `_cb` (leading
// underscore), which the template references. aarch64-macos-pinned; runs under
// the JIT here (sx run), ir-only elsewhere.
// The sx callback — `export` gives it external linkage + the `_cb` symbol + C ABI.
cb :: (n: i64) -> i64 export "cb" {
return n + 1;
}
// A global-asm trampoline that calls back into `cb`. It saves/restores the link
// register (x30) around the `bl` — it was itself reached via `bl`, so the return
// address must survive the nested call.
asm {
#string ASM
.global _caller
_caller:
stp x29, x30, [sp, #-16]!
bl _cb // x0 = cb(x0) — back into sx
ldp x29, x30, [sp], #16
ret
ASM,
};
caller :: (n: i64) -> i64 extern;
main :: () -> i64 {
return caller(41); // sx main → asm caller → bl _cb → sx cb → 42
}

View File

@@ -0,0 +1,25 @@
// ASM stream — symbol operand (`"s"`): feed a function/global SYMBOL into the
// template so a DIRECT `bl %[fn]` (PC-relative — one fewer indirection than a
// register-indirect `blr`: no pointer load, a relative reloc, a predictable
// branch) goes straight to it. The backend emits the platform-mangled name
// (`_cb` on macOS, `cb` on Linux), so the template stays portable — no hardcoded
// underscore. Round trip: sx → asm → `bl _cb` → sx → 42. aarch64-macos-pinned;
// runs under the JIT here, ir-only elsewhere (the `.ir` locks `"s"`/`ptr @cb`).
cb :: (n: i64) -> i64 export "cb" { return n + 1; }
tramp :: (n: i64) -> i64 {
return asm volatile {
#string ASM
stp x29, x30, [sp, #-16]!
mov x0, %[arg]
bl %[fn]
mov %[res], x0
ldp x29, x30, [sp], #16
ASM,
[res] "=r" -> i64,
[arg] "r" = n,
[fn] "s" = cb, // symbol operand → direct `bl _cb`
clobbers(.x0, .x30, .memory),
};
}
main :: () -> i64 { return tramp(41); }

View File

@@ -0,0 +1,12 @@
// ASM stream — read-write (`+`) place output on x86_64 (cross-arch sibling of
// the aarch64 1650). `incq %[v]` reads the operand register, increments it, and
// the result is stored back through the place. Locks the x86 lowering of `+`:
// an output `=r` plus a tied input (`=r,0`) seeded with the place's value.
// x86-pinned via `.build`: ir-only here (the `.ir` is the assertion), runs
// natively on x86_64-linux (main returns 0 on success, 1 if the asm misbehaved).
bump :: () -> i64 {
x : i64 = 41;
asm volatile { "incq %[v]", [v] "+r" -> @x };
return x; // 42
}
main :: () -> i64 { if bump() != 42 { return 1; } return 0; }

View File

@@ -0,0 +1,12 @@
// ASM stream — indirect-memory (`=*m`) place output on x86_64 (cross-arch sibling
// of the aarch64 1652). `movq $42, %[out]` stores straight through the place's
// address — the address is passed as an opaque `ptr` with an `elementtype(i64)`
// attribute, no return slot. Note `$42`: a literal `$` in the template is escaped
// to LLVM's `$$` and emitted back as `$42` (an x86 immediate). x86-pinned;
// ir-only here, runs on x86_64-linux.
poke :: () -> i64 {
x : i64 = 0;
asm volatile { "movq $42, %[out]", [out] "=*m" -> @x };
return x; // 42
}
main :: () -> i64 { if poke() != 42 { return 1; } return 0; }

View File

@@ -0,0 +1,19 @@
// ASM stream — symbol operand (`"s"`) on x86_64 (cross-arch sibling of the
// aarch64 1656). A DIRECT `call` to an `export`ed sx function by symbol, written
// with the SAME portable `%[fn]` as the aarch64 example — the compiler injects
// the `:c` operand modifier for symbol operands, so the symbol prints bare on
// every target (x86 would otherwise render `$cb`, a bad call target). The
// backend emits the platform-mangled name (`call cb` on Linux). x86-pinned;
// ir-only here, runs on x86_64-linux. Round trip: sx → asm → call cb → sx → 42.
cb :: (n: i64) -> i64 export "cb" { return n + 1; }
tramp :: (n: i64) -> i64 {
return asm volatile {
"call %[fn]",
[ret] "={rax}" -> i64,
"{rdi}" = n, // arg in rdi (SysV)
[fn] "s" = cb, // symbol operand → direct `call cb`
clobbers(.rcx, .rdx, .rsi, .r8, .r9, .r10, .r11, .memory),
};
}
main :: () -> i64 { if tramp(41) != 42 { return 1; } return 0; }

View File

@@ -0,0 +1,29 @@
// Windows x86_64 — print "42" and exit(0) through the Win32 system-call
// boundary. The Windows analog of the Linux raw-`syscall` write (see
// 1651): Windows has no stable raw syscall ABI (NtWriteFile's ordinal
// shifts between OS builds), so the documented boundary IS kernel32 —
// `GetStdHandle` + `WriteFile` to print, `ExitProcess` to terminate.
//
// Exercises the bundled-`zig` link backend end to end: built with
// `--target windows-gnu --self-contained`, zig cc (mingw) auto-resolves
// kernel32, producing a PE32+ that prints "42\n" and exits 0.
//
// Pinned `x86_64-windows-gnu` via `.build`: ir-only on this non-Windows
// host (the `.ir` snapshot locks the Win64-ABI lowering of the three
// extern calls); runs end-to-end on a Windows x86_64 runner.
kernel32 :: #library "kernel32";
// DWORD = u32, HANDLE/LPVOID = *void, BOOL = i32.
GetStdHandle :: (n_std_handle: u32) -> *void extern;
WriteFile :: (file: *void, buf: *u8, n: u32, written: *u32, overlapped: *void) -> i32 extern;
ExitProcess :: (code: u32) -> void extern;
main :: () {
// STD_OUTPUT_HANDLE = (DWORD)-11 = 0xFFFFFFF5.
out := GetStdHandle(0xFFFFFFF5);
msg : [3]u8 = .[52, 50, 10]; // "42\n"
written : u32 = 0;
WriteFile(out, @msg[0], 3, @written, null);
ExitProcess(0);
}

View File

@@ -0,0 +1 @@
long c_marker(void) { return 42; }

View File

@@ -0,0 +1,32 @@
#import "modules/std.sx";
#import "modules/build.sx";
#import "modules/compiler.sx";
// P5.4 — the build-pipeline query primitives on the comptime VM. A custom
// `on_build` override inspects the build metadata (`c_object_paths` returns the
// VM-built `List(string)` of C companion objects), then DELEGATES to the stdlib
// `default_pipeline` for the real emit + link. The `#import c` source compiles to
// one object, so `c_object_paths()` must be a one-element list of a non-empty
// path. AOT runs the produced binary → "runtime main: 42".
#import c {
#source "1662-platform-build-pipeline-queries.c";
};
c_marker :: () -> i64 extern;
build :: (opt: BuildOptions) -> bool abi(.compiler) {
objs := c_object_paths();
if objs.len != 1 { return false; }
if objs.items[0].len == 0 { return false; }
// link_libraries must be a well-formed (possibly empty) list — touch each.
libs := link_libraries();
sum : i64 = 0;
i : i64 = 0;
while i < libs.len { sum += libs.items[i].len; i += 1; }
if sum < 0 { return false; }
return default_pipeline(opt); // the real emit + link
}
#run on_build(build);
main :: () { print("runtime main: {}\n", c_marker()); }

View File

@@ -0,0 +1,21 @@
#import "modules/std.sx";
#import "modules/build.sx";
// P5.4 — the `on_build` override. A user `#run on_build(build);` replaces the
// stdlib `default_pipeline` as the build driver; the compiler invokes it after
// codegen with the `BuildOptions` handle. This build GROWS a `List` on the build
// VM (comptime List growth works only on the VM — issue 0141; the legacy interp
// fails it) then DELEGATES to `default_pipeline` for the real emit + link, so a
// working binary is still produced → "runtime main".
build :: (opt: BuildOptions) -> bool abi(.compiler) {
names := List(string).{};
names.append("alpha");
names.append("beta");
names.append("gamma");
if names.len != 3 { return false; }
return default_pipeline(opt);
}
#run on_build(build);
main :: () { print("runtime main\n"); }

View File

@@ -0,0 +1,21 @@
// macOS `.app` bundle smoke test — the corpus's first real bundler coverage.
//
// `default_pipeline` auto-bundles when `bundle_path` is set (build.sx imports the
// sx bundler, so no explicit `on_build` is needed). `sx build` runs the bundler
// after link, producing a signed `.app`. The `.build` `bundle` directive asserts
// the `.app` structure (`Contents/MacOS`, `Info.plist`, `_CodeSignature`) and then
// cleans it up. macOS-host ONLY — the directive skips the example on other hosts.
#import "modules/std.sx";
configure :: () abi(.compiler) {
opts := build_options();
opts.set_bundle_path(".sx-tmp/1665-platform-macos-bundle-smoke.app");
opts.set_bundle_id("co.example.bundlesmoke");
}
#run configure();
main :: () -> i32 {
print("bundle smoke ok\n");
return 0;
}

View File

@@ -0,0 +1,33 @@
// Android `.apk` bundle smoke test — the corpus's first Android bundler
// coverage.
//
// `sx build --target android --apk <out.apk> --bundle-id <id> -o <lib.so>`
// cross-compiles for aarch64-linux-android and runs the sx default_pipeline
// → bundle_main, which drives javac/d8/aapt2/zipalign/apksigner to produce a
// signed APK containing `AndroidManifest.xml`, `classes.dex`,
// `lib/arm64-v8a/<lib.so>`, and `META-INF/` signatures. The `.build` `apk`
// directive builds + inspects the zip entries, then cleans up.
//
// GATED on the Android SDK (auto-discovered at $ANDROID_HOME /
// $ANDROID_SDK_ROOT / ~/Library/Android/sdk) + a real JDK on PATH — the macOS
// `/usr/bin/javac` stub is not enough. When either is missing the example
// SKIPS cleanly so a plain `zig build test` stays green.
//
// Build-only: `--target android` is a cross-compile, so the APK can't run on
// the build host. Runtime launch is validated manually on an emulator/device.
//
// Shape mirrors 1424 (a `#jni_main` Activity whose `onCreate` calls
// `super.onCreate(b)`, so the app is well-formed).
#import "modules/std.sx";
#import "modules/build.sx";
Bundle :: #jni_class("android/os/Bundle") extern { }
SxApp :: #jni_main #jni_class("co/swipelab/sxapksmoke/SxApp") {
onCreate :: (self: *Self, b: *Bundle) {
super.onCreate(b);
}
}
main :: () -> i32 { 0 }

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
no image (expected in test)

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,5 @@
--- build done ---
build config: ok
pointer size: 8
os: macos
64-bit platform

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
--- build done ---

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,2 @@
--- build done ---
runtime main

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,6 @@
read: hello fs
basename(/a/b/c.txt) = c.txt
basename(foo) = foo
dirname(/a/b/c.txt) = /a/b
dirname(foo) = .
ok

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,6 @@
exit=0, stdout=hello world
false exit=1
unset var: null (ok)
ls is absolute (ok)
missing exec: null (ok)
ok

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,2 @@
--- build done ---
ios-sim runtime main

View File

@@ -0,0 +1 @@
{ "target": "macos" }

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
target-host ok

View File

@@ -0,0 +1 @@
{ "target": "x86_64-linux" }

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,6 @@
; Function Attrs: nounwind
define i32 @main() #0 {
entry:
ret i32 0
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
{ "target": "x86_64-linux" }

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,26 @@
; Function Attrs: nounwind
define internal { i64, i64 } @divmod(i64 %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %0, ptr %alloca, align 8
%allocaN = alloca i64, align 8
store i64 %1, ptr %allocaN, align 8
%load = load i64, ptr %alloca, align 8
%loadN = load i64, ptr %allocaN, align 8
%asm = call { i64, i64 } asm "divq ${4}", "={rax},={rdx},{rax},{rdx},r,~{cc}"(i64 %load, i64 0, i64 %loadN)
ret { i64, i64 } %asm
}
; Function Attrs: nounwind
define i32 @main() #0 {
entry:
%call = call { i64, i64 } @divmod(i64 17, i64 5)
%tg = extractvalue { i64, i64 } %call, 0
%alloca = alloca i64, align 8
store i64 %tg, ptr %alloca, align 8
%tgN = extractvalue { i64, i64 } %call, 1
%allocaN = alloca i64, align 8
store i64 %tgN, ptr %allocaN, align 8
ret i32 0
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,5 @@
error: asm expression with no outputs must be marked `volatile`
--> examples/platform/1641-platform-asm-missing-volatile.sx:5:14
|
5 | nope :: () { asm { "nop" }; }
| ^^^^^^^^^^^^^

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
error: redundant asm operand name `eax` — it already names the pinned register; drop the `[eax]`
--> examples/platform/1643-platform-asm-echo-name.sx:5:25
|
5 | f :: () -> u32 { return asm volatile { "cpuid", [eax] "={eax}" -> u32, "{eax}" = 1 }; }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
error: duplicate asm operand name `x`
--> examples/platform/1644-platform-asm-duplicate-name.sx:3:25
|
3 | f :: () -> u64 { return asm volatile { "nop", [x] "=r" -> u64, [x] "r" = 5 }; }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -0,0 +1 @@
{ "target": "macos" }

View File

@@ -0,0 +1 @@
42

View File

@@ -0,0 +1,21 @@
; Function Attrs: nounwind
define internal i64 @add_asm(i64 %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %0, ptr %alloca, align 8
%allocaN = alloca i64, align 8
store i64 %1, ptr %allocaN, align 8
%load = load i64, ptr %alloca, align 8
%loadN = load i64, ptr %allocaN, align 8
%asm = call i64 asm "add ${0}, ${1}, ${2}", "=r,r,r"(i64 %load, i64 %loadN)
ret i64 %asm
}
; Function Attrs: nounwind
define i32 @main() #0 {
entry:
%call = call i64 @add_asm(i64 40, i64 2)
%ca.tr = trunc i64 %call to i32
ret i32 %ca.tr
}

View File

@@ -0,0 +1 @@
{ "target": "macos" }

Some files were not shown because too many files have changed in this diff Show More