Skip to content

Commit 16d5fa2

Browse files
committed
Updated cross compile
1 parent ff1375c commit 16d5fa2

11 files changed

Lines changed: 1063 additions & 27 deletions

File tree

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
name: Cross-compile
2+
3+
# Verifies every v1.9 cross-compile target produces a binary of the right
4+
# architecture / ABI. Uses `zig cc` for the backend and skips gracefully
5+
# if zig isn't on the runner (currently shouldn't happen — ubuntu-latest
6+
# installs zig in the first step).
7+
#
8+
# Matrix is single-OS (ubuntu-latest) because the cross-compile is by
9+
# definition target-OS-agnostic: we ask zig cc to emit the right format.
10+
# Running on macOS or Windows would test the same thing more slowly.
11+
12+
on:
13+
push:
14+
branches: [main, master]
15+
pull_request:
16+
branches: [main, master]
17+
18+
env:
19+
CARGO_TERM_COLOR: always
20+
RUST_BACKTRACE: 1
21+
22+
jobs:
23+
cross-compile:
24+
name: Cross-compile (${{ matrix.triple }})
25+
runs-on: ubuntu-latest
26+
strategy:
27+
fail-fast: false
28+
matrix:
29+
triple:
30+
- aarch64-linux-musl
31+
- x86_64-linux-musl
32+
- aarch64-linux-gnu
33+
- x86_64-linux-gnu
34+
- aarch64-macos
35+
- x86_64-macos
36+
- wasm32-wasi
37+
- riscv64-linux-musl
38+
steps:
39+
- uses: actions/checkout@v4
40+
41+
- name: Install Rust
42+
uses: dtolnay/rust-toolchain@stable
43+
with:
44+
toolchain: "1.85"
45+
46+
- name: Cache cargo
47+
uses: actions/cache@v4
48+
with:
49+
path: |
50+
~/.cargo/bin/
51+
~/.cargo/registry/index/
52+
~/.cargo/registry/cache/
53+
~/.cargo/git/db/
54+
target/
55+
key: ${{ runner.os }}-cargo-xc-${{ hashFiles('**/Cargo.lock') }}
56+
57+
- name: Install zig (cross-compile backend)
58+
uses: mlugg/setup-zig@v2
59+
with:
60+
version: 0.13.0
61+
62+
- name: Build release fastc
63+
run: cargo build --release -p fastc
64+
65+
- name: Verify backend available
66+
run: ./target/release/fastc target check ${{ matrix.triple }}
67+
68+
- name: Cross-compile hello.fc
69+
run: |
70+
mkdir -p /tmp/xc/src
71+
cp examples/hello.fc /tmp/xc/src/main.fc
72+
cat > /tmp/xc/fastc.toml <<EOF
73+
[package]
74+
name = "hello_xc"
75+
version = "0.1.0"
76+
EOF
77+
cd /tmp/xc && $GITHUB_WORKSPACE/target/release/fastc build --target=${{ matrix.triple }}
78+
79+
- name: Inspect output
80+
run: |
81+
if [ -f /tmp/xc/build/main.wasm ]; then
82+
file /tmp/xc/build/main.wasm
83+
else
84+
file /tmp/xc/build/main
85+
fi
86+
87+
- name: Run cross-compile integration test for this triple
88+
run: cargo test -p fastc --test cross_compile --release

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ fastC is a modern C-like language for a world where most code is written by an A
2323
| Vendor-first deps (no central registry) | N/A | crates.io | Zon | modules | **** |
2424
| Sigstore / SLSA provenance ||||| **scheduled** |
2525
| Stripped binary (hello) | 33 KB | 341 KB | 50 KB | 2.4 MB | **53 KB** |
26+
| Cross-compile, no sysroot setup | depends on toolchain | rustup per-target dance | ✓ (`zig cc`, 50+) | ✓ (`GOOS`/`GOARCH`) | **✓ (8 presets via `zig cc`, any C cross-toolchain via `--cc-override`)** |
2627

