diff --git a/Cargo.lock b/Cargo.lock index f635a3ad69f..6b7bf85bfa8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -726,6 +726,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3362992a0d9f1dd7c3d0e89e0ab2bb540b7a95fea8cd798090e758fda2899b5e" dependencies = [ "codespan-reporting", + "serde", ] [[package]] @@ -745,6 +746,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ + "serde", "termcolor", "unicode-width", ] @@ -1253,6 +1255,7 @@ dependencies = [ "cfg-if", "codespan-reporting", "rust-embed", + "serde", "tempfile", "wasm-bindgen", ] @@ -1994,6 +1997,7 @@ dependencies = [ "iter-extended", "noirc_abi", "noirc_driver", + "noirc_errors", "rustc_version", "serde", "thiserror", @@ -2015,6 +2019,7 @@ dependencies = [ "color-eyre", "const_format", "dirs", + "fm", "hex", "iter-extended", "nargo", @@ -2106,6 +2111,7 @@ dependencies = [ "codespan", "codespan-reporting", "fm", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 44a3d2688cd..11af5c983e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ noir_wasm = { path = "crates/wasm" } cfg-if = "1.0.0" clap = { version = "4.1.4", features = ["derive"] } -codespan = "0.11.1" +codespan = {version = "0.11.1", features = ["serialization"]} codespan-lsp = "0.11.1" codespan-reporting = "0.11.1" chumsky = { git = "https://github.com/jfecher/chumsky", rev = "ad9d312" } diff --git a/crates/fm/Cargo.toml b/crates/fm/Cargo.toml index 8614671a861..48f1932f9d6 100644 --- a/crates/fm/Cargo.toml +++ b/crates/fm/Cargo.toml @@ -10,6 +10,7 @@ edition.workspace = true codespan-reporting.workspace = true cfg-if.workspace = true rust-embed = "6.6.0" +serde.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen.workspace = true diff --git a/crates/fm/src/file_map.rs b/crates/fm/src/file_map.rs index 4dbfbece0e0..8bbfcf99dd7 100644 --- a/crates/fm/src/file_map.rs +++ b/crates/fm/src/file_map.rs @@ -1,8 +1,8 @@ +use crate::FileManager; use codespan_reporting::files::{SimpleFile, SimpleFiles}; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use crate::FileManager; - // XXX: File and FileMap serve as opaque types, so that the rest of the library does not need to import the dependency // or worry about when we change the dep @@ -34,7 +34,7 @@ impl From<&PathBuf> for PathString { pub struct FileMap(SimpleFiles); // XXX: Note that we derive Default here due to ModuleOrigin requiring us to set a FileId -#[derive(Default, Debug, Clone, PartialEq, Eq, Copy, Hash)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Copy, Hash, Serialize, Deserialize)] pub struct FileId(usize); impl FileId { diff --git a/crates/nargo/Cargo.toml b/crates/nargo/Cargo.toml index 8d3c9fbd3cd..f85c18ec001 100644 --- a/crates/nargo/Cargo.toml +++ b/crates/nargo/Cargo.toml @@ -18,3 +18,4 @@ iter-extended.workspace = true toml.workspace = true serde.workspace = true thiserror.workspace = true +noirc_errors.workspace = true diff --git a/crates/nargo/src/ops/preprocess.rs b/crates/nargo/src/ops/preprocess.rs index b1613ea3195..d07da256ede 100644 --- a/crates/nargo/src/ops/preprocess.rs +++ b/crates/nargo/src/ops/preprocess.rs @@ -1,5 +1,6 @@ use acvm::ProofSystemCompiler; use noirc_driver::{CompiledProgram, ContractFunction}; +use noirc_errors::debug_info::DebugInfo; use crate::artifacts::{contract::PreprocessedContractFunction, program::PreprocessedProgram}; @@ -11,7 +12,7 @@ pub fn preprocess_program( include_keys: bool, common_reference_string: &[u8], compiled_program: CompiledProgram, -) -> Result { +) -> Result<(PreprocessedProgram, DebugInfo), B::Error> { // TODO: currently `compiled_program`'s bytecode is already optimized for the backend. // In future we'll need to apply those optimizations here. let optimized_bytecode = compiled_program.circuit; @@ -24,13 +25,16 @@ pub fn preprocess_program( (None, None) }; - Ok(PreprocessedProgram { - backend: String::from(BACKEND_IDENTIFIER), - abi: compiled_program.abi, - bytecode: optimized_bytecode, - proving_key, - verification_key, - }) + Ok(( + PreprocessedProgram { + backend: String::from(BACKEND_IDENTIFIER), + abi: compiled_program.abi, + bytecode: optimized_bytecode, + proving_key, + verification_key, + }, + compiled_program.debug, + )) } pub fn preprocess_contract_function( diff --git a/crates/nargo_cli/Cargo.toml b/crates/nargo_cli/Cargo.toml index 26e0436f5d4..1b09001ef59 100644 --- a/crates/nargo_cli/Cargo.toml +++ b/crates/nargo_cli/Cargo.toml @@ -30,6 +30,7 @@ noirc_frontend.workspace = true noirc_abi.workspace = true noirc_errors.workspace = true acvm.workspace = true +fm.workspace = true toml.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs b/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs index 40587509024..c19ed5df3a6 100644 --- a/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs +++ b/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs @@ -51,12 +51,12 @@ pub(crate) fn run( (common_reference_string, program) } None => { - let program = + let (program, _) = compile_circuit(backend, config.program_dir.as_ref(), &args.compile_options)?; let common_reference_string = update_common_reference_string(backend, &common_reference_string, &program.circuit) .map_err(CliError::CommonReferenceStringError)?; - let program = preprocess_program(backend, true, &common_reference_string, program) + let (program, _) = preprocess_program(backend, true, &common_reference_string, program) .map_err(CliError::ProofSystemCompilerError)?; (common_reference_string, program) } diff --git a/crates/nargo_cli/src/cli/compile_cmd.rs b/crates/nargo_cli/src/cli/compile_cmd.rs index e395b263efd..da945e6e15e 100644 --- a/crates/nargo_cli/src/cli/compile_cmd.rs +++ b/crates/nargo_cli/src/cli/compile_cmd.rs @@ -1,5 +1,7 @@ +use acvm::acir::circuit::OpcodeLabel; use acvm::{acir::circuit::Circuit, Backend}; use iter_extended::try_vecmap; +use iter_extended::vecmap; use nargo::{artifacts::contract::PreprocessedContract, NargoError}; use noirc_driver::{ compile_contracts, compile_main, CompileOptions, CompiledProgram, ErrorsAndWarnings, Warnings, @@ -68,8 +70,7 @@ pub(crate) fn run( try_vecmap(contracts, |contract| { let preprocessed_contract_functions = try_vecmap(contract.functions, |mut func| { - func.bytecode = optimize_circuit(backend, func.bytecode)?; - + func.bytecode = optimize_circuit(backend, func.bytecode)?.0; common_reference_string = update_common_reference_string( backend, &common_reference_string, @@ -100,13 +101,12 @@ pub(crate) fn run( ); } } else { - let program = compile_circuit(backend, &config.program_dir, &args.compile_options)?; - + let (program, _) = compile_circuit(backend, &config.program_dir, &args.compile_options)?; common_reference_string = update_common_reference_string(backend, &common_reference_string, &program.circuit) .map_err(CliError::CommonReferenceStringError)?; - let preprocessed_program = + let (preprocessed_program, _) = preprocess_program(backend, args.include_keys, &common_reference_string, program) .map_err(CliError::ProofSystemCompilerError)?; save_program_to_file(&preprocessed_program, &args.circuit_name, circuit_dir); @@ -121,27 +121,37 @@ pub(crate) fn compile_circuit( backend: &B, program_dir: &Path, compile_options: &CompileOptions, -) -> Result> { +) -> Result<(CompiledProgram, Context), CliError> { let mut context = resolve_root_manifest(program_dir)?; let result = compile_main(&mut context, compile_options); let mut program = report_errors(result, &context, compile_options.deny_warnings)?; // Apply backend specific optimizations. - program.circuit = optimize_circuit(backend, program.circuit).unwrap(); - Ok(program) + let (optimized_circuit, opcode_labels) = optimize_circuit(backend, program.circuit) + .expect("Backend does not support an opcode that is in the IR"); + + program.circuit = optimized_circuit; + let opcode_ids = vecmap(opcode_labels, |label| match label { + OpcodeLabel::Unresolved => { + unreachable!("Compiled circuit opcodes must resolve to some index") + } + OpcodeLabel::Resolved(index) => index as usize, + }); + program.debug.update_acir(opcode_ids); + + Ok((program, context)) } pub(super) fn optimize_circuit( backend: &B, circuit: Circuit, -) -> Result> { - let (optimized_circuit, _): (Circuit, Vec) = - acvm::compiler::compile(circuit, backend.np_language(), |opcode| { - backend.supports_opcode(opcode) - }) - .map_err(|_| NargoError::CompilationError)?; - - Ok(optimized_circuit) +) -> Result<(Circuit, Vec), CliError> { + let result = acvm::compiler::compile(circuit, backend.np_language(), |opcode| { + backend.supports_opcode(opcode) + }) + .map_err(|_| NargoError::CompilationError)?; + + Ok(result) } /// Helper function for reporting any errors in a Result<(T, Warnings), ErrorsAndWarnings> diff --git a/crates/nargo_cli/src/cli/execute_cmd.rs b/crates/nargo_cli/src/cli/execute_cmd.rs index c175c0336f1..d8b764a6282 100644 --- a/crates/nargo_cli/src/cli/execute_cmd.rs +++ b/crates/nargo_cli/src/cli/execute_cmd.rs @@ -1,11 +1,15 @@ use std::path::Path; +use acvm::acir::circuit::OpcodeLabel; use acvm::acir::{circuit::Circuit, native_types::WitnessMap}; use acvm::Backend; use clap::Args; +use nargo::NargoError; use noirc_abi::input_parser::{Format, InputValue}; use noirc_abi::{Abi, InputMap}; use noirc_driver::{CompileOptions, CompiledProgram}; +use noirc_errors::{debug_info::DebugInfo, CustomDiagnostic}; +use noirc_frontend::hir::Context; use super::fs::{inputs::read_inputs_from_file, witness::save_witness_to_dir}; use super::NargoConfig; @@ -57,29 +61,78 @@ fn execute_with_path( prover_name: String, compile_options: &CompileOptions, ) -> Result<(Option, WitnessMap), CliError> { - let CompiledProgram { abi, circuit } = compile_circuit(backend, program_dir, compile_options)?; + let (compiled_program, context) = compile_circuit(backend, program_dir, compile_options)?; + let CompiledProgram { abi, circuit, debug } = compiled_program; // Parse the initial witness values from Prover.toml let (inputs_map, _) = read_inputs_from_file(program_dir, prover_name.as_str(), Format::Toml, &abi)?; - let solved_witness = execute_program(backend, circuit, &abi, &inputs_map)?; - + let solved_witness = + execute_program(backend, circuit, &abi, &inputs_map, Some((debug, context)))?; let public_abi = abi.public_abi(); let (_, return_value) = public_abi.decode(&solved_witness)?; Ok((return_value, solved_witness)) } +fn extract_unsatisfied_constraint_from_nargo_error(nargo_err: &NargoError) -> Option { + let solving_err = match nargo_err { + nargo::NargoError::SolvingError(err) => err, + _ => return None, + }; + + match solving_err { + acvm::pwg::OpcodeResolutionError::UnsatisfiedConstrain { opcode_label } => { + match opcode_label { + OpcodeLabel::Unresolved => { + unreachable!("Cannot resolve index for unsatisfied constraint") + } + OpcodeLabel::Resolved(opcode_index) => Some(*opcode_index as usize), + } + } + _ => None, + } +} +fn report_unsatisfied_constraint_error( + opcode_idx: Option, + debug: &DebugInfo, + context: &Context, +) { + if let Some(opcode_index) = opcode_idx { + if let Some(loc) = debug.opcode_location(opcode_index) { + noirc_errors::reporter::report( + &context.file_manager, + &CustomDiagnostic::simple_error( + "Unsatisfied constraint".to_string(), + "Constraint failed".to_string(), + loc.span, + ), + Some(loc.file), + false, + ); + } + } +} + pub(crate) fn execute_program( backend: &B, circuit: Circuit, abi: &Abi, inputs_map: &InputMap, + debug_data: Option<(DebugInfo, Context)>, ) -> Result> { let initial_witness = abi.encode(inputs_map, None)?; - - let solved_witness = nargo::ops::execute_circuit(backend, circuit, initial_witness)?; - - Ok(solved_witness) + let solved_witness_err = nargo::ops::execute_circuit(backend, circuit, initial_witness); + match solved_witness_err { + Ok(solved_witness) => Ok(solved_witness), + Err(err) => { + if let Some((debug, context)) = debug_data { + let opcode_idx = extract_unsatisfied_constraint_from_nargo_error(&err); + report_unsatisfied_constraint_error(opcode_idx, &debug, &context); + } + + Err(crate::errors::CliError::NargoError(err)) + } + } } diff --git a/crates/nargo_cli/src/cli/gates_cmd.rs b/crates/nargo_cli/src/cli/gates_cmd.rs index 88e11c683eb..030daef9719 100644 --- a/crates/nargo_cli/src/cli/gates_cmd.rs +++ b/crates/nargo_cli/src/cli/gates_cmd.rs @@ -28,7 +28,7 @@ fn count_gates_with_path>( program_dir: P, compile_options: &CompileOptions, ) -> Result<(), CliError> { - let compiled_program = compile_circuit(backend, program_dir.as_ref(), compile_options)?; + let (compiled_program, _) = compile_circuit(backend, program_dir.as_ref(), compile_options)?; let num_opcodes = compiled_program.circuit.opcodes.len(); println!( diff --git a/crates/nargo_cli/src/cli/prove_cmd.rs b/crates/nargo_cli/src/cli/prove_cmd.rs index 0200b417396..346638835e3 100644 --- a/crates/nargo_cli/src/cli/prove_cmd.rs +++ b/crates/nargo_cli/src/cli/prove_cmd.rs @@ -91,7 +91,7 @@ pub(crate) fn prove_with_path>( ) -> Result, CliError> { let common_reference_string = read_cached_common_reference_string(); - let (common_reference_string, preprocessed_program) = match circuit_build_path { + let (common_reference_string, preprocessed_program, debug_data) = match circuit_build_path { Some(circuit_build_path) => { let program = read_program_from_file(circuit_build_path)?; let common_reference_string = update_common_reference_string( @@ -100,16 +100,18 @@ pub(crate) fn prove_with_path>( &program.bytecode, ) .map_err(CliError::CommonReferenceStringError)?; - (common_reference_string, program) + (common_reference_string, program, None) } None => { - let program = compile_circuit(backend, program_dir.as_ref(), compile_options)?; + let (program, context) = + compile_circuit(backend, program_dir.as_ref(), compile_options)?; let common_reference_string = update_common_reference_string(backend, &common_reference_string, &program.circuit) .map_err(CliError::CommonReferenceStringError)?; - let program = preprocess_program(backend, true, &common_reference_string, program) - .map_err(CliError::ProofSystemCompilerError)?; - (common_reference_string, program) + let (program, debug) = + preprocess_program(backend, true, &common_reference_string, program) + .map_err(CliError::ProofSystemCompilerError)?; + (common_reference_string, program, Some((debug, context))) } }; @@ -122,7 +124,7 @@ pub(crate) fn prove_with_path>( let (inputs_map, _) = read_inputs_from_file(&program_dir, prover_name.as_str(), Format::Toml, &abi)?; - let solved_witness = execute_program(backend, bytecode.clone(), &abi, &inputs_map)?; + let solved_witness = execute_program(backend, bytecode.clone(), &abi, &inputs_map, debug_data)?; // Write public inputs into Verifier.toml let public_abi = abi.public_abi(); diff --git a/crates/nargo_cli/src/cli/test_cmd.rs b/crates/nargo_cli/src/cli/test_cmd.rs index e129b38cac9..896ee6c4445 100644 --- a/crates/nargo_cli/src/cli/test_cmd.rs +++ b/crates/nargo_cli/src/cli/test_cmd.rs @@ -92,7 +92,7 @@ fn run_test( let mut program = compile_no_check(context, config, main) .map_err(|_| CliError::Generic(format!("Test '{test_name}' failed to compile")))?; // Note: We could perform this test using the unoptimized ACIR as generated by `compile_no_check`. - program.circuit = optimize_circuit(backend, program.circuit).unwrap(); + program.circuit = optimize_circuit(backend, program.circuit).unwrap().0; // Run the backend to ensure the PWG evaluates functions like std::hash::pedersen, // otherwise constraints involving these expressions will not error. diff --git a/crates/nargo_cli/src/cli/verify_cmd.rs b/crates/nargo_cli/src/cli/verify_cmd.rs index c962e9fd081..cc85159e488 100644 --- a/crates/nargo_cli/src/cli/verify_cmd.rs +++ b/crates/nargo_cli/src/cli/verify_cmd.rs @@ -83,11 +83,11 @@ fn verify_with_path>( (common_reference_string, program) } None => { - let program = compile_circuit(backend, program_dir.as_ref(), compile_options)?; + let (program, _) = compile_circuit(backend, program_dir.as_ref(), compile_options)?; let common_reference_string = update_common_reference_string(backend, &common_reference_string, &program.circuit) .map_err(CliError::CommonReferenceStringError)?; - let program = preprocess_program(backend, true, &common_reference_string, program) + let (program, _) = preprocess_program(backend, true, &common_reference_string, program) .map_err(CliError::ProofSystemCompilerError)?; (common_reference_string, program) } diff --git a/crates/noirc_driver/src/lib.rs b/crates/noirc_driver/src/lib.rs index 0976a6562db..771d62b97ef 100644 --- a/crates/noirc_driver/src/lib.rs +++ b/crates/noirc_driver/src/lib.rs @@ -312,11 +312,11 @@ pub fn compile_no_check( ) -> Result { let program = monomorphize(main_function, &context.def_interner); - let (circuit, abi) = if options.experimental_ssa { + let (circuit, debug, abi) = if options.experimental_ssa { experimental_create_circuit(program, options.show_ssa, options.show_output)? } else { create_circuit(program, options.show_ssa, options.show_output)? }; - Ok(CompiledProgram { circuit, abi }) + Ok(CompiledProgram { circuit, debug, abi }) } diff --git a/crates/noirc_driver/src/program.rs b/crates/noirc_driver/src/program.rs index 95405f36a5b..0e5adf3b39c 100644 --- a/crates/noirc_driver/src/program.rs +++ b/crates/noirc_driver/src/program.rs @@ -1,5 +1,6 @@ use acvm::acir::circuit::Circuit; +use noirc_errors::debug_info::DebugInfo; use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[derive(Debug, Serialize, Deserialize, Clone)] @@ -7,6 +8,7 @@ pub struct CompiledProgram { #[serde(serialize_with = "serialize_circuit", deserialize_with = "deserialize_circuit")] pub circuit: Circuit, pub abi: noirc_abi::Abi, + pub debug: DebugInfo, } pub(crate) fn serialize_circuit(circuit: &Circuit, s: S) -> Result diff --git a/crates/noirc_errors/Cargo.toml b/crates/noirc_errors/Cargo.toml index ed4c18fd0a2..8ab8420a166 100644 --- a/crates/noirc_errors/Cargo.toml +++ b/crates/noirc_errors/Cargo.toml @@ -11,3 +11,4 @@ codespan-reporting.workspace = true codespan.workspace = true fm.workspace = true chumsky.workspace = true +serde.workspace = true \ No newline at end of file diff --git a/crates/noirc_errors/src/debug_info.rs b/crates/noirc_errors/src/debug_info.rs new file mode 100644 index 00000000000..3217dba7a38 --- /dev/null +++ b/crates/noirc_errors/src/debug_info.rs @@ -0,0 +1,39 @@ +use std::collections::HashMap; + +use crate::Location; +use serde::{Deserialize, Serialize}; + +#[derive(Default, Debug, Clone, Deserialize, Serialize)] +pub struct DebugInfo { + /// Map opcode index of an ACIR circuit into the source code location + pub locations: HashMap, +} + +impl DebugInfo { + pub fn new(locations: HashMap) -> Self { + DebugInfo { locations } + } + + /// Updates the locations map when the circuit is modified + /// + /// When the circuit is generated, the indices are 0,1,..,n + /// When the circuit is modified, the opcodes are eventually + /// mixed, removed, or with new ones. For instance 5,2,6,n+1,0,12,.. + /// Since new opcodes (n+1 in the ex) don't have a location + /// we use the index of the old opcode that they replace. + /// This is the case during fallback or width 'optimization' + /// opcode_indices is this list of mixed indices + pub fn update_acir(&mut self, opcode_indices: Vec) { + let mut new_locations = HashMap::new(); + for (i, idx) in opcode_indices.iter().enumerate() { + if self.locations.contains_key(idx) { + new_locations.insert(i, self.locations[idx]); + } + } + self.locations = new_locations; + } + + pub fn opcode_location(&self, idx: usize) -> Option<&Location> { + self.locations.get(&idx) + } +} diff --git a/crates/noirc_errors/src/lib.rs b/crates/noirc_errors/src/lib.rs index 9112f56aece..43d5715fc54 100644 --- a/crates/noirc_errors/src/lib.rs +++ b/crates/noirc_errors/src/lib.rs @@ -3,6 +3,7 @@ #![warn(unreachable_pub)] #![warn(clippy::semicolon_if_nothing_returned)] +pub mod debug_info; mod position; pub mod reporter; pub use position::{Location, Position, Span, Spanned}; diff --git a/crates/noirc_errors/src/position.rs b/crates/noirc_errors/src/position.rs index 97f510d91bf..09c59da1980 100644 --- a/crates/noirc_errors/src/position.rs +++ b/crates/noirc_errors/src/position.rs @@ -1,5 +1,6 @@ use codespan::Span as ByteSpan; use fm::FileId; +use serde::{Deserialize, Serialize}; use std::{ hash::{Hash, Hasher}, ops::Range, @@ -50,7 +51,9 @@ impl std::borrow::Borrow for Spanned { } } -#[derive(PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Copy, Clone, Default)] +#[derive( + PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Copy, Clone, Default, Deserialize, Serialize, +)] pub struct Span(ByteSpan); impl Span { @@ -114,7 +117,7 @@ impl chumsky::Span for Span { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct Location { pub span: Span, pub file: FileId, diff --git a/crates/noirc_evaluator/src/lib.rs b/crates/noirc_evaluator/src/lib.rs index a77ded3e2de..420bfc1500e 100644 --- a/crates/noirc_evaluator/src/lib.rs +++ b/crates/noirc_evaluator/src/lib.rs @@ -16,9 +16,11 @@ use acvm::{ acir::circuit::{opcodes::Opcode as AcirOpcode, Circuit, PublicInputs}, acir::native_types::{Expression, Witness}, }; + use errors::{RuntimeError, RuntimeErrorKind}; use iter_extended::vecmap; use noirc_abi::{Abi, AbiType, AbiVisibility}; +use noirc_errors::debug_info::DebugInfo; use noirc_frontend::monomorphization::ast::*; use ssa::{node::ObjectType, ssa_gen::IrGenerator}; use std::collections::{BTreeMap, BTreeSet}; @@ -67,7 +69,7 @@ pub fn create_circuit( program: Program, enable_logging: bool, show_output: bool, -) -> Result<(Circuit, Abi), RuntimeError> { +) -> Result<(Circuit, DebugInfo, Abi), RuntimeError> { let mut evaluator = Evaluator::default(); // First evaluate the main function @@ -91,7 +93,7 @@ pub fn create_circuit( let (parameters, return_type) = program.main_function_signature; let abi = Abi { parameters, param_witnesses, return_type, return_witnesses: return_values }; - Ok((circuit, abi)) + Ok((circuit, DebugInfo::default(), abi)) } impl Evaluator { diff --git a/crates/noirc_evaluator/src/ssa_refactor.rs b/crates/noirc_evaluator/src/ssa_refactor.rs index a61eae4ca97..4c9dc848b0e 100644 --- a/crates/noirc_evaluator/src/ssa_refactor.rs +++ b/crates/noirc_evaluator/src/ssa_refactor.rs @@ -9,6 +9,9 @@ use crate::errors::RuntimeError; use acvm::acir::circuit::{Circuit, PublicInputs}; + +use noirc_errors::debug_info::DebugInfo; + use noirc_abi::Abi; use noirc_frontend::monomorphization::ast::Program; @@ -65,9 +68,9 @@ pub fn experimental_create_circuit( program: Program, enable_logging: bool, show_output: bool, -) -> Result<(Circuit, Abi), RuntimeError> { +) -> Result<(Circuit, DebugInfo, Abi), RuntimeError> { let func_sig = program.main_function_signature.clone(); - let GeneratedAcir { current_witness_index, opcodes, return_witnesses } = + let GeneratedAcir { current_witness_index, opcodes, return_witnesses, locations, .. } = optimize_into_acir(program, show_output, enable_logging); let abi = gen_abi(func_sig, return_witnesses.clone()); @@ -78,8 +81,9 @@ pub fn experimental_create_circuit( let return_values = PublicInputs(return_witnesses.into_iter().collect()); let circuit = Circuit { current_witness_index, opcodes, public_parameters, return_values }; + let debug_info = DebugInfo::new(locations); - Ok((circuit, abi)) + Ok((circuit, debug_info, abi)) } impl Ssa { diff --git a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/acir_variable.rs b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/acir_variable.rs index 397e61f685d..8f713575075 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/acir_variable.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/acir_variable.rs @@ -17,6 +17,7 @@ use acvm::{ FieldElement, }; use iter_extended::vecmap; +use noirc_errors::Location; use std::collections::HashMap; use std::{borrow::Cow, hash::Hash}; @@ -123,6 +124,10 @@ impl AcirContext { self.add_data(var_data) } + pub(crate) fn set_location(&mut self, location: Option) { + self.acir_ir.current_location = location; + } + /// True if the given AcirVar refers to a constant one value pub(crate) fn is_constant_one(&self, var: &AcirVar) -> bool { match self.vars[var] { diff --git a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/generated_acir.rs b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/generated_acir.rs index 597108f98cb..90aa1e11f6b 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/generated_acir.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/generated_acir.rs @@ -1,5 +1,7 @@ //! `GeneratedAcir` is constructed as part of the `acir_gen` pass to accumulate all of the ACIR //! program as it is being converted from SSA form. +use std::collections::HashMap; + use crate::brillig::brillig_gen::brillig_directive; use super::errors::AcirGenError; @@ -18,6 +20,7 @@ use acvm::{ FieldElement, }; use iter_extended::vecmap; +use noirc_errors::Location; use num_bigint::BigUint; #[derive(Debug, Default)] @@ -36,6 +39,13 @@ pub(crate) struct GeneratedAcir { /// Note: This may contain repeated indices, which is necessary for later mapping into the /// abi's return type. pub(crate) return_witnesses: Vec, + + /// Correspondance between an opcode index (in opcodes) and the source code location which generated it + pub(crate) locations: HashMap, + + /// Source code location of the current instruction being processed + /// None if we do not know the location + pub(crate) current_location: Option, } impl GeneratedAcir { @@ -47,6 +57,9 @@ impl GeneratedAcir { /// Adds a new opcode into ACIR. fn push_opcode(&mut self, opcode: AcirOpcode) { self.opcodes.push(opcode); + if let Some(location) = self.current_location { + self.locations.insert(self.opcodes.len() - 1, location); + } } /// Updates the witness index counter and returns diff --git a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs index 975ba9ea4fc..f743d975f91 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs @@ -236,7 +236,7 @@ impl Context { allow_log_ops: bool, ) { let instruction = &dfg[instruction_id]; - + self.acir_context.set_location(dfg.get_location(&instruction_id)); match instruction { Instruction::Binary(binary) => { let result_acir_var = self @@ -343,6 +343,7 @@ impl Context { unreachable!("Expected all load instructions to be removed before acir_gen") } } + self.acir_context.set_location(None); } fn gen_brillig_for(&self, func: &Function, brillig: &Brillig) -> Vec { diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index 78d55496cc4..5c9fde280a8 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -15,6 +15,7 @@ use super::{ use acvm::FieldElement; use iter_extended::vecmap; +use noirc_errors::Location; /// The DataFlowGraph contains most of the actual data in a function including /// its blocks, instructions, and values. This struct is largely responsible for @@ -69,6 +70,12 @@ pub(crate) struct DataFlowGraph { /// for that of another. This information is purely used for printing the SSA, and has no /// material effect on the SSA itself. replaced_value_ids: HashMap, + + /// Source location of each instruction for debugging and issuing errors. + /// + /// Instructions inserted by internal SSA passes that don't correspond to user code + /// may not have a corresponding location. + locations: HashMap, } impl DataFlowGraph { @@ -142,6 +149,7 @@ impl DataFlowGraph { instruction: Instruction, block: BasicBlockId, ctrl_typevars: Option>, + location: Option, ) -> InsertInstructionResult { use InsertInstructionResult::*; match instruction.simplify(self, block) { @@ -153,6 +161,9 @@ impl DataFlowGraph { SimplifyResult::None => { let id = self.make_instruction(instruction, ctrl_typevars); self.blocks[block].insert_instruction(id); + if let Some(location) = location { + self.locations.insert(id, location); + } InsertInstructionResult::Results(self.instruction_results(id)) } } @@ -405,6 +416,17 @@ impl DataFlowGraph { destination.instructions_mut().append(&mut instructions); destination.set_terminator(terminator); } + + pub(crate) fn get_location(&self, id: &InstructionId) -> Option { + self.locations.get(id).cloned() + } + + pub(crate) fn get_value_location(&self, id: &ValueId) -> Option { + match &self.values[*id] { + Value::Instruction { instruction, .. } => self.get_location(instruction), + _ => None, + } + } } impl std::ops::Index for DataFlowGraph { diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/function_inserter.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/function_inserter.rs index c5f662b5da4..22a1399ae79 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/function_inserter.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/function_inserter.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use iter_extended::vecmap; +use noirc_errors::Location; use super::{ basic_block::BasicBlockId, @@ -55,13 +56,16 @@ impl<'f> FunctionInserter<'f> { self.values.insert(key, value); } - pub(crate) fn map_instruction(&mut self, id: InstructionId) -> Instruction { - self.function.dfg[id].clone().map_values(|id| self.resolve(id)) + pub(crate) fn map_instruction(&mut self, id: InstructionId) -> (Instruction, Option) { + ( + self.function.dfg[id].clone().map_values(|id| self.resolve(id)), + self.function.dfg.get_location(&id), + ) } pub(crate) fn push_instruction(&mut self, id: InstructionId, block: BasicBlockId) { - let instruction = self.map_instruction(id); - self.push_instruction_value(instruction, id, block); + let (instruction, location) = self.map_instruction(id); + self.push_instruction_value(instruction, id, block, location); } pub(crate) fn push_instruction_value( @@ -69,6 +73,7 @@ impl<'f> FunctionInserter<'f> { instruction: Instruction, id: InstructionId, block: BasicBlockId, + location: Option, ) -> InsertInstructionResult { let results = self.function.dfg.instruction_results(id); let results = vecmap(results, |id| self.function.dfg.resolve(*id)); @@ -77,8 +82,12 @@ impl<'f> FunctionInserter<'f> { .requires_ctrl_typevars() .then(|| vecmap(&results, |result| self.function.dfg.type_of_value(*result))); - let new_results = - self.function.dfg.insert_instruction_and_results(instruction, block, ctrl_typevars); + let new_results = self.function.dfg.insert_instruction_and_results( + instruction, + block, + ctrl_typevars, + location, + ); Self::insert_new_instruction_results(&mut self.values, &results, &new_results); new_results diff --git a/crates/noirc_evaluator/src/ssa_refactor/opt/constant_folding.rs b/crates/noirc_evaluator/src/ssa_refactor/opt/constant_folding.rs index 9c2904926ff..3c40e2a15c5 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/opt/constant_folding.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/opt/constant_folding.rs @@ -71,13 +71,18 @@ impl Context { .requires_ctrl_typevars() .then(|| vecmap(&old_results, |result| function.dfg.type_of_value(*result))); - let new_results = - match function.dfg.insert_instruction_and_results(instruction, block, ctrl_typevars) { - InsertInstructionResult::SimplifiedTo(new_result) => vec![new_result], - InsertInstructionResult::SimplifiedToMultiple(new_results) => new_results, - InsertInstructionResult::Results(new_results) => new_results.to_vec(), - InsertInstructionResult::InstructionRemoved => vec![], - }; + let location = function.dfg.get_location(&id); + let new_results = match function.dfg.insert_instruction_and_results( + instruction, + block, + ctrl_typevars, + location, + ) { + InsertInstructionResult::SimplifiedTo(new_result) => vec![new_result], + InsertInstructionResult::SimplifiedToMultiple(new_results) => new_results, + InsertInstructionResult::Results(new_results) => new_results.to_vec(), + InsertInstructionResult::InstructionRemoved => vec![], + }; assert_eq!(old_results.len(), new_results.len()); for (old_result, new_result) in old_results.iter().zip(new_results) { function.dfg.set_value_from_id(*old_result, new_result); diff --git a/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs index 5aa915f0f3e..3b6512a3c45 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs @@ -138,6 +138,7 @@ use std::{ use acvm::FieldElement; use iter_extended::vecmap; +use noirc_errors::Location; use crate::ssa_refactor::{ ir::{ @@ -263,7 +264,8 @@ impl<'f> Context<'f> { let then_branch = self.inline_branch(block, then_block, old_condition, then_condition, one); - let else_condition = self.insert_instruction(Instruction::Not(then_condition)); + let else_condition = + self.insert_instruction(Instruction::Not(then_condition), None); let zero = FieldElement::zero(); // Make sure the else branch sees the previous values of each store @@ -317,7 +319,7 @@ impl<'f> Context<'f> { if let Some((_, previous_condition)) = self.conditions.last() { let and = Instruction::binary(BinaryOp::And, *previous_condition, condition); - let new_condition = self.insert_instruction(and); + let new_condition = self.insert_instruction(and, None); self.conditions.push((end_block, new_condition)); } else { self.conditions.push((end_block, condition)); @@ -327,9 +329,17 @@ impl<'f> Context<'f> { /// Insert a new instruction into the function's entry block. /// Unlike push_instruction, this function will not map any ValueIds. /// within the given instruction, nor will it modify self.values in any way. - fn insert_instruction(&mut self, instruction: Instruction) -> ValueId { + fn insert_instruction( + &mut self, + instruction: Instruction, + location: Option, + ) -> ValueId { let block = self.inserter.function.entry_block(); - self.inserter.function.dfg.insert_instruction_and_results(instruction, block, None).first() + self.inserter + .function + .dfg + .insert_instruction_and_results(instruction, block, None, location) + .first() } /// Inserts a new instruction into the function's entry block, using the given @@ -342,7 +352,12 @@ impl<'f> Context<'f> { ctrl_typevars: Option>, ) -> InsertInstructionResult { let block = self.inserter.function.entry_block(); - self.inserter.function.dfg.insert_instruction_and_results(instruction, block, ctrl_typevars) + self.inserter.function.dfg.insert_instruction_and_results( + instruction, + block, + ctrl_typevars, + None, + ) } /// Checks the branch condition on the top of the stack and uses it to build and insert an @@ -453,20 +468,38 @@ impl<'f> Context<'f> { "Expected values merged to be of the same type but found {then_type} and {else_type}" ); + let then_location = self.inserter.function.dfg.get_value_location(&then_value); + let else_location = self.inserter.function.dfg.get_value_location(&else_value); + let merge_location = then_location.or(else_location); + // We must cast the bool conditions to the actual numeric type used by each value. - let then_condition = self.insert_instruction(Instruction::Cast(then_condition, then_type)); - let else_condition = self.insert_instruction(Instruction::Cast(else_condition, else_type)); + let then_condition = + self.insert_instruction(Instruction::Cast(then_condition, then_type), then_location); + let else_condition = + self.insert_instruction(Instruction::Cast(else_condition, else_type), else_location); let mul = Instruction::binary(BinaryOp::Mul, then_condition, then_value); - let then_value = - self.inserter.function.dfg.insert_instruction_and_results(mul, block, None).first(); + let then_value = self + .inserter + .function + .dfg + .insert_instruction_and_results(mul, block, None, merge_location) + .first(); let mul = Instruction::binary(BinaryOp::Mul, else_condition, else_value); - let else_value = - self.inserter.function.dfg.insert_instruction_and_results(mul, block, None).first(); + let else_value = self + .inserter + .function + .dfg + .insert_instruction_and_results(mul, block, None, merge_location) + .first(); let add = Instruction::binary(BinaryOp::Add, then_value, else_value); - self.inserter.function.dfg.insert_instruction_and_results(add, block, None).first() + self.inserter + .function + .dfg + .insert_instruction_and_results(add, block, None, merge_location) + .first() } /// Inline one branch of a jmpif instruction. @@ -651,12 +684,12 @@ impl<'f> Context<'f> { /// with a different InstructionId from the original. The results of the given instruction /// will also be mapped to the results of the new instruction. fn push_instruction(&mut self, id: InstructionId) { - let instruction = self.inserter.map_instruction(id); - let instruction = self.handle_instruction_side_effects(instruction); + let (instruction, location) = self.inserter.map_instruction(id); + let instruction = self.handle_instruction_side_effects(instruction, location); let is_allocate = matches!(instruction, Instruction::Allocate); let entry = self.inserter.function.entry_block(); - let results = self.inserter.push_instruction_value(instruction, id, entry); + let results = self.inserter.push_instruction_value(instruction, id, entry, location); // Remember an allocate was created local to this branch so that we do not try to merge store // values across branches for it later. @@ -667,17 +700,22 @@ impl<'f> Context<'f> { /// If we are currently in a branch, we need to modify constrain instructions /// to multiply them by the branch's condition (see optimization #1 in the module comment). - fn handle_instruction_side_effects(&mut self, instruction: Instruction) -> Instruction { + fn handle_instruction_side_effects( + &mut self, + instruction: Instruction, + location: Option, + ) -> Instruction { if let Some((_, condition)) = self.conditions.last().copied() { match instruction { Instruction::Constrain(value) => { - let mul = self.insert_instruction(Instruction::binary( - BinaryOp::Mul, - value, - condition, - )); - let eq = - self.insert_instruction(Instruction::binary(BinaryOp::Eq, mul, condition)); + let mul = self.insert_instruction( + Instruction::binary(BinaryOp::Mul, value, condition), + location, + ); + let eq = self.insert_instruction( + Instruction::binary(BinaryOp::Eq, mul, condition), + location, + ); Instruction::Constrain(eq) } Instruction::Store { address, value } => { diff --git a/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs b/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs index c8c37df75c5..430b52ce9f6 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs @@ -379,6 +379,7 @@ impl<'function> PerFunctionContext<'function> { /// function being inlined into. fn push_instruction(&mut self, id: InstructionId) { let instruction = self.source_function.dfg[id].map_values(|id| self.translate_value(id)); + let location = self.source_function.dfg.get_location(&id); let results = self.source_function.dfg.instruction_results(id); let results = vecmap(results, |id| self.source_function.dfg.resolve(*id)); @@ -386,6 +387,9 @@ impl<'function> PerFunctionContext<'function> { .requires_ctrl_typevars() .then(|| vecmap(&results, |result| self.source_function.dfg.type_of_value(*result))); + if let Some(location) = location { + self.context.builder.set_location(location); + } let new_results = self.context.builder.insert_instruction(instruction, ctrl_typevars); Self::insert_new_instruction_results(&mut self.values, &results, new_results); } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs index 6609252f042..d3d9e56b3af 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs @@ -1,6 +1,7 @@ use std::{borrow::Cow, rc::Rc}; use acvm::FieldElement; +use noirc_errors::Location; use crate::ssa_refactor::ir::{ basic_block::BasicBlockId, @@ -32,6 +33,7 @@ pub(crate) struct FunctionBuilder { pub(super) current_function: Function, current_block: BasicBlockId, finished_functions: Vec, + current_location: Option, } impl FunctionBuilder { @@ -48,7 +50,12 @@ impl FunctionBuilder { new_function.set_runtime(runtime); let current_block = new_function.entry_block(); - Self { current_function: new_function, current_block, finished_functions: Vec::new() } + Self { + current_function: new_function, + current_block, + finished_functions: Vec::new(), + current_location: None, + } } /// Finish the current function and create a new function. @@ -148,6 +155,7 @@ impl FunctionBuilder { instruction, self.current_block, ctrl_typevars, + self.current_location, ) } @@ -170,6 +178,11 @@ impl FunctionBuilder { self.insert_instruction(Instruction::Allocate, None).first() } + pub(crate) fn set_location(&mut self, location: Location) -> &mut FunctionBuilder { + self.current_location = Some(location); + self + } + /// Insert a Load instruction at the end of the current block, loading from the given offset /// of the given address which should point to a previous Allocate instruction. Note that /// this is limited to loading a single value. Loading multiple values (such as a tuple) diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs index 9ce34cb1e6b..07ee32998d2 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -4,6 +4,7 @@ use std::sync::{Mutex, RwLock}; use acvm::FieldElement; use iter_extended::vecmap; +use noirc_errors::Location; use noirc_frontend::monomorphization::ast::{self, LocalId, Parameters}; use noirc_frontend::monomorphization::ast::{FuncId, Program}; use noirc_frontend::Signedness; @@ -232,18 +233,19 @@ impl<'a> FunctionContext<'a> { mut lhs: ValueId, operator: noirc_frontend::BinaryOpKind, mut rhs: ValueId, + location: Location, ) -> Values { let op = convert_operator(operator); if op == BinaryOp::Eq && matches!(self.builder.type_of_value(lhs), Type::Array(..)) { - return self.insert_array_equality(lhs, operator, rhs); + return self.insert_array_equality(lhs, operator, rhs, location); } if operator_requires_swapped_operands(operator) { std::mem::swap(&mut lhs, &mut rhs); } - let mut result = self.builder.insert_binary(lhs, op, rhs); + let mut result = self.builder.set_location(location).insert_binary(lhs, op, rhs); if let Some(max_bit_size) = operator_result_max_bit_size_to_truncate( operator, @@ -295,6 +297,7 @@ impl<'a> FunctionContext<'a> { lhs: ValueId, operator: noirc_frontend::BinaryOpKind, rhs: ValueId, + location: Location, ) -> Values { let lhs_type = self.builder.type_of_value(lhs); let rhs_type = self.builder.type_of_value(rhs); @@ -320,7 +323,7 @@ impl<'a> FunctionContext<'a> { let loop_end = self.builder.insert_block(); // pre-loop - let result_alloc = self.builder.insert_allocate(); + let result_alloc = self.builder.set_location(location).insert_allocate(); let true_value = self.builder.numeric_constant(1u128, Type::bool()); self.builder.insert_store(result_alloc, true_value); let zero = self.builder.field_constant(0u128); @@ -365,9 +368,11 @@ impl<'a> FunctionContext<'a> { function: ValueId, arguments: Vec, result_type: &ast::Type, + location: Location, ) -> Values { let result_types = Self::convert_type(result_type).flatten(); - let results = self.builder.insert_call(function, arguments, result_types); + let results = + self.builder.set_location(location).insert_call(function, arguments, result_types); let mut i = 0; let reshaped_return_values = Self::map_type(result_type, |_| { @@ -526,9 +531,9 @@ impl<'a> FunctionContext<'a> { let variable = self.ident_lvalue(ident); (variable.clone(), LValue::Ident(variable)) } - ast::LValue::Index { array, index, element_type, location: _ } => { + ast::LValue::Index { array, index, element_type, location } => { let (old_array, index, index_lvalue) = self.index_lvalue(array, index); - let element = self.codegen_array_index(old_array, index, element_type); + let element = self.codegen_array_index(old_array, index, element_type, *location); (element, index_lvalue) } ast::LValue::MemberAccess { object, field_index: index } => { diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs index ac89575ecea..13e67f26cc5 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -198,13 +198,13 @@ impl<'a> FunctionContext<'a> { fn codegen_binary(&mut self, binary: &ast::Binary) -> Values { let lhs = self.codegen_non_tuple_expression(&binary.lhs); let rhs = self.codegen_non_tuple_expression(&binary.rhs); - self.insert_binary(lhs, binary.operator, rhs) + self.insert_binary(lhs, binary.operator, rhs, binary.location) } fn codegen_index(&mut self, index: &ast::Index) -> Values { let array = self.codegen_non_tuple_expression(&index.collection); let index_value = self.codegen_non_tuple_expression(&index.index); - self.codegen_array_index(array, index_value, &index.element_type) + self.codegen_array_index(array, index_value, &index.element_type, index.location) } /// This is broken off from codegen_index so that it can also be @@ -218,11 +218,13 @@ impl<'a> FunctionContext<'a> { array: super::ir::value::ValueId, index: super::ir::value::ValueId, element_type: &ast::Type, + location: Location, ) -> Values { // base_index = index * type_size let type_size = Self::convert_type(element_type).size_of_type(); let type_size = self.builder.field_constant(type_size as u128); - let base_index = self.builder.insert_binary(index, BinaryOp::Mul, type_size); + let base_index = + self.builder.set_location(location).insert_binary(index, BinaryOp::Mul, type_size); let mut field_index = 0u128; Self::map_type(element_type, |typ| { @@ -369,7 +371,7 @@ impl<'a> FunctionContext<'a> { .flat_map(|argument| self.codegen_expression(argument).into_value_list(self)) .collect(); - self.insert_call(function, arguments, &call.return_type) + self.insert_call(function, arguments, &call.return_type, call.location) } /// Generate SSA for the given variable. @@ -390,9 +392,9 @@ impl<'a> FunctionContext<'a> { Self::unit_value() } - fn codegen_constrain(&mut self, expr: &Expression, _location: Location) -> Values { + fn codegen_constrain(&mut self, expr: &Expression, location: Location) -> Values { let boolean = self.codegen_non_tuple_expression(expr); - self.builder.insert_constrain(boolean); + self.builder.set_location(location).insert_constrain(boolean); Self::unit_value() }