mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00
Merge remote-tracking branch 'origin/trunk' into update_zig_09
This commit is contained in:
commit
a69bf971f0
583 changed files with 35930 additions and 13981 deletions
|
@ -26,21 +26,19 @@ pub const RocDec = extern struct {
|
|||
return .{ .num = num * one_point_zero_i128 };
|
||||
}
|
||||
|
||||
// TODO: There's got to be a better way to do this other than converting to Str
|
||||
pub fn fromF64(num: f64) ?RocDec {
|
||||
var digit_bytes: [19]u8 = undefined; // 19 = max f64 digits + '.' + '-'
|
||||
var result: f64 = num * comptime @intToFloat(f64, one_point_zero_i128);
|
||||
|
||||
var fbs = std.io.fixedBufferStream(digit_bytes[0..]);
|
||||
std.fmt.formatFloatDecimal(num, .{}, fbs.writer()) catch
|
||||
return null;
|
||||
|
||||
var dec = RocDec.fromStr(RocStr.init(&digit_bytes, fbs.pos));
|
||||
|
||||
if (dec) |d| {
|
||||
return d;
|
||||
} else {
|
||||
if (result > comptime @intToFloat(f64, math.maxInt(i128))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (result < comptime @intToFloat(f64, math.minInt(i128))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var ret: RocDec = .{ .num = @floatToInt(i128, result) };
|
||||
return ret;
|
||||
}
|
||||
|
||||
pub fn fromStr(roc_str: RocStr) ?RocDec {
|
||||
|
@ -729,6 +727,11 @@ test "fromF64" {
|
|||
try expectEqual(RocDec{ .num = 25500000000000000000 }, dec.?);
|
||||
}
|
||||
|
||||
test "fromF64 overflow" {
|
||||
var dec = RocDec.fromF64(1e308);
|
||||
try expectEqual(dec, null);
|
||||
}
|
||||
|
||||
test "fromStr: empty" {
|
||||
var roc_str = RocStr.init("", 0);
|
||||
var dec = RocDec.fromStr(roc_str);
|
||||
|
@ -898,11 +901,11 @@ test "toStr: -0.00045" {
|
|||
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
|
||||
}
|
||||
|
||||
test "toStr: -111.123456789" {
|
||||
var dec: RocDec = .{ .num = -111123456789000000000 };
|
||||
test "toStr: -111.123456" {
|
||||
var dec: RocDec = .{ .num = -111123456000000000000 };
|
||||
var res_roc_str = dec.toStr();
|
||||
|
||||
const res_slice: []const u8 = "-111.123456789"[0..];
|
||||
const res_slice: []const u8 = "-111.123456"[0..];
|
||||
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
|
||||
}
|
||||
|
||||
|
|
|
@ -408,7 +408,19 @@ const Dec = fn (?[*]u8) callconv(.C) void;
|
|||
const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
|
||||
|
||||
// Dict.insert : Dict k v, k, v -> Dict k v
|
||||
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 result = input.makeUnique(alignment, key_width, value_width);
|
||||
|
@ -543,7 +555,13 @@ pub fn elementsRc(dict: RocDict, alignment: Alignment, key_width: usize, value_w
|
|||
}
|
||||
}
|
||||
|
||||
pub fn dictKeys(dict: RocDict, alignment: Alignment, key_width: usize, value_width: usize, inc_key: Inc, output: *RocList) callconv(.C) void {
|
||||
pub fn dictKeys(
|
||||
dict: RocDict,
|
||||
alignment: Alignment,
|
||||
key_width: usize,
|
||||
value_width: usize,
|
||||
inc_key: Inc,
|
||||
) callconv(.C) RocList {
|
||||
const size = dict.capacity();
|
||||
|
||||
var length: usize = 0;
|
||||
|
@ -558,8 +576,7 @@ pub fn dictKeys(dict: RocDict, alignment: Alignment, key_width: usize, value_wid
|
|||
}
|
||||
|
||||
if (length == 0) {
|
||||
output.* = RocList.empty();
|
||||
return;
|
||||
return RocList.empty();
|
||||
}
|
||||
|
||||
const data_bytes = length * key_width;
|
||||
|
@ -581,10 +598,16 @@ pub fn dictKeys(dict: RocDict, alignment: Alignment, key_width: usize, value_wid
|
|||
}
|
||||
}
|
||||
|
||||
output.* = RocList{ .bytes = ptr, .length = length };
|
||||
return RocList{ .bytes = ptr, .length = length, .capacity = length };
|
||||
}
|
||||
|
||||
pub fn dictValues(dict: RocDict, alignment: Alignment, key_width: usize, value_width: usize, inc_value: Inc, output: *RocList) callconv(.C) void {
|
||||
pub fn dictValues(
|
||||
dict: RocDict,
|
||||
alignment: Alignment,
|
||||
key_width: usize,
|
||||
value_width: usize,
|
||||
inc_value: Inc,
|
||||
) callconv(.C) RocList {
|
||||
const size = dict.capacity();
|
||||
|
||||
var length: usize = 0;
|
||||
|
@ -599,8 +622,7 @@ pub fn dictValues(dict: RocDict, alignment: Alignment, key_width: usize, value_w
|
|||
}
|
||||
|
||||
if (length == 0) {
|
||||
output.* = RocList.empty();
|
||||
return;
|
||||
return RocList.empty();
|
||||
}
|
||||
|
||||
const data_bytes = length * value_width;
|
||||
|
@ -622,14 +644,25 @@ pub fn dictValues(dict: RocDict, alignment: Alignment, key_width: usize, value_w
|
|||
}
|
||||
}
|
||||
|
||||
output.* = RocList{ .bytes = ptr, .length = length };
|
||||
return RocList{ .bytes = ptr, .length = length, .capacity = length };
|
||||
}
|
||||
|
||||
fn doNothing(_: Opaque) callconv(.C) void {
|
||||
return;
|
||||
}
|
||||
|
||||
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(alignment, key_width, value_width);
|
||||
|
||||
var i: usize = 0;
|
||||
|
|
|
@ -16,6 +16,7 @@ const HasTagId = fn (u16, ?[*]u8) callconv(.C) extern struct { matched: bool, da
|
|||
pub const RocList = extern struct {
|
||||
bytes: ?[*]u8,
|
||||
length: usize,
|
||||
capacity: usize,
|
||||
|
||||
pub fn len(self: RocList) usize {
|
||||
return self.length;
|
||||
|
@ -26,7 +27,7 @@ pub const RocList = extern struct {
|
|||
}
|
||||
|
||||
pub fn empty() RocList {
|
||||
return RocList{ .bytes = null, .length = 0 };
|
||||
return RocList{ .bytes = null, .length = 0, .capacity = 0 };
|
||||
}
|
||||
|
||||
pub fn isUnique(self: RocList) bool {
|
||||
|
@ -50,6 +51,7 @@ pub const RocList = extern struct {
|
|||
return RocList{
|
||||
.bytes = utils.allocateWithRefcount(data_bytes, alignment),
|
||||
.length = length,
|
||||
.capacity = length,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -96,7 +98,7 @@ pub const RocList = extern struct {
|
|||
if (self.isUnique()) {
|
||||
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, .capacity = new_length };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,6 +130,7 @@ pub const RocList = extern struct {
|
|||
const result = RocList{
|
||||
.bytes = first_slot,
|
||||
.length = new_length,
|
||||
.capacity = new_length,
|
||||
};
|
||||
|
||||
utils.decref(self.bytes, old_length * element_width, alignment);
|
||||
|
@ -887,14 +890,23 @@ pub fn listSublist(
|
|||
}
|
||||
|
||||
const keep_len = std.math.min(len, size - start);
|
||||
const drop_len = std.math.max(start, 0);
|
||||
const drop_start_len = start;
|
||||
const drop_end_len = size - (start + keep_len);
|
||||
|
||||
// Decrement the reference counts of elements before `start`.
|
||||
var i: usize = 0;
|
||||
while (i < drop_len) : (i += 1) {
|
||||
while (i < drop_start_len) : (i += 1) {
|
||||
const element = source_ptr + i * element_width;
|
||||
dec(element);
|
||||
}
|
||||
|
||||
// Decrement the reference counts of elements after `start + keep_len`.
|
||||
i = 0;
|
||||
while (i < drop_end_len) : (i += 1) {
|
||||
const element = source_ptr + (start + keep_len + i) * element_width;
|
||||
dec(element);
|
||||
}
|
||||
|
||||
const output = RocList.allocate(alignment, keep_len, element_width);
|
||||
const target_ptr = output.bytes orelse unreachable;
|
||||
|
||||
|
@ -1237,7 +1249,7 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
|
|||
@memcpy(new_source + list_a.len() * element_width, source_b, list_b.len() * element_width);
|
||||
}
|
||||
|
||||
return RocList{ .bytes = new_source, .length = total_length };
|
||||
return RocList{ .bytes = new_source, .length = total_length, .capacity = total_length };
|
||||
}
|
||||
}
|
||||
const total_length: usize = list_a.len() + list_b.len();
|
||||
|
@ -1256,95 +1268,56 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
|
|||
return output;
|
||||
}
|
||||
|
||||
pub fn listSetInPlace(
|
||||
bytes: ?[*]u8,
|
||||
pub fn listReplaceInPlace(
|
||||
list: RocList,
|
||||
index: usize,
|
||||
element: Opaque,
|
||||
element_width: usize,
|
||||
dec: Dec,
|
||||
) callconv(.C) ?[*]u8 {
|
||||
out_element: ?[*]u8,
|
||||
) 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 LowLevelListGet input index item else input`
|
||||
// `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
|
||||
|
||||
return listSetInPlaceHelp(bytes, index, element, element_width, dec);
|
||||
return listReplaceInPlaceHelp(list, index, element, element_width, out_element);
|
||||
}
|
||||
|
||||
pub fn listSet(
|
||||
bytes: ?[*]u8,
|
||||
length: usize,
|
||||
pub fn listReplace(
|
||||
list: RocList,
|
||||
alignment: u32,
|
||||
index: usize,
|
||||
element: Opaque,
|
||||
element_width: usize,
|
||||
dec: Dec,
|
||||
) callconv(.C) ?[*]u8 {
|
||||
out_element: ?[*]u8,
|
||||
) 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 LowLevelListGet input index item else input`
|
||||
// `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
|
||||
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), bytes));
|
||||
|
||||
if ((ptr - 1)[0] == utils.REFCOUNT_ONE) {
|
||||
return listSetInPlaceHelp(bytes, index, element, element_width, dec);
|
||||
} else {
|
||||
return listSetImmutable(bytes, length, alignment, index, element, element_width, dec);
|
||||
}
|
||||
return listReplaceInPlaceHelp(list.makeUnique(alignment, element_width), index, element, element_width, out_element);
|
||||
}
|
||||
|
||||
inline fn listSetInPlaceHelp(
|
||||
bytes: ?[*]u8,
|
||||
inline fn listReplaceInPlaceHelp(
|
||||
list: RocList,
|
||||
index: usize,
|
||||
element: Opaque,
|
||||
element_width: usize,
|
||||
dec: Dec,
|
||||
) ?[*]u8 {
|
||||
out_element: ?[*]u8,
|
||||
) RocList {
|
||||
// the element we will replace
|
||||
var element_at_index = (bytes orelse undefined) + (index * element_width);
|
||||
var element_at_index = (list.bytes orelse undefined) + (index * element_width);
|
||||
|
||||
// decrement its refcount
|
||||
dec(element_at_index);
|
||||
// copy out the old element
|
||||
@memcpy(out_element orelse undefined, element_at_index, element_width);
|
||||
|
||||
// copy in the new element
|
||||
@memcpy(element_at_index, element orelse undefined, element_width);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
inline fn listSetImmutable(
|
||||
old_bytes: ?[*]u8,
|
||||
length: usize,
|
||||
alignment: u32,
|
||||
index: usize,
|
||||
element: Opaque,
|
||||
element_width: usize,
|
||||
dec: Dec,
|
||||
) ?[*]u8 {
|
||||
const data_bytes = length * element_width;
|
||||
|
||||
var new_bytes = utils.allocateWithRefcount(data_bytes, alignment);
|
||||
|
||||
@memcpy(new_bytes, old_bytes orelse undefined, data_bytes);
|
||||
|
||||
// the element we will replace
|
||||
var element_at_index = new_bytes + (index * element_width);
|
||||
|
||||
// decrement its refcount
|
||||
dec(element_at_index);
|
||||
|
||||
// copy in the new element
|
||||
@memcpy(element_at_index, element orelse undefined, element_width);
|
||||
|
||||
// consume RC token of original
|
||||
utils.decref(old_bytes, data_bytes, alignment);
|
||||
|
||||
//return list;
|
||||
return new_bytes;
|
||||
return list;
|
||||
}
|
||||
|
||||
pub fn listFindUnsafe(
|
||||
|
|
|
@ -49,8 +49,8 @@ comptime {
|
|||
exportListFn(list.listConcat, "concat");
|
||||
exportListFn(list.listSublist, "sublist");
|
||||
exportListFn(list.listDropAt, "drop_at");
|
||||
exportListFn(list.listSet, "set");
|
||||
exportListFn(list.listSetInPlace, "set_in_place");
|
||||
exportListFn(list.listReplace, "replace");
|
||||
exportListFn(list.listReplaceInPlace, "replace_in_place");
|
||||
exportListFn(list.listSwap, "swap");
|
||||
exportListFn(list.listAny, "any");
|
||||
exportListFn(list.listAll, "all");
|
||||
|
@ -98,6 +98,14 @@ comptime {
|
|||
num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil.");
|
||||
}
|
||||
|
||||
inline for (INTEGERS) |FROM| {
|
||||
inline for (INTEGERS) |TO| {
|
||||
// We're exporting more than we need here, but that's okay.
|
||||
num.exportToIntCheckingMax(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max.");
|
||||
num.exportToIntCheckingMaxAndMin(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max_and_min.");
|
||||
}
|
||||
}
|
||||
|
||||
inline for (FLOATS) |T| {
|
||||
num.exportAsin(T, ROC_BUILTINS ++ "." ++ NUM ++ ".asin.");
|
||||
num.exportAcos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".acos.");
|
||||
|
@ -147,6 +155,7 @@ comptime {
|
|||
exportUtilsFn(utils.increfC, "incref");
|
||||
exportUtilsFn(utils.decrefC, "decref");
|
||||
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
|
||||
exportUtilsFn(utils.allocateWithRefcountC, "allocate_with_refcount");
|
||||
exportExpectFn(expect.expectFailedC, "expect_failed");
|
||||
exportExpectFn(expect.getExpectFailuresC, "get_expect_failures");
|
||||
exportExpectFn(expect.deinitFailuresC, "deinit_failures");
|
||||
|
|
|
@ -108,6 +108,39 @@ pub fn exportDivCeil(comptime T: type, comptime name: []const u8) void {
|
|||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn ToIntCheckedResult(comptime T: type) type {
|
||||
// On the Roc side we sort by alignment; putting the errorcode last
|
||||
// always works out (no number with smaller alignment than 1).
|
||||
return extern struct {
|
||||
value: T,
|
||||
out_of_bounds: bool,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn exportToIntCheckingMax(comptime From: type, comptime To: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(input: From) callconv(.C) ToIntCheckedResult(To) {
|
||||
if (input > std.math.maxInt(To)) {
|
||||
return .{ .out_of_bounds = true, .value = 0 };
|
||||
}
|
||||
return .{ .out_of_bounds = false, .value = @intCast(To, input) };
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(From), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportToIntCheckingMaxAndMin(comptime From: type, comptime To: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(input: From) callconv(.C) ToIntCheckedResult(To) {
|
||||
if (input > std.math.maxInt(To) or input < std.math.minInt(To)) {
|
||||
return .{ .out_of_bounds = true, .value = 0 };
|
||||
}
|
||||
return .{ .out_of_bounds = false, .value = @intCast(To, input) };
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(From), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 {
|
||||
return @call(.{ .modifier = always_inline }, bytesToU16, .{ arg, position });
|
||||
}
|
||||
|
|
|
@ -15,9 +15,11 @@ const InPlace = enum(u8) {
|
|||
Clone,
|
||||
};
|
||||
|
||||
const SMALL_STR_MAX_LENGTH = small_string_size - 1;
|
||||
const small_string_size = @sizeOf(RocStr);
|
||||
const blank_small_string: [@sizeOf(RocStr)]u8 = init_blank_small_string(small_string_size);
|
||||
const MASK_ISIZE: isize = std.math.minInt(isize);
|
||||
const MASK: usize = @bitCast(usize, MASK_ISIZE);
|
||||
|
||||
const SMALL_STR_MAX_LENGTH = SMALL_STRING_SIZE - 1;
|
||||
const SMALL_STRING_SIZE = @sizeOf(RocStr);
|
||||
|
||||
fn init_blank_small_string(comptime n: usize) [n]u8 {
|
||||
var prime_list: [n]u8 = undefined;
|
||||
|
@ -33,14 +35,15 @@ fn init_blank_small_string(comptime n: usize) [n]u8 {
|
|||
pub const RocStr = extern struct {
|
||||
str_bytes: ?[*]u8,
|
||||
str_len: usize,
|
||||
str_capacity: usize,
|
||||
|
||||
pub const alignment = @alignOf(usize);
|
||||
|
||||
pub inline fn empty() RocStr {
|
||||
const small_str_flag: isize = std.math.minInt(isize);
|
||||
return RocStr{
|
||||
.str_len = @bitCast(usize, small_str_flag),
|
||||
.str_len = 0,
|
||||
.str_bytes = null,
|
||||
.str_capacity = MASK,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -59,24 +62,22 @@ pub const RocStr = extern struct {
|
|||
return RocStr{
|
||||
.str_bytes = first_element,
|
||||
.str_len = number_of_chars,
|
||||
.str_capacity = number_of_chars,
|
||||
};
|
||||
}
|
||||
|
||||
// allocate space for a (big or small) RocStr, but put nothing in it yet
|
||||
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) {
|
||||
return RocStr.initBig(result_in_place, number_of_chars);
|
||||
} else {
|
||||
var t = blank_small_string;
|
||||
var string = RocStr.empty();
|
||||
|
||||
const mask: u8 = 0b1000_0000;
|
||||
const final_byte = @truncate(u8, number_of_chars) | mask;
|
||||
string.asU8ptr()[@sizeOf(RocStr) - 1] = @intCast(u8, number_of_chars) | 0b1000_0000;
|
||||
|
||||
t[small_string_size - 1] = final_byte;
|
||||
|
||||
return @bitCast(RocStr, t);
|
||||
return string;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,23 +204,31 @@ pub const RocStr = extern struct {
|
|||
|
||||
// NOTE: returns false for empty string!
|
||||
pub fn isSmallStr(self: RocStr) bool {
|
||||
return @bitCast(isize, self.str_len) < 0;
|
||||
return @bitCast(isize, self.str_capacity) < 0;
|
||||
}
|
||||
|
||||
fn asArray(self: RocStr) [@sizeOf(RocStr)]u8 {
|
||||
const as_int = @ptrToInt(&self);
|
||||
const as_ptr = @intToPtr([*]u8, as_int);
|
||||
const slice = as_ptr[0..@sizeOf(RocStr)];
|
||||
|
||||
return slice.*;
|
||||
}
|
||||
|
||||
pub fn len(self: RocStr) usize {
|
||||
const bytes: [*]const u8 = @ptrCast([*]const u8, &self);
|
||||
const last_byte = bytes[@sizeOf(RocStr) - 1];
|
||||
const small_len = @as(usize, last_byte ^ 0b1000_0000);
|
||||
const big_len = self.str_len;
|
||||
if (self.isSmallStr()) {
|
||||
return self.asArray()[@sizeOf(RocStr) - 1] ^ 0b1000_0000;
|
||||
} else {
|
||||
return self.str_len;
|
||||
}
|
||||
}
|
||||
|
||||
// Since this conditional would be prone to branch misprediction,
|
||||
// make sure it will compile to a cmov.
|
||||
return if (self.isSmallStr()) small_len else big_len;
|
||||
pub fn capacity(self: RocStr) usize {
|
||||
return self.str_capacity ^ MASK;
|
||||
}
|
||||
|
||||
pub fn isEmpty(self: RocStr) bool {
|
||||
const empty_len = comptime RocStr.empty().str_len;
|
||||
return self.str_len == empty_len;
|
||||
return self.len() == 0;
|
||||
}
|
||||
|
||||
// If a string happens to be null-terminated already, then we can pass its
|
||||
|
@ -268,36 +277,6 @@ pub const RocStr = extern struct {
|
|||
}
|
||||
}
|
||||
|
||||
// Returns (@sizeOf(RocStr) - 1) for small strings and the empty string.
|
||||
// Returns 0 for refcounted strings and immortal strings.
|
||||
// Returns the stored capacity value for all other strings.
|
||||
pub fn capacity(self: RocStr) usize {
|
||||
const length = self.len();
|
||||
const longest_small_str = @sizeOf(RocStr) - 1;
|
||||
|
||||
if (length <= longest_small_str) {
|
||||
// Note that although empty strings technically have the full
|
||||
// capacity of a small string available, they aren't marked as small
|
||||
// strings, so if you want to make use of that capacity, you need
|
||||
// to first change its flag to mark it as a small string!
|
||||
return longest_small_str;
|
||||
} else {
|
||||
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), self.str_bytes));
|
||||
const capacity_or_refcount: isize = (ptr - 1)[0];
|
||||
|
||||
if (capacity_or_refcount > 0) {
|
||||
// If capacity_or_refcount is positive, that means it's a
|
||||
// capacity value.
|
||||
return capacity_or_refcount;
|
||||
} else {
|
||||
// This is either a refcount or else this big string is stored
|
||||
// in a readonly section; either way, it has no capacity,
|
||||
// because we cannot mutate it in-place!
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn isUnique(self: RocStr) bool {
|
||||
// small strings can be copied
|
||||
if (self.isSmallStr()) {
|
||||
|
@ -512,8 +491,12 @@ fn strFromFloatHelp(comptime T: type, float: T) RocStr {
|
|||
}
|
||||
|
||||
// Str.split
|
||||
pub fn strSplitInPlaceC(array: [*]RocStr, string: RocStr, delimiter: RocStr) callconv(.C) void {
|
||||
return @call(.{ .modifier = always_inline }, strSplitInPlace, .{ array, string, delimiter });
|
||||
pub fn strSplitInPlaceC(opt_array: ?[*]RocStr, string: RocStr, delimiter: RocStr) callconv(.C) void {
|
||||
if (opt_array) |array| {
|
||||
return @call(.{ .modifier = always_inline }, strSplitInPlace, .{ array, string, delimiter });
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn strSplitInPlace(array: [*]RocStr, string: RocStr, delimiter: RocStr) void {
|
||||
|
@ -1128,7 +1111,11 @@ fn strConcat(arg1: RocStr, arg2: RocStr) RocStr {
|
|||
|
||||
@memcpy(new_source + arg1.len(), arg2.asU8ptr(), arg2.len());
|
||||
|
||||
return RocStr{ .str_bytes = new_source, .str_len = combined_length };
|
||||
return RocStr{
|
||||
.str_bytes = new_source,
|
||||
.str_len = combined_length,
|
||||
.str_capacity = combined_length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1174,11 +1161,18 @@ test "RocStr.concat: small concat small" {
|
|||
pub const RocListStr = extern struct {
|
||||
list_elements: ?[*]RocStr,
|
||||
list_length: usize,
|
||||
list_capacity: usize,
|
||||
};
|
||||
|
||||
// Str.joinWith
|
||||
pub fn strJoinWithC(list: RocListStr, separator: RocStr) callconv(.C) RocStr {
|
||||
return @call(.{ .modifier = always_inline }, strJoinWith, .{ list, separator });
|
||||
pub fn strJoinWithC(list: RocList, separator: RocStr) callconv(.C) RocStr {
|
||||
const roc_list_str = RocListStr{
|
||||
.list_elements = @ptrCast(?[*]RocStr, @alignCast(@alignOf(usize), list.bytes)),
|
||||
.list_length = list.length,
|
||||
.list_capacity = list.capacity,
|
||||
};
|
||||
|
||||
return @call(.{ .modifier = always_inline }, strJoinWith, .{ roc_list_str, separator });
|
||||
}
|
||||
|
||||
fn strJoinWith(list: RocListStr, separator: RocStr) RocStr {
|
||||
|
@ -1235,7 +1229,11 @@ test "RocStr.joinWith: result is big" {
|
|||
var roc_result = RocStr.init(result_ptr, result_len);
|
||||
|
||||
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_capacity = 3,
|
||||
.list_elements = @ptrCast([*]RocStr, &elements),
|
||||
};
|
||||
|
||||
defer {
|
||||
roc_sep.deinit();
|
||||
|
@ -1264,9 +1262,9 @@ inline fn strToBytes(arg: RocStr) RocList {
|
|||
|
||||
@memcpy(ptr, arg.asU8ptr(), length);
|
||||
|
||||
return RocList{ .length = length, .bytes = ptr };
|
||||
return RocList{ .length = length, .bytes = ptr, .capacity = length };
|
||||
} else {
|
||||
return RocList{ .length = arg.len(), .bytes = arg.str_bytes };
|
||||
return RocList{ .length = arg.len(), .bytes = arg.str_bytes, .capacity = arg.str_capacity };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1308,7 +1306,11 @@ inline fn fromUtf8(arg: RocList, update_mode: UpdateMode) FromUtf8Result {
|
|||
} else {
|
||||
const byte_list = arg.makeUniqueExtra(RocStr.alignment, @sizeOf(u8), update_mode);
|
||||
|
||||
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,
|
||||
.str_capacity = byte_list.capacity,
|
||||
};
|
||||
|
||||
return FromUtf8Result{
|
||||
.is_ok = true,
|
||||
|
@ -1412,7 +1414,7 @@ pub const Utf8ByteProblem = enum(u8) {
|
|||
};
|
||||
|
||||
fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result {
|
||||
return fromUtf8(RocList{ .bytes = bytes, .length = length }, .Immutable);
|
||||
return fromUtf8(RocList{ .bytes = bytes, .length = length, .capacity = length }, .Immutable);
|
||||
}
|
||||
|
||||
fn validateUtf8BytesX(str: RocList) FromUtf8Result {
|
||||
|
@ -1824,13 +1826,13 @@ test "strTrim: blank" {
|
|||
}
|
||||
|
||||
test "strTrim: large to large" {
|
||||
const original_bytes = " hello giant world ";
|
||||
const original_bytes = " hello even more giant world ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.deinit();
|
||||
|
||||
try expect(!original.isSmallStr());
|
||||
|
||||
const expected_bytes = "hello giant world";
|
||||
const expected_bytes = "hello even more giant world";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
|
@ -1842,13 +1844,13 @@ test "strTrim: large to large" {
|
|||
}
|
||||
|
||||
test "strTrim: large to small" {
|
||||
const original_bytes = " hello world ";
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.deinit();
|
||||
|
||||
try expect(!original.isSmallStr());
|
||||
|
||||
const expected_bytes = "hello world";
|
||||
const expected_bytes = "hello";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
|
@ -1861,13 +1863,13 @@ test "strTrim: large to small" {
|
|||
}
|
||||
|
||||
test "strTrim: small to small" {
|
||||
const original_bytes = " hello world ";
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.deinit();
|
||||
|
||||
try expect(original.isSmallStr());
|
||||
|
||||
const expected_bytes = "hello world";
|
||||
const expected_bytes = "hello";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
|
@ -1895,13 +1897,13 @@ test "strTrimLeft: blank" {
|
|||
}
|
||||
|
||||
test "strTrimLeft: large to large" {
|
||||
const original_bytes = " hello giant world ";
|
||||
const original_bytes = " hello even more giant world ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.deinit();
|
||||
|
||||
try expect(!original.isSmallStr());
|
||||
|
||||
const expected_bytes = "hello giant world ";
|
||||
const expected_bytes = "hello even more giant world ";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
|
@ -1913,13 +1915,13 @@ test "strTrimLeft: large to large" {
|
|||
}
|
||||
|
||||
test "strTrimLeft: large to small" {
|
||||
const original_bytes = " hello world ";
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.deinit();
|
||||
|
||||
try expect(!original.isSmallStr());
|
||||
|
||||
const expected_bytes = "hello world ";
|
||||
const expected_bytes = "hello ";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
|
@ -1932,13 +1934,13 @@ test "strTrimLeft: large to small" {
|
|||
}
|
||||
|
||||
test "strTrimLeft: small to small" {
|
||||
const original_bytes = " hello world ";
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.deinit();
|
||||
|
||||
try expect(original.isSmallStr());
|
||||
|
||||
const expected_bytes = "hello world ";
|
||||
const expected_bytes = "hello ";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
|
@ -1966,13 +1968,13 @@ test "strTrimRight: blank" {
|
|||
}
|
||||
|
||||
test "strTrimRight: large to large" {
|
||||
const original_bytes = " hello giant world ";
|
||||
const original_bytes = " hello even more giant world ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.deinit();
|
||||
|
||||
try expect(!original.isSmallStr());
|
||||
|
||||
const expected_bytes = " hello giant world";
|
||||
const expected_bytes = " hello even more giant world";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
|
@ -1984,13 +1986,13 @@ test "strTrimRight: large to large" {
|
|||
}
|
||||
|
||||
test "strTrimRight: large to small" {
|
||||
const original_bytes = " hello world ";
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.deinit();
|
||||
|
||||
try expect(!original.isSmallStr());
|
||||
|
||||
const expected_bytes = " hello world";
|
||||
const expected_bytes = " hello";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
|
@ -2003,13 +2005,13 @@ test "strTrimRight: large to small" {
|
|||
}
|
||||
|
||||
test "strTrimRight: small to small" {
|
||||
const original_bytes = " hello world ";
|
||||
const original_bytes = " hello ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.deinit();
|
||||
|
||||
try expect(original.isSmallStr());
|
||||
|
||||
const expected_bytes = " hello world";
|
||||
const expected_bytes = " hello";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const std = @import("std");
|
||||
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
|
||||
const Monotonic = std.builtin.AtomicOrder.Monotonic;
|
||||
|
||||
pub fn WithOverflow(comptime T: type) type {
|
||||
return extern struct { value: T, has_overflowed: bool };
|
||||
|
@ -38,14 +39,14 @@ fn testing_roc_alloc(size: usize, _: u32) callconv(.C) ?*anyopaque {
|
|||
}
|
||||
|
||||
fn testing_roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, _: u32) callconv(.C) ?*anyopaque {
|
||||
const ptr = @ptrCast([*]u8, @alignCast(16, c_ptr));
|
||||
const ptr = @ptrCast([*]u8, @alignCast(2 * @alignOf(usize), c_ptr));
|
||||
const slice = ptr[0..old_size];
|
||||
|
||||
return @ptrCast(?*anyopaque, std.testing.allocator.realloc(slice, new_size) catch unreachable);
|
||||
}
|
||||
|
||||
fn testing_roc_dealloc(c_ptr: *anyopaque, _: u32) callconv(.C) void {
|
||||
const ptr = @ptrCast([*]u8, @alignCast(16, c_ptr));
|
||||
const ptr = @ptrCast([*]u8, @alignCast(2 * @alignOf(usize), c_ptr));
|
||||
|
||||
std.testing.allocator.destroy(ptr);
|
||||
}
|
||||
|
@ -120,10 +121,32 @@ pub const IntWidth = enum(u8) {
|
|||
I128 = 9,
|
||||
};
|
||||
|
||||
const Refcount = enum {
|
||||
none,
|
||||
normal,
|
||||
atomic,
|
||||
};
|
||||
|
||||
const RC_TYPE = Refcount.normal;
|
||||
|
||||
pub fn increfC(ptr_to_refcount: *isize, amount: isize) callconv(.C) void {
|
||||
if (RC_TYPE == Refcount.none) return;
|
||||
var refcount = ptr_to_refcount.*;
|
||||
var masked_amount = if (refcount == REFCOUNT_MAX_ISIZE) 0 else amount;
|
||||
ptr_to_refcount.* = refcount + masked_amount;
|
||||
if (refcount < REFCOUNT_MAX_ISIZE) {
|
||||
switch (RC_TYPE) {
|
||||
Refcount.normal => {
|
||||
ptr_to_refcount.* = std.math.min(refcount + amount, REFCOUNT_MAX_ISIZE);
|
||||
},
|
||||
Refcount.atomic => {
|
||||
var next = std.math.min(refcount + amount, REFCOUNT_MAX_ISIZE);
|
||||
while (@cmpxchgWeak(isize, ptr_to_refcount, refcount, next, Monotonic, Monotonic)) |found| {
|
||||
refcount = found;
|
||||
next = std.math.min(refcount + amount, REFCOUNT_MAX_ISIZE);
|
||||
}
|
||||
},
|
||||
Refcount.none => unreachable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrefC(
|
||||
|
@ -169,71 +192,51 @@ inline fn decref_ptr_to_refcount(
|
|||
refcount_ptr: [*]isize,
|
||||
alignment: u32,
|
||||
) void {
|
||||
const refcount: isize = refcount_ptr[0];
|
||||
if (RC_TYPE == Refcount.none) return;
|
||||
const extra_bytes = std.math.max(alignment, @sizeOf(usize));
|
||||
|
||||
if (refcount == REFCOUNT_ONE_ISIZE) {
|
||||
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
|
||||
} else if (refcount < 0) {
|
||||
refcount_ptr[0] = refcount - 1;
|
||||
switch (RC_TYPE) {
|
||||
Refcount.normal => {
|
||||
const refcount: isize = refcount_ptr[0];
|
||||
if (refcount == REFCOUNT_ONE_ISIZE) {
|
||||
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
|
||||
} else if (refcount < REFCOUNT_MAX_ISIZE) {
|
||||
refcount_ptr[0] = refcount - 1;
|
||||
}
|
||||
},
|
||||
Refcount.atomic => {
|
||||
if (refcount_ptr[0] < REFCOUNT_MAX_ISIZE) {
|
||||
var last = @atomicRmw(isize, &refcount_ptr[0], std.builtin.AtomicRmwOp.Sub, 1, Monotonic);
|
||||
if (last == REFCOUNT_ONE_ISIZE) {
|
||||
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
|
||||
}
|
||||
}
|
||||
},
|
||||
Refcount.none => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocateWithRefcountC(
|
||||
data_bytes: usize,
|
||||
element_alignment: u32,
|
||||
) callconv(.C) [*]u8 {
|
||||
return allocateWithRefcount(data_bytes, element_alignment);
|
||||
}
|
||||
|
||||
pub fn allocateWithRefcount(
|
||||
data_bytes: usize,
|
||||
element_alignment: u32,
|
||||
) [*]u8 {
|
||||
const alignment = std.math.max(@sizeOf(usize), element_alignment);
|
||||
const first_slot_offset = std.math.max(@sizeOf(usize), element_alignment);
|
||||
const ptr_width = @sizeOf(usize);
|
||||
const alignment = std.math.max(ptr_width, element_alignment);
|
||||
const length = alignment + data_bytes;
|
||||
|
||||
switch (alignment) {
|
||||
16 => {
|
||||
// TODO handle alloc failing!
|
||||
var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment) orelse unreachable);
|
||||
var new_bytes: [*]u8 = alloc(length, alignment) orelse unreachable;
|
||||
|
||||
var as_usize_array = @ptrCast([*]usize, new_bytes);
|
||||
as_usize_array[0] = 0;
|
||||
as_usize_array[1] = REFCOUNT_ONE;
|
||||
const data_ptr = new_bytes + alignment;
|
||||
const refcount_ptr = @ptrCast([*]usize, @alignCast(ptr_width, data_ptr) - ptr_width);
|
||||
refcount_ptr[0] = if (RC_TYPE == Refcount.none) REFCOUNT_MAX_ISIZE else REFCOUNT_ONE;
|
||||
|
||||
var as_u8_array = @ptrCast([*]u8, new_bytes);
|
||||
const first_slot = as_u8_array + first_slot_offset;
|
||||
|
||||
return first_slot;
|
||||
},
|
||||
8 => {
|
||||
// TODO handle alloc failing!
|
||||
var raw = alloc(length, alignment) orelse unreachable;
|
||||
var new_bytes: [*]align(8) u8 = @alignCast(8, raw);
|
||||
|
||||
var as_isize_array = @ptrCast([*]isize, new_bytes);
|
||||
as_isize_array[0] = REFCOUNT_ONE_ISIZE;
|
||||
|
||||
var as_u8_array = @ptrCast([*]u8, new_bytes);
|
||||
const first_slot = as_u8_array + first_slot_offset;
|
||||
|
||||
return first_slot;
|
||||
},
|
||||
4 => {
|
||||
// TODO handle alloc failing!
|
||||
var raw = alloc(length, alignment) orelse unreachable;
|
||||
var new_bytes: [*]align(@alignOf(isize)) u8 = @alignCast(@alignOf(isize), raw);
|
||||
|
||||
var as_isize_array = @ptrCast([*]isize, new_bytes);
|
||||
as_isize_array[0] = REFCOUNT_ONE_ISIZE;
|
||||
|
||||
var as_u8_array = @ptrCast([*]u8, new_bytes);
|
||||
const first_slot = as_u8_array + first_slot_offset;
|
||||
|
||||
return first_slot;
|
||||
},
|
||||
else => {
|
||||
// const stdout = std.io.getStdOut().writer();
|
||||
// stdout.print("alignment: {d}", .{alignment}) catch unreachable;
|
||||
// @panic("allocateWithRefcount with invalid alignment");
|
||||
unreachable;
|
||||
},
|
||||
}
|
||||
return data_ptr;
|
||||
}
|
||||
|
||||
pub const CSlice = extern struct {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue