|
1 | 1 | -- SPDX-License-Identifier: PMPL-1.0-or-later |
2 | | --- Copyright (c) {{CURRENT_YEAR}} {{AUTHOR}} ({{OWNER}}) <{{AUTHOR_EMAIL}}> |
| 2 | +-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
3 | 3 | -- |
4 | | -||| Foreign Function Interface Declarations |
| 4 | +||| Chapeliser Foreign Function Interface Declarations |
5 | 5 | ||| |
6 | | -||| This module declares all C-compatible functions that will be |
7 | | -||| implemented in the Zig FFI layer. |
| 6 | +||| Declares all C-compatible functions that the Chapeliser runtime calls. |
| 7 | +||| These functions are implemented in the Zig FFI layer, which delegates |
| 8 | +||| to the user's workload-specific code. |
8 | 9 | ||| |
9 | | -||| All functions are declared here with type signatures and safety proofs. |
10 | | -||| Implementations live in ffi/zig/ |
| 10 | +||| The function signatures here MUST match: |
| 11 | +||| 1. The Chapel `extern proc` declarations in the generated .chpl file |
| 12 | +||| 2. The Zig `export fn` signatures in the generated _ffi.zig file |
| 13 | +||| 3. The C function declarations in the generated .h header |
| 14 | +||| |
| 15 | +||| Any mismatch is a linking error caught at Chapel compile time. |
11 | 16 |
|
12 | | -module {{PROJECT}}.ABI.Foreign |
| 17 | +module Chapeliser.ABI.Foreign |
13 | 18 |
|
14 | | -import {{PROJECT}}.ABI.Types |
15 | | -import {{PROJECT}}.ABI.Layout |
| 19 | +import Chapeliser.ABI.Types |
| 20 | +import Chapeliser.ABI.Layout |
16 | 21 |
|
17 | 22 | %default total |
18 | 23 |
|
19 | 24 | -------------------------------------------------------------------------------- |
20 | 25 | -- Library Lifecycle |
21 | 26 | -------------------------------------------------------------------------------- |
22 | 27 |
|
23 | | -||| Initialize the library |
24 | | -||| Returns a handle to the library instance, or Nothing on failure |
| 28 | +||| Initialise the workload library. Called once on Chapel locale 0 |
| 29 | +||| before any items are loaded or processed. |
| 30 | +||| Returns 0 on success, non-zero error code on failure. |
25 | 31 | export |
26 | | -%foreign "C:{{project}}_init, lib{{project}}" |
27 | | -prim__init : PrimIO Bits64 |
| 32 | +%foreign "C:c_init, libchapeliser_ffi" |
| 33 | +prim__init : PrimIO Bits32 |
28 | 34 |
|
29 | | -||| Safe wrapper for library initialization |
| 35 | +||| Safe wrapper for initialisation |
30 | 36 | export |
31 | | -init : IO (Maybe Handle) |
| 37 | +init : IO (Either Result ()) |
32 | 38 | init = do |
33 | | - ptr <- primIO prim__init |
34 | | - pure (createHandle ptr) |
35 | | - |
36 | | -||| Clean up library resources |
37 | | -export |
38 | | -%foreign "C:{{project}}_free, lib{{project}}" |
39 | | -prim__free : Bits64 -> PrimIO () |
40 | | - |
41 | | -||| Safe wrapper for cleanup |
42 | | -export |
43 | | -free : Handle -> IO () |
44 | | -free h = primIO (prim__free (handlePtr h)) |
45 | | - |
46 | | --------------------------------------------------------------------------------- |
47 | | --- Core Operations |
48 | | --------------------------------------------------------------------------------- |
| 39 | + rc <- primIO prim__init |
| 40 | + pure $ if rc == 0 then Right () else Left Error |
49 | 41 |
|
50 | | -||| Example operation: process data |
| 42 | +||| Shut down the workload library. Called once on locale 0 |
| 43 | +||| after all results have been stored. |
51 | 44 | export |
52 | | -%foreign "C:{{project}}_process, lib{{project}}" |
53 | | -prim__process : Bits64 -> Bits32 -> PrimIO Bits32 |
| 45 | +%foreign "C:c_shutdown, libchapeliser_ffi" |
| 46 | +prim__shutdown : PrimIO Bits32 |
54 | 47 |
|
55 | | -||| Safe wrapper with error handling |
| 48 | +||| Safe wrapper for shutdown |
56 | 49 | export |
57 | | -process : Handle -> Bits32 -> IO (Either Result Bits32) |
58 | | -process h input = do |
59 | | - result <- primIO (prim__process (handlePtr h) input) |
60 | | - pure $ case result of |
61 | | - 0 => Left Error |
62 | | - n => Right n |
| 50 | +shutdown : IO (Either Result ()) |
| 51 | +shutdown = do |
| 52 | + rc <- primIO prim__shutdown |
| 53 | + pure $ if rc == 0 then Right () else Left Error |
63 | 54 |
|
64 | 55 | -------------------------------------------------------------------------------- |
65 | | --- String Operations |
| 56 | +-- Data I/O |
66 | 57 | -------------------------------------------------------------------------------- |
67 | 58 |
|
68 | | -||| Convert C string to Idris String |
| 59 | +||| Get the total number of input items. Called on locale 0. |
| 60 | +||| Returns item count (>= 0), or negative on error. |
69 | 61 | export |
70 | | -%foreign "support:idris2_getString, libidris2_support" |
71 | | -prim__getString : Bits64 -> String |
| 62 | +%foreign "C:c_get_total_items, libchapeliser_ffi" |
| 63 | +prim__getTotalItems : PrimIO Bits32 |
72 | 64 |
|
73 | | -||| Free C string |
| 65 | +||| Safe wrapper |
74 | 66 | export |
75 | | -%foreign "C:{{project}}_free_string, lib{{project}}" |
76 | | -prim__freeString : Bits64 -> PrimIO () |
| 67 | +getTotalItems : IO (Either Result Nat) |
| 68 | +getTotalItems = do |
| 69 | + n <- primIO prim__getTotalItems |
| 70 | + pure $ Right (cast n) |
77 | 71 |
|
78 | | -||| Get string result from library |
| 72 | +||| Load (serialise) input item at index `idx` into buffer `buf`. |
| 73 | +||| On entry, `*len` is the buffer capacity (maxItemBytes). |
| 74 | +||| On exit, `*len` is the actual serialised size. |
| 75 | +||| Returns 0 on success. |
79 | 76 | export |
80 | | -%foreign "C:{{project}}_get_string, lib{{project}}" |
81 | | -prim__getResult : Bits64 -> PrimIO Bits64 |
| 77 | +%foreign "C:c_load_item, libchapeliser_ffi" |
| 78 | +prim__loadItem : Bits32 -> Bits64 -> Bits64 -> PrimIO Bits32 |
82 | 79 |
|
83 | | -||| Safe string getter |
| 80 | +||| Store (receive) a processed result at index `idx`. |
| 81 | +||| `buf` contains `len` bytes of serialised result data. |
| 82 | +||| Returns 0 on success. |
84 | 83 | export |
85 | | -getString : Handle -> IO (Maybe String) |
86 | | -getString h = do |
87 | | - ptr <- primIO (prim__getResult (handlePtr h)) |
88 | | - if ptr == 0 |
89 | | - then pure Nothing |
90 | | - else do |
91 | | - let str = prim__getString ptr |
92 | | - primIO (prim__freeString ptr) |
93 | | - pure (Just str) |
| 84 | +%foreign "C:c_store_result, libchapeliser_ffi" |
| 85 | +prim__storeResult : Bits32 -> Bits64 -> Bits64 -> PrimIO Bits32 |
94 | 86 |
|
95 | 87 | -------------------------------------------------------------------------------- |
96 | | --- Array/Buffer Operations |
| 88 | +-- Item Processing |
97 | 89 | -------------------------------------------------------------------------------- |
98 | 90 |
|
99 | | -||| Process array data |
| 91 | +||| Process a single serialised item. |
| 92 | +||| Reads from (in_buf, in_len), writes result to (out_buf, *out_len). |
| 93 | +||| Called on any locale — the user's function must be thread-safe. |
| 94 | +||| Returns 0 on success. |
100 | 95 | export |
101 | | -%foreign "C:{{project}}_process_array, lib{{project}}" |
102 | | -prim__processArray : Bits64 -> Bits64 -> Bits32 -> PrimIO Bits32 |
| 96 | +%foreign "C:c_process_item, libchapeliser_ffi" |
| 97 | +prim__processItem : Bits64 -> Bits64 -> Bits64 -> Bits64 -> PrimIO Bits32 |
103 | 98 |
|
104 | | -||| Safe array processor |
| 99 | +||| Process a chunk of items (for chunk partition strategy). |
| 100 | +||| items_buf contains item_count items at the given offsets and sizes. |
| 101 | +||| Returns 0 on success. |
105 | 102 | export |
106 | | -processArray : Handle -> (buffer : Bits64) -> (len : Bits32) -> IO (Either Result ()) |
107 | | -processArray h buf len = do |
108 | | - result <- primIO (prim__processArray (handlePtr h) buf len) |
109 | | - pure $ case resultFromInt result of |
110 | | - Just Ok => Right () |
111 | | - Just err => Left err |
112 | | - Nothing => Left Error |
113 | | - where |
114 | | - resultFromInt : Bits32 -> Maybe Result |
115 | | - resultFromInt 0 = Just Ok |
116 | | - resultFromInt 1 = Just Error |
117 | | - resultFromInt 2 = Just InvalidParam |
118 | | - resultFromInt 3 = Just OutOfMemory |
119 | | - resultFromInt 4 = Just NullPointer |
120 | | - resultFromInt _ = Nothing |
| 103 | +%foreign "C:c_process_chunk, libchapeliser_ffi" |
| 104 | +prim__processChunk : Bits64 -> Bits32 -> Bits64 -> Bits64 -> Bits64 -> Bits64 -> PrimIO Bits32 |
121 | 105 |
|
122 | 106 | -------------------------------------------------------------------------------- |
123 | | --- Error Handling |
| 107 | +-- Reduction |
124 | 108 | -------------------------------------------------------------------------------- |
125 | 109 |
|
126 | | -||| Get last error message |
| 110 | +||| Combine two serialised results into one. |
| 111 | +||| Used by reduce and tree-reduce gather strategies. |
| 112 | +||| Reads (a_buf, a_len) and (b_buf, b_len), writes to (out_buf, *out_len). |
| 113 | +||| The reduce function must be associative for tree-reduce correctness. |
| 114 | +||| Returns 0 on success. |
127 | 115 | export |
128 | | -%foreign "C:{{project}}_last_error, lib{{project}}" |
129 | | -prim__lastError : PrimIO Bits64 |
| 116 | +%foreign "C:c_reduce, libchapeliser_ffi" |
| 117 | +prim__reduce : Bits64 -> Bits64 -> Bits64 -> Bits64 -> Bits64 -> Bits64 -> PrimIO Bits32 |
| 118 | + |
| 119 | +-------------------------------------------------------------------------------- |
| 120 | +-- Match Predicate (for 'first' gather) |
| 121 | +-------------------------------------------------------------------------------- |
130 | 122 |
|
131 | | -||| Retrieve last error as string |
| 123 | +||| Test whether a serialised result matches the search criterion. |
| 124 | +||| Returns 1 if the result matches, 0 if not. |
| 125 | +||| Used by the 'first' gather strategy to stop early. |
132 | 126 | export |
133 | | -lastError : IO (Maybe String) |
134 | | -lastError = do |
135 | | - ptr <- primIO prim__lastError |
136 | | - if ptr == 0 |
137 | | - then pure Nothing |
138 | | - else pure (Just (prim__getString ptr)) |
| 127 | +%foreign "C:c_is_match, libchapeliser_ffi" |
| 128 | +prim__isMatch : Bits64 -> Bits64 -> PrimIO Bits32 |
139 | 129 |
|
140 | | -||| Get error description for result code |
| 130 | +||| Safe wrapper |
141 | 131 | export |
142 | | -errorDescription : Result -> String |
143 | | -errorDescription Ok = "Success" |
144 | | -errorDescription Error = "Generic error" |
145 | | -errorDescription InvalidParam = "Invalid parameter" |
146 | | -errorDescription OutOfMemory = "Out of memory" |
147 | | -errorDescription NullPointer = "Null pointer" |
| 132 | +isMatch : (bufPtr : Bits64) -> (len : Bits64) -> IO Bool |
| 133 | +isMatch buf len = do |
| 134 | + rc <- primIO (prim__isMatch buf len) |
| 135 | + pure (rc == 1) |
148 | 136 |
|
149 | 137 | -------------------------------------------------------------------------------- |
150 | | --- Version Information |
| 138 | +-- Key Hash (for keyed partition) |
151 | 139 | -------------------------------------------------------------------------------- |
152 | 140 |
|
153 | | -||| Get library version |
| 141 | +||| Compute a hash of the item's distribution key. |
| 142 | +||| Items with the same hash (mod numLocales) land on the same locale. |
| 143 | +||| Returns an unsigned 32-bit hash value. |
154 | 144 | export |
155 | | -%foreign "C:{{project}}_version, lib{{project}}" |
156 | | -prim__version : PrimIO Bits64 |
| 145 | +%foreign "C:c_key_hash, libchapeliser_ffi" |
| 146 | +prim__keyHash : Bits64 -> Bits64 -> PrimIO Bits32 |
157 | 147 |
|
158 | | -||| Get version as string |
159 | | -export |
160 | | -version : IO String |
161 | | -version = do |
162 | | - ptr <- primIO prim__version |
163 | | - pure (prim__getString ptr) |
| 148 | +-------------------------------------------------------------------------------- |
| 149 | +-- Checkpoint (optional) |
| 150 | +-------------------------------------------------------------------------------- |
164 | 151 |
|
165 | | -||| Get library build info |
| 152 | +||| Save checkpoint data with a string tag. |
| 153 | +||| Returns 0 on success, -1 if checkpointing is not implemented. |
166 | 154 | export |
167 | | -%foreign "C:{{project}}_build_info, lib{{project}}" |
168 | | -prim__buildInfo : PrimIO Bits64 |
| 155 | +%foreign "C:c_checkpoint_save, libchapeliser_ffi" |
| 156 | +prim__checkpointSave : Bits64 -> Bits64 -> Bits64 -> PrimIO Bits32 |
169 | 157 |
|
170 | | -||| Get build information |
| 158 | +||| Load checkpoint data by tag. |
| 159 | +||| On entry, *len is buffer capacity. On exit, *len is data size. |
| 160 | +||| Returns 0 on success, -1 if no checkpoint exists or not implemented. |
171 | 161 | export |
172 | | -buildInfo : IO String |
173 | | -buildInfo = do |
174 | | - ptr <- primIO prim__buildInfo |
175 | | - pure (prim__getString ptr) |
| 162 | +%foreign "C:c_checkpoint_load, libchapeliser_ffi" |
| 163 | +prim__checkpointLoad : Bits64 -> Bits64 -> Bits64 -> PrimIO Bits32 |
176 | 164 |
|
177 | 165 | -------------------------------------------------------------------------------- |
178 | | --- Callback Support |
| 166 | +-- Version Information |
179 | 167 | -------------------------------------------------------------------------------- |
180 | 168 |
|
181 | | -||| Callback function type (C ABI) |
182 | | -public export |
183 | | -Callback : Type |
184 | | -Callback = Bits64 -> Bits32 -> Bits32 |
185 | | - |
186 | | -||| Register a callback |
| 169 | +||| Chapeliser ABI version (major * 1000 + minor) |
| 170 | +||| Used to detect ABI mismatches at runtime. |
187 | 171 | export |
188 | | -%foreign "C:{{project}}_register_callback, lib{{project}}" |
189 | | -prim__registerCallback : Bits64 -> AnyPtr -> PrimIO Bits32 |
190 | | - |
191 | | --- TODO: Implement safe callback registration. |
192 | | --- The callback must be wrapped via a proper FFI callback mechanism. |
193 | | --- Do NOT use cast — it is banned per project safety standards. |
194 | | --- See: https://idris2.readthedocs.io/en/latest/ffi/ffi.html#callbacks |
| 172 | +chapeliserABIVersion : Bits32 |
| 173 | +chapeliserABIVersion = 1000 -- v1.0 |
195 | 174 |
|
196 | 175 | -------------------------------------------------------------------------------- |
197 | | --- Utility Functions |
| 176 | +-- Verification: Partition + Gather Composition |
198 | 177 | -------------------------------------------------------------------------------- |
199 | 178 |
|
200 | | -||| Check if library is initialized |
201 | | -export |
202 | | -%foreign "C:{{project}}_is_initialized, lib{{project}}" |
203 | | -prim__isInitialized : Bits64 -> PrimIO Bits32 |
204 | | - |
205 | | -||| Check initialization status |
206 | | -export |
207 | | -isInitialized : Handle -> IO Bool |
208 | | -isInitialized h = do |
209 | | - result <- primIO (prim__isInitialized (handlePtr h)) |
210 | | - pure (result /= 0) |
| 179 | +||| The full Chapeliser pipeline is correct if: |
| 180 | +||| 1. The partition is valid (complete + disjoint) |
| 181 | +||| 2. Each item is processed exactly once |
| 182 | +||| 3. The gather preserves all results |
| 183 | +||| |
| 184 | +||| This type witnesses that a workload configuration satisfies all three. |
| 185 | +public export |
| 186 | +data PipelineCorrect : WorkloadConfig -> Type where |
| 187 | + MkPipelineCorrect : |
| 188 | + {cfg : WorkloadConfig} -> |
| 189 | + (partition : ValidPartition (MkPartition (perItemSlices cfg.totalItems cfg.numLocales))) -> |
| 190 | + (gather : GatherConservation (MkGatherInput (replicate cfg.numLocales (cfg.totalItems `div` cfg.numLocales))) cfg.totalItems) -> |
| 191 | + PipelineCorrect cfg |
0 commit comments