├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .rustfmt.toml ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── TODO.md └── src ├── lib.rs ├── lower.rs └── rvsdg.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: nightly 15 | override: true 16 | - name: Build 17 | run: cargo build --verbose 18 | - name: Run tests 19 | run: cargo test --verbose 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | /target/ 35 | **/*.rs.bk 36 | Cargo.lock 37 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | hard_tabs = false 3 | tab_spaces = 4 4 | newline_style = "Auto" 5 | use_small_heuristics = "Default" 6 | indent_style = "Block" 7 | wrap_comments = true 8 | format_code_in_doc_comments = true 9 | comment_width = 80 10 | normalize_comments = true 11 | normalize_doc_attributes = true 12 | format_strings = true 13 | format_macro_matchers = true 14 | format_macro_bodies = true 15 | empty_item_single_line = true 16 | struct_lit_single_line = true 17 | fn_single_line = false 18 | where_single_line = false 19 | imports_indent = "Block" 20 | imports_layout = "Mixed" 21 | merge_imports = true 22 | reorder_imports = true 23 | reorder_modules = true 24 | reorder_impl_items = true 25 | type_punctuation_density = "Wide" 26 | space_before_colon = false 27 | space_after_colon = true 28 | spaces_around_ranges = false 29 | binop_separator = "Front" 30 | remove_nested_parens = true 31 | combine_control_expr = true 32 | overflow_delimited_expr = false 33 | struct_field_align_threshold = 0 34 | enum_discrim_align_threshold = 0 35 | match_arm_blocks = true 36 | force_multiline_blocks = false 37 | fn_args_layout = "Tall" 38 | brace_style = "SameLineWhere" 39 | control_brace_style = "AlwaysSameLine" 40 | trailing_semicolon = true 41 | trailing_comma = "Vertical" 42 | match_block_trailing_comma = false 43 | blank_lines_upper_bound = 1 44 | blank_lines_lower_bound = 0 45 | edition = "2018" 46 | version = "One" 47 | inline_attribute_width = 0 48 | merge_derives = true 49 | use_try_shorthand = false 50 | use_field_init_shorthand = false 51 | force_explicit_abi = true 52 | condense_wildcard_suffixes = false 53 | color = "Auto" 54 | required_version = "1.4.9" 55 | unstable_features = false 56 | disable_all_formatting = false 57 | skip_children = false 58 | hide_parse_errors = false 59 | error_on_line_overflow = false 60 | error_on_unformatted = false 61 | report_todo = "Never" 62 | report_fixme = "Never" 63 | ignore = [] 64 | emit_mode = "Files" 65 | make_backup = false 66 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | cache: cargo 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oxide" 3 | version = "0.1.0" 4 | authors = ["Mário Feroldi "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | smallvec = "0.6.10" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mário Feroldi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Oxide 2 | 3 | [![Build Status](https://travis-ci.org/feroldi/oxide.svg?branch=master)](https://travis-ci.org/feroldi/oxide) 4 | 5 | Oxide is an implementation of the Regionalized Value State Dependence Graph as an intermediate representation for multiple compiling stages. 6 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | Maybe have a fixed set of target-independent operations, but provide the ability to implement the mapping to other IRs. 4 | 5 | Have nodes be immutable data, and the graph is a vector of edges (inputs, outputs etc) and mutate that. 6 | Or even no need to mutate, just create a new graph reconnecting existing nodes. 7 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(hash_raw_entry)] 2 | 3 | mod rvsdg; 4 | mod lower; 5 | -------------------------------------------------------------------------------- /src/lower.rs: -------------------------------------------------------------------------------- 1 | use crate::rvsdg::{Node, NodeCtxt, NodeId, NodeKind, Sig, SigS, ValOrigin}; 2 | use std::{collections::HashMap, hash::Hash}; 3 | 4 | trait Lower<'g, 'h: 'g, S: Sig, T: Sig> { 5 | fn lower(&mut self, node: Node<'h, S>, ncx: &'g NodeCtxt) -> Node<'g, T>; 6 | } 7 | 8 | #[derive(Clone, PartialEq, Eq, Hash, Debug)] 9 | enum Hir { 10 | I32(i32), 11 | Usize(usize), 12 | Array(Vec), 13 | GlobalState, 14 | Subscript, 15 | Add, 16 | Sub, 17 | Mul, 18 | Div, 19 | Le, 20 | Lt, 21 | Ge, 22 | Gt, 23 | And, 24 | Or, 25 | Xor, 26 | Not, 27 | Neg, 28 | Shl, 29 | Shr, 30 | Mod, 31 | } 32 | 33 | impl Sig for Hir { 34 | fn sig(&self) -> SigS { 35 | match self { 36 | Hir::I32(..) | Hir::Usize(..) | Hir::Array(..) => SigS { 37 | val_outs: 1, 38 | ..SigS::default() 39 | }, 40 | Hir::GlobalState => SigS { 41 | st_outs: 1, 42 | ..SigS::default() 43 | }, 44 | Hir::Add 45 | | Hir::Subscript 46 | | Hir::Mul 47 | | Hir::Sub 48 | | Hir::Div 49 | | Hir::Le 50 | | Hir::Lt 51 | | Hir::Ge 52 | | Hir::Gt 53 | | Hir::And 54 | | Hir::Or 55 | | Hir::Xor 56 | | Hir::Shl 57 | | Hir::Shr 58 | | Hir::Mod => SigS { 59 | val_ins: 2, 60 | val_outs: 1, 61 | ..SigS::default() 62 | }, 63 | Hir::Not | Hir::Neg => SigS { 64 | val_ins: 1, 65 | val_outs: 1, 66 | ..SigS::default() 67 | }, 68 | } 69 | } 70 | } 71 | 72 | #[derive(Clone, PartialEq, Eq, Hash, Debug)] 73 | enum Lir { 74 | I32(i32), 75 | Usize(usize), 76 | Alloc, 77 | Free, 78 | GlobalState, 79 | Load, 80 | Store, 81 | Add, 82 | Sub, 83 | Mul, 84 | DivMod, 85 | Cmp, 86 | BitAnd, 87 | BitOr, 88 | BitXor, 89 | BitNot, 90 | Shl, 91 | Shr, 92 | Inc, 93 | Dec, 94 | Merge { 95 | num_of_values: usize, 96 | num_of_states: usize, 97 | }, 98 | } 99 | 100 | impl Sig for Lir { 101 | fn sig(&self) -> SigS { 102 | match self { 103 | Lir::I32(..) | Lir::Usize(..) => SigS { 104 | val_outs: 1, 105 | ..SigS::default() 106 | }, 107 | Lir::Alloc => SigS { 108 | val_ins: 1, 109 | val_outs: 1, 110 | st_outs: 1, 111 | ..SigS::default() 112 | }, 113 | Lir::Free => SigS { 114 | val_ins: 1, 115 | ..SigS::default() 116 | }, 117 | Lir::GlobalState => SigS { 118 | st_outs: 1, 119 | ..SigS::default() 120 | }, 121 | Lir::Add 122 | | Lir::Mul 123 | | Lir::Sub 124 | | Lir::Cmp 125 | | Lir::BitAnd 126 | | Lir::BitOr 127 | | Lir::BitXor 128 | | Lir::Shl 129 | | Lir::Shr 130 | | Lir::BitNot => SigS { 131 | val_ins: 2, 132 | val_outs: 1, 133 | ..SigS::default() 134 | }, 135 | Lir::DivMod => SigS { 136 | val_ins: 2, 137 | val_outs: 2, 138 | ..SigS::default() 139 | }, 140 | Lir::Inc | Lir::Dec => SigS { 141 | val_ins: 1, 142 | val_outs: 1, 143 | ..SigS::default() 144 | }, 145 | Lir::Load => SigS { 146 | val_ins: 1, 147 | st_ins: 1, 148 | val_outs: 1, 149 | ..SigS::default() 150 | }, 151 | Lir::Store => SigS { 152 | val_ins: 2, 153 | st_ins: 1, 154 | st_outs: 1, 155 | ..SigS::default() 156 | }, 157 | Lir::Merge { 158 | num_of_values, 159 | num_of_states, 160 | } => SigS { 161 | val_ins: *num_of_values, 162 | val_outs: *num_of_values, 163 | st_ins: *num_of_states, 164 | st_outs: 1, 165 | ..SigS::default() 166 | }, 167 | } 168 | } 169 | } 170 | 171 | struct HirToLir { 172 | visited: HashMap, 173 | } 174 | 175 | impl HirToLir { 176 | fn new() -> HirToLir { 177 | HirToLir { 178 | visited: HashMap::new(), 179 | } 180 | } 181 | } 182 | 183 | impl<'g, 'h: 'g> Lower<'g, 'h, Hir, Lir> for HirToLir { 184 | fn lower(&mut self, node: Node<'h, Hir>, ncx: &'g NodeCtxt) -> Node<'g, Lir> { 185 | if let Some(existing_node_id) = self.visited.get(&node.id()) { 186 | return ncx.node_ref(*existing_node_id); 187 | } 188 | let node_kind = &*node.kind(); 189 | let op = match node_kind { 190 | NodeKind::Op(op) => op, 191 | _ => unimplemented!(), 192 | }; 193 | let lir_node = match op { 194 | Hir::I32(val) => ncx.mk_node(Lir::I32(*val)), 195 | Hir::Usize(val) => ncx.mk_node(Lir::Usize(*val)), 196 | Hir::Array(elems) => { 197 | // Creates an allocation node for the array size, creates a store node for every 198 | // array element, then merge all stores' state outputs. 199 | let element_size_in_bytes = 4usize; 200 | let array_length = ncx.mk_node(Lir::Usize(elems.len())); 201 | 202 | let array_size_node = ncx 203 | .node_builder(Lir::Mul) 204 | .operand(array_length.val_out(0)) 205 | .operand(ncx.mk_node(Lir::Usize(element_size_in_bytes)).val_out(0)) 206 | .finish(); 207 | 208 | let alloc_node = ncx 209 | .node_builder(Lir::Alloc) 210 | .operand(array_size_node.val_out(0)) 211 | .finish(); 212 | 213 | let mut merge_node_builder = ncx.node_builder(Lir::Merge { 214 | num_of_values: 1, // the array base address 215 | num_of_states: elems.len(), 216 | }); 217 | 218 | for (i, &val) in elems.iter().enumerate() { 219 | let elem_byte_offset = ncx 220 | .node_builder(Lir::Mul) 221 | .operand(ncx.mk_node(Lir::Usize(i)).val_out(0)) 222 | .operand(ncx.mk_node(Lir::Usize(element_size_in_bytes)).val_out(0)) 223 | .finish(); 224 | 225 | let elem_addr = ncx 226 | .node_builder(Lir::Add) 227 | .operand(alloc_node.val_out(0)) 228 | .operand(elem_byte_offset.val_out(0)) 229 | .finish(); 230 | 231 | let store_node = ncx 232 | .node_builder(Lir::Store) 233 | .operand(elem_addr.val_out(0)) 234 | .operand(ncx.mk_node(Lir::I32(val)).val_out(0)) 235 | .state(alloc_node.st_out(0)) 236 | .finish(); 237 | 238 | merge_node_builder = merge_node_builder.state(store_node.st_out(0)); 239 | } 240 | 241 | let merge_node = merge_node_builder.operand(alloc_node.val_out(0)).finish(); 242 | 243 | merge_node 244 | } 245 | Hir::GlobalState => ncx.mk_node(Lir::GlobalState), 246 | Hir::Subscript => { 247 | let base = self.lower(node.val_in(0).origin().producer(), ncx); 248 | let index = self.lower(node.val_in(1).origin().producer(), ncx); 249 | let state_port = if base.kind().sig().st_outs > 0 { 250 | base.st_out(0) 251 | } else { 252 | ncx.mk_node(Lir::GlobalState).st_out(0) 253 | }; 254 | 255 | let offset = ncx 256 | .node_builder(Lir::Mul) 257 | .operand(index.val_out(0)) 258 | .operand(ncx.mk_node(Lir::Usize(4)).val_out(0)) 259 | .finish(); 260 | 261 | let base_offset = ncx 262 | .node_builder(Lir::Add) 263 | .operand(base.val_out(0)) 264 | .operand(offset.val_out(0)) 265 | .finish(); 266 | 267 | ncx.node_builder(Lir::Load) 268 | .operand(base_offset.val_out(0)) 269 | .state(state_port) 270 | .finish() 271 | } 272 | Hir::Add => { 273 | let lhs = self.lower(node.val_in(0).origin().producer(), ncx); 274 | let rhs = self.lower(node.val_in(1).origin().producer(), ncx); 275 | 276 | ncx.node_builder(Lir::Add) 277 | .operand(lhs.val_out(0)) 278 | .operand(rhs.val_out(0)) 279 | .finish() 280 | } 281 | Hir::Sub => { 282 | let lhs = self.lower(node.val_in(0).origin().producer(), ncx); 283 | let rhs = self.lower(node.val_in(1).origin().producer(), ncx); 284 | 285 | ncx.node_builder(Lir::Sub) 286 | .operand(lhs.val_out(0)) 287 | .operand(rhs.val_out(0)) 288 | .finish() 289 | } 290 | Hir::Mul => { 291 | let lhs = self.lower(node.val_in(0).origin().producer(), ncx); 292 | let rhs = self.lower(node.val_in(1).origin().producer(), ncx); 293 | 294 | ncx.node_builder(Lir::Mul) 295 | .operand(lhs.val_out(0)) 296 | .operand(rhs.val_out(0)) 297 | .finish() 298 | } 299 | _ => unimplemented!(), 300 | }; 301 | self.visited.insert(node.id(), lir_node.id()); 302 | lir_node 303 | } 304 | } 305 | 306 | struct ConstFoldOpt<'graph, S> { 307 | memory_stack: Vec, 308 | alloc_stack_addrs: HashMap, usize>, 309 | } 310 | 311 | impl<'graph, S> ConstFoldOpt<'graph, S> { 312 | fn new() -> ConstFoldOpt<'graph, S> 313 | where 314 | S: Eq + Hash, 315 | { 316 | ConstFoldOpt { 317 | memory_stack: vec![], 318 | alloc_stack_addrs: HashMap::new(), 319 | } 320 | } 321 | } 322 | 323 | impl<'g, 'h: 'g> Lower<'g, 'h, Lir, Lir> for ConstFoldOpt<'g, Lir> { 324 | fn lower(&mut self, node: Node<'h, Lir>, ncx: &'g NodeCtxt) -> Node<'g, Lir> { 325 | let op = match &*node.kind() { 326 | NodeKind::Op(op) => op.clone(), 327 | _ => unimplemented!(), 328 | }; 329 | // TODO: states = self.lower in st_ins 330 | match op { 331 | Lir::I32(val) => ncx.mk_node(Lir::I32(val)), 332 | Lir::Usize(val) => ncx.mk_node(Lir::Usize(val)), 333 | Lir::Alloc => { 334 | let alloc_size_node = self.lower(node.val_in(0).origin().producer(), ncx); 335 | 336 | match *alloc_size_node.kind() { 337 | NodeKind::Op(Lir::Usize(val)) => { 338 | let cur_sp = self.memory_stack.len(); 339 | self.memory_stack.resize(cur_sp + val, 0); 340 | self.alloc_stack_addrs.insert(node.val_out(0), cur_sp); 341 | } 342 | _ => {} 343 | } 344 | 345 | ncx.node_builder(Lir::Alloc) 346 | .operand(alloc_size_node.val_out(0)) 347 | .finish() 348 | } 349 | Lir::Add => { 350 | let lhs = node.val_in(0).origin().producer(); 351 | let rhs = node.val_in(1).origin().producer(); 352 | 353 | match (lhs.kind().clone(), rhs.kind().clone()) { 354 | (_, NodeKind::Op(Lir::I32(0))) => { 355 | self.lower(lhs, ncx) 356 | } 357 | (NodeKind::Op(Lir::I32(0)), _) => { 358 | self.lower(rhs, ncx) 359 | } 360 | (NodeKind::Op(Lir::I32(val_lhs)), NodeKind::Op(Lir::I32(val_rhs))) => { 361 | ncx.mk_node(Lir::I32(val_lhs + val_rhs)) 362 | } 363 | (_, NodeKind::Op(Lir::Usize(0))) => { 364 | self.lower(lhs, ncx) 365 | } 366 | (NodeKind::Op(Lir::Usize(0)), _) => { 367 | self.lower(rhs, ncx) 368 | } 369 | (NodeKind::Op(Lir::Usize(val_lhs)), NodeKind::Op(Lir::Usize(val_rhs))) => { 370 | ncx.mk_node(Lir::Usize(val_lhs + val_rhs)) 371 | } 372 | (NodeKind::Op(Lir::Usize(val)), NodeKind::Op(Lir::Alloc)) 373 | if self.alloc_stack_addrs.contains_key(&rhs.val_out(0)) => 374 | { 375 | let stack_base_index = self.alloc_stack_addrs.get(&rhs.val_out(0)).unwrap(); 376 | ncx.mk_node(Lir::Usize(val + stack_base_index)) 377 | } 378 | _ => { 379 | let lhs = self.lower(node.val_in(0).origin().producer(), ncx); 380 | let rhs = self.lower(node.val_in(1).origin().producer(), ncx); 381 | let add_builder = ncx 382 | .node_builder(Lir::Add) 383 | .operand(lhs.val_out(0)) 384 | .operand(rhs.val_out(0)); 385 | 386 | let add = add_builder.finish(); 387 | 388 | add 389 | } 390 | } 391 | } 392 | Lir::Mul => { 393 | let lhs = node.val_in(0).origin().producer(); 394 | let rhs = node.val_in(1).origin().producer(); 395 | 396 | match (lhs.kind().clone(), rhs.kind().clone()) { 397 | (NodeKind::Op(Lir::I32(val_lhs)), NodeKind::Op(Lir::I32(val_rhs))) => { 398 | ncx.mk_node(Lir::I32(val_lhs * val_rhs)) 399 | } 400 | (NodeKind::Op(Lir::Usize(val_lhs)), NodeKind::Op(Lir::Usize(val_rhs))) => { 401 | ncx.mk_node(Lir::Usize(val_lhs * val_rhs)) 402 | } 403 | _ => { 404 | let lhs = self.lower(lhs, ncx); 405 | let rhs = self.lower(rhs, ncx); 406 | ncx.node_builder(Lir::Add) 407 | .operand(lhs.val_out(0)) 408 | .operand(rhs.val_out(0)) 409 | .finish() 410 | } 411 | } 412 | } 413 | _ => { 414 | let mut node_builder = ncx.node_builder(op.clone()); 415 | for i in 0..op.sig().st_ins { 416 | let opi = self.lower(node.st_in(i).origin().producer(), ncx); 417 | node_builder = node_builder.state(opi.st_out(0)); 418 | } 419 | for i in 0..op.sig().val_ins { 420 | let opi = self.lower(node.val_in(i).origin().producer(), ncx); 421 | node_builder = node_builder.operand(opi.val_out(0)); 422 | } 423 | node_builder.finish() 424 | } 425 | } 426 | } 427 | } 428 | 429 | #[cfg(test)] 430 | mod test { 431 | use super::{ConstFoldOpt, Hir, HirToLir, Lower}; 432 | use crate::rvsdg::{Node, NodeCtxt, NodeKind, Sig, SigS}; 433 | use std::io; 434 | 435 | #[test] 436 | fn hir_to_lir() { 437 | let hir = NodeCtxt::new(); 438 | 439 | let subscript = hir 440 | .node_builder(Hir::Subscript) 441 | .operand(hir.mk_node(Hir::Usize(110)).val_out(0)) 442 | .operand(hir.mk_node(Hir::Usize(7)).val_out(0)) 443 | .finish(); 444 | 445 | let mut hir_buffer = Vec::new(); 446 | hir.print(&mut hir_buffer).unwrap(); 447 | let hir_content = String::from_utf8(hir_buffer).unwrap(); 448 | 449 | assert_eq!( 450 | hir_content, 451 | r#"digraph rvsdg { 452 | node [shape=record] 453 | edge [arrowhead=none] 454 | n0 [label="{{Usize(110)}|{0}}"] 455 | n1 [label="{{Usize(7)}|{0}}"] 456 | n2 [label="{{0|1}|{Subscript}|{0}}"] 457 | n0:o0 -> n2:i0 [color=blue] 458 | n1:o0 -> n2:i1 [color=blue] 459 | } 460 | "# 461 | ); 462 | 463 | let mut hir_to_lir = HirToLir::new(); 464 | let lir = NodeCtxt::new(); 465 | hir_to_lir.lower(subscript, &lir); 466 | 467 | let mut lir_buffer = Vec::new(); 468 | lir.print(&mut lir_buffer).unwrap(); 469 | let lir_content = String::from_utf8(lir_buffer).unwrap(); 470 | 471 | assert_eq!( 472 | lir_content, 473 | r#"digraph rvsdg { 474 | node [shape=record] 475 | edge [arrowhead=none] 476 | n0 [label="{{Usize(110)}|{0}}"] 477 | n1 [label="{{Usize(7)}|{0}}"] 478 | n2 [label="{{GlobalState}|{0}}"] 479 | n3 [label="{{Usize(4)}|{0}}"] 480 | n4 [label="{{0|1}|{Mul}|{0}}"] 481 | n1:o0 -> n4:i0 [color=blue] 482 | n3:o0 -> n4:i1 [color=blue] 483 | n5 [label="{{0|1}|{Add}|{0}}"] 484 | n0:o0 -> n5:i0 [color=blue] 485 | n4:o0 -> n5:i1 [color=blue] 486 | n6 [label="{{0|1}|{Load}|{0}}"] 487 | n5:o0 -> n6:i0 [color=blue] 488 | n2:o0 -> n6:i1 [style=dashed, color=red] 489 | } 490 | "# 491 | ); 492 | } 493 | 494 | #[test] 495 | fn constant_folding() { 496 | #[derive(Clone, PartialEq, Eq, Hash, Debug)] 497 | enum Ir { 498 | Lit(i32), 499 | Var(&'static str), 500 | Add, 501 | } 502 | 503 | impl Sig for Ir { 504 | fn sig(&self) -> SigS { 505 | match self { 506 | Ir::Lit(..) => SigS { 507 | val_outs: 1, 508 | ..<_>::default() 509 | }, 510 | Ir::Var(..) => SigS { 511 | val_outs: 1, 512 | ..<_>::default() 513 | }, 514 | Ir::Add => SigS { 515 | val_ins: 2, 516 | val_outs: 1, 517 | ..<_>::default() 518 | }, 519 | } 520 | } 521 | } 522 | 523 | struct ConstFoldOpt; 524 | 525 | impl<'g, 'h: 'g> Lower<'g, 'h, Ir, Ir> for ConstFoldOpt { 526 | fn lower(&mut self, node: Node<'h, Ir>, ncx: &'g NodeCtxt) -> Node<'g, Ir> { 527 | let op = match node.kind().clone() { 528 | NodeKind::Op(op) => op, 529 | _ => unimplemented!(), 530 | }; 531 | match op { 532 | Ir::Lit(lit) => ncx.mk_node(Ir::Lit(lit)), 533 | Ir::Var(var) => ncx.mk_node(Ir::Var(var)), 534 | Ir::Add => { 535 | let lhs = node.val_in(0).origin().producer(); 536 | let rhs = node.val_in(1).origin().producer(); 537 | 538 | match (lhs.kind().clone(), rhs.kind().clone()) { 539 | (NodeKind::Op(Ir::Lit(val_lhs)), NodeKind::Op(Ir::Lit(val_rhs))) => { 540 | ncx.mk_node(Ir::Lit(val_lhs + val_rhs)) 541 | } 542 | _ => { 543 | let lhs = self.lower(lhs, ncx); 544 | let rhs = self.lower(rhs, ncx); 545 | ncx.node_builder(Ir::Add) 546 | .operand(lhs.val_out(0)) 547 | .operand(rhs.val_out(0)) 548 | .finish() 549 | } 550 | } 551 | } 552 | } 553 | } 554 | } 555 | 556 | let ncx_noopt = NodeCtxt::new(); 557 | 558 | let n0 = ncx_noopt 559 | .node_builder(Ir::Add) 560 | .operand(ncx_noopt.mk_node(Ir::Lit(2)).val_out(0)) 561 | .operand(ncx_noopt.mk_node(Ir::Lit(3)).val_out(0)) 562 | .finish(); 563 | 564 | let n1 = ncx_noopt 565 | .node_builder(Ir::Add) 566 | .operand(n0.val_out(0)) 567 | .operand(ncx_noopt.mk_node(Ir::Var("x")).val_out(0)) 568 | .finish(); 569 | 570 | let mut noopt_buffer = Vec::new(); 571 | ncx_noopt.print(&mut noopt_buffer).unwrap(); 572 | let noopt_content = String::from_utf8(noopt_buffer).unwrap(); 573 | 574 | assert_eq!( 575 | noopt_content, 576 | r#"digraph rvsdg { 577 | node [shape=record] 578 | edge [arrowhead=none] 579 | n0 [label="{{Lit(2)}|{0}}"] 580 | n1 [label="{{Lit(3)}|{0}}"] 581 | n2 [label="{{0|1}|{Add}|{0}}"] 582 | n0:o0 -> n2:i0 [color=blue] 583 | n1:o0 -> n2:i1 [color=blue] 584 | n3 [label="{{Var("x")}|{0}}"] 585 | n4 [label="{{0|1}|{Add}|{0}}"] 586 | n2:o0 -> n4:i0 [color=blue] 587 | n3:o0 -> n4:i1 [color=blue] 588 | } 589 | "# 590 | ); 591 | 592 | let mut cfopt = ConstFoldOpt; 593 | let ncx_opt = NodeCtxt::new(); 594 | 595 | cfopt.lower(n1, &ncx_opt); 596 | 597 | let mut opt_buffer = Vec::new(); 598 | ncx_opt.print(&mut opt_buffer).unwrap(); 599 | let opt_content = String::from_utf8(opt_buffer).unwrap(); 600 | 601 | assert_eq!( 602 | opt_content, 603 | r#"digraph rvsdg { 604 | node [shape=record] 605 | edge [arrowhead=none] 606 | n0 [label="{{Lit(5)}|{0}}"] 607 | n1 [label="{{Var("x")}|{0}}"] 608 | n2 [label="{{0|1}|{Add}|{0}}"] 609 | n0:o0 -> n2:i0 [color=blue] 610 | n1:o0 -> n2:i1 [color=blue] 611 | } 612 | "# 613 | ); 614 | } 615 | 616 | #[test] 617 | fn array_10x42_to_stores() { 618 | use crate::rvsdg::NodeCtxtConfig; 619 | 620 | let hir = NodeCtxt::new(); 621 | let arr = hir.mk_node(Hir::Array(vec![42; 10])); 622 | let subscript = hir 623 | .node_builder(Hir::Subscript) 624 | .operand(arr.val_out(0)) 625 | .operand(hir.mk_node(Hir::Usize(1)).val_out(0)) 626 | .finish(); 627 | hir.print(&mut io::stdout().lock()).unwrap(); 628 | 629 | println!( 630 | "array-10x42-hir - nodes({}), edges({})", 631 | hir.num_nodes(), 632 | hir.num_edges() 633 | ); 634 | 635 | let mut hir_to_lir = HirToLir::new(); 636 | let lir = NodeCtxt::with_config(NodeCtxtConfig { 637 | opt_interning: false, 638 | }); 639 | let merge = hir_to_lir.lower(subscript.clone(), &lir); 640 | lir.print(&mut io::stdout().lock()).unwrap(); 641 | 642 | println!( 643 | "array-10x42-lir - nodes({}), edges({})", 644 | lir.num_nodes(), 645 | lir.num_edges() 646 | ); 647 | 648 | let mut hir_to_lir = HirToLir::new(); 649 | let lir = NodeCtxt::with_config(NodeCtxtConfig { 650 | opt_interning: true, 651 | }); 652 | let merge = hir_to_lir.lower(subscript, &lir); 653 | lir.print(&mut io::stdout().lock()).unwrap(); 654 | 655 | println!( 656 | "array-10x42-lir-valuenum - nodes({}), edges({})", 657 | lir.num_nodes(), 658 | lir.num_edges() 659 | ); 660 | 661 | let lir_opt = NodeCtxt::new(); 662 | let mut const_fold = ConstFoldOpt::new(); 663 | let _ = const_fold.lower(merge, &lir_opt); 664 | lir_opt.print(&mut io::stdout().lock()).unwrap(); 665 | 666 | println!( 667 | "array-10x42-lir-valuenum-constfold - nodes({}), edges({})", 668 | lir_opt.num_nodes(), 669 | lir_opt.num_edges() 670 | ); 671 | } 672 | 673 | #[test] 674 | fn array_0to9_to_stores() { 675 | use crate::rvsdg::NodeCtxtConfig; 676 | 677 | { 678 | let hir = NodeCtxt::with_config(NodeCtxtConfig { opt_interning: false }); 679 | let arr1 = hir.mk_node(Hir::Array((0..2).collect())); 680 | let arr2 = hir.mk_node(Hir::Array((0..2).collect())); 681 | let subscript1 = hir 682 | .node_builder(Hir::Subscript) 683 | .operand(arr1.val_out(0)) 684 | .operand(hir.mk_node(Hir::Usize(1)).val_out(0)) 685 | .finish(); 686 | let subscript2 = hir 687 | .node_builder(Hir::Subscript) 688 | .operand(arr2.val_out(0)) 689 | .operand(hir.mk_node(Hir::Usize(1)).val_out(0)) 690 | .finish(); 691 | let add = hir 692 | .node_builder(Hir::Add) 693 | .operand(subscript1.val_out(0)) 694 | .operand(subscript2.val_out(0)) 695 | .finish(); 696 | hir.print(&mut io::stdout().lock()).unwrap(); 697 | 698 | println!( 699 | "array-0to9-hir noopt - nodes({}), edges({})", 700 | hir.num_nodes(), 701 | hir.num_edges() 702 | ); 703 | } 704 | 705 | let hir = NodeCtxt::new(); 706 | let arr1 = hir.mk_node(Hir::Array((0..4).collect())); 707 | let arr2 = hir.mk_node(Hir::Array((0..4).collect())); 708 | let subscript1 = hir 709 | .node_builder(Hir::Subscript) 710 | .operand(arr1.val_out(0)) 711 | .operand(hir.mk_node(Hir::Usize(1)).val_out(0)) 712 | .finish(); 713 | let subscript2 = hir 714 | .node_builder(Hir::Subscript) 715 | .operand(arr2.val_out(0)) 716 | .operand(hir.mk_node(Hir::Usize(1)).val_out(0)) 717 | .finish(); 718 | let add = hir 719 | .node_builder(Hir::Add) 720 | .operand(subscript1.val_out(0)) 721 | .operand(subscript2.val_out(0)) 722 | .finish(); 723 | hir.print(&mut io::stdout().lock()).unwrap(); 724 | 725 | println!( 726 | "array-0to9-hir noopt - nodes({}), edges({})", 727 | hir.num_nodes(), 728 | hir.num_edges() 729 | ); 730 | 731 | let mut hir_to_lir = HirToLir::new(); 732 | let lir = NodeCtxt::with_config(NodeCtxtConfig { 733 | opt_interning: false, 734 | }); 735 | let merge = hir_to_lir.lower(add.clone(), &lir); 736 | lir.print(&mut io::stdout().lock()).unwrap(); 737 | 738 | println!( 739 | "array-0to9-lir - nodes({}), edges({})", 740 | lir.num_nodes(), 741 | lir.num_edges() 742 | ); 743 | 744 | let mut hir_to_lir = HirToLir::new(); 745 | let lir = NodeCtxt::with_config(NodeCtxtConfig { 746 | opt_interning: true, 747 | }); 748 | let merge = hir_to_lir.lower(add, &lir); 749 | lir.print(&mut io::stdout().lock()).unwrap(); 750 | 751 | println!( 752 | "array-0to9-lir-valuenum - nodes({}), edges({})", 753 | lir.num_nodes(), 754 | lir.num_edges() 755 | ); 756 | 757 | let lir_opt1 = NodeCtxt::new(); 758 | let lir_opt2 = NodeCtxt::new(); 759 | 760 | let mut const_fold = ConstFoldOpt::new(); 761 | let merge1 = const_fold.lower(merge, &lir_opt1); 762 | let _ = const_fold.lower(merge1, &lir_opt2); 763 | lir_opt2.print(&mut io::stdout().lock()).unwrap(); 764 | 765 | println!( 766 | "array-0to9-lir-valuenum-constfold - nodes({}), edges({})", 767 | lir_opt2.num_nodes(), 768 | lir_opt2.num_edges() 769 | ); 770 | } 771 | 772 | #[test] 773 | #[should_panic] 774 | fn bug_traverse() { 775 | struct Traverser; 776 | 777 | #[derive(Clone, PartialEq, Eq, Hash, Debug)] 778 | enum D { 779 | A(usize), 780 | B, 781 | } 782 | 783 | impl Sig for D { 784 | fn sig(&self) -> SigS { 785 | match self { 786 | D::A(..) => SigS { 787 | val_outs: 1, 788 | ..SigS::default() 789 | }, 790 | D::B => SigS { 791 | val_ins: 2, 792 | val_outs: 1, 793 | ..SigS::default() 794 | }, 795 | } 796 | } 797 | } 798 | 799 | impl<'g, 'h: 'g> Lower<'g, 'h, D, D> for Traverser { 800 | fn lower(&mut self, node: Node<'h, D>, ncx: &'g NodeCtxt) -> Node<'g, D> { 801 | let op = match &*node.kind() { 802 | NodeKind::Op(op) => op.clone(), 803 | _ => unreachable!(), 804 | }; 805 | match op { 806 | D::A(val) => { 807 | println!("D::A"); 808 | ncx.mk_node(D::A(10)) 809 | } 810 | D::B => { 811 | let p1 = self.lower(node.val_in(0).origin().producer(), ncx); 812 | let p2 = self.lower(node.val_in(1).origin().producer(), ncx); 813 | 814 | match (p1.kind().clone(), p2.kind().clone()) { 815 | (NodeKind::Op(D::A(l)), NodeKind::Op(D::A(r))) => { 816 | let x = ncx.mk_node(D::A(l + r)); 817 | println!("two D::A"); 818 | x 819 | } 820 | _ => { 821 | println!("D::B"); 822 | ncx.node_builder(D::B) 823 | .operand(p1.val_out(0)) 824 | .operand(p2.val_out(0)) 825 | .finish() 826 | } 827 | } 828 | } 829 | } 830 | } 831 | } 832 | 833 | let ncx = NodeCtxt::new(); 834 | let a1 = ncx.mk_node(D::A(2)); 835 | let a2 = ncx.mk_node(D::A(3)); 836 | let b1 = ncx 837 | .node_builder(D::B) 838 | .operand(a1.val_out(0)) 839 | .operand(a2.val_out(0)) 840 | .finish(); 841 | 842 | let mut trav = Traverser; 843 | let ncx_out = NodeCtxt::new(); 844 | let x = trav.lower(b1, &ncx_out); 845 | } 846 | 847 | #[test] 848 | fn unreachable_code_elimination() { 849 | let hir = NodeCtxt::new(); 850 | 851 | let n_sub = hir 852 | .node_builder(Hir::Sub) 853 | .operand(hir.mk_node(Hir::I32(2)).val_out(0)) 854 | .operand(hir.mk_node(Hir::I32(5)).val_out(0)) 855 | .finish(); 856 | 857 | let n_add = hir 858 | .node_builder(Hir::Add) 859 | .operand(n_sub.val_out(0)) 860 | .operand(hir.mk_node(Hir::I32(8)).val_out(0)) 861 | .finish(); 862 | 863 | let n_mul = hir 864 | .node_builder(Hir::Mul) 865 | .operand(hir.mk_node(Hir::I32(2)).val_out(0)) 866 | .operand(hir.mk_node(Hir::I32(5)).val_out(0)) 867 | .finish(); 868 | 869 | hir.print(&mut io::stdout().lock()).unwrap(); 870 | 871 | let mut hir_to_lir = HirToLir::new(); 872 | let lir = NodeCtxt::new(); 873 | let lir_add = hir_to_lir.lower(n_add, &lir); 874 | lir.print(&mut io::stdout().lock()).unwrap(); 875 | } 876 | } 877 | -------------------------------------------------------------------------------- /src/rvsdg.rs: -------------------------------------------------------------------------------- 1 | use smallvec::SmallVec; 2 | use std::{ 3 | cell::{Cell, Ref, RefCell}, 4 | collections::{hash_map::RawEntryMut, HashMap}, 5 | fmt::{self, Debug}, 6 | hash::{BuildHasher, Hash, Hasher}, 7 | io::{self, Write}, 8 | ptr, 9 | }; 10 | 11 | /// An index for a NodeData in a NodeCtxt. 12 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 13 | pub(crate) struct NodeId(usize); 14 | 15 | /// An index for a RegionData in a NodeCtxt. 16 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 17 | pub(crate) struct RegionId(usize); 18 | 19 | /// An index for a UserData of an input or result port. 20 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 21 | pub(crate) enum UserId { 22 | In { node: NodeId, index: usize }, 23 | Res { region: RegionId, index: usize }, 24 | } 25 | 26 | impl UserId { 27 | pub(crate) fn node_id(&self) -> Option { 28 | match self { 29 | &UserId::In { node, .. } => Some(node), 30 | _ => None, 31 | } 32 | } 33 | } 34 | 35 | /// An index for an OriginData of an output or argument port. 36 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 37 | pub(crate) enum OriginId { 38 | Out { node: NodeId, index: usize }, 39 | Arg { region: RegionId, index: usize }, 40 | } 41 | 42 | impl OriginId { 43 | pub(crate) fn node_id(&self) -> Option { 44 | match self { 45 | &OriginId::Out { node, .. } => Some(node), 46 | _ => None, 47 | } 48 | } 49 | } 50 | 51 | /// A UserData contains information about an input or result port. 52 | #[derive(Clone, Default, Debug)] 53 | pub(crate) struct UserData { 54 | origin: Cell>, 55 | sink: Option, 56 | prev_user: Cell>, 57 | next_user: Cell>, 58 | } 59 | 60 | /// An OriginData contains information about an output or argument port. 61 | #[derive(Clone, Default, Debug)] 62 | pub(crate) struct OriginData { 63 | source: Option, 64 | users: Cell>, 65 | } 66 | 67 | /// A linked list of users connected to a common origin. 68 | #[derive(Clone, Copy, PartialEq, Debug)] 69 | pub(crate) struct UserIdList { 70 | first: UserId, 71 | last: UserId, 72 | } 73 | 74 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 75 | pub(crate) enum NodeKind { 76 | Op(S), 77 | Apply { 78 | arg_val_ins: usize, 79 | arg_st_ins: usize, 80 | region_val_res: usize, 81 | region_st_res: usize, 82 | }, 83 | Gamma { 84 | val_ins: usize, 85 | val_outs: usize, 86 | st_ins: usize, 87 | st_outs: usize, 88 | }, 89 | Omega { 90 | imports: usize, 91 | exports: usize, 92 | }, 93 | } 94 | 95 | pub(crate) struct NodeData { 96 | ins: Vec, 97 | outs: Vec, 98 | inner_regions: Cell>, 99 | outer_region: RegionId, 100 | kind: NodeKind, 101 | } 102 | 103 | #[derive(Copy, Clone)] 104 | pub(crate) struct InnerRegionList { 105 | first_region: RegionId, 106 | last_region: RegionId, 107 | } 108 | 109 | pub(crate) struct RegionData { 110 | sequence_index: usize, 111 | res: Vec, 112 | args: Vec, 113 | prev_region: Cell>, 114 | next_region: Cell>, 115 | } 116 | 117 | #[derive(Debug, Copy, Clone, PartialEq, Default)] 118 | pub(crate) struct SigS { 119 | pub(crate) val_ins: usize, 120 | pub(crate) val_outs: usize, 121 | pub(crate) st_ins: usize, 122 | pub(crate) st_outs: usize, 123 | } 124 | 125 | // TODO: remove this and let region ports be imperatively created. 126 | #[derive(Debug, Copy, Clone, PartialEq, Default)] 127 | pub(crate) struct RegionSigS { 128 | pub(crate) val_args: usize, 129 | pub(crate) val_res: usize, 130 | pub(crate) st_args: usize, 131 | pub(crate) st_res: usize, 132 | } 133 | 134 | impl SigS { 135 | pub(crate) fn num_input_ports(&self) -> usize { 136 | self.val_ins + self.st_ins 137 | } 138 | 139 | pub(crate) fn num_output_ports(&self) -> usize { 140 | self.val_outs + self.st_outs 141 | } 142 | 143 | fn is_side_effectful(&self) -> bool { 144 | self.st_outs > 0 145 | } 146 | } 147 | 148 | impl RegionSigS { 149 | pub(crate) fn num_argument_ports(&self) -> usize { 150 | self.val_args + self.st_args 151 | } 152 | 153 | pub(crate) fn num_result_ports(&self) -> usize { 154 | self.val_res + self.st_res 155 | } 156 | } 157 | 158 | pub(crate) trait Sig { 159 | fn sig(&self) -> SigS; 160 | } 161 | 162 | // TODO: implement this dynamically for structured nodes. 163 | impl Sig for NodeData { 164 | fn sig(&self) -> SigS { 165 | self.kind.sig() 166 | } 167 | } 168 | 169 | impl Sig for NodeKind { 170 | fn sig(&self) -> SigS { 171 | match self { 172 | NodeKind::Op(s) => s.sig(), 173 | &NodeKind::Apply { 174 | arg_val_ins, 175 | arg_st_ins, 176 | region_val_res, 177 | region_st_res, 178 | } => SigS { 179 | val_ins: 1 + arg_val_ins, // function input + argument inputs 180 | st_ins: arg_st_ins, 181 | val_outs: region_val_res, 182 | st_outs: region_st_res, 183 | ..SigS::default() 184 | }, 185 | &NodeKind::Gamma { 186 | val_ins, 187 | val_outs, 188 | st_ins, 189 | st_outs, 190 | } => { 191 | SigS { 192 | val_ins: 1 + val_ins, // predicate + inputs 193 | val_outs, 194 | st_ins, 195 | st_outs, 196 | ..SigS::default() 197 | } 198 | } 199 | &NodeKind::Omega { .. } => SigS::default(), 200 | } 201 | } 202 | } 203 | 204 | #[derive(PartialEq, Eq, Hash)] 205 | struct NodeTerm { 206 | region: RegionId, 207 | kind: NodeKind, 208 | origins: SmallVec<[OriginId; 4]>, 209 | } 210 | 211 | pub(crate) struct NodeCtxt { 212 | nodes: RefCell>>, 213 | regions: RefCell>, 214 | interned_nodes: RefCell, NodeId>>, 215 | config: NodeCtxtConfig, 216 | } 217 | 218 | pub(crate) struct NodeCtxtConfig { 219 | pub(crate) opt_interning: bool, 220 | } 221 | 222 | impl Default for NodeCtxtConfig { 223 | fn default() -> NodeCtxtConfig { 224 | NodeCtxtConfig { 225 | opt_interning: true, 226 | } 227 | } 228 | } 229 | 230 | impl std::hash::Hash for NodeCtxt { 231 | fn hash(&self, state: &mut H) 232 | where 233 | H: std::hash::Hasher, 234 | { 235 | state.write_usize(self as *const _ as usize); 236 | } 237 | } 238 | 239 | impl NodeCtxt { 240 | pub(crate) fn num_nodes(&self) -> usize { 241 | self.nodes.borrow().len() 242 | } 243 | 244 | pub(crate) fn num_edges(&self) -> usize { 245 | self.nodes.borrow().iter().map(|node| node.ins.len()).sum() 246 | } 247 | } 248 | 249 | impl NodeCtxt { 250 | pub(crate) fn new() -> NodeCtxt 251 | where 252 | S: Eq + Hash, 253 | { 254 | NodeCtxt { 255 | nodes: RefCell::new(vec![]), 256 | regions: RefCell::new(vec![]), 257 | interned_nodes: RefCell::default(), 258 | config: Default::default(), 259 | } 260 | } 261 | 262 | pub(crate) fn with_config(config: NodeCtxtConfig) -> NodeCtxt 263 | where 264 | S: Eq + Hash, 265 | { 266 | NodeCtxt { 267 | config, 268 | ..NodeCtxt::new() 269 | } 270 | } 271 | 272 | // FIXME: This doesn't do interning. How could we do it? 273 | fn create_node(&self, node_kind: NodeKind, outer_region_id: RegionId) -> Node<'_, S> 274 | where 275 | S: Sig, 276 | { 277 | let node_id; 278 | 279 | { 280 | let mut nodes = self.nodes.borrow_mut(); 281 | node_id = NodeId(nodes.len()); 282 | nodes.push(NodeData { 283 | ins: vec![UserData::default(); node_kind.sig().num_input_ports()], 284 | outs: vec![OriginData::default(); node_kind.sig().num_output_ports()], 285 | inner_regions: Cell::default(), 286 | outer_region: outer_region_id, 287 | kind: node_kind, 288 | }); 289 | } 290 | self.node_ref(node_id) 291 | } 292 | 293 | fn connect_ports(&self, user_id: UserId, origin_id: OriginId) { 294 | let user_data = self.user_data(user_id); 295 | 296 | assert_eq!(user_data.origin.get(), None); 297 | assert_eq!(user_data.prev_user.get(), None); 298 | assert_eq!(user_data.next_user.get(), None); 299 | 300 | user_data.origin.set(Some(origin_id)); 301 | 302 | let origin_data = self.origin_data(origin_id); 303 | 304 | let new_user_list = match origin_data.users.get() { 305 | Some(UserIdList { first, last }) => { 306 | self.user_data(last).next_user.set(Some(user_id)); 307 | user_data.prev_user.set(Some(last)); 308 | UserIdList { 309 | first, 310 | last: user_id, 311 | } 312 | } 313 | None => UserIdList { 314 | first: user_id, 315 | last: user_id, 316 | }, 317 | }; 318 | 319 | origin_data.users.set(Some(new_user_list)); 320 | } 321 | 322 | pub(crate) fn print(&self, out: &mut dyn Write) -> io::Result<()> 323 | where 324 | S: Sig + Debug, 325 | { 326 | writeln!(out, "digraph rvsdg {{")?; 327 | writeln!(out, " node [shape=record]")?; 328 | writeln!(out, " edge [arrowhead=none]")?; 329 | for idx in 0..self.nodes.borrow().len() { 330 | let node = self.node_ref(NodeId(idx)); 331 | let sig = node.kind().sig(); 332 | 333 | match *node.kind() { 334 | NodeKind::Op(ref operation) => { 335 | let dot_ins = (0..sig.num_input_ports()) 336 | .map(|i| format!("{0}", i)) 337 | .collect::>() 338 | .join("|"); 339 | let dot_outs = (0..sig.num_output_ports()) 340 | .map(|i| format!("{0}", i)) 341 | .collect::>() 342 | .join("|"); 343 | let mut label_op = String::with_capacity(16); 344 | for c in format!("{:?}", operation).chars() { 345 | if c == '{' || c == '}' { 346 | label_op.push('\\'); 347 | } 348 | label_op.push(c); 349 | } 350 | let label_value = vec![dot_ins, label_op, dot_outs] 351 | .into_iter() 352 | .filter(|s| !s.is_empty()) 353 | .collect::>() 354 | .join("}|{"); 355 | let label = format!("{{{{{}}}}}", label_value); 356 | writeln!(out, r#" n{} [label="{}"]"#, node.id.0, label)?; 357 | } 358 | _ => unimplemented!(), 359 | } 360 | 361 | for i in 0..sig.val_ins { 362 | let origin = node.val_in(i).origin(); 363 | match origin.0.origin_id { 364 | OriginId::Out { 365 | node: origin_node_id, 366 | index, 367 | } => { 368 | let port_origin = index; 369 | let port_user = i; 370 | writeln!( 371 | out, 372 | " n{}:o{} -> n{}:i{} [color=blue]", 373 | origin_node_id.0, port_origin, node.id.0, port_user 374 | )?; 375 | } 376 | _ => unimplemented!(), 377 | } 378 | } 379 | 380 | for i in 0..sig.st_ins { 381 | let origin = node.st_in(i).origin(); 382 | match origin.0.origin_id { 383 | OriginId::Out { 384 | node: origin_node_id, 385 | index, 386 | } => { 387 | let port_origin = index; 388 | let port_user = sig.val_ins + i; 389 | writeln!( 390 | out, 391 | " n{}:o{} -> n{}:i{} [style=dashed, color=red]", 392 | origin_node_id.0, port_origin, node.id.0, port_user 393 | )?; 394 | } 395 | _ => unimplemented!(), 396 | } 397 | } 398 | } 399 | writeln!(out, "}}") 400 | } 401 | 402 | pub(crate) fn node_data(&self, id: NodeId) -> Ref> { 403 | Ref::map(self.nodes.borrow(), |nodes| &nodes[id.0]) 404 | } 405 | 406 | pub(crate) fn region_data(&self, id: RegionId) -> Ref { 407 | Ref::map(self.regions.borrow(), |regions| ®ions[id.0]) 408 | } 409 | 410 | pub(crate) fn user_data(&self, user_id: UserId) -> Ref { 411 | match user_id { 412 | UserId::In { node, index } => { 413 | Ref::map(self.node_data(node), |node_data| &node_data.ins[index]) 414 | } 415 | UserId::Res { region, index } => Ref::map(self.region_data(region), |region_data| { 416 | ®ion_data.res[index] 417 | }), 418 | } 419 | } 420 | 421 | pub(crate) fn origin_data(&self, origin_id: OriginId) -> Ref { 422 | match origin_id { 423 | OriginId::Out { node, index } => { 424 | Ref::map(self.node_data(node), |node_data| &node_data.outs[index]) 425 | } 426 | OriginId::Arg { region, index } => Ref::map(self.region_data(region), |region_data| { 427 | ®ion_data.args[index] 428 | }), 429 | } 430 | } 431 | 432 | fn compute_node_hash(&self, node_term: &NodeTerm) -> u64 433 | where 434 | S: Eq + Hash, 435 | { 436 | let mut hasher = self.interned_nodes.borrow().hasher().build_hasher(); 437 | node_term.hash(&mut hasher); 438 | hasher.finish() 439 | } 440 | 441 | fn mk_node_with(&self, kind: NodeKind, origins: &[OriginId]) -> NodeId 442 | where 443 | S: Sig + Eq + Hash + Clone, 444 | { 445 | assert_eq!(kind.sig().num_input_ports(), origins.len()); 446 | 447 | let region_id = RegionId(0); 448 | 449 | let create_node = |kind: NodeKind, origins: &[OriginId]| { 450 | // Node creation works as follows: 451 | // 452 | // 1. Create the UserData sequence, whilst linking the user list of each origin. 453 | // 2. Initialize the OriginData sequence with empty users. 454 | // 3. Push the new node to the node context and return its id. 455 | 456 | // Input ports are put into this vector so the node creation comes down to just 457 | // a push into the `self.nodes`. 458 | let mut new_node_inputs = Vec::::with_capacity(kind.sig().num_input_ports()); 459 | let node_id = NodeId(self.nodes.borrow().len()); 460 | 461 | for (i, &origin) in origins.iter().enumerate() { 462 | let new_in_id = UserId::In { 463 | node: node_id, 464 | index: i, 465 | }; 466 | let (prev_user, new_user_list) = match self.origin_data(origin).users.get() { 467 | Some(UserIdList { first, last }) => { 468 | match last { 469 | UserId::In { node, index } if node == node_id => { 470 | new_node_inputs[index].next_user.set(Some(new_in_id)); 471 | } 472 | _ => { 473 | self.user_data(last).next_user.set(Some(new_in_id)); 474 | } 475 | } 476 | let new_user_list = UserIdList { 477 | first, 478 | last: new_in_id, 479 | }; 480 | (Some(last), new_user_list) 481 | } 482 | None => ( 483 | None, // No previous user. 484 | UserIdList { 485 | first: new_in_id, 486 | last: new_in_id, 487 | }, 488 | ), 489 | }; 490 | self.origin_data(origin).users.set(Some(new_user_list)); 491 | new_node_inputs.push(UserData { 492 | origin: Cell::new(Some(origin)), 493 | sink: None, 494 | prev_user: Cell::new(prev_user), 495 | next_user: Cell::default(), 496 | }); 497 | } 498 | 499 | let sig = kind.sig(); 500 | 501 | self.nodes.borrow_mut().push(NodeData { 502 | ins: new_node_inputs, 503 | outs: vec![OriginData::default(); kind.sig().num_output_ports()], 504 | inner_regions: Cell::default(), 505 | // FIXME replace with an argument from mk_node_with. 506 | outer_region: region_id, 507 | kind, 508 | }); 509 | 510 | assert_eq!(self.node_data(node_id).ins.len(), sig.num_input_ports()); 511 | assert_eq!(self.node_data(node_id).outs.len(), sig.num_output_ports()); 512 | 513 | node_id 514 | }; 515 | 516 | let node_term = NodeTerm { 517 | region: region_id, 518 | kind: kind.clone(), 519 | origins: origins.into(), 520 | }; 521 | 522 | if self.config.opt_interning && !kind.sig().is_side_effectful() { 523 | let node_hash = self.compute_node_hash(&node_term); 524 | let mut interned_nodes = self.interned_nodes.borrow_mut(); 525 | let entry = interned_nodes 526 | .raw_entry_mut() 527 | .from_key_hashed_nocheck(node_hash, &node_term); 528 | 529 | match entry { 530 | RawEntryMut::Occupied(e) => *e.get(), 531 | RawEntryMut::Vacant(e) => { 532 | let node_id = create_node(kind, origins); 533 | e.insert_hashed_nocheck(node_hash, node_term, node_id); 534 | node_id 535 | } 536 | } 537 | } else { 538 | create_node(kind, origins) 539 | } 540 | } 541 | 542 | fn mk_region_for_node(&self, node_id: NodeId, region_sig: RegionSigS) -> RegionId { 543 | unimplemented!() 544 | } 545 | 546 | pub(crate) fn mk_node(&self, op: S) -> Node 547 | where 548 | S: Sig + Eq + Hash + Clone, 549 | { 550 | let node_id = self.mk_node_with(NodeKind::Op(op), &[]); 551 | Node { 552 | ctxt: self, 553 | id: node_id, 554 | } 555 | } 556 | 557 | pub(crate) fn node_builder(&self, op: S) -> NodeBuilder 558 | where 559 | S: Sig, 560 | { 561 | NodeBuilder::new(self, NodeKind::Op(op)) 562 | } 563 | 564 | pub(crate) fn node_ref(&self, node_id: NodeId) -> Node { 565 | assert!(node_id.0 < self.nodes.borrow().len()); 566 | Node { 567 | ctxt: self, 568 | id: node_id, 569 | } 570 | } 571 | 572 | pub(crate) fn user_ref<'g>(&'g self, user_id: UserId) -> User<'g, S> { 573 | match user_id { 574 | UserId::In { node, index } => assert!(index < self.node_data(node).ins.len()), 575 | UserId::Res { region, index } => assert!(index < self.region_data(region).res.len()), 576 | } 577 | 578 | User { 579 | ctxt: self, 580 | user_id, 581 | } 582 | } 583 | 584 | pub(crate) fn origin_ref<'g>(&'g self, origin_id: OriginId) -> Origin<'g, S> { 585 | match origin_id { 586 | OriginId::Out { node, index } => assert!(index < self.node_data(node).outs.len()), 587 | OriginId::Arg { region, index } => assert!(index < self.region_data(region).args.len()), 588 | } 589 | 590 | Origin { 591 | ctxt: self, 592 | origin_id, 593 | } 594 | } 595 | } 596 | 597 | impl PartialEq for NodeCtxt { 598 | fn eq(&self, other: &NodeCtxt) -> bool { 599 | ptr::eq(self, other) 600 | } 601 | } 602 | 603 | impl Eq for NodeCtxt {} 604 | 605 | pub(crate) struct NodeBuilder<'g, S> { 606 | ctxt: &'g NodeCtxt, 607 | node_kind: NodeKind, 608 | val_origins: Vec>, 609 | st_origins: Vec>, 610 | } 611 | 612 | impl<'g, S: Sig> NodeBuilder<'g, S> { 613 | pub(crate) fn new(ctxt: &'g NodeCtxt, node_kind: NodeKind) -> NodeBuilder<'g, S> { 614 | let sig = node_kind.sig(); 615 | NodeBuilder { 616 | ctxt, 617 | node_kind, 618 | val_origins: Vec::with_capacity(sig.val_ins), 619 | st_origins: Vec::with_capacity(sig.st_ins), 620 | } 621 | } 622 | 623 | pub(crate) fn operand(mut self, val_origin: ValOrigin<'g, S>) -> NodeBuilder<'g, S> { 624 | assert!(self.val_origins.len() < self.node_kind.sig().val_ins); 625 | self.val_origins.push(val_origin); 626 | self 627 | } 628 | 629 | pub(crate) fn operands(mut self, val_origins: &[ValOrigin<'g, S>]) -> NodeBuilder<'g, S> 630 | where 631 | S: Clone, 632 | { 633 | assert!(self.val_origins.is_empty()); 634 | assert_eq!(self.node_kind.sig().val_ins, val_origins.len()); 635 | self.val_origins.extend(val_origins.iter().cloned()); 636 | self 637 | } 638 | 639 | pub(crate) fn state(mut self, st_origin: StOrigin<'g, S>) -> NodeBuilder<'g, S> { 640 | assert!(self.st_origins.len() < self.node_kind.sig().st_ins); 641 | self.st_origins.push(st_origin); 642 | self 643 | } 644 | 645 | pub(crate) fn states(mut self, st_origins: &[StOrigin<'g, S>]) -> NodeBuilder<'g, S> 646 | where 647 | S: Clone, 648 | { 649 | assert!(self.st_origins.is_empty()); 650 | assert_eq!(self.node_kind.sig().st_ins, st_origins.len()); 651 | self.st_origins.extend(st_origins.iter().cloned()); 652 | self 653 | } 654 | 655 | pub(crate) fn finish(self) -> Node<'g, S> 656 | where 657 | S: Eq + Hash + Clone, 658 | { 659 | let sig = self.node_kind.sig(); 660 | assert_eq!(self.val_origins.len(), sig.val_ins); 661 | assert_eq!(self.st_origins.len(), sig.st_ins); 662 | 663 | let origins: Vec = { 664 | let val_origins = self.val_origins.iter().map(|val_origin| val_origin.0.id()); 665 | let st_origins = self.st_origins.iter().map(|st_origin| st_origin.0.id()); 666 | val_origins.chain(st_origins).collect() 667 | }; 668 | 669 | assert_eq!(origins.len(), sig.val_ins + sig.st_ins); 670 | 671 | let node_id = self.ctxt.mk_node_with(self.node_kind, &origins); 672 | 673 | Node { 674 | ctxt: self.ctxt, 675 | id: node_id, 676 | } 677 | } 678 | } 679 | 680 | #[derive(Clone, Copy, PartialEq)] 681 | pub(crate) struct Node<'g, S> { 682 | ctxt: &'g NodeCtxt, 683 | id: NodeId, 684 | } 685 | 686 | impl<'g, S: fmt::Debug> fmt::Debug for Node<'g, S> { 687 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 688 | write!(f, "{:?}", self.data().kind) 689 | } 690 | } 691 | 692 | impl<'g, S> Node<'g, S> { 693 | pub(crate) fn id(&self) -> NodeId { 694 | self.id 695 | } 696 | 697 | pub(crate) fn data(&self) -> Ref<'g, NodeData> { 698 | self.ctxt.node_data(self.id) 699 | } 700 | 701 | pub(crate) fn kind(&self) -> Ref<'g, NodeKind> { 702 | Ref::map(self.ctxt.node_data(self.id), |node_data| &node_data.kind) 703 | } 704 | } 705 | 706 | impl<'g, S: Sig> Node<'g, S> { 707 | pub(crate) fn val_in(&self, port: usize) -> ValUser<'g, S> { 708 | let sig = self.data().sig(); 709 | assert!(port < sig.val_ins); 710 | ValUser(self.ctxt.user_ref(UserId::In { 711 | node: self.id, 712 | index: port, 713 | })) 714 | } 715 | 716 | pub(crate) fn val_out(&self, port: usize) -> ValOrigin<'g, S> { 717 | let sig = self.data().sig(); 718 | assert!(port < sig.val_outs); 719 | ValOrigin(self.ctxt.origin_ref(OriginId::Out { 720 | node: self.id, 721 | index: port, 722 | })) 723 | } 724 | 725 | pub(crate) fn st_in(&self, port: usize) -> StUser<'g, S> { 726 | let sig = self.data().sig(); 727 | assert!(port < sig.st_ins); 728 | StUser(self.ctxt.user_ref(UserId::In { 729 | node: self.id, 730 | index: sig.val_ins + port, 731 | })) 732 | } 733 | 734 | pub(crate) fn st_out(&self, port: usize) -> StOrigin<'g, S> { 735 | let sig = self.data().sig(); 736 | assert!(port < sig.st_outs); 737 | StOrigin(self.ctxt.origin_ref(OriginId::Out { 738 | node: self.id, 739 | index: sig.val_outs + port, 740 | })) 741 | } 742 | } 743 | 744 | #[derive(Copy, Clone, PartialEq, Eq)] 745 | pub(crate) struct User<'g, S> { 746 | ctxt: &'g NodeCtxt, 747 | user_id: UserId, 748 | } 749 | 750 | impl<'g, S: fmt::Debug> fmt::Debug for User<'g, S> { 751 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 752 | write!(f, "{:?}", self.user_id) 753 | } 754 | } 755 | 756 | impl<'g, S> User<'g, S> { 757 | pub(crate) fn id(&self) -> UserId { 758 | self.user_id 759 | } 760 | 761 | pub(crate) fn data(&self) -> Ref<'g, UserData> { 762 | self.ctxt.user_data(self.user_id) 763 | } 764 | 765 | pub(crate) fn origin(&self) -> Origin<'g, S> { 766 | let origin_id = self.data().origin.get().unwrap(); 767 | self.ctxt.origin_ref(origin_id) 768 | } 769 | } 770 | 771 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 772 | pub(crate) struct Origin<'g, S> { 773 | ctxt: &'g NodeCtxt, 774 | origin_id: OriginId, 775 | } 776 | 777 | impl<'g, S: fmt::Debug> fmt::Debug for Origin<'g, S> { 778 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 779 | write!(f, "{:?}", self.origin_id) 780 | } 781 | } 782 | 783 | impl<'g, S> Origin<'g, S> { 784 | pub(crate) fn id(&self) -> OriginId { 785 | self.origin_id 786 | } 787 | 788 | pub(crate) fn data(&self) -> Ref<'g, OriginData> { 789 | self.ctxt.origin_data(self.origin_id) 790 | } 791 | 792 | pub(crate) fn producer(&self) -> Node<'g, S> { 793 | match self.origin_id { 794 | OriginId::Out { node, .. } => self.ctxt.node_ref(node), 795 | _ => unimplemented!(), 796 | } 797 | } 798 | 799 | pub(crate) fn users(&self) -> Users<'g, S> { 800 | let user_ref = |user_id| self.ctxt.user_ref(user_id); 801 | Users { 802 | first_and_last: self 803 | .data() 804 | .users 805 | .get() 806 | .map(|users| (user_ref(users.first), user_ref(users.last))), 807 | } 808 | } 809 | } 810 | 811 | pub(crate) struct Users<'g, S> { 812 | first_and_last: Option<(User<'g, S>, User<'g, S>)>, 813 | } 814 | 815 | impl<'g, S> Iterator for Users<'g, S> { 816 | type Item = User<'g, S>; 817 | 818 | fn next(&mut self) -> Option { 819 | match self.first_and_last.take() { 820 | Some((first, last)) => { 821 | if first.id() != last.id() { 822 | if let Some(next_user) = first.data().next_user.get() { 823 | self.first_and_last = Some((first.ctxt.user_ref(next_user), last)); 824 | } 825 | } 826 | Some(first) 827 | } 828 | None => None, 829 | } 830 | } 831 | } 832 | 833 | impl<'g, S> DoubleEndedIterator for Users<'g, S> { 834 | fn next_back(&mut self) -> Option { 835 | match self.first_and_last.take() { 836 | Some((first, last)) => { 837 | if first.id() != last.id() { 838 | if let Some(prev_user) = last.data().prev_user.get() { 839 | self.first_and_last = Some((first, last.ctxt.user_ref(prev_user))); 840 | } 841 | } 842 | Some(last) 843 | } 844 | None => None, 845 | } 846 | } 847 | } 848 | 849 | #[derive(Copy, Clone, PartialEq, Debug)] 850 | pub(crate) struct ValUser<'g, S>(User<'g, S>); 851 | 852 | impl<'g, S> ValUser<'g, S> { 853 | fn id(&self) -> UserId { 854 | self.0.id() 855 | } 856 | 857 | fn connect(&self, val_origin: ValOrigin<'g, S>) { 858 | assert!(self.0.ctxt == val_origin.0.ctxt); 859 | self.0.ctxt.connect_ports(self.id(), val_origin.id()); 860 | } 861 | 862 | pub(crate) fn origin(&self) -> ValOrigin<'g, S> { 863 | ValOrigin(self.0.origin()) 864 | } 865 | } 866 | 867 | #[derive(Copy, Clone, PartialEq, Debug)] 868 | pub(crate) struct StUser<'g, S>(User<'g, S>); 869 | 870 | impl<'g, S> StUser<'g, S> { 871 | fn id(&self) -> UserId { 872 | self.0.id() 873 | } 874 | 875 | fn connect(&self, st_origin: StOrigin<'g, S>) { 876 | assert!(self.0.ctxt == st_origin.0.ctxt); 877 | self.0.ctxt.connect_ports(self.id(), st_origin.id()); 878 | } 879 | 880 | pub(crate) fn origin(&self) -> StOrigin<'g, S> { 881 | StOrigin(self.0.origin()) 882 | } 883 | } 884 | 885 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] 886 | pub(crate) struct ValOrigin<'g, S>(Origin<'g, S>); 887 | 888 | impl<'g, S> ValOrigin<'g, S> { 889 | fn id(&self) -> OriginId { 890 | self.0.id() 891 | } 892 | 893 | fn connect(&self, val_user: ValUser<'g, S>) { 894 | assert!(self.0.ctxt == val_user.0.ctxt); 895 | self.0.ctxt.connect_ports(val_user.id(), self.id()); 896 | } 897 | 898 | pub(crate) fn users(&self) -> impl DoubleEndedIterator> { 899 | self.0.users().map(ValUser) 900 | } 901 | 902 | pub(crate) fn producer(&self) -> Node<'g, S> { 903 | self.0.producer() 904 | } 905 | } 906 | 907 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 908 | pub(crate) struct StOrigin<'g, S>(Origin<'g, S>); 909 | 910 | impl<'g, S> StOrigin<'g, S> { 911 | fn id(&self) -> OriginId { 912 | self.0.id() 913 | } 914 | 915 | fn connect(&self, st_user: StUser<'g, S>) { 916 | assert!(self.0.ctxt == st_user.0.ctxt); 917 | self.0.ctxt.connect_ports(st_user.id(), self.id()); 918 | } 919 | 920 | pub(crate) fn users(&self) -> impl DoubleEndedIterator> { 921 | self.0.users().map(StUser) 922 | } 923 | 924 | pub(crate) fn producer(&self) -> Node<'g, S> { 925 | self.0.producer() 926 | } 927 | } 928 | 929 | #[cfg(test)] 930 | mod test { 931 | use super::{NodeCtxt, NodeKind, OriginId, RegionId, RegionSigS, Sig, SigS}; 932 | 933 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 934 | enum TestData { 935 | Lit(u32), 936 | Neg, 937 | St, 938 | BinAdd, 939 | BinSub, 940 | LoadOffset, 941 | Load, 942 | Store, 943 | OpA, 944 | OpB, 945 | OpC, 946 | } 947 | 948 | impl Sig for TestData { 949 | fn sig(&self) -> SigS { 950 | match self { 951 | TestData::Lit(..) => SigS { 952 | val_outs: 1, 953 | ..SigS::default() 954 | }, 955 | TestData::Neg | TestData::OpA | TestData::OpB | TestData::OpC => SigS { 956 | val_ins: 1, 957 | val_outs: 1, 958 | ..SigS::default() 959 | }, 960 | TestData::St => SigS { 961 | st_outs: 1, 962 | ..SigS::default() 963 | }, 964 | TestData::BinAdd | TestData::BinSub => SigS { 965 | val_ins: 2, 966 | val_outs: 1, 967 | ..SigS::default() 968 | }, 969 | TestData::LoadOffset => SigS { 970 | val_ins: 2, 971 | val_outs: 1, 972 | st_ins: 1, 973 | st_outs: 1, 974 | ..SigS::default() 975 | }, 976 | TestData::Load => SigS { 977 | val_ins: 1, 978 | val_outs: 1, 979 | st_ins: 1, 980 | st_outs: 0, 981 | ..SigS::default() 982 | }, 983 | TestData::Store => SigS { 984 | val_ins: 2, 985 | val_outs: 0, 986 | st_ins: 1, 987 | st_outs: 1, 988 | ..SigS::default() 989 | }, 990 | } 991 | } 992 | } 993 | 994 | #[test] 995 | fn create_single_node() { 996 | let ncx = NodeCtxt::new(); 997 | let n0 = ncx.mk_node_with(NodeKind::Op(TestData::Lit(0)), &[]); 998 | assert_eq!(0, ncx.node_data(n0).ins.len()); 999 | assert_eq!(1, ncx.node_data(n0).outs.len()); 1000 | } 1001 | 1002 | #[test] 1003 | fn create_node_with_an_input() { 1004 | let ncx = NodeCtxt::new(); 1005 | let n0 = ncx.mk_node_with(NodeKind::Op(TestData::Lit(0)), &[]); 1006 | let n1 = ncx.mk_node_with( 1007 | NodeKind::Op(TestData::Neg), 1008 | &[OriginId::Out { node: n0, index: 0 }], 1009 | ); 1010 | 1011 | assert_eq!( 1012 | Some(n0), 1013 | ncx.node_data(n1).ins[0].origin.get().unwrap().node_id() 1014 | ); 1015 | } 1016 | 1017 | #[test] 1018 | fn create_node_with_an_input_using_builder() { 1019 | let ncx = NodeCtxt::new(); 1020 | 1021 | let n0 = ncx.mk_node(TestData::Lit(0)); 1022 | let n1 = ncx 1023 | .node_builder(TestData::Neg) 1024 | .operand(n0.val_out(0)) 1025 | .finish(); 1026 | 1027 | assert_eq!( 1028 | Some(n0.id), 1029 | n1.data().ins[0].origin.get().unwrap().node_id() 1030 | ); 1031 | 1032 | assert_eq!(n0.val_out(0), n1.val_in(0).origin()); 1033 | } 1034 | 1035 | #[test] 1036 | fn create_node_with_input_ports() { 1037 | let ncx = NodeCtxt::new(); 1038 | 1039 | let n0 = ncx.mk_node_with(NodeKind::Op(TestData::Lit(2)), &[]); 1040 | 1041 | assert_eq!(0, ncx.node_data(n0).ins.len()); 1042 | assert_eq!(1, ncx.node_data(n0).outs.len()); 1043 | 1044 | let n1 = ncx.mk_node_with(NodeKind::Op(TestData::Lit(3)), &[]); 1045 | 1046 | assert_eq!(0, ncx.node_data(n1).ins.len()); 1047 | assert_eq!(1, ncx.node_data(n1).outs.len()); 1048 | 1049 | let n2 = ncx.mk_node_with( 1050 | NodeKind::Op(TestData::BinAdd), 1051 | &[ 1052 | OriginId::Out { node: n0, index: 0 }, 1053 | OriginId::Out { node: n1, index: 0 }, 1054 | ], 1055 | ); 1056 | 1057 | assert_eq!(2, ncx.node_data(n2).ins.len()); 1058 | assert_eq!(1, ncx.node_data(n2).outs.len()); 1059 | 1060 | assert_eq!( 1061 | Some(n2), 1062 | ncx.node_data(n0).outs[0] 1063 | .users 1064 | .get() 1065 | .unwrap() 1066 | .first 1067 | .node_id() 1068 | ); 1069 | assert_eq!( 1070 | Some(n2), 1071 | ncx.node_data(n0).outs[0] 1072 | .users 1073 | .get() 1074 | .unwrap() 1075 | .last 1076 | .node_id() 1077 | ); 1078 | assert_eq!( 1079 | Some(n2), 1080 | ncx.node_data(n1).outs[0] 1081 | .users 1082 | .get() 1083 | .unwrap() 1084 | .first 1085 | .node_id() 1086 | ); 1087 | assert_eq!( 1088 | Some(n2), 1089 | ncx.node_data(n1).outs[0] 1090 | .users 1091 | .get() 1092 | .unwrap() 1093 | .last 1094 | .node_id() 1095 | ); 1096 | } 1097 | 1098 | #[test] 1099 | fn create_node_operands_and_states_using_builder_single() { 1100 | let ncx = NodeCtxt::new(); 1101 | 1102 | let n0 = ncx.mk_node(TestData::Lit(2)); 1103 | let n1 = ncx.mk_node(TestData::Lit(3)); 1104 | let n2 = ncx.mk_node(TestData::St); 1105 | 1106 | let n3 = ncx 1107 | .node_builder(TestData::LoadOffset) 1108 | .operand(n0.val_out(0)) 1109 | .operand(n1.val_out(0)) 1110 | .state(n2.st_out(0)) 1111 | .finish(); 1112 | 1113 | assert_eq!(n0.val_out(0), n3.val_in(0).origin()); 1114 | assert_eq!(n1.val_out(0), n3.val_in(1).origin()); 1115 | assert_eq!(n2.st_out(0), n3.st_in(0).origin()); 1116 | } 1117 | 1118 | #[test] 1119 | fn create_node_operands_and_states_using_builder_slice() { 1120 | let ncx = NodeCtxt::new(); 1121 | 1122 | let n0 = ncx.mk_node(TestData::Lit(2)); 1123 | let n1 = ncx.mk_node(TestData::Lit(3)); 1124 | let n2 = ncx.mk_node(TestData::St); 1125 | 1126 | let n3 = ncx 1127 | .node_builder(TestData::LoadOffset) 1128 | .operands(&[n0.val_out(0), n1.val_out(0)]) 1129 | .states(&[n2.st_out(0)]) 1130 | .finish(); 1131 | 1132 | assert_eq!(n0.val_out(0), n3.val_in(0).origin()); 1133 | assert_eq!(n1.val_out(0), n3.val_in(1).origin()); 1134 | assert_eq!(n2.st_out(0), n3.st_in(0).origin()); 1135 | } 1136 | 1137 | #[test] 1138 | fn users_iterator() { 1139 | // TODO: state port users 1140 | let ncx = NodeCtxt::new(); 1141 | 1142 | let n0 = ncx.mk_node(TestData::Lit(0)); 1143 | 1144 | let n1 = ncx 1145 | .node_builder(TestData::OpA) 1146 | .operand(n0.val_out(0)) 1147 | .finish(); 1148 | 1149 | let n2 = ncx 1150 | .node_builder(TestData::OpB) 1151 | .operand(n0.val_out(0)) 1152 | .finish(); 1153 | 1154 | let n3 = ncx 1155 | .node_builder(TestData::OpC) 1156 | .operand(n0.val_out(0)) 1157 | .finish(); 1158 | 1159 | let mut users = n0.val_out(0).users(); 1160 | 1161 | assert_eq!(Some(n1.val_in(0)), users.next()); 1162 | assert_eq!(Some(n2.val_in(0)), users.next()); 1163 | assert_eq!(Some(n3.val_in(0)), users.next()); 1164 | assert_eq!(None, users.next()); 1165 | } 1166 | 1167 | #[test] 1168 | fn users_double_ended_iterator() { 1169 | // TODO: state port users 1170 | let ncx = NodeCtxt::new(); 1171 | 1172 | let n0 = ncx.mk_node(TestData::Lit(0)); 1173 | 1174 | let n1 = ncx 1175 | .node_builder(TestData::OpA) 1176 | .operand(n0.val_out(0)) 1177 | .finish(); 1178 | 1179 | let n2 = ncx 1180 | .node_builder(TestData::OpB) 1181 | .operand(n0.val_out(0)) 1182 | .finish(); 1183 | 1184 | let n3 = ncx 1185 | .node_builder(TestData::OpC) 1186 | .operand(n0.val_out(0)) 1187 | .finish(); 1188 | 1189 | let mut users = n0.val_out(0).users(); 1190 | 1191 | assert_eq!(Some(n1.val_in(0)), users.next()); 1192 | assert_eq!(Some(n3.val_in(0)), users.next_back()); 1193 | assert_eq!(Some(n2.val_in(0)), users.next_back()); 1194 | assert_eq!(None, users.next()); 1195 | assert_eq!(None, users.next_back()); 1196 | } 1197 | 1198 | #[test] 1199 | fn reuse_existing_eq_nodes_at_creation() { 1200 | let ncx = NodeCtxt::new(); 1201 | 1202 | let n0 = ncx.mk_node(TestData::Lit(2)); 1203 | let n1 = ncx.mk_node(TestData::Lit(3)); 1204 | let n2 = ncx.mk_node(TestData::Lit(2)); 1205 | 1206 | assert_eq!(n0.id, n2.id); 1207 | assert_ne!(n0.id, n1.id); 1208 | assert_ne!(n1.id, n2.id); 1209 | 1210 | let n3 = ncx 1211 | .node_builder(TestData::BinAdd) 1212 | .operand(n0.val_out(0)) 1213 | .operand(n1.val_out(0)) 1214 | .finish(); 1215 | 1216 | let n4 = ncx 1217 | .node_builder(TestData::BinAdd) 1218 | .operand(n0.val_out(0)) 1219 | .operand(n2.val_out(0)) 1220 | .finish(); 1221 | 1222 | let n5 = ncx 1223 | .node_builder(TestData::BinAdd) 1224 | .operand(n2.val_out(0)) 1225 | .operand(n1.val_out(0)) 1226 | .finish(); 1227 | 1228 | assert_ne!(n3.id, n4.id); 1229 | assert_ne!(n4.id, n5.id); 1230 | assert_eq!(n3.id, n5.id); 1231 | } 1232 | 1233 | #[test] 1234 | fn printing_load_store_nodes() { 1235 | let ncx = NodeCtxt::new(); 1236 | 1237 | let n_x = ncx.mk_node(TestData::Lit(100)); 1238 | let n_y = ncx.mk_node(TestData::Lit(104)); 1239 | let n_4 = ncx.mk_node(TestData::Lit(4)); 1240 | let n_5 = ncx.mk_node(TestData::Lit(5)); 1241 | let n_s = ncx.mk_node(TestData::St); 1242 | 1243 | let n_l1 = ncx 1244 | .node_builder(TestData::Load) 1245 | .operand(n_x.val_out(0)) 1246 | .state(n_s.st_out(0)) 1247 | .finish(); 1248 | 1249 | let n_add_4 = ncx 1250 | .node_builder(TestData::BinAdd) 1251 | .operand(n_l1.val_out(0)) 1252 | .operand(n_4.val_out(0)) 1253 | .finish(); 1254 | 1255 | let n_store1 = ncx 1256 | .node_builder(TestData::Store) 1257 | .operand(n_x.val_out(0)) 1258 | .operand(n_add_4.val_out(0)) 1259 | .state(n_s.st_out(0)) 1260 | .finish(); 1261 | 1262 | let n_l2 = ncx 1263 | .node_builder(TestData::Load) 1264 | .operand(n_y.val_out(0)) 1265 | .state(n_store1.st_out(0)) 1266 | .finish(); 1267 | 1268 | let n_add_5 = ncx 1269 | .node_builder(TestData::BinAdd) 1270 | .operand(n_l2.val_out(0)) 1271 | .operand(n_5.val_out(0)) 1272 | .finish(); 1273 | 1274 | let _ = ncx 1275 | .node_builder(TestData::Store) 1276 | .operand(n_y.val_out(0)) 1277 | .operand(n_add_5.val_out(0)) 1278 | .state(n_store1.st_out(0)) 1279 | .finish(); 1280 | 1281 | let mut buffer = Vec::new(); 1282 | ncx.print(&mut buffer).unwrap(); 1283 | let content = String::from_utf8(buffer).unwrap(); 1284 | assert_eq!( 1285 | content, 1286 | r#"digraph rvsdg { 1287 | node [shape=record] 1288 | edge [arrowhead=none] 1289 | n0 [label="{{Lit(100)}|{0}}"] 1290 | n1 [label="{{Lit(104)}|{0}}"] 1291 | n2 [label="{{Lit(4)}|{0}}"] 1292 | n3 [label="{{Lit(5)}|{0}}"] 1293 | n4 [label="{{St}|{0}}"] 1294 | n5 [label="{{0|1}|{Load}|{0}}"] 1295 | n0:o0 -> n5:i0 [color=blue] 1296 | n4:o0 -> n5:i1 [style=dashed, color=red] 1297 | n6 [label="{{0|1}|{BinAdd}|{0}}"] 1298 | n5:o0 -> n6:i0 [color=blue] 1299 | n2:o0 -> n6:i1 [color=blue] 1300 | n7 [label="{{0|1|2}|{Store}|{0}}"] 1301 | n0:o0 -> n7:i0 [color=blue] 1302 | n6:o0 -> n7:i1 [color=blue] 1303 | n4:o0 -> n7:i2 [style=dashed, color=red] 1304 | n8 [label="{{0|1}|{Load}|{0}}"] 1305 | n1:o0 -> n8:i0 [color=blue] 1306 | n7:o0 -> n8:i1 [style=dashed, color=red] 1307 | n9 [label="{{0|1}|{BinAdd}|{0}}"] 1308 | n8:o0 -> n9:i0 [color=blue] 1309 | n3:o0 -> n9:i1 [color=blue] 1310 | n10 [label="{{0|1|2}|{Store}|{0}}"] 1311 | n1:o0 -> n10:i0 [color=blue] 1312 | n9:o0 -> n10:i1 [color=blue] 1313 | n7:o0 -> n10:i2 [style=dashed, color=red] 1314 | } 1315 | "# 1316 | ); 1317 | } 1318 | 1319 | #[test] 1320 | fn manually_connecting_ports() { 1321 | let ncx = NodeCtxt::new(); 1322 | 1323 | let lit_a = ncx.create_node(NodeKind::Op(TestData::Lit(2)), RegionId(0)); 1324 | let lit_b = ncx.create_node(NodeKind::Op(TestData::Lit(3)), RegionId(0)); 1325 | let add = ncx.create_node(NodeKind::Op(TestData::BinAdd), RegionId(0)); 1326 | 1327 | add.val_in(0).connect(lit_a.val_out(0)); 1328 | add.val_in(1).connect(lit_b.val_out(0)); 1329 | 1330 | let mut users = lit_a.val_out(0).users(); 1331 | 1332 | assert_eq!(Some(add.val_in(0)), users.next()); 1333 | assert_eq!(None, users.next()); 1334 | 1335 | let mut users = lit_b.val_out(0).users(); 1336 | 1337 | assert_eq!(Some(add.val_in(1)), users.next()); 1338 | assert_eq!(None, users.next()); 1339 | } 1340 | 1341 | #[test] 1342 | #[should_panic] 1343 | fn regions() { 1344 | let ncx = NodeCtxt::::new(); 1345 | 1346 | let omega_id = ncx.mk_node_with( 1347 | NodeKind::Omega { 1348 | imports: 1, 1349 | exports: 1, 1350 | }, 1351 | &[], 1352 | ); 1353 | 1354 | let r0_id = ncx.mk_region_for_node( 1355 | omega_id, 1356 | RegionSigS { 1357 | val_args: 2, 1358 | val_res: 1, 1359 | ..RegionSigS::default() 1360 | }, 1361 | ); 1362 | } 1363 | 1364 | #[test] 1365 | fn bug_traverse() { 1366 | let ncx = NodeCtxt::new(); 1367 | 1368 | let n0 = ncx.mk_node(TestData::Lit(0)); 1369 | let n1 = ncx 1370 | .node_builder(TestData::Neg) 1371 | .operand(n0.val_out(0)) 1372 | .finish(); 1373 | let o = n1.val_in(0).origin().producer(); 1374 | let op = *o.kind(); 1375 | let n2 = ncx 1376 | .node_builder(TestData::Neg) 1377 | .operand(n1.val_out(0)) 1378 | .finish(); 1379 | } 1380 | 1381 | #[test] 1382 | fn do_not_intern_stateful_nodes() { 1383 | #[derive(Clone, Hash, PartialEq, Eq)] 1384 | enum Inst { 1385 | Val(usize), 1386 | Stateful, 1387 | Stateless, 1388 | } 1389 | 1390 | impl Sig for Inst { 1391 | fn sig(&self) -> SigS { 1392 | match self { 1393 | Inst::Val(..) => SigS { 1394 | val_outs: 1, 1395 | ..SigS::default() 1396 | }, 1397 | Inst::Stateful => SigS { 1398 | val_ins: 1, 1399 | val_outs: 1, 1400 | st_outs: 1, 1401 | ..SigS::default() 1402 | }, 1403 | Inst::Stateless => SigS { 1404 | val_ins: 1, 1405 | st_ins: 1, 1406 | val_outs: 1, 1407 | ..SigS::default() 1408 | } 1409 | } 1410 | } 1411 | } 1412 | 1413 | let ncx = NodeCtxt::new(); 1414 | 1415 | let n_val = ncx.mk_node(Inst::Val(42)); 1416 | 1417 | let n_stateful_1 = ncx.node_builder(Inst::Stateful) 1418 | .operand(n_val.val_out(0)) 1419 | .finish(); 1420 | 1421 | let n_stateful_2 = ncx.node_builder(Inst::Stateful) 1422 | .operand(n_val.val_out(0)) 1423 | .finish(); 1424 | 1425 | assert_ne!(n_stateful_1.id(), n_stateful_2.id()); 1426 | 1427 | let n_stateless_1 = ncx.node_builder(Inst::Stateless) 1428 | .operand(n_stateful_1.val_out(0)) 1429 | .state(n_stateful_1.st_out(0)) 1430 | .finish(); 1431 | 1432 | let n_stateless_2 = ncx.node_builder(Inst::Stateless) 1433 | .operand(n_stateful_1.val_out(0)) 1434 | .state(n_stateful_1.st_out(0)) 1435 | .finish(); 1436 | 1437 | assert_eq!(n_stateless_1.id(), n_stateless_2.id()); 1438 | 1439 | let n_stateless_3 = ncx.node_builder(Inst::Stateless) 1440 | .operand(n_stateful_2.val_out(0)) 1441 | .state(n_stateful_2.st_out(0)) 1442 | .finish(); 1443 | 1444 | assert_ne!(n_stateless_3.id(), n_stateless_1.id()); 1445 | assert_ne!(n_stateless_3.id(), n_stateless_2.id()); 1446 | } 1447 | } 1448 | --------------------------------------------------------------------------------