diff options
| -rw-r--r-- | AGENTS/ARCH/DEPENDENCIES.md | 3 | ||||
| -rw-r--r-- | AGENTS/ARCH/LAYOUT.md | 1 | ||||
| -rw-r--r-- | AGENTS/ARCH/LINKING.md | 7 | ||||
| -rw-r--r-- | AGENTS/PITFALLS/exceptions-and-c-interop.md | 10 | ||||
| -rw-r--r-- | AGENTS/SUBSYSTEMS/EXTERN.md | 51 | ||||
| -rw-r--r-- | doc/language/keyword/cpsc-keyword-extern.7.md | 36 | ||||
| -rw-r--r-- | doc/language/keyword/cpsc-keyword-type.7.md | 4 |
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). |
