use std::collections::VecDeque;
use fxprof_processed_profile::{CategoryPairHandle, Frame, FrameFlags, FrameInfo};
use super::jit_category_manager::{JsFrame, JsName};
use super::lib_mappings::{AndroidArtInfo, LibMappingsHierarchy};
use super::types::{StackFrame, StackMode};
#[derive(Debug)]
pub struct StackConverter {
user_category: CategoryPairHandle,
kernel_category: CategoryPairHandle,
libart_frame_buffer: VecDeque<SecondPassFrameInfo>,
}
struct FirstPassFrameInfo {
mode: StackMode,
lookup_address: u64,
from_ip: bool,
}
#[derive(Debug)]
struct SecondPassFrameInfo {
location: Frame,
category: CategoryPairHandle,
js_frame: Option<JsFrame>,
art_info: Option<AndroidArtInfo>,
}
struct FirstPassIter<I: Iterator<Item = StackFrame>>(I);
struct SecondPassIter<'a, I: Iterator<Item = FirstPassFrameInfo>> {
inner: I,
lib_mappings: &'a LibMappingsHierarchy,
user_category: CategoryPairHandle,
kernel_category: CategoryPairHandle,
}
struct LibartFilteringIter<'c, I: Iterator<Item = SecondPassFrameInfo>> {
inner: I,
last_emitted_was_java: bool,
buffer: &'c mut VecDeque<SecondPassFrameInfo>,
}
struct ConvertedStackIter<I: Iterator<Item = SecondPassFrameInfo>> {
inner: I,
pending_frame_info: Option<FrameInfo>,
js_name_for_baseline_interpreter: Option<JsName>,
}
impl<I: Iterator<Item = StackFrame>> Iterator for FirstPassIter<I> {
type Item = FirstPassFrameInfo;
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
fn next(&mut self) -> Option<Self::Item> {
loop {
let frame = self.0.next()?;
let (mode, lookup_address, from_ip) = match frame {
StackFrame::InstructionPointer(addr, mode) => (mode, addr, true),
StackFrame::ReturnAddress(addr, mode) => (mode, addr.saturating_sub(1), false),
StackFrame::AdjustedReturnAddress(addr, mode) => (mode, addr, false),
StackFrame::TruncatedStackMarker => continue,
};
return Some(FirstPassFrameInfo {
mode,
lookup_address,
from_ip,
});
}
}
}
impl<I: Iterator<Item = FirstPassFrameInfo>> Iterator for SecondPassIter<'_, I> {
type Item = SecondPassFrameInfo;
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
fn next(&mut self) -> Option<Self::Item> {
let FirstPassFrameInfo {
mode,
lookup_address,
from_ip,
} = self.inner.next()?;
let (location, category, js_frame, art_info) = match mode {
StackMode::User => match self.lib_mappings.convert_address(lookup_address) {
Some((relative_lookup_address, info)) => {
let location = if from_ip {
let relative_address = relative_lookup_address;
Frame::RelativeAddressFromInstructionPointer(
info.lib_handle,
relative_address,
)
} else {
Frame::RelativeAddressFromAdjustedReturnAddress(
info.lib_handle,
relative_lookup_address,
)
};
(
location,
info.category.unwrap_or(self.user_category),
info.js_frame,
info.art_info,
)
}
None => {
let location = match from_ip {
true => Frame::InstructionPointer(lookup_address),
false => Frame::AdjustedReturnAddress(lookup_address),
};
(location, self.user_category, None, None)
}
},
StackMode::Kernel => {
let location = match from_ip {
true => Frame::InstructionPointer(lookup_address),
false => Frame::AdjustedReturnAddress(lookup_address),
};
(location, self.kernel_category, None, None)
}
};
Some(SecondPassFrameInfo {
location,
category,
js_frame,
art_info,
})
}
}
impl<I: Iterator<Item = SecondPassFrameInfo>> Iterator for LibartFilteringIter<'_, I> {
type Item = SecondPassFrameInfo;
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
fn next(&mut self) -> Option<Self::Item> {
if let Some(buffered_libart_frame) = self.buffer.pop_front() {
return Some(buffered_libart_frame);
}
let mut frame = self.inner.next()?;
if self.last_emitted_was_java {
while frame.art_info == Some(AndroidArtInfo::LibArt) {
self.buffer.push_back(frame);
match self.inner.next() {
Some(next_frame) => {
frame = next_frame;
}
None => {
return self.buffer.pop_front();
}
}
}
}
if frame.art_info == Some(AndroidArtInfo::JavaFrame) {
self.buffer.clear();
self.last_emitted_was_java = true;
return Some(frame);
}
self.last_emitted_was_java = false;
if self.buffer.is_empty() {
Some(frame)
} else {
self.buffer.push_back(frame);
self.buffer.pop_front()
}
}
}
impl<I: Iterator<Item = SecondPassFrameInfo>> Iterator for ConvertedStackIter<I> {
type Item = FrameInfo;
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
fn next(&mut self) -> Option<Self::Item> {
if let Some(pending_frame_info) = self.pending_frame_info.take() {
return Some(pending_frame_info);
}
let SecondPassFrameInfo {
location,
category,
js_frame,
..
} = self.inner.next()?;
let mut frame_info = FrameInfo {
frame: location,
category_pair: category,
flags: FrameFlags::empty(),
};
let extra_js_name = match js_frame {
Some(JsFrame::NativeFrameIsJs) => {
frame_info.flags |= FrameFlags::IS_JS;
None
}
Some(JsFrame::RegularInAdditionToNativeFrame(js_name)) => {
self.js_name_for_baseline_interpreter = Some(js_name);
Some(js_name)
}
Some(JsFrame::BaselineInterpreterStub(js_name)) => {
self.js_name_for_baseline_interpreter = None;
Some(js_name)
}
Some(JsFrame::BaselineInterpreter) => self.js_name_for_baseline_interpreter.take(),
None => None,
};
if let Some(JsName::NonSelfHosted(js_name)) = extra_js_name {
let prepended_js_frame = FrameInfo {
frame: Frame::Label(js_name),
category_pair: category,
flags: FrameFlags::IS_JS,
};
let buffered_frame = std::mem::replace(&mut frame_info, prepended_js_frame);
self.pending_frame_info = Some(buffered_frame);
};
Some(frame_info)
}
}
impl StackConverter {
pub fn new(user_category: CategoryPairHandle, kernel_category: CategoryPairHandle) -> Self {
Self {
user_category,
kernel_category,
libart_frame_buffer: VecDeque::new(),
}
}
pub fn convert_stack<'a>(
&'a mut self,
stack: &'a [StackFrame],
lib_mappings: &'a LibMappingsHierarchy,
extra_first_frame: Option<FrameInfo>,
) -> impl Iterator<Item = FrameInfo> + 'a {
let pass1 = FirstPassIter(stack.iter().cloned().rev());
let pass2 = SecondPassIter {
inner: pass1,
lib_mappings,
user_category: self.user_category,
kernel_category: self.kernel_category,
};
self.libart_frame_buffer.clear();
let pass3 = LibartFilteringIter {
inner: pass2,
last_emitted_was_java: false,
buffer: &mut self.libart_frame_buffer,
};
ConvertedStackIter {
inner: pass3,
pending_frame_info: extra_first_frame,
js_name_for_baseline_interpreter: None,
}
}
}