Switch builtins to use roc_alloc and friends

This commit is contained in:
Richard Feldman 2021-05-22 22:05:19 -04:00
parent 8cafabc0c9
commit ab51582541
4 changed files with 299 additions and 297 deletions

View file

@ -2,7 +2,6 @@ const std = @import("std");
const testing = std.testing; const testing = std.testing;
const expectEqual = testing.expectEqual; const expectEqual = testing.expectEqual;
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator;
const assert = std.debug.assert; const assert = std.debug.assert;
const utils = @import("utils.zig"); const utils = @import("utils.zig");
@ -94,20 +93,18 @@ const Alignment = packed enum(u8) {
}; };
pub fn decref( pub fn decref(
allocator: *Allocator,
alignment: Alignment, alignment: Alignment,
bytes_or_null: ?[*]u8, bytes_or_null: ?[*]u8,
data_bytes: usize, data_bytes: usize,
) void { ) void {
return utils.decref(allocator, alignment.toUsize(), bytes_or_null, data_bytes); return utils.decref(alignment.toUsize(), bytes_or_null, data_bytes);
} }
pub fn allocateWithRefcount( pub fn allocateWithRefcount(
allocator: *Allocator,
alignment: Alignment, alignment: Alignment,
data_bytes: usize, data_bytes: usize,
) [*]u8 { ) [*]u8 {
return utils.allocateWithRefcount(allocator, alignment.toUsize(), data_bytes); return utils.allocateWithRefcount(alignment.toUsize(), data_bytes);
} }
pub const RocDict = extern struct { pub const RocDict = extern struct {
@ -124,7 +121,6 @@ pub const RocDict = extern struct {
} }
pub fn allocate( pub fn allocate(
allocator: *Allocator,
number_of_levels: usize, number_of_levels: usize,
number_of_entries: usize, number_of_entries: usize,
alignment: Alignment, alignment: Alignment,
@ -136,7 +132,7 @@ pub const RocDict = extern struct {
const data_bytes = number_of_slots * slot_size; const data_bytes = number_of_slots * slot_size;
return RocDict{ return RocDict{
.dict_bytes = allocateWithRefcount(allocator, alignment, data_bytes), .dict_bytes = allocateWithRefcount(alignment, data_bytes),
.number_of_levels = number_of_levels, .number_of_levels = number_of_levels,
.dict_entries_len = number_of_entries, .dict_entries_len = number_of_entries,
}; };
@ -144,7 +140,6 @@ pub const RocDict = extern struct {
pub fn reallocate( pub fn reallocate(
self: RocDict, self: RocDict,
allocator: *Allocator,
alignment: Alignment, alignment: Alignment,
key_width: usize, key_width: usize,
value_width: usize, value_width: usize,
@ -157,7 +152,7 @@ pub const RocDict = extern struct {
const delta_capacity = new_capacity - old_capacity; const delta_capacity = new_capacity - old_capacity;
const data_bytes = new_capacity * slot_size; const data_bytes = new_capacity * slot_size;
const first_slot = allocateWithRefcount(allocator, alignment, data_bytes); const first_slot = allocateWithRefcount(alignment, data_bytes);
// transfer the memory // transfer the memory
@ -204,7 +199,7 @@ pub const RocDict = extern struct {
}; };
// NOTE we fuse an increment of all keys/values with a decrement of the input dict // NOTE we fuse an increment of all keys/values with a decrement of the input dict
decref(allocator, alignment, self.dict_bytes, self.capacity() * slotSize(key_width, value_width)); decref(alignment, self.dict_bytes, self.capacity() * slotSize(key_width, value_width));
return result; return result;
} }
@ -236,7 +231,7 @@ pub const RocDict = extern struct {
return totalCapacityAtLevel(self.number_of_levels); return totalCapacityAtLevel(self.number_of_levels);
} }
pub fn makeUnique(self: RocDict, allocator: *Allocator, alignment: Alignment, key_width: usize, value_width: usize) RocDict { pub fn makeUnique(self: RocDict, alignment: Alignment, key_width: usize, value_width: usize) RocDict {
if (self.isEmpty()) { if (self.isEmpty()) {
return self; return self;
} }
@ -246,7 +241,7 @@ pub const RocDict = extern struct {
} }
// unfortunately, we have to clone // unfortunately, we have to clone
var new_dict = RocDict.allocate(allocator, self.number_of_levels, self.dict_entries_len, alignment, key_width, value_width); var new_dict = RocDict.allocate(self.number_of_levels, self.dict_entries_len, alignment, key_width, value_width);
var old_bytes: [*]u8 = @ptrCast([*]u8, self.dict_bytes); var old_bytes: [*]u8 = @ptrCast([*]u8, self.dict_bytes);
var new_bytes: [*]u8 = @ptrCast([*]u8, new_dict.dict_bytes); var new_bytes: [*]u8 = @ptrCast([*]u8, new_dict.dict_bytes);
@ -256,7 +251,7 @@ pub const RocDict = extern struct {
// NOTE we fuse an increment of all keys/values with a decrement of the input dict // NOTE we fuse an increment of all keys/values with a decrement of the input dict
const data_bytes = self.capacity() * slotSize(key_width, value_width); const data_bytes = self.capacity() * slotSize(key_width, value_width);
decref(allocator, alignment, self.dict_bytes, data_bytes); decref(alignment, self.dict_bytes, data_bytes);
return new_dict; return new_dict;
} }
@ -420,7 +415,7 @@ const Dec = fn (?[*]u8) callconv(.C) void;
pub fn dictInsert(input: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value: Opaque, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void { pub fn dictInsert(input: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value: Opaque, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
var seed: u64 = INITIAL_SEED; var seed: u64 = INITIAL_SEED;
var result = input.makeUnique(std.heap.c_allocator, alignment, key_width, value_width); var result = input.makeUnique(alignment, key_width, value_width);
var current_level: usize = 1; var current_level: usize = 1;
var current_level_size: usize = 8; var current_level_size: usize = 8;
@ -428,7 +423,7 @@ pub fn dictInsert(input: RocDict, alignment: Alignment, key: Opaque, key_width:
while (true) { while (true) {
if (current_level > result.number_of_levels) { if (current_level > result.number_of_levels) {
result = result.reallocate(std.heap.c_allocator, alignment, key_width, value_width); result = result.reallocate(alignment, key_width, value_width);
} }
const hash = hash_fn(seed, key); const hash = hash_fn(seed, key);
@ -484,7 +479,7 @@ pub fn dictRemove(input: RocDict, alignment: Alignment, key: Opaque, key_width:
return; return;
}, },
MaybeIndex.index => |index| { MaybeIndex.index => |index| {
var dict = input.makeUnique(std.heap.c_allocator, alignment, key_width, value_width); var dict = input.makeUnique(alignment, key_width, value_width);
assert(index < dict.capacity()); assert(index < dict.capacity());
@ -499,7 +494,7 @@ pub fn dictRemove(input: RocDict, alignment: Alignment, key: Opaque, key_width:
// if the dict is now completely empty, free its allocation // if the dict is now completely empty, free its allocation
if (dict.dict_entries_len == 0) { if (dict.dict_entries_len == 0) {
const data_bytes = dict.capacity() * slotSize(key_width, value_width); const data_bytes = dict.capacity() * slotSize(key_width, value_width);
decref(std.heap.c_allocator, alignment, dict.dict_bytes, data_bytes); decref(alignment, dict.dict_bytes, data_bytes);
output.* = RocDict.empty(); output.* = RocDict.empty();
return; return;
} }
@ -572,7 +567,7 @@ pub fn dictKeys(dict: RocDict, alignment: Alignment, key_width: usize, value_wid
} }
const data_bytes = length * key_width; const data_bytes = length * key_width;
var ptr = allocateWithRefcount(std.heap.c_allocator, alignment, data_bytes); var ptr = allocateWithRefcount(alignment, data_bytes);
var offset = blk: { var offset = blk: {
if (alignment.keyFirst()) { if (alignment.keyFirst()) {
@ -621,7 +616,7 @@ pub fn dictValues(dict: RocDict, alignment: Alignment, key_width: usize, value_w
} }
const data_bytes = length * value_width; const data_bytes = length * value_width;
var ptr = allocateWithRefcount(std.heap.c_allocator, alignment, data_bytes); var ptr = allocateWithRefcount(alignment, data_bytes);
var offset = blk: { var offset = blk: {
if (alignment.keyFirst()) { if (alignment.keyFirst()) {
@ -655,7 +650,7 @@ fn doNothing(ptr: Opaque) callconv(.C) void {
} }
pub fn dictUnion(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, inc_key: Inc, inc_value: Inc, output: *RocDict) callconv(.C) void { pub fn dictUnion(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, inc_key: Inc, inc_value: Inc, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(std.heap.c_allocator, alignment, key_width, value_width); output.* = dict1.makeUnique(alignment, key_width, value_width);
var i: usize = 0; var i: usize = 0;
while (i < dict2.capacity()) : (i += 1) { while (i < dict2.capacity()) : (i += 1) {
@ -690,7 +685,7 @@ pub fn dictUnion(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width
} }
pub fn dictIntersection(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Inc, dec_value: Inc, output: *RocDict) callconv(.C) void { pub fn dictIntersection(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Inc, dec_value: Inc, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(std.heap.c_allocator, alignment, key_width, value_width); output.* = dict1.makeUnique(alignment, key_width, value_width);
var i: usize = 0; var i: usize = 0;
const size = dict1.capacity(); const size = dict1.capacity();
@ -715,7 +710,7 @@ pub fn dictIntersection(dict1: RocDict, dict2: RocDict, alignment: Alignment, ke
} }
pub fn dictDifference(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void { pub fn dictDifference(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(std.heap.c_allocator, alignment, key_width, value_width); output.* = dict1.makeUnique(alignment, key_width, value_width);
var i: usize = 0; var i: usize = 0;
const size = dict1.capacity(); const size = dict1.capacity();
@ -756,16 +751,17 @@ pub fn setFromList(list: RocList, alignment: Alignment, key_width: usize, value_
// NOTE: decref checks for the empty case // NOTE: decref checks for the empty case
const data_bytes = size * key_width; const data_bytes = size * key_width;
decref(std.heap.c_allocator, alignment, list.bytes, data_bytes); decref(alignment, list.bytes, data_bytes);
} }
const StepperCaller = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; const StepperCaller = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn dictWalk(dict: RocDict, stepper: Opaque, stepper_caller: StepperCaller, accum: Opaque, alignment: Alignment, key_width: usize, value_width: usize, accum_width: usize, inc_key: Inc, inc_value: Inc, output: Opaque) callconv(.C) void { pub fn dictWalk(dict: RocDict, stepper: Opaque, stepper_caller: StepperCaller, accum: Opaque, alignment: Alignment, key_width: usize, value_width: usize, accum_width: usize, inc_key: Inc, inc_value: Inc, output: Opaque) callconv(.C) void {
const alignment_usize = alignment.toUsize();
// allocate space to write the result of the stepper into // allocate space to write the result of the stepper into
// experimentally aliasing the accum and output pointers is not a good idea // experimentally aliasing the accum and output pointers is not a good idea
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, accum_width) catch unreachable); const bytes_ptr: [*]u8 = utils.alloc(alignment_usize, accum_width);
var b1 = output orelse unreachable; var b1 = output orelse unreachable;
var b2 = alloc; var b2 = bytes_ptr;
@memcpy(b2, accum orelse unreachable, accum_width); @memcpy(b2, accum orelse unreachable, accum_width);
@ -788,8 +784,8 @@ pub fn dictWalk(dict: RocDict, stepper: Opaque, stepper_caller: StepperCaller, a
} }
@memcpy(output orelse unreachable, b2, accum_width); @memcpy(output orelse unreachable, b2, accum_width);
std.heap.c_allocator.free(alloc[0..accum_width]); utils.dealloc(alignment_usize, bytes_ptr);
const data_bytes = dict.capacity() * slotSize(key_width, value_width); const data_bytes = dict.capacity() * slotSize(key_width, value_width);
decref(std.heap.c_allocator, alignment, dict.dict_bytes, data_bytes); decref(alignment, dict.dict_bytes, data_bytes);
} }

View file

@ -2,7 +2,6 @@ const std = @import("std");
const utils = @import("utils.zig"); const utils = @import("utils.zig");
const RocResult = utils.RocResult; const RocResult = utils.RocResult;
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator;
const TAG_WIDTH = 8; const TAG_WIDTH = 8;
@ -42,7 +41,6 @@ pub const RocList = extern struct {
} }
pub fn allocate( pub fn allocate(
allocator: *Allocator,
alignment: usize, alignment: usize,
length: usize, length: usize,
element_size: usize, element_size: usize,
@ -50,12 +48,12 @@ pub const RocList = extern struct {
const data_bytes = length * element_size; const data_bytes = length * element_size;
return RocList{ return RocList{
.bytes = utils.allocateWithRefcount(allocator, alignment, data_bytes), .bytes = utils.allocateWithRefcount(alignment, data_bytes),
.length = length, .length = length,
}; };
} }
pub fn makeUnique(self: RocList, allocator: *Allocator, alignment: usize, element_width: usize) RocList { pub fn makeUnique(self: RocList, alignment: usize, element_width: usize) RocList {
if (self.isEmpty()) { if (self.isEmpty()) {
return self; return self;
} }
@ -65,7 +63,7 @@ pub const RocList = extern struct {
} }
// unfortunately, we have to clone // unfortunately, we have to clone
var new_list = RocList.allocate(allocator, alignment, self.length, element_width); var new_list = RocList.allocate(alignment, self.length, element_width);
var old_bytes: [*]u8 = @ptrCast([*]u8, self.bytes); var old_bytes: [*]u8 = @ptrCast([*]u8, self.bytes);
var new_bytes: [*]u8 = @ptrCast([*]u8, new_list.bytes); var new_bytes: [*]u8 = @ptrCast([*]u8, new_list.bytes);
@ -75,33 +73,31 @@ pub const RocList = extern struct {
// NOTE we fuse an increment of all keys/values with a decrement of the input dict // NOTE we fuse an increment of all keys/values with a decrement of the input dict
const data_bytes = self.len() * element_width; const data_bytes = self.len() * element_width;
utils.decref(allocator, alignment, self.bytes, data_bytes); utils.decref(alignment, self.bytes, data_bytes);
return new_list; return new_list;
} }
pub fn reallocate( pub fn reallocate(
self: RocList, self: RocList,
allocator: *Allocator,
alignment: usize, alignment: usize,
new_length: usize, new_length: usize,
element_width: usize, element_width: usize,
) RocList { ) RocList {
if (self.bytes) |source_ptr| { if (self.bytes) |source_ptr| {
if (self.isUnique()) { if (self.isUnique()) {
const new_source = utils.unsafeReallocate(source_ptr, allocator, alignment, self.len(), new_length, element_width); const new_source = utils.unsafeReallocate(source_ptr, alignment, self.len(), new_length, element_width);
return RocList{ .bytes = new_source, .length = new_length }; return RocList{ .bytes = new_source, .length = new_length };
} }
} }
return self.reallocateFresh(allocator, alignment, new_length, element_width); return self.reallocateFresh(alignment, new_length, element_width);
} }
/// reallocate by explicitly making a new allocation and copying elements over /// reallocate by explicitly making a new allocation and copying elements over
fn reallocateFresh( fn reallocateFresh(
self: RocList, self: RocList,
allocator: *Allocator,
alignment: usize, alignment: usize,
new_length: usize, new_length: usize,
element_width: usize, element_width: usize,
@ -110,7 +106,7 @@ pub const RocList = extern struct {
const delta_length = new_length - old_length; const delta_length = new_length - old_length;
const data_bytes = new_length * element_width; const data_bytes = new_length * element_width;
const first_slot = utils.allocateWithRefcount(allocator, alignment, data_bytes); const first_slot = utils.allocateWithRefcount(alignment, data_bytes);
// transfer the memory // transfer the memory
@ -126,7 +122,7 @@ pub const RocList = extern struct {
.length = new_length, .length = new_length,
}; };
utils.decref(allocator, alignment, self.bytes, old_length * element_width); utils.decref(alignment, self.bytes, old_length * element_width);
return result; return result;
} }
@ -155,7 +151,7 @@ pub fn listReverse(list: RocList, alignment: usize, element_width: usize) callco
return list; return list;
} else { } else {
const output = RocList.allocate(std.heap.c_allocator, alignment, size, element_width); const output = RocList.allocate(alignment, size, element_width);
const target_ptr = output.bytes orelse unreachable; const target_ptr = output.bytes orelse unreachable;
@ -165,7 +161,7 @@ pub fn listReverse(list: RocList, alignment: usize, element_width: usize) callco
@memcpy(target_ptr + (i * element_width), source_ptr + (last_position * element_width), element_width); @memcpy(target_ptr + (i * element_width), source_ptr + (last_position * element_width), element_width);
} }
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * element_width); utils.decref(alignment, list.bytes, size * element_width);
return output; return output;
} }
@ -178,14 +174,14 @@ pub fn listMap(list: RocList, transform: Opaque, caller: Caller1, alignment: usi
if (list.bytes) |source_ptr| { if (list.bytes) |source_ptr| {
const size = list.len(); const size = list.len();
var i: usize = 0; var i: usize = 0;
const output = RocList.allocate(std.heap.c_allocator, alignment, size, new_element_width); const output = RocList.allocate(alignment, size, new_element_width);
const target_ptr = output.bytes orelse unreachable; const target_ptr = output.bytes orelse unreachable;
while (i < size) : (i += 1) { while (i < size) : (i += 1) {
caller(transform, source_ptr + (i * old_element_width), target_ptr + (i * new_element_width)); caller(transform, source_ptr + (i * old_element_width), target_ptr + (i * new_element_width));
} }
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * old_element_width); utils.decref(alignment, list.bytes, size * old_element_width);
return output; return output;
} else { } else {
@ -197,14 +193,14 @@ pub fn listMapWithIndex(list: RocList, transform: Opaque, caller: Caller2, align
if (list.bytes) |source_ptr| { if (list.bytes) |source_ptr| {
const size = list.len(); const size = list.len();
var i: usize = 0; var i: usize = 0;
const output = RocList.allocate(std.heap.c_allocator, alignment, size, new_element_width); const output = RocList.allocate(alignment, size, new_element_width);
const target_ptr = output.bytes orelse unreachable; const target_ptr = output.bytes orelse unreachable;
while (i < size) : (i += 1) { while (i < size) : (i += 1) {
caller(transform, @ptrCast(?[*]u8, &i), source_ptr + (i * old_element_width), target_ptr + (i * new_element_width)); caller(transform, @ptrCast(?[*]u8, &i), source_ptr + (i * old_element_width), target_ptr + (i * new_element_width));
} }
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * old_element_width); utils.decref(alignment, list.bytes, size * old_element_width);
return output; return output;
} else { } else {
@ -217,7 +213,7 @@ pub fn listMap2(list1: RocList, list2: RocList, transform: Opaque, caller: Calle
if (list1.bytes) |source_a| { if (list1.bytes) |source_a| {
if (list2.bytes) |source_b| { if (list2.bytes) |source_b| {
const output = RocList.allocate(std.heap.c_allocator, alignment, output_length, c_width); const output = RocList.allocate(alignment, output_length, c_width);
const target_ptr = output.bytes orelse unreachable; const target_ptr = output.bytes orelse unreachable;
var i: usize = 0; var i: usize = 0;
@ -242,8 +238,8 @@ pub fn listMap2(list1: RocList, list2: RocList, transform: Opaque, caller: Calle
} }
} }
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width); utils.decref(alignment, list1.bytes, list1.len() * a_width);
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width); utils.decref(alignment, list2.bytes, list2.len() * b_width);
return output; return output;
} else { } else {
@ -253,7 +249,7 @@ pub fn listMap2(list1: RocList, list2: RocList, transform: Opaque, caller: Calle
const element_a = source_a + i * a_width; const element_a = source_a + i * a_width;
dec_a(element_a); dec_a(element_a);
} }
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width); utils.decref(alignment, list1.bytes, list1.len() * a_width);
return RocList.empty(); return RocList.empty();
} }
@ -265,7 +261,7 @@ pub fn listMap2(list1: RocList, list2: RocList, transform: Opaque, caller: Calle
const element_b = source_b + i * b_width; const element_b = source_b + i * b_width;
dec_b(element_b); dec_b(element_b);
} }
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width); utils.decref(alignment, list2.bytes, list2.len() * b_width);
} }
return RocList.empty(); return RocList.empty();
@ -279,7 +275,7 @@ pub fn listMap3(list1: RocList, list2: RocList, list3: RocList, transform: Opaqu
if (list1.bytes) |source_a| { if (list1.bytes) |source_a| {
if (list2.bytes) |source_b| { if (list2.bytes) |source_b| {
if (list3.bytes) |source_c| { if (list3.bytes) |source_c| {
const output = RocList.allocate(std.heap.c_allocator, alignment, output_length, d_width); const output = RocList.allocate(alignment, output_length, d_width);
const target_ptr = output.bytes orelse unreachable; const target_ptr = output.bytes orelse unreachable;
var i: usize = 0; var i: usize = 0;
@ -318,9 +314,9 @@ pub fn listMap3(list1: RocList, list2: RocList, list3: RocList, transform: Opaqu
} }
} }
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width); utils.decref(alignment, list1.bytes, list1.len() * a_width);
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width); utils.decref(alignment, list2.bytes, list2.len() * b_width);
utils.decref(std.heap.c_allocator, alignment, list3.bytes, list3.len() * c_width); utils.decref(alignment, list3.bytes, list3.len() * c_width);
return output; return output;
} else { } else {
@ -330,7 +326,7 @@ pub fn listMap3(list1: RocList, list2: RocList, list3: RocList, transform: Opaqu
const element_a = source_a + i * a_width; const element_a = source_a + i * a_width;
dec_a(element_a); dec_a(element_a);
} }
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width); utils.decref(alignment, list1.bytes, list1.len() * a_width);
// consume list2 elements (we know there is at least one because the list1.bytes pointer is non-null // consume list2 elements (we know there is at least one because the list1.bytes pointer is non-null
i = 0; i = 0;
@ -338,7 +334,7 @@ pub fn listMap3(list1: RocList, list2: RocList, list3: RocList, transform: Opaqu
const element_b = source_b + i * b_width; const element_b = source_b + i * b_width;
dec_b(element_b); dec_b(element_b);
} }
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width); utils.decref(alignment, list2.bytes, list2.len() * b_width);
return RocList.empty(); return RocList.empty();
} }
@ -350,7 +346,7 @@ pub fn listMap3(list1: RocList, list2: RocList, list3: RocList, transform: Opaqu
dec_a(element_a); dec_a(element_a);
} }
utils.decref(std.heap.c_allocator, alignment, list1.bytes, list1.len() * a_width); utils.decref(alignment, list1.bytes, list1.len() * a_width);
// consume list3 elements (if any) // consume list3 elements (if any)
if (list3.bytes) |source_c| { if (list3.bytes) |source_c| {
@ -361,7 +357,7 @@ pub fn listMap3(list1: RocList, list2: RocList, list3: RocList, transform: Opaqu
dec_c(element_c); dec_c(element_c);
} }
utils.decref(std.heap.c_allocator, alignment, list3.bytes, list3.len() * c_width); utils.decref(alignment, list3.bytes, list3.len() * c_width);
} }
return RocList.empty(); return RocList.empty();
@ -376,7 +372,7 @@ pub fn listMap3(list1: RocList, list2: RocList, list3: RocList, transform: Opaqu
dec_b(element_b); dec_b(element_b);
} }
utils.decref(std.heap.c_allocator, alignment, list2.bytes, list2.len() * b_width); utils.decref(alignment, list2.bytes, list2.len() * b_width);
} }
// consume list3 elements (if any) // consume list3 elements (if any)
@ -388,7 +384,7 @@ pub fn listMap3(list1: RocList, list2: RocList, list3: RocList, transform: Opaqu
dec_c(element_c); dec_c(element_c);
} }
utils.decref(std.heap.c_allocator, alignment, list3.bytes, list3.len() * c_width); utils.decref(alignment, list3.bytes, list3.len() * c_width);
} }
return RocList.empty(); return RocList.empty();
@ -399,7 +395,7 @@ pub fn listKeepIf(list: RocList, transform: Opaque, caller: Caller1, alignment:
if (list.bytes) |source_ptr| { if (list.bytes) |source_ptr| {
const size = list.len(); const size = list.len();
var i: usize = 0; var i: usize = 0;
var output = RocList.allocate(std.heap.c_allocator, alignment, list.len(), list.len() * element_width); var output = RocList.allocate(alignment, list.len(), list.len() * element_width);
const target_ptr = output.bytes orelse unreachable; const target_ptr = output.bytes orelse unreachable;
var kept: usize = 0; var kept: usize = 0;
@ -419,11 +415,11 @@ pub fn listKeepIf(list: RocList, transform: Opaque, caller: Caller1, alignment:
} }
// consume the input list // consume the input list
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * element_width); utils.decref(alignment, list.bytes, size * element_width);
if (kept == 0) { if (kept == 0) {
// if the output is empty, deallocate the space we made for the result // if the output is empty, deallocate the space we made for the result
utils.decref(std.heap.c_allocator, alignment, output.bytes, size * element_width); utils.decref(alignment, output.bytes, size * element_width);
return RocList.empty(); return RocList.empty();
} else { } else {
output.length = kept; output.length = kept;
@ -447,10 +443,10 @@ pub fn listKeepResult(list: RocList, is_good_constructor: fn (RocResult) bool, t
if (list.bytes) |source_ptr| { if (list.bytes) |source_ptr| {
const size = list.len(); const size = list.len();
var i: usize = 0; var i: usize = 0;
var output = RocList.allocate(std.heap.c_allocator, alignment, list.len(), list.len() * after_width); var output = RocList.allocate(alignment, list.len(), list.len() * after_width);
const target_ptr = output.bytes orelse unreachable; const target_ptr = output.bytes orelse unreachable;
var temporary = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, result_width) catch unreachable); var temporary = @ptrCast([*]u8, utils.alloc(alignment, result_width));
var kept: usize = 0; var kept: usize = 0;
while (i < size) : (i += 1) { while (i < size) : (i += 1) {
@ -469,11 +465,11 @@ pub fn listKeepResult(list: RocList, is_good_constructor: fn (RocResult) bool, t
} }
} }
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * before_width); utils.decref(alignment, list.bytes, size * before_width);
std.heap.c_allocator.free(temporary[0..result_width]); utils.dealloc(alignment, temporary);
if (kept == 0) { if (kept == 0) {
utils.decref(std.heap.c_allocator, alignment, output.bytes, size * after_width); utils.decref(alignment, output.bytes, size * after_width);
return RocList.empty(); return RocList.empty();
} else { } else {
output.length = kept; output.length = kept;
@ -494,9 +490,9 @@ pub fn listWalk(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum:
return; return;
} }
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, accum_width) catch unreachable); const bytes_ptr: [*]u8 = utils.alloc(alignment, accum_width);
var b1 = output orelse unreachable; var b1 = output orelse unreachable;
var b2 = alloc; var b2 = bytes_ptr;
@memcpy(b2, accum orelse unreachable, accum_width); @memcpy(b2, accum orelse unreachable, accum_width);
@ -514,10 +510,10 @@ pub fn listWalk(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum:
} }
@memcpy(output orelse unreachable, b2, accum_width); @memcpy(output orelse unreachable, b2, accum_width);
std.heap.c_allocator.free(alloc[0..accum_width]); utils.dealloc(alignment, bytes_ptr);
const data_bytes = list.len() * element_width; const data_bytes = list.len() * element_width;
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes); utils.decref(alignment, list.bytes, data_bytes);
} }
pub fn listWalkBackwards(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum: Opaque, alignment: usize, element_width: usize, accum_width: usize, output: Opaque) callconv(.C) void { pub fn listWalkBackwards(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum: Opaque, alignment: usize, element_width: usize, accum_width: usize, output: Opaque) callconv(.C) void {
@ -530,9 +526,9 @@ pub fn listWalkBackwards(list: RocList, stepper: Opaque, stepper_caller: Caller2
return; return;
} }
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, accum_width) catch unreachable); const bytes_ptr: [*]u8 = utils.alloc(alignment, accum_width);
var b1 = output orelse unreachable; var b1 = output orelse unreachable;
var b2 = alloc; var b2 = bytes_ptr;
@memcpy(b2, accum orelse unreachable, accum_width); @memcpy(b2, accum orelse unreachable, accum_width);
@ -551,10 +547,10 @@ pub fn listWalkBackwards(list: RocList, stepper: Opaque, stepper_caller: Caller2
} }
@memcpy(output orelse unreachable, b2, accum_width); @memcpy(output orelse unreachable, b2, accum_width);
std.heap.c_allocator.free(alloc[0..accum_width]); utils.dealloc(alignment, bytes_ptr);
const data_bytes = list.len() * element_width; const data_bytes = list.len() * element_width;
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes); utils.decref(alignment, list.bytes, data_bytes);
} }
pub fn listWalkUntil(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum: Opaque, alignment: usize, element_width: usize, accum_width: usize, dec: Dec, output: Opaque) callconv(.C) void { pub fn listWalkUntil(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum: Opaque, alignment: usize, element_width: usize, accum_width: usize, dec: Dec, output: Opaque) callconv(.C) void {
@ -570,18 +566,18 @@ pub fn listWalkUntil(list: RocList, stepper: Opaque, stepper_caller: Caller2, ac
return; return;
} }
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, TAG_WIDTH + accum_width) catch unreachable); const bytes_ptr: [*]u8 = utils.alloc(alignment, TAG_WIDTH + accum_width);
@memcpy(alloc + TAG_WIDTH, accum orelse unreachable, accum_width); @memcpy(bytes_ptr + TAG_WIDTH, accum orelse unreachable, accum_width);
if (list.bytes) |source_ptr| { if (list.bytes) |source_ptr| {
var i: usize = 0; var i: usize = 0;
const size = list.len(); const size = list.len();
while (i < size) : (i += 1) { while (i < size) : (i += 1) {
const element = source_ptr + i * element_width; const element = source_ptr + i * element_width;
stepper_caller(stepper, element, alloc + TAG_WIDTH, alloc); stepper_caller(stepper, element, bytes_ptr + TAG_WIDTH, bytes_ptr);
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, alloc)); const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, bytes_ptr));
if (usizes[0] != 0) { if (usizes[0] != 0) {
// decrement refcount of the remaining items // decrement refcount of the remaining items
i += 1; i += 1;
@ -593,11 +589,11 @@ pub fn listWalkUntil(list: RocList, stepper: Opaque, stepper_caller: Caller2, ac
} }
} }
@memcpy(output orelse unreachable, alloc + TAG_WIDTH, accum_width); @memcpy(output orelse unreachable, bytes_ptr + TAG_WIDTH, accum_width);
std.heap.c_allocator.free(alloc[0 .. TAG_WIDTH + accum_width]); utils.dealloc(alignment, bytes_ptr);
const data_bytes = list.len() * element_width; const data_bytes = list.len() * element_width;
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes); utils.decref(alignment, list.bytes, data_bytes);
} }
// List.contains : List k, k -> Bool // List.contains : List k, k -> Bool
@ -621,8 +617,7 @@ pub fn listRepeat(count: usize, alignment: usize, element: Opaque, element_width
return RocList.empty(); return RocList.empty();
} }
const allocator = std.heap.c_allocator; var output = RocList.allocate(alignment, count, element_width);
var output = RocList.allocate(allocator, alignment, count, element_width);
if (output.bytes) |target_ptr| { if (output.bytes) |target_ptr| {
// increment the element's RC N times // increment the element's RC N times
@ -641,7 +636,7 @@ pub fn listRepeat(count: usize, alignment: usize, element: Opaque, element_width
} }
pub fn listSingle(alignment: usize, element: Opaque, element_width: usize) callconv(.C) RocList { pub fn listSingle(alignment: usize, element: Opaque, element_width: usize) callconv(.C) RocList {
var output = RocList.allocate(std.heap.c_allocator, alignment, 1, element_width); var output = RocList.allocate(alignment, 1, element_width);
if (output.bytes) |target| { if (output.bytes) |target| {
if (element) |source| { if (element) |source| {
@ -654,7 +649,7 @@ pub fn listSingle(alignment: usize, element: Opaque, element_width: usize) callc
pub fn listAppend(list: RocList, alignment: usize, element: Opaque, element_width: usize) callconv(.C) RocList { pub fn listAppend(list: RocList, alignment: usize, element: Opaque, element_width: usize) callconv(.C) RocList {
const old_length = list.len(); const old_length = list.len();
var output = list.reallocate(std.heap.c_allocator, alignment, old_length + 1, element_width); var output = list.reallocate(alignment, old_length + 1, element_width);
if (output.bytes) |target| { if (output.bytes) |target| {
if (element) |source| { if (element) |source| {
@ -688,12 +683,12 @@ pub fn listDrop(
return RocList.empty(); return RocList.empty();
} }
const output = RocList.allocate(std.heap.c_allocator, alignment, keep_count, element_width); const output = RocList.allocate(alignment, keep_count, element_width);
const target_ptr = output.bytes orelse unreachable; const target_ptr = output.bytes orelse unreachable;
@memcpy(target_ptr, source_ptr + drop_count * element_width, keep_count * element_width); @memcpy(target_ptr, source_ptr + drop_count * element_width, keep_count * element_width);
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * element_width); utils.decref(alignment, list.bytes, size * element_width);
return output; return output;
} else { } else {
@ -702,54 +697,53 @@ pub fn listDrop(
} }
pub fn listRange(width: utils.IntWidth, low: Opaque, high: Opaque) callconv(.C) RocList { pub fn listRange(width: utils.IntWidth, low: Opaque, high: Opaque) callconv(.C) RocList {
const allocator = std.heap.c_allocator;
const IntWidth = utils.IntWidth; const IntWidth = utils.IntWidth;
switch (width) { switch (width) {
IntWidth.U8 => { IntWidth.U8 => {
return helper1(allocator, u8, low, high); return helper1(u8, low, high);
}, },
IntWidth.U16 => { IntWidth.U16 => {
return helper1(allocator, u16, low, high); return helper1(u16, low, high);
}, },
IntWidth.U32 => { IntWidth.U32 => {
return helper1(allocator, u32, low, high); return helper1(u32, low, high);
}, },
IntWidth.U64 => { IntWidth.U64 => {
return helper1(allocator, u64, low, high); return helper1(u64, low, high);
}, },
IntWidth.U128 => { IntWidth.U128 => {
return helper1(allocator, u128, low, high); return helper1(u128, low, high);
}, },
IntWidth.I8 => { IntWidth.I8 => {
return helper1(allocator, i8, low, high); return helper1(i8, low, high);
}, },
IntWidth.I16 => { IntWidth.I16 => {
return helper1(allocator, i16, low, high); return helper1(i16, low, high);
}, },
IntWidth.I32 => { IntWidth.I32 => {
return helper1(allocator, i32, low, high); return helper1(i32, low, high);
}, },
IntWidth.I64 => { IntWidth.I64 => {
return helper1(allocator, i64, low, high); return helper1(i64, low, high);
}, },
IntWidth.I128 => { IntWidth.I128 => {
return helper1(allocator, i128, low, high); return helper1(i128, low, high);
}, },
IntWidth.Usize => { IntWidth.Usize => {
return helper1(allocator, usize, low, high); return helper1(usize, low, high);
}, },
} }
} }
fn helper1(allocator: *Allocator, comptime T: type, low: Opaque, high: Opaque) RocList { fn helper1(comptime T: type, low: Opaque, high: Opaque) RocList {
const ptr1 = @ptrCast(*T, @alignCast(@alignOf(T), low)); const ptr1 = @ptrCast(*T, @alignCast(@alignOf(T), low));
const ptr2 = @ptrCast(*T, @alignCast(@alignOf(T), high)); const ptr2 = @ptrCast(*T, @alignCast(@alignOf(T), high));
return listRangeHelp(allocator, T, ptr1.*, ptr2.*); return listRangeHelp(T, ptr1.*, ptr2.*);
} }
fn listRangeHelp(allocator: *Allocator, comptime T: type, low: T, high: T) RocList { fn listRangeHelp(comptime T: type, low: T, high: T) RocList {
const Order = std.math.Order; const Order = std.math.Order;
switch (std.math.order(low, high)) { switch (std.math.order(low, high)) {
@ -758,7 +752,7 @@ fn listRangeHelp(allocator: *Allocator, comptime T: type, low: T, high: T) RocLi
}, },
Order.eq => { Order.eq => {
const list = RocList.allocate(allocator, @alignOf(usize), 1, @sizeOf(T)); const list = RocList.allocate(@alignOf(usize), 1, @sizeOf(T));
const buffer = @ptrCast([*]T, @alignCast(@alignOf(T), list.bytes orelse unreachable)); const buffer = @ptrCast([*]T, @alignCast(@alignOf(T), list.bytes orelse unreachable));
buffer[0] = low; buffer[0] = low;
@ -768,7 +762,7 @@ fn listRangeHelp(allocator: *Allocator, comptime T: type, low: T, high: T) RocLi
Order.lt => { Order.lt => {
const length: usize = @intCast(usize, high - low); const length: usize = @intCast(usize, high - low);
const list = RocList.allocate(allocator, @alignOf(usize), length, @sizeOf(T)); const list = RocList.allocate(@alignOf(usize), length, @sizeOf(T));
const buffer = @ptrCast([*]T, @alignCast(@alignOf(T), list.bytes orelse unreachable)); const buffer = @ptrCast([*]T, @alignCast(@alignOf(T), list.bytes orelse unreachable));
@ -822,7 +816,7 @@ fn quicksort(source_ptr: [*]u8, transform: Opaque, wrapper: CompareFn, element_w
} }
pub fn listSortWith(input: RocList, transform: Opaque, wrapper: CompareFn, alignment: usize, element_width: usize) callconv(.C) RocList { pub fn listSortWith(input: RocList, transform: Opaque, wrapper: CompareFn, alignment: usize, element_width: usize) callconv(.C) RocList {
var list = input.makeUnique(std.heap.c_allocator, alignment, element_width); var list = input.makeUnique(alignment, element_width);
if (list.bytes) |source_ptr| { if (list.bytes) |source_ptr| {
const low = 0; const low = 0;
@ -889,7 +883,7 @@ pub fn listJoin(list_of_lists: RocList, alignment: usize, element_width: usize)
total_length += slice_of_lists[i].len(); total_length += slice_of_lists[i].len();
} }
const output = RocList.allocate(std.heap.c_allocator, alignment, total_length, element_width); const output = RocList.allocate(alignment, total_length, element_width);
if (output.bytes) |target| { if (output.bytes) |target| {
var elements_copied: usize = 0; var elements_copied: usize = 0;
@ -918,7 +912,6 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: usize, element_wi
if (list_a.bytes) |source| { if (list_a.bytes) |source| {
const new_source = utils.unsafeReallocate( const new_source = utils.unsafeReallocate(
source, source,
std.heap.c_allocator,
alignment, alignment,
list_a.len(), list_a.len(),
total_length, total_length,
@ -934,7 +927,7 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: usize, element_wi
} }
const total_length: usize = list_a.len() + list_b.len(); const total_length: usize = list_a.len() + list_b.len();
const output = RocList.allocate(std.heap.c_allocator, alignment, total_length, element_width); const output = RocList.allocate(alignment, total_length, element_width);
if (output.bytes) |target| { if (output.bytes) |target| {
if (list_a.bytes) |source| { if (list_a.bytes) |source| {

View file

@ -1,9 +1,9 @@
const utils = @import("utils.zig"); const utils = @import("utils.zig");
const roc_mem = @import("mem.zig");
const RocList = @import("list.zig").RocList; const RocList = @import("list.zig").RocList;
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
const always_inline = std.builtin.CallOptions.Modifier.always_inline; const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const Allocator = mem.Allocator;
const unicode = std.unicode; const unicode = std.unicode;
const testing = std.testing; const testing = std.testing;
const expectEqual = testing.expectEqual; const expectEqual = testing.expectEqual;
@ -34,6 +34,8 @@ pub const RocStr = extern struct {
str_bytes: ?[*]u8, str_bytes: ?[*]u8,
str_len: usize, str_len: usize,
pub const alignment = @alignOf(usize);
pub inline fn empty() RocStr { pub inline fn empty() RocStr {
return RocStr{ return RocStr{
.str_len = 0, .str_len = 0,
@ -43,15 +45,15 @@ pub const RocStr = extern struct {
// This clones the pointed-to bytes if they won't fit in a // This clones the pointed-to bytes if they won't fit in a
// small string, and returns a (pointer, len) tuple which points to them. // small string, and returns a (pointer, len) tuple which points to them.
pub fn init(allocator: *Allocator, bytes_ptr: [*]const u8, length: usize) RocStr { pub fn init(bytes_ptr: [*]const u8, length: usize) RocStr {
var result = RocStr.allocate(allocator, InPlace.Clone, length); var result = RocStr.allocate(InPlace.Clone, length);
@memcpy(result.asU8ptr(), bytes_ptr, length); @memcpy(result.asU8ptr(), bytes_ptr, length);
return result; return result;
} }
pub fn initBig(allocator: *Allocator, in_place: InPlace, number_of_chars: u64) RocStr { pub fn initBig(in_place: InPlace, number_of_chars: u64) RocStr {
const first_element = utils.allocateWithRefcount(allocator, @sizeOf(usize), number_of_chars); const first_element = utils.allocateWithRefcount(@sizeOf(usize), number_of_chars);
return RocStr{ return RocStr{
.str_bytes = first_element, .str_bytes = first_element,
@ -60,11 +62,11 @@ pub const RocStr = extern struct {
} }
// allocate space for a (big or small) RocStr, but put nothing in it yet // allocate space for a (big or small) RocStr, but put nothing in it yet
pub fn allocate(allocator: *Allocator, result_in_place: InPlace, number_of_chars: usize) RocStr { pub fn allocate(result_in_place: InPlace, number_of_chars: usize) RocStr {
const result_is_big = number_of_chars >= small_string_size; const result_is_big = number_of_chars >= small_string_size;
if (result_is_big) { if (result_is_big) {
return RocStr.initBig(allocator, result_in_place, number_of_chars); return RocStr.initBig(result_in_place, number_of_chars);
} else { } else {
var t = blank_small_string; var t = blank_small_string;
@ -77,10 +79,9 @@ pub const RocStr = extern struct {
} }
} }
pub fn deinit(self: RocStr, allocator: *Allocator) void { pub fn deinit(self: RocStr) void {
if (!self.isSmallStr() and !self.isEmpty()) { if (!self.isSmallStr() and !self.isEmpty()) {
const alignment = @alignOf(usize); utils.decref(RocStr.alignment, self.str_bytes, self.str_len);
utils.decref(allocator, alignment, self.str_bytes, self.str_len);
} }
} }
@ -98,7 +99,7 @@ pub const RocStr = extern struct {
if (length < roc_str_size) { if (length < roc_str_size) {
return RocStr.empty(); return RocStr.empty();
} else { } else {
var new_bytes: []T = allocator.alloc(u8, length) catch unreachable; var new_bytes: []T = utils.alloc(RocStr.alignment, length) catch unreachable;
var new_bytes_ptr: [*]u8 = @ptrCast([*]u8, &new_bytes); var new_bytes_ptr: [*]u8 = @ptrCast([*]u8, &new_bytes);
@ -146,12 +147,12 @@ pub const RocStr = extern struct {
return true; return true;
} }
pub fn clone(allocator: *Allocator, in_place: InPlace, str: RocStr) RocStr { pub fn clone(in_place: InPlace, str: RocStr) RocStr {
if (str.isSmallStr() or str.isEmpty()) { if (str.isSmallStr() or str.isEmpty()) {
// just return the bytes // just return the bytes
return str; return str;
} else { } else {
var new_str = RocStr.initBig(allocator, in_place, str.str_len); var new_str = RocStr.initBig(in_place, str.str_len);
var old_bytes: [*]u8 = @ptrCast([*]u8, str.str_bytes); var old_bytes: [*]u8 = @ptrCast([*]u8, str.str_bytes);
var new_bytes: [*]u8 = @ptrCast([*]u8, new_str.str_bytes); var new_bytes: [*]u8 = @ptrCast([*]u8, new_str.str_bytes);
@ -164,33 +165,30 @@ pub const RocStr = extern struct {
pub fn reallocate( pub fn reallocate(
self: RocStr, self: RocStr,
allocator: *Allocator,
new_length: usize, new_length: usize,
) RocStr { ) RocStr {
const alignment = 1;
const element_width = 1; const element_width = 1;
if (self.bytes) |source_ptr| { if (self.bytes) |source_ptr| {
if (self.isUnique()) { if (self.isUnique()) {
const new_source = utils.unsafeReallocate(source_ptr, allocator, alignment, self.len(), new_length, element_width); const new_source = utils.unsafeReallocate(source_ptr, RocStr.alignment, self.len(), new_length, element_width);
return RocStr{ .str_bytes = new_source, .str_len = new_length }; return RocStr{ .str_bytes = new_source, .str_len = new_length };
} }
} }
return self.reallocateFresh(allocator, alignment, new_length, element_width); return self.reallocateFresh(RocStr.alignment, new_length, element_width);
} }
/// reallocate by explicitly making a new allocation and copying elements over /// reallocate by explicitly making a new allocation and copying elements over
pub fn reallocateFresh( pub fn reallocateFresh(
self: RocStr, self: RocStr,
allocator: *Allocator,
new_length: usize, new_length: usize,
) RocStr { ) RocStr {
const old_length = self.len(); const old_length = self.len();
const delta_length = new_length - old_length; const delta_length = new_length - old_length;
const result = RocStr.allocate(allocator, InPlace.Clone, new_length); const result = RocStr.allocate(InPlace.Clone, new_length);
// transfer the memory // transfer the memory
@ -200,7 +198,7 @@ pub const RocStr = extern struct {
@memcpy(dest_ptr, source_ptr, old_length); @memcpy(dest_ptr, source_ptr, old_length);
@memset(dest_ptr + old_length, 0, delta_length); @memset(dest_ptr + old_length, 0, delta_length);
self.deinit(allocator); self.deinit();
return result; return result;
} }
@ -268,33 +266,33 @@ pub const RocStr = extern struct {
const str1_len = 3; const str1_len = 3;
var str1: [str1_len]u8 = "abc".*; var str1: [str1_len]u8 = "abc".*;
const str1_ptr: [*]u8 = &str1; const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(testing.allocator, str1_ptr, str1_len); var roc_str1 = RocStr.init(str1_ptr, str1_len);
const str2_len = 3; const str2_len = 3;
var str2: [str2_len]u8 = "abc".*; var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2; const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(testing.allocator, str2_ptr, str2_len); var roc_str2 = RocStr.init(str2_ptr, str2_len);
expect(roc_str1.eq(roc_str2)); expect(roc_str1.eq(roc_str2));
roc_str1.deinit(testing.allocator); roc_str1.deinit();
roc_str2.deinit(testing.allocator); roc_str2.deinit();
} }
test "RocStr.eq: not equal different length" { test "RocStr.eq: not equal different length" {
const str1_len = 4; const str1_len = 4;
var str1: [str1_len]u8 = "abcd".*; var str1: [str1_len]u8 = "abcd".*;
const str1_ptr: [*]u8 = &str1; const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(testing.allocator, str1_ptr, str1_len); var roc_str1 = RocStr.init(str1_ptr, str1_len);
const str2_len = 3; const str2_len = 3;
var str2: [str2_len]u8 = "abc".*; var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2; const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(testing.allocator, str2_ptr, str2_len); var roc_str2 = RocStr.init(str2_ptr, str2_len);
defer { defer {
roc_str1.deinit(testing.allocator); roc_str1.deinit();
roc_str2.deinit(testing.allocator); roc_str2.deinit();
} }
expect(!roc_str1.eq(roc_str2)); expect(!roc_str1.eq(roc_str2));
@ -304,16 +302,16 @@ pub const RocStr = extern struct {
const str1_len = 3; const str1_len = 3;
var str1: [str1_len]u8 = "acb".*; var str1: [str1_len]u8 = "acb".*;
const str1_ptr: [*]u8 = &str1; const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(testing.allocator, str1_ptr, str1_len); var roc_str1 = RocStr.init(str1_ptr, str1_len);
const str2_len = 3; const str2_len = 3;
var str2: [str2_len]u8 = "abc".*; var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2; const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(testing.allocator, str2_ptr, str2_len); var roc_str2 = RocStr.init(str2_ptr, str2_len);
defer { defer {
roc_str1.deinit(testing.allocator); roc_str1.deinit();
roc_str2.deinit(testing.allocator); roc_str2.deinit();
} }
expect(!roc_str1.eq(roc_str2)); expect(!roc_str1.eq(roc_str2));
@ -321,7 +319,7 @@ pub const RocStr = extern struct {
}; };
pub fn init(bytes_ptr: [*]const u8, length: usize) callconv(.C) RocStr { pub fn init(bytes_ptr: [*]const u8, length: usize) callconv(.C) RocStr {
return @call(.{ .modifier = always_inline }, RocStr.init, .{ std.heap.c_allocator, bytes_ptr, length }); return @call(.{ .modifier = always_inline }, RocStr.init, .{ bytes_ptr, length });
} }
// Str.equal // Str.equal
@ -335,17 +333,12 @@ pub fn strNumberOfBytes(string: RocStr) callconv(.C) usize {
} }
// Str.fromInt // Str.fromInt
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strFromIntC(int: i64) callconv(.C) RocStr { pub fn strFromIntC(int: i64) callconv(.C) RocStr {
return strFromInt(std.heap.c_allocator, int);
}
fn strFromInt(allocator: *Allocator, int: i64) RocStr {
// prepare for having multiple integer types in the future // prepare for having multiple integer types in the future
return @call(.{ .modifier = always_inline }, strFromIntHelp, .{ allocator, i64, int }); return @call(.{ .modifier = always_inline }, strFromIntHelp, .{ i64, int });
} }
fn strFromIntHelp(allocator: *Allocator, comptime T: type, int: T) RocStr { fn strFromIntHelp(comptime T: type, int: T) RocStr {
// determine maximum size for this T // determine maximum size for this T
comptime const size = comptime blk: { comptime const size = comptime blk: {
// the string representation of the minimum i128 value uses at most 40 characters // the string representation of the minimum i128 value uses at most 40 characters
@ -357,11 +350,10 @@ fn strFromIntHelp(allocator: *Allocator, comptime T: type, int: T) RocStr {
var buf: [size]u8 = undefined; var buf: [size]u8 = undefined;
const result = std.fmt.bufPrint(&buf, "{}", .{int}) catch unreachable; const result = std.fmt.bufPrint(&buf, "{}", .{int}) catch unreachable;
return RocStr.init(allocator, &buf, result.len); return RocStr.init(&buf, result.len);
} }
// Str.fromFloat // Str.fromFloat
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strFromFloatC(float: f64) callconv(.C) RocStr { pub fn strFromFloatC(float: f64) callconv(.C) RocStr {
// NOTE the compiled zig for float formatting seems to use LLVM11-specific features // NOTE the compiled zig for float formatting seems to use LLVM11-specific features
// hopefully we can use zig instead of snprintf in the future when we upgrade // hopefully we can use zig instead of snprintf in the future when we upgrade
@ -374,16 +366,15 @@ pub fn strFromFloatC(float: f64) callconv(.C) RocStr {
const result = c.snprintf(&buf, 100, "%f", float); const result = c.snprintf(&buf, 100, "%f", float);
return RocStr.init(std.heap.c_allocator, &buf, @intCast(usize, result)); return RocStr.init(&buf, @intCast(usize, result));
} }
// Str.split // Str.split
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strSplitInPlaceC(array: [*]RocStr, string: RocStr, delimiter: RocStr) callconv(.C) void { pub fn strSplitInPlaceC(array: [*]RocStr, string: RocStr, delimiter: RocStr) callconv(.C) void {
return @call(.{ .modifier = always_inline }, strSplitInPlace, .{ std.heap.c_allocator, array, string, delimiter }); return @call(.{ .modifier = always_inline }, strSplitInPlace, .{ array, string, delimiter });
} }
fn strSplitInPlace(allocator: *Allocator, array: [*]RocStr, string: RocStr, delimiter: RocStr) void { fn strSplitInPlace(array: [*]RocStr, string: RocStr, delimiter: RocStr) void {
var ret_array_index: usize = 0; var ret_array_index: usize = 0;
var slice_start_index: usize = 0; var slice_start_index: usize = 0;
var str_index: usize = 0; var str_index: usize = 0;
@ -415,7 +406,7 @@ fn strSplitInPlace(allocator: *Allocator, array: [*]RocStr, string: RocStr, deli
if (matches_delimiter) { if (matches_delimiter) {
const segment_len: usize = str_index - slice_start_index; const segment_len: usize = str_index - slice_start_index;
array[ret_array_index] = RocStr.init(allocator, str_bytes + slice_start_index, segment_len); array[ret_array_index] = RocStr.init(str_bytes + slice_start_index, segment_len);
slice_start_index = str_index + delimiter_len; slice_start_index = str_index + delimiter_len;
ret_array_index += 1; ret_array_index += 1;
str_index += delimiter_len; str_index += delimiter_len;
@ -425,21 +416,21 @@ fn strSplitInPlace(allocator: *Allocator, array: [*]RocStr, string: RocStr, deli
} }
} }
array[ret_array_index] = RocStr.init(allocator, str_bytes + slice_start_index, str_len - slice_start_index); array[ret_array_index] = RocStr.init(str_bytes + slice_start_index, str_len - slice_start_index);
} }
test "strSplitInPlace: no delimiter" { test "strSplitInPlace: no delimiter" {
// Str.split "abc" "!" == [ "abc" ] // Str.split "abc" "!" == [ "abc" ]
const str_arr = "abc"; const str_arr = "abc";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len); const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "!"; const delimiter_arr = "!";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len); const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
var array: [1]RocStr = undefined; var array: [1]RocStr = undefined;
const array_ptr: [*]RocStr = &array; const array_ptr: [*]RocStr = &array;
strSplitInPlace(testing.allocator, array_ptr, str, delimiter); strSplitInPlace(array_ptr, str, delimiter);
var expected = [1]RocStr{ var expected = [1]RocStr{
str, str,
@ -447,15 +438,15 @@ test "strSplitInPlace: no delimiter" {
defer { defer {
for (array) |roc_str| { for (array) |roc_str| {
roc_str.deinit(testing.allocator); roc_str.deinit();
} }
for (expected) |roc_str| { for (expected) |roc_str| {
roc_str.deinit(testing.allocator); roc_str.deinit();
} }
str.deinit(testing.allocator); str.deinit();
delimiter.deinit(testing.allocator); delimiter.deinit();
} }
expectEqual(array.len, expected.len); expectEqual(array.len, expected.len);
@ -464,10 +455,10 @@ test "strSplitInPlace: no delimiter" {
test "strSplitInPlace: empty end" { test "strSplitInPlace: empty end" {
const str_arr = "1---- ---- ---- ---- ----2---- ---- ---- ---- ----"; const str_arr = "1---- ---- ---- ---- ----2---- ---- ---- ---- ----";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len); const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "---- ---- ---- ---- ----"; const delimiter_arr = "---- ---- ---- ---- ----";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len); const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
const array_len: usize = 3; const array_len: usize = 3;
var array: [array_len]RocStr = [_]RocStr{ var array: [array_len]RocStr = [_]RocStr{
@ -477,10 +468,10 @@ test "strSplitInPlace: empty end" {
}; };
const array_ptr: [*]RocStr = &array; const array_ptr: [*]RocStr = &array;
strSplitInPlace(testing.allocator, array_ptr, str, delimiter); strSplitInPlace(array_ptr, str, delimiter);
const one = RocStr.init(testing.allocator, "1", 1); const one = RocStr.init("1", 1);
const two = RocStr.init(testing.allocator, "2", 1); const two = RocStr.init("2", 1);
var expected = [3]RocStr{ var expected = [3]RocStr{
one, two, RocStr.empty(), one, two, RocStr.empty(),
@ -488,15 +479,15 @@ test "strSplitInPlace: empty end" {
defer { defer {
for (array) |rocStr| { for (array) |rocStr| {
rocStr.deinit(testing.allocator); rocStr.deinit();
} }
for (expected) |rocStr| { for (expected) |rocStr| {
rocStr.deinit(testing.allocator); rocStr.deinit();
} }
str.deinit(testing.allocator); str.deinit();
delimiter.deinit(testing.allocator); delimiter.deinit();
} }
expectEqual(array.len, expected.len); expectEqual(array.len, expected.len);
@ -507,10 +498,10 @@ test "strSplitInPlace: empty end" {
test "strSplitInPlace: delimiter on sides" { test "strSplitInPlace: delimiter on sides" {
const str_arr = "tttghittt"; const str_arr = "tttghittt";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len); const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "ttt"; const delimiter_arr = "ttt";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len); const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
const array_len: usize = 3; const array_len: usize = 3;
var array: [array_len]RocStr = [_]RocStr{ var array: [array_len]RocStr = [_]RocStr{
@ -519,10 +510,10 @@ test "strSplitInPlace: delimiter on sides" {
undefined, undefined,
}; };
const array_ptr: [*]RocStr = &array; const array_ptr: [*]RocStr = &array;
strSplitInPlace(testing.allocator, array_ptr, str, delimiter); strSplitInPlace(array_ptr, str, delimiter);
const ghi_arr = "ghi"; const ghi_arr = "ghi";
const ghi = RocStr.init(testing.allocator, ghi_arr, ghi_arr.len); const ghi = RocStr.init(ghi_arr, ghi_arr.len);
var expected = [3]RocStr{ var expected = [3]RocStr{
RocStr.empty(), ghi, RocStr.empty(), RocStr.empty(), ghi, RocStr.empty(),
@ -530,15 +521,15 @@ test "strSplitInPlace: delimiter on sides" {
defer { defer {
for (array) |rocStr| { for (array) |rocStr| {
rocStr.deinit(testing.allocator); rocStr.deinit();
} }
for (expected) |rocStr| { for (expected) |rocStr| {
rocStr.deinit(testing.allocator); rocStr.deinit();
} }
str.deinit(testing.allocator); str.deinit();
delimiter.deinit(testing.allocator); delimiter.deinit();
} }
expectEqual(array.len, expected.len); expectEqual(array.len, expected.len);
@ -550,20 +541,20 @@ test "strSplitInPlace: delimiter on sides" {
test "strSplitInPlace: three pieces" { test "strSplitInPlace: three pieces" {
// Str.split "a!b!c" "!" == [ "a", "b", "c" ] // Str.split "a!b!c" "!" == [ "a", "b", "c" ]
const str_arr = "a!b!c"; const str_arr = "a!b!c";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len); const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "!"; const delimiter_arr = "!";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len); const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
const array_len: usize = 3; const array_len: usize = 3;
var array: [array_len]RocStr = undefined; var array: [array_len]RocStr = undefined;
const array_ptr: [*]RocStr = &array; const array_ptr: [*]RocStr = &array;
strSplitInPlace(testing.allocator, array_ptr, str, delimiter); strSplitInPlace(array_ptr, str, delimiter);
const a = RocStr.init(testing.allocator, "a", 1); const a = RocStr.init("a", 1);
const b = RocStr.init(testing.allocator, "b", 1); const b = RocStr.init("b", 1);
const c = RocStr.init(testing.allocator, "c", 1); const c = RocStr.init("c", 1);
var expected_array = [array_len]RocStr{ var expected_array = [array_len]RocStr{
a, b, c, a, b, c,
@ -571,15 +562,15 @@ test "strSplitInPlace: three pieces" {
defer { defer {
for (array) |roc_str| { for (array) |roc_str| {
roc_str.deinit(testing.allocator); roc_str.deinit();
} }
for (expected_array) |roc_str| { for (expected_array) |roc_str| {
roc_str.deinit(testing.allocator); roc_str.deinit();
} }
str.deinit(testing.allocator); str.deinit();
delimiter.deinit(testing.allocator); delimiter.deinit();
} }
expectEqual(expected_array.len, array.len); expectEqual(expected_array.len, array.len);
@ -637,14 +628,14 @@ test "countSegments: long delimiter" {
// Str.split "str" "delimiter" == [ "str" ] // Str.split "str" "delimiter" == [ "str" ]
// 1 segment // 1 segment
const str_arr = "str"; const str_arr = "str";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len); const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "delimiter"; const delimiter_arr = "delimiter";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len); const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
defer { defer {
str.deinit(testing.allocator); str.deinit();
delimiter.deinit(testing.allocator); delimiter.deinit();
} }
const segments_count = countSegments(str, delimiter); const segments_count = countSegments(str, delimiter);
@ -655,14 +646,14 @@ test "countSegments: delimiter at start" {
// Str.split "hello there" "hello" == [ "", " there" ] // Str.split "hello there" "hello" == [ "", " there" ]
// 2 segments // 2 segments
const str_arr = "hello there"; const str_arr = "hello there";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len); const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "hello"; const delimiter_arr = "hello";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len); const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
defer { defer {
str.deinit(testing.allocator); str.deinit();
delimiter.deinit(testing.allocator); delimiter.deinit();
} }
const segments_count = countSegments(str, delimiter); const segments_count = countSegments(str, delimiter);
@ -674,14 +665,14 @@ test "countSegments: delimiter interspered" {
// Str.split "a!b!c" "!" == [ "a", "b", "c" ] // Str.split "a!b!c" "!" == [ "a", "b", "c" ]
// 3 segments // 3 segments
const str_arr = "a!b!c"; const str_arr = "a!b!c";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len); const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "!"; const delimiter_arr = "!";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len); const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
defer { defer {
str.deinit(testing.allocator); str.deinit();
delimiter.deinit(testing.allocator); delimiter.deinit();
} }
const segments_count = countSegments(str, delimiter); const segments_count = countSegments(str, delimiter);
@ -736,8 +727,8 @@ test "countGraphemeClusters: empty string" {
test "countGraphemeClusters: ascii characters" { test "countGraphemeClusters: ascii characters" {
const bytes_arr = "abcd"; const bytes_arr = "abcd";
const bytes_len = bytes_arr.len; const bytes_len = bytes_arr.len;
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len); const str = RocStr.init(bytes_arr, bytes_len);
defer str.deinit(testing.allocator); defer str.deinit();
const count = countGraphemeClusters(str); const count = countGraphemeClusters(str);
expectEqual(count, 4); expectEqual(count, 4);
@ -746,8 +737,8 @@ test "countGraphemeClusters: ascii characters" {
test "countGraphemeClusters: utf8 characters" { test "countGraphemeClusters: utf8 characters" {
const bytes_arr = "ãxā"; const bytes_arr = "ãxā";
const bytes_len = bytes_arr.len; const bytes_len = bytes_arr.len;
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len); const str = RocStr.init(bytes_arr, bytes_len);
defer str.deinit(testing.allocator); defer str.deinit();
const count = countGraphemeClusters(str); const count = countGraphemeClusters(str);
expectEqual(count, 3); expectEqual(count, 3);
@ -756,8 +747,8 @@ test "countGraphemeClusters: utf8 characters" {
test "countGraphemeClusters: emojis" { test "countGraphemeClusters: emojis" {
const bytes_arr = "🤔🤔🤔"; const bytes_arr = "🤔🤔🤔";
const bytes_len = bytes_arr.len; const bytes_len = bytes_arr.len;
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len); const str = RocStr.init(bytes_arr, bytes_len);
defer str.deinit(testing.allocator); defer str.deinit();
const count = countGraphemeClusters(str); const count = countGraphemeClusters(str);
expectEqual(count, 3); expectEqual(count, 3);
@ -766,8 +757,8 @@ test "countGraphemeClusters: emojis" {
test "countGraphemeClusters: emojis and ut8 characters" { test "countGraphemeClusters: emojis and ut8 characters" {
const bytes_arr = "🤔å🤔¥🤔ç"; const bytes_arr = "🤔å🤔¥🤔ç";
const bytes_len = bytes_arr.len; const bytes_len = bytes_arr.len;
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len); const str = RocStr.init(bytes_arr, bytes_len);
defer str.deinit(testing.allocator); defer str.deinit();
const count = countGraphemeClusters(str); const count = countGraphemeClusters(str);
expectEqual(count, 6); expectEqual(count, 6);
@ -776,8 +767,8 @@ test "countGraphemeClusters: emojis and ut8 characters" {
test "countGraphemeClusters: emojis, ut8, and ascii characters" { test "countGraphemeClusters: emojis, ut8, and ascii characters" {
const bytes_arr = "6🤔å🤔e¥🤔çpp"; const bytes_arr = "6🤔å🤔e¥🤔çpp";
const bytes_len = bytes_arr.len; const bytes_len = bytes_arr.len;
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len); const str = RocStr.init(bytes_arr, bytes_len);
defer str.deinit(testing.allocator); defer str.deinit();
const count = countGraphemeClusters(str); const count = countGraphemeClusters(str);
expectEqual(count, 10); expectEqual(count, 10);
@ -828,36 +819,36 @@ pub fn startsWithCodePoint(string: RocStr, prefix: u32) callconv(.C) bool {
} }
test "startsWithCodePoint: ascii char" { test "startsWithCodePoint: ascii char" {
const whole = RocStr.init(testing.allocator, "foobar", 6); const whole = RocStr.init("foobar", 6);
const prefix = 'f'; const prefix = 'f';
expect(startsWithCodePoint(whole, prefix)); expect(startsWithCodePoint(whole, prefix));
} }
test "startsWithCodePoint: emoji" { test "startsWithCodePoint: emoji" {
const yes = RocStr.init(testing.allocator, "💖foobar", 10); const yes = RocStr.init("💖foobar", 10);
const no = RocStr.init(testing.allocator, "foobar", 6); const no = RocStr.init("foobar", 6);
const prefix = '💖'; const prefix = '💖';
expect(startsWithCodePoint(yes, prefix)); expect(startsWithCodePoint(yes, prefix));
expect(!startsWithCodePoint(no, prefix)); expect(!startsWithCodePoint(no, prefix));
} }
test "startsWith: foo starts with fo" { test "startsWith: foo starts with fo" {
const foo = RocStr.init(testing.allocator, "foo", 3); const foo = RocStr.init("foo", 3);
const fo = RocStr.init(testing.allocator, "fo", 2); const fo = RocStr.init("fo", 2);
expect(startsWith(foo, fo)); expect(startsWith(foo, fo));
} }
test "startsWith: 123456789123456789 starts with 123456789123456789" { test "startsWith: 123456789123456789 starts with 123456789123456789" {
const str = RocStr.init(testing.allocator, "123456789123456789", 18); const str = RocStr.init("123456789123456789", 18);
defer str.deinit(testing.allocator); defer str.deinit();
expect(startsWith(str, str)); expect(startsWith(str, str));
} }
test "startsWith: 12345678912345678910 starts with 123456789123456789" { test "startsWith: 12345678912345678910 starts with 123456789123456789" {
const str = RocStr.init(testing.allocator, "12345678912345678910", 20); const str = RocStr.init("12345678912345678910", 20);
defer str.deinit(testing.allocator); defer str.deinit();
const prefix = RocStr.init(testing.allocator, "123456789123456789", 18); const prefix = RocStr.init("123456789123456789", 18);
defer prefix.deinit(testing.allocator); defer prefix.deinit();
expect(startsWith(str, prefix)); expect(startsWith(str, prefix));
} }
@ -886,63 +877,60 @@ pub fn endsWith(string: RocStr, suffix: RocStr) callconv(.C) bool {
} }
test "endsWith: foo ends with oo" { test "endsWith: foo ends with oo" {
const foo = RocStr.init(testing.allocator, "foo", 3); const foo = RocStr.init("foo", 3);
const oo = RocStr.init(testing.allocator, "oo", 2); const oo = RocStr.init("oo", 2);
defer foo.deinit(testing.allocator); defer foo.deinit();
defer oo.deinit(testing.allocator); defer oo.deinit();
expect(endsWith(foo, oo)); expect(endsWith(foo, oo));
} }
test "endsWith: 123456789123456789 ends with 123456789123456789" { test "endsWith: 123456789123456789 ends with 123456789123456789" {
const str = RocStr.init(testing.allocator, "123456789123456789", 18); const str = RocStr.init("123456789123456789", 18);
defer str.deinit(testing.allocator); defer str.deinit();
expect(endsWith(str, str)); expect(endsWith(str, str));
} }
test "endsWith: 12345678912345678910 ends with 345678912345678910" { test "endsWith: 12345678912345678910 ends with 345678912345678910" {
const str = RocStr.init(testing.allocator, "12345678912345678910", 20); const str = RocStr.init("12345678912345678910", 20);
const suffix = RocStr.init(testing.allocator, "345678912345678910", 18); const suffix = RocStr.init("345678912345678910", 18);
defer str.deinit(testing.allocator); defer str.deinit();
defer suffix.deinit(testing.allocator); defer suffix.deinit();
expect(endsWith(str, suffix)); expect(endsWith(str, suffix));
} }
test "endsWith: hello world ends with world" { test "endsWith: hello world ends with world" {
const str = RocStr.init(testing.allocator, "hello world", 11); const str = RocStr.init("hello world", 11);
const suffix = RocStr.init(testing.allocator, "world", 5); const suffix = RocStr.init("world", 5);
defer str.deinit(testing.allocator); defer str.deinit();
defer suffix.deinit(testing.allocator); defer suffix.deinit();
expect(endsWith(str, suffix)); expect(endsWith(str, suffix));
} }
// Str.concat // Str.concat
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strConcatC(result_in_place: InPlace, arg1: RocStr, arg2: RocStr) callconv(.C) RocStr { pub fn strConcatC(result_in_place: InPlace, arg1: RocStr, arg2: RocStr) callconv(.C) RocStr {
return @call(.{ .modifier = always_inline }, strConcat, .{ std.heap.c_allocator, result_in_place, arg1, arg2 }); return @call(.{ .modifier = always_inline }, strConcat, .{ result_in_place, arg1, arg2 });
} }
fn strConcat(allocator: *Allocator, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr { fn strConcat(result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr {
if (arg1.isEmpty()) { if (arg1.isEmpty()) {
// the second argument is borrowed, so we must increment its refcount before returning // the second argument is borrowed, so we must increment its refcount before returning
return RocStr.clone(allocator, result_in_place, arg2); return RocStr.clone(result_in_place, arg2);
} else if (arg2.isEmpty()) { } else if (arg2.isEmpty()) {
// the first argument is owned, so we can return it without cloning // the first argument is owned, so we can return it without cloning
return RocStr.clone(allocator, result_in_place, arg1); return RocStr.clone(result_in_place, arg1);
} else { } else {
const combined_length = arg1.len() + arg2.len(); const combined_length = arg1.len() + arg2.len();
const alignment = 1;
const element_width = 1; const element_width = 1;
if (!arg1.isSmallStr() and arg1.isUnique()) { if (!arg1.isSmallStr() and arg1.isUnique()) {
if (arg1.str_bytes) |source_ptr| { if (arg1.str_bytes) |source_ptr| {
const new_source = utils.unsafeReallocate( const new_source = utils.unsafeReallocate(
source_ptr, source_ptr,
allocator, RocStr.alignment,
alignment,
arg1.len(), arg1.len(),
combined_length, combined_length,
element_width, element_width,
@ -954,16 +942,13 @@ fn strConcat(allocator: *Allocator, result_in_place: InPlace, arg1: RocStr, arg2
} }
} }
var result = arg1.reallocateFresh( var result = arg1.reallocateFresh(combined_length);
allocator,
combined_length,
);
var result_ptr = result.asU8ptr(); var result_ptr = result.asU8ptr();
arg1.memcpy(result_ptr); arg1.memcpy(result_ptr);
arg2.memcpy(result_ptr + arg1.len()); arg2.memcpy(result_ptr + arg1.len());
arg1.deinit(allocator); arg1.deinit();
return result; return result;
} }
@ -973,27 +958,27 @@ test "RocStr.concat: small concat small" {
const str1_len = 3; const str1_len = 3;
var str1: [str1_len]u8 = "foo".*; var str1: [str1_len]u8 = "foo".*;
const str1_ptr: [*]u8 = &str1; const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(testing.allocator, str1_ptr, str1_len); var roc_str1 = RocStr.init(str1_ptr, str1_len);
const str2_len = 3; const str2_len = 3;
var str2: [str2_len]u8 = "abc".*; var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2; const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(testing.allocator, str2_ptr, str2_len); var roc_str2 = RocStr.init(str2_ptr, str2_len);
const str3_len = 6; const str3_len = 6;
var str3: [str3_len]u8 = "fooabc".*; var str3: [str3_len]u8 = "fooabc".*;
const str3_ptr: [*]u8 = &str3; const str3_ptr: [*]u8 = &str3;
var roc_str3 = RocStr.init(testing.allocator, str3_ptr, str3_len); var roc_str3 = RocStr.init(str3_ptr, str3_len);
defer { defer {
roc_str1.deinit(testing.allocator); roc_str1.deinit();
roc_str2.deinit(testing.allocator); roc_str2.deinit();
roc_str3.deinit(testing.allocator); roc_str3.deinit();
} }
const result = strConcat(testing.allocator, InPlace.Clone, roc_str1, roc_str2); const result = strConcat(InPlace.Clone, roc_str1, roc_str2);
defer result.deinit(testing.allocator); defer result.deinit();
expect(roc_str3.eq(result)); expect(roc_str3.eq(result));
} }
@ -1004,12 +989,11 @@ pub const RocListStr = extern struct {
}; };
// Str.joinWith // Str.joinWith
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strJoinWithC(list: RocListStr, separator: RocStr) callconv(.C) RocStr { pub fn strJoinWithC(list: RocListStr, separator: RocStr) callconv(.C) RocStr {
return @call(.{ .modifier = always_inline }, strJoinWith, .{ std.heap.c_allocator, list, separator }); return @call(.{ .modifier = always_inline }, strJoinWith, .{ list, separator });
} }
fn strJoinWith(allocator: *Allocator, list: RocListStr, separator: RocStr) RocStr { fn strJoinWith(list: RocListStr, separator: RocStr) RocStr {
const len = list.list_length; const len = list.list_length;
if (len == 0) { if (len == 0) {
@ -1027,7 +1011,7 @@ fn strJoinWith(allocator: *Allocator, list: RocListStr, separator: RocStr) RocSt
// include size of the separator // include size of the separator
total_size += separator.len() * (len - 1); total_size += separator.len() * (len - 1);
var result = RocStr.allocate(allocator, InPlace.Clone, total_size); var result = RocStr.allocate(InPlace.Clone, total_size);
var result_ptr = result.asU8ptr(); var result_ptr = result.asU8ptr();
var offset: usize = 0; var offset: usize = 0;
@ -1050,45 +1034,45 @@ test "RocStr.joinWith: result is big" {
const sep_len = 2; const sep_len = 2;
var sep: [sep_len]u8 = ", ".*; var sep: [sep_len]u8 = ", ".*;
const sep_ptr: [*]u8 = &sep; const sep_ptr: [*]u8 = &sep;
var roc_sep = RocStr.init(testing.allocator, sep_ptr, sep_len); var roc_sep = RocStr.init(sep_ptr, sep_len);
const elem_len = 13; const elem_len = 13;
var elem: [elem_len]u8 = "foobarbazspam".*; var elem: [elem_len]u8 = "foobarbazspam".*;
const elem_ptr: [*]u8 = &elem; const elem_ptr: [*]u8 = &elem;
var roc_elem = RocStr.init(testing.allocator, elem_ptr, elem_len); var roc_elem = RocStr.init(elem_ptr, elem_len);
const result_len = 43; const result_len = 43;
var xresult: [result_len]u8 = "foobarbazspam, foobarbazspam, foobarbazspam".*; var xresult: [result_len]u8 = "foobarbazspam, foobarbazspam, foobarbazspam".*;
const result_ptr: [*]u8 = &xresult; const result_ptr: [*]u8 = &xresult;
var roc_result = RocStr.init(testing.allocator, result_ptr, result_len); var roc_result = RocStr.init(result_ptr, result_len);
var elements: [3]RocStr = .{ roc_elem, roc_elem, roc_elem }; var elements: [3]RocStr = .{ roc_elem, roc_elem, roc_elem };
const list = RocListStr{ .list_length = 3, .list_elements = @ptrCast([*]RocStr, &elements) }; const list = RocListStr{ .list_length = 3, .list_elements = @ptrCast([*]RocStr, &elements) };
defer { defer {
roc_sep.deinit(testing.allocator); roc_sep.deinit();
roc_elem.deinit(testing.allocator); roc_elem.deinit();
roc_result.deinit(testing.allocator); roc_result.deinit();
} }
const result = strJoinWith(testing.allocator, list, roc_sep); const result = strJoinWith(list, roc_sep);
defer result.deinit(testing.allocator); defer result.deinit();
expect(roc_result.eq(result)); expect(roc_result.eq(result));
} }
// Str.toBytes // Str.toBytes
pub fn strToBytesC(arg: RocStr) callconv(.C) RocList { pub fn strToBytesC(arg: RocStr) callconv(.C) RocList {
return @call(.{ .modifier = always_inline }, strToBytes, .{ std.heap.c_allocator, arg }); return @call(.{ .modifier = always_inline }, strToBytes, .{ arg });
} }
fn strToBytes(allocator: *Allocator, arg: RocStr) RocList { fn strToBytes(arg: RocStr) RocList {
if (arg.isEmpty()) { if (arg.isEmpty()) {
return RocList.empty(); return RocList.empty();
} else if (arg.isSmallStr()) { } else if (arg.isSmallStr()) {
const length = arg.len(); const length = arg.len();
const ptr = utils.allocateWithRefcount(allocator, @alignOf(usize), length); const ptr = utils.allocateWithRefcount(RocStr.alignment, length);
@memcpy(ptr, arg.asU8ptr(), length); @memcpy(ptr, arg.asU8ptr(), length);
@ -1106,25 +1090,25 @@ const FromUtf8Result = extern struct {
}; };
pub fn fromUtf8C(arg: RocList, output: *FromUtf8Result) callconv(.C) void { pub fn fromUtf8C(arg: RocList, output: *FromUtf8Result) callconv(.C) void {
output.* = @call(.{ .modifier = always_inline }, fromUtf8, .{ std.heap.c_allocator, arg }); output.* = @call(.{ .modifier = always_inline }, fromUtf8, .{ arg });
} }
fn fromUtf8(allocator: *Allocator, arg: RocList) FromUtf8Result { fn fromUtf8(arg: RocList) FromUtf8Result {
const bytes = @ptrCast([*]const u8, arg.bytes)[0..arg.length]; const bytes = @ptrCast([*]const u8, arg.bytes)[0..arg.length];
if (unicode.utf8ValidateSlice(bytes)) { if (unicode.utf8ValidateSlice(bytes)) {
// the output will be correct. Now we need to take ownership of the input // the output will be correct. Now we need to take ownership of the input
if (arg.len() <= SMALL_STR_MAX_LENGTH) { if (arg.len() <= SMALL_STR_MAX_LENGTH) {
// turn the bytes into a small string // turn the bytes into a small string
const string = RocStr.init(allocator, @ptrCast([*]u8, arg.bytes), arg.len()); const string = RocStr.init(@ptrCast([*]u8, arg.bytes), arg.len());
// then decrement the input list // then decrement the input list
const data_bytes = arg.len(); const data_bytes = arg.len();
utils.decref(allocator, @alignOf(usize), arg.bytes, data_bytes); utils.decref(RocStr.alignment, arg.bytes, data_bytes);
return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte }; return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte };
} else { } else {
const byte_list = arg.makeUnique(allocator, @alignOf(usize), @sizeOf(u8)); const byte_list = arg.makeUnique(RocStr.alignment, @sizeOf(u8));
const string = RocStr{ .str_bytes = byte_list.bytes, .str_len = byte_list.length }; const string = RocStr{ .str_bytes = byte_list.bytes, .str_len = byte_list.length };
@ -1135,7 +1119,7 @@ fn fromUtf8(allocator: *Allocator, arg: RocList) FromUtf8Result {
// consume the input list // consume the input list
const data_bytes = arg.len(); const data_bytes = arg.len();
utils.decref(allocator, @alignOf(usize), arg.bytes, data_bytes); utils.decref(RocStr.alignment, arg.bytes, data_bytes);
return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = temp.index, .problem_code = temp.problem }; return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = temp.index, .problem_code = temp.problem };
} }
@ -1202,11 +1186,11 @@ pub const Utf8ByteProblem = packed enum(u8) {
}; };
fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result { fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result {
return fromUtf8(std.testing.allocator, RocList{ .bytes = bytes, .length = length }); return fromUtf8(RocList{ .bytes = bytes, .length = length });
} }
fn validateUtf8BytesX(str: RocList) FromUtf8Result { fn validateUtf8BytesX(str: RocList) FromUtf8Result {
return fromUtf8(std.testing.allocator, str); return fromUtf8(str);
} }
fn expectOk(result: FromUtf8Result) void { fn expectOk(result: FromUtf8Result) void {
@ -1214,7 +1198,7 @@ fn expectOk(result: FromUtf8Result) void {
} }
fn sliceHelp(bytes: [*]const u8, length: usize) RocList { fn sliceHelp(bytes: [*]const u8, length: usize) RocList {
var list = RocList.allocate(testing.allocator, @alignOf(usize), length, @sizeOf(u8)); var list = RocList.allocate(RocStr.alignment, length, @sizeOf(u8));
@memcpy(list.bytes orelse unreachable, bytes, length); @memcpy(list.bytes orelse unreachable, bytes, length);
list.length = length; list.length = length;

View file

@ -1,5 +1,37 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const always_inline = std.builtin.CallOptions.Modifier.always_inline;
// If allocation fails, this must cxa_throw - it must not return a null pointer!
extern fn roc_alloc(alignment: usize, size: usize) callconv(.C) *c_void;
// This should never be passed a null pointer.
// If allocation fails, this must cxa_throw - it must not return a null pointer!
extern fn roc_realloc(alignment: usize, c_ptr: *c_void, new_size: usize) callconv(.C) *c_void;
// This should never be passed a null pointer.
extern fn roc_dealloc(alignment: usize, c_ptr: *c_void) callconv(.C) void;
pub fn alloc(alignment: usize, size: usize) [*]u8 {
return @ptrCast(
[*]u8,
@call(.{ .modifier = always_inline }, roc_alloc, .{ alignment, size })
);
}
pub fn realloc(alignment: usize, c_ptr: [*]u8, new_size: usize) [*]u8 {
return @ptrCast(
[*]u8,
@call(.{ .modifier = always_inline }, roc_realloc, .{ alignment, c_ptr, new_size })
);
}
pub fn dealloc(alignment: usize, c_ptr: [*]u8) void {
return @call(
.{ .modifier = always_inline },
roc_dealloc,
.{ alignment, c_ptr }
);
}
pub const Inc = fn (?[*]u8) callconv(.C) void; pub const Inc = fn (?[*]u8) callconv(.C) void;
pub const IncN = fn (?[*]u8, u64) callconv(.C) void; pub const IncN = fn (?[*]u8, u64) callconv(.C) void;
@ -62,7 +94,6 @@ pub fn intWidth(width: IntWidth) anytype {
} }
pub fn decref( pub fn decref(
allocator: *Allocator,
alignment: usize, alignment: usize,
bytes_or_null: ?[*]u8, bytes_or_null: ?[*]u8,
data_bytes: usize, data_bytes: usize,
@ -81,7 +112,7 @@ pub fn decref(
switch (alignment) { switch (alignment) {
16 => { 16 => {
if (refcount == REFCOUNT_ONE_ISIZE) { if (refcount == REFCOUNT_ONE_ISIZE) {
allocator.free((bytes - 16)[0 .. 16 + data_bytes]); dealloc(alignment, bytes - 16);
} else if (refcount_isize < 0) { } else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1; (isizes - 1)[0] = refcount - 1;
} }
@ -89,7 +120,7 @@ pub fn decref(
else => { else => {
// NOTE enums can currently have an alignment of < 8 // NOTE enums can currently have an alignment of < 8
if (refcount == REFCOUNT_ONE_ISIZE) { if (refcount == REFCOUNT_ONE_ISIZE) {
allocator.free((bytes - 8)[0 .. 8 + data_bytes]); dealloc(alignment, bytes - 8);
} else if (refcount_isize < 0) { } else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1; (isizes - 1)[0] = refcount - 1;
} }
@ -98,7 +129,6 @@ pub fn decref(
} }
pub fn allocateWithRefcount( pub fn allocateWithRefcount(
allocator: *Allocator,
alignment: usize, alignment: usize,
data_bytes: usize, data_bytes: usize,
) [*]u8 { ) [*]u8 {
@ -108,7 +138,7 @@ pub fn allocateWithRefcount(
16 => { 16 => {
const length = 2 * @sizeOf(usize) + data_bytes; const length = 2 * @sizeOf(usize) + data_bytes;
var new_bytes: []align(16) u8 = allocator.alignedAlloc(u8, 16, length) catch unreachable; var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(alignment, length));
var as_usize_array = @ptrCast([*]usize, new_bytes); var as_usize_array = @ptrCast([*]usize, new_bytes);
if (result_in_place) { if (result_in_place) {
@ -127,13 +157,13 @@ pub fn allocateWithRefcount(
else => { else => {
const length = @sizeOf(usize) + data_bytes; const length = @sizeOf(usize) + data_bytes;
var new_bytes: []align(8) u8 = allocator.alignedAlloc(u8, 8, length) catch unreachable; var new_bytes: [*]align(8) u8 = @alignCast(8, alloc(alignment, length));
var as_usize_array = @ptrCast([*]isize, new_bytes); var as_isize_array = @ptrCast([*]isize, new_bytes);
if (result_in_place) { if (result_in_place) {
as_usize_array[0] = @intCast(isize, number_of_slots); as_isize_array[0] = @intCast(isize, number_of_slots);
} else { } else {
as_usize_array[0] = REFCOUNT_ONE_ISIZE; as_isize_array[0] = REFCOUNT_ONE_ISIZE;
} }
var as_u8_array = @ptrCast([*]u8, new_bytes); var as_u8_array = @ptrCast([*]u8, new_bytes);
@ -146,7 +176,6 @@ pub fn allocateWithRefcount(
pub fn unsafeReallocate( pub fn unsafeReallocate(
source_ptr: [*]u8, source_ptr: [*]u8,
allocator: *Allocator,
alignment: usize, alignment: usize,
old_length: usize, old_length: usize,
new_length: usize, new_length: usize,
@ -165,8 +194,8 @@ pub fn unsafeReallocate(
// TODO handle out of memory // TODO handle out of memory
// NOTE realloc will dealloc the original allocation // NOTE realloc will dealloc the original allocation
const old_allocation = (source_ptr - align_width)[0..old_width]; const old_allocation = source_ptr - align_width;
const new_allocation = allocator.realloc(old_allocation, new_width) catch unreachable; const new_allocation = realloc(alignment, old_allocation, new_width);
const new_source = @ptrCast([*]u8, new_allocation) + align_width; const new_source = @ptrCast([*]u8, new_allocation) + align_width;
return new_source; return new_source;