add Str.releaseExcessCapacity

This commit is contained in:
Brendan Hansknecht 2023-03-14 21:57:19 -07:00
parent a80b25d044
commit 48f17a8e2c
No known key found for this signature in database
GPG key ID: 0EA784685083E75B
11 changed files with 115 additions and 0 deletions

View file

@ -156,6 +156,7 @@ comptime {
exportStrFn(str.withCapacity, "with_capacity"); exportStrFn(str.withCapacity, "with_capacity");
exportStrFn(str.strGraphemes, "graphemes"); exportStrFn(str.strGraphemes, "graphemes");
exportStrFn(str.strRefcountPtr, "refcount_ptr"); exportStrFn(str.strRefcountPtr, "refcount_ptr");
exportStrFn(str.strReleaseExcessCapacity, "release_excess_capacity");
inline for (INTEGERS) |T| { inline for (INTEGERS) |T| {
str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int."); str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int.");

View file

@ -121,6 +121,22 @@ pub const RocStr = extern struct {
} }
} }
// allocate space for a (big or small) RocStr, but put nothing in it yet.
// Will have the exact same capacity as length if it is not a small string.
pub fn allocateExact(length: usize) RocStr {
const result_is_big = length >= SMALL_STRING_SIZE;
if (result_is_big) {
return RocStr.allocateBig(length, length);
} else {
var string = RocStr.empty();
string.asU8ptrMut()[@sizeOf(RocStr) - 1] = @intCast(u8, length) | 0b1000_0000;
return string;
}
}
// This returns all ones if the list is a seamless slice. // This returns all ones if the list is a seamless slice.
// Otherwise, it returns all zeros. // Otherwise, it returns all zeros.
// This is done without branching for optimization purposes. // This is done without branching for optimization purposes.
@ -2891,3 +2907,27 @@ pub fn strRefcountPtr(
) callconv(.C) ?[*]u8 { ) callconv(.C) ?[*]u8 {
return string.getRefcountPtr(); return string.getRefcountPtr();
} }
pub fn strReleaseExcessCapacity(
string: RocStr,
) callconv(.C) RocStr {
const old_length = string.len();
// We use the direct list.capacity_or_ref_ptr to make sure both that there is no extra capacity and that it isn't a seamless slice.
if (string.isSmallStr()) {
// SmallStr has no excess capacity.
return string;
} else if (string.isUnique() and !string.isSeamlessSlice() and string.getCapacity() == old_length) {
return string;
} else if (old_length == 0) {
string.decref();
return RocStr.empty();
} else {
var output = RocStr.allocateExact(old_length);
const source_ptr = string.asU8ptr();
const dest_ptr = output.asU8ptrMut();
@memcpy(dest_ptr, source_ptr, old_length);
return output;
}
}

View file

@ -109,6 +109,7 @@ interface Str
splitLast, splitLast,
walkUtf8WithIndex, walkUtf8WithIndex,
reserve, reserve,
releaseExcessCapacity,
appendScalar, appendScalar,
walkScalars, walkScalars,
walkScalarsUntil, walkScalarsUntil,
@ -746,6 +747,10 @@ walkUtf8WithIndexHelp = \string, state, step, index, length ->
## Enlarge a string for at least the given number additional bytes. ## Enlarge a string for at least the given number additional bytes.
reserve : Str, Nat -> Str reserve : Str, Nat -> Str
## Shrink the memory footprint of a str such that it's capacity and length are equal.
## Note: This will also convert seamless slices to regular lists.
releaseExcessCapacity : Str -> Str
## is UB when the scalar is invalid ## is UB when the scalar is invalid
appendScalarUnsafe : Str, U32 -> Str appendScalarUnsafe : Str, U32 -> Str

View file

@ -334,6 +334,7 @@ pub const STR_CLONE_TO: &str = "roc_builtins.str.clone_to";
pub const STR_WITH_CAPACITY: &str = "roc_builtins.str.with_capacity"; pub const STR_WITH_CAPACITY: &str = "roc_builtins.str.with_capacity";
pub const STR_GRAPHEMES: &str = "roc_builtins.str.graphemes"; pub const STR_GRAPHEMES: &str = "roc_builtins.str.graphemes";
pub const STR_REFCOUNT_PTR: &str = "roc_builtins.str.refcount_ptr"; pub const STR_REFCOUNT_PTR: &str = "roc_builtins.str.refcount_ptr";
pub const STR_RELEASE_EXCESS_CAPACITY: &str = "roc_builtins.str.release_excess_capacity";
pub const LIST_MAP: &str = "roc_builtins.list.map"; pub const LIST_MAP: &str = "roc_builtins.list.map";
pub const LIST_MAP2: &str = "roc_builtins.list.map2"; pub const LIST_MAP2: &str = "roc_builtins.list.map2";

View file

