Path: blob/main/crates/cranelift/src/debug/transform/expression.rs
1693 views
use super::address_transform::AddressTransform;1use super::dbi_log;2use crate::debug::ModuleMemoryOffset;3use crate::debug::transform::debug_transform_logging::{4dbi_log_enabled, log_get_value_loc, log_get_value_name, log_get_value_ranges,5};6use crate::translate::get_vmctx_value_label;7use anyhow::{Context, Error, Result};8use core::fmt;9use cranelift_codegen::LabelValueLoc;10use cranelift_codegen::ValueLabelsRanges;11use cranelift_codegen::ir::ValueLabel;12use cranelift_codegen::isa::TargetIsa;13use gimli::{Expression, Operation, Reader, ReaderOffset, write};14use itertools::Itertools;15use std::cmp::PartialEq;16use std::collections::{HashMap, HashSet};17use std::hash::{Hash, Hasher};18use std::rc::Rc;1920#[derive(Debug)]21pub struct FunctionFrameInfo<'a> {22pub value_ranges: &'a ValueLabelsRanges,23pub memory_offset: ModuleMemoryOffset,24}2526struct ExpressionWriter(write::EndianVec<gimli::RunTimeEndian>);2728enum VmctxBase {29Reg(u16),30OnStack,31}3233impl ExpressionWriter {34fn new() -> Self {35let endian = gimli::RunTimeEndian::Little;36let writer = write::EndianVec::new(endian);37ExpressionWriter(writer)38}3940fn write_op(&mut self, op: gimli::DwOp) -> write::Result<()> {41self.write_u8(op.0)42}4344fn write_op_reg(&mut self, reg: u16) -> write::Result<()> {45if reg < 32 {46self.write_u8(gimli::constants::DW_OP_reg0.0 + reg as u8)47} else {48self.write_op(gimli::constants::DW_OP_regx)?;49self.write_uleb128(reg.into())50}51}5253fn write_op_breg(&mut self, reg: u16) -> write::Result<()> {54if reg < 32 {55self.write_u8(gimli::constants::DW_OP_breg0.0 + reg as u8)56} else {57self.write_op(gimli::constants::DW_OP_bregx)?;58self.write_uleb128(reg.into())59}60}6162fn write_u8(&mut self, b: u8) -> write::Result<()> {63write::Writer::write_u8(&mut self.0, b)64}6566fn write_u32(&mut self, b: u32) -> write::Result<()> {67write::Writer::write_u32(&mut self.0, b)68}6970fn write_uleb128(&mut self, i: u64) -> write::Result<()> {71write::Writer::write_uleb128(&mut self.0, i)72}7374fn write_sleb128(&mut self, i: i64) -> write::Result<()> {75write::Writer::write_sleb128(&mut self.0, i)76}7778fn into_vec(self) -> Vec<u8> {79self.0.into_vec()80}8182fn gen_address_of_memory_base_pointer(83&mut self,84vmctx: VmctxBase,85memory_base: &ModuleMemoryOffset,86) -> write::Result<()> {87match *memory_base {88ModuleMemoryOffset::Defined(offset) => match vmctx {89VmctxBase::Reg(reg) => {90self.write_op_breg(reg)?;91self.write_sleb128(offset.into())?;92}93VmctxBase::OnStack => {94self.write_op(gimli::constants::DW_OP_consts)?;95self.write_sleb128(offset.into())?;96self.write_op(gimli::constants::DW_OP_plus)?;97}98},99ModuleMemoryOffset::Imported {100offset_to_vm_memory_definition,101offset_to_memory_base,102} => {103match vmctx {104VmctxBase::Reg(reg) => {105self.write_op_breg(reg)?;106self.write_sleb128(offset_to_vm_memory_definition.into())?;107}108VmctxBase::OnStack => {109if offset_to_vm_memory_definition > 0 {110self.write_op(gimli::constants::DW_OP_consts)?;111self.write_sleb128(offset_to_vm_memory_definition.into())?;112}113self.write_op(gimli::constants::DW_OP_plus)?;114}115}116self.write_op(gimli::constants::DW_OP_deref)?;117if offset_to_memory_base > 0 {118self.write_op(gimli::constants::DW_OP_consts)?;119self.write_sleb128(offset_to_memory_base.into())?;120self.write_op(gimli::constants::DW_OP_plus)?;121}122}123ModuleMemoryOffset::None => return Err(write::Error::InvalidAttributeValue),124}125Ok(())126}127}128129#[derive(Debug, Clone, PartialEq)]130enum CompiledExpressionPart {131// Untranslated DWARF expression.132Code(Vec<u8>),133// The wasm-local DWARF operator. The label points to `ValueLabel`.134// The trailing field denotes that the operator was last in sequence,135// and it is the DWARF location (not a pointer).136Local {137label: ValueLabel,138trailing: bool,139},140// Dereference is needed.141Deref,142// Jumping in the expression.143Jump {144conditionally: bool,145target: JumpTargetMarker,146},147// Floating landing pad.148LandingPad(JumpTargetMarker),149}150151#[derive(Debug, Clone, PartialEq)]152pub struct CompiledExpression {153parts: Vec<CompiledExpressionPart>,154need_deref: bool,155}156157impl CompiledExpression {158pub fn vmctx() -> CompiledExpression {159CompiledExpression::from_label(get_vmctx_value_label())160}161162pub fn from_label(label: ValueLabel) -> CompiledExpression {163CompiledExpression {164parts: vec![CompiledExpressionPart::Local {165label,166trailing: true,167}],168need_deref: false,169}170}171}172173fn translate_loc(174loc: LabelValueLoc,175isa: &dyn TargetIsa,176add_stack_value: bool,177) -> Result<Option<Vec<u8>>> {178Ok(match loc {179LabelValueLoc::Reg(r) => {180let machine_reg = isa.map_regalloc_reg_to_dwarf(r)?;181let mut writer = ExpressionWriter::new();182if add_stack_value {183writer.write_op_reg(machine_reg)?;184} else {185writer.write_op_breg(machine_reg)?;186writer.write_sleb128(0)?;187}188Some(writer.into_vec())189}190LabelValueLoc::CFAOffset(off) => {191let mut writer = ExpressionWriter::new();192writer.write_op(gimli::constants::DW_OP_fbreg)?;193writer.write_sleb128(off)?;194if !add_stack_value {195writer.write_op(gimli::constants::DW_OP_deref)?;196}197return Ok(Some(writer.into_vec()));198}199})200}201202fn append_memory_deref(203buf: &mut Vec<u8>,204frame_info: &FunctionFrameInfo,205vmctx_loc: LabelValueLoc,206isa: &dyn TargetIsa,207) -> Result<bool> {208let mut writer = ExpressionWriter::new();209let vmctx_base = match vmctx_loc {210LabelValueLoc::Reg(r) => VmctxBase::Reg(isa.map_regalloc_reg_to_dwarf(r)?),211LabelValueLoc::CFAOffset(off) => {212writer.write_op(gimli::constants::DW_OP_fbreg)?;213writer.write_sleb128(off)?;214writer.write_op(gimli::constants::DW_OP_deref)?;215VmctxBase::OnStack216}217};218writer.gen_address_of_memory_base_pointer(vmctx_base, &frame_info.memory_offset)?;219writer.write_op(gimli::constants::DW_OP_deref)?;220writer.write_op(gimli::constants::DW_OP_swap)?;221writer.write_op(gimli::constants::DW_OP_const4u)?;222writer.write_u32(0xffff_ffff)?;223writer.write_op(gimli::constants::DW_OP_and)?;224writer.write_op(gimli::constants::DW_OP_plus)?;225buf.extend(writer.into_vec());226Ok(true)227}228229pub struct BuiltCompiledExpression<TIter> {230pub expressions: TIter,231pub covers_entire_scope: bool,232}233234impl CompiledExpression {235pub fn is_simple(&self) -> bool {236if let [CompiledExpressionPart::Code(_)] = self.parts.as_slice() {237true238} else {239self.parts.is_empty()240}241}242243pub fn build(&self) -> Option<write::Expression> {244if let [CompiledExpressionPart::Code(code)] = self.parts.as_slice() {245return Some(write::Expression::raw(code.to_vec()));246}247// locals found, not supported248None249}250251pub fn build_with_locals<'a>(252&'a self,253scope: &'a [(u64, u64)], // wasm ranges254addr_tr: &'a AddressTransform,255frame_info: Option<&'a FunctionFrameInfo>,256isa: &'a dyn TargetIsa,257) -> BuiltCompiledExpression<258impl Iterator<Item = Result<(write::Address, u64, write::Expression)>> + use<'a>,259> {260enum BuildWithLocalsResult<'a> {261Empty,262Simple(263Box<dyn Iterator<Item = (write::Address, u64)> + 'a>,264Vec<u8>,265),266Ranges(Box<dyn Iterator<Item = Result<(usize, usize, usize, Vec<u8>)>> + 'a>),267}268impl Iterator for BuildWithLocalsResult<'_> {269type Item = Result<(write::Address, u64, write::Expression)>;270fn next(&mut self) -> Option<Self::Item> {271match self {272BuildWithLocalsResult::Empty => None,273BuildWithLocalsResult::Simple(it, code) => it274.next()275.map(|(addr, len)| Ok((addr, len, write::Expression::raw(code.to_vec())))),276BuildWithLocalsResult::Ranges(it) => it.next().map(|r| {277r.map(|(symbol, start, end, code_buf)| {278(279write::Address::Symbol {280symbol,281addend: start as i64,282},283(end - start) as u64,284write::Expression::raw(code_buf),285)286})287}),288}289}290}291292if scope.is_empty() {293return BuiltCompiledExpression {294expressions: BuildWithLocalsResult::Empty,295covers_entire_scope: false,296};297}298299// If it a simple DWARF code, no need in locals processing. Just translate300// the scope ranges.301if let [CompiledExpressionPart::Code(code)] = self.parts.as_slice() {302return BuiltCompiledExpression {303expressions: BuildWithLocalsResult::Simple(304Box::new(scope.iter().flat_map(move |(wasm_start, wasm_end)| {305addr_tr.translate_ranges(*wasm_start, *wasm_end)306})),307code.clone(),308),309covers_entire_scope: false,310};311}312313let vmctx_label = get_vmctx_value_label();314315// Some locals are present, preparing and divided ranges based on the scope316// and frame_info data.317let mut ranges_builder = ValueLabelRangesBuilder::new(scope, addr_tr, frame_info, isa);318for p in self.parts.iter() {319match p {320CompiledExpressionPart::Code(_)321| CompiledExpressionPart::Jump { .. }322| CompiledExpressionPart::LandingPad { .. } => (),323CompiledExpressionPart::Local { label, .. } => ranges_builder.process_label(*label),324CompiledExpressionPart::Deref => ranges_builder.process_label(vmctx_label),325}326}327if self.need_deref {328ranges_builder.process_label(vmctx_label);329}330331let ranges = ranges_builder.into_ranges();332let expressions = BuildWithLocalsResult::Ranges(Box::new(333ranges334.ranges335.map(336move |CachedValueLabelRange {337func_index,338start,339end,340label_location,341}| {342// build expression343let mut code_buf = Vec::new();344let mut jump_positions = Vec::new();345let mut landing_positions = HashMap::new();346347macro_rules! deref {348() => {349if let (Some(vmctx_loc), Some(frame_info)) =350(label_location.get(&vmctx_label), frame_info)351{352if !append_memory_deref(353&mut code_buf,354frame_info,355*vmctx_loc,356isa,357)? {358return Ok(None);359}360} else {361return Ok(None);362}363};364}365for part in &self.parts {366match part {367CompiledExpressionPart::Code(c) => {368code_buf.extend_from_slice(c.as_slice())369}370CompiledExpressionPart::LandingPad(marker) => {371landing_positions.insert(marker.clone(), code_buf.len());372}373CompiledExpressionPart::Jump {374conditionally,375target,376} => {377code_buf.push(378match conditionally {379true => gimli::constants::DW_OP_bra,380false => gimli::constants::DW_OP_skip,381}382.0,383);384code_buf.push(!0);385code_buf.push(!0); // these will be relocated below386jump_positions.push((target.clone(), code_buf.len()));387}388CompiledExpressionPart::Local { label, trailing } => {389let loc =390*label_location.get(&label).context("label_location")?;391if let Some(expr) = translate_loc(loc, isa, *trailing)? {392code_buf.extend_from_slice(&expr)393} else {394return Ok(None);395}396}397CompiledExpressionPart::Deref => deref!(),398}399}400if self.need_deref {401deref!();402}403404for (marker, new_from) in jump_positions {405// relocate jump targets406let new_to = landing_positions[&marker];407let new_diff = new_to as isize - new_from as isize;408// FIXME: use encoding? LittleEndian for now...409code_buf[new_from - 2..new_from]410.copy_from_slice(&(new_diff as i16).to_le_bytes());411}412Ok(Some((func_index, start, end, code_buf)))413},414)415.filter_map(Result::transpose),416));417418BuiltCompiledExpression {419expressions,420covers_entire_scope: ranges.covers_entire_scope,421}422}423}424425fn is_old_expression_format(buf: &[u8]) -> bool {426// Heuristic to detect old variable expression format without DW_OP_fbreg:427// DW_OP_plus_uconst op must be present, but not DW_OP_fbreg.428if buf.contains(&(gimli::constants::DW_OP_fbreg.0)) {429// Stop check if DW_OP_fbreg exist.430return false;431}432buf.contains(&(gimli::constants::DW_OP_plus_uconst.0))433}434435pub fn compile_expression<R>(436expr: &Expression<R>,437encoding: gimli::Encoding,438frame_base: Option<&CompiledExpression>,439) -> Result<Option<CompiledExpression>, Error>440where441R: Reader,442{443// Bail when `frame_base` is complicated.444if let Some(expr) = frame_base {445if expr.parts.iter().any(|p| match p {446CompiledExpressionPart::Jump { .. } => true,447_ => false,448}) {449return Ok(None);450}451}452453// jump_targets key is offset in buf starting from the end454// (see also `unread_bytes` below)455let mut jump_targets: HashMap<u64, JumpTargetMarker> = HashMap::new();456let mut pc = expr.0.clone();457458let buf = expr.0.to_slice()?;459let mut parts = Vec::new();460macro_rules! push {461($part:expr) => {{462let part = $part;463if let (CompiledExpressionPart::Code(cc2), Some(CompiledExpressionPart::Code(cc1))) =464(&part, parts.last_mut())465{466cc1.extend_from_slice(cc2);467} else {468parts.push(part)469}470}};471}472let mut need_deref = false;473if is_old_expression_format(&buf) && frame_base.is_some() {474// Still supporting old DWARF variable expressions without fbreg.475parts.extend_from_slice(&frame_base.unwrap().parts);476if let Some(CompiledExpressionPart::Local { trailing, .. }) = parts.last_mut() {477*trailing = false;478}479need_deref = frame_base.unwrap().need_deref;480}481let mut code_chunk = Vec::new();482macro_rules! flush_code_chunk {483() => {484if !code_chunk.is_empty() {485push!(CompiledExpressionPart::Code(code_chunk));486code_chunk = Vec::new();487let _ = code_chunk; // suppresses warning for final flush488}489};490}491492// Find all landing pads by scanning bytes, do not care about493// false location at this moment.494// Looks hacky but it is fast; does not need to be really exact.495if buf.len() > 2 {496for i in 0..buf.len() - 2 {497let op = buf[i];498if op == gimli::constants::DW_OP_bra.0 || op == gimli::constants::DW_OP_skip.0 {499// TODO fix for big-endian500let offset = i16::from_le_bytes([buf[i + 1], buf[i + 2]]);501let origin = i + 3;502// Discarding out-of-bounds jumps (also some of falsely detected ops)503if (offset >= 0 && offset as usize + origin <= buf.len())504|| (offset < 0 && -offset as usize <= origin)505{506let target = buf.len() as isize - origin as isize - offset as isize;507jump_targets.insert(target as u64, JumpTargetMarker::new());508}509}510}511}512513while !pc.is_empty() {514let unread_bytes = pc.len().into_u64();515if let Some(marker) = jump_targets.get(&unread_bytes) {516flush_code_chunk!();517parts.push(CompiledExpressionPart::LandingPad(marker.clone()));518}519520need_deref = true;521522let pos = pc.offset_from(&expr.0).into_u64() as usize;523let op = Operation::parse(&mut pc, encoding)?;524match op {525Operation::FrameOffset { offset } => {526// Expand DW_OP_fbreg into frame location and DW_OP_plus_uconst.527if frame_base.is_some() {528// Add frame base expressions.529flush_code_chunk!();530parts.extend_from_slice(&frame_base.unwrap().parts);531}532if let Some(CompiledExpressionPart::Local { trailing, .. }) = parts.last_mut() {533// Reset local trailing flag.534*trailing = false;535}536// Append DW_OP_plus_uconst part.537let mut writer = ExpressionWriter::new();538writer.write_op(gimli::constants::DW_OP_plus_uconst)?;539writer.write_uleb128(offset as u64)?;540code_chunk.extend(writer.into_vec());541continue;542}543Operation::Drop { .. }544| Operation::Pick { .. }545| Operation::Swap { .. }546| Operation::Rot { .. }547| Operation::Nop { .. }548| Operation::UnsignedConstant { .. }549| Operation::SignedConstant { .. }550| Operation::ConstantIndex { .. }551| Operation::PlusConstant { .. }552| Operation::Abs { .. }553| Operation::And { .. }554| Operation::Or { .. }555| Operation::Xor { .. }556| Operation::Shl { .. }557| Operation::Plus { .. }558| Operation::Minus { .. }559| Operation::Div { .. }560| Operation::Mod { .. }561| Operation::Mul { .. }562| Operation::Neg { .. }563| Operation::Not { .. }564| Operation::Lt { .. }565| Operation::Gt { .. }566| Operation::Le { .. }567| Operation::Ge { .. }568| Operation::Eq { .. }569| Operation::Ne { .. }570| Operation::TypedLiteral { .. }571| Operation::Convert { .. }572| Operation::Reinterpret { .. }573| Operation::Piece { .. } => (),574Operation::Bra { target } | Operation::Skip { target } => {575flush_code_chunk!();576let arc_to = (pc.len().into_u64() as isize - target as isize) as u64;577let marker = match jump_targets.get(&arc_to) {578Some(m) => m.clone(),579None => {580// Marker not found: probably out of bounds.581return Ok(None);582}583};584push!(CompiledExpressionPart::Jump {585conditionally: match op {586Operation::Bra { .. } => true,587_ => false,588},589target: marker,590});591continue;592}593Operation::StackValue => {594need_deref = false;595596// Find extra stack_value, that follow wasm-local operators,597// and mark such locals with special flag.598if let (Some(CompiledExpressionPart::Local { trailing, .. }), true) =599(parts.last_mut(), code_chunk.is_empty())600{601*trailing = true;602continue;603}604}605Operation::Deref { .. } => {606flush_code_chunk!();607push!(CompiledExpressionPart::Deref);608// Don't re-enter the loop here (i.e. continue), because the609// DW_OP_deref still needs to be kept.610}611Operation::WasmLocal { index } => {612flush_code_chunk!();613let label = ValueLabel::from_u32(index);614push!(CompiledExpressionPart::Local {615label,616trailing: false,617});618continue;619}620Operation::Shr { .. } | Operation::Shra { .. } => {621// Insert value normalisation part.622// The semantic value is 32 bits (TODO: check unit)623// but the target architecture is 64-bits. So we'll624// clean out the upper 32 bits (in a sign-correct way)625// to avoid contamination of the result with randomness.626let mut writer = ExpressionWriter::new();627writer.write_op(gimli::constants::DW_OP_plus_uconst)?;628writer.write_uleb128(32)?; // increase shift amount629writer.write_op(gimli::constants::DW_OP_swap)?;630writer.write_op(gimli::constants::DW_OP_const1u)?;631writer.write_u8(32)?;632writer.write_op(gimli::constants::DW_OP_shl)?;633writer.write_op(gimli::constants::DW_OP_swap)?;634code_chunk.extend(writer.into_vec());635// Don't re-enter the loop here (i.e. continue), because the636// DW_OP_shr* still needs to be kept.637}638Operation::Address { .. }639| Operation::AddressIndex { .. }640| Operation::Call { .. }641| Operation::Register { .. }642| Operation::RegisterOffset { .. }643| Operation::CallFrameCFA644| Operation::PushObjectAddress645| Operation::TLS646| Operation::ImplicitValue { .. }647| Operation::ImplicitPointer { .. }648| Operation::EntryValue { .. }649| Operation::ParameterRef { .. } => {650return Ok(None);651}652Operation::WasmGlobal { index: _ } | Operation::WasmStack { index: _ } => {653// TODO support those two654return Ok(None);655}656}657let chunk = &buf[pos..pc.offset_from(&expr.0).into_u64() as usize];658code_chunk.extend_from_slice(chunk);659}660661flush_code_chunk!();662if let Some(marker) = jump_targets.get(&0) {663parts.push(CompiledExpressionPart::LandingPad(marker.clone()));664}665666Ok(Some(CompiledExpression { parts, need_deref }))667}668669#[derive(Debug, Clone)]670struct CachedValueLabelRange {671func_index: usize,672start: usize,673end: usize,674label_location: HashMap<ValueLabel, LabelValueLoc>,675}676677struct BuiltRangeSummary<'a> {678range: &'a CachedValueLabelRange,679isa: &'a dyn TargetIsa,680}681682impl<'a> fmt::Debug for BuiltRangeSummary<'a> {683fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {684let range = self.range;685write!(f, "[")?;686let mut is_first = true;687for (value, value_loc) in &range.label_location {688if !is_first {689write!(f, ", ")?;690} else {691is_first = false;692}693write!(694f,695"{:?}:{:?}",696log_get_value_name(*value),697log_get_value_loc(*value_loc, self.isa)698)?;699}700write!(f, "]@[{}..{})", range.start, range.end)?;701Ok(())702}703}704705struct ValueLabelRangesBuilder<'a, 'b> {706isa: &'a dyn TargetIsa,707ranges: Vec<CachedValueLabelRange>,708frame_info: Option<&'a FunctionFrameInfo<'b>>,709processed_labels: HashSet<ValueLabel>,710covers_entire_scope: bool,711}712713struct BuiltValueLabelRanges<TIter> {714ranges: TIter,715covers_entire_scope: bool,716}717718impl<'a, 'b> ValueLabelRangesBuilder<'a, 'b> {719pub fn new(720scope: &[(u64, u64)], // wasm ranges721addr_tr: &'a AddressTransform,722frame_info: Option<&'a FunctionFrameInfo<'b>>,723isa: &'a dyn TargetIsa,724) -> Self {725let mut ranges = Vec::new();726for (wasm_start, wasm_end) in scope {727if let Some((func_index, tr)) = addr_tr.translate_ranges_raw(*wasm_start, *wasm_end) {728ranges.extend(tr.into_iter().map(|(start, end)| CachedValueLabelRange {729func_index,730start,731end,732label_location: HashMap::new(),733}));734}735}736ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start));737738dbi_log!(739"Building ranges for values in scope: {}\n{:?}",740ranges741.iter()742.map(|r| format!("[{}..{})", r.start, r.end))743.join(" "),744log_get_value_ranges(frame_info.map(|f| f.value_ranges), isa)745);746ValueLabelRangesBuilder {747isa,748ranges,749frame_info,750processed_labels: HashSet::new(),751covers_entire_scope: true,752}753}754755fn process_label(&mut self, label: ValueLabel) {756if self.processed_labels.contains(&label) {757return;758}759dbi_log!("Intersecting with {:?}", log_get_value_name(label));760self.processed_labels.insert(label);761762let value_ranges = match self.frame_info.and_then(|fi| fi.value_ranges.get(&label)) {763Some(value_ranges) => value_ranges,764None => {765return;766}767};768769let ranges = &mut self.ranges;770for value_range in value_ranges {771let range_start = value_range.start as usize;772let range_end = value_range.end as usize;773let loc = value_range.loc;774if range_start == range_end {775continue;776}777assert!(range_start < range_end);778779// Find acceptable scope of ranges to intersect with.780let i = match ranges.binary_search_by(|s| s.start.cmp(&range_start)) {781Ok(i) => i,782Err(i) => {783if i > 0 && range_start < ranges[i - 1].end {784i - 1785} else {786i787}788}789};790let j = match ranges.binary_search_by(|s| s.start.cmp(&range_end)) {791Ok(i) | Err(i) => i,792};793// Starting from the end, intersect (range_start..range_end) with794// self.ranges array.795for i in (i..j).rev() {796if range_end <= ranges[i].start || ranges[i].end <= range_start {797continue;798}799if range_end < ranges[i].end {800// Cutting some of the range from the end.801let mut tail = ranges[i].clone();802ranges[i].end = range_end;803tail.start = range_end;804ranges.insert(i + 1, tail);805self.covers_entire_scope = false;806}807assert!(ranges[i].end <= range_end);808if range_start <= ranges[i].start {809ranges[i].label_location.insert(label, loc);810continue;811}812// Cutting some of the range from the start.813let mut tail = ranges[i].clone();814ranges[i].end = range_start;815tail.start = range_start;816tail.label_location.insert(label, loc);817ranges.insert(i + 1, tail);818self.covers_entire_scope = false;819}820}821}822823pub fn into_ranges(824self,825) -> BuiltValueLabelRanges<impl Iterator<Item = CachedValueLabelRange> + use<>> {826// Ranges with not-enough labels are discarded.827let processed_labels_len = self.processed_labels.len();828let is_valid_range =829move |r: &CachedValueLabelRange| r.label_location.len() == processed_labels_len;830831if dbi_log_enabled!() {832dbi_log!("Built ranges:");833for range in self.ranges.iter().filter(|r| is_valid_range(*r)) {834dbi_log!(835"{:?}",836BuiltRangeSummary {837range,838isa: self.isa839}840);841}842dbi_log!("");843}844845BuiltValueLabelRanges {846ranges: self.ranges.into_iter().filter(is_valid_range),847covers_entire_scope: self.covers_entire_scope,848}849}850}851852/// Marker for tracking incoming jumps.853/// Different when created new, and the same when cloned.854#[derive(Clone, Eq)]855struct JumpTargetMarker(Rc<u32>);856857impl JumpTargetMarker {858fn new() -> JumpTargetMarker {859// Create somewhat unique hash data -- using part of860// the pointer of the RcBox.861let mut rc = Rc::new(0);862let hash_data = rc.as_ref() as *const u32 as usize as u32;863*Rc::get_mut(&mut rc).unwrap() = hash_data;864JumpTargetMarker(rc)865}866}867868impl PartialEq for JumpTargetMarker {869fn eq(&self, other: &JumpTargetMarker) -> bool {870Rc::ptr_eq(&self.0, &other.0)871}872}873874impl Hash for JumpTargetMarker {875fn hash<H: Hasher>(&self, hasher: &mut H) {876hasher.write_u32(*self.0);877}878}879impl std::fmt::Debug for JumpTargetMarker {880fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {881write!(882f,883"JumpMarker<{:08x}>",884self.0.as_ref() as *const u32 as usize885)886}887}888889#[cfg(test)]890#[expect(trivial_numeric_casts, reason = "macro-generated code")]891mod tests {892use super::{893AddressTransform, CompiledExpression, CompiledExpressionPart, FunctionFrameInfo,894JumpTargetMarker, ValueLabel, ValueLabelsRanges, compile_expression,895};896use crate::CompiledFunctionMetadata;897use cranelift_codegen::{isa::lookup, settings::Flags};898use gimli::{Encoding, EndianSlice, Expression, RunTimeEndian, constants};899use target_lexicon::triple;900use wasmtime_environ::FilePos;901902macro_rules! dw_op {903(DW_OP_WASM_location) => {9040xed905};906($i:literal) => {907$i908};909($d:ident) => {910constants::$d.0 as u8911};912($e:expr) => {913$e as u8914};915}916917macro_rules! expression {918($($t:tt),*) => {919Expression(EndianSlice::new(920&[$(dw_op!($t)),*],921RunTimeEndian::Little,922))923}924}925926fn find_jump_targets<'a>(ce: &'a CompiledExpression) -> Vec<&'a JumpTargetMarker> {927ce.parts928.iter()929.filter_map(|p| {930if let CompiledExpressionPart::LandingPad(t) = p {931Some(t)932} else {933None934}935})936.collect::<Vec<_>>()937}938939static DWARF_ENCODING: Encoding = Encoding {940address_size: 4,941format: gimli::Format::Dwarf32,942version: 4,943};944945#[test]946fn test_debug_expression_jump_target() {947let m1 = JumpTargetMarker::new();948let m2 = JumpTargetMarker::new();949assert!(m1 != m2);950assert!(m1 == m1.clone());951952// Internal hash_data test (theoretically can fail intermittently).953assert!(m1.0 != m2.0);954}955956#[test]957fn test_debug_parse_expressions() {958use cranelift_entity::EntityRef;959960let (val1, val3, val20) = (ValueLabel::new(1), ValueLabel::new(3), ValueLabel::new(20));961962let e = expression!(DW_OP_WASM_location, 0x0, 20, DW_OP_stack_value);963let ce = compile_expression(&e, DWARF_ENCODING, None)964.expect("non-error")965.expect("expression");966assert_eq!(967ce,968CompiledExpression {969parts: vec![CompiledExpressionPart::Local {970label: val20,971trailing: true972}],973need_deref: false,974}975);976977let e = expression!(978DW_OP_WASM_location,9790x0,9801,981DW_OP_plus_uconst,9820x10,983DW_OP_stack_value984);985let ce = compile_expression(&e, DWARF_ENCODING, None)986.expect("non-error")987.expect("expression");988assert_eq!(989ce,990CompiledExpression {991parts: vec![992CompiledExpressionPart::Local {993label: val1,994trailing: false995},996CompiledExpressionPart::Code(vec![35, 16, 159])997],998need_deref: false,999}1000);10011002let e = expression!(DW_OP_WASM_location, 0x0, 3, DW_OP_stack_value);1003let fe = compile_expression(&e, DWARF_ENCODING, None).expect("non-error");1004let e = expression!(DW_OP_fbreg, 0x12);1005let ce = compile_expression(&e, DWARF_ENCODING, fe.as_ref())1006.expect("non-error")1007.expect("expression");1008assert_eq!(1009ce,1010CompiledExpression {1011parts: vec![1012CompiledExpressionPart::Local {1013label: val3,1014trailing: false1015},1016CompiledExpressionPart::Code(vec![35, 18])1017],1018need_deref: true,1019}1020);10211022let e = expression!(1023DW_OP_WASM_location,10240x0,10251,1026DW_OP_plus_uconst,10275,1028DW_OP_deref,1029DW_OP_stack_value1030);1031let ce = compile_expression(&e, DWARF_ENCODING, None)1032.expect("non-error")1033.expect("expression");1034assert_eq!(1035ce,1036CompiledExpression {1037parts: vec![1038CompiledExpressionPart::Local {1039label: val1,1040trailing: false1041},1042CompiledExpressionPart::Code(vec![35, 5]),1043CompiledExpressionPart::Deref,1044CompiledExpressionPart::Code(vec![6, 159])1045],1046need_deref: false,1047}1048);10491050let e = expression!(1051DW_OP_WASM_location,10520x0,10531,1054DW_OP_lit16,1055DW_OP_shra,1056DW_OP_stack_value1057);1058let ce = compile_expression(&e, DWARF_ENCODING, None)1059.expect("non-error")1060.expect("expression");1061assert_eq!(1062ce,1063CompiledExpression {1064parts: vec![1065CompiledExpressionPart::Local {1066label: val1,1067trailing: false1068},1069CompiledExpressionPart::Code(vec![64, 35, 32, 22, 8, 32, 36, 22, 38, 159])1070],1071need_deref: false,1072}1073);10741075let e = expression!(1076DW_OP_lit1,1077DW_OP_dup,1078DW_OP_WASM_location,10790x0,10801,1081DW_OP_and,1082DW_OP_bra,10835,10840, // --> pointer1085DW_OP_swap,1086DW_OP_shr,1087DW_OP_skip,10882,10890, // --> done1090// pointer:1091DW_OP_plus,1092DW_OP_deref,1093// done:1094DW_OP_stack_value1095);1096let ce = compile_expression(&e, DWARF_ENCODING, None)1097.expect("non-error")1098.expect("expression");1099let targets = find_jump_targets(&ce);1100assert_eq!(targets.len(), 2);1101assert_eq!(1102ce,1103CompiledExpression {1104parts: vec![1105CompiledExpressionPart::Code(vec![49, 18]),1106CompiledExpressionPart::Local {1107label: val1,1108trailing: false1109},1110CompiledExpressionPart::Code(vec![26]),1111CompiledExpressionPart::Jump {1112conditionally: true,1113target: targets[0].clone(),1114},1115CompiledExpressionPart::Code(vec![22, 35, 32, 22, 8, 32, 36, 22, 37]),1116CompiledExpressionPart::Jump {1117conditionally: false,1118target: targets[1].clone(),1119},1120CompiledExpressionPart::LandingPad(targets[0].clone()), // capture from1121CompiledExpressionPart::Code(vec![34]),1122CompiledExpressionPart::Deref,1123CompiledExpressionPart::Code(vec![6]),1124CompiledExpressionPart::LandingPad(targets[1].clone()), // capture to1125CompiledExpressionPart::Code(vec![159])1126],1127need_deref: false,1128}1129);11301131let e = expression!(1132DW_OP_lit1,1133DW_OP_dup,1134DW_OP_bra,11352,11360, // --> target1137DW_OP_deref,1138DW_OP_lit0,1139// target:1140DW_OP_stack_value1141);1142let ce = compile_expression(&e, DWARF_ENCODING, None)1143.expect("non-error")1144.expect("expression");1145let targets = find_jump_targets(&ce);1146assert_eq!(targets.len(), 1);1147assert_eq!(1148ce,1149CompiledExpression {1150parts: vec![1151CompiledExpressionPart::Code(vec![49, 18]),1152CompiledExpressionPart::Jump {1153conditionally: true,1154target: targets[0].clone(),1155},1156CompiledExpressionPart::Deref,1157CompiledExpressionPart::Code(vec![6, 48]),1158CompiledExpressionPart::LandingPad(targets[0].clone()), // capture to1159CompiledExpressionPart::Code(vec![159])1160],1161need_deref: false,1162}1163);11641165let e = expression!(1166DW_OP_lit1,1167/* loop */ DW_OP_dup,1168DW_OP_lit25,1169DW_OP_ge,1170DW_OP_bra,11715,11720, // --> done1173DW_OP_plus_uconst,11741,1175DW_OP_skip,1176(-11_i8),1177(!0), // --> loop1178/* done */ DW_OP_stack_value1179);1180let ce = compile_expression(&e, DWARF_ENCODING, None)1181.expect("non-error")1182.expect("expression");1183let targets = find_jump_targets(&ce);1184assert_eq!(targets.len(), 2);1185assert_eq!(1186ce,1187CompiledExpression {1188parts: vec![1189CompiledExpressionPart::Code(vec![49]),1190CompiledExpressionPart::LandingPad(targets[0].clone()),1191CompiledExpressionPart::Code(vec![18, 73, 42]),1192CompiledExpressionPart::Jump {1193conditionally: true,1194target: targets[1].clone(),1195},1196CompiledExpressionPart::Code(vec![35, 1]),1197CompiledExpressionPart::Jump {1198conditionally: false,1199target: targets[0].clone(),1200},1201CompiledExpressionPart::LandingPad(targets[1].clone()),1202CompiledExpressionPart::Code(vec![159])1203],1204need_deref: false,1205}1206);12071208let e = expression!(DW_OP_WASM_location, 0x0, 1, DW_OP_plus_uconst, 5);1209let ce = compile_expression(&e, DWARF_ENCODING, None)1210.expect("non-error")1211.expect("expression");1212assert_eq!(1213ce,1214CompiledExpression {1215parts: vec![1216CompiledExpressionPart::Local {1217label: val1,1218trailing: false1219},1220CompiledExpressionPart::Code(vec![35, 5])1221],1222need_deref: true,1223}1224);1225}12261227fn create_mock_address_transform() -> AddressTransform {1228use crate::FunctionAddressMap;1229use cranelift_entity::PrimaryMap;1230use wasmtime_environ::InstructionAddressMap;1231use wasmtime_environ::WasmFileInfo;12321233let mut module_map = PrimaryMap::new();1234let code_section_offset: u32 = 100;1235let func = CompiledFunctionMetadata {1236address_map: FunctionAddressMap {1237instructions: vec![1238InstructionAddressMap {1239srcloc: FilePos::new(code_section_offset + 12),1240code_offset: 5,1241},1242InstructionAddressMap {1243srcloc: FilePos::default(),1244code_offset: 8,1245},1246InstructionAddressMap {1247srcloc: FilePos::new(code_section_offset + 17),1248code_offset: 15,1249},1250InstructionAddressMap {1251srcloc: FilePos::default(),1252code_offset: 23,1253},1254]1255.into(),1256start_srcloc: FilePos::new(code_section_offset + 10),1257end_srcloc: FilePos::new(code_section_offset + 20),1258body_offset: 0,1259body_len: 30,1260},1261..Default::default()1262};1263module_map.push(&func);1264let fi = WasmFileInfo {1265code_section_offset: code_section_offset.into(),1266funcs: Vec::new(),1267imported_func_count: 0,1268path: None,1269};1270AddressTransform::mock(&module_map, fi)1271}12721273fn create_mock_value_ranges() -> (ValueLabelsRanges, (ValueLabel, ValueLabel, ValueLabel)) {1274use cranelift_codegen::{LabelValueLoc, ValueLocRange};1275use cranelift_entity::EntityRef;1276use std::collections::HashMap;1277let mut value_ranges = HashMap::new();1278let value_0 = ValueLabel::new(0);1279let value_1 = ValueLabel::new(1);1280let value_2 = ValueLabel::new(2);1281value_ranges.insert(1282value_0,1283vec![ValueLocRange {1284loc: LabelValueLoc::CFAOffset(0),1285start: 0,1286end: 25,1287}],1288);1289value_ranges.insert(1290value_1,1291vec![ValueLocRange {1292loc: LabelValueLoc::CFAOffset(0),1293start: 5,1294end: 30,1295}],1296);1297value_ranges.insert(1298value_2,1299vec![1300ValueLocRange {1301loc: LabelValueLoc::CFAOffset(0),1302start: 0,1303end: 10,1304},1305ValueLocRange {1306loc: LabelValueLoc::CFAOffset(0),1307start: 20,1308end: 30,1309},1310],1311);1312(value_ranges, (value_0, value_1, value_2))1313}13141315#[test]1316fn test_debug_value_range_builder() {1317use super::ValueLabelRangesBuilder;1318use crate::debug::ModuleMemoryOffset;13191320// Ignore this test if cranelift doesn't support the native platform.1321if cranelift_native::builder().is_err() {1322return;1323}13241325let isa = lookup(triple!("x86_64"))1326.expect("expect x86_64 ISA")1327.finish(Flags::new(cranelift_codegen::settings::builder()))1328.expect("Creating ISA");13291330let addr_tr = create_mock_address_transform();1331let (value_ranges, value_labels) = create_mock_value_ranges();1332let fi = FunctionFrameInfo {1333memory_offset: ModuleMemoryOffset::None,1334value_ranges: &value_ranges,1335};13361337// No value labels, testing if entire function range coming through.1338let builder = ValueLabelRangesBuilder::new(&[(10, 20)], &addr_tr, Some(&fi), isa.as_ref());1339let ranges = builder.into_ranges().ranges.collect::<Vec<_>>();1340assert_eq!(ranges.len(), 1);1341assert_eq!(ranges[0].func_index, 0);1342assert_eq!(ranges[0].start, 0);1343assert_eq!(ranges[0].end, 30);13441345// Two labels ([email protected] and [email protected]), their common lifetime intersect at 5..25.1346let mut builder =1347ValueLabelRangesBuilder::new(&[(10, 20)], &addr_tr, Some(&fi), isa.as_ref());1348builder.process_label(value_labels.0);1349builder.process_label(value_labels.1);1350let ranges = builder.into_ranges().ranges.collect::<Vec<_>>();1351assert_eq!(ranges.len(), 1);1352assert_eq!(ranges[0].start, 5);1353assert_eq!(ranges[0].end, 25);13541355// Adds val2 with complex lifetime @0..10 and @20..30 to the previous test, and1356// also narrows range.1357let mut builder =1358ValueLabelRangesBuilder::new(&[(11, 17)], &addr_tr, Some(&fi), isa.as_ref());1359builder.process_label(value_labels.0);1360builder.process_label(value_labels.1);1361builder.process_label(value_labels.2);1362let ranges = builder.into_ranges().ranges.collect::<Vec<_>>();1363// Result is two ranges @5..10 and @20..231364assert_eq!(ranges.len(), 2);1365assert_eq!(ranges[0].start, 5);1366assert_eq!(ranges[0].end, 10);1367assert_eq!(ranges[1].start, 20);1368assert_eq!(ranges[1].end, 23);1369}1370}137113721373