use llvm_native_core::codegen::{
MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand, PhysReg, VirtReg,
};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
pub const STACK_MAP_VERSION: u8 = 3;
pub const STACK_MAP_RESERVED1: u8 = 0;
pub const STACK_MAP_RESERVED2: u16 = 0;
pub const STACK_MAP_SECTION_NAME: &str = ".llvm_stackmaps";
pub const MAX_LOCATIONS_PER_RECORD: usize = 256;
pub const MAX_LIVE_OUT_REGS: usize = 16;
pub const MAX_CONSTANT_ENTRIES: usize = 1024;
pub const PATCHABLE_NOP_SLED_SIZE: usize = 64;
pub const PATCHABLE_CALL_SEQUENCE_SIZE: usize = 16;
pub const PATCHABLE_JUMP_SEQUENCE_SIZE: usize = 10;
pub const POLLING_PAGE_ADDRESS: u64 = 0x7FFF_FFFF_0000u64;
pub const DWARF_REG_RSP: u16 = 7;
pub const DWARF_REG_RBP: u16 = 6;
pub const DWARF_REG_RIP: u16 = 16;
pub const DWARF_REG_RAX: u16 = 0;
pub const MAX_FRAME_SIZE: u64 = 0xFFFF_FFFFu64;
pub const DEOPT_BUNDLE_MAGIC: u32 = 0xDE0B_DE0B;
pub const DEOPT_BUNDLE_VERSION: u16 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum LocationKind {
Register = 1,
Direct = 2,
Indirect = 3,
Constant = 4,
ConstantIndex = 5,
VectorSplice = 6,
}
impl LocationKind {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
1 => Some(LocationKind::Register),
2 => Some(LocationKind::Direct),
3 => Some(LocationKind::Indirect),
4 => Some(LocationKind::Constant),
5 => Some(LocationKind::ConstantIndex),
6 => Some(LocationKind::VectorSplice),
_ => None,
}
}
pub fn name(&self) -> &'static str {
match self {
LocationKind::Register => "Register",
LocationKind::Direct => "Direct",
LocationKind::Indirect => "Indirect",
LocationKind::Constant => "Constant",
LocationKind::ConstantIndex => "ConstantIndex",
LocationKind::VectorSplice => "VectorSplice",
}
}
pub fn is_register(&self) -> bool {
matches!(self, LocationKind::Register | LocationKind::VectorSplice)
}
pub fn is_stack(&self) -> bool {
matches!(self, LocationKind::Direct | LocationKind::Indirect)
}
pub fn is_constant(&self) -> bool {
matches!(self, LocationKind::Constant | LocationKind::ConstantIndex)
}
}
impl std::fmt::Display for LocationKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SafepointKind {
FunctionEntry,
LoopBackedge,
AfterCall,
Statepoint,
CooperativeSuspend,
FunctionExit,
}
impl SafepointKind {
pub fn name(&self) -> &'static str {
match self {
SafepointKind::FunctionEntry => "function-entry",
SafepointKind::LoopBackedge => "loop-backedge",
SafepointKind::AfterCall => "after-call",
SafepointKind::Statepoint => "statepoint",
SafepointKind::CooperativeSuspend => "cooperative-suspend",
SafepointKind::FunctionExit => "function-exit",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PatchableType {
Call,
Jump,
Region,
Trampoline,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeoptReason {
TypeCheckFailed { expected: String, actual: String },
BoundsCheckFailed { index: i64, length: i64 },
NullCheckFailed,
SpeculationFailed { guard_id: u32 },
CHAMiss,
InlineCacheMiss,
ExplicitDeopt { reason: String },
DebugDeopt,
Other(String),
}
impl DeoptReason {
pub fn description(&self) -> String {
match self {
DeoptReason::TypeCheckFailed { expected, actual } => {
format!("Type check failed: expected {}, got {}", expected, actual)
}
DeoptReason::BoundsCheckFailed { index, length } => {
format!(
"Bounds check failed: index {} out of bounds [0, {})",
index, length
)
}
DeoptReason::NullCheckFailed => "Null check failed".to_string(),
DeoptReason::SpeculationFailed { guard_id } => {
format!("Speculation guard {} failed", guard_id)
}
DeoptReason::CHAMiss => "Class hierarchy analysis miss".to_string(),
DeoptReason::InlineCacheMiss => "Inline cache miss".to_string(),
DeoptReason::ExplicitDeopt { reason } => {
format!("Explicit deoptimization: {}", reason)
}
DeoptReason::DebugDeopt => "Debug deoptimization".to_string(),
DeoptReason::Other(s) => s.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86RegisterNames {
names_64: HashMap<u16, String>,
names_32: HashMap<u16, String>,
use_64bit: bool,
}
impl X86RegisterNames {
pub fn new(use_64bit: bool) -> Self {
let mut names_64 = HashMap::new();
let mut names_32 = HashMap::new();
names_64.insert(0, "RAX".to_string());
names_64.insert(1, "RDX".to_string());
names_64.insert(2, "RCX".to_string());
names_64.insert(3, "RBX".to_string());
names_64.insert(4, "RSI".to_string());
names_64.insert(5, "RDI".to_string());
names_64.insert(6, "RBP".to_string());
names_64.insert(7, "RSP".to_string());
names_64.insert(8, "R8".to_string());
names_64.insert(9, "R9".to_string());
names_64.insert(10, "R10".to_string());
names_64.insert(11, "R11".to_string());
names_64.insert(12, "R12".to_string());
names_64.insert(13, "R13".to_string());
names_64.insert(14, "R14".to_string());
names_64.insert(15, "R15".to_string());
names_64.insert(16, "RIP".to_string());
names_32.insert(0, "EAX".to_string());
names_32.insert(1, "EDX".to_string());
names_32.insert(2, "ECX".to_string());
names_32.insert(3, "EBX".to_string());
names_32.insert(4, "ESI".to_string());
names_32.insert(5, "EDI".to_string());
names_32.insert(6, "EBP".to_string());
names_32.insert(7, "ESP".to_string());
names_32.insert(8, "R8D".to_string());
names_32.insert(9, "R9D".to_string());
names_32.insert(10, "R10D".to_string());
names_32.insert(11, "R11D".to_string());
names_32.insert(12, "R12D".to_string());
names_32.insert(13, "R13D".to_string());
names_32.insert(14, "R14D".to_string());
names_32.insert(15, "R15D".to_string());
names_32.insert(16, "EIP".to_string());
for i in 0..32u16 {
let idx = i + 17;
let name = if i < 16 {
format!("XMM{}", i)
} else {
format!("XMM{}", i)
};
names_64.insert(idx, name.clone());
names_32.insert(idx, name);
}
for i in 0..32u16 {
let idx = i + 49;
let name = format!("YMM{}", i);
names_64.insert(idx, name.clone());
names_32.insert(idx, name);
}
for i in 0..32u16 {
let idx = i + 81;
let name = format!("ZMM{}", i);
names_64.insert(idx, name.clone());
names_32.insert(idx, name);
}
names_64.insert(49, "EFLAGS".to_string());
names_32.insert(49, "EFLAGS".to_string());
names_64.insert(50, "ES".to_string());
names_64.insert(51, "CS".to_string());
names_64.insert(52, "SS".to_string());
names_64.insert(53, "DS".to_string());
names_64.insert(54, "FS".to_string());
names_64.insert(55, "GS".to_string());
names_32.insert(50, "ES".to_string());
names_32.insert(51, "CS".to_string());
names_32.insert(52, "SS".to_string());
names_32.insert(53, "DS".to_string());
names_32.insert(54, "FS".to_string());
names_32.insert(55, "GS".to_string());
X86RegisterNames {
names_64,
names_32,
use_64bit,
}
}
pub fn resolve(&self, dwarf_reg: u16) -> String {
if self.use_64bit {
self.names_64
.get(&dwarf_reg)
.cloned()
.unwrap_or_else(|| format!("Reg({})", dwarf_reg))
} else {
self.names_32
.get(&dwarf_reg)
.cloned()
.unwrap_or_else(|| format!("Reg({})", dwarf_reg))
}
}
pub fn set_64bit(&mut self, use_64bit: bool) {
self.use_64bit = use_64bit;
}
pub fn is_gpr(&self, dwarf_reg: u16) -> bool {
dwarf_reg <= 15
}
pub fn is_xmm(&self, dwarf_reg: u16) -> bool {
(17..49).contains(&dwarf_reg)
}
pub fn is_ymm(&self, dwarf_reg: u16) -> bool {
(49..81).contains(&dwarf_reg)
}
pub fn is_zmm(&self, dwarf_reg: u16) -> bool {
(81..113).contains(&dwarf_reg)
}
pub fn is_caller_saved(&self, dwarf_reg: u16) -> bool {
matches!(
dwarf_reg,
0 | 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 ) || self.is_xmm(dwarf_reg)
}
pub fn is_callee_saved(&self, dwarf_reg: u16) -> bool {
matches!(
dwarf_reg,
3 | 6 | 7 | 12 | 13 | 14 | 15 )
}
}
impl Default for X86RegisterNames {
fn default() -> Self {
X86RegisterNames::new(true)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StackMapHeader {
pub version: u8,
pub reserved1: u8,
pub reserved2: u16,
pub reserved3: u32,
}
impl StackMapHeader {
pub fn new(version: u8) -> Self {
StackMapHeader {
version,
reserved1: 0,
reserved2: 0,
reserved3: 0,
}
}
pub fn current() -> Self {
StackMapHeader::new(STACK_MAP_VERSION)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(8);
buf.push(self.version);
buf.push(self.reserved1);
buf.extend_from_slice(&self.reserved2.to_le_bytes());
buf.extend_from_slice(&self.reserved3.to_le_bytes());
buf
}
pub fn from_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut version_buf = [0u8; 1];
reader.read_exact(&mut version_buf)?;
let version = version_buf[0];
let mut reserved1_buf = [0u8; 1];
reader.read_exact(&mut reserved1_buf)?;
let reserved1 = reserved1_buf[0];
let mut reserved2_buf = [0u8; 2];
reader.read_exact(&mut reserved2_buf)?;
let reserved2 = u16::from_le_bytes(reserved2_buf);
let mut reserved3_buf = [0u8; 4];
reader.read_exact(&mut reserved3_buf)?;
let reserved3 = u32::from_le_bytes(reserved3_buf);
Ok(StackMapHeader {
version,
reserved1,
reserved2,
reserved3,
})
}
pub fn validate(&self) -> Result<(), String> {
if self.version != STACK_MAP_VERSION {
return Err(format!(
"Unsupported stack map version: {} (expected {})",
self.version, STACK_MAP_VERSION
));
}
if self.reserved1 != 0 {
return Err(format!(
"Invalid reserved byte 1: {} (expected 0)",
self.reserved1
));
}
if self.reserved2 != 0 {
return Err(format!(
"Invalid reserved word 2: {} (expected 0)",
self.reserved2
));
}
Ok(())
}
}
impl Default for StackMapHeader {
fn default() -> Self {
StackMapHeader::current()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StackMapFunctionRecord {
pub function_address: u64,
pub stack_size: u64,
pub stack_size_count: u32,
pub record_count: u32,
}
impl StackMapFunctionRecord {
pub fn new(
function_address: u64,
stack_size: u64,
stack_size_count: u32,
record_count: u32,
) -> Self {
StackMapFunctionRecord {
function_address,
stack_size,
stack_size_count,
record_count,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(24);
buf.extend_from_slice(&self.function_address.to_le_bytes());
buf.extend_from_slice(&self.stack_size.to_le_bytes());
let packed: u64 = (self.record_count as u64) << 32 | (self.stack_size_count as u64);
buf.extend_from_slice(&packed.to_le_bytes());
buf
}
pub fn from_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut addr_buf = [0u8; 8];
reader.read_exact(&mut addr_buf)?;
let function_address = u64::from_le_bytes(addr_buf);
let mut stack_buf = [0u8; 8];
reader.read_exact(&mut stack_buf)?;
let stack_size = u64::from_le_bytes(stack_buf);
let mut packed_buf = [0u8; 8];
reader.read_exact(&mut packed_buf)?;
let packed = u64::from_le_bytes(packed_buf);
let stack_size_count = (packed & 0xFFFF_FFFF) as u32;
let record_count = ((packed >> 32) & 0xFFFF_FFFF) as u32;
Ok(StackMapFunctionRecord {
function_address,
stack_size,
stack_size_count,
record_count,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StackSizeRecord {
pub function_offset: u64,
pub stack_size: u64,
}
impl StackSizeRecord {
pub fn new(function_offset: u64, stack_size: u64) -> Self {
StackSizeRecord {
function_offset,
stack_size,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(16);
buf.extend_from_slice(&self.function_offset.to_le_bytes());
buf.extend_from_slice(&self.stack_size.to_le_bytes());
buf
}
pub fn from_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut offset_buf = [0u8; 8];
reader.read_exact(&mut offset_buf)?;
let function_offset = u64::from_le_bytes(offset_buf);
let mut size_buf = [0u8; 8];
reader.read_exact(&mut size_buf)?;
let stack_size = u64::from_le_bytes(size_buf);
Ok(StackSizeRecord {
function_offset,
stack_size,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocationRecord {
pub kind: LocationKind,
pub dwarf_reg_num: u16,
pub offset: i32,
pub size: u16,
pub reserved: u16,
}
impl LocationRecord {
pub fn new_register(dwarf_reg: u16, size: u16) -> Self {
LocationRecord {
kind: LocationKind::Register,
dwarf_reg_num: dwarf_reg,
offset: 0,
size,
reserved: 0,
}
}
pub fn new_direct(dwarf_reg: u16, offset: i32, size: u16) -> Self {
LocationRecord {
kind: LocationKind::Direct,
dwarf_reg_num: dwarf_reg,
offset,
size,
reserved: 0,
}
}
pub fn new_indirect(dwarf_reg: u16, offset: i32, size: u16) -> Self {
LocationRecord {
kind: LocationKind::Indirect,
dwarf_reg_num: dwarf_reg,
offset,
size,
reserved: 0,
}
}
pub fn new_constant(value: i32) -> Self {
LocationRecord {
kind: LocationKind::Constant,
dwarf_reg_num: 0,
offset: value,
size: 8, reserved: 0,
}
}
pub fn new_constant_index(index: i32) -> Self {
LocationRecord {
kind: LocationKind::ConstantIndex,
dwarf_reg_num: 0,
offset: index,
size: 0,
reserved: 0,
}
}
pub fn new_vector_splice(dwarf_reg: u16, offset: i32, size: u16) -> Self {
LocationRecord {
kind: LocationKind::VectorSplice,
dwarf_reg_num: dwarf_reg,
offset,
size,
reserved: 0,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(16);
let kind_byte = self.kind as u8;
buf.push(kind_byte);
buf.push(0); buf.extend_from_slice(&self.dwarf_reg_num.to_le_bytes());
buf.extend_from_slice(&self.offset.to_le_bytes());
buf.extend_from_slice(&self.size.to_le_bytes());
buf.extend_from_slice(&self.reserved.to_le_bytes());
buf
}
pub fn from_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut kind_buf = [0u8; 1];
reader.read_exact(&mut kind_buf)?;
let kind_val = kind_buf[0];
let mut pad_buf = [0u8; 1];
reader.read_exact(&mut pad_buf)?;
let mut reg_buf = [0u8; 2];
reader.read_exact(&mut reg_buf)?;
let dwarf_reg_num = u16::from_le_bytes(reg_buf);
let mut offset_buf = [0u8; 4];
reader.read_exact(&mut offset_buf)?;
let offset = i32::from_le_bytes(offset_buf);
let mut size_buf = [0u8; 2];
reader.read_exact(&mut size_buf)?;
let size = u16::from_le_bytes(size_buf);
let mut reserved_buf = [0u8; 2];
reader.read_exact(&mut reserved_buf)?;
let reserved = u16::from_le_bytes(reserved_buf);
let kind = LocationKind::from_u8(kind_val).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Unknown location kind: {}", kind_val),
)
})?;
Ok(LocationRecord {
kind,
dwarf_reg_num,
offset,
size,
reserved,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LiveOutRecord {
pub dwarf_reg_num: u16,
pub reserved: u16,
pub size: u8,
pub padding: u8,
}
impl LiveOutRecord {
pub fn new(dwarf_reg_num: u16, size: u8) -> Self {
LiveOutRecord {
dwarf_reg_num,
reserved: 0,
size,
padding: 0,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(8);
buf.extend_from_slice(&self.dwarf_reg_num.to_le_bytes());
buf.extend_from_slice(&self.reserved.to_le_bytes());
buf.push(self.size);
buf.push(self.padding);
buf.extend_from_slice(&0u32.to_le_bytes()); buf
}
pub fn from_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut reg_buf = [0u8; 2];
reader.read_exact(&mut reg_buf)?;
let dwarf_reg_num = u16::from_le_bytes(reg_buf);
let mut reserved_buf = [0u8; 2];
reader.read_exact(&mut reserved_buf)?;
let reserved = u16::from_le_bytes(reserved_buf);
let mut size_buf = [0u8; 1];
reader.read_exact(&mut size_buf)?;
let size = size_buf[0];
let mut pad_buf = [0u8; 1];
reader.read_exact(&mut pad_buf)?;
let padding = pad_buf[0];
let mut skip_buf = [0u8; 4];
reader.read_exact(&mut skip_buf)?;
Ok(LiveOutRecord {
dwarf_reg_num,
reserved,
size,
padding,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstantPoolRecord {
pub value: u64,
}
impl ConstantPoolRecord {
pub fn new(value: u64) -> Self {
ConstantPoolRecord { value }
}
pub fn to_bytes(&self) -> Vec<u8> {
self.value.to_le_bytes().to_vec()
}
pub fn from_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut buf = [0u8; 8];
reader.read_exact(&mut buf)?;
let value = u64::from_le_bytes(buf);
Ok(ConstantPoolRecord { value })
}
}
#[derive(Debug, Clone)]
pub struct StackMapRecord {
pub id: u64,
pub instruction_offset: u32,
pub reserved: u16,
pub num_locations: u16,
pub locations: Vec<LocationRecord>,
pub live_outs: Vec<LiveOutRecord>,
}
impl StackMapRecord {
pub fn new(id: u64, instruction_offset: u32) -> Self {
StackMapRecord {
id,
instruction_offset,
reserved: 0,
num_locations: 0,
locations: Vec::new(),
live_outs: Vec::new(),
}
}
pub fn add_location(&mut self, location: LocationRecord) {
self.locations.push(location);
self.num_locations = self.locations.len() as u16;
}
pub fn add_live_out(&mut self, live_out: LiveOutRecord) {
self.live_outs.push(live_out);
}
pub fn location_count(&self) -> usize {
self.locations.len()
}
pub fn live_out_count(&self) -> usize {
self.live_outs.len()
}
pub fn to_header_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(16);
buf.extend_from_slice(&self.id.to_le_bytes());
buf.extend_from_slice(&self.instruction_offset.to_le_bytes());
buf.extend_from_slice(&self.reserved.to_le_bytes());
buf.extend_from_slice(&self.num_locations.to_le_bytes());
buf
}
pub fn header_from_reader<R: Read>(reader: &mut R) -> io::Result<(u64, u32, u16, u16)> {
let mut id_buf = [0u8; 8];
reader.read_exact(&mut id_buf)?;
let id = u64::from_le_bytes(id_buf);
let mut offset_buf = [0u8; 4];
reader.read_exact(&mut offset_buf)?;
let instruction_offset = u32::from_le_bytes(offset_buf);
let mut reserved_buf = [0u8; 2];
reader.read_exact(&mut reserved_buf)?;
let reserved = u16::from_le_bytes(reserved_buf);
let mut num_buf = [0u8; 2];
reader.read_exact(&mut num_buf)?;
let num_locations = u16::from_le_bytes(num_buf);
Ok((id, instruction_offset, reserved, num_locations))
}
}
#[derive(Debug, Clone)]
pub struct X86StackMapFormat {
pub header: StackMapHeader,
pub num_functions: u32,
pub num_constants: u32,
pub functions: Vec<StackMapFunctionRecord>,
pub stack_sizes: Vec<StackSizeRecord>,
pub constants: Vec<ConstantPoolRecord>,
pub records: Vec<StackMapRecord>,
pub raw_data: Option<Vec<u8>>,
}
impl X86StackMapFormat {
pub fn new() -> Self {
X86StackMapFormat {
header: StackMapHeader::current(),
num_functions: 0,
num_constants: 0,
functions: Vec::new(),
stack_sizes: Vec::new(),
constants: Vec::new(),
records: Vec::new(),
raw_data: None,
}
}
pub fn add_function(
&mut self,
func: StackMapFunctionRecord,
sizes: Vec<StackSizeRecord>,
recs: Vec<StackMapRecord>,
) {
self.functions.push(func);
self.stack_sizes.extend(sizes);
self.records.extend(recs);
self.num_functions = self.functions.len() as u32;
}
pub fn add_constant(&mut self, value: u64) -> u32 {
let index = self.constants.len() as u32;
self.constants.push(ConstantPoolRecord::new(value));
self.num_constants = self.constants.len() as u32;
index
}
pub fn get_constant(&self, index: u32) -> Option<u64> {
self.constants.get(index as usize).map(|c| c.value)
}
pub fn find_records_by_address(&self, address: u64) -> Vec<&StackMapRecord> {
let mut record_offset = 0;
for func in &self.functions {
if func.function_address == address {
let count = func.record_count as usize;
return self.records[record_offset..record_offset + count]
.iter()
.collect();
}
record_offset += func.record_count as usize;
}
Vec::new()
}
pub fn find_record_by_id(&self, id: u64) -> Option<&StackMapRecord> {
self.records.iter().find(|r| r.id == id)
}
pub fn total_records(&self) -> usize {
self.records.len()
}
pub fn total_locations(&self) -> usize {
self.records.iter().map(|r| r.locations.len()).sum()
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&self.header.to_bytes());
buf.extend_from_slice(&self.num_functions.to_le_bytes());
buf.extend_from_slice(&self.num_constants.to_le_bytes());
let total_records = self.records.len() as u32;
buf.extend_from_slice(&total_records.to_le_bytes());
let current_len = buf.len();
if current_len % 8 != 0 {
let padding = 8 - (current_len % 8);
buf.extend_from_slice(&vec![0u8; padding]);
}
for func in &self.functions {
buf.extend_from_slice(&func.to_bytes());
}
for constant in &self.constants {
buf.extend_from_slice(&constant.to_bytes());
}
for record in &self.records {
buf.extend_from_slice(&record.to_header_bytes());
for loc in &record.locations {
buf.extend_from_slice(&loc.to_bytes());
}
let pad_len = (8 - (buf.len() % 8)) % 8;
if pad_len > 0 {
buf.extend_from_slice(&vec![0u8; pad_len]);
}
let live_out_count = record.live_outs.len() as u16;
buf.extend_from_slice(&live_out_count.to_le_bytes());
buf.extend_from_slice(&vec![0u8; 6]);
for live_out in &record.live_outs {
buf.extend_from_slice(&live_out.to_bytes());
}
}
for size_rec in &self.stack_sizes {
buf.extend_from_slice(&size_rec.to_bytes());
}
buf
}
pub fn from_bytes(data: &[u8]) -> io::Result<Self> {
let mut cursor = Cursor::new(data);
let header = StackMapHeader::from_reader(&mut cursor)?;
let mut num_func_buf = [0u8; 4];
cursor.read_exact(&mut num_func_buf)?;
let num_functions = u32::from_le_bytes(num_func_buf);
let mut num_const_buf = [0u8; 4];
cursor.read_exact(&mut num_const_buf)?;
let num_constants = u32::from_le_bytes(num_const_buf);
let mut num_rec_buf = [0u8; 4];
cursor.read_exact(&mut num_rec_buf)?;
let total_records = u32::from_le_bytes(num_rec_buf);
let pos = cursor.position() as usize;
if pos % 8 != 0 {
let pad = 8 - (pos % 8);
cursor.seek(SeekFrom::Current(pad as i64))?;
}
let mut functions = Vec::with_capacity(num_functions as usize);
for _ in 0..num_functions {
functions.push(StackMapFunctionRecord::from_reader(&mut cursor)?);
}
let mut constants = Vec::with_capacity(num_constants as usize);
for _ in 0..num_constants {
constants.push(ConstantPoolRecord::from_reader(&mut cursor)?);
}
let mut records = Vec::with_capacity(total_records as usize);
for _ in 0..total_records {
let (id, instruction_offset, reserved, num_locations) =
StackMapRecord::header_from_reader(&mut cursor)?;
let mut record = StackMapRecord::new(id, instruction_offset);
record.reserved = reserved;
record.num_locations = num_locations;
for _ in 0..num_locations {
record.add_location(LocationRecord::from_reader(&mut cursor)?);
}
let cur_pos = cursor.position() as usize;
if cur_pos % 8 != 0 {
let pad = 8 - (cur_pos % 8);
cursor.seek(SeekFrom::Current(pad as i64))?;
}
let mut live_count_buf = [0u8; 2];
cursor.read_exact(&mut live_count_buf)?;
let live_count = u16::from_le_bytes(live_count_buf);
cursor.seek(SeekFrom::Current(6))?;
for _ in 0..live_count {
record.add_live_out(LiveOutRecord::from_reader(&mut cursor)?);
}
records.push(record);
}
let mut stack_sizes = Vec::new();
let total_sizes: usize = functions.iter().map(|f| f.stack_size_count as usize).sum();
for _ in 0..total_sizes {
stack_sizes.push(StackSizeRecord::from_reader(&mut cursor)?);
}
Ok(X86StackMapFormat {
header,
num_functions,
num_constants,
functions,
stack_sizes,
constants,
records,
raw_data: Some(data.to_vec()),
})
}
pub fn validate(&self) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
if let Err(e) = self.header.validate() {
errors.push(e);
}
let expected_records: usize = self.functions.iter().map(|f| f.record_count as usize).sum();
if self.records.len() != expected_records {
errors.push(format!(
"Record count mismatch: expected {}, got {}",
expected_records,
self.records.len()
));
}
let expected_sizes: usize = self
.functions
.iter()
.map(|f| f.stack_size_count as usize)
.sum();
if self.stack_sizes.len() != expected_sizes {
errors.push(format!(
"Stack size record count mismatch: expected {}, got {}",
expected_sizes,
self.stack_sizes.len()
));
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
impl Default for X86StackMapFormat {
fn default() -> Self {
X86StackMapFormat::new()
}
}
#[derive(Debug, Clone)]
pub struct X86StackMaps {
pub format: X86StackMapFormat,
pub reg_names: X86RegisterNames,
pub next_id: u64,
pub functions_data: Vec<X86FunctionStackMapData>,
pub enabled: bool,
pub verbose: bool,
pub target_triple: String,
}
#[derive(Debug, Clone)]
pub struct X86FunctionStackMapData {
pub name: String,
pub address: u64,
pub stack_size: u64,
pub stack_sizes: Vec<StackSizeRecord>,
pub records: Vec<StackMapRecord>,
pub offset_to_id: HashMap<u32, u64>,
}
impl X86FunctionStackMapData {
pub fn new(name: &str, stack_size: u64) -> Self {
X86FunctionStackMapData {
name: name.to_string(),
address: 0,
stack_size,
stack_sizes: Vec::new(),
records: Vec::new(),
offset_to_id: HashMap::new(),
}
}
pub fn add_stack_size(&mut self, offset: u64, size: u64) {
self.stack_sizes.push(StackSizeRecord::new(offset, size));
}
pub fn add_record(&mut self, record: StackMapRecord) {
self.offset_to_id
.insert(record.instruction_offset, record.id);
self.records.push(record);
}
}
impl X86StackMaps {
pub fn new(target_triple: &str, use_64bit: bool) -> Self {
X86StackMaps {
format: X86StackMapFormat::new(),
reg_names: X86RegisterNames::new(use_64bit),
next_id: 0,
functions_data: Vec::new(),
enabled: true,
verbose: false,
target_triple: target_triple.to_string(),
}
}
pub fn allocate_id(&mut self) -> u64 {
let id = self.next_id;
self.next_id += 1;
id
}
pub fn register_function(&mut self, name: &str, stack_size: u64) -> usize {
let data = X86FunctionStackMapData::new(name, stack_size);
let index = self.functions_data.len();
self.functions_data.push(data);
index
}
pub fn add_stack_size(&mut self, func_index: usize, offset: u64, size: u64) {
if let Some(data) = self.functions_data.get_mut(func_index) {
data.add_stack_size(offset, size);
}
}
pub fn add_record(&mut self, func_index: usize, record: StackMapRecord) {
if let Some(data) = self.functions_data.get_mut(func_index) {
data.add_record(record);
}
}
pub fn finalize(&mut self) {
self.format = X86StackMapFormat::new();
for func_data in &self.functions_data {
let func_record = StackMapFunctionRecord::new(
func_data.address,
func_data.stack_size,
func_data.stack_sizes.len() as u32,
func_data.records.len() as u32,
);
self.format.add_function(
func_record,
func_data.stack_sizes.clone(),
func_data.records.clone(),
);
}
}
pub fn emit_section(&mut self) -> Vec<u8> {
self.finalize();
self.format.to_bytes()
}
pub fn emit_asm(&self) -> String {
let mut asm = String::new();
asm.push_str(&format!(
"\t.section\t{},\"\",@progbits\n",
STACK_MAP_SECTION_NAME
));
let bytes = self.format.to_bytes();
for chunk in bytes.chunks(16) {
asm.push_str("\t.byte\t");
let vals: Vec<String> = chunk.iter().map(|b| format!("{}", b)).collect();
asm.push_str(&vals.join(", "));
asm.push('\n');
}
asm
}
pub fn lookup_record(&self, id: u64) -> Option<&StackMapRecord> {
self.format.find_record_by_id(id)
}
pub fn get_function_records(&self, func_index: usize) -> Option<&[StackMapRecord]> {
self.functions_data
.get(func_index)
.map(|d| d.records.as_slice())
}
pub fn function_count(&self) -> usize {
self.functions_data.len()
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn set_verbose(&mut self, verbose: bool) {
self.verbose = verbose;
}
pub fn validate(&self) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
let mut ids: HashSet<u64> = HashSet::new();
for func_data in &self.functions_data {
for record in &func_data.records {
if !ids.insert(record.id) {
errors.push(format!("Duplicate stack map ID: {}", record.id));
}
}
}
for func_data in &self.functions_data {
for record in &func_data.records {
if record.locations.len() > MAX_LOCATIONS_PER_RECORD {
errors.push(format!(
"Too many locations in record {} ({} > {})",
record.id,
record.locations.len(),
MAX_LOCATIONS_PER_RECORD,
));
}
if record.live_outs.len() > MAX_LIVE_OUT_REGS {
errors.push(format!(
"Too many live-out regs in record {} ({} > {})",
record.id,
record.live_outs.len(),
MAX_LIVE_OUT_REGS,
));
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
impl Default for X86StackMaps {
fn default() -> Self {
X86StackMaps::new("x86_64-unknown-linux-gnu", true)
}
}
#[derive(Debug, Clone)]
pub struct X86PatchPoint {
pub id: u64,
pub patch_type: PatchableType,
pub nop_sled_size: usize,
pub target: Option<String>,
pub return_type: Option<String>,
pub num_args: u32,
pub calling_convention: String,
pub metadata: Vec<u8>,
pub is_stackmap_relative: bool,
}
impl X86PatchPoint {
pub fn new_call(id: u64, target: &str, num_args: u32) -> Self {
X86PatchPoint {
id,
patch_type: PatchableType::Call,
nop_sled_size: PATCHABLE_NOP_SLED_SIZE,
target: Some(target.to_string()),
return_type: None,
num_args,
calling_convention: "anyregcc".to_string(),
metadata: Vec::new(),
is_stackmap_relative: false,
}
}
pub fn new_typed_call(id: u64, target: &str, num_args: u32, ret_type: &str) -> Self {
X86PatchPoint {
id,
patch_type: PatchableType::Call,
nop_sled_size: PATCHABLE_NOP_SLED_SIZE,
target: Some(target.to_string()),
return_type: Some(ret_type.to_string()),
num_args,
calling_convention: "anyregcc".to_string(),
metadata: Vec::new(),
is_stackmap_relative: false,
}
}
pub fn new_void_call(id: u64, target: &str, num_args: u32) -> Self {
X86PatchPoint {
id,
patch_type: PatchableType::Call,
nop_sled_size: PATCHABLE_NOP_SLED_SIZE,
target: Some(target.to_string()),
return_type: None,
num_args,
calling_convention: "anyregcc".to_string(),
metadata: Vec::new(),
is_stackmap_relative: false,
}
}
pub fn new_jump(id: u64, target: &str) -> Self {
X86PatchPoint {
id,
patch_type: PatchableType::Jump,
nop_sled_size: PATCHABLE_JUMP_SEQUENCE_SIZE,
target: Some(target.to_string()),
return_type: None,
num_args: 0,
calling_convention: "".to_string(),
metadata: Vec::new(),
is_stackmap_relative: false,
}
}
pub fn new_region(id: u64, size: usize) -> Self {
X86PatchPoint {
id,
patch_type: PatchableType::Region,
nop_sled_size: size,
target: None,
return_type: None,
num_args: 0,
calling_convention: "".to_string(),
metadata: Vec::new(),
is_stackmap_relative: false,
}
}
pub fn set_calling_convention(&mut self, cc: &str) {
self.calling_convention = cc.to_string();
}
pub fn set_metadata(&mut self, data: &[u8]) {
self.metadata = data.to_vec();
}
pub fn set_stackmap_relative(&mut self, relative: bool) {
self.is_stackmap_relative = relative;
}
pub fn generate_nop_sled(&self) -> Vec<u8> {
let size = self.nop_sled_size;
let mut sled = Vec::with_capacity(size);
let mut remaining = size;
while remaining > 0 {
match remaining {
1 => {
sled.push(0x90);
remaining -= 1;
}
2 => {
sled.extend_from_slice(&[0x66, 0x90]);
remaining -= 2;
}
3 => {
sled.extend_from_slice(&[0x0F, 0x1F, 0x00]);
remaining -= 3;
}
x if x >= 9 => {
sled.extend_from_slice(&[0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]);
remaining -= 9;
}
x if x >= 8 => {
sled.extend_from_slice(&[0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]);
remaining -= 8;
}
x if x >= 7 => {
sled.extend_from_slice(&[0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00]);
remaining -= 7;
}
x if x >= 6 => {
sled.extend_from_slice(&[0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00]);
remaining -= 6;
}
x if x >= 5 => {
sled.extend_from_slice(&[0x0F, 0x1F, 0x44, 0x00, 0x00]);
remaining -= 5;
}
x if x >= 4 => {
sled.extend_from_slice(&[0x0F, 0x1F, 0x40, 0x00]);
remaining -= 4;
}
_ => {
sled.push(0x90);
remaining -= 1;
}
}
}
sled
}
pub fn generate_machine_instrs(&self) -> (Vec<MachineInstr>, Option<MachineInstr>) {
let sled_instrs = self._gen_nop_sled_instrs();
let call_instr = self._gen_call_or_jump_instr();
(sled_instrs, call_instr)
}
fn _gen_nop_sled_instrs(&self) -> Vec<MachineInstr> {
let num_nops = if self.nop_sled_size >= 8 {
self.nop_sled_size / 8
} else {
self.nop_sled_size
};
let mut instrs = Vec::with_capacity(num_nops);
for _ in 0..num_nops {
let mut instr = MachineInstr::new(0x90); instr.push_imm(0);
instrs.push(instr);
}
instrs
}
fn _gen_call_or_jump_instr(&self) -> Option<MachineInstr> {
match self.patch_type {
PatchableType::Call => {
let mut instr = MachineInstr::new(0xE8); if let Some(ref tgt) = self.target {
instr.push_label(tgt);
} else {
instr.push_imm(0); }
Some(instr)
}
PatchableType::Jump => {
let mut instr = MachineInstr::new(0xE9); if let Some(ref tgt) = self.target {
instr.push_label(tgt);
} else {
instr.push_imm(0);
}
Some(instr)
}
_ => None,
}
}
pub fn to_ir_text(&self) -> String {
let mut ir = String::new();
ir.push_str(&format!(
" call {} @llvm.experimental.patchpoint.{}(\n",
match self.patch_type {
PatchableType::Call if self.return_type.is_some() => "any",
_ => "void",
},
match self.patch_type {
PatchableType::Call => "i64",
PatchableType::Jump => "i64",
_ => "void",
},
));
ir.push_str(&format!(
" i64 {}, i32 {}, i8* null",
self.id, self.nop_sled_size
));
if let Some(ref target) = self.target {
ir.push_str(&format!(", i8* bitcast (void ()* @{} to i8*)", target));
} else {
ir.push_str(", i8* null");
}
for i in 0..self.num_args {
ir.push_str(&format!(", i64 {}", i));
}
if !self.metadata.is_empty() {
ir.push_str(&format!(
", i32 {}",
std::str::from_utf8(&self.metadata).unwrap_or("<binary>")
));
}
ir.push(')');
ir
}
pub fn generate_stack_map_record(&self, instruction_offset: u32) -> StackMapRecord {
StackMapRecord::new(self.id, instruction_offset)
}
}
impl Default for X86PatchPoint {
fn default() -> Self {
X86PatchPoint::new_void_call(0, "unknown", 0)
}
}
#[derive(Debug, Clone)]
pub struct X86StatePoint {
pub id: u64,
pub nop_bytes: u32,
pub callee: String,
pub num_call_args: u32,
pub flags: u32,
pub call_args: Vec<X86StatepointValue>,
pub num_transition_args: u32,
pub num_deopt_args: u32,
pub deopt_args: Vec<X86StatepointValue>,
pub gc_args: Vec<X86StatepointValue>,
pub derived_pointer_ids: Vec<u32>,
pub base_pointer_ids: Vec<u32>,
pub gc_transition_args: Vec<X86StatepointValue>,
pub relocation_records: Vec<X86RelocationRecord>,
}
#[derive(Debug, Clone)]
pub struct X86StatepointValue {
pub repr: X86StatepointValueRepr,
pub value_type: String,
pub is_gc_pointer: bool,
}
#[derive(Debug, Clone)]
pub enum X86StatepointValueRepr {
Register { dwarf_reg: u16, size: u16 },
Stack { offset: i32, size: u16 },
Constant { value: i64 },
Undef,
}
impl X86StatepointValue {
pub fn reg(dwarf_reg: u16, value_type: &str) -> Self {
X86StatepointValue {
repr: X86StatepointValueRepr::Register { dwarf_reg, size: 8 },
value_type: value_type.to_string(),
is_gc_pointer: false,
}
}
pub fn stack(offset: i32, value_type: &str) -> Self {
X86StatepointValue {
repr: X86StatepointValueRepr::Stack { offset, size: 8 },
value_type: value_type.to_string(),
is_gc_pointer: false,
}
}
pub fn constant(value: i64, value_type: &str) -> Self {
X86StatepointValue {
repr: X86StatepointValueRepr::Constant { value },
value_type: value_type.to_string(),
is_gc_pointer: false,
}
}
pub fn undef(value_type: &str) -> Self {
X86StatepointValue {
repr: X86StatepointValueRepr::Undef,
value_type: value_type.to_string(),
is_gc_pointer: false,
}
}
pub fn mark_gc_pointer(&mut self) {
self.is_gc_pointer = true;
}
pub fn to_location_record(&self) -> Option<LocationRecord> {
match self.repr {
X86StatepointValueRepr::Register { dwarf_reg, size } => {
Some(LocationRecord::new_register(dwarf_reg, size))
}
X86StatepointValueRepr::Stack { offset, size } => {
Some(LocationRecord::new_indirect(DWARF_REG_RSP, offset, size))
}
X86StatepointValueRepr::Constant { value } => {
Some(LocationRecord::new_constant(value as i32))
}
X86StatepointValueRepr::Undef => None,
}
}
}
#[derive(Debug, Clone)]
pub struct X86RelocationRecord {
pub statepoint_id: u64,
pub base_ptr_index: u32,
pub derived_ptr_index: u32,
pub offset_from_base: i64,
}
impl X86RelocationRecord {
pub fn new(
statepoint_id: u64,
base_ptr_index: u32,
derived_ptr_index: u32,
offset_from_base: i64,
) -> Self {
X86RelocationRecord {
statepoint_id,
base_ptr_index,
derived_ptr_index,
offset_from_base,
}
}
pub fn is_derived(&self) -> bool {
self.derived_ptr_index != self.base_ptr_index
}
pub fn has_interior_offset(&self) -> bool {
self.offset_from_base != 0
}
}
impl X86StatePoint {
pub fn new(id: u64, callee: &str, num_call_args: u32) -> Self {
X86StatePoint {
id,
nop_bytes: 0,
callee: callee.to_string(),
num_call_args,
flags: 0,
call_args: Vec::new(),
num_transition_args: 0,
num_deopt_args: 0,
deopt_args: Vec::new(),
gc_args: Vec::new(),
derived_pointer_ids: Vec::new(),
base_pointer_ids: Vec::new(),
gc_transition_args: Vec::new(),
relocation_records: Vec::new(),
}
}
pub fn add_call_arg(&mut self, arg: X86StatepointValue) {
self.call_args.push(arg);
self.num_call_args = self.call_args.len() as u32;
}
pub fn add_deopt_arg(&mut self, arg: X86StatepointValue) {
self.deopt_args.push(arg);
self.num_deopt_args = self.deopt_args.len() as u32;
}
pub fn add_gc_root(&mut self, value: X86StatepointValue) -> u32 {
let idx = self.gc_args.len() as u32;
self.gc_args.push(value);
self.base_pointer_ids.push(idx);
idx
}
pub fn add_derived_pointer(
&mut self,
derived: X86StatepointValue,
base_index: u32,
offset_from_base: i64,
) -> u32 {
let derived_idx = self.gc_args.len() as u32;
self.gc_args.push(derived);
self.derived_pointer_ids.push(derived_idx);
let reloc = X86RelocationRecord::new(self.id, base_index, derived_idx, offset_from_base);
self.relocation_records.push(reloc);
derived_idx
}
pub fn set_nop_bytes(&mut self, nop_bytes: u32) {
self.nop_bytes = nop_bytes;
}
pub fn to_ir_text(&self) -> String {
let mut ir = String::new();
ir.push_str(&format!(
" %result = call token (i64, i32, {}, i32, i32, ...)* @llvm.experimental.gc.statepoint.p0(\n",
if self.callee.starts_with('@') {
"i8*"
} else {
&self.callee
}
));
ir.push_str(&format!(" i64 {}, i32 {}", self.id, self.nop_bytes));
ir.push_str(&format!(", i8* bitcast (void ()* @{} to i8*)", self.callee));
ir.push_str(&format!(", i32 {}, i32 {}", self.num_call_args, self.flags));
for arg in &self.call_args {
ir.push_str(", ");
ir.push_str(&format_arg_value(arg));
}
ir.push_str(", i32 0, i32 0");
ir.push_str(&format!(", i32 {}", self.num_deopt_args));
for arg in &self.deopt_args {
ir.push_str(", ");
ir.push_str(&format_arg_value(arg));
}
for arg in &self.gc_args {
ir.push_str(", ");
ir.push_str(&format_arg_value(arg));
}
ir.push(')');
ir
}
pub fn generate_stack_map_record(&self, instruction_offset: u32) -> StackMapRecord {
let mut record = StackMapRecord::new(self.id, instruction_offset);
for gc_arg in &self.gc_args {
if let Some(loc) = gc_arg.to_location_record() {
record.add_location(loc);
}
}
for deopt_arg in &self.deopt_args {
if let Some(loc) = deopt_arg.to_location_record() {
record.add_location(loc);
}
}
record
}
pub fn get_gc_root_locations(&self) -> Vec<LocationRecord> {
self.gc_args
.iter()
.filter_map(|arg| arg.to_location_record())
.collect()
}
pub fn get_derived_locations(&self) -> Vec<(LocationRecord, u32, i64)> {
self.derived_pointer_ids
.iter()
.filter_map(|&idx| {
let reloc = self
.relocation_records
.iter()
.find(|r| r.derived_ptr_index == idx)?;
let arg = self.gc_args.get(idx as usize)?;
let loc = arg.to_location_record()?;
Some((loc, reloc.base_ptr_index, reloc.offset_from_base))
})
.collect()
}
pub fn get_base_for_derived(&self, derived_idx: u32) -> Option<&X86StatepointValue> {
let reloc = self
.relocation_records
.iter()
.find(|r| r.derived_ptr_index == derived_idx)?;
self.gc_args.get(reloc.base_ptr_index as usize)
}
}
fn format_arg_value(arg: &X86StatepointValue) -> String {
match &arg.repr {
X86StatepointValueRepr::Register { dwarf_reg, .. } => {
format!("%reg{}", dwarf_reg)
}
X86StatepointValueRepr::Stack { offset, .. } => {
format!("i64 %stack_{}", offset)
}
X86StatepointValueRepr::Constant { value } => {
format!("i64 {}", value)
}
X86StatepointValueRepr::Undef => "undef".to_string(),
}
}
#[derive(Debug, Clone)]
pub struct X86GCSafepoint {
pub enabled: bool,
pub polling_page_address: u64,
pub polling_page_size: u64,
pub safepoint_count: u32,
pub insert_at_entry: bool,
pub insert_at_backedge: bool,
pub insert_after_calls: bool,
pub backedge_frequency: u32,
pub stats: X86SafepointStats,
pub safepoints: Vec<X86SafepointLocation>,
}
#[derive(Debug, Clone, Default)]
pub struct X86SafepointStats {
pub entry_safepoints: u32,
pub backedge_safepoints: u32,
pub call_safepoints: u32,
pub statepoint_safepoints: u32,
pub coop_suspend_points: u32,
pub exit_safepoints: u32,
pub total: u32,
}
impl X86SafepointStats {
pub fn update_total(&mut self) {
self.total = self.entry_safepoints
+ self.backedge_safepoints
+ self.call_safepoints
+ self.statepoint_safepoints
+ self.coop_suspend_points
+ self.exit_safepoints;
}
}
#[derive(Debug, Clone)]
pub struct X86SafepointLocation {
pub kind: SafepointKind,
pub function_name: String,
pub instruction_offset: u32,
pub stack_map_id: u64,
pub machine_instr: Option<MachineInstr>,
}
impl X86SafepointLocation {
pub fn new(
kind: SafepointKind,
function_name: &str,
instruction_offset: u32,
stack_map_id: u64,
) -> Self {
X86SafepointLocation {
kind,
function_name: function_name.to_string(),
instruction_offset,
stack_map_id,
machine_instr: None,
}
}
pub fn set_machine_instr(&mut self, instr: MachineInstr) {
self.machine_instr = Some(instr);
}
}
impl X86GCSafepoint {
pub fn new() -> Self {
X86GCSafepoint {
enabled: true,
polling_page_address: POLLING_PAGE_ADDRESS,
polling_page_size: 4096,
safepoint_count: 0,
insert_at_entry: true,
insert_at_backedge: true,
insert_after_calls: false,
backedge_frequency: 1,
stats: X86SafepointStats::default(),
safepoints: Vec::new(),
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn set_polling_page(&mut self, address: u64) {
self.polling_page_address = address;
}
pub fn configure(
&mut self,
at_entry: bool,
at_backedge: bool,
after_calls: bool,
backedge_freq: u32,
) {
self.insert_at_entry = at_entry;
self.insert_at_backedge = at_backedge;
self.insert_after_calls = after_calls;
self.backedge_frequency = backedge_freq;
}
pub fn generate_poll_sequence(&self) -> Vec<MachineInstr> {
let mut seq = Vec::with_capacity(2);
let mut mov_instr = MachineInstr::new(0x48A1); mov_instr.push_imm(self.polling_page_address as i64);
seq.push(mov_instr);
let mut test_instr = MachineInstr::new(0x85); test_instr.push_reg(DWARF_REG_RAX as u32);
test_instr.push_reg(DWARF_REG_RAX as u32);
seq.push(test_instr);
seq
}
pub fn generate_poll_ir(&self) -> String {
format!(
" %safepoint_load = load volatile i64, i64* inttoptr (i64 {} to i64*)\n",
self.polling_page_address
)
}
pub fn insert_entry_safepoint(
&mut self,
func_name: &str,
stack_map_id: u64,
) -> (X86SafepointLocation, Vec<MachineInstr>) {
let loc = X86SafepointLocation::new(
SafepointKind::FunctionEntry,
func_name,
0, stack_map_id,
);
self.stats.entry_safepoints += 1;
self.stats.update_total();
self.safepoints.push(loc.clone());
self.safepoint_count += 1;
(loc, self.generate_poll_sequence())
}
pub fn insert_backedge_safepoint(
&mut self,
func_name: &str,
instruction_offset: u32,
stack_map_id: u64,
) -> (X86SafepointLocation, Vec<MachineInstr>) {
let loc = X86SafepointLocation::new(
SafepointKind::LoopBackedge,
func_name,
instruction_offset,
stack_map_id,
);
self.stats.backedge_safepoints += 1;
self.stats.update_total();
self.safepoints.push(loc.clone());
self.safepoint_count += 1;
(loc, self.generate_poll_sequence())
}
pub fn insert_call_safepoint(
&mut self,
func_name: &str,
instruction_offset: u32,
stack_map_id: u64,
) -> (X86SafepointLocation, Vec<MachineInstr>) {
let loc = X86SafepointLocation::new(
SafepointKind::AfterCall,
func_name,
instruction_offset,
stack_map_id,
);
self.stats.call_safepoints += 1;
self.stats.update_total();
self.safepoints.push(loc.clone());
self.safepoint_count += 1;
(loc, self.generate_poll_sequence())
}
pub fn record_statepoint(
&mut self,
func_name: &str,
instruction_offset: u32,
stack_map_id: u64,
) -> X86SafepointLocation {
let loc = X86SafepointLocation::new(
SafepointKind::Statepoint,
func_name,
instruction_offset,
stack_map_id,
);
self.stats.statepoint_safepoints += 1;
self.stats.update_total();
self.safepoints.push(loc.clone());
self.safepoint_count += 1;
loc
}
pub fn insert_cooperative_suspend(
&mut self,
func_name: &str,
instruction_offset: u32,
stack_map_id: u64,
) -> (X86SafepointLocation, Vec<MachineInstr>) {
let loc = X86SafepointLocation::new(
SafepointKind::CooperativeSuspend,
func_name,
instruction_offset,
stack_map_id,
);
self.stats.coop_suspend_points += 1;
self.stats.update_total();
self.safepoints.push(loc.clone());
self.safepoint_count += 1;
(loc, self.generate_poll_sequence())
}
pub fn get_stats(&self) -> &X86SafepointStats {
&self.stats
}
pub fn get_function_safepoints(&self, func_name: &str) -> Vec<&X86SafepointLocation> {
self.safepoints
.iter()
.filter(|sp| sp.function_name == func_name)
.collect()
}
pub fn is_safepoint(&self, func_name: &str, offset: u32) -> bool {
self.safepoints
.iter()
.any(|sp| sp.function_name == func_name && sp.instruction_offset == offset)
}
pub fn should_insert_backedge(&self, backedge_counter: u32) -> bool {
if !self.insert_at_backedge || self.backedge_frequency == 0 {
return false;
}
backedge_counter % self.backedge_frequency == 0
}
pub fn reset(&mut self) {
self.safepoint_count = 0;
self.stats = X86SafepointStats::default();
self.safepoints.clear();
}
}
impl Default for X86GCSafepoint {
fn default() -> Self {
X86GCSafepoint::new()
}
}
#[derive(Debug, Clone)]
pub struct X86StackMapGenerator {
pub stack_maps: X86StackMaps,
pub safepoint: X86GCSafepoint,
pub function_index_map: HashMap<String, usize>,
pub live_var_state: HashMap<String, X86LiveVarState>,
pub reg_names: X86RegisterNames,
pub current_function: Option<String>,
pub current_offset: u32,
}
#[derive(Debug, Clone)]
pub struct X86LiveVarState {
pub var_locations: HashMap<VirtReg, LocationRecord>,
pub var_types: HashMap<VirtReg, String>,
pub gc_pointers: HashSet<VirtReg>,
pub stack_slots: Vec<X86StackSlot>,
pub frame_size: u64,
pub offset: u32,
}
#[derive(Debug, Clone)]
pub struct X86StackSlot {
pub offset: i32,
pub size: u16,
pub is_gc_pointer: bool,
pub spilled_reg: Option<VirtReg>,
}
impl X86StackSlot {
pub fn new(offset: i32, size: u16) -> Self {
X86StackSlot {
offset,
size,
is_gc_pointer: false,
spilled_reg: None,
}
}
pub fn mark_gc_pointer(&mut self) {
self.is_gc_pointer = true;
}
}
impl X86LiveVarState {
pub fn new() -> Self {
X86LiveVarState {
var_locations: HashMap::new(),
var_types: HashMap::new(),
gc_pointers: HashSet::new(),
stack_slots: Vec::new(),
frame_size: 0,
offset: 0,
}
}
pub fn record_location(&mut self, reg: VirtReg, location: LocationRecord) {
self.var_locations.insert(reg, location);
}
pub fn record_type(&mut self, reg: VirtReg, ty: &str) {
self.var_types.insert(reg, ty.to_string());
}
pub fn mark_gc_pointer(&mut self, reg: VirtReg) {
self.gc_pointers.insert(reg);
}
pub fn is_gc_pointer(&self, reg: VirtReg) -> bool {
self.gc_pointers.contains(®)
}
pub fn allocate_stack_slot(&mut self, size: u16) -> X86StackSlot {
let offset = if self.stack_slots.is_empty() {
-(size as i32) } else {
let last = self.stack_slots.last().unwrap();
last.offset - (size as i32)
};
let slot = X86StackSlot::new(offset, size);
self.stack_slots.push(slot.clone());
self.frame_size = self.frame_size.max((-offset) as u64);
slot
}
pub fn get_live_location(&self, reg: VirtReg) -> Option<&LocationRecord> {
self.var_locations.get(®)
}
pub fn get_live_gc_pointers(&self) -> Vec<(VirtReg, &LocationRecord)> {
self.gc_pointers
.iter()
.filter_map(|reg| self.var_locations.get(reg).map(|loc| (*reg, loc)))
.collect()
}
}
impl Default for X86LiveVarState {
fn default() -> Self {
X86LiveVarState::new()
}
}
impl X86StackMapGenerator {
pub fn new(use_64bit: bool) -> Self {
X86StackMapGenerator {
stack_maps: X86StackMaps::new("x86_64-unknown-linux-gnu", use_64bit),
safepoint: X86GCSafepoint::new(),
function_index_map: HashMap::new(),
live_var_state: HashMap::new(),
reg_names: X86RegisterNames::new(use_64bit),
current_function: None,
current_offset: 0,
}
}
pub fn begin_function(&mut self, name: &str, stack_size: u64) {
self.current_function = Some(name.to_string());
self.current_offset = 0;
let func_index = self.stack_maps.register_function(name, stack_size);
self.function_index_map.insert(name.to_string(), func_index);
let state = X86LiveVarState::new();
self.live_var_state.insert(name.to_string(), state);
}
pub fn end_function(&mut self) {
self.current_function = None;
self.current_offset = 0;
}
pub fn advance_offset(&mut self, delta: u32) {
self.current_offset += delta;
}
pub fn record_register_location(
&mut self,
func_name: &str,
virt_reg: VirtReg,
dwarf_reg: u16,
size: u16,
ty: &str,
is_gc_ptr: bool,
) {
if let Some(state) = self.live_var_state.get_mut(func_name) {
let loc = LocationRecord::new_register(dwarf_reg, size);
state.record_location(virt_reg, loc);
state.record_type(virt_reg, ty);
if is_gc_ptr {
state.mark_gc_pointer(virt_reg);
}
}
}
pub fn record_stack_location(
&mut self,
func_name: &str,
virt_reg: VirtReg,
offset: i32,
size: u16,
ty: &str,
is_gc_ptr: bool,
is_indirect: bool,
) {
if let Some(state) = self.live_var_state.get_mut(func_name) {
let loc = if is_indirect {
LocationRecord::new_indirect(DWARF_REG_RSP, offset, size)
} else {
LocationRecord::new_direct(DWARF_REG_RSP, offset, size)
};
state.record_location(virt_reg, loc);
state.record_type(virt_reg, ty);
if is_gc_ptr {
state.mark_gc_pointer(virt_reg);
}
}
}
pub fn record_constant_location(
&mut self,
func_name: &str,
virt_reg: VirtReg,
value: i32,
ty: &str,
) {
if let Some(state) = self.live_var_state.get_mut(func_name) {
let loc = LocationRecord::new_constant(value);
state.record_location(virt_reg, loc);
state.record_type(virt_reg, ty);
}
}
pub fn generate_stack_map_record(
&mut self,
func_name: &str,
id: u64,
) -> Option<StackMapRecord> {
let state = self.live_var_state.get(func_name)?;
let func_index = self.function_index_map.get(func_name)?;
let mut record = StackMapRecord::new(id, self.current_offset);
let live_gc = state.get_live_gc_pointers();
for (_reg, loc) in live_gc {
record.add_location(loc.clone());
}
self.stack_maps.add_record(*func_index, record.clone());
Some(record)
}
pub fn generate_from_patchpoint(
&mut self,
func_name: &str,
patchpoint: &X86PatchPoint,
) -> Option<StackMapRecord> {
let func_index = self.function_index_map.get(func_name)?;
let record = patchpoint.generate_stack_map_record(self.current_offset);
self.stack_maps.add_record(*func_index, record.clone());
Some(record)
}
pub fn generate_from_statepoint(
&mut self,
func_name: &str,
statepoint: &X86StatePoint,
) -> Option<StackMapRecord> {
let func_index = self.function_index_map.get(func_name)?;
let record = statepoint.generate_stack_map_record(self.current_offset);
self.stack_maps.add_record(*func_index, record.clone());
Some(record)
}
pub fn generate_from_safepoint(
&mut self,
func_name: &str,
sp: &X86SafepointLocation,
) -> Option<StackMapRecord> {
self.current_offset = sp.instruction_offset;
self.generate_stack_map_record(func_name, sp.stack_map_id)
}
pub fn spill_register(
&mut self,
func_name: &str,
virt_reg: VirtReg,
size: u16,
) -> Option<X86StackSlot> {
let state = self.live_var_state.get_mut(func_name)?;
let is_gc_ptr = state.is_gc_pointer(virt_reg);
let mut slot = state.allocate_stack_slot(size);
if is_gc_ptr {
slot.mark_gc_pointer();
}
slot.spilled_reg = Some(virt_reg);
let loc = LocationRecord::new_direct(DWARF_REG_RSP, slot.offset, size);
state.record_location(virt_reg, loc);
Some(slot)
}
pub fn reload_register(&mut self, func_name: &str, virt_reg: VirtReg, dwarf_reg: u16) {
if let Some(state) = self.live_var_state.get_mut(func_name) {
let ty = state.var_types.get(&virt_reg).cloned().unwrap_or_default();
let size: u16 = 8; let loc = LocationRecord::new_register(dwarf_reg, size);
state.record_location(virt_reg, loc);
}
}
pub fn record_stack_size_change(&mut self, func_name: &str, new_size: u64) {
if let Some(func_index) = self.function_index_map.get(func_name) {
self.stack_maps
.add_stack_size(*func_index, self.current_offset as u64, new_size);
}
}
pub fn generate_section(&mut self) -> Vec<u8> {
self.stack_maps.emit_section()
}
pub fn generate_section_asm(&self) -> String {
self.stack_maps.emit_asm()
}
pub fn add_constant(&mut self, value: u64) -> u32 {
self.stack_maps.format.add_constant(value)
}
pub fn get_stack_maps(&self) -> &X86StackMaps {
&self.stack_maps
}
pub fn get_stack_maps_mut(&mut self) -> &mut X86StackMaps {
&mut self.stack_maps
}
pub fn set_safepoints_enabled(&mut self, enabled: bool) {
self.safepoint.set_enabled(enabled);
}
pub fn finalize(&mut self) -> Result<(), Vec<String>> {
self.stack_maps.finalize();
let mut errors = Vec::new();
if let Err(e) = self.stack_maps.validate() {
errors.extend(e);
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
impl Default for X86StackMapGenerator {
fn default() -> Self {
X86StackMapGenerator::new(true)
}
}
#[derive(Debug, Clone)]
pub struct X86StackMapParser {
pub format: X86StackMapFormat,
pub reg_names: X86RegisterNames,
pub id_index: HashMap<u64, StackMapRecord>,
pub addr_index: HashMap<u64, StackMapFunctionRecord>,
pub parsed: bool,
pub warnings: Vec<String>,
}
impl X86StackMapParser {
pub fn new(use_64bit: bool) -> Self {
X86StackMapParser {
format: X86StackMapFormat::new(),
reg_names: X86RegisterNames::new(use_64bit),
id_index: HashMap::new(),
addr_index: HashMap::new(),
parsed: false,
warnings: Vec::new(),
}
}
pub fn parse(&mut self, data: &[u8]) -> Result<(), String> {
self.warnings.clear();
let format = X86StackMapFormat::from_bytes(data)
.map_err(|e| format!("Failed to parse stack map section: {}", e))?;
if let Err(errors) = format.validate() {
return Err(format!("Validation errors: {}", errors.join("; ")));
}
for record in &format.records {
if self.id_index.contains_key(&record.id) {
self.warnings.push(format!(
"Duplicate stack map ID: {} (keeping first occurrence)",
record.id
));
} else {
self.id_index.insert(record.id, record.clone());
}
}
for func in &format.functions {
self.addr_index.insert(func.function_address, func.clone());
}
self.format = format;
self.parsed = true;
Ok(())
}
pub fn parse_section(&mut self, section_name: &str, data: &[u8]) -> Result<(), String> {
if section_name != STACK_MAP_SECTION_NAME {
return Err(format!(
"Not a stack map section: '{}' (expected '{}')",
section_name, STACK_MAP_SECTION_NAME
));
}
self.parse(data)
}
pub fn lookup_by_id(&self, id: u64) -> Option<&StackMapRecord> {
self.id_index.get(&id)
}
pub fn lookup_by_address(&self, address: u64) -> Vec<&StackMapRecord> {
self.format.find_records_by_address(address)
}
pub fn lookup_function(&self, address: u64) -> Option<&StackMapFunctionRecord> {
self.addr_index.get(&address)
}
pub fn extract_live_locations(
&self,
record: &StackMapRecord,
) -> Vec<(String, LocationKind, i32, u16)> {
record
.locations
.iter()
.map(|loc| {
let reg_name = self.reg_names.resolve(loc.dwarf_reg_num);
(reg_name, loc.kind, loc.offset, loc.size)
})
.collect()
}
pub fn extract_gc_roots(
&self,
record: &StackMapRecord,
pointer_size: u16,
) -> Vec<(String, i32)> {
record
.locations
.iter()
.filter(|loc| loc.size == pointer_size && loc.kind.is_register() || loc.kind.is_stack())
.map(|loc| {
let reg_name = self.reg_names.resolve(loc.dwarf_reg_num);
(reg_name, loc.offset)
})
.collect()
}
pub fn extract_live_outs(&self, record: &StackMapRecord) -> Vec<(String, u8)> {
record
.live_outs
.iter()
.map(|lo| {
let reg_name = self.reg_names.resolve(lo.dwarf_reg_num);
(reg_name, lo.size)
})
.collect()
}
pub fn resolve_register(&self, dwarf_reg: u16) -> String {
self.reg_names.resolve(dwarf_reg)
}
pub fn get_constant(&self, index: u32) -> Option<u64> {
self.format.get_constant(index)
}
pub fn get_all_ids(&self) -> Vec<u64> {
let mut ids: Vec<u64> = self.id_index.keys().copied().collect();
ids.sort();
ids
}
pub fn record_count(&self) -> usize {
self.format.total_records()
}
pub fn get_warnings(&self) -> &[String] {
&self.warnings
}
pub fn version(&self) -> u8 {
self.format.header.version
}
pub fn is_parsed(&self) -> bool {
self.parsed
}
pub fn dump_records(&self) -> String {
let mut output = String::new();
output.push_str(&format!(
"Stack Map Section (version {}, {} functions, {} constants, {} records)\n\n",
self.format.header.version,
self.format.functions.len(),
self.format.constants.len(),
self.format.records.len(),
));
for func in &self.format.functions {
output.push_str(&format!(
"Function at 0x{:016X}: stack_size={}, records={}\n",
func.function_address, func.stack_size, func.record_count
));
}
output.push('\n');
for record in &self.format.records {
output.push_str(&format!(
" Record ID={}: offset=0x{:04X}, {} locations, {} live-outs\n",
record.id,
record.instruction_offset,
record.locations.len(),
record.live_outs.len(),
));
for (i, loc) in record.locations.iter().enumerate() {
let reg_name = self.reg_names.resolve(loc.dwarf_reg_num);
output.push_str(&format!(
" loc[{}]: kind={}, reg={}, offset={}, size={}\n",
i, loc.kind, reg_name, loc.offset, loc.size,
));
}
for (i, lo) in record.live_outs.iter().enumerate() {
let reg_name = self.reg_names.resolve(lo.dwarf_reg_num);
output.push_str(&format!(
" live-out[{}]: reg={}, size={}\n",
i, reg_name, lo.size,
));
}
}
output
}
}
impl Default for X86StackMapParser {
fn default() -> Self {
X86StackMapParser::new(true)
}
}
#[derive(Debug, Clone)]
pub struct X86DeoptState {
pub magic: u32,
pub version: u16,
pub compilation_unit_id: u32,
pub frame_state: X86FrameState,
pub register_state: X86RegisterState,
pub continuation_point: X86ContinuationPoint,
pub reason: DeoptReason,
pub metadata: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct X86FrameState {
pub frame_size: u32,
pub num_locals: u32,
pub num_stack_slots: u32,
pub num_monitors: u32,
pub locals: Vec<X86FrameSlot>,
pub stack: Vec<X86FrameSlot>,
pub monitors: Vec<X86MonitorSlot>,
pub bytecode_offset: u32,
pub method_ref: u64,
}
#[derive(Debug, Clone)]
pub struct X86FrameSlot {
pub index: u32,
pub location: LocationRecord,
pub value_type: String,
pub is_reference: bool,
pub is_live: bool,
}
#[derive(Debug, Clone)]
pub struct X86MonitorSlot {
pub index: u32,
pub object_location: LocationRecord,
pub is_reentrant: bool,
}
#[derive(Debug, Clone)]
pub struct X86RegisterState {
pub num_gprs: u32,
pub num_xmms: u32,
pub num_ymms: u32,
pub num_zmms: u32,
pub gprs: Vec<X86RegisterSlot>,
pub xmms: Vec<X86RegisterSlot>,
pub ymms: Vec<X86RegisterSlot>,
pub zmms: Vec<X86RegisterSlot>,
pub rflags: Option<u64>,
pub rip: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct X86RegisterSlot {
pub dwarf_reg: u16,
pub value: u64,
pub is_reference: bool,
}
#[derive(Debug, Clone)]
pub struct X86ContinuationPoint {
pub bytecode_offset: u32,
pub method_ref: u64,
pub dispatch_kind: X86DeoptDispatchKind,
pub exception_handler_offset: Option<u32>,
pub is_reexecute: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DeoptDispatchKind {
Interpreter,
BaselineJIT,
OptimizingJIT,
ExceptionHandler,
UnwindStub,
}
impl X86DeoptDispatchKind {
pub fn name(&self) -> &'static str {
match self {
X86DeoptDispatchKind::Interpreter => "interpreter",
X86DeoptDispatchKind::BaselineJIT => "baseline-jit",
X86DeoptDispatchKind::OptimizingJIT => "optimizing-jit",
X86DeoptDispatchKind::ExceptionHandler => "exception-handler",
X86DeoptDispatchKind::UnwindStub => "unwind-stub",
}
}
}
impl Default for X86FrameSlot {
fn default() -> Self {
X86FrameSlot {
index: 0,
location: LocationRecord::new_register(0, 0),
value_type: "i64".to_string(),
is_reference: false,
is_live: true,
}
}
}
impl X86FrameSlot {
pub fn new_local(index: u32, location: LocationRecord, value_type: &str, is_ref: bool) -> Self {
X86FrameSlot {
index,
location,
value_type: value_type.to_string(),
is_reference: is_ref,
is_live: true,
}
}
pub fn new_stack(index: u32, location: LocationRecord, value_type: &str, is_ref: bool) -> Self {
X86FrameSlot {
index,
location,
value_type: value_type.to_string(),
is_reference: is_ref,
is_live: true,
}
}
pub fn mark_dead(&mut self) {
self.is_live = false;
}
}
impl X86MonitorSlot {
pub fn new(index: u32, object_location: LocationRecord, reentrant: bool) -> Self {
X86MonitorSlot {
index,
object_location,
is_reentrant: reentrant,
}
}
}
impl X86RegisterSlot {
pub fn new(dwarf_reg: u16, value: u64, is_ref: bool) -> Self {
X86RegisterSlot {
dwarf_reg,
value,
is_reference: is_ref,
}
}
}
impl X86ContinuationPoint {
pub fn new_interpreter(bytecode_offset: u32, method_ref: u64, reexecute: bool) -> Self {
X86ContinuationPoint {
bytecode_offset,
method_ref,
dispatch_kind: X86DeoptDispatchKind::Interpreter,
exception_handler_offset: None,
is_reexecute: reexecute,
}
}
pub fn new_baseline(bytecode_offset: u32, method_ref: u64) -> Self {
X86ContinuationPoint {
bytecode_offset,
method_ref,
dispatch_kind: X86DeoptDispatchKind::BaselineJIT,
exception_handler_offset: None,
is_reexecute: false,
}
}
pub fn new_exception_handler(
bytecode_offset: u32,
method_ref: u64,
handler_offset: u32,
) -> Self {
X86ContinuationPoint {
bytecode_offset,
method_ref,
dispatch_kind: X86DeoptDispatchKind::ExceptionHandler,
exception_handler_offset: Some(handler_offset),
is_reexecute: false,
}
}
}
impl X86RegisterState {
pub fn new() -> Self {
X86RegisterState {
num_gprs: 0,
num_xmms: 0,
num_ymms: 0,
num_zmms: 0,
gprs: Vec::new(),
xmms: Vec::new(),
ymms: Vec::new(),
zmms: Vec::new(),
rflags: None,
rip: None,
}
}
pub fn add_gpr(&mut self, dwarf_reg: u16, value: u64, is_ref: bool) {
self.gprs
.push(X86RegisterSlot::new(dwarf_reg, value, is_ref));
self.num_gprs = self.gprs.len() as u32;
}
pub fn add_xmm(&mut self, dwarf_reg: u16, value: u64) {
self.xmms
.push(X86RegisterSlot::new(dwarf_reg, value, false));
self.num_xmms = self.xmms.len() as u32;
}
pub fn add_ymm(&mut self, dwarf_reg: u16, value: u64) {
self.ymms
.push(X86RegisterSlot::new(dwarf_reg, value, false));
self.num_ymms = self.ymms.len() as u32;
}
pub fn add_zmm(&mut self, dwarf_reg: u16, value: u64) {
self.zmms
.push(X86RegisterSlot::new(dwarf_reg, value, false));
self.num_zmms = self.zmms.len() as u32;
}
pub fn set_rflags(&mut self, rflags: u64) {
self.rflags = Some(rflags);
}
pub fn set_rip(&mut self, rip: u64) {
self.rip = Some(rip);
}
pub fn get_gpr(&self, dwarf_reg: u16) -> Option<u64> {
self.gprs
.iter()
.find(|slot| slot.dwarf_reg == dwarf_reg)
.map(|slot| slot.value)
}
pub fn is_gpr_reference(&self, dwarf_reg: u16) -> bool {
self.gprs
.iter()
.find(|slot| slot.dwarf_reg == dwarf_reg)
.map(|slot| slot.is_reference)
.unwrap_or(false)
}
pub fn total_registers(&self) -> usize {
self.gprs.len() + self.xmms.len() + self.ymms.len() + self.zmms.len()
}
}
impl Default for X86RegisterState {
fn default() -> Self {
X86RegisterState::new()
}
}
impl X86FrameState {
pub fn new(method_ref: u64, bytecode_offset: u32) -> Self {
X86FrameState {
frame_size: 0,
num_locals: 0,
num_stack_slots: 0,
num_monitors: 0,
locals: Vec::new(),
stack: Vec::new(),
monitors: Vec::new(),
bytecode_offset,
method_ref,
}
}
pub fn add_local(
&mut self,
index: u32,
location: LocationRecord,
value_type: &str,
is_ref: bool,
) {
self.locals
.push(X86FrameSlot::new_local(index, location, value_type, is_ref));
self.num_locals = self.locals.len() as u32;
self.frame_size = self.frame_size.max(
(index + 1) * 8, );
}
pub fn add_stack_slot(
&mut self,
index: u32,
location: LocationRecord,
value_type: &str,
is_ref: bool,
) {
self.stack
.push(X86FrameSlot::new_stack(index, location, value_type, is_ref));
self.num_stack_slots = self.stack.len() as u32;
}
pub fn add_monitor(&mut self, index: u32, object_location: LocationRecord, reentrant: bool) {
self.monitors
.push(X86MonitorSlot::new(index, object_location, reentrant));
self.num_monitors = self.monitors.len() as u32;
}
pub fn get_live_refs(&self) -> Vec<&X86FrameSlot> {
let mut refs: Vec<&X86FrameSlot> = Vec::new();
refs.extend(self.locals.iter().filter(|s| s.is_reference && s.is_live));
refs.extend(self.stack.iter().filter(|s| s.is_reference && s.is_live));
refs
}
pub fn mark_all_dead(&mut self) {
for local in &mut self.locals {
local.mark_dead();
}
for slot in &mut self.stack {
slot.mark_dead();
}
}
}
impl X86DeoptState {
pub fn new(
compilation_unit_id: u32,
frame_state: X86FrameState,
register_state: X86RegisterState,
continuation_point: X86ContinuationPoint,
reason: DeoptReason,
) -> Self {
X86DeoptState {
magic: DEOPT_BUNDLE_MAGIC,
version: DEOPT_BUNDLE_VERSION,
compilation_unit_id,
frame_state,
register_state,
continuation_point,
reason,
metadata: Vec::new(),
}
}
pub fn set_metadata(&mut self, metadata: &[u8]) {
self.metadata = metadata.to_vec();
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&self.magic.to_le_bytes());
buf.extend_from_slice(&self.version.to_le_bytes());
buf.extend_from_slice(&self.compilation_unit_id.to_le_bytes());
buf.extend_from_slice(&self.frame_state.frame_size.to_le_bytes());
buf.extend_from_slice(&self.frame_state.num_locals.to_le_bytes());
buf.extend_from_slice(&self.frame_state.num_stack_slots.to_le_bytes());
buf.extend_from_slice(&self.frame_state.num_monitors.to_le_bytes());
buf.extend_from_slice(&self.frame_state.bytecode_offset.to_le_bytes());
buf.extend_from_slice(&self.frame_state.method_ref.to_le_bytes());
for local in &self.frame_state.locals {
buf.push(local.index as u8);
buf.push(local.is_reference as u8);
buf.push(local.is_live as u8);
buf.push(0); buf.extend_from_slice(&local.location.to_bytes());
}
for slot in &self.frame_state.stack {
buf.push(slot.index as u8);
buf.push(slot.is_reference as u8);
buf.push(slot.is_live as u8);
buf.push(0);
buf.extend_from_slice(&slot.location.to_bytes());
}
for monitor in &self.frame_state.monitors {
buf.push(monitor.index as u8);
buf.push(monitor.is_reentrant as u8);
buf.extend_from_slice(&[0u8; 2]);
buf.extend_from_slice(&monitor.object_location.to_bytes());
}
buf.extend_from_slice(&self.register_state.num_gprs.to_le_bytes());
buf.extend_from_slice(&self.register_state.num_xmms.to_le_bytes());
buf.extend_from_slice(&self.register_state.num_ymms.to_le_bytes());
buf.extend_from_slice(&self.register_state.num_zmms.to_le_bytes());
for gpr in &self.register_state.gprs {
buf.extend_from_slice(&gpr.dwarf_reg.to_le_bytes());
buf.push(gpr.is_reference as u8);
buf.extend_from_slice(&[0u8; 5]); buf.extend_from_slice(&gpr.value.to_le_bytes());
}
for xmm in &self.register_state.xmms {
buf.extend_from_slice(&xmm.dwarf_reg.to_le_bytes());
buf.extend_from_slice(&[0u8; 6]);
buf.extend_from_slice(&xmm.value.to_le_bytes());
}
buf.extend_from_slice(&self.continuation_point.bytecode_offset.to_le_bytes());
buf.extend_from_slice(&self.continuation_point.method_ref.to_le_bytes());
buf.push(self.continuation_point.dispatch_kind as u8);
buf.push(self.continuation_point.is_reexecute as u8);
buf.extend_from_slice(&[0u8; 2]); if let Some(handler) = self.continuation_point.exception_handler_offset {
buf.extend_from_slice(&handler.to_le_bytes());
} else {
buf.extend_from_slice(&0u32.to_le_bytes());
}
let reason_str = self.reason.description();
let reason_bytes = reason_str.as_bytes();
buf.extend_from_slice(&(reason_bytes.len() as u32).to_le_bytes());
buf.extend_from_slice(reason_bytes);
buf.extend_from_slice(&(self.metadata.len() as u32).to_le_bytes());
buf.extend_from_slice(&self.metadata);
buf
}
pub fn from_bytes(data: &[u8]) -> io::Result<Self> {
let mut cursor = Cursor::new(data);
let mut magic_buf = [0u8; 4];
cursor.read_exact(&mut magic_buf)?;
let magic = u32::from_le_bytes(magic_buf);
if magic != DEOPT_BUNDLE_MAGIC {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Invalid deopt bundle magic: 0x{:08X}", magic),
));
}
let mut ver_buf = [0u8; 2];
cursor.read_exact(&mut ver_buf)?;
let version = u16::from_le_bytes(ver_buf);
let mut cu_buf = [0u8; 4];
cursor.read_exact(&mut cu_buf)?;
let compilation_unit_id = u32::from_le_bytes(cu_buf);
let mut fs_size_buf = [0u8; 4];
cursor.read_exact(&mut fs_size_buf)?;
let frame_size = u32::from_le_bytes(fs_size_buf);
let mut nl_buf = [0u8; 4];
cursor.read_exact(&mut nl_buf)?;
let num_locals = u32::from_le_bytes(nl_buf);
let mut ns_buf = [0u8; 4];
cursor.read_exact(&mut ns_buf)?;
let num_stack_slots = u32::from_le_bytes(ns_buf);
let mut nm_buf = [0u8; 4];
cursor.read_exact(&mut nm_buf)?;
let num_monitors = u32::from_le_bytes(nm_buf);
let mut bc_buf = [0u8; 4];
cursor.read_exact(&mut bc_buf)?;
let bytecode_offset = u32::from_le_bytes(bc_buf);
let mut mr_buf = [0u8; 8];
cursor.read_exact(&mut mr_buf)?;
let method_ref = u64::from_le_bytes(mr_buf);
let mut frame_state = X86FrameState::new(method_ref, bytecode_offset);
frame_state.frame_size = frame_size;
for _ in 0..num_locals {
let mut idx_buf = [0u8; 1];
cursor.read_exact(&mut idx_buf)?;
let index = idx_buf[0] as u32;
let mut is_ref_buf = [0u8; 1];
cursor.read_exact(&mut is_ref_buf)?;
let is_reference = is_ref_buf[0] != 0;
let mut is_live_buf = [0u8; 1];
cursor.read_exact(&mut is_live_buf)?;
let is_live = is_live_buf[0] != 0;
cursor.seek(SeekFrom::Current(1))?; let location = LocationRecord::from_reader(&mut cursor)?;
let mut slot = X86FrameSlot::new_local(index, location, "unknown", is_reference);
if !is_live {
slot.mark_dead();
}
frame_state.locals.push(slot);
}
frame_state.num_locals = frame_state.locals.len() as u32;
for _ in 0..num_stack_slots {
let mut idx_buf = [0u8; 1];
cursor.read_exact(&mut idx_buf)?;
let index = idx_buf[0] as u32;
let mut is_ref_buf = [0u8; 1];
cursor.read_exact(&mut is_ref_buf)?;
let is_reference = is_ref_buf[0] != 0;
let mut is_live_buf = [0u8; 1];
cursor.read_exact(&mut is_live_buf)?;
let is_live = is_live_buf[0] != 0;
cursor.seek(SeekFrom::Current(1))?;
let location = LocationRecord::from_reader(&mut cursor)?;
let mut slot = X86FrameSlot::new_stack(index, location, "unknown", is_reference);
if !is_live {
slot.mark_dead();
}
frame_state.stack.push(slot);
}
frame_state.num_stack_slots = frame_state.stack.len() as u32;
for _ in 0..num_monitors {
let mut idx_buf = [0u8; 1];
cursor.read_exact(&mut idx_buf)?;
let index = idx_buf[0] as u32;
let mut re_buf = [0u8; 1];
cursor.read_exact(&mut re_buf)?;
let is_reentrant = re_buf[0] != 0;
cursor.seek(SeekFrom::Current(2))?;
let object_location = LocationRecord::from_reader(&mut cursor)?;
frame_state
.monitors
.push(X86MonitorSlot::new(index, object_location, is_reentrant));
}
frame_state.num_monitors = frame_state.monitors.len() as u32;
let mut ng_buf = [0u8; 4];
cursor.read_exact(&mut ng_buf)?;
let _num_gprs = u32::from_le_bytes(ng_buf);
let mut nx_buf = [0u8; 4];
cursor.read_exact(&mut nx_buf)?;
let _num_xmms = u32::from_le_bytes(nx_buf);
let mut ny_buf = [0u8; 4];
cursor.read_exact(&mut ny_buf)?;
let _num_ymms = u32::from_le_bytes(ny_buf);
let mut nz_buf = [0u8; 4];
cursor.read_exact(&mut nz_buf)?;
let _num_zmms = u32::from_le_bytes(nz_buf);
let mut register_state = X86RegisterState::new();
for _ in 0.._num_gprs {
let mut reg_buf = [0u8; 2];
cursor.read_exact(&mut reg_buf)?;
let dwarf_reg = u16::from_le_bytes(reg_buf);
let mut ref_buf = [0u8; 1];
cursor.read_exact(&mut ref_buf)?;
let is_ref = ref_buf[0] != 0;
cursor.seek(SeekFrom::Current(5))?;
let mut val_buf = [0u8; 8];
cursor.read_exact(&mut val_buf)?;
let value = u64::from_le_bytes(val_buf);
register_state.add_gpr(dwarf_reg, value, is_ref);
}
let mut cp_bc_buf = [0u8; 4];
cursor.read_exact(&mut cp_bc_buf)?;
let cp_bytecode_offset = u32::from_le_bytes(cp_bc_buf);
let mut cp_mr_buf = [0u8; 8];
cursor.read_exact(&mut cp_mr_buf)?;
let cp_method_ref = u64::from_le_bytes(cp_mr_buf);
let mut kind_buf = [0u8; 1];
cursor.read_exact(&mut kind_buf)?;
let dispatch_kind_val = kind_buf[0];
let mut re_buf = [0u8; 1];
cursor.read_exact(&mut re_buf)?;
let is_reexecute = re_buf[0] != 0;
cursor.seek(SeekFrom::Current(2))?;
let mut eh_buf = [0u8; 4];
cursor.read_exact(&mut eh_buf)?;
let eh_offset = u32::from_le_bytes(eh_buf);
let dispatch_kind = match dispatch_kind_val {
0 => X86DeoptDispatchKind::Interpreter,
1 => X86DeoptDispatchKind::BaselineJIT,
2 => X86DeoptDispatchKind::OptimizingJIT,
3 => X86DeoptDispatchKind::ExceptionHandler,
4 => X86DeoptDispatchKind::UnwindStub,
_ => X86DeoptDispatchKind::Interpreter,
};
let continuation_point = X86ContinuationPoint {
bytecode_offset: cp_bytecode_offset,
method_ref: cp_method_ref,
dispatch_kind,
exception_handler_offset: if eh_offset != 0 {
Some(eh_offset)
} else {
None
},
is_reexecute,
};
let mut reason_len_buf = [0u8; 4];
cursor.read_exact(&mut reason_len_buf)?;
let reason_len = u32::from_le_bytes(reason_len_buf) as usize;
let mut reason_bytes = vec![0u8; reason_len];
cursor.read_exact(&mut reason_bytes)?;
let reason_str = String::from_utf8_lossy(&reason_bytes).to_string();
let reason = DeoptReason::Other(reason_str);
let mut meta_len_buf = [0u8; 4];
cursor.read_exact(&mut meta_len_buf)?;
let meta_len = u32::from_le_bytes(meta_len_buf) as usize;
let mut metadata = vec![0u8; meta_len];
cursor.read_exact(&mut metadata)?;
Ok(X86DeoptState {
magic,
version,
compilation_unit_id,
frame_state,
register_state,
continuation_point,
reason,
metadata,
})
}
pub fn get_gc_references(&self) -> Vec<(String, u64)> {
let mut refs = Vec::new();
for local in &self.frame_state.locals {
if local.is_reference && local.is_live {
refs.push((format!("local[{}]", local.index), 0));
}
}
for slot in &self.frame_state.stack {
if slot.is_reference && slot.is_live {
refs.push((format!("stack[{}]", slot.index), 0));
}
}
for gpr in &self.register_state.gprs {
if gpr.is_reference {
refs.push((format!("Reg({})", gpr.dwarf_reg), gpr.value));
}
}
refs
}
pub fn validate(&self) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
if self.magic != DEOPT_BUNDLE_MAGIC {
errors.push(format!(
"Invalid magic: 0x{:08X} (expected 0x{:08X})",
self.magic, DEOPT_BUNDLE_MAGIC
));
}
if self.version != DEOPT_BUNDLE_VERSION {
errors.push(format!(
"Invalid version: {} (expected {})",
self.version, DEOPT_BUNDLE_VERSION
));
}
if self.frame_state.num_locals != self.frame_state.locals.len() as u32 {
errors.push(format!(
"Local count mismatch: header says {}, but {} locals present",
self.frame_state.num_locals,
self.frame_state.locals.len(),
));
}
if self.frame_state.num_stack_slots != self.frame_state.stack.len() as u32 {
errors.push(format!(
"Stack slot count mismatch: header says {}, but {} slots present",
self.frame_state.num_stack_slots,
self.frame_state.stack.len(),
));
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
#[derive(Debug, Clone)]
pub struct X86StackMapWriter {
pub alignment: u32,
pub flags: u64,
pub read_only: bool,
}
impl X86StackMapWriter {
pub fn new() -> Self {
X86StackMapWriter {
alignment: 8,
flags: 0, read_only: true,
}
}
pub fn write_header<W: Write>(
&self,
writer: &mut W,
generator: &mut X86StackMapGenerator,
) -> io::Result<()> {
let section = generator.stack_maps.emit_section();
writer.write_all(§ion)?;
Ok(())
}
pub fn write_section<W: Write>(
&self,
writer: &mut W,
format: &X86StackMapFormat,
) -> io::Result<()> {
let bytes = format.to_bytes();
writer.write_all(&bytes)?;
Ok(())
}
pub fn generate_elf_section_header(&self, offset: u64, size: u64) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&1u32.to_le_bytes());
buf.extend_from_slice(&(2u64).to_le_bytes());
buf.extend_from_slice(&0u64.to_le_bytes());
buf.extend_from_slice(&offset.to_le_bytes());
buf.extend_from_slice(&size.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&self.alignment.to_le_bytes());
buf.extend_from_slice(&0u64.to_le_bytes());
buf
}
}
impl Default for X86StackMapWriter {
fn default() -> Self {
X86StackMapWriter::new()
}
}
#[derive(Debug, Clone)]
pub struct X86StackMapVerifier {
pub stack_maps: X86StackMaps,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl X86StackMapVerifier {
pub fn new(stack_maps: X86StackMaps) -> Self {
X86StackMapVerifier {
stack_maps,
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn verify(&mut self) -> bool {
self.errors.clear();
self.warnings.clear();
self.check_header();
self.check_function_records();
self.check_location_records();
self.check_constant_pool();
self.check_id_uniqueness();
self.check_live_out_registers();
self.check_stack_size_consistency();
self.errors.is_empty()
}
fn check_header(&mut self) {
let h = &self.stack_maps.format.header;
if h.version != STACK_MAP_VERSION {
self.errors.push(format!(
"Unsupported stack map version: {} (expected {})",
h.version, STACK_MAP_VERSION
));
}
if h.reserved1 != 0 || h.reserved2 != 0 {
self.errors
.push("Reserved header fields are non-zero".to_string());
}
}
fn check_function_records(&mut self) {
for func in &self.stack_maps.format.functions {
if func.stack_size > MAX_FRAME_SIZE {
self.errors.push(format!(
"Function at 0x{:016X}: stack size {} exceeds maximum {}",
func.function_address, func.stack_size, MAX_FRAME_SIZE
));
}
if func.record_count == 0 {
self.warnings.push(format!(
"Function at 0x{:016X} has no stack map records",
func.function_address
));
}
}
}
fn check_location_records(&mut self) {
for record in &self.stack_maps.format.records {
for loc in &record.locations {
if loc.size == 0 && loc.kind != LocationKind::ConstantIndex {
self.warnings.push(format!(
"Record ID {}: location has size 0 (kind: {})",
record.id,
loc.kind.name()
));
}
}
if record.locations.len() > MAX_LOCATIONS_PER_RECORD {
self.errors.push(format!(
"Record ID {}: {} locations exceeds maximum {}",
record.id,
record.locations.len(),
MAX_LOCATIONS_PER_RECORD
));
}
}
}
fn check_constant_pool(&mut self) {
for record in &self.stack_maps.format.records {
for loc in &record.locations {
if loc.kind == LocationKind::ConstantIndex {
let index = loc.offset as u32;
if self.stack_maps.format.get_constant(index).is_none() {
self.errors.push(format!(
"Record ID {}: constant index {} out of bounds",
record.id, index
));
}
}
}
}
}
fn check_id_uniqueness(&mut self) {
let mut seen: HashMap<u64, usize> = HashMap::new();
for (i, record) in self.stack_maps.format.records.iter().enumerate() {
if let Some(prev) = seen.get(&record.id) {
self.errors.push(format!(
"Duplicate stack map ID {} (records {} and {})",
record.id, prev, i
));
} else {
seen.insert(record.id, i);
}
}
}
fn check_live_out_registers(&mut self) {
for record in &self.stack_maps.format.records {
for live_out in &record.live_outs {
if live_out.dwarf_reg_num > 112 {
self.warnings.push(format!(
"Record ID {}: live-out register {} may be invalid",
record.id, live_out.dwarf_reg_num
));
}
}
}
}
fn check_stack_size_consistency(&mut self) {
for func in &self.stack_maps.format.functions {
if func.stack_size_count > 0
&& func.stack_size_count as usize != self.stack_maps.format.stack_sizes.len()
{
}
}
}
pub fn report(&self) -> String {
let mut report = String::new();
report.push_str("=== Stack Map Verification Report ===\n\n");
if self.errors.is_empty() && self.warnings.is_empty() {
report.push_str("PASS: No errors or warnings.\n");
} else {
if !self.errors.is_empty() {
report.push_str(&format!("ERRORS ({}):\n", self.errors.len()));
for err in &self.errors {
report.push_str(&format!(" - {}\n", err));
}
report.push('\n');
}
if !self.warnings.is_empty() {
report.push_str(&format!("WARNINGS ({}):\n", self.warnings.len()));
for warn in &self.warnings {
report.push_str(&format!(" - {}\n", warn));
}
}
}
report
}
}
#[derive(Debug, Clone)]
pub struct X86StackMapScenario {
pub function_name: String,
pub generator: X86StackMapGenerator,
pub patchpoints: Vec<X86PatchPoint>,
pub statepoints: Vec<X86StatePoint>,
pub safepoints: Vec<X86SafepointLocation>,
pub stack_sizes: Vec<(u32, u64)>,
}
impl X86StackMapScenario {
pub fn new(function_name: &str, frame_size: u64, use_64bit: bool) -> Self {
let mut generator = X86StackMapGenerator::new(use_64bit);
generator.begin_function(function_name, frame_size);
X86StackMapScenario {
function_name: function_name.to_string(),
generator,
patchpoints: Vec::new(),
statepoints: Vec::new(),
safepoints: Vec::new(),
stack_sizes: Vec::new(),
}
}
pub fn add_patchpoint(&mut self, pp: X86PatchPoint) {
self.generator
.generate_from_patchpoint(&self.function_name, &pp);
self.patchpoints.push(pp);
}
pub fn add_statepoint(&mut self, sp: X86StatePoint) {
self.generator
.generate_from_statepoint(&self.function_name, &sp);
self.statepoints.push(sp);
}
pub fn add_entry_safepoint(&mut self) {
let id = self.generator.stack_maps.allocate_id();
let (sp, _) = self
.generator
.safepoint
.insert_entry_safepoint(&self.function_name, id);
self.generator
.generate_from_safepoint(&self.function_name, &sp);
self.safepoints.push(sp);
}
pub fn add_backedge_safepoint(&mut self, offset: u32) {
let id = self.generator.stack_maps.allocate_id();
let (sp, _) =
self.generator
.safepoint
.insert_backedge_safepoint(&self.function_name, offset, id);
self.generator
.generate_from_safepoint(&self.function_name, &sp);
self.safepoints.push(sp);
}
pub fn record_stack_size(&mut self, offset: u32, size: u64) {
self.generator
.record_stack_size_change(&self.function_name, size as u64);
self.stack_sizes.push((offset, size));
}
pub fn finalize(&mut self) -> Result<Vec<u8>, Vec<String>> {
self.generator.end_function();
self.generator
.finalize()
.map(|()| self.generator.generate_section())
}
pub fn generator(&self) -> &X86StackMapGenerator {
&self.generator
}
pub fn generator_mut(&mut self) -> &mut X86StackMapGenerator {
&mut self.generator
}
}
#[derive(Debug, Clone)]
pub struct X86FrameMap {
pub frame_size: u64,
pub num_locals: usize,
pub num_stack_slots: usize,
pub num_monitors: usize,
pub slots: Vec<X86FrameMapSlot>,
pub register_save_area: X86RASaveArea,
pub reg_to_slot: BTreeMap<u16, usize>,
pub stack_to_slot: BTreeMap<i32, usize>,
pub deopt_bundle: Option<X86DeoptBundle>,
pub validated: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X86FrameMapSlot {
pub index: usize,
pub slot_kind: X86FrameSlotKind,
pub location: X86FrameValueLocation,
pub value_type: String,
pub is_reference: bool,
pub is_live: bool,
pub size: u32,
pub debug_name: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86FrameSlotKind {
Local,
Stack,
Monitor,
CalleeSaveSpill,
ReturnAddress,
FramePointerSave,
}
impl X86FrameSlotKind {
pub fn name(&self) -> &'static str {
match self {
X86FrameSlotKind::Local => "Local",
X86FrameSlotKind::Stack => "Stack",
X86FrameSlotKind::Monitor => "Monitor",
X86FrameSlotKind::CalleeSaveSpill => "CalleeSaveSpill",
X86FrameSlotKind::ReturnAddress => "ReturnAddress",
X86FrameSlotKind::FramePointerSave => "FramePointerSave",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86FrameValueLocation {
Register { dwarf_reg: u16 },
Stack { offset: i32 },
Constant { value: i64 },
Undef,
Split {
locations: Vec<X86FrameValueLocation>,
},
}
impl X86FrameValueLocation {
pub fn is_register(&self) -> bool {
matches!(self, X86FrameValueLocation::Register { .. })
}
pub fn is_stack(&self) -> bool {
matches!(self, X86FrameValueLocation::Stack { .. })
}
pub fn is_constant(&self) -> bool {
matches!(self, X86FrameValueLocation::Constant { .. })
}
pub fn dwarf_reg(&self) -> Option<u16> {
match self {
X86FrameValueLocation::Register { dwarf_reg } => Some(*dwarf_reg),
_ => None,
}
}
pub fn stack_offset(&self) -> Option<i32> {
match self {
X86FrameValueLocation::Stack { offset } => Some(*offset),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct X86RASaveArea {
pub base_offset: i32,
pub total_size: u32,
pub gpr_saves: BTreeMap<u16, i32>,
pub xmm_saves: BTreeMap<u16, i32>,
pub ymm_saves: BTreeMap<u16, i32>,
pub saves_rflags: bool,
pub rflags_offset: Option<i32>,
pub saves_mxcsr: bool,
pub mxcsr_offset: Option<i32>,
pub alignment: u32,
}
#[derive(Debug, Clone)]
pub struct X86DeoptBundle {
pub magic: u32,
pub version: u16,
pub reason: DeoptReason,
pub frame_state_bytes: Vec<u8>,
pub num_register_values: u16,
pub num_stack_values: u16,
pub register_state_bytes: Vec<u8>,
pub continuation_bytes: Vec<u8>,
pub compilation_unit_id: u32,
}
#[derive(Debug)]
pub struct X86FrameReconstructor {
pub frame_map: X86FrameMap,
pub register_values: BTreeMap<u16, i64>,
pub stack_memory: BTreeMap<i32, Vec<u8>>,
pub reconstructed_slots: Vec<Option<i64>>,
pub success: bool,
}
impl Default for X86FrameSlotKind {
fn default() -> Self {
X86FrameSlotKind::Local
}
}
impl Default for X86FrameMapSlot {
fn default() -> Self {
X86FrameMapSlot {
index: 0,
slot_kind: X86FrameSlotKind::Local,
location: X86FrameValueLocation::Undef,
value_type: String::new(),
is_reference: false,
is_live: true,
size: 8,
debug_name: None,
}
}
}
impl X86FrameMapSlot {
pub fn new_local(index: usize, value_type: &str, size: u32) -> Self {
X86FrameMapSlot {
index,
slot_kind: X86FrameSlotKind::Local,
location: X86FrameValueLocation::Undef,
value_type: value_type.to_string(),
is_reference: false,
is_live: true,
size,
debug_name: None,
}
}
pub fn new_stack(index: usize, value_type: &str, size: u32) -> Self {
X86FrameMapSlot {
index,
slot_kind: X86FrameSlotKind::Stack,
location: X86FrameValueLocation::Undef,
value_type: value_type.to_string(),
is_reference: false,
is_live: true,
size,
debug_name: None,
}
}
pub fn new_monitor(index: usize) -> Self {
X86FrameMapSlot {
index,
slot_kind: X86FrameSlotKind::Monitor,
location: X86FrameValueLocation::Undef,
value_type: "ObjectReference".to_string(),
is_reference: true,
is_live: true,
size: 8,
debug_name: None,
}
}
pub fn set_location(&mut self, loc: X86FrameValueLocation) {
let is_live = !matches!(loc, X86FrameValueLocation::Undef);
self.location = loc;
self.is_live = is_live;
}
pub fn mark_reference(&mut self) {
self.is_reference = true;
}
pub fn mark_dead(&mut self) {
self.is_live = false;
self.location = X86FrameValueLocation::Undef;
}
pub fn set_debug_name(&mut self, name: &str) {
self.debug_name = Some(name.to_string());
}
}
impl Default for X86RASaveArea {
fn default() -> Self {
X86RASaveArea {
base_offset: -8,
total_size: 0,
gpr_saves: BTreeMap::new(),
xmm_saves: BTreeMap::new(),
ymm_saves: BTreeMap::new(),
saves_rflags: false,
rflags_offset: None,
saves_mxcsr: false,
mxcsr_offset: None,
alignment: 16,
}
}
}
impl X86RASaveArea {
pub fn new(base_offset: i32) -> Self {
X86RASaveArea {
base_offset,
total_size: 0,
gpr_saves: BTreeMap::new(),
xmm_saves: BTreeMap::new(),
ymm_saves: BTreeMap::new(),
saves_rflags: false,
rflags_offset: None,
saves_mxcsr: false,
mxcsr_offset: None,
alignment: 16,
}
}
pub fn save_gpr(&mut self, dwarf_reg: u16, offset: i32) {
self.gpr_saves.insert(dwarf_reg, offset);
self.total_size = self.total_size.max((offset.abs() + 8) as u32);
}
pub fn save_xmm(&mut self, dwarf_reg: u16, offset: i32) {
self.xmm_saves.insert(dwarf_reg, offset);
self.total_size = self.total_size.max((offset.abs() + 16) as u32);
}
pub fn save_ymm(&mut self, dwarf_reg: u16, offset: i32) {
self.ymm_saves.insert(dwarf_reg, offset);
self.total_size = self.total_size.max((offset.abs() + 16) as u32);
}
pub fn save_rflags(&mut self, offset: i32) {
self.saves_rflags = true;
self.rflags_offset = Some(offset);
self.total_size = self.total_size.max((offset.abs() + 8) as u32);
}
pub fn save_mxcsr(&mut self, offset: i32) {
self.saves_mxcsr = true;
self.mxcsr_offset = Some(offset);
self.total_size = self.total_size.max((offset.abs() + 4) as u32);
}
pub fn gpr_offset(&self, dwarf_reg: u16) -> Option<i32> {
self.gpr_saves.get(&dwarf_reg).copied()
}
pub fn xmm_offset(&self, dwarf_reg: u16) -> Option<i32> {
self.xmm_saves.get(&dwarf_reg).copied()
}
pub fn total_saved_regs(&self) -> usize {
self.gpr_saves.len() + self.xmm_saves.len() + self.ymm_saves.len()
}
pub fn align_size(&mut self) {
let align = self.alignment as u32;
if self.total_size % align != 0 {
self.total_size = (self.total_size + align - 1) & !(align - 1);
}
}
}
impl Default for X86DeoptBundle {
fn default() -> Self {
X86DeoptBundle {
magic: DEOPT_BUNDLE_MAGIC,
version: DEOPT_BUNDLE_VERSION,
reason: DeoptReason::ExplicitDeopt {
reason: String::new(),
},
frame_state_bytes: Vec::new(),
num_register_values: 0,
num_stack_values: 0,
register_state_bytes: Vec::new(),
continuation_bytes: Vec::new(),
compilation_unit_id: 0,
}
}
}
impl X86DeoptBundle {
pub fn new(reason: DeoptReason, compilation_unit_id: u32) -> Self {
X86DeoptBundle {
magic: DEOPT_BUNDLE_MAGIC,
version: DEOPT_BUNDLE_VERSION,
reason,
frame_state_bytes: Vec::new(),
num_register_values: 0,
num_stack_values: 0,
register_state_bytes: Vec::new(),
continuation_bytes: Vec::new(),
compilation_unit_id,
}
}
pub fn set_frame_state(&mut self, bytes: Vec<u8>, num_regs: u16, num_stacks: u16) {
self.frame_state_bytes = bytes;
self.num_register_values = num_regs;
self.num_stack_values = num_stacks;
}
pub fn set_register_state(&mut self, bytes: Vec<u8>) {
self.register_state_bytes = bytes;
}
pub fn set_continuation(&mut self, bytes: Vec<u8>) {
self.continuation_bytes = bytes;
}
pub fn validate(&self) -> bool {
self.magic == DEOPT_BUNDLE_MAGIC && self.version == DEOPT_BUNDLE_VERSION
}
pub fn total_size(&self) -> usize {
4 + 2 + self.frame_state_bytes.len()
+ 2 + 2 + self.register_state_bytes.len()
+ self.continuation_bytes.len()
+ 4 }
}
impl Default for X86FrameMap {
fn default() -> Self {
X86FrameMap {
frame_size: 0,
num_locals: 0,
num_stack_slots: 0,
num_monitors: 0,
slots: Vec::new(),
register_save_area: X86RASaveArea::default(),
reg_to_slot: BTreeMap::new(),
stack_to_slot: BTreeMap::new(),
deopt_bundle: None,
validated: false,
}
}
}
impl X86FrameMap {
pub fn new(frame_size: u64) -> Self {
X86FrameMap {
frame_size,
num_locals: 0,
num_stack_slots: 0,
num_monitors: 0,
slots: Vec::new(),
register_save_area: X86RASaveArea::new(-(frame_size as i32)),
reg_to_slot: BTreeMap::new(),
stack_to_slot: BTreeMap::new(),
deopt_bundle: None,
validated: false,
}
}
pub fn add_local(&mut self, value_type: &str, size: u32) -> usize {
let index = self.slots.len();
let slot = X86FrameMapSlot::new_local(index, value_type, size);
self.slots.push(slot);
self.num_locals += 1;
index
}
pub fn add_stack_slot(&mut self, value_type: &str, size: u32) -> usize {
let index = self.slots.len();
let slot = X86FrameMapSlot::new_stack(index, value_type, size);
self.slots.push(slot);
self.num_stack_slots += 1;
index
}
pub fn add_monitor(&mut self) -> usize {
let index = self.slots.len();
let slot = X86FrameMapSlot::new_monitor(index);
self.slots.push(slot);
self.num_monitors += 1;
index
}
pub fn map_register_to_slot(&mut self, dwarf_reg: u16, slot_index: usize) {
self.reg_to_slot.insert(dwarf_reg, slot_index);
if slot_index < self.slots.len() {
self.slots[slot_index].set_location(X86FrameValueLocation::Register { dwarf_reg });
}
}
pub fn map_stack_to_slot(&mut self, offset: i32, slot_index: usize) {
self.stack_to_slot.insert(offset, slot_index);
if slot_index < self.slots.len() {
self.slots[slot_index].set_location(X86FrameValueLocation::Stack { offset });
}
}
pub fn set_deopt_bundle(&mut self, bundle: X86DeoptBundle) {
self.deopt_bundle = Some(bundle);
}
pub fn reference_slots(&self) -> Vec<&X86FrameMapSlot> {
self.slots
.iter()
.filter(|s| s.is_reference && s.is_live)
.collect()
}
pub fn slot_for_register(&self, dwarf_reg: u16) -> Option<&X86FrameMapSlot> {
self.reg_to_slot
.get(&dwarf_reg)
.and_then(|&idx| self.slots.get(idx))
}
pub fn slot_for_stack_offset(&self, offset: i32) -> Option<&X86FrameMapSlot> {
self.stack_to_slot
.get(&offset)
.and_then(|&idx| self.slots.get(idx))
}
pub fn total_slots(&self) -> usize {
self.slots.len()
}
pub fn validate(&mut self) -> bool {
self.validated = true;
if let Some(ref bundle) = self.deopt_bundle {
if !bundle.validate() {
self.validated = false;
return false;
}
}
true
}
pub fn generate_save_area_cfi(&self) -> Vec<u8> {
let mut cfi = Vec::new();
for (®, &offset) in &self.register_save_area.gpr_saves {
cfi.extend_from_slice(&[0x80 | (reg as u8 & 0x3F), (offset & 0x7F) as u8]);
}
for (®, &offset) in &self.register_save_area.xmm_saves {
cfi.push(0x90);
cfi.push(reg as u8);
cfi.push((offset & 0x7F) as u8);
}
cfi
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&(self.frame_size as u32).to_le_bytes());
buf.extend_from_slice(&(self.slots.len() as u32).to_le_bytes());
for slot in &self.slots {
buf.push(if slot.is_reference { 1 } else { 0 });
buf.push(if slot.is_live { 1 } else { 0 });
buf.extend_from_slice(&slot.size.to_le_bytes());
let kind_byte = match slot.slot_kind {
X86FrameSlotKind::Local => 0u8,
X86FrameSlotKind::Stack => 1,
X86FrameSlotKind::Monitor => 2,
X86FrameSlotKind::CalleeSaveSpill => 3,
X86FrameSlotKind::ReturnAddress => 4,
X86FrameSlotKind::FramePointerSave => 5,
};
buf.push(kind_byte);
match &slot.location {
X86FrameValueLocation::Register { dwarf_reg } => {
buf.push(0);
buf.extend_from_slice(&dwarf_reg.to_le_bytes());
}
X86FrameValueLocation::Stack { offset } => {
buf.push(1);
buf.extend_from_slice(&offset.to_le_bytes());
}
X86FrameValueLocation::Constant { value } => {
buf.push(2);
buf.extend_from_slice(&value.to_le_bytes());
}
X86FrameValueLocation::Undef => {
buf.push(3);
}
X86FrameValueLocation::Split { locations } => {
buf.push(4);
buf.extend_from_slice(&(locations.len() as u32).to_le_bytes());
}
}
}
buf
}
}
impl X86FrameReconstructor {
pub fn new(frame_map: X86FrameMap) -> Self {
let slot_count = frame_map.slots.len();
X86FrameReconstructor {
frame_map,
register_values: BTreeMap::new(),
stack_memory: BTreeMap::new(),
reconstructed_slots: vec![None; slot_count],
success: false,
}
}
pub fn set_register(&mut self, dwarf_reg: u16, value: i64) {
self.register_values.insert(dwarf_reg, value);
}
pub fn set_stack_memory(&mut self, offset: i32, bytes: Vec<u8>) {
self.stack_memory.insert(offset, bytes);
}
pub fn reconstruct(&mut self) -> bool {
for (slot_idx, slot) in self.frame_map.slots.iter().enumerate() {
if !slot.is_live {
continue;
}
let value = match &slot.location {
X86FrameValueLocation::Register { dwarf_reg } => {
self.register_values.get(dwarf_reg).copied()
}
X86FrameValueLocation::Stack { offset } => {
self.stack_memory.get(offset).and_then(|bytes| {
if bytes.len() >= 8 {
Some(i64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5],
bytes[6], bytes[7],
]))
} else {
None
}
})
}
X86FrameValueLocation::Constant { value } => Some(*value),
X86FrameValueLocation::Undef => None,
X86FrameValueLocation::Split { .. } => None,
};
self.reconstructed_slots[slot_idx] = value;
}
self.success = self
.reconstructed_slots
.iter()
.enumerate()
.all(|(idx, v)| !self.frame_map.slots[idx].is_live || v.is_some());
self.success
}
pub fn get_slot_value(&self, index: usize) -> Option<i64> {
self.reconstructed_slots.get(index).copied().flatten()
}
pub fn get_gc_references(&self) -> Vec<i64> {
self.frame_map
.slots
.iter()
.enumerate()
.filter(|(_, s)| s.is_reference && s.is_live)
.filter_map(|(idx, _)| self.reconstructed_slots.get(idx).copied().flatten())
.collect()
}
}
#[derive(Debug, Clone)]
pub struct X86EHStackMap {
pub function_name: String,
pub landing_pads: BTreeMap<u32, X86LandingPadMap>,
pub personality: Option<X86PersonalityFuncMap>,
pub cleanups: BTreeMap<u32, X86CleanupMap>,
pub lsda_offset: Option<u32>,
pub has_eh: bool,
}
#[derive(Debug, Clone)]
pub struct X86LandingPadMap {
pub offset: u32,
pub stack_map_id: u64,
pub clauses: Vec<X86LandingPadClause>,
pub exception_pointer_reg: Option<u16>,
pub selector_reg: Option<u16>,
pub exception_object_spill: Option<i32>,
pub is_cleanup: bool,
pub is_catch_all: bool,
}
#[derive(Debug, Clone)]
pub enum X86LandingPadClause {
Catch {
type_info_ptr: u64,
type_name: String,
},
Filter {
type_info_ptrs: Vec<u64>,
type_names: Vec<String>,
},
Cleanup,
}
impl X86LandingPadClause {
pub fn is_catch(&self) -> bool {
matches!(self, X86LandingPadClause::Catch { .. })
}
pub fn is_filter(&self) -> bool {
matches!(self, X86LandingPadClause::Filter { .. })
}
pub fn is_cleanup(&self) -> bool {
matches!(self, X86LandingPadClause::Cleanup)
}
}
#[derive(Debug, Clone)]
pub struct X86PersonalityFuncMap {
pub personality_func: String,
pub is_gcc_personality: bool,
pub is_seh_personality: bool,
pub language: Option<String>,
pub got_offset: Option<u32>,
}
impl Default for X86PersonalityFuncMap {
fn default() -> Self {
X86PersonalityFuncMap {
personality_func: "__gxx_personality_v0".to_string(),
is_gcc_personality: true,
is_seh_personality: false,
language: Some("C++".to_string()),
got_offset: None,
}
}
}
#[derive(Debug, Clone)]
pub struct X86CleanupMap {
pub offset: u32,
pub stack_map_id: u64,
pub destroys_locals: bool,
pub releases_monitors: bool,
pub calls_destructors: bool,
pub destructor_calls: Vec<(i32, String)>,
pub run_on_normal_exit: bool,
}
impl Default for X86EHStackMap {
fn default() -> Self {
X86EHStackMap {
function_name: String::new(),
landing_pads: BTreeMap::new(),
personality: None,
cleanups: BTreeMap::new(),
lsda_offset: None,
has_eh: false,
}
}
}
impl X86EHStackMap {
pub fn new(function_name: &str) -> Self {
X86EHStackMap {
function_name: function_name.to_string(),
landing_pads: BTreeMap::new(),
personality: None,
cleanups: BTreeMap::new(),
lsda_offset: None,
has_eh: false,
}
}
pub fn add_landing_pad(&mut self, offset: u32, lp: X86LandingPadMap) {
self.has_eh = true;
self.landing_pads.insert(offset, lp);
}
pub fn set_personality(&mut self, personality: X86PersonalityFuncMap) {
self.has_eh = true;
self.personality = Some(personality);
}
pub fn add_cleanup(&mut self, offset: u32, cleanup: X86CleanupMap) {
self.has_eh = true;
self.cleanups.insert(offset, cleanup);
}
pub fn set_lsda_offset(&mut self, offset: u32) {
self.lsda_offset = Some(offset);
}
pub fn landing_pad_at(&self, offset: u32) -> Option<&X86LandingPadMap> {
self.landing_pads.get(&offset)
}
pub fn landing_pad_count(&self) -> usize {
self.landing_pads.len()
}
pub fn cleanup_count(&self) -> usize {
self.cleanups.len()
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.push(if self.has_eh { 1 } else { 0 });
buf.push(self.landing_pads.len() as u8);
buf.push(self.cleanups.len() as u8);
buf.push(if self.personality.is_some() { 1 } else { 0 });
for (&offset, lp) in &self.landing_pads {
buf.extend_from_slice(&offset.to_le_bytes());
buf.extend_from_slice(&lp.stack_map_id.to_le_bytes());
buf.push(if lp.is_cleanup { 1 } else { 0 });
buf.push(if lp.is_catch_all { 1 } else { 0 });
buf.push(lp.clauses.len() as u8);
for clause in &lp.clauses {
match clause {
X86LandingPadClause::Catch { type_info_ptr, .. } => {
buf.push(0);
buf.extend_from_slice(&type_info_ptr.to_le_bytes());
}
X86LandingPadClause::Filter { type_info_ptrs, .. } => {
buf.push(1);
buf.extend_from_slice(&(type_info_ptrs.len() as u32).to_le_bytes());
}
X86LandingPadClause::Cleanup => {
buf.push(2);
}
}
}
}
for (&offset, cleanup) in &self.cleanups {
buf.extend_from_slice(&offset.to_le_bytes());
buf.extend_from_slice(&cleanup.stack_map_id.to_le_bytes());
let mut flags: u8 = 0;
if cleanup.destroys_locals {
flags |= 1;
}
if cleanup.releases_monitors {
flags |= 2;
}
if cleanup.calls_destructors {
flags |= 4;
}
if cleanup.run_on_normal_exit {
flags |= 8;
}
buf.push(flags);
}
buf
}
}
impl Default for X86LandingPadMap {
fn default() -> Self {
X86LandingPadMap {
offset: 0,
stack_map_id: 0,
clauses: Vec::new(),
exception_pointer_reg: None,
selector_reg: None,
exception_object_spill: None,
is_cleanup: false,
is_catch_all: false,
}
}
}
impl X86LandingPadMap {
pub fn new(offset: u32, stack_map_id: u64) -> Self {
X86LandingPadMap {
offset,
stack_map_id,
clauses: Vec::new(),
exception_pointer_reg: None,
selector_reg: None,
exception_object_spill: None,
is_cleanup: false,
is_catch_all: false,
}
}
pub fn add_catch(&mut self, type_info_ptr: u64, type_name: &str) {
self.clauses.push(X86LandingPadClause::Catch {
type_info_ptr,
type_name: type_name.to_string(),
});
}
pub fn add_filter(&mut self, type_info_ptrs: Vec<u64>, type_names: Vec<String>) {
self.clauses.push(X86LandingPadClause::Filter {
type_info_ptrs,
type_names,
});
}
pub fn add_cleanup_clause(&mut self) {
self.is_cleanup = true;
self.clauses.push(X86LandingPadClause::Cleanup);
}
pub fn set_catch_all(&mut self) {
self.is_catch_all = true;
}
pub fn set_exception_pointer_reg(&mut self, reg: u16) {
self.exception_pointer_reg = Some(reg);
}
pub fn set_selector_reg(&mut self, reg: u16) {
self.selector_reg = Some(reg);
}
pub fn set_exception_object_spill(&mut self, offset: i32) {
self.exception_object_spill = Some(offset);
}
}
impl Default for X86CleanupMap {
fn default() -> Self {
X86CleanupMap {
offset: 0,
stack_map_id: 0,
destroys_locals: false,
releases_monitors: false,
calls_destructors: false,
destructor_calls: Vec::new(),
run_on_normal_exit: false,
}
}
}
impl X86CleanupMap {
pub fn new(offset: u32, stack_map_id: u64) -> Self {
X86CleanupMap {
offset,
stack_map_id,
destroys_locals: false,
releases_monitors: false,
calls_destructors: false,
destructor_calls: Vec::new(),
run_on_normal_exit: false,
}
}
pub fn mark_destroys_locals(&mut self) {
self.destroys_locals = true;
}
pub fn mark_releases_monitors(&mut self) {
self.releases_monitors = true;
}
pub fn add_destructor_call(&mut self, stack_offset: i32, type_name: &str) {
self.calls_destructors = true;
self.destructor_calls
.push((stack_offset, type_name.to_string()));
}
pub fn mark_run_on_normal_exit(&mut self) {
self.run_on_normal_exit = true;
}
}
#[derive(Debug, Clone)]
pub struct X86EHMapGenerator {
pub eh_maps: BTreeMap<String, X86EHStackMap>,
pub enabled: bool,
}
impl Default for X86EHMapGenerator {
fn default() -> Self {
X86EHMapGenerator {
eh_maps: BTreeMap::new(),
enabled: true,
}
}
}
impl X86EHMapGenerator {
pub fn new(enabled: bool) -> Self {
X86EHMapGenerator {
eh_maps: BTreeMap::new(),
enabled,
}
}
pub fn get_or_create(&mut self, function_name: &str) -> &mut X86EHStackMap {
self.eh_maps
.entry(function_name.to_string())
.or_insert_with(|| X86EHStackMap::new(function_name))
}
pub fn emit_section(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&(self.eh_maps.len() as u32).to_le_bytes());
for (name, eh_map) in &self.eh_maps {
let name_bytes = name.as_bytes();
buf.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
buf.extend_from_slice(name_bytes);
buf.extend_from_slice(&eh_map.encode());
}
buf
}
pub fn emit_assembly(&self) -> String {
let mut asm = String::new();
asm.push_str("\t.section .eh_frame_stackmaps,\"a\",@progbits\n");
for (name, eh_map) in &self.eh_maps {
asm.push_str(&format!("\t# EH map for: {}\n", name));
asm.push_str(&format!("\t.long {}\n", eh_map.landing_pad_count() as u32));
for (&offset, lp) in &eh_map.landing_pads {
asm.push_str(&format!("\t.long {} # landing pad offset\n", offset));
asm.push_str(&format!("\t.quad {} # stack map ID\n", lp.stack_map_id));
}
}
asm
}
pub fn function_count(&self) -> usize {
self.eh_maps.len()
}
}
#[derive(Debug, Clone)]
pub struct X86DebugVarLocation {
pub var_name: String,
pub dwarf_tag: u16,
pub type_name: String,
pub location: X86DebugLocation,
pub is_live: bool,
pub stack_map_id: u64,
pub instruction_offset: u32,
pub is_entry_value: bool,
pub fragment: Option<X86DebugFragment>,
}
#[derive(Debug, Clone)]
pub enum X86DebugLocation {
Register { dwarf_reg: u16, offset: i32 },
Stack { frame_offset: i32, size: u32 },
Constant { value: i64 },
Memory { address: u64 },
DwarfExpression { expr_bytes: Vec<u8> },
Unavailable,
Composite { pieces: Vec<X86DebugPiece> },
}
#[derive(Debug, Clone)]
pub struct X86DebugPiece {
pub offset_in_variable: u32,
pub size: u32,
pub location: X86DebugLocation,
}
#[derive(Debug, Clone)]
pub struct X86DebugFragment {
pub offset: u32,
pub size: u32,
pub total_size: u32,
}
#[derive(Debug, Clone)]
pub struct X86DebugValueRecord {
pub var_location: X86DebugVarLocation,
pub dwarf_expression: Vec<u8>,
pub expression_valid: bool,
pub di_expression: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct X86DebugInfoMap {
pub function_name: String,
pub locations_by_id: BTreeMap<u64, Vec<X86DebugVarLocation>>,
pub locations_by_offset: BTreeMap<u32, Vec<X86DebugVarLocation>>,
pub debug_values: Vec<X86DebugValueRecord>,
pub cu_offset: u64,
pub has_debug_info: bool,
}
#[derive(Debug, Clone)]
pub struct X86DebugLocTracker {
pub current_locations: HashMap<String, X86DebugLocation>,
pub var_types: HashMap<String, String>,
pub location_history: Vec<(u32, String, X86DebugLocation)>,
pub active: bool,
pub current_offset: u32,
}
impl Default for X86DebugVarLocation {
fn default() -> Self {
X86DebugVarLocation {
var_name: String::new(),
dwarf_tag: 0,
type_name: String::new(),
location: X86DebugLocation::Unavailable,
is_live: false,
stack_map_id: 0,
instruction_offset: 0,
is_entry_value: false,
fragment: None,
}
}
}
impl X86DebugVarLocation {
pub fn new_register(
var_name: &str,
dwarf_reg: u16,
type_name: &str,
stack_map_id: u64,
offset: u32,
) -> Self {
X86DebugVarLocation {
var_name: var_name.to_string(),
dwarf_tag: 0x34, type_name: type_name.to_string(),
location: X86DebugLocation::Register {
dwarf_reg,
offset: 0,
},
is_live: true,
stack_map_id,
instruction_offset: offset,
is_entry_value: false,
fragment: None,
}
}
pub fn new_stack(
var_name: &str,
frame_offset: i32,
size: u32,
type_name: &str,
stack_map_id: u64,
offset: u32,
) -> Self {
X86DebugVarLocation {
var_name: var_name.to_string(),
dwarf_tag: 0x34,
type_name: type_name.to_string(),
location: X86DebugLocation::Stack { frame_offset, size },
is_live: true,
stack_map_id,
instruction_offset: offset,
is_entry_value: false,
fragment: None,
}
}
pub fn mark_unavailable(&mut self) {
self.is_live = false;
self.location = X86DebugLocation::Unavailable;
}
pub fn mark_entry_value(&mut self) {
self.is_entry_value = true;
}
pub fn set_fragment(&mut self, offset: u32, size: u32, total_size: u32) {
self.fragment = Some(X86DebugFragment {
offset,
size,
total_size,
});
}
}
impl Default for X86DebugPiece {
fn default() -> Self {
X86DebugPiece {
offset_in_variable: 0,
size: 0,
location: X86DebugLocation::Unavailable,
}
}
}
impl X86DebugPiece {
pub fn new(offset_in_variable: u32, size: u32, location: X86DebugLocation) -> Self {
X86DebugPiece {
offset_in_variable,
size,
location,
}
}
}
#[derive(Debug, Clone)]
pub struct X86DwarfExprGenerator {
pub use_dwarf64: bool,
pub frame_base_reg: u16,
pub frame_base_offset: i32,
}
impl Default for X86DwarfExprGenerator {
fn default() -> Self {
X86DwarfExprGenerator {
use_dwarf64: false,
frame_base_reg: DWARF_REG_RBP,
frame_base_offset: 0,
}
}
}
impl X86DwarfExprGenerator {
pub fn new(use_dwarf64: bool) -> Self {
X86DwarfExprGenerator {
use_dwarf64,
frame_base_reg: DWARF_REG_RBP,
frame_base_offset: 0,
}
}
pub fn set_frame_base(&mut self, reg: u16, offset: i32) {
self.frame_base_reg = reg;
self.frame_base_offset = offset;
}
pub fn generate_register_expr(&self, dwarf_reg: u16, offset: i32) -> Vec<u8> {
let mut expr = Vec::new();
if dwarf_reg <= 31 {
expr.push(0x50 + dwarf_reg as u8); } else {
expr.push(0x90); expr.extend_from_slice(&self.encode_uleb128(dwarf_reg as u64));
}
if offset != 0 {
expr.push(0x23); expr.extend_from_slice(&self.encode_uleb128(offset as u64));
}
expr
}
pub fn generate_stack_expr(&self, frame_offset: i32, size: u32) -> Vec<u8> {
let mut expr = Vec::new();
let effective_offset = frame_offset - self.frame_base_offset;
expr.push(0x91); expr.extend_from_slice(&self.encode_sleb128(effective_offset as i64));
if size > 0 {
expr.push(0x94); expr.push(size as u8);
}
expr
}
pub fn generate_constant_expr(&self, value: i64) -> Vec<u8> {
let mut expr = Vec::new();
match value {
0 => expr.push(0x30), 1 => expr.push(0x31), 2..=31 => expr.push(0x2F + value as u8), v if v >= 0 && v <= 127 => {
expr.push(0x08); expr.push(v as u8);
}
v if v >= -128 && v <= -1 => {
expr.push(0x0C); expr.push(v as u8);
}
v if v >= 0 && v <= 0x7FFF => {
expr.push(0x0A); expr.extend_from_slice(&(v as u16).to_le_bytes());
}
v if v >= -0x8000 && v <= -1 => {
expr.push(0x0E); expr.extend_from_slice(&(v as i16).to_le_bytes());
}
v if v >= 0 && v <= 0x7FFF_FFFF => {
expr.push(0x0C); expr.extend_from_slice(&(v as u32).to_le_bytes());
}
v => {
expr.push(0x0E); expr.extend_from_slice(&v.to_le_bytes());
}
}
expr
}
pub fn generate_memory_expr(&self, address: u64) -> Vec<u8> {
let mut expr = Vec::new();
expr.push(0x03); if self.use_dwarf64 {
expr.extend_from_slice(&address.to_le_bytes());
} else {
expr.extend_from_slice(&(address as u32).to_le_bytes());
}
expr
}
pub fn generate_unavailable_expr(&self) -> Vec<u8> {
vec![0x00] }
pub fn generate_composite_expr(&self, pieces: &[X86DebugPiece]) -> Vec<u8> {
let mut expr = Vec::new();
for piece in pieces {
match &piece.location {
X86DebugLocation::Register { dwarf_reg, offset } => {
expr.extend_from_slice(&self.generate_register_expr(*dwarf_reg, *offset));
}
X86DebugLocation::Stack { frame_offset, size } => {
expr.extend_from_slice(&self.generate_stack_expr(*frame_offset, *size));
}
X86DebugLocation::Constant { value } => {
expr.extend_from_slice(&self.generate_constant_expr(*value));
}
_ => {
expr.push(0x00); }
}
expr.push(0x93); expr.extend_from_slice(&self.encode_uleb128(piece.size as u64));
}
expr
}
pub fn generate_expr(&self, location: &X86DebugLocation) -> Vec<u8> {
match location {
X86DebugLocation::Register { dwarf_reg, offset } => {
self.generate_register_expr(*dwarf_reg, *offset)
}
X86DebugLocation::Stack { frame_offset, size } => {
self.generate_stack_expr(*frame_offset, *size)
}
X86DebugLocation::Constant { value } => self.generate_constant_expr(*value),
X86DebugLocation::Memory { address } => self.generate_memory_expr(*address),
X86DebugLocation::DwarfExpression { expr_bytes } => expr_bytes.clone(),
X86DebugLocation::Unavailable => self.generate_unavailable_expr(),
X86DebugLocation::Composite { pieces } => self.generate_composite_expr(pieces),
}
}
pub fn generate_loclist_entry(
&self,
start_offset: u64,
end_offset: u64,
location: &X86DebugLocation,
) -> Vec<u8> {
let mut entry = Vec::new();
entry.extend_from_slice(&start_offset.to_le_bytes());
entry.extend_from_slice(&end_offset.to_le_bytes());
let expr = self.generate_expr(location);
entry.extend_from_slice(&(expr.len() as u16).to_le_bytes());
entry.extend_from_slice(&expr);
entry
}
fn encode_uleb128(&self, mut value: u64) -> Vec<u8> {
let mut buf = Vec::new();
loop {
let mut byte = (value & 0x7F) as u8;
value >>= 7;
if value != 0 {
byte |= 0x80;
}
buf.push(byte);
if value == 0 {
break;
}
}
buf
}
fn encode_sleb128(&self, mut value: i64) -> Vec<u8> {
let mut buf = Vec::new();
loop {
let mut byte = (value & 0x7F) as u8;
value >>= 7;
if (value == 0 && (byte & 0x40) == 0) || (value == -1 && (byte & 0x40) != 0) {
buf.push(byte);
break;
}
byte |= 0x80;
buf.push(byte);
}
buf
}
}
impl Default for X86DebugValueRecord {
fn default() -> Self {
X86DebugValueRecord {
var_location: X86DebugVarLocation::default(),
dwarf_expression: Vec::new(),
expression_valid: false,
di_expression: Vec::new(),
}
}
}
impl X86DebugValueRecord {
pub fn new(var_location: X86DebugVarLocation) -> Self {
X86DebugValueRecord {
var_location,
dwarf_expression: Vec::new(),
expression_valid: false,
di_expression: Vec::new(),
}
}
pub fn generate_expression(&mut self, generator: &X86DwarfExprGenerator) {
self.dwarf_expression = generator.generate_expr(&self.var_location.location);
self.expression_valid = true;
}
pub fn set_di_expression(&mut self, expr: Vec<u8>) {
self.di_expression = expr;
}
}
impl Default for X86DebugInfoMap {
fn default() -> Self {
X86DebugInfoMap {
function_name: String::new(),
locations_by_id: BTreeMap::new(),
locations_by_offset: BTreeMap::new(),
debug_values: Vec::new(),
cu_offset: 0,
has_debug_info: false,
}
}
}
impl X86DebugInfoMap {
pub fn new(function_name: &str) -> Self {
X86DebugInfoMap {
function_name: function_name.to_string(),
locations_by_id: BTreeMap::new(),
locations_by_offset: BTreeMap::new(),
debug_values: Vec::new(),
cu_offset: 0,
has_debug_info: false,
}
}
pub fn record_debug_value(&mut self, record: X86DebugValueRecord) {
self.has_debug_info = true;
let loc = record.var_location.clone();
self.locations_by_id
.entry(loc.stack_map_id)
.or_insert_with(Vec::new)
.push(loc.clone());
self.locations_by_offset
.entry(loc.instruction_offset)
.or_insert_with(Vec::new)
.push(loc);
self.debug_values.push(record);
}
pub fn locations_at_id(&self, id: u64) -> Option<&Vec<X86DebugVarLocation>> {
self.locations_by_id.get(&id)
}
pub fn locations_at_offset(&self, offset: u32) -> Option<&Vec<X86DebugVarLocation>> {
self.locations_by_offset.get(&offset)
}
pub fn all_variable_names(&self) -> HashSet<&str> {
self.debug_values
.iter()
.map(|dv| dv.var_location.var_name.as_str())
.collect()
}
pub fn record_count(&self) -> usize {
self.debug_values.len()
}
pub fn emit_debug_loc_section(&self, generator: &X86DwarfExprGenerator) -> Vec<u8> {
let mut buf = Vec::new();
for dv in &self.debug_values {
if !dv.var_location.is_live {
continue;
}
let expr = generator.generate_expr(&dv.var_location.location);
buf.extend_from_slice(&dv.var_location.instruction_offset.to_le_bytes());
buf.extend_from_slice(&(dv.var_location.instruction_offset + 4).to_le_bytes());
buf.extend_from_slice(&(expr.len() as u16).to_le_bytes());
buf.extend_from_slice(&expr);
}
buf.extend_from_slice(&0u64.to_le_bytes());
buf.extend_from_slice(&0u64.to_le_bytes());
buf
}
}
impl Default for X86DebugLocTracker {
fn default() -> Self {
X86DebugLocTracker {
current_locations: HashMap::new(),
var_types: HashMap::new(),
location_history: Vec::new(),
active: false,
current_offset: 0,
}
}
}
impl X86DebugLocTracker {
pub fn new() -> Self {
X86DebugLocTracker {
current_locations: HashMap::new(),
var_types: HashMap::new(),
location_history: Vec::new(),
active: false,
current_offset: 0,
}
}
pub fn begin(&mut self) {
self.active = true;
self.current_offset = 0;
}
pub fn advance_to(&mut self, offset: u32) {
self.current_offset = offset;
}
pub fn set_location(&mut self, var_name: &str, location: X86DebugLocation, type_name: &str) {
if !self.active {
return;
}
self.current_locations
.insert(var_name.to_string(), location.clone());
self.var_types
.insert(var_name.to_string(), type_name.to_string());
self.location_history
.push((self.current_offset, var_name.to_string(), location));
}
pub fn get_location(&self, var_name: &str) -> Option<&X86DebugLocation> {
self.current_locations.get(var_name)
}
pub fn get_type(&self, var_name: &str) -> Option<&String> {
self.var_types.get(var_name)
}
pub fn capture_locations(&self, stack_map_id: u64) -> Vec<X86DebugVarLocation> {
self.current_locations
.iter()
.filter_map(|(name, loc)| {
let type_name = self.var_types.get(name).cloned().unwrap_or_default();
match loc {
X86DebugLocation::Register {
dwarf_reg,
offset: _,
} => Some(X86DebugVarLocation::new_register(
name,
*dwarf_reg,
&type_name,
stack_map_id,
self.current_offset,
)),
X86DebugLocation::Stack { frame_offset, size } => {
Some(X86DebugVarLocation::new_stack(
name,
*frame_offset,
*size,
&type_name,
stack_map_id,
self.current_offset,
))
}
_ => {
let loc = X86DebugVarLocation {
var_name: name.clone(),
dwarf_tag: 0x34,
type_name,
location: loc.clone(),
is_live: true,
stack_map_id,
instruction_offset: self.current_offset,
is_entry_value: false,
fragment: None,
};
Some(loc)
}
}
})
.collect()
}
pub fn end(&mut self) {
self.active = false;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86SafepointAction {
PollOnly,
GCCollect { generation: u32, is_full_gc: bool },
Deoptimize {
reason: DeoptReason,
target_bytecode_offset: u32,
},
Suspend,
CooperativeSample { sample_id: u64 },
AsyncEventHandler { event_id: u64 },
Custom { action_id: u32, payload: Vec<u8> },
}
impl X86SafepointAction {
pub fn name(&self) -> &'static str {
match self {
X86SafepointAction::PollOnly => "PollOnly",
X86SafepointAction::GCCollect { .. } => "GCCollect",
X86SafepointAction::Deoptimize { .. } => "Deoptimize",
X86SafepointAction::Suspend => "Suspend",
X86SafepointAction::CooperativeSample { .. } => "CooperativeSample",
X86SafepointAction::AsyncEventHandler { .. } => "AsyncEventHandler",
X86SafepointAction::Custom { .. } => "Custom",
}
}
pub fn requires_gc_pause(&self) -> bool {
matches!(self, X86SafepointAction::GCCollect { .. })
}
pub fn requires_deopt(&self) -> bool {
matches!(self, X86SafepointAction::Deoptimize { .. })
}
pub fn requires_suspension(&self) -> bool {
matches!(
self,
X86SafepointAction::Suspend
| X86SafepointAction::GCCollect { .. }
| X86SafepointAction::Deoptimize { .. }
)
}
}
#[derive(Debug, Clone)]
pub struct X86Safepoints {
pub gc_safepoint: X86GCSafepoint,
pub pending_actions: BTreeMap<u64, X86SafepointAction>,
pub threads_at_safepoint: HashSet<u64>,
pub global_gc_in_progress: bool,
pub current_gc_generation: u32,
pub total_safepoint_hits: u64,
pub guard_page: X86SafepointGuardPage,
pub cooperative_suspension_enabled: bool,
pub thread_suspension_enabled: bool,
}
#[derive(Debug, Clone)]
pub struct X86SafepointGuardPage {
pub page_address: u64,
pub page_size: u64,
pub is_armed: bool,
pub poll_offset: u32,
pub use_dedicated_page: bool,
}
#[derive(Debug, Clone)]
pub struct X86CooperativeGCHandshake {
pub should_yield: bool,
pub gc_waiting: bool,
pub state: X86HandshakeState,
pub yield_count: u64,
pub supports_parking: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86HandshakeState {
Running,
GCRequested,
AtSafepoint,
Yielded,
Resume,
Resuming,
}
impl X86HandshakeState {
pub fn name(&self) -> &'static str {
match self {
X86HandshakeState::Running => "Running",
X86HandshakeState::GCRequested => "GCRequested",
X86HandshakeState::AtSafepoint => "AtSafepoint",
X86HandshakeState::Yielded => "Yielded",
X86HandshakeState::Resume => "Resume",
X86HandshakeState::Resuming => "Resuming",
}
}
}
impl Default for X86SafepointGuardPage {
fn default() -> Self {
X86SafepointGuardPage {
page_address: POLLING_PAGE_ADDRESS,
page_size: 4096,
is_armed: false,
poll_offset: 0,
use_dedicated_page: true,
}
}
}
impl X86SafepointGuardPage {
pub fn new(page_address: u64, page_size: u64) -> Self {
X86SafepointGuardPage {
page_address,
page_size,
is_armed: false,
poll_offset: 0,
use_dedicated_page: true,
}
}
pub fn arm(&mut self) {
self.is_armed = true;
}
pub fn disarm(&mut self) {
self.is_armed = false;
}
pub fn generate_poll_instruction_bytes(&self) -> Vec<u8> {
let mut bytes = vec![0x49, 0xBB];
bytes.extend_from_slice(&self.page_address.to_le_bytes());
bytes.extend_from_slice(&[0x41, 0x80, 0x3B, 0x00]);
bytes
}
pub fn generate_rip_relative_poll(&self, rip_offset: i32) -> Vec<u8> {
let mut bytes = vec![0x80, 0x3D];
bytes.extend_from_slice(&rip_offset.to_le_bytes());
bytes.push(0x00);
bytes
}
}
impl Default for X86CooperativeGCHandshake {
fn default() -> Self {
X86CooperativeGCHandshake {
should_yield: false,
gc_waiting: false,
state: X86HandshakeState::Running,
yield_count: 0,
supports_parking: false,
}
}
}
impl X86CooperativeGCHandshake {
pub fn new(supports_parking: bool) -> Self {
X86CooperativeGCHandshake {
should_yield: false,
gc_waiting: false,
state: X86HandshakeState::Running,
yield_count: 0,
supports_parking,
}
}
pub fn request_gc(&mut self) {
self.should_yield = true;
self.gc_waiting = true;
self.state = X86HandshakeState::GCRequested;
}
pub fn reach_safepoint(&mut self) {
if self.state == X86HandshakeState::GCRequested {
self.state = X86HandshakeState::AtSafepoint;
}
}
pub fn yield_to_gc(&mut self) {
if self.state == X86HandshakeState::AtSafepoint {
self.state = X86HandshakeState::Yielded;
self.yield_count += 1;
}
}
pub fn resume(&mut self) {
self.state = X86HandshakeState::Resume;
}
pub fn resume_execution(&mut self) {
if self.state == X86HandshakeState::Resume {
self.state = X86HandshakeState::Resuming;
self.should_yield = false;
self.gc_waiting = false;
self.state = X86HandshakeState::Running;
}
}
pub fn needs_yield(&self) -> bool {
self.should_yield
}
pub fn is_yielded(&self) -> bool {
self.state == X86HandshakeState::Yielded
}
pub fn current_state(&self) -> X86HandshakeState {
self.state
}
}
impl Default for X86Safepoints {
fn default() -> Self {
X86Safepoints {
gc_safepoint: X86GCSafepoint::default(),
pending_actions: BTreeMap::new(),
threads_at_safepoint: HashSet::new(),
global_gc_in_progress: false,
current_gc_generation: 0,
total_safepoint_hits: 0,
guard_page: X86SafepointGuardPage::default(),
cooperative_suspension_enabled: false,
thread_suspension_enabled: false,
}
}
}
impl X86Safepoints {
pub fn new() -> Self {
X86Safepoints::default()
}
pub fn enable_cooperative_suspension(&mut self) {
self.cooperative_suspension_enabled = true;
self.gc_safepoint.set_enabled(true);
}
pub fn enable_thread_suspension(&mut self) {
self.thread_suspension_enabled = true;
}
pub fn configure_guard_page(&mut self, page_address: u64, page_size: u64) {
self.guard_page = X86SafepointGuardPage::new(page_address, page_size);
}
pub fn set_action(&mut self, safepoint_id: u64, action: X86SafepointAction) {
self.pending_actions.insert(safepoint_id, action);
}
pub fn get_action(&self, safepoint_id: u64) -> Option<&X86SafepointAction> {
self.pending_actions.get(&safepoint_id)
}
pub fn take_action(&mut self, safepoint_id: u64) -> Option<X86SafepointAction> {
self.pending_actions.remove(&safepoint_id)
}
pub fn thread_at_safepoint(&mut self, thread_id: u64) {
self.threads_at_safepoint.insert(thread_id);
self.total_safepoint_hits += 1;
}
pub fn thread_left_safepoint(&mut self, thread_id: u64) {
self.threads_at_safepoint.remove(&thread_id);
}
pub fn all_threads_at_safepoint(&self, total_threads: usize) -> bool {
self.threads_at_safepoint.len() == total_threads
}
pub fn begin_global_gc(&mut self, generation: u32) {
self.global_gc_in_progress = true;
self.current_gc_generation = generation;
self.guard_page.arm();
}
pub fn end_global_gc(&mut self) {
self.global_gc_in_progress = false;
self.guard_page.disarm();
}
pub fn generate_signal_handler_stub(&self) -> String {
let mut asm = String::new();
asm.push_str("# Safepoint SEGV signal handler stub\n");
asm.push_str("safepoint_signal_handler:\n");
asm.push_str(" # Save all volatile registers\n");
asm.push_str(" push %rax\n");
asm.push_str(" push %rcx\n");
asm.push_str(" push %rdx\n");
asm.push_str(" push %rsi\n");
asm.push_str(" push %rdi\n");
asm.push_str(" push %r8\n");
asm.push_str(" push %r9\n");
asm.push_str(" push %r10\n");
asm.push_str(" push %r11\n");
asm.push_str(" # Check if fault was from polling page\n");
asm.push_str(" movq %r11, %rdi # fault address in R11\n");
asm.push_str(" callq safepoint_is_poll_fault\n");
asm.push_str(" test %al, %al\n");
asm.push_str(" jz not_safepoint_fault\n");
asm.push_str(" # Execute pending safepoint action\n");
asm.push_str(" callq safepoint_execute_action\n");
asm.push_str(" # Restore registers and return\n");
asm.push_str("not_safepoint_fault:\n");
asm.push_str(" pop %r11\n");
asm.push_str(" pop %r10\n");
asm.push_str(" pop %r9\n");
asm.push_str(" pop %r8\n");
asm.push_str(" pop %rdi\n");
asm.push_str(" pop %rsi\n");
asm.push_str(" pop %rdx\n");
asm.push_str(" pop %rcx\n");
asm.push_str(" pop %rax\n");
asm.push_str(" retq\n");
asm
}
pub fn is_poll_fault(&self, fault_address: u64) -> bool {
fault_address >= self.guard_page.page_address
&& fault_address < self.guard_page.page_address + self.guard_page.page_size
}
pub fn total_hits(&self) -> u64 {
self.total_safepoint_hits
}
pub fn pending_action_count(&self) -> usize {
self.pending_actions.len()
}
pub fn threads_at_safepoint_count(&self) -> usize {
self.threads_at_safepoint.len()
}
pub fn clear_actions(&mut self) {
self.pending_actions.clear();
}
pub fn reset_stats(&mut self) {
self.total_safepoint_hits = 0;
self.gc_safepoint.reset();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stack_map_header_creation() {
let header = StackMapHeader::current();
assert_eq!(header.version, STACK_MAP_VERSION);
assert_eq!(header.reserved1, 0);
assert_eq!(header.reserved2, 0);
assert_eq!(header.reserved3, 0);
}
#[test]
fn test_stack_map_header_roundtrip() {
let header = StackMapHeader::current();
let bytes = header.to_bytes();
assert_eq!(bytes.len(), 8);
let mut cursor = Cursor::new(&bytes);
let parsed = StackMapHeader::from_reader(&mut cursor).unwrap();
assert_eq!(header, parsed);
}
#[test]
fn test_stack_map_header_validation() {
let header = StackMapHeader::current();
assert!(header.validate().is_ok());
let bad_header = StackMapHeader::new(99);
assert!(bad_header.validate().is_err());
}
#[test]
fn test_location_kind_from_u8() {
assert_eq!(LocationKind::from_u8(1), Some(LocationKind::Register));
assert_eq!(LocationKind::from_u8(2), Some(LocationKind::Direct));
assert_eq!(LocationKind::from_u8(3), Some(LocationKind::Indirect));
assert_eq!(LocationKind::from_u8(4), Some(LocationKind::Constant));
assert_eq!(LocationKind::from_u8(5), Some(LocationKind::ConstantIndex));
assert_eq!(LocationKind::from_u8(6), Some(LocationKind::VectorSplice));
assert_eq!(LocationKind::from_u8(0), None);
assert_eq!(LocationKind::from_u8(255), None);
}
#[test]
fn test_location_kind_properties() {
assert!(LocationKind::Register.is_register());
assert!(LocationKind::VectorSplice.is_register());
assert!(LocationKind::Direct.is_stack());
assert!(LocationKind::Indirect.is_stack());
assert!(LocationKind::Constant.is_constant());
assert!(LocationKind::ConstantIndex.is_constant());
assert!(!LocationKind::Register.is_stack());
assert!(!LocationKind::Constant.is_register());
assert!(!LocationKind::Direct.is_constant());
}
#[test]
fn test_location_record_register() {
let loc = LocationRecord::new_register(0, 8);
assert_eq!(loc.kind, LocationKind::Register);
assert_eq!(loc.dwarf_reg_num, 0);
assert_eq!(loc.size, 8);
assert_eq!(loc.offset, 0);
}
#[test]
fn test_location_record_direct() {
let loc = LocationRecord::new_direct(6, -16, 8);
assert_eq!(loc.kind, LocationKind::Direct);
assert_eq!(loc.dwarf_reg_num, 6); assert_eq!(loc.offset, -16);
assert_eq!(loc.size, 8);
}
#[test]
fn test_location_record_indirect() {
let loc = LocationRecord::new_indirect(7, 32, 8);
assert_eq!(loc.kind, LocationKind::Indirect);
assert_eq!(loc.dwarf_reg_num, 7); assert_eq!(loc.offset, 32);
}
#[test]
fn test_location_record_constant() {
let loc = LocationRecord::new_constant(42);
assert_eq!(loc.kind, LocationKind::Constant);
assert_eq!(loc.offset, 42);
assert_eq!(loc.size, 8);
}
#[test]
fn test_location_record_constant_index() {
let loc = LocationRecord::new_constant_index(3);
assert_eq!(loc.kind, LocationKind::ConstantIndex);
assert_eq!(loc.offset, 3);
assert_eq!(loc.size, 0);
}
#[test]
fn test_location_record_roundtrip() {
let loc = LocationRecord::new_register(0, 8);
let bytes = loc.to_bytes();
assert_eq!(bytes.len(), 16);
let mut cursor = Cursor::new(&bytes);
let parsed = LocationRecord::from_reader(&mut cursor).unwrap();
assert_eq!(loc, parsed);
}
#[test]
fn test_location_record_multiple_roundtrip() {
let locs = vec![
LocationRecord::new_register(0, 8),
LocationRecord::new_direct(6, -16, 8),
LocationRecord::new_constant(42),
LocationRecord::new_vector_splice(17, 0, 32),
];
let mut buf = Vec::new();
for loc in &locs {
buf.extend_from_slice(&loc.to_bytes());
}
let mut cursor = Cursor::new(&buf);
for expected in &locs {
let parsed = LocationRecord::from_reader(&mut cursor).unwrap();
assert_eq!(*expected, parsed);
}
}
#[test]
fn test_function_record_roundtrip() {
let func = StackMapFunctionRecord::new(0x1000, 128, 2, 5);
let bytes = func.to_bytes();
assert_eq!(bytes.len(), 24);
let mut cursor = Cursor::new(&bytes);
let parsed = StackMapFunctionRecord::from_reader(&mut cursor).unwrap();
assert_eq!(func, parsed);
}
#[test]
fn test_stack_size_record_roundtrip() {
let rec = StackSizeRecord::new(0x20, 96);
let bytes = rec.to_bytes();
assert_eq!(bytes.len(), 16);
let mut cursor = Cursor::new(&bytes);
let parsed = StackSizeRecord::from_reader(&mut cursor).unwrap();
assert_eq!(rec, parsed);
}
#[test]
fn test_live_out_record_roundtrip() {
let rec = LiveOutRecord::new(0, 8);
let bytes = rec.to_bytes();
assert_eq!(bytes.len(), 8);
let mut cursor = Cursor::new(&bytes);
let parsed = LiveOutRecord::from_reader(&mut cursor).unwrap();
assert_eq!(rec, parsed);
}
#[test]
fn test_constant_pool_record_roundtrip() {
let rec = ConstantPoolRecord::new(0xDEADBEEF_CAFEBABE);
let bytes = rec.to_bytes();
assert_eq!(bytes.len(), 8);
let mut cursor = Cursor::new(&bytes);
let parsed = ConstantPoolRecord::from_reader(&mut cursor).unwrap();
assert_eq!(rec, parsed);
}
#[test]
fn test_stack_map_record_creation() {
let record = StackMapRecord::new(1, 0x20);
assert_eq!(record.id, 1);
assert_eq!(record.instruction_offset, 0x20);
assert_eq!(record.locations.len(), 0);
assert_eq!(record.live_outs.len(), 0);
}
#[test]
fn test_stack_map_record_add_location() {
let mut record = StackMapRecord::new(1, 0x10);
record.add_location(LocationRecord::new_register(0, 8));
record.add_location(LocationRecord::new_direct(6, -8, 8));
assert_eq!(record.location_count(), 2);
assert_eq!(record.num_locations, 2);
}
#[test]
fn test_stack_map_record_header_roundtrip() {
let record = StackMapRecord::new(42, 0x100);
let bytes = record.to_header_bytes();
assert_eq!(bytes.len(), 16);
let mut cursor = Cursor::new(&bytes);
let (id, offset, _reserved, num_locs) =
StackMapRecord::header_from_reader(&mut cursor).unwrap();
assert_eq!(id, 42);
assert_eq!(offset, 0x100);
assert_eq!(num_locs, 0);
}
#[test]
fn test_register_name_resolution_64bit() {
let names = X86RegisterNames::new(true);
assert_eq!(names.resolve(0), "RAX");
assert_eq!(names.resolve(6), "RBP");
assert_eq!(names.resolve(7), "RSP");
assert_eq!(names.resolve(16), "RIP");
assert_eq!(names.resolve(17), "XMM0");
}
#[test]
fn test_register_name_resolution_32bit() {
let names = X86RegisterNames::new(false);
assert_eq!(names.resolve(0), "EAX");
assert_eq!(names.resolve(6), "EBP");
assert_eq!(names.resolve(7), "ESP");
assert_eq!(names.resolve(16), "EIP");
}
#[test]
fn test_register_name_unknown() {
let names = X86RegisterNames::new(true);
assert_eq!(names.resolve(200), "Reg(200)");
}
#[test]
fn test_register_classification() {
let names = X86RegisterNames::new(true);
assert!(names.is_gpr(0)); assert!(names.is_gpr(15)); assert!(!names.is_gpr(17)); assert!(names.is_xmm(17));
assert!(names.is_ymm(49));
assert!(names.is_zmm(81));
}
#[test]
fn test_register_caller_callee_saved() {
let names = X86RegisterNames::new(true);
assert!(names.is_caller_saved(0)); assert!(names.is_caller_saved(2)); assert!(names.is_caller_saved(11)); assert!(names.is_caller_saved(17)); assert!(names.is_callee_saved(3)); assert!(names.is_callee_saved(6)); assert!(names.is_callee_saved(12)); }
#[test]
fn test_format_creation() {
let fmt = X86StackMapFormat::new();
assert_eq!(fmt.header.version, STACK_MAP_VERSION);
assert_eq!(fmt.num_functions, 0);
assert_eq!(fmt.num_constants, 0);
assert_eq!(fmt.functions.len(), 0);
}
#[test]
fn test_format_add_constant() {
let mut fmt = X86StackMapFormat::new();
let idx = fmt.add_constant(42);
assert_eq!(idx, 0);
assert_eq!(fmt.get_constant(0), Some(42));
assert_eq!(fmt.num_constants, 1);
let idx2 = fmt.add_constant(100);
assert_eq!(idx2, 1);
assert_eq!(fmt.get_constant(1), Some(100));
}
#[test]
fn test_format_find_record_by_id() {
let mut fmt = X86StackMapFormat::new();
let func = StackMapFunctionRecord::new(0x1000, 64, 0, 2);
let rec1 = StackMapRecord::new(10, 0x10);
let rec2 = StackMapRecord::new(20, 0x20);
fmt.add_function(func.clone(), Vec::new(), vec![rec1, rec2]);
assert!(fmt.find_record_by_id(10).is_some());
assert!(fmt.find_record_by_id(20).is_some());
assert!(fmt.find_record_by_id(99).is_none());
}
#[test]
fn test_format_roundtrip_empty() {
let fmt = X86StackMapFormat::new();
let bytes = fmt.to_bytes();
let parsed = X86StackMapFormat::from_bytes(&bytes).unwrap();
assert_eq!(parsed.header, fmt.header);
assert_eq!(parsed.num_functions, fmt.num_functions);
}
#[test]
fn test_format_roundtrip_with_data() {
let mut fmt = X86StackMapFormat::new();
let func = StackMapFunctionRecord::new(0x1000, 128, 1, 2);
let size_rec = StackSizeRecord::new(0, 128);
let mut rec1 = StackMapRecord::new(1, 0x10);
rec1.add_location(LocationRecord::new_register(0, 8));
let rec2 = StackMapRecord::new(2, 0x20);
rec2.add_location(LocationRecord::new_direct(6, -16, 8));
rec2.add_live_out(LiveOutRecord::new(0, 8));
fmt.add_function(func, vec![size_rec], vec![rec1, rec2]);
fmt.add_constant(0xDEADBEEF);
let bytes = fmt.to_bytes();
let parsed = X86StackMapFormat::from_bytes(&bytes).unwrap();
assert_eq!(parsed.num_functions, 1);
assert_eq!(parsed.num_constants, 1);
assert_eq!(parsed.records.len(), 2);
assert!(parsed.validate().is_ok());
}
#[test]
fn test_format_validation() {
let format = X86StackMapFormat::new();
assert!(format.validate().is_ok());
}
#[test]
fn test_stack_maps_creation() {
let sm = X86StackMaps::new("x86_64-unknown-linux-gnu", true);
assert!(sm.enabled);
assert!(!sm.verbose);
assert_eq!(sm.next_id, 0);
}
#[test]
fn test_stack_maps_allocate_id() {
let mut sm = X86StackMaps::new("x86_64-unknown-linux-gnu", true);
assert_eq!(sm.allocate_id(), 0);
assert_eq!(sm.allocate_id(), 1);
assert_eq!(sm.allocate_id(), 2);
assert_eq!(sm.next_id, 3);
}
#[test]
fn test_stack_maps_register_function() {
let mut sm = X86StackMaps::new("x86_64-unknown-linux-gnu", true);
let idx = sm.register_function("test_func", 128);
assert_eq!(idx, 0);
assert_eq!(sm.function_count(), 1);
let idx2 = sm.register_function("test_func2", 256);
assert_eq!(idx2, 1);
assert_eq!(sm.function_count(), 2);
}
#[test]
fn test_stack_maps_add_record() {
let mut sm = X86StackMaps::new("x86_64-unknown-linux-gnu", true);
let idx = sm.register_function("test", 128);
let record = StackMapRecord::new(1, 0x10);
sm.add_record(idx, record.clone());
sm.finalize();
assert_eq!(sm.format.records.len(), 1);
let found = sm.lookup_record(1);
assert!(found.is_some());
assert_eq!(found.unwrap().id, 1);
}
#[test]
fn test_stack_maps_validate_duplicate_ids() {
let mut sm = X86StackMaps::new("x86_64-unknown-linux-gnu", true);
let idx = sm.register_function("test", 128);
sm.next_id = 5;
let rec1 = StackMapRecord::new(5, 0x10);
let rec2 = StackMapRecord::new(5, 0x20);
sm.add_record(idx, rec1);
sm.add_record(idx, rec2);
assert!(sm.validate().is_err());
}
#[test]
fn test_stack_maps_emit_section() {
let mut sm = X86StackMaps::new("x86_64-unknown-linux-gnu", true);
let idx = sm.register_function("test", 128);
sm.add_record(idx, StackMapRecord::new(1, 0x10));
let section = sm.emit_section();
assert!(!section.is_empty());
}
#[test]
fn test_stack_maps_emit_asm() {
let mut sm = X86StackMaps::new("x86_64-unknown-linux-gnu", true);
let idx = sm.register_function("test", 128);
sm.add_record(idx, StackMapRecord::new(1, 0x10));
let asm = sm.emit_asm();
assert!(asm.contains(STACK_MAP_SECTION_NAME));
}
#[test]
fn test_patchpoint_call_creation() {
let pp = X86PatchPoint::new_call(1, "my_function", 3);
assert_eq!(pp.id, 1);
assert_eq!(pp.patch_type, PatchableType::Call);
assert_eq!(pp.target, Some("my_function".to_string()));
assert_eq!(pp.num_args, 3);
assert_eq!(pp.nop_sled_size, PATCHABLE_NOP_SLED_SIZE);
}
#[test]
fn test_patchpoint_typed_call() {
let pp = X86PatchPoint::new_typed_call(2, "alloc", 2, "i8*");
assert_eq!(pp.return_type, Some("i8*".to_string()));
}
#[test]
fn test_patchpoint_jump_creation() {
let pp = X86PatchPoint::new_jump(3, "loop_start");
assert_eq!(pp.patch_type, PatchableType::Jump);
assert_eq!(pp.nop_sled_size, PATCHABLE_JUMP_SEQUENCE_SIZE);
assert_eq!(pp.num_args, 0);
}
#[test]
fn test_patchpoint_region_creation() {
let pp = X86PatchPoint::new_region(4, 128);
assert_eq!(pp.patch_type, PatchableType::Region);
assert_eq!(pp.nop_sled_size, 128);
assert_eq!(pp.target, None);
}
#[test]
fn test_nop_sled_generation() {
let sizes = [0, 1, 7, 8, 9, 15, 16, 32, 64, 128];
for size in &sizes {
let pp = X86PatchPoint::new_region(0, *size);
let sled = pp.generate_nop_sled();
assert_eq!(
sled.len(),
*size,
"NOP sled size mismatch for size {}",
size
);
if !sled.is_empty() {
match sled[0] {
0x90 | 0x66 | 0x0F => {} _ => panic!("Unexpected byte in NOP sled: 0x{:02X}", sled[0]),
}
}
}
}
#[test]
fn test_patchpoint_machine_instrs_call() {
let pp = X86PatchPoint::new_call(1, "target_func", 2);
let (sled, call) = pp.generate_machine_instrs();
assert!(!sled.is_empty());
assert!(call.is_some());
let call_instr = call.unwrap();
assert_eq!(call_instr.opcode, 0xE8); }
#[test]
fn test_patchpoint_machine_instrs_jump() {
let pp = X86PatchPoint::new_jump(1, "target_label");
let (sled, jmp) = pp.generate_machine_instrs();
let jmp_instr = jmp.unwrap();
assert_eq!(jmp_instr.opcode, 0xE9); }
#[test]
fn test_patchpoint_machine_instrs_region() {
let pp = X86PatchPoint::new_region(1, 32);
let (sled, other) = pp.generate_machine_instrs();
assert!(!sled.is_empty());
assert!(other.is_none()); }
#[test]
fn test_patchpoint_calling_convention() {
let mut pp = X86PatchPoint::new_call(1, "func", 0);
pp.set_calling_convention("preserve_mostcc");
assert_eq!(pp.calling_convention, "preserve_mostcc");
}
#[test]
fn test_patchpoint_metadata() {
let mut pp = X86PatchPoint::new_call(1, "func", 0);
pp.set_metadata(b"test_metadata");
assert_eq!(pp.metadata, b"test_metadata");
}
#[test]
fn test_patchpoint_to_ir_text() {
let pp = X86PatchPoint::new_call(1, "target", 2);
let ir = pp.to_ir_text();
assert!(ir.contains("llvm.experimental.patchpoint"));
assert!(ir.contains("@target"));
}
#[test]
fn test_patchpoint_stack_map_record() {
let pp = X86PatchPoint::new_call(42, "target", 0);
let record = pp.generate_stack_map_record(0x100);
assert_eq!(record.id, 42);
assert_eq!(record.instruction_offset, 0x100);
}
#[test]
fn test_statepoint_creation() {
let sp = X86StatePoint::new(1, "my_func", 2);
assert_eq!(sp.id, 1);
assert_eq!(sp.callee, "my_func");
assert_eq!(sp.num_call_args, 2);
assert_eq!(sp.nop_bytes, 0);
assert_eq!(sp.flags, 0);
}
#[test]
fn test_statepoint_add_gc_root() {
let mut sp = X86StatePoint::new(1, "func", 0);
let val = X86StatepointValue::reg(0, "i8*");
sp.add_gc_root(val);
assert_eq!(sp.gc_args.len(), 1);
assert_eq!(sp.base_pointer_ids.len(), 1);
}
#[test]
fn test_statepoint_add_derived_pointer() {
let mut sp = X86StatePoint::new(1, "func", 0);
let base = X86StatepointValue::reg(0, "i8*");
let base_idx = sp.add_gc_root(base);
let derived = X86StatepointValue::reg(1, "i8*");
sp.add_derived_pointer(derived, base_idx, 16);
assert_eq!(sp.gc_args.len(), 2);
assert_eq!(sp.derived_pointer_ids.len(), 1);
assert_eq!(sp.relocation_records.len(), 1);
let reloc = &sp.relocation_records[0];
assert_eq!(reloc.base_ptr_index, base_idx);
assert_eq!(reloc.derived_ptr_index, 1);
assert_eq!(reloc.offset_from_base, 16);
}
#[test]
fn test_statepoint_values() {
let reg_val = X86StatepointValue::reg(0, "i8*");
assert!(matches!(
reg_val.repr,
X86StatepointValueRepr::Register { .. }
));
let stack_val = X86StatepointValue::stack(-8, "i64");
assert!(matches!(
stack_val.repr,
X86StatepointValueRepr::Stack { .. }
));
let const_val = X86StatepointValue::constant(42, "i32");
assert!(matches!(
const_val.repr,
X86StatepointValueRepr::Constant { value: 42 }
));
let undef_val = X86StatepointValue::undef("i64");
assert!(matches!(undef_val.repr, X86StatepointValueRepr::Undef));
}
#[test]
fn test_statepoint_value_to_location_record() {
let val = X86StatepointValue::reg(0, "i8*");
let loc = val.to_location_record();
assert!(loc.is_some());
assert_eq!(loc.unwrap().kind, LocationKind::Register);
}
#[test]
fn test_statepoint_to_ir_text() {
let mut sp = X86StatePoint::new(1, "target", 1);
sp.add_gc_root(X86StatepointValue::reg(0, "i8*"));
let ir = sp.to_ir_text();
assert!(ir.contains("llvm.experimental.gc.statepoint"));
assert!(ir.contains("@target"));
}
#[test]
fn test_statepoint_gc_root_locations() {
let mut sp = X86StatePoint::new(1, "func", 0);
sp.add_gc_root(X86StatepointValue::reg(0, "i8*"));
sp.add_gc_root(X86StatepointValue::reg(1, "i8*"));
let locs = sp.get_gc_root_locations();
assert_eq!(locs.len(), 2);
}
#[test]
fn test_statepoint_derived_locations() {
let mut sp = X86StatePoint::new(1, "func", 0);
let base_idx = sp.add_gc_root(X86StatepointValue::reg(0, "i8*"));
sp.add_derived_pointer(X86StatepointValue::reg(1, "i8*"), base_idx, 8);
let derived = sp.get_derived_locations();
assert_eq!(derived.len(), 1);
let (loc, base_idx, offset) = &derived[0];
assert_eq!(*base_idx, 0);
assert_eq!(*offset, 8);
}
#[test]
fn test_statepoint_stack_map_record() {
let mut sp = X86StatePoint::new(42, "func", 0);
sp.add_gc_root(X86StatepointValue::reg(0, "i8*"));
let record = sp.generate_stack_map_record(0x100);
assert_eq!(record.id, 42);
assert_eq!(record.instruction_offset, 0x100);
assert_eq!(record.locations.len(), 1);
}
#[test]
fn test_relocation_record() {
let rec = X86RelocationRecord::new(1, 0, 2, 16);
assert!(rec.is_derived());
assert!(rec.has_interior_offset());
}
#[test]
fn test_relocation_record_not_derived() {
let rec = X86RelocationRecord::new(1, 0, 0, 0);
assert!(!rec.is_derived());
assert!(!rec.has_interior_offset());
}
#[test]
fn test_safepoint_creation() {
let sp = X86GCSafepoint::new();
assert!(sp.enabled);
assert_eq!(sp.polling_page_address, POLLING_PAGE_ADDRESS);
assert!(sp.insert_at_entry);
assert!(sp.insert_at_backedge);
assert!(!sp.insert_after_calls);
assert_eq!(sp.backedge_frequency, 1);
}
#[test]
fn test_safepoint_poll_sequence() {
let sp = X86GCSafepoint::new();
let seq = sp.generate_poll_sequence();
assert_eq!(seq.len(), 2);
}
#[test]
fn test_safepoint_poll_ir() {
let sp = X86GCSafepoint::new();
let ir = sp.generate_poll_ir();
assert!(ir.contains("volatile"));
assert!(ir.contains("inttoptr"));
}
#[test]
fn test_safepoint_insert_entry() {
let mut sp = X86GCSafepoint::new();
let (loc, seq) = sp.insert_entry_safepoint("test_func", 1);
assert_eq!(loc.kind, SafepointKind::FunctionEntry);
assert_eq!(loc.function_name, "test_func");
assert_eq!(loc.instruction_offset, 0);
assert!(!seq.is_empty());
assert_eq!(sp.stats.entry_safepoints, 1);
assert_eq!(sp.safepoint_count, 1);
}
#[test]
fn test_safepoint_insert_backedge() {
let mut sp = X86GCSafepoint::new();
let (loc, _seq) = sp.insert_backedge_safepoint("loop_func", 0x40, 2);
assert_eq!(loc.kind, SafepointKind::LoopBackedge);
assert_eq!(loc.instruction_offset, 0x40);
assert_eq!(sp.stats.backedge_safepoints, 1);
}
#[test]
fn test_safepoint_record_statepoint() {
let mut sp = X86GCSafepoint::new();
let loc = sp.record_statepoint("func", 0x80, 3);
assert_eq!(loc.kind, SafepointKind::Statepoint);
assert_eq!(sp.stats.statepoint_safepoints, 1);
}
#[test]
fn test_safepoint_cooperative_suspend() {
let mut sp = X86GCSafepoint::new();
let (loc, _seq) = sp.insert_cooperative_suspend("func", 0x60, 4);
assert_eq!(loc.kind, SafepointKind::CooperativeSuspend);
assert_eq!(sp.stats.coop_suspend_points, 1);
}
#[test]
fn test_safepoint_is_safepoint() {
let mut sp = X86GCSafepoint::new();
sp.insert_entry_safepoint("func", 1);
assert!(sp.is_safepoint("func", 0));
assert!(!sp.is_safepoint("func", 0x10));
assert!(!sp.is_safepoint("other_func", 0));
}
#[test]
fn test_safepoint_should_insert_backedge() {
let sp = X86GCSafepoint::new();
assert!(sp.should_insert_backedge(0));
assert!(sp.should_insert_backedge(1));
assert!(sp.should_insert_backedge(2));
}
#[test]
fn test_safepoint_with_frequency() {
let mut sp = X86GCSafepoint::new();
sp.configure(true, true, false, 4);
assert!(sp.should_insert_backedge(0));
assert!(sp.should_insert_backedge(4));
assert!(!sp.should_insert_backedge(1));
assert!(!sp.should_insert_backedge(3));
}
#[test]
fn test_safepoint_reset() {
let mut sp = X86GCSafepoint::new();
sp.insert_entry_safepoint("func", 1);
assert_eq!(sp.safepoint_count, 1);
sp.reset();
assert_eq!(sp.safepoint_count, 0);
assert_eq!(sp.safepoints.len(), 0);
assert_eq!(sp.stats.total, 0);
}
#[test]
fn test_safepoint_get_function_safepoints() {
let mut sp = X86GCSafepoint::new();
sp.insert_entry_safepoint("func_a", 1);
sp.insert_backedge_safepoint("func_a", 0x20, 2);
sp.insert_entry_safepoint("func_b", 3);
let func_a_sps = sp.get_function_safepoints("func_a");
assert_eq!(func_a_sps.len(), 2);
let func_b_sps = sp.get_function_safepoints("func_b");
assert_eq!(func_b_sps.len(), 1);
}
#[test]
fn test_generator_creation() {
let r#gen = X86StackMapGenerator::new(true);
assert_eq!(r#gen.current_function, None);
assert_eq!(r#gen.current_offset, 0);
}
#[test]
fn test_generator_begin_end_function() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("test", 128);
assert_eq!(r#gen.current_function, Some("test".to_string()));
assert_eq!(r#gen.function_index_map.contains_key("test"), true);
r#gen.end_function();
assert_eq!(r#gen.current_function, None);
}
#[test]
fn test_generator_record_register_location() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("test", 128);
r#gen.record_register_location("test", 1, 0, 8, "i8*", true);
let state = r#gen.live_var_state.get("test").unwrap();
let loc = state.get_live_location(1);
assert!(loc.is_some());
assert_eq!(loc.unwrap().kind, LocationKind::Register);
assert!(state.is_gc_pointer(1));
assert!(!state.is_gc_pointer(2));
}
#[test]
fn test_generator_record_stack_location() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("test", 128);
r#gen.record_stack_location("test", 2, -16, 8, "i64", false, true);
let state = r#gen.live_var_state.get("test").unwrap();
let loc = state.get_live_location(2);
assert!(loc.is_some());
assert_eq!(loc.unwrap().kind, LocationKind::Indirect);
}
#[test]
fn test_generator_record_constant_location() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("test", 128);
r#gen.record_constant_location("test", 3, 42, "i32");
let state = r#gen.live_var_state.get("test").unwrap();
let loc = state.get_live_location(3);
assert!(loc.is_some());
assert_eq!(loc.unwrap().kind, LocationKind::Constant);
}
#[test]
fn test_generator_generate_stack_map_record() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("test", 128);
r#gen.record_register_location("test", 1, 0, 8, "i8*", true);
r#gen.advance_offset(0x10);
let record = r#gen.generate_stack_map_record("test", 1);
assert!(record.is_some());
let rec = record.unwrap();
assert_eq!(rec.id, 1);
assert!(rec.locations.len() > 0);
}
#[test]
fn test_generator_spill_register() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("test", 128);
r#gen.record_register_location("test", 1, 0, 8, "i8*", true);
let slot = r#gen.spill_register("test", 1, 8);
assert!(slot.is_some());
let state = r#gen.live_var_state.get("test").unwrap();
let loc = state.get_live_location(1);
assert!(loc.is_some());
assert_eq!(loc.unwrap().kind, LocationKind::Direct);
}
#[test]
fn test_generator_reload_register() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("test", 128);
r#gen.record_register_location("test", 1, 0, 8, "i8*", true);
r#gen.spill_register("test", 1, 8);
r#gen.reload_register("test", 1, 0);
let state = r#gen.live_var_state.get("test").unwrap();
let loc = state.get_live_location(1);
assert!(loc.is_some());
assert_eq!(loc.unwrap().kind, LocationKind::Register);
}
#[test]
fn test_generator_stack_size_change() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("test", 128);
r#gen.record_stack_size_change("test", 256);
let sm = r#gen.get_stack_maps();
assert_eq!(sm.function_count(), 1);
r#gen.end_function();
}
#[test]
fn test_generator_add_constant() {
let mut r#gen = X86StackMapGenerator::new(true);
let idx = r#gen.add_constant(0xBEEF);
assert_eq!(idx, 0);
let sm = r#gen.get_stack_maps();
assert_eq!(sm.format.get_constant(0), Some(0xBEEF));
}
#[test]
fn test_generator_generate_section() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("test", 128);
r#gen.record_register_location("test", 1, 0, 8, "i8*", true);
r#gen.generate_stack_map_record("test", 1);
r#gen.end_function();
let section = r#gen.generate_section();
assert!(!section.is_empty());
let mut parser = X86StackMapParser::new(true);
assert!(parser.parse(§ion).is_ok());
assert!(parser.lookup_by_id(1).is_some());
}
#[test]
fn test_generator_generate_section_asm() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("test", 128);
r#gen.end_function();
let asm = r#gen.generate_section_asm();
assert!(asm.contains(STACK_MAP_SECTION_NAME));
}
#[test]
fn test_generator_finalize() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("test", 128);
r#gen.record_register_location("test", 1, 0, 8, "i8*", true);
r#gen.generate_stack_map_record("test", 1);
r#gen.end_function();
assert!(r#gen.finalize().is_ok());
}
#[test]
fn test_generator_with_patchpoint() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("test", 128);
let pp = X86PatchPoint::new_call(42, "alloc", 1);
r#gen.generate_from_patchpoint("test", &pp);
r#gen.end_function();
let section = r#gen.generate_section();
let mut parser = X86StackMapParser::new(true);
parser.parse(§ion).unwrap();
assert!(parser.lookup_by_id(42).is_some());
}
#[test]
fn test_generator_with_statepoint() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("test", 128);
let mut sp = X86StatePoint::new(99, "alloc", 1);
sp.add_gc_root(X86StatepointValue::reg(0, "i8*"));
r#gen.generate_from_statepoint("test", &sp);
r#gen.end_function();
let section = r#gen.generate_section();
let mut parser = X86StackMapParser::new(true);
parser.parse(§ion).unwrap();
assert!(parser.lookup_by_id(99).is_some());
}
#[test]
fn test_parser_creation() {
let parser = X86StackMapParser::new(true);
assert!(!parser.is_parsed());
}
#[test]
fn test_parser_parse_empty_section() {
let mut parser = X86StackMapParser::new(true);
let fmt = X86StackMapFormat::new();
let bytes = fmt.to_bytes();
assert!(parser.parse(&bytes).is_ok());
assert!(parser.is_parsed());
assert_eq!(parser.record_count(), 0);
}
#[test]
fn test_parser_parse_with_data() {
let mut parser = X86StackMapParser::new(true);
let mut fmt = X86StackMapFormat::new();
let func = StackMapFunctionRecord::new(0x1000, 64, 0, 2);
let rec1 = StackMapRecord::new(10, 0x10);
let rec2 = StackMapRecord::new(20, 0x20);
fmt.add_function(func, Vec::new(), vec![rec1, rec2]);
let bytes = fmt.to_bytes();
parser.parse(&bytes).unwrap();
assert_eq!(parser.record_count(), 2);
assert_eq!(parser.version(), STACK_MAP_VERSION);
}
#[test]
fn test_parser_lookup_by_id() {
let mut parser = X86StackMapParser::new(true);
let mut fmt = X86StackMapFormat::new();
let func = StackMapFunctionRecord::new(0x1000, 64, 0, 1);
let rec = StackMapRecord::new(42, 0x10);
fmt.add_function(func, Vec::new(), vec![rec]);
parser.parse(&fmt.to_bytes()).unwrap();
let found = parser.lookup_by_id(42);
assert!(found.is_some());
assert_eq!(found.unwrap().instruction_offset, 0x10);
let not_found = parser.lookup_by_id(99);
assert!(not_found.is_none());
}
#[test]
fn test_parser_lookup_by_address() {
let mut parser = X86StackMapParser::new(true);
let mut fmt = X86StackMapFormat::new();
let func = StackMapFunctionRecord::new(0x2000, 64, 0, 2);
let rec1 = StackMapRecord::new(1, 0x10);
let rec2 = StackMapRecord::new(2, 0x20);
fmt.add_function(func, Vec::new(), vec![rec1, rec2]);
parser.parse(&fmt.to_bytes()).unwrap();
let records = parser.lookup_by_address(0x2000);
assert_eq!(records.len(), 2);
let no_records = parser.lookup_by_address(0x9999);
assert_eq!(no_records.len(), 0);
}
#[test]
fn test_parser_lookup_function() {
let mut parser = X86StackMapParser::new(true);
let mut fmt = X86StackMapFormat::new();
let func = StackMapFunctionRecord::new(0x3000, 96, 0, 0);
fmt.add_function(func.clone(), Vec::new(), Vec::new());
parser.parse(&fmt.to_bytes()).unwrap();
let found = parser.lookup_function(0x3000);
assert!(found.is_some());
assert_eq!(found.unwrap().stack_size, 96);
}
#[test]
fn test_parser_extract_live_locations() {
let mut parser = X86StackMapParser::new(true);
let mut fmt = X86StackMapFormat::new();
let func = StackMapFunctionRecord::new(0x1000, 64, 0, 1);
let mut rec = StackMapRecord::new(1, 0x10);
rec.add_location(LocationRecord::new_register(0, 8));
rec.add_location(LocationRecord::new_direct(6, -16, 8));
fmt.add_function(func, Vec::new(), vec![rec]);
parser.parse(&fmt.to_bytes()).unwrap();
let locs = parser.extract_live_locations(parser.lookup_by_id(1).unwrap());
assert_eq!(locs.len(), 2);
assert_eq!(locs[0].0, "RAX");
assert_eq!(locs[0].1, LocationKind::Register);
}
#[test]
fn test_parser_extract_gc_roots() {
let mut parser = X86StackMapParser::new(true);
let mut fmt = X86StackMapFormat::new();
let func = StackMapFunctionRecord::new(0x1000, 64, 0, 1);
let mut rec = StackMapRecord::new(1, 0x10);
rec.add_location(LocationRecord::new_register(0, 8)); rec.add_location(LocationRecord::new_constant(42)); fmt.add_function(func, Vec::new(), vec![rec]);
parser.parse(&fmt.to_bytes()).unwrap();
let roots = parser.extract_gc_roots(parser.lookup_by_id(1).unwrap(), 8);
assert!(!roots.is_empty());
}
#[test]
fn test_parser_extract_live_outs() {
let mut parser = X86StackMapParser::new(true);
let mut fmt = X86StackMapFormat::new();
let func = StackMapFunctionRecord::new(0x1000, 64, 0, 1);
let mut rec = StackMapRecord::new(1, 0x10);
rec.add_live_out(LiveOutRecord::new(0, 8));
rec.add_live_out(LiveOutRecord::new(2, 8));
fmt.add_function(func, Vec::new(), vec![rec]);
parser.parse(&fmt.to_bytes()).unwrap();
let live_outs = parser.extract_live_outs(parser.lookup_by_id(1).unwrap());
assert_eq!(live_outs.len(), 2);
assert_eq!(live_outs[0].0, "RAX");
}
#[test]
fn test_parser_duplicate_id_warning() {
let mut parser = X86StackMapParser::new(true);
let mut fmt = X86StackMapFormat::new();
let func = StackMapFunctionRecord::new(0x1000, 64, 0, 2);
let rec1 = StackMapRecord::new(1, 0x10);
let rec2 = StackMapRecord::new(1, 0x20); fmt.add_function(func, Vec::new(), vec![rec1, rec2]);
parser.parse(&fmt.to_bytes()).unwrap();
assert!(!parser.warnings.is_empty());
}
#[test]
fn test_parser_get_all_ids() {
let mut parser = X86StackMapParser::new(true);
let mut fmt = X86StackMapFormat::new();
let func = StackMapFunctionRecord::new(0x1000, 64, 0, 3);
fmt.add_function(
func,
Vec::new(),
vec![
StackMapRecord::new(5, 0x10),
StackMapRecord::new(3, 0x20),
StackMapRecord::new(1, 0x30),
],
);
parser.parse(&fmt.to_bytes()).unwrap();
let ids = parser.get_all_ids();
assert_eq!(ids, vec![1, 3, 5]);
}
#[test]
fn test_parser_dump_records() {
let mut parser = X86StackMapParser::new(true);
let mut fmt = X86StackMapFormat::new();
let func = StackMapFunctionRecord::new(0x1000, 64, 0, 1);
let mut rec = StackMapRecord::new(1, 0x10);
rec.add_location(LocationRecord::new_register(0, 8));
fmt.add_function(func, Vec::new(), vec![rec]);
parser.parse(&fmt.to_bytes()).unwrap();
let dump = parser.dump_records();
assert!(dump.contains("Stack Map Section"));
assert!(dump.contains("Record ID=1"));
assert!(dump.contains("RAX"));
}
#[test]
fn test_frame_state_creation() {
let fs = X86FrameState::new(0xABCD, 0x100);
assert_eq!(fs.method_ref, 0xABCD);
assert_eq!(fs.bytecode_offset, 0x100);
assert_eq!(fs.num_locals, 0);
assert_eq!(fs.num_stack_slots, 0);
}
#[test]
fn test_frame_state_add_local() {
let mut fs = X86FrameState::new(1, 0);
let loc = LocationRecord::new_register(0, 8);
fs.add_local(0, loc, "i8*", true);
assert_eq!(fs.num_locals, 1);
assert_eq!(fs.locals.len(), 1);
}
#[test]
fn test_frame_state_add_stack_slot() {
let mut fs = X86FrameState::new(1, 0);
let loc = LocationRecord::new_direct(6, -8, 8);
fs.add_stack_slot(0, loc, "i64", false);
assert_eq!(fs.num_stack_slots, 1);
}
#[test]
fn test_frame_state_add_monitor() {
let mut fs = X86FrameState::new(1, 0);
let loc = LocationRecord::new_register(0, 8);
fs.add_monitor(0, loc, false);
assert_eq!(fs.num_monitors, 1);
}
#[test]
fn test_frame_state_get_live_refs() {
let mut fs = X86FrameState::new(1, 0);
fs.add_local(0, LocationRecord::new_register(0, 8), "i8*", true);
fs.add_local(1, LocationRecord::new_register(1, 8), "i64", false);
fs.add_stack_slot(0, LocationRecord::new_direct(6, -8, 8), "i8*", true);
let refs = fs.get_live_refs();
assert_eq!(refs.len(), 2);
}
#[test]
fn test_frame_slot_mark_dead() {
let mut slot = X86FrameSlot::new_local(0, LocationRecord::new_register(0, 8), "i8*", true);
assert!(slot.is_live);
slot.mark_dead();
assert!(!slot.is_live);
}
#[test]
fn test_register_state_creation() {
let rs = X86RegisterState::new();
assert_eq!(rs.num_gprs, 0);
assert_eq!(rs.total_registers(), 0);
}
#[test]
fn test_register_state_add_gpr() {
let mut rs = X86RegisterState::new();
rs.add_gpr(0, 0xDEADBEEF, true);
assert_eq!(rs.num_gprs, 1);
assert!(rs.is_gpr_reference(0));
assert_eq!(rs.get_gpr(0), Some(0xDEADBEEF));
}
#[test]
fn test_register_state_add_xmm() {
let mut rs = X86RegisterState::new();
rs.add_xmm(17, 0xCAFE);
assert_eq!(rs.num_xmms, 1);
}
#[test]
fn test_register_state_set_rflags_rip() {
let mut rs = X86RegisterState::new();
rs.set_rflags(0x246);
rs.set_rip(0x4000);
assert_eq!(rs.rflags, Some(0x246));
assert_eq!(rs.rip, Some(0x4000));
}
#[test]
fn test_continuation_point_interpreter() {
let cp = X86ContinuationPoint::new_interpreter(0x100, 0xABCD, true);
assert_eq!(cp.bytecode_offset, 0x100);
assert_eq!(cp.method_ref, 0xABCD);
assert_eq!(cp.dispatch_kind, X86DeoptDispatchKind::Interpreter);
assert!(cp.is_reexecute);
}
#[test]
fn test_continuation_point_exception_handler() {
let cp = X86ContinuationPoint::new_exception_handler(0x50, 0xBEEF, 0x80);
assert_eq!(cp.dispatch_kind, X86DeoptDispatchKind::ExceptionHandler);
assert_eq!(cp.exception_handler_offset, Some(0x80));
}
#[test]
fn test_deopt_state_creation() {
let fs = X86FrameState::new(1, 0x10);
let rs = X86RegisterState::new();
let cp = X86ContinuationPoint::new_interpreter(0x10, 1, false);
let reason = DeoptReason::TypeCheckFailed {
expected: "String".to_string(),
actual: "Integer".to_string(),
};
let ds = X86DeoptState::new(42, fs, rs, cp, reason);
assert_eq!(ds.magic, DEOPT_BUNDLE_MAGIC);
assert_eq!(ds.version, DEOPT_BUNDLE_VERSION);
assert_eq!(ds.compilation_unit_id, 42);
assert!(ds.metadata.is_empty());
}
#[test]
fn test_deopt_reason_descriptions() {
let reasons = vec![
DeoptReason::TypeCheckFailed {
expected: "A".to_string(),
actual: "B".to_string(),
},
DeoptReason::BoundsCheckFailed {
index: 10,
length: 5,
},
DeoptReason::NullCheckFailed,
DeoptReason::SpeculationFailed { guard_id: 42 },
DeoptReason::CHAMiss,
DeoptReason::InlineCacheMiss,
DeoptReason::DebugDeopt,
];
for reason in reasons {
let desc = reason.description();
assert!(!desc.is_empty());
}
}
#[test]
fn test_deopt_state_get_gc_references() {
let mut fs = X86FrameState::new(1, 0);
fs.add_local(0, LocationRecord::new_register(0, 8), "i8*", true);
let mut rs = X86RegisterState::new();
rs.add_gpr(0, 0x1234, true);
rs.add_gpr(2, 0x5678, false);
let cp = X86ContinuationPoint::new_interpreter(0, 1, false);
let reason = DeoptReason::DebugDeopt;
let ds = X86DeoptState::new(1, fs, rs, cp, reason);
let refs = ds.get_gc_references();
assert!(!refs.is_empty());
}
#[test]
fn test_deopt_state_roundtrip() {
let mut fs = X86FrameState::new(1, 0x10);
fs.add_local(0, LocationRecord::new_register(0, 8), "i8*", true);
fs.add_stack_slot(0, LocationRecord::new_direct(6, -8, 8), "i64", false);
let mut rs = X86RegisterState::new();
rs.add_gpr(0, 0xDEAD, true);
rs.add_gpr(2, 0xBEEF, false);
let cp = X86ContinuationPoint::new_interpreter(0x10, 1, false);
let reason = DeoptReason::NullCheckFailed;
let ds = X86DeoptState::new(42, fs, rs, cp, reason);
let bytes = ds.to_bytes();
let parsed = X86DeoptState::from_bytes(&bytes).unwrap();
assert_eq!(parsed.magic, DEOPT_BUNDLE_MAGIC);
assert_eq!(parsed.compilation_unit_id, 42);
assert_eq!(parsed.frame_state.num_locals, 1);
assert_eq!(parsed.register_state.num_gprs, 2);
assert_eq!(parsed.continuation_point.bytecode_offset, 0x10);
}
#[test]
fn test_deopt_state_metadata() {
let mut ds = X86DeoptState::new(
1,
X86FrameState::new(1, 0),
X86RegisterState::new(),
X86ContinuationPoint::new_interpreter(0, 1, false),
DeoptReason::DebugDeopt,
);
ds.set_metadata(b"test_meta");
let bytes = ds.to_bytes();
let parsed = X86DeoptState::from_bytes(&bytes).unwrap();
assert_eq!(parsed.metadata, b"test_meta");
}
#[test]
fn test_deopt_state_validate() {
let ds = X86DeoptState::new(
1,
X86FrameState::new(1, 0),
X86RegisterState::new(),
X86ContinuationPoint::new_interpreter(0, 1, false),
DeoptReason::DebugDeopt,
);
assert!(ds.validate().is_ok());
}
#[test]
fn test_deopt_dispatch_kind_names() {
assert_eq!(X86DeoptDispatchKind::Interpreter.name(), "interpreter");
assert_eq!(X86DeoptDispatchKind::BaselineJIT.name(), "baseline-jit");
assert_eq!(X86DeoptDispatchKind::OptimizingJIT.name(), "optimizing-jit");
assert_eq!(
X86DeoptDispatchKind::ExceptionHandler.name(),
"exception-handler"
);
assert_eq!(X86DeoptDispatchKind::UnwindStub.name(), "unwind-stub");
}
#[test]
fn test_live_var_state_creation() {
let state = X86LiveVarState::new();
assert_eq!(state.frame_size, 0);
assert_eq!(state.var_locations.len(), 0);
}
#[test]
fn test_live_var_state_allocate_stack_slots() {
let mut state = X86LiveVarState::new();
let slot1 = state.allocate_stack_slot(8);
assert_eq!(slot1.offset, -8);
assert_eq!(state.frame_size, 8);
let slot2 = state.allocate_stack_slot(16);
assert_eq!(slot2.offset, -24);
assert_eq!(state.frame_size, 24);
}
#[test]
fn test_live_var_state_gc_pointer_tracking() {
let mut state = X86LiveVarState::new();
state.mark_gc_pointer(1);
state.mark_gc_pointer(2);
assert!(state.is_gc_pointer(1));
assert!(state.is_gc_pointer(2));
assert!(!state.is_gc_pointer(3));
let live = state.get_live_gc_pointers();
assert_eq!(live.len(), 0);
state.record_location(1, LocationRecord::new_register(0, 8));
let live = state.get_live_gc_pointers();
assert_eq!(live.len(), 1);
}
#[test]
fn test_scenario_creation() {
let scenario = X86StackMapScenario::new("test_func", 128, true);
assert_eq!(scenario.function_name, "test_func");
}
#[test]
fn test_scenario_add_patchpoint() {
let mut scenario = X86StackMapScenario::new("test_func", 128, true);
let pp = X86PatchPoint::new_call(1, "alloc", 1);
scenario.add_patchpoint(pp);
assert_eq!(scenario.patchpoints.len(), 1);
}
#[test]
fn test_scenario_add_statepoint() {
let mut scenario = X86StackMapScenario::new("test_func", 128, true);
let sp = X86StatePoint::new(2, "alloc", 1);
scenario.add_statepoint(sp);
assert_eq!(scenario.statepoints.len(), 1);
}
#[test]
fn test_scenario_add_entry_safepoint() {
let mut scenario = X86StackMapScenario::new("test_func", 128, true);
scenario.add_entry_safepoint();
assert_eq!(scenario.safepoints.len(), 1);
assert_eq!(scenario.safepoints[0].kind, SafepointKind::FunctionEntry);
}
#[test]
fn test_scenario_add_backedge_safepoint() {
let mut scenario = X86StackMapScenario::new("test_func", 128, true);
scenario.add_backedge_safepoint(0x40);
assert_eq!(scenario.safepoints.len(), 1);
assert_eq!(scenario.safepoints[0].kind, SafepointKind::LoopBackedge);
}
#[test]
fn test_scenario_record_stack_size() {
let mut scenario = X86StackMapScenario::new("test_func", 128, true);
scenario.record_stack_size(0x10, 256);
assert_eq!(scenario.stack_sizes.len(), 1);
}
#[test]
fn test_scenario_finalize() {
let mut scenario = X86StackMapScenario::new("test_func", 128, true);
scenario.add_entry_safepoint();
scenario
.generator_mut()
.record_register_location("test_func", 1, 0, 8, "i8*", true);
scenario
.generator_mut()
.generate_stack_map_record("test_func", 42);
let result = scenario.finalize();
assert!(result.is_ok());
let section = result.unwrap();
assert!(!section.is_empty());
let mut parser = X86StackMapParser::new(true);
parser.parse(§ion).unwrap();
assert!(parser.is_parsed());
}
#[test]
fn test_writer_creation() {
let writer = X86StackMapWriter::new();
assert_eq!(writer.alignment, 8);
assert!(writer.read_only);
}
#[test]
fn test_writer_generate_elf_section_header() {
let writer = X86StackMapWriter::new();
let header = writer.generate_elf_section_header(0x1000, 128);
assert_eq!(header.len(), 64);
}
#[test]
fn test_verifier_empty() {
let sm = X86StackMaps::new("x86_64-unknown-linux-gnu", true);
let mut verifier = X86StackMapVerifier::new(sm);
assert!(verifier.verify());
assert!(verifier.errors.is_empty());
}
#[test]
fn test_verifier_warns_empty_functions() {
let mut sm = X86StackMaps::new("x86_64-unknown-linux-gnu", true);
let idx = sm.register_function("empty_func", 64);
sm.finalize();
let func = StackMapFunctionRecord::new(0x1000, 64, 0, 0);
sm.format.functions.push(func);
let mut verifier = X86StackMapVerifier::new(sm);
assert!(verifier.verify()); assert!(!verifier.warnings.is_empty());
}
#[test]
fn test_verifier_duplicate_ids() {
let mut sm = X86StackMaps::new("x86_64-unknown-linux-gnu", true);
let idx = sm.register_function("test", 64);
sm.functions_data[0]
.records
.push(StackMapRecord::new(1, 0x10));
sm.functions_data[0]
.records
.push(StackMapRecord::new(1, 0x20));
sm.finalize();
let mut verifier = X86StackMapVerifier::new(sm);
assert!(!verifier.verify());
assert!(!verifier.errors.is_empty());
}
#[test]
fn test_verifier_report() {
let sm = X86StackMaps::new("x86_64-unknown-linux-gnu", true);
let verifier = X86StackMapVerifier::new(sm);
let report = verifier.report();
assert!(report.contains("PASS"));
}
#[test]
fn test_function_data_creation() {
let data = X86FunctionStackMapData::new("test", 128);
assert_eq!(data.name, "test");
assert_eq!(data.stack_size, 128);
assert_eq!(data.records.len(), 0);
}
#[test]
fn test_function_data_add_records() {
let mut data = X86FunctionStackMapData::new("test", 128);
data.add_stack_size(0, 128);
data.add_stack_size(0x20, 256);
assert_eq!(data.stack_sizes.len(), 2);
data.add_record(StackMapRecord::new(1, 0x10));
data.add_record(StackMapRecord::new(2, 0x20));
assert_eq!(data.records.len(), 2);
assert_eq!(data.offset_to_id.get(&0x10), Some(&1));
assert_eq!(data.offset_to_id.get(&0x20), Some(&2));
}
#[test]
fn test_safepoint_location_creation() {
let loc = X86SafepointLocation::new(SafepointKind::FunctionEntry, "test_func", 0x10, 42);
assert_eq!(loc.kind, SafepointKind::FunctionEntry);
assert_eq!(loc.function_name, "test_func");
assert_eq!(loc.instruction_offset, 0x10);
assert_eq!(loc.stack_map_id, 42);
assert!(loc.machine_instr.is_none());
}
#[test]
fn test_safepoint_location_set_machine_instr() {
let mut loc = X86SafepointLocation::new(SafepointKind::AfterCall, "test_func", 0x20, 1);
let instr = MachineInstr::new(0xE8);
loc.set_machine_instr(instr.clone());
assert!(loc.machine_instr.is_some());
}
#[test]
fn test_safepoint_stats_update_total() {
let mut stats = X86SafepointStats::default();
assert_eq!(stats.total, 0);
stats.entry_safepoints = 2;
stats.backedge_safepoints = 3;
stats.call_safepoints = 1;
stats.update_total();
assert_eq!(stats.total, 6);
}
#[test]
fn test_end_to_end_section_generation_and_parsing() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("function1", 128);
r#gen.record_register_location("function1", 1, 0, 8, "i8*", true);
r#gen.record_register_location("function1", 2, 1, 8, "i64", false);
r#gen.advance_offset(0x10);
r#gen.generate_stack_map_record("function1", 1);
r#gen.advance_offset(0x20);
r#gen.record_stack_location("function1", 3, -16, 8, "i8*", true, true);
r#gen.generate_stack_map_record("function1", 2);
r#gen.end_function();
r#gen.begin_function("function2", 256);
r#gen.record_register_location("function2", 1, 0, 8, "i8*", true);
r#gen.advance_offset(0x10);
r#gen.generate_stack_map_record("function2", 3);
r#gen.end_function();
r#gen.add_constant(0xDEAD);
r#gen.add_constant(0xBEEF);
let section = r#gen.generate_section();
let mut parser = X86StackMapParser::new(true);
parser.parse(§ion).unwrap();
assert_eq!(parser.record_count(), 3);
assert!(parser.lookup_by_id(1).is_some());
assert!(parser.lookup_by_id(2).is_some());
assert!(parser.lookup_by_id(3).is_some());
assert!(parser.lookup_by_id(99).is_none());
let all_ids = parser.get_all_ids();
assert_eq!(all_ids, vec![1, 2, 3]);
let record1 = parser.lookup_by_id(1).unwrap();
let locs = parser.extract_live_locations(record1);
assert!(!locs.is_empty());
}
#[test]
fn test_end_to_end_patchpoint_workflow() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("patch_func", 128);
let pp = X86PatchPoint::new_call(100, "alloc", 2);
let (sled, call) = pp.generate_machine_instrs();
assert!(!sled.is_empty());
assert!(call.is_some());
r#gen.generate_from_patchpoint("patch_func", &pp);
r#gen.end_function();
let section = r#gen.generate_section();
let mut parser = X86StackMapParser::new(true);
parser.parse(§ion).unwrap();
let found = parser.lookup_by_id(100);
assert!(found.is_some());
}
#[test]
fn test_end_to_end_statepoint_workflow() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("state_func", 128);
let mut sp = X86StatePoint::new(200, "alloc", 1);
let base_val = X86StatepointValue::reg(0, "i8*");
let base_idx = sp.add_gc_root(base_val);
sp.add_derived_pointer(X86StatepointValue::reg(1, "i8*"), base_idx, 16);
r#gen.generate_from_statepoint("state_func", &sp);
r#gen.end_function();
let section = r#gen.generate_section();
let mut parser = X86StackMapParser::new(true);
parser.parse(§ion).unwrap();
let found = parser.lookup_by_id(200);
assert!(found.is_some());
}
#[test]
fn test_end_to_end_safepoint_insertion() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("safe_func", 128);
let id = r#gen.stack_maps.allocate_id();
let (sp, _) = r#gen.safepoint.insert_entry_safepoint("safe_func", id);
r#gen.generate_from_safepoint("safe_func", &sp);
r#gen.advance_offset(0x40);
let id2 = r#gen.stack_maps.allocate_id();
let (sp2, _) = r#gen
.safepoint
.insert_backedge_safepoint("safe_func", 0x40, id2);
r#gen.generate_from_safepoint("safe_func", &sp2);
r#gen.end_function();
assert_eq!(r#gen.safepoint.stats.entry_safepoints, 1);
assert_eq!(r#gen.safepoint.stats.backedge_safepoints, 1);
let section = r#gen.generate_section();
let mut parser = X86StackMapParser::new(true);
parser.parse(§ion).unwrap();
}
#[test]
fn test_end_to_end_deoptimization_bundle() {
let mut fs = X86FrameState::new(0xCAFE, 0x40);
fs.add_local(0, LocationRecord::new_register(0, 8), "i8*", true);
fs.add_local(1, LocationRecord::new_register(1, 8), "i64", false);
fs.add_local(2, LocationRecord::new_direct(6, -8, 8), "i8*", true);
fs.add_stack_slot(0, LocationRecord::new_register(2, 8), "i32", false);
fs.add_stack_slot(1, LocationRecord::new_register(3, 8), "i8*", true);
let mut rs = X86RegisterState::new();
rs.add_gpr(0, 0x1000, true); rs.add_gpr(2, 0x2000, false); rs.add_gpr(6, 0x3000, false); rs.add_xmm(17, 0xCAFE_BABE);
let cp = X86ContinuationPoint::new_interpreter(0x40, 0xCAFE, false);
let reason = DeoptReason::BoundsCheckFailed {
index: 10,
length: 5,
};
let ds = X86DeoptState::new(100, fs, rs, cp, reason);
let bytes = ds.to_bytes();
assert!(!bytes.is_empty());
let parsed = X86DeoptState::from_bytes(&bytes).unwrap();
assert_eq!(parsed.magic, DEOPT_BUNDLE_MAGIC);
assert_eq!(parsed.compilation_unit_id, 100);
assert_eq!(parsed.frame_state.num_locals, 3);
assert_eq!(parsed.frame_state.num_stack_slots, 2);
assert_eq!(parsed.register_state.num_gprs, 3);
assert_eq!(parsed.register_state.num_xmms, 1);
assert_eq!(parsed.continuation_point.bytecode_offset, 0x40);
let gc_refs = parsed.get_gc_references();
assert!(!gc_refs.is_empty());
assert!(parsed.validate().is_ok());
}
#[test]
fn test_end_to_end_complete_scenario() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("main", 256);
r#gen.record_register_location("main", 1, 0, 8, "i8*", true);
r#gen.record_register_location("main", 2, 2, 8, "i64", false);
let eid = r#gen.stack_maps.allocate_id();
let (esp, _) = r#gen.safepoint.insert_entry_safepoint("main", eid);
r#gen.generate_from_safepoint("main", &esp);
r#gen.advance_offset(0x20);
let mut sp = X86StatePoint::new(100, "gc_alloc", 2);
sp.add_gc_root(X86StatepointValue::reg(0, "i8*"));
r#gen.generate_from_statepoint("main", &sp);
r#gen.advance_offset(0x30);
let bid = r#gen.stack_maps.allocate_id();
let (bsp, _) = r#gen.safepoint.insert_backedge_safepoint("main", 0x50, bid);
r#gen.generate_from_safepoint("main", &bsp);
r#gen.advance_offset(0x10);
let pp = X86PatchPoint::new_call(200, "compile_stub", 1);
r#gen.generate_from_patchpoint("main", &pp);
r#gen.end_function();
r#gen.begin_function("helper", 128);
r#gen.record_register_location("helper", 1, 0, 8, "i8*", true);
let eid2 = r#gen.stack_maps.allocate_id();
let (esp2, _) = r#gen.safepoint.insert_entry_safepoint("helper", eid2);
r#gen.generate_from_safepoint("helper", &esp2);
r#gen.end_function();
let section = r#gen.generate_section();
let mut parser = X86StackMapParser::new(true);
assert!(parser.parse(§ion).is_ok());
let all_ids = parser.get_all_ids();
assert!(!all_ids.is_empty());
let dump = parser.dump_records();
assert!(dump.contains("main"));
let mut verifier = X86StackMapVerifier::new(r#gen.stack_maps.clone());
assert!(verifier.verify());
}
#[test]
fn test_nop_sled_edge_cases() {
let edge_sizes = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 31, 32, 33, 63, 64, 65,
];
for size in &edge_sizes {
let pp = X86PatchPoint::new_region(0, *size);
let sled = pp.generate_nop_sled();
assert_eq!(
sled.len(),
*size,
"NOP sled size mismatch for edge size {}",
size
);
}
}
#[test]
fn test_multiple_functions_stack_size_tracking() {
let mut r#gen = X86StackMapGenerator::new(true);
r#gen.begin_function("dynamic_stack", 128);
r#gen.record_stack_size_change("dynamic_stack", 128);
r#gen.advance_offset(0x10);
r#gen.record_stack_size_change("dynamic_stack", 256);
r#gen.advance_offset(0x20);
r#gen.record_stack_size_change("dynamic_stack", 128);
r#gen.end_function();
let section = r#gen.generate_section();
let mut parser = X86StackMapParser::new(true);
assert!(parser.parse(§ion).is_ok());
}
#[test]
fn test_statepoint_all_arg_types() {
let mut sp = X86StatePoint::new(1, "callee", 4);
sp.add_call_arg(X86StatepointValue::reg(0, "i64"));
sp.add_call_arg(X86StatepointValue::stack(-8, "double"));
sp.add_call_arg(X86StatepointValue::constant(42, "i32"));
sp.add_call_arg(X86StatepointValue::undef("float"));
assert_eq!(sp.call_args.len(), 4);
assert_eq!(sp.num_call_args, 4);
}
#[test]
fn test_location_record_vector_splice() {
let loc = LocationRecord::new_vector_splice(17, 0, 32);
assert_eq!(loc.kind, LocationKind::VectorSplice);
assert_eq!(loc.dwarf_reg_num, 17);
assert_eq!(loc.size, 32);
let bytes = loc.to_bytes();
let mut cursor = Cursor::new(&bytes);
let parsed = LocationRecord::from_reader(&mut cursor).unwrap();
assert_eq!(parsed.kind, LocationKind::VectorSplice);
}
#[test]
fn test_register_names_ymm_zmm() {
let names = X86RegisterNames::new(true);
assert_eq!(names.resolve(49), "YMM0");
assert_eq!(names.resolve(81), "ZMM0");
assert!(names.is_ymm(49));
assert!(names.is_zmm(81));
}
#[test]
fn test_frame_map_creation() {
let fm = X86FrameMap::new(256);
assert_eq!(fm.frame_size, 256);
assert_eq!(fm.total_slots(), 0);
assert!(!fm.validated);
}
#[test]
fn test_frame_map_add_local() {
let mut fm = X86FrameMap::new(128);
let idx = fm.add_local("i64", 8);
assert_eq!(idx, 0);
assert_eq!(fm.num_locals, 1);
assert_eq!(fm.total_slots(), 1);
assert_eq!(fm.slots[0].slot_kind, X86FrameSlotKind::Local);
}
#[test]
fn test_frame_map_add_stack_slot() {
let mut fm = X86FrameMap::new(128);
let idx = fm.add_stack_slot("i32", 4);
assert_eq!(idx, 0);
assert_eq!(fm.num_stack_slots, 1);
assert_eq!(fm.slots[0].slot_kind, X86FrameSlotKind::Stack);
}
#[test]
fn test_frame_map_add_monitor() {
let mut fm = X86FrameMap::new(128);
let idx = fm.add_monitor();
assert_eq!(idx, 0);
assert_eq!(fm.num_monitors, 1);
assert!(fm.slots[0].is_reference);
}
#[test]
fn test_frame_map_register_mapping() {
let mut fm = X86FrameMap::new(128);
let idx = fm.add_local("i64", 8);
fm.map_register_to_slot(DWARF_REG_RAX, idx);
let slot = fm.slot_for_register(DWARF_REG_RAX);
assert!(slot.is_some());
assert!(slot.unwrap().location.is_register());
}
#[test]
fn test_frame_map_stack_mapping() {
let mut fm = X86FrameMap::new(128);
let idx = fm.add_local("i64", 8);
fm.map_stack_to_slot(-16, idx);
let slot = fm.slot_for_stack_offset(-16);
assert!(slot.is_some());
assert!(slot.unwrap().location.is_stack());
}
#[test]
fn test_frame_map_reference_slots() {
let mut fm = X86FrameMap::new(128);
let idx1 = fm.add_local("ptr", 8);
fm.slots[idx1].mark_reference();
let idx2 = fm.add_local("num", 4);
fm.slots[idx2].mark_dead();
let refs = fm.reference_slots();
assert_eq!(refs.len(), 1);
}
#[test]
fn test_frame_map_validate() {
let mut fm = X86FrameMap::new(128);
assert!(fm.validate());
assert!(fm.validated);
}
#[test]
fn test_frame_map_validate_with_bad_bundle() {
let mut fm = X86FrameMap::new(256);
let bundle = X86DeoptBundle {
magic: 0xDEADBEEF, ..X86DeoptBundle::default()
};
fm.set_deopt_bundle(bundle);
assert!(!fm.validate());
}
#[test]
fn test_frame_map_encode() {
let mut fm = X86FrameMap::new(128);
fm.add_local("i64", 8);
let idx = fm.add_stack_slot("i32", 4);
fm.map_register_to_slot(DWARF_REG_RAX, idx);
let encoded = fm.encode();
assert!(encoded.len() > 16);
assert_eq!(
u32::from_le_bytes([encoded[0], encoded[1], encoded[2], encoded[3]]),
128
);
}
#[test]
fn test_frame_map_generate_save_area_cfi() {
let fm = X86FrameMap::new(256);
let cfi = fm.generate_save_area_cfi();
assert_eq!(cfi.len(), 0);
}
#[test]
fn test_frame_slot_kind_names() {
assert_eq!(X86FrameSlotKind::Local.name(), "Local");
assert_eq!(X86FrameSlotKind::Stack.name(), "Stack");
assert_eq!(X86FrameSlotKind::Monitor.name(), "Monitor");
assert_eq!(X86FrameSlotKind::CalleeSaveSpill.name(), "CalleeSaveSpill");
assert_eq!(X86FrameSlotKind::ReturnAddress.name(), "ReturnAddress");
assert_eq!(
X86FrameSlotKind::FramePointerSave.name(),
"FramePointerSave"
);
}
#[test]
fn test_frame_value_location_properties() {
let reg_loc = X86FrameValueLocation::Register { dwarf_reg: 0 };
assert!(reg_loc.is_register());
assert!(!reg_loc.is_stack());
assert_eq!(reg_loc.dwarf_reg(), Some(0));
let stack_loc = X86FrameValueLocation::Stack { offset: -8 };
assert!(stack_loc.is_stack());
assert!(!reg_loc.is_constant());
assert_eq!(stack_loc.stack_offset(), Some(-8));
let const_loc = X86FrameValueLocation::Constant { value: 42 };
assert!(const_loc.is_constant());
}
#[test]
fn test_frame_slot_set_debug_name() {
let mut slot = X86FrameMapSlot::new_local(0, "i64", 8);
assert!(slot.debug_name.is_none());
slot.set_debug_name("myVar");
assert_eq!(slot.debug_name, Some("myVar".to_string()));
}
#[test]
fn test_register_save_area_creation() {
let mut area = X86RASaveArea::new(-128);
assert_eq!(area.base_offset, -128);
area.save_gpr(3, -8);
area.save_xmm(17, -24);
assert_eq!(area.total_saved_regs(), 2);
assert_eq!(area.gpr_offset(3), Some(-8));
}
#[test]
fn test_register_save_area_rflags_mxcsr() {
let mut area = X86RASaveArea::new(-256);
area.save_rflags(-32);
area.save_mxcsr(-40);
assert!(area.saves_rflags);
assert!(area.saves_mxcsr);
assert_eq!(area.rflags_offset, Some(-32));
assert_eq!(area.mxcsr_offset, Some(-40));
}
#[test]
fn test_register_save_area_align_size() {
let mut area = X86RASaveArea::new(-128);
area.save_gpr(3, -8);
area.align_size();
assert_eq!(area.total_size % 16, 0);
}
#[test]
fn test_deopt_bundle_creation() {
let bundle = X86DeoptBundle::new(
DeoptReason::TypeCheckFailed {
expected: "int".to_string(),
actual: "float".to_string(),
},
42,
);
assert_eq!(bundle.magic, DEOPT_BUNDLE_MAGIC);
assert_eq!(bundle.version, DEOPT_BUNDLE_VERSION);
assert_eq!(bundle.compilation_unit_id, 42);
assert!(bundle.validate());
}
#[test]
fn test_deopt_bundle_set_state() {
let mut bundle = X86DeoptBundle::new(DeoptReason::NullCheckFailed, 1);
bundle.set_frame_state(vec![1, 2, 3], 4, 5);
bundle.set_register_state(vec![6, 7, 8]);
bundle.set_continuation(vec![9, 10]);
assert_eq!(bundle.num_register_values, 4);
assert_eq!(bundle.num_stack_values, 5);
assert!(bundle.total_size() > 16);
}
#[test]
fn test_deopt_bundle_default() {
let bundle = X86DeoptBundle::default();
assert_eq!(bundle.magic, DEOPT_BUNDLE_MAGIC);
assert!(bundle.validate());
}
#[test]
fn test_frame_reconstructor_basic() {
let mut fm = X86FrameMap::new(256);
let idx = fm.add_local("ref", 8);
fm.slots[idx].mark_reference();
fm.slots[idx].set_location(X86FrameValueLocation::Register {
dwarf_reg: DWARF_REG_RAX,
});
let mut recon = X86FrameReconstructor::new(fm);
recon.set_register(DWARF_REG_RAX, 0x1234_5678);
assert!(recon.reconstruct());
assert_eq!(recon.get_slot_value(idx), Some(0x1234_5678));
}
#[test]
fn test_frame_reconstructor_from_stack() {
let mut fm = X86FrameMap::new(256);
let idx = fm.add_local("val", 4);
fm.slots[idx].set_location(X86FrameValueLocation::Stack { offset: -8 });
let mut recon = X86FrameReconstructor::new(fm);
let val_bytes = 42i64.to_le_bytes().to_vec();
recon.set_stack_memory(-8, val_bytes);
assert!(recon.reconstruct());
assert_eq!(recon.get_slot_value(idx), Some(42));
}
#[test]
fn test_frame_reconstructor_gc_references() {
let mut fm = X86FrameMap::new(128);
let idx1 = fm.add_local("obj1", 8);
fm.slots[idx1].mark_reference();
fm.slots[idx1].set_location(X86FrameValueLocation::Register {
dwarf_reg: DWARF_REG_RAX,
});
let idx2 = fm.add_local("obj2", 8);
fm.slots[idx2].mark_reference();
fm.slots[idx2].set_location(X86FrameValueLocation::Register { dwarf_reg: 5 });
let mut recon = X86FrameReconstructor::new(fm);
recon.set_register(DWARF_REG_RAX, 0xAAAA);
recon.set_register(5, 0xBBBB);
assert!(recon.reconstruct());
let refs = recon.get_gc_references();
assert_eq!(refs.len(), 2);
assert!(refs.contains(&0xAAAA));
assert!(refs.contains(&0xBBBB));
}
#[test]
fn test_eh_stack_map_creation() {
let eh = X86EHStackMap::new("test_func");
assert_eq!(eh.function_name, "test_func");
assert!(!eh.has_eh);
assert_eq!(eh.landing_pad_count(), 0);
}
#[test]
fn test_eh_stack_map_add_landing_pad() {
let mut eh = X86EHStackMap::new("func");
let lp = X86LandingPadMap::new(0x100, 42);
eh.add_landing_pad(0x100, lp);
assert!(eh.has_eh);
assert_eq!(eh.landing_pad_count(), 1);
assert!(eh.landing_pad_at(0x100).is_some());
}
#[test]
fn test_eh_stack_map_personality() {
let mut eh = X86EHStackMap::new("func");
let persona = X86PersonalityFuncMap::default();
eh.set_personality(persona);
assert!(eh.has_eh);
assert!(eh.personality.is_some());
assert!(eh.personality.unwrap().is_gcc_personality);
}
#[test]
fn test_eh_stack_map_cleanup() {
let mut eh = X86EHStackMap::new("func");
let cleanup = X86CleanupMap::new(0x200, 99);
eh.add_cleanup(0x200, cleanup);
assert_eq!(eh.cleanup_count(), 1);
}
#[test]
fn test_eh_stack_map_encode() {
let mut eh = X86EHStackMap::new("func");
let lp = X86LandingPadMap::new(0x100, 42);
eh.add_landing_pad(0x100, lp);
let encoded = eh.encode();
assert!(encoded.len() > 0);
assert_eq!(encoded[0], 1);
}
#[test]
fn test_landing_pad_add_catch() {
let mut lp = X86LandingPadMap::new(0x100, 1);
lp.add_catch(0x4000, "std::exception");
assert_eq!(lp.clauses.len(), 1);
assert!(lp.clauses[0].is_catch());
}
#[test]
fn test_landing_pad_add_filter() {
let mut lp = X86LandingPadMap::new(0x100, 1);
lp.add_filter(vec![0x5000], vec!["MyException".to_string()]);
assert_eq!(lp.clauses.len(), 1);
assert!(lp.clauses[0].is_filter());
}
#[test]
fn test_landing_pad_cleanup_clause() {
let mut lp = X86LandingPadMap::new(0x100, 1);
lp.add_cleanup_clause();
assert!(lp.is_cleanup);
assert!(lp.clauses[0].is_cleanup());
}
#[test]
fn test_landing_pad_catch_all() {
let mut lp = X86LandingPadMap::new(0x200, 2);
assert!(!lp.is_catch_all);
lp.set_catch_all();
assert!(lp.is_catch_all);
}
#[test]
fn test_landing_pad_register_config() {
let mut lp = X86LandingPadMap::new(0x300, 3);
lp.set_exception_pointer_reg(DWARF_REG_RAX);
lp.set_selector_reg(1);
lp.set_exception_object_spill(-16);
assert_eq!(lp.exception_pointer_reg, Some(DWARF_REG_RAX));
assert_eq!(lp.selector_reg, Some(1));
assert_eq!(lp.exception_object_spill, Some(-16));
}
#[test]
fn test_cleanup_map_creation() {
let mut cm = X86CleanupMap::new(0x100, 5);
assert_eq!(cm.stack_map_id, 5);
assert!(!cm.destroys_locals);
}
#[test]
fn test_cleanup_map_destructors() {
let mut cm = X86CleanupMap::new(0x100, 5);
cm.add_destructor_call(-24, "MyClass");
assert!(cm.calls_destructors);
assert_eq!(cm.destructor_calls.len(), 1);
}
#[test]
fn test_cleanup_map_mark_run_on_normal_exit() {
let mut cm = X86CleanupMap::new(0x200, 6);
assert!(!cm.run_on_normal_exit);
cm.mark_run_on_normal_exit();
assert!(cm.run_on_normal_exit);
}
#[test]
fn test_eh_map_generator_creation() {
let r#gen = X86EHMapGenerator::new(true);
assert!(r#gen.enabled);
assert_eq!(r#gen.function_count(), 0);
}
#[test]
fn test_eh_map_generator_get_or_create() {
let mut r#gen = X86EHMapGenerator::new(true);
{
let eh = r#gen.get_or_create("func1");
eh.add_landing_pad(0x100, X86LandingPadMap::new(0x100, 1));
}
assert_eq!(r#gen.function_count(), 1);
let eh2 = r#gen.get_or_create("func1");
assert_eq!(eh2.landing_pad_count(), 1);
}
#[test]
fn test_eh_map_generator_emit_section() {
let mut r#gen = X86EHMapGenerator::new(true);
r#gen.get_or_create("func")
.add_landing_pad(0x100, X86LandingPadMap::new(0x100, 1));
let section = r#gen.emit_section();
assert!(section.len() > 0);
}
#[test]
fn test_eh_map_generator_emit_assembly() {
let mut r#gen = X86EHMapGenerator::new(true);
r#gen.get_or_create("func")
.add_landing_pad(0x100, X86LandingPadMap::new(0x100, 1));
let asm = r#gen.emit_assembly();
assert!(asm.contains(".eh_frame_stackmaps"));
assert!(asm.contains("func"));
}
#[test]
fn test_eh_map_complex_scenario() {
let mut eh = X86EHStackMap::new("complexFunc");
let mut lp = X86LandingPadMap::new(0x100, 1);
lp.add_catch(0x4000, "TypeA");
lp.set_exception_pointer_reg(DWARF_REG_RAX);
eh.add_landing_pad(0x100, lp);
let persona = X86PersonalityFuncMap {
personality_func: "__gxx_personality_v0".to_string(),
is_gcc_personality: true,
is_seh_personality: false,
language: Some("C++".to_string()),
got_offset: Some(0x1000),
};
eh.set_personality(persona);
let mut cleanup = X86CleanupMap::new(0x300, 3);
cleanup.mark_destroys_locals();
cleanup.mark_releases_monitors();
cleanup.add_destructor_call(-32, "LockGuard");
eh.add_cleanup(0x300, cleanup);
assert_eq!(eh.landing_pad_count(), 1);
assert_eq!(eh.cleanup_count(), 1);
assert!(eh.has_eh);
}
#[test]
fn test_debug_var_location_register() {
let loc = X86DebugVarLocation::new_register("x", DWARF_REG_RAX, "i32", 42, 0x100);
assert_eq!(loc.var_name, "x");
assert!(loc.is_live);
assert_eq!(loc.stack_map_id, 42);
assert!(!loc.is_entry_value);
}
#[test]
fn test_debug_var_location_stack() {
let loc = X86DebugVarLocation::new_stack("y", -16, 8, "i64", 99, 0x200);
assert_eq!(loc.var_name, "y");
assert!(loc.is_live);
assert_eq!(loc.instruction_offset, 0x200);
}
#[test]
fn test_debug_var_location_mark_unavailable() {
let mut loc = X86DebugVarLocation::new_register("z", DWARF_REG_RAX, "i64", 1, 0);
loc.mark_unavailable();
assert!(!loc.is_live);
}
#[test]
fn test_debug_var_location_entry_value() {
let mut loc = X86DebugVarLocation::new_register("param", DWARF_REG_RAX, "i64", 1, 0);
assert!(!loc.is_entry_value);
loc.mark_entry_value();
assert!(loc.is_entry_value);
}
#[test]
fn test_debug_var_location_fragment() {
let mut loc = X86DebugVarLocation::new_register("big", DWARF_REG_RAX, "[4 x i32]", 1, 0);
loc.set_fragment(0, 4, 16);
assert!(loc.fragment.is_some());
let frag = loc.fragment.unwrap();
assert_eq!(frag.offset, 0);
assert_eq!(frag.size, 4);
assert_eq!(frag.total_size, 16);
}
#[test]
fn test_dwarf_expr_register_0_31() {
let r#gen = X86DwarfExprGenerator::new(false);
let expr = r#gen.generate_register_expr(0, 0);
assert_eq!(expr[0], 0x50);
}
#[test]
fn test_dwarf_expr_register_above_31() {
let r#gen = X86DwarfExprGenerator::new(false);
let expr = r#gen.generate_register_expr(49, 0);
assert_eq!(expr[0], 0x90);
}
#[test]
fn test_dwarf_expr_register_with_offset() {
let r#gen = X86DwarfExprGenerator::new(false);
let expr = r#gen.generate_register_expr(DWARF_REG_RAX, 8);
assert!(expr.len() > 2);
}
#[test]
fn test_dwarf_expr_stack() {
let r#gen = X86DwarfExprGenerator::new(false);
let expr = r#gen.generate_stack_expr(-16, 8);
assert_eq!(expr[0], 0x91);
}
#[test]
fn test_dwarf_expr_constant() {
let r#gen = X86DwarfExprGenerator::new(false);
let expr0 = r#gen.generate_constant_expr(0);
assert_eq!(expr0[0], 0x30);
let expr42 = r#gen.generate_constant_expr(42);
assert!(expr42.len() >= 1);
}
#[test]
fn test_dwarf_expr_memory() {
let r#gen = X86DwarfExprGenerator::new(false);
let expr = r#gen.generate_memory_expr(0x600000);
assert_eq!(expr[0], 0x03); }
#[test]
fn test_dwarf_expr_composite() {
let r#gen = X86DwarfExprGenerator::new(false);
let pieces = vec![
X86DebugPiece::new(
0,
4,
X86DebugLocation::Register {
dwarf_reg: DWARF_REG_RAX,
offset: 0,
},
),
X86DebugPiece::new(
4,
4,
X86DebugLocation::Stack {
frame_offset: -16,
size: 4,
},
),
];
let expr = r#gen.generate_composite_expr(&pieces);
assert!(expr.len() > 4);
}
#[test]
fn test_dwarf_expr_generate_from_location() {
let r#gen = X86DwarfExprGenerator::new(false);
let loc = X86DebugLocation::Register {
dwarf_reg: DWARF_REG_RAX,
offset: 0,
};
let expr = r#gen.generate_expr(&loc);
assert_eq!(expr[0], 0x50); }
#[test]
fn test_dwarf_expr_loclist_entry() {
let r#gen = X86DwarfExprGenerator::new(false);
let loc = X86DebugLocation::Register {
dwarf_reg: DWARF_REG_RAX,
offset: 0,
};
let entry = r#gen.generate_loclist_entry(0x100, 0x200, &loc);
assert!(entry.len() > 8);
}
#[test]
fn test_dwarf_expr_generator_frame_base() {
let mut r#gen = X86DwarfExprGenerator::new(false);
assert_eq!(r#gen.frame_base_reg, DWARF_REG_RBP);
r#gen.set_frame_base(DWARF_REG_RSP, 8);
assert_eq!(r#gen.frame_base_reg, DWARF_REG_RSP);
assert_eq!(r#gen.frame_base_offset, 8);
}
#[test]
fn test_debug_value_record_creation() {
let loc = X86DebugVarLocation::new_register("v", DWARF_REG_RAX, "i64", 1, 0x100);
let mut rec = X86DebugValueRecord::new(loc);
assert!(!rec.expression_valid);
let r#gen = X86DwarfExprGenerator::new(false);
rec.generate_expression(&r#gen);
assert!(rec.expression_valid);
assert!(rec.dwarf_expression.len() > 0);
}
#[test]
fn test_debug_value_record_di_expression() {
let loc = X86DebugVarLocation::new_register("v", DWARF_REG_RAX, "i64", 1, 0);
let mut rec = X86DebugValueRecord::new(loc);
rec.set_di_expression(vec![0x12, 0x34]);
assert_eq!(rec.di_expression, vec![0x12, 0x34]);
}
#[test]
fn test_debug_info_map_creation() {
let map = X86DebugInfoMap::new("func");
assert_eq!(map.function_name, "func");
assert!(!map.has_debug_info);
assert_eq!(map.record_count(), 0);
}
#[test]
fn test_debug_info_map_record_value() {
let mut map = X86DebugInfoMap::new("func");
let loc = X86DebugVarLocation::new_register("a", DWARF_REG_RAX, "i64", 1, 0x100);
let rec = X86DebugValueRecord::new(loc);
map.record_debug_value(rec);
assert!(map.has_debug_info);
assert_eq!(map.record_count(), 1);
assert!(map.locations_at_id(1).is_some());
assert!(map.locations_at_offset(0x100).is_some());
}
#[test]
fn test_debug_info_map_all_variable_names() {
let mut map = X86DebugInfoMap::new("func");
map.record_debug_value(X86DebugValueRecord::new(X86DebugVarLocation::new_register(
"x",
DWARF_REG_RAX,
"i64",
1,
0,
)));
map.record_debug_value(X86DebugValueRecord::new(X86DebugVarLocation::new_register(
"y", 5, "i32", 2, 4,
)));
let names = map.all_variable_names();
assert_eq!(names.len(), 2);
assert!(names.contains("x"));
assert!(names.contains("y"));
}
#[test]
fn test_debug_info_map_emit_debug_loc() {
let mut map = X86DebugInfoMap::new("func");
let loc = X86DebugVarLocation::new_register("x", DWARF_REG_RAX, "i64", 1, 0x100);
let mut rec = X86DebugValueRecord::new(loc);
let r#gen = X86DwarfExprGenerator::new(false);
rec.generate_expression(&r#gen);
map.record_debug_value(rec);
let section = map.emit_debug_loc_section(&r#gen);
assert!(section.len() > 0);
assert!(section.ends_with(&[0u8; 16]));
}
#[test]
fn test_debug_loc_tracker_lifecycle() {
let mut tracker = X86DebugLocTracker::new();
assert!(!tracker.active);
tracker.begin();
assert!(tracker.active);
tracker.end();
assert!(!tracker.active);
}
#[test]
fn test_debug_loc_tracker_set_and_get() {
let mut tracker = X86DebugLocTracker::new();
tracker.begin();
tracker.set_location(
"myVar",
X86DebugLocation::Register {
dwarf_reg: DWARF_REG_RAX,
offset: 0,
},
"i64",
);
let loc = tracker.get_location("myVar");
assert!(loc.is_some());
assert!(matches!(
loc.unwrap(),
X86DebugLocation::Register { dwarf_reg: 0, .. }
));
assert_eq!(tracker.get_type("myVar"), Some(&"i64".to_string()));
}
#[test]
fn test_debug_loc_tracker_capture_locations() {
let mut tracker = X86DebugLocTracker::new();
tracker.begin();
tracker.advance_to(0x100);
tracker.set_location(
"a",
X86DebugLocation::Register {
dwarf_reg: DWARF_REG_RAX,
offset: 0,
},
"i64",
);
tracker.set_location(
"b",
X86DebugLocation::Stack {
frame_offset: -8,
size: 4,
},
"i32",
);
let captured = tracker.capture_locations(42);
assert_eq!(captured.len(), 2);
for var in &captured {
assert_eq!(var.stack_map_id, 42);
assert_eq!(var.instruction_offset, 0x100);
}
}
#[test]
fn test_debug_loc_tracker_inactive_ignores_ops() {
let mut tracker = X86DebugLocTracker::new();
tracker.set_location(
"x",
X86DebugLocation::Register {
dwarf_reg: DWARF_REG_RAX,
offset: 0,
},
"i64",
);
assert!(tracker.get_location("x").is_none());
}
#[test]
fn test_debug_loc_tracker_history() {
let mut tracker = X86DebugLocTracker::new();
tracker.begin();
tracker.set_location(
"v",
X86DebugLocation::Register {
dwarf_reg: DWARF_REG_RAX,
offset: 0,
},
"i64",
);
tracker.advance_to(0x10);
tracker.set_location(
"v",
X86DebugLocation::Stack {
frame_offset: -16,
size: 8,
},
"i64",
);
assert_eq!(tracker.location_history.len(), 2);
assert_eq!(tracker.location_history[0].0, 0);
assert_eq!(tracker.location_history[1].0, 0x10);
}
#[test]
fn test_safepoint_action_names() {
assert_eq!(X86SafepointAction::PollOnly.name(), "PollOnly");
assert_eq!(
X86SafepointAction::GCCollect {
generation: 0,
is_full_gc: true
}
.name(),
"GCCollect"
);
assert_eq!(X86SafepointAction::Suspend.name(), "Suspend");
}
#[test]
fn test_safepoint_action_requires_gc_pause() {
assert!(!X86SafepointAction::PollOnly.requires_gc_pause());
assert!(X86SafepointAction::GCCollect {
generation: 0,
is_full_gc: false
}
.requires_gc_pause());
assert!(!X86SafepointAction::Suspend.requires_gc_pause());
}
#[test]
fn test_safepoint_action_requires_deopt() {
assert!(!X86SafepointAction::PollOnly.requires_deopt());
assert!(X86SafepointAction::Deoptimize {
reason: DeoptReason::NullCheckFailed,
target_bytecode_offset: 0,
}
.requires_deopt());
}
#[test]
fn test_safepoint_action_requires_suspension() {
assert!(!X86SafepointAction::PollOnly.requires_suspension());
assert!(X86SafepointAction::Suspend.requires_suspension());
assert!(X86SafepointAction::GCCollect {
generation: 0,
is_full_gc: true
}
.requires_suspension());
}
#[test]
fn test_safepoint_guard_page_creation() {
let page = X86SafepointGuardPage::new(0x7FFF_FFFF_0000, 4096);
assert_eq!(page.page_address, 0x7FFF_FFFF_0000);
assert!(!page.is_armed);
}
#[test]
fn test_safepoint_guard_page_arm_disarm() {
let mut page = X86SafepointGuardPage::new(0x1000, 4096);
assert!(!page.is_armed);
page.arm();
assert!(page.is_armed);
page.disarm();
assert!(!page.is_armed);
}
#[test]
fn test_safepoint_guard_page_poll_bytes() {
let page = X86SafepointGuardPage::new(0x7FFF_FFFF_0000, 4096);
let bytes = page.generate_poll_instruction_bytes();
assert_eq!(bytes.len(), 14);
assert_eq!(bytes[0], 0x49);
assert_eq!(bytes[1], 0xBB);
}
#[test]
fn test_safepoint_guard_page_rip_relative_poll() {
let page = X86SafepointGuardPage::new(0x1000, 4096);
let bytes = page.generate_rip_relative_poll(0x100);
assert_eq!(bytes.len(), 7);
}
#[test]
fn test_cooperative_handshake_creation() {
let hs = X86CooperativeGCHandshake::new(true);
assert_eq!(hs.current_state(), X86HandshakeState::Running);
assert!(hs.supports_parking);
assert!(!hs.needs_yield());
}
#[test]
fn test_cooperative_handshake_full_flow() {
let mut hs = X86CooperativeGCHandshake::new(false);
assert_eq!(hs.current_state(), X86HandshakeState::Running);
hs.request_gc();
assert!(hs.needs_yield());
assert_eq!(hs.current_state(), X86HandshakeState::GCRequested);
hs.reach_safepoint();
assert_eq!(hs.current_state(), X86HandshakeState::AtSafepoint);
hs.yield_to_gc();
assert!(hs.is_yielded());
assert_eq!(hs.yield_count, 1);
hs.resume();
assert_eq!(hs.current_state(), X86HandshakeState::Resume);
hs.resume_execution();
assert_eq!(hs.current_state(), X86HandshakeState::Running);
assert!(!hs.needs_yield());
}
#[test]
fn test_handshake_state_names() {
assert_eq!(X86HandshakeState::Running.name(), "Running");
assert_eq!(X86HandshakeState::GCRequested.name(), "GCRequested");
assert_eq!(X86HandshakeState::AtSafepoint.name(), "AtSafepoint");
assert_eq!(X86HandshakeState::Yielded.name(), "Yielded");
assert_eq!(X86HandshakeState::Resume.name(), "Resume");
assert_eq!(X86HandshakeState::Resuming.name(), "Resuming");
}
#[test]
fn test_x86_safepoints_creation() {
let sp = X86Safepoints::new();
assert_eq!(sp.total_hits(), 0);
assert_eq!(sp.pending_action_count(), 0);
assert!(!sp.global_gc_in_progress);
}
#[test]
fn test_x86_safepoints_actions() {
let mut sp = X86Safepoints::new();
sp.set_action(1, X86SafepointAction::PollOnly);
sp.set_action(2, X86SafepointAction::Suspend);
assert_eq!(sp.pending_action_count(), 2);
assert!(sp.get_action(1).is_some());
assert!(sp.get_action(999).is_none());
let taken = sp.take_action(1);
assert_eq!(taken, Some(X86SafepointAction::PollOnly));
assert_eq!(sp.pending_action_count(), 1);
}
#[test]
fn test_x86_safepoints_thread_tracking() {
let mut sp = X86Safepoints::new();
sp.thread_at_safepoint(100);
sp.thread_at_safepoint(200);
sp.thread_at_safepoint(300);
assert_eq!(sp.threads_at_safepoint_count(), 3);
assert_eq!(sp.total_hits(), 3);
sp.thread_left_safepoint(100);
assert_eq!(sp.threads_at_safepoint_count(), 2);
assert!(sp.all_threads_at_safepoint(2));
assert!(!sp.all_threads_at_safepoint(3));
}
#[test]
fn test_x86_safepoints_gc_cycle() {
let mut sp = X86Safepoints::new();
assert!(!sp.global_gc_in_progress);
sp.begin_global_gc(2);
assert!(sp.global_gc_in_progress);
assert_eq!(sp.current_gc_generation, 2);
assert!(sp.guard_page.is_armed);
sp.end_global_gc();
assert!(!sp.global_gc_in_progress);
assert!(!sp.guard_page.is_armed);
}
#[test]
fn test_x86_safepoints_poll_fault_check() {
let mut sp = X86Safepoints::new();
sp.configure_guard_page(0x7FFF_FFFF_0000, 4096);
assert!(sp.is_poll_fault(0x7FFF_FFFF_0000));
assert!(sp.is_poll_fault(0x7FFF_FFFF_0100));
assert!(!sp.is_poll_fault(0x1000));
assert!(!sp.is_poll_fault(0x7FFF_FFFF_1000));
}
#[test]
fn test_x86_safepoints_signal_handler_stub() {
let sp = X86Safepoints::new();
let asm = sp.generate_signal_handler_stub();
assert!(asm.contains("safepoint_signal_handler:"));
assert!(asm.contains("safepoint_is_poll_fault"));
assert!(asm.contains("safepoint_execute_action"));
}
#[test]
fn test_x86_safepoints_clear_actions() {
let mut sp = X86Safepoints::new();
sp.set_action(1, X86SafepointAction::PollOnly);
sp.set_action(2, X86SafepointAction::Suspend);
sp.clear_actions();
assert_eq!(sp.pending_action_count(), 0);
}
#[test]
fn test_x86_safepoints_reset_stats() {
let mut sp = X86Safepoints::new();
sp.thread_at_safepoint(1);
sp.thread_at_safepoint(2);
assert_eq!(sp.total_hits(), 2);
sp.reset_stats();
assert_eq!(sp.total_hits(), 0);
}
#[test]
fn test_x86_safepoints_cooperative_suspension() {
let mut sp = X86Safepoints::new();
assert!(!sp.cooperative_suspension_enabled);
sp.enable_cooperative_suspension();
assert!(sp.cooperative_suspension_enabled);
assert!(sp.gc_safepoint.enabled);
}
#[test]
fn test_x86_safepoints_thread_suspension() {
let mut sp = X86Safepoints::new();
assert!(!sp.thread_suspension_enabled);
sp.enable_thread_suspension();
assert!(sp.thread_suspension_enabled);
}
#[test]
fn test_end_to_end_frame_map_deopt_workflow() {
let mut fm = X86FrameMap::new(512);
let local_idx = fm.add_local("myObj", 8);
fm.slots[local_idx].mark_reference();
fm.slots[local_idx].set_location(X86FrameValueLocation::Register {
dwarf_reg: DWARF_REG_RAX,
});
fm.map_register_to_slot(DWARF_REG_RAX, local_idx);
let bundle = X86DeoptBundle::new(
DeoptReason::ExplicitDeopt {
reason: "test".to_string(),
},
1,
);
fm.set_deopt_bundle(bundle);
assert!(fm.validate());
let mut recon = X86FrameReconstructor::new(fm);
recon.set_register(DWARF_REG_RAX, 0xDEAD_BEEF);
assert!(recon.reconstruct());
let refs = recon.get_gc_references();
assert_eq!(refs, vec![0xDEAD_BEEF]);
}
#[test]
fn test_end_to_end_eh_debug_integration() {
let mut eh_map = X86EHStackMap::new("demo");
let lp = X86LandingPadMap::new(0x100, 42);
eh_map.add_landing_pad(0x100, lp);
let mut dbg_map = X86DebugInfoMap::new("demo");
let var_loc =
X86DebugVarLocation::new_register("error_obj", DWARF_REG_RAX, "Exception*", 42, 0x100);
dbg_map.record_debug_value(X86DebugValueRecord::new(var_loc));
assert!(eh_map.has_eh);
assert!(dbg_map.has_debug_info);
assert!(dbg_map.locations_at_offset(0x100).is_some());
assert!(eh_map.landing_pad_at(0x100).is_some());
}
#[test]
fn test_end_to_end_safepoints_with_frame_map() {
let mut safepoints = X86Safepoints::new();
safepoints.enable_cooperative_suspension();
safepoints.configure_guard_page(0x7FFF_FFFF_0000, 4096);
safepoints.begin_global_gc(1);
assert!(safepoints.global_gc_in_progress);
safepoints.thread_at_safepoint(1);
assert_eq!(safepoints.total_hits(), 1);
safepoints.end_global_gc();
assert!(!safepoints.global_gc_in_progress);
}
#[test]
fn test_end_to_end_all_four_systems() {
let mut fm = X86FrameMap::new(256);
fm.add_local("obj", 8);
fm.slots[0].mark_reference();
fm.slots[0].set_location(X86FrameValueLocation::Register {
dwarf_reg: DWARF_REG_RAX,
});
let mut eh = X86EHStackMap::new("test");
eh.add_landing_pad(0x100, X86LandingPadMap::new(0x100, 1));
let mut dbg = X86DebugInfoMap::new("test");
dbg.record_debug_value(X86DebugValueRecord::new(X86DebugVarLocation::new_register(
"obj",
DWARF_REG_RAX,
"Object*",
1,
0x100,
)));
let mut sp = X86Safepoints::new();
sp.set_action(
1,
X86SafepointAction::GCCollect {
generation: 0,
is_full_gc: true,
},
);
assert!(fm.validate());
assert!(eh.has_eh);
assert!(dbg.has_debug_info);
assert_eq!(sp.pending_action_count(), 1);
}
}