diff --git a/03_PROTO/crates/riina-codegen/src/emit.rs b/03_PROTO/crates/riina-codegen/src/emit.rs index aeadb9d1..32a76722 100644 --- a/03_PROTO/crates/riina-codegen/src/emit.rs +++ b/03_PROTO/crates/riina-codegen/src/emit.rs @@ -363,6 +363,9 @@ impl CEmitter { // Builtin function helpers self.emit_builtin_helpers(); + + // JALINAN runtime (actors, CRDTs, content-addressed) + self.emit_jalinan_runtime(); } /// Emit value constructor functions @@ -2558,6 +2561,83 @@ impl CEmitter { self.writeln(""); } + /// Emit JALINAN runtime support (actors, CRDTs, content-addressed hashing) + fn emit_jalinan_runtime(&mut self) { + self.writeln("/* ═══════════════════════════════════════════════════════════════════ */"); + self.writeln("/* JALINAN RUNTIME */"); + self.writeln("/* ═══════════════════════════════════════════════════════════════════ */"); + self.writeln(""); + + // Actor ID counter + self.writeln("static int64_t riina_next_actor_id = 0;"); + self.writeln(""); + + // Actor declaration (registers actor type, returns unit) + self.writeln("static riina_value_t* riina_actor_decl(riina_value_t* init_state, riina_value_t* handler) {"); + self.writeln(" (void)init_state; (void)handler;"); + self.writeln(" return riina_unit();"); + self.writeln("}"); + self.writeln(""); + + // Choreography declaration (protocol declaration, returns unit) + self.writeln("static riina_value_t* riina_choreography_decl(void) {"); + self.writeln(" return riina_unit();"); + self.writeln("}"); + self.writeln(""); + + // Actor spawn (creates actor instance, returns integer ref ID) + self.writeln("static riina_value_t* riina_actor_spawn(riina_value_t* actor_decl, riina_value_t* init_state) {"); + self.writeln(" (void)actor_decl; (void)init_state;"); + self.writeln(" return riina_int(++riina_next_actor_id);"); + self.writeln("}"); + self.writeln(""); + + // Actor send (enqueue message, returns unit) + self.writeln("static riina_value_t* riina_actor_send(riina_value_t* actor_ref, riina_value_t* msg) {"); + self.writeln(" (void)actor_ref; (void)msg;"); + self.writeln(" return riina_unit();"); + self.writeln("}"); + self.writeln(""); + + // Actor receive (dequeue message, returns unit in single-threaded mode) + self.writeln("static riina_value_t* riina_actor_recv(riina_value_t* actor_ref) {"); + self.writeln(" (void)actor_ref;"); + self.writeln(" return riina_unit();"); + self.writeln("}"); + self.writeln(""); + + // CRDT merge (pointwise max for integer counters) + self.writeln("static riina_value_t* riina_crdt_merge(riina_value_t* a, riina_value_t* b) {"); + self.writeln(" if (a->tag == RIINA_TAG_INT && b->tag == RIINA_TAG_INT) {"); + self.writeln(" return riina_int(a->data.int_val > b->data.int_val ? a->data.int_val : b->data.int_val);"); + self.writeln(" }"); + self.writeln(" return a;"); + self.writeln("}"); + self.writeln(""); + + // Content hash (DJB2 hash, returns hex string) + self.writeln("static riina_value_t* riina_content_hash(riina_value_t* val) {"); + self.writeln(" uint64_t hash = 5381;"); + self.writeln(" if (val->tag == RIINA_TAG_INT) {"); + self.writeln(" uint64_t n = val->data.int_val;"); + self.writeln(" int i;"); + self.writeln(" for (i = 0; i < 8; i++) {"); + self.writeln(" hash = ((hash << 5) + hash) + (n & 0xff);"); + self.writeln(" n >>= 8;"); + self.writeln(" }"); + self.writeln(" } else if (val->tag == RIINA_TAG_STRING) {"); + self.writeln(" const char* s = val->data.string_val.data;"); + self.writeln(" while (*s) { hash = ((hash << 5) + hash) + (unsigned char)*s++; }"); + self.writeln(" } else if (val->tag == RIINA_TAG_BOOL) {"); + self.writeln(" hash = ((hash << 5) + hash) + (val->data.bool_val ? 1 : 0);"); + self.writeln(" }"); + self.writeln(" char buf[17];"); + self.writeln(" snprintf(buf, sizeof(buf), \"%016llx\", (unsigned long long)hash);"); + self.writeln(" return riina_string(buf);"); + self.writeln("}"); + self.writeln(""); + } + /// Emit forward declarations for all functions fn emit_forward_declarations(&mut self, program: &Program) -> Result<()> { self.writeln("/* ═══════════════════════════════════════════════════════════════════ */"); @@ -2706,14 +2786,19 @@ impl CEmitter { | Instruction::Prove(v) | Instruction::UnaryOp(_, v) | Instruction::Inl(v) - | Instruction::Inr(v) => { + | Instruction::Inr(v) + | Instruction::ActorRecv(v) + | Instruction::ContentHash(v) => { vars.insert(*v); } Instruction::BinOp(_, a, b) | Instruction::Pair(a, b) | Instruction::Store(a, b) | Instruction::Declassify(a, b) - | Instruction::Call(a, b) => { + | Instruction::Call(a, b) + | Instruction::ActorSpawn(a, b) + | Instruction::ActorSend(a, b) + | Instruction::CRDTMerge(a, b) => { vars.insert(*a); vars.insert(*b); } @@ -2744,6 +2829,11 @@ impl CEmitter { Instruction::FixClosure { closure, .. } => { vars.insert(*closure); } + Instruction::ActorDecl { init_state, handler, .. } => { + vars.insert(*init_state); + vars.insert(*handler); + } + Instruction::ChoreographyDecl { .. } => {} Instruction::Const(_) | Instruction::RequireCap(_) | Instruction::GrantCap(_) => {} @@ -3043,6 +3133,68 @@ impl CEmitter { )); } + // ═══════════════════════════════════════════════════════════ + // JALINAN (actors, choreography, CRDTs, content-addressed) + // ═══════════════════════════════════════════════════════════ + + Instruction::ActorDecl { name: _, init_state, handler } => { + self.writeln(&format!( + "{result} = *riina_actor_decl(&{init}, &{handler});", + result = result, + init = self.var_name(init_state), + handler = self.var_name(handler), + )); + } + + Instruction::ChoreographyDecl { name: _, roles: _ } => { + self.writeln(&format!( + "{result} = *riina_choreography_decl();", + )); + } + + Instruction::ActorSpawn(decl, state) => { + self.writeln(&format!( + "{result} = *riina_actor_spawn(&{decl}, &{state});", + result = result, + decl = self.var_name(decl), + state = self.var_name(state), + )); + } + + Instruction::ActorSend(actor, msg) => { + self.writeln(&format!( + "{result} = *riina_actor_send(&{actor}, &{msg});", + result = result, + actor = self.var_name(actor), + msg = self.var_name(msg), + )); + } + + Instruction::ActorRecv(actor) => { + self.writeln(&format!( + "{result} = *riina_actor_recv(&{actor});", + result = result, + actor = self.var_name(actor), + )); + } + + Instruction::CRDTMerge(a, b) => { + self.writeln(&format!( + "{result} = *riina_crdt_merge(&{a}, &{b});", + result = result, + a = self.var_name(a), + b = self.var_name(b), + )); + } + + Instruction::ContentHash(val) => { + self.writeln(&format!( + "{result} = *riina_content_hash(&{val});", + result = result, + val = self.var_name(val), + )); + } + Instruction::Phi(_entries) => { // SSA phi destruction: phi nodes are eliminated by copy insertion. // The phi result variable is already declared. We do NOT emit code diff --git a/03_PROTO/crates/riina-codegen/src/interp.rs b/03_PROTO/crates/riina-codegen/src/interp.rs index 45484b9c..f55c8762 100644 --- a/03_PROTO/crates/riina-codegen/src/interp.rs +++ b/03_PROTO/crates/riina-codegen/src/interp.rs @@ -176,6 +176,8 @@ pub struct Interpreter { caps: Capabilities, /// Current security context security_context: SecurityLevel, + /// Next actor ID counter (JALINAN Phase 6) + next_actor_id: u64, } impl Interpreter { @@ -187,6 +189,7 @@ impl Interpreter { handlers: Vec::new(), caps: Capabilities::new(), security_context: SecurityLevel::Public, + next_actor_id: 0, } } @@ -624,13 +627,74 @@ impl Interpreter { // ═══════════════════════════════════════════════════════════════ // JALINAN Phase 6 (Actor, Choreography, CRDT, Content-Addressed) // ═══════════════════════════════════════════════════════════════ - Expr::ActorDecl { .. } - | Expr::ChoreographyBlock { .. } - | Expr::Spawn(_, _) - | Expr::ActorSend(_, _) - | Expr::ActorRecv(_) - | Expr::CRDTMerge(_, _) - | Expr::ContentHash(_) => todo!("JALINAN Phase 6"), + Expr::ActorDecl { name: _, init_state, handler, .. } => { + // Evaluate init state and handler to verify they're valid + let _init = self.eval_with_env(env, init_state)?; + let _handler = self.eval_with_env(env, handler)?; + Ok(Value::Unit) + } + + Expr::ChoreographyBlock { .. } => { + // Protocol declaration — no runtime behavior + Ok(Value::Unit) + } + + Expr::Spawn(actor_expr, state_expr) => { + // Evaluate subexpressions for side effects + let _actor = self.eval_with_env(env, actor_expr)?; + let _state = self.eval_with_env(env, state_expr)?; + self.next_actor_id += 1; + Ok(Value::ActorRef(self.next_actor_id)) + } + + Expr::ActorSend(actor_expr, msg_expr) => { + let _actor = self.eval_with_env(env, actor_expr)?; + let _msg = self.eval_with_env(env, msg_expr)?; + // Single-threaded: message send is a no-op + Ok(Value::Unit) + } + + Expr::ActorRecv(actor_expr) => { + let _actor = self.eval_with_env(env, actor_expr)?; + // Single-threaded: no mailbox, return unit + Ok(Value::Unit) + } + + Expr::CRDTMerge(a_expr, b_expr) => { + let a = self.eval_with_env(env, a_expr)?; + let b = self.eval_with_env(env, b_expr)?; + // GCounter merge: pointwise max for integers + match (&a, &b) { + (Value::Int(x), Value::Int(y)) => Ok(Value::Int(std::cmp::max(*x, *y))), + _ => Ok(a), + } + } + + Expr::ContentHash(val_expr) => { + let val = self.eval_with_env(env, val_expr)?; + // DJB2 hash + let mut hash: u64 = 5381; + match &val { + Value::Int(n) => { + let mut n = *n; + for _ in 0..8 { + hash = hash.wrapping_mul(33).wrapping_add(n & 0xff); + n >>= 8; + } + } + Value::String(s) => { + for b in s.bytes() { + hash = hash.wrapping_mul(33).wrapping_add(u64::from(b)); + } + } + Value::Bool(b) => { + hash = hash.wrapping_mul(33).wrapping_add(if *b { 1 } else { 0 }); + } + _ => {} + } + let hex = format!("{hash:016x}"); + Ok(Value::Hash(hex.into_bytes())) + } Expr::BinOp(op, lhs, rhs) => { let l = self.eval_with_env(env, lhs)?; @@ -1616,4 +1680,173 @@ mod tests { Ok(Value::String("hello world".into())) ); } + + // ═══════════════════════════════════════════════════════════════════════ + // JALINAN Phase 6 TESTS + // ═══════════════════════════════════════════════════════════════════════ + + #[test] + fn test_eval_actor_decl() { + let mut interp = Interpreter::new(); + let expr = Expr::ActorDecl { + name: "Counter".into(), + state_ty: Ty::Int, + message_ty: Ty::Int, + init_state: Box::new(Expr::Int(0)), + handler: Box::new(Expr::Lam( + "msg".into(), + Ty::Int, + Box::new(Expr::Var("msg".into())), + )), + }; + assert_eq!(interp.eval(&expr), Ok(Value::Unit)); + } + + #[test] + fn test_eval_choreography_block() { + let mut interp = Interpreter::new(); + let expr = Expr::ChoreographyBlock { + name: "TwoParty".into(), + roles: vec!["Alice".into(), "Bob".into()], + protocol: riina_types::SessionType::End, + }; + assert_eq!(interp.eval(&expr), Ok(Value::Unit)); + } + + #[test] + fn test_eval_spawn() { + let mut interp = Interpreter::new(); + let expr = Expr::Spawn( + Box::new(Expr::Unit), + Box::new(Expr::Int(0)), + ); + assert_eq!(interp.eval(&expr), Ok(Value::ActorRef(1))); + } + + #[test] + fn test_eval_spawn_unique_ids() { + let mut interp = Interpreter::new(); + let spawn = Expr::Spawn( + Box::new(Expr::Unit), + Box::new(Expr::Int(0)), + ); + let r1 = interp.eval(&spawn).unwrap(); + let r2 = interp.eval(&spawn).unwrap(); + let r3 = interp.eval(&spawn).unwrap(); + assert_eq!(r1, Value::ActorRef(1)); + assert_eq!(r2, Value::ActorRef(2)); + assert_eq!(r3, Value::ActorRef(3)); + } + + #[test] + fn test_eval_actor_send() { + let mut interp = Interpreter::new(); + let expr = Expr::ActorSend( + Box::new(Expr::Int(1)), // actor ref + Box::new(Expr::Int(42)), // message + ); + assert_eq!(interp.eval(&expr), Ok(Value::Unit)); + } + + #[test] + fn test_eval_actor_recv() { + let mut interp = Interpreter::new(); + let expr = Expr::ActorRecv(Box::new(Expr::Int(1))); + assert_eq!(interp.eval(&expr), Ok(Value::Unit)); + } + + #[test] + fn test_eval_crdt_merge_int() { + let mut interp = Interpreter::new(); + let expr = Expr::CRDTMerge( + Box::new(Expr::Int(5)), + Box::new(Expr::Int(10)), + ); + assert_eq!(interp.eval(&expr), Ok(Value::Int(10))); + } + + #[test] + fn test_eval_crdt_merge_int_reversed() { + let mut interp = Interpreter::new(); + let expr = Expr::CRDTMerge( + Box::new(Expr::Int(10)), + Box::new(Expr::Int(5)), + ); + assert_eq!(interp.eval(&expr), Ok(Value::Int(10))); + } + + #[test] + fn test_eval_crdt_merge_same() { + let mut interp = Interpreter::new(); + let expr = Expr::CRDTMerge( + Box::new(Expr::Int(7)), + Box::new(Expr::Int(7)), + ); + assert_eq!(interp.eval(&expr), Ok(Value::Int(7))); + } + + #[test] + fn test_eval_crdt_merge_zero() { + let mut interp = Interpreter::new(); + let expr = Expr::CRDTMerge( + Box::new(Expr::Int(0)), + Box::new(Expr::Int(0)), + ); + assert_eq!(interp.eval(&expr), Ok(Value::Int(0))); + } + + #[test] + fn test_eval_content_hash() { + let mut interp = Interpreter::new(); + let expr = Expr::ContentHash(Box::new(Expr::Int(42))); + let result = interp.eval(&expr).unwrap(); + assert!(result.is_hash()); + } + + #[test] + fn test_eval_content_hash_deterministic() { + let mut interp = Interpreter::new(); + let expr = Expr::ContentHash(Box::new(Expr::Int(42))); + let r1 = interp.eval(&expr).unwrap(); + let r2 = interp.eval(&expr).unwrap(); + assert_eq!(r1, r2); + } + + #[test] + fn test_eval_content_hash_different() { + let mut interp = Interpreter::new(); + let e1 = Expr::ContentHash(Box::new(Expr::Int(1))); + let e2 = Expr::ContentHash(Box::new(Expr::Int(2))); + let r1 = interp.eval(&e1).unwrap(); + let r2 = interp.eval(&e2).unwrap(); + assert_ne!(r1, r2); + } + + #[test] + fn test_eval_content_hash_string() { + let mut interp = Interpreter::new(); + let expr = Expr::ContentHash(Box::new(Expr::String("hello".into()))); + let result = interp.eval(&expr).unwrap(); + assert!(result.is_hash()); + // Hash of same string should be deterministic + let r2 = interp.eval(&expr).unwrap(); + assert_eq!(result, r2); + } + + #[test] + fn test_eval_actor_send_recv_roundtrip() { + let mut interp = Interpreter::new(); + // Spawn an actor + let spawn = Expr::Spawn(Box::new(Expr::Unit), Box::new(Expr::Int(0))); + let _ref_val = interp.eval(&spawn).unwrap(); + // Send a message + let send = Expr::ActorSend( + Box::new(Expr::Int(1)), + Box::new(Expr::Int(99)), + ); + assert_eq!(interp.eval(&send), Ok(Value::Unit)); + // Receive (single-threaded stub returns unit) + let recv = Expr::ActorRecv(Box::new(Expr::Int(1))); + assert_eq!(interp.eval(&recv), Ok(Value::Unit)); + } } diff --git a/03_PROTO/crates/riina-codegen/src/ir.rs b/03_PROTO/crates/riina-codegen/src/ir.rs index 176121b7..229ccd6c 100644 --- a/03_PROTO/crates/riina-codegen/src/ir.rs +++ b/03_PROTO/crates/riina-codegen/src/ir.rs @@ -480,6 +480,66 @@ pub enum Instruction { name: String, args: Vec, }, + + // ═══════════════════════════════════════════════════════════════════ + // JALINAN (actors, choreography, CRDTs, content-addressed) + // ═══════════════════════════════════════════════════════════════════ + + /// Declare an actor type with initial state and message handler + /// + /// ```text + /// v = actor_decl "Name" init_state handler + /// ``` + ActorDecl { + name: String, + init_state: VarId, + handler: VarId, + }, + + /// Declare a choreography protocol + /// + /// ```text + /// v = choreography_decl "Name" ["RoleA", "RoleB"] + /// ``` + ChoreographyDecl { + name: String, + roles: Vec, + }, + + /// Spawn an actor instance + /// + /// ```text + /// v = actor_spawn actor_decl init_state + /// ``` + ActorSpawn(VarId, VarId), + + /// Send a message to an actor + /// + /// ```text + /// v = actor_send actor_ref message + /// ``` + ActorSend(VarId, VarId), + + /// Receive a message from an actor + /// + /// ```text + /// v = actor_recv actor_ref + /// ``` + ActorRecv(VarId), + + /// Merge two CRDT states + /// + /// ```text + /// v = crdt_merge crdt1 crdt2 + /// ``` + CRDTMerge(VarId, VarId), + + /// Compute content hash of a value + /// + /// ```text + /// v = content_hash value + /// ``` + ContentHash(VarId), } impl std::fmt::Display for Instruction { @@ -547,6 +607,24 @@ impl std::fmt::Display for Instruction { } Ok(()) } + Self::ActorDecl { name, init_state, handler } => { + write!(f, "actor_decl \"{name}\" {init_state} {handler}") + } + Self::ChoreographyDecl { name, roles } => { + write!(f, "choreography_decl \"{name}\" [")?; + for (i, r) in roles.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "\"{r}\"")?; + } + write!(f, "]") + } + Self::ActorSpawn(decl, state) => write!(f, "actor_spawn {decl} {state}"), + Self::ActorSend(actor, msg) => write!(f, "actor_send {actor} {msg}"), + Self::ActorRecv(actor) => write!(f, "actor_recv {actor}"), + Self::CRDTMerge(a, b) => write!(f, "crdt_merge {a} {b}"), + Self::ContentHash(v) => write!(f, "content_hash {v}"), } } } diff --git a/03_PROTO/crates/riina-codegen/src/lower.rs b/03_PROTO/crates/riina-codegen/src/lower.rs index fdc5f60b..9eb3bf82 100644 --- a/03_PROTO/crates/riina-codegen/src/lower.rs +++ b/03_PROTO/crates/riina-codegen/src/lower.rs @@ -224,13 +224,18 @@ fn free_vars(expr: &Expr) -> HashSet { } fv } - Expr::ActorDecl { .. } - | Expr::ChoreographyBlock { .. } - | Expr::Spawn(_, _) - | Expr::ActorSend(_, _) - | Expr::ActorRecv(_) - | Expr::CRDTMerge(_, _) - | Expr::ContentHash(_) => todo!("JALINAN Phase 6"), + Expr::ActorDecl { init_state, handler, .. } => { + let mut fv = free_vars(init_state); + fv.extend(free_vars(handler)); + fv + } + Expr::ChoreographyBlock { .. } => HashSet::new(), + Expr::Spawn(a, b) | Expr::ActorSend(a, b) | Expr::CRDTMerge(a, b) => { + let mut fv = free_vars(a); + fv.extend(free_vars(b)); + fv + } + Expr::ActorRecv(a) | Expr::ContentHash(a) => free_vars(a), } } @@ -391,13 +396,13 @@ impl Lower { | BinOp::And | BinOp::Or => Ty::Bool, }, Expr::FFICall { ret_ty, .. } => ret_ty.clone(), - Expr::ActorDecl { .. } - | Expr::ChoreographyBlock { .. } - | Expr::Spawn(_, _) - | Expr::ActorSend(_, _) - | Expr::ActorRecv(_) - | Expr::CRDTMerge(_, _) - | Expr::ContentHash(_) => todo!("JALINAN Phase 6"), + Expr::ActorDecl { .. } => Ty::Unit, + Expr::ChoreographyBlock { .. } => Ty::Unit, + Expr::Spawn(_, _) => Ty::Int, // Actor ref as integer ID + Expr::ActorSend(_, _) => Ty::Unit, + Expr::ActorRecv(_) => Ty::Int, // Message as generic value + Expr::CRDTMerge(_, _) => Ty::Int, // Merged state + Expr::ContentHash(_) => Ty::String, // Hash as hex string } } @@ -452,13 +457,19 @@ impl Lower { } eff } - Expr::ActorDecl { .. } - | Expr::ChoreographyBlock { .. } - | Expr::Spawn(_, _) - | Expr::ActorSend(_, _) - | Expr::ActorRecv(_) - | Expr::CRDTMerge(_, _) - | Expr::ContentHash(_) => todo!("JALINAN Phase 6"), + Expr::ActorDecl { init_state, handler, .. } => { + self.infer_effect(init_state).join(self.infer_effect(handler)) + } + Expr::ChoreographyBlock { .. } => Effect::Pure, + Expr::Spawn(a, b) => { + self.infer_effect(a).join(self.infer_effect(b)).join(Effect::Alloc) + } + Expr::ActorSend(a, b) => { + self.infer_effect(a).join(self.infer_effect(b)).join(Effect::Write) + } + Expr::ActorRecv(a) => self.infer_effect(a).join(Effect::Read), + Expr::CRDTMerge(a, b) => self.infer_effect(a).join(self.infer_effect(b)), + Expr::ContentHash(a) => self.infer_effect(a), } } @@ -1250,13 +1261,85 @@ impl Lower { )) } - Expr::ActorDecl { .. } - | Expr::ChoreographyBlock { .. } - | Expr::Spawn(_, _) - | Expr::ActorSend(_, _) - | Expr::ActorRecv(_) - | Expr::CRDTMerge(_, _) - | Expr::ContentHash(_) => todo!("JALINAN Phase 6"), + Expr::ActorDecl { name, init_state, handler, .. } => { + let init_var = self.lower_expr(init_state)?; + let handler_var = self.lower_expr(handler)?; + Ok(self.emit( + Instruction::ActorDecl { + name: name.clone(), + init_state: init_var, + handler: handler_var, + }, + Ty::Unit, + SecurityLevel::Public, + Effect::Pure, + )) + } + + Expr::ChoreographyBlock { name, roles, .. } => { + Ok(self.emit( + Instruction::ChoreographyDecl { + name: name.clone(), + roles: roles.clone(), + }, + Ty::Unit, + SecurityLevel::Public, + Effect::Pure, + )) + } + + Expr::Spawn(actor_expr, state_expr) => { + let actor_var = self.lower_expr(actor_expr)?; + let state_var = self.lower_expr(state_expr)?; + Ok(self.emit( + Instruction::ActorSpawn(actor_var, state_var), + Ty::Int, + SecurityLevel::Public, + Effect::Alloc, + )) + } + + Expr::ActorSend(actor_expr, msg_expr) => { + let actor_var = self.lower_expr(actor_expr)?; + let msg_var = self.lower_expr(msg_expr)?; + Ok(self.emit( + Instruction::ActorSend(actor_var, msg_var), + Ty::Unit, + SecurityLevel::Public, + Effect::Write, + )) + } + + Expr::ActorRecv(actor_expr) => { + let actor_var = self.lower_expr(actor_expr)?; + Ok(self.emit( + Instruction::ActorRecv(actor_var), + Ty::Int, + SecurityLevel::Public, + Effect::Read, + )) + } + + Expr::CRDTMerge(a_expr, b_expr) => { + let a_var = self.lower_expr(a_expr)?; + let b_var = self.lower_expr(b_expr)?; + Ok(self.emit( + Instruction::CRDTMerge(a_var, b_var), + Ty::Int, + SecurityLevel::Public, + Effect::Pure, + )) + } + + Expr::ContentHash(val_expr) => { + let val_var = self.lower_expr(val_expr)?; + Ok(self.emit( + Instruction::ContentHash(val_var), + Ty::String, + SecurityLevel::Public, + Effect::Pure, + )) + } Expr::BinOp(op, lhs, rhs) => { let l = self.lower_expr(lhs)?; @@ -1617,4 +1700,115 @@ mod tests { .count(); assert!(pair_count >= 2); } + + // ═══════════════════════════════════════════════════════════════════════ + // JALINAN Phase 6 LOWERING TESTS + // ═══════════════════════════════════════════════════════════════════════ + + #[test] + fn test_lower_actor_decl() { + let mut lower = Lower::new(); + let expr = Expr::ActorDecl { + name: "Counter".into(), + state_ty: Ty::Int, + message_ty: Ty::Int, + init_state: Box::new(Expr::Int(0)), + handler: Box::new(Expr::Lam( + "msg".into(), + Ty::Int, + Box::new(Expr::Var("msg".into())), + )), + }; + let prog = lower.compile(&expr).unwrap(); + let main = prog.function(FuncId::MAIN).unwrap(); + let has_actor_decl = main.blocks[0].instrs.iter().any(|i| { + matches!(i.instr, Instruction::ActorDecl { .. }) + }); + assert!(has_actor_decl); + } + + #[test] + fn test_lower_choreography_block() { + let mut lower = Lower::new(); + let expr = Expr::ChoreographyBlock { + name: "Protocol".into(), + roles: vec!["A".into(), "B".into()], + protocol: riina_types::SessionType::End, + }; + let prog = lower.compile(&expr).unwrap(); + let main = prog.function(FuncId::MAIN).unwrap(); + let has_choreo = main.blocks[0].instrs.iter().any(|i| { + matches!(i.instr, Instruction::ChoreographyDecl { .. }) + }); + assert!(has_choreo); + } + + #[test] + fn test_lower_spawn() { + let mut lower = Lower::new(); + let expr = Expr::Spawn( + Box::new(Expr::Unit), + Box::new(Expr::Int(0)), + ); + let prog = lower.compile(&expr).unwrap(); + let main = prog.function(FuncId::MAIN).unwrap(); + let has_spawn = main.blocks[0].instrs.iter().any(|i| { + matches!(i.instr, Instruction::ActorSpawn(_, _)) + }); + assert!(has_spawn); + } + + #[test] + fn test_lower_actor_send() { + let mut lower = Lower::new(); + let expr = Expr::ActorSend( + Box::new(Expr::Int(1)), + Box::new(Expr::Int(42)), + ); + let prog = lower.compile(&expr).unwrap(); + let main = prog.function(FuncId::MAIN).unwrap(); + let has_send = main.blocks[0].instrs.iter().any(|i| { + matches!(i.instr, Instruction::ActorSend(_, _)) + }); + assert!(has_send); + } + + #[test] + fn test_lower_actor_recv() { + let mut lower = Lower::new(); + let expr = Expr::ActorRecv(Box::new(Expr::Int(1))); + let prog = lower.compile(&expr).unwrap(); + let main = prog.function(FuncId::MAIN).unwrap(); + let has_recv = main.blocks[0].instrs.iter().any(|i| { + matches!(i.instr, Instruction::ActorRecv(_)) + }); + assert!(has_recv); + } + + #[test] + fn test_lower_crdt_merge() { + let mut lower = Lower::new(); + let expr = Expr::CRDTMerge( + Box::new(Expr::Int(5)), + Box::new(Expr::Int(10)), + ); + let prog = lower.compile(&expr).unwrap(); + let main = prog.function(FuncId::MAIN).unwrap(); + let has_merge = main.blocks[0].instrs.iter().any(|i| { + matches!(i.instr, Instruction::CRDTMerge(_, _)) + }); + assert!(has_merge); + } + + #[test] + fn test_lower_content_hash() { + let mut lower = Lower::new(); + let expr = Expr::ContentHash(Box::new(Expr::Int(42))); + let prog = lower.compile(&expr).unwrap(); + let main = prog.function(FuncId::MAIN).unwrap(); + let has_hash = main.blocks[0].instrs.iter().any(|i| { + matches!(i.instr, Instruction::ContentHash(_)) + }); + assert!(has_hash); + } } diff --git a/03_PROTO/crates/riina-codegen/src/value.rs b/03_PROTO/crates/riina-codegen/src/value.rs index 36d0dc7b..e032c621 100644 --- a/03_PROTO/crates/riina-codegen/src/value.rs +++ b/03_PROTO/crates/riina-codegen/src/value.rs @@ -269,6 +269,25 @@ pub enum Value { /// /// Not in Coq — Rust-only extension for stdlib. Map(BTreeMap), + + // ═══════════════════════════════════════════════════════════════════ + // JALINAN VALUES (actor system, CRDTs, content-addressed) + // ═══════════════════════════════════════════════════════════════════ + + /// Actor reference (unique ID) + /// + /// Not in Coq — Rust-only extension for JALINAN Phase 6. + ActorRef(u64), + + /// CRDT state (value + metadata for merge) + /// + /// Not in Coq — Rust-only extension for JALINAN Phase 6. + CRDTState(Box, Box), + + /// Content hash (byte vector, typically 32 bytes for SHA-256) + /// + /// Not in Coq — Rust-only extension for JALINAN Phase 6. + Hash(Vec), } impl Value { @@ -348,6 +367,24 @@ impl Value { Self::Map(entries) } + /// Create an actor reference value + #[must_use] + pub const fn actor_ref(id: u64) -> Self { + Self::ActorRef(id) + } + + /// Create a CRDT state value + #[must_use] + pub fn crdt_state(value: Self, metadata: Self) -> Self { + Self::CRDTState(Box::new(value), Box::new(metadata)) + } + + /// Create a hash value + #[must_use] + pub fn hash(bytes: Vec) -> Self { + Self::Hash(bytes) + } + // ═══════════════════════════════════════════════════════════════════ // PREDICATES // ═══════════════════════════════════════════════════════════════════ @@ -442,6 +479,24 @@ impl Value { matches!(self, Self::Map(_)) } + /// Check if this is an actor reference + #[must_use] + pub const fn is_actor_ref(&self) -> bool { + matches!(self, Self::ActorRef(_)) + } + + /// Check if this is a CRDT state + #[must_use] + pub const fn is_crdt_state(&self) -> bool { + matches!(self, Self::CRDTState(_, _)) + } + + /// Check if this is a hash + #[must_use] + pub const fn is_hash(&self) -> bool { + matches!(self, Self::Hash(_)) + } + // ═══════════════════════════════════════════════════════════════════ // EXTRACTORS // ═══════════════════════════════════════════════════════════════════ @@ -606,6 +661,36 @@ impl Value { } } + /// Extract actor reference ID + #[must_use] + pub const fn as_actor_ref(&self) -> Option { + if let Self::ActorRef(id) = self { + Some(*id) + } else { + None + } + } + + /// Extract CRDT state components + #[must_use] + pub fn as_crdt_state(&self) -> Option<(&Value, &Value)> { + if let Self::CRDTState(val, meta) = self { + Some((val, meta)) + } else { + None + } + } + + /// Extract hash bytes + #[must_use] + pub fn as_hash(&self) -> Option<&[u8]> { + if let Self::Hash(bytes) = self { + Some(bytes) + } else { + None + } + } + // ═══════════════════════════════════════════════════════════════════ // SECURITY ANALYSIS // ═══════════════════════════════════════════════════════════════════ @@ -697,6 +782,15 @@ impl std::fmt::Display for Value { } write!(f, "}}") } + Self::ActorRef(id) => write!(f, ""), + Self::CRDTState(val, meta) => write!(f, ""), + Self::Hash(bytes) => { + write!(f, "hash(")?; + for b in bytes { + write!(f, "{b:02x}")?; + } + write!(f, ")") + } } } } @@ -1057,4 +1151,62 @@ mod tests { ); assert_eq!(nested_pair.to_string(), "((1, 2), 3)"); } + + // ═══════════════════════════════════════════════════════════════════ + // JALINAN VALUE TESTS + // ═══════════════════════════════════════════════════════════════════ + + #[test] + fn test_actor_ref_value() { + let actor = Value::actor_ref(42); + assert!(actor.is_actor_ref()); + assert_eq!(actor.as_actor_ref(), Some(42)); + assert!(!actor.is_int()); + assert_eq!(actor.as_int(), None); + } + + #[test] + fn test_actor_ref_display() { + let actor = Value::actor_ref(7); + assert_eq!(actor.to_string(), ""); + } + + #[test] + fn test_hash_value() { + let hash = Value::hash(vec![0xde, 0xad, 0xbe, 0xef]); + assert!(hash.is_hash()); + assert_eq!(hash.as_hash(), Some(&[0xde, 0xad, 0xbe, 0xef][..])); + assert!(!hash.is_string()); + } + + #[test] + fn test_hash_display() { + let hash = Value::hash(vec![0x0a, 0xff]); + assert_eq!(hash.to_string(), "hash(0aff)"); + } + + #[test] + fn test_crdt_state_value() { + let crdt = Value::crdt_state(Value::int(10), Value::int(1)); + assert!(crdt.is_crdt_state()); + let (val, meta) = crdt.as_crdt_state().unwrap(); + assert_eq!(val, &Value::Int(10)); + assert_eq!(meta, &Value::Int(1)); + } + + #[test] + fn test_crdt_state_display() { + let crdt = Value::crdt_state(Value::int(5), Value::int(0)); + assert_eq!(crdt.to_string(), ""); + } + + #[test] + fn test_jalinan_values_are_public() { + assert_eq!(Value::actor_ref(1).security_level(), SecurityLevel::Public); + assert_eq!(Value::hash(vec![1, 2, 3]).security_level(), SecurityLevel::Public); + assert_eq!( + Value::crdt_state(Value::int(1), Value::int(0)).security_level(), + SecurityLevel::Public + ); + } } diff --git a/03_PROTO/crates/riina-codegen/src/wasm.rs b/03_PROTO/crates/riina-codegen/src/wasm.rs index e7e23886..00a584c0 100644 --- a/03_PROTO/crates/riina-codegen/src/wasm.rs +++ b/03_PROTO/crates/riina-codegen/src/wasm.rs @@ -373,8 +373,8 @@ impl WasmBackend { if !func.captures.is_empty() { for i in 0..func.captures.len() { let cap_var = VarId::new(i as u32); - if !var_to_local.contains_key(&cap_var) { - var_to_local.insert(cap_var, local_count); + if let std::collections::hash_map::Entry::Vacant(e) = var_to_local.entry(cap_var) { + e.insert(local_count); local_count += 1; } } @@ -1057,6 +1057,18 @@ impl WasmBackend { code.push(Op::I32Const as u8); wasm_encode::encode_sleb128(0, code); } + + // JALINAN Phase 6: stub — push 0 (unit/default) + Instruction::ActorDecl { .. } + | Instruction::ChoreographyDecl { .. } + | Instruction::ActorSpawn(_, _) + | Instruction::ActorSend(_, _) + | Instruction::ActorRecv(_) + | Instruction::CRDTMerge(_, _) + | Instruction::ContentHash(_) => { + code.push(Op::I32Const as u8); + wasm_encode::encode_sleb128(0, code); + } } // Store result if there is one.