Skip to content

Commit

Permalink
read: cache abbreviations at offset 0
Browse files Browse the repository at this point in the history
This optimises for the case where there is a single set of
abbreviations that is used for all units.
  • Loading branch information
philipc committed Sep 24, 2022
1 parent f1bf29d commit fef8947
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 16 deletions.
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
108 changes: 108 additions & 0 deletions src/read/lazy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
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`.
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`.
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.
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,
Ordering::Release,
Ordering::Relaxed,
) {
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`.
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

0 comments on commit fef8947

Please sign in to comment.