From 809a46ba091e1fc3512837ccb144ee2b251afe19 Mon Sep 17 00:00:00 2001 From: tjjfvi Date: Wed, 28 Feb 2024 16:55:38 -0500 Subject: [PATCH 1/5] reorg files --- benches/benches.rs | 10 +- src/ast.rs | 8 +- src/host.rs | 411 +---- src/host/calc_labels.rs | 223 +++ src/host/encode_def.rs | 105 ++ src/host/{encode.rs => encode_net.rs} | 2 +- src/host/readback.rs | 79 + src/main.rs | 77 +- src/run.rs | 2221 +------------------------ src/run/addr.rs | 53 + src/run/allocator.rs | 100 ++ src/run/def.rs | 267 +++ src/run/instruction.rs | 288 ++++ src/run/interact.rs | 445 +++++ src/run/linker.rs | 341 ++++ src/run/net.rs | 153 ++ src/run/node.rs | 86 + src/run/parallel.rs | 153 ++ src/run/port.rs | 224 +++ src/run/wire.rs | 94 ++ src/util.rs | 261 +-- src/util/apply_tree.rs | 57 + src/util/bi_enum.rs | 85 + src/util/create_var.rs | 56 + src/util/deref.rs | 17 + src/util/stats.rs | 35 + tests/cli.rs | 2 +- tests/fuzz.rs | 12 +- tests/loaders/mod.rs | 2 +- tests/tests.rs | 2 +- 30 files changed, 2988 insertions(+), 2881 deletions(-) create mode 100644 src/host/calc_labels.rs create mode 100644 src/host/encode_def.rs rename src/host/{encode.rs => encode_net.rs} (98%) create mode 100644 src/host/readback.rs create mode 100644 src/run/addr.rs create mode 100644 src/run/allocator.rs create mode 100644 src/run/def.rs create mode 100644 src/run/instruction.rs create mode 100644 src/run/interact.rs create mode 100644 src/run/linker.rs create mode 100644 src/run/net.rs create mode 100644 src/run/node.rs create mode 100644 src/run/parallel.rs create mode 100644 src/run/port.rs create mode 100644 src/run/wire.rs create mode 100644 src/util/apply_tree.rs create mode 100644 src/util/bi_enum.rs create mode 100644 src/util/create_var.rs create mode 100644 src/util/deref.rs create mode 100644 src/util/stats.rs diff --git a/benches/benches.rs b/benches/benches.rs index 96d1dd0d..205b98fa 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -2,7 +2,7 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use hvmc::{ ast::{Book, Net}, host::Host, - run::{Net as RtNet, Strict}, + run::{Heap, Net as RtNet, Strict}, }; use std::{ fs, @@ -59,7 +59,7 @@ fn run_file(path: &PathBuf, group: Option, c: &mut Criterion) { } fn benchmark(file_name: &str, book: Book, c: &mut Criterion) { - let area = RtNet::::init_heap(1 << 29); + let area = Heap::new(1 << 29); let host = Host::new(&book); c.bench_function(file_name, |b| { b.iter(|| { @@ -71,7 +71,7 @@ fn benchmark(file_name: &str, book: Book, c: &mut Criterion) { } fn benchmark_group(file_name: &str, group: String, book: Book, c: &mut Criterion) { - let area = RtNet::::init_heap(1 << 29); + let area = Heap::new(1 << 29); let host = Host::new(&book); c.benchmark_group(group).bench_function(file_name, |b| { @@ -97,8 +97,8 @@ fn interact_benchmark(c: &mut Criterion) { for (name, redex) in cases { let mut book = Book::default(); - book.insert("main".to_string(), Net { root: Era, rdex: vec![redex] }); - let area = RtNet::::init_heap(1 << 24); + book.insert("main".to_string(), Net { root: Era, redexes: vec![redex] }); + let area = Heap::new(1 << 24); let host = Host::new(&book); group.bench_function(name, |b| { b.iter(|| { diff --git a/src/ast.rs b/src/ast.rs index 79575083..6ce572a9 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -27,14 +27,14 @@ deref!(Book => self.nets: BTreeMap); /// An AST node representing an interaction net with one free port. /// /// The tree connected to the free port is stored in `root`. The active pairs in -/// the net -- trees connected by their roots -- are stored in `rdex`. +/// the net -- trees connected by their roots -- are stored in `redexes`. /// /// (The wiring connecting the leaves of all the trees is represented within the /// trees via pairs of [`Tree::Var`] nodes with the same name.) #[derive(Clone, Hash, PartialEq, Eq, Debug, Default)] pub struct Net { pub root: Tree, - pub rdex: Vec<(Tree, Tree)>, + pub redexes: Vec<(Tree, Tree)>, } /// An AST node representing an interaction net tree. @@ -133,7 +133,7 @@ impl<'i> Parser<'i> { let tree2 = self.parse_tree()?; rdex.push((tree1, tree2)); } - Ok(Net { root, rdex }) + Ok(Net { root, redexes: rdex }) } fn parse_tree(&mut self) -> Result { @@ -337,7 +337,7 @@ impl fmt::Display for Book { impl fmt::Display for Net { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", &self.root)?; - for (a, b) in &self.rdex { + for (a, b) in &self.redexes { write!(f, "\n& {a} ~ {b}")?; } Ok(()) diff --git a/src/host.rs b/src/host.rs index 44ec5e8d..f706cdf2 100644 --- a/src/host.rs +++ b/src/host.rs @@ -11,7 +11,13 @@ use std::{ ops::{Deref, DerefMut, RangeFrom}, }; -mod encode; +mod calc_labels; +mod encode_def; +mod encode_net; +mod readback; + +use calc_labels::calculate_label_sets; +use encode_def::ast_net_to_instructions; /// Stores a bidirectional mapping between names and runtime defs. #[derive(Default)] @@ -80,407 +86,4 @@ impl Host { self.back.insert(Port::new_ref(&def).addr(), name.to_owned()); self.defs.insert(name.to_owned(), def); } - - /// Creates an ast tree from a wire in a runtime net. - pub fn readback_tree(&self, wire: &run::Wire) -> Tree { - ReadbackState { host: self, vars: Default::default(), var_id: 0 .. }.read_wire(wire.clone()) - } - - /// Creates an ast net from a runtime net. - /// - /// Note that vicious circles and disconnected subnets will not be in the - /// resulting ast net, as it is impossible to read these back from the runtime - /// net representation. In the case of vicious circles, this may result in - /// unbound variables. - pub fn readback(&self, rt_net: &run::Net) -> Net { - let mut state = ReadbackState { host: self, vars: Default::default(), var_id: 0 .. }; - let mut net = Net::default(); - - net.root = state.read_wire(rt_net.root.clone()); - - for (a, b) in &rt_net.rdex { - net.rdex.push((state.read_port(a.clone(), None), state.read_port(b.clone(), None))) - } - - net - } -} - -/// See [`Host::readback`]. -struct ReadbackState<'a> { - host: &'a Host, - vars: HashMap, - var_id: RangeFrom, -} - -impl<'a> ReadbackState<'a> { - /// Reads a tree out from a given `wire`. - fn read_wire(&mut self, wire: Wire) -> Tree { - let port = wire.load_target(); - self.read_port(port, Some(wire)) - } - /// Reads a tree out from a given `port`. If this is a var port, the - /// `wire` this port was reached from must be supplied to key into the - /// `vars` map. - fn read_port(&mut self, port: Port, wire: Option) -> Tree { - match port.tag() { - Tag::Var => { - let key = wire.unwrap().addr().min(port.addr()); - Tree::Var { - nam: create_var(match self.vars.entry(key) { - Entry::Occupied(e) => e.remove(), - Entry::Vacant(e) => *e.insert(self.var_id.next().unwrap()), - }), - } - } - Tag::Red => self.read_wire(port.wire()), - Tag::Ref if port == Port::ERA => Tree::Era, - Tag::Ref => Tree::Ref { nam: self.host.back[&port.addr()].clone() }, - Tag::Num => Tree::Num { val: port.num() }, - Tag::Op2 => { - let opr = port.op(); - let node = port.traverse_node(); - Tree::Op2 { opr, lft: Box::new(self.read_wire(node.p1)), rgt: Box::new(self.read_wire(node.p2)) } - } - Tag::Op1 => { - let opr = port.op(); - let node = port.traverse_op1(); - Tree::Op1 { opr, lft: node.num.num(), rgt: Box::new(self.read_wire(node.p2)) } - } - Tag::Ctr => { - let node = port.traverse_node(); - Tree::Ctr { lab: node.lab, lft: Box::new(self.read_wire(node.p1)), rgt: Box::new(self.read_wire(node.p2)) } - } - Tag::Mat => { - let node = port.traverse_node(); - Tree::Mat { sel: Box::new(self.read_wire(node.p1)), ret: Box::new(self.read_wire(node.p2)) } - } - } - } -} - -/// Converts an ast net to a list of instructions to create the net. -/// -/// `defs` must be populated with every `Ref` node that may appear in the net. -fn ast_net_to_instructions(defs: &HashMap, net: &Net) -> Vec { - let mut state = - State { defs, scope: Default::default(), instr: Default::default(), end: Default::default(), next_index: 1 }; - - state.visit_tree(&net.root, TrgId::new(0)); - - net.rdex.iter().for_each(|(a, b)| state.visit_redex(a, b)); - - assert!(state.scope.is_empty(), "unbound variables: {:?}", state.scope.keys()); - - state.instr.append(&mut state.end); - - return state.instr; - - struct State<'a> { - defs: &'a HashMap, - scope: HashMap<&'a str, TrgId>, - instr: Vec, - end: Vec, - next_index: usize, - } - - impl<'a> State<'a> { - fn id(&mut self) -> TrgId { - let i = self.next_index; - self.next_index += 1; - TrgId::new(i) - } - fn visit_redex(&mut self, a: &'a Tree, b: &'a Tree) { - let (port, tree) = match (a, b) { - (Tree::Era, t) | (t, Tree::Era) => (Port::ERA, t), - (Tree::Ref { nam }, t) | (t, Tree::Ref { nam }) => (Port::new_ref(&self.defs[nam]), t), - (Tree::Num { val }, t) | (t, Tree::Num { val }) => (Port::new_num(*val), t), - (t, u) => { - let av = self.id(); - let aw = self.id(); - let bv = self.id(); - let bw = self.id(); - self.next_index += 4; - self.instr.push(Instruction::Wires { av, aw, bv, bw }); - self.end.push(Instruction::Link { a: aw, b: bw }); - self.visit_tree(t, av); - self.visit_tree(u, bv); - return; - } - }; - let trg = self.id(); - self.instr.push(Instruction::Const { port, trg }); - self.visit_tree(tree, trg); - } - fn visit_tree(&mut self, tree: &'a Tree, trg: TrgId) { - match tree { - Tree::Era => { - self.instr.push(Instruction::LinkConst { trg, port: Port::ERA }); - } - Tree::Ref { nam } => { - self.instr.push(Instruction::LinkConst { trg, port: Port::new_ref(&self.defs[nam]) }); - } - Tree::Num { val } => { - self.instr.push(Instruction::LinkConst { trg, port: Port::new_num(*val) }); - } - Tree::Var { nam } => match self.scope.entry(nam) { - Entry::Occupied(e) => { - let other = e.remove(); - self.instr.push(Instruction::Link { a: other, b: trg }); - } - Entry::Vacant(e) => { - e.insert(trg); - } - }, - Tree::Ctr { lab, lft, rgt } => { - let l = self.id(); - let r = self.id(); - self.instr.push(Instruction::Ctr { lab: *lab, trg, lft: l, rgt: r }); - self.visit_tree(lft, l); - self.visit_tree(rgt, r); - } - Tree::Op2 { opr, lft, rgt } => { - let l = self.id(); - let r = self.id(); - self.instr.push(Instruction::Op2 { op: *opr, trg, lft: l, rgt: r }); - self.visit_tree(lft, l); - self.visit_tree(rgt, r); - } - Tree::Op1 { opr, lft, rgt } => { - let r = self.id(); - self.instr.push(Instruction::Op1 { op: *opr, num: *lft, trg, rgt: r }); - self.visit_tree(rgt, r); - } - Tree::Mat { sel, ret } => { - let l = self.id(); - let r = self.id(); - self.instr.push(Instruction::Mat { trg, lft: l, rgt: r }); - self.visit_tree(sel, l); - self.visit_tree(ret, r); - } - } - } - } -} - -/// Calculates the labels used in each definition of a book. -/// -/// # Background: Naive Algorithms -/// -/// The simplest algorithm to calculate labels would be to go to each def, -/// recursively traverse the tree (going into references), and collect all of -/// the labels. -/// -/// Now, this algorithm will not terminate for recursive definitions, but fixing -/// this is relatively simple: don't enter a reference twice in one traversal. -/// -/// This modified algorithm will work in all cases, but it's not very efficient. -/// Notably, it can take quadratic time; for example, it requires traversing -/// each of these refs 3 times: -/// ```text -/// @foo = (* @bar) -/// @bar = (* @baz) -/// @baz = (* @foo) -/// ``` -/// -/// This can be resolved with memoization. However, a simple memoization pass -/// will not work here, due to the cycle avoidance algorithm. For example, -/// consider the following program: -/// ```text -/// @foo = (@bar {1 x x}) -/// @bar = ({2 x x} @foo) -/// ``` -/// -/// A simple memoization pass would go as follows: -/// - calculate the labels for `@foo` -/// - encounter `@bar` -/// - this has not yet been processed, so calculate the labels for it -/// - encounter `{2 ...}`; add 2 to the label set -/// - encounter `@foo` -- this has already been visited, so skip it (cycle -/// avoidance) -/// - add all the labels from `@bar` (just 2) to `@foo` -/// - encounter `{1 ...}`; add 1 to the label set -/// - calculate the labels for `@bar` -- this has already been visited, so skip -/// it (memoization) -/// -/// The end result of this is `@foo: {1, 2}, @bar: {2}` -- `@bar` is missing -/// `1`. -/// -/// Instead, a two-phase memoization approach is needed. -/// -/// # Implemented Here: Two-Phase Algorithm -/// -/// Each phase follows a similar procedure as the simple memoization algorithm, -/// with a few modifications. (Henceforth, the *head node* of a cycle is the -/// first node in a cycle reached in a traversal.) -/// - in the first phase: -/// - when we process a node involved in a cycle: -/// - if it is not the head node of a cycle it is involved in, instead of -/// saving the computed label set to the memo, we store a placeholder -/// `Cycle` value into the memo -/// - otherwise, after storing the label set in the memo, we enter the -/// second phase, retraversing the node -/// - when we encounter a `Cycle` value in the memo, we treat it akin to an -/// empty label set -/// - in the second phase, we treat `Cycle` values as though they were missing -/// entries in the memo -/// -/// In the first phase, to know when we are processing nodes in a cycle, we keep -/// track of the depth of the traversal, and every time we enter a ref, we store -/// `Cycle(depth)` into the memo -- so if we encounter this in the traversal of -/// the children, we know that that node is involved in a cycle. Additionally, -/// when we exit a node, we return the *head depth* of the result that was -/// calculated -- the least depth of a node involved in a cycle with the node -/// just processed. Comparing this with the depth gives us the information -/// needed for the special rules. -/// -/// This algorithm runs in linear time (as refs are traversed at most twice), -/// and requires no more space than the naive algorithm. -fn calculate_label_sets<'a>(book: &'a Book, host: &Host) -> impl Iterator { - let mut state = State { book, host, labels: HashMap::with_capacity(book.len()) }; - - for name in book.keys() { - state.visit_def(name, Some(0), None); - } - - return state.labels.into_iter().map(|(nam, lab)| match lab { - LabelState::Done(lab) => (nam, lab), - _ => unreachable!(), - }); - - struct State<'a, 'b> { - book: &'a Book, - host: &'b Host, - labels: HashMap<&'a str, LabelState>, - } - - #[derive(Debug)] - enum LabelState { - Done(LabSet), - /// Encountering this node indicates participation in a cycle with the given - /// head depth. - Cycle(usize), - } - - /// All of these methods share a similar signature: - /// - `depth` is optional; `None` indicates that this is the second processing - /// pass (where the depth is irrelevant, as all cycles have been detected) - /// - `out`, if supplied, will be unioned with the result of this traversal - /// - the return value indicates the head depth, as defined above (or an - /// arbitrary value `>= depth` if no cycles are involved) - impl<'a, 'b> State<'a, 'b> { - fn visit_def(&mut self, key: &'a str, depth: Option, out: Option<&mut LabSet>) -> usize { - match self.labels.entry(key) { - Entry::Vacant(e) => { - e.insert(LabelState::Cycle(depth.unwrap())); - self.calc_def(key, depth, out) - } - Entry::Occupied(mut e) => match e.get_mut() { - LabelState::Done(labs) => { - if let Some(out) = out { - out.union(labs); - } - usize::MAX - } - LabelState::Cycle(d) if depth.is_some() => *d, - LabelState::Cycle(_) => { - e.insert(LabelState::Done(LabSet::default())); - self.calc_def(key, depth, out) - } - }, - } - } - - fn calc_def(&mut self, key: &'a str, depth: Option, out: Option<&mut LabSet>) -> usize { - let mut labs = LabSet::default(); - let head_depth = self.visit_within_def(key, depth, Some(&mut labs)); - if let Some(out) = out { - out.union(&labs); - } - if depth.is_some_and(|x| x > head_depth) { - self.labels.insert(key, LabelState::Cycle(head_depth)); - } else { - self.labels.insert(key, LabelState::Done(labs)); - if depth == Some(head_depth) { - self.visit_within_def(key, None, None); - } - } - head_depth - } - - fn visit_within_def(&mut self, key: &str, depth: Option, mut out: Option<&mut LabSet>) -> usize { - let def = &self.book[key]; - let mut head_depth = self.visit_tree(&def.root, depth, out.as_deref_mut()); - for (a, b) in &def.rdex { - head_depth = head_depth.min(self.visit_tree(a, depth, out.as_deref_mut())); - head_depth = head_depth.min(self.visit_tree(b, depth, out.as_deref_mut())); - } - head_depth - } - - fn visit_tree(&mut self, tree: &'a Tree, depth: Option, mut out: Option<&mut LabSet>) -> usize { - match tree { - Tree::Era | Tree::Var { .. } | Tree::Num { .. } => usize::MAX, - Tree::Ctr { lab, lft, rgt } => { - if let Some(out) = out.as_deref_mut() { - out.add(*lab); - } - usize::min(self.visit_tree(lft, depth, out.as_deref_mut()), self.visit_tree(rgt, depth, out.as_deref_mut())) - } - Tree::Ref { nam } => { - if let Some(def) = self.host.defs.get(nam) { - if let Some(out) = out { - out.union(&def.labs); - } - usize::MAX - } else { - self.visit_def(nam, depth.map(|x| x + 1), out) - } - } - Tree::Op1 { rgt, .. } => self.visit_tree(rgt, depth, out), - Tree::Op2 { lft, rgt, .. } | Tree::Mat { sel: lft, ret: rgt } => { - usize::min(self.visit_tree(lft, depth, out.as_deref_mut()), self.visit_tree(rgt, depth, out.as_deref_mut())) - } - } - } - } -} - -#[test] -fn test_calculate_labels() { - use std::collections::BTreeMap; - assert_eq!( - calculate_label_sets( - &" - @a = {0 @b @c} - @b = {1 @a *} - @c = {2 @a *} - - @p = {3 @q {4 x x}} - @q = {5 @r @t} - @r = {6 @s *} - @s = {7 @r *} - @t = {8 @q {9 @t @u}} - @u = {10 @u @s} - " - .parse() - .unwrap(), - &Host::default(), - ) - .collect::>(), - [ - ("a", [0, 1, 2].into_iter().collect()), - ("b", [0, 1, 2].into_iter().collect()), - ("c", [0, 1, 2].into_iter().collect()), - // - ("p", [3, 4, 5, 6, 7, 8, 9, 10].into_iter().collect()), - ("q", [5, 6, 7, 8, 9, 10].into_iter().collect()), - ("r", [6, 7].into_iter().collect()), - ("s", [6, 7].into_iter().collect()), - ("t", [5, 6, 7, 8, 9, 10].into_iter().collect()), - ("u", [6, 7, 10].into_iter().collect()), - ] - .into_iter() - .collect() - ); } diff --git a/src/host/calc_labels.rs b/src/host/calc_labels.rs new file mode 100644 index 00000000..4e3707f2 --- /dev/null +++ b/src/host/calc_labels.rs @@ -0,0 +1,223 @@ +use super::*; + +/// Calculates the labels used in each definition of a book. +/// +/// # Background: Naive Algorithms +/// +/// The simplest algorithm to calculate labels would be to go to each def, +/// recursively traverse the tree (going into references), and collect all of +/// the labels. +/// +/// Now, this algorithm will not terminate for recursive definitions, but fixing +/// this is relatively simple: don't enter a reference twice in one traversal. +/// +/// This modified algorithm will work in all cases, but it's not very efficient. +/// Notably, it can take quadratic time; for example, it requires traversing +/// each of these refs 3 times: +/// ```text +/// @foo = (* @bar) +/// @bar = (* @baz) +/// @baz = (* @foo) +/// ``` +/// +/// This can be resolved with memoization. However, a simple memoization pass +/// will not work here, due to the cycle avoidance algorithm. For example, +/// consider the following program: +/// ```text +/// @foo = (@bar {1 x x}) +/// @bar = ({2 x x} @foo) +/// ``` +/// +/// A simple memoization pass would go as follows: +/// - calculate the labels for `@foo` +/// - encounter `@bar` +/// - this has not yet been processed, so calculate the labels for it +/// - encounter `{2 ...}`; add 2 to the label set +/// - encounter `@foo` -- this has already been visited, so skip it (cycle +/// avoidance) +/// - add all the labels from `@bar` (just 2) to `@foo` +/// - encounter `{1 ...}`; add 1 to the label set +/// - calculate the labels for `@bar` -- this has already been visited, so skip +/// it (memoization) +/// +/// The end result of this is `@foo: {1, 2}, @bar: {2}` -- `@bar` is missing +/// `1`. +/// +/// Instead, a two-phase memoization approach is needed. +/// +/// # Implemented Here: Two-Phase Algorithm +/// +/// Each phase follows a similar procedure as the simple memoization algorithm, +/// with a few modifications. (Henceforth, the *head node* of a cycle is the +/// first node in a cycle reached in a traversal.) +/// - in the first phase: +/// - when we process a node involved in a cycle: +/// - if it is not the head node of a cycle it is involved in, instead of +/// saving the computed label set to the memo, we store a placeholder +/// `Cycle` value into the memo +/// - otherwise, after storing the label set in the memo, we enter the +/// second phase, retraversing the node +/// - when we encounter a `Cycle` value in the memo, we treat it akin to an +/// empty label set +/// - in the second phase, we treat `Cycle` values as though they were missing +/// entries in the memo +/// +/// In the first phase, to know when we are processing nodes in a cycle, we keep +/// track of the depth of the traversal, and every time we enter a ref, we store +/// `Cycle(depth)` into the memo -- so if we encounter this in the traversal of +/// the children, we know that that node is involved in a cycle. Additionally, +/// when we exit a node, we return the *head depth* of the result that was +/// calculated -- the least depth of a node involved in a cycle with the node +/// just processed. Comparing this with the depth gives us the information +/// needed for the special rules. +/// +/// This algorithm runs in linear time (as refs are traversed at most twice), +/// and requires no more space than the naive algorithm. +pub(crate) fn calculate_label_sets<'a>(book: &'a Book, host: &Host) -> impl Iterator { + let mut state = State { book, host, labels: HashMap::with_capacity(book.len()) }; + + for name in book.keys() { + state.visit_def(name, Some(0), None); + } + + return state.labels.into_iter().map(|(nam, lab)| match lab { + LabelState::Done(lab) => (nam, lab), + _ => unreachable!(), + }); + + struct State<'a, 'b> { + book: &'a Book, + host: &'b Host, + labels: HashMap<&'a str, LabelState>, + } + + #[derive(Debug)] + enum LabelState { + Done(LabSet), + /// Encountering this node indicates participation in a cycle with the given + /// head depth. + Cycle(usize), + } + + /// All of these methods share a similar signature: + /// - `depth` is optional; `None` indicates that this is the second processing + /// pass (where the depth is irrelevant, as all cycles have been detected) + /// - `out`, if supplied, will be unioned with the result of this traversal + /// - the return value indicates the head depth, as defined above (or an + /// arbitrary value `>= depth` if no cycles are involved) + impl<'a, 'b> State<'a, 'b> { + fn visit_def(&mut self, key: &'a str, depth: Option, out: Option<&mut LabSet>) -> usize { + match self.labels.entry(key) { + Entry::Vacant(e) => { + e.insert(LabelState::Cycle(depth.unwrap())); + self.calc_def(key, depth, out) + } + Entry::Occupied(mut e) => match e.get_mut() { + LabelState::Done(labs) => { + if let Some(out) = out { + out.union(labs); + } + usize::MAX + } + LabelState::Cycle(d) if depth.is_some() => *d, + LabelState::Cycle(_) => { + e.insert(LabelState::Done(LabSet::default())); + self.calc_def(key, depth, out) + } + }, + } + } + + fn calc_def(&mut self, key: &'a str, depth: Option, out: Option<&mut LabSet>) -> usize { + let mut labs = LabSet::default(); + let head_depth = self.visit_within_def(key, depth, Some(&mut labs)); + if let Some(out) = out { + out.union(&labs); + } + if depth.is_some_and(|x| x > head_depth) { + self.labels.insert(key, LabelState::Cycle(head_depth)); + } else { + self.labels.insert(key, LabelState::Done(labs)); + if depth == Some(head_depth) { + self.visit_within_def(key, None, None); + } + } + head_depth + } + + fn visit_within_def(&mut self, key: &str, depth: Option, mut out: Option<&mut LabSet>) -> usize { + let def = &self.book[key]; + let mut head_depth = self.visit_tree(&def.root, depth, out.as_deref_mut()); + for (a, b) in &def.redexes { + head_depth = head_depth.min(self.visit_tree(a, depth, out.as_deref_mut())); + head_depth = head_depth.min(self.visit_tree(b, depth, out.as_deref_mut())); + } + head_depth + } + + fn visit_tree(&mut self, tree: &'a Tree, depth: Option, mut out: Option<&mut LabSet>) -> usize { + match tree { + Tree::Era | Tree::Var { .. } | Tree::Num { .. } => usize::MAX, + Tree::Ctr { lab, lft, rgt } => { + if let Some(out) = out.as_deref_mut() { + out.add(*lab); + } + usize::min(self.visit_tree(lft, depth, out.as_deref_mut()), self.visit_tree(rgt, depth, out.as_deref_mut())) + } + Tree::Ref { nam } => { + if let Some(def) = self.host.defs.get(nam) { + if let Some(out) = out { + out.union(&def.labs); + } + usize::MAX + } else { + self.visit_def(nam, depth.map(|x| x + 1), out) + } + } + Tree::Op1 { rgt, .. } => self.visit_tree(rgt, depth, out), + Tree::Op2 { lft, rgt, .. } | Tree::Mat { sel: lft, ret: rgt } => { + usize::min(self.visit_tree(lft, depth, out.as_deref_mut()), self.visit_tree(rgt, depth, out.as_deref_mut())) + } + } + } + } +} + +#[test] +fn test_calculate_labels() { + use std::collections::BTreeMap; + assert_eq!( + calculate_label_sets( + &" + @a = {0 @b @c} + @b = {1 @a *} + @c = {2 @a *} + + @p = {3 @q {4 x x}} + @q = {5 @r @t} + @r = {6 @s *} + @s = {7 @r *} + @t = {8 @q {9 @t @u}} + @u = {10 @u @s} + " + .parse() + .unwrap(), + &Host::default(), + ) + .collect::>(), + [ + ("a", [0, 1, 2].into_iter().collect()), + ("b", [0, 1, 2].into_iter().collect()), + ("c", [0, 1, 2].into_iter().collect()), + // + ("p", [3, 4, 5, 6, 7, 8, 9, 10].into_iter().collect()), + ("q", [5, 6, 7, 8, 9, 10].into_iter().collect()), + ("r", [6, 7].into_iter().collect()), + ("s", [6, 7].into_iter().collect()), + ("t", [5, 6, 7, 8, 9, 10].into_iter().collect()), + ("u", [6, 7, 10].into_iter().collect()), + ] + .into_iter() + .collect() + ); +} diff --git a/src/host/encode_def.rs b/src/host/encode_def.rs new file mode 100644 index 00000000..cea95f81 --- /dev/null +++ b/src/host/encode_def.rs @@ -0,0 +1,105 @@ +use super::*; + +/// Converts an ast net to a list of instructions to create the net. +/// +/// `defs` must be populated with every `Ref` node that may appear in the net. +pub(super) fn ast_net_to_instructions(defs: &HashMap, net: &Net) -> Vec { + let mut state = + State { defs, scope: Default::default(), instr: Default::default(), end: Default::default(), next_index: 1 }; + + state.visit_tree(&net.root, TrgId::new(0)); + + net.redexes.iter().for_each(|(a, b)| state.visit_redex(a, b)); + + assert!(state.scope.is_empty(), "unbound variables: {:?}", state.scope.keys()); + + state.instr.append(&mut state.end); + + return state.instr; + + struct State<'a> { + defs: &'a HashMap, + scope: HashMap<&'a str, TrgId>, + instr: Vec, + end: Vec, + next_index: usize, + } + + impl<'a> State<'a> { + fn id(&mut self) -> TrgId { + let i = self.next_index; + self.next_index += 1; + TrgId::new(i) + } + fn visit_redex(&mut self, a: &'a Tree, b: &'a Tree) { + let (port, tree) = match (a, b) { + (Tree::Era, t) | (t, Tree::Era) => (Port::ERA, t), + (Tree::Ref { nam }, t) | (t, Tree::Ref { nam }) => (Port::new_ref(&self.defs[nam]), t), + (Tree::Num { val }, t) | (t, Tree::Num { val }) => (Port::new_num(*val), t), + (t, u) => { + let av = self.id(); + let aw = self.id(); + let bv = self.id(); + let bw = self.id(); + self.next_index += 4; + self.instr.push(Instruction::Wires { av, aw, bv, bw }); + self.end.push(Instruction::Link { a: aw, b: bw }); + self.visit_tree(t, av); + self.visit_tree(u, bv); + return; + } + }; + let trg = self.id(); + self.instr.push(Instruction::Const { port, trg }); + self.visit_tree(tree, trg); + } + fn visit_tree(&mut self, tree: &'a Tree, trg: TrgId) { + match tree { + Tree::Era => { + self.instr.push(Instruction::LinkConst { trg, port: Port::ERA }); + } + Tree::Ref { nam } => { + self.instr.push(Instruction::LinkConst { trg, port: Port::new_ref(&self.defs[nam]) }); + } + Tree::Num { val } => { + self.instr.push(Instruction::LinkConst { trg, port: Port::new_num(*val) }); + } + Tree::Var { nam } => match self.scope.entry(nam) { + Entry::Occupied(e) => { + let other = e.remove(); + self.instr.push(Instruction::Link { a: other, b: trg }); + } + Entry::Vacant(e) => { + e.insert(trg); + } + }, + Tree::Ctr { lab, lft, rgt } => { + let l = self.id(); + let r = self.id(); + self.instr.push(Instruction::Ctr { lab: *lab, trg, lft: l, rgt: r }); + self.visit_tree(lft, l); + self.visit_tree(rgt, r); + } + Tree::Op2 { opr, lft, rgt } => { + let l = self.id(); + let r = self.id(); + self.instr.push(Instruction::Op2 { op: *opr, trg, lft: l, rgt: r }); + self.visit_tree(lft, l); + self.visit_tree(rgt, r); + } + Tree::Op1 { opr, lft, rgt } => { + let r = self.id(); + self.instr.push(Instruction::Op1 { op: *opr, num: *lft, trg, rgt: r }); + self.visit_tree(rgt, r); + } + Tree::Mat { sel, ret } => { + let l = self.id(); + let r = self.id(); + self.instr.push(Instruction::Mat { trg, lft: l, rgt: r }); + self.visit_tree(sel, l); + self.visit_tree(ret, r); + } + } + } + } +} diff --git a/src/host/encode.rs b/src/host/encode_net.rs similarity index 98% rename from src/host/encode.rs rename to src/host/encode_net.rs index 77d1f879..c3c01d2e 100644 --- a/src/host/encode.rs +++ b/src/host/encode_net.rs @@ -19,7 +19,7 @@ impl Host { /// into `net` redex list. pub fn encode_net(&self, net: &mut run::Net, trg: Trg, ast_net: &Net) { let mut state = EncodeState { host: self, net, vars: Default::default() }; - for (l, r) in &ast_net.rdex { + for (l, r) in &ast_net.redexes { let (ap, a, bp, b) = state.net.do_wires(); state.encode(ap, l); state.encode(bp, r); diff --git a/src/host/readback.rs b/src/host/readback.rs new file mode 100644 index 00000000..6e0ad9c2 --- /dev/null +++ b/src/host/readback.rs @@ -0,0 +1,79 @@ +use super::*; + +impl Host { + /// Creates an ast tree from a wire in a runtime net. + pub fn readback_tree(&self, wire: &run::Wire) -> Tree { + ReadbackState { host: self, vars: Default::default(), var_id: 0 .. }.read_wire(wire.clone()) + } + + /// Creates an ast net from a runtime net. + /// + /// Note that vicious circles and disconnected subnets will not be in the + /// resulting ast net, as it is impossible to read these back from the runtime + /// net representation. In the case of vicious circles, this may result in + /// unbound variables. + pub fn readback(&self, rt_net: &run::Net) -> Net { + let mut state = ReadbackState { host: self, vars: Default::default(), var_id: 0 .. }; + let mut net = Net::default(); + + net.root = state.read_wire(rt_net.root.clone()); + + for (a, b) in &rt_net.redexes { + net.redexes.push((state.read_port(a.clone(), None), state.read_port(b.clone(), None))) + } + + net + } +} +/// See [`Host::readback`]. +struct ReadbackState<'a> { + host: &'a Host, + vars: HashMap, + var_id: RangeFrom, +} + +impl<'a> ReadbackState<'a> { + /// Reads a tree out from a given `wire`. + fn read_wire(&mut self, wire: Wire) -> Tree { + let port = wire.load_target(); + self.read_port(port, Some(wire)) + } + /// Reads a tree out from a given `port`. If this is a var port, the + /// `wire` this port was reached from must be supplied to key into the + /// `vars` map. + fn read_port(&mut self, port: Port, wire: Option) -> Tree { + match port.tag() { + Tag::Var => { + let key = wire.unwrap().addr().min(port.addr()); + Tree::Var { + nam: create_var(match self.vars.entry(key) { + Entry::Occupied(e) => e.remove(), + Entry::Vacant(e) => *e.insert(self.var_id.next().unwrap()), + }), + } + } + Tag::Red => self.read_wire(port.wire()), + Tag::Ref if port == Port::ERA => Tree::Era, + Tag::Ref => Tree::Ref { nam: self.host.back[&port.addr()].clone() }, + Tag::Num => Tree::Num { val: port.num() }, + Tag::Op2 => { + let opr = port.op(); + let node = port.traverse_node(); + Tree::Op2 { opr, lft: Box::new(self.read_wire(node.p1)), rgt: Box::new(self.read_wire(node.p2)) } + } + Tag::Op1 => { + let opr = port.op(); + let node = port.traverse_op1(); + Tree::Op1 { opr, lft: node.num.num(), rgt: Box::new(self.read_wire(node.p2)) } + } + Tag::Ctr => { + let node = port.traverse_node(); + Tree::Ctr { lab: node.lab, lft: Box::new(self.read_wire(node.p1)), rgt: Box::new(self.read_wire(node.p2)) } + } + Tag::Mat => { + let node = port.traverse_node(); + Tree::Mat { sel: Box::new(self.read_wire(node.p1)), ret: Box::new(self.read_wire(node.p2)) } + } + } + } +} diff --git a/src/main.rs b/src/main.rs index f896442a..5e3f4b3d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use clap::{Args, Parser, Subcommand}; use hvmc::{ ast::{Net, Tree}, host::Host, - run::{DynNet, Mode, Strict, Trg}, + run::{DynNet, Mode, Trg}, *, }; @@ -155,10 +155,10 @@ enum CliMode { } fn run(host: &Host, opts: RuntimeOpts, args: RunArgs) { - let mut net = Net { root: Tree::Ref { nam: args.entry_point }, rdex: vec![] }; + let mut net = Net { root: Tree::Ref { nam: args.entry_point }, redexes: vec![] }; for arg in args.args { let arg: Net = Net::from_str(&arg).unwrap(); - net.rdex.extend(arg.rdex); + net.redexes.extend(arg.redexes); net.apply_tree(arg.root); } @@ -209,7 +209,7 @@ fn load_files(files: &[String]) -> Arc> { } fn reduce_exprs(host: &Host, exprs: &[Net], opts: &RuntimeOpts) { - let heap = run::Net::::init_heap(opts.memory as usize); + let heap = run::Heap::new(opts.memory as usize); for expr in exprs { let mut net = DynNet::new(&heap, opts.lazy_mode); dispatch_dyn_net!(&mut net => { @@ -259,23 +259,64 @@ fn compile_executable(file_name: &str, host: &host::Host) -> Result<(), io::Erro } let cargo_toml = include_str!("../Cargo.toml"); let cargo_toml = cargo_toml.split("##--COMPILER-CUTOFF--##").next().unwrap(); - fs::create_dir_all(format!("{}/src", outdir))?; - fs::create_dir_all(format!("{}/src/host", outdir))?; + + macro_rules! include_files { + ($([$($prefix:ident)*])? $mod:ident {$($sub:tt)*} $($rest:tt)*) => { + fs::create_dir_all(concat!(".hvm/src/", $($(stringify!($prefix), "/",)*)? stringify!($mod)))?; + include_files!([$($($prefix)* $mod)?] $($sub)*); + include_files!([$($($prefix)*)?] $mod $($rest)*); + }; + ($([$($prefix:ident)*])? $file:ident $($rest:tt)*) => { + fs::write( + concat!(".hvm/src/", $($(stringify!($prefix), "/",)*)* stringify!($file), ".rs"), + include_str!(concat!($($(stringify!($prefix), "/",)*)* stringify!($file), ".rs")), + )?; + include_files!([$($($prefix)*)?] $($rest)*); + }; + ($([$($prefix:ident)*])?) => {}; + } + + fs::create_dir_all(".hvm/src")?; fs::write(".hvm/Cargo.toml", cargo_toml)?; - fs::write(".hvm/src/ast.rs", include_str!("../src/ast.rs"))?; - fs::write(".hvm/src/compile.rs", include_str!("../src/compile.rs"))?; - fs::write(".hvm/src/host/encode.rs", include_str!("../src/host/encode.rs"))?; - fs::write(".hvm/src/fuzz.rs", include_str!("../src/fuzz.rs"))?; - fs::write(".hvm/src/host.rs", include_str!("../src/host.rs"))?; - fs::write(".hvm/src/lib.rs", include_str!("../src/lib.rs"))?; - fs::write(".hvm/src/main.rs", include_str!("../src/main.rs"))?; - fs::write(".hvm/src/ops.rs", include_str!("../src/ops.rs"))?; - fs::write(".hvm/src/run.rs", include_str!("../src/run.rs"))?; - fs::write(".hvm/src/stdlib.rs", include_str!("../src/stdlib.rs"))?; - fs::write(".hvm/src/trace.rs", include_str!("../src/trace.rs"))?; - fs::write(".hvm/src/util.rs", include_str!("../src/util.rs"))?; fs::write(".hvm/src/gen.rs", gen)?; + include_files! { + ast + compile + fuzz + host { + calc_labels + encode_def + encode_net + readback + } + lib + main + ops + run { + addr + allocator + def + instruction + interact + linker + net + node + parallel + port + wire + } + stdlib + trace + util { + apply_tree + bi_enum + create_var + deref + stats + } + } + let output = process::Command::new("cargo") .current_dir("./.hvm") .arg("build") diff --git a/src/run.rs b/src/run.rs index 15e9426f..9b225f3c 100644 --- a/src/run.rs +++ b/src/run.rs @@ -16,9 +16,14 @@ //! - other agents are backed by allocated [`Node`]s, which store the targets of //! the *auxiliary ports* of the net (as managed by the linker); the target of //! the principal port is left implicit -//! - active pairs are thus stored in a dedicated vector, `net.rdex` +//! - active pairs are thus stored in a dedicated vector, `net.redexes` -use crate::{ops::Op, trace, trace::Tracer, util::bi_enum}; +use crate::{ + ops::Op, + trace, + trace::Tracer, + util::{bi_enum, deref}, +}; use nohash_hasher::{IntMap, IsEnabled}; use std::{ alloc::{self, Layout}, @@ -45,772 +50,31 @@ fn spin_loop() {} // this could use `std::hint::spin_loop`, but in practice it h use atomic::{AtomicU64, AtomicUsize, Ordering::Relaxed}; -// ------------------- -// Primitive Types -// ------------------- - -/// A port in the interaction net. -/// -/// The type of a port is determined by its *tag*, which is stored in the bottom -/// three bits. -/// -/// All tags other than [`Num`] divide the bits of the port as follows: -/// - the top 16 bits are the *label*, accessible with [`Port::lab`] -/// - the middle 45 bits are the non-alignment bits of the *address*, an -/// 8-byte-aligned pointer accessible with [`Port::addr`] -/// - the bottom 3 bits are the tag, as always -/// -/// The semantics of these fields depend upon the tag; see the documentation for -/// each [`Tag`] variant. -#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Default)] -#[repr(transparent)] -#[must_use] -pub struct Port(pub u64); - -bi_enum! { - #[repr(u8)] - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub enum Tag { - /// `Red` ports represent redirects, which are an implementation detail of - /// the atomic linking algorithm, and don't have a precise analogue in - /// interaction nets. - /// - /// These ports are never directly held, but rather replace the backlinks of - /// some var ports. They are used to resolve inter-thread conflicts, and - /// thus will never appear when single-threaded. - /// - /// See the documentation for the linking algorithm for more. - Red = 0, - /// A `Var` port represents an auxiliary port in the net. - /// - /// The address of this port represents the wire leaving this port, - /// accessible with `Port::wire`. - /// - /// The label of this port is currently unused and always 0. - Var = 1, - /// A `Ref` port represents the principal port of a nilary reference node. - /// - /// The address of this port is a pointer to the corresponding [`Def`]. - /// - /// The label of this port is always equivalent to `def.labs.min_safe`, and - /// is used as an optimization for the ref commutation interaction. - /// - /// Eraser nodes are represented by a null-pointer `Ref`, available as the - /// constant [`Port::ERA`]. - Ref = 2, - /// A `Num` port represents the principal port of a U60 node. - /// - /// The top 60 bits of the port are the value of this node, and are - /// accessible with [`Port::num`]. - /// - /// The 4th bit from the bottom is currently unused in this port. - Num = 3, - /// An `Op2` port represents the principal port of an Op2 node. - /// - /// The label of this port is the corresponding operation, which can be - /// accessed with [`Port::op`]. - /// - /// The address of this port is the address of a two-word allocation, - /// storing the targets of the wires connected to the two auxiliary ports of - /// this node. - Op2 = 4, - /// An `Op1` port represents the principal port of an Op1 node. - /// - /// The label of this port is the corresponding operation, which can be - /// accessed with [`Port::op`]. - /// - /// The address of this port is the address of a two-word allocation. The - /// first word in this allocation stores the first operand as a `Num` port, - /// and the second word stores the target of the wire connected to the - /// auxiliary port of this node. - Op1 = 5, - /// A `Mat` port represents the principal port of a Mat node. - /// - /// The address of this port is the address of a two-word allocation, - /// storing the targets of the wires connected to the two auxiliary ports of - /// the node. - /// - /// The label of this port is currently unused and always 0. - Mat = 6, - /// A `Ctr` port represents the principal port of an binary interaction - /// combinator node. - /// - /// The label of this port is the label of the combinator; two combinators - /// annihilate if they have the same label, or commute otherwise. - /// - /// The address of this port is the address of a two-word allocation, - /// storing the targets of the wires connected to the two auxiliary ports of - /// the node. - Ctr = 7, - } -} - use Tag::*; -pub type Lab = u16; - -impl fmt::Debug for Port { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:016x?} ", self.0)?; - match *self { - Port::ERA => write!(f, "[ERA]"), - Port::FREE => write!(f, "[FREE]"), - Port::GONE => write!(f, "[GONE]"), - Port::LOCK => write!(f, "[LOCK]"), - _ => match self.tag() { - Num => write!(f, "[Num {}]", self.num()), - Var | Red | Mat => write!(f, "[{:?} {:?}]", self.tag(), self.addr()), - Op2 | Op1 | Ctr | Ref => write!(f, "[{:?} {:?} {:?}]", self.tag(), self.lab(), self.addr()), - }, - } - } -} - -impl Port { - /// The principal port of an eraser node. - pub const ERA: Port = Port(Ref as _); - /// A sentinel value used to indicate free memory; see the allocator for more - /// details. - pub const FREE: Port = Port(0x8000_0000_0000_0000); - /// A sentinel value used to lock a wire; see the linking algorithm for more - /// details. - pub const LOCK: Port = Port(0xFFFF_FFFF_FFFF_FFF0); - /// A sentinel value used in the atomic linking algorithm; see it for more - /// details. - pub const GONE: Port = Port(0xFFFF_FFFF_FFFF_FFFF); - - /// Creates a new port with a given tag, label, and addr. - #[inline(always)] - pub fn new(tag: Tag, lab: Lab, addr: Addr) -> Self { - Port(((lab as u64) << 48) | (addr.0 as u64) | (tag as u64)) - } - - /// Creates a new [`Var`] port with a given addr. - #[inline(always)] - pub fn new_var(addr: Addr) -> Self { - Port::new(Var, 0, addr) - } - - /// Creates a new [`Num`] port with a given 60-bit numeric value. - #[inline(always)] - pub const fn new_num(val: u64) -> Self { - Port((val << 4) | (Num as u64)) - } - - /// Creates a new [`Ref`] port corresponding to a given definition. - #[inline(always)] - pub fn new_ref(def: &Def) -> Port { - Port::new(Ref, def.labs.min_safe, Addr(def as *const _ as _)) - } - - /// Accesses the tag of this port; this is valid for all ports. - #[inline(always)] - pub fn tag(&self) -> Tag { - unsafe { Tag::from_unchecked((self.0 & 0x7) as u8) } - } - - /// Accesses the label of this port; this is valid for all non-`Num` ports. - #[inline(always)] - pub const fn lab(&self) -> Lab { - (self.0 >> 48) as Lab - } - - /// Accesses the addr of this port; this is valid for all non-`Num` ports. - #[inline(always)] - pub const fn addr(&self) -> Addr { - Addr((self.0 & 0x0000_FFFF_FFFF_FFF8) as usize as _) - } - - /// Accesses the operation of this port; this is valid for [`Op1`] and [`Op2`] - /// ports. - #[inline(always)] - pub fn op(&self) -> Op { - unsafe { Op::from_unchecked(self.lab()) } - } - - /// Accesses the numeric value of this port; this is valid for [`Num`] ports. - #[inline(always)] - pub const fn num(&self) -> u64 { - self.0 >> 4 - } - - /// Accesses the wire leaving this port; this is valid for [`Var`] ports and - /// non-sentinel [`Red`] ports. - #[inline(always)] - pub fn wire(&self) -> Wire { - Wire::new(self.addr()) - } - - #[inline(always)] - pub fn is_principal(&self) -> bool { - self.tag() >= Ref - } - - /// Given a principal port, returns whether this principal port may be part of - /// a skippable active pair -- an active pair like `ERA-ERA` that does not - /// need to be added to the redex list. - #[inline(always)] - pub fn is_skippable(&self) -> bool { - self.tag() == Num || self.tag() == Ref && self.lab() != u16::MAX - } - - /// Converts a [`Var`] port into a [`Red`] port with the same address. - #[inline(always)] - fn redirect(&self) -> Port { - Port::new(Red, 0, self.addr()) - } - - /// Converts a [`Red`] port into a [`Var`] port with the same address. - #[inline(always)] - fn unredirect(&self) -> Port { - Port::new(Var, 0, self.addr()) - } - - fn is_full_node(&self) -> bool { - self.tag() > Num - } -} - -/// See [`Port::traverse_node`]. -pub struct TraverseNode { - pub tag: Tag, - pub lab: Lab, - pub p1: Wire, - pub p2: Wire, -} - -/// See [`Port::traverse_op1`]. -pub struct TraverseOp1 { - pub op: Op, - pub num: Port, - pub p2: Wire, -} - -impl Port { - #[inline(always)] - pub fn consume_node(self) -> TraverseNode { - self.traverse_node() - } - - #[inline(always)] - pub fn traverse_node(self) -> TraverseNode { - TraverseNode { - tag: self.tag(), - lab: self.lab(), - p1: Wire::new(self.addr()), - p2: Wire::new(self.addr().other_half()), - } - } - - #[inline(always)] - pub fn consume_op1(self) -> TraverseOp1 { - let op = self.op(); - let s = self.consume_node(); - let num = s.p1.swap_target(Port::FREE); - TraverseOp1 { op, num, p2: s.p2 } - } - - #[inline(always)] - pub fn traverse_op1(self) -> TraverseOp1 { - let op = self.op(); - let s = self.traverse_node(); - let num = s.p1.load_target(); - TraverseOp1 { op, num, p2: s.p2 } - } -} - -/// A memory address to be used in a [`Port`] or a [`Wire`]. -/// -/// The bottom three bits must be zero; i.e. this address must be at least -/// 8-byte-aligned. -/// -/// Additionally, all bits other than the lowest 48 must be zero. On a 32-bit -/// system, this has no effect, but on a 64-bit system, this means that the top -/// 16 bits much be zero. -#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[must_use] -pub struct Addr(pub usize); - -impl IsEnabled for Addr {} - -impl fmt::Debug for Addr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:012x?}", self.0) - } -} - -impl Addr { - pub const NULL: Addr = Addr(0); - - /// Casts this address into an `&AtomicU64`, which may or may not be valid. - #[inline(always)] - pub fn val<'a>(&self) -> &'a AtomicU64 { - unsafe { &*(self.0 as *const _) } - } - - /// Casts this address into an `&Def`, which may or may not be valid. - #[inline(always)] - pub fn def<'a>(&self) -> &'a Def { - unsafe { &*(self.0 as *const _) } - } - - const HALF_MASK: usize = 0b1000; - - /// Given an address to one word of a two-word allocation, returns the address - /// of the first word of that allocation. - #[inline(always)] - fn left_half(&self) -> Self { - Addr(self.0 & !Addr::HALF_MASK) - } - - /// Given an address to one word of a two-word allocation, returns the address - /// of the other word of that allocation. - #[inline(always)] - pub fn other_half(&self) -> Self { - Addr(self.0 ^ Addr::HALF_MASK) - } -} - -/// A wire in the interaction net. -/// -/// More accurately, this is a *directed view* of a wire. If ports `a` and `b` -/// are connected, then the wire leaving `a` and the wire leaving `b` are the -/// same wire, but viewed from opposite directions. -/// -/// This is represented by a pointer to an `AtomicU64` storing the *target* of -/// the wire -- the port on the other side. (The target of the wire leaving `a` -/// is `b`.) -/// -/// Changes to the target are handled by the linker. -#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[must_use] -pub struct Wire(pub *const AtomicU64); - -impl fmt::Debug for Wire { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:012x?}", self.0 as usize) - } -} - -unsafe impl Send for Wire {} -unsafe impl Sync for Wire {} - -impl Wire { - #[inline(always)] - pub fn addr(&self) -> Addr { - Addr(self.0 as _) - } - - #[inline(always)] - pub fn new(addr: Addr) -> Wire { - Wire(addr.0 as _) - } - - #[inline(always)] - fn target<'a>(&self) -> &'a AtomicU64 { - if cfg!(feature = "_fuzz") { - assert_ne!(self.0 as usize, 0xfffffffffff0u64 as usize); - assert_ne!(self.0 as usize, 0); - } - unsafe { &*self.0 } - } - - #[inline(always)] - pub fn load_target(&self) -> Port { - let port = Port(self.target().load(Relaxed)); - if cfg!(feature = "_fuzz") { - assert_ne!(port, Port::FREE); - } - port - } - - #[inline(always)] - pub fn set_target(&self, port: Port) { - self.target().store(port.0, Relaxed); - } - - #[inline(always)] - pub fn cas_target(&self, expected: Port, value: Port) -> Result { - self.target().compare_exchange(expected.0, value.0, Relaxed, Relaxed).map(Port).map_err(Port) - } - - #[inline(always)] - pub fn swap_target(&self, value: Port) -> Port { - let port = Port(self.target().swap(value.0, Relaxed)); - if cfg!(feature = "_fuzz") { - assert_ne!(port, Port::FREE); - } - port - } - - // Takes a pointer's target. - #[inline(always)] - pub fn lock_target(&self) -> Port { - loop { - let got = self.swap_target(Port::LOCK); - if cfg!(feature = "_fuzz") { - assert_ne!(got, Port::FREE); - } - if got != Port::LOCK { - return got; - } - spin_loop(); - } - } -} - -/// A bitset representing the set of labels used in a def. -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct LabSet { - /// The least label greater than every label in the set. - /// - /// `lab >= set.min_safe` implies `!set.has(lab)`. - pub(crate) min_safe: Lab, - pub(crate) bits: Cow<'static, [u64]>, -} - -impl LabSet { - pub const NONE: LabSet = LabSet { min_safe: 0, bits: Cow::Borrowed(&[]) }; - pub const ALL: LabSet = LabSet { min_safe: Lab::MAX, bits: Cow::Borrowed(&[u64::MAX; 1024]) }; - - pub fn add(&mut self, lab: Lab) { - self.min_safe = self.min_safe.max(lab + 1); - let index = (lab >> 6) as usize; - let bit = lab & 63; - let bits = self.bits.to_mut(); - if index >= bits.len() { - bits.resize(index + 1, 0); - } - bits[index] |= 1 << bit; - } - - pub fn has(&self, lab: Lab) -> bool { - if lab >= self.min_safe { - return false; - } - let index = (lab >> 6) as usize; - let bit = lab & 63; - unsafe { self.bits.get_unchecked(index) & 1 << bit != 0 } - } - - /// Adds all of the labels in `other` to this set. - pub fn union(&mut self, other: &LabSet) { - self.min_safe = self.min_safe.max(other.min_safe); - let bits = self.bits.to_mut(); - for (a, b) in bits.iter_mut().zip(other.bits.iter()) { - *a |= b; - } - if other.bits.len() > bits.len() { - bits.extend_from_slice(&other.bits[bits.len() ..]) - } - } - - pub const fn from_bits(bits: &'static [u64]) -> Self { - if bits.is_empty() { - return LabSet::NONE; - } - let min_safe = (bits.len() << 6) as u16 - bits[bits.len() - 1].leading_zeros() as u16; - LabSet { min_safe, bits: Cow::Borrowed(bits) } - } -} - -impl FromIterator for LabSet { - fn from_iter>(iter: T) -> Self { - let mut set = LabSet::default(); - for lab in iter { - set.add(lab); - } - set - } -} - -/// A custom nilary interaction net agent. -/// -/// This is *roughly* equivalent to the following definition: -/// ```rust,ignore -/// struct Def { -/// pub labs: LabSet, -/// pub data: T, -/// } -/// ``` -/// -/// Except that, with this definition, `&Def` is a thin pointer, as the vtable -/// data is effectively stored inline in `Def`. -#[repr(C)] // ensure that the fields will have a consistent alignment, regardless of `T` -#[repr(align(16))] // ensure the bottom 4 bits of `Ref` addresses are zero -pub struct Def { - /// The set of labels used by this agent; the agent commutes with any - /// interaction combinator whose label is not in this set. - pub labs: LabSet, - ty: TypeId, - call_strict: unsafe fn(*const Def, &mut Net, port: Port), - call_lazy: unsafe fn(*const Def, &mut Net, port: Port), - pub data: T, -} - -extern "C" { - /// An internal type used to mark dynamic `Def`s. - /// - /// Because this is an `extern type`, it is unsized, but has zero metadata. - /// This is essentially a workaround for the lack of custom DSTs. - #[doc(hidden)] - pub type Dynamic; -} - -unsafe impl Send for Dynamic {} -unsafe impl Sync for Dynamic {} - -pub trait AsDef: Any + Send + Sync { - unsafe fn call(slf: *const Def, net: &mut Net, port: Port); -} - -impl Def { - pub const fn new(labs: LabSet, data: T) -> Self - where - T: AsDef, - { - Def { labs, ty: TypeId::of::(), call_strict: T::call::, call_lazy: T::call::, data } - } - - #[inline(always)] - pub const fn upcast(&self) -> &Def { - unsafe { &*(self as *const _ as *const _) } - } - - #[inline(always)] - pub fn upcast_mut(&mut self) -> &mut Def { - unsafe { &mut *(self as *mut _ as *mut _) } - } -} - -impl Def { - #[inline(always)] - pub unsafe fn downcast_ptr(slf: *const Def) -> Option<*const Def> { - if (*slf).ty == TypeId::of::() { Some(slf.cast()) } else { None } - } - #[inline(always)] - pub unsafe fn downcast_mut_ptr(slf: *mut Def) -> Option<*mut Def> { - if (*slf).ty == TypeId::of::() { Some(slf.cast()) } else { None } - } - #[inline(always)] - pub fn downcast_ref(&self) -> Option<&Def> { - unsafe { Def::downcast_ptr(self).map(|x| &*x) } - } - #[inline(always)] - pub fn downcast_mut(&mut self) -> Option<&mut Def> { - unsafe { Def::downcast_mut_ptr(self).map(|x| &mut *x) } - } - #[inline(always)] - pub unsafe fn call(slf: *const Def, net: &mut Net, port: Port) { - if M::LAZY { - ((*slf).call_lazy)(slf as *const _, std::mem::transmute(net), port) - } else { - ((*slf).call_strict)(slf as *const _, std::mem::transmute(net), port) - } - } -} - -impl Deref for Def { - type Target = Def; - #[inline(always)] - fn deref(&self) -> &Self::Target { - self.upcast() - } -} - -impl DerefMut for Def { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - self.upcast_mut() - } -} - -impl, Port) + Send + Sync + 'static, G: Fn(&mut Net, Port) + Send + Sync + 'static> AsDef - for (F, G) -{ - unsafe fn call(slf: *const Def, net: &mut Net, port: Port) { - if M::LAZY { - ((*slf).data.1)(std::mem::transmute::<_, &mut Net>(net), port) - } else { - ((*slf).data.0)(std::mem::transmute::<_, &mut Net>(net), port) - } - } -} - -/// [`Def`]s, when not pre-compiled, are represented as lists of instructions. -#[derive(Debug, Default, Clone)] -pub struct InterpretedDef { - pub instr: Vec, -} - -/// Each instruction corresponds to a fragment of a net that has a native -/// implementation. -/// -/// These net fragments may have several free ports, which are each represented -/// with [`TrgId`]s. -/// -/// Each `TrgId` of an instruction has an associated polarity -- it can either -/// be an input or an output. Because the underlying interaction net model we're -/// using does not have polarity, we also need instructions for linking out-out -/// or in-in. -/// -/// Linking two outputs can be done with [`Instruction::Link`], which creates a -/// "cup" wire. -/// -/// Linking two inputs is more complicated, due to the way locking works. It can -/// be done with [`Instruction::Wires`], which creates two "cap" wires. One half -/// of each cap can be used for each input. Once those inputs have been fully -/// unlocked, the other halves of each cap can be linked with -/// [`Instruction::Link`]. For example: -/// ```rust,ignore -/// let (av, aw, bv, bw) = net.do_wires(); -/// some_subnet(net, av, bv); -/// net.link(aw, bw); -/// ``` -/// -/// Each instruction documents both the native implementation and the polarity -/// of each `TrgId`. -/// -/// Some instructions take a [`Port`]; these must always be statically-valid -/// ports -- that is, [`Ref`] or [`Num`] ports. -#[derive(Debug, Clone)] -pub enum Instruction { - /// ```rust,ignore - /// let trg = Trg::port(port); - /// ``` - Const { trg: TrgId, port: Port }, - /// ```rust,ignore - /// net.link_trg(a, b); - /// ``` - Link { a: TrgId, b: TrgId }, - /// ```rust,ignore - /// net.link_trg(trg, Trg::port(port)); - /// ``` - LinkConst { trg: TrgId, port: Port }, - /// See [`Net::do_ctr`]. - /// ```rust,ignore - /// let (lft, rgt) = net.do_ctr(lab, trg); - /// ``` - Ctr { lab: Lab, trg: TrgId, lft: TrgId, rgt: TrgId }, - /// See [`Net::do_op2`]. - /// ```rust,ignore - /// let (lft, rgt) = net.do_op2(lab, trg); - /// ``` - Op2 { op: Op, trg: TrgId, lft: TrgId, rgt: TrgId }, - /// See [`Net::do_op1`]. - /// ```rust,ignore - /// let rgt = net.do_op1(lab, num, trg); - /// ``` - Op1 { op: Op, num: u64, trg: TrgId, rgt: TrgId }, - /// See [`Net::do_mat`]. - /// ```rust,ignore - /// let (lft, rgt) = net.do_mat(trg); - /// ``` - Mat { trg: TrgId, lft: TrgId, rgt: TrgId }, - /// See [`Net::do_wires`]. - /// ```rust,ignore - /// let (av, aw, bv, bw) = net.do_wires(); - /// ``` - Wires { av: TrgId, aw: TrgId, bv: TrgId, bw: TrgId }, -} - -/// Part of the net to link to; either a wire or a port. -/// -/// To store this compactly, we reuse the [`Red`] tag to indicate if this is a -/// wire. -#[derive(Clone)] -pub struct Trg(pub(crate) Port); - -impl fmt::Debug for Trg { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.is_wire() { self.clone().as_wire().fmt(f) } else { self.0.fmt(f) } - } -} - -impl Trg { - /// Creates a `Trg` from a port. - #[inline(always)] - pub fn port(port: Port) -> Self { - Trg(port) - } - - /// Creates a `Trg` from a wire. - #[inline(always)] - pub fn wire(wire: Wire) -> Self { - Trg(Port(wire.0 as u64)) - } - - #[inline(always)] - fn is_wire(&self) -> bool { - self.0.tag() == Red - } - - #[inline(always)] - fn as_wire(self) -> Wire { - Wire(self.0.0 as _) - } - - #[inline(always)] - fn as_port(self) -> Port { - self.0 - } - - /// Access the target port; if this trg is already a port, this does nothing, - /// but if it is a wire, it loads the target. - /// - /// The returned port is only normative if it is principal; if it is a var or - /// a redirect, it must not be used for linking, and the original `Trg` should - /// be used instead. - #[inline(always)] - pub fn target(&self) -> Port { - if self.is_wire() { self.clone().as_wire().load_target() } else { self.0.clone() } - } -} - -/// An index to a [`Trg`] in an [`Instruction`]. These essentially serve the -/// function of registers. -/// -/// When compiled, each `TrgId` will be compiled to a variable. -/// -/// When interpreted, the `TrgId` serves as an index into a vector. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct TrgId { - /// Instead of storing the index directly, we store the byte offset, to save a - /// shift instruction when indexing into the `Trg` vector in interpreted mode. - /// - /// This is always `index * size_of::()`. - byte_offset: usize, -} - -impl TrgId { - pub fn new(index: usize) -> Self { - TrgId { byte_offset: index * size_of::() } - } - pub fn index(&self) -> usize { - self.byte_offset / size_of::() - } -} - -impl fmt::Display for TrgId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "t{}", self.index()) - } -} +mod addr; +mod allocator; +mod def; +mod instruction; +mod interact; +mod linker; +mod net; +mod node; +mod parallel; +mod port; +mod wire; + +pub use addr::*; +pub use allocator::*; +pub use def::*; +pub use instruction::*; +pub use linker::*; +pub use net::*; +pub use node::*; +pub use port::*; +pub use wire::*; -impl fmt::Debug for TrgId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "TrgId({})", self.index()) - } -} - -/// The memory behind a two-word allocation. -/// -/// This must be aligned to 16 bytes so that the left word's address always ends -/// with `0b0000` and the right word's address always ends with `0b1000`. -#[repr(C)] -#[repr(align(16))] -#[derive(Default)] -pub struct Node(pub AtomicU64, pub AtomicU64); +pub type Lab = u16; /// The runtime mode is represented with a generic such that, instead of /// repeatedly branching on the mode at runtime, the branch can happen at the @@ -836,37 +100,6 @@ unsafe impl Mode for Lazy { const LAZY: bool = true; } -/// Stores extra data needed about the nodes when in lazy mode. (In strict mode, -/// this is unused.) -struct Head { - /// the principal port of this node - this: Port, - /// the port connected to the principal port of this node - targ: Port, -} - -// ----------- -// The Net -// ----------- - -/// An interaction combinator net. -pub struct Net<'a, M: Mode> { - pub tid: usize, // thread id - pub tids: usize, // thread count - pub rdex: Vec<(Port, Port)>, // redexes - pub trgs: Vec, - pub rwts: Rewrites, // rewrite count - pub root: Wire, - // allocator - pub area: &'a [Node], - pub head: Addr, - pub next: usize, - heads: IntMap, - // - tracer: Tracer, - _mode: PhantomData, -} - /// Tracks the number of rewrites, categorized by type. #[derive(Clone, Copy, Debug, Default)] pub struct Rewrites { @@ -903,1396 +136,6 @@ impl AtomicRewrites { } } -impl<'a, M: Mode> Net<'a, M> { - /// Creates an empty net with a given heap area. - pub fn new(area: &'a [Node]) -> Self { - let mut net = Net::new_with_root(area, Wire(std::ptr::null())); - net.root = Wire::new(net.alloc()); - net - } - - fn new_with_root(area: &'a [Node], root: Wire) -> Self { - Net { - tid: 0, - tids: 1, - rdex: vec![], - trgs: vec![Trg::port(Port::FREE); 1 << 16], - rwts: Rewrites::default(), - root, - area, - head: Addr::NULL, - next: 0, - heads: Default::default(), - tracer: Tracer::default(), - _mode: PhantomData, - } - } - - /// Boots a net from a Ref. - pub fn boot(&mut self, def: &Def) { - let def = Port::new_ref(def); - trace!(self.tracer, def); - self.root.set_target(def); - } -} - -// ------------- -// Allocator -// ------------- - -impl<'a, M: Mode> Net<'a, M> { - /// Allocate an area for the net's heap with a given size. - pub fn init_heap(size: usize) -> Box<[Node]> { - unsafe { - Box::from_raw(core::ptr::slice_from_raw_parts_mut( - alloc::alloc(Layout::array::(size).unwrap()) as *mut _, - size, - )) - } - } - - /// Frees one word of a two-word allocation. - #[inline(always)] - pub fn half_free(&mut self, addr: Addr) { - trace!(self.tracer, addr); - const FREE: u64 = Port::FREE.0; - if cfg!(feature = "_fuzz") { - if cfg!(not(feature = "_fuzz_no_free")) { - assert_ne!(addr.val().swap(FREE, Relaxed), FREE, "double free"); - } - } else { - addr.val().store(FREE, Relaxed); - if addr.other_half().val().load(Relaxed) == FREE { - trace!(self.tracer, "other free"); - let addr = addr.left_half(); - if addr.val().compare_exchange(FREE, self.head.0 as u64, Relaxed, Relaxed).is_ok() { - let old_head = &self.head; - let new_head = addr; - trace!(self.tracer, "appended", old_head, new_head); - self.head = new_head; - } else { - trace!(self.tracer, "too slow"); - }; - } - } - } - - /// Allocates a two-word node. - #[inline(never)] - pub fn alloc(&mut self) -> Addr { - trace!(self.tracer, self.head); - let addr = if self.head != Addr::NULL { - let addr = self.head.clone(); - let next = Addr(self.head.val().load(Relaxed) as usize); - trace!(self.tracer, next); - self.head = next; - addr - } else { - let index = self.next; - self.next += 1; - Addr(&self.area.get(index).expect("OOM").0 as *const _ as _) - }; - trace!(self.tracer, addr, self.head); - addr.val().store(Port::LOCK.0, Relaxed); - addr.other_half().val().store(Port::LOCK.0, Relaxed); - addr - } - - /// If `trg` is a wire, frees the backing memory. - #[inline(always)] - pub fn free_trg(&mut self, trg: Trg) { - if trg.is_wire() { - self.half_free(trg.as_wire().addr()); - } - } -} - -pub struct CreatedNode { - pub p0: Port, - pub p1: Port, - pub p2: Port, -} - -impl<'a, M: Mode> Net<'a, M> { - #[inline(always)] - pub fn create_node(&mut self, tag: Tag, lab: Lab) -> CreatedNode { - let addr = self.alloc(); - CreatedNode { - p0: Port::new(tag, lab, addr.clone()), - p1: Port::new_var(addr.clone()), - p2: Port::new_var(addr.other_half()), - } - } - - /// Creates a wire an aux port pair. - #[inline(always)] - pub fn create_wire(&mut self) -> (Wire, Port) { - let addr = self.alloc(); - self.half_free(addr.other_half()); - (Wire::new(addr.clone()), Port::new_var(addr)) - } - - /// Creates a wire pointing to a given port; sometimes necessary to avoid - /// deadlock. - #[inline(always)] - pub fn create_wire_to(&mut self, port: Port) -> Wire { - let addr = self.alloc(); - self.half_free(addr.other_half()); - let wire = Wire::new(addr); - self.link_port_port(port, Port::new_var(wire.addr())); - wire - } -} - -// ---------- -// Linker -// ---------- - -/// When threads interfere, this uses the atomic linking algorithm described in -/// `paper/`. -/// -/// Linking wires must be done atomically, but linking ports can be done -/// non-atomically (because they must be locked). -impl<'a, M: Mode> Net<'a, M> { - /// Links two ports. - #[inline(always)] - pub fn link_port_port(&mut self, a_port: Port, b_port: Port) { - trace!(self.tracer, a_port, b_port); - if a_port.is_principal() && b_port.is_principal() { - self.redux(a_port, b_port); - } else { - self.half_link_port_port(a_port.clone(), b_port.clone()); - self.half_link_port_port(b_port, a_port); - } - } - - /// Links two wires. - #[inline(always)] - pub fn link_wire_wire(&mut self, a_wire: Wire, b_wire: Wire) { - trace!(self.tracer, a_wire, b_wire); - let a_port = a_wire.lock_target(); - let b_port = b_wire.lock_target(); - trace!(self.tracer, a_port, b_port); - if a_port.is_principal() && b_port.is_principal() { - self.half_free(a_wire.addr()); - self.half_free(b_wire.addr()); - self.redux(a_port, b_port); - } else { - self.half_link_wire_port(a_port.clone(), a_wire, b_port.clone()); - self.half_link_wire_port(b_port, b_wire, a_port); - } - } - - /// Links a wire to a port. - #[inline(always)] - pub fn link_wire_port(&mut self, a_wire: Wire, b_port: Port) { - trace!(self.tracer, a_wire, b_port); - let a_port = a_wire.lock_target(); - trace!(self.tracer, a_port); - if a_port.is_principal() && b_port.is_principal() { - self.half_free(a_wire.addr()); - self.redux(a_port, b_port); - } else { - self.half_link_wire_port(a_port.clone(), a_wire, b_port.clone()); - self.half_link_port_port(b_port, a_port); - } - } - - /// Pushes an active pair to the redex queue; `a` and `b` must both be - /// principal ports. - #[inline(always)] - fn redux(&mut self, a: Port, b: Port) { - trace!(self.tracer, a, b); - if a.is_skippable() && b.is_skippable() { - self.rwts.eras += 1; - } else if !M::LAZY { - self.rdex.push((a, b)); - } else { - self.set_head(a.clone(), b.clone()); - self.set_head(b.clone(), a.clone()); - } - } - - /// Half-links `a_port` to `b_port`, without linking `b_port` back to - /// `a_port`. - #[inline(always)] - fn half_link_port_port(&mut self, a_port: Port, b_port: Port) { - trace!(self.tracer, a_port, b_port); - if a_port.tag() == Var { - a_port.wire().set_target(b_port); - } else { - if M::LAZY { - self.set_head(a_port, b_port); - } - } - } - - /// Half-links a foreign `a_port` (taken from `a_wire`) to `b_port`, without - /// linking `b_port` back to `a_port`. - #[inline(always)] - fn half_link_wire_port(&mut self, a_port: Port, a_wire: Wire, b_port: Port) { - trace!(self.tracer, a_port, a_wire, b_port); - // If 'a_port' is a var... - if a_port.tag() == Var { - let got = a_port.wire().cas_target(Port::new_var(a_wire.addr()), b_port.clone()); - // Attempts to link using a compare-and-swap. - if got.is_ok() { - trace!(self.tracer, "cas ok"); - self.half_free(a_wire.addr()); - // If the CAS failed, resolve by using redirections. - } else { - let got = got.unwrap_err(); - trace!(self.tracer, "cas fail", got); - if b_port.tag() == Var { - let port = b_port.redirect(); - a_wire.set_target(port); - //self.resolve_redirect_var(a_port, a_wire, b_port); - } else if b_port.is_principal() { - a_wire.set_target(b_port.clone()); - self.resolve_redirect_pri(a_port, a_wire, b_port); - } else { - unreachable!(); - } - } - } else { - self.half_free(a_wire.addr()); - if M::LAZY { - self.set_head(a_port, b_port); - } - } - } - - /// Resolves redirects when 'b_port' is a principal port. - fn resolve_redirect_pri(&mut self, mut a_port: Port, a_wire: Wire, b_port: Port) { - trace!(self.tracer); - loop { - trace!(self.tracer, a_port, a_wire, b_port); - // Peek the target, which may not be owned by us. - let mut t_wire = a_port.wire(); - let mut t_port = t_wire.load_target(); - trace!(self.tracer, t_port); - // If it is taken, we wait. - if t_port == Port::LOCK { - spin_loop(); - continue; - } - // If target is a redirection, we own it. Clear and move forward. - if t_port.tag() == Red { - self.half_free(t_wire.addr()); - a_port = t_port; - continue; - } - // If target is a variable, we don't own it. Try replacing it. - if t_port.tag() == Var { - if t_wire.cas_target(t_port.clone(), b_port.clone()).is_ok() { - trace!(self.tracer, "var cas ok"); - // Clear source location. - // self.half_free(a_wire.addr()); - // Collect the orphaned backward path. - t_wire = t_port.wire(); - t_port = t_wire.load_target(); - while t_port != Port::LOCK && t_port.tag() == Red { - trace!(self.tracer, t_wire, t_port); - self.half_free(t_wire.addr()); - t_wire = t_port.wire(); - // if t_wire == a_wire { - // break; - // } - t_port = t_wire.load_target(); - } - return; - } - trace!(self.tracer, "var cas fail"); - // If the CAS failed, the var changed, so we try again. - continue; - } - - // If it is a node, two threads will reach this branch. - if t_port.is_principal() || t_port == Port::GONE { - // Sort references, to avoid deadlocks. - let x_wire = if a_wire < t_wire { a_wire.clone() } else { t_wire.clone() }; - let y_wire = if a_wire < t_wire { t_wire.clone() } else { a_wire.clone() }; - trace!(self.tracer, x_wire, y_wire); - // Swap first reference by Ptr::GONE placeholder. - let x_port = x_wire.swap_target(Port::GONE); - // First to arrive creates a redex. - if x_port != Port::GONE { - let y_port = y_wire.swap_target(Port::GONE); - trace!(self.tracer, "fst", x_wire, y_wire, x_port, y_port); - self.redux(x_port, y_port); - return; - // Second to arrive clears up the memory. - } else { - trace!(self.tracer, "snd !!!", x_wire, y_wire); - self.half_free(x_wire.addr()); - while y_wire.cas_target(Port::GONE, Port::LOCK).is_err() { - spin_loop(); - } - self.half_free(y_wire.addr()); - return; - } - } - // Shouldn't be reached. - trace!(self.tracer, t_port, a_wire, a_port, b_port); - unreachable!() - } - } - - /// Resolves redirects when 'b_port' is an aux port. - // TODO: this is currently broken - #[allow(unused)] - fn resolve_redirect_var(&mut self, _: Port, _: Wire, b_port: Port) { - loop { - let ste_wire = b_port.clone().wire(); - let ste_port = ste_wire.load_target(); - if ste_port.tag() == Var { - let trg_wire = ste_port.wire(); - let trg_port = trg_wire.load_target(); - if trg_port.tag() == Red { - let neo_port = trg_port.unredirect(); - if ste_wire.cas_target(ste_port, neo_port).is_ok() { - self.half_free(trg_wire.addr()); - continue; - } - } - } - break; - } - } - - /// Links a `Trg` to a port, delegating to the appropriate method based on the - /// type of `a`. - #[inline(always)] - pub fn link_trg_port(&mut self, a: Trg, b: Port) { - match a.is_wire() { - true => self.link_wire_port(a.as_wire(), b), - false => self.link_port_port(a.as_port(), b), - } - } - - /// Links two `Trg`s, delegating to the appropriate method based on the types - /// of `a` and `b`. - #[inline(always)] - pub fn link_trg(&mut self, a: Trg, b: Trg) { - match (a.is_wire(), b.is_wire()) { - (true, true) => self.link_wire_wire(a.as_wire(), b.as_wire()), - (true, false) => self.link_wire_port(a.as_wire(), b.as_port()), - (false, true) => self.link_wire_port(b.as_wire(), a.as_port()), - (false, false) => self.link_port_port(a.as_port(), b.as_port()), - } - } - - fn get_head(&self, addr: Addr) -> &Head { - assert!(M::LAZY); - &self.heads[&addr] - } - - fn set_head(&mut self, ptr: Port, trg: Port) { - assert!(M::LAZY); - trace!(self.tracer, ptr, trg); - if ptr.is_full_node() { - self.heads.insert(ptr.addr(), Head { this: ptr, targ: trg }); - } - } - - fn get_target_full(&self, port: Port) -> Port { - assert!(M::LAZY); - if !port.is_principal() { - return port.wire().load_target(); - } - self.heads[&port.addr()].targ.clone() - } -} - -// ---------------- -// Interactions -// ---------------- - -impl<'a, M: Mode> Net<'a, M> { - /// Performs an interaction between two connected principal ports. - #[inline(always)] - pub fn interact(&mut self, a: Port, b: Port) { - self.tracer.sync(); - trace!(self.tracer, a, b); - match (a.tag(), b.tag()) { - // not actually an active pair - (Var | Red, _) | (_, Var | Red) => unreachable!(), - // nil-nil - (Ref, Ref | Num) if !a.is_skippable() => self.call(a, b), - (Ref | Num, Ref) if !b.is_skippable() => self.call(b, a), - (Num | Ref, Num | Ref) => self.rwts.eras += 1, - // comm 2/2 - (Ctr, Mat) if a.lab() != 0 => self.comm22(a, b), - (Mat, Ctr) if b.lab() != 0 => self.comm22(a, b), - (Ctr, Op2) | (Op2, Ctr) => self.comm22(a, b), - (Ctr, Ctr) if a.lab() != b.lab() => self.comm22(a, b), - // comm 1/2 - (Op1, Ctr) => self.comm12(a, b), - (Ctr, Op1) => self.comm12(b, a), - // anni - (Mat, Mat) | (Op2, Op2) | (Ctr, Ctr) => self.anni2(a, b), - (Op1, Op1) => self.anni1(a, b), - // comm 2/0 - (Ref, Ctr) if b.lab() >= a.lab() => self.comm02(a, b), - (Ctr, Ref) if a.lab() >= b.lab() => self.comm02(b, a), - (Num, Ctr) => self.comm02(a, b), - (Ctr, Num) => self.comm02(b, a), - (Ref, _) if a == Port::ERA => self.comm02(a, b), - (_, Ref) if b == Port::ERA => self.comm02(b, a), - // deref - (Ref, _) => self.call(a, b), - (_, Ref) => self.call(b, a), - // native ops - (Op2, Num) => self.op2_num(a, b), - (Num, Op2) => self.op2_num(b, a), - (Op1, Num) => self.op1_num(a, b), - (Num, Op1) => self.op1_num(b, a), - (Mat, Num) => self.mat_num(a, b), - (Num, Mat) => self.mat_num(b, a), - // todo: what should the semantics of these be? - (Mat, Ctr) // b.lab() == 0 - | (Ctr, Mat) // a.lab() == 0 - | (Op2, Op1) - | (Op1, Op2) - | (Op2, Mat) - | (Mat, Op2) - | (Op1, Mat) - | (Mat, Op1) => unimplemented!("{:?}-{:?}", a.tag(), b.tag()), - } - } - - /// Annihilates two binary agents. - /// - /// ```text - /// - /// a2 | | a1 - /// _|___|_ - /// \ / - /// a \ / - /// \ / - /// | - /// / \ - /// b / \ - /// /_____\ - /// | | - /// b1 | | b2 - /// - /// --------------------------- anni2 - /// - /// a2 | | a1 - /// | | - /// \ / - /// X - /// / \ - /// | | - /// b1 | | b2 - /// - /// ``` - #[inline(never)] - pub fn anni2(&mut self, a: Port, b: Port) { - trace!(self.tracer, a, b); - self.rwts.anni += 1; - let a = a.consume_node(); - let b = b.consume_node(); - self.link_wire_wire(a.p1, b.p1); - self.link_wire_wire(a.p2, b.p2); - } - - /// Commutes two binary agents. - /// - /// ```text - /// - /// a2 | | a1 - /// _|___|_ - /// \ / - /// a \ / - /// \ / - /// | - /// /#\ - /// b /###\ - /// /#####\ - /// | | - /// b1 | | b2 - /// - /// --------------------------- comm22 - /// - /// a2 | | a1 - /// | | - /// /#\ /#\ - /// B2 /###\ /###\ B1 - /// /#####\ /#####\ - /// | \ / | - /// p1 | p2 \ / p1 | p2 - /// | \ / | - /// | X | - /// | / \ | - /// p2 | p1 / \ p2 | p1 - /// _|___/_ _\___|_ - /// \ / \ / - /// A1 \ / \ / A2 - /// \ / \ / - /// | | - /// b1 | | b2 - /// - /// ``` - #[inline(never)] - pub fn comm22(&mut self, a: Port, b: Port) { - trace!(self.tracer, a, b); - self.rwts.comm += 1; - - let a = a.consume_node(); - let b = b.consume_node(); - - let A1 = self.create_node(a.tag, a.lab); - let A2 = self.create_node(a.tag, a.lab); - let B1 = self.create_node(b.tag, b.lab); - let B2 = self.create_node(b.tag, b.lab); - - trace!(self.tracer, A1.p0, A2.p0, B1.p0, B2.p0); - self.link_port_port(A1.p1, B1.p1); - self.link_port_port(A1.p2, B2.p1); - self.link_port_port(A2.p1, B1.p2); - self.link_port_port(A2.p2, B2.p2); - - trace!(self.tracer); - self.link_wire_port(a.p1, B1.p0); - self.link_wire_port(a.p2, B2.p0); - self.link_wire_port(b.p1, A1.p0); - self.link_wire_port(b.p2, A2.p0); - } - - /// Commutes a nilary agent and a binary agent. - /// - /// ```text - /// - /// a (---) - /// | - /// | - /// /#\ - /// b /###\ - /// /#####\ - /// | | - /// b1 | | b2 - /// - /// --------------------------- comm02 - /// - /// a (---) (---) a - /// | | - /// b1 | | b2 - /// - /// ``` - #[inline(never)] - pub fn comm02(&mut self, a: Port, b: Port) { - trace!(self.tracer, a, b); - self.rwts.comm += 1; - let b = b.consume_node(); - self.link_wire_port(b.p1, a.clone()); - self.link_wire_port(b.p2, a); - } - - /// Annihilates two unary agents. - /// - /// ```text - /// - /// a2 | - /// | n - /// _|___|_ - /// \ / - /// a \op1/ - /// \ / - /// | - /// / \ - /// b /op1\ - /// /_____\ - /// | | - /// m | - /// | b2 - /// - /// --------------------------- anni1 - /// - /// a2 | - /// | - /// | - /// \ - /// \ - /// \ - /// | - /// | - /// | b2 - /// - /// ``` - #[inline(never)] - pub fn anni1(&mut self, a: Port, b: Port) { - trace!(self.tracer, a, b); - self.rwts.anni += 1; - let a = a.consume_op1(); - let b = b.consume_op1(); - self.link_wire_wire(a.p2, b.p2); - } - - /// Commutes a unary agent and a unary agent. - /// - /// ```text - /// - /// a2 | n - /// _|___|_ - /// \ / - /// a \op1/ - /// \ / - /// | - /// /#\ - /// b /###\ - /// /#####\ - /// | | - /// b1 | | b2 - /// - /// --------------------------- comm12 - /// - /// a2 | - /// | - /// /#\ - /// B2 /###\ - /// /#####\ - /// | \ - /// p1 | p2 \ - /// | \ - /// | \ - /// | \ - /// p2 | n \ p2 n - /// _|___|_ _\___|_ - /// \ / \ / - /// A1 \op1/ \op1/ A2 - /// \ / \ / - /// | | - /// b1 | | b2 - /// - /// ``` - #[inline(never)] - pub fn comm12(&mut self, a: Port, b: Port) { - trace!(self.tracer, a, b); - self.rwts.comm += 1; - - let a = a.consume_op1(); - let b = b.consume_node(); - - let A1 = self.create_node(Op1, a.op as Lab); - let A2 = self.create_node(Op1, a.op as Lab); - let B2 = self.create_node(b.tag, b.lab); - - trace!(self.tracer, B2.p0, A1.p0, A2.p0); - self.link_port_port(A1.p1, a.num.clone()); - self.link_port_port(A1.p2, B2.p1); - self.link_port_port(A2.p1, a.num.clone()); - self.link_port_port(A2.p2, B2.p2); - - trace!(self.tracer); - self.link_wire_port(a.p2, B2.p0); - self.link_wire_port(b.p1, A1.p0); - self.link_wire_port(b.p2, A2.p0); - } - - /// Commutes a nilary agent and a unary agent. - /// - /// ```text - /// - /// a (---) - /// | - /// | - /// / \ - /// b /op1\ - /// /_____\ - /// | | - /// n | - /// | b2 - /// - /// --------------------------- comm02 - /// - /// (---) a - /// | - /// | b2 - /// - /// ``` - #[inline(never)] - pub fn comm01(&mut self, a: Port, b: Port) { - trace!(self.tracer, a, b); - self.rwts.comm += 1; - let b = b.consume_op1(); - self.link_wire_port(b.p2, a); - } - - /// Interacts a number and a numeric match node. - /// - /// ```text - /// | - /// b (0) | b (n+1) - /// | | | - /// | | | - /// / \ | / \ - /// a /mat\ | a /mat\ - /// /_____\ | /_____\ - /// | | | | | - /// a1 | | a2 | a1 | | a2 - /// | - /// --------------------------- | --------------------------- mat_num - /// | _ _ _ _ _ - /// | / \ - /// | y2 | (n) y1 | - /// | _|___|_ | - /// | \ / | - /// _ | y \ / | - /// / \ | \ / | - /// x2 (*) | x1 | | x2 | (*) x1 | - /// _|___|_ | | _|___|_ | - /// \ / | | \ / | - /// x \ / | | x \ / | - /// \ / | | \ / | - /// | | | | | - /// a1 | | a2 | a1 | | a2 - /// | - /// ``` - #[inline(never)] - pub fn mat_num(&mut self, a: Port, b: Port) { - trace!(self.tracer, a, b); - self.rwts.oper += 1; - let a = a.consume_node(); - let b = b.num(); - if b == 0 { - let x = self.create_node(Ctr, 0); - trace!(self.tracer, x.p0); - self.link_port_port(x.p2, Port::ERA); - self.link_wire_port(a.p2, x.p1); - self.link_wire_port(a.p1, x.p0); - } else { - let x = self.create_node(Ctr, 0); - let y = self.create_node(Ctr, 0); - trace!(self.tracer, x.p0, y.p0); - self.link_port_port(x.p1, Port::ERA); - self.link_port_port(x.p2, y.p0); - self.link_port_port(y.p1, Port::new_num(b - 1)); - self.link_wire_port(a.p2, y.p2); - self.link_wire_port(a.p1, x.p0); - } - } - - /// Interacts a number and a binary numeric operation node. - /// - /// ```text - /// - /// b (n) - /// | - /// | - /// / \ - /// a /op2\ - /// /_____\ - /// | | - /// a1 | | a2 - /// - /// --------------------------- op2_num - /// _ _ _ - /// / \ - /// | n | - /// _|___|_ | - /// \ / | - /// x \op1/ | - /// \ / | - /// | | - /// a1 | | a2 - /// - /// ``` - #[inline(never)] - pub fn op2_num(&mut self, a: Port, b: Port) { - trace!(self.tracer, a, b); - self.rwts.oper += 1; - let a = a.consume_node(); - let a1 = a.p1.load_target(); - if a1.tag() == Num { - // skip creating the Op1 node if it will instantly interact - self.rwts.oper += 1; - let out = unsafe { Op::from_unchecked(a.lab) }.op(b.num(), a1.num()); - self.link_wire_port(a.p2, Port::new_num(out)); - } else { - let x = self.create_node(Op1, a.lab); - trace!(self.tracer, x.p0); - self.link_port_port(x.p1, b); - self.link_wire_port(a.p2, x.p2); - self.link_wire_port(a.p1, x.p0); - } - } - - /// Interacts a number and a unary numeric operation node. - /// - /// ```text - /// - /// b (m) - /// | - /// | - /// / \ - /// a /op1\ - /// /_____\ - /// | | - /// n | - /// | a2 - /// - /// --------------------------- op1_num - /// - /// (n opr m) - /// | - /// | a2 - /// - /// ``` - #[inline(never)] - pub fn op1_num(&mut self, a: Port, b: Port) { - trace!(self.tracer, a, b); - self.rwts.oper += 1; - let a = a.consume_op1(); - let n = a.num.num(); - let m = b.num(); - let out = a.op.op(n, m); - self.link_wire_port(a.p2, Port::new_num(out)); - } -} - -// ---------------- -// Instructions -// ---------------- - -impl<'a, M: Mode> Net<'a, M> { - /// `trg ~ {#lab x y}` - #[inline(always)] - pub(crate) fn do_ctr(&mut self, lab: Lab, trg: Trg) -> (Trg, Trg) { - let port = trg.target(); - if !M::LAZY && port.tag() == Ctr && port.lab() == lab { - trace!(self.tracer, "fast"); - self.free_trg(trg); - let node = port.consume_node(); - self.rwts.anni += 1; - (Trg::wire(node.p1), Trg::wire(node.p2)) - // TODO: fast copy? - } else if false && !M::LAZY && port.tag() == Num || port.tag() == Ref && lab >= port.lab() { - self.rwts.comm += 1; - (Trg::port(port.clone()), Trg::port(port)) - } else { - let n = self.create_node(Ctr, lab); - self.link_trg_port(trg, n.p0); - (Trg::port(n.p1), Trg::port(n.p2)) - } - } - - /// `trg ~ ` - #[inline(always)] - pub(crate) fn do_op2(&mut self, op: Op, trg: Trg) -> (Trg, Trg) { - trace!(self.tracer, op, trg); - let port = trg.target(); - if !M::LAZY && port.tag() == Num { - self.rwts.oper += 1; - self.free_trg(trg); - let n = self.create_node(Op1, op as Lab); - n.p1.wire().set_target(Port::new_num(port.num())); - (Trg::port(n.p0), Trg::port(n.p2)) - } else if !M::LAZY && port == Port::ERA { - (Trg::port(Port::ERA), Trg::port(Port::ERA)) - } else { - let n = self.create_node(Op2, op as Lab); - self.link_trg_port(trg, n.p0); - (Trg::port(n.p1), Trg::port(n.p2)) - } - } - - /// `trg ~ ` - #[inline(always)] - pub(crate) fn do_op1(&mut self, op: Op, a: u64, trg: Trg) -> Trg { - let port = trg.target(); - if !M::LAZY && trg.target().tag() == Num { - self.rwts.oper += 1; - self.free_trg(trg); - Trg::port(Port::new_num(op.op(a, port.num()))) - } else if !M::LAZY && port == Port::ERA { - Trg::port(Port::ERA) - } else { - let n = self.create_node(Op1, op as Lab); - self.link_trg_port(trg, n.p0); - n.p1.wire().set_target(Port::new_num(a)); - Trg::port(n.p2) - } - } - - /// `trg ~ ?` - #[inline(always)] - pub(crate) fn do_mat(&mut self, trg: Trg) -> (Trg, Trg) { - let port = trg.target(); - if !M::LAZY && port.tag() == Num { - self.rwts.oper += 1; - self.free_trg(trg); - let num = port.num(); - let c1 = self.create_node(Ctr, 0); - if num == 0 { - self.link_port_port(c1.p2, Port::ERA); - (Trg::port(c1.p0), Trg::wire(self.create_wire_to(c1.p1))) - } else { - let c2 = self.create_node(Ctr, 0); - self.link_port_port(c1.p1, Port::ERA); - self.link_port_port(c1.p2, c2.p0); - self.link_port_port(c2.p1, Port::new_num(num - 1)); - (Trg::port(c1.p0), Trg::wire(self.create_wire_to(c2.p2))) - } - } else if !M::LAZY && port == Port::ERA { - self.rwts.eras += 1; - self.free_trg(trg); - (Trg::port(Port::ERA), Trg::port(Port::ERA)) - } else { - let m = self.create_node(Mat, 0); - self.link_trg_port(trg, m.p0); - (Trg::port(m.p1), Trg::port(m.p2)) - } - } - - #[inline(always)] - pub(crate) fn do_wires(&mut self) -> (Trg, Trg, Trg, Trg) { - let a = self.alloc(); - let b = a.other_half(); - ( - Trg::port(Port::new_var(a.clone())), - Trg::wire(Wire::new(a)), - Trg::port(Port::new_var(b.clone())), - Trg::wire(Wire::new(b)), - ) - } - - /// `trg ~ ` - #[inline(always)] - #[allow(unused)] // TODO: emit this instruction - pub(crate) fn do_op2_num(&mut self, op: Op, b: u64, trg: Trg) -> Trg { - let port = trg.target(); - if !M::LAZY && port.tag() == Num { - self.rwts.oper += 2; - self.free_trg(trg); - Trg::port(Port::new_num(op.op(port.num(), b))) - } else if !M::LAZY && port == Port::ERA { - Trg::port(Port::ERA) - } else { - let n = self.create_node(Op2, op as Lab); - self.link_trg_port(trg, n.p0); - n.p1.wire().set_target(Port::new_num(b)); - Trg::port(n.p2) - } - } - - /// `trg ~ ?<(x (y z)) out>` - #[inline(always)] - #[allow(unused)] // TODO: emit this instruction - pub(crate) fn do_mat_con_con(&mut self, trg: Trg, out: Trg) -> (Trg, Trg, Trg) { - let port = trg.target(); - if !M::LAZY && trg.target().tag() == Num { - self.rwts.oper += 1; - self.free_trg(trg); - let num = port.num(); - if num == 0 { - (out, Trg::port(Port::ERA), Trg::port(Port::ERA)) - } else { - (Trg::port(Port::ERA), Trg::port(Port::new_num(num - 1)), out) - } - } else if !M::LAZY && port == Port::ERA { - self.link_trg_port(out, Port::ERA); - (Trg::port(Port::ERA), Trg::port(Port::ERA), Trg::port(Port::ERA)) - } else { - let m = self.create_node(Mat, 0); - let c1 = self.create_node(Ctr, 0); - let c2 = self.create_node(Ctr, 0); - self.link_port_port(m.p1, c1.p0); - self.link_port_port(c1.p2, c2.p0); - self.link_trg_port(out, m.p2); - (Trg::port(c1.p1), Trg::port(c2.p1), Trg::port(c2.p2)) - } - } - - /// `trg ~ ?<(x y) out>` - #[inline(always)] - #[allow(unused)] // TODO: emit this instruction - pub(crate) fn do_mat_con(&mut self, trg: Trg, out: Trg) -> (Trg, Trg) { - let port = trg.target(); - if trg.target().tag() == Num { - self.rwts.oper += 1; - self.free_trg(trg); - let num = port.num(); - if num == 0 { - (out, Trg::port(Port::ERA)) - } else { - let c2 = self.create_node(Ctr, 0); - c2.p1.wire().set_target(Port::new_num(num - 1)); - self.link_trg_port(out, c2.p2); - (Trg::port(Port::ERA), Trg::port(c2.p0)) - } - } else if port == Port::ERA { - self.link_trg_port(out, Port::ERA); - (Trg::port(Port::ERA), Trg::port(Port::ERA)) - } else { - let m = self.create_node(Mat, 0); - let c1 = self.create_node(Ctr, 0); - self.link_port_port(m.p1, c1.p0); - self.link_trg_port(out, m.p2); - self.link_trg_port(trg, m.p0); - (Trg::port(c1.p1), Trg::port(c1.p2)) - } - } -} - -impl<'a, M: Mode> Net<'a, M> { - /// Expands a [`Ref`] node connected to `trg`. - #[inline(never)] - pub fn call(&mut self, port: Port, trg: Port) { - trace!(self.tracer, port, trg); - - let def = port.addr().def(); - - if trg.tag() == Ctr && !def.labs.has(trg.lab()) { - return self.comm02(port, trg); - } - - self.rwts.dref += 1; - - unsafe { Def::call(port.addr().0 as *const _, self, trg) } - } -} - -impl AsDef for InterpretedDef { - unsafe fn call(slf: *const Def, net: &mut Net, trg: Port) { - let instructions = unsafe { &(*slf).data.instr }; - - let mut trgs = unsafe { std::mem::transmute::<_, Trgs>(Trgs(&mut net.trgs[..])) }; - - struct Trgs<'a>(&'a mut [Trg]); - - impl<'a> Trgs<'a> { - #[inline(always)] - fn get_trg(&self, i: TrgId) -> Trg { - unsafe { (*self.0.as_ptr().byte_offset(i.byte_offset as _)).clone() } - } - - #[inline(always)] - fn set_trg(&mut self, i: TrgId, trg: Trg) { - unsafe { *self.0.as_mut_ptr().byte_offset(i.byte_offset as _) = trg } - } - } - - trgs.set_trg(TrgId::new(0), Trg::port(trg)); - for i in instructions { - unsafe { - match *i { - Instruction::Const { trg, ref port } => trgs.set_trg(trg, Trg::port(port.clone())), - Instruction::Link { a, b } => net.link_trg(trgs.get_trg(a), trgs.get_trg(b)), - Instruction::LinkConst { trg, ref port } => { - if !port.is_principal() { - unreachable_unchecked() - } - net.link_trg_port(trgs.get_trg(trg), port.clone()) - } - Instruction::Ctr { lab, trg, lft, rgt } => { - let (l, r) = net.do_ctr(lab, trgs.get_trg(trg)); - trgs.set_trg(lft, l); - trgs.set_trg(rgt, r); - } - Instruction::Op2 { op, trg, lft, rgt } => { - let (l, r) = net.do_op2(op, trgs.get_trg(trg)); - trgs.set_trg(lft, l); - trgs.set_trg(rgt, r); - } - Instruction::Op1 { op, num, trg, rgt } => { - let r = net.do_op1(op, num, trgs.get_trg(trg)); - trgs.set_trg(rgt, r); - } - Instruction::Mat { trg, lft, rgt } => { - let (l, r) = net.do_mat(trgs.get_trg(trg)); - trgs.set_trg(lft, l); - trgs.set_trg(rgt, r); - } - Instruction::Wires { av, aw, bv, bw } => { - let (avt, awt, bvt, bwt) = net.do_wires(); - trgs.set_trg(av, avt); - trgs.set_trg(aw, awt); - trgs.set_trg(bv, bvt); - trgs.set_trg(bw, bwt); - } - } - } - } - } -} - -// ----------------- -// Normalization -// ----------------- - -impl<'a, M: Mode> Net<'a, M> { - /// Reduces at most `limit` redexes. - #[inline(always)] - pub fn reduce(&mut self, limit: usize) -> usize { - assert!(!M::LAZY); - let mut count = 0; - while let Some((a, b)) = self.rdex.pop() { - self.interact(a, b); - count += 1; - if count >= limit { - break; - } - } - count - } - - /// Expands [`Ref`] nodes in the tree connected to `root`. - #[inline(always)] - pub fn expand(&mut self) { - assert!(!M::LAZY); - fn go(net: &mut Net, wire: Wire, len: usize, key: usize) { - trace!(net.tracer, wire); - let port = wire.load_target(); - trace!(net.tracer, port); - if port == Port::LOCK { - return; - } - if port.tag() == Ctr { - let node = port.traverse_node(); - if len >= net.tids || key % 2 == 0 { - go(net, node.p1, len.saturating_mul(2), key / 2); - } - if len >= net.tids || key % 2 == 1 { - go(net, node.p2, len.saturating_mul(2), key / 2); - } - } else if port.tag() == Ref && port != Port::ERA { - let got = wire.swap_target(Port::LOCK); - if got != Port::LOCK { - trace!(net.tracer, port, wire); - net.call(port, Port::new_var(wire.addr())); - } - } - } - go(self, self.root.clone(), 1, self.tid); - } - - /// Forks the net into `tids` child nets, for parallel operation. - pub fn fork(&mut self, tids: usize) -> impl Iterator + '_ { - let mut redexes = std::mem::take(&mut self.rdex).into_iter(); - (0 .. tids).map(move |tid| { - let heap_size = self.area.len() / tids; - let heap_start = heap_size * tid; - let area = &self.area[heap_start .. heap_start + heap_size]; - let mut net = Net::new_with_root(area, self.root.clone()); - net.next = self.next.saturating_sub(heap_start); - net.head = if tid == 0 { self.head.clone() } else { Addr::NULL }; - net.tid = tid; - net.tids = tids; - net.tracer.set_tid(tid); - let count = redexes.len() / (tids - tid); - net.rdex.extend((&mut redexes).take(count)); - net - }) - } - - // Evaluates a term to normal form in parallel - pub fn parallel_normal(&mut self) { - assert!(!M::LAZY); - - const SHARE_LIMIT: usize = 1 << 12; // max share redexes per split - const LOCAL_LIMIT: usize = 1 << 18; // max local rewrites per epoch - - // Local thread context - struct ThreadContext<'a, M: Mode> { - tid: usize, // thread id - tlog2: usize, // log2 of thread count - tick: usize, // current tick - net: Net<'a, M>, // thread's own net object - delta: &'a AtomicRewrites, // global delta rewrites - share: &'a Vec<(AtomicU64, AtomicU64)>, // global share buffer - rlens: &'a Vec, // global redex lengths - total: &'a AtomicUsize, // total redex length - barry: Arc, // synchronization barrier - } - - // Initialize global objects - let cores = std::thread::available_parallelism().unwrap().get() as usize; - let tlog2 = cores.ilog2() as usize; - let tids = 1 << tlog2; - let delta = AtomicRewrites::default(); // delta rewrite counter - let rlens = (0 .. tids).map(|_| AtomicUsize::new(0)).collect::>(); - let share = (0 .. SHARE_LIMIT * tids).map(|_| Default::default()).collect::>(); - let total = AtomicUsize::new(0); // sum of redex bag length - let barry = Arc::new(Barrier::new(tids)); // global barrier - - // Perform parallel reductions - std::thread::scope(|s| { - for net in self.fork(tids) { - let mut ctx = ThreadContext { - tid: net.tid, - tick: 0, - net, - tlog2, - delta: &delta, - share: &share, - rlens: &rlens, - total: &total, - barry: Arc::clone(&barry), - }; - thread::Builder::new().name(format!("t{:02x?}", ctx.net.tid)).spawn_scoped(s, move || main(&mut ctx)).unwrap(); - } - }); - - // Clear redexes and sum stats - self.rdex.clear(); - delta.add_to(&mut self.rwts); - - // Main reduction loop - #[inline(always)] - fn main(ctx: &mut ThreadContext) { - loop { - reduce(ctx); - ctx.net.expand(); - if count(ctx) == 0 { - break; - } - } - ctx.net.rwts.add_to(ctx.delta); - } - - // Reduce redexes locally, then share with target - #[inline(always)] - fn reduce(ctx: &mut ThreadContext) { - loop { - ctx.net.reduce(LOCAL_LIMIT); - if count(ctx) == 0 { - break; - } - let tlog2 = ctx.tlog2; - split(ctx, tlog2); - ctx.tick += 1; - } - } - - // Count total redexes (and populate 'rlens') - #[inline(always)] - fn count(ctx: &mut ThreadContext) -> usize { - ctx.barry.wait(); - ctx.total.store(0, Relaxed); - ctx.barry.wait(); - ctx.rlens[ctx.tid].store(ctx.net.rdex.len(), Relaxed); - ctx.total.fetch_add(ctx.net.rdex.len(), Relaxed); - ctx.barry.wait(); - ctx.total.load(Relaxed) - } - - // Share redexes with target thread - #[inline(always)] - fn split(ctx: &mut ThreadContext, plog2: usize) { - unsafe { - let side = (ctx.tid >> (plog2 - 1 - (ctx.tick % plog2))) & 1; - let shift = (1 << (plog2 - 1)) >> (ctx.tick % plog2); - let a_tid = ctx.tid; - let b_tid = if side == 1 { a_tid - shift } else { a_tid + shift }; - let a_len = ctx.net.rdex.len(); - let b_len = ctx.rlens[b_tid].load(Relaxed); - let send = if a_len > b_len { (a_len - b_len) / 2 } else { 0 }; - let recv = if b_len > a_len { (b_len - a_len) / 2 } else { 0 }; - let send = std::cmp::min(send, SHARE_LIMIT); - let recv = std::cmp::min(recv, SHARE_LIMIT); - for i in 0 .. send { - let init = a_len - send * 2; - let rdx0 = ctx.net.rdex.get_unchecked(init + i * 2 + 0).clone(); - let rdx1 = ctx.net.rdex.get_unchecked(init + i * 2 + 1).clone(); - //let init = 0; - //let ref0 = ctx.net.rdex.get_unchecked_mut(init + i * 2 + 0); - //let rdx0 = *ref0; - //*ref0 = (Ptr(0), Ptr(0)); - //let ref1 = ctx.net.rdex.get_unchecked_mut(init + i * 2 + 1); - //let rdx1 = *ref1; - //*ref1 = (Ptr(0), Ptr(0)); - let targ = ctx.share.get_unchecked(b_tid * SHARE_LIMIT + i); - *ctx.net.rdex.get_unchecked_mut(init + i) = rdx0; - targ.0.store(rdx1.0.0, Relaxed); - targ.1.store(rdx1.1.0, Relaxed); - } - ctx.net.rdex.truncate(a_len - send); - ctx.barry.wait(); - for i in 0 .. recv { - let got = ctx.share.get_unchecked(a_tid * SHARE_LIMIT + i); - ctx.net.rdex.push((Port(got.0.load(Relaxed)), Port(got.1.load(Relaxed)))); - } - } - } - } - - // Lazy mode weak head normalizer - #[inline(always)] - fn weak_normal(&mut self, mut prev: Port, root: Wire) -> Port { - assert!(M::LAZY); - - let mut path: Vec = vec![]; - - loop { - trace!(self.tracer, prev); - // Load ptrs - let next = self.get_target_full(prev.clone()); - trace!(self.tracer, next); - - // If next is root, stop. - if next == Port::new_var(root.addr()) || next == Port::new_var(self.root.addr()) { - break; - } - - // If next is a main port... - if next.is_principal() { - // If prev is a main port, reduce the active pair. - if prev.is_principal() { - self.interact(next, prev.clone()); - prev = path.pop().unwrap(); - continue; - // Otherwise, if it is a ref, expand it. - } else if next.tag() == Ref && next != Port::ERA { - self.call(next, prev.clone()); - continue; - // Otherwise, we're done. - } else { - break; - } - } - - // If next is an aux port, pass through. - let main = self.get_head(next.addr().left_half()); - path.push(prev); - prev = main.this.clone(); - } - - return self.get_target_full(prev); - } - - pub fn normal_from(&mut self, root: Wire) { - assert!(M::LAZY); - let mut visit = vec![Port::new_var(root.addr())]; - while let Some(prev) = visit.pop() { - trace!(self.tracer, "visit", prev); - //println!("normal {} | {}", prev.view(), self.rewrites()); - let next = self.weak_normal(prev, root.clone()); - trace!(self.tracer, "got", next); - if next.is_full_node() { - if next.tag() != Op1 { - visit.push(Port::new_var(next.addr())); - } - visit.push(Port::new_var(next.addr().other_half())); - } - } - } - - /// Reduces a net to normal form. - pub fn normal(&mut self) { - if M::LAZY { - self.normal_from(self.root.clone()); - } else { - self.expand(); - while !self.rdex.is_empty() { - self.reduce(usize::MAX); - self.expand(); - } - } - } -} - /// A [`Net`] whose mode is determined dynamically, at runtime. /// /// Use [`dispatch_dyn_net!`] to wrap operations on the inner net. @@ -2301,9 +144,9 @@ pub enum DynNet<'a> { Strict(Net<'a, Strict>), } -impl<'a> DynNet<'a> { - pub fn new(area: &'a [Node], lazy: bool) -> Self { - if lazy { DynNet::Lazy(Net::new(area)) } else { DynNet::Strict(Net::new(area)) } +impl<'h> DynNet<'h> { + pub fn new(heap: &'h Heap, lazy: bool) -> Self { + if lazy { DynNet::Lazy(Net::new(heap)) } else { DynNet::Strict(Net::new(heap)) } } } diff --git a/src/run/addr.rs b/src/run/addr.rs new file mode 100644 index 00000000..c27eac8d --- /dev/null +++ b/src/run/addr.rs @@ -0,0 +1,53 @@ +use super::*; + +/// A memory address to be used in a [`Port`] or a [`Wire`]. +/// +/// The bottom three bits must be zero; i.e. this address must be at least +/// 8-byte-aligned. +/// +/// Additionally, all bits other than the lowest 48 must be zero. On a 32-bit +/// system, this has no effect, but on a 64-bit system, this means that the top +/// 16 bits much be zero. +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[must_use] +pub struct Addr(pub usize); + +impl IsEnabled for Addr {} + +impl fmt::Debug for Addr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:012x?}", self.0) + } +} + +impl Addr { + pub const NULL: Addr = Addr(0); + + /// Casts this address into an `&AtomicU64`, which may or may not be valid. + #[inline(always)] + pub fn val<'a>(&self) -> &'a AtomicU64 { + unsafe { &*(self.0 as *const _) } + } + + /// Casts this address into an `&Def`, which may or may not be valid. + #[inline(always)] + pub fn def<'a>(&self) -> &'a Def { + unsafe { &*(self.0 as *const _) } + } + + const HALF_MASK: usize = 0b1000; + + /// Given an address to one word of a two-word allocation, returns the address + /// of the first word of that allocation. + #[inline(always)] + pub(super) fn left_half(&self) -> Self { + Addr(self.0 & !Addr::HALF_MASK) + } + + /// Given an address to one word of a two-word allocation, returns the address + /// of the other word of that allocation. + #[inline(always)] + pub fn other_half(&self) -> Self { + Addr(self.0 ^ Addr::HALF_MASK) + } +} diff --git a/src/run/allocator.rs b/src/run/allocator.rs new file mode 100644 index 00000000..fd9e8508 --- /dev/null +++ b/src/run/allocator.rs @@ -0,0 +1,100 @@ +use super::*; + +/// The memory behind a two-word allocation. +/// +/// This must be aligned to 16 bytes so that the left word's address always ends +/// with `0b0000` and the right word's address always ends with `0b1000`. +#[repr(C)] +#[repr(align(16))] +#[derive(Default)] +pub(super) struct Node(pub AtomicU64, pub AtomicU64); + +#[repr(align(16))] +pub struct Heap(pub(super) [Node]); + +impl Heap { + /// Allocate an area for the net's heap with a given size. + pub fn new(size: usize) -> Box { + unsafe { + Box::from_raw(core::ptr::slice_from_raw_parts_mut( + alloc::alloc(Layout::array::(size).unwrap()) as *mut _, + size, + ) as *mut _) + } + } +} + +pub struct Allocator<'h> { + pub(super) tracer: Tracer, + pub(super) heap: &'h Heap, + pub(super) next: usize, + pub(super) head: Addr, +} + +deref!({<'h>} Allocator<'h> => self.tracer: Tracer); + +impl<'h> Allocator<'h> { + pub fn new(heap: &'h Heap) -> Self { + Allocator { tracer: Tracer::default(), heap, next: 0, head: Addr::NULL } + } + + /// Frees one word of a two-word allocation. + #[inline(always)] + pub fn half_free(&mut self, addr: Addr) { + trace!(self.tracer, addr); + const FREE: u64 = Port::FREE.0; + if cfg!(feature = "_fuzz") { + if cfg!(not(feature = "_fuzz_no_free")) { + assert_ne!(addr.val().swap(FREE, Relaxed), FREE, "double free"); + } + } else { + addr.val().store(FREE, Relaxed); + if addr.other_half().val().load(Relaxed) == FREE { + trace!(self.tracer, "other free"); + let addr = addr.left_half(); + if addr.val().compare_exchange(FREE, self.head.0 as u64, Relaxed, Relaxed).is_ok() { + let old_head = &self.head; + let new_head = addr; + trace!(self.tracer, "appended", old_head, new_head); + self.head = new_head; + } else { + trace!(self.tracer, "too slow"); + }; + } + } + } + + /// Allocates a two-word node. + #[inline(never)] + pub fn alloc(&mut self) -> Addr { + trace!(self.tracer, self.head); + let addr = if self.head != Addr::NULL { + let addr = self.head.clone(); + let next = Addr(self.head.val().load(Relaxed) as usize); + trace!(self.tracer, next); + self.head = next; + addr + } else { + let index = self.next; + self.next += 1; + Addr(&self.heap.0.get(index).expect("OOM").0 as *const _ as _) + }; + trace!(self.tracer, addr, self.head); + addr.val().store(Port::LOCK.0, Relaxed); + addr.other_half().val().store(Port::LOCK.0, Relaxed); + addr + } + + #[inline(always)] + pub(crate) fn free_wire(&mut self, wire: Wire) { + self.half_free(wire.addr()); + } + + /// If `trg` is a wire, frees the backing memory. + #[inline(always)] + pub(crate) fn free_trg(&mut self, trg: Trg) { + if trg.is_wire() { + self.free_wire(trg.as_wire()); + } + } +} diff --git a/src/run/def.rs b/src/run/def.rs new file mode 100644 index 00000000..6642badd --- /dev/null +++ b/src/run/def.rs @@ -0,0 +1,267 @@ +use super::*; + +/// A bitset representing the set of labels used in a def. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct LabSet { + /// The least label greater than every label in the set. + /// + /// `lab >= set.min_safe` implies `!set.has(lab)`. + pub(crate) min_safe: Lab, + pub(crate) bits: Cow<'static, [u64]>, +} + +impl LabSet { + pub const NONE: LabSet = LabSet { min_safe: 0, bits: Cow::Borrowed(&[]) }; + pub const ALL: LabSet = LabSet { min_safe: Lab::MAX, bits: Cow::Borrowed(&[u64::MAX; 1024]) }; + + pub fn add(&mut self, lab: Lab) { + self.min_safe = self.min_safe.max(lab + 1); + let index = (lab >> 6) as usize; + let bit = lab & 63; + let bits = self.bits.to_mut(); + if index >= bits.len() { + bits.resize(index + 1, 0); + } + bits[index] |= 1 << bit; + } + + pub fn has(&self, lab: Lab) -> bool { + if lab >= self.min_safe { + return false; + } + let index = (lab >> 6) as usize; + let bit = lab & 63; + unsafe { self.bits.get_unchecked(index) & 1 << bit != 0 } + } + + /// Adds all of the labels in `other` to this set. + pub fn union(&mut self, other: &LabSet) { + self.min_safe = self.min_safe.max(other.min_safe); + let bits = self.bits.to_mut(); + for (a, b) in bits.iter_mut().zip(other.bits.iter()) { + *a |= b; + } + if other.bits.len() > bits.len() { + bits.extend_from_slice(&other.bits[bits.len() ..]) + } + } + + pub const fn from_bits(bits: &'static [u64]) -> Self { + if bits.is_empty() { + return LabSet::NONE; + } + let min_safe = (bits.len() << 6) as u16 - bits[bits.len() - 1].leading_zeros() as u16; + LabSet { min_safe, bits: Cow::Borrowed(bits) } + } +} + +impl FromIterator for LabSet { + fn from_iter>(iter: T) -> Self { + let mut set = LabSet::default(); + for lab in iter { + set.add(lab); + } + set + } +} + +/// A custom nilary interaction net agent. +/// +/// This is *roughly* equivalent to the following definition: +/// ```rust,ignore +/// struct Def { +/// pub labs: LabSet, +/// pub data: T, +/// } +/// ``` +/// +/// Except that, with this definition, `&Def` is a thin pointer, as the vtable +/// data is effectively stored inline in `Def`. +#[repr(C)] // ensure that the fields will have a consistent alignment, regardless of `T` +#[repr(align(32))] // `Ref` ports are `Align4` +pub struct Def { + /// The set of labels used by this agent; the agent commutes with any + /// interaction combinator whose label is not in this set. + pub labs: LabSet, + ty: TypeId, + call_strict: unsafe fn(*const Def, &mut Net, port: Port), + call_lazy: unsafe fn(*const Def, &mut Net, port: Port), + pub data: T, +} + +extern "C" { + /// An internal type used to mark dynamic `Def`s. + /// + /// Because this is an `extern type`, it is unsized, but has zero metadata. + /// This is essentially a workaround for the lack of custom DSTs. + #[doc(hidden)] + pub type Dynamic; +} + +unsafe impl Send for Dynamic {} +unsafe impl Sync for Dynamic {} + +pub trait AsDef: Any + Send + Sync { + unsafe fn call(slf: *const Def, net: &mut Net, port: Port); +} + +impl Def { + pub const fn new(labs: LabSet, data: T) -> Self + where + T: AsDef, + { + Def { labs, ty: TypeId::of::(), call_strict: T::call::, call_lazy: T::call::, data } + } + + #[inline(always)] + pub const fn upcast(&self) -> &Def { + unsafe { &*(self as *const _ as *const _) } + } + + #[inline(always)] + pub fn upcast_mut(&mut self) -> &mut Def { + unsafe { &mut *(self as *mut _ as *mut _) } + } +} + +impl Def { + #[inline(always)] + pub unsafe fn downcast_ptr(slf: *const Def) -> Option<*const Def> { + if (*slf).ty == TypeId::of::() { Some(slf.cast()) } else { None } + } + #[inline(always)] + pub unsafe fn downcast_mut_ptr(slf: *mut Def) -> Option<*mut Def> { + if (*slf).ty == TypeId::of::() { Some(slf.cast()) } else { None } + } + #[inline(always)] + pub fn downcast_ref(&self) -> Option<&Def> { + unsafe { Def::downcast_ptr(self).map(|x| &*x) } + } + #[inline(always)] + pub fn downcast_mut(&mut self) -> Option<&mut Def> { + unsafe { Def::downcast_mut_ptr(self).map(|x| &mut *x) } + } + #[inline(always)] + pub unsafe fn call(slf: *const Def, net: &mut Net, port: Port) { + if M::LAZY { + ((*slf).call_lazy)(slf as *const _, std::mem::transmute(net), port) + } else { + ((*slf).call_strict)(slf as *const _, std::mem::transmute(net), port) + } + } +} + +impl Deref for Def { + type Target = Def; + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.upcast() + } +} + +impl DerefMut for Def { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.upcast_mut() + } +} + +impl, Port) + Send + Sync + 'static, G: Fn(&mut Net, Port) + Send + Sync + 'static> AsDef + for (F, G) +{ + unsafe fn call(slf: *const Def, net: &mut Net, port: Port) { + if M::LAZY { + ((*slf).data.1)(std::mem::transmute::<_, &mut Net>(net), port) + } else { + ((*slf).data.0)(std::mem::transmute::<_, &mut Net>(net), port) + } + } +} + +impl<'a, M: Mode> Net<'a, M> { + /// Expands a [`Ref`] node connected to `trg`. + #[inline(never)] + pub fn call(&mut self, port: Port, trg: Port) { + trace!(self, port, trg); + + let def = port.addr().def(); + + if trg.tag() == Ctr && !def.labs.has(trg.lab()) { + return self.comm02(port, trg); + } + + self.rwts.dref += 1; + + unsafe { Def::call(port.addr().0 as *const _, self, trg) } + } +} + +/// [`Def`]s, when not pre-compiled, are represented as lists of instructions. +#[derive(Debug, Default, Clone)] +pub struct InterpretedDef { + pub instr: Vec, +} + +impl AsDef for InterpretedDef { + unsafe fn call(slf: *const Def, net: &mut Net, trg: Port) { + let instructions = unsafe { &(*slf).data.instr }; + + let mut trgs = unsafe { std::mem::transmute::<_, Trgs>(Trgs(&mut net.trgs[..])) }; + + struct Trgs<'a>(&'a mut [Trg]); + + impl<'a> Trgs<'a> { + #[inline(always)] + fn get_trg(&self, i: TrgId) -> Trg { + unsafe { (*self.0.as_ptr().byte_offset(i.byte_offset as _)).clone() } + } + + #[inline(always)] + fn set_trg(&mut self, i: TrgId, trg: Trg) { + unsafe { *self.0.as_mut_ptr().byte_offset(i.byte_offset as _) = trg } + } + } + + trgs.set_trg(TrgId::new(0), Trg::port(trg)); + for i in instructions { + unsafe { + match *i { + Instruction::Const { trg, ref port } => trgs.set_trg(trg, Trg::port(port.clone())), + Instruction::Link { a, b } => net.link_trg(trgs.get_trg(a), trgs.get_trg(b)), + Instruction::LinkConst { trg, ref port } => { + if !port.is_principal() { + unreachable_unchecked() + } + net.link_trg_port(trgs.get_trg(trg), port.clone()) + } + Instruction::Ctr { lab, trg, lft, rgt } => { + let (l, r) = net.do_ctr(lab, trgs.get_trg(trg)); + trgs.set_trg(lft, l); + trgs.set_trg(rgt, r); + } + Instruction::Op2 { op, trg, lft, rgt } => { + let (l, r) = net.do_op2(op, trgs.get_trg(trg)); + trgs.set_trg(lft, l); + trgs.set_trg(rgt, r); + } + Instruction::Op1 { op, trg, num, rgt } => { + let r = net.do_op1(op, num, trgs.get_trg(trg)); + trgs.set_trg(rgt, r); + } + Instruction::Mat { trg, lft, rgt } => { + let (l, r) = net.do_mat(trgs.get_trg(trg)); + trgs.set_trg(lft, l); + trgs.set_trg(rgt, r); + } + Instruction::Wires { av, aw, bv, bw } => { + let (avt, awt, bvt, bwt) = net.do_wires(); + trgs.set_trg(av, avt); + trgs.set_trg(aw, awt); + trgs.set_trg(bv, bvt); + trgs.set_trg(bw, bwt); + } + } + } + } + } +} diff --git a/src/run/instruction.rs b/src/run/instruction.rs new file mode 100644 index 00000000..dd184bdc --- /dev/null +++ b/src/run/instruction.rs @@ -0,0 +1,288 @@ +use super::*; + +/// Each instruction corresponds to a fragment of a net that has a native +/// implementation. +/// +/// These net fragments may have several free ports, which are each represented +/// with [`TrgId`]s. +/// +/// Each `TrgId` of an instruction has an associated polarity -- it can either +/// be an input or an output. Because the underlying interaction net model we're +/// using does not have polarity, we also need instructions for linking out-out +/// or in-in. +/// +/// Linking two outputs can be done with [`Instruction::Link`], which creates a +/// "cup" wire. +/// +/// Linking two inputs is more complicated, due to the way locking works. It can +/// be done with [`Instruction::Wires`], which creates two "cap" wires. One half +/// of each cap can be used for each input. Once those inputs have been fully +/// unlocked, the other halves of each cap can be linked with +/// [`Instruction::Link`]. For example: +/// ```rust,ignore +/// let (av, aw, bv, bw) = net.do_wires(); +/// some_subnet(net, av, bv); +/// net.link(aw, bw); +/// ``` +/// +/// Each instruction documents both the native implementation and the polarity +/// of each `TrgId`. +/// +/// Some instructions take a [`Port`]; these must always be statically-valid +/// ports -- that is, [`Ref`] or [`Num`] ports. +#[derive(Debug, Clone)] +pub enum Instruction { + /// ```rust,ignore + /// let trg = Trg::port(port); + /// ``` + Const { trg: TrgId, port: Port }, + /// ```rust,ignore + /// net.link_trg(a, b); + /// ``` + Link { a: TrgId, b: TrgId }, + /// ```rust,ignore + /// net.link_trg(trg, Trg::port(port)); + /// ``` + LinkConst { trg: TrgId, port: Port }, + /// See [`Net::do_ctr`]. + /// ```rust,ignore + /// let (lft, rgt) = net.do_ctr(lab, trg); + /// ``` + Ctr { lab: Lab, trg: TrgId, lft: TrgId, rgt: TrgId }, + /// See [`Net::do_op2`]. + /// ```rust,ignore + /// let (lft, rgt) = net.do_op2(lab, trg); + /// ``` + Op2 { op: Op, trg: TrgId, lft: TrgId, rgt: TrgId }, + /// See [`Net::do_op1`]. + /// ```rust,ignore + /// let rgt = net.do_op1(lab, num, trg); + /// ``` + Op1 { op: Op, num: u64, trg: TrgId, rgt: TrgId }, + /// See [`Net::do_mat`]. + /// ```rust,ignore + /// let (lft, rgt) = net.do_mat(trg); + /// ``` + Mat { trg: TrgId, lft: TrgId, rgt: TrgId }, + /// See [`Net::do_wires`]. + /// ```rust,ignore + /// let (av, aw, bv, bw) = net.do_wires(); + /// ``` + Wires { av: TrgId, aw: TrgId, bv: TrgId, bw: TrgId }, +} + +/// An index to a [`Trg`] in an [`Instruction`]. These essentially serve the +/// function of registers. +/// +/// When compiled, each `TrgId` will be compiled to a variable. +/// +/// When interpreted, the `TrgId` serves as an index into a vector. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TrgId { + /// Instead of storing the index directly, we store the byte offset, to save a + /// shift instruction when indexing into the `Trg` vector in interpreted mode. + /// + /// This is always `index * size_of::()`. + pub(super) byte_offset: usize, +} + +impl TrgId { + pub fn new(index: usize) -> Self { + TrgId { byte_offset: index * size_of::() } + } + pub fn index(&self) -> usize { + self.byte_offset / size_of::() + } +} + +impl fmt::Display for TrgId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "t{}", self.index()) + } +} + +impl fmt::Debug for TrgId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TrgId({})", self.index()) + } +} + +impl<'a, M: Mode> Net<'a, M> { + /// `trg ~ {#lab x y}` + #[inline(always)] + pub(crate) fn do_ctr(&mut self, lab: Lab, trg: Trg) -> (Trg, Trg) { + let port = trg.target(); + if !M::LAZY && port.tag() == Ctr && port.lab() == lab { + trace!(self.tracer, "fast"); + self.free_trg(trg); + let node = port.consume_node(); + self.rwts.anni += 1; + (Trg::wire(node.p1), Trg::wire(node.p2)) + // TODO: fast copy? + } else if false && !M::LAZY && port.tag() == Num || port.tag() == Ref && lab >= port.lab() { + self.rwts.comm += 1; + (Trg::port(port.clone()), Trg::port(port)) + } else { + let n = self.create_node(Ctr, lab); + self.link_trg_port(trg, n.p0); + (Trg::port(n.p1), Trg::port(n.p2)) + } + } + + /// `trg ~ ` + #[inline(always)] + pub(crate) fn do_op2(&mut self, op: Op, trg: Trg) -> (Trg, Trg) { + trace!(self.tracer, op, trg); + let port = trg.target(); + if !M::LAZY && port.tag() == Num { + self.rwts.oper += 1; + self.free_trg(trg); + let n = self.create_node(Op1, op as Lab); + n.p1.wire().set_target(Port::new_num(port.num())); + (Trg::port(n.p0), Trg::port(n.p2)) + } else if !M::LAZY && port == Port::ERA { + (Trg::port(Port::ERA), Trg::port(Port::ERA)) + } else { + let n = self.create_node(Op2, op as Lab); + self.link_trg_port(trg, n.p0); + (Trg::port(n.p1), Trg::port(n.p2)) + } + } + + /// `trg ~ ` + #[inline(always)] + pub(crate) fn do_op1(&mut self, op: Op, a: u64, trg: Trg) -> Trg { + let port = trg.target(); + if !M::LAZY && trg.target().tag() == Num { + self.rwts.oper += 1; + self.free_trg(trg); + Trg::port(Port::new_num(op.op(a, port.num()))) + } else if !M::LAZY && port == Port::ERA { + Trg::port(Port::ERA) + } else { + let n = self.create_node(Op1, op as Lab); + self.link_trg_port(trg, n.p0); + n.p1.wire().set_target(Port::new_num(a)); + Trg::port(n.p2) + } + } + + /// `trg ~ ?` + #[inline(always)] + pub(crate) fn do_mat(&mut self, trg: Trg) -> (Trg, Trg) { + let port = trg.target(); + if !M::LAZY && port.tag() == Num { + self.rwts.oper += 1; + self.free_trg(trg); + let num = port.num(); + let c1 = self.create_node(Ctr, 0); + if num == 0 { + self.link_port_port(c1.p2, Port::ERA); + (Trg::port(c1.p0), Trg::wire(self.create_wire_to(c1.p1))) + } else { + let c2 = self.create_node(Ctr, 0); + self.link_port_port(c1.p1, Port::ERA); + self.link_port_port(c1.p2, c2.p0); + self.link_port_port(c2.p1, Port::new_num(num - 1)); + (Trg::port(c1.p0), Trg::wire(self.create_wire_to(c2.p2))) + } + } else if !M::LAZY && port == Port::ERA { + self.rwts.eras += 1; + self.free_trg(trg); + (Trg::port(Port::ERA), Trg::port(Port::ERA)) + } else { + let m = self.create_node(Mat, 0); + self.link_trg_port(trg, m.p0); + (Trg::port(m.p1), Trg::port(m.p2)) + } + } + + #[inline(always)] + pub(crate) fn do_wires(&mut self) -> (Trg, Trg, Trg, Trg) { + let a = self.alloc(); + let b = a.other_half(); + ( + Trg::port(Port::new_var(a.clone())), + Trg::wire(Wire::new(a)), + Trg::port(Port::new_var(b.clone())), + Trg::wire(Wire::new(b)), + ) + } + + /// `trg ~ ` + #[inline(always)] + #[allow(unused)] // TODO: emit this instruction + pub(crate) fn do_op2_num(&mut self, op: Op, b: u64, trg: Trg) -> Trg { + let port = trg.target(); + if !M::LAZY && port.tag() == Num { + self.rwts.oper += 2; + self.free_trg(trg); + Trg::port(Port::new_num(op.op(port.num(), b))) + } else if !M::LAZY && port == Port::ERA { + Trg::port(Port::ERA) + } else { + let n = self.create_node(Op2, op as Lab); + self.link_trg_port(trg, n.p0); + n.p1.wire().set_target(Port::new_num(b)); + Trg::port(n.p2) + } + } + + /// `trg ~ ?<(x (y z)) out>` + #[inline(always)] + #[allow(unused)] // TODO: emit this instruction + pub(crate) fn do_mat_con_con(&mut self, trg: Trg, out: Trg) -> (Trg, Trg, Trg) { + let port = trg.target(); + if !M::LAZY && trg.target().tag() == Num { + self.rwts.oper += 1; + self.free_trg(trg); + let num = port.num(); + if num == 0 { + (out, Trg::port(Port::ERA), Trg::port(Port::ERA)) + } else { + (Trg::port(Port::ERA), Trg::port(Port::new_num(num - 1)), out) + } + } else if !M::LAZY && port == Port::ERA { + self.link_trg_port(out, Port::ERA); + (Trg::port(Port::ERA), Trg::port(Port::ERA), Trg::port(Port::ERA)) + } else { + let m = self.create_node(Mat, 0); + let c1 = self.create_node(Ctr, 0); + let c2 = self.create_node(Ctr, 0); + self.link_port_port(m.p1, c1.p0); + self.link_port_port(c1.p2, c2.p0); + self.link_trg_port(out, m.p2); + (Trg::port(c1.p1), Trg::port(c2.p1), Trg::port(c2.p2)) + } + } + + /// `trg ~ ?<(x y) out>` + #[inline(always)] + #[allow(unused)] // TODO: emit this instruction + pub(crate) fn do_mat_con(&mut self, trg: Trg, out: Trg) -> (Trg, Trg) { + let port = trg.target(); + if trg.target().tag() == Num { + self.rwts.oper += 1; + self.free_trg(trg); + let num = port.num(); + if num == 0 { + (out, Trg::port(Port::ERA)) + } else { + let c2 = self.create_node(Ctr, 0); + c2.p1.wire().set_target(Port::new_num(num - 1)); + self.link_trg_port(out, c2.p2); + (Trg::port(Port::ERA), Trg::port(c2.p0)) + } + } else if port == Port::ERA { + self.link_trg_port(out, Port::ERA); + (Trg::port(Port::ERA), Trg::port(Port::ERA)) + } else { + let m = self.create_node(Mat, 0); + let c1 = self.create_node(Ctr, 0); + self.link_port_port(m.p1, c1.p0); + self.link_trg_port(out, m.p2); + self.link_trg_port(trg, m.p0); + (Trg::port(c1.p1), Trg::port(c1.p2)) + } + } +} diff --git a/src/run/interact.rs b/src/run/interact.rs new file mode 100644 index 00000000..5e69c42b --- /dev/null +++ b/src/run/interact.rs @@ -0,0 +1,445 @@ +use super::*; + +impl<'a, M: Mode> Net<'a, M> { + /// Performs an interaction between two connected principal ports. + #[inline(always)] + pub fn interact(&mut self, a: Port, b: Port) { + self.tracer.sync(); + trace!(self.tracer, a, b); + match (a.tag(), b.tag()) { + // not actually an active pair + (Var | Red, _) | (_, Var | Red) => unreachable!(), + // nil-nil + (Ref, Ref | Num) if !a.is_skippable() => self.call(a, b), + (Ref | Num, Ref) if !b.is_skippable() => self.call(b, a), + (Num | Ref, Num | Ref) => self.rwts.eras += 1, + // comm 2/2 + (Ctr, Mat) if a.lab() != 0 => self.comm22(a, b), + (Mat, Ctr) if b.lab() != 0 => self.comm22(a, b), + (Ctr, Op2) | (Op2, Ctr) => self.comm22(a, b), + (Ctr, Ctr) if a.lab() != b.lab() => self.comm22(a, b), + // comm 1/2 + (Op1, Ctr) => self.comm12(a, b), + (Ctr, Op1) => self.comm12(b, a), + // anni + (Mat, Mat) | (Op2, Op2) | (Ctr, Ctr) => self.anni2(a, b), + (Op1, Op1) => self.anni1(a, b), + // comm 2/0 + (Ref, Ctr) if b.lab() >= a.lab() => self.comm02(a, b), + (Ctr, Ref) if a.lab() >= b.lab() => self.comm02(b, a), + (Num, Ctr) => self.comm02(a, b), + (Ctr, Num) => self.comm02(b, a), + (Ref, _) if a == Port::ERA => self.comm02(a, b), + (_, Ref) if b == Port::ERA => self.comm02(b, a), + // deref + (Ref, _) => self.call(a, b), + (_, Ref) => self.call(b, a), + // native ops + (Op2, Num) => self.op2_num(a, b), + (Num, Op2) => self.op2_num(b, a), + (Op1, Num) => self.op1_num(a, b), + (Num, Op1) => self.op1_num(b, a), + (Mat, Num) => self.mat_num(a, b), + (Num, Mat) => self.mat_num(b, a), + // todo: what should the semantics of these be? + (Mat, Ctr) // b.lab() == 0 + | (Ctr, Mat) // a.lab() == 0 + | (Op2, Op1) + | (Op1, Op2) + | (Op2, Mat) + | (Mat, Op2) + | (Op1, Mat) + | (Mat, Op1) => unimplemented!("{:?}-{:?}", a.tag(), b.tag()), + } + } + + /// Annihilates two binary agents. + /// + /// ```text + /// + /// a2 | | a1 + /// _|___|_ + /// \ / + /// a \ / + /// \ / + /// | + /// / \ + /// b / \ + /// /_____\ + /// | | + /// b1 | | b2 + /// + /// --------------------------- anni2 + /// + /// a2 | | a1 + /// | | + /// \ / + /// X + /// / \ + /// | | + /// b1 | | b2 + /// + /// ``` + #[inline(never)] + pub fn anni2(&mut self, a: Port, b: Port) { + trace!(self.tracer, a, b); + self.rwts.anni += 1; + let a = a.consume_node(); + let b = b.consume_node(); + self.link_wire_wire(a.p1, b.p1); + self.link_wire_wire(a.p2, b.p2); + } + + /// Commutes two binary agents. + /// + /// ```text + /// + /// a2 | | a1 + /// _|___|_ + /// \ / + /// a \ / + /// \ / + /// | + /// /#\ + /// b /###\ + /// /#####\ + /// | | + /// b1 | | b2 + /// + /// --------------------------- comm22 + /// + /// a2 | | a1 + /// | | + /// /#\ /#\ + /// B2 /###\ /###\ B1 + /// /#####\ /#####\ + /// | \ / | + /// p1 | p2 \ / p1 | p2 + /// | \ / | + /// | X | + /// | / \ | + /// p2 | p1 / \ p2 | p1 + /// _|___/_ _\___|_ + /// \ / \ / + /// A1 \ / \ / A2 + /// \ / \ / + /// | | + /// b1 | | b2 + /// + /// ``` + #[inline(never)] + pub fn comm22(&mut self, a: Port, b: Port) { + trace!(self.tracer, a, b); + self.rwts.comm += 1; + + let a = a.consume_node(); + let b = b.consume_node(); + + let A1 = self.create_node(a.tag, a.lab); + let A2 = self.create_node(a.tag, a.lab); + let B1 = self.create_node(b.tag, b.lab); + let B2 = self.create_node(b.tag, b.lab); + + trace!(self.tracer, A1.p0, A2.p0, B1.p0, B2.p0); + self.link_port_port(A1.p1, B1.p1); + self.link_port_port(A1.p2, B2.p1); + self.link_port_port(A2.p1, B1.p2); + self.link_port_port(A2.p2, B2.p2); + + trace!(self.tracer); + self.link_wire_port(a.p1, B1.p0); + self.link_wire_port(a.p2, B2.p0); + self.link_wire_port(b.p1, A1.p0); + self.link_wire_port(b.p2, A2.p0); + } + + /// Commutes a nilary agent and a binary agent. + /// + /// ```text + /// + /// a (---) + /// | + /// | + /// /#\ + /// b /###\ + /// /#####\ + /// | | + /// b1 | | b2 + /// + /// --------------------------- comm02 + /// + /// a (---) (---) a + /// | | + /// b1 | | b2 + /// + /// ``` + #[inline(never)] + pub fn comm02(&mut self, a: Port, b: Port) { + trace!(self.tracer, a, b); + self.rwts.comm += 1; + let b = b.consume_node(); + self.link_wire_port(b.p1, a.clone()); + self.link_wire_port(b.p2, a); + } + + /// Annihilates two unary agents. + /// + /// ```text + /// + /// a2 | + /// | n + /// _|___|_ + /// \ / + /// a \op1/ + /// \ / + /// | + /// / \ + /// b /op1\ + /// /_____\ + /// | | + /// m | + /// | b2 + /// + /// --------------------------- anni1 + /// + /// a2 | + /// | + /// | + /// \ + /// \ + /// \ + /// | + /// | + /// | b2 + /// + /// ``` + #[inline(never)] + pub fn anni1(&mut self, a: Port, b: Port) { + trace!(self.tracer, a, b); + self.rwts.anni += 1; + let a = a.consume_op1(); + let b = b.consume_op1(); + self.link_wire_wire(a.p2, b.p2); + } + + /// Commutes a unary agent and a unary agent. + /// + /// ```text + /// + /// a2 | n + /// _|___|_ + /// \ / + /// a \op1/ + /// \ / + /// | + /// /#\ + /// b /###\ + /// /#####\ + /// | | + /// b1 | | b2 + /// + /// --------------------------- comm12 + /// + /// a2 | + /// | + /// /#\ + /// B2 /###\ + /// /#####\ + /// | \ + /// p1 | p2 \ + /// | \ + /// | \ + /// | \ + /// p2 | n \ p2 n + /// _|___|_ _\___|_ + /// \ / \ / + /// A1 \op1/ \op1/ A2 + /// \ / \ / + /// | | + /// b1 | | b2 + /// + /// ``` + #[inline(never)] + pub fn comm12(&mut self, a: Port, b: Port) { + trace!(self.tracer, a, b); + self.rwts.comm += 1; + + let a = a.consume_op1(); + let b = b.consume_node(); + + let A1 = self.create_node(Op1, a.op as Lab); + let A2 = self.create_node(Op1, a.op as Lab); + let B2 = self.create_node(b.tag, b.lab); + + trace!(self.tracer, B2.p0, A1.p0, A2.p0); + self.link_port_port(A1.p1, a.num.clone()); + self.link_port_port(A1.p2, B2.p1); + self.link_port_port(A2.p1, a.num.clone()); + self.link_port_port(A2.p2, B2.p2); + + trace!(self.tracer); + self.link_wire_port(a.p2, B2.p0); + self.link_wire_port(b.p1, A1.p0); + self.link_wire_port(b.p2, A2.p0); + } + + /// Commutes a nilary agent and a unary agent. + /// + /// ```text + /// + /// a (---) + /// | + /// | + /// / \ + /// b /op1\ + /// /_____\ + /// | | + /// n | + /// | b2 + /// + /// --------------------------- comm02 + /// + /// (---) a + /// | + /// | b2 + /// + /// ``` + #[inline(never)] + pub fn comm01(&mut self, a: Port, b: Port) { + trace!(self.tracer, a, b); + self.rwts.comm += 1; + let b = b.consume_op1(); + self.link_wire_port(b.p2, a); + } + + /// Interacts a number and a numeric match node. + /// + /// ```text + /// | + /// b (0) | b (n+1) + /// | | | + /// | | | + /// / \ | / \ + /// a /mat\ | a /mat\ + /// /_____\ | /_____\ + /// | | | | | + /// a1 | | a2 | a1 | | a2 + /// | + /// --------------------------- | --------------------------- mat_num + /// | _ _ _ _ _ + /// | / \ + /// | y2 | (n) y1 | + /// | _|___|_ | + /// | \ / | + /// _ | y \ / | + /// / \ | \ / | + /// x2 (*) | x1 | | x2 | (*) x1 | + /// _|___|_ | | _|___|_ | + /// \ / | | \ / | + /// x \ / | | x \ / | + /// \ / | | \ / | + /// | | | | | + /// a1 | | a2 | a1 | | a2 + /// | + /// ``` + #[inline(never)] + pub fn mat_num(&mut self, a: Port, b: Port) { + trace!(self.tracer, a, b); + self.rwts.oper += 1; + let a = a.consume_node(); + let b = b.num(); + if b == 0 { + let x = self.create_node(Ctr, 0); + trace!(self.tracer, x.p0); + self.link_port_port(x.p2, Port::ERA); + self.link_wire_port(a.p2, x.p1); + self.link_wire_port(a.p1, x.p0); + } else { + let x = self.create_node(Ctr, 0); + let y = self.create_node(Ctr, 0); + trace!(self.tracer, x.p0, y.p0); + self.link_port_port(x.p1, Port::ERA); + self.link_port_port(x.p2, y.p0); + self.link_port_port(y.p1, Port::new_num(b - 1)); + self.link_wire_port(a.p2, y.p2); + self.link_wire_port(a.p1, x.p0); + } + } + + /// Interacts a number and a binary numeric operation node. + /// + /// ```text + /// + /// b (n) + /// | + /// | + /// / \ + /// a /op2\ + /// /_____\ + /// | | + /// a1 | | a2 + /// + /// --------------------------- op2_num + /// _ _ _ + /// / \ + /// | n | + /// _|___|_ | + /// \ / | + /// x \op1/ | + /// \ / | + /// | | + /// a1 | | a2 + /// + /// ``` + #[inline(never)] + pub fn op2_num(&mut self, a: Port, b: Port) { + trace!(self.tracer, a, b); + self.rwts.oper += 1; + let a = a.consume_node(); + let a1 = a.p1.load_target(); + if a1.tag() == Num { + // skip creating the Op1 node if it will instantly interact + self.rwts.oper += 1; + let out = unsafe { Op::from_unchecked(a.lab) }.op(b.num(), a1.num()); + self.link_wire_port(a.p2, Port::new_num(out)); + } else { + let x = self.create_node(Op1, a.lab); + trace!(self.tracer, x.p0); + self.link_port_port(x.p1, b); + self.link_wire_port(a.p2, x.p2); + self.link_wire_port(a.p1, x.p0); + } + } + + /// Interacts a number and a unary numeric operation node. + /// + /// ```text + /// + /// b (m) + /// | + /// | + /// / \ + /// a /op1\ + /// /_____\ + /// | | + /// n | + /// | a2 + /// + /// --------------------------- op1_num + /// + /// (n opr m) + /// | + /// | a2 + /// + /// ``` + #[inline(never)] + pub fn op1_num(&mut self, a: Port, b: Port) { + trace!(self.tracer, a, b); + self.rwts.oper += 1; + let a = a.consume_op1(); + let n = a.num.num(); + let m = b.num(); + let out = a.op.op(n, m); + self.link_wire_port(a.p2, Port::new_num(out)); + } +} diff --git a/src/run/linker.rs b/src/run/linker.rs new file mode 100644 index 00000000..06f0c571 --- /dev/null +++ b/src/run/linker.rs @@ -0,0 +1,341 @@ +use super::*; + +/// Stores extra data needed about the nodes when in lazy mode. (In strict mode, +/// this is unused.) +pub(super) struct Header { + /// the principal port of this node + pub(super) this: Port, + /// the port connected to the principal port of this node + pub(super) targ: Port, +} + +pub struct Linker<'h, M: Mode> { + pub(super) allocator: Allocator<'h>, + pub redexes: Vec<(Port, Port)>, + pub rwts: Rewrites, + headers: IntMap, + _mode: PhantomData, +} + +deref!({<'h, M: Mode>} Linker<'h, M> => self.allocator: Allocator<'h>); + +/// When threads interfere, this uses the atomic linking algorithm described in +/// `paper/`. +/// +/// Linking wires must be done atomically, but linking ports can be done +/// non-atomically (because they must be locked). +impl<'h, M: Mode> Linker<'h, M> { + pub fn new(heap: &'h Heap) -> Self { + Linker { + allocator: Allocator::new(heap), + redexes: Vec::new(), + rwts: Default::default(), + headers: Default::default(), + _mode: PhantomData, + } + } + + /// Links two ports. + #[inline(always)] + pub fn link_port_port(&mut self, a_port: Port, b_port: Port) { + trace!(self, a_port, b_port); + if a_port.is_principal() && b_port.is_principal() { + self.redux(a_port, b_port); + } else { + self.half_link_port_port(a_port.clone(), b_port.clone()); + self.half_link_port_port(b_port, a_port); + } + } + + /// Links two wires. + #[inline(always)] + pub fn link_wire_wire(&mut self, a_wire: Wire, b_wire: Wire) { + trace!(self, a_wire, b_wire); + let a_port = a_wire.lock_target(); + let b_port = b_wire.lock_target(); + trace!(self, a_port, b_port); + if a_port.is_principal() && b_port.is_principal() { + self.free_wire(a_wire); + self.free_wire(b_wire); + self.redux(a_port, b_port); + } else { + self.half_link_wire_port(a_port.clone(), a_wire, b_port.clone()); + self.half_link_wire_port(b_port, b_wire, a_port); + } + } + + /// Links a wire to a port. + #[inline(always)] + pub fn link_wire_port(&mut self, a_wire: Wire, b_port: Port) { + trace!(self, a_wire, b_port); + let a_port = a_wire.lock_target(); + trace!(self, a_port); + if a_port.is_principal() && b_port.is_principal() { + self.free_wire(a_wire); + self.redux(a_port, b_port); + } else { + self.half_link_wire_port(a_port.clone(), a_wire, b_port.clone()); + self.half_link_port_port(b_port, a_port); + } + } + + /// Pushes an active pair to the redex queue; `a` and `b` must both be + /// principal ports. + #[inline(always)] + fn redux(&mut self, a: Port, b: Port) { + trace!(self, a, b); + if a.is_skippable() && b.is_skippable() { + self.rwts.eras += 1; + } else if !M::LAZY { + self.redexes.push((a, b)); + } else { + self.set_header(a.clone(), b.clone()); + self.set_header(b.clone(), a.clone()); + } + } + + /// Half-links `a_port` to `b_port`, without linking `b_port` back to + /// `a_port`. + #[inline(always)] + fn half_link_port_port(&mut self, a_port: Port, b_port: Port) { + trace!(self, a_port, b_port); + if a_port.is(Tag::Var) { + a_port.wire().set_target(b_port); + } else { + if M::LAZY { + self.set_header(a_port, b_port); + } + } + } + + /// Half-links a foreign `a_port` (taken from `a_wire`) to `b_port`, without + /// linking `b_port` back to `a_port`. + #[inline(always)] + fn half_link_wire_port(&mut self, a_port: Port, a_wire: Wire, b_port: Port) { + trace!(self, a_port, a_wire, b_port); + // If 'a_port' is a var... + if a_port.is(Tag::Var) { + let got = a_port.wire().cas_target(a_wire.as_var(), b_port.clone()); + // Attempts to link using a compare-and-swap. + if got.is_ok() { + trace!(self, "cas ok"); + self.free_wire(a_wire); + // If the CAS failed, resolve by using redirections. + } else { + let got = got.unwrap_err(); + trace!(self, "cas fail", got); + if b_port.is(Tag::Var) { + let port = b_port.redirect(); + a_wire.set_target(port); + //self.resolve_redirect_var(a_port, a_wire, b_port); + } else if b_port.is_principal() { + a_wire.set_target(b_port.clone()); + self.resolve_redirect_pri(a_port, a_wire, b_port); + } else { + unreachable!(); + } + } + } else { + self.free_wire(a_wire); + if M::LAZY { + self.set_header(a_port, b_port); + } + } + } + + /// Resolves redirects when 'b_port' is a principal port. + fn resolve_redirect_pri(&mut self, mut a_port: Port, a_wire: Wire, b_port: Port) { + trace!(self); + loop { + trace!(self, a_port, a_wire, b_port); + // Peek the target, which may not be owned by us. + let mut t_wire = a_port.wire(); + let mut t_port = t_wire.load_target(); + trace!(self, t_port); + // If it is taken, we wait. + if t_port == Port::LOCK { + spin_loop(); + continue; + } + // If target is a redirection, we own it. Clear and move forward. + if t_port.is(Tag::Red) { + self.free_wire(t_wire); + a_port = t_port; + continue; + } + // If target is a variable, we don't own it. Try replacing it. + if t_port.is(Tag::Var) { + if t_wire.cas_target(t_port.clone(), b_port.clone()).is_ok() { + trace!(self, "var cas ok"); + // Clear source location. + // self.half_free(a_wire.addr()); + // Collect the orphaned backward path. + t_wire = t_port.wire(); + t_port = t_wire.load_target(); + while t_port != Port::LOCK && t_port.is(Tag::Red) { + trace!(self, t_wire, t_port); + self.free_wire(t_wire); + t_wire = t_port.wire(); + // if t_wire == a_wire { + // break; + // } + t_port = t_wire.load_target(); + } + return; + } + trace!(self, "var cas fail"); + // If the CAS failed, the var changed, so we try again. + continue; + } + + // If it is a node, two threads will reach this branch. + if t_port.is_principal() || t_port == Port::GONE { + // Sort references, to avoid deadlocks. + let x_wire = if a_wire < t_wire { a_wire.clone() } else { t_wire.clone() }; + let y_wire = if a_wire < t_wire { t_wire.clone() } else { a_wire.clone() }; + trace!(self, x_wire, y_wire); + // Swap first reference by Ptr::GONE placeholder. + let x_port = x_wire.swap_target(Port::GONE); + // First to arrive creates a redex. + if x_port != Port::GONE { + let y_port = y_wire.swap_target(Port::GONE); + trace!(self, "fst", x_wire, y_wire, x_port, y_port); + self.redux(x_port, y_port); + return; + // Second to arrive clears up the memory. + } else { + trace!(self, "snd !!!", x_wire, y_wire); + self.free_wire(x_wire); + while y_wire.cas_target(Port::GONE, Port::LOCK).is_err() { + spin_loop(); + } + self.free_wire(y_wire); + return; + } + } + // Shouldn't be reached. + trace!(self, t_port, a_wire, a_port, b_port); + unreachable!() + } + } + + /// Resolves redirects when 'b_port' is an aux port. + // TODO: this is currently broken + #[allow(unused)] + fn resolve_redirect_var(&mut self, _: Port, _: Wire, b_port: Port) { + loop { + let ste_wire = b_port.clone().wire(); + let ste_port = ste_wire.load_target(); + if ste_port.is(Tag::Var) { + let trg_wire = ste_port.wire(); + let trg_port = trg_wire.load_target(); + if trg_port.is(Tag::Red) { + let neo_port = trg_port.unredirect(); + if ste_wire.cas_target(ste_port, neo_port).is_ok() { + self.free_wire(trg_wire); + continue; + } + } + } + break; + } + } +} + +/// Part of the net to link to; either a wire or a port. +/// +/// To store this compactly, we reuse the [`Red`] tag to indicate if this is a +/// wire. +#[derive(Clone)] +pub struct Trg(pub(crate) Port); + +impl fmt::Debug for Trg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_wire() { self.clone().as_wire().fmt(f) } else { self.0.fmt(f) } + } +} + +impl Trg { + /// Creates a `Trg` from a port. + #[inline(always)] + pub fn port(port: Port) -> Self { + Trg(port) + } + + /// Creates a `Trg` from a wire. + #[inline(always)] + pub fn wire(wire: Wire) -> Self { + Trg(Port(wire.0 as u64)) + } + + #[inline(always)] + pub(super) fn is_wire(&self) -> bool { + self.0.is(Tag::Red) + } + + #[inline(always)] + pub(super) fn as_wire(self) -> Wire { + Wire(self.0.0 as _) + } + + #[inline(always)] + pub(super) fn as_port(self) -> Port { + self.0 + } + + /// Access the target port; if this trg is already a port, this does nothing, + /// but if it is a wire, it loads the target. + /// + /// The returned port is only normative if it is principal; if it is a var or + /// a redirect, it must not be used for linking, and the original `Trg` should + /// be used instead. + #[inline(always)] + pub fn target(&self) -> Port { + if self.is_wire() { self.clone().as_wire().load_target() } else { self.0.clone() } + } +} + +impl<'h, M: Mode> Linker<'h, M> { + /// Links a `Trg` to a port, delegating to the appropriate method based on the + /// type of `a`. + #[inline(always)] + pub fn link_trg_port(&mut self, a: Trg, b: Port) { + match a.is_wire() { + true => self.link_wire_port(a.as_wire(), b), + false => self.link_port_port(a.as_port(), b), + } + } + + /// Links two `Trg`s, delegating to the appropriate method based on the types + /// of `a` and `b`. + #[inline(always)] + pub fn link_trg(&mut self, a: Trg, b: Trg) { + match (a.is_wire(), b.is_wire()) { + (true, true) => self.link_wire_wire(a.as_wire(), b.as_wire()), + (true, false) => self.link_wire_port(a.as_wire(), b.as_port()), + (false, true) => self.link_wire_port(b.as_wire(), a.as_port()), + (false, false) => self.link_port_port(a.as_port(), b.as_port()), + } + } + + pub(super) fn get_header(&self, addr: Addr) -> &Header { + assert!(M::LAZY); + &self.headers[&addr] + } + + pub(super) fn set_header(&mut self, ptr: Port, trg: Port) { + assert!(M::LAZY); + trace!(self, ptr, trg); + if ptr.is_full_node() { + self.headers.insert(ptr.addr(), Header { this: ptr, targ: trg }); + } + } + + pub(super) fn get_target_full(&self, port: Port) -> Port { + assert!(M::LAZY); + if !port.is_principal() { + return port.wire().load_target(); + } + self.headers[&port.addr()].targ.clone() + } +} diff --git a/src/run/net.rs b/src/run/net.rs new file mode 100644 index 00000000..36a2e690 --- /dev/null +++ b/src/run/net.rs @@ -0,0 +1,153 @@ +use super::*; + +/// An interaction combinator net. +pub struct Net<'a, M: Mode> { + linker: Linker<'a, M>, + pub tid: usize, // thread id + pub tids: usize, // thread count + pub trgs: Vec, + pub root: Wire, +} + +deref!({<'a, M: Mode>} Net<'a, M> => self.linker: Linker<'a, M>); + +impl<'h, M: Mode> Net<'h, M> { + /// Creates an empty net with a given heap. + pub fn new(heap: &'h Heap) -> Self { + let mut net = Net::new_with_root(heap, Wire(std::ptr::null())); + net.root = Wire::new(net.alloc()); + net + } + + pub(super) fn new_with_root(heap: &'h Heap, root: Wire) -> Self { + Net { linker: Linker::new(heap), tid: 0, tids: 1, trgs: vec![Trg::port(Port(0)); 1 << 16], root } + } + + /// Boots a net from a Ref. + pub fn boot(&mut self, def: &Def) { + let def = Port::new_ref(def); + trace!(self, def); + self.root.set_target(def); + } +} + +impl<'a, M: Mode> Net<'a, M> { + /// Reduces at most `limit` redexes. + #[inline(always)] + pub fn reduce(&mut self, limit: usize) -> usize { + assert!(!M::LAZY); + let mut count = 0; + while let Some((a, b)) = self.redexes.pop() { + self.interact(a, b); + count += 1; + if count >= limit { + break; + } + } + count + } + + /// Expands [`Ref`] nodes in the tree connected to `root`. + #[inline(always)] + pub fn expand(&mut self) { + assert!(!M::LAZY); + fn go(net: &mut Net, wire: Wire, len: usize, key: usize) { + trace!(net.tracer, wire); + let port = wire.load_target(); + trace!(net.tracer, port); + if port == Port::LOCK { + return; + } + if port.tag() == Ctr { + let node = port.traverse_node(); + if len >= net.tids || key % 2 == 0 { + go(net, node.p1, len.saturating_mul(2), key / 2); + } + if len >= net.tids || key % 2 == 1 { + go(net, node.p2, len.saturating_mul(2), key / 2); + } + } else if port.tag() == Ref && port != Port::ERA { + let got = wire.swap_target(Port::LOCK); + if got != Port::LOCK { + trace!(net.tracer, port, wire); + net.call(port, Port::new_var(wire.addr())); + } + } + } + go(self, self.root.clone(), 1, self.tid); + } + + // Lazy mode weak head normalizer + #[inline(always)] + fn weak_normal(&mut self, mut prev: Port, root: Wire) -> Port { + assert!(M::LAZY); + + let mut path: Vec = vec![]; + + loop { + trace!(self.tracer, prev); + // Load ptrs + let next = self.get_target_full(prev.clone()); + trace!(self.tracer, next); + + // If next is root, stop. + if next == Port::new_var(root.addr()) || next == Port::new_var(self.root.addr()) { + break; + } + + // If next is a main port... + if next.is_principal() { + // If prev is a main port, reduce the active pair. + if prev.is_principal() { + self.interact(next, prev.clone()); + prev = path.pop().unwrap(); + continue; + // Otherwise, if it is a ref, expand it. + } else if next.tag() == Ref && next != Port::ERA { + self.call(next, prev.clone()); + continue; + // Otherwise, we're done. + } else { + break; + } + } + + // If next is an aux port, pass through. + let main = self.get_header(next.addr().left_half()); + path.push(prev); + prev = main.this.clone(); + } + + return self.get_target_full(prev); + } + + pub fn normal_from(&mut self, root: Wire) { + assert!(M::LAZY); + let mut visit = vec![Port::new_var(root.addr())]; + while let Some(prev) = visit.pop() { + trace!(self.tracer, "visit", prev); + //println!("normal {} | {}", prev.view(), self.rewrites()); + let next = self.weak_normal(prev, root.clone()); + trace!(self.tracer, "got", next); + if next.is_full_node() { + if next.tag() != Op1 { + visit.push(Port::new_var(next.addr())); + } + visit.push(Port::new_var(next.addr().other_half())); + } + } + } + + /// Reduces a net to normal form. + pub fn normal(&mut self) { + if M::LAZY { + self.normal_from(self.root.clone()); + } else { + self.expand(); + while !self.redexes.is_empty() { + self.reduce(usize::MAX); + self.expand(); + } + } + } +} diff --git a/src/run/node.rs b/src/run/node.rs new file mode 100644 index 00000000..25094d54 --- /dev/null +++ b/src/run/node.rs @@ -0,0 +1,86 @@ +use super::*; + +/// See [`Port::traverse_node`]. +pub struct TraverseNode { + pub tag: Tag, + pub lab: Lab, + pub p1: Wire, + pub p2: Wire, +} + +/// See [`Port::traverse_op1`]. +pub struct TraverseOp1 { + pub op: Op, + pub num: Port, + pub p2: Wire, +} + +impl Port { + #[inline(always)] + pub fn consume_node(self) -> TraverseNode { + self.traverse_node() + } + + #[inline(always)] + pub fn traverse_node(self) -> TraverseNode { + TraverseNode { + tag: self.tag(), + lab: self.lab(), + p1: Wire::new(self.addr()), + p2: Wire::new(self.addr().other_half()), + } + } + + #[inline(always)] + pub fn consume_op1(self) -> TraverseOp1 { + let op = self.op(); + let s = self.consume_node(); + let num = s.p1.swap_target(Port::FREE); + TraverseOp1 { op, num, p2: s.p2 } + } + + #[inline(always)] + pub fn traverse_op1(self) -> TraverseOp1 { + let op = self.op(); + let s = self.traverse_node(); + let num = s.p1.load_target(); + TraverseOp1 { op, num, p2: s.p2 } + } +} + +pub struct CreatedNode { + pub p0: Port, + pub p1: Port, + pub p2: Port, +} + +impl<'a, M: Mode> Net<'a, M> { + #[inline(always)] + pub fn create_node(&mut self, tag: Tag, lab: Lab) -> CreatedNode { + let addr = self.alloc(); + CreatedNode { + p0: Port::new(tag, lab, addr.clone()), + p1: Port::new_var(addr.clone()), + p2: Port::new_var(addr.other_half()), + } + } + + /// Creates a wire an aux port pair. + #[inline(always)] + pub fn create_wire(&mut self) -> (Wire, Port) { + let addr = self.alloc(); + self.half_free(addr.other_half()); + (Wire::new(addr.clone()), Port::new_var(addr)) + } + + /// Creates a wire pointing to a given port; sometimes necessary to avoid + /// deadlock. + #[inline(always)] + pub fn create_wire_to(&mut self, port: Port) -> Wire { + let addr = self.alloc(); + self.half_free(addr.other_half()); + let wire = Wire::new(addr); + self.link_port_port(port, Port::new_var(wire.addr())); + wire + } +} diff --git a/src/run/parallel.rs b/src/run/parallel.rs new file mode 100644 index 00000000..9207a0eb --- /dev/null +++ b/src/run/parallel.rs @@ -0,0 +1,153 @@ +use super::*; + +impl<'h, M: Mode> Net<'h, M> { + /// Forks the net into `tids` child nets, for parallel operation. + pub fn fork(&mut self, tids: usize) -> impl Iterator + '_ { + let mut redexes = std::mem::take(&mut self.redexes).into_iter(); + (0 .. tids).map(move |tid| { + let heap_size = (self.heap.0.len() / tids) & !63; // round down to needed alignment + let heap_start = heap_size * tid; + let area = unsafe { std::mem::transmute(&self.heap.0[heap_start .. heap_start + heap_size]) }; + let mut net = Net::new_with_root(area, self.root.clone()); + net.next = self.next.saturating_sub(heap_start); + net.head = if tid == 0 { net.head } else { Addr::NULL }; + net.tid = tid; + net.tids = tids; + net.tracer.set_tid(tid); + let count = redexes.len() / (tids - tid); + net.redexes.extend((&mut redexes).take(count)); + net + }) + } + + // Evaluates a term to normal form in parallel + pub fn parallel_normal(&mut self) { + assert!(!M::LAZY); + + const SHARE_LIMIT: usize = 1 << 12; // max share redexes per split + const LOCAL_LIMIT: usize = 1 << 18; // max local rewrites per epoch + + // Local thread context + struct ThreadContext<'a, M: Mode> { + tid: usize, // thread id + tlog2: usize, // log2 of thread count + tick: usize, // current tick + net: Net<'a, M>, // thread's own net object + delta: &'a AtomicRewrites, // global delta rewrites + share: &'a Vec<(AtomicU64, AtomicU64)>, // global share buffer + rlens: &'a Vec, // global redex lengths + total: &'a AtomicUsize, // total redex length + barry: Arc, // synchronization barrier + } + + // Initialize global objects + let cores = std::thread::available_parallelism().unwrap().get() as usize; + let tlog2 = cores.ilog2() as usize; + let tids = 1 << tlog2; + let delta = AtomicRewrites::default(); // delta rewrite counter + let rlens = (0 .. tids).map(|_| AtomicUsize::new(0)).collect::>(); + let share = (0 .. SHARE_LIMIT * tids).map(|_| Default::default()).collect::>(); + let total = AtomicUsize::new(0); // sum of redex bag length + let barry = Arc::new(Barrier::new(tids)); // global barrier + + // Perform parallel reductions + std::thread::scope(|s| { + for net in self.fork(tids) { + let mut ctx = ThreadContext { + tid: net.tid, + tick: 0, + net, + tlog2, + delta: &delta, + share: &share, + rlens: &rlens, + total: &total, + barry: Arc::clone(&barry), + }; + thread::Builder::new().name(format!("t{:02x?}", ctx.net.tid)).spawn_scoped(s, move || main(&mut ctx)).unwrap(); + } + }); + + // Clear redexes and sum stats + self.redexes.clear(); + delta.add_to(&mut self.rwts); + + // Main reduction loop + #[inline(always)] + fn main(ctx: &mut ThreadContext) { + loop { + reduce(ctx); + ctx.net.expand(); + if count(ctx) == 0 { + break; + } + } + ctx.net.rwts.add_to(ctx.delta); + } + + // Reduce redexes locally, then share with target + #[inline(always)] + fn reduce(ctx: &mut ThreadContext) { + loop { + ctx.net.reduce(LOCAL_LIMIT); + if count(ctx) == 0 { + break; + } + let tlog2 = ctx.tlog2; + split(ctx, tlog2); + ctx.tick += 1; + } + } + + // Count total redexes (and populate 'rlens') + #[inline(always)] + fn count(ctx: &mut ThreadContext) -> usize { + ctx.barry.wait(); + ctx.total.store(0, Relaxed); + ctx.barry.wait(); + ctx.rlens[ctx.tid].store(ctx.net.redexes.len(), Relaxed); + ctx.total.fetch_add(ctx.net.redexes.len(), Relaxed); + ctx.barry.wait(); + ctx.total.load(Relaxed) + } + + // Share redexes with target thread + #[inline(always)] + fn split(ctx: &mut ThreadContext, plog2: usize) { + unsafe { + let side = (ctx.tid >> (plog2 - 1 - (ctx.tick % plog2))) & 1; + let shift = (1 << (plog2 - 1)) >> (ctx.tick % plog2); + let a_tid = ctx.tid; + let b_tid = if side == 1 { a_tid - shift } else { a_tid + shift }; + let a_len = ctx.net.redexes.len(); + let b_len = ctx.rlens[b_tid].load(Relaxed); + let send = if a_len > b_len { (a_len - b_len) / 2 } else { 0 }; + let recv = if b_len > a_len { (b_len - a_len) / 2 } else { 0 }; + let send = std::cmp::min(send, SHARE_LIMIT); + let recv = std::cmp::min(recv, SHARE_LIMIT); + for i in 0 .. send { + let init = a_len - send * 2; + let rdx0 = ctx.net.redexes.get_unchecked(init + i * 2 + 0).clone(); + let rdx1 = ctx.net.redexes.get_unchecked(init + i * 2 + 1).clone(); + //let init = 0; + //let ref0 = ctx.net.redexes.get_unchecked_mut(init + i * 2 + 0); + //let rdx0 = *ref0; + //*ref0 = (Ptr(0), Ptr(0)); + //let ref1 = ctx.net.redexes.get_unchecked_mut(init + i * 2 + 1); + //let rdx1 = *ref1; + //*ref1 = (Ptr(0), Ptr(0)); + let targ = ctx.share.get_unchecked(b_tid * SHARE_LIMIT + i); + *ctx.net.redexes.get_unchecked_mut(init + i) = rdx0; + targ.0.store(rdx1.0.0, Relaxed); + targ.1.store(rdx1.1.0, Relaxed); + } + ctx.net.redexes.truncate(a_len - send); + ctx.barry.wait(); + for i in 0 .. recv { + let got = ctx.share.get_unchecked(a_tid * SHARE_LIMIT + i); + ctx.net.redexes.push((Port(got.0.load(Relaxed)), Port(got.1.load(Relaxed)))); + } + } + } + } +} diff --git a/src/run/port.rs b/src/run/port.rs new file mode 100644 index 00000000..d0611a8d --- /dev/null +++ b/src/run/port.rs @@ -0,0 +1,224 @@ +use super::*; + +/// A port in the interaction net. +/// +/// The type of a port is determined by its *tag*, which is stored in the bottom +/// three bits. +/// +/// All tags other than [`Num`] divide the bits of the port as follows: +/// - the top 16 bits are the *label*, accessible with [`Port::lab`] +/// - the middle 45 bits are the non-alignment bits of the *address*, an +/// 8-byte-aligned pointer accessible with [`Port::addr`] +/// - the bottom 3 bits are the tag, as always +/// +/// The semantics of these fields depend upon the tag; see the documentation for +/// each [`Tag`] variant. +#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Default)] +#[repr(transparent)] +#[must_use] +pub struct Port(pub u64); + +bi_enum! { + #[repr(u8)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum Tag { + /// `Red` ports represent redirects, which are an implementation detail of + /// the atomic linking algorithm, and don't have a precise analogue in + /// interaction nets. + /// + /// These ports are never directly held, but rather replace the backlinks of + /// some var ports. They are used to resolve inter-thread conflicts, and + /// thus will never appear when single-threaded. + /// + /// See the documentation for the linking algorithm for more. + Red = 0, + /// A `Var` port represents an auxiliary port in the net. + /// + /// The address of this port represents the wire leaving this port, + /// accessible with `Port::wire`. + /// + /// The label of this port is currently unused and always 0. + Var = 1, + /// A `Ref` port represents the principal port of a nilary reference node. + /// + /// The address of this port is a pointer to the corresponding [`Def`]. + /// + /// The label of this port is always equivalent to `def.labs.min_safe`, and + /// is used as an optimization for the ref commutation interaction. + /// + /// Eraser nodes are represented by a null-pointer `Ref`, available as the + /// constant [`Port::ERA`]. + Ref = 2, + /// A `Num` port represents the principal port of a U60 node. + /// + /// The top 60 bits of the port are the value of this node, and are + /// accessible with [`Port::num`]. + /// + /// The 4th bit from the bottom is currently unused in this port. + Num = 3, + /// An `Op2` port represents the principal port of an Op2 node. + /// + /// The label of this port is the corresponding operation, which can be + /// accessed with [`Port::op`]. + /// + /// The address of this port is the address of a two-word allocation, + /// storing the targets of the wires connected to the two auxiliary ports of + /// this node. + Op2 = 4, + /// An `Op1` port represents the principal port of an Op1 node. + /// + /// The label of this port is the corresponding operation, which can be + /// accessed with [`Port::op`]. + /// + /// The address of this port is the address of a two-word allocation. The + /// first word in this allocation stores the first operand as a `Num` port, + /// and the second word stores the target of the wire connected to the + /// auxiliary port of this node. + Op1 = 5, + /// A `Mat` port represents the principal port of a Mat node. + /// + /// The address of this port is the address of a two-word allocation, + /// storing the targets of the wires connected to the two auxiliary ports of + /// the node. + /// + /// The label of this port is currently unused and always 0. + Mat = 6, + /// A `Ctr` port represents the principal port of an binary interaction + /// combinator node. + /// + /// The label of this port is the label of the combinator; two combinators + /// annihilate if they have the same label, or commute otherwise. + /// + /// The address of this port is the address of a two-word allocation, + /// storing the targets of the wires connected to the two auxiliary ports of + /// the node. + Ctr = 7, + } +} + +impl fmt::Debug for Port { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:016x?} ", self.0)?; + match *self { + Port::ERA => write!(f, "[ERA]"), + Port::FREE => write!(f, "[FREE]"), + Port::GONE => write!(f, "[GONE]"), + Port::LOCK => write!(f, "[LOCK]"), + _ => match self.tag() { + Num => write!(f, "[Num {}]", self.num()), + Var | Red | Mat => write!(f, "[{:?} {:?}]", self.tag(), self.addr()), + Op2 | Op1 | Ctr | Ref => write!(f, "[{:?} {:?} {:?}]", self.tag(), self.lab(), self.addr()), + }, + } + } +} + +impl Port { + /// The principal port of an eraser node. + pub const ERA: Port = Port(Ref as _); + /// A sentinel value used to indicate free memory; see the allocator for more + /// details. + pub const FREE: Port = Port(0x8000_0000_0000_0000); + /// A sentinel value used to lock a wire; see the linking algorithm for more + /// details. + pub const LOCK: Port = Port(0xFFFF_FFFF_FFFF_FFF0); + /// A sentinel value used in the atomic linking algorithm; see it for more + /// details. + pub const GONE: Port = Port(0xFFFF_FFFF_FFFF_FFFF); + + /// Creates a new port with a given tag, label, and addr. + #[inline(always)] + pub fn new(tag: Tag, lab: Lab, addr: Addr) -> Self { + Port(((lab as u64) << 48) | (addr.0 as u64) | (tag as u64)) + } + + /// Creates a new [`Var`] port with a given addr. + #[inline(always)] + pub fn new_var(addr: Addr) -> Self { + Port::new(Var, 0, addr) + } + + /// Creates a new [`Num`] port with a given 60-bit numeric value. + #[inline(always)] + pub const fn new_num(val: u64) -> Self { + Port((val << 4) | (Num as u64)) + } + + /// Creates a new [`Ref`] port corresponding to a given definition. + #[inline(always)] + pub fn new_ref(def: &Def) -> Port { + Port::new(Ref, def.labs.min_safe, Addr(def as *const _ as _)) + } + + /// Accesses the tag of this port; this is valid for all ports. + #[inline(always)] + pub fn tag(&self) -> Tag { + unsafe { Tag::from_unchecked((self.0 & 0x7) as u8) } + } + + #[inline(always)] + pub fn is(&self, tag: Tag) -> bool { + self.tag() == tag + } + + /// Accesses the label of this port; this is valid for all non-`Num` ports. + #[inline(always)] + pub const fn lab(&self) -> Lab { + (self.0 >> 48) as Lab + } + + /// Accesses the addr of this port; this is valid for all non-`Num` ports. + #[inline(always)] + pub const fn addr(&self) -> Addr { + Addr((self.0 & 0x0000_FFFF_FFFF_FFF8) as usize as _) + } + + /// Accesses the operation of this port; this is valid for [`Op1`] and [`Op2`] + /// ports. + #[inline(always)] + pub fn op(&self) -> Op { + unsafe { Op::from_unchecked(self.lab()) } + } + + /// Accesses the numeric value of this port; this is valid for [`Num`] ports. + #[inline(always)] + pub const fn num(&self) -> u64 { + self.0 >> 4 + } + + /// Accesses the wire leaving this port; this is valid for [`Var`] ports and + /// non-sentinel [`Red`] ports. + #[inline(always)] + pub fn wire(&self) -> Wire { + Wire::new(self.addr()) + } + + #[inline(always)] + pub fn is_principal(&self) -> bool { + self.tag() >= Ref + } + + /// Given a principal port, returns whether this principal port may be part of + /// a skippable active pair -- an active pair like `ERA-ERA` that does not + /// need to be added to the redex list. + #[inline(always)] + pub fn is_skippable(&self) -> bool { + self.tag() == Num || self.tag() == Ref && self.lab() != u16::MAX + } + + /// Converts a [`Var`] port into a [`Red`] port with the same address. + #[inline(always)] + pub(super) fn redirect(&self) -> Port { + Port::new(Red, 0, self.addr()) + } + + /// Converts a [`Red`] port into a [`Var`] port with the same address. + #[inline(always)] + pub(super) fn unredirect(&self) -> Port { + Port::new(Var, 0, self.addr()) + } + + pub(super) fn is_full_node(&self) -> bool { + self.tag() > Num + } +} diff --git a/src/run/wire.rs b/src/run/wire.rs new file mode 100644 index 00000000..7a79cd94 --- /dev/null +++ b/src/run/wire.rs @@ -0,0 +1,94 @@ +use super::*; + +/// A wire in the interaction net. +/// +/// More accurately, this is a *directed view* of a wire. If ports `a` and `b` +/// are connected, then the wire leaving `a` and the wire leaving `b` are the +/// same wire, but viewed from opposite directions. +/// +/// This is represented by a pointer to an `AtomicU64` storing the *target* of +/// the wire -- the port on the other side. (The target of the wire leaving `a` +/// is `b`.) +/// +/// Changes to the target are handled by the linker. +#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[must_use] +pub struct Wire(pub *const AtomicU64); + +impl fmt::Debug for Wire { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:012x?}", self.0 as usize) + } +} + +unsafe impl Send for Wire {} +unsafe impl Sync for Wire {} + +impl Wire { + #[inline(always)] + pub fn addr(&self) -> Addr { + Addr(self.0 as _) + } + + #[inline(always)] + pub fn new(addr: Addr) -> Wire { + Wire(addr.0 as _) + } + + #[inline(always)] + fn target<'a>(&self) -> &'a AtomicU64 { + if cfg!(feature = "_fuzz") { + assert_ne!(self.0 as usize, 0xfffffffffff0u64 as usize); + assert_ne!(self.0 as usize, 0); + } + unsafe { &*self.0 } + } + + #[inline(always)] + pub fn load_target(&self) -> Port { + let port = Port(self.target().load(Relaxed)); + if cfg!(feature = "_fuzz") { + assert_ne!(port, Port::FREE); + } + port + } + + #[inline(always)] + pub fn set_target(&self, port: Port) { + self.target().store(port.0, Relaxed); + } + + #[inline(always)] + pub fn cas_target(&self, expected: Port, value: Port) -> Result { + self.target().compare_exchange(expected.0, value.0, Relaxed, Relaxed).map(Port).map_err(Port) + } + + #[inline(always)] + pub fn swap_target(&self, value: Port) -> Port { + let port = Port(self.target().swap(value.0, Relaxed)); + if cfg!(feature = "_fuzz") { + assert_ne!(port, Port::FREE); + } + port + } + + // Takes a pointer's target. + #[inline(always)] + pub fn lock_target(&self) -> Port { + loop { + let got = self.swap_target(Port::LOCK); + if cfg!(feature = "_fuzz") { + assert_ne!(got, Port::FREE); + } + if got != Port::LOCK { + return got; + } + spin_loop(); + } + } + + #[inline(always)] + pub(super) fn as_var(&self) -> Port { + Port::new_var(self.addr()) + } +} diff --git a/src/util.rs b/src/util.rs index b9d0bdff..0b87dde2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,251 +1,10 @@ -use crate::{ - ast::{Net, Tree}, - run::Rewrites, -}; -use std::time::Duration; - -/// Creates a variable uniquely identified by `id`. -pub(crate) fn create_var(mut id: usize) -> String { - let mut txt = Vec::new(); - id += 1; - while id > 0 { - id -= 1; - txt.push((id % 26) as u8 + b'a'); - id /= 26; - } - txt.reverse(); - String::from_utf8(txt).unwrap() -} - -/// Inverse function of [`create_var`]. -/// -/// Returns None when the provided string is not an output of -/// `create_var`. -pub(crate) fn var_to_num(s: &str) -> Option { - let mut n = 0usize; - for i in s.chars() { - let i = (i as u32).checked_sub('a' as u32)? as usize; - if i > 'z' as usize { - return None; - } - n *= 26; - n += i; - n += 1; - } - n.checked_sub(1) // if it's none, then it means the initial string was '' -} - -#[test] -fn test_create_var() { - assert_eq!(create_var(0), "a"); - assert_eq!(create_var(1), "b"); - assert_eq!(create_var(25), "z"); - assert_eq!(create_var(26), "aa"); - assert_eq!(create_var(27), "ab"); - assert_eq!(create_var(51), "az"); - assert_eq!(create_var(52), "ba"); - assert_eq!(create_var(676), "za"); - assert_eq!(create_var(701), "zz"); - assert_eq!(create_var(702), "aaa"); - assert_eq!(create_var(703), "aab"); - assert_eq!(create_var(728), "aba"); - assert_eq!(create_var(1351), "ayz"); - assert_eq!(create_var(1352), "aza"); - assert_eq!(create_var(1378), "baa"); -} - -#[test] -fn test_var_to_num() { - for i in [0, 1, 2, 3, 10, 26, 27, 30, 50, 70] { - assert_eq!(Some(i), var_to_num(&create_var(i))); - } -} - -/// Defines bi-directional mappings for a numeric enum. -macro_rules! bi_enum { - ( - #[repr($uN:ident)] - $(#$attr:tt)* - $vis:vis enum $Ty:ident { - $($(#$var_addr:tt)* $Variant:ident = $value:literal),* $(,)? - } - ) => { - #[repr($uN)] $(#$attr)* $vis enum $Ty { $($(#$var_addr)* $Variant = $value,)* } - - impl TryFrom<$uN> for $Ty { - type Error = (); - fn try_from(value: $uN) -> Result { - Ok(match value { $($value => $Ty::$Variant,)* _ => Err(())?, }) - } - } - - impl $Ty { - #[allow(unused)] - pub unsafe fn from_unchecked(value: $uN) -> $Ty { - Self::try_from(value).unwrap_unchecked() - } - } - - impl From<$Ty> for $uN { - fn from(value: $Ty) -> Self { value as Self } - } - }; - ( - #[repr($uN:ident)] - $(#$attr:tt)* - $vis:vis enum $Ty:ident { - $($(#$var_addr:tt)* $str:literal: $Variant:ident = $value:literal),* $(,)? - } - ) => { - bi_enum! { #[repr($uN)] $(#$attr)* $vis enum $Ty { $($(#$var_addr)* $Variant = $value,)* } } - - impl std::fmt::Display for $Ty { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { $($Ty::$Variant => $str,)* }) - } - } - - impl std::str::FromStr for $Ty { - type Err = (); - fn from_str(s: &str) -> Result { - Ok(match s { $($str => $Ty::$Variant,)* _ => Err(())?, }) - } - } - }; -} - -pub(crate) use bi_enum; - -#[test] -fn test_bi_enum() { - use std::str::FromStr; - bi_enum! { - #[repr(u8)] - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - enum Trit { - Nil = 0, - One = 1, - Two = 2, - } - } - assert_eq!(u8::from(Trit::Nil), 0); - assert_eq!(Trit::try_from(1), Ok(Trit::One)); - assert_eq!(Trit::try_from(100), Err(())); - - bi_enum! { - #[repr(u8)] - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - enum Op { - "+": Add = 0, - "-": Sub = 1, - "*": Mul = 2, - "/": Div = 3, - } - } - assert_eq!(Op::Add.to_string(), "+"); - assert_eq!(Op::from_str("-"), Ok(Op::Sub)); - assert_eq!(Op::from_str("#"), Err(())); -} - -macro_rules! deref { - ($({$($gen:tt)*})? $ty:ty => self.$field:ident: $trg:ty) => { - impl $($($gen)*)? std::ops::Deref for $ty { - type Target = $trg; - fn deref(&self) -> &Self::Target { - &self.$field - } - } - impl $($($gen)*)? std::ops::DerefMut for $ty { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.$field - } - } - }; -} - -pub(crate) use deref; - -impl Tree { - /// Increases `fresh` until `create_var(*fresh)` does not conflict - /// with a [`Tree::Var`] in `tree` - /// - /// This function can be called multiple times with many trees to - /// ensure that `fresh` does not conflict with any of them. - pub(crate) fn ensure_no_conflicts(&self, fresh: &mut usize) { - match self { - Tree::Var { nam } => { - if let Some(var_num) = var_to_num(nam) { - *fresh = (*fresh).max(var_num); - } - } - // Recurse on children - Tree::Ctr { lft, rgt, .. } | Tree::Op2 { lft, rgt, .. } | Tree::Mat { sel: lft, ret: rgt } => { - lft.ensure_no_conflicts(fresh); - rgt.ensure_no_conflicts(fresh); - } - Tree::Op1 { rgt, .. } => { - rgt.ensure_no_conflicts(fresh); - } - Tree::Era | Tree::Num { .. } | Tree::Ref { .. } => {} - } - } -} - -impl Net { - pub(crate) fn ensure_no_conflicts(&self, fresh: &mut usize) { - self.root.ensure_no_conflicts(fresh); - for (a, b) in &self.rdex { - a.ensure_no_conflicts(fresh); - b.ensure_no_conflicts(fresh); - } - } - /// Transforms the net `x & ...` into `y & x ~ (arg y) & ...` - /// - /// The result is equivalent a λ-calculus application. Thus, - /// if the net is a λ-calculus term, then this function will - /// apply an argument to it. - pub fn apply_tree(&mut self, arg: Tree) { - let mut fresh = 0usize; - self.ensure_no_conflicts(&mut fresh); - arg.ensure_no_conflicts(&mut fresh); - - let fresh_str = create_var(fresh + 1); - - let fun = core::mem::take(&mut self.root); - let app = Tree::Ctr { lab: 0, lft: Box::new(arg), rgt: Box::new(Tree::Var { nam: fresh_str.clone() }) }; - self.root = Tree::Var { nam: fresh_str }; - self.rdex.push((fun, app)); - } -} - -pub fn show_rewrites(rwts: &Rewrites) -> String { - format!( - "{}{}{}{}{}{}", - format_args!("RWTS : {:>15}\n", pretty_num(rwts.total())), - format_args!("- ANNI : {:>15}\n", pretty_num(rwts.anni)), - format_args!("- COMM : {:>15}\n", pretty_num(rwts.comm)), - format_args!("- ERAS : {:>15}\n", pretty_num(rwts.eras)), - format_args!("- DREF : {:>15}\n", pretty_num(rwts.dref)), - format_args!("- OPER : {:>15}\n", pretty_num(rwts.oper)), - ) -} - -pub fn show_stats(rwts: &Rewrites, elapsed: Duration) -> String { - format!( - "{}{}{}", - show_rewrites(rwts), - format_args!("TIME : {:.3?}\n", elapsed), - format_args!("RPS : {:.3} M\n", (rwts.total() as f64) / (elapsed.as_millis() as f64) / 1000.0), - ) -} - -fn pretty_num(n: u64) -> String { - n.to_string() - .as_bytes() - .rchunks(3) - .rev() - .map(|x| std::str::from_utf8(x).unwrap()) - .flat_map(|x| ["_", x]) - .skip(1) - .collect() -} +mod apply_tree; +mod bi_enum; +mod create_var; +mod deref; +mod stats; + +pub(crate) use bi_enum::*; +pub(crate) use create_var::*; +pub(crate) use deref::*; +pub use stats::*; diff --git a/src/util/apply_tree.rs b/src/util/apply_tree.rs new file mode 100644 index 00000000..3850b736 --- /dev/null +++ b/src/util/apply_tree.rs @@ -0,0 +1,57 @@ +use crate::ast::{Net, Tree}; + +use super::{create_var, var_to_num}; + +impl Net { + /// Transforms the net `x & ...` into `y & x ~ (arg y) & ...` + /// + /// The result is equivalent a λ-calculus application. Thus, + /// if the net is a λ-calculus term, then this function will + /// apply an argument to it. + pub fn apply_tree(&mut self, arg: Tree) { + let mut fresh = 0usize; + self.ensure_no_conflicts(&mut fresh); + arg.ensure_no_conflicts(&mut fresh); + + let fresh_str = create_var(fresh + 1); + + let fun = core::mem::take(&mut self.root); + let app = Tree::Ctr { lab: 0, lft: Box::new(arg), rgt: Box::new(Tree::Var { nam: fresh_str.clone() }) }; + self.root = Tree::Var { nam: fresh_str }; + self.redexes.push((fun, app)); + } + + pub(crate) fn ensure_no_conflicts(&self, fresh: &mut usize) { + self.root.ensure_no_conflicts(fresh); + for (a, b) in &self.redexes { + a.ensure_no_conflicts(fresh); + b.ensure_no_conflicts(fresh); + } + } +} + +impl Tree { + /// Increases `fresh` until `create_var(*fresh)` does not conflict + /// with a [`Tree::Var`] in `tree` + /// + /// This function can be called multiple times with many trees to + /// ensure that `fresh` does not conflict with any of them. + pub(crate) fn ensure_no_conflicts(&self, fresh: &mut usize) { + match self { + Tree::Var { nam } => { + if let Some(var_num) = var_to_num(nam) { + *fresh = (*fresh).max(var_num); + } + } + // Recurse on children + Tree::Ctr { lft, rgt, .. } | Tree::Op2 { lft, rgt, .. } | Tree::Mat { sel: lft, ret: rgt } => { + lft.ensure_no_conflicts(fresh); + rgt.ensure_no_conflicts(fresh); + } + Tree::Op1 { rgt, .. } => { + rgt.ensure_no_conflicts(fresh); + } + Tree::Era | Tree::Num { .. } | Tree::Ref { .. } => {} + } + } +} diff --git a/src/util/bi_enum.rs b/src/util/bi_enum.rs new file mode 100644 index 00000000..faaf0b0f --- /dev/null +++ b/src/util/bi_enum.rs @@ -0,0 +1,85 @@ +/// Defines bi-directional mappings for a numeric enum. +macro_rules! bi_enum { + ( + #[repr($uN:ident)] + $(#$attr:tt)* + $vis:vis enum $Ty:ident { + $($(#$var_addr:tt)* $Variant:ident = $value:literal),* $(,)? + } + ) => { + #[repr($uN)] $(#$attr)* $vis enum $Ty { $($(#$var_addr)* $Variant = $value,)* } + + impl TryFrom<$uN> for $Ty { + type Error = (); + fn try_from(value: $uN) -> Result { + Ok(match value { $($value => $Ty::$Variant,)* _ => Err(())?, }) + } + } + + impl $Ty { + #[allow(unused)] + pub unsafe fn from_unchecked(value: $uN) -> $Ty { + Self::try_from(value).unwrap_unchecked() + } + } + + impl From<$Ty> for $uN { + fn from(value: $Ty) -> Self { value as Self } + } + }; + ( + #[repr($uN:ident)] + $(#$attr:tt)* + $vis:vis enum $Ty:ident { + $($(#$var_addr:tt)* $str:literal: $Variant:ident = $value:literal),* $(,)? + } + ) => { + bi_enum! { #[repr($uN)] $(#$attr)* $vis enum $Ty { $($(#$var_addr)* $Variant = $value,)* } } + + impl std::fmt::Display for $Ty { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { $($Ty::$Variant => $str,)* }) + } + } + + impl std::str::FromStr for $Ty { + type Err = (); + fn from_str(s: &str) -> Result { + Ok(match s { $($str => $Ty::$Variant,)* _ => Err(())?, }) + } + } + }; +} + +pub(crate) use bi_enum; + +#[test] +fn test_bi_enum() { + use std::str::FromStr; + bi_enum! { + #[repr(u8)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + enum Trit { + Nil = 0, + One = 1, + Two = 2, + } + } + assert_eq!(u8::from(Trit::Nil), 0); + assert_eq!(Trit::try_from(1), Ok(Trit::One)); + assert_eq!(Trit::try_from(100), Err(())); + + bi_enum! { + #[repr(u8)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + enum Op { + "+": Add = 0, + "-": Sub = 1, + "*": Mul = 2, + "/": Div = 3, + } + } + assert_eq!(Op::Add.to_string(), "+"); + assert_eq!(Op::from_str("-"), Ok(Op::Sub)); + assert_eq!(Op::from_str("#"), Err(())); +} diff --git a/src/util/create_var.rs b/src/util/create_var.rs new file mode 100644 index 00000000..4f6012e1 --- /dev/null +++ b/src/util/create_var.rs @@ -0,0 +1,56 @@ +/// Creates a variable uniquely identified by `id`. +pub(crate) fn create_var(mut id: usize) -> String { + let mut txt = Vec::new(); + id += 1; + while id > 0 { + id -= 1; + txt.push((id % 26) as u8 + b'a'); + id /= 26; + } + txt.reverse(); + String::from_utf8(txt).unwrap() +} + +/// Inverse function of [`create_var`]. +/// +/// Returns None when the provided string is not an output of +/// `create_var`. +pub(crate) fn var_to_num(s: &str) -> Option { + let mut n = 0usize; + for i in s.chars() { + let i = (i as u32).checked_sub('a' as u32)? as usize; + if i > 'z' as usize { + return None; + } + n *= 26; + n += i; + n += 1; + } + n.checked_sub(1) // if it's none, then it means the initial string was '' +} + +#[test] +fn test_create_var() { + assert_eq!(create_var(0), "a"); + assert_eq!(create_var(1), "b"); + assert_eq!(create_var(25), "z"); + assert_eq!(create_var(26), "aa"); + assert_eq!(create_var(27), "ab"); + assert_eq!(create_var(51), "az"); + assert_eq!(create_var(52), "ba"); + assert_eq!(create_var(676), "za"); + assert_eq!(create_var(701), "zz"); + assert_eq!(create_var(702), "aaa"); + assert_eq!(create_var(703), "aab"); + assert_eq!(create_var(728), "aba"); + assert_eq!(create_var(1351), "ayz"); + assert_eq!(create_var(1352), "aza"); + assert_eq!(create_var(1378), "baa"); +} + +#[test] +fn test_var_to_num() { + for i in [0, 1, 2, 3, 10, 26, 27, 30, 50, 70] { + assert_eq!(Some(i), var_to_num(&create_var(i))); + } +} diff --git a/src/util/deref.rs b/src/util/deref.rs new file mode 100644 index 00000000..6774a072 --- /dev/null +++ b/src/util/deref.rs @@ -0,0 +1,17 @@ +macro_rules! deref { + ($({$($gen:tt)*})? $ty:ty => self.$field:ident: $trg:ty) => { + impl $($($gen)*)? std::ops::Deref for $ty { + type Target = $trg; + fn deref(&self) -> &Self::Target { + &self.$field + } + } + impl $($($gen)*)? std::ops::DerefMut for $ty { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.$field + } + } + }; +} + +pub(crate) use deref; diff --git a/src/util/stats.rs b/src/util/stats.rs new file mode 100644 index 00000000..4e6552e3 --- /dev/null +++ b/src/util/stats.rs @@ -0,0 +1,35 @@ +use std::time::Duration; + +use crate::run::Rewrites; + +pub fn show_rewrites(rwts: &Rewrites) -> String { + format!( + "{}{}{}{}{}{}", + format_args!("RWTS : {:>15}\n", pretty_num(rwts.total())), + format_args!("- ANNI : {:>15}\n", pretty_num(rwts.anni)), + format_args!("- COMM : {:>15}\n", pretty_num(rwts.comm)), + format_args!("- ERAS : {:>15}\n", pretty_num(rwts.eras)), + format_args!("- DREF : {:>15}\n", pretty_num(rwts.dref)), + format_args!("- OPER : {:>15}\n", pretty_num(rwts.oper)), + ) +} + +pub fn show_stats(rwts: &Rewrites, elapsed: Duration) -> String { + format!( + "{}{}{}", + show_rewrites(rwts), + format_args!("TIME : {:.3?}\n", elapsed), + format_args!("RPS : {:.3} M\n", (rwts.total() as f64) / (elapsed.as_millis() as f64) / 1000.0), + ) +} + +fn pretty_num(n: u64) -> String { + n.to_string() + .as_bytes() + .rchunks(3) + .rev() + .map(|x| std::str::from_utf8(x).unwrap()) + .flat_map(|x| ["_", x]) + .skip(1) + .collect() +} diff --git a/tests/cli.rs b/tests/cli.rs index 6af8c6b3..26797e22 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -137,7 +137,7 @@ fn test_cli_errors() { fn test_apply_tree() { use hvmc::run; fn eval_with_args(fun: &str, args: &[&str]) -> Net { - let area = run::Net::::init_heap(1 << 10); + let area = run::Heap::new(1 << 10); let mut fun: Net = fun.parse().unwrap(); for arg in args { diff --git a/tests/fuzz.rs b/tests/fuzz.rs index 24fe2efb..b50546df 100644 --- a/tests/fuzz.rs +++ b/tests/fuzz.rs @@ -3,7 +3,7 @@ use hvmc::{ fuzz::*, - run::{Addr, Port, Strict, Tag}, + run::{Addr, Heap, Port, Strict, Tag}, trace, }; @@ -18,7 +18,7 @@ fn fuzz_var_link_link_var() { trace::set_hook(); Fuzzer::default().fuzz(|fuzz| { unsafe { trace::_reset_traces() }; - let heap = Net::init_heap(256); + let heap = Heap::new(256); let mut net = Net::new(&heap); let x = net.alloc(); let y = net.alloc(); @@ -63,7 +63,7 @@ fn fuzz_pri_link_link_pri() { unsafe { trace::_reset_traces() }; let p = Port::new(Tag::Ctr, 0, Addr::NULL); let q = Port::new(Tag::Ctr, 1, Addr::NULL); - let heap = Net::init_heap(256); + let heap = Heap::new(256); let mut net = Net::new(&heap); let x = net.alloc(); let a = Port::new_var(x.clone()); @@ -80,7 +80,7 @@ fn fuzz_pri_link_link_pri() { n1.link_wire_port(b.wire(), q); }); }); - assert!(n0.rdex.len() == 1 || n1.rdex.len() == 1); + assert!(n0.redexes.len() == 1 || n1.redexes.len() == 1); for x in [a, b] { assert_eq!(x.addr().val().read(), Port::FREE.0); } @@ -92,7 +92,7 @@ fn fuzz_pri_link_link_pri() { fn fuzz_var_link_link_pri() { assert!(cfg!(not(feature = "_fuzz_no_free"))); trace::set_hook(); - let heap = Net::init_heap(256); + let heap = Heap::new(256); Fuzzer::default().fuzz(|fuzz| { unsafe { trace::_reset_traces() }; let mut net = Net::new(&heap); @@ -133,7 +133,7 @@ fn fuzz_var_link_link_pri() { fn fuzz_var_link_link_link_var() { assert!(cfg!(feature = "_fuzz_no_free")); trace::set_hook(); - let heap = Net::init_heap(256); + let heap = Heap::new(256); Fuzzer::default().fuzz(|fuzz| { unsafe { trace::_reset_traces() }; let mut net = Net::new(&heap); diff --git a/tests/loaders/mod.rs b/tests/loaders/mod.rs index afbff8ca..616587fb 100644 --- a/tests/loaders/mod.rs +++ b/tests/loaders/mod.rs @@ -24,7 +24,7 @@ pub fn replace_template(mut code: String, map: &[(&str, &str)]) -> String { #[allow(unused_variables)] pub fn normal(book: Book, size: usize) -> (hvmc::run::Rewrites, Net) { - let area = run::Net::::init_heap(size); + let area = run::Heap::new(size); let host = Host::new(&book); let mut rnet = run::Net::::new(&area); diff --git a/tests/tests.rs b/tests/tests.rs index 28608de2..4359cd23 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -66,7 +66,7 @@ fn test_run(name: &str, host: Host) { println!(" skipping"); return; }; - let heap = run::Net::::init_heap(1 << 29); + let heap = run::Heap::new(1 << 29); let mut net = run::Net::::new(&heap); net.boot(entrypoint); let start = Instant::now(); From 91f9a74d066d243e5f8fcffc48a26b0a7255ac1a Mon Sep 17 00:00:00 2001 From: tjjfvi Date: Thu, 29 Feb 2024 08:43:23 -0500 Subject: [PATCH 2/5] be explicit about units when creating heap --- src/main.rs | 4 ++-- src/run/allocator.rs | 15 +++++++++++---- tests/cli.rs | 2 +- tests/fuzz.rs | 8 ++++---- tests/loaders/mod.rs | 4 ++-- tests/tests.rs | 2 +- 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5e3f4b3d..dd7e0663 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,7 +93,7 @@ struct RuntimeOpts { /// but allows running programs that would expand indefinitely otherwise. lazy_mode: bool, #[arg(short = 'm', long = "memory", default_value = "1G", value_parser = mem_parser)] - /// How much memory to allocate on startup. + /// How much memory to allocate on startup, measured in bytes. /// /// Supports abbreviations such as '4G' or '400M'. memory: u64, @@ -209,7 +209,7 @@ fn load_files(files: &[String]) -> Arc> { } fn reduce_exprs(host: &Host, exprs: &[Net], opts: &RuntimeOpts) { - let heap = run::Heap::new(opts.memory as usize); + let heap = run::Heap::new_bytes(opts.memory as usize); for expr in exprs { let mut net = DynNet::new(&heap, opts.lazy_mode); dispatch_dyn_net!(&mut net => { diff --git a/src/run/allocator.rs b/src/run/allocator.rs index fd9e8508..edae45e2 100644 --- a/src/run/allocator.rs +++ b/src/run/allocator.rs @@ -13,15 +13,22 @@ pub(super) struct Node(pub AtomicU64, pub AtomicU64); pub struct Heap(pub(super) [Node]); impl Heap { - /// Allocate an area for the net's heap with a given size. - pub fn new(size: usize) -> Box { + #[inline] + /// Allocate a new heap with a given size in words. + pub fn new_words(words: usize) -> Box { + let nodes = words / 2; unsafe { Box::from_raw(core::ptr::slice_from_raw_parts_mut( - alloc::alloc(Layout::array::(size).unwrap()) as *mut _, - size, + alloc::alloc(Layout::array::(nodes).unwrap()) as *mut _, + nodes, ) as *mut _) } } + #[inline(always)] + /// Allocate a new heap with a given size in bytes. + pub fn new_bytes(bytes: usize) -> Box { + Heap::new_words(bytes / 8) + } } pub struct Allocator<'h> { diff --git a/tests/cli.rs b/tests/cli.rs index 26797e22..40bf8c67 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -137,7 +137,7 @@ fn test_cli_errors() { fn test_apply_tree() { use hvmc::run; fn eval_with_args(fun: &str, args: &[&str]) -> Net { - let area = run::Heap::new(1 << 10); + let area = run::Heap::new_words(1 << 10); let mut fun: Net = fun.parse().unwrap(); for arg in args { diff --git a/tests/fuzz.rs b/tests/fuzz.rs index b50546df..b1ee4ea0 100644 --- a/tests/fuzz.rs +++ b/tests/fuzz.rs @@ -18,7 +18,7 @@ fn fuzz_var_link_link_var() { trace::set_hook(); Fuzzer::default().fuzz(|fuzz| { unsafe { trace::_reset_traces() }; - let heap = Heap::new(256); + let heap = Heap::new_words(16); let mut net = Net::new(&heap); let x = net.alloc(); let y = net.alloc(); @@ -63,7 +63,7 @@ fn fuzz_pri_link_link_pri() { unsafe { trace::_reset_traces() }; let p = Port::new(Tag::Ctr, 0, Addr::NULL); let q = Port::new(Tag::Ctr, 1, Addr::NULL); - let heap = Heap::new(256); + let heap = Heap::new_words(16); let mut net = Net::new(&heap); let x = net.alloc(); let a = Port::new_var(x.clone()); @@ -92,7 +92,7 @@ fn fuzz_pri_link_link_pri() { fn fuzz_var_link_link_pri() { assert!(cfg!(not(feature = "_fuzz_no_free"))); trace::set_hook(); - let heap = Heap::new(256); + let heap = Heap::new_words(16); Fuzzer::default().fuzz(|fuzz| { unsafe { trace::_reset_traces() }; let mut net = Net::new(&heap); @@ -133,7 +133,7 @@ fn fuzz_var_link_link_pri() { fn fuzz_var_link_link_link_var() { assert!(cfg!(feature = "_fuzz_no_free")); trace::set_hook(); - let heap = Heap::new(256); + let heap = Heap::new_words(16); Fuzzer::default().fuzz(|fuzz| { unsafe { trace::_reset_traces() }; let mut net = Net::new(&heap); diff --git a/tests/loaders/mod.rs b/tests/loaders/mod.rs index 616587fb..32ae7c81 100644 --- a/tests/loaders/mod.rs +++ b/tests/loaders/mod.rs @@ -23,8 +23,8 @@ pub fn replace_template(mut code: String, map: &[(&str, &str)]) -> String { } #[allow(unused_variables)] -pub fn normal(book: Book, size: usize) -> (hvmc::run::Rewrites, Net) { - let area = run::Heap::new(size); +pub fn normal(book: Book, words: usize) -> (hvmc::run::Rewrites, Net) { + let area = run::Heap::new_words(words); let host = Host::new(&book); let mut rnet = run::Net::::new(&area); diff --git a/tests/tests.rs b/tests/tests.rs index 4359cd23..05da2523 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -66,7 +66,7 @@ fn test_run(name: &str, host: Host) { println!(" skipping"); return; }; - let heap = run::Heap::new(1 << 29); + let heap = run::Heap::new_words(1 << 29); let mut net = run::Net::::new(&heap); net.boot(entrypoint); let start = Instant::now(); From 49f7466560a08e8a6be4b17cb95173325e63864c Mon Sep 17 00:00:00 2001 From: tjjfvi Date: Thu, 29 Feb 2024 08:46:19 -0500 Subject: [PATCH 3/5] update docs --- src/run.rs | 2 +- src/run/allocator.rs | 2 ++ src/run/linker.rs | 12 +++++++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/run.rs b/src/run.rs index 9b225f3c..19bdfa52 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,6 +1,6 @@ //! The HVM runtime. //! -//! This file can be divided into three major logical components: +//! The runtime is divided into three major logical components: //! - the **allocator**, which manages the creation and destruction of nodes in //! the net //! - the **linker**, which links ports and wires in the interaction net, in a diff --git a/src/run/allocator.rs b/src/run/allocator.rs index edae45e2..308207a2 100644 --- a/src/run/allocator.rs +++ b/src/run/allocator.rs @@ -9,6 +9,7 @@ use super::*; #[derive(Default)] pub(super) struct Node(pub AtomicU64, pub AtomicU64); +/// The memory buffer backing a [`Net`]. #[repr(align(16))] pub struct Heap(pub(super) [Node]); @@ -31,6 +32,7 @@ impl Heap { } } +/// Manages allocating and freeing nodes within the net. pub struct Allocator<'h> { pub(super) tracer: Tracer, pub(super) heap: &'h Heap, diff --git a/src/run/linker.rs b/src/run/linker.rs index 06f0c571..ea91e7d6 100644 --- a/src/run/linker.rs +++ b/src/run/linker.rs @@ -9,6 +9,13 @@ pub(super) struct Header { pub(super) targ: Port, } +/// Manages linking ports and wires within the net. +/// +/// When threads interfere, this uses the atomic linking algorithm described in +/// `paper/`. +/// +/// Linking wires must be done atomically, but linking ports can be done +/// non-atomically (because they must be locked). pub struct Linker<'h, M: Mode> { pub(super) allocator: Allocator<'h>, pub redexes: Vec<(Port, Port)>, @@ -19,11 +26,6 @@ pub struct Linker<'h, M: Mode> { deref!({<'h, M: Mode>} Linker<'h, M> => self.allocator: Allocator<'h>); -/// When threads interfere, this uses the atomic linking algorithm described in -/// `paper/`. -/// -/// Linking wires must be done atomically, but linking ports can be done -/// non-atomically (because they must be locked). impl<'h, M: Mode> Linker<'h, M> { pub fn new(heap: &'h Heap) -> Self { Linker { From 0d5346001ec63dd0cd801e5cfb9e57d7e5a1f1e2 Mon Sep 17 00:00:00 2001 From: tjjfvi Date: Thu, 29 Feb 2024 08:49:27 -0500 Subject: [PATCH 4/5] fix benches --- benches/benches.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benches/benches.rs b/benches/benches.rs index 205b98fa..627a3c25 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -59,7 +59,7 @@ fn run_file(path: &PathBuf, group: Option, c: &mut Criterion) { } fn benchmark(file_name: &str, book: Book, c: &mut Criterion) { - let area = Heap::new(1 << 29); + let area = Heap::new_words(1 << 29); let host = Host::new(&book); c.bench_function(file_name, |b| { b.iter(|| { @@ -71,7 +71,7 @@ fn benchmark(file_name: &str, book: Book, c: &mut Criterion) { } fn benchmark_group(file_name: &str, group: String, book: Book, c: &mut Criterion) { - let area = Heap::new(1 << 29); + let area = Heap::new_words(1 << 29); let host = Host::new(&book); c.benchmark_group(group).bench_function(file_name, |b| { @@ -98,7 +98,7 @@ fn interact_benchmark(c: &mut Criterion) { for (name, redex) in cases { let mut book = Book::default(); book.insert("main".to_string(), Net { root: Era, redexes: vec![redex] }); - let area = Heap::new(1 << 24); + let area = Heap::new_words(1 << 24); let host = Host::new(&book); group.bench_function(name, |b| { b.iter(|| { From 92642093857b54e2132349b5d5a299aabe83781c Mon Sep 17 00:00:00 2001 From: tjjfvi Date: Thu, 29 Feb 2024 10:28:23 -0500 Subject: [PATCH 5/5] rdex isnt a word --- README.md | 4 ++-- cspell.json | 1 - src/ast.rs | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 44cd7a8f..e54ed457 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ HVM-Core's textual syntax represents interaction combinators via an AST: ::= ::= - ::= "&" "~" + ::= "&" "~" ::= ::= "@" "=" @@ -419,7 +419,7 @@ struct Redex { // A closed net struct Net { root: Ptr, // a free wire - rdex: Vec // a vector of redexes + redexes: Vec // a vector of redexes heap: Vec // a vector of nodes } ``` diff --git a/cspell.json b/cspell.json index 33467b35..fb7cf2de 100644 --- a/cspell.json +++ b/cspell.json @@ -31,7 +31,6 @@ "plog", "ptrs", "rchunks", - "rdex", "readback", "redex", "redexes", diff --git a/src/ast.rs b/src/ast.rs index 6ce572a9..9e64d808 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -125,15 +125,15 @@ impl<'i> Parser<'i> { /// Net = Tree ("&" Tree "~" Tree)* fn parse_net(&mut self) -> Result { - let mut rdex = Vec::new(); + let mut redexes = Vec::new(); let root = self.parse_tree()?; while self.consume("&").is_ok() { let tree1 = self.parse_tree()?; self.consume("~")?; let tree2 = self.parse_tree()?; - rdex.push((tree1, tree2)); + redexes.push((tree1, tree2)); } - Ok(Net { root, redexes: rdex }) + Ok(Net { root, redexes }) } fn parse_tree(&mut self) -> Result {