mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41:12 +00:00
Merge branch 'trunk' of github.com:rtfeldman/roc into editor-let-value
This commit is contained in:
commit
bae7a12e9e
107 changed files with 5872 additions and 1502 deletions
2
.github/workflows/benchmarks.yml
vendored
2
.github/workflows/benchmarks.yml
vendored
|
@ -42,4 +42,4 @@ jobs:
|
|||
run: cd ci/bench-runner && cargo build --release && cd ../..
|
||||
|
||||
- name: run benchmarks with regression check
|
||||
run: ./ci/bench-runner/target/release/bench-runner --check-executables-changed
|
||||
run: echo "TODO re-enable benchmarks once race condition is fixed"#./ci/bench-runner/target/release/bench-runner --check-executables-changed
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,6 +3,7 @@ generated-docs
|
|||
zig-cache
|
||||
.direnv
|
||||
*.rs.bk
|
||||
*.o
|
||||
|
||||
# llvm human-readable output
|
||||
*.ll
|
||||
|
|
|
@ -7,6 +7,7 @@ To build the compiler, you need these installed:
|
|||
|
||||
* Python 2.7 (Windows only), `python-is-python3` (Ubuntu)
|
||||
* [Zig](https://ziglang.org/), see below for version
|
||||
* `libxkbcommon` - macOS seems to have it already; on Ubuntu or Debian you can get it with `apt-get install libxkbcommon-dev`
|
||||
* LLVM, see below for version
|
||||
|
||||
To run the test suite (via `cargo test`), you additionally need to install:
|
||||
|
@ -73,6 +74,8 @@ There are also alternative installation options at http://releases.llvm.org/down
|
|||
|
||||
## Using Nix
|
||||
|
||||
:exclamation: **Our Nix setup is currently broken, you'll have to install manually for now** :exclamation:
|
||||
|
||||
### Install
|
||||
|
||||
Using [nix](https://nixos.org/download.html) is a quick way to get an environment bootstrapped with a single command.
|
||||
|
|
62
Cargo.lock
generated
62
Cargo.lock
generated
|
@ -1726,6 +1726,16 @@ version = "2.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "iced-x86"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7383772b06135cede839b7270023b46403656a9148024886e721e82639d3f90e"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
|
@ -2032,6 +2042,15 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libmimalloc-sys"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d1b8479c593dba88c2741fc50b92e13dbabbbe0bd504d979f244ccc1a5b1c01"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.4"
|
||||
|
@ -2144,6 +2163,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.5.6"
|
||||
|
@ -2176,6 +2204,15 @@ dependencies = [
|
|||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mimalloc"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb74897ce508e6c49156fd1476fc5922cbc6e75183c65e399c765a09122e5130"
|
||||
dependencies = [
|
||||
"libmimalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.1.3"
|
||||
|
@ -2486,6 +2523,9 @@ version = "0.26.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"indexmap",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
|
@ -3368,7 +3408,9 @@ dependencies = [
|
|||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_constrain",
|
||||
"roc_gen_dev",
|
||||
"roc_gen_llvm",
|
||||
"roc_gen_wasm",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
|
@ -3438,6 +3480,7 @@ dependencies = [
|
|||
"libc",
|
||||
"libloading 0.6.7",
|
||||
"maplit",
|
||||
"mimalloc",
|
||||
"pretty_assertions 0.5.1",
|
||||
"quickcheck 0.8.5",
|
||||
"quickcheck_macros 0.8.0",
|
||||
|
@ -3450,6 +3493,7 @@ dependencies = [
|
|||
"roc_editor",
|
||||
"roc_fmt",
|
||||
"roc_gen_llvm",
|
||||
"roc_linker",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
|
@ -3687,6 +3731,24 @@ dependencies = [
|
|||
name = "roc_ident"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "roc_linker"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bumpalo",
|
||||
"clap 3.0.0-beta.1",
|
||||
"iced-x86",
|
||||
"memmap2 0.3.1",
|
||||
"object 0.26.2",
|
||||
"roc_build",
|
||||
"roc_collections",
|
||||
"roc_mono",
|
||||
"serde",
|
||||
"target-lexicon",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_load"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -33,6 +33,7 @@ members = [
|
|||
"cli/cli_utils",
|
||||
"roc_std",
|
||||
"docs",
|
||||
"linker",
|
||||
]
|
||||
exclude = [ "ci/bench-runner" ]
|
||||
# Needed to be able to run `cargo run -p roc_cli --no-default-features` -
|
||||
|
|
|
@ -46,7 +46,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
|
|||
|
||||
copy-dirs:
|
||||
FROM +install-zig-llvm-valgrind-clippy-rustfmt
|
||||
COPY --dir cli compiler docs editor roc_std vendor examples Cargo.toml Cargo.lock ./
|
||||
COPY --dir cli compiler docs editor roc_std vendor examples linker Cargo.toml Cargo.lock ./
|
||||
|
||||
test-zig:
|
||||
FROM +install-zig-llvm-valgrind-clippy-rustfmt
|
||||
|
@ -66,7 +66,7 @@ check-rustfmt:
|
|||
|
||||
check-typos:
|
||||
RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically
|
||||
COPY --dir .github ci cli compiler docs editor examples nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./
|
||||
COPY --dir .github ci cli compiler docs editor examples linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./
|
||||
RUN typos
|
||||
|
||||
test-rust:
|
||||
|
|
|
@ -57,6 +57,7 @@ roc_build = { path = "../compiler/build", default-features = false }
|
|||
roc_fmt = { path = "../compiler/fmt" }
|
||||
roc_reporting = { path = "../compiler/reporting" }
|
||||
roc_editor = { path = "../editor", optional = true }
|
||||
roc_linker = { path = "../linker" }
|
||||
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
|
||||
clap = { git = "https://github.com/rtfeldman/clap", branch = "master" }
|
||||
const_format = "0.2"
|
||||
|
@ -67,6 +68,7 @@ im-rc = "14" # im and im-rc should always have the same version!
|
|||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
libc = "0.2"
|
||||
libloading = "0.6"
|
||||
mimalloc = { version = "0.1.26", default-features = false }
|
||||
|
||||
inkwell = { path = "../vendor/inkwell", optional = true }
|
||||
target-lexicon = "0.12.2"
|
||||
|
|
|
@ -12,9 +12,11 @@ fn exec_bench_w_input<T: Measurement>(
|
|||
) {
|
||||
let flags: &[&str] = &["--optimize"];
|
||||
|
||||
println!("building {:?}", executable_filename);
|
||||
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
|
||||
println!("done building.");
|
||||
|
||||
if !compile_out.stderr.is_empty() {
|
||||
/*if !compile_out.stderr.is_empty() {
|
||||
panic!("{}", compile_out.stderr);
|
||||
}
|
||||
|
||||
|
@ -24,9 +26,13 @@ fn exec_bench_w_input<T: Measurement>(
|
|||
compile_out
|
||||
);
|
||||
|
||||
println!("checking output for {:?}", executable_filename);
|
||||
check_cmd_output(file, stdin_str, executable_filename, expected_ending);
|
||||
|
||||
println!("benching {:?}", executable_filename);
|
||||
bench_cmd(file, stdin_str, executable_filename, bench_group_opt);
|
||||
|
||||
println!("DONE");*/
|
||||
}
|
||||
|
||||
fn check_cmd_output(
|
||||
|
|
257
cli/src/build.rs
257
cli/src/build.rs
|
@ -43,6 +43,7 @@ pub struct BuiltFile {
|
|||
}
|
||||
|
||||
#[cfg(feature = "llvm")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_file<'a>(
|
||||
arena: &'a Bump,
|
||||
target: &Triple,
|
||||
|
@ -50,7 +51,10 @@ pub fn build_file<'a>(
|
|||
roc_file_path: PathBuf,
|
||||
opt_level: OptLevel,
|
||||
emit_debug_info: bool,
|
||||
emit_timings: bool,
|
||||
link_type: LinkType,
|
||||
surgically_link: bool,
|
||||
precompiled: bool,
|
||||
) -> Result<BuiltFile, LoadingProblem<'a>> {
|
||||
let compilation_start = SystemTime::now();
|
||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||
|
@ -59,10 +63,7 @@ pub fn build_file<'a>(
|
|||
let subs_by_module = MutMap::default();
|
||||
|
||||
// Release builds use uniqueness optimizations
|
||||
let stdlib = match opt_level {
|
||||
OptLevel::Normal => arena.alloc(roc_builtins::std::standard_stdlib()),
|
||||
OptLevel::Optimize => arena.alloc(roc_builtins::std::standard_stdlib()),
|
||||
};
|
||||
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
|
||||
|
||||
let loaded = roc_load::file::load_and_monomorphize(
|
||||
arena,
|
||||
|
@ -75,16 +76,7 @@ pub fn build_file<'a>(
|
|||
)?;
|
||||
|
||||
use target_lexicon::Architecture;
|
||||
let emit_wasm = match target.architecture {
|
||||
Architecture::X86_64 => false,
|
||||
Architecture::Aarch64(_) => false,
|
||||
Architecture::Wasm32 => true,
|
||||
Architecture::X86_32(_) => false,
|
||||
_ => panic!(
|
||||
"TODO gracefully handle unsupported architecture: {:?}",
|
||||
target.architecture
|
||||
),
|
||||
};
|
||||
let emit_wasm = matches!(target.architecture, Architecture::Wasm32);
|
||||
|
||||
// TODO wasm host extension should be something else ideally
|
||||
// .bc does not seem to work because
|
||||
|
@ -95,7 +87,39 @@ pub fn build_file<'a>(
|
|||
let host_extension = if emit_wasm { "zig" } else { "o" };
|
||||
let app_extension = if emit_wasm { "bc" } else { "o" };
|
||||
|
||||
let cwd = roc_file_path.parent().unwrap();
|
||||
let mut binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
|
||||
|
||||
if emit_wasm {
|
||||
binary_path.set_extension("wasm");
|
||||
}
|
||||
|
||||
let mut host_input_path = PathBuf::from(cwd);
|
||||
let path_to_platform = loaded.platform_path.clone();
|
||||
host_input_path.push(&*path_to_platform);
|
||||
host_input_path.push("host");
|
||||
host_input_path.set_extension(host_extension);
|
||||
|
||||
// TODO this should probably be moved before load_and_monomorphize.
|
||||
// To do this we will need to preprocess files just for their exported symbols.
|
||||
// Also, we should no longer need to do this once we have platforms on
|
||||
// a package repository, as we can then get precompiled hosts from there.
|
||||
let rebuild_thread = spawn_rebuild_thread(
|
||||
opt_level,
|
||||
surgically_link,
|
||||
precompiled,
|
||||
host_input_path.clone(),
|
||||
binary_path.clone(),
|
||||
target,
|
||||
loaded
|
||||
.exposed_to_host
|
||||
.keys()
|
||||
.map(|x| x.as_str(&loaded.interns).to_string())
|
||||
.collect(),
|
||||
);
|
||||
|
||||
// TODO try to move as much of this linking as possible to the precompiled
|
||||
// host, to minimize the amount of host-application linking required.
|
||||
let app_o_file = Builder::new()
|
||||
.prefix("roc_app")
|
||||
.suffix(&format!(".{}", app_extension))
|
||||
|
@ -146,9 +170,14 @@ pub fn build_file<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
let cwd = roc_file_path.parent().unwrap();
|
||||
let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
|
||||
let code_gen_timing = program::gen_from_mono_module(
|
||||
// This only needs to be mutable for report_problems. This can't be done
|
||||
// inside a nested scope without causing a borrow error!
|
||||
let mut loaded = loaded;
|
||||
program::report_problems(&mut loaded);
|
||||
let loaded = loaded;
|
||||
|
||||
let code_gen_timing = match opt_level {
|
||||
OptLevel::Normal | OptLevel::Optimize => program::gen_from_mono_module_llvm(
|
||||
arena,
|
||||
loaded,
|
||||
&roc_file_path,
|
||||
|
@ -156,7 +185,11 @@ pub fn build_file<'a>(
|
|||
app_o_file,
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
);
|
||||
),
|
||||
OptLevel::Development => {
|
||||
program::gen_from_mono_module_dev(arena, loaded, target, app_o_file)
|
||||
}
|
||||
};
|
||||
|
||||
buf.push('\n');
|
||||
buf.push_str(" ");
|
||||
|
@ -177,7 +210,7 @@ pub fn build_file<'a>(
|
|||
})
|
||||
.len();
|
||||
|
||||
if emit_debug_info {
|
||||
if emit_timings {
|
||||
println!(
|
||||
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
|
||||
buf
|
||||
|
@ -190,65 +223,181 @@ pub fn build_file<'a>(
|
|||
);
|
||||
}
|
||||
|
||||
// Step 2: link the precompiled host and compiled app
|
||||
let mut host_input_path = PathBuf::from(cwd);
|
||||
|
||||
host_input_path.push(&*path_to_platform);
|
||||
host_input_path.push("host");
|
||||
host_input_path.set_extension(host_extension);
|
||||
|
||||
// TODO we should no longer need to do this once we have platforms on
|
||||
// a package repository, as we can then get precompiled hosts from there.
|
||||
let rebuild_host_start = SystemTime::now();
|
||||
rebuild_host(target, host_input_path.as_path());
|
||||
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
|
||||
|
||||
if emit_debug_info {
|
||||
let rebuild_duration = rebuild_thread.join().unwrap();
|
||||
if emit_timings && !precompiled {
|
||||
println!(
|
||||
"Finished rebuilding the host in {} ms\n",
|
||||
rebuild_host_end.as_millis()
|
||||
"Finished rebuilding and preprocessing the host in {} ms\n",
|
||||
rebuild_duration
|
||||
);
|
||||
}
|
||||
|
||||
// TODO try to move as much of this linking as possible to the precompiled
|
||||
// host, to minimize the amount of host-application linking required.
|
||||
// Step 2: link the precompiled host and compiled app
|
||||
let link_start = SystemTime::now();
|
||||
let (mut child, binary_path) = // TODO use lld
|
||||
let outcome = if surgically_link {
|
||||
roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path)
|
||||
.map_err(|_| {
|
||||
todo!("gracefully handle failing to surgically link");
|
||||
})?;
|
||||
BuildOutcome::NoProblems
|
||||
} else {
|
||||
let (mut child, _) = // TODO use lld
|
||||
link(
|
||||
target,
|
||||
binary_path,
|
||||
binary_path.clone(),
|
||||
&[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()],
|
||||
link_type
|
||||
)
|
||||
.map_err(|_| {
|
||||
todo!("gracefully handle `rustc` failing to spawn.");
|
||||
todo!("gracefully handle `ld` failing to spawn.");
|
||||
})?;
|
||||
|
||||
let cmd_result = child.wait().map_err(|_| {
|
||||
todo!("gracefully handle error after `rustc` spawned");
|
||||
});
|
||||
let exit_status = child.wait().map_err(|_| {
|
||||
todo!("gracefully handle error after `ld` spawned");
|
||||
})?;
|
||||
|
||||
// TODO change this to report whether there were errors or warnings!
|
||||
if exit_status.success() {
|
||||
BuildOutcome::NoProblems
|
||||
} else {
|
||||
BuildOutcome::Errors
|
||||
}
|
||||
};
|
||||
let linking_time = link_start.elapsed().unwrap();
|
||||
|
||||
if emit_debug_info {
|
||||
if emit_timings {
|
||||
println!("Finished linking in {} ms\n", linking_time.as_millis());
|
||||
}
|
||||
|
||||
let total_time = compilation_start.elapsed().unwrap();
|
||||
|
||||
// If the cmd errored out, return the Err.
|
||||
let exit_status = cmd_result?;
|
||||
|
||||
// TODO change this to report whether there were errors or warnings!
|
||||
let outcome = if exit_status.success() {
|
||||
BuildOutcome::NoProblems
|
||||
} else {
|
||||
BuildOutcome::Errors
|
||||
};
|
||||
|
||||
Ok(BuiltFile {
|
||||
binary_path,
|
||||
outcome,
|
||||
total_time,
|
||||
})
|
||||
}
|
||||
|
||||
fn spawn_rebuild_thread(
|
||||
opt_level: OptLevel,
|
||||
surgically_link: bool,
|
||||
precompiled: bool,
|
||||
host_input_path: PathBuf,
|
||||
binary_path: PathBuf,
|
||||
target: &Triple,
|
||||
exported_symbols: Vec<String>,
|
||||
) -> std::thread::JoinHandle<u128> {
|
||||
let thread_local_target = target.clone();
|
||||
std::thread::spawn(move || {
|
||||
let rebuild_host_start = SystemTime::now();
|
||||
if !precompiled {
|
||||
if surgically_link {
|
||||
roc_linker::build_and_preprocess_host(
|
||||
opt_level,
|
||||
&thread_local_target,
|
||||
host_input_path.as_path(),
|
||||
exported_symbols,
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
rebuild_host(
|
||||
opt_level,
|
||||
&thread_local_target,
|
||||
host_input_path.as_path(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
if surgically_link {
|
||||
// Copy preprocessed host to executable location.
|
||||
let prehost = host_input_path.with_file_name("preprocessedhost");
|
||||
std::fs::copy(prehost, binary_path.as_path()).unwrap();
|
||||
}
|
||||
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
|
||||
rebuild_host_end.as_millis()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn check_file(
|
||||
arena: &Bump,
|
||||
src_dir: PathBuf,
|
||||
roc_file_path: PathBuf,
|
||||
emit_timings: bool,
|
||||
) -> Result<usize, LoadingProblem> {
|
||||
let compilation_start = SystemTime::now();
|
||||
|
||||
// only used for generating errors. We don't do code generation, so hardcoding should be fine
|
||||
// we need monomorphization for when exhaustiveness checking
|
||||
let ptr_bytes = 8;
|
||||
|
||||
// Step 1: compile the app and generate the .o file
|
||||
let subs_by_module = MutMap::default();
|
||||
|
||||
// Release builds use uniqueness optimizations
|
||||
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
|
||||
|
||||
let mut loaded = roc_load::file::load_and_monomorphize(
|
||||
arena,
|
||||
roc_file_path,
|
||||
stdlib,
|
||||
src_dir.as_path(),
|
||||
subs_by_module,
|
||||
ptr_bytes,
|
||||
builtin_defs_map,
|
||||
)?;
|
||||
|
||||
let buf = &mut String::with_capacity(1024);
|
||||
|
||||
let mut it = loaded.timings.iter().peekable();
|
||||
while let Some((module_id, module_timing)) = it.next() {
|
||||
let module_name = loaded.interns.module_name(*module_id);
|
||||
|
||||
buf.push_str(" ");
|
||||
|
||||
if module_name.is_empty() {
|
||||
// the App module
|
||||
buf.push_str("Application Module");
|
||||
} else {
|
||||
buf.push_str(module_name);
|
||||
}
|
||||
|
||||
buf.push('\n');
|
||||
|
||||
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
|
||||
report_timing(buf, "Parse header", module_timing.parse_header);
|
||||
report_timing(buf, "Parse body", module_timing.parse_body);
|
||||
report_timing(buf, "Canonicalize", module_timing.canonicalize);
|
||||
report_timing(buf, "Constrain", module_timing.constrain);
|
||||
report_timing(buf, "Solve", module_timing.solve);
|
||||
report_timing(
|
||||
buf,
|
||||
"Find Specializations",
|
||||
module_timing.find_specializations,
|
||||
);
|
||||
report_timing(
|
||||
buf,
|
||||
"Make Specializations",
|
||||
module_timing.make_specializations,
|
||||
);
|
||||
report_timing(buf, "Other", module_timing.other());
|
||||
buf.push('\n');
|
||||
report_timing(buf, "Total", module_timing.total());
|
||||
|
||||
if it.peek().is_some() {
|
||||
buf.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
let compilation_end = compilation_start.elapsed().unwrap();
|
||||
|
||||
if emit_timings {
|
||||
println!(
|
||||
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
|
||||
buf
|
||||
);
|
||||
|
||||
println!("Finished checking in {} ms\n", compilation_end.as_millis(),);
|
||||
}
|
||||
|
||||
Ok(program::report_problems(&mut loaded))
|
||||
}
|
||||
|
|
123
cli/src/lib.rs
123
cli/src/lib.rs
|
@ -26,11 +26,16 @@ pub const CMD_BUILD: &str = "build";
|
|||
pub const CMD_REPL: &str = "repl";
|
||||
pub const CMD_EDIT: &str = "edit";
|
||||
pub const CMD_DOCS: &str = "docs";
|
||||
pub const CMD_CHECK: &str = "check";
|
||||
|
||||
pub const FLAG_DEBUG: &str = "debug";
|
||||
pub const FLAG_DEV: &str = "dev";
|
||||
pub const FLAG_OPTIMIZE: &str = "optimize";
|
||||
pub const FLAG_LIB: &str = "lib";
|
||||
pub const FLAG_BACKEND: &str = "backend";
|
||||
pub const FLAG_TIME: &str = "time";
|
||||
pub const FLAG_LINK: &str = "roc-linker";
|
||||
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
|
||||
pub const ROC_FILE: &str = "ROC_FILE";
|
||||
pub const BACKEND: &str = "BACKEND";
|
||||
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
|
||||
|
@ -53,6 +58,12 @@ pub fn build_app<'a>() -> App<'a> {
|
|||
.help("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_DEV)
|
||||
.long(FLAG_DEV)
|
||||
.help("Make compilation as fast as possible. (Runtime performance may suffer)")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_BACKEND)
|
||||
.long(FLAG_BACKEND)
|
||||
|
@ -74,6 +85,24 @@ pub fn build_app<'a>() -> App<'a> {
|
|||
.help("Store LLVM debug information in the generated program")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_TIME)
|
||||
.long(FLAG_TIME)
|
||||
.help("Prints detailed compilation time information.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_LINK)
|
||||
.long(FLAG_LINK)
|
||||
.help("Uses the roc linker instead of the system linker.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_PRECOMPILED)
|
||||
.long(FLAG_PRECOMPILED)
|
||||
.help("Assumes the host has been precompiled and skips recompiling the host.")
|
||||
.required(false),
|
||||
)
|
||||
)
|
||||
.subcommand(App::new(CMD_RUN)
|
||||
.about("DEPRECATED - now use `roc [FILE]` instead of `roc run [FILE]`")
|
||||
|
@ -84,6 +113,12 @@ pub fn build_app<'a>() -> App<'a> {
|
|||
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_DEV)
|
||||
.long(FLAG_DEV)
|
||||
.help("Make compilation as fast as possible. (Runtime performance may suffer)")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_DEBUG)
|
||||
.long(FLAG_DEBUG)
|
||||
|
@ -104,6 +139,20 @@ pub fn build_app<'a>() -> App<'a> {
|
|||
.subcommand(App::new(CMD_REPL)
|
||||
.about("Launch the interactive Read Eval Print Loop (REPL)")
|
||||
)
|
||||
.subcommand(App::new(CMD_CHECK)
|
||||
.about("Build a binary from the given .roc file, but don't run it")
|
||||
.arg(
|
||||
Arg::with_name(FLAG_TIME)
|
||||
.long(FLAG_TIME)
|
||||
.help("Prints detailed compilation time information.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(ROC_FILE)
|
||||
.help("The .roc file of an app to run")
|
||||
.required(true),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
App::new(CMD_DOCS)
|
||||
.about("Generate documentation for Roc modules")
|
||||
|
@ -123,6 +172,12 @@ pub fn build_app<'a>() -> App<'a> {
|
|||
.requires(ROC_FILE)
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_DEV)
|
||||
.long(FLAG_DEV)
|
||||
.help("Make compilation as fast as possible. (Runtime performance may suffer)")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_DEBUG)
|
||||
.long(FLAG_DEBUG)
|
||||
|
@ -130,6 +185,24 @@ pub fn build_app<'a>() -> App<'a> {
|
|||
.requires(ROC_FILE)
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_TIME)
|
||||
.long(FLAG_TIME)
|
||||
.help("Prints detailed compilation time information.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_LINK)
|
||||
.long(FLAG_LINK)
|
||||
.help("Uses the roc linker instead of the system linker.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_PRECOMPILED)
|
||||
.long(FLAG_PRECOMPILED)
|
||||
.help("Assumes the host has been precompiled and skips recompiling the host.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(FLAG_BACKEND)
|
||||
.long(FLAG_BACKEND)
|
||||
|
@ -197,18 +270,31 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
|
|||
let filename = matches.value_of(ROC_FILE).unwrap();
|
||||
|
||||
let original_cwd = std::env::current_dir()?;
|
||||
let opt_level = if matches.is_present(FLAG_OPTIMIZE) {
|
||||
OptLevel::Optimize
|
||||
} else {
|
||||
OptLevel::Normal
|
||||
let opt_level = match (
|
||||
matches.is_present(FLAG_OPTIMIZE),
|
||||
matches.is_present(FLAG_DEV),
|
||||
) {
|
||||
(true, false) => OptLevel::Optimize,
|
||||
(true, true) => panic!("development cannot be optimized!"),
|
||||
(false, true) => OptLevel::Development,
|
||||
(false, false) => OptLevel::Normal,
|
||||
};
|
||||
let emit_debug_info = matches.is_present(FLAG_DEBUG);
|
||||
let emit_timings = matches.is_present(FLAG_TIME);
|
||||
|
||||
let link_type = if matches.is_present(FLAG_LIB) {
|
||||
LinkType::Dylib
|
||||
} else {
|
||||
LinkType::Executable
|
||||
};
|
||||
let surgically_link = matches.is_present(FLAG_LINK);
|
||||
let precompiled = matches.is_present(FLAG_PRECOMPILED);
|
||||
if surgically_link && !roc_linker::supported(&link_type, &target) {
|
||||
panic!(
|
||||
"Link type, {:?}, with target, {}, not supported by roc linker",
|
||||
link_type, target
|
||||
);
|
||||
}
|
||||
|
||||
let path = Path::new(filename);
|
||||
|
||||
|
@ -239,7 +325,10 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
|
|||
path,
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
emit_timings,
|
||||
link_type,
|
||||
surgically_link,
|
||||
precompiled,
|
||||
);
|
||||
|
||||
match res_binary_path {
|
||||
|
@ -371,24 +460,29 @@ fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) {
|
|||
|
||||
// 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!());
|
||||
let import_object = wasi_env.import_object(&module).unwrap();
|
||||
|
||||
let instance = Instance::new(&module, &import_object).unwrap();
|
||||
|
||||
let start = instance.exports.get_function("_start").unwrap();
|
||||
|
||||
start.call(&[]).unwrap();
|
||||
use wasmer_wasi::WasiError;
|
||||
match start.call(&[]) {
|
||||
Ok(_) => {}
|
||||
Err(e) => match e.downcast::<WasiError>() {
|
||||
Ok(WasiError::Exit(0)) => {
|
||||
// we run the `_start` function, so exit(0) is expected
|
||||
}
|
||||
other => panic!("Wasmer error: {:?}", other),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
enum Backend {
|
||||
Host,
|
||||
X86_32,
|
||||
X86_64,
|
||||
Dev,
|
||||
Wasm32,
|
||||
Wasm32Dev,
|
||||
}
|
||||
|
||||
impl Default for Backend {
|
||||
|
@ -403,9 +497,7 @@ impl Backend {
|
|||
Backend::Host => "host",
|
||||
Backend::X86_32 => "x86_32",
|
||||
Backend::X86_64 => "x86_64",
|
||||
Backend::Dev => "dev",
|
||||
Backend::Wasm32 => "wasm32",
|
||||
Backend::Wasm32Dev => "wasm32_dev",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,9 +506,7 @@ impl Backend {
|
|||
Backend::Host.as_str(),
|
||||
Backend::X86_32.as_str(),
|
||||
Backend::X86_64.as_str(),
|
||||
Backend::Dev.as_str(),
|
||||
Backend::Wasm32.as_str(),
|
||||
Backend::Wasm32Dev.as_str(),
|
||||
];
|
||||
|
||||
fn to_triple(&self) -> Triple {
|
||||
|
@ -439,8 +529,7 @@ impl Backend {
|
|||
|
||||
triple
|
||||
}
|
||||
Backend::Dev => todo!(),
|
||||
Backend::Wasm32 | Backend::Wasm32Dev => {
|
||||
Backend::Wasm32 => {
|
||||
triple.architecture = Architecture::Wasm32;
|
||||
triple.binary_format = BinaryFormat::Wasm;
|
||||
|
||||
|
@ -464,9 +553,7 @@ impl std::str::FromStr for Backend {
|
|||
"host" => Ok(Backend::Host),
|
||||
"x86_32" => Ok(Backend::X86_32),
|
||||
"x86_64" => Ok(Backend::X86_64),
|
||||
"dev" => Ok(Backend::Dev),
|
||||
"wasm32" => Ok(Backend::Wasm32),
|
||||
"wasm32_dev" => Ok(Backend::Wasm32Dev),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
use roc_cli::build::check_file;
|
||||
use roc_cli::{
|
||||
build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_DOCS, CMD_EDIT, CMD_REPL, CMD_RUN,
|
||||
DIRECTORY_OR_FILES, ROC_FILE,
|
||||
build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, CMD_REPL,
|
||||
CMD_RUN, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE,
|
||||
};
|
||||
use roc_load::file::LoadingProblem;
|
||||
use std::fs::{self, FileType};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
#[cfg(feature = "llvm")]
|
||||
use roc_cli::build;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
@ -49,6 +54,31 @@ If you're building the compiler from source you'll want to do `cargo run [FILE]`
|
|||
|
||||
Ok(1)
|
||||
}
|
||||
Some(CMD_CHECK) => {
|
||||
let arena = bumpalo::Bump::new();
|
||||
|
||||
let matches = matches.subcommand_matches(CMD_CHECK).unwrap();
|
||||
let emit_timings = matches.is_present(FLAG_TIME);
|
||||
let filename = matches.value_of(ROC_FILE).unwrap();
|
||||
let roc_file_path = PathBuf::from(filename);
|
||||
let src_dir = roc_file_path.parent().unwrap().to_owned();
|
||||
|
||||
match check_file(&arena, src_dir, roc_file_path, emit_timings) {
|
||||
Ok(number_of_errors) => {
|
||||
let exit_code = if number_of_errors != 0 { 1 } else { 0 };
|
||||
Ok(exit_code)
|
||||
}
|
||||
|
||||
Err(LoadingProblem::FormattedReport(report)) => {
|
||||
print!("{}", report);
|
||||
|
||||
Ok(1)
|
||||
}
|
||||
Err(other) => {
|
||||
panic!("build_file failed with error:\n{:?}", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(CMD_REPL) => {
|
||||
repl::main()?;
|
||||
|
||||
|
|
|
@ -353,6 +353,13 @@ fn jit_to_ast_help<'a>(
|
|||
| Layout::RecursivePointer => {
|
||||
todo!("add support for rendering recursive tag unions in the REPL")
|
||||
}
|
||||
Layout::LambdaSet(lambda_set) => jit_to_ast_help(
|
||||
env,
|
||||
lib,
|
||||
main_fn_name,
|
||||
&lambda_set.runtime_representation(),
|
||||
content,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -107,13 +107,14 @@ pub fn gen_and_eval<'a>(
|
|||
}
|
||||
|
||||
for problem in type_problems {
|
||||
let report = type_problem(&alloc, module_path.clone(), problem);
|
||||
if let Some(report) = type_problem(&alloc, module_path.clone(), problem) {
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, module_path.clone(), problem);
|
||||
|
|
|
@ -157,6 +157,15 @@ mod cli_run {
|
|||
let example = $example;
|
||||
let file_name = example_file(dir_name, example.filename);
|
||||
|
||||
match example.executable_filename {
|
||||
"hello-web" => {
|
||||
// this is a web webassembly example, but we don't test with JS at the moment
|
||||
eprintln!("WARNING: skipping testing example {} because the test is broken right now!", example.filename);
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Check with and without optimizations
|
||||
check_output_with_stdin(
|
||||
&file_name,
|
||||
|
@ -224,6 +233,20 @@ mod cli_run {
|
|||
expected_ending:"Hello, World!\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
hello_web:"hello-web" => Example {
|
||||
filename: "Hello.roc",
|
||||
executable_filename: "hello-web",
|
||||
stdin: &[],
|
||||
expected_ending:"Hello, World!\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
fib:"fib" => Example {
|
||||
filename: "Fib.roc",
|
||||
executable_filename: "fib",
|
||||
stdin: &[],
|
||||
expected_ending:"55\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
quicksort:"quicksort" => Example {
|
||||
filename: "Quicksort.roc",
|
||||
executable_filename: "quicksort",
|
||||
|
@ -242,7 +265,7 @@ mod cli_run {
|
|||
filename: "Main.roc",
|
||||
executable_filename: "effect-example",
|
||||
stdin: &["hi there!"],
|
||||
expected_ending: "hi there!\n",
|
||||
expected_ending: "hi there!\nIt is known\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
// tea:"tea" => Example {
|
||||
|
@ -603,7 +626,7 @@ mod cli_run {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm32-cli-run")]
|
||||
#[allow(dead_code)]
|
||||
fn run_with_wasmer(wasm_path: &std::path::Path, stdin: &[&str]) -> String {
|
||||
use std::io::Write;
|
||||
use wasmer::{Instance, Module, Store};
|
||||
|
@ -647,7 +670,22 @@ fn run_with_wasmer(wasm_path: &std::path::Path, stdin: &[&str]) -> String {
|
|||
let start = instance.exports.get_function("_start").unwrap();
|
||||
|
||||
match start.call(&[]) {
|
||||
Ok(_) => {
|
||||
Ok(_) => read_wasi_stdout(wasi_env),
|
||||
Err(e) => {
|
||||
use wasmer_wasi::WasiError;
|
||||
match e.downcast::<WasiError>() {
|
||||
Ok(WasiError::Exit(0)) => {
|
||||
// we run the `_start` function, so exit(0) is expected
|
||||
read_wasi_stdout(wasi_env)
|
||||
}
|
||||
other => format!("Something went wrong running a wasm test: {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn read_wasi_stdout(wasi_env: wasmer_wasi::WasiEnv) -> String {
|
||||
let mut state = wasi_env.state.lock().unwrap();
|
||||
|
||||
match state.fs.stdout_mut() {
|
||||
|
@ -659,9 +697,4 @@ fn run_with_wasmer(wasm_path: &std::path::Path, stdin: &[&str]) -> String {
|
|||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("Something went wrong running a wasm test:\n{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,8 @@ comptime {
|
|||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
extern fn roc__mainForHost_1_exposed(*RocCallResult) void;
|
||||
extern fn roc__mainForHost_1_exposed() RocStr;
|
||||
|
||||
|
||||
extern fn malloc(size: usize) callconv(.C) ?*c_void;
|
||||
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
|
||||
|
@ -47,28 +48,23 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
|||
std.process.exit(0);
|
||||
}
|
||||
|
||||
const RocCallResult = extern struct { flag: usize, content: RocStr };
|
||||
|
||||
const Unit = extern struct {};
|
||||
|
||||
pub export fn main() i32 {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
|
||||
// make space for the result
|
||||
var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() };
|
||||
|
||||
// start time
|
||||
var ts1: std.os.timespec = undefined;
|
||||
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
|
||||
|
||||
// actually call roc to populate the callresult
|
||||
roc__mainForHost_1_exposed(&callresult);
|
||||
const callresult = roc__mainForHost_1_exposed();
|
||||
|
||||
// stdout the result
|
||||
stdout.print("{s}\n", .{callresult.content.asSlice()}) catch unreachable;
|
||||
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
|
||||
|
||||
callresult.content.deinit();
|
||||
callresult.deinit();
|
||||
|
||||
// end time
|
||||
var ts2: std.os.timespec = undefined;
|
||||
|
|
|
@ -22,7 +22,7 @@ comptime {
|
|||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
extern fn roc__mainForHost_1_exposed(*RocCallResult) void;
|
||||
extern fn roc__mainForHost_1_exposed() RocStr;
|
||||
|
||||
extern fn malloc(size: usize) callconv(.C) ?*c_void;
|
||||
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
|
||||
|
@ -47,28 +47,23 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
|||
std.process.exit(0);
|
||||
}
|
||||
|
||||
const RocCallResult = extern struct { flag: usize, content: RocStr };
|
||||
|
||||
const Unit = extern struct {};
|
||||
|
||||
pub export fn main() i32 {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
|
||||
// make space for the result
|
||||
var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() };
|
||||
|
||||
// start time
|
||||
var ts1: std.os.timespec = undefined;
|
||||
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
|
||||
|
||||
// actually call roc to populate the callresult
|
||||
roc__mainForHost_1_exposed(&callresult);
|
||||
const callresult = roc__mainForHost_1_exposed();
|
||||
|
||||
// stdout the result
|
||||
stdout.print("{s}\n", .{callresult.content.asSlice()}) catch unreachable;
|
||||
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
|
||||
|
||||
callresult.content.deinit();
|
||||
callresult.deinit();
|
||||
|
||||
// end time
|
||||
var ts2: std.os.timespec = undefined;
|
||||
|
|
|
@ -20,6 +20,8 @@ roc_solve = { path = "../solve" }
|
|||
roc_mono = { path = "../mono" }
|
||||
roc_load = { path = "../load" }
|
||||
roc_gen_llvm = { path = "../gen_llvm", optional = true }
|
||||
roc_gen_wasm = { path = "../gen_wasm" }
|
||||
roc_gen_dev = { path = "../gen_dev" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
im = "14" # im and im-rc should always have the same version!
|
||||
|
|
|
@ -56,10 +56,27 @@ fn find_zig_str_path() -> PathBuf {
|
|||
return zig_str_path;
|
||||
}
|
||||
|
||||
panic!("cannot find `str.zig`")
|
||||
panic!("cannot find `str.zig`. Launch me from either the root of the roc repo or one level down(roc/examples, roc/cli...)")
|
||||
}
|
||||
|
||||
fn find_wasi_libc_path() -> PathBuf {
|
||||
let wasi_libc_path = PathBuf::from("compiler/builtins/bitcode/wasi-libc.a");
|
||||
|
||||
if std::path::Path::exists(&wasi_libc_path) {
|
||||
return wasi_libc_path;
|
||||
}
|
||||
|
||||
// when running the tests, we start in the /cli directory
|
||||
let wasi_libc_path = PathBuf::from("../compiler/builtins/bitcode/wasi-libc.a");
|
||||
if std::path::Path::exists(&wasi_libc_path) {
|
||||
return wasi_libc_path;
|
||||
}
|
||||
|
||||
panic!("cannot find `wasi-libc.a`")
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_zig_host_native(
|
||||
env_path: &str,
|
||||
env_home: &str,
|
||||
|
@ -67,13 +84,20 @@ pub fn build_zig_host_native(
|
|||
zig_host_src: &str,
|
||||
zig_str_path: &str,
|
||||
target: &str,
|
||||
opt_level: OptLevel,
|
||||
shared_lib_path: Option<&Path>,
|
||||
) -> Output {
|
||||
Command::new("zig")
|
||||
let mut command = Command::new("zig");
|
||||
command
|
||||
.env_clear()
|
||||
.env("PATH", env_path)
|
||||
.env("HOME", env_home)
|
||||
.args(&[
|
||||
"build-obj",
|
||||
.env("HOME", env_home);
|
||||
if let Some(shared_lib_path) = shared_lib_path {
|
||||
command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]);
|
||||
} else {
|
||||
command.args(&["build-obj", "-fPIC"]);
|
||||
}
|
||||
command.args(&[
|
||||
zig_host_src,
|
||||
emit_bin,
|
||||
"--pkg-begin",
|
||||
|
@ -85,15 +109,19 @@ pub fn build_zig_host_native(
|
|||
// include libc
|
||||
"--library",
|
||||
"c",
|
||||
"--strip",
|
||||
// cross-compile?
|
||||
"-target",
|
||||
target,
|
||||
])
|
||||
.output()
|
||||
.unwrap()
|
||||
]);
|
||||
if matches!(opt_level, OptLevel::Optimize) {
|
||||
command.args(&["-O", "ReleaseSafe"]);
|
||||
}
|
||||
command.output().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_zig_host_native(
|
||||
env_path: &str,
|
||||
env_home: &str,
|
||||
|
@ -101,6 +129,8 @@ pub fn build_zig_host_native(
|
|||
zig_host_src: &str,
|
||||
zig_str_path: &str,
|
||||
_target: &str,
|
||||
opt_level: OptLevel,
|
||||
shared_lib_path: Option<&Path>,
|
||||
) -> Output {
|
||||
use serde_json::Value;
|
||||
|
||||
|
@ -142,12 +172,17 @@ pub fn build_zig_host_native(
|
|||
zig_compiler_rt_path.push("special");
|
||||
zig_compiler_rt_path.push("compiler_rt.zig");
|
||||
|
||||
Command::new("zig")
|
||||
let mut command = Command::new("zig");
|
||||
command
|
||||
.env_clear()
|
||||
.env("PATH", &env_path)
|
||||
.env("HOME", &env_home)
|
||||
.args(&[
|
||||
"build-obj",
|
||||
.env("HOME", &env_home);
|
||||
if let Some(shared_lib_path) = shared_lib_path {
|
||||
command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]);
|
||||
} else {
|
||||
command.args(&["build-obj", "-fPIC"]);
|
||||
}
|
||||
command.args(&[
|
||||
zig_host_src,
|
||||
emit_bin,
|
||||
"--pkg-begin",
|
||||
|
@ -162,9 +197,12 @@ pub fn build_zig_host_native(
|
|||
// include libc
|
||||
"--library",
|
||||
"c",
|
||||
])
|
||||
.output()
|
||||
.unwrap()
|
||||
"--strip",
|
||||
]);
|
||||
if matches!(opt_level, OptLevel::Optimize) {
|
||||
command.args(&["-O", "ReleaseSafe"]);
|
||||
}
|
||||
command.output().unwrap()
|
||||
}
|
||||
|
||||
pub fn build_zig_host_wasm32(
|
||||
|
@ -173,7 +211,12 @@ pub fn build_zig_host_wasm32(
|
|||
emit_bin: &str,
|
||||
zig_host_src: &str,
|
||||
zig_str_path: &str,
|
||||
opt_level: OptLevel,
|
||||
shared_lib_path: Option<&Path>,
|
||||
) -> Output {
|
||||
if shared_lib_path.is_some() {
|
||||
unimplemented!("Linking a shared library to wasm not yet implemented");
|
||||
}
|
||||
// NOTE currently just to get compiler warnings if the host code is invalid.
|
||||
// the produced artifact is not used
|
||||
//
|
||||
|
@ -182,7 +225,8 @@ pub fn build_zig_host_wasm32(
|
|||
// we'd like to compile with `-target wasm32-wasi` but that is blocked on
|
||||
//
|
||||
// https://github.com/ziglang/zig/issues/9414
|
||||
Command::new("zig")
|
||||
let mut command = Command::new("zig");
|
||||
command
|
||||
.env_clear()
|
||||
.env("PATH", env_path)
|
||||
.env("HOME", env_home)
|
||||
|
@ -203,19 +247,67 @@ pub fn build_zig_host_wasm32(
|
|||
"i386-linux-musl",
|
||||
// "wasm32-wasi",
|
||||
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll",
|
||||
])
|
||||
.output()
|
||||
.unwrap()
|
||||
"-fPIC",
|
||||
"--strip",
|
||||
]);
|
||||
if matches!(opt_level, OptLevel::Optimize) {
|
||||
command.args(&["-O", "ReleaseSafe"]);
|
||||
}
|
||||
command.output().unwrap()
|
||||
}
|
||||
|
||||
pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
|
||||
pub fn build_c_host_native(
|
||||
env_path: &str,
|
||||
env_home: &str,
|
||||
dest: &str,
|
||||
sources: &[&str],
|
||||
opt_level: OptLevel,
|
||||
shared_lib_path: Option<&Path>,
|
||||
) -> Output {
|
||||
let mut command = Command::new("clang");
|
||||
command
|
||||
.env_clear()
|
||||
.env("PATH", &env_path)
|
||||
.env("HOME", &env_home)
|
||||
.args(sources)
|
||||
.args(&["-o", dest]);
|
||||
if let Some(shared_lib_path) = shared_lib_path {
|
||||
command.args(&[
|
||||
shared_lib_path.to_str().unwrap(),
|
||||
"-fPIE",
|
||||
"-pie",
|
||||
"-lm",
|
||||
"-lpthread",
|
||||
"-ldl",
|
||||
"-lrt",
|
||||
"-lutil",
|
||||
]);
|
||||
} else {
|
||||
command.args(&["-fPIC", "-c"]);
|
||||
}
|
||||
if matches!(opt_level, OptLevel::Optimize) {
|
||||
command.arg("-O2");
|
||||
}
|
||||
command.output().unwrap()
|
||||
}
|
||||
|
||||
pub fn rebuild_host(
|
||||
opt_level: OptLevel,
|
||||
target: &Triple,
|
||||
host_input_path: &Path,
|
||||
shared_lib_path: Option<&Path>,
|
||||
) {
|
||||
let c_host_src = host_input_path.with_file_name("host.c");
|
||||
let c_host_dest = host_input_path.with_file_name("c_host.o");
|
||||
let zig_host_src = host_input_path.with_file_name("host.zig");
|
||||
let rust_host_src = host_input_path.with_file_name("host.rs");
|
||||
let rust_host_dest = host_input_path.with_file_name("rust_host.o");
|
||||
let cargo_host_src = host_input_path.with_file_name("Cargo.toml");
|
||||
let host_dest_native = host_input_path.with_file_name("host.o");
|
||||
let host_dest_native = host_input_path.with_file_name(if shared_lib_path.is_some() {
|
||||
"dynhost"
|
||||
} else {
|
||||
"host.o"
|
||||
});
|
||||
let host_dest_wasm = host_input_path.with_file_name("host.bc");
|
||||
|
||||
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
|
||||
|
@ -241,6 +333,8 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
|
|||
&emit_bin,
|
||||
zig_host_src.to_str().unwrap(),
|
||||
zig_str_path.to_str().unwrap(),
|
||||
opt_level,
|
||||
shared_lib_path,
|
||||
)
|
||||
}
|
||||
Architecture::X86_64 => {
|
||||
|
@ -252,6 +346,8 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
|
|||
zig_host_src.to_str().unwrap(),
|
||||
zig_str_path.to_str().unwrap(),
|
||||
"native",
|
||||
opt_level,
|
||||
shared_lib_path,
|
||||
)
|
||||
}
|
||||
Architecture::X86_32(_) => {
|
||||
|
@ -263,41 +359,58 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
|
|||
zig_host_src.to_str().unwrap(),
|
||||
zig_str_path.to_str().unwrap(),
|
||||
"i386-linux-musl",
|
||||
opt_level,
|
||||
shared_lib_path,
|
||||
)
|
||||
}
|
||||
_ => panic!("Unsupported architecture {:?}", target.architecture),
|
||||
};
|
||||
|
||||
validate_output("host.zig", "zig", output)
|
||||
} else {
|
||||
// Compile host.c
|
||||
let output = Command::new("clang")
|
||||
.env_clear()
|
||||
.env("PATH", &env_path)
|
||||
.args(&[
|
||||
"-c",
|
||||
c_host_src.to_str().unwrap(),
|
||||
"-o",
|
||||
c_host_dest.to_str().unwrap(),
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
validate_output("host.c", "clang", output);
|
||||
}
|
||||
|
||||
if cargo_host_src.exists() {
|
||||
} else if cargo_host_src.exists() {
|
||||
// Compile and link Cargo.toml, if it exists
|
||||
let cargo_dir = host_input_path.parent().unwrap();
|
||||
let libhost_dir = cargo_dir.join("target").join("release");
|
||||
let libhost_dir =
|
||||
cargo_dir
|
||||
.join("target")
|
||||
.join(if matches!(opt_level, OptLevel::Optimize) {
|
||||
"release"
|
||||
} else {
|
||||
"debug"
|
||||
});
|
||||
let libhost = libhost_dir.join("libhost.a");
|
||||
|
||||
let output = Command::new("cargo")
|
||||
.args(&["build", "--release"])
|
||||
.current_dir(cargo_dir)
|
||||
.output()
|
||||
.unwrap();
|
||||
let mut command = Command::new("cargo");
|
||||
command.arg("build").current_dir(cargo_dir);
|
||||
if matches!(opt_level, OptLevel::Optimize) {
|
||||
command.arg("--release");
|
||||
}
|
||||
let output = command.output().unwrap();
|
||||
|
||||
validate_output("src/lib.rs", "cargo build --release", output);
|
||||
validate_output("src/lib.rs", "cargo build", output);
|
||||
|
||||
// Cargo hosts depend on a c wrapper for the api. Compile host.c as well.
|
||||
if shared_lib_path.is_some() {
|
||||
// If compiling to executable, let c deal with linking as well.
|
||||
let output = build_c_host_native(
|
||||
&env_path,
|
||||
&env_home,
|
||||
host_dest_native.to_str().unwrap(),
|
||||
&[c_host_src.to_str().unwrap(), libhost.to_str().unwrap()],
|
||||
opt_level,
|
||||
shared_lib_path,
|
||||
);
|
||||
validate_output("host.c", "clang", output);
|
||||
} else {
|
||||
let output = build_c_host_native(
|
||||
&env_path,
|
||||
&env_home,
|
||||
c_host_dest.to_str().unwrap(),
|
||||
&[c_host_src.to_str().unwrap()],
|
||||
opt_level,
|
||||
shared_lib_path,
|
||||
);
|
||||
validate_output("host.c", "clang", output);
|
||||
|
||||
let output = Command::new("ld")
|
||||
.env_clear()
|
||||
|
@ -313,21 +426,58 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
|
|||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
validate_output("c_host.o", "ld", output);
|
||||
} else if rust_host_src.exists() {
|
||||
// Compile and link host.rs, if it exists
|
||||
let output = Command::new("rustc")
|
||||
.args(&[
|
||||
rust_host_src.to_str().unwrap(),
|
||||
"-o",
|
||||
rust_host_dest.to_str().unwrap(),
|
||||
])
|
||||
|
||||
// Clean up c_host.o
|
||||
let output = Command::new("rm")
|
||||
.env_clear()
|
||||
.args(&["-f", c_host_dest.to_str().unwrap()])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
validate_output("rust_host.o", "rm", output);
|
||||
}
|
||||
} else if rust_host_src.exists() {
|
||||
// Compile and link host.rs, if it exists
|
||||
let mut command = Command::new("rustc");
|
||||
command.args(&[
|
||||
rust_host_src.to_str().unwrap(),
|
||||
"-o",
|
||||
rust_host_dest.to_str().unwrap(),
|
||||
]);
|
||||
if matches!(opt_level, OptLevel::Optimize) {
|
||||
command.arg("-O");
|
||||
}
|
||||
let output = command.output().unwrap();
|
||||
|
||||
validate_output("host.rs", "rustc", output);
|
||||
|
||||
// Rust hosts depend on a c wrapper for the api. Compile host.c as well.
|
||||
if shared_lib_path.is_some() {
|
||||
// If compiling to executable, let c deal with linking as well.
|
||||
let output = build_c_host_native(
|
||||
&env_path,
|
||||
&env_home,
|
||||
host_dest_native.to_str().unwrap(),
|
||||
&[
|
||||
c_host_src.to_str().unwrap(),
|
||||
rust_host_dest.to_str().unwrap(),
|
||||
],
|
||||
opt_level,
|
||||
shared_lib_path,
|
||||
);
|
||||
validate_output("host.c", "clang", output);
|
||||
} else {
|
||||
let output = build_c_host_native(
|
||||
&env_path,
|
||||
&env_home,
|
||||
c_host_dest.to_str().unwrap(),
|
||||
&[c_host_src.to_str().unwrap()],
|
||||
opt_level,
|
||||
shared_lib_path,
|
||||
);
|
||||
|
||||
validate_output("host.c", "clang", output);
|
||||
let output = Command::new("ld")
|
||||
.env_clear()
|
||||
.env("PATH", &env_path)
|
||||
|
@ -342,8 +492,9 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
|
|||
.unwrap();
|
||||
|
||||
validate_output("rust_host.o", "ld", output);
|
||||
}
|
||||
|
||||
// Clean up rust_host.o
|
||||
// Clean up rust_host.o and c_host.o
|
||||
let output = Command::new("rm")
|
||||
.env_clear()
|
||||
.args(&[
|
||||
|
@ -355,15 +506,17 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
|
|||
.unwrap();
|
||||
|
||||
validate_output("rust_host.o", "rm", output);
|
||||
} else if c_host_dest.exists() {
|
||||
// Clean up c_host.o
|
||||
let output = Command::new("mv")
|
||||
.env_clear()
|
||||
.args(&[c_host_dest, host_dest_native])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
validate_output("c_host.o", "mv", output);
|
||||
} else if c_host_src.exists() {
|
||||
// Compile host.c, if it exists
|
||||
let output = build_c_host_native(
|
||||
&env_path,
|
||||
&env_home,
|
||||
host_dest_native.to_str().unwrap(),
|
||||
&[c_host_src.to_str().unwrap()],
|
||||
opt_level,
|
||||
shared_lib_path,
|
||||
);
|
||||
validate_output("host.c", "clang", output);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -505,6 +658,7 @@ fn link_linux(
|
|||
"--eh-frame-hdr",
|
||||
"-arch",
|
||||
arch_str(target),
|
||||
"-pie",
|
||||
libcrt_path.join("crti.o").to_str().unwrap(),
|
||||
libcrt_path.join("crtn.o").to_str().unwrap(),
|
||||
])
|
||||
|
@ -605,6 +759,7 @@ fn link_wasm32(
|
|||
_link_type: LinkType,
|
||||
) -> io::Result<(Child, PathBuf)> {
|
||||
let zig_str_path = find_zig_str_path();
|
||||
let wasi_libc_path = find_wasi_libc_path();
|
||||
|
||||
let child = Command::new("zig9")
|
||||
// .env_clear()
|
||||
|
@ -612,9 +767,10 @@ fn link_wasm32(
|
|||
.args(&["build-exe"])
|
||||
.args(input_paths)
|
||||
.args([
|
||||
// include wasi libc
|
||||
// using `-lc` is broken in zig 8 (and early 9) in combination with ReleaseSmall
|
||||
wasi_libc_path.to_str().unwrap(),
|
||||
&format!("-femit-bin={}", output_path.to_str().unwrap()),
|
||||
// include libc
|
||||
"-lc",
|
||||
"-target",
|
||||
"wasm32-wasi-musl",
|
||||
"--pkg-begin",
|
||||
|
@ -622,7 +778,8 @@ fn link_wasm32(
|
|||
zig_str_path.to_str().unwrap(),
|
||||
"--pkg-end",
|
||||
"--strip",
|
||||
// "-O", "ReleaseSmall",
|
||||
"-O",
|
||||
"ReleaseSmall",
|
||||
// useful for debugging
|
||||
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll",
|
||||
])
|
||||
|
|
|
@ -10,6 +10,8 @@ use roc_mono::ir::OptLevel;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct CodeGenTiming {
|
||||
pub code_gen: Duration,
|
||||
|
@ -20,33 +22,15 @@ pub struct CodeGenTiming {
|
|||
// llvm we're using, consider moving me somewhere else.
|
||||
const LLVM_VERSION: &str = "12";
|
||||
|
||||
// TODO how should imported modules factor into this? What if those use builtins too?
|
||||
// TODO this should probably use more helper functions
|
||||
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
|
||||
#[cfg(feature = "llvm")]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn gen_from_mono_module(
|
||||
arena: &bumpalo::Bump,
|
||||
mut loaded: MonomorphizedModule,
|
||||
roc_file_path: &Path,
|
||||
target: &target_lexicon::Triple,
|
||||
app_o_file: &Path,
|
||||
opt_level: OptLevel,
|
||||
emit_debug_info: bool,
|
||||
) -> CodeGenTiming {
|
||||
use crate::target::{self, convert_opt_level};
|
||||
use inkwell::attributes::{Attribute, AttributeLoc};
|
||||
use inkwell::context::Context;
|
||||
use inkwell::module::Linkage;
|
||||
use inkwell::targets::{CodeModel, FileType, RelocMode};
|
||||
use std::time::SystemTime;
|
||||
|
||||
// TODO instead of finding exhaustiveness problems in monomorphization, find
|
||||
// them after type checking (like Elm does) so we can complete the entire
|
||||
// `roc check` process without needing to monomorphize.
|
||||
/// Returns the number of problems reported.
|
||||
pub fn report_problems(loaded: &mut MonomorphizedModule) -> usize {
|
||||
use roc_reporting::report::{
|
||||
can_problem, mono_problem, type_problem, Report, RocDocAllocator, Severity::*,
|
||||
DEFAULT_PALETTE,
|
||||
};
|
||||
|
||||
let code_gen_start = SystemTime::now();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
// This will often over-allocate total memory, but it means we definitely
|
||||
|
@ -55,10 +39,10 @@ pub fn gen_from_mono_module(
|
|||
let mut warnings = Vec::with_capacity(total_problems);
|
||||
let mut errors = Vec::with_capacity(total_problems);
|
||||
|
||||
for (home, (module_path, src)) in loaded.sources {
|
||||
for (home, (module_path, src)) in loaded.sources.iter() {
|
||||
let mut src_lines: Vec<&str> = Vec::new();
|
||||
|
||||
if let Some((_, header_src)) = loaded.header_sources.get(&home) {
|
||||
if let Some((_, header_src)) = loaded.header_sources.get(home) {
|
||||
src_lines.extend(header_src.split('\n'));
|
||||
src_lines.extend(src.split('\n').skip(1));
|
||||
} else {
|
||||
|
@ -66,9 +50,10 @@ pub fn gen_from_mono_module(
|
|||
}
|
||||
|
||||
// Report parsing and canonicalization problems
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &loaded.interns);
|
||||
let alloc = RocDocAllocator::new(&src_lines, *home, &loaded.interns);
|
||||
|
||||
let problems = loaded.can_problems.remove(home).unwrap_or_default();
|
||||
|
||||
let problems = loaded.can_problems.remove(&home).unwrap_or_default();
|
||||
for problem in problems.into_iter() {
|
||||
let report = can_problem(&alloc, module_path.clone(), problem);
|
||||
let severity = report.severity;
|
||||
|
@ -86,9 +71,10 @@ pub fn gen_from_mono_module(
|
|||
}
|
||||
}
|
||||
|
||||
let problems = loaded.type_problems.remove(&home).unwrap_or_default();
|
||||
let problems = loaded.type_problems.remove(home).unwrap_or_default();
|
||||
|
||||
for problem in problems {
|
||||
let report = type_problem(&alloc, module_path.clone(), problem);
|
||||
if let Some(report) = type_problem(&alloc, module_path.clone(), problem) {
|
||||
let severity = report.severity;
|
||||
let mut buf = String::new();
|
||||
|
||||
|
@ -103,8 +89,10 @@ pub fn gen_from_mono_module(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let problems = loaded.mono_problems.remove(home).unwrap_or_default();
|
||||
|
||||
let problems = loaded.mono_problems.remove(&home).unwrap_or_default();
|
||||
for problem in problems {
|
||||
let report = mono_problem(&alloc, module_path.clone(), problem);
|
||||
let severity = report.severity;
|
||||
|
@ -123,12 +111,18 @@ pub fn gen_from_mono_module(
|
|||
}
|
||||
}
|
||||
|
||||
let problems_reported;
|
||||
|
||||
// Only print warnings if there are no errors
|
||||
if errors.is_empty() {
|
||||
problems_reported = warnings.len();
|
||||
|
||||
for warning in warnings {
|
||||
println!("\n{}\n", warning);
|
||||
}
|
||||
} else {
|
||||
problems_reported = errors.len();
|
||||
|
||||
for error in errors {
|
||||
println!("\n{}\n", error);
|
||||
}
|
||||
|
@ -140,10 +134,35 @@ pub fn gen_from_mono_module(
|
|||
// The horizontal rule is nice when running the program right after
|
||||
// compiling it, as it lets you clearly see where the compiler
|
||||
// errors/warnings end and the program output begins.
|
||||
if total_problems > 0 {
|
||||
if problems_reported > 0 {
|
||||
println!("{}\u{001B}[0m\n", Report::horizontal_rule(&palette));
|
||||
}
|
||||
|
||||
problems_reported
|
||||
}
|
||||
|
||||
// TODO how should imported modules factor into this? What if those use builtins too?
|
||||
// TODO this should probably use more helper functions
|
||||
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
|
||||
#[cfg(feature = "llvm")]
|
||||
pub fn gen_from_mono_module_llvm(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
roc_file_path: &Path,
|
||||
target: &target_lexicon::Triple,
|
||||
app_o_file: &Path,
|
||||
opt_level: OptLevel,
|
||||
emit_debug_info: bool,
|
||||
) -> CodeGenTiming {
|
||||
use crate::target::{self, convert_opt_level};
|
||||
use inkwell::attributes::{Attribute, AttributeLoc};
|
||||
use inkwell::context::Context;
|
||||
use inkwell::module::Linkage;
|
||||
use inkwell::targets::{CodeModel, FileType, RelocMode};
|
||||
use std::time::SystemTime;
|
||||
|
||||
let code_gen_start = SystemTime::now();
|
||||
|
||||
// Generate the binary
|
||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||
let context = Context::create();
|
||||
|
@ -286,6 +305,7 @@ pub fn gen_from_mono_module(
|
|||
.unwrap();
|
||||
|
||||
let llc_args = &[
|
||||
"-relocation-model=pic",
|
||||
"-filetype=obj",
|
||||
app_bc_file.to_str().unwrap(),
|
||||
"-o",
|
||||
|
@ -325,7 +345,7 @@ pub fn gen_from_mono_module(
|
|||
use target_lexicon::Architecture;
|
||||
match target.architecture {
|
||||
Architecture::X86_64 | Architecture::X86_32(_) | Architecture::Aarch64(_) => {
|
||||
let reloc = RelocMode::Default;
|
||||
let reloc = RelocMode::PIC;
|
||||
let model = CodeModel::Default;
|
||||
let target_machine =
|
||||
target::target_machine(target, convert_opt_level(opt_level), reloc, model)
|
||||
|
@ -354,3 +374,78 @@ pub fn gen_from_mono_module(
|
|||
emit_o_file,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_from_mono_module_dev(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
target: &target_lexicon::Triple,
|
||||
app_o_file: &Path,
|
||||
) -> CodeGenTiming {
|
||||
use target_lexicon::Architecture;
|
||||
|
||||
match target.architecture {
|
||||
Architecture::Wasm32 => gen_from_mono_module_dev_wasm32(arena, loaded, app_o_file),
|
||||
Architecture::X86_64 => {
|
||||
gen_from_mono_module_dev_assembly(arena, loaded, target, app_o_file)
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_from_mono_module_dev_wasm32(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
app_o_file: &Path,
|
||||
) -> CodeGenTiming {
|
||||
let mut procedures = MutMap::default();
|
||||
|
||||
for (key, proc) in loaded.procedures {
|
||||
procedures.insert(key, proc);
|
||||
}
|
||||
|
||||
let exposed_to_host = loaded
|
||||
.exposed_to_host
|
||||
.keys()
|
||||
.copied()
|
||||
.collect::<MutSet<_>>();
|
||||
|
||||
let env = roc_gen_wasm::Env {
|
||||
arena,
|
||||
interns: loaded.interns,
|
||||
exposed_to_host,
|
||||
};
|
||||
|
||||
let bytes = roc_gen_wasm::build_module(&env, procedures).unwrap();
|
||||
|
||||
std::fs::write(&app_o_file, &bytes).expect("failed to write object to file");
|
||||
|
||||
CodeGenTiming::default()
|
||||
}
|
||||
|
||||
fn gen_from_mono_module_dev_assembly(
|
||||
arena: &bumpalo::Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
target: &target_lexicon::Triple,
|
||||
app_o_file: &Path,
|
||||
) -> CodeGenTiming {
|
||||
let lazy_literals = true;
|
||||
let generate_allocators = false; // provided by the platform
|
||||
|
||||
let env = roc_gen_dev::Env {
|
||||
arena,
|
||||
interns: loaded.interns,
|
||||
exposed_to_host: loaded.exposed_to_host.keys().copied().collect(),
|
||||
lazy_literals,
|
||||
generate_allocators,
|
||||
};
|
||||
|
||||
let module_object = roc_gen_dev::build_module(&env, target, loaded.procedures)
|
||||
.expect("failed to compile module");
|
||||
|
||||
let module_out = module_object
|
||||
.write()
|
||||
.expect("failed to build output object");
|
||||
std::fs::write(&app_o_file, module_out).expect("failed to write object to file");
|
||||
|
||||
CodeGenTiming::default()
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ pub fn target_machine(
|
|||
#[cfg(feature = "llvm")]
|
||||
pub fn convert_opt_level(level: OptLevel) -> OptimizationLevel {
|
||||
match level {
|
||||
OptLevel::Normal => OptimizationLevel::None,
|
||||
OptLevel::Development | OptLevel::Normal => OptimizationLevel::None,
|
||||
OptLevel::Optimize => OptimizationLevel::Aggressive,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ pub fn build(b: *Builder) void {
|
|||
|
||||
// 32-bit wasm
|
||||
wasm32_target.cpu_arch = std.Target.Cpu.Arch.wasm32;
|
||||
wasm32_target.os_tag = std.Target.Os.Tag.wasi;
|
||||
wasm32_target.os_tag = std.Target.Os.Tag.freestanding;
|
||||
wasm32_target.abi = std.Target.Abi.none;
|
||||
|
||||
const obj_name_wasm32 = "builtins-wasm32";
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
|
||||
// Dec Module
|
||||
const dec = @import("dec.zig");
|
||||
|
@ -110,6 +108,7 @@ const utils = @import("utils.zig");
|
|||
comptime {
|
||||
exportUtilsFn(utils.test_panic, "test_panic");
|
||||
exportUtilsFn(utils.decrefC, "decref");
|
||||
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
|
||||
|
||||
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
|
||||
}
|
||||
|
@ -140,13 +139,21 @@ fn exportUtilsFn(comptime func: anytype, comptime func_name: []const u8) void {
|
|||
|
||||
// Custom panic function, as builtin Zig version errors during LLVM verification
|
||||
pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn {
|
||||
if (std.builtin.is_test) {
|
||||
std.debug.print("{s}: {?}", .{ message, stacktrace });
|
||||
} else {
|
||||
_ = message;
|
||||
_ = stacktrace;
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
// Run all tests in imported modules
|
||||
// https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94
|
||||
test "" {
|
||||
const testing = std.testing;
|
||||
|
||||
testing.refAllDecls(@This());
|
||||
}
|
||||
|
||||
|
@ -158,7 +165,7 @@ test "" {
|
|||
//
|
||||
// Thank you Zig Contributors!
|
||||
export fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 {
|
||||
// @setRuntimeSafety(builtin.is_test);
|
||||
// @setRuntimeSafety(std.builtin.is_test);
|
||||
|
||||
const min = @bitCast(i128, @as(u128, 1 << (128 - 1)));
|
||||
const max = ~min;
|
||||
|
|
|
@ -1150,8 +1150,8 @@ fn strToBytes(arg: RocStr) RocList {
|
|||
}
|
||||
|
||||
const FromUtf8Result = extern struct {
|
||||
string: RocStr,
|
||||
byte_index: usize,
|
||||
string: RocStr,
|
||||
is_ok: bool,
|
||||
problem_code: Utf8ByteProblem,
|
||||
};
|
||||
|
|
|
@ -117,6 +117,16 @@ pub fn decrefC(
|
|||
return @call(.{ .modifier = always_inline }, decref_ptr_to_refcount, .{ bytes, alignment });
|
||||
}
|
||||
|
||||
pub fn decrefCheckNullC(
|
||||
bytes_or_null: ?[*]u8,
|
||||
alignment: u32,
|
||||
) callconv(.C) void {
|
||||
if (bytes_or_null) |bytes| {
|
||||
const isizes: [*]isize = @ptrCast([*]isize, @alignCast(@sizeOf(isize), bytes));
|
||||
return @call(.{ .modifier = always_inline }, decref_ptr_to_refcount, .{ isizes - 1, alignment });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decref(
|
||||
bytes_or_null: ?[*]u8,
|
||||
data_bytes: usize,
|
||||
|
|
BIN
compiler/builtins/bitcode/wasi-libc.a
Normal file
BIN
compiler/builtins/bitcode/wasi-libc.a
Normal file
Binary file not shown.
|
@ -83,3 +83,4 @@ pub const DEC_DIV: &str = "roc_builtins.dec.div";
|
|||
|
||||
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
|
||||
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
|
||||
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
|
||||
|
|
|
@ -82,6 +82,7 @@ pub fn canonicalize_annotation(
|
|||
let mut introduced_variables = IntroducedVariables::default();
|
||||
let mut references = MutSet::default();
|
||||
let mut aliases = SendMap::default();
|
||||
|
||||
let typ = can_annotation_help(
|
||||
env,
|
||||
annotation,
|
||||
|
@ -249,28 +250,7 @@ fn can_annotation_help(
|
|||
actual: Box::new(actual),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let mut args = Vec::new();
|
||||
|
||||
references.insert(symbol);
|
||||
|
||||
for arg in *type_arguments {
|
||||
let arg_ann = can_annotation_help(
|
||||
env,
|
||||
&arg.value,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
introduced_variables,
|
||||
local_aliases,
|
||||
references,
|
||||
);
|
||||
|
||||
args.push(arg_ann);
|
||||
}
|
||||
|
||||
Type::Apply(symbol, args)
|
||||
}
|
||||
None => Type::Apply(symbol, args),
|
||||
}
|
||||
}
|
||||
BoundVariable(v) => {
|
||||
|
|
|
@ -2,10 +2,9 @@ use crate::{Backend, Env, Relocation};
|
|||
use bumpalo::collections::Vec;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::ir::{BranchInfo, Literal, Stmt};
|
||||
use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, SelfRecursive, Stmt};
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
use std::marker::PhantomData;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
pub mod aarch64;
|
||||
pub mod x86_64;
|
||||
|
@ -211,12 +210,16 @@ pub struct Backend64Bit<
|
|||
env: &'a Env<'a>,
|
||||
buf: Vec<'a, u8>,
|
||||
relocs: Vec<'a, Relocation>,
|
||||
proc_name: Option<String>,
|
||||
is_self_recursive: Option<SelfRecursive>,
|
||||
|
||||
last_seen_map: MutMap<Symbol, *const Stmt<'a>>,
|
||||
layout_map: MutMap<Symbol, *const Layout<'a>>,
|
||||
layout_map: MutMap<Symbol, Layout<'a>>,
|
||||
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
|
||||
|
||||
symbol_storage_map: MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
|
||||
literal_map: MutMap<Symbol, Literal<'a>>,
|
||||
join_map: MutMap<JoinPointId, u64>,
|
||||
|
||||
// This should probably be smarter than a vec.
|
||||
// There are certain registers we should always use first. With pushing and popping, this could get mixed.
|
||||
|
@ -247,11 +250,13 @@ impl<
|
|||
CC: CallConv<GeneralReg, FloatReg>,
|
||||
> Backend<'a> for Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC>
|
||||
{
|
||||
fn new(env: &'a Env, _target: &Triple) -> Result<Self, String> {
|
||||
fn new(env: &'a Env) -> Result<Self, String> {
|
||||
Ok(Backend64Bit {
|
||||
phantom_asm: PhantomData,
|
||||
phantom_cc: PhantomData,
|
||||
env,
|
||||
proc_name: None,
|
||||
is_self_recursive: None,
|
||||
buf: bumpalo::vec![in env.arena],
|
||||
relocs: bumpalo::vec![in env.arena],
|
||||
last_seen_map: MutMap::default(),
|
||||
|
@ -259,6 +264,7 @@ impl<
|
|||
free_map: MutMap::default(),
|
||||
symbol_storage_map: MutMap::default(),
|
||||
literal_map: MutMap::default(),
|
||||
join_map: MutMap::default(),
|
||||
general_free_regs: bumpalo::vec![in env.arena],
|
||||
general_used_regs: bumpalo::vec![in env.arena],
|
||||
general_used_callee_saved_regs: MutSet::default(),
|
||||
|
@ -275,12 +281,15 @@ impl<
|
|||
self.env
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
fn reset(&mut self, name: String, is_self_recursive: SelfRecursive) {
|
||||
self.proc_name = Some(name);
|
||||
self.is_self_recursive = Some(is_self_recursive);
|
||||
self.stack_size = 0;
|
||||
self.free_stack_chunks.clear();
|
||||
self.fn_call_stack_size = 0;
|
||||
self.last_seen_map.clear();
|
||||
self.layout_map.clear();
|
||||
self.join_map.clear();
|
||||
self.free_map.clear();
|
||||
self.symbol_storage_map.clear();
|
||||
self.buf.clear();
|
||||
|
@ -304,7 +313,7 @@ impl<
|
|||
&mut self.last_seen_map
|
||||
}
|
||||
|
||||
fn layout_map(&mut self) -> &mut MutMap<Symbol, *const Layout<'a>> {
|
||||
fn layout_map(&mut self) -> &mut MutMap<Symbol, Layout<'a>> {
|
||||
&mut self.layout_map
|
||||
}
|
||||
|
||||
|
@ -330,8 +339,49 @@ impl<
|
|||
)?;
|
||||
let setup_offset = out.len();
|
||||
|
||||
// Deal with jumps to the return address.
|
||||
let old_relocs = std::mem::replace(&mut self.relocs, bumpalo::vec![in self.env.arena]);
|
||||
|
||||
// Check if their is an unnessary jump to return right at the end of the function.
|
||||
let mut end_jmp_size = 0;
|
||||
for reloc in old_relocs
|
||||
.iter()
|
||||
.filter(|reloc| matches!(reloc, Relocation::JmpToReturn { .. }))
|
||||
{
|
||||
if let Relocation::JmpToReturn {
|
||||
inst_loc,
|
||||
inst_size,
|
||||
..
|
||||
} = reloc
|
||||
{
|
||||
if *inst_loc as usize + *inst_size as usize == self.buf.len() {
|
||||
end_jmp_size = *inst_size as usize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update jumps to returns.
|
||||
let ret_offset = self.buf.len() - end_jmp_size;
|
||||
let mut tmp = bumpalo::vec![in self.env.arena];
|
||||
for reloc in old_relocs
|
||||
.iter()
|
||||
.filter(|reloc| matches!(reloc, Relocation::JmpToReturn { .. }))
|
||||
{
|
||||
if let Relocation::JmpToReturn {
|
||||
inst_loc,
|
||||
inst_size,
|
||||
offset,
|
||||
} = reloc
|
||||
{
|
||||
if *inst_loc as usize + *inst_size as usize != self.buf.len() {
|
||||
self.update_jmp_imm32_offset(&mut tmp, *inst_loc, *offset, ret_offset as u64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add function body.
|
||||
out.extend(&self.buf);
|
||||
out.extend(&self.buf[..self.buf.len() - end_jmp_size]);
|
||||
|
||||
// Cleanup stack.
|
||||
CC::cleanup_stack(
|
||||
|
@ -342,10 +392,13 @@ impl<
|
|||
)?;
|
||||
ASM::ret(&mut out);
|
||||
|
||||
// Update relocs to include stack setup offset.
|
||||
// Update other relocs to include stack setup offset.
|
||||
let mut out_relocs = bumpalo::vec![in self.env.arena];
|
||||
let old_relocs = std::mem::replace(&mut self.relocs, bumpalo::vec![in self.env.arena]);
|
||||
out_relocs.extend(old_relocs.into_iter().map(|reloc| match reloc {
|
||||
out_relocs.extend(
|
||||
old_relocs
|
||||
.into_iter()
|
||||
.filter(|reloc| !matches!(reloc, Relocation::JmpToReturn { .. }))
|
||||
.map(|reloc| match reloc {
|
||||
Relocation::LocalData { offset, data } => Relocation::LocalData {
|
||||
offset: offset + setup_offset as u64,
|
||||
data,
|
||||
|
@ -358,7 +411,9 @@ impl<
|
|||
offset: offset + setup_offset as u64,
|
||||
name,
|
||||
},
|
||||
}));
|
||||
Relocation::JmpToReturn { .. } => unreachable!(),
|
||||
}),
|
||||
);
|
||||
Ok((out.into_bump_slice(), out_relocs.into_bump_slice()))
|
||||
}
|
||||
|
||||
|
@ -401,29 +456,13 @@ impl<
|
|||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
if let Some(SelfRecursive::SelfRecursive(id)) = self.is_self_recursive {
|
||||
if &fn_name == self.proc_name.as_ref().unwrap() && self.join_map.contains_key(&id) {
|
||||
return self.build_jump(&id, args, arg_layouts, ret_layout);
|
||||
}
|
||||
}
|
||||
// Save used caller saved regs.
|
||||
let old_general_used_regs = std::mem::replace(
|
||||
&mut self.general_used_regs,
|
||||
bumpalo::vec![in self.env.arena],
|
||||
);
|
||||
for (reg, saved_sym) in old_general_used_regs.into_iter() {
|
||||
if CC::general_caller_saved(®) {
|
||||
self.general_free_regs.push(reg);
|
||||
self.free_to_stack(&saved_sym)?;
|
||||
} else {
|
||||
self.general_used_regs.push((reg, saved_sym));
|
||||
}
|
||||
}
|
||||
let old_float_used_regs =
|
||||
std::mem::replace(&mut self.float_used_regs, bumpalo::vec![in self.env.arena]);
|
||||
for (reg, saved_sym) in old_float_used_regs.into_iter() {
|
||||
if CC::float_caller_saved(®) {
|
||||
self.float_free_regs.push(reg);
|
||||
self.free_to_stack(&saved_sym)?;
|
||||
} else {
|
||||
self.float_used_regs.push((reg, saved_sym));
|
||||
}
|
||||
}
|
||||
self.push_used_caller_saved_regs_to_stack()?;
|
||||
|
||||
// Put values in param regs or on top of the stack.
|
||||
let tmp_stack_size = CC::store_args(
|
||||
|
@ -486,7 +525,7 @@ impl<
|
|||
// Build unconditional jump to the end of this switch.
|
||||
// Since we don't know the offset yet, set it to 0 and overwrite later.
|
||||
let jmp_location = self.buf.len();
|
||||
let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0);
|
||||
let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
|
||||
ret_jumps.push((jmp_location, jmp_offset));
|
||||
|
||||
// Overwite the original jne with the correct offset.
|
||||
|
@ -510,12 +549,12 @@ impl<
|
|||
// Update all return jumps to jump past the default case.
|
||||
let ret_offset = self.buf.len();
|
||||
for (jmp_location, start_offset) in ret_jumps.into_iter() {
|
||||
tmp.clear();
|
||||
let jmp_offset = ret_offset - start_offset;
|
||||
ASM::jmp_imm32(&mut tmp, jmp_offset as i32);
|
||||
for (i, byte) in tmp.iter().enumerate() {
|
||||
self.buf[jmp_location + i] = *byte;
|
||||
}
|
||||
self.update_jmp_imm32_offset(
|
||||
&mut tmp,
|
||||
jmp_location as u64,
|
||||
start_offset as u64,
|
||||
ret_offset as u64,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
|
@ -526,6 +565,134 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
fn build_join(
|
||||
&mut self,
|
||||
id: &JoinPointId,
|
||||
parameters: &'a [Param<'a>],
|
||||
body: &'a Stmt<'a>,
|
||||
remainder: &'a Stmt<'a>,
|
||||
ret_layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
// Create jump to remaining.
|
||||
let jmp_location = self.buf.len();
|
||||
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
|
||||
|
||||
// This section can essentially be seen as a sub function within the main function.
|
||||
// Thus we build using a new backend with some minor extra synchronization.
|
||||
let mut sub_backend = Self::new(self.env)?;
|
||||
sub_backend.reset(
|
||||
self.proc_name.as_ref().unwrap().clone(),
|
||||
self.is_self_recursive.as_ref().unwrap().clone(),
|
||||
);
|
||||
// Sync static maps of important information.
|
||||
sub_backend.last_seen_map = self.last_seen_map.clone();
|
||||
sub_backend.layout_map = self.layout_map.clone();
|
||||
sub_backend.free_map = self.free_map.clone();
|
||||
|
||||
// Setup join point.
|
||||
sub_backend.join_map.insert(*id, 0);
|
||||
self.join_map.insert(*id, self.buf.len() as u64);
|
||||
|
||||
// Sync stack size so the "sub function" doesn't mess up our stack.
|
||||
sub_backend.stack_size = self.stack_size;
|
||||
sub_backend.fn_call_stack_size = self.fn_call_stack_size;
|
||||
|
||||
// Load params as if they were args.
|
||||
let mut args = bumpalo::vec![in self.env.arena];
|
||||
for param in parameters {
|
||||
args.push((param.layout, param.symbol));
|
||||
}
|
||||
sub_backend.load_args(args.into_bump_slice(), ret_layout)?;
|
||||
|
||||
// Build all statements in body.
|
||||
sub_backend.build_stmt(body, ret_layout)?;
|
||||
|
||||
// Merge the "sub function" into the main function.
|
||||
let sub_func_offset = self.buf.len() as u64;
|
||||
self.buf.extend_from_slice(&sub_backend.buf);
|
||||
// Update stack based on how much was used by the sub function.
|
||||
self.stack_size = sub_backend.stack_size;
|
||||
self.fn_call_stack_size = sub_backend.fn_call_stack_size;
|
||||
// Relocations must be shifted to be merged correctly.
|
||||
self.relocs
|
||||
.extend(sub_backend.relocs.into_iter().map(|reloc| match reloc {
|
||||
Relocation::LocalData { offset, data } => Relocation::LocalData {
|
||||
offset: offset + sub_func_offset,
|
||||
data,
|
||||
},
|
||||
Relocation::LinkedData { offset, name } => Relocation::LinkedData {
|
||||
offset: offset + sub_func_offset,
|
||||
name,
|
||||
},
|
||||
Relocation::LinkedFunction { offset, name } => Relocation::LinkedFunction {
|
||||
offset: offset + sub_func_offset,
|
||||
name,
|
||||
},
|
||||
Relocation::JmpToReturn {
|
||||
inst_loc,
|
||||
inst_size,
|
||||
offset,
|
||||
} => Relocation::JmpToReturn {
|
||||
inst_loc: inst_loc + sub_func_offset,
|
||||
inst_size,
|
||||
offset: offset + sub_func_offset,
|
||||
},
|
||||
}));
|
||||
|
||||
// Overwite the original jump with the correct offset.
|
||||
let mut tmp = bumpalo::vec![in self.env.arena];
|
||||
self.update_jmp_imm32_offset(
|
||||
&mut tmp,
|
||||
jmp_location as u64,
|
||||
start_offset as u64,
|
||||
self.buf.len() as u64,
|
||||
);
|
||||
|
||||
// Build remainder of function.
|
||||
self.build_stmt(remainder, ret_layout)
|
||||
}
|
||||
|
||||
fn build_jump(
|
||||
&mut self,
|
||||
id: &JoinPointId,
|
||||
args: &'a [Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
// Treat this like a function call, but with a jump instead of a call instruction at the end.
|
||||
|
||||
self.push_used_caller_saved_regs_to_stack()?;
|
||||
|
||||
let tmp_stack_size = CC::store_args(
|
||||
&mut self.buf,
|
||||
&self.symbol_storage_map,
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
)?;
|
||||
self.fn_call_stack_size = std::cmp::max(self.fn_call_stack_size, tmp_stack_size);
|
||||
|
||||
let jmp_location = self.buf.len();
|
||||
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
|
||||
|
||||
if let Some(offset) = self.join_map.get(id) {
|
||||
let offset = *offset;
|
||||
let mut tmp = bumpalo::vec![in self.env.arena];
|
||||
self.update_jmp_imm32_offset(
|
||||
&mut tmp,
|
||||
jmp_location as u64,
|
||||
start_offset as u64,
|
||||
offset,
|
||||
);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"Jump: unknown point specified to jump to: {:?}",
|
||||
id
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn build_num_abs(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
|
@ -828,29 +995,26 @@ impl<
|
|||
fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String> {
|
||||
let val = self.symbol_storage_map.get(sym);
|
||||
match val {
|
||||
Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => Ok(()),
|
||||
Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => {}
|
||||
Some(SymbolStorage::GeneralReg(reg)) => {
|
||||
// If it fits in a general purpose register, just copy it over to.
|
||||
// Technically this can be optimized to produce shorter instructions if less than 64bits.
|
||||
ASM::mov_reg64_reg64(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *reg);
|
||||
Ok(())
|
||||
}
|
||||
Some(SymbolStorage::FloatReg(reg)) if *reg == CC::FLOAT_RETURN_REGS[0] => Ok(()),
|
||||
Some(SymbolStorage::FloatReg(reg)) if *reg == CC::FLOAT_RETURN_REGS[0] => {}
|
||||
Some(SymbolStorage::FloatReg(reg)) => {
|
||||
ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg);
|
||||
Ok(())
|
||||
}
|
||||
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(())
|
||||
}
|
||||
Layout::Builtin(Builtin::Float64) => {
|
||||
ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset);
|
||||
Ok(())
|
||||
}
|
||||
Layout::Struct(field_layouts) => {
|
||||
let (offset, size) = (*offset, *size);
|
||||
// Nothing to do for empty struct
|
||||
if size > 0 {
|
||||
let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER)
|
||||
{
|
||||
|
@ -858,23 +1022,34 @@ impl<
|
|||
} else {
|
||||
None
|
||||
};
|
||||
CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg)
|
||||
} else {
|
||||
// Nothing to do for empty struct
|
||||
Ok(())
|
||||
CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg)?;
|
||||
}
|
||||
}
|
||||
x => Err(format!(
|
||||
x => {
|
||||
return Err(format!(
|
||||
"returning symbol with layout, {:?}, is not yet implemented",
|
||||
x
|
||||
)),
|
||||
));
|
||||
}
|
||||
},
|
||||
Some(x) => Err(format!(
|
||||
Some(x) => {
|
||||
return Err(format!(
|
||||
"returning symbol storage, {:?}, is not yet implemented",
|
||||
x
|
||||
)),
|
||||
None => Err(format!("Unknown return symbol: {}", sym)),
|
||||
));
|
||||
}
|
||||
None => {
|
||||
return Err(format!("Unknown return symbol: {}", sym));
|
||||
}
|
||||
}
|
||||
let inst_loc = self.buf.len() as u64;
|
||||
let offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678) as u64;
|
||||
self.relocs.push(Relocation::JmpToReturn {
|
||||
inst_loc,
|
||||
inst_size: self.buf.len() as u64 - inst_loc,
|
||||
offset,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1212,4 +1387,74 @@ impl<
|
|||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_used_caller_saved_regs_to_stack(&mut self) -> Result<(), String> {
|
||||
let old_general_used_regs = std::mem::replace(
|
||||
&mut self.general_used_regs,
|
||||
bumpalo::vec![in self.env.arena],
|
||||
);
|
||||
for (reg, saved_sym) in old_general_used_regs.into_iter() {
|
||||
if CC::general_caller_saved(®) {
|
||||
self.general_free_regs.push(reg);
|
||||
self.free_to_stack(&saved_sym)?;
|
||||
} else {
|
||||
self.general_used_regs.push((reg, saved_sym));
|
||||
}
|
||||
}
|
||||
let old_float_used_regs =
|
||||
std::mem::replace(&mut self.float_used_regs, bumpalo::vec![in self.env.arena]);
|
||||
for (reg, saved_sym) in old_float_used_regs.into_iter() {
|
||||
if CC::float_caller_saved(®) {
|
||||
self.float_free_regs.push(reg);
|
||||
self.free_to_stack(&saved_sym)?;
|
||||
} else {
|
||||
self.float_used_regs.push((reg, saved_sym));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Updates a jump instruction to a new offset and returns the number of bytes written.
|
||||
fn update_jmp_imm32_offset(
|
||||
&mut self,
|
||||
tmp: &mut Vec<'a, u8>,
|
||||
jmp_location: u64,
|
||||
base_offset: u64,
|
||||
target_offset: u64,
|
||||
) {
|
||||
tmp.clear();
|
||||
let jmp_offset = target_offset as i32 - base_offset as i32;
|
||||
ASM::jmp_imm32(tmp, jmp_offset);
|
||||
for (i, byte) in tmp.iter().enumerate() {
|
||||
self.buf[jmp_location as usize + i] = *byte;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! single_register_integers {
|
||||
() => {
|
||||
Builtin::Int1
|
||||
| Builtin::Int8
|
||||
| Builtin::Int16
|
||||
| Builtin::Int32
|
||||
| Builtin::Int64
|
||||
| Builtin::Usize
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! single_register_floats {
|
||||
() => {
|
||||
// Float16 is explicitly ignored because it is not supported by must hardware and may require special exceptions.
|
||||
// Builtin::Float16 |
|
||||
Builtin::Float32 | Builtin::Float64
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! single_register_builtins {
|
||||
() => {
|
||||
single_register_integers!() | single_register_floats!()
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, PTR_SIZE};
|
||||
use crate::Relocation;
|
||||
use crate::{
|
||||
single_register_builtins, single_register_floats, single_register_integers, Relocation,
|
||||
};
|
||||
use bumpalo::collections::Vec;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
@ -191,7 +193,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
}
|
||||
for (layout, sym) in args.iter() {
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::Int64) => {
|
||||
Layout::Builtin(single_register_integers!()) => {
|
||||
if general_i < Self::GENERAL_PARAM_REGS.len() {
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
|
@ -210,7 +212,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
);
|
||||
}
|
||||
}
|
||||
Layout::Builtin(Builtin::Float64) => {
|
||||
Layout::Builtin(single_register_floats!()) => {
|
||||
if float_i < Self::FLOAT_PARAM_REGS.len() {
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
|
@ -229,6 +231,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
);
|
||||
}
|
||||
}
|
||||
Layout::Struct(&[]) => {}
|
||||
x => {
|
||||
return Err(format!(
|
||||
"Loading args with layout {:?} not yet implementd",
|
||||
|
@ -254,8 +257,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
// For most return layouts we will do nothing.
|
||||
// In some cases, we need to put the return address as the first arg.
|
||||
match ret_layout {
|
||||
Layout::Builtin(Builtin::Int64) => {}
|
||||
Layout::Builtin(Builtin::Float64) => {}
|
||||
Layout::Builtin(single_register_builtins!()) => {}
|
||||
x => {
|
||||
return Err(format!(
|
||||
"receiving return type, {:?}, is not yet implemented",
|
||||
|
@ -265,7 +267,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
}
|
||||
for (i, layout) in arg_layouts.iter().enumerate() {
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::Int64) => {
|
||||
Layout::Builtin(single_register_integers!()) => {
|
||||
if general_i < Self::GENERAL_PARAM_REGS.len() {
|
||||
// Load the value to the param reg.
|
||||
let dst = Self::GENERAL_PARAM_REGS[general_i];
|
||||
|
@ -319,7 +321,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
stack_offset += 8;
|
||||
}
|
||||
}
|
||||
Layout::Builtin(Builtin::Float64) => {
|
||||
Layout::Builtin(single_register_floats!()) => {
|
||||
if float_i < Self::FLOAT_PARAM_REGS.len() {
|
||||
// Load the value to the param reg.
|
||||
let dst = Self::FLOAT_PARAM_REGS[float_i];
|
||||
|
@ -371,6 +373,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
stack_offset += 8;
|
||||
}
|
||||
}
|
||||
Layout::Struct(&[]) => {}
|
||||
x => {
|
||||
return Err(format!(
|
||||
"calling with arg type, {:?}, is not yet implemented",
|
||||
|
@ -529,13 +532,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
for (layout, sym) in args.iter() {
|
||||
if i < Self::GENERAL_PARAM_REGS.len() {
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::Int64) => {
|
||||
Layout::Builtin(single_register_integers!()) => {
|
||||
symbol_map
|
||||
.insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]));
|
||||
}
|
||||
Layout::Builtin(Builtin::Float64) => {
|
||||
Layout::Builtin(single_register_floats!()) => {
|
||||
symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i]));
|
||||
}
|
||||
Layout::Struct(&[]) => {}
|
||||
x => {
|
||||
return Err(format!(
|
||||
"Loading args with layout {:?} not yet implementd",
|
||||
|
@ -546,8 +550,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
i += 1;
|
||||
} else {
|
||||
base_offset += match layout {
|
||||
Layout::Builtin(Builtin::Int64) => 8,
|
||||
Layout::Builtin(Builtin::Float64) => 8,
|
||||
Layout::Builtin(single_register_builtins!()) => 8,
|
||||
x => {
|
||||
return Err(format!(
|
||||
"Loading args with layout {:?} not yet implemented",
|
||||
|
@ -581,8 +584,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
// For most return layouts we will do nothing.
|
||||
// In some cases, we need to put the return address as the first arg.
|
||||
match ret_layout {
|
||||
Layout::Builtin(Builtin::Int64) => {}
|
||||
Layout::Builtin(Builtin::Float64) => {}
|
||||
Layout::Builtin(single_register_builtins!()) => {}
|
||||
x => {
|
||||
return Err(format!(
|
||||
"receiving return type, {:?}, is not yet implemented",
|
||||
|
@ -592,7 +594,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
}
|
||||
for (i, layout) in arg_layouts.iter().enumerate() {
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::Int64) => {
|
||||
Layout::Builtin(single_register_integers!()) => {
|
||||
if i < Self::GENERAL_PARAM_REGS.len() {
|
||||
// Load the value to the param reg.
|
||||
let dst = Self::GENERAL_PARAM_REGS[reg_i];
|
||||
|
@ -646,7 +648,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
stack_offset += 8;
|
||||
}
|
||||
}
|
||||
Layout::Builtin(Builtin::Float64) => {
|
||||
Layout::Builtin(single_register_floats!()) => {
|
||||
if i < Self::FLOAT_PARAM_REGS.len() {
|
||||
// Load the value to the param reg.
|
||||
let dst = Self::FLOAT_PARAM_REGS[reg_i];
|
||||
|
@ -698,6 +700,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
stack_offset += 8;
|
||||
}
|
||||
}
|
||||
Layout::Struct(&[]) => {}
|
||||
x => {
|
||||
return Err(format!(
|
||||
"calling with arg type, {:?}, is not yet implemented",
|
||||
|
|
|
@ -9,10 +9,10 @@ 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, ListLiteralElement, Literal, Proc, Stmt,
|
||||
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc,
|
||||
SelfRecursive, Stmt,
|
||||
};
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds};
|
||||
use target_lexicon::Triple;
|
||||
|
||||
mod generic64;
|
||||
mod object_builder;
|
||||
|
@ -46,6 +46,11 @@ pub enum Relocation {
|
|||
offset: u64,
|
||||
name: String,
|
||||
},
|
||||
JmpToReturn {
|
||||
inst_loc: u64,
|
||||
inst_size: u64,
|
||||
offset: u64,
|
||||
},
|
||||
}
|
||||
|
||||
trait Backend<'a>
|
||||
|
@ -53,12 +58,13 @@ where
|
|||
Self: Sized,
|
||||
{
|
||||
/// new creates a new backend that will output to the specific Object.
|
||||
fn new(env: &'a Env, target: &Triple) -> Result<Self, String>;
|
||||
fn new(env: &'a Env) -> Result<Self, String>;
|
||||
|
||||
fn env(&self) -> &'a Env<'a>;
|
||||
|
||||
/// reset resets any registers or other values that may be occupied at the end of a procedure.
|
||||
fn reset(&mut self);
|
||||
/// It also passes basic procedure information to the builder for setup of the next function.
|
||||
fn reset(&mut self, name: String, is_self_recursive: SelfRecursive);
|
||||
|
||||
/// finalize does any setup and cleanup that should happen around the procedure.
|
||||
/// finalize does setup because things like stack size and jump locations are not know until the function is written.
|
||||
|
@ -79,7 +85,10 @@ where
|
|||
|
||||
/// build_proc creates a procedure and outputs it to the wrapped object writer.
|
||||
fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
|
||||
self.reset();
|
||||
let proc_name = LayoutIds::default()
|
||||
.get(proc.name, &proc.ret_layout)
|
||||
.to_symbol_string(proc.name, &self.env().interns);
|
||||
self.reset(proc_name, proc.is_self_recursive);
|
||||
self.load_args(proc.args, &proc.ret_layout)?;
|
||||
for (layout, sym) in proc.args {
|
||||
self.set_layout_map(*sym, layout)?;
|
||||
|
@ -128,6 +137,35 @@ where
|
|||
self.free_symbols(stmt)?;
|
||||
Ok(())
|
||||
}
|
||||
Stmt::Join {
|
||||
id,
|
||||
parameters,
|
||||
body,
|
||||
remainder,
|
||||
} => {
|
||||
for param in parameters.iter() {
|
||||
self.set_layout_map(param.symbol, ¶m.layout)?;
|
||||
}
|
||||
self.build_join(id, parameters, body, remainder, ret_layout)?;
|
||||
self.free_symbols(stmt)?;
|
||||
Ok(())
|
||||
}
|
||||
Stmt::Jump(id, args) => {
|
||||
let mut arg_layouts: bumpalo::collections::Vec<Layout<'a>> =
|
||||
bumpalo::vec![in self.env().arena];
|
||||
arg_layouts.reserve(args.len());
|
||||
let layout_map = self.layout_map();
|
||||
for arg in *args {
|
||||
if let Some(layout) = layout_map.get(arg) {
|
||||
arg_layouts.push(*layout);
|
||||
} else {
|
||||
return Err(format!("the argument, {:?}, has no know layout", arg));
|
||||
}
|
||||
}
|
||||
self.build_jump(id, args, arg_layouts.into_bump_slice(), ret_layout)?;
|
||||
self.free_symbols(stmt)?;
|
||||
Ok(())
|
||||
}
|
||||
x => Err(format!("the statement, {:?}, is not yet implemented", x)),
|
||||
}
|
||||
}
|
||||
|
@ -141,6 +179,25 @@ where
|
|||
ret_layout: &Layout<'a>,
|
||||
) -> Result<(), String>;
|
||||
|
||||
// build_join generates a instructions for a join statement.
|
||||
fn build_join(
|
||||
&mut self,
|
||||
id: &JoinPointId,
|
||||
parameters: &'a [Param<'a>],
|
||||
body: &'a Stmt<'a>,
|
||||
remainder: &'a Stmt<'a>,
|
||||
ret_layout: &Layout<'a>,
|
||||
) -> Result<(), String>;
|
||||
|
||||
// build_jump generates a instructions for a jump statement.
|
||||
fn build_jump(
|
||||
&mut self,
|
||||
id: &JoinPointId,
|
||||
args: &'a [Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
) -> Result<(), String>;
|
||||
|
||||
/// build_expr builds the expressions for the specified symbol.
|
||||
/// The builder must keep track of the symbol because it may be referred to later.
|
||||
fn build_expr(
|
||||
|
@ -263,8 +320,7 @@ where
|
|||
let layout_map = self.layout_map();
|
||||
for arg in *arguments {
|
||||
if let Some(layout) = layout_map.get(arg) {
|
||||
// This is safe because every value in the map is always set with a valid layout and cannot be null.
|
||||
arg_layouts.push(unsafe { *(*layout) });
|
||||
arg_layouts.push(*layout);
|
||||
} else {
|
||||
return Err(format!("the argument, {:?}, has no know layout", arg));
|
||||
}
|
||||
|
@ -507,7 +563,7 @@ where
|
|||
/// load_literal sets a symbol to be equal to a literal.
|
||||
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>;
|
||||
|
||||
/// return_symbol moves a symbol to the correct return location for the backend.
|
||||
/// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function.
|
||||
fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String>;
|
||||
|
||||
/// free_symbols will free all symbols for the given statement.
|
||||
|
@ -542,12 +598,10 @@ where
|
|||
|
||||
/// set_layout_map sets the layout for a specific symbol.
|
||||
fn set_layout_map(&mut self, sym: Symbol, layout: &Layout<'a>) -> Result<(), String> {
|
||||
if let Some(x) = self.layout_map().insert(sym, layout) {
|
||||
if let Some(old_layout) = self.layout_map().insert(sym, *layout) {
|
||||
// Layout map already contains the symbol. We should never need to overwrite.
|
||||
// If the layout is not the same, that is a bug.
|
||||
// There is always an old layout value and this dereference is safe.
|
||||
let old_layout = unsafe { *x };
|
||||
if old_layout != *layout {
|
||||
if &old_layout != layout {
|
||||
Err(format!(
|
||||
"Overwriting layout for symbol, {:?}. This should never happen. got {:?}, want {:?}",
|
||||
sym, layout, old_layout
|
||||
|
@ -561,7 +615,7 @@ where
|
|||
}
|
||||
|
||||
/// layout_map gets the map from symbol to layout.
|
||||
fn layout_map(&mut self) -> &mut MutMap<Symbol, *const Layout<'a>>;
|
||||
fn layout_map(&mut self) -> &mut MutMap<Symbol, Layout<'a>>;
|
||||
|
||||
fn create_free_map(&mut self) {
|
||||
let mut free_map = MutMap::default();
|
||||
|
|
|
@ -34,7 +34,7 @@ pub fn build_module<'a>(
|
|||
x86_64::X86_64FloatReg,
|
||||
x86_64::X86_64Assembler,
|
||||
x86_64::X86_64SystemV,
|
||||
> = Backend::new(env, target)?;
|
||||
> = Backend::new(env)?;
|
||||
build_object(
|
||||
env,
|
||||
procedures,
|
||||
|
@ -52,7 +52,7 @@ pub fn build_module<'a>(
|
|||
x86_64::X86_64FloatReg,
|
||||
x86_64::X86_64Assembler,
|
||||
x86_64::X86_64SystemV,
|
||||
> = Backend::new(env, target)?;
|
||||
> = Backend::new(env)?;
|
||||
build_object(
|
||||
env,
|
||||
procedures,
|
||||
|
@ -74,7 +74,7 @@ pub fn build_module<'a>(
|
|||
aarch64::AArch64FloatReg,
|
||||
aarch64::AArch64Assembler,
|
||||
aarch64::AArch64Call,
|
||||
> = Backend::new(env, target)?;
|
||||
> = Backend::new(env)?;
|
||||
build_object(
|
||||
env,
|
||||
procedures,
|
||||
|
@ -191,10 +191,16 @@ fn build_object<'a, B: Backend<'a>>(
|
|||
let mut layout_ids = roc_mono::layout::LayoutIds::default();
|
||||
let mut procs = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
for ((sym, layout), proc) in procedures {
|
||||
let fn_name = layout_ids
|
||||
let base_name = layout_ids
|
||||
.get_toplevel(sym, &layout)
|
||||
.to_symbol_string(sym, &env.interns);
|
||||
|
||||
let fn_name = if env.exposed_to_host.contains(&sym) {
|
||||
format!("roc_{}_exposed", base_name)
|
||||
} else {
|
||||
base_name
|
||||
};
|
||||
|
||||
let section_id = output.add_section(
|
||||
output.segment_name(StandardSegment::Text).to_vec(),
|
||||
format!(".text.{:x}", sym.as_u64()).as_bytes().to_vec(),
|
||||
|
@ -298,6 +304,7 @@ fn build_object<'a, B: Backend<'a>>(
|
|||
return Err(format!("failed to find fn symbol for {:?}", name));
|
||||
}
|
||||
}
|
||||
Relocation::JmpToReturn { .. } => unreachable!(),
|
||||
};
|
||||
relocations.push((section_id, elfreloc));
|
||||
}
|
||||
|
|
|
@ -281,6 +281,24 @@ mod dev_num {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_fast_fib_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
fib = \n, a, b ->
|
||||
if n == 0 then
|
||||
a
|
||||
else
|
||||
fib (n - 1) b (a + b)
|
||||
fib 10 0 1
|
||||
"#
|
||||
),
|
||||
55,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f64_abs() {
|
||||
assert_evals_to!("Num.abs -4.7", 4.7, f64);
|
||||
|
@ -580,18 +598,18 @@ mod dev_num {
|
|||
// 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() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
1 + 3 * 7 - 2
|
||||
"#
|
||||
),
|
||||
20,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn gen_order_of_arithmetic_ops_complex_float() {
|
||||
|
@ -606,59 +624,59 @@ mod dev_num {
|
|||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn if_guard_bind_variable_false() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// wrapper = \{} ->
|
||||
// when 10 is
|
||||
// x if x == 5 -> 0
|
||||
// _ -> 42
|
||||
#[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
|
||||
// );
|
||||
// }
|
||||
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
|
||||
#[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
|
||||
// );
|
||||
// }
|
||||
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)
|
||||
#[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
|
||||
// );
|
||||
// }
|
||||
sum 1_000_000 0
|
||||
"#
|
||||
),
|
||||
500000500000,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn int_negate() {
|
||||
|
|
|
@ -94,10 +94,12 @@ pub fn helper<'a>(
|
|||
let main_fn_layout = loaded.entry_point.layout;
|
||||
|
||||
let mut layout_ids = roc_mono::layout::LayoutIds::default();
|
||||
let main_fn_name = layout_ids
|
||||
let main_fn_name_base = layout_ids
|
||||
.get_toplevel(main_fn_symbol, &main_fn_layout)
|
||||
.to_symbol_string(main_fn_symbol, &interns);
|
||||
|
||||
let main_fn_name = format!("roc_{}_exposed", main_fn_name_base);
|
||||
|
||||
let mut lines = Vec::new();
|
||||
// errors whose reporting we delay (so we can see that code gen generates runtime errors)
|
||||
let mut delayed_errors = Vec::new();
|
||||
|
@ -143,13 +145,14 @@ pub fn helper<'a>(
|
|||
}
|
||||
|
||||
for problem in type_problems {
|
||||
let report = type_problem(&alloc, module_path.clone(), problem);
|
||||
if let Some(report) = type_problem(&alloc, module_path.clone(), problem) {
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, module_path.clone(), problem);
|
||||
|
|
|
@ -10,7 +10,7 @@ use inkwell::types::{BasicType, BasicTypeEnum};
|
|||
use inkwell::values::{BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue};
|
||||
use inkwell::AddressSpace;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Layout, LayoutIds, UnionLayout};
|
||||
use roc_mono::layout::{LambdaSet, Layout, LayoutIds, UnionLayout};
|
||||
|
||||
pub fn call_bitcode_fn<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
|
@ -189,7 +189,7 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>(
|
|||
pub fn build_transform_caller<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
function: FunctionValue<'ctx>,
|
||||
closure_data_layout: Layout<'a>,
|
||||
closure_data_layout: LambdaSet<'a>,
|
||||
argument_layouts: &[Layout<'a>],
|
||||
) -> FunctionValue<'ctx> {
|
||||
let fn_name: &str = &format!(
|
||||
|
@ -212,7 +212,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>(
|
|||
fn build_transform_caller_help<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
roc_function: FunctionValue<'ctx>,
|
||||
closure_data_layout: Layout<'a>,
|
||||
closure_data_layout: LambdaSet<'a>,
|
||||
argument_layouts: &[Layout<'a>],
|
||||
fn_name: &str,
|
||||
) -> FunctionValue<'ctx> {
|
||||
|
@ -270,7 +270,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
|
|||
arguments_cast.push(argument);
|
||||
}
|
||||
|
||||
match closure_data_layout {
|
||||
match closure_data_layout.runtime_representation() {
|
||||
Layout::Struct(&[]) => {
|
||||
// nothing to add
|
||||
}
|
||||
|
@ -529,7 +529,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
|
|||
pub fn build_compare_wrapper<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
roc_function: FunctionValue<'ctx>,
|
||||
closure_data_layout: Layout<'a>,
|
||||
closure_data_layout: LambdaSet<'a>,
|
||||
layout: &Layout<'a>,
|
||||
) -> FunctionValue<'ctx> {
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
|
@ -595,7 +595,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
|
|||
|
||||
let default = [value1, value2];
|
||||
|
||||
let arguments_cast = match closure_data_layout {
|
||||
let arguments_cast = match closure_data_layout.runtime_representation() {
|
||||
Layout::Struct(&[]) => {
|
||||
// nothing to add
|
||||
&default
|
||||
|
|
|
@ -3,15 +3,15 @@ use std::path::Path;
|
|||
|
||||
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
|
||||
use crate::llvm::build_dict::{
|
||||
dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection,
|
||||
self, dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection,
|
||||
dict_keys, dict_len, dict_remove, dict_union, dict_values, dict_walk, set_from_list,
|
||||
};
|
||||
use crate::llvm::build_hash::generic_hash;
|
||||
use crate::llvm::build_list::{
|
||||
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
|
||||
list_drop, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len,
|
||||
list_map, list_map2, list_map3, list_map_with_index, list_prepend, list_range, list_repeat,
|
||||
list_reverse, list_set, list_single, list_sort_with, list_swap,
|
||||
self, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat,
|
||||
list_contains, list_drop, list_get_unsafe, list_join, list_keep_errs, list_keep_if,
|
||||
list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index, list_prepend,
|
||||
list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with, list_swap,
|
||||
};
|
||||
use crate::llvm::build_str::{
|
||||
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int,
|
||||
|
@ -638,7 +638,7 @@ pub fn construct_optimization_passes<'a>(
|
|||
|
||||
let pmb = PassManagerBuilder::create();
|
||||
match opt_level {
|
||||
OptLevel::Normal => {
|
||||
OptLevel::Development | OptLevel::Normal => {
|
||||
pmb.set_optimization_level(OptimizationLevel::None);
|
||||
}
|
||||
OptLevel::Optimize => {
|
||||
|
@ -704,7 +704,14 @@ fn promote_to_main_function<'a, 'ctx, 'env>(
|
|||
let main_fn_name = "$Test.main";
|
||||
|
||||
// Add main to the module.
|
||||
let main_fn = expose_function_to_host_help(env, main_fn_name, roc_main_fn, main_fn_name);
|
||||
let main_fn = expose_function_to_host_help_c_abi(
|
||||
env,
|
||||
main_fn_name,
|
||||
roc_main_fn,
|
||||
&[],
|
||||
top_level.result,
|
||||
main_fn_name,
|
||||
);
|
||||
|
||||
(main_fn_name, main_fn)
|
||||
}
|
||||
|
@ -1163,8 +1170,16 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
|||
StructAtIndex {
|
||||
index, structure, ..
|
||||
} => {
|
||||
let (value, layout) = load_symbol_and_layout(scope, structure);
|
||||
|
||||
let layout = if let Layout::LambdaSet(lambda_set) = layout {
|
||||
lambda_set.runtime_representation()
|
||||
} else {
|
||||
*layout
|
||||
};
|
||||
|
||||
// extract field from a record
|
||||
match load_symbol_and_layout(scope, structure) {
|
||||
match (value, layout) {
|
||||
(StructValue(argument), Layout::Struct(fields)) => {
|
||||
debug_assert!(!fields.is_empty());
|
||||
env.builder
|
||||
|
@ -2304,32 +2319,6 @@ fn list_literal<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
|
||||
fn decrement_with_size_check<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
size: IntValue<'ctx>,
|
||||
layout: Layout<'a>,
|
||||
refcount_ptr: PointerToRefcount<'ctx>,
|
||||
) {
|
||||
let not_empty = env.context.append_basic_block(parent, "not_null");
|
||||
|
||||
let done = env.context.append_basic_block(parent, "done");
|
||||
|
||||
let is_empty =
|
||||
env.builder
|
||||
.build_int_compare(IntPredicate::EQ, size, size.get_type().const_zero(), "");
|
||||
|
||||
env.builder
|
||||
.build_conditional_branch(is_empty, done, not_empty);
|
||||
|
||||
env.builder.position_at_end(not_empty);
|
||||
|
||||
refcount_ptr.decrement(env, &layout);
|
||||
|
||||
env.builder.build_unconditional_branch(done);
|
||||
env.builder.position_at_end(done);
|
||||
}
|
||||
|
||||
pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
|
@ -2539,34 +2528,25 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
|||
let (value, layout) = load_symbol_and_layout(scope, symbol);
|
||||
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::List(_)) => {
|
||||
Layout::Builtin(Builtin::List(element_layout)) => {
|
||||
debug_assert!(value.is_struct_value());
|
||||
let alignment = element_layout.alignment_bytes(env.ptr_bytes);
|
||||
|
||||
// because of how we insert DECREF for lists, we can't guarantee that
|
||||
// the list is non-empty. When the list is empty, the pointer to the
|
||||
// elements is NULL, and trying to get to the RC address will
|
||||
// underflow, causing a segfault. Therefore, in this case we must
|
||||
// manually check that the list is non-empty
|
||||
let refcount_ptr = PointerToRefcount::from_list_wrapper(
|
||||
env,
|
||||
value.into_struct_value(),
|
||||
);
|
||||
|
||||
let length = list_len(env.builder, value.into_struct_value());
|
||||
|
||||
decrement_with_size_check(env, parent, length, *layout, refcount_ptr);
|
||||
build_list::decref(env, value.into_struct_value(), alignment);
|
||||
}
|
||||
Layout::Builtin(Builtin::Dict(_, _)) | Layout::Builtin(Builtin::Set(_)) => {
|
||||
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
|
||||
debug_assert!(value.is_struct_value());
|
||||
let alignment = key_layout
|
||||
.alignment_bytes(env.ptr_bytes)
|
||||
.max(value_layout.alignment_bytes(env.ptr_bytes));
|
||||
|
||||
let refcount_ptr = PointerToRefcount::from_list_wrapper(
|
||||
env,
|
||||
value.into_struct_value(),
|
||||
);
|
||||
build_dict::decref(env, value.into_struct_value(), alignment);
|
||||
}
|
||||
Layout::Builtin(Builtin::Set(key_layout)) => {
|
||||
debug_assert!(value.is_struct_value());
|
||||
let alignment = key_layout.alignment_bytes(env.ptr_bytes);
|
||||
|
||||
let length = dict_len(env, scope, *symbol).into_int_value();
|
||||
|
||||
decrement_with_size_check(env, parent, length, *layout, refcount_ptr);
|
||||
build_dict::decref(env, value.into_struct_value(), alignment);
|
||||
}
|
||||
|
||||
_ if layout.is_refcounted() => {
|
||||
|
@ -2635,6 +2615,18 @@ pub fn load_symbol_and_layout<'a, 'ctx, 'b>(
|
|||
None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_symbol_and_lambda_set<'a, 'ctx, 'b>(
|
||||
scope: &'b Scope<'a, 'ctx>,
|
||||
symbol: &Symbol,
|
||||
) -> (BasicValueEnum<'ctx>, LambdaSet<'a>) {
|
||||
match scope.get(symbol) {
|
||||
Some((Layout::LambdaSet(lambda_set), ptr)) => (*ptr, *lambda_set),
|
||||
Some((other, ptr)) => panic!("Not a lambda set: {:?}, {:?}", other, ptr),
|
||||
None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope),
|
||||
}
|
||||
}
|
||||
|
||||
fn access_index_struct_value<'ctx>(
|
||||
builder: &Builder<'ctx>,
|
||||
from_value: StructValue<'ctx>,
|
||||
|
@ -3088,33 +3080,42 @@ fn expose_function_to_host<'a, 'ctx, 'env>(
|
|||
env: &Env<'a, 'ctx, 'env>,
|
||||
symbol: Symbol,
|
||||
roc_function: FunctionValue<'ctx>,
|
||||
arguments: &[Layout<'a>],
|
||||
return_layout: Layout<'a>,
|
||||
) {
|
||||
// Assumption: there is only one specialization of a host-exposed function
|
||||
let ident_string = symbol.as_str(&env.interns);
|
||||
let c_function_name: String = format!("roc__{}_1_exposed", ident_string);
|
||||
|
||||
expose_function_to_host_help(env, ident_string, roc_function, &c_function_name);
|
||||
expose_function_to_host_help_c_abi(
|
||||
env,
|
||||
ident_string,
|
||||
roc_function,
|
||||
arguments,
|
||||
return_layout,
|
||||
&c_function_name,
|
||||
);
|
||||
}
|
||||
|
||||
fn expose_function_to_host_help<'a, 'ctx, 'env>(
|
||||
fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
ident_string: &str,
|
||||
roc_function: FunctionValue<'ctx>,
|
||||
arguments: &[Layout<'a>],
|
||||
c_function_name: &str,
|
||||
) -> FunctionValue<'ctx> {
|
||||
let context = env.context;
|
||||
// NOTE we ingore env.is_gen_test here
|
||||
let wrapper_return_type = roc_function.get_type().get_return_type().unwrap();
|
||||
|
||||
let wrapper_return_type = context.struct_type(
|
||||
&[
|
||||
context.i64_type().into(),
|
||||
roc_function.get_type().get_return_type().unwrap(),
|
||||
],
|
||||
false,
|
||||
);
|
||||
let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena);
|
||||
for layout in arguments {
|
||||
cc_argument_types.push(to_cc_type(env, layout));
|
||||
}
|
||||
|
||||
// STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}`
|
||||
let mut argument_types = roc_function.get_type().get_param_types();
|
||||
// let mut argument_types = roc_function.get_type().get_param_types();
|
||||
let mut argument_types = cc_argument_types;
|
||||
let return_type = wrapper_return_type;
|
||||
|
||||
let output_type = return_type.ptr_type(AddressSpace::Generic);
|
||||
argument_types.push(output_type.into());
|
||||
|
||||
|
@ -3142,43 +3143,236 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>(
|
|||
debug_info_init!(env, c_function);
|
||||
|
||||
// drop the final argument, which is the pointer we write the result into
|
||||
let args = c_function.get_params();
|
||||
let output_arg_index = args.len() - 1;
|
||||
let args = &args[..args.len() - 1];
|
||||
let args_vector = c_function.get_params();
|
||||
let mut args = args_vector.as_slice();
|
||||
let args_length = args.len();
|
||||
|
||||
args = &args[..args.len() - 1];
|
||||
|
||||
let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena);
|
||||
|
||||
let it = args.iter().zip(roc_function.get_type().get_param_types());
|
||||
for (arg, fastcc_type) in it {
|
||||
let arg_type = arg.get_type();
|
||||
if arg_type == fastcc_type {
|
||||
// the C and Fast calling conventions agree
|
||||
arguments_for_call.push(*arg);
|
||||
} else {
|
||||
let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type");
|
||||
arguments_for_call.push(cast);
|
||||
}
|
||||
}
|
||||
|
||||
let arguments_for_call = &arguments_for_call.into_bump_slice();
|
||||
|
||||
debug_assert_eq!(args.len(), roc_function.get_params().len());
|
||||
|
||||
let call_result = {
|
||||
if env.is_gen_test {
|
||||
let roc_wrapper_function = make_exception_catcher(env, roc_function);
|
||||
debug_assert_eq!(args.len(), roc_wrapper_function.get_params().len());
|
||||
debug_assert_eq!(
|
||||
arguments_for_call.len(),
|
||||
roc_wrapper_function.get_params().len()
|
||||
);
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let call_wrapped =
|
||||
builder.build_call(roc_wrapper_function, args, "call_wrapped_function");
|
||||
let call_wrapped = builder.build_call(
|
||||
roc_wrapper_function,
|
||||
arguments_for_call,
|
||||
"call_wrapped_function",
|
||||
);
|
||||
call_wrapped.set_call_convention(FAST_CALL_CONV);
|
||||
|
||||
call_wrapped.try_as_basic_value().left().unwrap()
|
||||
} else {
|
||||
let call_unwrapped = builder.build_call(roc_function, args, "call_unwrapped_function");
|
||||
let call_unwrapped =
|
||||
builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function");
|
||||
call_unwrapped.set_call_convention(FAST_CALL_CONV);
|
||||
|
||||
let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap();
|
||||
|
||||
make_good_roc_result(env, call_unwrapped_result)
|
||||
// make_good_roc_result(env, call_unwrapped_result)
|
||||
call_unwrapped_result
|
||||
}
|
||||
};
|
||||
|
||||
let output_arg_index = args_length - 1;
|
||||
|
||||
let output_arg = c_function
|
||||
.get_nth_param(output_arg_index as u32)
|
||||
.unwrap()
|
||||
.into_pointer_value();
|
||||
|
||||
builder.build_store(output_arg, call_result);
|
||||
|
||||
builder.build_return(None);
|
||||
|
||||
c_function
|
||||
}
|
||||
|
||||
fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
ident_string: &str,
|
||||
roc_function: FunctionValue<'ctx>,
|
||||
arguments: &[Layout<'a>],
|
||||
return_layout: Layout<'a>,
|
||||
c_function_name: &str,
|
||||
) -> FunctionValue<'ctx> {
|
||||
let context = env.context;
|
||||
|
||||
// a generic version that writes the result into a passed *u8 pointer
|
||||
if !env.is_gen_test {
|
||||
expose_function_to_host_help_c_abi_generic(
|
||||
env,
|
||||
roc_function,
|
||||
arguments,
|
||||
&format!("{}_generic", c_function_name),
|
||||
);
|
||||
}
|
||||
|
||||
let wrapper_return_type = if env.is_gen_test {
|
||||
context
|
||||
.struct_type(
|
||||
&[
|
||||
context.i64_type().into(),
|
||||
roc_function.get_type().get_return_type().unwrap(),
|
||||
],
|
||||
false,
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
roc_function.get_type().get_return_type().unwrap()
|
||||
};
|
||||
|
||||
let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena);
|
||||
for layout in arguments {
|
||||
cc_argument_types.push(to_cc_type(env, layout));
|
||||
}
|
||||
|
||||
// STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` if the C abi demands it
|
||||
let mut argument_types = cc_argument_types;
|
||||
let return_type = wrapper_return_type;
|
||||
|
||||
let cc_return = to_cc_return(env, &return_layout);
|
||||
|
||||
let c_function_type = match cc_return {
|
||||
CCReturn::Void if !env.is_gen_test => {
|
||||
env.context.void_type().fn_type(&argument_types, false)
|
||||
}
|
||||
CCReturn::Return if !env.is_gen_test => return_type.fn_type(&argument_types, false),
|
||||
_ => {
|
||||
let output_type = return_type.ptr_type(AddressSpace::Generic);
|
||||
argument_types.push(output_type.into());
|
||||
env.context.void_type().fn_type(&argument_types, false)
|
||||
}
|
||||
};
|
||||
|
||||
let c_function = add_func(
|
||||
env.module,
|
||||
c_function_name,
|
||||
c_function_type,
|
||||
Linkage::External,
|
||||
C_CALL_CONV,
|
||||
);
|
||||
|
||||
let subprogram = env.new_subprogram(c_function_name);
|
||||
c_function.set_subprogram(subprogram);
|
||||
|
||||
// STEP 2: build the exposed function's body
|
||||
let builder = env.builder;
|
||||
let context = env.context;
|
||||
|
||||
let entry = context.append_basic_block(c_function, "entry");
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
debug_info_init!(env, c_function);
|
||||
|
||||
// drop the final argument, which is the pointer we write the result into
|
||||
let args_vector = c_function.get_params();
|
||||
let mut args = args_vector.as_slice();
|
||||
let args_length = args.len();
|
||||
|
||||
match cc_return {
|
||||
CCReturn::Return if !env.is_gen_test => {
|
||||
debug_assert_eq!(args.len(), roc_function.get_params().len());
|
||||
}
|
||||
CCReturn::Void if !env.is_gen_test => {
|
||||
debug_assert_eq!(args.len(), roc_function.get_params().len());
|
||||
}
|
||||
_ => {
|
||||
args = &args[..args.len() - 1];
|
||||
debug_assert_eq!(args.len(), roc_function.get_params().len());
|
||||
}
|
||||
}
|
||||
|
||||
let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena);
|
||||
|
||||
let it = args.iter().zip(roc_function.get_type().get_param_types());
|
||||
for (arg, fastcc_type) in it {
|
||||
let arg_type = arg.get_type();
|
||||
if arg_type == fastcc_type {
|
||||
// the C and Fast calling conventions agree
|
||||
arguments_for_call.push(*arg);
|
||||
} else {
|
||||
let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type");
|
||||
arguments_for_call.push(cast);
|
||||
}
|
||||
}
|
||||
|
||||
let arguments_for_call = &arguments_for_call.into_bump_slice();
|
||||
|
||||
let call_result = {
|
||||
if env.is_gen_test {
|
||||
let roc_wrapper_function = make_exception_catcher(env, roc_function);
|
||||
debug_assert_eq!(
|
||||
arguments_for_call.len(),
|
||||
roc_wrapper_function.get_params().len()
|
||||
);
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let call_wrapped = builder.build_call(
|
||||
roc_wrapper_function,
|
||||
arguments_for_call,
|
||||
"call_wrapped_function",
|
||||
);
|
||||
call_wrapped.set_call_convention(FAST_CALL_CONV);
|
||||
|
||||
call_wrapped.try_as_basic_value().left().unwrap()
|
||||
} else {
|
||||
let call_unwrapped =
|
||||
builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function");
|
||||
call_unwrapped.set_call_convention(FAST_CALL_CONV);
|
||||
|
||||
let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap();
|
||||
|
||||
// make_good_roc_result(env, call_unwrapped_result)
|
||||
call_unwrapped_result
|
||||
}
|
||||
};
|
||||
|
||||
match cc_return {
|
||||
CCReturn::Void if !env.is_gen_test => {
|
||||
// TODO return empty struct here?
|
||||
builder.build_return(None);
|
||||
}
|
||||
CCReturn::Return if !env.is_gen_test => {
|
||||
builder.build_return(Some(&call_result));
|
||||
}
|
||||
_ => {
|
||||
let output_arg_index = args_length - 1;
|
||||
|
||||
let output_arg = c_function
|
||||
.get_nth_param(output_arg_index as u32)
|
||||
.unwrap()
|
||||
.into_pointer_value();
|
||||
|
||||
builder.build_store(output_arg, call_result);
|
||||
builder.build_return(None);
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 3: build a {} -> u64 function that gives the size of the return type
|
||||
let size_function_type = env.context.i64_type().fn_type(&[], false);
|
||||
let size_function_name: String = format!("roc__{}_size", ident_string);
|
||||
|
@ -3691,7 +3885,14 @@ fn build_proc_header<'a, 'ctx, 'env>(
|
|||
fn_val.set_subprogram(subprogram);
|
||||
|
||||
if env.exposed_to_host.contains(&symbol) {
|
||||
expose_function_to_host(env, symbol, fn_val);
|
||||
let arguments = Vec::from_iter_in(proc.args.iter().map(|(layout, _)| *layout), env.arena);
|
||||
expose_function_to_host(
|
||||
env,
|
||||
symbol,
|
||||
fn_val,
|
||||
arguments.into_bump_slice(),
|
||||
proc.ret_layout,
|
||||
);
|
||||
}
|
||||
|
||||
fn_val
|
||||
|
@ -3758,24 +3959,18 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
|
|||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let mut parameters = function_value.get_params();
|
||||
let output = parameters.pop().unwrap().into_pointer_value();
|
||||
let mut evaluator_arguments = function_value.get_params();
|
||||
|
||||
let closure_data = if let Some(closure_data_ptr) = parameters.pop() {
|
||||
let closure_data =
|
||||
builder.build_load(closure_data_ptr.into_pointer_value(), "load_closure_data");
|
||||
// the final parameter is the output pointer, pop it
|
||||
let output = evaluator_arguments.pop().unwrap().into_pointer_value();
|
||||
|
||||
env.arena.alloc([closure_data]) as &[_]
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
|
||||
let mut parameters = parameters;
|
||||
|
||||
for param in parameters.iter_mut() {
|
||||
debug_assert!(param.is_pointer_value());
|
||||
// NOTE this may be incorrect in the long run
|
||||
// here we load any argument that is a pointer
|
||||
for param in evaluator_arguments.iter_mut() {
|
||||
if param.is_pointer_value() {
|
||||
*param = builder.build_load(param.into_pointer_value(), "load_param");
|
||||
}
|
||||
}
|
||||
|
||||
let call_result = if env.is_gen_test {
|
||||
set_jump_and_catch_long_jump(
|
||||
|
@ -3783,13 +3978,13 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
|
|||
function_value,
|
||||
evaluator,
|
||||
evaluator.get_call_conventions(),
|
||||
closure_data,
|
||||
&evaluator_arguments,
|
||||
result_type,
|
||||
)
|
||||
} else {
|
||||
let call = env
|
||||
.builder
|
||||
.build_call(evaluator, closure_data, "call_function");
|
||||
.build_call(evaluator, &evaluator_arguments, "call_function");
|
||||
|
||||
call.set_call_convention(evaluator.get_call_conventions());
|
||||
|
||||
|
@ -4090,7 +4285,7 @@ fn roc_function_call<'a, 'ctx, 'env>(
|
|||
layout_ids: &mut LayoutIds<'a>,
|
||||
transform: FunctionValue<'ctx>,
|
||||
closure_data: BasicValueEnum<'ctx>,
|
||||
closure_data_layout: Layout<'a>,
|
||||
lambda_set: LambdaSet<'a>,
|
||||
closure_data_is_owned: bool,
|
||||
argument_layouts: &[Layout<'a>],
|
||||
) -> RocFunctionCall<'ctx> {
|
||||
|
@ -4101,12 +4296,12 @@ fn roc_function_call<'a, 'ctx, 'env>(
|
|||
.build_alloca(closure_data.get_type(), "closure_data_ptr");
|
||||
env.builder.build_store(closure_data_ptr, closure_data);
|
||||
|
||||
let stepper_caller =
|
||||
build_transform_caller(env, transform, closure_data_layout, argument_layouts)
|
||||
let stepper_caller = build_transform_caller(env, transform, lambda_set, argument_layouts)
|
||||
.as_global_value()
|
||||
.as_pointer_value();
|
||||
|
||||
let inc_closure_data = build_inc_n_wrapper(env, layout_ids, &closure_data_layout)
|
||||
let inc_closure_data =
|
||||
build_inc_n_wrapper(env, layout_ids, &lambda_set.runtime_representation())
|
||||
.as_global_value()
|
||||
.as_pointer_value();
|
||||
|
||||
|
@ -4163,7 +4358,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
let function = passed_function_at_index!(2);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_layout(scope, &args[3]);
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[3]);
|
||||
|
||||
match list_layout {
|
||||
Layout::Builtin(Builtin::EmptyList) => default,
|
||||
|
@ -4175,7 +4370,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
layout_ids,
|
||||
function,
|
||||
closure,
|
||||
*closure_layout,
|
||||
closure_layout,
|
||||
function_owns_closure_data,
|
||||
argument_layouts,
|
||||
);
|
||||
|
@ -4205,7 +4400,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
let function = passed_function_at_index!(1);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]);
|
||||
|
||||
match (list_layout, return_layout) {
|
||||
(Layout::Builtin(Builtin::EmptyList), _) => empty_list(env),
|
||||
|
@ -4220,7 +4415,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
layout_ids,
|
||||
function,
|
||||
closure,
|
||||
*closure_layout,
|
||||
closure_layout,
|
||||
function_owns_closure_data,
|
||||
argument_layouts,
|
||||
);
|
||||
|
@ -4237,7 +4432,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
let (list2, list2_layout) = load_symbol_and_layout(scope, &args[1]);
|
||||
|
||||
let function = passed_function_at_index!(2);
|
||||
let (closure, closure_layout) = load_symbol_and_layout(scope, &args[3]);
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[3]);
|
||||
|
||||
match (list1_layout, list2_layout, return_layout) {
|
||||
(
|
||||
|
@ -4252,7 +4447,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
layout_ids,
|
||||
function,
|
||||
closure,
|
||||
*closure_layout,
|
||||
closure_layout,
|
||||
function_owns_closure_data,
|
||||
argument_layouts,
|
||||
);
|
||||
|
@ -4281,7 +4476,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
let (list3, list3_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||
|
||||
let function = passed_function_at_index!(3);
|
||||
let (closure, closure_layout) = load_symbol_and_layout(scope, &args[4]);
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[4]);
|
||||
|
||||
match (list1_layout, list2_layout, list3_layout, return_layout) {
|
||||
(
|
||||
|
@ -4298,7 +4493,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
layout_ids,
|
||||
function,
|
||||
closure,
|
||||
*closure_layout,
|
||||
closure_layout,
|
||||
function_owns_closure_data,
|
||||
argument_layouts,
|
||||
);
|
||||
|
@ -4330,7 +4525,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
let function = passed_function_at_index!(1);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]);
|
||||
|
||||
match (list_layout, return_layout) {
|
||||
(Layout::Builtin(Builtin::EmptyList), _) => empty_list(env),
|
||||
|
@ -4345,7 +4540,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
layout_ids,
|
||||
function,
|
||||
closure,
|
||||
*closure_layout,
|
||||
closure_layout,
|
||||
function_owns_closure_data,
|
||||
argument_layouts,
|
||||
);
|
||||
|
@ -4363,7 +4558,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
let function = passed_function_at_index!(1);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]);
|
||||
|
||||
match list_layout {
|
||||
Layout::Builtin(Builtin::EmptyList) => empty_list(env),
|
||||
|
@ -4375,7 +4570,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
layout_ids,
|
||||
function,
|
||||
closure,
|
||||
*closure_layout,
|
||||
closure_layout,
|
||||
function_owns_closure_data,
|
||||
argument_layouts,
|
||||
);
|
||||
|
@ -4393,7 +4588,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
let function = passed_function_at_index!(1);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]);
|
||||
|
||||
match (list_layout, return_layout) {
|
||||
(_, Layout::Builtin(Builtin::EmptyList))
|
||||
|
@ -4409,7 +4604,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
layout_ids,
|
||||
function,
|
||||
closure,
|
||||
*closure_layout,
|
||||
closure_layout,
|
||||
function_owns_closure_data,
|
||||
argument_layouts,
|
||||
);
|
||||
|
@ -4437,7 +4632,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
let function = passed_function_at_index!(1);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]);
|
||||
|
||||
match (list_layout, return_layout) {
|
||||
(_, Layout::Builtin(Builtin::EmptyList))
|
||||
|
@ -4453,7 +4648,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
layout_ids,
|
||||
function,
|
||||
closure,
|
||||
*closure_layout,
|
||||
closure_layout,
|
||||
function_owns_closure_data,
|
||||
argument_layouts,
|
||||
);
|
||||
|
@ -4490,7 +4685,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
let function = passed_function_at_index!(1);
|
||||
|
||||
let (closure, closure_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]);
|
||||
|
||||
match list_layout {
|
||||
Layout::Builtin(Builtin::EmptyList) => empty_list(env),
|
||||
|
@ -4500,7 +4695,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
let argument_layouts = &[**element_layout, **element_layout];
|
||||
|
||||
let compare_wrapper =
|
||||
build_compare_wrapper(env, function, *closure_layout, element_layout)
|
||||
build_compare_wrapper(env, function, closure_layout, element_layout)
|
||||
.as_global_value()
|
||||
.as_pointer_value();
|
||||
|
||||
|
@ -4509,7 +4704,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
layout_ids,
|
||||
function,
|
||||
closure,
|
||||
*closure_layout,
|
||||
closure_layout,
|
||||
function_owns_closure_data,
|
||||
argument_layouts,
|
||||
);
|
||||
|
@ -4531,7 +4726,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
let (default, default_layout) = load_symbol_and_layout(scope, &args[1]);
|
||||
let function = passed_function_at_index!(2);
|
||||
let (closure, closure_layout) = load_symbol_and_layout(scope, &args[3]);
|
||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[3]);
|
||||
|
||||
match dict_layout {
|
||||
Layout::Builtin(Builtin::EmptyDict) => {
|
||||
|
@ -4546,7 +4741,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
layout_ids,
|
||||
function,
|
||||
closure,
|
||||
*closure_layout,
|
||||
closure_layout,
|
||||
function_owns_closure_data,
|
||||
argument_layouts,
|
||||
);
|
||||
|
@ -4832,7 +5027,7 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
Usize | Int128 | Int64 | Int32 | Int16 | Int8 => {
|
||||
build_int_unary_op(env, arg.into_int_value(), arg_builtin, op)
|
||||
}
|
||||
Float128 | Float64 | Float32 | Float16 => {
|
||||
Float128 | Float64 | Float32 => {
|
||||
build_float_unary_op(env, arg.into_float_value(), op)
|
||||
}
|
||||
_ => {
|
||||
|
@ -4928,7 +5123,7 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
"lt_or_gt",
|
||||
)
|
||||
}
|
||||
Float128 | Float64 | Float32 | Float16 => {
|
||||
Float128 | Float64 | Float32 => {
|
||||
let are_equal = env.builder.build_float_compare(
|
||||
FloatPredicate::OEQ,
|
||||
lhs_arg.into_float_value(),
|
||||
|
@ -5374,8 +5569,7 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>(
|
|||
| Builtin::Decimal
|
||||
| Builtin::Float128
|
||||
| Builtin::Float64
|
||||
| Builtin::Float32
|
||||
| Builtin::Float16 => basic_type_from_builtin(env, builtin),
|
||||
| Builtin::Float32 => basic_type_from_builtin(env, builtin),
|
||||
Builtin::Str | Builtin::EmptyStr | Builtin::List(_) | Builtin::EmptyList => {
|
||||
env.str_list_c_abi().into()
|
||||
}
|
||||
|
@ -5738,7 +5932,7 @@ pub fn build_num_binop<'a, 'ctx, 'env>(
|
|||
rhs_layout,
|
||||
op,
|
||||
),
|
||||
Float128 | Float64 | Float32 | Float16 => build_float_binop(
|
||||
Float128 | Float64 | Float32 => build_float_binop(
|
||||
env,
|
||||
parent,
|
||||
lhs_arg.into_float_value(),
|
||||
|
|
|
@ -844,3 +844,17 @@ fn dict_symbol_to_zig_dict<'a, 'ctx, 'env>(
|
|||
)
|
||||
.into_struct_value()
|
||||
}
|
||||
|
||||
pub fn decref<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
wrapper_struct: StructValue<'ctx>,
|
||||
alignment: u32,
|
||||
) {
|
||||
let pointer = env
|
||||
.builder
|
||||
.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr")
|
||||
.unwrap()
|
||||
.into_pointer_value();
|
||||
|
||||
crate::llvm::refcounting::decref_pointer_check_null(env, pointer, alignment);
|
||||
}
|
||||
|
|
|
@ -59,6 +59,15 @@ fn build_hash_layout<'a, 'ctx, 'env>(
|
|||
val.into_struct_value(),
|
||||
),
|
||||
|
||||
Layout::LambdaSet(lambda_set) => build_hash_layout(
|
||||
env,
|
||||
layout_ids,
|
||||
seed,
|
||||
val,
|
||||
&lambda_set.runtime_representation(),
|
||||
when_recursive,
|
||||
),
|
||||
|
||||
Layout::Union(union_layout) => {
|
||||
build_hash_tag(env, layout_ids, layout, union_layout, seed, val)
|
||||
}
|
||||
|
@ -123,7 +132,6 @@ fn hash_builtin<'a, 'ctx, 'env>(
|
|||
| Builtin::Float64
|
||||
| Builtin::Float32
|
||||
| Builtin::Float128
|
||||
| Builtin::Float16
|
||||
| Builtin::Decimal
|
||||
| Builtin::Usize => {
|
||||
let hash_bytes = store_and_use_as_u8_ptr(env, val, layout);
|
||||
|
|
|
@ -1136,3 +1136,17 @@ pub fn store_list<'a, 'ctx, 'env>(
|
|||
"cast_collection",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn decref<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
wrapper_struct: StructValue<'ctx>,
|
||||
alignment: u32,
|
||||
) {
|
||||
let (_, pointer) = load_list(
|
||||
env.builder,
|
||||
wrapper_struct,
|
||||
env.context.i8_type().ptr_type(AddressSpace::Generic),
|
||||
);
|
||||
|
||||
crate::llvm::refcounting::decref_pointer_check_null(env, pointer, alignment);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
|
||||
use crate::llvm::build::{complex_bitcast, struct_from_fields, Env, Scope};
|
||||
use crate::llvm::build::{complex_bitcast, Env, Scope};
|
||||
use crate::llvm::build_list::{allocate_list, call_bitcode_fn_returns_list, store_list};
|
||||
use inkwell::builder::Builder;
|
||||
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
|
||||
|
@ -268,48 +268,19 @@ fn decode_from_utf8_result<'a, 'ctx, 'env>(
|
|||
let ctx = env.context;
|
||||
|
||||
let fields = match env.ptr_bytes {
|
||||
8 => [
|
||||
8 | 4 => [
|
||||
env.ptr_int().into(),
|
||||
super::convert::zig_str_type(env).into(),
|
||||
env.context.bool_type().into(),
|
||||
ctx.i8_type().into(),
|
||||
],
|
||||
4 => [
|
||||
super::convert::zig_str_type(env).into(),
|
||||
env.ptr_int().into(),
|
||||
env.context.bool_type().into(),
|
||||
ctx.i8_type().into(),
|
||||
],
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let record_type = env.context.struct_type(&fields, false);
|
||||
|
||||
match env.ptr_bytes {
|
||||
8 => {
|
||||
let zig_struct = builder
|
||||
.build_load(pointer, "load_utf8_validate_bytes_result")
|
||||
.into_struct_value();
|
||||
|
||||
let string = builder
|
||||
.build_extract_value(zig_struct, 0, "string")
|
||||
.unwrap();
|
||||
|
||||
let byte_index = builder
|
||||
.build_extract_value(zig_struct, 1, "byte_index")
|
||||
.unwrap();
|
||||
|
||||
let is_ok = builder.build_extract_value(zig_struct, 2, "is_ok").unwrap();
|
||||
|
||||
let problem_code = builder
|
||||
.build_extract_value(zig_struct, 3, "problem_code")
|
||||
.unwrap();
|
||||
|
||||
let values = [byte_index, string, is_ok, problem_code];
|
||||
|
||||
struct_from_fields(env, record_type, values.iter().copied().enumerate())
|
||||
}
|
||||
4 => {
|
||||
8 | 4 => {
|
||||
let result_ptr_cast = env
|
||||
.builder
|
||||
.build_bitcast(
|
||||
|
|
|
@ -103,7 +103,6 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
|
|||
Builtin::Float128 => float_cmp(FloatPredicate::OEQ, "eq_f128"),
|
||||
Builtin::Float64 => float_cmp(FloatPredicate::OEQ, "eq_f64"),
|
||||
Builtin::Float32 => float_cmp(FloatPredicate::OEQ, "eq_f32"),
|
||||
Builtin::Float16 => float_cmp(FloatPredicate::OEQ, "eq_f16"),
|
||||
|
||||
Builtin::Str => str_equal(env, lhs_val, rhs_val),
|
||||
Builtin::List(elem) => build_list_eq(
|
||||
|
@ -156,6 +155,8 @@ fn build_eq<'a, 'ctx, 'env>(
|
|||
rhs_val.into_struct_value(),
|
||||
),
|
||||
|
||||
Layout::LambdaSet(_) => unreachable!("cannot compare closures"),
|
||||
|
||||
Layout::Union(union_layout) => build_tag_eq(
|
||||
env,
|
||||
layout_ids,
|
||||
|
@ -245,7 +246,6 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
|
|||
Builtin::Float128 => float_cmp(FloatPredicate::ONE, "neq_f128"),
|
||||
Builtin::Float64 => float_cmp(FloatPredicate::ONE, "neq_f64"),
|
||||
Builtin::Float32 => float_cmp(FloatPredicate::ONE, "neq_f32"),
|
||||
Builtin::Float16 => float_cmp(FloatPredicate::ONE, "neq_f16"),
|
||||
|
||||
Builtin::Str => {
|
||||
let is_equal = str_equal(env, lhs_val, rhs_val).into_int_value();
|
||||
|
@ -336,6 +336,7 @@ fn build_neq<'a, 'ctx, 'env>(
|
|||
Layout::RecursivePointer => {
|
||||
unreachable!("recursion pointers should never be compared directly")
|
||||
}
|
||||
Layout::LambdaSet(_) => unreachable!("cannot compare closure"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
|
|||
|
||||
match layout {
|
||||
Struct(sorted_fields) => basic_type_from_record(env, sorted_fields),
|
||||
LambdaSet(lambda_set) => basic_type_from_layout(env, &lambda_set.runtime_representation()),
|
||||
Union(union_layout) => {
|
||||
use UnionLayout::*;
|
||||
|
||||
|
@ -96,7 +97,6 @@ pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
|
|||
Float128 => context.f128_type().as_basic_type_enum(),
|
||||
Float64 => context.f64_type().as_basic_type_enum(),
|
||||
Float32 => context.f32_type().as_basic_type_enum(),
|
||||
Float16 => context.f16_type().as_basic_type_enum(),
|
||||
Dict(_, _) | EmptyDict => zig_dict_type(env).into(),
|
||||
Set(_) | EmptySet => zig_dict_type(env).into(),
|
||||
List(_) | EmptyList => zig_list_type(env).into(),
|
||||
|
|
|
@ -84,7 +84,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn from_list_wrapper(env: &Env<'_, 'ctx, '_>, list_wrapper: StructValue<'ctx>) -> Self {
|
||||
fn from_list_wrapper(env: &Env<'_, 'ctx, '_>, list_wrapper: StructValue<'ctx>) -> Self {
|
||||
let data_ptr = env
|
||||
.builder
|
||||
.build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr")
|
||||
|
@ -102,7 +102,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
|
|||
.build_int_compare(IntPredicate::EQ, current, one, "is_one")
|
||||
}
|
||||
|
||||
pub fn get_refcount<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
|
||||
fn get_refcount<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
|
||||
env.builder
|
||||
.build_load(self.value, "get_refcount")
|
||||
.into_int_value()
|
||||
|
@ -220,23 +220,55 @@ impl<'ctx> PointerToRefcount<'ctx> {
|
|||
|
||||
debug_info_init!(env, parent);
|
||||
|
||||
let alignment = env.context.i32_type().const_int(alignment as _, false);
|
||||
decref_pointer(
|
||||
env,
|
||||
parent.get_nth_param(0).unwrap().into_pointer_value(),
|
||||
alignment,
|
||||
);
|
||||
|
||||
builder.build_return(None);
|
||||
}
|
||||
}
|
||||
|
||||
fn decref_pointer<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
pointer: PointerValue<'ctx>,
|
||||
alignment: u32,
|
||||
) {
|
||||
let alignment = env.context.i32_type().const_int(alignment as _, false);
|
||||
call_void_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
env.builder.build_bitcast(
|
||||
parent.get_nth_param(0).unwrap(),
|
||||
pointer,
|
||||
env.ptr_int().ptr_type(AddressSpace::Generic),
|
||||
"foo",
|
||||
"to_isize_ptr",
|
||||
),
|
||||
alignment.into(),
|
||||
],
|
||||
roc_builtins::bitcode::UTILS_DECREF,
|
||||
);
|
||||
}
|
||||
|
||||
builder.build_return(None);
|
||||
}
|
||||
/// Assumes a pointer to the refcount
|
||||
pub fn decref_pointer_check_null<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
pointer: PointerValue<'ctx>,
|
||||
alignment: u32,
|
||||
) {
|
||||
let alignment = env.context.i32_type().const_int(alignment as _, false);
|
||||
call_void_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
env.builder.build_bitcast(
|
||||
pointer,
|
||||
env.context.i8_type().ptr_type(AddressSpace::Generic),
|
||||
"to_i8_ptr",
|
||||
),
|
||||
alignment.into(),
|
||||
],
|
||||
roc_builtins::bitcode::UTILS_DECREF_CHECK_NULL,
|
||||
);
|
||||
}
|
||||
|
||||
fn modify_refcount_struct<'a, 'ctx, 'env>(
|
||||
|
@ -594,6 +626,14 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
|
|||
Some(function)
|
||||
}
|
||||
},
|
||||
LambdaSet(lambda_set) => modify_refcount_layout_build_function(
|
||||
env,
|
||||
parent,
|
||||
layout_ids,
|
||||
mode,
|
||||
when_recursive,
|
||||
&lambda_set.runtime_representation(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1093,7 +1133,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
|
|||
fn_val: FunctionValue<'ctx>,
|
||||
) {
|
||||
let tags = union_layout_tags(env.arena, &union_layout);
|
||||
let is_nullable = union_layout.is_nullable();
|
||||
debug_assert!(!tags.is_empty());
|
||||
|
||||
let context = &env.context;
|
||||
|
@ -1126,7 +1165,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
|
|||
let should_recurse_block = env.context.append_basic_block(parent, "should_recurse");
|
||||
|
||||
let ctx = env.context;
|
||||
if is_nullable {
|
||||
if union_layout.is_nullable() {
|
||||
let is_null = env.builder.build_is_null(value_ptr, "is_null");
|
||||
|
||||
let then_block = ctx.append_basic_block(parent, "then");
|
||||
|
@ -1201,6 +1240,12 @@ enum DecOrReuse {
|
|||
Reuse,
|
||||
}
|
||||
|
||||
fn fields_need_no_refcounting(field_layouts: &[Layout]) -> bool {
|
||||
!field_layouts
|
||||
.iter()
|
||||
.any(|x| x.is_refcounted() || x.contains_refcounted())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
|
@ -1220,21 +1265,6 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
|||
let call_mode = mode_to_call_mode(decrement_fn, mode);
|
||||
let builder = env.builder;
|
||||
|
||||
// branches that are not/don't contain anything refcounted
|
||||
// if there is only one branch, we don't need to switch
|
||||
let switch_needed: bool = (|| {
|
||||
for field_layouts in tags.iter() {
|
||||
// if none of the fields are or contain anything refcounted, just move on
|
||||
if !field_layouts
|
||||
.iter()
|
||||
.any(|x| x.is_refcounted() || x.contains_refcounted())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
})();
|
||||
|
||||
// next, make a jump table for all possible values of the tag_id
|
||||
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
|
||||
|
||||
|
@ -1243,10 +1273,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
|||
|
||||
for (tag_id, field_layouts) in tags.iter().enumerate() {
|
||||
// if none of the fields are or contain anything refcounted, just move on
|
||||
if !field_layouts
|
||||
.iter()
|
||||
.any(|x| x.is_refcounted() || x.contains_refcounted())
|
||||
{
|
||||
if fields_need_no_refcounting(field_layouts) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1346,11 +1373,13 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
|||
|
||||
cases.reverse();
|
||||
|
||||
if cases.len() == 1 && !switch_needed {
|
||||
// there is only one tag in total; we don't need a switch
|
||||
// this is essential for nullable unwrapped layouts,
|
||||
// because the `else` branch below would try to read its
|
||||
// (nonexistant) tag id
|
||||
if matches!(
|
||||
union_layout,
|
||||
UnionLayout::NullableUnwrapped { .. } | UnionLayout::NonNullableUnwrapped { .. }
|
||||
) {
|
||||
debug_assert_eq!(cases.len(), 1);
|
||||
|
||||
// in this case, don't switch, because the `else` branch below would try to read the (nonexistant) tag id
|
||||
let (_, only_branch) = cases.pop().unwrap();
|
||||
env.builder.build_unconditional_branch(only_branch);
|
||||
} else {
|
||||
|
@ -1444,7 +1473,6 @@ fn build_reuse_rec_union_help<'a, 'ctx, 'env>(
|
|||
dec_function: FunctionValue<'ctx>,
|
||||
) {
|
||||
let tags = union_layout_tags(env.arena, &union_layout);
|
||||
let is_nullable = union_layout.is_nullable();
|
||||
|
||||
debug_assert!(!tags.is_empty());
|
||||
|
||||
|
@ -1478,7 +1506,7 @@ fn build_reuse_rec_union_help<'a, 'ctx, 'env>(
|
|||
let should_recurse_block = env.context.append_basic_block(parent, "should_recurse");
|
||||
|
||||
let ctx = env.context;
|
||||
if is_nullable {
|
||||
if union_layout.is_nullable() {
|
||||
let is_null = env.builder.build_is_null(value_ptr, "is_null");
|
||||
|
||||
let then_block = ctx.append_basic_block(parent, "then");
|
||||
|
@ -1700,68 +1728,3 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
|
|||
// this function returns void
|
||||
builder.build_return(None);
|
||||
}
|
||||
|
||||
pub fn refcount_is_one_comparison<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
refcount: IntValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
env.builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
refcount,
|
||||
refcount_1(env.context, env.ptr_bytes),
|
||||
"refcount_one_check",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn list_get_refcount_ptr<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout: &Layout<'a>,
|
||||
list_wrapper: StructValue<'ctx>,
|
||||
) -> PointerValue<'ctx> {
|
||||
// fetch the pointer to the array data, as an integer
|
||||
let ptr_as_int = env
|
||||
.builder
|
||||
.build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr")
|
||||
.unwrap()
|
||||
.into_int_value();
|
||||
|
||||
get_refcount_ptr_help(env, layout, ptr_as_int)
|
||||
}
|
||||
|
||||
pub fn refcount_offset<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> u64 {
|
||||
let value_bytes = layout.stack_size(env.ptr_bytes) as u64;
|
||||
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::List(_)) => env.ptr_bytes as u64,
|
||||
Layout::Builtin(Builtin::Str) => env.ptr_bytes as u64,
|
||||
Layout::RecursivePointer | Layout::Union(_) => env.ptr_bytes as u64,
|
||||
_ => (env.ptr_bytes as u64).max(value_bytes),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_refcount_ptr_help<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout: &Layout<'a>,
|
||||
ptr_as_int: IntValue<'ctx>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
|
||||
let offset = refcount_offset(env, layout);
|
||||
|
||||
// pointer to usize
|
||||
let refcount_type = ptr_int(ctx, env.ptr_bytes);
|
||||
|
||||
// subtract offset, to access the refcount
|
||||
let refcount_ptr = builder.build_int_sub(
|
||||
ptr_as_int,
|
||||
refcount_type.const_int(offset, false),
|
||||
"make_refcount_ptr",
|
||||
);
|
||||
|
||||
builder.build_int_to_ptr(
|
||||
refcount_ptr,
|
||||
refcount_type.ptr_type(AddressSpace::Generic),
|
||||
"get_refcount_ptr",
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
- 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.
|
||||
- Improve the fundamentals
|
||||
- [x] Come up with a way to do control flow
|
||||
- [x] Flesh out the details of value representations between local variables and stack memory
|
||||
- [ ] Set up a way to write tests with any return value rather than just i64 and f64
|
||||
- [ ] Figure out relocations for linking object files
|
||||
- [ ] Think about the Wasm module builder library we're using, are we happy with it?
|
||||
- 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.
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
use parity_wasm::builder;
|
||||
use parity_wasm::builder::{CodeLocation, ModuleBuilder};
|
||||
use parity_wasm::elements::{Instruction, Instruction::*, Instructions, Local, ValueType};
|
||||
use parity_wasm::elements::{
|
||||
BlockType, 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};
|
||||
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
|
||||
use roc_mono::layout::{Builtin, Layout, UnionLayout};
|
||||
|
||||
use crate::*;
|
||||
|
||||
// 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)
|
||||
|
@ -21,24 +25,126 @@ struct LabelId(u32);
|
|||
#[derive(Debug)]
|
||||
struct SymbolStorage(LocalId, WasmLayout);
|
||||
|
||||
// See README for background information on Wasm locals, memory and function calls
|
||||
#[derive(Debug)]
|
||||
struct WasmLayout {
|
||||
value_type: ValueType,
|
||||
stack_memory: u32,
|
||||
pub enum WasmLayout {
|
||||
// Most number types can fit in a Wasm local without any stack memory.
|
||||
// Roc i8 is represented as an i32 local. Store the type and the original size.
|
||||
LocalOnly(ValueType, u32),
|
||||
|
||||
// A `local` pointing to stack memory
|
||||
StackMemory(u32),
|
||||
|
||||
// A `local` pointing to heap memory
|
||||
HeapMemory,
|
||||
}
|
||||
|
||||
impl WasmLayout {
|
||||
fn new(layout: &Layout) -> Result<Self, String> {
|
||||
fn new(layout: &Layout) -> Self {
|
||||
use ValueType::*;
|
||||
let size = layout.stack_size(PTR_SIZE);
|
||||
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)),
|
||||
Layout::Builtin(Builtin::Int128) => Self::StackMemory(size),
|
||||
Layout::Builtin(Builtin::Int64) => Self::LocalOnly(I64, size),
|
||||
Layout::Builtin(Builtin::Int32) => Self::LocalOnly(I32, size),
|
||||
Layout::Builtin(Builtin::Int16) => Self::LocalOnly(I32, size),
|
||||
Layout::Builtin(Builtin::Int8) => Self::LocalOnly(I32, size),
|
||||
Layout::Builtin(Builtin::Int1) => Self::LocalOnly(I32, size),
|
||||
Layout::Builtin(Builtin::Usize) => Self::LocalOnly(I32, size),
|
||||
Layout::Builtin(Builtin::Decimal) => Self::StackMemory(size),
|
||||
Layout::Builtin(Builtin::Float128) => Self::StackMemory(size),
|
||||
Layout::Builtin(Builtin::Float64) => Self::LocalOnly(F64, size),
|
||||
Layout::Builtin(Builtin::Float32) => Self::LocalOnly(F32, size),
|
||||
Layout::Builtin(Builtin::Str) => Self::StackMemory(size),
|
||||
Layout::Builtin(Builtin::Dict(_, _)) => Self::StackMemory(size),
|
||||
Layout::Builtin(Builtin::Set(_)) => Self::StackMemory(size),
|
||||
Layout::Builtin(Builtin::List(_)) => Self::StackMemory(size),
|
||||
Layout::Builtin(Builtin::EmptyStr) => Self::StackMemory(size),
|
||||
Layout::Builtin(Builtin::EmptyList) => Self::StackMemory(size),
|
||||
Layout::Builtin(Builtin::EmptyDict) => Self::StackMemory(size),
|
||||
Layout::Builtin(Builtin::EmptySet) => Self::StackMemory(size),
|
||||
Layout::LambdaSet(lambda_set) => WasmLayout::new(&lambda_set.runtime_representation()),
|
||||
Layout::Struct(_) => Self::StackMemory(size),
|
||||
Layout::Union(UnionLayout::NonRecursive(_)) => Self::StackMemory(size),
|
||||
Layout::Union(UnionLayout::Recursive(_)) => Self::HeapMemory,
|
||||
Layout::Union(UnionLayout::NonNullableUnwrapped(_)) => Self::HeapMemory,
|
||||
Layout::Union(UnionLayout::NullableWrapped { .. }) => Self::HeapMemory,
|
||||
Layout::Union(UnionLayout::NullableUnwrapped { .. }) => Self::HeapMemory,
|
||||
Layout::RecursivePointer => Self::HeapMemory,
|
||||
}
|
||||
}
|
||||
|
||||
fn value_type(&self) -> ValueType {
|
||||
match self {
|
||||
Self::LocalOnly(type_, _) => *type_,
|
||||
_ => PTR_TYPE,
|
||||
}
|
||||
}
|
||||
|
||||
fn stack_memory(&self) -> u32 {
|
||||
match self {
|
||||
Self::StackMemory(size) => *size,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn load(&self, offset: u32) -> Result<Instruction, String> {
|
||||
use crate::backend::WasmLayout::*;
|
||||
use ValueType::*;
|
||||
|
||||
match self {
|
||||
LocalOnly(I32, 4) => Ok(I32Load(ALIGN_4, offset)),
|
||||
LocalOnly(I32, 2) => Ok(I32Load16S(ALIGN_2, offset)),
|
||||
LocalOnly(I32, 1) => Ok(I32Load8S(ALIGN_1, offset)),
|
||||
LocalOnly(I64, 8) => Ok(I64Load(ALIGN_8, offset)),
|
||||
LocalOnly(F64, 8) => Ok(F64Load(ALIGN_8, offset)),
|
||||
LocalOnly(F32, 4) => Ok(F32Load(ALIGN_4, offset)),
|
||||
|
||||
// LocalOnly(F32, 2) => Ok(), // convert F16 to F32 (lowlevel function? Wasm-only?)
|
||||
// StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR?
|
||||
HeapMemory => {
|
||||
if PTR_TYPE == I64 {
|
||||
Ok(I64Load(ALIGN_8, offset))
|
||||
} else {
|
||||
Ok(I32Load(ALIGN_4, offset))
|
||||
}
|
||||
}
|
||||
|
||||
_ => Err(format!(
|
||||
"Failed to generate load instruction for WasmLayout {:?}",
|
||||
self
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn store(&self, offset: u32) -> Result<Instruction, String> {
|
||||
use crate::backend::WasmLayout::*;
|
||||
use ValueType::*;
|
||||
|
||||
match self {
|
||||
LocalOnly(I32, 4) => Ok(I32Store(ALIGN_4, offset)),
|
||||
LocalOnly(I32, 2) => Ok(I32Store16(ALIGN_2, offset)),
|
||||
LocalOnly(I32, 1) => Ok(I32Store8(ALIGN_1, offset)),
|
||||
LocalOnly(I64, 8) => Ok(I64Store(ALIGN_8, offset)),
|
||||
LocalOnly(F64, 8) => Ok(F64Store(ALIGN_8, offset)),
|
||||
LocalOnly(F32, 4) => Ok(F32Store(ALIGN_4, offset)),
|
||||
|
||||
// LocalOnly(F32, 2) => Ok(), // convert F32 to F16 (lowlevel function? Wasm-only?)
|
||||
// StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR?
|
||||
HeapMemory => {
|
||||
if PTR_TYPE == I64 {
|
||||
Ok(I64Store(ALIGN_8, offset))
|
||||
} else {
|
||||
Ok(I32Store(ALIGN_4, offset))
|
||||
}
|
||||
}
|
||||
|
||||
_ => Err(format!(
|
||||
"Failed to generate store instruction for WasmLayout {:?}",
|
||||
self
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +167,9 @@ pub struct WasmBackend<'a> {
|
|||
// Functions: internal state & IR mappings
|
||||
stack_memory: u32,
|
||||
symbol_storage_map: MutMap<Symbol, SymbolStorage>,
|
||||
// joinpoint_label_map: MutMap<JoinPointId, LabelId>,
|
||||
/// how many blocks deep are we (used for jumps)
|
||||
block_depth: u32,
|
||||
joinpoint_label_map: MutMap<JoinPointId, (u32, std::vec::Vec<LocalId>)>,
|
||||
}
|
||||
|
||||
impl<'a> WasmBackend<'a> {
|
||||
|
@ -84,7 +192,8 @@ impl<'a> WasmBackend<'a> {
|
|||
// Functions: internal state & IR mappings
|
||||
stack_memory: 0,
|
||||
symbol_storage_map: MutMap::default(),
|
||||
// joinpoint_label_map: MutMap::default(),
|
||||
block_depth: 0,
|
||||
joinpoint_label_map: MutMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,21 +210,21 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
|
||||
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
|
||||
let ret_layout = WasmLayout::new(&proc.ret_layout);
|
||||
|
||||
if let WasmLayout::StackMemory { .. } = ret_layout {
|
||||
return Err(format!(
|
||||
"Not yet implemented: Return in stack memory for non-primtitive layouts like {:?}",
|
||||
proc.ret_layout
|
||||
"Not yet implemented: Returning values to callee stack memory {:?} {:?}",
|
||||
proc.name, sym
|
||||
));
|
||||
}
|
||||
|
||||
self.ret_type = ret_layout.value_type;
|
||||
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);
|
||||
let wasm_layout = WasmLayout::new(layout);
|
||||
self.arg_types.push(wasm_layout.value_type());
|
||||
self.insert_local(wasm_layout, *symbol);
|
||||
}
|
||||
|
||||
|
@ -147,10 +256,10 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
|
||||
fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol) -> LocalId {
|
||||
self.stack_memory += layout.stack_memory;
|
||||
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));
|
||||
self.locals.push(Local::new(1, layout.value_type()));
|
||||
}
|
||||
let local_id = LocalId(index as u32);
|
||||
let storage = SymbolStorage(local_id, layout);
|
||||
|
@ -174,6 +283,27 @@ impl<'a> WasmBackend<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// start a loop that leaves a value on the stack
|
||||
fn start_loop_with_return(&mut self, value_type: ValueType) {
|
||||
self.block_depth += 1;
|
||||
|
||||
// self.instructions.push(Loop(BlockType::NoResult));
|
||||
self.instructions.push(Loop(BlockType::Value(value_type)));
|
||||
}
|
||||
|
||||
fn start_block(&mut self) {
|
||||
self.block_depth += 1;
|
||||
|
||||
// Our blocks always end with a `return` or `br`,
|
||||
// so they never leave extra values on the stack
|
||||
self.instructions.push(Block(BlockType::NoResult));
|
||||
}
|
||||
|
||||
fn end_block(&mut self) {
|
||||
self.block_depth -= 1;
|
||||
self.instructions.push(End);
|
||||
}
|
||||
|
||||
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.
|
||||
|
@ -185,7 +315,7 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
|
||||
Stmt::Let(sym, expr, layout, following) => {
|
||||
let wasm_layout = WasmLayout::new(layout)?;
|
||||
let wasm_layout = WasmLayout::new(layout);
|
||||
let local_id = self.insert_local(wasm_layout, *sym);
|
||||
|
||||
self.build_expr(sym, expr, layout)?;
|
||||
|
@ -207,6 +337,113 @@ impl<'a> WasmBackend<'a> {
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
Stmt::Switch {
|
||||
cond_symbol,
|
||||
cond_layout: _,
|
||||
branches,
|
||||
default_branch,
|
||||
ret_layout: _,
|
||||
} => {
|
||||
// NOTE currently implemented as a series of conditional jumps
|
||||
// We may be able to improve this in the future with `Select`
|
||||
// or `BrTable`
|
||||
|
||||
// create (number_of_branches - 1) new blocks.
|
||||
for _ in 0..branches.len() {
|
||||
self.start_block()
|
||||
}
|
||||
|
||||
// the LocalId of the symbol that we match on
|
||||
let matched_on = match self.symbol_storage_map.get(cond_symbol) {
|
||||
Some(SymbolStorage(local_id, _)) => local_id.0,
|
||||
None => unreachable!("symbol not defined: {:?}", cond_symbol),
|
||||
};
|
||||
|
||||
// then, we jump whenever the value under scrutiny is equal to the value of a branch
|
||||
for (i, (value, _, _)) in branches.iter().enumerate() {
|
||||
// put the cond_symbol on the top of the stack
|
||||
self.instructions.push(GetLocal(matched_on));
|
||||
|
||||
self.instructions.push(I32Const(*value as i32));
|
||||
|
||||
// compare the 2 topmost values
|
||||
self.instructions.push(I32Eq);
|
||||
|
||||
// "break" out of `i` surrounding blocks
|
||||
self.instructions.push(BrIf(i as u32));
|
||||
}
|
||||
|
||||
// if we never jumped because a value matched, we're in the default case
|
||||
self.build_stmt(default_branch.1, ret_layout)?;
|
||||
|
||||
// now put in the actual body of each branch in order
|
||||
// (the first branch would have broken out of 1 block,
|
||||
// hence we must generate its code first)
|
||||
for (_, _, branch) in branches.iter() {
|
||||
self.end_block();
|
||||
|
||||
self.build_stmt(branch, ret_layout)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Stmt::Join {
|
||||
id,
|
||||
parameters,
|
||||
body,
|
||||
remainder,
|
||||
} => {
|
||||
// make locals for join pointer parameters
|
||||
let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len());
|
||||
for parameter in parameters.iter() {
|
||||
let wasm_layout = WasmLayout::new(¶meter.layout);
|
||||
let local_id = self.insert_local(wasm_layout, parameter.symbol);
|
||||
|
||||
jp_parameter_local_ids.push(local_id);
|
||||
}
|
||||
|
||||
self.start_block();
|
||||
|
||||
self.joinpoint_label_map
|
||||
.insert(*id, (self.block_depth, jp_parameter_local_ids));
|
||||
|
||||
self.build_stmt(remainder, ret_layout)?;
|
||||
|
||||
self.end_block();
|
||||
|
||||
// A `return` inside of a `loop` seems to make it so that the `loop` itself
|
||||
// also "returns" (so, leaves on the stack) a value of the return type.
|
||||
let return_wasm_layout = WasmLayout::new(ret_layout);
|
||||
self.start_loop_with_return(return_wasm_layout.value_type());
|
||||
|
||||
self.build_stmt(body, ret_layout)?;
|
||||
|
||||
// ends the loop
|
||||
self.end_block();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Stmt::Jump(id, arguments) => {
|
||||
let (target, locals) = &self.joinpoint_label_map[id];
|
||||
|
||||
// put the arguments on the stack
|
||||
for (symbol, local_id) in arguments.iter().zip(locals.iter()) {
|
||||
let argument = match self.symbol_storage_map.get(symbol) {
|
||||
Some(SymbolStorage(local_id, _)) => local_id.0,
|
||||
None => unreachable!("symbol not defined: {:?}", symbol),
|
||||
};
|
||||
|
||||
self.instructions.push(GetLocal(argument));
|
||||
self.instructions.push(SetLocal(local_id.0));
|
||||
}
|
||||
|
||||
// jump
|
||||
let levels = self.block_depth - target;
|
||||
self.instructions.push(Br(levels));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
x => Err(format!("statement not yet implemented: {:?}", x)),
|
||||
}
|
||||
}
|
||||
|
@ -218,7 +455,7 @@ impl<'a> WasmBackend<'a> {
|
|||
layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
match expr {
|
||||
Expr::Literal(lit) => self.load_literal(lit),
|
||||
Expr::Literal(lit) => self.load_literal(lit, layout),
|
||||
|
||||
Expr::Call(roc_mono::ir::Call {
|
||||
call_type,
|
||||
|
@ -246,15 +483,38 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_literal(&mut self, lit: &Literal<'a>) -> Result<(), String> {
|
||||
fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> {
|
||||
match lit {
|
||||
Literal::Bool(x) => {
|
||||
self.instructions.push(I32Const(*x as i32));
|
||||
Ok(())
|
||||
}
|
||||
Literal::Byte(x) => {
|
||||
self.instructions.push(I32Const(*x as i32));
|
||||
Ok(())
|
||||
}
|
||||
Literal::Int(x) => {
|
||||
self.instructions.push(I64Const(*x as i64));
|
||||
let instruction = match layout {
|
||||
Layout::Builtin(Builtin::Int64) => I64Const(*x as i64),
|
||||
Layout::Builtin(
|
||||
Builtin::Int32
|
||||
| Builtin::Int16
|
||||
| Builtin::Int8
|
||||
| Builtin::Int1
|
||||
| Builtin::Usize,
|
||||
) => I32Const(*x as i32),
|
||||
x => panic!("loading literal, {:?}, is not yet implemented", x),
|
||||
};
|
||||
self.instructions.push(instruction);
|
||||
Ok(())
|
||||
}
|
||||
Literal::Float(x) => {
|
||||
let val: f64 = *x;
|
||||
self.instructions.push(F64Const(val.to_bits()));
|
||||
let instruction = match layout {
|
||||
Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()),
|
||||
Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()),
|
||||
x => panic!("loading literal, {:?}, is not yet implemented", x),
|
||||
};
|
||||
self.instructions.push(instruction);
|
||||
Ok(())
|
||||
}
|
||||
x => Err(format!("loading literal, {:?}, is not yet implemented", x)),
|
||||
|
@ -270,8 +530,8 @@ impl<'a> WasmBackend<'a> {
|
|||
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)?;
|
||||
let wasm_layout = WasmLayout::new(return_layout);
|
||||
self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -293,6 +553,22 @@ impl<'a> WasmBackend<'a> {
|
|||
ValueType::F32 => &[F32Add],
|
||||
ValueType::F64 => &[F64Add],
|
||||
},
|
||||
LowLevel::NumSub => match return_value_type {
|
||||
ValueType::I32 => &[I32Sub],
|
||||
ValueType::I64 => &[I64Sub],
|
||||
ValueType::F32 => &[F32Sub],
|
||||
ValueType::F64 => &[F64Sub],
|
||||
},
|
||||
LowLevel::NumMul => match return_value_type {
|
||||
ValueType::I32 => &[I32Mul],
|
||||
ValueType::I64 => &[I64Mul],
|
||||
ValueType::F32 => &[F32Mul],
|
||||
ValueType::F64 => &[F64Mul],
|
||||
},
|
||||
LowLevel::NumGt => {
|
||||
// needs layout of the argument to be implemented fully
|
||||
&[I32GtS]
|
||||
}
|
||||
_ => {
|
||||
return Err(format!("unsupported low-level op {:?}", lowlevel));
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ pub mod from_wasm32_memory;
|
|||
|
||||
use bumpalo::Bump;
|
||||
use parity_wasm::builder;
|
||||
use parity_wasm::elements::Internal;
|
||||
use parity_wasm::elements::{Instruction, Internal, ValueType};
|
||||
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
|
@ -12,6 +12,17 @@ use roc_mono::layout::LayoutIds;
|
|||
|
||||
use crate::backend::WasmBackend;
|
||||
|
||||
const PTR_SIZE: u32 = 4;
|
||||
const PTR_TYPE: ValueType = ValueType::I32;
|
||||
|
||||
// All usages of these alignment constants take u32, so an enum wouldn't add any safety.
|
||||
pub const ALIGN_1: u32 = 0;
|
||||
pub const ALIGN_2: u32 = 1;
|
||||
pub const ALIGN_4: u32 = 2;
|
||||
pub const ALIGN_8: u32 = 3;
|
||||
|
||||
pub const STACK_POINTER_GLOBAL_ID: u32 = 0;
|
||||
|
||||
pub struct Env<'a> {
|
||||
pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot
|
||||
pub interns: Interns,
|
||||
|
@ -21,12 +32,37 @@ pub struct Env<'a> {
|
|||
pub fn build_module<'a>(
|
||||
env: &'a Env,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Result<std::vec::Vec<u8>, String> {
|
||||
) -> Result<Vec<u8>, String> {
|
||||
let (builder, _) = build_module_help(env, procedures)?;
|
||||
let module = builder.build();
|
||||
module
|
||||
.to_bytes()
|
||||
.map_err(|e| -> String { format!("Error serialising Wasm module {:?}", e) })
|
||||
}
|
||||
|
||||
pub fn build_module_help<'a>(
|
||||
env: &'a Env,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Result<(builder::ModuleBuilder, u32), String> {
|
||||
let mut backend = WasmBackend::new();
|
||||
let mut layout_ids = LayoutIds::default();
|
||||
|
||||
// Sort procedures by occurrence order
|
||||
//
|
||||
// We sort by the "name", but those are interned strings, and the name that is
|
||||
// interned first will have a lower number.
|
||||
//
|
||||
// But, the name that occurs first is always `main` because it is in the (implicit)
|
||||
// file header. Therefore sorting high to low will put other functions before main
|
||||
//
|
||||
// This means that for now other functions in the file have to be ordered "in reverse": if A
|
||||
// uses B, then the name of A must first occur after the first occurrence of the name of B
|
||||
let mut procedures: std::vec::Vec<_> = procedures.into_iter().collect();
|
||||
procedures.sort_by(|a, b| b.0 .0.cmp(&a.0 .0));
|
||||
|
||||
let mut function_index: u32 = 0;
|
||||
for ((sym, layout), proc) in procedures {
|
||||
let function_index = backend.build_proc(proc, sym)?;
|
||||
function_index = backend.build_proc(proc, sym)?;
|
||||
if env.exposed_to_host.contains(&sym) {
|
||||
let fn_name = layout_ids
|
||||
.get_toplevel(sym, &layout)
|
||||
|
@ -41,6 +77,11 @@ pub fn build_module<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// Because of the sorting above, we know the last function in the `for` is the main function.
|
||||
// Here we grab its index and return it, so that the test_wrapper is able to call it.
|
||||
// This is a workaround until we implement object files with symbols and relocations.
|
||||
let main_function_index = function_index;
|
||||
|
||||
const MIN_MEMORY_SIZE_KB: u32 = 1024;
|
||||
const PAGE_SIZE_KB: u32 = 64;
|
||||
|
||||
|
@ -48,15 +89,18 @@ pub fn build_module<'a>(
|
|||
.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) })
|
||||
let stack_pointer_global = builder::global()
|
||||
.with_type(PTR_TYPE)
|
||||
.mutable()
|
||||
.init_expr(Instruction::I32Const((MIN_MEMORY_SIZE_KB * 1024) as i32))
|
||||
.build();
|
||||
backend.builder.push_global(stack_pointer_global);
|
||||
|
||||
Ok((backend.builder, main_function_index))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
use roc_can::builtins::builtin_defs_map;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
// use roc_std::{RocDec, RocList, RocOrder, RocStr};
|
||||
use crate::helpers::wasm32_test_result::Wasm32TestResult;
|
||||
use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory;
|
||||
|
||||
const TEST_WRAPPER_NAME: &str = "test_wrapper";
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
|
||||
|
@ -16,12 +20,11 @@ fn promote_expr_to_module(src: &str) -> String {
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn helper_wasm<'a>(
|
||||
pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
src: &str,
|
||||
stdlib: &'a roc_builtins::std::StdLib,
|
||||
_is_gen_test: bool,
|
||||
_ignore_problems: bool,
|
||||
_result_type_dummy: &T,
|
||||
) -> wasmer::Instance {
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
@ -91,7 +94,11 @@ pub fn helper_wasm<'a>(
|
|||
exposed_to_host,
|
||||
};
|
||||
|
||||
let module_bytes = roc_gen_wasm::build_module(&env, procedures).unwrap();
|
||||
let (mut builder, main_function_index) =
|
||||
roc_gen_wasm::build_module_help(&env, procedures).unwrap();
|
||||
T::insert_test_wrapper(&mut builder, TEST_WRAPPER_NAME, main_function_index);
|
||||
|
||||
let module_bytes = builder.build().to_bytes().unwrap();
|
||||
|
||||
// for debugging (e.g. with wasm2wat)
|
||||
if false {
|
||||
|
@ -128,44 +135,40 @@ pub fn helper_wasm<'a>(
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn assert_wasm_evals_to_help<T>(src: &str, ignore_problems: bool) -> Result<T, String>
|
||||
pub fn assert_wasm_evals_to_help<T>(src: &str, expected: T) -> Result<T, String>
|
||||
where
|
||||
T: Copy,
|
||||
T: FromWasm32Memory + Wasm32TestResult,
|
||||
{
|
||||
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 instance = crate::helpers::eval::helper_wasm(&arena, src, stdlib, &expected);
|
||||
|
||||
let main_function = instance.exports.get_function("#UserApp_main_1").unwrap();
|
||||
let memory = instance.exports.get_memory("memory").unwrap();
|
||||
|
||||
match main_function.call(&[]) {
|
||||
let test_wrapper = instance.exports.get_function(TEST_WRAPPER_NAME).unwrap();
|
||||
|
||||
match test_wrapper.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,
|
||||
let address = match result[0] {
|
||||
wasmer::Value::I32(a) => a,
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
let output_ptr: &T;
|
||||
unsafe {
|
||||
output_ptr = std::mem::transmute::<&i64, &T>(&integer);
|
||||
}
|
||||
let output = <T as FromWasm32Memory>::decode(memory, address as u32);
|
||||
|
||||
Ok(*output_ptr)
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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) {
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||
match $crate::helpers::eval::assert_wasm_evals_to_help::<$ty>($src, $expected) {
|
||||
Err(msg) => println!("{:?}", msg),
|
||||
Ok(actual) => {
|
||||
#[allow(clippy::bool_assert_comparison)]
|
||||
|
@ -175,11 +178,11 @@ macro_rules! assert_wasm_evals_to {
|
|||
};
|
||||
|
||||
($src:expr, $expected:expr, $ty:ty) => {
|
||||
$crate::assert_wasm_evals_to!($src, $expected, $ty, $crate::helpers::eval::identity, false);
|
||||
$crate::assert_wasm_evals_to!($src, $expected, $ty, $crate::helpers::eval::identity);
|
||||
};
|
||||
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||
$crate::assert_wasm_evals_to!($src, $expected, $ty, $transform, false);
|
||||
$crate::assert_wasm_evals_to!($src, $expected, $ty, $transform);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -191,7 +194,7 @@ macro_rules! assert_evals_to {
|
|||
($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);
|
||||
$crate::assert_wasm_evals_to!($src, $expected, $ty, $transform);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ extern crate bumpalo;
|
|||
|
||||
#[macro_use]
|
||||
pub mod eval;
|
||||
pub mod wasm32_test_result;
|
||||
|
||||
/// 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)
|
||||
|
|
165
compiler/gen_wasm/tests/helpers/wasm32_test_result.rs
Normal file
165
compiler/gen_wasm/tests/helpers/wasm32_test_result.rs
Normal file
|
@ -0,0 +1,165 @@
|
|||
use parity_wasm::builder;
|
||||
use parity_wasm::builder::ModuleBuilder;
|
||||
use parity_wasm::elements::{Instruction, Instruction::*, Instructions, Internal, ValueType};
|
||||
|
||||
use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory;
|
||||
use roc_gen_wasm::*;
|
||||
use roc_std::{RocDec, RocList, RocOrder, RocStr};
|
||||
|
||||
pub trait Wasm32TestResult {
|
||||
fn insert_test_wrapper(
|
||||
module_builder: &mut ModuleBuilder,
|
||||
wrapper_name: &str,
|
||||
main_function_index: u32,
|
||||
) {
|
||||
let instructions = Self::build_wrapper_body(main_function_index);
|
||||
|
||||
let signature = builder::signature().with_result(ValueType::I32).build_sig();
|
||||
|
||||
let function_def = builder::function()
|
||||
.with_signature(signature)
|
||||
.body()
|
||||
.with_instructions(Instructions::new(instructions))
|
||||
.build() // body
|
||||
.build(); // function
|
||||
|
||||
let location = module_builder.push_function(function_def);
|
||||
let export = builder::export()
|
||||
.field(wrapper_name)
|
||||
.with_internal(Internal::Function(location.body))
|
||||
.build();
|
||||
|
||||
module_builder.push_export(export);
|
||||
}
|
||||
|
||||
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction>;
|
||||
}
|
||||
|
||||
fn build_wrapper_body_prelude(stack_memory_size: usize) -> Vec<Instruction> {
|
||||
vec![
|
||||
GetGlobal(STACK_POINTER_GLOBAL_ID),
|
||||
I32Const(stack_memory_size as i32),
|
||||
I32Sub,
|
||||
SetGlobal(STACK_POINTER_GLOBAL_ID),
|
||||
]
|
||||
}
|
||||
|
||||
macro_rules! build_wrapper_body_primitive {
|
||||
($store_instruction: expr, $align: expr) => {
|
||||
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
|
||||
const MAX_ALIGNED_SIZE: usize = 16;
|
||||
let mut instructions = build_wrapper_body_prelude(MAX_ALIGNED_SIZE);
|
||||
instructions.extend([
|
||||
GetGlobal(STACK_POINTER_GLOBAL_ID),
|
||||
//
|
||||
// Call the main function with no arguments. Get primitive back.
|
||||
Call(main_function_index),
|
||||
//
|
||||
// Store the primitive at the allocated address
|
||||
$store_instruction($align, 0),
|
||||
//
|
||||
// Return the result pointer
|
||||
GetGlobal(STACK_POINTER_GLOBAL_ID),
|
||||
End,
|
||||
]);
|
||||
instructions
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! wasm_test_result_primitive {
|
||||
($type_name: ident, $store_instruction: expr, $align: expr) => {
|
||||
impl Wasm32TestResult for $type_name {
|
||||
build_wrapper_body_primitive!($store_instruction, $align);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec<Instruction> {
|
||||
let mut instructions = build_wrapper_body_prelude(size);
|
||||
instructions.extend([
|
||||
//
|
||||
// Call the main function with the allocated address to write the result.
|
||||
// No value is returned to the VM stack. This is the same as in compiled C.
|
||||
GetGlobal(STACK_POINTER_GLOBAL_ID),
|
||||
Call(main_function_index),
|
||||
//
|
||||
// Return the result address
|
||||
GetGlobal(STACK_POINTER_GLOBAL_ID),
|
||||
End,
|
||||
]);
|
||||
instructions
|
||||
}
|
||||
|
||||
macro_rules! wasm_test_result_stack_memory {
|
||||
($type_name: ident) => {
|
||||
impl Wasm32TestResult for $type_name {
|
||||
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
|
||||
build_wrapper_body_stack_memory(main_function_index, $type_name::ACTUAL_WIDTH)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
wasm_test_result_primitive!(bool, I32Store8, ALIGN_1);
|
||||
wasm_test_result_primitive!(RocOrder, I32Store8, ALIGN_1);
|
||||
|
||||
wasm_test_result_primitive!(u8, I32Store8, ALIGN_1);
|
||||
wasm_test_result_primitive!(i8, I32Store8, ALIGN_1);
|
||||
wasm_test_result_primitive!(u16, I32Store16, ALIGN_2);
|
||||
wasm_test_result_primitive!(i16, I32Store16, ALIGN_2);
|
||||
wasm_test_result_primitive!(u32, I32Store, ALIGN_4);
|
||||
wasm_test_result_primitive!(i32, I32Store, ALIGN_4);
|
||||
wasm_test_result_primitive!(u64, I64Store, ALIGN_8);
|
||||
wasm_test_result_primitive!(i64, I64Store, ALIGN_8);
|
||||
|
||||
wasm_test_result_primitive!(f32, F32Store, ALIGN_8);
|
||||
wasm_test_result_primitive!(f64, F64Store, ALIGN_8);
|
||||
|
||||
wasm_test_result_stack_memory!(u128);
|
||||
wasm_test_result_stack_memory!(i128);
|
||||
wasm_test_result_stack_memory!(RocDec);
|
||||
wasm_test_result_stack_memory!(RocStr);
|
||||
|
||||
impl<T: Wasm32TestResult> Wasm32TestResult for RocList<T> {
|
||||
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
|
||||
build_wrapper_body_stack_memory(main_function_index, 12)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Wasm32TestResult> Wasm32TestResult for &'_ T {
|
||||
build_wrapper_body_primitive!(I32Store, ALIGN_4);
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Wasm32TestResult for [T; N]
|
||||
where
|
||||
T: Wasm32TestResult + FromWasm32Memory,
|
||||
{
|
||||
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
|
||||
build_wrapper_body_stack_memory(main_function_index, N * T::ACTUAL_WIDTH)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Wasm32TestResult for (T, U)
|
||||
where
|
||||
T: Wasm32TestResult + FromWasm32Memory,
|
||||
U: Wasm32TestResult + FromWasm32Memory,
|
||||
{
|
||||
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
|
||||
build_wrapper_body_stack_memory(main_function_index, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U, V> Wasm32TestResult for (T, U, V)
|
||||
where
|
||||
T: Wasm32TestResult + FromWasm32Memory,
|
||||
U: Wasm32TestResult + FromWasm32Memory,
|
||||
V: Wasm32TestResult + FromWasm32Memory,
|
||||
{
|
||||
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
|
||||
build_wrapper_body_stack_memory(
|
||||
main_function_index,
|
||||
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ extern crate libc;
|
|||
mod helpers;
|
||||
|
||||
#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
|
||||
mod dev_num {
|
||||
mod wasm_num {
|
||||
#[test]
|
||||
fn i64_values() {
|
||||
assert_evals_to!("0", 0, i64);
|
||||
|
@ -36,6 +36,101 @@ mod dev_num {
|
|||
assert_evals_to!(&format!("{:0.1}", f64::MAX), f64::MAX, f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn i8_add_wrap() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : I8
|
||||
x = 0x7f + 0x7f
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
-2,
|
||||
i8
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn i16_add_wrap() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : I16
|
||||
x = 0x7fff + 0x7fff
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
-2,
|
||||
i16
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn i32_add_wrap() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : I32
|
||||
x = 0x7fffffff + 0x7fffffff
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
-2,
|
||||
i32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u8_add_wrap() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : U8
|
||||
x = 0xff + 0xff
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
0xfe,
|
||||
u8
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u16_add_wrap() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : U16
|
||||
x = 0xffff + 0xffff
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
0xfffe,
|
||||
u16
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn u32_add_wrap() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : U32
|
||||
x = 0xffffffff + 0xffffffff
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
0xfffffffe,
|
||||
u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_add_i64() {
|
||||
assert_evals_to!(
|
||||
|
@ -49,44 +144,149 @@ mod dev_num {
|
|||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn gen_add_f64() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 1.1 + 2.4 + 3
|
||||
// "#
|
||||
// ),
|
||||
// 6.5,
|
||||
// f64
|
||||
// );
|
||||
// }
|
||||
#[test]
|
||||
fn if_then_else() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
cond : Bool
|
||||
cond = True
|
||||
|
||||
// #[test]
|
||||
// fn gen_sub_i64() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 1 - 2 - 3
|
||||
// "#
|
||||
// ),
|
||||
// -4,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
if cond then
|
||||
0
|
||||
else
|
||||
1
|
||||
"#
|
||||
),
|
||||
0,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn gen_mul_i64() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 2 * 4 * 6
|
||||
// "#
|
||||
// ),
|
||||
// 48,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
#[test]
|
||||
fn rgb_red() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Red is
|
||||
Red -> 111
|
||||
Green -> 222
|
||||
Blue -> 333
|
||||
"#
|
||||
),
|
||||
111,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rgb_green() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Green is
|
||||
Red -> 111
|
||||
Green -> 222
|
||||
Blue -> 333
|
||||
"#
|
||||
),
|
||||
222,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rgb_blue() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
when Blue is
|
||||
Red -> 111
|
||||
Green -> 222
|
||||
Blue -> 333
|
||||
"#
|
||||
),
|
||||
333,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_point() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = if True then 111 else 222
|
||||
|
||||
x + 123
|
||||
"#
|
||||
),
|
||||
234,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factorial() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
fac : I32, I32 -> I32
|
||||
fac = \n, accum ->
|
||||
if n > 1 then
|
||||
fac (n - 1) (n * accum)
|
||||
else
|
||||
accum
|
||||
|
||||
main : I32
|
||||
main = fac 8 1
|
||||
"#
|
||||
),
|
||||
40_320,
|
||||
i32
|
||||
);
|
||||
}
|
||||
|
||||
#[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() {
|
||||
|
@ -371,18 +571,18 @@ mod dev_num {
|
|||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn gen_sub_f64() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// 1.5 - 2.4 - 3
|
||||
// "#
|
||||
// ),
|
||||
// -3.9,
|
||||
// f64
|
||||
// );
|
||||
// }
|
||||
#[test]
|
||||
fn gen_sub_f64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
1.5 - 2.4 - 3
|
||||
"#
|
||||
),
|
||||
-3.9,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn gen_div_i64() {
|
||||
|
@ -580,31 +780,31 @@ mod dev_num {
|
|||
// 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() {
|
||||
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 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() {
|
||||
|
|
|
@ -5,7 +5,7 @@ extern crate indoc;
|
|||
mod helpers;
|
||||
|
||||
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
|
||||
mod dev_records {
|
||||
mod wasm_records {
|
||||
// #[test]
|
||||
// fn basic_record() {
|
||||
// assert_evals_to!(
|
||||
|
@ -389,18 +389,18 @@ mod dev_records {
|
|||
// // );
|
||||
// // }
|
||||
|
||||
// #[test]
|
||||
// fn i64_record1_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { a: 3 }
|
||||
// "#
|
||||
// ),
|
||||
// 3,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
#[test]
|
||||
fn i64_record1_literal() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{ a: 3 }
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
// // #[test]
|
||||
// // fn i64_record9_literal() {
|
||||
|
@ -428,21 +428,21 @@ mod dev_records {
|
|||
// // );
|
||||
// // }
|
||||
|
||||
// #[test]
|
||||
// fn bool_literal() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// x : Bool
|
||||
// x = True
|
||||
#[test]
|
||||
fn bool_literal() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : Bool
|
||||
x = True
|
||||
|
||||
// x
|
||||
// "#
|
||||
// ),
|
||||
// true,
|
||||
// bool
|
||||
// );
|
||||
// }
|
||||
x
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn optional_field_when_use_default() {
|
||||
|
|
|
@ -723,7 +723,21 @@ pub struct MonomorphizedModule<'a> {
|
|||
|
||||
impl<'a> MonomorphizedModule<'a> {
|
||||
pub fn total_problems(&self) -> usize {
|
||||
self.can_problems.len() + self.type_problems.len() + self.mono_problems.len()
|
||||
let mut total = 0;
|
||||
|
||||
for problems in self.can_problems.values() {
|
||||
total += problems.len();
|
||||
}
|
||||
|
||||
for problems in self.type_problems.values() {
|
||||
total += problems.len();
|
||||
}
|
||||
|
||||
for problems in self.mono_problems.values() {
|
||||
total += problems.len();
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2099,8 +2113,6 @@ fn update<'a>(
|
|||
&mut state.procedures,
|
||||
);
|
||||
|
||||
Proc::insert_refcount_operations(arena, &mut state.procedures);
|
||||
|
||||
// display the mono IR of the module, for debug purposes
|
||||
if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS {
|
||||
let procs_string = state
|
||||
|
@ -2114,6 +2126,8 @@ fn update<'a>(
|
|||
println!("{}", result);
|
||||
}
|
||||
|
||||
Proc::insert_refcount_operations(arena, &mut state.procedures);
|
||||
|
||||
// This is not safe with the new non-recursive RC updates that we do for tag unions
|
||||
//
|
||||
// Proc::optimize_refcount_operations(
|
||||
|
@ -3927,7 +3941,7 @@ fn make_specializations<'a>(
|
|||
);
|
||||
|
||||
let external_specializations_requested = procs.externals_we_need.clone();
|
||||
let procedures = procs.get_specialized_procs_without_rc(mono_env.arena);
|
||||
let procedures = procs.get_specialized_procs_without_rc(&mut mono_env);
|
||||
|
||||
let make_specializations_end = SystemTime::now();
|
||||
module_timing.make_specializations = make_specializations_end
|
||||
|
|
|
@ -589,9 +589,9 @@ fn call_spec(
|
|||
let index = builder.add_make_tuple(block, &[])?;
|
||||
|
||||
let argument = if closure_env_layout.is_none() {
|
||||
builder.add_make_tuple(block, &[first, index])?
|
||||
builder.add_make_tuple(block, &[index, first])?
|
||||
} else {
|
||||
builder.add_make_tuple(block, &[first, index, closure_env])?
|
||||
builder.add_make_tuple(block, &[index, first, closure_env])?
|
||||
};
|
||||
builder.add_call(block, spec_var, module, name, argument)?;
|
||||
}
|
||||
|
@ -1191,6 +1191,11 @@ fn layout_spec_help(
|
|||
match layout {
|
||||
Builtin(builtin) => builtin_spec(builder, builtin, when_recursive),
|
||||
Struct(fields) => build_recursive_tuple_type(builder, fields, when_recursive),
|
||||
LambdaSet(lambda_set) => layout_spec_help(
|
||||
builder,
|
||||
&lambda_set.runtime_representation(),
|
||||
when_recursive,
|
||||
),
|
||||
Union(union_layout) => {
|
||||
let variant_types = build_variant_types(builder, union_layout)?;
|
||||
|
||||
|
@ -1236,7 +1241,7 @@ fn builtin_spec(
|
|||
|
||||
match builtin {
|
||||
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize => builder.add_tuple_type(&[]),
|
||||
Decimal | Float128 | Float64 | Float32 | Float16 => builder.add_tuple_type(&[]),
|
||||
Decimal | Float128 | Float64 | Float32 => builder.add_tuple_type(&[]),
|
||||
Str | EmptyStr => str_type(builder),
|
||||
Dict(key_layout, value_layout) => {
|
||||
let value_type = layout_spec_help(builder, value_layout, when_recursive)?;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::exhaustive::{Ctor, RenderAs, TagId, Union};
|
||||
use crate::ir::{
|
||||
BranchInfo, DestructType, Env, Expr, JoinPointId, Literal, Param, Pattern, Procs, Stmt,
|
||||
BranchInfo, DestructType, Env, Expr, FloatPrecision, IntPrecision, JoinPointId, Literal, Param,
|
||||
Pattern, Procs, Stmt,
|
||||
};
|
||||
use crate::layout::{Builtin, Layout, LayoutCache, UnionLayout};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
|
@ -85,8 +86,8 @@ enum Test<'a> {
|
|||
union: crate::exhaustive::Union,
|
||||
arguments: Vec<(Pattern<'a>, Layout<'a>)>,
|
||||
},
|
||||
IsInt(i128),
|
||||
IsFloat(u64),
|
||||
IsInt(i128, IntPrecision),
|
||||
IsFloat(u64, FloatPrecision),
|
||||
IsDecimal(RocDec),
|
||||
IsStr(Box<str>),
|
||||
IsBit(bool),
|
||||
|
@ -95,6 +96,7 @@ enum Test<'a> {
|
|||
num_alts: usize,
|
||||
},
|
||||
}
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
impl<'a> Hash for Test<'a> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
|
@ -106,13 +108,15 @@ impl<'a> Hash for Test<'a> {
|
|||
tag_id.hash(state);
|
||||
// The point of this custom implementation is to not hash the tag arguments
|
||||
}
|
||||
IsInt(v) => {
|
||||
IsInt(v, width) => {
|
||||
state.write_u8(1);
|
||||
v.hash(state);
|
||||
width.hash(state);
|
||||
}
|
||||
IsFloat(v) => {
|
||||
IsFloat(v, width) => {
|
||||
state.write_u8(2);
|
||||
v.hash(state);
|
||||
width.hash(state);
|
||||
}
|
||||
IsStr(v) => {
|
||||
state.write_u8(3);
|
||||
|
@ -306,8 +310,8 @@ fn tests_are_complete_help(last_test: &Test, number_of_tests: usize) -> bool {
|
|||
Test::IsCtor { union, .. } => number_of_tests == union.alternatives.len(),
|
||||
Test::IsByte { num_alts, .. } => number_of_tests == *num_alts,
|
||||
Test::IsBit(_) => number_of_tests == 2,
|
||||
Test::IsInt(_) => false,
|
||||
Test::IsFloat(_) => false,
|
||||
Test::IsInt(_, _) => false,
|
||||
Test::IsFloat(_, _) => false,
|
||||
Test::IsDecimal(_) => false,
|
||||
Test::IsStr(_) => false,
|
||||
}
|
||||
|
@ -561,8 +565,8 @@ fn test_at_path<'a>(
|
|||
tag_id: *tag_id,
|
||||
num_alts: union.alternatives.len(),
|
||||
},
|
||||
IntLiteral(v) => IsInt(*v),
|
||||
FloatLiteral(v) => IsFloat(*v),
|
||||
IntLiteral(v, precision) => IsInt(*v, *precision),
|
||||
FloatLiteral(v, precision) => IsFloat(*v, *precision),
|
||||
DecimalLiteral(v) => IsDecimal(*v),
|
||||
StrLiteral(v) => IsStr(v.clone()),
|
||||
};
|
||||
|
@ -807,8 +811,9 @@ fn to_relevant_branch_help<'a>(
|
|||
_ => None,
|
||||
},
|
||||
|
||||
IntLiteral(int) => match test {
|
||||
IsInt(is_int) if int == *is_int => {
|
||||
IntLiteral(int, p1) => match test {
|
||||
IsInt(is_int, p2) if int == *is_int => {
|
||||
debug_assert_eq!(p1, *p2);
|
||||
start.extend(end);
|
||||
Some(Branch {
|
||||
goal: branch.goal,
|
||||
|
@ -819,8 +824,9 @@ fn to_relevant_branch_help<'a>(
|
|||
_ => None,
|
||||
},
|
||||
|
||||
FloatLiteral(float) => match test {
|
||||
IsFloat(test_float) if float == *test_float => {
|
||||
FloatLiteral(float, p1) => match test {
|
||||
IsFloat(test_float, p2) if float == *test_float => {
|
||||
debug_assert_eq!(p1, *p2);
|
||||
start.extend(end);
|
||||
Some(Branch {
|
||||
goal: branch.goal,
|
||||
|
@ -928,8 +934,8 @@ fn needs_tests(pattern: &Pattern) -> bool {
|
|||
| AppliedTag { .. }
|
||||
| BitLiteral { .. }
|
||||
| EnumLiteral { .. }
|
||||
| IntLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
| IntLiteral(_, _)
|
||||
| FloatLiteral(_, _)
|
||||
| DecimalLiteral(_)
|
||||
| StrLiteral(_) => true,
|
||||
}
|
||||
|
@ -1280,22 +1286,22 @@ fn test_to_equality<'a>(
|
|||
_ => unreachable!("{:?}", (cond_layout, union)),
|
||||
}
|
||||
}
|
||||
Test::IsInt(test_int) => {
|
||||
Test::IsInt(test_int, precision) => {
|
||||
// TODO don't downcast i128 here
|
||||
debug_assert!(test_int <= i64::MAX as i128);
|
||||
let lhs = Expr::Literal(Literal::Int(test_int as i128));
|
||||
let lhs_symbol = env.unique_symbol();
|
||||
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs));
|
||||
stores.push((lhs_symbol, precision.as_layout(), lhs));
|
||||
|
||||
(stores, lhs_symbol, rhs_symbol, None)
|
||||
}
|
||||
|
||||
Test::IsFloat(test_int) => {
|
||||
Test::IsFloat(test_int, precision) => {
|
||||
// TODO maybe we can actually use i64 comparison here?
|
||||
let test_float = f64::from_bits(test_int as u64);
|
||||
let lhs = Expr::Literal(Literal::Float(test_float));
|
||||
let lhs_symbol = env.unique_symbol();
|
||||
stores.push((lhs_symbol, Layout::Builtin(Builtin::Float64), lhs));
|
||||
stores.push((lhs_symbol, precision.as_layout(), lhs));
|
||||
|
||||
(stores, lhs_symbol, rhs_symbol, None)
|
||||
}
|
||||
|
@ -1303,7 +1309,7 @@ fn test_to_equality<'a>(
|
|||
Test::IsDecimal(test_dec) => {
|
||||
let lhs = Expr::Literal(Literal::Int(test_dec.0));
|
||||
let lhs_symbol = env.unique_symbol();
|
||||
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int128), lhs));
|
||||
stores.push((lhs_symbol, *cond_layout, lhs));
|
||||
|
||||
(stores, lhs_symbol, rhs_symbol, None)
|
||||
}
|
||||
|
@ -1737,8 +1743,8 @@ fn decide_to_branching<'a>(
|
|||
);
|
||||
|
||||
let tag = match test {
|
||||
Test::IsInt(v) => v as u64,
|
||||
Test::IsFloat(v) => v as u64,
|
||||
Test::IsInt(v, _) => v as u64,
|
||||
Test::IsFloat(v, _) => v as u64,
|
||||
Test::IsBit(v) => v as u64,
|
||||
Test::IsByte { tag_id, .. } => tag_id as u64,
|
||||
Test::IsCtor { tag_id, .. } => tag_id as u64,
|
||||
|
|
|
@ -65,8 +65,8 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
|
|||
use crate::ir::Pattern::*;
|
||||
|
||||
match pattern {
|
||||
IntLiteral(v) => Literal(Literal::Int(*v)),
|
||||
FloatLiteral(v) => Literal(Literal::Float(*v)),
|
||||
IntLiteral(v, _) => Literal(Literal::Int(*v)),
|
||||
FloatLiteral(v, _) => Literal(Literal::Float(*v)),
|
||||
DecimalLiteral(v) => Literal(Literal::Decimal(*v)),
|
||||
StrLiteral(v) => Literal(Literal::Str(v.clone())),
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ macro_rules! return_on_layout_error_help {
|
|||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum OptLevel {
|
||||
Development,
|
||||
Normal,
|
||||
Optimize,
|
||||
}
|
||||
|
@ -272,6 +273,33 @@ impl<'a> Proc<'a> {
|
|||
proc.body = b.clone();
|
||||
}
|
||||
}
|
||||
|
||||
fn make_tail_recursive(&mut self, env: &mut Env<'a, '_>) {
|
||||
let mut args = Vec::with_capacity_in(self.args.len(), env.arena);
|
||||
let mut proc_args = Vec::with_capacity_in(self.args.len(), env.arena);
|
||||
|
||||
for (layout, symbol) in self.args {
|
||||
let new = env.unique_symbol();
|
||||
args.push((*layout, *symbol, new));
|
||||
proc_args.push((*layout, new));
|
||||
}
|
||||
|
||||
use self::SelfRecursive::*;
|
||||
if let SelfRecursive(id) = self.is_self_recursive {
|
||||
let transformed = crate::tail_recursion::make_tail_recursive(
|
||||
env.arena,
|
||||
id,
|
||||
self.name,
|
||||
self.body.clone(),
|
||||
args.into_bump_slice(),
|
||||
);
|
||||
|
||||
if let Some(with_tco) = transformed {
|
||||
self.body = with_tco;
|
||||
self.args = proc_args.into_bump_slice();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -350,7 +378,7 @@ pub enum InProgressProc<'a> {
|
|||
impl<'a> Procs<'a> {
|
||||
pub fn get_specialized_procs_without_rc(
|
||||
self,
|
||||
arena: &'a Bump,
|
||||
env: &mut Env<'a, '_>,
|
||||
) -> MutMap<(Symbol, ProcLayout<'a>), Proc<'a>> {
|
||||
let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
|
||||
|
||||
|
@ -376,16 +404,7 @@ impl<'a> Procs<'a> {
|
|||
panic!();
|
||||
}
|
||||
Done(mut proc) => {
|
||||
use self::SelfRecursive::*;
|
||||
if let SelfRecursive(id) = proc.is_self_recursive {
|
||||
proc.body = crate::tail_recursion::make_tail_recursive(
|
||||
arena,
|
||||
id,
|
||||
proc.name,
|
||||
proc.body.clone(),
|
||||
proc.args,
|
||||
);
|
||||
}
|
||||
proc.make_tail_recursive(env);
|
||||
|
||||
result.insert(key, proc);
|
||||
}
|
||||
|
@ -395,86 +414,6 @@ impl<'a> Procs<'a> {
|
|||
result
|
||||
}
|
||||
|
||||
// TODO investigate make this an iterator?
|
||||
pub fn get_specialized_procs(
|
||||
self,
|
||||
arena: &'a Bump,
|
||||
) -> MutMap<(Symbol, ProcLayout<'a>), Proc<'a>> {
|
||||
let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
|
||||
|
||||
for ((s, toplevel), in_prog_proc) in self.specialized.into_iter() {
|
||||
match in_prog_proc {
|
||||
InProgress => unreachable!(
|
||||
"The procedure {:?} should have be done by now",
|
||||
(s, toplevel)
|
||||
),
|
||||
Done(proc) => {
|
||||
result.insert((s, toplevel), proc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (_, proc) in result.iter_mut() {
|
||||
use self::SelfRecursive::*;
|
||||
if let SelfRecursive(id) = proc.is_self_recursive {
|
||||
proc.body = crate::tail_recursion::make_tail_recursive(
|
||||
arena,
|
||||
id,
|
||||
proc.name,
|
||||
proc.body.clone(),
|
||||
proc.args,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
|
||||
|
||||
crate::inc_dec::visit_procs(arena, borrow_params, &mut result);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn get_specialized_procs_help(
|
||||
self,
|
||||
arena: &'a Bump,
|
||||
) -> (
|
||||
MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
&'a crate::borrow::ParamMap<'a>,
|
||||
) {
|
||||
let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
|
||||
|
||||
for ((s, toplevel), in_prog_proc) in self.specialized.into_iter() {
|
||||
match in_prog_proc {
|
||||
InProgress => unreachable!(
|
||||
"The procedure {:?} should have be done by now",
|
||||
(s, toplevel)
|
||||
),
|
||||
Done(proc) => {
|
||||
result.insert((s, toplevel), proc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (_, proc) in result.iter_mut() {
|
||||
use self::SelfRecursive::*;
|
||||
if let SelfRecursive(id) = proc.is_self_recursive {
|
||||
proc.body = crate::tail_recursion::make_tail_recursive(
|
||||
arena,
|
||||
id,
|
||||
proc.name,
|
||||
proc.body.clone(),
|
||||
proc.args,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
|
||||
|
||||
crate::inc_dec::visit_procs(arena, borrow_params, &mut result);
|
||||
|
||||
(result, borrow_params)
|
||||
}
|
||||
|
||||
// TODO trim down these arguments!
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn insert_named(
|
||||
|
@ -756,7 +695,20 @@ impl<'a> Procs<'a> {
|
|||
// the `layout` is a function pointer, while `_ignore_layout` can be a
|
||||
// closure. We only specialize functions, storing this value with a closure
|
||||
// layout will give trouble.
|
||||
self.specialized.insert((symbol, layout), Done(proc));
|
||||
let arguments =
|
||||
Vec::from_iter_in(proc.args.iter().map(|(l, _)| *l), env.arena)
|
||||
.into_bump_slice();
|
||||
|
||||
let proper_layout = ProcLayout {
|
||||
arguments,
|
||||
result: proc.ret_layout,
|
||||
};
|
||||
|
||||
// NOTE: some function are specialized to have a closure, but don't actually
|
||||
// need any closure argument. Here is where we correct this sort of thing,
|
||||
// by trusting the layout of the Proc, not of what we specialize for
|
||||
self.specialized.remove(&(symbol, layout));
|
||||
self.specialized.insert((symbol, proper_layout), Done(proc));
|
||||
}
|
||||
Err(error) => {
|
||||
panic!("TODO generate a RuntimeError message for {:?}", error);
|
||||
|
@ -1905,7 +1857,7 @@ fn generate_runtime_error_function<'a>(
|
|||
args.push((*arg, env.unique_symbol()));
|
||||
}
|
||||
|
||||
args.push((lambda_set.runtime_representation(), Symbol::ARG_CLOSURE));
|
||||
args.push((Layout::LambdaSet(lambda_set), Symbol::ARG_CLOSURE));
|
||||
|
||||
(args.into_bump_slice(), *ret_layout)
|
||||
}
|
||||
|
@ -1981,7 +1933,29 @@ fn specialize_external<'a>(
|
|||
match layout {
|
||||
RawFunctionLayout::Function(argument_layouts, lambda_set, return_layout) => {
|
||||
let assigned = env.unique_symbol();
|
||||
let unit = env.unique_symbol();
|
||||
|
||||
let mut argument_symbols =
|
||||
Vec::with_capacity_in(argument_layouts.len(), env.arena);
|
||||
let mut proc_arguments =
|
||||
Vec::with_capacity_in(argument_layouts.len() + 1, env.arena);
|
||||
let mut top_level_arguments =
|
||||
Vec::with_capacity_in(argument_layouts.len() + 1, env.arena);
|
||||
|
||||
for layout in argument_layouts {
|
||||
let symbol = env.unique_symbol();
|
||||
|
||||
proc_arguments.push((*layout, symbol));
|
||||
|
||||
argument_symbols.push(symbol);
|
||||
top_level_arguments.push(*layout);
|
||||
}
|
||||
|
||||
// the proc needs to take an extra closure argument
|
||||
let lambda_set_layout = Layout::LambdaSet(lambda_set);
|
||||
proc_arguments.push((lambda_set_layout, Symbol::ARG_CLOSURE));
|
||||
|
||||
// this should also be reflected in the TopLevel signature
|
||||
top_level_arguments.push(lambda_set_layout);
|
||||
|
||||
let hole = env.arena.alloc(Stmt::Ret(assigned));
|
||||
|
||||
|
@ -1989,20 +1963,16 @@ fn specialize_external<'a>(
|
|||
env,
|
||||
lambda_set,
|
||||
Symbol::ARG_CLOSURE,
|
||||
env.arena.alloc([unit]),
|
||||
argument_symbols.into_bump_slice(),
|
||||
argument_layouts,
|
||||
*return_layout,
|
||||
assigned,
|
||||
hole,
|
||||
);
|
||||
|
||||
let body = let_empty_struct(unit, env.arena.alloc(body));
|
||||
|
||||
let proc = Proc {
|
||||
name,
|
||||
args: env
|
||||
.arena
|
||||
.alloc([(lambda_set.runtime_representation(), Symbol::ARG_CLOSURE)]),
|
||||
args: proc_arguments.into_bump_slice(),
|
||||
body,
|
||||
closure_data_layout: None,
|
||||
ret_layout: *return_layout,
|
||||
|
@ -2013,7 +1983,7 @@ fn specialize_external<'a>(
|
|||
|
||||
let top_level = ProcLayout::new(
|
||||
env.arena,
|
||||
env.arena.alloc([lambda_set.runtime_representation()]),
|
||||
top_level_arguments.into_bump_slice(),
|
||||
*return_layout,
|
||||
);
|
||||
|
||||
|
@ -2061,7 +2031,7 @@ fn specialize_external<'a>(
|
|||
env.subs.rollback_to(snapshot);
|
||||
|
||||
let closure_data_layout = match opt_closure_layout {
|
||||
Some(closure_layout) => closure_layout.runtime_representation(),
|
||||
Some(lambda_set) => Layout::LambdaSet(lambda_set),
|
||||
None => Layout::Struct(&[]),
|
||||
};
|
||||
|
||||
|
@ -2211,7 +2181,7 @@ fn specialize_external<'a>(
|
|||
env.subs.rollback_to(snapshot);
|
||||
|
||||
let closure_data_layout = match opt_closure_layout {
|
||||
Some(closure_layout) => Some(closure_layout.runtime_representation()),
|
||||
Some(lambda_set) => Some(Layout::LambdaSet(lambda_set)),
|
||||
None => None,
|
||||
};
|
||||
|
||||
|
@ -2322,7 +2292,7 @@ fn build_specialized_proc<'a>(
|
|||
Some(lambda_set) if pattern_symbols.last() == Some(&Symbol::ARG_CLOSURE) => {
|
||||
// here we define the lifted (now top-level) f function. Its final argument is `Symbol::ARG_CLOSURE`,
|
||||
// it stores the closure structure (just an integer in this case)
|
||||
proc_args.push((lambda_set.runtime_representation(), Symbol::ARG_CLOSURE));
|
||||
proc_args.push((Layout::LambdaSet(lambda_set), Symbol::ARG_CLOSURE));
|
||||
|
||||
debug_assert_eq!(
|
||||
pattern_layouts_len + 1,
|
||||
|
@ -2359,7 +2329,7 @@ fn build_specialized_proc<'a>(
|
|||
}
|
||||
Ordering::Greater => {
|
||||
if pattern_symbols.is_empty() {
|
||||
let ret_layout = lambda_set.runtime_representation();
|
||||
let ret_layout = Layout::LambdaSet(lambda_set);
|
||||
Ok(FunctionPointerBody {
|
||||
closure: None,
|
||||
ret_layout,
|
||||
|
@ -2544,7 +2514,7 @@ where
|
|||
let raw = if procs.module_thunks.contains(&proc_name) {
|
||||
match raw {
|
||||
RawFunctionLayout::Function(_, lambda_set, _) => {
|
||||
RawFunctionLayout::ZeroArgumentThunk(lambda_set.runtime_representation())
|
||||
RawFunctionLayout::ZeroArgumentThunk(Layout::LambdaSet(lambda_set))
|
||||
}
|
||||
_ => raw,
|
||||
}
|
||||
|
@ -2718,6 +2688,7 @@ macro_rules! match_on_closure_argument {
|
|||
let ret_layout = top_level.result;
|
||||
|
||||
|
||||
|
||||
match closure_data_layout {
|
||||
RawFunctionLayout::Function(_, lambda_set, _) => {
|
||||
lowlevel_match_on_lambda_set(
|
||||
|
@ -2822,13 +2793,13 @@ pub fn with_hole<'a>(
|
|||
IntOrFloat::SignedIntType(precision) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Int(int)),
|
||||
Layout::Builtin(int_precision_to_builtin(precision)),
|
||||
precision.as_layout(),
|
||||
hole,
|
||||
),
|
||||
IntOrFloat::UnsignedIntType(precision) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Int(int)),
|
||||
Layout::Builtin(int_precision_to_builtin(precision)),
|
||||
precision.as_layout(),
|
||||
hole,
|
||||
),
|
||||
_ => unreachable!("unexpected float precision for integer"),
|
||||
|
@ -2840,7 +2811,7 @@ pub fn with_hole<'a>(
|
|||
IntOrFloat::BinaryFloatType(precision) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Float(float)),
|
||||
Layout::Builtin(float_precision_to_builtin(precision)),
|
||||
precision.as_layout(),
|
||||
hole,
|
||||
),
|
||||
IntOrFloat::DecimalFloatType => {
|
||||
|
@ -2872,19 +2843,19 @@ pub fn with_hole<'a>(
|
|||
IntOrFloat::SignedIntType(precision) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Int(num.into())),
|
||||
Layout::Builtin(int_precision_to_builtin(precision)),
|
||||
precision.as_layout(),
|
||||
hole,
|
||||
),
|
||||
IntOrFloat::UnsignedIntType(precision) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Int(num.into())),
|
||||
Layout::Builtin(int_precision_to_builtin(precision)),
|
||||
precision.as_layout(),
|
||||
hole,
|
||||
),
|
||||
IntOrFloat::BinaryFloatType(precision) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Float(num as f64)),
|
||||
Layout::Builtin(float_precision_to_builtin(precision)),
|
||||
precision.as_layout(),
|
||||
hole,
|
||||
),
|
||||
IntOrFloat::DecimalFloatType => {
|
||||
|
@ -3748,13 +3719,15 @@ pub fn with_hole<'a>(
|
|||
|
||||
match what_to_do {
|
||||
UpdateExisting(field) => {
|
||||
substitute_in_exprs(env.arena, &mut stmt, assigned, symbols[0]);
|
||||
|
||||
stmt = assign_to_symbol(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
field.var,
|
||||
*field.loc_expr.clone(),
|
||||
assigned,
|
||||
symbols[0],
|
||||
stmt,
|
||||
);
|
||||
}
|
||||
|
@ -4191,6 +4164,8 @@ fn construct_closure_data<'a>(
|
|||
assigned: Symbol,
|
||||
hole: &'a Stmt<'a>,
|
||||
) -> Stmt<'a> {
|
||||
let lambda_set_layout = Layout::LambdaSet(lambda_set);
|
||||
|
||||
match lambda_set.layout_for_member(name) {
|
||||
ClosureRepresentation::Union {
|
||||
tag_id,
|
||||
|
@ -4222,12 +4197,7 @@ fn construct_closure_data<'a>(
|
|||
arguments: symbols,
|
||||
};
|
||||
|
||||
Stmt::Let(
|
||||
assigned,
|
||||
expr,
|
||||
lambda_set.runtime_representation(),
|
||||
env.arena.alloc(hole),
|
||||
)
|
||||
Stmt::Let(assigned, expr, lambda_set_layout, env.arena.alloc(hole))
|
||||
}
|
||||
ClosureRepresentation::AlphabeticOrderStruct(field_layouts) => {
|
||||
debug_assert_eq!(field_layouts.len(), symbols.len());
|
||||
|
@ -4258,7 +4228,7 @@ fn construct_closure_data<'a>(
|
|||
|
||||
let expr = Expr::Struct(symbols);
|
||||
|
||||
Stmt::Let(assigned, expr, lambda_set.runtime_representation(), hole)
|
||||
Stmt::Let(assigned, expr, lambda_set_layout, hole)
|
||||
}
|
||||
ClosureRepresentation::Other(Layout::Builtin(Builtin::Int1)) => {
|
||||
debug_assert_eq!(symbols.len(), 0);
|
||||
|
@ -4267,7 +4237,7 @@ fn construct_closure_data<'a>(
|
|||
let tag_id = name != lambda_set.set[0].0;
|
||||
let expr = Expr::Literal(Literal::Bool(tag_id));
|
||||
|
||||
Stmt::Let(assigned, expr, lambda_set.runtime_representation(), hole)
|
||||
Stmt::Let(assigned, expr, lambda_set_layout, hole)
|
||||
}
|
||||
ClosureRepresentation::Other(Layout::Builtin(Builtin::Int8)) => {
|
||||
debug_assert_eq!(symbols.len(), 0);
|
||||
|
@ -4276,7 +4246,7 @@ fn construct_closure_data<'a>(
|
|||
let tag_id = lambda_set.set.iter().position(|(s, _)| *s == name).unwrap() as u8;
|
||||
let expr = Expr::Literal(Literal::Byte(tag_id));
|
||||
|
||||
Stmt::Let(assigned, expr, lambda_set.runtime_representation(), hole)
|
||||
Stmt::Let(assigned, expr, lambda_set_layout, hole)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -5683,8 +5653,8 @@ fn store_pattern_help<'a>(
|
|||
// do nothing
|
||||
return StorePattern::NotProductive(stmt);
|
||||
}
|
||||
IntLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
IntLiteral(_, _)
|
||||
| FloatLiteral(_, _)
|
||||
| DecimalLiteral(_)
|
||||
| EnumLiteral { .. }
|
||||
| BitLiteral { .. }
|
||||
|
@ -5818,8 +5788,8 @@ fn store_tag_pattern<'a>(
|
|||
Underscore => {
|
||||
// ignore
|
||||
}
|
||||
IntLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
IntLiteral(_, _)
|
||||
| FloatLiteral(_, _)
|
||||
| DecimalLiteral(_)
|
||||
| EnumLiteral { .. }
|
||||
| BitLiteral { .. }
|
||||
|
@ -5894,8 +5864,8 @@ fn store_newtype_pattern<'a>(
|
|||
Underscore => {
|
||||
// ignore
|
||||
}
|
||||
IntLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
IntLiteral(_, _)
|
||||
| FloatLiteral(_, _)
|
||||
| DecimalLiteral(_)
|
||||
| EnumLiteral { .. }
|
||||
| BitLiteral { .. }
|
||||
|
@ -5970,8 +5940,8 @@ fn store_record_destruct<'a>(
|
|||
// internally. But `y` is never used, so we must make sure it't not stored/loaded.
|
||||
return StorePattern::NotProductive(stmt);
|
||||
}
|
||||
IntLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
IntLiteral(_, _)
|
||||
| FloatLiteral(_, _)
|
||||
| DecimalLiteral(_)
|
||||
| EnumLiteral { .. }
|
||||
| BitLiteral { .. }
|
||||
|
@ -6120,7 +6090,7 @@ fn reuse_function_symbol<'a>(
|
|||
let layout = match raw {
|
||||
RawFunctionLayout::ZeroArgumentThunk(layout) => layout,
|
||||
RawFunctionLayout::Function(_, lambda_set, _) => {
|
||||
lambda_set.runtime_representation()
|
||||
Layout::LambdaSet(lambda_set)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -6218,7 +6188,7 @@ fn reuse_function_symbol<'a>(
|
|||
// TODO suspicious
|
||||
// let layout = Layout::Closure(argument_layouts, lambda_set, ret_layout);
|
||||
// panic!("suspicious");
|
||||
let layout = lambda_set.runtime_representation();
|
||||
let layout = Layout::LambdaSet(lambda_set);
|
||||
let top_level = ProcLayout::new(env.arena, &[], layout);
|
||||
procs.insert_passed_by_name(
|
||||
env,
|
||||
|
@ -6238,9 +6208,14 @@ fn reuse_function_symbol<'a>(
|
|||
layout_cache,
|
||||
);
|
||||
|
||||
// a function name (non-closure) that is passed along
|
||||
// it never has closure data, so we use the empty struct
|
||||
return let_empty_struct(symbol, env.arena.alloc(result));
|
||||
construct_closure_data(
|
||||
env,
|
||||
lambda_set,
|
||||
original,
|
||||
&[],
|
||||
symbol,
|
||||
env.arena.alloc(result),
|
||||
)
|
||||
}
|
||||
}
|
||||
RawFunctionLayout::ZeroArgumentThunk(ret_layout) => {
|
||||
|
@ -6408,7 +6383,7 @@ fn call_by_name<'a>(
|
|||
procs,
|
||||
fn_var,
|
||||
proc_name,
|
||||
env.arena.alloc(lambda_set.runtime_representation()),
|
||||
env.arena.alloc(Layout::LambdaSet(lambda_set)),
|
||||
layout_cache,
|
||||
assigned,
|
||||
hole,
|
||||
|
@ -6452,7 +6427,7 @@ fn call_by_name<'a>(
|
|||
procs,
|
||||
fn_var,
|
||||
proc_name,
|
||||
env.arena.alloc(lambda_set.runtime_representation()),
|
||||
env.arena.alloc(Layout::LambdaSet(lambda_set)),
|
||||
layout_cache,
|
||||
closure_data_symbol,
|
||||
env.arena.alloc(result),
|
||||
|
@ -6582,7 +6557,7 @@ fn call_by_name_help<'a>(
|
|||
force_thunk(
|
||||
env,
|
||||
proc_name,
|
||||
lambda_set.runtime_representation(),
|
||||
Layout::LambdaSet(lambda_set),
|
||||
assigned,
|
||||
hole,
|
||||
)
|
||||
|
@ -6896,13 +6871,7 @@ fn call_specialized_proc<'a>(
|
|||
arguments: field_symbols,
|
||||
};
|
||||
|
||||
build_call(
|
||||
env,
|
||||
call,
|
||||
assigned,
|
||||
lambda_set.runtime_representation(),
|
||||
hole,
|
||||
)
|
||||
build_call(env, call, assigned, Layout::LambdaSet(lambda_set), hole)
|
||||
}
|
||||
RawFunctionLayout::ZeroArgumentThunk(_) => {
|
||||
unreachable!()
|
||||
|
@ -6942,8 +6911,8 @@ fn call_specialized_proc<'a>(
|
|||
pub enum Pattern<'a> {
|
||||
Identifier(Symbol),
|
||||
Underscore,
|
||||
IntLiteral(i128),
|
||||
FloatLiteral(u64),
|
||||
IntLiteral(i128, IntPrecision),
|
||||
FloatLiteral(u64, FloatPrecision),
|
||||
DecimalLiteral(RocDec),
|
||||
BitLiteral {
|
||||
value: bool,
|
||||
|
@ -7021,21 +6990,35 @@ fn from_can_pattern_help<'a>(
|
|||
match can_pattern {
|
||||
Underscore => Ok(Pattern::Underscore),
|
||||
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
|
||||
IntLiteral(_, _, int) => Ok(Pattern::IntLiteral(*int as i128)),
|
||||
IntLiteral(var, _, int) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, false) {
|
||||
IntOrFloat::SignedIntType(precision) | IntOrFloat::UnsignedIntType(precision) => {
|
||||
Ok(Pattern::IntLiteral(*int as i128, precision))
|
||||
}
|
||||
other => {
|
||||
panic!(
|
||||
"Invalid precision for int pattern: {:?} has {:?}",
|
||||
can_pattern, other
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
FloatLiteral(var, float_str, float) => {
|
||||
// TODO: Can I reuse num_argument_to_int_or_float here if I pass in true?
|
||||
match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, true) {
|
||||
IntOrFloat::SignedIntType(_) => {
|
||||
panic!("Invalid percision for float literal = {:?}", var)
|
||||
IntOrFloat::SignedIntType(_) | IntOrFloat::UnsignedIntType(_) => {
|
||||
panic!("Invalid precision for float pattern {:?}", var)
|
||||
}
|
||||
IntOrFloat::UnsignedIntType(_) => {
|
||||
panic!("Invalid percision for float literal = {:?}", var)
|
||||
IntOrFloat::BinaryFloatType(precision) => {
|
||||
Ok(Pattern::FloatLiteral(f64::to_bits(*float), precision))
|
||||
}
|
||||
IntOrFloat::BinaryFloatType(_) => Ok(Pattern::FloatLiteral(f64::to_bits(*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),
|
||||
None => panic!(
|
||||
r"Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message",
|
||||
float_str
|
||||
),
|
||||
};
|
||||
Ok(Pattern::DecimalLiteral(dec))
|
||||
}
|
||||
|
@ -7053,9 +7036,15 @@ fn from_can_pattern_help<'a>(
|
|||
}
|
||||
NumLiteral(var, num_str, num) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, false) {
|
||||
IntOrFloat::SignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)),
|
||||
IntOrFloat::UnsignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)),
|
||||
IntOrFloat::BinaryFloatType(_) => Ok(Pattern::FloatLiteral(*num as u64)),
|
||||
IntOrFloat::SignedIntType(precision) => {
|
||||
Ok(Pattern::IntLiteral(*num as i128, precision))
|
||||
}
|
||||
IntOrFloat::UnsignedIntType(precision) => {
|
||||
Ok(Pattern::IntLiteral(*num as i128, precision))
|
||||
}
|
||||
IntOrFloat::BinaryFloatType(precision) => {
|
||||
Ok(Pattern::FloatLiteral(*num as u64, precision))
|
||||
}
|
||||
IntOrFloat::DecimalFloatType => {
|
||||
let dec = match RocDec::from_str(num_str) {
|
||||
Some(d) => d,
|
||||
|
@ -7637,7 +7626,7 @@ fn from_can_record_destruct<'a>(
|
|||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Hash)]
|
||||
pub enum IntPrecision {
|
||||
Usize,
|
||||
I128,
|
||||
|
@ -7647,29 +7636,14 @@ pub enum IntPrecision {
|
|||
I8,
|
||||
}
|
||||
|
||||
pub enum FloatPrecision {
|
||||
F64,
|
||||
F32,
|
||||
}
|
||||
|
||||
pub enum IntOrFloat {
|
||||
SignedIntType(IntPrecision),
|
||||
UnsignedIntType(IntPrecision),
|
||||
BinaryFloatType(FloatPrecision),
|
||||
DecimalFloatType,
|
||||
}
|
||||
|
||||
fn float_precision_to_builtin(precision: FloatPrecision) -> Builtin<'static> {
|
||||
use FloatPrecision::*;
|
||||
match precision {
|
||||
F64 => Builtin::Float64,
|
||||
F32 => Builtin::Float32,
|
||||
impl IntPrecision {
|
||||
pub fn as_layout(&self) -> Layout<'static> {
|
||||
Layout::Builtin(self.as_builtin())
|
||||
}
|
||||
}
|
||||
|
||||
fn int_precision_to_builtin(precision: IntPrecision) -> Builtin<'static> {
|
||||
pub fn as_builtin(&self) -> Builtin<'static> {
|
||||
use IntPrecision::*;
|
||||
match precision {
|
||||
match self {
|
||||
I128 => Builtin::Int128,
|
||||
I64 => Builtin::Int64,
|
||||
I32 => Builtin::Int32,
|
||||
|
@ -7677,6 +7651,35 @@ fn int_precision_to_builtin(precision: IntPrecision) -> Builtin<'static> {
|
|||
I8 => Builtin::Int8,
|
||||
Usize => Builtin::Usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Hash)]
|
||||
pub enum FloatPrecision {
|
||||
F64,
|
||||
F32,
|
||||
}
|
||||
|
||||
impl FloatPrecision {
|
||||
pub fn as_layout(&self) -> Layout<'static> {
|
||||
Layout::Builtin(self.as_builtin())
|
||||
}
|
||||
|
||||
pub fn as_builtin(&self) -> Builtin<'static> {
|
||||
use FloatPrecision::*;
|
||||
match self {
|
||||
F64 => Builtin::Float64,
|
||||
F32 => Builtin::Float32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum IntOrFloat {
|
||||
SignedIntType(IntPrecision),
|
||||
UnsignedIntType(IntPrecision),
|
||||
BinaryFloatType(FloatPrecision),
|
||||
DecimalFloatType,
|
||||
}
|
||||
|
||||
/// Given the `a` in `Num a`, determines whether it's an int or a float
|
||||
|
@ -7975,8 +7978,8 @@ fn match_on_lambda_set<'a>(
|
|||
|
||||
let result = union_lambda_set_to_switch(
|
||||
env,
|
||||
lambda_set.set,
|
||||
lambda_set.runtime_representation(),
|
||||
lambda_set,
|
||||
Layout::Union(union_layout),
|
||||
closure_tag_id_symbol,
|
||||
union_layout.tag_id_layout(),
|
||||
closure_data_symbol,
|
||||
|
@ -8006,6 +8009,7 @@ fn match_on_lambda_set<'a>(
|
|||
union_lambda_set_branch_help(
|
||||
env,
|
||||
function_symbol,
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
Layout::Struct(fields),
|
||||
argument_symbols,
|
||||
|
@ -8054,7 +8058,7 @@ fn match_on_lambda_set<'a>(
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
fn union_lambda_set_to_switch<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
lambda_set: &'a [(Symbol, &'a [Layout<'a>])],
|
||||
lambda_set: LambdaSet<'a>,
|
||||
closure_layout: Layout<'a>,
|
||||
closure_tag_id_symbol: Symbol,
|
||||
closure_tag_id_layout: Layout<'a>,
|
||||
|
@ -8065,7 +8069,7 @@ fn union_lambda_set_to_switch<'a>(
|
|||
assigned: Symbol,
|
||||
hole: &'a Stmt<'a>,
|
||||
) -> Stmt<'a> {
|
||||
if lambda_set.is_empty() {
|
||||
if lambda_set.set.is_empty() {
|
||||
// NOTE this can happen if there is a type error somewhere. Since the lambda set is empty,
|
||||
// there is really nothing we can do here. We generate a runtime error here which allows
|
||||
// code gen to proceed. We then assume that we hit another (more descriptive) error before
|
||||
|
@ -8077,11 +8081,12 @@ fn union_lambda_set_to_switch<'a>(
|
|||
|
||||
let join_point_id = JoinPointId(env.unique_symbol());
|
||||
|
||||
let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena);
|
||||
let mut branches = Vec::with_capacity_in(lambda_set.set.len(), env.arena);
|
||||
|
||||
for (i, (function_symbol, _)) in lambda_set.iter().enumerate() {
|
||||
for (i, (function_symbol, _)) in lambda_set.set.iter().enumerate() {
|
||||
let stmt = union_lambda_set_branch(
|
||||
env,
|
||||
lambda_set,
|
||||
join_point_id,
|
||||
*function_symbol,
|
||||
closure_data_symbol,
|
||||
|
@ -8124,6 +8129,7 @@ fn union_lambda_set_to_switch<'a>(
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
fn union_lambda_set_branch<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
lambda_set: LambdaSet<'a>,
|
||||
join_point_id: JoinPointId,
|
||||
function_symbol: Symbol,
|
||||
closure_data_symbol: Symbol,
|
||||
|
@ -8139,6 +8145,7 @@ fn union_lambda_set_branch<'a>(
|
|||
union_lambda_set_branch_help(
|
||||
env,
|
||||
function_symbol,
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
closure_data_layout,
|
||||
argument_symbols_slice,
|
||||
|
@ -8153,6 +8160,7 @@ fn union_lambda_set_branch<'a>(
|
|||
fn union_lambda_set_branch_help<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
function_symbol: Symbol,
|
||||
lambda_set: LambdaSet<'a>,
|
||||
closure_data_symbol: Symbol,
|
||||
closure_data_layout: Layout<'a>,
|
||||
argument_symbols_slice: &'a [Symbol],
|
||||
|
@ -8165,12 +8173,19 @@ fn union_lambda_set_branch_help<'a>(
|
|||
Layout::Struct(&[]) | Layout::Builtin(Builtin::Int1) | Layout::Builtin(Builtin::Int8) => {
|
||||
(argument_layouts_slice, argument_symbols_slice)
|
||||
}
|
||||
_ if lambda_set.member_does_not_need_closure_argument(function_symbol) => {
|
||||
// sometimes unification causes a function that does not itself capture anything
|
||||
// to still get a lambda set that does store information. We must not pass a closure
|
||||
// argument in this case
|
||||
|
||||
(argument_layouts_slice, argument_symbols_slice)
|
||||
}
|
||||
_ => {
|
||||
// extend layouts with the layout of the closure environment
|
||||
let mut argument_layouts =
|
||||
Vec::with_capacity_in(argument_layouts_slice.len() + 1, env.arena);
|
||||
argument_layouts.extend(argument_layouts_slice);
|
||||
argument_layouts.push(closure_data_layout);
|
||||
argument_layouts.push(Layout::LambdaSet(lambda_set));
|
||||
|
||||
// extend symbols with the symbol of the closure environment
|
||||
let mut argument_symbols =
|
||||
|
|
|
@ -189,6 +189,7 @@ pub enum Layout<'a> {
|
|||
/// this is important for closures that capture zero-sized values
|
||||
Struct(&'a [Layout<'a>]),
|
||||
Union(UnionLayout<'a>),
|
||||
LambdaSet(LambdaSet<'a>),
|
||||
RecursivePointer,
|
||||
}
|
||||
|
||||
|
@ -454,6 +455,17 @@ impl<'a> LambdaSet<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn member_does_not_need_closure_argument(&self, function_symbol: Symbol) -> bool {
|
||||
match self.layout_for_member(function_symbol) {
|
||||
ClosureRepresentation::Union {
|
||||
alphabetic_order_fields,
|
||||
..
|
||||
} => alphabetic_order_fields.is_empty(),
|
||||
ClosureRepresentation::AlphabeticOrderStruct(fields) => fields.is_empty(),
|
||||
ClosureRepresentation::Other(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layout_for_member(&self, function_symbol: Symbol) -> ClosureRepresentation<'a> {
|
||||
debug_assert!(
|
||||
self.set.iter().any(|(s, _)| *s == function_symbol),
|
||||
|
@ -531,7 +543,7 @@ impl<'a> LambdaSet<'a> {
|
|||
_ => {
|
||||
let mut arguments = Vec::with_capacity_in(argument_layouts.len() + 1, arena);
|
||||
arguments.extend(argument_layouts);
|
||||
arguments.push(self.runtime_representation());
|
||||
arguments.push(Layout::LambdaSet(*self));
|
||||
|
||||
arguments.into_bump_slice()
|
||||
}
|
||||
|
@ -606,7 +618,9 @@ impl<'a> LambdaSet<'a> {
|
|||
use UnionVariant::*;
|
||||
match variant {
|
||||
Never => Layout::Union(UnionLayout::NonRecursive(&[])),
|
||||
Unit | UnitWithArguments | BoolUnion { .. } | ByteUnion(_) => {
|
||||
BoolUnion { .. } => Layout::Builtin(Builtin::Int1),
|
||||
ByteUnion { .. } => Layout::Builtin(Builtin::Int8),
|
||||
Unit | UnitWithArguments => {
|
||||
// no useful information to store
|
||||
Layout::Struct(&[])
|
||||
}
|
||||
|
@ -667,7 +681,6 @@ pub enum Builtin<'a> {
|
|||
Float128,
|
||||
Float64,
|
||||
Float32,
|
||||
Float16,
|
||||
Str,
|
||||
Dict(&'a Layout<'a>, &'a Layout<'a>),
|
||||
Set(&'a Layout<'a>),
|
||||
|
@ -826,6 +839,7 @@ impl<'a> Layout<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
LambdaSet(lambda_set) => lambda_set.runtime_representation().safe_to_memcpy(),
|
||||
RecursivePointer => {
|
||||
// We cannot memcpy pointers, because then we would have the same pointer in multiple places!
|
||||
false
|
||||
|
@ -890,6 +904,9 @@ impl<'a> Layout<'a> {
|
|||
| NonNullableUnwrapped(_) => pointer_size,
|
||||
}
|
||||
}
|
||||
LambdaSet(lambda_set) => lambda_set
|
||||
.runtime_representation()
|
||||
.stack_size_without_alignment(pointer_size),
|
||||
RecursivePointer => pointer_size,
|
||||
}
|
||||
}
|
||||
|
@ -919,6 +936,9 @@ impl<'a> Layout<'a> {
|
|||
| NonNullableUnwrapped(_) => pointer_size,
|
||||
}
|
||||
}
|
||||
Layout::LambdaSet(lambda_set) => lambda_set
|
||||
.runtime_representation()
|
||||
.alignment_bytes(pointer_size),
|
||||
Layout::Builtin(builtin) => builtin.alignment_bytes(pointer_size),
|
||||
Layout::RecursivePointer => pointer_size,
|
||||
}
|
||||
|
@ -929,6 +949,9 @@ impl<'a> Layout<'a> {
|
|||
Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(pointer_size),
|
||||
Layout::Struct(_) => unreachable!("not heap-allocated"),
|
||||
Layout::Union(union_layout) => union_layout.allocation_alignment_bytes(pointer_size),
|
||||
Layout::LambdaSet(lambda_set) => lambda_set
|
||||
.runtime_representation()
|
||||
.allocation_alignment_bytes(pointer_size),
|
||||
Layout::RecursivePointer => unreachable!("should be looked up to get an actual layout"),
|
||||
}
|
||||
}
|
||||
|
@ -979,6 +1002,7 @@ impl<'a> Layout<'a> {
|
|||
| NonNullableUnwrapped(_) => true,
|
||||
}
|
||||
}
|
||||
LambdaSet(lambda_set) => lambda_set.runtime_representation().contains_refcounted(),
|
||||
RecursivePointer => true,
|
||||
}
|
||||
}
|
||||
|
@ -1002,6 +1026,7 @@ impl<'a> Layout<'a> {
|
|||
.append(alloc.text("}"))
|
||||
}
|
||||
Union(union_layout) => union_layout.to_doc(alloc, parens),
|
||||
LambdaSet(lambda_set) => lambda_set.runtime_representation().to_doc(alloc, parens),
|
||||
RecursivePointer => alloc.text("*self"),
|
||||
}
|
||||
}
|
||||
|
@ -1093,7 +1118,6 @@ impl<'a> Builtin<'a> {
|
|||
const F128_SIZE: u32 = 16;
|
||||
const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32;
|
||||
const F32_SIZE: u32 = std::mem::size_of::<f32>() as u32;
|
||||
const F16_SIZE: u32 = 2;
|
||||
|
||||
/// Number of machine words in an empty one of these
|
||||
pub const STR_WORDS: u32 = 2;
|
||||
|
@ -1123,7 +1147,6 @@ impl<'a> Builtin<'a> {
|
|||
Float128 => Builtin::F128_SIZE,
|
||||
Float64 => Builtin::F64_SIZE,
|
||||
Float32 => Builtin::F32_SIZE,
|
||||
Float16 => Builtin::F16_SIZE,
|
||||
Str | EmptyStr => Builtin::STR_WORDS * pointer_size,
|
||||
Dict(_, _) | EmptyDict => Builtin::DICT_WORDS * pointer_size,
|
||||
Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size,
|
||||
|
@ -1150,7 +1173,6 @@ impl<'a> Builtin<'a> {
|
|||
Float128 => align_of::<i128>() as u32,
|
||||
Float64 => align_of::<f64>() as u32,
|
||||
Float32 => align_of::<f32>() as u32,
|
||||
Float16 => align_of::<i16>() as u32,
|
||||
Dict(_, _) | EmptyDict => pointer_size,
|
||||
Set(_) | EmptySet => pointer_size,
|
||||
// we often treat these as i128 (64-bit systems)
|
||||
|
@ -1158,8 +1180,8 @@ impl<'a> Builtin<'a> {
|
|||
//
|
||||
// In webassembly, For that to be safe
|
||||
// they must be aligned to allow such access
|
||||
List(_) | EmptyList => pointer_size.max(8),
|
||||
Str | EmptyStr => pointer_size.max(8),
|
||||
List(_) | EmptyList => pointer_size,
|
||||
Str | EmptyStr => pointer_size,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1168,7 +1190,7 @@ impl<'a> Builtin<'a> {
|
|||
|
||||
match self {
|
||||
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64
|
||||
| Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => true,
|
||||
| Float32 | EmptyStr | EmptyDict | EmptyList | EmptySet => true,
|
||||
Str | Dict(_, _) | Set(_) | List(_) => false,
|
||||
}
|
||||
}
|
||||
|
@ -1179,7 +1201,7 @@ impl<'a> Builtin<'a> {
|
|||
|
||||
match self {
|
||||
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64
|
||||
| Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => false,
|
||||
| Float32 | EmptyStr | EmptyDict | EmptyList | EmptySet => false,
|
||||
List(_) => true,
|
||||
|
||||
Str | Dict(_, _) | Set(_) => true,
|
||||
|
@ -1206,7 +1228,6 @@ impl<'a> Builtin<'a> {
|
|||
Float128 => alloc.text("Float128"),
|
||||
Float64 => alloc.text("Float64"),
|
||||
Float32 => alloc.text("Float32"),
|
||||
Float16 => alloc.text("Float16"),
|
||||
|
||||
EmptyStr => alloc.text("EmptyStr"),
|
||||
EmptyList => alloc.text("EmptyList"),
|
||||
|
@ -1240,8 +1261,7 @@ impl<'a> Builtin<'a> {
|
|||
| Builtin::Decimal
|
||||
| Builtin::Float128
|
||||
| Builtin::Float64
|
||||
| Builtin::Float32
|
||||
| Builtin::Float16 => unreachable!("not heap-allocated"),
|
||||
| Builtin::Float32 => unreachable!("not heap-allocated"),
|
||||
Builtin::Str => pointer_size,
|
||||
Builtin::Dict(k, v) => k
|
||||
.alignment_bytes(pointer_size)
|
||||
|
@ -1360,7 +1380,7 @@ fn layout_from_flat_type<'a>(
|
|||
Func(_, closure_var, _) => {
|
||||
let lambda_set = LambdaSet::from_var(env.arena, env.subs, closure_var, env.ptr_bytes)?;
|
||||
|
||||
Ok(lambda_set.runtime_representation())
|
||||
Ok(Layout::LambdaSet(lambda_set))
|
||||
}
|
||||
Record(fields, ext_var) => {
|
||||
// extract any values from the ext_var
|
||||
|
|
|
@ -33,16 +33,16 @@ pub fn make_tail_recursive<'a>(
|
|||
id: JoinPointId,
|
||||
needle: Symbol,
|
||||
stmt: Stmt<'a>,
|
||||
args: &'a [(Layout<'a>, Symbol)],
|
||||
) -> Stmt<'a> {
|
||||
args: &'a [(Layout<'a>, Symbol, Symbol)],
|
||||
) -> Option<Stmt<'a>> {
|
||||
let allocated = arena.alloc(stmt);
|
||||
match insert_jumps(arena, allocated, id, needle) {
|
||||
None => allocated.clone(),
|
||||
None => None,
|
||||
Some(new) => {
|
||||
// jumps were inserted, we must now add a join point
|
||||
|
||||
let params = Vec::from_iter_in(
|
||||
args.iter().map(|(layout, symbol)| Param {
|
||||
args.iter().map(|(layout, symbol, _)| Param {
|
||||
symbol: *symbol,
|
||||
layout: *layout,
|
||||
borrow: true,
|
||||
|
@ -52,16 +52,18 @@ pub fn make_tail_recursive<'a>(
|
|||
.into_bump_slice();
|
||||
|
||||
// TODO could this be &[]?
|
||||
let args = Vec::from_iter_in(args.iter().map(|t| t.1), arena).into_bump_slice();
|
||||
let args = Vec::from_iter_in(args.iter().map(|t| t.2), arena).into_bump_slice();
|
||||
|
||||
let jump = arena.alloc(Stmt::Jump(id, args));
|
||||
|
||||
Stmt::Join {
|
||||
let join = Stmt::Join {
|
||||
id,
|
||||
remainder: jump,
|
||||
parameters: params,
|
||||
body: new,
|
||||
}
|
||||
};
|
||||
|
||||
Some(join)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1192,7 +1192,7 @@ fn not_found<'b>(
|
|||
alloc.reflow(" missing up-top"),
|
||||
]);
|
||||
|
||||
let default_yes = alloc.reflow("these names seem close though:");
|
||||
let default_yes = alloc.reflow("Did you mean one of these?");
|
||||
|
||||
let to_details = |no_suggestion_details, yes_suggestion_details| {
|
||||
if suggestions.is_empty() {
|
||||
|
@ -1240,7 +1240,7 @@ fn module_not_found<'b>(
|
|||
]);
|
||||
|
||||
let default_yes = alloc
|
||||
.reflow("Is there an import missing? Perhaps there is a typo, these names seem close:");
|
||||
.reflow("Is there an import missing? Perhaps there is a typo. Did you mean one of these?");
|
||||
|
||||
let to_details = |no_suggestion_details, yes_suggestion_details| {
|
||||
if suggestions.is_empty() {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use roc_can::expected::{Expected, PExpected};
|
||||
use roc_collections::all::{Index, MutSet, SendMap};
|
||||
use roc_module::ident::{IdentStr, Lowercase, TagName};
|
||||
use roc_module::ident::{Ident, IdentStr, Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Located, Region};
|
||||
use roc_solve::solve;
|
||||
use roc_types::pretty_print::Parens;
|
||||
use roc_types::types::{Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt};
|
||||
|
@ -10,34 +11,39 @@ use std::path::PathBuf;
|
|||
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
const DUPLICATE_NAME: &str = "DUPLICATE NAME";
|
||||
const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#;
|
||||
|
||||
pub fn type_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
problem: solve::TypeError,
|
||||
) -> Report<'b> {
|
||||
) -> Option<Report<'b>> {
|
||||
use solve::TypeError::*;
|
||||
|
||||
fn report(title: String, doc: RocDocBuilder<'_>, filename: PathBuf) -> Report<'_> {
|
||||
Report {
|
||||
fn report(title: String, doc: RocDocBuilder<'_>, filename: PathBuf) -> Option<Report<'_>> {
|
||||
Some(Report {
|
||||
title,
|
||||
filename,
|
||||
doc,
|
||||
severity: Severity::RuntimeError,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
match problem {
|
||||
BadExpr(region, category, found, expected) => {
|
||||
to_expr_report(alloc, filename, region, category, found, expected)
|
||||
}
|
||||
BadPattern(region, category, found, expected) => {
|
||||
to_pattern_report(alloc, filename, region, category, found, expected)
|
||||
}
|
||||
CircularType(region, symbol, overall_type) => {
|
||||
to_circular_report(alloc, filename, region, symbol, overall_type)
|
||||
}
|
||||
BadExpr(region, category, found, expected) => Some(to_expr_report(
|
||||
alloc, filename, region, category, found, expected,
|
||||
)),
|
||||
BadPattern(region, category, found, expected) => Some(to_pattern_report(
|
||||
alloc, filename, region, category, found, expected,
|
||||
)),
|
||||
CircularType(region, symbol, overall_type) => Some(to_circular_report(
|
||||
alloc,
|
||||
filename,
|
||||
region,
|
||||
symbol,
|
||||
overall_type,
|
||||
)),
|
||||
UnexposedLookup(symbol) => {
|
||||
let title = "UNRECOGNIZED NAME".to_string();
|
||||
let doc = alloc
|
||||
|
@ -97,12 +103,40 @@ pub fn type_problem<'b>(
|
|||
report(title, doc, filename)
|
||||
}
|
||||
|
||||
SolvedTypeError => None, // Don't re-report cascading errors - see https://github.com/rtfeldman/roc/pull/1711
|
||||
|
||||
Shadowed(original_region, shadow) => {
|
||||
let doc = report_shadowing(alloc, original_region, shadow);
|
||||
let title = DUPLICATE_NAME.to_string();
|
||||
|
||||
report(title, doc, filename)
|
||||
}
|
||||
|
||||
other => panic!("unhandled bad type: {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn report_shadowing<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
original_region: Region,
|
||||
shadow: Located<Ident>,
|
||||
) -> RocDocBuilder<'b> {
|
||||
let line = r#"Since these types have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#;
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.text("The ")
|
||||
.append(alloc.ident(shadow.value))
|
||||
.append(alloc.reflow(" name is first defined here:")),
|
||||
alloc.region(original_region),
|
||||
alloc.reflow("But then it's defined a second time here:"),
|
||||
alloc.region(shadow.region),
|
||||
alloc.reflow(line),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn cyclic_alias<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
symbol: Symbol,
|
||||
|
|
|
@ -128,6 +128,7 @@ impl<'b> Report<'b> {
|
|||
pub struct Palette<'a> {
|
||||
pub primary: &'a str,
|
||||
pub code_block: &'a str,
|
||||
pub keyword: &'a str,
|
||||
pub variable: &'a str,
|
||||
pub type_variable: &'a str,
|
||||
pub structure: &'a str,
|
||||
|
@ -146,6 +147,7 @@ pub struct Palette<'a> {
|
|||
pub const DEFAULT_PALETTE: Palette = Palette {
|
||||
primary: WHITE_CODE,
|
||||
code_block: WHITE_CODE,
|
||||
keyword: GREEN_CODE,
|
||||
variable: BLUE_CODE,
|
||||
type_variable: YELLOW_CODE,
|
||||
structure: GREEN_CODE,
|
||||
|
@ -810,6 +812,9 @@ where
|
|||
Symbol => {
|
||||
self.write_str(self.palette.variable)?;
|
||||
}
|
||||
Keyword => {
|
||||
self.write_str(self.palette.keyword)?;
|
||||
}
|
||||
GutterBar => {
|
||||
self.write_str(self.palette.gutter_bar)?;
|
||||
}
|
||||
|
@ -837,7 +842,7 @@ where
|
|||
ParserSuggestion => {
|
||||
self.write_str(self.palette.parser_suggestion)?;
|
||||
}
|
||||
TypeBlock | GlobalTag | PrivateTag | RecordField | Keyword => { /* nothing yet */ }
|
||||
TypeBlock | GlobalTag | PrivateTag | RecordField => { /* nothing yet */ }
|
||||
}
|
||||
self.style_stack.push(*annotation);
|
||||
Ok(())
|
||||
|
@ -851,11 +856,11 @@ where
|
|||
Some(annotation) => match annotation {
|
||||
Emphasized | Url | TypeVariable | Alias | Symbol | BinOp | Error | GutterBar
|
||||
| Typo | TypoSuggestion | ParserSuggestion | Structure | CodeBlock | PlainText
|
||||
| LineNumber | Tip | Module | Header => {
|
||||
| LineNumber | Tip | Module | Header | Keyword => {
|
||||
self.write_str(RESET_CODE)?;
|
||||
}
|
||||
|
||||
TypeBlock | GlobalTag | PrivateTag | RecordField | Keyword => { /* nothing yet */ }
|
||||
TypeBlock | GlobalTag | PrivateTag | RecordField => { /* nothing yet */ }
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -154,9 +154,10 @@ mod test_reporting {
|
|||
}
|
||||
|
||||
for problem in type_problems {
|
||||
let report = type_problem(&alloc, filename.clone(), problem.clone());
|
||||
if let Some(report) = type_problem(&alloc, filename.clone(), problem.clone()) {
|
||||
reports.push(report);
|
||||
}
|
||||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, filename.clone(), problem.clone());
|
||||
|
@ -541,7 +542,7 @@ mod test_reporting {
|
|||
8│ 4 -> bar baz "yay"
|
||||
^^^
|
||||
|
||||
these names seem close though:
|
||||
Did you mean one of these?
|
||||
|
||||
baz
|
||||
Nat
|
||||
|
@ -739,7 +740,7 @@ mod test_reporting {
|
|||
<cyan>3<reset><cyan>│<reset> <white>theAdmin<reset>
|
||||
<red>^^^^^^^^<reset>
|
||||
|
||||
these names seem close though:
|
||||
Did you mean one of these?
|
||||
|
||||
Decimal
|
||||
Dec
|
||||
|
@ -1491,7 +1492,7 @@ mod test_reporting {
|
|||
2│ { foo: 2 } -> foo
|
||||
^^^
|
||||
|
||||
these names seem close though:
|
||||
Did you mean one of these?
|
||||
|
||||
Bool
|
||||
U8
|
||||
|
@ -1947,7 +1948,7 @@ mod test_reporting {
|
|||
2│ f = \_ -> ok 4
|
||||
^^
|
||||
|
||||
these names seem close though:
|
||||
Did you mean one of these?
|
||||
|
||||
U8
|
||||
f
|
||||
|
@ -3634,8 +3635,8 @@ mod test_reporting {
|
|||
1│ Foo.test
|
||||
^^^^^^^^
|
||||
|
||||
Is there an import missing? Perhaps there is a typo, these names seem
|
||||
close:
|
||||
Is there an import missing? Perhaps there is a typo. Did you mean one
|
||||
of these?
|
||||
|
||||
Bool
|
||||
Num
|
||||
|
@ -5797,7 +5798,7 @@ mod test_reporting {
|
|||
1│ [ "foo", bar("") ]
|
||||
^^^
|
||||
|
||||
these names seem close though:
|
||||
Did you mean one of these?
|
||||
|
||||
Nat
|
||||
Str
|
||||
|
|
|
@ -2017,3 +2017,17 @@ fn lists_with_incompatible_type_param_in_if() {
|
|||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_with_index_multi_record() {
|
||||
// see https://github.com/rtfeldman/roc/issues/1700
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
List.mapWithIndex [ { x: {}, y: {} } ] \_, _ -> {}
|
||||
"#
|
||||
),
|
||||
RocList::from_slice(&[((), ())]),
|
||||
RocList<((), ())>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1778,4 +1778,48 @@ mod gen_num {
|
|||
u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_on_i32() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
x : I32
|
||||
x = 0
|
||||
|
||||
main : I32
|
||||
main =
|
||||
when x is
|
||||
0 -> 42
|
||||
_ -> -1
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_on_i16() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
x : I16
|
||||
x = 0
|
||||
|
||||
main : I16
|
||||
main =
|
||||
when x is
|
||||
0 -> 42
|
||||
_ -> -1
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i16
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2531,6 +2531,8 @@ fn pattern_match_unit_tag() {
|
|||
);
|
||||
}
|
||||
|
||||
// see for why this is disabled on wasm32 https://github.com/rtfeldman/roc/issues/1687
|
||||
#[cfg(not(feature = "wasm-cli-run"))]
|
||||
#[test]
|
||||
fn mirror_llvm_alignment_padding() {
|
||||
// see https://github.com/rtfeldman/roc/issues/1569
|
||||
|
@ -2616,7 +2618,8 @@ fn lambda_set_struct_byte() {
|
|||
r = Red
|
||||
|
||||
p1 = (\u -> r == u)
|
||||
oneOfResult = List.map [p1, p1] (\p -> p Green)
|
||||
foobarbaz = (\p -> p Green)
|
||||
oneOfResult = List.map [p1, p1] foobarbaz
|
||||
|
||||
when oneOfResult is
|
||||
_ -> 32
|
||||
|
@ -2779,3 +2782,127 @@ fn value_not_exposed_hits_panic() {
|
|||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mix_function_and_closure() {
|
||||
// see https://github.com/rtfeldman/roc/pull/1706
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
# foo does not capture any variables
|
||||
# but through unification will get a lambda set that does store information
|
||||
# we must handle that correctly
|
||||
foo = \x -> x
|
||||
|
||||
bar = \y -> \_ -> y
|
||||
|
||||
main : Str
|
||||
main =
|
||||
(if 1 == 1 then foo else (bar "nope nope nope")) "hello world"
|
||||
"#
|
||||
),
|
||||
RocStr::from_slice(b"hello world"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mix_function_and_closure_level_of_indirection() {
|
||||
// see https://github.com/rtfeldman/roc/pull/1706
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
foo = \x -> x
|
||||
|
||||
bar = \y -> \_ -> y
|
||||
|
||||
f = (if 1 == 1 then foo else (bar "nope nope nope"))
|
||||
|
||||
main : Str
|
||||
main =
|
||||
f "hello world"
|
||||
"#
|
||||
),
|
||||
RocStr::from_slice(b"hello world"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_pass_bool_byte_closure_layout() {
|
||||
// see https://github.com/rtfeldman/roc/pull/1706
|
||||
// the distinction is actually important, dropping that info means some functions just get
|
||||
// skipped
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
## PARSER
|
||||
|
||||
Parser a : List U8 -> List [Pair a (List U8)]
|
||||
|
||||
|
||||
## ANY
|
||||
|
||||
# If succcessful, the any parser consumes one character
|
||||
|
||||
any: Parser U8
|
||||
any = \inp ->
|
||||
when List.first inp is
|
||||
Ok u -> [Pair u (List.drop inp 1)]
|
||||
_ -> [ ]
|
||||
|
||||
|
||||
|
||||
## SATISFY
|
||||
|
||||
satisfy : (U8 -> Bool) -> Parser U8
|
||||
satisfy = \predicate ->
|
||||
\input ->
|
||||
walker = \(Pair u rest), accum ->
|
||||
if predicate u then
|
||||
Stop [ Pair u rest ]
|
||||
|
||||
else
|
||||
Stop accum
|
||||
|
||||
List.walkUntil (any input) walker []
|
||||
|
||||
|
||||
|
||||
oneOf : List (Parser a) -> Parser a
|
||||
oneOf = \parserList ->
|
||||
\input ->
|
||||
walker = \p, accum ->
|
||||
output = p input
|
||||
if List.len output == 1 then
|
||||
Stop output
|
||||
|
||||
else
|
||||
Continue accum
|
||||
|
||||
List.walkUntil parserList walker []
|
||||
|
||||
|
||||
satisfyA = satisfy (\u -> u == 97) # recognize 97
|
||||
satisfyB = satisfy (\u -> u == 98) # recognize 98
|
||||
|
||||
test1 = if List.len ((oneOf [satisfyA, satisfyB]) [97, 98, 99, 100] ) == 1 then "PASS" else "FAIL"
|
||||
test2 = if List.len ((oneOf [satisfyA, satisfyB]) [98, 99, 100, 97] ) == 1 then "PASS" else "FAIL"
|
||||
test3 = if List.len ((oneOf [satisfyB , satisfyA]) [98, 99, 100, 97] ) == 1 then "PASS" else "FAIL"
|
||||
test4 = if List.len ((oneOf [satisfyA, satisfyB]) [99, 100, 101] ) == 0 then "PASS" else "FAIL"
|
||||
|
||||
|
||||
main : Str
|
||||
main = [test1, test2, test3, test4] |> Str.joinWith ", "
|
||||
"#
|
||||
),
|
||||
RocStr::from_slice(b"PASS, PASS, PASS, PASS"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
|
|
@ -889,3 +889,26 @@ fn blue_and_absent() {
|
|||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_the_only_field() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Model : { foo : I64 }
|
||||
|
||||
model : Model
|
||||
model = { foo: 3 }
|
||||
|
||||
foo = 4
|
||||
|
||||
newModel : Model
|
||||
newModel = { model & foo }
|
||||
|
||||
newModel.foo
|
||||
"#
|
||||
),
|
||||
4,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
|
|
@ -143,13 +143,14 @@ fn create_llvm_module<'a>(
|
|||
}
|
||||
|
||||
for problem in type_problems {
|
||||
let report = type_problem(&alloc, module_path.clone(), problem);
|
||||
if let Some(report) = type_problem(&alloc, module_path.clone(), problem) {
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, module_path.clone(), problem);
|
||||
|
|
|
@ -6,7 +6,7 @@ procedure Num.26 (#Attr.2, #Attr.3):
|
|||
let Test.12 = lowlevel NumMul #Attr.2 #Attr.3;
|
||||
ret Test.12;
|
||||
|
||||
procedure Test.1 (Test.2, Test.3):
|
||||
procedure Test.1 (Test.17, Test.18):
|
||||
joinpoint Test.7 Test.2 Test.3:
|
||||
let Test.15 = 0i64;
|
||||
let Test.16 = lowlevel Eq Test.15 Test.2;
|
||||
|
@ -18,7 +18,7 @@ procedure Test.1 (Test.2, Test.3):
|
|||
let Test.11 = CallByName Num.26 Test.2 Test.3;
|
||||
jump Test.7 Test.10 Test.11;
|
||||
in
|
||||
jump Test.7 Test.2 Test.3;
|
||||
jump Test.7 Test.17 Test.18;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.5 = 10i64;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
procedure Test.3 (Test.4):
|
||||
procedure Test.3 (Test.29):
|
||||
joinpoint Test.13 Test.4:
|
||||
let Test.23 = 1i64;
|
||||
let Test.24 = GetTagId Test.4;
|
||||
|
@ -18,7 +18,7 @@ procedure Test.3 (Test.4):
|
|||
let Test.7 = UnionAtIndex (Id 0) (Index 1) Test.4;
|
||||
jump Test.13 Test.7;
|
||||
in
|
||||
jump Test.13 Test.4;
|
||||
jump Test.13 Test.29;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.28 = 3i64;
|
||||
|
|
|
@ -10,7 +10,7 @@ procedure Num.27 (#Attr.2, #Attr.3):
|
|||
let Test.26 = lowlevel NumLt #Attr.2 #Attr.3;
|
||||
ret Test.26;
|
||||
|
||||
procedure Test.1 (Test.2, Test.3, Test.4):
|
||||
procedure Test.1 (Test.27, Test.28, Test.29):
|
||||
joinpoint Test.12 Test.2 Test.3 Test.4:
|
||||
let Test.14 = CallByName Num.27 Test.3 Test.4;
|
||||
if Test.14 then
|
||||
|
@ -29,7 +29,7 @@ procedure Test.1 (Test.2, Test.3, Test.4):
|
|||
else
|
||||
ret Test.2;
|
||||
in
|
||||
jump Test.12 Test.2 Test.3 Test.4;
|
||||
jump Test.12 Test.27 Test.28 Test.29;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.9 = Array [];
|
||||
|
|
|
@ -1,43 +1,53 @@
|
|||
procedure Num.24 (#Attr.2, #Attr.3):
|
||||
let Test.24 = lowlevel NumAdd #Attr.2 #Attr.3;
|
||||
ret Test.24;
|
||||
let Test.27 = lowlevel NumAdd #Attr.2 #Attr.3;
|
||||
ret Test.27;
|
||||
|
||||
procedure Num.26 (#Attr.2, #Attr.3):
|
||||
let Test.19 = lowlevel NumMul #Attr.2 #Attr.3;
|
||||
ret Test.19;
|
||||
|
||||
procedure Test.1 ():
|
||||
let Test.25 = 1i64;
|
||||
ret Test.25;
|
||||
|
||||
procedure Test.2 ():
|
||||
let Test.20 = 2i64;
|
||||
ret Test.20;
|
||||
|
||||
procedure Test.3 (Test.6):
|
||||
let Test.23 = CallByName Test.1;
|
||||
let Test.22 = CallByName Num.24 Test.6 Test.23;
|
||||
let Test.22 = lowlevel NumMul #Attr.2 #Attr.3;
|
||||
ret Test.22;
|
||||
|
||||
procedure Test.1 ():
|
||||
let Test.28 = 1i64;
|
||||
ret Test.28;
|
||||
|
||||
procedure Test.2 ():
|
||||
let Test.23 = 2i64;
|
||||
ret Test.23;
|
||||
|
||||
procedure Test.3 (Test.6):
|
||||
let Test.26 = CallByName Test.1;
|
||||
let Test.25 = CallByName Num.24 Test.6 Test.26;
|
||||
ret Test.25;
|
||||
|
||||
procedure Test.4 (Test.7):
|
||||
let Test.18 = CallByName Test.2;
|
||||
let Test.17 = CallByName Num.26 Test.7 Test.18;
|
||||
ret Test.17;
|
||||
let Test.21 = CallByName Test.2;
|
||||
let Test.20 = CallByName Num.26 Test.7 Test.21;
|
||||
ret Test.20;
|
||||
|
||||
procedure Test.5 (Test.8, Test.9):
|
||||
let Test.14 = CallByName Test.3 Test.9;
|
||||
joinpoint Test.15 Test.14:
|
||||
ret Test.14;
|
||||
in
|
||||
switch Test.8:
|
||||
case 0:
|
||||
let Test.16 = CallByName Test.3 Test.9;
|
||||
jump Test.15 Test.16;
|
||||
|
||||
default:
|
||||
let Test.17 = CallByName Test.4 Test.9;
|
||||
jump Test.15 Test.17;
|
||||
|
||||
|
||||
procedure Test.0 ():
|
||||
joinpoint Test.16 Test.12:
|
||||
joinpoint Test.19 Test.12:
|
||||
let Test.13 = 42i64;
|
||||
let Test.11 = CallByName Test.5 Test.12 Test.13;
|
||||
ret Test.11;
|
||||
in
|
||||
let Test.21 = true;
|
||||
if Test.21 then
|
||||
let Test.3 = Struct {};
|
||||
jump Test.16 Test.3;
|
||||
let Test.24 = true;
|
||||
if Test.24 then
|
||||
let Test.3 = false;
|
||||
jump Test.19 Test.3;
|
||||
else
|
||||
let Test.4 = Struct {};
|
||||
jump Test.16 Test.4;
|
||||
let Test.4 = true;
|
||||
jump Test.19 Test.4;
|
||||
|
|
|
@ -226,14 +226,6 @@ pub fn print_err(err: &EdError) {
|
|||
}
|
||||
}
|
||||
|
||||
/*pub fn print_ui_err(err: &UIError) {
|
||||
eprintln!("{}", format!("{}", err).truecolor(255, 0, 0));
|
||||
|
||||
if let Some(backtrace) = ErrorCompat::backtrace(err) {
|
||||
eprintln!("{}", color_backtrace(backtrace));
|
||||
}
|
||||
}*/
|
||||
|
||||
fn color_backtrace(backtrace: &snafu::Backtrace) -> String {
|
||||
let backtrace_str = format!("{}", backtrace);
|
||||
let backtrace_split = backtrace_str.split('\n');
|
||||
|
|
4
examples/.gitignore
vendored
4
examples/.gitignore
vendored
|
@ -4,3 +4,7 @@ app
|
|||
libhost.a
|
||||
roc_app.ll
|
||||
roc_app.bc
|
||||
dynhost
|
||||
preprocessedhost
|
||||
metadata
|
||||
libapp.so
|
|
@ -23,16 +23,19 @@ comptime {
|
|||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
extern fn roc__mainForHost_1_exposed([*]u8) void;
|
||||
extern fn roc__mainForHost_1_exposed_generic([*]u8) void;
|
||||
extern fn roc__mainForHost_size() i64;
|
||||
extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void;
|
||||
extern fn roc__mainForHost_1_Fx_size() i64;
|
||||
extern fn roc__mainForHost_1_Fx_result_size() i64;
|
||||
|
||||
const Align = extern struct { a: usize, b: usize };
|
||||
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
|
||||
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void;
|
||||
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
|
||||
|
||||
const Align = 2 * @alignOf(usize);
|
||||
extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void;
|
||||
extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*c_void;
|
||||
extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void;
|
||||
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
|
||||
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
|
||||
|
||||
const DEBUG: bool = false;
|
||||
|
||||
|
@ -53,7 +56,7 @@ export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignmen
|
|||
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);
|
||||
return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size);
|
||||
}
|
||||
|
||||
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
|
||||
|
@ -62,7 +65,7 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
|
|||
stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
|
||||
}
|
||||
|
||||
free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)));
|
||||
free(@alignCast(Align, @ptrCast([*]u8, c_ptr)));
|
||||
}
|
||||
|
||||
export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
||||
|
@ -74,37 +77,35 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
|||
std.process.exit(0);
|
||||
}
|
||||
|
||||
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{
|
||||
return memcpy(dst, src, size);
|
||||
}
|
||||
|
||||
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{
|
||||
return memset(dst, value, size);
|
||||
}
|
||||
|
||||
const Unit = extern struct {};
|
||||
|
||||
pub fn main() u8 {
|
||||
pub export fn main() callconv(.C) u8 {
|
||||
const allocator = std.heap.page_allocator;
|
||||
|
||||
const size = @intCast(usize, roc__mainForHost_size());
|
||||
const raw_output = std.heap.c_allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
|
||||
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
|
||||
var output = @ptrCast([*]u8, raw_output);
|
||||
|
||||
defer {
|
||||
std.heap.c_allocator.free(raw_output);
|
||||
allocator.free(raw_output);
|
||||
}
|
||||
|
||||
var ts1: std.os.timespec = undefined;
|
||||
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
|
||||
|
||||
roc__mainForHost_1_exposed(output);
|
||||
roc__mainForHost_1_exposed_generic(output);
|
||||
|
||||
const flag = @ptrCast(*u64, @alignCast(@alignOf(u64), output)).*;
|
||||
|
||||
if (flag == 0) {
|
||||
// all is well
|
||||
const closure_data_pointer = @ptrCast([*]u8, output[@sizeOf(u64)..size]);
|
||||
const closure_data_pointer = @ptrCast([*]u8, output);
|
||||
|
||||
call_the_closure(closure_data_pointer);
|
||||
} else {
|
||||
const ptr = @ptrCast(*u32, output + @sizeOf(u64));
|
||||
const msg = @intToPtr([*:0]const u8, ptr.*);
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
var ts2: std.os.timespec = undefined;
|
||||
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
|
||||
|
@ -122,12 +123,14 @@ fn to_seconds(tms: std.os.timespec) f64 {
|
|||
}
|
||||
|
||||
fn call_the_closure(closure_data_pointer: [*]u8) void {
|
||||
const allocator = std.heap.page_allocator;
|
||||
|
||||
const size = roc__mainForHost_1_Fx_result_size();
|
||||
const raw_output = std.heap.c_allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
|
||||
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
|
||||
var output = @ptrCast([*]u8, raw_output);
|
||||
|
||||
defer {
|
||||
std.heap.c_allocator.free(raw_output);
|
||||
allocator.free(raw_output);
|
||||
}
|
||||
|
||||
const flags: u8 = 0;
|
||||
|
|
|
@ -4,7 +4,7 @@ use core::alloc::Layout;
|
|||
use core::ffi::c_void;
|
||||
use core::mem::MaybeUninit;
|
||||
use libc;
|
||||
use roc_std::{RocCallResult, RocStr};
|
||||
use roc_std::RocStr;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
|
@ -70,23 +70,11 @@ pub fn rust_main() -> isize {
|
|||
|
||||
roc_main(buffer);
|
||||
|
||||
let output = buffer as *mut RocCallResult<()>;
|
||||
|
||||
match (&*output).into() {
|
||||
Ok(()) => {
|
||||
let closure_data_ptr = buffer.offset(8);
|
||||
let result = call_the_closure(closure_data_ptr as *const u8);
|
||||
let result = call_the_closure(buffer);
|
||||
|
||||
std::alloc::dealloc(buffer, layout);
|
||||
|
||||
result
|
||||
}
|
||||
Err(msg) => {
|
||||
std::alloc::dealloc(buffer, layout);
|
||||
|
||||
panic!("Roc failed with message: {}", msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Exit code
|
||||
|
@ -105,15 +93,9 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
|
|||
buffer as *mut u8,
|
||||
);
|
||||
|
||||
let output = &*(buffer as *mut RocCallResult<()>);
|
||||
|
||||
match output.into() {
|
||||
Ok(_) => {
|
||||
std::alloc::dealloc(buffer, layout);
|
||||
|
||||
0
|
||||
}
|
||||
Err(e) => panic!("failed with {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
|
|
@ -5,4 +5,7 @@ app "effect-example"
|
|||
|
||||
main : Effect.Effect {}
|
||||
main =
|
||||
Effect.after Effect.getLine \lineThisThing -> Effect.putLine lineThisThing
|
||||
Effect.after (Effect.getLine) \line ->
|
||||
Effect.after (Effect.putLine "You entered: \(line)") \{} ->
|
||||
Effect.after (Effect.putLine "It is known") \{} ->
|
||||
Effect.always {}
|
||||
|
|
|
@ -32,6 +32,8 @@ extern fn roc__mainForHost_1_Fx_result_size() i64;
|
|||
extern fn malloc(size: usize) callconv(.C) ?*c_void;
|
||||
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
|
||||
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void;
|
||||
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
|
||||
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
|
||||
|
||||
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
|
||||
return malloc(size);
|
||||
|
@ -52,18 +54,29 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
|||
std.process.exit(0);
|
||||
}
|
||||
|
||||
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{
|
||||
return memcpy(dst, src, size);
|
||||
}
|
||||
|
||||
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{
|
||||
return memset(dst, value, size);
|
||||
}
|
||||
|
||||
const Unit = extern struct {};
|
||||
|
||||
pub export fn main() u8 {
|
||||
const allocator = std.heap.page_allocator;
|
||||
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
|
||||
const size = @intCast(usize, roc__mainForHost_size());
|
||||
const raw_output = std.heap.c_allocator.alloc(u8, size) catch unreachable;
|
||||
// NOTE the return size can be zero, which will segfault. Always allocate at least 8 bytes
|
||||
const size = std.math.max(8, @intCast(usize, roc__mainForHost_size()));
|
||||
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
|
||||
var output = @ptrCast([*]u8, raw_output);
|
||||
|
||||
defer {
|
||||
std.heap.c_allocator.free(raw_output);
|
||||
allocator.free(raw_output);
|
||||
}
|
||||
|
||||
var ts1: std.os.timespec = undefined;
|
||||
|
@ -71,21 +84,7 @@ pub export fn main() u8 {
|
|||
|
||||
roc__mainForHost_1_exposed(output);
|
||||
|
||||
const elements = @ptrCast([*]u64, @alignCast(8, output));
|
||||
|
||||
var flag = elements[0];
|
||||
|
||||
if (flag == 0) {
|
||||
// all is well
|
||||
const closure_data_pointer = @ptrCast([*]u8, output[8..size]);
|
||||
|
||||
call_the_closure(closure_data_pointer);
|
||||
} else {
|
||||
const msg = @intToPtr([*:0]const u8, elements[1]);
|
||||
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
|
||||
|
||||
return 0;
|
||||
}
|
||||
call_the_closure(output);
|
||||
|
||||
var ts2: std.os.timespec = undefined;
|
||||
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
|
||||
|
@ -102,27 +101,20 @@ fn to_seconds(tms: std.os.timespec) f64 {
|
|||
}
|
||||
|
||||
fn call_the_closure(closure_data_pointer: [*]u8) void {
|
||||
const allocator = std.heap.page_allocator;
|
||||
|
||||
const size = roc__mainForHost_1_Fx_result_size();
|
||||
const raw_output = std.heap.c_allocator.alloc(u8, @intCast(usize, size)) catch unreachable;
|
||||
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
|
||||
var output = @ptrCast([*]u8, raw_output);
|
||||
|
||||
defer {
|
||||
std.heap.c_allocator.free(raw_output);
|
||||
allocator.free(raw_output);
|
||||
}
|
||||
|
||||
const flags: u8 = 0;
|
||||
|
||||
roc__mainForHost_1_Fx_caller(&flags, closure_data_pointer, output);
|
||||
|
||||
const elements = @ptrCast([*]u64, @alignCast(8, output));
|
||||
|
||||
var flag = elements[0];
|
||||
|
||||
if (flag == 0) {
|
||||
return;
|
||||
} else {
|
||||
unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
pub export fn roc_fx_getLine() str.RocStr {
|
||||
|
|
2
examples/fib/.gitignore
vendored
Normal file
2
examples/fib/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
add
|
||||
fib
|
15
examples/fib/Fib.roc
Normal file
15
examples/fib/Fib.roc
Normal file
|
@ -0,0 +1,15 @@
|
|||
app "fib"
|
||||
packages { base: "platform" }
|
||||
imports []
|
||||
provides [ main ] to base
|
||||
|
||||
main = \n -> fib n 0 1
|
||||
|
||||
|
||||
# the clever implementation requires join points
|
||||
fib = \n, a, b ->
|
||||
if n == 0 then
|
||||
a
|
||||
|
||||
else
|
||||
fib (n - 1) b (a + b)
|
10
examples/fib/platform/Package-Config.roc
Normal file
10
examples/fib/platform/Package-Config.roc
Normal file
|
@ -0,0 +1,10 @@
|
|||
platform examples/add
|
||||
requires {}{ main : I64 -> I64 }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
effects fx.Effect {}
|
||||
|
||||
mainForHost : I64 -> I64
|
||||
mainForHost = \a -> main a
|
97
examples/fib/platform/host.zig
Normal file
97
examples/fib/platform/host.zig
Normal file
|
@ -0,0 +1,97 @@
|
|||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const expectEqual = testing.expectEqual;
|
||||
const expect = testing.expect;
|
||||
|
||||
comptime {
|
||||
// This is a workaround for https://github.com/ziglang/zig/issues/8218
|
||||
// which is only necessary on macOS.
|
||||
//
|
||||
// Once that issue is fixed, we can undo the changes in
|
||||
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
|
||||
// -fcompiler-rt in link.rs instead of doing this. Note that this
|
||||
// workaround is present in many host.zig files, so make sure to undo
|
||||
// it everywhere!
|
||||
if (std.builtin.os.tag == .macos) {
|
||||
_ = @import("compiler_rt");
|
||||
}
|
||||
}
|
||||
|
||||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
// NOTE the LLVM backend expects this signature
|
||||
// extern fn roc__mainForHost_1_exposed(i64, *i64) void;
|
||||
extern fn roc__mainForHost_1_exposed(i64) i64;
|
||||
|
||||
const Align = extern struct { a: usize, b: usize };
|
||||
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
|
||||
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void;
|
||||
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
|
||||
|
||||
const DEBUG: bool = false;
|
||||
|
||||
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}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
|
||||
return ptr;
|
||||
} else {
|
||||
return malloc(size);
|
||||
}
|
||||
}
|
||||
|
||||
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
|
||||
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);
|
||||
}
|
||||
|
||||
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
|
||||
if (DEBUG) {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
|
||||
}
|
||||
|
||||
free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)));
|
||||
}
|
||||
|
||||
export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
||||
_ = tag_id;
|
||||
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
const msg = @ptrCast([*:0]const u8, c_ptr);
|
||||
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
|
||||
std.process.exit(0);
|
||||
}
|
||||
|
||||
pub export fn main() u8 {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
|
||||
// start time
|
||||
var ts1: std.os.timespec = undefined;
|
||||
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
|
||||
|
||||
const result = roc__mainForHost_1_exposed(10);
|
||||
|
||||
stdout.print("{d}\n", .{result}) catch unreachable;
|
||||
|
||||
// end time
|
||||
var ts2: std.os.timespec = undefined;
|
||||
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
|
||||
|
||||
const delta = to_seconds(ts2) - to_seconds(ts1);
|
||||
|
||||
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn to_seconds(tms: std.os.timespec) f64 {
|
||||
return @intToFloat(f64, tms.tv_sec) + (@intToFloat(f64, tms.tv_nsec) / 1_000_000_000.0);
|
||||
}
|
|
@ -1,7 +1,12 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
extern int rust_main();
|
||||
|
||||
int main() {
|
||||
return rust_main();
|
||||
int main() { return rust_main(); }
|
||||
|
||||
void *roc_memcpy(void *dest, const void *src, size_t n) {
|
||||
return memcpy(dest, src, n);
|
||||
}
|
||||
|
||||
void *roc_memset(void *str, int c, size_t n) { return memset(str, c, n); }
|
|
@ -3,12 +3,12 @@
|
|||
use core::ffi::c_void;
|
||||
use core::mem::MaybeUninit;
|
||||
use libc::c_char;
|
||||
use roc_std::{RocCallResult, RocStr};
|
||||
use roc_std::RocStr;
|
||||
use std::ffi::CStr;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "roc__mainForHost_1_exposed"]
|
||||
fn roc_main(output: *mut RocCallResult<RocStr>) -> ();
|
||||
fn roc_main() -> RocStr;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -46,15 +46,9 @@ pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
|
|||
|
||||
#[no_mangle]
|
||||
pub fn rust_main() -> isize {
|
||||
let mut call_result: MaybeUninit<RocCallResult<RocStr>> = MaybeUninit::uninit();
|
||||
|
||||
unsafe {
|
||||
roc_main(call_result.as_mut_ptr());
|
||||
let roc_str = roc_main();
|
||||
|
||||
let output = call_result.assume_init();
|
||||
|
||||
match output.into() {
|
||||
Ok(roc_str) => {
|
||||
let len = roc_str.len();
|
||||
let str_bytes = roc_str.get_bytes() as *const libc::c_void;
|
||||
|
||||
|
@ -62,11 +56,6 @@ pub fn rust_main() -> isize {
|
|||
panic!("Writing to stdout failed!");
|
||||
}
|
||||
}
|
||||
Err(msg) => {
|
||||
panic!("Roc failed with message: {}", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit code
|
||||
0
|
||||
|
|
2
examples/hello-web/.gitignore
vendored
Normal file
2
examples/hello-web/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
hello-web
|
||||
*.wat
|
12
examples/hello-web/Hello.roc
Normal file
12
examples/hello-web/Hello.roc
Normal file
|
@ -0,0 +1,12 @@
|
|||
app "hello-web"
|
||||
packages { base: "platform" }
|
||||
imports []
|
||||
provides [ main ] to base
|
||||
|
||||
greeting =
|
||||
hi = "Hello"
|
||||
name = "World"
|
||||
|
||||
"\(hi), \(name)!"
|
||||
|
||||
main = greeting
|
49
examples/hello-web/README.md
Normal file
49
examples/hello-web/README.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
# Hello, World!
|
||||
|
||||
To run, go to the project home directory and run:
|
||||
|
||||
```bash
|
||||
$ cargo run -- build --backend=wasm32 examples/hello-web/Hello.roc
|
||||
```
|
||||
|
||||
Then `cd` into the example directory and run any web server that can handle WebAssembly.
|
||||
For example with `http-server`:
|
||||
|
||||
```bash
|
||||
cd examples/hello-web
|
||||
npm install -g http-server
|
||||
http-server
|
||||
```
|
||||
|
||||
Now open your browser at http://localhost:8080
|
||||
|
||||
## Design Notes
|
||||
|
||||
This demonstrates the basic design of hosts: Roc code gets compiled into a pure
|
||||
function (in this case, a thunk that always returns `"Hello, World!"`) and
|
||||
then the host calls that function. Fundamentally, that's the whole idea! The host
|
||||
might not even have a `main` - it could be a library, a plugin, anything.
|
||||
Everything else is built on this basic "hosts calling linked pure functions" design.
|
||||
|
||||
For example, things get more interesting when the compiled Roc function returns
|
||||
a `Task` - that is, a tagged union data structure containing function pointers
|
||||
to callback closures. This lets the Roc pure function describe arbitrary
|
||||
chainable effects, which the host can interpret to perform I/O as requested by
|
||||
the Roc program. (The tagged union `Task` would have a variant for each supported
|
||||
I/O operation.)
|
||||
|
||||
In this trivial example, it's very easy to line up the API between the host and
|
||||
the Roc program. In a more involved host, this would be much trickier - especially
|
||||
if the API were changing frequently during development.
|
||||
|
||||
The idea there is to have a first-class concept of "glue code" which host authors
|
||||
can write (it would be plain Roc code, but with some extra keywords that aren't
|
||||
available in normal modules - kinda like `port module` in Elm), and which
|
||||
describe both the Roc-host/C boundary as well as the Roc-host/Roc-app boundary.
|
||||
Roc application authors only care about the Roc-host/Roc-app portion, and the
|
||||
host author only cares about the Roc-host/C boundary when implementing the host.
|
||||
|
||||
Using this glue code, the Roc compiler can generate C header files describing the
|
||||
boundary. This not only gets us host compatibility with C compilers, but also
|
||||
Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen)
|
||||
generates correct Rust FFI bindings from C headers.
|
12
examples/hello-web/index.html
Normal file
12
examples/hello-web/index.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<html>
|
||||
<body>
|
||||
<div id="output"></div>
|
||||
<script src="platform/host.js"></script>
|
||||
<script>
|
||||
const elem = document.getElementById("output");
|
||||
roc_web_platform_run("./hello-web.wasm", (string_from_roc) => {
|
||||
elem.textContent = string_from_roc;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
10
examples/hello-web/platform/Package-Config.roc
Normal file
10
examples/hello-web/platform/Package-Config.roc
Normal file
|
@ -0,0 +1,10 @@
|
|||
platform examples/hello-world
|
||||
requires {}{ main : Str }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
effects fx.Effect {}
|
||||
|
||||
mainForHost : Str
|
||||
mainForHost = main
|
51
examples/hello-web/platform/host.js
Normal file
51
examples/hello-web/platform/host.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
async function roc_web_platform_run(wasm_filename, callback) {
|
||||
const decoder = new TextDecoder();
|
||||
let memory_bytes;
|
||||
let exit_code;
|
||||
|
||||
function js_display_roc_string(str_bytes, str_len) {
|
||||
const utf8_bytes = memory_bytes.subarray(str_bytes, str_bytes + str_len);
|
||||
const js_string = decoder.decode(utf8_bytes);
|
||||
callback(js_string);
|
||||
}
|
||||
|
||||
const importObj = {
|
||||
wasi_snapshot_preview1: {
|
||||
roc_panic: (_pointer, _tag_id) => {
|
||||
throw 'Roc panicked!';
|
||||
}
|
||||
},
|
||||
env: {
|
||||
js_display_roc_string,
|
||||
},
|
||||
};
|
||||
|
||||
let wasm;
|
||||
|
||||
const response = await fetch(wasm_filename);
|
||||
|
||||
if (WebAssembly.instantiateStreaming) {
|
||||
// streaming API has better performance if available
|
||||
wasm = await WebAssembly.instantiateStreaming(response, importObj);
|
||||
} else {
|
||||
const module_bytes = await response.arrayBuffer();
|
||||
wasm = await WebAssembly.instantiate(module_bytes, importObj);
|
||||
}
|
||||
|
||||
memory_bytes = new Uint8Array(wasm.instance.exports.memory.buffer);
|
||||
|
||||
try {
|
||||
wasm.instance.exports._start();
|
||||
} catch (e) {
|
||||
const is_ok = e.message === "unreachable" && exit_code === 0;
|
||||
if (!is_ok) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = {
|
||||
roc_web_platform_run,
|
||||
};
|
||||
}
|
72
examples/hello-web/platform/host.zig
Normal file
72
examples/hello-web/platform/host.zig
Normal file
|
@ -0,0 +1,72 @@
|
|||
const std = @import("std");
|
||||
const str = @import("str");
|
||||
const RocStr = str.RocStr;
|
||||
const testing = std.testing;
|
||||
const expectEqual = testing.expectEqual;
|
||||
const expect = testing.expect;
|
||||
|
||||
comptime {
|
||||
// This is a workaround for https://github.com/ziglang/zig/issues/8218
|
||||
// which is only necessary on macOS.
|
||||
//
|
||||
// Once that issue is fixed, we can undo the changes in
|
||||
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
|
||||
// -fcompiler-rt in link.rs instead of doing this. Note that this
|
||||
// workaround is present in many host.zig files, so make sure to undo
|
||||
// it everywhere!
|
||||
if (std.builtin.os.tag == .macos) {
|
||||
_ = @import("compiler_rt");
|
||||
}
|
||||
}
|
||||
|
||||
const Align = extern struct { a: usize, b: usize };
|
||||
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
|
||||
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void;
|
||||
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
|
||||
|
||||
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
|
||||
_ = alignment;
|
||||
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
export fn roc_realloc(c_ptr: *c_void, old_size: usize, new_size: usize, alignment: u32) callconv(.C) ?*c_void {
|
||||
_ = old_size;
|
||||
_ = alignment;
|
||||
|
||||
return realloc(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)), new_size);
|
||||
}
|
||||
|
||||
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
|
||||
_ = alignment;
|
||||
|
||||
free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)));
|
||||
}
|
||||
|
||||
// NOTE roc_panic is provided in the JS file, so it can throw an exception
|
||||
|
||||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
extern fn roc__mainForHost_1_exposed(*RocCallResult) void;
|
||||
|
||||
const RocCallResult = extern struct { flag: u64, content: RocStr };
|
||||
|
||||
const Unit = extern struct {};
|
||||
|
||||
extern fn js_display_roc_string(str_bytes: ?[*]u8, str_len: usize) void;
|
||||
|
||||
pub fn main() u8 {
|
||||
// make space for the result
|
||||
var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() };
|
||||
|
||||
// actually call roc to populate the callresult
|
||||
roc__mainForHost_1_exposed(&callresult);
|
||||
|
||||
// display the result using JavaScript
|
||||
js_display_roc_string(callresult.content.str_bytes, callresult.content.str_len);
|
||||
|
||||
callresult.content.deinit();
|
||||
|
||||
return 0;
|
||||
}
|
25
examples/hello-web/test-node.js
Normal file
25
examples/hello-web/test-node.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Node.js test file for hello-web example
|
||||
* We are not running this in CI currently, and Node.js is not a Roc dependency.
|
||||
* But if you happen to have it, you can run this.
|
||||
*/
|
||||
|
||||
// Node doesn't have the fetch API
|
||||
const fs = require("fs/promises");
|
||||
global.fetch = (filename) =>
|
||||
fs.readFile(filename).then((buffer) => ({
|
||||
arrayBuffer() {
|
||||
return buffer;
|
||||
},
|
||||
}));
|
||||
|
||||
const { roc_web_platform_run } = require("./platform/host");
|
||||
|
||||
roc_web_platform_run("./hello-world.wasm", (string_from_roc) => {
|
||||
const expected = "Hello, World!";
|
||||
if (string_from_roc !== expected) {
|
||||
console.error(`Expected "${expected}", but got "${string_from_roc}"`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("OK");
|
||||
});
|
|
@ -1,36 +1,38 @@
|
|||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void* roc_alloc(size_t size, unsigned int alignment) {
|
||||
return malloc(size);
|
||||
}
|
||||
void* roc_alloc(size_t size, unsigned int alignment) { return malloc(size); }
|
||||
|
||||
void* roc_realloc(void* ptr, size_t old_size, size_t new_size, unsigned int alignment) {
|
||||
void* roc_realloc(void* ptr, size_t old_size, size_t new_size,
|
||||
unsigned int alignment) {
|
||||
return realloc(ptr, new_size);
|
||||
}
|
||||
|
||||
void roc_dealloc(void* ptr, unsigned int alignment) {
|
||||
free(ptr);
|
||||
}
|
||||
void roc_dealloc(void* ptr, unsigned int alignment) { free(ptr); }
|
||||
|
||||
void roc_panic(void* ptr, unsigned int alignment) {
|
||||
char* msg = (char *)ptr;
|
||||
fprintf(stderr, "Application crashed with message\n\n %s\n\nShutting down\n", msg);
|
||||
char* msg = (char*)ptr;
|
||||
fprintf(stderr,
|
||||
"Application crashed with message\n\n %s\n\nShutting down\n", msg);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void* roc_memcpy(void* dest, const void* src, size_t n) {
|
||||
return memcpy(dest, src, n);
|
||||
}
|
||||
|
||||
void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); }
|
||||
|
||||
struct RocStr {
|
||||
char* bytes;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
bool is_small_str(struct RocStr str) {
|
||||
return ((ssize_t)str.len) < 0;
|
||||
}
|
||||
bool is_small_str(struct RocStr str) { return ((ssize_t)str.len) < 0; }
|
||||
|
||||
// Determine the length of the string, taking into
|
||||
// account the small string optimization
|
||||
|
@ -51,23 +53,13 @@ size_t roc_str_len(struct RocStr str) {
|
|||
}
|
||||
}
|
||||
|
||||
struct RocCallResult {
|
||||
size_t flag;
|
||||
struct RocStr content;
|
||||
};
|
||||
|
||||
extern void roc__mainForHost_1_exposed(struct RocCallResult *re);
|
||||
extern struct RocStr roc__mainForHost_1_exposed();
|
||||
|
||||
int main() {
|
||||
// Make space for the Roc call result
|
||||
struct RocCallResult call_result;
|
||||
|
||||
// Call Roc to populate call_result
|
||||
roc__mainForHost_1_exposed(&call_result);
|
||||
struct RocStr str = roc__mainForHost_1_exposed();
|
||||
|
||||
// Determine str_len and the str_bytes pointer,
|
||||
// taking into account the small string optimization.
|
||||
struct RocStr str = call_result.content;
|
||||
size_t str_len = roc_str_len(str);
|
||||
char* str_bytes;
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ const Align = extern struct { a: usize, b: usize };
|
|||
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
|
||||
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void;
|
||||
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
|
||||
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
|
||||
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
|
||||
|
||||
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
|
||||
_ = alignment;
|
||||
|
@ -51,12 +53,18 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
|||
std.process.exit(0);
|
||||
}
|
||||
|
||||
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{
|
||||
return memcpy(dst, src, size);
|
||||
}
|
||||
|
||||
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{
|
||||
return memset(dst, value, size);
|
||||
}
|
||||
|
||||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
extern fn roc__mainForHost_1_exposed(*RocCallResult) void;
|
||||
|
||||
const RocCallResult = extern struct { flag: u64, content: RocStr };
|
||||
extern fn roc__mainForHost_1_exposed() RocStr;
|
||||
|
||||
const Unit = extern struct {};
|
||||
|
||||
|
@ -64,20 +72,17 @@ pub fn main() u8 {
|
|||
const stdout = std.io.getStdOut().writer();
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
|
||||
// make space for the result
|
||||
var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() };
|
||||
|
||||
// start time
|
||||
var ts1: std.os.timespec = undefined;
|
||||
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
|
||||
|
||||
// actually call roc to populate the callresult
|
||||
roc__mainForHost_1_exposed(&callresult);
|
||||
var callresult = roc__mainForHost_1_exposed();
|
||||
|
||||
// stdout the result
|
||||
stdout.print("{s}\n", .{callresult.content.asSlice()}) catch unreachable;
|
||||
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
|
||||
|
||||
callresult.content.deinit();
|
||||
callresult.deinit();
|
||||
|
||||
// end time
|
||||
var ts2: std.os.timespec = undefined;
|
||||
|
|
|
@ -20,41 +20,71 @@ comptime {
|
|||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
extern fn roc__mainForHost_1_exposed(RocList, *RocCallResult) void;
|
||||
extern fn roc__mainForHost_1_exposed(RocList) RocList;
|
||||
|
||||
extern fn malloc(size: usize) callconv(.C) ?*c_void;
|
||||
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
|
||||
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void;
|
||||
const Align = extern struct { a: usize, b: usize };
|
||||
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
|
||||
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void;
|
||||
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
|
||||
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
|
||||
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
|
||||
|
||||
const DEBUG: bool = false;
|
||||
|
||||
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}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
|
||||
return ptr;
|
||||
} else {
|
||||
return malloc(size);
|
||||
}
|
||||
}
|
||||
|
||||
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
|
||||
return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size);
|
||||
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);
|
||||
}
|
||||
|
||||
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
|
||||
free(@alignCast(16, @ptrCast([*]u8, c_ptr)));
|
||||
if (DEBUG) {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
|
||||
}
|
||||
|
||||
free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)));
|
||||
}
|
||||
|
||||
export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
||||
_ = tag_id;
|
||||
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
const msg = @ptrCast([*:0]const u8, c_ptr);
|
||||
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
|
||||
std.process.exit(0);
|
||||
}
|
||||
|
||||
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{
|
||||
return memcpy(dst, src, size);
|
||||
}
|
||||
|
||||
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{
|
||||
return memset(dst, value, size);
|
||||
}
|
||||
|
||||
// warning! the array is currently stack-allocated so don't make this too big
|
||||
const NUM_NUMS = 100;
|
||||
|
||||
const RocList = extern struct { elements: [*]i64, length: usize };
|
||||
|
||||
const RocCallResult = extern struct { flag: usize, content: RocList };
|
||||
|
||||
const Unit = extern struct {};
|
||||
|
||||
pub export fn main() i32 {
|
||||
pub export fn main() u8 {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
|
||||
|
@ -65,25 +95,22 @@ pub export fn main() i32 {
|
|||
|
||||
var numbers = raw_numbers[1..];
|
||||
|
||||
for (numbers) |x, i| {
|
||||
for (numbers) |_, i| {
|
||||
numbers[i] = @mod(@intCast(i64, i), 12);
|
||||
}
|
||||
|
||||
const roc_list = RocList{ .elements = numbers, .length = NUM_NUMS };
|
||||
|
||||
// make space for the result
|
||||
var callresult = RocCallResult{ .flag = 0, .content = undefined };
|
||||
|
||||
// start time
|
||||
var ts1: std.os.timespec = undefined;
|
||||
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
|
||||
|
||||
// actually call roc to populate the callresult
|
||||
roc__mainForHost_1_exposed(roc_list, &callresult);
|
||||
var callresult = roc__mainForHost_1_exposed(roc_list);
|
||||
|
||||
// stdout the result
|
||||
const length = std.math.min(20, callresult.content.length);
|
||||
var result = callresult.content.elements[0..length];
|
||||
const length = std.math.min(20, callresult.length);
|
||||
var result = callresult.elements[0..length];
|
||||
|
||||
for (result) |x, i| {
|
||||
if (i == 0) {
|
||||
|
|
33
linker/Cargo.toml
Normal file
33
linker/Cargo.toml
Normal file
|
@ -0,0 +1,33 @@
|
|||
[package]
|
||||
name = "roc_linker"
|
||||
version = "0.1.0"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
repository = "https://github.com/rtfeldman/roc"
|
||||
edition = "2018"
|
||||
description = "A surgical linker for Roc"
|
||||
|
||||
[lib]
|
||||
name = "roc_linker"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "link"
|
||||
path = "src/main.rs"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
roc_mono = { path = "../compiler/mono" }
|
||||
roc_build = { path = "../compiler/build", default-features = false }
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
bumpalo = { version = "3.6", features = ["collections"] }
|
||||
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
|
||||
clap = { git = "https://github.com/rtfeldman/clap", branch = "master" }
|
||||
iced-x86 = "1.14"
|
||||
memmap2 = "0.3"
|
||||
object = { version = "0.26", features = ["read", "write"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
bincode = "1.3"
|
||||
target-lexicon = "0.12.2"
|
||||
tempfile = "3.1.0"
|
44
linker/README.md
Normal file
44
linker/README.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
# The Roc Surgical Linker
|
||||
|
||||
This linker has the goal of being extremely slim lined and fast.
|
||||
It is focused on the scope of only linking platforms to Roc applications.
|
||||
This restriction enables ignoring most of linking.
|
||||
|
||||
## General Overview
|
||||
|
||||
This linker is run in 2 phases: preprocessing and surigical linking.
|
||||
|
||||
### Platform Preprocessor
|
||||
|
||||
1. Dynamically link the platform to a dummy Roc application dynamic library
|
||||
1. Create metadata related to Roc dynamically linked functions
|
||||
- Symbols that need to be redefined
|
||||
- Call locations that need to be modified for each symbol
|
||||
- Locations of special roc functions (roc_alloc, roc_dealloc, builtins, etc)
|
||||
1. Modify the main executable to no longer be dynamically link
|
||||
- Delete dependency on dynamic library
|
||||
- Remove symbols from the dynamic table (maybe add them to the regular table?)
|
||||
- Delete GOT and PLT entries
|
||||
- Remove relocations from the dynamic table
|
||||
- Add extra header information about new text and data section at end of file
|
||||
|
||||
### Surgical Linker
|
||||
|
||||
1. Build off of preprocessed platform
|
||||
1. Append text and data of application, dealing with app relocations
|
||||
1. Surgically update all call locations in the platform
|
||||
1. Surgically update call information in the application (also dealing with other relocations for builtins)
|
||||
|
||||
## TODO (In a lightly prioritized order)
|
||||
|
||||
- Run CLI tests and/or benchmarks with the Roc Linker.
|
||||
- Test with an executable completely generated by Cargo (It will hopefully work out of the box like zig).
|
||||
- Add Macho support
|
||||
- Honestly should be almost exactly the same code.
|
||||
This means we likely need to do a lot of refactoring to minimize the duplicate code.
|
||||
The fun of almost but not quite the same.
|
||||
- Add PE support
|
||||
- As a prereq, we need roc building on Windows (I'm not sure it does currently).
|
||||
- Definitely a solid bit different than elf, but hopefully after refactoring for Macho, won't be that crazy to add.
|
||||
- Look at enabling completely in memory linking that could be used with `roc run` and/or `roc repl`
|
||||
- Add a feature to the compiler to make this linker optional.
|
1626
linker/src/lib.rs
Normal file
1626
linker/src/lib.rs
Normal file
File diff suppressed because it is too large
Load diff
20
linker/src/main.rs
Normal file
20
linker/src/main.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use roc_linker::{build_app, preprocess, surgery, CMD_PREPROCESS, CMD_SURGERY};
|
||||
use std::io;
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let matches = build_app().get_matches();
|
||||
|
||||
let exit_code = match matches.subcommand_name() {
|
||||
None => Ok::<i32, io::Error>(-1),
|
||||
Some(CMD_PREPROCESS) => {
|
||||
let sub_matches = matches.subcommand_matches(CMD_PREPROCESS).unwrap();
|
||||
preprocess(sub_matches)
|
||||
}
|
||||
Some(CMD_SURGERY) => {
|
||||
let sub_matches = matches.subcommand_matches(CMD_SURGERY).unwrap();
|
||||
surgery(sub_matches)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}?;
|
||||
std::process::exit(exit_code);
|
||||
}
|
31
linker/src/metadata.rs
Normal file
31
linker/src/metadata.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use roc_collections::all::MutMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
pub struct SurgeryEntry {
|
||||
pub file_offset: u64,
|
||||
pub virtual_offset: u64,
|
||||
pub size: u8,
|
||||
}
|
||||
|
||||
// TODO: Reanalyze each piece of data in this struct.
|
||||
// I think a number of them can be combined to reduce string duplication.
|
||||
// Also I think a few of them aren't need.
|
||||
// For example, I think preprocessing can deal with all shifting and remove the need for added_byte_count.
|
||||
#[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
|
||||
pub struct Metadata {
|
||||
pub app_functions: Vec<String>,
|
||||
// offset followed by address.
|
||||
pub plt_addresses: MutMap<String, (u64, u64)>,
|
||||
pub surgeries: MutMap<String, Vec<SurgeryEntry>>,
|
||||
pub dynamic_symbol_indices: MutMap<String, u64>,
|
||||
pub roc_symbol_vaddresses: MutMap<String, u64>,
|
||||
pub exec_len: u64,
|
||||
pub load_align_constraint: u64,
|
||||
pub added_byte_count: u64,
|
||||
pub last_vaddr: u64,
|
||||
pub dynamic_section_offset: u64,
|
||||
pub dynamic_symbol_table_section_offset: u64,
|
||||
pub symbol_table_section_offset: u64,
|
||||
pub symbol_table_size: u64,
|
||||
}
|
11
linker/tests/fib/.gitignore
vendored
Normal file
11
linker/tests/fib/.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
fib
|
||||
|
||||
zig-cache
|
||||
zig-out
|
||||
|
||||
*.o
|
||||
|
||||
dynhost
|
||||
preprocessedhost
|
||||
metadata
|
||||
libapp.so
|
15
linker/tests/fib/Main.roc
Normal file
15
linker/tests/fib/Main.roc
Normal file
|
@ -0,0 +1,15 @@
|
|||
app "fib"
|
||||
packages { base: "platform" }
|
||||
imports []
|
||||
provides [ main ] to base
|
||||
|
||||
main : U64 -> U64
|
||||
main = \index ->
|
||||
fibHelp index 0 1
|
||||
|
||||
fibHelp : U64, U64, U64 -> U64
|
||||
fibHelp = \index, parent, grandparent ->
|
||||
if index == 0 then
|
||||
parent
|
||||
else
|
||||
fibHelp (index - 1) grandparent (parent + grandparent)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue