Merge branch 'trunk' into linker

This commit is contained in:
Brendan Hansknecht 2021-09-11 22:55:07 -07:00
commit 1d23f4c0d2
28 changed files with 683 additions and 324 deletions

19
Cargo.lock generated
View file

@ -2036,6 +2036,15 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "libmimalloc-sys"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1b8479c593dba88c2741fc50b92e13dbabbbe0bd504d979f244ccc1a5b1c01"
dependencies = [
"cc",
]
[[package]]
name = "linked-hash-map"
version = "0.5.4"
@ -2189,6 +2198,15 @@ dependencies = [
"objc",
]
[[package]]
name = "mimalloc"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb74897ce508e6c49156fd1476fc5922cbc6e75183c65e399c765a09122e5130"
dependencies = [
"libmimalloc-sys",
]
[[package]]
name = "minimal-lexical"
version = "0.1.3"
@ -3452,6 +3470,7 @@ dependencies = [
"libc",
"libloading 0.6.7",
"maplit",
"mimalloc",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",

View file

@ -67,6 +67,7 @@ im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] }
libc = "0.2"
libloading = "0.6"
mimalloc = { version = "0.1.26", default-features = false }
inkwell = { path = "../vendor/inkwell", optional = true }
target-lexicon = "0.12.2"

View file

@ -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
// and attach it to the Wasm instance.
let import_object = wasi_env
.import_object(&module)
.unwrap_or_else(|_| wasmer::imports!());
let import_object = wasi_env.import_object(&module).unwrap();
let instance = Instance::new(&module, &import_object).unwrap();
let start = instance.exports.get_function("_start").unwrap();
start.call(&[]).unwrap();
use wasmer_wasi::WasiError;
match start.call(&[]) {
Ok(_) => {}
Err(e) => match e.downcast::<WasiError>() {
Ok(WasiError::Exit(0)) => {
// we run the `_start` function, so exit(0) is expected
}
other => panic!("Wasmer error: {:?}", other),
},
}
}
enum Backend {

View file

@ -6,6 +6,9 @@ use std::fs::{self, FileType};
use std::io;
use std::path::{Path, PathBuf};
#[global_allocator]
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[cfg(feature = "llvm")]
use roc_cli::build;
use std::ffi::{OsStr, OsString};

View file

@ -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 {
use std::io::Write;
use wasmer::{Instance, Module, Store};
@ -647,7 +647,22 @@ fn run_with_wasmer(wasm_path: &std::path::Path, stdin: &[&str]) -> String {
let start = instance.exports.get_function("_start").unwrap();
match start.call(&[]) {
Ok(_) => {
Ok(_) => read_wasi_stdout(wasi_env),
Err(e) => {
use wasmer_wasi::WasiError;
match e.downcast::<WasiError>() {
Ok(WasiError::Exit(0)) => {
// we run the `_start` function, so exit(0) is expected
read_wasi_stdout(wasi_env)
}
other => format!("Something went wrong running a wasm test: {:?}", other),
}
}
}
}
#[allow(dead_code)]
fn read_wasi_stdout(wasi_env: wasmer_wasi::WasiEnv) -> String {
let mut state = wasi_env.state.lock().unwrap();
match state.fs.stdout_mut() {
@ -660,8 +675,3 @@ fn run_with_wasmer(wasm_path: &std::path::Path, stdin: &[&str]) -> String {
_ => todo!(),
}
}
Err(e) => {
panic!("Something went wrong running a wasm test:\n{:?}", e);
}
}
}

View file

@ -59,6 +59,22 @@ fn find_zig_str_path() -> PathBuf {
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"))]
pub fn build_zig_host_native(
env_path: &str,
@ -617,6 +633,7 @@ fn link_wasm32(
_link_type: LinkType,
) -> io::Result<(Child, PathBuf)> {
let zig_str_path = find_zig_str_path();
let wasi_libc_path = find_wasi_libc_path();
let child = Command::new("zig9")
// .env_clear()
@ -624,9 +641,10 @@ fn link_wasm32(
.args(&["build-exe"])
.args(input_paths)
.args([
// include wasi libc
// using `-lc` is broken in zig 8 (and early 9) in combination with ReleaseSmall
wasi_libc_path.to_str().unwrap(),
&format!("-femit-bin={}", output_path.to_str().unwrap()),
// include libc
"-lc",
"-target",
"wasm32-wasi-musl",
"--pkg-begin",
@ -634,7 +652,8 @@ fn link_wasm32(
zig_str_path.to_str().unwrap(),
"--pkg-end",
"--strip",
// "-O", "ReleaseSmall",
"-O",
"ReleaseSmall",
// useful for debugging
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll",
])

View file

@ -54,7 +54,7 @@ pub fn build(b: *Builder) void {
// 32-bit wasm
wasm32_target.cpu_arch = std.Target.Cpu.Arch.wasm32;
wasm32_target.os_tag = std.Target.Os.Tag.wasi;
wasm32_target.os_tag = std.Target.Os.Tag.freestanding;
wasm32_target.abi = std.Target.Abi.none;
const obj_name_wasm32 = "builtins-wasm32";

View file

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

View file

@ -117,6 +117,16 @@ pub fn decrefC(
return @call(.{ .modifier = always_inline }, decref_ptr_to_refcount, .{ bytes, alignment });
}
pub fn decrefCheckNullC(
bytes_or_null: ?[*]u8,
alignment: u32,
) callconv(.C) void {
if (bytes_or_null) |bytes| {
const isizes: [*]isize = @ptrCast([*]isize, @alignCast(@sizeOf(isize), bytes));
return @call(.{ .modifier = always_inline }, decref_ptr_to_refcount, .{ isizes - 1, alignment });
}
}
pub fn decref(
bytes_or_null: ?[*]u8,
data_bytes: usize,

Binary file not shown.

View file

@ -83,3 +83,4 @@ pub const DEC_DIV: &str = "roc_builtins.dec.div";
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";

View file

@ -3,15 +3,15 @@ use std::path::Path;
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
use crate::llvm::build_dict::{
dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection,
self, dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection,
dict_keys, dict_len, dict_remove, dict_union, dict_values, dict_walk, set_from_list,
};
use crate::llvm::build_hash::generic_hash;
use crate::llvm::build_list::{
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
list_drop, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len,
list_map, list_map2, list_map3, list_map_with_index, list_prepend, list_range, list_repeat,
list_reverse, list_set, list_single, list_sort_with, list_swap,
self, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat,
list_contains, list_drop, list_get_unsafe, list_join, list_keep_errs, list_keep_if,
list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index, list_prepend,
list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with, list_swap,
};
use crate::llvm::build_str::{
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int,
@ -704,7 +704,8 @@ fn promote_to_main_function<'a, 'ctx, 'env>(
let main_fn_name = "$Test.main";
// Add main to the module.
let main_fn = expose_function_to_host_help(env, main_fn_name, roc_main_fn, main_fn_name);
let main_fn =
expose_function_to_host_help_c_abi(env, main_fn_name, roc_main_fn, &[], main_fn_name);
(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>(
env: &Env<'a, 'ctx, 'env>,
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);
match layout {
Layout::Builtin(Builtin::List(_)) => {
Layout::Builtin(Builtin::List(element_layout)) => {
debug_assert!(value.is_struct_value());
let alignment = element_layout.alignment_bytes(env.ptr_bytes);
// because of how we insert DECREF for lists, we can't guarantee that
// the list is non-empty. When the list is empty, the pointer to the
// elements is NULL, and trying to get to the RC address will
// underflow, causing a segfault. Therefore, in this case we must
// manually check that the list is non-empty
let refcount_ptr = PointerToRefcount::from_list_wrapper(
env,
value.into_struct_value(),
);
let length = list_len(env.builder, value.into_struct_value());
decrement_with_size_check(env, parent, length, *layout, refcount_ptr);
build_list::decref(env, value.into_struct_value(), alignment);
}
Layout::Builtin(Builtin::Dict(_, _)) | Layout::Builtin(Builtin::Set(_)) => {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
debug_assert!(value.is_struct_value());
let alignment = key_layout
.alignment_bytes(env.ptr_bytes)
.max(value_layout.alignment_bytes(env.ptr_bytes));
let refcount_ptr = PointerToRefcount::from_list_wrapper(
env,
value.into_struct_value(),
);
build_dict::decref(env, value.into_struct_value(), alignment);
}
Layout::Builtin(Builtin::Set(key_layout)) => {
debug_assert!(value.is_struct_value());
let alignment = key_layout.alignment_bytes(env.ptr_bytes);
let length = dict_len(env, scope, *symbol).into_int_value();
decrement_with_size_check(env, parent, length, *layout, refcount_ptr);
build_dict::decref(env, value.into_struct_value(), alignment);
}
_ if layout.is_refcounted() => {
@ -3088,18 +3054,26 @@ fn expose_function_to_host<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
symbol: Symbol,
roc_function: FunctionValue<'ctx>,
arguments: &[Layout<'a>],
) {
// Assumption: there is only one specialization of a host-exposed function
let ident_string = symbol.as_str(&env.interns);
let c_function_name: String = format!("roc__{}_1_exposed", ident_string);
expose_function_to_host_help(env, ident_string, roc_function, &c_function_name);
expose_function_to_host_help_c_abi(
env,
ident_string,
roc_function,
arguments,
&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>,
ident_string: &str,
roc_function: FunctionValue<'ctx>,
arguments: &[Layout<'a>],
c_function_name: &str,
) -> FunctionValue<'ctx> {
let context = env.context;
@ -3112,8 +3086,14 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>(
false,
);
let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena);
for layout in arguments {
cc_argument_types.push(to_cc_type(env, layout));
}
// STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}`
let mut argument_types = roc_function.get_type().get_param_types();
// let mut argument_types = roc_function.get_type().get_param_types();
let mut argument_types = cc_argument_types;
let return_type = wrapper_return_type;
let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.push(output_type.into());
@ -3146,22 +3126,45 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>(
let output_arg_index = 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());
let call_result = {
if env.is_gen_test {
let roc_wrapper_function = make_exception_catcher(env, roc_function);
debug_assert_eq!(args.len(), roc_wrapper_function.get_params().len());
debug_assert_eq!(
arguments_for_call.len(),
roc_wrapper_function.get_params().len()
);
builder.position_at_end(entry);
let call_wrapped =
builder.build_call(roc_wrapper_function, args, "call_wrapped_function");
let call_wrapped = builder.build_call(
roc_wrapper_function,
arguments_for_call,
"call_wrapped_function",
);
call_wrapped.set_call_convention(FAST_CALL_CONV);
call_wrapped.try_as_basic_value().left().unwrap()
} else {
let call_unwrapped = builder.build_call(roc_function, args, "call_unwrapped_function");
let call_unwrapped =
builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function");
call_unwrapped.set_call_convention(FAST_CALL_CONV);
let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap();
@ -3691,7 +3694,8 @@ fn build_proc_header<'a, 'ctx, 'env>(
fn_val.set_subprogram(subprogram);
if env.exposed_to_host.contains(&symbol) {
expose_function_to_host(env, symbol, fn_val);
let arguments = Vec::from_iter_in(proc.args.iter().map(|(layout, _)| *layout), env.arena);
expose_function_to_host(env, symbol, fn_val, arguments.into_bump_slice());
}
fn_val

View file

@ -844,3 +844,17 @@ fn dict_symbol_to_zig_dict<'a, 'ctx, 'env>(
)
.into_struct_value()
}
pub fn decref<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
wrapper_struct: StructValue<'ctx>,
alignment: u32,
) {
let pointer = env
.builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr")
.unwrap()
.into_pointer_value();
crate::llvm::refcounting::decref_pointer_check_null(env, pointer, alignment);
}

View file

@ -1136,3 +1136,17 @@ pub fn store_list<'a, 'ctx, 'env>(
"cast_collection",
)
}
pub fn decref<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
wrapper_struct: StructValue<'ctx>,
alignment: u32,
) {
let (_, pointer) = load_list(
env.builder,
wrapper_struct,
env.context.i8_type().ptr_type(AddressSpace::Generic),
);
crate::llvm::refcounting::decref_pointer_check_null(env, pointer, alignment);
}

View file

@ -84,7 +84,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
}
}
pub fn from_list_wrapper(env: &Env<'_, 'ctx, '_>, list_wrapper: StructValue<'ctx>) -> Self {
fn from_list_wrapper(env: &Env<'_, 'ctx, '_>, list_wrapper: StructValue<'ctx>) -> Self {
let data_ptr = env
.builder
.build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr")
@ -102,7 +102,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
.build_int_compare(IntPredicate::EQ, current, one, "is_one")
}
pub fn get_refcount<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
fn get_refcount<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
env.builder
.build_load(self.value, "get_refcount")
.into_int_value()
@ -220,23 +220,55 @@ impl<'ctx> PointerToRefcount<'ctx> {
debug_info_init!(env, parent);
let alignment = env.context.i32_type().const_int(alignment as _, false);
decref_pointer(
env,
parent.get_nth_param(0).unwrap().into_pointer_value(),
alignment,
);
builder.build_return(None);
}
}
fn decref_pointer<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
pointer: PointerValue<'ctx>,
alignment: u32,
) {
let alignment = env.context.i32_type().const_int(alignment as _, false);
call_void_bitcode_fn(
env,
&[
env.builder.build_bitcast(
parent.get_nth_param(0).unwrap(),
pointer,
env.ptr_int().ptr_type(AddressSpace::Generic),
"foo",
"to_isize_ptr",
),
alignment.into(),
],
roc_builtins::bitcode::UTILS_DECREF,
);
builder.build_return(None);
}
/// Assumes a pointer to the refcount
pub fn decref_pointer_check_null<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
pointer: PointerValue<'ctx>,
alignment: u32,
) {
let alignment = env.context.i32_type().const_int(alignment as _, false);
call_void_bitcode_fn(
env,
&[
env.builder.build_bitcast(
pointer,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"to_i8_ptr",
),
alignment.into(),
],
roc_builtins::bitcode::UTILS_DECREF_CHECK_NULL,
);
}
fn modify_refcount_struct<'a, 'ctx, 'env>(
@ -1093,7 +1125,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
fn_val: FunctionValue<'ctx>,
) {
let tags = union_layout_tags(env.arena, &union_layout);
let is_nullable = union_layout.is_nullable();
debug_assert!(!tags.is_empty());
let context = &env.context;
@ -1126,7 +1157,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
let should_recurse_block = env.context.append_basic_block(parent, "should_recurse");
let ctx = env.context;
if is_nullable {
if union_layout.is_nullable() {
let is_null = env.builder.build_is_null(value_ptr, "is_null");
let then_block = ctx.append_basic_block(parent, "then");
@ -1201,6 +1232,12 @@ enum DecOrReuse {
Reuse,
}
fn fields_need_no_refcounting(field_layouts: &[Layout]) -> bool {
!field_layouts
.iter()
.any(|x| x.is_refcounted() || x.contains_refcounted())
}
#[allow(clippy::too_many_arguments)]
fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -1220,21 +1257,6 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
let call_mode = mode_to_call_mode(decrement_fn, mode);
let builder = env.builder;
// branches that are not/don't contain anything refcounted
// if there is only one branch, we don't need to switch
let switch_needed: bool = (|| {
for field_layouts in tags.iter() {
// if none of the fields are or contain anything refcounted, just move on
if !field_layouts
.iter()
.any(|x| x.is_refcounted() || x.contains_refcounted())
{
return true;
}
}
false
})();
// next, make a jump table for all possible values of the tag_id
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
@ -1243,10 +1265,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
for (tag_id, field_layouts) in tags.iter().enumerate() {
// if none of the fields are or contain anything refcounted, just move on
if !field_layouts
.iter()
.any(|x| x.is_refcounted() || x.contains_refcounted())
{
if fields_need_no_refcounting(field_layouts) {
continue;
}
@ -1346,11 +1365,13 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
cases.reverse();
if cases.len() == 1 && !switch_needed {
// there is only one tag in total; we don't need a switch
// this is essential for nullable unwrapped layouts,
// because the `else` branch below would try to read its
// (nonexistant) tag id
if matches!(
union_layout,
UnionLayout::NullableUnwrapped { .. } | UnionLayout::NonNullableUnwrapped { .. }
) {
debug_assert_eq!(cases.len(), 1);
// in this case, don't switch, because the `else` branch below would try to read the (nonexistant) tag id
let (_, only_branch) = cases.pop().unwrap();
env.builder.build_unconditional_branch(only_branch);
} else {
@ -1444,7 +1465,6 @@ fn build_reuse_rec_union_help<'a, 'ctx, 'env>(
dec_function: FunctionValue<'ctx>,
) {
let tags = union_layout_tags(env.arena, &union_layout);
let is_nullable = union_layout.is_nullable();
debug_assert!(!tags.is_empty());
@ -1478,7 +1498,7 @@ fn build_reuse_rec_union_help<'a, 'ctx, 'env>(
let should_recurse_block = env.context.append_basic_block(parent, "should_recurse");
let ctx = env.context;
if is_nullable {
if union_layout.is_nullable() {
let is_null = env.builder.build_is_null(value_ptr, "is_null");
let then_block = ctx.append_basic_block(parent, "then");
@ -1700,68 +1720,3 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
// this function returns void
builder.build_return(None);
}
pub fn refcount_is_one_comparison<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
refcount: IntValue<'ctx>,
) -> IntValue<'ctx> {
env.builder.build_int_compare(
IntPredicate::EQ,
refcount,
refcount_1(env.context, env.ptr_bytes),
"refcount_one_check",
)
}
pub fn list_get_refcount_ptr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
list_wrapper: StructValue<'ctx>,
) -> PointerValue<'ctx> {
// fetch the pointer to the array data, as an integer
let ptr_as_int = env
.builder
.build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr")
.unwrap()
.into_int_value();
get_refcount_ptr_help(env, layout, ptr_as_int)
}
pub fn refcount_offset<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> u64 {
let value_bytes = layout.stack_size(env.ptr_bytes) as u64;
match layout {
Layout::Builtin(Builtin::List(_)) => env.ptr_bytes as u64,
Layout::Builtin(Builtin::Str) => env.ptr_bytes as u64,
Layout::RecursivePointer | Layout::Union(_) => env.ptr_bytes as u64,
_ => (env.ptr_bytes as u64).max(value_bytes),
}
}
fn get_refcount_ptr_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
ptr_as_int: IntValue<'ctx>,
) -> PointerValue<'ctx> {
let builder = env.builder;
let ctx = env.context;
let offset = refcount_offset(env, layout);
// pointer to usize
let refcount_type = ptr_int(ctx, env.ptr_bytes);
// subtract offset, to access the refcount
let refcount_ptr = builder.build_int_sub(
ptr_as_int,
refcount_type.const_int(offset, false),
"make_refcount_ptr",
);
builder.build_int_to_ptr(
refcount_ptr,
refcount_type.ptr_type(AddressSpace::Generic),
"get_refcount_ptr",
)
}

View file

@ -1,11 +1,13 @@
use parity_wasm::builder;
use parity_wasm::builder::{CodeLocation, ModuleBuilder};
use parity_wasm::elements::{Instruction, Instruction::*, Instructions, Local, ValueType};
use parity_wasm::elements::{
BlockType, Instruction, Instruction::*, Instructions, Local, ValueType,
};
use roc_collections::all::MutMap;
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use roc_mono::ir::{CallType, Expr, Literal, Proc, Stmt};
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
use roc_mono::layout::{Builtin, Layout};
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone.
@ -21,7 +23,7 @@ struct LabelId(u32);
#[derive(Debug)]
struct SymbolStorage(LocalId, WasmLayout);
#[derive(Debug)]
#[derive(Clone, Copy, Debug)]
struct WasmLayout {
value_type: ValueType,
stack_memory: u32,
@ -30,6 +32,12 @@ struct WasmLayout {
impl WasmLayout {
fn new(layout: &Layout) -> Result<Self, String> {
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 {
value_type: ValueType::I64,
stack_memory: 0,
@ -61,7 +69,9 @@ pub struct WasmBackend<'a> {
// Functions: internal state & IR mappings
stack_memory: u32,
symbol_storage_map: MutMap<Symbol, SymbolStorage>,
// joinpoint_label_map: MutMap<JoinPointId, LabelId>,
/// how many blocks deep are we (used for jumps)
block_depth: u32,
joinpoint_label_map: MutMap<JoinPointId, (u32, std::vec::Vec<LocalId>)>,
}
impl<'a> WasmBackend<'a> {
@ -84,7 +94,8 @@ impl<'a> WasmBackend<'a> {
// Functions: internal state & IR mappings
stack_memory: 0,
symbol_storage_map: MutMap::default(),
// joinpoint_label_map: MutMap::default(),
block_depth: 0,
joinpoint_label_map: MutMap::default(),
}
}
@ -174,6 +185,27 @@ impl<'a> WasmBackend<'a> {
Ok(())
}
/// start a loop that leaves a value on the stack
fn start_loop_with_return(&mut self, value_type: ValueType) {
self.block_depth += 1;
// self.instructions.push(Loop(BlockType::NoResult));
self.instructions.push(Loop(BlockType::Value(value_type)));
}
fn start_block(&mut self) {
self.block_depth += 1;
// Our blocks always end with a `return` or `br`,
// so they never leave extra values on the stack
self.instructions.push(Block(BlockType::NoResult));
}
fn end_block(&mut self) {
self.block_depth -= 1;
self.instructions.push(End);
}
fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
match stmt {
// This pattern is a simple optimisation to get rid of one local and two instructions per proc.
@ -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(&parameter.layout)?;
let local_id = self.insert_local(wasm_layout, parameter.symbol);
jp_parameter_local_ids.push(local_id);
}
self.start_block();
self.joinpoint_label_map
.insert(*id, (self.block_depth, jp_parameter_local_ids));
self.build_stmt(remainder, ret_layout)?;
self.end_block();
// A `return` inside of a `loop` seems to make it so that the `loop` itself
// also "returns" (so, leaves on the stack) a value of the return type.
let return_wasm_layout = WasmLayout::new(ret_layout)?;
self.start_loop_with_return(return_wasm_layout.value_type);
self.build_stmt(body, ret_layout)?;
// ends the loop
self.end_block();
Ok(())
}
Stmt::Jump(id, arguments) => {
let (target, locals) = &self.joinpoint_label_map[id];
// put the arguments on the stack
for (symbol, local_id) in arguments.iter().zip(locals.iter()) {
let argument = match self.symbol_storage_map.get(symbol) {
Some(SymbolStorage(local_id, _)) => local_id.0,
None => unreachable!("symbol not defined: {:?}", symbol),
};
self.instructions.push(GetLocal(argument));
self.instructions.push(SetLocal(local_id.0));
}
// jump
let levels = self.block_depth - target;
self.instructions.push(Br(levels));
Ok(())
}
x => Err(format!("statement not yet implemented: {:?}", x)),
}
}
@ -218,7 +357,7 @@ impl<'a> WasmBackend<'a> {
layout: &Layout<'a>,
) -> Result<(), String> {
match expr {
Expr::Literal(lit) => self.load_literal(lit),
Expr::Literal(lit) => self.load_literal(lit, layout),
Expr::Call(roc_mono::ir::Call {
call_type,
@ -246,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 {
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) => {
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(())
}
Literal::Float(x) => {
@ -293,6 +448,22 @@ impl<'a> WasmBackend<'a> {
ValueType::F32 => &[F32Add],
ValueType::F64 => &[F64Add],
},
LowLevel::NumSub => match return_value_type {
ValueType::I32 => &[I32Sub],
ValueType::I64 => &[I64Sub],
ValueType::F32 => &[F32Sub],
ValueType::F64 => &[F64Sub],
},
LowLevel::NumMul => match return_value_type {
ValueType::I32 => &[I32Mul],
ValueType::I64 => &[I64Mul],
ValueType::F32 => &[F32Mul],
ValueType::F64 => &[F64Mul],
},
LowLevel::NumGt => {
// needs layout of the argument to be implemented fully
&[I32GtS]
}
_ => {
return Err(format!("unsupported low-level op {:?}", lowlevel));
}

View file

@ -25,6 +25,19 @@ pub fn build_module<'a>(
let mut backend = WasmBackend::new();
let mut layout_ids = LayoutIds::default();
// Sort procedures by occurrence order
//
// We sort by the "name", but those are interned strings, and the name that is
// interned first will have a lower number.
//
// But, the name that occurs first is always `main` because it is in the (implicit)
// file header. Therefore sorting high to low will put other functions before main
//
// This means that for now other functions in the file have to be ordered "in reverse": if A
// uses B, then the name of A must first occur after the first occurrence of the name of B
let mut procedures: std::vec::Vec<_> = procedures.into_iter().collect();
procedures.sort_by(|a, b| b.0 .0.cmp(&a.0 .0));
for ((sym, layout), proc) in procedures {
let function_index = backend.build_proc(proc, sym)?;
if env.exposed_to_host.contains(&sym) {

View file

@ -147,6 +147,7 @@ where
Err(e) => Err(format!("{:?}", e)),
Ok(result) => {
let integer = match result[0] {
wasmer::Value::I32(a) => a as i64,
wasmer::Value::I64(a) => a,
wasmer::Value::F64(a) => a.to_bits() as i64,
_ => panic!(),

View file

@ -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]
// fn gen_add_f64() {
// assert_evals_to!(

View file

@ -723,7 +723,21 @@ pub struct MonomorphizedModule<'a> {
impl<'a> MonomorphizedModule<'a> {
pub fn total_problems(&self) -> usize {
self.can_problems.len() + self.type_problems.len() + self.mono_problems.len()
let mut total = 0;
for problems in self.can_problems.values() {
total += problems.len();
}
for problems in self.type_problems.values() {
total += problems.len();
}
for problems in self.mono_problems.values() {
total += problems.len();
}
total
}
}
@ -3927,7 +3941,7 @@ fn make_specializations<'a>(
);
let external_specializations_requested = procs.externals_we_need.clone();
let procedures = procs.get_specialized_procs_without_rc(mono_env.arena);
let procedures = procs.get_specialized_procs_without_rc(&mut mono_env);
let make_specializations_end = SystemTime::now();
module_timing.make_specializations = make_specializations_end

View file

@ -272,6 +272,33 @@ impl<'a> Proc<'a> {
proc.body = b.clone();
}
}
fn make_tail_recursive(&mut self, env: &mut Env<'a, '_>) {
let mut args = Vec::with_capacity_in(self.args.len(), env.arena);
let mut proc_args = Vec::with_capacity_in(self.args.len(), env.arena);
for (layout, symbol) in self.args {
let new = env.unique_symbol();
args.push((*layout, *symbol, new));
proc_args.push((*layout, new));
}
use self::SelfRecursive::*;
if let SelfRecursive(id) = self.is_self_recursive {
let transformed = crate::tail_recursion::make_tail_recursive(
env.arena,
id,
self.name,
self.body.clone(),
args.into_bump_slice(),
);
if let Some(with_tco) = transformed {
self.body = with_tco;
self.args = proc_args.into_bump_slice();
}
}
}
}
#[derive(Clone, Debug)]
@ -350,7 +377,7 @@ pub enum InProgressProc<'a> {
impl<'a> Procs<'a> {
pub fn get_specialized_procs_without_rc(
self,
arena: &'a Bump,
env: &mut Env<'a, '_>,
) -> MutMap<(Symbol, ProcLayout<'a>), Proc<'a>> {
let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
@ -376,16 +403,7 @@ impl<'a> Procs<'a> {
panic!();
}
Done(mut proc) => {
use self::SelfRecursive::*;
if let SelfRecursive(id) = proc.is_self_recursive {
proc.body = crate::tail_recursion::make_tail_recursive(
arena,
id,
proc.name,
proc.body.clone(),
proc.args,
);
}
proc.make_tail_recursive(env);
result.insert(key, proc);
}
@ -395,86 +413,6 @@ impl<'a> Procs<'a> {
result
}
// TODO investigate make this an iterator?
pub fn get_specialized_procs(
self,
arena: &'a Bump,
) -> MutMap<(Symbol, ProcLayout<'a>), Proc<'a>> {
let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
for ((s, toplevel), in_prog_proc) in self.specialized.into_iter() {
match in_prog_proc {
InProgress => unreachable!(
"The procedure {:?} should have be done by now",
(s, toplevel)
),
Done(proc) => {
result.insert((s, toplevel), proc);
}
}
}
for (_, proc) in result.iter_mut() {
use self::SelfRecursive::*;
if let SelfRecursive(id) = proc.is_self_recursive {
proc.body = crate::tail_recursion::make_tail_recursive(
arena,
id,
proc.name,
proc.body.clone(),
proc.args,
);
}
}
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
crate::inc_dec::visit_procs(arena, borrow_params, &mut result);
result
}
pub fn get_specialized_procs_help(
self,
arena: &'a Bump,
) -> (
MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
&'a crate::borrow::ParamMap<'a>,
) {
let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
for ((s, toplevel), in_prog_proc) in self.specialized.into_iter() {
match in_prog_proc {
InProgress => unreachable!(
"The procedure {:?} should have be done by now",
(s, toplevel)
),
Done(proc) => {
result.insert((s, toplevel), proc);
}
}
}
for (_, proc) in result.iter_mut() {
use self::SelfRecursive::*;
if let SelfRecursive(id) = proc.is_self_recursive {
proc.body = crate::tail_recursion::make_tail_recursive(
arena,
id,
proc.name,
proc.body.clone(),
proc.args,
);
}
}
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
crate::inc_dec::visit_procs(arena, borrow_params, &mut result);
(result, borrow_params)
}
// TODO trim down these arguments!
#[allow(clippy::too_many_arguments)]
pub fn insert_named(
@ -3748,13 +3686,15 @@ pub fn with_hole<'a>(
match what_to_do {
UpdateExisting(field) => {
substitute_in_exprs(env.arena, &mut stmt, assigned, symbols[0]);
stmt = assign_to_symbol(
env,
procs,
layout_cache,
field.var,
*field.loc_expr.clone(),
assigned,
symbols[0],
stmt,
);
}

View file

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

View file

@ -889,3 +889,26 @@ fn blue_and_absent() {
i64
);
}
#[test]
fn update_the_only_field() {
assert_evals_to!(
indoc!(
r#"
Model : { foo : I64 }
model : Model
model = { foo: 3 }
foo = 4
newModel : Model
newModel = { model & foo }
newModel.foo
"#
),
4,
i64
);
}

View file

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

View file

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

View file

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

View file

@ -77,12 +77,14 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
const Unit = extern struct {};
pub fn main() u8 {
const allocator = std.heap.page_allocator;
const size = @intCast(usize, roc__mainForHost_size());
const raw_output = std.heap.c_allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
var output = @ptrCast([*]u8, raw_output);
defer {
std.heap.c_allocator.free(raw_output);
allocator.free(raw_output);
}
var ts1: std.os.timespec = undefined;
@ -122,12 +124,14 @@ fn to_seconds(tms: std.os.timespec) f64 {
}
fn call_the_closure(closure_data_pointer: [*]u8) void {
const allocator = std.heap.page_allocator;
const size = roc__mainForHost_1_Fx_result_size();
const raw_output = std.heap.c_allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
var output = @ptrCast([*]u8, raw_output);
defer {
std.heap.c_allocator.free(raw_output);
allocator.free(raw_output);
}
const flags: u8 = 0;

View file

@ -22,23 +22,45 @@ const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed(RocList, *RocCallResult) void;
extern fn malloc(size: usize) callconv(.C) ?*c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void;
const Align = extern struct { a: usize, b: usize };
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
const DEBUG: bool = false;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
if (DEBUG) {
var ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
return ptr;
} else {
return malloc(size);
}
}
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size);
if (DEBUG) {
const stdout = std.io.getStdOut().writer();
stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable;
}
return realloc(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
free(@alignCast(16, @ptrCast([*]u8, c_ptr)));
if (DEBUG) {
const stdout = std.io.getStdOut().writer();
stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
}
free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)));
}
export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
_ = tag_id;
const stderr = std.io.getStdErr().writer();
const msg = @ptrCast([*:0]const u8, c_ptr);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
@ -50,11 +72,11 @@ const NUM_NUMS = 100;
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 {};
pub export fn main() i32 {
pub export fn main() u8 {
const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer();
@ -65,7 +87,7 @@ pub export fn main() i32 {
var numbers = raw_numbers[1..];
for (numbers) |x, i| {
for (numbers) |_, i| {
numbers[i] = @mod(@intCast(i64, i), 12);
}