@ -126,6 +126,7 @@ map_symbol_to_lowlevel_and_arity! {
StrGetCapacity; STR_CAPACITY; 1, StrGetCapacity; STR_CAPACITY; 1,
StrWithCapacity; STR_WITH_CAPACITY; 1, StrWithCapacity; STR_WITH_CAPACITY; 1,
StrGraphemes; STR_GRAPHEMES; 1, StrGraphemes; STR_GRAPHEMES; 1,
StrReleaseExcessCapacity; STR_RELEASE_EXCESS_CAPACITY; 1,
ListLen; LIST_LEN; 1, ListLen; LIST_LEN; 1,
ListWithCapacity; LIST_WITH_CAPACITY; 1, ListWithCapacity; LIST_WITH_CAPACITY; 1,

View file

@ -559,6 +559,18 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
bitcode::STR_RESERVE, bitcode::STR_RESERVE,
) )
} }
StrReleaseExcessCapacity => {
// Str.releaseExcessCapacity: Str -> Str
arguments!(string);
call_str_bitcode_fn(
env,
&[string],
&[],
BitcodeReturns::Str,
bitcode::STR_RELEASE_EXCESS_CAPACITY,
)
}
StrAppendScalar => { StrAppendScalar => {
// Str.appendScalar : Str, U32 -> Str // Str.appendScalar : Str, U32 -> Str
arguments!(string, capacity); arguments!(string, capacity);

View file

@ -285,6 +285,9 @@ impl<'a> LowLevelCall<'a> {
StrTrimRight => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_RIGHT), StrTrimRight => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_RIGHT),
StrToUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_TO_UTF8), StrToUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_TO_UTF8),
StrReserve => self.load_args_and_call_zig(backend, bitcode::STR_RESERVE), StrReserve => self.load_args_and_call_zig(backend, bitcode::STR_RESERVE),
StrReleaseExcessCapacity => {
self.load_args_and_call_zig(backend, bitcode::STR_RELEASE_EXCESS_CAPACITY)
}
StrRepeat => self.load_args_and_call_zig(backend, bitcode::STR_REPEAT), StrRepeat => self.load_args_and_call_zig(backend, bitcode::STR_REPEAT),
StrAppendScalar => self.load_args_and_call_zig(backend, bitcode::STR_APPEND_SCALAR), StrAppendScalar => self.load_args_and_call_zig(backend, bitcode::STR_APPEND_SCALAR),
StrTrim => self.load_args_and_call_zig(backend, bitcode::STR_TRIM), StrTrim => self.load_args_and_call_zig(backend, bitcode::STR_TRIM),

View file

@ -32,6 +32,7 @@ pub enum LowLevel {
StrGetCapacity, StrGetCapacity,
StrWithCapacity, StrWithCapacity,
StrGraphemes, StrGraphemes,
StrReleaseExcessCapacity,
ListLen, ListLen,
ListWithCapacity, ListWithCapacity,
ListReserve, ListReserve,
@ -259,6 +260,7 @@ map_symbol_to_lowlevel! {
StrGetCapacity <= STR_CAPACITY, StrGetCapacity <= STR_CAPACITY,
StrWithCapacity <= STR_WITH_CAPACITY, StrWithCapacity <= STR_WITH_CAPACITY,
StrGraphemes <= STR_GRAPHEMES, StrGraphemes <= STR_GRAPHEMES,
StrReleaseExcessCapacity <= STR_RELEASE_EXCESS_CAPACITY,
ListLen <= LIST_LEN, ListLen <= LIST_LEN,
ListGetCapacity <= LIST_CAPACITY, ListGetCapacity <= LIST_CAPACITY,
ListWithCapacity <= LIST_WITH_CAPACITY, ListWithCapacity <= LIST_WITH_CAPACITY,

View file

@ -1327,6 +1327,7 @@ define_builtins! {
54 STR_WITH_PREFIX: "withPrefix" 54 STR_WITH_PREFIX: "withPrefix"
55 STR_GRAPHEMES: "graphemes" 55 STR_GRAPHEMES: "graphemes"
56 STR_IS_VALID_SCALAR: "isValidScalar" 56 STR_IS_VALID_SCALAR: "isValidScalar"
57 STR_RELEASE_EXCESS_CAPACITY: "releaseExcessCapacity"
} }
6 LIST: "List" => { 6 LIST: "List" => {
0 LIST_LIST: "List" exposed_apply_type=true // the List.List type alias 0 LIST_LIST: "List" exposed_apply_type=true // the List.List type alias

View file

@ -1017,6 +1017,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListDropAt => arena.alloc_slice_copy(&[owned, irrelevant]), ListDropAt => arena.alloc_slice_copy(&[owned, irrelevant]),
ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListReleaseExcessCapacity => arena.alloc_slice_copy(&[owned]), ListReleaseExcessCapacity => arena.alloc_slice_copy(&[owned]),
StrReleaseExcessCapacity => arena.alloc_slice_copy(&[owned]),
Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]), Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]),

View file

@ -2035,3 +2035,51 @@ fn destructure_pattern_assigned_from_thunk_tag() {
RocStr RocStr
); );
} }
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn release_excess_capacity() {
assert_evals_to!(
indoc!(
r#"
Str.reserve "" 50
|> Str.releaseExcessCapacity
"#
),
(RocStr::empty().capacity(), RocStr::empty()),
RocStr,
|value: RocStr| (value.capacity(), value)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn release_excess_capacity_with_len() {
assert_evals_to!(
indoc!(
r#"
"123456789012345678901234567890"
|> Str.reserve 50
|> Str.releaseExcessCapacity
"#
),
(30, "123456789012345678901234567890".into()),
RocStr,
|value: RocStr| (value.capacity(), value)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn release_excess_capacity_empty() {
assert_evals_to!(
indoc!(
r#"
Str.releaseExcessCapacity ""
"#
),
(RocStr::empty().capacity(), RocStr::empty()),
RocStr,
|value: RocStr| (value.capacity(), value)
);
}