aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-22 14:02:47 +0000
committer_ov4 <ov4@ov4.org>2026-10-22 14:02:47 +0000
commite57e63d0d2a0e4c38460d399a0c62d140a633706 (patch)
treeb2f0cea741b0f87bf35002e48a1aa66fcba62030
parentf694cc443ccc17a494cd195151882ab5e491bbb2 (diff)
docs: document C-struct records for EXTERN
Describe which TYPE records count as C structs, passing them by value and by pointer as parameters, RETURNS and variadic arguments, and the recursive pointer types EXTERN accepts. Note the clangCodeGen dependency and the CABI sources in the architecture notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--AGENTS/ARCH/DEPENDENCIES.md3
-rw-r--r--AGENTS/ARCH/LAYOUT.md1
-rw-r--r--AGENTS/ARCH/LINKING.md7
-rw-r--r--AGENTS/PITFALLS/exceptions-and-c-interop.md10
-rw-r--r--AGENTS/SUBSYSTEMS/EXTERN.md51
-rw-r--r--doc/language/keyword/cpsc-keyword-extern.7.md36
-rw-r--r--doc/language/keyword/cpsc-keyword-type.7.md4
7 files changed, 101 insertions, 11 deletions
diff --git a/AGENTS/ARCH/DEPENDENCIES.md b/AGENTS/ARCH/DEPENDENCIES.md
index 75a165e..24a34a6 100644
--- a/AGENTS/ARCH/DEPENDENCIES.md
+++ b/AGENTS/ARCH/DEPENDENCIES.md
@@ -32,3 +32,6 @@ Build pin: **LLVM 23 and upstream Clang 23** (any 23.x release); 23.1.3 is teste
by nightly CI on Linux, Windows and macOS. LLVM 23 uses `CreateGlobalString` for opaque string pointers,
`hasTerminator()` for block termination checks (its `getTerminator()` requires an
existing terminator), and an explicit `llvm::Triple` for the embedded target.
+The static Clang archives used are `clangDriver` (native linking) and `clangCodeGen`
+(the C ABI for EXTERN, through `clang/CodeGen/CodeGenABITypes.h`); CMake checks that
+both are static.
diff --git a/AGENTS/ARCH/LAYOUT.md b/AGENTS/ARCH/LAYOUT.md
index 422d4bb..805f1e1 100644
--- a/AGENTS/ARCH/LAYOUT.md
+++ b/AGENTS/ARCH/LAYOUT.md
@@ -25,6 +25,7 @@
| `lib/CodeGen/MemoryHandler.cc` | Explicit raw memory allocation, resizing and release with a live-address tracking list |
| `lib/CodeGen/FileHandler.cc`, `FileRuntimeIO.cc` | File runtime setup/lookup/open and call entry points; read/write/close/EOF helper bodies |
| `lib/CodeGen/ArrayHandler.cc`, `ArrayParameters.cc`, `ArrayOutput.cc` | Array allocation/indexing, parameter binding/arguments and whole/subarray output |
+| `include/cps/CABI.h`, `lib/CodeGen/CABI/ClangABI.cc`, `lib/CodeGen/CABI/Lowering.cc` | EXTERN C ABI: the ABI description, the only file that includes clang CodeGen headers, and the generic lowering of signatures, arguments and returns |
| `lib/CodeGen/*Handler.cc`, `FunctionGen.cc`, `RuntimeCheck.cc`, `TypeSystem.cc` | Helpers owned by CodeGen (including exception emission) |
| `CMakeLists.txt` | 3 static libs (CPSLexer, CPSParser, CPSCodeGen) + `cpsc`. **New .cc files must be added to the source lists here** |
| `tools/driver/NativeCompiler.cc`, `cmake/NativeCompiler.cmake` | Host object generation, embedded Clang/LLD linking and compressed link resources |
diff --git a/AGENTS/ARCH/LINKING.md b/AGENTS/ARCH/LINKING.md
index 7bf1ecd..f43d5c3 100644
--- a/AGENTS/ARCH/LINKING.md
+++ b/AGENTS/ARCH/LINKING.md
@@ -1,6 +1,6 @@
# Static linking and native compilation
-All modes require static LLVM, Clang Driver and LLD archives. `cmake/EmbeddedLLVM.cmake` also selects
+All modes require static LLVM, Clang Driver, Clang CodeGen and LLD archives. `cmake/EmbeddedLLVM.cmake` also selects
static compression/terminal support archives when LLVM uses them. Linux Clang
builds link the C++ runtime statically; MinGW uses `-static`. The tested Debian
LLVM 23 executable has only libc, libm and the system loader as dynamic dependencies.
@@ -12,6 +12,11 @@ plus the system bcrypt library. XML definitions are loaded with `--whole-archive
to satisfy dllimport references in the prebuilt LLVM archive. `-static` alone
does not replace an imported target's explicit `.dll.a` path.
+`CPSCodeGen` links `clangCodeGen` and `clangDriver` (for `createInvocation`) with
+Clang's include directories: EXTERN signatures use clang's C ABI code
+([SUBSYSTEMS/EXTERN.md](../SUBSYSTEMS/EXTERN.md#c-structs-and-the-c-abi)).
+`cmake/NativeCompiler.cmake` rejects a non-static `clangCodeGen` like `clangDriver`.
+
`NativeCompiler` clones the module, sets the host triple/DataLayout and emits a PIC
object. Clang Driver constructs one platform linker job, which is dispatched to
embedded LLD without launching a process. `cmake/NativeCompiler.cmake` runs
diff --git a/AGENTS/PITFALLS/exceptions-and-c-interop.md b/AGENTS/PITFALLS/exceptions-and-c-interop.md
index 511c7e1..104be41 100644
--- a/AGENTS/PITFALLS/exceptions-and-c-interop.md
+++ b/AGENTS/PITFALLS/exceptions-and-c-interop.md
@@ -31,8 +31,14 @@ Part of the [pitfalls index](../PITFALLS.md).
## EXTERN "C" extension
-- Top-level, body-less; parameters/results are C types only, no BYREF/OVERLOAD/structs.
- Variadic arguments must be C values (default promotions).
+- Top-level, body-less; parameters/results are C types, C-struct records or pointer
+ types finally pointing to either (not `^INTEGER`, not `TYPE P = ^P`); no
+ BYREF/OVERLOAD. Variadic arguments must be C values (default promotions), C-struct
+ records or pointers (never promoted); `^x` of a C value passes as `void*`.
+- A C-struct record's fields must have those types too: one INTEGER, REAL, CHAR,
+ BOOLEAN, STRING, enum or CLASS field rejects the whole record (`... uses record R,
+ which is not a C struct: field F of R has type T`). cpsc never sees the C
+ declaration: field order and types must match it by hand.
- The driver checks every EXTERN symbol against INCLUDE_OBJ inputs before CodeGen;
`"__libc"` must be included explicitly even though the runtime always links libc.
- Only `.o` user objects; INCLUDE_OBJ is global across inputs. `-S` does not link them.
diff --git a/AGENTS/SUBSYSTEMS/EXTERN.md b/AGENTS/SUBSYSTEMS/EXTERN.md
index 4d0616a..f71bfd7 100644
--- a/AGENTS/SUBSYSTEMS/EXTERN.md
+++ b/AGENTS/SUBSYSTEMS/EXTERN.md
@@ -8,14 +8,57 @@ Part of the [subsystem notes](../SUBSYSTEMS.md). Related: [functions](FUNCTIONS.
- The parser builds a body-less `PrototypeAST` (IsExternal, IsVariadic, unnamed `argN`
params).
-- `FunctionGen::emitExternPrototype` validates C types, records a `FuncSig` with
- `IsExternal`, the declared `FunctionType` and sext/zext attributes for sub-32-bit
- integers, and `getOrInsertFunction`s the exact symbol.
+- `FunctionGen::emitExternPrototype` admits types with `TypeSystem::isExternType`
+ (after `pointerBase` follows pointer types: a non-void C type or a C struct;
+ `^INTEGER` and cycles such as `TYPE P = ^P` fail), records a `FuncSig` with
+ `IsExternal` and the clang-arranged `FuncSig::ExternABI` (see below), and
+ `getOrInsertFunction`s the exact symbol.
- Identical redeclarations append a FuncSig per declaring unit (visibility unchanged)
and `resolveCall` takes the first visible one; mismatched signatures are errors.
- Calls go through `FunctionGen::emitCall(const FuncSig &, ...)` with that type, so a
runtime declaration of the same libc symbol with another type is harmless.
-- `CodeGen::emitVariadicArgument` applies C default promotions.
+- `CodeGen::emitVariadicArgument` applies C default promotions to C scalars and
+ passes C-struct records and pointers to them unchanged (C11 §6.5.2.2p6–7,
+ §7.16.1.1p2). `^x` of a C value or C struct passes as `void*`, like C's `&x`; a
+ pseudocode-typed `^x` keeps "must be a C value; cast it to a C type". Each variadic
+ call is arranged separately from the fixed-parameter count and the promoted types.
+
+## C structs and the C ABI
+
+- `TypeSystem::isCStruct`: a TYPE record (not a CLASS) whose fields, after
+ `pointerBase`, are non-void C types or C structs. A seen list stops recursion, so
+ self-referential lists terminate. Any pseudocode field type (INTEGER, REAL, CHAR,
+ BOOLEAN, STRING, enum, CLASS) fails: `<What> uses record <Rec>, which is not a C
+ struct: field <Field> of <Owner> has type <Type>`, where `<What>` is `EXTERN
+ parameter N of F`, `The return type of EXTERN_FUNCTION F` or `Variadic argument N
+ to F`. Other unsupported types keep `EXTERN parameter N of F must be a non-void C
+ type (got X)`.
+- Records already have C layout (`TypeSystem::defineRecord`: non-packed named
+ struct), so no conversion is needed to pass a pointer.
+- Every EXTERN signature, scalar-only ones included, is arranged by clang through the
+ FunctionGen member `ClangABI ABI`: `ClangABI::arrange` (`lib/CodeGen/CABI/ClangABI.cc`)
+ calls `clang::CodeGen::arrangeFreeFunctionCall` and translates each `ABIArgInfo`
+ into a `CABISignature` of `CABIArg`s (`include/cps/CABI.h`; Direct, Extend,
+ Indirect, Ignore; any other kind is an internal error).
+ - The lazy `ClangABI::State`, created on the first EXTERN, owns the clang
+ CodeGenerator. It targets `llvm::sys::getProcessTriple()` with options from
+ `clang::createInvocation`, so darwinpcs and char signedness match the C compiler.
+ - Scalars map by name, all pointers to `void*`, and each record to a cached
+ `RecordDecl`. A size mismatch is an internal error: `Internal: cannot arrange
+ EXTERN F: record R has N bytes in cpsc but M in C` (`cannot arrange the call to
+ F` for a variadic call).
+ - Keep clang code in this file: `clang::CodeGen` clashes with `cps::CodeGen`, clang
+ classes cannot be subclassed across the RTTI mismatch, and the CodeGenerator must
+ be destroyed before the ASTContext.
+- `lib/CodeGen/CABI/Lowering.cc` applies the description without architecture
+ branches: `getCABIFunctionType`, `getCABIAttributes` (sret/byval/align/signext/
+ zeroext), `lowerCABIArguments` (coercion through a temporary
+ `alloca {RecordTy, CoerceTy}`, flattening, copies for Indirect) and
+ `lowerCABIResult` (sret, or a coerced return stored back into a record).
+- The result matches the platform C compiler on Linux x86-64, Windows x86-64 (MinGW)
+ and macOS arm64. The latter two pass large records as a pointer to a copy
+ (non-byval Indirect); only nightly CI tests that path. MinGW `long double` should
+ now pass by reference, as in C, but is untested.
## Driver and object files
diff --git a/doc/language/keyword/cpsc-keyword-extern.7.md b/doc/language/keyword/cpsc-keyword-extern.7.md
index 5797a4f..1de7a40 100644
--- a/doc/language/keyword/cpsc-keyword-extern.7.md
+++ b/doc/language/keyword/cpsc-keyword-extern.7.md
@@ -15,9 +15,11 @@ EXTERN "C" EXTERN_PROCEDURE name(type, ...)
EXTERN is a compiler extension, allowed only at top level. It declares a C function without a body; there is no ENDFUNCTION or ENDPROCEDURE. The name is the exact C symbol; calls from pseudocode remain case insensitive. Call an EXTERN_FUNCTION in an expression and an EXTERN_PROCEDURE with CALL.
-Parameters list only types. Parameter and result types must be C types: C_INT, C_CHAR, C_STRING, C_DOUBLE or a C_TYPE alias, including pointers. void is not allowed; use EXTERN_PROCEDURE for a C function returning void. EXTERN_FUNCTION requires RETURNS and EXTERN_PROCEDURE does not accept it. Structs, BYREF and BYVAL are unsupported; declare a C pointer parameter and pass ^Variable instead. Pseudocode values must be cast, as for other C values.
+Parameters list only types. Parameter and result types must be C types (C_INT, C_CHAR, C_STRING, C_DOUBLE or a C_TYPE alias, including pointers), C struct records, or pointer types such as `TYPE PInt = ^C_INT` whose chain finally points to either. Pointers to pseudocode types such as `^INTEGER` and pointer cycles such as `TYPE P = ^P` are rejected. void is not allowed; use EXTERN_PROCEDURE for a C function returning void. EXTERN_FUNCTION requires RETURNS and EXTERN_PROCEDURE does not accept it. BYREF and BYVAL are unsupported; to let C modify a variable, declare a pointer parameter and pass ^Variable instead. Pseudocode values must be cast, as for other C values.
-A final `...` makes the function variadic. Variadic arguments must also be C values; they receive C default argument promotions (float to double, narrow integers and _Bool to int or unsigned int).
+A C struct record is a TYPE record, not a CLASS, whose fields all have such types; a pointer field may refer to its own record, as in a linked list. A record with any other field, such as INTEGER, REAL, CHAR, BOOLEAN, STRING, an enumeration or a CLASS, is rejected with an error naming the field, for example `EXTERN parameter 1 of puts uses record Person, which is not a C struct: field Name of Person has type STRING`. cpsc does not read C headers: declare the fields in the same order and with the same C types as the C struct. A C struct record passes by value, as a copy, or through a pointer type to the record, which lets the C function change the caller's variable; neither needs a cast. Both work for parameters, RETURNS and variadic arguments. The calling convention is that of the platform's C compiler, computed with Clang's C ABI rules.
+
+A final `...` makes the function variadic. Variadic arguments must also be C values, C struct records or pointers to them; `^x` passes a plain pointer when x is a C value or a C struct, like C's `&x`. C scalars receive C default argument promotions (float to double, narrow integers and _Bool to int or unsigned int). As in C, records and pointers are not promoted; the C function reads them with `va_arg(ap, struct S)` or `va_arg(ap, struct S *)`.
Every EXTERN symbol must be defined by an object named with INCLUDE_OBJ, otherwise compilation fails. EXTERN cannot be combined with OVERLOAD or share a name with a FUNCTION or PROCEDURE. As in C, the same function may be declared again in the same or another input file when the signature is identical (equivalent C aliases match); conflicting declarations are errors. Each file sees its own declarations and those of the files it includes.
@@ -32,6 +34,34 @@ Count <- printf((C_STRING)"%d|", abs((C_INT)-5))
OUTPUT ""
```
+`vec.c`, compiled with `cc -c vec.c`:
+
+```text
+struct Vec2 { double x, y; };
+double vec2_dot(struct Vec2 a, struct Vec2 b) { return a.x * b.x + a.y * b.y; }
+void vec2_scale(struct Vec2 *v, double k) { v->x *= k; v->y *= k; }
+```
+
+`me.cps`:
+
+```text
+INCLUDE_OBJ "vec.o"
+TYPE Vec2
+ DECLARE X : C_DOUBLE
+ DECLARE Y : C_DOUBLE
+ENDTYPE
+TYPE Vec2Ptr = ^Vec2
+EXTERN "C" EXTERN_FUNCTION vec2_dot(Vec2, Vec2) RETURNS C_DOUBLE
+EXTERN "C" EXTERN_PROCEDURE vec2_scale(Vec2Ptr, C_DOUBLE)
+DECLARE V : Vec2
+V.X <- (C_DOUBLE)1.5
+V.Y <- (C_DOUBLE)2
+CALL vec2_scale(^V, (C_DOUBLE)2)
+OUTPUT vec2_dot(V, V)
+```
+
+Run with `cpsc me.cps -r` to print `25.000000`.
+
# SEE ALSO
-[cpsc-keyword-include_obj(7)](cpsc-keyword-include_obj.7.md), [cpsc-keyword-c_type(7)](cpsc-keyword-c_type.7.md), [cpsc-keyword-call(7)](cpsc-keyword-call.7.md), [cpsc-language-overview(7)](../cpsc-language-overview.7.md).
+[cpsc-keyword-include_obj(7)](cpsc-keyword-include_obj.7.md), [cpsc-keyword-c_type(7)](cpsc-keyword-c_type.7.md), [cpsc-keyword-type(7)](cpsc-keyword-type.7.md), [cpsc-keyword-call(7)](cpsc-keyword-call.7.md), [cpsc-language-overview(7)](../cpsc-language-overview.7.md).
diff --git a/doc/language/keyword/cpsc-keyword-type.7.md b/doc/language/keyword/cpsc-keyword-type.7.md
index 1ca245f..c460117 100644
--- a/doc/language/keyword/cpsc-keyword-type.7.md
+++ b/doc/language/keyword/cpsc-keyword-type.7.md
@@ -23,6 +23,8 @@ A prefix caret takes an address (`^variable`); a suffix caret dereferences a poi
Records contain one or more DECLARE fields and end with ENDTYPE. Access fields with a dot. Whole records copy by value; array fields are unsupported. Distinct user-defined type names remain distinct types. SET/DEFINE are reserved but unsupported.
+A record whose fields are all C types, such records or pointer types to either is a C struct: EXTERN functions take and return it by value or through a pointer type. See [cpsc-keyword-extern(7)](cpsc-keyword-extern.7.md).
+
C_TYPE imports a native C type as an alias. Unlike the original enumerations, records and pointer declarations, C aliases with the same underlying C type are interchangeable. See [cpsc-keyword-c_type(7)](cpsc-keyword-c_type.7.md) for supported types and explicit casts.
# EXAMPLE
@@ -42,4 +44,4 @@ OUTPUT Position.X
# SEE ALSO
-[cpsc-language-overview(7)](../cpsc-language-overview.7.md), [cpsc-keyword-declare(7)](cpsc-keyword-declare.7.md), [cpsc-keyword-class(7)](cpsc-keyword-class.7.md), [cpsc-keyword-try(7)](cpsc-keyword-try.7.md).
+[cpsc-language-overview(7)](../cpsc-language-overview.7.md), [cpsc-keyword-declare(7)](cpsc-keyword-declare.7.md), [cpsc-keyword-class(7)](cpsc-keyword-class.7.md), [cpsc-keyword-extern(7)](cpsc-keyword-extern.7.md), [cpsc-keyword-try(7)](cpsc-keyword-try.7.md).