From a75431915837af9a95d5df726ac809ea83e65c90 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Sun, 16 Jan 2022 08:25:07 +0200 Subject: [PATCH 1/2] Implement initial support for source mapping --- Cargo.lock | 1 + forc/src/cli/commands/build.rs | 3 + forc/src/cli/commands/deploy.rs | 3 + forc/src/cli/commands/run.rs | 4 ++ forc/src/ops/forc_build.rs | 29 +++++++-- forc/src/ops/forc_deploy.rs | 2 + forc/src/ops/forc_fmt.rs | 1 + forc/src/ops/forc_run.rs | 1 + sway-core/Cargo.toml | 1 + sway-core/src/asm_generation/finalized_asm.rs | 15 +++-- sway-core/src/lib.rs | 5 +- sway-core/src/source_map.rs | 60 +++++++++++++++++++ test/src/e2e_vm_tests/harness.rs | 3 + 13 files changed, 119 insertions(+), 9 deletions(-) create mode 100755 sway-core/src/source_map.rs diff --git a/Cargo.lock b/Cargo.lock index e2405ac8d61..7e20f8ca214 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2815,6 +2815,7 @@ dependencies = [ "lazy_static", "petgraph", "prettydiff 0.5.1", + "serde", "sha2", "smallvec", "structopt 0.3.26", diff --git a/forc/src/cli/commands/build.rs b/forc/src/cli/commands/build.rs index 814d754aac8..46906c8ecbd 100644 --- a/forc/src/cli/commands/build.rs +++ b/forc/src/cli/commands/build.rs @@ -22,6 +22,9 @@ pub struct Command { /// If set, outputs a binary file representing the script bytes. #[structopt(short = "o")] pub binary_outfile: Option, + /// If set, outputs source file mapping in JSON format + #[structopt(short = "g", long)] + pub debug_outfile: Option, /// Offline mode, prevents Forc from using the network when managing dependencies. /// Meaning it will only try to use previously downloaded dependencies. #[structopt(long = "offline")] diff --git a/forc/src/cli/commands/deploy.rs b/forc/src/cli/commands/deploy.rs index b16d7e936ff..57e4d64c7c2 100644 --- a/forc/src/cli/commands/deploy.rs +++ b/forc/src/cli/commands/deploy.rs @@ -23,6 +23,9 @@ pub struct Command { /// If set, outputs a binary file representing the script bytes. #[structopt(short = "o")] pub binary_outfile: Option, + /// If set, outputs source file mapping in JSON format + #[structopt(short = "g", long)] + pub debug_outfile: Option, /// Offline mode, prevents Forc from using the network when managing dependencies. /// Meaning it will only try to use previously downloaded dependencies. #[structopt(long = "offline")] diff --git a/forc/src/cli/commands/run.rs b/forc/src/cli/commands/run.rs index 3b7ccf9865c..59831c98409 100644 --- a/forc/src/cli/commands/run.rs +++ b/forc/src/cli/commands/run.rs @@ -46,6 +46,10 @@ pub struct Command { #[structopt(short = "o")] pub binary_outfile: Option, + /// If set, outputs source file mapping in JSON format + #[structopt(short = "g", long)] + pub debug_outfile: Option, + /// Silent mode. Don't output any warnings or errors to the command line. #[structopt(long = "silent", short = "s")] pub silent_mode: bool, diff --git a/forc/src/ops/forc_build.rs b/forc/src/ops/forc_build.rs index 6a85e917258..789e76547a0 100644 --- a/forc/src/ops/forc_build.rs +++ b/forc/src/ops/forc_build.rs @@ -7,15 +7,15 @@ use crate::{ get_main_file, print_on_failure, print_on_success, print_on_success_library, read_manifest, }, }; -use std::fs::File; +use std::fs::{self, File}; use std::io::Write; use std::sync::Arc; use sway_core::{FinalizedAsm, TreeType}; use sway_utils::{constants, find_manifest_dir}; use sway_core::{ - create_module, BuildConfig, BytecodeCompilationResult, CompilationResult, NamespaceRef, - NamespaceWrapper, + create_module, source_map::SourceMap, BuildConfig, BytecodeCompilationResult, + CompilationResult, NamespaceRef, NamespaceWrapper, }; use anyhow::Result; @@ -33,6 +33,7 @@ pub fn build(command: BuildCommand) -> Result, String> { let BuildCommand { binary_outfile, use_ir, + debug_outfile, print_finalized_asm, print_intermediate_asm, print_ir, @@ -94,19 +95,31 @@ pub fn build(command: BuildCommand) -> Result, String> { // now, compile this program with all of its dependencies let main_file = get_main_file(&manifest, &manifest_dir)?; + let mut source_map = SourceMap::new(); + let main = compile( main_file, &manifest.project.name, namespace, build_config, &mut dependency_graph, + &mut source_map, silent_mode, )?; + if let Some(outfile) = binary_outfile { let mut file = File::create(outfile).map_err(|e| e.to_string())?; file.write_all(main.as_slice()).map_err(|e| e.to_string())?; } + if let Some(outfile) = debug_outfile { + fs::write( + outfile, + &serde_json::to_vec(&source_map).expect("JSON seralizatio failed"), + ) + .map_err(|e| e.to_string())?; + } + println!(" Bytecode size is {} bytes.", main.len()); Ok(main) @@ -269,9 +282,17 @@ fn compile( namespace: NamespaceRef, build_config: BuildConfig, dependency_graph: &mut HashMap>, + source_map: &mut SourceMap, silent_mode: bool, ) -> Result, String> { - let res = sway_core::compile_to_bytecode(source, namespace, build_config, dependency_graph); + let res = sway_core::compile_to_bytecode( + source, + namespace, + build_config, + dependency_graph, + source_map, + ); + match res { BytecodeCompilationResult::Success { bytes, warnings } => { print_on_success(silent_mode, proj_name, warnings, TreeType::Script {}); diff --git a/forc/src/ops/forc_deploy.rs b/forc/src/ops/forc_deploy.rs index 5b4cbec81ad..ae99a065fad 100644 --- a/forc/src/ops/forc_deploy.rs +++ b/forc/src/ops/forc_deploy.rs @@ -26,6 +26,7 @@ pub async fn deploy(command: DeployCommand) -> Result Result Result<(), FormatError> { print_intermediate_asm: false, print_ir: false, binary_outfile: None, + debug_outfile: None, offline_mode: false, silent_mode: false, }; diff --git a/forc/src/ops/forc_run.rs b/forc/src/ops/forc_run.rs index 27003d26f08..55f569c8dcb 100644 --- a/forc/src/ops/forc_run.rs +++ b/forc/src/ops/forc_run.rs @@ -45,6 +45,7 @@ pub async fn run(command: RunCommand) -> Result<(), CliError> { print_intermediate_asm: command.print_intermediate_asm, print_ir: command.print_ir, binary_outfile: command.binary_outfile, + debug_outfile: command.debug_outfile, offline_mode: false, silent_mode: command.silent_mode, }; diff --git a/sway-core/Cargo.toml b/sway-core/Cargo.toml index e0ad0cc8b6b..218f0661002 100644 --- a/sway-core/Cargo.toml +++ b/sway-core/Cargo.toml @@ -22,6 +22,7 @@ pest = { version = "3.0.4", package = "fuel-pest" } pest_derive = { version = "3.0.4", package = "fuel-pest_derive" } petgraph = "0.5" prettydiff = "0.5" +serde = { version = "1.0", features = ["derive"] } sha2 = "0.9" smallvec = "1.7" structopt = { version = "0.3", default-features = false, optional = true } diff --git a/sway-core/src/asm_generation/finalized_asm.rs b/sway-core/src/asm_generation/finalized_asm.rs index 3b6d72129ff..e152fb3a21a 100644 --- a/sway-core/src/asm_generation/finalized_asm.rs +++ b/sway-core/src/asm_generation/finalized_asm.rs @@ -1,6 +1,8 @@ use super::{DataSection, InstructionSet}; use crate::asm_lang::allocated_ops::AllocatedOpcode; use crate::error::*; +use crate::source_map::SourceMap; + use sway_types::span::Span; use either::Either; @@ -26,23 +28,23 @@ pub enum FinalizedAsm { Library, } impl FinalizedAsm { - pub(crate) fn to_bytecode_mut(&mut self) -> CompileResult> { + pub(crate) fn to_bytecode_mut(&mut self, source_map: &mut SourceMap) -> CompileResult> { use FinalizedAsm::*; match self { ContractAbi { program_section, ref mut data_section, - } => to_bytecode_mut(program_section, data_section), + } => to_bytecode_mut(program_section, data_section, source_map), // libraries are not compiled to asm Library => ok(vec![], vec![], vec![]), ScriptMain { program_section, ref mut data_section, - } => to_bytecode_mut(program_section, data_section), + } => to_bytecode_mut(program_section, data_section, source_map), PredicateMain { program_section, ref mut data_section, - } => to_bytecode_mut(program_section, data_section), + } => to_bytecode_mut(program_section, data_section, source_map), } } } @@ -50,6 +52,7 @@ impl FinalizedAsm { fn to_bytecode_mut( program_section: &InstructionSet, data_section: &mut DataSection, + source_map: &mut SourceMap, ) -> CompileResult> { let mut errors = vec![]; if program_section.ops.len() & 1 != 0 { @@ -92,6 +95,7 @@ fn to_bytecode_mut( let mut half_word_ix = 0; for op in program_section.ops.iter() { + let span = op.owning_span.clone(); let op = op.to_fuel_asm(offset_to_data_section_in_bytes, data_section); match op { Either::Right(data) => { @@ -105,6 +109,9 @@ fn to_bytecode_mut( buf.resize(buf.len() + ((ops.len() - 1) * 4), 0); } for mut op in ops { + if let Some(span) = &span { + source_map.insert(half_word_ix, span); + } op.read_exact(&mut buf[half_word_ix * 4..]) .expect("Failed to write to in-memory buffer."); half_word_ix += 1; diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index 2faf2530b4d..c67990cd484 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -13,6 +13,7 @@ mod optimize; pub mod parse_tree; mod parser; pub mod semantic_analysis; +pub mod source_map; mod style; pub mod type_engine; @@ -20,6 +21,7 @@ pub use crate::parser::{Rule, SwayParser}; use crate::{ asm_generation::{checks, compile_ast_to_asm}, error::*, + source_map::SourceMap, }; pub use asm_generation::{AbstractInstructionSet, FinalizedAsm, SwayAsmSet}; pub use build_config::BuildConfig; @@ -506,13 +508,14 @@ pub fn compile_to_bytecode( initial_namespace: crate::semantic_analysis::NamespaceRef, build_config: BuildConfig, dependency_graph: &mut HashMap>, + source_map: &mut SourceMap, ) -> BytecodeCompilationResult { match compile_to_asm(input, initial_namespace, build_config, dependency_graph) { CompilationResult::Success { mut asm, mut warnings, } => { - let mut asm_res = asm.to_bytecode_mut(); + let mut asm_res = asm.to_bytecode_mut(source_map); warnings.append(&mut asm_res.warnings); if asm_res.value.is_none() || !asm_res.errors.is_empty() { BytecodeCompilationResult::Failure { diff --git a/sway-core/src/source_map.rs b/sway-core/src/source_map.rs new file mode 100755 index 00000000000..783ec09b9b3 --- /dev/null +++ b/sway-core/src/source_map.rs @@ -0,0 +1,60 @@ +use std::collections::HashMap; +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +use sway_types::span::Span; + +/// Index of an interned path string +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[serde(transparent)] +pub struct PathIndex(usize); + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SourceMap { + paths: Vec, + map: HashMap, +} +impl SourceMap { + pub fn new() -> Self { + Self { + paths: Vec::new(), + map: HashMap::new(), + } + } + + pub fn insert(&mut self, pc: usize, span: &Span) { + if let Some(path) = span.path.as_ref() { + let path_index = self + .paths + .iter() + .position(|p| *p == **path) + .unwrap_or_else(|| { + self.paths.push((**path).to_owned()); + self.paths.len() - 1 + }); + self.map.insert( + pc, + SourceMapSpan { + path: PathIndex(path_index), + range: LocationRange { + start: span.start(), + end: span.end(), + }, + }, + ); + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SourceMapSpan { + pub path: PathIndex, + pub range: LocationRange, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LocationRange { + pub start: usize, + pub end: usize, +} diff --git a/test/src/e2e_vm_tests/harness.rs b/test/src/e2e_vm_tests/harness.rs index e5206eb80c8..93c20bf0456 100644 --- a/test/src/e2e_vm_tests/harness.rs +++ b/test/src/e2e_vm_tests/harness.rs @@ -26,6 +26,7 @@ pub(crate) fn deploy_contract(file_name: &str) -> ContractId { print_intermediate_asm: false, print_ir: false, binary_outfile: None, + debug_outfile: None, offline_mode: false, silent_mode: true, })) @@ -54,6 +55,7 @@ pub(crate) fn runs_on_node(file_name: &str, contract_ids: &[fuel_tx::ContractId] kill_node: false, use_ir: false, binary_outfile: None, + debug_outfile: None, print_finalized_asm: false, print_intermediate_asm: false, print_ir: false, @@ -121,6 +123,7 @@ pub(crate) fn compile_to_bytes(file_name: &str) -> Result, String> { print_intermediate_asm: false, print_ir: false, binary_outfile: None, + debug_outfile: None, offline_mode: false, silent_mode: true, }) From d663c269604ceee78051d01521e702d6bf592670 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Mon, 17 Jan 2022 15:44:19 +0200 Subject: [PATCH 2/2] Impl Default for SourceMap --- sway-core/src/source_map.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sway-core/src/source_map.rs b/sway-core/src/source_map.rs index 783ec09b9b3..f1577c67b0e 100755 --- a/sway-core/src/source_map.rs +++ b/sway-core/src/source_map.rs @@ -10,17 +10,14 @@ use sway_types::span::Span; #[serde(transparent)] pub struct PathIndex(usize); -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct SourceMap { paths: Vec, map: HashMap, } impl SourceMap { pub fn new() -> Self { - Self { - paths: Vec::new(), - map: HashMap::new(), - } + Self::default() } pub fn insert(&mut self, pc: usize, span: &Span) {