From 05cd390d5ad1fa0b541ac79e41b35350ba15f5b7 Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder <955793+bitwalker@users.noreply.github.com> Date: Mon, 11 Mar 2024 06:02:01 -0400 Subject: [PATCH 1/3] chore: update rust toolchain to latest nightly --- .github/workflows/ci.yml | 2 +- Cargo.lock | 12 ++++++------ codegen/masm/src/codegen/emit/unary.rs | 4 +--- codegen/masm/src/codegen/scheduler.rs | 2 +- codegen/masm/src/codegen/stack.rs | 1 - frontend-wasm/src/component/dfg.rs | 1 - frontend-wasm/src/component/inline.rs | 2 ++ frontend-wasm/src/module/func_translation_state.rs | 5 +---- frontend-wasm/src/module/module_env.rs | 1 - hir-analysis/src/validation/block.rs | 2 +- hir-analysis/src/validation/mod.rs | 2 +- hir-analysis/src/validation/naming.rs | 2 +- hir-analysis/src/validation/typecheck.rs | 2 +- hir-type/src/layout.rs | 2 +- hir/src/asm/builder.rs | 11 +++++++---- hir/src/asm/display.rs | 4 +--- hir/src/block.rs | 1 - hir/src/builder.rs | 2 -- hir/src/component/mod.rs | 5 +---- hir/src/dataflow.rs | 3 +-- hir/src/function.rs | 2 +- hir/src/globals.rs | 2 +- hir/src/instruction.rs | 8 ++------ hir/src/lib.rs | 8 +++++--- hir/src/module.rs | 2 +- hir/src/parser/ast/convert.rs | 5 ++--- hir/src/parser/ast/functions.rs | 2 +- hir/src/parser/ast/instruction.rs | 4 +--- hir/src/program/mod.rs | 5 +---- hir/src/write.rs | 2 +- rust-toolchain.toml | 2 +- 31 files changed, 44 insertions(+), 64 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 382d174b1..40554cbd5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ on: - '*.md' env: - CARGO_MAKE_TOOLCHAIN: nightly-2023-12-20 + CARGO_MAKE_TOOLCHAIN: nightly-2024-03-10 jobs: compiler: diff --git a/Cargo.lock b/Cargo.lock index db8f85691..b23e9f38f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,9 +41,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -52,9 +52,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "const-random", @@ -1438,7 +1438,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e0afc943ef18eebf6bc3335daeb8d338202093d18444a1784ea7f57fe7680f8" dependencies = [ - "ahash 0.7.7", + "ahash 0.7.8", "num_cpus", "parking_lot", "seize", @@ -3900,7 +3900,7 @@ version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3625f343d89990133d013e39c46e350915178cf94f1bec9f49b0cbef98a3e3c" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.11", "bitflags 2.4.2", "instant", "num-traits", diff --git a/codegen/masm/src/codegen/emit/unary.rs b/codegen/masm/src/codegen/emit/unary.rs index c3ec0c2a6..59cb6da09 100644 --- a/codegen/masm/src/codegen/emit/unary.rs +++ b/codegen/masm/src/codegen/emit/unary.rs @@ -1,6 +1,4 @@ -use miden_hir::{Overflow, Type}; - -use crate::masm::Op; +use miden_hir::Overflow; use super::*; diff --git a/codegen/masm/src/codegen/scheduler.rs b/codegen/masm/src/codegen/scheduler.rs index b71040211..699a1cccc 100644 --- a/codegen/masm/src/codegen/scheduler.rs +++ b/codegen/masm/src/codegen/scheduler.rs @@ -491,7 +491,7 @@ impl<'a> BlockScheduler<'a> { if inst_info .pre .as_slice() - .is_sorted_by(|a, b| Some(self.block_info.treegraph.cmp_scheduling(*a, *b))) + .is_sorted_by(|a, b| self.block_info.treegraph.cmp_scheduling(*a, *b).is_le()) { self.schedule_inst_dependencies(&inst_info, inst_info.pre.as_slice()); } else { diff --git a/codegen/masm/src/codegen/stack.rs b/codegen/masm/src/codegen/stack.rs index cc79dd6df..a41720c35 100644 --- a/codegen/masm/src/codegen/stack.rs +++ b/codegen/masm/src/codegen/stack.rs @@ -1,5 +1,4 @@ use core::{ - convert::AsRef, fmt, hash::{Hash, Hasher}, ops::{Index, IndexMut}, diff --git a/frontend-wasm/src/component/dfg.rs b/frontend-wasm/src/component/dfg.rs index 2d886f890..d635307d5 100644 --- a/frontend-wasm/src/component/dfg.rs +++ b/frontend-wasm/src/component/dfg.rs @@ -32,7 +32,6 @@ // TODO: remove this once Wasm CM support is complete #![allow(dead_code)] -use crate::component::info; use crate::component::*; use crate::module::types::{EntityIndex, MemoryIndex, WasmType}; use indexmap::IndexMap; diff --git a/frontend-wasm/src/component/inline.rs b/frontend-wasm/src/component/inline.rs index d0b7333aa..7986b7b68 100644 --- a/frontend-wasm/src/component/inline.rs +++ b/frontend-wasm/src/component/inline.rs @@ -1278,6 +1278,8 @@ impl<'a> ComponentItemDef<'a> { } enum InstanceModule { + #[allow(dead_code)] Static(StaticModuleIndex), + #[allow(dead_code)] Import(TypeModuleIndex), } diff --git a/frontend-wasm/src/module/func_translation_state.rs b/frontend-wasm/src/module/func_translation_state.rs index b04c9c78a..df3d77a2b 100644 --- a/frontend-wasm/src/module/func_translation_state.rs +++ b/frontend-wasm/src/module/func_translation_state.rs @@ -16,10 +16,7 @@ use miden_hir::{ }; use miden_hir_type::Type; use rustc_hash::FxHashMap; -use std::{ - collections::{hash_map::Entry::Occupied, hash_map::Entry::Vacant}, - vec::Vec, -}; +use std::collections::{hash_map::Entry::Occupied, hash_map::Entry::Vacant}; use super::{function_builder_ext::FunctionBuilderExt, Module}; diff --git a/frontend-wasm/src/module/module_env.rs b/frontend-wasm/src/module/module_env.rs index e11319f3e..1953e1267 100644 --- a/frontend-wasm/src/module/module_env.rs +++ b/frontend-wasm/src/module/module_env.rs @@ -12,7 +12,6 @@ use miden_diagnostics::DiagnosticsHandler; use miden_hir::cranelift_entity::packed_option::ReservedValue; use miden_hir::cranelift_entity::PrimaryMap; use rustc_hash::FxHashMap; -use std::convert::TryFrom; use std::ops::Range; use std::path::PathBuf; use std::sync::Arc; diff --git a/hir-analysis/src/validation/block.rs b/hir-analysis/src/validation/block.rs index 2907af3a1..15b7036e8 100644 --- a/hir-analysis/src/validation/block.rs +++ b/hir-analysis/src/validation/block.rs @@ -1,4 +1,4 @@ -use miden_diagnostics::{DiagnosticsHandler, Severity, SourceSpan, Spanned}; +use miden_diagnostics::{DiagnosticsHandler, Severity, Spanned}; use miden_hir::*; use rustc_hash::FxHashSet; use smallvec::SmallVec; diff --git a/hir-analysis/src/validation/mod.rs b/hir-analysis/src/validation/mod.rs index bf2be4e9a..3e6b3af64 100644 --- a/hir-analysis/src/validation/mod.rs +++ b/hir-analysis/src/validation/mod.rs @@ -202,7 +202,7 @@ mod typecheck; pub use self::typecheck::TypeError; -use miden_diagnostics::{DiagnosticsHandler, SourceSpan}; +use miden_diagnostics::DiagnosticsHandler; use miden_hir::pass::{Analysis, AnalysisManager, AnalysisResult}; use miden_hir::*; diff --git a/hir-analysis/src/validation/naming.rs b/hir-analysis/src/validation/naming.rs index 477a5520f..413d66a4e 100644 --- a/hir-analysis/src/validation/naming.rs +++ b/hir-analysis/src/validation/naming.rs @@ -1,4 +1,4 @@ -use miden_diagnostics::{DiagnosticsHandler, Severity, SourceSpan, Spanned}; +use miden_diagnostics::{DiagnosticsHandler, Severity, Spanned}; use miden_hir::*; use super::{Rule, ValidationError}; diff --git a/hir-analysis/src/validation/typecheck.rs b/hir-analysis/src/validation/typecheck.rs index 159908192..3af9c984d 100644 --- a/hir-analysis/src/validation/typecheck.rs +++ b/hir-analysis/src/validation/typecheck.rs @@ -1,6 +1,6 @@ use core::fmt; -use miden_diagnostics::{DiagnosticsHandler, Severity, SourceSpan, Spanned}; +use miden_diagnostics::{DiagnosticsHandler, Severity, Spanned}; use miden_hir::*; use rustc_hash::FxHashMap; diff --git a/hir-type/src/layout.rs b/hir-type/src/layout.rs index a09f093d1..b4fdb2693 100644 --- a/hir-type/src/layout.rs +++ b/hir-type/src/layout.rs @@ -1,4 +1,4 @@ -use alloc::{alloc::Layout, boxed::Box, collections::VecDeque, vec::Vec}; +use alloc::{alloc::Layout, collections::VecDeque}; use core::cmp::{self, Ordering}; use smallvec::SmallVec; diff --git a/hir/src/asm/builder.rs b/hir/src/asm/builder.rs index 2beb21e66..1d3d71e5f 100644 --- a/hir/src/asm/builder.rs +++ b/hir/src/asm/builder.rs @@ -1,6 +1,5 @@ use crate::{ - CallConv, DataFlowGraph, Felt, FunctionIdent, Inst, InstBuilder, Instruction, Overflow, - SourceSpan, Type, Value, + CallConv, Felt, FunctionIdent, Inst, InstBuilder, Instruction, Overflow, SourceSpan, Value, }; use smallvec::smallvec; @@ -1940,8 +1939,12 @@ fn apply_op_stack_effects( stack.push(Type::U32); } MasmOp::U32Lt | MasmOp::U32Gt | MasmOp::U32Lte | MasmOp::U32Gte => { - let rty = stack.pop().expect("failed to pop right operand: stack is empty"); - let lty = stack.pop().expect("failed to pop left operand: stack is empty"); + let rty = stack + .pop() + .expect("failed to pop right operand: stack is empty"); + let lty = stack + .pop() + .expect("failed to pop left operand: stack is empty"); assert_compatible_u32_operands!(lty, rty, op); stack.push(Type::I1); } diff --git a/hir/src/asm/display.rs b/hir/src/asm/display.rs index 88e8fc823..a550edd0c 100644 --- a/hir/src/asm/display.rs +++ b/hir/src/asm/display.rs @@ -1,9 +1,7 @@ use std::fmt::{self, Write}; -use cranelift_entity::PrimaryMap; - use super::*; -use crate::{write::DisplayIndent, DataFlowGraph, FunctionIdent, Ident, Symbol}; +use crate::{write::DisplayIndent, FunctionIdent, Ident, Symbol}; pub struct DisplayInlineAsm<'a> { function: Option, diff --git a/hir/src/block.rs b/hir/src/block.rs index f7ef8c857..f39b933fb 100644 --- a/hir/src/block.rs +++ b/hir/src/block.rs @@ -1,6 +1,5 @@ use cranelift_entity::entity_impl; use intrusive_collections::linked_list::{Cursor, CursorMut}; -use intrusive_collections::UnsafeRef; use super::*; diff --git a/hir/src/builder.rs b/hir/src/builder.rs index 6436ec837..955bd79e9 100644 --- a/hir/src/builder.rs +++ b/hir/src/builder.rs @@ -1,7 +1,5 @@ use cranelift_entity::packed_option::PackedOption; -use miden_diagnostics::SourceSpan; - use super::*; pub struct FunctionBuilder<'f> { diff --git a/hir/src/component/mod.rs b/hir/src/component/mod.rs index 20e60994a..f4e89f271 100644 --- a/hir/src/component/mod.rs +++ b/hir/src/component/mod.rs @@ -1,7 +1,4 @@ -use core::{ - convert::{AsMut, AsRef}, - ops::{Deref, DerefMut}, -}; +use core::ops::{Deref, DerefMut}; use intrusive_collections::RBTree; use miden_core::crypto::hash::RpoDigest; use std::collections::BTreeMap; diff --git a/hir/src/dataflow.rs b/hir/src/dataflow.rs index 1050d895b..a8d528476 100644 --- a/hir/src/dataflow.rs +++ b/hir/src/dataflow.rs @@ -1,11 +1,10 @@ use std::ops::{Deref, Index, IndexMut}; use cranelift_entity::{PrimaryMap, SecondaryMap}; -use intrusive_collections::UnsafeRef; use rustc_hash::FxHashMap; use smallvec::SmallVec; -use miden_diagnostics::{SourceSpan, Span, Spanned}; +use miden_diagnostics::{Span, Spanned}; use super::*; diff --git a/hir/src/function.rs b/hir/src/function.rs index 040e3e016..69066a800 100644 --- a/hir/src/function.rs +++ b/hir/src/function.rs @@ -4,7 +4,7 @@ use cranelift_entity::entity_impl; use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink}; use miden_diagnostics::Spanned; -use super::{pass::AnalysisKey, *}; +use super::*; /// This error is raised when two function declarations conflict with the same symbol name #[derive(Debug, thiserror::Error)] diff --git a/hir/src/globals.rs b/hir/src/globals.rs index 166f65fa2..c22abf730 100644 --- a/hir/src/globals.rs +++ b/hir/src/globals.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Write}; use std::hash::{Hash, Hasher}; use cranelift_entity::entity_impl; -use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink, UnsafeRef}; +use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink}; use miden_diagnostics::Spanned; use super::*; diff --git a/hir/src/instruction.rs b/hir/src/instruction.rs index 99f33a230..a25c04c56 100644 --- a/hir/src/instruction.rs +++ b/hir/src/instruction.rs @@ -1,11 +1,7 @@ -use std::{ - convert::{AsMut, AsRef}, - fmt, - ops::{Deref, DerefMut}, -}; +use core::ops::{Deref, DerefMut}; use cranelift_entity::entity_impl; -use intrusive_collections::{intrusive_adapter, LinkedListLink, UnsafeRef}; +use intrusive_collections::{intrusive_adapter, LinkedListLink}; use smallvec::SmallVec; use miden_diagnostics::{Span, Spanned}; diff --git a/hir/src/lib.rs b/hir/src/lib.rs index 5ab37118b..451deedfe 100644 --- a/hir/src/lib.rs +++ b/hir/src/lib.rs @@ -1,9 +1,11 @@ #![deny(warnings)] -// Temporary until 1.76 is released -#![allow(stable_features)] -// TODO: Stabilized in 1.76, not yet released +// TODO: Stabilized in 1.76, then de-stablized before release due to +// a soundness bug when interacting with #![feature(arbitrary_self_types)] +// so this got punted to a later release once they come up with a solution. +// // Required for pass infrastructure, can be removed when it gets stabilized // in an upcoming release, see https://github.com/rust-lang/rust/issues/65991 +// for details #![feature(trait_upcasting)] pub mod parser; diff --git a/hir/src/module.rs b/hir/src/module.rs index 1aed37cad..60ec74e8f 100644 --- a/hir/src/module.rs +++ b/hir/src/module.rs @@ -8,7 +8,7 @@ use intrusive_collections::{ use miden_diagnostics::{DiagnosticsHandler, Severity, Spanned}; use rustc_hash::FxHashSet; -use super::{pass::AnalysisKey, *}; +use super::*; /// This error is raised when two modules conflict with the same symbol name #[derive(Debug, thiserror::Error)] diff --git a/hir/src/parser/ast/convert.rs b/hir/src/parser/ast/convert.rs index 9b1404682..f5add871d 100644 --- a/hir/src/parser/ast/convert.rs +++ b/hir/src/parser/ast/convert.rs @@ -2,13 +2,12 @@ use std::collections::VecDeque; use cranelift_entity::packed_option::ReservedValue; use intrusive_collections::UnsafeRef; -use miden_diagnostics::{Severity, SourceSpan, Spanned}; use midenc_session::Session; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashSet; use crate::parser::ParseError; use crate::pass::{AnalysisManager, ConversionError, ConversionPass, ConversionResult}; -use crate::{FunctionIdent, Ident, Immediate, Opcode, PassInfo, Type}; +use crate::{Immediate, Opcode, PassInfo, Type}; use super::*; diff --git a/hir/src/parser/ast/functions.rs b/hir/src/parser/ast/functions.rs index 51421244d..d01904cd8 100644 --- a/hir/src/parser/ast/functions.rs +++ b/hir/src/parser/ast/functions.rs @@ -1,6 +1,6 @@ use core::fmt; -use crate::{AttributeSet, Ident, Signature}; +use crate::{AttributeSet, Signature}; use super::*; diff --git a/hir/src/parser/ast/instruction.rs b/hir/src/parser/ast/instruction.rs index d88cf7f01..e1720ea54 100644 --- a/hir/src/parser/ast/instruction.rs +++ b/hir/src/parser/ast/instruction.rs @@ -1,8 +1,6 @@ use core::fmt; -use miden_diagnostics::Span; - -use crate::{FunctionIdent, Ident, Opcode, Overflow, Type}; +use crate::{Opcode, Overflow, Type}; use super::*; diff --git a/hir/src/program/mod.rs b/hir/src/program/mod.rs index f749ae85d..205b72523 100644 --- a/hir/src/program/mod.rs +++ b/hir/src/program/mod.rs @@ -1,9 +1,6 @@ mod linker; -use core::{ - convert::{AsMut, AsRef}, - ops::{Deref, DerefMut}, -}; +use core::ops::{Deref, DerefMut}; use intrusive_collections::RBTree; pub use self::linker::{Linker, LinkerError}; diff --git a/hir/src/write.rs b/hir/src/write.rs index f4a8feb3b..d0619e9f7 100644 --- a/hir/src/write.rs +++ b/hir/src/write.rs @@ -1,6 +1,6 @@ use std::fmt::{self, Write}; -use super::{display::DisplayValues, *}; +use super::*; pub fn write_function(w: &mut dyn Write, func: &Function) -> fmt::Result { for attr in func.dfg.attrs.iter() { diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 7be3bf391..7b699598c 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2023-12-20" +channel = "nightly-2024-03-10" components = ["rustfmt", "rust-src"] targets = ["wasm32-unknown-unknown"] profile = "minimal" From fcd6a586ba68e227ad39bf92dadd849e1faae03d Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder <955793+bitwalker@users.noreply.github.com> Date: Mon, 11 Mar 2024 06:31:14 -0400 Subject: [PATCH 2/3] chore: add formatter config, format most crates --- .editorconfig | 23 + codegen/masm/src/codegen/emit/binary.rs | 18 +- codegen/masm/src/codegen/emit/felt.rs | 3 +- codegen/masm/src/codegen/emit/int128.rs | 9 +- codegen/masm/src/codegen/emit/int32.rs | 52 +- codegen/masm/src/codegen/emit/int64.rs | 28 +- codegen/masm/src/codegen/emit/mem.rs | 46 +- codegen/masm/src/codegen/emit/mod.rs | 58 ++- codegen/masm/src/codegen/emit/primop.rs | 101 ++-- codegen/masm/src/codegen/emit/smallint.rs | 8 +- codegen/masm/src/codegen/emit/unary.rs | 21 +- codegen/masm/src/codegen/emitter.rs | 84 +--- codegen/masm/src/codegen/mod.rs | 8 +- .../masm/src/codegen/opt/operands/context.rs | 8 +- codegen/masm/src/codegen/opt/operands/mod.rs | 10 +- .../masm/src/codegen/opt/operands/solver.rs | 175 ++++--- .../codegen/opt/operands/tactics/copy_all.rs | 19 +- .../codegen/opt/operands/tactics/linear.rs | 50 +- .../src/codegen/opt/operands/tactics/mod.rs | 14 +- .../operands/tactics/move_down_and_swap.rs | 41 +- .../opt/operands/tactics/move_up_and_swap.rs | 52 +- .../opt/operands/tactics/swap_and_move_up.rs | 29 +- codegen/masm/src/codegen/scheduler.rs | 233 +++++---- codegen/masm/src/codegen/stack.rs | 72 +-- codegen/masm/src/convert.rs | 24 +- codegen/masm/src/emulator/breakpoints.rs | 49 +- codegen/masm/src/emulator/functions.rs | 34 +- codegen/masm/src/emulator/mod.rs | 111 +++-- codegen/masm/src/lib.rs | 25 +- codegen/masm/src/masm/function.rs | 20 +- codegen/masm/src/masm/intrinsics.rs | 2 +- codegen/masm/src/masm/mod.rs | 14 +- codegen/masm/src/masm/module.rs | 23 +- codegen/masm/src/masm/program.rs | 17 +- codegen/masm/src/masm/region.rs | 26 +- codegen/masm/src/tests.rs | 79 +-- frontend-wasm/src/component/build_ir.rs | 35 +- hir-analysis/src/control_flow.rs | 36 +- hir-analysis/src/data.rs | 12 +- hir-analysis/src/dependency_graph.rs | 140 +++--- hir-analysis/src/dominance.rs | 127 ++--- hir-analysis/src/lib.rs | 18 +- hir-analysis/src/liveness.rs | 98 ++-- hir-analysis/src/loops.rs | 58 +-- hir-analysis/src/treegraph.rs | 97 ++-- hir-analysis/src/validation/block.rs | 65 ++- hir-analysis/src/validation/function.rs | 88 ++-- hir-analysis/src/validation/mod.rs | 70 +-- hir-analysis/src/validation/naming.rs | 22 +- hir-analysis/src/validation/typecheck.rs | 202 ++++---- hir-macros/src/lib.rs | 23 +- hir-symbol/build.rs | 33 +- hir-symbol/src/lib.rs | 14 +- hir-transform/src/adt/scoped_map.rs | 9 +- hir-transform/src/inline_blocks.rs | 58 +-- hir-transform/src/lib.rs | 6 +- hir-transform/src/split_critical_edges.rs | 42 +- hir-transform/src/treeify.rs | 61 +-- hir-type/src/layout.rs | 44 +- hir-type/src/lib.rs | 117 +++-- hir/src/adt/mod.rs | 10 +- hir/src/adt/smallmap.rs | 18 +- hir/src/adt/smallordset.rs | 6 +- hir/src/adt/smallset.rs | 3 +- hir/src/adt/sparsemap.rs | 19 +- hir/src/asm/builder.rs | 459 +++++++++--------- hir/src/asm/display.rs | 13 +- hir/src/asm/import.rs | 8 +- hir/src/asm/isa.rs | 69 +-- hir/src/asm/mod.rs | 13 +- hir/src/asm/stack.rs | 23 +- hir/src/builder.rs | 161 +++--- hir/src/component/interface.rs | 12 +- hir/src/component/mod.rs | 9 +- hir/src/dataflow.rs | 54 +-- hir/src/display.rs | 3 + hir/src/function.rs | 20 +- hir/src/globals.rs | 83 ++-- hir/src/ident.rs | 14 +- hir/src/immediates.rs | 65 ++- hir/src/instruction.rs | 43 +- hir/src/layout.rs | 69 +-- hir/src/lib.rs | 66 ++- hir/src/module.rs | 98 ++-- hir/src/parser/ast/convert.rs | 88 ++-- hir/src/parser/ast/functions.rs | 19 +- hir/src/parser/ast/instruction.rs | 3 +- hir/src/parser/ast/mod.rs | 29 +- hir/src/parser/error.rs | 11 +- hir/src/parser/lexer/error.rs | 14 +- hir/src/parser/lexer/mod.rs | 18 +- hir/src/parser/mod.rs | 23 +- hir/src/parser/tests/mod.rs | 17 +- hir/src/parser/tests/utils.rs | 17 +- hir/src/pass/analysis.rs | 25 +- hir/src/pass/conversion.rs | 3 +- hir/src/pass/mod.rs | 30 +- hir/src/pass/rewrite.rs | 2 +- hir/src/program/linker.rs | 156 +++--- hir/src/program/mod.rs | 20 +- hir/src/segments.rs | 42 +- hir/src/testing.rs | 395 +++++---------- hir/src/tests.rs | 20 +- hir/src/value.rs | 1 - hir/src/write.rs | 14 +- midenc-compile/src/compiler.rs | 6 +- midenc-compile/src/lib.rs | 97 ++-- midenc-compile/src/stages/mod.rs | 24 +- midenc-compile/src/stages/parse.rs | 11 +- midenc-compile/src/stages/rewrite.rs | 4 +- midenc-driver/src/midenc.rs | 9 +- midenc-session/src/duration.rs | 6 +- midenc-session/src/emit.rs | 8 +- midenc-session/src/inputs.rs | 14 +- midenc-session/src/lib.rs | 71 +-- midenc-session/src/options/mod.rs | 8 +- midenc-session/src/outputs.rs | 36 +- midenc-session/src/statistics.rs | 6 +- midenc/src/main.rs | 8 +- rustfmt.toml | 18 + tools/cargo-miden/src/build.rs | 17 +- tools/cargo-miden/src/config.rs | 46 +- tools/cargo-miden/src/lib.rs | 6 +- tools/cargo-miden/src/new_project.rs | 29 +- tools/cargo-miden/src/run_cargo_command.rs | 38 +- tools/cargo-miden/src/target.rs | 18 +- tools/cargo-miden/tests/build.rs | 17 +- tools/cargo-miden/tests/utils.rs | 3 +- 128 files changed, 2722 insertions(+), 3124 deletions(-) create mode 100644 .editorconfig create mode 100644 rustfmt.toml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..8e3c490a8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# Documentation available at editorconfig.org + +root=true + +[*] +ident_style = space +ident_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.rs] +max_line_length = 100 + +[*.yml] +ident_size = 2 + +[*.hir] +indent_size = 4 + +[*.toml] +indent_size = 2 diff --git a/codegen/masm/src/codegen/emit/binary.rs b/codegen/masm/src/codegen/emit/binary.rs index 220f22e9b..1cc27d866 100644 --- a/codegen/masm/src/codegen/emit/binary.rs +++ b/codegen/masm/src/codegen/emit/binary.rs @@ -1,8 +1,7 @@ use miden_hir::{assert_matches, Felt, Immediate, Overflow, Type}; -use crate::masm::Op; - use super::OpEmitter; +use crate::masm::Op; impl<'a> OpEmitter<'a> { pub fn eq(&mut self) { @@ -450,7 +449,8 @@ impl<'a> OpEmitter<'a> { // // z = z2 * (2^63)^2 + z1 * 2^63 + z0 // - // We assume the stack holds two words representing x and y, with y on top of the stack + // We assume the stack holds two words representing x and y, with y on top of the + // stack todo!() } Type::U64 => self.mul_u64(overflow), @@ -806,11 +806,8 @@ impl<'a> OpEmitter<'a> { let lhs = self.pop().expect("operand stack is empty"); let ty = lhs.ty(); assert_eq!(ty, imm.ty(), "expected exp operands to be the same type"); - let exp: u8 = imm - .as_u64() - .unwrap() - .try_into() - .expect("invalid exponent: must be value < 64"); + let exp: u8 = + imm.as_u64().unwrap().try_into().expect("invalid exponent: must be value < 64"); match &ty { Type::U64 => todo!("exponentiation by squaring"), Type::Felt => { @@ -1047,10 +1044,7 @@ impl<'a> OpEmitter<'a> { assert_eq!(ty, imm.ty(), "expected shl operands to be the same type"); match &ty { Type::U64 => { - assert!( - imm.as_u64().unwrap() < 64, - "invalid shift value: must be < 64" - ); + assert!(imm.as_u64().unwrap() < 64, "invalid shift value: must be < 64"); self.push_immediate(imm); self.shl_u64(); } diff --git a/codegen/masm/src/codegen/emit/felt.rs b/codegen/masm/src/codegen/emit/felt.rs index e2f8ab66b..dce240093 100644 --- a/codegen/masm/src/codegen/emit/felt.rs +++ b/codegen/masm/src/codegen/emit/felt.rs @@ -1,8 +1,7 @@ use miden_hir::{Felt, FieldElement}; -use crate::masm::Op; - use super::OpEmitter; +use crate::masm::Op; /// The value zero, as a field element pub const ZERO: Felt = Felt::ZERO; diff --git a/codegen/masm/src/codegen/emit/int128.rs b/codegen/masm/src/codegen/emit/int128.rs index 551c54d5e..e61468f8e 100644 --- a/codegen/masm/src/codegen/emit/int128.rs +++ b/codegen/masm/src/codegen/emit/int128.rs @@ -1,6 +1,5 @@ -use crate::masm::Op; - use super::OpEmitter; +use crate::masm::Op; #[allow(unused)] impl<'a> OpEmitter<'a> { @@ -153,7 +152,8 @@ impl<'a> OpEmitter<'a> { } } - /// Pop two i128 values, `b` and `a`, off the operand stack, and place the result of `a == b` on the stack. + /// Pop two i128 values, `b` and `a`, off the operand stack, and place the result of `a == b` on + /// the stack. #[inline] pub fn eq_i128(&mut self) { self.emit_all(&[ @@ -166,7 +166,8 @@ impl<'a> OpEmitter<'a> { ]); } - /// Pop two i128 values, `b` and `a`, off the operand stack, and place the result of `a == b` on the stack. + /// Pop two i128 values, `b` and `a`, off the operand stack, and place the result of `a == b` on + /// the stack. #[inline] pub fn neq_i128(&mut self) { self.eq_i128(); diff --git a/codegen/masm/src/codegen/emit/int32.rs b/codegen/masm/src/codegen/emit/int32.rs index e5aaa910e..1ebdd9943 100644 --- a/codegen/masm/src/codegen/emit/int32.rs +++ b/codegen/masm/src/codegen/emit/int32.rs @@ -1,8 +1,7 @@ use miden_hir::{Felt, FieldElement, Overflow}; -use crate::masm::Op; - use super::OpEmitter; +use crate::masm::Op; pub const SIGN_BIT: u32 = 1 << 31; @@ -107,7 +106,8 @@ impl<'a> OpEmitter<'a> { self.emit(Op::Assert); } - /// Emits code to assert that a 32-bit value on the operand stack does not have the i32 sign bit set. + /// Emits code to assert that a 32-bit value on the operand stack does not have the i32 sign bit + /// set. /// /// The value on top of the stack IS NOT consumed. /// @@ -118,7 +118,8 @@ impl<'a> OpEmitter<'a> { self.emit(Op::Assertz); } - /// Emits code to assert that a 32-bit value on the operand stack is equal to the given constant value. + /// Emits code to assert that a 32-bit value on the operand stack is equal to the given constant + /// value. /// /// The value on top of the stack IS NOT consumed. /// @@ -130,8 +131,8 @@ impl<'a> OpEmitter<'a> { self.emit_all(&[Op::Dup(0), Op::EqImm(Felt::new(value as u64)), Op::Assert]); } - /// Emits code to assert that two 32-bit values, `expected` and `value`, on top of the operand stack are equal, - /// without consuming `value`. + /// Emits code to assert that two 32-bit values, `expected` and `value`, on top of the operand + /// stack are equal, without consuming `value`. /// /// The `expected` operand is consumed, while the `value` operand IS NOT. /// @@ -281,11 +282,13 @@ impl<'a> OpEmitter<'a> { ]); } - /// Emit code to truncate a 32-bit value on top of the operand stack, to N bits, where N is <= 32 + /// Emit code to truncate a 32-bit value on top of the operand stack, to N bits, where N is <= + /// 32 /// /// This consumes the input value, and leaves an N-bit value on the stack. /// - /// NOTE: This function does not validate the input as < 2^32, the caller is expected to validate this. + /// NOTE: This function does not validate the input as < 2^32, the caller is expected to + /// validate this. #[inline] pub fn trunc_int32(&mut self, n: u32) { assert_valid_integer_size!(n, 1, 32); @@ -457,9 +460,9 @@ impl<'a> OpEmitter<'a> { Overflow::Checked => { self.emit(Op::Exec("intrinsics::i32::checked_sub".parse().unwrap())) } - Overflow::Overflowing => self.emit(Op::Exec( - "intrinsics::i32::overflowing_sub".parse().unwrap(), - )), + Overflow::Overflowing => { + self.emit(Op::Exec("intrinsics::i32::overflowing_sub".parse().unwrap())) + } } } @@ -529,9 +532,9 @@ impl<'a> OpEmitter<'a> { Overflow::Checked => { self.emit(Op::Exec("intrinsics::i32::checked_mul".parse().unwrap())) } - Overflow::Overflowing => self.emit(Op::Exec( - "intrinsics::i32::overflowing_mul".parse().unwrap(), - )), + Overflow::Overflowing => { + self.emit(Op::Exec("intrinsics::i32::overflowing_mul".parse().unwrap())) + } } } @@ -679,7 +682,8 @@ impl<'a> OpEmitter<'a> { self.emit(Op::U32ModImm(imm)); } - /// Pops two u32 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the stack. + /// Pops two u32 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the + /// stack. /// /// This operation is checked, so if the operands or result are not valid u32, execution traps. pub fn checked_divmod_u32(&mut self) { @@ -694,7 +698,8 @@ impl<'a> OpEmitter<'a> { self.emit_all(&[Op::U32DivModImm(imm), Op::U32Assert]); } - /// Pops two u32 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the stack. + /// Pops two u32 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the + /// stack. /// /// This operation is unchecked, so the result is not guaranteed to be a valid u32 pub fn unchecked_divmod_u32(&mut self) { @@ -829,7 +834,8 @@ impl<'a> OpEmitter<'a> { self.emit(Op::U32RotlImm(imm)); } - /// Pops two u32 values off the stack, `b` and `a`, and rotates the bits of `a` right by `b` bits + /// Pops two u32 values off the stack, `b` and `a`, and rotates the bits of `a` right by `b` + /// bits /// /// Execution traps if `b` > 31. /// @@ -846,14 +852,16 @@ impl<'a> OpEmitter<'a> { self.emit(Op::U32RotrImm(imm)); } - /// Pops two u32 values off the stack, `b` and `a`, and puts the result of `min(a, b)` on the stack + /// Pops two u32 values off the stack, `b` and `a`, and puts the result of `min(a, b)` on the + /// stack /// /// This operation is checked, if the operands or result are not valid u32, execution traps. pub fn min_u32(&mut self) { self.emit(Op::U32Min); } - /// Pops two i32 values off the stack, `b` and `a`, and puts the result of `min(a, b)` on the stack + /// Pops two i32 values off the stack, `b` and `a`, and puts the result of `min(a, b)` on the + /// stack /// /// This operation is checked, if the operands or result are not valid i32, execution traps. pub fn min_i32(&mut self) { @@ -877,14 +885,16 @@ impl<'a> OpEmitter<'a> { ]); } - /// Pops two u32 values off the stack, `b` and `a`, and puts the result of `max(a, b)` on the stack + /// Pops two u32 values off the stack, `b` and `a`, and puts the result of `max(a, b)` on the + /// stack /// /// This operation is checked, if the operands or result are not valid u32, execution traps. pub fn max_u32(&mut self) { self.emit(Op::U32Max); } - /// Pops two i32 values off the stack, `b` and `a`, and puts the result of `max(a, b)` on the stack + /// Pops two i32 values off the stack, `b` and `a`, and puts the result of `max(a, b)` on the + /// stack /// /// This operation is checked, if the operands or result are not valid i32, execution traps. pub fn max_i32(&mut self) { diff --git a/codegen/masm/src/codegen/emit/int64.rs b/codegen/masm/src/codegen/emit/int64.rs index 65cf75eb6..a9ab468f9 100644 --- a/codegen/masm/src/codegen/emit/int64.rs +++ b/codegen/masm/src/codegen/emit/int64.rs @@ -1,8 +1,7 @@ use miden_hir::{Felt, FieldElement, Overflow}; -use crate::masm::{self as masm, Op}; - use super::{felt, OpEmitter, P}; +use crate::masm::{self as masm, Op}; #[allow(unused)] impl<'a> OpEmitter<'a> { @@ -21,7 +20,8 @@ impl<'a> OpEmitter<'a> { /// Convert a i64 value to felt. /// - /// This operation will assert at runtime if the value is negative, or larger than the felt field. + /// This operation will assert at runtime if the value is negative, or larger than the felt + /// field. pub fn i64_to_felt(&mut self) { self.assert_unsigned_int64(); self.u64_to_felt(); @@ -399,7 +399,8 @@ impl<'a> OpEmitter<'a> { } } - /// Pops two u64 values off the stack, `b` and `a`, and pushes the result of `a / b` on the stack. + /// Pops two u64 values off the stack, `b` and `a`, and pushes the result of `a / b` on the + /// stack. /// /// Both the operands and result are validated to ensure they are valid u64 values. #[inline] @@ -407,7 +408,8 @@ impl<'a> OpEmitter<'a> { self.emit(Op::Exec("std::math::u64::checked_div".parse().unwrap())); } - /// Pops two u64 values off the stack, `b` and `a`, and pushes the result of `a / b` on the stack. + /// Pops two u64 values off the stack, `b` and `a`, and pushes the result of `a / b` on the + /// stack. /// /// This operation is unchecked, it is up to the caller to ensure validity of the operands. #[inline] @@ -415,7 +417,8 @@ impl<'a> OpEmitter<'a> { self.emit(Op::Exec("std::math::u64::unchecked_div".parse().unwrap())); } - /// Pops two u64 values off the stack, `b` and `a`, and pushes the result of `a % b` on the stack. + /// Pops two u64 values off the stack, `b` and `a`, and pushes the result of `a % b` on the + /// stack. /// /// Both the operands and result are validated to ensure they are valid u64 values. #[inline] @@ -423,7 +426,8 @@ impl<'a> OpEmitter<'a> { self.emit(Op::Exec("std::math::u64::checked_mod".parse().unwrap())); } - /// Pops two u64 values off the stack, `b` and `a`, and pushes the result of `a % b` on the stack. + /// Pops two u64 values off the stack, `b` and `a`, and pushes the result of `a % b` on the + /// stack. /// /// This operation is unchecked, it is up to the caller to ensure validity of the operands. #[inline] @@ -431,7 +435,8 @@ impl<'a> OpEmitter<'a> { self.emit(Op::Exec("std::math::u64::unchecked_mod".parse().unwrap())); } - /// Pops two u64 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the stack. + /// Pops two u64 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the + /// stack. /// /// Both the operands and result are validated to ensure they are valid u64 values. #[inline] @@ -439,14 +444,13 @@ impl<'a> OpEmitter<'a> { self.emit(Op::Exec("std::math::u64::checked_divmod".parse().unwrap())); } - /// Pops two u64 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the stack. + /// Pops two u64 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the + /// stack. /// /// This operation is unchecked, it is up to the caller to ensure validity of the operands. #[inline] pub fn unchecked_divmod_u64(&mut self) { - self.emit(Op::Exec( - "std::math::u64::unchecked_divmod".parse().unwrap(), - )); + self.emit(Op::Exec("std::math::u64::unchecked_divmod".parse().unwrap())); } /// Pops two 64-bit values off the stack, `b` and `a`, and pushes `a & b` on the stack. diff --git a/codegen/masm/src/codegen/emit/mem.rs b/codegen/masm/src/codegen/emit/mem.rs index d42729772..3efd7ed4e 100644 --- a/codegen/masm/src/codegen/emit/mem.rs +++ b/codegen/masm/src/codegen/emit/mem.rs @@ -1,8 +1,7 @@ use miden_hir::{StructType, Type}; -use crate::masm::{NativePtr, Op}; - use super::OpEmitter; +use crate::masm::{NativePtr, Op}; /// Allocation impl<'a> OpEmitter<'a> { @@ -125,10 +124,7 @@ impl<'a> OpEmitter<'a> { } fn load_felt_imm(&mut self, ptr: NativePtr) { - assert!( - ptr.is_element_aligned(), - "felt values must be naturally aligned" - ); + assert!(ptr.is_element_aligned(), "felt values must be naturally aligned"); match ptr.index { 0 => self.emit(Op::MemLoadImm(ptr.waddr)), 1 => { @@ -165,7 +161,8 @@ impl<'a> OpEmitter<'a> { } } - /// Loads a single 32-bit machine word, i.e. a single field element, not the Miden notion of a word + /// Loads a single 32-bit machine word, i.e. a single field element, not the Miden notion of a + /// word /// /// Expects a native pointer triplet on the stack if an immediate address is not given. fn load_word(&mut self, ptr: Option) { @@ -605,11 +602,11 @@ impl<'a> OpEmitter<'a> { /// [00000000111111111111111111111111, 111111111111111111111111111111, 11111111111111111111111100000000] /// ``` /// - /// As illustrated above, what should be a double-word value is occupying three words. To "realign" the - /// value, i.e. ensure that it is naturally aligned and fits in two words, we have to perform a sequence - /// of shifts and masks to get the bits where they belong. This function performs those steps, with the - /// assumption that the caller has three values on the operand stack representing any unaligned double-word - /// value + /// As illustrated above, what should be a double-word value is occupying three words. To + /// "realign" the value, i.e. ensure that it is naturally aligned and fits in two words, we + /// have to perform a sequence of shifts and masks to get the bits where they belong. This + /// function performs those steps, with the assumption that the caller has three values on + /// the operand stack representing any unaligned double-word value fn realign_double_word(&mut self, ptr: NativePtr) { // The stack starts as: [chunk_hi, chunk_mid, chunk_lo] // @@ -828,15 +825,9 @@ impl<'a> OpEmitter<'a> { let ptr = self.stack.pop().expect("operand stack is empty"); let value = self.stack.pop().expect("operand stack is empty"); let ptr_ty = ptr.ty(); - assert!( - ptr_ty.is_pointer(), - "expected load operand to be a pointer, got {ptr_ty}" - ); + assert!(ptr_ty.is_pointer(), "expected load operand to be a pointer, got {ptr_ty}"); let value_ty = value.ty(); - assert!( - !value_ty.is_zst(), - "cannot store a zero-sized type in memory" - ); + assert!(!value_ty.is_zst(), "cannot store a zero-sized type in memory"); match ptr_ty { Type::Ptr(_) => { // Converet the pointer to a native pointer representation @@ -867,10 +858,7 @@ impl<'a> OpEmitter<'a> { pub fn store_imm(&mut self, addr: u32) { let value = self.stack.pop().expect("operand stack is empty"); let value_ty = value.ty(); - assert!( - !value_ty.is_zst(), - "cannot store a zero-sized type in memory" - ); + assert!(!value_ty.is_zst(), "cannot store a zero-sized type in memory"); let ptr = NativePtr::from_ptr(addr); match value_ty { Type::I128 => self.store_quad_word(Some(ptr)), @@ -891,8 +879,8 @@ impl<'a> OpEmitter<'a> { /// The order of operands on the stack is `src`, `dst`, then `count`. /// /// The addresses on the stack are interpreted based on the pointer type: native pointers are - /// in the Miden address space; non-native pointers are assumed to be in the IR's byte addressable - /// address space, and require translation. + /// in the Miden address space; non-native pointers are assumed to be in the IR's byte + /// addressable address space, and require translation. /// /// The semantics of this instruction are as follows: /// @@ -903,11 +891,7 @@ impl<'a> OpEmitter<'a> { let count = self.stack.pop().expect("operand stack is empty"); assert_eq!(count.ty(), Type::U32, "expected count operand to be a u32"); let ty = src.ty(); - assert_eq!( - ty, - dst.ty(), - "expected src and dst operands to have the same type" - ); + assert_eq!(ty, dst.ty(), "expected src and dst operands to have the same type"); match ty { Type::Ptr(ref _pointee) => { todo!() diff --git a/codegen/masm/src/codegen/emit/mod.rs b/codegen/masm/src/codegen/emit/mod.rs index 11f350930..c74c7dfb9 100644 --- a/codegen/masm/src/codegen/emit/mod.rs +++ b/codegen/masm/src/codegen/emit/mod.rs @@ -5,10 +5,7 @@ pub const P: u64 = (2u128.pow(64) - 2u128.pow(32) + 1) as u64; /// integer sizes we support as a general rule. macro_rules! assert_valid_integer_size { ($n:ident) => { - assert!( - $n > 0, - "invalid integer size: size in bits must be non-zero" - ); + assert!($n > 0, "invalid integer size: size in bits must be non-zero"); assert!( $n.is_power_of_two(), "invalid integer size: size in bits must be a power of two, got {}", @@ -62,7 +59,8 @@ macro_rules! assert_valid_stack_index { ($idx:ident) => { assert!( $idx < 16, - "invalid stack index: only the first 16 elements on the stack are directly accessible, got {}", + "invalid stack index: only the first 16 elements on the stack are directly \ + accessible, got {}", $idx ); }; @@ -70,10 +68,11 @@ macro_rules! assert_valid_stack_index { ($idx:expr) => { assert!( ($idx) < 16, - "invalid stack index: only the first 16 elements on the stack are directly accessible, got {}", + "invalid stack index: only the first 16 elements on the stack are directly \ + accessible, got {}", $idx ); - } + }; } pub mod binary; @@ -87,11 +86,11 @@ pub mod smallint; pub mod unary; use core::ops::{Deref, DerefMut}; -use miden_hir::{self as hir, Immediate, Type}; -use crate::masm::{self as masm, Op}; +use miden_hir::{self as hir, Immediate, Type}; use super::{Operand, OperandStack}; +use crate::masm::{self as masm, Op}; /// This structure is used to emit the Miden Assembly ops corresponding to an IR instruction. /// @@ -199,6 +198,7 @@ impl<'a> OpEmitter<'a> { pub fn stack_mut<'c, 'b: 'c>(&'b mut self) -> &'c mut OperandStack { self.stack } + /// Emit `op` to the current block #[inline(always)] pub fn emit(&mut self, op: masm::Op) { @@ -479,10 +479,11 @@ impl<'a> OpEmitter<'a> { /// Copy the `n`th operand on the stack, and make it the `m`th operand on the stack. /// - /// If the operand is for a commutative, binary operator, indicated by `is_commutative_binary_operand`, - /// and the desired position is just below the top of stack, this function may leave it on top of the - /// stack instead, since the order of the operands is not strict. This can result in fewer stack - /// manipulation instructions in some scenarios. + /// If the operand is for a commutative, binary operator, indicated by + /// `is_commutative_binary_operand`, and the desired position is just below the top of + /// stack, this function may leave it on top of the stack instead, since the order of the + /// operands is not strict. This can result in fewer stack manipulation instructions in some + /// scenarios. pub fn copy_operand_to_position( &mut self, n: usize, @@ -516,11 +517,11 @@ impl<'a> OpEmitter<'a> { /// Make the `n`th operand on the stack, the `m`th operand on the stack. /// - /// If the operand is for a commutative, binary operator, indicated by `is_commutative_binary_operand`, - /// and the desired position is one of the first two items on the stack, this function may leave the - /// operand in it's current position if it is already one of the first two items on the stack, - /// since the order of the operands is not strict. This can result in fewer stack manipulation - /// instructions in some scenarios. + /// If the operand is for a commutative, binary operator, indicated by + /// `is_commutative_binary_operand`, and the desired position is one of the first two items + /// on the stack, this function may leave the operand in it's current position if it is + /// already one of the first two items on the stack, since the order of the operands is not + /// strict. This can result in fewer stack manipulation instructions in some scenarios. pub fn move_operand_to_position( &mut self, n: usize, @@ -574,13 +575,10 @@ impl<'a> OpEmitter<'a> { #[cfg(test)] mod tests { - use miden_hir::{AbiParam, Felt, FieldElement, Overflow, Signature, Type}; + use miden_hir::{AbiParam, Felt, FieldElement, Overflow, Signature}; use super::*; - use crate::{ - codegen::{OperandStack, TypedValue}, - masm::Function, - }; + use crate::{codegen::TypedValue, masm::Function}; #[test] fn op_emitter_stack_manipulation_test() { @@ -609,10 +607,7 @@ mod tests { assert_eq!(ops[1], Op::PushU32(2)); assert_eq!(ops[2], Op::PushU8(3)); assert_eq!(ops[3], Op::Push2([Felt::new(1), Felt::ZERO])); - assert_eq!( - ops[4], - Op::Push2([Felt::new(3), Felt::new(u32::MAX as u64)]) - ); + assert_eq!(ops[4], Op::Push2([Felt::new(3), Felt::new(u32::MAX as u64)])); } assert_eq!(emitter.stack()[0], five); @@ -688,7 +683,8 @@ mod tests { assert_eq!(ops[9], Op::Movdn(6)); // [five_b, three, five_c, five_d, four_a, four_b, five_a] assert_eq!(ops[10], Op::Movdn(6)); // [three, five_c, five_d, four_a, four_b, five_a, five_b] assert_eq!(ops[11], Op::Movup(4)); // [four_b, three, five_c, five_d, four_a, five_a, five_b] - assert_eq!(ops[12], Op::Movup(4)); // [four_a, four_b, three, five_c, five_d, five_a, five_b] + assert_eq!(ops[12], Op::Movup(4)); // [four_a, four_b, three, five_c, five_d, five_a, + // five_b] } emitter.movdn(2); @@ -708,7 +704,8 @@ mod tests { assert_eq!(ops[11], Op::Movup(4)); // [four_b, three, five_c, five_d, four_a, five_a, five_b] assert_eq!(ops[12], Op::Movup(4)); // [four_a, four_b, three, five_c, five_d, five_a, five_b] assert_eq!(ops[13], Op::Movdn(4)); // [four_b, three, five_c, five_d, four_a, five_a, five_b] - assert_eq!(ops[14], Op::Movdn(4)); // [three, five_c, five_d, four_a, four_b, five_a, five_b] + assert_eq!(ops[14], Op::Movdn(4)); // [three, five_c, five_d, four_a, four_b, five_a, + // five_b] } emitter.movup(2); @@ -726,7 +723,8 @@ mod tests { assert_eq!(ops[13], Op::Movdn(4)); // [four_b, three, five_c, five_d, four_a, five_a, five_b] assert_eq!(ops[14], Op::Movdn(4)); // [three, five_c, five_d, four_a, four_b, five_a, five_b] assert_eq!(ops[15], Op::Movup(4)); // [four_b, three, five_c, five_d, four_a, five_a, five_b] - assert_eq!(ops[16], Op::Movup(4)); // [four_a, four_b, three, five_c, five_d, five_a, five_b] + assert_eq!(ops[16], Op::Movup(4)); // [four_a, four_b, three, five_c, five_d, five_a, + // five_b] } emitter.drop(); diff --git a/codegen/masm/src/codegen/emit/primop.rs b/codegen/masm/src/codegen/emit/primop.rs index ae1794956..0d8706858 100644 --- a/codegen/masm/src/codegen/emit/primop.rs +++ b/codegen/masm/src/codegen/emit/primop.rs @@ -1,8 +1,7 @@ use miden_hir::{self as hir, ArgumentExtension, ArgumentPurpose, Felt, Immediate, Type}; -use crate::masm::Op; - use super::{int64, OpEmitter}; +use crate::masm::Op; impl<'a> OpEmitter<'a> { /// Assert that an integer value on the stack has the value 1 @@ -67,11 +66,7 @@ impl<'a> OpEmitter<'a> { let rhs = self.pop().expect("operand stack is empty"); let lhs = self.pop().expect("operand stack is empty"); let ty = lhs.ty(); - assert_eq!( - ty, - rhs.ty(), - "expected assert_eq operands to have the same type" - ); + assert_eq!(ty, rhs.ty(), "expected assert_eq operands to have the same type"); match ty { Type::Felt | Type::U32 @@ -107,11 +102,7 @@ impl<'a> OpEmitter<'a> { pub fn assert_eq_imm(&mut self, imm: Immediate) { let lhs = self.pop().expect("operand stack is empty"); let ty = lhs.ty(); - assert_eq!( - ty, - imm.ty(), - "expected assert_eq_imm operands to have the same type" - ); + assert_eq!(ty, imm.ty(), "expected assert_eq_imm operands to have the same type"); match ty { Type::Felt | Type::U32 @@ -151,8 +142,9 @@ impl<'a> OpEmitter<'a> { /// Emit code to select between two values of the same type, based on a boolean condition. /// /// The semantics of this instruction are basically the same as Miden's `cdrop` instruction, - /// but with support for selecting between any of the representable integer/pointer types as values. - /// Given three values on the operand stack (in order of appearance), `c`, `b`, and `a`: + /// but with support for selecting between any of the representable integer/pointer types as + /// values. Given three values on the operand stack (in order of appearance), `c`, `b`, and + /// `a`: /// /// * Pop `c` from the stack. This value must be an i1/boolean, or execution will trap. /// * Pop `b` and `a` from the stack, and push back `b` if `c` is true, or `a` if `c` is false. @@ -213,9 +205,23 @@ impl<'a> OpEmitter<'a> { // Validate the purpose matches match param.purpose { ArgumentPurpose::StructReturn => { - assert_eq!(i, 0, "invalid function signature: sret parameters must be the first parameter, and only one sret parameter is allowed"); - assert_eq!(signature.results.len(), 0, "invalid function signature: a function with sret parameters cannot also have results"); - assert!(ty.is_pointer(), "invalid exec to {callee}: invalid argument for sret parameter, expected {}, got {ty}", ¶m.ty); + assert_eq!( + i, 0, + "invalid function signature: sret parameters must be the first parameter, \ + and only one sret parameter is allowed" + ); + assert_eq!( + signature.results.len(), + 0, + "invalid function signature: a function with sret parameters cannot also \ + have results" + ); + assert!( + ty.is_pointer(), + "invalid exec to {callee}: invalid argument for sret parameter, expected \ + {}, got {ty}", + ¶m.ty + ); } ArgumentPurpose::Default => (), } @@ -223,34 +229,71 @@ impl<'a> OpEmitter<'a> { match param.extension { // Types must match exactly ArgumentExtension::None => { - assert_eq!(ty, param.ty, "invalid call to {callee}: invalid argument type for parameter at index {i}"); + assert_eq!( + ty, param.ty, + "invalid call to {callee}: invalid argument type for parameter at index \ + {i}" + ); } - // Caller can provide a smaller type which will be zero-extended to the expected type + // Caller can provide a smaller type which will be zero-extended to the expected + // type // - // However, the argument must be an unsigned integer, and of smaller or equal size in order for the types to differ + // However, the argument must be an unsigned integer, and of smaller or equal size + // in order for the types to differ ArgumentExtension::Zext if ty != param.ty => { - assert!(param.ty.is_unsigned_integer(), "invalid function signature: zero-extension is only valid for unsigned integer types"); - assert!(ty.is_unsigned_integer(), "invalid call to {callee}: invalid argument type for parameter at index {i}, expected unsigned integer type, got {ty}"); + assert!( + param.ty.is_unsigned_integer(), + "invalid function signature: zero-extension is only valid for unsigned \ + integer types" + ); + assert!( + ty.is_unsigned_integer(), + "invalid call to {callee}: invalid argument type for parameter at index \ + {i}, expected unsigned integer type, got {ty}" + ); let expected_size = param.ty.size_in_bits(); let provided_size = param.ty.size_in_bits(); - assert!(provided_size <= expected_size, "invalid call to {callee}: invalid argument type for parameter at index {i}, expected integer width to be <= {expected_size} bits"); + assert!( + provided_size <= expected_size, + "invalid call to {callee}: invalid argument type for parameter at index \ + {i}, expected integer width to be <= {expected_size} bits" + ); // Zero-extend this argument self.stack.push(arg); self.zext(¶m.ty); self.stack.drop(); } - // Caller can provide a smaller type which will be sign-extended to the expected type + // Caller can provide a smaller type which will be sign-extended to the expected + // type // - // However, the argument must be an integer which can fit in the range of the expected type + // However, the argument must be an integer which can fit in the range of the + // expected type ArgumentExtension::Sext if ty != param.ty => { - assert!(param.ty.is_signed_integer(), "invalid function signature: sign-extension is only valid for signed integer types"); - assert!(ty.is_integer(), "invalid call to {callee}: invalid argument type for parameter at index {i}, expected integer type, got {ty}"); + assert!( + param.ty.is_signed_integer(), + "invalid function signature: sign-extension is only valid for signed \ + integer types" + ); + assert!( + ty.is_integer(), + "invalid call to {callee}: invalid argument type for parameter at index \ + {i}, expected integer type, got {ty}" + ); let expected_size = param.ty.size_in_bits(); let provided_size = param.ty.size_in_bits(); if ty.is_unsigned_integer() { - assert!(provided_size < expected_size, "invalid call to {callee}: invalid argument type for parameter at index {i}, expected unsigned integer width to be < {expected_size} bits"); + assert!( + provided_size < expected_size, + "invalid call to {callee}: invalid argument type for parameter at \ + index {i}, expected unsigned integer width to be < {expected_size} \ + bits" + ); } else { - assert!(provided_size <= expected_size, "invalid call to {callee}: invalid argument type for parameter at index {i}, expected integer width to be <= {expected_size} bits"); + assert!( + provided_size <= expected_size, + "invalid call to {callee}: invalid argument type for parameter at \ + index {i}, expected integer width to be <= {expected_size} bits" + ); } // Push the operand back on the stack for `sext` self.stack.push(arg); diff --git a/codegen/masm/src/codegen/emit/smallint.rs b/codegen/masm/src/codegen/emit/smallint.rs index 87e75a732..89f539b59 100644 --- a/codegen/masm/src/codegen/emit/smallint.rs +++ b/codegen/masm/src/codegen/emit/smallint.rs @@ -8,10 +8,10 @@ //! For signed smallint operations, we implement them in terms of a two's complement representation, //! using a set of common primitives. The only thing that changes are which bits are considered by //! those primitives. -use crate::masm::Op; use miden_hir::Overflow; use super::OpEmitter; +use crate::masm::Op; #[allow(unused)] impl<'a> OpEmitter<'a> { @@ -277,7 +277,8 @@ impl<'a> OpEmitter<'a> { self.unchecked_mod_imm_u32(imm); } - /// Pops two u32 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the stack. + /// Pops two u32 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the + /// stack. /// /// This operation is checked, so if the operands or result are not valid u32, execution traps. #[inline(always)] @@ -293,7 +294,8 @@ impl<'a> OpEmitter<'a> { self.checked_divmod_imm_u32(imm); } - /// Pops two u32 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the stack. + /// Pops two u32 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the + /// stack. /// /// This operation is unchecked, so the result is not guaranteed to be a valid u32 #[inline(always)] diff --git a/codegen/masm/src/codegen/emit/unary.rs b/codegen/masm/src/codegen/emit/unary.rs index 59cb6da09..93964783f 100644 --- a/codegen/masm/src/codegen/emit/unary.rs +++ b/codegen/masm/src/codegen/emit/unary.rs @@ -88,7 +88,8 @@ impl<'a> OpEmitter<'a> { let src = arg.ty(); assert!( src.is_unsigned_integer() && dst.is_integer(), - "invalid zero-extension of {src} to {dst}: only unsigned integer-to-integer casts are supported" + "invalid zero-extension of {src} to {dst}: only unsigned integer-to-integer casts are \ + supported" ); let src_bits = src.size_in_bits() as u32; let dst_bits = dst.size_in_bits() as u32; @@ -103,12 +104,17 @@ impl<'a> OpEmitter<'a> { (Type::U64, Type::I128) => self.push_u64(0), (Type::Felt, Type::U64 | Type::I128) => self.zext_felt(dst_bits), (Type::U32, Type::U64 | Type::I64 | Type::I128) => self.zext_int32(dst_bits), - (Type::I1 | Type::U8 | Type::U16, Type::U64 | Type::I64 | Type::I128) => self.zext_smallint(src_bits, dst_bits), + (Type::I1 | Type::U8 | Type::U16, Type::U64 | Type::I64 | Type::I128) => { + self.zext_smallint(src_bits, dst_bits) + } // Zero-extending to u32/i32 from smaller integers is a no-op (Type::I1 | Type::U8 | Type::U16, Type::U32 | Type::I32) => (), // Zero-extending to felt, from types that fit in felt, is a no-op (Type::I1 | Type::U8 | Type::U16 | Type::U32, Type::Felt) => (), - (src, dst) if dst.is_signed_integer() => panic!("invalid zero-extension from {src} to {dst}: value may not fit in range, use explicit cast instead"), + (src, dst) if dst.is_signed_integer() => panic!( + "invalid zero-extension from {src} to {dst}: value may not fit in range, use \ + explicit cast instead" + ), (src, dst) => panic!("unsupported zero-extension from {src} to {dst}"), } self.stack.push(dst.clone()); @@ -143,7 +149,11 @@ impl<'a> OpEmitter<'a> { pub fn sext(&mut self, dst: &Type) { let arg = self.stack.pop().expect("operand stack is empty"); let src = arg.ty(); - assert!(src.is_integer() && dst.is_signed_integer(), "invalid sign-extension of {src} to {dst}: only integer-to-signed-integer casts are supported"); + assert!( + src.is_integer() && dst.is_signed_integer(), + "invalid sign-extension of {src} to {dst}: only integer-to-signed-integer casts are \ + supported" + ); let src_bits = src.size_in_bits() as u32; let dst_bits = dst.size_in_bits() as u32; assert!( @@ -537,7 +547,8 @@ impl<'a> OpEmitter<'a> { self.stack.push(ty); } - /// Compute the modular multiplicative inverse of the operand on top of the stack, `n`, i.e. `n^-1 mod P`. + /// Compute the modular multiplicative inverse of the operand on top of the stack, `n`, i.e. + /// `n^-1 mod P`. /// /// This operation consumes the input operand. pub fn inv(&mut self) { diff --git a/codegen/masm/src/codegen/emitter.rs b/codegen/masm/src/codegen/emitter.rs index 1340b7fe9..5bbefffac 100644 --- a/codegen/masm/src/codegen/emitter.rs +++ b/codegen/masm/src/codegen/emitter.rs @@ -7,14 +7,13 @@ use miden_hir_analysis::{ }; use smallvec::SmallVec; -use crate::masm::{self, Op}; - use super::{ emit::{InstOpEmitter, OpEmitter}, opt::{OperandMovementConstraintSolver, SolverError}, scheduler::{BlockInfo, InstInfo, Schedule, ScheduleOp}, Constraint, OperandStack, }; +use crate::masm::{self, Op}; pub struct FunctionEmitter<'a> { f: &'a hir::Function, @@ -203,13 +202,9 @@ impl<'b, 'f: 'b> BlockEmitter<'b, 'f> { // NOTE: This does not include block arguments for control flow instructions, those are // handled separately within the specific handlers for those instructions let args = self.function.f.dfg.inst_args(inst_info.inst); - self.schedule_operands(args, inst_info.plain_arguments()) - .unwrap_or_else(|err| { - panic!( - "failed to schedule operands for {}: {err:?}", - inst_info.inst - ) - }); + self.schedule_operands(args, inst_info.plain_arguments()).unwrap_or_else(|err| { + panic!("failed to schedule operands for {}: {err:?}", inst_info.inst) + }); match self.function.f.dfg.inst(inst_info.inst) { ix @ (Instruction::RetImm(_) | Instruction::Ret(_)) => self.emit_ret(inst_info, ix), @@ -283,10 +278,7 @@ impl<'b, 'f: 'b> BlockEmitter<'b, 'f> { let args = op.args.as_slice(&self.function.f.dfg.value_lists); self.schedule_operands(args, inst_info.block_arguments(destination)) .unwrap_or_else(|err| { - panic!( - "failed to schedule operands for {}: {err:?}", - inst_info.inst - ) + panic!("failed to schedule operands for {}: {err:?}", inst_info.inst) }); // Rename operands on stack to destination block parameters let params = self.function.f.dfg.block_params(destination); @@ -317,18 +309,11 @@ impl<'b, 'f: 'b> BlockEmitter<'b, 'f> { } else { // We should only be emitting code for a block more than once if that block // is a loop header. All other blocks should only be visited a single time. - assert!( - in_loop_header, - "unexpected cycle at {}", - self.block_info.source - ); + assert!(in_loop_header, "unexpected cycle at {}", self.block_info.source); // Calculate let current_level = self.controlling_loop_level().unwrap_or_else(|| { - panic!( - "expected controlling loop to be set in {}", - self.block_info.source - ) + panic!("expected controlling loop to be set in {}", self.block_info.source) }); let target_level = self.loop_level(self.block_info.source); let mut emitter = self.emitter(); @@ -367,10 +352,8 @@ impl<'b, 'f: 'b> BlockEmitter<'b, 'f> { self.emit_op(Op::If(then_blk, else_blk)); } - let successors = [ - (then_dest, then_blk, op.then_dest.1), - (else_dest, else_blk, op.else_dest.1), - ]; + let successors = + [(then_dest, then_blk, op.then_dest.1), (else_dest, else_blk, op.else_dest.1)]; for (block, masm_block, args) in successors.into_iter() { // Make a copy of the operand stack in the current block // to be used as the state of the operand stack in the @@ -419,10 +402,7 @@ impl<'b, 'f: 'b> BlockEmitter<'b, 'f> { ); let current_level = self.controlling_loop_level().unwrap_or_else(|| { - panic!( - "expected controlling loop to be set in {}", - self.block_info.source - ) + panic!("expected controlling loop to be set in {}", self.block_info.source) }); let target_level = self.loop_level(self.block_info.source); // Continue the target loop when it is reached, the top of the stack @@ -649,11 +629,8 @@ impl<'b, 'f: 'b> BlockEmitter<'b, 'f> { } // Store a value at a constant address hir::Opcode::Store => { - emitter.store_imm( - op.imm - .as_u32() - .expect("invalid address immediate: out of range"), - ); + emitter + .store_imm(op.imm.as_u32().expect("invalid address immediate: out of range")); } opcode => unimplemented!("unrecognized primop with immediate opcode: '{opcode}'"), } @@ -745,17 +722,8 @@ impl<'b, 'f: 'b> BlockEmitter<'b, 'f> { self.rewrite_inline_assembly_block(op, &mut rewrites, &mapped); // Pop arguments, push results - self.stack - .dropn(op.args.len(&self.function.f.dfg.value_lists)); - for result in self - .function - .f - .dfg - .inst_results(inst_info.inst) - .iter() - .copied() - .rev() - { + self.stack.dropn(op.args.len(&self.function.f.dfg.value_lists)); + for result in self.function.f.dfg.inst_results(inst_info.inst).iter().copied().rev() { let ty = self.function.f.dfg.value_type(result).clone(); self.stack.push(TypedValue { value: result, ty }); } @@ -807,9 +775,7 @@ impl<'b, 'f: 'b> BlockEmitter<'b, 'f> { let mut unused = SmallVec::<[hir::Value; 4]>::default(); let mut constraints = SmallVec::<[Constraint; 4]>::default(); for operand in self.stack.iter().rev() { - let value = operand - .as_value() - .expect("unexpected non-ssa value on stack"); + let value = operand.as_value().expect("unexpected non-ssa value on stack"); // If the given value is not live on entry to this block, it should be dropped if !self.function.liveness.is_live_at(&value, pp) { println!( @@ -928,13 +894,12 @@ impl<'b, 'f: 'b> BlockEmitter<'b, 'f> { current_index = 0; } } else { - self.schedule_operands(&unused, &constraints) - .unwrap_or_else(|err| { - panic!( - "failed to schedule unused operands for {}: {err:?}", - self.block_info.source - ) - }); + self.schedule_operands(&unused, &constraints).unwrap_or_else(|err| { + panic!( + "failed to schedule unused operands for {}: {err:?}", + self.block_info.source + ) + }); let mut emitter = self.emitter(); emitter.dropn(unused.len()); } @@ -1036,8 +1001,8 @@ impl<'b, 'f: 'b> BlockEmitter<'b, 'f> { ); self.controlling_loop } - // If we're entering a nested loop, then we need to update the controlling loop - // to reflect the loop we've entered + // If we're entering a nested loop, then we need to update the controlling + // loop to reflect the loop we've entered Ordering::Less => Some(dst), Ordering::Equal => self.controlling_loop, } @@ -1083,8 +1048,7 @@ impl<'b, 'f: 'b> BlockEmitter<'b, 'f> { } fn controlling_loop_level(&self) -> Option { - self.controlling_loop - .map(|lp| self.function.loops.level(lp).level()) + self.controlling_loop.map(|lp| self.function.loops.level(lp).level()) } fn loop_level(&self, block: hir::Block) -> usize { diff --git a/codegen/masm/src/codegen/mod.rs b/codegen/masm/src/codegen/mod.rs index 65dc6037b..2f025858e 100644 --- a/codegen/masm/src/codegen/mod.rs +++ b/codegen/masm/src/codegen/mod.rs @@ -4,6 +4,8 @@ mod opt; mod scheduler; mod stack; -pub use self::emitter::FunctionEmitter; -pub use self::scheduler::Scheduler; -pub use self::stack::{Constraint, Operand, OperandStack, TypedValue}; +pub use self::{ + emitter::FunctionEmitter, + scheduler::Scheduler, + stack::{Constraint, Operand, OperandStack, TypedValue}, +}; diff --git a/codegen/masm/src/codegen/opt/operands/context.rs b/codegen/masm/src/codegen/opt/operands/context.rs index 429b7f802..97d34b488 100644 --- a/codegen/masm/src/codegen/opt/operands/context.rs +++ b/codegen/masm/src/codegen/opt/operands/context.rs @@ -1,5 +1,4 @@ -use std::collections::BTreeMap; -use std::num::NonZeroU8; +use std::{collections::BTreeMap, num::NonZeroU8}; use miden_hir as hir; @@ -67,10 +66,7 @@ impl SolverContext { // copies of them anyway. let requires_copies = !copies.is_empty(); let is_solved = !requires_copies - && expected_output - .iter() - .rev() - .all(|op| &stack[op.pos as usize] == op); + && expected_output.iter().rev().all(|op| &stack[op.pos as usize] == op); if is_solved { return Err(SolverError::AlreadySolved); } diff --git a/codegen/masm/src/codegen/opt/operands/mod.rs b/codegen/masm/src/codegen/opt/operands/mod.rs index 46081a939..71e3926f7 100644 --- a/codegen/masm/src/codegen/opt/operands/mod.rs +++ b/codegen/masm/src/codegen/opt/operands/mod.rs @@ -3,15 +3,13 @@ mod solver; mod stack; mod tactics; -use self::context::SolverContext; -pub use self::solver::{OperandMovementConstraintSolver, SolverError}; -use self::stack::Stack; - -use std::fmt; -use std::num::NonZeroU8; +use std::{fmt, num::NonZeroU8}; use miden_hir as hir; +pub use self::solver::{OperandMovementConstraintSolver, SolverError}; +use self::{context::SolverContext, stack::Stack}; + /// This represents a specific action that should be taken by /// the code generator with regard to an operand on the stack. /// diff --git a/codegen/masm/src/codegen/opt/operands/solver.rs b/codegen/masm/src/codegen/opt/operands/solver.rs index dab0fe4a7..d804478d9 100644 --- a/codegen/masm/src/codegen/opt/operands/solver.rs +++ b/codegen/masm/src/codegen/opt/operands/solver.rs @@ -1,9 +1,8 @@ use miden_hir as hir; use smallvec::SmallVec; -use crate::codegen::Constraint; - use super::{tactics::Tactic, *}; +use crate::codegen::Constraint; /// This error type is produced by the [OperandMovementConstraintSolver] #[derive(Debug)] @@ -16,35 +15,40 @@ pub enum SolverError { /// The [OperandMovementConstraintSolver] is used to produce a solution to the following problem: /// -/// An instruction is being emitted which requires some specific set of operands, in a particular order. -/// These operands are known to be on the operand stack, but their usage is constrained by a rule that -/// determines whether a specific use of an operand can consume the operand, or must copy it and consume -/// the copy. Furthermore, the operands on the stack are not guaranteed to be in the desired order, so we -/// must also move operands into position while operating within the bounds of the move/copy constraints. +/// An instruction is being emitted which requires some specific set of operands, in a particular +/// order. These operands are known to be on the operand stack, but their usage is constrained by a +/// rule that determines whether a specific use of an operand can consume the operand, or must copy +/// it and consume the copy. Furthermore, the operands on the stack are not guaranteed to be in the +/// desired order, so we must also move operands into position while operating within the bounds of +/// the move/copy constraints. /// -/// Complicating matters further, a naive approach to solving this problem will produce a lot of unnecessary -/// stack manipulation instructions in the emitted code. We would like the code we emit to match what a human -/// might write if facing the same set of constraints. As a result, we are looking for a solution to this -/// problem that is also the "smallest" solution, i.e. the least expensive solution in terms of cycle count. +/// Complicating matters further, a naive approach to solving this problem will produce a lot of +/// unnecessary stack manipulation instructions in the emitted code. We would like the code we emit +/// to match what a human might write if facing the same set of constraints. As a result, we are +/// looking for a solution to this problem that is also the "smallest" solution, i.e. the least +/// expensive solution in terms of cycle count. /// /// ## Implementation /// -/// With that context in mind, what we have here is a non-trivial optimization problem. If we could treat the -/// operand stack as an array, and didn't have to worry about copies, we could solve this using a standard -/// minimum-swap solution, but neither of those are true here. The copy constraint, when present, means -/// that even if the stack is in the exact order we need, we must still find a way to copy the operands -/// we are required to copy, move the ones we are required to consume, and do so in such a way that getting -/// them into the required order on top of the stack takes the minimum number of steps. +/// With that context in mind, what we have here is a non-trivial optimization problem. If we could +/// treat the operand stack as an array, and didn't have to worry about copies, we could solve this +/// using a standard minimum-swap solution, but neither of those are true here. The copy constraint, +/// when present, means that even if the stack is in the exact order we need, we must still find a +/// way to copy the operands we are required to copy, move the ones we are required to consume, and +/// do so in such a way that getting them into the required order on top of the stack takes the +/// minimum number of steps. /// -/// Even this would be relatively straightforward, but an additional problem is that the MASM instruction -/// set does not provide us a way to swap two operands at arbitrary positions on the stack. We are forced -/// to move operands to the top of the stack before we can move them elsewhere (either by swapping them -/// with the current operand on top of the stack, or by moving the operand up to the top, shifting all -/// the remaining operands on the stack down by one). However, moving a value up/down the stack also has -/// the effect of shifting other values on the stack, which may shift them in to, or out of, position. +/// Even this would be relatively straightforward, but an additional problem is that the MASM +/// instruction set does not provide us a way to swap two operands at arbitrary positions on the +/// stack. We are forced to move operands to the top of the stack before we can move them elsewhere +/// (either by swapping them with the current operand on top of the stack, or by moving the operand +/// up to the top, shifting all the remaining operands on the stack down by one). However, moving a +/// value up/down the stack also has the effect of shifting other values on the stack, which may +/// shift them in to, or out of, position. /// -/// Long story short, all of this must be taken into consideration at once, which is extremely difficult -/// to express in a way that is readable/maintainable, but also debuggable if something goes wrong. +/// Long story short, all of this must be taken into consideration at once, which is extremely +/// difficult to express in a way that is readable/maintainable, but also debuggable if something +/// goes wrong. /// /// To address these concerns, the [OperandMovementConstraintSolver] is architected as follows: /// @@ -56,15 +60,20 @@ pub enum SolverError { /// /// The solver produces one of three possible outcomes: /// -/// * `Ok(solution)`, where `solution` is a vector of actions the code generator must take to get the operands into place correctly +/// * `Ok(solution)`, where `solution` is a vector of actions the code generator must take to get +/// the operands into place correctly /// * `Err(AlreadySolved)`, indicating that the solver is not needed, and the stack is usable as-is -/// * `Err(_)`, indicating an unrecoverable error that prevented the solver from finding a solution with the given inputs +/// * `Err(_)`, indicating an unrecoverable error that prevented the solver from finding a solution +/// with the given inputs /// /// When the solver is constructed, it performs the following steps: /// -/// 1. Identify and rename aliased values to make them unique (i.e. multiple uses of the same value will be uniqued) -/// 2. Determine if any expected operands require copying (if so, then the solver is always required) -/// 3. Determine if the solver is required for the given inputs, and if not, return `Err(AlreadySolved)` +/// 1. Identify and rename aliased values to make them unique (i.e. multiple uses of the same value +/// will be uniqued) +/// 2. Determine if any expected operands require copying (if so, then the solver is always +/// required) +/// 3. Determine if the solver is required for the given inputs, and if not, return +/// `Err(AlreadySolved)` /// /// When the solver is run, it attempts to find an optimal solution using the following algorithm: /// @@ -72,8 +81,10 @@ pub enum SolverError { /// 2. If the tactic failed, go back to step 1. /// 3. If the tactic succeeded, take the best solution between the one we just produced, and the /// last one produced (if applicable). -/// 4. If we have optimization fuel remaining, go back to step 1 and see if we can find a better solution. -/// 5. If we have a solution, and either run out of optimization fuel, or tactics to try, then that solution is returned. +/// 4. If we have optimization fuel remaining, go back to step 1 and see if we can find a better +/// solution. +/// 5. If we have a solution, and either run out of optimization fuel, or tactics to try, then that +/// solution is returned. /// 6. If we haven't found a solution, then return an error pub struct OperandMovementConstraintSolver { context: SolverContext, @@ -82,7 +93,8 @@ pub struct OperandMovementConstraintSolver { fuel: usize, } impl OperandMovementConstraintSolver { - /// Construct a new solver for the given expected operands, constraints, and operand stack state. + /// Construct a new solver for the given expected operands, constraints, and operand stack + /// state. pub fn new( expected: &[hir::Value], constraints: &[Constraint], @@ -112,20 +124,17 @@ impl OperandMovementConstraintSolver { // We use a few heuristics to guide which tactics we try: // // * If all operands are copies, we only apply copy-all - // * If copies are needed, we only apply tactics which - // support copies, or a mix of copies and moves. - // * If no copies are needed, we start with the various - // move up/down + swap patterns, as many common patterns - // are solved in two moves or less with them. If no tactics - // are successful, move-all is used as the fallback. - // * If we have no optimization fuel, we do not attempt to - // look for better solutions once we've found one. - // * If we have optimization fuel, we will try additional - // tactics looking for a solution until we have exhausted - // the fuel, assuming the solution we do have can be - // minimized. For example, a solution which requires less - // than two actions is by definition optimal already, so - // we never waste time on optimization in such cases. + // * If copies are needed, we only apply tactics which support copies, or a mix of copies + // and moves. + // * If no copies are needed, we start with the various move up/down + swap patterns, as + // many common patterns are solved in two moves or less with them. If no tactics are + // successful, move-all is used as the fallback. + // * If we have no optimization fuel, we do not attempt to look for better solutions once + // we've found one. + // * If we have optimization fuel, we will try additional tactics looking for a solution + // until we have exhausted the fuel, assuming the solution we do have can be minimized. + // For example, a solution which requires less than two actions is by definition optimal + // already, so we never waste time on optimization in such cases. // The tactics are pushed in reverse order if self.tactics.is_empty() { @@ -171,18 +180,35 @@ impl OperandMovementConstraintSolver { match best_size { Some(best_size) if best_size > solution_size => { best_solution = Some(solution); - log::debug!("a better solution ({solution_size} vs {best_size}) was found using tactic {}", tactic.name()); + log::debug!( + "a better solution ({solution_size} vs {best_size}) was found \ + using tactic {}", + tactic.name() + ); } Some(best_size) => { - log::debug!("a solution of size {solution_size} was found using tactic {}, but it is no better than the best found so far ({best_size})", tactic.name()); + log::debug!( + "a solution of size {solution_size} was found using tactic \ + {}, but it is no better than the best found so far \ + ({best_size})", + tactic.name() + ); } None => { best_solution = Some(solution); - log::debug!("an initial solution of size {solution_size} was found using tactic {}", tactic.name()); + log::debug!( + "an initial solution of size {solution_size} was found using \ + tactic {}", + tactic.name() + ); } } } else { - log::debug!("a partial solution was found using tactic {}, but is not sufficient on its own", tactic.name()); + log::debug!( + "a partial solution was found using tactic {}, but is not sufficient \ + on its own", + tactic.name() + ); builder.discard(); } } @@ -223,16 +249,15 @@ impl OperandMovementConstraintSolver { "{:?} was not found on the operand stack", expected.value ); - let current_position = self - .context - .stack() - .position(&expected.value.unaliased()) - .unwrap_or_else(|| { - panic!( - "{:?} was not found on the operand stack", - expected.value.unaliased() - ) - }); + let current_position = + self.context.stack().position(&expected.value.unaliased()).unwrap_or_else( + || { + panic!( + "{:?} was not found on the operand stack", + expected.value.unaliased() + ) + }, + ); emitter.copy_operand_to_position(current_position, 0, false); } @@ -266,11 +291,10 @@ impl OperandMovementConstraintSolver { #[cfg(test)] mod tests { - use super::*; use miden_hir::{self as hir, Type}; - use proptest::prelude::*; - use proptest::strategy::Just; - use proptest::test_runner::TestRunner; + use proptest::{prelude::*, test_runner::TestRunner}; + + use super::*; #[allow(unused)] fn setup() { @@ -405,8 +429,10 @@ mod tests { // Strategy: // - // 1. Generate a set of 1..16 operands to form a stack (called `stack`), with no more than 2 pairs of duplicate operands - // 2. Generate a set of up to 8 constraints (called `constraints`) by sampling `stack` twice, and treating duplicate samples as copies + // 1. Generate a set of 1..16 operands to form a stack (called `stack`), with no more than 2 + // pairs of duplicate operands + // 2. Generate a set of up to 8 constraints (called `constraints`) by sampling `stack` twice, + // and treating duplicate samples as copies // 3. Generate the set of expected operands by mapping `constraints` to values #[derive(Debug)] struct ProblemInputs { @@ -514,6 +540,7 @@ mod tests { impl Strategy for CopyStrategy { type Tree = CopyStrategyValueTree; type Value = usize; + fn new_tree(&self, runner: &mut TestRunner) -> proptest::strategy::NewTree { let tree = self.strategy.new_tree(runner)?; Ok(CopyStrategyValueTree { @@ -662,16 +689,14 @@ mod tests { "solver returned error {result:?} for problem: {problem:#?}" ); let actions = result.unwrap(); - // We are expecting that if all operands are copies, that the number of actions is equal to the number of copies - if problem - .constraints - .iter() - .all(|c| matches!(c, Constraint::Copy)) - { + // We are expecting that if all operands are copies, that the number of actions is + // equal to the number of copies + if problem.constraints.iter().all(|c| matches!(c, Constraint::Copy)) { prop_assert_eq!(actions.len(), problem.expected.len()); } - // We are expecting that applying `actions` to the input stack will produce a stack that - // has all of the expected operands on top of the stack, ordered by id, e.g. [v1, v2, ..vN] + // We are expecting that applying `actions` to the input stack will produce a stack + // that has all of the expected operands on top of the stack, + // ordered by id, e.g. [v1, v2, ..vN] let mut stack = problem.stack.clone(); for action in actions.into_iter() { match action { diff --git a/codegen/masm/src/codegen/opt/operands/tactics/copy_all.rs b/codegen/masm/src/codegen/opt/operands/tactics/copy_all.rs index a4f66bc06..e91340ff2 100644 --- a/codegen/masm/src/codegen/opt/operands/tactics/copy_all.rs +++ b/codegen/masm/src/codegen/opt/operands/tactics/copy_all.rs @@ -14,7 +14,12 @@ impl Tactic for CopyAll { // We can't apply this tactic if any values should be moved let arity = builder.arity(); if builder.num_copies() != arity { - log::debug!("expected all operands to require copying; but only {} out of {} operands are copied", builder.num_copies(), arity); + log::debug!( + "expected all operands to require copying; but only {} out of {} operands are \ + copied", + builder.num_copies(), + arity + ); return Err(TacticError::PreconditionFailed); } @@ -36,7 +41,11 @@ impl Tactic for CopyAll { continue; } - log::trace!("moving {expected_value:?} at index {index} up to top of stack, shifting {:?} down one", builder.unwrap_current(0)); + log::trace!( + "moving {expected_value:?} at index {index} up to top of stack, shifting {:?} \ + down one", + builder.unwrap_current(0) + ); builder.movup(current_position); } else { let current_position = builder @@ -48,7 +57,11 @@ impl Tactic for CopyAll { ) }); // A copy already exists, so use it - log::trace!("copying {expected_value:?} at index {index} to top of stack, shifting {:?} down one", builder.unwrap_current(0)); + log::trace!( + "copying {expected_value:?} at index {index} to top of stack, shifting {:?} \ + down one", + builder.unwrap_current(0) + ); builder.dup(current_position, expected_value.unwrap_alias()); } } diff --git a/codegen/masm/src/codegen/opt/operands/tactics/linear.rs b/codegen/masm/src/codegen/opt/operands/tactics/linear.rs index b67e15826..e48a14e8d 100644 --- a/codegen/masm/src/codegen/opt/operands/tactics/linear.rs +++ b/codegen/masm/src/codegen/opt/operands/tactics/linear.rs @@ -1,6 +1,5 @@ -use petgraph::prelude::{DiGraphMap, Direction}; - use miden_hir::adt::SmallSet; +use petgraph::prelude::{DiGraphMap, Direction}; use super::*; @@ -68,7 +67,10 @@ impl Tactic for Linear { continue; } let occupied_by = builder.unwrap_current(expected_at); - log::trace!("{value:?} at index {currently_at}, is expected at index {expected_at}, which is currently occupied by {occupied_by:?}"); + log::trace!( + "{value:?} at index {currently_at}, is expected at index {expected_at}, which \ + is currently occupied by {occupied_by:?}" + ); let from = graph.add_node(Operand { pos: currently_at, value, @@ -94,23 +96,27 @@ impl Tactic for Linear { pos: currently_at, value, }; - let mut parent = graph - .neighbors_directed(operand, Direction::Incoming) - .next(); + let mut parent = graph.neighbors_directed(operand, Direction::Incoming).next(); // There must have been an immediate parent to `value`, or it would // have an expected position on the stack, and only expected operands // are materialized initially. let mut root = parent.unwrap(); - log::trace!("{value:?} at index {currently_at}, is not an expected operand; but must be moved to make space for {:?}", root.value); + log::trace!( + "{value:?} at index {currently_at}, is not an expected operand; but must be \ + moved to make space for {:?}", + root.value + ); let mut seen = std::collections::BTreeSet::default(); seen.insert(root); while let Some(parent_operand) = parent { root = parent_operand; - parent = graph - .neighbors_directed(parent_operand, Direction::Incoming) - .next(); + parent = graph.neighbors_directed(parent_operand, Direction::Incoming).next(); } - log::trace!("forming component with {value:?} by adding edge to {:?}, the start of the path which led to it", root.value); + log::trace!( + "forming component with {value:?} by adding edge to {:?}, the start of the \ + path which led to it", + root.value + ); graph.add_edge(operand, root, ()); } current_index += 1; @@ -119,7 +125,10 @@ impl Tactic for Linear { // Compute the strongly connected components of the graph we've constructed, // and use that to drive our decisions about moving operands into place. let components = petgraph::algo::kosaraju_scc(&graph); - log::trace!("found the following connected components when analyzing required operand moves: {components:?}"); + log::trace!( + "found the following connected components when analyzing required operand moves: \ + {components:?}" + ); for component in components.into_iter() { // A component of two or more elements indicates a cycle of operands. // @@ -164,11 +173,7 @@ impl Tactic for Linear { // if component.len() > 1 { // Find the operand at the shallowest depth on the stack to move. - let start = component - .iter() - .min_by(|a, b| a.pos.cmp(&b.pos)) - .copied() - .unwrap(); + let start = component.iter().min_by(|a, b| a.pos.cmp(&b.pos)).copied().unwrap(); log::trace!( "resolving component {component:?} by starting from {:?} at index {}", start.value, @@ -182,10 +187,8 @@ impl Tactic for Linear { } // Do the initial swap to set up our state for the remaining swaps - let mut child = graph - .neighbors_directed(start, Direction::Outgoing) - .next() - .unwrap(); + let mut child = + graph.neighbors_directed(start, Direction::Outgoing).next().unwrap(); // Swap each child with its parent until we reach the edge that forms a cycle while child != start { log::trace!( @@ -195,10 +198,7 @@ impl Tactic for Linear { child.pos ); builder.swap(child.pos); - child = graph - .neighbors_directed(child, Direction::Outgoing) - .next() - .unwrap(); + child = graph.neighbors_directed(child, Direction::Outgoing).next().unwrap(); } // If necessary, move the final operand to the original starting position diff --git a/codegen/masm/src/codegen/opt/operands/tactics/mod.rs b/codegen/masm/src/codegen/opt/operands/tactics/mod.rs index 04866b09b..4b8c1093a 100644 --- a/codegen/masm/src/codegen/opt/operands/tactics/mod.rs +++ b/codegen/masm/src/codegen/opt/operands/tactics/mod.rs @@ -8,11 +8,10 @@ mod move_down_and_swap; mod move_up_and_swap; mod swap_and_move_up; -pub use self::copy_all::CopyAll; -pub use self::linear::Linear; -pub use self::move_down_and_swap::MoveDownAndSwap; -pub use self::move_up_and_swap::MoveUpAndSwap; -pub use self::swap_and_move_up::SwapAndMoveUp; +pub use self::{ + copy_all::CopyAll, linear::Linear, move_down_and_swap::MoveDownAndSwap, + move_up_and_swap::MoveUpAndSwap, swap_and_move_up::SwapAndMoveUp, +}; /// An error returned by an [OperandMovementConstraintSolver] tactic #[derive(Debug)] @@ -179,10 +178,7 @@ impl<'a> SolutionBuilder<'a> { /// Get the position at which `value` is expected pub fn get_expected_position(&self, value: &ValueOrAlias) -> Option { - self.context - .expected() - .position(value) - .map(|index| index as u8) + self.context.expected().position(value).map(|index| index as u8) } #[track_caller] diff --git a/codegen/masm/src/codegen/opt/operands/tactics/move_down_and_swap.rs b/codegen/masm/src/codegen/opt/operands/tactics/move_down_and_swap.rs index 33216cfea..ee0e65fbc 100644 --- a/codegen/masm/src/codegen/opt/operands/tactics/move_down_and_swap.rs +++ b/codegen/masm/src/codegen/opt/operands/tactics/move_down_and_swap.rs @@ -18,8 +18,8 @@ use super::*; /// * The operand on top of the stack is out of place, /// and the operand in its place is either: /// 1. Expected to be on top of the stack, so we can swap -/// 2. Will be moved into place if we move the top of the -/// stack immediately past it, so we can move +/// 2. Will be moved into place if we move the top of the stack immediately past it, so we can +/// move /// /// If we apply these steps, and the stack ends up in the /// desired order, then this tactic was successful. @@ -28,7 +28,12 @@ pub struct MoveDownAndSwap; impl Tactic for MoveDownAndSwap { fn apply(&mut self, builder: &mut SolutionBuilder) -> TacticResult { if builder.requires_copies() || builder.arity() < 2 { - log::debug!("cannot apply tactic when there are required copies ({}) or fewer than 2 operands ({})", builder.requires_copies(), builder.arity()); + log::debug!( + "cannot apply tactic when there are required copies ({}) or fewer than 2 operands \ + ({})", + builder.requires_copies(), + builder.arity() + ); return Err(TacticError::PreconditionFailed); } @@ -45,7 +50,10 @@ impl Tactic for MoveDownAndSwap { log::trace!( "{actual0:?} expects to be at index {target_pos}, but is on top of the stack" ); - log::trace!("looking for operands after index {target_pos} which need to come before {actual0:?} on the stack"); + log::trace!( + "looking for operands after index {target_pos} which need to come before \ + {actual0:?} on the stack" + ); let target_offset = builder .stack() .iter() @@ -75,7 +83,10 @@ impl Tactic for MoveDownAndSwap { } else { let expected0 = builder.unwrap_expected(0); let expected0_at = builder.unwrap_current_position(&expected0); - log::trace!("{actual0:?} is not an expected operand, but is occupying index {expected0_at}, where we expect {expected0:?}, evicting.."); + log::trace!( + "{actual0:?} is not an expected operand, but is occupying index {expected0_at}, \ + where we expect {expected0:?}, evicting.." + ); builder.evict(); } @@ -88,7 +99,10 @@ impl Tactic for MoveDownAndSwap { // have a solution already, and also so that we can // potentially try combining this tactic with another // to find a solution - log::trace!("item on top of the stack is now in position, so we cannot proceed further, returning possible solution"); + log::trace!( + "item on top of the stack is now in position, so we cannot proceed further, \ + returning possible solution" + ); return Ok(()); } @@ -101,12 +115,18 @@ impl Tactic for MoveDownAndSwap { match target_expected_pos { Some(0) => { // The target expects to be on top, so we can swap - log::trace!("{actual0:?} is expected at {target_pos}, the occupant of which is expected on top of the stack, swapping.."); + log::trace!( + "{actual0:?} is expected at {target_pos}, the occupant of which is \ + expected on top of the stack, swapping.." + ); builder.swap(target_pos); } Some(pos) if pos == target_pos - 1 => { // The target would be moved into place if we move the top down - log::trace!("moving {actual0:?} to {target_pos}, the occupant of which is expected at {pos}"); + log::trace!( + "moving {actual0:?} to {target_pos}, the occupant of which is expected at \ + {pos}" + ); builder.movdn(target_pos); } Some(_) | None => { @@ -120,7 +140,10 @@ impl Tactic for MoveDownAndSwap { // that is needed here let expected0 = builder.unwrap_expected(0); let expected0_at = builder.unwrap_expected_position(&expected0); - log::trace!("{actual0:?} is not an expected operand, but is occupying index {expected0_at}, where we expect {expected0:?}, evicting.."); + log::trace!( + "{actual0:?} is not an expected operand, but is occupying index {expected0_at}, \ + where we expect {expected0:?}, evicting.." + ); builder.evict(); } diff --git a/codegen/masm/src/codegen/opt/operands/tactics/move_up_and_swap.rs b/codegen/masm/src/codegen/opt/operands/tactics/move_up_and_swap.rs index 9e37dff03..f504f9f36 100644 --- a/codegen/masm/src/codegen/opt/operands/tactics/move_up_and_swap.rs +++ b/codegen/masm/src/codegen/opt/operands/tactics/move_up_and_swap.rs @@ -8,12 +8,10 @@ use super::*; /// /// 0. There must be no copies required and at least one operand expected /// 1. The value expected on top of the stack should be one of: -/// * On top, but the next element expected behind it is off-by-one. -/// In which case we search for the element that goes there, move -/// it up, and swap. -/// * Not on top, but in a cycle with another misplaced operand, such -/// that moving the latter to the top of the stack and swapping it -/// with the expected top operand puts them both into place +/// * On top, but the next element expected behind it is off-by-one. In which case we search for +/// the element that goes there, move it up, and swap. +/// * Not on top, but in a cycle with another misplaced operand, such that moving the latter to +/// the top of the stack and swapping it with the expected top operand puts them both into place /// * Not on top, but once moved to the top, is a valid solution already /// /// If after performing those steps, the stack is in the correct order, @@ -24,7 +22,12 @@ pub struct MoveUpAndSwap; impl Tactic for MoveUpAndSwap { fn apply(&mut self, builder: &mut SolutionBuilder) -> TacticResult { if builder.requires_copies() || builder.arity() < 2 { - log::debug!("cannot apply tactic when there are required copies ({}) or fewer than 2 operands ({})", builder.requires_copies(), builder.arity()); + log::debug!( + "cannot apply tactic when there are required copies ({}) or fewer than 2 operands \ + ({})", + builder.requires_copies(), + builder.arity() + ); return Err(TacticError::PreconditionFailed); } @@ -32,7 +35,10 @@ impl Tactic for MoveUpAndSwap { let actual0 = builder.unwrap_current(0); if actual0 == expected0 { let Some(expected1) = builder.get_expected(1) else { - log::debug!("top two operands on the stack are already in position, returning possible solution"); + log::debug!( + "top two operands on the stack are already in position, returning possible \ + solution" + ); return Ok(()); }; let move_from = builder.unwrap_current_position(&expected1); @@ -40,7 +46,10 @@ impl Tactic for MoveUpAndSwap { log::debug!("abandoning tactic because operand at index 1 is already in position"); return Err(TacticError::NotApplicable); } - log::trace!("moving {expected1:?} to top of stack from index {move_from}, then swapping with {expected0:?}"); + log::trace!( + "moving {expected1:?} to top of stack from index {move_from}, then swapping with \ + {expected0:?}" + ); builder.movup(move_from); builder.swap(1); @@ -60,22 +69,20 @@ impl Tactic for MoveUpAndSwap { // The movup + swap pattern can solve this as follows: // // 1. `a` is expected on top, but is down stack - // 2. If we traverse the stack between the top and `a`, - // until we find a pair of operands where the expected - // positions of the pair are not in descending order, - // we will find that `d` and `c` are such a pair. If no - // such pair is found, we consider this tactic failed. + // 2. If we traverse the stack between the top and `a`, until we find a pair of operands + // where the expected positions of the pair are not in descending order, we will find + // that `d` and `c` are such a pair. If no such pair is found, we consider this tactic + // failed. // - // * NOTE: If the pair includes `a` itself, then we simply - // move `a` directly to the top. + // * NOTE: If the pair includes `a` itself, then we simply move `a` directly to the top. // - // 3. If we move the larger of the two operands to the - // top of the stack, we will obtain the following order: + // 3. If we move the larger of the two operands to the top of the stack, we will obtain the + // following order: // // [d, b, c, a, e] // - // 4. We then identify where `a` is on the stack, and swap - // with the top operand `d`, leaving us with: + // 4. We then identify where `a` is on the stack, and swap with the top operand `d`, leaving + // us with: // // [a, b, c, d, e] let mut descending_pair = None; @@ -139,7 +146,10 @@ impl Tactic for MoveUpAndSwap { // instead defer to MoveDownAndSwap or fallback, both // of which focus on moving elements from the top first, // and handle the various patterns that might arise there. - log::trace!("abandoning tactic because by implication, operands are in order, and an unused operand must need eviction"); + log::trace!( + "abandoning tactic because by implication, operands are in order, and an unused \ + operand must need eviction" + ); Err(TacticError::NotApplicable) } } diff --git a/codegen/masm/src/codegen/opt/operands/tactics/swap_and_move_up.rs b/codegen/masm/src/codegen/opt/operands/tactics/swap_and_move_up.rs index 4239563a0..993d6a05c 100644 --- a/codegen/masm/src/codegen/opt/operands/tactics/swap_and_move_up.rs +++ b/codegen/masm/src/codegen/opt/operands/tactics/swap_and_move_up.rs @@ -7,12 +7,10 @@ use super::*; /// /// 0. There must be no copies required and at least two operands expected /// 1. The value on top of the stack should either be: -/// * Evicted, and the value at the back of the constrained stack -/// is an expected operand, thus swapping them accomplishes both -/// goals at once -/// * Swapped into its expected position, putting an operand at the -/// top which is in position, can be moved into position, or is -/// to be evicted. +/// * Evicted, and the value at the back of the constrained stack is an expected operand, thus +/// swapping them accomplishes both goals at once +/// * Swapped into its expected position, putting an operand at the top which is in position, can +/// be moved into position, or is to be evicted. /// /// If after performing those steps, the stack is in the correct order, /// the tactic was successful, otherwise the operand ordering cannot be @@ -22,7 +20,12 @@ pub struct SwapAndMoveUp; impl Tactic for SwapAndMoveUp { fn apply(&mut self, builder: &mut SolutionBuilder) -> TacticResult { if builder.requires_copies() || builder.arity() < 2 { - log::debug!("cannot apply tactic when there are required copies ({}) or fewer than 2 operands ({})", builder.requires_copies(), builder.arity()); + log::debug!( + "cannot apply tactic when there are required copies ({}) or fewer than 2 operands \ + ({})", + builder.requires_copies(), + builder.arity() + ); return Err(TacticError::PreconditionFailed); } @@ -40,7 +43,11 @@ impl Tactic for SwapAndMoveUp { ); builder.swap(1); } else { - log::trace!("swapping {expected1:?} at index {expected1_pos} to the top of the stack, with {:?}", builder.stack()[0].value); + log::trace!( + "swapping {expected1:?} at index {expected1_pos} to the top of the stack, with \ + {:?}", + builder.stack()[0].value + ); builder.swap(expected1_pos); } @@ -48,7 +55,11 @@ impl Tactic for SwapAndMoveUp { let expected0 = builder.unwrap_expected(0); let expected0_pos = builder.unwrap_current_position(&expected0); if expected0_pos > 0 { - log::trace!("moving {expected0:?} from index {expected0_pos} to the top of stack, shifting {:?} down by one", builder.stack()[0].value); + log::trace!( + "moving {expected0:?} from index {expected0_pos} to the top of stack, shifting \ + {:?} down by one", + builder.stack()[0].value + ); builder.movup(expected0_pos); } diff --git a/codegen/masm/src/codegen/scheduler.rs b/codegen/masm/src/codegen/scheduler.rs index 699a1cccc..7813eb630 100644 --- a/codegen/masm/src/codegen/scheduler.rs +++ b/codegen/masm/src/codegen/scheduler.rs @@ -1,6 +1,4 @@ -use std::cmp::Ordering; -use std::collections::VecDeque; -use std::rc::Rc; +use std::{cmp::Ordering, collections::VecDeque, rc::Rc}; use cranelift_entity::SecondaryMap; use miden_hir::{ @@ -12,11 +10,9 @@ use miden_hir_analysis::{ dependency_graph::{ArgumentNode, DependencyGraph, Node, NodeId}, DominatorTree, LivenessAnalysis, Loop, LoopAnalysis, OrderedTreeGraph, }; - use smallvec::SmallVec; -use crate::codegen::Constraint; -use crate::masm; +use crate::{codegen::Constraint, masm}; /// Information about a block's successor #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -361,17 +357,19 @@ impl<'a> BlockScheduler<'a> { Plan::Finish => { scheduled_ops.push(ScheduleOp::Exit); } - // We're emitting code required to execute an instruction, such as materialization of - // data dependencies used as direct arguments. This is only emitted when an instruction - // has arguments which are derived from the results of an instruction that has not been - // scheduled yet + // We're emitting code required to execute an instruction, such as materialization + // of data dependencies used as direct arguments. This is only + // emitted when an instruction has arguments which are derived from + // the results of an instruction that has not been scheduled yet Plan::PreInst(inst_info) => self.schedule_pre_inst(inst_info), - // We're emitting code for an instruction whose pre-requisite dependencies are already - // materialized, so we need only worry about how a specific instruction is lowered. + // We're emitting code for an instruction whose pre-requisite dependencies are + // already materialized, so we need only worry about how a specific + // instruction is lowered. Plan::Inst(inst_info) => self.schedule_inst(inst_info, scheduled_ops), - // We're emitting code for an instruction that has started executing, and in some specific - // cases, may have dependencies which have been deferred until this point. This is only emitted - // currently for block arguments which are conditionally materialized + // We're emitting code for an instruction that has started executing, and in some + // specific cases, may have dependencies which have been deferred + // until this point. This is only emitted currently for block + // arguments which are conditionally materialized Plan::PostInst(inst_info) => self.schedule_post_inst(inst_info), Plan::Drop(value) => { scheduled_ops.push(ScheduleOp::Drop(value)); @@ -388,7 +386,8 @@ impl<'a> BlockScheduler<'a> { // The treegraph iterator visits each node before any of that node's successors, // i.e. dependencies. As a result, all code that emits planning tasks (i.e. Plan), - // must be written in the order opposite of the resulting scheduling tasks (i.e. ScheduleOp). + // must be written in the order opposite of the resulting scheduling tasks (i.e. + // ScheduleOp). // // This is critical to internalize, because reasoning about dependencies and when things // are live/dead is _inverted_ here. You must ensure that when planning things based on @@ -408,27 +407,32 @@ impl<'a> BlockScheduler<'a> { match node_id.into() { // Result nodes are treegraph roots by two paths: // - // * The result is used multiple times. Here, in the planning phase, we must have just + // * The result is used multiple times. Here, in the planning phase, we must have + // just // finished visiting all of its dependents, so to ensure that during scheduling the - // result is materialized before its first use, we must do so here. If this result is - // one of many produced by the same instruction, we must determine if this is the first - // result to be seen, or the last. The last result to be visited during planning is the - // one that will actually materialize all of the results for the corresponding instruction. - // Thus, if this is not the last result visited. + // result is materialized before its first use, we must do so here. If this result + // is one of many produced by the same instruction, we must + // determine if this is the first result to be seen, or the last. + // The last result to be visited during planning is the + // one that will actually materialize all of the results for the corresponding + // instruction. Thus, if this is not the last result visited. // - // * The result is never used, in which case, like above, we must determine if this result - // should force materialization of the instruction. The caveat here is that if this is the - // only result produced by its instruction, and it is not live after the end of the current - // block, then we will avoid materializing at all if the instruction has no side effects. - // If it _does_ have side effects, then we will force materialization of the result, but - // then schedule it to be immediately dropped + // * The result is never used, in which case, like above, we must determine if this + // result + // should force materialization of the instruction. The caveat here is that if this + // is the only result produced by its instruction, and it is not + // live after the end of the current block, then we will avoid + // materializing at all if the instruction has no side effects. + // If it _does_ have side effects, then we will force materialization of the result, + // but then schedule it to be immediately dropped Node::Result { value, .. } => { self.maybe_force_materialize_inst_results(value, node_id) } - // During the planning phase, there is only one useful thing to do with this node type, - // which is to determine if it has any dependents in the current block, and if not, - // schedule a drop of the value if liveness analysis tells us that the value is not - // used after the current block. + // During the planning phase, there is only one useful thing to do with this node + // type, which is to determine if it has any dependents in the + // current block, and if not, schedule a drop of the value if + // liveness analysis tells us that the value is not used after the + // current block. Node::Stack(value) => { // If this value is live after the end of this block, it cannot be dropped if self @@ -481,11 +485,13 @@ impl<'a> BlockScheduler<'a> { } } - /// Schedule pre-requisites for an instruction based on the given analysis, see [Plan::PreInst] docs for more. + /// Schedule pre-requisites for an instruction based on the given analysis, see [Plan::PreInst] + /// docs for more. /// - /// This function, while nominally occurring during the scheduling phase, actually emits planning - /// tasks which are processed _before_ the [Plan::Inst] task corresponding to `inst_info`. As a - /// result, we visit the pre-requisites in planning order, _not_ execution order. + /// This function, while nominally occurring during the scheduling phase, actually emits + /// planning tasks which are processed _before_ the [Plan::Inst] task corresponding to + /// `inst_info`. As a result, we visit the pre-requisites in planning order, _not_ execution + /// order. fn schedule_pre_inst(&mut self, inst_info: Rc) { // Schedule dependencies for execution in the order that they must execute if inst_info @@ -622,10 +628,7 @@ impl<'a> BlockScheduler<'a> { fn materialize_inst_results(&mut self, inst_info: Rc) { let inst_results = self.f.dfg.inst_results(inst_info.inst); for result in inst_results.iter().copied() { - let is_used = inst_info - .results - .iter() - .any(|v| v.value == result && v.is_used()); + let is_used = inst_info.results.iter().any(|v| v.value == result && v.is_used()); if !is_used { self.worklist.push(Plan::Drop(result)); } @@ -885,9 +888,8 @@ impl<'a> BlockScheduler<'a> { let result_node_id = result_node.id(); // A result is "externally used" if it is live after the current block - let is_externally_used = self - .liveness - .is_live_after(&value, ProgramPoint::Block(self.block_info.source)); + let is_externally_used = + self.liveness.is_live_after(&value, ProgramPoint::Block(self.block_info.source)); let mut info = ValueInfo { value, @@ -899,8 +901,7 @@ impl<'a> BlockScheduler<'a> { // Record all of the instructions in the current block which use this result for pred in self.block_info.depgraph.predecessors(result_node_id) { if pred.dependent.is_argument() { - info.users - .push(self.block_info.depgraph.unwrap_parent(pred.dependent)); + info.users.push(self.block_info.depgraph.unwrap_parent(pred.dependent)); } else { assert!(pred.dependent.is_instruction()); info.users.push(pred.dependent); @@ -934,8 +935,8 @@ impl<'a> BlockScheduler<'a> { /// An operand must be copied unless all of the following are true: /// /// * This is the last use (in the current block) of the value - /// * The value is not live in any successor, with the exception of - /// successors which define the value (as seen along loopback edges) + /// * The value is not live in any successor, with the exception of successors which define the + /// value (as seen along loopback edges) /// /// If both of those properties hold, then the operand can be consumed directly. fn constraint( @@ -957,34 +958,29 @@ impl<'a> BlockScheduler<'a> { transitive_instruction_dependents(arg_sourced_from, &self.block_info); let this_dependent = self.block_info.depgraph.unwrap_parent(arg_node); transitive_dependents.remove(&this_dependent); - let is_last_dependent = transitive_dependents.iter().copied().all(|td| { - self.block_info - .treegraph - .is_scheduled_after(td, this_dependent) - }); + let is_last_dependent = transitive_dependents + .iter() + .copied() + .all(|td| self.block_info.treegraph.is_scheduled_after(td, this_dependent)); let is_live_after = match arg_node.into() { Node::Argument(ArgumentNode::Direct { .. }) => successors .map(|jts| { jts.iter().any(|jt| { let defined_by = self.f.dfg.block_args(jt.destination).contains(&arg); - let is_live_at = self - .liveness - .is_live_at(&arg, ProgramPoint::Block(jt.destination)); + let is_live_at = + self.liveness.is_live_at(&arg, ProgramPoint::Block(jt.destination)); is_live_at && !defined_by }) }) .unwrap_or_else(|| { - self.liveness - .is_live_after(&arg, ProgramPoint::Block(self.block_info.source)) + self.liveness.is_live_after(&arg, ProgramPoint::Block(self.block_info.source)) }), Node::Argument(ArgumentNode::Indirect { successor, .. }) | Node::Argument(ArgumentNode::Conditional { successor, .. }) => { let successors = successors.unwrap(); let successor = successors[successor as usize].destination; let defined_by = self.f.dfg.block_args(successor).contains(&arg); - let is_live_at = self - .liveness - .is_live_at(&arg, ProgramPoint::Block(successor)); + let is_live_at = self.liveness.is_live_at(&arg, ProgramPoint::Block(successor)); is_live_at && !defined_by } _ => unreachable!(), @@ -1068,7 +1064,8 @@ fn build_dependency_graph( } } BranchInfo::MultiDest(ref jts) => { - // Preprocess the arguments which are used so we can determine materialization requirements + // Preprocess the arguments which are used so we can determine materialization + // requirements for jt in jts.iter() { for arg in jt.args.iter().copied() { block_arg_uses @@ -1077,12 +1074,14 @@ fn build_dependency_graph( .insert(jt.destination); } } - // For each successor, check if we should implicitly require an argument along that edge due - // to liveness analysis indicating that it is used somewhere downstream. We only consider - // block arguments passed to at least one other successor, and which are not already explicitly + // For each successor, check if we should implicitly require an argument along that + // edge due to liveness analysis indicating that it is used + // somewhere downstream. We only consider block arguments passed to + // at least one other successor, and which are not already explicitly // provided to this successor. let materialization_threshold = jts.len(); - // Finally, add edges to the dependency graph representing the nature of each argument + // Finally, add edges to the dependency graph representing the nature of each + // argument for (succ_idx, jt) in jts.iter().enumerate() { for (arg_idx, arg) in jt.args.iter().copied().enumerate() { let is_conditionally_materialized = @@ -1112,17 +1111,20 @@ fn build_dependency_graph( } } - // HACK: If there are any instruction nodes with no predecessors, with the exception of the block terminator, - // then we must add a control dependency to the graph to reflect the fact that the instruction - // must have been placed in this block intentionally. However, we are free to schedule the instruction - // as we see fit to avoid de-optimizing the normal instruction schedule unintentionally. + // HACK: If there are any instruction nodes with no predecessors, with the exception of the + // block terminator, then we must add a control dependency to the graph to reflect the fact + // that the instruction must have been placed in this block intentionally. However, we are + // free to schedule the instruction as we see fit to avoid de-optimizing the normal + // instruction schedule unintentionally. // - // We also avoid adding control dependencies for instructions without side effects that are not live - // beyond the current block, as those are dead code and should be eliminated in the DCE step. + // We also avoid adding control dependencies for instructions without side effects that are not + // live beyond the current block, as those are dead code and should be eliminated in the DCE + // step. // // The actual scheduling decision for the instruction is deferred to `analyze_inst`, where we - // treat the instruction similarly to argument materialization, and either make it a pre-requisite - // of the instruction or execute it in the post-execution phase depending on the terminator type + // treat the instruction similarly to argument materialization, and either make it a + // pre-requisite of the instruction or execute it in the post-execution phase depending on + // the terminator type assign_control_dependencies(&mut graph, block_id, function, liveness); // Eliminate dead code as indicated by the state of the dependency graph @@ -1131,23 +1133,24 @@ fn build_dependency_graph( graph } -/// Discover any instructions in the given block that have no predecessors, but that must be scheduled -/// anyway, i.e. due to side effects - and make the block terminator dependent on them to ensure that -/// they are scheduled. +/// Discover any instructions in the given block that have no predecessors, but that must be +/// scheduled anyway, i.e. due to side effects - and make the block terminator dependent on them to +/// ensure that they are scheduled. /// -/// We call these instruction->instruction dependencies "control dependencies", since control flow in the -/// block depends on them being executed first. In a way these dependencies are control-flow sensitive, but -/// because the instruction has no direct predecessors, we assume that we are free to schedule them anywhere -/// in the block. For the time being, we choose to schedule them just prior to leaving the block, but in the -/// future we may wish to do more intelligent scheduling of these items, either to reduce the live ranges of -/// values which are used as instruction operands, or if we find that we must attempt to more faithfully -/// preserve the original program ordering for some reason. +/// We call these instruction->instruction dependencies "control dependencies", since control flow +/// in the block depends on them being executed first. In a way these dependencies are control-flow +/// sensitive, but because the instruction has no direct predecessors, we assume that we are free to +/// schedule them anywhere in the block. For the time being, we choose to schedule them just prior +/// to leaving the block, but in the future we may wish to do more intelligent scheduling of these +/// items, either to reduce the live ranges of values which are used as instruction operands, or if +/// we find that we must attempt to more faithfully preserve the original program ordering for some +/// reason. /// /// NOTE: This function only assigns control dependencies for instructions _with_ side effects. An -/// instruction with no dependents, and no side effects, is treated as dead code, since by definition -/// its effects cannot be visible. It should be noted however that we are quite conservative about -/// determining if an instruction has side effects - e.g., all function calls are assumed to have -/// side effects at this point in time. +/// instruction with no dependents, and no side effects, is treated as dead code, since by +/// definition its effects cannot be visible. It should be noted however that we are quite +/// conservative about determining if an instruction has side effects - e.g., all function calls are +/// assumed to have side effects at this point in time. fn assign_control_dependencies( graph: &mut DependencyGraph, block_id: hir::Block, @@ -1176,7 +1179,8 @@ fn assign_control_dependencies( }; let node_id = node.id(); - // Skip instructions with transitive dependents on at least one result, or a direct dependent + // Skip instructions with transitive dependents on at least one result, or a direct + // dependent let has_dependents = graph.predecessors(node_id).any(|pred| { if pred.dependent.is_result() { graph.num_predecessors(pred.dependent) > 0 @@ -1231,20 +1235,16 @@ fn dce( // side-effects, then remove all of the nodes related to that instruction, continuing // until there are no more nodes to process. let mut worklist = VecDeque::<(hir::Inst, NodeId)>::from_iter( - function - .dfg - .block_insts(block_id) - .enumerate() - .map(|(i, inst)| { - ( - inst, - Node::Inst { - id: inst, - pos: i as u16, - } - .into(), - ) - }), + function.dfg.block_insts(block_id).enumerate().map(|(i, inst)| { + ( + inst, + Node::Inst { + id: inst, + pos: i as u16, + } + .into(), + ) + }), ); let mut remove_nodes = Vec::::default(); while let Some((inst, inst_node)) = worklist.pop_front() { @@ -1308,7 +1308,10 @@ fn dce( args[index] } BranchInfo::MultiDest(ref jts) => jts[successor].args[index], - BranchInfo::NotABranch => unreachable!("indirect/conditional arguments are only valid as successors of a branch instruction"), + BranchInfo::NotABranch => unreachable!( + "indirect/conditional arguments are only valid as successors of a \ + branch instruction" + ), }; match function.dfg.value_data(value) { hir::ValueData::Inst { @@ -1375,20 +1378,16 @@ fn is_dead_instruction( } let pp = ProgramPoint::Block(block_id); - let is_live = results - .iter() - .copied() - .enumerate() - .any(|(result_idx, result)| { - let result_node = Node::Result { - value: result, - index: result_idx as u8, - }; - if graph.num_predecessors(result_node) > 0 { - return true; - } - liveness.is_live_after(&result, pp) - }); + let is_live = results.iter().copied().enumerate().any(|(result_idx, result)| { + let result_node = Node::Result { + value: result, + index: result_idx as u8, + }; + if graph.num_predecessors(result_node) > 0 { + return true; + } + liveness.is_live_after(&result, pp) + }); !is_live && !has_side_effects } diff --git a/codegen/masm/src/codegen/stack.rs b/codegen/masm/src/codegen/stack.rs index a41720c35..69759cbf8 100644 --- a/codegen/masm/src/codegen/stack.rs +++ b/codegen/masm/src/codegen/stack.rs @@ -4,9 +4,8 @@ use core::{ ops::{Index, IndexMut}, }; -use smallvec::{smallvec, SmallVec}; - use miden_hir::{Felt, FieldElement, Immediate, Type, Value}; +use smallvec::{smallvec, SmallVec}; /// This represents a constraint an operand's usage at /// a given program point, namely when used as an instruction @@ -83,7 +82,7 @@ impl PartialEq for ConstantValue { match (self, other) { (Self::Imm(ref a), Self::Imm(ref b)) => a.cmp(b).is_eq(), (Self::Bytes(ref a), Self::Bytes(ref b)) => a == b, - (_, _) => false, + (..) => false, } } } @@ -377,10 +376,7 @@ impl Operand { let ty = operand.ty(); let mut word = ty.to_raw_parts().expect("invalid operand type"); assert!(!word.is_empty(), "invalid operand: must be a sized type"); - assert!( - word.len() <= 4, - "invalid operand: must be smaller than or equal to a word" - ); + assert!(word.len() <= 4, "invalid operand: must be smaller than or equal to a word"); if word.len() > 1 { word.reverse(); } @@ -414,9 +410,10 @@ impl Operand { /// as a new [Operand]. /// /// For operands that fit in a field element, this is equivalent to cloning the operand. - /// For operands that are larger than a field element, the type will be split at the fourth byte, - /// this may destroy the semantics of a higher-level type (i.e. a large integer might become a - /// smaller one, or an array of raw bytes). It is assumed that the caller is doing this intentionally. + /// For operands that are larger than a field element, the type will be split at the fourth + /// byte, this may destroy the semantics of a higher-level type (i.e. a large integer might + /// become a smaller one, or an array of raw bytes). It is assumed that the caller is doing + /// this intentionally. #[allow(unused)] pub fn pop(&mut self) -> Operand { if self.word.len() == 1 { @@ -591,13 +588,7 @@ impl OperandStack { pub fn effective_index_inclusive(&self, index: usize) -> usize { assert!(index < self.stack.len()); - self.stack - .iter() - .rev() - .take(index + 1) - .map(|o| o.size()) - .sum::() - - 1 + self.stack.iter().rev().take(index + 1).map(|o| o.size()).sum::() - 1 } /// Returns the number of operands on the stack @@ -745,10 +736,7 @@ impl OperandStack { /// larger than a field element, it may be split to accommodate the request. #[allow(unused)] pub fn dropw(&mut self) { - assert!( - self.raw_len() >= 4, - "expected at least a word on the operand stack" - ); + assert!(self.raw_len() >= 4, "expected at least a word on the operand stack"); let mut dropped = 0usize; while let Some(mut elem) = self.stack.pop() { let needed = 4 - dropped; @@ -772,12 +760,7 @@ impl OperandStack { #[inline] pub fn dropn(&mut self, n: usize) { let len = self.stack.len(); - assert!( - n <= len, - "unable to drop {} operands, operand stack only has {}", - n, - len - ); + assert!(n <= len, "unable to drop {} operands, operand stack only has {}", n, len); self.stack.truncate(len - n); } @@ -847,7 +830,8 @@ impl OperandStack { // Split the stack so that the desired position is in the top half let mid = len - (n + 1); let (_, r) = self.stack.split_at_mut(mid); - // Move all elements above the `n`th position up by one, moving the top element to the `n`th position + // Move all elements above the `n`th position up by one, moving the top element to the `n`th + // position r.rotate_right(1); } @@ -868,16 +852,11 @@ impl Index for OperandStack { index, len ); - let effective_len: usize = self - .stack - .iter() - .rev() - .take(index + 1) - .map(|o| o.size()) - .sum(); + let effective_len: usize = self.stack.iter().rev().take(index + 1).map(|o| o.size()).sum(); assert!( effective_len <= 16, - "invalid operand stack index ({}): requires access to more than 16 elements, which is not supported in Miden", + "invalid operand stack index ({}): requires access to more than 16 elements, which is \ + not supported in Miden", index ); &self.stack[len - index - 1] @@ -892,16 +871,11 @@ impl IndexMut for OperandStack { index, len ); - let effective_len: usize = self - .stack - .iter() - .rev() - .take(index + 1) - .map(|o| o.size()) - .sum(); + let effective_len: usize = self.stack.iter().rev().take(index + 1).map(|o| o.size()).sum(); assert!( effective_len <= 16, - "invalid operand stack index ({}): requires access to more than 16 elements, which is not supported in Miden", + "invalid operand stack index ({}): requires access to more than 16 elements, which is \ + not supported in Miden", index ); &mut self.stack[len - index - 1] @@ -931,8 +905,9 @@ impl fmt::Debug for OperandStack { #[cfg(test)] mod tests { + use miden_hir::StructType; + use super::*; - use miden_hir::{Immediate, StructType, Type}; #[test] fn operand_stack_homogenous_operand_sizes_test() { @@ -1202,11 +1177,8 @@ mod tests { let one = Immediate::U32(1); let two = Type::U64; let three = Type::U64; - let struct_a = Type::Struct(StructType::new([ - Type::Ptr(Box::new(Type::U8)), - Type::U16, - Type::U32, - ])); + let struct_a = + Type::Struct(StructType::new([Type::Ptr(Box::new(Type::U8)), Type::U16, Type::U32])); // push stack.push(zero); diff --git a/codegen/masm/src/convert.rs b/codegen/masm/src/convert.rs index f76d90283..e319ad11b 100644 --- a/codegen/masm/src/convert.rs +++ b/codegen/masm/src/convert.rs @@ -20,14 +20,14 @@ type ModuleGlobalVariableAnalysis = analysis::GlobalVariableAnalysis Default for ConvertHirToMasm { } } impl PassInfo for ConvertHirToMasm { + const DESCRIPTION: &'static str = "Convert an HIR module or program to Miden Assembly\n\nSee \ + the module documentation for ConvertHirToMasm for more \ + details"; const FLAG: &'static str = "convert-hir-to-masm"; const SUMMARY: &'static str = "Convert an HIR module or program to Miden Assembly"; - const DESCRIPTION: &'static str = "Convert an HIR module or program to Miden Assembly\n\n\ - See the module documentation for ConvertHirToMasm for more details"; } impl ConversionPass for ConvertHirToMasm { @@ -110,7 +111,8 @@ impl ConversionPass for ConvertHirToMasm { // Compute import information for this module masm_module.imports = module.imports(); - // If we don't have a program-wide global variable analysis, compute it using the module global table. + // If we don't have a program-wide global variable analysis, compute it using the module + // global table. if !analyses.is_available::(&ProgramAnalysisKey) { analyses.get_or_compute::(&module, session)?; } diff --git a/codegen/masm/src/emulator/breakpoints.rs b/codegen/masm/src/emulator/breakpoints.rs index bd8f0eb95..404f2eaed 100644 --- a/codegen/masm/src/emulator/breakpoints.rs +++ b/codegen/masm/src/emulator/breakpoints.rs @@ -246,15 +246,14 @@ impl BreakpointManager { } EmulatorEvent::CycleStart(cycle) => { let mut cycle_hit = false; - self.break_at_cycles - .retain(|break_at_cycle| match cycle.cmp(break_at_cycle) { - Ordering::Equal => { - cycle_hit = true; - false - } - Ordering::Greater => false, - Ordering::Less => true, - }); + self.break_at_cycles.retain(|break_at_cycle| match cycle.cmp(break_at_cycle) { + Ordering::Equal => { + cycle_hit = true; + false + } + Ordering::Greater => false, + Ordering::Less => true, + }); if cycle_hit { Some(BreakpointEvent::ReachedCycle(cycle)) } else if self.break_every_cycle { @@ -287,10 +286,9 @@ impl BreakpointManager { } } }, - EmulatorEvent::MemoryWrite { addr, size } => self - .matches_watchpoint(addr, size) - .copied() - .map(BreakpointEvent::Watch), + EmulatorEvent::MemoryWrite { addr, size } => { + self.matches_watchpoint(addr, size).copied().map(BreakpointEvent::Watch) + } EmulatorEvent::Stopped | EmulatorEvent::Suspended => None, EmulatorEvent::Breakpoint(bp) => Some(bp), } @@ -330,28 +328,21 @@ impl BreakpointIter { let mut iter = BreakpointIter { bps: Vec::with_capacity(4), }; - iter.bps.extend( - bpm.break_on_writes - .iter() - .enumerate() - .filter_map(|(i, wp)| { - if wp.mode == WatchMode::Break { - Some(Breakpoint::Watch(WatchpointId(i))) - } else { - None - } - }), - ); - iter.bps - .extend(bpm.break_at_cycles.iter().copied().map(Breakpoint::Cycle)); + iter.bps.extend(bpm.break_on_writes.iter().enumerate().filter_map(|(i, wp)| { + if wp.mode == WatchMode::Break { + Some(Breakpoint::Watch(WatchpointId(i))) + } else { + None + } + })); + iter.bps.extend(bpm.break_at_cycles.iter().copied().map(Breakpoint::Cycle)); for (block, indices) in bpm.break_on_reached.iter() { if indices.is_empty() { continue; } let block = *block; for index in indices.iter().copied() { - iter.bps - .push(Breakpoint::At(InstructionPointer { block, index })) + iter.bps.push(Breakpoint::At(InstructionPointer { block, index })) } } if bpm.break_loops { diff --git a/codegen/masm/src/emulator/functions.rs b/codegen/masm/src/emulator/functions.rs index 6be46bbf0..b4304ec9e 100644 --- a/codegen/masm/src/emulator/functions.rs +++ b/codegen/masm/src/emulator/functions.rs @@ -122,13 +122,11 @@ impl ControlStack { #[inline] pub fn enter_repeat(&mut self, block: BlockId, n: u8) { let ip = InstructionPointer::new(block); - let pending_frame = self - .pending_frame - .replace(ControlFrame::Repeat(RepeatState { - ip, - n, - iterations: 0, - })); + let pending_frame = self.pending_frame.replace(ControlFrame::Repeat(RepeatState { + ip, + n, + iterations: 0, + })); self.pending = None; self.current = ControlFrame::Repeat(RepeatState { ip, @@ -385,7 +383,8 @@ impl fmt::Debug for Activation { } } impl Activation { - /// Create a new activation record for `function`, using `fp` as the frame pointer for this activation + /// Create a new activation record for `function`, using `fp` as the frame pointer for this + /// activation pub fn new(function: Arc, fp: Addr) -> Self { let block = function.body.id(); let control_stack = ControlStack::new(InstructionPointer::new(block)); @@ -441,19 +440,16 @@ impl Activation { self.control_stack.peek() } - /// Peek at the [InstructionWithOp] corresponding to the next instruction to be returned from [move_next] + /// Peek at the [InstructionWithOp] corresponding to the next instruction to be returned from + /// [move_next] pub fn peek_with_op(&self) -> Option { - self.control_stack - .peek() - .and_then(|ix| ix.with_op(&self.function)) + self.control_stack.peek().and_then(|ix| ix.with_op(&self.function)) } /// Peek at the [Op] coresponding to the next instruction to be returned from [move_next] #[allow(unused)] pub fn peek_op(&self) -> Option { - self.control_stack - .peek() - .and_then(|ix| ix.op(&self.function)) + self.control_stack.peek().and_then(|ix| ix.op(&self.function)) } /// Set the instruction pointer to the first instruction of `block` @@ -489,10 +485,8 @@ impl Activation { #[cfg(test)] mod tests { use miden_hir::{assert_matches, Signature}; - use std::sync::Arc; use super::*; - use crate::{Function, Op}; #[test] fn activation_record_start_of_block() { @@ -742,10 +736,8 @@ mod tests { } fn test_function() -> Arc { - let mut function = Function::new( - "test::main".parse().unwrap(), - Signature::new(vec![], vec![]), - ); + let mut function = + Function::new("test::main".parse().unwrap(), Signature::new(vec![], vec![])); let then_blk = function.create_block(); let else_blk = function.create_block(); let while_blk = function.create_block(); diff --git a/codegen/masm/src/emulator/mod.rs b/codegen/masm/src/emulator/mod.rs index 4b16ffffc..48c3d824b 100644 --- a/codegen/masm/src/emulator/mod.rs +++ b/codegen/masm/src/emulator/mod.rs @@ -3,12 +3,6 @@ mod debug; mod events; mod functions; -pub use self::breakpoints::*; -pub use self::debug::{CallFrame, DebugInfo, DebugInfoWithStack}; -pub use self::events::{BreakpointEvent, ControlEffect, EmulatorEvent}; -use self::functions::{Activation, Stub}; -pub use self::functions::{Instruction, InstructionWithOp, NativeFn}; - use std::{cell::RefCell, cmp, rc::Rc, sync::Arc}; use miden_hir::{ @@ -16,6 +10,13 @@ use miden_hir::{ }; use rustc_hash::{FxHashMap, FxHashSet}; +use self::functions::{Activation, Stub}; +pub use self::{ + breakpoints::*, + debug::{CallFrame, DebugInfo, DebugInfoWithStack}, + events::{BreakpointEvent, ControlEffect, EmulatorEvent}, + functions::{Instruction, InstructionWithOp, NativeFn}, +}; use crate::{Begin, BlockId, Function, Module, Op, Program}; /// This type represents the various sorts of errors which can occur when @@ -132,26 +133,21 @@ pub struct Emulator { } impl Default for Emulator { fn default() -> Self { - Self::new( - Self::DEFAULT_HEAP_SIZE, - Self::DEFAULT_HEAP_START, - Self::DEFAULT_LOCALS_START, - ) + Self::new(Self::DEFAULT_HEAP_SIZE, Self::DEFAULT_HEAP_START, Self::DEFAULT_LOCALS_START) } } impl Emulator { - const PAGE_SIZE: u32 = 64 * 1024; pub const DEFAULT_HEAP_SIZE: u32 = (4 * Self::PAGE_SIZE) / 16; pub const DEFAULT_HEAP_START: u32 = (2 * Self::PAGE_SIZE) / 16; pub const DEFAULT_LOCALS_START: u32 = (3 * Self::PAGE_SIZE) / 16; const EMPTY_WORD: [Felt; 4] = [Felt::ZERO; 4]; + const PAGE_SIZE: u32 = 64 * 1024; /// Construct a new, empty emulator with: /// /// * A linear memory heap of `memory_size` words /// * The start of the usable heap set to `hp` (an address in words) /// * The start of the reserved heap used for locals set to `lp` (an address in words) - /// pub fn new(memory_size: u32, hp: u32, lp: u32) -> Self { let memory = vec![Self::EMPTY_WORD; memory_size as usize]; Self { @@ -260,16 +256,12 @@ impl Emulator { /// Get the instruction pointer that will be next executed by the emulator pub fn current_ip(&self) -> Option { - self.callstack - .last() - .and_then(|activation| activation.peek()) + self.callstack.last().and_then(|activation| activation.peek()) } /// Get the name of the function that is currently executing pub fn current_function(&self) -> Option { - self.callstack - .last() - .map(|activation| activation.function().name) + self.callstack.last().map(|activation| activation.function().name) } /// Get access to the current state of the operand stack @@ -315,7 +307,12 @@ impl Emulator { pub fn load_module(&mut self, module: Arc) -> Result<(), EmulationError> { use std::collections::hash_map::Entry; - assert_matches!(self.status, Status::Init | Status::Loaded, "cannot load modules once execution has started without calling stop() or reset() first"); + assert_matches!( + self.status, + Status::Init | Status::Loaded, + "cannot load modules once execution has started without calling stop() or reset() \ + first" + ); match self.modules_loaded.entry(module.name) { Entry::Occupied(_) => return Err(EmulationError::AlreadyLoaded(module.name)), @@ -359,7 +356,12 @@ impl Emulator { /// /// This function will panic if the named module is not currently loaded. pub fn unload_module(&mut self, name: Ident) { - assert_matches!(self.status, Status::Loaded, "cannot unload modules once execution has started without calling stop() or reset() first"); + assert_matches!( + self.status, + Status::Loaded, + "cannot unload modules once execution has started without calling stop() or reset() \ + first" + ); let prev = self .modules_loaded @@ -372,7 +374,8 @@ impl Emulator { self.locals.remove(&f.name); } - // Determine if we need to add `name` to `modules_pending` if there are dependents still loaded + // Determine if we need to add `name` to `modules_pending` if there are dependents still + // loaded for module in self.modules_loaded.values() { if module.imports.is_import(&name) { self.modules_pending.insert(name); @@ -431,8 +434,7 @@ impl Emulator { if self.functions.contains_key(&id) { return Err(EmulationError::DuplicateFunction(id)); } - self.functions - .insert(id, Stub::Native(Rc::new(RefCell::new(function)))); + self.functions.insert(id, Stub::Native(Rc::new(RefCell::new(function)))); Ok(()) } @@ -460,10 +462,7 @@ impl Emulator { }; if elem_idx == 4 { elem_idx = 0; - assert!( - self.hp + 1 < self.lp, - "heap has overflowed into reserved region" - ); + assert!(self.hp + 1 < self.lp, "heap has overflowed into reserved region"); self.hp += 1; } self.memory[self.hp as usize][elem_idx] = Felt::new(elem as u64); @@ -486,10 +485,7 @@ impl Emulator { let size = size as u32; let extra = size % 16; let words = (size / 16) + (extra > 0) as u32; - assert!( - self.hp + words < self.lp, - "heap has overflowed into reserved region" - ); + assert!(self.hp + words < self.lp, "heap has overflowed into reserved region"); self.hp += words; addr * 16 @@ -507,9 +503,11 @@ impl Emulator { self.memory[addr][ptr.index as usize] = value; } - /// Start executing the current program by `invoke`ing the top-level initialization block (the entrypoint). + /// Start executing the current program by `invoke`ing the top-level initialization block (the + /// entrypoint). /// - /// This function will run the program to completion, and return the state of the operand stack on exit. + /// This function will run the program to completion, and return the state of the operand stack + /// on exit. /// /// NOTE: If no entrypoint has been loaded, an error is returned. /// @@ -521,7 +519,10 @@ impl Emulator { Status::Stopped => { self.stop(); } - Status::Started | Status::Suspended => panic!("cannot start the emulator when it is already started without calling stop() or reset() first"), + Status::Started | Status::Suspended => panic!( + "cannot start the emulator when it is already started without calling stop() or \ + reset() first" + ), Status::Faulted(ref err) => return Err(err.clone()), } @@ -558,7 +559,10 @@ impl Emulator { Status::Stopped => { self.stop(); } - Status::Started | Status::Suspended => panic!("cannot start the emulator when it is already started without calling stop() or reset() first"), + Status::Started | Status::Suspended => panic!( + "cannot start the emulator when it is already started without calling stop() or \ + reset() first" + ), Status::Faulted(ref err) => return Err(err.clone()), } @@ -622,7 +626,12 @@ impl Emulator { callee: FunctionIdent, args: &[Felt], ) -> Result, EmulationError> { - assert_matches!(self.status, Status::Loaded, "cannot start executing a function when the emulator is already started without calling stop() or reset() first"); + assert_matches!( + self.status, + Status::Loaded, + "cannot start executing a function when the emulator is already started without \ + calling stop() or reset() first" + ); let fun = self .functions .get(&callee) @@ -700,7 +709,12 @@ impl Emulator { callee: FunctionIdent, args: &[Felt], ) -> Result { - assert_matches!(self.status, Status::Loaded, "cannot start executing a function when the emulator is already started without calling stop() or reset() first"); + assert_matches!( + self.status, + Status::Loaded, + "cannot start executing a function when the emulator is already started without \ + calling stop() or reset() first" + ); let fun = self .functions @@ -719,7 +733,8 @@ impl Emulator { } /// Stage a MASM IR function for execution by the emulator, placing the given arguments on the - /// operand stack in FIFO order, then immediately suspending execution until the next resumption. + /// operand stack in FIFO order, then immediately suspending execution until the next + /// resumption. #[inline] fn enter_function( &mut self, @@ -1292,10 +1307,8 @@ impl Emulator { Op::LocStorew(id) => { let addr = (state.fp() + id.as_usize() as u32) as usize; assert!(addr < self.memory.len() - 4, "out of bounds memory access"); - let word = self - .stack - .peekw() - .expect("operand stack does not contain a full word"); + let word = + self.stack.peekw().expect("operand stack does not contain a full word"); self.memory[addr] = word; return Ok(EmulatorEvent::MemoryWrite { addr: addr as u32, @@ -1382,10 +1395,8 @@ impl Emulator { } Op::MemStorew => { let addr = pop_addr!(self); - let word = self - .stack - .peekw() - .expect("operand stack does not contain a full word"); + let word = + self.stack.peekw().expect("operand stack does not contain a full word"); self.memory[addr] = word; self.callstack.push(state); return Ok(EmulatorEvent::MemoryWrite { @@ -1396,10 +1407,8 @@ impl Emulator { Op::MemStorewImm(addr) => { let addr = addr as usize; assert!(addr < self.memory.len() - 4, "out of bounds memory access"); - let word = self - .stack - .peekw() - .expect("operand stack does not contain a full word"); + let word = + self.stack.peekw().expect("operand stack does not contain a full word"); self.memory[addr] = word; self.callstack.push(state); return Ok(EmulatorEvent::MemoryWrite { diff --git a/codegen/masm/src/lib.rs b/codegen/masm/src/lib.rs index 934a2485f..786e191f3 100644 --- a/codegen/masm/src/lib.rs +++ b/codegen/masm/src/lib.rs @@ -8,16 +8,18 @@ mod masm; #[cfg(test)] mod tests; -pub use self::convert::ConvertHirToMasm; -pub use self::emulator::{ - Breakpoint, BreakpointEvent, CallFrame, DebugInfo, DebugInfoWithStack, EmulationError, - Emulator, EmulatorEvent, InstructionPointer, WatchMode, Watchpoint, WatchpointId, -}; -pub use self::masm::*; - use miden_hir as hir; use midenc_session::Session; +pub use self::{ + convert::ConvertHirToMasm, + emulator::{ + Breakpoint, BreakpointEvent, CallFrame, DebugInfo, DebugInfoWithStack, EmulationError, + Emulator, EmulatorEvent, InstructionPointer, WatchMode, Watchpoint, WatchpointId, + }, + masm::*, +}; + /// This error type represents all of the errors produced by [MasmCompiler] #[derive(Debug, thiserror::Error)] pub enum CompilerError { @@ -67,9 +69,7 @@ impl<'a> MasmCompiler<'a> { use miden_hir_transform as transforms; let mut rewrites = RewriteSet::default(); - rewrites.push(ModuleRewritePassAdapter::new( - transforms::SplitCriticalEdges, - )); + rewrites.push(ModuleRewritePassAdapter::new(transforms::SplitCriticalEdges)); rewrites.push(ModuleRewritePassAdapter::new(transforms::Treeify)); rewrites.push(ModuleRewritePassAdapter::new(transforms::InlineBlocks)); @@ -101,9 +101,8 @@ impl<'a> MasmCompiler<'a> { /// rewrites have been applied. If one of these invariants is not upheld, compilation /// may fail. pub fn compile_module(&mut self, input: Box) -> CompilerResult> { - let program = hir::ProgramBuilder::new(&self.session.diagnostics) - .with_module(input)? - .link()?; + let program = + hir::ProgramBuilder::new(&self.session.diagnostics).with_module(input)?.link()?; self.compile(program) } diff --git a/codegen/masm/src/masm/function.rs b/codegen/masm/src/masm/function.rs index fe51fee5d..01f87d188 100644 --- a/codegen/masm/src/masm/function.rs +++ b/codegen/masm/src/masm/function.rs @@ -1,5 +1,4 @@ -use std::fmt; -use std::sync::Arc; +use std::{fmt, sync::Arc}; use cranelift_entity::EntityRef; use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListAtomicLink}; @@ -85,10 +84,7 @@ impl Function { /// Get the local with the given identifier pub fn local(&self, id: LocalId) -> &Local { - self.locals - .iter() - .find(|l| l.id == id) - .expect("invalid local id") + self.locals.iter().find(|l| l.id == id).expect("invalid local id") } /// Return the locals allocated in this function as a slice @@ -173,9 +169,7 @@ impl Function { SourceLocation::new(loc.line.to_usize() as u32, loc.column.to_usize() as u32) }) .unwrap_or_default(); - let body = self - .body - .to_code_body(codemap, imports, local_ids, proc_ids); + let body = self.body.to_code_body(codemap, imports, local_ids, proc_ids); ProcedureAst { name, @@ -222,13 +216,7 @@ impl<'a> fmt::Display for DisplayMasmFunction<'a> { } } - writeln!( - f, - "{}", - self.function - .body - .display(Some(self.function.name), self.imports, 1) - )?; + writeln!(f, "{}", self.function.body.display(Some(self.function.name), self.imports, 1))?; f.write_str("end") } diff --git a/codegen/masm/src/masm/intrinsics.rs b/codegen/masm/src/masm/intrinsics.rs index 0d617d7c3..1cded637b 100644 --- a/codegen/masm/src/masm/intrinsics.rs +++ b/codegen/masm/src/masm/intrinsics.rs @@ -18,7 +18,7 @@ const INTRINSICS: [(&str, &str, &str); 2] = [ /// Expects the fully-qualified name to be given, e.g. `intrinsics::mem` pub fn load>(name: N, codemap: &CodeMap) -> Option { let name = name.as_ref(); - let (name, source, filename) = INTRINSICS.iter().copied().find(|(n, _, _)| *n == name)?; + let (name, source, filename) = INTRINSICS.iter().copied().find(|(n, ..)| *n == name)?; let id = codemap.add(FileName::Virtual(filename.into()), source.to_string()); let source_file = codemap.get(id).unwrap(); match Module::parse_source_file(source_file, name, codemap) { diff --git a/codegen/masm/src/masm/mod.rs b/codegen/masm/src/masm/mod.rs index 7efa6c21a..2dd9ea6c0 100644 --- a/codegen/masm/src/masm/mod.rs +++ b/codegen/masm/src/masm/mod.rs @@ -4,22 +4,26 @@ mod module; mod program; mod region; -pub use self::function::{FrozenFunctionList, Function, FunctionList}; -pub use self::module::{FrozenModuleTree, LoadModuleError, Module, ModuleTree}; -pub use self::program::Program; -pub use self::region::{Begin, Region}; pub use miden_hir::{ Local, LocalId, MasmBlock as Block, MasmBlockId as BlockId, MasmImport as Import, MasmOp as Op, ModuleImportInfo, }; +pub use self::{ + function::{FrozenFunctionList, Function, FunctionList}, + module::{FrozenModuleTree, LoadModuleError, Module, ModuleTree}, + program::Program, + region::{Begin, Region}, +}; + /// This represents a descriptor for a pointer translated from the IR into a form suitable for /// referencing data in Miden's linear memory. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct NativePtr { /// This is the address of the word containing the first byte of data pub waddr: u32, - /// This is the element index of the word referenced by `waddr` containing the first byte of data + /// This is the element index of the word referenced by `waddr` containing the first byte of + /// data /// /// Each element is assumed to be a 32-bit value/chunk pub index: u8, diff --git a/codegen/masm/src/masm/module.rs b/codegen/masm/src/masm/module.rs index 6fb786a39..174e82c32 100644 --- a/codegen/masm/src/masm/module.rs +++ b/codegen/masm/src/masm/module.rs @@ -96,13 +96,7 @@ impl Module { ) -> Result { let id = codemap.add_file(path)?; let source_file = codemap.get(id).unwrap(); - let basename = source_file - .name() - .as_ref() - .file_name() - .unwrap() - .to_str() - .unwrap(); + let basename = source_file.name().as_ref().file_name().unwrap().to_str().unwrap(); let name = match root_ns { None => basename.to_string(), Some(root_ns) => format!("{}::{basename}", root_ns.as_str()), @@ -220,18 +214,15 @@ impl Module { fn num_imported_functions(&self) -> usize { self.imports .iter() - .map(|i| { - self.imports - .imported(&i.alias) - .map(|imported| imported.len()) - .unwrap_or(0) - }) + .map(|i| self.imports.imported(&i.alias).map(|imported| imported.len()).unwrap_or(0)) .sum() } - /// Write this module to a new file under `dir`, assuming `dir` is the root directory for a program. + /// Write this module to a new file under `dir`, assuming `dir` is the root directory for a + /// program. /// - /// For example, if this module is named `std::math::u64`, then it will be written to `/std/math/u64.masm` + /// For example, if this module is named `std::math::u64`, then it will be written to + /// `/std/math/u64.masm` pub fn write_to_directory>( &self, codemap: &miden_diagnostics::CodeMap, @@ -289,9 +280,11 @@ impl midenc_session::Emit for Module { fn name(&self) -> Option { Some(self.name.as_symbol()) } + fn output_type(&self) -> midenc_session::OutputType { midenc_session::OutputType::Masm } + fn write_to(&self, mut writer: W) -> std::io::Result<()> { writer.write_fmt(format_args!("{}", self)) } diff --git a/codegen/masm/src/masm/program.rs b/codegen/masm/src/masm/program.rs index 15f6be516..4c3c4dbe2 100644 --- a/codegen/masm/src/masm/program.rs +++ b/codegen/masm/src/masm/program.rs @@ -184,7 +184,7 @@ impl Program { let library = MaslLibrary::read_from_dir( path, root_ns, - /*with_source_locations=*/ true, + /* with_source_locations= */ true, Default::default(), ) .map_err(|err| LibraryError::file_error(path.to_str().unwrap(), &format!("{err}")))?; @@ -197,9 +197,10 @@ impl Program { root_ns: S, codemap: &miden_diagnostics::CodeMap, ) -> Result { - use miden_assembly::{LibraryNamespace, MaslLibrary, Version}; use std::collections::BTreeSet; + use miden_assembly::{LibraryNamespace, MaslLibrary, Version}; + let ns = LibraryNamespace::new(root_ns)?; let version = Version::default(); let has_source_locations = false; @@ -255,14 +256,10 @@ impl From<&hir::Program> for Program { let mut begin = Begin::default(); begin.imports.add(entry); let entry_module = begin.imports.alias(&entry.module); - begin - .body - .block_mut(begin.body.body) - .ops - .push(Op::Exec(FunctionIdent { - module: entry_module.unwrap_or(entry.module), - function: entry.function, - })); + begin.body.block_mut(begin.body.body).ops.push(Op::Exec(FunctionIdent { + module: entry_module.unwrap_or(entry.module), + function: entry.function, + })); Some(begin) } else { None diff --git a/codegen/masm/src/masm/region.rs b/codegen/masm/src/masm/region.rs index 78a3b9de5..b69a1b140 100644 --- a/codegen/masm/src/masm/region.rs +++ b/codegen/masm/src/masm/region.rs @@ -6,9 +6,8 @@ use miden_hir::FunctionIdent; use rustc_hash::FxHashMap; use smallvec::smallvec; -use crate::InstructionPointer; - use super::*; +use crate::InstructionPointer; /// This struct represents the top-level initialization code for a [Program] #[derive(Default)] @@ -87,7 +86,8 @@ impl Region { id } - /// Render the code in this region as Miden Assembly, at the specified indentation level (in units of 4 spaces) + /// Render the code in this region as Miden Assembly, at the specified indentation level (in + /// units of 4 spaces) pub fn display<'a, 'b: 'a>( &'b self, function: Option, @@ -111,14 +111,7 @@ impl Region { local_ids: &FxHashMap, proc_ids: &FxHashMap, ) -> ast::CodeBody { - emit_block( - self.body, - &self.blocks, - codemap, - imports, - local_ids, - proc_ids, - ) + emit_block(self.body, &self.blocks, codemap, imports, local_ids, proc_ids) } /// Create a [Region] from a [miden_assembly::ast::CodeBody] and the set of imports @@ -194,17 +187,14 @@ fn import_code_body( let else_blk = region.create_block(); import_code_body(region, then_blk, true_case, locals, imported); import_code_body(region, else_blk, false_case, locals, imported); - region - .block_mut(current_block_id) - .push(Op::If(then_blk, else_blk)); + region.block_mut(current_block_id).push(Op::If(then_blk, else_blk)); } ast::Node::Repeat { times, ref body } => { let body_blk = region.create_block(); import_code_body(region, body_blk, body, locals, imported); - region.block_mut(current_block_id).push(Op::Repeat( - (*times).try_into().expect("too many repetitions"), - body_blk, - )); + region + .block_mut(current_block_id) + .push(Op::Repeat((*times).try_into().expect("too many repetitions"), body_blk)); } ast::Node::While { ref body } => { let body_blk = region.create_block(); diff --git a/codegen/masm/src/tests.rs b/codegen/masm/src/tests.rs index 0dff5cdfa..fea13232e 100644 --- a/codegen/masm/src/tests.rs +++ b/codegen/masm/src/tests.rs @@ -1,12 +1,11 @@ +use std::sync::Arc; + use miden_hir::{ - self, pass::{AnalysisManager, ConversionPass}, testing::{self, TestContext}, AbiParam, Felt, FieldElement, FunctionIdent, Immediate, InstBuilder, OperandStack, ProgramBuilder, Signature, SourceSpan, Stack, StarkField, Type, }; -use std::sync::Arc; - use proptest::prelude::*; use super::*; @@ -135,9 +134,7 @@ impl TestByEmulationHarness { args: &[Felt], ) -> Result, EmulationError> { let entrypoint = module.entrypoint().expect("cannot execute a library"); - self.emulator - .load_module(module) - .expect("failed to load module"); + self.emulator.load_module(module).expect("failed to load module"); self.emulator.invoke(entrypoint, args) } @@ -146,9 +143,7 @@ impl TestByEmulationHarness { program: Arc, args: &[Felt], ) -> Result, EmulationError> { - self.emulator - .load_program(program) - .expect("failed to load program"); + self.emulator.load_program(program).expect("failed to load program"); { let stack = self.emulator.stack_mut(); for arg in args.iter().copied().rev() { @@ -165,9 +160,7 @@ impl TestByEmulationHarness { entrypoint: FunctionIdent, args: &[Felt], ) -> Result, EmulationError> { - self.emulator - .load_program(program) - .expect("failed to load program"); + self.emulator.load_program(program).expect("failed to load program"); self.emulator.invoke(entrypoint, args) } @@ -219,10 +212,7 @@ fn issue56() { testing::issue56(mb.as_mut(), &harness.context); mb.build().unwrap(); - let program = builder - .with_entrypoint("test::entrypoint".parse().unwrap()) - .link() - .unwrap(); + let program = builder.with_entrypoint("test::entrypoint".parse().unwrap()).link().unwrap(); let mut compiler = MasmCompiler::new(&harness.context.session); compiler.compile(program).expect("compilation failed"); @@ -239,8 +229,7 @@ fn fib_emulator() { // Build test module with fib function let mut mb = builder.module("test"); testing::fib1(mb.as_mut(), &harness.context); - mb.build() - .expect("unexpected error constructing test module"); + mb.build().expect("unexpected error constructing test module"); // Link the program let program = builder @@ -255,9 +244,7 @@ fn fib_emulator() { // Test it via the emulator let n = Felt::new(10); - let mut stack = harness - .execute_program(program.freeze(), &[n]) - .expect("execution failed"); + let mut stack = harness.execute_program(program.freeze(), &[n]).expect("execution failed"); assert_eq!(stack.len(), 1); assert_eq!(stack.pop().map(|e| e.as_int()), Some(55)); } @@ -291,14 +278,7 @@ fn codegen_fundamental_if() { let is_odd_blk = fb.create_block(); let is_even_blk = fb.create_block(); let is_odd = fb.ins().is_odd(a, SourceSpan::UNKNOWN); - fb.ins().cond_br( - is_odd, - is_odd_blk, - &[], - is_even_blk, - &[], - SourceSpan::UNKNOWN, - ); + fb.ins().cond_br(is_odd, is_odd_blk, &[], is_even_blk, &[], SourceSpan::UNKNOWN); fb.switch_to_block(is_odd_blk); let c = fb.ins().add_checked(a, b, SourceSpan::UNKNOWN); fb.ins().ret(Some(c), SourceSpan::UNKNOWN); @@ -308,14 +288,10 @@ fn codegen_fundamental_if() { fb.build().expect("unexpected error building function") }; - mb.build() - .expect("unexpected error constructing test module"); + mb.build().expect("unexpected error constructing test module"); // Link the program - let program = builder - .with_entrypoint(id) - .link() - .expect("failed to link program"); + let program = builder.with_entrypoint(id).link().expect("failed to link program"); let mut compiler = MasmCompiler::new(&harness.context.session); let program = compiler.compile(program).expect("compilation failed"); @@ -323,9 +299,7 @@ fn codegen_fundamental_if() { let a = Felt::new(3); let b = Felt::new(4); - let mut stack = harness - .execute_program(program.freeze(), &[a, b]) - .expect("execution failed"); + let mut stack = harness.execute_program(program.freeze(), &[a, b]).expect("execution failed"); assert_eq!(stack.len(), 1); assert_eq!(stack.pop().map(|e| e.as_int()), Some(12)); } @@ -366,20 +340,12 @@ fn codegen_fundamental_loops() { fb.switch_to_block(loop_header_blk); let is_zero = fb.ins().eq_imm(n1, Immediate::U32(0), SourceSpan::UNKNOWN); - fb.ins().cond_br( - is_zero, - loop_exit_blk, - &[a1], - loop_body_blk, - &[], - SourceSpan::UNKNOWN, - ); + fb.ins() + .cond_br(is_zero, loop_exit_blk, &[a1], loop_body_blk, &[], SourceSpan::UNKNOWN); fb.switch_to_block(loop_body_blk); let a2 = fb.ins().incr_checked(a1, SourceSpan::UNKNOWN); - let n2 = fb - .ins() - .sub_imm_checked(n1, Immediate::U32(1), SourceSpan::UNKNOWN); + let n2 = fb.ins().sub_imm_checked(n1, Immediate::U32(1), SourceSpan::UNKNOWN); fb.ins().br(loop_header_blk, &[a2, n2], SourceSpan::UNKNOWN); fb.switch_to_block(loop_exit_blk); @@ -388,14 +354,10 @@ fn codegen_fundamental_loops() { fb.build().expect("unexpected error building function") }; - mb.build() - .expect("unexpected error constructing test module"); + mb.build().expect("unexpected error constructing test module"); // Link the program - let program = builder - .with_entrypoint(id) - .link() - .expect("failed to link program"); + let program = builder.with_entrypoint(id).link().expect("failed to link program"); let mut compiler = MasmCompiler::new(&harness.context.session); let program = compiler.compile(program).expect("compilation failed"); @@ -403,9 +365,7 @@ fn codegen_fundamental_loops() { let a = Felt::new(3); let n = Felt::new(4); - let mut stack = harness - .execute_program(program.freeze(), &[a, n]) - .expect("execution failed"); + let mut stack = harness.execute_program(program.freeze(), &[a, n]).expect("execution failed"); assert_eq!(stack.len(), 1); assert_eq!(stack.pop().map(|e| e.as_int()), Some(7)); } @@ -421,8 +381,7 @@ fn codegen_sum_matrix() { // Build test module with fib function let mut mb = builder.module("test"); testing::sum_matrix(mb.as_mut(), &harness.context); - mb.build() - .expect("unexpected error constructing test module"); + mb.build().expect("unexpected error constructing test module"); // Link the program let program = builder diff --git a/frontend-wasm/src/component/build_ir.rs b/frontend-wasm/src/component/build_ir.rs index aca4f84d4..33732d8b6 100644 --- a/frontend-wasm/src/component/build_ir.rs +++ b/frontend-wasm/src/component/build_ir.rs @@ -6,13 +6,6 @@ use miden_hir::{ use miden_hir_type::LiftedFunctionType; use wasmparser::WasmFeatures; -use crate::{ - component::{ComponentParser, StringEncoding}, - error::WasmResult, - module::{build_ir::build_ir_module, module_env::ParsedModule, types::EntityIndex}, - WasmError, WasmTranslationConfig, -}; - use super::{ inline, instance::{ComponentImport, ComponentInstance, ComponentInstanceBuilder}, @@ -20,6 +13,12 @@ use super::{ ExportItem, LinearComponent, LinearComponentTranslation, ParsedRootComponent, StaticModuleIndex, TypeFuncIndex, }; +use crate::{ + component::{ComponentParser, StringEncoding}, + error::WasmResult, + module::{build_ir::build_ir_module, module_env::ParsedModule, types::EntityIndex}, + WasmError, WasmTranslationConfig, +}; /// Translate a Wasm component binary into Miden IR component pub fn translate_component( @@ -114,8 +113,7 @@ fn build_ir<'data>( config, diagnostics, )?; - cb.add_module(module.into()) - .expect("module is already added"); + cb.add_module(module.into()).expect("module is already added"); } Ok(cb.build()) @@ -283,18 +281,12 @@ fn assert_empty_canonical_options(options: &CanonicalOptions) { StringEncoding::Utf8, "UTF-8 is expected in CanonicalOptions, string transcoding is not yet supported" ); - assert!( - options.realloc.is_none(), - "realloc in CanonicalOptions is not yet supported" - ); + assert!(options.realloc.is_none(), "realloc in CanonicalOptions is not yet supported"); assert!( options.post_return.is_none(), "post_return in CanonicalOptions is not yet supported" ); - assert!( - options.memory.is_none(), - "memory in CanonicalOptions is not yet supported" - ); + assert!(options.memory.is_none(), "memory in CanonicalOptions is not yet supported"); } #[cfg(test)] @@ -303,14 +295,12 @@ mod tests { use miden_core::crypto::hash::RpoDigest; use miden_hir_type::Type; + use super::*; use crate::{ - component::StaticModuleIndex, config::{ExportMetadata, ImportMetadata}, test_utils::test_diagnostics, }; - use super::*; - #[test] fn translate_simple() { let wat = format!( @@ -522,10 +512,7 @@ mod tests { assert_eq!(function_id.module, module.name); // assert_eq!(function_id.function, interface_function_ident.function); let component_import = ir.imports().get(&function_id).unwrap(); - assert_eq!( - component_import.interface_function, - interface_function_ident - ); + assert_eq!(component_import.interface_function, interface_function_ident); assert!(!component_import.function_ty.params.is_empty()); let expected_import_func_ty = LiftedFunctionType { params: vec![Type::U32, Type::U32], diff --git a/hir-analysis/src/control_flow.rs b/hir-analysis/src/control_flow.rs index 5c34296b9..ab0f68f68 100644 --- a/hir-analysis/src/control_flow.rs +++ b/hir-analysis/src/control_flow.rs @@ -1,8 +1,9 @@ use cranelift_bforest as bforest; use cranelift_entity::SecondaryMap; - -use miden_hir::pass::{Analysis, AnalysisManager, AnalysisResult}; -use miden_hir::{Block, DataFlowGraph, Function, Inst, Instruction}; +use miden_hir::{ + pass::{Analysis, AnalysisManager, AnalysisResult}, + Block, DataFlowGraph, Function, Inst, Instruction, +}; use midenc_session::Session; /// Represents the predecessor of the current basic block. @@ -21,7 +22,8 @@ impl BlockPredecessor { } } -/// A node in the control flow graph, which contains the successors and predecessors of a given `Block`. +/// A node in the control flow graph, which contains the successors and predecessors of a given +/// `Block`. #[derive(Clone, Default)] struct Node { /// Instructions which transfer control to this block @@ -139,10 +141,7 @@ impl ControlFlowGraph { /// Return the number of predecessors for `block` pub fn num_predecessors(&self, block: Block) -> usize { - self.data[block] - .predecessors - .iter(&self.pred_forest) - .count() + self.data[block].predecessors.iter(&self.pred_forest).count() } /// Return the number of successors for `block` @@ -189,12 +188,8 @@ impl ControlFlowGraph { } fn add_edge(&mut self, from: Block, from_inst: Inst, to: Block) { - self.data[from] - .successors - .insert(to, &mut self.succ_forest, &()); - self.data[to] - .predecessors - .insert(from_inst, from, &mut self.pred_forest, &()); + self.data[from].successors.insert(to, &mut self.succ_forest, &()); + self.data[to].predecessors.insert(from_inst, from, &mut self.pred_forest, &()); } } @@ -264,7 +259,7 @@ mod tests { use std::sync::Arc; use miden_diagnostics::{ - term::termcolor::ColorChoice, CodeMap, DefaultEmitter, DiagnosticsHandler, SourceSpan, + term::termcolor::ColorChoice, CodeMap, DefaultEmitter, DiagnosticsHandler, }; use miden_hir::*; @@ -313,9 +308,8 @@ mod tests { cc: CallConv::SystemV, linkage: Linkage::External, }; - let mut fb = builder - .function("branches_and_jumps", sig) - .expect("unexpected symbol conflict"); + let mut fb = + builder.function("branches_and_jumps", sig).expect("unexpected symbol conflict"); let block0 = fb.entry_block(); let cond = { @@ -328,12 +322,10 @@ mod tests { let cond = fb.ins().trunc(cond, Type::I1, SourceSpan::default()); let br_block0_block2_block1 = - fb.ins() - .cond_br(cond, block2, &[], block1, &[], SourceSpan::default()); + fb.ins().cond_br(cond, block2, &[], block1, &[], SourceSpan::default()); fb.switch_to_block(block1); let br_block1_block1_block2 = - fb.ins() - .cond_br(cond, block1, &[], block2, &[], SourceSpan::default()); + fb.ins().cond_br(cond, block1, &[], block2, &[], SourceSpan::default()); let id = fb .build(&diagnostics) diff --git a/hir-analysis/src/data.rs b/hir-analysis/src/data.rs index f728aff16..d1dd65f75 100644 --- a/hir-analysis/src/data.rs +++ b/hir-analysis/src/data.rs @@ -1,11 +1,12 @@ -use miden_hir::pass::{Analysis, AnalysisManager, AnalysisResult}; use miden_hir::{ + pass::{Analysis, AnalysisManager, AnalysisResult}, Function, FunctionIdent, GlobalValue, GlobalValueData, GlobalVariableTable, Module, Program, }; use midenc_session::Session; use rustc_hash::FxHashMap; -/// This analysis calculates the addresses/offsets of all global variables in a [Program] or [Module] +/// This analysis calculates the addresses/offsets of all global variables in a [Program] or +/// [Module] pub struct GlobalVariableAnalysis { layout: GlobalVariableLayout, _marker: core::marker::PhantomData, @@ -104,14 +105,13 @@ impl GlobalVariableLayout { self.global_table_offset } - /// Get the statically-allocated address at which the global value `gv` for `function` is stored. + /// Get the statically-allocated address at which the global value `gv` for `function` is + /// stored. /// /// This function returns `None` if the analysis does not know about `function`, `gv`, or if /// the symbol which `gv` resolves to was undefined. pub fn get_computed_addr(&self, function: &FunctionIdent, gv: GlobalValue) -> Option { - self.offsets - .get(function) - .and_then(|offsets| offsets.get(&gv).copied()) + self.offsets.get(function).and_then(|offsets| offsets.get(&gv).copied()) } } diff --git a/hir-analysis/src/dependency_graph.rs b/hir-analysis/src/dependency_graph.rs index 3d4a97095..3a12a2fa5 100644 --- a/hir-analysis/src/dependency_graph.rs +++ b/hir-analysis/src/dependency_graph.rs @@ -1,28 +1,26 @@ -use std::cmp::Ordering; -use std::collections::{BTreeMap, BTreeSet}; -use std::fmt; - -use smallvec::SmallVec; +use std::{ + cmp::Ordering, + collections::{BTreeMap, BTreeSet}, + fmt, +}; use miden_hir as hir; +use smallvec::SmallVec; /// This represents a node in a [DependencyGraph]. /// /// The node types here are carefully chosen to provide us with the following /// properties once we've constructed a [DependencyGraph] from a block: /// -/// * Distinguish between block-local operands and those which come from a -/// dominating block. This let's us reason globally about how function -/// arguments and instruction results are used in blocks of the program -/// so that they can be moved/copied as appropriate to keep them live only +/// * Distinguish between block-local operands and those which come from a dominating block. This +/// let's us reason globally about how function arguments and instruction results are used in +/// blocks of the program so that they can be moved/copied as appropriate to keep them live only /// for as long as they are needed. -/// * Represent the dependencies of individual arguments, this ensures -/// that dependencies between expressions in a block are correctly -/// represented when we compute a [TreeGraph], and that we can determine -/// exactly how many instances of a value are needed in a function. -/// * Represent usage of individual instruction results - both to ensure -/// we make copies of those results as needed, but to ensure we drop -/// unused results immediately if they are not needed. +/// * Represent the dependencies of individual arguments, this ensures that dependencies between +/// expressions in a block are correctly represented when we compute a [TreeGraph], and that we +/// can determine exactly how many instances of a value are needed in a function. +/// * Represent usage of individual instruction results - both to ensure we make copies of those +/// results as needed, but to ensure we drop unused results immediately if they are not needed. /// /// Furthermore, the precise layout and ordering of this enum is intentional, /// as it determines the order in which nodes are sorted, and thus the order @@ -386,12 +384,12 @@ pub struct InvalidNodeIdError; #[repr(transparent)] pub struct NodeId(u64); impl NodeId { + const IS_CONDITIONAL_ARG: u64 = 1; const TAG_ARG_DIRECT: u64 = 1 << 60; const TAG_ARG_INDIRECT: u64 = 2 << 60; const TAG_INST: u64 = 3 << 60; - const TAG_RESULT: u64 = 4 << 60; - const IS_CONDITIONAL_ARG: u64 = 1; const TAG_MASK: u64 = 0b111 << 60; + const TAG_RESULT: u64 = 4 << 60; /// Returns true if the [Node] corresponding to this identifier is of `Stack` type #[inline] @@ -414,10 +412,7 @@ impl NodeId { /// Returns true if the [Node] corresponding to this identifier is of `Argument` type #[inline] pub fn is_argument(&self) -> bool { - matches!( - self.0 & Self::TAG_MASK, - Self::TAG_ARG_DIRECT | Self::TAG_ARG_INDIRECT - ) + matches!(self.0 & Self::TAG_MASK, Self::TAG_ARG_DIRECT | Self::TAG_ARG_INDIRECT) } /// Decode this identifier into its corresponding [Node] @@ -728,10 +723,7 @@ impl DependencyGraph { .. } in edges.into_iter() { - self.edges - .get_mut(&other_node_id) - .unwrap() - .retain(|e| e.node != id); + self.edges.get_mut(&other_node_id).unwrap().retain(|e| e.node != id); } } } @@ -756,11 +748,7 @@ impl DependencyGraph { let id = node.into(); self.edges .get(&id) - .map(|es| { - es.iter() - .filter(|e| e.direction == Direction::Dependency) - .count() - }) + .map(|es| es.iter().filter(|e| e.direction == Direction::Dependency).count()) .unwrap_or_default() } @@ -843,7 +831,8 @@ impl DependencyGraph { } } - /// Like `predecessors`, but avoids decoding [Node] values, instead producing the raw [NodeId] values. + /// Like `predecessors`, but avoids decoding [Node] values, instead producing the raw [NodeId] + /// values. pub fn predecessor_ids(&self, node: impl Into) -> impl Iterator + '_ { let id = node.into(); self.edges[&id].iter().filter_map(|edge| { @@ -864,7 +853,8 @@ impl DependencyGraph { } } - /// Like `successors`, but avoids decoding [Node] values, instead producing the raw [NodeId] values. + /// Like `successors`, but avoids decoding [Node] values, instead producing the raw [NodeId] + /// values. pub fn successor_ids(&self, node: impl Into) -> impl Iterator + '_ { let id = node.into(); self.edges[&id].iter().filter_map(|edge| { @@ -877,10 +867,10 @@ impl DependencyGraph { } /// Returns a data structure which assigns an index to each node in the graph for which `root` - /// is an ancestor, including `root` itself. The assigned index indicates the order in which nodes - /// will be emitted during code generation - the lower the index, the earlier the node is emitted. - /// Conversely, a higher index indicates that a node will be scheduled later in the program, so - /// values will be materialized from lowest index to highest. + /// is an ancestor, including `root` itself. The assigned index indicates the order in which + /// nodes will be emitted during code generation - the lower the index, the earlier the node + /// is emitted. Conversely, a higher index indicates that a node will be scheduled later in + /// the program, so values will be materialized from lowest index to highest. pub fn indexed( &self, root: impl Into, @@ -895,18 +885,13 @@ impl DependencyGraph { while let Some(node) = stack.last().copied() { if discovered.insert(node) { if node.is_instruction() { - for arg in self - .successors(node) - .filter(|succ| succ.dependency.is_argument()) - { + for arg in self.successors(node).filter(|succ| succ.dependency.is_argument()) { let arg_source_id = self.unwrap_child(arg.dependency); if !discovered.contains(&arg_source_id) { stack.push(arg_source_id); } } - for other in self - .successors(node) - .filter(|succ| !succ.dependency.is_argument()) + for other in self.successors(node).filter(|succ| !succ.dependency.is_argument()) { let succ_node_id = if other.dependency.is_instruction() { other.dependency @@ -963,10 +948,7 @@ impl DependencyGraph { } } - let has_cycle = depgraph - .edges - .iter() - .any(|(n, es)| output.contains(n) && !es.is_empty()); + let has_cycle = depgraph.edges.iter().any(|(n, es)| output.contains(n) && !es.is_empty()); if has_cycle { Err(UnexpectedCycleError) } else { @@ -998,11 +980,8 @@ impl DependencyGraph { let dep_inst = *dep_inst; let block_id = function.dfg.pp_block(pp); if function.dfg.insts[dep_inst].block == block_id { - let dep_inst_index = function - .dfg - .block_insts(block_id) - .position(|id| id == dep_inst) - .unwrap(); + let dep_inst_index = + function.dfg.block_insts(block_id).position(|id| id == dep_inst).unwrap(); let result_inst_node_id = self.add_node(Node::Inst { id: dep_inst, pos: dep_inst_index as u16, @@ -1052,7 +1031,8 @@ pub struct DependencyGraphIndices { impl DependencyGraphIndices { /// Get the index of `node` /// - /// NOTE: This function will panic if `node` was not in the corresponding dependency graph, or is unresolved + /// NOTE: This function will panic if `node` was not in the corresponding dependency graph, or + /// is unresolved #[inline] pub fn get(&self, node: impl Into) -> Option { let id = node.into(); @@ -1163,13 +1143,24 @@ fn is_valid_dependency(dependent: NodeId, dependency: NodeId) -> bool { match (dependent.into(), dependency.into()) { (Node::Argument(_), Node::Stack(_) | Node::Result { .. }) => true, (Node::Argument(_), Node::Inst { .. } | Node::Argument(_)) => { - panic!("{dependent} -> {dependency} is invalid: arguments may only depend on results or operands"); + panic!( + "{dependent} -> {dependency} is invalid: arguments may only depend on results or \ + operands" + ); } (Node::Inst { .. }, Node::Inst { .. } | Node::Result { .. } | Node::Argument(_)) => true, - (Node::Inst { .. }, _) => panic!("{dependent} -> {dependency} is invalid: instruction nodes may only depend directly on arguments"), + (Node::Inst { .. }, _) => panic!( + "{dependent} -> {dependency} is invalid: instruction nodes may only depend directly \ + on arguments" + ), (Node::Result { .. }, Node::Inst { .. }) => true, - (Node::Result { .. }, _) => panic!("{dependent} -> {dependency} is invalid: result nodes may only depend directly on instructions"), - (Node::Stack(_), _) => panic!("{dependent} -> {dependency} is invalid: stack nodes may not have dependencies"), + (Node::Result { .. }, _) => panic!( + "{dependent} -> {dependency} is invalid: result nodes may only depend directly on \ + instructions" + ), + (Node::Stack(_), _) => { + panic!("{dependent} -> {dependency} is invalid: stack nodes may not have dependencies") + } } } @@ -1194,8 +1185,7 @@ const fn is_valid_dependency(_dependent: NodeId, _dependency: NodeId) -> bool { /// * All node types /// * All three argument types /// * All types of result usage (unused, singly/multiply used) -/// * Instruction and value identifiers which are added out of order -/// with respect to program order +/// * Instruction and value identifiers which are added out of order with respect to program order #[cfg(test)] pub(crate) fn simple_dependency_graph() -> DependencyGraph { let mut graph = DependencyGraph::new(); @@ -1376,36 +1366,18 @@ mod tests { assert_eq!(graph.child(inst1_arg0_node), Ok(Some(v1_node.into()))); assert_eq!(graph.child(inst1_arg1_node), Ok(Some(v0_node.into()))); assert_eq!(graph.child(inst2_arg0_node), Ok(Some(v2_node.into()))); - assert_eq!( - graph.child(inst2_block1_arg0_node), - Ok(Some(v1_node.into())) - ); - assert_eq!( - graph.child(inst2_block2_arg0_node), - Ok(Some(v1_node.into())) - ); - assert_eq!( - graph.child(inst2_block2_arg1_node), - Ok(Some(v0_node.into())) - ); + assert_eq!(graph.child(inst2_block1_arg0_node), Ok(Some(v1_node.into()))); + assert_eq!(graph.child(inst2_block2_arg0_node), Ok(Some(v1_node.into()))); + assert_eq!(graph.child(inst2_block2_arg1_node), Ok(Some(v0_node.into()))); // Arguments only have one dependent, the instruction they belong to assert_eq!(graph.parent(inst0_arg0_node), Ok(Some(inst0_node.into()))); assert_eq!(graph.parent(inst1_arg0_node), Ok(Some(inst1_node.into()))); assert_eq!(graph.parent(inst1_arg1_node), Ok(Some(inst1_node.into()))); assert_eq!(graph.parent(inst2_arg0_node), Ok(Some(inst2_node.into()))); - assert_eq!( - graph.parent(inst2_block1_arg0_node), - Ok(Some(inst2_node.into())) - ); - assert_eq!( - graph.parent(inst2_block2_arg0_node), - Ok(Some(inst2_node.into())) - ); - assert_eq!( - graph.parent(inst2_block2_arg1_node), - Ok(Some(inst2_node.into())) - ); + assert_eq!(graph.parent(inst2_block1_arg0_node), Ok(Some(inst2_node.into()))); + assert_eq!(graph.parent(inst2_block2_arg0_node), Ok(Some(inst2_node.into()))); + assert_eq!(graph.parent(inst2_block2_arg1_node), Ok(Some(inst2_node.into()))); // Results which are unused have no dependents assert_eq!(graph.parent(v3_node), Ok(None)); diff --git a/hir-analysis/src/dominance.rs b/hir-analysis/src/dominance.rs index ca8720e6b..42dce415b 100644 --- a/hir-analysis/src/dominance.rs +++ b/hir-analysis/src/dominance.rs @@ -3,14 +3,13 @@ use std::{ mem, }; -use cranelift_entity::packed_option::PackedOption; -use cranelift_entity::SecondaryMap; - -use rustc_hash::FxHashSet; - -use miden_hir::pass::{Analysis, AnalysisManager, AnalysisResult, PreservedAnalyses}; -use miden_hir::{Block, BranchInfo, DataFlowGraph, Function, Inst, ProgramPoint}; +use cranelift_entity::{packed_option::PackedOption, SecondaryMap}; +use miden_hir::{ + pass::{Analysis, AnalysisManager, AnalysisResult, PreservedAnalyses}, + Block, BranchInfo, DataFlowGraph, Function, Inst, ProgramPoint, +}; use midenc_session::Session; +use rustc_hash::FxHashSet; use super::{BlockPredecessor, ControlFlowGraph}; @@ -24,15 +23,16 @@ const SEEN: u32 = 1; /// A node in the dominator tree. Each block has one of these. #[derive(Clone, Default)] struct Node { - /// Number of this node in a reverse post-order traversal of the control-flow graph, starting from 1. + /// Number of this node in a reverse post-order traversal of the control-flow graph, starting + /// from 1. /// - /// This number is monotonic in the reverse post-order but not contiguous, as we leave holes for - /// localized modifications of the dominator tree after it is initially computed. + /// This number is monotonic in the reverse post-order but not contiguous, as we leave holes + /// for localized modifications of the dominator tree after it is initially computed. /// /// Unreachable nodes get number 0, all others are > 0. rpo_number: u32, - /// The immediate dominator of this block, represented as the instruction at the end of the dominating - /// block which transfers control to this block. + /// The immediate dominator of this block, represented as the instruction at the end of the + /// dominating block which transfers control to this block. /// /// This is `None` for unreachable blocks, as well as the entry block, which has no dominators. idom: PackedOption, @@ -132,17 +132,17 @@ impl DominatorTree { /// Returns the immediate dominator of `block`. /// /// The immediate dominator of a basic block is the instruction which transfers control to that - /// block (and implicitly, its enclosing block). This instruction does not have to be the terminator - /// of its block, though it typically is. + /// block (and implicitly, its enclosing block). This instruction does not have to be the + /// terminator of its block, though it typically is. /// - /// An instruction "dominates" `block` if all control flow paths from the function entry to `block` - /// must go through that instruction. + /// An instruction "dominates" `block` if all control flow paths from the function entry to + /// `block` must go through that instruction. /// /// The "immediate dominator" is the dominator that is closest to `block`. All other dominators /// also dominate the immediate dominator. /// - /// This returns `None` if `block` is not reachable from the entry block, or if it is the entry block - /// which has no dominators. + /// This returns `None` if `block` is not reachable from the entry block, or if it is the entry + /// block which has no dominators. pub fn idom(&self, block: Block) -> Option { self.nodes[block].idom.into() } @@ -207,10 +207,9 @@ impl DominatorTree { { let (mut block_b, mut inst_b) = match b.into() { ProgramPoint::Block(block) => (block, None), - ProgramPoint::Inst(inst) => ( - dfg.inst_block(inst).expect("Instruction not in layout."), - Some(inst), - ), + ProgramPoint::Inst(inst) => { + (dfg.inst_block(inst).expect("Instruction not in layout."), Some(inst)) + } }; let rpo_a = self.nodes[a].rpo_number; @@ -262,10 +261,7 @@ impl DominatorTree { } } - debug_assert_eq!( - a.block, b.block, - "Unreachable block passed to common_dominator?" - ); + debug_assert_eq!(a.block, b.block, "Unreachable block passed to common_dominator?"); // We're in the same block. The common dominator is the earlier instruction. if dfg.pp_cmp(a.inst, b.inst) == Ordering::Less { @@ -406,9 +402,8 @@ impl DominatorTree { .filter(|&BlockPredecessor { block: pred, .. }| self.nodes[pred].rpo_number > 1); // The RPO must visit at least one predecessor before this node. - let mut idom = reachable_preds - .next() - .expect("block node must have one reachable predecessor"); + let mut idom = + reachable_preds.next().expect("block node must have one reachable predecessor"); for pred in reachable_preds { idom = self.common_dominator(idom, pred, dfg); @@ -505,8 +500,8 @@ impl DominatorTreePreorder { /// Get an iterator over the immediate children of `block` in the dominator tree. /// - /// These are the blocks whose immediate dominator is an instruction in `block`, ordered according - /// to the CFG reverse post-order. + /// These are the blocks whose immediate dominator is an instruction in `block`, ordered + /// according to the CFG reverse post-order. pub fn children(&self, block: Block) -> ChildIter { ChildIter { dtpo: self, @@ -613,24 +608,25 @@ impl<'a> Iterator for ChildIter<'a> { /// You might wonder if `block3` is in the dominance frontier of `block0`, and the answer is no. /// That's because `block0` strictly dominates `block3`, i.e. all control flow must pass through it /// to reach `block3`. The reason why strict dominance matters becomes more clear when you consider -/// that any value defined in `block0` will have the same definition same regardless of which path is -/// taken to reach `block3`. +/// that any value defined in `block0` will have the same definition same regardless of which path +/// is taken to reach `block3`. /// /// ## Purpose /// -/// The dominance frontier is used to place new phi nodes (which in our IR are represented by block arguments) -/// after introducing register spills/reloads. Reloads would naturally introduce multiple definitions for -/// a given value, which would break the SSA property of the IR, so to preserve it, reloads introduce new -/// definitions, and all uses of the original definition dominated by the reload are updated. +/// The dominance frontier is used to place new phi nodes (which in our IR are represented by block +/// arguments) after introducing register spills/reloads. Reloads would naturally introduce multiple +/// definitions for a given value, which would break the SSA property of the IR, so to preserve it, +/// reloads introduce new definitions, and all uses of the original definition dominated by the +/// reload are updated. /// -/// However, that alone is insufficient, since there may be uses of the original definition which are _not_ -/// dominated by the reload due to branching control flow. To address this, we must introduce new block -/// arguments to every block in the dominance frontier of the block in which reloads occur, and where -/// the reloaded value is live. All uses of either the original definition dominated by that phi node are -/// rewritten to use the definition produced by the phi. +/// However, that alone is insufficient, since there may be uses of the original definition which +/// are _not_ dominated by the reload due to branching control flow. To address this, we must +/// introduce new block arguments to every block in the dominance frontier of the block in which +/// reloads occur, and where the reloaded value is live. All uses of either the original definition +/// dominated by that phi node are rewritten to use the definition produced by the phi. /// -/// The actual algorithm works bottom-up, rather than top-down, but the relationship to the dominance frontier -/// is the same in both cases. +/// The actual algorithm works bottom-up, rather than top-down, but the relationship to the +/// dominance frontier is the same in both cases. #[derive(Default)] pub struct DominanceFrontier { /// The dominance frontier for each block, as a set of blocks @@ -698,12 +694,12 @@ where #[cfg(test)] mod tests { - use super::*; - use crate::ControlFlowGraph; use miden_hir::{ - AbiParam, Function, FunctionBuilder, Immediate, InstBuilder, Signature, SourceSpan, Type, + AbiParam, FunctionBuilder, Immediate, InstBuilder, Signature, SourceSpan, Type, }; + use super::*; + #[test] fn domtree_empty() { let id = "test::empty".parse().unwrap(); @@ -737,12 +733,8 @@ mod tests { }; builder.switch_to_block(block0); - let cond = builder - .ins() - .neq_imm(v0, Immediate::I32(0), SourceSpan::UNKNOWN); - builder - .ins() - .cond_br(cond, block2, &[], trap_block, &[], SourceSpan::UNKNOWN); + let cond = builder.ins().neq_imm(v0, Immediate::I32(0), SourceSpan::UNKNOWN); + builder.ins().cond_br(cond, block2, &[], trap_block, &[], SourceSpan::UNKNOWN); builder.switch_to_block(trap_block); builder.ins().unreachable(SourceSpan::UNKNOWN); @@ -796,9 +788,7 @@ mod tests { let block1 = function.dfg.create_block(); let block2 = function.dfg.create_block(); let block3 = function.dfg.create_block(); - let cond = function - .dfg - .append_block_param(block3, Type::I1, SourceSpan::UNKNOWN); + let cond = function.dfg.append_block_param(block3, Type::I1, SourceSpan::UNKNOWN); function.dfg.entry = block3; function.signature.params.push(AbiParam::new(Type::I1)); let (br_block3_block1, br_block1_block0_block2) = { @@ -809,9 +799,7 @@ mod tests { builder.switch_to_block(block1); let br_block1_block0_block2 = - builder - .ins() - .cond_br(cond, block0, &[], block2, &[], SourceSpan::UNKNOWN); + builder.ins().cond_br(cond, block0, &[], block2, &[], SourceSpan::UNKNOWN); builder.switch_to_block(block2); builder.ins().br(block0, &[], SourceSpan::UNKNOWN); @@ -848,26 +836,13 @@ mod tests { assert_eq!(domtree.idom(block2).unwrap(), br_block1_block0_block2); assert_eq!(domtree.idom(block0).unwrap(), br_block1_block0_block2); - assert!(domtree.dominates( - br_block1_block0_block2, - br_block1_block0_block2, - &function.dfg - )); + assert!(domtree.dominates(br_block1_block0_block2, br_block1_block0_block2, &function.dfg)); assert!(!domtree.dominates(br_block1_block0_block2, br_block3_block1, &function.dfg)); assert!(domtree.dominates(br_block3_block1, br_block1_block0_block2, &function.dfg)); - assert_eq!( - domtree.rpo_cmp(block3, block3, &function.dfg), - Ordering::Equal - ); - assert_eq!( - domtree.rpo_cmp(block3, block1, &function.dfg), - Ordering::Less - ); - assert_eq!( - domtree.rpo_cmp(block3, br_block3_block1, &function.dfg), - Ordering::Less - ); + assert_eq!(domtree.rpo_cmp(block3, block3, &function.dfg), Ordering::Equal); + assert_eq!(domtree.rpo_cmp(block3, block1, &function.dfg), Ordering::Less); + assert_eq!(domtree.rpo_cmp(block3, br_block3_block1, &function.dfg), Ordering::Less); assert_eq!( domtree.rpo_cmp(br_block3_block1, br_block1_block0_block2, &function.dfg), Ordering::Less diff --git a/hir-analysis/src/lib.rs b/hir-analysis/src/lib.rs index 06d427e3f..429ca4a45 100644 --- a/hir-analysis/src/lib.rs +++ b/hir-analysis/src/lib.rs @@ -7,11 +7,13 @@ mod loops; mod treegraph; mod validation; -pub use self::control_flow::{BlockPredecessor, ControlFlowGraph}; -pub use self::data::{GlobalVariableAnalysis, GlobalVariableLayout}; -pub use self::dependency_graph::DependencyGraph; -pub use self::dominance::{DominanceFrontier, DominatorTree, DominatorTreePreorder}; -pub use self::liveness::LivenessAnalysis; -pub use self::loops::{Loop, LoopAnalysis, LoopLevel}; -pub use self::treegraph::{OrderedTreeGraph, TreeGraph}; -pub use self::validation::{ModuleValidationAnalysis, Rule}; +pub use self::{ + control_flow::{BlockPredecessor, ControlFlowGraph}, + data::{GlobalVariableAnalysis, GlobalVariableLayout}, + dependency_graph::DependencyGraph, + dominance::{DominanceFrontier, DominatorTree, DominatorTreePreorder}, + liveness::LivenessAnalysis, + loops::{Loop, LoopAnalysis, LoopLevel}, + treegraph::{OrderedTreeGraph, TreeGraph}, + validation::{ModuleValidationAnalysis, Rule}, +}; diff --git a/hir-analysis/src/liveness.rs b/hir-analysis/src/liveness.rs index 315fd80a3..d6c92ecf6 100644 --- a/hir-analysis/src/liveness.rs +++ b/hir-analysis/src/liveness.rs @@ -3,8 +3,11 @@ use std::{ collections::{BTreeMap, VecDeque}, }; -use miden_hir::pass::{Analysis, AnalysisManager, AnalysisResult, PreservedAnalyses}; -use miden_hir::{self as hir, Block as BlockId, Inst as InstId, Value as ValueId, *}; +use miden_hir::{ + self as hir, + pass::{Analysis, AnalysisManager, AnalysisResult, PreservedAnalyses}, + Block as BlockId, Inst as InstId, Value as ValueId, *, +}; use midenc_session::Session; use rustc_hash::FxHashMap; @@ -185,10 +188,7 @@ impl LivenessAnalysis { pub fn chromatic_number(&self) -> usize { let mut max = 0; for (_pp, next_used) in self.live_in.iter() { - max = cmp::max( - next_used.iter().filter(|(_, d)| *d < &u32::MAX).count(), - max, - ); + max = cmp::max(next_used.iter().filter(|(_, d)| *d < &u32::MAX).count(), max); } max } @@ -384,12 +384,13 @@ impl<'a, 'b> std::ops::BitXor<&'b NextUseSet> for &'a NextUseSet { /// This function computes global next-use distances/liveness until a fixpoint is reached. /// -/// The resulting data structure associates two [NextUseSet]s with every block and instruction in `function`. -/// One set provides next-use distances _at_ a given program point, the other _after_ a given program point. -/// Intuitively, these are basically what they sound like. If a value is used "at" a given instruction, it's -/// next-use distance will be 0, and if it is never used after that, it won't be present in the "after" set. -/// However, if it used again later, it's distance in the "after" set will be the distance from the instruction -/// following the current one (or the block exit if the current one is a terminator). +/// The resulting data structure associates two [NextUseSet]s with every block and instruction in +/// `function`. One set provides next-use distances _at_ a given program point, the other _after_ a +/// given program point. Intuitively, these are basically what they sound like. If a value is used +/// "at" a given instruction, it's next-use distance will be 0, and if it is never used after that, +/// it won't be present in the "after" set. However, if it used again later, it's distance in the +/// "after" set will be the distance from the instruction following the current one (or the block +/// exit if the current one is a terminator). fn compute_liveness( liveness: &mut LivenessAnalysis, function: &Function, @@ -472,11 +473,7 @@ fn compute_liveness( let mut inst_next_uses_after = inst_next_uses.clone(); // Add uses by this instruction to the live-in set, with a distance of 0 - for arg in inst_data - .arguments(&function.dfg.value_lists) - .iter() - .copied() - { + for arg in inst_data.arguments(&function.dfg.value_lists).iter().copied() { inst_next_uses.insert(arg, 0); } @@ -495,11 +492,7 @@ fn compute_liveness( // Add uses by this instruction to the live-in set, with a distance of 0 let mut inst_next_uses = NextUseSet::default(); - for arg in inst_data - .arguments(&function.dfg.value_lists) - .iter() - .copied() - { + for arg in inst_data.arguments(&function.dfg.value_lists).iter().copied() { inst_next_uses.insert(arg, 0); } @@ -508,15 +501,13 @@ fn compute_liveness( (inst_next_uses, NextUseSet::default()) } } - // This is a branch instruction, so get the next-use set at the entry of each successor, - // increment the distances in those sets based on the distance of the edge, and then take - // the join of those sets as the initial next-use set for `inst` + // This is a branch instruction, so get the next-use set at the entry of each + // successor, increment the distances in those sets based on the + // distance of the edge, and then take the join of those sets as the + // initial next-use set for `inst` BranchInfo::SingleDest(succ, extra_uses) => { - let mut inst_next_uses = liveness - .live_in - .entry(ProgramPoint::Block(succ)) - .or_default() - .clone(); + let mut inst_next_uses = + liveness.live_in.entry(ProgramPoint::Block(succ)).or_default().clone(); // Increment the next-use distance for all inherited next uses for (_value, dist) in inst_next_uses.iter_mut() { @@ -550,7 +541,8 @@ fn compute_liveness( inst_next_uses_after.remove(value); } - // If this block is in a loop, make sure we add the loop exit distance for edges leaving the loop + // If this block is in a loop, make sure we add the loop exit distance for edges + // leaving the loop if let Some(block_loop) = block_loop { let succ_loop = loops.innermost_loop(succ); let is_loop_exit = succ_loop @@ -569,9 +561,10 @@ fn compute_liveness( } // Same as above // - // NOTE: We additionally assert here that all critical edges in the control flow graph have been split, - // as we cannot proceed correctly otherwise. It is expected that either no critical edges - // exist, or that they have been split by a prior transformation. + // NOTE: We additionally assert here that all critical edges in the control flow + // graph have been split, as we cannot proceed correctly otherwise. + // It is expected that either no critical edges exist, or that they + // have been split by a prior transformation. BranchInfo::MultiDest(jts) => { let mut inst_next_uses = NextUseSet::default(); let mut inst_next_uses_after = NextUseSet::default(); @@ -581,8 +574,9 @@ fn compute_liveness( } in jts.iter() { let destination = *destination; - // If the successor block has multiple predecessors, this is a critical edge, as by - // definition this instruction means the current block has multiple successors + // If the successor block has multiple predecessors, this is a critical + // edge, as by definition this instruction means the + // current block has multiple successors assert_eq!( cfg.num_predecessors(destination), 1, @@ -600,7 +594,8 @@ fn compute_liveness( *dist = dist.saturating_add(1); } - // The next uses up to this point forms the initial next-use set after `inst` + // The next uses up to this point forms the initial next-use set after + // `inst` let mut jt_next_uses_after = jt_next_uses.clone(); // Add uses by this instruction to the live-in set, with a distance of 0 @@ -613,9 +608,10 @@ fn compute_liveness( jt_next_uses.insert(arg, 0); } - // Remove the successor block arguments from live-in/live-out, as those values - // cannot be live before they are defined, and even if the destination block - // dominates the current block (via loop), those values must be dead at this + // Remove the successor block arguments from live-in/live-out, as those + // values cannot be live before they are defined, + // and even if the destination block dominates the + // current block (via loop), those values must be dead at this // instruction as we're providing new definitions for them. for value in function.dfg.block_args(destination) { // Only remove them from live-in if they are not actually used though, @@ -627,7 +623,8 @@ fn compute_liveness( jt_next_uses_after.remove(value); } - // If this block is in a loop, make sure we add the loop exit distance for edges leaving the loop + // If this block is in a loop, make sure we add the loop exit distance for + // edges leaving the loop if let Some(block_loop) = block_loop { let succ_loop = loops.innermost_loop(destination); let is_loop_exit = succ_loop @@ -653,10 +650,7 @@ fn compute_liveness( // live-in values at any point within the block. max_register_pressure = cmp::max( max_register_pressure, - inst_next_uses - .iter() - .filter(|(_, d)| *d < &u32::MAX) - .count(), + inst_next_uses.iter().filter(|(_, d)| *d < &u32::MAX).count(), ); // Record the next-use distances for this program point @@ -669,17 +663,13 @@ fn compute_liveness( // Handle the block header let pp = ProgramPoint::Block(block_id); - // The block header derives it's next-use distances from the live-in set of it's first instruction + // The block header derives it's next-use distances from the live-in set of it's first + // instruction let first_inst = block.insts.front().get().unwrap().key; let mut block_next_uses = liveness.live_in[&ProgramPoint::Inst(first_inst)].clone(); // For each block argument, make sure a default next-use distance (u32::MAX) is set // if a distance is not found in the live-in set of the first instruction - for arg in block - .params - .as_slice(&function.dfg.value_lists) - .iter() - .copied() - { + for arg in block.params.as_slice(&function.dfg.value_lists).iter().copied() { block_next_uses.entry(arg).or_insert(u32::MAX); } // For blocks, the "after" set corresponds to the next-use set "after" the block @@ -708,9 +698,7 @@ fn compute_liveness( liveness.live_out.insert(pp, block_next_uses_after); } } - liveness - .per_block_register_pressure - .insert(block_id, max_register_pressure); + liveness.per_block_register_pressure.insert(block_id, max_register_pressure); } } diff --git a/hir-analysis/src/loops.rs b/hir-analysis/src/loops.rs index 2f521c7b1..7bf72781e 100644 --- a/hir-analysis/src/loops.rs +++ b/hir-analysis/src/loops.rs @@ -1,8 +1,8 @@ -use cranelift_entity::packed_option::PackedOption; -use cranelift_entity::{entity_impl, PrimaryMap, SecondaryMap}; - -use miden_hir::pass::{Analysis, AnalysisManager, AnalysisResult, PreservedAnalyses}; -use miden_hir::{Block, DataFlowGraph, Function}; +use cranelift_entity::{entity_impl, packed_option::PackedOption, PrimaryMap, SecondaryMap}; +use miden_hir::{ + pass::{Analysis, AnalysisManager, AnalysisResult, PreservedAnalyses}, + Block, DataFlowGraph, Function, +}; use midenc_session::Session; use super::{BlockPredecessor, ControlFlowGraph, DominatorTree}; @@ -173,8 +173,7 @@ impl LoopAnalysis { /// Determine if a Block is a loop header. If so, return the loop. pub fn is_loop_header(&self, block: Block) -> Option { - self.innermost_loop(block) - .filter(|&lp| self.loop_header(lp) == block) + self.innermost_loop(block).filter(|&lp| self.loop_header(lp) == block) } /// Determine if a Block belongs to a loop by running a finger along the loop tree. @@ -205,8 +204,7 @@ impl LoopAnalysis { /// Returns the loop-nest level of a given block. pub fn loop_level(&self, block: Block) -> LoopLevel { - self.innermost_loop(block) - .map_or(LoopLevel(0), |lp| self.loops[lp].level) + self.innermost_loop(block).map_or(LoopLevel(0), |lp| self.loops[lp].level) } /// Returns the loop level of the given level @@ -342,11 +340,9 @@ impl LoopAnalysis { #[cfg(test)] mod tests { + use miden_hir::{AbiParam, FunctionBuilder, InstBuilder, Signature, SourceSpan, Type}; + use super::*; - use crate::{ControlFlowGraph, DominatorTree}; - use miden_hir::{ - AbiParam, Function, FunctionBuilder, InstBuilder, Signature, SourceSpan, Type, - }; #[test] fn nested_loops_variant1_detection() { @@ -372,14 +368,10 @@ mod tests { builder.ins().br(block2, &[], SourceSpan::UNKNOWN); builder.switch_to_block(block2); - builder - .ins() - .cond_br(cond, block1, &[], block3, &[], SourceSpan::UNKNOWN); + builder.ins().cond_br(cond, block1, &[], block3, &[], SourceSpan::UNKNOWN); builder.switch_to_block(block3); - builder - .ins() - .cond_br(cond, block0, &[], block4, &[], SourceSpan::UNKNOWN); + builder.ins().cond_br(cond, block0, &[], block4, &[], SourceSpan::UNKNOWN); builder.switch_to_block(block4); builder.ins().ret(None, SourceSpan::UNKNOWN); @@ -431,9 +423,7 @@ mod tests { // block0 is outside of any loop builder.switch_to_block(block0); - builder - .ins() - .cond_br(cond, block1, &[], exit, &[], SourceSpan::UNKNOWN); + builder.ins().cond_br(cond, block1, &[], exit, &[], SourceSpan::UNKNOWN); // block1 simply branches to a loop header builder.switch_to_block(block1); @@ -441,9 +431,7 @@ mod tests { // block2 is the outer loop, which is conditionally entered builder.switch_to_block(block2); - builder - .ins() - .cond_br(cond, block3, &[], exit, &[], SourceSpan::UNKNOWN); + builder.ins().cond_br(cond, block3, &[], exit, &[], SourceSpan::UNKNOWN); // block3 simply branches to a nested loop header builder.switch_to_block(block3); @@ -451,9 +439,7 @@ mod tests { // block4 is the inner loop, which is conditionally escaped to the outer loop builder.switch_to_block(block4); - builder - .ins() - .cond_br(cond, block5, &[], block6, &[], SourceSpan::UNKNOWN); + builder.ins().cond_br(cond, block5, &[], block6, &[], SourceSpan::UNKNOWN); // block5 is the loop body of the inner loop builder.switch_to_block(block5); @@ -528,30 +514,22 @@ mod tests { builder.ins().br(block1, &[], SourceSpan::UNKNOWN); builder.switch_to_block(block1); - builder - .ins() - .cond_br(cond, block2, &[], block4, &[], SourceSpan::UNKNOWN); + builder.ins().cond_br(cond, block2, &[], block4, &[], SourceSpan::UNKNOWN); builder.switch_to_block(block2); builder.ins().br(block3, &[], SourceSpan::UNKNOWN); builder.switch_to_block(block3); - builder - .ins() - .cond_br(cond, block2, &[], block6, &[], SourceSpan::UNKNOWN); + builder.ins().cond_br(cond, block2, &[], block6, &[], SourceSpan::UNKNOWN); builder.switch_to_block(block4); builder.ins().br(block5, &[], SourceSpan::UNKNOWN); builder.switch_to_block(block5); - builder - .ins() - .cond_br(cond, block4, &[], block6, &[], SourceSpan::UNKNOWN); + builder.ins().cond_br(cond, block4, &[], block6, &[], SourceSpan::UNKNOWN); builder.switch_to_block(block6); - builder - .ins() - .cond_br(cond, block1, &[], block7, &[], SourceSpan::UNKNOWN); + builder.ins().cond_br(cond, block1, &[], block7, &[], SourceSpan::UNKNOWN); builder.switch_to_block(block7); builder.ins().ret(None, SourceSpan::UNKNOWN); diff --git a/hir-analysis/src/treegraph.rs b/hir-analysis/src/treegraph.rs index 434620f97..42594ae21 100644 --- a/hir-analysis/src/treegraph.rs +++ b/hir-analysis/src/treegraph.rs @@ -1,12 +1,15 @@ -use std::cmp::Ordering; -use std::collections::{BTreeMap, BTreeSet, VecDeque}; -use std::fmt; +use std::{ + cmp::Ordering, + collections::{BTreeMap, BTreeSet, VecDeque}, + fmt, +}; use smallvec::SmallVec; use crate::dependency_graph::*; -/// [OrderedTreeGraph] represents an immutable, fully-constructed and topologically sorted [TreeGraph]. +/// [OrderedTreeGraph] represents an immutable, fully-constructed and topologically sorted +/// [TreeGraph]. /// /// This is the representation we use during instruction scheduling. #[derive(Default, Debug)] @@ -26,13 +29,10 @@ impl TryFrom for OrderedTreeGraph { fn try_from(depgraph: DependencyGraph) -> Result { let graph = TreeGraph::from(depgraph.clone()); let ordering = graph.toposort()?; - let indices = ordering - .iter() - .copied() - .try_fold(BTreeMap::default(), |mut acc, root| { - acc.insert(root, depgraph.indexed(root)?); - Ok(acc) - })?; + let indices = ordering.iter().copied().try_fold(BTreeMap::default(), |mut acc, root| { + acc.insert(root, depgraph.indexed(root)?); + Ok(acc) + })?; Ok(Self { ordering, @@ -117,7 +117,8 @@ impl OrderedTreeGraph { // scheduled before the other. assert!( !self.ordering.is_empty(), - "invalid treegraph: the topographical ordering is empty even though the underlying graph is not" + "invalid treegraph: the topographical ordering is empty even though the underlying \ + graph is not" ); for n in self.ordering.iter().copied() { if n == a_tree { @@ -128,7 +129,10 @@ impl OrderedTreeGraph { } } - unreachable!("invalid treegraph: there are roots in the dependency graph not represented in the topographical ordering") + unreachable!( + "invalid treegraph: there are roots in the dependency graph not represented in the \ + topographical ordering" + ) } } impl core::convert::AsRef for OrderedTreeGraph { @@ -206,14 +210,15 @@ impl core::ops::Deref for OrderedTreeGraph { /// /// The treegraph data structure was (to my knowledge) first described in /// [this paper](https://www.sciencedirect.com/science/article/pii/S1571066111001538?via=ihub) -/// by Park, et al., called _Treegraph-based Instruction Scheduling for Stack-based Virtual Machines_. +/// by Park, et al., called _Treegraph-based Instruction Scheduling for Stack-based Virtual +/// Machines_. /// /// The implementation and usage of both [DependencyGraph] and [TreeGraph] are based on the design /// and algorithms described in that paper, so it is worth reading if you are curious about it. /// Our implementation here is tailored for our use case (i.e. the way we represent nodes has a -/// specific effect on the order in which we visit instructions and their arguments during scheduling), -/// but the overall properties of the data structure described in that paper hold for [TreeGraph] -/// as well. +/// specific effect on the order in which we visit instructions and their arguments during +/// scheduling), but the overall properties of the data structure described in that paper hold for +/// [TreeGraph] as well. #[derive(Default, Clone)] pub struct TreeGraph { /// The nodes which are explicitly represented in the graph @@ -435,7 +440,8 @@ impl TreeGraph { /// parent (dependent), they will be visited in this order. In general, this heuristic can be /// thought of as falling back to the original program order when no other criteria is available /// for sorting. In reality, it's more of a natural synergy in the data structures representing - /// the graph and nodes in the graph, so it is not nearly so explicit - but the effect is the same. + /// the graph and nodes in the graph, so it is not nearly so explicit - but the effect is the + /// same. /// /// ## Example /// @@ -538,7 +544,8 @@ impl TreeGraph { /// /// 1. Seed a queue with the set of treegraph roots with no predecessors, enqueuing them /// in their natural sort order. - /// 2. Pop the next root from the queue, remove all edges between that root and its dependencies, + /// 2. Pop the next root from the queue, remove all edges between that root and its + /// dependencies, /// and add the root to the output vector. If any of the dependencies have no remaining /// predecessors after the aforementioned edge was removed, it is added to the queue. /// 3. The process in step 2 is repeated until the queue is empty. @@ -547,11 +554,11 @@ impl TreeGraph { /// 5. Otherwise, the sort is complete. /// /// In effect, a node is only emitted once all of its dependents are emitted, so for codegen, - /// we can use this ordering for instruction scheduling, by visiting nodes in reverse topological - /// order (i.e. visiting dependencies before any of their dependents). This also has the effect - /// of placing items on the stack in the correct order needed for each instruction, as instruction - /// operands will be pushed on the stack right-to-left, so that the first operand to an instruction - /// is on top of the stack. + /// we can use this ordering for instruction scheduling, by visiting nodes in reverse + /// topological order (i.e. visiting dependencies before any of their dependents). This also + /// has the effect of placing items on the stack in the correct order needed for each + /// instruction, as instruction operands will be pushed on the stack right-to-left, so that + /// the first operand to an instruction is on top of the stack. pub fn toposort(&self) -> Result, UnexpectedCycleError> { let mut treegraph = self.clone(); let mut output = Vec::::with_capacity(treegraph.nodes.len()); @@ -592,11 +599,7 @@ impl From for TreeGraph { for node_id in depgraph.node_ids() { let is_multi_use = depgraph.num_predecessors(node_id) > 1; if is_multi_use { - cutset.extend( - depgraph - .predecessors(node_id) - .map(|d| (d.dependent, d.dependency)), - ); + cutset.extend(depgraph.predecessors(node_id).map(|d| (d.dependent, d.dependency))); } } @@ -686,7 +689,6 @@ mod tests { use miden_hir as hir; use super::*; - use crate::dependency_graph::simple_dependency_graph; /// See [simple_dependency_graph] for details on the input dependency graph. /// @@ -741,34 +743,13 @@ mod tests { let inst2_node = Node::Inst { id: inst2, pos: 3 }; let inst3_node = Node::Inst { id: inst3, pos: 1 }; - assert_eq!( - treegraph.cmp_scheduling(inst2_node, inst2_node), - Ordering::Equal - ); - assert_eq!( - treegraph.cmp_scheduling(inst2_node, inst3_node), - Ordering::Less - ); - assert_eq!( - treegraph.cmp_scheduling(inst2_node, inst1_node), - Ordering::Less - ); - assert_eq!( - treegraph.cmp_scheduling(inst1_node, inst2_node), - Ordering::Greater - ); - assert_eq!( - treegraph.cmp_scheduling(inst1_node, inst0_node), - Ordering::Less - ); - assert_eq!( - treegraph.cmp_scheduling(inst1_node, v1_node), - Ordering::Less - ); - assert_eq!( - treegraph.cmp_scheduling(inst0_node, v0_node), - Ordering::Less - ); + assert_eq!(treegraph.cmp_scheduling(inst2_node, inst2_node), Ordering::Equal); + assert_eq!(treegraph.cmp_scheduling(inst2_node, inst3_node), Ordering::Less); + assert_eq!(treegraph.cmp_scheduling(inst2_node, inst1_node), Ordering::Less); + assert_eq!(treegraph.cmp_scheduling(inst1_node, inst2_node), Ordering::Greater); + assert_eq!(treegraph.cmp_scheduling(inst1_node, inst0_node), Ordering::Less); + assert_eq!(treegraph.cmp_scheduling(inst1_node, v1_node), Ordering::Less); + assert_eq!(treegraph.cmp_scheduling(inst0_node, v0_node), Ordering::Less); // Instructions must be scheduled before all of their dependencies assert!(treegraph.is_scheduled_before(inst2_node, inst3_node)); diff --git a/hir-analysis/src/validation/block.rs b/hir-analysis/src/validation/block.rs index 15b7036e8..e45711cd3 100644 --- a/hir-analysis/src/validation/block.rs +++ b/hir-analysis/src/validation/block.rs @@ -64,26 +64,29 @@ impl<'a> Rule for DefsDominateUses<'a> { node.key, span, "an instruction may not use its own results as arguments", - "This situation can only arise if one has manually modified the arguments of an instruction, \ - incorrectly inserting a value obtained from the set of instruction results." + "This situation can only arise if one has manually modified the arguments of \ + an instruction, incorrectly inserting a value obtained from the set of \ + instruction results." ); } // Next, ensure that all used values are dominated by their definition for value in uses.iter().copied() { match self.dfg.value_data(value) { - // If the value comes from the current block's parameter list, this use is trivially dominated + // If the value comes from the current block's parameter list, this use is + // trivially dominated ValueData::Param { block, .. } if block == ¤t_block => continue, - // If the value comes from another block, then as long as all paths to the current - // block flow through that block, then this use is dominated by its definition + // If the value comes from another block, then as long as all paths to the + // current block flow through that block, then this use is + // dominated by its definition ValueData::Param { block, .. } => { if self.domtree.dominates(*block, current_block, self.dfg) { continue; } } - // If the value is an instruction result, then as long as all paths to the current - // instruction flow through the defining instruction, then this use is dominated - // by its definition + // If the value is an instruction result, then as long as all paths to the + // current instruction flow through the defining + // instruction, then this use is dominated by its definition ValueData::Inst { inst, .. } => { if self.domtree.dominates(*inst, node.key, self.dfg) { continue; @@ -97,10 +100,11 @@ impl<'a> Rule for DefsDominateUses<'a> { diagnostics, node.key, span, - "an argument of this instruction, {value}, is not defined on all paths leading to this point", - "All uses of a value must be dominated by its definition, i.e. all control flow paths \ - from the function entry to the point of each use must flow through the point where \ - that value is defined." + "an argument of this instruction, {value}, is not defined on all paths \ + leading to this point", + "All uses of a value must be dominated by its definition, i.e. all control \ + flow paths from the function entry to the point of each use must flow \ + through the point where that value is defined." ); } } @@ -114,9 +118,8 @@ impl<'a> Rule for DefsDominateUses<'a> { /// * A block may not be empty /// * A block must end with a terminator instruction /// * A block may not contain a terminator instruction in any position but the end -/// * A block which terminates with a branch instruction must reference a block -/// that is present in the function body (i.e. it is not valid to reference -/// detached blocks) +/// * A block which terminates with a branch instruction must reference a block that is present in +/// the function body (i.e. it is not valid to reference detached blocks) /// * A multi-way branch instruction must have at least one successor /// * A multi-way branch instruction must not specify the same block as a successor multiple times. /// @@ -163,7 +166,10 @@ impl<'a> Rule for BlockValidator<'a> { id, self.span, "invalid block terminator", - format!("The last instruction in a block must be a terminator, but {id} ends with {op} which is not a valid terminator") + format!( + "The last instruction in a block must be a terminator, but {id} ends with \ + {op} which is not a valid terminator" + ) ); } match terminator.analyze_branch(&self.dfg.value_lists) { @@ -174,8 +180,11 @@ impl<'a> Rule for BlockValidator<'a> { terminator.key, terminator.span(), "invalid successor", - format!("A block reference is only valid if the referenced block is present in the function layout. \ - {id} references {destination}, but the latter is not in the layout") + format!( + "A block reference is only valid if the referenced block is present \ + in the function layout. {id} references {destination}, but the \ + latter is not in the layout" + ) ); } } @@ -199,8 +208,11 @@ impl<'a> Rule for BlockValidator<'a> { terminator.key, terminator.span(), "invalid successor", - format!("A block reference is only valid if the referenced block is present in the function layout. \ - {id} references {destination}, but the latter is not in the layout") + format!( + "A block reference is only valid if the referenced block is \ + present in the function layout. {id} references {destination}, \ + but the latter is not in the layout" + ) ); } @@ -210,8 +222,11 @@ impl<'a> Rule for BlockValidator<'a> { terminator.key, terminator.span(), "invalid {op} instruction", - format!("A given block may only be a successor along a single control flow path, \ - but {id} uses {destination} as a successor for more than one path") + format!( + "A given block may only be a successor along a single control \ + flow path, but {id} uses {destination} as a successor for more \ + than one path" + ) ); } @@ -230,8 +245,10 @@ impl<'a> Rule for BlockValidator<'a> { id, self.span, "terminator found in middle of block", - format!("A block may only have a terminator instruction as the last instruction in the block, \ - but {id} uses {op} before the end of the block") + format!( + "A block may only have a terminator instruction as the last instruction \ + in the block, but {id} uses {op} before the end of the block" + ) ); } } diff --git a/hir-analysis/src/validation/function.rs b/hir-analysis/src/validation/function.rs index 04ae4edbe..99563fb0f 100644 --- a/hir-analysis/src/validation/function.rs +++ b/hir-analysis/src/validation/function.rs @@ -59,9 +59,8 @@ impl Rule for FunctionValidator { /// 1. The linkage is valid for functions /// 2. The calling convention is valid in the context the function is defined in /// 3. The ABI of its parameters matches the calling convention -/// 4. The ABI of the parameters and results are coherent, e.g. -/// there are no signed integer parameters which are specified -/// as being zero-extended, there are no results if an sret +/// 4. The ABI of the parameters and results are coherent, e.g. there are no signed integer +/// parameters which are specified as being zero-extended, there are no results if an sret /// parameter is present, etc. struct CoherentSignature { in_kernel_module: bool, @@ -86,8 +85,8 @@ impl Rule for CoherentSignature { invalid_function!( diagnostics, function.id, - "the signature of this function specifies '{linkage}' linkage, \ - but only 'external' or 'internal' are valid" + "the signature of this function specifies '{linkage}' linkage, but only \ + 'external' or 'internal' are valid" ); } @@ -101,10 +100,10 @@ impl Rule for CoherentSignature { diagnostics, function.id, function.id.span(), - "the '{cc}' calling convention may only be used with \ - 'internal' linkage in kernel modules", - "This function is declared with 'external' linkage in a kernel module, so \ - it must use the 'kernel' calling convention" + "the '{cc}' calling convention may only be used with 'internal' linkage in \ + kernel modules", + "This function is declared with 'external' linkage in a kernel module, so it \ + must use the 'kernel' calling convention" ); } else if !is_public && is_kernel_function { invalid_function!( @@ -130,9 +129,12 @@ impl Rule for CoherentSignature { // 3 // * sret parameters may not be used with kernel calling convention // * pointer-typed parameters/results may not be used with kernel calling convention - // * parameters larger than 8 bytes must be passed by reference with fast/C calling conventions - // * results larger than 8 bytes require the use of an sret parameter with fast/C calling conventions - // * total size of all parameters when laid out on the operand stack may not exceed 64 bytes (16 field elements) + // * parameters larger than 8 bytes must be passed by reference with fast/C calling + // conventions + // * results larger than 8 bytes require the use of an sret parameter with fast/C calling + // conventions + // * total size of all parameters when laid out on the operand stack may not exceed 64 bytes + // (16 field elements) // // 4 // * parameter count and types must be consistent between the signature and the entry block @@ -150,9 +152,9 @@ impl Rule for CoherentSignature { function.id, function.id.span(), "function signature and entry block have different arities", - "This happens if the signature or entry block are modified without updating the other, \ - make sure the number and types of all parameters are the same in both the signature and \ - the entry block" + "This happens if the signature or entry block are modified without updating the \ + other, make sure the number and types of all parameters are the same in both the \ + signature and the entry block" ); } for (i, param) in function.signature.params.iter().enumerate() { @@ -169,8 +171,8 @@ impl Rule for CoherentSignature { span, "parameter type mismatch between signature and entry block", format!( - "The function declares this parameter as having type {param_ty}, \ - but the actual type is {value_ty}" + "The function declares this parameter as having type {param_ty}, but the \ + actual type is {value_ty}" ) ); } @@ -184,7 +186,8 @@ impl Rule for CoherentSignature { function.id, span, "signed integer parameters may not be combined with zero-extension", - "Zero-extending a signed-integer loses the signedness, you should use signed-extension instead" + "Zero-extending a signed-integer loses the signedness, you should use \ + signed-extension instead" ); } ArgumentExtension::Sext | ArgumentExtension::Zext if !is_integer => { @@ -192,7 +195,8 @@ impl Rule for CoherentSignature { diagnostics, function.id, span, - "non-integer parameters may not be combined with argument extension attributes", + "non-integer parameters may not be combined with argument extension \ + attributes", "Argument extension has no meaning for types other than integers" ); } @@ -210,8 +214,10 @@ impl Rule for CoherentSignature { diagnostics, function.id, span, - "functions using the 'kernel' calling convention may not use sret or pointer-typed parameters", - "Kernel functions are invoked in a different memory context, so they may not pass or return values by reference" + "functions using the 'kernel' calling convention may not use sret or \ + pointer-typed parameters", + "Kernel functions are invoked in a different memory context, so they may not \ + pass or return values by reference" ); } @@ -222,9 +228,11 @@ impl Rule for CoherentSignature { diagnostics, function.id, span, - "a function may only have a single sret parameter, and it must be the first parameter", - "The sret parameter type is used to return a large value from a function, \ - but it may only be used for functions with a single return value" + "a function may only have a single sret parameter, and it must be the \ + first parameter", + "The sret parameter type is used to return a large value from a \ + function, but it may only be used for functions with a single return \ + value" ); } if !is_pointer { @@ -246,8 +254,9 @@ impl Rule for CoherentSignature { function.id, span, "functions with an sret parameter must have no results", - "An sret parameter is used in place of normal return values, but this function uses both, \ - which is not valid. You should remove the results from the function signature." + "An sret parameter is used in place of normal return values, but this \ + function uses both, which is not valid. You should remove the \ + results from the function signature." ); } } @@ -259,16 +268,16 @@ impl Rule for CoherentSignature { function.id, span, "this parameter type is too large to pass by value", - format!("This parameter has type {param_ty}, you must refactor this function to pass it by reference instead") + format!( + "This parameter has type {param_ty}, you must refactor this function \ + to pass it by reference instead" + ) ); } } - effective_stack_usage += param_ty - .clone() - .to_raw_parts() - .map(|parts| parts.len()) - .unwrap_or(0); + effective_stack_usage += + param_ty.clone().to_raw_parts().map(|parts| parts.len()).unwrap_or(0); } if effective_stack_usage > 16 { @@ -277,10 +286,11 @@ impl Rule for CoherentSignature { function.id, span, "this function has a signature with too many parameters", - "Due to the constraints of the Miden VM, all function parameters must fit on the operand stack, \ - which is 16 elements (each of which is effectively 4 bytes, a maximum of 64 bytes). \ - The layout of the parameter list of this function requires more than this limit. \ - You should either remove parameters, or combine some of them into a struct which is then passed by reference." + "Due to the constraints of the Miden VM, all function parameters must fit on the \ + operand stack, which is 16 elements (each of which is effectively 4 bytes, a \ + maximum of 64 bytes). The layout of the parameter list of this function requires \ + more than this limit. You should either remove parameters, or combine some of \ + them into a struct which is then passed by reference." ); } @@ -308,7 +318,11 @@ impl Rule for CoherentSignature { function.id, function.id.span(), "This function specifies a result type which is too large to pass by value", - format!("The parameter at index {} has type {}, you must refactor this function to pass it by reference instead", i, &result.ty) + format!( + "The parameter at index {} has type {}, you must refactor this function \ + to pass it by reference instead", + i, &result.ty + ) ); } } diff --git a/hir-analysis/src/validation/mod.rs b/hir-analysis/src/validation/mod.rs index 3e6b3af64..029fde4f6 100644 --- a/hir-analysis/src/validation/mod.rs +++ b/hir-analysis/src/validation/mod.rs @@ -12,15 +12,7 @@ macro_rules! bug { }}; ($diagnostics:ident, $msg:literal, $span:expr, $label:expr, $span2:expr, $label2:expr) => {{ - diagnostic!( - $diagnostics, - Severity::Bug, - $msg, - $span, - $label, - $span2, - $label2 - ); + diagnostic!($diagnostics, Severity::Bug, $msg, $span, $label, $span2, $label2); }}; } @@ -38,15 +30,7 @@ macro_rules! error { }}; ($diagnostics:ident, $msg:literal, $span:expr, $label:expr, $span2:expr, $label2:expr) => {{ - diagnostic!( - $diagnostics, - Severity::Error, - $msg, - $span, - $label, - $span2, - $label2 - ); + diagnostic!($diagnostics, Severity::Error, $msg, $span, $label, $span2, $label2); }}; } @@ -65,13 +49,7 @@ macro_rules! invalid_instruction { ($diagnostics:ident, $inst:expr, $span:expr, $label:expr, $note:expr) => {{ let span = $span; let reason = format!($label); - bug!( - $diagnostics, - "invalid instruction", - span, - reason.as_str(), - $note - ); + bug!($diagnostics, "invalid instruction", span, reason.as_str(), $note); return Err(crate::validation::ValidationError::InvalidInstruction { span, inst: $inst, @@ -144,13 +122,7 @@ macro_rules! invalid_function { ($diagnostics:ident, $function:expr, $span:expr, $label:expr, $note:expr) => {{ let span = $span; let reason = format!($label); - error!( - $diagnostics, - "invalid function", - span, - reason.as_str(), - $note - ); + error!($diagnostics, "invalid function", span, reason.as_str(), $note); return Err(crate::validation::ValidationError::InvalidFunction { function: $function, reason, @@ -182,12 +154,7 @@ macro_rules! invalid_global { ($diagnostics:ident, $name:expr, $span:expr, $label:expr) => {{ let span = $span; let reason = format!($label); - error!( - $diagnostics, - "invalid global variable", - span, - reason.as_str() - ); + error!($diagnostics, "invalid global variable", span, reason.as_str()); return Err(crate::validation::ValidationError::InvalidGlobalVariable { name: $name, reason, @@ -200,18 +167,20 @@ mod function; mod naming; mod typecheck; -pub use self::typecheck::TypeError; - use miden_diagnostics::DiagnosticsHandler; -use miden_hir::pass::{Analysis, AnalysisManager, AnalysisResult}; - -use miden_hir::*; +use miden_hir::{ + pass::{Analysis, AnalysisManager, AnalysisResult}, + *, +}; use midenc_session::Session; -use self::block::{BlockValidator, DefsDominateUses}; -use self::function::FunctionValidator; -use self::naming::NamingConventions; -use self::typecheck::TypeCheck; +pub use self::typecheck::TypeError; +use self::{ + block::{BlockValidator, DefsDominateUses}, + function::FunctionValidator, + naming::NamingConventions, + typecheck::TypeCheck, +}; /// This error is produced by validation rules run against the IR #[derive(Debug, thiserror::Error)] @@ -303,7 +272,7 @@ impl PartialEq for ValidationError { ) => ai == bi && ar == br, (Self::TypeError(a), Self::TypeError(b)) => a == b, (Self::Failed(a), Self::Failed(b)) => a.to_string() == b.to_string(), - (_, _) => false, + (..) => false, } } } @@ -464,10 +433,7 @@ impl From for Result<(), ValidationError> { #[cfg(test)] mod tests { - use miden_hir::{ - testing::{self, TestContext}, - ModuleBuilder, - }; + use miden_hir::testing::TestContext; use super::*; diff --git a/hir-analysis/src/validation/naming.rs b/hir-analysis/src/validation/naming.rs index 413d66a4e..885afa8b9 100644 --- a/hir-analysis/src/validation/naming.rs +++ b/hir-analysis/src/validation/naming.rs @@ -161,7 +161,13 @@ impl Rule for NamingConventions { } else { debug_assert_eq!(offset, 0); let span = SourceSpan::new(span.start(), span.start()); - invalid_function!(diagnostics, function.id, span, "function names must start with an ascii-alphabetic character, '_', '$', or '@'"); + invalid_function!( + diagnostics, + function.id, + span, + "function names must start with an ascii-alphabetic character, '_', '$', or \ + '@'" + ); } } @@ -179,11 +185,7 @@ impl Rule for NamingConventions { // 1. Must not be empty if name.is_empty() { - invalid_global!( - diagnostics, - global.name, - "global variable names cannot be empty" - ); + invalid_global!(diagnostics, global.name, "global variable names cannot be empty"); } // 2. Must start with an ASCII-alphabetic character, underscore, `.`, `$` or `@` @@ -205,7 +207,13 @@ impl Rule for NamingConventions { } else { debug_assert_eq!(offset, 0); let span = SourceSpan::new(span.start(), span.start()); - invalid_global!(diagnostics, global.name, span, "global variable names must start with an ascii-alphabetic character, '_', '.', '$', or '@'"); + invalid_global!( + diagnostics, + global.name, + span, + "global variable names must start with an ascii-alphabetic character, '_', \ + '.', '$', or '@'" + ); } } diff --git a/hir-analysis/src/validation/typecheck.rs b/hir-analysis/src/validation/typecheck.rs index 3af9c984d..81280c905 100644 --- a/hir-analysis/src/validation/typecheck.rs +++ b/hir-analysis/src/validation/typecheck.rs @@ -2,7 +2,6 @@ use core::fmt; use miden_diagnostics::{DiagnosticsHandler, Severity, Spanned}; use miden_hir::*; - use rustc_hash::FxHashMap; use super::{Rule, ValidationError}; @@ -30,7 +29,8 @@ pub enum TypeError { actual: Type, index: usize, }, - /// The number of arguments given to a successor block does not match what is expected by the block + /// The number of arguments given to a successor block does not match what is expected by the + /// block #[error("{successor} expected {expected} arguments, but {actual} are given")] IncorrectSuccessorArgumentCount { successor: Block, @@ -45,25 +45,33 @@ pub enum TypeError { actual: Type, index: usize, }, - /// An attempt was made to cast from a larger integer type to a smaller one via widening cast, e.g. `zext` + /// An attempt was made to cast from a larger integer type to a smaller one via widening cast, + /// e.g. `zext` #[error("expected result to be an integral type larger than {expected}, but got {actual}")] InvalidWideningCast { expected: Type, actual: Type }, - /// An attempt was made to cast from a smaller integer type to a larger one via narrowing cast, e.g. `trunc` + /// An attempt was made to cast from a smaller integer type to a larger one via narrowing cast, + /// e.g. `trunc` #[error("expected result to be an integral type smaller than {expected}, but got {actual}")] InvalidNarrowingCast { expected: Type, actual: Type }, - /// The arguments of an instruction were supposed to be the same type, but at least one differs from the controlling type - #[error("expected arguments to be the same type ({expected}), but argument at index {index} is {actual}")] + /// The arguments of an instruction were supposed to be the same type, but at least one differs + /// from the controlling type + #[error( + "expected arguments to be the same type ({expected}), but argument at index {index} is \ + {actual}" + )] MatchingArgumentTypeViolation { expected: Type, actual: Type, index: usize, }, - /// The result type of an instruction was supposed to be the same as the arguments, but it wasn't + /// The result type of an instruction was supposed to be the same as the arguments, but it + /// wasn't #[error("expected result to be the same type ({expected}) as the arguments, but got {actual}")] MatchingResultTypeViolation { expected: Type, actual: Type }, } -/// This validation rule type checks a block to catch any type violations by instructions in that block +/// This validation rule type checks a block to catch any type violations by instructions in that +/// block pub struct TypeCheck<'a> { signature: &'a Signature, dfg: &'a DataFlowGraph, @@ -121,7 +129,13 @@ impl<'a> Rule for TypeCheck<'a> { }, Instruction::Load(LoadOp { ref ty, addr, .. }) => { if ty.size_in_felts() > 4 { - invalid_instruction!(diagnostics, node.key, span, "cannot load a value of type {ty} on the stack, as it is larger than 16 bytes"); + invalid_instruction!( + diagnostics, + node.key, + span, + "cannot load a value of type {ty} on the stack, as it is larger than \ + 16 bytes" + ); } typechecker.check(&[*addr], results)?; } @@ -151,12 +165,8 @@ impl<'a> Rule for TypeCheck<'a> { }, )); } - for (index, (expected, arg)) in self - .signature - .results - .iter() - .zip(args.iter().copied()) - .enumerate() + for (index, (expected, arg)) in + self.signature.results.iter().zip(args.iter().copied()).enumerate() { let actual = self.dfg.value_type(arg); if actual != &expected.ty { @@ -182,13 +192,11 @@ impl<'a> Rule for TypeCheck<'a> { let expected = &self.signature.results[0].ty; let actual = arg.ty(); if &actual != expected { - return Err(ValidationError::TypeError( - TypeError::IncorrectArgumentType { - expected: expected.clone().into(), - actual, - index: 0, - }, - )); + return Err(ValidationError::TypeError(TypeError::IncorrectArgumentType { + expected: expected.clone().into(), + actual, + index: 0, + })); } } Instruction::Br(Br { @@ -208,11 +216,8 @@ impl<'a> Rule for TypeCheck<'a> { }, )); } - for (index, (param, arg)) in expected - .iter() - .copied() - .zip(args.iter().copied()) - .enumerate() + for (index, (param, arg)) in + expected.iter().copied().zip(args.iter().copied()).enumerate() { let expected = self.dfg.value_type(param); let actual = self.dfg.value_type(arg); @@ -252,11 +257,8 @@ impl<'a> Rule for TypeCheck<'a> { }, )); } - for (index, (param, arg)) in expected - .iter() - .copied() - .zip(args.iter().copied()) - .enumerate() + for (index, (param, arg)) in + expected.iter().copied().zip(args.iter().copied()).enumerate() { let expected = self.dfg.value_type(param); let actual = self.dfg.value_type(arg); @@ -284,17 +286,42 @@ impl<'a> Rule for TypeCheck<'a> { let mut seen = FxHashMap::::default(); for (i, (key, successor)) in arms.iter().enumerate() { if let Some(prev) = seen.insert(*key, i) { - return Err(ValidationError::InvalidInstruction { span, inst: node.key, reason: format!("all arms of a 'switch' must have a unique discriminant, but the arm at index {i} has the same discriminant as the arm at {prev}") }); + return Err(ValidationError::InvalidInstruction { + span, + inst: node.key, + reason: format!( + "all arms of a 'switch' must have a unique discriminant, but \ + the arm at index {i} has the same discriminant as the arm at \ + {prev}" + ), + }); } let expected = self.dfg.block_args(*successor); if !expected.is_empty() { - return Err(ValidationError::InvalidInstruction { span, inst: node.key, reason: format!("all successors of a 'switch' must not have block parameters, but {successor}, the successor for discriminant {key}, has {} arguments", expected.len()) }); + return Err(ValidationError::InvalidInstruction { + span, + inst: node.key, + reason: format!( + "all successors of a 'switch' must not have block parameters, \ + but {successor}, the successor for discriminant {key}, has \ + {} arguments", + expected.len() + ), + }); } } let expected = self.dfg.block_args(*fallback); if !expected.is_empty() { - return Err(ValidationError::InvalidInstruction { span, inst: node.key, reason: format!("all successors of a 'switch' must not have block parameters, but {fallback}, the default successor, has {} arguments", expected.len()) }); + return Err(ValidationError::InvalidInstruction { + span, + inst: node.key, + reason: format!( + "all successors of a 'switch' must not have block parameters, but \ + {fallback}, the default successor, has {} arguments", + expected.len() + ), + }); } } } @@ -369,15 +396,20 @@ pub enum InstPattern { Unary(TypePattern), /// The instruction matches if it has one argument of the given type and no results UnaryNoResult(TypePattern), - /// The instruction matches if it has one argument of the first type and one result of the second type + /// The instruction matches if it has one argument of the first type and one result of the + /// second type /// - /// This is used to represent things like `inttoptr` or `ptrtoint` which map one type to another + /// This is used to represent things like `inttoptr` or `ptrtoint` which map one type to + /// another UnaryMap(TypePattern, TypePattern), - /// The instruction matches if it has one argument of integral type, and one result of a larger integral type + /// The instruction matches if it has one argument of integral type, and one result of a larger + /// integral type UnaryWideningCast(TypePattern, TypePattern), - /// The instruction matches if it has one argument of integral type, and one result of a smaller integral type + /// The instruction matches if it has one argument of integral type, and one result of a + /// smaller integral type UnaryNarrowingCast(TypePattern, TypePattern), - /// The instruction matches if it has two arguments of the given type, and one result which is the same type as the first argument + /// The instruction matches if it has two arguments of the given type, and one result which is + /// the same type as the first argument Binary(TypePattern, TypePattern), /// The instruction matches if it has two arguments and one result, all of the same type BinaryMatching(TypePattern), @@ -385,11 +417,13 @@ pub enum InstPattern { BinaryMatchingNoResult(TypePattern), /// The instruction matches if it has two arguments of the same type, and returns a boolean BinaryPredicate(TypePattern), - /// The instruction matches if its first argument matches the first type, with two more arguments and one result matching the second type + /// The instruction matches if its first argument matches the first type, with two more + /// arguments and one result matching the second type /// /// This is used to model instructions like `select` TernaryMatching(TypePattern, TypePattern), - /// The instruction matches if it has the exact number of arguments and results given, each corresponding to the given type + /// The instruction matches if it has the exact number of arguments and results given, each + /// corresponding to the given type Exact(Vec, Vec), /// The instruction matches any number of arguments and results, of any type Any, @@ -419,9 +453,9 @@ impl InstPattern { Ok(()) } Self::Unary(_) - | Self::UnaryMap(_, _) - | Self::UnaryWideningCast(_, _) - | Self::UnaryNarrowingCast(_, _) => { + | Self::UnaryMap(..) + | Self::UnaryWideningCast(..) + | Self::UnaryNarrowingCast(..) => { if args.len() != 1 { return Err(TypeError::IncorrectArgumentCount { expected: 1, @@ -454,7 +488,7 @@ impl InstPattern { let actual = dfg.value_type(args[0]); self.into_unary_match(actual, None) } - Self::Binary(_, _) | Self::BinaryMatching(_) | Self::BinaryPredicate(_) => { + Self::Binary(..) | Self::BinaryMatching(_) | Self::BinaryPredicate(_) => { if args.len() != 2 { return Err(TypeError::IncorrectArgumentCount { expected: 2, @@ -489,7 +523,7 @@ impl InstPattern { let rhs = dfg.value_type(args[1]); self.into_binary_match(lhs, rhs, None) } - Self::TernaryMatching(_, _) => { + Self::TernaryMatching(..) => { if args.len() != 3 { return Err(TypeError::IncorrectArgumentCount { expected: 3, @@ -521,10 +555,8 @@ impl InstPattern { actual: results.len(), }); } - for (index, (expected, arg)) in expected_args - .into_iter() - .zip(args.iter().copied()) - .enumerate() + for (index, (expected, arg)) in + expected_args.into_iter().zip(args.iter().copied()).enumerate() { let actual = dfg.value_type(arg); if !expected.matches(actual) { @@ -535,10 +567,8 @@ impl InstPattern { }); } } - for (index, (expected, result)) in expected_results - .into_iter() - .zip(results.iter().copied()) - .enumerate() + for (index, (expected, result)) in + expected_results.into_iter().zip(results.iter().copied()).enumerate() { let actual = dfg.value_type(result); if !expected.matches(actual) { @@ -556,7 +586,8 @@ impl InstPattern { } } - /// Evaluate this pattern against the given arguments (including an immediate argument) and results + /// Evaluate this pattern against the given arguments (including an immediate argument) and + /// results pub fn into_match_with_immediate( self, dfg: &DataFlowGraph, @@ -567,9 +598,9 @@ impl InstPattern { match self { Self::Empty => panic!("invalid empty pattern for instruction with immediate argument"), Self::Unary(_) - | Self::UnaryMap(_, _) - | Self::UnaryWideningCast(_, _) - | Self::UnaryNarrowingCast(_, _) => { + | Self::UnaryMap(..) + | Self::UnaryWideningCast(..) + | Self::UnaryNarrowingCast(..) => { if !args.is_empty() { return Err(TypeError::IncorrectArgumentCount { expected: 1, @@ -602,7 +633,7 @@ impl InstPattern { let actual = imm.ty(); self.into_unary_match(&actual, None) } - Self::Binary(_, _) | Self::BinaryMatching(_) | Self::BinaryPredicate(_) => { + Self::Binary(..) | Self::BinaryMatching(_) | Self::BinaryPredicate(_) => { if args.len() != 1 { return Err(TypeError::IncorrectArgumentCount { expected: 2, @@ -637,7 +668,7 @@ impl InstPattern { let rhs = imm.ty(); self.into_binary_match(lhs, &rhs, None) } - Self::TernaryMatching(_, _) => { + Self::TernaryMatching(..) => { if args.len() != 2 { return Err(TypeError::IncorrectArgumentCount { expected: 3, @@ -669,10 +700,8 @@ impl InstPattern { actual: results.len(), }); } - for (index, (expected, arg)) in expected_args - .into_iter() - .zip(args.iter().copied()) - .enumerate() + for (index, (expected, arg)) in + expected_args.into_iter().zip(args.iter().copied()).enumerate() { let actual = dfg.value_type(arg); if !expected.matches(actual) { @@ -683,10 +712,8 @@ impl InstPattern { }); } } - for (index, (expected, result)) in expected_results - .into_iter() - .zip(results.iter().copied()) - .enumerate() + for (index, (expected, result)) in + expected_results.into_iter().zip(results.iter().copied()).enumerate() { let actual = dfg.value_type(result); if !expected.matches(actual) { @@ -791,12 +818,12 @@ impl InstPattern { } } Self::Empty - | Self::Binary(_, _) + | Self::Binary(..) | Self::BinaryMatching(_) | Self::BinaryMatchingNoResult(_) | Self::BinaryPredicate(_) - | Self::TernaryMatching(_, _) - | Self::Exact(_, _) + | Self::TernaryMatching(..) + | Self::Exact(..) | Self::Any => unreachable!(), } @@ -884,11 +911,11 @@ impl InstPattern { Self::Empty | Self::Unary(_) | Self::UnaryNoResult(_) - | Self::UnaryMap(_, _) - | Self::UnaryWideningCast(_, _) - | Self::UnaryNarrowingCast(_, _) - | Self::TernaryMatching(_, _) - | Self::Exact(_, _) + | Self::UnaryMap(..) + | Self::UnaryWideningCast(..) + | Self::UnaryNarrowingCast(..) + | Self::TernaryMatching(..) + | Self::Exact(..) | Self::Any => unreachable!(), } @@ -935,14 +962,14 @@ impl InstPattern { Self::Empty | Self::Unary(_) | Self::UnaryNoResult(_) - | Self::UnaryMap(_, _) - | Self::UnaryWideningCast(_, _) - | Self::UnaryNarrowingCast(_, _) - | Self::Binary(_, _) + | Self::UnaryMap(..) + | Self::UnaryWideningCast(..) + | Self::UnaryNarrowingCast(..) + | Self::Binary(..) | Self::BinaryMatching(_) | Self::BinaryMatchingNoResult(_) | Self::BinaryPredicate(_) - | Self::Exact(_, _) + | Self::Exact(..) | Self::Any => unreachable!(), } @@ -1085,7 +1112,8 @@ impl<'a> InstTypeChecker<'a> { }) } - /// Checks that the given `operands` and `results` match the types represented by this [InstTypeChecker] + /// Checks that the given `operands` and `results` match the types represented by this + /// [InstTypeChecker] pub fn check(self, operands: &[Value], results: &[Value]) -> Result<(), ValidationError> { let diagnostics = self.diagnostics; let dfg = self.dfg; @@ -1104,7 +1132,8 @@ impl<'a> InstTypeChecker<'a> { } } - /// Checks that the given `operands` (with immediate) and `results` match the types represented by this [InstTypeChecker] + /// Checks that the given `operands` (with immediate) and `results` match the types represented + /// by this [InstTypeChecker] pub fn check_immediate( self, operands: &[Value], @@ -1113,10 +1142,7 @@ impl<'a> InstTypeChecker<'a> { ) -> Result<(), ValidationError> { let diagnostics = self.diagnostics; let dfg = self.dfg; - match self - .pattern - .into_match_with_immediate(dfg, operands, imm, results) - { + match self.pattern.into_match_with_immediate(dfg, operands, imm, results) { Ok(_) => Ok(()), Err(err) => { let opcode = self.opcode; diff --git a/hir-macros/src/lib.rs b/hir-macros/src/lib.rs index 8c934064d..7baa5f1fa 100644 --- a/hir-macros/src/lib.rs +++ b/hir-macros/src/lib.rs @@ -1,7 +1,6 @@ use inflector::cases::kebabcase::to_kebab_case; use quote::quote; -use syn::spanned::Spanned; -use syn::{parse_macro_input, DeriveInput, Ident, Token}; +use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Ident, Token}; #[proc_macro_derive(PassInfo)] pub fn derive_pass_info(item: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -190,17 +189,25 @@ pub fn derive_conversion_pass_registration( } syn::GenericParam::Type(ref ty) => { if !ty.bounds.empty_or_trailing() { - return syn::Error::new(gp.span(), "cannot derive ConversionPassRegistration on a generic type with type bounds") - .into_compile_error() - .into(); + return syn::Error::new( + gp.span(), + "cannot derive ConversionPassRegistration on a generic type with type \ + bounds", + ) + .into_compile_error() + .into(); } let param_ty: syn::Type = syn::parse_quote_spanned! { ty.span() => () }; params.push(syn::GenericArgument::Type(param_ty)); } syn::GenericParam::Const(_) => { - return syn::Error::new(gp.span(), "cannot derive ConversionPassRegistration on a generic type with const arguments") - .into_compile_error() - .into(); + return syn::Error::new( + gp.span(), + "cannot derive ConversionPassRegistration on a generic type with const \ + arguments", + ) + .into_compile_error() + .into(); } } } diff --git a/hir-symbol/build.rs b/hir-symbol/build.rs index 38bfbd484..96d42798a 100644 --- a/hir-symbol/build.rs +++ b/hir-symbol/build.rs @@ -2,12 +2,14 @@ extern crate inflector; extern crate rustc_hash; extern crate toml; -use std::cmp::Ordering; -use std::collections::BTreeSet; -use std::env; -use std::fs::{self, File}; -use std::io::prelude::*; -use std::path::{Path, PathBuf}; +use std::{ + cmp::Ordering, + collections::BTreeSet, + env, + fs::{self, File}, + io::prelude::*, + path::{Path, PathBuf}, +}; use inflector::Inflector; use rustc_hash::FxHashSet; @@ -51,13 +53,8 @@ impl Symbol { fn from_value>(name: S, value: &Value) -> Self { let name = name.into(); let table = value.as_table().unwrap(); - let id = table - .get("id") - .map(|id| id.as_integer().expect("id must be an integer")); - let value = match table - .get("value") - .map(|v| v.as_str().expect("value must be a string")) - { + let id = table.get("id").map(|id| id.as_integer().expect("id must be an integer")); + let value = match table.get("value").map(|v| v.as_str().expect("value must be a string")) { None => name.clone(), Some(value) => value.to_string(), }; @@ -117,10 +114,7 @@ fn main() { let root = root.as_table().unwrap(); let mut sections = vec![]; for (name, value) in root.iter() { - sections.push(Section::from_table( - name.to_string(), - value.as_table().unwrap(), - )); + sections.push(Section::from_table(name.to_string(), value.as_table().unwrap())); } let mut reserved = FxHashSet::default(); @@ -164,10 +158,7 @@ fn generate_symbols_rs(path: &Path, symbols: Vec) -> std::io::Result<()> let key = &symbol.key; let id = symbol.id.unwrap(); writeln!(&mut file, "#[allow(non_upper_case_globals)]")?; - writeln!( - &mut file, - "pub const {key}: crate::Symbol = crate::Symbol::new({id});" - )? + writeln!(&mut file, "pub const {key}: crate::Symbol = crate::Symbol::new({id});")? } // Symbol strings diff --git a/hir-symbol/src/lib.rs b/hir-symbol/src/lib.rs index 8befefcef..db385679c 100644 --- a/hir-symbol/src/lib.rs +++ b/hir-symbol/src/lib.rs @@ -1,10 +1,8 @@ -use core::fmt; -use core::mem; -use core::ops::Deref; -use core::str; - -use std::collections::BTreeMap; -use std::sync::{OnceLock, RwLock}; +use core::{fmt, mem, ops::Deref, str}; +use std::{ + collections::BTreeMap, + sync::{OnceLock, RwLock}, +}; static SYMBOL_TABLE: OnceLock = OnceLock::new(); @@ -94,7 +92,7 @@ impl> PartialEq for Symbol { struct SymbolIndex(u32); impl SymbolIndex { // shave off 256 indices at the end to allow space for packing these indices into enums - pub const MAX_AS_U32: u32 = 0xFFFF_FF00; + pub const MAX_AS_U32: u32 = 0xffff_ff00; #[inline] const fn new(n: u32) -> Self { diff --git a/hir-transform/src/adt/scoped_map.rs b/hir-transform/src/adt/scoped_map.rs index 2f996fb6d..fe6562b11 100644 --- a/hir-transform/src/adt/scoped_map.rs +++ b/hir-transform/src/adt/scoped_map.rs @@ -1,7 +1,4 @@ -use std::borrow::Borrow; -use std::fmt; -use std::hash::Hash; -use std::rc::Rc; +use std::{borrow::Borrow, fmt, hash::Hash, rc::Rc}; use rustc_hash::FxHashMap; @@ -40,9 +37,7 @@ where K: Borrow, Q: Hash + Eq + ?Sized, { - self.map - .get(k) - .or_else(|| self.parent.as_ref().and_then(|p| p.get(k))) + self.map.get(k).or_else(|| self.parent.as_ref().and_then(|p| p.get(k))) } pub fn insert(&mut self, k: K, v: V) { diff --git a/hir-transform/src/inline_blocks.rs b/hir-transform/src/inline_blocks.rs index 1a623c5fa..361fb4c4f 100644 --- a/hir-transform/src/inline_blocks.rs +++ b/hir-transform/src/inline_blocks.rs @@ -1,12 +1,14 @@ use std::collections::VecDeque; -use rustc_hash::FxHashSet; -use smallvec::SmallVec; - -use miden_hir::pass::{AnalysisManager, RewritePass, RewriteResult}; -use miden_hir::{self as hir, *}; +use miden_hir::{ + self as hir, + pass::{AnalysisManager, RewritePass, RewriteResult}, + *, +}; use miden_hir_analysis::ControlFlowGraph; use midenc_session::Session; +use rustc_hash::FxHashSet; +use smallvec::SmallVec; use crate::adt::ScopedMap; @@ -85,9 +87,8 @@ impl RewritePass for InlineBlocks { // If inlining can proceed, do so until we reach a point where the inlined terminator // returns from the function, has multiple successors, or branches to a block with // multiple predecessors. - while let BranchInfo::SingleDest(b, args) = function - .dfg - .analyze_branch(function.dfg.last_inst(p).unwrap()) + while let BranchInfo::SingleDest(b, args) = + function.dfg.analyze_branch(function.dfg.last_inst(p).unwrap()) { // If this successor has other predecessors, it can't be inlined, so // add it to the work list and move on @@ -104,12 +105,8 @@ impl RewritePass for InlineBlocks { // valuable as an optimization. if !args.is_empty() { // Compute the set of values to rewrite - for (from, to) in function - .dfg - .block_params(b) - .iter() - .copied() - .zip(args.iter().copied()) + for (from, to) in + function.dfg.block_params(b).iter().copied().zip(args.iter().copied()) { rewrites.insert(from, to); } @@ -183,9 +180,7 @@ fn inline( } _ => (), } - function.dfg.blocks[from] - .insts - .push_back(UnsafeRef::from_box(from_terminator)); + function.dfg.blocks[from].insts.push_back(UnsafeRef::from_box(from_terminator)); // Detach the original block from the function function.dfg.detach_block(from); // Update the control flow graph to reflect the changes @@ -362,10 +357,7 @@ mod tests { let mut function = Function::new( id, Signature::new( - [ - AbiParam::new(Type::Ptr(Box::new(Type::U8))), - AbiParam::new(Type::I32), - ], + [AbiParam::new(Type::Ptr(Box::new(Type::U8))), AbiParam::new(Type::I32)], [AbiParam::new(Type::Ptr(Box::new(Type::U8)))], ), ); @@ -395,30 +387,20 @@ mod tests { // entry let ptr1 = builder.ins().ptrtoint(ptr0, Type::U32, SourceSpan::UNKNOWN); - let is_null = builder - .ins() - .eq_imm(ptr1, Immediate::U32(0), SourceSpan::UNKNOWN); + let is_null = builder.ins().eq_imm(ptr1, Immediate::U32(0), SourceSpan::UNKNOWN); builder.ins().br(a, &[ptr1, is_null], SourceSpan::UNKNOWN); // blk0 builder.switch_to_block(a); - builder - .ins() - .cond_br(is_null1, c, &[ptr0], b, &[ptr2], SourceSpan::UNKNOWN); + builder.ins().cond_br(is_null1, c, &[ptr0], b, &[ptr2], SourceSpan::UNKNOWN); // blk1 builder.switch_to_block(b); let ptr3_i32 = builder.ins().cast(ptr3, Type::I32, SourceSpan::UNKNOWN); - let ptr4_i32 = builder - .ins() - .add_checked(ptr3_i32, offset, SourceSpan::UNKNOWN); + let ptr4_i32 = builder.ins().add_checked(ptr3_i32, offset, SourceSpan::UNKNOWN); let ptr4 = builder.ins().cast(ptr4_i32, Type::U32, SourceSpan::UNKNOWN); - let is_null2 = builder - .ins() - .eq_imm(ptr4, Immediate::U32(0), SourceSpan::UNKNOWN); - builder - .ins() - .cond_br(is_null2, e, &[ptr0], f, &[ptr4], SourceSpan::UNKNOWN); + let is_null2 = builder.ins().eq_imm(ptr4, Immediate::U32(0), SourceSpan::UNKNOWN); + builder.ins().cond_br(is_null2, e, &[ptr0], f, &[ptr4], SourceSpan::UNKNOWN); // blk2 builder.switch_to_block(c); @@ -435,9 +417,7 @@ mod tests { // blk5 builder.switch_to_block(f); let ptr6 = - builder - .ins() - .inttoptr(ptr5, Type::Ptr(Box::new(Type::U8)), SourceSpan::UNKNOWN); + builder.ins().inttoptr(ptr5, Type::Ptr(Box::new(Type::U8)), SourceSpan::UNKNOWN); builder.ins().ret(Some(ptr6), SourceSpan::UNKNOWN); } diff --git a/hir-transform/src/lib.rs b/hir-transform/src/lib.rs index b257f8fbd..1112dd9bc 100644 --- a/hir-transform/src/lib.rs +++ b/hir-transform/src/lib.rs @@ -3,6 +3,6 @@ mod inline_blocks; mod split_critical_edges; mod treeify; -pub use self::inline_blocks::InlineBlocks; -pub use self::split_critical_edges::SplitCriticalEdges; -pub use self::treeify::Treeify; +pub use self::{ + inline_blocks::InlineBlocks, split_critical_edges::SplitCriticalEdges, treeify::Treeify, +}; diff --git a/hir-transform/src/split_critical_edges.rs b/hir-transform/src/split_critical_edges.rs index 4efa9b6d4..cd0144597 100644 --- a/hir-transform/src/split_critical_edges.rs +++ b/hir-transform/src/split_critical_edges.rs @@ -1,12 +1,14 @@ use std::collections::VecDeque; -use rustc_hash::FxHashSet; -use smallvec::SmallVec; - -use miden_hir::pass::{AnalysisManager, RewritePass, RewriteResult}; -use miden_hir::{self as hir, Block as BlockId, *}; +use miden_hir::{ + self as hir, + pass::{AnalysisManager, RewritePass, RewriteResult}, + Block as BlockId, *, +}; use miden_hir_analysis::ControlFlowGraph; use midenc_session::Session; +use rustc_hash::FxHashSet; +use smallvec::SmallVec; /// This pass breaks any critical edges in the CFG of a function. /// @@ -21,9 +23,8 @@ use midenc_session::Session; /// a new block, `B`, in which we insert a branch to `S` with whatever arguments were originally /// provided in `P`, and then rewriting the branch in `P` that went to `S`, to go to `B` instead. /// -/// After this pass completes, no node in the control flow graph will have both multiple predecessors -/// and multiple successors. -/// +/// After this pass completes, no node in the control flow graph will have both multiple +/// predecessors and multiple successors. #[derive(Default, PassInfo, ModuleRewritePassAdapter)] pub struct SplitCriticalEdges; impl RewritePass for SplitCriticalEdges { @@ -187,10 +188,7 @@ mod tests { let mut function = Function::new( id, Signature::new( - [ - AbiParam::new(Type::Ptr(Box::new(Type::U8))), - AbiParam::new(Type::U32), - ], + [AbiParam::new(Type::Ptr(Box::new(Type::U8))), AbiParam::new(Type::U32)], [AbiParam::new(Type::Ptr(Box::new(Type::U8)))], ), ); @@ -218,25 +216,15 @@ mod tests { // blk0 builder.switch_to_block(a); - let is_null = builder - .ins() - .eq_imm(ptr2, Immediate::U32(0), SourceSpan::UNKNOWN); - builder - .ins() - .cond_br(is_null, c, &[ptr0], b, &[ptr2, n1], SourceSpan::UNKNOWN); + let is_null = builder.ins().eq_imm(ptr2, Immediate::U32(0), SourceSpan::UNKNOWN); + builder.ins().cond_br(is_null, c, &[ptr0], b, &[ptr2, n1], SourceSpan::UNKNOWN); // blk1 builder.switch_to_block(b); let ptr4 = builder.ins().sub_checked(ptr3, n2, SourceSpan::UNKNOWN); - let n3 = builder - .ins() - .sub_imm_checked(n2, Immediate::U32(1), SourceSpan::UNKNOWN); - let is_zero = builder - .ins() - .eq_imm(n3, Immediate::U32(0), SourceSpan::UNKNOWN); - builder - .ins() - .cond_br(is_zero, c, &[ptr4], a, &[ptr4, n3], SourceSpan::UNKNOWN); + let n3 = builder.ins().sub_imm_checked(n2, Immediate::U32(1), SourceSpan::UNKNOWN); + let is_zero = builder.ins().eq_imm(n3, Immediate::U32(0), SourceSpan::UNKNOWN); + builder.ins().cond_br(is_zero, c, &[ptr4], a, &[ptr4, n3], SourceSpan::UNKNOWN); // blk2 builder.switch_to_block(c); diff --git a/hir-transform/src/treeify.rs b/hir-transform/src/treeify.rs index b2db2d3c1..50ae0724f 100644 --- a/hir-transform/src/treeify.rs +++ b/hir-transform/src/treeify.rs @@ -1,8 +1,10 @@ -use std::collections::VecDeque; -use std::rc::Rc; +use std::{collections::VecDeque, rc::Rc}; -use miden_hir::pass::{AnalysisManager, RewritePass, RewriteResult}; -use miden_hir::{self as hir, Block as BlockId, Value as ValueId, *}; +use miden_hir::{ + self as hir, + pass::{AnalysisManager, RewritePass, RewriteResult}, + Block as BlockId, Value as ValueId, *, +}; use miden_hir_analysis::{BlockPredecessor, ControlFlowGraph, DominatorTree, LoopAnalysis}; use midenc_session::Session; use rustc_hash::FxHashSet; @@ -256,7 +258,6 @@ use crate::adt::ScopedMap; /// blk5 /// end /// ``` -/// #[derive(Default, PassInfo, ModuleRewritePassAdapter)] pub struct Treeify; impl RewritePass for Treeify { @@ -362,25 +363,13 @@ fn treeify( // block arguments of `b` to the values passed from the predecessor match function.dfg.analyze_branch(p.inst) { BranchInfo::SingleDest(_, args) => { - value_map.extend( - function - .dfg - .block_args(b) - .iter() - .copied() - .zip(args.iter().copied()), - ); + value_map.extend(function.dfg.block_args(b).iter().copied().zip(args.iter().copied())); } BranchInfo::MultiDest(ref jts) => { for jt in jts.iter() { if jt.destination == b { value_map.extend( - function - .dfg - .block_args(b) - .iter() - .copied() - .zip(jt.args.iter().copied()), + function.dfg.block_args(b).iter().copied().zip(jt.args.iter().copied()), ); break; } @@ -395,13 +384,11 @@ fn treeify( dest_args.clear(pool); } }); - // 4. Copy contents of `b` to `b'`, inserting defs in the lookup table, and mapping operands - // to their new "corrected" values + // 4. Copy contents of `b` to `b'`, inserting defs in the lookup table, and mapping operands to + // their new "corrected" values copy_instructions(b, b_prime, function, &mut value_map, &block_map); // 5. Recursively copy all children of `b` to `b_prime` - copy_children( - b, b_prime, function, cfg, loops, block_q, value_map, block_map, - ) + copy_children(b, b_prime, function, cfg, loops, block_q, value_map, block_map) } #[allow(clippy::too_many_arguments)] @@ -429,19 +416,18 @@ fn treeify_loop( { value_map.insert(src, dest); } - // 2. Update the predecessor instruction to reference the new block, leave block arguments unchanged + // 2. Update the predecessor instruction to reference the new block, leave block arguments + // unchanged update_predecessor(function, p, |dest, _, _| { if *dest == b { *dest = b_prime; } }); - // 3. Copy contents of `b` to `b'`, inserting defs in the lookup table, and mapping operands - // to their new "corrected" values + // 3. Copy contents of `b` to `b'`, inserting defs in the lookup table, and mapping operands to + // their new "corrected" values copy_instructions(b, b_prime, function, &mut value_map, &block_map); // 4. Recursively copy all children of `b` to `b_prime` - copy_children( - b, b_prime, function, cfg, loops, block_q, value_map, block_map, - ) + copy_children(b, b_prime, function, cfg, loops, block_q, value_map, block_map) } /// Detach `root`, and all of it's reachable children, from the layout of `function` @@ -482,10 +468,7 @@ fn copy_children( block_map: ScopedMap, ) -> anyhow::Result<()> { let pred = BlockPredecessor { - inst: function - .dfg - .last_inst(b_prime) - .expect("expected non-empty block"), + inst: function.dfg.last_inst(b_prime).expect("expected non-empty block"), block: b_prime, }; let value_map = Rc::new(value_map); @@ -592,10 +575,7 @@ fn copy_instructions( } } other => { - for arg in other - .arguments_mut(&mut function.dfg.value_lists) - .iter_mut() - { + for arg in other.arguments_mut(&mut function.dfg.value_lists).iter_mut() { if let Some(arg_prime) = value_map.get(arg) { *arg = *arg_prime; } @@ -697,10 +677,7 @@ mod tests { let mut builder = ModuleBuilder::new("test"); let id = testing::sum_matrix(&mut builder, &context); let mut module = builder.build(); - let mut function = module - .cursor_mut_at(id.function) - .remove() - .expect("undefined function"); + let mut function = module.cursor_mut_at(id.function).remove().expect("undefined function"); let original = function.to_string(); let mut analyses = AnalysisManager::default(); diff --git a/hir-type/src/layout.rs b/hir-type/src/layout.rs index b4fdb2693..db249b95d 100644 --- a/hir-type/src/layout.rs +++ b/hir-type/src/layout.rs @@ -41,11 +41,14 @@ impl Type { /// Split this type into two parts: /// - /// * The first part is no more than `n` bytes in size, and may contain the type itself if it fits - /// * The second part is None if the first part is smaller than or equal in size to the requested split size - /// * The second part is Some if there is data left in the original type after the split. This part will be - /// a type that attempts to preserve, to the extent possible, the original type structure, but will fall back - /// to an array of bytes if a larger type must be split down the middle somewhere. + /// * The first part is no more than `n` bytes in size, and may contain the type itself if it + /// fits + /// * The second part is None if the first part is smaller than or equal in size to the + /// requested split size + /// * The second part is Some if there is data left in the original type after the split. This + /// part will be a type that attempts to preserve, to the extent possible, the original type + /// structure, but will fall back to an array of bytes if a larger type must be split down + /// the middle somewhere. pub fn split(self, n: usize) -> (Type, Option) { if n == 0 { return (self, None); @@ -130,7 +133,8 @@ impl Type { }; (split, Some(rest)) } else { - // The element type must be split somewhere in order to get the input type down to the requested size + // The element type must be split somewhere in order to get the input type + // down to the requested size let (partial1, partial2) = (*elem_ty).clone().split(elem_size - extra); match array_len - take { 0 => unreachable!(), @@ -197,7 +201,10 @@ impl Type { size: 0, fields: Vec::new(), }; - let mut needed: u32 = n.try_into().expect("invalid type split: number of bytes is larger than what is representable in memory"); + let mut needed: u32 = n.try_into().expect( + "invalid type split: number of bytes is larger than what is representable in \ + memory", + ); let mut current_offset = 0u32; while let Some(mut field) = fields.pop_front() { let padding = field.offset - current_offset; @@ -303,7 +310,11 @@ impl Type { field.offset = current_offset; split.fields.push(field); - debug_assert!(!fields.is_empty(), "expected struct that is the exact size of the split request to have been handled elsewhere"); + debug_assert!( + !fields.is_empty(), + "expected struct that is the exact size of the split request to have \ + been handled elsewhere" + ); remaining.repr = original_repr; remaining.size = original_size - split.size; @@ -405,7 +416,7 @@ impl Type { // 64-bit integers and floats must be element-aligned Self::I64 | Self::U64 | Self::F64 => 4, // 32-bit integers and pointers must be element-aligned - Self::I32 | Self::U32 | Self::Ptr(_) | Self::NativePtr(_, _) => 4, + Self::I32 | Self::U32 | Self::Ptr(_) | Self::NativePtr(..) => 4, // 16-bit integers can be naturally aligned Self::I16 | Self::U16 => 2, // 8-bit integers and booleans can be naturally aligned @@ -439,7 +450,7 @@ impl Type { // Raw pointers are 32-bits, the same size as the native integer width, u32 Self::Ptr(_) => 32, // Native pointers are essentially a tuple/struct, composed of three 32-bit parts - Self::NativePtr(_, _) => 96, + Self::NativePtr(..) => 96, // Packed structs have no alignment padding between fields Self::Struct(ref struct_ty) => struct_ty.size as usize * 8, // Zero-sized arrays have no size in memory @@ -609,9 +620,10 @@ alignable!(u8, u16, u32, u64, usize); #[cfg(test)] #[allow(unstable_name_collisions)] mod tests { - use crate::*; use smallvec::smallvec; + use crate::*; + #[test] fn struct_type_test() { let ptr_ty = Type::Ptr(Box::new(Type::U32)); @@ -745,10 +757,7 @@ mod tests { // Default struct let ty = Type::Struct(StructType::new([ptr_ty.clone(), Type::U8, Type::I32])); - assert_eq!( - ty.to_raw_parts(), - Some(smallvec![ptr_ty.clone(), Type::U8, Type::I32,]) - ); + assert_eq!(ty.to_raw_parts(), Some(smallvec![ptr_ty.clone(), Type::U8, Type::I32,])); // Packed struct let ty = Type::Struct(StructType::new_with_repr( @@ -759,10 +768,7 @@ mod tests { TypeRepr::packed(1), [Type::U8, Type::Array(Box::new(Type::U8), 3)], )); - assert_eq!( - ty.to_raw_parts(), - Some(smallvec![ptr_ty.clone(), partial_ty, Type::U8]) - ); + assert_eq!(ty.to_raw_parts(), Some(smallvec![ptr_ty.clone(), partial_ty, Type::U8])); } #[test] diff --git a/hir-type/src/lib.rs b/hir-type/src/lib.rs index 9d611d43e..bd01524b4 100644 --- a/hir-type/src/lib.rs +++ b/hir-type/src/lib.rs @@ -4,11 +4,11 @@ extern crate alloc; mod layout; -pub use self::layout::Alignable; - use alloc::{boxed::Box, vec::Vec}; use core::{fmt, num::NonZeroU16, str::FromStr}; +pub use self::layout::Alignable; + /// Represents the type of a value #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Type { @@ -36,11 +36,13 @@ pub enum Type { Felt, /// A pointer to a value in the default byte-addressable address space used by the IR. /// - /// Pointers of this type will be translated to an appropriate address space during code generation. + /// Pointers of this type will be translated to an appropriate address space during code + /// generation. Ptr(Box), /// A pointer to a valude in Miden's native word-addressable address space. /// - /// In the type system, we represent the type of the pointee, as well as an address space identifier. + /// In the type system, we represent the type of the pointee, as well as an address space + /// identifier. /// /// This pointer type is represented on Miden's operand stack as a u64 value, consisting of /// two 32-bit elements, the most-significant bits being on top of the stack: @@ -49,16 +51,19 @@ pub enum Type { /// * The least-significant 2 bits represent a zero-based element index (range is 0-3) /// * The next most significant 4 bits represent a zero-based byte index (range is 0-31) /// * The remaining 26 bits represent an address space identifier - /// 2. The lower 32-bit limb contains the word-aligned address, which forms the base address of the pointer. + /// 2. The lower 32-bit limb contains the word-aligned address, which forms the base address of + /// the pointer. /// - /// Dereferencing a pointer of this type involves popping the pointer metadata, and determining what type - /// of load to issue based on the size of the value being loaded, and where the start of the data is - /// according to the metadata. Then the word-aligned address is popped and the value is loaded. + /// Dereferencing a pointer of this type involves popping the pointer metadata, and determining + /// what type of load to issue based on the size of the value being loaded, and where the + /// start of the data is according to the metadata. Then the word-aligned address is popped + /// and the value is loaded. /// - /// If the load is naturally aligned, i.e. the element index and byte offset are zero, and the size is exactly - /// one element or word; then a mem_load or mem_loadw are issued and no further action is required. If the load - /// is not naturally aligned, then either one or two words will be loaded, depending on the type being loaded, - /// unused elements will be dropped, and if the byte offset is non-zero, the data will be shifted bitwise into + /// If the load is naturally aligned, i.e. the element index and byte offset are zero, and the + /// size is exactly one element or word; then a mem_load or mem_loadw are issued and no + /// further action is required. If the load is not naturally aligned, then either one or + /// two words will be loaded, depending on the type being loaded, unused elements will be + /// dropped, and if the byte offset is non-zero, the data will be shifted bitwise into /// alignment on an element boundary. NativePtr(Box, AddressSpace), /// A compound type of fixed shape and size @@ -95,7 +100,7 @@ impl Type { | Self::F64 | Self::Felt | Self::Ptr(_) - | Self::NativePtr(_, _) => false, + | Self::NativePtr(..) => false, } } @@ -139,22 +144,17 @@ impl Type { } pub fn is_signed_integer(&self) -> bool { - matches!( - self, - Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::I128 - ) + matches!(self, Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::I128) } pub fn is_unsigned_integer(&self) -> bool { - matches!( - self, - Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::U128 - ) + matches!(self, Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::U128) } /// Get this type as its unsigned integral twin, e.g. i32 becomes u32. /// - /// This function will panic if the type is not an integer type, or has no unsigned representation + /// This function will panic if the type is not an integer type, or has no unsigned + /// representation pub fn as_unsigned(&self) -> Type { match self { Self::I8 | Self::U8 => Self::U8, @@ -211,24 +211,24 @@ impl Type { matches!(self, Self::Array(_, _)) } - /// Returns true if `self` and `other` are compatible operand types for a binary operator, e.g. `add` + /// Returns true if `self` and `other` are compatible operand types for a binary operator, e.g. + /// `add` /// /// In short, the rules are as follows: /// - /// * The operand order is assumed to be `self other`, i.e. `op` is being applied - /// to `self` using `other`. The left-hand operand is used as the "controlling" type - /// for the operator, i.e. it determines what instruction will be used to perform the - /// operation. + /// * The operand order is assumed to be `self other`, i.e. `op` is being applied to `self` + /// using `other`. The left-hand operand is used as the "controlling" type for the operator, + /// i.e. it determines what instruction will be used to perform the operation. /// * The operand types must be numeric, or support being manipulated numerically - /// * If the controlling type is unsigned, it is never compatible with signed types, because Miden - /// instructions for unsigned types use a simple unsigned binary encoding, thus they will not handle - /// signed operands using two's complement correctly. - /// * If the controlling type is signed, it is compatible with both signed and unsigned types, as long - /// as the values fit in the range of the controlling type, e.g. adding a `u16` to an `i32` is fine, - /// but adding a `u32` to an `i32` is not. - /// * Pointer types are permitted to be the controlling type, and since they are represented using u32, - /// they have the same compatibility set as u32 does. In all other cases, pointer types are treated - /// the same as any other non-numeric type. + /// * If the controlling type is unsigned, it is never compatible with signed types, because + /// Miden instructions for unsigned types use a simple unsigned binary encoding, thus they + /// will not handle signed operands using two's complement correctly. + /// * If the controlling type is signed, it is compatible with both signed and unsigned types, + /// as long as the values fit in the range of the controlling type, e.g. adding a `u16` to an + /// `i32` is fine, but adding a `u32` to an `i32` is not. + /// * Pointer types are permitted to be the controlling type, and since they are represented + /// using u32, they have the same compatibility set as u32 does. In all other cases, pointer + /// types are treated the same as any other non-numeric type. /// * Non-numeric types are always incompatible, since no operators support these types pub fn is_compatible_operand(&self, other: &Type) -> bool { match (self, other) { @@ -271,7 +271,7 @@ impl Type { (Type::U128, Type::U8 | Type::U16 | Type::U32 | Type::U64 | Type::U128) => true, (Type::U256, rty) => rty.is_integer(), (Type::F64, Type::F64) => true, - (Type::Ptr(_) | Type::NativePtr(_, _), Type::U8 | Type::U16 | Type::U32) => true, + (Type::Ptr(_) | Type::NativePtr(..), Type::U8 | Type::U16 | Type::U32) => true, _ => false, } } @@ -420,7 +420,8 @@ pub struct StructType { pub(crate) fields: Vec, } impl StructType { - /// Create a new struct with default representation, i.e. a struct with representation of `TypeRepr::Packed(1)`. + /// Create a new struct with default representation, i.e. a struct with representation of + /// `TypeRepr::Packed(1)`. #[inline] pub fn new>(fields: I) -> Self { Self::new_with_repr(TypeRepr::Default, fields) @@ -436,9 +437,8 @@ impl StructType { TypeRepr::Transparent => { let mut offset = 0u32; for (index, ty) in tys.into_iter().enumerate() { - let index: u8 = index - .try_into() - .expect("invalid struct: expected no more than 255 fields"); + let index: u8 = + index.try_into().expect("invalid struct: expected no more than 255 fields"); let field_size: u32 = ty .size_in_bytes() .try_into() @@ -451,8 +451,15 @@ impl StructType { ty, }); } else { - let align = ty.min_alignment().try_into().expect("invalid struct field alignment: expected power of two between 1 and 2^16"); - assert_eq!(offset, 0, "invalid transparent representation for struct: repr(transparent) is only valid for structs with a single non-zero sized field"); + let align = ty.min_alignment().try_into().expect( + "invalid struct field alignment: expected power of two between 1 and \ + 2^16", + ); + assert_eq!( + offset, 0, + "invalid transparent representation for struct: repr(transparent) is \ + only valid for structs with a single non-zero sized field" + ); fields.push(StructField { index, align, @@ -466,13 +473,8 @@ impl StructType { } repr => { let mut offset = 0u32; - let default_align: u16 = tys - .iter() - .map(|t| t.min_alignment()) - .max() - .unwrap_or(1) - .try_into() - .expect( + let default_align: u16 = + tys.iter().map(|t| t.min_alignment()).max().unwrap_or(1).try_into().expect( "invalid struct field alignment: expected power of two between 1 and 2^16", ); let align = match repr { @@ -482,9 +484,8 @@ impl StructType { }; for (index, ty) in tys.into_iter().enumerate() { - let index: u8 = index - .try_into() - .expect("invalid struct: expected no more than 255 fields"); + let index: u8 = + index.try_into().expect("invalid struct: expected no more than 255 fields"); let field_size: u32 = ty .size_in_bytes() .try_into() @@ -519,13 +520,9 @@ impl StructType { /// Get the minimum alignment for this struct pub fn min_alignment(&self) -> usize { - self.repr.min_alignment().unwrap_or_else(|| { - self.fields - .iter() - .map(|f| f.align as usize) - .max() - .unwrap_or(1) - }) + self.repr + .min_alignment() + .unwrap_or_else(|| self.fields.iter().map(|f| f.align as usize).max().unwrap_or(1)) } /// Get the total size in bytes required to hold this struct, including alignment padding diff --git a/hir/src/adt/mod.rs b/hir/src/adt/mod.rs index e5d9caa83..9aeca6bfb 100644 --- a/hir/src/adt/mod.rs +++ b/hir/src/adt/mod.rs @@ -3,7 +3,9 @@ pub mod smallordset; pub mod smallset; pub mod sparsemap; -pub use self::smallmap::SmallMap; -pub use self::smallordset::SmallOrdSet; -pub use self::smallset::SmallSet; -pub use self::sparsemap::{SparseMap, SparseMapValue}; +pub use self::{ + smallmap::SmallMap, + smallordset::SmallOrdSet, + smallset::SmallSet, + sparsemap::{SparseMap, SparseMapValue}, +}; diff --git a/hir/src/adt/smallmap.rs b/hir/src/adt/smallmap.rs index dd07efe3c..d3f4afff6 100644 --- a/hir/src/adt/smallmap.rs +++ b/hir/src/adt/smallmap.rs @@ -1,7 +1,9 @@ -use core::borrow::Borrow; -use core::cmp::Ordering; -use core::fmt; -use core::ops::{Index, IndexMut}; +use core::{ + borrow::Borrow, + cmp::Ordering, + fmt, + ops::{Index, IndexMut}, +}; use smallvec::SmallVec; @@ -48,9 +50,7 @@ where /// Return an iterator over mutable key/value pairs in this map pub fn iter_mut(&mut self) -> impl DoubleEndedIterator { - self.items - .iter_mut() - .map(|pair| (&pair.key, &mut pair.value)) + self.items.iter_mut().map(|pair| (&pair.key, &mut pair.value)) } /// Returns true if `key` has been inserted in this map @@ -130,8 +130,7 @@ where K: Borrow, Q: Ord + ?Sized, { - self.items - .binary_search_by(|probe| Ord::cmp(probe.key.borrow(), item)) + self.items.binary_search_by(|probe| Ord::cmp(probe.key.borrow(), item)) } } impl Default for SmallMap { @@ -276,6 +275,7 @@ impl DoubleEndedIterator for SmallMapIntoIter { fn next_back(&mut self) -> Option { self.iter.next_back().map(|pair| (pair.key, pair.value)) } + #[inline] fn nth_back(&mut self, n: usize) -> Option { self.iter.nth_back(n).map(|pair| (pair.key, pair.value)) diff --git a/hir/src/adt/smallordset.rs b/hir/src/adt/smallordset.rs index 2c95ca99e..1ab27c013 100644 --- a/hir/src/adt/smallordset.rs +++ b/hir/src/adt/smallordset.rs @@ -1,5 +1,4 @@ -use core::borrow::Borrow; -use core::fmt; +use core::{borrow::Borrow, fmt}; use smallvec::SmallVec; @@ -180,8 +179,7 @@ where T: Borrow, Q: Ord + ?Sized, { - self.items - .binary_search_by(|probe| Ord::cmp(probe.borrow(), item)) + self.items.binary_search_by(|probe| Ord::cmp(probe.borrow(), item)) } fn sort_and_dedup(&mut self) { diff --git a/hir/src/adt/smallset.rs b/hir/src/adt/smallset.rs index d0692ef2f..df12cfa25 100644 --- a/hir/src/adt/smallset.rs +++ b/hir/src/adt/smallset.rs @@ -1,5 +1,4 @@ -use core::borrow::Borrow; -use core::fmt; +use core::{borrow::Borrow, fmt}; use smallvec::SmallVec; diff --git a/hir/src/adt/sparsemap.rs b/hir/src/adt/sparsemap.rs index e0e56c882..c5f7ca961 100644 --- a/hir/src/adt/sparsemap.rs +++ b/hir/src/adt/sparsemap.rs @@ -50,14 +50,13 @@ impl SparseMapValue for crate::Block { /// maps that can be replaced with a `SparseMap`. /// /// - A secondary entity map assigns a default mapping to all keys. It doesn't distinguish between -/// an unmapped key and one that maps to the default value. `SparseMap` does not require -/// `Default` values, and it tracks accurately if a key has been mapped or not. +/// an unmapped key and one that maps to the default value. `SparseMap` does not require `Default` +/// values, and it tracks accurately if a key has been mapped or not. /// - Iterating over the contents of an `SecondaryMap` is linear in the size of the *key space*, -/// while iterating over a `SparseMap` is linear in the number of elements in the mapping. This -/// is an advantage precisely when the mapping is sparse. -/// - `SparseMap::clear()` is constant time and super-fast. `SecondaryMap::clear()` is linear in -/// the size of the key space. (Or, rather the required `resize()` call following the `clear()` -/// is). +/// while iterating over a `SparseMap` is linear in the number of elements in the mapping. This is +/// an advantage precisely when the mapping is sparse. +/// - `SparseMap::clear()` is constant time and super-fast. `SecondaryMap::clear()` is linear in the +/// size of the key space. (Or, rather the required `resize()` call following the `clear()` is). /// - `SparseMap` requires the values to implement `SparseMapValue` which means that they must /// contain their own key. pub struct SparseMap @@ -86,9 +85,7 @@ where V: SparseMapValue + core::fmt::Debug, { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.debug_map() - .entries(self.values().map(|v| (v.key(), v))) - .finish() + f.debug_map().entries(self.values().map(|v| (v.key(), v))).finish() } } impl SparseMap @@ -232,8 +229,8 @@ where K: EntityRef, V: SparseMapValue, { - type Item = &'a V; type IntoIter = core::slice::Iter<'a, V>; + type Item = &'a V; fn into_iter(self) -> Self::IntoIter { self.values() diff --git a/hir/src/asm/builder.rs b/hir/src/asm/builder.rs index 1d3d71e5f..0442c0ec7 100644 --- a/hir/src/asm/builder.rs +++ b/hir/src/asm/builder.rs @@ -1,10 +1,9 @@ -use crate::{ - CallConv, Felt, FunctionIdent, Inst, InstBuilder, Instruction, Overflow, SourceSpan, Value, -}; - use smallvec::smallvec; use super::*; +use crate::{ + CallConv, Felt, FunctionIdent, Inst, InstBuilder, Instruction, Overflow, SourceSpan, Value, +}; /// Used to construct an [InlineAsm] instruction, while checking the input/output types, /// and enforcing various safety invariants. @@ -30,15 +29,18 @@ pub struct MasmBuilder { stack: OperandStack, } impl<'f, B: InstBuilder<'f>> MasmBuilder { - /// Construct a new inline assembly builder in the function represented by `dfg`, to be inserted at `ip`. + /// Construct a new inline assembly builder in the function represented by `dfg`, to be inserted + /// at `ip`. /// - /// The `args` list represents the arguments which will be visible on the operand stack in this inline assembly block. + /// The `args` list represents the arguments which will be visible on the operand stack in this + /// inline assembly block. /// - /// The `results` set represents the types that are expected to be found on the operand stack when the inline - /// assembly block finishes executing. Use an empty set to represent no results. + /// The `results` set represents the types that are expected to be found on the operand stack + /// when the inline assembly block finishes executing. Use an empty set to represent no + /// results. /// - /// Any attempt to modify the operand stack beyond what is made visible via arguments, or introduced within the - /// inline assembly block, will cause an assertion to fail. + /// Any attempt to modify the operand stack beyond what is made visible via arguments, or + /// introduced within the inline assembly block, will cause an assertion to fail. pub fn new(mut builder: B, args: &[Value], results: Vec, span: SourceSpan) -> Self { // Construct the initial operand stack with the given arguments let mut stack = OperandStack::::default(); @@ -90,13 +92,19 @@ impl<'f, B: InstBuilder<'f>> MasmBuilder { } } - /// Finalize this inline assembly block, inserting it into the `Function` from which this builder was derived. + /// Finalize this inline assembly block, inserting it into the `Function` from which this + /// builder was derived. /// - /// Returns the [Inst] which corresponds to the inline assembly instruction, and the inner [DataFlowGraph] reference - /// held by the underlying [InstBuilderBase]. + /// Returns the [Inst] which corresponds to the inline assembly instruction, and the inner + /// [DataFlowGraph] reference held by the underlying [InstBuilderBase]. pub fn build(self) -> Inst { if self.asm.results.is_empty() { - assert!(self.stack.is_empty(), "invalid inline assembly: expected operand stack to be empty upon exit, found: {:?}", self.stack.debug()); + assert!( + self.stack.is_empty(), + "invalid inline assembly: expected operand stack to be empty upon exit, found: \ + {:?}", + self.stack.debug() + ); } else { let mut len = 0; for ty in self.asm.results.iter() { @@ -262,33 +270,25 @@ impl<'a> MasmOpBuilder<'a> { /// Pops the top element on the stack, and traps if that element is != 1. pub fn assert(mut self, error_code: Option) { - let op = error_code - .map(MasmOp::AssertWithError) - .unwrap_or(MasmOp::Assert); + let op = error_code.map(MasmOp::AssertWithError).unwrap_or(MasmOp::Assert); self.build(self.ip, op); } /// Pops the top element on the stack, and traps if that element is != 0. pub fn assertz(mut self, error_code: Option) { - let op = error_code - .map(MasmOp::AssertzWithError) - .unwrap_or(MasmOp::Assertz); + let op = error_code.map(MasmOp::AssertzWithError).unwrap_or(MasmOp::Assertz); self.build(self.ip, op); } /// Pops the top two elements on the stack, and traps if they are not equal. pub fn assert_eq(mut self, error_code: Option) { - let op = error_code - .map(MasmOp::AssertEqWithError) - .unwrap_or(MasmOp::AssertEq); + let op = error_code.map(MasmOp::AssertEqWithError).unwrap_or(MasmOp::AssertEq); self.build(self.ip, op); } /// Pops the top two words on the stack, and traps if they are not equal. pub fn assert_eqw(mut self, error_code: Option) { - let op = error_code - .map(MasmOp::AssertEqwWithError) - .unwrap_or(MasmOp::AssertEqw); + let op = error_code.map(MasmOp::AssertEqwWithError).unwrap_or(MasmOp::AssertEqw); self.build(self.ip, op); } @@ -311,15 +311,12 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::MemLoadOffset); } - /// Loads the element of the word at the given address and element offset to the top of the stack. + /// Loads the element of the word at the given address and element offset to the top of the + /// stack. /// /// NOTE: This is an experimental instruction which is not implemented in Miden VM yet. pub fn load_offset_imm(mut self, addr: u32, offset: u8) { - assert!( - offset < 4, - "invalid element offset, must be in the range 0..=3, got {}", - offset - ); + assert!(offset < 4, "invalid element offset, must be in the range 0..=3, got {}", offset); self.build(self.ip, MasmOp::MemLoadOffsetImm(addr, offset)); } @@ -356,11 +353,7 @@ impl<'a> MasmOpBuilder<'a> { /// Pops an element from the top of the stack, and stores it at the given offset of /// the word at the given address. pub fn store_offset_imm(mut self, addr: u32, offset: u8) { - assert!( - offset < 4, - "invalid element offset, must be in the range 0..=3, got {}", - offset - ); + assert!(offset < 4, "invalid element offset, must be in the range 0..=3, got {}", offset); self.build(self.ip, MasmOp::MemStoreOffsetImm(addr, offset)); } @@ -390,11 +383,7 @@ impl<'a> MasmOpBuilder<'a> { /// fails, the builder will panic. pub fn if_true(self) -> IfTrueBuilder<'a> { let cond = self.stack.pop().expect("operand stack is empty"); - assert_eq!( - cond, - Type::I1, - "expected while.true condition to be a boolean value" - ); + assert_eq!(cond, Type::I1, "expected while.true condition to be a boolean value"); let out_stack = self.stack.clone(); IfTrueBuilder { dfg: self.dfg, @@ -429,11 +418,7 @@ impl<'a> MasmOpBuilder<'a> { /// Both of these are validated by [LoopBuilder], and a panic is raised if validation fails. pub fn while_true(self) -> LoopBuilder<'a> { let cond = self.stack.pop().expect("operand stack is empty"); - assert_eq!( - cond, - Type::I1, - "expected while.true condition to be a boolean value" - ); + assert_eq!(cond, Type::I1, "expected while.true condition to be a boolean value"); let out_stack = self.stack.clone(); let body = self.asm.create_block(); LoopBuilder { @@ -449,14 +434,12 @@ impl<'a> MasmOpBuilder<'a> { /// Begins construction of a `repeat` loop, with an iteration count of `n`. /// - /// A `repeat` instruction requires no operands on the stack, and will execute the loop body `n` times. + /// A `repeat` instruction requires no operands on the stack, and will execute the loop body `n` + /// times. /// /// NOTE: The iteration count must be non-zero, or this function will panic. pub fn repeat(self, n: u8) -> LoopBuilder<'a> { - assert!( - n > 0, - "invalid iteration count for `repeat.n`, must be non-zero" - ); + assert!(n > 0, "invalid iteration count for `repeat.n`, must be non-zero"); let out_stack = self.stack.clone(); let body = self.asm.create_block(); LoopBuilder { @@ -497,17 +480,20 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::Add); } - /// Pops a field element from the stack, adds the given value to it, and places the result on the stack. + /// Pops a field element from the stack, adds the given value to it, and places the result on + /// the stack. pub fn add_imm(mut self, imm: Felt) { self.build(self.ip, MasmOp::AddImm(imm)); } - /// Pops two field elements from the stack, subtracts the second from the first, and places the result on the stack. + /// Pops two field elements from the stack, subtracts the second from the first, and places the + /// result on the stack. pub fn sub(mut self) { self.build(self.ip, MasmOp::Sub); } - /// Pops a field element from the stack, subtracts the given value from it, and places the result on the stack. + /// Pops a field element from the stack, subtracts the given value from it, and places the + /// result on the stack. pub fn sub_imm(mut self, imm: Felt) { self.build(self.ip, MasmOp::SubImm(imm)); } @@ -517,17 +503,20 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::Mul); } - /// Pops a field element from the stack, multiplies it by the given value, and places the result on the stack. + /// Pops a field element from the stack, multiplies it by the given value, and places the result + /// on the stack. pub fn mul_imm(mut self, imm: Felt) { self.build(self.ip, MasmOp::MulImm(imm)); } - /// Pops two field elements from the stack, divides the first by the second, and places the result on the stack. + /// Pops two field elements from the stack, divides the first by the second, and places the + /// result on the stack. pub fn div(mut self) { self.build(self.ip, MasmOp::Div); } - /// Pops a field element from the stack, divides it by the given value, and places the result on the stack. + /// Pops a field element from the stack, divides it by the given value, and places the result on + /// the stack. pub fn div_imm(mut self, imm: Felt) { self.build(self.ip, MasmOp::DivImm(imm)); } @@ -537,7 +526,8 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::Neg); } - /// Replaces the field element on top of the stack with it's multiplicative inverse, i.e. `a^-1 mod p` + /// Replaces the field element on top of the stack with it's multiplicative inverse, i.e. `a^-1 + /// mod p` pub fn inv(mut self) { self.build(self.ip, MasmOp::Inv); } @@ -547,29 +537,32 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::Incr); } - /// Pops an element, `a`, from the top of the stack, and places the result of `2^a` on the stack. + /// Pops an element, `a`, from the top of the stack, and places the result of `2^a` on the + /// stack. /// /// Traps if `a` is not in the range 0..=63 pub fn pow2(mut self) { self.build(self.ip, MasmOp::Pow2); } - /// Pops two elements from the stack, `b` and `a` respectively, and places the result of `a^b` on the stack. + /// Pops two elements from the stack, `b` and `a` respectively, and places the result of `a^b` + /// on the stack. /// /// Traps if `b` is not in the range 0..=63 pub fn exp(mut self) { self.build(self.ip, MasmOp::Exp); } - /// Pops an element from the stack, `a`, and places the result of `a^b` on the stack, where `b` is - /// the given immediate value. + /// Pops an element from the stack, `a`, and places the result of `a^b` on the stack, where `b` + /// is the given immediate value. /// /// Traps if `b` is not in the range 0..=63 pub fn exp_imm(mut self, exponent: u8) { self.build(self.ip, MasmOp::ExpImm(exponent)); } - /// Pops a value off the stack, and applies logical NOT, and places the result back on the stack. + /// Pops a value off the stack, and applies logical NOT, and places the result back on the + /// stack. /// /// Traps if the value is not 0 or 1. pub fn not(mut self) { @@ -583,7 +576,8 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::And); } - /// Pops a value off the stack, applies logical AND with the given immediate, and places the result back on the stack. + /// Pops a value off the stack, applies logical AND with the given immediate, and places the + /// result back on the stack. /// /// Traps if the value is not 0 or 1. pub fn and_imm(mut self, imm: bool) { @@ -597,7 +591,8 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::Or); } - /// Pops a value off the stack, applies logical OR with the given immediate, and places the result back on the stack. + /// Pops a value off the stack, applies logical OR with the given immediate, and places the + /// result back on the stack. /// /// Traps if the value is not 0 or 1. pub fn or_imm(mut self, imm: bool) { @@ -611,7 +606,8 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::Xor); } - /// Pops a value off the stack, applies logical XOR with the given immediate, and places the result back on the stack. + /// Pops a value off the stack, applies logical XOR with the given immediate, and places the + /// result back on the stack. /// /// Traps if the value is not 0 or 1. pub fn xor_imm(mut self, imm: bool) { @@ -623,7 +619,8 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::Eq); } - /// Pops an element off the stack, and pushes 1 on the stack if that value and the given immediate are equal, else 0. + /// Pops an element off the stack, and pushes 1 on the stack if that value and the given + /// immediate are equal, else 0. pub fn eq_imm(mut self, imm: Felt) { self.build(self.ip, MasmOp::EqImm(imm)); } @@ -638,52 +635,62 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::Neq); } - /// Pops an element off the stack, and pushes 1 on the stack if that value and the given immediate are not equal, else 0. + /// Pops an element off the stack, and pushes 1 on the stack if that value and the given + /// immediate are not equal, else 0. pub fn neq_imm(mut self, imm: Felt) { self.build(self.ip, MasmOp::NeqImm(imm)); } - /// Pops two elements off the stack, and pushes 1 on the stack if the first is greater than the second, else 0. + /// Pops two elements off the stack, and pushes 1 on the stack if the first is greater than the + /// second, else 0. pub fn gt(mut self) { self.build(self.ip, MasmOp::Gt); } - /// Pops an element off the stack, and pushes 1 on the stack if that value is greater than the given immediate, else 0. + /// Pops an element off the stack, and pushes 1 on the stack if that value is greater than the + /// given immediate, else 0. pub fn gt_imm(mut self, imm: Felt) { self.build(self.ip, MasmOp::GtImm(imm)); } - /// Pops two elements off the stack, and pushes 1 on the stack if the first is greater than or equal to the second, else 0. + /// Pops two elements off the stack, and pushes 1 on the stack if the first is greater than or + /// equal to the second, else 0. pub fn gte(mut self) { self.build(self.ip, MasmOp::Gte); } - /// Pops an element off the stack, and pushes 1 on the stack if that value is greater than or equal to the given immediate, else 0. + /// Pops an element off the stack, and pushes 1 on the stack if that value is greater than or + /// equal to the given immediate, else 0. pub fn gte_imm(mut self, imm: Felt) { self.build(self.ip, MasmOp::GteImm(imm)); } - /// Pops two elements off the stack, and pushes 1 on the stack if the first is less than the second, else 0. + /// Pops two elements off the stack, and pushes 1 on the stack if the first is less than the + /// second, else 0. pub fn lt(mut self) { self.build(self.ip, MasmOp::Lt); } - /// Pops an element off the stack, and pushes 1 on the stack if that value is less than the given immediate, else 0. + /// Pops an element off the stack, and pushes 1 on the stack if that value is less than the + /// given immediate, else 0. pub fn lt_imm(mut self, imm: Felt) { self.build(self.ip, MasmOp::LtImm(imm)); } - /// Pops two elements off the stack, and pushes 1 on the stack if the first is less than or equal to the second, else 0. + /// Pops two elements off the stack, and pushes 1 on the stack if the first is less than or + /// equal to the second, else 0. pub fn lte(mut self) { self.build(self.ip, MasmOp::Lte); } - /// Pops an element off the stack, and pushes 1 on the stack if that value is less than or equal to the given immediate, else 0. + /// Pops an element off the stack, and pushes 1 on the stack if that value is less than or equal + /// to the given immediate, else 0. pub fn lte_imm(mut self, imm: Felt) { self.build(self.ip, MasmOp::LteImm(imm)); } - /// Pops an element off the stack, and pushes 1 on the stack if that value is an odd number, else 0. + /// Pops an element off the stack, and pushes 1 on the stack if that value is an odd number, + /// else 0. pub fn is_odd(mut self) { self.build(self.ip, MasmOp::IsOdd); } @@ -703,32 +710,28 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::U32Test); } - /// Pushes 1 on the stack if every element of the word on top of the stack is less than 2^32, else 0. + /// Pushes 1 on the stack if every element of the word on top of the stack is less than 2^32, + /// else 0. pub fn testw_u32(mut self) { self.build(self.ip, MasmOp::U32Testw); } /// Traps if the element on top of the stack is greater than or equal to 2^32 pub fn assert_u32(mut self, error_code: Option) { - let op = error_code - .map(MasmOp::U32AssertWithError) - .unwrap_or(MasmOp::U32Assert); + let op = error_code.map(MasmOp::U32AssertWithError).unwrap_or(MasmOp::U32Assert); self.build(self.ip, op); } - /// Traps if either of the first two elements on top of the stack are greater than or equal to 2^32 + /// Traps if either of the first two elements on top of the stack are greater than or equal to + /// 2^32 pub fn assert2_u32(mut self, error_code: Option) { - let op = error_code - .map(MasmOp::U32Assert2WithError) - .unwrap_or(MasmOp::U32Assert2); + let op = error_code.map(MasmOp::U32Assert2WithError).unwrap_or(MasmOp::U32Assert2); self.build(self.ip, op); } /// Traps if any element of the first word on the stack are greater than or equal to 2^32 pub fn assertw_u32(mut self, error_code: Option) { - let op = error_code - .map(MasmOp::U32AssertwWithError) - .unwrap_or(MasmOp::U32Assertw); + let op = error_code.map(MasmOp::U32AssertwWithError).unwrap_or(MasmOp::U32Assertw); self.build(self.ip, op); } @@ -737,26 +740,25 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::U32Cast); } - /// Pops an element, `a`, from the stack, and splits it into two elements, `b` and `c`, each of which are a valid u32 value. + /// Pops an element, `a`, from the stack, and splits it into two elements, `b` and `c`, each of + /// which are a valid u32 value. /// - /// The value for `b` is given by `a mod 2^32`, and the value for `c` by `a / 2^32`. They are pushed on the stack in - /// that order, i.e. `c` will be on top of the stack afterwards. + /// The value for `b` is given by `a mod 2^32`, and the value for `c` by `a / 2^32`. They are + /// pushed on the stack in that order, i.e. `c` will be on top of the stack afterwards. pub fn split_u32(mut self) { self.build(self.ip, MasmOp::U32Split); } - /// Performs unsigned addition of the top two elements on the stack, `b` and `a` respectively, which - /// are expected to be valid u32 values. + /// Performs unsigned addition of the top two elements on the stack, `b` and `a` respectively, + /// which are expected to be valid u32 values. /// /// See the [Overflow] enum for how `overflow` modifies the semantics of this instruction. pub fn add_u32(mut self, overflow: Overflow) { let op = match overflow { Overflow::Unchecked => MasmOp::Add, Overflow::Checked => { - return self.build_many( - self.ip, - [MasmOp::U32Assert2, MasmOp::Add, MasmOp::U32Assert], - ); + return self + .build_many(self.ip, [MasmOp::U32Assert2, MasmOp::Add, MasmOp::U32Assert]); } Overflow::Overflowing => MasmOp::U32OverflowingAdd, Overflow::Wrapping => MasmOp::U32WrappingAdd, @@ -772,11 +774,7 @@ impl<'a> MasmOpBuilder<'a> { Overflow::Checked => { return self.build_many( self.ip, - [ - MasmOp::U32Assert, - MasmOp::AddImm(Felt::new(imm as u64)), - MasmOp::U32Assert, - ], + [MasmOp::U32Assert, MasmOp::AddImm(Felt::new(imm as u64)), MasmOp::U32Assert], ); } Overflow::Overflowing => MasmOp::U32OverflowingAddImm(imm), @@ -786,30 +784,30 @@ impl<'a> MasmOpBuilder<'a> { } /// Pops three elements from the stack, `c`, `b`, and `a`, and computes `a + b + c` using the - /// overflowing semantics of `add_u32`. The first two elements on the stack after this instruction - /// will be a boolean indicating whether addition overflowed, and the result itself, mod 2^32. + /// overflowing semantics of `add_u32`. The first two elements on the stack after this + /// instruction will be a boolean indicating whether addition overflowed, and the result + /// itself, mod 2^32. pub fn add3_overflowing_u32(mut self) { self.build(self.ip, MasmOp::U32OverflowingAdd3); } /// Pops three elements from the stack, `c`, `b`, and `a`, and computes `a + b + c` using the - /// wrapping semantics of `add_u32`. The result will be on top of the stack afterwards, mod 2^32. + /// wrapping semantics of `add_u32`. The result will be on top of the stack afterwards, mod + /// 2^32. pub fn add3_wrapping_u32(mut self) { self.build(self.ip, MasmOp::U32WrappingAdd3); } - /// Performs unsigned subtraction of the top two elements on the stack, `b` and `a` respectively, which - /// are expected to be valid u32 values. + /// Performs unsigned subtraction of the top two elements on the stack, `b` and `a` + /// respectively, which are expected to be valid u32 values. /// /// See the [Overflow] enum for how `overflow` modifies the semantics of this instruction. pub fn sub_u32(mut self, overflow: Overflow) { let op = match overflow { Overflow::Unchecked => MasmOp::Sub, Overflow::Checked => { - return self.build_many( - self.ip, - [MasmOp::U32Assert2, MasmOp::Sub, MasmOp::U32Assert], - ); + return self + .build_many(self.ip, [MasmOp::U32Assert2, MasmOp::Sub, MasmOp::U32Assert]); } Overflow::Overflowing => MasmOp::U32OverflowingSub, Overflow::Wrapping => MasmOp::U32WrappingSub, @@ -824,11 +822,7 @@ impl<'a> MasmOpBuilder<'a> { Overflow::Checked => { return self.build_many( self.ip, - [ - MasmOp::U32Assert, - MasmOp::SubImm(Felt::new(imm as u64)), - MasmOp::U32Assert, - ], + [MasmOp::U32Assert, MasmOp::SubImm(Felt::new(imm as u64)), MasmOp::U32Assert], ); } Overflow::Overflowing => MasmOp::U32OverflowingSubImm(imm), @@ -837,18 +831,16 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, op); } - /// Performs unsigned multiplication of the top two elements on the stack, `b` and `a` respectively, which - /// are expected to be valid u32 values. + /// Performs unsigned multiplication of the top two elements on the stack, `b` and `a` + /// respectively, which are expected to be valid u32 values. /// /// See the [Overflow] enum for how `overflow` modifies the semantics of this instruction. pub fn mul_u32(mut self, overflow: Overflow) { let op = match overflow { Overflow::Unchecked => MasmOp::Mul, Overflow::Checked => { - return self.build_many( - self.ip, - [MasmOp::U32Assert2, MasmOp::Mul, MasmOp::U32Assert], - ); + return self + .build_many(self.ip, [MasmOp::U32Assert2, MasmOp::Mul, MasmOp::U32Assert]); } Overflow::Overflowing => MasmOp::U32OverflowingMul, Overflow::Wrapping => MasmOp::U32WrappingMul, @@ -863,11 +855,7 @@ impl<'a> MasmOpBuilder<'a> { Overflow::Checked => { return self.build_many( self.ip, - [ - MasmOp::U32Assert, - MasmOp::MulImm(Felt::new(imm as u64)), - MasmOp::U32Assert, - ], + [MasmOp::U32Assert, MasmOp::MulImm(Felt::new(imm as u64)), MasmOp::U32Assert], ); } Overflow::Overflowing => MasmOp::U32OverflowingMulImm(imm), @@ -876,21 +864,21 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, op); } - /// Pops three elements from the stack, `b`, `a`, and `c`, and computes `a * b + c`, using overflowing - /// semantics, i.e. the result is wrapped mod 2^32, and a flag is pushed on the stack if the result - /// overflowed the u32 range. + /// Pops three elements from the stack, `b`, `a`, and `c`, and computes `a * b + c`, using + /// overflowing semantics, i.e. the result is wrapped mod 2^32, and a flag is pushed on the + /// stack if the result overflowed the u32 range. pub fn madd_overflowing_u32(mut self) { self.build(self.ip, MasmOp::U32OverflowingMadd); } - /// Pops three elements from the stack, `b`, `a`, and `c`, and computes `a * b + c`, using wrapping - /// semantics, i.e. the result is wrapped mod 2^32. + /// Pops three elements from the stack, `b`, `a`, and `c`, and computes `a * b + c`, using + /// wrapping semantics, i.e. the result is wrapped mod 2^32. pub fn madd_wrapping_u32(mut self) { self.build(self.ip, MasmOp::U32WrappingMadd); } - /// Performs unsigned division of the top two elements on the stack, `b` and `a` respectively, which - /// are expected to be valid u32 values. + /// Performs unsigned division of the top two elements on the stack, `b` and `a` respectively, + /// which are expected to be valid u32 values. /// /// This operation is unchecked, so if either operand is >= 2^32, the result is undefined. /// @@ -918,8 +906,8 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::U32ModImm(imm)); } - /// Pops two elements off the stack, `b` and `a` respectively, and computes `a / b`, and `a mod b`, - /// pushing the results of each on the stack in that order. + /// Pops two elements off the stack, `b` and `a` respectively, and computes `a / b`, and `a mod + /// b`, pushing the results of each on the stack in that order. /// /// This operation is unchecked, so if either operand is >= 2^32, the results are undefined. /// @@ -933,36 +921,40 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::U32DivModImm(imm)); } - /// Pops two elements off the stack, and computes the bitwise AND of those values, placing the result on the stack. + /// Pops two elements off the stack, and computes the bitwise AND of those values, placing the + /// result on the stack. /// /// Traps if either element is not a valid u32 value. pub fn band_u32(mut self) { self.build(self.ip, MasmOp::U32And); } - /// Pops two elements off the stack, and computes the bitwise OR of those values, placing the result on the stack. + /// Pops two elements off the stack, and computes the bitwise OR of those values, placing the + /// result on the stack. /// /// Traps if either element is not a valid u32 value. pub fn bor_u32(mut self) { self.build(self.ip, MasmOp::U32Or); } - /// Pops two elements off the stack, and computes the bitwise XOR of those values, placing the result on the stack. + /// Pops two elements off the stack, and computes the bitwise XOR of those values, placing the + /// result on the stack. /// /// Traps if either element is not a valid u32 value. pub fn bxor_u32(mut self) { self.build(self.ip, MasmOp::U32Xor); } - /// Pops an element off the stack, and computes the bitwise NOT of that value, placing the result on the stack. + /// Pops an element off the stack, and computes the bitwise NOT of that value, placing the + /// result on the stack. /// /// Traps if the element is not a valid u32 value. pub fn bnot_u32(mut self) { self.build(self.ip, MasmOp::U32Not); } - /// Pops two elements off the stack, `b` and `a` respectively, and shifts `a` left by `b` bits. More precisely, - /// the result is computed as `(a * 2^b) mod 2^32`. + /// Pops two elements off the stack, `b` and `a` respectively, and shifts `a` left by `b` bits. + /// More precisely, the result is computed as `(a * 2^b) mod 2^32`. /// /// The result is undefined if `a` is not a valid u32, or `b` is > 31. pub fn shl_u32(mut self) { @@ -974,8 +966,8 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::U32ShlImm(imm)); } - /// Pops two elements off the stack, `b` and `a` respectively, and shifts `a` right by `b` bits. More precisely, - /// the result is computed as `a / 2^b`. + /// Pops two elements off the stack, `b` and `a` respectively, and shifts `a` right by `b` bits. + /// More precisely, the result is computed as `a / 2^b`. /// /// The result is undefined if `a` is not a valid u32, or `b` is > 31. pub fn shr_u32(mut self) { @@ -987,8 +979,8 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::U32ShrImm(imm)); } - /// Pops two elements off the stack, `b` and `a` respectively, and rotates the binary representation of `a` - /// left by `b` bits. + /// Pops two elements off the stack, `b` and `a` respectively, and rotates the binary + /// representation of `a` left by `b` bits. /// /// The result is undefined if `a` is not a valid u32, or `b` is > 31. pub fn rotl_u32(mut self) { @@ -1000,8 +992,8 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::U32RotlImm(imm)); } - /// Pops two elements off the stack, `b` and `a` respectively, and rotates the binary representation of `a` - /// right by `b` bits. + /// Pops two elements off the stack, `b` and `a` respectively, and rotates the binary + /// representation of `a` right by `b` bits. /// /// The result is undefined if `a` is not a valid u32, or `b` is > 31. pub fn rotr_u32(mut self) { @@ -1013,8 +1005,8 @@ impl<'a> MasmOpBuilder<'a> { self.build(self.ip, MasmOp::U32RotrImm(imm)); } - /// Pops an element off the stack, and computes the number of set bits in its binary representation, i.e. - /// its hamming weight, and places the result on the stack. + /// Pops an element off the stack, and computes the number of set bits in its binary + /// representation, i.e. its hamming weight, and places the result on the stack. /// /// The result is undefined if the input value is not a valid u32. pub fn popcnt_u32(mut self) { @@ -1148,10 +1140,7 @@ impl<'f> IfTrueBuilder<'f> { /// /// NOTE: This function will panic if the then block has already been built pub fn build_then<'a: 'f, 'b: 'f + 'a>(&'b mut self) -> IfTrueBlockBuilder<'a> { - assert!( - self.then_blk.is_none(), - "cannot build the 'then' branch twice" - ); + assert!(self.then_blk.is_none(), "cannot build the 'then' branch twice"); let then_blk = self.asm.create_block(); let stack = self.in_stack.clone(); IfTrueBlockBuilder { @@ -1166,10 +1155,7 @@ impl<'f> IfTrueBuilder<'f> { /// /// NOTE: This function will panic if the else block has already been built pub fn build_else<'a: 'f, 'b: 'f + 'a>(&'b mut self) -> IfTrueBlockBuilder<'a> { - assert!( - self.else_blk.is_none(), - "cannot build the 'else' branch twice" - ); + assert!(self.else_blk.is_none(), "cannot build the 'else' branch twice"); let else_blk = self.asm.create_block(); let stack = self.in_stack.clone(); IfTrueBlockBuilder { @@ -1236,7 +1222,12 @@ impl<'a> Drop for IfTrueBlockBuilder<'a> { // branch when it is constructed let is_complete = self.builder.then_blk.is_some() && self.builder.else_blk.is_some(); if is_complete { - assert_eq!(self.stack.stack(), self.builder.out_stack.stack(), "expected the operand stack to be in the same abstract state upon exit from either branch of this if.true instruction"); + assert_eq!( + self.stack.stack(), + self.builder.out_stack.stack(), + "expected the operand stack to be in the same abstract state upon exit from \ + either branch of this if.true instruction" + ); } else { core::mem::swap(&mut self.builder.out_stack, &mut self.stack); } @@ -1382,11 +1373,21 @@ impl<'f> LoopBuilder<'f> { LoopType::While => { // First, validate that the top of the stack holds a boolean let cond = self.out_stack.pop().expect("operand stack is empty"); - assert_eq!(cond, Type::I1, "expected there to be a boolean on top of the stack at the end of the while.true body"); + assert_eq!( + cond, + Type::I1, + "expected there to be a boolean on top of the stack at the end of the \ + while.true body" + ); // Next, validate that the contents of the operand stack match // the input stack, in order to ensure that the operand stack // is consistent whether the loop is taken or not - assert_eq!(self.in_stack.stack(), self.out_stack.stack(), "expected the operand stack to be in the same abstract state whether the while.true loop is taken or skipped"); + assert_eq!( + self.in_stack.stack(), + self.out_stack.stack(), + "expected the operand stack to be in the same abstract state whether the \ + while.true loop is taken or skipped" + ); self.asm.push(self.ip, MasmOp::While(self.body)); } LoopType::Repeat(1) => { @@ -1407,19 +1408,22 @@ impl<'f> LoopBuilder<'f> { } LoopType::Repeat(n) => { // Apply the stack effects of the loop body `n` times, asserting if some operation - // in the loop fails due to type mismatches. This is sufficient to validate `repeat`, - // as it's iteration count is known statically, entry into the loop is unconditional, - // and the only way to exit the loop is to complete all `n` iterations. + // in the loop fails due to type mismatches. This is sufficient to validate + // `repeat`, as it's iteration count is known statically, entry into + // the loop is unconditional, and the only way to exit the loop is + // to complete all `n` iterations. // // By validating in this way, we also implicitly validate the following: // - // 1. If we were to translate this to SSA form, the resulting control flow graph would - // have the same number and type of arguments passed to the loop header both on entry - // and along the loopback edges. + // 1. If we were to translate this to SSA form, the resulting control flow graph + // would + // have the same number and type of arguments passed to the loop header both on + // entry and along the loopback edges. // - // 2. If the body of the loop removes elements from the stack, we ensure that all `n` - // iterations can be performed without exhausting the stack, or perform any other invalid - // stack operation. + // 2. If the body of the loop removes elements from the stack, we ensure that all + // `n` + // iterations can be performed without exhausting the stack, or perform any other + // invalid stack operation. let code = &self.asm.blocks[self.body]; for _ in 1..n { for op in code.ops.iter() { @@ -1481,7 +1485,8 @@ macro_rules! assert_compatible_u32_operands { }; } -/// Asserts that the given value is an integer or pointer type which is compatible with basic felt arithmetic +/// Asserts that the given value is an integer or pointer type which is compatible with basic felt +/// arithmetic macro_rules! assert_compatible_arithmetic_operand { ($ty:ident) => { assert!( @@ -1584,14 +1589,28 @@ macro_rules! assert_compatible_felt_operands { }; } -/// Asserts that the two operands are of compatible types, where the first operand is assumed to determine the controlling type +/// Asserts that the two operands are of compatible types, where the first operand is assumed to +/// determine the controlling type macro_rules! assert_compatible_operand_types { ($lty:ident, $rty:ident) => { - assert!($lty.is_compatible_operand(&$rty), "expected operands to be compatible types, the controlling type {} is not compatible with {}", $lty, $rty); + assert!( + $lty.is_compatible_operand(&$rty), + "expected operands to be compatible types, the controlling type {} is not compatible \ + with {}", + $lty, + $rty + ); }; ($lty:ident, $rty:ident, $op:expr) => { - assert!($lty.is_compatible_operand(&$rty), "expected operands for {} to be compatible types, the controlling type {} is not compatible with {}", $op, $lty, $rty); + assert!( + $lty.is_compatible_operand(&$rty), + "expected operands for {} to be compatible types, the controlling type {} is not \ + compatible with {}", + $op, + $lty, + $rty + ); }; } @@ -1690,7 +1709,7 @@ fn apply_op_stack_effects( ); stack.push(ty.pointee().unwrap().clone()); } - MasmOp::MemLoadImm(_) | MasmOp::MemLoadOffsetImm(_, _) => { + MasmOp::MemLoadImm(_) | MasmOp::MemLoadOffsetImm(..) => { // We don't know what we're loading, so fall back to the default type of field element stack.push(Type::Felt); } @@ -1717,7 +1736,7 @@ fn apply_op_stack_effects( ); stack.drop(); } - MasmOp::MemStoreImm(_) | MasmOp::MemStoreOffsetImm(_, _) => { + MasmOp::MemStoreImm(_) | MasmOp::MemStoreOffsetImm(..) => { stack.drop(); } MasmOp::MemStorew => { @@ -1733,13 +1752,10 @@ fn apply_op_stack_effects( stack.dropw(); } MasmOp::MemStream => { - // Read two sequential words from memory starting at `a`, overwriting the first two words on the stack, - // and advancing `a` to the next address following the two that were loaded - // [C, B, A, a] <- [*a, *(a + 1), A, a + 2] - assert!( - stack.len() > 12, - "expected at least 13 elements on the stack for mem_stream" - ); + // Read two sequential words from memory starting at `a`, overwriting the first two + // words on the stack, and advancing `a` to the next address following the + // two that were loaded [C, B, A, a] <- [*a, *(a + 1), A, a + 2] + assert!(stack.len() > 12, "expected at least 13 elements on the stack for mem_stream"); stack.dropw(); stack.dropw(); stack.padw(); @@ -1751,10 +1767,7 @@ fn apply_op_stack_effects( // into memory at `a` and `a + 1` // // [C, B, A, a] <- [*a, *(a + 1), A, a + 2] - assert!( - stack.len() > 12, - "expected at least 13 elements on the stack for mem_stream" - ); + assert!(stack.len() > 12, "expected at least 13 elements on the stack for mem_stream"); stack.dropw(); stack.dropw(); stack.padw(); @@ -1766,17 +1779,14 @@ fn apply_op_stack_effects( } } MasmOp::AdvLoadw => { - assert!( - stack.len() > 3, - "expected at least 4 elements on the stack for mem_stream" - ); + assert!(stack.len() > 3, "expected at least 4 elements on the stack for mem_stream"); stack.dropw(); stack.padw(); } // This function is not called from [MasmOpBuilder] when building an `if.true` instruction, // instead, the only time we are evaluating this is when traversing the body of a `repeat.n` - // instruction and applying the stack effects of instructions which have already been inserted - // once. + // instruction and applying the stack effects of instructions which have already been + // inserted once. // // NOTE: We only apply the effects from a single branch, because we have already validated // that regardless of which branch is taken, the stack effects are the same. @@ -1788,21 +1798,21 @@ fn apply_op_stack_effects( apply_op_stack_effects(op, stack, dfg, asm); } } - // This function is not called from [MasmOpBuilder] when building an `while.true` instruction, - // instead, the only time we are evaluating this is when traversing the body of a `repeat.n` - // instruction and applying the stack effects of instructions which have already been inserted - // once. + // This function is not called from [MasmOpBuilder] when building an `while.true` + // instruction, instead, the only time we are evaluating this is when traversing the + // body of a `repeat.n` instruction and applying the stack effects of instructions + // which have already been inserted once. // - // NOTE: We don't need to traverse the body of the `while.true`, because we have already validated - // that whether the loop is taken or not, the stack effects are the same + // NOTE: We don't need to traverse the body of the `while.true`, because we have already + // validated that whether the loop is taken or not, the stack effects are the same MasmOp::While(_body) => { let lty = stack.pop().expect("operand stack is empty"); assert_eq!(lty, Type::I1, "expected boolean conditional"); } // This function is not called from [MasmOpBuilder] when building an `repeat.n` instruction, // instead, the only time we are evaluating this is when traversing the body of a `repeat.n` - // instruction and applying the stack effects of instructions which have already been inserted - // once. + // instruction and applying the stack effects of instructions which have already been + // inserted once. MasmOp::Repeat(n, body) => { let body = asm.blocks[*body].ops.as_slice(); for _ in 0..*n { @@ -1894,10 +1904,7 @@ fn apply_op_stack_effects( stack.push(Type::Felt); } MasmOp::Caller => { - assert!( - stack.len() > 3, - "expected at least 4 elements on the operand stack" - ); + assert!(stack.len() > 3, "expected at least 4 elements on the operand stack"); stack.popw(); stack.pushw([Type::Felt, Type::Felt, Type::Felt, Type::Felt]); } @@ -1906,26 +1913,17 @@ fn apply_op_stack_effects( stack.push(Type::I1); } MasmOp::U32Testw => { - assert!( - stack.len() > 3, - "expected at least 4 elements on the operand stack" - ); + assert!(stack.len() > 3, "expected at least 4 elements on the operand stack"); stack.push(Type::I1); } MasmOp::U32Assert | MasmOp::U32AssertWithError(_) => { assert!(!stack.is_empty()); } MasmOp::U32Assert2 | MasmOp::U32Assert2WithError(_) => { - assert!( - stack.len() > 1, - "expected at least 2 elements on the operand stack" - ); + assert!(stack.len() > 1, "expected at least 2 elements on the operand stack"); } MasmOp::U32Assertw | MasmOp::U32AssertwWithError(_) => { - assert!( - stack.len() > 3, - "expected at least 4 elements on the operand stack" - ); + assert!(stack.len() > 3, "expected at least 4 elements on the operand stack"); } MasmOp::U32Cast => { let lty = stack.pop().expect("operand stack is empty"); @@ -1939,12 +1937,8 @@ fn apply_op_stack_effects( stack.push(Type::U32); } MasmOp::U32Lt | MasmOp::U32Gt | MasmOp::U32Lte | MasmOp::U32Gte => { - let rty = stack - .pop() - .expect("failed to pop right operand: stack is empty"); - let lty = stack - .pop() - .expect("failed to pop left operand: stack is empty"); + let rty = stack.pop().expect("failed to pop right operand: stack is empty"); + let lty = stack.pop().expect("failed to pop left operand: stack is empty"); assert_compatible_u32_operands!(lty, rty, op); stack.push(Type::I1); } @@ -2044,9 +2038,7 @@ fn execute_call( stack: &mut OperandStack, dfg: &DataFlowGraph, ) { - let import = dfg - .get_import(id) - .expect("unknown function, are you missing an import?"); + let import = dfg.get_import(id).expect("unknown function, are you missing an import?"); if is_syscall { assert_eq!( import.signature.cc, @@ -2072,7 +2064,14 @@ fn execute_call( // Verify that we have `elements_needed` values on the operand stack let elements_available = stack.len(); - assert!(elements_needed <= elements_available, "the operand stack does not contain enough values to call {} ({} exepected vs {} available)", id, elements_needed, elements_available); + assert!( + elements_needed <= elements_available, + "the operand stack does not contain enough values to call {} ({} exepected vs {} \ + available)", + id, + elements_needed, + elements_available + ); stack.dropn(elements_needed); // Update the operand stack to reflect the results @@ -2084,9 +2083,7 @@ fn execute_call( } fn push_type_on_stack(ty: Type, stack: &mut OperandStack) { - let parts = ty - .to_raw_parts() - .expect("invalid unknown type: cannot proceed"); + let parts = ty.to_raw_parts().expect("invalid unknown type: cannot proceed"); for part in parts.into_iter().rev() { stack.push(part); } diff --git a/hir/src/asm/display.rs b/hir/src/asm/display.rs index a550edd0c..e0fb4203c 100644 --- a/hir/src/asm/display.rs +++ b/hir/src/asm/display.rs @@ -111,10 +111,7 @@ impl<'a> DisplayOp<'a> { pub fn is_local_module(&self, id: &Ident) -> bool { match self.function { Some(function) => &function.module == id, - None => self - .imports - .map(|imports| !imports.is_import(id)) - .unwrap_or(false), + None => self.imports.map(|imports| !imports.is_import(id)).unwrap_or(false), } } @@ -131,11 +128,9 @@ impl<'a> fmt::Display for DisplayOp<'a> { match self.op { MasmOp::Push(imm) => write!(f, "push.{imm}"), MasmOp::Push2([a, b]) => write!(f, "push.{a}.{b}"), - MasmOp::Pushw(word) => write!( - f, - "push.{}.{}.{}.{}", - &word[0], &word[1], &word[2], &word[3] - ), + MasmOp::Pushw(word) => { + write!(f, "push.{}.{}.{}.{}", &word[0], &word[1], &word[2], &word[3]) + } MasmOp::PushU8(imm) => write!(f, "push.{imm}"), MasmOp::PushU16(imm) => write!(f, "push.{imm}"), MasmOp::PushU32(imm) => write!(f, "push.{imm}"), diff --git a/hir/src/asm/import.rs b/hir/src/asm/import.rs index 8aa0ff5d2..540b128b4 100644 --- a/hir/src/asm/import.rs +++ b/hir/src/asm/import.rs @@ -96,9 +96,7 @@ impl ModuleImportInfo { Ident: core::borrow::Borrow, Q: Hash + Eq + ?Sized, { - self.modules - .get(module) - .map(|i| Ident::new(i.alias, i.span)) + self.modules.get(module).map(|i| Ident::new(i.alias, i.span)) } /// Given an aliased module name, get the fully-qualified identifier @@ -199,9 +197,7 @@ impl PartialOrd for MasmImport { } impl Ord for MasmImport { fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.name - .cmp(&other.name) - .then_with(|| self.alias.cmp(&other.alias)) + self.name.cmp(&other.name).then_with(|| self.alias.cmp(&other.alias)) } } impl Hash for MasmImport { diff --git a/hir/src/asm/isa.rs b/hir/src/asm/isa.rs index 704fac206..a994b5df8 100644 --- a/hir/src/asm/isa.rs +++ b/hir/src/asm/isa.rs @@ -184,7 +184,8 @@ pub enum MasmOp { AssertEqwWithError(u32), /// Places the memory address of the given local index on top of the stack LocAddr(LocalId), - /// Writes a value to the first element of the word at the address corresponding to the given local index + /// Writes a value to the first element of the word at the address corresponding to the given + /// local index LocStore(LocalId), /// Writes a word to the address corresponding to the given local index LocStorew(LocalId), @@ -195,14 +196,15 @@ pub enum MasmOp { MemLoad, /// Same as above, but the address is given as an immediate MemLoadImm(u32), - /// Pops `a`, representing a memory address + offset pair, from the top of the stack, then loads the - /// element at the given offset from the base of the word starting at that address, placing it on top - /// of the stack. + /// Pops `a`, representing a memory address + offset pair, from the top of the stack, then + /// loads the element at the given offset from the base of the word starting at that + /// address, placing it on top of the stack. /// /// Traps if `a` >= 2^32 /// - /// NOTE: This instruction doesn't actually exist in Miden Assembly yet, it is a proposed extension of - /// `MemLoad` which allows addressing all field elements of a word individually. It is here for testing. + /// NOTE: This instruction doesn't actually exist in Miden Assembly yet, it is a proposed + /// extension of `MemLoad` which allows addressing all field elements of a word + /// individually. It is here for testing. MemLoadOffset, /// Same as above, but the address and offset are given as a immediates MemLoadOffsetImm(u32, u8), @@ -221,26 +223,28 @@ pub enum MasmOp { MemStore, /// Same as above, but the address is given as an immediate MemStoreImm(u32), - /// Pops `a, v` from the stack, where `a` represents a memory address + offset pair, and `v` the value - /// to be stored, and stores `v` as the element at the given offset from the base of the word starting - /// at that address. The remaining elements of the word are not modified. + /// Pops `a, v` from the stack, where `a` represents a memory address + offset pair, and `v` + /// the value to be stored, and stores `v` as the element at the given offset from the base + /// of the word starting at that address. The remaining elements of the word are not + /// modified. /// /// Traps if `a` >= 2^32 /// - /// NOTE: This instruction doesn't actually exist in Miden Assembly yet, it is a proposed extension of - /// `MemStore` which allows addressing all field elements of a word individually. It is here for testing. + /// NOTE: This instruction doesn't actually exist in Miden Assembly yet, it is a proposed + /// extension of `MemStore` which allows addressing all field elements of a word + /// individually. It is here for testing. MemStoreOffset, /// Same as above, but the address and offset are given as a immediates MemStoreOffsetImm(u32, u8), - /// Pops `a, V` from the stack, where `a` represents a memory address, and `V` is a word to be stored - /// at that location, and overwrites the word located at `a`. + /// Pops `a, V` from the stack, where `a` represents a memory address, and `V` is a word to be + /// stored at that location, and overwrites the word located at `a`. /// /// Traps if `a` >= 2^32 MemStorew, /// Same as above, but the address is given as an immediate MemStorewImm(u32), - /// Read two sequential words from memory starting at `a`, overwriting the first two words on the stack, - /// and advancing `a` to the next address following the two that were loaded + /// Read two sequential words from memory starting at `a`, overwriting the first two words on + /// the stack, and advancing `a` to the next address following the two that were loaded /// [C, B, A, a] <- [*a, *(a + 1), A, a + 2] MemStream, /// Pops the next two words from the advice stack, overwrites the @@ -270,13 +274,16 @@ pub enum MasmOp { Repeat(u8, MasmBlockId), /// Pops `N` args off the stack, executes the procedure, results will be placed on the stack Exec(FunctionIdent), - /// Pops `N` args off the stack, executes the procedure in the root context, results will be placed on the stack + /// Pops `N` args off the stack, executes the procedure in the root context, results will be + /// placed on the stack Syscall(FunctionIdent), - /// Pops the address (MAST root hash) of a callee off the stack, and dynamically `exec` the function + /// Pops the address (MAST root hash) of a callee off the stack, and dynamically `exec` the + /// function DynExec, /// TODO DynCall, - /// Pushes the address (MAST root hash) of the given function on the stack, to be used by `dynexec` or `dyncall` + /// Pushes the address (MAST root hash) of the given function on the stack, to be used by + /// `dynexec` or `dyncall` ProcRef(FunctionIdent), /// Pops `b, a` off the stack, and places the result of `(a + b) mod p` on the stack Add, @@ -381,7 +388,8 @@ pub enum MasmOp { Clk, /// Peeks `a` from the top of the stack, and places the 1 on the stack if `a < 2^32`, else 0 U32Test, - /// Peeks `A` from the top of the stack, and places the 1 on the stack if `forall a : A, a < 2^32`, else 0 + /// Peeks `A` from the top of the stack, and places the 1 on the stack if `forall a : A, a < + /// 2^32`, else 0 U32Testw, /// Peeks `a` from the top of the stack, and traps if `a >= 2^32` U32Assert, @@ -389,11 +397,13 @@ pub enum MasmOp { U32AssertWithError(u32), /// Peeks `b, a` from the top of the stack, and traps if either `a` or `b` is >= 2^32 U32Assert2, - /// Peeks `b, a` from the top of the stack, and traps if either `a` or `b` is >= 2^32, raising the given error code + /// Peeks `b, a` from the top of the stack, and traps if either `a` or `b` is >= 2^32, raising + /// the given error code U32Assert2WithError(u32), /// Peeks `A` from the top of the stack, and traps unless `forall a : A, a < 2^32`, else 0 U32Assertw, - /// Peeks `A` from the top of the stack, and traps unless `forall a : A, a < 2^32`, else 0, raising the given error code + /// Peeks `A` from the top of the stack, and traps unless `forall a : A, a < 2^32`, else 0, + /// raising the given error code U32AssertwWithError(u32), /// Pops `a` from the top of the stack, and places the result of `a mod 2^32` on the stack /// @@ -574,9 +584,10 @@ impl MasmOp { locals: &[FunctionIdent], imported: &miden_assembly::ast::ModuleImports, ) -> SmallVec<[Self; 2]> { - use crate::{StarkField, Symbol}; use miden_assembly::ast::Instruction; + use crate::{StarkField, Symbol}; + let op = match ix { Instruction::Assert => Self::Assert, Instruction::AssertWithError(code) => Self::AssertWithError(code), @@ -994,16 +1005,16 @@ impl MasmOp { Self::MemStorew => Instruction::MemStoreW, Self::MemStorewImm(addr) => Instruction::MemStoreWImm(addr), Self::MemLoadOffset - | Self::MemLoadOffsetImm(_, _) + | Self::MemLoadOffsetImm(..) | Self::MemStoreOffset - | Self::MemStoreOffsetImm(_, _) => unimplemented!( + | Self::MemStoreOffsetImm(..) => unimplemented!( "this is an experimental instruction that is not supported by the Miden VM" ), Self::MemStream => Instruction::MemStream, Self::AdvPipe => Instruction::AdvPipe, Self::AdvPush(n) => Instruction::AdvPush(n), Self::AdvLoadw => Instruction::AdvLoadW, - Self::If(_, _) | Self::While(_) | Self::Repeat(_, _) => { + Self::If(..) | Self::While(_) | Self::Repeat(..) => { panic!("control flow instructions are meant to be handled specially by the caller") } Self::Exec(ref callee) => { @@ -1251,20 +1262,20 @@ impl fmt::Display for MasmOp { Self::MemLoad | Self::MemLoadOffset | Self::MemLoadImm(_) - | Self::MemLoadOffsetImm(_, _) => f.write_str("mem_load"), + | Self::MemLoadOffsetImm(..) => f.write_str("mem_load"), Self::MemLoadw | Self::MemLoadwImm(_) => f.write_str("mem_loadw"), Self::MemStore | Self::MemStoreOffset | Self::MemStoreImm(_) - | Self::MemStoreOffsetImm(_, _) => f.write_str("mem_store"), + | Self::MemStoreOffsetImm(..) => f.write_str("mem_store"), Self::MemStorew | Self::MemStorewImm(_) => f.write_str("mem_storew"), Self::MemStream => f.write_str("mem_stream"), Self::AdvPipe => f.write_str("adv_pipe"), Self::AdvPush(_) => f.write_str("adv_push"), Self::AdvLoadw => f.write_str("adv_loadw"), - Self::If(_, _) => f.write_str("if.true"), + Self::If(..) => f.write_str("if.true"), Self::While(_) => f.write_str("while.true"), - Self::Repeat(_, _) => f.write_str("repeat"), + Self::Repeat(..) => f.write_str("repeat"), Self::Exec(_) => f.write_str("exec"), Self::Syscall(_) => f.write_str("syscall"), Self::DynExec => f.write_str("dynexec"), diff --git a/hir/src/asm/mod.rs b/hir/src/asm/mod.rs index 6b218f27d..df82ade6d 100644 --- a/hir/src/asm/mod.rs +++ b/hir/src/asm/mod.rs @@ -4,15 +4,16 @@ mod import; mod isa; mod stack; -pub use self::builder::*; -pub use self::display::{DisplayInlineAsm, DisplayMasmBlock}; -pub use self::import::{MasmImport, ModuleImportInfo}; -pub use self::isa::*; -pub use self::stack::{OperandStack, Stack, StackElement}; - use cranelift_entity::PrimaryMap; use smallvec::smallvec; +pub use self::{ + builder::*, + display::{DisplayInlineAsm, DisplayMasmBlock}, + import::{MasmImport, ModuleImportInfo}, + isa::*, + stack::{OperandStack, Stack, StackElement}, +}; use super::{DataFlowGraph, Opcode, Type, ValueList}; /// Represents Miden Assembly (MASM) directly in the IR diff --git a/hir/src/asm/stack.rs b/hir/src/asm/stack.rs index 6de90ffd5..88931e647 100644 --- a/hir/src/asm/stack.rs +++ b/hir/src/asm/stack.rs @@ -125,12 +125,7 @@ pub trait Stack: IndexMut::Element> { fn dropn(&mut self, n: usize) { let stack = self.stack_mut(); let len = stack.len(); - assert!( - n <= len, - "unable to drop {} elements, operand stack only has {}", - n, - len - ); + assert!(n <= len, "unable to drop {} elements, operand stack only has {}", n, len); stack.truncate(len - n); } @@ -289,7 +284,8 @@ pub trait Stack: IndexMut::Element> { // Split the stack so that the desired position is in the top half let mid = len - (n + 1); let (_, r) = stack.split_at_mut(mid); - // Move all elements above the `n`th position up by one, moving the top element to the `n`th position + // Move all elements above the `n`th position up by one, moving the top element to the `n`th + // position r.rotate_right(1); } @@ -312,7 +308,8 @@ pub trait Stack: IndexMut::Element> { // Split the stack so that the desired position is in the top half let mid = len - index; let (_, r) = stack.split_at_mut(mid); - // Move all elements above the `n`th word up by one word, moving the top word to the `n`th position + // Move all elements above the `n`th word up by one word, moving the top word to the `n`th + // position r.rotate_right(4); } } @@ -377,10 +374,12 @@ impl Stack for OperandStack { fn stack(&self) -> &Vec { &self.stack } + #[inline(always)] fn stack_mut(&mut self) -> &mut Vec { &mut self.stack } + #[inline(always)] fn clear(&mut self) { self.stack.clear(); @@ -473,7 +472,6 @@ impl<'a, E: StackElement, T: ?Sized + Stack> fmt::Debug for DebugSt #[cfg(test)] mod tests { use super::*; - use crate::Felt; #[test] fn operand_stack_primitive_ops_test() { @@ -501,12 +499,7 @@ mod tests { #[inline(always)] fn as_int(word: [Felt; 4]) -> [u64; 4] { - [ - word[0].as_int(), - word[1].as_int(), - word[2].as_int(), - word[3].as_int(), - ] + [word[0].as_int(), word[1].as_int(), word[2].as_int(), word[3].as_int()] } // peekw diff --git a/hir/src/builder.rs b/hir/src/builder.rs index 955bd79e9..400045a34 100644 --- a/hir/src/builder.rs +++ b/hir/src/builder.rs @@ -82,9 +82,7 @@ impl<'f> FunctionBuilder<'f> { } pub fn ins<'a, 'b: 'a>(&'b mut self) -> DefaultInstBuilder<'a> { - let block = self - .position - .expect("must be in a block to insert instructions"); + let block = self.position.expect("must be in a block to insert instructions"); DefaultInstBuilder::new(&mut self.func.dfg, block) } } @@ -204,7 +202,8 @@ pub trait InstBuilderBase<'f>: Sized { ctrl_ty: Type, span: SourceSpan, ) -> (Inst, &'f mut DataFlowGraph); - /// Get a default instruction builder using the dataflow graph and insertion point of the current builder + /// Get a default instruction builder using the dataflow graph and insertion point of the + /// current builder fn ins<'a, 'b: 'a>(&'b mut self) -> DefaultInstBuilder<'a> { let ip = self.insertion_point(); DefaultInstBuilder::at(self.data_flow_graph_mut(), ip) @@ -266,29 +265,15 @@ macro_rules! require_unsigned_integer { macro_rules! require_integer { ($this:ident, $val:ident) => {{ let ty = $this.data_flow_graph().value_type($val); - assert!( - ty.is_integer(), - "expected {} to be of integral type", - stringify!($val) - ); + assert!(ty.is_integer(), "expected {} to be of integral type", stringify!($val)); ty }}; ($this:ident, $val:ident, $ty:expr) => {{ let ty = $this.data_flow_graph().value_type($val); let expected_ty = $ty; - assert!( - ty.is_integer(), - "expected {} to be of integral type", - stringify!($val) - ); - assert_eq!( - ty, - &expected_ty, - "expected {} to be a {}", - stringify!($val), - &expected_ty - ); + assert!(ty.is_integer(), "expected {} to be of integral type", stringify!($val)); + assert_eq!(ty, &expected_ty, "expected {} to be a {}", stringify!($val), &expected_ty); ty }}; } @@ -603,8 +588,7 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { let pool = &mut self.data_flow_graph_mut().value_lists; vlist.push(rhs, pool); } - self.PrimOpImm(Opcode::AssertEq, Type::Unit, lhs, vlist, span) - .0 + self.PrimOpImm(Opcode::AssertEq, Type::Unit, lhs, vlist, span).0 } signed_integer_literal!(1, bool); @@ -652,7 +636,8 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { self.symbol_relative(name, 0, span) } - /// Same semantics as `symbol`, but applies a constant offset to the address of the given symbol. + /// Same semantics as `symbol`, but applies a constant offset to the address of the given + /// symbol. /// /// If the offset is zero, this is equivalent to `symbol` fn symbol_relative>( @@ -661,11 +646,10 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { offset: i32, span: SourceSpan, ) -> GlobalValue { - self.data_flow_graph_mut() - .create_global_value(GlobalValueData::Symbol { - name: Ident::new(Symbol::intern(name.as_ref()), span), - offset, - }) + self.data_flow_graph_mut().create_global_value(GlobalValueData::Symbol { + name: Ident::new(Symbol::intern(name.as_ref()), span), + offset, + }) } /// Get the address of a global variable whose symbol is `name` @@ -677,7 +661,8 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { self.symbol_relative_addr(name, 0, ty, span) } - /// Same semantics as `symbol_addr`, but applies a constant offset to the address of the given symbol. + /// Same semantics as `symbol_addr`, but applies a constant offset to the address of the given + /// symbol. /// /// If the offset is zero, this is equivalent to `symbol_addr` fn symbol_relative_addr>( @@ -688,12 +673,10 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { span: SourceSpan, ) -> Value { assert!(ty.is_pointer(), "expected pointer type, got '{}'", &ty); - let gv = self - .data_flow_graph_mut() - .create_global_value(GlobalValueData::Symbol { - name: Ident::new(Symbol::intern(name.as_ref()), span), - offset, - }); + let gv = self.data_flow_graph_mut().create_global_value(GlobalValueData::Symbol { + name: Ident::new(Symbol::intern(name.as_ref()), span), + offset, + }); into_first_result!(self.Global(gv, ty, span)) } @@ -706,7 +689,8 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { self.load_symbol_relative(name, ty, 0, span) } - /// Same semantics as `load_symbol`, but a constant offset is applied to the address before issuing the load. + /// Same semantics as `load_symbol`, but a constant offset is applied to the address before + /// issuing the load. fn load_symbol_relative>( mut self, name: S, @@ -714,12 +698,10 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { offset: i32, span: SourceSpan, ) -> Value { - let base = self - .data_flow_graph_mut() - .create_global_value(GlobalValueData::Symbol { - name: Ident::new(Symbol::intern(name.as_ref()), span), - offset: 0, - }); + let base = self.data_flow_graph_mut().create_global_value(GlobalValueData::Symbol { + name: Ident::new(Symbol::intern(name.as_ref()), span), + offset: 0, + }); self.load_global_relative(base, ty, offset, span) } @@ -732,7 +714,8 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { self.load_global_relative(addr, ty, 0, span) } - /// Same semantics as `load_global_relative`, but a constant offset is applied to the address before issuing the load. + /// Same semantics as `load_global_relative`, but a constant offset is applied to the address + /// before issuing the load. fn load_global_relative( mut self, base: GlobalValue, @@ -746,31 +729,24 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { { // If the base global is a load, the target address cannot be computed until runtime, // so expand this to the appropriate sequence of instructions to do so in that case - assert!( - base_ty.is_pointer(), - "expected global value to have pointer type" - ); + assert!(base_ty.is_pointer(), "expected global value to have pointer type"); let base_ty = base_ty.clone(); let base = self.ins().load_global(base, base_ty.clone(), span); let addr = self.ins().ptrtoint(base, Type::U32, span); let offset_addr = if offset >= 0 { - self.ins() - .add_imm_checked(addr, Immediate::U32(offset as u32), span) + self.ins().add_imm_checked(addr, Immediate::U32(offset as u32), span) } else { - self.ins() - .sub_imm_checked(addr, Immediate::U32(offset.unsigned_abs()), span) + self.ins().sub_imm_checked(addr, Immediate::U32(offset.unsigned_abs()), span) }; let ptr = self.ins().inttoptr(offset_addr, base_ty, span); self.load(ptr, span) } else { // The global address can be computed statically - let gv = self - .data_flow_graph_mut() - .create_global_value(GlobalValueData::Load { - base, - offset, - ty: ty.clone(), - }); + let gv = self.data_flow_graph_mut().create_global_value(GlobalValueData::Load { + base, + offset, + ty: ty.clone(), + }); into_first_result!(self.Global(gv, ty, span)) } } @@ -795,10 +771,7 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { { // If the base global is a load, the target address cannot be computed until runtime, // so expand this to the appropriate sequence of instructions to do so in that case - assert!( - base_ty.is_pointer(), - "expected global value to have pointer type" - ); + assert!(base_ty.is_pointer(), "expected global value to have pointer type"); let base_ty = base_ty.clone(); let base = self.ins().load_global(base, base_ty.clone(), span); let addr = self.ins().ptrtoint(base, Type::U32, span); @@ -808,23 +781,19 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { .expect("invalid type: size is larger than 2^32"); let computed_offset = unit_size * offset; let offset_addr = if computed_offset >= 0 { - self.ins() - .add_imm_checked(addr, Immediate::U32(offset as u32), span) + self.ins().add_imm_checked(addr, Immediate::U32(offset as u32), span) } else { - self.ins() - .sub_imm_checked(addr, Immediate::U32(offset.unsigned_abs()), span) + self.ins().sub_imm_checked(addr, Immediate::U32(offset.unsigned_abs()), span) }; let ptr = self.ins().inttoptr(offset_addr, base_ty, span); self.load(ptr, span) } else { // The global address can be computed statically - let gv = self - .data_flow_graph_mut() - .create_global_value(GlobalValueData::IAddImm { - base, - offset, - ty: unit_ty.clone(), - }); + let gv = self.data_flow_graph_mut().create_global_value(GlobalValueData::IAddImm { + base, + offset, + ty: unit_ty.clone(), + }); let ty = self.data_flow_graph().global_type(gv); into_first_result!(self.Global(gv, ty, span)) } @@ -849,11 +818,7 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { fn store(mut self, ptr: Value, value: Value, span: SourceSpan) -> Inst { let pointee_ty = require_pointee!(self, ptr); let value_ty = self.data_flow_graph().value_type(value); - assert_eq!( - pointee_ty, value_ty, - "expected value to be a {}, got {}", - pointee_ty, value_ty - ); + assert_eq!(pointee_ty, value_ty, "expected value to be a {}, got {}", pointee_ty, value_ty); let mut vlist = ValueList::default(); { let dfg = self.data_flow_graph_mut(); @@ -872,10 +837,7 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { require_integer!(self, count); let src_ty = require_pointer!(self, src); let dst_ty = require_pointer!(self, dst); - assert_eq!( - src_ty, dst_ty, - "the source and destination pointers must be the same type" - ); + assert_eq!(src_ty, dst_ty, "the source and destination pointers must be the same type"); let mut vlist = ValueList::default(); { let dfg = self.data_flow_graph_mut(); @@ -906,24 +868,22 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { /// This is an intrinsic which derives a new pointer from an existing pointer to an aggregate. /// - /// In short, this represents the common need to calculate a new pointer from an existing pointer, - /// but without losing provenance of the original pointer. It is specifically intended for use - /// in obtaining a pointer to an element/field of an array/struct, of the correct type, given a - /// well typed pointer to the aggregate. + /// In short, this represents the common need to calculate a new pointer from an existing + /// pointer, but without losing provenance of the original pointer. It is specifically + /// intended for use in obtaining a pointer to an element/field of an array/struct, of the + /// correct type, given a well typed pointer to the aggregate. /// /// This function will panic if the pointer is not to an aggregate type /// /// The new pointer is derived by statically navigating the structure of the pointee type, using /// `offsets` to guide the traversal. Initially, the first offset is relative to the original /// pointer, where `0` refers to the base/first field of the object. The second offset is then - /// relative to the base of the object selected by the first offset, and so on. Offsets must remain - /// in bounds, any attempt to index outside a type's boundaries will result in a panic. + /// relative to the base of the object selected by the first offset, and so on. Offsets must + /// remain in bounds, any attempt to index outside a type's boundaries will result in a + /// panic. fn getelementptr(mut self, ptr: Value, mut indices: &[usize], span: SourceSpan) -> Value { let mut ty = require_pointee!(self, ptr); - assert!( - !indices.is_empty(), - "getelementptr requires at least one index" - ); + assert!(!indices.is_empty(), "getelementptr requires at least one index"); // Calculate the offset in bytes from `ptr` to get the element pointer let mut offset = 0; @@ -954,10 +914,7 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { offset += field.offset as usize; ty = &field.ty; } - other => panic!( - "invalid getelementptr: cannot index values of type {}", - other - ), + other => panic!("invalid getelementptr: cannot index values of type {}", other), } } @@ -969,9 +926,7 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { let offset: u32 = offset.try_into().expect( "invalid getelementptr type: computed offset cannot possibly fit in linear memory", ); - let new_addr = self - .ins() - .add_imm_checked(addr, Immediate::U32(offset), span); + let new_addr = self.ins().add_imm_checked(addr, Immediate::U32(offset), span); // Cast back to a pointer to the selected element type self.inttoptr(new_addr, ty, span) } @@ -988,7 +943,8 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { let kind_matches = both_numeric || both_pointers; assert!( kind_matches, - "invalid cast, expected source and target types to be of the same kind, where a kind is either numeric or pointer: value is of type {}, and target type is {}", + "invalid cast, expected source and target types to be of the same kind, where a kind \ + is either numeric or pointer: value is of type {}, and target type is {}", &arg_ty, &ty ); into_first_result!(self.Unary(Opcode::Cast, ty, arg, span)) @@ -1241,8 +1197,7 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { then_vlist.extend(then_args.iter().copied(), pool); else_vlist.extend(else_args.iter().copied(), pool); } - self.CondBr(cond, then_dest, then_vlist, else_dest, else_vlist, span) - .0 + self.CondBr(cond, then_dest, then_vlist, else_dest, else_vlist, span).0 } fn switch(self, arg: Value, arms: Vec<(u32, Block)>, default: Block, span: SourceSpan) -> Inst { diff --git a/hir/src/component/interface.rs b/hir/src/component/interface.rs index 34d6ada61..9e87faf1d 100644 --- a/hir/src/component/interface.rs +++ b/hir/src/component/interface.rs @@ -1,14 +1,17 @@ use miden_hir_symbol::Symbol; -/// A fully-qualified identifier for the interface being imported, e.g. `namespace::package/interface@version` +/// A fully-qualified identifier for the interface being imported, e.g. +/// `namespace::package/interface@version` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct InterfaceIdent { - /// A fully-qualified identifier for the interface being imported, e.g. `namespace::package/interface@version` + /// A fully-qualified identifier for the interface being imported, e.g. + /// `namespace::package/interface@version` pub full_name: Symbol, } impl InterfaceIdent { - /// Create a new [InterfaceIdent] from a fully-qualified interface identifier, e.g. `namespace::package/interface@version` + /// Create a new [InterfaceIdent] from a fully-qualified interface identifier, e.g. + /// `namespace::package/interface@version` pub fn from_full_ident(full_ident: String) -> Self { Self { full_name: Symbol::intern(full_ident), @@ -19,7 +22,8 @@ impl InterfaceIdent { /// An identifier for a function in an interface #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct InterfaceFunctionIdent { - /// An interface identifier for the interface being imported (e.g. `namespace::package/interface@version`) + /// An interface identifier for the interface being imported (e.g. + /// `namespace::package/interface@version`) pub interface: InterfaceIdent, /// The name of the function from the interface pub function: Symbol, diff --git a/hir/src/component/mod.rs b/hir/src/component/mod.rs index f4e89f271..6e6527f96 100644 --- a/hir/src/component/mod.rs +++ b/hir/src/component/mod.rs @@ -1,7 +1,8 @@ use core::ops::{Deref, DerefMut}; +use std::collections::BTreeMap; + use intrusive_collections::RBTree; use miden_core::crypto::hash::RpoDigest; -use std::collections::BTreeMap; use super::*; @@ -47,7 +48,8 @@ pub struct ComponentExport { pub invoke_method: FunctionInvocationMethod, } -/// A [Component] is a collection of [Module]s that are being compiled together as a package and have exports/imports. +/// A [Component] is a collection of [Module]s that are being compiled together as a package and +/// have exports/imports. #[derive(Default)] pub struct Component { /// This tree stores all of the modules @@ -99,7 +101,8 @@ impl Component { /// This struct provides an ergonomic way to construct a [Component] in an imperative fashion. /// -/// Simply create the builder, add/build one or more modules, then call `link` to obtain a [Component]. +/// Simply create the builder, add/build one or more modules, then call `link` to obtain a +/// [Component]. pub struct ComponentBuilder<'a> { modules: BTreeMap>, imports: BTreeMap, diff --git a/hir/src/dataflow.rs b/hir/src/dataflow.rs index a8d528476..957abdb5f 100644 --- a/hir/src/dataflow.rs +++ b/hir/src/dataflow.rs @@ -1,11 +1,10 @@ use std::ops::{Deref, Index, IndexMut}; use cranelift_entity::{PrimaryMap, SecondaryMap}; +use miden_diagnostics::{Span, Spanned}; use rustc_hash::FxHashMap; use smallvec::SmallVec; -use miden_diagnostics::{Span, Spanned}; - use super::*; pub struct DataFlowGraph { @@ -236,12 +235,7 @@ impl DataFlowGraph { ctrl_ty: Type, span: SourceSpan, ) -> Inst { - self.insert_inst( - InsertionPoint::after(ProgramPoint::Block(block)), - data, - ctrl_ty, - span, - ) + self.insert_inst(InsertionPoint::after(ProgramPoint::Block(block)), data, ctrl_ty, span) } /// Insert a new instruction at `ip`, using the provided instruction @@ -257,13 +251,12 @@ impl DataFlowGraph { let id = self.insts.alloc_key(); let block_id = match ip.at { ProgramPoint::Block(block) => block, - ProgramPoint::Inst(inst) => self - .inst_block(inst) - .expect("cannot insert after detached instruction"), + ProgramPoint::Inst(inst) => { + self.inst_block(inst).expect("cannot insert after detached instruction") + } }; // Store the instruction metadata - self.insts - .append(id, InstNode::new(id, block_id, Span::new(span, data))); + self.insts.append(id, InstNode::new(id, block_id, Span::new(span, data))); // Manufacture values for all of the instruction results self.make_results(id, ctrl_ty); // Insert the instruction based on the insertion point provided @@ -317,10 +310,8 @@ impl DataFlowGraph { let id = self.insts.alloc_key(); let span = self.insts[inst].data.span(); let data = self.insts[inst].data.deep_clone(&mut self.value_lists); - self.insts.append( - id, - InstNode::new(id, Block::default(), Span::new(span, data)), - ); + self.insts + .append(id, InstNode::new(id, Block::default(), Span::new(span, data))); // Derive results for the cloned instruction using the results // of the original instruction @@ -349,9 +340,7 @@ impl DataFlowGraph { } pub fn first_result(&self, inst: Inst) -> Value { - self.results[inst] - .first(&self.value_lists) - .expect("instruction has no results") + self.results[inst].first(&self.value_lists).expect("instruction has no results") } pub fn has_results(&self, inst: Inst) -> bool { @@ -615,10 +604,7 @@ impl DataFlowGraph { } pub fn block_param_types(&self, block: Block) -> SmallVec<[Type; 1]> { - self.block_params(block) - .iter() - .map(|&v| self.value_type(v).clone()) - .collect() + self.block_params(block).iter().map(|&v| self.value_type(v).clone()).collect() } /// Clone the block parameters of `src` as a new set of values, derived from the data used to @@ -667,14 +653,10 @@ impl DataFlowGraph { } else { panic!("{} must be a block parameter", val); }; - self.blocks[block] - .params - .remove(num as usize, &mut self.value_lists); + self.blocks[block].params.remove(num as usize, &mut self.value_lists); for index in num..(self.num_block_params(block) as u16) { - let value_data = &mut self.values[self.blocks[block] - .params - .get(index as usize, &self.value_lists) - .unwrap()]; + let value_data = &mut self.values + [self.blocks[block].params.get(index as usize, &self.value_lists).unwrap()]; let mut value_data_clone = value_data.clone(); match &mut value_data_clone { ValueData::Param { ref mut num, .. } => { @@ -683,10 +665,7 @@ impl DataFlowGraph { } _ => panic!( "{} must be a block parameter", - self.blocks[block] - .params - .get(index as usize, &self.value_lists) - .unwrap() + self.blocks[block].params.get(index as usize, &self.value_lists).unwrap() ), } } @@ -726,7 +705,10 @@ impl DataFlowGraph { arms: _, default: _, }) => { - panic!("cannot append argument {value} to Switch destination block {dest}, since it has no block arguments support"); + panic!( + "cannot append argument {value} to Switch destination block {dest}, since it \ + has no block arguments support" + ); } _ => panic!("{} must be a branch instruction", branch_inst), } diff --git a/hir/src/display.rs b/hir/src/display.rs index fce2daff0..a50458658 100644 --- a/hir/src/display.rs +++ b/hir/src/display.rs @@ -28,12 +28,15 @@ impl Decorator for () { fn skip_block(&self, _block: Block) -> bool { true } + fn skip_inst(&self, _inst: Inst) -> bool { true } + fn decorate_block<'a, 'd: 'a>(&'d self, _block: Block) -> Self::Display<'a> { "" } + fn decorate_inst<'a, 'd: 'a>(&'d self, _inst: Inst) -> Self::Display<'a> { "" } diff --git a/hir/src/function.rs b/hir/src/function.rs index 69066a800..c4369b515 100644 --- a/hir/src/function.rs +++ b/hir/src/function.rs @@ -265,10 +265,12 @@ pub type FunctionList = LinkedList; /// fully-qualified names. /// * Functions consist of one or more basic blocks, where the entry block is predefined based /// on the function signature. -/// * Basic blocks consist of a sequence of [Instruction] without any control flow (excluding calls), +/// * Basic blocks consist of a sequence of [Instruction] without any control flow (excluding +/// calls), /// terminating with a control flow instruction. Our SSA representation uses block arguments rather /// than phi nodes to represent join points in the control flow graph. -/// * Instructions consume zero or more arguments, and produce zero or more results. Results produced +/// * Instructions consume zero or more arguments, and produce zero or more results. Results +/// produced /// by an instruction constitute definitions of those values. A value may only ever have a single /// definition, e.g. you can't reassign a value after it is introduced by an instruction. /// @@ -427,7 +429,8 @@ impl PartialEq for Function { return false; } - // We expect the blocks to be laid out in the same order, and to have the same parameter lists + // We expect the blocks to be laid out in the same order, and to have the same parameter + // lists for (block_id, block) in self.dfg.blocks() { if let Some(other_block) = other.dfg.blocks.get(block_id) { if block.params.as_slice(&self.dfg.value_lists) @@ -443,13 +446,10 @@ impl PartialEq for Function { inst: i, value_lists: &self.dfg.value_lists, }) - .eq(other_block - .insts - .iter() - .map(|i| InstructionWithValueListPool { - inst: i, - value_lists: &other.dfg.value_lists, - })) + .eq(other_block.insts.iter().map(|i| InstructionWithValueListPool { + inst: i, + value_lists: &other.dfg.value_lists, + })) { return false; } diff --git a/hir/src/globals.rs b/hir/src/globals.rs index c22abf730..df05d948a 100644 --- a/hir/src/globals.rs +++ b/hir/src/globals.rs @@ -1,7 +1,9 @@ -use std::alloc::Layout; -use std::collections::{hash_map::DefaultHasher, BTreeMap}; -use std::fmt::{self, Write}; -use std::hash::{Hash, Hasher}; +use std::{ + alloc::Layout, + collections::{hash_map::DefaultHasher, BTreeMap}, + fmt::{self, Write}, + hash::{Hash, Hasher}, +}; use cranelift_entity::entity_impl; use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink}; @@ -26,13 +28,13 @@ use super::*; /// /// However, with global variables, we have a bit more freedom, as it is a concept that we are /// completely inventing from whole cloth without explicit support from the VM or Miden Assembly. -/// In short, when we compile a [Program] to MASM, we first gather together all of the global variables -/// into a program-wide table, merging and garbage collecting as appropriate, and updating all -/// references to them in each module. This global variable table is then assumed to be laid out -/// in memory starting at the base of the linear memory address space in the same order, with appropriate -/// padding to ensure accesses are aligned. Then, when emitting MASM instructions which reference -/// global values, we use the layout information to derive the address where that global value -/// is allocated. +/// In short, when we compile a [Program] to MASM, we first gather together all of the global +/// variables into a program-wide table, merging and garbage collecting as appropriate, and updating +/// all references to them in each module. This global variable table is then assumed to be laid out +/// in memory starting at the base of the linear memory address space in the same order, with +/// appropriate padding to ensure accesses are aligned. Then, when emitting MASM instructions which +/// reference global values, we use the layout information to derive the address where that global +/// value is allocated. /// /// This has some downsides however, the biggest of which is that we can't prevent someone from /// loading modules generated from a [Program] with either their own hand-written modules, or @@ -87,7 +89,8 @@ pub enum GlobalVariableError { /// An attempt was made to set the initializer for a global that already has one #[error("cannot set an initializer for '{0}', it is already initialized")] AlreadyInitialized(Ident), - /// The initializer data is invalid for the declared type of the given global, e.g. size mismatch. + /// The initializer data is invalid for the declared type of the given global, e.g. size + /// mismatch. #[error( "invalid global variable initializer for '{0}': the data does not match the declared type" )] @@ -109,7 +112,8 @@ pub enum ConflictResolutionStrategy { /// This table is used to lay out and link together global variables for a [Program]. /// -/// See the docs for [Linkage], [GlobalVariableData], and [GlobalVariableTable::declare] for more details. +/// See the docs for [Linkage], [GlobalVariableData], and [GlobalVariableTable::declare] for more +/// details. pub struct GlobalVariableTable { layout: LinkedList, names: BTreeMap, @@ -268,20 +272,23 @@ impl GlobalVariableTable { !self.data.is_empty() } - /// Declares a new global variable with the given symbol name, type, linkage, and optional initializer. + /// Declares a new global variable with the given symbol name, type, linkage, and optional + /// initializer. /// - /// If successful, `Ok` is returned, with the [GlobalVariable] corresponding to the data for the symbol. + /// If successful, `Ok` is returned, with the [GlobalVariable] corresponding to the data for the + /// symbol. /// - /// Returns an error if the specification of the global is invalid in any way, or the declaration conflicts - /// with a previous declaration of the same name. + /// Returns an error if the specification of the global is invalid in any way, or the + /// declaration conflicts with a previous declaration of the same name. /// - /// NOTE: While similar to `try_insert`, a key difference is that `try_declare` does not attempt to resolve - /// conflicts. If the given name has been previously declared, and the declarations are not identical, - /// then an error will be returned. This is because conflict resolution is a process performed when linking - /// together modules. Declaring globals is done during the initial construction of a module, where any - /// attempt to rename a global variable locally would cause unexpected issues as references to that global - /// are emitted. Once a module is constructed, globals it declares with internal linkage can be renamed freely, - /// as the name is no longer significant. + /// NOTE: While similar to `try_insert`, a key difference is that `try_declare` does not attempt + /// to resolve conflicts. If the given name has been previously declared, and the + /// declarations are not identical, then an error will be returned. This is because conflict + /// resolution is a process performed when linking together modules. Declaring globals is + /// done during the initial construction of a module, where any attempt to rename a global + /// variable locally would cause unexpected issues as references to that global are emitted. + /// Once a module is constructed, globals it declares with internal linkage can be renamed + /// freely, as the name is no longer significant. pub fn declare( &mut self, name: Ident, @@ -340,9 +347,10 @@ impl GlobalVariableTable { /// Attempt to insert the given [GlobalVariableData] into this table. /// - /// Returns the id of the global variable in the table, along with a flag indicating whether the global symbol - /// was renamed to resolve a conflict with an existing symbol. The caller is expected to handle such renames - /// so that any references to the original name that are affected can be updated. + /// Returns the id of the global variable in the table, along with a flag indicating whether the + /// global symbol was renamed to resolve a conflict with an existing symbol. The caller is + /// expected to handle such renames so that any references to the original name that are + /// affected can be updated. /// /// If there was an unresolvable conflict, an error will be returned. pub fn try_insert( @@ -419,9 +427,11 @@ impl GlobalVariableTable { /// This function will return `Err` if any of the following occur: /// /// * The global variable already has an initializer - /// * The given data does not match the type of the global variable, i.e. more data than the type supports. + /// * The given data does not match the type of the global variable, i.e. more data than the + /// type supports. /// - /// If the data is smaller than the type of the global variable, the data will be zero-extended to fill it out. + /// If the data is smaller than the type of the global variable, the data will be zero-extended + /// to fill it out. /// /// NOTE: The initializer data is expected to be in little-endian order. pub fn set_initializer( @@ -454,13 +464,14 @@ impl GlobalVariableTable { Ok(()) } - /// This is a low-level operation to insert global variable data directly into the table, allocating - /// a fresh unique id, which is then returned. + /// This is a low-level operation to insert global variable data directly into the table, + /// allocating a fresh unique id, which is then returned. /// /// # SAFETY /// - /// It is expected that the caller has already guaranteed that the name of the given global variable - /// is not present in the table, and that all validation rules for global variables have been enforced. + /// It is expected that the caller has already guaranteed that the name of the given global + /// variable is not present in the table, and that all validation rules for global variables + /// have been enforced. pub(crate) unsafe fn insert(&mut self, mut data: GlobalVariableData) -> GlobalVariable { let name = data.name; // Allocate the data in the arena @@ -563,7 +574,8 @@ impl GlobalVariableData { /// Returns true if `self` is compatible with `other`, meaning that the two declarations are /// identical in terms of type and linkage, and do not have conflicting initializers. /// - /// NOTE: The name of the global is not considered here, only the properties of the value itself. + /// NOTE: The name of the global is not considered here, only the properties of the value + /// itself. pub fn is_compatible_with(&self, other: &Self) -> bool { let compatible_init = self.init.is_none() || other.init.is_none() || self.init == other.init; @@ -668,7 +680,8 @@ impl GlobalValueData { !matches!(self, Self::Load { .. }) } - /// Return the computed offset for this global value (relative to it's position in the global table) + /// Return the computed offset for this global value (relative to it's position in the global + /// table) pub fn offset(&self) -> i32 { match self { Self::Symbol { offset, .. } => *offset, diff --git a/hir/src/ident.rs b/hir/src/ident.rs index fe77b032c..701e1e661 100644 --- a/hir/src/ident.rs +++ b/hir/src/ident.rs @@ -25,12 +25,12 @@ impl FromStr for FunctionIdent { Some((ns, id)) => { let module = Ident::with_empty_span(Symbol::intern(ns)); let function = Ident::with_empty_span(Symbol::intern(id)); - Ok(Self { - module, - function, - }) + Ok(Self { module, function }) } - None => Err(anyhow!("invalid function name, expected fully-qualified identifier, e.g. 'std::math::u64::checked_add'")), + None => Err(anyhow!( + "invalid function name, expected fully-qualified identifier, e.g. \ + 'std::math::u64::checked_add'" + )), } } } @@ -54,9 +54,7 @@ impl PartialOrd for FunctionIdent { } impl Ord for FunctionIdent { fn cmp(&self, other: &Self) -> Ordering { - self.module - .cmp(&other.module) - .then(self.function.cmp(&other.function)) + self.module.cmp(&other.module).then(self.function.cmp(&other.function)) } } diff --git a/hir/src/immediates.rs b/hir/src/immediates.rs index 0e0e23ab5..e54316ab3 100644 --- a/hir/src/immediates.rs +++ b/hir/src/immediates.rs @@ -349,7 +349,8 @@ impl Ord for Immediate { o => o, } } else { - // The float is larger than i128 can represent, the sign tells us in what direction + // The float is larger than i128 can represent, the sign tells us in what + // direction if is_positive { Ordering::Greater } else { @@ -363,42 +364,34 @@ impl Ord for Immediate { (Self::I128(x), Self::I128(y)) => x.cmp(y), // We're only comparing against values here which are u64, i64, or smaller than 64-bits (Self::I128(x), y) => { - let y = y - .as_i128() - .expect("expected rhs to be an integer smaller than i128"); + let y = y.as_i128().expect("expected rhs to be an integer smaller than i128"); x.cmp(&y) } (x, Self::I128(y)) => { - let x = x - .as_i128() - .expect("expected rhs to be an integer smaller than i128"); + let x = x.as_i128().expect("expected rhs to be an integer smaller than i128"); x.cmp(y) } // u64 immediates may not fit in an i64 (Self::U64(x), Self::U64(y)) => x.cmp(y), // We're only comparing against values here which are i64, or smaller than 64-bits (Self::U64(x), y) => { - let y = y - .as_i64() - .expect("expected rhs to be an integer capable of fitting in an i64") - as u64; + let y = + y.as_i64().expect("expected rhs to be an integer capable of fitting in an i64") + as u64; x.cmp(&y) } (x, Self::U64(y)) => { - let x = x - .as_i64() - .expect("expected rhs to be an integer capable of fitting in an i64") - as u64; + let x = + x.as_i64().expect("expected rhs to be an integer capable of fitting in an i64") + as u64; x.cmp(y) } // All immediates at this point are i64 or smaller (x, y) => { - let x = x - .as_i64() - .expect("expected rhs to be an integer capable of fitting in an i64"); - let y = y - .as_i64() - .expect("expected rhs to be an integer capable of fitting in an i64"); + let x = + x.as_i64().expect("expected rhs to be an integer capable of fitting in an i64"); + let y = + y.as_i64().expect("expected rhs to be an integer capable of fitting in an i64"); x.cmp(&y) } } @@ -503,12 +496,15 @@ impl FloatToInt for f64 { fn upper_bound() -> Self { f64::from(i8::MAX) + 1.0 } + fn lower_bound() -> Self { f64::from(i8::MIN) - 1.0 } + fn to_int(self) -> Result { float_to_int(self) } + unsafe fn to_int_unchecked(self) -> i8 { f64::to_int_unchecked(self) } @@ -519,12 +515,15 @@ impl FloatToInt for f64 { fn upper_bound() -> Self { f64::from(u8::MAX) + 1.0 } + fn lower_bound() -> Self { 0.0 } + fn to_int(self) -> Result { float_to_int(self) } + unsafe fn to_int_unchecked(self) -> u8 { f64::to_int_unchecked(self) } @@ -535,12 +534,15 @@ impl FloatToInt for f64 { fn upper_bound() -> Self { f64::from(i16::MAX) + 1.0 } + fn lower_bound() -> Self { f64::from(i16::MIN) - 1.0 } + fn to_int(self) -> Result { float_to_int(self) } + unsafe fn to_int_unchecked(self) -> i16 { f64::to_int_unchecked(self) } @@ -551,12 +553,15 @@ impl FloatToInt for f64 { fn upper_bound() -> Self { f64::from(u16::MAX) + 1.0 } + fn lower_bound() -> Self { 0.0 } + fn to_int(self) -> Result { float_to_int(self) } + unsafe fn to_int_unchecked(self) -> u16 { f64::to_int_unchecked(self) } @@ -567,12 +572,15 @@ impl FloatToInt for f64 { fn upper_bound() -> Self { f64::from(i32::MAX) + 1.0 } + fn lower_bound() -> Self { f64::from(i32::MIN) - 1.0 } + fn to_int(self) -> Result { float_to_int(self) } + unsafe fn to_int_unchecked(self) -> i32 { f64::to_int_unchecked(self) } @@ -583,12 +591,15 @@ impl FloatToInt for f64 { fn upper_bound() -> Self { f64::from(u32::MAX) + 1.0 } + fn lower_bound() -> Self { 0.0 } + fn to_int(self) -> Result { float_to_int(self) } + unsafe fn to_int_unchecked(self) -> u32 { f64::to_int_unchecked(self) } @@ -599,12 +610,15 @@ impl FloatToInt for f64 { fn upper_bound() -> Self { 63.0f64.exp2() } + fn lower_bound() -> Self { (63.0f64.exp2() * -1.0) - 1.0 } + fn to_int(self) -> Result { float_to_int(self) } + unsafe fn to_int_unchecked(self) -> i64 { f64::to_int_unchecked(self) } @@ -615,12 +629,15 @@ impl FloatToInt for f64 { fn upper_bound() -> Self { 64.0f64.exp2() } + fn lower_bound() -> Self { 0.0 } + fn to_int(self) -> Result { float_to_int(self) } + unsafe fn to_int_unchecked(self) -> u64 { f64::to_int_unchecked(self) } @@ -631,12 +648,15 @@ impl FloatToInt for f64 { fn upper_bound() -> Self { 64.0f64.exp2() - 32.0f64.exp2() + 1.0 } + fn lower_bound() -> Self { 0.0 } + fn to_int(self) -> Result { float_to_int(self).map(Felt::new) } + unsafe fn to_int_unchecked(self) -> Felt { Felt::new(f64::to_int_unchecked::(self)) } @@ -647,12 +667,15 @@ impl FloatToInt for f64 { fn upper_bound() -> Self { f64::from(i128::BITS - 1).exp2() } + fn lower_bound() -> Self { (f64::from(i128::BITS - 1) * -1.0).exp2() - 1.0 } + fn to_int(self) -> Result { float_to_int(self) } + unsafe fn to_int_unchecked(self) -> i128 { f64::to_int_unchecked(self) } diff --git a/hir/src/instruction.rs b/hir/src/instruction.rs index a25c04c56..986f8815a 100644 --- a/hir/src/instruction.rs +++ b/hir/src/instruction.rs @@ -2,9 +2,8 @@ use core::ops::{Deref, DerefMut}; use cranelift_entity::entity_impl; use intrusive_collections::{intrusive_adapter, LinkedListLink}; -use smallvec::SmallVec; - use miden_diagnostics::{Span, Spanned}; +use smallvec::SmallVec; use super::*; @@ -176,7 +175,6 @@ impl Instruction { /// /// Side effects are defined as control flow, writing memory, trapping execution, /// I/O, etc. - /// #[inline] pub fn has_side_effects(&self) -> bool { self.opcode().has_side_effects() @@ -262,10 +260,8 @@ impl Instruction { ref default, .. }) => { - let mut targets = arms - .iter() - .map(|(_, b)| JumpTable::new(*b, &[])) - .collect::>(); + let mut targets = + arms.iter().map(|(_, b)| JumpTable::new(*b, &[])).collect::>(); targets.push(JumpTable::new(*default, &[])); BranchInfo::MultiDest(targets) } @@ -347,8 +343,9 @@ pub enum Opcode { /// of the type, but whose contents are undefined, i.e. you cannot assume that the binary /// representation of the value is zeroed. Alloca, - /// Like the WebAssembly `memory.grow` instruction, this allocates a given number of pages from the - /// global heap, and returns the previous size of the heap, in pages. Each page is 64kb by default. + /// Like the WebAssembly `memory.grow` instruction, this allocates a given number of pages from + /// the global heap, and returns the previous size of the heap, in pages. Each page is 64kb + /// by default. /// /// For the time being, this instruction is emulated using a heap pointer global which tracks /// the "end" of the available heap. Nothing actually prevents one from accessing memory past @@ -357,8 +354,8 @@ pub enum Opcode { MemGrow, /// This instruction is used to represent a global value in the IR /// - /// See [GlobalValueOp] and [GlobalValueData] for details on what types of values are represented - /// behind this opcode. + /// See [GlobalValueOp] and [GlobalValueData] for details on what types of values are + /// represented behind this opcode. GlobalValue, /// Loads a value from a pointer to memory Load, @@ -377,9 +374,11 @@ pub enum Opcode { Cast, /// Truncates a larger integral type to a smaller integral type, e.g. i64 -> i32 Trunc, - /// Zero-extends a smaller unsigned integral type to a larger unsigned integral type, e.g. u32 -> u64 + /// Zero-extends a smaller unsigned integral type to a larger unsigned integral type, e.g. u32 + /// -> u64 Zext, - /// Sign-extends a smaller signed integral type to a larger signed integral type, e.g. i32 -> i64 + /// Sign-extends a smaller signed integral type to a larger signed integral type, e.g. i32 -> + /// i64 Sext, /// Returns true if argument fits in the given integral type, e.g. u32, otherwise false Test, @@ -429,10 +428,7 @@ pub enum Opcode { } impl Opcode { pub fn is_terminator(&self) -> bool { - matches!( - self, - Self::Br | Self::CondBr | Self::Switch | Self::Ret | Self::Unreachable - ) + matches!(self, Self::Br | Self::CondBr | Self::Switch | Self::Ret | Self::Unreachable) } pub fn is_branch(&self) -> bool { @@ -786,15 +782,16 @@ impl fmt::Display for Opcode { /// meaning of each variant. #[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] pub enum Overflow { - /// Typically, this means the operation is performed using the equivalent field element operation, rather - /// than a dedicated operation for the given type. Because of this, the result of the operation may exceed - /// that of the integral type expected, but this will not be caught right away. + /// Typically, this means the operation is performed using the equivalent field element + /// operation, rather than a dedicated operation for the given type. Because of this, the + /// result of the operation may exceed that of the integral type expected, but this will + /// not be caught right away. /// /// It is the callers responsibility to ensure that resulting value is in range. #[default] Unchecked, - /// The operation will trap if the operands, or the result, is not valid for the range of the integral - /// type involved, e.g. u32. + /// The operation will trap if the operands, or the result, is not valid for the range of the + /// integral type involved, e.g. u32. Checked, /// The operation will wrap around, depending on the range of the integral type. For example, /// given a u32 value, this is done by applying `mod 2^32` to the result. @@ -1029,7 +1026,7 @@ impl<'a> PartialEq for InstructionWithValueListPool<'a> { && l.body == r.body && l.blocks == r.blocks } - (_, _) => unreachable!(), + (..) => unreachable!(), } } } diff --git a/hir/src/layout.rs b/hir/src/layout.rs index a881bf9d2..8a558e6d5 100644 --- a/hir/src/layout.rs +++ b/hir/src/layout.rs @@ -4,8 +4,11 @@ use std::{ }; use cranelift_entity::EntityRef; -use intrusive_collections::linked_list::{Cursor, CursorMut, LinkedList}; -use intrusive_collections::{intrusive_adapter, LinkedListLink, UnsafeRef}; +use intrusive_collections::{ + intrusive_adapter, + linked_list::{Cursor, CursorMut, LinkedList}, + LinkedListLink, UnsafeRef, +}; use typed_arena::Arena; /// This struct holds the data for each node in an ArenaMap/OrderedArenaMap @@ -54,22 +57,25 @@ intrusive_adapter!(pub LayoutAdapter = UnsafeRef>: Layout /// /// # Pros /// -/// * Once allocated, values stored in the map have a stable location, this can be useful for when you +/// * Once allocated, values stored in the map have a stable location, this can be useful for when +/// you /// expect to store elements of the map in an intrusive collection. -/// * Keys can be more efficiently sized, i.e. rather than pointers/usize keys, you can choose arbitrarily +/// * Keys can be more efficiently sized, i.e. rather than pointers/usize keys, you can choose +/// arbitrarily /// small bitwidths, as long as there is sufficient keyspace for your use case. -/// * Attempt to keep data in the map as contiguous in memory as possible. This is again useful for when -/// the data is also linked into an intrusive collection, like a linked list, where traversing the list -/// will end up visiting many of the nodes in the map. If each node was its own Box, this would cause -/// thrashing of the cache - ArenaMap sidesteps this by allocating values in chunks of memory that are -/// friendlier to the cache. +/// * Attempt to keep data in the map as contiguous in memory as possible. This is again useful for +/// when +/// the data is also linked into an intrusive collection, like a linked list, where traversing the +/// list will end up visiting many of the nodes in the map. If each node was its own Box, this would +/// cause thrashing of the cache - ArenaMap sidesteps this by allocating values in chunks of memory +/// that are friendlier to the cache. /// /// # Cons /// /// * Memory allocated for data stored in the map is not released until the map is dropped. This is -/// a tradeoff made to ensure that the data has a stable location in memory, but the flip side of that -/// is increased memory usage for maps that stick around for a long time. In our case, these maps are -/// relatively short-lived, so it isn't a problem in practice. +/// a tradeoff made to ensure that the data has a stable location in memory, but the flip side of +/// that is increased memory usage for maps that stick around for a long time. In our case, these +/// maps are relatively short-lived, so it isn't a problem in practice. /// * It doesn't provide as rich of an API as HashMap and friends pub struct ArenaMap { keys: Vec>>, @@ -124,10 +130,7 @@ impl ArenaMap { /// Returns true if this map contains `key` pub fn contains(&self, key: K) -> bool { - self.keys - .get(key.index()) - .map(|item| item.is_some()) - .unwrap_or(false) + self.keys.get(key.index()).map(|item| item.is_some()).unwrap_or(false) } /// Adds a new entry to the map, returning the key it is associated to @@ -223,16 +226,18 @@ impl IndexMut for ArenaMap { /// OrderedArenaMap is an extension of ArenaMap that provides for arbitrary ordering of keys/values /// /// This is done using an intrusive linked list alongside an ArenaMap. The list is used to link one -/// key/value pair to the next, so any ordering you wish to implement is possible. This is particularly -/// useful for layout of blocks in a function, or instructions within blocks, as you can precisely position -/// them relative to other blocks/instructions. +/// key/value pair to the next, so any ordering you wish to implement is possible. This is +/// particularly useful for layout of blocks in a function, or instructions within blocks, as you +/// can precisely position them relative to other blocks/instructions. /// /// Because the linked list is intrusive, it is virtually free in terms of space, but comes with the -/// standard overhead for traversals. That said, there are a couple of niceties that give it good overall -/// performance: +/// standard overhead for traversals. That said, there are a couple of niceties that give it good +/// overall performance: /// -/// * It is a doubly-linked list, so you can traverse equally efficiently front-to-back or back-to-front, -/// * It has O(1) indexing; given a key, we can directly obtain a reference to a node, and with that, +/// * It is a doubly-linked list, so you can traverse equally efficiently front-to-back or +/// back-to-front, +/// * It has O(1) indexing; given a key, we can directly obtain a reference to a node, and with +/// that, /// obtain a cursor over the list starting at that node. pub struct OrderedArenaMap { list: LinkedList>, @@ -304,7 +309,8 @@ impl OrderedArenaMap { } } - /// Returns a mutable reference to the value associated with the given key, if present and linked + /// Returns a mutable reference to the value associated with the given key, if present and + /// linked pub fn get_mut(&mut self, key: K) -> Option<&mut V> { let node = self.map.get_mut(key)?; if node.link.is_linked() { @@ -320,7 +326,8 @@ impl OrderedArenaMap { self.map.alloc_key() } - /// Used with `create` when ready to associate data with the allocated key, linking it in to the end of the list + /// Used with `create` when ready to associate data with the allocated key, linking it in to the + /// end of the list pub fn append(&mut self, key: K, value: V) { debug_assert!(!self.contains(key)); let data = self.alloc_node(key, value); @@ -372,10 +379,7 @@ impl OrderedArenaMap { /// clone the map, and drop the original. pub fn remove(&mut self, key: K) { if let Some(value) = self.map.get(key) { - assert!( - value.link.is_linked(), - "cannot remove a value that is not linked" - ); + assert!(value.link.is_linked(), "cannot remove a value that is not linked"); let mut cursor = unsafe { self.list.cursor_mut_from_ptr(value) }; cursor.remove(); } @@ -408,8 +412,8 @@ impl OrderedArenaMap { unsafe { self.list.cursor_from_ptr(ptr) } } - /// Returns a cursor which can be used to traverse the map mutably, in order (front to back), starting - /// at the key given. + /// Returns a cursor which can be used to traverse the map mutably, in order (front to back), + /// starting at the key given. pub fn cursor_mut_at(&mut self, key: K) -> CursorMut<'_, LayoutAdapter> { let ptr = &self.map[key] as *const LayoutNode; unsafe { self.list.cursor_mut_from_ptr(ptr) } @@ -417,7 +421,8 @@ impl OrderedArenaMap { /// Returns an iterator over the key/value pairs in the map. /// - /// The iterator is double-ended, so can be used to traverse the map front-to-back, or back-to-front + /// The iterator is double-ended, so can be used to traverse the map front-to-back, or + /// back-to-front pub fn iter(&self) -> OrderedArenaMapIter<'_, K, V> { OrderedArenaMapIter(self.list.iter()) } diff --git a/hir/src/lib.rs b/hir/src/lib.rs index 451deedfe..89d5c22bc 100644 --- a/hir/src/lib.rs +++ b/hir/src/lib.rs @@ -77,11 +77,7 @@ macro_rules! diagnostic { ($diagnostics:ident, $severity:expr, $msg:literal, $span:expr, $label:expr) => {{ let span = $span; if span.is_unknown() { - $diagnostics - .diagnostic($severity) - .with_message($msg) - .with_note($label) - .emit(); + $diagnostics.diagnostic($severity).with_message($msg).with_note($label).emit(); } else { $diagnostics .diagnostic($severity) @@ -138,9 +134,7 @@ macro_rules! diagnostic { if span2.is_unknown() { diag.with_note($label2).with_note($note).emit(); } else { - diag.with_secondary_label(span2, $label2) - .with_note($note) - .emit(); + diag.with_secondary_label(span2, $label2).with_note($note).emit(); } }}; } @@ -172,38 +166,38 @@ mod tests; mod value; mod write; -pub use self::asm::*; -pub use self::attribute::{attributes, Attribute, AttributeSet, AttributeValue}; -pub use self::block::{Block, BlockData}; -pub use self::builder::{ - DefaultInstBuilder, FunctionBuilder, InstBuilder, InstBuilderBase, ReplaceBuilder, -}; -pub use self::component::*; -pub use self::constants::{Constant, ConstantData, ConstantPool, IntoBytes}; -pub use self::dataflow::DataFlowGraph; -pub use self::display::{Decorator, DisplayValues}; -pub use self::function::*; -pub use self::globals::*; -pub use self::ident::{FunctionIdent, Ident}; -pub use self::immediates::Immediate; -pub use self::insert::{Insert, InsertionPoint}; -pub use self::instruction::*; -pub use self::layout::{ArenaMap, LayoutAdapter, LayoutNode, OrderedArenaMap}; -pub use self::locals::{Local, LocalId}; -pub use self::module::*; -pub use self::pass::{ - AnalysisKey, ConversionPassRegistration, ModuleRewritePassAdapter, PassInfo, - RewritePassRegistration, -}; -pub use self::program::{Linker, LinkerError, Program, ProgramAnalysisKey, ProgramBuilder}; -pub use self::segments::{DataSegment, DataSegmentAdapter, DataSegmentError, DataSegmentTable}; -pub use self::value::{Value, ValueData, ValueList, ValueListPool}; -pub use self::write::{write_external_function, write_function, write_instruction}; +use core::fmt; // Re-export cranelift_entity so that users don't have to hunt for the same version pub use cranelift_entity; -use core::fmt; +pub use self::{ + asm::*, + attribute::{attributes, Attribute, AttributeSet, AttributeValue}, + block::{Block, BlockData}, + builder::{DefaultInstBuilder, FunctionBuilder, InstBuilder, InstBuilderBase, ReplaceBuilder}, + component::*, + constants::{Constant, ConstantData, ConstantPool, IntoBytes}, + dataflow::DataFlowGraph, + display::{Decorator, DisplayValues}, + function::*, + globals::*, + ident::{FunctionIdent, Ident}, + immediates::Immediate, + insert::{Insert, InsertionPoint}, + instruction::*, + layout::{ArenaMap, LayoutAdapter, LayoutNode, OrderedArenaMap}, + locals::{Local, LocalId}, + module::*, + pass::{ + AnalysisKey, ConversionPassRegistration, ModuleRewritePassAdapter, PassInfo, + RewritePassRegistration, + }, + program::{Linker, LinkerError, Program, ProgramAnalysisKey, ProgramBuilder}, + segments::{DataSegment, DataSegmentAdapter, DataSegmentError, DataSegmentTable}, + value::{Value, ValueData, ValueList, ValueListPool}, + write::{write_external_function, write_function, write_instruction}, +}; /// A `ProgramPoint` represents a position in a function where the live range of an SSA value can /// begin or end. It can be either: diff --git a/hir/src/module.rs b/hir/src/module.rs index 60ec74e8f..a654d225a 100644 --- a/hir/src/module.rs +++ b/hir/src/module.rs @@ -57,7 +57,8 @@ pub struct Module { /// modules do not, in exchange for some useful functionality: /// /// * Functions with external linkage are required to use the `Kernel` calling convention. - /// * A kernel module executes in the root context of the Miden VM, allowing one to expose functionality + /// * A kernel module executes in the root context of the Miden VM, allowing one to expose + /// functionality /// that is protected from tampering by other non-kernel functions in the program. /// * Due to the above, you may not reference globals outside the kernel module, from within /// kernel functions, as they are not available in the root context. @@ -65,9 +66,10 @@ pub struct Module { } impl fmt::Display for Module { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use crate::write::DisplayIdent; use std::fmt::Write; + use crate::write::DisplayIdent; + if self.is_kernel { writeln!(f, "kernel {}\n", DisplayIdent(&self.name))?; } else { @@ -115,12 +117,7 @@ impl fmt::Display for Module { )?; match global.init { Some(init) => { - writeln!( - f, - " = ${} {{ id = {} }};", - init.as_u32(), - global.id().as_u32() - )?; + writeln!(f, " = ${} {{ id = {} }};", init.as_u32(), global.id().as_u32())?; } None => { writeln!(f, " {{ id = {} }};", global.id().as_u32())?; @@ -137,9 +134,7 @@ impl fmt::Display for Module { if import.id.module == self.name { continue; } - external_functions - .entry(import.id) - .or_insert_with(|| import.signature.clone()); + external_functions.entry(import.id).or_insert_with(|| import.signature.clone()); } if i > 0 { writeln!(f)?; @@ -175,9 +170,11 @@ impl midenc_session::Emit for Module { fn name(&self) -> Option { Some(self.name.as_symbol()) } + fn output_type(&self) -> midenc_session::OutputType { midenc_session::OutputType::Hir } + fn write_to(&self, mut writer: W) -> std::io::Result<()> { writer.write_fmt(format_args!("{}", self)) } @@ -235,32 +232,35 @@ macro_rules! assert_valid_function { if $function.is_kernel() { assert!($module.is_kernel, "cannot add kernel functions to a non-kernel module"); } else if $module.is_kernel && $function.is_public() { - panic!("functions with external linkage in kernel modules must use the kernel calling convention"); + panic!( + "functions with external linkage in kernel modules must use the kernel calling \ + convention" + ); } - } + }; } impl Module { /// Create a new, empty [Module] pub fn new>(name: S) -> Self { - Self::make(name.into(), /*is_kernel=*/ false) + Self::make(name.into(), /* is_kernel= */ false) } /// Create a new, empty [Module] with the given source location pub fn new_with_span>(name: S, span: SourceSpan) -> Self { let name = Ident::new(Symbol::intern(name.as_ref()), span); - Self::make(name, /*is_kernel=*/ false) + Self::make(name, /* is_kernel= */ false) } /// Create a new, empty kernel [Module] pub fn new_kernel>(name: S) -> Self { - Self::make(name.into(), /*is_kernel=*/ true) + Self::make(name.into(), /* is_kernel= */ true) } /// Create a new, empty kernel [Module] with the given source location pub fn new_kernel_with_span>(name: S, span: SourceSpan) -> Self { let name = Ident::new(Symbol::intern(name.as_ref()), span); - Self::make(name, /*is_kernel=*/ true) + Self::make(name, /* is_kernel= */ true) } fn make(name: Ident, is_kernel: bool) -> Self { @@ -314,7 +314,8 @@ impl Module { &self.globals } - /// Declare a new [GlobalVariable] in this module, with the given name, type, linkage, and optional initializer. + /// Declare a new [GlobalVariable] in this module, with the given name, type, linkage, and + /// optional initializer. /// /// Returns `Err` if a symbol with the same name but conflicting declaration already exists, /// or if the specification of the global variable is invalid in any way. @@ -333,7 +334,8 @@ impl Module { /// Set the initializer for a [GlobalVariable] to `init`. /// - /// Returns `Err` if the initializer conflicts with the current definition of the global in any way. + /// Returns `Err` if the initializer conflicts with the current definition of the global in any + /// way. pub fn set_global_initializer( &mut self, gv: GlobalVariable, @@ -382,11 +384,7 @@ impl Module { /// are namespace conflicts pub fn imports(&self) -> ModuleImportInfo { let mut imports = ModuleImportInfo::default(); - let locals = self - .functions - .iter() - .map(|f| f.id) - .collect::>(); + let locals = self.functions.iter().map(|f| f.id).collect::>(); for function in self.functions.iter() { for import in function.imports() { @@ -414,7 +412,8 @@ impl Module { /// /// NOTE: This function will panic if either of the following rules are violated: /// - /// * If this module is a kernel module, public functions must use the kernel calling convention, + /// * If this module is a kernel module, public functions must use the kernel calling + /// convention, /// however private functions can use any convention. /// * If this module is not a kernel module, functions may not use the kernel calling convention pub fn push(&mut self, function: Box) -> Result<(), SymbolConflictError> { @@ -674,8 +673,7 @@ impl ModuleBuilder { init: I, readonly: bool, ) -> Result<(), DataSegmentError> { - self.module - .declare_data_segment(offset, size, init.into(), readonly) + self.module.declare_data_segment(offset, size, init.into(), readonly) } /// Start building a new function in this module @@ -767,8 +765,7 @@ impl<'m> ModuleFunctionBuilder<'m> { } pub fn append_block_param(&mut self, block: Block, ty: Type, span: SourceSpan) -> Value { - self.data_flow_graph_mut() - .append_block_param(block, ty, span) + self.data_flow_graph_mut().append_block_param(block, ty, span) } pub fn inst_results(&self, inst: Inst) -> &[Value] { @@ -793,9 +790,7 @@ impl<'m> ModuleFunctionBuilder<'m> { M: Into, F: Into, { - self.function - .dfg - .import_function(module.into(), function.into(), signature) + self.function.dfg.import_function(module.into(), function.into(), signature) } pub fn ins<'a, 'b: 'a>(&'b mut self) -> DefaultInstBuilder<'a> { @@ -828,24 +823,45 @@ impl<'m> ModuleFunctionBuilder<'m> { match sig.cc { CallConv::Kernel if is_kernel_module => { if !is_public { - diagnostics.diagnostic(Severity::Error) - .with_message(format!("expected external linkage for kernel function '{}'", &self.function.id)) - .with_note("This function is private, but uses the 'kernel' calling convention. It must either be made public, or use a different convention") + diagnostics + .diagnostic(Severity::Error) + .with_message(format!( + "expected external linkage for kernel function '{}'", + &self.function.id + )) + .with_note( + "This function is private, but uses the 'kernel' calling convention. \ + It must either be made public, or use a different convention", + ) .emit(); return Err(InvalidFunctionError); } } CallConv::Kernel => { - diagnostics.diagnostic(Severity::Error) - .with_message(format!("invalid calling convention for function '{}'", &self.function.id)) - .with_note("The 'kernel' calling convention is only allowed in kernel modules, on functions with external linkage") + diagnostics + .diagnostic(Severity::Error) + .with_message(format!( + "invalid calling convention for function '{}'", + &self.function.id + )) + .with_note( + "The 'kernel' calling convention is only allowed in kernel modules, on \ + functions with external linkage", + ) .emit(); return Err(InvalidFunctionError); } _ if is_kernel_module && is_public => { - diagnostics.diagnostic(Severity::Error) - .with_message(format!("invalid calling convention for function '{}'", &self.function.id)) - .with_note("Functions with external linkage, must use the 'kernel' calling convention when defined in a kernel module") + diagnostics + .diagnostic(Severity::Error) + .with_message(format!( + "invalid calling convention for function '{}'", + &self.function.id + )) + .with_note( + "Functions with external linkage, must use the 'kernel' calling \ + convention when defined in a kernel module", + ) .emit(); return Err(InvalidFunctionError); } diff --git a/hir/src/parser/ast/convert.rs b/hir/src/parser/ast/convert.rs index f5add871d..5d86e52ab 100644 --- a/hir/src/parser/ast/convert.rs +++ b/hir/src/parser/ast/convert.rs @@ -5,11 +5,12 @@ use intrusive_collections::UnsafeRef; use midenc_session::Session; use rustc_hash::FxHashSet; -use crate::parser::ParseError; -use crate::pass::{AnalysisManager, ConversionError, ConversionPass, ConversionResult}; -use crate::{Immediate, Opcode, PassInfo, Type}; - use super::*; +use crate::{ + parser::ParseError, + pass::{AnalysisManager, ConversionError, ConversionPass, ConversionResult}, + Immediate, Opcode, PassInfo, Type, +}; /// This pass converts the syntax tree of an HIR module to HIR #[derive(PassInfo)] @@ -235,10 +236,7 @@ fn try_insert_inst( overflow, operands: [imm, Operand::Value(rhs)], } => { - let imm_ty = values_by_id - .get(&rhs) - .map(|v| v.ty().clone()) - .unwrap_or(Type::Unknown); + let imm_ty = values_by_id.get(&rhs).map(|v| v.ty().clone()).unwrap_or(Type::Unknown); operand_to_immediate(imm, &imm_ty, diagnostics).map(|imm| { Instruction::BinaryOpImm(BinaryOpImm { op, @@ -490,9 +488,16 @@ fn try_insert_inst( let expected_returns = function.signature.results.len(); let actual_returns = operands.len(); if actual_returns != expected_returns { - diagnostics.diagnostic(Severity::Error) + diagnostics + .diagnostic(Severity::Error) .with_message("invalid instruction") - .with_primary_label(span, format!("the current function expects {expected_returns} values to be returned, but {actual_returns} are returned here")) + .with_primary_label( + span, + format!( + "the current function expects {expected_returns} values to be \ + returned, but {actual_returns} are returned here" + ), + ) .emit(); None } else { @@ -560,9 +565,14 @@ fn try_insert_inst( if let Some(ef) = imports_by_id.get(&callee) { entry.insert(ef.item.clone()); } else { - diagnostics.diagnostic(Severity::Error) + diagnostics + .diagnostic(Severity::Error) .with_message("invalid call instruction") - .with_primary_label(callee.span(), "this function is not imported in the current module, you must do so in order to reference it") + .with_primary_label( + callee.span(), + "this function is not imported in the current module, you must do so \ + in order to reference it", + ) .with_secondary_label(span, "invalid callee for this instruction") .emit(); is_valid = false; @@ -624,10 +634,18 @@ fn try_insert_inst( } } operand @ (Operand::Int(_) | Operand::BigInt(_)) => { - diagnostics.diagnostic(Severity::Error) + diagnostics + .diagnostic(Severity::Error) .with_message("invalid immediate operand") - .with_primary_label(operand.span(), "expected an ssa value here, but got an immediate") - .with_secondary_label(span, "only the first argument of this instruction may be an immediate") + .with_primary_label( + operand.span(), + "expected an ssa value here, but got an immediate", + ) + .with_secondary_label( + span, + "only the first argument of this instruction may be an \ + immediate", + ) .emit(); is_valid = false; } @@ -708,10 +726,7 @@ fn is_valid_successor( diagnostics .diagnostic(Severity::Error) .with_message("invalid instruction") - .with_primary_label( - successor.span, - "invalid successor: the named block does not exist", - ) + .with_primary_label(successor.span, "invalid successor: the named block does not exist") .with_secondary_label(parent_span, "found in this instruction") .emit(); Err(successor.id) @@ -853,10 +868,7 @@ fn smallint_to_immediate( diagnostics .diagnostic(Severity::Error) .with_message("invalid immediate operand") - .with_primary_label( - span, - format!("immediates of type {ty} are not yet supported"), - ) + .with_primary_label(span, format!("immediates of type {ty} are not yet supported")) .emit(); None } @@ -883,7 +895,8 @@ fn bigint_to_immediate( use num_traits::cast::ToPrimitive; let is_negative = i.sign() == num_bigint::Sign::Minus; - // NOTE: If we are calling this function, then `i` was too large to fit in an `isize`, so it must be a large integer type + // NOTE: If we are calling this function, then `i` was too large to fit in an `isize`, so it + // must be a large integer type let imm = match ty { Type::U32 if !is_negative => i.to_u32().map(Immediate::U32), Type::I64 => i.to_i64().map(Immediate::I64), @@ -893,17 +906,21 @@ fn bigint_to_immediate( diagnostics .diagnostic(Severity::Error) .with_message("invalid immediate operand") - .with_primary_label( - span, - format!("immediates of type {ty} are not yet supported"), - ) + .with_primary_label(span, format!("immediates of type {ty} are not yet supported")) .emit(); return None; } ty if ty.is_integer() => { - diagnostics.diagnostic(Severity::Error) + diagnostics + .diagnostic(Severity::Error) .with_message("invalid immediate operand") - .with_primary_label(span, format!("expected an immediate of type {ty}, but got {i}, which is out of range for that type")) + .with_primary_label( + span, + format!( + "expected an immediate of type {ty}, but got {i}, which is out of range \ + for that type" + ), + ) .emit(); return None; } @@ -920,9 +937,16 @@ fn bigint_to_immediate( } }; if imm.is_none() { - diagnostics.diagnostic(Severity::Error) + diagnostics + .diagnostic(Severity::Error) .with_message("invalid immediate operand") - .with_primary_label(span, format!("expected an immediate of type {ty}, but got {i}, which is out of range for that type")) + .with_primary_label( + span, + format!( + "expected an immediate of type {ty}, but got {i}, which is out of range for \ + that type" + ), + ) .emit(); } imm diff --git a/hir/src/parser/ast/functions.rs b/hir/src/parser/ast/functions.rs index d01904cd8..574fa5d0e 100644 --- a/hir/src/parser/ast/functions.rs +++ b/hir/src/parser/ast/functions.rs @@ -1,8 +1,7 @@ use core::fmt; -use crate::{AttributeSet, Signature}; - use super::*; +use crate::{AttributeSet, Signature}; /// Represents the declaration of a function in a [Module] #[derive(Spanned)] @@ -60,10 +59,20 @@ impl FunctionDeclaration { let expected_ty = &expected.ty; let declared_ty = &declared.ty; if expected_ty != declared_ty { - diagnostics.diagnostic(Severity::Error) + diagnostics + .diagnostic(Severity::Error) .with_message("invalid function") - .with_primary_label(entry_block.span, "the parameter list of the entry block does not match the function signature") - .with_secondary_label(declared.span, format!("expected a parameter of type {expected_ty}, but got {declared_ty}")) + .with_primary_label( + entry_block.span, + "the parameter list of the entry block does not match the function \ + signature", + ) + .with_secondary_label( + declared.span, + format!( + "expected a parameter of type {expected_ty}, but got {declared_ty}" + ), + ) .emit(); is_valid = false; } diff --git a/hir/src/parser/ast/instruction.rs b/hir/src/parser/ast/instruction.rs index e1720ea54..42f771495 100644 --- a/hir/src/parser/ast/instruction.rs +++ b/hir/src/parser/ast/instruction.rs @@ -1,8 +1,7 @@ use core::fmt; -use crate::{Opcode, Overflow, Type}; - use super::*; +use crate::{Opcode, Overflow, Type}; /// Represents a single instruction. #[derive(Spanned)] diff --git a/hir/src/parser/ast/mod.rs b/hir/src/parser/ast/mod.rs index 9bdf59900..15b40c8bd 100644 --- a/hir/src/parser/ast/mod.rs +++ b/hir/src/parser/ast/mod.rs @@ -4,17 +4,12 @@ mod functions; mod globals; mod instruction; -pub use self::block::*; -pub use self::convert::ConvertAstToHir; -pub use self::functions::*; -pub use self::globals::*; -pub use self::instruction::*; - use std::{collections::BTreeMap, fmt}; use miden_diagnostics::{DiagnosticsHandler, Severity, SourceSpan, Span, Spanned}; use rustc_hash::FxHashMap; +pub use self::{block::*, convert::ConvertAstToHir, functions::*, globals::*, instruction::*}; use crate::{ExternalFunction, FunctionIdent, Ident}; /// This represents the parsed contents of a single Miden IR module @@ -45,9 +40,11 @@ impl midenc_session::Emit for Module { fn name(&self) -> Option { Some(self.name.as_symbol()) } + fn output_type(&self) -> midenc_session::OutputType { midenc_session::OutputType::Ast } + fn write_to(&self, mut writer: W) -> std::io::Result<()> { writer.write_fmt(format_args!("{:#?}", self)) } @@ -138,9 +135,16 @@ impl Module { if let Some(id) = global.init { if !constants_by_id.contains_key(&id) { let id = id.as_u32(); - diagnostics.diagnostic(Severity::Error) + diagnostics + .diagnostic(Severity::Error) .with_message("invalid global variable declaration") - .with_primary_label(global.span, format!("invalid initializer: no constant named '{id}' in this module")) + .with_primary_label( + global.span, + format!( + "invalid initializer: no constant named '{id}' in this \ + module" + ), + ) .emit(); is_valid = false; } @@ -183,9 +187,14 @@ impl Module { let mut is_valid = true; for external in core::mem::take(&mut self.externals).into_iter() { if external.id.module == self.name { - diagnostics.diagnostic(Severity::Error) + diagnostics + .diagnostic(Severity::Error) .with_message("invalid external function declaration") - .with_primary_label(external.span(), "external function declarations may not reference functions in the current module") + .with_primary_label( + external.span(), + "external function declarations may not reference functions in the \ + current module", + ) .emit(); is_valid = false; continue; diff --git a/hir/src/parser/error.rs b/hir/src/parser/error.rs index 3fc9e2ef5..bbe0436c4 100644 --- a/hir/src/parser/error.rs +++ b/hir/src/parser/error.rs @@ -121,11 +121,8 @@ impl ToDiagnostic for ParseError { Diagnostic::error() .with_message("unexpected eof") - .with_labels(vec![Label::primary( - at.source_id(), - SourceSpan::new(at, at), - ) - .with_message(message)]) + .with_labels(vec![Label::primary(at.source_id(), SourceSpan::new(at, at)) + .with_message(message)]) } Self::UnrecognizedToken { span, ref expected, .. @@ -141,9 +138,7 @@ impl ToDiagnostic for ParseError { Diagnostic::error() .with_message("unexpected token") - .with_labels(vec![ - Label::primary(span.source_id(), span).with_message(message) - ]) + .with_labels(vec![Label::primary(span.source_id(), span).with_message(message)]) } Self::ExtraToken { span, .. } => Diagnostic::error() .with_message("extraneous token") diff --git a/hir/src/parser/lexer/error.rs b/hir/src/parser/lexer/error.rs index 033b9faa7..732abbe6d 100644 --- a/hir/src/parser/lexer/error.rs +++ b/hir/src/parser/lexer/error.rs @@ -47,19 +47,19 @@ impl ToDiagnostic for LexicalError { .with_message("invalid integer literal") .with_labels(vec![Label::primary(span.source_id(), span) .with_message(format!("{}", DisplayIntErrorKind(reason)))]), - Self::UnexpectedCharacter { start, .. } => Diagnostic::error() - .with_message("unexpected character") - .with_labels(vec![Label::primary( - start.source_id(), - SourceSpan::new(start, start), - )]), + Self::UnexpectedCharacter { start, .. } => { + Diagnostic::error().with_message("unexpected character").with_labels(vec![ + Label::primary(start.source_id(), SourceSpan::new(start, start)), + ]) + } Self::UnclosedString { span, .. } => Diagnostic::error() .with_message("unclosed string") .with_labels(vec![Label::primary(span.source_id(), span)]), Self::InvalidModuleIdentifier { span, .. } => Diagnostic::error() .with_message("invalid module identifier") .with_labels(vec![Label::primary(span.source_id(), span).with_message( - "module names must be non-empty, start with 'a-z', and only contain ascii alpha-numeric characters, '_', or '::' as a namespacing operator", + "module names must be non-empty, start with 'a-z', and only contain ascii \ + alpha-numeric characters, '_', or '::' as a namespacing operator", )]), Self::InvalidFunctionIdentifier { span, .. } => Diagnostic::error() .with_message("invalid function identifier") diff --git a/hir/src/parser/lexer/mod.rs b/hir/src/parser/lexer/mod.rs index 9a0f40ca6..2960e3882 100644 --- a/hir/src/parser/lexer/mod.rs +++ b/hir/src/parser/lexer/mod.rs @@ -7,11 +7,9 @@ use miden_diagnostics::{SourceIndex, SourceSpan}; use miden_parsing::{Scanner, Source}; use num_traits::Num; +pub use self::{error::LexicalError, token::Token}; use crate::{parser::ParseError, Block, Symbol, Value}; -pub use self::error::LexicalError; -pub use self::token::Token; - /// The value produced by the [Lexer] when iterated pub type Lexed = Result<(SourceIndex, Token, SourceIndex), ParseError>; @@ -368,7 +366,8 @@ where } } - // Returns true if the identifier is a namespaced identifier (contains double colons), false otherwise + // Returns true if the identifier is a namespaced identifier (contains double colons), false + // otherwise fn skip_ident(&mut self, allow_dot: bool) -> bool { let mut is_namespaced = false; loop { @@ -398,10 +397,7 @@ where } fn handle_function_ident(&self, s: &str) -> Token { - if let Some((offset, c)) = s - .char_indices() - .find(|(_, c)| *c == '.' || c.is_whitespace()) - { + if let Some((offset, c)) = s.char_indices().find(|(_, c)| *c == '.' || c.is_whitespace()) { Token::Error(LexicalError::UnexpectedCharacter { start: self.span().start() + offset, found: c, @@ -507,9 +503,9 @@ where match res { Some(Ok((start, Token::FunctionIdent((mid, fid)), end))) => { match last { - // If we parse a namespaced identifier right after the `module` or `kernel` keyword, - // it is a module name, not a function name, so convert it into a Ident token when this - // happens. + // If we parse a namespaced identifier right after the `module` or `kernel` + // keyword, it is a module name, not a function name, so + // convert it into a Ident token when this happens. Token::Module | Token::Kernel => { let module_name = format!("{}::{}", mid, fid); let module_id = Symbol::intern(module_name); diff --git a/hir/src/parser/mod.rs b/hir/src/parser/mod.rs index 2cc2bca3f..e43080efa 100644 --- a/hir/src/parser/mod.rs +++ b/hir/src/parser/mod.rs @@ -20,8 +20,7 @@ lalrpop_mod!( "/parser/grammar.rs" ); -use std::path::Path; -use std::sync::Arc; +use std::{path::Path, sync::Arc}; use miden_diagnostics::SourceFile; use miden_parsing::{FileMapSource, Scanner, Source}; @@ -57,10 +56,7 @@ impl<'a> Parser<'a> { where T: Parse, { - let id = self - .session - .codemap - .add("nofile", source.as_ref().to_string()); + let id = self.session.codemap.add("nofile", source.as_ref().to_string()); let file = self.session.codemap.get(id).unwrap(); self.parse(file) } @@ -124,12 +120,7 @@ impl Parse for crate::Module { let mut next_var = 0; let result = ::Grammar::new() - .parse( - &parser.session.diagnostics, - &parser.session.codemap, - &mut next_var, - tokens, - ) + .parse(&parser.session.diagnostics, &parser.session.codemap, &mut next_var, tokens) .map(Box::new); match result { Ok(ast) => { @@ -138,15 +129,15 @@ impl Parse for crate::Module { } let mut analyses = AnalysisManager::new(); let mut convert_to_hir = ConvertAstToHir; - convert_to_hir - .convert(ast, &mut analyses, parser.session) - .map_err(|err| match err { + convert_to_hir.convert(ast, &mut analyses, parser.session).map_err( + |err| match err { ConversionError::Failed(err) => match err.downcast::() { Ok(err) => err, Err(_) => ParseError::InvalidModule, }, _ => ParseError::InvalidModule, - }) + }, + ) } Err(lalrpop_util::ParseError::User { error }) => Err(error), Err(err) => Err(err.into()), diff --git a/hir/src/parser/tests/mod.rs b/hir/src/parser/tests/mod.rs index 60028a0fd..89cda17f2 100644 --- a/hir/src/parser/tests/mod.rs +++ b/hir/src/parser/tests/mod.rs @@ -1,17 +1,13 @@ use miden_diagnostics::{SourceSpan, Span}; -use crate::parser::ast::*; use crate::{ - AbiParam, ArgumentExtension, ArgumentPurpose, CallConv, ExternalFunction, FunctionIdent, Ident, - Linkage, Opcode, Overflow, Signature, StructType, Type, + parser::ast::*, AbiParam, ArgumentExtension, ArgumentPurpose, CallConv, ExternalFunction, + FunctionIdent, Ident, Linkage, Opcode, Overflow, Signature, StructType, Type, }; macro_rules! ident { ($name:ident) => { - Ident::new( - crate::Symbol::intern(stringify!($name)), - miden_diagnostics::SourceSpan::UNKNOWN, - ) + Ident::new(crate::Symbol::intern(stringify!($name)), miden_diagnostics::SourceSpan::UNKNOWN) }; } @@ -25,11 +21,8 @@ fn parser_integration_test() { // global internal @DEADBEEF : u32 = 0xdeadbeef { id = 0 }; let deadbeef_const_id = crate::Constant::from_u32(0); - let deadbeef_const = ConstantDeclaration::new( - dummy_sourcespan, - deadbeef_const_id, - "deadbeef".parse().unwrap(), - ); + let deadbeef_const = + ConstantDeclaration::new(dummy_sourcespan, deadbeef_const_id, "deadbeef".parse().unwrap()); let deadbeef = GlobalVarDeclaration::new( dummy_sourcespan, crate::GlobalVariable::from_u32(0), diff --git a/hir/src/parser/tests/utils.rs b/hir/src/parser/tests/utils.rs index b3d79e28b..27c96c392 100644 --- a/hir/src/parser/tests/utils.rs +++ b/hir/src/parser/tests/utils.rs @@ -1,13 +1,11 @@ -use std::path::Path; -use std::sync::Arc; +use std::{path::Path, sync::Arc}; use miden_diagnostics::{Emitter, Verbosity}; use midenc_session::{Options, Warnings}; use pretty_assertions::assert_eq; use crate::{ - parser::ast::Module, - parser::{ParseError, Parser}, + parser::{ast::Module, ParseError, Parser}, testing::TestContext, }; @@ -73,7 +71,8 @@ impl ParseTest { /// This adds a new in-memory file to the [CodeMap] for this test. /// - /// This is used when we want to write a test with imports, without having to place files on disk + /// This is used when we want to write a test with imports, without having to place files on + /// disk #[allow(unused)] pub fn add_virtual_file>(&self, name: P, content: String) { self.context.session.codemap.add(name.as_ref(), content); @@ -121,8 +120,8 @@ impl ParseTest { } } - /// Parses a [Module] from the given source string and asserts that executing the test will result - /// in the expected AST. + /// Parses a [Module] from the given source string and asserts that executing the test will + /// result in the expected AST. #[track_caller] pub fn expect_module(&self, source: &str, expected: &crate::Module) { match self.parse_module(source) { @@ -136,8 +135,8 @@ impl ParseTest { } } - /// Parses a [Module] from the given source string and asserts that executing the test will result - /// in the expected AST. + /// Parses a [Module] from the given source string and asserts that executing the test will + /// result in the expected AST. #[track_caller] #[allow(unused)] pub fn expect_module_ast(&self, source: &str, expected: Module) { diff --git a/hir/src/pass/analysis.rs b/hir/src/pass/analysis.rs index f1ad2d974..3523728c6 100644 --- a/hir/src/pass/analysis.rs +++ b/hir/src/pass/analysis.rs @@ -1,6 +1,8 @@ -use std::any::{Any, TypeId}; -use std::hash::Hash; -use std::rc::Rc; +use std::{ + any::{Any, TypeId}, + hash::Hash, + rc::Rc, +}; use midenc_session::Session; use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; @@ -265,26 +267,21 @@ impl AnalysisManager { A: Analysis, { let key = CachedAnalysisKey::new::(key); - self.cached - .get(&key) - .cloned() - .map(preservable_analysis_to_concrete) + self.cached.get(&key).cloned().map(preservable_analysis_to_concrete) } - /// Get a reference to the analysis of the requested type, for the given entity, or panics with `msg` + /// Get a reference to the analysis of the requested type, for the given entity, or panics with + /// `msg` pub fn expect(&self, key: &<::Entity as AnalysisKey>::Key, msg: &str) -> Rc where A: Analysis, { let key = CachedAnalysisKey::new::(key); - self.cached - .get(&key) - .cloned() - .map(preservable_analysis_to_concrete) - .expect(msg) + self.cached.get(&key).cloned().map(preservable_analysis_to_concrete).expect(msg) } - /// Get a reference to the analysis of the requested type, or the default value, for the given entity, if available + /// Get a reference to the analysis of the requested type, or the default value, for the given + /// entity, if available /// /// If unavailable, and the default value is returned, that value is not cached. pub fn get_or_default(&self, key: &<::Entity as AnalysisKey>::Key) -> Rc diff --git a/hir/src/pass/conversion.rs b/hir/src/pass/conversion.rs index 30d0068a3..b464ed96b 100644 --- a/hir/src/pass/conversion.rs +++ b/hir/src/pass/conversion.rs @@ -24,7 +24,8 @@ impl

ConversionPassInfo for P where P: PassInfo + ConversionPass {} /// A [ConversionPass] is a pass which applies a change in representation to some compiler entity. /// -/// Specifically, this is used to convert between intermediate representations/dialects in the compiler. +/// Specifically, this is used to convert between intermediate representations/dialects in the +/// compiler. /// /// For example, a conversion pass would be used to lower a `miden_hir::parser::ast::Module` /// to a `miden_hir::Module`. Each conversion between dialects like this can be thought of diff --git a/hir/src/pass/mod.rs b/hir/src/pass/mod.rs index e9ae7c58c..c34f75d5f 100644 --- a/hir/src/pass/mod.rs +++ b/hir/src/pass/mod.rs @@ -28,12 +28,10 @@ mod analysis; mod conversion; mod rewrite; -pub use self::analysis::*; -pub use self::conversion::*; -pub use self::rewrite::*; - use midenc_session::Session; +pub use self::{analysis::*, conversion::*, rewrite::*}; + /// This trait provides descriptive information about a pass /// /// This is primarily intended to assist in registering passes with the pass manager @@ -57,16 +55,16 @@ pub trait PassInfo { const DESCRIPTION: &'static str; } -/// The [Pass] trait represents a fallible operation which takes an input of any type, and produces an -/// output of any type. This is intentionally abstract, and is intended as a building block for compiler -/// pipelines. +/// The [Pass] trait represents a fallible operation which takes an input of any type, and produces +/// an output of any type. This is intentionally abstract, and is intended as a building block for +/// compiler pipelines. /// -/// [Pass] is in fact so abstract, that it is automatically implemented for any Rust function whose type -/// is representable by `FnMut(I) -> Result`. +/// [Pass] is in fact so abstract, that it is automatically implemented for any Rust function whose +/// type is representable by `FnMut(I) -> Result`. /// -/// Implementations of [Pass] can be combined via [Pass::chain], which returns an instantiation of the -/// [Chain] type that itself implements [Pass]. This permits any number of passes to be combined/chained -/// together and passed around as a value. +/// Implementations of [Pass] can be combined via [Pass::chain], which returns an instantiation of +/// the [Chain] type that itself implements [Pass]. This permits any number of passes to be +/// combined/chained together and passed around as a value. pub trait Pass { type Input<'a>; type Output<'a>; @@ -96,9 +94,9 @@ impl Pass for &mut P where P: for<'a> Pass = T, Output<'a> = U, Error = E>, { + type Error = E; type Input<'a> = T; type Output<'a> = U; - type Error = E; fn run<'a>( &mut self, @@ -113,9 +111,9 @@ impl Pass for Box

where P: ?Sized + for<'a> Pass = T, Output<'a> = U, Error = E>, { + type Error = E; type Input<'a> = T; type Output<'a> = U; - type Error = E; fn run<'a>( &mut self, @@ -127,9 +125,9 @@ where } } impl Pass for dyn FnMut(T, &mut AnalysisManager, &Session) -> Result { + type Error = E; type Input<'a> = T; type Output<'a> = U; - type Error = E; #[inline] fn run<'a>( @@ -180,9 +178,9 @@ where A: for<'a> Pass, B: for<'a> Pass = ::Output<'a>, Error = E>, { + type Error = ::Error; type Input<'a> = ::Input<'a>; type Output<'a> = ::Output<'a>; - type Error = ::Error; fn run<'a>( &mut self, diff --git a/hir/src/pass/rewrite.rs b/hir/src/pass/rewrite.rs index 6d744826f..8f6fccaca 100644 --- a/hir/src/pass/rewrite.rs +++ b/hir/src/pass/rewrite.rs @@ -131,9 +131,9 @@ where } } impl PassInfo for ModuleRewritePassAdapter { + const DESCRIPTION: &'static str = ::DESCRIPTION; const FLAG: &'static str = ::FLAG; const SUMMARY: &'static str = ::SUMMARY; - const DESCRIPTION: &'static str = ::DESCRIPTION; } impl RewritePass for ModuleRewritePassAdapter where diff --git a/hir/src/program/linker.rs b/hir/src/program/linker.rs index d594998d9..5b37fb61a 100644 --- a/hir/src/program/linker.rs +++ b/hir/src/program/linker.rs @@ -30,7 +30,8 @@ pub enum LinkerError { /// * The calling convention is different /// * The number and/or types of the arguments and results are not the same /// * A special purpose parameter is declared in one signature but not the other - /// * Argument extension conflicts, i.e. one signature says a parameter is zero-extended, the other sign-extended + /// * Argument extension conflicts, i.e. one signature says a parameter is zero-extended, the + /// other sign-extended #[error( "signature mismatch for '{0}': external function declaration does not match definition" )] @@ -40,14 +41,20 @@ pub enum LinkerError { /// linkage. /// /// This error is a variant of `SignatureMismatch`, but occurs when the signature is otherwise - /// correct, but is ultimately an invalid declaration because the function should not be visible - /// outside its containing module. - #[error("invalid reference to '{0}': only functions with external linkage can be referenced from other modules")] + /// correct, but is ultimately an invalid declaration because the function should not be + /// visible outside its containing module. + #[error( + "invalid reference to '{0}': only functions with external linkage can be referenced from \ + other modules" + )] LinkageMismatch(FunctionIdent), /// A cycle in the call graph was found starting at the given function. /// /// This occurs due to recursion (self or mutual), and is not supported by Miden. - #[error("encountered an invalid cycle in the call graph caused by a call from '{caller}' to '{callee}'")] + #[error( + "encountered an invalid cycle in the call graph caused by a call from '{caller}' to \ + '{callee}'" + )] InvalidCycle { caller: FunctionIdent, callee: FunctionIdent, @@ -56,7 +63,10 @@ pub enum LinkerError { #[error("invalid entrypoint '{0}': must have external linkage")] InvalidEntryLinkage(FunctionIdent), /// Occurs when attempting to set the program entrypoint when it has already been set - #[error("conflicting entrypoints: '{current}' conflicts with previously declared entrypoint '{prev}'")] + #[error( + "conflicting entrypoints: '{current}' conflicts with previously declared entrypoint \ + '{prev}'" + )] InvalidMultipleEntry { current: FunctionIdent, prev: FunctionIdent, @@ -70,8 +80,8 @@ pub enum LinkerError { SegmentError(#[from] DataSegmentError), /// A conflict between two global variables with the same symbol was detected. /// - /// When this occurs, the definitions must have been in separate modules, with external linkage, - /// and they disagree on the type of the value or its initializer. + /// When this occurs, the definitions must have been in separate modules, with external + /// linkage, and they disagree on the type of the value or its initializer. #[error(transparent)] GlobalVariableError(#[from] GlobalVariableError), } @@ -85,16 +95,18 @@ enum Node { Function(FunctionIdent), } -/// The [Linker] performs a similar role in conjunction with the Miden compiler, as the system linker -/// does (e.g. `ld`) when used with compilers like `clang` or `rustc`. +/// The [Linker] performs a similar role in conjunction with the Miden compiler, as the system +/// linker does (e.g. `ld`) when used with compilers like `clang` or `rustc`. /// /// As a (very) rough overview, a typical linker is given a set of object files containing machine -/// code and data, one for every translation unit participating in the link (e.g. for Rust the translation -/// unit is a single crate). The linker will also be informed when dependencies are expected to be provided -/// at runtime, i.e. dynamic libraries. With this, a linker does the following: +/// code and data, one for every translation unit participating in the link (e.g. for Rust the +/// translation unit is a single crate). The linker will also be informed when dependencies are +/// expected to be provided at runtime, i.e. dynamic libraries. With this, a linker does the +/// following: /// /// * Determines the final layout of all code and data in the executable or library being produced, -/// this allows the linker to know the absolute and/or relative address for every symbol in the program. +/// this allows the linker to know the absolute and/or relative address for every symbol in the +/// program. /// * Ensures that all referenced symbols (functions/globals) are defined, or that there are runtime /// dependencies that will satisfy the missing symbols (in practice, what actually happens is the /// static linker, i.e. `ld`, assumes missing symbols will be provided by the runtime dependencies, @@ -104,31 +116,39 @@ enum Node { /// layout of the program in memory is known. /// * Emits the linked program in binary form, either as an executable or as a library /// -/// However, there a couple of things that make [Linker] somewhat different than your typical system linker: +/// However, there a couple of things that make [Linker] somewhat different than your typical system +/// linker: /// -/// * We do not emit assembly/run the assembler prior to linking. This is because Miden Assembly (MASM) -/// does not have a way to represent things like data segments or global variables natively. Instead, the -/// linker is responsible for laying those out in memory ahead of time, and then all operations involving -/// them are lowered to use absolute addresses. -/// * [Linker] does not emit the final binary form of the program. It still plans the layout of program data -/// in memory, and performs the same type of validations as a typical linker, but the output of the linker -/// is a [Program], which must be emitted as Miden Assembly in a separate step _after_ being linked. -/// * We cannot guarantee that the [Program] we emit constitutes a closed set of modules/functions, even -/// accounting for functions whose definitions will be provided at runtime. This is because the Miden VM -/// acts as the final assembler of the programs it runs, and if the [Program] we emit is used as a library, -/// we can't know what other modules might end up being linked into the final program run by the VM. As a -/// result, it is assumed that any code introduced separately is either: -/// 1. memory-agnostic, i.e. it doesn't use the heap and/or make any assumptions about the heap layout. -/// 2. compatible with the layout decided upon by the linker, i.e. it uses well-known allocator functions -/// like `malloc`; or it places its memory in the range 2^30-2^31 for user contexts, or 2^30-(2^32 - 2^30) -/// for root contexts (the latter allocates a separate region for syscall locals). The linker will always -/// reserve memory starting at address 2^30 for locals and "unmanaged" memory allocations, to support -/// scenarios whereby a linked library is used with a program that needs its own region of heap to manage. -/// * Miden has separate address spaces depending on the context in which a function is executed, i.e. the root -/// vs user context distinction. Currently, all programs are assumed to be executed in the root context, and -/// we do not provide instructions for executing calls in another context. However, we will eventually be linking -/// programs which have a potentially unbounded number of address spaces, which is an additional complication -/// that your typical linker doesn't have to deal with +/// * We do not emit assembly/run the assembler prior to linking. This is because Miden Assembly +/// (MASM) +/// does not have a way to represent things like data segments or global variables natively. +/// Instead, the linker is responsible for laying those out in memory ahead of time, and then all +/// operations involving them are lowered to use absolute addresses. +/// * [Linker] does not emit the final binary form of the program. It still plans the layout of +/// program data +/// in memory, and performs the same type of validations as a typical linker, but the output of the +/// linker is a [Program], which must be emitted as Miden Assembly in a separate step _after_ being +/// linked. +/// * We cannot guarantee that the [Program] we emit constitutes a closed set of modules/functions, +/// even +/// accounting for functions whose definitions will be provided at runtime. This is because the +/// Miden VM acts as the final assembler of the programs it runs, and if the [Program] we emit is +/// used as a library, we can't know what other modules might end up being linked into the final +/// program run by the VM. As a result, it is assumed that any code introduced separately is either: +/// 1. memory-agnostic, i.e. it doesn't use the heap and/or make any assumptions about the heap +/// layout. +/// 2. compatible with the layout decided upon by the linker, i.e. it uses well-known allocator +/// functions like `malloc`; or it places its memory in the range 2^30-2^31 for user contexts, +/// or 2^30-(2^32 - 2^30) for root contexts (the latter allocates a separate region for syscall +/// locals). The linker will always reserve memory starting at address 2^30 for locals and +/// "unmanaged" memory allocations, to support scenarios whereby a linked library is used with +/// a program that needs its own region of heap to manage. +/// * Miden has separate address spaces depending on the context in which a function is executed, +/// i.e. the root +/// vs user context distinction. Currently, all programs are assumed to be executed in the root +/// context, and we do not provide instructions for executing calls in another context. However, we +/// will eventually be linking programs which have a potentially unbounded number of address spaces, +/// which is an additional complication that your typical linker doesn't have to deal with pub struct Linker { /// This is the program being constructed by the linker program: Box, @@ -332,8 +352,8 @@ impl Linker { /// following tasks: /// /// * Verify that all referenced modules exist, or are known to be provided at runtime - /// * Verify that all referenced functions exist, or are known to be provided at runtime, - /// and that the signature known to the caller matches the actual definition. + /// * Verify that all referenced functions exist, or are known to be provided at runtime, and + /// that the signature known to the caller matches the actual definition. /// * Verifies that the entrypoint, if set, is valid /// * Verify that there are no cycles in the call graph, i.e. that there is no recursion present /// * Verify that all references to global symbols have corresponding definitions @@ -356,9 +376,8 @@ impl Linker { } let module = &self.pending[&entry.module]; - let function = module - .function(entry.function) - .ok_or(LinkerError::MissingFunction(entry))?; + let function = + module.function(entry.function).ok_or(LinkerError::MissingFunction(entry))?; if !function.is_public() { return Err(LinkerError::InvalidEntryLinkage(entry)); } @@ -388,14 +407,14 @@ impl Linker { // The module is present, so we must verify that the function is defined in that module let module = &self.pending[&node.module]; - let function = module - .function(node.function) - .ok_or(LinkerError::MissingFunction(node))?; + let function = + module.function(node.function).ok_or(LinkerError::MissingFunction(node))?; let is_externally_linkable = function.is_public(); // Next, visit all of the dependent functions, and ensure their signatures match for dependent_id in self.callgraph.neighbors_directed(node, Direction::Incoming) { - // If the dependent is in another module, but the function has internal linkage, raise an error + // If the dependent is in another module, but the function has internal linkage, + // raise an error if dependent_id.module != node.module && !is_externally_linkable { return Err(LinkerError::LinkageMismatch(node)); } @@ -404,10 +423,8 @@ impl Linker { let dependent_function = dependent_module .function(dependent_id.function) .expect("dependency graph is outdated"); - let external_ref = dependent_function - .dfg - .get_import(&node) - .expect("dependency graph is outdated"); + let external_ref = + dependent_function.dfg.get_import(&node).expect("dependency graph is outdated"); verify_matching_signature( function.id, &function.signature, @@ -460,7 +477,8 @@ impl Linker { // We provide three globals for managing the heap, based on the layout // of the data segments and these globals. let globals_offset = self.program.segments.next_available_offset(); - // Compute the start of the heap by finding the end of the globals segment, aligned to the nearest word boundary + // Compute the start of the heap by finding the end of the globals segment, aligned to the + // nearest word boundary let heap_base = globals_offset .checked_add( self.program @@ -474,26 +492,22 @@ impl Linker { let hp = heap_base.to_le_bytes(); // Initialize all 3 globals with the computed heap pointer let heap_ptr_ty = Type::Ptr(Box::new(Type::U8)); - self.program.globals.declare("HEAP_BASE".into(), heap_ptr_ty.clone(), Linkage::External, Some(hp.into())).expect("unable to declare HEAP_BASE, a conflicting global by that name was already defined"); self.program .globals - .declare( - "HEAP_TOP".into(), - heap_ptr_ty.clone(), - Linkage::External, - Some(hp.into()), - ) + .declare("HEAP_BASE".into(), heap_ptr_ty.clone(), Linkage::External, Some(hp.into())) + .expect( + "unable to declare HEAP_BASE, a conflicting global by that name was already \ + defined", + ); + self.program + .globals + .declare("HEAP_TOP".into(), heap_ptr_ty.clone(), Linkage::External, Some(hp.into())) .expect( "unable to declare HEAP_TOP, a conflicting global by that name was already defined", ); self.program .globals - .declare( - "HEAP_END".into(), - heap_ptr_ty, - Linkage::External, - Some(hp.into()), - ) + .declare("HEAP_END".into(), heap_ptr_ty, Linkage::External, Some(hp.into())) .expect( "unable to declare HEAP_END, a conflicting global by that name was already defined", ); @@ -573,12 +587,8 @@ fn is_matching_param(expected: &AbiParam, actual: &AbiParam) -> bool { fn validate_callgraph(callgraph: &DiGraphMap) -> Result<(), LinkerError> { use petgraph::visit::{depth_first_search, DfsEvent, IntoNodeIdentifiers}; - depth_first_search( - callgraph, - callgraph.node_identifiers(), - |event| match event { - DfsEvent::BackEdge(caller, callee) => Err(LinkerError::InvalidCycle { caller, callee }), - _ => Ok(()), - }, - ) + depth_first_search(callgraph, callgraph.node_identifiers(), |event| match event { + DfsEvent::BackEdge(caller, callee) => Err(LinkerError::InvalidCycle { caller, callee }), + _ => Ok(()), + }) } diff --git a/hir/src/program/mod.rs b/hir/src/program/mod.rs index 205b72523..1dd2c0b69 100644 --- a/hir/src/program/mod.rs +++ b/hir/src/program/mod.rs @@ -1,10 +1,10 @@ mod linker; use core::ops::{Deref, DerefMut}; + use intrusive_collections::RBTree; pub use self::linker::{Linker, LinkerError}; - use super::*; /// A [Program] is a collection of [Module]s that are being compiled together as a package. @@ -19,10 +19,10 @@ use super::*; /// /// This structure is intended to be allocated via [std::sync::Arc], so that it can be shared /// across multiple threads which are emitting/compiling modules at the same time. It is designed -/// so that individual fields are locked, rather than the structure as a whole, to minimize contention. -/// The intuition is that, in general, changes at the [Program] level are relatively infrequent, i.e. -/// only when declaring a new [Module], or [GlobalVariable], do we actually need to mutate the structure. -/// In all other situations, changes are scoped at the [Module] level. +/// so that individual fields are locked, rather than the structure as a whole, to minimize +/// contention. The intuition is that, in general, changes at the [Program] level are relatively +/// infrequent, i.e. only when declaring a new [Module], or [GlobalVariable], do we actually need to +/// mutate the structure. In all other situations, changes are scoped at the [Module] level. #[derive(Default)] pub struct Program { /// This tree stores all of the modules being compiled as part of the current program. @@ -67,8 +67,7 @@ impl Program { /// Returns the [FunctionIdent] corresponding to the program entrypoint pub fn entrypoint(&self) -> Option { - self.entrypoint - .or_else(|| self.modules.iter().find_map(|m| m.entrypoint())) + self.entrypoint.or_else(|| self.modules.iter().find_map(|m| m.entrypoint())) } /// Return a reference to the module table for this program @@ -121,7 +120,8 @@ impl crate::pass::AnalysisKey for Program { /// This struct provides an ergonomic way to construct a [Program] in an imperative fashion. /// -/// Simply create the builder, add/build one or more modules, then call `link` to obtain a [Program]. +/// Simply create the builder, add/build one or more modules, then call `link` to obtain a +/// [Program]. pub struct ProgramBuilder<'a> { modules: std::collections::BTreeMap>, entry: Option, @@ -186,9 +186,7 @@ impl<'a> ProgramBuilder<'a> { /// Link a [Program] from the current [ProgramBuilder] state pub fn link(self) -> Result, LinkerError> { let mut linker = Linker::new(); - let entrypoint = self - .entry - .or_else(|| self.modules.values().find_map(|m| m.entrypoint())); + let entrypoint = self.entry.or_else(|| self.modules.values().find_map(|m| m.entrypoint())); if let Some(entry) = entrypoint { linker.with_entrypoint(entry)?; } diff --git a/hir/src/segments.rs b/hir/src/segments.rs index 1a27ab5e3..eb0fc5c2b 100644 --- a/hir/src/segments.rs +++ b/hir/src/segments.rs @@ -14,7 +14,10 @@ use super::{Alignable, ConstantData, Offset}; #[derive(Debug, thiserror::Error)] pub enum DataSegmentError { /// The current segment overlaps with a previously allocated segment - #[error("invalid data segment: segment of {size1} bytes at {offset1:#x} overlaps with segment of {size2} bytes at {offset2:#x}")] + #[error( + "invalid data segment: segment of {size1} bytes at {offset1:#x} overlaps with segment of \ + {size2} bytes at {offset2:#x}" + )] OverlappingSegments { offset1: Offset, size1: u32, @@ -24,7 +27,10 @@ pub enum DataSegmentError { /// The current segment and a previous definition of that segment do /// not agree on the data or read/write properties of the memory they /// represent. - #[error("invalid data segment: segment at {0:#x} conflicts with a previous segment declaration at this address")] + #[error( + "invalid data segment: segment at {0:#x} conflicts with a previous segment declaration at \ + this address" + )] Mismatch(Offset), /// The current segment and size do not fall in the boundaries of the heap /// which is allocatable to globals and other heap allocations. @@ -32,13 +38,22 @@ pub enum DataSegmentError { /// For example, Miden reserves some amount of memory for procedure locals /// at a predetermined address, and we do not permit segments to be allocated /// past that point. - #[error("invalid data segment: segment of {size} bytes at {offset:#x} would extend beyond the end of the usable heap")] + #[error( + "invalid data segment: segment of {size} bytes at {offset:#x} would extend beyond the end \ + of the usable heap" + )] OutOfBounds { offset: Offset, size: u32 }, /// The initializer for the current segment has a size greater than `u32::MAX` bytes - #[error("invalid data segment: segment at {0:#x} was declared with an initializer larger than 2^32 bytes")] + #[error( + "invalid data segment: segment at {0:#x} was declared with an initializer larger than \ + 2^32 bytes" + )] InitTooLarge(Offset), /// The initializer for the current segment has a size greater than the declared segment size - #[error("invalid data segment: segment of {size} bytes at {offset:#x} has an initializer of {actual} bytes")] + #[error( + "invalid data segment: segment of {size} bytes at {offset:#x} has an initializer of \ + {actual} bytes" + )] InitOutOfBounds { offset: Offset, size: u32, @@ -46,7 +61,8 @@ pub enum DataSegmentError { }, } -/// Similar to [GlobalVariableTable], this structure is used to track data segments in a module or program. +/// Similar to [GlobalVariableTable], this structure is used to track data segments in a module or +/// program. #[derive(Default)] pub struct DataSegmentTable { segments: LinkedList, @@ -55,9 +71,7 @@ impl Clone for DataSegmentTable { fn clone(&self) -> Self { let mut table = Self::default(); for segment in self.segments.iter() { - table - .segments - .push_back(UnsafeRef::from_box(Box::new(segment.clone()))); + table.segments.push_back(UnsafeRef::from_box(Box::new(segment.clone()))); } table } @@ -221,10 +235,8 @@ impl DataSegment { readonly: bool, ) -> Result { // Require the initializer data to be no larger than 2^32 bytes - let init_size = init - .len() - .try_into() - .map_err(|_| DataSegmentError::InitTooLarge(offset))?; + let init_size = + init.len().try_into().map_err(|_| DataSegmentError::InitTooLarge(offset))?; // Require the initializer to fit within the declared bounds if size < init_size { @@ -237,9 +249,7 @@ impl DataSegment { // Require the entire segment to fit within the linear memory address space let size = core::cmp::max(size, init_size); - offset - .checked_add(size) - .ok_or(DataSegmentError::OutOfBounds { offset, size })?; + offset.checked_add(size).ok_or(DataSegmentError::OutOfBounds { offset, size })?; Ok(Self { link: Default::default(), diff --git a/hir/src/testing.rs b/hir/src/testing.rs index ff50a8251..3b6645ea6 100644 --- a/hir/src/testing.rs +++ b/hir/src/testing.rs @@ -47,10 +47,7 @@ impl TestContext { /// Add a source file to this context pub fn add>(&mut self, path: P) -> miden_diagnostics::SourceId { - self.session - .codemap - .add_file(path) - .expect("invalid source file") + self.session.codemap.add_file(path).expect("invalid source file") } /// Get a [SourceSpan] corresponding to the callsite of this function @@ -58,22 +55,17 @@ impl TestContext { #[inline(never)] pub fn current_span(&self) -> miden_diagnostics::SourceSpan { let caller = core::panic::Location::caller(); - let caller_file = Path::new(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .join(caller.file()); - let source_id = self - .session - .codemap - .add_file(caller_file) - .expect("invalid source file"); + let caller_file = + Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().join(caller.file()); + let source_id = self.session.codemap.add_file(caller_file).expect("invalid source file"); self.span(source_id, caller.line(), caller.column()) } - /// Get a [SourceSpan] representing the location in the given source file (by id), line and column + /// Get a [SourceSpan] representing the location in the given source file (by id), line and + /// column /// - /// It is expected that line and column are 1-indexed, so they will be shifted to be 0-indexed, make - /// sure to add 1 if you already have a 0-indexed line/column on hand + /// It is expected that line and column are 1-indexed, so they will be shifted to be 0-indexed, + /// make sure to add 1 if you already have a 0-indexed line/column on hand pub fn span( &self, source_id: miden_diagnostics::SourceId, @@ -90,19 +82,14 @@ impl TestContext { #[macro_export] macro_rules! current_file { () => { - std::path::Path::new(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .join(file!()) + std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().join(file!()) }; } #[macro_export] macro_rules! span { ($codemap:ident, $src:ident) => { - $codemap - .line_column_to_span($src, line!() - 1, column!() - 1) - .unwrap() + $codemap.line_column_to_span($src, line!() - 1, column!() - 1).unwrap() }; } @@ -131,9 +118,7 @@ pub fn issue56(builder: &mut ModuleBuilder, context: &TestContext) -> FunctionId cc: CallConv::SystemV, linkage: Linkage::External, }; - let mut fb = builder - .function("entrypoint", sig) - .expect("unexpected symbol conflict"); + let mut fb = builder.function("entrypoint", sig).expect("unexpected symbol conflict"); let entry = fb.current_block(); // Get the value for `v0` and `v1` @@ -144,9 +129,7 @@ pub fn issue56(builder: &mut ModuleBuilder, context: &TestContext) -> FunctionId let v3 = fb.ins().lt(v0, v1, context.current_span()); let v4 = fb.ins().cast(v3, Type::I32, context.current_span()); - let v5 = fb - .ins() - .neq_imm(v4, Immediate::I32(0), context.current_span()); + let v5 = fb.ins().neq_imm(v4, Immediate::I32(0), context.current_span()); let v6 = fb.ins().select(v5, v0, v1, context.current_span()); let v7 = fb.ins().i32(0, context.current_span()); let cond = fb.ins().i1(true, context.current_span()); @@ -156,8 +139,7 @@ pub fn issue56(builder: &mut ModuleBuilder, context: &TestContext) -> FunctionId let v8 = fb.append_block_param(block1, Type::I32, context.current_span()); let v10 = fb.append_block_param(block2, Type::I32, context.current_span()); - fb.ins() - .cond_br(cond, block1, &[v6], block2, &[v6], context.current_span()); + fb.ins().cond_br(cond, block1, &[v6], block2, &[v6], context.current_span()); fb.switch_to_block(block1); let v9 = fb.ins().add_wrapping(v7, v8, context.current_span()); @@ -227,9 +209,7 @@ pub fn fib1(builder: &mut ModuleBuilder, context: &TestContext) -> FunctionIdent cc: CallConv::SystemV, linkage: Linkage::External, }; - let mut fb = builder - .function("fib", sig) - .expect("unexpected symbol conflict"); + let mut fb = builder.function("fib", sig).expect("unexpected symbol conflict"); let entry = fb.current_block(); // Get the value for `n` @@ -251,30 +231,23 @@ pub fn fib1(builder: &mut ModuleBuilder, context: &TestContext) -> FunctionIdent let loop_exit = fb.create_block(); let result = fb.append_block_param(loop_exit, Type::U32, context.current_span()); - // Now, starting from the entry block, we build out the rest of the function in control flow order + // Now, starting from the entry block, we build out the rest of the function in control flow + // order fb.switch_to_block(entry); let a0 = fb.ins().u32(0, context.current_span()); let b0 = fb.ins().u32(1, context.current_span()); let i0 = fb.ins().u32(0, context.current_span()); - fb.ins() - .br(loop_header, &[a0, b0, i0], context.current_span()); + fb.ins().br(loop_header, &[a0, b0, i0], context.current_span()); fb.switch_to_block(loop_header); let continue_flag = fb.ins().lt(n1, n, context.current_span()); - fb.ins().cond_br( - continue_flag, - loop_body, - &[], - loop_exit, - &[a1], - context.current_span(), - ); + fb.ins() + .cond_br(continue_flag, loop_body, &[], loop_exit, &[a1], context.current_span()); fb.switch_to_block(loop_body); let b2 = fb.ins().add_checked(a1, b1, context.current_span()); let n2 = fb.ins().incr_wrapping(n1, context.current_span()); - fb.ins() - .br(loop_header, &[b1, b2, n2], context.current_span()); + fb.ins().br(loop_header, &[b1, b2, n2], context.current_span()); fb.switch_to_block(loop_exit); fb.ins().ret(Some(result), context.current_span()); @@ -364,9 +337,7 @@ pub fn sum_matrix(builder: &mut ModuleBuilder, context: &TestContext) -> Functio [AbiParam::new(Type::U32)], ); let id = Ident::new(Symbol::intern("sum_matrix"), context.current_span()); - let mut fb = builder - .function(id, sig) - .expect("unexpected symbol conflict"); + let mut fb = builder.function(id, sig).expect("unexpected symbol conflict"); let entry = fb.current_block(); @@ -391,11 +362,8 @@ pub fn sum_matrix(builder: &mut ModuleBuilder, context: &TestContext) -> Functio // entry let sum0 = fb.ins().u32(0, context.current_span()); let ptr1 = fb.ins().ptrtoint(ptr0, Type::U32, context.current_span()); - let not_null = fb - .ins() - .neq_imm(ptr1, Immediate::U32(0), context.current_span()); - fb.ins() - .cond_br(not_null, b, &[], a, &[sum0], context.current_span()); + let not_null = fb.ins().neq_imm(ptr1, Immediate::U32(0), context.current_span()); + fb.ins().cond_br(not_null, b, &[], a, &[sum0], context.current_span()); // blk0 fb.switch_to_block(a); @@ -405,59 +373,37 @@ pub fn sum_matrix(builder: &mut ModuleBuilder, context: &TestContext) -> Functio fb.switch_to_block(b); let rows0 = fb.ins().u32(0, context.current_span()); let cols0 = fb.ins().u32(0, context.current_span()); - let row_size = fb - .ins() - .mul_imm_checked(cols, Immediate::U32(4), context.current_span()); - fb.ins() - .br(c, &[sum0, rows0, cols0], context.current_span()); + let row_size = fb.ins().mul_imm_checked(cols, Immediate::U32(4), context.current_span()); + fb.ins().br(c, &[sum0, rows0, cols0], context.current_span()); // blk2(sum1, rows1, cols1) fb.switch_to_block(c); let has_more_rows = fb.ins().lt(rows1, rows, context.current_span()); - let row_skip = fb - .ins() - .mul_checked(rows1, row_size, context.current_span()); - fb.ins().cond_br( - has_more_rows, - d, - &[sum1, rows1, cols1], - a, - &[sum1], - context.current_span(), - ); + let row_skip = fb.ins().mul_checked(rows1, row_size, context.current_span()); + fb.ins() + .cond_br(has_more_rows, d, &[sum1, rows1, cols1], a, &[sum1], context.current_span()); // blk3(sum3, rows3, cols3) fb.switch_to_block(d); let has_more_cols = fb.ins().lt(cols3, cols, context.current_span()); - fb.ins() - .cond_br(has_more_cols, e, &[], f, &[], context.current_span()); + fb.ins().cond_br(has_more_cols, e, &[], f, &[], context.current_span()); // blk4 fb.switch_to_block(e); - let col_skip = fb - .ins() - .mul_imm_checked(cols3, Immediate::U32(4), context.current_span()); - let skip = fb - .ins() - .add_checked(row_skip, col_skip, context.current_span()); + let col_skip = fb.ins().mul_imm_checked(cols3, Immediate::U32(4), context.current_span()); + let skip = fb.ins().add_checked(row_skip, col_skip, context.current_span()); let ptr4i = fb.ins().add_checked(ptr1, skip, context.current_span()); - let ptr4 = fb.ins().inttoptr( - ptr4i, - Type::Ptr(Box::new(Type::U32)), - context.current_span(), - ); + let ptr4 = fb.ins().inttoptr(ptr4i, Type::Ptr(Box::new(Type::U32)), context.current_span()); let value = fb.ins().load(ptr4, context.current_span()); let sum4 = fb.ins().add_checked(sum3, value, context.current_span()); let cols4 = fb.ins().incr_wrapping(cols3, context.current_span()); - fb.ins() - .br(d, &[sum4, rows3, cols4], context.current_span()); + fb.ins().br(d, &[sum4, rows3, cols4], context.current_span()); // blk5 fb.switch_to_block(f); let rows5 = fb.ins().incr_wrapping(rows3, context.current_span()); let cols5 = fb.ins().u32(0, context.current_span()); - fb.ins() - .br(c, &[sum3, rows5, cols5], context.current_span()); + fb.ins().br(c, &[sum3, rows5, cols5], context.current_span()); // We're done fb.build(&context.session.diagnostics) @@ -469,10 +415,8 @@ pub fn sum_matrix(builder: &mut ModuleBuilder, context: &TestContext) -> Functio /// /// This defines the following modules and functions: /// -/// * The `mem` module, containing memory-management intrinsics, -/// see [mem_intrinsics] for details. -/// * The `str` module, containing string-related intrinsics, -/// see [str_intrinsics] for details +/// * The `mem` module, containing memory-management intrinsics, see [mem_intrinsics] for details. +/// * The `str` module, containing string-related intrinsics, see [str_intrinsics] for details pub fn intrinsics(builder: &mut ProgramBuilder, context: &TestContext) -> anyhow::Result<()> { mem_intrinsics(builder, context)?; str_intrinsics(builder, context) @@ -483,8 +427,8 @@ pub fn intrinsics(builder: &mut ProgramBuilder, context: &TestContext) -> anyhow /// /// * `malloc(u32) -> *mut u8`, allocates from the usable heap /// * `memory_size() -> u32`, returns the amount of allocated memory, in pages -/// * `memory_grow(u32) -> u32`, grows memory by a given number of pages, -/// returning the previous memory size +/// * `memory_grow(u32) -> u32`, grows memory by a given number of pages, returning the previous +/// memory size /// /// Expressed as pseudocode, the `mem` module is as follows: /// @@ -564,14 +508,10 @@ pub fn mem_intrinsics(builder: &mut ProgramBuilder, _context: &TestContext) -> a .expect("unexpected global variable error"); // Define the alloc function - let mut fb = mb - .function("alloc", malloc_signature()) - .expect("unexpected symbol conflict"); + let mut fb = mb.function("alloc", malloc_signature()).expect("unexpected symbol conflict"); let memory_grow_sig = Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]); - let memory_grow = fb - .import_function("mem", "memory_grow", memory_grow_sig.clone()) - .unwrap(); + let memory_grow = fb.import_function("mem", "memory_grow", memory_grow_sig.clone()).unwrap(); // pub fn alloc(size: usize) -> *mut u8 { // let top = HEAP_TOP as usize; @@ -596,90 +536,50 @@ pub fn mem_intrinsics(builder: &mut ProgramBuilder, _context: &TestContext) -> a let args = fb.block_params(fb.current_block()); args[0] }; - let heap_top = fb - .ins() - .load_symbol("HEAP_TOP", Type::U32, SourceSpan::UNKNOWN); - let heap_end = fb - .ins() - .load_symbol("HEAP_END", Type::U32, SourceSpan::UNKNOWN); - let available = fb - .ins() - .sub_checked(heap_end, heap_top, SourceSpan::UNKNOWN); + let heap_top = fb.ins().load_symbol("HEAP_TOP", Type::U32, SourceSpan::UNKNOWN); + let heap_end = fb.ins().load_symbol("HEAP_END", Type::U32, SourceSpan::UNKNOWN); + let available = fb.ins().sub_checked(heap_end, heap_top, SourceSpan::UNKNOWN); let requires_growth = fb.ins().gt(size, available, SourceSpan::UNKNOWN); let grow_mem_block = fb.create_block(); let alloc_block = fb.create_block(); - fb.ins().cond_br( - requires_growth, - grow_mem_block, - &[], - alloc_block, - &[], - SourceSpan::UNKNOWN, - ); + fb.ins() + .cond_br(requires_growth, grow_mem_block, &[], alloc_block, &[], SourceSpan::UNKNOWN); fb.switch_to_block(grow_mem_block); let needed = fb.ins().sub_checked(size, available, SourceSpan::UNKNOWN); let need_pages = - fb.ins() - .div_imm_checked(needed, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); + fb.ins().div_imm_checked(needed, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); let need_extra = - fb.ins() - .mod_imm_checked(needed, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); - let extra_page = fb - .ins() - .gt_imm(need_extra, Immediate::U32(0), SourceSpan::UNKNOWN); + fb.ins().mod_imm_checked(needed, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); + let extra_page = fb.ins().gt_imm(need_extra, Immediate::U32(0), SourceSpan::UNKNOWN); let extra_count = fb.ins().zext(extra_page, Type::U32, SourceSpan::UNKNOWN); - let num_pages = fb - .ins() - .add_checked(need_pages, extra_count, SourceSpan::UNKNOWN); + let num_pages = fb.ins().add_checked(need_pages, extra_count, SourceSpan::UNKNOWN); let prev_pages = { - let call = fb - .ins() - .call(memory_grow, &[num_pages], SourceSpan::UNKNOWN); + let call = fb.ins().call(memory_grow, &[num_pages], SourceSpan::UNKNOWN); fb.first_result(call) }; let usize_max = fb.ins().u32(u32::MAX, SourceSpan::UNKNOWN); - fb.ins() - .assert_eq(prev_pages, usize_max, SourceSpan::UNKNOWN); + fb.ins().assert_eq(prev_pages, usize_max, SourceSpan::UNKNOWN); fb.ins().br(alloc_block, &[], SourceSpan::UNKNOWN); fb.switch_to_block(alloc_block); let addr = fb.ins().add_checked(heap_top, size, SourceSpan::UNKNOWN); - let align_offset = fb - .ins() - .mod_imm_checked(addr, Immediate::U32(8), SourceSpan::UNKNOWN); - let is_aligned = fb - .ins() - .eq_imm(align_offset, Immediate::U32(0), SourceSpan::UNKNOWN); + let align_offset = fb.ins().mod_imm_checked(addr, Immediate::U32(8), SourceSpan::UNKNOWN); + let is_aligned = fb.ins().eq_imm(align_offset, Immediate::U32(0), SourceSpan::UNKNOWN); let align_block = fb.create_block(); let aligned_block = fb.create_block(); let new_heap_top_ptr = fb.append_block_param(aligned_block, raw_ptr_ty.clone(), SourceSpan::UNKNOWN); - let ptr = fb - .ins() - .inttoptr(addr, raw_ptr_ty.clone(), SourceSpan::UNKNOWN); - fb.ins().cond_br( - is_aligned, - aligned_block, - &[ptr], - align_block, - &[], - SourceSpan::UNKNOWN, - ); + let ptr = fb.ins().inttoptr(addr, raw_ptr_ty.clone(), SourceSpan::UNKNOWN); + fb.ins() + .cond_br(is_aligned, aligned_block, &[ptr], align_block, &[], SourceSpan::UNKNOWN); fb.switch_to_block(align_block); - let aligned_addr = fb - .ins() - .add_imm_checked(addr, Immediate::U32(8), SourceSpan::UNKNOWN); - let aligned_addr = fb - .ins() - .sub_checked(aligned_addr, align_offset, SourceSpan::UNKNOWN); - let aligned_ptr = fb - .ins() - .inttoptr(aligned_addr, raw_ptr_ty.clone(), SourceSpan::UNKNOWN); - fb.ins() - .br(aligned_block, &[aligned_ptr], SourceSpan::UNKNOWN); + let aligned_addr = fb.ins().add_imm_checked(addr, Immediate::U32(8), SourceSpan::UNKNOWN); + let aligned_addr = fb.ins().sub_checked(aligned_addr, align_offset, SourceSpan::UNKNOWN); + let aligned_ptr = fb.ins().inttoptr(aligned_addr, raw_ptr_ty.clone(), SourceSpan::UNKNOWN); + fb.ins().br(aligned_block, &[aligned_ptr], SourceSpan::UNKNOWN); fb.switch_to_block(aligned_block); let heap_top_addr = fb.ins().symbol_addr( @@ -687,45 +587,28 @@ pub fn mem_intrinsics(builder: &mut ProgramBuilder, _context: &TestContext) -> a Type::Ptr(Box::new(raw_ptr_ty.clone())), SourceSpan::UNKNOWN, ); - fb.ins() - .store(heap_top_addr, new_heap_top_ptr, SourceSpan::UNKNOWN); + fb.ins().store(heap_top_addr, new_heap_top_ptr, SourceSpan::UNKNOWN); fb.ins().ret(Some(new_heap_top_ptr), SourceSpan::UNKNOWN); - let _alloc = fb - .build() - .expect("unexpected validation error, see diagnostics output"); + let _alloc = fb.build().expect("unexpected validation error, see diagnostics output"); // Define the memory_size function let memory_size_sig = Signature::new([], [AbiParam::new(Type::U32)]); - let mut fb = mb - .function("memory_size", memory_size_sig) - .expect("unexpected symbol conflict"); + let mut fb = mb.function("memory_size", memory_size_sig).expect("unexpected symbol conflict"); // pub fn memory_size() -> usize { // (HEAP_END as usize - HEAP_BASE as usize) / PAGE_SIZE // } - let heap_base_addr = fb - .ins() - .load_symbol("HEAP_BASE", Type::U32, SourceSpan::UNKNOWN); - let heap_end_addr = fb - .ins() - .load_symbol("HEAP_END", Type::U32, SourceSpan::UNKNOWN); - let used = fb - .ins() - .sub_checked(heap_end_addr, heap_base_addr, SourceSpan::UNKNOWN); - let used_pages = fb - .ins() - .div_imm_checked(used, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); + let heap_base_addr = fb.ins().load_symbol("HEAP_BASE", Type::U32, SourceSpan::UNKNOWN); + let heap_end_addr = fb.ins().load_symbol("HEAP_END", Type::U32, SourceSpan::UNKNOWN); + let used = fb.ins().sub_checked(heap_end_addr, heap_base_addr, SourceSpan::UNKNOWN); + let used_pages = fb.ins().div_imm_checked(used, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); fb.ins().ret(Some(used_pages), SourceSpan::UNKNOWN); - let _memory_size = fb - .build() - .expect("unexpected validation error, see diagnostics output"); + let _memory_size = fb.build().expect("unexpected validation error, see diagnostics output"); // Define the memory_grow function - let mut fb = mb - .function("memory_grow", memory_grow_sig) - .expect("unexpected symbol conflict"); + let mut fb = mb.function("memory_grow", memory_grow_sig).expect("unexpected symbol conflict"); // pub fn memory_grow(num_pages: usize) -> usize { // const HEAP_MAX: usize = 2u32.pow(30); @@ -743,18 +626,12 @@ pub fn mem_intrinsics(builder: &mut ProgramBuilder, _context: &TestContext) -> a let args = fb.block_params(fb.current_block()); args[0] }; - let heap_end = fb - .ins() - .load_symbol("HEAP_END", Type::U32, SourceSpan::UNKNOWN); + let heap_end = fb.ins().load_symbol("HEAP_END", Type::U32, SourceSpan::UNKNOWN); let heap_max = fb.ins().u32(u32::MAX, SourceSpan::UNKNOWN); - let remaining_bytes = fb - .ins() - .sub_checked(heap_max, heap_end, SourceSpan::UNKNOWN); - let remaining_pages = fb.ins().div_imm_checked( - remaining_bytes, - Immediate::U32(PAGE_SIZE), - SourceSpan::UNKNOWN, - ); + let remaining_bytes = fb.ins().sub_checked(heap_max, heap_end, SourceSpan::UNKNOWN); + let remaining_pages = + fb.ins() + .div_imm_checked(remaining_bytes, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); let out_of_memory = fb.ins().gt(num_pages, remaining_pages, SourceSpan::UNKNOWN); let out_of_memory_block = fb.create_block(); let grow_memory_block = fb.create_block(); @@ -768,37 +645,25 @@ pub fn mem_intrinsics(builder: &mut ProgramBuilder, _context: &TestContext) -> a ); fb.switch_to_block(out_of_memory_block); - fb.ins() - .ret_imm(Immediate::U32(u32::MAX), SourceSpan::UNKNOWN); + fb.ins().ret_imm(Immediate::U32(u32::MAX), SourceSpan::UNKNOWN); fb.switch_to_block(grow_memory_block); - let heap_base = fb - .ins() - .load_symbol("HEAP_BASE", Type::U32, SourceSpan::UNKNOWN); - let prev_bytes = fb - .ins() - .sub_checked(heap_end, heap_base, SourceSpan::UNKNOWN); + let heap_base = fb.ins().load_symbol("HEAP_BASE", Type::U32, SourceSpan::UNKNOWN); + let prev_bytes = fb.ins().sub_checked(heap_end, heap_base, SourceSpan::UNKNOWN); let prev_pages = fb.ins() .div_imm_checked(prev_bytes, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); let num_bytes = fb.ins() .mul_imm_checked(num_pages, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); - let new_heap_end = fb - .ins() - .add_checked(heap_end, num_bytes, SourceSpan::UNKNOWN); - let heap_end_addr = fb.ins().symbol_addr( - "HEAP_END", - Type::Ptr(Box::new(Type::U32)), - SourceSpan::UNKNOWN, - ); - fb.ins() - .store(heap_end_addr, new_heap_end, SourceSpan::UNKNOWN); + let new_heap_end = fb.ins().add_checked(heap_end, num_bytes, SourceSpan::UNKNOWN); + let heap_end_addr = + fb.ins() + .symbol_addr("HEAP_END", Type::Ptr(Box::new(Type::U32)), SourceSpan::UNKNOWN); + fb.ins().store(heap_end_addr, new_heap_end, SourceSpan::UNKNOWN); fb.ins().ret(Some(prev_pages), SourceSpan::UNKNOWN); - let _memory_grow = fb - .build() - .expect("unexpected validation error, see diagnostics output"); + let _memory_grow = fb.build().expect("unexpected validation error, see diagnostics output"); mb.build()?; @@ -876,9 +741,7 @@ pub fn str_intrinsics(builder: &mut ProgramBuilder, _context: &TestContext) -> a (args[0], args[1], args[2]) }; let addr = fb.ins().ptrtoint(ptr, Type::U32, SourceSpan::UNKNOWN); - let is_nonnull_addr = fb - .ins() - .gt_imm(addr, Immediate::U32(0), SourceSpan::UNKNOWN); + let is_nonnull_addr = fb.ins().gt_imm(addr, Immediate::U32(0), SourceSpan::UNKNOWN); fb.ins().assert(is_nonnull_addr, SourceSpan::UNKNOWN); let ptr_ptr = fb.ins().getelementptr(result, &[0], SourceSpan::UNKNOWN); fb.ins().store(ptr_ptr, ptr, SourceSpan::UNKNOWN); @@ -886,9 +749,7 @@ pub fn str_intrinsics(builder: &mut ProgramBuilder, _context: &TestContext) -> a fb.ins().store(len_ptr, len, SourceSpan::UNKNOWN); fb.ins().ret(None, SourceSpan::UNKNOWN); - let _from_raw_parts = fb - .build() - .expect("unexpected validation error, see diagnostics output"); + let _from_raw_parts = fb.build().expect("unexpected validation error, see diagnostics output"); // Define the compare function let mut fb = mb @@ -946,23 +807,18 @@ pub fn str_intrinsics(builder: &mut ProgramBuilder, _context: &TestContext) -> a fb.switch_to_block(loop_header); let done = fb.ins().lt(i, len, SourceSpan::UNKNOWN); - fb.ins() - .cond_br(done, loop_exit, &[], loop_body, &[], SourceSpan::UNKNOWN); + fb.ins().cond_br(done, loop_exit, &[], loop_body, &[], SourceSpan::UNKNOWN); fb.switch_to_block(loop_body); let a_char_addr = fb.ins().incr_wrapping(a_addr, SourceSpan::UNKNOWN); - let a_char_ptr = fb.ins().inttoptr( - a_char_addr, - Type::Ptr(Box::new(Type::U8)), - SourceSpan::UNKNOWN, - ); + let a_char_ptr = + fb.ins() + .inttoptr(a_char_addr, Type::Ptr(Box::new(Type::U8)), SourceSpan::UNKNOWN); let a_char = fb.ins().load(a_char_ptr, SourceSpan::UNKNOWN); let b_char_addr = fb.ins().incr_wrapping(b_addr, SourceSpan::UNKNOWN); - let b_char_ptr = fb.ins().inttoptr( - b_char_addr, - Type::Ptr(Box::new(Type::U8)), - SourceSpan::UNKNOWN, - ); + let b_char_ptr = + fb.ins() + .inttoptr(b_char_addr, Type::Ptr(Box::new(Type::U8)), SourceSpan::UNKNOWN); let b_char = fb.ins().load(b_char_ptr, SourceSpan::UNKNOWN); let is_eq = fb.ins().eq(a_char, b_char, SourceSpan::UNKNOWN); let is_gt = fb.ins().gt(a_char, b_char, SourceSpan::UNKNOWN); @@ -983,21 +839,14 @@ pub fn str_intrinsics(builder: &mut ProgramBuilder, _context: &TestContext) -> a fb.switch_to_block(loop_exit); let is_len_eq = fb.ins().eq(a_len, b_len, SourceSpan::UNKNOWN); let is_len_gt = fb.ins().gt(a_len, b_len, SourceSpan::UNKNOWN); - let len_gt_result = fb - .ins() - .select(is_len_gt, one, neg_one, SourceSpan::UNKNOWN); - let len_eq_result = fb - .ins() - .select(is_len_eq, zero, len_gt_result, SourceSpan::UNKNOWN); - fb.ins() - .br(exit_block, &[len_eq_result], SourceSpan::UNKNOWN); + let len_gt_result = fb.ins().select(is_len_gt, one, neg_one, SourceSpan::UNKNOWN); + let len_eq_result = fb.ins().select(is_len_eq, zero, len_gt_result, SourceSpan::UNKNOWN); + fb.ins().br(exit_block, &[len_eq_result], SourceSpan::UNKNOWN); fb.switch_to_block(exit_block); fb.ins().ret(Some(result), SourceSpan::UNKNOWN); - let _compare = fb - .build() - .expect("unexpected validation error, see diagnostics output"); + let _compare = fb.build().expect("unexpected validation error, see diagnostics output"); mb.build()?; @@ -1055,20 +904,14 @@ pub fn hello_world(builder: &mut ProgramBuilder, context: &TestContext) -> anyho // Declare the `main` function, with the appropriate type signature let sig = Signature::new([], [AbiParam::new(Type::I32)]); - let mut fb = mb - .function("main", sig) - .expect("unexpected symbol conflict"); + let mut fb = mb.function("main", sig).expect("unexpected symbol conflict"); let raw_ptr_ty = Type::Ptr(Box::new(Type::U8)); - let malloc = fb - .import_function("mem", "alloc", malloc_signature()) - .unwrap(); + let malloc = fb.import_function("mem", "alloc", malloc_signature()).unwrap(); let str_from_raw_parts = fb .import_function("str", "from_raw_parts", str_from_raw_parts_signature()) .unwrap(); - let str_compare = fb - .import_function("str", "compare", str_compare_signature()) - .unwrap(); + let str_compare = fb.import_function("str", "compare", str_compare_signature()).unwrap(); // const HELLO: &str = "hello"; // @@ -1115,30 +958,20 @@ pub fn hello_world(builder: &mut ProgramBuilder, context: &TestContext) -> anyho mem::size_of::() as i32, SourceSpan::UNKNOWN, ); - //let hello_data_ptr = fb.ins().load_symbol_relative("HELLO", raw_ptr_ty.clone(), mem::size_of::(), SourceSpan::UNKNOWN); - fb.ins() - .memcpy(hello_data_ptr, ptr, len, SourceSpan::UNKNOWN); + //let hello_data_ptr = fb.ins().load_symbol_relative("HELLO", raw_ptr_ty.clone(), + // mem::size_of::(), SourceSpan::UNKNOWN); + fb.ins().memcpy(hello_data_ptr, ptr, len, SourceSpan::UNKNOWN); let greeting_ptr = fb.ins().alloca(str_type(), SourceSpan::UNKNOWN); - fb.ins().call( - str_from_raw_parts, - &[greeting_ptr, ptr, len], - SourceSpan::UNKNOWN, - ); - let page_size = fb - .ins() - .load_symbol("PAGE_SIZE", Type::U32, SourceSpan::UNKNOWN); - let expected_page_size = fb.ins().u32(PAGE_SIZE, SourceSpan::UNKNOWN); fb.ins() - .assert_eq(page_size, expected_page_size, SourceSpan::UNKNOWN); + .call(str_from_raw_parts, &[greeting_ptr, ptr, len], SourceSpan::UNKNOWN); + let page_size = fb.ins().load_symbol("PAGE_SIZE", Type::U32, SourceSpan::UNKNOWN); + let expected_page_size = fb.ins().u32(PAGE_SIZE, SourceSpan::UNKNOWN); + fb.ins().assert_eq(page_size, expected_page_size, SourceSpan::UNKNOWN); let compared = { - let hello_ptr = fb.ins().symbol_addr( - "HELLO", - Type::Ptr(Box::new(str_type())), - SourceSpan::UNKNOWN, - ); - let call = fb - .ins() - .call(str_compare, &[hello_ptr, greeting_ptr], SourceSpan::UNKNOWN); + let hello_ptr = + fb.ins() + .symbol_addr("HELLO", Type::Ptr(Box::new(str_type())), SourceSpan::UNKNOWN); + let call = fb.ins().call(str_compare, &[hello_ptr, greeting_ptr], SourceSpan::UNKNOWN); fb.first_result(call) }; let compared = fb.ins().trunc(compared, Type::I1, SourceSpan::UNKNOWN); @@ -1146,8 +979,7 @@ pub fn hello_world(builder: &mut ProgramBuilder, context: &TestContext) -> anyho fb.ins().ret_imm(Immediate::I32(0), SourceSpan::UNKNOWN); // Finalize 'test::main' - fb.build() - .expect("unexpected validation error, see diagnostics output"); + fb.build().expect("unexpected validation error, see diagnostics output"); mb.build()?; // Add intrinsics @@ -1160,10 +992,7 @@ fn str_type() -> Type { } fn malloc_signature() -> Signature { - Signature::new( - [AbiParam::new(Type::U32)], - [AbiParam::new(Type::Ptr(Box::new(Type::U8)))], - ) + Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::Ptr(Box::new(Type::U8)))]) } fn str_from_raw_parts_signature() -> Signature { diff --git a/hir/src/tests.rs b/hir/src/tests.rs index d6576a298..c043725ba 100644 --- a/hir/src/tests.rs +++ b/hir/src/tests.rs @@ -1,9 +1,4 @@ -use winter_math::FieldElement; - -use super::{ - testing::{self, TestContext}, - *, -}; +use super::{testing::TestContext, *}; /// Test that we can construct a basic module and function and validate it #[test] @@ -29,17 +24,12 @@ fn inline_asm_builders_test() { // Declare the `sum` function, with the appropriate type signature let sig = Signature { - params: vec![ - AbiParam::new(Type::Ptr(Box::new(Type::Felt))), - AbiParam::new(Type::U32), - ], + params: vec![AbiParam::new(Type::Ptr(Box::new(Type::Felt))), AbiParam::new(Type::U32)], results: vec![AbiParam::new(Type::Felt)], cc: CallConv::SystemV, linkage: Linkage::External, }; - let mut fb = builder - .function("sum", sig) - .expect("unexpected symbol conflict"); + let mut fb = builder.function("sum", sig).expect("unexpected symbol conflict"); let entry = fb.current_block(); let (ptr, len) = { @@ -47,9 +37,7 @@ fn inline_asm_builders_test() { (args[0], args[1]) }; - let mut asm_builder = fb - .ins() - .inline_asm(&[ptr, len], [Type::Felt], SourceSpan::UNKNOWN); + let mut asm_builder = fb.ins().inline_asm(&[ptr, len], [Type::Felt], SourceSpan::UNKNOWN); asm_builder.ins().push(Felt::ZERO); // [sum, ptr, len] asm_builder.ins().push_u32(0); // [i, sum, ptr, len] asm_builder.ins().dup(0); // [i, i, sum, ptr, len] diff --git a/hir/src/value.rs b/hir/src/value.rs index 0436bca3a..efe6a9b32 100644 --- a/hir/src/value.rs +++ b/hir/src/value.rs @@ -1,5 +1,4 @@ use cranelift_entity::{self as entity, entity_impl}; - use miden_diagnostics::SourceSpan; use super::{Block, Inst, Type}; diff --git a/hir/src/write.rs b/hir/src/write.rs index d0619e9f7..b9cbcf958 100644 --- a/hir/src/write.rs +++ b/hir/src/write.rs @@ -227,12 +227,7 @@ fn write_operands( } Instruction::RetImm(RetImm { arg, .. }) => write!(w, " {arg}"), Instruction::Call(Call { callee, args, .. }) => { - write!( - w, - " {}({})", - callee, - DisplayValues::new(args.as_slice(pool).iter()) - ) + write!(w, " {}({})", callee, DisplayValues::new(args.as_slice(pool).iter())) } Instruction::CondBr(CondBr { cond, @@ -272,12 +267,7 @@ fn write_operands( write!(w, " {}", DisplayValues::new(args.as_slice(pool).iter())) } Instruction::PrimOpImm(PrimOpImm { imm, args, .. }) => { - write!( - w, - " {}, {}", - imm, - DisplayValues::new(args.as_slice(pool).iter()) - ) + write!(w, " {}, {}", imm, DisplayValues::new(args.as_slice(pool).iter())) } Instruction::Load(LoadOp { addr, .. }) => { write!(w, " {}", addr) diff --git a/midenc-compile/src/compiler.rs b/midenc-compile/src/compiler.rs index b7af98639..dc78f6d92 100644 --- a/midenc-compile/src/compiler.rs +++ b/midenc-compile/src/compiler.rs @@ -1,9 +1,7 @@ -use std::path::PathBuf; -use std::sync::Arc; +use std::{path::PathBuf, sync::Arc}; use clap::{Args, ColorChoice}; -use miden_diagnostics::term::termcolor::ColorChoice as MDColorChoice; -use miden_diagnostics::Emitter; +use miden_diagnostics::{term::termcolor::ColorChoice as MDColorChoice, Emitter}; use midenc_session::{ InputFile, Options, OutputFile, OutputType, OutputTypeSpec, OutputTypes, ProjectType, Session, TargetEnv, VerbosityFlag, Warnings, diff --git a/midenc-compile/src/lib.rs b/midenc-compile/src/lib.rs index 743a99732..dc135f590 100644 --- a/midenc-compile/src/lib.rs +++ b/midenc-compile/src/lib.rs @@ -2,17 +2,14 @@ mod compiler; mod stage; mod stages; -pub use self::compiler::Compiler; -use self::stage::Stage; -use self::stages::*; - use std::sync::Arc; use miden_codegen_masm as masm; use miden_hir::{pass::AnalysisManager, Symbol}; use midenc_session::{OutputType, Session}; -pub use self::stages::Compiled; +pub use self::{compiler::Compiler, stages::Compiled}; +use self::{stage::Stage, stages::*}; pub type CompilerResult = Result; @@ -69,48 +66,46 @@ pub fn register_flags(cmd: clap::Command) -> clap::Command { use miden_hir::RewritePassRegistration; use midenc_session::CompileFlag; - let cmd = inventory::iter:: - .into_iter() - .fold(cmd, |cmd, flag| { - let arg = clap::Arg::new(flag.name) - .long(flag.long.unwrap_or(flag.name)) - .action(clap::ArgAction::from(flag.action)); - let arg = if let Some(help) = flag.help { - arg.help(help) - } else { - arg - }; - let arg = if let Some(help_heading) = flag.help_heading { - arg.help_heading(help_heading) - } else { - arg - }; - let arg = if let Some(short) = flag.short { - arg.short(short) - } else { - arg - }; - let arg = if let Some(env) = flag.env { - arg.env(env) - } else { - arg - }; - let arg = if let Some(value) = flag.default_missing_value { - arg.default_missing_value(value) - } else { - arg - }; - let arg = if let Some(value) = flag.default_value { - arg.default_value(value) - } else { - arg - }; - cmd.arg(arg) - }); + let cmd = inventory::iter::.into_iter().fold(cmd, |cmd, flag| { + let arg = clap::Arg::new(flag.name) + .long(flag.long.unwrap_or(flag.name)) + .action(clap::ArgAction::from(flag.action)); + let arg = if let Some(help) = flag.help { + arg.help(help) + } else { + arg + }; + let arg = if let Some(help_heading) = flag.help_heading { + arg.help_heading(help_heading) + } else { + arg + }; + let arg = if let Some(short) = flag.short { + arg.short(short) + } else { + arg + }; + let arg = if let Some(env) = flag.env { + arg.env(env) + } else { + arg + }; + let arg = if let Some(value) = flag.default_missing_value { + arg.default_missing_value(value) + } else { + arg + }; + let arg = if let Some(value) = flag.default_value { + arg.default_value(value) + } else { + arg + }; + cmd.arg(arg) + }); - inventory::iter::> - .into_iter() - .fold(cmd, |cmd, rewrite| { + inventory::iter::>.into_iter().fold( + cmd, + |cmd, rewrite| { let name = rewrite.name(); let arg = clap::Arg::new(name) .long(name) @@ -118,7 +113,8 @@ pub fn register_flags(cmd: clap::Command) -> clap::Command { .help(rewrite.summary()) .help_heading("Transformations"); cmd.arg(arg) - }) + }, + ) } /// Run the compiler using the provided [Session] @@ -140,10 +136,9 @@ pub fn compile(session: Arc) -> CompilerResult<()> { if program.is_executable() { use miden_assembly::LibraryPath; let ast = program.to_program_ast(&session.codemap); - if let Some(path) = session.emit_to( - OutputType::Masm, - Some(Symbol::intern(LibraryPath::EXEC_PATH)), - ) { + if let Some(path) = session + .emit_to(OutputType::Masm, Some(Symbol::intern(LibraryPath::EXEC_PATH))) + { ast.write_to_file(path)?; } else { println!("{ast}"); diff --git a/midenc-compile/src/stages/mod.rs b/midenc-compile/src/stages/mod.rs index f78e431fc..0916d006f 100644 --- a/midenc-compile/src/stages/mod.rs +++ b/midenc-compile/src/stages/mod.rs @@ -1,19 +1,25 @@ -use super::Stage; -use crate::{CompilerError, CompilerResult}; use miden_codegen_masm as masm; use miden_frontend_wasm as wasm; -use miden_hir::pass::{AnalysisManager, ConversionPass, RewritePass}; -use miden_hir::{self as hir, parser::ast}; +use miden_hir::{ + self as hir, + parser::ast, + pass::{AnalysisManager, ConversionPass, RewritePass}, +}; use midenc_session::Session; +use super::Stage; +use crate::{CompilerError, CompilerResult}; + mod codegen; mod link; mod parse; mod rewrite; mod sema; -pub use self::codegen::{CodegenStage, Compiled}; -pub use self::link::{LinkerStage, MaybeLinked}; -pub use self::parse::{ParseOutput, ParseStage}; -pub use self::rewrite::ApplyRewritesStage; -pub use self::sema::SemanticAnalysisStage; +pub use self::{ + codegen::{CodegenStage, Compiled}, + link::{LinkerStage, MaybeLinked}, + parse::{ParseOutput, ParseStage}, + rewrite::ApplyRewritesStage, + sema::SemanticAnalysisStage, +}; diff --git a/midenc-compile/src/stages/parse.rs b/midenc-compile/src/stages/parse.rs index 1473756bf..6a4784f7c 100644 --- a/midenc-compile/src/stages/parse.rs +++ b/midenc-compile/src/stages/parse.rs @@ -1,5 +1,6 @@ -use midenc_session::InputFile; use std::path::Path; + +use midenc_session::InputFile; use wasm::WasmTranslationConfig; use super::*; @@ -62,14 +63,12 @@ impl ParseStage { } fn parse_ast_from_bytes(&self, bytes: &[u8], session: &Session) -> CompilerResult { - use miden_hir::parser::Parser; use std::io::{Error, ErrorKind}; + use miden_hir::parser::Parser; + let source = core::str::from_utf8(bytes).map_err(|_| { - CompilerError::Io(Error::new( - ErrorKind::InvalidInput, - "input is not valid utf-8", - )) + CompilerError::Io(Error::new(ErrorKind::InvalidInput, "input is not valid utf-8")) })?; let parser = Parser::new(session); let ast = Box::new(parser.parse_str(source)?); diff --git a/midenc-compile/src/stages/rewrite.rs b/midenc-compile/src/stages/rewrite.rs index cfbd46c77..22c2beb4b 100644 --- a/midenc-compile/src/stages/rewrite.rs +++ b/midenc-compile/src/stages/rewrite.rs @@ -44,9 +44,7 @@ impl Stage for ApplyRewritesStage { let mut rewrites = RewriteSet::default(); if registered.is_empty() { if session.should_codegen() { - rewrites.push(ModuleRewritePassAdapter::new( - transforms::SplitCriticalEdges, - )); + rewrites.push(ModuleRewritePassAdapter::new(transforms::SplitCriticalEdges)); rewrites.push(ModuleRewritePassAdapter::new(transforms::Treeify)); rewrites.push(ModuleRewritePassAdapter::new(transforms::InlineBlocks)); } diff --git a/midenc-driver/src/midenc.rs b/midenc-driver/src/midenc.rs index 49330b616..380379b9f 100644 --- a/midenc-driver/src/midenc.rs +++ b/midenc-driver/src/midenc.rs @@ -1,6 +1,4 @@ -use std::ffi::OsString; -use std::path::PathBuf; -use std::sync::Arc; +use std::{ffi::OsString, path::PathBuf, sync::Arc}; use clap::{ColorChoice, Parser, Subcommand}; use miden_diagnostics::Emitter; @@ -171,10 +169,7 @@ impl Midenc { let command = command.mut_subcommand("compile", compile::register_flags); let mut matches = command.try_get_matches_from(args)?; - let compile_matches = matches - .subcommand_matches("compile") - .cloned() - .unwrap_or_default(); + let compile_matches = matches.subcommand_matches("compile").cloned().unwrap_or_default(); let cli = ::from_arg_matches_mut(&mut matches) .map_err(format_error::)?; diff --git a/midenc-session/src/duration.rs b/midenc-session/src/duration.rs index 52cf2d272..88681479b 100644 --- a/midenc-session/src/duration.rs +++ b/midenc-session/src/duration.rs @@ -1,5 +1,7 @@ -use std::fmt; -use std::time::{Duration, Instant}; +use std::{ + fmt, + time::{Duration, Instant}, +}; pub struct HumanDuration(Duration); impl HumanDuration { diff --git a/midenc-session/src/emit.rs b/midenc-session/src/emit.rs index 19e58b5d9..25b30b3c6 100644 --- a/midenc-session/src/emit.rs +++ b/midenc-session/src/emit.rs @@ -1,6 +1,4 @@ -use std::fs::File; -use std::io::Write; -use std::path::Path; +use std::{fs::File, io::Write, path::Path}; use miden_hir_symbol::Symbol; @@ -33,18 +31,22 @@ impl Emit for Box { fn name(&self) -> Option { (**self).name() } + #[inline] fn output_type(&self) -> OutputType { (**self).output_type() } + #[inline] fn write_to_stdout(&self) -> std::io::Result<()> { (**self).write_to_stdout() } + #[inline] fn write_to_file(&self, path: &Path) -> std::io::Result<()> { (**self).write_to_file(path) } + #[inline] fn write_to(&self, writer: W) -> std::io::Result<()> { (**self).write_to(writer) diff --git a/midenc-session/src/inputs.rs b/midenc-session/src/inputs.rs index 108013af6..ef483b456 100644 --- a/midenc-session/src/inputs.rs +++ b/midenc-session/src/inputs.rs @@ -1,6 +1,8 @@ -use std::ffi::OsStr; -use std::fmt; -use std::path::{Path, PathBuf}; +use std::{ + ffi::OsStr, + fmt, + path::{Path, PathBuf}, +}; use miden_diagnostics::FileName; @@ -62,9 +64,9 @@ impl InputFile { file_type, }), // We do not yet have frontends for these file types - FileType::Masm | FileType::Masl | FileType::Wat => Err( - InvalidInputError::UnsupportedFileType(PathBuf::from("stdin")), - ), + FileType::Masm | FileType::Masl | FileType::Wat => { + Err(InvalidInputError::UnsupportedFileType(PathBuf::from("stdin"))) + } } } diff --git a/midenc-session/src/lib.rs b/midenc-session/src/lib.rs index 21078f707..783091c3e 100644 --- a/midenc-session/src/lib.rs +++ b/midenc-session/src/lib.rs @@ -6,22 +6,26 @@ mod options; mod outputs; mod statistics; -pub use self::duration::HumanDuration; -pub use self::emit::Emit; -pub use self::flags::{CompileFlag, FlagAction}; -pub use self::inputs::{FileType, InputFile, InputType, InvalidInputError}; -pub use self::options::*; -pub use self::outputs::{OutputFile, OutputFiles, OutputType, OutputTypeSpec, OutputTypes}; -pub use self::statistics::Statistics; - -use std::fmt; -use std::path::{Path, PathBuf}; -use std::sync::Arc; +use std::{ + fmt, + path::{Path, PathBuf}, + sync::Arc, +}; use clap::ValueEnum; use miden_diagnostics::{CodeMap, DiagnosticsHandler, Emitter}; use miden_hir_symbol::Symbol; +pub use self::{ + duration::HumanDuration, + emit::Emit, + flags::{CompileFlag, FlagAction}, + inputs::{FileType, InputFile, InputType, InvalidInputError}, + options::*, + outputs::{OutputFile, OutputFiles, OutputType, OutputTypeSpec, OutputTypes}, + statistics::Statistics, +}; + /// The type of project being compiled #[derive(Debug, Copy, Clone, Default)] pub enum ProjectType { @@ -96,26 +100,12 @@ impl Session { let output_files = match output_file { None => { let output_dir = output_dir.unwrap_or_default(); - let stem = options - .name - .clone() - .unwrap_or_else(|| input.filestem().to_owned()); + let stem = options.name.clone().unwrap_or_else(|| input.filestem().to_owned()); - OutputFiles::new( - stem, - output_dir, - None, - tmp_dir, - options.output_types.clone(), - ) + OutputFiles::new(stem, output_dir, None, tmp_dir, options.output_types.clone()) } Some(out_file) => OutputFiles::new( - out_file - .filestem() - .unwrap_or_default() - .to_str() - .unwrap() - .to_string(), + out_file.filestem().unwrap_or_default().to_str().unwrap().to_string(), output_dir.unwrap_or_default(), Some(out_file), tmp_dir, @@ -193,12 +183,7 @@ impl Session { } }) .unwrap_or_else(|| { - self.options - .current_dir - .file_name() - .unwrap() - .to_string_lossy() - .into_owned() + self.options.current_dir.file_name().unwrap().to_string_lossy().into_owned() }) } @@ -229,9 +214,7 @@ impl Session { } } ProjectType::Library => OutputFile::Real( - outputs - .out_dir - .join(format!("{progname}.{}", OutputType::Masl.extension())), + outputs.out_dir.join(format!("{progname}.{}", OutputType::Masl.extension())), ), } } @@ -267,10 +250,7 @@ impl Session { if self.should_emit(ty) { match self.output_files.path(ty) { OutputFile::Real(path) => name - .map(|name| { - path.with_file_name(name.as_str()) - .with_extension(ty.extension()) - }) + .map(|name| path.with_file_name(name.as_str()).with_extension(ty.extension())) .or(Some(path)), OutputFile::Stdout => None, } @@ -286,12 +266,9 @@ impl Session { match self.output_files.path(output_type) { OutputFile::Real(path) => { let file_path = if path.is_dir() { - let item_name = item - .name() - .map(|s| s.to_string()) - .unwrap_or("noname".to_string()); - path.join(item_name.as_str()) - .with_extension(output_type.extension()) + let item_name = + item.name().map(|s| s.to_string()).unwrap_or("noname".to_string()); + path.join(item_name.as_str()).with_extension(output_type.extension()) } else { path }; diff --git a/midenc-session/src/options/mod.rs b/midenc-session/src/options/mod.rs index f5dbd3536..52acdab2b 100644 --- a/midenc-session/src/options/mod.rs +++ b/midenc-session/src/options/mod.rs @@ -1,11 +1,7 @@ -use std::fmt; -use std::path::PathBuf; -use std::str::FromStr; -use std::sync::Arc; +use std::{fmt, path::PathBuf, str::FromStr, sync::Arc}; use clap::ValueEnum; -use miden_diagnostics::term::termcolor::ColorChoice; -use miden_diagnostics::{DiagnosticsConfig, Emitter, Verbosity}; +use miden_diagnostics::{term::termcolor::ColorChoice, DiagnosticsConfig, Emitter, Verbosity}; use crate::OutputTypes; diff --git a/midenc-session/src/outputs.rs b/midenc-session/src/outputs.rs index 504e44227..f2222929d 100644 --- a/midenc-session/src/outputs.rs +++ b/midenc-session/src/outputs.rs @@ -1,8 +1,10 @@ -use std::collections::BTreeMap; -use std::ffi::OsStr; -use std::fmt; -use std::path::{Path, PathBuf}; -use std::str::FromStr; +use std::{ + collections::BTreeMap, + ffi::OsStr, + fmt, + path::{Path, PathBuf}, + str::FromStr, +}; use clap::ValueEnum; @@ -30,13 +32,7 @@ impl OutputType { } pub fn shorthand_display() -> String { - format!( - "`{}`, `{}`, `{}`, `{}`", - Self::Ast, - Self::Hir, - Self::Masm, - Self::Masl, - ) + format!("`{}`, `{}`, `{}`, `{}`", Self::Ast, Self::Hir, Self::Masm, Self::Masl,) } } impl fmt::Display for OutputType { @@ -205,9 +201,7 @@ pub struct OutputTypes(BTreeMap>); impl OutputTypes { pub fn new>(entries: I) -> Self { Self(BTreeMap::from_iter( - entries - .into_iter() - .map(|spec| (spec.output_type, spec.path)), + entries.into_iter().map(|spec| (spec.output_type, spec.path)), )) } @@ -250,15 +244,11 @@ impl OutputTypes { } pub fn should_codegen(&self) -> bool { - self.0 - .keys() - .any(|k| matches!(k, OutputType::Masm | OutputType::Masl)) + self.0.keys().any(|k| matches!(k, OutputType::Masm | OutputType::Masl)) } pub fn should_link(&self) -> bool { - self.0 - .keys() - .any(|k| matches!(k, OutputType::Masm | OutputType::Masl)) + self.0.keys().any(|k| matches!(k, OutputType::Masm | OutputType::Masl)) } } @@ -290,9 +280,7 @@ impl clap::builder::TypedValueParser for OutputTypeParser { ) -> Result { use clap::error::{Error, ErrorKind}; - let output_type = value - .to_str() - .ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?; + let output_type = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?; let (shorthand, path) = match output_type.split_once('=') { None => (output_type, None), diff --git a/midenc-session/src/statistics.rs b/midenc-session/src/statistics.rs index 1a6c39624..32c94be81 100644 --- a/midenc-session/src/statistics.rs +++ b/midenc-session/src/statistics.rs @@ -1,5 +1,7 @@ -use std::sync::atomic::{AtomicU64, Ordering}; -use std::time::{Duration, Instant}; +use std::{ + sync::atomic::{AtomicU64, Ordering}, + time::{Duration, Instant}, +}; use crate::HumanDuration; diff --git a/midenc/src/main.rs b/midenc/src/main.rs index 671524ad2..e13fe66a4 100644 --- a/midenc/src/main.rs +++ b/midenc/src/main.rs @@ -1,7 +1,6 @@ use std::env; use anyhow::anyhow; - use midenc_driver::{self as driver, DriverError}; pub fn main() -> Result<(), DriverError> { @@ -22,9 +21,10 @@ pub fn main() -> Result<(), DriverError> { "ns" => builder.format_timestamp_nanos(), other => { return Err(DriverError::Failed(anyhow!( - "invalid MIDENC_TRACE_TIMING precision, expected one of [s, ms, us, ns], got '{}'", - other - ))) + "invalid MIDENC_TRACE_TIMING precision, expected one of [s, ms, us, ns], got \ + '{}'", + other + ))) } }; } else { diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..728b941b1 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,18 @@ +edition = "2021" +array_width = 80 +chain_width = 80 +comment_width = 100 +wrap_comments = true +fn_call_width = 80 +reorder_modules = true +reorder_imports = true +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +hex_literal_case = "Lower" +newline_style = "Unix" +reorder_impl_items = true +use_field_init_shorthand = true +use_try_shorthand = true +tab_spaces = 4 +condense_wildcard_suffixes = true +format_strings = true diff --git a/tools/cargo-miden/src/build.rs b/tools/cargo-miden/src/build.rs index e072f787c..6ab9777aa 100644 --- a/tools/cargo-miden/src/build.rs +++ b/tools/cargo-miden/src/build.rs @@ -21,10 +21,7 @@ pub fn build_masm( }; if !output_folder.exists() { - bail!( - "MASM output folder '{}' does not exist.", - output_folder.to_str().unwrap() - ); + bail!("MASM output folder '{}' does not exist.", output_folder.to_str().unwrap()); } log::debug!( "Compiling '{}' Wasm to '{}' directory with midenc ...", @@ -46,16 +43,8 @@ pub fn build_masm( .with_output_types(output_types); let target = TargetEnv::default(); let session = Arc::new( - Session::new( - target, - input, - Some(output_folder.to_path_buf()), - None, - None, - options, - None, - ) - .with_project_type(project_type), + Session::new(target, input, Some(output_folder.to_path_buf()), None, None, options, None) + .with_project_type(project_type), ); midenc_compile::compile(session.clone()).context("Wasm to MASM compilation failed!")?; let mut output_path = output_folder.join(wasm_file_path.file_stem().unwrap()); diff --git a/tools/cargo-miden/src/config.rs b/tools/cargo-miden/src/config.rs index 22eb9d2ee..5d714734e 100644 --- a/tools/cargo-miden/src/config.rs +++ b/tools/cargo-miden/src/config.rs @@ -18,13 +18,12 @@ //! that `cargo` supports which are necessary for `cargo-miden` //! to function. +use std::{collections::BTreeMap, fmt, fmt::Display, path::PathBuf, str::FromStr}; + use anyhow::{anyhow, bail, Context, Result}; use cargo_component_core::terminal::{Color, Terminal}; use parse_arg::{iter_short, match_arg}; use semver::Version; -use std::fmt; -use std::str::FromStr; -use std::{collections::BTreeMap, fmt::Display, path::PathBuf}; /// Represents a cargo package specifier. /// @@ -425,12 +424,7 @@ impl CargoArguments { } Ok(Self { - color: args - .get_mut("--color") - .unwrap() - .take_single() - .map(|v| v.parse()) - .transpose()?, + color: args.get_mut("--color").unwrap().take_single().map(|v| v.parse()).transpose()?, verbose: args.get("--verbose").unwrap().count(), quiet: args.get("--quiet").unwrap().count() > 0, manifest_path: args @@ -479,9 +473,10 @@ impl Config { #[cfg(test)] mod test { - use super::*; use std::iter::empty; + use super::*; + #[test] fn it_parses_flags() { let mut args = Args::default().flag("--flag", Some('f')); @@ -494,9 +489,7 @@ mod test { // Test the flag args.parse("--flag", &mut empty::()).unwrap(); assert_eq!( - args.parse("--flag", &mut empty::()) - .unwrap_err() - .to_string(), + args.parse("--flag", &mut empty::()).unwrap_err().to_string(), "the argument '--flag' cannot be used multiple times" ); let arg = args.get_mut("--flag").unwrap(); @@ -511,9 +504,7 @@ mod test { // Test the short flag args.parse("-rfx", &mut empty::()).unwrap(); assert_eq!( - args.parse("-fxz", &mut empty::()) - .unwrap_err() - .to_string(), + args.parse("-fxz", &mut empty::()).unwrap_err().to_string(), "the argument '--flag' cannot be used multiple times" ); let arg = args.get("--flag").unwrap(); @@ -534,19 +525,14 @@ mod test { // Test missing value assert_eq!( - args.parse("--option", &mut empty::()) - .unwrap_err() - .to_string(), + args.parse("--option", &mut empty::()).unwrap_err().to_string(), "a value is required for '--option ' but none was supplied" ); // Test the option with equals - args.parse("--option=value", &mut empty::()) - .unwrap(); + args.parse("--option=value", &mut empty::()).unwrap(); assert_eq!( - args.parse("--option=value", &mut empty::()) - .unwrap_err() - .to_string(), + args.parse("--option=value", &mut empty::()).unwrap_err().to_string(), "the argument '--option ' cannot be used multiple times" ); let arg = args.get_mut("--option").unwrap(); @@ -572,9 +558,7 @@ mod test { assert_eq!(arg.take_single(), None); assert_eq!( - args.parse("-fo", &mut empty::()) - .unwrap_err() - .to_string(), + args.parse("-fo", &mut empty::()).unwrap_err().to_string(), "a value is required for '--option ' but none was supplied" ); @@ -622,9 +606,7 @@ mod test { // Test missing value assert_eq!( - args.parse("--option", &mut empty::()) - .unwrap_err() - .to_string(), + args.parse("--option", &mut empty::()).unwrap_err().to_string(), "a value is required for '--option ' but none was supplied" ); @@ -663,9 +645,7 @@ mod test { // Test missing shot option value assert_eq!( - args.parse("-fo", &mut empty::()) - .unwrap_err() - .to_string(), + args.parse("-fo", &mut empty::()).unwrap_err().to_string(), "a value is required for '--option ' but none was supplied" ); diff --git a/tools/cargo-miden/src/lib.rs b/tools/cargo-miden/src/lib.rs index 856c36f85..c741eb988 100644 --- a/tools/cargo-miden/src/lib.rs +++ b/tools/cargo-miden/src/lib.rs @@ -1,6 +1,5 @@ use std::path::PathBuf; -use crate::run_cargo_command::run_cargo_command; use anyhow::bail; use cargo_component::load_metadata; use cargo_component_core::terminal::Terminal; @@ -8,6 +7,8 @@ use clap::{CommandFactory, Parser}; use config::CargoArguments; use new_project::NewCommand; +use crate::run_cargo_command::run_cargo_command; + mod build; pub mod config; mod new_project; @@ -26,8 +27,7 @@ const BUILTIN_COMMANDS: &[&str] = &[ const AFTER_HELP: &str = "Unrecognized subcommands will be passed to cargo verbatim and the artifacts will be processed afterwards (e.g. `build` command compiles MASM). - \n\ - See `cargo help` for more information on available cargo commands."; + \nSee `cargo help` for more information on available cargo commands."; /// Cargo integration for Miden #[derive(Parser)] diff --git a/tools/cargo-miden/src/new_project.rs b/tools/cargo-miden/src/new_project.rs index 7419364a3..cde70e922 100644 --- a/tools/cargo-miden/src/new_project.rs +++ b/tools/cargo-miden/src/new_project.rs @@ -1,8 +1,7 @@ use std::path::PathBuf; use anyhow::Context; -use cargo_generate::GenerateArgs; -use cargo_generate::TemplatePath; +use cargo_generate::{GenerateArgs, TemplatePath}; use clap::Args; /// Create a new Miden project at @@ -16,16 +15,22 @@ pub struct NewCommand { impl NewCommand { pub fn exec(self) -> anyhow::Result { - let name = self.path - .file_name() - .ok_or_else(|| { - anyhow::anyhow!("Failed to get the last segment of the provided path for the project name") - })? - .to_str() - .ok_or_else(|| { - anyhow::anyhow!("The last segment of the provided path must be valid UTF8 to generate a valid project name") - })? - .to_string(); + let name = self + .path + .file_name() + .ok_or_else(|| { + anyhow::anyhow!( + "Failed to get the last segment of the provided path for the project name" + ) + })? + .to_str() + .ok_or_else(|| { + anyhow::anyhow!( + "The last segment of the provided path must be valid UTF8 to generate a valid \ + project name" + ) + })? + .to_string(); let generate_args = GenerateArgs { template_path: TemplatePath { diff --git a/tools/cargo-miden/src/run_cargo_command.rs b/tools/cargo-miden/src/run_cargo_command.rs index f62276a60..dc0e21fcf 100644 --- a/tools/cargo-miden/src/run_cargo_command.rs +++ b/tools/cargo-miden/src/run_cargo_command.rs @@ -1,11 +1,13 @@ +use std::{path::PathBuf, process::Command}; + use anyhow::bail; use cargo_metadata::Metadata; -use std::path::PathBuf; -use std::process::Command; -use crate::build::build_masm; -use crate::config::CargoArguments; -use crate::target::{install_wasm32_wasi, WASM32_WASI_TARGET}; +use crate::{ + build::build_masm, + config::CargoArguments, + target::{install_wasm32_wasi, WASM32_WASI_TARGET}, +}; fn is_wasm_target(target: &str) -> bool { target == WASM32_WASI_TARGET @@ -75,24 +77,18 @@ pub fn run_cargo_command( .chain(cargo_args.targets.is_empty().then_some(WASM32_WASI_TARGET)); for target in targets { - let out_dir = metadata - .target_directory - .join(target) - .join(if cargo_args.release { + let out_dir = metadata.target_directory.join(target).join(if cargo_args.release { + "release" + } else { + "debug" + }); + + let miden_out_dir = + metadata.target_directory.join("miden").join(if cargo_args.release { "release" } else { "debug" }); - - let miden_out_dir = - metadata - .target_directory - .join("miden") - .join(if cargo_args.release { - "release" - } else { - "debug" - }); if !miden_out_dir.exists() { std::fs::create_dir_all(&miden_out_dir)?; } @@ -107,9 +103,7 @@ pub fn run_cargo_command( build_masm(path.as_std_path(), miden_out_dir.as_std_path(), is_bin)?; outputs.push(output); } else { - let path = out_dir - .join(package.name.replace('-', "_")) - .with_extension("wasm"); + let path = out_dir.join(package.name.replace('-', "_")).with_extension("wasm"); if path.exists() { let output = build_masm(path.as_std_path(), miden_out_dir.as_std_path(), is_bin)?; diff --git a/tools/cargo-miden/src/target.rs b/tools/cargo-miden/src/target.rs index 653979969..3088396ae 100644 --- a/tools/cargo-miden/src/target.rs +++ b/tools/cargo-miden/src/target.rs @@ -1,10 +1,11 @@ -use anyhow::{bail, Result}; use std::{ env, path::PathBuf, process::{Command, Stdio}, }; +use anyhow::{bail, Result}; + pub const WASM32_WASI_TARGET: &str = "wasm32-wasi"; pub fn install_wasm32_wasi() -> Result<()> { @@ -16,10 +17,9 @@ pub fn install_wasm32_wasi() -> Result<()> { if env::var_os("RUSTUP_TOOLCHAIN").is_none() { bail!( - "failed to find the `wasm32-wasi` target \ - and `rustup` is not available. If you're using rustup \ - make sure that it's correctly installed; if not, make sure to \ - install the `wasm32-wasi` target before using this command" + "failed to find the `wasm32-wasi` target and `rustup` is not available. If you're \ + using rustup make sure that it's correctly installed; if not, make sure to install \ + the `wasm32-wasi` target before using this command" ); } @@ -39,15 +39,11 @@ pub fn install_wasm32_wasi() -> Result<()> { } fn get_sysroot() -> Result { - let output = Command::new("rustc") - .arg("--print") - .arg("sysroot") - .output()?; + let output = Command::new("rustc").arg("--print").arg("sysroot").output()?; if !output.status.success() { bail!( - "failed to execute `rustc --print sysroot`, \ - command exited with error: {output}", + "failed to execute `rustc --print sysroot`, command exited with error: {output}", output = String::from_utf8_lossy(&output.stderr) ); } diff --git a/tools/cargo-miden/tests/build.rs b/tools/cargo-miden/tests/build.rs index b68be32d8..cca3d5ed4 100644 --- a/tools/cargo-miden/tests/build.rs +++ b/tools/cargo-miden/tests/build.rs @@ -1,7 +1,7 @@ +use std::{env, fs}; + use cargo_component_core::terminal; use cargo_miden::run; -use std::env; -use std::fs; // NOTE: This test sets the current working directory so don't run it in parallel with tests // that depend on the current directory @@ -16,22 +16,15 @@ fn build_new_project_from_template() { if expected_new_project_dir.exists() { fs::remove_dir_all(expected_new_project_dir).unwrap(); } - let args = ["cargo", "miden", "new", project_name] - .into_iter() - .map(|s| s.to_string()); + let args = ["cargo", "miden", "new", project_name].into_iter().map(|s| s.to_string()); let terminal = terminal::Terminal::new(terminal::Verbosity::Verbose, terminal::Color::Auto); let outputs = run(args, &terminal).expect("Failed to create new project"); let new_project_path = outputs.first().unwrap().canonicalize().unwrap(); dbg!(&new_project_path); assert!(new_project_path.exists()); - assert_eq!( - new_project_path, - expected_new_project_dir.canonicalize().unwrap() - ); + assert_eq!(new_project_path, expected_new_project_dir.canonicalize().unwrap()); env::set_current_dir(&new_project_path).unwrap(); - let args = ["cargo", "miden", "build", "--release"] - .iter() - .map(|s| s.to_string()); + let args = ["cargo", "miden", "build", "--release"].iter().map(|s| s.to_string()); let outputs = run(args, &terminal).expect("Failed to compile"); let expected_masm_path = outputs.first().unwrap(); dbg!(&expected_masm_path); diff --git a/tools/cargo-miden/tests/utils.rs b/tools/cargo-miden/tests/utils.rs index de17a8abd..b58c8b06e 100644 --- a/tools/cargo-miden/tests/utils.rs +++ b/tools/cargo-miden/tests/utils.rs @@ -1,5 +1,4 @@ -use std::env; -use std::path::PathBuf; +use std::{env, path::PathBuf}; #[allow(dead_code)] pub(crate) fn get_test_path(test_dir_name: &str) -> PathBuf { From a6aa09ac97e35b9e27b719183d4a8e9a86ab4149 Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder <955793+bitwalker@users.noreply.github.com> Date: Mon, 11 Mar 2024 06:32:08 -0400 Subject: [PATCH 3/3] chore: remove deprecated filecheck tool --- Cargo.lock | 100 ++++-------------------------------- tools/filecheck/Cargo.toml | 10 ---- tools/filecheck/src/main.rs | 75 --------------------------- 3 files changed, 10 insertions(+), 175 deletions(-) delete mode 100644 tools/filecheck/Cargo.toml delete mode 100644 tools/filecheck/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index b23e9f38f..f4f4614d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,15 +97,6 @@ dependencies = [ "libc", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anstream" version = "0.6.11" @@ -585,7 +576,7 @@ dependencies = [ "cargo-component-core", "cargo-config2", "cargo_metadata", - "clap 4.4.18", + "clap", "futures", "heck", "indexmap 2.1.0", @@ -621,7 +612,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde709273523be5af0e052c0e968e83988010fe8f34d9013a0e13255ac2e2da3" dependencies = [ "anyhow", - "clap 4.4.18", + "clap", "futures", "indexmap 2.1.0", "keyring", @@ -662,7 +653,7 @@ checksum = "8a2885ae054e000b117515ab33e91c10eca90c2788a7baec1b97ada1f1f51e57" dependencies = [ "anyhow", "auth-git2", - "clap 4.4.18", + "clap", "console", "dialoguer", "env_logger 0.10.2", @@ -703,7 +694,7 @@ dependencies = [ "cargo-component-core", "cargo-generate", "cargo_metadata", - "clap 4.4.18", + "clap", "env_logger 0.9.3", "log", "miden-diagnostics", @@ -775,21 +766,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "ansi_term", - "atty", - "bitflags 1.3.2", - "strsim 0.8.0", - "textwrap", - "unicode-width", - "vec_map", -] - [[package]] name = "clap" version = "4.4.18" @@ -809,7 +785,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.10.0", + "strsim", ] [[package]] @@ -1047,7 +1023,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", + "strsim", "syn 2.0.48", ] @@ -1319,16 +1295,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "backtrace", - "version_check", -] - [[package]] name = "event-listener" version = "2.5.3" @@ -1417,15 +1383,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "filecheck" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap 4.4.18", - "lit", -] - [[package]] name = "fixedbitset" version = "0.4.2" @@ -2497,22 +2454,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "lit" -version = "1.0.4" -source = "git+https://github.com/bitwalker/lit?branch=main#a4f29aba5023d66cce64918334fc5c8af68f0ae7" -dependencies = [ - "clap 2.34.0", - "error-chain", - "itertools 0.10.5", - "lazy_static", - "log", - "regex", - "tempfile", - "term", - "walkdir", -] - [[package]] name = "lock_api" version = "0.4.11" @@ -2870,7 +2811,7 @@ name = "midenc-compile" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.4.18", + "clap", "inventory", "log", "miden-assembly", @@ -2889,7 +2830,7 @@ name = "midenc-driver" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.4.18", + "clap", "miden-diagnostics", "miden-hir", "midenc-compile", @@ -2902,7 +2843,7 @@ name = "midenc-session" version = "0.1.0" dependencies = [ "atty", - "clap 4.4.18", + "clap", "inventory", "miden-diagnostics", "miden-hir-symbol", @@ -4383,12 +4324,6 @@ dependencies = [ "precomputed-hash", ] -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "strsim" version = "0.10.0" @@ -4487,15 +4422,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "thiserror" version = "1.0.56" @@ -4837,12 +4763,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.4" @@ -4906,7 +4826,7 @@ dependencies = [ "anyhow", "async-trait", "bytes", - "clap 4.4.18", + "clap", "dirs", "futures-util", "itertools 0.11.0", diff --git a/tools/filecheck/Cargo.toml b/tools/filecheck/Cargo.toml deleted file mode 100644 index b7952868e..000000000 --- a/tools/filecheck/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "filecheck" -version = "0.1.0" -edition = "2021" -publish = false - -[dependencies] -anyhow.workspace = true -clap.workspace = true -lit = { git = "https://github.com/bitwalker/lit", branch = "main" } diff --git a/tools/filecheck/src/main.rs b/tools/filecheck/src/main.rs deleted file mode 100644 index 71d0bfecf..000000000 --- a/tools/filecheck/src/main.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::env; -use std::path::PathBuf; - -use anyhow::{anyhow, bail}; -use clap::Parser; -use lit::event_handler::Default as EventHandler; - -#[derive(Parser)] -#[clap(version, about, long_about = None)] -#[command(arg_required_else_help = true)] -struct Config { - /// Path to directory containing the lit tests to be run - #[arg(value_name = "DIR")] - tests: PathBuf, - /// Specify one or more file extensions to include when searching for test inputs - #[arg(long = "type", short = 't', default_value = "hir")] - file_types: Vec, - /// Define one or more variables to make available in test commands - /// - /// You must specify each one in `key=value` format, comma-separated. - #[arg(long = "define", short = 'D')] - defines: Vec, -} - -pub fn main() -> anyhow::Result<()> { - let config = Config::parse(); - - let cwd = env::current_dir().unwrap(); - let test_path = config.tests.as_path(); - let lit_dir = if test_path.is_file() { - test_path.parent().unwrap().to_str().unwrap() - } else { - test_path.to_str().unwrap() - }; - - let midenc_exe = cwd.join("bin/midenc"); - if !midenc_exe.is_file() { - bail!( - "expected to find midenc at {}, but it either doesn't exist, or is not a file", - midenc_exe.display() - ); - } - - let mut defines = Vec::with_capacity(config.defines.len()); - for define in config.defines.iter() { - if let Some((key, value)) = define.split_once('=') { - defines.push((key.to_string(), value.to_string())); - } else { - bail!( - "invalid variable definition, expected 'key=value', got '{}'", - define - ); - } - } - - lit::run::tests(EventHandler::default(), move |runner| { - runner.add_search_path(test_path.to_str().unwrap()); - for ty in config.file_types.iter() { - runner.add_extension(ty.as_str()); - } - - runner - .constants - .insert("tests".to_string(), lit_dir.to_string()); - runner.constants.insert( - "midenc".to_string(), - midenc_exe.to_str().unwrap().to_string(), - ); - - for (k, v) in defines.drain(..) { - runner.constants.insert(k, v); - } - }) - .map_err(|_| anyhow!("lit tests failed, see output for details")) -}