diff --git a/code-samples/appendices-annotations-c-api-annotation.pony b/code-samples/appendices-annotations-c-api-annotation.pony new file mode 100644 index 00000000..6faa0912 --- /dev/null +++ b/code-samples/appendices-annotations-c-api-annotation.pony @@ -0,0 +1,5 @@ +class \c_api\ val Adder + let _base: I64 + + new val create(base: I64) => _base = base + fun val add(x: I64): I64 => _base + x diff --git a/code-samples/c-ffi-exporting-basic.pony b/code-samples/c-ffi-exporting-basic.pony new file mode 100644 index 00000000..1b362ccc --- /dev/null +++ b/code-samples/c-ffi-exporting-basic.pony @@ -0,0 +1,6 @@ +class \c_api\ val Adder + let _base: I64 + + new val create(base: I64) => _base = base + fun val add(x: I64): I64 => _base + x + fun val base(): I64 => _base diff --git a/code-samples/c-ffi-exporting-generic-alias.pony b/code-samples/c-ffi-exporting-generic-alias.pony new file mode 100644 index 00000000..b781530c --- /dev/null +++ b/code-samples/c-ffi-exporting-generic-alias.pony @@ -0,0 +1,8 @@ +class MyBox[A: (Real[A] val & Integer[A] val)] + let _value: A + + new val create(value: A) => _value = value + fun val get(): A => _value + fun val add(y: A): A => _value + y + +type \c_api\ BoxedI64 is MyBox[I64] diff --git a/code-samples/c-ffi-exporting-primitive.pony b/code-samples/c-ffi-exporting-primitive.pony new file mode 100644 index 00000000..d32945aa --- /dev/null +++ b/code-samples/c-ffi-exporting-primitive.pony @@ -0,0 +1,3 @@ +primitive \c_api\ Math + fun val add(x: I64, y: I64): I64 => x + y + fun val mul(x: I64, y: I64): I64 => x * y diff --git a/code-samples/c-ffi-exporting-use-alias.pony b/code-samples/c-ffi-exporting-use-alias.pony new file mode 100644 index 00000000..a1c73d3c --- /dev/null +++ b/code-samples/c-ffi-exporting-use-alias.pony @@ -0,0 +1 @@ +use math = "mylib" diff --git a/code-samples/c-ffi-exporting-use-package.pony b/code-samples/c-ffi-exporting-use-package.pony new file mode 100644 index 00000000..d0973cc2 --- /dev/null +++ b/code-samples/c-ffi-exporting-use-package.pony @@ -0,0 +1,7 @@ +use "mylib" +use @add_from_c[I64](adder: Adder, x: I64) + +actor Main + new create(env: Env) => + let adder = Adder(10) + env.out.print(@add_from_c(adder, 5).string()) diff --git a/docs/appendices/annotations.md b/docs/appendices/annotations.md index 49a9ebeb..67e99d9b 100644 --- a/docs/appendices/annotations.md +++ b/docs/appendices/annotations.md @@ -12,6 +12,7 @@ Here, `annotation1` and `annotation2` can be any valid Pony identifier, i.e. a s Annotations are allowed after any scoping keyword or symbol. The full list is: +- `type` - `actor` - `class` - `struct` @@ -104,3 +105,13 @@ The annotation is also useful on matches that are already exhaustive as a future ```pony --8<-- "appendices-annotations-exhaustive-annotation.pony:8:13" ``` + +#### `c_api` + +Recognised on type declarations (`class`, `primitive`, `struct`, `actor`, `type`). Generates C-ABI wrapper functions for the type's eligible public methods and a `.h` header that declares them, so C code can call Pony methods on Pony objects. This feature is experimental and may change in future releases. + +```pony +--8<-- "appendices-annotations-c-api-annotation.pony" +``` + +See [Exporting Pony Methods to C](/c-ffi/exporting.md) for the full details. diff --git a/docs/c-ffi/c-shims.md b/docs/c-ffi/c-shims.md index ac16ca70..2318a558 100644 --- a/docs/c-ffi/c-shims.md +++ b/docs/c-ffi/c-shims.md @@ -89,6 +89,8 @@ include/version.h Building and running prints `answer: 42` and `version: 7`. `answer` returns the macro set by `cdefine:`; `version` returns the constant from the header found via `cincludedir:`. +A shim can also call Pony methods on Pony objects through exported wrappers. The `\c_api\` annotation on a type declaration generates C-ABI wrappers and a header that any shim can include. See [Exporting Pony Methods to C](/c-ffi/exporting.md) for details. + That is everything you need to write and build a shim. The rest of this page is reference material — how shims are discovered and linked, how `--safe` applies, and which platforms are supported. ## How shims are discovered diff --git a/docs/c-ffi/exporting.md b/docs/c-ffi/exporting.md new file mode 100644 index 00000000..85b714ea --- /dev/null +++ b/docs/c-ffi/exporting.md @@ -0,0 +1,143 @@ +# Exporting Pony Methods to C + +The `\c_api\` annotation on a type declaration generates C-callable wrapper functions for that type's public methods and a `.h` header that declares them. C code — including [C shims](/c-ffi/c-shims.md) — can then call Pony methods on Pony objects through these wrappers. + +Without this annotation, calling a Pony method from C is not possible. Methods that are never called from Pony are not compiled at all, and methods that are compiled use LLVM's fast calling convention, which C cannot call. The annotation addresses both problems: it forces the methods to be compiled and generates a wrapper for each one that uses the C calling convention. + +This feature is experimental and may change in future releases. + +## A basic example + +This example has two packages. The `Adder` class lives in a library package called `mylib/`, and the main package consumes it through a C shim. + +Annotate the class with `\c_api\` to export its public methods: + +```pony +--8<-- "c-ffi-exporting-basic.pony" +``` + +ponyc generates a wrapper function for each eligible public method (`add` and `base` in this example) and writes their declarations to `mylib_export.h`. A C shim in the main package can include that header and call the wrappers: + +```c +// shim.c (in the main package directory) +#include "mylib_export.h" + +int64_t add_from_c(void* adder, int64_t x) { + return mylib_Adder_add(adder, x); +} +``` + +The main package's Pony code calls the shim function via FFI, which in turn calls the Pony method through the generated wrapper: + +```pony +--8<-- "c-ffi-exporting-use-package.pony" +``` + +```text +mylib/ + mylib.pony (the Adder class) +main.pony (use "mylib", FFI declaration, Main) +shim.c (calls mylib_Adder_add) +``` + +## Which types can be exported + +The annotation is valid on `class`, `primitive`, `struct`, `actor`, and `type` (type alias) declarations. It is not valid on `trait` or `interface` declarations, on methods, or on private types (names starting with `_`). Annotating any of these produces a compile error. + +Generic types cannot be exported directly. To export a concrete reification of a generic type, use a type alias — see [Exporting generic types](#exporting-generic-types) below. + +## How C names are derived + +The C-facing name of each wrapper function is `[prefix_]TypeName_method_name`. + +The prefix comes from the `use` statement in the package that imports the exported type's package. With `use "mylib"`, the prefix is `mylib`. With an alias — `use math = "mylib"` — the prefix is the alias: + +```pony +--8<-- "c-ffi-exporting-use-alias.pony" +``` + +| `use` statement | Wrapper name | Header file | +|---|---|---| +| `use "mylib"` | `mylib_Adder_add` | `mylib_export.h` | +| `use math = "mylib"` | `math_Adder_add` | `math_export.h` | +| Main package (no `use`) | `Adder_add` | `_export.h` | + +Characters that are not valid in C identifiers are replaced with underscores. + +## Which methods are exported + +Only public, non-generic `fun` methods with no tuple parameters or return types and no `?` (partial) marker are exported. Everything else is excluded: + +- **Constructors** (`new`) — C code cannot construct Pony objects through this mechanism. +- **Behaviors** (`be`) — asynchronous message sends with no return value meaningful to C. +- **Private methods** — names starting with `_`. +- **Partial methods** — Pony's error mechanism has no C equivalent. +- **Methods with tuple parameters or return types** — Pony tuples have no direct C representation. +- **Bare methods** (`@`) — already use the C calling convention by definition. +- **Generic methods** — there is no way to determine which concrete types to instantiate. + +If every public method on an exported type falls into one of these categories, the compiler reports an error: the type has no exportable methods. + +## Primitives + +Pony primitives are stateless singletons. When the compiler generates a wrapper for a primitive method, it omits the `self` parameter from the C signature and supplies the singleton internally. The C caller does not need to obtain or pass a receiver: + +```pony +--8<-- "c-ffi-exporting-primitive.pony" +``` + +The generated C declarations for `Math` have no `self` parameter: + +```c +extern int64_t Math_add(int64_t x, int64_t y); +extern int64_t Math_mul(int64_t x, int64_t y); +``` + +For classes, structs, and actors, the first parameter is `void* pony_this` — the Pony object the method is called on: + +```c +extern int64_t Adder_add(void* pony_this, int64_t x); +extern int64_t Adder_base(void* pony_this); +``` + +## Exporting generic types + +A generic type cannot be annotated with `\c_api\` directly — there is no way to determine which type arguments to use. Instead, create a type alias that pins the type parameters to concrete types, and annotate the alias: + +```pony +--8<-- "c-ffi-exporting-generic-alias.pony" +``` + +The alias name becomes the type name in the C symbols: + +```c +extern int64_t BoxedI64_get(void* pony_this); +extern int64_t BoxedI64_add(void* pony_this, int64_t y); +``` + +The alias must refer to a single concrete type — not a union or intersection — and the underlying type must be a class, primitive, struct, or actor. + +## Type mapping + +Pony types map to C types in the generated header as follows: + +| Pony type | C type | +|---|---| +| `Bool` | `bool` | +| `I8` / `I16` / `I32` / `I64` | `int8_t` / `int16_t` / `int32_t` / `int64_t` | +| `I128` | `__int128_t` | +| `ILong` | `long` | +| `ISize` | `intptr_t` | +| `U8` / `U16` / `U32` / `U64` | `uint8_t` / `uint16_t` / `uint32_t` / `uint64_t` | +| `U128` | `__uint128_t` | +| `ULong` | `unsigned long` | +| `USize` | `size_t` | +| `F32` / `F64` | `float` / `double` | +| `None` | `void` | +| Any other type | `void*` | + +## Working with C shims + +The generated header is written to the output directory before C shim files are compiled. Packages that `use` a package with exports get `-I` added to their C compiler flags automatically, so `#include "mylib_export.h"` resolves without any `cincludedir:` configuration. + +A typical pattern: a Pony library package annotates its types with `\c_api\`, and a consuming package provides a C shim that includes the generated header, defines C functions, and calls the exported wrappers. The Pony code in the consuming package then calls those C functions via FFI. diff --git a/docs/c-ffi/index.md b/docs/c-ffi/index.md index e64f1618..0b5c6053 100644 --- a/docs/c-ffi/index.md +++ b/docs/c-ffi/index.md @@ -2,6 +2,6 @@ Pony supports integration with other native languages through the Foreign Function Interface (FFI). The FFI library provides a stable and portable API and high-level programming interface allowing Pony to integrate with native libraries easily. -Native code can reach your program two ways: you can link a library, or you can write a small piece of C yourself and let ponyc compile it for you as a [C shim](/c-ffi/c-shims.md). +Native code can reach your program two ways: you can link a library, or you can write a small piece of C yourself and let ponyc compile it for you as a [C shim](/c-ffi/c-shims.md). You can also go the other direction — [export Pony methods](/c-ffi/exporting.md) so that C code can call them. Note that calling C (or other low-level languages) is inherently dangerous. C code fundamentally has access to all memory in the process and can change any of it, either deliberately or due to bugs. This is one of the language's most useful, but also most dangerous, features. Calling well written, bug-free, C code will have no ill effects on your program. However, calling buggy or malicious C code or calling C incorrectly can cause your Pony program to go wrong, including corrupting data and crashing. Consequently, all of the Pony guarantees regarding not crashing, memory safety and concurrent correctness can be voided by calling FFI functions. diff --git a/mkdocs.yml b/mkdocs.yml index a1e4b032..efa825e4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -109,6 +109,7 @@ plugins: - c-ffi/calling-c.md - c-ffi/linking-c.md - c-ffi/c-shims.md + - c-ffi/exporting.md - c-ffi/callbacks.md Runtime Basics: - runtime-basics/index.md @@ -245,6 +246,7 @@ nav: - Calling C from Pony: "c-ffi/calling-c.md" - Linking to C Libraries: "c-ffi/linking-c.md" - C Shims: "c-ffi/c-shims.md" + - Exporting Pony Methods to C: "c-ffi/exporting.md" - Callbacks: "c-ffi/callbacks.md" - Runtime Basics: - Overview: "runtime-basics/index.md"