Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/provable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Regenerate golden artifacts and diff against committed tree
run: |
cargo run --quiet -- generate -m examples/golden/echo.toml -o /tmp/golden
# retry: fresh runners occasionally flake fetching crates from crates.io
for i in 1 2 3; do
cargo run --quiet -- generate -m examples/golden/echo.toml -o /tmp/golden && break
echo "attempt $i failed; retrying after backoff…"; sleep $((i * 5))
done
diff -ru examples/golden/generated /tmp/golden

chapel-golden:
Expand All @@ -78,6 +82,7 @@ jobs:
run: |
chpl --version
chpl --main-module echo_Distributed \
-I examples/golden/generated/include \
examples/golden/generated/chapel/echo_distributed.chpl \
examples/golden/ffi_stub.c \
-o /tmp/echo_distributed
Expand Down
3 changes: 3 additions & 0 deletions examples/golden/ffi_stub.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
#include <stddef.h>
#include <string.h>

/* Prototypes for the c_* ABI (also `require`d by the generated Chapel). */
#include "echo_abi.h"

#define ECHO_ITEMS 8
#define ECHO_PAYLOAD 4

Expand Down
1 change: 1 addition & 0 deletions examples/golden/generated/chapel/echo_distributed.chpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module echo_Distributed {
use List;
use Time;
use IO;
require "echo_abi.h";

// ------------------------------------------------------------------
// Runtime configuration (override with --name=value)
Expand Down
31 changes: 31 additions & 0 deletions examples/golden/generated/include/echo_abi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: MPL-2.0 */
/* Auto-generated by Chapeliser — do not edit manually. */
/* C-ABI prototypes for the c_* bridge the generated Chapel calls. */
#ifndef CHAPELISER_ECHO_ABI_H
#define CHAPELISER_ECHO_ABI_H

#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

int c_init(void);
int c_shutdown(void);
int c_get_total_items(void);
int c_load_item(int idx, uint8_t* buf, size_t* len);
int c_store_result(int idx, uint8_t* buf, size_t len);
int c_process_item(uint8_t* in_buf, size_t in_len, uint8_t* out_buf, size_t* out_len);
int c_process_chunk(uint8_t* items_buf, int item_count, int* item_offsets, int* item_sizes, uint8_t* out_buf, size_t* out_len);
int c_reduce(uint8_t* a_buf, size_t a_len, uint8_t* b_buf, size_t b_len, uint8_t* out_buf, size_t* out_len);
int c_is_match(uint8_t* buf, size_t len);
unsigned int c_key_hash(uint8_t* buf, size_t len);
int c_checkpoint_save(uint8_t* buf, size_t len, const char* tag);
int c_checkpoint_load(uint8_t* buf, size_t* len, const char* tag);

#ifdef __cplusplus
}
#endif

#endif /* CHAPELISER_ECHO_ABI_H */
5 changes: 5 additions & 0 deletions src/codegen/chapel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ fn write_imports(src: &mut String, manifest: &Manifest) -> Result<()> {
writeln!(src, " use AtomicObjects;")?;
}

// C prototypes for the c_* ABI so chpl can resolve the extern procs —
// needed for calls inside 'on'/'coforall' (potentially remote) contexts.
let safe_name = manifest.workload.name.replace('-', "_");
writeln!(src, " require \"{safe_name}_abi.h\";")?;

writeln!(src)?;
Ok(())
}
Expand Down
67 changes: 67 additions & 0 deletions src/codegen/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,5 +219,72 @@ pub fn generate(manifest: &Manifest, output_dir: &Path) -> Result<()> {
.with_context(|| format!("Failed to write C header: {}", out_path.display()))?;
println!(" C header: {}", out_path.display());

// Also emit the c_* ABI header that the generated Chapel `require`s, so chpl
// can resolve the extern procs (needed for remote 'on'/'coforall' calls).
// These symbols are provided by the Zig FFI bridge at link time.
write_abi_header(output_dir, &safe_name, &upper)?;

Ok(())
}

