Skip to content

Commit 0e8a19a

Browse files
hyperpolymathclaude
andcommitted
feat(codegen): rewrite Chapel/Zig/header generators with real distribution logic
Phase 1 of chapeliser implementation — the codegen now produces: - Chapel: compilable .chpl with real coforall distribution, 5 partition strategies (per-item, chunk, adaptive/DynamicIters, spatial/BlockDist, keyed), 5 gather strategies (merge, reduce, tree-reduce, stream, first), buffer-based cross-locale data transfer, retry logic, checkpoint support - Zig FFI: buffer-based C-ABI bridge (not pointer-based), delegates to user's workload-specific functions, 12 Chapel-facing exports - C header: full FFI contract with lifecycle (init/shutdown), data I/O (load_item/store_result), processing, reduction, match, key hash, and checkpoint functions - Build script: environment variable overrides, Chapel config passthrough Split codegen/mod.rs into 4 submodules (chapel.rs, zig.rs, header.rs, build_script.rs). Tests expanded from 6 to 9 — all passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 78c47a5 commit 0e8a19a

5 files changed

Lines changed: 685 additions & 437 deletions

File tree

src/codegen/build_script.rs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
//
4+
// Build script generator for Chapeliser.
5+
//
6+
// Generates a bash script that compiles all generated artifacts:
7+
// 1. Zig FFI bridge → .o object file
8+
// 2. User's application → .o or .a (user-specific, documented in comments)
9+
// 3. Chapel wrapper + FFI object + user code → distributed binary
10+
//
11+
// The build script also supports environment variable overrides for
12+
// cross-compilation and cluster-specific Chapel configurations.
13+
14+
use anyhow::{Context, Result};
15+
use std::fmt::Write as FmtWrite;
16+
use std::fs;
17+
use std::path::Path;
18+
19+
use crate::manifest::Manifest;
20+
21+
/// Generate the build script from a manifest.
22+
/// Writes to `output_dir/build.sh` (executable).
23+
pub fn generate(manifest: &Manifest, output_dir: &Path) -> Result<()> {
24+
let name = &manifest.workload.name;
25+
let safe_name = name.replace('-', "_");
26+
27+
let mut src = String::with_capacity(2048);
28+
29+
writeln!(src, "#!/usr/bin/env bash")?;
30+
writeln!(src, "# SPDX-License-Identifier: PMPL-1.0-or-later")?;
31+
writeln!(src, "# Auto-generated by Chapeliser — build script for workload: {name}")?;
32+
writeln!(src, "# Regenerate with: chapeliser generate")?;
33+
writeln!(src)?;
34+
writeln!(src, "set -euo pipefail")?;
35+
writeln!(src)?;
36+
37+
// Environment variable overrides
38+
writeln!(src, "# Override these environment variables for your setup:")?;
39+
writeln!(src, "# ZIG — path to zig compiler (default: zig)")?;
40+
writeln!(src, "# CHPL — path to Chapel compiler (default: chpl)")?;
41+
writeln!(src, "# USER_LIB — path to user's compiled library (.a or .o)")?;
42+
writeln!(src, "# CHPL_FLAGS — extra Chapel compiler flags")?;
43+
writeln!(src, "ZIG=${{ZIG:-zig}}")?;
44+
writeln!(src, "CHPL=${{CHPL:-chpl}}")?;
45+
writeln!(src, "USER_LIB=${{USER_LIB:-}}")?;
46+
writeln!(src, "CHPL_FLAGS=${{CHPL_FLAGS:-}}")?;
47+
writeln!(src)?;
48+
49+
writeln!(src, "echo \"=== Chapeliser Build: {name} ===\"")?;
50+
writeln!(src)?;
51+
52+
// Step 1: Compile Zig FFI bridge
53+
writeln!(src, "# Step 1: Compile the Zig FFI bridge to a C-ABI object file")?;
54+
writeln!(src, "echo \"[1/3] Compiling Zig FFI bridge...\"")?;
55+
writeln!(
56+
src,
57+
"$ZIG build-obj -O ReleaseFast zig/{safe_name}_ffi.zig -femit-bin={safe_name}_ffi.o"
58+
)?;
59+
writeln!(src)?;
60+
61+
// Step 2: User code
62+
writeln!(src, "# Step 2: Link user's application code")?;
63+
writeln!(src, "echo \"[2/3] Checking user code...\"")?;
64+
writeln!(src, "if [ -z \"$USER_LIB\" ]; then")?;
65+
writeln!(src, " echo \" WARNING: USER_LIB not set. You must provide your compiled application.\"")?;
66+
writeln!(src, " echo \" For Rust: cargo build --release && export USER_LIB=target/release/lib{safe_name}.a\"")?;
67+
writeln!(src, " echo \" For C: gcc -c -O2 your_code.c -o user_code.o && export USER_LIB=user_code.o\"")?;
68+
writeln!(src, " echo \" For Zig: zig build-obj -O ReleaseFast your_code.zig && export USER_LIB=your_code.o\"")?;
69+
writeln!(src, " exit 1")?;
70+
writeln!(src, "fi")?;
71+
writeln!(src)?;
72+
writeln!(src, "if [ ! -f \"$USER_LIB\" ]; then")?;
73+
writeln!(src, " echo \" ERROR: USER_LIB=$USER_LIB not found\"")?;
74+
writeln!(src, " exit 1")?;
75+
writeln!(src, "fi")?;
76+
writeln!(src, "echo \" Using: $USER_LIB\"")?;
77+
writeln!(src)?;
78+
79+
// Step 3: Chapel compilation
80+
writeln!(src, "# Step 3: Compile the Chapel distributed wrapper")?;
81+
writeln!(src, "echo \"[3/3] Compiling Chapel wrapper...\"")?;
82+
83+
// Build Chapel compile command with appropriate flags
84+
if manifest.resilience.checkpoint {
85+
writeln!(src, "# Checkpointing enabled — Chapel needs IO support")?;
86+
}
87+
88+
let mut chpl_cmd = format!(
89+
"$CHPL --fast -I include/ chapel/{safe_name}_distributed.chpl {safe_name}_ffi.o \"$USER_LIB\""
90+
);
91+
92+
// Add Chapel config from manifest
93+
if let Some(ref comm) = manifest.chapel.comm_layer {
94+
writeln!(src, "export CHPL_COMM={comm}")?;
95+
}
96+
97+
for flag in &manifest.chapel.compiler_flags {
98+
chpl_cmd.push(' ');
99+
chpl_cmd.push_str(flag);
100+
}
101+
102+
if manifest.chapel.gpu_enabled {
103+
chpl_cmd.push_str(" --set gpuEnabled=true");
104+
}
105+
106+
chpl_cmd.push_str(" $CHPL_FLAGS");
107+
chpl_cmd.push_str(&format!(" -o {safe_name}_distributed"));
108+
109+
writeln!(src, "{chpl_cmd}")?;
110+
writeln!(src)?;
111+
112+
// Success message
113+
writeln!(src, "echo \"=== Build complete: ./{safe_name}_distributed ===\"")?;
114+
writeln!(src, "echo \"Run with: ./{safe_name}_distributed -nl <num_locales>\"")?;
115+
writeln!(src, "echo \" --totalItems=N override item count\"")?;
116+
writeln!(src, "echo \" --maxItemBytes=N override max buffer size\"")?;
117+
writeln!(src, "echo \" --grainSize=N override chunk grain size\"")?;
118+
writeln!(src, "echo \" --maxRetries=N override retry limit\"")?;
119+
writeln!(src, "echo \" --enableCheckpoint enable checkpointing\"")?;
120+
121+
let out_path = output_dir.join("build.sh");
122+
fs::write(&out_path, &src)
123+
.with_context(|| format!("Failed to write build script: {}", out_path.display()))?;
124+
125+
// Make executable
126+
#[cfg(unix)]
127+
{
128+
use std::os::unix::fs::PermissionsExt;
129+
let mut perms = fs::metadata(&out_path)?.permissions();
130+
perms.set_mode(0o755);
131+
fs::set_permissions(&out_path, perms)?;
132+
}
133+
134+
println!(" Build script: {}", out_path.display());
135+
Ok(())
136+
}

