Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

read: cache abbreviations at offset 0 #628

Merged
merged 2 commits into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 95 additions & 1 deletion src/read/abbrev.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Functions for parsing DWARF debugging abbreviations.

use alloc::collections::btree_map;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::convert::TryFrom;
use core::fmt::{self, Debug};
Expand All @@ -10,7 +11,8 @@ use core::ops::Deref;
use crate::common::{DebugAbbrevOffset, Encoding, SectionId};
use crate::constants;
use crate::endianity::Endianity;
use crate::read::{EndianSlice, Error, Reader, Result, Section, UnitHeader};
use crate::read::lazy::LazyArc;
use crate::read::{EndianSlice, Error, Reader, ReaderOffset, Result, Section, UnitHeader};

/// The `DebugAbbrev` struct represents the abbreviations describing
/// `DebuggingInformationEntry`s' attribute names and forms found in the
Expand Down Expand Up @@ -100,6 +102,38 @@ impl<R> From<R> for DebugAbbrev<R> {
}
}

/// A cache of previously parsed `Abbreviations`.
///
/// Currently this only caches the abbreviations for offset 0,
/// since this is a common case in which abbreviations are reused.
/// This strategy may change in future if there is sufficient need.
#[derive(Debug, Default)]
pub struct AbbreviationsCache {
abbreviations: LazyArc<Abbreviations>,
}

impl AbbreviationsCache {
/// Create an empty abbreviations cache.
pub fn new() -> Self {
Self::default()
}

/// Parse the abbreviations at the given offset.
///
/// This uses or updates the cache as required.
pub fn get<R: Reader>(
&self,
debug_abbrev: &DebugAbbrev<R>,
offset: DebugAbbrevOffset<R::Offset>,
) -> Result<Arc<Abbreviations>> {
if offset.0 != R::Offset::from_u8(0) {
return debug_abbrev.abbreviations(offset).map(Arc::new);
}
self.abbreviations
.get(|| debug_abbrev.abbreviations(offset))
}
}

/// A set of type abbreviations.
///
/// Construct an `Abbreviations` instance with the
Expand Down Expand Up @@ -993,4 +1027,64 @@ pub mod tests {
.unwrap();
assert!(abbrevs.get(0).is_none());
}

#[test]
fn abbreviations_cache() {
#[rustfmt::skip]
let buf = Section::new()
.abbrev(1, constants::DW_TAG_subprogram, constants::DW_CHILDREN_no)
.abbrev_attr(constants::DW_AT_name, constants::DW_FORM_string)
.abbrev_attr_null()
.abbrev_null()
.abbrev(1, constants::DW_TAG_compile_unit, constants::DW_CHILDREN_yes)
.abbrev_attr(constants::DW_AT_producer, constants::DW_FORM_strp)
.abbrev_attr(constants::DW_AT_language, constants::DW_FORM_data2)
.abbrev_attr_null()
.abbrev_null()
.get_contents()
.unwrap();

let abbrev1 = Abbreviation::new(
1,
constants::DW_TAG_subprogram,
constants::DW_CHILDREN_no,
vec![AttributeSpecification::new(
constants::DW_AT_name,
constants::DW_FORM_string,
None,
)]
.into(),
);

let abbrev2 = Abbreviation::new(
1,
constants::DW_TAG_compile_unit,
constants::DW_CHILDREN_yes,
vec![
AttributeSpecification::new(
constants::DW_AT_producer,
constants::DW_FORM_strp,
None,
),
AttributeSpecification::new(
constants::DW_AT_language,
constants::DW_FORM_data2,
None,
),
]
.into(),
);

let debug_abbrev = DebugAbbrev::new(&buf, LittleEndian);
let cache = AbbreviationsCache::new();
let abbrevs1 = cache.get(&debug_abbrev, DebugAbbrevOffset(0)).unwrap();
assert_eq!(abbrevs1.get(1), Some(&abbrev1));
let abbrevs2 = cache.get(&debug_abbrev, DebugAbbrevOffset(8)).unwrap();
assert_eq!(abbrevs2.get(1), Some(&abbrev2));
let abbrevs3 = cache.get(&debug_abbrev, DebugAbbrevOffset(0)).unwrap();
assert_eq!(abbrevs3.get(1), Some(&abbrev1));

assert!(!Arc::ptr_eq(&abbrevs1, &abbrevs2));
assert!(Arc::ptr_eq(&abbrevs1, &abbrevs3));
}
}
26 changes: 16 additions & 10 deletions src/read/dwarf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ use crate::common::{
};
use crate::constants;
use crate::read::{
Abbreviations, AttributeValue, DebugAbbrev, DebugAddr, DebugAranges, DebugCuIndex, DebugInfo,
DebugInfoUnitHeadersIter, DebugLine, DebugLineStr, DebugLoc, DebugLocLists, DebugRngLists,
DebugStr, DebugStrOffsets, DebugTuIndex, DebugTypes, DebugTypesUnitHeadersIter,
DebuggingInformationEntry, EntriesCursor, EntriesRaw, EntriesTree, Error,
IncompleteLineProgram, LocListIter, LocationLists, Range, RangeLists, RawLocListIter,
Abbreviations, AbbreviationsCache, AttributeValue, DebugAbbrev, DebugAddr, DebugAranges,
DebugCuIndex, DebugInfo, DebugInfoUnitHeadersIter, DebugLine, DebugLineStr, DebugLoc,
DebugLocLists, DebugRngLists, DebugStr, DebugStrOffsets, DebugTuIndex, DebugTypes,
DebugTypesUnitHeadersIter, DebuggingInformationEntry, EntriesCursor, EntriesRaw, EntriesTree,
Error, IncompleteLineProgram, LocListIter, LocationLists, Range, RangeLists, RawLocListIter,
RawRngListIter, Reader, ReaderOffset, ReaderOffsetId, Result, RngListIter, Section, UnitHeader,
UnitIndex, UnitIndexSectionIterator, UnitOffset, UnitType,
};
Expand Down Expand Up @@ -59,6 +59,9 @@ pub struct Dwarf<R> {

/// The DWARF sections for a supplementary object file.
pub sup: Option<Arc<Dwarf<R>>>,

/// A cache of previously parsed abbreviations for units in this file.
pub abbreviations_cache: AbbreviationsCache,
}

