Merge remote-tracking branch 'origin/trunk' into update_zig_09

This commit is contained in:
Folkert 2022-04-06 14:48:04 +02:00
commit a69bf971f0
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
583 changed files with 35930 additions and 13981 deletions

View file

@ -7,7 +7,7 @@ To add a builtin:
2. Make sure the function is public with the `pub` keyword and uses the C calling convention. This is really easy, just add `pub` and `callconv(.C)` to the function declaration like so: `pub fn atan(num: f64) callconv(.C) f64 { ... }`
3. In `src/main.zig`, export the function. This is also organized by module. For example, for a `Num` function find the `Num` section and add: `comptime { exportNumFn(num.atan, "atan"); }`. The first argument is the function, the second is the name of it in LLVM.
4. In `compiler/builtins/src/bitcode.rs`, add a constant for the new function. This is how we use it in Rust. Once again, this is organized by module, so just find the relevant area and add your new function.
5. You can now your function in Rust using `call_bitcode_fn` in `llvm/src/build.rs`!
5. You can now use your function in Rust using `call_bitcode_fn` in `llvm/src/build.rs`!
## How it works
@ -28,7 +28,7 @@ There will be two directories like `roc_builtins-[some random characters]`, look
> The bitcode is a bunch of bytes that aren't particularly human-readable.
> If you want to take a look at the human-readable LLVM IR, look at
> `target/debug/build/roc_builtins-[some random characters]/out/builtins.ll`
> `compiler/builtins/bitcode/builtins.ll`
## Calling bitcode functions

View file

@ -0,0 +1,9 @@
#!/bin/bash
set -euxo pipefail
# Test failures will always point at the _start function
# Make sure to look at the rest of the stack trace!
warning_about_non_native_binary=$(zig test -target wasm32-wasi-musl -O ReleaseFast src/main.zig 2>&1)
wasm_test_binary=$(echo $warning_about_non_native_binary | cut -d' ' -f 3)
wasmer $wasm_test_binary dummyArgForZigTestBinary

View file

@ -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());
}

View file

@ -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;

View file

@ -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(

View file

@ -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");

View file

@ -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 });
}

View file

@ -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();

View file

@ -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 {