Skip to main content

byteflow/
samples.rs

1//! Built-in demo chunks assembled with [`crate::Program`].
2//!
3//! Use these as runnable specs of the messaging contract (and as regression
4//! tests). Prefer copying a sample over inventing hop register layouts from
5//! scratch.
6//!
7//! | Sample | Shows |
8//! |--------|--------|
9//! | [`add_forty_two`] | Scalar VM path (no natives) |
10//! | [`ping_pong`] | Cap spawn + Atomic Hop round-trip |
11//! | [`atomic_request_reply`] | Tagged REQ/REP + correlated receive |
12//! | [`atomic_actors`] | Server loop + two `Ask` clients + minted `request_id` |
13//! | [`cap_in_payload`] | Cap in hop payload is reissued to the recipient |
14//! | [`selective_receive`] | `ReceiveMatch` FIFO skip |
15//! | [`ask_reply`] | `Ask` RPC hop |
16//! | [`ask_timeout_expires`] | `AskTimeout` writes `Unit` when the server stays silent |
17//! | [`ask_target_exits`] | `Ask` dest is `TAG_SYS_EXIT` when the server dies first |
18//! | [`server_loop`] | BEAM-style receive → handle → reply loop |
19//! | [`named_service`] | Bytecode `register_name` / `whereis` (Cap, not FlowId) |
20//! | [`forged_sender_send`] / [`forged_sender_ask`] | S1: forged `make_msg` sender dies |
21//! | [`boom`] | Immediate trap (supervisor demos) |
22//! | [`monitor_down`] | Monitor → [`crate::TAG_SYS_DOWN`] on child exit |
23//! | [`waiting_send`] | `WAITING_SEND`: second hop parks until the first is received |
24//!
25//! Hop samples require [`crate::std_native_table`].
26
27use crate::{Chunk, Program};
28use crate::natives::std_native;
29
30/// Native indices (must match [`crate::std_native_map`]).
31const N_PRINT: u32 = std_native::PRINT;
32const N_MAKE_MSG: u32 = std_native::MAKE_MSG;
33
34/// Protocol tags for Atomic Hop samples.
35pub const TAG_REQ: i32 = 1;
36pub const TAG_REP: i32 = 2;
37pub const TAG_PING: i32 = 10;
38pub const TAG_PONG: i32 = 11;
39/// Decoy hop for [`selective_receive`] — must be skipped by `ReceiveMatch`.
40pub const TAG_JUNK: i32 = 99;
41
42/// `41 + 1; return` — the 60-second sanity chunk.
43pub fn add_forty_two() -> Chunk {
44    let mut p = Program::new("add-forty-two");
45    p.function("main", 0, |f| {
46        let a = f.load_i32(41);
47        let b = f.load_i32(1);
48        let sum = f.add(a, b);
49        f.return_(sum);
50    });
51    p.build()
52}
53
54/// Two flows, one **Atomic Hop** round-trip: `main` sends a `Message` to
55/// `pong`, `pong` replies with payload+1 via `msg_reply_cap`, `main` returns
56/// that payload (`2`).
57pub fn ping_pong() -> Chunk {
58    let mut p = Program::new("ping-pong");
59    let pong = p.function("pong", 0, |f| {
60        let msg = f.receive();
61        let payload = f.hop_payload(msg);
62        f.add_imm(payload, 1);
63        f.send_reply(msg, TAG_PONG, payload);
64        f.exit(payload);
65    });
66    p.function("main", 0, |f| {
67        let child = f.spawn(pong, 0);
68        let payload = f.load_i32(1);
69        let req = f.hop_fresh(TAG_PING, payload);
70        let rid = f.hop_request_id(req);
71        f.send(child, req);
72        let reply = f.receive_match_corr_imm(TAG_PONG as u16, rid);
73        let out = f.hop_payload(reply);
74        f.return_(out);
75    });
76    p.build()
77}
78
79/// Atomic request-reply with [`crate::Value::Message`] (one envelope per hop).
80pub fn atomic_request_reply() -> Chunk {
81    let mut p = Program::new("atomic-request-reply");
82    let server = p.function("server", 0, |f| {
83        let msg = f.receive();
84        f.native1_on(msg, N_PRINT);
85        let payload = f.hop_payload(msg);
86        f.add_imm(payload, 1);
87        f.send_reply(msg, TAG_REP, payload);
88        f.exit(payload);
89    });
90    p.function("main", 0, |f| {
91        let server_cap = f.spawn(server, 0);
92        let payload = f.load_i32(41);
93        let req = f.hop_fresh(TAG_REQ, payload);
94        let rid = f.hop_request_id(req);
95        f.native1_on(req, N_PRINT);
96        f.send(server_cap, req);
97        let reply = f.receive_match_corr_imm(TAG_REP as u16, rid);
98        f.native1_on(reply, N_PRINT);
99        let out = f.hop_payload(reply);
100        f.return_(out);
101    });
102    p.build()
103}
104
105/// Selective Atomic Hop: server waits for `TAG_REQ` while a `TAG_JUNK` hop
106/// sits ahead in the mailbox (FIFO skip, not drop).
107pub fn selective_receive() -> Chunk {
108    let mut p = Program::new("selective-receive");
109    let server = p.function("server", 0, |f| {
110        let msg = f.receive_match_imm(TAG_REQ as u16);
111        let payload = f.hop_payload(msg);
112        f.add_imm(payload, 1);
113        f.send_reply(msg, TAG_REP, payload);
114        let junk = f.receive();
115        let tag = f.hop_tag(junk);
116        let is_junk = f.eq_imm(tag, TAG_JUNK);
117        let trap_lbl = f.label();
118        f.branch_if_falsy(is_junk, trap_lbl);
119        f.exit(payload);
120        f.bind(trap_lbl);
121        f.trap(2);
122    });
123    p.function("main", 0, |f| {
124        let server_cap = f.spawn(server, 0);
125        let req_id = f.load_i32(1);
126        let zero = f.load_i32(0);
127        let junk = f.hop(req_id, TAG_JUNK, zero);
128        f.send(server_cap, junk);
129        let payload = f.load_i32(41);
130        let req = f.hop(req_id, TAG_REQ, payload);
131        f.send(server_cap, req);
132        let reply = f.receive_match_imm(TAG_REP as u16);
133        let out = f.hop_payload(reply);
134        f.return_(out);
135    });
136    p.build()
137}
138
139/// Atomic request/reply via `Ask` (RPC hop).
140pub fn ask_reply() -> Chunk {
141    let mut p = Program::new("ask-reply");
142    let server = p.function("server", 0, |f| {
143        let msg = f.receive_match_imm(TAG_REQ as u16);
144        let payload = f.hop_payload(msg);
145        f.add_imm(payload, 1);
146        f.send_reply(msg, TAG_REP, payload);
147        f.exit(payload);
148    });
149    p.function("main", 0, |f| {
150        let server_cap = f.spawn(server, 0);
151        let payload = f.load_i32(41);
152        let req = f.hop_fresh(TAG_REQ, payload);
153        let reply = f.ask(server_cap, req);
154        let out = f.hop_payload(reply);
155        f.return_(out);
156    });
157    p.build()
158}
159
160/// `AskTimeout` against a server that never replies → `Unit`.
161pub fn ask_timeout_expires() -> Chunk {
162    let mut p = Program::new("ask-timeout");
163    let server = p.function("server", 0, |f| {
164        let _msg = f.receive();
165        let ms = f.load_i32(10_000);
166        f.sleep(ms);
167        let zero = f.load_i32(0);
168        f.return_(zero);
169    });
170    p.function("main", 0, |f| {
171        let server_cap = f.spawn(server, 0);
172        let req_id = f.load_i32(1);
173        let payload = f.load_i32(0);
174        let req = f.hop(req_id, TAG_REQ, payload);
175        let ms = f.load_i32(40);
176        let reply = f.ask_timeout(server_cap, req, ms);
177        f.return_(reply);
178    });
179    p.build()
180}
181
182/// Server takes the request and exits; client `Ask` must not hang.
183pub fn ask_target_exits() -> Chunk {
184    let mut p = Program::new("ask-target-exits");
185    let server = p.function("server", 0, |f| {
186        let _msg = f.receive();
187        let z = f.load_i32(0);
188        f.return_(z);
189    });
190    p.function("main", 0, |f| {
191        let server_cap = f.spawn(server, 0);
192        let req_id = f.load_i32(1);
193        let payload = f.load_i32(0);
194        let req = f.hop(req_id, TAG_REQ, payload);
195        let reply = f.ask(server_cap, req);
196        let tag = f.hop_tag(reply);
197        f.return_(tag);
198    });
199    p.build()
200}
201
202/// BEAM-style server loop: `receive_match` → handle → `send_reply` → repeat.
203pub fn server_loop() -> Chunk {
204    let mut p = Program::new("server-loop");
205    let server = p.function("server", 0, |f| {
206        let loop_lbl = f.label();
207        f.bind(loop_lbl);
208        let req = f.receive_match_imm(TAG_REQ as u16);
209        let payload = f.hop_payload(req);
210        f.add_imm(payload, 1);
211        f.send_reply(req, TAG_REP, payload);
212        f.jump(loop_lbl);
213    });
214    p.function("main", 0, |f| {
215        let server_cap = f.spawn(server, 0);
216        let payload = f.load_i32(41);
217        let req = f.hop_fresh(TAG_REQ, payload);
218        f.send(server_cap, req);
219        let reply = f.receive_match_imm(TAG_REP as u16);
220        let out = f.hop_payload(reply);
221        f.return_(out);
222    });
223    p.build()
224}
225
226/// Server publishes `"svc"`; client loops `whereis` until it gets a SEND Cap
227/// (not ASK — use `Send` + `Receive`, not `Ask`). Returns `42` (41 + 1).
228/// Discovery never exposes a raw FlowId.
229pub fn named_service() -> Chunk {
230    let mut p = Program::new("named-service");
231    let server = p.function("server", 0, |f| {
232        let name = f.load_str("svc");
233        f.register_name(name);
234        let loop_lbl = f.label();
235        f.bind(loop_lbl);
236        let req = f.receive_match_imm(TAG_REQ as u16);
237        let payload = f.hop_payload(req);
238        f.add_imm(payload, 1);
239        f.send_reply(req, TAG_REP, payload);
240        f.jump(loop_lbl);
241    });
242    p.function("main", 0, |f| {
243        let _server = f.spawn(server, 0);
244        let name = f.load_str("svc");
245        let retry = f.label();
246        let miss = f.label();
247        f.bind(retry);
248        let cap = f.whereis(name);
249        f.branch_if_falsy(cap, miss);
250        let payload = f.load_i32(41);
251        let req = f.hop_fresh(TAG_REQ, payload);
252        f.send(cap, req);
253        let reply = f.receive_match_imm(TAG_REP as u16);
254        let out = f.hop_payload(reply);
255        f.return_(out);
256        f.bind(miss);
257        // Sleep (not Yield): Yield re-queues on the local worker deque and
258        // can starve the injector, so the server would never register.
259        let ms = f.load_i32(1);
260        f.sleep(ms);
261        f.jump(retry);
262    });
263    p.build()
264}
265
266/// Canonical Atomic Hop actor demo: one server loop, two clients, each
267/// doing `N` `Ask`s with minted `request_id`s. Main waits for both DONE
268/// hops and returns the sum of the client accumulators (`2 * 36 = 72`).
269pub fn atomic_actors() -> Chunk {
270    const N: i32 = 8;
271    let mut p = Program::new("atomic-actors");
272    let server = p.function("server", 0, |f| {
273        let loop_lbl = f.label();
274        f.bind(loop_lbl);
275        let req = f.receive_match_imm(TAG_REQ as u16);
276        let payload = f.hop_payload(req);
277        f.add_imm(payload, 1);
278        f.send_reply(req, TAG_REP, payload);
279        f.jump(loop_lbl);
280    });
281    let client = p.function("client", 2, |f| {
282        let server_cap = f.reg(0);
283        let parent_cap = f.reg(1);
284        let acc = f.load_i32(0);
285        let i = f.load_i32(0);
286        let n = f.load_i32(N);
287        f.while_lt(i, n, |f| {
288            let req = f.hop_fresh(TAG_REQ, i);
289            let reply = f.ask(server_cap, req);
290            let got = f.hop_payload(reply);
291            let sum = f.add(acc, got);
292            f.mov(acc, sum);
293            f.add_imm(i, 1);
294        });
295        let done = f.hop_fresh(TAG_REP, acc);
296        f.send(parent_cap, done);
297        f.return_(acc);
298    });
299    p.function("main", 0, |f| {
300        let server_cap = f.spawn(server, 0);
301        let me = f.self_cap();
302        let w1 = f.window(3);
303        f.mov(w1.at(1), server_cap);
304        f.mov(w1.at(2), me);
305        f.spawn_at(w1.at(0), client, 2);
306        let w2 = f.window(3);
307        f.mov(w2.at(1), server_cap);
308        f.mov(w2.at(2), me);
309        f.spawn_at(w2.at(0), client, 2);
310        let a = f.receive_match_imm(TAG_REP as u16);
311        let b = f.receive_match_imm(TAG_REP as u16);
312        let pa = f.hop_payload(a);
313        let pb = f.hop_payload(b);
314        let out = f.add(pa, pb);
315        f.return_(out);
316    });
317    p.build()
318}
319
320/// Server receives a Cap in the payload and sends `42` back through it.
321/// Exercises hop-time reissue (holder becomes the recipient).
322pub fn cap_in_payload() -> Chunk {
323    let mut p = Program::new("cap-in-payload");
324    let server = p.function("server", 0, |f| {
325        let msg = f.receive();
326        let dest = f.hop_payload(msg);
327        let payload = f.load_i32(42);
328        let reply = f.hop_fresh(TAG_REP, payload);
329        f.send(dest, reply);
330        f.exit(payload);
331    });
332    p.function("main", 0, |f| {
333        let server_cap = f.spawn(server, 0);
334        let me = f.self_cap();
335        let req = f.hop_fresh(TAG_REQ, me);
336        f.send(server_cap, req);
337        let reply = f.receive_match_imm(TAG_REP as u16);
338        let out = f.hop_payload(reply);
339        f.return_(out);
340    });
341    p.build()
342}
343
344/// Security regression: forged `make_msg` sender must not survive `Send`.
345pub fn forged_sender_send() -> Chunk {
346    let mut p = Program::new("forged-sender-send");
347    let server = p.function("server", 0, |f| {
348        let msg = f.receive();
349        let sender = f.hop_sender(msg);
350        f.send_reply(msg, TAG_REP, sender);
351        f.exit(sender);
352    });
353    p.function("main", 0, |f| {
354        let server_cap = f.spawn(server, 0);
355        let forged = f.load_i32(999);
356        let req_id = f.load_i32(1);
357        let zero = f.load_i32(0);
358        let req = f.make_msg_legacy_sender(N_MAKE_MSG, forged, req_id, TAG_REQ, zero);
359        f.send(server_cap, req);
360        let reply = f.receive();
361        let out = f.hop_payload(reply);
362        f.return_(out);
363    });
364    p.build()
365}
366
367/// Same security property as [`forged_sender_send`], via `Ask`.
368pub fn forged_sender_ask() -> Chunk {
369    let mut p = Program::new("forged-sender-ask");
370    let server = p.function("server", 0, |f| {
371        let msg = f.receive_match_imm(TAG_REQ as u16);
372        let sender = f.hop_sender(msg);
373        f.send_reply(msg, TAG_REP, sender);
374        f.exit(sender);
375    });
376    p.function("main", 0, |f| {
377        let server_cap = f.spawn(server, 0);
378        let forged = f.load_i32(999);
379        let req_id = f.load_i32(1);
380        let zero = f.load_i32(0);
381        let req = f.make_msg_legacy_sender(N_MAKE_MSG, forged, req_id, TAG_REQ, zero);
382        let reply = f.ask(server_cap, req);
383        let out = f.hop_payload(reply);
384        f.return_(out);
385    });
386    p.build()
387}
388
389/// Child sleeps then exits; parent monitors and returns the `DOWN` reason (`0` = normal).
390pub fn monitor_down() -> Chunk {
391    let mut p = Program::new("monitor-down");
392    let child = p.function("child", 0, |f| {
393        let ms = f.load_i32(40);
394        f.sleep(ms);
395        let z = f.load_i32(0);
396        f.return_(z);
397    });
398    p.function("main", 0, |f| {
399        let cap = f.spawn(child, 0);
400        let mon = f.monitor(cap);
401        let msg = f.receive_match_imm(crate::TAG_SYS_DOWN);
402        let id = f.hop_request_id(msg);
403        let ok = f.eq(id, mon);
404        let trap = f.label();
405        f.branch_if_falsy(ok, trap);
406        let out = f.hop_payload(msg);
407        f.return_(out);
408        f.bind(trap);
409        f.trap(3);
410    });
411    p.build()
412}
413
414/// Client arity 1 (`r0` = server Cap) sends two hops; server drains both.
415/// With mailbox capacity 1 the second send parks (`WAITING_SEND`).
416pub fn waiting_send() -> Chunk {
417    let mut p = Program::new("waiting-send");
418    p.function("server", 0, |f| {
419        let ms = f.load_i32(50);
420        f.sleep(ms);
421        let _first = f.receive();
422        let second = f.receive();
423        let out = f.hop_payload(second);
424        f.return_(out);
425    });
426    p.function("client", 1, |f| {
427        let server = f.reg(0);
428        let req_id = f.load_i32(1);
429        let one = f.load_i32(1);
430        let first = f.hop(req_id, TAG_REQ, one);
431        f.send(server, first);
432        let forty_two = f.load_i32(42);
433        let second = f.hop(req_id, TAG_REQ, forty_two);
434        f.send(server, second);
435        f.exit(second);
436    });
437    p.build()
438}
439
440/// Immediate `Trap` — used to show [`crate::Supervisor`] restart.
441pub fn boom() -> Chunk {
442    let mut p = Program::new("boom");
443    p.function("boom", 0, |f| f.trap(1));
444    p.build()
445}
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450    use crate::{
451        bytecode::CapId, decode, encode, std_native_table, verify, FlowOutcome, Message,
452        QuotaConfig, Runtime, RuntimeConfig, Value,
453    };
454
455    fn tiny(chunk: Chunk) -> Result<Runtime, crate::SpawnError> {
456        Runtime::with_config(
457            chunk,
458            RuntimeConfig {
459                workers: 1,
460                quantum: 10_000,
461                mailbox: crate::MailboxConfig::DEFAULT,
462                ..Default::default()
463            },
464        )
465    }
466
467    fn tiny_natives(chunk: Chunk) -> Result<Runtime, crate::SpawnError> {
468        Runtime::with_natives_and_config(
469            chunk,
470            std_native_table(),
471            RuntimeConfig {
472                workers: 1,
473                quantum: 10_000,
474                mailbox: crate::MailboxConfig::DEFAULT,
475                ..Default::default()
476            },
477        )
478    }
479
480    #[test]
481    fn add_forty_two_joins_42() -> Result<(), Box<dyn std::error::Error>> {
482        let rt = tiny(add_forty_two())?;
483        let idx = rt.function_index("main").ok_or("main")?;
484        let outcome = rt.spawn(idx, &[])?.join();
485        rt.shutdown();
486        assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(42))));
487        Ok(())
488    }
489
490    #[test]
491    fn ping_pong_joins_2() -> Result<(), Box<dyn std::error::Error>> {
492        let chunk = ping_pong();
493        assert!(verify(&chunk).is_ok());
494        let bytes = encode(&chunk);
495        let chunk = decode(&bytes)?;
496        let rt = tiny_natives(chunk)?;
497        let idx = rt.function_index("main").ok_or("main")?;
498        let outcome = rt.spawn(idx, &[])?.join();
499        let sent = rt.metrics().messages_sent;
500        rt.shutdown();
501        assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(2))));
502        assert!(sent >= 2);
503        Ok(())
504    }
505
506    #[test]
507    fn atomic_request_reply_joins_42() -> Result<(), Box<dyn std::error::Error>> {
508        let chunk = atomic_request_reply();
509        assert!(verify(&chunk).is_ok());
510        let bytes = encode(&chunk);
511        let chunk = decode(&bytes)?;
512        let rt = tiny_natives(chunk)?;
513        let idx = rt.function_index("main").ok_or("main")?;
514        let outcome = rt.spawn(idx, &[])?.join();
515        let sent = rt.metrics().messages_sent;
516        rt.shutdown();
517        assert!(
518            matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
519            "got {outcome:?}"
520        );
521        assert!(sent >= 1);
522        Ok(())
523    }
524
525    #[test]
526    fn atomic_actors_two_clients_sum_72() -> Result<(), Box<dyn std::error::Error>> {
527        let chunk = atomic_actors();
528        assert!(verify(&chunk).is_ok());
529        let bytes = encode(&chunk);
530        let chunk = decode(&bytes)?;
531        let rt = tiny_natives(chunk)?;
532        let idx = rt.function_index("main").ok_or("main")?;
533        let outcome = rt.spawn(idx, &[])?.join();
534        let sent = rt.metrics().messages_sent;
535        rt.shutdown();
536        assert!(
537            matches!(outcome, FlowOutcome::Completed(Value::Int(72))),
538            "got {outcome:?}"
539        );
540        // 8 Ask + 8 reply per client, plus 2 DONE hops.
541        assert!(sent >= 34);
542        Ok(())
543    }
544
545    #[test]
546    fn cap_in_payload_reissues_holder() -> Result<(), Box<dyn std::error::Error>> {
547        let chunk = cap_in_payload();
548        assert!(verify(&chunk).is_ok());
549        let rt = tiny_natives(chunk)?;
550        let idx = rt.function_index("main").ok_or("main")?;
551        let outcome = rt.spawn(idx, &[])?.join();
552        rt.shutdown();
553        assert!(
554            matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
555            "got {outcome:?}"
556        );
557        Ok(())
558    }
559
560    #[test]
561    fn selective_receive_skips_junk_tag() -> Result<(), Box<dyn std::error::Error>> {
562        let chunk = selective_receive();
563        assert!(verify(&chunk).is_ok());
564        let bytes = encode(&chunk);
565        let chunk = decode(&bytes)?;
566        let rt = tiny_natives(chunk)?;
567        let idx = rt.function_index("main").ok_or("main")?;
568        let outcome = rt.spawn(idx, &[])?.join();
569        rt.shutdown();
570        assert!(
571            matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
572            "got {outcome:?}"
573        );
574        Ok(())
575    }
576
577    #[test]
578    fn ask_reply_joins_42() -> Result<(), Box<dyn std::error::Error>> {
579        let chunk = ask_reply();
580        assert!(verify(&chunk).is_ok());
581        let bytes = encode(&chunk);
582        let chunk = decode(&bytes)?;
583        let rt = tiny_natives(chunk)?;
584        let idx = rt.function_index("main").ok_or("main")?;
585        let outcome = rt.spawn(idx, &[])?.join();
586        let sent = rt.metrics().messages_sent;
587        rt.shutdown();
588        assert!(
589            matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
590            "got {outcome:?}"
591        );
592        assert!(sent >= 2);
593        Ok(())
594    }
595
596    #[test]
597    fn ask_timeout_writes_unit() -> Result<(), Box<dyn std::error::Error>> {
598        let chunk = ask_timeout_expires();
599        assert!(verify(&chunk).is_ok());
600        let rt = tiny_natives(chunk)?;
601        let idx = rt.function_index("main").ok_or("main")?;
602        let outcome = rt.spawn(idx, &[])?.join();
603        rt.shutdown();
604        assert!(
605            matches!(outcome, FlowOutcome::Completed(Value::Unit)),
606            "got {outcome:?}"
607        );
608        Ok(())
609    }
610
611    #[test]
612    fn ask_target_exit_writes_sys_exit() -> Result<(), Box<dyn std::error::Error>> {
613        let chunk = ask_target_exits();
614        assert!(verify(&chunk).is_ok());
615        let rt = tiny_natives(chunk)?;
616        let idx = rt.function_index("main").ok_or("main")?;
617        let outcome = rt.spawn(idx, &[])?.join();
618        rt.shutdown();
619        assert!(
620            matches!(
621                outcome,
622                FlowOutcome::Completed(Value::Int(n)) if n == i64::from(crate::TAG_SYS_EXIT)
623            ),
624            "got {outcome:?}"
625        );
626        Ok(())
627    }
628
629    #[test]
630    fn server_loop_joins_42() -> Result<(), Box<dyn std::error::Error>> {
631        let chunk = server_loop();
632        assert!(verify(&chunk).is_ok());
633        let rt = tiny_natives(chunk)?;
634        let idx = rt.function_index("main").ok_or("main")?;
635        let outcome = rt.spawn(idx, &[])?.join();
636        rt.shutdown();
637        assert!(
638            matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
639            "got {outcome:?}"
640        );
641        Ok(())
642    }
643
644    #[test]
645    fn send_overwrites_forged_sender() -> Result<(), Box<dyn std::error::Error>> {
646        let chunk = forged_sender_send();
647        assert!(verify(&chunk).is_ok());
648        let rt = tiny_natives(chunk)?;
649        let idx = rt.function_index("main").ok_or("main")?;
650        let outcome = rt.spawn(idx, &[])?.join();
651        rt.shutdown();
652        match outcome {
653            FlowOutcome::Completed(Value::Pid(n)) => {
654                assert_ne!(n, 999, "forged make_msg sender must not survive Send");
655                assert!(n >= 1, "authenticated sender must be a live flow id");
656                Ok(())
657            }
658            other => Err(format!("expected Completed(Pid), got {other:?}").into()),
659        }
660    }
661
662    #[test]
663    fn ask_overwrites_forged_request_sender() -> Result<(), Box<dyn std::error::Error>> {
664        let chunk = forged_sender_ask();
665        assert!(verify(&chunk).is_ok());
666        let rt = tiny_natives(chunk)?;
667        let idx = rt.function_index("main").ok_or("main")?;
668        let outcome = rt.spawn(idx, &[])?.join();
669        rt.shutdown();
670        match outcome {
671            FlowOutcome::Completed(Value::Pid(n)) => {
672                assert_ne!(n, 999, "forged make_msg sender must not survive Ask");
673                assert!(n >= 1, "authenticated sender must be a live flow id");
674                Ok(())
675            }
676            other => Err(format!("expected Completed(Pid), got {other:?}").into()),
677        }
678    }
679
680    #[test]
681    fn send_scalar_target_traps() -> Result<(), Box<dyn std::error::Error>> {
682        let mut p = Program::new("bad-cap-target");
683        p.function("main", 0, |f| {
684            let bad_cap = f.load_i32(99);
685            let req_id = f.load_i32(1);
686            let payload = f.load_i32(1);
687            let msg = f.hop(req_id, TAG_PING, payload);
688            f.send(bad_cap, msg);
689            f.return_(msg);
690        });
691        let rt = tiny_natives(p.build())?;
692        let outcome = rt.spawn(0, &[])?.join();
693        rt.shutdown();
694        assert!(
695            matches!(outcome, FlowOutcome::Failed(_)),
696            "non-Cap Send target must fail, got {outcome:?}"
697        );
698        Ok(())
699    }
700
701    #[test]
702    fn send_scalar_is_not_an_atomic_hop() -> Result<(), Box<dyn std::error::Error>> {
703        let mut p = Program::new("bad-hop");
704        p.function("main", 0, |f| {
705            let cap = f.self_cap();
706            let scalar = f.load_i32(99);
707            f.send(cap, scalar);
708            f.return_(scalar);
709        });
710        let rt = tiny(p.build())?;
711        let outcome = rt.spawn(0, &[])?.join();
712        rt.shutdown();
713        assert!(
714            matches!(outcome, FlowOutcome::Failed(_)),
715            "scalar Send must trap, got {outcome:?}"
716        );
717        Ok(())
718    }
719
720    #[test]
721    fn monitor_down_joins_normal_reason() -> Result<(), Box<dyn std::error::Error>> {
722        let chunk = monitor_down();
723        assert!(verify(&chunk).is_ok());
724        let rt = tiny_natives(chunk)?;
725        let idx = rt.function_index("main").ok_or("main")?;
726        let outcome = rt.spawn(idx, &[])?.join();
727        rt.shutdown();
728        assert!(
729            matches!(outcome, FlowOutcome::Completed(Value::Int(0))),
730            "DOWN reason should be Normal (0), got {outcome:?}"
731        );
732        Ok(())
733    }
734
735    #[test]
736    fn waiting_send_second_hop_arrives() -> Result<(), Box<dyn std::error::Error>> {
737        let cap = crate::MailboxCapacity::new(1).ok_or("cap")?;
738        let rt = Runtime::with_natives_and_config(
739            waiting_send(),
740            std_native_table(),
741            RuntimeConfig {
742                workers: 1,
743                quantum: 10_000,
744                mailbox: crate::MailboxConfig::new(cap, crate::OverflowPolicy::Reject),
745                ..Default::default()
746            },
747        )?;
748        let server = rt.function_index("server").ok_or("server")?;
749        let client = rt.function_index("client").ok_or("client")?;
750        let server_h = rt.spawn(server, &[])?;
751        let server_cap = rt.mint_cap(server_h.id())?;
752        rt.spawn(client, &[Value::Cap(server_cap)])?;
753        let outcome = server_h.join();
754        rt.shutdown();
755        assert!(
756            matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
757            "second hop should be admitted after the first pop, got {outcome:?}"
758        );
759        Ok(())
760    }
761
762    #[test]
763    fn link_kills_peer_on_fault() -> Result<(), Box<dyn std::error::Error>> {
764        let mut p = Program::new("link-kill");
765        p.function("park", 0, |f| {
766            let _ = f.receive();
767            f.trap(9);
768        });
769        p.function("boom", 0, |f| f.trap(1));
770        let rt = tiny(p.build())?;
771        let park = rt.function_index("park").ok_or("park")?;
772        let boom = rt.function_index("boom").ok_or("boom")?;
773        let parked = rt.spawn(park, &[])?;
774        let killer = rt.spawn(boom, &[])?;
775        rt.link(parked.id(), killer.id())?;
776        let boom_out = killer.join();
777        let park_out = parked.join();
778        rt.shutdown();
779        assert!(matches!(boom_out, FlowOutcome::Failed(_)), "{boom_out:?}");
780        assert!(
781            matches!(park_out, FlowOutcome::Failed(_)),
782            "linked peer must die on fault, got {park_out:?}"
783        );
784        Ok(())
785    }
786
787    #[test]
788    fn linked_exit_down_carries_link_reason() -> Result<(), Box<dyn std::error::Error>> {
789        let mut p = Program::new("link-down-reason");
790        p.function("watcher", 0, |f| {
791            let msg = f.receive_match_imm(crate::TAG_SYS_DOWN);
792            let out = f.hop_payload(msg);
793            f.return_(out);
794        });
795        p.function("park", 0, |f| {
796            let _ = f.receive();
797            f.trap(9);
798        });
799        p.function("boom", 0, |f| f.trap(1));
800        let rt = tiny_natives(p.build())?;
801        let watcher = rt.spawn(rt.function_index("watcher").ok_or("watcher")?, &[])?;
802        let parked = rt.spawn(rt.function_index("park").ok_or("park")?, &[])?;
803        let killer = rt.spawn(rt.function_index("boom").ok_or("boom")?, &[])?;
804        rt.monitor(watcher.id(), parked.id())?;
805        rt.link(parked.id(), killer.id())?;
806        let _ = killer.join();
807        let watched = watcher.join();
808        let _ = parked.join();
809        rt.shutdown();
810        assert!(
811            matches!(
812                watched,
813                FlowOutcome::Completed(Value::Int(n)) if n == crate::FlowExitReason::Link.as_u64() as i64
814            ),
815            "DOWN payload must be Link, got {watched:?}"
816        );
817        Ok(())
818    }
819
820    #[test]
821    fn monitor_dead_owner_is_rejected() -> Result<(), Box<dyn std::error::Error>> {
822        let rt = tiny(add_forty_two())?;
823        let first = rt.spawn(0, &[])?;
824        let dead = first.id();
825        let done = first.join();
826        assert!(matches!(done, FlowOutcome::Completed(_)));
827        let live = rt.spawn(0, &[])?;
828        let err = rt.monitor(dead, live.id());
829        live.join();
830        rt.shutdown();
831        assert!(
832            matches!(err, Err(crate::LifecycleError::NoSuchFlow(_))),
833            "{err:?}"
834        );
835        Ok(())
836    }
837
838    #[test]
839    fn forged_cap_cannot_be_registered() -> Result<(), Box<dyn std::error::Error>> {
840        let rt = tiny(add_forty_two())?;
841        let err = rt.register_name("svc", CapId::from_raw(99_999));
842        rt.shutdown();
843        assert_eq!(err, Err(crate::LifecycleError::InvalidCapability));
844        Ok(())
845    }
846
847    #[test]
848    fn bytecode_register_name_joins() -> Result<(), Box<dyn std::error::Error>> {
849        let mut p = Program::new("reg-self");
850        p.function("main", 0, |f| {
851            let name = f.load_str("svc");
852            f.register_name(name);
853            let z = f.load_i32(1);
854            f.return_(z);
855        });
856        let rt = tiny(p.build())?;
857        let outcome = rt.spawn(0, &[])?.join();
858        rt.shutdown();
859        assert!(
860            matches!(outcome, FlowOutcome::Completed(Value::Int(1))),
861            "host-spawned flow must be able to register_name, got {outcome:?}"
862        );
863        Ok(())
864    }
865
866    #[test]
867    fn named_service_whereis_returns_cap() -> Result<(), Box<dyn std::error::Error>> {
868        let chunk = named_service();
869        assert!(verify(&chunk).is_ok());
870        let rt = tiny_natives(chunk)?;
871        let idx = rt.function_index("main").ok_or("main")?;
872        let outcome = rt
873            .spawn(idx, &[])?
874            .join_timeout(std::time::Duration::from_secs(2))
875            .ok_or("named_service timed out")?;
876        rt.shutdown();
877        assert!(
878            matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
879            "named whereis should yield a usable SEND Cap, got {outcome:?}"
880        );
881        Ok(())
882    }
883
884    #[test]
885    fn confined_spawn_cannot_register_name() -> Result<(), Box<dyn std::error::Error>> {
886        let mut p = Program::new("confined-reg");
887        let child = p.function("child", 0, |f| {
888            let name = f.load_str("stolen");
889            f.register_name(name);
890            // Stay alive so a successful register would remain visible.
891            // Sleep does not require RECV (a confined child has no RECV).
892            let ms = f.load_i32(5_000);
893            f.sleep(ms);
894            let z = f.load_i32(1);
895            f.return_(z);
896        });
897        p.function("main", 0, |f| {
898            let _c = f.spawn_confined(child, 0);
899            let ms = f.load_i32(80);
900            f.sleep(ms);
901            let name = f.load_str("stolen");
902            let cap = f.whereis(name);
903            let ok = f.label();
904            f.branch_if_falsy(cap, ok);
905            let one = f.load_i32(1);
906            f.return_(one);
907            f.bind(ok);
908            let zero = f.load_i32(0);
909            f.return_(zero);
910        });
911        let rt = tiny(p.build())?;
912        let idx = rt.function_index("main").ok_or("main")?;
913        let outcome = rt.spawn(idx, &[])?.join();
914        rt.shutdown();
915        assert!(
916            matches!(outcome, FlowOutcome::Completed(Value::Int(0))),
917            "confined child must not publish a name, got {outcome:?}"
918        );
919        Ok(())
920    }
921
922    #[test]
923    fn heap_quota_charges_str_on_register_store() -> Result<(), Box<dyn std::error::Error>> {
924        let mut p = Program::new("heap-str");
925        p.function("main", 0, |f| {
926            let s = f.load_str("x".repeat(200));
927            f.return_(s);
928        });
929        let mut quota = QuotaConfig::permissive();
930        quota.mem_limit = 64;
931        let rt = Runtime::with_config(
932            p.build(),
933            RuntimeConfig {
934                workers: 1,
935                quantum: 10_000,
936                mailbox: crate::MailboxConfig::DEFAULT,
937                quota,
938                ..Default::default()
939            },
940        )?;
941        let outcome = rt.spawn(0, &[])?.join();
942        rt.shutdown();
943        assert!(
944            matches!(outcome, FlowOutcome::Failed(_)),
945            "200-byte Str must exceed 64-byte heap quota, got {outcome:?}"
946        );
947        Ok(())
948    }
949
950    #[test]
951    fn host_send_stamps_sender_zero_and_mints_request_id() -> Result<(), Box<dyn std::error::Error>>
952    {
953        let mut p = Program::new("host-send");
954        p.function("main", 0, |f| {
955            let msg = f.receive();
956            let sender = f.hop_sender(msg);
957            let rid = f.hop_request_id(msg);
958            let miss = f.label();
959            f.branch_if_falsy(rid, miss);
960            f.return_(sender);
961            f.bind(miss);
962            let neg = f.load_i32(-1);
963            f.return_(neg);
964        });
965        let rt = tiny_natives(p.build())?;
966        let h = rt.spawn(0, &[])?;
967        rt.send(h.id(), Value::Message(Message::request(0, 1, 0)))?;
968        let outcome = h.join();
969        rt.shutdown();
970        assert!(
971            matches!(outcome, FlowOutcome::Completed(Value::Pid(0))),
972            "host send must stamp sender=0 (Pid) and mint request_id, got {outcome:?}"
973        );
974        Ok(())
975    }
976
977    #[test]
978    fn registry_clears_on_exit() -> Result<(), Box<dyn std::error::Error>> {
979        let mut p = Program::new("reg");
980        p.function("main", 0, |f| {
981            let ms = f.load_i32(80);
982            f.sleep(ms);
983            let z = f.load_i32(1);
984            f.return_(z);
985        });
986        let rt = tiny(p.build())?;
987        let h = rt.spawn(0, &[])?;
988        let cap = rt.mint_cap(h.id())?;
989        rt.register_name("svc", cap)?;
990        assert_eq!(rt.whereis("svc")?, Some(cap));
991        let _ = h.join();
992        assert_eq!(rt.whereis("svc")?, None);
993        rt.shutdown();
994        Ok(())
995    }
996}