diff --git a/acvm-repo/acir/src/native_types/expression/mod.rs b/acvm-repo/acir/src/native_types/expression/mod.rs index 1feda5703c8..2bbbc39d0ca 100644 --- a/acvm-repo/acir/src/native_types/expression/mod.rs +++ b/acvm-repo/acir/src/native_types/expression/mod.rs @@ -273,6 +273,60 @@ impl Expression { Expression { mul_terms, linear_combinations, q_c } } + + /// Determine the width of this expression. + /// The width meaning the number of unique witnesses needed for this expression. + pub fn width(&self) -> usize { + let mut width = 0; + + for mul_term in &self.mul_terms { + // The coefficient should be non-zero, as this method is ran after the compiler removes all zero coefficient terms + assert_ne!(mul_term.0, F::zero()); + + let mut found_x = false; + let mut found_y = false; + + for term in self.linear_combinations.iter() { + let witness = &term.1; + let x = &mul_term.1; + let y = &mul_term.2; + if witness == x { + found_x = true; + }; + if witness == y { + found_y = true; + }; + if found_x & found_y { + break; + } + } + + // If the multiplication is a squaring then we must assign the two witnesses to separate wires and so we + // can never get a zero contribution to the width. + let multiplication_is_squaring = mul_term.1 == mul_term.2; + + let mul_term_width_contribution = if !multiplication_is_squaring && (found_x & found_y) + { + // Both witnesses involved in the multiplication exist elsewhere in the expression. + // They both do not contribute to the width of the expression as this would be double-counting + // due to their appearance in the linear terms. + 0 + } else if found_x || found_y { + // One of the witnesses involved in the multiplication exists elsewhere in the expression. + // The multiplication then only contributes 1 new witness to the width. + 1 + } else { + // Worst case scenario, the multiplication is using completely unique witnesses so has a contribution of 2. + 2 + }; + + width += mul_term_width_contribution; + } + + width += self.linear_combinations.len(); + + width + } } impl From for Expression { diff --git a/acvm-repo/acvm/src/compiler/transformers/csat.rs b/acvm-repo/acvm/src/compiler/transformers/csat.rs index 19cc18ca7f3..f258e0a8818 100644 --- a/acvm-repo/acvm/src/compiler/transformers/csat.rs +++ b/acvm-repo/acvm/src/compiler/transformers/csat.rs @@ -415,71 +415,8 @@ fn fits_in_one_identity(expr: &Expression, width: usize) -> boo if expr.mul_terms.len() > 1 { return false; }; - // A Polynomial with more terms than fan-in cannot fit within a single opcode - if expr.linear_combinations.len() > width { - return false; - } - - // A polynomial with no mul term and a fan-in that fits inside of the width can fit into a single opcode - if expr.mul_terms.is_empty() { - return true; - } - - // A polynomial with width-2 fan-in terms and a single non-zero mul term can fit into one opcode - // Example: Axy + Dz . Notice, that the mul term places a constraint on the first two terms, but not the last term - // XXX: This would change if our arithmetic polynomial equation was changed to Axyz for example, but for now it is not. - if expr.linear_combinations.len() <= (width - 2) { - return true; - } - - // We now know that we have a single mul term. We also know that the mul term must match up with at least one of the other terms - // A polynomial whose mul terms are non zero which do not match up with two terms in the fan-in cannot fit into one opcode - // An example of this is: Axy + Bx + Cy + ... - // Notice how the bivariate monomial xy has two univariate monomials with their respective coefficients - // XXX: note that if x or y is zero, then we could apply a further optimization, but this would be done in another algorithm. - // It would be the same as when we have zero coefficients - Can only work if wire is constrained to be zero publicly - let mul_term = &expr.mul_terms[0]; - - // The coefficient should be non-zero, as this method is ran after the compiler removes all zero coefficient terms - assert_ne!(mul_term.0, F::zero()); - - let mut found_x = false; - let mut found_y = false; - - for term in expr.linear_combinations.iter() { - let witness = &term.1; - let x = &mul_term.1; - let y = &mul_term.2; - if witness == x { - found_x = true; - }; - if witness == y { - found_y = true; - }; - if found_x & found_y { - break; - } - } - - // If the multiplication is a squaring then we must assign the two witnesses to separate wires and so we - // can never get a zero contribution to the width. - let multiplication_is_squaring = mul_term.1 == mul_term.2; - - let mul_term_width_contribution = if !multiplication_is_squaring && (found_x & found_y) { - // Both witnesses involved in the multiplication exist elsewhere in the expression. - // They both do not contribute to the width of the expression as this would be double-counting - // due to their appearance in the linear terms. - 0 - } else if found_x || found_y { - // One of the witnesses involved in the multiplication exists elsewhere in the expression. - // The multiplication then only contributes 1 new witness to the width. - 1 - } else { - // Worst case scenario, the multiplication is using completely unique witnesses so has a contribution of 2. - 2 - }; - mul_term_width_contribution + expr.linear_combinations.len() <= width + expr.width() <= width } #[cfg(test)] diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index dd774a1eeec..f430eb8ad19 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -56,6 +56,12 @@ pub struct CompileOptions { #[arg(long, value_parser = parse_expression_width)] pub expression_width: Option, + /// Generate ACIR with the target backend expression width. + /// The default is to generate ACIR without a bound and split expressions after code generation. + /// Activating this flag can sometimes provide optimizations for certain programs. + #[arg(long, default_value = "false")] + pub bounded_codegen: bool, + /// Force a full recompilation. #[arg(long = "force")] pub force_compile: bool, @@ -512,6 +518,12 @@ fn compile_contract_inner( } } +/// Default expression width used for Noir compilation. +/// The ACVM native type `ExpressionWidth` has its own default which should always be unbounded, +/// while we can sometimes expect the compilation target width to change. +/// Thus, we set it separately here rather than trying to alter the default derivation of the type. +pub const DEFAULT_EXPRESSION_WIDTH: ExpressionWidth = ExpressionWidth::Bounded { width: 4 }; + /// Compile the current crate using `main_function` as the entrypoint. /// /// This function assumes [`check_crate`] is called beforehand. @@ -550,6 +562,11 @@ pub fn compile_no_check( enable_brillig_logging: options.show_brillig, force_brillig_output: options.force_brillig, print_codegen_timings: options.benchmark_codegen, + expression_width: if options.bounded_codegen { + options.expression_width.unwrap_or(DEFAULT_EXPRESSION_WIDTH) + } else { + ExpressionWidth::default() + }, }; let SsaProgramArtifact { program, debug, warnings, names, error_types, .. } = diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index 81327cec013..41dbf3b7272 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -42,6 +42,22 @@ pub mod ir; mod opt; pub mod ssa_gen; +pub struct SsaEvaluatorOptions { + /// Emit debug information for the intermediate SSA IR + pub enable_ssa_logging: bool, + + pub enable_brillig_logging: bool, + + /// Force Brillig output (for step debugging) + pub force_brillig_output: bool, + + /// Pretty print benchmark times of each code generation pass + pub print_codegen_timings: bool, + + /// Width of expressions to be used for ACIR + pub expression_width: ExpressionWidth, +} + pub(crate) struct ArtifactsAndWarnings(Artifacts, Vec); /// Optimize the given program by converting it into SSA @@ -99,7 +115,9 @@ pub(crate) fn optimize_into_acir( drop(ssa_gen_span_guard); - let artifacts = time("SSA to ACIR", options.print_codegen_timings, || ssa.into_acir(&brillig))?; + let artifacts = time("SSA to ACIR", options.print_codegen_timings, || { + ssa.into_acir(&brillig, options.expression_width) + })?; Ok(ArtifactsAndWarnings(artifacts, ssa_level_warnings)) } @@ -160,19 +178,6 @@ impl SsaProgramArtifact { } } -pub struct SsaEvaluatorOptions { - /// Emit debug information for the intermediate SSA IR - pub enable_ssa_logging: bool, - - pub enable_brillig_logging: bool, - - /// Force Brillig output (for step debugging) - pub force_brillig_output: bool, - - /// Pretty print benchmark times of each code generation pass - pub print_codegen_timings: bool, -} - /// Compiles the [`Program`] into [`ACIR``][acvm::acir::circuit::Program]. /// /// The output ACIR is backend-agnostic and so must go through a transformation pass before usage in proof generation. diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 74149af25ef..9e97fd3bc50 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -9,7 +9,7 @@ use crate::ssa::ir::types::Type as SsaType; use crate::ssa::ir::{instruction::Endian, types::NumericType}; use acvm::acir::circuit::brillig::{BrilligInputs, BrilligOutputs}; use acvm::acir::circuit::opcodes::{BlockId, BlockType, MemOp}; -use acvm::acir::circuit::{AssertionPayload, ExpressionOrMemory, Opcode}; +use acvm::acir::circuit::{AssertionPayload, ExpressionOrMemory, ExpressionWidth, Opcode}; use acvm::blackbox_solver; use acvm::brillig_vm::{MemoryValue, VMStatus, VM}; use acvm::{ @@ -24,6 +24,7 @@ use acvm::{ use fxhash::FxHashMap as HashMap; use iter_extended::{try_vecmap, vecmap}; use num_bigint::BigUint; +use std::cmp::Ordering; use std::{borrow::Cow, hash::Hash}; #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -124,9 +125,15 @@ pub(crate) struct AcirContext { /// The BigIntContext, used to generate identifiers for BigIntegers big_int_ctx: BigIntContext, + + expression_width: ExpressionWidth, } impl AcirContext { + pub(crate) fn set_expression_width(&mut self, expression_width: ExpressionWidth) { + self.expression_width = expression_width; + } + pub(crate) fn current_witness_index(&self) -> Witness { self.acir_ir.current_witness_index() } @@ -584,6 +591,7 @@ impl AcirContext { pub(crate) fn mul_var(&mut self, lhs: AcirVar, rhs: AcirVar) -> Result { let lhs_data = self.vars[&lhs].clone(); let rhs_data = self.vars[&rhs].clone(); + let result = match (lhs_data, rhs_data) { // (x * 1) == (1 * x) == x (AcirVarData::Const(constant), _) if constant.is_one() => rhs, @@ -655,6 +663,7 @@ impl AcirContext { self.mul_var(lhs, rhs)? } }; + Ok(result) } @@ -670,9 +679,62 @@ impl AcirContext { pub(crate) fn add_var(&mut self, lhs: AcirVar, rhs: AcirVar) -> Result { let lhs_expr = self.var_to_expression(lhs)?; let rhs_expr = self.var_to_expression(rhs)?; + let sum_expr = &lhs_expr + &rhs_expr; + if fits_in_one_identity(&sum_expr, self.expression_width) { + let sum_var = self.add_data(AcirVarData::from(sum_expr)); + + return Ok(sum_var); + } + + let sum_expr = match lhs_expr.width().cmp(&rhs_expr.width()) { + Ordering::Greater => { + let lhs_witness_var = self.get_or_create_witness_var(lhs)?; + let lhs_witness_expr = self.var_to_expression(lhs_witness_var)?; + + let new_sum_expr = &lhs_witness_expr + &rhs_expr; + if fits_in_one_identity(&new_sum_expr, self.expression_width) { + new_sum_expr + } else { + let rhs_witness_var = self.get_or_create_witness_var(rhs)?; + let rhs_witness_expr = self.var_to_expression(rhs_witness_var)?; + + &lhs_expr + &rhs_witness_expr + } + } + Ordering::Less => { + let rhs_witness_var = self.get_or_create_witness_var(rhs)?; + let rhs_witness_expr = self.var_to_expression(rhs_witness_var)?; + + let new_sum_expr = &lhs_expr + &rhs_witness_expr; + if fits_in_one_identity(&new_sum_expr, self.expression_width) { + new_sum_expr + } else { + let lhs_witness_var = self.get_or_create_witness_var(lhs)?; + let lhs_witness_expr = self.var_to_expression(lhs_witness_var)?; - Ok(self.add_data(AcirVarData::from(sum_expr))) + &lhs_witness_expr + &rhs_expr + } + } + Ordering::Equal => { + let lhs_witness_var = self.get_or_create_witness_var(lhs)?; + let lhs_witness_expr = self.var_to_expression(lhs_witness_var)?; + + let new_sum_expr = &lhs_witness_expr + &rhs_expr; + if fits_in_one_identity(&new_sum_expr, self.expression_width) { + new_sum_expr + } else { + let rhs_witness_var = self.get_or_create_witness_var(rhs)?; + let rhs_witness_expr = self.var_to_expression(rhs_witness_var)?; + + &lhs_witness_expr + &rhs_witness_expr + } + } + }; + + let sum_var = self.add_data(AcirVarData::from(sum_expr)); + + Ok(sum_var) } /// Adds a new Variable to context whose value will @@ -1990,6 +2052,23 @@ impl From> for AcirVarData { } } +/// Checks if this expression can fit into one arithmetic identity +fn fits_in_one_identity(expr: &Expression, width: ExpressionWidth) -> bool { + let width = match &width { + ExpressionWidth::Unbounded => { + return true; + } + ExpressionWidth::Bounded { width } => *width, + }; + + // A Polynomial with more than one mul term cannot fit into one opcode + if expr.mul_terms.len() > 1 { + return false; + }; + + expr.width() <= width +} + /// A Reference to an `AcirVarData` #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct AcirVar(usize); diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 1bdc9aaf4eb..dc0e026b3d1 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -33,7 +33,7 @@ use acvm::acir::circuit::opcodes::BlockType; use noirc_frontend::monomorphization::ast::InlineType; use acvm::acir::circuit::brillig::BrilligBytecode; -use acvm::acir::circuit::{AssertionPayload, ErrorSelector, OpcodeLocation}; +use acvm::acir::circuit::{AssertionPayload, ErrorSelector, ExpressionWidth, OpcodeLocation}; use acvm::acir::native_types::Witness; use acvm::acir::BlackBoxFunc; use acvm::{acir::circuit::opcodes::BlockId, acir::AcirField, FieldElement}; @@ -282,12 +282,16 @@ pub(crate) type Artifacts = ( impl Ssa { #[tracing::instrument(level = "trace", skip_all)] - pub(crate) fn into_acir(self, brillig: &Brillig) -> Result { + pub(crate) fn into_acir( + self, + brillig: &Brillig, + expression_width: ExpressionWidth, + ) -> Result { let mut acirs = Vec::new(); - // TODO: can we parallelise this? + // TODO: can we parallelize this? let mut shared_context = SharedContext::default(); for function in self.functions.values() { - let context = Context::new(&mut shared_context); + let context = Context::new(&mut shared_context, expression_width); if let Some(mut generated_acir) = context.convert_ssa_function(&self, function, brillig)? { @@ -334,8 +338,12 @@ impl Ssa { } impl<'a> Context<'a> { - fn new(shared_context: &'a mut SharedContext) -> Context<'a> { + fn new( + shared_context: &'a mut SharedContext, + expression_width: ExpressionWidth, + ) -> Context<'a> { let mut acir_context = AcirContext::default(); + acir_context.set_expression_width(expression_width); let current_side_effects_enabled_var = acir_context.add_constant(FieldElement::one()); Context { @@ -1288,6 +1296,7 @@ impl<'a> Context<'a> { index_side_effect = false; } } + // Fallback to multiplication if the index side_effects have not already been handled if index_side_effect { // Set the value to 0 if current_side_effects is 0, to ensure it fits in any value type @@ -2820,7 +2829,7 @@ mod test { use acvm::{ acir::{ - circuit::{Opcode, OpcodeLocation}, + circuit::{ExpressionWidth, Opcode, OpcodeLocation}, native_types::Witness, }, FieldElement, @@ -2917,7 +2926,7 @@ mod test { let ssa = builder.finish(); let (acir_functions, _, _) = ssa - .into_acir(&Brillig::default()) + .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); // Expected result: // main f0 @@ -3012,7 +3021,7 @@ mod test { let ssa = builder.finish(); let (acir_functions, _, _) = ssa - .into_acir(&Brillig::default()) + .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); // The expected result should look very similar to the above test expect that the input witnesses of the `Call` // opcodes will be different. The changes can discerned from the checks below. @@ -3102,7 +3111,7 @@ mod test { let ssa = builder.finish(); let (acir_functions, _, _) = ssa - .into_acir(&Brillig::default()) + .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 3, "Should have three ACIR functions"); @@ -3215,8 +3224,9 @@ mod test { let ssa = builder.finish(); let brillig = ssa.to_brillig(false); - let (acir_functions, brillig_functions, _) = - ssa.into_acir(&brillig).expect("Should compile manually written SSA into ACIR"); + let (acir_functions, brillig_functions, _) = ssa + .into_acir(&brillig, ExpressionWidth::default()) + .expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); assert_eq!(brillig_functions.len(), 2, "Should only have generated two Brillig functions"); @@ -3272,7 +3282,7 @@ mod test { // The Brillig bytecode we insert for the stdlib is hardcoded so we do not need to provide any // Brillig artifacts to the ACIR gen pass. let (acir_functions, brillig_functions, _) = ssa - .into_acir(&Brillig::default()) + .into_acir(&Brillig::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); @@ -3343,8 +3353,9 @@ mod test { let brillig = ssa.to_brillig(false); println!("{}", ssa); - let (acir_functions, brillig_functions, _) = - ssa.into_acir(&brillig).expect("Should compile manually written SSA into ACIR"); + let (acir_functions, brillig_functions, _) = ssa + .into_acir(&brillig, ExpressionWidth::default()) + .expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); // We expect 3 brillig functions: @@ -3431,8 +3442,9 @@ mod test { let brillig = ssa.to_brillig(false); println!("{}", ssa); - let (acir_functions, brillig_functions, _) = - ssa.into_acir(&brillig).expect("Should compile manually written SSA into ACIR"); + let (acir_functions, brillig_functions, _) = ssa + .into_acir(&brillig, ExpressionWidth::default()) + .expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 2, "Should only have two ACIR functions"); // We expect 3 brillig functions: diff --git a/compiler/noirc_frontend/src/elaborator/comptime.rs b/compiler/noirc_frontend/src/elaborator/comptime.rs index 0cbd2db55da..6bdad076501 100644 --- a/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -2,8 +2,7 @@ use std::mem::replace; use crate::{ hir_def::expr::HirIdent, - macros_api::Expression, - node_interner::{DependencyId, ExprId, FuncId}, + node_interner::{DependencyId, FuncId}, }; use super::{Elaborator, FunctionContext, ResolverMeta}; @@ -12,22 +11,22 @@ impl<'context> Elaborator<'context> { /// Elaborate an expression from the middle of a comptime scope. /// When this happens we require additional information to know /// what variables should be in scope. - pub fn elaborate_expression_from_comptime( + pub fn elaborate_item_from_comptime( &mut self, - expr: Expression, - function: Option, - ) -> ExprId { + current_function: Option, + f: impl FnOnce(&mut Self) -> T, + ) -> T { self.function_context.push(FunctionContext::default()); let old_scope = self.scopes.end_function(); self.scopes.start_function(); - let function_id = function.map(DependencyId::Function); + let function_id = current_function.map(DependencyId::Function); let old_item = replace(&mut self.current_item, function_id); // Note: recover_generics isn't good enough here because any existing generics // should not be in scope of this new function let old_generics = std::mem::take(&mut self.generics); - let old_crate_and_module = function.map(|function| { + let old_crate_and_module = current_function.map(|function| { let meta = self.interner.function_meta(&function); let old_crate = replace(&mut self.crate_id, meta.source_crate); let old_module = replace(&mut self.local_module, meta.source_module); @@ -36,7 +35,7 @@ impl<'context> Elaborator<'context> { }); self.populate_scope_from_comptime_scopes(); - let expr = self.elaborate_expression(expr).0; + let result = f(self); if let Some((old_crate, old_module)) = old_crate_and_module { self.crate_id = old_crate; @@ -48,7 +47,7 @@ impl<'context> Elaborator<'context> { self.scopes.end_function(); self.scopes.0.push(old_scope); self.check_and_pop_function_context(); - expr + result } fn populate_scope_from_comptime_scopes(&mut self) { diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 853098ce931..3b8fab5360d 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -35,7 +35,7 @@ use crate::{ use super::{Elaborator, LambdaContext}; impl<'context> Elaborator<'context> { - pub(super) fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) { + pub(crate) fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) { let (hir_expr, typ) = match expr.kind { ExpressionKind::Literal(literal) => self.elaborate_literal(literal, expr.span), ExpressionKind::Block(block) => self.elaborate_block(block), diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index f884d76dbf7..50db298d878 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -41,7 +41,7 @@ pub const WILDCARD_TYPE: &str = "_"; impl<'context> Elaborator<'context> { /// Translates an UnresolvedType to a Type with a `TypeKind::Normal` - pub(super) fn resolve_type(&mut self, typ: UnresolvedType) -> Type { + pub(crate) fn resolve_type(&mut self, typ: UnresolvedType) -> Type { let span = typ.span; let resolved_type = self.resolve_type_inner(typ, &Kind::Normal); if resolved_type.is_nested_slice() { diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 2090310585c..b0fde492d39 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -1,3 +1,4 @@ +use std::collections::VecDeque; use std::{collections::hash_map::Entry, rc::Rc}; use acvm::{acir::AcirField, FieldElement}; @@ -75,11 +76,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { perform_instantiation_bindings(&instantiation_bindings); let impl_bindings = perform_impl_bindings(self.elaborator.interner, trait_method, function, location)?; - let old_function = self.current_function.replace(function); let result = self.call_function_inner(function, arguments, location); - self.current_function = old_function; undo_instantiation_bindings(impl_bindings); undo_instantiation_bindings(instantiation_bindings); result @@ -110,9 +109,25 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { if meta.kind != FunctionKind::Normal { let return_type = meta.return_type().follow_bindings(); - return self.call_builtin(function, arguments, return_type, location); + return self.call_special(function, arguments, return_type, location); } + // Wait until after call_special to set the current function so that builtin functions like + // `.as_type()` still call the resolver in the caller's scope. + let old_function = self.current_function.replace(function); + let result = self.call_user_defined_function(function, arguments, location); + self.current_function = old_function; + result + } + + /// Call a non-builtin function + fn call_user_defined_function( + &mut self, + function: FuncId, + arguments: Vec<(Value, Location)>, + location: Location, + ) -> IResult { + let meta = self.elaborator.interner.function_meta(&function); let parameters = meta.parameters.0.clone(); let previous_state = self.enter_function(); @@ -132,7 +147,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Ok(result) } - fn call_builtin( + fn call_special( &mut self, function: FuncId, arguments: Vec<(Value, Location)>, @@ -145,13 +160,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { if let Some(builtin) = func_attrs.builtin() { let builtin = builtin.clone(); - builtin::call_builtin( - self.elaborator.interner, - &builtin, - arguments, - return_type, - location, - ) + self.call_builtin(&builtin, arguments, return_type, location) } else if let Some(foreign) = func_attrs.foreign() { let foreign = foreign.clone(); foreign::call_foreign(self.elaborator.interner, &foreign, arguments, location) @@ -449,16 +458,50 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { self.evaluate_integer(value, is_negative, id) } HirLiteral::Str(string) => Ok(Value::String(Rc::new(string))), - HirLiteral::FmtStr(_, _) => { - let item = "format strings in a comptime context".into(); - let location = self.elaborator.interner.expr_location(&id); - Err(InterpreterError::Unimplemented { item, location }) + HirLiteral::FmtStr(string, captures) => { + self.evaluate_format_string(string, captures, id) } HirLiteral::Array(array) => self.evaluate_array(array, id), HirLiteral::Slice(array) => self.evaluate_slice(array, id), } } + fn evaluate_format_string( + &mut self, + string: String, + captures: Vec, + id: ExprId, + ) -> IResult { + let mut result = String::new(); + let mut escaped = false; + let mut consuming = false; + + let mut values: VecDeque<_> = + captures.into_iter().map(|capture| self.evaluate(capture)).collect::>()?; + + for character in string.chars() { + match character { + '\\' => escaped = true, + '{' if !escaped => consuming = true, + '}' if !escaped && consuming => { + consuming = false; + + if let Some(value) = values.pop_front() { + result.push_str(&value.to_string()); + } + } + other if !consuming => { + escaped = false; + result.push(other); + } + _ => (), + } + } + + let typ = self.elaborator.interner.id_type(id); + Ok(Value::FormatString(Rc::new(result), typ)) + } + fn evaluate_integer( &self, value: FieldElement, @@ -1124,7 +1167,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let expr = result.into_expression(self.elaborator.interner, location)?; let expr = self .elaborator - .elaborate_expression_from_comptime(expr, self.current_function); + .elaborate_item_from_comptime(self.current_function, |elab| { + elab.elaborate_expression(expr).0 + }); result = self.evaluate(expr)?; } Ok(result) diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 02c45165ee3..92890cb66b8 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -19,41 +19,49 @@ use crate::{ QuotedType, Shared, Type, }; -pub(super) fn call_builtin( - interner: &mut NodeInterner, - name: &str, - arguments: Vec<(Value, Location)>, - return_type: Type, - location: Location, -) -> IResult { - match name { - "array_len" => array_len(interner, arguments, location), - "as_slice" => as_slice(interner, arguments, location), - "is_unconstrained" => Ok(Value::Bool(true)), - "modulus_be_bits" => modulus_be_bits(interner, arguments, location), - "modulus_be_bytes" => modulus_be_bytes(interner, arguments, location), - "modulus_le_bits" => modulus_le_bits(interner, arguments, location), - "modulus_le_bytes" => modulus_le_bytes(interner, arguments, location), - "modulus_num_bits" => modulus_num_bits(interner, arguments, location), - "slice_insert" => slice_insert(interner, arguments, location), - "slice_pop_back" => slice_pop_back(interner, arguments, location), - "slice_pop_front" => slice_pop_front(interner, arguments, location), - "slice_push_back" => slice_push_back(interner, arguments, location), - "slice_push_front" => slice_push_front(interner, arguments, location), - "slice_remove" => slice_remove(interner, arguments, location), - "struct_def_as_type" => struct_def_as_type(interner, arguments, location), - "struct_def_fields" => struct_def_fields(interner, arguments, location), - "struct_def_generics" => struct_def_generics(interner, arguments, location), - "trait_constraint_eq" => trait_constraint_eq(interner, arguments, location), - "trait_constraint_hash" => trait_constraint_hash(interner, arguments, location), - "trait_def_as_trait_constraint" => { - trait_def_as_trait_constraint(interner, arguments, location) - } - "quoted_as_trait_constraint" => quoted_as_trait_constraint(interner, arguments, location), - "zeroed" => zeroed(return_type, location), - _ => { - let item = format!("Comptime evaluation for builtin function {name}"); - Err(InterpreterError::Unimplemented { item, location }) +use super::Interpreter; + +impl<'local, 'context> Interpreter<'local, 'context> { + pub(super) fn call_builtin( + &mut self, + name: &str, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, + ) -> IResult { + let interner = &mut self.elaborator.interner; + match name { + "array_len" => array_len(interner, arguments, location), + "as_slice" => as_slice(interner, arguments, location), + "is_unconstrained" => Ok(Value::Bool(true)), + "modulus_be_bits" => modulus_be_bits(interner, arguments, location), + "modulus_be_bytes" => modulus_be_bytes(interner, arguments, location), + "modulus_le_bits" => modulus_le_bits(interner, arguments, location), + "modulus_le_bytes" => modulus_le_bytes(interner, arguments, location), + "modulus_num_bits" => modulus_num_bits(interner, arguments, location), + "slice_insert" => slice_insert(interner, arguments, location), + "slice_pop_back" => slice_pop_back(interner, arguments, location), + "slice_pop_front" => slice_pop_front(interner, arguments, location), + "slice_push_back" => slice_push_back(interner, arguments, location), + "slice_push_front" => slice_push_front(interner, arguments, location), + "slice_remove" => slice_remove(interner, arguments, location), + "struct_def_as_type" => struct_def_as_type(interner, arguments, location), + "struct_def_fields" => struct_def_fields(interner, arguments, location), + "struct_def_generics" => struct_def_generics(interner, arguments, location), + "trait_constraint_eq" => trait_constraint_eq(interner, arguments, location), + "trait_constraint_hash" => trait_constraint_hash(interner, arguments, location), + "trait_def_as_trait_constraint" => { + trait_def_as_trait_constraint(interner, arguments, location) + } + "quoted_as_trait_constraint" => { + quoted_as_trait_constraint(interner, arguments, location) + } + "quoted_as_type" => quoted_as_type(self, arguments, location), + "zeroed" => zeroed(return_type), + _ => { + let item = format!("Comptime evaluation for builtin function {name}"); + Err(InterpreterError::Unimplemented { item, location }) + } } } } @@ -203,7 +211,7 @@ fn slice_push_back( Ok(Value::Slice(values, typ)) } -/// fn as_type(self) -> Quoted +/// fn as_type(self) -> Type fn struct_def_as_type( interner: &NodeInterner, mut arguments: Vec<(Value, Location)>, @@ -211,28 +219,23 @@ fn struct_def_as_type( ) -> IResult { check_argument_count(1, &arguments, location)?; - let (struct_def, span) = match arguments.pop().unwrap() { - (Value::StructDefinition(id), location) => (id, location.span), + let struct_def = match arguments.pop().unwrap().0 { + Value::StructDefinition(id) => id, value => { let expected = Type::Quoted(QuotedType::StructDefinition); - return Err(InterpreterError::TypeMismatch { expected, location, value: value.0 }); + return Err(InterpreterError::TypeMismatch { expected, location, value }); } }; - let struct_def = interner.get_struct(struct_def); - let struct_def = struct_def.borrow(); - let make_token = |name| SpannedToken::new(Token::Ident(name), span); - - let mut tokens = vec![make_token(struct_def.name.to_string())]; + let struct_def_rc = interner.get_struct(struct_def); + let struct_def = struct_def_rc.borrow(); - for (i, generic) in struct_def.generics.iter().enumerate() { - if i != 0 { - tokens.push(SpannedToken::new(Token::Comma, span)); - } - tokens.push(make_token(generic.type_var.borrow().to_string())); - } + let generics = vecmap(&struct_def.generics, |generic| { + Type::NamedGeneric(generic.type_var.clone(), generic.name.clone(), generic.kind.clone()) + }); - Ok(Value::Code(Rc::new(Tokens(tokens)))) + drop(struct_def); + Ok(Value::Type(Type::Struct(struct_def_rc, generics))) } /// fn generics(self) -> [Quoted] @@ -263,7 +266,7 @@ fn struct_def_generics( Ok(Value::Slice(generics.collect(), typ)) } -/// fn fields(self) -> [(Quoted, Quoted)] +/// fn fields(self) -> [(Quoted, Type)] /// Returns (name, type) pairs of each field of this StructDefinition fn struct_def_fields( interner: &mut NodeInterner, @@ -290,15 +293,13 @@ fn struct_def_fields( for (name, typ) in struct_def.get_fields_as_written() { let name = make_quoted(vec![make_token(name)]); - let id = interner.push_quoted_type(typ); - let typ = SpannedToken::new(Token::QuotedType(id), span); - let typ = Value::Code(Rc::new(Tokens(vec![typ]))); + let typ = Value::Type(typ); fields.push_back(Value::Tuple(vec![name, typ])); } let typ = Type::Slice(Box::new(Type::Tuple(vec![ Type::Quoted(QuotedType::Quoted), - Type::Quoted(QuotedType::Quoted), + Type::Quoted(QuotedType::Type), ]))); Ok(Value::Slice(fields, typ)) } @@ -404,6 +405,30 @@ fn quoted_as_trait_constraint( Ok(Value::TraitConstraint(trait_bound)) } +// fn as_type(quoted: Quoted) -> Type +fn quoted_as_type( + interpreter: &mut Interpreter, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(1, &arguments, location)?; + + let tokens = get_quoted(arguments.pop().unwrap().0, location)?; + let quoted = tokens.as_ref().clone(); + + let typ = parser::parse_type().parse(quoted).map_err(|mut errors| { + let error = errors.swap_remove(0); + let rule = "a type"; + InterpreterError::FailedToParseMacro { error, tokens, rule, file: location.file } + })?; + + let typ = interpreter + .elaborator + .elaborate_item_from_comptime(interpreter.current_function, |elab| elab.resolve_type(typ)); + + Ok(Value::Type(typ)) +} + // fn constraint_hash(constraint: TraitConstraint) -> Field fn trait_constraint_hash( _interner: &mut NodeInterner, @@ -436,12 +461,12 @@ fn trait_constraint_eq( } // fn zeroed() -> T -fn zeroed(return_type: Type, location: Location) -> IResult { +fn zeroed(return_type: Type) -> IResult { match return_type { Type::FieldElement => Ok(Value::Field(0u128.into())), Type::Array(length_type, elem) => { if let Some(length) = length_type.evaluate_to_u32() { - let element = zeroed(elem.as_ref().clone(), location)?; + let element = zeroed(elem.as_ref().clone())?; let array = std::iter::repeat(element).take(length as usize).collect(); Ok(Value::Array(array, Type::Array(length_type, elem))) } else { @@ -471,33 +496,37 @@ fn zeroed(return_type: Type, location: Location) -> IResult { Ok(Value::Zeroed(Type::String(length_type))) } } - Type::FmtString(_, _) => { - let item = "format strings in a comptime context".into(); - Err(InterpreterError::Unimplemented { item, location }) + Type::FmtString(length_type, captures) => { + let length = length_type.evaluate_to_u32(); + let typ = Type::FmtString(length_type, captures); + if let Some(length) = length { + Ok(Value::FormatString(Rc::new("\0".repeat(length as usize)), typ)) + } else { + // Assume we can resolve the length later + Ok(Value::Zeroed(typ)) + } } Type::Unit => Ok(Value::Unit), - Type::Tuple(fields) => { - Ok(Value::Tuple(try_vecmap(fields, |field| zeroed(field, location))?)) - } + Type::Tuple(fields) => Ok(Value::Tuple(try_vecmap(fields, zeroed)?)), Type::Struct(struct_type, generics) => { let fields = struct_type.borrow().get_fields(&generics); let mut values = HashMap::default(); for (field_name, field_type) in fields { - let field_value = zeroed(field_type, location)?; + let field_value = zeroed(field_type)?; values.insert(Rc::new(field_name), field_value); } let typ = Type::Struct(struct_type, generics); Ok(Value::Struct(values, typ)) } - Type::Alias(alias, generics) => zeroed(alias.borrow().get_type(&generics), location), + Type::Alias(alias, generics) => zeroed(alias.borrow().get_type(&generics)), typ @ Type::Function(..) => { // Using Value::Zeroed here is probably safer than using FuncId::dummy_id() or similar Ok(Value::Zeroed(typ)) } Type::MutableReference(element) => { - let element = zeroed(*element, location)?; + let element = zeroed(*element)?; Ok(Value::Pointer(Shared::new(element), false)) } Type::Quoted(QuotedType::TraitConstraint) => Ok(Value::TraitConstraint(TraitBound { diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs index 94a848b891d..c80d39f8df8 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs @@ -1,8 +1,8 @@ use noirc_errors::Location; use crate::{ - hir::comptime::{errors::IResult, value::unwrap_rc, Value}, - token::{SpannedToken, Token, Tokens}, + hir::comptime::errors::IResult, + token::{Token, Tokens}, }; use super::Interpreter; @@ -19,20 +19,11 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let mut new_tokens = Vec::with_capacity(tokens.0.len()); for token in tokens.0 { - let span = token.to_span(); match token.token() { Token::UnquoteMarker(id) => { - match self.evaluate(*id)? { - // If the value is already quoted we don't want to change the token stream by - // turning it into a Quoted block (which would add `quote`, `{`, and `}` tokens). - Value::Code(stream) => new_tokens.extend(unwrap_rc(stream).0), - value => { - let new_id = - value.into_hir_expression(self.elaborator.interner, location)?; - let new_token = Token::UnquoteMarker(new_id); - new_tokens.push(SpannedToken::new(new_token, span)); - } - } + let value = self.evaluate(*id)?; + let tokens = value.into_tokens(self.elaborator.interner, location)?; + new_tokens.extend(tokens.0); } _ => new_tokens.push(token), } diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index f29b67bfc4e..d20372e6812 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -38,6 +38,7 @@ pub enum Value { U32(u32), U64(u64), String(Rc), + FormatString(Rc, Type), Function(FuncId, Type, Rc), Closure(HirLambda, Vec, Type), Tuple(Vec), @@ -51,6 +52,7 @@ pub enum Value { TraitDefinition(TraitId), FunctionDefinition(FuncId), ModuleDefinition(ModuleId), + Type(Type), Zeroed(Type), } @@ -73,6 +75,7 @@ impl Value { let length = Type::Constant(value.len() as u32); Type::String(Box::new(length)) } + Value::FormatString(_, typ) => return Cow::Borrowed(typ), Value::Function(_, typ, _) => return Cow::Borrowed(typ), Value::Closure(_, _, typ) => return Cow::Borrowed(typ), Value::Tuple(fields) => { @@ -95,6 +98,7 @@ impl Value { Value::TraitDefinition(_) => Type::Quoted(QuotedType::TraitDefinition), Value::FunctionDefinition(_) => Type::Quoted(QuotedType::FunctionDefinition), Value::ModuleDefinition(_) => Type::Quoted(QuotedType::Module), + Value::Type(_) => Type::Quoted(QuotedType::Type), Value::Zeroed(typ) => return Cow::Borrowed(typ), }) } @@ -148,6 +152,10 @@ impl Value { ExpressionKind::Literal(Literal::Integer((value as u128).into(), false)) } Value::String(value) => ExpressionKind::Literal(Literal::Str(unwrap_rc(value))), + // Format strings are lowered as normal strings since they are already interpolated. + Value::FormatString(value, _) => { + ExpressionKind::Literal(Literal::Str(unwrap_rc(value))) + } Value::Function(id, typ, bindings) => { let id = interner.function_definition_id(id); let impl_kind = ImplKind::NotATraitMethod; @@ -218,6 +226,7 @@ impl Value { | Value::TraitDefinition(_) | Value::FunctionDefinition(_) | Value::Zeroed(_) + | Value::Type(_) | Value::ModuleDefinition(_) => { return Err(InterpreterError::CannotInlineMacro { value: self, location }) } @@ -277,6 +286,10 @@ impl Value { HirExpression::Literal(HirLiteral::Integer((value as u128).into(), false)) } Value::String(value) => HirExpression::Literal(HirLiteral::Str(unwrap_rc(value))), + // Format strings are lowered as normal strings since they are already interpolated. + Value::FormatString(value, _) => { + HirExpression::Literal(HirLiteral::Str(unwrap_rc(value))) + } Value::Function(id, typ, bindings) => { let id = interner.function_definition_id(id); let impl_kind = ImplKind::NotATraitMethod; @@ -333,6 +346,7 @@ impl Value { | Value::TraitDefinition(_) | Value::FunctionDefinition(_) | Value::Zeroed(_) + | Value::Type(_) | Value::ModuleDefinition(_) => { return Err(InterpreterError::CannotInlineMacro { value: self, location }) } @@ -344,6 +358,19 @@ impl Value { Ok(id) } + pub(crate) fn into_tokens( + self, + interner: &mut NodeInterner, + location: Location, + ) -> IResult { + let token = match self { + Value::Code(tokens) => return Ok(unwrap_rc(tokens)), + Value::Type(typ) => Token::QuotedType(interner.push_quoted_type(typ)), + other => Token::UnquoteMarker(other.into_hir_expression(interner, location)?), + }; + Ok(Tokens(vec![SpannedToken::new(token, location.span)])) + } + /// Converts any unsigned `Value` into a `u128`. /// Returns `None` for negative integers. pub(crate) fn to_u128(&self) -> Option { @@ -407,6 +434,7 @@ impl Display for Value { Value::U32(value) => write!(f, "{value}"), Value::U64(value) => write!(f, "{value}"), Value::String(value) => write!(f, "{value}"), + Value::FormatString(value, _) => write!(f, "{value}"), Value::Function(..) => write!(f, "(function)"), Value::Closure(_, _, _) => write!(f, "(closure)"), Value::Tuple(fields) => { @@ -443,6 +471,7 @@ impl Display for Value { Value::FunctionDefinition(_) => write!(f, "(function definition)"), Value::ModuleDefinition(_) => write!(f, "(module)"), Value::Zeroed(typ) => write!(f, "(zeroed {typ})"), + Value::Type(typ) => write!(f, "{}", typ), } } } diff --git a/compiler/noirc_frontend/src/parser/mod.rs b/compiler/noirc_frontend/src/parser/mod.rs index c62d66769ac..677d741b5e0 100644 --- a/compiler/noirc_frontend/src/parser/mod.rs +++ b/compiler/noirc_frontend/src/parser/mod.rs @@ -22,7 +22,7 @@ use chumsky::primitive::Container; pub use errors::ParserError; pub use errors::ParserErrorReason; use noirc_errors::Span; -pub use parser::{expression, parse_program, top_level_items, trait_bound}; +pub use parser::{expression, parse_program, parse_type, top_level_items, trait_bound}; #[derive(Debug, Clone)] pub enum TopLevelStatement { diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index ca2f4b69aa5..3879f628eae 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -24,7 +24,8 @@ //! be limited to cases like the above `fn` example where it is clear we shouldn't back out of the //! current parser to try alternative parsers in a `choice` expression. use self::primitives::{keyword, macro_quote_marker, mutable_reference, variable}; -use self::types::{generic_type_args, maybe_comp_time, parse_type}; +use self::types::{generic_type_args, maybe_comp_time}; +pub use types::parse_type; use super::{ foldl_with_span, labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, diff --git a/compiler/noirc_frontend/src/parser/parser.rs:28:9 b/compiler/noirc_frontend/src/parser/parser.rs:28:9 new file mode 100644 index 00000000000..47dfb32b53b --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser.rs:28:9 @@ -0,0 +1,45 @@ +[?1049h[?1h[?2004h[?2026$p[?u[?12h[?25h[?25l(B[38:2:235:219:178m[48:2:168:153:132m [No Name]  (B[38:2:168:153:132m(B[38:2:235:219:178m (B[38:2:80:73:69m(B[38:2:168:153:132m[48:2:80:73:69m buffers +(B[38:2:124:111:100m 1 (B[38:2:235:219:178m +(B[38:2:80:73:69m~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +~ +(B[38:2:235:219:178m[48:2:168:153:132m (B[38:2:235:219:178m[48:2:168:153:132mNORMAL(B[38:2:235:219:178m[48:2:168:153:132m (B[38:2:168:153:132m[48:2:80:73:69m  jf/quoted-as-type (B[38:2:80:73:69m[48:2:60:56:54m(B[38:2:168:153:132m[48:2:60:56:54m (B[38:2:60:56:54m[48:2:60:56:54m(B[38:2:168:153:132m[48:2:60:56:54m  (B[38:2:80:73:69m[48:2:60:56:54m(B[38:2:168:153:132m[48:2:80:73:69m(B[38:2:235:219:178m[48:2:168:153:132m 100% (B[38:2:235:219:178m[48:2:168:153:132m☰ 0/1 (B[38:2:235:219:178m[48:2:168:153:132m : 1 (B[38:2:254:128:25m[48:2:168:153:132m(B[38:2:235:219:178mNVIM v0.10.0Nvim is open source and freely distributablehttps://neovim.io/#chattype :help nvim(B[38:2:80:73:69m(B[38:2:235:219:178m if you are new! type :checkhealth(B[38:2:80:73:69m(B[38:2:235:219:178m to optimize Nvimtype :q(B[38:2:80:73:69m(B[38:2:235:219:178m to exit type :help(B[38:2:80:73:69m(B[38:2:235:219:178m for help type :help news(B[38:2:80:73:69m(B[38:2:235:219:178m to see changes in v0.10Help poor children in Uganda!type :help iccf(B[38:2:80:73:69m(B[38:2:235:219:178m for information ]112[2 q]112[2 q[?1002h[?1006h(B[38:2:235:219:178m[48:2:168:153:132m [No Name]  (B[38:2:168:153:132m(B[38:2:235:219:178m (B[38:2:80:73:69m(B[38:2:168:153:132m[48:2:80:73:69m buffers +(B[38:2:124:111:100m 1 (B[38:2:235:219:178m +(B[38:2:80:73:69m~ +~ +~ (B[38:2:235:219:178mNVIM v0.10.0(B[38:2:80:73:69m +~ +~ (B[38:2:235:219:178mNvim is open source and freely distributable(B[38:2:80:73:69m +~ (B[38:2:235:219:178mhttps://neovim.io/#chat(B[38:2:80:73:69m +~ +~ (B[38:2:235:219:178mtype :help nvim(B[38:2:80:73:69m(B[38:2:235:219:178m if you are new! (B[38:2:80:73:69m +~ (B[38:2:235:219:178mtype :checkhealth(B[38:2:80:73:69m(B[38:2:235:219:178m to optimize Nvim(B[38:2:80:73:69m +~ (B[38:2:235:219:178mtype :q(B[38:2:80:73:69m(B[38:2:235:219:178m to exit (B[38:2:80:73:69m +~ (B[38:2:235:219:178mtype :help(B[38:2:80:73:69m(B[38:2:235:219:178m for help (B[38:2:80:73:69m +~ +~ (B[38:2:235:219:178mtype :help news(B[38:2:80:73:69m(B[38:2:235:219:178m to see changes in v0.10(B[38:2:80:73:69m +~ +~ (B[38:2:235:219:178mHelp poor children in Uganda!(B[38:2:80:73:69m +~ (B[38:2:235:219:178mtype :help iccf(B[38:2:80:73:69m(B[38:2:235:219:178m for information (B[38:2:80:73:69m +~ +~ +~ +~ +(B[38:2:235:219:178m[48:2:168:153:132m (B[38:2:235:219:178m[48:2:168:153:132mNORMAL(B[38:2:235:219:178m[48:2:168:153:132m (B[38:2:168:153:132m[48:2:80:73:69m  jf/quoted-as-type (B[38:2:80:73:69m[48:2:60:56:54m(B[38:2:168:153:132m[48:2:60:56:54m (B[38:2:60:56:54m[48:2:60:56:54m(B[38:2:168:153:132m[48:2:60:56:54m  (B[38:2:80:73:69m[48:2:60:56:54m(B[38:2:168:153:132m[48:2:80:73:69m(B[38:2:235:219:178m[48:2:168:153:132m 100% (B[38:2:235:219:178m[48:2:168:153:132m☰ 0/1 (B[38:2:235:219:178m[48:2:168:153:132m : 1 (B[38:2:254:128:25m[48:2:168:153:132m(B[38:2:235:219:178m[?12h[?25h[?25l[?1004h[?12h[?25h \ No newline at end of file diff --git a/compiler/noirc_frontend/src/parser/parser/types.rs b/compiler/noirc_frontend/src/parser/parser/types.rs index cecc1cbcd4c..f859227b82b 100644 --- a/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/compiler/noirc_frontend/src/parser/parser/types.rs @@ -14,7 +14,7 @@ use crate::token::{Keyword, Token, TokenKind}; use chumsky::prelude::*; use noirc_errors::Span; -pub(super) fn parse_type<'a>() -> impl NoirParser + 'a { +pub fn parse_type<'a>() -> impl NoirParser + 'a { recursive(parse_type_inner) } diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index f0c986f1c28..29f612b0109 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -14,7 +14,6 @@ export default { favicon: 'img/favicon.ico', url: 'https://noir-lang.org', baseUrl: '/', - trailingSlash: true, onBrokenLinks: 'throw', onBrokenMarkdownLinks: 'throw', i18n: { diff --git a/docs/src/components/Notes/_blackbox.mdx b/docs/src/components/Notes/_blackbox.mdx index 226017072c8..514ca00a7e7 100644 --- a/docs/src/components/Notes/_blackbox.mdx +++ b/docs/src/components/Notes/_blackbox.mdx @@ -1,5 +1,5 @@ :::info -This is a black box function. Read [this section](../../black_box_fns) to learn more about black box functions in Noir. +This is a black box function. Read [this section](/docs/noir/standard_library/black_box_fns) to learn more about black box functions in Noir. ::: diff --git a/noir_stdlib/src/meta/quoted.nr b/noir_stdlib/src/meta/quoted.nr index 6273d64b10c..8e96e8828f8 100644 --- a/noir_stdlib/src/meta/quoted.nr +++ b/noir_stdlib/src/meta/quoted.nr @@ -1,4 +1,7 @@ impl Quoted { #[builtin(quoted_as_trait_constraint)] fn as_trait_constraint(self) -> TraitConstraint {} + + #[builtin(quoted_as_type)] + fn as_type(self) -> Type {} } diff --git a/noir_stdlib/src/meta/type_def.nr b/noir_stdlib/src/meta/type_def.nr index c01aab4b141..7382aeb8e75 100644 --- a/noir_stdlib/src/meta/type_def.nr +++ b/noir_stdlib/src/meta/type_def.nr @@ -2,7 +2,7 @@ impl StructDefinition { /// Return a syntactic version of this struct definition as a type. /// For example, `as_type(quote { type Foo { ... } })` would return `Foo` #[builtin(struct_def_as_type)] - fn as_type(self) -> Quoted {} + fn as_type(self) -> Type {} /// Return each generic on this struct. The names of these generics are unchanged /// so users may need to keep name collisions in mind if this is used directly in a macro. @@ -12,5 +12,5 @@ impl StructDefinition { /// Returns (name, type) pairs of each field in this struct. Each type is as-is /// with any generic arguments unchanged. #[builtin(struct_def_fields)] - fn fields(self) -> [(Quoted, Quoted)] {} + fn fields(self) -> [(Quoted, Type)] {} } diff --git a/test_programs/compile_success_empty/comptime_fmt_strings/Nargo.toml b/test_programs/compile_success_empty/comptime_fmt_strings/Nargo.toml new file mode 100644 index 00000000000..84162d3c093 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_fmt_strings/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_fmt_strings" +type = "bin" +authors = [""] +compiler_version = ">=0.32.0" + +[dependencies] diff --git a/test_programs/compile_success_empty/comptime_fmt_strings/src/main.nr b/test_programs/compile_success_empty/comptime_fmt_strings/src/main.nr new file mode 100644 index 00000000000..19572fd15a1 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_fmt_strings/src/main.nr @@ -0,0 +1,15 @@ +fn main() { + // format strings are lowered as normal strings + let (s1, s2): (str<39>, str<4>) = comptime { + let x = 4; + let y = 5; + + // Can't print these at compile-time here since printing to stdout while + // compiling breaks the test runner. + let s1 = f"x is {x}, fake interpolation: \{y}, y is {y}"; + let s2 = std::unsafe::zeroed::>(); + (s1, s2) + }; + assert_eq(s1, "x is 4, fake interpolation: {y}, y is 5"); + assert_eq(s2, "\0\0\0\0"); +} diff --git a/test_programs/compile_success_empty/derive_impl/src/main.nr b/test_programs/compile_success_empty/derive_impl/src/main.nr index 5463a61d969..62b8a0421e6 100644 --- a/test_programs/compile_success_empty/derive_impl/src/main.nr +++ b/test_programs/compile_success_empty/derive_impl/src/main.nr @@ -27,7 +27,7 @@ struct Foo { #[derive_default] struct Bar {} -comptime fn make_field_exprs(fields: [(Quoted, Quoted)]) -> [Quoted] { +comptime fn make_field_exprs(fields: [(Quoted, Type)]) -> [Quoted] { let mut result = &[]; for my_field in fields { let name = my_field.0; diff --git a/test_programs/compile_success_empty/quoted_as_type/Nargo.toml b/test_programs/compile_success_empty/quoted_as_type/Nargo.toml new file mode 100644 index 00000000000..7d669ead363 --- /dev/null +++ b/test_programs/compile_success_empty/quoted_as_type/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "quoted_as_type" +type = "bin" +authors = [""] +compiler_version = ">=0.32.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/quoted_as_type/src/main.nr b/test_programs/compile_success_empty/quoted_as_type/src/main.nr new file mode 100644 index 00000000000..c48ac717bc0 --- /dev/null +++ b/test_programs/compile_success_empty/quoted_as_type/src/main.nr @@ -0,0 +1,21 @@ +fn main() { + macro!().do_nothing(); +} + +comptime fn macro() -> Quoted { + let typ = quote { Foo }.as_type(); + quote { let foo: $typ = Foo {}; foo } +} + +comptime struct Foo {} + +// Ensure we call the Foo impl +impl Foo { + fn do_nothing(_self: Self) { + assert(false); + } +} + +impl Foo { + fn do_nothing(_self: Self) {} +} diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index a2877ebdeac..3e3560c91bf 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -9,8 +9,8 @@ use nargo::package::Package; use nargo::workspace::Workspace; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; -use noirc_driver::file_manager_with_stdlib; use noirc_driver::NOIR_ARTIFACT_VERSION_STRING; +use noirc_driver::{file_manager_with_stdlib, DEFAULT_EXPRESSION_WIDTH}; use noirc_driver::{CompilationResult, CompileOptions, CompiledContract}; use noirc_frontend::graph::CrateName; @@ -250,12 +250,6 @@ fn save_contract( } } -/// Default expression width used for Noir compilation. -/// The ACVM native type `ExpressionWidth` has its own default which should always be unbounded, -/// while we can sometimes expect the compilation target width to change. -/// Thus, we set it separately here rather than trying to alter the default derivation of the type. -const DEFAULT_EXPRESSION_WIDTH: ExpressionWidth = ExpressionWidth::Bounded { width: 4 }; - /// If a target width was not specified in the CLI we can safely override the default. pub(crate) fn get_target_width( package_default_width: Option,