Merge branch 'main' of github.com:lukewilliamboswell/roc into tutorial

This commit is contained in:
Anton-4 2022-12-23 14:40:51 +01:00
commit 1d44bd4a50
No known key found for this signature in database
GPG Key ID: 0971D718C0A9B937
152 changed files with 7095 additions and 5490 deletions

View File

@ -14,16 +14,15 @@ jobs:
- name: write version to file
run: ./ci/write_version.sh
# build has to be done before tests #2572
- name: build release
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower.
- name: execute rust tests
run: cargo test --release --locked -- --skip opaque_wrap_function --skip bool_list_literal --skip platform_switching_swift --skip swift_ui
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
# this issue may be caused by using older versions of XCode
- name: build release
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower.
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV

View File

@ -26,7 +26,7 @@ jobs:
- name: zig fmt check, zig tests
run: cd crates/compiler/builtins/bitcode && ./run-tests.sh
- name: roc format check on builtins
run: cargo run --locked --release format --check crates/compiler/builtins/roc
@ -54,7 +54,6 @@ jobs:
- name: run `roc test` on Dict builtins
run: cargo run --locked --release -- test crates/compiler/builtins/roc/Dict.roc && sccache --show-stats
#TODO pass --locked into the script here as well, this avoids rebuilding dependencies unnecessarily
- name: wasm repl test
run: crates/repl_test/test_wasm.sh && sccache --show-stats

View File

