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) -> Runtime {
395 Runtime::with_config(
396 chunk,
397 RuntimeConfig {
398 workers: 1,
399 quantum: 10_000,
400 },
401 )
402 .expect("runtime")
403 }
404
405 fn tiny_natives(chunk: Chunk) -> Runtime {
406 Runtime::with_natives_and_config(
407 chunk,
408 std_native_table(),
409 RuntimeConfig {
410 workers: 1,
411 quantum: 10_000,
412 },
413 )
414 .expect("runtime")
415 }
416
417 #[test]
418 fn add_forty_two_joins_42() {
419 let rt = tiny(add_forty_two());
420 let idx = rt.function_index("main").expect("main");
421 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
422 rt.shutdown();
423 assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(42))));
424 }
425
426 #[test]
427 fn ping_pong_joins_2() {
428 let chunk = ping_pong();
429 assert!(verify(&chunk).is_ok());
430 let bytes = encode(&chunk);
431 let chunk = decode(&bytes).expect("decode");
432 let rt = tiny_natives(chunk);
433 let idx = rt.function_index("main").expect("main");
434 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
435 let sent = rt.metrics().messages_sent;
436 rt.shutdown();
437 assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(2))));
438 assert!(sent >= 2);
439 }
440
441 #[test]
442 fn atomic_request_reply_joins_42() {
443 let chunk = atomic_request_reply();
444 assert!(verify(&chunk).is_ok());
445 let bytes = encode(&chunk);
446 let chunk = decode(&bytes).expect("decode");
447 let rt = tiny_natives(chunk);
448 let idx = rt.function_index("main").expect("main");
449 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
450 let sent = rt.metrics().messages_sent;
451 rt.shutdown();
452 assert!(
453 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
454 "got {outcome:?}"
455 );
456 assert!(sent >= 1);
457 }
458
459 #[test]
460 fn selective_receive_skips_junk_tag() {
461 let chunk = selective_receive();
462 assert!(verify(&chunk).is_ok());
463 let bytes = encode(&chunk);
464 let chunk = decode(&bytes).expect("decode");
465 let rt = tiny_natives(chunk);
466 let idx = rt.function_index("main").expect("main");
467 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
468 rt.shutdown();
469 assert!(
470 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
471 "got {outcome:?}"
472 );
473 }
474
475 #[test]
476 fn ask_reply_joins_42() {
477 let chunk = ask_reply();
478 assert!(verify(&chunk).is_ok());
479 let bytes = encode(&chunk);
480 let chunk = decode(&bytes).expect("decode");
481 let rt = tiny_natives(chunk);
482 let idx = rt.function_index("main").expect("main");
483 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
484 let sent = rt.metrics().messages_sent;
485 rt.shutdown();
486 assert!(
487 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
488 "got {outcome:?}"
489 );
490 assert!(sent >= 2);
492 }
493
494 #[test]
495 fn send_overwrites_forged_sender() {
496 let chunk = forged_sender_send();
498 assert!(verify(&chunk).is_ok());
499 let rt = tiny_natives(chunk);
500 let idx = rt.function_index("main").expect("main");
501 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
502 rt.shutdown();
503 match outcome {
504 FlowOutcome::Completed(Value::Int(n)) => {
505 assert_ne!(n, 999, "forged make_msg sender must not survive Send");
506 assert!(n >= 1, "authenticated sender must be a live flow id");
507 }
508 other => panic!("expected Completed(Int), got {other:?}"),
509 }
510 }
511
512 #[test]
513 fn ask_overwrites_forged_request_sender() {
514 let chunk = forged_sender_ask();
516 assert!(verify(&chunk).is_ok());
517 let rt = tiny_natives(chunk);
518 let idx = rt.function_index("main").expect("main");
519 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
520 rt.shutdown();
521 match outcome {
522 FlowOutcome::Completed(Value::Int(n)) => {
523 assert_ne!(n, 999, "forged make_msg sender must not survive Ask");
524 assert!(n >= 1, "authenticated sender must be a live flow id");
525 }
526 other => panic!("expected Completed(Int), got {other:?}"),
527 }
528 }
529
530 #[test]
531 fn send_scalar_target_traps() {
532 let mut b = ChunkBuilder::new("bad-cap-target");
533 b.begin_function("main", 0, 6);
534 b.emit_load_imm(0, 99); b.emit_load_imm(1, 0);
536 b.emit_load_imm(2, 1);
537 b.emit_load_imm(3, TAG_PING);
538 b.emit_load_imm(4, 1);
539 emit_native_n!(b, 1, N_MAKE_MSG, 4);
540 b.emit_send(0, 1);
541 b.emit_return(1);
542 let rt = tiny_natives(b.finish());
543 let outcome = rt.spawn(0, &[]).expect("spawn").join();
544 rt.shutdown();
545 assert!(
546 matches!(outcome, FlowOutcome::Failed(_)),
547 "non-Cap Send target must fail, got {outcome:?}"
548 );
549 }
550
551 #[test]
552 fn send_scalar_is_not_an_atomic_hop() {
553 let mut b = ChunkBuilder::new("bad-hop");
554 b.begin_function("main", 0, 2);
555 b.emit_self_pid(0);
556 b.emit_load_imm(1, 99);
557 b.emit_send(0, 1);
558 b.emit_return(1);
559 let rt = tiny(b.finish());
560 let outcome = rt.spawn(0, &[]).expect("spawn").join();
561 rt.shutdown();
562 assert!(
563 matches!(outcome, FlowOutcome::Failed(_)),
564 "scalar Send must trap, got {outcome:?}"
565 );
566 }
567}