diff --git a/src/protocol/ast.rs b/src/protocol/ast.rs index b94f81be548e9f95787e334020dfb98457e8d578..a633ce0c0c9ee19ebf048d71834b28bc23c58200 100644 --- a/src/protocol/ast.rs +++ b/src/protocol/ast.rs @@ -1646,6 +1646,7 @@ pub enum Method { Create, Length, Assert, + Print, UserFunction, UserComponent, } diff --git a/src/protocol/eval/executor.rs b/src/protocol/eval/executor.rs index 2e9946bb6cf62cb6001b1472e3a00f8fe9cae134..94cfa43e69728518a3983d7741342a2554ff8f56 100644 --- a/src/protocol/eval/executor.rs +++ b/src/protocol/eval/executor.rs @@ -613,16 +613,6 @@ impl Prompt { let msg_value = cur_frame.expr_values.pop_front().unwrap(); let deref_msg_value = self.store.maybe_read_ref(&msg_value).clone(); - match deref_msg_value { - Value::Message(_) => {}, - _ => { - return Err(EvalError::new_error_at_expr( - self, modules, heap, expr_id, - String::from("Calls to `put` are currently restricted to only send instances of `msg` types. This will change in the future") - )); - } - } - if ctx.did_put(port_id) { // We're fine, deallocate in case the expression value stack // held an owned value @@ -703,6 +693,23 @@ impl Prompt { return Ok(EvalContinuation::Inconsistent) } }, + Method::Print => { + // Convert the runtime-variant of a string + // into an actual string. + let value = cur_frame.expr_values.pop_front().unwrap(); + let value_heap_pos = value.as_string(); + let elements = &self.store.heap_regions[value_heap_pos as usize].values; + + let mut message = String::with_capacity(elements.len()); + for element in elements { + message.push(element.as_char()); + } + + // Drop the heap-allocated value from the + // store + self.store.drop_heap_pos(value_heap_pos); + println!("{}", message); + }, Method::UserComponent => { // This is actually handled by the evaluation // of the statement. diff --git a/src/protocol/eval/store.rs b/src/protocol/eval/store.rs index de98d77e70a5314dc0f094dd8b0dff51bc257d53..4b598afe4fe7b31f566f3e3df2ff7ac336064666 100644 --- a/src/protocol/eval/store.rs +++ b/src/protocol/eval/store.rs @@ -52,8 +52,14 @@ impl Store { pub(crate) fn clear_stack(&mut self, unique_stack_idx: usize) { let new_size = self.cur_stack_boundary + unique_stack_idx + 1; for idx in new_size..self.stack.len() { - self.drop_value(self.stack[idx].get_heap_pos()); - self.stack[idx] = Value::Unassigned; + let heap_pos = self.stack[idx].get_heap_pos(); + self.drop_value(heap_pos); + + // TODO: @remove, somewhat temporarily not clearing pure stack + // values for testing purposes. + if heap_pos.is_some() { + self.stack[idx] = Value::Unassigned; + } } } diff --git a/src/protocol/parser/mod.rs b/src/protocol/parser/mod.rs index e23b69169d47007e1d58c8c94af6f6786a6a0822..396e9cbde8900e54e00332b8b224483e1430188d 100644 --- a/src/protocol/parser/mod.rs +++ b/src/protocol/parser/mod.rs @@ -161,6 +161,12 @@ impl Parser { ], quick_type(&[PTV::Void]) )); + insert_builtin_function(&mut parser, "print", &[], |_id| ( + vec![ + ("message", quick_type(&[PTV::String])), + ], + quick_type(&[PTV::Void]) + )); parser } diff --git a/src/protocol/parser/pass_definitions.rs b/src/protocol/parser/pass_definitions.rs index 7a48893e72677a23ba5654dfbc30215d74d61db1..f2c983898b35c4ed1c5a62bec09888ea3fe50146 100644 --- a/src/protocol/parser/pass_definitions.rs +++ b/src/protocol/parser/pass_definitions.rs @@ -1451,13 +1451,14 @@ impl PassDefinitions { Definition::Function(function_definition) => { // Check whether it is a builtin function let method = if function_definition.builtin { - match function_definition.identifier.value.as_str() { - "get" => Method::Get, - "put" => Method::Put, - "fires" => Method::Fires, - "create" => Method::Create, - "length" => Method::Length, - "assert" => Method::Assert, + match function_definition.identifier.value.as_bytes() { + KW_FUNC_GET => Method::Get, + KW_FUNC_PUT => Method::Put, + KW_FUNC_FIRES => Method::Fires, + KW_FUNC_CREATE => Method::Create, + KW_FUNC_LENGTH => Method::Length, + KW_FUNC_ASSERT => Method::Assert, + KW_FUNC_PRINT => Method::Print, _ => unreachable!(), } } else { diff --git a/src/protocol/parser/pass_validation_linking.rs b/src/protocol/parser/pass_validation_linking.rs index dca88025f2fd36f261e77a8f9e67e650ac8d4023..f5bcae1bef2743e65581b156e5f61ddf92a70433 100644 --- a/src/protocol/parser/pass_validation_linking.rs +++ b/src/protocol/parser/pass_validation_linking.rs @@ -1056,6 +1056,7 @@ impl Visitor for PassValidationLinking { )); } }, + Method::Print => {}, Method::UserFunction => {}, Method::UserComponent => { expected_wrapping_new_stmt = true; diff --git a/src/protocol/parser/token_parsing.rs b/src/protocol/parser/token_parsing.rs index 6e43569461ecc32ceffc4df90fbfb7d746a59748..c1fe33866a320f12cb0a6aab04db481579eddae1 100644 --- a/src/protocol/parser/token_parsing.rs +++ b/src/protocol/parser/token_parsing.rs @@ -34,6 +34,7 @@ pub(crate) const KW_FUNC_FIRES: &'static [u8] = b"fires"; pub(crate) const KW_FUNC_CREATE: &'static [u8] = b"create"; pub(crate) const KW_FUNC_LENGTH: &'static [u8] = b"length"; pub(crate) const KW_FUNC_ASSERT: &'static [u8] = b"assert"; +pub(crate) const KW_FUNC_PRINT: &'static [u8] = b"print"; // Keywords - statements pub(crate) const KW_STMT_CHANNEL: &'static [u8] = b"channel"; @@ -535,7 +536,7 @@ fn is_reserved_expression_keyword(text: &[u8]) -> bool { match text { KW_LET | KW_CAST | KW_LIT_TRUE | KW_LIT_FALSE | KW_LIT_NULL | - KW_FUNC_GET | KW_FUNC_PUT | KW_FUNC_FIRES | KW_FUNC_CREATE | KW_FUNC_ASSERT | KW_FUNC_LENGTH => true, + KW_FUNC_GET | KW_FUNC_PUT | KW_FUNC_FIRES | KW_FUNC_CREATE | KW_FUNC_ASSERT | KW_FUNC_LENGTH | KW_FUNC_PRINT => true, _ => false, } } diff --git a/src/runtime2/messages.rs b/src/runtime2/messages.rs index c2cde8548f2bd93cf5fa8058a1c975851d2ba318..191b5ebb98e9f84142b405cec1ae18d2c3f88615 100644 --- a/src/runtime2/messages.rs +++ b/src/runtime2/messages.rs @@ -50,7 +50,7 @@ impl ConnectorInbox { // If it is the first message on the port, then we cannot possible have // a previous port mapping on that port. let port_action = PortAction{ - port_id: message.sending_port.0.u32_suffix, + port_id: message.receiving_port.0.u32_suffix, prev_branch_id: message.peer_prev_branch_id, }; diff --git a/src/runtime2/runtime.rs b/src/runtime2/runtime.rs index b75d686ad2cea3fa8f9f1fe36eb812aca2da7080..e276852bdc0eea566fe69b86c7ebec6d1b25d692 100644 --- a/src/runtime2/runtime.rs +++ b/src/runtime2/runtime.rs @@ -9,23 +9,24 @@ use crate::protocol::eval::*; use super::messages::*; -enum AddComponentError { +#[derive(Debug)] +pub enum AddComponentError { ModuleDoesNotExist, ConnectorDoesNotExist, InvalidArgumentType(usize), // value is index of (first) invalid argument } -struct PortDesc { +pub(crate) struct PortDesc { id: u32, peer_id: u32, owning_connector_id: Option, is_getter: bool, // otherwise one can only call `put` } -struct ConnectorDesc { +pub(crate) struct ConnectorDesc { id: u32, in_sync: bool, - branches: Vec, // first one is always non-speculative one + pub(crate) branches: Vec, // first one is always non-speculative one spec_branches_active: VecDeque, // branches that can be run immediately spec_branches_pending_receive: HashMap>, // from port_id to branch index spec_branches_done: Vec, @@ -38,14 +39,11 @@ impl ConnectorDesc { /// Creates a new connector description. Implicit assumption is that there /// is one (non-sync) branch that can be immediately executed. fn new(id: u32, component_state: ComponentState, owned_ports: Vec) -> Self { - let mut branches_active = VecDeque::new(); - branches_active.push_back(0); - Self{ id, in_sync: false, branches: vec![BranchDesc::new_non_sync(component_state, owned_ports)], - spec_branches_active: branches_active, + spec_branches_active: VecDeque::new(), spec_branches_pending_receive: HashMap::new(), spec_branches_done: Vec::new(), last_checked_done: 0, @@ -56,15 +54,16 @@ impl ConnectorDesc { } #[derive(Debug, PartialEq, Eq)] -enum BranchState { +pub(crate) enum BranchState { RunningNonSync, // regular running non-speculative branch RunningSync, // regular running speculative branch BranchPoint, // branch which ended up being a branching point ReachedEndSync, // branch that successfully reached the end-sync point, is a possible local solution Failed, // branch that became inconsistent + Finished, // branch (necessarily non-sync) that reached end of code } -#[derive(Clone)] +#[derive(Debug, Clone)] struct BranchPortDesc { last_registered_index: Option, // if putter, then last sent branch ID, if getter, then last received branch ID num_times_fired: u32, // number of puts/gets on this port @@ -75,11 +74,11 @@ struct BranchContext { pending_channel: Option<(Value, Value)>, } -struct BranchDesc { +pub(crate) struct BranchDesc { index: u32, parent_index: Option, - code_state: ComponentState, - branch_state: BranchState, + pub(crate) code_state: ComponentState, + pub(crate) branch_state: BranchState, owned_ports: Vec, message_inbox: HashMap<(PortId, u32), ValueGroup>, // from (port id, 1-based recv index) to received value port_mapping: HashMap, @@ -159,11 +158,11 @@ struct ProposedSolution { } // TODO: @performance, use freelists+ids instead of HashMaps -struct Runtime { +pub struct Runtime { protocol: Arc, - ports: HashMap, + pub(crate) ports: HashMap, port_counter: u32, - connectors: HashMap, + pub(crate) connectors: HashMap, connector_counter: u32, connectors_active: VecDeque, } @@ -210,7 +209,10 @@ impl Runtime { } }; - // Make sure supplied values (and types) are correct + // Make sure supplied values (and types) are correct. At the same time + // modify the port IDs such that they contain the ID of the connector + // we're about the create. + let component_id = self.generate_connector_id(); let mut ports = Vec::with_capacity(values.values.len()); for (value_idx, value) in values.values.iter().enumerate() { @@ -222,24 +224,35 @@ impl Runtime { return Err(ACE::InvalidArgumentType(value_idx)) } - ports.push(*port_id); + ports.push(PortId(Id{ + connector_id: component_id, + u32_suffix: port_id.0.u32_suffix, + })); }, Value::Output(port_id) => { if *polarity != Polarity::Putter { return Err(ACE::InvalidArgumentType(value_idx)) } - ports.push(*port_id); + ports.push(PortId(Id{ + connector_id: component_id, + u32_suffix: port_id.0.u32_suffix + })); }, _ => return Err(ACE::InvalidArgumentType(value_idx)) } } - // Instantiate the component - let component_id = self.generate_connector_id(); + // Instantiate the component, and mark the ports as being owned by the + // newly instantiated component let component_state = self.protocol.new_component(module.as_bytes(), procedure.as_bytes(), &ports); let ports = ports.into_iter().map(|v| v.0.u32_suffix).collect(); + for port in &ports { + let desc = self.ports.get_mut(port).unwrap(); + desc.owning_connector_id = Some(component_id); + } + self.connectors.insert(component_id, ConnectorDesc::new(component_id, component_state, ports)); self.connectors_active.push_back(component_id); @@ -274,7 +287,7 @@ impl Runtime { /// connector should be run again in the future, and return `false` if the /// connector has terminated. Note that a terminated connector still /// requires cleanup. - pub fn run_connector(&mut self, connector_id: u32) -> Scheduling { + fn run_connector(&mut self, connector_id: u32) -> Scheduling { let desc = self.connectors.get_mut(&connector_id).unwrap(); if desc.in_sync { @@ -293,9 +306,9 @@ impl Runtime { let branch_index = desc.spec_branches_active.pop_front().unwrap(); let branch = &mut desc.branches[branch_index as usize]; + debug_assert_eq!(branch_index, branch.index); // Run this particular branch to a next blocking point - // TODO: PERSISTENT RUN CTX let mut run_context = Context{ inbox: &branch.message_inbox, port_mapping: &branch.port_mapping, @@ -346,6 +359,8 @@ impl Runtime { debug_assert!(branch.owned_ports.contains(&port_id.0.u32_suffix)); let mut insert_in_pending_receive = false; + println!("DEBUG: Connector {} performing get on {:#?}", connector_id, port_id); + match branch.port_mapping.entry(port_id) { Entry::Vacant(entry) => { // No entry yet, so force to firing @@ -372,6 +387,8 @@ impl Runtime { } } + println!("DEBUG: insert = {}, port mapping is now {:#?}", insert_in_pending_receive, &branch.port_mapping); + if insert_in_pending_receive { // Perform the insert match desc.spec_branches_pending_receive.entry(port_id) { @@ -409,8 +426,8 @@ impl Runtime { RunResult::BranchAtSyncEnd => { // Check the branch for any ports that were not used and // insert them in the port mapping as not having fired. - for port_index in branch.owned_ports.iter().copied() { - let port_id = PortId(Id{ connector_id: desc.id, u32_suffix: port_index }); + for port_id in branch.owned_ports.iter().copied() { + let port_id = PortId(Id{ connector_id: desc.id, u32_suffix: port_id }); if let Entry::Vacant(entry) = branch.port_mapping.entry(port_id) { entry.insert(BranchPortDesc { last_registered_index: None, @@ -430,6 +447,7 @@ impl Runtime { // Branch just performed a `put()`. Check if we have // assigned the port value and if so, if it is // consistent. + println!("DEBUG: Connector {} performing put on {:#?}", connector_id, port_id); let mut can_put = true; branch.branch_context.just_called_did_put = true; match branch.port_mapping.entry(port_id) { @@ -459,10 +477,12 @@ impl Runtime { } } } + println!("DEBUG: can_put = {}, port mapping is now {:#?}", can_put, &branch.port_mapping); if can_put { // Actually put the message in the outbox let port_desc = self.ports.get(&port_id.0.u32_suffix).unwrap(); + debug_assert_eq!(port_desc.owning_connector_id.unwrap(), connector_id); let peer_id = port_desc.peer_id; let peer_desc = self.ports.get(&peer_id).unwrap(); debug_assert!(peer_desc.owning_connector_id.is_some()); @@ -479,7 +499,7 @@ impl Runtime { sending_port: port_id, receiving_port: peer_id, peer_prev_branch_id: None, - peer_cur_branch_id: 0, + peer_cur_branch_id: branch_index, message: value_group, }); @@ -506,9 +526,9 @@ impl Runtime { fn run_connector_regular_mode(&mut self, connector_id: u32) -> Scheduling { // Retrieve the connector and the branch (which is always the first one, // since we assume we're not running in sync-mode). - // TODO: CONTINUE HERE, PERSEISTENT BRANCH CONTEXT let desc = self.connectors.get_mut(&connector_id).unwrap(); debug_assert!(!desc.in_sync); + debug_assert!(desc.spec_branches_active.is_empty()); debug_assert_eq!(desc.branches.len(), 1); let branch = &mut desc.branches[0]; @@ -522,7 +542,10 @@ impl Runtime { let run_result = branch.code_state.run(&mut run_context, &self.protocol); match run_result { - RunResult::ComponentTerminated => return Scheduling::NotNow, + RunResult::ComponentTerminated => { + branch.branch_state = BranchState::Finished; + return Scheduling::NotNow + }, RunResult::ComponentAtSyncStart => { // Prepare for sync execution Self::prepare_branch_for_sync(desc); @@ -927,7 +950,6 @@ impl Runtime { // And reset the connector's state for further execution connector.in_sync = false; connector.spec_branches_active.clear(); - connector.spec_branches_active.push_back(0); connector.spec_branches_pending_receive.clear(); connector.spec_branches_done.clear(); connector.last_checked_done = 0; diff --git a/src/runtime2/tests/mod.rs b/src/runtime2/tests/mod.rs index c662f7760439d3068db4b43f1e79da64d2ed32bf..f2438d5a42ba702860ddfa35b7b77e2b86ff1bf2 100644 --- a/src/runtime2/tests/mod.rs +++ b/src/runtime2/tests/mod.rs @@ -1,4 +1,129 @@ +use std::sync::Arc; + +use super::runtime::*; +use crate::ProtocolDescription; +use crate::protocol::eval::*; + #[test] -fn testing_runtime2() { - println!("YESH!"); +fn test_single_message() { + // Simple test were we have a `putter` component, which will simply send a + // single message (a boolean), and a `getter` component, which will receive + // that message. + // We will write this behaviour in the various ways that the language + // currently allows. We will cheat a bit by peeking into the runtime to make + // sure that the getter actually received the message. + // TODO: Expose ports to a "native application" + + fn check_store_bool(value: &Value, expected: bool) { + if let Value::Bool(value) = value { + assert_eq!(*value, expected); + } else { + assert!(false); + } + } + fn run_putter_getter(code: &[u8]) { + // Compile code + let pd = ProtocolDescription::parse(code) + .expect("code successfully compiles"); + let pd = Arc::new(pd); + + // Construct runtime and the appropriate ports and connectors + let mut rt = Runtime::new(pd); + let (put_port, get_port) = rt.add_channel(); + + let mut put_args = ValueGroup::new_stack(vec![ + put_port, + ]); + rt.add_component("", "putter", put_args) + .expect("'putter' component created"); + + let mut get_args = ValueGroup::new_stack(vec![ + get_port, + ]); + rt.add_component("", "getter", get_args) + .expect("'getter' component created"); + + // Run until completion + rt.run(); + + // Check for success (the 'received' and 'did_receive" flags) + let getter_component = rt.connectors.get(&1).unwrap(); + let branch = &getter_component.branches[0]; + assert_eq!(branch.branch_state, BranchState::Finished); + + // Note: with the stack structure of the store, the first entry is the + // "previous stack pos" and the second one is the input port passed to + // the procedure. Hence the third/fourth entries are the boolean + // variables on the stack. + check_store_bool(&branch.code_state.prompt.store.stack[2], true); + check_store_bool(&branch.code_state.prompt.store.stack[3], true); + } + + // Without `fires()`, just a single valid behaviour + run_putter_getter( + b"primitive putter(out put_here) { + synchronous { + put(put_here, true); + } + } + + primitive getter(in get_here) { + bool received = false; + bool did_receive = false; + + synchronous { + received = get(get_here); + if (received) { + print(\"value was 'true'\"); + } else { + print(\"value was 'false'\"); + } + did_receive = true; + } + }"); + + // With `fires()`, but eliminating on the putter side + run_putter_getter( + b"primitive putter(out put_here) { + synchronous { + if (!fires(put_here)) { + assert(false); + } else { + put(put_here, true); + } + } + } + + primitive getter(in get_here) { + bool received = false; bool did_receive = false; + synchronous { + if (fires(get_here)) { + received = get(get_here); + did_receive = true; + } + } + }"); + + // With `fires()`, but eliminating on the getter side + run_putter_getter( + b"primitive putter(out put_here) { + synchronous { + if (fires(put_here)) { + put(put_here, true); + } + } + } + + primitive getter(in get_here) { + bool received = false; bool did_receive = false; + synchronous { + if (fires(get_here)) { + received = get(get_here); + did_receive = true; + } else { + assert(false); + } + } + }" + ); } \ No newline at end of file