From 23dc591c5a2e60f69e769cc1b0e3b184dc902887 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 4 Sep 2021 13:25:49 +0200 Subject: [PATCH 01/21] add i386 cli tests --- cli/Cargo.toml | 3 ++- cli/tests/cli_run.rs | 47 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 101d0d87c7..a98ab09045 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -16,7 +16,8 @@ bench = false [features] default = ["target-x86", "llvm", "editor"] -wasm-cli-run = [] +wasm32-cli-run = [] +i386-cli-run = [] # This is a separate feature because when we generate docs on Netlify, # it doesn't have LLVM installed. (Also, it doesn't need to do code gen.) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 5e20ecf4db..8670815ae1 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -108,7 +108,7 @@ mod cli_run { assert!(out.status.success()); } - #[cfg(feature = "wasm-cli-run")] + #[cfg(feature = "wasm32-cli-run")] fn check_wasm_output_with_stdin( file: &Path, stdin: &[&str], @@ -315,7 +315,7 @@ mod cli_run { )* - #[cfg(feature = "wasm-cli-run")] + #[cfg(feature = "wasm32-cli-run")] mod wasm { use super::*; $( @@ -354,6 +354,47 @@ mod cli_run { )* } + #[cfg(feature = "i386-cli-run")] + mod i386 { + use super::*; + $( + #[test] + #[cfg_attr(not(debug_assertions), serial(benchmark))] + fn $test_name() { + let benchmark = $benchmark; + let file_name = examples_dir("benchmarks").join(benchmark.filename); + + // TODO fix QuicksortApp and then remove this! + match benchmark.filename { + "QuicksortApp.roc" => { + eprintln!("WARNING: skipping testing benchmark {} because the test is broken right now!", benchmark.filename); + return; + } + _ => {} + } + + // Check with and without optimizations + check_output_with_stdin( + &file_name, + benchmark.stdin, + benchmark.executable_filename, + &["--backend=x86_32"], + benchmark.expected_ending, + benchmark.use_valgrind, + ); + + check_output_with_stdin( + &file_name, + benchmark.stdin, + benchmark.executable_filename, + &["--backend=x86_32", "--optimize"], + benchmark.expected_ending, + benchmark.use_valgrind, + ); + } + )* + } + #[test] #[cfg(not(debug_assertions))] fn all_benchmarks_have_tests() { @@ -562,7 +603,7 @@ mod cli_run { } } -#[cfg(feature = "wasm-cli-run")] +#[cfg(feature = "wasm32-cli-run")] fn run_with_wasmer(wasm_path: &std::path::Path, stdin: &[&str]) -> String { use std::io::Write; use wasmer::{Instance, Module, Store}; From f8c3351c08fe354d1332f91fae9fb88bb61c7659 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 4 Sep 2021 13:26:31 +0200 Subject: [PATCH 02/21] better debug messages from bench host --- examples/benchmarks/platform/host.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/benchmarks/platform/host.zig b/examples/benchmarks/platform/host.zig index 420a8f379d..9f3e5efb5a 100644 --- a/examples/benchmarks/platform/host.zig +++ b/examples/benchmarks/platform/host.zig @@ -40,7 +40,7 @@ export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { if (DEBUG) { var ptr = malloc(size); const stdout = std.io.getStdOut().writer(); - stdout.print("alloc: {d} (alignment {d})\n", .{ ptr, alignment }) catch unreachable; + stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable; return ptr; } else { return malloc(size); @@ -48,8 +48,10 @@ export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { } export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { - _ = old_size; - _ = alignment; + if (DEBUG) { + const stdout = std.io.getStdOut().writer(); + stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable; + } return realloc(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)), new_size); } From aaed62dad3001675b090202867e81dc5f728546a Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 4 Sep 2021 13:26:42 +0200 Subject: [PATCH 03/21] always pick musl as libc --- compiler/build/src/link.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 9041d277c0..a2c1fc40de 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -262,7 +262,7 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) { &emit_bin, zig_host_src.to_str().unwrap(), zig_str_path.to_str().unwrap(), - "i386-linux-gnu", + "i386-linux-musl", ) } _ => panic!("Unsupported architecture {:?}", target.architecture), From 3adfbf3459693025b40ea91167354fdc66aa9419 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 4 Sep 2021 13:46:30 +0200 Subject: [PATCH 04/21] fix bug in realloc on 32-bit platforms --- compiler/builtins/bitcode/src/utils.zig | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index d68d923183..260156b0a7 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -208,13 +208,7 @@ pub fn unsafeReallocate( new_length: usize, element_width: usize, ) [*]u8 { - const align_width: usize = blk: { - if (alignment > 8) { - break :blk (2 * @sizeOf(usize)); - } else { - break :blk @sizeOf(usize); - } - }; + const align_width: usize = std.math.max(alignment, @sizeOf(usize)); const old_width = align_width + old_length * element_width; const new_width = align_width + new_length * element_width; From 9256bdf4ab67c896044ce4175cdf844f0e91dae0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 4 Sep 2021 13:53:10 +0200 Subject: [PATCH 05/21] rename wasm -> wasm32 --- cli/tests/cli_run.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 8670815ae1..2b075bb660 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -117,7 +117,7 @@ mod cli_run { expected_ending: &str, ) { let mut flags = flags.to_vec(); - flags.push("--backend=wasm"); + flags.push("--backend=wasm32"); let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags.as_slice()].concat()); if !compile_out.stderr.is_empty() { @@ -316,7 +316,7 @@ mod cli_run { )* #[cfg(feature = "wasm32-cli-run")] - mod wasm { + mod wasm32 { use super::*; $( #[test] From 6beff62ece20137a8d7ec8c451e41132253a70cd Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 4 Sep 2021 13:55:22 +0200 Subject: [PATCH 06/21] add i386 tests to earthfile --- Earthfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Earthfile b/Earthfile index 24b7b3aa67..a5add2648a 100644 --- a/Earthfile +++ b/Earthfile @@ -82,6 +82,10 @@ test-rust: RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc RUN --mount=type=cache,target=$SCCACHE_DIR \ cargo test --release && sccache --show-stats + # run i386 (32-bit linux) cli tests + RUN echo "4" | cargo run --release --target=x86_32 examples/benchmarks/NQueens.roc + RUN --mount=type=cache,target=$SCCACHE_DIR \ + cargo test --release --test cli_run wasm32 --features="wasm32-cli-run" && sccache --show-stats verify-no-git-changes: FROM +test-rust From cdc61817f23a1a668c9361c8832ac0790260d4fd Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 4 Sep 2021 14:27:40 +0200 Subject: [PATCH 07/21] fix cargo run invocation --- Earthfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Earthfile b/Earthfile index a5add2648a..63ebc722ea 100644 --- a/Earthfile +++ b/Earthfile @@ -83,7 +83,7 @@ test-rust: RUN --mount=type=cache,target=$SCCACHE_DIR \ cargo test --release && sccache --show-stats # run i386 (32-bit linux) cli tests - RUN echo "4" | cargo run --release --target=x86_32 examples/benchmarks/NQueens.roc + RUN echo "4" | cargo run --release -- --target=x86_32 examples/benchmarks/NQueens.roc RUN --mount=type=cache,target=$SCCACHE_DIR \ cargo test --release --test cli_run wasm32 --features="wasm32-cli-run" && sccache --show-stats From 8461166cd21113ab2128e9a5fefad312fd614c2e Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 4 Sep 2021 15:32:57 +0200 Subject: [PATCH 08/21] fix typo --- Earthfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Earthfile b/Earthfile index 63ebc722ea..726144bcc5 100644 --- a/Earthfile +++ b/Earthfile @@ -83,7 +83,7 @@ test-rust: RUN --mount=type=cache,target=$SCCACHE_DIR \ cargo test --release && sccache --show-stats # run i386 (32-bit linux) cli tests - RUN echo "4" | cargo run --release -- --target=x86_32 examples/benchmarks/NQueens.roc + RUN echo "4" | cargo run --release -- --backend=x86_32 examples/benchmarks/NQueens.roc RUN --mount=type=cache,target=$SCCACHE_DIR \ cargo test --release --test cli_run wasm32 --features="wasm32-cli-run" && sccache --show-stats From 17ae8042693b80d9aa95ea0ea9639059910fab94 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 4 Sep 2021 15:59:26 +0200 Subject: [PATCH 09/21] run i386 tests... --- Earthfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Earthfile b/Earthfile index 726144bcc5..d5c2eddc5e 100644 --- a/Earthfile +++ b/Earthfile @@ -85,7 +85,7 @@ test-rust: # run i386 (32-bit linux) cli tests RUN echo "4" | cargo run --release -- --backend=x86_32 examples/benchmarks/NQueens.roc RUN --mount=type=cache,target=$SCCACHE_DIR \ - cargo test --release --test cli_run wasm32 --features="wasm32-cli-run" && sccache --show-stats + cargo test --release --test cli_run i386 --features="i386-cli-run" && sccache --show-stats verify-no-git-changes: FROM +test-rust From 71b6b56f8cf5b1691c6e4448e5bbe41e70dc4c72 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 4 Sep 2021 22:07:33 +0200 Subject: [PATCH 10/21] for i386, disable valgrind for cli_run tests --- cli/tests/cli_run.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 2b075bb660..5f90a93b78 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -380,7 +380,9 @@ mod cli_run { benchmark.executable_filename, &["--backend=x86_32"], benchmark.expected_ending, - benchmark.use_valgrind, + // TODO enable valgrind when CI has 32-bit valgrind support + // benchmark.use_valgrind, + false ); check_output_with_stdin( @@ -389,7 +391,9 @@ mod cli_run { benchmark.executable_filename, &["--backend=x86_32", "--optimize"], benchmark.expected_ending, - benchmark.use_valgrind, + // TODO enable valgrind when CI has 32-bit valgrind support + // benchmark.use_valgrind, + false ); } )* From f8809a3eef3bc7c205b9407b0719c8e5af4a4860 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 5 Sep 2021 14:07:37 +0200 Subject: [PATCH 11/21] rename wasm -> wasm32 --- Cargo.lock | 30 +++ Cargo.toml | 1 + compiler/gen_wasm/Cargo.toml | 29 +++ compiler/gen_wasm/src/from_wasm32_memory.rs | 230 +++++++++++++++++++ compiler/gen_wasm/src/lib.rs | 1 + compiler/test_gen/Cargo.toml | 1 + compiler/test_gen/src/helpers/eval.rs | 235 +------------------- 7 files changed, 295 insertions(+), 232 deletions(-) create mode 100644 compiler/gen_wasm/Cargo.toml create mode 100644 compiler/gen_wasm/src/from_wasm32_memory.rs create mode 100644 compiler/gen_wasm/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 7e1feca44c..2a22b9af18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2570,6 +2570,12 @@ dependencies = [ "syn 1.0.72", ] +[[package]] +name = "parity-wasm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" + [[package]] name = "parking_lot" version = "0.11.1" @@ -3642,6 +3648,29 @@ dependencies = [ "tokio", ] +[[package]] +name = "roc_gen_wasm" +version = "0.1.0" +dependencies = [ + "bumpalo", + "indoc 0.3.6", + "libc", + "parity-wasm", + "pretty_assertions 0.5.1", + "roc_builtins", + "roc_can", + "roc_collections", + "roc_load", + "roc_module", + "roc_mono", + "roc_std", + "roc_types", + "target-lexicon", + "tempfile", + "wasmer", + "wasmer-wasi", +] + [[package]] name = "roc_ident" version = "0.1.0" @@ -4348,6 +4377,7 @@ dependencies = [ "roc_collections", "roc_constrain", "roc_gen_llvm", + "roc_gen_wasm", "roc_load", "roc_module", "roc_mono", diff --git a/Cargo.toml b/Cargo.toml index 01b160bf9a..27242439f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "compiler/load", "compiler/gen_llvm", "compiler/gen_dev", + "compiler/gen_wasm", "compiler/build", "compiler/arena_pool", "compiler/test_gen", diff --git a/compiler/gen_wasm/Cargo.toml b/compiler/gen_wasm/Cargo.toml new file mode 100644 index 0000000000..2804be6eab --- /dev/null +++ b/compiler/gen_wasm/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "roc_gen_wasm" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +roc_collections = { path = "../collections" } +roc_module = { path = "../module" } +roc_mono = { path = "../mono" } +bumpalo = { version = "3.6.1", features = ["collections"] } +parity-wasm = "0.42" + +roc_std = { path = "../../roc_std" } +wasmer = "2.0.0" +wasmer-wasi = "2.0.0" + +[dev-dependencies] +roc_can = { path = "../can" } +roc_builtins = { path = "../builtins" } +roc_load = { path = "../load" } +roc_types = { path = "../types" } +roc_module = { path = "../module" } +indoc = "0.3.3" +pretty_assertions = "0.5.1" +libc = "0.2" +target-lexicon = "0.12.2" +tempfile = "3.1.0" diff --git a/compiler/gen_wasm/src/from_wasm32_memory.rs b/compiler/gen_wasm/src/from_wasm32_memory.rs new file mode 100644 index 0000000000..d544488f7b --- /dev/null +++ b/compiler/gen_wasm/src/from_wasm32_memory.rs @@ -0,0 +1,230 @@ +use roc_std::{RocDec, RocList, RocOrder, RocStr}; + +pub trait FromWasm32Memory: Sized { + const SIZE_OF_WASM: usize; + const ALIGN_OF_WASM: usize; + const ACTUAL_WIDTH: usize = if (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM) == 0 { + Self::SIZE_OF_WASM + } else { + Self::SIZE_OF_WASM + (Self::ALIGN_OF_WASM - (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM)) + }; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self; +} + +macro_rules! from_wasm_memory_primitive_decode { + ($type_name:ident) => { + const SIZE_OF_WASM: usize = core::mem::size_of::<$type_name>(); + const ALIGN_OF_WASM: usize = core::mem::align_of::<$type_name>(); + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + use core::mem::MaybeUninit; + + let mut output: MaybeUninit = MaybeUninit::uninit(); + let width = std::mem::size_of::(); + + let ptr = output.as_mut_ptr(); + let raw_ptr = ptr as *mut u8; + let slice = unsafe { std::slice::from_raw_parts_mut(raw_ptr, width) }; + + let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(offset as u32); + let foobar = (ptr.deref(memory, 0, width as u32)).unwrap(); + let wasm_slice = unsafe { std::mem::transmute(foobar) }; + + slice.copy_from_slice(wasm_slice); + + unsafe { output.assume_init() } + } + }; +} + +macro_rules! from_wasm_memory_primitive { + ($($type_name:ident ,)+) => { + $( + impl FromWasm32Memory for $type_name { + from_wasm_memory_primitive_decode!($type_name); + } + )* + } +} + +from_wasm_memory_primitive!( + u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder, +); + +impl FromWasm32Memory for () { + const SIZE_OF_WASM: usize = 0; + const ALIGN_OF_WASM: usize = 0; + + fn decode(_: &wasmer::Memory, _: u32) -> Self {} +} + +impl FromWasm32Memory for RocStr { + const SIZE_OF_WASM: usize = 8; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let bytes = ::decode(memory, offset); + + let length = (bytes >> 32) as u32; + let elements = bytes as u32; + + if length == 0 { + RocStr::default() + } else if (length as i32) < 0 { + // this is a small string + let last_byte = bytes.to_ne_bytes()[7]; + let actual_length = (last_byte ^ 0b1000_0000) as usize; + + let slice = &bytes.to_ne_bytes()[..actual_length as usize]; + RocStr::from_slice(slice) + } else { + // this is a big string + let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(elements); + let foobar = (ptr.deref(memory, 0, length)).unwrap(); + let wasm_slice = unsafe { std::mem::transmute(foobar) }; + + RocStr::from_slice(wasm_slice) + } + } +} + +impl FromWasm32Memory for RocList { + const SIZE_OF_WASM: usize = 8; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let bytes = ::decode(memory, offset); + + let length = (bytes >> 32) as u32; + let elements = bytes as u32; + + let mut items = Vec::with_capacity(length as usize); + + for i in 0..length { + let item = ::decode( + memory, + elements + i * ::SIZE_OF_WASM as u32, + ); + items.push(item); + } + + RocList::from_slice(&items) + } +} + +impl FromWasm32Memory for &'_ [T] { + const SIZE_OF_WASM: usize = 8; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let bytes = ::decode(memory, offset); + + let length = (bytes >> 32) as u32; + let elements = bytes as u32; + + let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(elements); + let width = ::SIZE_OF_WASM as u32 * length; + let foobar = (ptr.deref(memory, 0, width)).unwrap(); + let wasm_slice = + unsafe { std::slice::from_raw_parts(foobar as *const _ as *const _, length as usize) }; + + wasm_slice + } +} + +impl FromWasm32Memory for &'_ T { + const SIZE_OF_WASM: usize = 4; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let elements = ::decode(memory, offset); + + let actual = ::decode(memory, elements); + + let b = Box::new(actual); + + std::boxed::Box::::leak(b) + } +} + +impl FromWasm32Memory for [T; N] { + const SIZE_OF_WASM: usize = N * T::SIZE_OF_WASM; + const ALIGN_OF_WASM: usize = T::ALIGN_OF_WASM; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(offset); + let width = ::SIZE_OF_WASM as u32 * N as u32; + let foobar = (ptr.deref(memory, 0, width)).unwrap(); + let wasm_slice: &[T; N] = unsafe { &*(foobar as *const _ as *const [T; N]) }; + + wasm_slice.clone() + } +} + +impl FromWasm32Memory for usize { + const SIZE_OF_WASM: usize = 4; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + ::decode(memory, offset) as usize + } +} + +impl FromWasm32Memory for (T, U) { + const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM; + const ALIGN_OF_WASM: usize = max2(T::SIZE_OF_WASM, U::SIZE_OF_WASM); + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + assert!( + T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, + "this function does not handle alignment" + ); + + let t = ::decode(memory, offset); + + let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); + + (t, u) + } +} + +const fn max2(a: usize, b: usize) -> usize { + if a > b { + a + } else { + b + } +} + +const fn max3(a: usize, b: usize, c: usize) -> usize { + max2(max2(a, b), c) +} + +impl FromWasm32Memory for (T, U, V) { + const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM + V::SIZE_OF_WASM; + const ALIGN_OF_WASM: usize = max3(T::SIZE_OF_WASM, U::SIZE_OF_WASM, V::SIZE_OF_WASM); + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + assert!( + T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, + "this function does not handle alignment" + ); + + assert!( + U::ALIGN_OF_WASM >= V::ALIGN_OF_WASM, + "this function does not handle alignment" + ); + + let t = ::decode(memory, offset); + + let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); + + let v = ::decode( + memory, + offset + T::ACTUAL_WIDTH as u32 + U::ACTUAL_WIDTH as u32, + ); + + (t, u, v) + } +} diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs new file mode 100644 index 0000000000..b620f374d7 --- /dev/null +++ b/compiler/gen_wasm/src/lib.rs @@ -0,0 +1 @@ +pub mod from_wasm32_memory; diff --git a/compiler/test_gen/Cargo.toml b/compiler/test_gen/Cargo.toml index e255efcf5a..ab08fc1527 100644 --- a/compiler/test_gen/Cargo.toml +++ b/compiler/test_gen/Cargo.toml @@ -23,6 +23,7 @@ roc_can = { path = "../can" } roc_parse = { path = "../parse" } roc_build = { path = "../build" } roc_std = { path = "../../roc_std" } +roc_gen_wasm = { path = "../gen_wasm" } im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! bumpalo = { version = "3.6.1", features = ["collections"] } diff --git a/compiler/test_gen/src/helpers/eval.rs b/compiler/test_gen/src/helpers/eval.rs index f380b8ac88..94c8ac7756 100644 --- a/compiler/test_gen/src/helpers/eval.rs +++ b/compiler/test_gen/src/helpers/eval.rs @@ -6,9 +6,9 @@ use roc_can::builtins::builtin_defs_map; use roc_can::def::Def; use roc_collections::all::{MutMap, MutSet}; use roc_gen_llvm::llvm::externs::add_default_roc_externs; +use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; use roc_module::symbol::Symbol; use roc_mono::ir::OptLevel; -use roc_std::{RocDec, RocList, RocOrder, RocStr}; use roc_types::subs::VarStore; use target_lexicon::Triple; @@ -500,7 +500,7 @@ fn fake_wasm_main_function(_: u32, _: u32) -> u32 { #[allow(dead_code)] pub fn assert_wasm_evals_to_help(src: &str, ignore_problems: bool) -> Result where - T: FromWasmMemory, + T: FromWasm32Memory, { let arena = bumpalo::Bump::new(); let context = inkwell::context::Context::create(); @@ -534,7 +534,7 @@ where _ => panic!(), }; - let output = ::decode( + let output = ::decode( memory, // skip the RocCallResult tag id address as u32 + 8, @@ -643,232 +643,3 @@ macro_rules! assert_non_opt_evals_to { $crate::assert_llvm_evals_to!($src, $expected, $ty, $transform); }}; } - -pub trait FromWasmMemory: Sized { - const SIZE_OF_WASM: usize; - const ALIGN_OF_WASM: usize; - const ACTUAL_WIDTH: usize = if (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM) == 0 { - Self::SIZE_OF_WASM - } else { - Self::SIZE_OF_WASM + (Self::ALIGN_OF_WASM - (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM)) - }; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self; -} - -macro_rules! from_wasm_memory_primitive_decode { - ($type_name:ident) => { - const SIZE_OF_WASM: usize = core::mem::size_of::<$type_name>(); - const ALIGN_OF_WASM: usize = core::mem::align_of::<$type_name>(); - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - use core::mem::MaybeUninit; - - let mut output: MaybeUninit = MaybeUninit::uninit(); - let width = std::mem::size_of::(); - - let ptr = output.as_mut_ptr(); - let raw_ptr = ptr as *mut u8; - let slice = unsafe { std::slice::from_raw_parts_mut(raw_ptr, width) }; - - let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(offset as u32); - let foobar = (ptr.deref(memory, 0, width as u32)).unwrap(); - let wasm_slice = unsafe { std::mem::transmute(foobar) }; - - slice.copy_from_slice(wasm_slice); - - unsafe { output.assume_init() } - } - }; -} - -macro_rules! from_wasm_memory_primitive { - ($($type_name:ident ,)+) => { - $( - impl FromWasmMemory for $type_name { - from_wasm_memory_primitive_decode!($type_name); - } - )* - } -} - -from_wasm_memory_primitive!( - u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder, -); - -impl FromWasmMemory for () { - const SIZE_OF_WASM: usize = 0; - const ALIGN_OF_WASM: usize = 0; - - fn decode(_: &wasmer::Memory, _: u32) -> Self {} -} - -impl FromWasmMemory for RocStr { - const SIZE_OF_WASM: usize = 8; - const ALIGN_OF_WASM: usize = 4; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let bytes = ::decode(memory, offset); - - let length = (bytes >> 32) as u32; - let elements = bytes as u32; - - if length == 0 { - RocStr::default() - } else if (length as i32) < 0 { - // this is a small string - let last_byte = bytes.to_ne_bytes()[7]; - let actual_length = (last_byte ^ 0b1000_0000) as usize; - - let slice = &bytes.to_ne_bytes()[..actual_length as usize]; - RocStr::from_slice(slice) - } else { - // this is a big string - let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(elements); - let foobar = (ptr.deref(memory, 0, length)).unwrap(); - let wasm_slice = unsafe { std::mem::transmute(foobar) }; - - RocStr::from_slice(wasm_slice) - } - } -} - -impl FromWasmMemory for RocList { - const SIZE_OF_WASM: usize = 8; - const ALIGN_OF_WASM: usize = 4; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let bytes = ::decode(memory, offset); - - let length = (bytes >> 32) as u32; - let elements = bytes as u32; - - let mut items = Vec::with_capacity(length as usize); - - for i in 0..length { - let item = ::decode( - memory, - elements + i * ::SIZE_OF_WASM as u32, - ); - items.push(item); - } - - RocList::from_slice(&items) - } -} - -impl FromWasmMemory for &'_ [T] { - const SIZE_OF_WASM: usize = 8; - const ALIGN_OF_WASM: usize = 4; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let bytes = ::decode(memory, offset); - - let length = (bytes >> 32) as u32; - let elements = bytes as u32; - - let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(elements); - let width = ::SIZE_OF_WASM as u32 * length; - let foobar = (ptr.deref(memory, 0, width)).unwrap(); - let wasm_slice = - unsafe { std::slice::from_raw_parts(foobar as *const _ as *const _, length as usize) }; - - wasm_slice - } -} - -impl FromWasmMemory for &'_ T { - const SIZE_OF_WASM: usize = 4; - const ALIGN_OF_WASM: usize = 4; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let elements = ::decode(memory, offset); - - let actual = ::decode(memory, elements); - - let b = Box::new(actual); - - std::boxed::Box::::leak(b) - } -} - -impl FromWasmMemory for [T; N] { - const SIZE_OF_WASM: usize = N * T::SIZE_OF_WASM; - const ALIGN_OF_WASM: usize = T::ALIGN_OF_WASM; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(offset); - let width = ::SIZE_OF_WASM as u32 * N as u32; - let foobar = (ptr.deref(memory, 0, width)).unwrap(); - let wasm_slice: &[T; N] = unsafe { &*(foobar as *const _ as *const [T; N]) }; - - wasm_slice.clone() - } -} - -impl FromWasmMemory for usize { - const SIZE_OF_WASM: usize = 4; - const ALIGN_OF_WASM: usize = 4; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - ::decode(memory, offset) as usize - } -} - -impl FromWasmMemory for (T, U) { - const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM; - const ALIGN_OF_WASM: usize = max2(T::SIZE_OF_WASM, U::SIZE_OF_WASM); - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - assert!( - T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, - "this function does not handle alignment" - ); - - let t = ::decode(memory, offset); - - let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); - - (t, u) - } -} - -const fn max2(a: usize, b: usize) -> usize { - if a > b { - a - } else { - b - } -} - -const fn max3(a: usize, b: usize, c: usize) -> usize { - max2(max2(a, b), c) -} - -impl FromWasmMemory for (T, U, V) { - const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM + V::SIZE_OF_WASM; - const ALIGN_OF_WASM: usize = max3(T::SIZE_OF_WASM, U::SIZE_OF_WASM, V::SIZE_OF_WASM); - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - assert!( - T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, - "this function does not handle alignment" - ); - - assert!( - U::ALIGN_OF_WASM >= V::ALIGN_OF_WASM, - "this function does not handle alignment" - ); - - let t = ::decode(memory, offset); - - let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); - - let v = ::decode( - memory, - offset + T::ACTUAL_WIDTH as u32 + U::ACTUAL_WIDTH as u32, - ); - - (t, u, v) - } -} From b3b2e7de1119bdcf4726b2a7fd3d85b511225345 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 5 Sep 2021 14:09:15 +0200 Subject: [PATCH 12/21] remove faulty instance --- compiler/gen_wasm/src/from_wasm32_memory.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/compiler/gen_wasm/src/from_wasm32_memory.rs b/compiler/gen_wasm/src/from_wasm32_memory.rs index d544488f7b..70f94d3b86 100644 --- a/compiler/gen_wasm/src/from_wasm32_memory.rs +++ b/compiler/gen_wasm/src/from_wasm32_memory.rs @@ -113,26 +113,6 @@ impl FromWasm32Memory for RocList { } } -impl FromWasm32Memory for &'_ [T] { - const SIZE_OF_WASM: usize = 8; - const ALIGN_OF_WASM: usize = 4; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let bytes = ::decode(memory, offset); - - let length = (bytes >> 32) as u32; - let elements = bytes as u32; - - let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(elements); - let width = ::SIZE_OF_WASM as u32 * length; - let foobar = (ptr.deref(memory, 0, width)).unwrap(); - let wasm_slice = - unsafe { std::slice::from_raw_parts(foobar as *const _ as *const _, length as usize) }; - - wasm_slice - } -} - impl FromWasm32Memory for &'_ T { const SIZE_OF_WASM: usize = 4; const ALIGN_OF_WASM: usize = 4; From 71ce267f59b20269f8147859f95236dafa4b299a Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 5 Sep 2021 14:10:13 +0200 Subject: [PATCH 13/21] debug_assert --- compiler/gen_wasm/src/from_wasm32_memory.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/gen_wasm/src/from_wasm32_memory.rs b/compiler/gen_wasm/src/from_wasm32_memory.rs index 70f94d3b86..db59b8ef95 100644 --- a/compiler/gen_wasm/src/from_wasm32_memory.rs +++ b/compiler/gen_wasm/src/from_wasm32_memory.rs @@ -156,7 +156,7 @@ impl FromWasm32Memory for (T, U) { const ALIGN_OF_WASM: usize = max2(T::SIZE_OF_WASM, U::SIZE_OF_WASM); fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - assert!( + debug_assert!( T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, "this function does not handle alignment" ); @@ -186,12 +186,12 @@ impl FromWasm32Me const ALIGN_OF_WASM: usize = max3(T::SIZE_OF_WASM, U::SIZE_OF_WASM, V::SIZE_OF_WASM); fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - assert!( + debug_assert!( T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, "this function does not handle alignment" ); - assert!( + debug_assert!( U::ALIGN_OF_WASM >= V::ALIGN_OF_WASM, "this function does not handle alignment" ); From dbee9dd0ce9f1c336d13aaae634a751b65e13025 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 5 Sep 2021 14:19:21 +0200 Subject: [PATCH 14/21] cleanup --- compiler/test_gen/src/helpers/eval.rs | 41 --------------------------- 1 file changed, 41 deletions(-) diff --git a/compiler/test_gen/src/helpers/eval.rs b/compiler/test_gen/src/helpers/eval.rs index 94c8ac7756..10c770a3d7 100644 --- a/compiler/test_gen/src/helpers/eval.rs +++ b/compiler/test_gen/src/helpers/eval.rs @@ -366,29 +366,6 @@ pub fn helper_wasm<'a>( use std::process::Command; - // Command::new("/opt/wasi-sdk/bin/clang") - - /* - Command::new("zig") - .current_dir(dir_path) - .args(&[ - "cc", - "/home/folkertdev/roc/wasm/libmain.a", - test_a_path.to_str().unwrap(), - "-target", - "wasm32-wasi", - "-o", - test_wasm_path.to_str().unwrap(), - "--sysroot=/opt/wasi-sdk/share/wasi-sysroot/", - "-Xlinker", "--export-dynamic", - // "-Xlinker", "--allow-undefined" - // "--global-cache-dir", - // zig_global_cache_path.to_str().unwrap(), - ]) - .status() - .unwrap(); - */ - Command::new("/home/folkertdev/Downloads/zig-linux-x86_64-0.9.0-dev.848+d5ef5da59/zig") .current_dir(dir_path) .args(&[ @@ -405,24 +382,6 @@ pub fn helper_wasm<'a>( .status() .unwrap(); - /* - Command::new("/home/folkertdev/Downloads/zig-linux-x86_64-0.9.0-dev.848+d5ef5da59/zig") - .current_dir(dir_path) - .args(&[ - "build-lib", - "/home/folkertdev/roc/wasm/libmain.a", - test_a_path.to_str().unwrap(), - "-target", - "wasm32-wasi", - "-dynamic", - "-lc", - // "--global-cache-dir", - // zig_global_cache_path.to_str().unwrap(), - ]) - .status() - .unwrap(); - */ - // now, do wasmer stuff use wasmer::{Function, Instance, Module, Store}; From 068452ef958018d38bc3f597d2012439579944b0 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sun, 5 Sep 2021 14:56:42 +0200 Subject: [PATCH 15/21] reenable i386 tests --- cli/tests/cli_run.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 5f90a93b78..2b075bb660 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -380,9 +380,7 @@ mod cli_run { benchmark.executable_filename, &["--backend=x86_32"], benchmark.expected_ending, - // TODO enable valgrind when CI has 32-bit valgrind support - // benchmark.use_valgrind, - false + benchmark.use_valgrind, ); check_output_with_stdin( @@ -391,9 +389,7 @@ mod cli_run { benchmark.executable_filename, &["--backend=x86_32", "--optimize"], benchmark.expected_ending, - // TODO enable valgrind when CI has 32-bit valgrind support - // benchmark.use_valgrind, - false + benchmark.use_valgrind, ); } )* From 8e7aef314a5641de5405314ae9de07bd64d18199 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 5 Sep 2021 16:36:18 +0200 Subject: [PATCH 16/21] add ListLiteralElement --- compiler/gen_dev/src/lib.rs | 10 ++++-- compiler/gen_llvm/src/llvm/build.rs | 14 ++++++--- compiler/mono/src/alias_analysis.rs | 10 ++++-- compiler/mono/src/borrow.rs | 10 +++++- compiler/mono/src/inc_dec.rs | 31 +++++++++++-------- compiler/mono/src/ir.rs | 48 +++++++++++++++++++++++------ compiler/mono/src/reset_reuse.rs | 16 ++++++++-- 7 files changed, 103 insertions(+), 36 deletions(-) diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index ea6aac119d..d91f6b3ebc 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -8,7 +8,9 @@ use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{ModuleName, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, Symbol}; -use roc_mono::ir::{BranchInfo, CallType, Expr, JoinPointId, Literal, Proc, Stmt}; +use roc_mono::ir::{ + BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Proc, Stmt, +}; use roc_mono::layout::{Builtin, Layout, LayoutIds}; use target_lexicon::Triple; @@ -601,8 +603,10 @@ where self.set_last_seen(*structure, stmt); } Expr::Array { elems, .. } => { - for sym in *elems { - self.set_last_seen(*sym, stmt); + for elem in *elems { + if let ListLiteralElement::Symbol(sym) = elem { + self.set_last_seen(*sym, stmt); + } } } Expr::Reuse { diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 322ec34de6..9694dc92b7 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -51,7 +51,10 @@ use roc_builtins::bitcode; use roc_collections::all::{ImMap, MutMap, MutSet}; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; -use roc_mono::ir::{BranchInfo, CallType, EntryPoint, JoinPointId, ModifyRc, OptLevel, ProcLayout}; +use roc_mono::ir::{ + BranchInfo, CallType, EntryPoint, JoinPointId, ListLiteralElement, ModifyRc, OptLevel, + ProcLayout, +}; use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, UnionLayout}; use target_lexicon::{Architecture, OperatingSystem, Triple}; @@ -2157,7 +2160,7 @@ fn list_literal<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, elem_layout: &Layout<'a>, - elems: &&[Symbol], + elems: &[ListLiteralElement], ) -> BasicValueEnum<'ctx> { let ctx = env.context; let builder = env.builder; @@ -2172,8 +2175,11 @@ fn list_literal<'a, 'ctx, 'env>( }; // Copy the elements from the list literal into the array - for (index, symbol) in elems.iter().enumerate() { - let val = load_symbol(scope, symbol); + for (index, element) in elems.iter().enumerate() { + let val = match element { + ListLiteralElement::Literal(literal) => build_exp_literal(env, elem_layout, literal), + ListLiteralElement::Symbol(symbol) => load_symbol(scope, symbol), + }; let index_val = ctx.i64_type().const_int(index as u64, false); let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 467169b9bb..229ed7cbee 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -9,7 +9,7 @@ use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use std::convert::TryFrom; -use crate::ir::{Call, CallType, Expr, Literal, ModifyRc, Proc, Stmt}; +use crate::ir::{Call, CallType, Expr, ListLiteralElement, Literal, ModifyRc, Proc, Stmt}; use crate::layout::{Builtin, Layout, ListLayout, UnionLayout}; // just using one module for now @@ -1118,8 +1118,12 @@ fn expr_spec<'a>( let mut bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; - for symbol in elems.iter() { - let value_id = env.symbols[symbol]; + for element in elems.iter() { + let value_id = if let ListLiteralElement::Symbol(symbol) = element { + env.symbols[symbol] + } else { + builder.add_make_tuple(block, &[]).unwrap() + }; bag = builder.add_bag_insert(block, bag, value_id)?; } diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 419158288a..89d6ae2446 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -740,7 +740,15 @@ impl<'a> BorrowInfState<'a> { use Expr::*; match e { - Tag { arguments: xs, .. } | Struct(xs) | Array { elems: xs, .. } => { + Array { elems: xs, .. } => { + let xs = Vec::from_iter_in(xs.iter().filter_map(|e| e.to_symbol()), self.arena); + self.own_var(z); + + // if the used symbol is an argument to the current function, + // the function must take it as an owned parameter + self.own_args_if_param(&xs); + } + Tag { arguments: xs, .. } | Struct(xs) => { self.own_var(z); // if the used symbol is an argument to the current function, diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index e8fc6476a2..9622355cc7 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -95,11 +95,11 @@ pub fn occurring_variables_expr(expr: &Expr<'_>, result: &mut MutSet) { Call(call) => occurring_variables_call(call, result), - Tag { arguments, .. } - | Struct(arguments) - | Array { + Array { elems: arguments, .. - } => { + } => result.extend(arguments.iter().filter_map(|e| e.to_symbol())), + + Tag { arguments, .. } | Struct(arguments) => { result.extend(arguments.iter().copied()); } Reuse { @@ -733,14 +733,21 @@ impl<'a> Context<'a> { live_vars.remove(&z); let new_b = match v { - Reuse { arguments: ys, .. } - | Tag { arguments: ys, .. } - | Struct(ys) - | Array { elems: ys, .. } => self.add_inc_before_consume_all( - ys, - self.arena.alloc(Stmt::Let(z, v, l, b)), - b_live_vars, - ), + Reuse { arguments: ys, .. } | Tag { arguments: ys, .. } | Struct(ys) => self + .add_inc_before_consume_all( + ys, + self.arena.alloc(Stmt::Let(z, v, l, b)), + b_live_vars, + ), + + Array { elems, .. } => { + let ys = Vec::from_iter_in(elems.iter().filter_map(|e| e.to_symbol()), self.arena); + self.add_inc_before_consume_all( + &ys, + self.arena.alloc(Stmt::Let(z, v, l, b)), + b_live_vars, + ) + } Call(crate::ir::Call { call_type, diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index daade748e6..ec290207e3 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1020,7 +1020,7 @@ impl ModifyRc { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum Literal<'a> { // Literals Int(i128), @@ -1038,6 +1038,21 @@ pub enum Literal<'a> { Byte(u8), } +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ListLiteralElement<'a> { + Literal(Literal<'a>), + Symbol(Symbol), +} + +impl<'a> ListLiteralElement<'a> { + pub fn to_symbol(&self) -> Option { + match self { + Self::Symbol(s) => Some(*s), + _ => None, + } + } +} + #[derive(Clone, Debug, PartialEq)] pub struct Call<'a> { pub call_type: CallType<'a>, @@ -1177,7 +1192,7 @@ pub enum Expr<'a> { Array { elem_layout: Layout<'a>, - elems: &'a [Symbol], + elems: &'a [ListLiteralElement<'a>], }, EmptyArray, @@ -1317,7 +1332,10 @@ impl<'a> Expr<'a> { .append(alloc.text("}")) } Array { elems, .. } => { - let it = elems.iter().map(|s| symbol_to_doc(alloc, *s)); + let it = elems.iter().map(|e| match e { + ListLiteralElement::Literal(l) => l.to_doc(alloc), + ListLiteralElement::Symbol(s) => symbol_to_doc(alloc, *s), + }); alloc .text("Array [") @@ -3423,8 +3441,12 @@ pub fn with_hole<'a>( loc_elems, } => { let mut arg_symbols = Vec::with_capacity_in(loc_elems.len(), env.arena); + let mut elements = Vec::with_capacity_in(loc_elems.len(), env.arena); for arg_expr in loc_elems.iter() { - arg_symbols.push(possible_reuse_symbol(env, procs, &arg_expr.value)); + let symbol = possible_reuse_symbol(env, procs, &arg_expr.value); + + elements.push(ListLiteralElement::Symbol(symbol)); + arg_symbols.push(symbol) } let arg_symbols = arg_symbols.into_bump_slice(); @@ -3434,7 +3456,7 @@ pub fn with_hole<'a>( let expr = Expr::Array { elem_layout, - elems: arg_symbols, + elems: elements.into_bump_slice(), }; let stmt = Stmt::Let( @@ -5485,11 +5507,17 @@ fn substitute_in_expr<'a>( } => { let mut did_change = false; let new_args = Vec::from_iter_in( - args.iter().map(|s| match substitute(subs, *s) { - None => *s, - Some(s) => { - did_change = true; - s + args.iter().map(|e| { + if let ListLiteralElement::Symbol(s) = e { + match substitute(subs, *s) { + None => ListLiteralElement::Symbol(*s), + Some(s) => { + did_change = true; + ListLiteralElement::Symbol(s) + } + } + } else { + *e } }), arena, diff --git a/compiler/mono/src/reset_reuse.rs b/compiler/mono/src/reset_reuse.rs index 41fdc63c63..490314c885 100644 --- a/compiler/mono/src/reset_reuse.rs +++ b/compiler/mono/src/reset_reuse.rs @@ -1,5 +1,5 @@ use crate::inc_dec::{collect_stmt, occurring_variables_expr, JPLiveVarMap, LiveVarSet}; -use crate::ir::{BranchInfo, Call, Expr, Proc, Stmt}; +use crate::ir::{BranchInfo, Call, Expr, ListLiteralElement, Proc, Stmt}; use crate::layout::{Layout, UnionLayout}; use bumpalo::collections::Vec; use bumpalo::Bump; @@ -564,8 +564,18 @@ fn has_live_var_expr<'a>(expr: &'a Expr<'a>, needle: Symbol) -> bool { match expr { Expr::Literal(_) => false, Expr::Call(call) => has_live_var_call(call, needle), - Expr::Array { elems: fields, .. } - | Expr::Tag { + Expr::Array { elems: fields, .. } => { + for element in fields.iter() { + if let ListLiteralElement::Symbol(s) = element { + if *s == needle { + return true; + } + } + } + + false + } + Expr::Tag { arguments: fields, .. } | Expr::Struct(fields) => fields.iter().any(|s| *s == needle), From b97c2d5d84f8c1186d5bd5e3c243140c7fe015c9 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 5 Sep 2021 17:06:02 +0200 Subject: [PATCH 17/21] store literals in lists directly --- compiler/mono/src/ir.rs | 75 +++++++++++++++++-- compiler/test_mono/generated/fst.txt | 10 +-- compiler/test_mono/generated/ir_int_add.txt | 4 +- .../let_with_record_pattern_list.txt | 5 +- compiler/test_mono/generated/list_append.txt | 3 +- .../generated/list_append_closure.txt | 4 +- .../generated/list_cannot_update_inplace.txt | 31 ++++---- compiler/test_mono/generated/list_get.txt | 5 +- compiler/test_mono/generated/list_len.txt | 8 +- .../generated/list_pass_to_function.txt | 5 +- compiler/test_mono/generated/mk_pair_of.txt | 5 +- .../test_mono/generated/quicksort_swap.txt | 4 +- compiler/test_mono/generated/rigids.txt | 3 +- 13 files changed, 97 insertions(+), 65 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index ec290207e3..b39bb8b722 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -2746,6 +2746,63 @@ macro_rules! match_on_closure_argument { }}; } +fn try_make_literal<'a>( + env: &mut Env<'a, '_>, + can_expr: &roc_can::expr::Expr, +) -> Option> { + use roc_can::expr::Expr::*; + + match can_expr { + Int(_, precision, _, int) => { + match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *precision, false) { + IntOrFloat::SignedIntType(_) | IntOrFloat::UnsignedIntType(_) => { + Some(Literal::Int(*int)) + } + _ => unreachable!("unexpected float precision for integer"), + } + } + + Float(_, precision, float_str, float) => { + match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *precision, true) { + IntOrFloat::BinaryFloatType(_) => Some(Literal::Float(*float)), + IntOrFloat::DecimalFloatType => { + let dec = match RocDec::from_str(float_str) { + Some(d) => d, + None => panic!("Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", float_str), + }; + + Some(Literal::Decimal(dec)) + } + _ => unreachable!("unexpected float precision for integer"), + } + } + + // TODO investigate lifetime trouble + // Str(string) => Some(Literal::Str(env.arena.alloc(string))), + Num(var, num_str, num) => { + // first figure out what kind of number this is + match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, false) { + IntOrFloat::SignedIntType(_) | IntOrFloat::UnsignedIntType(_) => { + Some(Literal::Int((*num).into())) + } + IntOrFloat::BinaryFloatType(_) => Some(Literal::Float(*num as f64)), + IntOrFloat::DecimalFloatType => { + let dec = match RocDec::from_str(num_str) { + Some(d) => d, + None => panic!( + r"Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", + num_str + ), + }; + + Some(Literal::Decimal(dec)) + } + } + } + _ => None, + } +} + pub fn with_hole<'a>( env: &mut Env<'a, '_>, can_expr: roc_can::expr::Expr, @@ -3442,11 +3499,19 @@ pub fn with_hole<'a>( } => { let mut arg_symbols = Vec::with_capacity_in(loc_elems.len(), env.arena); let mut elements = Vec::with_capacity_in(loc_elems.len(), env.arena); - for arg_expr in loc_elems.iter() { - let symbol = possible_reuse_symbol(env, procs, &arg_expr.value); - elements.push(ListLiteralElement::Symbol(symbol)); - arg_symbols.push(symbol) + let mut symbol_exprs = Vec::with_capacity_in(loc_elems.len(), env.arena); + + for arg_expr in loc_elems.into_iter() { + if let Some(literal) = try_make_literal(env, &arg_expr.value) { + elements.push(ListLiteralElement::Literal(literal)); + } else { + let symbol = possible_reuse_symbol(env, procs, &arg_expr.value); + + elements.push(ListLiteralElement::Symbol(symbol)); + arg_symbols.push(symbol); + symbol_exprs.push(arg_expr); + } } let arg_symbols = arg_symbols.into_bump_slice(); @@ -3466,7 +3531,7 @@ pub fn with_hole<'a>( hole, ); - let iter = loc_elems + let iter = symbol_exprs .into_iter() .rev() .map(|e| (elem_var, e)) diff --git a/compiler/test_mono/generated/fst.txt b/compiler/test_mono/generated/fst.txt index 13e6954e29..fe81173aa5 100644 --- a/compiler/test_mono/generated/fst.txt +++ b/compiler/test_mono/generated/fst.txt @@ -3,14 +3,8 @@ procedure Test.1 (Test.2, Test.3): ret Test.2; procedure Test.0 (): - let Test.11 = 1i64; - let Test.12 = 2i64; - let Test.13 = 3i64; - let Test.5 = Array [Test.11, Test.12, Test.13]; - let Test.8 = 3i64; - let Test.9 = 2i64; - let Test.10 = 1i64; - let Test.6 = Array [Test.8, Test.9, Test.10]; + let Test.5 = Array [1i64, 2i64, 3i64]; + let Test.6 = Array [3i64, 2i64, 1i64]; let Test.4 = CallByName Test.1 Test.5 Test.6; dec Test.6; dec Test.5; diff --git a/compiler/test_mono/generated/ir_int_add.txt b/compiler/test_mono/generated/ir_int_add.txt index a4168a5109..0da2caed32 100644 --- a/compiler/test_mono/generated/ir_int_add.txt +++ b/compiler/test_mono/generated/ir_int_add.txt @@ -7,9 +7,7 @@ procedure Num.24 (#Attr.2, #Attr.3): ret Test.5; procedure Test.0 (): - let Test.11 = 1i64; - let Test.12 = 2i64; - let Test.1 = Array [Test.11, Test.12]; + let Test.1 = Array [1i64, 2i64]; let Test.9 = 5i64; let Test.10 = 4i64; let Test.7 = CallByName Num.24 Test.9 Test.10; diff --git a/compiler/test_mono/generated/let_with_record_pattern_list.txt b/compiler/test_mono/generated/let_with_record_pattern_list.txt index e2ed3831d0..91f6ea8249 100644 --- a/compiler/test_mono/generated/let_with_record_pattern_list.txt +++ b/compiler/test_mono/generated/let_with_record_pattern_list.txt @@ -1,8 +1,5 @@ procedure Test.0 (): - let Test.6 = 1i64; - let Test.7 = 3i64; - let Test.8 = 4i64; - let Test.4 = Array [Test.6, Test.7, Test.8]; + let Test.4 = Array [1i64, 3i64, 4i64]; let Test.5 = 3.14f64; let Test.3 = Struct {Test.4, Test.5}; let Test.1 = StructAtIndex 0 Test.3; diff --git a/compiler/test_mono/generated/list_append.txt b/compiler/test_mono/generated/list_append.txt index 49c502afd3..8cb3ba9758 100644 --- a/compiler/test_mono/generated/list_append.txt +++ b/compiler/test_mono/generated/list_append.txt @@ -3,8 +3,7 @@ procedure List.5 (#Attr.2, #Attr.3): ret Test.4; procedure Test.0 (): - let Test.5 = 1i64; - let Test.2 = Array [Test.5]; + let Test.2 = Array [1i64]; let Test.3 = 2i64; let Test.1 = CallByName List.5 Test.2 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/list_append_closure.txt b/compiler/test_mono/generated/list_append_closure.txt index cd33ef5754..cffdbe7e48 100644 --- a/compiler/test_mono/generated/list_append_closure.txt +++ b/compiler/test_mono/generated/list_append_closure.txt @@ -8,8 +8,6 @@ procedure Test.1 (Test.2): ret Test.5; procedure Test.0 (): - let Test.8 = 1i64; - let Test.9 = 2i64; - let Test.4 = Array [Test.8, Test.9]; + let Test.4 = Array [1i64, 2i64]; let Test.3 = CallByName Test.1 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/list_cannot_update_inplace.txt b/compiler/test_mono/generated/list_cannot_update_inplace.txt index 0a29c418b8..88039ce8a0 100644 --- a/compiler/test_mono/generated/list_cannot_update_inplace.txt +++ b/compiler/test_mono/generated/list_cannot_update_inplace.txt @@ -1,9 +1,9 @@ procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.22 = lowlevel ListLen #Attr.2; - let Test.20 = lowlevel NumLt #Attr.3 Test.22; - if Test.20 then - let Test.21 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; - ret Test.21; + let Test.19 = lowlevel ListLen #Attr.2; + let Test.17 = lowlevel NumLt #Attr.3 Test.19; + if Test.17 then + let Test.18 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + ret Test.18; else ret #Attr.2; @@ -16,23 +16,20 @@ procedure Num.24 (#Attr.2, #Attr.3): ret Test.7; procedure Test.1 (): - let Test.11 = 1i64; - let Test.12 = 2i64; - let Test.13 = 3i64; - let Test.10 = Array [Test.11, Test.12, Test.13]; + let Test.10 = Array [1i64, 2i64, 3i64]; ret Test.10; procedure Test.2 (Test.3): - let Test.17 = 0i64; - let Test.18 = 0i64; - let Test.16 = CallByName List.4 Test.3 Test.17 Test.18; - ret Test.16; + let Test.14 = 0i64; + let Test.15 = 0i64; + let Test.13 = CallByName List.4 Test.3 Test.14 Test.15; + ret Test.13; procedure Test.0 (): - let Test.15 = CallByName Test.1; - let Test.14 = CallByName Test.2 Test.15; - let Test.5 = CallByName List.7 Test.14; - dec Test.14; + let Test.12 = CallByName Test.1; + let Test.11 = CallByName Test.2 Test.12; + let Test.5 = CallByName List.7 Test.11; + dec Test.11; let Test.8 = CallByName Test.1; let Test.6 = CallByName List.7 Test.8; dec Test.8; diff --git a/compiler/test_mono/generated/list_get.txt b/compiler/test_mono/generated/list_get.txt index e2da3a0e9b..49280478d1 100644 --- a/compiler/test_mono/generated/list_get.txt +++ b/compiler/test_mono/generated/list_get.txt @@ -11,10 +11,7 @@ procedure List.3 (#Attr.2, #Attr.3): ret Test.8; procedure Test.1 (Test.2): - let Test.14 = 1i64; - let Test.15 = 2i64; - let Test.16 = 3i64; - let Test.6 = Array [Test.14, Test.15, Test.16]; + let Test.6 = Array [1i64, 2i64, 3i64]; let Test.7 = 0i64; let Test.5 = CallByName List.3 Test.6 Test.7; dec Test.6; diff --git a/compiler/test_mono/generated/list_len.txt b/compiler/test_mono/generated/list_len.txt index d5cd03a2b9..3cc3d980bd 100644 --- a/compiler/test_mono/generated/list_len.txt +++ b/compiler/test_mono/generated/list_len.txt @@ -11,12 +11,8 @@ procedure Num.24 (#Attr.2, #Attr.3): ret Test.6; procedure Test.0 (): - let Test.10 = 1i64; - let Test.11 = 2i64; - let Test.12 = 3i64; - let Test.1 = Array [Test.10, Test.11, Test.12]; - let Test.9 = 1f64; - let Test.2 = Array [Test.9]; + let Test.1 = Array [1i64, 2i64, 3i64]; + let Test.2 = Array [1f64]; let Test.4 = CallByName List.7 Test.1; dec Test.1; let Test.5 = CallByName List.7 Test.2; diff --git a/compiler/test_mono/generated/list_pass_to_function.txt b/compiler/test_mono/generated/list_pass_to_function.txt index b33881bde8..c2d21a9d87 100644 --- a/compiler/test_mono/generated/list_pass_to_function.txt +++ b/compiler/test_mono/generated/list_pass_to_function.txt @@ -14,9 +14,6 @@ procedure Test.2 (Test.3): ret Test.5; procedure Test.0 (): - let Test.12 = 1i64; - let Test.13 = 2i64; - let Test.14 = 3i64; - let Test.1 = Array [Test.12, Test.13, Test.14]; + let Test.1 = Array [1i64, 2i64, 3i64]; let Test.4 = CallByName Test.2 Test.1; ret Test.4; diff --git a/compiler/test_mono/generated/mk_pair_of.txt b/compiler/test_mono/generated/mk_pair_of.txt index ae4932f71a..a223ddd1c2 100644 --- a/compiler/test_mono/generated/mk_pair_of.txt +++ b/compiler/test_mono/generated/mk_pair_of.txt @@ -4,9 +4,6 @@ procedure Test.1 (Test.2): ret Test.6; procedure Test.0 (): - let Test.7 = 1i64; - let Test.8 = 2i64; - let Test.9 = 3i64; - let Test.5 = Array [Test.7, Test.8, Test.9]; + let Test.5 = Array [1i64, 2i64, 3i64]; let Test.4 = CallByName Test.1 Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/quicksort_swap.txt b/compiler/test_mono/generated/quicksort_swap.txt index 4845bb7c58..5bb60a4c11 100644 --- a/compiler/test_mono/generated/quicksort_swap.txt +++ b/compiler/test_mono/generated/quicksort_swap.txt @@ -56,8 +56,6 @@ procedure Test.1 (Test.2): jump Test.26; procedure Test.0 (): - let Test.39 = 1i64; - let Test.40 = 2i64; - let Test.7 = Array [Test.39, Test.40]; + let Test.7 = Array [1i64, 2i64]; let Test.6 = CallByName Test.1 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/rigids.txt b/compiler/test_mono/generated/rigids.txt index d6a146ba1b..583b0a0182 100644 --- a/compiler/test_mono/generated/rigids.txt +++ b/compiler/test_mono/generated/rigids.txt @@ -54,7 +54,6 @@ procedure Test.1 (Test.2, Test.3, Test.4): procedure Test.0 (): let Test.10 = 0i64; let Test.11 = 0i64; - let Test.40 = 1i64; - let Test.12 = Array [Test.40]; + let Test.12 = Array [1i64]; let Test.9 = CallByName Test.1 Test.10 Test.11 Test.12; ret Test.9; From 12bc34f451b7bb31b21aace2f0746ee7f3fe540f Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 5 Sep 2021 17:07:00 +0200 Subject: [PATCH 18/21] clippy --- compiler/gen_dev/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index d91f6b3ebc..808d2caf9b 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -152,7 +152,7 @@ where match expr { Expr::Literal(lit) => { if self.env().lazy_literals { - self.literal_map().insert(*sym, lit.clone()); + self.literal_map().insert(*sym, *lit); } else { self.load_literal(sym, lit)?; } From da966ed506dc74169851b6a503526ecf932880df Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 5 Sep 2021 19:23:46 +0200 Subject: [PATCH 19/21] store integer lists in the constants section --- compiler/gen_llvm/src/llvm/build.rs | 97 ++++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 10 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 9694dc92b7..3ce075e336 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -2165,8 +2165,9 @@ fn list_literal<'a, 'ctx, 'env>( let ctx = env.context; let builder = env.builder; - let len_u64 = elems.len() as u64; + let element_type = basic_type_from_layout(env, elem_layout); + let len_u64 = elems.len() as u64; let ptr = { let len_type = env.ptr_int(); let len = len_type.const_int(len_u64, false); @@ -2174,16 +2175,92 @@ fn list_literal<'a, 'ctx, 'env>( allocate_list(env, elem_layout, len) }; - // Copy the elements from the list literal into the array - for (index, element) in elems.iter().enumerate() { - let val = match element { - ListLiteralElement::Literal(literal) => build_exp_literal(env, elem_layout, literal), - ListLiteralElement::Symbol(symbol) => load_symbol(scope, symbol), - }; - let index_val = ctx.i64_type().const_int(index as u64, false); - let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; + if element_type.is_int_type() { + let element_type = element_type.into_int_type(); + let size = elems.len() * elem_layout.stack_size(env.ptr_bytes) as usize; + let alignment = elem_layout + .alignment_bytes(env.ptr_bytes) + .max(env.ptr_bytes); - builder.build_store(elem_ptr, val); + // set up a global that contains all the literal elements of the array + // any variables or expressions are represented as `undef` + let global = { + let mut bytes = Vec::with_capacity_in(size, env.arena); + + // // insert NULL bytes for the refcount + // for _ in 0..env.ptr_bytes { + // bytes.push(env.context.i8_type().const_zero()); + // } + + // Copy the elements from the list literal into the array + for element in elems.iter() { + match element { + ListLiteralElement::Literal(literal) => { + let val = build_exp_literal(env, elem_layout, literal); + bytes.push(val.into_int_value()); + } + ListLiteralElement::Symbol(_) => { + let val = element_type.get_undef(); + bytes.push(val); + } + }; + } + + // use None for the address space (e.g. Const does not work) + let typ = element_type.array_type(bytes.len() as u32); + let global = env.module.add_global(typ, None, "roc__list_literal"); + + global.set_initializer(&element_type.const_array(bytes.into_bump_slice())); + + global.set_constant(true); + global.set_alignment(alignment); + global.set_unnamed_addr(true); + global.set_linkage(inkwell::module::Linkage::Private); + + global + }; + + // at runtime, copy the elements from the constant section into the heap + env.builder + .build_memcpy( + ptr, + alignment, + global.as_pointer_value(), + alignment, + env.ptr_int().const_int(size as _, false), + ) + .unwrap(); + + // Copy the elements from the list literal into the array + for (index, element) in elems.iter().enumerate() { + match element { + ListLiteralElement::Literal(_) => { + // do nothing, it's copied in already + } + ListLiteralElement::Symbol(symbol) => { + let val = load_symbol(scope, symbol); + let index_val = ctx.i64_type().const_int(index as u64, false); + let elem_ptr = + unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; + + builder.build_store(elem_ptr, val); + } + }; + } + } else { + // Copy the elements from the list literal into the array + for (index, element) in elems.iter().enumerate() { + let val = match element { + ListLiteralElement::Literal(literal) => { + build_exp_literal(env, elem_layout, literal) + } + ListLiteralElement::Symbol(symbol) => load_symbol(scope, symbol), + }; + let index_val = ctx.i64_type().const_int(index as u64, false); + let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; + + builder.build_store(elem_ptr, val); + } } let u8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic); From 6201ac5e6bc7ca443969e8292ced1820ca020623 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 5 Sep 2021 19:53:44 +0200 Subject: [PATCH 20/21] if all constants, store list + refcount in constants section --- compiler/gen_llvm/src/llvm/build.rs | 112 ++++++++++++++++++---------- 1 file changed, 72 insertions(+), 40 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 3ce075e336..d6f504044d 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -2168,29 +2168,28 @@ fn list_literal<'a, 'ctx, 'env>( let element_type = basic_type_from_layout(env, elem_layout); let len_u64 = elems.len() as u64; - let ptr = { - let len_type = env.ptr_int(); - let len = len_type.const_int(len_u64, false); - - allocate_list(env, elem_layout, len) - }; + let ptr; if element_type.is_int_type() { let element_type = element_type.into_int_type(); - let size = elems.len() * elem_layout.stack_size(env.ptr_bytes) as usize; + let element_width = elem_layout.stack_size(env.ptr_bytes); + let size = elems.len() * element_width as usize; let alignment = elem_layout .alignment_bytes(env.ptr_bytes) .max(env.ptr_bytes); + let mut is_all_constant = true; + let zero_elements = (env.ptr_bytes as f64 / element_width as f64).ceil() as usize; + // set up a global that contains all the literal elements of the array // any variables or expressions are represented as `undef` let global = { let mut bytes = Vec::with_capacity_in(size, env.arena); - // // insert NULL bytes for the refcount - // for _ in 0..env.ptr_bytes { - // bytes.push(env.context.i8_type().const_zero()); - // } + // insert NULL bytes for the refcount + for _ in 0..zero_elements { + bytes.push(element_type.const_zero()); + } // Copy the elements from the list literal into the array for element in elems.iter() { @@ -2200,54 +2199,87 @@ fn list_literal<'a, 'ctx, 'env>( bytes.push(val.into_int_value()); } ListLiteralElement::Symbol(_) => { + is_all_constant = false; + let val = element_type.get_undef(); bytes.push(val); } }; } - // use None for the address space (e.g. Const does not work) - let typ = element_type.array_type(bytes.len() as u32); - let global = env.module.add_global(typ, None, "roc__list_literal"); + let const_elements = if is_all_constant { + bytes.into_bump_slice() + } else { + &bytes[zero_elements..] + }; - global.set_initializer(&element_type.const_array(bytes.into_bump_slice())); + // use None for the address space (e.g. Const does not work) + let typ = element_type.array_type(const_elements.len() as u32); + let global = env.module.add_global(typ, None, "roc__list_literal"); global.set_constant(true); global.set_alignment(alignment); global.set_unnamed_addr(true); global.set_linkage(inkwell::module::Linkage::Private); - global + global.set_initializer(&element_type.const_array(const_elements)); + global.as_pointer_value() }; - // at runtime, copy the elements from the constant section into the heap - env.builder - .build_memcpy( - ptr, - alignment, - global.as_pointer_value(), - alignment, - env.ptr_int().const_int(size as _, false), - ) - .unwrap(); + let len_type = env.ptr_int(); + let len = len_type.const_int(len_u64, false); - // Copy the elements from the list literal into the array - for (index, element) in elems.iter().enumerate() { - match element { - ListLiteralElement::Literal(_) => { - // do nothing, it's copied in already - } - ListLiteralElement::Symbol(symbol) => { - let val = load_symbol(scope, symbol); - let index_val = ctx.i64_type().const_int(index as u64, false); - let elem_ptr = - unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; - - builder.build_store(elem_ptr, val); - } + ptr = if is_all_constant { + let zero = env.ptr_int().const_zero(); + let offset = env.ptr_int().const_int(zero_elements as _, false); + let first_element_pointer = unsafe { + env.builder + .build_in_bounds_gep(global, &[zero, offset], "first_element_pointer") }; + + // store_list(env, first_element_pointer, len) + first_element_pointer + } else { + let ptr = allocate_list(env, elem_layout, len); + + // at runtime, copy the elements from the constant section into the heap + env.builder + .build_memcpy( + ptr, + alignment, + global, + alignment, + env.ptr_int().const_int(size as _, false), + ) + .unwrap(); + + // Copy the elements from the list literal into the array + for (index, element) in elems.iter().enumerate() { + match element { + ListLiteralElement::Literal(_) => { + // do nothing, value is already copied from constants section + } + ListLiteralElement::Symbol(symbol) => { + let val = load_symbol(scope, symbol); + let index_val = ctx.i64_type().const_int(index as u64, false); + let elem_ptr = + unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; + + builder.build_store(elem_ptr, val); + } + }; + } + + ptr } } else { + ptr = { + let len_type = env.ptr_int(); + let len = len_type.const_int(len_u64, false); + + allocate_list(env, elem_layout, len) + }; + // Copy the elements from the list literal into the array for (index, element) in elems.iter().enumerate() { let val = match element { From 05f56a34c753474246a1ee81200537ba0e767208 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 5 Sep 2021 21:22:34 +0200 Subject: [PATCH 21/21] cleanup --- compiler/gen_llvm/src/llvm/build.rs | 116 +++++++++++----------------- 1 file changed, 44 insertions(+), 72 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index d6f504044d..ebf0ecbc29 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -2167,13 +2167,13 @@ fn list_literal<'a, 'ctx, 'env>( let element_type = basic_type_from_layout(env, elem_layout); - let len_u64 = elems.len() as u64; - let ptr; + let list_length = elems.len(); + let list_length_intval = env.ptr_int().const_int(list_length as _, false); if element_type.is_int_type() { let element_type = element_type.into_int_type(); let element_width = elem_layout.stack_size(env.ptr_bytes); - let size = elems.len() * element_width as usize; + let size = list_length * element_width as usize; let alignment = elem_layout .alignment_bytes(env.ptr_bytes) .max(env.ptr_bytes); @@ -2181,36 +2181,48 @@ fn list_literal<'a, 'ctx, 'env>( let mut is_all_constant = true; let zero_elements = (env.ptr_bytes as f64 / element_width as f64).ceil() as usize; + // runtime-evaluated elements + let mut runtime_evaluated_elements = Vec::with_capacity_in(list_length, env.arena); + // set up a global that contains all the literal elements of the array // any variables or expressions are represented as `undef` let global = { - let mut bytes = Vec::with_capacity_in(size, env.arena); + let mut global_elements = Vec::with_capacity_in(list_length, env.arena); // insert NULL bytes for the refcount + // these elements are (dropped again if the list contains non-constants) for _ in 0..zero_elements { - bytes.push(element_type.const_zero()); + global_elements.push(element_type.const_zero()); } // Copy the elements from the list literal into the array - for element in elems.iter() { + for (index, element) in elems.iter().enumerate() { match element { ListLiteralElement::Literal(literal) => { let val = build_exp_literal(env, elem_layout, literal); - bytes.push(val.into_int_value()); + global_elements.push(val.into_int_value()); } - ListLiteralElement::Symbol(_) => { - is_all_constant = false; + ListLiteralElement::Symbol(symbol) => { + let val = load_symbol(scope, symbol); + let intval = val.into_int_value(); - let val = element_type.get_undef(); - bytes.push(val); + if intval.is_const() { + global_elements.push(intval); + } else { + is_all_constant = false; + + runtime_evaluated_elements.push((index, val)); + + global_elements.push(element_type.get_undef()); + } } }; } let const_elements = if is_all_constant { - bytes.into_bump_slice() + global_elements.into_bump_slice() } else { - &bytes[zero_elements..] + &global_elements[zero_elements..] }; // use None for the address space (e.g. Const does not work) @@ -2226,23 +2238,24 @@ fn list_literal<'a, 'ctx, 'env>( global.as_pointer_value() }; - let len_type = env.ptr_int(); - let len = len_type.const_int(len_u64, false); - - ptr = if is_all_constant { + if is_all_constant { + // all elements are constants, so we can use the memory in the constants section directly + // here we make a pointer to the first actual element (skipping the 0 bytes that + // represent the refcount) let zero = env.ptr_int().const_zero(); let offset = env.ptr_int().const_int(zero_elements as _, false); - let first_element_pointer = unsafe { + + let ptr = unsafe { env.builder .build_in_bounds_gep(global, &[zero, offset], "first_element_pointer") }; - // store_list(env, first_element_pointer, len) - first_element_pointer + super::build_list::store_list(env, ptr, list_length_intval) } else { - let ptr = allocate_list(env, elem_layout, len); + // some of our elements are non-constant, so we must allocate space on the heap + let ptr = allocate_list(env, elem_layout, list_length_intval); - // at runtime, copy the elements from the constant section into the heap + // then, copy the relevant segment from the constant section into the heap env.builder .build_memcpy( ptr, @@ -2253,32 +2266,18 @@ fn list_literal<'a, 'ctx, 'env>( ) .unwrap(); - // Copy the elements from the list literal into the array - for (index, element) in elems.iter().enumerate() { - match element { - ListLiteralElement::Literal(_) => { - // do nothing, value is already copied from constants section - } - ListLiteralElement::Symbol(symbol) => { - let val = load_symbol(scope, symbol); - let index_val = ctx.i64_type().const_int(index as u64, false); - let elem_ptr = - unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; + // then replace the `undef`s with the values that we evaluate at runtime + for (index, val) in runtime_evaluated_elements { + let index_val = ctx.i64_type().const_int(index as u64, false); + let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; - builder.build_store(elem_ptr, val); - } - }; + builder.build_store(elem_ptr, val); } - ptr + super::build_list::store_list(env, ptr, list_length_intval) } } else { - ptr = { - let len_type = env.ptr_int(); - let len = len_type.const_int(len_u64, false); - - allocate_list(env, elem_layout, len) - }; + let ptr = allocate_list(env, elem_layout, list_length_intval); // Copy the elements from the list literal into the array for (index, element) in elems.iter().enumerate() { @@ -2293,36 +2292,9 @@ fn list_literal<'a, 'ctx, 'env>( builder.build_store(elem_ptr, val); } + + super::build_list::store_list(env, ptr, list_length_intval) } - - let u8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic); - let generic_ptr = builder.build_bitcast(ptr, u8_ptr_type, "to_generic_ptr"); - - let struct_type = super::convert::zig_list_type(env); - let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false)); - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - generic_ptr, - Builtin::WRAPPER_PTR, - "insert_ptr_list_literal", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") - .unwrap(); - - // Bitcast to an array of raw bytes - builder.build_bitcast( - struct_val.into_struct_value(), - super::convert::zig_list_type(env), - "cast_collection", - ) } fn decrement_with_size_check<'a, 'ctx, 'env>(