diff --git a/Cargo.lock b/Cargo.lock index 45bd11e6..a6b6dce9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "addr2line" version = "0.14.1" @@ -30,6 +32,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" + [[package]] name = "atomic-option" version = "0.1.2" @@ -194,6 +202,119 @@ dependencies = [ "winapi", ] +[[package]] +name = "cranelift" +version = "0.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955691177386ac7e6a5d816a74cce0c397a03001b8614de2dbc98bb9ca10f613" +dependencies = [ + "cranelift-codegen", + "cranelift-frontend", +] + +[[package]] +name = "cranelift-bforest" +version = "0.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07f641ec9146b7d7498d78cd832007d66ca44a9b61f23474d1fb78e5a3701e99" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd1f2c0cd4ac12c954116ab2e26e40df0d51db322a855b5664fa208bc32d6686" +dependencies = [ + "byteorder", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "log", + "regalloc", + "smallvec", + "target-lexicon", + "thiserror", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "105e11b2f0ff7ac81f80dd05ec938ce529a75e36f3d598360d806bb5bfa75e5a" +dependencies = [ + "cranelift-codegen-shared", + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e5eba2c1858d50abf023be4d88bd0450cb12d4ec2ba3ffac56353e6d09caf2" + +[[package]] +name = "cranelift-entity" +version = "0.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79fa6fdd77a8d317763cd21668d3e72b96e09ac8a974326c6149f7de5aafa8ed" + +[[package]] +name = "cranelift-frontend" +version = "0.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae11da9ca99f987c29e3eb39ebe10e9b879ecca30f3aeaee13db5e8e02b80fb6" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-jit" +version = "0.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2daf41b7523140b15d994ffd24527a121ae2f7cac3d08fa2f027b5f4ac75a2" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-entity", + "cranelift-module", + "cranelift-native", + "errno", + "libc", + "log", + "region", + "target-lexicon", + "winapi", +] + +[[package]] +name = "cranelift-module" +version = "0.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edabf3bf10db71f884cd30bb9a03acff1c2795791a806c49f7c590f36ac76531" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-entity", + "log", + "thiserror", +] + +[[package]] +name = "cranelift-native" +version = "0.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "100ca4810058e23a5c4dcaedfa25289d1853f4a899d0960265aa7c54a4789351" +dependencies = [ + "cranelift-codegen", + "target-lexicon", +] + [[package]] name = "crc32fast" version = "1.2.0" @@ -335,6 +456,27 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +[[package]] +name = "errno" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +dependencies = [ + "gcc", + "libc", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -369,6 +511,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + [[package]] name = "getrandom" version = "0.2.0" @@ -496,6 +644,15 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -512,6 +669,9 @@ dependencies = [ "bus", "byteorder", "chrono", + "cranelift", + "cranelift-jit", + "cranelift-module", "criterion", "ctrlc", "fern", @@ -754,6 +914,17 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +[[package]] +name = "regalloc" +version = "0.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" +dependencies = [ + "log", + "rustc-hash", + "smallvec", +] + [[package]] name = "regex" version = "1.4.3" @@ -781,6 +952,18 @@ version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" +[[package]] +name = "region" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +dependencies = [ + "bitflags", + "libc", + "mach", + "winapi", +] + [[package]] name = "rusqlite" version = "0.24.2" @@ -802,6 +985,12 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -890,9 +1079,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.3.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "syn" @@ -905,6 +1094,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "target-lexicon" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ae3b39281e4b14b8123bdbaddd472b7dfe215e444181f2f9d2443c2444f834" + [[package]] name = "textwrap" version = "0.11.0" @@ -914,6 +1109,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 33db8645..f78d6517 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,3 +45,7 @@ regex = "1.4.3" lazy_static = "1.4.0" backtrace = "0.3.56" rusqlite = { version="0.24.2", features=["bundled"] } +# TODO: Put the cranelift stuff behind a feature flag +cranelift = "0.73.0" +cranelift-jit = "0.73.0" +cranelift-module = "0.73.0" diff --git a/docs/Redpiler.md b/docs/Redpiler.md index 694502bf..4c478485 100644 --- a/docs/Redpiler.md +++ b/docs/Redpiler.md @@ -39,4 +39,20 @@ TODO ## Native Code generation -TODO +Each node will generate 2 functions: update and tick. Each node will have a global memory location holding their state. +Example of generated code in C form: +```c +struct State { + // Information such as powered or output strength +} + +struct State n0; + +void n0_update() { + // ... +} + +void n0_tick() { + // ... +} +``` \ No newline at end of file diff --git a/src/plot/mod.rs b/src/plot/mod.rs index 61204a16..8af67a3a 100644 --- a/src/plot/mod.rs +++ b/src/plot/mod.rs @@ -586,7 +586,7 @@ impl Plot { fn update(&mut self) { if self.redpiler.is_active { - let changes: Vec<(BlockPos, Block)> = self.redpiler.change_queue.drain(..).collect(); + let changes: Vec<(BlockPos, Block)> = self.redpiler.block_changes().drain(..).collect(); for (pos, block) in changes { self.set_block(pos, block); } @@ -822,10 +822,10 @@ impl Plot { always_running: bool, initial_player: Option, ) { - let mut plot = Plot::load(x, z, rx, tx, priv_rx, always_running); thread::Builder::new() .name(format!("p{},{}", x, z)) .spawn(move || { + let mut plot = Plot::load(x, z, rx, tx, priv_rx, always_running); plot.run(initial_player); }) .unwrap(); diff --git a/src/redpiler/codegen/cranelift.rs b/src/redpiler/codegen/cranelift.rs index e69de29b..9ca83344 100644 --- a/src/redpiler/codegen/cranelift.rs +++ b/src/redpiler/codegen/cranelift.rs @@ -0,0 +1,55 @@ +use super::JITBackend; +use crate::blocks::{self, BlockPos}; +use crate::redpiler::Node; +use crate::world::TickEntry; +use cranelift::prelude::*; +use cranelift_jit::{JITBuilder, JITModule}; +use cranelift_module::{DataContext, Module}; + +struct CraneliftBackend { + builder_context: FunctionBuilderContext, + ctx: codegen::Context, + data_ctx: DataContext, + module: JITModule, +} + +impl Default for CraneliftBackend { + fn default() -> Self { + let builder = JITBuilder::new(cranelift_module::default_libcall_names()); + let module = JITModule::new(builder); + Self { + builder_context: FunctionBuilderContext::new(), + ctx: module.make_context(), + data_ctx: DataContext::new(), + module, + } + } +} + +impl CraneliftBackend { + fn translate_node(&mut self, idx: usize, node: &Node) {} +} + +impl JITBackend for CraneliftBackend { + fn compile(&mut self, nodes: Vec, ticks: Vec) { + let mut backend: CraneliftBackend = Default::default(); + + for (idx, node) in nodes.iter().enumerate() { + backend.translate_node(idx, node); + } + + backend.module.finalize_definitions(); + } + + fn tick(&mut self) {} + + fn on_use_block(&mut self, pos: BlockPos) {} + + fn reset(&mut self) -> Vec { + Vec::new() + } + + fn block_changes(&mut self) -> &mut Vec<(BlockPos, blocks::Block)> { + unimplemented!(); + } +} diff --git a/src/redpiler/codegen/direct.rs b/src/redpiler/codegen/direct.rs new file mode 100644 index 00000000..d5778b7f --- /dev/null +++ b/src/redpiler/codegen/direct.rs @@ -0,0 +1,337 @@ +//! The direct backend does not do code generation and executes the graph directly + +use super::JITBackend; +use crate::blocks::{Block, BlockPos, ComparatorMode}; +use crate::redpiler::{LinkType, Node, NodeId}; +use crate::world::{TickEntry, TickPriority}; +use log::warn; +use std::collections::HashMap; + +struct RPTickEntry { + ticks_left: u32, + tick_priority: TickPriority, + node: NodeId, +} + +#[derive(Default)] +pub struct DirectBackend { + change_queue: Vec<(BlockPos, Block)>, + nodes: Vec, + to_be_ticked: Vec, + pos_map: HashMap, +} + +impl DirectBackend { + fn schedule_tick(&mut self, node_id: NodeId, delay: u32, priority: TickPriority) { + self.to_be_ticked.push(RPTickEntry { + node: node_id, + ticks_left: delay, + tick_priority: priority, + }); + self.to_be_ticked + .sort_by_key(|e| (e.ticks_left, e.tick_priority.clone())); + } + + fn pending_tick_at(&mut self, node: NodeId) -> bool { + self.to_be_ticked.iter().any(|e| e.node == node) + } + + fn set_node(&mut self, node_id: NodeId, new_block: Block, update: bool) { + let node = &mut self.nodes[node_id.index]; + node.state = new_block; + let pos = node.pos; + if update { + for update in node.updates.clone() { + self.update_node(update); + } + self.update_node(node_id); + } + self.change_queue.push((pos, new_block)); + } + + fn comparator_should_be_powered( + &mut self, + mode: ComparatorMode, + input_strength: u8, + power_on_sides: u8, + ) -> bool { + if input_strength == 0 { + false + } else if input_strength > power_on_sides { + true + } else { + power_on_sides == input_strength && mode == ComparatorMode::Compare + } + } + + fn calculate_comparator_output( + &mut self, + mode: ComparatorMode, + input_strength: u8, + power_on_sides: u8, + ) -> u8 { + if mode == ComparatorMode::Subtract { + input_strength.saturating_sub(power_on_sides) + } else if input_strength >= power_on_sides { + input_strength + } else { + 0 + } + } + + fn update_node(&mut self, node_id: NodeId) { + let node = &self.nodes[node_id.index]; + + let mut input_power = 0; + let mut side_input_power = 0; + for link in &node.inputs { + let power = match link.ty { + LinkType::Default => &mut input_power, + LinkType::Side => &mut side_input_power, + }; + *power = (*power).max( + self.nodes[link.end.index] + .get_output_power() + .saturating_sub(link.weight), + ); + } + + let facing_diode = node.facing_diode; + let comparator_output = node.comparator_output; + + match node.state { + Block::RedstoneRepeater { mut repeater } => { + let should_be_locked = side_input_power > 0; + if !repeater.locked && should_be_locked { + repeater.locked = true; + self.set_node(node_id, Block::RedstoneRepeater { repeater }, false); + } else if repeater.locked && !should_be_locked { + repeater.locked = false; + self.set_node(node_id, Block::RedstoneRepeater { repeater }, false); + } + + if !repeater.locked && !self.pending_tick_at(node_id) { + let should_be_powered = input_power > 0; + if should_be_powered != repeater.powered { + let priority = if facing_diode { + TickPriority::Highest + } else if !should_be_powered { + TickPriority::Higher + } else { + TickPriority::High + }; + self.schedule_tick(node_id, repeater.delay as u32, priority); + } + } + } + Block::RedstoneTorch { lit } => { + if lit == (input_power > 0) && !self.pending_tick_at(node_id) { + self.schedule_tick(node_id, 1, TickPriority::Normal); + } + } + Block::RedstoneComparator { comparator } => { + if self.pending_tick_at(node_id) { + return; + } + let output_power = self.calculate_comparator_output( + comparator.mode, + input_power, + side_input_power, + ); + let old_strength = comparator_output; + if output_power != old_strength + || comparator.powered + != self.comparator_should_be_powered( + comparator.mode, + input_power, + side_input_power, + ) + { + let priority = if facing_diode { + TickPriority::High + } else { + TickPriority::Normal + }; + self.schedule_tick(node_id, 1, priority); + } + } + Block::RedstoneWallTorch { lit, .. } => { + if lit == (input_power > 0) && !self.pending_tick_at(node_id) { + self.schedule_tick(node_id, 1, TickPriority::Normal); + } + } + Block::RedstoneLamp { lit } => { + let should_be_lit = input_power > 0; + if lit && !should_be_lit { + self.schedule_tick(node_id, 2, TickPriority::Normal); + } else if !lit && should_be_lit { + self.set_node(node_id, Block::RedstoneLamp { lit: true }, false); + } + } + Block::RedstoneWire { mut wire } => { + if wire.power != input_power { + wire.power = input_power; + self.set_node(node_id, Block::RedstoneWire { wire }, true); + } + } + _ => {} // panic!("Node {:?} should not be updated!", node.state), + } + } +} + +impl JITBackend for DirectBackend { + fn reset(&mut self) -> Vec { + let mut ticks = Vec::new(); + for entry in self.to_be_ticked.drain(..) { + ticks.push(TickEntry { + ticks_left: entry.ticks_left, + tick_priority: entry.tick_priority, + pos: self.nodes[entry.node.index].pos, + }) + } + + self.nodes.clear(); + self.pos_map.clear(); + + ticks + } + + fn on_use_block(&mut self, pos: BlockPos) { + let node_id = self.pos_map[&pos]; + let node = self.nodes[node_id.index].clone(); + match node.state { + Block::StoneButton { mut button } => { + button.powered = !button.powered; + self.schedule_tick(node_id, 10, TickPriority::Normal); + self.set_node(node_id, Block::StoneButton { button }, true); + } + Block::Lever { mut lever } => { + lever.powered = !lever.powered; + self.set_node(node_id, Block::Lever { lever }, true); + } + _ => warn!("Tried to use a {:?} redpiler node", node.state), + } + } + + fn tick(&mut self) { + for pending in &mut self.to_be_ticked { + pending.ticks_left = pending.ticks_left.saturating_sub(1); + } + while self.to_be_ticked.first().map(|e| e.ticks_left).unwrap_or(1) == 0 { + let entry = self.to_be_ticked.remove(0); + let node_id = entry.node; + let node = self.nodes[node_id.index].clone(); + + let mut input_power = 0u8; + let mut side_input_power = 0u8; + for link in &node.inputs { + let power = match link.ty { + LinkType::Default => &mut input_power, + LinkType::Side => &mut side_input_power, + }; + *power = (*power).max( + self.nodes[link.end.index] + .get_output_power() + .saturating_sub(link.weight), + ); + } + + match node.state { + Block::RedstoneRepeater { mut repeater } => { + if repeater.locked { + continue; + } + + let should_be_powered = input_power > 0; + if repeater.powered && !should_be_powered { + repeater.powered = false; + self.set_node(node_id, Block::RedstoneRepeater { repeater }, true); + } else if !repeater.powered { + repeater.powered = true; + self.set_node(node_id, Block::RedstoneRepeater { repeater }, true); + } + } + Block::RedstoneTorch { lit } => { + let should_be_off = input_power > 0; + if lit && should_be_off { + self.set_node(node_id, Block::RedstoneTorch { lit: false }, true); + } else if !lit && !should_be_off { + self.set_node(node_id, Block::RedstoneTorch { lit: true }, true); + } + } + Block::RedstoneComparator { mut comparator } => { + let new_strength = self.calculate_comparator_output( + comparator.mode, + input_power, + side_input_power, + ); + let old_strength = node.comparator_output; + if new_strength != old_strength || comparator.mode == ComparatorMode::Compare { + self.nodes[node_id.index].comparator_output = new_strength; + let should_be_powered = self.comparator_should_be_powered( + comparator.mode, + input_power, + side_input_power, + ); + let powered = comparator.powered; + if powered && !should_be_powered { + comparator.powered = false; + } else if !powered && should_be_powered { + comparator.powered = true; + } + self.set_node(node_id, Block::RedstoneComparator { comparator }, true); + } + } + Block::RedstoneWallTorch { lit, facing } => { + let should_be_off = input_power > 0; + if lit && should_be_off { + self.set_node( + node_id, + Block::RedstoneWallTorch { lit: false, facing }, + true, + ); + } else if !lit && !should_be_off { + self.set_node( + node_id, + Block::RedstoneWallTorch { lit: true, facing }, + true, + ); + } + } + Block::RedstoneLamp { lit } => { + let should_be_lit = input_power > 0; + if lit && !should_be_lit { + self.set_node(node_id, Block::RedstoneLamp { lit: false }, false); + } + } + Block::StoneButton { mut button } => { + if button.powered { + button.powered = false; + self.set_node(node_id, Block::StoneButton { button }, true); + } + } + _ => warn!("Node {:?} should not be ticked!", node.state), + } + } + } + + fn compile(&mut self, nodes: Vec, ticks: Vec) { + for (i, node) in nodes.iter().enumerate() { + self.pos_map.insert(node.pos, NodeId { index: i }); + } + self.nodes = nodes; + for entry in ticks { + if let Some(node) = self.pos_map.get(&entry.pos) { + self.to_be_ticked.push(RPTickEntry { + ticks_left: entry.ticks_left, + tick_priority: entry.tick_priority, + node: *node, + }); + } + } + } + + fn block_changes(&mut self) -> &mut Vec<(BlockPos, Block)> { + &mut self.change_queue + } +} diff --git a/src/redpiler/codegen/llvm.rs b/src/redpiler/codegen/llvm.rs index e69de29b..8b137891 100644 --- a/src/redpiler/codegen/llvm.rs +++ b/src/redpiler/codegen/llvm.rs @@ -0,0 +1 @@ + diff --git a/src/redpiler/codegen/mod.rs b/src/redpiler/codegen/mod.rs index 88017c46..7a8a3221 100644 --- a/src/redpiler/codegen/mod.rs +++ b/src/redpiler/codegen/mod.rs @@ -1,6 +1,16 @@ -mod cranelift; -mod llvm; +pub mod cranelift; +pub mod direct; +pub mod llvm; -trait CodegenBackend { +use crate::blocks::{Block, BlockPos}; +use crate::world::TickEntry; +use super::Node; + +pub trait JITBackend { + fn compile(&mut self, nodes: Vec, ticks: Vec); + fn tick(&mut self); + fn on_use_block(&mut self, pos: BlockPos); + fn reset(&mut self) -> Vec; + fn block_changes(&mut self) -> &mut Vec<(BlockPos, Block)>; } diff --git a/src/redpiler/mod.rs b/src/redpiler/mod.rs index a2faf98c..6617b922 100644 --- a/src/redpiler/mod.rs +++ b/src/redpiler/mod.rs @@ -1,13 +1,13 @@ mod codegen; use crate::blocks::{ - Block, BlockDirection, BlockEntity, BlockFace, BlockPos, ButtonFace, ComparatorMode, LeverFace, + Block, BlockDirection, BlockEntity, BlockFace, BlockPos, ButtonFace, LeverFace, }; use crate::plot::Plot; -use crate::world::{TickEntry, TickPriority, World}; -use log::warn; +use crate::world::{TickEntry, World}; +use codegen::JITBackend; +use log::{error, warn}; use std::collections::{HashMap, VecDeque}; -use std::fmt::Display; fn is_wire(world: &dyn World, pos: BlockPos) -> bool { matches!(world.get_block(pos), Block::RedstoneWire { .. }) @@ -44,7 +44,7 @@ impl Link { } #[derive(Debug, Clone)] -struct Node { +pub struct Node { pos: BlockPos, state: Block, inputs: Vec, @@ -108,22 +108,22 @@ impl Node { struct InputSearch<'a> { plot: &'a mut Plot, + nodes: &'a mut Vec, pos_map: HashMap, } impl<'a> InputSearch<'a> { - fn new(plot: &mut Plot) -> InputSearch<'_> { - let compiler = &mut plot.redpiler; - let nodes = &mut compiler.nodes; - + fn new(plot: &'a mut Plot, nodes: &'a mut Vec) -> InputSearch<'a> { let mut pos_map = HashMap::new(); for (i, node) in nodes.iter().enumerate() { pos_map.insert(node.pos, NodeId { index: i }); } - compiler.pos_map.clone_from(&pos_map); - - InputSearch { plot, pos_map } + InputSearch { + plot, + pos_map, + nodes, + } } fn provides_weak_power(&self, block: Block, side: BlockFace) -> bool { @@ -356,7 +356,7 @@ impl<'a> InputSearch<'a> { id, true, ); - self.plot.redpiler.nodes[id.index].inputs = inputs; + self.nodes[id.index].inputs = inputs; } Block::RedstoneWallTorch { facing, .. } => { let wall_pos = node.pos.offset(facing.opposite().block_face()); @@ -370,7 +370,7 @@ impl<'a> InputSearch<'a> { id, true, ); - self.plot.redpiler.nodes[id.index].inputs = inputs; + self.nodes[id.index].inputs = inputs; } Block::RedstoneComparator { comparator } => { let facing = comparator.facing; @@ -382,7 +382,7 @@ impl<'a> InputSearch<'a> { let input_pos = node.pos.offset(facing.block_face()); let input_block = self.plot.get_block(input_pos); if input_block.has_comparator_override() { - self.plot.redpiler.nodes[id.index].container_overriding = true; + self.nodes[id.index].container_overriding = true; inputs.push(Link::new( LinkType::Default, id, @@ -399,8 +399,8 @@ impl<'a> InputSearch<'a> { 0 }; - self.plot.redpiler.nodes[id.index].comparator_output = output_strength; - self.plot.redpiler.nodes[id.index].inputs = inputs; + self.nodes[id.index].comparator_output = output_strength; + self.nodes[id.index].inputs = inputs; } Block::RedstoneRepeater { repeater } => { let facing = repeater.facing; @@ -410,11 +410,11 @@ impl<'a> InputSearch<'a> { .map(|l| inputs.push(l)); self.search_repeater_side(id, node.pos, facing.rotate_ccw()) .map(|l| inputs.push(l)); - self.plot.redpiler.nodes[id.index].inputs = inputs; + self.nodes[id.index].inputs = inputs; } Block::RedstoneWire { .. } => { let inputs = self.search_wire(id, node.pos, LinkType::Default, 0); - self.plot.redpiler.nodes[id.index].inputs = inputs; + self.nodes[id.index].inputs = inputs; } Block::RedstoneLamp { .. } => { let mut inputs = Vec::new(); @@ -432,10 +432,10 @@ impl<'a> InputSearch<'a> { ); inputs.append(&mut links); } - self.plot.redpiler.nodes[id.index].inputs = inputs; + self.nodes[id.index].inputs = inputs; } block if block.has_comparator_override() => { - self.plot.redpiler.nodes[id.index].comparator_output = + self.nodes[id.index].comparator_output = block.get_comparator_override(self.plot, node.pos); } _ => {} @@ -443,7 +443,7 @@ impl<'a> InputSearch<'a> { } fn search(&mut self) { - let nodes = self.plot.redpiler.nodes.clone(); + let nodes = self.nodes.clone(); for (i, node) in nodes.into_iter().enumerate() { let id = NodeId { index: i }; self.search_node(id, node); @@ -452,7 +452,7 @@ impl<'a> InputSearch<'a> { // Optimizations against the search graph like wire stripping and dedup go here // Dedup links - let nodes = self.plot.redpiler.nodes.clone(); + let nodes = self.nodes.clone(); for (i, node) in nodes.into_iter().enumerate() { let mut links: Vec = Vec::new(); for link in node.inputs.clone() { @@ -470,26 +470,24 @@ impl<'a> InputSearch<'a> { links.push(link); } } - self.plot.redpiler.nodes[i].inputs = links; + self.nodes[i].inputs = links; } // Remove other inputs to comparators with a comparator overriding container input. - for (i, mut node) in self.plot.redpiler.nodes.clone().into_iter().enumerate() { + for (i, mut node) in self.nodes.clone().into_iter().enumerate() { if node.container_overriding { node.inputs.retain(|link| { link.ty != LinkType::Default - || self.plot.redpiler.nodes[link.end.index] - .state - .has_comparator_override() + || self.nodes[link.end.index].state.has_comparator_override() }); - self.plot.redpiler.nodes[i] = node; + self.nodes[i] = node; } } // Create update links - for (id, node) in self.plot.redpiler.nodes.clone().into_iter().enumerate() { + for (id, node) in self.nodes.clone().into_iter().enumerate() { for input_node in node.inputs { - self.plot.redpiler.nodes[input_node.end.index] + self.nodes[input_node.end.index] .updates .push(NodeId { index: id }); } @@ -497,12 +495,6 @@ impl<'a> InputSearch<'a> { } } -struct RPTickEntry { - ticks_left: u32, - tick_priority: TickPriority, - node: NodeId, -} - #[derive(Default)] pub struct CompilerOptions { pub use_worldedit: bool, @@ -528,13 +520,16 @@ impl CompilerOptions { #[derive(Default)] pub struct Compiler { pub is_active: bool, - pub change_queue: Vec<(BlockPos, Block)>, - nodes: Vec, - to_be_ticked: Vec, - pos_map: HashMap, + jit: Option>, } impl Compiler { + /// Use just-in-time compilation with a `JITBackend` such as `CraneliftBackend` or `LLVMBackend`. + /// Requires recompilation to take effect. + pub fn use_jit(&mut self, jit: Box) { + self.jit = Some(jit); + } + pub fn compile( plot: &mut Plot, options: CompilerOptions, @@ -557,326 +552,69 @@ impl Compiler { ) }; - Compiler::identify_nodes(plot, first_pos, second_pos); - InputSearch::new(plot).search(); + let mut nodes = Compiler::identify_nodes(plot, first_pos, second_pos); + InputSearch::new(plot, &mut nodes).search(); let compiler = &mut plot.redpiler; - - for entry in ticks { - if let Some(node) = compiler.pos_map.get(&entry.pos) { - compiler.to_be_ticked.push(RPTickEntry { - ticks_left: entry.ticks_left, - tick_priority: entry.tick_priority, - node: *node, - }); - } - } - compiler.is_active = true; - // dbg!(&compiler.nodes); - // println!("{}", compiler); - - // TODO: Everything else - } - pub fn reset(&mut self) -> Vec { - let mut ticks = Vec::new(); - for entry in self.to_be_ticked.drain(..) { - ticks.push(TickEntry { - ticks_left: entry.ticks_left, - tick_priority: entry.tick_priority, - pos: self.nodes[entry.node.index].pos, - }) + // TODO: Remove this once there is proper backend switching + if compiler.jit.is_none() { + let jit: Box = Default::default(); + compiler.use_jit(jit); } - self.nodes.clear(); - self.pos_map.clear(); - self.is_active = false; - - ticks - } - - pub fn on_use_block(&mut self, pos: BlockPos) { - let node_id = self.pos_map[&pos]; - let node = self.nodes[node_id.index].clone(); - match node.state { - Block::StoneButton { mut button } => { - button.powered = !button.powered; - self.schedule_tick(node_id, 10, TickPriority::Normal); - self.set_node(node_id, Block::StoneButton { button }, true); - } - Block::Lever { mut lever } => { - lever.powered = !lever.powered; - self.set_node(node_id, Block::Lever { lever }, true); - } - _ => warn!("Tried to use a {:?} redpiler node", node.state), + if let Some(jit) = &mut compiler.jit { + jit.compile(nodes, ticks); + } else { + error!("Cannot compile without JIT variation"); } } - fn schedule_tick(&mut self, node_id: NodeId, delay: u32, priority: TickPriority) { - self.to_be_ticked.push(RPTickEntry { - node: node_id, - ticks_left: delay, - tick_priority: priority, - }); - self.to_be_ticked - .sort_by_key(|e| (e.ticks_left, e.tick_priority.clone())); - } - - fn pending_tick_at(&mut self, node: NodeId) -> bool { - self.to_be_ticked.iter().any(|e| e.node == node) - } - - fn set_node(&mut self, node_id: NodeId, new_block: Block, update: bool) { - let node = &mut self.nodes[node_id.index]; - node.state = new_block; - let pos = node.pos; - if update { - for update in node.updates.clone() { - self.update_node(update); - } - self.update_node(node_id); + pub fn reset(&mut self) -> Vec { + self.is_active = false; + if let Some(jit) = &mut self.jit { + jit.reset() + } else { + Vec::new() } - self.change_queue.push((pos, new_block)); } - fn comparator_should_be_powered( - &mut self, - mode: ComparatorMode, - input_strength: u8, - power_on_sides: u8, - ) -> bool { - if input_strength == 0 { - false - } else if input_strength > power_on_sides { - true + pub fn tick(&mut self) { + assert!(self.is_active, "Redpiler cannot tick while inactive"); + if let Some(jit) = &mut self.jit { + jit.tick(); } else { - power_on_sides == input_strength && mode == ComparatorMode::Compare + error!("Tried to tick redpiler while missing its JIT variant. How is it even active?"); } } - fn calculate_comparator_output( - &mut self, - mode: ComparatorMode, - input_strength: u8, - power_on_sides: u8, - ) -> u8 { - if mode == ComparatorMode::Subtract { - input_strength.saturating_sub(power_on_sides) - } else if input_strength >= power_on_sides { - input_strength + pub fn block_changes(&mut self) -> &mut Vec<(BlockPos, Block)> { + assert!( + self.is_active, + "Redpiler cannot drain block changes while inactive" + ); + // self.jit.unwrap().block_changes() + if let Some(jit) = &mut self.jit { + jit.block_changes() } else { - 0 + // Can't recover from this + panic!("Tried to drain redpiler block changes while missing its JIT variant. How is it even active?"); } } - fn update_node(&mut self, node_id: NodeId) { - let node = &self.nodes[node_id.index]; - - let mut input_power = 0; - let mut side_input_power = 0; - for link in &node.inputs { - let power = match link.ty { - LinkType::Default => &mut input_power, - LinkType::Side => &mut side_input_power, - }; - *power = (*power).max( - self.nodes[link.end.index] - .get_output_power() - .saturating_sub(link.weight), + pub fn on_use_block(&mut self, pos: BlockPos) { + assert!(self.is_active, "Redpiler cannot use block while inactive"); + if let Some(jit) = &mut self.jit { + jit.on_use_block(pos); + } else { + error!( + "Tried to use redpiler block while missing its JIT variant. How is it even active?" ); } - - let facing_diode = node.facing_diode; - let comparator_output = node.comparator_output; - - match node.state { - Block::RedstoneRepeater { mut repeater } => { - let should_be_locked = side_input_power > 0; - if !repeater.locked && should_be_locked { - repeater.locked = true; - self.set_node(node_id, Block::RedstoneRepeater { repeater }, false); - } else if repeater.locked && !should_be_locked { - repeater.locked = false; - self.set_node(node_id, Block::RedstoneRepeater { repeater }, false); - } - - if !repeater.locked && !self.pending_tick_at(node_id) { - let should_be_powered = input_power > 0; - if should_be_powered != repeater.powered { - let priority = if facing_diode { - TickPriority::Highest - } else if !should_be_powered { - TickPriority::Higher - } else { - TickPriority::High - }; - self.schedule_tick(node_id, repeater.delay as u32, priority); - } - } - } - Block::RedstoneTorch { lit } => { - if lit == (input_power > 0) && !self.pending_tick_at(node_id) { - self.schedule_tick(node_id, 1, TickPriority::Normal); - } - } - Block::RedstoneComparator { comparator } => { - if self.pending_tick_at(node_id) { - return; - } - let output_power = self.calculate_comparator_output( - comparator.mode, - input_power, - side_input_power, - ); - let old_strength = comparator_output; - if output_power != old_strength - || comparator.powered - != self.comparator_should_be_powered( - comparator.mode, - input_power, - side_input_power, - ) - { - let priority = if facing_diode { - TickPriority::High - } else { - TickPriority::Normal - }; - self.schedule_tick(node_id, 1, priority); - } - } - Block::RedstoneWallTorch { lit, .. } => { - if lit == (input_power > 0) && !self.pending_tick_at(node_id) { - self.schedule_tick(node_id, 1, TickPriority::Normal); - } - } - Block::RedstoneLamp { lit } => { - let should_be_lit = input_power > 0; - if lit && !should_be_lit { - self.schedule_tick(node_id, 2, TickPriority::Normal); - } else if !lit && should_be_lit { - self.set_node(node_id, Block::RedstoneLamp { lit: true }, false); - } - } - Block::RedstoneWire { mut wire } => { - if wire.power != input_power { - wire.power = input_power; - self.set_node(node_id, Block::RedstoneWire { wire }, true); - } - } - _ => {} // panic!("Node {:?} should not be updated!", node.state), - } } - pub fn tick(&mut self) { - for pending in &mut self.to_be_ticked { - pending.ticks_left = pending.ticks_left.saturating_sub(1); - } - while self.to_be_ticked.first().map(|e| e.ticks_left).unwrap_or(1) == 0 { - let entry = self.to_be_ticked.remove(0); - let node_id = entry.node; - let node = self.nodes[node_id.index].clone(); - - let mut input_power = 0u8; - let mut side_input_power = 0u8; - for link in &node.inputs { - let power = match link.ty { - LinkType::Default => &mut input_power, - LinkType::Side => &mut side_input_power, - }; - *power = (*power).max( - self.nodes[link.end.index] - .get_output_power() - .saturating_sub(link.weight), - ); - } - - match node.state { - Block::RedstoneRepeater { mut repeater } => { - if repeater.locked { - continue; - } - - let should_be_powered = input_power > 0; - if repeater.powered && !should_be_powered { - repeater.powered = false; - self.set_node(node_id, Block::RedstoneRepeater { repeater }, true); - } else if !repeater.powered { - repeater.powered = true; - self.set_node(node_id, Block::RedstoneRepeater { repeater }, true); - } - } - Block::RedstoneTorch { lit } => { - let should_be_off = input_power > 0; - if lit && should_be_off { - self.set_node(node_id, Block::RedstoneTorch { lit: false }, true); - } else if !lit && !should_be_off { - self.set_node(node_id, Block::RedstoneTorch { lit: true }, true); - } - } - Block::RedstoneComparator { mut comparator } => { - let new_strength = self.calculate_comparator_output( - comparator.mode, - input_power, - side_input_power, - ); - let old_strength = node.comparator_output; - if new_strength != old_strength || comparator.mode == ComparatorMode::Compare { - self.nodes[node_id.index].comparator_output = new_strength; - let should_be_powered = self.comparator_should_be_powered( - comparator.mode, - input_power, - side_input_power, - ); - let powered = comparator.powered; - if powered && !should_be_powered { - comparator.powered = false; - } else if !powered && should_be_powered { - comparator.powered = true; - } - self.set_node(node_id, Block::RedstoneComparator { comparator }, true); - } - } - Block::RedstoneWallTorch { lit, facing } => { - let should_be_off = input_power > 0; - if lit && should_be_off { - self.set_node( - node_id, - Block::RedstoneWallTorch { lit: false, facing }, - true, - ); - } else if !lit && !should_be_off { - self.set_node( - node_id, - Block::RedstoneWallTorch { lit: true, facing }, - true, - ); - } - } - Block::RedstoneLamp { lit } => { - let should_be_lit = input_power > 0; - if lit && !should_be_lit { - self.set_node(node_id, Block::RedstoneLamp { lit: false }, false); - } - } - Block::StoneButton { mut button } => { - if button.powered { - button.powered = false; - self.set_node(node_id, Block::StoneButton { button }, true); - } - } - _ => warn!("Node {:?} should not be ticked!", node.state), - } - } - } - - fn identify_node(&mut self, pos: BlockPos, block: Block, facing_diode: bool) { - if let Some(node) = Node::from_block(pos, block, facing_diode) { - self.nodes.push(node); - } - } - - fn identify_nodes(plot: &mut Plot, first_pos: BlockPos, second_pos: BlockPos) { + fn identify_nodes(plot: &mut Plot, first_pos: BlockPos, second_pos: BlockPos) -> Vec { + let mut nodes = Vec::new(); let start_pos = first_pos.min(second_pos); let end_pos = first_pos.max(second_pos); for y in start_pos.y..=end_pos.y { @@ -893,44 +631,48 @@ impl Compiler { } else { false }; - plot.redpiler.identify_node(pos, block, facing_diode); + + if let Some(node) = Node::from_block(pos, block, facing_diode) { + nodes.push(node); + } } } } + nodes } } -impl Display for Compiler { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("digraph{")?; - for (id, node) in self.nodes.iter().enumerate() { - write!( - f, - "n{}[label=\"{}\\n({}, {}, {})\"];", - id, - format!("{:?}", node.state) - .split_whitespace() - .next() - .unwrap(), - node.pos.x, - node.pos.y, - node.pos.z - )?; - for link in &node.inputs { - let color = match link.ty { - LinkType::Default => "", - LinkType::Side => ",color=\"blue\"", - }; - write!( - f, - "n{}->n{}[label=\"{}\"{}];", - link.end.index, link.start.index, link.weight, color - )?; - } - // for update in &node.updates { - // write!(f, "n{}->n{}[style=dotted];", id, update.index)?; - // } - } - f.write_str("}\n") - } -} +// impl Display for Compiler { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// f.write_str("digraph{")?; +// for (id, node) in self.nodes.iter().enumerate() { +// write!( +// f, +// "n{}[label=\"{}\\n({}, {}, {})\"];", +// id, +// format!("{:?}", node.state) +// .split_whitespace() +// .next() +// .unwrap(), +// node.pos.x, +// node.pos.y, +// node.pos.z +// )?; +// for link in &node.inputs { +// let color = match link.ty { +// LinkType::Default => "", +// LinkType::Side => ",color=\"blue\"", +// }; +// write!( +// f, +// "n{}->n{}[label=\"{}\"{}];", +// link.end.index, link.start.index, link.weight, color +// )?; +// } +// // for update in &node.updates { +// // write!(f, "n{}->n{}[style=dotted];", id, update.index)?; +// // } +// } +// f.write_str("}\n") +// } +// }