Skip to content

Commit e79218f

Browse files
committed
optimizations to libcc2rs related with strings
Reduces heap allocations in the common case
1 parent d3a1428 commit e79218f

23 files changed

Lines changed: 401 additions & 166 deletions

‎docs/src/runtime/cstr.md‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ instead of undefined behavior), `memset`, `memcmp`, and `to_rust_string` for
1010
crossing into Rust APIs.
1111

1212
`CStringIterator`, returned by `to_c_string_iterator`, walks the bytes of a
13-
`Ptr<u8>` up to the null terminator; the `string.h` rules are built on it,
14-
`to_rust_string` collects it into a `String`, and `Display` for `Ptr<u8>` prints
15-
it, so a C string can be formatted directly.
13+
`Ptr<u8>` up to the null terminator; the `string.h` rules are built on it, and
14+
`Display` for `Ptr<u8>` prints it, so a C string can be formatted directly.
15+
16+
Copying a string does not go through the iterator, which would grow its
17+
destination as it goes. `with_c_str` scans for the null terminator in the
18+
backing storage and lends the bytes to a closure, so measuring or borrowing a
19+
string allocates nothing (`c_str_len`, and `count` on a `CStringIterator`, are
20+
built on it). `to_c_bytes` and `to_rust_string`, and through them `strdup` and
21+
the `std::string` constructors, copy the string with a single allocation.
22+
`to_c_bytes` leaves room for one more byte, which callers usually spend on the
23+
terminator or a newline.
24+
25+
Iterating a `Ptr<T>` reports how many elements are left, so collecting a `Ptr`
26+
into a `Vec` also allocates once.

‎libcc2rs/src/alloc.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub fn calloc_refcount(a0: usize, a1: usize) -> AnyPtr {
3131
}
3232

3333
pub fn strdup_refcount(a0: Ptr<u8>) -> Ptr<u8> {
34-
let mut bytes: Vec<u8> = a0.to_c_string_iterator().collect();
34+
let mut bytes = a0.to_c_bytes();
3535
bytes.push(0);
3636
Ptr::alloc_array(bytes.into_boxed_slice())
3737
}

‎libcc2rs/src/cstr.rs‎

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,116 @@ impl Ptr<u8> {
117117
CStringIterator { ptr: self.clone() }
118118
}
119119

