mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 08:11:12 +00:00
Merge branch 'trunk' into linker
This commit is contained in:
commit
7408c152b7
43 changed files with 3982 additions and 878 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -11,7 +11,7 @@ env:
|
|||
jobs:
|
||||
build-fmt-clippy-test:
|
||||
name: fmt, clippy, test --release
|
||||
runs-on: [self-hosted]
|
||||
runs-on: [self-hosted, i5-4690K]
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
|
688
Cargo.lock
generated
688
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -20,6 +20,7 @@ members = [
|
|||
"compiler/load",
|
||||
"compiler/gen_llvm",
|
||||
"compiler/gen_dev",
|
||||
"compiler/gen_wasm",
|
||||
"compiler/build",
|
||||
"compiler/arena_pool",
|
||||
"compiler/test_gen",
|
||||
|
|
17
Earthfile
17
Earthfile
|
@ -1,4 +1,4 @@
|
|||
FROM rust:1.54-slim-buster
|
||||
FROM rust:1.54-slim-bullseye
|
||||
WORKDIR /earthbuild
|
||||
|
||||
prep-debian:
|
||||
|
@ -27,20 +27,15 @@ install-zig-llvm-valgrind-clippy-rustfmt:
|
|||
RUN ln -s /usr/bin/lld-12 /usr/bin/ld.lld
|
||||
ENV RUSTFLAGS="-C link-arg=-fuse-ld=lld -C target-cpu=native"
|
||||
# valgrind
|
||||
RUN apt -y install autotools-dev cmake automake libc6-dbg
|
||||
RUN wget https://sourceware.org/pub/valgrind/valgrind-3.16.1.tar.bz2
|
||||
RUN tar -xf valgrind-3.16.1.tar.bz2
|
||||
# need to cd every time, every command starts at WORKDIR
|
||||
RUN cd valgrind-3.16.1 && ./autogen.sh
|
||||
RUN cd valgrind-3.16.1 && ./configure --disable-dependency-tracking
|
||||
RUN cd valgrind-3.16.1 && make -j`nproc`
|
||||
RUN cd valgrind-3.16.1 && make install
|
||||
RUN apt -y install valgrind
|
||||
# clippy
|
||||
RUN rustup component add clippy
|
||||
# rustfmt
|
||||
RUN rustup component add rustfmt
|
||||
# criterion
|
||||
RUN cargo install cargo-criterion
|
||||
# wasm
|
||||
RUN apt -y install libxkbcommon-dev
|
||||
# sccache
|
||||
RUN apt -y install libssl-dev
|
||||
RUN cargo install sccache
|
||||
|
@ -82,6 +77,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 -- --backend=x86_32 examples/benchmarks/NQueens.roc
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --release --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
|
||||
|
||||
verify-no-git-changes:
|
||||
FROM +test-rust
|
||||
|
|
|
@ -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.)
|
||||
|
|
|
@ -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],
|
||||
|
@ -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() {
|
||||
|
@ -315,8 +315,8 @@ mod cli_run {
|
|||
|
||||
)*
|
||||
|
||||
#[cfg(feature = "wasm-cli-run")]
|
||||
mod wasm {
|
||||
#[cfg(feature = "wasm32-cli-run")]
|
||||
mod wasm32 {
|
||||
use super::*;
|
||||
$(
|
||||
#[test]
|
||||
|
@ -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};
|
||||
|
|
|
@ -271,7 +271,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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -175,13 +175,26 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum SymbolStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
|
||||
GeneralReg(GeneralReg),
|
||||
FloatReg(FloatReg),
|
||||
Base(i32),
|
||||
BaseAndGeneralReg(GeneralReg, i32),
|
||||
BaseAndFloatReg(FloatReg, i32),
|
||||
Base {
|
||||
offset: i32,
|
||||
size: u32,
|
||||
owned: bool,
|
||||
},
|
||||
BaseAndGeneralReg {
|
||||
reg: GeneralReg,
|
||||
offset: i32,
|
||||
size: u32,
|
||||
owned: bool,
|
||||
},
|
||||
BaseAndFloatReg {
|
||||
reg: FloatReg,
|
||||
offset: i32,
|
||||
size: u32,
|
||||
owned: bool,
|
||||
},
|
||||
}
|
||||
|
||||
pub trait RegTrait: Copy + Eq + std::hash::Hash + std::fmt::Debug + 'static {}
|
||||
|
@ -220,6 +233,7 @@ pub struct Backend64Bit<
|
|||
general_used_callee_saved_regs: MutSet<GeneralReg>,
|
||||
float_used_callee_saved_regs: MutSet<FloatReg>,
|
||||
|
||||
free_stack_chunks: Vec<'a, (i32, u32)>,
|
||||
stack_size: u32,
|
||||
// The amount of stack space needed to pass args for function calling.
|
||||
fn_call_stack_size: u32,
|
||||
|
@ -251,6 +265,7 @@ impl<
|
|||
float_free_regs: bumpalo::vec![in env.arena],
|
||||
float_used_regs: bumpalo::vec![in env.arena],
|
||||
float_used_callee_saved_regs: MutSet::default(),
|
||||
free_stack_chunks: bumpalo::vec![in env.arena],
|
||||
stack_size: 0,
|
||||
fn_call_stack_size: 0,
|
||||
})
|
||||
|
@ -262,6 +277,7 @@ impl<
|
|||
|
||||
fn reset(&mut self) {
|
||||
self.stack_size = 0;
|
||||
self.free_stack_chunks.clear();
|
||||
self.fn_call_stack_size = 0;
|
||||
self.last_seen_map.clear();
|
||||
self.layout_map.clear();
|
||||
|
@ -355,15 +371,15 @@ impl<
|
|||
// Update used and free regs.
|
||||
for (sym, storage) in &self.symbol_storage_map {
|
||||
match storage {
|
||||
SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg(reg, _) => {
|
||||
SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||
self.general_free_regs.retain(|r| *r != *reg);
|
||||
self.general_used_regs.push((*reg, *sym));
|
||||
}
|
||||
SymbolStorage::FloatReg(reg) | SymbolStorage::BaseAndFloatReg(reg, _) => {
|
||||
SymbolStorage::FloatReg(reg) | SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||
self.float_free_regs.retain(|r| *r != *reg);
|
||||
self.float_used_regs.push((*reg, *sym));
|
||||
}
|
||||
SymbolStorage::Base(_) => {}
|
||||
SymbolStorage::Base { .. } => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -625,9 +641,15 @@ impl<
|
|||
if let Layout::Struct(field_layouts) = layout {
|
||||
let struct_size = layout.stack_size(PTR_SIZE);
|
||||
if struct_size > 0 {
|
||||
let offset = self.increase_stack_size(struct_size)?;
|
||||
self.symbol_storage_map
|
||||
.insert(*sym, SymbolStorage::Base(offset));
|
||||
let offset = self.claim_stack_size(struct_size)?;
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset,
|
||||
size: struct_size,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
|
||||
let mut current_offset = offset;
|
||||
for (field, field_layout) in fields.iter().zip(field_layouts.iter()) {
|
||||
|
@ -636,15 +658,28 @@ impl<
|
|||
current_offset += field_size as i32;
|
||||
}
|
||||
} else {
|
||||
self.symbol_storage_map.insert(*sym, SymbolStorage::Base(0));
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset: 0,
|
||||
size: 0,
|
||||
owned: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
// This is a single element struct. Just copy the single field to the stack.
|
||||
let struct_size = layout.stack_size(PTR_SIZE);
|
||||
let offset = self.increase_stack_size(struct_size)?;
|
||||
self.symbol_storage_map
|
||||
.insert(*sym, SymbolStorage::Base(offset));
|
||||
let offset = self.claim_stack_size(struct_size)?;
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset,
|
||||
size: struct_size,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
self.copy_symbol_to_stack_offset(offset, &fields[0], layout)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -657,14 +692,20 @@ impl<
|
|||
index: u64,
|
||||
field_layouts: &'a [Layout<'a>],
|
||||
) -> Result<(), String> {
|
||||
if let Some(SymbolStorage::Base(struct_offset)) = self.symbol_storage_map.get(structure) {
|
||||
let mut data_offset = *struct_offset;
|
||||
if let Some(SymbolStorage::Base { offset, .. }) = self.symbol_storage_map.get(structure) {
|
||||
let mut data_offset = *offset;
|
||||
for i in 0..index {
|
||||
let field_size = field_layouts[i as usize].stack_size(PTR_SIZE);
|
||||
data_offset += field_size as i32;
|
||||
}
|
||||
self.symbol_storage_map
|
||||
.insert(*sym, SymbolStorage::Base(data_offset));
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset: data_offset,
|
||||
size: field_layouts[index as usize].stack_size(PTR_SIZE),
|
||||
owned: false,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("unknown struct: {:?}", structure))
|
||||
|
@ -689,8 +730,82 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
fn free_symbol(&mut self, sym: &Symbol) {
|
||||
self.symbol_storage_map.remove(sym);
|
||||
fn free_symbol(&mut self, sym: &Symbol) -> Result<(), String> {
|
||||
match self.symbol_storage_map.remove(sym) {
|
||||
Some(
|
||||
SymbolStorage::Base {
|
||||
offset,
|
||||
size,
|
||||
owned: true,
|
||||
}
|
||||
| SymbolStorage::BaseAndGeneralReg {
|
||||
offset,
|
||||
size,
|
||||
owned: true,
|
||||
..
|
||||
}
|
||||
| SymbolStorage::BaseAndFloatReg {
|
||||
offset,
|
||||
size,
|
||||
owned: true,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
let loc = (offset, size);
|
||||
// Note: this position current points to the offset following the specified location.
|
||||
// If loc was inserted at this position, it would shift the data at this position over by 1.
|
||||
let pos = self
|
||||
.free_stack_chunks
|
||||
.binary_search(&loc)
|
||||
.unwrap_or_else(|e| e);
|
||||
|
||||
// Check for overlap with previous and next free chunk.
|
||||
let merge_with_prev = if pos > 0 {
|
||||
if let Some((prev_offset, prev_size)) = self.free_stack_chunks.get(pos - 1) {
|
||||
let prev_end = *prev_offset + *prev_size as i32;
|
||||
if prev_end > offset {
|
||||
return Err("Double free? A previously freed stack location overlaps with the currently freed stack location.".to_string());
|
||||
}
|
||||
prev_end == offset
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let merge_with_next = if let Some((next_offset, _)) =
|
||||
self.free_stack_chunks.get(pos)
|
||||
{
|
||||
let current_end = offset + size as i32;
|
||||
if current_end > *next_offset {
|
||||
return Err("Double free? A previously freed stack location overlaps with the currently freed stack location.".to_string());
|
||||
}
|
||||
current_end == *next_offset
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
match (merge_with_prev, merge_with_next) {
|
||||
(true, true) => {
|
||||
let (prev_offset, prev_size) = self.free_stack_chunks[pos - 1];
|
||||
let (_, next_size) = self.free_stack_chunks[pos];
|
||||
self.free_stack_chunks[pos - 1] =
|
||||
(prev_offset, prev_size + size + next_size);
|
||||
self.free_stack_chunks.remove(pos);
|
||||
}
|
||||
(true, false) => {
|
||||
let (prev_offset, prev_size) = self.free_stack_chunks[pos - 1];
|
||||
self.free_stack_chunks[pos - 1] = (prev_offset, prev_size + size);
|
||||
}
|
||||
(false, true) => {
|
||||
let (_, next_size) = self.free_stack_chunks[pos];
|
||||
self.free_stack_chunks[pos] = (offset, next_size + size);
|
||||
}
|
||||
(false, false) => self.free_stack_chunks.insert(pos, loc),
|
||||
}
|
||||
}
|
||||
Some(_) | None => {}
|
||||
}
|
||||
for i in 0..self.general_used_regs.len() {
|
||||
let (reg, saved_sym) = self.general_used_regs[i];
|
||||
if saved_sym == *sym {
|
||||
|
@ -699,6 +814,15 @@ impl<
|
|||
break;
|
||||
}
|
||||
}
|
||||
for i in 0..self.float_used_regs.len() {
|
||||
let (reg, saved_sym) = self.float_used_regs[i];
|
||||
if saved_sym == *sym {
|
||||
self.float_free_regs.push(reg);
|
||||
self.float_used_regs.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String> {
|
||||
|
@ -716,7 +840,7 @@ impl<
|
|||
ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg);
|
||||
Ok(())
|
||||
}
|
||||
Some(SymbolStorage::Base(offset)) => match layout {
|
||||
Some(SymbolStorage::Base { offset, size, .. }) => match layout {
|
||||
Layout::Builtin(Builtin::Int64) => {
|
||||
ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset);
|
||||
Ok(())
|
||||
|
@ -726,28 +850,15 @@ impl<
|
|||
Ok(())
|
||||
}
|
||||
Layout::Struct(field_layouts) => {
|
||||
let struct_size = layout.stack_size(PTR_SIZE);
|
||||
if struct_size > 0 {
|
||||
let struct_offset = if let Some(SymbolStorage::Base(offset)) =
|
||||
self.symbol_storage_map.get(sym)
|
||||
{
|
||||
Ok(*offset)
|
||||
} else {
|
||||
Err(format!("unknown struct: {:?}", sym))
|
||||
}?;
|
||||
let (offset, size) = (*offset, *size);
|
||||
if size > 0 {
|
||||
let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER)
|
||||
{
|
||||
Some(self.load_to_general_reg(&Symbol::RET_POINTER)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
CC::return_struct(
|
||||
&mut self.buf,
|
||||
struct_offset,
|
||||
struct_size,
|
||||
field_layouts,
|
||||
ret_reg,
|
||||
)
|
||||
CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg)
|
||||
} else {
|
||||
// Nothing to do for empty struct
|
||||
Ok(())
|
||||
|
@ -846,19 +957,42 @@ impl<
|
|||
.insert(*sym, SymbolStorage::GeneralReg(reg));
|
||||
Ok(reg)
|
||||
}
|
||||
Some(SymbolStorage::Base(offset)) => {
|
||||
Some(SymbolStorage::Base {
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
}) => {
|
||||
let reg = self.claim_general_reg(sym)?;
|
||||
self.symbol_storage_map
|
||||
.insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset));
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::BaseAndGeneralReg {
|
||||
reg,
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
},
|
||||
);
|
||||
ASM::mov_reg64_base32(&mut self.buf, reg, offset as i32);
|
||||
Ok(reg)
|
||||
}
|
||||
Some(SymbolStorage::BaseAndGeneralReg(reg, offset)) => {
|
||||
self.symbol_storage_map
|
||||
.insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset));
|
||||
Some(SymbolStorage::BaseAndGeneralReg {
|
||||
reg,
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
}) => {
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::BaseAndGeneralReg {
|
||||
reg,
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
},
|
||||
);
|
||||
Ok(reg)
|
||||
}
|
||||
Some(SymbolStorage::FloatReg(_)) | Some(SymbolStorage::BaseAndFloatReg(_, _)) => {
|
||||
Some(SymbolStorage::FloatReg(_)) | Some(SymbolStorage::BaseAndFloatReg { .. }) => {
|
||||
Err("Cannot load floating point symbol into GeneralReg".to_string())
|
||||
}
|
||||
None => Err(format!("Unknown symbol: {}", sym)),
|
||||
|
@ -873,19 +1007,42 @@ impl<
|
|||
.insert(*sym, SymbolStorage::FloatReg(reg));
|
||||
Ok(reg)
|
||||
}
|
||||
Some(SymbolStorage::Base(offset)) => {
|
||||
Some(SymbolStorage::Base {
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
}) => {
|
||||
let reg = self.claim_float_reg(sym)?;
|
||||
self.symbol_storage_map
|
||||
.insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset));
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::BaseAndFloatReg {
|
||||
reg,
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
},
|
||||
);
|
||||
ASM::mov_freg64_base32(&mut self.buf, reg, offset as i32);
|
||||
Ok(reg)
|
||||
}
|
||||
Some(SymbolStorage::BaseAndFloatReg(reg, offset)) => {
|
||||
self.symbol_storage_map
|
||||
.insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset));
|
||||
Some(SymbolStorage::BaseAndFloatReg {
|
||||
reg,
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
}) => {
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::BaseAndFloatReg {
|
||||
reg,
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
},
|
||||
);
|
||||
Ok(reg)
|
||||
}
|
||||
Some(SymbolStorage::GeneralReg(_)) | Some(SymbolStorage::BaseAndGeneralReg(_, _)) => {
|
||||
Some(SymbolStorage::GeneralReg(_)) | Some(SymbolStorage::BaseAndGeneralReg { .. }) => {
|
||||
Err("Cannot load integer symbol into FloatReg".to_string())
|
||||
}
|
||||
None => Err(format!("Unknown symbol: {}", sym)),
|
||||
|
@ -896,45 +1053,107 @@ impl<
|
|||
let val = self.symbol_storage_map.remove(sym);
|
||||
match val {
|
||||
Some(SymbolStorage::GeneralReg(reg)) => {
|
||||
let offset = self.increase_stack_size(8)?;
|
||||
let offset = self.claim_stack_size(8)?;
|
||||
// For base addresssing, use the negative offset - 8.
|
||||
ASM::mov_base32_reg64(&mut self.buf, offset, reg);
|
||||
self.symbol_storage_map
|
||||
.insert(*sym, SymbolStorage::Base(offset));
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset,
|
||||
size: 8,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Some(SymbolStorage::FloatReg(reg)) => {
|
||||
let offset = self.increase_stack_size(8)?;
|
||||
let offset = self.claim_stack_size(8)?;
|
||||
// For base addresssing, use the negative offset.
|
||||
ASM::mov_base32_freg64(&mut self.buf, offset, reg);
|
||||
self.symbol_storage_map
|
||||
.insert(*sym, SymbolStorage::Base(offset));
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset,
|
||||
size: 8,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Some(SymbolStorage::Base(offset)) => {
|
||||
self.symbol_storage_map
|
||||
.insert(*sym, SymbolStorage::Base(offset));
|
||||
Some(SymbolStorage::Base {
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
}) => {
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Some(SymbolStorage::BaseAndGeneralReg(_, offset)) => {
|
||||
self.symbol_storage_map
|
||||
.insert(*sym, SymbolStorage::Base(offset));
|
||||
Some(SymbolStorage::BaseAndGeneralReg {
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
..
|
||||
}) => {
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Some(SymbolStorage::BaseAndFloatReg(_, offset)) => {
|
||||
self.symbol_storage_map
|
||||
.insert(*sym, SymbolStorage::Base(offset));
|
||||
Some(SymbolStorage::BaseAndFloatReg {
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
..
|
||||
}) => {
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset,
|
||||
size,
|
||||
owned,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(format!("Unknown symbol: {}", sym)),
|
||||
}
|
||||
}
|
||||
|
||||
/// increase_stack_size increase the current stack size `amount` bytes.
|
||||
/// claim_stack_size claims `amount` bytes from the stack.
|
||||
/// This may be free space in the stack or result in increasing the stack size.
|
||||
/// It returns base pointer relative offset of the new data.
|
||||
fn increase_stack_size(&mut self, amount: u32) -> Result<i32, String> {
|
||||
fn claim_stack_size(&mut self, amount: u32) -> Result<i32, String> {
|
||||
debug_assert!(amount > 0);
|
||||
if let Some(new_size) = self.stack_size.checked_add(amount) {
|
||||
if let Some(fitting_chunk) = self
|
||||
.free_stack_chunks
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, (_, size))| *size >= amount)
|
||||
.min_by_key(|(_, (_, size))| size)
|
||||
{
|
||||
let (pos, (offset, size)) = fitting_chunk;
|
||||
let (offset, size) = (*offset, *size);
|
||||
if size == amount {
|
||||
self.free_stack_chunks.remove(pos);
|
||||
Ok(offset)
|
||||
} else {
|
||||
let (prev_offset, prev_size) = self.free_stack_chunks[pos];
|
||||
self.free_stack_chunks[pos] = (prev_offset + amount as i32, prev_size - amount);
|
||||
Ok(prev_offset)
|
||||
}
|
||||
} else if let Some(new_size) = self.stack_size.checked_add(amount) {
|
||||
// Since stack size is u32, but the max offset is i32, if we pass i32 max, we have overflowed.
|
||||
if new_size > i32::MAX as u32 {
|
||||
Err("Ran out of stack space".to_string())
|
||||
|
@ -967,7 +1186,17 @@ impl<
|
|||
}
|
||||
Layout::Struct(_) if layout.safe_to_memcpy() => {
|
||||
let tmp_reg = self.get_tmp_general_reg()?;
|
||||
if let Some(SymbolStorage::Base(from_offset)) = self.symbol_storage_map.get(sym) {
|
||||
if let Some(SymbolStorage::Base {
|
||||
offset: from_offset,
|
||||
size,
|
||||
..
|
||||
}) = self.symbol_storage_map.get(sym)
|
||||
{
|
||||
debug_assert_eq!(
|
||||
*size,
|
||||
layout.stack_size(PTR_SIZE),
|
||||
"expected struct to have same size as data being stored in it"
|
||||
);
|
||||
for i in 0..layout.stack_size(PTR_SIZE) as i32 {
|
||||
ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i);
|
||||
ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg);
|
||||
|
|
|
@ -200,7 +200,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
general_i += 1;
|
||||
} else {
|
||||
base_offset += 8;
|
||||
symbol_map.insert(*sym, SymbolStorage::Base(base_offset));
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset: base_offset,
|
||||
size: 8,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Layout::Builtin(Builtin::Float64) => {
|
||||
|
@ -212,7 +219,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
float_i += 1;
|
||||
} else {
|
||||
base_offset += 8;
|
||||
symbol_map.insert(*sym, SymbolStorage::Base(base_offset));
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset: base_offset,
|
||||
size: 8,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
x => {
|
||||
|
@ -260,13 +274,13 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
.ok_or("function argument does not reference any symbol")?
|
||||
{
|
||||
SymbolStorage::GeneralReg(reg)
|
||||
| SymbolStorage::BaseAndGeneralReg(reg, _) => {
|
||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||
X86_64Assembler::mov_reg64_reg64(buf, dst, *reg);
|
||||
}
|
||||
SymbolStorage::Base(offset) => {
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_reg64_base32(buf, dst, *offset);
|
||||
}
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg(_, _) => {
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||
return Err(
|
||||
"Cannot load floating point symbol into GeneralReg".to_string()
|
||||
)
|
||||
|
@ -280,10 +294,10 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
.ok_or("function argument does not reference any symbol")?
|
||||
{
|
||||
SymbolStorage::GeneralReg(reg)
|
||||
| SymbolStorage::BaseAndGeneralReg(reg, _) => {
|
||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||
X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg);
|
||||
}
|
||||
SymbolStorage::Base(offset) => {
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
// Use RAX as a tmp reg because it will be free before function calls.
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
buf,
|
||||
|
@ -296,7 +310,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
X86_64GeneralReg::RAX,
|
||||
);
|
||||
}
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg(_, _) => {
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||
return Err(
|
||||
"Cannot load floating point symbol into GeneralReg".to_string()
|
||||
)
|
||||
|
@ -314,14 +328,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
.ok_or("function argument does not reference any symbol")?
|
||||
{
|
||||
SymbolStorage::FloatReg(reg)
|
||||
| SymbolStorage::BaseAndFloatReg(reg, _) => {
|
||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||
X86_64Assembler::mov_freg64_freg64(buf, dst, *reg);
|
||||
}
|
||||
SymbolStorage::Base(offset) => {
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_freg64_base32(buf, dst, *offset);
|
||||
}
|
||||
SymbolStorage::GeneralReg(_)
|
||||
| SymbolStorage::BaseAndGeneralReg(_, _) => {
|
||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||
return Err("Cannot load general symbol into FloatReg".to_string())
|
||||
}
|
||||
}
|
||||
|
@ -333,10 +347,10 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
.ok_or("function argument does not reference any symbol")?
|
||||
{
|
||||
SymbolStorage::FloatReg(reg)
|
||||
| SymbolStorage::BaseAndFloatReg(reg, _) => {
|
||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||
X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg);
|
||||
}
|
||||
SymbolStorage::Base(offset) => {
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
// Use XMM0 as a tmp reg because it will be free before function calls.
|
||||
X86_64Assembler::mov_freg64_base32(
|
||||
buf,
|
||||
|
@ -350,7 +364,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
);
|
||||
}
|
||||
SymbolStorage::GeneralReg(_)
|
||||
| SymbolStorage::BaseAndGeneralReg(_, _) => {
|
||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||
return Err("Cannot load general symbol into FloatReg".to_string())
|
||||
}
|
||||
}
|
||||
|
@ -541,7 +555,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
));
|
||||
}
|
||||
};
|
||||
symbol_map.insert(*sym, SymbolStorage::Base(base_offset));
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset: base_offset,
|
||||
size: 8,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -580,13 +601,13 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
.ok_or("function argument does not reference any symbol")?
|
||||
{
|
||||
SymbolStorage::GeneralReg(reg)
|
||||
| SymbolStorage::BaseAndGeneralReg(reg, _) => {
|
||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||
X86_64Assembler::mov_reg64_reg64(buf, dst, *reg);
|
||||
}
|
||||
SymbolStorage::Base(offset) => {
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_reg64_base32(buf, dst, *offset);
|
||||
}
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg(_, _) => {
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||
return Err(
|
||||
"Cannot load floating point symbol into GeneralReg".to_string()
|
||||
)
|
||||
|
@ -600,10 +621,10 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
.ok_or("function argument does not reference any symbol")?
|
||||
{
|
||||
SymbolStorage::GeneralReg(reg)
|
||||
| SymbolStorage::BaseAndGeneralReg(reg, _) => {
|
||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||
X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg);
|
||||
}
|
||||
SymbolStorage::Base(offset) => {
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
// Use RAX as a tmp reg because it will be free before function calls.
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
buf,
|
||||
|
@ -616,7 +637,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
X86_64GeneralReg::RAX,
|
||||
);
|
||||
}
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg(_, _) => {
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||
return Err(
|
||||
"Cannot load floating point symbol into GeneralReg".to_string()
|
||||
)
|
||||
|
@ -634,14 +655,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
.ok_or("function argument does not reference any symbol")?
|
||||
{
|
||||
SymbolStorage::FloatReg(reg)
|
||||
| SymbolStorage::BaseAndFloatReg(reg, _) => {
|
||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||
X86_64Assembler::mov_freg64_freg64(buf, dst, *reg);
|
||||
}
|
||||
SymbolStorage::Base(offset) => {
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_freg64_base32(buf, dst, *offset);
|
||||
}
|
||||
SymbolStorage::GeneralReg(_)
|
||||
| SymbolStorage::BaseAndGeneralReg(_, _) => {
|
||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||
return Err("Cannot load general symbol into FloatReg".to_string())
|
||||
}
|
||||
}
|
||||
|
@ -653,10 +674,10 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
.ok_or("function argument does not reference any symbol")?
|
||||
{
|
||||
SymbolStorage::FloatReg(reg)
|
||||
| SymbolStorage::BaseAndFloatReg(reg, _) => {
|
||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||
X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg);
|
||||
}
|
||||
SymbolStorage::Base(offset) => {
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
// Use XMM0 as a tmp reg because it will be free before function calls.
|
||||
X86_64Assembler::mov_freg64_base32(
|
||||
buf,
|
||||
|
@ -670,7 +691,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
);
|
||||
}
|
||||
SymbolStorage::GeneralReg(_)
|
||||
| SymbolStorage::BaseAndGeneralReg(_, _) => {
|
||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||
return Err("Cannot load general symbol into FloatReg".to_string())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -98,14 +100,14 @@ where
|
|||
Stmt::Let(sym, expr, layout, following) => {
|
||||
self.build_expr(sym, expr, layout)?;
|
||||
self.set_layout_map(*sym, layout)?;
|
||||
self.free_symbols(stmt);
|
||||
self.free_symbols(stmt)?;
|
||||
self.build_stmt(following, ret_layout)?;
|
||||
Ok(())
|
||||
}
|
||||
Stmt::Ret(sym) => {
|
||||
self.load_literal_symbols(&[*sym])?;
|
||||
self.return_symbol(sym, ret_layout)?;
|
||||
self.free_symbols(stmt);
|
||||
self.free_symbols(stmt)?;
|
||||
Ok(())
|
||||
}
|
||||
Stmt::Switch {
|
||||
|
@ -123,7 +125,7 @@ where
|
|||
default_branch,
|
||||
ret_layout,
|
||||
)?;
|
||||
self.free_symbols(stmt);
|
||||
self.free_symbols(stmt)?;
|
||||
Ok(())
|
||||
}
|
||||
x => Err(format!("the statement, {:?}, is not yet implemented", x)),
|
||||
|
@ -150,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)?;
|
||||
}
|
||||
|
@ -509,21 +511,30 @@ where
|
|||
fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String>;
|
||||
|
||||
/// free_symbols will free all symbols for the given statement.
|
||||
fn free_symbols(&mut self, stmt: &Stmt<'a>) {
|
||||
fn free_symbols(&mut self, stmt: &Stmt<'a>) -> Result<(), String> {
|
||||
if let Some(syms) = self.free_map().remove(&(stmt as *const Stmt<'a>)) {
|
||||
for sym in syms {
|
||||
//println!("Freeing symbol: {:?}", sym);
|
||||
self.free_symbol(&sym);
|
||||
// println!("Freeing symbol: {:?}", sym);
|
||||
self.free_symbol(&sym)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// free_symbol frees any registers or stack space used to hold a symbol.
|
||||
fn free_symbol(&mut self, sym: &Symbol);
|
||||
fn free_symbol(&mut self, sym: &Symbol) -> Result<(), String>;
|
||||
|
||||
/// set_last_seen sets the statement a symbol was last seen in.
|
||||
fn set_last_seen(&mut self, sym: Symbol, stmt: &Stmt<'a>) {
|
||||
fn set_last_seen(
|
||||
&mut self,
|
||||
sym: Symbol,
|
||||
stmt: &Stmt<'a>,
|
||||
owning_symbol: &MutMap<Symbol, Symbol>,
|
||||
) {
|
||||
self.last_seen_map().insert(sym, stmt);
|
||||
if let Some(parent) = owning_symbol.get(&sym) {
|
||||
self.last_seen_map().insert(*parent, stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/// last_seen_map gets the map from symbol to when it is last seen in the function.
|
||||
|
@ -573,36 +584,46 @@ where
|
|||
/// scan_ast runs through the ast and fill the last seen map.
|
||||
/// This must iterate through the ast in the same way that build_stmt does. i.e. then before else.
|
||||
fn scan_ast(&mut self, stmt: &Stmt<'a>) {
|
||||
// This keeps track of symbols that depend on other symbols.
|
||||
// The main case of this is data in structures and tagged unions.
|
||||
// This data must extend the lifetime of the original structure or tagged union.
|
||||
// For arrays the loading is always done through low levels and does not depend on the underlying array's lifetime.
|
||||
let mut owning_symbol: MutMap<Symbol, Symbol> = MutMap::default();
|
||||
match stmt {
|
||||
Stmt::Let(sym, expr, _, following) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
match expr {
|
||||
Expr::Literal(_) => {}
|
||||
|
||||
Expr::Call(call) => self.scan_ast_call(call, stmt),
|
||||
Expr::Call(call) => self.scan_ast_call(call, stmt, &owning_symbol),
|
||||
|
||||
Expr::Tag { arguments, .. } => {
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
}
|
||||
}
|
||||
Expr::Struct(syms) => {
|
||||
for sym in *syms {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
}
|
||||
}
|
||||
Expr::StructAtIndex { structure, .. } => {
|
||||
self.set_last_seen(*structure, stmt);
|
||||
self.set_last_seen(*structure, stmt, &owning_symbol);
|
||||
owning_symbol.insert(*sym, *structure);
|
||||
}
|
||||
Expr::GetTagId { structure, .. } => {
|
||||
self.set_last_seen(*structure, stmt);
|
||||
self.set_last_seen(*structure, stmt, &owning_symbol);
|
||||
owning_symbol.insert(*sym, *structure);
|
||||
}
|
||||
Expr::UnionAtIndex { structure, .. } => {
|
||||
self.set_last_seen(*structure, stmt);
|
||||
self.set_last_seen(*structure, stmt, &owning_symbol);
|
||||
owning_symbol.insert(*sym, *structure);
|
||||
}
|
||||
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, &owning_symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Reuse {
|
||||
|
@ -611,22 +632,22 @@ where
|
|||
tag_name,
|
||||
..
|
||||
} => {
|
||||
self.set_last_seen(*symbol, stmt);
|
||||
self.set_last_seen(*symbol, stmt, &owning_symbol);
|
||||
match tag_name {
|
||||
TagName::Closure(sym) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
}
|
||||
TagName::Private(sym) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
}
|
||||
TagName::Global(_) => {}
|
||||
}
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
}
|
||||
}
|
||||
Expr::Reset(sym) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
}
|
||||
Expr::EmptyArray => {}
|
||||
Expr::RuntimeErrorFunction(_) => {}
|
||||
|
@ -640,19 +661,19 @@ where
|
|||
default_branch,
|
||||
..
|
||||
} => {
|
||||
self.set_last_seen(*cond_symbol, stmt);
|
||||
self.set_last_seen(*cond_symbol, stmt, &owning_symbol);
|
||||
for (_, _, branch) in *branches {
|
||||
self.scan_ast(branch);
|
||||
}
|
||||
self.scan_ast(default_branch.1);
|
||||
}
|
||||
Stmt::Ret(sym) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
}
|
||||
Stmt::Refcounting(modify, following) => {
|
||||
let sym = modify.get_symbol();
|
||||
|
||||
self.set_last_seen(sym, stmt);
|
||||
self.set_last_seen(sym, stmt, &owning_symbol);
|
||||
self.scan_ast(following);
|
||||
}
|
||||
Stmt::Join {
|
||||
|
@ -662,29 +683,34 @@ where
|
|||
..
|
||||
} => {
|
||||
for param in *parameters {
|
||||
self.set_last_seen(param.symbol, stmt);
|
||||
self.set_last_seen(param.symbol, stmt, &owning_symbol);
|
||||
}
|
||||
self.scan_ast(continuation);
|
||||
self.scan_ast(remainder);
|
||||
}
|
||||
Stmt::Jump(JoinPointId(sym), symbols) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
for sym in *symbols {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
}
|
||||
}
|
||||
Stmt::RuntimeError(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_ast_call(&mut self, call: &roc_mono::ir::Call, stmt: &roc_mono::ir::Stmt<'a>) {
|
||||
fn scan_ast_call(
|
||||
&mut self,
|
||||
call: &roc_mono::ir::Call,
|
||||
stmt: &roc_mono::ir::Stmt<'a>,
|
||||
owning_symbol: &MutMap<Symbol, Symbol>,
|
||||
) {
|
||||
let roc_mono::ir::Call {
|
||||
call_type,
|
||||
arguments,
|
||||
} = call;
|
||||
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.set_last_seen(*sym, stmt, owning_symbol);
|
||||
}
|
||||
|
||||
match call_type {
|
||||
|
|
|
@ -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,57 +2160,141 @@ 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;
|
||||
|
||||
let len_u64 = elems.len() as u64;
|
||||
let element_type = basic_type_from_layout(env, elem_layout);
|
||||
|
||||
let ptr = {
|
||||
let len_type = env.ptr_int();
|
||||
let len = len_type.const_int(len_u64, false);
|
||||
let list_length = elems.len();
|
||||
let list_length_intval = env.ptr_int().const_int(list_length as _, false);
|
||||
|
||||
allocate_list(env, elem_layout, len)
|
||||
};
|
||||
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 = list_length * element_width as usize;
|
||||
let alignment = elem_layout
|
||||
.alignment_bytes(env.ptr_bytes)
|
||||
.max(env.ptr_bytes);
|
||||
|
||||
// Copy the elements from the list literal into the array
|
||||
for (index, symbol) in elems.iter().enumerate() {
|
||||
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") };
|
||||
let mut is_all_constant = true;
|
||||
let zero_elements = (env.ptr_bytes as f64 / element_width as f64).ceil() as usize;
|
||||
|
||||
builder.build_store(elem_ptr, val);
|
||||
// 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 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 {
|
||||
global_elements.push(element_type.const_zero());
|
||||
}
|
||||
|
||||
// Copy the elements from the list literal into the array
|
||||
for (index, element) in elems.iter().enumerate() {
|
||||
match element {
|
||||
ListLiteralElement::Literal(literal) => {
|
||||
let val = build_exp_literal(env, elem_layout, literal);
|
||||
global_elements.push(val.into_int_value());
|
||||
}
|
||||
ListLiteralElement::Symbol(symbol) => {
|
||||
let val = load_symbol(scope, symbol);
|
||||
let intval = val.into_int_value();
|
||||
|
||||
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 {
|
||||
global_elements.into_bump_slice()
|
||||
} else {
|
||||
&global_elements[zero_elements..]
|
||||
};
|
||||
|
||||
// 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.set_initializer(&element_type.const_array(const_elements));
|
||||
global.as_pointer_value()
|
||||
};
|
||||
|
||||
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 ptr = unsafe {
|
||||
env.builder
|
||||
.build_in_bounds_gep(global, &[zero, offset], "first_element_pointer")
|
||||
};
|
||||
|
||||
super::build_list::store_list(env, ptr, list_length_intval)
|
||||
} else {
|
||||
// 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);
|
||||
|
||||
// then, copy the relevant segment from the constant section into the heap
|
||||
env.builder
|
||||
.build_memcpy(
|
||||
ptr,
|
||||
alignment,
|
||||
global,
|
||||
alignment,
|
||||
env.ptr_int().const_int(size as _, false),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
super::build_list::store_list(env, ptr, list_length_intval)
|
||||
}
|
||||
} else {
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
|
||||
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>(
|
||||
|
|
3
compiler/gen_wasm/.gitignore
vendored
Normal file
3
compiler/gen_wasm/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.wasm
|
||||
*.wat
|
||||
/notes.md
|
29
compiler/gen_wasm/Cargo.toml
Normal file
29
compiler/gen_wasm/Cargo.toml
Normal file
|
@ -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"
|
170
compiler/gen_wasm/README.md
Normal file
170
compiler/gen_wasm/README.md
Normal file
|
@ -0,0 +1,170 @@
|
|||
# Development backend for WebAssembly
|
||||
|
||||
## Plan
|
||||
|
||||
- Initial bringup
|
||||
- Get a wasm backend working for some of the number tests.
|
||||
- Use a separate `gen_wasm` directory for now, to avoid trying to do bringup and integration at the same time.
|
||||
- Integration
|
||||
- Move wasm files to `gen_dev/src/wasm`
|
||||
- Share tests between wasm and x64, with some way of saying which tests work on which backends, and dispatching to different eval helpers based on that.
|
||||
- Get `build_module` in object_builder.rs to dispatch to the wasm generator (adding some Wasm options to the `Triple` struct)
|
||||
- Get `build_module` to write to a file, or maybe return `Vec<u8>`, instead of returning an Object structure
|
||||
- Code sharing
|
||||
- Try to ensure that both Wasm and x64 use the same `Backend` trait so that we can share code.
|
||||
- We need to work towards this after we've progressed a bit more with Wasm and gained more understanding and experience of the differences.
|
||||
- We will have to think about how to deal with the `Backend` code that doesn't apply to Wasm. Perhaps we will end up with more traits like `RegisterBackend` / `StackBackend` or `NativeBackend` / `WasmBackend`, and perhaps even some traits to do with backends that support jumps and those that don't.
|
||||
|
||||
## Structured control flow
|
||||
|
||||
🚨 **This is an area that could be tricky** 🚨
|
||||
|
||||
One of the security features of WebAssembly is that it does not allow unrestricted "jumps" to anywhere you like. It does not have an instruction for that. All of the [control instructions][control-inst] can only implement "structured" control flow, and have names like `if`, `loop`, `block` that you'd normally associate with high-level languages. There are branch (`br`) instructions that can jump to labelled blocks within the same function, but the blocks have to be nested in sensible ways.
|
||||
|
||||
[control-inst]: https://webassembly.github.io/spec/core/syntax/instructions.html#control-instructions
|
||||
|
||||
Implications:
|
||||
|
||||
Roc, like most modern languages, is already enforcing structured control flow in the source program. Constructs from the Roc AST like `When`, `If` and `LetRec` can all be converted straightforwardly to Wasm constructs.
|
||||
|
||||
However the Mono IR converts this to jumps and join points, which are more of a Control Flow Graph than a tree. That doesn't map so directly to the Wasm structures. This is such a common issue for compiler back-ends that the WebAssembly compiler toolkit `binaryen` has an [API for control-flow graphs][cfg-api]. We're not using `binaryen` right now. It's a C++ library, though it does have a (very thin and somewhat hard-to-use) [Rust wrapper][binaryen-rs]. We should probably investigate this area sooner rather than later. If relooping turns out to be necessary or difficult, we might need to switch from parity_wasm to binaryen.
|
||||
|
||||
> By the way, it's not obvious how to pronounce "binaryen" but apparently it rhymes with "Targaryen", the family name from the "Game of Thrones" TV series
|
||||
|
||||
[cfg-api]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#cfg-api
|
||||
[binaryen-rs]: https://crates.io/crates/binaryen
|
||||
|
||||
Binaryen's control-flow graph API implements the "Relooper" algorithm developed by the Emscripten project and described in [this paper](https://github.com/emscripten-core/emscripten/blob/main/docs/paper.pdf).
|
||||
|
||||
There is an alternative algorithm that is supposed to be an improvement on Relooper, called ["Stackifier"](https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2).
|
||||
|
||||
|
||||
## Stack machine vs register machine
|
||||
|
||||
Wasm's instruction set is based on a stack-machine VM. Whereas CPU instructions have named registers that they operate on, Wasm has no named registers at all. The instructions don't contain register names. Instructions can oly operate on whatever data is at the top of the stack.
|
||||
|
||||
For example the instruction `i64.add` takes two operands. It pops the top two arguments off the VM stack and pushes the result back.
|
||||
|
||||
In the [spec][spec-instructions], every instruction has a type signature! This is not something you would see for CPU instructions. The type signature for i64.add is `[i64 i64] → [i64]` because it pushes two i64's and pops an i64.
|
||||
|
||||
[spec-instructions]: https://webassembly.github.io/spec/core/appendix/index-instructions.html
|
||||
|
||||
This means that WebAssembly has a concept of type checking. When you load a .wasm file as a chunk of bytes into a Wasm runtime (like a browser or [wasmer](https://wasmer.io/)), the runtime will first _validate_ those bytes. They have some fast way of checking whether the types being pushed and popped are consistent. So if you try to do the i64.add instruction when you have floats on the stack, it will fail validation.
|
||||
|
||||
Note that the instruction makes no mention of any source or destination registers, because there is no such thing. It just pops two values and pushes one. (This architecture choice helps to keep WebAssembly programs quite compact. There are no extra bytes specifying source and destination registers.)
|
||||
|
||||
Implications of the stack machine for Roc:
|
||||
|
||||
- There is no such thing as register allocation, since there are no registers! There is no reason to maintain hashmaps of what registers are free or not. And there is no need to do a pass over the IR to find the "last seen" occurrence of a symbol in the IR. That means we don't need the `Backend` methods `scan_ast`, `scan_ast_call`, `set_last_seen`, `last_seen_map`, `free_map`, `free_symbols`, `free_symbol`, `set_free_map`.
|
||||
|
||||
- There is no random access to the stack. All instructions operate on the data at the _top_ of the stack. There is no instruction that says "get the value at index 17 in the stack". If such an instruction did exist, it wouldn't be a stack machine. And there is no way to "free up some of the slots in the stack". You have to consume the stuff at the top, then the stuff further down. However Wasm has a concept of local variables, which do allow random access. See below.
|
||||
|
||||
## Local variables
|
||||
|
||||
WebAssembly functions can have any number of local variables. They are declared at the beginning of the function, along with their types (just like C). WebAssembly has 4 value types: `i32`, `i64`, `f32`, `f64`.
|
||||
|
||||
In this backend, each symbol in the Mono IR gets one WebAssembly local. To illustrate, let's translate a simple Roc example to WebAssembly text format.
|
||||
The WebAssembly code below is completely unoptimised and uses far more locals than necessary. But that does help to illustrate the concept of locals.
|
||||
|
||||
```
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
main =
|
||||
1 + 2 + 4
|
||||
```
|
||||
|
||||
The Mono IR contains two functions, `Num.add` and `main`, so we generate two corresponding WebAssembly functions.
|
||||
|
||||
```
|
||||
(func (;0;) (param i64 i64) (result i64) ; declare function index 0 (Num.add) with two i64 parameters and an i64 result
|
||||
local.get 0 ; load param 0 stack=[param0]
|
||||
local.get 1 ; load param 1 stack=[param0, param1]
|
||||
i64.add ; pop two values, add, and push result stack=[param0 + param1]
|
||||
return) ; return the value at the top of the stack
|
||||
|
||||
(func (;1;) (result i64) ; declare function index 1 (main) with no parameters and an i64 result
|
||||
(local i64 i64 i64 i64) ; declare 4 local variables, all with type i64, one for each symbol in the Mono IR
|
||||
i64.const 1 ; load constant of type i64 and value 1 stack=[1]
|
||||
local.set 0 ; store top of stack to local0 stack=[] local0=1
|
||||
i64.const 2 ; load constant of type i64 and value 2 stack=[2] local0=1
|
||||
local.set 1 ; store top of stack to local1 stack=[] local0=1 local1=2
|
||||
local.get 0 ; load local0 to top of stack stack=[1] local0=1 local1=2
|
||||
local.get 1 ; load local1 to top of stack stack=[1,2] local0=1 local1=2
|
||||
call 0 ; call function index 0 (which pops 2 and pushes 1) stack=[3] local0=1 local1=2
|
||||
local.set 2 ; store top of stack to local2 stack=[] local0=1 local1=2 local2=3
|
||||
i64.const 4 ; load constant of type i64 and value 4 stack=[4] local0=1 local1=2 local2=3
|
||||
local.set 3 ; store top of stack to local3 stack=[] local0=1 local1=2 local2=3 local3=4
|
||||
local.get 2 ; load local2 to top of stack stack=[3] local0=1 local1=2 local2=3 local3=4
|
||||
local.get 3 ; load local3 to top of stack stack=[3,4] local0=1 local1=2 local2=3 local3=4
|
||||
call 0 ; call function index 0 (which pops 2 and pushes 1) stack=[7] local0=1 local1=2 local2=3 local3=4
|
||||
return) ; return the value at the top of the stack
|
||||
```
|
||||
|
||||
If we run this code through the `wasm-opt` tool from the [binaryen toolkit](https://github.com/WebAssembly/binaryen#tools), the unnecessary locals get optimised away. The command line below runs the minimum number of passes to achieve this (`--simplify-locals` must come first).
|
||||
|
||||
```
|
||||
$ wasm-opt --simplify-locals --reorder-locals --vacuum example.wasm > opt.wasm
|
||||
```
|
||||
|
||||
The optimised functions have no local variables, and the code shrinks to about 60% of its original size.
|
||||
```
|
||||
(func (;0;) (param i64 i64) (result i64)
|
||||
local.get 0
|
||||
local.get 1
|
||||
i64.add)
|
||||
(func (;1;) (result i64)
|
||||
i64.const 1
|
||||
i64.const 2
|
||||
call 0
|
||||
i64.const 4)
|
||||
```
|
||||
|
||||
## Memory
|
||||
|
||||
WebAssembly programs have a "linear memory" for storing data, which is a block of memory assigned to it by the host. You can assign a min and max size to the memory, and the WebAssembly program can request 64kB pages from the host, just like a "normal" program would request pages from the OS. Addresses start at zero and go up to whatever the current size is. Zero is a perfectly normal address like any other, and dereferencing it is not a segfault. But addresses beyond the current memory size are out of bounds and dereferencing them will cause a panic.
|
||||
|
||||
The program has full read/write access to the memory and can divide it into whatever sections it wants. Most programs will want to do the traditional split of static memory, stack memory and heap memory.
|
||||
|
||||
The WebAssembly module structure includes a data section that will be copied into the linear memory at a specified offset on initialisation, so you can use that for string literals etc. But the division of the rest of memory into "stack" and "heap" areas is not a first-class concept. It is up to the compiler to generate instructions to do whatever it wants with that memory.
|
||||
|
||||
## Stack machine vs stack memory
|
||||
|
||||
**There are two entirely different meanings of the word "stack" that are relevant to the WebAssembly backend.** It's unfortunate that the word "stack" is so overloaded. I guess it's just a useful data structure. The worst thing is that both of them tend to be referred to as just "the stack"! We need more precise terms.
|
||||
|
||||
When we are talking about the instruction set, I'll use the term _machine stack_ or _VM stack_. This is the implicit data structure that WebAssembly instructions operate on. In the examples above, it's where `i64.add` gets its arguments and stores its result. I think of it as an abstraction over CPU registers, that WebAssembly uses in order to be portable and compact.
|
||||
|
||||
When we are talking about how we store values in _memory_, I'll use the term _stack memory_ rather than just "the stack". It feels clunky but it's the best I can think of.
|
||||
|
||||
Of course our program can use another area of memory as a heap as well. WebAssembly doesn't mind how you divide up your memory. It just gives you some memory and some instructions for loading and storing.
|
||||
|
||||
## Function calls
|
||||
|
||||
In WebAssembly you call a function by pushing arguments to the stack and then issuing a `call` instruction, which specifies a function index. The VM knows how many values to pop off the stack by examining the _type_ of the function. In our example earlier, `Num.add` had the type `[i64 i64] → [i64]` so it expects to find two i64's on the stack and pushes one i64 back as the result. Remember, the runtime engine will validate the module before running it, and if your generated code is trying to call a function at a point in the program where the wrong value types are on the stack, it will fail validation.
|
||||
|
||||
Function arguments are restricted to the four value types, `i32`, `i64`, `f32` and `f64`. If those are all we need, then there is _no need for any stack memory_, stack pointer, etc. We saw this in our example earlier. We just said `call 0`. We didn't need any instructions to create a stack frame with a return address, and there was no "jump" instruction. Essentially, WebAssembly has a first-class concept of function calls, so you don't build it up from lower-level primitives. You could think of this as an abstraction over calling conventions.
|
||||
|
||||
That's all great for primitive values but what happens when we want to pass more complex data structures between functions?
|
||||
|
||||
Well, remember, "stack memory" is not a special kind of memory in WebAssembly, it's just an area of our memory where we _decide_ that we want to implement a stack data structure. So we can implement it however we want. A good choice would be to make our stack frame look the same as it would when we're targeting a CPU, except without the return address (since there's no need for one). We can also decide to pass numbers through the machine stack rather than in stack memory, since that takes fewer instructions.
|
||||
|
||||
The only other thing we need is a stack pointer. On CPU targets, there's often have a specific "stack pointer" register. WebAssembly has no equivalent to that, but we can use a `global` variable.
|
||||
|
||||
The system I've outlined above is based on my experience of compiling C to WebAssembly via the Emscripten toolchain (which is built on top of clang). It's also in line with what the WebAssembly project describes [here](https://github.com/WebAssembly/design/blob/main/Rationale.md#locals).
|
||||
|
||||
## Modules vs Instances
|
||||
|
||||
What's the difference between a Module and an Instance in WebAssembly?
|
||||
|
||||
Well, if I compare it to running a program on Linux, it's like the difference between an ELF binary and the executable image in memory that you get when you _load_ that ELF file. The ELF file is essentially a _specification_ for how to create the executable image. In order to start executing the program, the OS has to actually allocate a stack and a heap, and load the text and data. If you run multiple copies of the same program, they will each have their own memory and their own execution state. (More detail [here](https://wiki.osdev.org/ELF#Loading_ELF_Binaries)).
|
||||
|
||||
The Module is like the ELF file, and the Instance is like the executable image.
|
||||
|
||||
The Module is a _specification_ for how to create an Instance of the program. The Module says how much memory the program needs, but the Instance actually _contains_ that memory. In order to run the Wasm program, the VM needs to create an instance, allocate some memory for it, and copy the data section into that memory. If you run many copies of the same Wasm program, you will have one Module but many Instances. Each instance will have its own separate area of memory, and its own execution state.
|
||||
|
||||
## Modules, object files, and linking
|
||||
|
||||
A WebAssembly module is equivalent to an executable file. It doesn't normally need relocations since at the WebAssembly layer, there is no Address Space Layout Randomisation. If it has relocations then it's an object file.
|
||||
|
||||
The [official spec](https://webassembly.github.io/spec/core/binary/modules.html#sections) lists the sections that are part of the final module. It doesn't mention any sections for relocations or symbol names, but it has room for "custom sections" that in practice seem to be used for that.
|
||||
|
||||
The WebAssembly `tool-conventions` repo has a document on [linking](https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md), and the `parity_wasm` crate supports "name" and "relocation" [sections](https://docs.rs/parity-wasm/0.42.2/parity_wasm/elements/enum.Section.html).
|
303
compiler/gen_wasm/src/backend.rs
Normal file
303
compiler/gen_wasm/src/backend.rs
Normal file
|
@ -0,0 +1,303 @@
|
|||
use parity_wasm::builder;
|
||||
use parity_wasm::builder::{CodeLocation, ModuleBuilder};
|
||||
use parity_wasm::elements::{Instruction, Instruction::*, Instructions, Local, ValueType};
|
||||
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::ir::{CallType, Expr, Literal, Proc, Stmt};
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
|
||||
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone.
|
||||
// Follow Emscripten's example by using 1kB (4 bytes would probably do)
|
||||
const UNUSED_DATA_SECTION_BYTES: u32 = 1024;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct LocalId(u32);
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct LabelId(u32);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SymbolStorage(LocalId, WasmLayout);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct WasmLayout {
|
||||
value_type: ValueType,
|
||||
stack_memory: u32,
|
||||
}
|
||||
|
||||
impl WasmLayout {
|
||||
fn new(layout: &Layout) -> Result<Self, String> {
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::Int64) => Ok(Self {
|
||||
value_type: ValueType::I64,
|
||||
stack_memory: 0,
|
||||
}),
|
||||
Layout::Builtin(Builtin::Float64) => Ok(Self {
|
||||
value_type: ValueType::F64,
|
||||
stack_memory: 0,
|
||||
}),
|
||||
x => Err(format!("layout, {:?}, not implemented yet", x)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WasmBackend<'a> {
|
||||
// Module: Wasm AST
|
||||
pub builder: ModuleBuilder,
|
||||
|
||||
// Module: internal state & IR mappings
|
||||
_data_offset_map: MutMap<Literal<'a>, u32>,
|
||||
_data_offset_next: u32,
|
||||
proc_symbol_map: MutMap<Symbol, CodeLocation>,
|
||||
|
||||
// Functions: Wasm AST
|
||||
instructions: std::vec::Vec<Instruction>,
|
||||
ret_type: ValueType,
|
||||
arg_types: std::vec::Vec<ValueType>,
|
||||
locals: std::vec::Vec<Local>,
|
||||
|
||||
// Functions: internal state & IR mappings
|
||||
stack_memory: u32,
|
||||
symbol_storage_map: MutMap<Symbol, SymbolStorage>,
|
||||
// joinpoint_label_map: MutMap<JoinPointId, LabelId>,
|
||||
}
|
||||
|
||||
impl<'a> WasmBackend<'a> {
|
||||
pub fn new() -> Self {
|
||||
WasmBackend {
|
||||
// Module: Wasm AST
|
||||
builder: builder::module(),
|
||||
|
||||
// Module: internal state & IR mappings
|
||||
_data_offset_map: MutMap::default(),
|
||||
_data_offset_next: UNUSED_DATA_SECTION_BYTES,
|
||||
proc_symbol_map: MutMap::default(),
|
||||
|
||||
// Functions: Wasm AST
|
||||
instructions: std::vec::Vec::with_capacity(256),
|
||||
ret_type: ValueType::I32,
|
||||
arg_types: std::vec::Vec::with_capacity(8),
|
||||
locals: std::vec::Vec::with_capacity(32),
|
||||
|
||||
// Functions: internal state & IR mappings
|
||||
stack_memory: 0,
|
||||
symbol_storage_map: MutMap::default(),
|
||||
// joinpoint_label_map: MutMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
// Functions: Wasm AST
|
||||
self.instructions.clear();
|
||||
self.arg_types.clear();
|
||||
self.locals.clear();
|
||||
|
||||
// Functions: internal state & IR mappings
|
||||
self.stack_memory = 0;
|
||||
self.symbol_storage_map.clear();
|
||||
// joinpoint_label_map.clear();
|
||||
}
|
||||
|
||||
pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result<u32, String> {
|
||||
let ret_layout = WasmLayout::new(&proc.ret_layout)?;
|
||||
if ret_layout.stack_memory > 0 {
|
||||
// TODO: if returning a struct by value, add an extra argument for a pointer to callee's stack memory
|
||||
return Err(format!(
|
||||
"Not yet implemented: Return in stack memory for non-primtitive layouts like {:?}",
|
||||
proc.ret_layout
|
||||
));
|
||||
}
|
||||
|
||||
self.ret_type = ret_layout.value_type;
|
||||
self.arg_types.reserve(proc.args.len());
|
||||
|
||||
for (layout, symbol) in proc.args {
|
||||
let wasm_layout = WasmLayout::new(layout)?;
|
||||
self.arg_types.push(wasm_layout.value_type);
|
||||
self.insert_local(wasm_layout, *symbol);
|
||||
}
|
||||
|
||||
self.build_stmt(&proc.body, &proc.ret_layout)?;
|
||||
|
||||
let signature = builder::signature()
|
||||
.with_params(self.arg_types.clone()) // requires std::Vec, not Bumpalo
|
||||
.with_result(self.ret_type)
|
||||
.build_sig();
|
||||
|
||||
// functions must end with an End instruction/opcode
|
||||
let mut instructions = self.instructions.clone();
|
||||
instructions.push(Instruction::End);
|
||||
|
||||
let function_def = builder::function()
|
||||
.with_signature(signature)
|
||||
.body()
|
||||
.with_locals(self.locals.clone())
|
||||
.with_instructions(Instructions::new(instructions))
|
||||
.build() // body
|
||||
.build(); // function
|
||||
|
||||
let location = self.builder.push_function(function_def);
|
||||
let function_index = location.body;
|
||||
self.proc_symbol_map.insert(sym, location);
|
||||
self.reset();
|
||||
|
||||
Ok(function_index)
|
||||
}
|
||||
|
||||
fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol) -> LocalId {
|
||||
self.stack_memory += layout.stack_memory;
|
||||
let index = self.symbol_storage_map.len();
|
||||
if index >= self.arg_types.len() {
|
||||
self.locals.push(Local::new(1, layout.value_type));
|
||||
}
|
||||
let local_id = LocalId(index as u32);
|
||||
let storage = SymbolStorage(local_id, layout);
|
||||
self.symbol_storage_map.insert(symbol, storage);
|
||||
local_id
|
||||
}
|
||||
|
||||
fn get_symbol_storage(&self, sym: &Symbol) -> Result<&SymbolStorage, String> {
|
||||
self.symbol_storage_map.get(sym).ok_or_else(|| {
|
||||
format!(
|
||||
"Symbol {:?} not found in function scope:\n{:?}",
|
||||
sym, self.symbol_storage_map
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn load_from_symbol(&mut self, sym: &Symbol) -> Result<(), String> {
|
||||
let SymbolStorage(LocalId(local_id), _) = self.get_symbol_storage(sym)?;
|
||||
let id: u32 = *local_id;
|
||||
self.instructions.push(GetLocal(id));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
|
||||
match stmt {
|
||||
// This pattern is a simple optimisation to get rid of one local and two instructions per proc.
|
||||
// If we are just returning the expression result, then don't SetLocal and immediately GetLocal
|
||||
Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if let_sym == ret_sym => {
|
||||
self.build_expr(let_sym, expr, layout)?;
|
||||
self.instructions.push(Return);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Stmt::Let(sym, expr, layout, following) => {
|
||||
let wasm_layout = WasmLayout::new(layout)?;
|
||||
let local_id = self.insert_local(wasm_layout, *sym);
|
||||
|
||||
self.build_expr(sym, expr, layout)?;
|
||||
self.instructions.push(SetLocal(local_id.0));
|
||||
|
||||
self.build_stmt(following, ret_layout)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Stmt::Ret(sym) => {
|
||||
if let Some(SymbolStorage(local_id, _)) = self.symbol_storage_map.get(sym) {
|
||||
self.instructions.push(GetLocal(local_id.0));
|
||||
self.instructions.push(Return);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"Not yet implemented: returning values with layout {:?}",
|
||||
ret_layout
|
||||
))
|
||||
}
|
||||
}
|
||||
x => Err(format!("statement not yet implemented: {:?}", x)),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_expr(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
expr: &Expr<'a>,
|
||||
layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
match expr {
|
||||
Expr::Literal(lit) => self.load_literal(lit),
|
||||
|
||||
Expr::Call(roc_mono::ir::Call {
|
||||
call_type,
|
||||
arguments,
|
||||
}) => match call_type {
|
||||
CallType::ByName { name: func_sym, .. } => {
|
||||
for arg in *arguments {
|
||||
self.load_from_symbol(arg)?;
|
||||
}
|
||||
let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!(
|
||||
"Cannot find function {:?} called from {:?}",
|
||||
func_sym, sym
|
||||
))?;
|
||||
self.instructions.push(Call(function_location.body));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
CallType::LowLevel { op: lowlevel, .. } => {
|
||||
self.build_call_low_level(lowlevel, arguments, layout)
|
||||
}
|
||||
x => Err(format!("the call type, {:?}, is not yet implemented", x)),
|
||||
},
|
||||
|
||||
x => Err(format!("Expression is not yet implemented {:?}", x)),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_literal(&mut self, lit: &Literal<'a>) -> Result<(), String> {
|
||||
match lit {
|
||||
Literal::Int(x) => {
|
||||
self.instructions.push(I64Const(*x as i64));
|
||||
Ok(())
|
||||
}
|
||||
Literal::Float(x) => {
|
||||
let val: f64 = *x;
|
||||
self.instructions.push(F64Const(val.to_bits()));
|
||||
Ok(())
|
||||
}
|
||||
x => Err(format!("loading literal, {:?}, is not yet implemented", x)),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_call_low_level(
|
||||
&mut self,
|
||||
lowlevel: &LowLevel,
|
||||
args: &'a [Symbol],
|
||||
return_layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
for arg in args {
|
||||
self.load_from_symbol(arg)?;
|
||||
}
|
||||
let wasm_layout = WasmLayout::new(return_layout)?;
|
||||
self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_instructions_lowlevel(
|
||||
&mut self,
|
||||
lowlevel: &LowLevel,
|
||||
return_value_type: ValueType,
|
||||
) -> Result<(), String> {
|
||||
// TODO: Find a way to organise all the lowlevel ops and layouts! There's lots!
|
||||
//
|
||||
// Some Roc low-level ops care about wrapping, clipping, sign-extending...
|
||||
// For those, we'll need to pre-process each argument before the main op,
|
||||
// so simple arrays of instructions won't work. But there are common patterns.
|
||||
let instructions: &[Instruction] = match lowlevel {
|
||||
// Wasm type might not be enough, may need to sign-extend i8 etc. Maybe in load_from_symbol?
|
||||
LowLevel::NumAdd => match return_value_type {
|
||||
ValueType::I32 => &[I32Add],
|
||||
ValueType::I64 => &[I64Add],
|
||||
ValueType::F32 => &[F32Add],
|
||||
ValueType::F64 => &[F64Add],
|
||||
},
|
||||
_ => {
|
||||
return Err(format!("unsupported low-level op {:?}", lowlevel));
|
||||
}
|
||||
};
|
||||
self.instructions.extend_from_slice(instructions);
|
||||
Ok(())
|
||||
}
|
||||
}
|
210
compiler/gen_wasm/src/from_wasm32_memory.rs
Normal file
210
compiler/gen_wasm/src/from_wasm32_memory.rs
Normal file
|
@ -0,0 +1,210 @@
|
|||
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<Self> = MaybeUninit::uninit();
|
||||
let width = std::mem::size_of::<Self>();
|
||||
|
||||
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<u8, wasmer::Array> = 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 = <u64 as FromWasm32Memory>::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<u8, wasmer::Array> = 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<T: FromWasm32Memory + Clone> FromWasm32Memory for RocList<T> {
|
||||
const SIZE_OF_WASM: usize = 8;
|
||||
const ALIGN_OF_WASM: usize = 4;
|
||||
|
||||
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
|
||||
let bytes = <u64 as FromWasm32Memory>::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 = <T as FromWasm32Memory>::decode(
|
||||
memory,
|
||||
elements + i * <T as FromWasm32Memory>::SIZE_OF_WASM as u32,
|
||||
);
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
RocList::from_slice(&items)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromWasm32Memory> 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 = <u32 as FromWasm32Memory>::decode(memory, offset);
|
||||
|
||||
let actual = <T as FromWasm32Memory>::decode(memory, elements);
|
||||
|
||||
let b = Box::new(actual);
|
||||
|
||||
std::boxed::Box::<T>::leak(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromWasm32Memory + Clone, const N: usize> 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<u8, wasmer::Array> = wasmer::WasmPtr::new(offset);
|
||||
let width = <T as FromWasm32Memory>::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 {
|
||||
<u32 as FromWasm32Memory>::decode(memory, offset) as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromWasm32Memory, U: FromWasm32Memory> 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 {
|
||||
debug_assert!(
|
||||
T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM,
|
||||
"this function does not handle alignment"
|
||||
);
|
||||
|
||||
let t = <T as FromWasm32Memory>::decode(memory, offset);
|
||||
|
||||
let u = <U as FromWasm32Memory>::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<T: FromWasm32Memory, U: FromWasm32Memory, V: FromWasm32Memory> 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 {
|
||||
debug_assert!(
|
||||
T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM,
|
||||
"this function does not handle alignment"
|
||||
);
|
||||
|
||||
debug_assert!(
|
||||
U::ALIGN_OF_WASM >= V::ALIGN_OF_WASM,
|
||||
"this function does not handle alignment"
|
||||
);
|
||||
|
||||
let t = <T as FromWasm32Memory>::decode(memory, offset);
|
||||
|
||||
let u = <U as FromWasm32Memory>::decode(memory, offset + T::ACTUAL_WIDTH as u32);
|
||||
|
||||
let v = <V as FromWasm32Memory>::decode(
|
||||
memory,
|
||||
offset + T::ACTUAL_WIDTH as u32 + U::ACTUAL_WIDTH as u32,
|
||||
);
|
||||
|
||||
(t, u, v)
|
||||
}
|
||||
}
|
62
compiler/gen_wasm/src/lib.rs
Normal file
62
compiler/gen_wasm/src/lib.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
mod backend;
|
||||
pub mod from_wasm32_memory;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use parity_wasm::builder;
|
||||
use parity_wasm::elements::Internal;
|
||||
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_mono::ir::{Proc, ProcLayout};
|
||||
use roc_mono::layout::LayoutIds;
|
||||
|
||||
use crate::backend::WasmBackend;
|
||||
|
||||
pub struct Env<'a> {
|
||||
pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot
|
||||
pub interns: Interns,
|
||||
pub exposed_to_host: MutSet<Symbol>,
|
||||
}
|
||||
|
||||
pub fn build_module<'a>(
|
||||
env: &'a Env,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Result<std::vec::Vec<u8>, String> {
|
||||
let mut backend = WasmBackend::new();
|
||||
let mut layout_ids = LayoutIds::default();
|
||||
|
||||
for ((sym, layout), proc) in procedures {
|
||||
let function_index = backend.build_proc(proc, sym)?;
|
||||
if env.exposed_to_host.contains(&sym) {
|
||||
let fn_name = layout_ids
|
||||
.get_toplevel(sym, &layout)
|
||||
.to_symbol_string(sym, &env.interns);
|
||||
|
||||
let export = builder::export()
|
||||
.field(fn_name.as_str())
|
||||
.with_internal(Internal::Function(function_index))
|
||||
.build();
|
||||
|
||||
backend.builder.push_export(export);
|
||||
}
|
||||
}
|
||||
|
||||
const MIN_MEMORY_SIZE_KB: u32 = 1024;
|
||||
const PAGE_SIZE_KB: u32 = 64;
|
||||
|
||||
let memory = builder::MemoryBuilder::new()
|
||||
.with_min(MIN_MEMORY_SIZE_KB / PAGE_SIZE_KB)
|
||||
.build();
|
||||
backend.builder.push_memory(memory);
|
||||
|
||||
let memory_export = builder::export()
|
||||
.field("memory")
|
||||
.with_internal(Internal::Memory(0))
|
||||
.build();
|
||||
backend.builder.push_export(memory_export);
|
||||
|
||||
let module = backend.builder.build();
|
||||
module
|
||||
.to_bytes()
|
||||
.map_err(|e| -> String { format!("Error serialising Wasm module {:?}", e) })
|
||||
}
|
202
compiler/gen_wasm/tests/helpers/eval.rs
Normal file
202
compiler/gen_wasm/tests/helpers/eval.rs
Normal file
|
@ -0,0 +1,202 @@
|
|||
use roc_can::builtins::builtin_defs_map;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
// use roc_std::{RocDec, RocList, RocOrder, RocStr};
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
|
||||
|
||||
for line in src.lines() {
|
||||
// indent the body!
|
||||
buffer.push_str(" ");
|
||||
buffer.push_str(line);
|
||||
buffer.push('\n');
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn helper_wasm<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
src: &str,
|
||||
stdlib: &'a roc_builtins::std::StdLib,
|
||||
_is_gen_test: bool,
|
||||
_ignore_problems: bool,
|
||||
) -> wasmer::Instance {
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
let filename = PathBuf::from("Test.roc");
|
||||
let src_dir = Path::new("fake/test/path");
|
||||
|
||||
let module_src;
|
||||
let temp;
|
||||
if src.starts_with("app") {
|
||||
// this is already a module
|
||||
module_src = src;
|
||||
} else {
|
||||
// this is an expression, promote it to a module
|
||||
temp = promote_expr_to_module(src);
|
||||
module_src = &temp;
|
||||
}
|
||||
|
||||
let exposed_types = MutMap::default();
|
||||
let loaded = roc_load::file::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
filename,
|
||||
module_src,
|
||||
stdlib,
|
||||
src_dir,
|
||||
exposed_types,
|
||||
8,
|
||||
builtin_defs_map,
|
||||
);
|
||||
|
||||
let loaded = loaded.expect("failed to load module");
|
||||
|
||||
use roc_load::file::MonomorphizedModule;
|
||||
let MonomorphizedModule {
|
||||
procedures: top_procedures,
|
||||
interns,
|
||||
exposed_to_host,
|
||||
..
|
||||
} = loaded;
|
||||
|
||||
let mut procedures = MutMap::default();
|
||||
|
||||
for (key, proc) in top_procedures {
|
||||
procedures.insert(key, proc);
|
||||
}
|
||||
|
||||
// You can comment and uncomment this block out to get more useful information
|
||||
// while you're working on the wasm backend!
|
||||
// {
|
||||
// println!("=========== Procedures ==========");
|
||||
// println!("{:?}", procedures);
|
||||
// println!("=================================\n");
|
||||
|
||||
// println!("=========== Interns ==========");
|
||||
// println!("{:?}", interns);
|
||||
// println!("=================================\n");
|
||||
|
||||
// println!("=========== Exposed ==========");
|
||||
// println!("{:?}", exposed_to_host);
|
||||
// println!("=================================\n");
|
||||
// }
|
||||
|
||||
let exposed_to_host = exposed_to_host.keys().copied().collect::<MutSet<_>>();
|
||||
|
||||
let env = roc_gen_wasm::Env {
|
||||
arena,
|
||||
interns,
|
||||
exposed_to_host,
|
||||
};
|
||||
|
||||
let module_bytes = roc_gen_wasm::build_module(&env, procedures).unwrap();
|
||||
|
||||
// for debugging (e.g. with wasm2wat)
|
||||
if false {
|
||||
use std::io::Write;
|
||||
let path = "/home/brian/Documents/roc/compiler/gen_wasm/debug.wasm";
|
||||
|
||||
match std::fs::File::create(path) {
|
||||
Err(e) => eprintln!("Problem creating wasm debug file: {:?}", e),
|
||||
Ok(mut file) => {
|
||||
file.write_all(&module_bytes).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now, do wasmer stuff
|
||||
|
||||
use wasmer::{Instance, Module, Store};
|
||||
|
||||
let store = Store::default();
|
||||
// let module = Module::from_file(&store, &test_wasm_path).unwrap();
|
||||
let module = Module::from_binary(&store, &module_bytes).unwrap();
|
||||
|
||||
// First, we create the `WasiEnv`
|
||||
use wasmer_wasi::WasiState;
|
||||
let mut wasi_env = WasiState::new("hello").finalize().unwrap();
|
||||
|
||||
// Then, we get the import object related to our WASI
|
||||
// and attach it to the Wasm instance.
|
||||
let import_object = wasi_env
|
||||
.import_object(&module)
|
||||
.unwrap_or_else(|_| wasmer::imports!());
|
||||
|
||||
Instance::new(&module, &import_object).unwrap()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn assert_wasm_evals_to_help<T>(src: &str, ignore_problems: bool) -> Result<T, String>
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
let arena = bumpalo::Bump::new();
|
||||
|
||||
// NOTE the stdlib must be in the arena; just taking a reference will segfault
|
||||
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
|
||||
|
||||
let is_gen_test = true;
|
||||
let instance =
|
||||
crate::helpers::eval::helper_wasm(&arena, src, stdlib, is_gen_test, ignore_problems);
|
||||
|
||||
let main_function = instance.exports.get_function("#UserApp_main_1").unwrap();
|
||||
|
||||
match main_function.call(&[]) {
|
||||
Err(e) => Err(format!("{:?}", e)),
|
||||
Ok(result) => {
|
||||
let integer = match result[0] {
|
||||
wasmer::Value::I64(a) => a,
|
||||
wasmer::Value::F64(a) => a.to_bits() as i64,
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
let output_ptr: &T;
|
||||
unsafe {
|
||||
output_ptr = std::mem::transmute::<&i64, &T>(&integer);
|
||||
}
|
||||
|
||||
Ok(*output_ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_wasm_evals_to {
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr, $ignore_problems:expr) => {
|
||||
match $crate::helpers::eval::assert_wasm_evals_to_help::<$ty>($src, $ignore_problems) {
|
||||
Err(msg) => println!("{:?}", msg),
|
||||
Ok(actual) => {
|
||||
#[allow(clippy::bool_assert_comparison)]
|
||||
assert_eq!($transform(actual), $expected)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($src:expr, $expected:expr, $ty:ty) => {
|
||||
$crate::assert_wasm_evals_to!($src, $expected, $ty, $crate::helpers::eval::identity, false);
|
||||
};
|
||||
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||
$crate::assert_wasm_evals_to!($src, $expected, $ty, $transform, false);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_evals_to {
|
||||
($src:expr, $expected:expr, $ty:ty) => {{
|
||||
assert_evals_to!($src, $expected, $ty, $crate::helpers::eval::identity);
|
||||
}};
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||
// Same as above, except with an additional transformation argument.
|
||||
{
|
||||
$crate::assert_wasm_evals_to!($src, $expected, $ty, $transform, false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn identity<T>(value: T) -> T {
|
||||
value
|
||||
}
|
44
compiler/gen_wasm/tests/helpers/mod.rs
Normal file
44
compiler/gen_wasm/tests/helpers/mod.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
extern crate bumpalo;
|
||||
|
||||
#[macro_use]
|
||||
pub mod eval;
|
||||
|
||||
/// Used in the with_larger_debug_stack() function, for tests that otherwise
|
||||
/// run out of stack space in debug builds (but don't in --release builds)
|
||||
#[allow(dead_code)]
|
||||
const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024;
|
||||
|
||||
/// Without this, some tests pass in `cargo test --release` but fail without
|
||||
/// the --release flag because they run out of stack space. This increases
|
||||
/// stack size for debug builds only, while leaving the stack space at the default
|
||||
/// amount for release builds.
|
||||
#[allow(dead_code)]
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn with_larger_debug_stack<F>(run_test: F)
|
||||
where
|
||||
F: FnOnce(),
|
||||
F: Send,
|
||||
F: 'static,
|
||||
{
|
||||
std::thread::Builder::new()
|
||||
.stack_size(EXPANDED_STACK_SIZE)
|
||||
.spawn(run_test)
|
||||
.expect("Error while spawning expanded dev stack size thread")
|
||||
.join()
|
||||
.expect("Error while joining expanded dev stack size thread")
|
||||
}
|
||||
|
||||
/// In --release builds, don't increase the stack size. Run the test normally.
|
||||
/// This way, we find out if any of our tests are blowing the stack even after
|
||||
/// optimizations in release builds.
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[inline(always)]
|
||||
pub fn with_larger_debug_stack<F>(run_test: F)
|
||||
where
|
||||
F: FnOnce() -> (),
|
||||
F: Send,
|
||||
F: 'static,
|
||||
{
|
||||
run_test()
|
||||
}
|
863
compiler/gen_wasm/tests/wasm_num.rs
Normal file
863
compiler/gen_wasm/tests/wasm_num.rs
Normal file
|
@ -0,0 +1,863 @@
|
|||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
#[macro_use]
|
||||
extern crate indoc;
|
||||
|
||||
extern crate bumpalo;
|
||||
extern crate libc;
|
||||
|
||||
#[macro_use]
|
||||
mod helpers;
|
||||
|
||||
#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
|
||||
mod dev_num {
|
||||
#[test]
|
||||
fn i64_values() {
|
||||
assert_evals_to!("0", 0, i64);
|
||||
assert_evals_to!("-0", 0, i64);
|
||||
assert_evals_to!("-1", -1, i64);
|
||||
assert_evals_to!("1", 1, i64);
|
||||
assert_evals_to!("9_000_000_000_000", 9_000_000_000_000, i64);
|
||||
assert_evals_to!("-9_000_000_000_000", -9_000_000_000_000, i64);
|
||||
assert_evals_to!("0b1010", 0b1010, i64);
|
||||
assert_evals_to!("0o17", 0o17, i64);
|
||||
assert_evals_to!("0x1000_0000_0000_0000", 0x1000_0000_0000_0000, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f64_values() {
|
||||
assert_evals_to!("0.0", 0.0, f64);
|
||||
assert_evals_to!("-0.0", 0.0, f64);
|
||||
assert_evals_to!("1.0", 1.0, f64);
|
||||
assert_evals_to!("-1.0", -1.0, f64);
|
||||
assert_evals_to!("3.1415926535897932", 3.141_592_653_589_793, f64);
|
||||
assert_evals_to!(&format!("{:0.1}", f64::MIN), f64::MIN, f64);
|
||||
assert_evals_to!(&format!("{:0.1}", f64::MAX), f64::MAX, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_add_i64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
1 + 2 + 3
|
||||
"#
|
||||
),
|
||||
6,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn gen_add_f64() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 1.1 + 2.4 + 3
|
||||
// "#
|
||||
// ),
|
||||
// 6.5,
|
||||
// f64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_sub_i64() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 1 - 2 - 3
|
||||
// "#
|
||||
// ),
|
||||
// -4,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_mul_i64() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 2 * 4 * 6
|
||||
// "#
|
||||
// ),
|
||||
// 48,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn i64_force_stack() {
|
||||
// This claims 33 registers. One more than Arm and RISC-V, and many more than x86-64.
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
a = 0
|
||||
b = 1
|
||||
c = 2
|
||||
d = 3
|
||||
e = 4
|
||||
f = 5
|
||||
g = 6
|
||||
h = 7
|
||||
i = 8
|
||||
j = 9
|
||||
k = 10
|
||||
l = 11
|
||||
m = 12
|
||||
n = 13
|
||||
o = 14
|
||||
p = 15
|
||||
q = 16
|
||||
r = 17
|
||||
s = 18
|
||||
t = 19
|
||||
u = 20
|
||||
v = 21
|
||||
w = 22
|
||||
x = 23
|
||||
y = 24
|
||||
z = 25
|
||||
aa = 26
|
||||
ab = 27
|
||||
ac = 28
|
||||
ad = 29
|
||||
ae = 30
|
||||
af = 31
|
||||
ag = 32
|
||||
|
||||
a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + aa + ab + ac + ad + ae + af + ag
|
||||
"#
|
||||
),
|
||||
528,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn i64_abs() {
|
||||
// assert_evals_to!("Num.abs -6", 6, i64);
|
||||
// assert_evals_to!("Num.abs 7", 7, i64);
|
||||
// assert_evals_to!("Num.abs 0", 0, i64);
|
||||
// assert_evals_to!("Num.abs -0", 0, i64);
|
||||
// assert_evals_to!("Num.abs -1", 1, i64);
|
||||
// assert_evals_to!("Num.abs 1", 1, i64);
|
||||
// assert_evals_to!("Num.abs 9_000_000_000_000", 9_000_000_000_000, i64);
|
||||
// assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_int_eq() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 4 == 4
|
||||
// "#
|
||||
// ),
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 3 == 4
|
||||
// "#
|
||||
// ),
|
||||
// false,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_basic_fn() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// always42 : Num.Num (Num.Integer Num.Signed64) -> Num.Num (Num.Integer Num.Signed64)
|
||||
// always42 = \_ -> 42
|
||||
|
||||
// always42 5
|
||||
// "#
|
||||
// ),
|
||||
// 42,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_wrap_add_nums() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// add2 = \num1, num2 -> num1 + num2
|
||||
|
||||
// add2 4 5
|
||||
// "#
|
||||
// ),
|
||||
// 9,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_wrap_add_nums_force_stack() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// add9 = \num1, num2, num3, num4, num5, num6, num7, num8, num9 -> num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8 + num9
|
||||
|
||||
// add9 1 2 3 4 5 6 7 8 9
|
||||
// "#
|
||||
// ),
|
||||
// 45,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn pow_int() {
|
||||
// assert_evals_to!("Num.powInt 2 3", 8, i64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn acos() {
|
||||
// assert_evals_to!("Num.acos 0.5", 1.0471975511965979, f64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn asin() {
|
||||
// assert_evals_to!("Num.asin 0.5", 0.5235987755982989, f64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn atan() {
|
||||
// assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_if_fn() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// limitedNegate = \num ->
|
||||
// x =
|
||||
// if num == 1 then
|
||||
// -1
|
||||
// else if num == -1 then
|
||||
// 1
|
||||
// else
|
||||
// num
|
||||
// x
|
||||
|
||||
// limitedNegate 1
|
||||
// "#
|
||||
// ),
|
||||
// -1,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_fib_fn() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// fib = \n ->
|
||||
// if n == 0 then
|
||||
// 0
|
||||
// else if n == 1 then
|
||||
// 1
|
||||
// else
|
||||
// (fib (n - 1)) + (fib (n - 2))
|
||||
|
||||
// fib 10
|
||||
// "#
|
||||
// ),
|
||||
// 55,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn f64_abs() {
|
||||
// assert_evals_to!("Num.abs -4.7", 4.7, f64);
|
||||
// assert_evals_to!("Num.abs 5.8", 5.8, f64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn f64_round() {
|
||||
// assert_evals_to!("Num.round 3.6", 4, i64);
|
||||
// assert_evals_to!("Num.round 3.4", 3, i64);
|
||||
// assert_evals_to!("Num.round 2.5", 3, i64);
|
||||
// assert_evals_to!("Num.round -2.3", -2, i64);
|
||||
// assert_evals_to!("Num.round -2.5", -3, i64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn f64_sqrt() {
|
||||
// // FIXME this works with normal types, but fails when checking uniqueness types
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Num.sqrt 100 is
|
||||
// Ok val -> val
|
||||
// Err _ -> -1
|
||||
// "#
|
||||
// ),
|
||||
// 10.0,
|
||||
// f64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_float_eq() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 1.0 == 1.0
|
||||
// "#
|
||||
// ),
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_div_f64() {
|
||||
// // FIXME this works with normal types, but fails when checking uniqueness types
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when 48 / 2 is
|
||||
// Ok val -> val
|
||||
// Err _ -> -1
|
||||
// "#
|
||||
// ),
|
||||
// 24.0,
|
||||
// f64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_int_neq() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 4 != 5
|
||||
// "#
|
||||
// ),
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_wrap_int_neq() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// wrappedNotEq : a, a -> Bool
|
||||
// wrappedNotEq = \num1, num2 ->
|
||||
// num1 != num2
|
||||
|
||||
// wrappedNotEq 2 3
|
||||
// "#
|
||||
// ),
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_sub_f64() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 1.5 - 2.4 - 3
|
||||
// "#
|
||||
// ),
|
||||
// -3.9,
|
||||
// f64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_div_i64() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when 1000 // 10 is
|
||||
// Ok val -> val
|
||||
// Err _ -> -1
|
||||
// "#
|
||||
// ),
|
||||
// 100,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_div_by_zero_i64() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when 1000 // 0 is
|
||||
// Err DivByZero -> 99
|
||||
// _ -> -24
|
||||
// "#
|
||||
// ),
|
||||
// 99,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_rem_i64() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Num.rem 8 3 is
|
||||
// Ok val -> val
|
||||
// Err _ -> -1
|
||||
// "#
|
||||
// ),
|
||||
// 2,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_rem_div_by_zero_i64() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Num.rem 8 0 is
|
||||
// Err DivByZero -> 4
|
||||
// Ok _ -> -23
|
||||
// "#
|
||||
// ),
|
||||
// 4,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_is_zero_i64() {
|
||||
// assert_evals_to!("Num.isZero 0", true, bool);
|
||||
// assert_evals_to!("Num.isZero 1", false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_is_positive_i64() {
|
||||
// assert_evals_to!("Num.isPositive 0", false, bool);
|
||||
// assert_evals_to!("Num.isPositive 1", true, bool);
|
||||
// assert_evals_to!("Num.isPositive -5", false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_is_negative_i64() {
|
||||
// assert_evals_to!("Num.isNegative 0", false, bool);
|
||||
// assert_evals_to!("Num.isNegative 3", false, bool);
|
||||
// assert_evals_to!("Num.isNegative -2", true, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_is_positive_f64() {
|
||||
// assert_evals_to!("Num.isPositive 0.0", false, bool);
|
||||
// assert_evals_to!("Num.isPositive 4.7", true, bool);
|
||||
// assert_evals_to!("Num.isPositive -8.5", false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_is_negative_f64() {
|
||||
// assert_evals_to!("Num.isNegative 0.0", false, bool);
|
||||
// assert_evals_to!("Num.isNegative 9.9", false, bool);
|
||||
// assert_evals_to!("Num.isNegative -4.4", true, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_is_zero_f64() {
|
||||
// assert_evals_to!("Num.isZero 0", true, bool);
|
||||
// assert_evals_to!("Num.isZero 0_0", true, bool);
|
||||
// assert_evals_to!("Num.isZero 0.0", true, bool);
|
||||
// assert_evals_to!("Num.isZero 1", false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_is_odd() {
|
||||
// assert_evals_to!("Num.isOdd 4", false, bool);
|
||||
// assert_evals_to!("Num.isOdd 5", true, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_is_even() {
|
||||
// assert_evals_to!("Num.isEven 6", true, bool);
|
||||
// assert_evals_to!("Num.isEven 7", false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn sin() {
|
||||
// assert_evals_to!("Num.sin 0", 0.0, f64);
|
||||
// assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn cos() {
|
||||
// assert_evals_to!("Num.cos 0", 1.0, f64);
|
||||
// assert_evals_to!("Num.cos 3.14159265359", -1.0, f64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn tan() {
|
||||
// assert_evals_to!("Num.tan 0", 0.0, f64);
|
||||
// assert_evals_to!("Num.tan 1", 1.557407724654902, f64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn lt_i64() {
|
||||
// assert_evals_to!("1 < 2", true, bool);
|
||||
// assert_evals_to!("1 < 1", false, bool);
|
||||
// assert_evals_to!("2 < 1", false, bool);
|
||||
// assert_evals_to!("0 < 0", false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn lte_i64() {
|
||||
// assert_evals_to!("1 <= 1", true, bool);
|
||||
// assert_evals_to!("2 <= 1", false, bool);
|
||||
// assert_evals_to!("1 <= 2", true, bool);
|
||||
// assert_evals_to!("0 <= 0", true, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gt_i64() {
|
||||
// assert_evals_to!("2 > 1", true, bool);
|
||||
// assert_evals_to!("2 > 2", false, bool);
|
||||
// assert_evals_to!("1 > 1", false, bool);
|
||||
// assert_evals_to!("0 > 0", false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gte_i64() {
|
||||
// assert_evals_to!("1 >= 1", true, bool);
|
||||
// assert_evals_to!("1 >= 2", false, bool);
|
||||
// assert_evals_to!("2 >= 1", true, bool);
|
||||
// assert_evals_to!("0 >= 0", true, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn lt_f64() {
|
||||
// assert_evals_to!("1.1 < 1.2", true, bool);
|
||||
// assert_evals_to!("1.1 < 1.1", false, bool);
|
||||
// assert_evals_to!("1.2 < 1.1", false, bool);
|
||||
// assert_evals_to!("0.0 < 0.0", false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn lte_f64() {
|
||||
// assert_evals_to!("1.1 <= 1.1", true, bool);
|
||||
// assert_evals_to!("1.2 <= 1.1", false, bool);
|
||||
// assert_evals_to!("1.1 <= 1.2", true, bool);
|
||||
// assert_evals_to!("0.0 <= 0.0", true, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gt_f64() {
|
||||
// assert_evals_to!("2.2 > 1.1", true, bool);
|
||||
// assert_evals_to!("2.2 > 2.2", false, bool);
|
||||
// assert_evals_to!("1.1 > 2.2", false, bool);
|
||||
// assert_evals_to!("0.0 > 0.0", false, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gte_f64() {
|
||||
// assert_evals_to!("1.1 >= 1.1", true, bool);
|
||||
// assert_evals_to!("1.1 >= 1.2", false, bool);
|
||||
// assert_evals_to!("1.2 >= 1.1", true, bool);
|
||||
// assert_evals_to!("0.0 >= 0.0", true, bool);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_order_of_arithmetic_ops() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 1 + 3 * 7 - 2
|
||||
// "#
|
||||
// ),
|
||||
// 20,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_order_of_arithmetic_ops_complex_float() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 3 - 48 * 2.0
|
||||
// "#
|
||||
// ),
|
||||
// -93.0,
|
||||
// f64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn if_guard_bind_variable_false() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// wrapper = \{} ->
|
||||
// when 10 is
|
||||
// x if x == 5 -> 0
|
||||
// _ -> 42
|
||||
|
||||
// wrapper {}
|
||||
// "#
|
||||
// ),
|
||||
// 42,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn if_guard_bind_variable_true() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// wrapper = \{} ->
|
||||
// when 10 is
|
||||
// x if x == 10 -> 42
|
||||
// _ -> 0
|
||||
|
||||
// wrapper {}
|
||||
// "#
|
||||
// ),
|
||||
// 42,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn tail_call_elimination() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// sum = \n, accum ->
|
||||
// when n is
|
||||
// 0 -> accum
|
||||
// _ -> sum (n - 1) (n + accum)
|
||||
|
||||
// sum 1_000_000 0
|
||||
// "#
|
||||
// ),
|
||||
// 500000500000,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn int_negate() {
|
||||
// assert_evals_to!("Num.neg 123", -123, i64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_wrap_int_neg() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// wrappedNeg = \num -> -num
|
||||
|
||||
// wrappedNeg 3
|
||||
// "#
|
||||
// ),
|
||||
// -3,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn int_to_float() {
|
||||
// assert_evals_to!("Num.toFloat 0x9", 9.0, f64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn num_to_float() {
|
||||
// assert_evals_to!("Num.toFloat 9", 9.0, f64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn float_to_float() {
|
||||
// assert_evals_to!("Num.toFloat 0.5", 0.5, f64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn int_compare() {
|
||||
// assert_evals_to!("Num.compare 0 1", RocOrder::Lt, RocOrder);
|
||||
// assert_evals_to!("Num.compare 1 1", RocOrder::Eq, RocOrder);
|
||||
// assert_evals_to!("Num.compare 1 0", RocOrder::Gt, RocOrder);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn float_compare() {
|
||||
// assert_evals_to!("Num.compare 0.01 3.14", RocOrder::Lt, RocOrder);
|
||||
// assert_evals_to!("Num.compare 3.14 3.14", RocOrder::Eq, RocOrder);
|
||||
// assert_evals_to!("Num.compare 3.14 0.01", RocOrder::Gt, RocOrder);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn pow() {
|
||||
// assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn ceiling() {
|
||||
// assert_evals_to!("Num.ceiling 1.1", 2, i64);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn floor() {
|
||||
// assert_evals_to!("Num.floor 1.9", 1, i64);
|
||||
// }
|
||||
|
||||
// // #[test]
|
||||
// // #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
|
||||
// // fn int_overflow() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // 9_223_372_036_854_775_807 + 1
|
||||
// // "#
|
||||
// // ),
|
||||
// // 0,
|
||||
// // i64
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// #[test]
|
||||
// fn int_add_checked() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Num.addChecked 1 2 is
|
||||
// Ok v -> v
|
||||
// _ -> -1
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Num.addChecked 9_223_372_036_854_775_807 1 is
|
||||
// Err Overflow -> -1
|
||||
// Ok v -> v
|
||||
// "#
|
||||
// ),
|
||||
// -1,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn int_add_wrap() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Num.addWrap 9_223_372_036_854_775_807 1
|
||||
// "#
|
||||
// ),
|
||||
// std::i64::MIN,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn float_add_checked_pass() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Num.addChecked 1.0 0.0 is
|
||||
// Ok v -> v
|
||||
// Err Overflow -> -1.0
|
||||
// "#
|
||||
// ),
|
||||
// 1.0,
|
||||
// f64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn float_add_checked_fail() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when Num.addChecked 1.7976931348623157e308 1.7976931348623157e308 is
|
||||
// Err Overflow -> -1
|
||||
// Ok v -> v
|
||||
// "#
|
||||
// ),
|
||||
// -1.0,
|
||||
// f64
|
||||
// );
|
||||
// }
|
||||
|
||||
// // #[test]
|
||||
// // #[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)]
|
||||
// // fn float_overflow() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // 1.7976931348623157e308 + 1.7976931348623157e308
|
||||
// // "#
|
||||
// // ),
|
||||
// // 0.0,
|
||||
// // f64
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// #[test]
|
||||
// fn max_i128() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Num.maxI128
|
||||
// "#
|
||||
// ),
|
||||
// i128::MAX,
|
||||
// i128
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn num_max_int() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Num.maxInt
|
||||
// "#
|
||||
// ),
|
||||
// i64::MAX,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn num_min_int() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// Num.minInt
|
||||
// "#
|
||||
// ),
|
||||
// i64::MIN,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
}
|
937
compiler/gen_wasm/tests/wasm_records.rs
Normal file
937
compiler/gen_wasm/tests/wasm_records.rs
Normal file
|
@ -0,0 +1,937 @@
|
|||
#[macro_use]
|
||||
extern crate indoc;
|
||||
|
||||
#[macro_use]
|
||||
mod helpers;
|
||||
|
||||
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
|
||||
mod dev_records {
|
||||
// #[test]
|
||||
// fn basic_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { y: 17, x: 15, z: 19 }.x
|
||||
// "#
|
||||
// ),
|
||||
// 15,
|
||||
// i64
|
||||
// );
|
||||
//
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 15, y: 17, z: 19 }.y
|
||||
// "#
|
||||
// ),
|
||||
// 17,
|
||||
// i64
|
||||
// );
|
||||
//
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 15, y: 17, z: 19 }.z
|
||||
// "#
|
||||
// ),
|
||||
// 19,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn nested_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x
|
||||
// "#
|
||||
// ),
|
||||
// 15,
|
||||
// i64
|
||||
// );
|
||||
//
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a
|
||||
// "#
|
||||
// ),
|
||||
// 12,
|
||||
// i64
|
||||
// );
|
||||
//
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b
|
||||
// "#
|
||||
// ),
|
||||
// 15,
|
||||
// i64
|
||||
// );
|
||||
//
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c
|
||||
// "#
|
||||
// ),
|
||||
// 2,
|
||||
// i64
|
||||
// );
|
||||
//
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z
|
||||
// "#
|
||||
// ),
|
||||
// 19,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn f64_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { y: 17.2, x: 15.1, z: 19.3 }
|
||||
//
|
||||
// rec.x
|
||||
// "#
|
||||
// ),
|
||||
// 15.1,
|
||||
// f64
|
||||
// );
|
||||
//
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { y: 17.2, x: 15.1, z: 19.3 }
|
||||
//
|
||||
// rec.y
|
||||
// "#
|
||||
// ),
|
||||
// 17.2,
|
||||
// f64
|
||||
// );
|
||||
//
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { y: 17.2, x: 15.1, z: 19.3 }
|
||||
//
|
||||
// rec.z
|
||||
// "#
|
||||
// ),
|
||||
// 19.3,
|
||||
// f64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn fn_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// getRec = \x -> { y: 17, x, z: 19 }
|
||||
|
||||
// (getRec 15).x
|
||||
// "#
|
||||
// ),
|
||||
// 15,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { x: 15, y: 17, z: 19 }
|
||||
|
||||
// rec.y
|
||||
// "#
|
||||
// ),
|
||||
// 17,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { x: 15, y: 17, z: 19 }
|
||||
|
||||
// rec.z
|
||||
// "#
|
||||
// ),
|
||||
// 19,
|
||||
// i64
|
||||
// );
|
||||
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { x: 15, y: 17, z: 19 }
|
||||
|
||||
// rec.z + rec.x
|
||||
// "#
|
||||
// ),
|
||||
// 34,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn def_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { y: 17, x: 15, z: 19 }
|
||||
//
|
||||
// rec.x
|
||||
// "#
|
||||
// ),
|
||||
// 15,
|
||||
// i64
|
||||
// );
|
||||
//
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { x: 15, y: 17, z: 19 }
|
||||
//
|
||||
// rec.y
|
||||
// "#
|
||||
// ),
|
||||
// 17,
|
||||
// i64
|
||||
// );
|
||||
//
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { x: 15, y: 17, z: 19 }
|
||||
//
|
||||
// rec.z
|
||||
// "#
|
||||
// ),
|
||||
// 19,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn when_on_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when { x: 0x2 } is
|
||||
// { x } -> x + 3
|
||||
// "#
|
||||
// ),
|
||||
// 5,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn when_record_with_guard_pattern() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when { x: 0x2, y: 3.14 } is
|
||||
// { x: var } -> var + 3
|
||||
// "#
|
||||
// ),
|
||||
// 5,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn let_with_record_pattern() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x } = { x: 0x2, y: 3.14 }
|
||||
//
|
||||
// x
|
||||
// "#
|
||||
// ),
|
||||
// 2,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn record_guard_pattern() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when { x: 0x2, y: 3.14 } is
|
||||
// { x: 0x4 } -> 5
|
||||
// { x } -> x + 3
|
||||
// "#
|
||||
// ),
|
||||
// 5,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn twice_record_access() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// x = {a: 0x2, b: 0x3 }
|
||||
//
|
||||
// x.a + x.b
|
||||
// "#
|
||||
// ),
|
||||
// 5,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
// #[test]
|
||||
// fn empty_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// v = {}
|
||||
//
|
||||
// v
|
||||
// "#
|
||||
// ),
|
||||
// (),
|
||||
// ()
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn i64_record1_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 3 }
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn i64_record2_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 3, y: 5 }
|
||||
// "#
|
||||
// ),
|
||||
// (3, 5),
|
||||
// (i64, i64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// // #[test]
|
||||
// // fn i64_record3_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // { x: 3, y: 5, z: 17 }
|
||||
// // "#
|
||||
// // ),
|
||||
// // (3, 5, 17),
|
||||
// // (i64, i64, i64)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// #[test]
|
||||
// fn f64_record2_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 3.1, y: 5.1 }
|
||||
// "#
|
||||
// ),
|
||||
// (3.1, 5.1),
|
||||
// (f64, f64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// // #[test]
|
||||
// // fn f64_record3_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // { x: 3.1, y: 5.1, z: 17.1 }
|
||||
// // "#
|
||||
// // ),
|
||||
// // (3.1, 5.1, 17.1),
|
||||
// // (f64, f64, f64)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// // #[test]
|
||||
// // fn bool_record4_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // record : { a : Bool, b : Bool, c : Bool, d : Bool }
|
||||
// // record = { a: True, b: True, c : True, d : Bool }
|
||||
|
||||
// // record
|
||||
// // "#
|
||||
// // ),
|
||||
// // (true, false, false, true),
|
||||
// // (bool, bool, bool, bool)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// #[test]
|
||||
// fn i64_record1_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3 }
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// // #[test]
|
||||
// // fn i64_record9_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 }
|
||||
// // "#
|
||||
// // ),
|
||||
// // (3, 5, 17, 1, 9, 12, 13, 14, 15),
|
||||
// // (i64, i64, i64, i64, i64, i64, i64, i64, i64)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// // #[test]
|
||||
// // fn f64_record3_literal() {
|
||||
// // assert_evals_to!(
|
||||
// // indoc!(
|
||||
// // r#"
|
||||
// // { x: 3.1, y: 5.1, z: 17.1 }
|
||||
// // "#
|
||||
// // ),
|
||||
// // (3.1, 5.1, 17.1),
|
||||
// // (f64, f64, f64)
|
||||
// // );
|
||||
// // }
|
||||
|
||||
// #[test]
|
||||
// fn bool_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// x : Bool
|
||||
// x = True
|
||||
|
||||
// x
|
||||
// "#
|
||||
// ),
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_when_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \r ->
|
||||
// when r is
|
||||
// { x: Blue, y ? 3 } -> y
|
||||
// { x: Red, y ? 5 } -> y
|
||||
|
||||
// main =
|
||||
// a = f { x: Blue, y: 7 }
|
||||
// b = f { x: Blue }
|
||||
// c = f { x: Red, y: 11 }
|
||||
// d = f { x: Red }
|
||||
|
||||
// a * b * c * d
|
||||
// "#
|
||||
// ),
|
||||
// 3 * 5 * 7 * 11,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_when_use_default_nested() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \r ->
|
||||
// when r is
|
||||
// { x: Blue, y ? 3 } -> y
|
||||
// { x: Red, y ? 5 } -> y
|
||||
|
||||
// a = f { x: Blue, y: 7 }
|
||||
// b = f { x: Blue }
|
||||
// c = f { x: Red, y: 11 }
|
||||
// d = f { x: Red }
|
||||
|
||||
// a * b * c * d
|
||||
// "#
|
||||
// ),
|
||||
// 3 * 5 * 7 * 11,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_when_no_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// main =
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_when_no_use_default_nested() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_let_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// main =
|
||||
// f { y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 19,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_let_no_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// main =
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_let_no_use_default_nested() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \r ->
|
||||
// { x ? 10, y } = r
|
||||
// x + y
|
||||
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_function_use_default() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \{ x ? 10, y } -> x + y
|
||||
|
||||
// f { y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 19,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// #[ignore]
|
||||
// fn optional_field_function_no_use_default() {
|
||||
// // blocked on https://github.com/rtfeldman/roc/issues/786
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// app "test" provides [ main ] to "./platform"
|
||||
|
||||
// f = \{ x ? 10, y } -> x + y
|
||||
|
||||
// main =
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// #[ignore]
|
||||
// fn optional_field_function_no_use_default_nested() {
|
||||
// // blocked on https://github.com/rtfeldman/roc/issues/786
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \{ x ? 10, y } -> x + y
|
||||
|
||||
// f { x: 4, y: 9 }
|
||||
// "#
|
||||
// ),
|
||||
// 13,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_singleton_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when { x : 4 } is
|
||||
// { x ? 3 } -> x
|
||||
// "#
|
||||
// ),
|
||||
// 4,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_empty_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// when { } is
|
||||
// { x ? 3 } -> x
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_2() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 3, y: 5 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5],
|
||||
// [i64; 2]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_3() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x: 3, y: 5, z: 4 }
|
||||
// "#
|
||||
// ),
|
||||
// (3, 5, 4),
|
||||
// (i64, i64, i64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_4() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3, b: 5, c: 4, d: 2 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5, 4, 2],
|
||||
// [i64; 4]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_5() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3, b: 5, c: 4, d: 2, e: 1 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5, 4, 2, 1],
|
||||
// [i64; 5]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_6() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5, 4, 2, 1, 7],
|
||||
// [i64; 6]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_7() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
|
||||
// "#
|
||||
// ),
|
||||
// [3, 5, 4, 2, 1, 7, 8],
|
||||
// [i64; 7]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_float_int() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3.14, b: 0x1 }
|
||||
// "#
|
||||
// ),
|
||||
// (3.14, 0x1),
|
||||
// (f64, i64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_int_float() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 0x1, b: 3.14 }
|
||||
// "#
|
||||
// ),
|
||||
// (0x1, 3.14),
|
||||
// (i64, f64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_float_float() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 6.28, b: 3.14 }
|
||||
// "#
|
||||
// ),
|
||||
// (6.28, 3.14),
|
||||
// (f64, f64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_record_float_float_float() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 6.28, b: 3.14, c: 0.1 }
|
||||
// "#
|
||||
// ),
|
||||
// (6.28, 3.14, 0.1),
|
||||
// (f64, f64, f64)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_nested_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } }
|
||||
// "#
|
||||
// ),
|
||||
// (0x0, (6.28, 3.14, 0.1)),
|
||||
// (i64, (f64, f64, f64))
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn accessor() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 }
|
||||
// "#
|
||||
// ),
|
||||
// 7,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn accessor_single_element_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// .foo { foo: 4 }
|
||||
// "#
|
||||
// ),
|
||||
// 4,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn update_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// rec = { foo: 42, bar: 6 }
|
||||
|
||||
// { rec & foo: rec.foo + 1 }
|
||||
// "#
|
||||
// ),
|
||||
// (6, 43),
|
||||
// (i64, i64)
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn update_single_element_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { foo: 42}
|
||||
|
||||
{ rec & foo: rec.foo + 1 }
|
||||
"#
|
||||
),
|
||||
43,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn booleans_in_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!("{ x: 1 == 1, y: 1 == 1 }"),
|
||||
// (true, true),
|
||||
// (bool, bool)
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// indoc!("{ x: 1 != 1, y: 1 == 1 }"),
|
||||
// (false, true),
|
||||
// (bool, bool)
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// indoc!("{ x: 1 == 1, y: 1 != 1 }"),
|
||||
// (true, false),
|
||||
// (bool, bool)
|
||||
// );
|
||||
// assert_evals_to!(
|
||||
// indoc!("{ x: 1 != 1, y: 1 != 1 }"),
|
||||
// (false, false),
|
||||
// (bool, bool)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn alignment_in_record() {
|
||||
// assert_evals_to!(
|
||||
// indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"),
|
||||
// (32i64, true, 2u8),
|
||||
// (i64, bool, u8)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn blue_and_present() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \r ->
|
||||
// when r is
|
||||
// { x: Blue, y ? 3 } -> y
|
||||
// { x: Red, y ? 5 } -> y
|
||||
|
||||
// f { x: Blue, y: 7 }
|
||||
// "#
|
||||
// ),
|
||||
// 7,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn blue_and_absent() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// f = \r ->
|
||||
// when r is
|
||||
// { x: Blue, y ? 3 } -> y
|
||||
// { x: Red, y ? 5 } -> y
|
||||
|
||||
// f { x: Blue }
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
}
|
|
@ -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)?;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -95,11 +95,11 @@ pub fn occurring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
|
|||
|
||||
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,
|
||||
|
|
|
@ -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<Symbol> {
|
||||
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 [")
|
||||
|
@ -2728,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<Literal<'a>> {
|
||||
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,
|
||||
|
@ -3423,8 +3498,20 @@ pub fn with_hole<'a>(
|
|||
loc_elems,
|
||||
} => {
|
||||
let mut arg_symbols = 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 mut elements = Vec::with_capacity_in(loc_elems.len(), env.arena);
|
||||
|
||||
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();
|
||||
|
||||
|
@ -3434,7 +3521,7 @@ pub fn with_hole<'a>(
|
|||
|
||||
let expr = Expr::Array {
|
||||
elem_layout,
|
||||
elems: arg_symbols,
|
||||
elems: elements.into_bump_slice(),
|
||||
};
|
||||
|
||||
let stmt = Stmt::Let(
|
||||
|
@ -3444,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))
|
||||
|
@ -5485,11 +5572,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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -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};
|
||||
|
@ -500,7 +459,7 @@ fn fake_wasm_main_function(_: u32, _: u32) -> u32 {
|
|||
#[allow(dead_code)]
|
||||
pub fn assert_wasm_evals_to_help<T>(src: &str, ignore_problems: bool) -> Result<T, String>
|
||||
where
|
||||
T: FromWasmMemory,
|
||||
T: FromWasm32Memory,
|
||||
{
|
||||
let arena = bumpalo::Bump::new();
|
||||
let context = inkwell::context::Context::create();
|
||||
|
@ -534,7 +493,7 @@ where
|
|||
_ => panic!(),
|
||||
};
|
||||
|
||||
let output = <T as crate::helpers::eval::FromWasmMemory>::decode(
|
||||
let output = <T as crate::helpers::eval::FromWasm32Memory>::decode(
|
||||
memory,
|
||||
// skip the RocCallResult tag id
|
||||
address as u32 + 8,
|
||||
|
@ -643,232 +602,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<Self> = MaybeUninit::uninit();
|
||||
let width = std::mem::size_of::<Self>();
|
||||
|
||||
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<u8, wasmer::Array> = 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 = <u64 as FromWasmMemory>::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<u8, wasmer::Array> = 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<T: FromWasmMemory + Clone> FromWasmMemory for RocList<T> {
|
||||
const SIZE_OF_WASM: usize = 8;
|
||||
const ALIGN_OF_WASM: usize = 4;
|
||||
|
||||
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
|
||||
let bytes = <u64 as FromWasmMemory>::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 = <T as FromWasmMemory>::decode(
|
||||
memory,
|
||||
elements + i * <T as FromWasmMemory>::SIZE_OF_WASM as u32,
|
||||
);
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
RocList::from_slice(&items)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromWasmMemory + Clone> 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 = <u64 as FromWasmMemory>::decode(memory, offset);
|
||||
|
||||
let length = (bytes >> 32) as u32;
|
||||
let elements = bytes as u32;
|
||||
|
||||
let ptr: wasmer::WasmPtr<u8, wasmer::Array> = wasmer::WasmPtr::new(elements);
|
||||
let width = <T as FromWasmMemory>::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<T: FromWasmMemory> 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 = <u32 as FromWasmMemory>::decode(memory, offset);
|
||||
|
||||
let actual = <T as FromWasmMemory>::decode(memory, elements);
|
||||
|
||||
let b = Box::new(actual);
|
||||
|
||||
std::boxed::Box::<T>::leak(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromWasmMemory + Clone, const N: usize> 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<u8, wasmer::Array> = wasmer::WasmPtr::new(offset);
|
||||
let width = <T as FromWasmMemory>::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 {
|
||||
<u32 as FromWasmMemory>::decode(memory, offset) as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromWasmMemory, U: FromWasmMemory> 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 = <T as FromWasmMemory>::decode(memory, offset);
|
||||
|
||||
let u = <U as FromWasmMemory>::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<T: FromWasmMemory, U: FromWasmMemory, V: FromWasmMemory> 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 = <T as FromWasmMemory>::decode(memory, offset);
|
||||
|
||||
let u = <U as FromWasmMemory>::decode(memory, offset + T::ACTUAL_WIDTH as u32);
|
||||
|
||||
let v = <V as FromWasmMemory>::decode(
|
||||
memory,
|
||||
offset + T::ACTUAL_WIDTH as u32 + U::ACTUAL_WIDTH as u32,
|
||||
);
|
||||
|
||||
(t, u, v)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
2
vendor/inkwell/Cargo.toml
vendored
2
vendor/inkwell/Cargo.toml
vendored
|
@ -23,7 +23,7 @@ edition = "2018"
|
|||
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
|
||||
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
|
||||
# This way, GitHub Actions works and nobody's builds get broken.
|
||||
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm12-0.release5", features = [ "llvm12-0" ] }
|
||||
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm12-0.release8", features = [ "llvm12-0" ] }
|
||||
|
||||
[features]
|
||||
target-arm = []
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue