From 3a6eb921b50e191468d29c233718657f9f85d68e Mon Sep 17 00:00:00 2001 From: _ov4 Date: Thu, 22 Oct 2026 13:45:36 +0000 Subject: feat(codegen): pass C-struct records by value EXTERN parameters may now be C-struct records by value. clang's arrangement decides the passing: coerced through memory, flattened into elements, or indirect as byval or a pointer to a copy. Returning a record by value stays rejected for now. Co-Authored-By: Claude Opus 5.5 --- tests/i/extern-c-struct-value/case.json | 5 +++ tests/i/extern-c-struct-value/expected.txt | 5 +++ tests/i/extern-c-struct-value/lib.c | 16 ++++++++ tests/i/extern-c-struct-value/notes.md | 7 ++++ tests/i/extern-c-struct-value/program.cps | 57 ++++++++++++++++++++++++++++ tests/iii/extern-struct-rejected/case.json | 6 +-- tests/iii/extern-struct-rejected/notes.md | 2 +- tests/iii/extern-struct-rejected/program.cps | 8 +--- 8 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 tests/i/extern-c-struct-value/case.json create mode 100644 tests/i/extern-c-struct-value/expected.txt create mode 100644 tests/i/extern-c-struct-value/lib.c create mode 100644 tests/i/extern-c-struct-value/notes.md create mode 100644 tests/i/extern-c-struct-value/program.cps (limited to 'tests') diff --git a/tests/i/extern-c-struct-value/case.json b/tests/i/extern-c-struct-value/case.json new file mode 100644 index 0000000..1ce5970 --- /dev/null +++ b/tests/i/extern-c-struct-value/case.json @@ -0,0 +1,5 @@ +{ + "objects": [ + {"source": "lib.c", "output": "lib.o"} + ] +} diff --git a/tests/i/extern-c-struct-value/expected.txt b/tests/i/extern-c-struct-value/expected.txt new file mode 100644 index 0000000..c1cc44b --- /dev/null +++ b/tests/i/extern-c-struct-value/expected.txt @@ -0,0 +1,5 @@ +7 +6.500000 +13.000000 +3.750000 +1115 diff --git a/tests/i/extern-c-struct-value/lib.c b/tests/i/extern-c-struct-value/lib.c new file mode 100644 index 0000000..d6abe01 --- /dev/null +++ b/tests/i/extern-c-struct-value/lib.c @@ -0,0 +1,16 @@ +/* Each struct is passed by value; the host C ABI decides how. */ +struct Pair { int a, b; }; +struct Vec2 { double x, y; }; +struct Mixed { char tag; double value; int count; }; +struct Vec3f { float x, y, z; }; +struct Wide { long long lo, hi; }; + +int pair_sum(struct Pair p) { return p.a + p.b; } +double vec2_dot(struct Vec2 u, struct Vec2 v) { return u.x * v.x + u.y * v.y; } +double mixed_score(short bias, struct Mixed m, int k) { + return bias + m.value * k + m.count + (m.tag == 'M'); +} +double vec3f_sum(struct Vec3f v) { return v.x + v.y + v.z; } +long long wide_after_five(int a, int b, int c, int d, int e, struct Wide w) { + return a + b + c + d + e + w.lo + w.hi; +} diff --git a/tests/i/extern-c-struct-value/notes.md b/tests/i/extern-c-struct-value/notes.md new file mode 100644 index 0000000..912352a --- /dev/null +++ b/tests/i/extern-c-struct-value/notes.md @@ -0,0 +1,7 @@ +# extern-c-struct-value + +Human review: pending. Expected output was independently traced by AI, not captured from cpsc. + +Basis: compiler extension (EXTERN "C", INCLUDE_OBJ of a user object); a record whose fields are all C types is passed by value like the matching C struct (C11 6.5.2.2p7: arguments are assigned to the parameters). + +Trace: the harness compiles `lib.c` with the host C compiler. pair_sum receives Pair {3, 4} and returns 3 + 4 = 7. vec2_dot receives {1.5, 2} and {4, 0.25}: 1.5 * 4 + 2 * 0.25 = 6 + 0.5 = 6.5, printed like other C doubles as `6.500000`. mixed_score receives bias -1, Mixed {'M', 2.5, 3} and k 4: -1 + 2.5 * 4 + 3 + 1 (the tag is 'M') = 13, printed `13.000000`. vec3f_sum receives {0.5, 1.25, 2}, all exact in float, and returns 0.5 + 1.25 + 2 = 3.75, printed `3.750000`. wide_after_five receives five ints and Wide {100, 1000}: 1 + 2 + 3 + 4 + 5 + 100 + 1000 = 1115. On x86-64 System V the five ints leave one integer register, so the 16-byte Wide goes on the stack. diff --git a/tests/i/extern-c-struct-value/program.cps b/tests/i/extern-c-struct-value/program.cps new file mode 100644 index 0000000..9db6319 --- /dev/null +++ b/tests/i/extern-c-struct-value/program.cps @@ -0,0 +1,57 @@ +// Records whose fields are C types pass to C functions by value. +INCLUDE_OBJ "lib.o" +TYPE CShort = C_TYPE("short") +TYPE CFloat = C_TYPE("float") +TYPE CLongLong = C_TYPE("long long") +TYPE Pair + DECLARE A : C_INT + DECLARE B : C_INT +ENDTYPE +TYPE Vec2 + DECLARE X : C_DOUBLE + DECLARE Y : C_DOUBLE +ENDTYPE +TYPE Mixed + DECLARE Tag : C_CHAR + DECLARE Value : C_DOUBLE + DECLARE Count : C_INT +ENDTYPE +TYPE Vec3f + DECLARE X : CFloat + DECLARE Y : CFloat + DECLARE Z : CFloat +ENDTYPE +TYPE Wide + DECLARE Lo : CLongLong + DECLARE Hi : CLongLong +ENDTYPE +EXTERN "C" EXTERN_FUNCTION pair_sum(Pair) RETURNS C_INT +EXTERN "C" EXTERN_FUNCTION vec2_dot(Vec2, Vec2) RETURNS C_DOUBLE +EXTERN "C" EXTERN_FUNCTION mixed_score(CShort, Mixed, C_INT) RETURNS C_DOUBLE +EXTERN "C" EXTERN_FUNCTION vec3f_sum(Vec3f) RETURNS C_DOUBLE +EXTERN "C" EXTERN_FUNCTION wide_after_five(C_INT, C_INT, C_INT, C_INT, C_INT, Wide) RETURNS CLongLong +DECLARE P : Pair +DECLARE U : Vec2 +DECLARE V : Vec2 +DECLARE M : Mixed +DECLARE F : Vec3f +DECLARE W : Wide +P.A <- (C_INT)3 +P.B <- (C_INT)4 +OUTPUT pair_sum(P) +U.X <- (C_DOUBLE)1.5 +U.Y <- (C_DOUBLE)2 +V.X <- (C_DOUBLE)4 +V.Y <- (C_DOUBLE)0.25 +OUTPUT vec2_dot(U, V) +M.Tag <- (C_CHAR)'M' +M.Value <- (C_DOUBLE)2.5 +M.Count <- (C_INT)3 +OUTPUT mixed_score((CShort)-1, M, (C_INT)4) +F.X <- (CFloat)0.5 +F.Y <- (CFloat)1.25 +F.Z <- (CFloat)2 +OUTPUT vec3f_sum(F) +W.Lo <- (CLongLong)100 +W.Hi <- (CLongLong)1000 +OUTPUT wide_after_five((C_INT)1, (C_INT)2, (C_INT)3, (C_INT)4, (C_INT)5, W) diff --git a/tests/iii/extern-struct-rejected/case.json b/tests/iii/extern-struct-rejected/case.json index 6a4483f..09c7186 100644 --- a/tests/iii/extern-struct-rejected/case.json +++ b/tests/iii/extern-struct-rejected/case.json @@ -2,11 +2,11 @@ "errors": [ { "reason": "EXTERN\\ parameter\\ 1\\ of\\ puts\\ uses\\ record\\ Person,\\ which\\ is\\ not\\ a\\ C\\ struct:\\ field\\ Name\\ of\\ Person\\ has\\ type\\ STRING", - "line": 12 + "line": 8 }, { - "reason": "EXTERN\\ parameter\\ 1\\ of\\ abs\\ cannot\\ be\\ record\\ Point\\ by\\ value;\\ use\\ a\\ pointer\\ type\\ to\\ it", - "line": 13 + "reason": "EXTERN\\ parameter\\ 1\\ of\\ abs\\ uses\\ record\\ Person,\\ which\\ is\\ not\\ a\\ C\\ struct:\\ field\\ Name\\ of\\ Person\\ has\\ type\\ STRING", + "line": 9 } ] } diff --git a/tests/iii/extern-struct-rejected/notes.md b/tests/iii/extern-struct-rejected/notes.md index f33ef66..8d38d13 100644 --- a/tests/iii/extern-struct-rejected/notes.md +++ b/tests/iii/extern-struct-rejected/notes.md @@ -1,5 +1,5 @@ # extern-struct-rejected -Compiler extension: an EXTERN signature accepts a record only as a C struct (every field a C type, a C struct or a pointer type to one) and, for now, only through a pointer type. Person has a STRING field, so a pointer to it is rejected and the field is named. Point is a C struct, but passing it by value is not supported yet. +Compiler extension: an EXTERN signature accepts a record, through a pointer type or by value, only as a C struct (every field a C type, a C struct or a pointer type to one). Person has a STRING field, so both a pointer to it and Person by value are rejected and the field is named. Require exit 1 and each cause on its EXTERN line. diff --git a/tests/iii/extern-struct-rejected/program.cps b/tests/iii/extern-struct-rejected/program.cps index e7f38d1..eec88a1 100644 --- a/tests/iii/extern-struct-rejected/program.cps +++ b/tests/iii/extern-struct-rejected/program.cps @@ -1,14 +1,10 @@ -// Records cross the EXTERN boundary only as C structs through a pointer type. +// Records cross the EXTERN boundary only as C structs. INCLUDE_OBJ "__libc" TYPE Person DECLARE Name : STRING DECLARE Age : C_INT ENDTYPE TYPE PPerson = ^Person -TYPE Point - DECLARE X : C_INT - DECLARE Y : C_INT -ENDTYPE EXTERN "C" EXTERN_PROCEDURE puts(PPerson) -EXTERN "C" EXTERN_FUNCTION abs(Point) RETURNS C_INT +EXTERN "C" EXTERN_FUNCTION abs(Person) RETURNS C_INT OUTPUT 1 -- cgit v1.3.1-4-g156e