Skip to main content

hara_native/
wasm_binding.rs

1//! Restricted `.hal` interface contracts for portable Wasm extension bindings.
2//!
3//! Sources are parsed as data with the Hara reader. This module never evaluates
4//! an interface, instantiates a module, or acquires host authority.
5
6#[cfg(not(target_arch = "wasm32"))]
7mod adapter;
8mod canonical;
9mod direct;
10mod memory;
11#[cfg(not(target_arch = "wasm32"))]
12mod package;
13mod parser;
14#[cfg(not(target_arch = "wasm32"))]
15mod runtime;
16mod syntax;
17pub mod wit;
18mod wit_format;
19mod wit_parser;
20
21#[cfg(test)]
22mod tests;
23
24use std::collections::{BTreeMap, BTreeSet};
25
26use sha2::{Digest, Sha256};
27
28use crate::extension::ExtensionExport;
29
30pub use crate::direct_wasm::{
31    DirectWasmFunctionExport, DirectWasmImport, DirectWasmImportKind, DirectWasmInspection,
32    DirectWasmMemory,
33};
34#[cfg(not(target_arch = "wasm32"))]
35pub use adapter::{
36    generate_adapter, generate_hta_adapter, verify_hta_scalar, AdapterArtifact,
37    ADAPTER_MANIFEST_SCHEMA,
38};
39pub use direct::{
40    direct_inspection_source, direct_interface_skeleton, inspect_direct,
41    DIRECT_WASM_INSPECTION_SCHEMA,
42};
43pub use memory::{
44    MemoryArgumentPlan, MemoryBindingPlan, MemoryFunctionPlan, MemoryResultPlan,
45    MEMORY_BINDING_SCHEMA,
46};
47#[cfg(not(target_arch = "wasm32"))]
48pub use package::{
49    bind_package, inspect_module, write_interface_skeleton, BindingTarget, BoundPackage,
50    InspectionArtifact, DIRECT_WASM_BINDING_SCHEMA, DIRECT_WASM_BUILD_PRODUCT_SCHEMA,
51    DIRECT_WASM_CONFORMANCE_SCHEMA,
52};
53#[cfg(not(target_arch = "wasm32"))]
54pub use runtime::WasmtimeMemoryExecutor;
55pub use wit::{
56    import_wit, project_wit, WitDiagnostic, WitDiagnosticSeverity, WitImportArtifact,
57    WitImportOptions, WitProjectionArtifact, WitProjectionOptions, WitRoute, WIT_IR_SCHEMA,
58    WIT_MANIFEST_SCHEMA,
59};
60
61pub const WASM_INTERFACE_SCHEMA: &str = "hara.wasm-interface/0-alpha";
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
64pub enum WasmValueType {
65    I32,
66    I64,
67    F32,
68    F64,
69    Void,
70}
71
72impl WasmValueType {
73    pub fn as_keyword(self) -> &'static str {
74        match self {
75            Self::I32 => "i32",
76            Self::I64 => "i64",
77            Self::F32 => "f32",
78            Self::F64 => "f64",
79            Self::Void => "void",
80        }
81    }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
85pub enum HaraValueType {
86    I32,
87    I64,
88    F32,
89    F64,
90    Boolean,
91    String,
92    Bytes,
93    Record(String),
94    Variant(String),
95    Handle(String),
96    Callback(String),
97    Void,
98}
99
100impl HaraValueType {
101    fn direct_wasm_type(&self) -> Option<WasmValueType> {
102        match self {
103            Self::I32 | Self::Boolean => Some(WasmValueType::I32),
104            Self::I64 => Some(WasmValueType::I64),
105            Self::F32 => Some(WasmValueType::F32),
106            Self::F64 => Some(WasmValueType::F64),
107            Self::Void => Some(WasmValueType::Void),
108            _ => None,
109        }
110    }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum Ownership {
115    Borrowed,
116    Caller,
117    Callee,
118    Transferred,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum Lowering {
123    Direct,
124    PointerLength,
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum Lifting {
129    Direct,
130    PointerLength,
131    PackedI64,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct MemoryContract {
136    pub export: String,
137    pub allocate: Option<String>,
138    pub reallocate: Option<String>,
139    pub release: Option<String>,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
143pub struct BindingParameter {
144    pub name: String,
145    pub hara_type: HaraValueType,
146    pub wasm_type: WasmValueType,
147    pub lowering: Option<Lowering>,
148    pub ownership: Option<Ownership>,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct BindingResult {
153    pub hara_type: HaraValueType,
154    pub wasm_type: WasmValueType,
155    pub lifting: Option<Lifting>,
156    pub ownership: Option<Ownership>,
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct ErrorContract {
161    pub convention: String,
162    pub codes: BTreeMap<i64, String>,
163}
164
165#[derive(Debug, Clone, Default, PartialEq, Eq)]
166pub struct RequestPolicy {
167    pub timeout_ms: Option<u64>,
168    pub max_in_flight: Option<u32>,
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub enum CancellationPolicy {
173    Cooperative,
174    Abort,
175    Ignore,
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub struct AsyncPolicy {
180    pub operation: String,
181    pub request: RequestPolicy,
182    pub cancellation: CancellationPolicy,
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct HostCallContract {
187    pub methods: BTreeSet<String>,
188    pub capabilities: BTreeSet<String>,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq)]
192pub struct CallbackParameter {
193    pub name: String,
194    pub hara_type: HaraValueType,
195}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
198pub struct CallbackContract {
199    pub arguments: Vec<CallbackParameter>,
200    pub returns: HaraValueType,
201    pub reentrant: bool,
202}
203
204#[derive(Debug, Clone, PartialEq, Eq)]
205pub struct HandleContract {
206    pub tag: String,
207    pub release: Option<String>,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq)]
211pub struct BindingFunction {
212    pub name: String,
213    pub wasm_export: String,
214    pub arguments: Vec<BindingParameter>,
215    pub returns: BindingResult,
216    pub asynchronous: bool,
217    pub operation: Option<String>,
218    pub request: Option<RequestPolicy>,
219    pub cancellation: Option<CancellationPolicy>,
220    pub errors: Option<ErrorContract>,
221    pub capabilities: BTreeSet<String>,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq)]
225pub struct WasmInterface {
226    pub schema: String,
227    pub namespace: String,
228    pub module: String,
229    pub memory: Option<MemoryContract>,
230    pub exports: Vec<BindingFunction>,
231    pub capabilities: BTreeSet<String>,
232    pub host_calls: BTreeMap<String, HostCallContract>,
233    pub callbacks: BTreeMap<String, CallbackContract>,
234    pub handles: BTreeMap<String, HandleContract>,
235    pub resources: BTreeMap<String, HandleContract>,
236}
237
238impl WasmInterface {
239    pub fn parse(source: &str, origin: &str) -> Result<Self, String> {
240        parser::parse_interface(source, origin)
241    }
242
243    pub fn canonical_source(&self) -> String {
244        canonical::source(self)
245    }
246
247    pub fn digest(&self) -> String {
248        let digest = Sha256::digest(self.canonical_source().as_bytes());
249        format!("sha256:{digest:x}")
250    }
251
252    pub fn hta_required(&self) -> bool {
253        !self.host_calls.is_empty()
254            || !self.callbacks.is_empty()
255            || !self.handles.is_empty()
256            || !self.resources.is_empty()
257            || self.exports.iter().any(|export| {
258                export.asynchronous
259                    || export.operation.is_some()
260                    || export.request.is_some()
261                    || export.cancellation.is_some()
262            })
263    }
264
265    pub fn direct_exports(&self) -> Vec<(String, ExtensionExport)> {
266        self.exports
267            .iter()
268            .map(|export| {
269                (
270                    export.wasm_export.clone(),
271                    ExtensionExport {
272                        arguments: export
273                            .arguments
274                            .iter()
275                            .map(|argument| argument.wasm_type.as_keyword().to_owned())
276                            .collect(),
277                        returns: export.returns.wasm_type.as_keyword().to_owned(),
278                        asynchronous: false,
279                        raw_export: None,
280                    },
281                )
282            })
283            .collect()
284    }
285}