aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-22 13:45:36 +0000
committer_ov4 <ov4@ov4.org>2026-10-22 13:45:36 +0000
commit3a6eb921b50e191468d29c233718657f9f85d68e (patch)
treeebf411667c1a4360f6e78e0ac971f9df924511d1
parent06538905915b15bde6364459929fe89cf0b8de5c (diff)
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 <noreply@anthropic.com>
-rw-r--r--lib/CodeGen/FunctionGen.cc12
-rw-r--r--tests/i/extern-c-struct-value/case.json5
-rw-r--r--tests/i/extern-c-struct-value/expected.txt5
-rw-r--r--tests/i/extern-c-struct-value/lib.c16
-rw-r--r--tests/i/extern-c-struct-value/notes.md7
-rw-r--r--tests/i/extern-c-struct-value/program.cps57
-rw-r--r--tests/iii/extern-struct-rejected/case.json6
-rw-r--r--tests/iii/extern-struct-rejected/notes.md2
-rw-r--r--tests/iii/extern-struct-rejected/program.cps8
9 files changed, 102 insertions, 16 deletions
diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc
index 2cf6da3..9cc1c08 100644
--- a/lib/CodeGen/FunctionGen.cc
+++ b/lib/CodeGen/FunctionGen.cc
@@ -291,9 +291,9 @@ bool FunctionGen::emitExternPrototype(PrototypeAST *Proto) {
return false;
}
- // Records cross the C boundary only as C structs, for now through a
- // pointer type. What names the parameter or return type in diagnostics.
- auto CheckRecord = [&](const TypeInfo *Info, const std::string &What) {
+ // Records cross the C boundary only as C structs, and are not yet
+ // returned by value. What names the parameter or return type in diagnostics.
+ auto CheckRecord = [&](const TypeInfo *Info, const std::string &What, bool IsResult) {
const TypeInfo *Record = Info->Kind == TypeKind::Pointer
? Types.resolve(Info->Pointer.PointeeTypeName) : Info;
if (!Record || !Record->isRecord() || Record->IsClass) return true;
@@ -303,7 +303,7 @@ bool FunctionGen::emitExternPrototype(PrototypeAST *Proto) {
Record->Name.c_str(), Why.c_str());
return false;
}
- if (Record == Info) {
+ if (IsResult && Record == Info) {
reportError("%s cannot be record %s by value; use a pointer type to it", What.c_str(),
Record->Name.c_str());
return false;
@@ -328,7 +328,7 @@ bool FunctionGen::emitExternPrototype(PrototypeAST *Proto) {
reportError("Unknown type %s", P.TypeName.c_str());
return false;
}
- if (!CheckRecord(Info, "EXTERN parameter " + std::to_string(I + 1) + " of " + Name))
+ if (!CheckRecord(Info, "EXTERN parameter " + std::to_string(I + 1) + " of " + Name, false))
return false;
if (!Types.isExternType(Info)) {
reportError("EXTERN parameter %zu of %s must be a non-void C type (got %s)",
@@ -347,7 +347,7 @@ bool FunctionGen::emitExternPrototype(PrototypeAST *Proto) {
reportError("Unknown type %s", Proto->getReturnType().c_str());
return false;
}
- if (!CheckRecord(Info, "The return type of EXTERN_FUNCTION " + Name)) return false;
+ if (!CheckRecord(Info, "The return type of EXTERN_FUNCTION " + Name, true)) return false;
if (!Types.isExternType(Info)) {
reportError("EXTERN_FUNCTION %s must return a non-void C type (got %s)",
Name.c_str(), Proto->getReturnType().c_str());
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