From f246d97d3b930febd7752349bdd7e8a8283d1745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Matos?= Date: Tue, 27 Jun 2023 10:41:49 +0100 Subject: [PATCH] Unify AST compilation code between core and forc. (#4670) ## Description This unifies the AST compilation step between forc and core, so both are running througn the same code path. Working on this as a prerequisite towards AST caching. ## Checklist - [ ] I have linked to any relevant issues. - [ ] I have commented my code, particularly in hard-to-understand areas. - [ ] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [ ] I have added tests that prove my fix is effective or that my feature works. - [ ] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [ ] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [ ] I have requested a review from the relevant team or maintainers. Co-authored-by: Joshua Batty --- forc-pkg/src/pkg.rs | 177 ++++++++++------------------- sway-core/src/language/mod.rs | 2 + sway-core/src/language/programs.rs | 18 +++ sway-core/src/lib.rs | 58 +++++----- sway-lsp/src/core/session.rs | 4 +- test/src/ir_generation/mod.rs | 16 +-- 6 files changed, 123 insertions(+), 152 deletions(-) create mode 100644 sway-core/src/language/programs.rs diff --git a/forc-pkg/src/pkg.rs b/forc-pkg/src/pkg.rs index 1d2f78ab58a..4bf3385a44d 100644 --- a/forc-pkg/src/pkg.rs +++ b/forc-pkg/src/pkg.rs @@ -26,6 +26,7 @@ use std::{ sync::Arc, }; use sway_core::fuel_prelude::fuel_tx::ConsensusParameters; +pub use sway_core::Programs; use sway_core::{ abi_generation::{ evm_json_abi, @@ -37,11 +38,7 @@ use sway_core::{ fuel_crypto, fuel_tx::{self, Contract, ContractId, StorageSlot}, }, - language::{ - lexed::LexedProgram, - parsed::{ParseProgram, TreeType}, - ty, Visibility, - }, + language::{parsed::TreeType, Visibility}, semantic_analysis::namespace, source_map::SourceMap, transform::AttributeKind, @@ -322,13 +319,6 @@ pub struct MemberFilter { pub build_libraries: bool, } -/// Contains the lexed, parsed, and typed compilation stages of a program. -pub struct Programs { - pub lexed: LexedProgram, - pub parsed: ParseProgram, - pub typed: Option, -} - impl Default for MemberFilter { fn default() -> Self { Self { @@ -826,20 +816,6 @@ impl BuildPlan { } } -impl Programs { - pub fn new( - lexed: LexedProgram, - parsed: ParseProgram, - typed: Option, - ) -> Programs { - Programs { - lexed, - parsed, - typed, - } - } -} - /// Given a graph and the known project name retrieved from the manifest, produce an iterator /// yielding any nodes from the graph that might potentially be a project node. fn potential_proj_nodes<'a>(g: &'a Graph, proj_name: &'a str) -> impl 'a + Iterator { @@ -1750,33 +1726,6 @@ fn find_core_dep(graph: &Graph, node: NodeIx) -> Option { None } -/// Compiles the package to an AST. -pub fn compile_ast( - pkg: &PackageDescriptor, - build_profile: &BuildProfile, - engines: &Engines, - namespace: namespace::Module, - package_name: &str, - metrics: &mut PerformanceData, -) -> Result> { - let source = pkg.manifest_file.entry_string()?; - let sway_build_config = sway_build_config( - pkg.manifest_file.dir(), - &pkg.manifest_file.entry_path(), - pkg.target, - build_profile, - )?; - let ast_res = sway_core::compile_to_ast( - engines, - source, - namespace, - Some(&sway_build_config), - package_name, - metrics, - ); - Ok(ast_res) -} - /// Compiles the given package. /// /// ## Program Types @@ -1813,16 +1762,28 @@ pub fn compile( print_on_failure(engines.se(), terse_mode, warnings, errors, reverse_results); bail!("Failed to compile {}", pkg.name); }; + let source = pkg.manifest_file.entry_string()?; // First, compile to an AST. We'll update the namespace and check for JSON ABI output. let ast_res = time_expr!( "compile to ast", "compile_to_ast", - compile_ast(pkg, profile, engines, namespace, &pkg.name, &mut metrics)?, + sway_core::compile_to_ast( + engines, + source, + namespace, + Some(&sway_build_config), + &pkg.name, + &mut metrics, + ), Some(sway_build_config.clone()), metrics ); - let typed_program = match ast_res.value.as_ref() { + let programs = match ast_res.value.as_ref() { + None => return fail(&ast_res.warnings, &ast_res.errors), + Some(programs) => programs, + }; + let typed_program = match programs.typed.as_ref() { None => return fail(&ast_res.warnings, &ast_res.errors), Some(typed_program) => typed_program, }; @@ -2610,7 +2571,7 @@ fn update_json_type_declaration( } /// Compile the entire forc package and return the lexed, parsed and typed programs -/// of the dependancies and project. +/// of the dependencies and project. /// The final item in the returned vector is the project. pub fn check( plan: &BuildPlan, @@ -2638,52 +2599,63 @@ pub fn check( ) .expect("failed to create dependency namespace"); - let CompileResult { - value, - mut warnings, - mut errors, - } = parse(manifest, build_target, terse_mode, include_tests, engines)?; + let profile = BuildProfile { + terse: terse_mode, + ..BuildProfile::debug() + }; - let (lexed, parsed) = match value { - None => { - results.push(CompileResult::new(None, warnings, errors)); + let build_config = sway_build_config( + manifest.dir(), + &manifest.entry_path(), + build_target, + &profile, + )? + .include_tests(include_tests); + + let mut metrics = PerformanceData::default(); + let programs_res = sway_core::compile_to_ast( + engines, + manifest.entry_string()?, + dep_namespace, + Some(&build_config), + &pkg.name, + &mut metrics, + ); + + let programs = match programs_res.value.as_ref() { + Some(programs) => programs, + _ => { + results.push(programs_res); return Ok(results); } - Some(modules) => modules, }; - let ast_result = sway_core::parsed_to_ast(engines, &parsed, dep_namespace, None, &pkg.name); - warnings.extend(ast_result.warnings); - errors.extend(ast_result.errors); + match programs.typed.as_ref() { + Some(typed_program) => { + if let TreeType::Library = typed_program.kind.tree_type() { + let mut namespace = typed_program.root.namespace.clone(); + namespace.name = Some(Ident::new_no_span(pkg.name.clone())); + namespace.span = Some( + Span::new( + manifest.entry_string()?, + 0, + 0, + Some(engines.se().get_source_id(&manifest.entry_path())), + ) + .unwrap(), + ); + lib_namespace_map.insert(node, namespace.module().clone()); + } - let typed_program = match ast_result.value { + source_map.insert_dependency(manifest.dir()); + } None => { - let value = Some(Programs::new(lexed, parsed, None)); - results.push(CompileResult::new(value, warnings, errors)); + results.push(programs_res); return Ok(results); } - Some(typed_program) => typed_program, - }; - - if let TreeType::Library = typed_program.kind.tree_type() { - let mut namespace = typed_program.root.namespace.clone(); - namespace.name = Some(Ident::new_no_span(pkg.name.clone())); - namespace.span = Some( - Span::new( - manifest.entry_string()?, - 0, - 0, - Some(engines.se().get_source_id(&manifest.entry_path())), - ) - .unwrap(), - ); - lib_namespace_map.insert(node, namespace.module().clone()); } - source_map.insert_dependency(manifest.dir()); - - let value = Some(Programs::new(lexed, parsed, Some(typed_program))); - results.push(CompileResult::new(value, warnings, errors)); + results.push(programs_res) } if results.is_empty() { @@ -2693,29 +2665,6 @@ pub fn check( Ok(results) } -/// Returns a parsed AST from the supplied [PackageManifestFile] -pub fn parse( - manifest: &PackageManifestFile, - build_target: BuildTarget, - terse_mode: bool, - include_tests: bool, - engines: &Engines, -) -> anyhow::Result> { - let profile = BuildProfile { - terse: terse_mode, - ..BuildProfile::debug() - }; - let source = manifest.entry_string()?; - let sway_build_config = sway_build_config( - manifest.dir(), - &manifest.entry_path(), - build_target, - &profile, - )? - .include_tests(include_tests); - Ok(sway_core::parse(source, engines, Some(&sway_build_config))) -} - /// Format an error message for an absent `Forc.toml`. pub fn manifest_file_missing(dir: &Path) -> anyhow::Error { let message = format!( diff --git a/sway-core/src/language/mod.rs b/sway-core/src/language/mod.rs index 5b38f9dbeb0..456c3f823d6 100644 --- a/sway-core/src/language/mod.rs +++ b/sway-core/src/language/mod.rs @@ -6,6 +6,7 @@ pub mod lexed; mod literal; mod module; pub mod parsed; +pub mod programs; mod purity; pub mod ty; mod visibility; @@ -16,5 +17,6 @@ pub use inline::*; pub use lazy_op::*; pub use literal::*; pub use module::*; +pub use programs::*; pub use purity::*; pub use visibility::*; diff --git a/sway-core/src/language/programs.rs b/sway-core/src/language/programs.rs new file mode 100644 index 00000000000..3797994fdd5 --- /dev/null +++ b/sway-core/src/language/programs.rs @@ -0,0 +1,18 @@ +use super::{lexed::LexedProgram, parsed::ParseProgram, ty::TyProgram}; + +/// Contains the lexed, parsed, and typed compilation stages of a program. +pub struct Programs { + pub lexed: LexedProgram, + pub parsed: ParseProgram, + pub typed: Option, +} + +impl Programs { + pub fn new(lexed: LexedProgram, parsed: ParseProgram, typed: Option) -> Programs { + Programs { + lexed, + parsed, + typed, + } + } +} diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index fa30a7454ca..6ee8b16fb48 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -54,6 +54,7 @@ use sway_error::warning::CompileWarning; use sway_types::{ident::Ident, span, Spanned}; pub use type_system::*; +pub use language::Programs; use language::{lexed, parsed, ty, Visibility}; use transform::to_parsed_lang::{self, convert_module_kind}; @@ -456,7 +457,7 @@ pub fn compile_to_ast( build_config: Option<&BuildConfig>, package_name: &str, metrics: &mut PerformanceData, -) -> CompileResult { +) -> CompileResult { // Parse the program to a concrete syntax tree (CST). let CompileResult { value: parse_program_opt, @@ -470,8 +471,8 @@ pub fn compile_to_ast( metrics ); - let (.., mut parse_program) = match parse_program_opt { - Some(parse_program) => parse_program, + let (lexed_program, mut parsed_program) = match parse_program_opt { + Some(modules) => modules, None => return deduped_err(warnings, errors), }; @@ -480,7 +481,7 @@ pub fn compile_to_ast( .map(|config| !config.include_tests) .unwrap_or(true) { - parse_program.exclude_tests(); + parsed_program.exclude_tests(); } // Type check (+ other static analysis) the CST to a typed AST. @@ -489,7 +490,7 @@ pub fn compile_to_ast( "parse_ast", parsed_to_ast( engines, - &parse_program, + &parsed_program, initial_namespace, build_config, package_name, @@ -500,20 +501,15 @@ pub fn compile_to_ast( errors.extend(typed_res.errors); warnings.extend(typed_res.warnings); - let typed_program = match typed_res.value { - Some(tp) => tp, - None => return deduped_err(warnings, errors), - }; ok( - typed_program, + Programs::new(lexed_program, parsed_program, typed_res.value), dedup_unsorted(warnings), dedup_unsorted(errors), ) } -/// Given input Sway source code, -/// try compiling to a `CompiledAsm`, +/// Given input Sway source code, try compiling to a `CompiledAsm`, /// containing the asm in opcode form (not raw bytes/bytecode). pub fn compile_to_asm( engines: &Engines, @@ -534,28 +530,32 @@ pub fn compile_to_asm( ast_to_asm(engines, &ast_res, &build_config) } -/// Given an AST compilation result, -/// try compiling to a `CompiledAsm`, +/// Given an AST compilation result, try compiling to a `CompiledAsm`, /// containing the asm in opcode form (not raw bytes/bytecode). pub fn ast_to_asm( engines: &Engines, - ast_res: &CompileResult, + ast_res: &CompileResult, build_config: &BuildConfig, ) -> CompileResult { - match &ast_res.value { - None => err(ast_res.warnings.clone(), ast_res.errors.clone()), - Some(typed_program) => { - let mut errors = ast_res.errors.clone(); - let mut warnings = ast_res.warnings.clone(); - let asm = check!( - compile_ast_to_ir_to_asm(engines, typed_program, build_config), - return deduped_err(warnings, errors), - warnings, - errors - ); - ok(CompiledAsm(asm), warnings, errors) - } - } + let programs = match ast_res.value.as_ref() { + Some(programs) => programs, + None => return err(ast_res.warnings.clone(), ast_res.errors.clone()), + }; + + let typed_program = match &programs.typed { + Some(typed_program) => typed_program, + None => return err(ast_res.warnings.clone(), ast_res.errors.clone()), + }; + + let mut errors = ast_res.errors.clone(); + let mut warnings = ast_res.warnings.clone(); + let asm = check!( + compile_ast_to_ir_to_asm(engines, typed_program, build_config), + return deduped_err(warnings, errors), + warnings, + errors + ); + ok(CompiledAsm(asm), warnings, errors) } pub(crate) fn compile_ast_to_ir_to_asm( diff --git a/sway-lsp/src/core/session.rs b/sway-lsp/src/core/session.rs index b38c259c186..3f4737eca07 100644 --- a/sway-lsp/src/core/session.rs +++ b/sway-lsp/src/core/session.rs @@ -23,7 +23,7 @@ use lsp_types::{ TextDocumentContentChangeEvent, TextEdit, Url, }; use parking_lot::RwLock; -use pkg::{manifest::ManifestFile, Programs}; +use pkg::manifest::ManifestFile; use std::{ fs::File, io::Write, @@ -38,7 +38,7 @@ use sway_core::{ parsed::{AstNode, ParseProgram}, ty, }, - BuildTarget, CompileResult, Engines, Namespace, + BuildTarget, CompileResult, Engines, Namespace, Programs, }; use sway_types::{Span, Spanned}; use sway_utils::helpers::get_sway_files; diff --git a/test/src/ir_generation/mod.rs b/test/src/ir_generation/mod.rs index 021c48e0b11..0237405f69e 100644 --- a/test/src/ir_generation/mod.rs +++ b/test/src/ir_generation/mod.rs @@ -129,7 +129,7 @@ pub(super) async fn run(filter_regex: Option<®ex::Regex>) -> Result<()> { let mut metrics = PerformanceData::default(); let sway_str = String::from_utf8_lossy(&sway_str); - let typed_res = compile_to_ast( + let compile_res = compile_to_ast( &engines, Arc::from(sway_str), core_lib.clone(), @@ -137,11 +137,11 @@ pub(super) async fn run(filter_regex: Option<®ex::Regex>) -> Result<()> { "test_lib", &mut metrics, ); - if !typed_res.errors.is_empty() { + if !compile_res.errors.is_empty() { panic!( "Failed to compile test {}:\n{}", path.display(), - typed_res + compile_res .errors .iter() .map(|err| err.to_string()) @@ -150,13 +150,15 @@ pub(super) async fn run(filter_regex: Option<®ex::Regex>) -> Result<()> { .join("\n") ); } - let typed_program = typed_res + let programs = compile_res .value .expect("there were no errors, so there should be a program"); + let typed_program = programs.typed.as_ref().unwrap(); + // Compile to IR. let include_tests = true; - let mut ir = compile_program(&typed_program, include_tests, &engines) + let mut ir = compile_program(typed_program, include_tests, &engines) .unwrap_or_else(|e| { panic!("Failed to compile test {}:\n{e}", path.display()); }) @@ -182,7 +184,7 @@ pub(super) async fn run(filter_regex: Option<®ex::Regex>) -> Result<()> { panic!( "Failed to compile test {}:\n{}", path.display(), - typed_res + compile_res .errors .iter() .map(|err| err.to_string()) @@ -226,7 +228,7 @@ pub(super) async fn run(filter_regex: Option<®ex::Regex>) -> Result<()> { panic!( "Failed to compile test {}:\n{}", path.display(), - typed_res + compile_res .errors .iter() .map(|err| err.to_string())