diff --git a/cli/src/build.rs b/cli/src/build.rs index cad9b9bf9c..82b8242bc7 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -240,7 +240,7 @@ pub fn build_file<'a>( app_o_file.to_str().unwrap(), ]; if matches!(opt_level, OptLevel::Development) { - inputs.push(bitcode::OBJ_PATH); + inputs.push(bitcode::BUILTINS_HOST_OBJ_PATH); } let (mut child, _) = // TODO use lld diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 6b0d8a9793..d5285b3602 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -97,7 +97,7 @@ pub fn build_zig_host_native( "build-exe", "-fPIE", shared_lib_path.to_str().unwrap(), - bitcode::OBJ_PATH, + bitcode::BUILTINS_HOST_OBJ_PATH, ]); } else { command.args(&["build-obj", "-fPIC"]); @@ -186,7 +186,7 @@ pub fn build_zig_host_native( "build-exe", "-fPIE", shared_lib_path.to_str().unwrap(), - bitcode::OBJ_PATH, + bitcode::BUILTINS_HOST_OBJ_PATH, ]); } else { command.args(&["build-obj", "-fPIC"]); @@ -282,7 +282,7 @@ pub fn build_c_host_native( if let Some(shared_lib_path) = shared_lib_path { command.args(&[ shared_lib_path.to_str().unwrap(), - bitcode::OBJ_PATH, + bitcode::BUILTINS_HOST_OBJ_PATH, "-fPIE", "-pie", "-lm", @@ -878,7 +878,7 @@ fn link_wasm32( let zig_str_path = find_zig_str_path(); let wasi_libc_path = find_wasi_libc_path(); - let child = Command::new("zig9") + let child = Command::new("zig") // .env_clear() // .env("PATH", &env_path) .args(&["build-exe"]) diff --git a/compiler/builtins/bitcode/build.zig b/compiler/builtins/bitcode/build.zig index 9dba3a8d4b..93b970edd7 100644 --- a/compiler/builtins/bitcode/build.zig +++ b/compiler/builtins/bitcode/build.zig @@ -59,8 +59,9 @@ fn generateLlvmIrFile( // Generate Object File // TODO: figure out how to get this to emit symbols that are only scoped to linkage (global but hidden). -// Also, zig has -ffunction-sections, but I am not sure how to add it here. -// With both of those changes, unused zig functions will be cleaned up by the linker saving around 100k. +// @bhansconnect: I believe anything with global scope will still be preserved by the linker even if it +// is never called. I think it could theoretically be called by a dynamic lib that links to the executable +// or something similar. fn generateObjectFile( b: *Builder, mode: std.builtin.Mode, @@ -75,6 +76,7 @@ fn generateObjectFile( obj.setOutputDir("."); obj.strip = true; obj.target = target; + obj.link_function_sections = true; const obj_step = b.step(step_name, "Build object file for linking"); obj_step.dependOn(&obj.step); } diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 077a6fb9bc..cb45e67ac1 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -1,10 +1,15 @@ use std::ops::Index; -pub const OBJ_PATH: &str = env!( +pub const BUILTINS_HOST_OBJ_PATH: &str = env!( "BUILTINS_HOST_O", "Env var BUILTINS_HOST_O not found. Is there a problem with the build script?" ); +pub const BUILTINS_WASM32_OBJ_PATH: &str = env!( + "BUILTINS_WASM32_O", + "Env var BUILTINS_WASM32_O not found. Is there a problem with the build script?" +); + #[derive(Debug, Default)] pub struct IntrinsicName { pub options: [&'static str; 14], diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index e0cedec8d5..791dfffc04 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -11,7 +11,8 @@ use roc_mono::layout::{Layout, LayoutIds}; use crate::layout::WasmLayout; use crate::storage::{Storage, StoredValue, StoredValueKind}; use crate::wasm_module::linking::{ - DataSymbol, LinkingSection, RelocationSection, WasmObjectSymbol, WASM_SYM_UNDEFINED, + DataSymbol, LinkingSection, RelocationSection, WasmObjectSymbol, WASM_SYM_BINDING_WEAK, + WASM_SYM_UNDEFINED, }; use crate::wasm_module::sections::{ CodeSection, DataMode, DataSection, DataSegment, ExportSection, FunctionSection, GlobalSection, @@ -21,7 +22,10 @@ use crate::wasm_module::{ code_builder, BlockType, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType, LocalId, Signature, SymInfo, ValueType, }; -use crate::{copy_memory, CopyMemoryConfig, Env, PTR_TYPE}; +use crate::{ + copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_TYPE, + STACK_POINTER_NAME, +}; /// The memory address where the constants data will be loaded during module instantiation. /// We avoid address zero and anywhere near it. They're valid addresses but maybe bug-prone. @@ -31,8 +35,6 @@ const CONST_SEGMENT_BASE_ADDR: u32 = 1024; /// Index of the data segment where we store constants const CONST_SEGMENT_INDEX: usize = 0; -const IMPORT_MODULE_BUILTINS: &str = "builtins"; - pub struct WasmBackend<'a> { env: &'a Env<'a>, @@ -66,7 +68,7 @@ impl<'a> WasmBackend<'a> { let num_procs = proc_symbols.len(); exports.push(Export { - name: "__linear_memory".to_string(), + name: MEMORY_NAME.to_string(), ty: ExportType::Mem, index: 0, }); @@ -78,10 +80,17 @@ impl<'a> WasmBackend<'a> { }, init: ConstExpr::I32(MEMORY_INIT_SIZE as i32), }; - linker_symbols.push(SymInfo::Global(WasmObjectSymbol::Defined { - flags: 0, + + exports.push(Export { + name: STACK_POINTER_NAME.to_string(), + ty: ExportType::Global, index: 0, - name: "__stack_pointer".to_string(), + }); + + linker_symbols.push(SymInfo::Global(WasmObjectSymbol::Defined { + flags: WASM_SYM_BINDING_WEAK, + index: 0, + name: STACK_POINTER_NAME.to_string(), })); let const_segment = DataSegment { @@ -759,7 +768,7 @@ impl<'a> WasmBackend<'a> { let import_index = self.module.import.entries.len() as u32; let import = Import { - module: IMPORT_MODULE_BUILTINS, + module: BUILTINS_IMPORT_MODULE_NAME, name: name.to_string(), description: ImportDesc::Func { signature_index }, }; diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 26e3e63bdb..6fcad7c739 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -21,6 +21,9 @@ const PTR_TYPE: ValueType = ValueType::I32; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; pub const FRAME_ALIGNMENT_BYTES: i32 = 16; +pub const MEMORY_NAME: &str = "memory"; +pub const BUILTINS_IMPORT_MODULE_NAME: &str = "builtins"; +pub const STACK_POINTER_NAME: &str = "__stack_pointer"; pub struct Env<'a> { pub arena: &'a Bump, diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 31048b4a2a..c251035e62 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -2159,7 +2159,8 @@ fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, List<'a>> min_indent, List::Open, List::Space, - List::IndentEnd + List::IndentEnd, + Expr::SpaceBefore ) .parse(arena, state)?; diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 8e8c4efc0e..89e4968668 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -1261,53 +1261,46 @@ macro_rules! collection_trailing_sep { #[macro_export] macro_rules! collection_trailing_sep_e { - ($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr, $open_problem:expr, $space_problem:expr, $indent_problem:expr) => { + ($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr, $open_problem:expr, $space_problem:expr, $indent_problem:expr, $space_before:expr) => { skip_first!( $opening_brace, - skip_first!( - // We specifically allow space characters inside here, so that - // `[ ]` can be successfully parsed as an empty list, and then - // changed by the formatter back into `[]`. - // - // We don't allow newlines or comments in the middle of empty - // roc_collections because those are normally stored in an Expr, - // and there's no Expr in which to store them in an empty collection! - // - // We could change the AST to add extra storage specifically to - // support empty literals containing newlines or comments, but this - // does not seem worth even the tiniest regression in compiler performance. - zero_or_more!($crate::parser::word1(b' ', |row, col| $space_problem( - crate::parser::BadInputError::LineTooLong, - row, - col - ))), - |arena, state| { - let (_, elements, state) = - and!( - $crate::parser::trailing_sep_by0( - $delimiter, - $crate::blankspace::space0_before_optional_after( - $elem, - $min_indent, - $space_problem, - $indent_problem, - $indent_problem - ) - ), - $crate::blankspace::space0_e($min_indent, $space_problem, $indent_problem) - ).parse(arena, state)?; + |arena, state| { + let (_, spaces, state) = space0_e($min_indent, $space_problem, $indent_problem) + .parse(arena, state)?; - let (_,_, state) = - if elements.0.is_empty() { - one_of_with_error![$open_problem; $closing_brace].parse(arena, state)? - } else { - $closing_brace.parse(arena, state)? - }; + let (_, (mut parsed_elems, mut final_comments), state) = + and!( + $crate::parser::trailing_sep_by0( + $delimiter, + $crate::blankspace::space0_before_optional_after( + $elem, + $min_indent, + $space_problem, + $indent_problem, + $indent_problem + ) + ), + $crate::blankspace::space0_e($min_indent, $space_problem, $indent_problem) + ).parse(arena, state)?; + let (_,_, state) = + if parsed_elems.is_empty() { + one_of_with_error![$open_problem; $closing_brace].parse(arena, state)? + } else { + $closing_brace.parse(arena, state)? + }; - Ok((MadeProgress, elements, state)) + if !spaces.is_empty() { + if let Some(first) = parsed_elems.first_mut() { + first.value = $space_before(arena.alloc(first.value), spaces) + } else { + assert!(final_comments.is_empty()); + final_comments = spaces; + } } - ) + + Ok((MadeProgress, (parsed_elems, final_comments), state)) + } ) }; } diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index b214d4b603..de90f5bf4b 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -326,7 +326,8 @@ fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRec min_indent, PRecord::Open, PRecord::Space, - PRecord::IndentEnd + PRecord::IndentEnd, + Pattern::SpaceBefore ) .parse(arena, state)?; diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index ac95ca185e..0fe9fd3c85 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -26,7 +26,8 @@ fn tag_union_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TT min_indent, TTagUnion::Open, TTagUnion::Space, - TTagUnion::IndentEnd + TTagUnion::IndentEnd, + Tag::SpaceBefore ) .parse(arena, state)?; @@ -276,7 +277,8 @@ fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TReco min_indent, TRecord::Open, TRecord::Space, - TRecord::IndentEnd + TRecord::IndentEnd, + AssignedField::SpaceBefore ) .parse(arena, state)?; diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index e184cf3dfb..38a2b6ae7a 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -1193,6 +1193,30 @@ mod test_parse { assert_eq!(Ok(expected), actual); } + #[test] + fn newline_inside_empty_list() { + let arena = Bump::new(); + let expected = List { + items: &[], + final_comments: &[Newline], + }; + let actual = parse_expr_with(&arena, "[\n]"); + + assert_eq!(Ok(expected), actual); + } + + #[test] + fn comment_inside_empty_list() { + let arena = Bump::new(); + let expected = List { + items: &[], + final_comments: &[LineComment("comment")], + }; + let actual = parse_expr_with(&arena, "[#comment\n]"); + + assert_eq!(Ok(expected), actual); + } + #[test] fn packed_singleton_list() { let arena = Bump::new(); @@ -1219,6 +1243,24 @@ mod test_parse { assert_eq!(Ok(expected), actual); } + #[test] + fn newline_singleton_list() { + let arena = Bump::new(); + let item = &*arena.alloc(Expr::SpaceAfter( + arena.alloc(Num("1")), + arena.alloc([Newline]), + )); + let item = Expr::SpaceBefore(item, arena.alloc([Newline])); + let items = [&*arena.alloc(Located::new(1, 1, 0, 1, item))]; + let expected = List { + items: &items, + final_comments: &[], + }; + let actual = parse_expr_with(&arena, "[\n1\n]"); + + assert_eq!(Ok(expected), actual); + } + // FIELD ACCESS #[test] diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index d88148090f..f1f0bdd27b 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -1330,7 +1330,7 @@ fn pow_int() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn atan() { assert_evals_to!("Num.atan 10", 1.4711276743037347, f64); } diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index d49d602931..4dcab7ccd9 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -193,7 +193,10 @@ pub fn helper( app_o_file.clone(), // Long term we probably want a smarter way to link in zig builtins. // With the current method all methods are kept and it adds about 100k to all outputs. - &[app_o_file.to_str().unwrap(), bitcode::OBJ_PATH], + &[ + app_o_file.to_str().unwrap(), + bitcode::BUILTINS_HOST_OBJ_PATH, + ], LinkType::Dylib, ) .expect("failed to link dynamic library"); diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index 4f665232e1..d8f209d782 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -367,7 +367,7 @@ pub fn helper_wasm<'a>( use std::process::Command; - Command::new("zig9") + Command::new("zig") .current_dir(dir_path) .args(&[ "wasm-ld", diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index d0a8ebfa26..b9ee9459aa 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -4,8 +4,12 @@ use std::hash::{Hash, Hasher}; use crate::helpers::from_wasm32_memory::FromWasm32Memory; use crate::helpers::wasm32_test_result::Wasm32TestResult; +use roc_builtins::bitcode; use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; +use roc_gen_wasm::MEMORY_NAME; + +use tempfile::tempdir; const TEST_WRAPPER_NAME: &str = "test_wrapper"; @@ -143,8 +147,43 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( use wasmer::{Instance, Module, Store}; let store = Store::default(); - // let module = Module::from_file(&store, &test_wasm_path).unwrap(); - let wasmer_module = Module::from_binary(&store, &module_bytes).unwrap(); + + let wasmer_module = { + let dir = tempdir().unwrap(); + let dirpath = dir.path(); + let final_wasm_file = dirpath.join("final.wasm"); + let app_o_file = dirpath.join("app.o"); + + // write the module to a file so the linker can access it + std::fs::write(&app_o_file, &module_bytes).unwrap(); + + std::process::Command::new("zig") + .args(&[ + "wasm-ld", + // input files + app_o_file.to_str().unwrap(), + bitcode::BUILTINS_WASM32_OBJ_PATH, + // output + "-o", + final_wasm_file.to_str().unwrap(), + // we don't define `_start` + "--no-entry", + // If you only specify test_wrapper, it will stop at the call to UserApp_main_1 + // But if you specify both exports, you get all the dependencies. + // + // It seems that it will not write out an export you didn't explicitly specify, + // even if it's a dependency of another export! + // In our case we always export main and test_wrapper so that's OK. + "--export", + "test_wrapper", + "--export", + "#UserApp_main_1", + ]) + .output() + .unwrap(); + + Module::from_file(&store, &final_wasm_file).unwrap() + }; // First, we create the `WasiEnv` use wasmer_wasi::WasiState; @@ -171,7 +210,7 @@ where let instance = crate::helpers::wasm::helper_wasm(&arena, src, stdlib, &expected); - let memory = instance.exports.get_memory("__linear_memory").unwrap(); + let memory = instance.exports.get_memory(MEMORY_NAME).unwrap(); let test_wrapper = instance.exports.get_function(TEST_WRAPPER_NAME).unwrap(); diff --git a/examples/hello-web/.gitignore b/examples/hello-web/.gitignore index 227b499154..9cd8cd131d 100644 --- a/examples/hello-web/.gitignore +++ b/examples/hello-web/.gitignore @@ -1,2 +1,2 @@ -hello-web +hello-web.wasm *.wat