aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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).