1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//! NIF registration surface.
use std::collections::BTreeSet;
use beamr::native::NativeFn;
/// Module/function/arity key for a native implemented function.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Mfa {
/// BEAM module name that owns the native function import.
pub module: String,
/// BEAM function name imported from `module`.
pub function: String,
/// Function arity.
pub arity: u8,
}
impl Mfa {
/// Construct an MFA key from string-like module and function names.
#[must_use]
pub fn new(module: impl Into<String>, function: impl Into<String>, arity: u8) -> Self {
Self {
module: module.into(),
function: function.into(),
arity,
}
}
/// Return the human-readable MFA as `module:function/arity`.
#[must_use]
pub fn display(&self) -> String {
format!("{}:{}/{}", self.module, self.function, self.arity)
}
}
/// Classifies whether a NIF is safe to re-execute inline during workflow replay.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Determinism {
/// Deterministic helper whose output is fully determined by its inputs.
Pure,
/// Operation with effects or runtime-dependent output that must be mediated
/// by the engine's durable execution contract.
SideEffectful,
}
/// A host- or engine-owned native implemented function entry.
#[derive(Clone, Debug)]
pub struct NifEntry {
/// MFA key used by BEAM import resolution.
pub mfa: Mfa,
/// Rust function pointer compatible with beamr's native registry.
pub function: NativeFn,
/// Whether beamr should mark the entry for dirty scheduler execution.
pub is_dirty: bool,
/// Replay-safety classification for this native function.
pub determinism: Determinism,
}
impl NifEntry {
/// Construct a normal native implemented function entry.
#[must_use]
pub fn new(mfa: Mfa, function: NativeFn, determinism: Determinism) -> Self {
Self {
mfa,
function,
is_dirty: false,
determinism,
}
}
/// Construct a native implemented function entry marked dirty.
#[must_use]
pub fn dirty(mfa: Mfa, function: NativeFn, determinism: Determinism) -> Self {
Self {
mfa,
function,
is_dirty: true,
determinism,
}
}
}
/// Accumulates NIF entries before they are installed into the runtime.
#[derive(Clone, Debug, Default)]
pub struct NifRegistration {
entries: Vec<NifEntry>,
}
#[cfg(test)]
pub(crate) fn test_native_zero(
args: &[beamr::term::Term],
context: &mut beamr::native::ProcessContext,
) -> Result<beamr::term::Term, beamr::term::Term> {
let _ = context;
if args.len() > 255 {
return Err(beamr::term::Term::small_int(0));
}
Ok(beamr::term::Term::small_int(0))
}
impl NifRegistration {
/// Construct an empty NIF registration collection.
#[must_use]
pub const fn new() -> Self {
Self {
entries: Vec::new(),
}
}
/// Add host-supplied NIF entries to the collection.
pub fn add_host_nifs(&mut self, entries: impl IntoIterator<Item = NifEntry>) -> &mut Self {
self.entries.extend(entries);
self
}
/// Add engine-owned NIF entries to the collection.
///
/// Registers the `aion_flow_ffi` NIFs that back the Gleam SDK's
/// `@external(erlang, "aion_flow_ffi", ...)` declarations.
pub fn add_engine_nifs(&mut self) -> &mut Self {
self.entries
.extend(super::engine_nifs::engine_nif_entries());
self
}
/// Return the unique module names represented by the collected NIF entries.
///
/// These names are derived from each entry's MFA and are therefore kept in
/// sync with both engine-owned and host-supplied registrations.
#[must_use]
pub fn module_names(&self) -> Vec<String> {
self.entries
.iter()
.map(|entry| entry.mfa.module.clone())
.collect::<BTreeSet<_>>()
.into_iter()
.collect()
}
/// Consume the collection and return the entries to install.
#[must_use]
pub fn into_entries(self) -> Vec<NifEntry> {
self.entries
}
/// Return true when no NIF entries have been collected.
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
/// Return the number of NIF entries collected.
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
}
#[cfg(test)]
mod tests {
use beamr::native::ProcessContext;
use beamr::term::Term;
use super::{Determinism, Mfa, NifEntry, NifRegistration};
fn native_zero(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
super::test_native_zero(args, context)
}
#[test]
fn collects_host_and_engine_nifs() {
let mut registration = NifRegistration::new();
registration
.add_engine_nifs()
.add_host_nifs([NifEntry::dirty(
Mfa::new("host", "zero", 0),
native_zero,
Determinism::Pure,
)]);
assert!(registration.len() >= 2);
let module_names = registration.module_names();
assert!(module_names.iter().any(|module| module == "aion_flow_ffi"));
assert!(module_names.iter().any(|module| module == "host"));
let entries = registration.into_entries();
let host_entry = entries.iter().find(|e| e.mfa.display() == "host:zero/0");
assert!(host_entry.is_some_and(|e| e.is_dirty && e.determinism == Determinism::Pure));
let dispatch_activity = entries
.iter()
.find(|e| e.mfa.display() == "aion_flow_ffi:dispatch_activity/3");
assert!(dispatch_activity.is_some_and(|e| !e.is_dirty));
let await_activity = entries
.iter()
.find(|e| e.mfa.display() == "aion_flow_ffi:await_activity_result/1");
assert!(await_activity.is_some_and(|e| !e.is_dirty));
}
}