Path: blob/main/crates/cranelift/src/debug/transform/expression.rs
3064 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 core::fmt;8use cranelift_codegen::LabelValueLoc;9use cranelift_codegen::ValueLabelsRanges;10use cranelift_codegen::ir::ValueLabel;11use cranelift_codegen::isa::TargetIsa;12use gimli::{Expression, Operation, Reader, ReaderOffset, write};13use itertools::Itertools;14use std::cmp::PartialEq;15use std::collections::{HashMap, HashSet};16use std::hash::{Hash, Hasher};17use std::rc::Rc;18use wasmtime_environ::error::{Context, Error, Result};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 { .. }650| Operation::VariableValue { .. }651| Operation::Uninitialized => {652return Ok(None);653}654Operation::WasmGlobal { index: _ } | Operation::WasmStack { index: _ } => {655// TODO support those two656return Ok(None);657}658}659let chunk = &buf[pos..pc.offset_from(&expr.0).into_u64() as usize];660code_chunk.extend_from_slice(chunk);661}662663flush_code_chunk!();664if let Some(marker) = jump_targets.get(&0) {665parts.push(CompiledExpressionPart::LandingPad(marker.clone()));666}667668Ok(Some(CompiledExpression { parts, need_deref }))669}670671#[derive(Debug, Clone)]672struct CachedValueLabelRange {673func_index: usize,674start: usize,675end: usize,676label_location: HashMap<ValueLabel, LabelValueLoc>,677}678679struct BuiltRangeSummary<'a> {680range: &'a CachedValueLabelRange,681isa: &'a dyn TargetIsa,682}683684impl<'a> fmt::Debug for BuiltRangeSummary<'a> {685fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {686let range = self.range;687write!(f, "[")?;688let mut is_first = true;689for (value, value_loc) in &range.label_location {690if !is_first {691write!(f, ", ")?;692} else {693is_first = false;694}695write!(696f,697"{:?}:{:?}",698log_get_value_name(*value),699log_get_value_loc(*value_loc, self.isa)700)?;701}702write!(f, "]@[{}..{})", range.start, range.end)?;703Ok(())704}705}706707struct ValueLabelRangesBuilder<'a, 'b> {708isa: &'a dyn TargetIsa,709ranges: Vec<CachedValueLabelRange>,710frame_info: Option<&'a FunctionFrameInfo<'b>>,711processed_labels: HashSet<ValueLabel>,712covers_entire_scope: bool,713}714715struct BuiltValueLabelRanges<TIter> {716ranges: TIter,717covers_entire_scope: bool,718}719720impl<'a, 'b> ValueLabelRangesBuilder<'a, 'b> {721pub fn new(722scope: &[(u64, u64)], // wasm ranges723addr_tr: &'a AddressTransform,724frame_info: Option<&'a FunctionFrameInfo<'b>>,725isa: &'a dyn TargetIsa,726) -> Self {727let mut ranges = Vec::new();728for (wasm_start, wasm_end) in scope {729if let Some((func_index, tr)) = addr_tr.translate_ranges_raw(*wasm_start, *wasm_end) {730ranges.extend(tr.into_iter().map(|(start, end)| CachedValueLabelRange {731func_index,732start,733end,734label_location: HashMap::new(),735}));736}737}738ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start));739740dbi_log!(741"Building ranges for values in scope: {}\n{:?}",742ranges743.iter()744.map(|r| format!("[{}..{})", r.start, r.end))745.join(" "),746log_get_value_ranges(frame_info.map(|f| f.value_ranges), isa)747);748ValueLabelRangesBuilder {749isa,750ranges,751frame_info,752processed_labels: HashSet::new(),753covers_entire_scope: true,754}755}756757fn process_label(&mut self, label: ValueLabel) {758if self.processed_labels.contains(&label) {759return;760}761dbi_log!("Intersecting with {:?}", log_get_value_name(label));762self.processed_labels.insert(label);763764let value_ranges = match self.frame_info.and_then(|fi| fi.value_ranges.get(&label)) {765Some(value_ranges) => value_ranges,766None => {767return;768}769};770771let ranges = &mut self.ranges;772for value_range in value_ranges {773let range_start = value_range.start as usize;774let range_end = value_range.end as usize;775let loc = value_range.loc;776if range_start == range_end {777continue;778}779assert!(range_start < range_end);780781// Find acceptable scope of ranges to intersect with.782let i = match ranges.binary_search_by(|s| s.start.cmp(&range_start)) {783Ok(i) => i,784Err(i) => {785if i > 0 && range_start < ranges[i - 1].end {786i - 1787} else {788i789}790}791};792let j = match ranges.binary_search_by(|s| s.start.cmp(&range_end)) {793Ok(i) | Err(i) => i,794};795// Starting from the end, intersect (range_start..range_end) with796// self.ranges array.797for i in (i..j).rev() {798if range_end <= ranges[i].start || ranges[i].end <= range_start {799continue;800}801if range_end < ranges[i].end {802// Cutting some of the range from the end.803let mut tail = ranges[i].clone();804ranges[i].end = range_end;805tail.start = range_end;806ranges.insert(i + 1, tail);807self.covers_entire_scope = false;808}809assert!(ranges[i].end <= range_end);810if range_start <= ranges[i].start {811ranges[i].label_location.insert(label, loc);812continue;813}814// Cutting some of the range from the start.815let mut tail = ranges[i].clone();816ranges[i].end = range_start;817tail.start = range_start;818tail.label_location.insert(label, loc);819ranges.insert(i + 1, tail);820self.covers_entire_scope = false;821}822}823}824825pub fn into_ranges(826self,827) -> BuiltValueLabelRanges<impl Iterator<Item = CachedValueLabelRange> + use<>> {828// Ranges with not-enough labels are discarded.829let processed_labels_len = self.processed_labels.len();830let is_valid_range =831move |r: &CachedValueLabelRange| r.label_location.len() == processed_labels_len;832833if dbi_log_enabled!() {834dbi_log!("Built ranges:");835for range in self.ranges.iter().filter(|r| is_valid_range(*r)) {836dbi_log!(837"{:?}",838BuiltRangeSummary {839range,840isa: self.isa841}842);843}844dbi_log!("");845}846847BuiltValueLabelRanges {848ranges: self.ranges.into_iter().filter(is_valid_range),849covers_entire_scope: self.covers_entire_scope,850}851}852}853854/// Marker for tracking incoming jumps.855/// Different when created new, and the same when cloned.856#[derive(Clone, Eq)]857struct JumpTargetMarker(Rc<u32>);858859impl JumpTargetMarker {860fn new() -> JumpTargetMarker {861// Create somewhat unique hash data -- using part of862// the pointer of the RcBox.863let mut rc = Rc::new(0);864let hash_data = rc.as_ref() as *const u32 as usize as u32;865*Rc::get_mut(&mut rc).unwrap() = hash_data;866JumpTargetMarker(rc)867}868}869870impl PartialEq for JumpTargetMarker {871fn eq(&self, other: &JumpTargetMarker) -> bool {872Rc::ptr_eq(&self.0, &other.0)873}874}875876impl Hash for JumpTargetMarker {877fn hash<H: Hasher>(&self, hasher: &mut H) {878hasher.write_u32(*self.0);879}880}881impl std::fmt::Debug for JumpTargetMarker {882fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {883write!(884f,885"JumpMarker<{:08x}>",886self.0.as_ref() as *const u32 as usize887)888}889}890891#[cfg(test)]892#[expect(trivial_numeric_casts, reason = "macro-generated code")]893mod tests {894use super::{895AddressTransform, CompiledExpression, CompiledExpressionPart, FunctionFrameInfo,896JumpTargetMarker, ValueLabel, ValueLabelsRanges, compile_expression,897};898use crate::CompiledFunctionMetadata;899use cranelift_codegen::{isa::lookup, settings::Flags};900use gimli::{Encoding, EndianSlice, Expression, RunTimeEndian, constants};901use target_lexicon::triple;902use wasmtime_environ::FilePos;903904macro_rules! dw_op {905(DW_OP_WASM_location) => {9060xed907};908($i:literal) => {909$i910};911($d:ident) => {912constants::$d.0 as u8913};914($e:expr) => {915$e as u8916};917}918919macro_rules! expression {920($($t:tt),*) => {921Expression(EndianSlice::new(922&[$(dw_op!($t)),*],923RunTimeEndian::Little,924))925}926}927928fn find_jump_targets<'a>(ce: &'a CompiledExpression) -> Vec<&'a JumpTargetMarker> {929ce.parts930.iter()931.filter_map(|p| {932if let CompiledExpressionPart::LandingPad(t) = p {933Some(t)934} else {935None936}937})938.collect::<Vec<_>>()939}940941static DWARF_ENCODING: Encoding = Encoding {942address_size: 4,943format: gimli::Format::Dwarf32,944version: 4,945};946947#[test]948fn test_debug_expression_jump_target() {949let m1 = JumpTargetMarker::new();950let m2 = JumpTargetMarker::new();951assert!(m1 != m2);952assert!(m1 == m1.clone());953954// Internal hash_data test (theoretically can fail intermittently).955assert!(m1.0 != m2.0);956}957958#[test]959fn test_debug_parse_expressions() {960use cranelift_entity::EntityRef;961962let (val1, val3, val20) = (ValueLabel::new(1), ValueLabel::new(3), ValueLabel::new(20));963964let e = expression!(DW_OP_WASM_location, 0x0, 20, DW_OP_stack_value);965let ce = compile_expression(&e, DWARF_ENCODING, None)966.expect("non-error")967.expect("expression");968assert_eq!(969ce,970CompiledExpression {971parts: vec![CompiledExpressionPart::Local {972label: val20,973trailing: true974}],975need_deref: false,976}977);978979let e = expression!(980DW_OP_WASM_location,9810x0,9821,983DW_OP_plus_uconst,9840x10,985DW_OP_stack_value986);987let ce = compile_expression(&e, DWARF_ENCODING, None)988.expect("non-error")989.expect("expression");990assert_eq!(991ce,992CompiledExpression {993parts: vec![994CompiledExpressionPart::Local {995label: val1,996trailing: false997},998CompiledExpressionPart::Code(vec![35, 16, 159])999],1000need_deref: false,1001}1002);10031004let e = expression!(DW_OP_WASM_location, 0x0, 3, DW_OP_stack_value);1005let fe = compile_expression(&e, DWARF_ENCODING, None).expect("non-error");1006let e = expression!(DW_OP_fbreg, 0x12);1007let ce = compile_expression(&e, DWARF_ENCODING, fe.as_ref())1008.expect("non-error")1009.expect("expression");1010assert_eq!(1011ce,1012CompiledExpression {1013parts: vec![1014CompiledExpressionPart::Local {1015label: val3,1016trailing: false1017},1018CompiledExpressionPart::Code(vec![35, 18])1019],1020need_deref: true,1021}1022);10231024let e = expression!(1025DW_OP_WASM_location,10260x0,10271,1028DW_OP_plus_uconst,10295,1030DW_OP_deref,1031DW_OP_stack_value1032);1033let ce = compile_expression(&e, DWARF_ENCODING, None)1034.expect("non-error")1035.expect("expression");1036assert_eq!(1037ce,1038CompiledExpression {1039parts: vec![1040CompiledExpressionPart::Local {1041label: val1,1042trailing: false1043},1044CompiledExpressionPart::Code(vec![35, 5]),1045CompiledExpressionPart::Deref,1046CompiledExpressionPart::Code(vec![6, 159])1047],1048need_deref: false,1049}1050);10511052let e = expression!(1053DW_OP_WASM_location,10540x0,10551,1056DW_OP_lit16,1057DW_OP_shra,1058DW_OP_stack_value1059);1060let ce = compile_expression(&e, DWARF_ENCODING, None)1061.expect("non-error")1062.expect("expression");1063assert_eq!(1064ce,1065CompiledExpression {1066parts: vec![1067CompiledExpressionPart::Local {1068label: val1,1069trailing: false1070},1071CompiledExpressionPart::Code(vec![64, 35, 32, 22, 8, 32, 36, 22, 38, 159])1072],1073need_deref: false,1074}1075);10761077let e = expression!(1078DW_OP_lit1,1079DW_OP_dup,1080DW_OP_WASM_location,10810x0,10821,1083DW_OP_and,1084DW_OP_bra,10855,10860, // --> pointer1087DW_OP_swap,1088DW_OP_shr,1089DW_OP_skip,10902,10910, // --> done1092// pointer:1093DW_OP_plus,1094DW_OP_deref,1095// done:1096DW_OP_stack_value1097);1098let ce = compile_expression(&e, DWARF_ENCODING, None)1099.expect("non-error")1100.expect("expression");1101let targets = find_jump_targets(&ce);1102assert_eq!(targets.len(), 2);1103assert_eq!(1104ce,1105CompiledExpression {1106parts: vec![1107CompiledExpressionPart::Code(vec![49, 18]),1108CompiledExpressionPart::Local {1109label: val1,1110trailing: false1111},1112CompiledExpressionPart::Code(vec![26]),1113CompiledExpressionPart::Jump {1114conditionally: true,1115target: targets[0].clone(),1116},1117CompiledExpressionPart::Code(vec![22, 35, 32, 22, 8, 32, 36, 22, 37]),1118CompiledExpressionPart::Jump {1119conditionally: false,1120target: targets[1].clone(),1121},1122CompiledExpressionPart::LandingPad(targets[0].clone()), // capture from1123CompiledExpressionPart::Code(vec![34]),1124CompiledExpressionPart::Deref,1125CompiledExpressionPart::Code(vec![6]),1126CompiledExpressionPart::LandingPad(targets[1].clone()), // capture to1127CompiledExpressionPart::Code(vec![159])1128],1129need_deref: false,1130}1131);11321133let e = expression!(1134DW_OP_lit1,1135DW_OP_dup,1136DW_OP_bra,11372,11380, // --> target1139DW_OP_deref,1140DW_OP_lit0,1141// target:1142DW_OP_stack_value1143);1144let ce = compile_expression(&e, DWARF_ENCODING, None)1145.expect("non-error")1146.expect("expression");1147let targets = find_jump_targets(&ce);1148assert_eq!(targets.len(), 1);1149assert_eq!(1150ce,1151CompiledExpression {1152parts: vec![1153CompiledExpressionPart::Code(vec![49, 18]),1154CompiledExpressionPart::Jump {1155conditionally: true,1156target: targets[0].clone(),1157},1158CompiledExpressionPart::Deref,1159CompiledExpressionPart::Code(vec![6, 48]),1160CompiledExpressionPart::LandingPad(targets[0].clone()), // capture to1161CompiledExpressionPart::Code(vec![159])1162],1163need_deref: false,1164}1165);11661167let e = expression!(1168DW_OP_lit1,1169/* loop */ DW_OP_dup,1170DW_OP_lit25,1171DW_OP_ge,1172DW_OP_bra,11735,11740, // --> done1175DW_OP_plus_uconst,11761,1177DW_OP_skip,1178(-11_i8),1179(!0), // --> loop1180/* done */ DW_OP_stack_value1181);1182let ce = compile_expression(&e, DWARF_ENCODING, None)1183.expect("non-error")1184.expect("expression");1185let targets = find_jump_targets(&ce);1186assert_eq!(targets.len(), 2);1187assert_eq!(1188ce,1189CompiledExpression {1190parts: vec![1191CompiledExpressionPart::Code(vec![49]),1192CompiledExpressionPart::LandingPad(targets[0].clone()),1193CompiledExpressionPart::Code(vec![18, 73, 42]),1194CompiledExpressionPart::Jump {1195conditionally: true,1196target: targets[1].clone(),1197},1198CompiledExpressionPart::Code(vec![35, 1]),1199CompiledExpressionPart::Jump {1200conditionally: false,1201target: targets[0].clone(),1202},1203CompiledExpressionPart::LandingPad(targets[1].clone()),1204CompiledExpressionPart::Code(vec![159])1205],1206need_deref: false,1207}1208);12091210let e = expression!(DW_OP_WASM_location, 0x0, 1, DW_OP_plus_uconst, 5);1211let ce = compile_expression(&e, DWARF_ENCODING, None)1212.expect("non-error")1213.expect("expression");1214assert_eq!(1215ce,1216CompiledExpression {1217parts: vec![1218CompiledExpressionPart::Local {1219label: val1,1220trailing: false1221},1222CompiledExpressionPart::Code(vec![35, 5])1223],1224need_deref: true,1225}1226);1227}12281229fn create_mock_address_transform() -> AddressTransform {1230use crate::FunctionAddressMap;1231use cranelift_entity::PrimaryMap;1232use wasmtime_environ::InstructionAddressMap;1233use wasmtime_environ::WasmFileInfo;12341235let mut module_map = PrimaryMap::new();1236let code_section_offset: u32 = 100;1237let func = CompiledFunctionMetadata {1238address_map: FunctionAddressMap {1239instructions: vec![1240InstructionAddressMap {1241srcloc: FilePos::new(code_section_offset + 12),1242code_offset: 5,1243},1244InstructionAddressMap {1245srcloc: FilePos::default(),1246code_offset: 8,1247},1248InstructionAddressMap {1249srcloc: FilePos::new(code_section_offset + 17),1250code_offset: 15,1251},1252InstructionAddressMap {1253srcloc: FilePos::default(),1254code_offset: 23,1255},1256]1257.into(),1258start_srcloc: FilePos::new(code_section_offset + 10),1259end_srcloc: FilePos::new(code_section_offset + 20),1260body_offset: 0,1261body_len: 30,1262},1263..Default::default()1264};1265module_map.push(&func);1266let fi = WasmFileInfo {1267code_section_offset: code_section_offset.into(),1268funcs: Vec::new(),1269imported_func_count: 0,1270path: None,1271};1272AddressTransform::mock(&module_map, fi)1273}12741275fn create_mock_value_ranges() -> (ValueLabelsRanges, (ValueLabel, ValueLabel, ValueLabel)) {1276use cranelift_codegen::{LabelValueLoc, ValueLocRange};1277use cranelift_entity::EntityRef;1278use std::collections::HashMap;1279let mut value_ranges = HashMap::new();1280let value_0 = ValueLabel::new(0);1281let value_1 = ValueLabel::new(1);1282let value_2 = ValueLabel::new(2);1283value_ranges.insert(1284value_0,1285vec![ValueLocRange {1286loc: LabelValueLoc::CFAOffset(0),1287start: 0,1288end: 25,1289}],1290);1291value_ranges.insert(1292value_1,1293vec![ValueLocRange {1294loc: LabelValueLoc::CFAOffset(0),1295start: 5,1296end: 30,1297}],1298);1299value_ranges.insert(1300value_2,1301vec![1302ValueLocRange {1303loc: LabelValueLoc::CFAOffset(0),1304start: 0,1305end: 10,1306},1307ValueLocRange {1308loc: LabelValueLoc::CFAOffset(0),1309start: 20,1310end: 30,1311},1312],1313);1314(value_ranges, (value_0, value_1, value_2))1315}13161317#[test]1318fn test_debug_value_range_builder() {1319use super::ValueLabelRangesBuilder;1320use crate::debug::ModuleMemoryOffset;13211322// Ignore this test if cranelift doesn't support the native platform.1323if cranelift_native::builder().is_err() {1324return;1325}13261327let isa = lookup(triple!("x86_64"))1328.expect("expect x86_64 ISA")1329.finish(Flags::new(cranelift_codegen::settings::builder()))1330.expect("Creating ISA");13311332let addr_tr = create_mock_address_transform();1333let (value_ranges, value_labels) = create_mock_value_ranges();1334let fi = FunctionFrameInfo {1335memory_offset: ModuleMemoryOffset::None,1336value_ranges: &value_ranges,1337};13381339// No value labels, testing if entire function range coming through.1340let builder = ValueLabelRangesBuilder::new(&[(10, 20)], &addr_tr, Some(&fi), isa.as_ref());1341let ranges = builder.into_ranges().ranges.collect::<Vec<_>>();1342assert_eq!(ranges.len(), 1);1343assert_eq!(ranges[0].func_index, 0);1344assert_eq!(ranges[0].start, 0);1345assert_eq!(ranges[0].end, 30);13461347// Two labels ([email protected] and [email protected]), their common lifetime intersect at 5..25.1348let mut builder =1349ValueLabelRangesBuilder::new(&[(10, 20)], &addr_tr, Some(&fi), isa.as_ref());1350builder.process_label(value_labels.0);1351builder.process_label(value_labels.1);1352let ranges = builder.into_ranges().ranges.collect::<Vec<_>>();1353assert_eq!(ranges.len(), 1);1354assert_eq!(ranges[0].start, 5);1355assert_eq!(ranges[0].end, 25);13561357// Adds val2 with complex lifetime @0..10 and @20..30 to the previous test, and1358// also narrows range.1359let mut builder =1360ValueLabelRangesBuilder::new(&[(11, 17)], &addr_tr, Some(&fi), isa.as_ref());1361builder.process_label(value_labels.0);1362builder.process_label(value_labels.1);1363builder.process_label(value_labels.2);1364let ranges = builder.into_ranges().ranges.collect::<Vec<_>>();1365// Result is two ranges @5..10 and @20..231366assert_eq!(ranges.len(), 2);1367assert_eq!(ranges[0].start, 5);1368assert_eq!(ranges[0].end, 10);1369assert_eq!(ranges[1].start, 20);1370assert_eq!(ranges[1].end, 23);1371}1372}137313741375