120+
// Calls `f` with the bytes of the C string, excluding the terminating NUL.
121+
// The bytes are borrowed from the allocation, so `f` must not write to it.
122+
pub fn with_c_str<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
123+
fn until_nul(tail: &[u8]) -> &[u8] {
124+
match tail.iter().position(|&b| b == 0) {
125+
Some(len) => &tail[..len],
126+
None => panic!("ub: unterminated string"),
127+
}
128+
}
129+
match &self.kind {
130+
PtrKind::Null => panic!("ub: null pointer"),
131+
PtrKind::StackSingle(weak) | PtrKind::HeapSingle(weak) => {
132+
assert_eq!(self.offset, 0, "ub: invalid offset");
133+
let rc = weak.upgrade().expect("ub: dangling pointer");
134+
let b = rc.borrow();
135+
f(until_nul(std::slice::from_ref(&*b)))
136+
}
137+
PtrKind::StackArray(weak) | PtrKind::HeapArray(weak) => {
138+
let rc = weak.upgrade().expect("ub: dangling pointer");
139+
let b = rc.borrow();
140+
f(until_nul(&b[self.offset..]))
141+
}
142+
PtrKind::StackVec(weak) | PtrKind::HeapVec(weak) => {
143+
let rc = weak.upgrade().expect("ub: dangling pointer");
144+
let b = rc.borrow();
145+
f(until_nul(&b[self.offset..]))
146+
}
147+
PtrKind::Reinterpreted(_) => f(&self.to_c_string_iterator().collect::<Vec<u8>>()),
148+
}
149+
}
150+
151+
// The length of the C string, like `strlen`.
152+
pub fn c_str_len(&self) -> usize {
153+
self.with_c_str(|s| s.len())
154+
}
155+
156+
// Copies the C string, excluding the terminating NUL, using a single
157+
// allocation. The vector has room for one more byte, which is what callers
158+
// usually append (a NUL or a newline).
159+
pub fn to_c_bytes(&self) -> Vec<u8> {
160+
self.with_c_str(|s| {
161+
let mut bytes = Vec::with_capacity(s.len() + 1);
162+
bytes.extend_from_slice(s);
163+
bytes
164+
})
165+
}
166+
120167
pub fn to_rust_string(&self) -> String {
121-
let bytes: Vec<u8> = self.to_c_string_iterator().collect();
122-
String::from_utf8_lossy(&bytes).into_owned()
168+
self.with_c_str(|s| String::from_utf8_lossy(s).into_owned())
169+
}
170+
}
171+
172+
#[cfg(test)]
173+
mod tests {
174+
use super::*;
175+
use crate::AsPointer;
176+
177+
// "ab\0cd\0": C string functions must stop at the first NUL.
178+
const EMBEDDED_NUL: [u8; 6] = [b'a', b'b', 0, b'c', b'd', 0];
179+
180+
fn check(p: &Ptr<u8>) {
181+
assert_eq!(p.c_str_len(), 2);
182+
assert_eq!(p.to_c_bytes(), b"ab");
183+
assert_eq!(p.to_rust_string(), "ab");
184+
assert_eq!(p.to_c_string_iterator().count(), 2);
185+
assert_eq!(p.to_c_string_iterator().collect::<Vec<u8>>(), b"ab");
186+
assert_eq!(p.with_c_str(|s| s.to_vec()), b"ab");
187+
assert_eq!(p.offset(3).to_c_bytes(), b"cd");
188+
assert_eq!(p.offset(2).c_str_len(), 0);
189+
}
190+
191+
#[test]
192+
fn stops_at_first_nul_in_array() {
193+
let p = Ptr::alloc_array(EMBEDDED_NUL.to_vec().into_boxed_slice());
194+
check(&p);
195+
p.delete();
196+
}
197+
198+
#[test]
199+
fn stops_at_first_nul_in_vec() {
200+
let v = std::rc::Rc::new(RefCell::new(EMBEDDED_NUL.to_vec()));
201+
check(&v.as_pointer());
202+
}
203+
204+
#[test]
205+
fn stops_at_first_nul_in_reinterpreted_view() {
206+
let p: Ptr<u16> = Ptr::alloc_array(
207+
EMBEDDED_NUL
208+
.chunks(2)
209+
.map(|c| u16::from_ne_bytes([c[0], c[1]]))
210+
.collect::<Vec<u16>>()
211+
.into_boxed_slice(),
212+
);
213+
let bytes = p.reinterpret_cast::<u8>();
214+
check(&bytes);
215+
bytes.delete();
216+
}
217+
218+
#[test]
219+
fn empty_string() {
220+
let p = Ptr::<u8>::from_string_literal(b"");
221+
assert_eq!(p.c_str_len(), 0);
222+
assert!(p.to_c_bytes().is_empty());
223+
assert_eq!(p.to_rust_string(), "");
224+
}
225+
226+
#[test]
227+
#[should_panic(expected = "ub: unterminated string")]
228+
fn unterminated_string_panics() {
229+
let p = Ptr::alloc_array(vec![b'a', b'b'].into_boxed_slice());
230+
p.c_str_len();
123231
}
124232
}