impl<T> Dwarf<T> {
Expand Down Expand Up @@ -96,6 +99,7 @@ impl<T> Dwarf<T> {
ranges: RangeLists::new(debug_ranges, debug_rnglists),
file_type: DwarfFileType::Main,
sup: None,
abbreviations_cache: AbbreviationsCache::new(),
})
}

Expand Down Expand Up @@ -157,6 +161,7 @@ impl<T> Dwarf<T> {
ranges: self.ranges.borrow(&mut borrow),
file_type: self.file_type,
sup: self.sup().map(|sup| Arc::new(sup.borrow(borrow))),
abbreviations_cache: AbbreviationsCache::new(),
}
}

Expand Down Expand Up @@ -192,10 +197,10 @@ impl<R: Reader> Dwarf<R> {
}

/// Parse the abbreviations for a compilation unit.
// TODO: provide caching of abbreviations
#[inline]
pub fn abbreviations(&self, unit: &UnitHeader<R>) -> Result<Abbreviations> {
unit.abbreviations(&self.debug_abbrev)
pub fn abbreviations(&self, unit: &UnitHeader<R>) -> Result<Arc<Abbreviations>> {
self.abbreviations_cache
.get(&self.debug_abbrev, unit.debug_abbrev_offset())
}

/// Return the string offset at the given index.
Expand Down Expand Up @@ -783,6 +788,7 @@ impl<R: Reader> DwarfPackage<R> {
ranges: RangeLists::new(debug_ranges, debug_rnglists),
file_type: DwarfFileType::Dwo,
sup: None,
abbreviations_cache: AbbreviationsCache::new(),
})
}
}
Expand All @@ -799,7 +805,7 @@ where
pub header: UnitHeader<R, Offset>,

/// The parsed abbreviations for the unit.
pub abbreviations: Abbreviations,
pub abbreviations: Arc<Abbreviations>,