src/codegen/header.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
//
4+
// C header generator for Chapeliser.
5+
//
6+
// Generates the C header that declares all functions the user must implement.
7+
// This is the contract between the user's application code and the Chapeliser
8+
// runtime (Chapel wrapper + Zig FFI bridge).
9+
10+
use anyhow::{Context, Result};
11+
use std::fmt::Write as FmtWrite;
12+
use std::fs;
13+
use std::path::Path;
14+
15+
use crate::manifest::Manifest;
16+
17+
/// Generate the C header from a manifest.
18+
/// Writes to `output_dir/include/<name>_chapeliser.h`.
19+
pub fn generate(manifest: &Manifest, output_dir: &Path) -> Result<()> {
20+
let name = &manifest.workload.name;
21+
let safe_name = name.replace('-', "_");
22+
let upper = safe_name.to_uppercase();
23+
24+
let mut src = String::with_capacity(4096);
25+
26+
writeln!(src, "/* SPDX-License-Identifier: PMPL-1.0-or-later */")?;
27+
writeln!(src, "/* Auto-generated by Chapeliser — do not edit manually. */")?;
28+
writeln!(src, "/* C header for workload: {name} */")?;
29+
writeln!(src, "/* Regenerate with: chapeliser generate */")?;
30+
writeln!(src)?;
31+
writeln!(src, "#ifndef CHAPELISER_{upper}_H")?;
32+
writeln!(src, "#define CHAPELISER_{upper}_H")?;
33+
writeln!(src)?;
34+
writeln!(src, "#include <stddef.h>")?;
35+
writeln!(src, "#include <stdint.h>")?;
36+
writeln!(src)?;
37+
writeln!(src, "#ifdef __cplusplus")?;
38+
writeln!(src, "extern \"C\" {{")?;
39+
writeln!(src, "#endif")?;
40+
writeln!(src)?;
41+
42+
// Documentation block
43+
writeln!(src, "/*")?;
44+
writeln!(src, " * Chapeliser FFI contract for workload: {name}")?;
45+
writeln!(src, " *")?;
46+
writeln!(src, " * Implement these functions with C linkage in your application.")?;
47+
writeln!(src, " * Chapeliser's generated Chapel + Zig code calls them at runtime.")?;
48+
writeln!(src, " *")?;
49+
writeln!(src, " * Return values: 0 = success, non-zero = error code.")?;
50+
writeln!(src, " *")?;
51+
writeln!(src, " * For Rust: #[no_mangle] pub extern \"C\" fn {safe_name}_init() -> i32 {{ ... }}")?;
52+
writeln!(src, " * For Zig: export fn {safe_name}_init() callconv(.C) c_int {{ ... }}")?;
53+
writeln!(src, " * For C: int {safe_name}_init(void) {{ ... }}")?;
54+
writeln!(src, " */")?;
55+
writeln!(src)?;
56+
57+
// --- Lifecycle ---
58+
writeln!(src, "/* ---- Lifecycle ---- */")?;
59+
writeln!(src)?;
60+
writeln!(src, "/* Initialise the workload. Called once on locale 0 before processing. */")?;
61+
writeln!(src, "int {safe_name}_init(void);")?;
62+
writeln!(src)?;
63+
writeln!(src, "/* Shut down the workload. Called once on locale 0 after all results stored. */")?;
64+
writeln!(src, "int {safe_name}_shutdown(void);")?;
65+
writeln!(src)?;
66+
67+
// --- Data I/O ---
68+
writeln!(src, "/* ---- Data I/O ---- */")?;
69+
writeln!(src)?;
70+
writeln!(src, "/* Return the total number of input items. Called on locale 0. */")?;
71+
writeln!(src, "int {safe_name}_get_total_items(void);")?;
72+
writeln!(src)?;
73+
writeln!(src, "/* Serialise input item `idx` into `buf`. Set `*len` to bytes written. */")?;
74+
writeln!(src, "/* `buf` has capacity {max_bytes} bytes (from manifest max-item-bytes). */",
75+
max_bytes = manifest.data.max_item_bytes.unwrap_or(1_048_576))?;
76+
writeln!(src, "int {safe_name}_load_item(int idx, uint8_t* buf, size_t* len);")?;
77+
writeln!(src)?;
78+
writeln!(src, "/* Receive a processed result. `buf` contains `len` serialised bytes. */")?;
79+
writeln!(src, "int {safe_name}_store_result(int idx, const uint8_t* buf, size_t len);")?;
80+
writeln!(src)?;
81+
82+
// --- Processing ---
83+
writeln!(src, "/* ---- Processing ---- */")?;
84+
writeln!(src)?;
85+
writeln!(src, "/* Process a single serialised item. Read from in_buf/in_len, */")?;
86+
writeln!(src, "/* write result to out_buf, set *out_len. Called on any locale. */")?;
87+
writeln!(src, "int {safe_name}_process_item(")?;
88+
writeln!(src, " const uint8_t* in_buf, size_t in_len,")?;
89+
writeln!(src, " uint8_t* out_buf, size_t* out_len")?;
90+
writeln!(src, ");")?;
91+
writeln!(src)?;
92+
writeln!(src, "/* Process a chunk of items (for chunk partition strategy). */")?;
93+
writeln!(src, "int {safe_name}_process_chunk(")?;
94+
writeln!(src, " const uint8_t* items_buf, int item_count,")?;
95+
writeln!(src, " const int* item_offsets, const int* item_sizes,")?;
96+
writeln!(src, " uint8_t* out_buf, size_t* out_len")?;
97+
writeln!(src, ");")?;
98+
writeln!(src)?;
99+
100+
// --- Reduction ---
101+
writeln!(src, "/* ---- Reduction (for reduce/tree-reduce gather) ---- */")?;
102+
writeln!(src)?;
103+
writeln!(src, "/* Combine two results into one. */")?;
104+
writeln!(src, "int {safe_name}_reduce(")?;
105+
writeln!(src, " const uint8_t* a_buf, size_t a_len,")?;
106+
writeln!(src, " const uint8_t* b_buf, size_t b_len,")?;
107+
writeln!(src, " uint8_t* out_buf, size_t* out_len")?;
108+
writeln!(src, ");")?;
109+
writeln!(src)?;
110+
111+
// --- Match predicate ---
112+
writeln!(src, "/* ---- Match predicate (for 'first' gather) ---- */")?;
113+
writeln!(src)?;
114+
writeln!(src, "/* Return 1 if result satisfies the search criterion, 0 otherwise. */")?;
115+
writeln!(src, "int {safe_name}_is_match(const uint8_t* buf, size_t len);")?;
116+
writeln!(src)?;
117+
118+
// --- Key hash ---
119+
writeln!(src, "/* ---- Key hash (for keyed partition) ---- */")?;
120+
writeln!(src)?;
121+
writeln!(src, "/* Return a hash of the item's key for distribution routing. */")?;
122+
writeln!(src, "unsigned int {safe_name}_key_hash(const uint8_t* buf, size_t len);")?;
123+
writeln!(src)?;
124+
125+
// --- Checkpoint ---
126+
writeln!(src, "/* ---- Checkpoint (optional) ---- */")?;
127+
writeln!(src)?;
128+
writeln!(src, "/* Save checkpoint data. Return -1 if not implemented. */")?;
129+
writeln!(src, "int {safe_name}_checkpoint_save(const uint8_t* buf, size_t len, const char* tag);")?;
130+
writeln!(src)?;
131+
writeln!(src, "/* Load checkpoint data. Return -1 if not implemented. */")?;
132+
writeln!(src, "int {safe_name}_checkpoint_load(uint8_t* buf, size_t* len, const char* tag);")?;
133+
writeln!(src)?;
134+
135+
// Close
136+
writeln!(src, "#ifdef __cplusplus")?;
137+
writeln!(src, "}}")?;
138+
writeln!(src, "#endif")?;
139+
writeln!(src)?;
140+
writeln!(src, "#endif /* CHAPELISER_{upper}_H */")?;
141+
142+
let out_path = output_dir.join(format!("{}_chapeliser.h", safe_name));
143+
fs::write(&out_path, &src)
144+
.with_context(|| format!("Failed to write C header: {}", out_path.display()))?;
145+
println!(" C header: {}", out_path.display());
146+
147+
Ok(())
148+
}

0 commit comments

Comments
 (0)