diff --git a/src/protocol/ast.rs b/src/protocol/ast.rs index 41106ef2a24594e50d98ab3af8a142076c69686a..d13858742e9dfebef5e58077d83232985bbdd954 100644 --- a/src/protocol/ast.rs +++ b/src/protocol/ast.rs @@ -1301,7 +1301,7 @@ pub struct ForkStatement { #[derive(Debug, Clone)] pub struct EndForkStatement { - pub this: EndForKStatementId, + pub this: EndForkStatementId, pub start_fork: ForkStatementId, pub next: StatementId, } diff --git a/src/protocol/eval/executor.rs b/src/protocol/eval/executor.rs index 68dc3eec80a2ebb90159a4d62401f7eb6c5a727d..aded401d18af5b9c3253fe43f2183f73167db1b4 100644 --- a/src/protocol/eval/executor.rs +++ b/src/protocol/eval/executor.rs @@ -200,6 +200,7 @@ pub enum EvalContinuation { SyncBlockEnd, NewComponent(DefinitionId, i32, ValueGroup), NewChannel, + NewFork, BlockFires(PortId), BlockGet(PortId), Put(PortId, Value), @@ -886,6 +887,33 @@ impl Prompt { Ok(EvalContinuation::SyncBlockEnd) }, + Statement::Fork(stmt) => { + if stmt.right_body.is_none() { + // No reason to fork + cur_frame.position = stmt.left_body.upcast(); + } else { + // Need to fork + if let Some(go_left) = ctx.get_fork() { + // Runtime has created a fork + if go_left { + cur_frame.position = stmt.left_body.upcast(); + } else { + cur_frame.position = stmt.right_body.unwrap().upcast(); + } + } else { + // Request the runtime to create a fork of the current + // branch + return Ok(EvalContinuation::NewFork); + } + } + + Ok(EvalContinuation::Stepping) + }, + Statement::EndFork(stmt) => { + cur_frame.position = stmt.next; + + Ok(EvalContinuation::Stepping) + } Statement::Return(_stmt) => { debug_assert!(heap[cur_frame.definition].is_function()); debug_assert_eq!(cur_frame.expr_values.len(), 1, "expected one expr value for return statement"); diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index c806614529f4e902549e03aff14271826afe2623..44988824b0ca822a44d4640b61b480ee6eb19ba5 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -287,6 +287,7 @@ pub trait RunContext { fn did_put(&mut self, port: PortId) -> bool; fn get(&mut self, port: PortId) -> Option; // None if still waiting on message fn fires(&mut self, port: PortId) -> Option; // None if not yet branched + fn get_fork(&mut self) -> Option; // None if not yet forked fn get_channel(&mut self) -> Option<(Value, Value)>; // None if not yet prepared } @@ -302,6 +303,7 @@ pub enum RunResult { BranchMissingPortState(PortId), // branch doesn't know about port firing BranchMissingPortValue(PortId), // branch hasn't received message on input port yet BranchAtSyncEnd, + BranchFork, BranchPut(PortId, ValueGroup), } @@ -329,6 +331,8 @@ impl ComponentState { return RR::NewComponent(definition_id, monomorph_idx, args), EC::NewChannel => return RR::NewChannel, + EC::NewFork => + return RR::BranchFork, EC::BlockFires(port_id) => return RR::BranchMissingPortState(port_id), EC::BlockGet(port_id) => return RR::BranchMissingPortValue(port_id), EC::Put(port_id, value) => { @@ -398,6 +402,7 @@ impl ComponentState { // to the runtime unreachable!(); }, + EvalContinuation::NewFork => unreachable!(), // Outside synchronous blocks, no fires/get/put happens EvalContinuation::BlockFires(_) => unreachable!(), EvalContinuation::BlockGet(_) => unreachable!(), @@ -431,6 +436,7 @@ impl ComponentState { // Not possible to create component in sync block EvalContinuation::NewComponent(_, _, _) => unreachable!(), EvalContinuation::NewChannel => unreachable!(), + EvalContinuation::NewFork => unreachable!(), EvalContinuation::BlockFires(port) => { return SyncBlocker::CouldntCheckFiring(port); }, @@ -524,6 +530,11 @@ impl RunContext for EvalContext<'_> { EvalContext::Sync(_) => unreachable!(), } } + + fn get_fork(&mut self) -> Option { + // Never actually used in the old runtime + return None; + } } // TODO: @remove once old runtime has disappeared diff --git a/src/protocol/parser/pass_validation_linking.rs b/src/protocol/parser/pass_validation_linking.rs index e6564ee6715297d1f90008489874a103bc566b45..f7362e0996add4819ae92058adb09dff264b9f64 100644 --- a/src/protocol/parser/pass_validation_linking.rs +++ b/src/protocol/parser/pass_validation_linking.rs @@ -414,6 +414,14 @@ impl Visitor for PassValidationLinking { let left_body_id = fork_stmt.left_body; let right_body_id = fork_stmt.right_body; + // Fork statements may only occur inside sync blocks + if self.in_sync.is_invalid() { + return Err(ParseError::new_error_str_at_span( + &ctx.module().source, fork_stmt.span, + "Forking may only occur inside sync blocks" + )); + } + // Visit the respective bodies. Like the if statement, a fork statement // does not have a single static subsequent statement. It forks and then // each fork has a different next statement.