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:
16
examples/ffi/1200-ffi-callconv-c-callbacks.sx
Normal file
16
examples/ffi/1200-ffi-callconv-c-callbacks.sx
Normal file
@@ -0,0 +1,16 @@
|
||||
// `abi(.c)` on function pointers passed to extern callbacks — ensures
|
||||
// the function uses C ABI so it can be safely invoked from `extern`
|
||||
// functions like SDL_AddEventWatch.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
// A function with C calling convention
|
||||
add_c :: (a: i64, b: i64) -> i64 abi(.c) {
|
||||
a + b
|
||||
}
|
||||
|
||||
main :: () {
|
||||
// Call it directly — should work like any other function
|
||||
result := add_c(10, 32);
|
||||
print("abi(.c): {}\n", result);
|
||||
}
|
||||
54
examples/ffi/1201-ffi-callconv-c-globals.sx
Normal file
54
examples/ffi/1201-ffi-callconv-c-globals.sx
Normal file
@@ -0,0 +1,54 @@
|
||||
// `abi(.c)` callbacks accessing struct methods on global pointers —
|
||||
// regression coverage for prior data-corruption when the callback dispatches
|
||||
// through a global pointer to a method on the pointed-to struct.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Pipe :: struct {
|
||||
pw: i32;
|
||||
ph: i32;
|
||||
frame: i32;
|
||||
|
||||
resize :: (self: *Pipe, nw: i32, nh: i32) {
|
||||
self.pw = nw;
|
||||
self.ph = nh;
|
||||
}
|
||||
|
||||
tick :: (self: *Pipe) {
|
||||
self.frame = self.frame + 1;
|
||||
}
|
||||
}
|
||||
|
||||
g_pipe : *Pipe = ---;
|
||||
g_width : i32 = 800;
|
||||
g_height : i32 = 600;
|
||||
|
||||
do_render :: () {
|
||||
g_pipe.resize(g_width, g_height);
|
||||
g_pipe.tick();
|
||||
print("wrapper: pw={}, ph={}, frame={}\n", g_pipe.pw, g_pipe.ph, g_pipe.frame);
|
||||
}
|
||||
|
||||
callback_inline :: (userdata: *void, code: i64) -> bool abi(.c) {
|
||||
g_width = xx code;
|
||||
g_height = xx (code + 1);
|
||||
g_pipe.resize(xx g_width, xx g_height);
|
||||
g_pipe.tick();
|
||||
print("inline: pw={}, ph={}, frame={}\n", g_pipe.pw, g_pipe.ph, g_pipe.frame);
|
||||
true
|
||||
}
|
||||
|
||||
callback_wrapper :: (userdata: *void, code: i64) -> bool abi(.c) {
|
||||
g_width = xx code;
|
||||
g_height = xx (code + 1);
|
||||
do_render();
|
||||
true
|
||||
}
|
||||
|
||||
main :: () {
|
||||
pipe := Pipe.{ pw = 0, ph = 0, frame = 0 };
|
||||
g_pipe = @pipe;
|
||||
|
||||
callback_inline(xx 0, 320);
|
||||
callback_wrapper(xx 0, 640);
|
||||
}
|
||||
31
examples/ffi/1202-ffi-cc-c-large-aggregate.sx
Normal file
31
examples/ffi/1202-ffi-cc-c-large-aggregate.sx
Normal file
@@ -0,0 +1,31 @@
|
||||
// Regression test for issue-0025 path A.
|
||||
//
|
||||
// sx functions declared with `abi(.c)` that take a composite > 16 bytes
|
||||
// by value must marshal the arg through `ptr byval(<T>)` per AAPCS64 / SysV
|
||||
// AArch64: the caller copies the struct to an alloca, passes the alloca
|
||||
// pointer with a `byval(<T>)` attribute, and the callee's entry block loads
|
||||
// the struct back from the pointer.
|
||||
//
|
||||
// Before the fix, abiCoerceParamType returned the raw LLVM struct type for
|
||||
// >16-byte composites (TODO at src/ir/emit_llvm.zig:2793), so the C ABI
|
||||
// promise was silently violated whenever sx-emitted C-callable code
|
||||
// interoperated with a real C caller.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Wide :: struct {
|
||||
a: i64;
|
||||
b: i64;
|
||||
c: i64;
|
||||
d: i64;
|
||||
}
|
||||
|
||||
accept_c :: (w: Wide) -> i64 abi(.c) {
|
||||
w.a + w.b + w.c + w.d
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
w := Wide.{ a = 1, b = 10, c = 100, d = 1000 };
|
||||
if accept_c(w) != 1111 { return 1; }
|
||||
0
|
||||
}
|
||||
37
examples/ffi/1203-ffi-callconv-c-fnptr-large-aggregate.sx
Normal file
37
examples/ffi/1203-ffi-callconv-c-fnptr-large-aggregate.sx
Normal file
@@ -0,0 +1,37 @@
|
||||
// Regression test for issue-0025 path B.
|
||||
//
|
||||
// When a fn-pointer's type is spelled with `abi(.c)`, the indirect
|
||||
// call must apply the same C-ABI byval coercion that direct C-ABI calls
|
||||
// do at the call site (path A): >16-byte non-HFA aggregates are passed
|
||||
// as `ptr byval(<T>)`. Without the fix, the indirect call site builds
|
||||
// an LLVM function type whose param slot is the raw struct, which the
|
||||
// AArch64/x86_64 backend tries to lay out across registers + stack in
|
||||
// ways that don't match the byval-attributed callee signature — the
|
||||
// callee then reads garbage out of the wrong machine-state slots.
|
||||
//
|
||||
// The opt-in is the `abi(.c)` on the fn-pointer type spelling.
|
||||
// Pure-sx fn-pointer casts (no abi suffix) keep their default
|
||||
// calling convention — verified by examples/87-fnptr-cast-large-aggregate.sx.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Wide :: struct {
|
||||
a: i64;
|
||||
b: i64;
|
||||
c: i64;
|
||||
d: i64;
|
||||
}
|
||||
|
||||
accept_c :: (w: Wide) -> i64 abi(.c) {
|
||||
w.a + w.b + w.c + w.d
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
w := Wide.{ a = 1, b = 10, c = 100, d = 1000 };
|
||||
if accept_c(w) != 1111 { return 1; }
|
||||
|
||||
fn_ptr : (Wide) -> i64 abi(.c) = xx accept_c;
|
||||
if fn_ptr(w) != 1111 { return 2; }
|
||||
|
||||
0
|
||||
}
|
||||
29
examples/ffi/1204-ffi-fnptr-cast-large-aggregate.sx
Normal file
29
examples/ffi/1204-ffi-fnptr-cast-large-aggregate.sx
Normal file
@@ -0,0 +1,29 @@
|
||||
// Pure-sx fn-pointer cast: a function-pointer typed without `abi(.c)`
|
||||
// keeps the default (sx) calling convention. Passing a >16-byte aggregate
|
||||
// through that pointer must not get the C-ABI byval coercion — the sx-CC
|
||||
// callee expects the struct as an SSA value, not as a `ptr byval(<T>)`.
|
||||
//
|
||||
// Pair with examples/86-abi-c-fnptr-large-aggregate.sx, which covers
|
||||
// the opposite arm (fn-pointer typed `abi(.c)` does get byval).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Wide :: struct {
|
||||
a: i64; b: i64; c: i64; d: i64;
|
||||
}
|
||||
|
||||
accept :: (w: Wide) -> i64 {
|
||||
w.a + w.b + w.c + w.d
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
w := Wide.{ a = 1, b = 10, c = 100, d = 1000 };
|
||||
direct := accept(w);
|
||||
if direct != 1111 { return 1; }
|
||||
|
||||
fn_ptr : (Wide) -> i64 = xx accept;
|
||||
indirect := fn_ptr(w);
|
||||
if indirect != 1111 { return 2; }
|
||||
|
||||
0
|
||||
}
|
||||
13
examples/ffi/1205-ffi-extern-global-helper.sx
Normal file
13
examples/ffi/1205-ffi-extern-global-helper.sx
Normal file
@@ -0,0 +1,13 @@
|
||||
// Companion module for examples/1205-ffi-extern-global.sx (PLAN-FFI 0.10).
|
||||
// Declares the same `extern` extern global as the main file; the
|
||||
// linker should treat both decls as one symbol. We deliberately don't
|
||||
// READ `@__stdinp` from inside a helper fn body — that path is busted
|
||||
// today (see examples/issue-0037.sx) — we just expose a trivial fn so
|
||||
// this file participates in the link and the cross-file decl
|
||||
// coexistence is exercised.
|
||||
|
||||
__stdinp : *void extern;
|
||||
|
||||
stdinp_addr_present :: () -> i32 {
|
||||
1
|
||||
}
|
||||
32
examples/ffi/1205-ffi-extern-global.sx
Normal file
32
examples/ffi/1205-ffi-extern-global.sx
Normal file
@@ -0,0 +1,32 @@
|
||||
// Extern data globals via `<name> : <type> extern;`. Lets sx code
|
||||
// reference libSystem / framework symbols (NSConcreteStackBlock,
|
||||
// __stdinp, etc.) for FFI bridges. Mirrors the long-standing
|
||||
// `<fn> :: (...) -> ... extern;` form on the function side.
|
||||
//
|
||||
// Cross-file dimension (PLAN-FFI step 0.10): the helper companion
|
||||
// `1205-ffi-extern-global-helper.sx` ALSO declares `__stdinp : *void extern;`.
|
||||
// Both files referencing the same extern symbol must link cleanly —
|
||||
// LLVM dedupes the named global, the C linker resolves both refs to
|
||||
// the one libSystem symbol.
|
||||
//
|
||||
// We *don't* check that the helper computes the same address — see
|
||||
// issue-0037 (helper-function-scoped `@extern_global` lowers to
|
||||
// undef today). When that fixes, fold the helper's address back into
|
||||
// the equality check here.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "1205-ffi-extern-global-helper.sx";
|
||||
|
||||
__stdinp : *void extern;
|
||||
|
||||
main :: () -> i32 {
|
||||
addr_bits : u64 = xx @__stdinp;
|
||||
print("stdin extern global non-null: {}\n", addr_bits != 0);
|
||||
// Force the helper symbol to participate in linking (otherwise the
|
||||
// imported file's #extern decl might get dropped by the
|
||||
// dead-code stripper). The actual return value is busted today
|
||||
// — see issue-0037.
|
||||
_ := stdinp_addr_present();
|
||||
print("helper file linked: true\n");
|
||||
0
|
||||
}
|
||||
13
examples/ffi/1206-ffi-medium-struct.c
Normal file
13
examples/ffi/1206-ffi-medium-struct.c
Normal file
@@ -0,0 +1,13 @@
|
||||
// Companion to examples/101-ffi-medium-struct.sx — a single
|
||||
// roundtrip through a 16-byte integer-only struct. Pinned in a
|
||||
// dedicated example because integer aggregates in this size class
|
||||
// route through emit_llvm.zig's `[2 x i64]` ABI coercion slot, a
|
||||
// different path from the small (≤8 B) integer struct, the 16-byte
|
||||
// HFA, and the >16 B byval-pointer cases.
|
||||
|
||||
typedef struct { long long a; long long b; } Pair64;
|
||||
|
||||
Pair64 ffi_pair64_swap(Pair64 p) {
|
||||
Pair64 r = { p.b, p.a };
|
||||
return r;
|
||||
}
|
||||
33
examples/ffi/1206-ffi-medium-struct.sx
Normal file
33
examples/ffi/1206-ffi-medium-struct.sx
Normal file
@@ -0,0 +1,33 @@
|
||||
// 16-byte integer-only struct passed by value through `extern`.
|
||||
//
|
||||
// emit_llvm.zig's `abiCoerceParamType` routes 9..16-byte non-HFA
|
||||
// structs through `[2 x i64]` for register-pair passing on AAPCS64 /
|
||||
// SysV AMD64. The call-site side loads the sx struct as `{ i64, i64 }`,
|
||||
// so the verifier needs a memory-bitcast bridge in both directions
|
||||
// (`abi.struct2arr` on the way in, `abi.arr2struct` on the way back).
|
||||
//
|
||||
// Different ABI path from 8-byte register-pair structs (coerce to
|
||||
// i64), HFAs at the same total size (Vec4f stays a struct), and
|
||||
// >16-byte aggregates (passed via pointer + byval).
|
||||
//
|
||||
// This file was originally filed as issue-0036 (LLVM verifier
|
||||
// failure repro). Promoted to a focused feature example once the
|
||||
// emit_llvm.zig coerceArg branches landed.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
#import c {
|
||||
#source "1206-ffi-medium-struct.c";
|
||||
};
|
||||
|
||||
Pair64 :: struct { a: i64; b: i64; }
|
||||
|
||||
ffi_pair64_swap :: (p: Pair64) -> Pair64 extern;
|
||||
|
||||
main :: () -> i32 {
|
||||
p : Pair64 = .{ a = 1, b = 2 };
|
||||
q := ffi_pair64_swap(p);
|
||||
print("swap = ({}, {})\n", q.a, q.b);
|
||||
print("ok = {}\n", q.a == 2 and q.b == 1);
|
||||
0
|
||||
}
|
||||
28
examples/ffi/1207-ffi-extern-global-from-helper.sx
Normal file
28
examples/ffi/1207-ffi-extern-global-from-helper.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
// `xx @<extern_global>` round-trips through a non-main helper
|
||||
// function: the helper's `xx @__stdinp` cast lowers to a `bitcast`
|
||||
// IR opcode that emit_llvm.zig dispatches to `LLVMBuildPtrToInt`
|
||||
// (BitCast doesn't accept ptr↔int on modern LLVM with opaque
|
||||
// pointers), and a `u64`-returning function correctly returns
|
||||
// the address.
|
||||
//
|
||||
// Was issue-0037 — the helper used to emit `ret i64 undef` because
|
||||
// `coerceToType` had no pointer↔integer case, so the explicit
|
||||
// `xx ptr` cast produced an unchanged pointer value handed to a
|
||||
// `ret i64` slot.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
__stdinp : *void extern;
|
||||
|
||||
stdinp_addr_via_helper :: () -> u64 {
|
||||
xx @__stdinp
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
direct : u64 = xx @__stdinp;
|
||||
via_helper := stdinp_addr_via_helper();
|
||||
print("direct non-null = {}\n", direct != 0);
|
||||
print("helper non-null = {}\n", via_helper != 0);
|
||||
print("eq = {}\n", direct == via_helper);
|
||||
0
|
||||
}
|
||||
38
examples/ffi/1208-ffi-closure-capture.sx
Normal file
38
examples/ffi/1208-ffi-closure-capture.sx
Normal file
@@ -0,0 +1,38 @@
|
||||
// Closure free-variable capture works through `FfiIntrinsicCall`
|
||||
// nodes — names referenced inside `#objc_call` / `#jni_call` /
|
||||
// `#jni_static_call` argument lists from inside a closure body are
|
||||
// recognized as captured variables and bound from the closure's env
|
||||
// struct at call time. `passthrough_works` is the baseline (normal
|
||||
// expression capture); `passthrough_via_objc_call` exercises the same
|
||||
// capture through an FFI intrinsic call's arg list.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/build.sx";
|
||||
#import "modules/ffi/objc.sx";
|
||||
|
||||
passthrough_works :: (recv: *void) -> Closure(i32) -> *void {
|
||||
closure((d: i32) -> *void => recv) // captures `recv` — fine
|
||||
}
|
||||
|
||||
passthrough_via_objc_call :: (recv: *void) -> Closure(i32) -> i64 {
|
||||
// Same `recv` capture, but inside `#objc_call(...)`'s arg list.
|
||||
closure((d: i32) -> i64 => #objc_call(i64)(recv, "hash"))
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
inline if OS == .macos {
|
||||
f := passthrough_works(null);
|
||||
p := f(0);
|
||||
print("ok (passthrough works) = {}\n", p == null);
|
||||
|
||||
// Capture inside the `#objc_call` arg list.
|
||||
ns_object := objc_getClass("NSObject".ptr);
|
||||
g := passthrough_via_objc_call(ns_object);
|
||||
h := g(0);
|
||||
print("ok (passthrough via #objc_call) = {}\n", h != 0);
|
||||
}
|
||||
inline if OS != .macos {
|
||||
print("skipped (not macos)\n");
|
||||
}
|
||||
0
|
||||
}
|
||||
16
examples/ffi/1209-ffi-01-primitives.c
Normal file
16
examples/ffi/1209-ffi-01-primitives.c
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "1209-ffi-01-primitives.h"
|
||||
|
||||
int ffi_id_int (int v) { return v; }
|
||||
unsigned int ffi_id_uint (unsigned int v) { return v; }
|
||||
short ffi_id_short (short v) { return v; }
|
||||
unsigned short ffi_id_ushort(unsigned short v) { return v; }
|
||||
long long ffi_id_i64 (long long v) { return v; }
|
||||
unsigned long long ffi_id_u64 (unsigned long long v) { return v; }
|
||||
signed char ffi_id_schar (signed char v) { return v; }
|
||||
unsigned char ffi_id_uchar (unsigned char v) { return v; }
|
||||
float ffi_id_f32 (float v) { return v; }
|
||||
double ffi_id_f64 (double v) { return v; }
|
||||
void * ffi_id_ptr (void * v) { return v; }
|
||||
|
||||
int ffi_add_int (int a, int b) { return a + b; }
|
||||
double ffi_add_double(double a, double b) { return a + b; }
|
||||
20
examples/ffi/1209-ffi-01-primitives.h
Normal file
20
examples/ffi/1209-ffi-01-primitives.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// FFI baseline test helpers — one trivial roundtrip per primitive C
|
||||
// type so the sx-side test can verify both the parameter ABI and the
|
||||
// return-value ABI per type. Locking these in BEFORE the Phase 1
|
||||
// `#objc_call` / `#jni_call` work so any future lowering change that
|
||||
// silently regresses primitive marshalling shows up here.
|
||||
|
||||
int ffi_id_int (int v);
|
||||
unsigned int ffi_id_uint (unsigned int v);
|
||||
short ffi_id_short (short v);
|
||||
unsigned short ffi_id_ushort(unsigned short v);
|
||||
long long ffi_id_i64 (long long v);
|
||||
unsigned long long ffi_id_u64 (unsigned long long v);
|
||||
signed char ffi_id_schar (signed char v);
|
||||
unsigned char ffi_id_uchar (unsigned char v);
|
||||
float ffi_id_f32 (float v);
|
||||
double ffi_id_f64 (double v);
|
||||
void * ffi_id_ptr (void * v);
|
||||
|
||||
int ffi_add_int (int a, int b);
|
||||
double ffi_add_double(double a, double b);
|
||||
49
examples/ffi/1209-ffi-01-primitives.sx
Normal file
49
examples/ffi/1209-ffi-01-primitives.sx
Normal file
@@ -0,0 +1,49 @@
|
||||
// Phase 0 baseline (PLAN-FFI.md step 0.1): every primitive type passed
|
||||
// in/out of a C `extern` fn via `#import c { #include / #source }`.
|
||||
// Locks today's parameter + return ABI so Phase 1's lowering changes
|
||||
// (`#objc_call` / `#jni_call`) can't silently regress us.
|
||||
//
|
||||
// Note: c_import maps `signed char` to sx `u8` (see c_import.zig:541)
|
||||
// — narrow signed-char roundtrips are still surfaced here so any future
|
||||
// mapping change shows up as a snapshot diff.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
#import c {
|
||||
#include "1209-ffi-01-primitives.h";
|
||||
#source "1209-ffi-01-primitives.c";
|
||||
};
|
||||
|
||||
main :: () -> i32 {
|
||||
// Signed roundtrips
|
||||
print("ffi_id_int(-42) = {}\n", ffi_id_int(0 - 42));
|
||||
print("ffi_id_short(-1234) = {}\n", ffi_id_short(0 - 1234));
|
||||
print("ffi_id_i64(huge) = {}\n", ffi_id_i64(9000000000000000000));
|
||||
|
||||
// Unsigned roundtrips
|
||||
print("ffi_id_uint(0xDEADBEEF) = {}\n", ffi_id_uint(0xDEADBEEF));
|
||||
print("ffi_id_ushort(0xFFFF) = {}\n", ffi_id_ushort(0xFFFF));
|
||||
print("ffi_id_u64(0x7FEE...) = {}\n", ffi_id_u64(0x7FEEDFACECAFEBEE));
|
||||
|
||||
// Narrow char roundtrips (both map to u8 in current sx)
|
||||
print("ffi_id_schar(127) = {}\n", ffi_id_schar(127));
|
||||
print("ffi_id_uchar(255) = {}\n", ffi_id_uchar(255));
|
||||
|
||||
// Floating point
|
||||
print("ffi_id_f32(3.5) = {}\n", ffi_id_f32(3.5));
|
||||
print("ffi_id_f64(1.5) = {}\n", ffi_id_f64(1.5));
|
||||
|
||||
// Pointer roundtrip — proves *void survives the boundary
|
||||
sentinel : i32 = 42;
|
||||
p_in : *void = xx @sentinel;
|
||||
p_out := ffi_id_ptr(p_in);
|
||||
addr_in : u64 = xx p_in;
|
||||
addr_out : u64 = xx p_out;
|
||||
print("ffi_id_ptr roundtrip = {}\n", addr_in == addr_out);
|
||||
|
||||
// Arithmetic helpers — exercise multi-arg ABI
|
||||
print("ffi_add_int(7, 8) = {}\n", ffi_add_int(7, 8));
|
||||
print("ffi_add_double(0.25, 0.75) = {}\n", ffi_add_double(0.25, 0.75));
|
||||
|
||||
0
|
||||
}
|
||||
57
examples/ffi/1210-ffi-02-small-struct.c
Normal file
57
examples/ffi/1210-ffi-02-small-struct.c
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "1210-ffi-02-small-struct.h"
|
||||
|
||||
Vec2 ffi_vec2_make(float x, float y) {
|
||||
Vec2 r = { x, y };
|
||||
return r;
|
||||
}
|
||||
|
||||
Vec2 ffi_vec2_swap(Vec2 v) {
|
||||
Vec2 r = { v.y, v.x };
|
||||
return r;
|
||||
}
|
||||
|
||||
float ffi_vec2_sum(Vec2 v) {
|
||||
return v.x + v.y;
|
||||
}
|
||||
|
||||
Vec4f ffi_vec4f_make(float x, float y, float z, float w) {
|
||||
Vec4f r = { x, y, z, w };
|
||||
return r;
|
||||
}
|
||||
|
||||
Vec4f ffi_vec4f_reverse(Vec4f v) {
|
||||
Vec4f r = { v.w, v.z, v.y, v.x };
|
||||
return r;
|
||||
}
|
||||
|
||||
float ffi_vec4f_sum(Vec4f v) {
|
||||
return v.x + v.y + v.z + v.w;
|
||||
}
|
||||
|
||||
Pair64 ffi_pair64_make(long long a, long long b) {
|
||||
Pair64 r = { a, b };
|
||||
return r;
|
||||
}
|
||||
|
||||
Pair64 ffi_pair64_swap(Pair64 p) {
|
||||
Pair64 r = { p.b, p.a };
|
||||
return r;
|
||||
}
|
||||
|
||||
long long ffi_pair64_sum(Pair64 p) {
|
||||
return p.a + p.b;
|
||||
}
|
||||
|
||||
Quad32 ffi_quad32_make(int a, int b, int c, int d) {
|
||||
Quad32 r = { a, b, c, d };
|
||||
return r;
|
||||
}
|
||||
|
||||
Quad32 ffi_quad32_reverse(Quad32 q) {
|
||||
Quad32 r = { q.d, q.c, q.b, q.a };
|
||||
return r;
|
||||
}
|
||||
|
||||
int ffi_quad32_sum(Quad32 q) {
|
||||
return q.a + q.b + q.c + q.d;
|
||||
}
|
||||
29
examples/ffi/1210-ffi-02-small-struct.h
Normal file
29
examples/ffi/1210-ffi-02-small-struct.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// FFI struct-marshalling baselines covering four aggregate ABI slots:
|
||||
// Vec2 — 8 B, two f32 — register-pair (float) path
|
||||
// Vec4f — 16 B, four f32 — HFA (homogeneous float aggregate)
|
||||
// Pair64 — 16 B, two i64 — 9..16 B int ABI ([2 x i64] coercion)
|
||||
// Quad32 — 16 B, four i32 — 9..16 B int ABI ([2 x i64] coercion)
|
||||
// Declared here so the .c has a header to include; sx side imports
|
||||
// via `#source` only and re-declares the structs natively (c_import
|
||||
// rewrites struct-typed params/returns to *void).
|
||||
|
||||
typedef struct { float x; float y; } Vec2;
|
||||
typedef struct { float x; float y; float z; float w; } Vec4f;
|
||||
typedef struct { long long a; long long b; } Pair64;
|
||||
typedef struct { int a; int b; int c; int d; } Quad32;
|
||||
|
||||
Vec2 ffi_vec2_make (float x, float y);
|
||||
Vec2 ffi_vec2_swap (Vec2 v);
|
||||
float ffi_vec2_sum (Vec2 v);
|
||||
|
||||
Vec4f ffi_vec4f_make (float x, float y, float z, float w);
|
||||
Vec4f ffi_vec4f_reverse(Vec4f v);
|
||||
float ffi_vec4f_sum (Vec4f v);
|
||||
|
||||
Pair64 ffi_pair64_make (long long a, long long b);
|
||||
Pair64 ffi_pair64_swap (Pair64 p);
|
||||
long long ffi_pair64_sum (Pair64 p);
|
||||
|
||||
Quad32 ffi_quad32_make (int a, int b, int c, int d);
|
||||
Quad32 ffi_quad32_reverse(Quad32 q);
|
||||
int ffi_quad32_sum (Quad32 q);
|
||||
75
examples/ffi/1210-ffi-02-small-struct.sx
Normal file
75
examples/ffi/1210-ffi-02-small-struct.sx
Normal file
@@ -0,0 +1,75 @@
|
||||
// Phase 0 baseline (PLAN-FFI.md step 0.2): small structs (≤16 bytes)
|
||||
// passed by value into a C `extern` fn and returned by value. Four
|
||||
// shapes that exercise distinct aggregate ABI paths:
|
||||
// Vec2 — 8 B, two f32 (register pair, float)
|
||||
// Vec4f — 16 B, four f32 (HFA — homogeneous float aggregate)
|
||||
// Pair64 — 16 B, two i64 (9..16 B int — [2 x i64] coercion slot)
|
||||
// Quad32 — 16 B, four i32 (9..16 B int — same slot as Pair64)
|
||||
//
|
||||
// Pair64 / Quad32 were originally excluded (LLVM verifier rejected the
|
||||
// struct<->[2 x i64] type mismatch — see git history for issue-0036);
|
||||
// folded back in once emit_llvm.zig's coerceArg learned to bridge
|
||||
// struct <-> array via the abi.struct2arr / abi.arr2struct branches.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
// `#source` only — c_import would rewrite struct-typed params/returns
|
||||
// in the .h to *void (its struct/opaque pointer default), losing the
|
||||
// by-value ABI. The hand-written extern decls below keep sx's
|
||||
// struct types end-to-end.
|
||||
#import c {
|
||||
#source "1210-ffi-02-small-struct.c";
|
||||
};
|
||||
|
||||
Vec2 :: struct { x: f32; y: f32; }
|
||||
Vec4f :: struct { x: f32; y: f32; z: f32; w: f32; }
|
||||
Pair64 :: struct { a: i64; b: i64; }
|
||||
Quad32 :: struct { a: i32; b: i32; c: i32; d: i32; }
|
||||
|
||||
ffi_vec2_make :: (x: f32, y: f32) -> Vec2 extern;
|
||||
ffi_vec2_swap :: (v: Vec2) -> Vec2 extern;
|
||||
ffi_vec2_sum :: (v: Vec2) -> f32 extern;
|
||||
|
||||
ffi_vec4f_make :: (x: f32, y: f32, z: f32, w: f32) -> Vec4f extern;
|
||||
ffi_vec4f_reverse :: (v: Vec4f) -> Vec4f extern;
|
||||
ffi_vec4f_sum :: (v: Vec4f) -> f32 extern;
|
||||
|
||||
ffi_pair64_make :: (a: i64, b: i64) -> Pair64 extern;
|
||||
ffi_pair64_swap :: (p: Pair64) -> Pair64 extern;
|
||||
ffi_pair64_sum :: (p: Pair64) -> i64 extern;
|
||||
|
||||
ffi_quad32_make :: (a: i32, b: i32, c: i32, d: i32) -> Quad32 extern;
|
||||
ffi_quad32_reverse :: (q: Quad32) -> Quad32 extern;
|
||||
ffi_quad32_sum :: (q: Quad32) -> i32 extern;
|
||||
|
||||
main :: () -> i32 {
|
||||
// ── Vec2 (8 bytes, float pair) ─────────────────────────────────
|
||||
v := ffi_vec2_make(1.5, 2.5);
|
||||
print("vec2 make = ({}, {})\n", v.x, v.y);
|
||||
w := ffi_vec2_swap(v);
|
||||
print("vec2 swap = ({}, {})\n", w.x, w.y);
|
||||
print("vec2 sum = {}\n", ffi_vec2_sum(v));
|
||||
|
||||
// ── Vec4f (16 bytes, HFA) ──────────────────────────────────────
|
||||
f := ffi_vec4f_make(1.0, 2.0, 3.0, 4.0);
|
||||
print("vec4f make = ({}, {}, {}, {})\n", f.x, f.y, f.z, f.w);
|
||||
g := ffi_vec4f_reverse(f);
|
||||
print("vec4f rev = ({}, {}, {}, {})\n", g.x, g.y, g.z, g.w);
|
||||
print("vec4f sum = {}\n", ffi_vec4f_sum(f));
|
||||
|
||||
// ── Pair64 (16 bytes, 2×i64 — [2 x i64] coercion path) ─────────
|
||||
p := ffi_pair64_make(100, 200);
|
||||
print("pair64 make = ({}, {})\n", p.a, p.b);
|
||||
pp := ffi_pair64_swap(p);
|
||||
print("pair64 swap = ({}, {})\n", pp.a, pp.b);
|
||||
print("pair64 sum = {}\n", ffi_pair64_sum(p));
|
||||
|
||||
// ── Quad32 (16 bytes, 4×i32 — same coercion path as Pair64) ────
|
||||
q := ffi_quad32_make(10, 20, 30, 40);
|
||||
print("quad32 make = ({}, {}, {}, {})\n", q.a, q.b, q.c, q.d);
|
||||
r := ffi_quad32_reverse(q);
|
||||
print("quad32 rev = ({}, {}, {}, {})\n", r.a, r.b, r.c, r.d);
|
||||
print("quad32 sum = {}\n", ffi_quad32_sum(q));
|
||||
|
||||
0
|
||||
}
|
||||
30
examples/ffi/1211-ffi-03-large-struct.c
Normal file
30
examples/ffi/1211-ffi-03-large-struct.c
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "1211-ffi-03-large-struct.h"
|
||||
|
||||
Big24 ffi_big24_make(long long a, long long b, long long c) {
|
||||
Big24 r = { a, b, c };
|
||||
return r;
|
||||
}
|
||||
|
||||
Big24 ffi_big24_rotate(Big24 v) {
|
||||
Big24 r = { v.c, v.a, v.b };
|
||||
return r;
|
||||
}
|
||||
|
||||
long long ffi_big24_sum(Big24 v) {
|
||||
return v.a + v.b + v.c;
|
||||
}
|
||||
|
||||
Big48 ffi_big48_make(long long a, long long b, long long c,
|
||||
long long d, long long e, long long f) {
|
||||
Big48 r = { a, b, c, d, e, f };
|
||||
return r;
|
||||
}
|
||||
|
||||
Big48 ffi_big48_reverse(Big48 v) {
|
||||
Big48 r = { v.f, v.e, v.d, v.c, v.b, v.a };
|
||||
return r;
|
||||
}
|
||||
|
||||
long long ffi_big48_sum(Big48 v) {
|
||||
return v.a + v.b + v.c + v.d + v.e + v.f;
|
||||
}
|
||||
22
examples/ffi/1211-ffi-03-large-struct.h
Normal file
22
examples/ffi/1211-ffi-03-large-struct.h
Normal file
@@ -0,0 +1,22 @@
|
||||
// FFI large-struct (>16 B) by-value roundtrips. These route through
|
||||
// the byval-pointer ABI path (caller copies onto its stack, hands the
|
||||
// callee a pointer; on AAPCS64 a separate `x8` indirect-return
|
||||
// register; on SysV AMD64 a hidden first arg). Distinct from the
|
||||
// register-pair / [2 x i64] / HFA paths the small-struct baseline
|
||||
// covers — locking those in here keeps the byval path honest.
|
||||
//
|
||||
// Big24 — 24 B, three i64
|
||||
// Big48 — 48 B, six i64
|
||||
|
||||
typedef struct { long long a; long long b; long long c; } Big24;
|
||||
typedef struct { long long a; long long b; long long c;
|
||||
long long d; long long e; long long f; } Big48;
|
||||
|
||||
Big24 ffi_big24_make (long long a, long long b, long long c);
|
||||
Big24 ffi_big24_rotate(Big24 v);
|
||||
long long ffi_big24_sum (Big24 v);
|
||||
|
||||
Big48 ffi_big48_make (long long a, long long b, long long c,
|
||||
long long d, long long e, long long f);
|
||||
Big48 ffi_big48_reverse(Big48 v);
|
||||
long long ffi_big48_sum (Big48 v);
|
||||
53
examples/ffi/1211-ffi-03-large-struct.sx
Normal file
53
examples/ffi/1211-ffi-03-large-struct.sx
Normal file
@@ -0,0 +1,53 @@
|
||||
// Phase 0 baseline (PLAN-FFI.md step 0.3): structs >16 bytes passed
|
||||
// by value into a C `extern` fn and returned by value. Exercises
|
||||
// the byval-pointer ABI path — the caller copies the struct onto its
|
||||
// stack and hands a pointer to the callee; on AAPCS64 the return
|
||||
// uses the indirect `x8` register; on SysV AMD64 the return is a
|
||||
// hidden first arg pointer that the caller allocates.
|
||||
//
|
||||
// Distinct from the register-pair / [2 x i64] / HFA paths the
|
||||
// small-struct baseline (ffi-02) covers. The two relevant emit_llvm
|
||||
// helpers are `needsByval` (returns true when size > 16) and
|
||||
// `materializeByvalArg` (alloca + store + pass pointer).
|
||||
//
|
||||
// Big24 — 24 B, three i64
|
||||
// Big48 — 48 B, six i64
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
#import c {
|
||||
#source "1211-ffi-03-large-struct.c";
|
||||
};
|
||||
|
||||
Big24 :: struct { a: i64; b: i64; c: i64; }
|
||||
Big48 :: struct {
|
||||
a: i64; b: i64; c: i64;
|
||||
d: i64; e: i64; f: i64;
|
||||
}
|
||||
|
||||
ffi_big24_make :: (a: i64, b: i64, c: i64) -> Big24 extern;
|
||||
ffi_big24_rotate :: (v: Big24) -> Big24 extern;
|
||||
ffi_big24_sum :: (v: Big24) -> i64 extern;
|
||||
|
||||
ffi_big48_make :: (a: i64, b: i64, c: i64,
|
||||
d: i64, e: i64, f: i64) -> Big48 extern;
|
||||
ffi_big48_reverse :: (v: Big48) -> Big48 extern;
|
||||
ffi_big48_sum :: (v: Big48) -> i64 extern;
|
||||
|
||||
main :: () -> i32 {
|
||||
// ── Big24 (24 bytes, byval pointer) ────────────────────────────
|
||||
v := ffi_big24_make(1, 2, 3);
|
||||
print("big24 make = ({}, {}, {})\n", v.a, v.b, v.c);
|
||||
w := ffi_big24_rotate(v);
|
||||
print("big24 rotate = ({}, {}, {})\n", w.a, w.b, w.c);
|
||||
print("big24 sum = {}\n", ffi_big24_sum(v));
|
||||
|
||||
// ── Big48 (48 bytes, byval pointer) ────────────────────────────
|
||||
x := ffi_big48_make(10, 20, 30, 40, 50, 60);
|
||||
print("big48 make = ({}, {}, {}, {}, {}, {})\n", x.a, x.b, x.c, x.d, x.e, x.f);
|
||||
y := ffi_big48_reverse(x);
|
||||
print("big48 reverse = ({}, {}, {}, {}, {}, {})\n", y.a, y.b, y.c, y.d, y.e, y.f);
|
||||
print("big48 sum = {}\n", ffi_big48_sum(x));
|
||||
|
||||
0
|
||||
}
|
||||
29
examples/ffi/1212-ffi-04-fp-struct.c
Normal file
29
examples/ffi/1212-ffi-04-fp-struct.c
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "1212-ffi-04-fp-struct.h"
|
||||
|
||||
FQuad ffi_fquad_make(float a, float b, float c, float d) {
|
||||
FQuad r = { a, b, c, d };
|
||||
return r;
|
||||
}
|
||||
|
||||
FQuad ffi_fquad_reverse(FQuad v) {
|
||||
FQuad r = { v.d, v.c, v.b, v.a };
|
||||
return r;
|
||||
}
|
||||
|
||||
float ffi_fquad_sum(FQuad v) {
|
||||
return v.a + v.b + v.c + v.d;
|
||||
}
|
||||
|
||||
DQuad ffi_dquad_make(double a, double b, double c, double d) {
|
||||
DQuad r = { a, b, c, d };
|
||||
return r;
|
||||
}
|
||||
|
||||
DQuad ffi_dquad_reverse(DQuad v) {
|
||||
DQuad r = { v.d, v.c, v.b, v.a };
|
||||
return r;
|
||||
}
|
||||
|
||||
double ffi_dquad_sum(DQuad v) {
|
||||
return v.a + v.b + v.c + v.d;
|
||||
}
|
||||
20
examples/ffi/1212-ffi-04-fp-struct.h
Normal file
20
examples/ffi/1212-ffi-04-fp-struct.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Focused FP-aggregate (HFA) FFI baselines. Distinct from the int-aggregate
|
||||
// register-coercion paths because all-float / all-double structs of ≤4 fields
|
||||
// stay as struct values in LLVM and are passed/returned via the float
|
||||
// register file (AAPCS64 v0..v3; SysV AMD64 xmm0..xmm7). This was the
|
||||
// `UIEdgeInsets`-as-f32-vs-f64 landmine — pinned here so a future ABI rule
|
||||
// change that wrecks the FP path fails this test directly.
|
||||
//
|
||||
// FQuad — 16 B, four float (small HFA; same slot as Vec4f)
|
||||
// DQuad — 32 B, four double (UIEdgeInsets-shape HFA)
|
||||
|
||||
typedef struct { float a; float b; float c; float d; } FQuad;
|
||||
typedef struct { double a; double b; double c; double d; } DQuad;
|
||||
|
||||
FQuad ffi_fquad_make (float a, float b, float c, float d);
|
||||
FQuad ffi_fquad_reverse(FQuad v);
|
||||
float ffi_fquad_sum (FQuad v);
|
||||
|
||||
DQuad ffi_dquad_make (double a, double b, double c, double d);
|
||||
DQuad ffi_dquad_reverse(DQuad v);
|
||||
double ffi_dquad_sum (DQuad v);
|
||||
49
examples/ffi/1212-ffi-04-fp-struct.sx
Normal file
49
examples/ffi/1212-ffi-04-fp-struct.sx
Normal file
@@ -0,0 +1,49 @@
|
||||
// Phase 0 baseline (PLAN-FFI.md step 0.4): focused FP-aggregate (HFA)
|
||||
// FFI test. All-float / all-double aggregates of ≤4 fields stay as
|
||||
// struct values in LLVM and pass through the float register file
|
||||
// (AAPCS64 v0..v3, SysV AMD64 xmm0..xmm7). Distinct from the int
|
||||
// register-coercion paths (i64 / [2 x i64]).
|
||||
//
|
||||
// FQuad — 16 B, four f32 (same slot as ffi-02's Vec4f)
|
||||
// DQuad — 32 B, four f64 (UIEdgeInsets-shape HFA — the
|
||||
// f32-vs-f64 landmine from this session)
|
||||
//
|
||||
// Already nominally covered by ffi-02's Vec4f, but pinning it as a
|
||||
// focused single-file test means a future ABI rule change that
|
||||
// breaks the FP path fails *this* test directly without a noisy
|
||||
// drag-in from the multi-shape baseline.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
#import c {
|
||||
#source "1212-ffi-04-fp-struct.c";
|
||||
};
|
||||
|
||||
FQuad :: struct { a: f32; b: f32; c: f32; d: f32; }
|
||||
DQuad :: struct { a: f64; b: f64; c: f64; d: f64; }
|
||||
|
||||
ffi_fquad_make :: (a: f32, b: f32, c: f32, d: f32) -> FQuad extern;
|
||||
ffi_fquad_reverse :: (v: FQuad) -> FQuad extern;
|
||||
ffi_fquad_sum :: (v: FQuad) -> f32 extern;
|
||||
|
||||
ffi_dquad_make :: (a: f64, b: f64, c: f64, d: f64) -> DQuad extern;
|
||||
ffi_dquad_reverse :: (v: DQuad) -> DQuad extern;
|
||||
ffi_dquad_sum :: (v: DQuad) -> f64 extern;
|
||||
|
||||
main :: () -> i32 {
|
||||
// ── FQuad (16 B, 4×f32 HFA) ────────────────────────────────────
|
||||
f := ffi_fquad_make(1.0, 2.0, 3.0, 4.0);
|
||||
print("fquad make = ({}, {}, {}, {})\n", f.a, f.b, f.c, f.d);
|
||||
g := ffi_fquad_reverse(f);
|
||||
print("fquad rev = ({}, {}, {}, {})\n", g.a, g.b, g.c, g.d);
|
||||
print("fquad sum = {}\n", ffi_fquad_sum(f));
|
||||
|
||||
// ── DQuad (32 B, 4×f64 HFA — UIEdgeInsets-shape) ──────────────
|
||||
d := ffi_dquad_make(1.5, 2.5, 3.5, 4.5);
|
||||
print("dquad make = ({}, {}, {}, {})\n", d.a, d.b, d.c, d.d);
|
||||
e := ffi_dquad_reverse(d);
|
||||
print("dquad rev = ({}, {}, {}, {})\n", e.a, e.b, e.c, e.d);
|
||||
print("dquad sum = {}\n", ffi_dquad_sum(d));
|
||||
|
||||
0
|
||||
}
|
||||
25
examples/ffi/1213-ffi-05-string-args.c
Normal file
25
examples/ffi/1213-ffi-05-string-args.c
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "1213-ffi-05-string-args.h"
|
||||
|
||||
int ffi_strlen(const char *s) {
|
||||
int n = 0;
|
||||
while (s[n] != 0) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
int ffi_first_byte(const char *s) {
|
||||
return (int)(unsigned char)s[0];
|
||||
}
|
||||
|
||||
int ffi_sum_bytes(const unsigned char *buf, int len) {
|
||||
int total = 0;
|
||||
for (int i = 0; i < len; i++) total += buf[i];
|
||||
return total;
|
||||
}
|
||||
|
||||
void ffi_write_byte(unsigned char *buf, int idx, unsigned char val) {
|
||||
buf[idx] = val;
|
||||
}
|
||||
|
||||
const char* ffi_static_greeting(void) {
|
||||
return "hello from C";
|
||||
}
|
||||
14
examples/ffi/1213-ffi-05-string-args.h
Normal file
14
examples/ffi/1213-ffi-05-string-args.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// String / byte-pointer FFI baselines. Covers the three shapes
|
||||
// callers actually use at the sx ↔ C boundary:
|
||||
// - null-terminated `[:0]u8` (C-style string)
|
||||
// - raw byte pointer `[*]u8` + length (slice-style)
|
||||
// - sx `string` decayed to `ptr` (the slice-decay branch
|
||||
// in coerceArg pulls .ptr)
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
int ffi_strlen (const char *s);
|
||||
int ffi_first_byte (const char *s);
|
||||
int ffi_sum_bytes (const unsigned char *buf, int len);
|
||||
void ffi_write_byte (unsigned char *buf, int idx, unsigned char val);
|
||||
const char* ffi_static_greeting(void);
|
||||
49
examples/ffi/1213-ffi-05-string-args.sx
Normal file
49
examples/ffi/1213-ffi-05-string-args.sx
Normal file
@@ -0,0 +1,49 @@
|
||||
// Phase 0 baseline (PLAN-FFI.md step 0.5): the three shapes that
|
||||
// actually appear at the sx ↔ C boundary today:
|
||||
//
|
||||
// 1. Null-terminated `[:0]u8` — passed by `.ptr` to a `const char *`.
|
||||
// 2. Raw byte buffer `[*]u8` — pointer + explicit length.
|
||||
// 3. sx `string` value — coerceArg's "fat-pointer decay" branch
|
||||
// pulls `.ptr` automatically for `*u8`/
|
||||
// `[:0]u8` params (string is `{ptr, len}`).
|
||||
// 4. Pointer return from C — round-trips back as `[*]u8` / `*u8`.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
#import c {
|
||||
#source "1213-ffi-05-string-args.c";
|
||||
};
|
||||
|
||||
ffi_strlen :: (s: [:0]u8) -> i32 extern;
|
||||
ffi_first_byte :: (s: [:0]u8) -> i32 extern;
|
||||
ffi_sum_bytes :: (buf: [*]u8, len: i32) -> i32 extern;
|
||||
ffi_write_byte :: (buf: [*]u8, idx: i32, v: u8) -> void extern;
|
||||
ffi_static_greeting :: () -> [*]u8 extern;
|
||||
|
||||
main :: () -> i32 {
|
||||
// ── [:0]u8 null-terminated literal ─────────────────────────────
|
||||
msg : [:0]u8 = "hello";
|
||||
print("strlen(\"hello\") = {}\n", ffi_strlen(msg));
|
||||
print("first_byte = {}\n", ffi_first_byte(msg));
|
||||
|
||||
// ── sx `string` → C `const char *` (slice-decay through coerceArg) ─
|
||||
s : string = "sx string here";
|
||||
print("strlen(sx string) = {}\n", ffi_strlen(xx s.ptr));
|
||||
|
||||
// ── [*]u8 raw buffer + length ──────────────────────────────────
|
||||
bytes : [4]u8 = .[10, 20, 30, 40];
|
||||
bp : [*]u8 = xx @bytes[0];
|
||||
print("sum_bytes([10,20,30,40]) = {}\n", ffi_sum_bytes(bp, 4));
|
||||
|
||||
// ── Mutate buffer via C, read back from sx ─────────────────────
|
||||
ffi_write_byte(bp, 1, 99);
|
||||
print("bytes[1] after write = {}\n", bytes[1]);
|
||||
|
||||
// ── C returns a static string pointer; read it byte-by-byte ────
|
||||
g := ffi_static_greeting();
|
||||
n := ffi_strlen(xx g);
|
||||
print("static greeting len = {}\n", n);
|
||||
print("static greeting [0] = {}\n", g[0]);
|
||||
|
||||
0
|
||||
}
|
||||
9
examples/ffi/1214-ffi-06-callback.c
Normal file
9
examples/ffi/1214-ffi-06-callback.c
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "1214-ffi-06-callback.h"
|
||||
|
||||
int ffi_apply_callback(int (*cb)(int), int value) {
|
||||
return cb(value);
|
||||
}
|
||||
|
||||
int ffi_apply_callback2(int (*cb)(void *ctx, int v), void *ctx, int v) {
|
||||
return cb(ctx, v);
|
||||
}
|
||||
13
examples/ffi/1214-ffi-06-callback.h
Normal file
13
examples/ffi/1214-ffi-06-callback.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// C-to-sx callback FFI baseline. C takes a function pointer + a value,
|
||||
// invokes the callback with the value, and returns whatever the callback
|
||||
// returned. Mirrors the `app->onInputEvent` pattern in
|
||||
// library/modules/platform/android.sx where sx installs a handler that
|
||||
// native_app_glue invokes from its input-event loop.
|
||||
|
||||
int ffi_apply_callback(int (*cb)(int), int value);
|
||||
|
||||
// Two-arg variant — the actual chess-on-Android shape:
|
||||
// the callback receives a pointer + a value (mirrors
|
||||
// onInputEvent(app, event) where both are opaque pointers from
|
||||
// the C caller's point of view).
|
||||
int ffi_apply_callback2(int (*cb)(void *ctx, int v), void *ctx, int v);
|
||||
59
examples/ffi/1214-ffi-06-callback.sx
Normal file
59
examples/ffi/1214-ffi-06-callback.sx
Normal file
@@ -0,0 +1,59 @@
|
||||
// Phase 0 baseline (PLAN-FFI.md step 0.6): sx function passed to C
|
||||
// as a function pointer; C invokes it; sx-side observable effect.
|
||||
// Mirrors the `app->onInputEvent` install pattern in
|
||||
// library/modules/platform/android.sx.
|
||||
//
|
||||
// Two arities covered:
|
||||
// 1. (i32) -> i32 — single-arg callback
|
||||
// 2. (*void, i32) -> i32 — pointer + value (onInputEvent shape)
|
||||
//
|
||||
// Plus a side-effect via a global so we can confirm the callback
|
||||
// actually fired (return value + state mutation both observable).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
#import c {
|
||||
#source "1214-ffi-06-callback.c";
|
||||
};
|
||||
|
||||
ffi_apply_callback :: (cb: (i32) -> i32 abi(.c), value: i32) -> i32 extern;
|
||||
ffi_apply_callback2 :: (cb: (*void, i32) -> i32 abi(.c), ctx: *void, v: i32) -> i32 extern;
|
||||
|
||||
g_callback_hits : i32 = 0;
|
||||
g_callback_sum : i32 = 0;
|
||||
|
||||
double_it :: (x: i32) -> i32 abi(.c) {
|
||||
g_callback_hits += 1;
|
||||
g_callback_sum += x;
|
||||
x * 2
|
||||
}
|
||||
|
||||
add_with_ctx :: (ctx: *void, v: i32) -> i32 abi(.c) {
|
||||
g_callback_hits += 1;
|
||||
// Pass a sentinel via ctx to prove the pointer arg also survives the
|
||||
// round-trip — read it back as an i32 through *i32.
|
||||
p : *i32 = xx ctx;
|
||||
p.* + v
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
// ── Single-arg callback ────────────────────────────────────────
|
||||
r1 := ffi_apply_callback(double_it, 21);
|
||||
print("callback returned = {}\n", r1);
|
||||
print("hits after first call = {}\n", g_callback_hits);
|
||||
print("sum after first call = {}\n", g_callback_sum);
|
||||
|
||||
// Two more calls confirm the same fn pointer keeps working.
|
||||
ffi_apply_callback(double_it, 7);
|
||||
ffi_apply_callback(double_it, 11);
|
||||
print("hits after three calls = {}\n", g_callback_hits);
|
||||
print("sum after three calls = {}\n", g_callback_sum);
|
||||
|
||||
// ── Two-arg callback with opaque ctx pointer ───────────────────
|
||||
ctx_val : i32 = 100;
|
||||
r2 := ffi_apply_callback2(add_with_ctx, xx @ctx_val, 42);
|
||||
print("ctx + value = {}\n", r2);
|
||||
print("hits after ctx callback = {}\n", g_callback_hits);
|
||||
|
||||
0
|
||||
}
|
||||
25
examples/ffi/1215-ffi-07-c-import-block.sx
Normal file
25
examples/ffi/1215-ffi-07-c-import-block.sx
Normal file
@@ -0,0 +1,25 @@
|
||||
// Phase 0 baseline (PLAN-FFI.md step 0.7): `#import c { #include /
|
||||
// #source }` end-to-end, with the new stdlib-path resolution
|
||||
// (src/imports.zig added this session). The header + .c live ONLY
|
||||
// under `library/vendors/sx_ffi_resolve_test/` — not the sx repo
|
||||
// root vendors/ — so the path can be found only by the stdlib
|
||||
// search branch (`<exe>/../../library` etc.), not by the CWD or
|
||||
// importing-file's-dir branches.
|
||||
//
|
||||
// `#include` triggers c_import.zig's auto-synthesis of `extern`
|
||||
// fn decls from the C header; `#source` adds the .c to the build's
|
||||
// object list. Together they let the sx side call the C functions
|
||||
// by their declared names with no manual decls.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
#import c {
|
||||
#include "vendors/sx_ffi_resolve_test/sx_ffi_resolve_test.h";
|
||||
#source "vendors/sx_ffi_resolve_test/sx_ffi_resolve_test.c";
|
||||
};
|
||||
|
||||
main :: () -> i32 {
|
||||
print("add(3, 4) = {}\n", sx_ffi_resolve_test_add(3, 4));
|
||||
print("mul(6, 7) = {}\n", sx_ffi_resolve_test_mul(6, 7));
|
||||
0
|
||||
}
|
||||
3
examples/ffi/1216-ffi-08-extern-in-method.c
Normal file
3
examples/ffi/1216-ffi-08-extern-in-method.c
Normal file
@@ -0,0 +1,3 @@
|
||||
#include "1216-ffi-08-extern-in-method.h"
|
||||
|
||||
int ffi_method_helper(int x) { return x * 10; }
|
||||
1
examples/ffi/1216-ffi-08-extern-in-method.h
Normal file
1
examples/ffi/1216-ffi-08-extern-in-method.h
Normal file
@@ -0,0 +1 @@
|
||||
int ffi_method_helper(int x);
|
||||
70
examples/ffi/1216-ffi-08-extern-in-method.sx
Normal file
70
examples/ffi/1216-ffi-08-extern-in-method.sx
Normal file
@@ -0,0 +1,70 @@
|
||||
// Phase 0 baseline (PLAN-FFI.md step 0.8): `extern` C call sites
|
||||
// embedded inside the major sx surface constructs. None of these
|
||||
// touch a new ABI shape — they only verify lowering routes the call
|
||||
// through identically regardless of the enclosing context:
|
||||
//
|
||||
// 1. struct method body (Counter.next)
|
||||
// 2. protocol impl method body (impl Doubler for Counter)
|
||||
// 3. closure value body (closure { ... })
|
||||
// 4. comptime-gated branch (inline if OS == ...)
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/build.sx";
|
||||
|
||||
#import c {
|
||||
#include "1216-ffi-08-extern-in-method.h";
|
||||
#source "1216-ffi-08-extern-in-method.c";
|
||||
};
|
||||
|
||||
// ── 1. Struct method calling a #extern fn ───────────────────────────
|
||||
Counter :: struct {
|
||||
seed: i32 = 0;
|
||||
next :: (self: *Counter) -> i32 {
|
||||
v := ffi_method_helper(self.seed);
|
||||
self.seed += 1;
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
// ── 2. Protocol impl method calling a #extern fn ────────────────────
|
||||
Doubler :: protocol {
|
||||
doubled :: (self: *Self) -> i32;
|
||||
}
|
||||
|
||||
impl Doubler for Counter {
|
||||
doubled :: (self: *Counter) -> i32 {
|
||||
ffi_method_helper(self.seed) * 2
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. Closure body calling a #extern fn ────────────────────────────
|
||||
make_adder :: (bias: i32) -> Closure(i32) -> i32 {
|
||||
closure((x: i32) -> i32 => ffi_method_helper(x) + bias)
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
c : Counter = .{ seed = 1 };
|
||||
|
||||
// 1. struct method
|
||||
print("method next 1 = {}\n", c.next());
|
||||
print("method next 2 = {}\n", c.next());
|
||||
|
||||
// 2. protocol method (still operating on the now-bumped Counter)
|
||||
print("protocol = {}\n", c.doubled());
|
||||
|
||||
// 3. closure
|
||||
adder := make_adder(100);
|
||||
print("closure(5) = {}\n", adder(5));
|
||||
|
||||
// 4. inline if OS branch — only one arm survives codegen on a
|
||||
// given target. `inline if X == { case ... }` reads cleaner
|
||||
// than chained `inline if X == .a; inline if X == .b; ...`.
|
||||
inline if OS == {
|
||||
case .macos: { print("inline if macos = {}\n", ffi_method_helper(7)); }
|
||||
case .ios: { print("inline if ios = {}\n", ffi_method_helper(7)); }
|
||||
case .linux: { print("inline if linux = {}\n", ffi_method_helper(7)); }
|
||||
else: { print("inline if other = {}\n", ffi_method_helper(7)); }
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
22
examples/ffi/1217-ffi-09-extern-result-chain.c
Normal file
22
examples/ffi/1217-ffi-09-extern-result-chain.c
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "1217-ffi-09-extern-result-chain.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
void *ffi_chain_make(int seed) {
|
||||
int *p = (int *)malloc(sizeof(int));
|
||||
if (p) *p = seed;
|
||||
return p;
|
||||
}
|
||||
|
||||
int ffi_chain_bump(void *h, int delta) {
|
||||
int *p = (int *)h;
|
||||
*p += delta;
|
||||
return *p;
|
||||
}
|
||||
|
||||
int ffi_chain_peek(void *h) {
|
||||
return *(int *)h;
|
||||
}
|
||||
|
||||
void ffi_chain_dispose(void *h) {
|
||||
free(h);
|
||||
}
|
||||
10
examples/ffi/1217-ffi-09-extern-result-chain.h
Normal file
10
examples/ffi/1217-ffi-09-extern-result-chain.h
Normal file
@@ -0,0 +1,10 @@
|
||||
// Trivial opaque-handle pattern — `make` produces a heap-allocated
|
||||
// counter, `bump` returns the new value, `peek` reads without
|
||||
// mutating, `dispose` frees. Mirrors the shape of real C handles
|
||||
// (MTLBuffer*, AAssetManager*, file pointers, etc.) without pulling
|
||||
// in any platform deps.
|
||||
|
||||
void *ffi_chain_make (int seed);
|
||||
int ffi_chain_bump (void *h, int delta);
|
||||
int ffi_chain_peek (void *h);
|
||||
void ffi_chain_dispose (void *h);
|
||||
79
examples/ffi/1217-ffi-09-extern-result-chain.sx
Normal file
79
examples/ffi/1217-ffi-09-extern-result-chain.sx
Normal file
@@ -0,0 +1,79 @@
|
||||
// Phase 0 baseline (PLAN-FFI.md step 0.9): FFI result chains. The
|
||||
// shapes that real sx code uses for opaque C handles (MTLBuffer*,
|
||||
// AAssetManager*, file pointers, ...) — passing a C-returned
|
||||
// pointer into another C call, stashing it in a struct field,
|
||||
// pushing into a `List(*void)`, and iterating that list to feed each
|
||||
// handle back through C.
|
||||
//
|
||||
// No new ABI shape — pointer-in, pointer-out. The lemma locked in:
|
||||
// handle-shaped flows survive sx's struct-field assignment, List
|
||||
// storage, and iteration-then-call cycles.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
#import c {
|
||||
#include "1217-ffi-09-extern-result-chain.h";
|
||||
#source "1217-ffi-09-extern-result-chain.c";
|
||||
};
|
||||
|
||||
// Struct field hosts an FFI-returned handle.
|
||||
Counter :: struct {
|
||||
handle: *void = null;
|
||||
label: string;
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
// ── 1. Chain: make → bump → peek ───────────────────────────────
|
||||
a := ffi_chain_make(100);
|
||||
print("peek after make = {}\n", ffi_chain_peek(a));
|
||||
print("bump(+5) = {}\n", ffi_chain_bump(a, 5));
|
||||
print("bump(+3) = {}\n", ffi_chain_bump(a, 3));
|
||||
print("peek after bumps = {}\n", ffi_chain_peek(a));
|
||||
|
||||
// ── 2. Stash handle in a struct field, use through `.handle` ──
|
||||
c : Counter = .{ handle = ffi_chain_make(50), label = "ctr-a" };
|
||||
print("ctr label = {}\n", c.label);
|
||||
print("ctr peek = {}\n", ffi_chain_peek(c.handle));
|
||||
ffi_chain_bump(c.handle, 7);
|
||||
print("ctr after bump = {}\n", ffi_chain_peek(c.handle));
|
||||
|
||||
// ── 3. Push handles into a List, iterate, feed back to C ──────
|
||||
handles : List(*void) = .{};
|
||||
i : i32 = 0;
|
||||
while i < 3 {
|
||||
h := ffi_chain_make(i * 10);
|
||||
handles.append(h);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
j : i64 = 0;
|
||||
while j < handles.len {
|
||||
h := handles.items[j];
|
||||
v := ffi_chain_peek(h);
|
||||
print("list[{}] peek = {}\n", j, v);
|
||||
j += 1;
|
||||
}
|
||||
|
||||
// Iterate again, bump each, observe the cumulative effect.
|
||||
j = 0;
|
||||
while j < handles.len {
|
||||
ffi_chain_bump(handles.items[j], 1);
|
||||
j += 1;
|
||||
}
|
||||
j = 0;
|
||||
while j < handles.len {
|
||||
print("list[{}] after bump= {}\n", j, ffi_chain_peek(handles.items[j]));
|
||||
j += 1;
|
||||
}
|
||||
|
||||
// ── Cleanup ─────────────────────────────────────────────────────
|
||||
ffi_chain_dispose(a);
|
||||
ffi_chain_dispose(c.handle);
|
||||
j = 0;
|
||||
while j < handles.len {
|
||||
ffi_chain_dispose(handles.items[j]);
|
||||
j += 1;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
23
examples/ffi/1219-ffi-extern.sx
Normal file
23
examples/ffi/1219-ffi-extern.sx
Normal file
@@ -0,0 +1,23 @@
|
||||
#import "modules/std.sx";
|
||||
#import "modules/math";
|
||||
#import "modules/build.sx";
|
||||
#import "modules/std/test.sx";
|
||||
pkg :: #import "tests/fixtures/testpkg";
|
||||
|
||||
// --- Extern function binding ---
|
||||
libc :: #library "c";
|
||||
|
||||
c_abs :: (n: i32) -> i32 extern libc "abs";
|
||||
|
||||
// --- Protocol declarations (Phase 1: static dispatch only) ---
|
||||
|
||||
main :: () {
|
||||
|
||||
// ========================================================
|
||||
// 15. EXTERN FUNCTION BINDING
|
||||
// ========================================================
|
||||
print("=== 15. Extern ===\n");
|
||||
|
||||
// Symbol rename: c_abs maps to C's abs()
|
||||
print("extern-rename: {}\n", c_abs(xx -42));
|
||||
}
|
||||
13
examples/ffi/1220-ffi-c-import-reserved-name-params.c
Normal file
13
examples/ffi/1220-ffi-c-import-reserved-name-params.c
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "1220-ffi-c-import-reserved-name-params.h"
|
||||
|
||||
int ffi_pick(int i1, int i2, int which) {
|
||||
return which == 0 ? i1 : i2;
|
||||
}
|
||||
|
||||
int ffi_sum(int i1, int i2) {
|
||||
return i1 + i2;
|
||||
}
|
||||
|
||||
int i2(int u8) {
|
||||
return u8 + 100;
|
||||
}
|
||||
7
examples/ffi/1220-ffi-c-import-reserved-name-params.h
Normal file
7
examples/ffi/1220-ffi-c-import-reserved-name-params.h
Normal file
@@ -0,0 +1,7 @@
|
||||
/* Extern C declarations whose names collide with sx's reserved type spellings.
|
||||
The `#import c` exemption must accept these generated names unedited, both as
|
||||
parameter names (`i1`, `i2`) and as a FUNCTION name (`i2`) — and an extern
|
||||
reserved-name function must be bare-callable (issue 0089). */
|
||||
int ffi_pick(int i1, int i2, int which);
|
||||
int ffi_sum(int i1, int i2);
|
||||
int i2(int u8);
|
||||
24
examples/ffi/1220-ffi-c-import-reserved-name-params.sx
Normal file
24
examples/ffi/1220-ffi-c-import-reserved-name-params.sx
Normal file
@@ -0,0 +1,24 @@
|
||||
// `#import c` extern-name exemption: C names that collide with sx's reserved
|
||||
// type spellings import unedited. Extern decls are treated as RAW — their names
|
||||
// are never type-classified nor reserved-checked — so the generated `extern`
|
||||
// bindings import and call without hand-edits (no backticks needed). This covers
|
||||
// parameter names (`i1`/`i2`), a function whose own NAME is a reserved spelling
|
||||
// (`i2`), and bare-calling that function (its callee spelling parses as a type
|
||||
// but resolves to the extern fn). Before issue 0089 the params errored with
|
||||
// "'i1' is a reserved type name and cannot be used as an identifier", and the
|
||||
// bare call errored with "unresolved 'i2'".
|
||||
// Regression (issue 0089).
|
||||
#import "modules/std.sx";
|
||||
|
||||
#import c {
|
||||
#include "1220-ffi-c-import-reserved-name-params.h";
|
||||
#source "1220-ffi-c-import-reserved-name-params.c";
|
||||
};
|
||||
|
||||
main :: () -> i32 {
|
||||
print("pick(10,20,0) = {}\n", ffi_pick(10, 20, 0));
|
||||
print("pick(10,20,1) = {}\n", ffi_pick(10, 20, 1));
|
||||
print("sum(10,20) = {}\n", ffi_sum(10, 20));
|
||||
print("i2(4) bare = {}\n", i2(4));
|
||||
0
|
||||
}
|
||||
31
examples/ffi/1221-ffi-cstring-returns.sx
Normal file
31
examples/ffi/1221-ffi-cstring-returns.sx
Normal file
@@ -0,0 +1,31 @@
|
||||
// Extern `-> [:0]u8` / `-> ?[:0]u8` returns: C hands back ONE `char *`;
|
||||
// the fat sx string is synthesized at the call boundary ({ptr, strlen};
|
||||
// NULL maps to the optional's null / an empty string) — issue 0128.
|
||||
// Pre-fix, the call read the pointer register pair as {ptr, len} and the
|
||||
// length was garbage (bus error on print).
|
||||
#import "modules/std.sx";
|
||||
|
||||
libc :: #library "c";
|
||||
err_text :: (code: i32) -> [:0]u8 extern libc "strerror";
|
||||
sig_text :: (sig: i32) -> ?[:0]u8 extern libc "strsignal";
|
||||
dlerror :: () -> ?[:0]u8 extern libc;
|
||||
|
||||
main :: () -> i32 {
|
||||
// plain: strerror(0) = "Undefined error: 0" on macOS — assert shape,
|
||||
// not the exact text (locale/platform variance)
|
||||
t := err_text(2);
|
||||
if t.len < 5 { print("BUG: strerror too short ({})\n", t.len); return 1; }
|
||||
print("strerror(2) len ok\n");
|
||||
|
||||
// optional, non-null branch
|
||||
o := sig_text(2);
|
||||
if o == null { print("BUG: strsignal null\n"); return 2; }
|
||||
if o!.len < 3 { print("BUG: strsignal too short\n"); return 3; }
|
||||
print("optional non-null ok\n");
|
||||
|
||||
// optional, NULL branch: dlerror() with no pending error is NULL
|
||||
d := dlerror();
|
||||
if d != null { print("BUG: dlerror non-null\n"); return 4; }
|
||||
print("optional null ok\n");
|
||||
return 0;
|
||||
}
|
||||
38
examples/ffi/1222-ffi-cstring-type.sx
Normal file
38
examples/ffi/1222-ffi-cstring-type.sx
Normal file
@@ -0,0 +1,38 @@
|
||||
// The `cstring` type: ONE pointer to a null-terminated u8 buffer — C's
|
||||
// `char *`. Crosses extern boundaries verbatim in both directions;
|
||||
// `?cstring` is the nullable case (null pointer = absent); string
|
||||
// LITERALS coerce implicitly (terminated constants); arbitrary strings
|
||||
// materialize via to_cstring; from_cstring is the zero-copy view back.
|
||||
#import "modules/std.sx";
|
||||
|
||||
libc :: #library "c";
|
||||
strerror_c :: (code: i32) -> cstring extern libc "strerror";
|
||||
getenv_c :: (name: cstring) -> ?cstring extern libc "getenv";
|
||||
dlerror_c :: () -> ?cstring extern libc "dlerror";
|
||||
|
||||
main :: () -> i32 {
|
||||
// literal -> cstring param; cstring return -> view
|
||||
e := strerror_c(2);
|
||||
v := from_cstring(e);
|
||||
if v.len < 5 { print("BUG: strerror short\n"); return 1; }
|
||||
print("strerror ok\n");
|
||||
|
||||
// ?cstring: present, absent, and null-returning C call
|
||||
p := getenv_c("PATH");
|
||||
if p == null { print("BUG: PATH null\n"); return 2; }
|
||||
if cstring_len(p!) < 1 { print("BUG: PATH empty\n"); return 3; }
|
||||
q := getenv_c("NO_SUCH_VAR_ZZZ");
|
||||
if q != null { print("BUG: missing not null\n"); return 4; }
|
||||
d := dlerror_c();
|
||||
if d != null { print("BUG: dlerror non-null\n"); return 5; }
|
||||
print("?cstring ok\n");
|
||||
|
||||
// round trip: built string -> owned cstring -> view -> equality
|
||||
s := concat("hi-", "there");
|
||||
c := to_cstring(s);
|
||||
r := from_cstring(c);
|
||||
if r != "hi-there" { print("BUG: round trip\n"); return 6; }
|
||||
if cstring_len(c) != 8 { print("BUG: len\n"); return 7; }
|
||||
print("round trip ok\n");
|
||||
return 0;
|
||||
}
|
||||
14
examples/ffi/1223-ffi-extern-fn.sx
Normal file
14
examples/ffi/1223-ffi-extern-fn.sx
Normal file
@@ -0,0 +1,14 @@
|
||||
// extern function binding (FFI-linkage stream, Phase 1): bind libc's `abs`
|
||||
// directly via the bare `extern` linkage modifier — no `extern`, no
|
||||
// `#library`. `extern` ⇒ external linkage + C ABI + no sx ctx; the symbol
|
||||
// resolves against the default-linked libc at link time. The sx name `abs`
|
||||
// IS the C symbol (no rename — the `extern LIB "csym"` forms land in 1.2).
|
||||
#import "modules/std.sx";
|
||||
|
||||
abs :: (n: i32) -> i32 extern;
|
||||
|
||||
main :: () -> i32 {
|
||||
print("abs(-7) = {}\n", abs(xx -7));
|
||||
print("abs(42) = {}\n", abs(xx 42));
|
||||
0
|
||||
}
|
||||
13
examples/ffi/1224-ffi-extern-fn-rename.sx
Normal file
13
examples/ffi/1224-ffi-extern-fn-rename.sx
Normal file
@@ -0,0 +1,13 @@
|
||||
// extern with a "csym" rename (FFI-linkage stream, Phase 1.2): the sx name
|
||||
// `c_abs` binds C's `abs` via the optional symbol-name override after the
|
||||
// `extern` keyword — mirrors `extern "abs"`. The optional `LIB` ident slot
|
||||
// (extern_lib) sits before the string; here it's omitted (libc is
|
||||
// default-linked).
|
||||
#import "modules/std.sx";
|
||||
|
||||
c_abs :: (n: i32) -> i32 extern "abs";
|
||||
|
||||
main :: () -> i32 {
|
||||
print("c_abs(-42) = {}\n", c_abs(xx -42));
|
||||
0
|
||||
}
|
||||
15
examples/ffi/1225-ffi-extern-global.sx
Normal file
15
examples/ffi/1225-ffi-extern-global.sx
Normal file
@@ -0,0 +1,15 @@
|
||||
// extern data global (FFI-linkage stream, Phase 1.2): reference a symbol
|
||||
// defined elsewhere (here libSystem's __stdinp) via the bare `extern`
|
||||
// linkage modifier on a typed var decl — the extern-named counterpart of
|
||||
// `<name> : <type> extern;` (see examples/1205). The optional
|
||||
// `extern [LIB] ["csym"]` tail mirrors the fn form; bare here (the sx name
|
||||
// IS the C symbol, resolved against the default-linked libSystem).
|
||||
#import "modules/std.sx";
|
||||
|
||||
__stdinp : *void extern;
|
||||
|
||||
main :: () -> i32 {
|
||||
addr_bits : u64 = xx @__stdinp;
|
||||
print("stdin extern global non-null: {}\n", addr_bits != 0);
|
||||
0
|
||||
}
|
||||
8
examples/ffi/1226-ffi-export-fn.c
Normal file
8
examples/ffi/1226-ffi-export-fn.c
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "1226-ffi-export-fn.h"
|
||||
|
||||
// Defined on the sx side via `export` — a plain C-ABI symbol, no sx context.
|
||||
extern int sx_square(int n);
|
||||
|
||||
int call_sx_square(int n) {
|
||||
return sx_square(n) + 1;
|
||||
}
|
||||
7
examples/ffi/1226-ffi-export-fn.h
Normal file
7
examples/ffi/1226-ffi-export-fn.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef SX_EXPORT_FN_H
|
||||
#define SX_EXPORT_FN_H
|
||||
|
||||
// Calls back into the sx-exported `sx_square` and adds 1.
|
||||
int call_sx_square(int n);
|
||||
|
||||
#endif
|
||||
28
examples/ffi/1226-ffi-export-fn.sx
Normal file
28
examples/ffi/1226-ffi-export-fn.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
// export function (FFI-linkage stream, Phase 2): define an sx function with
|
||||
// the bare `export` linkage modifier — external linkage + C ABI + no sx ctx —
|
||||
// so a companion C translation unit can call back into it by its plain symbol
|
||||
// name. The C side (`#source`) declares `sx_square` as a normal `extern int`
|
||||
// and calls it; sx `main` drives the C side via `call_sx_square`. Mirrors the
|
||||
// import-direction `extern` examples (1223–1225) for the define direction.
|
||||
//
|
||||
// Without `export`, an sx-defined fn is `internal` linkage + carries the
|
||||
// implicit `__sx_ctx` slot, so the C object can neither resolve nor correctly
|
||||
// call the symbol — this is the gap `export` fills.
|
||||
#import "modules/std.sx";
|
||||
|
||||
#import c {
|
||||
#include "1226-ffi-export-fn.h";
|
||||
#source "1226-ffi-export-fn.c";
|
||||
};
|
||||
|
||||
// sx-defined, exported to C: external linkage + C ABI + no implicit ctx.
|
||||
sx_square :: (n: i32) -> i32 export {
|
||||
return n * n;
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
// call_sx_square (C) calls back into sx_square, adds 1.
|
||||
print("call_sx_square(6) = {}\n", call_sx_square(6));
|
||||
print("call_sx_square(9) = {}\n", call_sx_square(9));
|
||||
0
|
||||
}
|
||||
8
examples/ffi/1227-ffi-export-fn-rename.c
Normal file
8
examples/ffi/1227-ffi-export-fn-rename.c
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "1227-ffi-export-fn-rename.h"
|
||||
|
||||
// Defined on the sx side via `export "triple_c"` — a plain C-ABI symbol.
|
||||
extern int triple_c(int n);
|
||||
|
||||
int call_triple(int n) {
|
||||
return triple_c(n) + 1;
|
||||
}
|
||||
7
examples/ffi/1227-ffi-export-fn-rename.h
Normal file
7
examples/ffi/1227-ffi-export-fn-rename.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef SX_EXPORT_FN_RENAME_H
|
||||
#define SX_EXPORT_FN_RENAME_H
|
||||
|
||||
// Calls back into the sx-exported `triple_c` and adds 1.
|
||||
int call_triple(int n);
|
||||
|
||||
#endif
|
||||
23
examples/ffi/1227-ffi-export-fn-rename.sx
Normal file
23
examples/ffi/1227-ffi-export-fn-rename.sx
Normal file
@@ -0,0 +1,23 @@
|
||||
// export with a "csym" rename (FFI-linkage stream, Phase 2.2): the sx name
|
||||
// `sx_triple` is exposed to C under the symbol `triple_c` via the optional
|
||||
// symbol-name override after `export` — the define-direction mirror of
|
||||
// `extern "csym"` (1224). The companion C calls `triple_c` by that name; sx
|
||||
// `main` drives it via `call_triple`. Runs in AOT mode (see the `.aot`
|
||||
// marker) because a C->sx-by-name call cannot link against a JIT-resident
|
||||
// symbol.
|
||||
#import "modules/std.sx";
|
||||
|
||||
#import c {
|
||||
#include "1227-ffi-export-fn-rename.h";
|
||||
#source "1227-ffi-export-fn-rename.c";
|
||||
};
|
||||
|
||||
// sx-defined, exported to C under the C symbol `triple_c`.
|
||||
sx_triple :: (n: i32) -> i32 export "triple_c" {
|
||||
return n * 3;
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
print("call_triple(7) = {}\n", call_triple(7));
|
||||
0
|
||||
}
|
||||
14
examples/ffi/1228-ffi-extern-c-non-transitive.sx
Normal file
14
examples/ffi/1228-ffi-extern-c-non-transitive.sx
Normal file
@@ -0,0 +1,14 @@
|
||||
// `#import` is non-transitive for C-import functions: main imports b,
|
||||
// b imports c, so main must NOT see c's lib-less `extern` C functions
|
||||
// directly. Referencing either is rejected by the C-import visibility
|
||||
// gate (lower/decl.zig `c_import_bare`) with a C-specific "not visible"
|
||||
// diagnostic — not the generic top-level-name wording. Two distinct
|
||||
// extern symbols pin that the gate fires per-symbol.
|
||||
#import "modules/std.sx";
|
||||
#import "1228-ffi-extern-c-non-transitive/b.sx";
|
||||
|
||||
main :: () -> i32 {
|
||||
print("{}\n", c_abs_one(-3));
|
||||
print("{}\n", c_abs_two(-4));
|
||||
return 0;
|
||||
}
|
||||
7
examples/ffi/1228-ffi-extern-c-non-transitive/b.sx
Normal file
7
examples/ffi/1228-ffi-extern-c-non-transitive/b.sx
Normal file
@@ -0,0 +1,7 @@
|
||||
// Intermediate module: directly imports c.sx, so BOTH of c's lib-less
|
||||
// C functions are legitimately visible here (the legal usage site).
|
||||
#import "c.sx";
|
||||
|
||||
b_use :: () -> i32 {
|
||||
return c_abs_one(-1) + c_abs_two(-2);
|
||||
}
|
||||
5
examples/ffi/1228-ffi-extern-c-non-transitive/c.sx
Normal file
5
examples/ffi/1228-ffi-extern-c-non-transitive/c.sx
Normal file
@@ -0,0 +1,5 @@
|
||||
// Two lib-less `extern` C-symbol imports: each declares a C function
|
||||
// resolved at link time with no library reference. Both are policed by
|
||||
// the non-transitive C-import visibility gate, per-symbol.
|
||||
c_abs_one :: (x: i32) -> i32 extern;
|
||||
c_abs_two :: (x: i32) -> i32 extern;
|
||||
20
examples/ffi/1229-ffi-extern-cvariadic.c
Normal file
20
examples/ffi/1229-ffi-extern-cvariadic.c
Normal file
@@ -0,0 +1,20 @@
|
||||
#include <stdarg.h>
|
||||
|
||||
long long sx_ext_sum_ints(int n, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, n);
|
||||
long long total = 0;
|
||||
for (int i = 0; i < n; i++) total += va_arg(ap, int);
|
||||
va_end(ap);
|
||||
return total;
|
||||
}
|
||||
|
||||
double sx_ext_avg_doubles(int n, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, n);
|
||||
double total = 0.0;
|
||||
for (int i = 0; i < n; i++) total += va_arg(ap, double);
|
||||
va_end(ap);
|
||||
if (n == 0) return 0.0;
|
||||
return total / n;
|
||||
}
|
||||
26
examples/ffi/1229-ffi-extern-cvariadic.sx
Normal file
26
examples/ffi/1229-ffi-extern-cvariadic.sx
Normal file
@@ -0,0 +1,26 @@
|
||||
// `extern` C-variadic tail: a trailing `..args: []T` on an `extern` fn
|
||||
// maps to the C calling convention's `...`. Extras at the call site pass through the variadic
|
||||
// slot with standard default argument promotion (i8/i16/bool → i32,
|
||||
// f32 → f64), NOT packed into an sx slice.
|
||||
//
|
||||
// Regression (FFI-linkage Part B): the `is_variadic` drop in
|
||||
// `declareFunction` + the call-site early-out in `packVariadicCallArgs`
|
||||
// were gated on `extern` only, so a migrated variadic `extern` lost
|
||||
// its `...` tail and slice-packed the extras (garbage at the C ABI).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
#import c {
|
||||
#source "1229-ffi-extern-cvariadic.c";
|
||||
};
|
||||
|
||||
sx_ext_sum_ints :: (n: i32, ..args: []i32) -> i64 extern;
|
||||
sx_ext_avg_doubles :: (n: i32, ..args: []f64) -> f64 extern;
|
||||
|
||||
main :: () -> i32 {
|
||||
print("sum_ints(3, 10, 20, 30) = {}\n", sx_ext_sum_ints(3, 10, 20, 30));
|
||||
print("sum_ints(0) = {}\n", sx_ext_sum_ints(0));
|
||||
print("avg_doubles(2) = {}\n", sx_ext_avg_doubles(2, 1.5, 2.5));
|
||||
print("avg_doubles(3) = {}\n", sx_ext_avg_doubles(3, 1.0, 2.0, 3.0));
|
||||
0
|
||||
}
|
||||
21
examples/ffi/1230-ffi-extern-same-name-authors.sx
Normal file
21
examples/ffi/1230-ffi-extern-same-name-authors.sx
Normal file
@@ -0,0 +1,21 @@
|
||||
// Two flat FILE imports each declare the SAME libc symbol `absval` via the
|
||||
// `extern` keyword (the linkage-keyword twin of example 0729's `extern`
|
||||
// form). The bare-call resolver must NOT count extern authors when deciding
|
||||
// ambiguity — they are external C symbols, never rerouted by the bare-call
|
||||
// machinery, so the existing first-wins extern dispatch binds the
|
||||
// call and a same-name extern collision compiles + runs (prints 7), it does
|
||||
// NOT error as ambiguous.
|
||||
//
|
||||
// Regression (FFI-linkage Part B): `isPlainFreeFn` / `isPlainFreeFnDecl`
|
||||
// excluded a `extern` body but classified an empty-block `extern` fn as a
|
||||
// plain free function, so the two extern authors were wrongly counted as an
|
||||
// ambiguous bare-call collision. Prerequisite for migrating the fn-decl
|
||||
// `extern` path onto `extern`.
|
||||
#import "modules/std.sx";
|
||||
#import "1230-ffi-extern-same-name-authors/a.sx";
|
||||
#import "1230-ffi-extern-same-name-authors/b.sx";
|
||||
|
||||
main :: () -> i32 {
|
||||
print("absval = {}\n", absval(-7));
|
||||
0
|
||||
}
|
||||
6
examples/ffi/1230-ffi-extern-same-name-authors/a.sx
Normal file
6
examples/ffi/1230-ffi-extern-same-name-authors/a.sx
Normal file
@@ -0,0 +1,6 @@
|
||||
// One of two flat authors of `absval`, an `extern` libc binding — the
|
||||
// `extern` twin of example 0729's `extern libc "abs"`. A consumer
|
||||
// flat-importing BOTH must NOT see this as an ambiguous bare-call
|
||||
// collision: extern authors (external C symbols) are excluded from the
|
||||
// bare-call ambiguity verdict, exactly like their `extern` twins.
|
||||
absval :: (n: i32) -> i32 extern libc "abs";
|
||||
2
examples/ffi/1230-ffi-extern-same-name-authors/b.sx
Normal file
2
examples/ffi/1230-ffi-extern-same-name-authors/b.sx
Normal file
@@ -0,0 +1,2 @@
|
||||
// The second flat author of `absval` — the identical `extern` binding.
|
||||
absval :: (n: i32) -> i32 extern libc "abs";
|
||||
19
examples/ffi/1231-ffi-extern-undeclared-lib.sx
Normal file
19
examples/ffi/1231-ffi-extern-undeclared-lib.sx
Normal file
@@ -0,0 +1,19 @@
|
||||
// An `extern LIB "csym"` reference must name something real, exactly like
|
||||
// its `extern LIB` twin (example 1620): `nosuchunit` names neither a
|
||||
// #library constant nor a named `#import c` unit, so this is a compile-time
|
||||
// diagnostic — the bogus library reference is caught BEFORE the symbol
|
||||
// would silently resolve through whatever image happens to carry it.
|
||||
//
|
||||
// Regression (FFI-linkage Part B): `checkExternRefs` validated only a
|
||||
// `extern` (extern-import shape) library_ref and skipped the `extern` keyword's
|
||||
// `extern_lib`, so a bogus `extern` lib reference compiled silently (the
|
||||
// symbol resolved via the default image and ran). Prerequisite for
|
||||
// migrating the fn-decl `extern` path onto `extern`.
|
||||
#import "modules/std.sx";
|
||||
|
||||
c_abs :: (n: i32) -> i32 extern nosuchunit "abs";
|
||||
|
||||
main :: () -> i32 {
|
||||
print("c_abs = {}\n", c_abs(-5));
|
||||
0
|
||||
}
|
||||
1
examples/ffi/expected/1200-ffi-callconv-c-callbacks.exit
Normal file
1
examples/ffi/expected/1200-ffi-callconv-c-callbacks.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
abi(.c): 42
|
||||
1
examples/ffi/expected/1201-ffi-callconv-c-globals.exit
Normal file
1
examples/ffi/expected/1201-ffi-callconv-c-globals.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/ffi/expected/1201-ffi-callconv-c-globals.stderr
Normal file
1
examples/ffi/expected/1201-ffi-callconv-c-globals.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
2
examples/ffi/expected/1201-ffi-callconv-c-globals.stdout
Normal file
2
examples/ffi/expected/1201-ffi-callconv-c-globals.stdout
Normal file
@@ -0,0 +1,2 @@
|
||||
inline: pw=320, ph=321, frame=1
|
||||
wrapper: pw=640, ph=641, frame=2
|
||||
1
examples/ffi/expected/1202-ffi-cc-c-large-aggregate.exit
Normal file
1
examples/ffi/expected/1202-ffi-cc-c-large-aggregate.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
15982
examples/ffi/expected/1202-ffi-cc-c-large-aggregate.ir
Normal file
15982
examples/ffi/expected/1202-ffi-cc-c-large-aggregate.ir
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
1
examples/ffi/expected/1205-ffi-extern-global.exit
Normal file
1
examples/ffi/expected/1205-ffi-extern-global.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/ffi/expected/1205-ffi-extern-global.stderr
Normal file
1
examples/ffi/expected/1205-ffi-extern-global.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
2
examples/ffi/expected/1205-ffi-extern-global.stdout
Normal file
2
examples/ffi/expected/1205-ffi-extern-global.stdout
Normal file
@@ -0,0 +1,2 @@
|
||||
stdin extern global non-null: true
|
||||
helper file linked: true
|
||||
1
examples/ffi/expected/1206-ffi-medium-struct.exit
Normal file
1
examples/ffi/expected/1206-ffi-medium-struct.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/ffi/expected/1206-ffi-medium-struct.stderr
Normal file
1
examples/ffi/expected/1206-ffi-medium-struct.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
2
examples/ffi/expected/1206-ffi-medium-struct.stdout
Normal file
2
examples/ffi/expected/1206-ffi-medium-struct.stdout
Normal file
@@ -0,0 +1,2 @@
|
||||
swap = (2, 1)
|
||||
ok = true
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
direct non-null = true
|
||||
helper non-null = true
|
||||
eq = true
|
||||
1
examples/ffi/expected/1208-ffi-closure-capture.exit
Normal file
1
examples/ffi/expected/1208-ffi-closure-capture.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/ffi/expected/1208-ffi-closure-capture.stderr
Normal file
1
examples/ffi/expected/1208-ffi-closure-capture.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
2
examples/ffi/expected/1208-ffi-closure-capture.stdout
Normal file
2
examples/ffi/expected/1208-ffi-closure-capture.stdout
Normal file
@@ -0,0 +1,2 @@
|
||||
ok (passthrough works) = true
|
||||
ok (passthrough via #objc_call) = true
|
||||
1
examples/ffi/expected/1209-ffi-01-primitives.exit
Normal file
1
examples/ffi/expected/1209-ffi-01-primitives.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/ffi/expected/1209-ffi-01-primitives.stderr
Normal file
1
examples/ffi/expected/1209-ffi-01-primitives.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
13
examples/ffi/expected/1209-ffi-01-primitives.stdout
Normal file
13
examples/ffi/expected/1209-ffi-01-primitives.stdout
Normal file
@@ -0,0 +1,13 @@
|
||||
ffi_id_int(-42) = -42
|
||||
ffi_id_short(-1234) = -1234
|
||||
ffi_id_i64(huge) = 9000000000000000000
|
||||
ffi_id_uint(0xDEADBEEF) = 3735928559
|
||||
ffi_id_ushort(0xFFFF) = 65535
|
||||
ffi_id_u64(0x7FEE...) = 9218551421072305134
|
||||
ffi_id_schar(127) = 127
|
||||
ffi_id_uchar(255) = 255
|
||||
ffi_id_f32(3.5) = 3.500000
|
||||
ffi_id_f64(1.5) = 1.500000
|
||||
ffi_id_ptr roundtrip = true
|
||||
ffi_add_int(7, 8) = 15
|
||||
ffi_add_double(0.25, 0.75) = 1.000000
|
||||
1
examples/ffi/expected/1210-ffi-02-small-struct.exit
Normal file
1
examples/ffi/expected/1210-ffi-02-small-struct.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/ffi/expected/1210-ffi-02-small-struct.stderr
Normal file
1
examples/ffi/expected/1210-ffi-02-small-struct.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
12
examples/ffi/expected/1210-ffi-02-small-struct.stdout
Normal file
12
examples/ffi/expected/1210-ffi-02-small-struct.stdout
Normal file
@@ -0,0 +1,12 @@
|
||||
vec2 make = (1.500000, 2.500000)
|
||||
vec2 swap = (2.500000, 1.500000)
|
||||
vec2 sum = 4.000000
|
||||
vec4f make = (1.000000, 2.000000, 3.000000, 4.000000)
|
||||
vec4f rev = (4.000000, 3.000000, 2.000000, 1.000000)
|
||||
vec4f sum = 10.000000
|
||||
pair64 make = (100, 200)
|
||||
pair64 swap = (200, 100)
|
||||
pair64 sum = 300
|
||||
quad32 make = (10, 20, 30, 40)
|
||||
quad32 rev = (40, 30, 20, 10)
|
||||
quad32 sum = 100
|
||||
1
examples/ffi/expected/1211-ffi-03-large-struct.exit
Normal file
1
examples/ffi/expected/1211-ffi-03-large-struct.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/ffi/expected/1211-ffi-03-large-struct.stderr
Normal file
1
examples/ffi/expected/1211-ffi-03-large-struct.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
6
examples/ffi/expected/1211-ffi-03-large-struct.stdout
Normal file
6
examples/ffi/expected/1211-ffi-03-large-struct.stdout
Normal file
@@ -0,0 +1,6 @@
|
||||
big24 make = (1, 2, 3)
|
||||
big24 rotate = (3, 1, 2)
|
||||
big24 sum = 6
|
||||
big48 make = (10, 20, 30, 40, 50, 60)
|
||||
big48 reverse = (60, 50, 40, 30, 20, 10)
|
||||
big48 sum = 210
|
||||
1
examples/ffi/expected/1212-ffi-04-fp-struct.exit
Normal file
1
examples/ffi/expected/1212-ffi-04-fp-struct.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/ffi/expected/1212-ffi-04-fp-struct.stderr
Normal file
1
examples/ffi/expected/1212-ffi-04-fp-struct.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
6
examples/ffi/expected/1212-ffi-04-fp-struct.stdout
Normal file
6
examples/ffi/expected/1212-ffi-04-fp-struct.stdout
Normal file
@@ -0,0 +1,6 @@
|
||||
fquad make = (1.000000, 2.000000, 3.000000, 4.000000)
|
||||
fquad rev = (4.000000, 3.000000, 2.000000, 1.000000)
|
||||
fquad sum = 10.000000
|
||||
dquad make = (1.500000, 2.500000, 3.500000, 4.500000)
|
||||
dquad rev = (4.500000, 3.500000, 2.500000, 1.500000)
|
||||
dquad sum = 12.000000
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user