mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 14:24:45 +00:00

Instead of -max_size to -1 for regular refcounts, use 1 to max_size. 0 still means constant refcount. The highest bit is used to signify atomic refcounting required. This does not turn on any sort of atomic refcounting.
1019 lines
35 KiB
Zig
1019 lines
35 KiB
Zig
const std = @import("std");
|
|
const utils = @import("utils.zig");
|
|
const str = @import("str.zig");
|
|
const sort = @import("sort.zig");
|
|
const UpdateMode = utils.UpdateMode;
|
|
const mem = std.mem;
|
|
const math = std.math;
|
|
|
|
const expect = std.testing.expect;
|
|
const expectEqual = std.testing.expectEqual;
|
|
|
|
const Opaque = ?[*]u8;
|
|
const EqFn = *const fn (Opaque, Opaque) callconv(.C) bool;
|
|
const CompareFn = *const fn (Opaque, Opaque, Opaque) callconv(.C) u8;
|
|
const CopyFn = *const fn (Opaque, Opaque) callconv(.C) void;
|
|
|
|
const Inc = *const fn (?[*]u8) callconv(.C) void;
|
|
const IncN = *const fn (?[*]u8, usize) callconv(.C) void;
|
|
const Dec = *const fn (?[*]u8) callconv(.C) void;
|
|
const HasTagId = *const fn (u16, ?[*]u8) callconv(.C) extern struct { matched: bool, data: ?[*]u8 };
|
|
|
|
const SEAMLESS_SLICE_BIT: usize =
|
|
@as(usize, @bitCast(@as(isize, std.math.minInt(isize))));
|
|
|
|
pub const RocList = extern struct {
|
|
bytes: ?[*]u8,
|
|
length: usize,
|
|
// For normal lists, contains the capacity.
|
|
// For seamless slices contains the pointer to the original allocation.
|
|
// This pointer is to the first element of the original list.
|
|
// Note we storing an allocation pointer, the pointer must be right shifted by one.
|
|
capacity_or_alloc_ptr: usize,
|
|
|
|
pub inline fn len(self: RocList) usize {
|
|
return self.length;
|
|
}
|
|
|
|
pub fn getCapacity(self: RocList) usize {
|
|
const list_capacity = self.capacity_or_alloc_ptr;
|
|
const slice_capacity = self.length;
|
|
const slice_mask = self.seamlessSliceMask();
|
|
const capacity = (list_capacity & ~slice_mask) | (slice_capacity & slice_mask);
|
|
return capacity;
|
|
}
|
|
|
|
pub fn isSeamlessSlice(self: RocList) bool {
|
|
return @as(isize, @bitCast(self.capacity_or_alloc_ptr)) < 0;
|
|
}
|
|
|
|
// This returns all ones if the list is a seamless slice.
|
|
// Otherwise, it returns all zeros.
|
|
// This is done without branching for optimization purposes.
|
|
pub fn seamlessSliceMask(self: RocList) usize {
|
|
return @as(usize, @bitCast(@as(isize, @bitCast(self.capacity_or_alloc_ptr)) >> (@bitSizeOf(isize) - 1)));
|
|
}
|
|
|
|
pub fn isEmpty(self: RocList) bool {
|
|
return self.len() == 0;
|
|
}
|
|
|
|
pub fn empty() RocList {
|
|
return RocList{ .bytes = null, .length = 0, .capacity_or_alloc_ptr = 0 };
|
|
}
|
|
|
|
pub fn eql(self: RocList, other: RocList) bool {
|
|
if (self.len() != other.len()) {
|
|
return false;
|
|
}
|
|
|
|
// Their lengths are the same, and one is empty; they're both empty!
|
|
if (self.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
var index: usize = 0;
|
|
const self_bytes = self.bytes orelse unreachable;
|
|
const other_bytes = other.bytes orelse unreachable;
|
|
|
|
while (index < self.len()) {
|
|
if (self_bytes[index] != other_bytes[index]) {
|
|
return false;
|
|
}
|
|
|
|
index += 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
pub fn fromSlice(comptime T: type, slice: []const T, elements_refcounted: bool) RocList {
|
|
if (slice.len == 0) {
|
|
return RocList.empty();
|
|
}
|
|
|
|
const list = allocate(@alignOf(T), slice.len, @sizeOf(T), elements_refcounted);
|
|
|
|
if (slice.len > 0) {
|
|
const dest = list.bytes orelse unreachable;
|
|
const src = @as([*]const u8, @ptrCast(slice.ptr));
|
|
const num_bytes = slice.len * @sizeOf(T);
|
|
|
|
@memcpy(dest[0..num_bytes], src[0..num_bytes]);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
// returns a pointer to the original allocation.
|
|
// This pointer points to the first element of the allocation.
|
|
// The pointer is to just after the refcount.
|
|
// For big lists, it just returns their bytes pointer.
|
|
// For seamless slices, it returns the pointer stored in capacity_or_alloc_ptr.
|
|
pub fn getAllocationDataPtr(self: RocList) ?[*]u8 {
|
|
const list_alloc_ptr = @intFromPtr(self.bytes);
|
|
const slice_alloc_ptr = self.capacity_or_alloc_ptr << 1;
|
|
const slice_mask = self.seamlessSliceMask();
|
|
const alloc_ptr = (list_alloc_ptr & ~slice_mask) | (slice_alloc_ptr & slice_mask);
|
|
return @as(?[*]u8, @ptrFromInt(alloc_ptr));
|
|
}
|
|
|
|
// This function is only valid if the list has refcounted elements.
|
|
fn getAllocationElementCount(self: RocList) usize {
|
|
if (self.isSeamlessSlice()) {
|
|
// Seamless slices always refer to an underlying allocation.
|
|
const alloc_ptr = self.getAllocationDataPtr() orelse unreachable;
|
|
// - 1 is refcount.
|
|
// - 2 is size on heap.
|
|
const ptr = @as([*]usize, @ptrCast(@alignCast(alloc_ptr))) - 2;
|
|
return ptr[0];
|
|
} else {
|
|
return self.length;
|
|
}
|
|
}
|
|
|
|
// This needs to be called when creating seamless slices from unique list.
|
|
// It will put the allocation size on the heap to enable the seamless slice to free the underlying allocation.
|
|
fn setAllocationElementCount(self: RocList, elements_refcounted: bool) void {
|
|
if (elements_refcounted and !self.isSeamlessSlice()) {
|
|
// - 1 is refcount.
|
|
// - 2 is size on heap.
|
|
const ptr = @as([*]usize, @alignCast(@ptrCast(self.getAllocationDataPtr()))) - 2;
|
|
ptr[0] = self.length;
|
|
}
|
|
}
|
|
|
|
pub fn incref(self: RocList, amount: isize, elements_refcounted: bool) void {
|
|
// If the list is unique and not a seamless slice, the length needs to be store on the heap if the elements are refcounted.
|
|
if (elements_refcounted and self.isUnique() and !self.isSeamlessSlice()) {
|
|
if (self.getAllocationDataPtr()) |source| {
|
|
// - 1 is refcount.
|
|
// - 2 is size on heap.
|
|
const ptr = @as([*]usize, @alignCast(@ptrCast(source))) - 2;
|
|
ptr[0] = self.length;
|
|
}
|
|
}
|
|
utils.increfDataPtrC(self.getAllocationDataPtr(), amount);
|
|
}
|
|
|
|
pub fn decref(self: RocList, alignment: u32, element_width: usize, elements_refcounted: bool, dec: Dec) void {
|
|
// If unique, decref will free the list. Before that happens, all elements must be decremented.
|
|
if (elements_refcounted and self.isUnique()) {
|
|
if (self.getAllocationDataPtr()) |source| {
|
|
const count = self.getAllocationElementCount();
|
|
|
|
var i: usize = 0;
|
|
while (i < count) : (i += 1) {
|
|
const element = source + i * element_width;
|
|
dec(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We use the raw capacity to ensure we always decrement the refcount of seamless slices.
|
|
utils.decref(self.getAllocationDataPtr(), self.capacity_or_alloc_ptr, alignment, elements_refcounted);
|
|
}
|
|
|
|
pub fn elements(self: RocList, comptime T: type) ?[*]T {
|
|
return @as(?[*]T, @ptrCast(@alignCast(self.bytes)));
|
|
}
|
|
|
|
pub fn isUnique(self: RocList) bool {
|
|
return utils.rcUnique(@bitCast(self.refcount()));
|
|
}
|
|
|
|
fn refcount(self: RocList) usize {
|
|
if (self.getCapacity() == 0 and !self.isSeamlessSlice()) {
|
|
// the zero-capacity is Clone, copying it will not leak memory
|
|
return 1;
|
|
}
|
|
|
|
const ptr: [*]usize = @as([*]usize, @ptrCast(@alignCast(self.getAllocationDataPtr())));
|
|
return (ptr - 1)[0];
|
|
}
|
|
|
|
pub fn makeUniqueExtra(self: RocList, alignment: u32, element_width: usize, elements_refcounted: bool, dec: Dec, update_mode: UpdateMode) RocList {
|
|
if (update_mode == .InPlace) {
|
|
return self;
|
|
} else {
|
|
return self.makeUnique(alignment, element_width, elements_refcounted, dec);
|
|
}
|
|
}
|
|
|
|
pub fn makeUnique(
|
|
self: RocList,
|
|
alignment: u32,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
inc: Inc,
|
|
dec: Dec,
|
|
) RocList {
|
|
if (self.isUnique()) {
|
|
return self;
|
|
}
|
|
|
|
if (self.isEmpty()) {
|
|
// Empty is not necessarily unique on it's own.
|
|
// The list could have capacity and be shared.
|
|
self.decref(alignment, element_width, elements_refcounted, dec);
|
|
return RocList.empty();
|
|
}
|
|
|
|
// unfortunately, we have to clone
|
|
const new_list = RocList.allocate(alignment, self.length, element_width, elements_refcounted);
|
|
|
|
var old_bytes: [*]u8 = @as([*]u8, @ptrCast(self.bytes));
|
|
var new_bytes: [*]u8 = @as([*]u8, @ptrCast(new_list.bytes));
|
|
|
|
const number_of_bytes = self.len() * element_width;
|
|
@memcpy(new_bytes[0..number_of_bytes], old_bytes[0..number_of_bytes]);
|
|
|
|
// Increment refcount of all elements now in a new list.
|
|
if (elements_refcounted) {
|
|
var i: usize = 0;
|
|
while (i < self.len()) : (i += 1) {
|
|
inc(new_bytes + i * element_width);
|
|
}
|
|
}
|
|
|
|
self.decref(alignment, element_width, elements_refcounted, dec);
|
|
|
|
return new_list;
|
|
}
|
|
|
|
pub fn allocate(
|
|
alignment: u32,
|
|
length: usize,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
) RocList {
|
|
if (length == 0) {
|
|
return empty();
|
|
}
|
|
|
|
const capacity = utils.calculateCapacity(0, length, element_width);
|
|
const data_bytes = capacity * element_width;
|
|
return RocList{
|
|
.bytes = utils.allocateWithRefcount(data_bytes, alignment, elements_refcounted),
|
|
.length = length,
|
|
.capacity_or_alloc_ptr = capacity,
|
|
};
|
|
}
|
|
|
|
pub fn allocateExact(
|
|
alignment: u32,
|
|
length: usize,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
) RocList {
|
|
if (length == 0) {
|
|
return empty();
|
|
}
|
|
|
|
const data_bytes = length * element_width;
|
|
return RocList{
|
|
.bytes = utils.allocateWithRefcount(data_bytes, alignment, elements_refcounted),
|
|
.length = length,
|
|
.capacity_or_alloc_ptr = length,
|
|
};
|
|
}
|
|
|
|
pub fn reallocate(
|
|
self: RocList,
|
|
alignment: u32,
|
|
new_length: usize,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
inc: Inc,
|
|
) RocList {
|
|
if (self.bytes) |source_ptr| {
|
|
if (self.isUnique() and !self.isSeamlessSlice()) {
|
|
const capacity = self.capacity_or_alloc_ptr;
|
|
if (capacity >= new_length) {
|
|
return RocList{ .bytes = self.bytes, .length = new_length, .capacity_or_alloc_ptr = capacity };
|
|
} else {
|
|
const new_capacity = utils.calculateCapacity(capacity, new_length, element_width);
|
|
const new_source = utils.unsafeReallocate(source_ptr, alignment, capacity, new_capacity, element_width, elements_refcounted);
|
|
return RocList{ .bytes = new_source, .length = new_length, .capacity_or_alloc_ptr = new_capacity };
|
|
}
|
|
}
|
|
return self.reallocateFresh(alignment, new_length, element_width, elements_refcounted, inc);
|
|
}
|
|
return RocList.allocate(alignment, new_length, element_width, elements_refcounted);
|
|
}
|
|
|
|
/// reallocate by explicitly making a new allocation and copying elements over
|
|
fn reallocateFresh(
|
|
self: RocList,
|
|
alignment: u32,
|
|
new_length: usize,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
inc: Inc,
|
|
) RocList {
|
|
const old_length = self.length;
|
|
|
|
const result = RocList.allocate(alignment, new_length, element_width, elements_refcounted);
|
|
|
|
if (self.bytes) |source_ptr| {
|
|
// transfer the memory
|
|
const dest_ptr = result.bytes orelse unreachable;
|
|
|
|
@memcpy(dest_ptr[0..(old_length * element_width)], source_ptr[0..(old_length * element_width)]);
|
|
@memset(dest_ptr[(old_length * element_width)..(new_length * element_width)], 0);
|
|
|
|
// Increment refcount of all elements now in a new list.
|
|
if (elements_refcounted) {
|
|
var i: usize = 0;
|
|
while (i < old_length) : (i += 1) {
|
|
inc(dest_ptr + i * element_width);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calls utils.decref directly to avoid decrementing the refcount of elements.
|
|
utils.decref(self.getAllocationDataPtr(), self.capacity_or_alloc_ptr, alignment, elements_refcounted);
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
pub fn listIncref(list: RocList, amount: isize, elements_refcounted: bool) callconv(.C) void {
|
|
list.incref(amount, elements_refcounted);
|
|
}
|
|
|
|
pub fn listDecref(list: RocList, alignment: u32, element_width: usize, elements_refcounted: bool, dec: Dec) callconv(.C) void {
|
|
list.decref(alignment, element_width, elements_refcounted, dec);
|
|
}
|
|
|
|
pub fn listWithCapacity(
|
|
capacity: u64,
|
|
alignment: u32,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
inc: Inc,
|
|
) callconv(.C) RocList {
|
|
return listReserve(RocList.empty(), alignment, capacity, element_width, elements_refcounted, inc, .InPlace);
|
|
}
|
|
|
|
pub fn listReserve(
|
|
list: RocList,
|
|
alignment: u32,
|
|
spare: u64,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
inc: Inc,
|
|
update_mode: UpdateMode,
|
|
) callconv(.C) RocList {
|
|
const original_len = list.len();
|
|
const cap = @as(u64, @intCast(list.getCapacity()));
|
|
const desired_cap = @as(u64, @intCast(original_len)) +| spare;
|
|
|
|
if ((update_mode == .InPlace or list.isUnique()) and cap >= desired_cap) {
|
|
return list;
|
|
} else {
|
|
// Make sure on 32-bit targets we don't accidentally wrap when we cast our U64 desired capacity to U32.
|
|
const reserve_size: u64 = @min(desired_cap, @as(u64, @intCast(std.math.maxInt(usize))));
|
|
|
|
var output = list.reallocate(alignment, @as(usize, @intCast(reserve_size)), element_width, elements_refcounted, inc);
|
|
output.length = original_len;
|
|
return output;
|
|
}
|
|
}
|
|
|
|
pub fn listReleaseExcessCapacity(
|
|
list: RocList,
|
|
alignment: u32,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
inc: Inc,
|
|
dec: Dec,
|
|
update_mode: UpdateMode,
|
|
) callconv(.C) RocList {
|
|
const old_length = list.len();
|
|
// We use the direct list.capacity_or_alloc_ptr to make sure both that there is no extra capacity and that it isn't a seamless slice.
|
|
if ((update_mode == .InPlace or list.isUnique()) and list.capacity_or_alloc_ptr == old_length) {
|
|
return list;
|
|
} else if (old_length == 0) {
|
|
list.decref(alignment, element_width, elements_refcounted, dec);
|
|
return RocList.empty();
|
|
} else {
|
|
// TODO: This can be made more efficient, but has to work around the `decref`.
|
|
// If the list is unique, we can avoid incrementing and decrementing the live items.
|
|
// We can just decrement the dead elements and free the old list.
|
|
// This pattern is also like true in other locations like listConcat and listDropAt.
|
|
const output = RocList.allocateExact(alignment, old_length, element_width, elements_refcounted);
|
|
if (list.bytes) |source_ptr| {
|
|
const dest_ptr = output.bytes orelse unreachable;
|
|
|
|
@memcpy(dest_ptr[0..(old_length * element_width)], source_ptr[0..(old_length * element_width)]);
|
|
if (elements_refcounted) {
|
|
var i: usize = 0;
|
|
while (i < old_length) : (i += 1) {
|
|
const element = source_ptr + i * element_width;
|
|
inc(element);
|
|
}
|
|
}
|
|
}
|
|
list.decref(alignment, element_width, elements_refcounted, dec);
|
|
return output;
|
|
}
|
|
}
|
|
|
|
pub fn listAppendUnsafe(
|
|
list: RocList,
|
|
element: Opaque,
|
|
element_width: usize,
|
|
copy: CopyFn,
|
|
) callconv(.C) RocList {
|
|
const old_length = list.len();
|
|
var output = list;
|
|
output.length += 1;
|
|
|
|
if (output.bytes) |bytes| {
|
|
if (element) |source| {
|
|
const target = bytes + old_length * element_width;
|
|
copy(target, source);
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
fn listAppend(
|
|
list: RocList,
|
|
alignment: u32,
|
|
element: Opaque,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
inc: Inc,
|
|
update_mode: UpdateMode,
|
|
copy: CopyFn,
|
|
) callconv(.C) RocList {
|
|
const with_capacity = listReserve(list, alignment, 1, element_width, elements_refcounted, inc, update_mode);
|
|
return listAppendUnsafe(with_capacity, element, element_width, copy);
|
|
}
|
|
|
|
pub fn listPrepend(
|
|
list: RocList,
|
|
alignment: u32,
|
|
element: Opaque,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
inc: Inc,
|
|
copy: CopyFn,
|
|
) callconv(.C) RocList {
|
|
const old_length = list.len();
|
|
// TODO: properly wire in update mode.
|
|
var with_capacity = listReserve(list, alignment, 1, element_width, elements_refcounted, inc, .Immutable);
|
|
with_capacity.length += 1;
|
|
|
|
// can't use one memcpy here because source and target overlap
|
|
if (with_capacity.bytes) |target| {
|
|
const from = target;
|
|
const to = target + element_width;
|
|
const size = element_width * old_length;
|
|
std.mem.copyBackwards(u8, to[0..size], from[0..size]);
|
|
|
|
// finally copy in the new first element
|
|
if (element) |source| {
|
|
copy(target, source);
|
|
}
|
|
}
|
|
|
|
return with_capacity;
|
|
}
|
|
|
|
pub fn listSwap(
|
|
list: RocList,
|
|
alignment: u32,
|
|
element_width: usize,
|
|
index_1: u64,
|
|
index_2: u64,
|
|
elements_refcounted: bool,
|
|
inc: Inc,
|
|
dec: Dec,
|
|
update_mode: UpdateMode,
|
|
copy: CopyFn,
|
|
) callconv(.C) RocList {
|
|
// Early exit to avoid swapping the same element.
|
|
if (index_1 == index_2)
|
|
return list;
|
|
|
|
const size = @as(u64, @intCast(list.len()));
|
|
if (index_1 == index_2 or index_1 >= size or index_2 >= size) {
|
|
// Either one index was out of bounds, or both indices were the same; just return
|
|
return list;
|
|
}
|
|
|
|
const newList = blk: {
|
|
if (update_mode == .InPlace) {
|
|
break :blk list;
|
|
} else {
|
|
break :blk list.makeUnique(alignment, element_width, elements_refcounted, inc, dec);
|
|
}
|
|
};
|
|
|
|
const source_ptr = @as([*]u8, @ptrCast(newList.bytes));
|
|
|
|
swapElements(source_ptr, element_width, @as(usize,
|
|
// We already verified that both indices are less than the stored list length,
|
|
// which is usize, so casting them to usize will definitely be lossless.
|
|
@intCast(index_1)), @as(usize, @intCast(index_2)), copy);
|
|
|
|
return newList;
|
|
}
|
|
|
|
pub fn listSublist(
|
|
list: RocList,
|
|
alignment: u32,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
start_u64: u64,
|
|
len_u64: u64,
|
|
dec: Dec,
|
|
) callconv(.C) RocList {
|
|
const size = list.len();
|
|
if (size == 0 or len_u64 == 0 or start_u64 >= @as(u64, @intCast(size))) {
|
|
if (list.isUnique()) {
|
|
// Decrement the reference counts of all elements.
|
|
if (list.bytes) |source_ptr| {
|
|
if (elements_refcounted) {
|
|
var i: usize = 0;
|
|
while (i < size) : (i += 1) {
|
|
const element = source_ptr + i * element_width;
|
|
dec(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
var output = list;
|
|
output.length = 0;
|
|
return output;
|
|
}
|
|
list.decref(alignment, element_width, elements_refcounted, dec);
|
|
return RocList.empty();
|
|
}
|
|
|
|
if (list.bytes) |source_ptr| {
|
|
// This cast is lossless because we would have early-returned already
|
|
// if `start_u64` were greater than `size`, and `size` fits in usize.
|
|
const start: usize = @intCast(start_u64);
|
|
|
|
// (size - start) can't overflow because we would have early-returned already
|
|
// if `start` were greater than `size`.
|
|
const size_minus_start = size - start;
|
|
|
|
// This outer cast to usize is lossless. size, start, and size_minus_start all fit in usize,
|
|
// and @min guarantees that if `len_u64` gets returned, it's because it was smaller
|
|
// than something that fit in usize.
|
|
const keep_len = @as(usize, @intCast(@min(len_u64, @as(u64, @intCast(size_minus_start)))));
|
|
|
|
if (start == 0 and list.isUnique()) {
|
|
// The list is unique, we actually have to decrement refcounts to elements we aren't keeping around.
|
|
// Decrement the reference counts of elements after `start + keep_len`.
|
|
if (elements_refcounted) {
|
|
const drop_end_len = size_minus_start - keep_len;
|
|
var i: usize = 0;
|
|
while (i < drop_end_len) : (i += 1) {
|
|
const element = source_ptr + (start + keep_len + i) * element_width;
|
|
dec(element);
|
|
}
|
|
}
|
|
|
|
var output = list;
|
|
output.length = keep_len;
|
|
return output;
|
|
} else {
|
|
if (list.isUnique()) {
|
|
list.setAllocationElementCount(elements_refcounted);
|
|
}
|
|
const list_alloc_ptr = (@intFromPtr(source_ptr) >> 1) | SEAMLESS_SLICE_BIT;
|
|
const slice_alloc_ptr = list.capacity_or_alloc_ptr;
|
|
const slice_mask = list.seamlessSliceMask();
|
|
const alloc_ptr = (list_alloc_ptr & ~slice_mask) | (slice_alloc_ptr & slice_mask);
|
|
return RocList{
|
|
.bytes = source_ptr + start * element_width,
|
|
.length = keep_len,
|
|
.capacity_or_alloc_ptr = alloc_ptr,
|
|
};
|
|
}
|
|
}
|
|
|
|
return RocList.empty();
|
|
}
|
|
|
|
pub fn listDropAt(
|
|
list: RocList,
|
|
alignment: u32,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
drop_index_u64: u64,
|
|
inc: Inc,
|
|
dec: Dec,
|
|
) callconv(.C) RocList {
|
|
const size = list.len();
|
|
const size_u64 = @as(u64, @intCast(size));
|
|
|
|
// NOTE
|
|
// we need to return an empty list explicitly,
|
|
// because we rely on the pointer field being null if the list is empty
|
|
// which also requires duplicating the utils.decref call to spend the RC token
|
|
if (size <= 1) {
|
|
list.decref(alignment, element_width, elements_refcounted, dec);
|
|
return RocList.empty();
|
|
}
|
|
|
|
// If droping the first or last element, return a seamless slice.
|
|
// For simplicity, do this by calling listSublist.
|
|
// In the future, we can test if it is faster to manually inline the important parts here.
|
|
if (drop_index_u64 == 0) {
|
|
return listSublist(list, alignment, element_width, elements_refcounted, 1, size -| 1, dec);
|
|
} else if (drop_index_u64 == size_u64 - 1) { // It's fine if (size - 1) wraps on size == 0 here,
|
|
// because if size is 0 then it's always fine for this branch to be taken; no
|
|
// matter what drop_index was, we're size == 0, so empty list will always be returned.
|
|
return listSublist(list, alignment, element_width, elements_refcounted, 0, size -| 1, dec);
|
|
}
|
|
|
|
if (list.bytes) |source_ptr| {
|
|
if (drop_index_u64 >= size_u64) {
|
|
return list;
|
|
}
|
|
|
|
// This cast must be lossless, because we would have just early-returned if drop_index
|
|
// were >= than `size`, and we know `size` fits in usize.
|
|
const drop_index: usize = @intCast(drop_index_u64);
|
|
|
|
if (list.isUnique()) {
|
|
if (elements_refcounted) {
|
|
const element = source_ptr + drop_index * element_width;
|
|
dec(element);
|
|
}
|
|
|
|
const copy_target = source_ptr + (drop_index * element_width);
|
|
const copy_source = copy_target + element_width;
|
|
const copy_size = (size - drop_index - 1) * element_width;
|
|
std.mem.copyForwards(u8, copy_target[0..copy_size], copy_source[0..copy_size]);
|
|
|
|
var new_list = list;
|
|
|
|
new_list.length -= 1;
|
|
return new_list;
|
|
}
|
|
|
|
const output = RocList.allocate(alignment, size - 1, element_width, elements_refcounted);
|
|
const target_ptr = output.bytes orelse unreachable;
|
|
|
|
const head_size = drop_index * element_width;
|
|
@memcpy(target_ptr[0..head_size], source_ptr[0..head_size]);
|
|
|
|
const tail_target = target_ptr + drop_index * element_width;
|
|
const tail_source = source_ptr + (drop_index + 1) * element_width;
|
|
const tail_size = (size - drop_index - 1) * element_width;
|
|
@memcpy(tail_target[0..tail_size], tail_source[0..tail_size]);
|
|
|
|
if (elements_refcounted) {
|
|
var i: usize = 0;
|
|
while (i < output.len()) : (i += 1) {
|
|
const cloned_elem = target_ptr + i * element_width;
|
|
inc(cloned_elem);
|
|
}
|
|
}
|
|
|
|
list.decref(alignment, element_width, elements_refcounted, dec);
|
|
|
|
return output;
|
|
} else {
|
|
return RocList.empty();
|
|
}
|
|
}
|
|
|
|
pub fn listSortWith(
|
|
input: RocList,
|
|
cmp: CompareFn,
|
|
cmp_data: Opaque,
|
|
inc_n_data: IncN,
|
|
data_is_owned: bool,
|
|
alignment: u32,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
inc: Inc,
|
|
dec: Dec,
|
|
copy: CopyFn,
|
|
) callconv(.C) RocList {
|
|
if (input.len() < 2) {
|
|
return input;
|
|
}
|
|
var list = input.makeUnique(alignment, element_width, elements_refcounted, inc, dec);
|
|
|
|
if (list.bytes) |source_ptr| {
|
|
sort.fluxsort(source_ptr, list.len(), cmp, cmp_data, data_is_owned, inc_n_data, element_width, alignment, copy);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
// SWAP ELEMENTS
|
|
|
|
fn swap(
|
|
element_width: usize,
|
|
p1: [*]u8,
|
|
p2: [*]u8,
|
|
copy: CopyFn,
|
|
) void {
|
|
const threshold: usize = 64;
|
|
|
|
var buffer_actual: [threshold]u8 = undefined;
|
|
const buffer: [*]u8 = buffer_actual[0..];
|
|
|
|
if (element_width <= threshold) {
|
|
copy(buffer, p1);
|
|
copy(p1, p2);
|
|
copy(p2, buffer);
|
|
return;
|
|
}
|
|
|
|
var width = element_width;
|
|
|
|
var ptr1 = p1;
|
|
var ptr2 = p2;
|
|
while (true) {
|
|
if (width < threshold) {
|
|
@memcpy(buffer[0..width], ptr1[0..width]);
|
|
@memcpy(ptr1[0..width], ptr2[0..width]);
|
|
@memcpy(ptr2[0..width], buffer[0..width]);
|
|
return;
|
|
} else {
|
|
@memcpy(buffer[0..threshold], ptr1[0..threshold]);
|
|
@memcpy(ptr1[0..threshold], ptr2[0..threshold]);
|
|
@memcpy(ptr2[0..threshold], buffer[0..threshold]);
|
|
|
|
ptr1 += threshold;
|
|
ptr2 += threshold;
|
|
|
|
width -= threshold;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn swapElements(
|
|
source_ptr: [*]u8,
|
|
element_width: usize,
|
|
index_1: usize,
|
|
index_2: usize,
|
|
copy: CopyFn,
|
|
) void {
|
|
const element_at_i = source_ptr + (index_1 * element_width);
|
|
const element_at_j = source_ptr + (index_2 * element_width);
|
|
|
|
return swap(element_width, element_at_i, element_at_j, copy);
|
|
}
|
|
|
|
pub fn listConcat(
|
|
list_a: RocList,
|
|
list_b: RocList,
|
|
alignment: u32,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
inc: Inc,
|
|
dec: Dec,
|
|
) callconv(.C) RocList {
|
|
// NOTE we always use list_a! because it is owned, we must consume it, and it may have unused capacity
|
|
if (list_b.isEmpty()) {
|
|
if (list_a.getCapacity() == 0) {
|
|
// a could be a seamless slice, so we still need to decref.
|
|
list_a.decref(alignment, element_width, elements_refcounted, dec);
|
|
return list_b;
|
|
} else {
|
|
// we must consume this list. Even though it has no elements, it could still have capacity
|
|
list_b.decref(alignment, element_width, elements_refcounted, dec);
|
|
|
|
return list_a;
|
|
}
|
|
} else if (list_a.isUnique()) {
|
|
const total_length: usize = list_a.len() + list_b.len();
|
|
|
|
const resized_list_a = list_a.reallocate(alignment, total_length, element_width, elements_refcounted, inc);
|
|
|
|
// These must exist, otherwise, the lists would have been empty.
|
|
const source_a = resized_list_a.bytes orelse unreachable;
|
|
const source_b = list_b.bytes orelse unreachable;
|
|
@memcpy(source_a[(list_a.len() * element_width)..(total_length * element_width)], source_b[0..(list_b.len() * element_width)]);
|
|
|
|
// Increment refcount of all cloned elements.
|
|
if (elements_refcounted) {
|
|
var i: usize = 0;
|
|
while (i < list_b.len()) : (i += 1) {
|
|
const cloned_elem = source_b + i * element_width;
|
|
inc(cloned_elem);
|
|
}
|
|
}
|
|
|
|
// decrement list b.
|
|
list_b.decref(alignment, element_width, elements_refcounted, dec);
|
|
|
|
return resized_list_a;
|
|
} else if (list_b.isUnique()) {
|
|
const total_length: usize = list_a.len() + list_b.len();
|
|
|
|
const resized_list_b = list_b.reallocate(alignment, total_length, element_width, elements_refcounted, inc);
|
|
|
|
// These must exist, otherwise, the lists would have been empty.
|
|
const source_a = list_a.bytes orelse unreachable;
|
|
const source_b = resized_list_b.bytes orelse unreachable;
|
|
|
|
// This is a bit special, we need to first copy the elements of list_b to the end,
|
|
// then copy the elements of list_a to the beginning.
|
|
// This first call must use mem.copy because the slices might overlap.
|
|
const byte_count_a = list_a.len() * element_width;
|
|
const byte_count_b = list_b.len() * element_width;
|
|
mem.copyBackwards(u8, source_b[byte_count_a .. byte_count_a + byte_count_b], source_b[0..byte_count_b]);
|
|
@memcpy(source_b[0..byte_count_a], source_a[0..byte_count_a]);
|
|
|
|
// Increment refcount of all cloned elements.
|
|
if (elements_refcounted) {
|
|
var i: usize = 0;
|
|
while (i < list_a.len()) : (i += 1) {
|
|
const cloned_elem = source_a + i * element_width;
|
|
inc(cloned_elem);
|
|
}
|
|
}
|
|
|
|
// decrement list a.
|
|
list_a.decref(alignment, element_width, elements_refcounted, dec);
|
|
|
|
return resized_list_b;
|
|
}
|
|
const total_length: usize = list_a.len() + list_b.len();
|
|
|
|
const output = RocList.allocate(alignment, total_length, element_width, elements_refcounted);
|
|
|
|
// These must exist, otherwise, the lists would have been empty.
|
|
const target = output.bytes orelse unreachable;
|
|
const source_a = list_a.bytes orelse unreachable;
|
|
const source_b = list_b.bytes orelse unreachable;
|
|
|
|
@memcpy(target[0..(list_a.len() * element_width)], source_a[0..(list_a.len() * element_width)]);
|
|
@memcpy(target[(list_a.len() * element_width)..(total_length * element_width)], source_b[0..(list_b.len() * element_width)]);
|
|
|
|
// Increment refcount of all cloned elements.
|
|
if (elements_refcounted) {
|
|
var i: usize = 0;
|
|
while (i < list_a.len()) : (i += 1) {
|
|
const cloned_elem = source_a + i * element_width;
|
|
inc(cloned_elem);
|
|
}
|
|
i = 0;
|
|
while (i < list_b.len()) : (i += 1) {
|
|
const cloned_elem = source_b + i * element_width;
|
|
inc(cloned_elem);
|
|
}
|
|
}
|
|
|
|
// decrement list a and b.
|
|
list_a.decref(alignment, element_width, elements_refcounted, dec);
|
|
list_b.decref(alignment, element_width, elements_refcounted, dec);
|
|
|
|
return output;
|
|
}
|
|
|
|
pub fn listReplaceInPlace(
|
|
list: RocList,
|
|
index: u64,
|
|
element: Opaque,
|
|
element_width: usize,
|
|
out_element: ?[*]u8,
|
|
copy: CopyFn,
|
|
) callconv(.C) RocList {
|
|
// INVARIANT: bounds checking happens on the roc side
|
|
//
|
|
// at the time of writing, the function is implemented roughly as
|
|
// `if inBounds then LowLevelListReplace 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,
|
|
// and it's always safe to cast index to usize.
|
|
return listReplaceInPlaceHelp(list, @as(usize, @intCast(index)), element, element_width, out_element, copy);
|
|
}
|
|
|
|
pub fn listReplace(
|
|
list: RocList,
|
|
alignment: u32,
|
|
index: u64,
|
|
element: Opaque,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
inc: Inc,
|
|
dec: Dec,
|
|
out_element: ?[*]u8,
|
|
copy: CopyFn,
|
|
) callconv(.C) RocList {
|
|
// INVARIANT: bounds checking happens on the roc side
|
|
//
|
|
// at the time of writing, the function is implemented roughly as
|
|
// `if inBounds then LowLevelListReplace 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,
|
|
// and it's always safe to cast index to usize.
|
|
// because inserting into an empty list is always out of bounds
|
|
return listReplaceInPlaceHelp(list.makeUnique(alignment, element_width, elements_refcounted, inc, dec), @as(usize, @intCast(index)), element, element_width, out_element, copy);
|
|
}
|
|
|
|
inline fn listReplaceInPlaceHelp(
|
|
list: RocList,
|
|
index: usize,
|
|
element: Opaque,
|
|
element_width: usize,
|
|
out_element: ?[*]u8,
|
|
copy: CopyFn,
|
|
) RocList {
|
|
// the element we will replace
|
|
const element_at_index = (list.bytes orelse unreachable) + (index * element_width);
|
|
|
|
// copy out the old element
|
|
copy((out_element orelse unreachable), element_at_index);
|
|
|
|
// copy in the new element
|
|
copy(element_at_index, (element orelse unreachable));
|
|
|
|
return list;
|
|
}
|
|
|
|
pub fn listIsUnique(
|
|
list: RocList,
|
|
) callconv(.C) bool {
|
|
return list.isEmpty() or list.isUnique();
|
|
}
|
|
|
|
pub fn listClone(
|
|
list: RocList,
|
|
alignment: u32,
|
|
element_width: usize,
|
|
elements_refcounted: bool,
|
|
inc: Inc,
|
|
dec: Dec,
|
|
) callconv(.C) RocList {
|
|
return list.makeUnique(alignment, element_width, elements_refcounted, inc, dec);
|
|
}
|
|
|
|
pub fn listCapacity(
|
|
list: RocList,
|
|
) callconv(.C) usize {
|
|
return list.getCapacity();
|
|
}
|
|
|
|
pub fn listAllocationPtr(
|
|
list: RocList,
|
|
) callconv(.C) ?[*]u8 {
|
|
return list.getAllocationDataPtr();
|
|
}
|
|
|
|
fn rcNone(_: ?[*]u8) callconv(.C) void {}
|
|
|
|
test "listConcat: non-unique with unique overlapping" {
|
|
var nonUnique = RocList.fromSlice(u8, ([_]u8{1})[0..], false);
|
|
const bytes: [*]u8 = @as([*]u8, @ptrCast(nonUnique.bytes));
|
|
const ptr_width = @sizeOf(usize);
|
|
const refcount_ptr = @as([*]isize, @ptrCast(@as([*]align(ptr_width) u8, @alignCast(bytes)) - ptr_width));
|
|
utils.increfRcPtrC(&refcount_ptr[0], 1);
|
|
defer nonUnique.decref(@alignOf(u8), @sizeOf(u8), false, rcNone); // listConcat will dec the other refcount
|
|
|
|
var unique = RocList.fromSlice(u8, ([_]u8{ 2, 3, 4 })[0..], false);
|
|
defer unique.decref(@alignOf(u8), @sizeOf(u8), false, rcNone);
|
|
|
|
var concatted = listConcat(nonUnique, unique, 1, 1, false, rcNone, rcNone);
|
|
var wanted = RocList.fromSlice(u8, ([_]u8{ 1, 2, 3, 4 })[0..], false);
|
|
defer wanted.decref(@alignOf(u8), @sizeOf(u8), false, rcNone);
|
|
|
|
try expect(concatted.eql(wanted));
|
|
}
|
|
|
|
pub fn listConcatUtf8(
|
|
list: RocList,
|
|
string: str.RocStr,
|
|
) callconv(.C) RocList {
|
|
if (string.len() == 0) {
|
|
return list;
|
|
} else {
|
|
const combined_length = list.len() + string.len();
|
|
|
|
// List U8 has alignment 1 and element_width 1
|
|
const result = list.reallocate(1, combined_length, 1, false, &rcNone);
|
|
// We just allocated combined_length, which is > 0 because string.len() > 0
|
|
var bytes = result.bytes orelse unreachable;
|
|
@memcpy(bytes[list.len()..combined_length], string.asU8ptr()[0..string.len()]);
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
test "listConcatUtf8" {
|
|
const list = RocList.fromSlice(u8, &[_]u8{ 1, 2, 3, 4 }, false);
|
|
defer list.decref(1, 1, false, &rcNone);
|
|
const string_bytes = "🐦";
|
|
const string = str.RocStr.init(string_bytes, string_bytes.len);
|
|
defer string.decref();
|
|
const ret = listConcatUtf8(list, string);
|
|
const expected = RocList.fromSlice(u8, &[_]u8{ 1, 2, 3, 4, 240, 159, 144, 166 }, false);
|
|
defer expected.decref(1, 1, false, &rcNone);
|
|
try expect(ret.eql(expected));
|
|
}
|