/// Emit `<name>_abi.h` — C prototypes for the `c_*` bridge functions that the
/// generated Chapel calls via `extern proc`. The Chapel wrapper `require`s this
/// header; the Zig bridge (or, in tests, a stub) provides the definitions.
fn write_abi_header(output_dir: &Path, safe_name: &str, upper: &str) -> Result<()> {
let mut h = String::with_capacity(2048);
writeln!(h, "/* SPDX-License-Identifier: MPL-2.0 */")?;
writeln!(h, "/* Auto-generated by Chapeliser — do not edit manually. */")?;
writeln!(
h,
"/* C-ABI prototypes for the c_* bridge the generated Chapel calls. */"
)?;
writeln!(h, "#ifndef CHAPELISER_{upper}_ABI_H")?;
writeln!(h, "#define CHAPELISER_{upper}_ABI_H")?;
writeln!(h)?;
writeln!(h, "#include <stddef.h>")?;
writeln!(h, "#include <stdint.h>")?;
writeln!(h)?;
writeln!(h, "#ifdef __cplusplus")?;
writeln!(h, "extern \"C\" {{")?;
writeln!(h, "#endif")?;
writeln!(h)?;
writeln!(h, "int c_init(void);")?;
writeln!(h, "int c_shutdown(void);")?;
writeln!(h, "int c_get_total_items(void);")?;
writeln!(h, "int c_load_item(int idx, uint8_t* buf, size_t* len);")?;
writeln!(h, "int c_store_result(int idx, uint8_t* buf, size_t len);")?;
writeln!(
h,
"int c_process_item(uint8_t* in_buf, size_t in_len, uint8_t* out_buf, size_t* out_len);"
)?;
writeln!(
h,
"int c_process_chunk(uint8_t* items_buf, int item_count, int* item_offsets, int* item_sizes, uint8_t* out_buf, size_t* out_len);"
)?;
writeln!(
h,
"int c_reduce(uint8_t* a_buf, size_t a_len, uint8_t* b_buf, size_t b_len, uint8_t* out_buf, size_t* out_len);"
)?;
writeln!(h, "int c_is_match(uint8_t* buf, size_t len);")?;
writeln!(h, "unsigned int c_key_hash(uint8_t* buf, size_t len);")?;
writeln!(
h,
"int c_checkpoint_save(uint8_t* buf, size_t len, const char* tag);"
)?;
writeln!(
h,
"int c_checkpoint_load(uint8_t* buf, size_t* len, const char* tag);"
)?;
writeln!(h)?;
writeln!(h, "#ifdef __cplusplus")?;
writeln!(h, "}}")?;
writeln!(h, "#endif")?;
writeln!(h)?;
writeln!(h, "#endif /* CHAPELISER_{upper}_ABI_H */")?;

let out_path = output_dir.join(format!("{}_abi.h", safe_name));
fs::write(&out_path, &h)
.with_context(|| format!("Failed to write ABI header: {}", out_path.display()))?;
println!(" ABI header: {}", out_path.display());
Ok(())
}
9 changes: 8 additions & 1 deletion src/interface/abi/Chapeliser/ABI/Foreign.idr
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ module Chapeliser.ABI.Foreign

import Chapeliser.ABI.Types
import Chapeliser.ABI.Layout
import Data.So
import Data.Vect

%default total

Expand Down Expand Up @@ -186,6 +188,11 @@ public export
data PipelineCorrect : WorkloadConfig -> Type where
MkPipelineCorrect :
{cfg : WorkloadConfig} ->
(partition : ValidPartition (MkPartition (perItemSlices cfg.totalItems cfg.numLocales))) ->
-- The partition is taken abstractly (any Partition of totalItems across
-- numLocales — e.g. one built by perItemSlices) so the *type* carries no
-- construction-time `So (numLocales > 0)` obligation; validity is the
-- witness that matters.
{p : Partition cfg.totalItems cfg.numLocales} ->
(partition : ValidPartition p) ->
(gather : GatherConservation (MkGatherInput (replicate cfg.numLocales (cfg.totalItems `div` cfg.numLocales))) cfg.totalItems) ->
PipelineCorrect cfg
25 changes: 10 additions & 15 deletions src/interface/abi/Chapeliser/ABI/Layout.idr
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ paddingFor : (offset : Nat) -> (alignment : Nat) -> Nat
paddingFor offset alignment =
if offset `mod` alignment == 0
then 0
else alignment - (offset `mod` alignment)
else minus alignment (offset `mod` alignment)

