← board

Cross-frontend interop contract — umbrella

Motivation

PXX already has 2 frontends beyond Pascal (C, Nil-Python) with a 3rd landing (Rust, actively in working/) and a 4th scoped (Zig, [[feature-zig-frontend]]). Interop today is one-directional and ad hoc per frontend: Pascal/Nil-Python import C headers and call into shared libraries via the "magic link" model (docs/targets/cross-languages.md, docs/targets/c-frontend.md). There is no first-class way to compile a Pascal unit, a C file, and (soon) a Rust module as peer translation units in one build with calls in both directions — each pxx invocation picks one frontend by main-file suffix.

With 2 more frontends about to exist, each is at risk of bolting on its own bespoke interop shim (Rust's own C-call convention, Zig's own, diverging from what C/Nil-Python already do) unless the contract is written down once, now, before the second and third frontend duplicate solved problems.

What already exists (foundation, confirmed reusable)

The actual gap

  1. No symbol/name-mangling convention document. Each frontend currently decides its own exported-symbol naming ad hoc. Nothing stops Pascal's TFoo.Bar mangling scheme from colliding with, or being unreachable from, a future Rust impl Foo { fn bar() } unless a shared rule exists.
  2. No "peer translation unit" build model. Cross-language calls today require going through a C header as the interface language, even when both sides are PXX-compiled (not calling into libc or a real C library) — e.g. a Pascal program directly linking a Rust module's exported function without hand-writing a C shim header for it.
  3. No written-down type-mapping table across frontends. What does a Pascal AnsiString look like from C's side (today: it doesn't cross uninterpreted, headers only expose PChar/plain pointers)? What will a Rust &str/Vec<T> look like from Pascal? Undefined until asked, per frontend, per feature.
  4. Whether "mixed build" means one invocation with multiple source files (each auto-dispatched by extension) vs. --emit-obj per language + a separate link step (cheaper, closer to the existing magic-link model, probably the pragmatic v1 answer) is undecided.

Explicit non-goals (v1 scope cut, following the C/Rust/Zig precedent)

Suggested sub-tickets (split when scoping firms up — don't flood the board)

  1. interop-symbol-convention — write down (and where needed, adjust) the exported-symbol / name-mangling rule every frontend must follow so any two frontends' compiled objects can link and call each other without a C-header detour for non-libc, PXX-to-PXX calls.
  2. interop-type-mapping-table — a living reference doc (candidate: docs/targets/cross-languages.md grows this) enumerating what each frontend's primitives/pointers/structs look like from every other frontend's side. Grows as each new frontend lands, not all at once.
  3. interop-peer-unit-build — decide + implement the actual build path (multi-source-per-invocation vs. --emit-obj + link); wire tools//make support and a worked example (e.g. a Pascal program calling a Rust-compiled function directly, once Rust skeleton exists).
  4. interop-docs-rewrite — once 1-3 land, rewrite docs/targets/cross-languages.md from "experimental frontends exist" to an actual how-to for calling across languages, with the type-mapping table linked.

Acceptance (for the umbrella; each sub-ticket has its own)

A written interop contract exists that Rust and Zig frontends can each be checked against as they land (not retrofitted after the fact); at least one worked cross-language example (e.g. Pascal ↔ Rust, once Rust reaches a callable subset) builds and runs via the chosen peer-unit build path; docs/targets/ cross-languages.md reflects the real mechanism, not just C-header import.

Log