diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-22 13:51:44 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-22 13:51:44 +0000 |
| commit | 5eccd90b47b327917ad0e5858c79ca8fab27cd74 (patch) | |
| tree | 56e5c4110560679047e8813ab221742a0ab739f6 /tests | |
| parent | e3cc6c585a15fe4478dd2312bf5d390896729a31 (diff) | |
feat(codegen): pass C-struct records by value as variadic arguments
A variadic EXTERN call is now arranged by clang per call, from the fixed
parameter count and the promoted argument types. C-struct records pass
unchanged in variadic positions, as C11 6.5.2.2p6-7 and 7.16.1.1p2
allow; a record that is not a C struct names the offending field.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/i/extern-c-struct-value/expected.txt | 2 | ||||
| -rw-r--r-- | tests/i/extern-c-struct-value/lib.c | 24 | ||||
| -rw-r--r-- | tests/i/extern-c-struct-value/notes.md | 4 | ||||
| -rw-r--r-- | tests/i/extern-c-struct-value/program.cps | 9 | ||||
| -rw-r--r-- | tests/iii/extern-struct-rejected/case.json | 4 | ||||
| -rw-r--r-- | tests/iii/extern-struct-rejected/notes.md | 4 | ||||
| -rw-r--r-- | tests/iii/extern-struct-rejected/program.cps | 4 |
7 files changed, 45 insertions, 6 deletions
diff --git a/tests/i/extern-c-struct-value/expected.txt b/tests/i/extern-c-struct-value/expected.txt index 19eef3c..f96c960 100644 --- a/tests/i/extern-c-struct-value/expected.txt +++ b/tests/i/extern-c-struct-value/expected.txt @@ -8,3 +8,5 @@ 1.500000 2.000000 Q 0.250000 9 0.500000 1.250000 2.000000 +7.750000 +14 diff --git a/tests/i/extern-c-struct-value/lib.c b/tests/i/extern-c-struct-value/lib.c index 94afc3f..d74a1d0 100644 --- a/tests/i/extern-c-struct-value/lib.c +++ b/tests/i/extern-c-struct-value/lib.c @@ -1,3 +1,5 @@ +#include <stdarg.h> + /* Each struct is passed or returned by value; the host C ABI decides how. */ struct Pair { int a, b; }; struct Vec2 { double x, y; }; @@ -22,3 +24,25 @@ struct Mixed make_mixed(char tag, double value, int count) { return m; } struct Vec3f make_vec3f(float x, float y, float z) { struct Vec3f v = {x, y, z}; return v; } + +/* Each argument after n is a struct read back with va_arg. */ +double sum_vec2(int n, ...) { + va_list ap; + double total = 0; + va_start(ap, n); + for (int i = 0; i < n; ++i) { + struct Vec2 v = va_arg(ap, struct Vec2); + total += v.x + v.y; + } + va_end(ap); + return total; +} + +int mixed_counts(int n, ...) { + va_list ap; + int total = 0; + va_start(ap, n); + for (int i = 0; i < n; ++i) total += va_arg(ap, struct Mixed).count; + va_end(ap); + return total; +} diff --git a/tests/i/extern-c-struct-value/notes.md b/tests/i/extern-c-struct-value/notes.md index a614d7b..c694d08 100644 --- a/tests/i/extern-c-struct-value/notes.md +++ b/tests/i/extern-c-struct-value/notes.md @@ -2,6 +2,6 @@ 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 and returned by value like the matching C struct (C11 6.5.2.2p7: arguments are assigned to the parameters; 6.8.6.4p3: the returned value is the function call's value). +Basis: compiler extension (EXTERN "C", INCLUDE_OBJ of a user object); a record whose fields are all C types is passed and returned by value like the matching C struct (C11 6.5.2.2p7: arguments are assigned to the parameters; 6.8.6.4p3: the returned value is the function call's value; 6.5.2.2p6-7 and 7.16.1.1p2: default promotions leave structs unchanged and va_arg reads a struct type). -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. The make_ functions return the struct built from their arguments: make_pair(3, 4) gives `3 4`, and pair_sum(make_pair(5, 6)) passes the returned Pair straight on, 5 + 6 = 11. make_vec2(1.5, 2) gives `1.500000 2.000000`. make_mixed('Q', 0.25, 9) gives `Q 0.250000 9`; on x86-64 System V this 24-byte struct comes back through memory supplied by the caller. make_vec3f(0.5, 1.25, 2) gives `0.500000 1.250000 2.000000`, the C floats printed with %f. +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. The make_ functions return the struct built from their arguments: make_pair(3, 4) gives `3 4`, and pair_sum(make_pair(5, 6)) passes the returned Pair straight on, 5 + 6 = 11. make_vec2(1.5, 2) gives `1.500000 2.000000`. make_mixed('Q', 0.25, 9) gives `Q 0.250000 9`; on x86-64 System V this 24-byte struct comes back through memory supplied by the caller. make_vec3f(0.5, 1.25, 2) gives `0.500000 1.250000 2.000000`, the C floats printed with %f. sum_vec2 reads two struct Vec2 values with va_arg: U is still {1.5, 2} from make_vec2 and V is {4, 0.25}, so 1.5 + 2 + 4 + 0.25 = 7.75, printed `7.750000`. mixed_counts reads two struct Mixed values: M holds count 9 from make_mixed and N = make_mixed('R', 1, 5) holds count 5, so 9 + 5 = 14. diff --git a/tests/i/extern-c-struct-value/program.cps b/tests/i/extern-c-struct-value/program.cps index 6b2cb8e..c8b3a1f 100644 --- a/tests/i/extern-c-struct-value/program.cps +++ b/tests/i/extern-c-struct-value/program.cps @@ -1,4 +1,5 @@ -// Records whose fields are C types pass to and return from C functions by value. +// Records whose fields are C types pass to and return from C functions by +// value, also as variadic arguments. INCLUDE_OBJ "lib.o" TYPE CShort = C_TYPE("short") TYPE CFloat = C_TYPE("float") @@ -34,10 +35,13 @@ EXTERN "C" EXTERN_FUNCTION make_pair(C_INT, C_INT) RETURNS Pair EXTERN "C" EXTERN_FUNCTION make_vec2(C_DOUBLE, C_DOUBLE) RETURNS Vec2 EXTERN "C" EXTERN_FUNCTION make_mixed(C_CHAR, C_DOUBLE, C_INT) RETURNS Mixed EXTERN "C" EXTERN_FUNCTION make_vec3f(CFloat, CFloat, CFloat) RETURNS Vec3f +EXTERN "C" EXTERN_FUNCTION sum_vec2(C_INT, ...) RETURNS C_DOUBLE +EXTERN "C" EXTERN_FUNCTION mixed_counts(C_INT, ...) RETURNS C_INT DECLARE P : Pair DECLARE U : Vec2 DECLARE V : Vec2 DECLARE M : Mixed +DECLARE N : Mixed DECLARE F : Vec3f DECLARE W : Wide P.A <- (C_INT)3 @@ -68,3 +72,6 @@ M <- make_mixed((C_CHAR)'Q', (C_DOUBLE)0.25, (C_INT)9) OUTPUT M.Tag, " ", M.Value, " ", M.Count F <- make_vec3f((CFloat)0.5, (CFloat)1.25, (CFloat)2) OUTPUT F.X, " ", F.Y, " ", F.Z +OUTPUT sum_vec2((C_INT)2, U, V) +N <- make_mixed((C_CHAR)'R', (C_DOUBLE)1, (C_INT)5) +OUTPUT mixed_counts((C_INT)2, M, N) diff --git a/tests/iii/extern-struct-rejected/case.json b/tests/iii/extern-struct-rejected/case.json index f83ef3e..cd7a3dd 100644 --- a/tests/iii/extern-struct-rejected/case.json +++ b/tests/iii/extern-struct-rejected/case.json @@ -11,6 +11,10 @@ { "reason": "The\\ return\\ type\\ of\\ EXTERN_FUNCTION\\ atoi\\ uses\\ record\\ Person,\\ which\\ is\\ not\\ a\\ C\\ struct:\\ field\\ Name\\ of\\ Person\\ has\\ type\\ STRING", "line": 10 + }, + { + "reason": "Variadic\\ argument\\ 2\\ to\\ printf\\ uses\\ record\\ Person,\\ which\\ is\\ not\\ a\\ C\\ struct:\\ field\\ Name\\ of\\ Person\\ has\\ type\\ STRING", + "line": 13 } ] } diff --git a/tests/iii/extern-struct-rejected/notes.md b/tests/iii/extern-struct-rejected/notes.md index 3e733c1..650b420 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, 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 a pointer to it, Person as a by-value parameter and Person as a by-value result are all rejected and the field is named. +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 a pointer to it, Person as a by-value parameter, as a by-value result and as a by-value variadic argument are all rejected and the field is named. -Require exit 1 and each cause on its EXTERN line. +Require exit 1 and each cause on its EXTERN or call line. diff --git a/tests/iii/extern-struct-rejected/program.cps b/tests/iii/extern-struct-rejected/program.cps index 8fd1fd9..ced6fee 100644 --- a/tests/iii/extern-struct-rejected/program.cps +++ b/tests/iii/extern-struct-rejected/program.cps @@ -8,4 +8,6 @@ TYPE PPerson = ^Person EXTERN "C" EXTERN_PROCEDURE puts(PPerson) EXTERN "C" EXTERN_FUNCTION abs(Person) RETURNS C_INT EXTERN "C" EXTERN_FUNCTION atoi(C_STRING) RETURNS Person -OUTPUT 1 +EXTERN "C" EXTERN_FUNCTION printf(C_STRING, ...) RETURNS C_INT +DECLARE Someone : Person +OUTPUT printf((C_STRING)"%d", Someone) |