@ -82,7 +82,6 @@ To build the compiler, you need these installed:
- On Debian/Ubuntu `sudo apt-get install pkg-config`
- LLVM, see below for version
- [rust](https://rustup.rs/)
- Also run `cargo install bindgen` after installing rust. You may need to open a new terminal.
To run the test suite (via `cargo test`), you additionally need to install:

924
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -113,7 +113,6 @@ target-lexicon = "0.12.3"
tempfile = "3.2.0"
unicode-segmentation = "1.10.0"
walkdir = "2.3.2"
wasm3 = { git = "https://github.com/roc-lang/wasm3-rs", rev = "ba0cdab7404f7f2995a8c18e614ce020dabd6da0" }
wyhash = "0.5.0"
# TODO: Deal with the update of object to 0.27.

View File

@ -22,7 +22,7 @@ i386-cli-run = ["target-x86"]
editor = ["roc_editor"]
run-wasm32 = ["wasmer", "wasmer-wasi"]
run-wasm32 = ["roc_wasm_interp"]
# Compiling for a different target than the current machine can cause linker errors.
target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
@ -39,6 +39,8 @@ target-all = [
"target-wasm32"
]
sanitizers = ["roc_build/sanitizers"]
[dependencies]
roc_collections = { path = "../compiler/collections" }
@ -63,11 +65,10 @@ roc_repl_cli = { path = "../repl_cli", optional = true }
roc_tracing = { path = "../tracing" }
roc_intern = { path = "../compiler/intern" }
roc_gen_llvm = {path = "../compiler/gen_llvm"}
roc_wasm_interp = { path = "../wasm_interp", optional = true }
ven_pretty = { path = "../vendor/pretty" }
wasmer-wasi = { version = "2.2.1", optional = true }
clap.workspace = true
const_format.workspace = true
mimalloc.workspace = true
@ -86,15 +87,8 @@ inkwell.workspace = true
[target.'cfg(not(windows))'.dependencies]
roc_repl_expect = { path = "../repl_expect" }
# Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dependencies]
wasmer = { version = "2.2.1", optional = true, default-features = false, features = ["singlepass", "universal"] }
[target.'cfg(not(target_arch = "x86_64"))'.dependencies]
wasmer = { version = "2.2.1", optional = true, default-features = false, features = ["cranelift", "universal"] }
[dev-dependencies]
wasmer-wasi = "2.2.1"
pretty_assertions = "1.3.0"
roc_test_utils = { path = "../test_utils" }
roc_utils = { path = "../utils" }
@ -105,13 +99,6 @@ cli_utils = { path = "../cli_utils" }
once_cell = "1.15.0"
parking_lot = "0.12"
# Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
wasmer = { version = "2.2.1", default-features = false, features = ["singlepass", "universal"] }
[target.'cfg(not(target_arch = "x86_64"))'.dev-dependencies]
wasmer = { version = "2.2.1", default-features = false, features = ["cranelift", "universal"] }
[[bench]]
name = "time_bench"
harness = false

View File

@ -186,7 +186,9 @@ pub fn build_file<'a>(
};
// We don't need to spawn a rebuild thread when using a prebuilt host.
let rebuild_thread = if is_prebuilt {
let rebuild_thread = if matches!(link_type, LinkType::Dylib | LinkType::None) {
None
} else if is_prebuilt {
if !preprocessed_host_path.exists() {
if prebuilt_requested {
eprintln!(
@ -378,10 +380,11 @@ pub fn build_file<'a>(
std::fs::write(app_o_file, &*roc_app_bytes).unwrap();
let mut inputs = vec![
host_input_path.as_path().to_str().unwrap(),
app_o_file.to_str().unwrap(),
];
let mut inputs = vec![app_o_file.to_str().unwrap()];
if !matches!(link_type, LinkType::Dylib | LinkType::None) {
inputs.push(host_input_path.as_path().to_str().unwrap());
}
let builtins_host_tempfile = {
#[cfg(unix)]

View File

@ -853,7 +853,7 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
{
use std::os::unix::ffi::OsStrExt;
run_with_wasmer(
run_wasm(
generated_filename,
args.into_iter().map(|os_str| os_str.as_bytes()),
);
@ -861,11 +861,11 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
#[cfg(not(target_family = "unix"))]
{
run_with_wasmer(
run_wasm(
generated_filename,
args.into_iter().map(|os_str| {
os_str.to_str().expect(
"Roc does not currently support passing non-UTF8 arguments to Wasmer.",
"Roc does not currently support passing non-UTF8 arguments to Wasm.",
)
}),
);
@ -1239,38 +1239,33 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
}
#[cfg(feature = "run-wasm32")]
fn run_with_wasmer<I: Iterator<Item = S>, S: AsRef<[u8]>>(wasm_path: &std::path::Path, args: I) {
use wasmer::{Instance, Module, Store};
fn run_wasm<I: Iterator<Item = S>, S: AsRef<[u8]>>(wasm_path: &std::path::Path, args: I) {
use bumpalo::collections::Vec;
use roc_wasm_interp::{DefaultImportDispatcher, Instance};
let store = Store::default();
let module = Module::from_file(&store, &wasm_path).unwrap();
let bytes = std::fs::read(wasm_path).unwrap();
let arena = Bump::new();
// First, we create the `WasiEnv`
use wasmer_wasi::WasiState;
let mut wasi_env = WasiState::new("hello").args(args).finalize().unwrap();
// Then, we get the import object related to our WASI
// and attach it to the Wasm instance.
let import_object = wasi_env.import_object(&module).unwrap();
let instance = Instance::new(&module, &import_object).unwrap();
let start = instance.exports.get_function("_start").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),
},
let mut argv = Vec::<&[u8]>::new_in(&arena);
for arg in args {
let mut arg_copy = Vec::<u8>::new_in(&arena);
arg_copy.extend_from_slice(arg.as_ref());
argv.push(arg_copy.into_bump_slice());
}
let import_dispatcher = DefaultImportDispatcher::new(&argv);
let mut instance = Instance::from_bytes(&arena, &bytes, import_dispatcher, false).unwrap();
instance
.call_export("_start", [])
.unwrap()
.unwrap()
.expect_i32()
.unwrap();
}
#[cfg(not(feature = "run-wasm32"))]
fn run_with_wasmer<I: Iterator<Item = S>, S: AsRef<[u8]>>(_wasm_path: &std::path::Path, _args: I) {
fn run_wasm<I: Iterator<Item = S>, S: AsRef<[u8]>>(_wasm_path: &std::path::Path, _args: I) {
println!("Running wasm files is not supported on this target.");
}

View File

@ -117,9 +117,10 @@ mod cli_run {
assert!(
compile_out.status.success(),
"\n___________\nRoc command failed with status {:?}:\n\n {:?}\n___________\n",
"\n___________\nRoc command failed with status {:?}:\n\n {} {}\n___________\n",
compile_out.status,
compile_out
compile_out.stdout,
compile_out.stderr,
);
compile_out
@ -560,7 +561,7 @@ mod cli_run {
test_roc_app(
"crates/cli_testing_examples/expects",
"expects.roc",
"expects",
"expects-test",
&[],
&[],
&[],
@ -568,7 +569,7 @@ mod cli_run {
r#"
This expectation failed:
14 expect x != x
18 expect x != x
^^^^^^
When it failed, these variables had these values:
@ -576,8 +577,11 @@ mod cli_run {
x : Num *
x = 42
[<ignored for tests> 15:9] 42
[<ignored for tests> 16:9] "Fjoer en ferdjer frieten oan dyn geve lea"
[<ignored for tests> 19:9] 42
[<ignored for tests> 20:9] "Fjoer en ferdjer frieten oan dyn geve lea"
[<ignored for tests> 13:9] "abc"
[<ignored for tests> 13:9] 10
[<ignored for tests> 13:9] A (B C)
Program finished!
"#
),
@ -588,7 +592,7 @@ mod cli_run {
test_roc_app(
"crates/cli_testing_examples/expects",
"expects.roc",
"expects",
"expects-test",
&[],
&[],
&[],
@ -609,7 +613,7 @@ mod cli_run {
b : Num *
b = 2
1 failed and 0 passed in <ignored for test> ms."#
@ -713,6 +717,22 @@ mod cli_run {
assert!(out.status.success());
}
// TODO: write a new test once mono bugs are resolved in investigation
#[test]
fn check_virtual_dom_server() {
let path = file_path_from_root("examples/virtual-dom-wip", "example-server.roc");
let out = run_roc(&[CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success());
}
// TODO: write a new test once mono bugs are resolved in investigation
#[test]
fn check_virtual_dom_client() {
let path = file_path_from_root("examples/virtual-dom-wip", "example-client.roc");
let out = run_roc(&[CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success());
}
#[test]
#[cfg_attr(windows, ignore)]
fn interactive_effects() {
@ -1002,7 +1022,7 @@ mod cli_run {
let mut path = file.with_file_name(executable_filename);
path.set_extension("wasm");
let stdout = crate::run_with_wasmer(&path, stdin);
let stdout = crate::run_wasm(&path, stdin);
if !stdout.ends_with(expected_ending) {
panic!(
@ -1018,14 +1038,17 @@ mod cli_run {
stdin: &[&str],
executable_filename: &str,
expected_ending: &str,
use_valgrind: bool,
use_valgrind: UseValgrind,
) {
use super::{concatcp, CMD_BUILD, TARGET_FLAG};
check_output_with_stdin(
&file_name,
stdin,
executable_filename,
&[concatcp!(TARGET_FLAG, "=x86_32")],
&[],
&[],
expected_ending,
use_valgrind,
TestCliCommands::Run,
@ -1037,6 +1060,7 @@ mod cli_run {
executable_filename,
&[concatcp!(TARGET_FLAG, "=x86_32"), OPTIMIZE_FLAG],
&[],
&[],
expected_ending,
use_valgrind,
TestCliCommands::Run,
@ -1227,6 +1251,40 @@ mod cli_run {
);
}
#[test]
#[serial(multi_dep_thunk)]
#[cfg_attr(windows, ignore)]
fn run_packages_unoptimized() {
check_output_with_stdin(
&fixture_file("packages", "app.roc"),
&[],
"packages-test",
&[],
&[],
&[],
"Hello, World! This text came from a package! This text came from a CSV package!\n",
UseValgrind::Yes,
TestCliCommands::Run,
);
}
#[test]
#[serial(multi_dep_thunk)]
#[cfg_attr(windows, ignore)]
fn run_packages_optimized() {
check_output_with_stdin(
&fixture_file("packages", "app.roc"),
&[],
"packages-test",
&[OPTIMIZE_FLAG],
&[],
&[],
"Hello, World! This text came from a package! This text came from a CSV package!\n",
UseValgrind::Yes,
TestCliCommands::Run,
);
}
#[test]
fn known_type_error() {
check_compile_error(
@ -1351,75 +1409,49 @@ mod cli_run {
}
}
#[allow(dead_code)]
fn run_with_wasmer(wasm_path: &std::path::Path, stdin: &[&str]) -> String {
use std::io::Write;
use wasmer::{Instance, Module, Store};
#[cfg(feature = "wasm32-cli-run")]
fn run_wasm(wasm_path: &std::path::Path, stdin: &[&str]) -> String {
use bumpalo::Bump;
use roc_wasm_interp::{DefaultImportDispatcher, Instance, Value, WasiFile};
// std::process::Command::new("cp")
// .args(&[
// wasm_path.to_str().unwrap(),
// "/home/folkertdev/roc/wasm/nqueens.wasm",
// ])
// .output()
// .unwrap();
let wasm_bytes = std::fs::read(wasm_path).unwrap();
let arena = Bump::new();
let store = Store::default();
let module = Module::from_file(&store, wasm_path).unwrap();
let mut instance = {
let mut fake_stdin = vec![];
let fake_stdout = vec![];
let fake_stderr = vec![];
for s in stdin {
fake_stdin.extend_from_slice(s.as_bytes())
}
let mut fake_stdin = wasmer_wasi::Pipe::new();
let fake_stdout = wasmer_wasi::Pipe::new();
let fake_stderr = wasmer_wasi::Pipe::new();
let mut dispatcher = DefaultImportDispatcher::default();
dispatcher.wasi.files = vec![
WasiFile::ReadOnly(fake_stdin),
WasiFile::WriteOnly(fake_stdout),
WasiFile::WriteOnly(fake_stderr),
];
for line in stdin {
write!(fake_stdin, "{}", line).unwrap();
}
Instance::from_bytes(&arena, &wasm_bytes, dispatcher, false).unwrap()
};
// First, we create the `WasiEnv`
use wasmer_wasi::WasiState;
let mut wasi_env = WasiState::new("hello")
.stdin(Box::new(fake_stdin))
.stdout(Box::new(fake_stdout))
.stderr(Box::new(fake_stderr))
.finalize()
.unwrap();
let result = instance.call_export("_start", []);
// Then, we get the import object related to our WASI
// and attach it to the Wasm instance.
let import_object = wasi_env
.import_object(&module)
.unwrap_or_else(|_| wasmer::imports!());
let instance = Instance::new(&module, &import_object).unwrap();
let start = instance.exports.get_function("_start").unwrap();
match start.call(&[]) {
Ok(_) => read_wasi_stdout(wasi_env),
match result {
Ok(Some(Value::I32(0))) => match &instance.import_dispatcher.wasi.files[1] {
WasiFile::WriteOnly(fake_stdout) => String::from_utf8(fake_stdout.clone())
.unwrap_or_else(|_| "Wasm test printed invalid UTF-8".into()),
_ => unreachable!(),
},
Ok(Some(Value::I32(exit_code))) => {
format!("WASI app exit code {}", exit_code)
}
Ok(Some(val)) => {
format!("WASI _start returned an unexpected number type {:?}", val)
}
Ok(None) => "WASI _start returned no value".into(),
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),
}
format!("WASI error {}", e)
}
}
}
#[allow(dead_code)]
fn read_wasi_stdout(wasi_env: wasmer_wasi::WasiEnv) -> String {
let mut state = wasi_env.state.lock().unwrap();
match state.fs.stdout_mut() {
Ok(Some(stdout)) => {
let mut buf = String::new();
stdout.read_to_string(&mut buf).unwrap();
buf
}
_ => todo!(),
}
}

View File

@ -5,3 +5,4 @@ dynhost
libapp.so
metadata
preprocessedhost
packages-test

View File

@ -0,0 +1,6 @@
app "packages-test"
packages { pf: "platform/main.roc", json: "json/main.roc", csv: "csv/main.roc" }
imports [json.JsonParser, csv.Csv]
provides [main] to pf
main = "Hello, World! \(JsonParser.example) \(Csv.example)"

View File

@ -0,0 +1,6 @@
interface Csv
exposes [example]
imports []
example : Str
example = "This text came from a CSV package!"

View File

@ -0,0 +1,3 @@
package "csv"
exposes [Csv]
packages {}

View File

@ -0,0 +1,6 @@
interface JsonParser
exposes [example]
imports []
example : Str
example = "This text came from a package!"

View File

@ -0,0 +1,3 @@
package "json"
exposes [JsonParser]
packages {}

View File

@ -0,0 +1,127 @@
const std = @import("std");
const builtin = @import("builtin");
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 (builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const mem = std.mem;
const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed_generic(*RocStr) void;
const Align = 2 * @alignOf(usize);
extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque;
extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque;
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;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
_ = alignment;
return malloc(size);
}
export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque {
_ = old_size;
_ = alignment;
return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
_ = alignment;
free(@alignCast(16, @ptrCast([*]u8, c_ptr)));
}
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);
}
export fn roc_panic(c_ptr: *anyopaque, 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);
}
extern fn kill(pid: c_int, sig: c_int) c_int;
extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
extern fn getppid() c_int;
fn roc_getppid() callconv(.C) c_int {
return getppid();
}
fn roc_getppid_windows_stub() callconv(.C) c_int {
return 0;
}
fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
return shm_open(name, oflag, mode);
}
fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
return mmap(addr, length, prot, flags, fd, offset);
}
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
}
if (builtin.os.tag == .windows) {
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong });
}
}
const Unit = extern struct {};
pub export fn main() i32 {
const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer();
var timer = std.time.Timer.start() catch unreachable;
// actually call roc to populate the callresult
var callresult = RocStr.empty();
roc__mainForHost_1_exposed_generic(&callresult);
const nanos = timer.read();
const seconds = (@intToFloat(f64, nanos) / 1_000_000_000.0);
// stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.deinit();
stderr.print("runtime: {d:.3}ms\n", .{seconds * 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

@ -0,0 +1,9 @@
platform "multi-module"
requires {}{ main : Str }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : Str
mainForHost = main

View File

@ -4,3 +4,4 @@ libapp.so
dynhost
preprocessedhost
metadata
expects-test

View File

@ -1,4 +1,4 @@
app "expects"
app "expects-test"
packages { pf: "zig-platform/main.roc" }
imports []
provides [main] to pf
@ -9,9 +9,17 @@ expect
a == b
polyDbg = \x ->
dbg x
x
main =
x = 42
expect x != x
dbg x
dbg "Fjoer en ferdjer frieten oan dyn geve lea"
"Program finished!\n"
r = {x : polyDbg "abc", y: polyDbg 10u8, z : polyDbg (A (B C))}
when r is
_ -> "Program finished!\n"

View File

@ -5,6 +5,7 @@ extern crate roc_module;
extern crate tempfile;
use roc_utils::cargo;
use roc_utils::pretty_command_string;
use roc_utils::root_dir;
use serde::Deserialize;
use serde_xml_rs::from_str;
@ -447,15 +448,3 @@ pub fn known_bad_file(file_name: &str) -> PathBuf {
path
}
fn pretty_command_string(command: &Command) -> OsString {
let mut command_string = std::ffi::OsString::new();
command_string.push(command.get_program());
for arg in command.get_args() {
command_string.push(" ");
command_string.push(arg);
}
command_string
}

View File

@ -1287,6 +1287,14 @@ fn lowlevel_spec<'a>(
builder.add_make_tuple(block, &[byte_index, string, is_ok, problem_code])
}
Dbg => {
let arguments = [env.symbols[&arguments[0]]];
let result_type =
layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
builder.add_unknown_with(block, &arguments, result_type)
}
_other => {
// println!("missing {:?}", _other);
// TODO overly pessimstic

View File

@ -47,3 +47,7 @@ target-aarch64 = ["roc_gen_dev/target-aarch64"]
target-x86 = []
target-x86_64 = ["roc_gen_dev/target-x86_64"]
target-wasm32 = []
# This is used to enable fuzzing and sanitizers.
# Example use is describe here: https://github.com/bhansconnect/roc-fuzz
sanitizers = []

View File

@ -1248,14 +1248,17 @@ fn link_macos(
input_paths: &[&str],
link_type: LinkType,
) -> io::Result<(Child, PathBuf)> {
let (link_type_arg, output_path) = match link_type {
LinkType::Executable => ("-execute", output_path),
let (link_type_args, output_path) = match link_type {
LinkType::Executable => (vec!["-execute"], output_path),
LinkType::Dylib => {
let mut output_path = output_path;
output_path.set_extension("dylib");
("-dylib", output_path)
(
vec!["-dylib", "-undefined", "dynamic_lookup", "-no_fixup_chains"],
output_path,
)
}
LinkType::None => internal_error!("link_macos should not be called with link type of none"),
};
@ -1272,13 +1275,13 @@ fn link_macos(
// The `-l` flags should go after the `.o` arguments
// Don't allow LD_ env vars to affect this
.env_clear()
.args(&link_type_args)
.args([
// NOTE: we don't do --gc-sections on macOS because the default
// macOS linker doesn't support it, but it's a performance
// optimization, so if we ever switch to a different linker,
// we'd like to re-enable it on macOS!
// "--gc-sections",
link_type_arg,
"-arch",
&arch,
"-macos_version_min",

View File

@ -237,15 +237,94 @@ fn gen_from_mono_module_llvm<'a>(
// annotate the LLVM IR output with debug info
// so errors are reported with the line number of the LLVM source
let memory_buffer = if emit_debug_info {
let memory_buffer = if cfg!(feature = "sanitizers") && std::env::var("ROC_SANITIZERS").is_ok() {
let dir = tempfile::tempdir().unwrap();
let dir = dir.into_path();
let app_ll_file = dir.join("app.ll");
let app_bc_file = dir.join("app.bc");
let app_o_file = dir.join("app.o");
// write the ll code to a file, so we can modify it
module.print_to_file(&app_ll_file).unwrap();
// Apply coverage passes.
// Note, this is specifically tailored for `cargo afl` and afl++.
// It most likely will not work with other fuzzer setups without modification.
let mut passes = vec![];
let mut extra_args = vec![];
let mut unrecognized = vec![];
for sanitizer in std::env::var("ROC_SANITIZERS")
.unwrap()
.split(',')
.map(|x| x.trim())
{
match sanitizer {
"address" => passes.push("asan-module"),
"memory" => passes.push("msan-module"),
"thread" => passes.push("tsan-module"),
"fuzzer" => {
passes.push("sancov-module");
extra_args.extend_from_slice(&[
"-sanitizer-coverage-level=3",
"-sanitizer-coverage-prune-blocks=0",
"-sanitizer-coverage-trace-pc-guard",
// This can be used instead of the line above to enable working with `cargo fuzz` and libFuzzer.
// "-sanitizer-coverage-inline-8bit-counters",
]);
}
x => unrecognized.push(x.to_owned()),
}
}
if !unrecognized.is_empty() {
let out = unrecognized
.iter()
.map(|x| format!("{:?}", x))
.collect::<Vec<String>>()
.join(", ");
eprintln!("Unrecognized sanitizer: {}\nSupported options are \"address\", \"memory\", \"thread\", and \"fuzzer\"", out);
}
use std::process::Command;
let mut opt = Command::new("opt");
opt.args([
app_ll_file.to_str().unwrap(),
"-o",
app_bc_file.to_str().unwrap(),
])
.args(extra_args);
if !passes.is_empty() {
opt.arg(format!("-passes={}", passes.join(",")));
}
let opt = opt.output().unwrap();
assert!(opt.stderr.is_empty(), "{:#?}", opt);
// write the .o file. Note that this builds the .o for the local machine,
// and ignores the `target_machine` entirely.
//
// different systems name this executable differently, so we shotgun for
// the most common ones and then give up.
let bc_to_object = Command::new("llc")
.args(&[
"-relocation-model=pic",
"-filetype=obj",
app_bc_file.to_str().unwrap(),
"-o",
app_o_file.to_str().unwrap(),
])
.output()
.unwrap();
assert!(bc_to_object.status.success(), "{:#?}", bc_to_object);
MemoryBuffer::create_from_file(&app_o_file).expect("memory buffer creation works")
} else if emit_debug_info {
module.strip_debug_info();
let mut app_ll_dbg_file = PathBuf::from(roc_file_path);
app_ll_dbg_file.set_extension("dbg.ll");
let mut app_bc_file = PathBuf::from(roc_file_path);
app_bc_file.set_extension("bc");
let mut app_o_file = PathBuf::from(roc_file_path);
app_o_file.set_extension("o");
@ -277,33 +356,23 @@ fn gen_from_mono_module_llvm<'a>(
| Architecture::X86_32(_)
| Architecture::Aarch64(_)
| Architecture::Wasm32 => {
let ll_to_bc = Command::new("llvm-as")
.args([
app_ll_dbg_file.to_str().unwrap(),
"-o",
app_bc_file.to_str().unwrap(),
])
.output()
.unwrap();
assert!(ll_to_bc.stderr.is_empty(), "{:#?}", ll_to_bc);
let llc_args = &[
"-relocation-model=pic",
"-filetype=obj",
app_bc_file.to_str().unwrap(),
"-o",
app_o_file.to_str().unwrap(),
];
// write the .o file. Note that this builds the .o for the local machine,
// and ignores the `target_machine` entirely.
//
// different systems name this executable differently, so we shotgun for
// the most common ones and then give up.
let bc_to_object = Command::new("llc").args(llc_args).output().unwrap();
let ll_to_object = Command::new("llc")
.args(&[
"-relocation-model=pic",
"-filetype=obj",
app_ll_dbg_file.to_str().unwrap(),
"-o",
app_o_file.to_str().unwrap(),
])
.output()
.unwrap();
assert!(bc_to_object.stderr.is_empty(), "{:#?}", bc_to_object);
assert!(ll_to_object.stderr.is_empty(), "{:#?}", ll_to_object);
}
_ => unreachable!(),
}

View File

@ -3,10 +3,6 @@
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail
# Test failures will always point at the _start function
# Make sure to look at the rest of the stack trace!
# Zig will try to run the test binary it produced, but it is a wasm object and hence your OS won't
# know how to run it. In the error message, it prints the binary it tried to run. We use some fun
# unix tools to get that path, then feed it to wasmer
zig test -target wasm32-wasi-musl -O ReleaseFast src/main.zig --test-cmd wasmer --test-cmd-bin
# For non-native binaries, Zig test needs a "test command" it can use
cargo build --locked --release -p roc_wasm_interp
zig test -target wasm32-wasi-musl -O ReleaseFast src/main.zig --test-cmd ../../../../target/release/roc_wasm_interp --test-cmd-bin

View File

@ -52,7 +52,7 @@ pub const RocStr = extern struct {
// small string, and returns a (pointer, len) tuple which points to them.
pub fn init(bytes_ptr: [*]const u8, length: usize) RocStr {
var result = RocStr.allocate(length);
@memcpy(result.asU8ptr(), bytes_ptr, length);
@memcpy(result.asU8ptrMut(), bytes_ptr, length);
return result;
}
@ -83,7 +83,7 @@ pub const RocStr = extern struct {
} else {
var string = RocStr.empty();
string.asU8ptr()[@sizeOf(RocStr) - 1] = @intCast(u8, length) | 0b1000_0000;
string.asU8ptrMut()[@sizeOf(RocStr) - 1] = @intCast(u8, length) | 0b1000_0000;
return string;
}
@ -190,12 +190,12 @@ pub const RocStr = extern struct {
const old_length = self.len();
const delta_length = new_length - old_length;
const result = RocStr.allocate(new_length);
var result = RocStr.allocate(new_length);
// transfer the memory
const source_ptr = self.asU8ptr();
const dest_ptr = result.asU8ptr();
const dest_ptr = result.asU8ptrMut();
@memcpy(dest_ptr, source_ptr, old_length);
@memset(dest_ptr + old_length, 0, delta_length);
@ -230,7 +230,7 @@ pub const RocStr = extern struct {
pub fn setLen(self: *RocStr, length: usize) void {
if (self.isSmallStr()) {
self.asU8ptr()[@sizeOf(RocStr) - 1] = @intCast(u8, length) | 0b1000_0000;
self.asU8ptrMut()[@sizeOf(RocStr) - 1] = @intCast(u8, length) | 0b1000_0000;
} else {
self.str_len = length;
}
@ -320,23 +320,29 @@ pub const RocStr = extern struct {
return (ptr - 1)[0] == utils.REFCOUNT_ONE;
}
pub fn asSlice(self: RocStr) []u8 {
pub fn asSlice(self: *const RocStr) []const u8 {
return self.asU8ptr()[0..self.len()];
}
pub fn asSliceWithCapacity(self: RocStr) []u8 {
pub fn asSliceWithCapacity(self: *const RocStr) []const u8 {
return self.asU8ptr()[0..self.getCapacity()];
}
pub fn asU8ptr(self: RocStr) [*]u8 {
pub fn asSliceWithCapacityMut(self: *RocStr) []u8 {
return self.asU8ptrMut()[0..self.getCapacity()];
}
// Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov.
// return if (self.isSmallStr()) (&@bitCast([@sizeOf(RocStr)]u8, self)) else (@ptrCast([*]u8, self.str_bytes));
pub fn asU8ptr(self: *const RocStr) [*]const u8 {
if (self.isSmallStr()) {
const as_int = @ptrToInt(&self);
const as_ptr = @intToPtr([*]u8, as_int);
return as_ptr;
return @ptrCast([*]const u8, self);
} else {
return @ptrCast([*]const u8, self.str_bytes);
}
}
pub fn asU8ptrMut(self: *RocStr) [*]u8 {
if (self.isSmallStr()) {
return @ptrCast([*]u8, self);
} else {
return @ptrCast([*]u8, self.str_bytes);
}
@ -1402,7 +1408,7 @@ pub fn repeat(string: RocStr, count: usize) callconv(.C) RocStr {
const bytes_ptr = string.asU8ptr();
var ret_string = RocStr.allocate(count * bytes_len);
var ret_string_ptr = ret_string.asU8ptr();
var ret_string_ptr = ret_string.asU8ptrMut();
var i: usize = 0;
while (i < count) : (i += 1) {
@ -1542,9 +1548,8 @@ fn strConcat(arg1: RocStr, arg2: RocStr) RocStr {
} else {
const combined_length = arg1.len() + arg2.len();
const result = arg1.reallocate(combined_length);
@memcpy(result.asU8ptr() + arg1.len(), arg2.asU8ptr(), arg2.len());
var result = arg1.reallocate(combined_length);
@memcpy(result.asU8ptrMut() + arg1.len(), arg2.asU8ptr(), arg2.len());
return result;
}
@ -1615,7 +1620,7 @@ fn strJoinWith(list: RocListStr, separator: RocStr) RocStr {
total_size += separator.len() * (len - 1);
var result = RocStr.allocate(total_size);
var result_ptr = result.asU8ptr();
var result_ptr = result.asU8ptrMut();
var offset: usize = 0;
for (slice[0 .. len - 1]) |substr| {
@ -2534,7 +2539,7 @@ pub fn appendScalar(string: RocStr, scalar_u32: u32) callconv(.C) RocStr {
const width = std.unicode.utf8CodepointSequenceLength(scalar) catch unreachable;
var output = string.reallocate(string.len() + width);
var slice = output.asSliceWithCapacity();
var slice = output.asSliceWithCapacityMut();
_ = std.unicode.utf8Encode(scalar, slice[string.len() .. string.len() + width]) catch unreachable;

View File

@ -90,19 +90,19 @@ fn testing_roc_memcpy(dest: *anyopaque, src: *anyopaque, bytes: usize) callconv(
}
pub fn alloc(size: usize, alignment: u32) ?[*]u8 {
return @ptrCast(?[*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment }));
return @ptrCast(?[*]u8, roc_alloc(size, alignment));
}
pub fn realloc(c_ptr: [*]u8, new_size: usize, old_size: usize, alignment: u32) [*]u8 {
return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_realloc, .{ c_ptr, new_size, old_size, alignment }));
return @ptrCast([*]u8, roc_realloc(c_ptr, new_size, old_size, alignment));
}
pub fn dealloc(c_ptr: [*]u8, alignment: u32) void {
return @call(.{ .modifier = always_inline }, roc_dealloc, .{ c_ptr, alignment });
return roc_dealloc(c_ptr, alignment);
}
pub fn memcpy(dst: [*]u8, src: [*]u8, size: usize) void {
@call(.{ .modifier = always_inline }, roc_memcpy, .{ dst, src, size });
roc_memcpy(dst, src, size);
}
// indirection because otherwise zig creates an alias to the panic function which our LLVM code
@ -275,7 +275,8 @@ pub inline fn calculateCapacity(
} else {
new_capacity = (old_capacity * 3 + 1) / 2;
}
return @maximum(new_capacity, requested_length);
return std.math.max(new_capacity, requested_length);
}
pub fn allocateWithRefcountC(

View File

@ -192,7 +192,8 @@ fn cp_unless_zig_cache(src_dir: &Path, target_dir: &Path) -> io::Result<()> {
}
fn run_command(mut command: Command, flaky_fail_counter: usize) {
let command_str = format!("{:?}", &command);
let command_str = roc_utils::pretty_command_string(&command);
let command_str = command_str.to_string_lossy();
let output_result = command.output();

View File

@ -73,7 +73,6 @@ pub enum DecWidth {
pub enum FloatWidth {
F32,
F64,
F128,
}
impl FloatWidth {
@ -86,7 +85,6 @@ impl FloatWidth {
match self {
F32 => 4,
F64 => 8,
F128 => 16,
}
}
@ -99,7 +97,7 @@ impl FloatWidth {
// the compiler is targeting (e.g. what the Roc code will be compiled to).
match self {
F32 => 4,
F64 | F128 => match target_info.architecture {
F64 => match target_info.architecture {
X86_64 | Aarch64 | Wasm32 => 8,
X86_32 | Aarch32 => 4,
},
@ -225,7 +223,6 @@ impl Index<FloatWidth> for IntrinsicName {
match index {
FloatWidth::F32 => self.options[1],
FloatWidth::F64 => self.options[2],
FloatWidth::F128 => self.options[3],
}
}
}
@ -256,7 +253,6 @@ macro_rules! float_intrinsic {
output.options[1] = concat!($name, ".f32");
output.options[2] = concat!($name, ".f64");
output.options[3] = concat!($name, ".f128");
output
}};

View File

@ -205,6 +205,7 @@ impl GeneratedInfo {
generates,
generates_with,
name: _,
exposes: _,
} => {
let name: &str = generates.into();
let (generated_functions, unknown_generated) =
@ -240,6 +241,7 @@ impl GeneratedInfo {
HeaderType::Builtin {
generates_with,
name: _,
exposes: _,
} => {
debug_assert!(generates_with.is_empty());
GeneratedInfo::Builtin

View File

@ -705,7 +705,7 @@ pub fn constrain_expr(
expected,
);
constraints.exists_many([], [cond_con, continuation_con])
constraints.exists_many([*variable], [cond_con, continuation_con])
}
If {

View File

@ -153,6 +153,9 @@ flags! {
/// Writes a `final.wasm` file to /tmp
ROC_WRITE_FINAL_WASM
/// Prints Wasm interpreter debug log in test_gen
ROC_LOG_WASM_INTERP
// ===Load===
/// Print load phases as they complete.

View File

@ -8,8 +8,8 @@ use bumpalo::Bump;
use roc_parse::ast::{Collection, Header, Module, Spaced, Spaces};
use roc_parse::header::{
AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword, HostedHeader, ImportsEntry,
ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName, PackageEntry,
PackageKeyword, PackagePath, PackagesKeyword, PlatformHeader, PlatformRequires,
ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName, PackageEntry, PackageHeader,
PackageKeyword, PackageName, PackagesKeyword, PlatformHeader, PlatformRequires,
ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword,
};
use roc_parse::ident::UppercaseIdent;
@ -24,6 +24,9 @@ pub fn fmt_module<'a>(buf: &mut Buf<'_>, module: &'a Module<'a>) {
Header::App(header) => {
fmt_app_header(buf, header);
}
Header::Package(header) => {
fmt_package_header(buf, header);
}
Header::Platform(header) => {
fmt_platform_header(buf, header);
}
@ -226,6 +229,20 @@ pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>)
header.provides.format(buf, indent);
}
pub fn fmt_package_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PackageHeader<'a>) {
buf.indent(0);
buf.push_str("package");
let indent = INDENT;
fmt_default_spaces(buf, header.before_name, indent);
fmt_package_name(buf, header.name.value, indent);
header.exposes.keyword.format(buf, indent);
fmt_exposes(buf, header.exposes.item, indent);
header.packages.keyword.format(buf, indent);
fmt_packages(buf, header.packages.item, indent);
}
pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHeader<'a>) {
buf.indent(0);
buf.push_str("platform");
@ -276,7 +293,7 @@ impl<'a> Formattable for TypedIdent<'a> {
}
}
fn fmt_package_name<'buf>(buf: &mut Buf<'buf>, name: PackagePath, _indent: u16) {
fn fmt_package_name<'buf>(buf: &mut Buf<'buf>, name: PackageName, _indent: u16) {
buf.push('"');
buf.push_str_allow_spaces(name.to_str());
buf.push('"');
@ -453,7 +470,7 @@ fn fmt_packages_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &PackageEntry<'a>, i
buf.push_str(entry.shorthand);
buf.push(':');
fmt_default_spaces(buf, entry.spaces_after_shorthand, indent);
fmt_package_name(buf, entry.package_path.value, indent);
fmt_package_name(buf, entry.package_name.value, indent);
}
fn fmt_imports_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &ImportsEntry<'a>, indent: u16) {

View File

@ -9,8 +9,8 @@ use roc_parse::{
},
header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, KeywordItem,
ModuleName, PackageEntry, PackagePath, PlatformHeader, PlatformRequires, ProvidesTo, To,
TypedIdent,
ModuleName, PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires,
ProvidesTo, To, TypedIdent,
},
ident::UppercaseIdent,
};
@ -290,6 +290,12 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
}),
Header::Package(header) => Header::Package(PackageHeader {
before_name: &[],
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
}),
Header::Platform(header) => Header::Platform(PlatformHeader {
before_name: &[],
name: header.name.remove_spaces(arena),
@ -349,7 +355,7 @@ impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
}
}
impl<'a> RemoveSpaces<'a> for PackagePath<'a> {
impl<'a> RemoveSpaces<'a> for PackageName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
@ -394,7 +400,7 @@ impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
PackageEntry {
shorthand: self.shorthand,
spaces_after_shorthand: &[],
package_path: self.package_path.remove_spaces(arena),
package_name: self.package_name.remove_spaces(arena),
}
}
}

View File

@ -9,7 +9,7 @@ use crate::llvm::refcounting::{
decrement_refcount_layout, increment_n_refcount_layout, increment_refcount_layout,
};
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::types::{BasicType, BasicTypeEnum};
use inkwell::types::{BasicType, BasicTypeEnum, StructType};
use inkwell::values::{
BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue, IntValue,
PointerValue, StructValue,
@ -19,9 +19,8 @@ use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds};
use super::build::create_entry_block_alloca;
use std::convert::TryInto;
use super::build::{create_entry_block_alloca, BuilderExt};
use super::convert::zig_list_type;
pub fn call_bitcode_fn<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -96,6 +95,7 @@ fn call_bitcode_fn_help<'a, 'ctx, 'env>(
pub fn call_bitcode_fn_fixing_for_convention<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
bitcode_return_type: StructType<'ctx>,
args: &[BasicValueEnum<'ctx>],
return_layout: &Layout<'_>,
fn_name: &str,
@ -111,17 +111,7 @@ pub fn call_bitcode_fn_fixing_for_convention<'a, 'ctx, 'env>(
// We need to pass the return value by pointer.
let roc_return_type = basic_type_from_layout(env, return_layout);
let cc_ptr_return_type = env
.module
.get_function(fn_name)
.unwrap()
.get_type()
.get_param_types()[0]
.into_pointer_type();
let cc_return_type: BasicTypeEnum<'ctx> = cc_ptr_return_type
.get_element_type()
.try_into()
.expect("Zig bitcode return type is not a basic type!");
let cc_return_type: BasicTypeEnum<'ctx> = bitcode_return_type.into();
// when we write an i128 into this (happens in NumToInt), zig expects this pointer to
// be 16-byte aligned. Not doing so is UB and will immediately fail on CI
@ -139,7 +129,9 @@ pub fn call_bitcode_fn_fixing_for_convention<'a, 'ctx, 'env>(
.collect();
call_void_bitcode_fn(env, &fixed_args, fn_name);
let cc_return_value = env.builder.build_load(cc_return_value_ptr, "read_result");
let cc_return_value =
env.builder
.new_build_load(cc_return_type, cc_return_value_ptr, "read_result");
if roc_return_type.size_of() == cc_return_type.size_of() {
cc_return_value
} else {
@ -270,10 +262,9 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
(true, layout) => {
let closure_type = basic_type_from_layout(env, &layout).ptr_type(AddressSpace::Generic);
let closure_cast = env
.builder
.build_bitcast(closure_ptr, closure_type, "cast_opaque_closure")
.into_pointer_value();
let closure_cast =
env.builder
.build_pointer_cast(closure_ptr, closure_type, "cast_opaque_closure");
let closure_data = load_roc_value(env, layout, closure_cast, "load_closure");
@ -389,23 +380,24 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
debug_info_init!(env, function_value);
let mut it = function_value.get_param_iter();
let value_ptr = it.next().unwrap().into_pointer_value();
let generic_value_ptr = it.next().unwrap().into_pointer_value();
value_ptr.set_name(Symbol::ARG_1.as_str(&env.interns));
generic_value_ptr.set_name(Symbol::ARG_1.as_str(&env.interns));
let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);
let value = if layout.is_passed_by_reference(env.layout_interner, env.target_info) {
let value_type = basic_type_from_layout(env, layout);
let value_ptr_type = value_type.ptr_type(AddressSpace::Generic);
let value_ptr =
env.builder
.build_pointer_cast(value_ptr, value_type, "cast_ptr_to_tag_build_rc_wrapper")
.into()
} else {
let value_cast = env
.builder
.build_bitcast(value_ptr, value_type, "load_opaque")
.into_pointer_value();
.build_pointer_cast(generic_value_ptr, value_ptr_type, "load_opaque");
env.builder.build_load(value_cast, "load_opaque")
// even though this looks like a `load_roc_value`, that gives segfaults in practice.
// I suspect it has something to do with the lifetime of the alloca that is created by
// `load_roc_value`
let value = if layout.is_passed_by_reference(env.layout_interner, env.target_info) {
value_ptr.into()
} else {
env.builder
.new_build_load(value_type, value_ptr, "load_opaque")
};
match rc_operation {
@ -486,13 +478,11 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
let value_cast1 = env
.builder
.build_bitcast(value_ptr1, value_type, "load_opaque")
.into_pointer_value();
.build_pointer_cast(value_ptr1, value_type, "load_opaque");
let value_cast2 = env
.builder
.build_bitcast(value_ptr2, value_type, "load_opaque")
.into_pointer_value();
.build_pointer_cast(value_ptr2, value_type, "load_opaque");
// load_roc_value(env, *element_layout, elem_ptr, "get_elem")
let value1 = load_roc_value(env, *layout, value_cast1, "load_opaque");
@ -568,18 +558,20 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
let value_type = basic_type_from_layout(env, layout);
let value_ptr_type = value_type.ptr_type(AddressSpace::Generic);
let value_cast1 = env
.builder
.build_bitcast(value_ptr1, value_ptr_type, "load_opaque")
.into_pointer_value();
let value_cast1 =
env.builder
.build_pointer_cast(value_ptr1, value_ptr_type, "load_opaque");
let value_cast2 = env
.builder
.build_bitcast(value_ptr2, value_ptr_type, "load_opaque")
.into_pointer_value();
let value_cast2 =
env.builder
.build_pointer_cast(value_ptr2, value_ptr_type, "load_opaque");
let value1 = env.builder.build_load(value_cast1, "load_opaque");
let value2 = env.builder.build_load(value_cast2, "load_opaque");
let value1 = env
.builder
.new_build_load(value_type, value_cast1, "load_opaque");
let value2 = env
.builder
.new_build_load(value_type, value_cast2, "load_opaque");
let default = [value1.into(), value2.into()];
@ -592,15 +584,18 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
&default
}
other => {
let closure_type =
basic_type_from_layout(env, &other).ptr_type(AddressSpace::Generic);
let closure_type = basic_type_from_layout(env, &other);
let closure_ptr_type = closure_type.ptr_type(AddressSpace::Generic);
let closure_cast = env
.builder
.build_bitcast(closure_ptr, closure_type, "load_opaque")
.into_pointer_value();
let closure_cast = env.builder.build_pointer_cast(
closure_ptr,
closure_ptr_type,
"load_opaque",
);
let closure_data = env.builder.build_load(closure_cast, "load_opaque");
let closure_data =
env.builder
.new_build_load(closure_type, closure_cast, "load_opaque");
env.arena
.alloc([value1.into(), value2.into(), closure_data.into()])
@ -648,7 +643,8 @@ impl<'ctx> BitcodeReturnValue<'ctx> {
match self {
BitcodeReturnValue::List(result) => {
call_void_bitcode_fn(env, arguments, fn_name);
env.builder.build_load(*result, "load_list")
env.builder
.new_build_load(zig_list_type(env), *result, "load_list")
}
BitcodeReturnValue::Str(result) => {
call_void_bitcode_fn(env, arguments, fn_name);

View File

@ -53,13 +53,82 @@ use std::convert::TryInto;
use std::path::Path;
use target_lexicon::{Architecture, OperatingSystem, Triple};
use super::convert::RocUnion;
use super::convert::{struct_type_from_union_layout, RocUnion};
use super::intrinsics::{
add_intrinsics, LLVM_FRAME_ADDRESS, LLVM_MEMSET_I32, LLVM_MEMSET_I64, LLVM_SETJMP,
LLVM_STACK_SAVE,
};
use super::lowlevel::run_higher_order_low_level;
pub(crate) trait BuilderExt<'ctx> {
fn new_build_struct_gep(
&self,
struct_type: StructType<'ctx>,
ptr: PointerValue<'ctx>,
index: u32,
name: &str,
) -> Result<PointerValue<'ctx>, ()>;
fn new_build_load(
&self,
element_type: impl BasicType<'ctx>,
ptr: PointerValue<'ctx>,
name: &str,
) -> BasicValueEnum<'ctx>;
unsafe fn new_build_in_bounds_gep(
&self,
element_type: impl BasicType<'ctx>,
ptr: PointerValue<'ctx>,
ordered_indexes: &[IntValue<'ctx>],
name: &str,
) -> PointerValue<'ctx>;
}
impl<'ctx> BuilderExt<'ctx> for Builder<'ctx> {
fn new_build_struct_gep(
&self,
struct_type: StructType<'ctx>,
ptr: PointerValue<'ctx>,
index: u32,
name: &str,
) -> Result<PointerValue<'ctx>, ()> {
debug_assert_eq!(
ptr.get_type().get_element_type().into_struct_type(),
struct_type
);
self.build_struct_gep(ptr, index, name)
}
fn new_build_load(
&self,
element_type: impl BasicType<'ctx>,
ptr: PointerValue<'ctx>,
name: &str,
) -> BasicValueEnum<'ctx> {
debug_assert_eq!(
ptr.get_type().get_element_type(),
element_type.as_any_type_enum()
);
self.build_load(ptr, name)
}
unsafe fn new_build_in_bounds_gep(
&self,
element_type: impl BasicType<'ctx>,
ptr: PointerValue<'ctx>,
ordered_indexes: &[IntValue<'ctx>],
name: &str,
) -> PointerValue<'ctx> {
debug_assert_eq!(
ptr.get_type().get_element_type(),
element_type.as_any_type_enum()
);
self.build_in_bounds_gep(ptr, ordered_indexes, name)
}
}
#[inline(always)]
fn print_fn_verification_output() -> bool {
dbg_do!(ROC_PRINT_LLVM_FN_VERIFICATION, {
@ -709,7 +778,6 @@ fn float_with_precision<'a, 'ctx, 'env>(
match float_width {
FloatWidth::F64 => env.context.f64_type().const_float(value).into(),
FloatWidth::F32 => env.context.f32_type().const_float(value).into(),
FloatWidth::F128 => todo!("F128 is not implemented"),
}
}
@ -774,7 +842,10 @@ fn build_string_literal<'a, 'ctx, 'env>(
let alloca = const_str_alloca_ptr(env, parent, ptr, number_of_elements, number_of_elements);
match env.target_info.ptr_width() {
PtrWidth::Bytes4 => env.builder.build_load(alloca, "load_const_str"),
PtrWidth::Bytes4 => {
env.builder
.new_build_load(zig_str_type(env), alloca, "load_const_str")
}
PtrWidth::Bytes8 => alloca.into(),
}
}
@ -978,7 +1049,7 @@ fn struct_pointer_from_fields<'a, 'ctx, 'env, I>(
for (index, (field_layout, field_value)) in values {
let field_ptr = env
.builder
.build_struct_gep(struct_ptr, index as u32, "field_struct_gep")
.new_build_struct_gep(struct_type, struct_ptr, index as u32, "field_struct_gep")
.unwrap();
store_roc_value(env, field_layout, field_ptr, field_value);
@ -1158,33 +1229,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let field_layout = field_layouts[*index as usize];
use_roc_value(env, field_layout, field_value, "struct_field_tag")
}
(
PointerValue(argument),
Layout::Union(UnionLayout::NonNullableUnwrapped(fields)),
) => {
let struct_layout = Layout::struct_no_name_order(fields);
let struct_type = basic_type_from_layout(env, &struct_layout);
let cast_argument = env
.builder
.build_bitcast(
argument,
struct_type.ptr_type(AddressSpace::Generic),
"cast_rosetree_like",
)
.into_pointer_value();
let ptr = env
.builder
.build_struct_gep(
cast_argument,
*index as u32,
env.arena.alloc(format!("non_nullable_unwrapped_{}", index)),
)
.unwrap();
env.builder.build_load(ptr, "load_rosetree_like")
}
(other, layout) => {
// potential cause: indexing into an unwrapped 1-element record/tag?
unreachable!(
@ -1206,7 +1250,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
union_layout,
} => {
// cast the argument bytes into the desired shape for this tag
let (argument, _structure_layout) = load_symbol_and_layout(scope, structure);
let (argument, structure_layout) = load_symbol_and_layout(scope, structure);
match union_layout {
UnionLayout::NonRecursive(tag_layouts) => {
@ -1219,7 +1263,8 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let opaque_data_ptr = env
.builder
.build_struct_gep(
.new_build_struct_gep(
basic_type_from_layout(env, structure_layout).into_struct_type(),
argument.into_pointer_value(),
RocUnion::TAG_DATA_INDEX,
"get_opaque_data_ptr",
@ -1234,7 +1279,12 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let element_ptr = env
.builder
.build_struct_gep(data_ptr, *index as _, "get_opaque_data_ptr")
.new_build_struct_gep(
struct_type.into_struct_type(),
data_ptr,
*index as _,
"get_opaque_data_ptr",
)
.unwrap();
load_roc_value(
@ -1340,13 +1390,20 @@ fn build_wrapped_tag<'a, 'ctx, 'env>(
let (field_types, field_values) = build_tag_fields(env, scope, tag_field_layouts, arguments);
let union_struct_type = struct_type_from_union_layout(env, union_layout);
// Create the struct_type
let raw_data_ptr = allocate_tag(env, parent, reuse_allocation, union_layout, tags);
let struct_type = env.context.struct_type(&field_types, false);
if union_layout.stores_tag_id_as_data(env.target_info) {
let tag_id_ptr = builder
.build_struct_gep(raw_data_ptr, RocUnion::TAG_ID_INDEX, "tag_id_index")
.new_build_struct_gep(
union_struct_type,
raw_data_ptr,
RocUnion::TAG_ID_INDEX,
"tag_id_index",
)
.unwrap();
let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type();
@ -1355,7 +1412,12 @@ fn build_wrapped_tag<'a, 'ctx, 'env>(
.build_store(tag_id_ptr, tag_id_type.const_int(tag_id as u64, false));
let opaque_struct_ptr = builder
.build_struct_gep(raw_data_ptr, RocUnion::TAG_DATA_INDEX, "tag_data_index")
.new_build_struct_gep(
union_struct_type,
raw_data_ptr,
RocUnion::TAG_DATA_INDEX,
"tag_data_index",
)
.unwrap();
struct_pointer_from_fields(
@ -1402,11 +1464,13 @@ fn build_tag_field_value<'a, 'ctx, 'env>(
debug_assert!(value.is_pointer_value());
// we store recursive pointers as `i64*`
env.builder.build_bitcast(
value,
env.context.i64_type().ptr_type(AddressSpace::Generic),
"cast_recursive_pointer",
)
env.builder
.build_pointer_cast(
value.into_pointer_value(),
env.context.i64_type().ptr_type(AddressSpace::Generic),
"cast_recursive_pointer",
)
.into()
} else if tag_field_layout.is_passed_by_reference(env.layout_interner, env.target_info) {
debug_assert!(value.is_pointer_value());
@ -1466,12 +1530,15 @@ fn build_struct<'a, 'ctx, 'env>(
// The layout of the struct expects them to be dropped!
let (field_expr, field_layout) = load_symbol_and_layout(scope, symbol);
if !field_layout.is_dropped_because_empty() {
field_types.push(basic_type_from_layout(env, field_layout));
let field_type = basic_type_from_layout(env, field_layout);
field_types.push(field_type);
if field_layout.is_passed_by_reference(env.layout_interner, env.target_info) {
let field_value = env
.builder
.build_load(field_expr.into_pointer_value(), "load_tag_to_put_in_struct");
let field_value = env.builder.new_build_load(
field_type,
field_expr.into_pointer_value(),
"load_tag_to_put_in_struct",
);
field_vals.push(field_value);
} else {
@ -1764,13 +1831,13 @@ pub fn get_tag_id<'a, 'ctx, 'env>(
debug_assert!(argument.is_pointer_value(), "{:?}", argument);
let argument_ptr = argument.into_pointer_value();
get_tag_id_wrapped(env, argument_ptr)
get_tag_id_wrapped(env, *union_layout, argument_ptr)
}
UnionLayout::Recursive(_) => {
let argument_ptr = argument.into_pointer_value();
if union_layout.stores_tag_id_as_data(env.target_info) {
get_tag_id_wrapped(env, argument_ptr)
get_tag_id_wrapped(env, *union_layout, argument_ptr)
} else {
tag_pointer_read_tag_id(env, argument_ptr)
}
@ -1801,7 +1868,7 @@ pub fn get_tag_id<'a, 'ctx, 'env>(
env.builder.position_at_end(else_block);
let tag_id = if union_layout.stores_tag_id_as_data(env.target_info) {
get_tag_id_wrapped(env, argument_ptr)
get_tag_id_wrapped(env, *union_layout, argument_ptr)
} else {
tag_pointer_read_tag_id(env, argument_ptr)
};
@ -1812,7 +1879,7 @@ pub fn get_tag_id<'a, 'ctx, 'env>(
env.builder.position_at_end(cont_block);
env.builder
.build_load(result, "load_result")
.new_build_load(tag_id_int_type, result, "load_result")
.into_int_value()
}
UnionLayout::NullableUnwrapped { nullable_id, .. } => {
@ -1839,17 +1906,14 @@ fn lookup_at_index_ptr<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let ptr = env
.builder
.build_bitcast(
value,
struct_type.ptr_type(AddressSpace::Generic),
"cast_lookup_at_index_ptr",
)
.into_pointer_value();
let ptr = env.builder.build_pointer_cast(
value,
struct_type.ptr_type(AddressSpace::Generic),
"cast_lookup_at_index_ptr",
);
let elem_ptr = builder
.build_struct_gep(ptr, index as u32, "at_index_struct_gep")
.new_build_struct_gep(struct_type, ptr, index as u32, "at_index_struct_gep")
.unwrap();
let field_layout = field_layouts[index];
@ -1861,11 +1925,13 @@ fn lookup_at_index_ptr<'a, 'ctx, 'env>(
let actual_type = basic_type_from_layout(env, &Layout::Union(*union_layout));
debug_assert!(actual_type.is_pointer_type());
builder.build_bitcast(
result,
actual_type,
"cast_rec_pointer_lookup_at_index_ptr_old",
)
builder
.build_pointer_cast(
result.into_pointer_value(),
actual_type.into_pointer_type(),
"cast_rec_pointer_lookup_at_index_ptr_old",
)
.into()
} else {
result
}
@ -1881,19 +1947,21 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>(
let builder = env.builder;
let struct_layout = Layout::struct_no_name_order(field_layouts);
let struct_type = basic_type_from_layout(env, &struct_layout);
let struct_type = basic_type_from_layout(env, &struct_layout).into_struct_type();
let data_ptr = env
.builder
.build_bitcast(
value,
struct_type.ptr_type(AddressSpace::Generic),
"cast_lookup_at_index_ptr",
)
.into_pointer_value();
let data_ptr = env.builder.build_pointer_cast(
value,
struct_type.ptr_type(AddressSpace::Generic),
"cast_lookup_at_index_ptr",
);
let elem_ptr = builder
.build_struct_gep(data_ptr, index as u32, "at_index_struct_gep_data")
.new_build_struct_gep(
struct_type,
data_ptr,
index as u32,
"at_index_struct_gep_data",
)
.unwrap();
let field_layout = field_layouts[index];
@ -1906,11 +1974,13 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>(
let actual_type = basic_type_from_layout(env, &Layout::Union(*union_layout));
debug_assert!(actual_type.is_pointer_type());
builder.build_bitcast(
result,
actual_type,
"cast_rec_pointer_lookup_at_index_ptr_new",
)
builder
.build_pointer_cast(
result.into_pointer_value(),
actual_type.into_pointer_type(),
"cast_rec_pointer_lookup_at_index_ptr_new",
)
.into()
} else {
result
}
@ -1994,8 +2064,7 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
let ptr_type = value_type.ptr_type(AddressSpace::Generic);
env.builder
.build_bitcast(ptr, ptr_type, "alloc_cast_to_desired")
.into_pointer_value()
.build_pointer_cast(ptr, ptr_type, "alloc_cast_to_desired")
}
fn list_literal<'a, 'ctx, 'env>(
@ -2101,8 +2170,12 @@ fn list_literal<'a, 'ctx, 'env>(
let offset = env.ptr_int().const_int(zero_elements as _, false);
let ptr = unsafe {
env.builder
.build_in_bounds_gep(global, &[zero, offset], "first_element_pointer")
env.builder.new_build_in_bounds_gep(
element_type,
global,
&[zero, offset],
"first_element_pointer",
)
};
super::build_list::store_list(env, ptr, list_length_intval).into()
@ -2124,7 +2197,9 @@ fn list_literal<'a, 'ctx, 'env>(
// then replace the `undef`s with the values that we evaluate at runtime
for (index, val) in runtime_evaluated_elements {
let index_val = ctx.i64_type().const_int(index as u64, false);
let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") };
let elem_ptr = unsafe {
builder.new_build_in_bounds_gep(element_type, ptr, &[index_val], "index")
};
builder.build_store(elem_ptr, val);
}
@ -2143,7 +2218,9 @@ fn list_literal<'a, 'ctx, 'env>(
ListLiteralElement::Symbol(symbol) => load_symbol(scope, symbol),
};
let index_val = ctx.i64_type().const_int(index as u64, false);
let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") };
let elem_ptr = unsafe {
builder.new_build_in_bounds_gep(element_type, ptr, &[index_val], "index")
};
store_roc_value(env, *element_layout, elem_ptr, val);
}
@ -2158,14 +2235,16 @@ pub fn load_roc_value<'a, 'ctx, 'env>(
source: PointerValue<'ctx>,
name: &str,
) -> BasicValueEnum<'ctx> {
let basic_type = basic_type_from_layout(env, &layout);
if layout.is_passed_by_reference(env.layout_interner, env.target_info) {
let alloca = entry_block_alloca_zerofill(env, basic_type_from_layout(env, &layout), name);
let alloca = entry_block_alloca_zerofill(env, basic_type, name);
store_roc_value(env, layout, alloca, source.into());
alloca.into()
} else {
env.builder.build_load(source, name)
env.builder.new_build_load(basic_type, source, name)
}
}
@ -2586,6 +2665,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
condition: cond_symbol,
region,
lookups,
variables,
remainder,
} => {
let bd = env.builder;
@ -2620,6 +2700,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
*cond_symbol,
*region,
lookups,
variables,
);
if let LlvmBackendMode::BinaryDev = env.mode {
@ -2654,6 +2735,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
condition: cond_symbol,
region,
lookups,
variables,
remainder,
} => {
let bd = env.builder;
@ -2688,6 +2770,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
*cond_symbol,
*region,
lookups,
variables,
);
bd.build_unconditional_branch(then_block);
@ -2813,7 +2896,13 @@ pub fn complex_bitcast<'ctx>(
// we can't use the more straightforward bitcast in all cases
// it seems like a bitcast only works on integers and pointers
// and crucially does not work not on arrays
return builder.build_bitcast(from_value, to_type, name);
return builder
.build_pointer_cast(
from_value.into_pointer_value(),
to_type.into_pointer_type(),
name,
)
.into();
}
complex_bitcast_from_bigger_than_to(builder, from_value, to_type, name)
@ -2833,7 +2922,14 @@ pub fn complex_bitcast_check_size<'a, 'ctx, 'env>(
// we can't use the more straightforward bitcast in all cases
// it seems like a bitcast only works on integers and pointers
// and crucially does not work not on arrays
return env.builder.build_bitcast(from_value, to_type, name);
return env
.builder
.build_pointer_cast(
from_value.into_pointer_value(),
to_type.into_pointer_type(),
name,
)
.into();
}
let block = env.builder.get_insert_block().expect("to be in a function");
@ -2889,15 +2985,13 @@ fn complex_bitcast_from_bigger_than_to<'ctx>(
builder.build_store(argument_pointer, from_value);
// then read it back as a different type
let to_type_pointer = builder
.build_bitcast(
argument_pointer,
to_type.ptr_type(inkwell::AddressSpace::Generic),
name,
)
.into_pointer_value();
let to_type_pointer = builder.build_pointer_cast(
argument_pointer,
to_type.ptr_type(inkwell::AddressSpace::Generic),
name,
);
builder.build_load(to_type_pointer, "cast_value")
builder.new_build_load(to_type, to_type_pointer, "cast_value")
}
fn complex_bitcast_to_bigger_than_from<'ctx>(
@ -2912,35 +3006,42 @@ fn complex_bitcast_to_bigger_than_from<'ctx>(
let storage = builder.build_alloca(to_type, "cast_alloca");
// then cast the pointer to our desired type
let from_type_pointer = builder
.build_bitcast(
storage,
from_value
.get_type()
.ptr_type(inkwell::AddressSpace::Generic),
name,
)
.into_pointer_value();
let from_type_pointer = builder.build_pointer_cast(
storage,
from_value
.get_type()
.ptr_type(inkwell::AddressSpace::Generic),
name,
);
// store the value in memory
builder.build_store(from_type_pointer, from_value);
// then read it back as a different type
builder.build_load(storage, "cast_value")
builder.new_build_load(to_type, storage, "cast_value")
}
/// get the tag id out of a pointer to a wrapped (i.e. stores the tag id at runtime) layout
fn get_tag_id_wrapped<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
union_layout: UnionLayout<'a>,
from_value: PointerValue<'ctx>,
) -> IntValue<'ctx> {
let union_struct_type = struct_type_from_union_layout(env, &union_layout);
let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout());
let tag_id_ptr = env
.builder
.build_struct_gep(from_value, RocUnion::TAG_ID_INDEX, "tag_id_ptr")
.new_build_struct_gep(
union_struct_type,
from_value,
RocUnion::TAG_ID_INDEX,
"tag_id_ptr",
)
.unwrap();
env.builder
.build_load(tag_id_ptr, "load_tag_id")
.new_build_load(tag_id_type, tag_id_ptr, "load_tag_id")
.into_int_value()
}
@ -3034,7 +3135,6 @@ fn build_switch_ir<'a, 'ctx, 'env>(
let int_type = match float_width {
FloatWidth::F32 => env.context.i32_type(),
FloatWidth::F64 => env.context.i64_type(),
FloatWidth::F128 => env.context.i128_type(),
};
builder
@ -3312,16 +3412,15 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
// not pretty, but seems to cover all our current cases
if arg_type.is_pointer_type() && !fastcc_type.is_pointer_type() {
// bitcast the ptr
let fastcc_ptr = env
.builder
.build_bitcast(
*arg,
fastcc_type.ptr_type(AddressSpace::Generic),
"bitcast_arg",
)
.into_pointer_value();
let fastcc_ptr = env.builder.build_pointer_cast(
arg.into_pointer_value(),
fastcc_type.ptr_type(AddressSpace::Generic),
"bitcast_arg",
);
let loaded = env.builder.build_load(fastcc_ptr, "load_arg");
let loaded = env
.builder
.new_build_load(fastcc_type, fastcc_ptr, "load_arg");
arguments_for_call.push(loaded);
} else {
let as_cc_type = env.builder.build_pointer_cast(
@ -3437,9 +3536,11 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
} else {
match layout {
Layout::Builtin(Builtin::List(_)) => {
let loaded = env
.builder
.build_load(arg.into_pointer_value(), "load_list_pointer");
let loaded = env.builder.new_build_load(
arg_type,
arg.into_pointer_value(),
"load_list_pointer",
);
let cast =
complex_bitcast_check_size(env, loaded, fastcc_type, "to_fastcc_type_1");
arguments_for_call.push(cast);
@ -3537,6 +3638,15 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
Linkage::External,
);
let c_abi_roc_str_type = env.context.struct_type(
&[
env.context.i8_type().ptr_type(AddressSpace::Generic).into(),
env.ptr_int().into(),
env.ptr_int().into(),
],
false,
);
// a temporary solution to be able to pass RocStr by-value from a host language.
{
let extra = match cc_return {
@ -3555,7 +3665,7 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
// if ret_typ is a pointer type. We need the base type here.
let ret_typ = c_function.get_type().get_param_types()[i + extra];
let ret_base_typ = if ret_typ.is_pointer_type() {
ret_typ.into_pointer_type().get_element_type()
c_abi_roc_str_type.as_any_type_enum()
} else {
ret_typ.as_any_type_enum()
};
@ -3610,8 +3720,9 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
let it = params
.iter()
.zip(param_types)
.zip(arguments)
.enumerate()
.map(|(i, (arg, fastcc_type))| {
.map(|(i, ((arg, fastcc_type), layout))| {
let arg_type = arg.get_type();
if arg_type == *fastcc_type {
// the C and Fast calling conventions agree
@ -3625,13 +3736,18 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
env.target_info.architecture,
roc_target::Architecture::X86_32 | roc_target::Architecture::X86_64
) {
let c_abi_type = match layout {
Layout::Builtin(Builtin::Str | Builtin::List(_)) => c_abi_roc_str_type,
_ => todo!("figure out what the C type is"),
};
let byval = context.create_type_attribute(
Attribute::get_named_enum_kind_id("byval"),
arg_type.into_pointer_type().get_element_type(),
c_abi_type.as_any_type_enum(),
);
let nonnull = context.create_type_attribute(
Attribute::get_named_enum_kind_id("nonnull"),
arg_type.into_pointer_type().get_element_type(),
c_abi_type.as_any_type_enum(),
);
// C return pointer goes at the beginning of params, and we must skip it if it exists.
let returns_pointer = matches!(cc_return, CCReturn::ByPointer);
@ -3641,16 +3757,14 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
c_function.add_attribute(AttributeLoc::Param(param_index), nonnull);
}
// bitcast the ptr
let fastcc_ptr = env
.builder
.build_bitcast(
*arg,
fastcc_type.ptr_type(AddressSpace::Generic),
"bitcast_arg",
)
.into_pointer_value();
let fastcc_ptr = env.builder.build_pointer_cast(
arg.into_pointer_value(),
fastcc_type.ptr_type(AddressSpace::Generic),
"bitcast_arg",
);
env.builder.build_load(fastcc_ptr, "load_arg")
env.builder
.new_build_load(*fastcc_type, fastcc_ptr, "load_arg")
} else {
complex_bitcast_check_size(env, *arg, *fastcc_type, "to_fastcc_type_2")
}
@ -3667,9 +3781,11 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
env.builder.build_return(Some(&value));
}
RocReturn::ByPointer => {
let loaded = env
.builder
.build_load(value.into_pointer_value(), "load_result");
let loaded = env.builder.new_build_load(
return_type,
value.into_pointer_value(),
"load_result",
);
env.builder.build_return(Some(&loaded));
}
},
@ -3683,9 +3799,11 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
// TODO: ideally, in this case, we should pass the C return pointer directly
// into the call_roc_function rather than forcing an extra alloca, load, and
// store!
let value = env
.builder
.build_load(value.into_pointer_value(), "load_roc_result");
let value = env.builder.new_build_load(
return_type,
value.into_pointer_value(),
"load_roc_result",
);
env.builder.build_store(out_ptr, value);
}
}
@ -3806,13 +3924,11 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu
global.set_initializer(&type_.const_zero());
env.builder
.build_bitcast(
global.as_pointer_value(),
env.context.i32_type().ptr_type(AddressSpace::Generic),
"cast_sjlj_buffer",
)
.into_pointer_value()
env.builder.build_pointer_cast(
global.as_pointer_value(),
env.context.i32_type().ptr_type(AddressSpace::Generic),
"cast_sjlj_buffer",
)
}
pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> {
@ -3824,18 +3940,17 @@ pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
// Anywhere else, use the LLVM intrinsic.
// https://llvm.org/docs/ExceptionHandling.html#llvm-eh-sjlj-setjmp
let jmp_buf_i8p_arr = env
.builder
.build_bitcast(
jmp_buf,
env.context
.i8_type()
.ptr_type(AddressSpace::Generic)
.array_type(5)
.ptr_type(AddressSpace::Generic),
"jmp_buf [5 x i8*]",
)
.into_pointer_value();
let buf_type = env
.context
.i8_type()
.ptr_type(AddressSpace::Generic)
.array_type(5);
let jmp_buf_i8p_arr = env.builder.build_pointer_cast(
jmp_buf,
buf_type.ptr_type(AddressSpace::Generic),
"jmp_buf [5 x i8*]",
);
// LLVM asks us to please store the frame pointer in the first word.
let frame_address = env.call_intrinsic(
@ -3846,7 +3961,8 @@ pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
let zero = env.context.i32_type().const_zero();
let fa_index = env.context.i32_type().const_zero();
let fa = unsafe {
env.builder.build_in_bounds_gep(
env.builder.new_build_in_bounds_gep(
buf_type,
jmp_buf_i8p_arr,
&[zero, fa_index],
"frame address index",
@ -3859,17 +3975,24 @@ pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
// usage. But for whatever reason, on x86, it appears we need a stacksave in those words.
let ss_index = env.context.i32_type().const_int(2, false);
let ss = unsafe {
env.builder
.build_in_bounds_gep(jmp_buf_i8p_arr, &[zero, ss_index], "name")
env.builder.new_build_in_bounds_gep(
buf_type,
jmp_buf_i8p_arr,
&[zero, ss_index],
"name",
)
};
let stack_save = env.call_intrinsic(LLVM_STACK_SAVE, &[]);
env.builder.build_store(ss, stack_save);
let jmp_buf_i8p = env.builder.build_bitcast(
jmp_buf,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"jmp_buf i8*",
);
let jmp_buf_i8p = env
.builder
.build_pointer_cast(
jmp_buf,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"jmp_buf i8*",
)
.into();
env.call_intrinsic(LLVM_SETJMP, &[jmp_buf_i8p])
}
}
@ -3958,7 +4081,8 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>(
let v1 = call_result_type.const_zero();
// tag must be non-zero, indicating failure
let tag = builder.build_load(error_tag_ptr, "load_panic_tag");
let tag =
builder.new_build_load(env.context.i64_type(), error_tag_ptr, "load_panic_tag");
let v2 = builder.build_insert_value(v1, tag, 0, "set_error").unwrap();
@ -3975,7 +4099,11 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>(
env.builder.position_at_end(cont_block);
builder.build_load(result_alloca, "set_jump_and_catch_long_jump_load_result")
builder.new_build_load(
call_result_type,
result_alloca,
"set_jump_and_catch_long_jump_load_result",
)
}
fn make_exception_catcher<'a, 'ctx, 'env>(
@ -4025,6 +4153,8 @@ fn make_good_roc_result<'a, 'ctx, 'env>(
let context = env.context;
let builder = env.builder;
let return_type = basic_type_from_layout(env, &return_layout);
let v1 = roc_call_result_type(env, basic_type_from_layout(env, &return_layout)).const_zero();
let v2 = builder
@ -4032,7 +4162,8 @@ fn make_good_roc_result<'a, 'ctx, 'env>(
.unwrap();
let v3 = if return_layout.is_passed_by_reference(env.layout_interner, env.target_info) {
let loaded = env.builder.build_load(
let loaded = env.builder.new_build_load(
return_type,
return_value.into_pointer_value(),
"load_call_result_passed_by_ptr",
);
@ -4623,7 +4754,8 @@ fn build_closure_caller<'a, 'ctx, 'env>(
if param.is_pointer_value()
&& !layout.is_passed_by_reference(env.layout_interner, env.target_info)
{
*param = builder.build_load(param.into_pointer_value(), "load_param");
let basic_type = basic_type_from_layout(env, layout);
*param = builder.new_build_load(basic_type, param.into_pointer_value(), "load_param");
}
}
@ -4923,7 +5055,8 @@ pub fn call_roc_function<'a, 'ctx, 'env>(
debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV);
call.set_call_convention(FAST_CALL_CONV);
env.builder.build_load(result_alloca, "load_result")
env.builder
.new_build_load(result_type, result_alloca, "load_result")
}
RocReturn::ByPointer => {
let it = arguments.iter().map(|x| (*x).into());
@ -4947,8 +5080,11 @@ pub fn call_roc_function<'a, 'ctx, 'env>(
if result_layout.is_passed_by_reference(env.layout_interner, env.target_info) {
result_alloca.into()
} else {
env.builder
.build_load(result_alloca, "return_by_pointer_load_result")
env.builder.new_build_load(
result_type,
result_alloca,
"return_by_pointer_load_result",
)
}
}
RocReturn::Return => {
@ -5155,31 +5291,24 @@ pub struct FunctionSpec<'ctx> {
pub typ: FunctionType<'ctx>,
call_conv: u32,
/// Index (0-based) of return-by-pointer parameter, if it exists.
/// We only care about this for C-call-conv functions, because this may take
/// ownership of a register due to the convention. For example, on AArch64,
/// values returned-by-pointer use the x8 register.
/// But for internal functions we don't need to worry about that and we don't
/// want the convention, since it might eat a register and cause a spill!
cconv_sret_parameter: Option<u32>,
cconv_stack_return_type: Option<BasicTypeEnum<'ctx>>,
}
impl<'ctx> FunctionSpec<'ctx> {
fn attach_attributes(&self, ctx: &Context, fn_val: FunctionValue<'ctx>) {
fn_val.set_call_conventions(self.call_conv);
if let Some(param_index) = self.cconv_sret_parameter {
if let Some(stack_return_type) = self.cconv_stack_return_type {
// Indicate to LLVM that this argument holds the return value of the function.
let sret_attribute_id = Attribute::get_named_enum_kind_id("sret");
debug_assert!(sret_attribute_id > 0);
let ret_typ = self.typ.get_param_types()[param_index as usize];
// if ret_typ is a pointer type. We need the base type here.
let ret_base_typ = if ret_typ.is_pointer_type() {
ret_typ.into_pointer_type().get_element_type()
} else {
ret_typ.as_any_type_enum()
};
let sret_attribute = ctx.create_type_attribute(sret_attribute_id, ret_base_typ);
let sret_attribute =
ctx.create_type_attribute(sret_attribute_id, stack_return_type.as_any_type_enum());
fn_val.add_attribute(AttributeLoc::Param(0), sret_attribute);
}
}
@ -5201,7 +5330,11 @@ impl<'ctx> FunctionSpec<'ctx> {
arguments.extend(argument_types);
let arguments = function_arguments(env, &arguments);
(env.context.void_type().fn_type(&arguments, false), Some(0))
(
env.context.void_type().fn_type(&arguments, false),
Some(return_type.unwrap()),
)
}
CCReturn::Return => {
let arguments = function_arguments(env, argument_types);
@ -5216,7 +5349,7 @@ impl<'ctx> FunctionSpec<'ctx> {
Self {
typ,
call_conv: C_CALL_CONV,
cconv_sret_parameter: opt_sret_parameter,
cconv_stack_return_type: opt_sret_parameter,
}
}
@ -5242,7 +5375,7 @@ impl<'ctx> FunctionSpec<'ctx> {
Self {
typ,
call_conv: FAST_CALL_CONV,
cconv_sret_parameter: None,
cconv_stack_return_type: None,
}
}
@ -5250,7 +5383,7 @@ impl<'ctx> FunctionSpec<'ctx> {
Self {
typ: fn_type,
call_conv: FAST_CALL_CONV,
cconv_sret_parameter: None,
cconv_stack_return_type: None,
}
}
@ -5260,7 +5393,7 @@ impl<'ctx> FunctionSpec<'ctx> {
Self {
typ: fn_type,
call_conv: C_CALL_CONV,
cconv_sret_parameter: None,
cconv_stack_return_type: None,
}
}
}
@ -5395,7 +5528,7 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
env.builder.build_alloca(param.get_type(), "param_alloca");
env.builder.build_store(param_alloca, param);
let as_cc_type = env.builder.build_bitcast(
let as_cc_type = env.builder.build_pointer_cast(
param_alloca,
cc_type.into_pointer_type(),
"to_cc_type_ptr",
@ -5425,9 +5558,11 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
let return_value = match cc_return {
CCReturn::Return => call.try_as_basic_value().left().unwrap(),
CCReturn::ByPointer => {
env.builder.build_load(return_pointer, "read_result")
}
CCReturn::ByPointer => env.builder.new_build_load(
return_type,
return_pointer,
"read_result",
),
CCReturn::Void => return_type.const_zero(),
};
@ -5465,18 +5600,16 @@ fn define_global_str_literal_ptr<'a, 'ctx, 'env>(
) -> PointerValue<'ctx> {
let global = define_global_str_literal(env, message);
let ptr = env
.builder
.build_bitcast(
global,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"to_opaque",
)
.into_pointer_value();
let ptr = env.builder.build_pointer_cast(
global.as_pointer_value(),
env.context.i8_type().ptr_type(AddressSpace::Generic),
"to_opaque",
);
// a pointer to the first actual data (skipping over the refcount)
let ptr = unsafe {
env.builder.build_in_bounds_gep(
env.builder.new_build_in_bounds_gep(
env.context.i8_type(),
ptr,
&[env
.ptr_int()
@ -5615,7 +5748,7 @@ pub fn add_func<'ctx>(
fn_val
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum WhenRecursive<'a> {
Unreachable,
Loop(UnionLayout<'a>),

View File

@ -1,844 +0,0 @@
use crate::debug_info_init;
use crate::llvm::bitcode::{
build_dec_wrapper, build_eq_wrapper, build_inc_wrapper, call_bitcode_fn, call_void_bitcode_fn,
};
use crate::llvm::build::{
complex_bitcast, load_roc_value, load_symbol, load_symbol_and_layout, Env, RocFunctionCall,
Scope,
};
use crate::llvm::build_list::{layout_width, pass_as_opaque};
use crate::llvm::convert::{basic_type_from_layout, zig_dict_type};
use crate::llvm::refcounting::Mode;
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::types::BasicType;
use inkwell::values::{BasicValue, BasicValueEnum, FunctionValue, IntValue, StructValue};
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds};
use roc_target::TargetInfo;
use super::bitcode::call_list_bitcode_fn;
use super::build::store_roc_value;
use super::build_list::list_to_c_abi;
#[repr(transparent)]
struct Alignment(u8);
impl Alignment {
fn from_key_value_layout(key: &Layout, value: &Layout, target_info: TargetInfo) -> Alignment {
let key_align = key.alignment_bytes(target_info);
let value_align = value.alignment_bytes(target_info);
let mut bits = key_align.max(value_align) as u8;
// alignment must be a power of 2
debug_assert!(bits.is_power_of_two());
let value_before_key_flag = 0b1000_0000;
if key_align < value_align {
bits |= value_before_key_flag;
}
Alignment(bits)
}
fn as_int_value<'ctx>(&self, context: &'ctx Context) -> IntValue<'ctx> {
context.i8_type().const_int(self.0 as u64, false)
}
}
pub fn dict_len<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
dict_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let (_, dict_layout) = load_symbol_and_layout(scope, &dict_symbol);
match dict_layout {
Layout::Builtin(Builtin::Dict(_, _)) =>
_ => unreachable!("Invalid layout given to Dict.len : {:?}", dict_layout),
}
}
pub fn dict_empty<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> {
// get the RocDict type defined by zig
let roc_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
// we must give a pointer for the bitcode function to write the result into
let result_alloc = env.builder.build_alloca(roc_dict_type, "dict_empty");
call_void_bitcode_fn(env, &[result_alloc.into()], bitcode::DICT_EMPTY);
env.builder.build_load(result_alloc, "load_result")
}
#[allow(clippy::too_many_arguments)]
pub fn dict_insert<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict: BasicValueEnum<'ctx>,
key: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value: BasicValueEnum<'ctx>,
value_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let key_type = basic_type_from_layout(env, key_layout);
let value_type = basic_type_from_layout(env, value_layout);
let key_ptr = builder.build_alloca(key_type, "key_ptr");
let value_ptr = builder.build_alloca(value_type, "value_ptr");
store_roc_value(env, *key_layout, key_ptr, key);
store_roc_value(env, *value_layout, value_ptr, value);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env
.ptr_int()
.const_int(value_layout.stack_size(env.target_info) as u64, false);
let result_ptr = builder.build_alloca(zig_dict_type(env), "result_ptr");
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
let dec_key_fn = build_dec_wrapper(env, layout_ids, key_layout);
let dec_value_fn = build_dec_wrapper(env, layout_ids, value_layout);
call_void_bitcode_fn(
env,
&[
pass_dict_c_abi(env, dict),
alignment_iv.into(),
env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"),
key_width.into(),
env.builder.build_bitcast(value_ptr, u8_ptr, "to_u8_ptr"),
value_width.into(),
hash_fn.as_global_value().as_pointer_value().into(),
eq_fn.as_global_value().as_pointer_value().into(),
dec_key_fn.as_global_value().as_pointer_value().into(),
dec_value_fn.as_global_value().as_pointer_value().into(),
result_ptr.into(),
],
bitcode::DICT_INSERT,
);
env.builder.build_load(result_ptr, "load_result")
}
#[allow(clippy::too_many_arguments)]
pub fn dict_remove<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict: BasicValueEnum<'ctx>,
key: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let key_ptr = builder.build_alloca(key.get_type(), "key_ptr");
env.builder.build_store(key_ptr, key);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env
.ptr_int()
.const_int(value_layout.stack_size(env.target_info) as u64, false);
let result_ptr = builder.build_alloca(zig_dict_type(env), "result_ptr");
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
let dec_key_fn = build_dec_wrapper(env, layout_ids, key_layout);
let dec_value_fn = build_dec_wrapper(env, layout_ids, value_layout);
call_void_bitcode_fn(
env,
&[
pass_dict_c_abi(env, dict),
alignment_iv.into(),
env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"),
key_width.into(),
value_width.into(),
hash_fn.as_global_value().as_pointer_value().into(),
eq_fn.as_global_value().as_pointer_value().into(),
dec_key_fn.as_global_value().as_pointer_value().into(),
dec_value_fn.as_global_value().as_pointer_value().into(),
result_ptr.into(),
],
bitcode::DICT_REMOVE,
);
env.builder.build_load(result_ptr, "load_result")
}
#[allow(clippy::too_many_arguments)]
pub fn dict_contains<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict: BasicValueEnum<'ctx>,
key: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let key_ptr = builder.build_alloca(key.get_type(), "key_ptr");
env.builder.build_store(key_ptr, key);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env
.ptr_int()
.const_int(value_layout.stack_size(env.target_info) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
call_bitcode_fn(
env,
&[
pass_dict_c_abi(env, dict),
alignment_iv.into(),
env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"),
key_width.into(),
value_width.into(),
hash_fn.as_global_value().as_pointer_value().into(),
eq_fn.as_global_value().as_pointer_value().into(),
],
bitcode::DICT_CONTAINS,
)
}
#[allow(clippy::too_many_arguments)]
pub fn dict_get<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict: BasicValueEnum<'ctx>,
key: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let key_ptr = builder.build_alloca(key.get_type(), "key_ptr");
env.builder.build_store(key_ptr, key);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env
.ptr_int()
.const_int(value_layout.stack_size(env.target_info) as u64, false);
let value_bt = basic_type_from_layout(env, value_layout);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout);
// { flag: bool, value: *const u8 }
let result = call_bitcode_fn(
env,
&[
pass_dict_c_abi(env, dict),
alignment_iv.into(),
env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"),
key_width.into(),
value_width.into(),
hash_fn.as_global_value().as_pointer_value().into(),
eq_fn.as_global_value().as_pointer_value().into(),
inc_value_fn.as_global_value().as_pointer_value().into(),
],
bitcode::DICT_GET,
)
.into_struct_value();
let flag_u8 = env
.builder
.build_extract_value(result, 1, "get_flag")
.unwrap()
.into_int_value();
let flag = env
.builder
.build_int_cast(flag_u8, env.context.bool_type(), "to_bool");
let value_u8_ptr_int = env
.builder
.build_extract_value(result, 0, "get_value_ptr_int")
.unwrap()
.into_int_value();
let ptr_type = value_bt.ptr_type(AddressSpace::Generic);
let value_u8_ptr = env
.builder
.build_int_to_ptr(value_u8_ptr_int, ptr_type, "opaque_value_ptr");
let start_block = env.builder.get_insert_block().unwrap();
let parent = start_block.get_parent().unwrap();
let if_not_null = env.context.append_basic_block(parent, "if_not_null");
let done_block = env.context.append_basic_block(parent, "done");
let default = value_bt.const_zero();
env.builder
.build_conditional_branch(flag, if_not_null, done_block);
env.builder.position_at_end(if_not_null);
let value_ptr = env
.builder
.build_bitcast(
value_u8_ptr,
value_bt.ptr_type(AddressSpace::Generic),
"from_opaque",
)
.into_pointer_value();
let loaded = env.builder.build_load(value_ptr, "load_value");
env.builder.build_unconditional_branch(done_block);
env.builder.position_at_end(done_block);
let result_phi = env.builder.build_phi(value_bt, "result");
result_phi.add_incoming(&[(&default, start_block), (&loaded, if_not_null)]);
let value = result_phi.as_basic_value();
let result = env
.context
.struct_type(&[value_bt, env.context.bool_type().into()], false)
.const_zero();
let result = env
.builder
.build_insert_value(result, flag, 1, "insert_flag")
.unwrap();
env.builder
.build_insert_value(result, value, 0, "insert_value")
.unwrap()
.into_struct_value()
.into()
}
#[allow(clippy::too_many_arguments)]
pub fn dict_elements_rc<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
rc_operation: Mode,
) {
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env
.ptr_int()
.const_int(value_layout.stack_size(env.target_info) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context);
let (key_fn, value_fn) = match rc_operation {
Mode::Inc => (
build_inc_wrapper(env, layout_ids, key_layout),
build_inc_wrapper(env, layout_ids, value_layout),
),
Mode::Dec => (
build_dec_wrapper(env, layout_ids, key_layout),
build_dec_wrapper(env, layout_ids, value_layout),
),
};
call_void_bitcode_fn(
env,
&[
pass_dict_c_abi(env, dict),
alignment_iv.into(),
key_width.into(),
value_width.into(),
key_fn.as_global_value().as_pointer_value().into(),
value_fn.as_global_value().as_pointer_value().into(),
],
bitcode::DICT_ELEMENTS_RC,
);
}
#[allow(clippy::too_many_arguments)]
pub fn dict_keys<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env
.ptr_int()
.const_int(value_layout.stack_size(env.target_info) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context);
let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout);
call_list_bitcode_fn(
env,
&[
pass_dict_c_abi(env, dict),
alignment_iv.into(),
key_width.into(),
value_width.into(),
inc_key_fn.as_global_value().as_pointer_value().into(),
],
bitcode::DICT_KEYS,
)
}
fn pass_dict_c_abi<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
dict: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
match env.target_info.ptr_width() {
roc_target::PtrWidth::Bytes4 => {
let target_type = env.context.custom_width_int_type(96).into();
complex_bitcast(env.builder, dict, target_type, "to_i96")
}
roc_target::PtrWidth::Bytes8 => {
let dict_ptr = env.builder.build_alloca(zig_dict_type(env), "dict_ptr");
env.builder.build_store(dict_ptr, dict);
dict_ptr.into()
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn dict_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict1: BasicValueEnum<'ctx>,
dict2: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env
.ptr_int()
.const_int(value_layout.stack_size(env.target_info) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout);
let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout);
let output_ptr = builder.build_alloca(zig_dict_type(env), "output_ptr");
call_void_bitcode_fn(
env,
&[
pass_dict_c_abi(env, dict1),
pass_dict_c_abi(env, dict2),
alignment_iv.into(),
key_width.into(),
value_width.into(),
hash_fn.as_global_value().as_pointer_value().into(),
eq_fn.as_global_value().as_pointer_value().into(),
inc_key_fn.as_global_value().as_pointer_value().into(),
inc_value_fn.as_global_value().as_pointer_value().into(),
output_ptr.into(),
],
bitcode::DICT_UNION,
);
env.builder.build_load(output_ptr, "load_output_ptr")
}
#[allow(clippy::too_many_arguments)]
pub fn dict_difference<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict1: BasicValueEnum<'ctx>,
dict2: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
dict_intersect_or_difference(
env,
layout_ids,
dict1,
dict2,
key_layout,
value_layout,
bitcode::DICT_DIFFERENCE,
)
}
#[allow(clippy::too_many_arguments)]
pub fn dict_intersection<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict1: BasicValueEnum<'ctx>,
dict2: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
dict_intersect_or_difference(
env,
layout_ids,
dict1,
dict2,
key_layout,
value_layout,
bitcode::DICT_INTERSECTION,
)
}
#[allow(clippy::too_many_arguments)]
fn dict_intersect_or_difference<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict1: BasicValueEnum<'ctx>,
dict2: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
op: &str,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env
.ptr_int()
.const_int(value_layout.stack_size(env.target_info) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
let dec_key_fn = build_dec_wrapper(env, layout_ids, key_layout);
let dec_value_fn = build_dec_wrapper(env, layout_ids, value_layout);
let output_ptr = builder.build_alloca(zig_dict_type, "output_ptr");
call_void_bitcode_fn(
env,
&[
pass_dict_c_abi(env, dict1),
pass_dict_c_abi(env, dict2),
alignment_iv.into(),
key_width.into(),
value_width.into(),
hash_fn.as_global_value().as_pointer_value().into(),
eq_fn.as_global_value().as_pointer_value().into(),
dec_key_fn.as_global_value().as_pointer_value().into(),
dec_value_fn.as_global_value().as_pointer_value().into(),
output_ptr.into(),
],
op,
);
env.builder.build_load(output_ptr, "load_output_ptr")
}
#[allow(clippy::too_many_arguments)]
pub fn dict_walk<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
roc_function_call: RocFunctionCall<'ctx>,
dict: BasicValueEnum<'ctx>,
accum: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
accum_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let accum_bt = basic_type_from_layout(env, accum_layout);
let accum_ptr = builder.build_alloca(accum_bt, "accum_ptr");
env.builder.build_store(accum_ptr, accum);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context);
let output_ptr = builder.build_alloca(accum_bt, "output_ptr");
call_void_bitcode_fn(
env,
&[
pass_dict_c_abi(env, dict),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.builder.build_bitcast(accum_ptr, u8_ptr, "to_opaque"),
alignment_iv.into(),
layout_width(env, key_layout),
layout_width(env, value_layout),
layout_width(env, accum_layout),
env.builder.build_bitcast(output_ptr, u8_ptr, "to_opaque"),
],
bitcode::DICT_WALK,
);
env.builder.build_load(output_ptr, "load_output_ptr")
}
#[allow(clippy::too_many_arguments)]
pub fn dict_values<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
dict: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env
.ptr_int()
.const_int(value_layout.stack_size(env.target_info) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context);
let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout);
call_list_bitcode_fn(
env,
&[
pass_dict_c_abi(env, dict),
alignment_iv.into(),
key_width.into(),
value_width.into(),
inc_value_fn.as_global_value().as_pointer_value().into(),
],
bitcode::DICT_VALUES,
)
}
/// Dict.capacity : Dict * * -> Nat
pub fn dict_capacity<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
) -> IntValue<'ctx> {
builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "dict_capacity")
.unwrap()
.into_int_value()
}
/// Set.capacity : Set * -> Nat
pub fn set_capacity<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
) -> IntValue<'ctx> {
builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "set_capacity")
.unwrap()
.into_int_value()
}
#[allow(clippy::too_many_arguments)]
pub fn set_from_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
list: BasicValueEnum<'ctx>,
key_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env.ptr_int().const_zero();
let result_alloca = builder.build_alloca(zig_dict_type(env), "result_alloca");
let alignment = Alignment::from_key_value_layout(key_layout, &Layout::UNIT, env.target_info);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
let eq_fn = build_eq_wrapper(env, layout_ids, key_layout);
let dec_key_fn = build_dec_wrapper(env, layout_ids, key_layout);
call_void_bitcode_fn(
env,
&[
list_to_c_abi(env, list).into(),
alignment_iv.into(),
key_width.into(),
value_width.into(),
hash_fn.as_global_value().as_pointer_value().into(),
eq_fn.as_global_value().as_pointer_value().into(),
dec_key_fn.as_global_value().as_pointer_value().into(),
result_alloca.into(),
],
bitcode::SET_FROM_LIST,
);
env.builder.build_load(result_alloca, "load_result")
}
fn build_hash_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::GENERIC_HASH_REF;
let fn_name = layout_ids
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let seed_type = env.context.i64_type();
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
seed_type.into(),
&[seed_type.into(), arg_type.into()],
);
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = env.context.create_enum_attribute(kind_id, 1);
function_value.add_attribute(AttributeLoc::Function, attr);
let entry = env.context.append_basic_block(function_value, "entry");
env.builder.position_at_end(entry);
debug_info_init!(env, function_value);
let mut it = function_value.get_param_iter();
let seed_arg = it.next().unwrap().into_int_value();
let value_ptr = it.next().unwrap().into_pointer_value();
seed_arg.set_name(Symbol::ARG_1.as_str(&env.interns));
value_ptr.set_name(Symbol::ARG_2.as_str(&env.interns));
let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);
let value_cast = env
.builder
.build_bitcast(value_ptr, value_type, "cast_to_known_type")
.into_pointer_value();
let val_arg = load_roc_value(env, *layout, value_cast, "load_opaque");
let result =
crate::llvm::build_hash::generic_hash(env, layout_ids, seed_arg, val_arg, layout);
env.builder.build_return(Some(&result));
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
function_value
}
fn dict_symbol_to_zig_dict<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
symbol: Symbol,
) -> StructValue<'ctx> {
let dict = load_symbol(scope, &symbol);
complex_bitcast(
env.builder,
dict,
crate::llvm::convert::zig_dict_type(env).into(),
"dict_to_zig_dict",
)
.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

@ -17,6 +17,7 @@ use roc_mono::layout::{Builtin, Layout, LayoutIds};
use super::bitcode::{call_list_bitcode_fn, BitcodeReturns};
use super::build::{
create_entry_block_alloca, load_roc_value, load_symbol, store_roc_value, struct_from_fields,
BuilderExt,
};
use super::convert::zig_list_type;
@ -70,11 +71,13 @@ fn pass_element_as_opaque<'a, 'ctx, 'env>(
.build_alloca(element_type, "element_to_pass_as_opaque");
store_roc_value(env, layout, element_ptr, element);
env.builder.build_bitcast(
element_ptr,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"pass_element_as_opaque",
)
env.builder
.build_pointer_cast(
element_ptr,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"pass_element_as_opaque",
)
.into()
}
pub(crate) fn layout_width<'a, 'ctx, 'env>(
@ -93,11 +96,13 @@ pub(crate) fn pass_as_opaque<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
ptr: PointerValue<'ctx>,
) -> BasicValueEnum<'ctx> {
env.builder.build_bitcast(
ptr,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"pass_as_opaque",
)
env.builder
.build_pointer_cast(
ptr,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"pass_as_opaque",
)
.into()
}
pub(crate) fn list_with_capacity<'a, 'ctx, 'env>(
@ -134,8 +139,14 @@ pub(crate) fn list_get_unsafe<'a, 'ctx, 'env>(
// Assume the bounds have already been checked earlier
// (e.g. by List.get or List.first, which wrap List.#getUnsafe)
let elem_ptr =
unsafe { builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "list_get_element") };
let elem_ptr = unsafe {
builder.new_build_in_bounds_gep(
elem_type,
array_data_ptr,
&[elem_index],
"list_get_element",
)
};
let result = load_roc_value(env, *element_layout, elem_ptr, "list_get_load_element");
@ -315,7 +326,9 @@ pub(crate) fn list_replace_unsafe<'a, 'ctx, 'env>(
};
// Load the element and returned list into a struct.
let old_element = env.builder.build_load(element_ptr, "load_element");
let old_element = env
.builder
.new_build_load(element_type, element_ptr, "load_element");
// the list has the same alignment as a usize / ptr. The element comes first in the struct if
// its alignment is bigger than that of a list.
@ -604,9 +617,12 @@ where
{
let builder = env.builder;
let element_type = basic_type_from_layout(env, &element_layout);
incrementing_index_loop(env, parent, len, index_name, |index| {
// The pointer to the element in the list
let element_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index], "load_index") };
let element_ptr =
unsafe { builder.new_build_in_bounds_gep(element_type, ptr, &[index], "load_index") };
let elem = load_roc_value(
env,
@ -651,7 +667,9 @@ where
{
builder.position_at_end(loop_bb);
let current_index = builder.build_load(index_alloca, "index").into_int_value();
let current_index = builder
.new_build_load(env.ptr_int(), index_alloca, "index")
.into_int_value();
let next_index = builder.build_int_add(current_index, one, "next_index");
builder.build_store(index_alloca, next_index);

View File

@ -6,6 +6,7 @@ use roc_mono::layout::Layout;
use roc_target::PtrWidth;
use super::bitcode::{call_str_bitcode_fn, BitcodeReturns};
use super::build::BuilderExt;
pub static CHAR_LAYOUT: Layout = Layout::u8();
@ -29,17 +30,18 @@ pub(crate) fn decode_from_utf8_result<'a, 'ctx, 'env>(
match env.target_info.ptr_width() {
PtrWidth::Bytes4 | PtrWidth::Bytes8 => {
let result_ptr_cast = env
.builder
.build_bitcast(
pointer,
record_type.ptr_type(AddressSpace::Generic),
"to_unnamed",
)
.into_pointer_value();
let result_ptr_cast = env.builder.build_pointer_cast(
pointer,
record_type.ptr_type(AddressSpace::Generic),
"to_unnamed",
);
builder
.build_load(result_ptr_cast, "load_utf8_validate_bytes_result")
.new_build_load(
record_type,
result_ptr_cast,
"load_utf8_validate_bytes_result",
)
.into_struct_value()
}
}

View File

@ -15,7 +15,7 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
use super::build::{load_roc_value, use_roc_value};
use super::build::{load_roc_value, use_roc_value, BuilderExt};
use super::convert::argument_type_from_union_layout;
use super::lowlevel::dec_binop_with_unchecked;
@ -111,7 +111,6 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
use FloatWidth::*;
let name = match float_width {
F128 => "eq_f128",
F64 => "eq_f64",
F32 => "eq_f32",
};
@ -199,15 +198,17 @@ fn build_eq<'a, 'ctx, 'env>(
let bt = basic_type_from_layout(env, &layout);
// cast the i64 pointer to a pointer to block of memory
let field1_cast = env
.builder
.build_bitcast(lhs_val, bt, "i64_to_opaque")
.into_pointer_value();
let field1_cast = env.builder.build_pointer_cast(
lhs_val.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
let field2_cast = env
.builder
.build_bitcast(rhs_val, bt, "i64_to_opaque")
.into_pointer_value();
let field2_cast = env.builder.build_pointer_cast(
rhs_val.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
build_tag_eq(
env,
@ -276,7 +277,6 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
use FloatWidth::*;
let name = match float_width {
F128 => "neq_f128",
F64 => "neq_f64",
F32 => "neq_f32",
};
@ -527,7 +527,9 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
builder.build_unconditional_branch(loop_bb);
builder.position_at_end(loop_bb);
let curr_index = builder.build_load(index_alloca, "index").into_int_value();
let curr_index = builder
.new_build_load(env.ptr_int(), index_alloca, "index")
.into_int_value();
// #index < end
let loop_end_cond =
@ -542,14 +544,16 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
builder.position_at_end(body_bb);
let elem1 = {
let elem_ptr =
unsafe { builder.build_in_bounds_gep(ptr1, &[curr_index], "load_index") };
let elem_ptr = unsafe {
builder.new_build_in_bounds_gep(element_type, ptr1, &[curr_index], "load_index")
};
load_roc_value(env, *element_layout, elem_ptr, "get_elem")
};
let elem2 = {
let elem_ptr =
unsafe { builder.build_in_bounds_gep(ptr2, &[curr_index], "load_index") };
let elem_ptr = unsafe {
builder.new_build_in_bounds_gep(element_type, ptr2, &[curr_index], "load_index")
};
load_roc_value(env, *element_layout, elem_ptr, "get_elem")
};
@ -724,15 +728,17 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
let bt = basic_type_from_layout(env, &field_layout);
// cast the i64 pointer to a pointer to block of memory
let field1_cast = env
.builder
.build_bitcast(field1, bt, "i64_to_opaque")
.into_pointer_value();
let field1_cast = env.builder.build_pointer_cast(
field1.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
let field2_cast = env
.builder
.build_bitcast(field2, bt, "i64_to_opaque")
.into_pointer_value();
let field2_cast = env.builder.build_pointer_cast(
field2.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
build_eq(
env,
@ -754,7 +760,7 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
use_roc_value(env, *field_layout, field2, "field2"),
field_layout,
field_layout,
when_recursive.clone(),
when_recursive,
)
.into_int_value()
};
@ -946,7 +952,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env,
layout_ids,
union_layout,
Some(when_recursive.clone()),
Some(when_recursive),
field_layouts,
tag1,
tag2,
@ -1237,32 +1243,26 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
debug_assert!(wrapper_type.is_struct_type());
// cast the opaque pointer to a pointer of the correct shape
let struct1_ptr = env
.builder
.build_bitcast(
tag1,
wrapper_type.ptr_type(AddressSpace::Generic),
"opaque_to_correct",
)
.into_pointer_value();
let struct1_ptr = env.builder.build_pointer_cast(
tag1,
wrapper_type.ptr_type(AddressSpace::Generic),
"opaque_to_correct",
);
let struct2_ptr = env
.builder
.build_bitcast(
tag2,
wrapper_type.ptr_type(AddressSpace::Generic),
"opaque_to_correct",
)
.into_pointer_value();
let struct2_ptr = env.builder.build_pointer_cast(
tag2,
wrapper_type.ptr_type(AddressSpace::Generic),
"opaque_to_correct",
);
let struct1 = env
.builder
.build_load(struct1_ptr, "load_struct1")
.new_build_load(wrapper_type, struct1_ptr, "load_struct1")
.into_struct_value();
let struct2 = env
.builder
.build_load(struct2_ptr, "load_struct2")
.new_build_load(wrapper_type, struct2_ptr, "load_struct2")
.into_struct_value();
build_struct_eq(

View File

@ -1,4 +1,4 @@
use crate::llvm::build::Env;
use crate::llvm::build::{BuilderExt, Env};
use bumpalo::collections::Vec;
use inkwell::context::Context;
use inkwell::types::{BasicType, BasicTypeEnum, FloatType, IntType, StructType};
@ -53,18 +53,16 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
}
}
pub fn basic_type_from_union_layout<'a, 'ctx, 'env>(
pub fn struct_type_from_union_layout<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
union_layout: &UnionLayout<'_>,
) -> BasicTypeEnum<'ctx> {
) -> StructType<'ctx> {
use UnionLayout::*;
match union_layout {
NonRecursive(tags) => {
//
RocUnion::tagged_from_slices(env.layout_interner, env.context, tags, env.target_info)
.struct_type()
.into()
}
Recursive(tags)
| NullableWrapped {
@ -78,8 +76,6 @@ pub fn basic_type_from_union_layout<'a, 'ctx, 'env>(
env.target_info,
)
.struct_type()
.ptr_type(AddressSpace::Generic)
.into()
} else {
RocUnion::untagged_from_slices(
env.layout_interner,
@ -88,8 +84,6 @@ pub fn basic_type_from_union_layout<'a, 'ctx, 'env>(
env.target_info,
)
.struct_type()
.ptr_type(AddressSpace::Generic)
.into()
}
}
NullableUnwrapped { other_fields, .. } => RocUnion::untagged_from_slices(
@ -98,18 +92,31 @@ pub fn basic_type_from_union_layout<'a, 'ctx, 'env>(
&[other_fields],
env.target_info,
)
.struct_type()
.ptr_type(AddressSpace::Generic)
.into(),
.struct_type(),
NonNullableUnwrapped(fields) => RocUnion::untagged_from_slices(
env.layout_interner,
env.context,
&[fields],
env.target_info,
)
.struct_type()
.ptr_type(AddressSpace::Generic)
.into(),
.struct_type(),
}
}
pub fn basic_type_from_union_layout<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
union_layout: &UnionLayout<'_>,
) -> BasicTypeEnum<'ctx> {
use UnionLayout::*;
let struct_type = struct_type_from_union_layout(env, union_layout);
match union_layout {
NonRecursive(_) => struct_type.into(),
Recursive(_)
| NonNullableUnwrapped(_)
| NullableWrapped { .. }
| NullableUnwrapped { .. } => struct_type.ptr_type(AddressSpace::Generic).into(),
}
}
@ -202,7 +209,6 @@ pub fn float_type_from_float_width<'a, 'ctx, 'env>(
use FloatWidth::*;
match float_width {
F128 => todo!("F128 is not implemented"),
F64 => env.context.f64_type(),
F32 => env.context.f32_type(),
}
@ -364,7 +370,12 @@ impl<'ctx> RocUnion<'ctx> {
let data_buffer = env
.builder
.build_struct_gep(tag_alloca, Self::TAG_DATA_INDEX, "data_buffer")
.new_build_struct_gep(
self.struct_type(),
tag_alloca,
Self::TAG_DATA_INDEX,
"data_buffer",
)
.unwrap();
let cast_pointer = env.builder.build_pointer_cast(
@ -390,7 +401,12 @@ impl<'ctx> RocUnion<'ctx> {
let tag_id_ptr = env
.builder
.build_struct_gep(tag_alloca, Self::TAG_ID_INDEX, "tag_id_ptr")
.new_build_struct_gep(
self.struct_type(),
tag_alloca,
Self::TAG_ID_INDEX,
"tag_id_ptr",
)
.unwrap();
let tag_id = tag_id_type.const_int(tag_id as u64, false);
@ -399,7 +415,7 @@ impl<'ctx> RocUnion<'ctx> {
}
env.builder
.build_load(tag_alloca, "load_tag")
.new_build_load(self.struct_type(), tag_alloca, "load_tag")
.into_struct_value()
}
}
@ -431,6 +447,30 @@ pub fn zig_has_tag_id_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructT
.struct_type(&[env.context.bool_type().into(), u8_ptr_t.into()], false)
}
pub fn zig_num_parse_result_type<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
type_name: &str,
) -> StructType<'ctx> {
let name = format!("num.NumParseResult({type_name})");
match env.module.get_struct_type(&name) {
Some(zig_type) => zig_type,
None => panic!("zig does not define the `{name}` type!"),
}
}
pub fn zig_to_int_checked_result_type<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
type_name: &str,
) -> StructType<'ctx> {
let name = format!("num.ToIntCheckedResult({type_name})");
match env.module.get_struct_type(&name) {
Some(zig_type) => zig_type,
None => panic!("zig does not define the `{name}` type!"),
}
}
pub fn zig_with_overflow_roc_dec<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> {
env.module
.get_struct_type("utils.WithOverflow(dec.RocDec)")

View File

@ -5,18 +5,21 @@ use crate::llvm::build_list::{self, incrementing_elem_loop};
use crate::llvm::convert::{basic_type_from_layout, RocUnion};
use inkwell::builder::Builder;
use inkwell::module::Linkage;
use inkwell::types::{BasicMetadataTypeEnum, BasicType};
use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue};
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
use roc_mono::ir::LookupType;
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
use roc_region::all::Region;
use super::build::BuilderExt;
use super::build::{
add_func, load_roc_value, load_symbol_and_layout, use_roc_value, FunctionSpec, LlvmBackendMode,
Scope,
Scope, WhenRecursive,
};
use super::convert::struct_type_from_union_layout;
pub(crate) struct SharedMemoryPointer<'ctx>(PointerValue<'ctx>);
@ -52,10 +55,11 @@ struct Cursors<'ctx> {
fn pointer_at_offset<'ctx>(
bd: &Builder<'ctx>,
element_type: impl BasicType<'ctx>,
ptr: PointerValue<'ctx>,
offset: IntValue<'ctx>,
) -> PointerValue<'ctx> {
unsafe { bd.build_gep(ptr, &[offset], "offset_ptr") }
unsafe { bd.new_build_in_bounds_gep(element_type, ptr, &[offset], "offset_ptr") }
}
/// Writes the module and region into the buffer
@ -96,10 +100,12 @@ fn read_state<'a, 'ctx, 'env>(
let ptr = env.builder.build_pointer_cast(ptr, ptr_type, "");
let one = env.ptr_int().const_int(1, false);
let offset_ptr = pointer_at_offset(env.builder, ptr, one);
let offset_ptr = pointer_at_offset(env.builder, env.ptr_int(), ptr, one);
let count = env.builder.build_load(ptr, "load_count");
let offset = env.builder.build_load(offset_ptr, "load_offset");
let count = env.builder.new_build_load(env.ptr_int(), ptr, "load_count");
let offset = env
.builder
.new_build_load(env.ptr_int(), offset_ptr, "load_offset");
(count.into_int_value(), offset.into_int_value())
}
@ -114,7 +120,7 @@ fn write_state<'a, 'ctx, 'env>(
let ptr = env.builder.build_pointer_cast(ptr, ptr_type, "");
let one = env.ptr_int().const_int(1, false);
let offset_ptr = pointer_at_offset(env.builder, ptr, one);
let offset_ptr = pointer_at_offset(env.builder, env.ptr_int(), ptr, one);
env.builder.build_store(ptr, count);
env.builder.build_store(offset_ptr, offset);
@ -143,6 +149,21 @@ pub(crate) fn notify_parent_dbg(env: &Env, shared_memory: &SharedMemoryPointer)
);
}
// Shape of expect frame:
//
// ===
// Fixed-size header
// ===
// /-- ptr_lookup_1 (ptr_size)
// | var_lookup_1 (u32)
// | ..
// | ptr_lookup_n (ptr_size)
// | var_lookup_n (u32)
// \-> lookup_val_1 (varsize)
// ..
// lookup_val_n (varsize)
//
#[allow(clippy::too_many_arguments)]
pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
@ -151,6 +172,7 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
condition: Symbol,
region: Region,
lookups: &[Symbol],
lookup_variables: &[LookupType],
) {
let original_ptr = shared_memory.0;
@ -160,9 +182,11 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
let after_header = offset;
let space_for_offsets = env
.ptr_int()
.const_int((lookups.len() * env.target_info.ptr_size()) as _, false);
let space_for_offsets = env.ptr_int().const_int(
(lookups.len() * env.target_info.ptr_size() + lookups.len() * std::mem::size_of::<u32>())
as _,
false,
);
let mut lookup_starts = bumpalo::collections::Vec::with_capacity_in(lookups.len(), env.arena);
@ -203,14 +227,47 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
{
let mut offset = after_header;
for lookup_start in lookup_starts {
build_copy(env, original_ptr, offset, lookup_start.into());
for (lookup_start, lookup_var) in lookup_starts.into_iter().zip(lookup_variables) {
// Store the pointer to the value
{
build_copy(env, original_ptr, offset, lookup_start.into());
let ptr_width = env
.ptr_int()
.const_int(env.target_info.ptr_size() as _, false);
let ptr_width = env
.ptr_int()
.const_int(env.target_info.ptr_size() as _, false);
offset = env.builder.build_int_add(offset, ptr_width, "offset")
offset = env.builder.build_int_add(offset, ptr_width, "offset");
}
// Store the specialized variable of the value
{
let ptr = unsafe {
env.builder.new_build_in_bounds_gep(
env.context.i8_type(),
original_ptr,
&[offset],
"at_current_offset",
)
};
let u32_ptr = env.context.i32_type().ptr_type(AddressSpace::Generic);
let ptr = env
.builder
.build_pointer_cast(ptr, u32_ptr, "cast_ptr_type");
let var_value = env
.context
.i32_type()
.const_int(lookup_var.index() as _, false);
env.builder.build_store(ptr, var_value);
let var_size = env
.ptr_int()
.const_int(std::mem::size_of::<u32>() as _, false);
offset = env.builder.build_int_add(offset, var_size, "offset");
}
}
}
@ -219,13 +276,6 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
write_state(env, original_ptr, new_count, offset)
}
#[derive(Clone, Debug, Copy)]
enum WhenRecursive<'a> {
Unreachable,
#[allow(dead_code)]
Loop(UnionLayout<'a>),
}
#[allow(clippy::too_many_arguments)]
fn build_clone<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -264,8 +314,12 @@ fn build_clone<'a, 'ctx, 'env>(
Layout::Union(union_layout) => {
if layout.safe_to_memcpy(env.layout_interner) {
let ptr = unsafe {
env.builder
.build_in_bounds_gep(ptr, &[cursors.offset], "at_current_offset")
env.builder.new_build_in_bounds_gep(
env.context.i8_type(),
ptr,
&[cursors.offset],
"at_current_offset",
)
};
let ptr_type = value.get_type().ptr_type(AddressSpace::Generic);
@ -332,14 +386,18 @@ fn build_clone<'a, 'ctx, 'env>(
let bt = basic_type_from_layout(env, &layout);
// cast the i64 pointer to a pointer to block of memory
let field1_cast = env.builder.build_bitcast(value, bt, "i64_to_opaque");
let field1_cast = env.builder.build_pointer_cast(
value.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
build_clone_tag(
env,
layout_ids,
ptr,
cursors,
field1_cast,
field1_cast.into(),
union_layout,
WhenRecursive::Loop(union_layout),
)
@ -477,6 +535,33 @@ fn build_clone_tag<'a, 'ctx, 'env>(
result.into_int_value()
}
fn load_tag_data<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
union_layout: UnionLayout<'a>,
tag_value: PointerValue<'ctx>,
tag_type: BasicTypeEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let union_struct_type = struct_type_from_union_layout(env, &union_layout);
let raw_data_ptr = env
.builder
.new_build_struct_gep(
union_struct_type,
tag_value,
RocUnion::TAG_DATA_INDEX,
"tag_data",
)
.unwrap();
let data_ptr = env.builder.build_pointer_cast(
raw_data_ptr,
tag_type.ptr_type(AddressSpace::Generic),
"data_ptr",
);
env.builder.new_build_load(tag_type, data_ptr, "load_data")
}
#[allow(clippy::too_many_arguments)]
fn build_clone_tag_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -536,29 +621,19 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
let raw_data_ptr = env
.builder
.build_struct_gep(
tag_value.into_pointer_value(),
RocUnion::TAG_DATA_INDEX,
"tag_data",
)
.unwrap();
let layout = Layout::struct_no_name_order(field_layouts);
let layout = Layout::struct_no_name_order(
env.arena.alloc([layout, union_layout.tag_id_layout()]),
);
let basic_type = basic_type_from_layout(env, &layout);
let data_ptr = env.builder.build_pointer_cast(
raw_data_ptr,
basic_type.ptr_type(AddressSpace::Generic),
"data_ptr",
let data = load_tag_data(
env,
union_layout,
tag_value.into_pointer_value(),
basic_type,
);
let data = env.builder.build_load(data_ptr, "load_data");
let answer =
build_clone(env, layout_ids, ptr, cursors, data, layout, when_recursive);
@ -596,11 +671,6 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
let tag_value = tag_pointer_clear_tag_id(env, tag_value.into_pointer_value());
let raw_data_ptr = env
.builder
.build_struct_gep(tag_value, RocUnion::TAG_DATA_INDEX, "tag_data")
.unwrap();
let layout = Layout::struct_no_name_order(field_layouts);
let layout = if union_layout.stores_tag_id_in_pointer(env.target_info) {
layout
@ -609,15 +679,9 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
env.arena.alloc([layout, union_layout.tag_id_layout()]),
)
};
let basic_type = basic_type_from_layout(env, &layout);
let data_ptr = env.builder.build_pointer_cast(
raw_data_ptr,
basic_type.ptr_type(AddressSpace::Generic),
"data_ptr",
);
let data = env.builder.build_load(data_ptr, "load_data");
let data = load_tag_data(env, union_layout, tag_value, basic_type);
let (width, _) =
union_layout.data_size_and_alignment(env.layout_interner, env.target_info);
@ -653,8 +717,6 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
}
}
NonNullableUnwrapped(fields) => {
//
let tag_value = tag_value.into_pointer_value();
build_copy(env, ptr, offset, extra_offset.into());
@ -674,18 +736,7 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
),
};
let raw_data_ptr = env
.builder
.build_struct_gep(tag_value, RocUnion::TAG_DATA_INDEX, "tag_data")
.unwrap();
let data_ptr = env.builder.build_pointer_cast(
raw_data_ptr,
basic_type.ptr_type(AddressSpace::Generic),
"data_ptr",
);
let data = env.builder.build_load(data_ptr, "load_data");
let data = load_tag_data(env, union_layout, tag_value, basic_type);
let when_recursive = WhenRecursive::Loop(union_layout);
let answer = build_clone(env, layout_ids, ptr, cursors, data, layout, when_recursive);
@ -744,19 +795,7 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
};
let tag_value = tag_pointer_clear_tag_id(env, tag_value.into_pointer_value());
let raw_data_ptr = env
.builder
.build_struct_gep(tag_value, RocUnion::TAG_DATA_INDEX, "tag_data")
.unwrap();
let data_ptr = env.builder.build_pointer_cast(
raw_data_ptr,
basic_type.ptr_type(AddressSpace::Generic),
"data_ptr",
);
let data = env.builder.build_load(data_ptr, "load_data");
let data = load_tag_data(env, union_layout, tag_value, basic_type);
let when_recursive = WhenRecursive::Loop(union_layout);
let answer =
@ -830,23 +869,13 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
),
};
let raw_data_ptr = env
.builder
.build_struct_gep(
tag_value.into_pointer_value(),
RocUnion::TAG_DATA_INDEX,
"tag_data",
)
.unwrap();
let data_ptr = env.builder.build_pointer_cast(
raw_data_ptr,
basic_type.ptr_type(AddressSpace::Generic),
"data_ptr",
let data = load_tag_data(
env,
union_layout,
tag_value.into_pointer_value(),
basic_type,
);
let data = env.builder.build_load(data_ptr, "load_data");
let when_recursive = WhenRecursive::Loop(union_layout);
let answer =
build_clone(env, layout_ids, ptr, cursors, data, layout, when_recursive);
@ -892,8 +921,12 @@ fn build_copy<'a, 'ctx, 'env>(
value: BasicValueEnum<'ctx>,
) -> IntValue<'ctx> {
let ptr = unsafe {
env.builder
.build_in_bounds_gep(ptr, &[offset], "at_current_offset")
env.builder.new_build_in_bounds_gep(
env.context.i8_type(),
ptr,
&[offset],
"at_current_offset",
)
};
let ptr_type = value.get_type().ptr_type(AddressSpace::Generic);
@ -963,7 +996,7 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
if elem.safe_to_memcpy(env.layout_interner) {
// NOTE we are not actually sure the dest is properly aligned
let dest = pointer_at_offset(bd, ptr, offset);
let dest = pointer_at_offset(bd, env.context.i8_type(), ptr, offset);
let src = bd.build_pointer_cast(
elements,
env.context.i8_type().ptr_type(AddressSpace::Generic),
@ -1002,7 +1035,8 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
bd.build_int_mul(element_stack_size, index, "current_offset");
let current_offset =
bd.build_int_add(elements_start_offset, current_offset, "current_offset");
let current_extra_offset = bd.build_load(rest_offset, "element_offset");
let current_extra_offset =
bd.new_build_load(env.ptr_int(), rest_offset, "element_offset");
let offset = current_offset;
let extra_offset = current_extra_offset.into_int_value();
@ -1033,7 +1067,7 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
incrementing_elem_loop(env, parent, *elem, elements, len, "index", body);
bd.build_load(rest_offset, "rest_start_offset")
bd.new_build_load(env.ptr_int(), rest_offset, "rest_start_offset")
.into_int_value()
}
}