‎libcc2rs/src/iterators.rs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,11 @@ pub struct CStringIterator {
232232

233233
impl Iterator for CStringIterator {
234234
type Item = u8;
235+
236+
fn count(self) -> usize {
237+
self.ptr.c_str_len()
238+
}
239+
235240
fn next(&mut self) -> Option<Self::Item> {
236241
// read until the null terminator
237242
match self.ptr.read() {

‎libcc2rs/src/rc.rs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,13 @@ impl<T> Iterator for Ptr<T> {
655655
None
656656
}
657657
}
658+
659+
// Lets collections built from the iterator (e.g. `std::string`'s
660+
// constructors) allocate their storage once instead of growing it.
661+
fn size_hint(&self) -> (usize, Option<usize>) {
662+
let remaining = self.len().saturating_sub(self.get_offset());
663+
(remaining, Some(remaining))
664+
}
658665
}
659666

660667
// Ptr iterator that yields values instead of pointers.

‎rules/stdio/tgt_refcount.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,15 @@ fn f11(a0: i32, a1: Ptr<CFile>) -> i32 {
7272
}
7373

7474
fn f12(a0: Ptr<u8>, a1: Ptr<CFile>) -> i32 {
75-
let __bytes: Vec<u8> = a0.to_c_string_iterator().collect();
75+
let __bytes = a0.to_c_bytes();
7676
match a1.with_mut(|__f| __f.write(&__bytes)) == __bytes.len() {
7777
true => 0,
7878
false => -1,
7979
}
8080
}
8181

8282
fn f13(a0: Ptr<u8>) -> i32 {
83-
let mut __bytes: Vec<u8> = a0.to_c_string_iterator().collect();
83+
let mut __bytes = a0.to_c_bytes();
8484
__bytes.push(b'\n');
8585
match libcc2rs::c_stdout().with_mut(|__f| __f.write(&__bytes)) == __bytes.len() {
8686
true => 0,

‎rules/string/tgt_refcount.rs‎

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn f1(a0: Vec<u8>, a1: usize, a2: usize) -> Vec<u8> {
2323
fn f3(a0: Vec<u8>, a1: Ptr<u8>) -> Vec<u8> {
2424
let mut r = a0;
2525
r.pop();
26-
r.extend(a1.to_c_string_iterator());
26+
a1.with_c_str(|__s| r.extend_from_slice(__s));
2727
r.push(0);
2828
r
2929
}
@@ -53,9 +53,9 @@ fn f7(a0: Ptr<u8>, a1: usize) -> Vec<u8> {
5353
}
5454

5555
fn f10(a0: Ptr<u8>) -> Vec<u8> {
56-
a0.to_c_string_iterator()
57-
.chain(std::iter::once(0))
58-
.collect::<Vec<u8>>()
56+
let mut __bytes = a0.to_c_bytes();
57+
__bytes.push(0);
58+
__bytes
5959
}
6060

6161
fn f11(a0: Ptr<u8>) -> Ptr<u8> {
@@ -83,18 +83,19 @@ fn f15(a0: Ptr<u8>) -> Ptr<u8> {
8383
}
8484

8585
fn f16(a0: Vec<u8>, a1: Ptr<u8>) -> usize {
86-
let __lookup: Vec<u8> = a1.to_c_string_iterator().collect();
87-
a0.iter()
88-
.take(a0.len().saturating_sub(1))
89-
.rposition(|&x| __lookup.contains(&x))
90-
.unwrap_or(usize::MAX)
86+
a1.with_c_str(|__lookup| {
87+
a0.iter()
88+
.take(a0.len().saturating_sub(1))
89+
.rposition(|&x| __lookup.contains(&x))
90+
.unwrap_or(usize::MAX)
91+
})
9192
}
9293

9394
// TODO: This should modify a0 in place
9495
fn f17(a0: Vec<u8>, a1: Ptr<u8>) -> Vec<u8> {
9596
let mut __tmp2 = a0;
9697
__tmp2.pop();
97-
__tmp2.extend(a1.to_c_string_iterator());
98+
a1.with_c_str(|__s| __tmp2.extend_from_slice(__s));
9899
__tmp2.push(0);
99100
__tmp2
100101
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// C string functions stop at the first NUL.
2+
#include <cstdio>
3+
#include <cstring>
4+
#include <string>
5+
6+
int main() {
7+
char buf[] = {'a', 'b', '\0', 'c', 'd', '\0'};
8+
fputs(buf, stdout);
9+
fputc('|', stdout);
10+
puts(buf);
11+
std::string s(buf);
12+
printf("%zu\n", s.size());
13+
printf("%zu %zu\n", strlen(buf), s.find_last_of(buf));
14+
const char *lit = "xy\0zw";
15+
std::string t(lit);
16+
printf("%zu\n", t.size());
17+
return 0;
18+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
extern crate libcc2rs;
2+
use libcc2rs::*;
3+
use std::cell::RefCell;
4+
use std::collections::BTreeMap;
5+
use std::io::prelude::*;
6+
use std::io::{Read, Seek, Write};
7+
use std::os::fd::AsFd;
8+
use std::rc::{Rc, Weak};
9+
pub fn main() {
10+
__cpp2rust_init_globals();
11+
std::process::exit(main_0());
12+
}
13+
fn main_0() -> i32 {
14+
let buf: Value<Box<[u8]>> = Rc::new(RefCell::new(Box::new([
15+
('a' as u8),
16+
('b' as u8),
17+
('\0' as u8),
18+
('c' as u8),
19+
('d' as u8),
20+
('\0' as u8),
21+
])));
22+
{
23+
let __bytes = (buf.as_pointer() as Ptr<u8>).to_c_bytes();
24+
match libcc2rs::c_stdout().with_mut(|__f| __f.write(&__bytes)) == __bytes.len() {
25+
true => 0,
26+
false => -1,
27+
}
28+
};
29+
{
30+
let __c = (('|' as u8) as i32) as u8;
31+
match libcc2rs::c_stdout().with_mut(|__f| __f.write(&[__c])) {
32+
1 => __c as i32,
33+
_ => -1,
34+
}
35+
};
36+
{
37+
let mut __bytes = (buf.as_pointer() as Ptr<u8>).to_c_bytes();
38+
__bytes.push(b'\n');
39+
match libcc2rs::c_stdout().with_mut(|__f| __f.write(&__bytes)) == __bytes.len() {
40+
true => 0,
41+
false => -1,
42+
}
43+
};
44+
let s: Value<Vec<u8>> = Rc::new(RefCell::new({
45+
let mut __bytes = (buf.as_pointer() as Ptr<u8>).to_c_bytes();
46+
__bytes.push(0);
47+
__bytes
48+
}));
49+
println!("{}", ((*s.borrow()).len() - 1));
50+
println!(
51+
"{} {}",
52+
(buf.as_pointer() as Ptr::<u8>)
53+
.to_c_string_iterator()
54+
.count(),
55+
(buf.as_pointer() as Ptr::<u8>).with_c_str(|__lookup| {
56+
(*s.borrow())
57+
.iter()
58+
.take((*s.borrow()).len().saturating_sub(1))
59+
.rposition(|&x| __lookup.contains(&x))
60+
.unwrap_or(usize::MAX)
61+
})
62+
);
63+
let lit: Value<Ptr<u8>> = Rc::new(RefCell::new(Ptr::<u8>::from_string_literal(b"xy\0zw")));
64+
let t: Value<Vec<u8>> = Rc::new(RefCell::new({
65+
let mut __bytes = (*lit.borrow()).to_c_bytes();
66+
__bytes.push(0);
67+
__bytes
68+
}));
69+
println!("{}", ((*t.borrow()).len() - 1));
70+
return 0;
71+
}
72+
pub fn __cpp2rust_init_globals() {}

‎tests/unit/out/refcount/char_printing.rs‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@ pub fn main() {
1313
fn main_0() -> i32 {
1414
let vec_: Value<Vec<u8>> = Rc::new(RefCell::new(vec![195_u8, 167_u8]));
1515
let i: Value<i32> = Rc::new(RefCell::new(27));
16-
let str: Value<Vec<u8>> = Rc::new(RefCell::new(
17-
Ptr::<u8>::from_string_literal(b"rdas.")
18-
.to_c_string_iterator()
19-
.chain(std::iter::once(0))
20-
.collect::<Vec<u8>>(),
21-
));
16+
let str: Value<Vec<u8>> = Rc::new(RefCell::new({
17+
let mut __bytes = Ptr::<u8>::from_string_literal(b"rdas.").to_c_bytes();
18+
__bytes.push(0);
19+
__bytes
20+
}));
2221
write!(libcc2rs::cout(), "{:} a", (*i.borrow()),);
2322
libcc2rs::cout().write_all(
2423
&([

0 commit comments

Comments
 (0)