Skip to content

Add extern fun support for calling external C functions#70

Open
ihasq wants to merge 1 commit into
vercel-labs:mainfrom
ihasq:main
Open

Add extern fun support for calling external C functions#70
ihasq wants to merge 1 commit into
vercel-labs:mainfrom
ihasq:main

Conversation

@ihasq
Copy link
Copy Markdown

@ihasq ihasq commented May 18, 2026

Add extern fun support for calling external C functions

Summary

Implements extern fun syntax to enable Zero programs to call external C functions. This allows Zero to interoperate with C libraries through proper ELF linking without requiring compiler builtin implementations.

extern fun abs(n: i32) -> i32

pub fun main(world: World) -> Void raises {
    let result = abs(0 - 42)
    check world.out.write("result: ")
}

Motivation

Zero currently lacks a mechanism for users to call external C functions. While std.* modules provide core functionality through compiler builtins, there is no way to link against arbitrary C libraries (libc, libm, custom libraries, etc.) from Zero code. This PR addresses that gap by implementing extern function declarations with proper ELF undefined symbol generation and PLT relocations.

Use cases enabled:

  • Calling libc functions (abs, strlen, memcpy, etc.)
  • Integrating custom C libraries into Zero programs
  • Incremental migration from C codebases (call existing C code from Zero)
  • Foundation for future GPU/graphics library interop

This capability significantly expands Zero's ecosystem reach. With extern fun, Zero programs can now interface with established C libraries such as webgpu.h (GPU compute and graphics), libcurl (HTTP clients), SQLite (embedded databases), libpng/libjpeg (image processing), and countless domain-specific libraries. For AI agent tooling—Zero's core target—this opens access to ML inference runtimes (ONNX Runtime, TensorFlow Lite C API), vector databases, and specialized hardware accelerators. Rather than reimplementing these battle-tested libraries as compiler builtins, extern fun enables Zero to leverage decades of C ecosystem development while maintaining Zero's safety and expressiveness at the application layer.

Changes

1. Parser (native/zero-c/src/parser.c)

Added extern function syntax:

  • extern fun name(...) -> Type (no function body)
  • Optional extern c fun variant for consistency with existing extern c "header.h"
  • Sets Function.extern_c = true flag in AST

Key modifications:

  • Lines 725-728: Detect extern keyword with optional c qualifier
  • Lines 752-759: Skip body parsing for extern functions
  • Lines 971-973: Distinguish extern c "header.h" (header import) from extern c fun (function declaration)

2. Checker (native/zero-c/src/checker.c)

Added ABI validation:

  • New diagnostic code 3031 for C ABI violations
  • Validates extern functions use only C-compatible types (i32, Void, no shapes/interfaces)
  • Validates extern functions do not use raises (C ABI incompatible)
  • Skips function body checks for extern functions

Key modifications:

  • Lines 7192-7204: validate_extern_c_function() enforces ABI constraints
  • Line 7086: Skip return value validation for extern functions (no body)
  • Line 7592: Integration into checker main loop

3. IR Layer (native/zero-c/src/ir.c)

Added extern call representation:

  • New field IrValue.extern_symbol_name for extern function calls
  • extern_symbol_name != NULL indicates external linkage
  • extern_symbol_name == NULL indicates internal call (uses callee_index)

Key modifications:

  • Lines 2142-2217: Generate extern calls with symbol name instead of function index
  • Lines 3024-3030: Exclude extern functions from direct_functions (no IR lowering needed)
  • Line 2858: Skip body lowering for extern functions
  • Line 623: Free extern_symbol_name in cleanup

4. ELF64 Emitter (native/zero-c/src/emit_elf64.c)

Added undefined symbol generation and PLT relocations:

  • Track extern call sites during code emission
  • Generate undefined symbols (shndx=0) for extern functions
  • Generate R_X86_64_PLT32 relocations with addend -4
  • Maintain proper symbol table ordering (locals → globals → externs)
  • Add .note.GNU-stack section (security best practice)

Key modifications:

  • Lines 254-267: ExternCallSite structure and tracking arrays
  • Lines 765-781: Record extern call sites during IR_VALUE_CALL emission
  • Lines 2407-2447: Symbol table generation with proper ordering
  • Lines 2456-2474: R_X86_64_PLT32 relocation generation
  • Lines 2507-2570: .note.GNU-stack section header

ELF structure changes:

  • Symbol table now includes undefined symbols for extern functions
  • .rela.text section contains PLT32 relocations for extern calls
  • .note.GNU-stack section prevents executable stack warnings

5. AST Definition (native/zero-c/include/zero.h)