View File

@ -1,6 +1,7 @@
use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::build::{add_func, get_panic_msg_ptr, get_panic_tag_ptr, C_CALL_CONV};
use crate::llvm::build::{add_func, get_panic_msg_ptr, get_panic_tag_ptr, BuilderExt, C_CALL_CONV};
use crate::llvm::build::{CCReturn, Env, FunctionSpec};
use crate::llvm::convert::zig_str_type;
use inkwell::module::Linkage;
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
@ -217,7 +218,12 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
roc_target::PtrWidth::Bytes4 => roc_str_arg,
// On 64-bit we pass RocStrs by reference internally
roc_target::PtrWidth::Bytes8 => {
builder.build_load(roc_str_arg.into_pointer_value(), "load_roc_str")
let str_typ = zig_str_type(env);
builder.new_build_load(
str_typ,
roc_str_arg.into_pointer_value(),
"load_roc_str",
)
}
};
@ -263,11 +269,11 @@ pub fn build_longjmp_call(env: &Env) {
call_void_bitcode_fn(env, &[jmp_buf.into(), tag.into()], bitcode::UTILS_LONGJMP);
} else {
// Call the LLVM-intrinsic longjmp: `void @llvm.eh.sjlj.longjmp(i8* %setjmp_buf)`
let jmp_buf_i8p = env.builder.build_bitcast(
let jmp_buf_i8p = env.builder.build_pointer_cast(
jmp_buf,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"jmp_buf i8*",
);
let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[jmp_buf_i8p]);
let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[jmp_buf_i8p.into()]);
}
}

