1use crate::{emit_native1_from, emit_native_n, Chunk, ChunkBuilder, Opcode};
8
9const N_PRINT: u32 = 0;
14const N_MAKE_MSG: u32 = 2;
15const N_MSG_SENDER: u32 = 3;
16const N_MSG_REQUEST_ID: u32 = 4;
17const N_MSG_TAG: u32 = 5;
18const N_MSG_PAYLOAD: u32 = 6;
19const N_MSG_REPLY_CAP: u32 = 7;
20
21pub const TAG_REQ: i32 = 1;
25pub const TAG_REP: i32 = 2;
26pub const TAG_PING: i32 = 10;
27pub const TAG_PONG: i32 = 11;
28pub const TAG_JUNK: i32 = 99;
30
31pub fn add_forty_two() -> Chunk {
33 let mut b = ChunkBuilder::new("add-forty-two");
34 b.begin_function("main", 0, 2);
35 b.emit_load_imm(0, 41);
36 b.emit_load_imm(1, 1);
37 b.emit_binop(Opcode::Add, 0, 0, 1);
38 b.emit_return(0);
39 b.finish()
40}
41
42pub fn ping_pong() -> Chunk {
51 let mut b = ChunkBuilder::new("ping-pong");
52
53 let pong = b.begin_function("pong", 0, 8);
60 b.emit_receive(0);
61 emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
62 emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
63 emit_native1_from!(b, 3, 0, N_MSG_PAYLOAD);
64 b.emit_load_imm(7, 1);
65 b.emit_binop(Opcode::Add, 3, 3, 7);
66 b.emit_self_pid(4);
67 b.emit_move(5, 2);
68 b.emit_load_imm(6, TAG_PONG);
69 b.emit_move(7, 3);
70 emit_native_n!(b, 4, N_MAKE_MSG, 4);
71 b.emit_send(1, 4);
72 b.emit_exit(4);
73
74 b.begin_function("main", 0, 8);
76 b.emit_self_pid(1);
77 b.emit_spawn(0, pong, 0);
78 b.emit_move(2, 1);
79 b.emit_load_imm(3, 1);
80 b.emit_load_imm(4, TAG_PING);
81 b.emit_load_imm(5, 1);
82 emit_native_n!(b, 2, N_MAKE_MSG, 4);
83 b.emit_send(0, 2);
84 b.emit_receive(6);
85 emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
86 b.emit_return(4);
87
88 b.finish()
89}
90
91pub fn atomic_request_reply() -> Chunk {
117 let mut b = ChunkBuilder::new("atomic-request-reply");
118
119 let server = b.begin_function("server", 0, 8);
129 b.emit_receive(0);
130 emit_native1_from!(b, 7, 0, N_PRINT);
131 emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
132 emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
133 emit_native1_from!(b, 3, 0, N_MSG_TAG);
134 emit_native1_from!(b, 4, 0, N_MSG_PAYLOAD);
135 b.emit_load_imm(7, 1);
136 b.emit_binop(Opcode::Add, 4, 4, 7);
137 b.emit_self_pid(5);
138 b.emit_move(7, 4);
139 b.emit_move(4, 5);
140 b.emit_move(5, 2);
141 b.emit_load_imm(6, TAG_REP);
142 emit_native_n!(b, 4, N_MAKE_MSG, 4);
143 emit_native1_from!(b, 7, 4, N_PRINT);
144 b.emit_send(1, 4);
145 b.emit_exit(4);
146
147 b.begin_function("main", 0, 8);
154 b.emit_self_pid(1);
155 b.emit_spawn(0, server, 0);
156 b.emit_move(2, 1);
157 b.emit_load_imm(3, 1);
158 b.emit_load_imm(4, TAG_REQ);
159 b.emit_load_imm(5, 41);
160 emit_native_n!(b, 2, N_MAKE_MSG, 4);
161 emit_native1_from!(b, 7, 2, N_PRINT);
162 b.emit_send(0, 2);
163 b.emit_receive(6);
164 emit_native1_from!(b, 7, 6, N_PRINT);
165 emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
166 b.emit_return(4);
167
168 b.finish()
169}
170
171pub fn selective_receive() -> Chunk {
181 let mut b = ChunkBuilder::new("selective-receive");
182
183 let server = b.begin_function("server", 0, 8);
185 b.emit_receive_match_imm(0, TAG_REQ as u16);
186 emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
187 emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
188 emit_native1_from!(b, 4, 0, N_MSG_PAYLOAD);
189 b.emit_load_imm(7, 1);
190 b.emit_binop(Opcode::Add, 4, 4, 7);
191 b.emit_self_pid(5);
192 b.emit_move(7, 4);
193 b.emit_move(4, 5);
194 b.emit_move(5, 2);
195 b.emit_load_imm(6, TAG_REP);
196 emit_native_n!(b, 4, N_MAKE_MSG, 4);
197 b.emit_send(1, 4);
198 b.emit_receive(0);
200 emit_native1_from!(b, 3, 0, N_MSG_TAG);
201 b.emit_load_imm(7, TAG_JUNK);
202 b.emit_binop(Opcode::Eq, 3, 3, 7);
203 let trap_lbl = b.new_label();
205 b.emit_branch(3, trap_lbl);
206 b.emit_exit(4);
207 b.bind_label(trap_lbl);
208 b.emit_trap(2);
209
210 b.begin_function("main", 0, 8);
211 b.emit_self_pid(1);
212 b.emit_spawn(0, server, 0);
213 b.emit_move(2, 1);
215 b.emit_load_imm(3, 1);
216 b.emit_load_imm(4, TAG_JUNK);
217 b.emit_load_imm(5, 0);
218 emit_native_n!(b, 2, N_MAKE_MSG, 4);
219 b.emit_send(0, 2);
220 b.emit_move(2, 1);
222 b.emit_load_imm(3, 1);
223 b.emit_load_imm(4, TAG_REQ);
224 b.emit_load_imm(5, 41);
225 emit_native_n!(b, 2, N_MAKE_MSG, 4);
226 b.emit_send(0, 2);
227 b.emit_receive_match_imm(6, TAG_REP as u16);
228 emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
229 b.emit_return(4);
230
231 b.finish()
232}
233
234pub fn ask_reply() -> Chunk {
243 let mut b = ChunkBuilder::new("ask-reply");
244
245 let server = b.begin_function("server", 0, 8);
246 b.emit_receive_match_imm(0, TAG_REQ as u16);
247 emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
248 emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
249 emit_native1_from!(b, 4, 0, N_MSG_PAYLOAD);
250 b.emit_load_imm(7, 1);
251 b.emit_binop(Opcode::Add, 4, 4, 7);
252 b.emit_self_pid(5);
253 b.emit_move(7, 4);
254 b.emit_move(4, 5);
255 b.emit_move(5, 2);
256 b.emit_load_imm(6, TAG_REP);
257 emit_native_n!(b, 4, N_MAKE_MSG, 4);
258 b.emit_send(1, 4);
259 b.emit_exit(4);
260
261 b.begin_function("main", 0, 8);
263 b.emit_self_pid(1);
264 b.emit_spawn(0, server, 0);
265 b.emit_move(2, 1);
266 b.emit_load_imm(3, 1);
267 b.emit_load_imm(4, TAG_REQ);
268 b.emit_load_imm(5, 41);
269 emit_native_n!(b, 2, N_MAKE_MSG, 4);
270 b.emit_ask(6, 0, 2);
271 emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
272 b.emit_return(4);
273
274 b.finish()
275}
276
277pub fn forged_sender_send() -> Chunk {
297 let mut b = ChunkBuilder::new("forged-sender-send");
298
299 let server = b.begin_function("server", 0, 8);
300 b.emit_receive(0);
301 emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
302 emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
303 emit_native1_from!(b, 3, 0, N_MSG_SENDER);
304 b.emit_self_pid(4);
306 b.emit_move(5, 2);
307 b.emit_load_imm(6, TAG_REP);
308 b.emit_move(7, 3);
309 emit_native_n!(b, 4, N_MAKE_MSG, 4);
310 b.emit_send(1, 4);
311 b.emit_exit(4);
312
313 b.begin_function("main", 0, 8);
314 b.emit_self_pid(1);
315 b.emit_spawn(0, server, 0);
316 b.emit_load_imm(2, 999);
318 b.emit_load_imm(3, 1);
319 b.emit_load_imm(4, TAG_REQ);
320 b.emit_load_imm(5, 0);
321 emit_native_n!(b, 2, N_MAKE_MSG, 4);
322 b.emit_send(0, 2);
323 b.emit_receive(6);
324 emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
325 b.emit_return(4);
326
327 b.finish()
328}
329
330pub fn forged_sender_ask() -> Chunk {
337 let mut b = ChunkBuilder::new("forged-sender-ask");
338
339 let server = b.begin_function("server", 0, 8);
340 b.emit_receive_match_imm(0, TAG_REQ as u16);
341 emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
342 emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
343 emit_native1_from!(b, 3, 0, N_MSG_SENDER);
344 b.emit_self_pid(4);
345 b.emit_move(5, 2);
346 b.emit_load_imm(6, TAG_REP);
347 b.emit_move(7, 3);
348 emit_native_n!(b, 4, N_MAKE_MSG, 4);
349 b.emit_send(1, 4);
350 b.emit_exit(4);
351
352 b.begin_function("main", 0, 8);
353 b.emit_self_pid(1);
354 b.emit_spawn(0, server, 0);
355 b.emit_load_imm(2, 999);
356 b.emit_load_imm(3, 1);
357 b.emit_load_imm(4, TAG_REQ);
358 b.emit_load_imm(5, 0);
359 emit_native_n!(b, 2, N_MAKE_MSG, 4);
360 b.emit_ask(6, 0, 2);
361 emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
362 b.emit_return(4);
363
364 b.finish()
365}
366
367pub fn boom() -> Chunk {
369 let mut b = ChunkBuilder::new("boom");
370 b.begin_function("boom", 0, 1);
371 b.emit_trap(1);
372 b.finish()
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378 use crate::{
379 decode, encode, std_native_table, verify, FlowOutcome, Runtime, RuntimeConfig, Value,
380 };
381
382 fn tiny(chunk: Chunk) -> Runtime {
383 Runtime::with_config(
384 chunk,
385 RuntimeConfig {
386 workers: 1,
387 quantum: 10_000,
388 },
389 )
390 .expect("runtime")
391 }
392
393 fn tiny_natives(chunk: Chunk) -> Runtime {
394 Runtime::with_natives_and_config(
395 chunk,
396 std_native_table(),
397 RuntimeConfig {
398 workers: 1,
399 quantum: 10_000,
400 },
401 )
402 .expect("runtime")
403 }
404
405 #[test]
406 fn add_forty_two_joins_42() {
407 let rt = tiny(add_forty_two());
408 let idx = rt.function_index("main").expect("main");
409 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
410 rt.shutdown();
411 assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(42))));
412 }
413
414 #[test]
415 fn ping_pong_joins_2() {
416 let chunk = ping_pong();
417 assert!(verify(&chunk).is_ok());
418 let bytes = encode(&chunk);
419 let chunk = decode(&bytes).expect("decode");
420 let rt = tiny_natives(chunk);
421 let idx = rt.function_index("main").expect("main");
422 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
423 let sent = rt.metrics().messages_sent;
424 rt.shutdown();
425 assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(2))));
426 assert!(sent >= 2);
427 }
428
429 #[test]
430 fn atomic_request_reply_joins_42() {
431 let chunk = atomic_request_reply();
432 assert!(verify(&chunk).is_ok());
433 let bytes = encode(&chunk);
434 let chunk = decode(&bytes).expect("decode");
435 let rt = tiny_natives(chunk);
436 let idx = rt.function_index("main").expect("main");
437 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
438 let sent = rt.metrics().messages_sent;
439 rt.shutdown();
440 assert!(
441 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
442 "got {outcome:?}"
443 );
444 assert!(sent >= 1);
445 }
446
447 #[test]
448 fn selective_receive_skips_junk_tag() {
449 let chunk = selective_receive();
450 assert!(verify(&chunk).is_ok());
451 let bytes = encode(&chunk);
452 let chunk = decode(&bytes).expect("decode");
453 let rt = tiny_natives(chunk);
454 let idx = rt.function_index("main").expect("main");
455 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
456 rt.shutdown();
457 assert!(
458 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
459 "got {outcome:?}"
460 );
461 }
462
463 #[test]
464 fn ask_reply_joins_42() {
465 let chunk = ask_reply();
466 assert!(verify(&chunk).is_ok());
467 let bytes = encode(&chunk);
468 let chunk = decode(&bytes).expect("decode");
469 let rt = tiny_natives(chunk);
470 let idx = rt.function_index("main").expect("main");
471 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
472 let sent = rt.metrics().messages_sent;
473 rt.shutdown();
474 assert!(
475 matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
476 "got {outcome:?}"
477 );
478 assert!(sent >= 2);
480 }
481
482 #[test]
483 fn send_overwrites_forged_sender() {
484 let chunk = forged_sender_send();
486 assert!(verify(&chunk).is_ok());
487 let rt = tiny_natives(chunk);
488 let idx = rt.function_index("main").expect("main");
489 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
490 rt.shutdown();
491 match outcome {
492 FlowOutcome::Completed(Value::Int(n)) => {
493 assert_ne!(n, 999, "forged make_msg sender must not survive Send");
494 assert!(n >= 1, "authenticated sender must be a live flow id");
495 }
496 other => panic!("expected Completed(Int), got {other:?}"),
497 }
498 }
499
500 #[test]
501 fn ask_overwrites_forged_request_sender() {
502 let chunk = forged_sender_ask();
504 assert!(verify(&chunk).is_ok());
505 let rt = tiny_natives(chunk);
506 let idx = rt.function_index("main").expect("main");
507 let outcome = rt.spawn(idx, &[]).expect("spawn").join();
508 rt.shutdown();
509 match outcome {
510 FlowOutcome::Completed(Value::Int(n)) => {
511 assert_ne!(n, 999, "forged make_msg sender must not survive Ask");
512 assert!(n >= 1, "authenticated sender must be a live flow id");
513 }
514 other => panic!("expected Completed(Int), got {other:?}"),
515 }
516 }
517
518 #[test]
519 fn send_scalar_target_traps() {
520 let mut b = ChunkBuilder::new("bad-cap-target");
521 b.begin_function("main", 0, 6);
522 b.emit_load_imm(0, 99); b.emit_load_imm(1, 0);
524 b.emit_load_imm(2, 1);
525 b.emit_load_imm(3, TAG_PING);
526 b.emit_load_imm(4, 1);
527 emit_native_n!(b, 1, N_MAKE_MSG, 4);
528 b.emit_send(0, 1);
529 b.emit_return(1);
530 let rt = tiny_natives(b.finish());
531 let outcome = rt.spawn(0, &[]).expect("spawn").join();
532 rt.shutdown();
533 assert!(
534 matches!(outcome, FlowOutcome::Failed(_)),
535 "non-Cap Send target must fail, got {outcome:?}"
536 );
537 }
538
539 #[test]
540 fn send_scalar_is_not_an_atomic_hop() {
541 let mut b = ChunkBuilder::new("bad-hop");
542 b.begin_function("main", 0, 2);
543 b.emit_self_pid(0);
544 b.emit_load_imm(1, 99);
545 b.emit_send(0, 1);
546 b.emit_return(1);
547 let rt = tiny(b.finish());
548 let outcome = rt.spawn(0, &[]).expect("spawn").join();
549 rt.shutdown();
550 assert!(
551 matches!(outcome, FlowOutcome::Failed(_)),
552 "scalar Send must trap, got {outcome:?}"
553 );
554 }
555}