2728
Each row has a paragraph of context at [docs.skelfresearch.com/fastc/why/rubric](https://docs.skelfresearch.com/fastc/why/rubric). The honest framing of which trade-offs fastC actually wins on (and which it loses) is in [docs/MANIFESTO.md](docs/MANIFESTO.md).
2829

@@ -83,6 +84,8 @@ The scripts and golden data are in [`benchmarks/cross-lang/`](benchmarks/cross-l
8384

8485
The [supply-chain side-by-side demo](examples/supply_chain_demo/) shows `cargo build` executing a malicious `build.rs` vs `fastc.toml` rejecting the same shape at parse time.
8586

87+
**Cross-compile, one flag.** `fastc build --target=<triple>` ships eight pre-wired presets via `zig cc` (aarch64/x86_64 × linux-musl/linux-gnu, aarch64/x86_64-macos, wasm32-wasi, riscv64-linux-musl). One `brew install zig` and `fastc build --target=aarch64-linux-musl` produces a statically-linked ARM Linux binary, or `--target=wasm32-wasi` a `.wasm` for sandboxed runtimes. `--cc-override=<path>` swaps in a proprietary toolchain when needed. fastC emits portable C11, so any C cross-compiler in the world targets fastC binaries — we just default to the best one. See [docs/cross-compile.md](docs/cross-compile.md) and `fastc target list` for the live matrix.
88+
8689
## Quick Start
8790

8891
```bash

crates/fastc/src/build.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -346,30 +346,50 @@ impl BuildContext {
346346

347347
/// Compile the generated C code with a C compiler
348348
///
349-
/// Returns the path to the executable
349+
/// `compiler_prefix_args` are inserted right after the compiler binary
350+
/// and before the source file. Cross-compile uses this for `zig cc
351+
/// --target=...` — `compiler="zig"`, `compiler_prefix_args=["cc",
352+
/// "--target=aarch64-linux-musl"]`. The `output_ext` lets WASI targets
353+
/// override the default empty extension with `.wasm`.
354+
///
355+
/// Returns the path to the executable.
350356
pub fn cc_compile(
351357
&self,
352358
c_file: &Path,
353359
compiler: &str,
360+
compiler_prefix_args: &[&str],
354361
cflags: &[&str],
355362
release: bool,
363+
output_ext: &str,
356364
) -> Result<PathBuf, BuildError> {
357365
let output_dir = c_file.parent().unwrap_or(Path::new("."));
358366
let base_name = c_file.file_stem().unwrap().to_string_lossy();
359367

360-
// Output executable name (add .exe on Windows)
361-
#[cfg(windows)]
362-
let exe_name = format!("{}.exe", base_name);
363-
#[cfg(not(windows))]
364-
let exe_name = base_name.to_string();
368+
// Output executable name (add .exe on Windows when no explicit ext).
369+
let exe_name = if !output_ext.is_empty() {
370+
format!("{}{}", base_name, output_ext)
371+
} else {
372+
#[cfg(windows)]
373+
{
374+
format!("{}.exe", base_name)
375+
}
376+
#[cfg(not(windows))]
377+
{
378+
base_name.to_string()
379+
}
380+
};
365381

366382
let executable = output_dir.join(&exe_name);
367383

368384
eprintln!("Compiling C code with {}...", compiler);
369385

370-
// Build compiler arguments
371-
let mut args: Vec<&str> =
372-
vec![c_file.to_str().unwrap(), "-o", executable.to_str().unwrap()];
386+
// Build compiler arguments. `compiler_prefix_args` first (e.g. zig's
387+
// `cc` subcommand, --target flag), then the source file.
388+
let mut args: Vec<&str> = Vec::new();
389+
args.extend(compiler_prefix_args.iter().copied());
390+
args.push(c_file.to_str().unwrap());
391+
args.push("-o");
392+
args.push(executable.to_str().unwrap());
373393

374394
// Add runtime include path
375395
// Try to find the runtime directory relative to the executable or use env var

crates/fastc/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub mod parser;
2323
pub mod prelude;
2424
pub mod resolve;
2525
pub mod scaffold;
26+
pub mod targets;
2627
pub mod timing;
2728
pub mod typecheck;
2829

0 commit comments

Comments
 (0)