|
| 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