Skip to content

Commit 77f471c

Browse files
committed
use strict provenance
1 parent 3c48534 commit 77f471c

10 files changed

Lines changed: 281 additions & 126 deletions

File tree

.github/workflows/rust.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
rustup component add miri --toolchain nightly
3333
- name: Run Miri
3434
run: |
35-
cargo +nightly miri test --manifest-path cold-string/Cargo.toml --test property
35+
cargo +nightly miri test --manifest-path cold-string/Cargo.toml
3636
- name: Run Miri Unknown
3737
run: |
38-
cargo +nightly miri test --manifest-path cold-string/Cargo.toml --test property --target mips-unknown-linux-gnu
38+
cargo +nightly miri test --manifest-path cold-string/Cargo.toml --target mips-unknown-linux-gnu

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bench/benches/bench.rs

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -98,36 +98,12 @@ fn bench_hash(c: &mut Criterion) {
9898
group.finish();
9999
}
100100

101-
/*
102-
fn bench_eq(c: &mut Criterion) {
103-
let cold1 = ColdString::from(LONG);
104-
let cold2 = ColdString::from(LONG);
105-
106-
let string1 = String::from(LONG);
107-
let string2 = String::from(LONG);
108-
109-
let mut group = c.benchmark_group("eq");
110-
111-
group.bench_function("ColdString eq", |b| {
112-
b.iter(|| black_box(&cold1 == &cold2))
113-
});
114-
115-
group.bench_function("String eq", |b| {
116-
b.iter(|| black_box(&string1 == &string2))
117-
});
118-
119-
group.finish();
120-
}
121-
*/
122-
123101
fn bench_clone(c: &mut Criterion) {
124102
let cold = ColdString::from(LONG);
125103
let string = String::from(LONG);
126104

127105
let mut group = c.benchmark_group("clone");
128-
129106
group.bench_function("ColdString clone", |b| b.iter(|| black_box(cold.clone())));
130-
131107
group.bench_function("String clone", |b| b.iter(|| black_box(string.clone())));
132108

133109
group.finish();
@@ -139,7 +115,6 @@ criterion_group!(
139115
bench_len,
140116
bench_as_str,
141117
bench_hash,
142-
// bench_eq,
143118
bench_clone
144119
);
145120
criterion_main!(benches);

bench/benches/eq.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ where
7171

7272
fn bench_eq(c: &mut Criterion) {
7373
bench_eq_type::<ColdString>(c, "ColdString_eq");
74-
//bench_eq_type::<String>(c, "String_eq");
74+
bench_eq_type::<String>(c, "String_eq");
7575
}
7676

7777
criterion_group!(benches, bench_eq);

cold-string/Cargo.lock

Lines changed: 58 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cold-string/Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "cold-string"
33
version = "0.1.0"
4-
edition = "2021"
4+
edition = "2018"
55
rust-version = "1.60.0"
66
authors = ["tomtomwombat"]
77
description = "A 1-word SSO string that saves up to 24 bytes over String."
@@ -16,14 +16,15 @@ readme = "README.md"
1616
maintenance = { status = "actively-developed" }
1717

1818
[features]
19-
default = ["std"]
20-
std = []
21-
serde = ["dep:serde"]
19+
default = []
20+
serde = ["dep:serde", "serde/alloc"]
2221

2322
[dependencies]
24-
serde = { version = "1.0.228", optional = true }
23+
serde = { version = "1.0.228", optional = true, default-features = false }
24+
sptr = { version = "0.3.2", default-features = false }
2525
rustversion = "1.0.22"
2626

2727
[dev-dependencies]
28+
hashbrown = "0.12.3"
2829
serde_test = "1.0.177"
2930
proptest = {version = "=1.8.0", default-features = false, features = ["std", "bit-set"] }

cold-string/README.md

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
![MSRV](https://img.shields.io/crates/msrv/cold-string?style=for-the-badge)
66
![Downloads](https://img.shields.io/crates/d/cold-string?style=for-the-badge)
77

8-
Compact representation of immutable UTF-8 strings. Optimized for memory usage and struct packing.
8+
A 1-word sized representation of immutable UTF-8 strings. In-lines up to 1 word bytes. Optimized for memory usage and struct packing.
99

1010
# Usage
1111

@@ -22,29 +22,37 @@ Packs well with other types:
2222
use std::mem;
2323
use cold_string::ColdString;
2424

25-
assert_eq!(mem::size_of::<ColdString>(), 8);
25+
assert_eq!(mem::size_of::<ColdString>(), mem::size_of::<usize>());
2626
assert_eq!(mem::align_of::<ColdString>(), 1);
2727

28-
assert_eq!(mem::size_of::<(ColdString, u8)>(), 9);
28+
assert_eq!(mem::size_of::<(ColdString, u8)>(), mem::size_of::<usize>() + 1);
2929
assert_eq!(mem::align_of::<(ColdString, u8)>(), 1);
3030
```
3131

3232
# How It Works
3333

34-
ColdString is an 8 byte array (4 bytes on 32-bit machines):
35-
```rust,ignore
36-
pub struct ColdString([u8; 8]);
34+
ColdString is 8-byte tagged pointer (4 bytes on 32-bit machines):
35+
```rust
36+
#[repr(packed)]
37+
pub struct ColdString {
38+
/// The first byte of `encoded` is the "tag" and it determines the type:
39+
/// - 10xxxxxx: an encoded address for the heap. To decode, 10 is set to 00 and swapped
40+
/// with the LSB bits of the tag byte. The address is always a multiple of 4 (`HEAP_ALIGN`).
41+
/// - 11111xxx: xxx is the length in range 0..=7, followed by length UTF-8 bytes.
42+
/// - xxxxxxxx (valid UTF-8): 8 UTF-8 bytes.
43+
encoded: *mut u8,
44+
}
3745
```
38-
The array acts as either a pointer to heap data for strings longer than 8 bytes or is the inlined data itself. The first byte indicates one of 3 encodings:
46+
`encoded` acts as either a pointer to the heap for strings longer than 8 bytes or is the inlined data itself. The first/"tag" byte indicates one of 3 encodings:
3947

4048
## Inline Mode (0 to 7 Bytes)
41-
The first byte has bits 11111xxx, where xxx is the length. `self.0[1]` to `self.0[7]` store the bytes of string.
49+
The tag byte has bits 11111xxx, where xxx is the length. `self.0[1]` to `self.0[7]` store the bytes of string.
4250

4351
## Inline Mode (8 Bytes)
44-
`self.0` stores the bytes of string. Since the string is UTF-8, the first byte is guaranteed to not be 10xxxxx or 11111xxx.
52+
The tag byte is any valid UTF-8 byte. `self.0` stores the bytes of string. Since the string is UTF-8, the tag byte is guaranteed to not be 10xxxxx or 11111xxx.
4553

4654
## Heap Mode
47-
`self.0` encodes the pointer to heap, where first byte is 10xxxxxx. 10xxxxxx is chosen because it's a UTF-8 continuation byte and therefore an impossible first byte for inline mode. Since a heap-alignment of 4 is chosen, the pointer's least significant 2 bits are guaranteed to be 0 ([See more](https://doc.rust-lang.org/beta/std/alloc/struct.Layout.html#method.from_size_align)). These bits are swapped with the 10 "tag" bits when de/coding between `self.0` and the address value.
55+
`self.0` encodes the pointer to heap, where tag byte is 10xxxxxx. 10xxxxxx is chosen because it's a UTF-8 continuation byte and therefore an impossible tag byte for inline mode. Since a heap-alignment of 4 is chosen, the pointer's least significant 2 bits are guaranteed to be 0 ([See more](https://doc.rust-lang.org/beta/std/alloc/struct.Layout.html#method.from_size_align)). These bits are swapped with the 10 "tag" bits when de/coding between `self.0` and the address value.
4856

4957
On the heap, the data starts with a variable length integer encoding of the length, followed by the bytes.
5058
```text,ignore

0 commit comments

Comments
 (0)