/// The `DW_AT_name` attribute of the unit.
pub name: Option<R>,
Expand Down Expand Up @@ -833,7 +839,7 @@ impl<R: Reader> Unit<R> {
/// Construct a new `Unit` from the given unit header.
#[inline]
pub fn new(dwarf: &Dwarf<R>, header: UnitHeader<R>) -> Result<Self> {
let abbreviations = header.abbreviations(&dwarf.debug_abbrev)?;
let abbreviations = dwarf.abbreviations(&header)?;
let mut unit = Unit {
abbreviations,
name: None,
Expand Down
115 changes: 115 additions & 0 deletions src/read/lazy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
pub(crate) use imp::*;

#[cfg(not(feature = "std"))]
mod imp {
use alloc::sync::Arc;
use core::sync::atomic::{AtomicPtr, Ordering};
use core::{mem, ptr};

#[derive(Debug, Default)]
pub(crate) struct LazyArc<T> {
// Only written once with a value obtained from `Arc<T>::into_raw`.
// This holds a ref count for the `Arc`, so it is always safe to
// clone the `Arc` given a reference to the `LazyArc`.
value: AtomicPtr<T>,
}

impl<T> Drop for LazyArc<T> {
fn drop(&mut self) {
let value_ptr = self.value.load(Ordering::Acquire);
if !value_ptr.is_null() {
// SAFETY: all writes to `self.value` are pointers obtained from `Arc::into_raw`.
drop(unsafe { Arc::from_raw(value_ptr) });
}
}
}

impl<T> LazyArc<T> {
pub(crate) fn get<E, F: FnOnce() -> Result<T, E>>(&self, f: F) -> Result<Arc<T>, E> {
// Clone an `Arc` given a pointer obtained from `Arc::into_raw`.
// SAFETY: `value_ptr` must be a valid pointer obtained from `Arc<T>::into_raw`.
unsafe fn clone_arc_ptr<T>(value_ptr: *const T) -> Arc<T> {
let value = Arc::from_raw(value_ptr);
let clone = Arc::clone(&value);
mem::forget(value);
clone
}

// Return the existing value if already computed.
// `Ordering::Acquire` is needed so that the content of the loaded `Arc` is
// visible to this thread.
let value_ptr = self.value.load(Ordering::Acquire);
if !value_ptr.is_null() {
// SAFETY: all writes to `self.value` are pointers obtained from `Arc::into_raw`.
return Ok(unsafe { clone_arc_ptr(value_ptr) });
}

// Race to compute and set the value.
let value = f().map(Arc::new)?;
let value_ptr = Arc::into_raw(value);
match self.value.compare_exchange(
ptr::null_mut(),
value_ptr as *mut T,
// Success: `Ordering::Release` is needed so that the content of the stored `Arc`
// is visible to other threads. No ordering is required for the null ptr that is
// loaded.
Ordering::Release,
// Failure: `Ordering::Acquire` is needed so that the content of the loaded `Arc`
// is visible to this thread.
Ordering::Acquire,
) {
Ok(_) => {
// Return the value we computed.
// SAFETY: `value_ptr` was obtained from `Arc::into_raw`.
Ok(unsafe { clone_arc_ptr(value_ptr) })
}
Err(existing_value_ptr) => {
// We lost the race, drop unneeded `value_ptr`.
// SAFETY: `value_ptr` was obtained from `Arc::into_raw`.
drop(unsafe { Arc::from_raw(value_ptr) });
// Return the existing value.
// SAFETY: all writes to `self.value` are pointers obtained from `Arc::into_raw`.
Ok(unsafe { clone_arc_ptr(existing_value_ptr) })
}
}
}
}
}

#[cfg(feature = "std")]
mod imp {
use std::sync::{Arc, Mutex};

#[derive(Debug, Default)]
pub(crate) struct LazyArc<T> {
value: Mutex<Option<Arc<T>>>,
}

impl<T> LazyArc<T> {
pub(crate) fn get<E, F: FnOnce() -> Result<T, E>>(&self, f: F) -> Result<Arc<T>, E> {
let mut lock = self.value.lock().unwrap();
if let Some(value) = &*lock {
return Ok(value.clone());
}
let value = f().map(Arc::new)?;
*lock = Some(value.clone());
Ok(value)
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn lazy_arc() {
let lazy = LazyArc::default();
let value = lazy.get(|| Err(()));
assert_eq!(value, Err(()));
let value = lazy.get(|| Ok::<i32, ()>(3)).unwrap();
assert_eq!(*value, 3);
let value = lazy.get(|| Err(())).unwrap();
assert_eq!(*value, 3);
}
}
3 changes: 3 additions & 0 deletions src/read/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ pub use self::aranges::*;
mod index;
pub use self::index::*;

#[cfg(feature = "read")]
mod lazy;

#[cfg(feature = "read")]
mod line;
#[cfg(feature = "read")]
Expand Down
3 changes: 2 additions & 1 deletion src/write/loc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ mod tests {
};
use crate::LittleEndian;
use std::collections::HashMap;
use std::sync::Arc;

#[test]
fn test_loc_list() {
Expand Down Expand Up @@ -508,7 +509,7 @@ mod tests {
DebugInfoOffset(0).into(),
read::EndianSlice::default(),
),
abbreviations: read::Abbreviations::default(),
abbreviations: Arc::new(read::Abbreviations::default()),
name: None,
comp_dir: None,
low_pc: 0,
Expand Down
3 changes: 2 additions & 1 deletion src/write/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,7 @@ mod tests {
};
use crate::LittleEndian;
use std::collections::HashMap;
use std::sync::Arc;

#[test]
fn test_operation() {
Expand Down Expand Up @@ -1578,7 +1579,7 @@ mod tests {
DebugInfoOffset(0).into(),
read::EndianSlice::new(&[], LittleEndian),
),
abbreviations: read::Abbreviations::default(),
abbreviations: Arc::new(read::Abbreviations::default()),
name: None,
comp_dir: None,
low_pc: 0,
Expand Down
3 changes: 2 additions & 1 deletion src/write/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ mod tests {
};
use crate::LittleEndian;
use std::collections::HashMap;
use std::sync::Arc;

#[test]
fn test_range() {
Expand Down Expand Up @@ -375,7 +376,7 @@ mod tests {
DebugInfoOffset(0).into(),
read::EndianSlice::default(),
),
abbreviations: read::Abbreviations::default(),
abbreviations: Arc::new(read::Abbreviations::default()),
name: None,
comp_dir: None,
low_pc: 0,
Expand Down
Loading