View File

@ -34,7 +34,6 @@ fn add_float_intrinsic<'ctx, F>(
check!(FloatWidth::F32, ctx.f32_type());
check!(FloatWidth::F64, ctx.f64_type());
// check!(IntWidth::F128, ctx.i128_type());
}
fn add_int_intrinsic<'ctx, F>(

View File

@ -11,7 +11,7 @@ use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_error_macros::internal_error;
use roc_module::{low_level::LowLevel, symbol::Symbol};
use roc_mono::{
ir::HigherOrderLowLevel,
ir::{HigherOrderLowLevel, LookupType},
layout::{Builtin, LambdaSet, Layout, LayoutIds},
};
use roc_target::PtrWidth;
@ -24,7 +24,7 @@ use crate::llvm::{
},
build::{
complex_bitcast_check_size, create_entry_block_alloca, function_value_by_func_spec,
load_roc_value, roc_function_call, RocReturn,
load_roc_value, roc_function_call, BuilderExt, RocReturn,
},
build_list::{
list_append_unsafe, list_capacity, list_concat, list_drop_at, list_get_unsafe, list_len,
@ -33,7 +33,9 @@ use crate::llvm::{
pass_update_mode,
},
compare::{generic_eq, generic_neq},
convert::{self, basic_type_from_layout},
convert::{
self, basic_type_from_layout, zig_num_parse_result_type, zig_to_int_checked_result_type,
},
intrinsics::{
LLVM_ADD_SATURATED, LLVM_ADD_WITH_OVERFLOW, LLVM_CEILING, LLVM_COS, LLVM_FABS, LLVM_FLOOR,
LLVM_LOG, LLVM_MUL_WITH_OVERFLOW, LLVM_POW, LLVM_ROUND, LLVM_SIN, LLVM_SQRT,
@ -225,14 +227,23 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
intrinsic,
),
None => {
let return_type = zig_function_type.get_param_types()[0]
.into_pointer_type()
.get_element_type()
.into_struct_type()
.into();
let return_type_name = match number_layout {
Layout::Builtin(Builtin::Int(int_width)) => int_width.type_name(),
Layout::Builtin(Builtin::Decimal) => {
// zig picks 128 for dec.RocDec
"i128"
}
_ => unreachable!(),
};
let zig_return_alloca =
create_entry_block_alloca(env, parent, return_type, "str_to_num");
let return_type = zig_num_parse_result_type(env, return_type_name);
let zig_return_alloca = create_entry_block_alloca(
env,
parent,
return_type.into(),
"str_to_num",
);
let (a, b) =
pass_list_or_string_to_zig_32bit(env, string.into_struct_value());
@ -257,7 +268,31 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
}
}
PtrWidth::Bytes8 => {
call_bitcode_fn_fixing_for_convention(env, &[string], layout, intrinsic)
let cc_return_by_pointer = match number_layout {
Layout::Builtin(Builtin::Int(int_width)) => {
(int_width.stack_size() as usize > env.target_info.ptr_size())
.then_some(int_width.type_name())
}
Layout::Builtin(Builtin::Decimal) => {
// zig picks 128 for dec.RocDec
Some("i128")
}
_ => None,
};
if let Some(type_name) = cc_return_by_pointer {
let bitcode_return_type = zig_num_parse_result_type(env, type_name);
call_bitcode_fn_fixing_for_convention(
env,
bitcode_return_type,
&[string],
layout,
intrinsic,
)
} else {
call_bitcode_fn(env, &[string], intrinsic)
}
}
};
@ -438,16 +473,10 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
use roc_target::OperatingSystem::*;
match env.target_info.operating_system {
Windows => {
// we have to go digging to find the return type
let function = env
.module
.get_function(bitcode::STR_GET_SCALAR_UNSAFE)
.unwrap();
let return_type = function.get_type().get_param_types()[0]
.into_pointer_type()
.get_element_type()
.into_struct_type();
let return_type = env.context.struct_type(
&[env.ptr_int().into(), env.context.i32_type().into()],
false,
);
let result = env.builder.build_alloca(return_type, "result");
@ -464,7 +493,8 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
"cast",
);
env.builder.build_load(cast_result, "load_result")
env.builder
.new_build_load(return_type, cast_result, "load_result")
}
Unix => {
let result = call_str_bitcode_fn(
@ -1120,14 +1150,19 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
}
},
Dbg => {
// now what
arguments!(condition);
assert_eq!(args.len(), 2);
let condition = load_symbol(scope, &args[0]);
let dbg_spec_var_symbol = args[1];
if env.mode.runs_expects() {
let region = unsafe { std::mem::transmute::<_, roc_region::all::Region>(args[0]) };
let shared_memory = crate::llvm::expect::SharedMemoryPointer::get(env);
// HACK(dbg-spec-var): the specialized type variable is passed along as a fake symbol
let specialized_var =
unsafe { LookupType::from_index(dbg_spec_var_symbol.ident_id().index() as _) };
crate::llvm::expect::clone_to_shared_memory(
env,
scope,
@ -1136,6 +1171,7 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
args[0],
region,
&[args[0]],
&[specialized_var],
);
crate::llvm::expect::notify_parent_dbg(env, &shared_memory);
@ -1687,7 +1723,7 @@ fn dec_binop_with_overflow<'a, 'ctx, 'env>(
}
env.builder
.build_load(return_alloca, "load_dec")
.new_build_load(return_type, return_alloca, "load_dec")
.into_struct_value()
}
@ -1933,16 +1969,15 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
intrinsic,
),
None => {
let return_type = zig_function_type.get_param_types()[0]
.into_pointer_type()
.get_element_type()
.into_struct_type()
.into();
let return_type = zig_to_int_checked_result_type(
env,
target_int_width.type_name(),
);
let zig_return_alloca = create_entry_block_alloca(
env,
parent,
return_type,
return_type.into(),
"num_to_int",
);
@ -1966,14 +2001,20 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
}
}
PtrWidth::Bytes8 => {
// call_bitcode_fn_fixing_for_convention(env, &[string], layout, intrinsic)
if target_int_width.stack_size() as usize > env.target_info.ptr_size() {
let bitcode_return_type =
zig_to_int_checked_result_type(env, target_int_width.type_name());
call_bitcode_fn_fixing_for_convention(
env,
&[arg.into()],
return_layout,
intrinsic,
)
call_bitcode_fn_fixing_for_convention(
env,
bitcode_return_type,
&[arg.into()],
return_layout,
intrinsic,
)
} else {
call_bitcode_fn(env, &[arg.into()], intrinsic)
}
}
};
@ -2076,15 +2117,19 @@ fn int_abs_with_overflow<'a, 'ctx, 'env>(
let xored_arg = bd.build_xor(
arg,
bd.build_load(shifted_alloca, shifted_name).into_int_value(),
bd.new_build_load(int_type, shifted_alloca, shifted_name)
.into_int_value(),
"xor_arg_shifted",
);
BasicValueEnum::IntValue(bd.build_int_sub(
xored_arg,
bd.build_load(shifted_alloca, shifted_name).into_int_value(),
"sub_xored_shifted",
))
BasicValueEnum::IntValue(
bd.build_int_sub(
xored_arg,
bd.new_build_load(int_type, shifted_alloca, shifted_name)
.into_int_value(),
"sub_xored_shifted",
),
)
}
fn build_float_unary_op<'a, 'ctx, 'env>(
@ -2124,13 +2169,6 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
"f64_to_f32",
),
(FloatWidth::F64, FloatWidth::F64) => arg.into(),
(FloatWidth::F128, FloatWidth::F128) => arg.into(),
(FloatWidth::F128, _) => {
unimplemented!("I cannot handle F128 with Num.toFrac yet")
}
(_, FloatWidth::F128) => {
unimplemented!("I cannot handle F128 with Num.toFrac yet")
}
}
}
NumCeiling => {

View File

@ -1,11 +1,12 @@
use crate::debug_info_init;
use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::build::BuilderExt;
use crate::llvm::build::{
add_func, cast_basic_basic, get_tag_id, tag_pointer_clear_tag_id, use_roc_value, Env,
WhenRecursive, FAST_CALL_CONV,
};
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
use crate::llvm::convert::{basic_type_from_layout, RocUnion};
use crate::llvm::convert::{basic_type_from_layout, zig_str_type, RocUnion};
use bumpalo::collections::Vec;
use inkwell::basic_block::BasicBlock;
use inkwell::module::Linkage;
@ -35,14 +36,11 @@ impl<'ctx> PointerToRefcount<'ctx> {
// must make sure it's a pointer to usize
let refcount_type = env.ptr_int();
let value = env
.builder
.build_bitcast(
ptr,
refcount_type.ptr_type(AddressSpace::Generic),
"to_refcount_ptr",
)
.into_pointer_value();
let value = env.builder.build_pointer_cast(
ptr,
refcount_type.ptr_type(AddressSpace::Generic),
"to_refcount_ptr",
);
Self { value }
}
@ -56,14 +54,18 @@ impl<'ctx> PointerToRefcount<'ctx> {
let refcount_type = env.ptr_int();
let refcount_ptr_type = refcount_type.ptr_type(AddressSpace::Generic);
let ptr_as_usize_ptr = builder
.build_bitcast(data_ptr, refcount_ptr_type, "as_usize_ptr")
.into_pointer_value();
let ptr_as_usize_ptr =
builder.build_pointer_cast(data_ptr, refcount_ptr_type, "as_usize_ptr");
// get a pointer to index -1
let index_intvalue = refcount_type.const_int(-1_i64 as u64, false);
let refcount_ptr = unsafe {
builder.build_in_bounds_gep(ptr_as_usize_ptr, &[index_intvalue], "get_rc_ptr")
builder.new_build_in_bounds_gep(
env.ptr_int(),
ptr_as_usize_ptr,
&[index_intvalue],
"get_rc_ptr",
)
};
Self {
@ -98,7 +100,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
fn get_refcount<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
env.builder
.build_load(self.value, "get_refcount")
.new_build_load(env.ptr_int(), self.value, "get_refcount")
.into_int_value()
}
@ -203,11 +205,13 @@ fn incref_pointer<'a, 'ctx, 'env>(
call_void_bitcode_fn(
env,
&[
env.builder.build_bitcast(
pointer,
env.ptr_int().ptr_type(AddressSpace::Generic),
"to_isize_ptr",
),
env.builder
.build_pointer_cast(
pointer,
env.ptr_int().ptr_type(AddressSpace::Generic),
"to_isize_ptr",
)
.into(),
amount.into(),
],
roc_builtins::bitcode::UTILS_INCREF,
@ -223,11 +227,13 @@ fn decref_pointer<'a, 'ctx, 'env>(
call_void_bitcode_fn(
env,
&[
env.builder.build_bitcast(
pointer,
env.ptr_int().ptr_type(AddressSpace::Generic),
"to_isize_ptr",
),
env.builder
.build_pointer_cast(
pointer,
env.ptr_int().ptr_type(AddressSpace::Generic),
"to_isize_ptr",
)
.into(),
alignment.into(),
],
roc_builtins::bitcode::UTILS_DECREF,
@ -244,11 +250,13 @@ pub fn decref_pointer_check_null<'a, 'ctx, 'env>(
call_void_bitcode_fn(
env,
&[
env.builder.build_bitcast(
pointer,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"to_i8_ptr",
),
env.builder
.build_pointer_cast(
pointer,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"to_i8_ptr",
)
.into(),
alignment.into(),
],
roc_builtins::bitcode::UTILS_DECREF_CHECK_NULL,
@ -466,10 +474,11 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
let bt = basic_type_from_layout(env, &layout);
// cast the i64 pointer to a pointer to block of memory
let field_cast = env
.builder
.build_bitcast(value, bt, "i64_to_opaque")
.into_pointer_value();
let field_cast = env.builder.build_pointer_cast(
value.into_pointer_value(),
bt.into_pointer_type(),
"i64_to_opaque",
);
call_help(env, function, call_mode, field_cast.into());
}
@ -538,7 +547,7 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
NonRecursive(tags) => {
let function =
modify_refcount_union(env, layout_ids, mode, when_recursive, tags);
modify_refcount_nonrecursive(env, layout_ids, mode, when_recursive, tags);
Some(function)
}
@ -795,8 +804,9 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
let arg_val = if Layout::Builtin(Builtin::Str)
.is_passed_by_reference(env.layout_interner, env.target_info)
{
let str_type = zig_str_type(env);
env.builder
.build_load(arg_val.into_pointer_value(), "load_str_to_stack")
.new_build_load(str_type, arg_val.into_pointer_value(), "load_str_to_stack")
} else {
// it's already a struct, just do nothing
debug_assert!(arg_val.is_struct_value());
@ -1207,14 +1217,11 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
basic_type_from_layout(env, &Layout::struct_no_name_order(field_layouts));
// cast the opaque pointer to a pointer of the correct shape
let struct_ptr = env
.builder
.build_bitcast(
value_ptr,
wrapper_type.ptr_type(AddressSpace::Generic),
"opaque_to_correct_recursive_decrement",
)
.into_pointer_value();
let struct_ptr = env.builder.build_pointer_cast(
value_ptr,
wrapper_type.ptr_type(AddressSpace::Generic),
"opaque_to_correct_recursive_decrement",
);
// defer actually performing the refcount modifications until after the current cell has
// been decremented, see below
@ -1226,12 +1233,19 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
// this field has type `*i64`, but is really a pointer to the data we want
let elem_pointer = env
.builder
.build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer")
.new_build_struct_gep(
wrapper_type.into_struct_type(),
struct_ptr,
i as u32,
"gep_recursive_pointer",
)
.unwrap();
let ptr_as_i64_ptr = env
.builder
.build_load(elem_pointer, "load_recursive_pointer");
let ptr_as_i64_ptr = env.builder.new_build_load(
env.context.i64_type().ptr_type(AddressSpace::Generic),
elem_pointer,
"load_recursive_pointer",
);
debug_assert!(ptr_as_i64_ptr.is_pointer_value());
@ -1243,7 +1257,12 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
} else if field_layout.contains_refcounted(env.layout_interner) {
let elem_pointer = env
.builder
.build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer")
.new_build_struct_gep(
wrapper_type.into_struct_type(),
struct_ptr,
i as u32,
"gep_recursive_pointer",
)
.unwrap();
let field =
@ -1499,7 +1518,7 @@ fn function_name_from_mode<'a>(
}
}
fn modify_refcount_union<'a, 'ctx, 'env>(
fn modify_refcount_nonrecursive<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
@ -1527,7 +1546,7 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
let basic_type = argument_type_from_union_layout(env, &union_layout);
let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_union_help(
modify_refcount_nonrecursive_help(
env,
layout_ids,
mode,
@ -1547,7 +1566,7 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
function
}
fn modify_refcount_union_help<'a, 'ctx, 'env>(
fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
@ -1577,15 +1596,28 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
let before_block = env.builder.get_insert_block().expect("to be in a function");
let union_layout = UnionLayout::NonRecursive(tags);
let layout = Layout::Union(union_layout);
let union_struct_type = basic_type_from_layout(env, &layout).into_struct_type();
// read the tag_id
let tag_id_ptr = env
.builder
.build_struct_gep(arg_ptr, RocUnion::TAG_ID_INDEX, "tag_id_ptr")
.new_build_struct_gep(
union_struct_type,
arg_ptr,
RocUnion::TAG_ID_INDEX,
"tag_id_ptr",
)
.unwrap();
let tag_id = env
.builder
.build_load(tag_id_ptr, "load_tag_id")
.new_build_load(
basic_type_from_layout(env, &union_layout.tag_id_layout()),
tag_id_ptr,
"load_tag_id",
)
.into_int_value();
let tag_id_u8 =
@ -1611,18 +1643,24 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
let wrapper_type =
let data_struct_type =
basic_type_from_layout(env, &Layout::struct_no_name_order(field_layouts));
debug_assert!(wrapper_type.is_struct_type());
debug_assert!(data_struct_type.is_struct_type());
let data_struct_type = data_struct_type.into_struct_type();
let opaque_tag_data_ptr = env
.builder
.build_struct_gep(arg_ptr, RocUnion::TAG_DATA_INDEX, "field_ptr")
.new_build_struct_gep(
union_struct_type,
arg_ptr,
RocUnion::TAG_DATA_INDEX,
"field_ptr",
)
.unwrap();
let cast_tag_data_pointer = env.builder.build_pointer_cast(
opaque_tag_data_ptr,
wrapper_type.ptr_type(AddressSpace::Generic),
data_struct_type.ptr_type(AddressSpace::Generic),
"cast_to_concrete_tag",
);
@ -1638,11 +1676,20 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
// This field is a pointer to the recursive pointer.
let field_ptr = env
.builder
.build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field")
.new_build_struct_gep(
data_struct_type,
cast_tag_data_pointer,
i as u32,
"modify_tag_field",
)
.unwrap();
// This is the actual pointer to the recursive data.
let field_value = env.builder.build_load(field_ptr, "load_recursive_pointer");
let field_value = env.builder.new_build_load(
env.context.i64_type().ptr_type(AddressSpace::Generic),
field_ptr,
"load_recursive_pointer",
);
debug_assert!(field_value.is_pointer_value());
@ -1663,14 +1710,23 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
} else if field_layout.contains_refcounted(env.layout_interner) {
let field_ptr = env
.builder
.build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field")
.new_build_struct_gep(
data_struct_type,
cast_tag_data_pointer,
i as u32,
"modify_tag_field",
)
.unwrap();
let field_value =
if field_layout.is_passed_by_reference(env.layout_interner, env.target_info) {
field_ptr.into()
} else {
env.builder.build_load(field_ptr, "field_value")
env.builder.new_build_load(
basic_type_from_layout(env, field_layout),
field_ptr,
"field_value",
)
};
modify_refcount_layout_help(

View File

@ -21,7 +21,6 @@ pub enum StackMemoryFormat {
/// Record, Str, List, etc.
DataStructure,
Int128,
Float128,
Decimal,
}
@ -71,11 +70,6 @@ impl WasmLayout {
match float_width {
F32 => Self::Primitive(ValueType::F32, size),
F64 => Self::Primitive(ValueType::F64, size),
F128 => Self::StackMemory {
size,
alignment_bytes,
format: StackMemoryFormat::Float128,
},
}
}
@ -156,7 +150,7 @@ impl CallConv {
use ValueType::*;
match format {
Int128 | Float128 | Decimal => &[I64, I64],
Int128 | Decimal => &[I64, I64],
DataStructure => {
if size == 0 {
@ -191,7 +185,7 @@ impl CallConv {
use StackMemoryFormat::*;
match format {
Int128 | Float128 | Decimal => WriteToPointerArg,
Int128 | Decimal => WriteToPointerArg,
DataStructure => {
if size == 0 {

View File

@ -251,7 +251,7 @@ pub const DEBUG_SETTINGS: WasmDebugSettings = WasmDebugSettings {
let_stmt_ir: false && cfg!(debug_assertions),
instructions: false && cfg!(debug_assertions),
storage_map: false && cfg!(debug_assertions),
keep_test_binary: false && cfg!(debug_assertions),
keep_test_binary: false && cfg!(debug_assertions), // see also ROC_WRITE_FINAL_WASM
};
#[cfg(test)]

View File

@ -30,7 +30,6 @@ enum CodeGenNumType {
F32, // Supported in Wasm instruction set
F64, // Supported in Wasm instruction set
I128, // Bytes in memory, needs Zig builtins
F128, // Bytes in memory, needs Zig builtins
Decimal, // Bytes in memory, needs Zig builtins
}
@ -66,7 +65,6 @@ impl From<Layout<'_>> for CodeGenNumType {
Builtin::Float(float_width) => match float_width {
FloatWidth::F32 => F32,
FloatWidth::F64 => F64,
FloatWidth::F128 => F128,
},
Builtin::Decimal => Decimal,
_ => not_num_error(),
@ -91,7 +89,6 @@ impl From<StackMemoryFormat> for CodeGenNumType {
fn from(format: StackMemoryFormat) -> CodeGenNumType {
match format {
StackMemoryFormat::Int128 => CodeGenNumType::I128,
StackMemoryFormat::Float128 => CodeGenNumType::F128,
StackMemoryFormat::Decimal => CodeGenNumType::Decimal,
StackMemoryFormat::DataStructure => {
internal_error!("Tried to perform a Num low-level operation on a data structure")
@ -804,7 +801,6 @@ impl<'a> LowLevelCall<'a> {
self.load_args(backend);
backend.code_builder.f64_add()
}
FloatWidth::F128 => todo!("Num.add for f128"),
},
Layout::Builtin(Builtin::Decimal) => {
self.load_args_and_call_zig(backend, bitcode::DEC_ADD_OR_PANIC)
@ -841,7 +837,6 @@ impl<'a> LowLevelCall<'a> {
self.load_args(backend);
backend.code_builder.f64_add()
}
FloatWidth::F128 => todo!("Num.add for f128"),
},
Layout::Builtin(Builtin::Decimal) => {
// TODO: don't panic
@ -897,7 +892,6 @@ impl<'a> LowLevelCall<'a> {
self.load_args(backend);
backend.code_builder.f64_sub()
}
FloatWidth::F128 => todo!("Num.sub for f128"),
},
Layout::Builtin(Builtin::Decimal) => {
self.load_args_and_call_zig(backend, bitcode::DEC_SUB_OR_PANIC)
@ -934,7 +928,6 @@ impl<'a> LowLevelCall<'a> {
self.load_args(backend);
backend.code_builder.f64_sub()
}
FloatWidth::F128 => todo!("Num.sub for f128"),
},
Layout::Builtin(Builtin::Decimal) => {
// TODO: don't panic
@ -988,7 +981,6 @@ impl<'a> LowLevelCall<'a> {
self.load_args(backend);
backend.code_builder.f64_mul()
}
FloatWidth::F128 => todo!("Num.mul for f128"),
},
Layout::Builtin(Builtin::Decimal) => {
self.load_args_and_call_zig(backend, bitcode::DEC_MUL_OR_PANIC)
@ -1024,7 +1016,6 @@ impl<'a> LowLevelCall<'a> {
self.load_args(backend);
backend.code_builder.f64_mul()
}
FloatWidth::F128 => todo!("Num.mul for f128"),
},
Layout::Builtin(Builtin::Decimal) => {
// TODO: don't panic
@ -1466,9 +1457,6 @@ impl<'a> LowLevelCall<'a> {
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
backend.code_builder.f64_sqrt()
}
Layout::Builtin(Builtin::Float(FloatWidth::F128)) => {
todo!("sqrt for f128")
}
_ => panic_ret_type(),
}
}
@ -1625,7 +1613,10 @@ impl<'a> LowLevelCall<'a> {
.load_symbols(&mut backend.code_builder, &[num, bits]);
match CodeGenNumType::from(self.ret_layout) {
I32 => backend.code_builder.i32_shl(),
I64 => backend.code_builder.i64_shl(),
I64 => {
backend.code_builder.i64_extend_u_i32();
backend.code_builder.i64_shl();
}
I128 => todo!("{:?} for I128", self.lowlevel),
_ => panic_ret_type(),
}
@ -1672,6 +1663,7 @@ impl<'a> LowLevelCall<'a> {
backend
.storage
.load_symbols(&mut backend.code_builder, &[num, bits]);
backend.code_builder.i64_extend_u_i32();
backend.code_builder.i64_shr_s();
}
I128 => todo!("{:?} for I128", self.lowlevel),
@ -1714,6 +1706,7 @@ impl<'a> LowLevelCall<'a> {
backend
.storage
.load_symbols(&mut backend.code_builder, &[num, bits]);
backend.code_builder.i64_extend_u_i32();
backend.code_builder.i64_shr_u();
}
I128 => todo!("{:?} for I128", self.lowlevel),
@ -2004,8 +1997,6 @@ impl<'a> LowLevelCall<'a> {
StackMemoryFormat::Int128 => Self::eq_num128_bytes(backend, locations),
StackMemoryFormat::Float128 => todo!("equality for f128"),
StackMemoryFormat::DataStructure => {
internal_error!("Data structure equality is handled elsewhere")
}
@ -2052,7 +2043,6 @@ impl<'a> LowLevelCall<'a> {
FloatWidth::F64 => {
self.load_args_and_call_zig(backend, &bitcode::STR_FROM_FLOAT[width]);
}
FloatWidth::F128 => todo!("F128 to Str"),
},
Layout::Builtin(Builtin::Decimal) => {
self.load_args_and_call_zig(backend, bitcode::DEC_TO_STR)
@ -2090,27 +2080,13 @@ fn num_is_finite(backend: &mut WasmBackend<'_>, argument: Symbol) {
}
}
}
StackMemory {
format, location, ..
} => {
let (local_id, offset) = location.local_and_offset(backend.storage.stack_frame_pointer);
StackMemory { format, .. } => {
match format {
// Integers and fixed-point numbers are always finite. Just return True.
StackMemoryFormat::Int128 | StackMemoryFormat::Decimal => {
backend.code_builder.i32_const(1)
}
// f128 is not supported anywhere else but it's easy to support it here, so why not...
StackMemoryFormat::Float128 => {
backend.code_builder.get_local(local_id);
backend.code_builder.i64_load(Align::Bytes4, offset + 8);
backend.code_builder.i64_const(0x7fff_0000_0000_0000);
backend.code_builder.i64_and();
backend.code_builder.i64_const(0x7fff_0000_0000_0000);
backend.code_builder.i64_ne();
}
StackMemoryFormat::DataStructure => {
internal_error!("Tried to perform NumIsFinite on a data structure")
}

View File

@ -252,7 +252,7 @@ impl<'a> Storage<'a> {
.extend_from_slice(CallConv::C.stack_memory_arg_types(size, format));
let location = match format {
Int128 | Float128 | Decimal => {
Int128 | Decimal => {
// passed as two i64's but stored in the stack frame
wide_number_args.push(local_index);
let loc =

File diff suppressed because it is too large Load Diff

View File

@ -654,7 +654,8 @@ fn platform_does_not_exist() {
match multiple_modules("platform_does_not_exist", modules) {
Err(report) => {
assert!(report.contains("FILE NOT FOUND"), "report=({})", report);
// TODO restore this assert once it can pass.
// assert!(report.contains("FILE NOT FOUND"), "report=({})", report);
assert!(
report.contains("zzz-does-not-exist/main.roc"),
"report=({})",

View File

@ -594,6 +594,13 @@ impl IdentId {
pub const fn index(self) -> usize {
self.0 as usize
}
/// # Safety
///
/// The index is not guaranteed to know to exist.
pub unsafe fn from_index(index: u32) -> Self {
Self(index)
}
}
/// Stores a mapping between Ident and IdentId.

View File

@ -948,7 +948,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListIsUnique => arena.alloc_slice_copy(&[borrowed]),
Dbg => arena.alloc_slice_copy(&[borrowed]),
Dbg => arena.alloc_slice_copy(&[borrowed, /* dbg-spec-var */ irrelevant]),
BoxExpr | UnboxExpr => {
unreachable!("These lowlevel operations are turned into mono Expr's")

View File

@ -309,12 +309,14 @@ impl<'a, 'r> Ctx<'a, 'r> {
condition,
region: _,
lookups,
variables: _,
remainder,
}
| &Stmt::ExpectFx {
condition,
region: _,
lookups,
variables: _,
remainder,
} => {
self.check_sym_layout(

View File

@ -63,6 +63,8 @@ where
.pretty(80)
.to_string();
eprintln!("Full source: {}", src);
let interpolated_docs = stack(
f,
docs.into_iter()

View File

@ -555,7 +555,13 @@ impl<'a, 'i> Context<'a, 'i> {
match &call_type {
LowLevel { op, .. } => {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
let b = match op {
roc_module::low_level::LowLevel::Dbg => {
// NB(dbg-spec-var) second var is the Variable
self.add_dec_after_lowlevel(&arguments[..1], ps, b, b_live_vars)
}
_ => self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars),
};
let v = Expr::Call(crate::ir::Call {
call_type,
@ -1199,6 +1205,7 @@ impl<'a, 'i> Context<'a, 'i> {
condition,
region,
lookups,
variables,
} => {
let (b, mut b_live_vars) = self.visit_stmt(codegen, remainder);
@ -1206,6 +1213,7 @@ impl<'a, 'i> Context<'a, 'i> {
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
});
@ -1221,6 +1229,7 @@ impl<'a, 'i> Context<'a, 'i> {
condition,
region,
lookups,
variables,
} => {
let (b, mut b_live_vars) = self.visit_stmt(codegen, remainder);
@ -1228,6 +1237,7 @@ impl<'a, 'i> Context<'a, 'i> {
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
});

View File

@ -27,14 +27,14 @@ use roc_late_solve::storage::{ExternalModuleStorage, ExternalModuleStorageSnapsh
use roc_late_solve::{resolve_ability_specialization, AbilitiesView, Resolved, UnificationFailed};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_module::symbol::{IdentId, IdentIds, ModuleId, Symbol};
use roc_problem::can::{RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region};
use roc_std::RocDec;
use roc_target::TargetInfo;
use roc_types::subs::{
instantiate_rigids, Content, ExhaustiveMark, FlatType, RedundantMark, StorageSubs, Subs,
Variable, VariableSubsSlice,
instantiate_rigids, storage_copy_var_to, Content, ExhaustiveMark, FlatType, RedundantMark,
StorageSubs, Subs, Variable, VariableSubsSlice,
};
use std::collections::HashMap;
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder};
@ -1465,6 +1465,9 @@ impl<'a> Specializations<'a> {
pub struct Env<'a, 'i> {
pub arena: &'a Bump,
pub subs: &'i mut Subs,
/// [Subs] to write specialized variables of lookups in expects.
/// [None] if this module doesn't produce any expects.
pub expectation_subs: Option<&'i mut Subs>,
pub home: ModuleId,
pub ident_ids: &'i mut IdentIds,
pub target_info: TargetInfo,
@ -1601,6 +1604,9 @@ pub fn cond<'a>(
pub type Stores<'a> = &'a [(Symbol, Layout<'a>, Expr<'a>)];
/// The specialized type of a lookup. Represented as a type-variable.
pub type LookupType = Variable;
#[derive(Clone, Debug, PartialEq)]
pub enum Stmt<'a> {
Let(Symbol, Expr<'a>, Layout<'a>, &'a Stmt<'a>),
@ -1622,6 +1628,7 @@ pub enum Stmt<'a> {
condition: Symbol,
region: Region,
lookups: &'a [Symbol],
variables: &'a [LookupType],
/// what happens after the expect
remainder: &'a Stmt<'a>,
},
@ -1629,6 +1636,7 @@ pub enum Stmt<'a> {
condition: Symbol,
region: Region,
lookups: &'a [Symbol],
variables: &'a [LookupType],
/// what happens after the expect
remainder: &'a Stmt<'a>,
},
@ -6568,12 +6576,14 @@ pub fn from_can<'a>(
let cond_symbol = env.unique_symbol();
let mut lookups = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut lookup_variables = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut specialized_variables = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
for ExpectLookup {
symbol,
var,
ability_info,
} in lookups_in_cond
} in lookups_in_cond.iter().copied()
{
let symbol = match ability_info {
Some(specialization_id) => late_resolve_ability_specialization(
@ -6584,16 +6594,28 @@ pub fn from_can<'a>(
),
None => symbol,
};
let expectation_subs = env
.expectation_subs
.as_deref_mut()
.expect("if expects are compiled, their subs should be available");
let spec_var = expectation_subs.fresh_unnamed_flex_var();
if !env.subs.is_function(var) {
// Exclude functions from lookups
lookups.push(symbol);
lookup_variables.push(var);
specialized_variables.push(spec_var);
}
}
let specialized_variables = specialized_variables.into_bump_slice();
let mut stmt = Stmt::Expect {
condition: cond_symbol,
region: loc_condition.region,
lookups: lookups.into_bump_slice(),
variables: specialized_variables,
remainder: env.arena.alloc(rest),
};
@ -6607,6 +6629,10 @@ pub fn from_can<'a>(
env.arena.alloc(stmt),
);
// Now that the condition has been specialized, export the specialized types of our
// lookups into the expectation subs.
store_specialized_expectation_lookups(env, lookup_variables, specialized_variables);
stmt
}
@ -6619,12 +6645,14 @@ pub fn from_can<'a>(
let cond_symbol = env.unique_symbol();
let mut lookups = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut lookup_variables = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut specialized_variables = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
for ExpectLookup {
symbol,
var,
ability_info,
} in lookups_in_cond
} in lookups_in_cond.iter().copied()
{
let symbol = match ability_info {
Some(specialization_id) => late_resolve_ability_specialization(
@ -6635,16 +6663,28 @@ pub fn from_can<'a>(
),
None => symbol,
};
let expectation_subs = env
.expectation_subs
.as_deref_mut()
.expect("if expects are compiled, their subs should be available");
let spec_var = expectation_subs.fresh_unnamed_flex_var();
if !env.subs.is_function(var) {
// Exclude functions from lookups
lookups.push(symbol);
lookup_variables.push(var);
specialized_variables.push(spec_var);
}
}
let specialized_variables = specialized_variables.into_bump_slice();
let mut stmt = Stmt::ExpectFx {
condition: cond_symbol,
region: loc_condition.region,
lookups: lookups.into_bump_slice(),
variables: specialized_variables,
remainder: env.arena.alloc(rest),
};
@ -6658,6 +6698,8 @@ pub fn from_can<'a>(
env.arena.alloc(stmt),
);
store_specialized_expectation_lookups(env, lookup_variables, specialized_variables);
stmt
}
@ -6669,12 +6711,23 @@ pub fn from_can<'a>(
} => {
let rest = from_can(env, variable, loc_continuation.value, procs, layout_cache);
let spec_var = env
.expectation_subs
.as_mut()
.unwrap()
.fresh_unnamed_flex_var();
// HACK(dbg-spec-var): pass the specialized type variable along injected into a fake symbol
let dbg_spec_var_symbol = Symbol::new(ModuleId::ATTR, unsafe {
IdentId::from_index(spec_var.index())
});
// TODO: need to store the specialized variable of this dbg in the expectation_subs
let call = crate::ir::Call {
call_type: CallType::LowLevel {
op: LowLevel::Dbg,
update_mode: env.next_update_mode_id(),
},
arguments: env.arena.alloc([dbg_symbol]),
arguments: env.arena.alloc([dbg_symbol, dbg_spec_var_symbol]),
};
let dbg_layout = layout_cache
@ -6702,6 +6755,10 @@ pub fn from_can<'a>(
);
}
// Now that the dbg value has been specialized, export its specialized type into the
// expectations subs.
store_specialized_expectation_lookups(env, [variable], &[spec_var]);
stmt
}
@ -6740,6 +6797,21 @@ pub fn from_can<'a>(
}
}
fn store_specialized_expectation_lookups(
env: &mut Env,
lookup_variables: impl IntoIterator<Item = Variable>,
specialized_variables: &[Variable],
) {
let subs = &env.subs;
let expectation_subs = env.expectation_subs.as_deref_mut().unwrap();
for (lookup_var, stored_var) in lookup_variables.into_iter().zip(specialized_variables) {
let stored_specialized_var =
storage_copy_var_to(&mut Default::default(), subs, expectation_subs, lookup_var);
let stored_specialized_desc = expectation_subs.get(stored_specialized_var);
expectation_subs.union(*stored_var, stored_specialized_var, stored_specialized_desc);
}
}
fn to_opt_branches<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
@ -7070,6 +7142,7 @@ fn substitute_in_stmt_help<'a>(
condition,
region,
lookups,
variables,
remainder,
} => {
let new_remainder =
@ -7084,6 +7157,7 @@ fn substitute_in_stmt_help<'a>(
condition: substitute(subs, *condition).unwrap_or(*condition),
region: *region,
lookups: new_lookups.into_bump_slice(),
variables,
remainder: new_remainder,
};
@ -7094,6 +7168,7 @@ fn substitute_in_stmt_help<'a>(
condition,
region,
lookups,
variables,
remainder,
} => {
let new_remainder =
@ -7108,6 +7183,7 @@ fn substitute_in_stmt_help<'a>(
condition: substitute(subs, *condition).unwrap_or(*condition),
region: *region,
lookups: new_lookups.into_bump_slice(),
variables,
remainder: new_remainder,
};
@ -8218,49 +8294,71 @@ fn specialize_symbol<'a>(
Err(e) => return_on_layout_error_help!(env, e, "specialize_symbol"),
};
if procs.is_imported_module_thunk(original) {
let layout = match raw {
RawFunctionLayout::ZeroArgumentThunk(layout) => layout,
RawFunctionLayout::Function(_, lambda_set, _) => {
Layout::LambdaSet(lambda_set)
}
};
match raw {
RawFunctionLayout::Function(_, lambda_set, _)
if !procs.is_imported_module_thunk(original) =>
{
let lambda_name =
find_lambda_name(env, layout_cache, lambda_set, original, &[]);
let raw = RawFunctionLayout::ZeroArgumentThunk(layout);
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
raw,
CapturesNiche::no_niche(),
);
debug_assert!(
lambda_name.no_captures(),
"imported functions are top-level and should never capture"
);
procs.insert_passed_by_name(
env,
arg_var,
LambdaName::no_niche(original),
top_level,
layout_cache,
);
let function_ptr_layout = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
raw,
lambda_name.captures_niche(),
);
procs.insert_passed_by_name(
env,
arg_var,
lambda_name,
function_ptr_layout,
layout_cache,
);
force_thunk(env, original, layout, assign_to, env.arena.alloc(result))
} else {
// Imported symbol, so it must have no captures niche (since
// top-levels can't capture)
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
raw,
CapturesNiche::no_niche(),
);
procs.insert_passed_by_name(
env,
arg_var,
LambdaName::no_niche(original),
top_level,
layout_cache,
);
construct_closure_data(
env,
procs,
layout_cache,
lambda_set,
lambda_name,
&[],
assign_to,
env.arena.alloc(result),
)
}
_ => {
// This is an imported ZAT that returns either a value, or the closure
// data for a lambda set.
let layout = match raw {
RawFunctionLayout::ZeroArgumentThunk(layout) => layout,
RawFunctionLayout::Function(_, lambda_set, _) => {
Layout::LambdaSet(lambda_set)
}
};
let_empty_struct(assign_to, env.arena.alloc(result))
let raw = RawFunctionLayout::ZeroArgumentThunk(layout);
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
raw,
CapturesNiche::no_niche(),
);
procs.insert_passed_by_name(
env,
arg_var,
LambdaName::no_niche(original),
top_level,
layout_cache,
);
force_thunk(env, original, layout, assign_to, env.arena.alloc(result))
}
}
}

View File

@ -2890,7 +2890,6 @@ impl<'a> Builtin<'a> {
use FloatWidth::*;
match float_width {
F128 => alloc.text("Float128"),
F64 => alloc.text("Float64"),
F32 => alloc.text("Float32"),
}

View File

@ -195,6 +195,7 @@ fn function_s<'a, 'i>(
condition,
region,
lookups,
variables,
remainder,
} => {
let continuation: &Stmt = remainder;
@ -207,6 +208,7 @@ fn function_s<'a, 'i>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: new_continuation,
};
@ -218,6 +220,7 @@ fn function_s<'a, 'i>(
condition,
region,
lookups,
variables,
remainder,
} => {
let continuation: &Stmt = remainder;
@ -230,6 +233,7 @@ fn function_s<'a, 'i>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: new_continuation,
};
@ -438,6 +442,7 @@ fn function_d_main<'a, 'i>(
condition,
region,
lookups,
variables,
remainder,
} => {
let (b, found) = function_d_main(env, x, c, remainder);
@ -447,6 +452,7 @@ fn function_d_main<'a, 'i>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
};
@ -458,6 +464,7 @@ fn function_d_main<'a, 'i>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
};
@ -468,6 +475,7 @@ fn function_d_main<'a, 'i>(
condition,
region,
lookups,
variables,
remainder,
} => {
let (b, found) = function_d_main(env, x, c, remainder);
@ -477,6 +485,7 @@ fn function_d_main<'a, 'i>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
};
@ -488,6 +497,7 @@ fn function_d_main<'a, 'i>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
};
@ -650,6 +660,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
condition,
region,
lookups,
variables,
remainder,
} => {
let b = function_r(env, remainder);
@ -658,6 +669,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
};
@ -668,6 +680,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
condition,
region,
lookups,
variables,
remainder,
} => {
let b = function_r(env, remainder);
@ -676,6 +689,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
condition: *condition,
region: *region,
lookups,
variables,
remainder: b,
};

View File

@ -253,6 +253,7 @@ fn insert_jumps<'a>(
condition,
region,
lookups,
variables,
remainder,
} => match insert_jumps(
arena,
@ -266,6 +267,7 @@ fn insert_jumps<'a>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: cont,
})),
None => None,
@ -275,6 +277,7 @@ fn insert_jumps<'a>(
condition,
region,
lookups,
variables,
remainder,
} => match insert_jumps(
arena,
@ -288,6 +291,7 @@ fn insert_jumps<'a>(
condition: *condition,
region: *region,
lookups,
variables,
remainder: cont,
})),
None => None,

View File

@ -9,6 +9,7 @@
"app"
"platform"
"package"
"provides"
"requires"
"exposes"

View File

@ -1,6 +1,6 @@
use std::fmt::Debug;
use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PlatformHeader};
use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PackageHeader, PlatformHeader};
use crate::ident::Ident;
use bumpalo::collections::{String, Vec};
use bumpalo::Bump;
@ -90,6 +90,7 @@ pub struct Module<'a> {
pub enum Header<'a> {
Interface(InterfaceHeader<'a>),
App(AppHeader<'a>),
Package(PackageHeader<'a>),
Platform(PlatformHeader<'a>),
Hosted(HostedHeader<'a>),
}

View File

@ -2,29 +2,50 @@ use crate::ast::{Collection, CommentOrNewline, Spaced, Spaces, StrLiteral, TypeA
use crate::blankspace::space0_e;
use crate::ident::{lowercase_ident, UppercaseIdent};
use crate::parser::{optional, then};
use crate::parser::{specialize, word1, EPackageEntry, EPackagePath, Parser};
use crate::parser::{specialize, word1, EPackageEntry, EPackageName, Parser};
use crate::string_literal;
use bumpalo::collections::Vec;
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::Loc;
use std::fmt::Debug;
impl<'a> HeaderType<'a> {
pub fn exposed_or_provided_values(&'a self) -> &'a [Loc<ExposedName<'a>>] {
match self {
HeaderType::App {
provides: exposes, ..
}
| HeaderType::Hosted { exposes, .. }
| HeaderType::Builtin { exposes, .. }
| HeaderType::Interface { exposes, .. } => exposes,
HeaderType::Platform { .. } | HeaderType::Package { .. } => &[],
}
}
}
#[derive(Debug)]
pub enum HeaderType<'a> {
App {
output_name: StrLiteral<'a>,
provides: &'a [Loc<ExposedName<'a>>],
to_platform: To<'a>,
},
Hosted {
name: ModuleName<'a>,
exposes: &'a [Loc<ExposedName<'a>>],
generates: UppercaseIdent<'a>,
generates_with: &'a [Loc<ExposedName<'a>>],
},
/// Only created during canonicalization, never actually parsed from source
Builtin {
name: ModuleName<'a>,
exposes: &'a [Loc<ExposedName<'a>>],
generates_with: &'a [Symbol],
},
Package {
/// usually something other than `pf`
config_shorthand: &'a str,
exposes: &'a [Loc<ModuleName<'a>>],
},
Platform {
opt_app_module_id: Option<ModuleId>,
/// the name and type scheme of the main function (required by the platform)
@ -32,12 +53,14 @@ pub enum HeaderType<'a> {
provides: &'a [(Loc<ExposedName<'a>>, Loc<TypedIdent<'a>>)],
requires: &'a [Loc<TypedIdent<'a>>],
requires_types: &'a [Loc<UppercaseIdent<'a>>],
exposes: &'a [Loc<ModuleName<'a>>],
/// usually `pf`
config_shorthand: &'a str,
},
Interface {
name: ModuleName<'a>,
exposes: &'a [Loc<ExposedName<'a>>],
},
}
@ -59,9 +82,9 @@ pub enum VersionComparison {
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct PackagePath<'a>(&'a str);
pub struct PackageName<'a>(&'a str);
impl<'a> PackagePath<'a> {
impl<'a> PackageName<'a> {
pub fn to_str(self) -> &'a str {
self.0
}
@ -71,13 +94,13 @@ impl<'a> PackagePath<'a> {
}
}
impl<'a> From<PackagePath<'a>> for &'a str {
fn from(name: PackagePath<'a>) -> &'a str {
impl<'a> From<PackageName<'a>> for &'a str {
fn from(name: PackageName<'a>) -> &'a str {
name.0
}
}
impl<'a> From<&'a str> for PackagePath<'a> {
impl<'a> From<&'a str> for PackageName<'a> {
fn from(string: &'a str) -> Self {
Self(string)
}
@ -181,7 +204,7 @@ pub struct HostedHeader<'a> {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum To<'a> {
ExistingPackage(&'a str),
NewPackage(PackagePath<'a>),
NewPackage(PackageName<'a>),
}
#[derive(Clone, Debug, PartialEq)]
@ -209,16 +232,11 @@ pub struct ProvidesTo<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct PackageHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub name: Loc<PackagePath<'a>>,
pub name: Loc<PackageName<'a>>,
pub exposes_keyword: Spaces<'a, ExposesKeyword>,
pub exposes: Vec<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub packages_keyword: Spaces<'a, PackagesKeyword>,
pub packages: Vec<'a, (Loc<&'a str>, Loc<PackagePath<'a>>)>,
pub imports_keyword: Spaces<'a, ImportsKeyword>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
pub exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>>,
pub packages:
KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>,
}
#[derive(Clone, Debug, PartialEq)]
@ -230,7 +248,7 @@ pub struct PlatformRequires<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct PlatformHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub name: Loc<PackagePath<'a>>,
pub name: Loc<PackageName<'a>>,
pub requires: KeywordItem<'a, RequiresKeyword, PlatformRequires<'a>>,
pub exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>>,
@ -271,7 +289,7 @@ pub struct TypedIdent<'a> {
pub struct PackageEntry<'a> {
pub shorthand: &'a str,
pub spaces_after_shorthand: &'a [CommentOrNewline<'a>],
pub package_path: Loc<PackagePath<'a>>,
pub package_name: Loc<PackageName<'a>>,
}
pub fn package_entry<'a>() -> impl Parser<'a, Spaced<'a, PackageEntry<'a>>, EPackageEntry<'a>> {
@ -288,19 +306,19 @@ pub fn package_entry<'a>() -> impl Parser<'a, Spaced<'a, PackageEntry<'a>>, EPac
),
space0_e(EPackageEntry::IndentPackage)
)),
loc!(specialize(EPackageEntry::BadPackage, package_path()))
loc!(specialize(EPackageEntry::BadPackage, package_name()))
),
move |(opt_shorthand, package_or_path)| {
let entry = match opt_shorthand {
Some((shorthand, spaces_after_shorthand)) => PackageEntry {
shorthand,
spaces_after_shorthand,
package_path: package_or_path,
package_name: package_or_path,
},
None => PackageEntry {
shorthand: "",
spaces_after_shorthand: &[],
package_path: package_or_path,
package_name: package_or_path,
},
};
@ -309,13 +327,13 @@ pub fn package_entry<'a>() -> impl Parser<'a, Spaced<'a, PackageEntry<'a>>, EPac
)
}
pub fn package_path<'a>() -> impl Parser<'a, PackagePath<'a>, EPackagePath<'a>> {
pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, EPackageName<'a>> {
then(
loc!(specialize(EPackagePath::BadPath, string_literal::parse())),
loc!(specialize(EPackageName::BadPath, string_literal::parse())),
move |_arena, state, progress, text| match text.value {
StrLiteral::PlainLine(text) => Ok((progress, PackagePath(text), state)),
StrLiteral::Line(_) => Err((progress, EPackagePath::Escapes(text.region.start()))),
StrLiteral::Block(_) => Err((progress, EPackagePath::Multiline(text.region.start()))),
StrLiteral::PlainLine(text) => Ok((progress, PackageName(text), state)),
StrLiteral::Line(_) => Err((progress, EPackageName::Escapes(text.region.start()))),
StrLiteral::Block(_) => Err((progress, EPackageName::Multiline(text.region.start()))),
},
)
}

View File

@ -1,10 +1,10 @@
use crate::ast::{Collection, Defs, Header, Module, Spaced, Spaces};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::header::{
package_entry, package_path, AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword,
package_entry, package_name, AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword,
HostedHeader, ImportsEntry, ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName,
PackageEntry, PackagesKeyword, PlatformHeader, PlatformRequires, ProvidesKeyword, ProvidesTo,
RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword,
PackageEntry, PackageHeader, PackagesKeyword, PlatformHeader, PlatformRequires,
ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword,
};
use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase, UppercaseIdent};
use crate::parser::Progress::{self, *};
@ -67,6 +67,13 @@ fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
),
Header::App
),
map!(
skip_first!(
keyword_e("package", EHeader::Start),
increment_min_indent(package_header())
),
Header::Package
),
map!(
skip_first!(
keyword_e("platform", EHeader::Start),
@ -183,11 +190,22 @@ fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
.trace("app_header")
}
#[inline(always)]
fn package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> {
record!(PackageHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc!(specialize(EHeader::PackageName, package_name())),
exposes: specialize(EHeader::Exposes, exposes_modules()),
packages: specialize(EHeader::Packages, packages()),
})
.trace("package_header")
}
#[inline(always)]
fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
record!(PlatformHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc!(specialize(EHeader::PlatformName, package_path())),
name: loc!(specialize(EHeader::PlatformName, package_name())),
requires: specialize(EHeader::Requires, requires()),
exposes: specialize(EHeader::Exposes, exposes_modules()),
packages: specialize(EHeader::Packages, packages()),
@ -203,7 +221,7 @@ fn provides_to_package<'a>() -> impl Parser<'a, To<'a>, EProvides<'a>> {
|_, pos| EProvides::Identifier(pos),
map!(lowercase_ident(), To::ExistingPackage)
),
specialize(EProvides::Package, map!(package_path(), To::NewPackage))
specialize(EProvides::Package, map!(package_name(), To::NewPackage))
]
}

