Skip to content

Commit f1a03bf

Browse files
committed
refactor: expand unsafe and better document it
1 parent 7d451b3 commit f1a03bf

File tree

3 files changed

+99
-30
lines changed

3 files changed

+99
-30
lines changed

libdd-profiling/benches/add_samples.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ pub fn bench_add_sample_vs_add2(c: &mut Criterion) {
113113
})
114114
.unwrap();
115115
Frame2 {
116-
function: unsafe { core::mem::transmute::<SetId<Function>, FunctionId2>(set_id) },
116+
function: FunctionId2::from(set_id),
117117
line_number: f.line_number,
118118
}
119119
});
@@ -147,7 +147,11 @@ pub fn bench_add_sample_vs_add2(c: &mut Criterion) {
147147
for _ in 0..1000 {
148148
// Provide an empty iterator for labels conversion path
149149
let labels_iter = std::iter::empty::<anyhow::Result<api2::Label>>();
150-
black_box(profile.try_add_sample2(&locations, &values, labels_iter, None)).unwrap();
150+
// SAFETY: all ids come from the profile's dictionary.
151+
black_box(unsafe {
152+
profile.try_add_sample2(&locations, &values, labels_iter, None)
153+
})
154+
.unwrap();
151155
}
152156
black_box(profile.only_for_testing_num_aggregated_samples())
153157
})

libdd-profiling/src/internal/profile/mod.rs

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,15 @@ impl Profile {
173173
}
174174

