1use crate::{emit_native1_from, emit_native_n, Chunk, ChunkBuilder, Opcode};
20
21const N_PRINT: u32 = 0;
26const N_MAKE_MSG: u32 = 2;
27const N_MSG_SENDER: u32 = 3;
28const N_MSG_REQUEST_ID: u32 = 4;
29const N_MSG_TAG: u32 = 5;
30const N_MSG_PAYLOAD: u32 = 6;
31const N_MSG_REPLY_CAP: u32 = 7;
32
33pub const TAG_REQ: i32 = 1;
37pub const TAG_REP: i32 = 2;
38pub const TAG_PING: i32 = 10;
39pub const TAG_PONG: i32 = 11;
40pub const TAG_JUNK: i32 = 99;
42
43pub fn add_forty_two() -> Chunk {
45 let mut b = ChunkBuilder::new("add-forty-two");
46 b.begin_function("main", 0, 2);
47 b.emit_load_imm(0, 41);
48 b.emit_load_imm(1, 1);
49 b.emit_binop(Opcode::Add, 0, 0, 1);
50 b.emit_return(0);
51 b.finish()
52}
53
54pub fn ping_pong() -> Chunk {
63 let mut b = ChunkBuilder::new("ping-pong");
64
65 let pong = b.begin_function("pong", 0, 8);
72 b.emit_receive(0);
73 emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
74 emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
75 emit_native1_from!(b, 3, 0, N_MSG_PAYLOAD);
76 b.emit_load_imm(7, 1);
77 b.emit_binop(Opcode::Add, 3, 3, 7);
78 b.emit_self_pid(4);
79 b.emit_move(5, 2);
80 b.emit_load_imm(6, TAG_PONG);
81 b.emit_move(7, 3);
82 emit_native_n!(b, 4, N_MAKE_MSG, 4);
83 b.emit_send(1, 4);
84 b.emit_exit(4);
85
86 b.begin_function("main", 0, 8);
88 b.emit_self_pid(1);
89 b.emit_spawn(0, pong, 0);
90 b.emit_move(2, 1);
91 b.emit_load_imm(3, 1);
92 b.emit_load_imm(4, TAG_PING);
93 b.emit_load_imm(5, 1);
94 emit_native_n!(b, 2, N_MAKE_MSG, 4);
95 b.emit_send(0, 2);
96 b.emit_receive(6);
97 emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
98 b.emit_return(4);
99
100 b.finish()
101}
102
103pub fn atomic_request_reply() -> Chunk {
129 let mut b = ChunkBuilder::new("atomic-request-reply");
130
131 let server = b.begin_function("server", 0, 8);
141 b.emit_receive(0);
142 emit_native1_from!(b, 7, 0, N_PRINT);
143 emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
144 emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
145 emit_native1_from!(b, 3, 0, N_MSG_TAG);
146 emit_native1_from!(b, 4, 0, N_MSG_PAYLOAD);
147 b.emit_load_imm(7, 1);
148 b.emit_binop(Opcode::Add, 4, 4, 7);
149 b.emit_self_pid(5);
150 b.emit_move(7, 4);
151 b.emit_move(4, 5);
152 b.emit_move(5, 2);
153 b.emit_load_imm(6, TAG_REP);
154 emit_native_n!(b, 4, N_MAKE_MSG, 4);
155 emit_native1_from!(b, 7, 4, N_PRINT);
156 b.emit_send(1, 4);
157 b.emit_exit(4);
158
159 b.begin_function("main", 0, 8);
166 b.emit_self_pid(1);
167 b.emit_spawn(0, server, 0);
168 b.emit_move(2, 1);
169 b.emit_load_imm(3, 1);
170 b.emit_load_imm(4, TAG_REQ);
171 b.emit_load_imm(5, 41);
172 emit_native_n!(b, 2, N_MAKE_MSG, 4);
173 emit_native1_from!(b, 7, 2, N_PRINT);
174 b.emit_send(0, 2);
175 b.emit_receive(6);
176 emit_native1_from!(b, 7, 6, N_PRINT);
177 emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
178 b.emit_return(4);
179
180 b.finish()
181}
182
183pub fn selective_receive() -> Chunk {
193 let mut b = ChunkBuilder::new("selective-receive");
194
195 let server = b.begin_function("server", 0, 8);
197 b.emit_receive_match_imm(0, TAG_REQ as u16);
198 emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
199 emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
200 emit_native1_from!(b, 4, 0, N_MSG_PAYLOAD);
201 b.emit_load_imm(7, 1);
202 b.emit_binop(Opcode::Add, 4, 4, 7);
203 b.emit_self_pid(5);
204 b.emit_move(7, 4);
205 b.emit_move(4, 5);
206 b.emit_move(5, 2);
207 b.emit_load_imm(6, TAG_REP);
208 emit_native_n!(b, 4, N_MAKE_MSG, 4);
209 b.emit_send(1, 4);
210 b.emit_receive(0);
212 emit_native1_from!(b, 3, 0, N_MSG_TAG);
213 b.emit_load_imm(7, TAG_JUNK);
214 b.emit_binop(Opcode::Eq, 3, 3, 7);
215 let trap_lbl = b.new_label();
217 b.emit_branch(3, trap_lbl);
218 b.emit_exit(4);
219 b.bind_label(trap_lbl);
220 b.emit_trap(2);
221
222 b.begin_function("main", 0, 8);
223 b.emit_self_pid(1);
224 b.emit_spawn(0, server, 0);
225 b.emit_move(2, 1);
227 b.emit_load_imm(3, 1);
228 b.emit_load_imm(4, TAG_JUNK);
229 b.emit_load_imm(5, 0);
230 emit_native_n!(b, 2, N_MAKE_MSG, 4);
231 b.emit_send(0, 2);
232 b.emit_move(2, 1);
234 b.emit_load_imm(3, 1);
235 b.emit_load_imm(4, TAG_REQ);
236 b.emit_load_imm(5, 41);
237 emit_native_n!(b, 2, N_MAKE_MSG, 4);
238 b.emit_send(0, 2);
239 b.emit_receive_match_imm(6, TAG_REP as u16);
240 emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
241 b.emit_return(4);
242
243 b.finish()
244}
245
246pub fn ask_reply() -> Chunk {
255 let mut b = ChunkBuilder::new("ask-reply");
256
257 let server = b.begin_function("server", 0, 8);
258 b.emit_receive_match_imm(0, TAG_REQ as u16);
259 emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
260 emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
261 emit_native1_from!(b, 4, 0, N_MSG_PAYLOAD);
262 b.emit_load_imm(7, 1);
263 b.emit_binop(Opcode::Add, 4, 4, 7);
264 b.emit_self_pid(5);
265 b.emit_move(7, 4);
266 b.emit_move(4, 5);
267 b.emit_move(5, 2);
268 b.emit_load_imm(6, TAG_REP);
269 emit_native_n!(b, 4, N_MAKE_MSG, 4);
270 b.emit_send(1, 4);
271 b.emit_exit(4);
272
273 b.begin_function("main", 0, 8);
275 b.emit_self_pid(1);
276 b.emit_spawn(0, server, 0);
277 b.emit_move(2, 1);
278 b.emit_load_imm(3, 1);
279 b.emit_load_imm(4, TAG_REQ);
280 b.emit_load_imm(5, 41);
281 emit_native_n!(b, 2, N_MAKE_MSG, 4);
282 b.emit_ask(6, 0, 2);
283 emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
284 b.emit_return(4);
285
286 b.finish()
287}
288
289pub fn forged_sender_send() -> Chunk {
309 let mut b = ChunkBuilder::new("forged-sender-send");
310
311 let server = b.begin_function("server", 0, 8);
312 b.emit_receive(0);
313 emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
314 emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
315 emit_native1_from!(b, 3, 0, N_MSG_SENDER);
316 b.emit_self_pid(4);
318 b.emit_move(5, 2);
319 b.emit_load_imm(6, TAG_REP);
320 b.emit_move(7, 3);
321 emit_native_n!(b, 4, N_MAKE_MSG, 4);
322 b.emit_send(1, 4);
323 b.emit_exit(4);
324
325 b.begin_function("main", 0, 8);
326 b.emit_self_pid(1);
327 b.emit_spawn(0, server, 0);
328 b.emit_load_imm(2, 999);
330 b.emit_load_imm(3, 1);
331 b.emit_load_imm(4, TAG_REQ);
332 b.emit_load_imm(5, 0);
333 emit_native_n!(b, 2, N_MAKE_MSG, 4);
334 b.emit_send(0, 2);
335 b.emit_receive(6);
336 emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
337 b.emit_return(4);
338
339 b.finish()
340}
341
342pub fn forged_sender_ask() -> Chunk {
349 let mut b = ChunkBuilder::new("forged-sender-ask");
350
351 let server = b.begin_function("server", 0, 8);
352 b.emit_receive_match_imm(0, TAG_REQ as u16);
353 emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
354 emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
355 emit_native1_from!(b, 3, 0, N_MSG_SENDER);
356 b.emit_self_pid(4);
357 b.emit_move(5, 2);
358 b.emit_load_imm(6, TAG_REP);
359 b.emit_move(7, 3);
360 emit_native_n!(b, 4, N_MAKE_MSG, 4);
361 b.emit_send(1, 4);
362 b.emit_exit(4);
363
364 b.begin_function("main", 0, 8);
365 b.emit_self_pid(1);
366 b.emit_spawn(0, server, 0);
367 b.emit_load_imm(2, 999);
368 b.emit_load_imm(3, 1);
369 b.emit_load_imm(4, TAG_REQ);
370 b.emit_load_imm(5, 0);
371 emit_native_n!(b, 2, N_MAKE_MSG, 4);
372 b.emit_ask(6, 0, 2);
373 emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
374 b.emit_return(4);
375
376 b.finish()
377}
378
379pub fn boom() -> Chunk {
381 let mut b = ChunkBuilder::new("boom");
382 b.begin_function("boom", 0, 1);
383 b.emit_trap(1);
384 b.finish()
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390 use crate::{
391 decode, encode, std_native_table, verify, FlowOutcome, Runtime, RuntimeConfig, Value,
392 };
393
394 fn tiny(chunk: Chunk) -> Result<Runtime, crate::SpawnError> {
395 Runtime::with_config(
396 chunk,
397 RuntimeConfig {
398 workers: 1,
399 quantum: 10_000,
400 mailbox: crate::MailboxConfig::DEFAULT,
401 },
402 )
403 }
404
405 fn tiny_natives(chunk: Chunk) -> Result<Runtime, crate::SpawnError> {
406 Runtime::with_natives_and_config(
407 chunk,
408 std_native_table(),
409 RuntimeConfig {
410 workers: 1,
411 quantum: 10_000,
412 mailbox: crate::MailboxConfig::DEFAULT,
413 },
414 )
415 }
416
417 #[test]
418 fn add_forty_two_joins_42() -> Result<(), Box<dyn std::error::Error>> {
419 let rt = tiny(add_forty_two())?;
420 let idx = rt.function_index("main").ok_or("main")?;
421 let outcome = rt.spawn(idx, &[])?.join();
422 rt.shutdown();
423 assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(42))));
424 Ok(())
425 }
426
427 #[test]
428 fn ping_pong_joins_2() -> Result<(), Box<dyn std::error::Error>> {
429 let chunk = ping_pong();
430 assert!(verify(&chunk).is_ok());
431 let bytes = encode(&chunk);
432 let chunk = decode(&bytes)?;
433 let rt = tiny_natives(chunk)?;
434 let idx = rt.function_index("main").ok_or("main")?;
435 let outcome = rt.spawn(idx, &[])?.join();
436 let sent = rt.metrics().messages_sent;
437 rt.shutdown();
438 assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(2))));
439 assert!(sent >= 2);
440 Ok(())
441 }
442
443 #[test]
444 fn atomic_request_reply_joins_42() -> Result<(), Box<dyn std::error::Error>> {
445 let chunk = atomic_request_reply();
446 assert!(verify(&chunk).is_ok());
447 let bytes = encode(&chunk);
448 let chunk = decode(&bytes)?;
449 let rt = tiny_natives(chunk)?;
450 let idx = rt.function_index("main").ok_or("main")?;
451 let outcome = rt.spawn(idx, &[])?.join();
452 let sent = rt.metrics().messages_sent;
453 rt.shutdown();
454 assert!(
455 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
456 "got {outcome:?}"
457 );
458 assert!(sent >= 1);
459 Ok(())
460 }
461
462 #[test]
463 fn selective_receive_skips_junk_tag() -> Result<(), Box<dyn std::error::Error>> {
464 let chunk = selective_receive();
465 assert!(verify(&chunk).is_ok());
466 let bytes = encode(&chunk);
467 let chunk = decode(&bytes)?;
468 let rt = tiny_natives(chunk)?;
469 let idx = rt.function_index("main").ok_or("main")?;
470 let outcome = rt.spawn(idx, &[])?.join();
471 rt.shutdown();
472 assert!(
473 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
474 "got {outcome:?}"
475 );
476 Ok(())
477 }
478
479 #[test]
480 fn ask_reply_joins_42() -> Result<(), Box<dyn std::error::Error>> {
481 let chunk = ask_reply();
482 assert!(verify(&chunk).is_ok());
483 let bytes = encode(&chunk);
484 let chunk = decode(&bytes)?;
485 let rt = tiny_natives(chunk)?;
486 let idx = rt.function_index("main").ok_or("main")?;
487 let outcome = rt.spawn(idx, &[])?.join();
488 let sent = rt.metrics().messages_sent;
489 rt.shutdown();
490 assert!(
491 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
492 "got {outcome:?}"
493 );
494 assert!(sent >= 2);
496 Ok(())
497 }
498
499 #[test]
500 fn send_overwrites_forged_sender() -> Result<(), Box<dyn std::error::Error>> {
501 let chunk = forged_sender_send();
503 assert!(verify(&chunk).is_ok());
504 let rt = tiny_natives(chunk)?;
505 let idx = rt.function_index("main").ok_or("main")?;
506 let outcome = rt.spawn(idx, &[])?.join();
507 rt.shutdown();
508 match outcome {
509 FlowOutcome::Completed(Value::Int(n)) => {
510 assert_ne!(n, 999, "forged make_msg sender must not survive Send");
511 assert!(n >= 1, "authenticated sender must be a live flow id");
512 Ok(())
513 }
514 other => Err(format!("expected Completed(Int), got {other:?}").into()),
515 }
516 }
517
518 #[test]
519 fn ask_overwrites_forged_request_sender() -> Result<(), Box<dyn std::error::Error>> {
520 let chunk = forged_sender_ask();
522 assert!(verify(&chunk).is_ok());
523 let rt = tiny_natives(chunk)?;
524 let idx = rt.function_index("main").ok_or("main")?;
525 let outcome = rt.spawn(idx, &[])?.join();
526 rt.shutdown();
527 match outcome {
528 FlowOutcome::Completed(Value::Int(n)) => {
529 assert_ne!(n, 999, "forged make_msg sender must not survive Ask");
530 assert!(n >= 1, "authenticated sender must be a live flow id");
531 Ok(())
532 }
533 other => Err(format!("expected Completed(Int), got {other:?}").into()),
534 }
535 }
536
537 #[test]
538 fn send_scalar_target_traps() -> Result<(), Box<dyn std::error::Error>> {
539 let mut b = ChunkBuilder::new("bad-cap-target");
540 b.begin_function("main", 0, 6);
541 b.emit_load_imm(0, 99); b.emit_load_imm(1, 0);
543 b.emit_load_imm(2, 1);
544 b.emit_load_imm(3, TAG_PING);
545 b.emit_load_imm(4, 1);
546 emit_native_n!(b, 1, N_MAKE_MSG, 4);
547 b.emit_send(0, 1);
548 b.emit_return(1);
549 let rt = tiny_natives(b.finish())?;
550 let outcome = rt.spawn(0, &[])?.join();
551 rt.shutdown();
552 assert!(
553 matches!(outcome, FlowOutcome::Failed(_)),
554 "non-Cap Send target must fail, got {outcome:?}"
555 );
556 Ok(())
557 }
558
559 #[test]
560 fn send_scalar_is_not_an_atomic_hop() -> Result<(), Box<dyn std::error::Error>> {
561 let mut b = ChunkBuilder::new("bad-hop");
562 b.begin_function("main", 0, 2);
563 b.emit_self_pid(0);
564 b.emit_load_imm(1, 99);
565 b.emit_send(0, 1);
566 b.emit_return(1);
567 let rt = tiny(b.finish())?;
568 let outcome = rt.spawn(0, &[])?.join();
569 rt.shutdown();
570 assert!(
571 matches!(outcome, FlowOutcome::Failed(_)),
572 "scalar Send must trap, got {outcome:?}"
573 );
574 Ok(())
575 }
576}