use crate::{emit_native1_from, emit_native_n, Chunk, ChunkBuilder, Opcode};
const N_PRINT: u32 = 0;
const N_MAKE_MSG: u32 = 2;
const N_MSG_SENDER: u32 = 3;
const N_MSG_REQUEST_ID: u32 = 4;
const N_MSG_TAG: u32 = 5;
const N_MSG_PAYLOAD: u32 = 6;
const N_MSG_REPLY_CAP: u32 = 7;
pub const TAG_REQ: i32 = 1;
pub const TAG_REP: i32 = 2;
pub const TAG_PING: i32 = 10;
pub const TAG_PONG: i32 = 11;
pub const TAG_JUNK: i32 = 99;
pub fn add_forty_two() -> Chunk {
let mut b = ChunkBuilder::new("add-forty-two");
b.begin_function("main", 0, 2);
b.emit_load_imm(0, 41);
b.emit_load_imm(1, 1);
b.emit_binop(Opcode::Add, 0, 0, 1);
b.emit_return(0);
b.finish()
}
pub fn ping_pong() -> Chunk {
let mut b = ChunkBuilder::new("ping-pong");
let pong = b.begin_function("pong", 0, 8);
b.emit_receive(0);
emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
emit_native1_from!(b, 3, 0, N_MSG_PAYLOAD);
b.emit_load_imm(7, 1);
b.emit_binop(Opcode::Add, 3, 3, 7);
b.emit_self_pid(4);
b.emit_move(5, 2);
b.emit_load_imm(6, TAG_PONG);
b.emit_move(7, 3);
emit_native_n!(b, 4, N_MAKE_MSG, 4);
b.emit_send(1, 4);
b.emit_exit(4);
b.begin_function("main", 0, 8);
b.emit_self_pid(1);
b.emit_spawn(0, pong, 0);
b.emit_move(2, 1);
b.emit_load_imm(3, 1);
b.emit_load_imm(4, TAG_PING);
b.emit_load_imm(5, 1);
emit_native_n!(b, 2, N_MAKE_MSG, 4);
b.emit_send(0, 2);
b.emit_receive(6);
emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
b.emit_return(4);
b.finish()
}
pub fn atomic_request_reply() -> Chunk {
let mut b = ChunkBuilder::new("atomic-request-reply");
let server = b.begin_function("server", 0, 8);
b.emit_receive(0);
emit_native1_from!(b, 7, 0, N_PRINT);
emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
emit_native1_from!(b, 3, 0, N_MSG_TAG);
emit_native1_from!(b, 4, 0, N_MSG_PAYLOAD);
b.emit_load_imm(7, 1);
b.emit_binop(Opcode::Add, 4, 4, 7);
b.emit_self_pid(5);
b.emit_move(7, 4);
b.emit_move(4, 5);
b.emit_move(5, 2);
b.emit_load_imm(6, TAG_REP);
emit_native_n!(b, 4, N_MAKE_MSG, 4);
emit_native1_from!(b, 7, 4, N_PRINT);
b.emit_send(1, 4);
b.emit_exit(4);
b.begin_function("main", 0, 8);
b.emit_self_pid(1);
b.emit_spawn(0, server, 0);
b.emit_move(2, 1);
b.emit_load_imm(3, 1);
b.emit_load_imm(4, TAG_REQ);
b.emit_load_imm(5, 41);
emit_native_n!(b, 2, N_MAKE_MSG, 4);
emit_native1_from!(b, 7, 2, N_PRINT);
b.emit_send(0, 2);
b.emit_receive(6);
emit_native1_from!(b, 7, 6, N_PRINT);
emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
b.emit_return(4);
b.finish()
}
pub fn selective_receive() -> Chunk {
let mut b = ChunkBuilder::new("selective-receive");
let server = b.begin_function("server", 0, 8);
b.emit_receive_match_imm(0, TAG_REQ as u16);
emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
emit_native1_from!(b, 4, 0, N_MSG_PAYLOAD);
b.emit_load_imm(7, 1);
b.emit_binop(Opcode::Add, 4, 4, 7);
b.emit_self_pid(5);
b.emit_move(7, 4);
b.emit_move(4, 5);
b.emit_move(5, 2);
b.emit_load_imm(6, TAG_REP);
emit_native_n!(b, 4, N_MAKE_MSG, 4);
b.emit_send(1, 4);
b.emit_receive(0);
emit_native1_from!(b, 3, 0, N_MSG_TAG);
b.emit_load_imm(7, TAG_JUNK);
b.emit_binop(Opcode::Eq, 3, 3, 7);
let trap_lbl = b.new_label();
b.emit_branch(3, trap_lbl);
b.emit_exit(4);
b.bind_label(trap_lbl);
b.emit_trap(2);
b.begin_function("main", 0, 8);
b.emit_self_pid(1);
b.emit_spawn(0, server, 0);
b.emit_move(2, 1);
b.emit_load_imm(3, 1);
b.emit_load_imm(4, TAG_JUNK);
b.emit_load_imm(5, 0);
emit_native_n!(b, 2, N_MAKE_MSG, 4);
b.emit_send(0, 2);
b.emit_move(2, 1);
b.emit_load_imm(3, 1);
b.emit_load_imm(4, TAG_REQ);
b.emit_load_imm(5, 41);
emit_native_n!(b, 2, N_MAKE_MSG, 4);
b.emit_send(0, 2);
b.emit_receive_match_imm(6, TAG_REP as u16);
emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
b.emit_return(4);
b.finish()
}
pub fn ask_reply() -> Chunk {
let mut b = ChunkBuilder::new("ask-reply");
let server = b.begin_function("server", 0, 8);
b.emit_receive_match_imm(0, TAG_REQ as u16);
emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
emit_native1_from!(b, 4, 0, N_MSG_PAYLOAD);
b.emit_load_imm(7, 1);
b.emit_binop(Opcode::Add, 4, 4, 7);
b.emit_self_pid(5);
b.emit_move(7, 4);
b.emit_move(4, 5);
b.emit_move(5, 2);
b.emit_load_imm(6, TAG_REP);
emit_native_n!(b, 4, N_MAKE_MSG, 4);
b.emit_send(1, 4);
b.emit_exit(4);
b.begin_function("main", 0, 8);
b.emit_self_pid(1);
b.emit_spawn(0, server, 0);
b.emit_move(2, 1);
b.emit_load_imm(3, 1);
b.emit_load_imm(4, TAG_REQ);
b.emit_load_imm(5, 41);
emit_native_n!(b, 2, N_MAKE_MSG, 4);
b.emit_ask(6, 0, 2);
emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
b.emit_return(4);
b.finish()
}
pub fn forged_sender_send() -> Chunk {
let mut b = ChunkBuilder::new("forged-sender-send");
let server = b.begin_function("server", 0, 8);
b.emit_receive(0);
emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
emit_native1_from!(b, 3, 0, N_MSG_SENDER);
b.emit_self_pid(4);
b.emit_move(5, 2);
b.emit_load_imm(6, TAG_REP);
b.emit_move(7, 3);
emit_native_n!(b, 4, N_MAKE_MSG, 4);
b.emit_send(1, 4);
b.emit_exit(4);
b.begin_function("main", 0, 8);
b.emit_self_pid(1);
b.emit_spawn(0, server, 0);
b.emit_load_imm(2, 999);
b.emit_load_imm(3, 1);
b.emit_load_imm(4, TAG_REQ);
b.emit_load_imm(5, 0);
emit_native_n!(b, 2, N_MAKE_MSG, 4);
b.emit_send(0, 2);
b.emit_receive(6);
emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
b.emit_return(4);
b.finish()
}
pub fn forged_sender_ask() -> Chunk {
let mut b = ChunkBuilder::new("forged-sender-ask");
let server = b.begin_function("server", 0, 8);
b.emit_receive_match_imm(0, TAG_REQ as u16);
emit_native1_from!(b, 1, 0, N_MSG_REPLY_CAP);
emit_native1_from!(b, 2, 0, N_MSG_REQUEST_ID);
emit_native1_from!(b, 3, 0, N_MSG_SENDER);
b.emit_self_pid(4);
b.emit_move(5, 2);
b.emit_load_imm(6, TAG_REP);
b.emit_move(7, 3);
emit_native_n!(b, 4, N_MAKE_MSG, 4);
b.emit_send(1, 4);
b.emit_exit(4);
b.begin_function("main", 0, 8);
b.emit_self_pid(1);
b.emit_spawn(0, server, 0);
b.emit_load_imm(2, 999);
b.emit_load_imm(3, 1);
b.emit_load_imm(4, TAG_REQ);
b.emit_load_imm(5, 0);
emit_native_n!(b, 2, N_MAKE_MSG, 4);
b.emit_ask(6, 0, 2);
emit_native1_from!(b, 4, 6, N_MSG_PAYLOAD);
b.emit_return(4);
b.finish()
}
pub fn boom() -> Chunk {
let mut b = ChunkBuilder::new("boom");
b.begin_function("boom", 0, 1);
b.emit_trap(1);
b.finish()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
decode, encode, std_native_table, verify, FlowOutcome, Runtime, RuntimeConfig, Value,
};
fn tiny(chunk: Chunk) -> Runtime {
Runtime::with_config(
chunk,
RuntimeConfig {
workers: 1,
quantum: 10_000,
},
)
.expect("runtime")
}
fn tiny_natives(chunk: Chunk) -> Runtime {
Runtime::with_natives_and_config(
chunk,
std_native_table(),
RuntimeConfig {
workers: 1,
quantum: 10_000,
},
)
.expect("runtime")
}
#[test]
fn add_forty_two_joins_42() {
let rt = tiny(add_forty_two());
let idx = rt.function_index("main").expect("main");
let outcome = rt.spawn(idx, &[]).expect("spawn").join();
rt.shutdown();
assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(42))));
}
#[test]
fn ping_pong_joins_2() {
let chunk = ping_pong();
assert!(verify(&chunk).is_ok());
let bytes = encode(&chunk);
let chunk = decode(&bytes).expect("decode");
let rt = tiny_natives(chunk);
let idx = rt.function_index("main").expect("main");
let outcome = rt.spawn(idx, &[]).expect("spawn").join();
let sent = rt.metrics().messages_sent;
rt.shutdown();
assert!(matches!(outcome, FlowOutcome::Completed(Value::Int(2))));
assert!(sent >= 2);
}
#[test]
fn atomic_request_reply_joins_42() {
let chunk = atomic_request_reply();
assert!(verify(&chunk).is_ok());
let bytes = encode(&chunk);
let chunk = decode(&bytes).expect("decode");
let rt = tiny_natives(chunk);
let idx = rt.function_index("main").expect("main");
let outcome = rt.spawn(idx, &[]).expect("spawn").join();
let sent = rt.metrics().messages_sent;
rt.shutdown();
assert!(
matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
"got {outcome:?}"
);
assert!(sent >= 1);
}
#[test]
fn selective_receive_skips_junk_tag() {
let chunk = selective_receive();
assert!(verify(&chunk).is_ok());
let bytes = encode(&chunk);
let chunk = decode(&bytes).expect("decode");
let rt = tiny_natives(chunk);
let idx = rt.function_index("main").expect("main");
let outcome = rt.spawn(idx, &[]).expect("spawn").join();
rt.shutdown();
assert!(
matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
"got {outcome:?}"
);
}
#[test]
fn ask_reply_joins_42() {
let chunk = ask_reply();
assert!(verify(&chunk).is_ok());
let bytes = encode(&chunk);
let chunk = decode(&bytes).expect("decode");
let rt = tiny_natives(chunk);
let idx = rt.function_index("main").expect("main");
let outcome = rt.spawn(idx, &[]).expect("spawn").join();
let sent = rt.metrics().messages_sent;
rt.shutdown();
assert!(
matches!(outcome, FlowOutcome::Completed(Value::Int(42))),
"got {outcome:?}"
);
assert!(sent >= 2);
}
#[test]
fn send_overwrites_forged_sender() {
let chunk = forged_sender_send();
assert!(verify(&chunk).is_ok());
let rt = tiny_natives(chunk);
let idx = rt.function_index("main").expect("main");
let outcome = rt.spawn(idx, &[]).expect("spawn").join();
rt.shutdown();
match outcome {
FlowOutcome::Completed(Value::Int(n)) => {
assert_ne!(n, 999, "forged make_msg sender must not survive Send");
assert!(n >= 1, "authenticated sender must be a live flow id");
}
other => panic!("expected Completed(Int), got {other:?}"),
}
}
#[test]
fn ask_overwrites_forged_request_sender() {
let chunk = forged_sender_ask();
assert!(verify(&chunk).is_ok());
let rt = tiny_natives(chunk);
let idx = rt.function_index("main").expect("main");
let outcome = rt.spawn(idx, &[]).expect("spawn").join();
rt.shutdown();
match outcome {
FlowOutcome::Completed(Value::Int(n)) => {
assert_ne!(n, 999, "forged make_msg sender must not survive Ask");
assert!(n >= 1, "authenticated sender must be a live flow id");
}
other => panic!("expected Completed(Int), got {other:?}"),
}
}
#[test]
fn send_scalar_target_traps() {
let mut b = ChunkBuilder::new("bad-cap-target");
b.begin_function("main", 0, 6);
b.emit_load_imm(0, 99); b.emit_load_imm(1, 0);
b.emit_load_imm(2, 1);
b.emit_load_imm(3, TAG_PING);
b.emit_load_imm(4, 1);
emit_native_n!(b, 1, N_MAKE_MSG, 4);
b.emit_send(0, 1);
b.emit_return(1);
let rt = tiny_natives(b.finish());
let outcome = rt.spawn(0, &[]).expect("spawn").join();
rt.shutdown();
assert!(
matches!(outcome, FlowOutcome::Failed(_)),
"non-Cap Send target must fail, got {outcome:?}"
);
}
#[test]
fn send_scalar_is_not_an_atomic_hop() {
let mut b = ChunkBuilder::new("bad-hop");
b.begin_function("main", 0, 2);
b.emit_self_pid(0);
b.emit_load_imm(1, 99);
b.emit_send(0, 1);
b.emit_return(1);
let rt = tiny(b.finish());
let outcome = rt.spawn(0, &[]).expect("spawn").join();
rt.shutdown();
assert!(
matches!(outcome, FlowOutcome::Failed(_)),
"scalar Send must trap, got {outcome:?}"
);
}
}