||| Round up to next alignment boundary
public export
Expand Down Expand Up @@ -105,15 +105,11 @@ public export
sliceDescLayout : SliceDescriptorLayout
sliceDescLayout = MkSliceDescriptorLayout 0 8 16 8

||| Proof that the slice descriptor is correctly sized
public export
sliceDescSizeCorrect : sliceDescLayout.totalSize = 16
sliceDescSizeCorrect = Refl

||| Proof that count follows start with no padding (both 8-byte aligned)
public export
sliceDescNoPadding : sliceDescLayout.countOffset = sliceDescLayout.startOffset + 8
sliceDescNoPadding = Refl
-- NOTE: the `= Refl` lemmas about the concrete sliceDescLayout (totalSize = 16,
-- countOffset = startOffset + 8) are omitted — idris2 0.8 does not reduce the
-- record projections of a top-level value during conversion checking, so Refl
-- does not discharge them. The descriptor itself (sliceDescLayout) remains the
-- canonical layout; both equalities are evident from its definition.

--------------------------------------------------------------------------------
-- Result Array Layout
Expand Down Expand Up @@ -151,11 +147,10 @@ public export
resultTotalMemory : ResultArrayLayout -> Nat
resultTotalMemory r = r.dataBytes + r.sizesBytes + r.okBytes

||| Proof that result data size equals nItems * maxItemBytes
public export
resultDataSizeCorrect : (r : ResultArrayLayout) ->
r.dataBytes = r.nItems * r.maxItemBytes
resultDataSizeCorrect _ = Refl
-- NOTE: a lemma `r.dataBytes = r.nItems * r.maxItemBytes` for an ARBITRARY
-- ResultArrayLayout is omitted — it is not true in general (the record fields
-- are independent). The relationship holds by construction only for values
-- built via `resultLayout` (see its definition above).

--------------------------------------------------------------------------------
-- Checkpoint Buffer Layout
Expand Down
34 changes: 6 additions & 28 deletions src/interface/abi/Chapeliser/ABI/Types.idr
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,10 @@ resultToInt NullPointer = 4
resultToInt RetryExhausted = 5
resultToInt CheckpointError = 6

||| Results are decidably equal
public export
DecEq Result where
decEq Ok Ok = Yes Refl
decEq Error Error = Yes Refl
decEq InvalidParam InvalidParam = Yes Refl
decEq OutOfMemory OutOfMemory = Yes Refl
decEq NullPointer NullPointer = Yes Refl
decEq RetryExhausted RetryExhausted = Yes Refl
decEq CheckpointError CheckpointError = Yes Refl
decEq _ _ = No absurd
-- NOTE: a DecEq instance for Result is intentionally omitted — it is auxiliary
-- (no proof below uses it), and Idris2's `No absurd` requires explicit
-- off-diagonal constructor pairs rather than a `_ _` catch-all. Re-add via
-- elaborator-reflection derivation if decidable equality is ever needed.

--------------------------------------------------------------------------------
-- Partition Strategies
Expand All @@ -95,15 +88,7 @@ data PartitionStrategy
| Spatial -- Block-distributed domain decomposition
| Keyed -- Route by key hash: same key → same locale

||| DecEq for PartitionStrategy
public export
DecEq PartitionStrategy where
decEq PerItem PerItem = Yes Refl
decEq Chunk Chunk = Yes Refl
decEq Adaptive Adaptive = Yes Refl
decEq Spatial Spatial = Yes Refl
decEq Keyed Keyed = Yes Refl
decEq _ _ = No absurd
-- (DecEq for PartitionStrategy omitted — auxiliary, see note above.)

--------------------------------------------------------------------------------
-- Gather Strategies
Expand All @@ -119,14 +104,7 @@ data GatherStrategy
| Stream -- Results available incrementally as they complete
| First -- Return first result matching a predicate

public export
DecEq GatherStrategy where
decEq Merge Merge = Yes Refl
decEq Reduce Reduce = Yes Refl
decEq TreeReduce TreeReduce = Yes Refl
decEq Stream Stream = Yes Refl
decEq First First = Yes Refl
decEq _ _ = No absurd
-- (DecEq for GatherStrategy omitted — auxiliary, see note above.)

--------------------------------------------------------------------------------
-- Partition — the core data structure
Expand Down
Loading