mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 08:11:12 +00:00
Merge branch 'trunk' into linker
This commit is contained in:
commit
1d23f4c0d2
28 changed files with 683 additions and 324 deletions
19
Cargo.lock
generated
19
Cargo.lock
generated
|
@ -2036,6 +2036,15 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libmimalloc-sys"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d1b8479c593dba88c2741fc50b92e13dbabbbe0bd504d979f244ccc1a5b1c01"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linked-hash-map"
|
name = "linked-hash-map"
|
||||||
version = "0.5.4"
|
version = "0.5.4"
|
||||||
|
@ -2189,6 +2198,15 @@ dependencies = [
|
||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mimalloc"
|
||||||
|
version = "0.1.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb74897ce508e6c49156fd1476fc5922cbc6e75183c65e399c765a09122e5130"
|
||||||
|
dependencies = [
|
||||||
|
"libmimalloc-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
@ -3452,6 +3470,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"libloading 0.6.7",
|
"libloading 0.6.7",
|
||||||
"maplit",
|
"maplit",
|
||||||
|
"mimalloc",
|
||||||
"pretty_assertions 0.5.1",
|
"pretty_assertions 0.5.1",
|
||||||
"quickcheck 0.8.5",
|
"quickcheck 0.8.5",
|
||||||
"quickcheck_macros 0.8.0",
|
"quickcheck_macros 0.8.0",
|
||||||
|
|
|
@ -67,6 +67,7 @@ im-rc = "14" # im and im-rc should always have the same version!
|
||||||
bumpalo = { version = "3.2", features = ["collections"] }
|
bumpalo = { version = "3.2", features = ["collections"] }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
libloading = "0.6"
|
libloading = "0.6"
|
||||||
|
mimalloc = { version = "0.1.26", default-features = false }
|
||||||
|
|
||||||
inkwell = { path = "../vendor/inkwell", optional = true }
|
inkwell = { path = "../vendor/inkwell", optional = true }
|
||||||
target-lexicon = "0.12.2"
|
target-lexicon = "0.12.2"
|
||||||
|
|
|
@ -371,15 +371,22 @@ fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) {
|
||||||
|
|
||||||
// Then, we get the import object related to our WASI
|
// Then, we get the import object related to our WASI
|
||||||
// and attach it to the Wasm instance.
|
// and attach it to the Wasm instance.
|
||||||
let import_object = wasi_env
|
let import_object = wasi_env.import_object(&module).unwrap();
|
||||||
.import_object(&module)
|
|
||||||
.unwrap_or_else(|_| wasmer::imports!());
|
|
||||||
|
|
||||||
let instance = Instance::new(&module, &import_object).unwrap();
|
let instance = Instance::new(&module, &import_object).unwrap();
|
||||||
|
|
||||||
let start = instance.exports.get_function("_start").unwrap();
|
let start = instance.exports.get_function("_start").unwrap();
|
||||||
|
|
||||||
start.call(&[]).unwrap();
|
use wasmer_wasi::WasiError;
|
||||||
|
match start.call(&[]) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => match e.downcast::<WasiError>() {
|
||||||
|
Ok(WasiError::Exit(0)) => {
|
||||||
|
// we run the `_start` function, so exit(0) is expected
|
||||||
|
}
|
||||||
|
other => panic!("Wasmer error: {:?}", other),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Backend {
|
enum Backend {
|
||||||
|
|
|
@ -6,6 +6,9 @@ use std::fs::{self, FileType};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||||
|
|
||||||
#[cfg(feature = "llvm")]
|
#[cfg(feature = "llvm")]
|
||||||
use roc_cli::build;
|
use roc_cli::build;
|
||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
|
|
|
@ -603,7 +603,7 @@ mod cli_run {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wasm32-cli-run")]
|
#[allow(dead_code)]
|
||||||
fn run_with_wasmer(wasm_path: &std::path::Path, stdin: &[&str]) -> String {
|
fn run_with_wasmer(wasm_path: &std::path::Path, stdin: &[&str]) -> String {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use wasmer::{Instance, Module, Store};
|
use wasmer::{Instance, Module, Store};
|
||||||
|
@ -647,21 +647,31 @@ fn run_with_wasmer(wasm_path: &std::path::Path, stdin: &[&str]) -> String {
|
||||||
let start = instance.exports.get_function("_start").unwrap();
|
let start = instance.exports.get_function("_start").unwrap();
|
||||||
|
|
||||||
match start.call(&[]) {
|
match start.call(&[]) {
|
||||||
Ok(_) => {
|
Ok(_) => read_wasi_stdout(wasi_env),
|
||||||
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();
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
panic!("Something went wrong running a wasm test:\n{:?}", e);
|
use wasmer_wasi::WasiError;
|
||||||
|
match e.downcast::<WasiError>() {
|
||||||
|
Ok(WasiError::Exit(0)) => {
|
||||||
|
// we run the `_start` function, so exit(0) is expected
|
||||||
|
read_wasi_stdout(wasi_env)
|
||||||
|
}
|
||||||
|
other => format!("Something went wrong running a wasm test: {:?}", other),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn read_wasi_stdout(wasi_env: wasmer_wasi::WasiEnv) -> String {
|
||||||
|
let mut state = wasi_env.state.lock().unwrap();
|
||||||
|
|
||||||
|
match state.fs.stdout_mut() {
|
||||||
|
Ok(Some(stdout)) => {
|
||||||
|
let mut buf = String::new();
|
||||||
|
stdout.read_to_string(&mut buf).unwrap();
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -59,6 +59,22 @@ fn find_zig_str_path() -> PathBuf {
|
||||||
panic!("cannot find `str.zig`")
|
panic!("cannot find `str.zig`")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_wasi_libc_path() -> PathBuf {
|
||||||
|
let wasi_libc_path = PathBuf::from("compiler/builtins/bitcode/wasi-libc.a");
|
||||||
|
|
||||||
|
if std::path::Path::exists(&wasi_libc_path) {
|
||||||
|
return wasi_libc_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// when running the tests, we start in the /cli directory
|
||||||
|
let wasi_libc_path = PathBuf::from("../compiler/builtins/bitcode/wasi-libc.a");
|
||||||
|
if std::path::Path::exists(&wasi_libc_path) {
|
||||||
|
return wasi_libc_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("cannot find `wasi-libc.a`")
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
pub fn build_zig_host_native(
|
pub fn build_zig_host_native(
|
||||||
env_path: &str,
|
env_path: &str,
|
||||||
|
@ -617,6 +633,7 @@ fn link_wasm32(
|
||||||
_link_type: LinkType,
|
_link_type: LinkType,
|
||||||
) -> io::Result<(Child, PathBuf)> {
|
) -> io::Result<(Child, PathBuf)> {
|
||||||
let zig_str_path = find_zig_str_path();
|
let zig_str_path = find_zig_str_path();
|
||||||
|
let wasi_libc_path = find_wasi_libc_path();
|
||||||
|
|
||||||
let child = Command::new("zig9")
|
let child = Command::new("zig9")
|
||||||
// .env_clear()
|
// .env_clear()
|
||||||
|
@ -624,9 +641,10 @@ fn link_wasm32(
|
||||||
.args(&["build-exe"])
|
.args(&["build-exe"])
|
||||||
.args(input_paths)
|
.args(input_paths)
|
||||||
.args([
|
.args([
|
||||||
|
// include wasi libc
|
||||||
|
// using `-lc` is broken in zig 8 (and early 9) in combination with ReleaseSmall
|
||||||
|
wasi_libc_path.to_str().unwrap(),
|
||||||
&format!("-femit-bin={}", output_path.to_str().unwrap()),
|
&format!("-femit-bin={}", output_path.to_str().unwrap()),
|
||||||
// include libc
|
|
||||||
"-lc",
|
|
||||||
"-target",
|
"-target",
|
||||||
"wasm32-wasi-musl",
|
"wasm32-wasi-musl",
|
||||||
"--pkg-begin",
|
"--pkg-begin",
|
||||||
|
@ -634,7 +652,8 @@ fn link_wasm32(
|
||||||
zig_str_path.to_str().unwrap(),
|
zig_str_path.to_str().unwrap(),
|
||||||
"--pkg-end",
|
"--pkg-end",
|
||||||
"--strip",
|
"--strip",
|
||||||
// "-O", "ReleaseSmall",
|
"-O",
|
||||||
|
"ReleaseSmall",
|
||||||
// useful for debugging
|
// useful for debugging
|
||||||
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll",
|
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll",
|
||||||
])
|
])
|
||||||
|
|
|
@ -54,7 +54,7 @@ pub fn build(b: *Builder) void {
|
||||||
|
|
||||||
// 32-bit wasm
|
// 32-bit wasm
|
||||||
wasm32_target.cpu_arch = std.Target.Cpu.Arch.wasm32;
|
wasm32_target.cpu_arch = std.Target.Cpu.Arch.wasm32;
|
||||||
wasm32_target.os_tag = std.Target.Os.Tag.wasi;
|
wasm32_target.os_tag = std.Target.Os.Tag.freestanding;
|
||||||
wasm32_target.abi = std.Target.Abi.none;
|
wasm32_target.abi = std.Target.Abi.none;
|
||||||
|
|
||||||
const obj_name_wasm32 = "builtins-wasm32";
|
const obj_name_wasm32 = "builtins-wasm32";
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
const builtin = @import("builtin");
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const testing = std.testing;
|
|
||||||
|
|
||||||
// Dec Module
|
// Dec Module
|
||||||
const dec = @import("dec.zig");
|
const dec = @import("dec.zig");
|
||||||
|
@ -110,6 +108,7 @@ const utils = @import("utils.zig");
|
||||||
comptime {
|
comptime {
|
||||||
exportUtilsFn(utils.test_panic, "test_panic");
|
exportUtilsFn(utils.test_panic, "test_panic");
|
||||||
exportUtilsFn(utils.decrefC, "decref");
|
exportUtilsFn(utils.decrefC, "decref");
|
||||||
|
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
|
||||||
|
|
||||||
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
|
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
|
||||||
}
|
}
|
||||||
|
@ -140,13 +139,21 @@ fn exportUtilsFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||||
|
|
||||||
// Custom panic function, as builtin Zig version errors during LLVM verification
|
// Custom panic function, as builtin Zig version errors during LLVM verification
|
||||||
pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn {
|
pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn {
|
||||||
std.debug.print("{s}: {?}", .{ message, stacktrace });
|
if (std.builtin.is_test) {
|
||||||
|
std.debug.print("{s}: {?}", .{ message, stacktrace });
|
||||||
|
} else {
|
||||||
|
_ = message;
|
||||||
|
_ = stacktrace;
|
||||||
|
}
|
||||||
|
|
||||||
unreachable;
|
unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run all tests in imported modules
|
// Run all tests in imported modules
|
||||||
// https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94
|
// https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94
|
||||||
test "" {
|
test "" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
testing.refAllDecls(@This());
|
testing.refAllDecls(@This());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +165,7 @@ test "" {
|
||||||
//
|
//
|
||||||
// Thank you Zig Contributors!
|
// Thank you Zig Contributors!
|
||||||
export fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 {
|
export fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 {
|
||||||
// @setRuntimeSafety(builtin.is_test);
|
// @setRuntimeSafety(std.builtin.is_test);
|
||||||
|
|
||||||
const min = @bitCast(i128, @as(u128, 1 << (128 - 1)));
|
const min = @bitCast(i128, @as(u128, 1 << (128 - 1)));
|
||||||
const max = ~min;
|
const max = ~min;
|
||||||
|
|
|
@ -117,6 +117,16 @@ pub fn decrefC(
|
||||||
return @call(.{ .modifier = always_inline }, decref_ptr_to_refcount, .{ bytes, alignment });
|
return @call(.{ .modifier = always_inline }, decref_ptr_to_refcount, .{ bytes, alignment });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decrefCheckNullC(
|
||||||
|
bytes_or_null: ?[*]u8,
|
||||||
|
alignment: u32,
|
||||||
|
) callconv(.C) void {
|
||||||
|
if (bytes_or_null) |bytes| {
|
||||||
|
const isizes: [*]isize = @ptrCast([*]isize, @alignCast(@sizeOf(isize), bytes));
|
||||||
|
return @call(.{ .modifier = always_inline }, decref_ptr_to_refcount, .{ isizes - 1, alignment });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn decref(
|
pub fn decref(
|
||||||
bytes_or_null: ?[*]u8,
|
bytes_or_null: ?[*]u8,
|
||||||
data_bytes: usize,
|
data_bytes: usize,
|
||||||
|
|
BIN
compiler/builtins/bitcode/wasi-libc.a
Normal file
BIN
compiler/builtins/bitcode/wasi-libc.a
Normal file
Binary file not shown.
|
@ -83,3 +83,4 @@ pub const DEC_DIV: &str = "roc_builtins.dec.div";
|
||||||
|
|
||||||
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
|
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
|
||||||
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
|
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
|
||||||
|
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
|
||||||
|
|
|
@ -3,15 +3,15 @@ use std::path::Path;
|
||||||
|
|
||||||
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
|
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
|
||||||
use crate::llvm::build_dict::{
|
use crate::llvm::build_dict::{
|
||||||
dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection,
|
self, dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection,
|
||||||
dict_keys, dict_len, dict_remove, dict_union, dict_values, dict_walk, set_from_list,
|
dict_keys, dict_len, dict_remove, dict_union, dict_values, dict_walk, set_from_list,
|
||||||
};
|
};
|
||||||
use crate::llvm::build_hash::generic_hash;
|
use crate::llvm::build_hash::generic_hash;
|
||||||
use crate::llvm::build_list::{
|
use crate::llvm::build_list::{
|
||||||
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
|
self, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat,
|
||||||
list_drop, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len,
|
list_contains, list_drop, list_get_unsafe, list_join, list_keep_errs, list_keep_if,
|
||||||
list_map, list_map2, list_map3, list_map_with_index, list_prepend, list_range, list_repeat,
|
list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index, list_prepend,
|
||||||
list_reverse, list_set, list_single, list_sort_with, list_swap,
|
list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with, list_swap,
|
||||||
};
|
};
|
||||||
use crate::llvm::build_str::{
|
use crate::llvm::build_str::{
|
||||||
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int,
|
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int,
|
||||||
|
@ -704,7 +704,8 @@ fn promote_to_main_function<'a, 'ctx, 'env>(
|
||||||
let main_fn_name = "$Test.main";
|
let main_fn_name = "$Test.main";
|
||||||
|
|
||||||
// Add main to the module.
|
// Add main to the module.
|
||||||
let main_fn = expose_function_to_host_help(env, main_fn_name, roc_main_fn, main_fn_name);
|
let main_fn =
|
||||||
|
expose_function_to_host_help_c_abi(env, main_fn_name, roc_main_fn, &[], main_fn_name);
|
||||||
|
|
||||||
(main_fn_name, main_fn)
|
(main_fn_name, main_fn)
|
||||||
}
|
}
|
||||||
|
@ -2304,32 +2305,6 @@ fn list_literal<'a, 'ctx, 'env>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrement_with_size_check<'a, 'ctx, 'env>(
|
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
|
||||||
parent: FunctionValue<'ctx>,
|
|
||||||
size: IntValue<'ctx>,
|
|
||||||
layout: Layout<'a>,
|
|
||||||
refcount_ptr: PointerToRefcount<'ctx>,
|
|
||||||
) {
|
|
||||||
let not_empty = env.context.append_basic_block(parent, "not_null");
|
|
||||||
|
|
||||||
let done = env.context.append_basic_block(parent, "done");
|
|
||||||
|
|
||||||
let is_empty =
|
|
||||||
env.builder
|
|
||||||
.build_int_compare(IntPredicate::EQ, size, size.get_type().const_zero(), "");
|
|
||||||
|
|
||||||
env.builder
|
|
||||||
.build_conditional_branch(is_empty, done, not_empty);
|
|
||||||
|
|
||||||
env.builder.position_at_end(not_empty);
|
|
||||||
|
|
||||||
refcount_ptr.decrement(env, &layout);
|
|
||||||
|
|
||||||
env.builder.build_unconditional_branch(done);
|
|
||||||
env.builder.position_at_end(done);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
layout_ids: &mut LayoutIds<'a>,
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
@ -2539,34 +2514,25 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
||||||
let (value, layout) = load_symbol_and_layout(scope, symbol);
|
let (value, layout) = load_symbol_and_layout(scope, symbol);
|
||||||
|
|
||||||
match layout {
|
match layout {
|
||||||
Layout::Builtin(Builtin::List(_)) => {
|
Layout::Builtin(Builtin::List(element_layout)) => {
|
||||||
debug_assert!(value.is_struct_value());
|
debug_assert!(value.is_struct_value());
|
||||||
|
let alignment = element_layout.alignment_bytes(env.ptr_bytes);
|
||||||
|
|
||||||
// because of how we insert DECREF for lists, we can't guarantee that
|
build_list::decref(env, value.into_struct_value(), alignment);
|
||||||
// the list is non-empty. When the list is empty, the pointer to the
|
|
||||||
// elements is NULL, and trying to get to the RC address will
|
|
||||||
// underflow, causing a segfault. Therefore, in this case we must
|
|
||||||
// manually check that the list is non-empty
|
|
||||||
let refcount_ptr = PointerToRefcount::from_list_wrapper(
|
|
||||||
env,
|
|
||||||
value.into_struct_value(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let length = list_len(env.builder, value.into_struct_value());
|
|
||||||
|
|
||||||
decrement_with_size_check(env, parent, length, *layout, refcount_ptr);
|
|
||||||
}
|
}
|
||||||
Layout::Builtin(Builtin::Dict(_, _)) | Layout::Builtin(Builtin::Set(_)) => {
|
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
|
||||||
debug_assert!(value.is_struct_value());
|
debug_assert!(value.is_struct_value());
|
||||||
|
let alignment = key_layout
|
||||||
|
.alignment_bytes(env.ptr_bytes)
|
||||||
|
.max(value_layout.alignment_bytes(env.ptr_bytes));
|
||||||
|
|
||||||
let refcount_ptr = PointerToRefcount::from_list_wrapper(
|
build_dict::decref(env, value.into_struct_value(), alignment);
|
||||||
env,
|
}
|
||||||
value.into_struct_value(),
|
Layout::Builtin(Builtin::Set(key_layout)) => {
|
||||||
);
|
debug_assert!(value.is_struct_value());
|
||||||
|
let alignment = key_layout.alignment_bytes(env.ptr_bytes);
|
||||||
|
|
||||||
let length = dict_len(env, scope, *symbol).into_int_value();
|
build_dict::decref(env, value.into_struct_value(), alignment);
|
||||||
|
|
||||||
decrement_with_size_check(env, parent, length, *layout, refcount_ptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ if layout.is_refcounted() => {
|
_ if layout.is_refcounted() => {
|
||||||
|
@ -3088,18 +3054,26 @@ fn expose_function_to_host<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
symbol: Symbol,
|
symbol: Symbol,
|
||||||
roc_function: FunctionValue<'ctx>,
|
roc_function: FunctionValue<'ctx>,
|
||||||
|
arguments: &[Layout<'a>],
|
||||||
) {
|
) {
|
||||||
// Assumption: there is only one specialization of a host-exposed function
|
// Assumption: there is only one specialization of a host-exposed function
|
||||||
let ident_string = symbol.as_str(&env.interns);
|
let ident_string = symbol.as_str(&env.interns);
|
||||||
let c_function_name: String = format!("roc__{}_1_exposed", ident_string);
|
let c_function_name: String = format!("roc__{}_1_exposed", ident_string);
|
||||||
|
|
||||||
expose_function_to_host_help(env, ident_string, roc_function, &c_function_name);
|
expose_function_to_host_help_c_abi(
|
||||||
|
env,
|
||||||
|
ident_string,
|
||||||
|
roc_function,
|
||||||
|
arguments,
|
||||||
|
&c_function_name,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expose_function_to_host_help<'a, 'ctx, 'env>(
|
fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
ident_string: &str,
|
ident_string: &str,
|
||||||
roc_function: FunctionValue<'ctx>,
|
roc_function: FunctionValue<'ctx>,
|
||||||
|
arguments: &[Layout<'a>],
|
||||||
c_function_name: &str,
|
c_function_name: &str,
|
||||||
) -> FunctionValue<'ctx> {
|
) -> FunctionValue<'ctx> {
|
||||||
let context = env.context;
|
let context = env.context;
|
||||||
|
@ -3112,8 +3086,14 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>(
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena);
|
||||||
|
for layout in arguments {
|
||||||
|
cc_argument_types.push(to_cc_type(env, layout));
|
||||||
|
}
|
||||||
|
|
||||||
// STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}`
|
// STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}`
|
||||||
let mut argument_types = roc_function.get_type().get_param_types();
|
// let mut argument_types = roc_function.get_type().get_param_types();
|
||||||
|
let mut argument_types = cc_argument_types;
|
||||||
let return_type = wrapper_return_type;
|
let return_type = wrapper_return_type;
|
||||||
let output_type = return_type.ptr_type(AddressSpace::Generic);
|
let output_type = return_type.ptr_type(AddressSpace::Generic);
|
||||||
argument_types.push(output_type.into());
|
argument_types.push(output_type.into());
|
||||||
|
@ -3146,22 +3126,45 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>(
|
||||||
let output_arg_index = args.len() - 1;
|
let output_arg_index = args.len() - 1;
|
||||||
let args = &args[..args.len() - 1];
|
let args = &args[..args.len() - 1];
|
||||||
|
|
||||||
|
let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena);
|
||||||
|
|
||||||
|
let it = args.iter().zip(roc_function.get_type().get_param_types());
|
||||||
|
for (arg, fastcc_type) in it {
|
||||||
|
let arg_type = arg.get_type();
|
||||||
|
if arg_type == fastcc_type {
|
||||||
|
// the C and Fast calling conventions agree
|
||||||
|
arguments_for_call.push(*arg);
|
||||||
|
} else {
|
||||||
|
let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type");
|
||||||
|
arguments_for_call.push(cast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let arguments_for_call = &arguments_for_call.into_bump_slice();
|
||||||
|
|
||||||
debug_assert_eq!(args.len(), roc_function.get_params().len());
|
debug_assert_eq!(args.len(), roc_function.get_params().len());
|
||||||
|
|
||||||
let call_result = {
|
let call_result = {
|
||||||
if env.is_gen_test {
|
if env.is_gen_test {
|
||||||
let roc_wrapper_function = make_exception_catcher(env, roc_function);
|
let roc_wrapper_function = make_exception_catcher(env, roc_function);
|
||||||
debug_assert_eq!(args.len(), roc_wrapper_function.get_params().len());
|
debug_assert_eq!(
|
||||||
|
arguments_for_call.len(),
|
||||||
|
roc_wrapper_function.get_params().len()
|
||||||
|
);
|
||||||
|
|
||||||
builder.position_at_end(entry);
|
builder.position_at_end(entry);
|
||||||
|
|
||||||
let call_wrapped =
|
let call_wrapped = builder.build_call(
|
||||||
builder.build_call(roc_wrapper_function, args, "call_wrapped_function");
|
roc_wrapper_function,
|
||||||
|
arguments_for_call,
|
||||||
|
"call_wrapped_function",
|
||||||
|
);
|
||||||
call_wrapped.set_call_convention(FAST_CALL_CONV);
|
call_wrapped.set_call_convention(FAST_CALL_CONV);
|
||||||
|
|
||||||
call_wrapped.try_as_basic_value().left().unwrap()
|
call_wrapped.try_as_basic_value().left().unwrap()
|
||||||
} else {
|
} else {
|
||||||
let call_unwrapped = builder.build_call(roc_function, args, "call_unwrapped_function");
|
let call_unwrapped =
|
||||||
|
builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function");
|
||||||
call_unwrapped.set_call_convention(FAST_CALL_CONV);
|
call_unwrapped.set_call_convention(FAST_CALL_CONV);
|
||||||
|
|
||||||
let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap();
|
let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap();
|
||||||
|
@ -3691,7 +3694,8 @@ fn build_proc_header<'a, 'ctx, 'env>(
|
||||||
fn_val.set_subprogram(subprogram);
|
fn_val.set_subprogram(subprogram);
|
||||||
|
|
||||||
if env.exposed_to_host.contains(&symbol) {
|
if env.exposed_to_host.contains(&symbol) {
|
||||||
expose_function_to_host(env, symbol, fn_val);
|
let arguments = Vec::from_iter_in(proc.args.iter().map(|(layout, _)| *layout), env.arena);
|
||||||
|
expose_function_to_host(env, symbol, fn_val, arguments.into_bump_slice());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn_val
|
fn_val
|
||||||
|
|
|
@ -844,3 +844,17 @@ fn dict_symbol_to_zig_dict<'a, 'ctx, 'env>(
|
||||||
)
|
)
|
||||||
.into_struct_value()
|
.into_struct_value()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decref<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
wrapper_struct: StructValue<'ctx>,
|
||||||
|
alignment: u32,
|
||||||
|
) {
|
||||||
|
let pointer = env
|
||||||
|
.builder
|
||||||
|
.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr")
|
||||||
|
.unwrap()
|
||||||
|
.into_pointer_value();
|
||||||
|
|
||||||
|
crate::llvm::refcounting::decref_pointer_check_null(env, pointer, alignment);
|
||||||
|
}
|
||||||
|
|
|
@ -1136,3 +1136,17 @@ pub fn store_list<'a, 'ctx, 'env>(
|
||||||
"cast_collection",
|
"cast_collection",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decref<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
wrapper_struct: StructValue<'ctx>,
|
||||||
|
alignment: u32,
|
||||||
|
) {
|
||||||
|
let (_, pointer) = load_list(
|
||||||
|
env.builder,
|
||||||
|
wrapper_struct,
|
||||||
|
env.context.i8_type().ptr_type(AddressSpace::Generic),
|
||||||
|
);
|
||||||
|
|
||||||
|
crate::llvm::refcounting::decref_pointer_check_null(env, pointer, alignment);
|
||||||
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_list_wrapper(env: &Env<'_, 'ctx, '_>, list_wrapper: StructValue<'ctx>) -> Self {
|
fn from_list_wrapper(env: &Env<'_, 'ctx, '_>, list_wrapper: StructValue<'ctx>) -> Self {
|
||||||
let data_ptr = env
|
let data_ptr = env
|
||||||
.builder
|
.builder
|
||||||
.build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr")
|
.build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr")
|
||||||
|
@ -102,7 +102,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
|
||||||
.build_int_compare(IntPredicate::EQ, current, one, "is_one")
|
.build_int_compare(IntPredicate::EQ, current, one, "is_one")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_refcount<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
|
fn get_refcount<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
|
||||||
env.builder
|
env.builder
|
||||||
.build_load(self.value, "get_refcount")
|
.build_load(self.value, "get_refcount")
|
||||||
.into_int_value()
|
.into_int_value()
|
||||||
|
@ -220,25 +220,57 @@ impl<'ctx> PointerToRefcount<'ctx> {
|
||||||
|
|
||||||
debug_info_init!(env, parent);
|
debug_info_init!(env, parent);
|
||||||
|
|
||||||
let alignment = env.context.i32_type().const_int(alignment as _, false);
|
decref_pointer(
|
||||||
|
|
||||||
call_void_bitcode_fn(
|
|
||||||
env,
|
env,
|
||||||
&[
|
parent.get_nth_param(0).unwrap().into_pointer_value(),
|
||||||
env.builder.build_bitcast(
|
alignment,
|
||||||
parent.get_nth_param(0).unwrap(),
|
|
||||||
env.ptr_int().ptr_type(AddressSpace::Generic),
|
|
||||||
"foo",
|
|
||||||
),
|
|
||||||
alignment.into(),
|
|
||||||
],
|
|
||||||
roc_builtins::bitcode::UTILS_DECREF,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
builder.build_return(None);
|
builder.build_return(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decref_pointer<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
pointer: PointerValue<'ctx>,
|
||||||
|
alignment: u32,
|
||||||
|
) {
|
||||||
|
let alignment = env.context.i32_type().const_int(alignment as _, false);
|
||||||
|
call_void_bitcode_fn(
|
||||||
|
env,
|
||||||
|
&[
|
||||||
|
env.builder.build_bitcast(
|
||||||
|
pointer,
|
||||||
|
env.ptr_int().ptr_type(AddressSpace::Generic),
|
||||||
|
"to_isize_ptr",
|
||||||
|
),
|
||||||
|
alignment.into(),
|
||||||
|
],
|
||||||
|
roc_builtins::bitcode::UTILS_DECREF,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assumes a pointer to the refcount
|
||||||
|
pub fn decref_pointer_check_null<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
pointer: PointerValue<'ctx>,
|
||||||
|
alignment: u32,
|
||||||
|
) {
|
||||||
|
let alignment = env.context.i32_type().const_int(alignment as _, false);
|
||||||
|
call_void_bitcode_fn(
|
||||||
|
env,
|
||||||
|
&[
|
||||||
|
env.builder.build_bitcast(
|
||||||
|
pointer,
|
||||||
|
env.context.i8_type().ptr_type(AddressSpace::Generic),
|
||||||
|
"to_i8_ptr",
|
||||||
|
),
|
||||||
|
alignment.into(),
|
||||||
|
],
|
||||||
|
roc_builtins::bitcode::UTILS_DECREF_CHECK_NULL,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn modify_refcount_struct<'a, 'ctx, 'env>(
|
fn modify_refcount_struct<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
layout_ids: &mut LayoutIds<'a>,
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
@ -1093,7 +1125,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
|
||||||
fn_val: FunctionValue<'ctx>,
|
fn_val: FunctionValue<'ctx>,
|
||||||
) {
|
) {
|
||||||
let tags = union_layout_tags(env.arena, &union_layout);
|
let tags = union_layout_tags(env.arena, &union_layout);
|
||||||
let is_nullable = union_layout.is_nullable();
|
|
||||||
debug_assert!(!tags.is_empty());
|
debug_assert!(!tags.is_empty());
|
||||||
|
|
||||||
let context = &env.context;
|
let context = &env.context;
|
||||||
|
@ -1126,7 +1157,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
|
||||||
let should_recurse_block = env.context.append_basic_block(parent, "should_recurse");
|
let should_recurse_block = env.context.append_basic_block(parent, "should_recurse");
|
||||||
|
|
||||||
let ctx = env.context;
|
let ctx = env.context;
|
||||||
if is_nullable {
|
if union_layout.is_nullable() {
|
||||||
let is_null = env.builder.build_is_null(value_ptr, "is_null");
|
let is_null = env.builder.build_is_null(value_ptr, "is_null");
|
||||||
|
|
||||||
let then_block = ctx.append_basic_block(parent, "then");
|
let then_block = ctx.append_basic_block(parent, "then");
|
||||||
|
@ -1201,6 +1232,12 @@ enum DecOrReuse {
|
||||||
Reuse,
|
Reuse,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fields_need_no_refcounting(field_layouts: &[Layout]) -> bool {
|
||||||
|
!field_layouts
|
||||||
|
.iter()
|
||||||
|
.any(|x| x.is_refcounted() || x.contains_refcounted())
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
@ -1220,21 +1257,6 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
||||||
let call_mode = mode_to_call_mode(decrement_fn, mode);
|
let call_mode = mode_to_call_mode(decrement_fn, mode);
|
||||||
let builder = env.builder;
|
let builder = env.builder;
|
||||||
|
|
||||||
// branches that are not/don't contain anything refcounted
|
|
||||||
// if there is only one branch, we don't need to switch
|
|
||||||
let switch_needed: bool = (|| {
|
|
||||||
for field_layouts in tags.iter() {
|
|
||||||
// if none of the fields are or contain anything refcounted, just move on
|
|
||||||
if !field_layouts
|
|
||||||
.iter()
|
|
||||||
.any(|x| x.is_refcounted() || x.contains_refcounted())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
})();
|
|
||||||
|
|
||||||
// next, make a jump table for all possible values of the tag_id
|
// next, make a jump table for all possible values of the tag_id
|
||||||
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
|
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
|
||||||
|
|
||||||
|
@ -1243,10 +1265,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
for (tag_id, field_layouts) in tags.iter().enumerate() {
|
for (tag_id, field_layouts) in tags.iter().enumerate() {
|
||||||
// if none of the fields are or contain anything refcounted, just move on
|
// if none of the fields are or contain anything refcounted, just move on
|
||||||
if !field_layouts
|
if fields_need_no_refcounting(field_layouts) {
|
||||||
.iter()
|
|
||||||
.any(|x| x.is_refcounted() || x.contains_refcounted())
|
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1346,11 +1365,13 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
cases.reverse();
|
cases.reverse();
|
||||||
|
|
||||||
if cases.len() == 1 && !switch_needed {
|
if matches!(
|
||||||
// there is only one tag in total; we don't need a switch
|
union_layout,
|
||||||
// this is essential for nullable unwrapped layouts,
|
UnionLayout::NullableUnwrapped { .. } | UnionLayout::NonNullableUnwrapped { .. }
|
||||||
// because the `else` branch below would try to read its
|
) {
|
||||||
// (nonexistant) tag id
|
debug_assert_eq!(cases.len(), 1);
|
||||||
|
|
||||||
|
// in this case, don't switch, because the `else` branch below would try to read the (nonexistant) tag id
|
||||||
let (_, only_branch) = cases.pop().unwrap();
|
let (_, only_branch) = cases.pop().unwrap();
|
||||||
env.builder.build_unconditional_branch(only_branch);
|
env.builder.build_unconditional_branch(only_branch);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1444,7 +1465,6 @@ fn build_reuse_rec_union_help<'a, 'ctx, 'env>(
|
||||||
dec_function: FunctionValue<'ctx>,
|
dec_function: FunctionValue<'ctx>,
|
||||||
) {
|
) {
|
||||||
let tags = union_layout_tags(env.arena, &union_layout);
|
let tags = union_layout_tags(env.arena, &union_layout);
|
||||||
let is_nullable = union_layout.is_nullable();
|
|
||||||
|
|
||||||
debug_assert!(!tags.is_empty());
|
debug_assert!(!tags.is_empty());
|
||||||
|
|
||||||
|
@ -1478,7 +1498,7 @@ fn build_reuse_rec_union_help<'a, 'ctx, 'env>(
|
||||||
let should_recurse_block = env.context.append_basic_block(parent, "should_recurse");
|
let should_recurse_block = env.context.append_basic_block(parent, "should_recurse");
|
||||||
|
|
||||||
let ctx = env.context;
|
let ctx = env.context;
|
||||||
if is_nullable {
|
if union_layout.is_nullable() {
|
||||||
let is_null = env.builder.build_is_null(value_ptr, "is_null");
|
let is_null = env.builder.build_is_null(value_ptr, "is_null");
|
||||||
|
|
||||||
let then_block = ctx.append_basic_block(parent, "then");
|
let then_block = ctx.append_basic_block(parent, "then");
|
||||||
|
@ -1700,68 +1720,3 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
|
||||||
// this function returns void
|
// this function returns void
|
||||||
builder.build_return(None);
|
builder.build_return(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refcount_is_one_comparison<'a, 'ctx, 'env>(
|
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
|
||||||
refcount: IntValue<'ctx>,
|
|
||||||
) -> IntValue<'ctx> {
|
|
||||||
env.builder.build_int_compare(
|
|
||||||
IntPredicate::EQ,
|
|
||||||
refcount,
|
|
||||||
refcount_1(env.context, env.ptr_bytes),
|
|
||||||
"refcount_one_check",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_get_refcount_ptr<'a, 'ctx, 'env>(
|
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
|
||||||
layout: &Layout<'a>,
|
|
||||||
list_wrapper: StructValue<'ctx>,
|
|
||||||
) -> PointerValue<'ctx> {
|
|
||||||
// fetch the pointer to the array data, as an integer
|
|
||||||
let ptr_as_int = env
|
|
||||||
.builder
|
|
||||||
.build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr")
|
|
||||||
.unwrap()
|
|
||||||
.into_int_value();
|
|
||||||
|
|
||||||
get_refcount_ptr_help(env, layout, ptr_as_int)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refcount_offset<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> u64 {
|
|
||||||
let value_bytes = layout.stack_size(env.ptr_bytes) as u64;
|
|
||||||
|
|
||||||
match layout {
|
|
||||||
Layout::Builtin(Builtin::List(_)) => env.ptr_bytes as u64,
|
|
||||||
Layout::Builtin(Builtin::Str) => env.ptr_bytes as u64,
|
|
||||||
Layout::RecursivePointer | Layout::Union(_) => env.ptr_bytes as u64,
|
|
||||||
_ => (env.ptr_bytes as u64).max(value_bytes),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_refcount_ptr_help<'a, 'ctx, 'env>(
|
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
|
||||||
layout: &Layout<'a>,
|
|
||||||
ptr_as_int: IntValue<'ctx>,
|
|
||||||
) -> PointerValue<'ctx> {
|
|
||||||
let builder = env.builder;
|
|
||||||
let ctx = env.context;
|
|
||||||
|
|
||||||
let offset = refcount_offset(env, layout);
|
|
||||||
|
|
||||||
// pointer to usize
|
|
||||||
let refcount_type = ptr_int(ctx, env.ptr_bytes);
|
|
||||||
|
|
||||||
// subtract offset, to access the refcount
|
|
||||||
let refcount_ptr = builder.build_int_sub(
|
|
||||||
ptr_as_int,
|
|
||||||
refcount_type.const_int(offset, false),
|
|
||||||
"make_refcount_ptr",
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_int_to_ptr(
|
|
||||||
refcount_ptr,
|
|
||||||
refcount_type.ptr_type(AddressSpace::Generic),
|
|
||||||
"get_refcount_ptr",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
use parity_wasm::builder;
|
use parity_wasm::builder;
|
||||||
use parity_wasm::builder::{CodeLocation, ModuleBuilder};
|
use parity_wasm::builder::{CodeLocation, ModuleBuilder};
|
||||||
use parity_wasm::elements::{Instruction, Instruction::*, Instructions, Local, ValueType};
|
use parity_wasm::elements::{
|
||||||
|
BlockType, Instruction, Instruction::*, Instructions, Local, ValueType,
|
||||||
|
};
|
||||||
|
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_module::low_level::LowLevel;
|
use roc_module::low_level::LowLevel;
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_mono::ir::{CallType, Expr, Literal, Proc, Stmt};
|
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
|
||||||
use roc_mono::layout::{Builtin, Layout};
|
use roc_mono::layout::{Builtin, Layout};
|
||||||
|
|
||||||
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone.
|
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone.
|
||||||
|
@ -21,7 +23,7 @@ struct LabelId(u32);
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SymbolStorage(LocalId, WasmLayout);
|
struct SymbolStorage(LocalId, WasmLayout);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct WasmLayout {
|
struct WasmLayout {
|
||||||
value_type: ValueType,
|
value_type: ValueType,
|
||||||
stack_memory: u32,
|
stack_memory: u32,
|
||||||
|
@ -30,6 +32,12 @@ struct WasmLayout {
|
||||||
impl WasmLayout {
|
impl WasmLayout {
|
||||||
fn new(layout: &Layout) -> Result<Self, String> {
|
fn new(layout: &Layout) -> Result<Self, String> {
|
||||||
match layout {
|
match layout {
|
||||||
|
Layout::Builtin(Builtin::Int1 | Builtin::Int8 | Builtin::Int16 | Builtin::Int32) => {
|
||||||
|
Ok(Self {
|
||||||
|
value_type: ValueType::I32,
|
||||||
|
stack_memory: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
Layout::Builtin(Builtin::Int64) => Ok(Self {
|
Layout::Builtin(Builtin::Int64) => Ok(Self {
|
||||||
value_type: ValueType::I64,
|
value_type: ValueType::I64,
|
||||||
stack_memory: 0,
|
stack_memory: 0,
|
||||||
|
@ -61,7 +69,9 @@ pub struct WasmBackend<'a> {
|
||||||
// Functions: internal state & IR mappings
|
// Functions: internal state & IR mappings
|
||||||
stack_memory: u32,
|
stack_memory: u32,
|
||||||
symbol_storage_map: MutMap<Symbol, SymbolStorage>,
|
symbol_storage_map: MutMap<Symbol, SymbolStorage>,
|
||||||
// joinpoint_label_map: MutMap<JoinPointId, LabelId>,
|
/// how many blocks deep are we (used for jumps)
|
||||||
|
block_depth: u32,
|
||||||
|
joinpoint_label_map: MutMap<JoinPointId, (u32, std::vec::Vec<LocalId>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> WasmBackend<'a> {
|
impl<'a> WasmBackend<'a> {
|
||||||
|
@ -84,7 +94,8 @@ impl<'a> WasmBackend<'a> {
|
||||||
// Functions: internal state & IR mappings
|
// Functions: internal state & IR mappings
|
||||||
stack_memory: 0,
|
stack_memory: 0,
|
||||||
symbol_storage_map: MutMap::default(),
|
symbol_storage_map: MutMap::default(),
|
||||||
// joinpoint_label_map: MutMap::default(),
|
block_depth: 0,
|
||||||
|
joinpoint_label_map: MutMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +185,27 @@ impl<'a> WasmBackend<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// start a loop that leaves a value on the stack
|
||||||
|
fn start_loop_with_return(&mut self, value_type: ValueType) {
|
||||||
|
self.block_depth += 1;
|
||||||
|
|
||||||
|
// self.instructions.push(Loop(BlockType::NoResult));
|
||||||
|
self.instructions.push(Loop(BlockType::Value(value_type)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_block(&mut self) {
|
||||||
|
self.block_depth += 1;
|
||||||
|
|
||||||
|
// Our blocks always end with a `return` or `br`,
|
||||||
|
// so they never leave extra values on the stack
|
||||||
|
self.instructions.push(Block(BlockType::NoResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_block(&mut self) {
|
||||||
|
self.block_depth -= 1;
|
||||||
|
self.instructions.push(End);
|
||||||
|
}
|
||||||
|
|
||||||
fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
|
fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
|
||||||
match stmt {
|
match stmt {
|
||||||
// This pattern is a simple optimisation to get rid of one local and two instructions per proc.
|
// This pattern is a simple optimisation to get rid of one local and two instructions per proc.
|
||||||
|
@ -207,6 +239,113 @@ impl<'a> WasmBackend<'a> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stmt::Switch {
|
||||||
|
cond_symbol,
|
||||||
|
cond_layout: _,
|
||||||
|
branches,
|
||||||
|
default_branch,
|
||||||
|
ret_layout: _,
|
||||||
|
} => {
|
||||||
|
// NOTE currently implemented as a series of conditional jumps
|
||||||
|
// We may be able to improve this in the future with `Select`
|
||||||
|
// or `BrTable`
|
||||||
|
|
||||||
|
// create (number_of_branches - 1) new blocks.
|
||||||
|
for _ in 0..branches.len() {
|
||||||
|
self.start_block()
|
||||||
|
}
|
||||||
|
|
||||||
|
// the LocalId of the symbol that we match on
|
||||||
|
let matched_on = match self.symbol_storage_map.get(cond_symbol) {
|
||||||
|
Some(SymbolStorage(local_id, _)) => local_id.0,
|
||||||
|
None => unreachable!("symbol not defined: {:?}", cond_symbol),
|
||||||
|
};
|
||||||
|
|
||||||
|
// then, we jump whenever the value under scrutiny is equal to the value of a branch
|
||||||
|
for (i, (value, _, _)) in branches.iter().enumerate() {
|
||||||
|
// put the cond_symbol on the top of the stack
|
||||||
|
self.instructions.push(GetLocal(matched_on));
|
||||||
|
|
||||||
|
self.instructions.push(I32Const(*value as i32));
|
||||||
|
|
||||||
|
// compare the 2 topmost values
|
||||||
|
self.instructions.push(I32Eq);
|
||||||
|
|
||||||
|
// "break" out of `i` surrounding blocks
|
||||||
|
self.instructions.push(BrIf(i as u32));
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we never jumped because a value matched, we're in the default case
|
||||||
|
self.build_stmt(default_branch.1, ret_layout)?;
|
||||||
|
|
||||||
|
// now put in the actual body of each branch in order
|
||||||
|
// (the first branch would have broken out of 1 block,
|
||||||
|
// hence we must generate its code first)
|
||||||
|
for (_, _, branch) in branches.iter() {
|
||||||
|
self.end_block();
|
||||||
|
|
||||||
|
self.build_stmt(branch, ret_layout)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Stmt::Join {
|
||||||
|
id,
|
||||||
|
parameters,
|
||||||
|
body,
|
||||||
|
remainder,
|
||||||
|
} => {
|
||||||
|
// make locals for join pointer parameters
|
||||||
|
let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len());
|
||||||
|
for parameter in parameters.iter() {
|
||||||
|
let wasm_layout = WasmLayout::new(¶meter.layout)?;
|
||||||
|
let local_id = self.insert_local(wasm_layout, parameter.symbol);
|
||||||
|
|
||||||
|
jp_parameter_local_ids.push(local_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.start_block();
|
||||||
|
|
||||||
|
self.joinpoint_label_map
|
||||||
|
.insert(*id, (self.block_depth, jp_parameter_local_ids));
|
||||||
|
|
||||||
|
self.build_stmt(remainder, ret_layout)?;
|
||||||
|
|
||||||
|
self.end_block();
|
||||||
|
|
||||||
|
// A `return` inside of a `loop` seems to make it so that the `loop` itself
|
||||||
|
// also "returns" (so, leaves on the stack) a value of the return type.
|
||||||
|
let return_wasm_layout = WasmLayout::new(ret_layout)?;
|
||||||
|
self.start_loop_with_return(return_wasm_layout.value_type);
|
||||||
|
|
||||||
|
self.build_stmt(body, ret_layout)?;
|
||||||
|
|
||||||
|
// ends the loop
|
||||||
|
self.end_block();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Stmt::Jump(id, arguments) => {
|
||||||
|
let (target, locals) = &self.joinpoint_label_map[id];
|
||||||
|
|
||||||
|
// put the arguments on the stack
|
||||||
|
for (symbol, local_id) in arguments.iter().zip(locals.iter()) {
|
||||||
|
let argument = match self.symbol_storage_map.get(symbol) {
|
||||||
|
Some(SymbolStorage(local_id, _)) => local_id.0,
|
||||||
|
None => unreachable!("symbol not defined: {:?}", symbol),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.instructions.push(GetLocal(argument));
|
||||||
|
self.instructions.push(SetLocal(local_id.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// jump
|
||||||
|
let levels = self.block_depth - target;
|
||||||
|
self.instructions.push(Br(levels));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
x => Err(format!("statement not yet implemented: {:?}", x)),
|
x => Err(format!("statement not yet implemented: {:?}", x)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,7 +357,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
layout: &Layout<'a>,
|
layout: &Layout<'a>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Literal(lit) => self.load_literal(lit),
|
Expr::Literal(lit) => self.load_literal(lit, layout),
|
||||||
|
|
||||||
Expr::Call(roc_mono::ir::Call {
|
Expr::Call(roc_mono::ir::Call {
|
||||||
call_type,
|
call_type,
|
||||||
|
@ -246,10 +385,26 @@ impl<'a> WasmBackend<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_literal(&mut self, lit: &Literal<'a>) -> Result<(), String> {
|
fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> {
|
||||||
match lit {
|
match lit {
|
||||||
|
Literal::Bool(x) => {
|
||||||
|
self.instructions.push(I32Const(*x as i32));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Literal::Byte(x) => {
|
||||||
|
self.instructions.push(I32Const(*x as i32));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Literal::Int(x) => {
|
Literal::Int(x) => {
|
||||||
self.instructions.push(I64Const(*x as i64));
|
match layout {
|
||||||
|
Layout::Builtin(Builtin::Int32) => {
|
||||||
|
self.instructions.push(I32Const(*x as i32));
|
||||||
|
}
|
||||||
|
Layout::Builtin(Builtin::Int64) => {
|
||||||
|
self.instructions.push(I64Const(*x as i64));
|
||||||
|
}
|
||||||
|
x => panic!("loading literal, {:?}, is not yet implemented", x),
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Literal::Float(x) => {
|
Literal::Float(x) => {
|
||||||
|
@ -293,6 +448,22 @@ impl<'a> WasmBackend<'a> {
|
||||||
ValueType::F32 => &[F32Add],
|
ValueType::F32 => &[F32Add],
|
||||||
ValueType::F64 => &[F64Add],
|
ValueType::F64 => &[F64Add],
|
||||||
},
|
},
|
||||||
|
LowLevel::NumSub => match return_value_type {
|
||||||
|
ValueType::I32 => &[I32Sub],
|
||||||
|
ValueType::I64 => &[I64Sub],
|
||||||
|
ValueType::F32 => &[F32Sub],
|
||||||
|
ValueType::F64 => &[F64Sub],
|
||||||
|
},
|
||||||
|
LowLevel::NumMul => match return_value_type {
|
||||||
|
ValueType::I32 => &[I32Mul],
|
||||||
|
ValueType::I64 => &[I64Mul],
|
||||||
|
ValueType::F32 => &[F32Mul],
|
||||||
|
ValueType::F64 => &[F64Mul],
|
||||||
|
},
|
||||||
|
LowLevel::NumGt => {
|
||||||
|
// needs layout of the argument to be implemented fully
|
||||||
|
&[I32GtS]
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!("unsupported low-level op {:?}", lowlevel));
|
return Err(format!("unsupported low-level op {:?}", lowlevel));
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,19 @@ pub fn build_module<'a>(
|
||||||
let mut backend = WasmBackend::new();
|
let mut backend = WasmBackend::new();
|
||||||
let mut layout_ids = LayoutIds::default();
|
let mut layout_ids = LayoutIds::default();
|
||||||
|
|
||||||
|
// Sort procedures by occurrence order
|
||||||
|
//
|
||||||
|
// We sort by the "name", but those are interned strings, and the name that is
|
||||||
|
// interned first will have a lower number.
|
||||||
|
//
|
||||||
|
// But, the name that occurs first is always `main` because it is in the (implicit)
|
||||||
|
// file header. Therefore sorting high to low will put other functions before main
|
||||||
|
//
|
||||||
|
// This means that for now other functions in the file have to be ordered "in reverse": if A
|
||||||
|
// uses B, then the name of A must first occur after the first occurrence of the name of B
|
||||||
|
let mut procedures: std::vec::Vec<_> = procedures.into_iter().collect();
|
||||||
|
procedures.sort_by(|a, b| b.0 .0.cmp(&a.0 .0));
|
||||||
|
|
||||||
for ((sym, layout), proc) in procedures {
|
for ((sym, layout), proc) in procedures {
|
||||||
let function_index = backend.build_proc(proc, sym)?;
|
let function_index = backend.build_proc(proc, sym)?;
|
||||||
if env.exposed_to_host.contains(&sym) {
|
if env.exposed_to_host.contains(&sym) {
|
||||||
|
|
|
@ -147,6 +147,7 @@ where
|
||||||
Err(e) => Err(format!("{:?}", e)),
|
Err(e) => Err(format!("{:?}", e)),
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
let integer = match result[0] {
|
let integer = match result[0] {
|
||||||
|
wasmer::Value::I32(a) => a as i64,
|
||||||
wasmer::Value::I64(a) => a,
|
wasmer::Value::I64(a) => a,
|
||||||
wasmer::Value::F64(a) => a.to_bits() as i64,
|
wasmer::Value::F64(a) => a.to_bits() as i64,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
|
|
@ -49,6 +49,111 @@ mod dev_num {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_then_else() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
cond : Bool
|
||||||
|
cond = True
|
||||||
|
|
||||||
|
if cond then
|
||||||
|
0
|
||||||
|
else
|
||||||
|
1
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rgb_red() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when Red is
|
||||||
|
Red -> 111
|
||||||
|
Green -> 222
|
||||||
|
Blue -> 333
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
111,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rgb_green() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when Green is
|
||||||
|
Red -> 111
|
||||||
|
Green -> 222
|
||||||
|
Blue -> 333
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
222,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rgb_blue() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when Blue is
|
||||||
|
Red -> 111
|
||||||
|
Green -> 222
|
||||||
|
Blue -> 333
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
333,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn join_point() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x = if True then 111 else 222
|
||||||
|
|
||||||
|
x + 123
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
234,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn factorial() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app "test" provides [ main ] to "./platform"
|
||||||
|
|
||||||
|
fac : I32, I32 -> I32
|
||||||
|
fac = \n, accum ->
|
||||||
|
if n > 1 then
|
||||||
|
fac (n - 1) (n * accum)
|
||||||
|
else
|
||||||
|
accum
|
||||||
|
|
||||||
|
main : I32
|
||||||
|
main = fac 8 1
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
40_320,
|
||||||
|
i32
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn gen_add_f64() {
|
// fn gen_add_f64() {
|
||||||
// assert_evals_to!(
|
// assert_evals_to!(
|
||||||
|
|
|
@ -723,7 +723,21 @@ pub struct MonomorphizedModule<'a> {
|
||||||
|
|
||||||
impl<'a> MonomorphizedModule<'a> {
|
impl<'a> MonomorphizedModule<'a> {
|
||||||
pub fn total_problems(&self) -> usize {
|
pub fn total_problems(&self) -> usize {
|
||||||
self.can_problems.len() + self.type_problems.len() + self.mono_problems.len()
|
let mut total = 0;
|
||||||
|
|
||||||
|
for problems in self.can_problems.values() {
|
||||||
|
total += problems.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
for problems in self.type_problems.values() {
|
||||||
|
total += problems.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
for problems in self.mono_problems.values() {
|
||||||
|
total += problems.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
total
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3927,7 +3941,7 @@ fn make_specializations<'a>(
|
||||||
);
|
);
|
||||||
|
|
||||||
let external_specializations_requested = procs.externals_we_need.clone();
|
let external_specializations_requested = procs.externals_we_need.clone();
|
||||||
let procedures = procs.get_specialized_procs_without_rc(mono_env.arena);
|
let procedures = procs.get_specialized_procs_without_rc(&mut mono_env);
|
||||||
|
|
||||||
let make_specializations_end = SystemTime::now();
|
let make_specializations_end = SystemTime::now();
|
||||||
module_timing.make_specializations = make_specializations_end
|
module_timing.make_specializations = make_specializations_end
|
||||||
|
|
|
@ -272,6 +272,33 @@ impl<'a> Proc<'a> {
|
||||||
proc.body = b.clone();
|
proc.body = b.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_tail_recursive(&mut self, env: &mut Env<'a, '_>) {
|
||||||
|
let mut args = Vec::with_capacity_in(self.args.len(), env.arena);
|
||||||
|
let mut proc_args = Vec::with_capacity_in(self.args.len(), env.arena);
|
||||||
|
|
||||||
|
for (layout, symbol) in self.args {
|
||||||
|
let new = env.unique_symbol();
|
||||||
|
args.push((*layout, *symbol, new));
|
||||||
|
proc_args.push((*layout, new));
|
||||||
|
}
|
||||||
|
|
||||||
|
use self::SelfRecursive::*;
|
||||||
|
if let SelfRecursive(id) = self.is_self_recursive {
|
||||||
|
let transformed = crate::tail_recursion::make_tail_recursive(
|
||||||
|
env.arena,
|
||||||
|
id,
|
||||||
|
self.name,
|
||||||
|
self.body.clone(),
|
||||||
|
args.into_bump_slice(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(with_tco) = transformed {
|
||||||
|
self.body = with_tco;
|
||||||
|
self.args = proc_args.into_bump_slice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -350,7 +377,7 @@ pub enum InProgressProc<'a> {
|
||||||
impl<'a> Procs<'a> {
|
impl<'a> Procs<'a> {
|
||||||
pub fn get_specialized_procs_without_rc(
|
pub fn get_specialized_procs_without_rc(
|
||||||
self,
|
self,
|
||||||
arena: &'a Bump,
|
env: &mut Env<'a, '_>,
|
||||||
) -> MutMap<(Symbol, ProcLayout<'a>), Proc<'a>> {
|
) -> MutMap<(Symbol, ProcLayout<'a>), Proc<'a>> {
|
||||||
let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
|
let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
|
||||||
|
|
||||||
|
@ -376,16 +403,7 @@ impl<'a> Procs<'a> {
|
||||||
panic!();
|
panic!();
|
||||||
}
|
}
|
||||||
Done(mut proc) => {
|
Done(mut proc) => {
|
||||||
use self::SelfRecursive::*;
|
proc.make_tail_recursive(env);
|
||||||
if let SelfRecursive(id) = proc.is_self_recursive {
|
|
||||||
proc.body = crate::tail_recursion::make_tail_recursive(
|
|
||||||
arena,
|
|
||||||
id,
|
|
||||||
proc.name,
|
|
||||||
proc.body.clone(),
|
|
||||||
proc.args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.insert(key, proc);
|
result.insert(key, proc);
|
||||||
}
|
}
|
||||||
|
@ -395,86 +413,6 @@ impl<'a> Procs<'a> {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO investigate make this an iterator?
|
|
||||||
pub fn get_specialized_procs(
|
|
||||||
self,
|
|
||||||
arena: &'a Bump,
|
|
||||||
) -> MutMap<(Symbol, ProcLayout<'a>), Proc<'a>> {
|
|
||||||
let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
|
|
||||||
|
|
||||||
for ((s, toplevel), in_prog_proc) in self.specialized.into_iter() {
|
|
||||||
match in_prog_proc {
|
|
||||||
InProgress => unreachable!(
|
|
||||||
"The procedure {:?} should have be done by now",
|
|
||||||
(s, toplevel)
|
|
||||||
),
|
|
||||||
Done(proc) => {
|
|
||||||
result.insert((s, toplevel), proc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (_, proc) in result.iter_mut() {
|
|
||||||
use self::SelfRecursive::*;
|
|
||||||
if let SelfRecursive(id) = proc.is_self_recursive {
|
|
||||||
proc.body = crate::tail_recursion::make_tail_recursive(
|
|
||||||
arena,
|
|
||||||
id,
|
|
||||||
proc.name,
|
|
||||||
proc.body.clone(),
|
|
||||||
proc.args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
|
|
||||||
|
|
||||||
crate::inc_dec::visit_procs(arena, borrow_params, &mut result);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_specialized_procs_help(
|
|
||||||
self,
|
|
||||||
arena: &'a Bump,
|
|
||||||
) -> (
|
|
||||||
MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
|
||||||
&'a crate::borrow::ParamMap<'a>,
|
|
||||||
) {
|
|
||||||
let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
|
|
||||||
|
|
||||||
for ((s, toplevel), in_prog_proc) in self.specialized.into_iter() {
|
|
||||||
match in_prog_proc {
|
|
||||||
InProgress => unreachable!(
|
|
||||||
"The procedure {:?} should have be done by now",
|
|
||||||
(s, toplevel)
|
|
||||||
),
|
|
||||||
Done(proc) => {
|
|
||||||
result.insert((s, toplevel), proc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (_, proc) in result.iter_mut() {
|
|
||||||
use self::SelfRecursive::*;
|
|
||||||
if let SelfRecursive(id) = proc.is_self_recursive {
|
|
||||||
proc.body = crate::tail_recursion::make_tail_recursive(
|
|
||||||
arena,
|
|
||||||
id,
|
|
||||||
proc.name,
|
|
||||||
proc.body.clone(),
|
|
||||||
proc.args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
|
|
||||||
|
|
||||||
crate::inc_dec::visit_procs(arena, borrow_params, &mut result);
|
|
||||||
|
|
||||||
(result, borrow_params)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO trim down these arguments!
|
// TODO trim down these arguments!
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn insert_named(
|
pub fn insert_named(
|
||||||
|
@ -3748,13 +3686,15 @@ pub fn with_hole<'a>(
|
||||||
|
|
||||||
match what_to_do {
|
match what_to_do {
|
||||||
UpdateExisting(field) => {
|
UpdateExisting(field) => {
|
||||||
|
substitute_in_exprs(env.arena, &mut stmt, assigned, symbols[0]);
|
||||||
|
|
||||||
stmt = assign_to_symbol(
|
stmt = assign_to_symbol(
|
||||||
env,
|
env,
|
||||||
procs,
|
procs,
|
||||||
layout_cache,
|
layout_cache,
|
||||||
field.var,
|
field.var,
|
||||||
*field.loc_expr.clone(),
|
*field.loc_expr.clone(),
|
||||||
assigned,
|
symbols[0],
|
||||||
stmt,
|
stmt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,16 +33,16 @@ pub fn make_tail_recursive<'a>(
|
||||||
id: JoinPointId,
|
id: JoinPointId,
|
||||||
needle: Symbol,
|
needle: Symbol,
|
||||||
stmt: Stmt<'a>,
|
stmt: Stmt<'a>,
|
||||||
args: &'a [(Layout<'a>, Symbol)],
|
args: &'a [(Layout<'a>, Symbol, Symbol)],
|
||||||
) -> Stmt<'a> {
|
) -> Option<Stmt<'a>> {
|
||||||
let allocated = arena.alloc(stmt);
|
let allocated = arena.alloc(stmt);
|
||||||
match insert_jumps(arena, allocated, id, needle) {
|
match insert_jumps(arena, allocated, id, needle) {
|
||||||
None => allocated.clone(),
|
None => None,
|
||||||
Some(new) => {
|
Some(new) => {
|
||||||
// jumps were inserted, we must now add a join point
|
// jumps were inserted, we must now add a join point
|
||||||
|
|
||||||
let params = Vec::from_iter_in(
|
let params = Vec::from_iter_in(
|
||||||
args.iter().map(|(layout, symbol)| Param {
|
args.iter().map(|(layout, symbol, _)| Param {
|
||||||
symbol: *symbol,
|
symbol: *symbol,
|
||||||
layout: *layout,
|
layout: *layout,
|
||||||
borrow: true,
|
borrow: true,
|
||||||
|
@ -52,16 +52,18 @@ pub fn make_tail_recursive<'a>(
|
||||||
.into_bump_slice();
|
.into_bump_slice();
|
||||||
|
|
||||||
// TODO could this be &[]?
|
// TODO could this be &[]?
|
||||||
let args = Vec::from_iter_in(args.iter().map(|t| t.1), arena).into_bump_slice();
|
let args = Vec::from_iter_in(args.iter().map(|t| t.2), arena).into_bump_slice();
|
||||||
|
|
||||||
let jump = arena.alloc(Stmt::Jump(id, args));
|
let jump = arena.alloc(Stmt::Jump(id, args));
|
||||||
|
|
||||||
Stmt::Join {
|
let join = Stmt::Join {
|
||||||
id,
|
id,
|
||||||
remainder: jump,
|
remainder: jump,
|
||||||
parameters: params,
|
parameters: params,
|
||||||
body: new,
|
body: new,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
Some(join)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -889,3 +889,26 @@ fn blue_and_absent() {
|
||||||
i64
|
i64
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_the_only_field() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
Model : { foo : I64 }
|
||||||
|
|
||||||
|
model : Model
|
||||||
|
model = { foo: 3 }
|
||||||
|
|
||||||
|
foo = 4
|
||||||
|
|
||||||
|
newModel : Model
|
||||||
|
newModel = { model & foo }
|
||||||
|
|
||||||
|
newModel.foo
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
4,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ procedure Num.26 (#Attr.2, #Attr.3):
|
||||||
let Test.12 = lowlevel NumMul #Attr.2 #Attr.3;
|
let Test.12 = lowlevel NumMul #Attr.2 #Attr.3;
|
||||||
ret Test.12;
|
ret Test.12;
|
||||||
|
|
||||||
procedure Test.1 (Test.2, Test.3):
|
procedure Test.1 (Test.17, Test.18):
|
||||||
joinpoint Test.7 Test.2 Test.3:
|
joinpoint Test.7 Test.2 Test.3:
|
||||||
let Test.15 = 0i64;
|
let Test.15 = 0i64;
|
||||||
let Test.16 = lowlevel Eq Test.15 Test.2;
|
let Test.16 = lowlevel Eq Test.15 Test.2;
|
||||||
|
@ -18,7 +18,7 @@ procedure Test.1 (Test.2, Test.3):
|
||||||
let Test.11 = CallByName Num.26 Test.2 Test.3;
|
let Test.11 = CallByName Num.26 Test.2 Test.3;
|
||||||
jump Test.7 Test.10 Test.11;
|
jump Test.7 Test.10 Test.11;
|
||||||
in
|
in
|
||||||
jump Test.7 Test.2 Test.3;
|
jump Test.7 Test.17 Test.18;
|
||||||
|
|
||||||
procedure Test.0 ():
|
procedure Test.0 ():
|
||||||
let Test.5 = 10i64;
|
let Test.5 = 10i64;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
procedure Test.3 (Test.4):
|
procedure Test.3 (Test.29):
|
||||||
joinpoint Test.13 Test.4:
|
joinpoint Test.13 Test.4:
|
||||||
let Test.23 = 1i64;
|
let Test.23 = 1i64;
|
||||||
let Test.24 = GetTagId Test.4;
|
let Test.24 = GetTagId Test.4;
|
||||||
|
@ -18,7 +18,7 @@ procedure Test.3 (Test.4):
|
||||||
let Test.7 = UnionAtIndex (Id 0) (Index 1) Test.4;
|
let Test.7 = UnionAtIndex (Id 0) (Index 1) Test.4;
|
||||||
jump Test.13 Test.7;
|
jump Test.13 Test.7;
|
||||||
in
|
in
|
||||||
jump Test.13 Test.4;
|
jump Test.13 Test.29;
|
||||||
|
|
||||||
procedure Test.0 ():
|
procedure Test.0 ():
|
||||||
let Test.28 = 3i64;
|
let Test.28 = 3i64;
|
||||||
|
|
|
@ -10,7 +10,7 @@ procedure Num.27 (#Attr.2, #Attr.3):
|
||||||
let Test.26 = lowlevel NumLt #Attr.2 #Attr.3;
|
let Test.26 = lowlevel NumLt #Attr.2 #Attr.3;
|
||||||
ret Test.26;
|
ret Test.26;
|
||||||
|
|
||||||
procedure Test.1 (Test.2, Test.3, Test.4):
|
procedure Test.1 (Test.29, Test.30, Test.31):
|
||||||
joinpoint Test.12 Test.2 Test.3 Test.4:
|
joinpoint Test.12 Test.2 Test.3 Test.4:
|
||||||
let Test.14 = CallByName Num.27 Test.3 Test.4;
|
let Test.14 = CallByName Num.27 Test.3 Test.4;
|
||||||
if Test.14 then
|
if Test.14 then
|
||||||
|
@ -29,7 +29,7 @@ procedure Test.1 (Test.2, Test.3, Test.4):
|
||||||
else
|
else
|
||||||
ret Test.2;
|
ret Test.2;
|
||||||
in
|
in
|
||||||
jump Test.12 Test.2 Test.3 Test.4;
|
jump Test.12 Test.29 Test.30 Test.31;
|
||||||
|
|
||||||
procedure Test.0 ():
|
procedure Test.0 ():
|
||||||
let Test.9 = Array [];
|
let Test.9 = Array [];
|
||||||
|
|
|
@ -77,12 +77,14 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
||||||
const Unit = extern struct {};
|
const Unit = extern struct {};
|
||||||
|
|
||||||
pub fn main() u8 {
|
pub fn main() u8 {
|
||||||
|
const allocator = std.heap.page_allocator;
|
||||||
|
|
||||||
const size = @intCast(usize, roc__mainForHost_size());
|
const size = @intCast(usize, roc__mainForHost_size());
|
||||||
const raw_output = std.heap.c_allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
|
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
|
||||||
var output = @ptrCast([*]u8, raw_output);
|
var output = @ptrCast([*]u8, raw_output);
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
std.heap.c_allocator.free(raw_output);
|
allocator.free(raw_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
var ts1: std.os.timespec = undefined;
|
var ts1: std.os.timespec = undefined;
|
||||||
|
@ -122,12 +124,14 @@ fn to_seconds(tms: std.os.timespec) f64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call_the_closure(closure_data_pointer: [*]u8) void {
|
fn call_the_closure(closure_data_pointer: [*]u8) void {
|
||||||
|
const allocator = std.heap.page_allocator;
|
||||||
|
|
||||||
const size = roc__mainForHost_1_Fx_result_size();
|
const size = roc__mainForHost_1_Fx_result_size();
|
||||||
const raw_output = std.heap.c_allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
|
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
|
||||||
var output = @ptrCast([*]u8, raw_output);
|
var output = @ptrCast([*]u8, raw_output);
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
std.heap.c_allocator.free(raw_output);
|
allocator.free(raw_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
const flags: u8 = 0;
|
const flags: u8 = 0;
|
||||||
|
|
|
@ -22,23 +22,45 @@ const Allocator = mem.Allocator;
|
||||||
|
|
||||||
extern fn roc__mainForHost_1_exposed(RocList, *RocCallResult) void;
|
extern fn roc__mainForHost_1_exposed(RocList, *RocCallResult) void;
|
||||||
|
|
||||||
extern fn malloc(size: usize) callconv(.C) ?*c_void;
|
const Align = extern struct { a: usize, b: usize };
|
||||||
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
|
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
|
||||||
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void;
|
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void;
|
||||||
|
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
|
||||||
|
|
||||||
|
const DEBUG: bool = false;
|
||||||
|
|
||||||
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
|
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
|
||||||
return malloc(size);
|
if (DEBUG) {
|
||||||
|
var ptr = malloc(size);
|
||||||
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
|
||||||
|
return ptr;
|
||||||
|
} else {
|
||||||
|
return malloc(size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
|
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
|
||||||
return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size);
|
if (DEBUG) {
|
||||||
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return realloc(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)), new_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
|
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
|
||||||
free(@alignCast(16, @ptrCast([*]u8, c_ptr)));
|
if (DEBUG) {
|
||||||
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)));
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
||||||
|
_ = tag_id;
|
||||||
|
|
||||||
const stderr = std.io.getStdErr().writer();
|
const stderr = std.io.getStdErr().writer();
|
||||||
const msg = @ptrCast([*:0]const u8, c_ptr);
|
const msg = @ptrCast([*:0]const u8, c_ptr);
|
||||||
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
|
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
|
||||||
|
@ -50,11 +72,11 @@ const NUM_NUMS = 100;
|
||||||
|
|
||||||
const RocList = extern struct { elements: [*]i64, length: usize };
|
const RocList = extern struct { elements: [*]i64, length: usize };
|
||||||
|
|
||||||
const RocCallResult = extern struct { flag: usize, content: RocList };
|
const RocCallResult = extern struct { flag: u64, content: RocList };
|
||||||
|
|
||||||
const Unit = extern struct {};
|
const Unit = extern struct {};
|
||||||
|
|
||||||
pub export fn main() i32 {
|
pub export fn main() u8 {
|
||||||
const stdout = std.io.getStdOut().writer();
|
const stdout = std.io.getStdOut().writer();
|
||||||
const stderr = std.io.getStdErr().writer();
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
|
||||||
|
@ -65,7 +87,7 @@ pub export fn main() i32 {
|
||||||
|
|
||||||
var numbers = raw_numbers[1..];
|
var numbers = raw_numbers[1..];
|
||||||
|
|
||||||
for (numbers) |x, i| {
|
for (numbers) |_, i| {
|
||||||
numbers[i] = @mod(@intCast(i64, i), 12);
|
numbers[i] = @mod(@intCast(i64, i), 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue