mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-21 07:49:17 +03:00
Merge branch 'trunk' into ci_cache_fix
This commit is contained in:
commit
77b84bee14
@ -1014,3 +1014,71 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// input: RocList,
|
||||
pub fn listSet(
|
||||
bytes: ?[*]u8,
|
||||
length: usize,
|
||||
alignment: u32,
|
||||
index: usize,
|
||||
element: Opaque,
|
||||
element_width: usize,
|
||||
dec: Dec,
|
||||
) callconv(.C) ?[*]u8 {
|
||||
// INVARIANT: bounds checking happens on the roc side
|
||||
//
|
||||
// at the time of writing, the function is implemented roughly as
|
||||
// `if inBounds then LowLevelListGet input index item else input`
|
||||
// so we don't do a bounds check here. Hence, the list is also non-empty,
|
||||
// because inserting into an empty list is always out of bounds
|
||||
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, bytes));
|
||||
|
||||
if ((ptr - 1)[0] == utils.REFCOUNT_ONE) {
|
||||
|
||||
// the element we will replace
|
||||
var element_at_index = (bytes orelse undefined) + (index * element_width);
|
||||
|
||||
// decrement its refcount
|
||||
dec(element_at_index);
|
||||
|
||||
// copy in the new element
|
||||
@memcpy(element_at_index, element orelse undefined, element_width);
|
||||
|
||||
return bytes;
|
||||
} else {
|
||||
return listSetClone(bytes, length, alignment, index, element, element_width, dec);
|
||||
}
|
||||
}
|
||||
|
||||
inline fn listSetClone(
|
||||
old_bytes: ?[*]u8,
|
||||
length: usize,
|
||||
alignment: u32,
|
||||
index: usize,
|
||||
element: Opaque,
|
||||
element_width: usize,
|
||||
dec: Dec,
|
||||
) ?[*]u8 {
|
||||
@setCold(true);
|
||||
|
||||
const data_bytes = length * element_width;
|
||||
|
||||
var new_bytes = utils.allocateWithRefcount(data_bytes, alignment);
|
||||
|
||||
@memcpy(new_bytes, old_bytes orelse undefined, data_bytes);
|
||||
|
||||
// the element we will replace
|
||||
var element_at_index = new_bytes + (index * element_width);
|
||||
|
||||
// decrement its refcount
|
||||
dec(element_at_index);
|
||||
|
||||
// copy in the new element
|
||||
@memcpy(element_at_index, element orelse undefined, element_width);
|
||||
|
||||
// consume RC token of original
|
||||
utils.decref(old_bytes, data_bytes, alignment);
|
||||
|
||||
//return list;
|
||||
return new_bytes;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ comptime {
|
||||
exportListFn(list.listSortWith, "sort_with");
|
||||
exportListFn(list.listConcat, "concat");
|
||||
exportListFn(list.listDrop, "drop");
|
||||
exportListFn(list.listSet, "set");
|
||||
}
|
||||
|
||||
// Dict Module
|
||||
|
@ -54,7 +54,7 @@ pub const IncN = fn (?[*]u8, u64) callconv(.C) void;
|
||||
pub const Dec = fn (?[*]u8) callconv(.C) void;
|
||||
|
||||
const REFCOUNT_MAX_ISIZE: comptime isize = 0;
|
||||
const REFCOUNT_ONE_ISIZE: comptime isize = std.math.minInt(isize);
|
||||
pub const REFCOUNT_ONE_ISIZE: comptime isize = std.math.minInt(isize);
|
||||
pub const REFCOUNT_ONE: usize = @bitCast(usize, REFCOUNT_ONE_ISIZE);
|
||||
|
||||
pub const IntWidth = enum(u8) {
|
||||
|
@ -83,3 +83,4 @@ pub const LIST_RANGE: &str = "roc_builtins.list.range";
|
||||
pub const LIST_REVERSE: &str = "roc_builtins.list.reverse";
|
||||
pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with";
|
||||
pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
|
||||
pub const LIST_SET: &str = "roc_builtins.list.set";
|
||||
|
@ -21,8 +21,7 @@ use crate::llvm::convert::{
|
||||
get_fn_type, get_ptr_type, ptr_int,
|
||||
};
|
||||
use crate::llvm::refcounting::{
|
||||
decrement_refcount_layout, increment_refcount_layout, refcount_is_one_comparison,
|
||||
PointerToRefcount,
|
||||
decrement_refcount_layout, increment_refcount_layout, PointerToRefcount,
|
||||
};
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
@ -4476,46 +4475,46 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||
)
|
||||
}
|
||||
ListSetInPlace => {
|
||||
let (list_symbol, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
let (index, _) = load_symbol_and_layout(scope, &args[1]);
|
||||
let (element, _) = load_symbol_and_layout(scope, &args[2]);
|
||||
|
||||
let output_inplace = get_inplace_from_layout(layout);
|
||||
|
||||
list_set(
|
||||
parent,
|
||||
&[
|
||||
(list_symbol, list_layout),
|
||||
(load_symbol_and_layout(scope, &args[1])),
|
||||
(load_symbol_and_layout(scope, &args[2])),
|
||||
],
|
||||
match list_layout {
|
||||
Layout::Builtin(Builtin::EmptyList) => {
|
||||
// no elements, so nothing to remove
|
||||
empty_list(env)
|
||||
}
|
||||
Layout::Builtin(Builtin::List(_, element_layout)) => list_set(
|
||||
env,
|
||||
InPlace::InPlace,
|
||||
output_inplace,
|
||||
)
|
||||
layout_ids,
|
||||
list,
|
||||
index.into_int_value(),
|
||||
element,
|
||||
element_layout,
|
||||
),
|
||||
_ => unreachable!("invalid dict layout"),
|
||||
}
|
||||
}
|
||||
ListSet => {
|
||||
let (list_symbol, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
let (index, _) = load_symbol_and_layout(scope, &args[1]);
|
||||
let (element, _) = load_symbol_and_layout(scope, &args[2]);
|
||||
|
||||
let arguments = &[
|
||||
(list_symbol, list_layout),
|
||||
(load_symbol_and_layout(scope, &args[1])),
|
||||
(load_symbol_and_layout(scope, &args[2])),
|
||||
];
|
||||
|
||||
let output_inplace = get_inplace_from_layout(layout);
|
||||
|
||||
let in_place = || list_set(parent, arguments, env, InPlace::InPlace, output_inplace);
|
||||
let clone = || list_set(parent, arguments, env, InPlace::Clone, output_inplace);
|
||||
let empty = || list_symbol;
|
||||
|
||||
maybe_inplace_list(
|
||||
match list_layout {
|
||||
Layout::Builtin(Builtin::EmptyList) => {
|
||||
// no elements, so nothing to remove
|
||||
empty_list(env)
|
||||
}
|
||||
Layout::Builtin(Builtin::List(_, element_layout)) => list_set(
|
||||
env,
|
||||
parent,
|
||||
list_layout,
|
||||
list_symbol.into_struct_value(),
|
||||
in_place,
|
||||
clone,
|
||||
empty,
|
||||
)
|
||||
layout_ids,
|
||||
list,
|
||||
index.into_int_value(),
|
||||
element,
|
||||
element_layout,
|
||||
),
|
||||
_ => unreachable!("invalid dict layout"),
|
||||
}
|
||||
}
|
||||
Hash => {
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
@ -4880,45 +4879,6 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_inplace_list<'a, 'ctx, 'env, InPlace, CloneFirst, Empty>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
list_layout: &Layout<'a>,
|
||||
original_wrapper: StructValue<'ctx>,
|
||||
mut in_place: InPlace,
|
||||
clone: CloneFirst,
|
||||
mut empty: Empty,
|
||||
) -> BasicValueEnum<'ctx>
|
||||
where
|
||||
InPlace: FnMut() -> BasicValueEnum<'ctx>,
|
||||
CloneFirst: FnMut() -> BasicValueEnum<'ctx>,
|
||||
Empty: FnMut() -> BasicValueEnum<'ctx>,
|
||||
{
|
||||
match list_layout {
|
||||
Layout::Builtin(Builtin::List(MemoryMode::Unique, _)) => {
|
||||
// the layout tells us this List.set can be done in-place
|
||||
in_place()
|
||||
}
|
||||
Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) => {
|
||||
// no static guarantees, but all is not lost: we can check the refcount
|
||||
// if it is one, we hold the final reference, and can mutate it in-place!
|
||||
|
||||
let ret_type = basic_type_from_layout(env, list_layout);
|
||||
|
||||
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);
|
||||
let refcount = refcount_ptr.get_refcount(env);
|
||||
|
||||
let comparison = refcount_is_one_comparison(env, refcount);
|
||||
|
||||
crate::llvm::build_list::build_basic_phi2(
|
||||
env, parent, comparison, in_place, clone, ret_type,
|
||||
)
|
||||
}
|
||||
Layout::Builtin(Builtin::EmptyList) => empty(),
|
||||
other => unreachable!("Attempting list operation on invalid layout {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_int_binop<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
|
@ -7,9 +7,7 @@ use crate::llvm::build::{
|
||||
allocate_with_refcount_help, cast_basic_basic, complex_bitcast, Env, InPlace, RocFunctionCall,
|
||||
};
|
||||
use crate::llvm::convert::{basic_type_from_layout, get_ptr_type};
|
||||
use crate::llvm::refcounting::{
|
||||
increment_refcount_layout, refcount_is_one_comparison, PointerToRefcount,
|
||||
};
|
||||
use crate::llvm::refcounting::increment_refcount_layout;
|
||||
use inkwell::builder::Builder;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::types::{BasicTypeEnum, PointerType};
|
||||
@ -336,94 +334,36 @@ pub fn list_drop<'a, 'ctx, 'env>(
|
||||
|
||||
/// List.set : List elem, Nat, elem -> List elem
|
||||
pub fn list_set<'a, 'ctx, 'env>(
|
||||
parent: FunctionValue<'ctx>,
|
||||
args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)],
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
input_inplace: InPlace,
|
||||
output_inplace: InPlace,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
list: BasicValueEnum<'ctx>,
|
||||
index: IntValue<'ctx>,
|
||||
element: BasicValueEnum<'ctx>,
|
||||
element_layout: &'a Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let builder = env.builder;
|
||||
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
|
||||
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
let (length, bytes) = load_list(
|
||||
env.builder,
|
||||
list.into_struct_value(),
|
||||
env.context.i8_type().ptr_type(AddressSpace::Generic),
|
||||
);
|
||||
|
||||
let original_wrapper = args[0].0.into_struct_value();
|
||||
let elem_index = args[1].0.into_int_value();
|
||||
|
||||
// Load the usize length from the wrapper. We need it for bounds checking.
|
||||
let list_len = list_len(builder, original_wrapper);
|
||||
|
||||
// Bounds check: only proceed if index < length.
|
||||
// Otherwise, return the list unaltered.
|
||||
let comparison = bounds_check_comparison(builder, elem_index, list_len);
|
||||
|
||||
// If the index is in bounds, clone and mutate in place.
|
||||
let build_then = || {
|
||||
let (elem, elem_layout) = args[2];
|
||||
let ctx = env.context;
|
||||
let elem_type = basic_type_from_layout(env, elem_layout);
|
||||
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
|
||||
|
||||
let (new_wrapper, array_data_ptr) = match input_inplace {
|
||||
InPlace::InPlace => (
|
||||
original_wrapper,
|
||||
load_list_ptr(builder, original_wrapper, ptr_type),
|
||||
),
|
||||
InPlace::Clone => {
|
||||
let list_ptr = load_list_ptr(builder, original_wrapper, ptr_type);
|
||||
|
||||
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, list_ptr);
|
||||
let refcount = refcount_ptr.get_refcount(env);
|
||||
|
||||
let rc_is_one = refcount_is_one_comparison(env, refcount);
|
||||
|
||||
let source_block = env.builder.get_insert_block().unwrap();
|
||||
let clone_block = ctx.append_basic_block(parent, "clone");
|
||||
let done_block = ctx.append_basic_block(parent, "done");
|
||||
|
||||
env.builder
|
||||
.build_conditional_branch(rc_is_one, done_block, clone_block);
|
||||
|
||||
env.builder.position_at_end(clone_block);
|
||||
|
||||
let cloned =
|
||||
clone_nonempty_list(env, output_inplace, list_len, list_ptr, elem_layout).0;
|
||||
|
||||
env.builder.build_unconditional_branch(done_block);
|
||||
|
||||
env.builder.position_at_end(done_block);
|
||||
|
||||
let list_type = original_wrapper.get_type();
|
||||
let merged = env.builder.build_phi(list_type, "writable_list");
|
||||
merged.add_incoming(&[(&original_wrapper, source_block), (&cloned, clone_block)]);
|
||||
|
||||
let result = merged.as_basic_value().into_struct_value();
|
||||
|
||||
(result, load_list_ptr(builder, result, ptr_type))
|
||||
}
|
||||
};
|
||||
|
||||
// If we got here, we passed the bounds check, so this is an in-bounds GEP
|
||||
let elem_ptr =
|
||||
unsafe { builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "load_index") };
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(elem_ptr, elem);
|
||||
|
||||
BasicValueEnum::StructValue(new_wrapper)
|
||||
};
|
||||
|
||||
// If the index was out of bounds, return the original list unaltered.
|
||||
let build_else = || BasicValueEnum::StructValue(original_wrapper);
|
||||
let ret_type = original_wrapper.get_type();
|
||||
|
||||
build_basic_phi2(
|
||||
let new_bytes = call_bitcode_fn(
|
||||
env,
|
||||
parent,
|
||||
comparison,
|
||||
build_then,
|
||||
build_else,
|
||||
ret_type.into(),
|
||||
)
|
||||
&[
|
||||
bytes.into(),
|
||||
length.into(),
|
||||
env.alignment_intvalue(&element_layout),
|
||||
index.into(),
|
||||
pass_element_as_opaque(env, element),
|
||||
layout_width(env, element_layout),
|
||||
dec_element_fn.as_global_value().as_pointer_value().into(),
|
||||
],
|
||||
&bitcode::LIST_SET,
|
||||
);
|
||||
|
||||
store_list(env, new_bytes.into_pointer_value(), length)
|
||||
}
|
||||
|
||||
fn bounds_check_comparison<'ctx>(
|
||||
@ -1173,69 +1113,6 @@ pub fn load_list_ptr<'ctx>(
|
||||
cast_basic_basic(builder, generic_ptr.into(), ptr_type.into()).into_pointer_value()
|
||||
}
|
||||
|
||||
fn clone_nonempty_list<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
inplace: InPlace,
|
||||
list_len: IntValue<'ctx>,
|
||||
elems_ptr: PointerValue<'ctx>,
|
||||
elem_layout: &Layout<'_>,
|
||||
) -> (StructValue<'ctx>, PointerValue<'ctx>) {
|
||||
let builder = env.builder;
|
||||
let ptr_bytes = env.ptr_bytes;
|
||||
|
||||
// Calculate the number of bytes we'll need to allocate.
|
||||
let elem_bytes = env
|
||||
.ptr_int()
|
||||
.const_int(elem_layout.stack_size(env.ptr_bytes) as u64, false);
|
||||
let size = env
|
||||
.builder
|
||||
.build_int_mul(elem_bytes, list_len, "clone_mul_len_by_elem_bytes");
|
||||
|
||||
// Allocate space for the new array that we'll copy into.
|
||||
let clone_ptr = allocate_list(env, inplace, elem_layout, list_len);
|
||||
|
||||
// Either memcpy or deep clone the array elements
|
||||
if elem_layout.safe_to_memcpy() {
|
||||
// Copy the bytes from the original array into the new
|
||||
// one we just allocated
|
||||
//
|
||||
// TODO how do we decide when to do the small memcpy vs the normal one?
|
||||
builder
|
||||
.build_memcpy(clone_ptr, ptr_bytes, elems_ptr, ptr_bytes, size)
|
||||
.unwrap();
|
||||
} else {
|
||||
panic!("TODO Cranelift currently only knows how to clone list elements that are Copy.");
|
||||
}
|
||||
|
||||
let struct_type = super::convert::zig_list_type(env);
|
||||
let mut struct_val;
|
||||
|
||||
// Store the pointer
|
||||
struct_val = builder
|
||||
.build_insert_value(
|
||||
struct_type.get_undef(),
|
||||
pass_as_opaque(env, clone_ptr),
|
||||
Builtin::WRAPPER_PTR,
|
||||
"insert_ptr_clone_nonempty_list",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Store the length
|
||||
struct_val = builder
|
||||
.build_insert_value(struct_val, list_len, Builtin::WRAPPER_LEN, "insert_len")
|
||||
.unwrap();
|
||||
|
||||
let answer = builder
|
||||
.build_bitcast(
|
||||
struct_val.into_struct_value(),
|
||||
super::convert::zig_list_type(env),
|
||||
"cast_collection",
|
||||
)
|
||||
.into_struct_value();
|
||||
|
||||
(answer, clone_ptr)
|
||||
}
|
||||
|
||||
pub fn allocate_list<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
inplace: InPlace,
|
||||
|
@ -423,7 +423,10 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
|
||||
// prevent long chains of `Let`s from blowing the stack
|
||||
let mut literal_stack = Vec::new_in(env.arena);
|
||||
|
||||
while !matches!(&expr, Expr::AccessAtIndex { .. } | Expr::Struct(_)) {
|
||||
while !matches!(
|
||||
&expr,
|
||||
Expr::AccessAtIndex { .. } | Expr::Struct(_) | Expr::Call(_)
|
||||
) {
|
||||
if let Stmt::Let(symbol1, expr1, layout1, cont1) = cont {
|
||||
literal_stack.push((symbol, expr.clone(), *layout));
|
||||
|
||||
@ -485,6 +488,17 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
|
||||
new_cont = expand_and_cancel(env, cont);
|
||||
}
|
||||
}
|
||||
Expr::Call(_) => {
|
||||
if let Layout::Struct(fields) = layout {
|
||||
env.insert_struct_info(symbol, fields);
|
||||
|
||||
new_cont = expand_and_cancel(env, cont);
|
||||
|
||||
env.remove_struct_info(symbol);
|
||||
} else {
|
||||
new_cont = expand_and_cancel(env, cont);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
new_cont = expand_and_cancel(env, cont);
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ const Allocator = mem.Allocator;
|
||||
|
||||
extern fn roc__mainForHost_1_exposed([*]u8) void;
|
||||
extern fn roc__mainForHost_1_size() i64;
|
||||
extern fn roc__mainForHost_1_Fx_caller(*const u8, *const u8, [*]u8, [*]u8) void;
|
||||
extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void;
|
||||
extern fn roc__mainForHost_1_Fx_size() i64;
|
||||
extern fn roc__mainForHost_1_Fx_result_size() i64;
|
||||
|
||||
@ -53,10 +53,9 @@ pub export fn main() u8 {
|
||||
|
||||
if (flag == 0) {
|
||||
// all is well
|
||||
const function_pointer = @intToPtr(*const u8, elements[1]);
|
||||
const closure_data_pointer = @ptrCast([*]u8, output[16..size]);
|
||||
const closure_data_pointer = @ptrCast([*]u8, output[8..size]);
|
||||
|
||||
call_the_closure(function_pointer, closure_data_pointer);
|
||||
call_the_closure(closure_data_pointer);
|
||||
} else {
|
||||
unreachable;
|
||||
}
|
||||
@ -96,7 +95,7 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
|
||||
stdout.print("\x1B[36m{} | \x1B[39m Custom dealloc ran in {} ms!\n", .{ startNs, totalMs }) catch unreachable;
|
||||
}
|
||||
|
||||
fn call_the_closure(function_pointer: *const u8, closure_data_pointer: [*]u8) void {
|
||||
fn call_the_closure(closure_data_pointer: [*]u8) void {
|
||||
const size = roc__mainForHost_1_Fx_result_size();
|
||||
const raw_output = std.heap.c_allocator.alloc(u8, @intCast(usize, size)) catch unreachable;
|
||||
var output = @ptrCast([*]u8, raw_output);
|
||||
@ -107,7 +106,7 @@ fn call_the_closure(function_pointer: *const u8, closure_data_pointer: [*]u8) vo
|
||||
|
||||
const flags: u8 = 0;
|
||||
|
||||
roc__mainForHost_1_Fx_caller(&flags, function_pointer, closure_data_pointer, output);
|
||||
roc__mainForHost_1_Fx_caller(&flags, closure_data_pointer, output);
|
||||
|
||||
const elements = @ptrCast([*]u64, @alignCast(8, output));
|
||||
|
||||
|
@ -55,5 +55,6 @@ swap = \i, j, list ->
|
||||
|> List.set j atI
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
# to prevent a decrement on list
|
||||
# turns out this is very important for optimizations
|
||||
list
|
||||
|
@ -153,9 +153,9 @@ impl<T> RocList<T> {
|
||||
// pointer to the first element
|
||||
let raw_ptr = Self::get_element_ptr(raw_ptr as *mut T) as *mut T;
|
||||
|
||||
// write the capacity
|
||||
let capacity_ptr = raw_ptr as *mut usize;
|
||||
*(capacity_ptr.offset(-1)) = capacity;
|
||||
// write the refcount
|
||||
let refcount_ptr = raw_ptr as *mut isize;
|
||||
*(refcount_ptr.offset(-1)) = isize::MIN;
|
||||
|
||||
{
|
||||
// NOTE: using a memcpy here causes weird issues
|
||||
|
Loading…
Reference in New Issue
Block a user