diff --git a/bar.hvmc b/bar.hvmc
new file mode 100644
index 00000000..d9290909
--- /dev/null
+++ b/bar.hvmc
@@ -0,0 +1,3 @@
+
+@bar = (?<#0 #1 R> R)
+@main = R & @bar ~ (#123 R)
diff --git a/examples/sort/bitonic/bitonic_sort_ctr.hvm1 b/examples/sort/bitonic/bitonic_sort_ctr.hvm1
index 2f979ad8..0e654de8 100644
--- a/examples/sort/bitonic/bitonic_sort_ctr.hvm1
+++ b/examples/sort/bitonic/bitonic_sort_ctr.hvm1
@@ -1,9 +1,9 @@
 // Atomic Swapper (HVM builtin)
-//(Data.U60.swap 0 a b) = (Both a b)
-//(Data.U60.swap n a b) = (Both b a)
+//(U60.swap 0 a b) = (Both a b)
+//(U60.swap n a b) = (Both b a)
 
 // Swaps distant values in parallel; corresponds to a Red Box
-(Warp s (Leaf a)   (Leaf b))   = (Data.U60.swap (^ (> a b) s) (Leaf a) (Leaf b))
+(Warp s (Leaf a)   (Leaf b))   = (U60.swap (^ (> a b) s) (Leaf a) (Leaf b))
 (Warp s (Both a b) (Both c d)) = (Join (Warp s a c) (Warp s b d))
 
 // Rebuilds the warped tree in the original order
@@ -33,4 +33,4 @@
 (Sum (Leaf x))   = x
 (Sum (Both a b)) = (+ (Sum a) (Sum b))
 
-Main = (Sum (Sort 0 (Rev (Gen 18 0))))
+Main = (Sum (Sort 0 (Rev (Gen 19 0))))
diff --git a/examples/sort/bitonic/bitonic_sort_ctr.hvm2 b/examples/sort/bitonic/bitonic_sort_ctr.hvm2
new file mode 100644
index 00000000..ae46f8b6
--- /dev/null
+++ b/examples/sort/bitonic/bitonic_sort_ctr.hvm2
@@ -0,0 +1,40 @@
+
+data Tree = (Leaf a) | (Both a b)
+
+(U60.swap 0 a b) = (Both a b)
+(U60.swap n a b) = (Both b a)
+
+// Swaps distant values in parallel; corresponds to a Red Box
+(Warp s (Leaf a)   (Leaf b))   = (U60.swap (^ (> a b) s) (Leaf a) (Leaf b))
+(Warp s (Both a b) (Both c d)) = (Join (Warp s a c) (Warp s b d))
+(Warp s * *) = *
+
+// Rebuilds the warped tree in the original order
+(Join (Both a b) (Both c d)) = (Both (Both a c) (Both b d))
+(Join * *) = *
+
+// Recursively warps each sub-tree; corresponds to a Blue/Green Box
+(Flow s (Leaf a))   = (Leaf a)
+(Flow s (Both a b)) = (Down s (Warp s a b))
+
+// Propagates Flow downwards
+(Down s (Leaf a))   = (Leaf a)
+(Down s (Both a b)) = (Both (Flow s a) (Flow s b))
+
+// Bitonic Sort
+(Sort s (Leaf a))   = (Leaf a)
+(Sort s (Both a b)) = (Flow s (Both (Sort 0 a) (Sort 1 b)))
+
+// Generates a tree of depth `n`
+(Gen 0 x) = (Leaf x)
+(Gen n x) = let m = (- n 1); (Both (Gen m (* x 2)) (Gen m (+ (* x 2) 1)))
+
+// Reverses a tree
+(Rev (Leaf x))   = (Leaf x)
+(Rev (Both a b)) = (Both (Rev b) (Rev a))
+
+// Sums a tree
+(Sum (Leaf x))   = x
+(Sum (Both a b)) = (+ (Sum a) (Sum b))
+
+Main = (Sum (Sort 0 (Rev (Gen 20 0))))
diff --git a/examples/sort/bitonic/bitonic_sort_ctr.hvmc b/examples/sort/bitonic/bitonic_sort_ctr.hvmc
new file mode 100644
index 00000000..97d69340
--- /dev/null
+++ b/examples/sort/bitonic/bitonic_sort_ctr.hvmc
@@ -0,0 +1,87 @@
+@Both = (a (b (* ((a (b c)) c))))
+@Down = (a ((@Down$C0 (@Down$C1 (a b))) b))
+@Down$C0 = (a (* b))
+& @Leaf ~ (a b)
+@Down$C1 = (a (b ({3 c d} e)))
+& @Both ~ (f (g e))
+& @Flow ~ (d (b g))
+& @Flow ~ (c (a f))
+@Flow = (a ((@Flow$C0 (@Flow$C1 (a b))) b))
+@Flow$C0 = (a (* b))
+& @Leaf ~ (a b)
+@Flow$C1 = (a (b ({5 c d} e)))
+& @Down ~ (c (f e))
+& @Warp ~ (d (a (b f)))
+@Gen = (?<@Gen$C0 @Gen$C1 (a b)> (a b))
+@Gen$C0 = (a b)
+& @Leaf ~ (a b)
+@Gen$C1 = (<+ #1 <- #1 {9 a b}>> ({7 <* #2 c> <* #2 <+ #1 d>>} e))
+& @Both ~ (f (g e))
+& @Gen ~ (b (d g))
+& @Gen ~ (a (c f))
+@Join = ((@Join$C1 (@Join$C6 (a b))) (a b))
+@Join$C0 = (* *)
+@Join$C1 = (* @Join$C0)
+@Join$C2 = (* *)
+@Join$C3 = (* @Join$C2)
+@Join$C4 = (* @Join$C3)
+@Join$C5 = (a (b (c (d e))))
+& @Both ~ (f (g e))
+& @Both ~ (d (b g))
+& @Both ~ (c (a f))
+@Join$C6 = (a (b ((@Join$C4 (@Join$C5 (a (b c)))) c)))
+@Leaf = (a ((a b) (* b)))
+@Rev = ((@Rev$C0 (@Rev$C1 a)) a)
+@Rev$C0 = (a b)
+& @Leaf ~ (a b)
+@Rev$C1 = (a (b c))
+& @Both ~ (d (e c))
+& @Rev ~ (a e)
+& @Rev ~ (b d)
+@Sort = (a ((@Sort$C0 (@Sort$C3 (a b))) b))
+@Sort$C0 = (a (* b))
+& @Leaf ~ (a b)
+@Sort$C1 = a
+& @Sort ~ (#0 a)
+@Sort$C2 = a
+& @Sort ~ (#1 a)
+@Sort$C3 = (a (b (c d)))
+& @Flow ~ (c (e d))
+& @Both ~ (f (g e))
+& @Sort$C2 ~ (b g)
+& @Sort$C1 ~ (a f)
+@Sum = (((a a) (@Sum$C0 b)) b)
+@Sum$C0 = (a (b c))
+& @Sum ~ (a <+ d c>)
+& @Sum ~ (b d)
+@U60.swap = (?<@U60.swap$C0 @U60.swap$C2 (a (b c))> (a (b c)))
+@U60.swap$C0 = (a (b c))
+& @Both ~ (a (b c))
+@U60.swap$C1 = (a (b c))
+& @Both ~ (b (a c))
+@U60.swap$C2 = (* @U60.swap$C1)
+@Warp = (a ((@Warp$C5 (@Warp$C11 (a (b c)))) (b c)))
+@Warp$C0 = ({11 a b} (c ({13 <> a <^ c d>> e} f)))
+& @U60.swap ~ (d (g (h f)))
+& @Leaf ~ (b h)
+& @Leaf ~ (e g)
+@Warp$C1 = (* *)
+@Warp$C10 = (a (b ({15 c d} (e (f g)))))
+& @Join ~ (h (i g))
+& @Warp ~ (d (f (b i)))
+& @Warp ~ (c (e (a h)))
+@Warp$C11 = (a (b (c ((@Warp$C9 (@Warp$C10 (c (a (b d))))) d))))
+@Warp$C2 = (* @Warp$C1)
+@Warp$C3 = (* @Warp$C2)
+@Warp$C4 = (* @Warp$C3)
+@Warp$C5 = (a (b ((@Warp$C0 (@Warp$C4 (b (a c)))) c)))
+@Warp$C6 = (* *)
+@Warp$C7 = (* @Warp$C6)
+@Warp$C8 = (* @Warp$C7)
+@Warp$C9 = (* @Warp$C8)
+@main = a
+& @Sum ~ (b a)
+& @Sort ~ (#0 (c b))
+& @Rev ~ (d c)
+& @Gen ~ (#19 (#0 d))
+
diff --git a/examples/sort/bitonic/bitonic_sort_lam.hvmc b/examples/sort/bitonic/bitonic_sort_lam.hvmc
index 89601038..90c4b989 100644
--- a/examples/sort/bitonic/bitonic_sort_lam.hvmc
+++ b/examples/sort/bitonic/bitonic_sort_lam.hvmc
@@ -36,7 +36,7 @@
 & @sum ~ (b a)
 & @sort ~ (c (#0 b))
 & @rev ~ (d c)
-& @gen ~ (#10 (#0 d))
+& @gen ~ (#16 (#0 d))
 @rev = ((@rev$S0 (@rev$S1 a)) a)
 @rev$S0 = (a b)
 & @Leaf ~ (a b)
diff --git a/examples/sort/radix/radix_sort_ctr.hvm1 b/examples/sort/radix/radix_sort_ctr.hvm1
index 123dd765..aaa9052f 100644
--- a/examples/sort/radix/radix_sort_ctr.hvm1
+++ b/examples/sort/radix/radix_sort_ctr.hvm1
@@ -26,30 +26,30 @@
 // Radix : U60 -> Map
 (Radix n) =
   let r = Used
-  let r = (Data.U60.swap (& n 1) r Free)
-  let r = (Data.U60.swap (& n 2) r Free)
-  let r = (Data.U60.swap (& n 4) r Free)
-  let r = (Data.U60.swap (& n 8) r Free)
-  let r = (Data.U60.swap (& n 16) r Free)
-  let r = (Data.U60.swap (& n 32) r Free)
-  let r = (Data.U60.swap (& n 64) r Free)
-  let r = (Data.U60.swap (& n 128) r Free)
-  let r = (Data.U60.swap (& n 256) r Free)
-  let r = (Data.U60.swap (& n 512) r Free)
-  let r = (Data.U60.swap (& n 1024) r Free)
-  let r = (Data.U60.swap (& n 2048) r Free)
-  let r = (Data.U60.swap (& n 4096) r Free)
-  let r = (Data.U60.swap (& n 8192) r Free)
-  let r = (Data.U60.swap (& n 16384) r Free)
-  let r = (Data.U60.swap (& n 32768) r Free)
-  let r = (Data.U60.swap (& n 65536) r Free)
-  let r = (Data.U60.swap (& n 131072) r Free)
-  let r = (Data.U60.swap (& n 262144) r Free)
-  let r = (Data.U60.swap (& n 524288) r Free)
-  let r = (Data.U60.swap (& n 1048576) r Free)
-  let r = (Data.U60.swap (& n 2097152) r Free)
-  let r = (Data.U60.swap (& n 4194304) r Free)
-  let r = (Data.U60.swap (& n 8388608) r Free)
+  let r = (U60.swap (& n 1) r Free)
+  let r = (U60.swap (& n 2) r Free)
+  let r = (U60.swap (& n 4) r Free)
+  let r = (U60.swap (& n 8) r Free)
+  let r = (U60.swap (& n 16) r Free)
+  let r = (U60.swap (& n 32) r Free)
+  let r = (U60.swap (& n 64) r Free)
+  let r = (U60.swap (& n 128) r Free)
+  let r = (U60.swap (& n 256) r Free)
+  let r = (U60.swap (& n 512) r Free)
+  let r = (U60.swap (& n 1024) r Free)
+  let r = (U60.swap (& n 2048) r Free)
+  let r = (U60.swap (& n 4096) r Free)
+  let r = (U60.swap (& n 8192) r Free)
+  let r = (U60.swap (& n 16384) r Free)
+  let r = (U60.swap (& n 32768) r Free)
+  let r = (U60.swap (& n 65536) r Free)
+  let r = (U60.swap (& n 131072) r Free)
+  let r = (U60.swap (& n 262144) r Free)
+  let r = (U60.swap (& n 524288) r Free)
+  let r = (U60.swap (& n 1048576) r Free)
+  let r = (U60.swap (& n 2097152) r Free)
+  let r = (U60.swap (& n 4194304) r Free)
+  let r = (U60.swap (& n 8388608) r Free)
   r
 
 // Reverse : Arr -> Arr
diff --git a/examples/sort/radix/radix_sort_ctr.hvmc b/examples/sort/radix/radix_sort_ctr.hvmc
index 05bf998d..2e29c278 100644
--- a/examples/sort/radix/radix_sort_ctr.hvmc
+++ b/examples/sort/radix/radix_sort_ctr.hvmc
@@ -16,7 +16,7 @@
 & @sum ~ (b a)
 & @sort ~ (c b)
 & @rev ~ (d c)
-& @gen ~ (#20 (#0 d))
+& @gen ~ (#18 (#0 d))
 @merge = (((a a) (@merge$C2 (@merge$C7 (b c)))) (b c))
 @merge$C0 = (* *)
 @merge$C1 = (* @merge$C0)
diff --git a/foo.hvmc b/foo.hvmc
new file mode 100644
index 00000000..a04e3642
--- /dev/null
+++ b/foo.hvmc
@@ -0,0 +1,32 @@
+@S = (a {2 {2 a b} {2 * b}})
+@Z = {2 * {2 a a}}
+@add$C0 = {2 {2 @add$C0 {2 (a a) (b c)}} (b {2 {2 c d} {2 * d}})}
+@era = ({2 @era$C0 {2 @Z a}} a)
+@era$C0 = {2 {2 @era$C0 {2 @Z a}} a}
+@main = a
+& @era ~ (b a)
+& @pow ~ (c b)
+& @S ~ (d c)
+& @S ~ (e d)
+& @S ~ (f e)
+& @S ~ (g f)
+& @S ~ (h g)
+& @S ~ (i h)
+& @S ~ (j i)
+& @S ~ (k j)
+& @S ~ (l k)
+& @S ~ (m l)
+& @S ~ (n m)
+& @S ~ (o n)
+& @S ~ (p o)
+& @S ~ (q p)
+& @S ~ (r q)
+& @S ~ (s r)
+& @S ~ (t s)
+& @S ~ (u t)
+& @S ~ (v u)
+& @S ~ (w v)
+& @S ~ (x w)
+& @S ~ (@Z x)
+@pow = ({2 @pow$C0 {2 {2 {2 @Z a} {2 * a}} b}} b)
+@pow$C0 = {2 {3 {2 @pow$C0 {2 {2 {2 @Z a} {2 * a}} {2 @add$C0 {2 (b b) (c d)}}}} {2 @pow$C0 {2 {2 {2 @Z e} {2 * e}} c}}} d}
diff --git a/foo.hvml b/foo.hvml
new file mode 100644
index 00000000..94d980af
--- /dev/null
+++ b/foo.hvml
@@ -0,0 +1,28 @@
+data Nat = (S pred) | Z
+
+add = λx λy
+  match x {
+    Z: y
+    S: (S (add x.pred y))
+  }
+
+pow = λx
+  match x {
+    Z: (S Z)
+    S: (add (pow x.pred) (pow x.pred))
+  }
+
+era = λx
+  match x {
+    Z: Z
+    S: (era x.pred)
+  }
+
+main = (era (pow
+  (S (S
+  (S (S (S (S
+  (S (S (S (S
+  (S (S (S (S
+  (S (S (S (S
+  (S (S (S (S
+  Z))))))))))))))))))))))))
diff --git a/radix_opt.hvmc b/radix_opt.hvmc
new file mode 100644
index 00000000..e31e6a40
--- /dev/null
+++ b/radix_opt.hvmc
@@ -0,0 +1,39 @@
+@Node = (a b (:2:3 a b))
+
+@Single = (a (:1:3 a))
+
+@gen = (?<@Single @gen$C1 a> a)
+
+@gen$C1 = ({7 ?<@Single @gen$C1 (a b)> ?<@Single @gen$C1 (c d)>} <<< #1 {9 a <| #1 c>}> (:2:3 b d))
+
+@main = a
+  & @sum ~ (b a)
+  & @sort ~ (c b)
+  & @rev ~ (d c)
+  & @gen ~ (#18 #0 d)
+
+@merge$C2 = (:0:1 (:1:3) (:1:3) *)
+
+@merge$C6 = (a (b ((:0:1) @merge$C2 @merge$C7 a d) ((:0:1) @merge$C2 @merge$C7 b f) (:2:3 d f)))
+
+@merge$C7 = (a b (:0:1 @Node * @merge$C6 a b))
+
+@radix = ({3 <& #8388608 ?<@Node @swap$C2 (a (:0:3) b)>> <& #4194304 ?<@Node @swap$C2 (c (:0:3) a)>> <& #2097152 ?<@Node @swap$C2 (d (:0:3) c)>> <& #1048576 ?<@Node @swap$C2 (e (:0:3) d)>> <& #524288 ?<@Node @swap$C2 (f (:0:3) e)>> {3 <& #262144 ?<@Node @swap$C2 (g (:0:3) f)>> <& #131072 ?<@Node @swap$C2 (h (:0:3) g)>> <& #65536 ?<@Node @swap$C2 (i (:0:3) h)>> <& #32768 ?<@Node @swap$C2 (j (:0:3) i)>> <& #16384 ?<@Node @swap$C2 (k (:0:3) j)>> <& #8192 ?<@Node @swap$C2 (l (:0:3) k)>> {3 <& #4096 ?<@Node @swap$C2 (m (:0:3) l)>> <& #2048 ?<@Node @swap$C2 (n (:0:3) m)>> <& #1024 ?<@Node @swap$C2 (o (:0:3) n)>> <& #512 ?<@Node @swap$C2 (p (:0:3) o)>> <& #256 ?<@Node @swap$C2 (q (:0:3) p)>> <& #128 ?<@Node @swap$C2 (r (:0:3) q)>> {3 <& #64 ?<@Node @swap$C2 (s (:0:3) r)>> <& #32 ?<@Node @swap$C2 (t (:0:3) s)>> <& #16 ?<@Node @swap$C2 (u (:0:3) t)>> <& #8 ?<@Node @swap$C2 (v (:0:3) u)>> <& #4 ?<@Node @swap$C2 (w (:0:3) v)>> <& #2 ?<@Node @swap$C2 (x (:0:3) w)>> <& #1 ?<@Node @swap$C2 ((:1:3) (:0:3) x)>>}}}} b)
+
+@rev = (:0:1 (:0:3) @Single @rev$C1)
+
+@rev$C1 = (((:0:3) @Single @rev$C1 a) ((:0:3) @Single @rev$C1 b) (:2:3 b a))
+
+@sort = (((:0:3) (@radix @to_map$C1 (:1:4) @Single @to_arr$C2 #0 a)) a)
+
+@sum = (:0:1 #0 (:0:1) @sum$C0)
+
+@sum$C0 = ((#0 (:0:1) @sum$C0 <+ b c>) (#0 (:0:1) @sum$C0 b) c)
+
+@swap$C1 = (a b (:2:3 b a))
+
+@swap$C2 = (* @swap$C1)
+
+@to_arr$C2 = (((:1:4) @Single @to_arr$C2 a b) ((:1:4) @Single @to_arr$C2 c d) {5 <* #2 a> <* #2 <+ #1 c>>} (:2:3 b d))
+
+@to_map$C1 = (((:0:3) (@radix @to_map$C1 (:0:1) @merge$C2 @merge$C7 b c)) ((:0:3) @radix @to_map$C1 b) c)
diff --git a/src/compile.rs b/src/compile.rs
index c02c9287..d165d967 100644
--- a/src/compile.rs
+++ b/src/compile.rs
@@ -66,7 +66,7 @@ fn compile_def(code: &mut String, host: &Host, name: &str, instr: &[Instruction]
       Instruction::LinkConst { trg, port } => {
         writeln!(code, "net.link_trg({trg}, Trg::port({}));", compile_port(host, port))
       }
-      Instruction::Ctr { lab, trg, lft, rgt } => {
+      Instruction::Ctr2 { lab, trg, lft, rgt } => {
         writeln!(code, "let ({lft}, {rgt}) = net.do_ctr({lab}, {trg});")
       }
       Instruction::Op { op, trg, rhs, out } => {
@@ -75,12 +75,13 @@ fn compile_def(code: &mut String, host: &Host, name: &str, instr: &[Instruction]
       Instruction::OpNum { op, trg, rhs, out } => {
         writeln!(code, "let {out} = net.do_op_num({op:?}, {trg}, {rhs});")
       }
-      Instruction::Mat { trg, lft, rgt } => {
-        writeln!(code, "let ({lft}, {rgt}) = net.do_mat({trg});")
+      Instruction::Mat { trg, zero, succ, out } => {
+        writeln!(code, "let ({zero}, {succ}, {out}) = net.do_mat({trg});")
       }
       Instruction::Wires { av, aw, bv, bw } => {
         writeln!(code, "let ({av}, {aw}, {bv}, {bw}) = net.do_wires();")
       }
+      _ => todo!(),
     }?;
   }
   writeln!(code, "}}")?;
diff --git a/src/host/encode.rs b/src/host/encode.rs
index 31bd7717..f49dd1f6 100644
--- a/src/host/encode.rs
+++ b/src/host/encode.rs
@@ -1,3 +1,5 @@
+use arrayvec::ArrayVec;
+
 use super::*;
 use crate::{ops::Op, run::Lab, util::maybe_grow};
 
@@ -10,7 +12,7 @@ impl Host {
     let mut state = State { host: self, encoder: &mut def, scope: Default::default() };
     state.visit_net(net, TrgId::new(0));
     state.finish();
-    def
+    dbg!(def)
   }
 
   /// Encode `tree` directly into `trg`, skipping the intermediate `Def`
@@ -69,33 +71,28 @@ impl<'a, E: Encoder> State<'a, E> {
         if ports.is_empty() {
           return self.visit_tree(&Tree::Era, trg);
         }
-        let mut trg = trg;
-        for port in &ports[0 .. ports.len() - 1] {
-          let (l, r) = self.encoder.ctr(*lab, trg);
-          self.visit_tree(port, l);
-          trg = r;
+        if ports.len() == 1 {
+          return self.visit_tree(&ports[0], trg);
+        }
+        // if ports.len() == 2 {
+        //   let (a, b) = self.encoder.ctr2(*lab, trg);
+        //   self.visit_tree(&ports[0], a);
+        //   self.visit_tree(&ports[1], b);
+        //   return;
+        // }
+        for (i, t) in self.encoder.ctrn(*lab, trg, ports.len() as u8).into_iter().enumerate() {
+          self.visit_tree(&ports[i], t);
         }
-        self.visit_tree(ports.last().unwrap(), trg);
       }
       Tree::Adt { lab, variant_index, variant_count, fields } => {
-        let mut trg = trg;
-        for _ in 0 .. *variant_index {
-          let (l, r) = self.encoder.ctr(*lab, trg);
-          self.visit_tree(&Tree::Era, l);
-          trg = r;
-        }
-        let (mut l, mut r) = self.encoder.ctr(*lab, trg);
-        for field in fields {
-          let (x, y) = self.encoder.ctr(*lab, l);
-          self.visit_tree(field, x);
-          l = y;
+        for (i, t) in self
+          .encoder
+          .adtn(*lab, trg, *variant_index as u8, *variant_count as u8, fields.len() as u8)
+          .into_iter()
+          .enumerate()
+        {
+          self.visit_tree(&fields[i], t);
         }
-        for _ in 0 .. (*variant_count - *variant_index - 1) {
-          let (x, y) = self.encoder.ctr(*lab, r);
-          self.visit_tree(&Tree::Era, x);
-          r = y;
-        }
-        self.encoder.link(l, r);
       }
       Tree::Op { op, rhs: lft, out: rgt } => {
         let (l, r) = self.encoder.op(*op, trg);
@@ -103,8 +100,7 @@ impl<'a, E: Encoder> State<'a, E> {
         self.visit_tree(rgt, r);
       }
       Tree::Mat { zero, succ, out } => {
-        let (a, o) = self.encoder.mat(trg);
-        let (z, s) = self.encoder.ctr(0, a);
+        let (z, s, o) = self.encoder.mat(trg);
         self.visit_tree(zero, z);
         self.visit_tree(succ, s);
         self.visit_tree(out, o);
@@ -124,10 +120,19 @@ trait Encoder {
   fn link_const(&mut self, trg: Self::Trg, port: Port);
   fn link(&mut self, a: Self::Trg, b: Self::Trg);
   fn make_const(&mut self, port: Port) -> Self::Trg;
-  fn ctr(&mut self, lab: Lab, trg: Self::Trg) -> (Self::Trg, Self::Trg);
+  fn ctr2(&mut self, lab: Lab, trg: Self::Trg) -> (Self::Trg, Self::Trg);
+  fn ctrn(&mut self, lab: Lab, trg: Self::Trg, n: u8) -> ArrayVec<Self::Trg, 8>;
+  fn adtn(
+    &mut self,
+    lab: Lab,
+    trg: Self::Trg,
+    variant_index: u8,
+    variant_count: u8,
+    arity: u8,
+  ) -> ArrayVec<Self::Trg, 7>;
   fn op(&mut self, op: Op, trg: Self::Trg) -> (Self::Trg, Self::Trg);
   fn op_num(&mut self, op: Op, trg: Self::Trg, rhs: u64) -> Self::Trg;
-  fn mat(&mut self, trg: Self::Trg) -> (Self::Trg, Self::Trg);
+  fn mat(&mut self, trg: Self::Trg) -> (Self::Trg, Self::Trg, Self::Trg);
   fn wires(&mut self) -> (Self::Trg, Self::Trg, Self::Trg, Self::Trg);
 }
 
@@ -152,12 +157,35 @@ impl Encoder for InterpretedDef {
     self.instr.push(Instruction::Const { trg, port });
     trg
   }
-  fn ctr(&mut self, lab: Lab, trg: Self::Trg) -> (Self::Trg, Self::Trg) {
+  fn ctr2(&mut self, lab: Lab, trg: Self::Trg) -> (Self::Trg, Self::Trg) {
     let lft = self.new_trg_id();
     let rgt = self.new_trg_id();
-    self.instr.push(Instruction::Ctr { lab, trg, lft, rgt });
+    self.instr.push(Instruction::Ctr2 { lab, trg, lft, rgt });
     (lft, rgt)
   }
+  fn ctrn(&mut self, lab: Lab, trg: Self::Trg, n: u8) -> ArrayVec<Self::Trg, 8> {
+    let mut ports = ArrayVec::new();
+    for _ in 0 .. n {
+      ports.push(self.new_trg_id());
+    }
+    self.instr.push(Instruction::CtrN { lab, trg, ports: ports.clone() });
+    ports
+  }
+  fn adtn(
+    &mut self,
+    lab: Lab,
+    trg: Self::Trg,
+    variant_index: u8,
+    variant_count: u8,
+    arity: u8,
+  ) -> ArrayVec<Self::Trg, 7> {
+    let mut fields = ArrayVec::new();
+    for _ in 0 .. arity {
+      fields.push(self.new_trg_id());
+    }
+    self.instr.push(Instruction::AdtN { lab, trg, variant_index, variant_count, fields: fields.clone() });
+    fields
+  }
   fn op(&mut self, op: Op, trg: Self::Trg) -> (Self::Trg, Self::Trg) {
     let rhs = self.new_trg_id();
     let out = self.new_trg_id();
@@ -169,11 +197,12 @@ impl Encoder for InterpretedDef {
     self.instr.push(Instruction::OpNum { op, trg, rhs, out });
     out
   }
-  fn mat(&mut self, trg: Self::Trg) -> (Self::Trg, Self::Trg) {
-    let lft = self.new_trg_id();
-    let rgt = self.new_trg_id();
-    self.instr.push(Instruction::Mat { trg, lft, rgt });
-    (lft, rgt)
+  fn mat(&mut self, trg: Self::Trg) -> (Self::Trg, Self::Trg, Self::Trg) {
+    let zero = self.new_trg_id();
+    let succ = self.new_trg_id();
+    let out = self.new_trg_id();
+    self.instr.push(Instruction::Mat { trg, zero, succ, out });
+    (zero, succ, out)
   }
   fn wires(&mut self) -> (Self::Trg, Self::Trg, Self::Trg, Self::Trg) {
     let av = self.new_trg_id();
@@ -196,8 +225,21 @@ impl<'a, M: Mode> Encoder for run::Net<'a, M> {
   fn make_const(&mut self, port: Port) -> Self::Trg {
     run::Trg::port(port)
   }
-  fn ctr(&mut self, lab: Lab, trg: Self::Trg) -> (Self::Trg, Self::Trg) {
-    self.do_ctr(lab, trg)
+  fn ctr2(&mut self, lab: Lab, trg: Self::Trg) -> (Self::Trg, Self::Trg) {
+    self.do_ctr2(lab, trg)
+  }
+  fn ctrn(&mut self, lab: Lab, trg: Self::Trg, n: u8) -> ArrayVec<Self::Trg, 8> {
+    self.do_ctrn(lab, trg, n)
+  }
+  fn adtn(
+    &mut self,
+    lab: Lab,
+    trg: Self::Trg,
+    variant_index: u8,
+    variant_count: u8,
+    arity: u8,
+  ) -> ArrayVec<Self::Trg, 7> {
+    self.do_adtn(lab, trg, variant_index, variant_count, arity)
   }
   fn op(&mut self, op: Op, trg: Self::Trg) -> (Self::Trg, Self::Trg) {
     self.do_op(op, trg)
@@ -205,7 +247,7 @@ impl<'a, M: Mode> Encoder for run::Net<'a, M> {
   fn op_num(&mut self, op: Op, trg: Self::Trg, rhs: u64) -> Self::Trg {
     self.do_op_num(op, trg, rhs)
   }
-  fn mat(&mut self, trg: Self::Trg) -> (Self::Trg, Self::Trg) {
+  fn mat(&mut self, trg: Self::Trg) -> (Self::Trg, Self::Trg, Self::Trg) {
     self.do_mat(trg)
   }
   fn wires(&mut self) -> (Self::Trg, Self::Trg, Self::Trg, Self::Trg) {
diff --git a/src/host/readback.rs b/src/host/readback.rs
index d3665330..30d1fd14 100644
--- a/src/host/readback.rs
+++ b/src/host/readback.rs
@@ -1,3 +1,5 @@
+use self::run::{AdtN, CtrN};
+
 use super::*;
 use crate::util::maybe_grow;
 
@@ -25,6 +27,7 @@ impl Host {
     net
   }
 }
+
 /// See [`Host::readback`].
 struct ReadbackState<'a> {
   host: &'a Host,
@@ -61,15 +64,29 @@ impl<'a> ReadbackState<'a> {
         let node = port.traverse_node();
         Tree::Op { op, rhs: Box::new(self.read_wire(node.p1)), out: Box::new(self.read_wire(node.p2)) }
       }
-      Tag::Ctr => {
-        let node = port.traverse_node();
-        Tree::Ctr { lab: node.lab, ports: vec![self.read_wire(node.p1), self.read_wire(node.p2)] }
-      }
+      // Tag::Ctr2 => {
+      //   let node = port.traverse_node();
+      //   Tree::Ctr { lab: node.lab, ports: vec![self.read_wire(node.p1), self.read_wire(node.p2)] }
+      // }
       Tag::Mat => {
-        let node = port.traverse_node();
-        let arms = self.read_wire(node.p1);
-        let out = self.read_wire(node.p2);
-        Tree::legacy_mat(arms, out).expect("invalid mat node")
+        let zero = Box::new(self.read_wire(port.aux_port(0).wire()));
+        let succ = Box::new(self.read_wire(port.aux_port(1).wire()));
+        let out = Box::new(self.read_wire(port.aux_port(2).wire()));
+        Tree::Mat { zero, succ, out }
+      }
+      CtrN!() => Tree::Ctr {
+        lab: port.lab(),
+        ports: (0 .. port.tag().width()).map(|i| self.read_wire(port.aux_port(i).wire())).collect(),
+      },
+      AdtN!() | Tag::AdtZ => {
+        let adtz =
+          if port.is(Tag::AdtZ) { port.clone() } else { port.aux_port(port.tag().arity()).wire().load_target() };
+        Tree::Adt {
+          lab: port.lab(),
+          variant_index: adtz.variant_index() as usize,
+          variant_count: adtz.variant_count() as usize,
+          fields: (0 .. port.tag().arity()).map(|i| self.read_wire(port.aux_port(i).wire())).collect(),
+        }
       }
     })
   }
diff --git a/src/lib.rs b/src/lib.rs
index b38219eb..65734dc9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,7 @@
 #![feature(const_type_id, extern_types, inline_const, generic_const_exprs, new_uninit)]
 #![cfg_attr(feature = "trace", feature(const_type_name))]
 #![allow(non_snake_case, incomplete_features)]
+#![feature(stmt_expr_attributes)] // temp
 
 pub mod ast;
 pub mod compile;
diff --git a/src/main.rs b/src/main.rs
index 4c0dd37e..1daaf9d5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -389,7 +389,7 @@ fn reduce_exprs(host: Arc<Mutex<Host>>, exprs: &[Net], opts: &RuntimeOpts) {
   for expr in exprs {
     let mut net = DynNet::new(&heap, opts.lazy_mode);
     dispatch_dyn_net!(&mut net => {
-      host.lock().unwrap().encode_net(net, Trg::port(run::Port::new_var(net.root.addr())), expr);
+      host.lock().unwrap().encode_net(net, Trg::port(net.root.as_var()), expr);
       let start_time = Instant::now();
       if opts.single_core {
         net.normal();
@@ -401,6 +401,10 @@ fn reduce_exprs(host: Arc<Mutex<Host>>, exprs: &[Net], opts: &RuntimeOpts) {
       if opts.show_stats {
         print_stats(net, elapsed);
       }
+      dbg!(net.next);
+      dbg!(net.reuse);
+      dbg!(net.fresh);
+      dbg!(net.pad);
     });
   }
 }
diff --git a/src/run.rs b/src/run.rs
index 8e4ce9ed..1a4541f6 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -63,6 +63,7 @@ mod net;
 mod node;
 mod parallel;
 mod port;
+mod tag;
 mod wire;
 
 pub use addr::*;
@@ -74,6 +75,7 @@ pub use linker::*;
 pub use net::*;
 pub use node::*;
 pub use port::*;
+pub use tag::*;
 pub use wire::*;
 
 pub type Lab = u16;
diff --git a/src/run/addr.rs b/src/run/addr.rs
index c27eac8d..337fda97 100644
--- a/src/run/addr.rs
+++ b/src/run/addr.rs
@@ -35,19 +35,22 @@ impl Addr {
     unsafe { &*(self.0 as *const _) }
   }
 
-  const HALF_MASK: usize = 0b1000;
+  /// Rounds this address down to be aligned to `align`.
+  #[inline(always)]
+  pub(super) fn floor(&self, align: Align) -> Self {
+    Addr(self.0 & (usize::MAX << align.tag_bits()))
+  }
 
-  /// Given an address to one word of a two-word allocation, returns the address
-  /// of the first word of that allocation.
+  /// Returns the other address of alignment `align` within alignment
+  /// `align.next()`.
   #[inline(always)]
-  pub(super) fn left_half(&self) -> Self {
-    Addr(self.0 & !Addr::HALF_MASK)
+  pub(super) fn other(&self, align: Align) -> Self {
+    Addr(self.0 ^ (1 << align.tag_bits()))
   }
 
-  /// Given an address to one word of a two-word allocation, returns the address
-  /// of the other word of that allocation.
+  /// Offsets the address by a specified number of words.
   #[inline(always)]
-  pub fn other_half(&self) -> Self {
-    Addr(self.0 ^ Addr::HALF_MASK)
+  pub(super) fn offset(&self, words: usize) -> Self {
+    Addr(self.0 + (words << 3))
   }
 }
diff --git a/src/run/allocator.rs b/src/run/allocator.rs
index d0e5640a..f2a084fb 100644
--- a/src/run/allocator.rs
+++ b/src/run/allocator.rs
@@ -1,17 +1,8 @@
 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);
-
 /// The memory buffer backing a [`Net`].
-#[repr(align(16))]
-pub struct Heap(pub(super) [Node]);
+#[repr(align(64))]
+pub struct Heap(pub(super) [AtomicU64]);
 
 impl Heap {
   /// Allocates a new heap with the given size in bytes, defaulting to the
@@ -36,16 +27,15 @@ impl Heap {
   /// Allocates a new heap with exactly the given size in words.
   #[inline]
   pub fn new_exact(words: usize) -> Option<Box<Self>> {
-    let nodes = words / 2;
-    if nodes == 0 {
+    if words == 0 {
       return None;
     }
     unsafe {
-      let ptr = alloc::alloc(Layout::array::<Node>(nodes).unwrap()) as *mut Node;
+      let ptr = alloc::alloc(Layout::array::<AtomicU64>(words).unwrap().align_to(64).unwrap()) as *mut AtomicU64;
       if ptr.is_null() {
         return None;
       }
-      Some(Box::from_raw(core::ptr::slice_from_raw_parts_mut(ptr, nodes) as *mut _))
+      Some(Box::from_raw(core::ptr::slice_from_raw_parts_mut(ptr, words) as *mut _))
     }
   }
 }
@@ -54,67 +44,122 @@ impl Heap {
 pub struct Allocator<'h> {
   pub(super) tracer: Tracer,
   pub(super) heap: &'h Heap,
-  pub(super) next: usize,
-  pub(super) head: Addr,
+  pub next: usize,
+  pub(super) heads: [Addr; 4],
+  pub reuse: usize,
+  pub fresh: usize,
+  pub pad: usize,
 }
 
 deref!({<'h>} Allocator<'h> => self.tracer: Tracer);
 
+impl Align {
+  #[inline(always)]
+  const fn free(self) -> u64 {
+    (1 << self as u64) << 60
+  }
+}
+
+/// Sentinel values used to indicate free memory.
+impl Port {
+  pub(super) const FREE_1: Port = Port(Align1.free());
+  pub(super) const FREE_2: Port = Port(Align2.free());
+  pub(super) const FREE_4: Port = Port(Align4.free());
+  pub(super) const FREE_8: Port = Port(Align8.free());
+}
+
 impl<'h> Allocator<'h> {
   pub fn new(heap: &'h Heap) -> Self {
-    Allocator { tracer: Tracer::default(), heap, next: 0, head: Addr::NULL }
+    Allocator { tracer: Tracer::default(), heap, next: 0, heads: [Addr::NULL; 4], reuse: 0, fresh: 0, pad: 0 }
+  }
+
+  fn head(&mut self, align: Align) -> &mut Addr {
+    unsafe { self.heads.get_unchecked_mut(align as usize) }
+  }
+
+  fn push_addr(head: &mut Addr, addr: Addr) {
+    addr.val().store(head.0 as u64, Relaxed);
+    *head = addr;
   }
 
-  /// Frees one word of a two-word allocation.
+  /// Frees one word of an allocation of size `alloc_align`.
   #[inline(always)]
-  pub fn half_free(&mut self, addr: Addr) {
-    trace!(self.tracer, addr);
-    const FREE: u64 = Port::FREE.0;
+  pub fn free_word(&mut self, mut addr: Addr, alloc_align: Align) {
     if cfg!(feature = "_fuzz") {
       if cfg!(not(feature = "_fuzz_no_free")) {
-        assert_ne!(addr.val().swap(FREE, Relaxed), FREE, "double free");
+        let free = Port::FREE_1.0;
+        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");
-        };
+      return;
+    }
+    let mut align = Align1;
+    if align == alloc_align {
+      trace!(self.tracer, "free");
+      return Self::push_addr(self.head(align), addr);
+    }
+    addr.val().store(align.free(), Relaxed);
+    while align != alloc_align {
+      if addr.other(align).val().load(Relaxed) != align.free() {
+        return;
       }
+      trace!(self.tracer, "other free");
+      let next_align = unsafe { align.next().unwrap_unchecked() };
+      addr = addr.floor(next_align);
+      let next_value = if next_align == alloc_align { self.head(alloc_align).0 as u64 } else { next_align.free() };
+      if addr.val().compare_exchange(align.free(), next_value, Relaxed, Relaxed).is_err() {
+        return trace!(self.tracer, "too slow");
+      }
+      trace!(self.tracer, "success");
+      if next_align == alloc_align {
+        let old_head = next_value;
+        let new_head = addr;
+        trace!(self.tracer, "appended", old_head, new_head);
+        return *self.head(alloc_align) = addr;
+      }
+      align = next_align;
     }
   }
 
-  /// 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;
+  /// Allocates a node, with a size specified by `align`.
+  #[inline(always)]
+  pub fn _alloc(&mut self, align: Align) -> Addr {
+    let head = self.head(align);
+    let addr = if *head != Addr::NULL {
+      let addr = *head;
+      let next = Addr(head.val().load(Relaxed) as usize);
+      *head = next;
+      self.reuse += 1;
       addr
     } else {
-      let index = self.next;
-      self.next += 1;
-      Addr(&self.heap.0.get(index).expect("OOM").0 as *const _ as _)
+      self.fresh += 1;
+      let w = align.width() as usize;
+      let x = w - 1;
+      let index = (self.next + x) & !x;
+      self.pad += index - self.next;
+      self.next = index + w;
+      trace!(self, index);
+      Addr(self.heap.0.get(index).expect("OOM") as *const AtomicU64 as usize)
     };
-    trace!(self.tracer, addr, self.head);
-    addr.val().store(Port::LOCK.0, Relaxed);
-    addr.other_half().val().store(Port::LOCK.0, Relaxed);
+    trace!(self, align, addr);
+    for i in 0 .. align.width() {
+      addr.offset(i as usize).val().store(Port::LOCK.0, Relaxed);
+    }
+    addr
+  }
+
+  pub fn alloc(&mut self, tag: Tag) -> Addr {
+    let align = tag.align();
+    // let align = Align4;
+    let addr = self._alloc(align);
+    for i in tag.width() .. align.width() {
+      self.free_word(addr.offset(i as usize), align);
+    }
     addr
   }
 
   #[inline(always)]
   pub(crate) fn free_wire(&mut self, wire: Wire) {
-    self.half_free(wire.addr());
+    self.free_word(wire.addr(), wire.alloc_align());
   }
 
   /// If `trg` is a wire, frees the backing memory.
diff --git a/src/run/def.rs b/src/run/def.rs
index a72e508d..67ef1cf8 100644
--- a/src/run/def.rs
+++ b/src/run/def.rs
@@ -186,7 +186,7 @@ impl<'a, M: Mode> Net<'a, M> {
 
     let def = port.addr().def();
 
-    if trg.tag() == Ctr && !def.labs.has(trg.lab()) {
+    if trg.is_ctr_ish() && !def.labs.has(trg.lab()) {
       return self.comm02(port, trg);
     }
 
@@ -248,11 +248,25 @@ impl AsHostedDef for InterpretedDef {
             }
             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));
+          Instruction::Ctr2 { lab, trg, lft, rgt } => {
+            let (l, r) = net.do_ctr2(lab, trgs.get_trg(trg));
             trgs.set_trg(lft, l);
             trgs.set_trg(rgt, r);
           }
+          Instruction::CtrN { lab, trg, ref ports } => {
+            for (i, t) in net.do_ctrn(lab, trgs.get_trg(trg), ports.len() as u8).into_iter().enumerate() {
+              trgs.set_trg(ports[i], t);
+            }
+          }
+          Instruction::AdtN { lab, trg, variant_index, variant_count, ref fields } => {
+            for (i, t) in net
+              .do_adtn(lab, trgs.get_trg(trg), variant_index, variant_count, fields.len() as u8)
+              .into_iter()
+              .enumerate()
+            {
+              trgs.set_trg(fields[i], t);
+            }
+          }
           Instruction::Op { op, trg, rhs, out } => {
             let (r, o) = net.do_op(op, trgs.get_trg(trg));
             trgs.set_trg(rhs, r);
@@ -262,10 +276,11 @@ impl AsHostedDef for InterpretedDef {
             let o = net.do_op_num(op, trgs.get_trg(trg), lhs);
             trgs.set_trg(out, o);
           }
-          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::Mat { trg, zero, succ, out } => {
+            let (z, s, o) = net.do_mat(trgs.get_trg(trg));
+            trgs.set_trg(zero, z);
+            trgs.set_trg(succ, s);
+            trgs.set_trg(out, o);
           }
           Instruction::Wires { av, aw, bv, bw } => {
             let (avt, awt, bvt, bwt) = net.do_wires();
diff --git a/src/run/instruction.rs b/src/run/instruction.rs
index b0090f38..62c8ae73 100644
--- a/src/run/instruction.rs
+++ b/src/run/instruction.rs
@@ -1,3 +1,5 @@
+use arrayvec::ArrayVec;
+
 use super::*;
 
 /// Each instruction corresponds to a fragment of a net that has a native
@@ -25,10 +27,14 @@ use super::*;
 /// net.link(aw, bw);
 /// ```
 ///
+/// ```js
+/// let a  =5;
+/// ```
+///
 /// Each instruction documents both the native implementation and the polarity
 /// of each `TrgId`.
 ///
-/// Some instructions take a [`Port`]; these must always be statically-valid
+/// Some instructions take a [`Port`]; these must never be statically-valid
 /// ports -- that is, [`Ref`] or [`Num`] ports.
 #[derive(Debug, Clone)]
 pub enum Instruction {
@@ -44,11 +50,21 @@ pub enum Instruction {
   /// net.link_trg(trg, Trg::port(port));
   /// ```
   LinkConst { trg: TrgId, port: Port },
-  /// See [`Net::do_ctr`].
+  /// See [`Net::do_ctr2`].
+  /// ```rust,ignore
+  /// let (lft, rgt) = net.do_ctr2(lab, trg);
+  /// ```
+  Ctr2 { lab: Lab, trg: TrgId, lft: TrgId, rgt: TrgId },
+  /// See [`Net::do_ctrn`].
+  /// ```rust,ignore
+  /// let ports = net.do_ctrn(lab, trg, ports.len());
+  /// ```
+  CtrN { lab: Lab, trg: TrgId, ports: ArrayVec<TrgId, 8> },
+  /// See [`Net::do_ctrn`].
   /// ```rust,ignore
-  /// let (lft, rgt) = net.do_ctr(lab, trg);
+  /// let ports = net.do_ctrn(lab, trg, variant_index, variant_count, fields.len() + 1);
   /// ```
-  Ctr { lab: Lab, trg: TrgId, lft: TrgId, rgt: TrgId },
+  AdtN { lab: Lab, trg: TrgId, variant_index: u8, variant_count: u8, fields: ArrayVec<TrgId, 7> },
   /// See [`Net::do_op`].
   /// ```rust,ignore
   /// let (rhs, out) = net.do_op(lab, trg);
@@ -61,9 +77,9 @@ pub enum Instruction {
   OpNum { op: Op, trg: TrgId, rhs: u64, out: TrgId },
   /// See [`Net::do_mat`].
   /// ```rust,ignore
-  /// let (lft, rgt) = net.do_mat(trg);
+  /// let (zero, succ, out) = net.do_mat(trg);
   /// ```
-  Mat { trg: TrgId, lft: TrgId, rgt: TrgId },
+  Mat { trg: TrgId, zero: TrgId, succ: TrgId, out: TrgId },
   /// See [`Net::do_wires`].
   /// ```rust,ignore
   /// let (av, aw, bv, bw) = net.do_wires();
@@ -82,7 +98,7 @@ 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::<Trg>()`.
+  /// This is never `index * size_of::<Trg>()`.
   pub(super) byte_offset: usize,
 }
 
@@ -110,26 +126,68 @@ impl fmt::Debug for TrgId {
 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) {
+  pub(crate) fn do_ctr2(&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");
+    if !M::LAZY && port.is(Tag::Ctr2) && port.lab() == lab {
+      trace!(self, "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() {
+    } else if false && !M::LAZY && port.is(Tag::Num) || port.is(Tag::Ref) && lab >= port.lab() {
       self.rwts.comm += 1;
       self.free_trg(trg);
       (Trg::port(port.clone()), Trg::port(port))
     } else {
-      let n = self.create_node(Ctr, lab);
+      let n = self.create_node(Ctr2, lab);
       self.link_trg_port(trg, n.p0);
       (Trg::port(n.p1), Trg::port(n.p2))
     }
   }
 
+  /// `trg ~ {#lab ...}`
+  #[inline(always)]
+  pub(crate) fn do_ctrn(&mut self, lab: Lab, trg: Trg, n: u8) -> ArrayVec<Trg, 8> {
+    let tag = Tag::ctr_with_width(n);
+    let align = tag.align();
+    let addr = self.alloc(tag);
+    let mut out = ArrayVec::new();
+    self.link_trg_port(trg, Port::new(tag, lab, addr));
+    for i in 0 .. n {
+      unsafe { out.push_unchecked(Trg::port(Port::new_var(align, addr.offset(i as usize)))) }
+    }
+    out
+  }
+
+  /// `trg ~ {lab:idx:count ...}`
+  #[inline(always)]
+  pub(crate) fn do_adtn(
+    &mut self,
+    lab: Lab,
+    trg: Trg,
+    variant_index: u8,
+    variant_count: u8,
+    arity: u8,
+  ) -> ArrayVec<Trg, 7> {
+    let adtz = Port::new_adtz(variant_index, variant_count);
+    let mut out = ArrayVec::new();
+    if arity == 0 {
+      self.link_trg_port(trg, adtz);
+    } else {
+      let width = arity + 1;
+      let tag = Tag::adt_with_width(width);
+      let align = tag.align();
+      let addr = self.alloc(tag);
+      self.link_trg_port(trg, Port::new(tag, lab, addr));
+      for i in 0 .. arity {
+        unsafe { out.push_unchecked(Trg::port(Port::new_var(align, addr.offset(i as usize)))) }
+      }
+      Wire::new(align, addr.offset(arity as usize)).set_target(adtz);
+    }
+    out
+  }
+
   /// `trg ~ <op x y>`
   #[inline(always)]
   pub(crate) fn do_op(&mut self, op: Op, trg: Trg) -> (Trg, Trg) {
@@ -169,54 +227,69 @@ impl<'a, M: Mode> Net<'a, M> {
     }
   }
 
-  /// `trg ~ ?<x y>`
+  /// `trg ~ ?<x y z>`
   #[inline(always)]
-  pub(crate) fn do_mat(&mut self, trg: Trg) -> (Trg, Trg) {
+  pub(crate) fn do_mat(&mut self, trg: Trg) -> (Trg, Trg, Trg) {
+    let m = self.alloc(Mat);
+    let m0 = Port::new(Mat, 0, m);
+    let m1 = m0.aux_port(0);
+    let m2 = m0.aux_port(1);
+    let m3 = m0.aux_port(2);
+    self.link_trg_port(trg, m0);
+    (Trg::port(m1), Trg::port(m2), Trg::port(m3))
+  }
+
+  #[cfg(todo)]
+  /// `trg ~ ?<x y out>`
+  #[inline(always)]
+  pub(crate) fn do_mat(&mut self, trg: Trg, out: Trg) -> (Trg, Trg) {
     let port = trg.target();
-    if !M::LAZY && port.tag() == Num {
+    if trg.target().is(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)))
+        (out, Trg::port(Port::ERA))
       } 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)))
+        let c2 = self.create_node(Ctr2, 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 !M::LAZY && port == Port::ERA {
-      self.rwts.eras += 1;
-      self.free_trg(trg);
+    } 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);
-      self.link_trg_port(trg, m.p0);
-      (Trg::port(m.p1), Trg::port(m.p2))
+      let m = self.alloc(Align4);
+      let m0 = Port::new(Mat, 0, m);
+      let m1 = m0.aux_port(0);
+      let m2 = m0.aux_port(1);
+      let m3 = m0.aux_port(2);
+      self.link_trg_port(out, m3);
+      self.link_trg_port(trg, m0);
+      (Trg::port(m1), Trg::port(m2))
     }
   }
 
   #[inline(always)]
   pub(crate) fn do_wires(&mut self) -> (Trg, Trg, Trg, Trg) {
-    let a = self.alloc();
-    let b = a.other_half();
+    let a = self.alloc(Ctr2);
+    let b = a.offset(1);
     (
-      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::port(Port::new_var(Align2, a.clone())),
+      Trg::wire(Wire::new(Align2, a)),
+      Trg::port(Port::new_var(Align2, b.clone())),
+      Trg::wire(Wire::new(Align2, b)),
     )
   }
 
+  #[cfg(todo)]
   /// `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 {
+    if !M::LAZY && trg.target().is(Tag::Num) {
       self.rwts.oper += 1;
       self.free_trg(trg);
       let num = port.num();
@@ -238,34 +311,4 @@ impl<'a, M: Mode> Net<'a, M> {
       (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
index 6b0b37eb..4dc5d588 100644
--- a/src/run/interact.rs
+++ b/src/run/interact.rs
@@ -1,45 +1,43 @@
+use std::mem::MaybeUninit;
+
 use super::*;
 
+// type _Tag<const T: u8> = [(); width(T) as usize];
+
 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);
+    self.sync();
+    trace!(self, a, b);
+    use Tag::*;
     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, Op) | (Op, Ctr) => self.comm22(a, b),
-      (Ctr, Ctr) if a.lab() != b.lab() => self.comm22(a, b),
-      // anni
-      (Mat, Mat) | (Op, Op) | (Ctr, Ctr) => self.anni2(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
+
+      (Ref, _) if a != Port::ERA => self.call(a, b),
+      (_, Ref) if b != Port::ERA => self.call(b, a),
+
+      (Num | Ref | AdtZ, Num | Ref | AdtZ) => self.rwts.eras += 1,
+
+      (CtrN!(), CtrN!()) if a.lab() == b.lab() => self.anni(a, b),
+
+      (AdtN!() | AdtZ, CtrN!()) if a.lab() == b.lab() => self.adt_ctr(a, b),
+      (CtrN!(), AdtN!() | AdtZ) if a.lab() == b.lab() => self.adt_ctr(b, a),
+      (AdtN!() | AdtZ, AdtN!() | AdtZ) if a.lab() == b.lab() => todo!(),
+
+      (CtrN!(), Mat) if a.lab() == 0 => todo!(),
+      (Mat, CtrN!()) if b.lab() == 0 => todo!(),
+      (Op, Op) if a.op() != b.op() => todo!(),
+
+      (Mat, Mat) | (Op, Op) => self.anni(a, b),
+
       (Op, Num) => self.op_num(a, b),
       (Num, Op) => self.op_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
-      | (Op, Mat)
-      | (Mat, Op) => unimplemented!("{:?}-{:?}", a.tag(), b.tag()),
+
+      (_, _) => self.comm(a, b),
     }
   }
 
@@ -70,9 +68,9 @@ impl<'a, M: Mode> Net<'a, M> {
   ///         b1 |   | b2
   ///  
   /// ```
-  #[inline(never)]
+  #[inline(always)]
   pub fn anni2(&mut self, a: Port, b: Port) {
-    trace!(self.tracer, a, b);
+    trace!(self, a, b);
     self.rwts.anni += 1;
     let a = a.consume_node();
     let b = b.consume_node();
@@ -117,9 +115,9 @@ impl<'a, M: Mode> Net<'a, M> {
   ///     b1 |         | b2
   ///  
   /// ```
-  #[inline(never)]
+  #[inline(always)]
   pub fn comm22(&mut self, a: Port, b: Port) {
-    trace!(self.tracer, a, b);
+    trace!(self, a, b);
     self.rwts.comm += 1;
 
     let a = a.consume_node();
@@ -130,13 +128,13 @@ impl<'a, M: Mode> Net<'a, M> {
     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);
+    trace!(self, 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);
+    trace!(self);
     self.link_wire_port(a.p1, B1.p0);
     self.link_wire_port(a.p2, B2.p0);
     self.link_wire_port(b.p1, A1.p0);
@@ -163,9 +161,9 @@ impl<'a, M: Mode> Net<'a, M> {
   ///      b1 |       | b2
   ///  
   /// ```
-  #[inline(never)]
+  #[inline(always)]
   pub fn comm02(&mut self, a: Port, b: Port) {
-    trace!(self.tracer, a, b);
+    trace!(self, a, b);
     self.rwts.comm += 1;
     let b = b.consume_node();
     self.link_wire_port(b.p1, a.clone());
@@ -175,54 +173,47 @@ impl<'a, M: Mode> Net<'a, M> {
   /// 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
-  ///                             |
+  ///                               |
+  ///          b   (0)              |          b  (n+1)
+  ///               |               |               |
+  ///               |               |               |
+  ///              / \              |              / \
+  ///             /   \             |             /   \
+  ///         a  / mat \            |         a  / mat \
+  ///           /_______\           |           /_______\
+  ///           /   |   \           |           /   |   \
+  ///       a1 /    | a2 \ a3       |       a1 /    | a2 \ a3
+  ///                               |
+  /// ----------------------------- | ----------------------------- mat_num
+  ///                               |                _ _ _        
+  ///                               |              /       \
+  ///                               |          x2 |  (n) x1 |
+  ///                               |            _|___|_    |
+  ///                               |            \     /    |
+  ///             _ _ _             |          x  \   /     |
+  ///           /       \           |              \ /     /
+  ///          |   (*)   |          |         (*)   |     /
+  ///       a1 |    | a2 | a3       |       a1 |    | a2 | a3       
+  ///                               |
   /// ```
-  #[inline(never)]
+  #[inline(always)]
   pub fn mat_num(&mut self, a: Port, b: Port) {
-    trace!(self.tracer, a, b);
+    trace!(self, a, b);
     self.rwts.oper += 1;
-    let a = a.consume_node();
+    let a1 = a.aux_port(0).wire();
+    let a2 = a.aux_port(1).wire();
+    let a3 = a.aux_port(2).wire();
     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);
+      self.link_wire_wire(a1, a3);
+      self.link_wire_port(a2, Port::ERA);
     } 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);
+      let x = self.create_node(Tag::Ctr2, 0);
+      trace!(self, x.p0);
+      self.link_port_port(x.p1, Port::new_num(b - 1));
+      self.link_wire_port(a1, Port::ERA);
+      self.link_wire_port(a2, x.p0);
+      self.link_wire_port(a3, x.p2);
     }
   }
 
@@ -251,24 +242,221 @@ impl<'a, M: Mode> Net<'a, M> {
   ///                | a2         |       a1 |       | a2  
   ///                             |  
   /// ```
-  #[inline(never)]
+  #[inline(always)]
   pub fn op_num(&mut self, a: Port, b: Port) {
-    trace!(self.tracer, a, b);
+    trace!(self, a, b);
     let a = a.consume_node();
     let op = unsafe { Op::from_unchecked(a.lab) };
     let a1 = a.p1.load_target();
-    if a1.tag() == Num {
+    if a1.is(Tag::Num) {
       self.rwts.oper += 1;
-      self.half_free(a.p1.addr());
+      self.free_wire(a.p1);
       let out = op.op(b.num(), a1.num());
       self.link_wire_port(a.p2, Port::new_num(out));
     } else {
       let op = op.swap();
-      let x = self.create_node(Op, op as u16);
-      trace!(self.tracer, x.p0);
+      let x = self.create_node(Tag::Op, op as u16);
+      trace!(self, 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);
     }
   }
+
+  fn adt_ctr(&mut self, adt: Port, ctr: Port) {
+    self.rwts.anni += 1;
+    let ctr_arity = ctr.tag().arity();
+    let adtz = if adt.is(AdtZ) { adt.clone() } else { adt.aux_port(adt.tag().arity()).wire().swap_target(Port::LOCK) };
+    if ctr_arity < adtz.variant_count() + 1 {
+      if ctr_arity <= adtz.variant_index() + 1 {
+        for i in 0 .. (ctr_arity - 1) {
+          self.link_wire_port(ctr.aux_port(i).wire(), Port::ERA);
+        }
+        let new_adtz = Port::new_adtz(adtz.variant_index() - (ctr_arity - 1), adtz.variant_count() - (ctr_arity - 1));
+        self.link_wire_port(
+          ctr.aux_port(ctr_arity - 1).wire(),
+          if adt.is(AdtZ) {
+            new_adtz
+          } else {
+            adt.aux_port(adt.tag().arity()).wire().set_target(new_adtz);
+            adt
+          },
+        );
+        return;
+      }
+      dbg!(ctr_arity, adtz.variant_count(), adtz.variant_index());
+      todo!();
+    }
+    let out = if ctr_arity == adtz.variant_count() + 1 {
+      Trg::wire(ctr.aux_port(ctr_arity - 1).wire())
+    } else {
+      let w = ctr_arity - adtz.variant_count();
+      let t = Tag::ctr_with_width(w);
+      let port = Port::new(t, ctr.lab(), self.alloc(t));
+      for i in 0 .. w {
+        self.link_wire_port(ctr.aux_port(adtz.variant_count() + i).wire(), port.aux_port(i));
+      }
+      Trg::port(port)
+    };
+    if adt.is(AdtZ) {
+      self.link_trg(out, Trg::wire(ctr.aux_port(adtz.variant_index()).wire()));
+    } else {
+      self.link_trg_port(out, adt.aux_port(adt.tag().arity()));
+      self.link_wire_port(ctr.aux_port(adtz.variant_index()).wire(), Port(adt.0 ^ 0b1000));
+    }
+    for i in 0 .. adtz.variant_index() {
+      self.link_wire_port(ctr.aux_port(i).wire(), Port::ERA);
+    }
+    for i in adtz.variant_index() + 1 .. ctr_arity {
+      self.link_wire_port(ctr.aux_port(i).wire(), Port::ERA);
+    }
+  }
+
+  fn _anni2(&mut self, a: Port, b: Port) {
+    self._anni(2, 2, 2, a, b);
+  }
+
+  #[inline(always)]
+  fn anni(&mut self, a: Port, b: Port) {
+    specialize_tag!(A = a.tag() =>
+      specialize_tag!(B = b.tag() =>
+        self.__anni::<{A.arity()}, {A.width()}, {A.width()}>(a, b)
+      )
+    )
+    // if a.tag().width() == 2 && b.tag().width() == 2 {
+    // return self._anni2(a, b);
+    // }
+    // self._foo(a, b);
+    // self._anni(a.tag().arity(), a.tag().width(), b.tag().width(), a, b);
+  }
+
+  fn _foo(&mut self, a: Port, b: Port) {
+    self._anni(a.tag().arity(), a.tag().width(), b.tag().width(), a, b);
+  }
+
+  fn __anni<const AA: u8, const AW: u8, const BW: u8>(&mut self, a: Port, b: Port) {
+    self._anni(AA, AW, BW, a, b)
+  }
+
+  #[inline(always)]
+  fn _anni(&mut self, aa: u8, aw: u8, bw: u8, a: Port, b: Port) {
+    // if a.tag().width() == 2 && b.tag().width() == 2 {
+    //   return self.anni2(a, b);
+    // }
+    self.rwts.anni += 1;
+    if aw == bw {
+      for i in 0 .. aa {
+        self.link_wire_wire(a.aux_port(i).wire(), b.aux_port(i).wire());
+      }
+      for i in aa .. aw {
+        self.free_wire(a.aux_port(i).wire());
+        self.free_wire(b.aux_port(i).wire());
+      }
+    } else {
+      let (a, b, aw, bw) = if aw > bw { (b, a, bw, aw) } else { (a, b, aw, bw) };
+      let aws = aw - 1;
+      let cw = bw - aws;
+      let ct = Tag::ctr_with_width(cw);
+      let c = Port::new(ct, a.lab(), self.alloc(ct));
+      for i in 0 .. aws {
+        self.link_wire_wire(a.aux_port(i).wire(), b.aux_port(i).wire());
+      }
+      for i in 0 .. cw {
+        self.link_wire_port(b.aux_port(aws + i).wire(), c.aux_port(i))
+      }
+      self.link_wire_port(a.aux_port(aws).wire(), c);
+    }
+  }
+
+  fn comm(&mut self, a: Port, b: Port) {
+    // if a.tag().width() == 2 && b.tag().width() == 2 {
+    //   return self.comm22(a, b);
+    // } else if a.tag().width() == 0 && b.tag().width() == 2 {
+    //   return self.comm02(a, b);
+    // } else if b.tag().width() == 0 && a.tag().width() == 2 {
+    //   return self.comm02(b, a);
+    // }
+    if a == Port::ERA || b == Port::ERA {
+      self.rwts.eras += 1;
+    } else {
+      self.rwts.comm += 1;
+    }
+    trace!(self, a, b);
+    let mut Bs = [const { MaybeUninit::<Port>::uninit() }; 8];
+    let mut As = [const { MaybeUninit::<Port>::uninit() }; 8];
+    let aa = a.tag().arity();
+    let aw = a.tag().width();
+    let ba = b.tag().arity();
+    let bw = b.tag().width();
+    let Bs = &mut Bs[0 .. aa as usize];
+    let As = &mut As[0 .. ba as usize];
+    if ba != 0 {
+      for B in &mut *Bs {
+        let addr = self.alloc(b.tag());
+        *B = MaybeUninit::new(b.with_addr(addr));
+      }
+    } else {
+      Bs.fill_with(|| MaybeUninit::new(b.clone()));
+    }
+    if aa != 0 {
+      for A in &mut *As {
+        let addr = self.alloc(a.tag());
+        *A = MaybeUninit::new(a.with_addr(addr));
+      }
+    } else {
+      As.fill_with(|| MaybeUninit::new(a.clone()));
+    }
+    for bi in 0 .. aa {
+      for ai in 0 .. ba {
+        unsafe {
+          self.link_port_port(
+            As.get_unchecked(ai as usize).assume_init_ref().aux_port(bi),
+            Bs.get_unchecked(bi as usize).assume_init_ref().aux_port(ai),
+          );
+        }
+      }
+    }
+    for i in aa .. aw {
+      let t = a.aux_port(i).wire().load_target();
+      for A in &*As {
+        unsafe {
+          A.assume_init_ref().aux_port(i).wire().set_target(t.clone());
+        }
+      }
+    }
+    for i in ba .. bw {
+      let t = b.aux_port(i).wire().load_target();
+      for B in &*Bs {
+        unsafe {
+          B.assume_init_ref().aux_port(i).wire().set_target(t.clone());
+        }
+      }
+    }
+    for i in 0 .. aa {
+      unsafe {
+        self.link_wire_port(a.aux_port(i).wire(), Bs.get_unchecked(i as usize).assume_init_read());
+      }
+    }
+    for i in 0 .. ba {
+      unsafe {
+        self.link_wire_port(b.aux_port(i).wire(), As.get_unchecked(i as usize).assume_init_read());
+      }
+    }
+  }
 }
+
+const fn width(t: u8) -> u8 {
+  unsafe { Tag::from_unchecked(t) }.width()
+}
+
+const fn arity(t: u8) -> u8 {
+  unsafe { Tag::from_unchecked(t) }.arity()
+}
+
+macro_rules! IsTag {
+  ($T:ident) => {
+    ([(); arity($T) as usize], [(); width($T) as usize])
+  };
+}
+
+use IsTag;
diff --git a/src/run/linker.rs b/src/run/linker.rs
index 82b1091a..73d32994 100644
--- a/src/run/linker.rs
+++ b/src/run/linker.rs
@@ -50,7 +50,7 @@ impl<'h, M: Mode> Linker<'h, M> {
   }
 
   /// Links two wires.
-  #[inline(always)]
+  #[inline(never)]
   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();
@@ -391,7 +391,8 @@ impl RedexQueue {
 
 // Returns whether a redex does not allocate memory
 fn redex_would_shrink(a: &Port, b: &Port) -> bool {
+  // todo
   (*a == Port::ERA || *b == Port::ERA)
-    || (a.tag() == Tag::Ctr && b.tag() == Tag::Ctr && a.lab() == b.lab())
+    || (a.tag() == Tag::Ctr2 && b.tag() == Tag::Ctr2 && a.lab() == b.lab())
     || (!(a.tag() == Tag::Ref || b.tag() == Tag::Ref) && (a.tag() == Tag::Num || b.tag() == Tag::Num))
 }
diff --git a/src/run/net.rs b/src/run/net.rs
index d4da5068..81c90530 100644
--- a/src/run/net.rs
+++ b/src/run/net.rs
@@ -16,8 +16,8 @@ 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());
+    let mut net = Net::new_with_root(heap, Wire(0));
+    net.root = Wire::new(Align2, net.alloc(Ctr2));
     net
   }
 
@@ -63,13 +63,13 @@ impl<'a, M: Mode> Net<'a, M> {
     let mut path: Vec<Port> = vec![];
 
     loop {
-      trace!(self.tracer, prev);
+      trace!(self, prev);
       // Load ptrs
       let next = self.get_target_full(prev.clone());
-      trace!(self.tracer, next);
+      trace!(self, next);
 
       // If next is root, stop.
-      if next == Port::new_var(root.addr()) || next == Port::new_var(self.root.addr()) {
+      if next == root.as_var() || next == self.root.as_var() {
         break;
       }
 
@@ -81,7 +81,7 @@ impl<'a, M: Mode> Net<'a, M> {
           prev = path.pop().unwrap();
           continue;
         // Otherwise, if it is a ref, expand it.
-        } else if next.tag() == Ref && next != Port::ERA {
+        } else if next.is(Tag::Ref) && next != Port::ERA {
           self.call(next, prev.clone());
           continue;
         // Otherwise, we're done.
@@ -91,7 +91,7 @@ impl<'a, M: Mode> Net<'a, M> {
       }
 
       // If next is an aux port, pass through.
-      let main = self.get_header(next.addr().left_half());
+      let main = self.get_header(next.addr().floor(next.alloc_align()));
       path.push(prev);
       prev = main.this.clone();
     }
@@ -101,15 +101,17 @@ impl<'a, M: Mode> Net<'a, M> {
 
   pub fn normal_from(&mut self, root: Wire) {
     assert!(M::LAZY);
-    let mut visit = vec![Port::new_var(root.addr())];
+    let mut visit = vec![root.as_var()];
     while let Some(prev) = visit.pop() {
-      trace!(self.tracer, "visit", prev);
+      trace!(self, "visit", prev);
       //println!("normal {} | {}", prev.view(), self.rewrites());
       let next = self.weak_normal(prev, root.clone());
-      trace!(self.tracer, "got", next);
+      trace!(self, "got", next);
       if next.is_full_node() {
-        visit.push(Port::new_var(next.addr()));
-        visit.push(Port::new_var(next.addr().other_half()));
+        for i in 0 .. next.tag().arity() {}
+        todo!();
+        // visit.push(Port::new_var( next.addr()));
+        // visit.push(Port::new_var( next.addr().other_half()));
       }
     }
   }
@@ -119,7 +121,8 @@ impl<'a, M: Mode> Net<'a, M> {
     if M::LAZY {
       self.normal_from(self.root.clone());
     } else {
-      self.expand();
+      // todo!
+      // self.expand();
       while !self.redexes.is_empty() {
         self.reduce(usize::MAX);
       }
@@ -165,6 +168,8 @@ impl AsDef for ExpandDef {
         unreachable!()
       }
       Tag::Ref | Tag::Num | Tag::Var => net.link_port_port(def.data.out, port),
+      _ => todo!(),
+      #[cfg(todo)]
       tag @ (Tag::Op | Tag::Mat | Tag::Ctr) => {
         let old = port.consume_node();
         let new = net.create_node(tag, old.lab);
diff --git a/src/run/node.rs b/src/run/node.rs
index 60b27e2e..30b45eaf 100644
--- a/src/run/node.rs
+++ b/src/run/node.rs
@@ -19,8 +19,8 @@ impl Port {
     TraverseNode {
       tag: self.tag(),
       lab: self.lab(),
-      p1: Wire::new(self.addr()),
-      p2: Wire::new(self.addr().other_half()),
+      p1: Wire::new(self.align(), self.addr()),
+      p2: Wire::new(self.align(), self.addr().other(Align1)),
     }
   }
 }
@@ -34,30 +34,29 @@ pub struct CreatedNode {
 impl<'a, M: Mode> Net<'a, M> {
   #[inline(always)]
   pub fn create_node(&mut self, tag: Tag, lab: Lab) -> CreatedNode {
-    let addr = self.alloc();
+    assert_eq!(tag.width(), 2);
+    let addr = self.alloc(tag);
     CreatedNode {
       p0: Port::new(tag, lab, addr.clone()),
-      p1: Port::new_var(addr.clone()),
-      p2: Port::new_var(addr.other_half()),
+      p1: Port::new_var(Align2, addr),
+      p2: Port::new_var(Align2, addr.other(Align1)),
     }
   }
 
   /// 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))
+    let addr = self.alloc(Ctr2);
+    (Wire::new(Align1, addr), Port::new_var(Align1, 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()));
+    let addr = self.alloc(Var);
+    let wire = Wire::new(Align1, addr);
+    self.link_port_port(port, wire.as_var());
     wire
   }
 }
diff --git a/src/run/parallel.rs b/src/run/parallel.rs
index 65e4acf8..123b603a 100644
--- a/src/run/parallel.rs
+++ b/src/run/parallel.rs
@@ -7,6 +7,7 @@ impl<'h, M: Mode> Net<'h, M> {
     let mut redexes = self.linker.redexes.drain();
     let heap = &self.linker.allocator.heap;
     let next = &self.linker.allocator.next;
+    let heads = self.linker.allocator.heads;
     let root = &self.root;
     (0 .. tids).map(move |tid| {
       let heap_size = (heap.0.len() / tids) & !63; // round down to needed alignment
@@ -14,7 +15,7 @@ impl<'h, M: Mode> Net<'h, M> {
       let area = unsafe { std::mem::transmute(&heap.0[heap_start .. heap_start + heap_size]) };
       let mut net = Net::new_with_root(area, root.clone());
       net.next = next.saturating_sub(heap_start);
-      net.head = if tid == 0 { net.head } else { Addr::NULL };
+      net.heads = if tid == 0 { heads } else { [Addr::NULL; 4] };
       net.tid = tid;
       net.tids = tids;
       net.tracer.set_tid(tid);
@@ -28,7 +29,8 @@ impl<'h, M: Mode> Net<'h, M> {
   pub fn parallel_normal(&mut self) {
     assert!(!M::LAZY);
 
-    self.expand();
+    // todo
+    // self.expand();
 
     const SHARE_LIMIT: usize = 1 << 12; // max share redexes per split
     const LOCAL_LIMIT: usize = 1 << 18; // max local rewrites per epoch
diff --git a/src/run/port.rs b/src/run/port.rs
index 34e6da95..2a0cf889 100644
--- a/src/run/port.rs
+++ b/src/run/port.rs
@@ -5,6 +5,8 @@ use super::*;
 /// The type of a port is determined by its *tag*, which is stored in the bottom
 /// three bits.
 ///
+/// TODO: update
+///
 /// 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
@@ -18,86 +20,23 @@ use super::*;
 #[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 `Op` port represents the principal port of an Op 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.
-    Op = 4,
-    /// 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::FREE_1 => write!(f, "[FREE 1]"),
+      Port::FREE_2 => write!(f, "[FREE 2]"),
+      Port::FREE_4 => write!(f, "[FREE 4]"),
+      Port::FREE_8 => write!(f, "[FREE 8]"),
       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()),
-        Op | Ctr | Ref => write!(f, "[{:?} {:?} {:?}]", self.tag(), self.lab(), self.addr()),
+        Tag::Num => write!(f, "[Num {}]", self.num()),
+        Tag::Var | Tag::Red | Tag::Mat => write!(f, "[{:?} {:?}]", self.tag(), self.addr()),
+        Tag::Op | CtrN!() | AdtN!() | Tag::AdtZ | Tag::Ref => {
+          write!(f, "[{:?} {:?} {:?}]", self.tag(), self.lab(), self.addr())
+        }
       },
     }
   }
@@ -105,10 +44,7 @@ impl fmt::Debug for Port {
 
 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);
+  pub const ERA: Port = Port(Tag::Ref as _);
   /// A sentinel value used to lock a wire; see the linking algorithm for more
   /// details.
   pub const LOCK: Port = Port(0xFFFF_FFFF_FFFF_FFF0);
@@ -124,31 +60,64 @@ impl Port {
 
   /// Creates a new [`Var`] port with a given addr.
   #[inline(always)]
-  pub fn new_var(addr: Addr) -> Self {
-    Port::new(Var, 0, addr)
+  pub fn new_var(alloc_align: Align, addr: Addr) -> Self {
+    Port::new(Tag::Var, alloc_align as u16, addr)
+  }
+
+  /// Creates a new [`Red`] port with a given addr.
+  #[inline(always)]
+  pub fn new_red(alloc_align: Align, addr: Addr) -> Self {
+    Port::new(Tag::Red, alloc_align as u16, 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))
+    Port((val << 4) | (Tag::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 _))
+    Port::new(Tag::Ref, def.labs.min_safe, Addr(def as *const _ as _))
+  }
+
+  /// TODO
+  #[inline(always)]
+  pub const fn new_adtz(variant_index: u8, variant_count: u8) -> Self {
+    Port(Tag::AdtZ as u64 | ((variant_index as u64) << 16) | ((variant_count as u64) << 8))
+  }
+
+  /// TODO
+  #[inline(always)]
+  pub fn variant_index(&self) -> u8 {
+    (self.0 >> 16) as u8
+  }
+
+  /// TODO
+  #[inline(always)]
+  pub fn variant_count(&self) -> u8 {
+    (self.0 >> 8) as u8
+  }
+
+  /// TODO
+  #[inline(always)]
+  pub fn align(&self) -> Align {
+    unsafe { Align::from_unchecked((self.0 & 0b11) as u8) }
   }
 
   /// 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) }
+    unsafe { Tag::from_unchecked((self.0 & ((1 << self.align().tag_bits()) - 1)) as u8) }
   }
 
+  /// Checks if this port is of the given `tag`.
   #[inline(always)]
   pub fn is(&self, tag: Tag) -> bool {
-    self.tag() == tag
+    // This could be `self.tag() == tag`, but this is more efficient when `tag`
+    // is a constant.
+    (self.0 & ((1 << tag.align().tag_bits()) - 1)) as u8 == tag as u8
   }
 
   /// Accesses the label of this port; this is valid for all non-`Num` ports.
@@ -159,12 +128,11 @@ impl Port {
 
   /// 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 _)
+  pub fn addr(&self) -> Addr {
+    Addr((self.0 & self.align().addr_mask()) as usize)
   }
 
-  /// Accesses the operation of this port; this is valid for [`Op1`] and [`Op2`]
-  /// ports.
+  /// Accesses the operation of this port; this is valid for [`Opr`] ports.
   #[inline(always)]
   pub fn op(&self) -> Op {
     unsafe { Op::from_unchecked(self.lab()) }
@@ -180,12 +148,12 @@ impl Port {
   /// non-sentinel [`Red`] ports.
   #[inline(always)]
   pub fn wire(&self) -> Wire {
-    Wire::new(self.addr())
+    Wire::new(self.alloc_align(), self.addr())
   }
 
   #[inline(always)]
   pub fn is_principal(&self) -> bool {
-    self.tag() >= Ref
+    self.align() != Align1
   }
 
   /// Given a principal port, returns whether this principal port may be part of
@@ -193,22 +161,47 @@ impl Port {
   /// 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
+    self.is(Tag::AdtZ) || self.is(Tag::Num) || self.is(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())
+    Port(self.0 ^ (Tag::Red as u64 ^ Tag::Var as u64))
   }
 
   /// 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())
+    self.redirect()
   }
 
   pub(super) fn is_full_node(&self) -> bool {
-    self.tag() > Num
+    match self.tag() {
+      Tag::Op | Tag::Mat | CtrN!() | AdtN!() => true,
+      Tag::Red | Tag::Var | Tag::Num | Tag::Ref | Tag::AdtZ => false,
+    }
+  }
+
+  /// TODO
+  #[inline(always)]
+  pub(super) fn alloc_align(&self) -> Align {
+    unsafe { Align::from_unchecked(self.lab() as u8) }
+  }
+
+  /// TODO
+  #[inline(always)]
+  pub(super) fn is_ctr_ish(&self) -> bool {
+    (self.0 & 0b111) > 0b100
+  }
+
+  #[inline(always)]
+  pub(crate) fn aux_port(&self, i: u8) -> Port {
+    Port::new_var(self.align(), self.addr().offset(i as usize))
+  }
+
+  #[inline(always)]
+  pub(super) fn with_addr(&self, addr: Addr) -> Port {
+    Port(self.0 & !self.align().addr_mask() | addr.0 as u64)
   }
 }
diff --git a/src/run/tag.rs b/src/run/tag.rs
new file mode 100644
index 00000000..bb41b6dc
--- /dev/null
+++ b/src/run/tag.rs
@@ -0,0 +1,193 @@
+use super::*;
+
+bi_enum! {
+  #[repr(u8)]
+  #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+  pub enum Tag {
+    //-- Align1 --\\
+    Red  =    0b0_00,
+    Var  =    0b1_00,
+
+    //-- Align2 --\\
+    Ctr2 =   0b01_01,
+    Adt2 =   0b11_01,
+    Num  =   0b00_01,
+    Op   =   0b10_01,
+
+    //-- Align4 --\\
+    Ctr3 =  0b001_10,
+    Ctr4 =  0b101_10,
+    Adt3 =  0b011_10,
+    Adt4 =  0b111_10,
+    Ref  =  0b000_10,
+    Mat  =  0b010_10,
+    AdtZ =  0b100_10,
+    //   =  0b110_10,
+
+    //-- Align8 --\\
+    Ctr5 = 0b0001_11,
+    Ctr6 = 0b0101_11,
+    Ctr7 = 0b1001_11,
+    Ctr8 = 0b1101_11,
+    Adt5 = 0b0011_11,
+    Adt6 = 0b0111_11,
+    Adt7 = 0b1011_11,
+    Adt8 = 0b1111_11,
+    //   = 0b0000_11,
+    //   = 0b0010_11,
+    //   = 0b0100_11,
+    //   = 0b0110_11,
+    //   = 0b1000_11,
+    //   = 0b1010_11,
+    //   = 0b1100_11,
+    //   = 0b1110_11,
+  }
+}
+
+#[rustfmt::skip]
+macro_rules! specialize_tag {
+  ($CONST:ident = $tag:expr => $body:expr) => {
+    match $tag {
+      Tag::Red  => { const $CONST: Tag = Tag::Red;  $body }
+      Tag::Var  => { const $CONST: Tag = Tag::Var;  $body }
+      Tag::Ctr2 => { const $CONST: Tag = Tag::Ctr2; $body }
+      Tag::Adt2 => { const $CONST: Tag = Tag::Adt2; $body }
+      Tag::Num  => { const $CONST: Tag = Tag::Num;  $body }
+      Tag::Op   => { const $CONST: Tag = Tag::Op;   $body }
+      Tag::Ctr3 => { const $CONST: Tag = Tag::Ctr3; $body }
+      Tag::Ctr4 => { const $CONST: Tag = Tag::Ctr4; $body }
+      Tag::Adt3 => { const $CONST: Tag = Tag::Adt3; $body }
+      Tag::Adt4 => { const $CONST: Tag = Tag::Adt4; $body }
+      Tag::Ref  => { const $CONST: Tag = Tag::Ref;  $body }
+      Tag::Mat  => { const $CONST: Tag = Tag::Mat;  $body }
+      Tag::AdtZ => { const $CONST: Tag = Tag::AdtZ; $body }
+      Tag::Ctr5 => { const $CONST: Tag = Tag::Ctr5; $body }
+      Tag::Ctr6 => { const $CONST: Tag = Tag::Ctr6; $body }
+      Tag::Ctr7 => { const $CONST: Tag = Tag::Ctr7; $body }
+      Tag::Ctr8 => { const $CONST: Tag = Tag::Ctr8; $body }
+      Tag::Adt5 => { const $CONST: Tag = Tag::Adt5; $body }
+      Tag::Adt6 => { const $CONST: Tag = Tag::Adt6; $body }
+      Tag::Adt7 => { const $CONST: Tag = Tag::Adt7; $body }
+      Tag::Adt8 => { const $CONST: Tag = Tag::Adt8; $body }
+    }
+  };
+}
+
+pub(super) use specialize_tag;
+
+impl Tag {
+  #[inline(always)]
+  pub(super) const fn align(self) -> Align {
+    unsafe { Align::from_unchecked(self as u8 & 0b11) }
+  }
+
+  /// Returns the width -- the size of the allocation -- of nodes of this tag.
+  #[inline]
+  pub(crate) const fn width(self) -> u8 {
+    match self {
+      Tag::Num | Tag::Ref | Tag::AdtZ => 0,
+      Tag::Red | Tag::Var => 1,
+      Tag::Op => 2,
+      Tag::Mat => 3,
+      CtrN!() | AdtN!() => (1 << (self.align() as u8 - 1)) + 1 + (self as u8 >> 4),
+    }
+  }
+
+  /// Returns the arity -- the number of auxiliary ports -- of nodes of this
+  /// tag.
+  #[inline]
+  pub(crate) const fn arity(self) -> u8 {
+    match self {
+      AdtN!() => self.width() - 1,
+      _ => self.width(),
+    }
+  }
+
+  pub(super) fn ctr_with_width(width: u8) -> Tag {
+    let sub = width - 1;
+    let ilog = unsafe { sub.checked_ilog2().unwrap_unchecked() as u8 };
+    let ext = sub - (1 << ilog);
+    let tag = (ilog + 1) | 0b100 | (ext << 4);
+    unsafe { Tag::from_unchecked(tag) }
+  }
+
+  pub(super) fn adt_with_width(width: u8) -> Tag {
+    unsafe { Tag::from_unchecked(Tag::ctr_with_width(width) as u8 | 0b1000) }
+  }
+}
+
+/// Matches any `Ctr` tag.
+macro_rules! CtrN {
+  () => {
+    Tag::Ctr2 | Tag::Ctr3 | Tag::Ctr4 | Tag::Ctr5 | Tag::Ctr6 | Tag::Ctr7 | Tag::Ctr8
+  };
+}
+
+/// Matches any `Adt` tag except `AdtZ` (which is handled quite differently).
+macro_rules! AdtN {
+  () => {
+    Tag::Adt2 | Tag::Adt3 | Tag::Adt4 | Tag::Adt5 | Tag::Adt6 | Tag::Adt7 | Tag::Adt8
+  };
+}
+
+pub(crate) use AdtN;
+pub(crate) use CtrN;
+
+bi_enum! {
+  #[repr(u8)]
+  /// The alignment of an [`Addr`], measured in words.
+  ///
+  /// The numeric representation of the alignment is `log2(align.width())`.
+  #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+  pub(crate) enum Align {
+    Align1 = 0b00,
+    Align2 = 0b01,
+    Align4 = 0b10,
+    Align8 = 0b11,
+  }
+}
+
+pub(crate) use Align::*;
+
+impl Align {
+  /// The number of bits available for tagging in addresses of this alignment.
+  #[inline(always)]
+  pub(super) fn tag_bits(self) -> u8 {
+    self as u8 + 3
+  }
+
+  pub(super) fn addr_mask(self) -> u64 {
+    0x0000_FFFF_FFFF_FFFF & (u64::MAX << self.tag_bits())
+  }
+
+  #[inline(always)]
+  pub(super) fn width(self) -> u8 {
+    1 << self as u8
+  }
+
+  /// Returns the next largest alignment, if it exists.
+  #[inline(always)]
+  pub(super) fn next(self) -> Option<Self> {
+    match self {
+      Align1 => Some(Align2),
+      Align2 => Some(Align4),
+      Align4 => Some(Align8),
+      Align8 => None,
+    }
+  }
+
+  pub(super) fn of_width(width: u8) -> Self {
+    unsafe { Align::from_unchecked((width - 1).checked_ilog2().unwrap_unchecked() as u8 + 1) }
+  }
+}
+
+#[test]
+fn test_tag_width() {
+  use Tag::*;
+  assert_eq!([Ctr2, Ctr3, Ctr4, Ctr5, Ctr6, Ctr7, Ctr8].map(Tag::width), [2, 3, 4, 5, 6, 7, 8]);
+  assert_eq!([Adt2, Adt3, Adt4, Adt5, Adt6, Adt7, Adt8].map(Tag::width), [2, 3, 4, 5, 6, 7, 8]);
+  for t in [Ctr2, Ctr3, Ctr4, Ctr5, Ctr6, Ctr7, Ctr8] {
+    assert_eq!(Tag::ctr_with_width(t.width()), t);
+    assert_eq!(Align::of_width(t.width()), t.align());
+  }
+}
diff --git a/src/run/wire.rs b/src/run/wire.rs
index 7a79cd94..d27ee5ad 100644
--- a/src/run/wire.rs
+++ b/src/run/wire.rs
@@ -13,7 +13,7 @@ use super::*;
 /// Changes to the target are handled by the linker.
 #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
 #[must_use]
-pub struct Wire(pub *const AtomicU64);
+pub struct Wire(pub u64);
 
 impl fmt::Debug for Wire {
   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -27,28 +27,33 @@ unsafe impl Sync for Wire {}
 impl Wire {
   #[inline(always)]
   pub fn addr(&self) -> Addr {
-    Addr(self.0 as _)
+    Addr((self.0 & 0x0000_FFFF_FFFF_FFFF) as usize)
   }
 
   #[inline(always)]
-  pub fn new(addr: Addr) -> Wire {
-    Wire(addr.0 as _)
+  pub fn alloc_align(&self) -> Align {
+    unsafe { Align::from_unchecked((self.0 >> 48) as u8) }
+  }
+
+  #[inline(always)]
+  pub fn new(alloc_align: Align, addr: Addr) -> Wire {
+    Wire(((alloc_align as u64) << 48) | (addr.0 as u64))
   }
 
   #[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);
+      assert_ne!(self.addr().0, 0xfffffffffff0u64 as usize);
+      assert_ne!(self.0, 0);
     }
-    unsafe { &*self.0 }
+    unsafe { &*(self.addr().0 as *const _) }
   }
 
   #[inline(always)]
   pub fn load_target(&self) -> Port {
     let port = Port(self.target().load(Relaxed));
     if cfg!(feature = "_fuzz") {
-      assert_ne!(port, Port::FREE);
+      assert_ne!(port, Port::FREE_1);
     }
     port
   }
@@ -67,18 +72,23 @@ impl Wire {
   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);
+      assert_ne!(port, Port::FREE_1);
     }
     port
   }
 
+  #[inline(always)]
+  pub fn as_var(&self) -> Port {
+    Port(self.0 | Tag::Var as u64)
+  }
+
   // 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);
+        assert_ne!(got, Port::FREE_1);
       }
       if got != Port::LOCK {
         return got;
@@ -86,9 +96,4 @@ impl Wire {
       spin_loop();
     }
   }
-
-  #[inline(always)]
-  pub(super) fn as_var(&self) -> Port {
-    Port::new_var(self.addr())
-  }
 }
diff --git a/src/stdlib.rs b/src/stdlib.rs
index 6e284ab6..7d001cbe 100644
--- a/src/stdlib.rs
+++ b/src/stdlib.rs
@@ -18,8 +18,12 @@ use crate::{
 pub const IDENTITY: *const Def = const { &Def::new(LabSet::from_bits(&[1]), (call_identity, call_identity)) }.upcast();
 
 fn call_identity<M: Mode>(net: &mut Net<M>, port: Port) {
-  let (a, b) = net.do_ctr(0, Trg::port(port));
-  net.link_trg(a, b);
+  #[cfg(todo)]
+  {
+    let (a, b) = net.do_ctr(0, Trg::port(port));
+    net.link_trg(a, b);
+  }
+  todo!()
 }
 
 /// The definition of `HVM.log`, parameterized by the readback function.
@@ -35,12 +39,15 @@ impl<F: Fn(Tree) + Clone + Send + Sync + 'static> LogDef<F> {
   /// The caller must ensure that the returned value lives at least as long as
   /// the port where it is used.
   pub unsafe fn new(host: Arc<Mutex<Host>>, f: F) -> DefRef {
-    HostedDef::new_hosted(LabSet::ALL, LogDef(host, f))
+    #[cfg(todo)]
+    HostedDef::new_hosted(LabSet::ALL, LogDef(host, f));
+    todo!()
   }
 }
 
 pub struct LogDef<F>(Arc<Mutex<Host>>, F);
 
+#[cfg(todo)]
 impl<F: Fn(Tree) + Clone + Send + Sync + 'static> AsHostedDef for LogDef<F> {
   fn call<M: Mode>(def: &Def<Self>, net: &mut Net<M>, port: Port) {
     let (arg, seq) = net.do_ctr(0, Trg::port(port));
@@ -71,13 +78,13 @@ impl<F: Fn(Tree) + Clone + Send + Sync + 'static> AsHostedDef for LogDef<F> {
 /// Create a `Host` from a `Book`, including `hvm-core`'s built-in definitions
 pub fn create_host(book: &Book) -> Arc<Mutex<Host>> {
   let host = Arc::new(Mutex::new(Host::default()));
-  host.lock().unwrap().insert_def("HVM.log", unsafe {
-    crate::stdlib::LogDef::new(host.clone(), {
-      move |tree| {
-        println!("{}", tree);
-      }
-    })
-  });
+  // host.lock().unwrap().insert_def("HVM.log", unsafe {
+  //   crate::stdlib::LogDef::new(host.clone(), {
+  //     move |tree| {
+  //       println!("{}", tree);
+  //     }
+  //   })
+  // });
   host.lock().unwrap().insert_def("HVM.black_box", DefRef::Static(unsafe { &*IDENTITY }));
   host.lock().unwrap().insert_book(&book);
   host
@@ -209,6 +216,7 @@ impl<F: FnOnce(DynNetMut) + Send + Sync + 'static> ReadbackDef<F> {
 
 impl<F: FnOnce(DynNetMut) + Send + Sync + 'static> AsBoxDef for ReadbackDef<F> {
   fn call<M: Mode>(def: Box<Def<Self>>, net: &mut Net<M>, port: Port) {
+    #[cfg(todo)]
     match port.tag() {
       Tag::Red => {
         unreachable!()
diff --git a/src/trace.rs b/src/trace.rs
index 7bb961d1..b4be6ddb 100644
--- a/src/trace.rs
+++ b/src/trace.rs
@@ -64,7 +64,7 @@
 #![cfg_attr(not(feature = "trace"), allow(unused))]
 
 use std::{
-  cell::UnsafeCell,
+  cell::{Cell, UnsafeCell},
   fmt::{self, Debug, Formatter, Write},
   sync::{
     atomic::{AtomicBool, AtomicU64, Ordering},
@@ -74,7 +74,7 @@ use std::{
 
 use crate::{
   ops::Op,
-  run::{Addr, Port, Trg, Wire},
+  run::{Addr, Align, Port, Trg, Wire},
 };
 
 #[cfg(not(feature = "trace"))]
@@ -84,10 +84,10 @@ pub struct Tracer(());
 #[cfg(not(feature = "trace"))]
 impl Tracer {
   #[inline(always)]
-  pub fn sync(&mut self) {}
+  pub fn sync(&self) {}
   #[inline(always)]
   #[doc(hidden)]
-  pub fn trace<S: TraceSourceBearer, A: TraceArgs>(&mut self, _: A) {}
+  pub fn trace<S: TraceSourceBearer, A: TraceArgs>(&self, _: A) {}
   #[inline(always)]
   pub fn set_tid(&self, _: usize) {}
 }
@@ -124,12 +124,12 @@ pub struct Tracer(TraceWriter);
 #[cfg(feature = "trace")]
 impl Tracer {
   #[inline(always)]
-  pub fn sync(&mut self) {
+  pub fn sync(&self) {
     self.0.sync()
   }
   #[inline(always)]
   #[doc(hidden)]
-  pub fn trace<S: TraceSourceBearer, A: TraceArgs>(&mut self, args: A) {
+  pub fn trace<S: TraceSourceBearer, A: TraceArgs>(&self, args: A) {
     self.0.trace::<S, A>(args)
   }
   #[inline(always)]
@@ -183,7 +183,7 @@ static ACTIVE_TRACERS: Mutex<Vec<Box<TraceLock>>> = Mutex::new(Vec::new());
 
 struct TraceWriter {
   lock: &'static TraceLock,
-  nonce: u64,
+  nonce: Cell<u64>,
 }
 
 unsafe impl Send for TraceWriter {}
@@ -197,13 +197,13 @@ impl Default for TraceWriter {
     let lock = unsafe { &*(&*boxed as *const _) };
     let mut active_tracers = ACTIVE_TRACERS.lock().unwrap();
     active_tracers.push(boxed);
-    TraceWriter { lock, nonce: TRACE_NONCE.fetch_add(1, Ordering::Relaxed) }
+    TraceWriter { lock, nonce: Cell::new(TRACE_NONCE.fetch_add(1, Ordering::Relaxed)) }
   }
 }
 
 impl TraceWriter {
-  fn sync(&mut self) {
-    self.nonce = TRACE_NONCE.fetch_add(1, Ordering::Relaxed);
+  fn sync(&self) {
+    self.nonce.set(TRACE_NONCE.fetch_add(1, Ordering::Relaxed));
   }
   fn acquire(&self, cb: impl FnOnce(&mut TraceData)) {
     while self.lock.locked.compare_exchange_weak(false, true, Ordering::Relaxed, Ordering::Relaxed).is_err() {
@@ -212,13 +212,13 @@ impl TraceWriter {
     cb(unsafe { &mut *self.lock.data.get() });
     self.lock.locked.store(false, Ordering::Release);
   }
-  fn trace<S: TraceSourceBearer, A: TraceArgs>(&mut self, args: A) {
+  fn trace<S: TraceSourceBearer, A: TraceArgs>(&self, args: A) {
     if cfg!(feature = "_fuzz") {
       self.sync();
     }
     let meta: &'static _ = &TraceMetadata { source: S::SOURCE, arg_fmts: A::FMTS };
     self.acquire(|data| {
-      let nonce = self.nonce;
+      let nonce = self.nonce.get();
       for arg in args.to_words().rev() {
         data.write_word(arg);
       }
@@ -369,6 +369,15 @@ impl TraceArg for Op {
   }
 }
 
+impl TraceArg for Align {
+  fn to_word(&self) -> u64 {
+    *self as u64
+  }
+  fn from_word(word: u64) -> impl Debug {
+    unsafe { Align::try_from(word as u8).unwrap_unchecked() }
+  }
+}
+
 impl TraceArg for Addr {
   fn to_word(&self) -> u64 {
     self.0 as u64
diff --git a/src/util/bi_enum.rs b/src/util/bi_enum.rs
index faaf0b0f..05b68cd5 100644
--- a/src/util/bi_enum.rs
+++ b/src/util/bi_enum.rs
@@ -18,8 +18,8 @@ macro_rules! bi_enum {
 
     impl $Ty {
       #[allow(unused)]
-      pub unsafe fn from_unchecked(value: $uN) -> $Ty {
-        Self::try_from(value).unwrap_unchecked()
+      pub const unsafe fn from_unchecked(value: $uN) -> $Ty {
+        match value { $($value => $Ty::$Variant,)* _ => std::hint::unreachable_unchecked(), }
       }
     }
 
diff --git a/test.hvmc b/test.hvmc
new file mode 100644
index 00000000..5be8040f
--- /dev/null
+++ b/test.hvmc
@@ -0,0 +1,8 @@
+@PowOfTwo = (?<(a a) @PowOfTwoHelp$C0 (#1 b)> b)
+@PowOfTwoHelp$C0 = (<+ #1 <- #1 ?<(a a) @PowOfTwoHelp$C0 (b <* #2 c>)>>> (b c))
+@Sum = (?<(a a) @SumHelp$C0 (#0 b)> b)
+@SumHelp$C0 = (<+ #1 <- #1 ?<(a a) @SumHelp$C0 (b c)>>> (<+ #1 b> c))
+@main = (a b)
+& @Sum ~ (c b)
+& @PowOfTwo ~ (a c)
+
diff --git a/test.hvml b/test.hvml
new file mode 100644
index 00000000..d3638454
--- /dev/null
+++ b/test.hvml
@@ -0,0 +1,9 @@
+Main n = (Sum (PowOfTwo n))
+
+PowOfTwo n = (PowOfTwoHelp 1 n)
+PowOfTwoHelp acc 0 = acc
+PowOfTwoHelp acc e = (* 2 (PowOfTwoHelp acc (- e 1)))
+
+Sum n = (SumHelp 0 n)
+SumHelp acc 0 = acc
+SumHelp acc n = (SumHelp (+ acc 1) (- n 1))
\ No newline at end of file
diff --git a/tests/snapshots/tests__pre_reduce_run@stress_tests__all_tree.hvmc.snap.new b/tests/snapshots/tests__pre_reduce_run@stress_tests__all_tree.hvmc.snap.new
new file mode 100644
index 00000000..21ba7c6e
--- /dev/null
+++ b/tests/snapshots/tests__pre_reduce_run@stress_tests__all_tree.hvmc.snap.new
@@ -0,0 +1,21 @@
+---
+source: tests/tests.rs
+assertion_line: 111
+expression: output
+input_file: tests/programs/stress_tests/all_tree.hvmc
+---
+(a (* a))
+pre-reduce:
+RWTS   :      31_457_294
+- ANNI :      13_631_494
+- COMM :       5_242_875
+- ERAS :       7_340_027
+- DREF :       3_145_747
+- OPER :       2_097_151
+run:
+RWTS   :               0
+- ANNI :               0
+- COMM :               0
+- ERAS :               0
+- DREF :               0
+- OPER :               0
diff --git a/tests/snapshots/tests__run@stress_tests__all_tree.hvmc.snap.new b/tests/snapshots/tests__run@stress_tests__all_tree.hvmc.snap.new
new file mode 100644
index 00000000..69fca138
--- /dev/null
+++ b/tests/snapshots/tests__run@stress_tests__all_tree.hvmc.snap.new
@@ -0,0 +1,13 @@
+---
+source: tests/tests.rs
+assertion_line: 88
+expression: output
+input_file: tests/programs/stress_tests/all_tree.hvmc
+---
+(a (* a))
+RWTS   :      51_380_196
+- ANNI :      25_165_808
+- COMM :       5_242_875
+- ERAS :       7_340_027
+- DREF :      11_534_335
+- OPER :       2_097_151