View File

@ -127,7 +127,8 @@ pub enum EHeader<'a> {
Start(Position),
ModuleName(Position),
AppName(EString<'a>, Position),
PlatformName(EPackagePath<'a>, Position),
PackageName(EPackageName<'a>, Position),
PlatformName(EPackageName<'a>, Position),
IndentStart(Position),
InconsistentModuleName(Region),
@ -146,7 +147,7 @@ pub enum EProvides<'a> {
ListStart(Position),
ListEnd(Position),
Identifier(Position),
Package(EPackagePath<'a>, Position),
Package(EPackageName<'a>, Position),
Space(BadInputError, Position),
}
@ -202,7 +203,7 @@ pub enum EPackages<'a> {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EPackagePath<'a> {
pub enum EPackageName<'a> {
BadPath(EString<'a>, Position),
Escapes(Position),
Multiline(Position),
@ -210,7 +211,7 @@ pub enum EPackagePath<'a> {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EPackageEntry<'a> {
BadPackage(EPackagePath<'a>, Position),
BadPackage(EPackageName<'a>, Position),
Shorthand(Position),
Colon(Position),
IndentPackage(Position),

View File

@ -0,0 +1,27 @@
Module {
comments: [],
header: Package(
PackageHeader {
before_name: [],
name: @8-24 PackageName(
"rtfeldman/blah",
),
exposes: KeywordItem {
keyword: Spaces {
before: [],
item: ExposesKeyword,
after: [],
},
item: [],
},
packages: KeywordItem {
keyword: Spaces {
before: [],
item: PackagesKeyword,
after: [],
},
item: [],
},
},
),
}

View File

@ -0,0 +1 @@
package "rtfeldman/blah" exposes [] packages {}

View File

@ -3,7 +3,7 @@ Module {
header: Platform(
PlatformHeader {
before_name: [],
name: @9-25 PackagePath(
name: @9-25 PackageName(
"rtfeldman/blah",
),
requires: KeywordItem {

View File

@ -19,7 +19,7 @@ Module {
@31-47 PackageEntry {
shorthand: "pf",
spaces_after_shorthand: [],
package_path: @35-47 PackagePath(
package_name: @35-47 PackageName(
"./platform",
),
},

View File

@ -19,7 +19,7 @@ Module {
@31-47 PackageEntry {
shorthand: "pf",
spaces_after_shorthand: [],
package_path: @35-47 PackagePath(
package_name: @35-47 PackageName(
"./platform",
),
},

View File

@ -3,7 +3,7 @@ Module {
header: Platform(
PlatformHeader {
before_name: [],
name: @9-14 PackagePath(
name: @9-14 PackageName(
"cli",
),
requires: KeywordItem {

View File

@ -22,7 +22,7 @@ Module {
after: [],
},
to: @30-38 NewPackage(
PackagePath(
PackageName(
"./blah",
),
),

View File

@ -0,0 +1,46 @@
Module {
comments: [],
header: Package(
PackageHeader {
before_name: [],
name: @8-20 PackageName(
"foo/barbaz",
),
exposes: KeywordItem {
keyword: Spaces {
before: [
Newline,
],
item: ExposesKeyword,
after: [],
},
item: [
@34-37 ModuleName(
"Foo",
),
@39-42 ModuleName(
"Bar",
),
],
},
packages: KeywordItem {
keyword: Spaces {
before: [
Newline,
],
item: PackagesKeyword,
after: [],
},
item: [
@59-71 PackageEntry {
shorthand: "foo",
spaces_after_shorthand: [],
package_name: @64-71 PackageName(
"./foo",
),
},
],
},
},
),
}

View File

@ -0,0 +1,3 @@
package "foo/barbaz"
exposes [Foo, Bar]
packages { foo: "./foo" }

View File

@ -3,7 +3,7 @@ Module {
header: Platform(
PlatformHeader {
before_name: [],
name: @9-21 PackagePath(
name: @9-21 PackageName(
"foo/barbaz",
),
requires: KeywordItem {
@ -52,7 +52,7 @@ Module {
@87-99 PackageEntry {
shorthand: "foo",
spaces_after_shorthand: [],
package_path: @92-99 PackagePath(
package_name: @92-99 PackageName(
"./foo",
),
},

View File

@ -19,7 +19,7 @@ Module {
@26-42 PackageEntry {
shorthand: "pf",
spaces_after_shorthand: [],
package_path: @30-42 PackagePath(
package_name: @30-42 PackageName(
"./platform",
),
},

View File

@ -3,7 +3,7 @@ Module {
header: Platform(
PlatformHeader {
before_name: [],
name: @9-21 PackagePath(
name: @9-21 PackageName(
"test/types",
),
requires: KeywordItem {

View File

@ -224,6 +224,7 @@ mod test_parse {
pass/empty_hosted_header.header,
pass/empty_interface_header.header,
pass/empty_list.expr,
pass/empty_package_header.header,
pass/empty_platform_header.header,
pass/empty_record.expr,
pass/empty_string.expr,
@ -280,6 +281,7 @@ mod test_parse {
pass/newline_inside_empty_list.expr,
pass/newline_singleton_list.expr,
pass/nonempty_hosted_header.header,
pass/nonempty_package_header.header,
pass/nonempty_platform_header.header,
pass/not_docs.expr,
pass/number_literal_suffixes.expr,

View File

@ -522,6 +522,41 @@ mod solve_expr {
);
}
#[test]
fn choose_correct_recursion_var_under_record() {
infer_queries!(
indoc!(
r#"
Parser : [
Specialize Parser,
Record (List {parser: Parser}),
]
printCombinatorParser : Parser -> Str
printCombinatorParser = \parser ->
when parser is
# ^^^^^^
Specialize p ->
printed = printCombinatorParser p
if Bool.false then printed else "foo"
Record fields ->
fields
|> List.map \f ->
printed = printCombinatorParser f.parser
if Bool.false then printed else "foo"
|> List.first
|> Result.withDefault ("foo")
printCombinatorParser (Record [])
"#
),
@r###"
parser : [Record (List { parser : a }), Specialize a] as a
"###
print_only_under_alias: true
);
}
// #[test]
// fn block_string_literal() {
// infer_eq(
@ -8529,4 +8564,23 @@ mod solve_expr {
print_can_decls: true
);
}
#[test]
fn constrain_dbg_flex_var() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
polyDbg = \x ->
#^^^^^^^{-1}
dbg x
x
main = polyDbg ""
"#
),
@"polyDbg : a -[[polyDbg(1)]]-> a"
);
}
}

View File

@ -42,6 +42,7 @@ roc_error_macros = { path = "../../error_macros" }
roc_std = { path = "../../roc_std" }
roc_debug_flags = {path="../debug_flags"}
roc_wasm_module = {path="../../wasm_module"}
roc_wasm_interp = {path="../../wasm_interp"}
bumpalo.workspace = true
libc.workspace = true
@ -49,7 +50,6 @@ libloading.workspace = true
criterion.workspace = true
tempfile.workspace = true
indoc.workspace = true
wasm3.workspace = true
lazy_static.workspace = true
inkwell.workspace = true
target-lexicon.workspace = true

View File

@ -885,7 +885,7 @@ mod decode_immediate {
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use indoc::indoc;
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
#[cfg(all(test, any(feature = "gen-llvm")))]
use roc_std::RocStr;
#[test]

View File

@ -339,7 +339,7 @@ fn eq_linked_list_false() {
}
#[test]
#[cfg(any(feature = "gen-wasm"))]
#[ignore] // breaks for LLVM (no tail recursion), takes a long time for Wasm
fn eq_linked_list_long() {
assert_evals_to!(
indoc!(
@ -355,7 +355,7 @@ fn eq_linked_list_long() {
prependOnes (n-1) (Cons 1 tail)
main =
n = 100_000
n = 100_000 # be careful, can make a noticeble difference to test_gen total time!
x : LinkedList I64
x = prependOnes n (Cons 999 Nil)

View File

@ -368,7 +368,7 @@ fn character_literal() {
"#
),
65,
u32
i64
);
}
@ -383,7 +383,7 @@ fn character_literal_back_slash() {
"#
),
92,
u32
i64
);
}
@ -398,7 +398,7 @@ fn character_literal_single_quote() {
"#
),
39,
u32
i64
);
}
@ -413,7 +413,7 @@ fn character_literal_new_line() {
"#
),
10,
u32
i64
);
}

View File

@ -1363,7 +1363,7 @@ fn linked_list_singleton() {
"#
),
0,
i64,
usize,
|_| 0
);
}

View File

@ -1362,18 +1362,7 @@ fn str_trim_right_small_to_small_shared() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_to_nat() {
assert_evals_to!(
indoc!(
r#"
when Str.toNat "1" is
Ok n -> n
Err _ -> 0
"#
),
1,
usize
);
assert_evals_to!(r#"Str.toNat "1" |> Result.withDefault 0"#, 1, usize);
}
#[test]

View File

@ -2083,3 +2083,53 @@ fn unify_types_with_fixed_fixpoints_outside_fixing_region() {
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn lambda_set_with_imported_toplevels_issue_4733() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
fn = \s ->
instr = if s == "*" then (Op Num.mul) else (Op Num.add)
Op op = instr
\a -> op a a
main = ((fn "*") 3) * ((fn "+") 5)
"#
),
90,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn non_unary_union_with_lambda_set_with_imported_toplevels_issue_4733() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
fn = \s ->
instr =
if s == "*" then (Op Num.mul)
else if s == "+" then (Op Num.add)
else Noop
when instr is
Op op -> (\a -> op a a)
_ -> (\a -> a)
main = ((fn "*") 3) * ((fn "+") 5)
"#
),
90,
i64
);
}

View File

@ -1,5 +1,6 @@
use super::RefCount;
use crate::helpers::from_wasm32_memory::FromWasm32Memory;
use bumpalo::Bump;
use roc_collections::all::MutSet;
use roc_gen_wasm::wasm32_result::Wasm32Result;
use roc_gen_wasm::DEBUG_SETTINGS;
@ -7,12 +8,10 @@ use roc_load::{ExecutionMode, LoadConfig, Threading};
use roc_packaging::cache::RocCacheDir;
use roc_reporting::report::DEFAULT_PALETTE_HTML;
use roc_std::RocStr;
use roc_wasm_module::{Export, ExportType};
use roc_wasm_interp::{wasi, ImportDispatcher, Instance, WasiDispatcher};
use roc_wasm_module::{Export, ExportType, Value, WasmModule};
use std::marker::PhantomData;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Mutex;
use wasm3::{Environment, Module, Runtime};
const TEST_WRAPPER_NAME: &str = "test_wrapper";
const INIT_REFCOUNT_NAME: &str = "init_refcount_test";
@ -182,6 +181,39 @@ where
run_wasm_test_bytes::<T>(TEST_WRAPPER_NAME, wasm_bytes)
}
struct TestDispatcher<'a> {
wasi: WasiDispatcher<'a>,
}
impl<'a> ImportDispatcher for TestDispatcher<'a> {
fn dispatch(
&mut self,
module_name: &str,
function_name: &str,
arguments: &[Value],
memory: &mut [u8],
) -> Option<Value> {
if module_name == wasi::MODULE_NAME {
self.wasi.dispatch(function_name, arguments, memory)
} else if module_name == "env" && function_name == "send_panic_msg_to_rust" {
let msg_ptr = arguments[0].expect_i32().unwrap();
let tag = arguments[1].expect_i32().unwrap();
let roc_msg = RocStr::decode(memory, msg_ptr as _);
let msg = match tag {
0 => format!(r#"Roc failed with message: "{}""#, roc_msg),
1 => format!(r#"User crash with message: "{}""#, roc_msg),
tag => format!(r#"Got an invald panic tag: "{}""#, tag),
};
panic!("{}", msg)
} else {
panic!(
"TestDispatcher does not implement {}.{}",
module_name, function_name
);
}
}
}
pub(crate) fn run_wasm_test_bytes<T>(
test_wrapper_name: &str,
wasm_bytes: Vec<u8>,
@ -189,57 +221,31 @@ pub(crate) fn run_wasm_test_bytes<T>(
where
T: FromWasm32Memory + Wasm32Result,
{
let env = Environment::new().expect("Unable to create environment");
let rt = env
.create_runtime(1024 * 60)
.expect("Unable to create runtime");
let arena = Bump::new();
let require_relocatable = false;
let module = WasmModule::preload(&arena, &wasm_bytes, require_relocatable)
.map_err(|e| format!("{:?}", e))?;
run_wasm_test_module(&arena, test_wrapper_name, &module)
}
let parsed = Module::parse(&env, &wasm_bytes[..]).expect("Unable to parse module");
let mut module = rt.load_module(parsed).expect("Unable to load module");
let panic_msg: Rc<Mutex<Option<(i32, u32)>>> = Default::default();
link_module(&mut module, panic_msg.clone());
let test_wrapper = module
.find_function::<(), i32>(test_wrapper_name)
.expect("Unable to find test wrapper function");
match test_wrapper.call() {
Err(e) => {
if let Some((msg_ptr, tag)) = *panic_msg.lock().unwrap() {
let memory: &[u8] = get_memory(&rt);
let msg = RocStr::decode(memory, msg_ptr as _);
dbg!(tag);
let msg = match tag {
0 => format!(r#"Roc failed with message: "{}""#, msg),
1 => format!(r#"User crash with message: "{}""#, msg),
tag => format!(r#"Got an invald panic tag: "{}""#, tag),
};
Err(msg)
} else {
Err(format!("{}", e))
}
}
Ok(address) => {
let memory: &[u8] = get_memory(&rt);
if false {
println!("test_wrapper returned 0x{:x}", address);
println!("Stack:");
crate::helpers::wasm::debug_memory_hex(memory, address, std::mem::size_of::<T>());
}
if false {
println!("Heap:");
// Manually provide address and size based on printf in wasm_test_platform.c
crate::helpers::wasm::debug_memory_hex(memory, 0x11440, 24);
}
let output = <T as FromWasm32Memory>::decode(memory, address as u32);
Ok(output)
}
}
pub(crate) fn run_wasm_test_module<'a, T>(
arena: &'a Bump,
test_wrapper_name: &str,
module: &WasmModule<'a>,
) -> Result<T, String>
where
T: FromWasm32Memory + Wasm32Result,
{
let dispatcher = TestDispatcher {
wasi: wasi::WasiDispatcher::default(),
};
let is_debug_mode = roc_debug_flags::dbg_set!(roc_debug_flags::ROC_LOG_WASM_INTERP);
let mut inst = Instance::for_module(&arena, &module, dispatcher, is_debug_mode)?;
let opt_value = inst.call_export(test_wrapper_name, [])?;
let addr_value = opt_value.ok_or("No return address from Wasm test")?;
let addr = addr_value.expect_i32().map_err(|e| format!("{:?}", e))?;
let output = <T as FromWasm32Memory>::decode(&inst.memory, addr as u32);
Ok(output)
}
#[allow(dead_code)]
@ -255,36 +261,36 @@ where
let wasm_bytes = crate::helpers::wasm::compile_to_wasm_bytes(&arena, src, phantom);
let env = Environment::new().expect("Unable to create environment");
let rt = env
.create_runtime(1024 * 60)
.expect("Unable to create runtime");
let parsed = Module::parse(&env, wasm_bytes).expect("Unable to parse module");
let mut module = rt.load_module(parsed).expect("Unable to load module");
let require_relocatable = false;
let module = WasmModule::preload(&arena, &wasm_bytes, require_relocatable)
.map_err(|e| format!("{:?}", e))?;
let panic_msg: Rc<Mutex<Option<(i32, u32)>>> = Default::default();
link_module(&mut module, panic_msg.clone());
let dispatcher = TestDispatcher {
wasi: wasi::WasiDispatcher::default(),
};
let is_debug_mode = roc_debug_flags::dbg_set!(roc_debug_flags::ROC_LOG_WASM_INTERP);
let mut inst = Instance::for_module(&arena, &module, dispatcher, is_debug_mode)?;
let expected_len = num_refcounts as i32;
let init_refcount_test = module
.find_function::<i32, i32>(INIT_REFCOUNT_NAME)
.expect("Unable to find refcount test init function");
let mut refcount_vector_offset = init_refcount_test.call(expected_len).unwrap() as usize;
// Allocate a vector in the test host that refcounts will be copied into
let mut refcount_vector_addr: i32 = inst
.call_export(INIT_REFCOUNT_NAME, [Value::I32(num_refcounts as i32)])?
.ok_or_else(|| format!("No return address from {}", INIT_REFCOUNT_NAME))?
.expect_i32()
.map_err(|type_err| format!("{:?}", type_err))?;
// Run the test
let test_wrapper = module
.find_function::<(), i32>(TEST_WRAPPER_NAME)
.expect("Unable to find test wrapper function");
test_wrapper.call().map_err(|e| format!("{:?}", e))?;
let memory: &[u8] = get_memory(&rt);
// Run the test, ignoring the result
let _result_addr: i32 = inst
.call_export(TEST_WRAPPER_NAME, [])?
.ok_or_else(|| format!("No return address from {}", TEST_WRAPPER_NAME))?
.expect_i32()
.map_err(|type_err| format!("{:?}", type_err))?;
// Read the length of the vector in the C host
let actual_len = read_i32(memory, refcount_vector_offset);
if actual_len != expected_len {
let actual_num_refcounts = read_i32(&inst.memory, refcount_vector_addr) as usize;
if actual_num_refcounts != num_refcounts {
return Err(format!(
"Expected {} refcounts but got {}",
expected_len, actual_len
num_refcounts, actual_num_refcounts
));
}
@ -292,13 +298,13 @@ where
let mut refcounts = Vec::with_capacity(num_refcounts);
for _ in 0..num_refcounts {
// Get the next RC pointer from the host's vector
refcount_vector_offset += 4;
let rc_ptr = read_i32(memory, refcount_vector_offset) as usize;
refcount_vector_addr += 4;
let rc_ptr = read_i32(&inst.memory, refcount_vector_addr);
let rc = if rc_ptr == 0 {
RefCount::Deallocated
} else {
// Dereference the RC pointer and decode its value from the negative number format
let rc_encoded = read_i32(memory, rc_ptr);
let rc_encoded = read_i32(&inst.memory, rc_ptr);
if rc_encoded == 0 {
RefCount::Constant
} else {
@ -311,40 +317,16 @@ where
Ok(refcounts)
}
fn get_memory(rt: &Runtime) -> &[u8] {
unsafe {
let memory_ptr: *const [u8] = rt.memory();
let (_, memory_size) = std::mem::transmute::<_, (usize, usize)>(memory_ptr);
std::slice::from_raw_parts(memory_ptr as _, memory_size)
}
}
fn read_i32(memory: &[u8], ptr: usize) -> i32 {
let mut bytes = [0u8; 4];
bytes.copy_from_slice(&memory[ptr..][..4]);
fn read_i32(memory: &[u8], addr: i32) -> i32 {
let index = addr as usize;
let mut bytes = [0; 4];
bytes.copy_from_slice(&memory[index..][..4]);
i32::from_le_bytes(bytes)
}
fn link_module(module: &mut Module, panic_msg: Rc<Mutex<Option<(i32, u32)>>>) {
let try_link_panic = module.link_closure(
"env",
"send_panic_msg_to_rust",
move |_call_context, (msg_ptr, tag): (i32, u32)| {
let mut w = panic_msg.lock().unwrap();
*w = Some((msg_ptr, tag));
Ok(())
},
);
match try_link_panic {
Ok(()) => {}
Err(wasm3::error::Error::FunctionNotFound) => {}
Err(e) => panic!("{:?}", e),
}
}
/// Print out hex bytes of the test result, and a few words on either side
/// Can be handy for debugging misalignment issues etc.
#[allow(dead_code)]
pub fn debug_memory_hex(memory_bytes: &[u8], address: i32, size: usize) {
let memory_words: &[u32] =
unsafe { std::slice::from_raw_parts(memory_bytes.as_ptr().cast(), memory_bytes.len() / 4) };

View File

@ -6,7 +6,7 @@ extern fn js_unused() i32;
extern fn roc__app_proc_1_exposed() i32;
export fn host_called_indirectly_from_roc() i32 {
fn host_called_indirectly_from_roc() i32 {
return 0x40;
}
@ -14,11 +14,11 @@ export fn host_called_directly_from_roc() i32 {
return 0x80 | host_called_indirectly_from_roc() | js_called_indirectly_from_roc();
}
export fn host_called_indirectly_from_main() i32 {
fn host_called_indirectly_from_main() i32 {
return 0x100;
}
export fn host_called_directly_from_main() i32 {
fn host_called_directly_from_main() i32 {
return 0x200 | host_called_indirectly_from_main() | js_called_indirectly_from_main();
}

View File

@ -18,8 +18,8 @@ use roc_mono::ir::{
UpdateModeId,
};
use roc_mono::layout::{Builtin, CapturesNiche, LambdaName, Layout, STLayoutInterner};
use roc_wasm_module::WasmModule;
use wasm3::{Environment, Module};
use roc_wasm_interp::{wasi, ImportDispatcher, Instance, WasiDispatcher};
use roc_wasm_module::{Value, WasmModule};
const LINKING_TEST_HOST_WASM: &str = "build/wasm_linking_test_host.wasm";
const LINKING_TEST_HOST_NATIVE: &str = "build/wasm_linking_test_host";
@ -183,36 +183,67 @@ impl<'a> BackendInputs<'a> {
}
}
fn execute_wasm_bytes(wasm_bytes: &[u8]) -> i32 {
let env = Environment::new().unwrap();
let rt = env.create_runtime(1024 * 60).unwrap();
struct TestDispatcher<'a> {
wasi: WasiDispatcher<'a>,
}
let parsed_module = Module::parse(&env, &wasm_bytes[..]).unwrap();
let mut module = rt.load_module(parsed_module).unwrap();
module
.link_closure("env", "js_called_directly_from_roc", |_, ()| Ok(0x01i32))
.unwrap();
module
.link_closure("env", "js_called_indirectly_from_roc", |_, ()| Ok(0x02i32))
.unwrap();
module
.link_closure("env", "js_called_directly_from_main", |_, ()| Ok(0x04i32))
.unwrap();
module
.link_closure("env", "js_called_indirectly_from_main", |_, ()| Ok(0x08i32))
.unwrap();
module
.link_closure("env", "js_unused", |_, ()| Ok(0x10i32))
.unwrap_or_else(|_| {});
impl ImportDispatcher for TestDispatcher<'_> {
fn dispatch(
&mut self,
module_name: &str,
function_name: &str,
arguments: &[Value],
memory: &mut [u8],
) -> Option<Value> {
if module_name == wasi::MODULE_NAME {
self.wasi.dispatch(function_name, arguments, memory)
} else if module_name == "env" {
match function_name {
"js_called_directly_from_roc" => Some(Value::I32(0x01)),
"js_called_indirectly_from_roc" => Some(Value::I32(0x02)),
"js_called_directly_from_main" => Some(Value::I32(0x04)),
"js_called_indirectly_from_main" => Some(Value::I32(0x08)),
"js_unused" => Some(Value::I32(0x10)),
_ => panic!("Unknown import env.{}", function_name),
}
} else {
panic!(
"TestDispatcher does not implement {}.{}",
module_name, function_name
);
}
}
}
fn execute_wasm_module<'a>(arena: &'a Bump, orig_module: WasmModule<'a>) -> Result<i32, String> {
// FIXME: see if we can skip serializing and re-parsing!
// Some metadata seems to be left over from the host file. e.g. CodeSection::section_start
let module = {
let mut buffer = Vec::with_capacity(orig_module.size());
orig_module.serialize(&mut buffer);
WasmModule::preload(arena, &buffer, false).map_err(|e| format!("{:?}", e))?
};
let dispatcher = TestDispatcher {
wasi: wasi::WasiDispatcher::default(),
};
let is_debug_mode = false;
let mut inst = Instance::for_module(&arena, &module, dispatcher, is_debug_mode)?;
// In Zig, main can only return u8 or void, but our result is too wide for that.
// But I want to use main so that I can test that _start is created for it!
// So return void from main, and call another function to get the result.
let start = module.find_function::<(), ()>("_start").unwrap();
start.call().unwrap();
inst.call_export("_start", [])?;
let read_host_result = module.find_function::<(), i32>("read_host_result").unwrap();
read_host_result.call().unwrap()
// FIXME: read_host_result does not actually appear as an export!
// The interpreter has to look it up in debug info! (Apparently Wasm3 did this!)
// If we change gen_wasm to export it, then it does the same for js_unused,
// so we can't test import elimination and function reordering.
// We should to come back to this and fix it.
inst.call_export("read_host_result", [])?
.ok_or(String::from("expected a return value"))?
.expect_i32()
.map_err(|type_err| format!("{:?}", type_err))
}
fn get_native_result() -> i32 {
@ -224,63 +255,13 @@ fn get_native_result() -> i32 {
result_str.parse().unwrap()
}
#[test]
fn test_linking_without_dce() {
let arena = Bump::new();
let layout_interner = STLayoutInterner::with_capacity(4);
let BackendInputs {
env,
mut interns,
host_module,
procedures,
} = BackendInputs::new(&arena, &layout_interner);
let host_import_names = Vec::from_iter(host_module.import.imports.iter().map(|i| i.name));
assert_eq!(
&host_import_names,
&[
"__linear_memory",
"__stack_pointer",
"js_called_indirectly_from_roc",
"js_called_indirectly_from_main",
"js_unused",
"js_called_directly_from_roc",
"js_called_directly_from_main",
"roc__app_proc_1_exposed",
"__indirect_function_table",
]
);
let (final_module, _called_fns, _roc_main_index) =
roc_gen_wasm::build_app_module(&env, &mut interns, host_module, procedures);
let mut buffer = Vec::with_capacity(final_module.size());
final_module.serialize(&mut buffer);
if std::env::var("DEBUG_WASM").is_ok() {
fs::write("build/without_dce.wasm", &buffer).unwrap();
}
let final_import_names = Vec::from_iter(final_module.import.imports.iter().map(|i| i.name));
assert_eq!(
&final_import_names,
&[
"js_called_indirectly_from_roc",
"js_called_indirectly_from_main",
"js_unused",
"js_called_directly_from_roc",
"js_called_directly_from_main",
]
);
let wasm_result = execute_wasm_bytes(&buffer);
assert_eq!(wasm_result, get_native_result());
}
#[test]
fn test_linking_with_dce() {
fn test_help(
eliminate_dead_code: bool,
expected_host_import_names: &[&str],
expected_final_import_names: &[&str],
expected_name_section_start: &[(u32, &str)],
dump_filename: &str,
) {
let arena = Bump::new();
let layout_interner = STLayoutInterner::with_capacity(4);
@ -292,57 +273,105 @@ fn test_linking_with_dce() {
} = BackendInputs::new(&arena, &layout_interner);
let host_import_names = Vec::from_iter(host_module.import.imports.iter().map(|imp| imp.name));
assert_eq!(
&host_import_names,
&[
"__linear_memory",
"__stack_pointer",
"js_called_indirectly_from_roc",
"js_called_indirectly_from_main",
"js_unused",
"js_called_directly_from_roc",
"js_called_directly_from_main",
"roc__app_proc_1_exposed",
"__indirect_function_table",
]
);
assert_eq!(&host_import_names, expected_host_import_names);
assert!(&host_module.names.function_names.is_empty());
let (mut final_module, called_fns, _roc_main_index) =
roc_gen_wasm::build_app_module(&env, &mut interns, host_module, procedures);
final_module.eliminate_dead_code(env.arena, called_fns);
if eliminate_dead_code {
final_module.eliminate_dead_code(env.arena, called_fns);
}
let mut buffer = Vec::with_capacity(final_module.size());
final_module.serialize(&mut buffer);
if std::env::var("DEBUG_WASM").is_ok() {
fs::write("build/with_dce.wasm", &buffer).unwrap();
let mut buffer = Vec::with_capacity(final_module.size());
final_module.serialize(&mut buffer);
fs::write(dump_filename, &buffer).unwrap();
}
let final_import_names = Vec::from_iter(final_module.import.imports.iter().map(|i| i.name));
assert_eq!(&final_import_names, expected_final_import_names);
let name_count = expected_name_section_start.len();
assert_eq!(
&final_import_names,
&[
"js_called_indirectly_from_roc",
"js_called_indirectly_from_main",
"js_called_directly_from_roc",
"js_called_directly_from_main",
]
&final_module.names.function_names[0..name_count],
expected_name_section_start
);
assert_eq!(
&final_module.names.function_names[0..5],
&[
(0, "js_called_indirectly_from_roc"),
(1, "js_called_indirectly_from_main"),
(2, "js_called_directly_from_roc"),
(3, "js_called_directly_from_main"),
(4, "js_unused"),
]
);
let wasm_result = execute_wasm_bytes(&buffer);
let wasm_result = execute_wasm_module(&arena, final_module).unwrap();
assert_eq!(wasm_result, get_native_result());
}
const EXPECTED_HOST_IMPORT_NAMES: [&'static str; 9] = [
"__linear_memory",
"__stack_pointer",
"js_called_indirectly_from_roc",
"js_unused",
"js_called_directly_from_roc",
"js_called_directly_from_main",
"roc__app_proc_1_exposed",
"js_called_indirectly_from_main",
"__indirect_function_table",
];
#[test]
fn test_linking_without_dce() {
let expected_final_import_names = &[
"js_called_indirectly_from_roc",
"js_unused", // not eliminated
"js_called_directly_from_roc",
"js_called_directly_from_main",
"js_called_indirectly_from_main",
];
let expected_name_section_start = &[
(0, "js_called_indirectly_from_roc"),
(1, "js_unused"), // not eliminated
(2, "js_called_directly_from_roc"),
(3, "js_called_directly_from_main"),
(4, "js_called_indirectly_from_main"),
];
let eliminate_dead_code = false;
let dump_filename = "build/without_dce.wasm";
test_help(
eliminate_dead_code,
&EXPECTED_HOST_IMPORT_NAMES,
expected_final_import_names,
expected_name_section_start,
dump_filename,
);
}
#[test]
fn test_linking_with_dce() {
let expected_final_import_names = &[
"js_called_indirectly_from_roc",
// "js_unused", // eliminated
"js_called_directly_from_roc",
"js_called_directly_from_main",
"js_called_indirectly_from_main",
];
let expected_name_section_start = &[
(0, "js_called_indirectly_from_roc"),
(1, "js_called_directly_from_roc"), // index changed
(2, "js_called_directly_from_main"), // index changed
(3, "js_called_indirectly_from_main"), // index changed
(4, "js_unused"), // still exists, but now an internal dummy, with index changed
];
let eliminate_dead_code = true;
let dump_filename = "build/with_dce.wasm";
test_help(
eliminate_dead_code,
&EXPECTED_HOST_IMPORT_NAMES,
expected_final_import_names,
expected_name_section_start,
dump_filename,
);
}

View File

@ -0,0 +1,105 @@
procedure Bool.1 ():
let Bool.24 : Int1 = false;
ret Bool.24;
procedure List.2 (List.95, List.96):
let List.492 : U64 = CallByName List.6 List.95;
let List.488 : Int1 = CallByName Num.22 List.96 List.492;
if List.488 then
let List.490 : Str = CallByName List.66 List.95 List.96;
let List.489 : [C {}, C Str] = TagId(1) List.490;
ret List.489;
else
let List.487 : {} = Struct {};
let List.486 : [C {}, C Str] = TagId(0) List.487;
ret List.486;
procedure List.5 (#Attr.2, #Attr.3):
let List.494 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.10 #Attr.3;
ret List.494;
procedure List.6 (#Attr.2):
let List.493 : U64 = lowlevel ListLen #Attr.2;
ret List.493;
procedure List.66 (#Attr.2, #Attr.3):
let List.491 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.491;
procedure List.9 (List.283):
let List.485 : U64 = 0i64;
let List.478 : [C {}, C Str] = CallByName List.2 List.283 List.485;
let List.482 : U8 = 1i64;
let List.483 : U8 = GetTagId List.478;
let List.484 : Int1 = lowlevel Eq List.482 List.483;
if List.484 then
let List.284 : Str = UnionAtIndex (Id 1) (Index 0) List.478;
inc List.284;
dec List.478;
let List.479 : [C {}, C Str] = TagId(1) List.284;
ret List.479;
else
dec List.478;
let List.481 : {} = Struct {};
let List.480 : [C {}, C Str] = TagId(0) List.481;
ret List.480;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.256 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.256;
procedure Result.5 (Result.12, Result.13):
let Result.39 : U8 = 1i64;
let Result.40 : U8 = GetTagId Result.12;
let Result.41 : Int1 = lowlevel Eq Result.39 Result.40;
if Result.41 then
let Result.14 : Str = UnionAtIndex (Id 1) (Index 0) Result.12;
inc Result.14;
dec Result.12;
ret Result.14;
else
dec Result.12;
inc Result.13;
ret Result.13;
procedure Test.10 (Test.11):
let Test.12 : Str = CallByName Test.2 Test.11;
let Test.26 : Int1 = CallByName Bool.1;
if Test.26 then
ret Test.12;
else
dec Test.12;
let Test.25 : Str = "foo";
ret Test.25;
procedure Test.2 (Test.6):
let Test.29 : U8 = 1i64;
let Test.30 : U8 = GetTagId Test.6;
let Test.31 : Int1 = lowlevel Eq Test.29 Test.30;
if Test.31 then
let Test.7 : [<r>C List *self, C *self] = UnionAtIndex (Id 1) (Index 0) Test.6;
let Test.8 : Str = CallByName Test.2 Test.7;
let Test.18 : Int1 = CallByName Bool.1;
if Test.18 then
ret Test.8;
else
dec Test.8;
let Test.17 : Str = "foo";
ret Test.17;
else
let Test.9 : List [<r>C List *self, C *self] = UnionAtIndex (Id 0) (Index 0) Test.6;
let Test.24 : {} = Struct {};
let Test.23 : List Str = CallByName List.5 Test.9 Test.24;
let Test.21 : [C {}, C Str] = CallByName List.9 Test.23;
dec Test.23;
let Test.22 : Str = "foo";
let Test.20 : Str = CallByName Result.5 Test.21 Test.22;
dec Test.22;
ret Test.20;
procedure Test.0 ():
let Test.32 : List [<r>C List *self, C *self] = Array [];
let Test.15 : [<r>C List *self, C *self] = TagId(0) Test.32;
let Test.14 : Str = CallByName Test.2 Test.15;
dec Test.15;
ret Test.14;

View File

@ -28,7 +28,7 @@ procedure Decode.24 (Decode.95, Decode.114, Decode.97):
ret Decode.127;
procedure Decode.25 (Decode.98, Decode.99):
let Decode.126 : {} = Struct {};
let Decode.126 : {} = CallByName Json.41;
let Decode.125 : {List U8, [C {}, C Str]} = CallByName Decode.24 Decode.98 Decode.126 Decode.99;
ret Decode.125;

View File

@ -0,0 +1,49 @@
procedure Bool.11 (#Attr.2, #Attr.3):
let Bool.23 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
ret Bool.23;
procedure Bool.2 ():
let Bool.24 : Int1 = true;
ret Bool.24;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.256 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.256;
procedure Num.21 (#Attr.2, #Attr.3):
let Num.257 : U64 = lowlevel NumMul #Attr.2 #Attr.3;
ret Num.257;
procedure Test.0 (Test.8):
let Test.23 : Int1 = CallByName Bool.2;
if Test.23 then
let Test.24 : Int1 = true;
ret Test.24;
else
let Test.22 : Int1 = false;
ret Test.22;
procedure Test.5 (Test.6, Test.2):
joinpoint Test.19 Test.18:
ret Test.18;
in
switch Test.2:
case 0:
let Test.20 : U64 = CallByName Num.19 Test.6 Test.6;
jump Test.19 Test.20;
default:
let Test.21 : U64 = CallByName Num.21 Test.6 Test.6;
jump Test.19 Test.21;
procedure Test.7 ():
let Test.13 : U64 = 3i64;
let Test.15 : {} = Struct {};
let Test.14 : Int1 = CallByName Test.0 Test.15;
let Test.11 : U64 = CallByName Test.5 Test.13 Test.14;
let Test.12 : U64 = 9i64;
let Test.10 : Int1 = CallByName Bool.11 Test.11 Test.12;
expect Test.10;
let Test.9 : {} = Struct {};
ret Test.9;

View File

@ -1530,6 +1530,34 @@ fn encode_derived_record() {
)
}
#[mono_test(no_check)]
fn choose_correct_recursion_var_under_record() {
indoc!(
r#"
Parser : [
Specialize Parser,
Record (List {parser: Parser}),
]
printCombinatorParser : Parser -> Str
printCombinatorParser = \parser ->
when parser is
Specialize p ->
printed = printCombinatorParser p
if Bool.false then printed else "foo"
Record fields ->
fields
|> List.map \f ->
printed = printCombinatorParser f.parser
if Bool.false then printed else "foo"
|> List.first
|> Result.withDefault ("foo")
printCombinatorParser (Record [])
"#
)
}
#[mono_test]
fn tail_call_elimination() {
indoc!(
@ -2173,3 +2201,22 @@ fn issue_4749() {
"###
)
}
#[mono_test(mode = "test", no_check)]
fn lambda_set_with_imported_toplevels_issue_4733() {
indoc!(
r###"
interface Test exposes [] imports []
fn = \{} ->
instr : [ Op (U64, U64 -> U64) ]
instr = if Bool.true then (Op Num.mul) else (Op Num.add)
Op op = instr
\a -> op a a
expect ((fn {}) 3) == 9
"###
)
}

View File

@ -2055,33 +2055,41 @@ fn unify_shared_fields<M: MetaCollector>(
// this is an error, but we continue to give better error messages
continue;
}
(Demanded(val), Required(_))
| (Required(val), Demanded(_))
| (Demanded(val), Demanded(_)) => Demanded(val),
(Required(val), Required(_)) => Required(val),
(Required(val), Optional(_)) => Required(val),
(Optional(val), Required(_)) => Required(val),
(Optional(val), Optional(_)) => Optional(val),
(Demanded(a), Required(b))
| (Required(a), Demanded(b))
| (Demanded(a), Demanded(b)) => Demanded(choose_merged_var(env.subs, a, b)),
(Required(a), Required(b))
| (Required(a), Optional(b))
| (Optional(a), Required(b)) => Required(choose_merged_var(env.subs, a, b)),
(Optional(a), Optional(b)) => Optional(choose_merged_var(env.subs, a, b)),
// rigid optional
(RigidOptional(val), Optional(_)) | (Optional(_), RigidOptional(val)) => {
RigidOptional(val)
(RigidOptional(a), Optional(b)) | (Optional(b), RigidOptional(a)) => {
RigidOptional(choose_merged_var(env.subs, a, b))
}
(RigidOptional(a), RigidOptional(b)) => {
RigidOptional(choose_merged_var(env.subs, a, b))
}
(RigidOptional(_), Demanded(_) | Required(_) | RigidRequired(_))
| (Demanded(_) | Required(_) | RigidRequired(_), RigidOptional(_)) => {
// this is an error, but we continue to give better error messages
continue;
}
(RigidOptional(val), RigidOptional(_)) => RigidOptional(val),
// rigid required
(RigidRequired(_), Optional(_)) | (Optional(_), RigidRequired(_)) => {
// this is an error, but we continue to give better error messages
continue;
}
(RigidRequired(val), Demanded(_) | Required(_))
| (Demanded(_) | Required(_), RigidRequired(val)) => RigidRequired(val),
(RigidRequired(val), RigidRequired(_)) => RigidRequired(val),
(RigidRequired(a), Demanded(b) | Required(b))
| (Demanded(b) | Required(b), RigidRequired(a)) => {
RigidRequired(choose_merged_var(env.subs, a, b))
}
(RigidRequired(a), RigidRequired(b)) => {
RigidRequired(choose_merged_var(env.subs, a, b))
}
};
matching_fields.push((name, actual));

View File

@ -91,7 +91,6 @@ RocNum : [
U128,
F32,
F64,
F128,
Dec,
]
@ -153,4 +152,4 @@ RocTagUnion : [
nonNullPayload: TypeId,
whichTagIsNull: [FirstTagIsNull, SecondTagIsNull],
},
]
]

View File

@ -571,7 +571,6 @@ struct RocType_RocDict {
#[repr(u8)]
pub enum RocNum {
Dec = 0,
F128 = 1,
F32 = 2,
F64 = 3,
I128 = 4,
@ -590,7 +589,6 @@ impl core::fmt::Debug for RocNum {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Dec => f.write_str("RocNum::Dec"),
Self::F128 => f.write_str("RocNum::F128"),
Self::F32 => f.write_str("RocNum::F32"),
Self::F64 => f.write_str("RocNum::F64"),
Self::I128 => f.write_str("RocNum::I128"),

View File

@ -1737,7 +1737,6 @@ fn type_name(id: TypeId, types: &Types) -> String {
RocType::Num(RocNum::I128) => "roc_std::I128".to_string(),
RocType::Num(RocNum::F32) => "f32".to_string(),
RocType::Num(RocNum::F64) => "f64".to_string(),
RocType::Num(RocNum::F128) => "roc_std::F128".to_string(),
RocType::Num(RocNum::Dec) => "roc_std::RocDec".to_string(),
RocType::RocDict(key_id, val_id) => format!(
"roc_std::RocDict<{}, {}>",
@ -2433,7 +2432,7 @@ fn has_float_help(roc_type: &RocType, types: &Types, do_not_recurse: &[TypeId])
use RocNum::*;
match num {
F32 | F64 | F128 => true,
F32 | F64 => true,
I8 | U8 | I16 | U16 | I32 | U32 | I64 | U64 | I128 | U128 | Dec => false,
}
}

View File

@ -544,7 +544,6 @@ pub enum RocNum {
U128,
F32,
F64,
F128,
Dec,
}
@ -566,7 +565,6 @@ impl RocNum {
RocNum::U128 => size_of::<roc_std::U128>(),
RocNum::F32 => size_of::<f32>(),
RocNum::F64 => size_of::<f64>(),
RocNum::F128 => todo!(),
RocNum::Dec => size_of::<roc_std::RocDec>(),
};
@ -1083,11 +1081,6 @@ fn add_builtin_type<'a>(
RocType::Num(RocNum::F64),
layout,
),
F128 => types.add_anonymous(
&env.layout_cache.interner,
RocType::Num(RocNum::F128),
layout,
),
},
(Builtin::Decimal, _) => types.add_anonymous(
&env.layout_cache.interner,

View File

@ -31,6 +31,7 @@ pub enum Token {
KeywordTo = 0b_0010_1110,
KeywordExposes = 0b_0010_1111,
KeywordEffects = 0b_0011_0000,
KeywordPackage = 0b_0111_1100,
KeywordPlatform = 0b_0011_0001,
KeywordRequires = 0b_0011_0010,
KeywordDbg = 0b_0111_1011,
@ -428,6 +429,7 @@ fn lex_ident(uppercase: bool, bytes: &[u8]) -> (Token, usize) {
b"to" => Token::KeywordTo,
b"exposes" => Token::KeywordExposes,
b"effects" => Token::KeywordEffects,
b"package" => Token::KeywordPackage,
b"platform" => Token::KeywordPlatform,
b"requires" => Token::KeywordRequires,
ident => {

Some files were not shown because too many files have changed in this diff Show More