175175
/// Tries to add a sample using `api2` structures.
176-
pub fn try_add_sample2<'a, L: ExactSizeIterator<Item = anyhow::Result<api2::Label<'a>>>>(
176+
///
177+
/// # Safety
178+
///
179+
/// All MappingId2, FunctionId2, and StringId2 values should be coming
180+
/// from the same profiles dictionary used by this profile internally.
181+
pub unsafe fn try_add_sample2<
182+
'a,
183+
L: ExactSizeIterator<Item = anyhow::Result<api2::Label<'a>>>,
184+
>(
177185
&mut self,
178186
locations: &[api2::Location2],
179187
values: &[i64],
@@ -2784,17 +2792,23 @@ mod api_tests {
27842792

27852793
// Add sample without labels
27862794
let labels_iter = std::iter::empty::<anyhow::Result<api2::Label>>();
2787-
profile
2788-
.try_add_sample2(&locations, &values, labels_iter, None)
2789-
.expect("add to succeed");
2795+
// SAFETY: adding ids from the correct ProfilesDictionary.
2796+
unsafe {
2797+
profile
2798+
.try_add_sample2(&locations, &values, labels_iter, None)
2799+
.expect("add to succeed");
2800+
}
27902801

27912802
assert_eq!(profile.only_for_testing_num_aggregated_samples(), 1);
27922803

27932804
// Add same sample again - should aggregate
27942805
let labels_iter = std::iter::empty::<anyhow::Result<api2::Label>>();
2795-
profile
2796-
.try_add_sample2(&locations, &values, labels_iter, None)
2797-
.expect("add to succeed");
2806+
// SAFETY: adding ids from the correct ProfilesDictionary.
2807+
unsafe {
2808+
profile
2809+
.try_add_sample2(&locations, &values, labels_iter, None)
2810+
.expect("add to succeed");
2811+
}
27982812

27992813
// Still 1 sample because it aggregated
28002814
assert_eq!(profile.only_for_testing_num_aggregated_samples(), 1);
@@ -2804,19 +2818,25 @@ mod api_tests {
28042818
let label_value = "worker-1";
28052819

28062820
let labels_iter = std::iter::once(Ok(api2::Label::str(label_key, label_value)));
2807-
profile
2808-
.try_add_sample2(&locations, &values, labels_iter, None)
2809-
.expect("add with label to succeed");
2821+
// SAFETY: adding ids from the correct ProfilesDictionary.
2822+
unsafe {
2823+
profile
2824+
.try_add_sample2(&locations, &values, labels_iter, None)
2825+
.expect("add with label to succeed");
2826+
}
28102827

28112828
// Should be 2 samples now (different label set)
28122829
assert_eq!(profile.only_for_testing_num_aggregated_samples(), 2);
28132830

28142831
// Test with numeric label
28152832
let thread_id_key = dict.try_insert_str2("thread_id_num").unwrap();
28162833
let labels_iter = std::iter::once(Ok(api2::Label::num(thread_id_key, 42, "")));
2817-
profile
2818-
.try_add_sample2(&locations, &values, labels_iter, None)
2819-
.expect("add with numeric label to succeed");
2834+
// SAFETY: adding ids from the correct ProfilesDictionary.
2835+
unsafe {
2836+
profile
2837+
.try_add_sample2(&locations, &values, labels_iter, None)
2838+
.expect("add with numeric label to succeed");
2839+
}
28202840

28212841
// Should be 3 samples now
28222842
assert_eq!(profile.only_for_testing_num_aggregated_samples(), 3);

libdd-profiling/src/internal/profile/profiles_dictionary_translator.rs

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,30 @@ use anyhow::Context;
1010
use indexmap::map::Entry;
1111
use std::ptr::NonNull;
1212

13+
/// Translates IDs from a [`ProfilesDictionary`] into the IDs used by the
14+
/// current Profile's internal collections.
15+
///
16+
/// # Safety
17+
///
18+
/// All IDs passed to the translate methods (translate_function,
19+
/// translate_mapping, translate_string) MUST have been created by the same
20+
/// ProfilesDictionary that this translator wraps.
1321
pub struct ProfilesDictionaryTranslator {
1422
pub profiles_dictionary: crate::profiles::collections::Arc<ProfilesDictionary>,
1523
pub mappings: FxIndexMap<SetId<dt::Mapping>, MappingId>,
1624
pub functions: FxIndexMap<Option<SetId<dt::Function>>, FunctionId>,
1725
pub strings: FxIndexMap<StringRef, StringId>,
1826
}
1927

20-
// SAFETY: the profiles_dictionary keeps the storage for Ids alive.
28+
// SAFETY: ProfilesDictionaryTranslator is Send because:
29+
// 1. The profiles_dictionary Arc ensures the underlying storage remains alive and valid for the
30+
// lifetime of this translator, and Arc<T> is Send when T is Send + Sync. ProfilesDictionary is
31+
// Send + Sync.
32+
// 2. SetId<T> and StringRef are non-owning handles (thin pointers) to immutable data in the
33+
// ProfilesDictionary's concurrent collections, which use arena allocation with stable addresses.
34+
// The Arc protects this data, making the pointers safe to send across threads.
35+
// 3. FxIndexMap<K, V> is Send when K and V are Send. The keys (SetId, StringRef) and values
36+
// (MappingId, FunctionId, StringId) are all Copy types that are Send.
2137
unsafe impl Send for ProfilesDictionaryTranslator {}
2238

2339
impl ProfilesDictionaryTranslator {
@@ -32,7 +48,14 @@ impl ProfilesDictionaryTranslator {
3248
}
3349
}
3450

35-
pub fn translate_function(
51+
/// Translates a FunctionId2 from the ProfilesDictionary into a FunctionId
52+
/// for this profile's StringTable.
53+
///
54+
/// # Safety
55+
///
56+
/// The `id2` must have been created by `self.profiles_dictionary`, and
57+
/// the strings must also live in the same dictionary.
58+
pub unsafe fn translate_function(
3659
&mut self,
3760
functions: &mut impl Dedup<Function>,
3861
string_table: &mut StringTable,
@@ -55,12 +78,19 @@ impl ProfilesDictionaryTranslator {
5578
return Ok(*internal);
5679
}
5780

58-
// SAFETY: todo
81+
// SAFETY: This is safe if `id2` (the FunctionId2) was created by
82+
// `self.profiles_dictionary`, which is a precondition of calling
83+
// this method.
5984
let function = unsafe { *self.profiles_dictionary.functions().get(set_id) };
60-
let function = Function {
61-
name: self.translate_string(string_table, function.name)?,
62-
system_name: self.translate_string(string_table, function.system_name)?,
63-
filename: self.translate_string(string_table, function.file_name)?,
85+
// SAFETY: safe if the strings were made by
86+
// `self.profiles_dictionary`, which is a precondition of
87+
// calling this method.
88+
let function = unsafe {
89+
Function {
90+
name: self.translate_string(string_table, function.name)?,
91+
system_name: self.translate_string(string_table, function.system_name)?,
92+
filename: self.translate_string(string_table, function.file_name)?,
93+
}
6494
};
6595
(Some(set_id), function)
6696
}
@@ -76,7 +106,14 @@ impl ProfilesDictionaryTranslator {
76106
Ok(internal_id)
77107
}
78108

79-
pub fn translate_mapping(
109+
/// Translates a MappingId2 from the ProfilesDictionary into a MappingId
110+
/// for this profile's internal collections.
111+
///
112+
/// # Safety
113+
///
114+
/// The `id2` must have been created by `self.profiles_dictionary`, and
115+
/// the strings must also live in the same dictionary.
116+
pub unsafe fn translate_mapping(
80117
&mut self,
81118
mappings: &mut impl Dedup<Mapping>,
82119
string_table: &mut StringTable,
@@ -93,7 +130,9 @@ impl ProfilesDictionaryTranslator {
93130
return Ok(Some(*internal));
94131
}
95132

96-
// SAFETY: todo
133+
// SAFETY: This is safe if `id2` (the MappingId2) was created by
134+
// `self.profiles_dictionary`, which is a precondition of calling
135+
// this method.
97136
let mapping = unsafe { *self.profiles_dictionary.mappings().get(set_id) };
98137
let internal = Mapping {
99138
memory_start: mapping.memory_start,
@@ -112,7 +151,14 @@ impl ProfilesDictionaryTranslator {
112151
Ok(Some(internal_id))
113152
}
114153

115-
pub fn translate_string(
154+
/// Translates a StringRef from the ProfilesDictionary into a StringId
155+
/// for this profile's internal string table.
156+
///
157+
/// # Safety
158+
///
159+
/// The `str_ref` must have been created by `self.profiles_dictionary`.
160+
/// Violating this precondition results in undefined behavior.
161+
pub unsafe fn translate_string(
116162
&mut self,
117163
string_table: &mut StringTable,
118164
str_ref: StringRef,
@@ -121,12 +167,11 @@ impl ProfilesDictionaryTranslator {
121167
match self.strings.entry(str_ref) {
122168
Entry::Occupied(o) => Ok(*o.get()),
123169
Entry::Vacant(v) => {
170+
// SAFETY: This is safe if `str_ref` was created by
171+
// `self.profiles_dictionary`, which is a precondition of calling
172+
// this method.
124173
let str = unsafe { self.profiles_dictionary.strings().get(str_ref) };
125-
// SAFETY: we're keeping these lifetimes in sync. I think.
126-
// TODO: BUT longer-term we want to avoid copying them
127-
// entirely, so this should go away.
128-
let decouple_str = unsafe { core::mem::transmute::<&str, &str>(str) };
129-
let internal_id = string_table.try_intern(decouple_str)?;
174+
let internal_id = string_table.try_intern(str)?;
130175
v.insert(internal_id);
131176
Ok(internal_id)
132177
}

0 commit comments

Comments
 (0)