Added extern tracking fields:

  • Line 206: Function.extern_c flag
  • Line 446: IrValue.extern_symbol_name pointer

Usage

Basic usage (libc functions)

extern fun abs(n: i32) -> i32
extern fun strlen(s: Ptr[u8]) -> i32

pub fun main(world: World) -> Void raises {
    let neg = 0 - 42
    let result = abs(neg)
    // result == 42
}

Compile and link:

$ bin/zero build --emit obj program.0 --out program.o
$ cc program.o -o program
$ ./program

Custom C libraries

C helper (helpers.c):

int add_values(int a, int b) { return a + b; }

Zero code:

extern fun add_values(a: i32, b: i32) -> i32

pub fun main(world: World) -> Void raises {
    let sum = add_values(40, 2)
    // sum == 42
}

Build:

$ cc -c helpers.c -o helpers.o
$ bin/zero build --emit obj program.0 --out program.o
$ cc program.o helpers.o -o program

Testing

Conformance tests

Four conformance test cases added in conformance/native/pass/:

  1. extern-fun-libc-abs.0: libc abs() function call (baseline)
  2. extern-fun-custom.0: Custom C functions with linking
  3. extern-fun-six-args.0: Six-argument ABI boundary test (System V AMD64)
  4. extern-fun-void-return.0: Void return type validation

Integration test script

tests/extern-fun-test.sh performs full compile-link-execute validation:

$ ./tests/extern-fun-test.sh
=== Extern Function Call Tests ===
Test 1: libc abs() function
  ✓ PASS
Test 2: Custom C functions
  ✓ PASS
Test 3: Six arguments (System V AMD64 ABI)
  ✓ PASS
Test 4: Void return type
  ✓ PASS
=== All extern function tests passed ===

ELF validation

Generated object files verified with objdump:

  • Undefined symbols: 0000000000000000 *UND* abs
  • PLT relocations: R_X86_64_PLT32 abs-0x4
  • Symbol ordering: local → global → extern (ELF spec compliant)

Limitations

1. C ABI type restrictions (enforced by checker diagnostic 3031)

Only C-compatible types allowed:

  • ✅ Primitive integers (i32, u8, etc.)
  • ✅ Void
  • ✅ Ptr[T] (raw pointers)
  • ❌ Zero shapes (no C equivalent)
  • ❌ Zero interfaces (no C equivalent)
  • ❌ raises (C functions don't raise)

2. Manual linking required

This PR does NOT include automatic linking driver. Users must:

  1. Compile Zero code: bin/zero build --emit obj file.0 --out file.o
  2. Link manually: cc file.o [other.o...] -o executable

Automatic linking (detecting required libraries, invoking linker) is deferred to future work.

3. x86_64 Linux only

Implementation uses:

  • System V AMD64 calling convention (rdi, rsi, rdx, rcx, r8, r9 for first 6 args)
  • ELF64 object format
  • R_X86_64_PLT32 relocations

Other platforms (aarch64, Windows, macOS) not supported in this PR.

Future work

  • Auto-linking driver: Detect C library dependencies, invoke cc automatically
  • Platform support: aarch64 (R_AARCH64_CALL26), Windows (COFF), macOS (Mach-O)
  • Type mapping expansion: Support more complex C types (structs via Ptr, function pointers)
  • Build system integration: Make conformance test runner handle C compilation
  • varargs support: extern fun printf(format: Ptr[u8], ...) -> i32 (requires parser + IR changes)

Conformance tests: Located in conformance/native/pass/extern-fun-*.0. Run ./tests/extern-fun-test.sh to verify full linking and execution.

Implements extern fun syntax to enable Zero programs to call external C functions through ELF linking. This allows interop with C libraries (libc, webgpu.h, SQLite, etc.) without requiring compiler builtin implementations.

Key changes:
- Parser: recognize extern [c] fun syntax, skip body parsing
- Checker: add diagnostic 3031 for C ABI validation
- IR: add extern_symbol_name field to distinguish extern calls
- ELF emitter: generate undefined symbols and R_X86_64_PLT32 relocations
- Tests: 4 conformance tests + integration test script

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 18, 2026

@ihasq is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

char **extern_symbol_names = NULL;
uint32_t *extern_symbol_strtab_offsets = NULL;
if (ctx.extern_call_site_len > 0) {
extern_symbol_names = calloc(ctx.extern_call_site_len, sizeof(char *));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing NULL check on calloc for extern_symbol_names and extern_symbol_strtab_offsets causes NULL pointer dereference if allocation fails.

Fix on Vercel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant