Merge branch 'trunk' of github.com:rtfeldman/roc into editor-let-value

This commit is contained in:
Anton-4 2021-09-24 11:52:27 +02:00
commit bae7a12e9e
107 changed files with 5872 additions and 1502 deletions

View file

@ -42,4 +42,4 @@ jobs:
run: cd ci/bench-runner && cargo build --release && cd ../.. run: cd ci/bench-runner && cargo build --release && cd ../..
- name: run benchmarks with regression check - 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
View file

@ -3,6 +3,7 @@ generated-docs
zig-cache zig-cache
.direnv .direnv
*.rs.bk *.rs.bk
*.o
# llvm human-readable output # llvm human-readable output
*.ll *.ll

View file

@ -7,6 +7,7 @@ To build the compiler, you need these installed:
* Python 2.7 (Windows only), `python-is-python3` (Ubuntu) * Python 2.7 (Windows only), `python-is-python3` (Ubuntu)
* [Zig](https://ziglang.org/), see below for version * [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 * LLVM, see below for version
To run the test suite (via `cargo test`), you additionally need to install: 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 ## Using Nix
:exclamation: **Our Nix setup is currently broken, you'll have to install manually for now** :exclamation:
### Install ### Install
Using [nix](https://nixos.org/download.html) is a quick way to get an environment bootstrapped with a single command. Using [nix](https://nixos.org/download.html) is a quick way to get an environment bootstrapped with a single command.

62
Cargo.lock generated
View file

@ -1726,6 +1726,16 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 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]] [[package]]
name = "ident_case" name = "ident_case"
version = "1.0.1" version = "1.0.1"
@ -2032,6 +2042,15 @@ dependencies = [
"winapi 0.3.9", "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]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.5.4" version = "0.5.4"
@ -2144,6 +2163,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "memmap2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "memoffset" name = "memoffset"
version = "0.5.6" version = "0.5.6"
@ -2176,6 +2204,15 @@ dependencies = [
"objc", "objc",
] ]
[[package]]
name = "mimalloc"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb74897ce508e6c49156fd1476fc5922cbc6e75183c65e399c765a09122e5130"
dependencies = [
"libmimalloc-sys",
]
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.1.3" version = "0.1.3"
@ -2486,6 +2523,9 @@ version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2" checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2"
dependencies = [ dependencies = [
"crc32fast",
"flate2",
"indexmap",
"memchr", "memchr",
] ]
@ -3368,7 +3408,9 @@ dependencies = [
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain", "roc_constrain",
"roc_gen_dev",
"roc_gen_llvm", "roc_gen_llvm",
"roc_gen_wasm",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
@ -3438,6 +3480,7 @@ dependencies = [
"libc", "libc",
"libloading 0.6.7", "libloading 0.6.7",
"maplit", "maplit",
"mimalloc",
"pretty_assertions 0.5.1", "pretty_assertions 0.5.1",
"quickcheck 0.8.5", "quickcheck 0.8.5",
"quickcheck_macros 0.8.0", "quickcheck_macros 0.8.0",
@ -3450,6 +3493,7 @@ dependencies = [
"roc_editor", "roc_editor",
"roc_fmt", "roc_fmt",
"roc_gen_llvm", "roc_gen_llvm",
"roc_linker",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
@ -3687,6 +3731,24 @@ dependencies = [
name = "roc_ident" name = "roc_ident"
version = "0.1.0" 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]] [[package]]
name = "roc_load" name = "roc_load"
version = "0.1.0" version = "0.1.0"

View file

@ -33,6 +33,7 @@ members = [
"cli/cli_utils", "cli/cli_utils",
"roc_std", "roc_std",
"docs", "docs",
"linker",
] ]
exclude = [ "ci/bench-runner" ] exclude = [ "ci/bench-runner" ]
# Needed to be able to run `cargo run -p roc_cli --no-default-features` - # Needed to be able to run `cargo run -p roc_cli --no-default-features` -

View file

@ -46,7 +46,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs: copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt 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: test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt
@ -66,7 +66,7 @@ check-rustfmt:
check-typos: check-typos:
RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically 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 RUN typos
test-rust: test-rust:

View file

@ -57,6 +57,7 @@ roc_build = { path = "../compiler/build", default-features = false }
roc_fmt = { path = "../compiler/fmt" } roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" } roc_reporting = { path = "../compiler/reporting" }
roc_editor = { path = "../editor", optional = true } 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 # 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" } clap = { git = "https://github.com/rtfeldman/clap", branch = "master" }
const_format = "0.2" 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"] } bumpalo = { version = "3.2", features = ["collections"] }
libc = "0.2" libc = "0.2"
libloading = "0.6" libloading = "0.6"
mimalloc = { version = "0.1.26", default-features = false }
inkwell = { path = "../vendor/inkwell", optional = true } inkwell = { path = "../vendor/inkwell", optional = true }
target-lexicon = "0.12.2" target-lexicon = "0.12.2"

View file

@ -12,9 +12,11 @@ fn exec_bench_w_input<T: Measurement>(
) { ) {
let flags: &[&str] = &["--optimize"]; let flags: &[&str] = &["--optimize"];
println!("building {:?}", executable_filename);
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat()); 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); panic!("{}", compile_out.stderr);
} }
@ -24,9 +26,13 @@ fn exec_bench_w_input<T: Measurement>(
compile_out compile_out
); );
println!("checking output for {:?}", executable_filename);
check_cmd_output(file, stdin_str, executable_filename, expected_ending); check_cmd_output(file, stdin_str, executable_filename, expected_ending);
println!("benching {:?}", executable_filename);
bench_cmd(file, stdin_str, executable_filename, bench_group_opt); bench_cmd(file, stdin_str, executable_filename, bench_group_opt);
println!("DONE");*/
} }
fn check_cmd_output( fn check_cmd_output(

View file

@ -43,6 +43,7 @@ pub struct BuiltFile {
} }
#[cfg(feature = "llvm")] #[cfg(feature = "llvm")]
#[allow(clippy::too_many_arguments)]
pub fn build_file<'a>( pub fn build_file<'a>(
arena: &'a Bump, arena: &'a Bump,
target: &Triple, target: &Triple,
@ -50,7 +51,10 @@ pub fn build_file<'a>(
roc_file_path: PathBuf, roc_file_path: PathBuf,
opt_level: OptLevel, opt_level: OptLevel,
emit_debug_info: bool, emit_debug_info: bool,
emit_timings: bool,
link_type: LinkType, link_type: LinkType,
surgically_link: bool,
precompiled: bool,
) -> Result<BuiltFile, LoadingProblem<'a>> { ) -> Result<BuiltFile, LoadingProblem<'a>> {
let compilation_start = SystemTime::now(); let compilation_start = SystemTime::now();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; 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(); let subs_by_module = MutMap::default();
// Release builds use uniqueness optimizations // Release builds use uniqueness optimizations
let stdlib = match opt_level { let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
OptLevel::Normal => arena.alloc(roc_builtins::std::standard_stdlib()),
OptLevel::Optimize => arena.alloc(roc_builtins::std::standard_stdlib()),
};
let loaded = roc_load::file::load_and_monomorphize( let loaded = roc_load::file::load_and_monomorphize(
arena, arena,
@ -75,16 +76,7 @@ pub fn build_file<'a>(
)?; )?;
use target_lexicon::Architecture; use target_lexicon::Architecture;
let emit_wasm = match target.architecture { let emit_wasm = matches!(target.architecture, Architecture::Wasm32);
Architecture::X86_64 => false,
Architecture::Aarch64(_) => false,
Architecture::Wasm32 => true,
Architecture::X86_32(_) => false,
_ => panic!(
"TODO gracefully handle unsupported architecture: {:?}",
target.architecture
),
};
// TODO wasm host extension should be something else ideally // TODO wasm host extension should be something else ideally
// .bc does not seem to work because // .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 host_extension = if emit_wasm { "zig" } else { "o" };
let app_extension = if emit_wasm { "bc" } 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(); 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() let app_o_file = Builder::new()
.prefix("roc_app") .prefix("roc_app")
.suffix(&format!(".{}", app_extension)) .suffix(&format!(".{}", app_extension))
@ -146,9 +170,14 @@ pub fn build_file<'a>(
} }
} }
let cwd = roc_file_path.parent().unwrap(); // This only needs to be mutable for report_problems. This can't be done
let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows // inside a nested scope without causing a borrow error!
let code_gen_timing = program::gen_from_mono_module( 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, arena,
loaded, loaded,
&roc_file_path, &roc_file_path,
@ -156,7 +185,11 @@ pub fn build_file<'a>(
app_o_file, app_o_file,
opt_level, opt_level,
emit_debug_info, emit_debug_info,
); ),
OptLevel::Development => {
program::gen_from_mono_module_dev(arena, loaded, target, app_o_file)
}
};
buf.push('\n'); buf.push('\n');
buf.push_str(" "); buf.push_str(" ");
@ -177,7 +210,7 @@ pub fn build_file<'a>(
}) })
.len(); .len();
if emit_debug_info { if emit_timings {
println!( println!(
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}", "\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
buf buf
@ -190,65 +223,181 @@ pub fn build_file<'a>(
); );
} }
// Step 2: link the precompiled host and compiled app let rebuild_duration = rebuild_thread.join().unwrap();
let mut host_input_path = PathBuf::from(cwd); if emit_timings && !precompiled {
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 {
println!( println!(
"Finished rebuilding the host in {} ms\n", "Finished rebuilding and preprocessing the host in {} ms\n",
rebuild_host_end.as_millis() rebuild_duration
); );
} }
// TODO try to move as much of this linking as possible to the precompiled // Step 2: link the precompiled host and compiled app
// host, to minimize the amount of host-application linking required.
let link_start = SystemTime::now(); 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( link(
target, target,
binary_path, binary_path.clone(),
&[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()], &[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()],
link_type link_type
) )
.map_err(|_| { .map_err(|_| {
todo!("gracefully handle `rustc` failing to spawn."); todo!("gracefully handle `ld` failing to spawn.");
})?; })?;
let cmd_result = child.wait().map_err(|_| { let exit_status = child.wait().map_err(|_| {
todo!("gracefully handle error after `rustc` spawned"); 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(); let linking_time = link_start.elapsed().unwrap();
if emit_debug_info { if emit_timings {
println!("Finished linking in {} ms\n", linking_time.as_millis()); println!("Finished linking in {} ms\n", linking_time.as_millis());
} }
let total_time = compilation_start.elapsed().unwrap(); 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 { Ok(BuiltFile {
binary_path, binary_path,
outcome, outcome,
total_time, 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))
}

View file

@ -26,11 +26,16 @@ pub const CMD_BUILD: &str = "build";
pub const CMD_REPL: &str = "repl"; pub const CMD_REPL: &str = "repl";
pub const CMD_EDIT: &str = "edit"; pub const CMD_EDIT: &str = "edit";
pub const CMD_DOCS: &str = "docs"; pub const CMD_DOCS: &str = "docs";
pub const CMD_CHECK: &str = "check";
pub const FLAG_DEBUG: &str = "debug"; pub const FLAG_DEBUG: &str = "debug";
pub const FLAG_DEV: &str = "dev";
pub const FLAG_OPTIMIZE: &str = "optimize"; pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_LIB: &str = "lib"; pub const FLAG_LIB: &str = "lib";
pub const FLAG_BACKEND: &str = "backend"; 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 ROC_FILE: &str = "ROC_FILE";
pub const BACKEND: &str = "BACKEND"; pub const BACKEND: &str = "BACKEND";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; 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.)") .help("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
.required(false), .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(
Arg::with_name(FLAG_BACKEND) Arg::with_name(FLAG_BACKEND)
.long(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") .help("Store LLVM debug information in the generated program")
.required(false), .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) .subcommand(App::new(CMD_RUN)
.about("DEPRECATED - now use `roc [FILE]` instead of `roc run [FILE]`") .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.)") .help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.required(false), .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(
Arg::with_name(FLAG_DEBUG) Arg::with_name(FLAG_DEBUG)
.long(FLAG_DEBUG) .long(FLAG_DEBUG)
@ -104,6 +139,20 @@ pub fn build_app<'a>() -> App<'a> {
.subcommand(App::new(CMD_REPL) .subcommand(App::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (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( .subcommand(
App::new(CMD_DOCS) App::new(CMD_DOCS)
.about("Generate documentation for Roc modules") .about("Generate documentation for Roc modules")
@ -123,6 +172,12 @@ pub fn build_app<'a>() -> App<'a> {
.requires(ROC_FILE) .requires(ROC_FILE)
.required(false), .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(
Arg::with_name(FLAG_DEBUG) Arg::with_name(FLAG_DEBUG)
.long(FLAG_DEBUG) .long(FLAG_DEBUG)
@ -130,6 +185,24 @@ pub fn build_app<'a>() -> App<'a> {
.requires(ROC_FILE) .requires(ROC_FILE)
.required(false), .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(
Arg::with_name(FLAG_BACKEND) Arg::with_name(FLAG_BACKEND)
.long(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 filename = matches.value_of(ROC_FILE).unwrap();
let original_cwd = std::env::current_dir()?; let original_cwd = std::env::current_dir()?;
let opt_level = if matches.is_present(FLAG_OPTIMIZE) { let opt_level = match (
OptLevel::Optimize matches.is_present(FLAG_OPTIMIZE),
} else { matches.is_present(FLAG_DEV),
OptLevel::Normal ) {
(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_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME);
let link_type = if matches.is_present(FLAG_LIB) { let link_type = if matches.is_present(FLAG_LIB) {
LinkType::Dylib LinkType::Dylib
} else { } else {
LinkType::Executable 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); let path = Path::new(filename);
@ -239,7 +325,10 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
path, path,
opt_level, opt_level,
emit_debug_info, emit_debug_info,
emit_timings,
link_type, link_type,
surgically_link,
precompiled,
); );
match res_binary_path { 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 // Then, we get the import object related to our WASI
// and attach it to the Wasm instance. // and attach it to the Wasm instance.
let import_object = wasi_env let import_object = wasi_env.import_object(&module).unwrap();
.import_object(&module)
.unwrap_or_else(|_| wasmer::imports!());
let instance = Instance::new(&module, &import_object).unwrap(); let instance = Instance::new(&module, &import_object).unwrap();
let start = instance.exports.get_function("_start").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 { enum Backend {
Host, Host,
X86_32, X86_32,
X86_64, X86_64,
Dev,
Wasm32, Wasm32,
Wasm32Dev,
} }
impl Default for Backend { impl Default for Backend {
@ -403,9 +497,7 @@ impl Backend {
Backend::Host => "host", Backend::Host => "host",
Backend::X86_32 => "x86_32", Backend::X86_32 => "x86_32",
Backend::X86_64 => "x86_64", Backend::X86_64 => "x86_64",
Backend::Dev => "dev",
Backend::Wasm32 => "wasm32", Backend::Wasm32 => "wasm32",
Backend::Wasm32Dev => "wasm32_dev",
} }
} }
@ -414,9 +506,7 @@ impl Backend {
Backend::Host.as_str(), Backend::Host.as_str(),
Backend::X86_32.as_str(), Backend::X86_32.as_str(),
Backend::X86_64.as_str(), Backend::X86_64.as_str(),
Backend::Dev.as_str(),
Backend::Wasm32.as_str(), Backend::Wasm32.as_str(),
Backend::Wasm32Dev.as_str(),
]; ];
fn to_triple(&self) -> Triple { fn to_triple(&self) -> Triple {
@ -439,8 +529,7 @@ impl Backend {
triple triple
} }
Backend::Dev => todo!(), Backend::Wasm32 => {
Backend::Wasm32 | Backend::Wasm32Dev => {
triple.architecture = Architecture::Wasm32; triple.architecture = Architecture::Wasm32;
triple.binary_format = BinaryFormat::Wasm; triple.binary_format = BinaryFormat::Wasm;
@ -464,9 +553,7 @@ impl std::str::FromStr for Backend {
"host" => Ok(Backend::Host), "host" => Ok(Backend::Host),
"x86_32" => Ok(Backend::X86_32), "x86_32" => Ok(Backend::X86_32),
"x86_64" => Ok(Backend::X86_64), "x86_64" => Ok(Backend::X86_64),
"dev" => Ok(Backend::Dev),
"wasm32" => Ok(Backend::Wasm32), "wasm32" => Ok(Backend::Wasm32),
"wasm32_dev" => Ok(Backend::Wasm32Dev),
_ => Err(()), _ => Err(()),
} }
} }

View file

@ -1,11 +1,16 @@
use roc_cli::build::check_file;
use roc_cli::{ use roc_cli::{
build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_DOCS, CMD_EDIT, CMD_REPL, CMD_RUN, build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, CMD_REPL,
DIRECTORY_OR_FILES, ROC_FILE, CMD_RUN, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE,
}; };
use roc_load::file::LoadingProblem;
use std::fs::{self, FileType}; use std::fs::{self, FileType};
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[global_allocator]
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[cfg(feature = "llvm")] #[cfg(feature = "llvm")]
use roc_cli::build; use roc_cli::build;
use std::ffi::{OsStr, OsString}; 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) 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) => { Some(CMD_REPL) => {
repl::main()?; repl::main()?;

View file

@ -353,6 +353,13 @@ fn jit_to_ast_help<'a>(
| Layout::RecursivePointer => { | Layout::RecursivePointer => {
todo!("add support for rendering recursive tag unions in the REPL") 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,
),
} }
} }

View file

@ -107,13 +107,14 @@ pub fn gen_and_eval<'a>(
} }
for problem in type_problems { 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(); let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette); report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf); lines.push(buf);
} }
}
for problem in mono_problems { for problem in mono_problems {
let report = mono_problem(&alloc, module_path.clone(), problem); let report = mono_problem(&alloc, module_path.clone(), problem);

View file

@ -157,6 +157,15 @@ mod cli_run {
let example = $example; let example = $example;
let file_name = example_file(dir_name, example.filename); 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 with and without optimizations
check_output_with_stdin( check_output_with_stdin(
&file_name, &file_name,
@ -224,6 +233,20 @@ mod cli_run {
expected_ending:"Hello, World!\n", expected_ending:"Hello, World!\n",
use_valgrind: true, 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 { quicksort:"quicksort" => Example {
filename: "Quicksort.roc", filename: "Quicksort.roc",
executable_filename: "quicksort", executable_filename: "quicksort",
@ -242,7 +265,7 @@ mod cli_run {
filename: "Main.roc", filename: "Main.roc",
executable_filename: "effect-example", executable_filename: "effect-example",
stdin: &["hi there!"], stdin: &["hi there!"],
expected_ending: "hi there!\n", expected_ending: "hi there!\nIt is known\n",
use_valgrind: true, use_valgrind: true,
}, },
// tea:"tea" => Example { // 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 { fn run_with_wasmer(wasm_path: &std::path::Path, stdin: &[&str]) -> String {
use std::io::Write; use std::io::Write;
use wasmer::{Instance, Module, Store}; 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(); let start = instance.exports.get_function("_start").unwrap();
match start.call(&[]) { 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(); let mut state = wasi_env.state.lock().unwrap();
match state.fs.stdout_mut() { match state.fs.stdout_mut() {
@ -660,8 +698,3 @@ fn run_with_wasmer(wasm_path: &std::path::Path, stdin: &[&str]) -> String {
_ => todo!(), _ => todo!(),
} }
} }
Err(e) => {
panic!("Something went wrong running a wasm test:\n{:?}", e);
}
}
}

View file

@ -22,7 +22,8 @@ comptime {
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator; 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 malloc(size: usize) callconv(.C) ?*c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, 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); std.process.exit(0);
} }
const RocCallResult = extern struct { flag: usize, content: RocStr };
const Unit = extern struct {}; const Unit = extern struct {};
pub export fn main() i32 { pub export fn main() i32 {
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
// make space for the result
var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() };
// start time // start time
var ts1: std.os.timespec = undefined; var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
// actually call roc to populate the callresult // actually call roc to populate the callresult
roc__mainForHost_1_exposed(&callresult); const callresult = roc__mainForHost_1_exposed();
// stdout the result // 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 // end time
var ts2: std.os.timespec = undefined; var ts2: std.os.timespec = undefined;

View file

@ -22,7 +22,7 @@ comptime {
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator; 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 malloc(size: usize) callconv(.C) ?*c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, 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); std.process.exit(0);
} }
const RocCallResult = extern struct { flag: usize, content: RocStr };
const Unit = extern struct {}; const Unit = extern struct {};
pub export fn main() i32 { pub export fn main() i32 {
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
// make space for the result
var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() };
// start time // start time
var ts1: std.os.timespec = undefined; var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
// actually call roc to populate the callresult // actually call roc to populate the callresult
roc__mainForHost_1_exposed(&callresult); const callresult = roc__mainForHost_1_exposed();
// stdout the result // 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 // end time
var ts2: std.os.timespec = undefined; var ts2: std.os.timespec = undefined;

View file

@ -20,6 +20,8 @@ roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_load = { path = "../load" } roc_load = { path = "../load" }
roc_gen_llvm = { path = "../gen_llvm", optional = true } 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_reporting = { path = "../reporting" }
roc_std = { path = "../../roc_std" } roc_std = { path = "../../roc_std" }
im = "14" # im and im-rc should always have the same version! im = "14" # im and im-rc should always have the same version!

View file

@ -56,10 +56,27 @@ fn find_zig_str_path() -> PathBuf {
return zig_str_path; 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"))] #[cfg(not(target_os = "macos"))]
#[allow(clippy::too_many_arguments)]
pub fn build_zig_host_native( pub fn build_zig_host_native(
env_path: &str, env_path: &str,
env_home: &str, env_home: &str,
@ -67,13 +84,20 @@ pub fn build_zig_host_native(
zig_host_src: &str, zig_host_src: &str,
zig_str_path: &str, zig_str_path: &str,
target: &str, target: &str,
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
) -> Output { ) -> Output {
Command::new("zig") let mut command = Command::new("zig");
command
.env_clear() .env_clear()
.env("PATH", env_path) .env("PATH", env_path)
.env("HOME", env_home) .env("HOME", env_home);
.args(&[ if let Some(shared_lib_path) = shared_lib_path {
"build-obj", command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]);
} else {
command.args(&["build-obj", "-fPIC"]);
}
command.args(&[
zig_host_src, zig_host_src,
emit_bin, emit_bin,
"--pkg-begin", "--pkg-begin",
@ -85,15 +109,19 @@ pub fn build_zig_host_native(
// include libc // include libc
"--library", "--library",
"c", "c",
"--strip",
// cross-compile? // cross-compile?
"-target", "-target",
target, target,
]) ]);
.output() if matches!(opt_level, OptLevel::Optimize) {
.unwrap() command.args(&["-O", "ReleaseSafe"]);
}
command.output().unwrap()
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
#[allow(clippy::too_many_arguments)]
pub fn build_zig_host_native( pub fn build_zig_host_native(
env_path: &str, env_path: &str,
env_home: &str, env_home: &str,
@ -101,6 +129,8 @@ pub fn build_zig_host_native(
zig_host_src: &str, zig_host_src: &str,
zig_str_path: &str, zig_str_path: &str,
_target: &str, _target: &str,
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
) -> Output { ) -> Output {
use serde_json::Value; 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("special");
zig_compiler_rt_path.push("compiler_rt.zig"); zig_compiler_rt_path.push("compiler_rt.zig");
Command::new("zig") let mut command = Command::new("zig");
command
.env_clear() .env_clear()
.env("PATH", &env_path) .env("PATH", &env_path)
.env("HOME", &env_home) .env("HOME", &env_home);
.args(&[ if let Some(shared_lib_path) = shared_lib_path {
"build-obj", command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]);
} else {
command.args(&["build-obj", "-fPIC"]);
}
command.args(&[
zig_host_src, zig_host_src,
emit_bin, emit_bin,
"--pkg-begin", "--pkg-begin",
@ -162,9 +197,12 @@ pub fn build_zig_host_native(
// include libc // include libc
"--library", "--library",
"c", "c",
]) "--strip",
.output() ]);
.unwrap() if matches!(opt_level, OptLevel::Optimize) {
command.args(&["-O", "ReleaseSafe"]);
}
command.output().unwrap()
} }
pub fn build_zig_host_wasm32( pub fn build_zig_host_wasm32(
@ -173,7 +211,12 @@ pub fn build_zig_host_wasm32(
emit_bin: &str, emit_bin: &str,
zig_host_src: &str, zig_host_src: &str,
zig_str_path: &str, zig_str_path: &str,
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
) -> Output { ) -> 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. // NOTE currently just to get compiler warnings if the host code is invalid.
// the produced artifact is not used // 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 // we'd like to compile with `-target wasm32-wasi` but that is blocked on
// //
// https://github.com/ziglang/zig/issues/9414 // https://github.com/ziglang/zig/issues/9414
Command::new("zig") let mut command = Command::new("zig");
command
.env_clear() .env_clear()
.env("PATH", env_path) .env("PATH", env_path)
.env("HOME", env_home) .env("HOME", env_home)
@ -203,19 +247,67 @@ pub fn build_zig_host_wasm32(
"i386-linux-musl", "i386-linux-musl",
// "wasm32-wasi", // "wasm32-wasi",
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll", // "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll",
]) "-fPIC",
.output() "--strip",
.unwrap() ]);
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_src = host_input_path.with_file_name("host.c");
let c_host_dest = host_input_path.with_file_name("c_host.o"); 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 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_src = host_input_path.with_file_name("host.rs");
let rust_host_dest = host_input_path.with_file_name("rust_host.o"); 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 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 host_dest_wasm = host_input_path.with_file_name("host.bc");
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string()); 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, &emit_bin,
zig_host_src.to_str().unwrap(), zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(), zig_str_path.to_str().unwrap(),
opt_level,
shared_lib_path,
) )
} }
Architecture::X86_64 => { Architecture::X86_64 => {
@ -252,6 +346,8 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
zig_host_src.to_str().unwrap(), zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(), zig_str_path.to_str().unwrap(),
"native", "native",
opt_level,
shared_lib_path,
) )
} }
Architecture::X86_32(_) => { Architecture::X86_32(_) => {
@ -263,41 +359,58 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
zig_host_src.to_str().unwrap(), zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(), zig_str_path.to_str().unwrap(),
"i386-linux-musl", "i386-linux-musl",
opt_level,
shared_lib_path,
) )
} }
_ => panic!("Unsupported architecture {:?}", target.architecture), _ => panic!("Unsupported architecture {:?}", target.architecture),
}; };
validate_output("host.zig", "zig", output) validate_output("host.zig", "zig", output)
} else { } else if cargo_host_src.exists() {
// 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() {
// Compile and link Cargo.toml, if it exists // Compile and link Cargo.toml, if it exists
let cargo_dir = host_input_path.parent().unwrap(); 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") let mut command = Command::new("cargo");
.args(&["build", "--release"]) command.arg("build").current_dir(cargo_dir);
.current_dir(cargo_dir) if matches!(opt_level, OptLevel::Optimize) {
.output() command.arg("--release");
.unwrap(); }
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") let output = Command::new("ld")
.env_clear() .env_clear()
@ -313,21 +426,58 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
]) ])
.output() .output()
.unwrap(); .unwrap();
validate_output("c_host.o", "ld", output); validate_output("c_host.o", "ld", output);
} else if rust_host_src.exists() {
// Compile and link host.rs, if it exists // Clean up c_host.o
let output = Command::new("rustc") let output = Command::new("rm")
.args(&[ .env_clear()
rust_host_src.to_str().unwrap(), .args(&["-f", c_host_dest.to_str().unwrap()])
"-o",
rust_host_dest.to_str().unwrap(),
])
.output() .output()
.unwrap(); .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); 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") let output = Command::new("ld")
.env_clear() .env_clear()
.env("PATH", &env_path) .env("PATH", &env_path)
@ -342,8 +492,9 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
.unwrap(); .unwrap();
validate_output("rust_host.o", "ld", output); 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") let output = Command::new("rm")
.env_clear() .env_clear()
.args(&[ .args(&[
@ -355,15 +506,17 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
.unwrap(); .unwrap();
validate_output("rust_host.o", "rm", output); validate_output("rust_host.o", "rm", output);
} else if c_host_dest.exists() { } else if c_host_src.exists() {
// Clean up c_host.o // Compile host.c, if it exists
let output = Command::new("mv") let output = build_c_host_native(
.env_clear() &env_path,
.args(&[c_host_dest, host_dest_native]) &env_home,
.output() host_dest_native.to_str().unwrap(),
.unwrap(); &[c_host_src.to_str().unwrap()],
opt_level,
validate_output("c_host.o", "mv", output); shared_lib_path,
);
validate_output("host.c", "clang", output);
} }
} }
@ -505,6 +658,7 @@ fn link_linux(
"--eh-frame-hdr", "--eh-frame-hdr",
"-arch", "-arch",
arch_str(target), arch_str(target),
"-pie",
libcrt_path.join("crti.o").to_str().unwrap(), libcrt_path.join("crti.o").to_str().unwrap(),
libcrt_path.join("crtn.o").to_str().unwrap(), libcrt_path.join("crtn.o").to_str().unwrap(),
]) ])
@ -605,6 +759,7 @@ fn link_wasm32(
_link_type: LinkType, _link_type: LinkType,
) -> io::Result<(Child, PathBuf)> { ) -> io::Result<(Child, PathBuf)> {
let zig_str_path = find_zig_str_path(); let zig_str_path = find_zig_str_path();
let wasi_libc_path = find_wasi_libc_path();
let child = Command::new("zig9") let child = Command::new("zig9")
// .env_clear() // .env_clear()
@ -612,9 +767,10 @@ fn link_wasm32(
.args(&["build-exe"]) .args(&["build-exe"])
.args(input_paths) .args(input_paths)
.args([ .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()), &format!("-femit-bin={}", output_path.to_str().unwrap()),
// include libc
"-lc",
"-target", "-target",
"wasm32-wasi-musl", "wasm32-wasi-musl",
"--pkg-begin", "--pkg-begin",
@ -622,7 +778,8 @@ fn link_wasm32(
zig_str_path.to_str().unwrap(), zig_str_path.to_str().unwrap(),
"--pkg-end", "--pkg-end",
"--strip", "--strip",
// "-O", "ReleaseSmall", "-O",
"ReleaseSmall",
// useful for debugging // useful for debugging
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll", // "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll",
]) ])

View file

@ -10,6 +10,8 @@ use roc_mono::ir::OptLevel;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::Duration; use std::time::Duration;
use roc_collections::all::{MutMap, MutSet};
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct CodeGenTiming { pub struct CodeGenTiming {
pub code_gen: Duration, pub code_gen: Duration,
@ -20,33 +22,15 @@ pub struct CodeGenTiming {
// llvm we're using, consider moving me somewhere else. // llvm we're using, consider moving me somewhere else.
const LLVM_VERSION: &str = "12"; const LLVM_VERSION: &str = "12";
// TODO how should imported modules factor into this? What if those use builtins too? // TODO instead of finding exhaustiveness problems in monomorphization, find
// TODO this should probably use more helper functions // them after type checking (like Elm does) so we can complete the entire
// TODO make this polymorphic in the llvm functions so it can be reused for another backend. // `roc check` process without needing to monomorphize.
#[cfg(feature = "llvm")] /// Returns the number of problems reported.
#[allow(clippy::cognitive_complexity)] pub fn report_problems(loaded: &mut MonomorphizedModule) -> usize {
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;
use roc_reporting::report::{ use roc_reporting::report::{
can_problem, mono_problem, type_problem, Report, RocDocAllocator, Severity::*, can_problem, mono_problem, type_problem, Report, RocDocAllocator, Severity::*,
DEFAULT_PALETTE, DEFAULT_PALETTE,
}; };
let code_gen_start = SystemTime::now();
let palette = DEFAULT_PALETTE; let palette = DEFAULT_PALETTE;
// This will often over-allocate total memory, but it means we definitely // 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 warnings = Vec::with_capacity(total_problems);
let mut errors = 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(); 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(header_src.split('\n'));
src_lines.extend(src.split('\n').skip(1)); src_lines.extend(src.split('\n').skip(1));
} else { } else {
@ -66,9 +50,10 @@ pub fn gen_from_mono_module(
} }
// Report parsing and canonicalization problems // 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() { for problem in problems.into_iter() {
let report = can_problem(&alloc, module_path.clone(), problem); let report = can_problem(&alloc, module_path.clone(), problem);
let severity = report.severity; 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 { 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 severity = report.severity;
let mut buf = String::new(); 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 { for problem in problems {
let report = mono_problem(&alloc, module_path.clone(), problem); let report = mono_problem(&alloc, module_path.clone(), problem);
let severity = report.severity; 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 // Only print warnings if there are no errors
if errors.is_empty() { if errors.is_empty() {
problems_reported = warnings.len();
for warning in warnings { for warning in warnings {
println!("\n{}\n", warning); println!("\n{}\n", warning);
} }
} else { } else {
problems_reported = errors.len();
for error in errors { for error in errors {
println!("\n{}\n", error); 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 // The horizontal rule is nice when running the program right after
// compiling it, as it lets you clearly see where the compiler // compiling it, as it lets you clearly see where the compiler
// errors/warnings end and the program output begins. // 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)); 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 // Generate the binary
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let context = Context::create(); let context = Context::create();
@ -286,6 +305,7 @@ pub fn gen_from_mono_module(
.unwrap(); .unwrap();
let llc_args = &[ let llc_args = &[
"-relocation-model=pic",
"-filetype=obj", "-filetype=obj",
app_bc_file.to_str().unwrap(), app_bc_file.to_str().unwrap(),
"-o", "-o",
@ -325,7 +345,7 @@ pub fn gen_from_mono_module(
use target_lexicon::Architecture; use target_lexicon::Architecture;
match target.architecture { match target.architecture {
Architecture::X86_64 | Architecture::X86_32(_) | Architecture::Aarch64(_) => { Architecture::X86_64 | Architecture::X86_32(_) | Architecture::Aarch64(_) => {
let reloc = RelocMode::Default; let reloc = RelocMode::PIC;
let model = CodeModel::Default; let model = CodeModel::Default;
let target_machine = let target_machine =
target::target_machine(target, convert_opt_level(opt_level), reloc, model) target::target_machine(target, convert_opt_level(opt_level), reloc, model)
@ -354,3 +374,78 @@ pub fn gen_from_mono_module(
emit_o_file, 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()
}

View file

@ -106,7 +106,7 @@ pub fn target_machine(
#[cfg(feature = "llvm")] #[cfg(feature = "llvm")]
pub fn convert_opt_level(level: OptLevel) -> OptimizationLevel { pub fn convert_opt_level(level: OptLevel) -> OptimizationLevel {
match level { match level {
OptLevel::Normal => OptimizationLevel::None, OptLevel::Development | OptLevel::Normal => OptimizationLevel::None,
OptLevel::Optimize => OptimizationLevel::Aggressive, OptLevel::Optimize => OptimizationLevel::Aggressive,
} }
} }

View file

@ -54,7 +54,7 @@ pub fn build(b: *Builder) void {
// 32-bit wasm // 32-bit wasm
wasm32_target.cpu_arch = std.Target.Cpu.Arch.wasm32; 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; wasm32_target.abi = std.Target.Abi.none;
const obj_name_wasm32 = "builtins-wasm32"; const obj_name_wasm32 = "builtins-wasm32";

View file

@ -1,6 +1,4 @@
const builtin = @import("builtin");
const std = @import("std"); const std = @import("std");
const testing = std.testing;
// Dec Module // Dec Module
const dec = @import("dec.zig"); const dec = @import("dec.zig");
@ -110,6 +108,7 @@ const utils = @import("utils.zig");
comptime { comptime {
exportUtilsFn(utils.test_panic, "test_panic"); exportUtilsFn(utils.test_panic, "test_panic");
exportUtilsFn(utils.decrefC, "decref"); exportUtilsFn(utils.decrefC, "decref");
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak }); @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 // Custom panic function, as builtin Zig version errors during LLVM verification
pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn { pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn {
if (std.builtin.is_test) {
std.debug.print("{s}: {?}", .{ message, stacktrace }); std.debug.print("{s}: {?}", .{ message, stacktrace });
} else {
_ = message;
_ = stacktrace;
}
unreachable; unreachable;
} }
// Run all tests in imported modules // Run all tests in imported modules
// https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94 // https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94
test "" { test "" {
const testing = std.testing;
testing.refAllDecls(@This()); testing.refAllDecls(@This());
} }
@ -158,7 +165,7 @@ test "" {
// //
// Thank you Zig Contributors! // Thank you Zig Contributors!
export fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 { 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 min = @bitCast(i128, @as(u128, 1 << (128 - 1)));
const max = ~min; const max = ~min;

View file

@ -1150,8 +1150,8 @@ fn strToBytes(arg: RocStr) RocList {
} }
const FromUtf8Result = extern struct { const FromUtf8Result = extern struct {
string: RocStr,
byte_index: usize, byte_index: usize,
string: RocStr,
is_ok: bool, is_ok: bool,
problem_code: Utf8ByteProblem, problem_code: Utf8ByteProblem,
}; };

View file

@ -117,6 +117,16 @@ pub fn decrefC(
return @call(.{ .modifier = always_inline }, decref_ptr_to_refcount, .{ bytes, alignment }); 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( pub fn decref(
bytes_or_null: ?[*]u8, bytes_or_null: ?[*]u8,
data_bytes: usize, data_bytes: usize,

Binary file not shown.

View file

@ -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_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref"; pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";

View file

@ -82,6 +82,7 @@ pub fn canonicalize_annotation(
let mut introduced_variables = IntroducedVariables::default(); let mut introduced_variables = IntroducedVariables::default();
let mut references = MutSet::default(); let mut references = MutSet::default();
let mut aliases = SendMap::default(); let mut aliases = SendMap::default();
let typ = can_annotation_help( let typ = can_annotation_help(
env, env,
annotation, annotation,
@ -249,28 +250,7 @@ fn can_annotation_help(
actual: Box::new(actual), actual: Box::new(actual),
} }
} }
None => { None => Type::Apply(symbol, args),
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)
}
} }
} }
BoundVariable(v) => { BoundVariable(v) => {

View file

@ -2,10 +2,9 @@ use crate::{Backend, Env, Relocation};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::Symbol; 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 roc_mono::layout::{Builtin, Layout};
use std::marker::PhantomData; use std::marker::PhantomData;
use target_lexicon::Triple;
pub mod aarch64; pub mod aarch64;
pub mod x86_64; pub mod x86_64;
@ -211,12 +210,16 @@ pub struct Backend64Bit<
env: &'a Env<'a>, env: &'a Env<'a>,
buf: Vec<'a, u8>, buf: Vec<'a, u8>,
relocs: Vec<'a, Relocation>, relocs: Vec<'a, Relocation>,
proc_name: Option<String>,
is_self_recursive: Option<SelfRecursive>,
last_seen_map: MutMap<Symbol, *const Stmt<'a>>, 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>>, free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
symbol_storage_map: MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>, symbol_storage_map: MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
literal_map: MutMap<Symbol, Literal<'a>>, literal_map: MutMap<Symbol, Literal<'a>>,
join_map: MutMap<JoinPointId, u64>,
// This should probably be smarter than a vec. // 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. // 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>, CC: CallConv<GeneralReg, FloatReg>,
> Backend<'a> for Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> > 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 { Ok(Backend64Bit {
phantom_asm: PhantomData, phantom_asm: PhantomData,
phantom_cc: PhantomData, phantom_cc: PhantomData,
env, env,
proc_name: None,
is_self_recursive: None,
buf: bumpalo::vec![in env.arena], buf: bumpalo::vec![in env.arena],
relocs: bumpalo::vec![in env.arena], relocs: bumpalo::vec![in env.arena],
last_seen_map: MutMap::default(), last_seen_map: MutMap::default(),
@ -259,6 +264,7 @@ impl<
free_map: MutMap::default(), free_map: MutMap::default(),
symbol_storage_map: MutMap::default(), symbol_storage_map: MutMap::default(),
literal_map: MutMap::default(), literal_map: MutMap::default(),
join_map: MutMap::default(),
general_free_regs: bumpalo::vec![in env.arena], general_free_regs: bumpalo::vec![in env.arena],
general_used_regs: bumpalo::vec![in env.arena], general_used_regs: bumpalo::vec![in env.arena],
general_used_callee_saved_regs: MutSet::default(), general_used_callee_saved_regs: MutSet::default(),
@ -275,12 +281,15 @@ impl<
self.env 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.stack_size = 0;
self.free_stack_chunks.clear(); self.free_stack_chunks.clear();
self.fn_call_stack_size = 0; self.fn_call_stack_size = 0;
self.last_seen_map.clear(); self.last_seen_map.clear();
self.layout_map.clear(); self.layout_map.clear();
self.join_map.clear();
self.free_map.clear(); self.free_map.clear();
self.symbol_storage_map.clear(); self.symbol_storage_map.clear();
self.buf.clear(); self.buf.clear();
@ -304,7 +313,7 @@ impl<
&mut self.last_seen_map &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 &mut self.layout_map
} }
@ -330,8 +339,49 @@ impl<
)?; )?;
let setup_offset = out.len(); 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. // Add function body.
out.extend(&self.buf); out.extend(&self.buf[..self.buf.len() - end_jmp_size]);
// Cleanup stack. // Cleanup stack.
CC::cleanup_stack( CC::cleanup_stack(
@ -342,10 +392,13 @@ impl<
)?; )?;
ASM::ret(&mut out); 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 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(
out_relocs.extend(old_relocs.into_iter().map(|reloc| match reloc { old_relocs
.into_iter()
.filter(|reloc| !matches!(reloc, Relocation::JmpToReturn { .. }))
.map(|reloc| match reloc {
Relocation::LocalData { offset, data } => Relocation::LocalData { Relocation::LocalData { offset, data } => Relocation::LocalData {
offset: offset + setup_offset as u64, offset: offset + setup_offset as u64,
data, data,
@ -358,7 +411,9 @@ impl<
offset: offset + setup_offset as u64, offset: offset + setup_offset as u64,
name, name,
}, },
})); Relocation::JmpToReturn { .. } => unreachable!(),
}),
);
Ok((out.into_bump_slice(), out_relocs.into_bump_slice())) Ok((out.into_bump_slice(), out_relocs.into_bump_slice()))
} }
@ -401,29 +456,13 @@ impl<
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<(), String> { ) -> 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. // Save used caller saved regs.
let old_general_used_regs = std::mem::replace( self.push_used_caller_saved_regs_to_stack()?;
&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(&reg) {
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(&reg) {
self.float_free_regs.push(reg);
self.free_to_stack(&saved_sym)?;
} else {
self.float_used_regs.push((reg, saved_sym));
}
}
// Put values in param regs or on top of the stack. // Put values in param regs or on top of the stack.
let tmp_stack_size = CC::store_args( let tmp_stack_size = CC::store_args(
@ -486,7 +525,7 @@ impl<
// Build unconditional jump to the end of this switch. // Build unconditional jump to the end of this switch.
// Since we don't know the offset yet, set it to 0 and overwrite later. // Since we don't know the offset yet, set it to 0 and overwrite later.
let jmp_location = self.buf.len(); 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)); ret_jumps.push((jmp_location, jmp_offset));
// Overwite the original jne with the correct offset. // Overwite the original jne with the correct offset.
@ -510,12 +549,12 @@ impl<
// Update all return jumps to jump past the default case. // Update all return jumps to jump past the default case.
let ret_offset = self.buf.len(); let ret_offset = self.buf.len();
for (jmp_location, start_offset) in ret_jumps.into_iter() { for (jmp_location, start_offset) in ret_jumps.into_iter() {
tmp.clear(); self.update_jmp_imm32_offset(
let jmp_offset = ret_offset - start_offset; &mut tmp,
ASM::jmp_imm32(&mut tmp, jmp_offset as i32); jmp_location as u64,
for (i, byte) in tmp.iter().enumerate() { start_offset as u64,
self.buf[jmp_location + i] = *byte; ret_offset as u64,
} );
} }
Ok(()) Ok(())
} else { } 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( fn build_num_abs(
&mut self, &mut self,
dst: &Symbol, dst: &Symbol,
@ -828,29 +995,26 @@ impl<
fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String> { fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String> {
let val = self.symbol_storage_map.get(sym); let val = self.symbol_storage_map.get(sym);
match val { 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)) => { Some(SymbolStorage::GeneralReg(reg)) => {
// If it fits in a general purpose register, just copy it over to. // 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. // 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); 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)) => { Some(SymbolStorage::FloatReg(reg)) => {
ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg); ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg);
Ok(())
} }
Some(SymbolStorage::Base { offset, size, .. }) => match layout { Some(SymbolStorage::Base { offset, size, .. }) => match layout {
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(Builtin::Int64) => {
ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset);
Ok(())
} }
Layout::Builtin(Builtin::Float64) => { Layout::Builtin(Builtin::Float64) => {
ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset); ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset);
Ok(())
} }
Layout::Struct(field_layouts) => { Layout::Struct(field_layouts) => {
let (offset, size) = (*offset, *size); let (offset, size) = (*offset, *size);
// Nothing to do for empty struct
if size > 0 { if size > 0 {
let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER)
{ {
@ -858,23 +1022,34 @@ impl<
} else { } else {
None None
}; };
CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg) CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg)?;
} else {
// Nothing to do for empty struct
Ok(())
} }
} }
x => Err(format!( x => {
return Err(format!(
"returning symbol with layout, {:?}, is not yet implemented", "returning symbol with layout, {:?}, is not yet implemented",
x x
)), ));
}
}, },
Some(x) => Err(format!( Some(x) => {
return Err(format!(
"returning symbol storage, {:?}, is not yet implemented", "returning symbol storage, {:?}, is not yet implemented",
x 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(&reg) {
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(&reg) {
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!()
};
} }

View file

@ -1,5 +1,7 @@
use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, PTR_SIZE}; 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 bumpalo::collections::Vec;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -191,7 +193,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
} }
for (layout, sym) in args.iter() { for (layout, sym) in args.iter() {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(single_register_integers!()) => {
if general_i < Self::GENERAL_PARAM_REGS.len() { if general_i < Self::GENERAL_PARAM_REGS.len() {
symbol_map.insert( symbol_map.insert(
*sym, *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() { if float_i < Self::FLOAT_PARAM_REGS.len() {
symbol_map.insert( symbol_map.insert(
*sym, *sym,
@ -229,6 +231,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
); );
} }
} }
Layout::Struct(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
"Loading args with layout {:?} not yet implementd", "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. // For most return layouts we will do nothing.
// In some cases, we need to put the return address as the first arg. // In some cases, we need to put the return address as the first arg.
match ret_layout { match ret_layout {
Layout::Builtin(Builtin::Int64) => {} Layout::Builtin(single_register_builtins!()) => {}
Layout::Builtin(Builtin::Float64) => {}
x => { x => {
return Err(format!( return Err(format!(
"receiving return type, {:?}, is not yet implemented", "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() { for (i, layout) in arg_layouts.iter().enumerate() {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(single_register_integers!()) => {
if general_i < Self::GENERAL_PARAM_REGS.len() { if general_i < Self::GENERAL_PARAM_REGS.len() {
// Load the value to the param reg. // Load the value to the param reg.
let dst = Self::GENERAL_PARAM_REGS[general_i]; let dst = Self::GENERAL_PARAM_REGS[general_i];
@ -319,7 +321,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
stack_offset += 8; stack_offset += 8;
} }
} }
Layout::Builtin(Builtin::Float64) => { Layout::Builtin(single_register_floats!()) => {
if float_i < Self::FLOAT_PARAM_REGS.len() { if float_i < Self::FLOAT_PARAM_REGS.len() {
// Load the value to the param reg. // Load the value to the param reg.
let dst = Self::FLOAT_PARAM_REGS[float_i]; let dst = Self::FLOAT_PARAM_REGS[float_i];
@ -371,6 +373,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
stack_offset += 8; stack_offset += 8;
} }
} }
Layout::Struct(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
"calling with arg type, {:?}, is not yet implemented", "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() { for (layout, sym) in args.iter() {
if i < Self::GENERAL_PARAM_REGS.len() { if i < Self::GENERAL_PARAM_REGS.len() {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(single_register_integers!()) => {
symbol_map symbol_map
.insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i])); .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])); symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i]));
} }
Layout::Struct(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
"Loading args with layout {:?} not yet implementd", "Loading args with layout {:?} not yet implementd",
@ -546,8 +550,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
i += 1; i += 1;
} else { } else {
base_offset += match layout { base_offset += match layout {
Layout::Builtin(Builtin::Int64) => 8, Layout::Builtin(single_register_builtins!()) => 8,
Layout::Builtin(Builtin::Float64) => 8,
x => { x => {
return Err(format!( return Err(format!(
"Loading args with layout {:?} not yet implemented", "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. // For most return layouts we will do nothing.
// In some cases, we need to put the return address as the first arg. // In some cases, we need to put the return address as the first arg.
match ret_layout { match ret_layout {
Layout::Builtin(Builtin::Int64) => {} Layout::Builtin(single_register_builtins!()) => {}
Layout::Builtin(Builtin::Float64) => {}
x => { x => {
return Err(format!( return Err(format!(
"receiving return type, {:?}, is not yet implemented", "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() { for (i, layout) in arg_layouts.iter().enumerate() {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(single_register_integers!()) => {
if i < Self::GENERAL_PARAM_REGS.len() { if i < Self::GENERAL_PARAM_REGS.len() {
// Load the value to the param reg. // Load the value to the param reg.
let dst = Self::GENERAL_PARAM_REGS[reg_i]; let dst = Self::GENERAL_PARAM_REGS[reg_i];
@ -646,7 +648,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
stack_offset += 8; stack_offset += 8;
} }
} }
Layout::Builtin(Builtin::Float64) => { Layout::Builtin(single_register_floats!()) => {
if i < Self::FLOAT_PARAM_REGS.len() { if i < Self::FLOAT_PARAM_REGS.len() {
// Load the value to the param reg. // Load the value to the param reg.
let dst = Self::FLOAT_PARAM_REGS[reg_i]; let dst = Self::FLOAT_PARAM_REGS[reg_i];
@ -698,6 +700,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
stack_offset += 8; stack_offset += 8;
} }
} }
Layout::Struct(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
"calling with arg type, {:?}, is not yet implemented", "calling with arg type, {:?}, is not yet implemented",

View file

@ -9,10 +9,10 @@ use roc_module::ident::{ModuleName, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_mono::ir::{ 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 roc_mono::layout::{Builtin, Layout, LayoutIds};
use target_lexicon::Triple;
mod generic64; mod generic64;
mod object_builder; mod object_builder;
@ -46,6 +46,11 @@ pub enum Relocation {
offset: u64, offset: u64,
name: String, name: String,
}, },
JmpToReturn {
inst_loc: u64,
inst_size: u64,
offset: u64,
},
} }
trait Backend<'a> trait Backend<'a>
@ -53,12 +58,13 @@ where
Self: Sized, Self: Sized,
{ {
/// new creates a new backend that will output to the specific Object. /// 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>; fn env(&self) -> &'a Env<'a>;
/// reset resets any registers or other values that may be occupied at the end of a procedure. /// 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 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. /// 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. /// 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> { 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)?; self.load_args(proc.args, &proc.ret_layout)?;
for (layout, sym) in proc.args { for (layout, sym) in proc.args {
self.set_layout_map(*sym, layout)?; self.set_layout_map(*sym, layout)?;
@ -128,6 +137,35 @@ where
self.free_symbols(stmt)?; self.free_symbols(stmt)?;
Ok(()) Ok(())
} }
Stmt::Join {
id,
parameters,
body,
remainder,
} => {
for param in parameters.iter() {
self.set_layout_map(param.symbol, &param.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)), x => Err(format!("the statement, {:?}, is not yet implemented", x)),
} }
} }
@ -141,6 +179,25 @@ where
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<(), String>; ) -> 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. /// build_expr builds the expressions for the specified symbol.
/// The builder must keep track of the symbol because it may be referred to later. /// The builder must keep track of the symbol because it may be referred to later.
fn build_expr( fn build_expr(
@ -263,8 +320,7 @@ where
let layout_map = self.layout_map(); let layout_map = self.layout_map();
for arg in *arguments { for arg in *arguments {
if let Some(layout) = layout_map.get(arg) { 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(*layout);
arg_layouts.push(unsafe { *(*layout) });
} else { } else {
return Err(format!("the argument, {:?}, has no know layout", arg)); 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. /// load_literal sets a symbol to be equal to a literal.
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>; 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>; fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String>;
/// free_symbols will free all symbols for the given statement. /// 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. /// set_layout_map sets the layout for a specific symbol.
fn set_layout_map(&mut self, sym: Symbol, layout: &Layout<'a>) -> Result<(), String> { 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. // Layout map already contains the symbol. We should never need to overwrite.
// If the layout is not the same, that is a bug. // If the layout is not the same, that is a bug.
// There is always an old layout value and this dereference is safe. if &old_layout != layout {
let old_layout = unsafe { *x };
if old_layout != *layout {
Err(format!( Err(format!(
"Overwriting layout for symbol, {:?}. This should never happen. got {:?}, want {:?}", "Overwriting layout for symbol, {:?}. This should never happen. got {:?}, want {:?}",
sym, layout, old_layout sym, layout, old_layout
@ -561,7 +615,7 @@ where
} }
/// layout_map gets the map from symbol to layout. /// 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) { fn create_free_map(&mut self) {
let mut free_map = MutMap::default(); let mut free_map = MutMap::default();

View file

@ -34,7 +34,7 @@ pub fn build_module<'a>(
x86_64::X86_64FloatReg, x86_64::X86_64FloatReg,
x86_64::X86_64Assembler, x86_64::X86_64Assembler,
x86_64::X86_64SystemV, x86_64::X86_64SystemV,
> = Backend::new(env, target)?; > = Backend::new(env)?;
build_object( build_object(
env, env,
procedures, procedures,
@ -52,7 +52,7 @@ pub fn build_module<'a>(
x86_64::X86_64FloatReg, x86_64::X86_64FloatReg,
x86_64::X86_64Assembler, x86_64::X86_64Assembler,
x86_64::X86_64SystemV, x86_64::X86_64SystemV,
> = Backend::new(env, target)?; > = Backend::new(env)?;
build_object( build_object(
env, env,
procedures, procedures,
@ -74,7 +74,7 @@ pub fn build_module<'a>(
aarch64::AArch64FloatReg, aarch64::AArch64FloatReg,
aarch64::AArch64Assembler, aarch64::AArch64Assembler,
aarch64::AArch64Call, aarch64::AArch64Call,
> = Backend::new(env, target)?; > = Backend::new(env)?;
build_object( build_object(
env, env,
procedures, procedures,
@ -191,10 +191,16 @@ fn build_object<'a, B: Backend<'a>>(
let mut layout_ids = roc_mono::layout::LayoutIds::default(); let mut layout_ids = roc_mono::layout::LayoutIds::default();
let mut procs = Vec::with_capacity_in(procedures.len(), env.arena); let mut procs = Vec::with_capacity_in(procedures.len(), env.arena);
for ((sym, layout), proc) in procedures { for ((sym, layout), proc) in procedures {
let fn_name = layout_ids let base_name = layout_ids
.get_toplevel(sym, &layout) .get_toplevel(sym, &layout)
.to_symbol_string(sym, &env.interns); .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( let section_id = output.add_section(
output.segment_name(StandardSegment::Text).to_vec(), output.segment_name(StandardSegment::Text).to_vec(),
format!(".text.{:x}", sym.as_u64()).as_bytes().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)); return Err(format!("failed to find fn symbol for {:?}", name));
} }
} }
Relocation::JmpToReturn { .. } => unreachable!(),
}; };
relocations.push((section_id, elfreloc)); relocations.push((section_id, elfreloc));
} }

View file

@ -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] #[test]
fn f64_abs() { fn f64_abs() {
assert_evals_to!("Num.abs -4.7", 4.7, f64); 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); // assert_evals_to!("0.0 >= 0.0", true, bool);
// } // }
// #[test] #[test]
// fn gen_order_of_arithmetic_ops() { fn gen_order_of_arithmetic_ops() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// 1 + 3 * 7 - 2 1 + 3 * 7 - 2
// "# "#
// ), ),
// 20, 20,
// i64 i64
// ); );
// } }
// #[test] // #[test]
// fn gen_order_of_arithmetic_ops_complex_float() { // fn gen_order_of_arithmetic_ops_complex_float() {
@ -606,59 +624,59 @@ mod dev_num {
// ); // );
// } // }
// #[test] #[test]
// fn if_guard_bind_variable_false() { fn if_guard_bind_variable_false() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// wrapper = \{} -> wrapper = \{} ->
// when 10 is when 10 is
// x if x == 5 -> 0 x if x == 5 -> 0
// _ -> 42 _ -> 42
// wrapper {} wrapper {}
// "# "#
// ), ),
// 42, 42,
// i64 i64
// ); );
// } }
// #[test] #[test]
// fn if_guard_bind_variable_true() { fn if_guard_bind_variable_true() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// wrapper = \{} -> wrapper = \{} ->
// when 10 is when 10 is
// x if x == 10 -> 42 x if x == 10 -> 42
// _ -> 0 _ -> 0
// wrapper {} wrapper {}
// "# "#
// ), ),
// 42, 42,
// i64 i64
// ); );
// } }
// #[test] #[test]
// fn tail_call_elimination() { fn tail_call_elimination() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// sum = \n, accum -> sum = \n, accum ->
// when n is when n is
// 0 -> accum 0 -> accum
// _ -> sum (n - 1) (n + accum) _ -> sum (n - 1) (n + accum)
// sum 1_000_000 0 sum 1_000_000 0
// "# "#
// ), ),
// 500000500000, 500000500000,
// i64 i64
// ); );
// } }
// #[test] // #[test]
// fn int_negate() { // fn int_negate() {

View file

@ -94,10 +94,12 @@ pub fn helper<'a>(
let main_fn_layout = loaded.entry_point.layout; let main_fn_layout = loaded.entry_point.layout;
let mut layout_ids = roc_mono::layout::LayoutIds::default(); 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) .get_toplevel(main_fn_symbol, &main_fn_layout)
.to_symbol_string(main_fn_symbol, &interns); .to_symbol_string(main_fn_symbol, &interns);
let main_fn_name = format!("roc_{}_exposed", main_fn_name_base);
let mut lines = Vec::new(); let mut lines = Vec::new();
// errors whose reporting we delay (so we can see that code gen generates runtime errors) // errors whose reporting we delay (so we can see that code gen generates runtime errors)
let mut delayed_errors = Vec::new(); let mut delayed_errors = Vec::new();
@ -143,13 +145,14 @@ pub fn helper<'a>(
} }
for problem in type_problems { 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(); let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette); report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf); lines.push(buf);
} }
}
for problem in mono_problems { for problem in mono_problems {
let report = mono_problem(&alloc, module_path.clone(), problem); let report = mono_problem(&alloc, module_path.clone(), problem);

View file

@ -10,7 +10,7 @@ use inkwell::types::{BasicType, BasicTypeEnum};
use inkwell::values::{BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue}; use inkwell::values::{BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue};
use inkwell::AddressSpace; use inkwell::AddressSpace;
use roc_module::symbol::Symbol; 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>( pub fn call_bitcode_fn<'a, 'ctx, 'env>(
env: &Env<'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>( pub fn build_transform_caller<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
function: FunctionValue<'ctx>, function: FunctionValue<'ctx>,
closure_data_layout: Layout<'a>, closure_data_layout: LambdaSet<'a>,
argument_layouts: &[Layout<'a>], argument_layouts: &[Layout<'a>],
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
let fn_name: &str = &format!( 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>( fn build_transform_caller_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
roc_function: FunctionValue<'ctx>, roc_function: FunctionValue<'ctx>,
closure_data_layout: Layout<'a>, closure_data_layout: LambdaSet<'a>,
argument_layouts: &[Layout<'a>], argument_layouts: &[Layout<'a>],
fn_name: &str, fn_name: &str,
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
@ -270,7 +270,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
arguments_cast.push(argument); arguments_cast.push(argument);
} }
match closure_data_layout { match closure_data_layout.runtime_representation() {
Layout::Struct(&[]) => { Layout::Struct(&[]) => {
// nothing to add // nothing to add
} }
@ -529,7 +529,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
pub fn build_compare_wrapper<'a, 'ctx, 'env>( pub fn build_compare_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
roc_function: FunctionValue<'ctx>, roc_function: FunctionValue<'ctx>,
closure_data_layout: Layout<'a>, closure_data_layout: LambdaSet<'a>,
layout: &Layout<'a>, layout: &Layout<'a>,
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function"); 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 default = [value1, value2];
let arguments_cast = match closure_data_layout { let arguments_cast = match closure_data_layout.runtime_representation() {
Layout::Struct(&[]) => { Layout::Struct(&[]) => {
// nothing to add // nothing to add
&default &default

View file

@ -3,15 +3,15 @@ use std::path::Path;
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn}; use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
use crate::llvm::build_dict::{ 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, 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_hash::generic_hash;
use crate::llvm::build_list::{ use crate::llvm::build_list::{
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains, self, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat,
list_drop, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_contains, list_drop, list_get_unsafe, list_join, list_keep_errs, list_keep_if,
list_map, list_map2, list_map3, list_map_with_index, list_prepend, list_range, list_repeat, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index, list_prepend,
list_reverse, list_set, list_single, list_sort_with, list_swap, list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with, list_swap,
}; };
use crate::llvm::build_str::{ use crate::llvm::build_str::{
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, 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(); let pmb = PassManagerBuilder::create();
match opt_level { match opt_level {
OptLevel::Normal => { OptLevel::Development | OptLevel::Normal => {
pmb.set_optimization_level(OptimizationLevel::None); pmb.set_optimization_level(OptimizationLevel::None);
} }
OptLevel::Optimize => { OptLevel::Optimize => {
@ -704,7 +704,14 @@ fn promote_to_main_function<'a, 'ctx, 'env>(
let main_fn_name = "$Test.main"; let main_fn_name = "$Test.main";
// Add main to the module. // 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) (main_fn_name, main_fn)
} }
@ -1163,8 +1170,16 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
StructAtIndex { StructAtIndex {
index, structure, .. 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 // extract field from a record
match load_symbol_and_layout(scope, structure) { match (value, layout) {
(StructValue(argument), Layout::Struct(fields)) => { (StructValue(argument), Layout::Struct(fields)) => {
debug_assert!(!fields.is_empty()); debug_assert!(!fields.is_empty());
env.builder 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>( pub fn build_exp_stmt<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, 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); let (value, layout) = load_symbol_and_layout(scope, symbol);
match layout { match layout {
Layout::Builtin(Builtin::List(_)) => { Layout::Builtin(Builtin::List(element_layout)) => {
debug_assert!(value.is_struct_value()); 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 build_list::decref(env, value.into_struct_value(), alignment);
// 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);
} }
Layout::Builtin(Builtin::Dict(_, _)) | Layout::Builtin(Builtin::Set(_)) => { Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
debug_assert!(value.is_struct_value()); 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( build_dict::decref(env, value.into_struct_value(), alignment);
env, }
value.into_struct_value(), 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(); build_dict::decref(env, value.into_struct_value(), alignment);
decrement_with_size_check(env, parent, length, *layout, refcount_ptr);
} }
_ if layout.is_refcounted() => { _ 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), 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>( fn access_index_struct_value<'ctx>(
builder: &Builder<'ctx>, builder: &Builder<'ctx>,
from_value: StructValue<'ctx>, from_value: StructValue<'ctx>,
@ -3088,33 +3080,42 @@ fn expose_function_to_host<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
symbol: Symbol, symbol: Symbol,
roc_function: FunctionValue<'ctx>, roc_function: FunctionValue<'ctx>,
arguments: &[Layout<'a>],
return_layout: Layout<'a>,
) { ) {
// Assumption: there is only one specialization of a host-exposed function // Assumption: there is only one specialization of a host-exposed function
let ident_string = symbol.as_str(&env.interns); let ident_string = symbol.as_str(&env.interns);
let c_function_name: String = format!("roc__{}_1_exposed", ident_string); 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>, env: &Env<'a, 'ctx, 'env>,
ident_string: &str,
roc_function: FunctionValue<'ctx>, roc_function: FunctionValue<'ctx>,
arguments: &[Layout<'a>],
c_function_name: &str, c_function_name: &str,
) -> FunctionValue<'ctx> { ) -> 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( let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena);
&[ for layout in arguments {
context.i64_type().into(), cc_argument_types.push(to_cc_type(env, layout));
roc_function.get_type().get_return_type().unwrap(), }
],
false,
);
// STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` // 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 return_type = wrapper_return_type;
let output_type = return_type.ptr_type(AddressSpace::Generic); let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.push(output_type.into()); 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); debug_info_init!(env, c_function);
// drop the final argument, which is the pointer we write the result into // drop the final argument, which is the pointer we write the result into
let args = c_function.get_params(); let args_vector = c_function.get_params();
let output_arg_index = args.len() - 1; let mut args = args_vector.as_slice();
let args = &args[..args.len() - 1]; 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()); debug_assert_eq!(args.len(), roc_function.get_params().len());
let call_result = { let call_result = {
if env.is_gen_test { if env.is_gen_test {
let roc_wrapper_function = make_exception_catcher(env, roc_function); 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); builder.position_at_end(entry);
let call_wrapped = let call_wrapped = builder.build_call(
builder.build_call(roc_wrapper_function, args, "call_wrapped_function"); roc_wrapper_function,
arguments_for_call,
"call_wrapped_function",
);
call_wrapped.set_call_convention(FAST_CALL_CONV); call_wrapped.set_call_convention(FAST_CALL_CONV);
call_wrapped.try_as_basic_value().left().unwrap() call_wrapped.try_as_basic_value().left().unwrap()
} else { } 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); call_unwrapped.set_call_convention(FAST_CALL_CONV);
let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap(); 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 let output_arg = c_function
.get_nth_param(output_arg_index as u32) .get_nth_param(output_arg_index as u32)
.unwrap() .unwrap()
.into_pointer_value(); .into_pointer_value();
builder.build_store(output_arg, call_result); builder.build_store(output_arg, call_result);
builder.build_return(None); 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 // 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_type = env.context.i64_type().fn_type(&[], false);
let size_function_name: String = format!("roc__{}_size", ident_string); 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); fn_val.set_subprogram(subprogram);
if env.exposed_to_host.contains(&symbol) { 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 fn_val
@ -3758,24 +3959,18 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
builder.position_at_end(entry); builder.position_at_end(entry);
let mut parameters = function_value.get_params(); let mut evaluator_arguments = function_value.get_params();
let output = parameters.pop().unwrap().into_pointer_value();
let closure_data = if let Some(closure_data_ptr) = parameters.pop() { // the final parameter is the output pointer, pop it
let closure_data = let output = evaluator_arguments.pop().unwrap().into_pointer_value();
builder.build_load(closure_data_ptr.into_pointer_value(), "load_closure_data");
env.arena.alloc([closure_data]) as &[_] // NOTE this may be incorrect in the long run
} else { // here we load any argument that is a pointer
&[] for param in evaluator_arguments.iter_mut() {
}; if param.is_pointer_value() {
let mut parameters = parameters;
for param in parameters.iter_mut() {
debug_assert!(param.is_pointer_value());
*param = builder.build_load(param.into_pointer_value(), "load_param"); *param = builder.build_load(param.into_pointer_value(), "load_param");
} }
}
let call_result = if env.is_gen_test { let call_result = if env.is_gen_test {
set_jump_and_catch_long_jump( set_jump_and_catch_long_jump(
@ -3783,13 +3978,13 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
function_value, function_value,
evaluator, evaluator,
evaluator.get_call_conventions(), evaluator.get_call_conventions(),
closure_data, &evaluator_arguments,
result_type, result_type,
) )
} else { } else {
let call = env let call = env
.builder .builder
.build_call(evaluator, closure_data, "call_function"); .build_call(evaluator, &evaluator_arguments, "call_function");
call.set_call_convention(evaluator.get_call_conventions()); call.set_call_convention(evaluator.get_call_conventions());
@ -4090,7 +4285,7 @@ fn roc_function_call<'a, 'ctx, 'env>(
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
transform: FunctionValue<'ctx>, transform: FunctionValue<'ctx>,
closure_data: BasicValueEnum<'ctx>, closure_data: BasicValueEnum<'ctx>,
closure_data_layout: Layout<'a>, lambda_set: LambdaSet<'a>,
closure_data_is_owned: bool, closure_data_is_owned: bool,
argument_layouts: &[Layout<'a>], argument_layouts: &[Layout<'a>],
) -> RocFunctionCall<'ctx> { ) -> RocFunctionCall<'ctx> {
@ -4101,12 +4296,12 @@ fn roc_function_call<'a, 'ctx, 'env>(
.build_alloca(closure_data.get_type(), "closure_data_ptr"); .build_alloca(closure_data.get_type(), "closure_data_ptr");
env.builder.build_store(closure_data_ptr, closure_data); env.builder.build_store(closure_data_ptr, closure_data);
let stepper_caller = let stepper_caller = build_transform_caller(env, transform, lambda_set, argument_layouts)
build_transform_caller(env, transform, closure_data_layout, argument_layouts)
.as_global_value() .as_global_value()
.as_pointer_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_global_value()
.as_pointer_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 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 { match list_layout {
Layout::Builtin(Builtin::EmptyList) => default, Layout::Builtin(Builtin::EmptyList) => default,
@ -4175,7 +4370,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
layout_ids, layout_ids,
function, function,
closure, closure,
*closure_layout, closure_layout,
function_owns_closure_data, function_owns_closure_data,
argument_layouts, argument_layouts,
); );
@ -4205,7 +4400,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
let function = passed_function_at_index!(1); 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) { match (list_layout, return_layout) {
(Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), (Layout::Builtin(Builtin::EmptyList), _) => empty_list(env),
@ -4220,7 +4415,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
layout_ids, layout_ids,
function, function,
closure, closure,
*closure_layout, closure_layout,
function_owns_closure_data, function_owns_closure_data,
argument_layouts, 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 (list2, list2_layout) = load_symbol_and_layout(scope, &args[1]);
let function = passed_function_at_index!(2); 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) { match (list1_layout, list2_layout, return_layout) {
( (
@ -4252,7 +4447,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
layout_ids, layout_ids,
function, function,
closure, closure,
*closure_layout, closure_layout,
function_owns_closure_data, function_owns_closure_data,
argument_layouts, 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 (list3, list3_layout) = load_symbol_and_layout(scope, &args[2]);
let function = passed_function_at_index!(3); 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) { match (list1_layout, list2_layout, list3_layout, return_layout) {
( (
@ -4298,7 +4493,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
layout_ids, layout_ids,
function, function,
closure, closure,
*closure_layout, closure_layout,
function_owns_closure_data, function_owns_closure_data,
argument_layouts, argument_layouts,
); );
@ -4330,7 +4525,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
let function = passed_function_at_index!(1); 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) { match (list_layout, return_layout) {
(Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), (Layout::Builtin(Builtin::EmptyList), _) => empty_list(env),
@ -4345,7 +4540,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
layout_ids, layout_ids,
function, function,
closure, closure,
*closure_layout, closure_layout,
function_owns_closure_data, function_owns_closure_data,
argument_layouts, argument_layouts,
); );
@ -4363,7 +4558,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
let function = passed_function_at_index!(1); 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 { match list_layout {
Layout::Builtin(Builtin::EmptyList) => empty_list(env), Layout::Builtin(Builtin::EmptyList) => empty_list(env),
@ -4375,7 +4570,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
layout_ids, layout_ids,
function, function,
closure, closure,
*closure_layout, closure_layout,
function_owns_closure_data, function_owns_closure_data,
argument_layouts, argument_layouts,
); );
@ -4393,7 +4588,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
let function = passed_function_at_index!(1); 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) { match (list_layout, return_layout) {
(_, Layout::Builtin(Builtin::EmptyList)) (_, Layout::Builtin(Builtin::EmptyList))
@ -4409,7 +4604,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
layout_ids, layout_ids,
function, function,
closure, closure,
*closure_layout, closure_layout,
function_owns_closure_data, function_owns_closure_data,
argument_layouts, argument_layouts,
); );
@ -4437,7 +4632,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
let function = passed_function_at_index!(1); 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) { match (list_layout, return_layout) {
(_, Layout::Builtin(Builtin::EmptyList)) (_, Layout::Builtin(Builtin::EmptyList))
@ -4453,7 +4648,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
layout_ids, layout_ids,
function, function,
closure, closure,
*closure_layout, closure_layout,
function_owns_closure_data, function_owns_closure_data,
argument_layouts, argument_layouts,
); );
@ -4490,7 +4685,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
let function = passed_function_at_index!(1); 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 { match list_layout {
Layout::Builtin(Builtin::EmptyList) => empty_list(env), 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 argument_layouts = &[**element_layout, **element_layout];
let compare_wrapper = 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_global_value()
.as_pointer_value(); .as_pointer_value();
@ -4509,7 +4704,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
layout_ids, layout_ids,
function, function,
closure, closure,
*closure_layout, closure_layout,
function_owns_closure_data, function_owns_closure_data,
argument_layouts, 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 (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (default, default_layout) = load_symbol_and_layout(scope, &args[1]); let (default, default_layout) = load_symbol_and_layout(scope, &args[1]);
let function = passed_function_at_index!(2); 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 { match dict_layout {
Layout::Builtin(Builtin::EmptyDict) => { Layout::Builtin(Builtin::EmptyDict) => {
@ -4546,7 +4741,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
layout_ids, layout_ids,
function, function,
closure, closure,
*closure_layout, closure_layout,
function_owns_closure_data, function_owns_closure_data,
argument_layouts, argument_layouts,
); );
@ -4832,7 +5027,7 @@ fn run_low_level<'a, 'ctx, 'env>(
Usize | Int128 | Int64 | Int32 | Int16 | Int8 => { Usize | Int128 | Int64 | Int32 | Int16 | Int8 => {
build_int_unary_op(env, arg.into_int_value(), arg_builtin, op) 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) build_float_unary_op(env, arg.into_float_value(), op)
} }
_ => { _ => {
@ -4928,7 +5123,7 @@ fn run_low_level<'a, 'ctx, 'env>(
"lt_or_gt", "lt_or_gt",
) )
} }
Float128 | Float64 | Float32 | Float16 => { Float128 | Float64 | Float32 => {
let are_equal = env.builder.build_float_compare( let are_equal = env.builder.build_float_compare(
FloatPredicate::OEQ, FloatPredicate::OEQ,
lhs_arg.into_float_value(), lhs_arg.into_float_value(),
@ -5374,8 +5569,7 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>(
| Builtin::Decimal | Builtin::Decimal
| Builtin::Float128 | Builtin::Float128
| Builtin::Float64 | Builtin::Float64
| Builtin::Float32 | Builtin::Float32 => basic_type_from_builtin(env, builtin),
| Builtin::Float16 => basic_type_from_builtin(env, builtin),
Builtin::Str | Builtin::EmptyStr | Builtin::List(_) | Builtin::EmptyList => { Builtin::Str | Builtin::EmptyStr | Builtin::List(_) | Builtin::EmptyList => {
env.str_list_c_abi().into() env.str_list_c_abi().into()
} }
@ -5738,7 +5932,7 @@ pub fn build_num_binop<'a, 'ctx, 'env>(
rhs_layout, rhs_layout,
op, op,
), ),
Float128 | Float64 | Float32 | Float16 => build_float_binop( Float128 | Float64 | Float32 => build_float_binop(
env, env,
parent, parent,
lhs_arg.into_float_value(), lhs_arg.into_float_value(),

View file

@ -844,3 +844,17 @@ fn dict_symbol_to_zig_dict<'a, 'ctx, 'env>(
) )
.into_struct_value() .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);
}

View file

@ -59,6 +59,15 @@ fn build_hash_layout<'a, 'ctx, 'env>(
val.into_struct_value(), 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) => { Layout::Union(union_layout) => {
build_hash_tag(env, layout_ids, layout, union_layout, seed, val) build_hash_tag(env, layout_ids, layout, union_layout, seed, val)
} }
@ -123,7 +132,6 @@ fn hash_builtin<'a, 'ctx, 'env>(
| Builtin::Float64 | Builtin::Float64
| Builtin::Float32 | Builtin::Float32
| Builtin::Float128 | Builtin::Float128
| Builtin::Float16
| Builtin::Decimal | Builtin::Decimal
| Builtin::Usize => { | Builtin::Usize => {
let hash_bytes = store_and_use_as_u8_ptr(env, val, layout); let hash_bytes = store_and_use_as_u8_ptr(env, val, layout);

View file

@ -1136,3 +1136,17 @@ pub fn store_list<'a, 'ctx, 'env>(
"cast_collection", "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);
}

View file

@ -1,5 +1,5 @@
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn}; 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 crate::llvm::build_list::{allocate_list, call_bitcode_fn_returns_list, store_list};
use inkwell::builder::Builder; use inkwell::builder::Builder;
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; 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 ctx = env.context;
let fields = match env.ptr_bytes { let fields = match env.ptr_bytes {
8 => [ 8 | 4 => [
env.ptr_int().into(), env.ptr_int().into(),
super::convert::zig_str_type(env).into(), super::convert::zig_str_type(env).into(),
env.context.bool_type().into(), env.context.bool_type().into(),
ctx.i8_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!(), _ => unreachable!(),
}; };
let record_type = env.context.struct_type(&fields, false); let record_type = env.context.struct_type(&fields, false);
match env.ptr_bytes { match env.ptr_bytes {
8 => { 8 | 4 => {
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 => {
let result_ptr_cast = env let result_ptr_cast = env
.builder .builder
.build_bitcast( .build_bitcast(

View file

@ -103,7 +103,6 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
Builtin::Float128 => float_cmp(FloatPredicate::OEQ, "eq_f128"), Builtin::Float128 => float_cmp(FloatPredicate::OEQ, "eq_f128"),
Builtin::Float64 => float_cmp(FloatPredicate::OEQ, "eq_f64"), Builtin::Float64 => float_cmp(FloatPredicate::OEQ, "eq_f64"),
Builtin::Float32 => float_cmp(FloatPredicate::OEQ, "eq_f32"), 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::Str => str_equal(env, lhs_val, rhs_val),
Builtin::List(elem) => build_list_eq( Builtin::List(elem) => build_list_eq(
@ -156,6 +155,8 @@ fn build_eq<'a, 'ctx, 'env>(
rhs_val.into_struct_value(), rhs_val.into_struct_value(),
), ),
Layout::LambdaSet(_) => unreachable!("cannot compare closures"),
Layout::Union(union_layout) => build_tag_eq( Layout::Union(union_layout) => build_tag_eq(
env, env,
layout_ids, layout_ids,
@ -245,7 +246,6 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
Builtin::Float128 => float_cmp(FloatPredicate::ONE, "neq_f128"), Builtin::Float128 => float_cmp(FloatPredicate::ONE, "neq_f128"),
Builtin::Float64 => float_cmp(FloatPredicate::ONE, "neq_f64"), Builtin::Float64 => float_cmp(FloatPredicate::ONE, "neq_f64"),
Builtin::Float32 => float_cmp(FloatPredicate::ONE, "neq_f32"), Builtin::Float32 => float_cmp(FloatPredicate::ONE, "neq_f32"),
Builtin::Float16 => float_cmp(FloatPredicate::ONE, "neq_f16"),
Builtin::Str => { Builtin::Str => {
let is_equal = str_equal(env, lhs_val, rhs_val).into_int_value(); 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 => { Layout::RecursivePointer => {
unreachable!("recursion pointers should never be compared directly") unreachable!("recursion pointers should never be compared directly")
} }
Layout::LambdaSet(_) => unreachable!("cannot compare closure"),
} }
} }

View file

@ -27,6 +27,7 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
match layout { match layout {
Struct(sorted_fields) => basic_type_from_record(env, sorted_fields), 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) => { Union(union_layout) => {
use UnionLayout::*; use UnionLayout::*;
@ -96,7 +97,6 @@ pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
Float128 => context.f128_type().as_basic_type_enum(), Float128 => context.f128_type().as_basic_type_enum(),
Float64 => context.f64_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(),
Float32 => context.f32_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(), Dict(_, _) | EmptyDict => zig_dict_type(env).into(),
Set(_) | EmptySet => zig_dict_type(env).into(), Set(_) | EmptySet => zig_dict_type(env).into(),
List(_) | EmptyList => zig_list_type(env).into(), List(_) | EmptyList => zig_list_type(env).into(),

View file

@ -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 let data_ptr = env
.builder .builder
.build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr") .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") .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 env.builder
.build_load(self.value, "get_refcount") .build_load(self.value, "get_refcount")
.into_int_value() .into_int_value()
@ -220,23 +220,55 @@ impl<'ctx> PointerToRefcount<'ctx> {
debug_info_init!(env, parent); 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( call_void_bitcode_fn(
env, env,
&[ &[
env.builder.build_bitcast( env.builder.build_bitcast(
parent.get_nth_param(0).unwrap(), pointer,
env.ptr_int().ptr_type(AddressSpace::Generic), env.ptr_int().ptr_type(AddressSpace::Generic),
"foo", "to_isize_ptr",
), ),
alignment.into(), alignment.into(),
], ],
roc_builtins::bitcode::UTILS_DECREF, 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>( fn modify_refcount_struct<'a, 'ctx, 'env>(
@ -594,6 +626,14 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
Some(function) 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>, fn_val: FunctionValue<'ctx>,
) { ) {
let tags = union_layout_tags(env.arena, &union_layout); let tags = union_layout_tags(env.arena, &union_layout);
let is_nullable = union_layout.is_nullable();
debug_assert!(!tags.is_empty()); debug_assert!(!tags.is_empty());
let context = &env.context; 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 should_recurse_block = env.context.append_basic_block(parent, "should_recurse");
let ctx = env.context; 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 is_null = env.builder.build_is_null(value_ptr, "is_null");
let then_block = ctx.append_basic_block(parent, "then"); let then_block = ctx.append_basic_block(parent, "then");
@ -1201,6 +1240,12 @@ enum DecOrReuse {
Reuse, 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)] #[allow(clippy::too_many_arguments)]
fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
env: &Env<'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 call_mode = mode_to_call_mode(decrement_fn, mode);
let builder = env.builder; 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 // next, make a jump table for all possible values of the tag_id
let mut cases = Vec::with_capacity_in(tags.len(), env.arena); 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() { for (tag_id, field_layouts) in tags.iter().enumerate() {
// if none of the fields are or contain anything refcounted, just move on // if none of the fields are or contain anything refcounted, just move on
if !field_layouts if fields_need_no_refcounting(field_layouts) {
.iter()
.any(|x| x.is_refcounted() || x.contains_refcounted())
{
continue; continue;
} }
@ -1346,11 +1373,13 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
cases.reverse(); cases.reverse();
if cases.len() == 1 && !switch_needed { if matches!(
// there is only one tag in total; we don't need a switch union_layout,
// this is essential for nullable unwrapped layouts, UnionLayout::NullableUnwrapped { .. } | UnionLayout::NonNullableUnwrapped { .. }
// because the `else` branch below would try to read its ) {
// (nonexistant) tag id 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(); let (_, only_branch) = cases.pop().unwrap();
env.builder.build_unconditional_branch(only_branch); env.builder.build_unconditional_branch(only_branch);
} else { } else {
@ -1444,7 +1473,6 @@ fn build_reuse_rec_union_help<'a, 'ctx, 'env>(
dec_function: FunctionValue<'ctx>, dec_function: FunctionValue<'ctx>,
) { ) {
let tags = union_layout_tags(env.arena, &union_layout); let tags = union_layout_tags(env.arena, &union_layout);
let is_nullable = union_layout.is_nullable();
debug_assert!(!tags.is_empty()); 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 should_recurse_block = env.context.append_basic_block(parent, "should_recurse");
let ctx = env.context; 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 is_null = env.builder.build_is_null(value_ptr, "is_null");
let then_block = ctx.append_basic_block(parent, "then"); 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 // this function returns void
builder.build_return(None); 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",
)
}

View file

@ -5,6 +5,12 @@
- Initial bringup - Initial bringup
- Get a wasm backend working for some of the number tests. - 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. - 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 - Integration
- Move wasm files to `gen_dev/src/wasm` - 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. - 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.

View file

@ -1,12 +1,16 @@
use parity_wasm::builder; use parity_wasm::builder;
use parity_wasm::builder::{CodeLocation, ModuleBuilder}; 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_collections::all::MutMap;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::ir::{CallType, Expr, Literal, Proc, Stmt}; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
use roc_mono::layout::{Builtin, Layout}; 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. // 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) // Follow Emscripten's example by using 1kB (4 bytes would probably do)
@ -21,24 +25,126 @@ struct LabelId(u32);
#[derive(Debug)] #[derive(Debug)]
struct SymbolStorage(LocalId, WasmLayout); struct SymbolStorage(LocalId, WasmLayout);
// See README for background information on Wasm locals, memory and function calls
#[derive(Debug)] #[derive(Debug)]
struct WasmLayout { pub enum WasmLayout {
value_type: ValueType, // Most number types can fit in a Wasm local without any stack memory.
stack_memory: u32, // 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 { 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 { match layout {
Layout::Builtin(Builtin::Int64) => Ok(Self { Layout::Builtin(Builtin::Int128) => Self::StackMemory(size),
value_type: ValueType::I64, Layout::Builtin(Builtin::Int64) => Self::LocalOnly(I64, size),
stack_memory: 0, Layout::Builtin(Builtin::Int32) => Self::LocalOnly(I32, size),
}), Layout::Builtin(Builtin::Int16) => Self::LocalOnly(I32, size),
Layout::Builtin(Builtin::Float64) => Ok(Self { Layout::Builtin(Builtin::Int8) => Self::LocalOnly(I32, size),
value_type: ValueType::F64, Layout::Builtin(Builtin::Int1) => Self::LocalOnly(I32, size),
stack_memory: 0, Layout::Builtin(Builtin::Usize) => Self::LocalOnly(I32, size),
}), Layout::Builtin(Builtin::Decimal) => Self::StackMemory(size),
x => Err(format!("layout, {:?}, not implemented yet", x)), 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 // Functions: internal state & IR mappings
stack_memory: u32, stack_memory: u32,
symbol_storage_map: MutMap<Symbol, SymbolStorage>, 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> { impl<'a> WasmBackend<'a> {
@ -84,7 +192,8 @@ impl<'a> WasmBackend<'a> {
// Functions: internal state & IR mappings // Functions: internal state & IR mappings
stack_memory: 0, stack_memory: 0,
symbol_storage_map: MutMap::default(), 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> { pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result<u32, String> {
let ret_layout = WasmLayout::new(&proc.ret_layout)?; 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 if let WasmLayout::StackMemory { .. } = ret_layout {
return Err(format!( return Err(format!(
"Not yet implemented: Return in stack memory for non-primtitive layouts like {:?}", "Not yet implemented: Returning values to callee stack memory {:?} {:?}",
proc.ret_layout proc.name, sym
)); ));
} }
self.ret_type = ret_layout.value_type; self.ret_type = ret_layout.value_type();
self.arg_types.reserve(proc.args.len()); self.arg_types.reserve(proc.args.len());
for (layout, symbol) in proc.args { for (layout, symbol) in proc.args {
let wasm_layout = WasmLayout::new(layout)?; let wasm_layout = WasmLayout::new(layout);
self.arg_types.push(wasm_layout.value_type); self.arg_types.push(wasm_layout.value_type());
self.insert_local(wasm_layout, *symbol); self.insert_local(wasm_layout, *symbol);
} }
@ -147,10 +256,10 @@ impl<'a> WasmBackend<'a> {
} }
fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol) -> LocalId { 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(); let index = self.symbol_storage_map.len();
if index >= self.arg_types.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 local_id = LocalId(index as u32);
let storage = SymbolStorage(local_id, layout); let storage = SymbolStorage(local_id, layout);
@ -174,6 +283,27 @@ impl<'a> WasmBackend<'a> {
Ok(()) 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> { fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
match stmt { match stmt {
// This pattern is a simple optimisation to get rid of one local and two instructions per proc. // 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) => { 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); let local_id = self.insert_local(wasm_layout, *sym);
self.build_expr(sym, expr, layout)?; 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(&parameter.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)), x => Err(format!("statement not yet implemented: {:?}", x)),
} }
} }
@ -218,7 +455,7 @@ impl<'a> WasmBackend<'a> {
layout: &Layout<'a>, layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
match expr { match expr {
Expr::Literal(lit) => self.load_literal(lit), Expr::Literal(lit) => self.load_literal(lit, layout),
Expr::Call(roc_mono::ir::Call { Expr::Call(roc_mono::ir::Call {
call_type, 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 { 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) => { 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(()) Ok(())
} }
Literal::Float(x) => { Literal::Float(x) => {
let val: f64 = *x; let instruction = match layout {
self.instructions.push(F64Const(val.to_bits())); 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(()) Ok(())
} }
x => Err(format!("loading literal, {:?}, is not yet implemented", x)), x => Err(format!("loading literal, {:?}, is not yet implemented", x)),
@ -270,8 +530,8 @@ impl<'a> WasmBackend<'a> {
for arg in args { for arg in args {
self.load_from_symbol(arg)?; self.load_from_symbol(arg)?;
} }
let wasm_layout = WasmLayout::new(return_layout)?; let wasm_layout = WasmLayout::new(return_layout);
self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type)?; self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?;
Ok(()) Ok(())
} }
@ -293,6 +553,22 @@ impl<'a> WasmBackend<'a> {
ValueType::F32 => &[F32Add], ValueType::F32 => &[F32Add],
ValueType::F64 => &[F64Add], 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)); return Err(format!("unsupported low-level op {:?}", lowlevel));
} }

View file

@ -3,7 +3,7 @@ pub mod from_wasm32_memory;
use bumpalo::Bump; use bumpalo::Bump;
use parity_wasm::builder; use parity_wasm::builder;
use parity_wasm::elements::Internal; use parity_wasm::elements::{Instruction, Internal, ValueType};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
@ -12,6 +12,17 @@ use roc_mono::layout::LayoutIds;
use crate::backend::WasmBackend; 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 struct Env<'a> {
pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot
pub interns: Interns, pub interns: Interns,
@ -21,12 +32,37 @@ pub struct Env<'a> {
pub fn build_module<'a>( pub fn build_module<'a>(
env: &'a Env, env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, 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 backend = WasmBackend::new();
let mut layout_ids = LayoutIds::default(); 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 { 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) { if env.exposed_to_host.contains(&sym) {
let fn_name = layout_ids let fn_name = layout_ids
.get_toplevel(sym, &layout) .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 MIN_MEMORY_SIZE_KB: u32 = 1024;
const PAGE_SIZE_KB: u32 = 64; const PAGE_SIZE_KB: u32 = 64;
@ -48,15 +89,18 @@ pub fn build_module<'a>(
.with_min(MIN_MEMORY_SIZE_KB / PAGE_SIZE_KB) .with_min(MIN_MEMORY_SIZE_KB / PAGE_SIZE_KB)
.build(); .build();
backend.builder.push_memory(memory); backend.builder.push_memory(memory);
let memory_export = builder::export() let memory_export = builder::export()
.field("memory") .field("memory")
.with_internal(Internal::Memory(0)) .with_internal(Internal::Memory(0))
.build(); .build();
backend.builder.push_export(memory_export); backend.builder.push_export(memory_export);
let module = backend.builder.build(); let stack_pointer_global = builder::global()
module .with_type(PTR_TYPE)
.to_bytes() .mutable()
.map_err(|e| -> String { format!("Error serialising Wasm module {:?}", e) }) .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))
} }

View file

@ -1,6 +1,10 @@
use roc_can::builtins::builtin_defs_map; use roc_can::builtins::builtin_defs_map;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
// use roc_std::{RocDec, RocList, RocOrder, RocStr}; // 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 { fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n"); 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)] #[allow(dead_code)]
pub fn helper_wasm<'a>( pub fn helper_wasm<'a, T: Wasm32TestResult>(
arena: &'a bumpalo::Bump, arena: &'a bumpalo::Bump,
src: &str, src: &str,
stdlib: &'a roc_builtins::std::StdLib, stdlib: &'a roc_builtins::std::StdLib,
_is_gen_test: bool, _result_type_dummy: &T,
_ignore_problems: bool,
) -> wasmer::Instance { ) -> wasmer::Instance {
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -91,7 +94,11 @@ pub fn helper_wasm<'a>(
exposed_to_host, 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) // for debugging (e.g. with wasm2wat)
if false { if false {
@ -128,44 +135,40 @@ pub fn helper_wasm<'a>(
} }
#[allow(dead_code)] #[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 where
T: Copy, T: FromWasm32Memory + Wasm32TestResult,
{ {
let arena = bumpalo::Bump::new(); let arena = bumpalo::Bump::new();
// NOTE the stdlib must be in the arena; just taking a reference will segfault // NOTE the stdlib must be in the arena; just taking a reference will segfault
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
let is_gen_test = true; let instance = crate::helpers::eval::helper_wasm(&arena, src, stdlib, &expected);
let instance =
crate::helpers::eval::helper_wasm(&arena, src, stdlib, is_gen_test, ignore_problems);
let main_function = instance.exports.get_function("#UserApp_main_1").unwrap(); 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)), Err(e) => Err(format!("{:?}", e)),
Ok(result) => { Ok(result) => {
let integer = match result[0] { let address = match result[0] {
wasmer::Value::I64(a) => a, wasmer::Value::I32(a) => a,
wasmer::Value::F64(a) => a.to_bits() as i64,
_ => panic!(), _ => panic!(),
}; };
let output_ptr: &T; let output = <T as FromWasm32Memory>::decode(memory, address as u32);
unsafe {
output_ptr = std::mem::transmute::<&i64, &T>(&integer);
}
Ok(*output_ptr) Ok(output)
} }
} }
} }
#[macro_export] #[macro_export]
macro_rules! assert_wasm_evals_to { macro_rules! assert_wasm_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr, $ignore_problems:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
match $crate::helpers::eval::assert_wasm_evals_to_help::<$ty>($src, $ignore_problems) { match $crate::helpers::eval::assert_wasm_evals_to_help::<$ty>($src, $expected) {
Err(msg) => println!("{:?}", msg), Err(msg) => println!("{:?}", msg),
Ok(actual) => { Ok(actual) => {
#[allow(clippy::bool_assert_comparison)] #[allow(clippy::bool_assert_comparison)]
@ -175,11 +178,11 @@ macro_rules! assert_wasm_evals_to {
}; };
($src:expr, $expected:expr, $ty:ty) => { ($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) => { ($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) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument. // 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);
} }
}; };
} }

View file

@ -2,6 +2,7 @@ extern crate bumpalo;
#[macro_use] #[macro_use]
pub mod eval; pub mod eval;
pub mod wasm32_test_result;
/// Used in the with_larger_debug_stack() function, for tests that otherwise /// 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) /// run out of stack space in debug builds (but don't in --release builds)

View 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,
)
}
}

View file

@ -11,7 +11,7 @@ extern crate libc;
mod helpers; mod helpers;
#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] #[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] #[test]
fn i64_values() { fn i64_values() {
assert_evals_to!("0", 0, i64); assert_evals_to!("0", 0, i64);
@ -36,6 +36,101 @@ mod dev_num {
assert_evals_to!(&format!("{:0.1}", f64::MAX), f64::MAX, f64); 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] #[test]
fn gen_add_i64() { fn gen_add_i64() {
assert_evals_to!( assert_evals_to!(
@ -49,44 +144,149 @@ mod dev_num {
); );
} }
// #[test] #[test]
// fn gen_add_f64() { fn if_then_else() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// 1.1 + 2.4 + 3 cond : Bool
// "# cond = True
// ),
// 6.5,
// f64
// );
// }
// #[test] if cond then
// fn gen_sub_i64() { 0
// assert_evals_to!( else
// indoc!( 1
// r#" "#
// 1 - 2 - 3 ),
// "# 0,
// ), i64
// -4, );
// i64 }
// );
// }
// #[test] #[test]
// fn gen_mul_i64() { fn rgb_red() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// 2 * 4 * 6 when Red is
// "# Red -> 111
// ), Green -> 222
// 48, Blue -> 333
// i64 "#
// ); ),
// } 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] #[test]
fn i64_force_stack() { fn i64_force_stack() {
@ -371,18 +571,18 @@ mod dev_num {
// ); // );
// } // }
// #[test] #[test]
// fn gen_sub_f64() { fn gen_sub_f64() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// 1.5 - 2.4 - 3 1.5 - 2.4 - 3
// "# "#
// ), ),
// -3.9, -3.9,
// f64 f64
// ); );
// } }
// #[test] // #[test]
// fn gen_div_i64() { // fn gen_div_i64() {
@ -580,31 +780,31 @@ mod dev_num {
// assert_evals_to!("0.0 >= 0.0", true, bool); // assert_evals_to!("0.0 >= 0.0", true, bool);
// } // }
// #[test] #[test]
// fn gen_order_of_arithmetic_ops() { fn gen_order_of_arithmetic_ops() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// 1 + 3 * 7 - 2 1 + 3 * 7 - 2
// "# "#
// ), ),
// 20, 20,
// i64 i64
// ); );
// } }
// #[test] #[test]
// fn gen_order_of_arithmetic_ops_complex_float() { fn gen_order_of_arithmetic_ops_complex_float() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// 3 - 48 * 2.0 3 - 48 * 2.0
// "# "#
// ), ),
// -93.0, -93.0,
// f64 f64
// ); );
// } }
// #[test] // #[test]
// fn if_guard_bind_variable_false() { // fn if_guard_bind_variable_false() {

View file

@ -5,7 +5,7 @@ extern crate indoc;
mod helpers; mod helpers;
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] #[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod dev_records { mod wasm_records {
// #[test] // #[test]
// fn basic_record() { // fn basic_record() {
// assert_evals_to!( // assert_evals_to!(
@ -389,18 +389,18 @@ mod dev_records {
// // ); // // );
// // } // // }
// #[test] #[test]
// fn i64_record1_literal() { fn i64_record1_literal() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 3 } { a: 3 }
// "# "#
// ), ),
// 3, 3,
// i64 i64
// ); );
// } }
// // #[test] // // #[test]
// // fn i64_record9_literal() { // // fn i64_record9_literal() {
@ -428,21 +428,21 @@ mod dev_records {
// // ); // // );
// // } // // }
// #[test] #[test]
// fn bool_literal() { fn bool_literal() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// x : Bool x : Bool
// x = True x = True
// x x
// "# "#
// ), ),
// true, true,
// bool bool
// ); );
// } }
// #[test] // #[test]
// fn optional_field_when_use_default() { // fn optional_field_when_use_default() {

View file

@ -723,7 +723,21 @@ pub struct MonomorphizedModule<'a> {
impl<'a> MonomorphizedModule<'a> { impl<'a> MonomorphizedModule<'a> {
pub fn total_problems(&self) -> usize { 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, &mut state.procedures,
); );
Proc::insert_refcount_operations(arena, &mut state.procedures);
// display the mono IR of the module, for debug purposes // display the mono IR of the module, for debug purposes
if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS { if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS {
let procs_string = state let procs_string = state
@ -2114,6 +2126,8 @@ fn update<'a>(
println!("{}", result); 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 // This is not safe with the new non-recursive RC updates that we do for tag unions
// //
// Proc::optimize_refcount_operations( // Proc::optimize_refcount_operations(
@ -3927,7 +3941,7 @@ fn make_specializations<'a>(
); );
let external_specializations_requested = procs.externals_we_need.clone(); 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(); let make_specializations_end = SystemTime::now();
module_timing.make_specializations = make_specializations_end module_timing.make_specializations = make_specializations_end

View file

@ -589,9 +589,9 @@ fn call_spec(
let index = builder.add_make_tuple(block, &[])?; let index = builder.add_make_tuple(block, &[])?;
let argument = if closure_env_layout.is_none() { let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[first, index])? builder.add_make_tuple(block, &[index, first])?
} else { } 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)?; builder.add_call(block, spec_var, module, name, argument)?;
} }
@ -1191,6 +1191,11 @@ fn layout_spec_help(
match layout { match layout {
Builtin(builtin) => builtin_spec(builder, builtin, when_recursive), Builtin(builtin) => builtin_spec(builder, builtin, when_recursive),
Struct(fields) => build_recursive_tuple_type(builder, fields, 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) => { Union(union_layout) => {
let variant_types = build_variant_types(builder, union_layout)?; let variant_types = build_variant_types(builder, union_layout)?;
@ -1236,7 +1241,7 @@ fn builtin_spec(
match builtin { match builtin {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize => builder.add_tuple_type(&[]), 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), Str | EmptyStr => str_type(builder),
Dict(key_layout, value_layout) => { Dict(key_layout, value_layout) => {
let value_type = layout_spec_help(builder, value_layout, when_recursive)?; let value_type = layout_spec_help(builder, value_layout, when_recursive)?;

View file

@ -1,6 +1,7 @@
use crate::exhaustive::{Ctor, RenderAs, TagId, Union}; use crate::exhaustive::{Ctor, RenderAs, TagId, Union};
use crate::ir::{ 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 crate::layout::{Builtin, Layout, LayoutCache, UnionLayout};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
@ -85,8 +86,8 @@ enum Test<'a> {
union: crate::exhaustive::Union, union: crate::exhaustive::Union,
arguments: Vec<(Pattern<'a>, Layout<'a>)>, arguments: Vec<(Pattern<'a>, Layout<'a>)>,
}, },
IsInt(i128), IsInt(i128, IntPrecision),
IsFloat(u64), IsFloat(u64, FloatPrecision),
IsDecimal(RocDec), IsDecimal(RocDec),
IsStr(Box<str>), IsStr(Box<str>),
IsBit(bool), IsBit(bool),
@ -95,6 +96,7 @@ enum Test<'a> {
num_alts: usize, num_alts: usize,
}, },
} }
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
impl<'a> Hash for Test<'a> { impl<'a> Hash for Test<'a> {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
@ -106,13 +108,15 @@ impl<'a> Hash for Test<'a> {
tag_id.hash(state); tag_id.hash(state);
// The point of this custom implementation is to not hash the tag arguments // The point of this custom implementation is to not hash the tag arguments
} }
IsInt(v) => { IsInt(v, width) => {
state.write_u8(1); state.write_u8(1);
v.hash(state); v.hash(state);
width.hash(state);
} }
IsFloat(v) => { IsFloat(v, width) => {
state.write_u8(2); state.write_u8(2);
v.hash(state); v.hash(state);
width.hash(state);
} }
IsStr(v) => { IsStr(v) => {
state.write_u8(3); 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::IsCtor { union, .. } => number_of_tests == union.alternatives.len(),
Test::IsByte { num_alts, .. } => number_of_tests == *num_alts, Test::IsByte { num_alts, .. } => number_of_tests == *num_alts,
Test::IsBit(_) => number_of_tests == 2, Test::IsBit(_) => number_of_tests == 2,
Test::IsInt(_) => false, Test::IsInt(_, _) => false,
Test::IsFloat(_) => false, Test::IsFloat(_, _) => false,
Test::IsDecimal(_) => false, Test::IsDecimal(_) => false,
Test::IsStr(_) => false, Test::IsStr(_) => false,
} }
@ -561,8 +565,8 @@ fn test_at_path<'a>(
tag_id: *tag_id, tag_id: *tag_id,
num_alts: union.alternatives.len(), num_alts: union.alternatives.len(),
}, },
IntLiteral(v) => IsInt(*v), IntLiteral(v, precision) => IsInt(*v, *precision),
FloatLiteral(v) => IsFloat(*v), FloatLiteral(v, precision) => IsFloat(*v, *precision),
DecimalLiteral(v) => IsDecimal(*v), DecimalLiteral(v) => IsDecimal(*v),
StrLiteral(v) => IsStr(v.clone()), StrLiteral(v) => IsStr(v.clone()),
}; };
@ -807,8 +811,9 @@ fn to_relevant_branch_help<'a>(
_ => None, _ => None,
}, },
IntLiteral(int) => match test { IntLiteral(int, p1) => match test {
IsInt(is_int) if int == *is_int => { IsInt(is_int, p2) if int == *is_int => {
debug_assert_eq!(p1, *p2);
start.extend(end); start.extend(end);
Some(Branch { Some(Branch {
goal: branch.goal, goal: branch.goal,
@ -819,8 +824,9 @@ fn to_relevant_branch_help<'a>(
_ => None, _ => None,
}, },
FloatLiteral(float) => match test { FloatLiteral(float, p1) => match test {
IsFloat(test_float) if float == *test_float => { IsFloat(test_float, p2) if float == *test_float => {
debug_assert_eq!(p1, *p2);
start.extend(end); start.extend(end);
Some(Branch { Some(Branch {
goal: branch.goal, goal: branch.goal,
@ -928,8 +934,8 @@ fn needs_tests(pattern: &Pattern) -> bool {
| AppliedTag { .. } | AppliedTag { .. }
| BitLiteral { .. } | BitLiteral { .. }
| EnumLiteral { .. } | EnumLiteral { .. }
| IntLiteral(_) | IntLiteral(_, _)
| FloatLiteral(_) | FloatLiteral(_, _)
| DecimalLiteral(_) | DecimalLiteral(_)
| StrLiteral(_) => true, | StrLiteral(_) => true,
} }
@ -1280,22 +1286,22 @@ fn test_to_equality<'a>(
_ => unreachable!("{:?}", (cond_layout, union)), _ => unreachable!("{:?}", (cond_layout, union)),
} }
} }
Test::IsInt(test_int) => { Test::IsInt(test_int, precision) => {
// TODO don't downcast i128 here // TODO don't downcast i128 here
debug_assert!(test_int <= i64::MAX as i128); debug_assert!(test_int <= i64::MAX as i128);
let lhs = Expr::Literal(Literal::Int(test_int as i128)); let lhs = Expr::Literal(Literal::Int(test_int as i128));
let lhs_symbol = env.unique_symbol(); 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) (stores, lhs_symbol, rhs_symbol, None)
} }
Test::IsFloat(test_int) => { Test::IsFloat(test_int, precision) => {
// TODO maybe we can actually use i64 comparison here? // TODO maybe we can actually use i64 comparison here?
let test_float = f64::from_bits(test_int as u64); let test_float = f64::from_bits(test_int as u64);
let lhs = Expr::Literal(Literal::Float(test_float)); let lhs = Expr::Literal(Literal::Float(test_float));
let lhs_symbol = env.unique_symbol(); 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) (stores, lhs_symbol, rhs_symbol, None)
} }
@ -1303,7 +1309,7 @@ fn test_to_equality<'a>(
Test::IsDecimal(test_dec) => { Test::IsDecimal(test_dec) => {
let lhs = Expr::Literal(Literal::Int(test_dec.0)); let lhs = Expr::Literal(Literal::Int(test_dec.0));
let lhs_symbol = env.unique_symbol(); 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) (stores, lhs_symbol, rhs_symbol, None)
} }
@ -1737,8 +1743,8 @@ fn decide_to_branching<'a>(
); );
let tag = match test { let tag = match test {
Test::IsInt(v) => v as u64, Test::IsInt(v, _) => v as u64,
Test::IsFloat(v) => v as u64, Test::IsFloat(v, _) => v as u64,
Test::IsBit(v) => v as u64, Test::IsBit(v) => v as u64,
Test::IsByte { tag_id, .. } => tag_id as u64, Test::IsByte { tag_id, .. } => tag_id as u64,
Test::IsCtor { tag_id, .. } => tag_id as u64, Test::IsCtor { tag_id, .. } => tag_id as u64,

View file

@ -65,8 +65,8 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
use crate::ir::Pattern::*; use crate::ir::Pattern::*;
match pattern { match pattern {
IntLiteral(v) => Literal(Literal::Int(*v)), IntLiteral(v, _) => Literal(Literal::Int(*v)),
FloatLiteral(v) => Literal(Literal::Float(*v)), FloatLiteral(v, _) => Literal(Literal::Float(*v)),
DecimalLiteral(v) => Literal(Literal::Decimal(*v)), DecimalLiteral(v) => Literal(Literal::Decimal(*v)),
StrLiteral(v) => Literal(Literal::Str(v.clone())), StrLiteral(v) => Literal(Literal::Str(v.clone())),

View file

@ -54,6 +54,7 @@ macro_rules! return_on_layout_error_help {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum OptLevel { pub enum OptLevel {
Development,
Normal, Normal,
Optimize, Optimize,
} }
@ -272,6 +273,33 @@ impl<'a> Proc<'a> {
proc.body = b.clone(); 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)] #[derive(Clone, Debug)]
@ -350,7 +378,7 @@ pub enum InProgressProc<'a> {
impl<'a> Procs<'a> { impl<'a> Procs<'a> {
pub fn get_specialized_procs_without_rc( pub fn get_specialized_procs_without_rc(
self, self,
arena: &'a Bump, env: &mut Env<'a, '_>,
) -> MutMap<(Symbol, ProcLayout<'a>), Proc<'a>> { ) -> MutMap<(Symbol, ProcLayout<'a>), Proc<'a>> {
let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher()); let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
@ -376,16 +404,7 @@ impl<'a> Procs<'a> {
panic!(); panic!();
} }
Done(mut proc) => { Done(mut proc) => {
use self::SelfRecursive::*; proc.make_tail_recursive(env);
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,
);
}
result.insert(key, proc); result.insert(key, proc);
} }
@ -395,86 +414,6 @@ impl<'a> Procs<'a> {
result 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! // TODO trim down these arguments!
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn insert_named( pub fn insert_named(
@ -756,7 +695,20 @@ impl<'a> Procs<'a> {
// the `layout` is a function pointer, while `_ignore_layout` can be a // the `layout` is a function pointer, while `_ignore_layout` can be a
// closure. We only specialize functions, storing this value with a closure // closure. We only specialize functions, storing this value with a closure
// layout will give trouble. // 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) => { Err(error) => {
panic!("TODO generate a RuntimeError message for {:?}", 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((*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) (args.into_bump_slice(), *ret_layout)
} }
@ -1981,7 +1933,29 @@ fn specialize_external<'a>(
match layout { match layout {
RawFunctionLayout::Function(argument_layouts, lambda_set, return_layout) => { RawFunctionLayout::Function(argument_layouts, lambda_set, return_layout) => {
let assigned = env.unique_symbol(); 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)); let hole = env.arena.alloc(Stmt::Ret(assigned));
@ -1989,20 +1963,16 @@ fn specialize_external<'a>(
env, env,
lambda_set, lambda_set,
Symbol::ARG_CLOSURE, Symbol::ARG_CLOSURE,
env.arena.alloc([unit]), argument_symbols.into_bump_slice(),
argument_layouts, argument_layouts,
*return_layout, *return_layout,
assigned, assigned,
hole, hole,
); );
let body = let_empty_struct(unit, env.arena.alloc(body));
let proc = Proc { let proc = Proc {
name, name,
args: env args: proc_arguments.into_bump_slice(),
.arena
.alloc([(lambda_set.runtime_representation(), Symbol::ARG_CLOSURE)]),
body, body,
closure_data_layout: None, closure_data_layout: None,
ret_layout: *return_layout, ret_layout: *return_layout,
@ -2013,7 +1983,7 @@ fn specialize_external<'a>(
let top_level = ProcLayout::new( let top_level = ProcLayout::new(
env.arena, env.arena,
env.arena.alloc([lambda_set.runtime_representation()]), top_level_arguments.into_bump_slice(),
*return_layout, *return_layout,
); );
@ -2061,7 +2031,7 @@ fn specialize_external<'a>(
env.subs.rollback_to(snapshot); env.subs.rollback_to(snapshot);
let closure_data_layout = match opt_closure_layout { 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(&[]), None => Layout::Struct(&[]),
}; };
@ -2211,7 +2181,7 @@ fn specialize_external<'a>(
env.subs.rollback_to(snapshot); env.subs.rollback_to(snapshot);
let closure_data_layout = match opt_closure_layout { 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, None => None,
}; };
@ -2322,7 +2292,7 @@ fn build_specialized_proc<'a>(
Some(lambda_set) if pattern_symbols.last() == Some(&Symbol::ARG_CLOSURE) => { 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`, // 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) // 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!( debug_assert_eq!(
pattern_layouts_len + 1, pattern_layouts_len + 1,
@ -2359,7 +2329,7 @@ fn build_specialized_proc<'a>(
} }
Ordering::Greater => { Ordering::Greater => {
if pattern_symbols.is_empty() { if pattern_symbols.is_empty() {
let ret_layout = lambda_set.runtime_representation(); let ret_layout = Layout::LambdaSet(lambda_set);
Ok(FunctionPointerBody { Ok(FunctionPointerBody {
closure: None, closure: None,
ret_layout, ret_layout,
@ -2544,7 +2514,7 @@ where
let raw = if procs.module_thunks.contains(&proc_name) { let raw = if procs.module_thunks.contains(&proc_name) {
match raw { match raw {
RawFunctionLayout::Function(_, lambda_set, _) => { RawFunctionLayout::Function(_, lambda_set, _) => {
RawFunctionLayout::ZeroArgumentThunk(lambda_set.runtime_representation()) RawFunctionLayout::ZeroArgumentThunk(Layout::LambdaSet(lambda_set))
} }
_ => raw, _ => raw,
} }
@ -2718,6 +2688,7 @@ macro_rules! match_on_closure_argument {
let ret_layout = top_level.result; let ret_layout = top_level.result;
match closure_data_layout { match closure_data_layout {
RawFunctionLayout::Function(_, lambda_set, _) => { RawFunctionLayout::Function(_, lambda_set, _) => {
lowlevel_match_on_lambda_set( lowlevel_match_on_lambda_set(
@ -2822,13 +2793,13 @@ pub fn with_hole<'a>(
IntOrFloat::SignedIntType(precision) => Stmt::Let( IntOrFloat::SignedIntType(precision) => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Int(int)), Expr::Literal(Literal::Int(int)),
Layout::Builtin(int_precision_to_builtin(precision)), precision.as_layout(),
hole, hole,
), ),
IntOrFloat::UnsignedIntType(precision) => Stmt::Let( IntOrFloat::UnsignedIntType(precision) => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Int(int)), Expr::Literal(Literal::Int(int)),
Layout::Builtin(int_precision_to_builtin(precision)), precision.as_layout(),
hole, hole,
), ),
_ => unreachable!("unexpected float precision for integer"), _ => unreachable!("unexpected float precision for integer"),
@ -2840,7 +2811,7 @@ pub fn with_hole<'a>(
IntOrFloat::BinaryFloatType(precision) => Stmt::Let( IntOrFloat::BinaryFloatType(precision) => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Float(float)), Expr::Literal(Literal::Float(float)),
Layout::Builtin(float_precision_to_builtin(precision)), precision.as_layout(),
hole, hole,
), ),
IntOrFloat::DecimalFloatType => { IntOrFloat::DecimalFloatType => {
@ -2872,19 +2843,19 @@ pub fn with_hole<'a>(
IntOrFloat::SignedIntType(precision) => Stmt::Let( IntOrFloat::SignedIntType(precision) => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Int(num.into())), Expr::Literal(Literal::Int(num.into())),
Layout::Builtin(int_precision_to_builtin(precision)), precision.as_layout(),
hole, hole,
), ),
IntOrFloat::UnsignedIntType(precision) => Stmt::Let( IntOrFloat::UnsignedIntType(precision) => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Int(num.into())), Expr::Literal(Literal::Int(num.into())),
Layout::Builtin(int_precision_to_builtin(precision)), precision.as_layout(),
hole, hole,
), ),
IntOrFloat::BinaryFloatType(precision) => Stmt::Let( IntOrFloat::BinaryFloatType(precision) => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Float(num as f64)), Expr::Literal(Literal::Float(num as f64)),
Layout::Builtin(float_precision_to_builtin(precision)), precision.as_layout(),
hole, hole,
), ),
IntOrFloat::DecimalFloatType => { IntOrFloat::DecimalFloatType => {
@ -3748,13 +3719,15 @@ pub fn with_hole<'a>(
match what_to_do { match what_to_do {
UpdateExisting(field) => { UpdateExisting(field) => {
substitute_in_exprs(env.arena, &mut stmt, assigned, symbols[0]);
stmt = assign_to_symbol( stmt = assign_to_symbol(
env, env,
procs, procs,
layout_cache, layout_cache,
field.var, field.var,
*field.loc_expr.clone(), *field.loc_expr.clone(),
assigned, symbols[0],
stmt, stmt,
); );
} }
@ -4191,6 +4164,8 @@ fn construct_closure_data<'a>(
assigned: Symbol, assigned: Symbol,
hole: &'a Stmt<'a>, hole: &'a Stmt<'a>,
) -> Stmt<'a> { ) -> Stmt<'a> {
let lambda_set_layout = Layout::LambdaSet(lambda_set);
match lambda_set.layout_for_member(name) { match lambda_set.layout_for_member(name) {
ClosureRepresentation::Union { ClosureRepresentation::Union {
tag_id, tag_id,
@ -4222,12 +4197,7 @@ fn construct_closure_data<'a>(
arguments: symbols, arguments: symbols,
}; };
Stmt::Let( Stmt::Let(assigned, expr, lambda_set_layout, env.arena.alloc(hole))
assigned,
expr,
lambda_set.runtime_representation(),
env.arena.alloc(hole),
)
} }
ClosureRepresentation::AlphabeticOrderStruct(field_layouts) => { ClosureRepresentation::AlphabeticOrderStruct(field_layouts) => {
debug_assert_eq!(field_layouts.len(), symbols.len()); debug_assert_eq!(field_layouts.len(), symbols.len());
@ -4258,7 +4228,7 @@ fn construct_closure_data<'a>(
let expr = Expr::Struct(symbols); 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)) => { ClosureRepresentation::Other(Layout::Builtin(Builtin::Int1)) => {
debug_assert_eq!(symbols.len(), 0); 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 tag_id = name != lambda_set.set[0].0;
let expr = Expr::Literal(Literal::Bool(tag_id)); 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)) => { ClosureRepresentation::Other(Layout::Builtin(Builtin::Int8)) => {
debug_assert_eq!(symbols.len(), 0); 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 tag_id = lambda_set.set.iter().position(|(s, _)| *s == name).unwrap() as u8;
let expr = Expr::Literal(Literal::Byte(tag_id)); 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!(), _ => unreachable!(),
} }
@ -5683,8 +5653,8 @@ fn store_pattern_help<'a>(
// do nothing // do nothing
return StorePattern::NotProductive(stmt); return StorePattern::NotProductive(stmt);
} }
IntLiteral(_) IntLiteral(_, _)
| FloatLiteral(_) | FloatLiteral(_, _)
| DecimalLiteral(_) | DecimalLiteral(_)
| EnumLiteral { .. } | EnumLiteral { .. }
| BitLiteral { .. } | BitLiteral { .. }
@ -5818,8 +5788,8 @@ fn store_tag_pattern<'a>(
Underscore => { Underscore => {
// ignore // ignore
} }
IntLiteral(_) IntLiteral(_, _)
| FloatLiteral(_) | FloatLiteral(_, _)
| DecimalLiteral(_) | DecimalLiteral(_)
| EnumLiteral { .. } | EnumLiteral { .. }
| BitLiteral { .. } | BitLiteral { .. }
@ -5894,8 +5864,8 @@ fn store_newtype_pattern<'a>(
Underscore => { Underscore => {
// ignore // ignore
} }
IntLiteral(_) IntLiteral(_, _)
| FloatLiteral(_) | FloatLiteral(_, _)
| DecimalLiteral(_) | DecimalLiteral(_)
| EnumLiteral { .. } | EnumLiteral { .. }
| BitLiteral { .. } | 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. // internally. But `y` is never used, so we must make sure it't not stored/loaded.
return StorePattern::NotProductive(stmt); return StorePattern::NotProductive(stmt);
} }
IntLiteral(_) IntLiteral(_, _)
| FloatLiteral(_) | FloatLiteral(_, _)
| DecimalLiteral(_) | DecimalLiteral(_)
| EnumLiteral { .. } | EnumLiteral { .. }
| BitLiteral { .. } | BitLiteral { .. }
@ -6120,7 +6090,7 @@ fn reuse_function_symbol<'a>(
let layout = match raw { let layout = match raw {
RawFunctionLayout::ZeroArgumentThunk(layout) => layout, RawFunctionLayout::ZeroArgumentThunk(layout) => layout,
RawFunctionLayout::Function(_, lambda_set, _) => { RawFunctionLayout::Function(_, lambda_set, _) => {
lambda_set.runtime_representation() Layout::LambdaSet(lambda_set)
} }
}; };
@ -6218,7 +6188,7 @@ fn reuse_function_symbol<'a>(
// TODO suspicious // TODO suspicious
// let layout = Layout::Closure(argument_layouts, lambda_set, ret_layout); // let layout = Layout::Closure(argument_layouts, lambda_set, ret_layout);
// panic!("suspicious"); // panic!("suspicious");
let layout = lambda_set.runtime_representation(); let layout = Layout::LambdaSet(lambda_set);
let top_level = ProcLayout::new(env.arena, &[], layout); let top_level = ProcLayout::new(env.arena, &[], layout);
procs.insert_passed_by_name( procs.insert_passed_by_name(
env, env,
@ -6238,9 +6208,14 @@ fn reuse_function_symbol<'a>(
layout_cache, layout_cache,
); );
// a function name (non-closure) that is passed along construct_closure_data(
// it never has closure data, so we use the empty struct env,
return let_empty_struct(symbol, env.arena.alloc(result)); lambda_set,
original,
&[],
symbol,
env.arena.alloc(result),
)
} }
} }
RawFunctionLayout::ZeroArgumentThunk(ret_layout) => { RawFunctionLayout::ZeroArgumentThunk(ret_layout) => {
@ -6408,7 +6383,7 @@ fn call_by_name<'a>(
procs, procs,
fn_var, fn_var,
proc_name, proc_name,
env.arena.alloc(lambda_set.runtime_representation()), env.arena.alloc(Layout::LambdaSet(lambda_set)),
layout_cache, layout_cache,
assigned, assigned,
hole, hole,
@ -6452,7 +6427,7 @@ fn call_by_name<'a>(
procs, procs,
fn_var, fn_var,
proc_name, proc_name,
env.arena.alloc(lambda_set.runtime_representation()), env.arena.alloc(Layout::LambdaSet(lambda_set)),
layout_cache, layout_cache,
closure_data_symbol, closure_data_symbol,
env.arena.alloc(result), env.arena.alloc(result),
@ -6582,7 +6557,7 @@ fn call_by_name_help<'a>(
force_thunk( force_thunk(
env, env,
proc_name, proc_name,
lambda_set.runtime_representation(), Layout::LambdaSet(lambda_set),
assigned, assigned,
hole, hole,
) )
@ -6896,13 +6871,7 @@ fn call_specialized_proc<'a>(
arguments: field_symbols, arguments: field_symbols,
}; };
build_call( build_call(env, call, assigned, Layout::LambdaSet(lambda_set), hole)
env,
call,
assigned,
lambda_set.runtime_representation(),
hole,
)
} }
RawFunctionLayout::ZeroArgumentThunk(_) => { RawFunctionLayout::ZeroArgumentThunk(_) => {
unreachable!() unreachable!()
@ -6942,8 +6911,8 @@ fn call_specialized_proc<'a>(
pub enum Pattern<'a> { pub enum Pattern<'a> {
Identifier(Symbol), Identifier(Symbol),
Underscore, Underscore,
IntLiteral(i128), IntLiteral(i128, IntPrecision),
FloatLiteral(u64), FloatLiteral(u64, FloatPrecision),
DecimalLiteral(RocDec), DecimalLiteral(RocDec),
BitLiteral { BitLiteral {
value: bool, value: bool,
@ -7021,21 +6990,35 @@ fn from_can_pattern_help<'a>(
match can_pattern { match can_pattern {
Underscore => Ok(Pattern::Underscore), Underscore => Ok(Pattern::Underscore),
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), 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) => { FloatLiteral(var, float_str, float) => {
// TODO: Can I reuse num_argument_to_int_or_float here if I pass in true? // 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) { match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, true) {
IntOrFloat::SignedIntType(_) => { IntOrFloat::SignedIntType(_) | IntOrFloat::UnsignedIntType(_) => {
panic!("Invalid percision for float literal = {:?}", var) panic!("Invalid precision for float pattern {:?}", var)
} }
IntOrFloat::UnsignedIntType(_) => { IntOrFloat::BinaryFloatType(precision) => {
panic!("Invalid percision for float literal = {:?}", var) Ok(Pattern::FloatLiteral(f64::to_bits(*float), precision))
} }
IntOrFloat::BinaryFloatType(_) => Ok(Pattern::FloatLiteral(f64::to_bits(*float))),
IntOrFloat::DecimalFloatType => { IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(float_str) { let dec = match RocDec::from_str(float_str) {
Some(d) => d, 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)) Ok(Pattern::DecimalLiteral(dec))
} }
@ -7053,9 +7036,15 @@ fn from_can_pattern_help<'a>(
} }
NumLiteral(var, num_str, num) => { NumLiteral(var, num_str, num) => {
match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, false) { match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, false) {
IntOrFloat::SignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)), IntOrFloat::SignedIntType(precision) => {
IntOrFloat::UnsignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)), Ok(Pattern::IntLiteral(*num as i128, precision))
IntOrFloat::BinaryFloatType(_) => Ok(Pattern::FloatLiteral(*num as u64)), }
IntOrFloat::UnsignedIntType(precision) => {
Ok(Pattern::IntLiteral(*num as i128, precision))
}
IntOrFloat::BinaryFloatType(precision) => {
Ok(Pattern::FloatLiteral(*num as u64, precision))
}
IntOrFloat::DecimalFloatType => { IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(num_str) { let dec = match RocDec::from_str(num_str) {
Some(d) => d, Some(d) => d,
@ -7637,7 +7626,7 @@ fn from_can_record_destruct<'a>(
}) })
} }
#[derive(Debug)] #[derive(Debug, Clone, Copy, PartialEq, Hash)]
pub enum IntPrecision { pub enum IntPrecision {
Usize, Usize,
I128, I128,
@ -7647,29 +7636,14 @@ pub enum IntPrecision {
I8, I8,
} }
pub enum FloatPrecision { impl IntPrecision {
F64, pub fn as_layout(&self) -> Layout<'static> {
F32, Layout::Builtin(self.as_builtin())
} }
pub enum IntOrFloat { pub fn as_builtin(&self) -> Builtin<'static> {
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,
}
}
fn int_precision_to_builtin(precision: IntPrecision) -> Builtin<'static> {
use IntPrecision::*; use IntPrecision::*;
match precision { match self {
I128 => Builtin::Int128, I128 => Builtin::Int128,
I64 => Builtin::Int64, I64 => Builtin::Int64,
I32 => Builtin::Int32, I32 => Builtin::Int32,
@ -7678,6 +7652,35 @@ fn int_precision_to_builtin(precision: IntPrecision) -> Builtin<'static> {
Usize => Builtin::Usize, 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 /// Given the `a` in `Num a`, determines whether it's an int or a float
pub fn num_argument_to_int_or_float( pub fn num_argument_to_int_or_float(
@ -7975,8 +7978,8 @@ fn match_on_lambda_set<'a>(
let result = union_lambda_set_to_switch( let result = union_lambda_set_to_switch(
env, env,
lambda_set.set, lambda_set,
lambda_set.runtime_representation(), Layout::Union(union_layout),
closure_tag_id_symbol, closure_tag_id_symbol,
union_layout.tag_id_layout(), union_layout.tag_id_layout(),
closure_data_symbol, closure_data_symbol,
@ -8006,6 +8009,7 @@ fn match_on_lambda_set<'a>(
union_lambda_set_branch_help( union_lambda_set_branch_help(
env, env,
function_symbol, function_symbol,
lambda_set,
closure_data_symbol, closure_data_symbol,
Layout::Struct(fields), Layout::Struct(fields),
argument_symbols, argument_symbols,
@ -8054,7 +8058,7 @@ fn match_on_lambda_set<'a>(
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn union_lambda_set_to_switch<'a>( fn union_lambda_set_to_switch<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
lambda_set: &'a [(Symbol, &'a [Layout<'a>])], lambda_set: LambdaSet<'a>,
closure_layout: Layout<'a>, closure_layout: Layout<'a>,
closure_tag_id_symbol: Symbol, closure_tag_id_symbol: Symbol,
closure_tag_id_layout: Layout<'a>, closure_tag_id_layout: Layout<'a>,
@ -8065,7 +8069,7 @@ fn union_lambda_set_to_switch<'a>(
assigned: Symbol, assigned: Symbol,
hole: &'a Stmt<'a>, hole: &'a Stmt<'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, // 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 // 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 // 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 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( let stmt = union_lambda_set_branch(
env, env,
lambda_set,
join_point_id, join_point_id,
*function_symbol, *function_symbol,
closure_data_symbol, closure_data_symbol,
@ -8124,6 +8129,7 @@ fn union_lambda_set_to_switch<'a>(
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn union_lambda_set_branch<'a>( fn union_lambda_set_branch<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
lambda_set: LambdaSet<'a>,
join_point_id: JoinPointId, join_point_id: JoinPointId,
function_symbol: Symbol, function_symbol: Symbol,
closure_data_symbol: Symbol, closure_data_symbol: Symbol,
@ -8139,6 +8145,7 @@ fn union_lambda_set_branch<'a>(
union_lambda_set_branch_help( union_lambda_set_branch_help(
env, env,
function_symbol, function_symbol,
lambda_set,
closure_data_symbol, closure_data_symbol,
closure_data_layout, closure_data_layout,
argument_symbols_slice, argument_symbols_slice,
@ -8153,6 +8160,7 @@ fn union_lambda_set_branch<'a>(
fn union_lambda_set_branch_help<'a>( fn union_lambda_set_branch_help<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
function_symbol: Symbol, function_symbol: Symbol,
lambda_set: LambdaSet<'a>,
closure_data_symbol: Symbol, closure_data_symbol: Symbol,
closure_data_layout: Layout<'a>, closure_data_layout: Layout<'a>,
argument_symbols_slice: &'a [Symbol], 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) => { Layout::Struct(&[]) | Layout::Builtin(Builtin::Int1) | Layout::Builtin(Builtin::Int8) => {
(argument_layouts_slice, argument_symbols_slice) (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 // extend layouts with the layout of the closure environment
let mut argument_layouts = let mut argument_layouts =
Vec::with_capacity_in(argument_layouts_slice.len() + 1, env.arena); Vec::with_capacity_in(argument_layouts_slice.len() + 1, env.arena);
argument_layouts.extend(argument_layouts_slice); 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 // extend symbols with the symbol of the closure environment
let mut argument_symbols = let mut argument_symbols =

View file

@ -189,6 +189,7 @@ pub enum Layout<'a> {
/// this is important for closures that capture zero-sized values /// this is important for closures that capture zero-sized values
Struct(&'a [Layout<'a>]), Struct(&'a [Layout<'a>]),
Union(UnionLayout<'a>), Union(UnionLayout<'a>),
LambdaSet(LambdaSet<'a>),
RecursivePointer, 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> { pub fn layout_for_member(&self, function_symbol: Symbol) -> ClosureRepresentation<'a> {
debug_assert!( debug_assert!(
self.set.iter().any(|(s, _)| *s == function_symbol), 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); let mut arguments = Vec::with_capacity_in(argument_layouts.len() + 1, arena);
arguments.extend(argument_layouts); arguments.extend(argument_layouts);
arguments.push(self.runtime_representation()); arguments.push(Layout::LambdaSet(*self));
arguments.into_bump_slice() arguments.into_bump_slice()
} }
@ -606,7 +618,9 @@ impl<'a> LambdaSet<'a> {
use UnionVariant::*; use UnionVariant::*;
match variant { match variant {
Never => Layout::Union(UnionLayout::NonRecursive(&[])), 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 // no useful information to store
Layout::Struct(&[]) Layout::Struct(&[])
} }
@ -667,7 +681,6 @@ pub enum Builtin<'a> {
Float128, Float128,
Float64, Float64,
Float32, Float32,
Float16,
Str, Str,
Dict(&'a Layout<'a>, &'a Layout<'a>), Dict(&'a Layout<'a>, &'a Layout<'a>),
Set(&'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 => { RecursivePointer => {
// We cannot memcpy pointers, because then we would have the same pointer in multiple places! // We cannot memcpy pointers, because then we would have the same pointer in multiple places!
false false
@ -890,6 +904,9 @@ impl<'a> Layout<'a> {
| NonNullableUnwrapped(_) => pointer_size, | NonNullableUnwrapped(_) => pointer_size,
} }
} }
LambdaSet(lambda_set) => lambda_set
.runtime_representation()
.stack_size_without_alignment(pointer_size),
RecursivePointer => pointer_size, RecursivePointer => pointer_size,
} }
} }
@ -919,6 +936,9 @@ impl<'a> Layout<'a> {
| NonNullableUnwrapped(_) => pointer_size, | NonNullableUnwrapped(_) => pointer_size,
} }
} }
Layout::LambdaSet(lambda_set) => lambda_set
.runtime_representation()
.alignment_bytes(pointer_size),
Layout::Builtin(builtin) => builtin.alignment_bytes(pointer_size), Layout::Builtin(builtin) => builtin.alignment_bytes(pointer_size),
Layout::RecursivePointer => pointer_size, Layout::RecursivePointer => pointer_size,
} }
@ -929,6 +949,9 @@ impl<'a> Layout<'a> {
Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(pointer_size), Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(pointer_size),
Layout::Struct(_) => unreachable!("not heap-allocated"), Layout::Struct(_) => unreachable!("not heap-allocated"),
Layout::Union(union_layout) => union_layout.allocation_alignment_bytes(pointer_size), 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"), Layout::RecursivePointer => unreachable!("should be looked up to get an actual layout"),
} }
} }
@ -979,6 +1002,7 @@ impl<'a> Layout<'a> {
| NonNullableUnwrapped(_) => true, | NonNullableUnwrapped(_) => true,
} }
} }
LambdaSet(lambda_set) => lambda_set.runtime_representation().contains_refcounted(),
RecursivePointer => true, RecursivePointer => true,
} }
} }
@ -1002,6 +1026,7 @@ impl<'a> Layout<'a> {
.append(alloc.text("}")) .append(alloc.text("}"))
} }
Union(union_layout) => union_layout.to_doc(alloc, parens), Union(union_layout) => union_layout.to_doc(alloc, parens),
LambdaSet(lambda_set) => lambda_set.runtime_representation().to_doc(alloc, parens),
RecursivePointer => alloc.text("*self"), RecursivePointer => alloc.text("*self"),
} }
} }
@ -1093,7 +1118,6 @@ impl<'a> Builtin<'a> {
const F128_SIZE: u32 = 16; const F128_SIZE: u32 = 16;
const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32; const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32;
const F32_SIZE: u32 = std::mem::size_of::<f32>() 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 /// Number of machine words in an empty one of these
pub const STR_WORDS: u32 = 2; pub const STR_WORDS: u32 = 2;
@ -1123,7 +1147,6 @@ impl<'a> Builtin<'a> {
Float128 => Builtin::F128_SIZE, Float128 => Builtin::F128_SIZE,
Float64 => Builtin::F64_SIZE, Float64 => Builtin::F64_SIZE,
Float32 => Builtin::F32_SIZE, Float32 => Builtin::F32_SIZE,
Float16 => Builtin::F16_SIZE,
Str | EmptyStr => Builtin::STR_WORDS * pointer_size, Str | EmptyStr => Builtin::STR_WORDS * pointer_size,
Dict(_, _) | EmptyDict => Builtin::DICT_WORDS * pointer_size, Dict(_, _) | EmptyDict => Builtin::DICT_WORDS * pointer_size,
Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size, Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size,
@ -1150,7 +1173,6 @@ impl<'a> Builtin<'a> {
Float128 => align_of::<i128>() as u32, Float128 => align_of::<i128>() as u32,
Float64 => align_of::<f64>() as u32, Float64 => align_of::<f64>() as u32,
Float32 => align_of::<f32>() as u32, Float32 => align_of::<f32>() as u32,
Float16 => align_of::<i16>() as u32,
Dict(_, _) | EmptyDict => pointer_size, Dict(_, _) | EmptyDict => pointer_size,
Set(_) | EmptySet => pointer_size, Set(_) | EmptySet => pointer_size,
// we often treat these as i128 (64-bit systems) // we often treat these as i128 (64-bit systems)
@ -1158,8 +1180,8 @@ impl<'a> Builtin<'a> {
// //
// In webassembly, For that to be safe // In webassembly, For that to be safe
// they must be aligned to allow such access // they must be aligned to allow such access
List(_) | EmptyList => pointer_size.max(8), List(_) | EmptyList => pointer_size,
Str | EmptyStr => pointer_size.max(8), Str | EmptyStr => pointer_size,
} }
} }
@ -1168,7 +1190,7 @@ impl<'a> Builtin<'a> {
match self { match self {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64 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, Str | Dict(_, _) | Set(_) | List(_) => false,
} }
} }
@ -1179,7 +1201,7 @@ impl<'a> Builtin<'a> {
match self { match self {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64 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, List(_) => true,
Str | Dict(_, _) | Set(_) => true, Str | Dict(_, _) | Set(_) => true,
@ -1206,7 +1228,6 @@ impl<'a> Builtin<'a> {
Float128 => alloc.text("Float128"), Float128 => alloc.text("Float128"),
Float64 => alloc.text("Float64"), Float64 => alloc.text("Float64"),
Float32 => alloc.text("Float32"), Float32 => alloc.text("Float32"),
Float16 => alloc.text("Float16"),
EmptyStr => alloc.text("EmptyStr"), EmptyStr => alloc.text("EmptyStr"),
EmptyList => alloc.text("EmptyList"), EmptyList => alloc.text("EmptyList"),
@ -1240,8 +1261,7 @@ impl<'a> Builtin<'a> {
| Builtin::Decimal | Builtin::Decimal
| Builtin::Float128 | Builtin::Float128
| Builtin::Float64 | Builtin::Float64
| Builtin::Float32 | Builtin::Float32 => unreachable!("not heap-allocated"),
| Builtin::Float16 => unreachable!("not heap-allocated"),
Builtin::Str => pointer_size, Builtin::Str => pointer_size,
Builtin::Dict(k, v) => k Builtin::Dict(k, v) => k
.alignment_bytes(pointer_size) .alignment_bytes(pointer_size)
@ -1360,7 +1380,7 @@ fn layout_from_flat_type<'a>(
Func(_, closure_var, _) => { Func(_, closure_var, _) => {
let lambda_set = LambdaSet::from_var(env.arena, env.subs, closure_var, env.ptr_bytes)?; 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) => { Record(fields, ext_var) => {
// extract any values from the ext_var // extract any values from the ext_var

View file

@ -33,16 +33,16 @@ pub fn make_tail_recursive<'a>(
id: JoinPointId, id: JoinPointId,
needle: Symbol, needle: Symbol,
stmt: Stmt<'a>, stmt: Stmt<'a>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol, Symbol)],
) -> Stmt<'a> { ) -> Option<Stmt<'a>> {
let allocated = arena.alloc(stmt); let allocated = arena.alloc(stmt);
match insert_jumps(arena, allocated, id, needle) { match insert_jumps(arena, allocated, id, needle) {
None => allocated.clone(), None => None,
Some(new) => { Some(new) => {
// jumps were inserted, we must now add a join point // jumps were inserted, we must now add a join point
let params = Vec::from_iter_in( let params = Vec::from_iter_in(
args.iter().map(|(layout, symbol)| Param { args.iter().map(|(layout, symbol, _)| Param {
symbol: *symbol, symbol: *symbol,
layout: *layout, layout: *layout,
borrow: true, borrow: true,
@ -52,16 +52,18 @@ pub fn make_tail_recursive<'a>(
.into_bump_slice(); .into_bump_slice();
// TODO could this be &[]? // 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)); let jump = arena.alloc(Stmt::Jump(id, args));
Stmt::Join { let join = Stmt::Join {
id, id,
remainder: jump, remainder: jump,
parameters: params, parameters: params,
body: new, body: new,
} };
Some(join)
} }
} }
} }

View file

@ -1192,7 +1192,7 @@ fn not_found<'b>(
alloc.reflow(" missing up-top"), 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| { let to_details = |no_suggestion_details, yes_suggestion_details| {
if suggestions.is_empty() { if suggestions.is_empty() {
@ -1240,7 +1240,7 @@ fn module_not_found<'b>(
]); ]);
let default_yes = alloc 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| { let to_details = |no_suggestion_details, yes_suggestion_details| {
if suggestions.is_empty() { if suggestions.is_empty() {

View file

@ -1,7 +1,8 @@
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{Index, MutSet, SendMap}; 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_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_solve::solve; use roc_solve::solve;
use roc_types::pretty_print::Parens; use roc_types::pretty_print::Parens;
use roc_types::types::{Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt}; 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 crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
use ven_pretty::DocAllocator; 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"#; 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>( pub fn type_problem<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
filename: PathBuf, filename: PathBuf,
problem: solve::TypeError, problem: solve::TypeError,
) -> Report<'b> { ) -> Option<Report<'b>> {
use solve::TypeError::*; use solve::TypeError::*;
fn report(title: String, doc: RocDocBuilder<'_>, filename: PathBuf) -> Report<'_> { fn report(title: String, doc: RocDocBuilder<'_>, filename: PathBuf) -> Option<Report<'_>> {
Report { Some(Report {
title, title,
filename, filename,
doc, doc,
severity: Severity::RuntimeError, severity: Severity::RuntimeError,
} })
} }
match problem { match problem {
BadExpr(region, category, found, expected) => { BadExpr(region, category, found, expected) => Some(to_expr_report(
to_expr_report(alloc, filename, region, category, found, expected) alloc, filename, region, category, found, expected,
} )),
BadPattern(region, category, found, expected) => { BadPattern(region, category, found, expected) => Some(to_pattern_report(
to_pattern_report(alloc, filename, region, category, found, expected) alloc, filename, region, category, found, expected,
} )),
CircularType(region, symbol, overall_type) => { CircularType(region, symbol, overall_type) => Some(to_circular_report(
to_circular_report(alloc, filename, region, symbol, overall_type) alloc,
} filename,
region,
symbol,
overall_type,
)),
UnexposedLookup(symbol) => { UnexposedLookup(symbol) => {
let title = "UNRECOGNIZED NAME".to_string(); let title = "UNRECOGNIZED NAME".to_string();
let doc = alloc let doc = alloc
@ -97,12 +103,40 @@ pub fn type_problem<'b>(
report(title, doc, filename) 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), 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>( pub fn cyclic_alias<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
symbol: Symbol, symbol: Symbol,

View file

@ -128,6 +128,7 @@ impl<'b> Report<'b> {
pub struct Palette<'a> { pub struct Palette<'a> {
pub primary: &'a str, pub primary: &'a str,
pub code_block: &'a str, pub code_block: &'a str,
pub keyword: &'a str,
pub variable: &'a str, pub variable: &'a str,
pub type_variable: &'a str, pub type_variable: &'a str,
pub structure: &'a str, pub structure: &'a str,
@ -146,6 +147,7 @@ pub struct Palette<'a> {
pub const DEFAULT_PALETTE: Palette = Palette { pub const DEFAULT_PALETTE: Palette = Palette {
primary: WHITE_CODE, primary: WHITE_CODE,
code_block: WHITE_CODE, code_block: WHITE_CODE,
keyword: GREEN_CODE,
variable: BLUE_CODE, variable: BLUE_CODE,
type_variable: YELLOW_CODE, type_variable: YELLOW_CODE,
structure: GREEN_CODE, structure: GREEN_CODE,
@ -810,6 +812,9 @@ where
Symbol => { Symbol => {
self.write_str(self.palette.variable)?; self.write_str(self.palette.variable)?;
} }
Keyword => {
self.write_str(self.palette.keyword)?;
}
GutterBar => { GutterBar => {
self.write_str(self.palette.gutter_bar)?; self.write_str(self.palette.gutter_bar)?;
} }
@ -837,7 +842,7 @@ where
ParserSuggestion => { ParserSuggestion => {
self.write_str(self.palette.parser_suggestion)?; 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); self.style_stack.push(*annotation);
Ok(()) Ok(())
@ -851,11 +856,11 @@ where
Some(annotation) => match annotation { Some(annotation) => match annotation {
Emphasized | Url | TypeVariable | Alias | Symbol | BinOp | Error | GutterBar Emphasized | Url | TypeVariable | Alias | Symbol | BinOp | Error | GutterBar
| Typo | TypoSuggestion | ParserSuggestion | Structure | CodeBlock | PlainText | Typo | TypoSuggestion | ParserSuggestion | Structure | CodeBlock | PlainText
| LineNumber | Tip | Module | Header => { | LineNumber | Tip | Module | Header | Keyword => {
self.write_str(RESET_CODE)?; self.write_str(RESET_CODE)?;
} }
TypeBlock | GlobalTag | PrivateTag | RecordField | Keyword => { /* nothing yet */ } TypeBlock | GlobalTag | PrivateTag | RecordField => { /* nothing yet */ }
}, },
} }
Ok(()) Ok(())

View file

@ -154,9 +154,10 @@ mod test_reporting {
} }
for problem in type_problems { 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); reports.push(report);
} }
}
for problem in mono_problems { for problem in mono_problems {
let report = mono_problem(&alloc, filename.clone(), problem.clone()); let report = mono_problem(&alloc, filename.clone(), problem.clone());
@ -541,7 +542,7 @@ mod test_reporting {
8 4 -> bar baz "yay" 8 4 -> bar baz "yay"
^^^ ^^^
these names seem close though: Did you mean one of these?
baz baz
Nat Nat
@ -739,7 +740,7 @@ mod test_reporting {
<cyan>3<reset><cyan><reset> <white>theAdmin<reset> <cyan>3<reset><cyan><reset> <white>theAdmin<reset>
<red>^^^^^^^^<reset> <red>^^^^^^^^<reset>
these names seem close though: Did you mean one of these?
Decimal Decimal
Dec Dec
@ -1491,7 +1492,7 @@ mod test_reporting {
2 { foo: 2 } -> foo 2 { foo: 2 } -> foo
^^^ ^^^
these names seem close though: Did you mean one of these?
Bool Bool
U8 U8
@ -1947,7 +1948,7 @@ mod test_reporting {
2 f = \_ -> ok 4 2 f = \_ -> ok 4
^^ ^^
these names seem close though: Did you mean one of these?
U8 U8
f f
@ -3634,8 +3635,8 @@ mod test_reporting {
1 Foo.test 1 Foo.test
^^^^^^^^ ^^^^^^^^
Is there an import missing? Perhaps there is a typo, these names seem Is there an import missing? Perhaps there is a typo. Did you mean one
close: of these?
Bool Bool
Num Num
@ -5797,7 +5798,7 @@ mod test_reporting {
1 [ "foo", bar("") ] 1 [ "foo", bar("") ]
^^^ ^^^
these names seem close though: Did you mean one of these?
Nat Nat
Str Str

View file

@ -2017,3 +2017,17 @@ fn lists_with_incompatible_type_param_in_if() {
RocStr 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<((), ())>
);
}

View file

@ -1778,4 +1778,48 @@ mod gen_num {
u32 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
);
}
} }

View file

@ -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] #[test]
fn mirror_llvm_alignment_padding() { fn mirror_llvm_alignment_padding() {
// see https://github.com/rtfeldman/roc/issues/1569 // see https://github.com/rtfeldman/roc/issues/1569
@ -2616,7 +2618,8 @@ fn lambda_set_struct_byte() {
r = Red r = Red
p1 = (\u -> r == u) 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 when oneOfResult is
_ -> 32 _ -> 32
@ -2779,3 +2782,127 @@ fn value_not_exposed_hits_panic() {
i64 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
);
}

View file

@ -889,3 +889,26 @@ fn blue_and_absent() {
i64 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
);
}

View file

@ -143,13 +143,14 @@ fn create_llvm_module<'a>(
} }
for problem in type_problems { 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(); let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette); report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf); lines.push(buf);
} }
}
for problem in mono_problems { for problem in mono_problems {
let report = mono_problem(&alloc, module_path.clone(), problem); let report = mono_problem(&alloc, module_path.clone(), problem);

View file

@ -6,7 +6,7 @@ procedure Num.26 (#Attr.2, #Attr.3):
let Test.12 = lowlevel NumMul #Attr.2 #Attr.3; let Test.12 = lowlevel NumMul #Attr.2 #Attr.3;
ret Test.12; ret Test.12;
procedure Test.1 (Test.2, Test.3): procedure Test.1 (Test.17, Test.18):
joinpoint Test.7 Test.2 Test.3: joinpoint Test.7 Test.2 Test.3:
let Test.15 = 0i64; let Test.15 = 0i64;
let Test.16 = lowlevel Eq Test.15 Test.2; 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; let Test.11 = CallByName Num.26 Test.2 Test.3;
jump Test.7 Test.10 Test.11; jump Test.7 Test.10 Test.11;
in in
jump Test.7 Test.2 Test.3; jump Test.7 Test.17 Test.18;
procedure Test.0 (): procedure Test.0 ():
let Test.5 = 10i64; let Test.5 = 10i64;

View file

@ -1,4 +1,4 @@
procedure Test.3 (Test.4): procedure Test.3 (Test.29):
joinpoint Test.13 Test.4: joinpoint Test.13 Test.4:
let Test.23 = 1i64; let Test.23 = 1i64;
let Test.24 = GetTagId Test.4; 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; let Test.7 = UnionAtIndex (Id 0) (Index 1) Test.4;
jump Test.13 Test.7; jump Test.13 Test.7;
in in
jump Test.13 Test.4; jump Test.13 Test.29;
procedure Test.0 (): procedure Test.0 ():
let Test.28 = 3i64; let Test.28 = 3i64;

View file

@ -10,7 +10,7 @@ procedure Num.27 (#Attr.2, #Attr.3):
let Test.26 = lowlevel NumLt #Attr.2 #Attr.3; let Test.26 = lowlevel NumLt #Attr.2 #Attr.3;
ret Test.26; 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: joinpoint Test.12 Test.2 Test.3 Test.4:
let Test.14 = CallByName Num.27 Test.3 Test.4; let Test.14 = CallByName Num.27 Test.3 Test.4;
if Test.14 then if Test.14 then
@ -29,7 +29,7 @@ procedure Test.1 (Test.2, Test.3, Test.4):
else else
ret Test.2; ret Test.2;
in in
jump Test.12 Test.2 Test.3 Test.4; jump Test.12 Test.27 Test.28 Test.29;
procedure Test.0 (): procedure Test.0 ():
let Test.9 = Array []; let Test.9 = Array [];

View file

@ -1,43 +1,53 @@
procedure Num.24 (#Attr.2, #Attr.3): procedure Num.24 (#Attr.2, #Attr.3):
let Test.24 = lowlevel NumAdd #Attr.2 #Attr.3; let Test.27 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.24; ret Test.27;
procedure Num.26 (#Attr.2, #Attr.3): procedure Num.26 (#Attr.2, #Attr.3):
let Test.19 = lowlevel NumMul #Attr.2 #Attr.3; let Test.22 = 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;
ret Test.22; 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): procedure Test.4 (Test.7):
let Test.18 = CallByName Test.2; let Test.21 = CallByName Test.2;
let Test.17 = CallByName Num.26 Test.7 Test.18; let Test.20 = CallByName Num.26 Test.7 Test.21;
ret Test.17; ret Test.20;
procedure Test.5 (Test.8, Test.9): procedure Test.5 (Test.8, Test.9):
let Test.14 = CallByName Test.3 Test.9; joinpoint Test.15 Test.14:
ret 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 (): procedure Test.0 ():
joinpoint Test.16 Test.12: joinpoint Test.19 Test.12:
let Test.13 = 42i64; let Test.13 = 42i64;
let Test.11 = CallByName Test.5 Test.12 Test.13; let Test.11 = CallByName Test.5 Test.12 Test.13;
ret Test.11; ret Test.11;
in in
let Test.21 = true; let Test.24 = true;
if Test.21 then if Test.24 then
let Test.3 = Struct {}; let Test.3 = false;
jump Test.16 Test.3; jump Test.19 Test.3;
else else
let Test.4 = Struct {}; let Test.4 = true;
jump Test.16 Test.4; jump Test.19 Test.4;

View file

@ -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 { fn color_backtrace(backtrace: &snafu::Backtrace) -> String {
let backtrace_str = format!("{}", backtrace); let backtrace_str = format!("{}", backtrace);
let backtrace_split = backtrace_str.split('\n'); let backtrace_split = backtrace_str.split('\n');

4
examples/.gitignore vendored
View file

@ -4,3 +4,7 @@ app
libhost.a libhost.a
roc_app.ll roc_app.ll
roc_app.bc roc_app.bc
dynhost
preprocessedhost
metadata
libapp.so

View file

@ -23,16 +23,19 @@ comptime {
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator; 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_size() i64;
extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void; 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_size() i64;
extern fn roc__mainForHost_1_Fx_result_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; const Align = 2 * @alignOf(usize);
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void; extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void;
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.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; 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; 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 { 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; 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 { 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); 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 {}; 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 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); var output = @ptrCast([*]u8, raw_output);
defer { defer {
std.heap.c_allocator.free(raw_output); allocator.free(raw_output);
} }
var ts1: std.os.timespec = undefined; var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; 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)).*; const closure_data_pointer = @ptrCast([*]u8, output);
if (flag == 0) {
// all is well
const closure_data_pointer = @ptrCast([*]u8, output[@sizeOf(u64)..size]);
call_the_closure(closure_data_pointer); 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; var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; 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 { fn call_the_closure(closure_data_pointer: [*]u8) void {
const allocator = std.heap.page_allocator;
const size = roc__mainForHost_1_Fx_result_size(); 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); var output = @ptrCast([*]u8, raw_output);
defer { defer {
std.heap.c_allocator.free(raw_output); allocator.free(raw_output);
} }
const flags: u8 = 0; const flags: u8 = 0;

View file

@ -4,7 +4,7 @@ use core::alloc::Layout;
use core::ffi::c_void; use core::ffi::c_void;
use core::mem::MaybeUninit; use core::mem::MaybeUninit;
use libc; use libc;
use roc_std::{RocCallResult, RocStr}; use roc_std::RocStr;
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::c_char; use std::os::raw::c_char;
@ -70,23 +70,11 @@ pub fn rust_main() -> isize {
roc_main(buffer); roc_main(buffer);
let output = buffer as *mut RocCallResult<()>; let result = call_the_closure(buffer);
match (&*output).into() {
Ok(()) => {
let closure_data_ptr = buffer.offset(8);
let result = call_the_closure(closure_data_ptr as *const u8);
std::alloc::dealloc(buffer, layout); std::alloc::dealloc(buffer, layout);
result result
}
Err(msg) => {
std::alloc::dealloc(buffer, layout);
panic!("Roc failed with message: {}", msg);
}
}
}; };
// Exit code // Exit code
@ -105,16 +93,10 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
buffer as *mut u8, buffer as *mut u8,
); );
let output = &*(buffer as *mut RocCallResult<()>);
match output.into() {
Ok(_) => {
std::alloc::dealloc(buffer, layout); std::alloc::dealloc(buffer, layout);
0 0
} }
Err(e) => panic!("failed with {}", e),
}
}
#[no_mangle] #[no_mangle]
pub fn roc_fx_getLine() -> RocStr { pub fn roc_fx_getLine() -> RocStr {

View file

@ -5,4 +5,7 @@ app "effect-example"
main : Effect.Effect {} main : Effect.Effect {}
main = 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 {}

View file

@ -32,6 +32,8 @@ extern fn roc__mainForHost_1_Fx_result_size() i64;
extern fn malloc(size: usize) callconv(.C) ?*c_void; 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 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 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 { export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
return malloc(size); 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); 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 {}; const Unit = extern struct {};
pub export fn main() u8 { pub export fn main() u8 {
const allocator = std.heap.page_allocator;
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
const size = @intCast(usize, roc__mainForHost_size()); // NOTE the return size can be zero, which will segfault. Always allocate at least 8 bytes
const raw_output = std.heap.c_allocator.alloc(u8, size) catch unreachable; 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); var output = @ptrCast([*]u8, raw_output);
defer { defer {
std.heap.c_allocator.free(raw_output); allocator.free(raw_output);
} }
var ts1: std.os.timespec = undefined; var ts1: std.os.timespec = undefined;
@ -71,21 +84,7 @@ pub export fn main() u8 {
roc__mainForHost_1_exposed(output); roc__mainForHost_1_exposed(output);
const elements = @ptrCast([*]u64, @alignCast(8, output)); call_the_closure(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;
}
var ts2: std.os.timespec = undefined; var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; 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 { fn call_the_closure(closure_data_pointer: [*]u8) void {
const allocator = std.heap.page_allocator;
const size = roc__mainForHost_1_Fx_result_size(); 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); var output = @ptrCast([*]u8, raw_output);
defer { defer {
std.heap.c_allocator.free(raw_output); allocator.free(raw_output);
} }
const flags: u8 = 0; const flags: u8 = 0;
roc__mainForHost_1_Fx_caller(&flags, closure_data_pointer, output); 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; return;
} else {
unreachable;
}
} }
pub export fn roc_fx_getLine() str.RocStr { pub export fn roc_fx_getLine() str.RocStr {

2
examples/fib/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
add
fib

15
examples/fib/Fib.roc Normal file
View 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)

View 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

View 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);
}

View file

@ -1,7 +1,12 @@
#include <stdio.h> #include <stdio.h>
#include <string.h>
extern int rust_main(); extern int rust_main();
int main() { int main() { return rust_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); }

View file

@ -3,12 +3,12 @@
use core::ffi::c_void; use core::ffi::c_void;
use core::mem::MaybeUninit; use core::mem::MaybeUninit;
use libc::c_char; use libc::c_char;
use roc_std::{RocCallResult, RocStr}; use roc_std::RocStr;
use std::ffi::CStr; use std::ffi::CStr;
extern "C" { extern "C" {
#[link_name = "roc__mainForHost_1_exposed"] #[link_name = "roc__mainForHost_1_exposed"]
fn roc_main(output: *mut RocCallResult<RocStr>) -> (); fn roc_main() -> RocStr;
} }
#[no_mangle] #[no_mangle]
@ -46,15 +46,9 @@ pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
#[no_mangle] #[no_mangle]
pub fn rust_main() -> isize { pub fn rust_main() -> isize {
let mut call_result: MaybeUninit<RocCallResult<RocStr>> = MaybeUninit::uninit();
unsafe { 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 len = roc_str.len();
let str_bytes = roc_str.get_bytes() as *const libc::c_void; 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!"); panic!("Writing to stdout failed!");
} }
} }
Err(msg) => {
panic!("Roc failed with message: {}", msg);
}
}
}
// Exit code // Exit code
0 0

2
examples/hello-web/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
hello-web
*.wat

View 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

View 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.

View 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>

View file

@ -0,0 +1,10 @@
platform examples/hello-world
requires {}{ main : Str }
exposes []
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View 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,
};
}

View 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;
}

View 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");
});

View file

@ -1,36 +1,38 @@
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h> #include <errno.h>
#include <string.h>
#include <stdbool.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) { void* roc_alloc(size_t size, unsigned int alignment) { return malloc(size); }
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); return realloc(ptr, new_size);
} }
void roc_dealloc(void* ptr, unsigned int alignment) { void roc_dealloc(void* ptr, unsigned int alignment) { free(ptr); }
free(ptr);
}
void roc_panic(void* ptr, unsigned int alignment) { void roc_panic(void* ptr, unsigned int alignment) {
char* msg = (char*)ptr; char* msg = (char*)ptr;
fprintf(stderr, "Application crashed with message\n\n %s\n\nShutting down\n", msg); fprintf(stderr,
"Application crashed with message\n\n %s\n\nShutting down\n", msg);
exit(0); 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 { struct RocStr {
char* bytes; char* bytes;
size_t len; size_t len;
}; };
bool is_small_str(struct RocStr str) { bool is_small_str(struct RocStr str) { return ((ssize_t)str.len) < 0; }
return ((ssize_t)str.len) < 0;
}
// Determine the length of the string, taking into // Determine the length of the string, taking into
// account the small string optimization // account the small string optimization
@ -51,23 +53,13 @@ size_t roc_str_len(struct RocStr str) {
} }
} }
struct RocCallResult { extern struct RocStr roc__mainForHost_1_exposed();
size_t flag;
struct RocStr content;
};
extern void roc__mainForHost_1_exposed(struct RocCallResult *re);
int main() { int main() {
// Make space for the Roc call result struct RocStr str = roc__mainForHost_1_exposed();
struct RocCallResult call_result;
// Call Roc to populate call_result
roc__mainForHost_1_exposed(&call_result);
// Determine str_len and the str_bytes pointer, // Determine str_len and the str_bytes pointer,
// taking into account the small string optimization. // taking into account the small string optimization.
struct RocStr str = call_result.content;
size_t str_len = roc_str_len(str); size_t str_len = roc_str_len(str);
char* str_bytes; char* str_bytes;

View file

@ -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 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 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 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 { export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
_ = alignment; _ = alignment;
@ -51,12 +53,18 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
std.process.exit(0); 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 mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed(*RocCallResult) void; extern fn roc__mainForHost_1_exposed() RocStr;
const RocCallResult = extern struct { flag: u64, content: RocStr };
const Unit = extern struct {}; const Unit = extern struct {};
@ -64,20 +72,17 @@ pub fn main() u8 {
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
// make space for the result
var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() };
// start time // start time
var ts1: std.os.timespec = undefined; var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
// actually call roc to populate the callresult // actually call roc to populate the callresult
roc__mainForHost_1_exposed(&callresult); var callresult = roc__mainForHost_1_exposed();
// stdout the result // 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 // end time
var ts2: std.os.timespec = undefined; var ts2: std.os.timespec = undefined;

View file

@ -20,41 +20,71 @@ comptime {
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator; 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; const Align = extern struct { a: usize, b: usize };
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.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 { 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); return malloc(size);
} }
}
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
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 { 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 { export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
_ = tag_id;
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
const msg = @ptrCast([*:0]const u8, c_ptr); const msg = @ptrCast([*:0]const u8, c_ptr);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
std.process.exit(0); 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 // warning! the array is currently stack-allocated so don't make this too big
const NUM_NUMS = 100; const NUM_NUMS = 100;
const RocList = extern struct { elements: [*]i64, length: usize }; const RocList = extern struct { elements: [*]i64, length: usize };
const RocCallResult = extern struct { flag: usize, content: RocList };
const Unit = extern struct {}; const Unit = extern struct {};
pub export fn main() i32 { pub export fn main() u8 {
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
@ -65,25 +95,22 @@ pub export fn main() i32 {
var numbers = raw_numbers[1..]; var numbers = raw_numbers[1..];
for (numbers) |x, i| { for (numbers) |_, i| {
numbers[i] = @mod(@intCast(i64, i), 12); numbers[i] = @mod(@intCast(i64, i), 12);
} }
const roc_list = RocList{ .elements = numbers, .length = NUM_NUMS }; const roc_list = RocList{ .elements = numbers, .length = NUM_NUMS };
// make space for the result
var callresult = RocCallResult{ .flag = 0, .content = undefined };
// start time // start time
var ts1: std.os.timespec = undefined; var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
// actually call roc to populate the callresult // 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 // stdout the result
const length = std.math.min(20, callresult.content.length); const length = std.math.min(20, callresult.length);
var result = callresult.content.elements[0..length]; var result = callresult.elements[0..length];
for (result) |x, i| { for (result) |x, i| {
if (i == 0) { if (i == 0) {

33
linker/Cargo.toml Normal file
View 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
View 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

File diff suppressed because it is too large Load diff

20
linker/src/main.rs Normal file
View 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
View 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
View file

@ -0,0 +1,11 @@
fib
zig-cache
zig-out
*.o
dynhost
preprocessedhost
metadata
libapp.so

15
linker/tests/fib/Main.roc Normal file
View 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