#![allow(non_upper_case_globals, dead_code)]
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use crate::codegen::MachineInstr;
pub const GC_EH_MAX_ROOTS: usize = 1024;
pub const GC_EH_MAX_SAFEPOINTS: usize = 2048;
pub const GC_EH_MAX_LANDINGPADS: usize = 128;
pub const GC_EH_MAX_CATCH_CLAUSES: usize = 64;
pub const GC_EH_STACK_MAP_VERSION: u8 = 3;
pub const GC_EH_STACK_MAP_SECTION: &str = ".llvm_stackmaps";
pub const GC_EH_STACK_MAP_RECORD_SIZE: u32 = 24;
pub const GC_EH_MAX_PATCHPOINT_SIZE: u32 = 256;
pub const GC_EH_LSDA_ENCODING: u8 = 0xFF;
pub const GC_EH_FDE_ENCODING: u8 = 0x00;
pub const GC_EH_PERSONALITY_ENCODING: u8 = 0x9B;
pub const GC_EH_CALLSITE_ENCODING: u8 = 0x01;
pub const GC_EH_CXA_EXCEPTION_HEADER_SIZE: usize = 128;
pub const GC_EH_UNWIND_CODE_SIZE: usize = 2;
pub const GC_EH_MAX_UNWIND_CODES: usize = 256;
const DW_RAX: u16 = 0;
const DW_RDX: u16 = 1;
const DW_RCX: u16 = 2;
const DW_RBX: u16 = 3;
const DW_RSI: u16 = 4;
const DW_RDI: u16 = 5;
const DW_RBP: u16 = 6;
const DW_RSP: u16 = 7;
const DW_R8: u16 = 8;
const DW_R9: u16 = 9;
const DW_R10: u16 = 10;
const DW_R11: u16 = 11;
const DW_R12: u16 = 12;
const DW_R13: u16 = 13;
const DW_R14: u16 = 14;
const DW_R15: u16 = 15;
const DW_RIP: u16 = 16;
const CALLEE_SAVED_GPRS: &[u16] = &[DW_RBX, DW_RBP, DW_R12, DW_R13, DW_R14, DW_R15];
const CALLER_SAVED_GPRS: &[u16] = &[
DW_RAX, DW_RCX, DW_RDX, DW_RSI, DW_RDI, DW_R8, DW_R9, DW_R10, DW_R11,
];
const GC_POINTER_GPRS: &[u16] = &[
DW_RAX, DW_RBX, DW_RCX, DW_RDX, DW_RSI, DW_RDI, DW_R8, DW_R9, DW_R10, DW_R11, DW_R12, DW_R13,
DW_R14, DW_R15,
];
pub const GC_EH_MAX_DERIVED_OFFSET: i64 = 4096;
pub const GC_EH_POLLING_PAGE: u64 = 0x7FFF_FFFF_FFFF_F000;
pub const GC_EH_POLLING_PAGE_SIZE: u64 = 4096;
pub const GC_EH_STACK_ALIGNMENT: u64 = 16;
pub const GC_EH_RED_ZONE_SIZE: i64 = 128;
pub const GC_EH_EXCEPTION_PTR_REGNO_COUNT: usize = 2;
#[derive(Debug, Clone)]
pub struct X86GCEHFull {
pub gc_strategy: X86GCEHStrategy,
pub enable_statepoints: bool,
pub enable_stack_maps: bool,
pub safepoints_at_backedges: bool,
pub safepoints_at_calls: bool,
pub cooperative_polling: bool,
pub personality: X86GCEHPersonality,
pub emit_dwarf_eh: bool,
pub emit_seh: bool,
pub outline_landing_pads: bool,
pub merge_landing_pads: bool,
pub prune_unreachable_handlers: bool,
pub target_triple: String,
pub data_layout: String,
pub pointer_size: u8,
pub is_64bit: bool,
pub is_windows: bool,
pub is_mingw: bool,
pub frame_pointer_reg: u16,
pub stack_pointer_reg: u16,
pub return_address_reg: u16,
pub stack_maps: Vec<X86GCEHStackMapRecord>,
pub safepoints: Vec<X86GCEHSafepoint>,
pub landing_pads: Vec<X86GCEHLandingPad>,
pub personality_refs: HashSet<String>,
next_statepoint_id: u64,
next_stack_map_id: u64,
next_safepoint_id: u64,
next_landingpad_id: u64,
next_catch_id: u64,
unwind_codes: Vec<X86GCEHUnwindCode>,
lsda_buffer: Vec<u8>,
eh_frame_cie: Vec<u8>,
eh_frame_fdes: Vec<u8>,
xdata_buffer: Vec<u8>,
pdata_buffer: Vec<u8>,
stack_map_buffer: Vec<u8>,
active_roots: BTreeMap<u16, X86GCEHRoot>,
stack_roots: Vec<X86GCEHStackSlot>,
call_site_table: Vec<X86GCEHCallSiteEntry>,
type_info_table: Vec<X86GCEHTypeInfoEntry>,
action_records: Vec<X86GCEHActionRecord>,
pub uses_gc: bool,
pub uses_eh: bool,
pub has_personality: bool,
pub current_function: String,
}
impl Default for X86GCEHFull {
fn default() -> Self {
Self {
gc_strategy: X86GCEHStrategy::Statepoint,
enable_statepoints: true,
enable_stack_maps: true,
safepoints_at_backedges: true,
safepoints_at_calls: true,
cooperative_polling: false,
personality: X86GCEHPersonality::GxxV0,
emit_dwarf_eh: true,
emit_seh: false,
outline_landing_pads: true,
merge_landing_pads: true,
prune_unreachable_handlers: true,
target_triple: "x86_64-unknown-linux-gnu".to_string(),
data_layout: "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-\
f80:128-n8:16:32:64-S128"
.to_string(),
pointer_size: 8,
is_64bit: true,
is_windows: false,
is_mingw: false,
frame_pointer_reg: DW_RBP,
stack_pointer_reg: DW_RSP,
return_address_reg: DW_RIP,
stack_maps: Vec::new(),
safepoints: Vec::new(),
landing_pads: Vec::new(),
personality_refs: HashSet::new(),
next_statepoint_id: 0,
next_stack_map_id: 0,
next_safepoint_id: 0,
next_landingpad_id: 0,
next_catch_id: 0,
unwind_codes: Vec::new(),
lsda_buffer: Vec::new(),
eh_frame_cie: Vec::new(),
eh_frame_fdes: Vec::new(),
xdata_buffer: Vec::new(),
pdata_buffer: Vec::new(),
stack_map_buffer: Vec::new(),
active_roots: BTreeMap::new(),
stack_roots: Vec::new(),
call_site_table: Vec::new(),
type_info_table: Vec::new(),
action_records: Vec::new(),
uses_gc: false,
uses_eh: false,
has_personality: false,
current_function: String::new(),
}
}
}
impl X86GCEHFull {
pub fn next_statepoint_id(&mut self) -> u64 {
let id = self.next_statepoint_id;
self.next_statepoint_id += 1;
id
}
pub fn next_stack_map_id(&mut self) -> u64 {
let id = self.next_stack_map_id;
self.next_stack_map_id += 1;
id
}
pub fn next_safepoint_id(&mut self) -> u64 {
let id = self.next_safepoint_id;
self.next_safepoint_id += 1;
id
}
pub fn next_landingpad_id(&mut self) -> u64 {
let id = self.next_landingpad_id;
self.next_landingpad_id += 1;
id
}
pub fn next_catch_id(&mut self) -> u64 {
let id = self.next_catch_id;
self.next_catch_id += 1;
id
}
pub fn begin_function(&mut self, name: &str, uses_gc: bool, uses_eh: bool) {
self.current_function = name.to_string();
self.uses_gc = uses_gc;
self.uses_eh = uses_eh;
self.has_personality = uses_eh;
self.active_roots.clear();
self.stack_roots.clear();
self.call_site_table.clear();
self.type_info_table.clear();
self.action_records.clear();
self.unwind_codes.clear();
self.lsda_buffer.clear();
self.eh_frame_fdes.clear();
self.xdata_buffer.clear();
self.pdata_buffer.clear();
}
pub fn end_function(&mut self) {
if self.uses_gc && self.enable_stack_maps {
self.emit_function_stack_map();
}
if self.uses_eh {
if self.emit_dwarf_eh {
self.emit_dwarf_eh_frame();
}
if self.emit_seh {
self.emit_seh_unwind_info();
}
}
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86GCEHStrategy {
Statepoint,
CoreCLR,
OCaml,
Erlang,
ShadowStack,
Custom,
None,
}
impl fmt::Display for X86GCEHStrategy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Statepoint => write!(f, "statepoint-example"),
Self::CoreCLR => write!(f, "coreclr"),
Self::OCaml => write!(f, "ocaml"),
Self::Erlang => write!(f, "erlang"),
Self::ShadowStack => write!(f, "shadow-stack"),
Self::Custom => write!(f, "custom"),
Self::None => write!(f, "none"),
}
}
}
impl X86GCEHStrategy {
pub fn uses_statepoints(&self) -> bool {
matches!(self, Self::Statepoint)
}
pub fn uses_shadow_stack(&self) -> bool {
matches!(self, Self::ShadowStack)
}
pub fn needs_stack_maps(&self) -> bool {
matches!(self, Self::Statepoint | Self::CoreCLR | Self::OCaml)
}
pub fn requires_write_barriers(&self) -> bool {
matches!(
self,
Self::Statepoint | Self::CoreCLR | Self::Erlang | Self::OCaml
)
}
pub fn llvm_attr_name(&self) -> &str {
match self {
Self::Statepoint => "statepoint-example",
Self::CoreCLR => "coreclr",
Self::OCaml => "ocaml",
Self::Erlang => "erlang",
Self::ShadowStack => "shadow-stack",
Self::Custom => "custom",
Self::None => "",
}
}
pub fn suspension(&self) -> X86GCEHSuspension {
match self {
Self::Statepoint | Self::CoreCLR | Self::OCaml => X86GCEHSuspension::Polling,
Self::Erlang => X86GCEHSuspension::ReductionBased,
Self::ShadowStack => X86GCEHSuspension::SignalBased,
Self::Custom | Self::None => X86GCEHSuspension::None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86GCEHSuspension {
None,
Polling,
SignalBased,
PageFault,
ReductionBased,
Hybrid,
}
impl Default for X86GCEHSuspension {
fn default() -> Self {
Self::Polling
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct X86GCEHRoot {
pub kind: X86GCEHRootKind,
pub location: X86GCEHRootLocation,
pub is_derived: bool,
pub derived_offset: i64,
pub is_live: bool,
pub description: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86GCEHRootKind {
ObjectPointer,
DerivedPointer,
StaticRoot,
ThreadLocal,
StackAllocated,
Alloca,
PhiNode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86GCEHRootLocation {
Register(u16),
StackOffset(i32),
StackSPOffset(i32),
FixedAddress(u64),
Unknown,
}
impl fmt::Display for X86GCEHRootLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Register(r) => write!(f, "reg(D{})", r),
Self::StackOffset(off) => write!(f, "bp{:+}", off),
Self::StackSPOffset(off) => write!(f, "sp{:+}", off),
Self::FixedAddress(addr) => write!(f, "addr(0x{:x})", addr),
Self::Unknown => write!(f, "unknown"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X86GCEHStackSlot {
pub offset: i32,
pub size: u8,
pub is_derived: bool,
pub is_live: bool,
pub name: String,
}
#[derive(Debug, Clone)]
pub struct X86GCEHStatepoint {
pub id: u64,
pub pc_offset: u64,
pub stack_map_id: u64,
pub call_target: Option<String>,
pub num_call_args: u32,
pub flags: u32,
pub num_deopt_bytes: u32,
pub deopt_state: Vec<u8>,
pub gc_transition: X86GCEHGCTransition,
pub num_roots: u32,
pub roots: Vec<X86GCEHRoot>,
pub stack_slots: Vec<X86GCEHStackSlot>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86GCEHGCTransition {
None,
Safepoint,
Relocation,
Deopt,
}
#[derive(Debug, Clone)]
pub struct X86GCEHRelocate {
pub statepoint_id: u64,
pub base_offset: u32,
pub derived_offset: u32,
pub original_value: u64,
pub relocated_value: u64,
pub target_reg: Option<u16>,
pub target_stack_offset: Option<i32>,
}
#[derive(Debug, Clone)]
pub struct X86GCEHSafepoint {
pub id: u64,
pub kind: X86GCEHSafepointKind,
pub pc_offset: u64,
pub may_gc: bool,
pub statepoint_id: Option<u64>,
pub stack_map_id: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86GCEHSafepointKind {
PreCall,
PostCall,
LoopBackedge,
FunctionEntry,
FunctionReturn,
Poll,
MachineSafepoint,
}
impl fmt::Display for X86GCEHSafepointKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PreCall => write!(f, "pre-call"),
Self::PostCall => write!(f, "post-call"),
Self::LoopBackedge => write!(f, "loop-backedge"),
Self::FunctionEntry => write!(f, "entry"),
Self::FunctionReturn => write!(f, "return"),
Self::Poll => write!(f, "poll"),
Self::MachineSafepoint => write!(f, "machine"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86GCEHStackMapRecord {
pub id: u64,
pub function_address: u64,
pub instruction_offset: u64,
pub num_locations: u16,
pub locations: Vec<X86GCEHStackMapLocation>,
pub num_live_outs: u16,
pub live_outs: Vec<X86GCEHLiveOut>,
pub padding: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct X86GCEHStackMapLocation {
pub dwarf_reg: u16,
pub reserved: u16,
pub offset: i32,
pub kind: X86GCEHStackMapLocationKind,
pub size: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum X86GCEHStackMapLocationKind {
Register = 1,
Direct = 2,
Indirect = 3,
Constant = 4,
ConstantIndex = 5,
}
#[derive(Debug, Clone, Copy)]
pub struct X86GCEHLiveOut {
pub dwarf_reg: u16,
pub reserved: u8,
pub size: u8,
}
#[derive(Debug, Clone)]
pub struct X86GCEHPatchpoint {
pub id: u64,
pub flags: u32,
pub num_bytes: u32,
pub num_args: u32,
pub callee: u64,
pub num_patch_args: u16,
pub cc: u32,
pub stack_map_id: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86GCEHPersonality {
GxxV0,
GccV0,
CxxFrameHandler3,
CSpecificHandler,
ObjCV0,
GxxSeh0,
None,
}
impl X86GCEHPersonality {
pub fn symbol_name(&self) -> &str {
match self {
Self::GxxV0 => "__gxx_personality_v0",
Self::GccV0 => "__gcc_personality_v0",
Self::CxxFrameHandler3 => "__CxxFrameHandler3",
Self::CSpecificHandler => "__C_specific_handler",
Self::ObjCV0 => "__objc_personality_v0",
Self::GxxSeh0 => "__gxx_personality_seh0",
Self::None => "",
}
}
pub fn is_itanium(&self) -> bool {
matches!(self, Self::GxxV0 | Self::GccV0 | Self::ObjCV0)
}
pub fn is_seh(&self) -> bool {
matches!(
self,
Self::CxxFrameHandler3 | Self::CSpecificHandler | Self::GxxSeh0
)
}
}
#[derive(Debug, Clone)]
pub struct X86GCEHLandingPad {
pub id: u64,
pub kind: X86GCEHLandingPadKind,
pub exc_ptr_reg: u16,
pub filter_ptr_reg: u16,
pub clauses: Vec<X86GCEHClause>,
pub is_cleanup: bool,
pub is_unreachable: bool,
pub is_outlined: bool,
pub call_site_entries: Vec<u32>,
pub merged_from: Vec<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86GCEHLandingPadKind {
LandingPad,
CleanupPad,
CatchPad,
CatchSwitch,
CatchRet,
CleanupRet,
Resume,
}
#[derive(Debug, Clone)]
pub struct X86GCEHClause {
pub kind: X86GCEHClauseKind,
pub type_info: Option<X86GCEHTypeInfoEntry>,
pub filter_fn: Option<String>,
pub handler: Option<String>,
pub is_catch_all: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86GCEHClauseKind {
Catch,
CatchAll,
Filter,
Cleanup,
}
#[derive(Debug, Clone)]
pub struct X86GCEHCallSiteEntry {
pub cs_start: u32,
pub cs_length: u32,
pub cs_lp: u32,
pub cs_action: u32,
}
#[derive(Debug, Clone)]
pub struct X86GCEHTypeInfoEntry {
pub encoding: u8,
pub type_info_ptr: u64,
pub type_name: String,
}
#[derive(Debug, Clone)]
pub struct X86GCEHActionRecord {
pub type_filter: i32,
pub next_action_offset: i32,
}
#[derive(Debug, Clone)]
pub struct X86GCEHWinUnwindInfo {
pub version: u8,
pub flags: u8,
pub prologue_size: u8,
pub count_of_codes: u8,
pub frame_register: u8,
pub frame_offset: u8,
pub unwind_codes: Vec<X86GCEHUnwindCode>,
pub handler_rva: Option<u32>,
pub handler_data_rva: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct X86GCEHUnwindCode {
pub code_offset: u8,
pub unwind_op: X86GCEHUnwindOp,
pub op_info: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum X86GCEHUnwindOp {
PushNonVol = 0,
AllocLarge = 1,
AllocSmall = 2,
SetFPReg = 3,
SaveNonVol = 4,
SaveNonVolFar = 5,
SaveXmm128 = 8,
SaveXmm128Far = 9,
PushMachFrame = 10,
Epilogue = 6,
SpareCode = 7,
SetMxcsr = 0x1E,
RvaHandlers = 0x60,
}
impl X86GCEHUnwindOp {
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(Self::PushNonVol),
1 => Some(Self::AllocLarge),
2 => Some(Self::AllocSmall),
3 => Some(Self::SetFPReg),
4 => Some(Self::SaveNonVol),
5 => Some(Self::SaveNonVolFar),
6 => Some(Self::Epilogue),
7 => Some(Self::SpareCode),
8 => Some(Self::SaveXmm128),
9 => Some(Self::SaveXmm128Far),
10 => Some(Self::PushMachFrame),
0x1E => Some(Self::SetMxcsr),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct X86GCEHRuntimeFunction {
pub begin_address: u32,
pub end_address: u32,
pub unwind_info: u32,
}
#[derive(Debug, Clone)]
pub struct X86GCEHFilterFunction {
pub name: String,
pub address: u64,
pub filter_action: X86GCEHFilterAction,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum X86GCEHFilterAction {
ExecuteHandler = 1,
ContinueSearch = 0,
ContinueExecution = -1,
ExceptionUnhandled = 0x80010007u32 as i32,
}
#[derive(Debug, Clone)]
pub struct X86GCEHDwarfCIE {
pub length: u32,
pub cie_id: u32,
pub version: u8,
pub augmentation: String,
pub code_alignment_factor: u8,
pub data_alignment_factor: i8,
pub return_address_register: u8,
pub initial_instructions: Vec<u8>,
pub personality_addr: Option<u64>,
pub personality_encoding: Option<u8>,
pub lsda_encoding: Option<u8>,
pub fde_encoding: Option<u8>,
}
#[derive(Debug, Clone)]
pub struct X86GCEHDwarfFDE {
pub length: u32,
pub cie_pointer: u32,
pub initial_location: u64,
pub address_range: u64,
pub instructions: Vec<u8>,
pub lsda_pointer: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct X86GCEHLsda {
pub landing_pad_base: u32,
pub type_table: Vec<u64>,
pub type_table_encoding: u8,
pub call_sites: Vec<X86GCEHCallSiteEntry>,
pub actions: Vec<X86GCEHActionRecord>,
pub gcc_except_table: bool,
pub tt_base: u32,
}
#[derive(Debug, Clone, Default)]
pub struct X86GCEHAugmentation {
pub has_aug_length: bool,
pub has_lsda: bool,
pub has_personality: bool,
pub has_fde_encoding: bool,
pub is_signal_frame: bool,
pub is_bfd_cie: bool,
pub is_gnu: bool,
}
impl X86GCEHAugmentation {
pub fn parse(aug: &str) -> Self {
let mut result = Self::default();
for ch in aug.chars() {
match ch {
'z' => result.has_aug_length = true,
'L' => result.has_lsda = true,
'P' => result.has_personality = true,
'R' => result.has_fde_encoding = true,
'S' => result.is_signal_frame = true,
'B' => result.is_bfd_cie = true,
'G' => result.is_gnu = true,
_ => {}
}
}
result
}
pub fn to_string(&self) -> String {
let mut s = String::new();
if self.has_aug_length {
s.push('z');
}
if self.has_lsda {
s.push('L');
}
if self.has_personality {
s.push('P');
}
if self.has_fde_encoding {
s.push('R');
}
if self.is_signal_frame {
s.push('S');
}
if self.is_bfd_cie {
s.push('B');
}
if self.is_gnu {
s.push('G');
}
s
}
}
#[derive(Debug, Clone, Copy)]
pub struct X86GCEHExceptionPointerRegs {
pub exception_pointer_reg: u16,
pub selector_reg: u16,
}
impl X86GCEHExceptionPointerRegs {
pub fn for_x86_64() -> Self {
Self {
exception_pointer_reg: DW_RAX,
selector_reg: DW_RDX,
}
}
pub fn for_x86_32() -> Self {
Self {
exception_pointer_reg: 0, selector_reg: 1, }
}
pub fn eh_return_data_regno(index: u32) -> Option<u16> {
match index {
0 => Some(DW_RAX),
1 => Some(DW_RDX),
_ => None,
}
}
}
impl X86GCEHFull {
pub fn set_gc_strategy(&mut self, strategy: X86GCEHStrategy) {
self.gc_strategy = strategy;
self.enable_statepoints = strategy.uses_statepoints();
self.enable_stack_maps = strategy.needs_stack_maps();
}
pub fn set_gc_strategy_from_attr(&mut self, attr: &str) {
let strategy = match attr {
"statepoint-example" => X86GCEHStrategy::Statepoint,
"coreclr" => X86GCEHStrategy::CoreCLR,
"ocaml" => X86GCEHStrategy::OCaml,
"erlang" => X86GCEHStrategy::Erlang,
"shadow-stack" => X86GCEHStrategy::ShadowStack,
_ => X86GCEHStrategy::None,
};
self.set_gc_strategy(strategy);
}
pub fn needs_gc_support(&self) -> bool {
self.uses_gc && self.gc_strategy != X86GCEHStrategy::None
}
pub fn needs_eh_support(&self) -> bool {
self.uses_eh && self.personality != X86GCEHPersonality::None
}
}
impl X86GCEHFull {
pub fn lower_gc_statepoint(
&mut self,
call_target: Option<&str>,
num_call_args: u32,
_call_args: &[X86GCEHRoot],
transition: X86GCEHGCTransition,
deopt_state: &[u8],
flags: u32,
) -> X86GCEHStatepoint {
let id = self.next_statepoint_id();
let stack_map_id = self.next_stack_map_id();
let roots = self.enumerate_live_roots();
let pc_offset = self.compute_current_pc_offset();
let statepoint = X86GCEHStatepoint {
id,
pc_offset,
stack_map_id,
call_target: call_target.map(String::from),
num_call_args,
flags,
num_deopt_bytes: deopt_state.len() as u32,
deopt_state: deopt_state.to_vec(),
gc_transition: transition,
num_roots: roots.len() as u32,
roots: roots.clone(),
stack_slots: self.stack_roots.clone(),
};
if self.enable_stack_maps {
let record =
self.build_stack_map_record(stack_map_id, pc_offset, &roots, &self.stack_roots);
self.stack_maps.push(record);
}
if transition == X86GCEHGCTransition::Relocation {
for root in &roots {
let _loc = &root.location;
}
}
let safepoint_id = self.next_safepoint_id();
self.safepoints.push(X86GCEHSafepoint {
id: safepoint_id,
kind: X86GCEHSafepointKind::PreCall,
pc_offset,
may_gc: transition != X86GCEHGCTransition::None,
statepoint_id: Some(id),
stack_map_id: Some(stack_map_id),
});
statepoint
}
fn compute_current_pc_offset(&self) -> u64 {
self.stack_maps.len() as u64 * 4
}
}
impl X86GCEHFull {
pub fn lower_gc_result(&self, statepoint_id: u64, result_reg: u16) -> X86GCEHRoot {
X86GCEHRoot {
kind: X86GCEHRootKind::ObjectPointer,
location: X86GCEHRootLocation::Register(result_reg),
is_derived: false,
derived_offset: 0,
is_live: true,
description: format!(
"gc.result of statepoint {} in reg D{}",
statepoint_id, result_reg
),
}
}
pub fn lower_gc_relocate(
&self,
statepoint_id: u64,
base_offset: u32,
derived_offset: u32,
original_value: u64,
relocated_value: u64,
) -> X86GCEHRelocate {
X86GCEHRelocate {
statepoint_id,
base_offset,
derived_offset,
original_value,
relocated_value,
target_reg: None,
target_stack_offset: None,
}
}
pub fn emit_gc_relocates(
&mut self,
statepoint_id: u64,
relocated_roots: &[(u16, u64)],
) -> Vec<X86GCEHRelocate> {
let mut result = Vec::new();
for (reg, new_value) in relocated_roots {
let relocate = self.lower_gc_relocate(
statepoint_id,
*reg as u32, *reg as u32, *new_value, *new_value, );
result.push(relocate);
}
result
}
}
impl X86GCEHFull {
pub fn enumerate_live_roots(&self) -> Vec<X86GCEHRoot> {
let mut roots: Vec<X86GCEHRoot> = Vec::new();
for (_reg, root) in &self.active_roots {
if root.is_live {
roots.push(root.clone());
}
}
for slot in &self.stack_roots {
if slot.is_live {
roots.push(X86GCEHRoot {
kind: if slot.is_derived {
X86GCEHRootKind::DerivedPointer
} else {
X86GCEHRootKind::StackAllocated
},
location: X86GCEHRootLocation::StackOffset(slot.offset),
is_derived: slot.is_derived,
derived_offset: 0,
is_live: true,
description: format!("stack slot {} at sp{:+}", slot.name, slot.offset),
});
}
}
roots.sort_by_key(|r| r.description.clone());
roots
}
pub fn register_root(
&mut self,
reg: u16,
kind: X86GCEHRootKind,
is_derived: bool,
derived_offset: i64,
desc: &str,
) {
self.active_roots.insert(
reg,
X86GCEHRoot {
kind,
location: X86GCEHRootLocation::Register(reg),
is_derived,
derived_offset,
is_live: true,
description: desc.to_string(),
},
);
}
pub fn register_stack_root(&mut self, offset: i32, size: u8, is_derived: bool, name: &str) {
self.stack_roots.push(X86GCEHStackSlot {
offset,
size,
is_derived,
is_live: true,
name: name.to_string(),
});
}
pub fn kill_root(&mut self, reg: u16) {
if let Some(root) = self.active_roots.get_mut(®) {
root.is_live = false;
}
}
pub fn kill_stack_root(&mut self, offset: i32) {
for slot in &mut self.stack_roots {
if slot.offset == offset {
slot.is_live = false;
}
}
}
pub fn enumerate_callee_saved_roots(&self) -> Vec<X86GCEHRoot> {
let mut result = Vec::new();
for reg in CALLEE_SAVED_GPRS {
if let Some(root) = self.active_roots.get(reg) {
if root.is_live {
result.push(root.clone());
}
}
}
result
}
pub fn enumerate_caller_saved_roots(&self) -> Vec<X86GCEHRoot> {
let mut result = Vec::new();
for reg in CALLER_SAVED_GPRS {
if let Some(root) = self.active_roots.get(reg) {
if root.is_live {
result.push(root.clone());
}
}
}
result
}
}
impl X86GCEHFull {
pub fn build_stack_map_record(
&self,
id: u64,
instruction_offset: u64,
register_roots: &[X86GCEHRoot],
stack_roots: &[X86GCEHStackSlot],
) -> X86GCEHStackMapRecord {
let mut locations = Vec::new();
let mut live_outs = Vec::new();
for root in register_roots {
match &root.location {
X86GCEHRootLocation::Register(reg) => {
locations.push(X86GCEHStackMapLocation {
dwarf_reg: *reg,
reserved: 0,
offset: root.derived_offset as i32,
kind: X86GCEHStackMapLocationKind::Register,
size: self.pointer_size,
});
live_outs.push(X86GCEHLiveOut {
dwarf_reg: *reg,
reserved: 0,
size: self.pointer_size,
});
}
X86GCEHRootLocation::StackOffset(off) => {
locations.push(X86GCEHStackMapLocation {
dwarf_reg: self.frame_pointer_reg,
reserved: 0,
offset: *off,
kind: X86GCEHStackMapLocationKind::Indirect,
size: self.pointer_size,
});
}
X86GCEHRootLocation::FixedAddress(addr) => {
locations.push(X86GCEHStackMapLocation {
dwarf_reg: 0,
reserved: 0,
offset: *addr as i32,
kind: X86GCEHStackMapLocationKind::Constant,
size: self.pointer_size,
});
}
_ => {}
}
}
for slot in stack_roots {
locations.push(X86GCEHStackMapLocation {
dwarf_reg: self.frame_pointer_reg,
reserved: 0,
offset: slot.offset,
kind: X86GCEHStackMapLocationKind::Indirect,
size: slot.size,
});
}
X86GCEHStackMapRecord {
id,
function_address: 0, instruction_offset,
num_locations: locations.len() as u16,
locations,
num_live_outs: live_outs.len() as u16,
live_outs,
padding: 0,
}
}
pub fn emit_function_stack_map(&mut self) {
let function_addr: u64 = 0;
let stack_size: u64 = self.compute_current_stack_size();
let record_count: u64 = self
.stack_maps
.iter()
.filter(|r| r.function_address == 0)
.count() as u64;
Self::append_u64_to_buffer(&mut self.stack_map_buffer, function_addr);
Self::append_u64_to_buffer(&mut self.stack_map_buffer, stack_size);
Self::append_u64_to_buffer(&mut self.stack_map_buffer, record_count);
let mut pending = Vec::new();
std::mem::swap(&mut pending, &mut self.stack_maps);
for record in &pending {
if record.id == 0 {
continue;
}
self.emit_single_stack_map_record(record);
}
self.stack_maps = pending;
}
fn emit_single_stack_map_record(&mut self, record: &X86GCEHStackMapRecord) {
Self::append_u64_to_buffer(&mut self.stack_map_buffer, record.id);
Self::append_u32_to_buffer(&mut self.stack_map_buffer, record.instruction_offset as u32);
Self::append_u16_to_buffer(&mut self.stack_map_buffer, 0);
let num_locations = record.locations.len() as u16;
Self::append_u16_to_buffer(&mut self.stack_map_buffer, num_locations);
for loc in &record.locations {
Self::append_u8_to_buffer(&mut self.stack_map_buffer, loc.kind as u8);
Self::append_u8_to_buffer(&mut self.stack_map_buffer, 0);
Self::append_u16_to_buffer(&mut self.stack_map_buffer, loc.size as u16);
Self::append_u16_to_buffer(&mut self.stack_map_buffer, loc.dwarf_reg);
Self::append_u16_to_buffer(&mut self.stack_map_buffer, 0);
Self::append_i32_to_buffer(&mut self.stack_map_buffer, loc.offset);
}
if num_locations % 2 == 1 {
Self::append_u16_to_buffer(&mut self.stack_map_buffer, 0);
}
let num_live_outs = record.live_outs.len() as u16;
Self::append_u16_to_buffer(&mut self.stack_map_buffer, num_live_outs);
for lo in &record.live_outs {
Self::append_u16_to_buffer(&mut self.stack_map_buffer, lo.dwarf_reg);
Self::append_u8_to_buffer(&mut self.stack_map_buffer, lo.reserved);
Self::append_u8_to_buffer(&mut self.stack_map_buffer, lo.size);
}
let current_len = (self.stack_map_buffer.len() as u32) % 8;
if current_len != 0 {
for _ in 0..(8 - current_len) {
Self::append_u8_to_buffer(&mut self.stack_map_buffer, 0);
}
}
}
fn compute_current_stack_size(&self) -> u64 {
let mut size = 0u64;
for slot in &self.stack_roots {
let end = (slot.offset as i64 + slot.size as i64) as u64;
if end > size {
size = end;
}
}
(size + 15) & !15
}
pub fn finalize_stack_map_section(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.push(GC_EH_STACK_MAP_VERSION);
buf.push(0);
buf.extend_from_slice(&[0u8, 0u8]);
buf.extend_from_slice(&self.stack_map_buffer);
buf
}
}
impl X86GCEHFull {
fn append_u8_to_buffer(buf: &mut Vec<u8>, val: u8) {
buf.push(val);
}
fn append_u16_to_buffer(buf: &mut Vec<u8>, val: u16) {
buf.extend_from_slice(&val.to_le_bytes());
}
fn append_u32_to_buffer(buf: &mut Vec<u8>, val: u32) {
buf.extend_from_slice(&val.to_le_bytes());
}
fn append_i32_to_buffer(buf: &mut Vec<u8>, val: i32) {
buf.extend_from_slice(&val.to_le_bytes());
}
fn append_u64_to_buffer(buf: &mut Vec<u8>, val: u64) {
buf.extend_from_slice(&val.to_le_bytes());
}
}
impl X86GCEHFull {
pub fn insert_loop_backedge_safepoint(&mut self, pc_offset: u64) -> Option<X86GCEHSafepoint> {
if !self.safepoints_at_backedges {
return None;
}
let id = self.next_safepoint_id();
let sp = X86GCEHSafepoint {
id,
kind: X86GCEHSafepointKind::LoopBackedge,
pc_offset,
may_gc: true,
statepoint_id: None,
stack_map_id: None,
};
self.safepoints.push(sp.clone());
Some(sp)
}
pub fn insert_pre_call_safepoint(&mut self, pc_offset: u64) -> Option<X86GCEHSafepoint> {
if !self.safepoints_at_calls {
return None;
}
let id = self.next_safepoint_id();
let sp = X86GCEHSafepoint {
id,
kind: X86GCEHSafepointKind::PreCall,
pc_offset,
may_gc: true,
statepoint_id: None,
stack_map_id: None,
};
self.safepoints.push(sp.clone());
Some(sp)
}
pub fn insert_polling_safepoint(&mut self, pc_offset: u64) -> Option<X86GCEHSafepoint> {
if !self.cooperative_polling {
return None;
}
let id = self.next_safepoint_id();
let sp = X86GCEHSafepoint {
id,
kind: X86GCEHSafepointKind::Poll,
pc_offset,
may_gc: true,
statepoint_id: None,
stack_map_id: None,
};
self.safepoints.push(sp.clone());
Some(sp)
}
pub fn insert_entry_safepoint(&mut self, pc_offset: u64) {
let id = self.next_safepoint_id();
self.safepoints.push(X86GCEHSafepoint {
id,
kind: X86GCEHSafepointKind::FunctionEntry,
pc_offset,
may_gc: false,
statepoint_id: None,
stack_map_id: None,
});
}
pub fn compute_safepoint_placement(&mut self) -> Vec<u64> {
let mut offsets = Vec::new();
match self.gc_strategy {
X86GCEHStrategy::Statepoint => {
if self.safepoints_at_calls {
}
if self.safepoints_at_backedges {
}
}
X86GCEHStrategy::CoreCLR => {
}
X86GCEHStrategy::Erlang => {
}
X86GCEHStrategy::OCaml => {
}
_ => {}
}
for sp in &self.safepoints {
offsets.push(sp.pc_offset);
}
offsets.sort();
offsets.dedup();
offsets
}
}
impl X86GCEHFull {
pub fn rewrite_statepoints_for_gc(
&mut self,
call_sites: &[(u64, Option<String>, u32)],
live_roots: &[X86GCEHRoot],
) -> Vec<X86GCEHStatepoint> {
let mut statepoints = Vec::new();
for (_pc_offset, call_target, num_args) in call_sites {
let statepoint = self.lower_gc_statepoint(
call_target.as_deref(),
*num_args,
live_roots,
X86GCEHGCTransition::Relocation,
&[],
0,
);
statepoints.push(statepoint);
}
for sp in &statepoints {
for root in live_roots {
if let X86GCEHRootLocation::Register(reg) = root.location {
let _ = self.lower_gc_relocate(sp.id, reg as u32, reg as u32, 0, 0);
}
}
}
statepoints
}
pub fn call_needs_statepoint(&self, call_target: &str, is_leaf: bool) -> bool {
if !self.uses_gc {
return false;
}
let gc_leaves: &[&str] = &[
"memcpy",
"memmove",
"memset",
"bzero",
"llvm.memcpy",
"llvm.memmove",
"llvm.memset",
"llvm.sqrt",
"llvm.sin",
"llvm.cos",
"llvm.x86.sse",
"llvm.x86.sse2",
];
if is_leaf || gc_leaves.contains(&call_target) {
return false;
}
true
}
pub fn replace_with_relocated(
&mut self,
original_reg: u16,
statepoint_id: u64,
) -> X86GCEHRelocate {
self.lower_gc_relocate(
statepoint_id,
original_reg as u32,
original_reg as u32,
0, 0, )
}
}
impl X86GCEHFull {
pub fn lower_landingpad(
&mut self,
clauses: Vec<X86GCEHClause>,
is_cleanup: bool,
) -> X86GCEHLandingPad {
let id = self.next_landingpad_id();
let exc_regs = X86GCEHExceptionPointerRegs::for_x86_64();
self.landing_pads.push(X86GCEHLandingPad {
id,
kind: X86GCEHLandingPadKind::LandingPad,
exc_ptr_reg: exc_regs.exception_pointer_reg,
filter_ptr_reg: exc_regs.selector_reg,
clauses: clauses.clone(),
is_cleanup,
is_unreachable: false,
is_outlined: false,
call_site_entries: Vec::new(),
merged_from: Vec::new(),
});
for clause in &clauses {
let call_site = X86GCEHCallSiteEntry {
cs_start: 0,
cs_length: 0,
cs_lp: id as u32,
cs_action: self.compute_action_index(clause),
};
self.call_site_table.push(call_site);
}
self.landing_pads.last().cloned().unwrap()
}
fn compute_action_index(&mut self, clause: &X86GCEHClause) -> u32 {
match clause.kind {
X86GCEHClauseKind::Catch | X86GCEHClauseKind::CatchAll => {
let type_index = if let Some(ref _ti) = clause.type_info {
self.type_info_table.len() as i32
} else {
-1 };
let action = X86GCEHActionRecord {
type_filter: if clause.is_catch_all {
0
} else {
type_index + 1
},
next_action_offset: 0,
};
self.action_records.push(action);
self.action_records.len() as u32
}
X86GCEHClauseKind::Filter => {
let action = X86GCEHActionRecord {
type_filter: 0,
next_action_offset: 0,
};
self.action_records.push(action);
self.action_records.len() as u32
}
X86GCEHClauseKind::Cleanup => {
0
}
}
}
pub fn lower_resume(&mut self, _landing_pad_id: u64) -> X86GCEHLandingPad {
let id = self.next_landingpad_id();
let pad = X86GCEHLandingPad {
id,
kind: X86GCEHLandingPadKind::Resume,
exc_ptr_reg: DW_RAX,
filter_ptr_reg: DW_RDX,
clauses: Vec::new(),
is_cleanup: false,
is_unreachable: false,
is_outlined: false,
call_site_entries: Vec::new(),
merged_from: Vec::new(),
};
self.landing_pads.push(pad.clone());
pad
}
pub fn lower_cleanupret(
&mut self,
_from_pad_id: u64,
_unwind_target: Option<u64>,
) -> X86GCEHLandingPad {
let id = self.next_landingpad_id();
let pad = X86GCEHLandingPad {
id,
kind: X86GCEHLandingPadKind::CleanupRet,
exc_ptr_reg: 0,
filter_ptr_reg: 0,
clauses: Vec::new(),
is_cleanup: true,
is_unreachable: false,
is_outlined: false,
call_site_entries: Vec::new(),
merged_from: Vec::new(),
};
self.landing_pads.push(pad.clone());
pad
}
pub fn lower_catchret(&mut self, _from_pad_id: u64, _target_label: &str) -> X86GCEHLandingPad {
let id = self.next_landingpad_id();
let pad = X86GCEHLandingPad {
id,
kind: X86GCEHLandingPadKind::CatchRet,
exc_ptr_reg: 0,
filter_ptr_reg: 0,
clauses: Vec::new(),
is_cleanup: false,
is_unreachable: false,
is_outlined: false,
call_site_entries: Vec::new(),
merged_from: Vec::new(),
};
self.landing_pads.push(pad.clone());
pad
}
pub fn lower_catchswitch(
&mut self,
_targets: &[u64],
_unwind_target: Option<u64>,
) -> X86GCEHLandingPad {
let id = self.next_landingpad_id();
let pad = X86GCEHLandingPad {
id,
kind: X86GCEHLandingPadKind::CatchSwitch,
exc_ptr_reg: 0,
filter_ptr_reg: 0,
clauses: Vec::new(),
is_cleanup: false,
is_unreachable: false,
is_outlined: false,
call_site_entries: Vec::new(),
merged_from: Vec::new(),
};
self.landing_pads.push(pad.clone());
pad
}
pub fn lower_catchpad(
&mut self,
_parent_switch_id: u64,
type_info: Option<X86GCEHTypeInfoEntry>,
) -> X86GCEHLandingPad {
let id = self.next_landingpad_id();
let pad = X86GCEHLandingPad {
id,
kind: X86GCEHLandingPadKind::CatchPad,
exc_ptr_reg: 0,
filter_ptr_reg: 0,
clauses: vec![X86GCEHClause {
kind: X86GCEHClauseKind::Catch,
type_info,
filter_fn: None,
handler: None,
is_catch_all: false,
}],
is_cleanup: false,
is_unreachable: false,
is_outlined: false,
call_site_entries: Vec::new(),
merged_from: Vec::new(),
};
self.landing_pads.push(pad.clone());
pad
}
pub fn lower_cleanuppad(&mut self) -> X86GCEHLandingPad {
let id = self.next_landingpad_id();
let pad = X86GCEHLandingPad {
id,
kind: X86GCEHLandingPadKind::CleanupPad,
exc_ptr_reg: 0,
filter_ptr_reg: 0,
clauses: Vec::new(),
is_cleanup: true,
is_unreachable: false,
is_outlined: false,
call_site_entries: Vec::new(),
merged_from: Vec::new(),
};
self.landing_pads.push(pad.clone());
pad
}
}
impl X86GCEHFull {
pub fn configure_c_specific_handler(&mut self) {
self.personality = X86GCEHPersonality::CSpecificHandler;
self.emit_seh = true;
self.emit_dwarf_eh = false;
self.personality_refs
.insert("__C_specific_handler".to_string());
}
pub fn configure_cxx_frame_handler3(&mut self) {
self.personality = X86GCEHPersonality::CxxFrameHandler3;
self.emit_seh = true;
self.emit_dwarf_eh = true;
self.personality_refs
.insert("__CxxFrameHandler3".to_string());
}
pub fn lower_seh_filter(
&mut self,
name: &str,
address: u64,
action: X86GCEHFilterAction,
) -> X86GCEHFilterFunction {
X86GCEHFilterFunction {
name: name.to_string(),
address,
filter_action: action,
}
}
pub fn add_seh_scope(
&mut self,
try_start: u32,
try_end: u32,
filter_fn: Option<&str>,
handler_rva: u32,
) {
let call_site = X86GCEHCallSiteEntry {
cs_start: try_start,
cs_length: try_end - try_start,
cs_lp: handler_rva,
cs_action: if filter_fn.is_some() { 1 } else { 0 },
};
self.call_site_table.push(call_site);
if let Some(filter_name) = filter_fn {
self.personality_refs.insert(filter_name.to_string());
}
}
pub fn emit_seh_scope_table(&self) -> Vec<u8> {
let mut buf = Vec::new();
let count = self.call_site_table.len() as u32;
buf.extend_from_slice(&count.to_le_bytes());
for cs in &self.call_site_table {
buf.extend_from_slice(&cs.cs_start.to_le_bytes());
buf.extend_from_slice(&(cs.cs_start + cs.cs_length).to_le_bytes());
buf.extend_from_slice(&cs.cs_lp.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
}
buf
}
}
impl X86GCEHFull {
pub fn build_dwarf_cie(&mut self) -> X86GCEHDwarfCIE {
let aug = X86GCEHAugmentation {
has_aug_length: true,
has_lsda: true,
has_personality: true,
has_fde_encoding: true,
is_signal_frame: false,
is_bfd_cie: false,
is_gnu: false,
};
X86GCEHDwarfCIE {
length: 0, cie_id: 0,
version: 3, augmentation: aug.to_string(),
code_alignment_factor: 1,
data_alignment_factor: -8, return_address_register: DW_RIP as u8,
initial_instructions: self.build_cie_initial_instructions(),
personality_addr: None,
personality_encoding: Some(0x9B), lsda_encoding: Some(0xFF), fde_encoding: Some(0x1B), }
}
fn build_cie_initial_instructions(&self) -> Vec<u8> {
let mut insns = Vec::new();
insns.push(0x0C); insns.push(DW_RSP as u8);
insns.push(8);
insns.push(0x80 | DW_RIP as u8); insns.push(1); insns
}
pub fn build_dwarf_fde(
&mut self,
function_start: u64,
function_size: u64,
lsda_address: Option<u64>,
) -> X86GCEHDwarfFDE {
let fde = X86GCEHDwarfFDE {
length: 0, cie_pointer: 0,
initial_location: function_start,
address_range: function_size,
instructions: self.build_fde_instructions(),
lsda_pointer: lsda_address,
};
self.eh_frame_fdes
.extend_from_slice(&X86GCEHFull::serialize_fde(&fde));
fde
}
fn build_fde_instructions(&self) -> Vec<u8> {
let mut insns = Vec::new();
if self.frame_pointer_reg == DW_RBP {
insns.push(0x0C); insns.push(DW_RSP as u8);
insns.push(16);
insns.push(0x80 | DW_RBP as u8); insns.push(2);
for (i, reg) in CALLEE_SAVED_GPRS.iter().enumerate() {
if *reg != DW_RBP {
insns.push(0x80 | *reg as u8);
insns.push((3 + i) as u8);
}
}
}
insns
}
pub fn serialize_cie(cie: &X86GCEHDwarfCIE) -> Vec<u8> {
let mut buf = Vec::new();
let length_offset = buf.len();
buf.extend_from_slice(&[0u8; 4]);
buf.extend_from_slice(&cie.cie_id.to_le_bytes());
buf.push(cie.version);
buf.extend_from_slice(cie.augmentation.as_bytes());
buf.push(0);
buf.extend_from_slice(&uleb128(cie.code_alignment_factor as u64));
buf.extend_from_slice(&sleb128(cie.data_alignment_factor as i64));
buf.push(cie.return_address_register);
if cie.augmentation.contains('z') {
if let Some(pe) = cie.personality_encoding {
buf.push(pe);
}
if let Some(le) = cie.lsda_encoding {
buf.push(le);
}
if let Some(fe) = cie.fde_encoding {
buf.push(fe);
}
}
buf.extend_from_slice(&cie.initial_instructions);
let total_len = buf.len() - length_offset - 4;
let len_bytes = (total_len as u32).to_le_bytes();
buf[length_offset..length_offset + 4].copy_from_slice(&len_bytes);
buf
}
pub fn serialize_fde(fde: &X86GCEHDwarfFDE) -> Vec<u8> {
let mut buf = Vec::new();
let length_offset = buf.len();
buf.extend_from_slice(&[0u8; 4]);
buf.extend_from_slice(&fde.cie_pointer.to_le_bytes());
buf.extend_from_slice(&fde.initial_location.to_le_bytes());
buf.extend_from_slice(&fde.address_range.to_le_bytes());
if let Some(lsda) = fde.lsda_pointer {
buf.extend_from_slice(&lsda.to_le_bytes());
}
buf.extend_from_slice(&fde.instructions);
let total_len = buf.len() - length_offset - 4;
let len_bytes = (total_len as u32).to_le_bytes();
buf[length_offset..length_offset + 4].copy_from_slice(&len_bytes);
buf
}
pub fn emit_dwarf_eh_frame(&mut self) {
let cie = self.build_dwarf_cie();
self.eh_frame_cie = X86GCEHFull::serialize_cie(&cie);
}
pub fn build_lsda(&mut self) -> X86GCEHLsda {
X86GCEHLsda {
landing_pad_base: 0,
type_table: Vec::new(),
type_table_encoding: 0xFF, call_sites: self.call_site_table.clone(),
actions: self.action_records.clone(),
gcc_except_table: true,
tt_base: 0,
}
}
pub fn encode_lsda(&mut self) -> Vec<u8> {
let mut buf = Vec::new();
buf.push(0xFF);
buf.push(0xFF);
buf.push(GC_EH_CALLSITE_ENCODING);
let cs_length_offset = buf.len();
buf.extend_from_slice(&[0u8; 4]);
let cs_start = buf.len();
for cs in &self.call_site_table {
let start = uleb128(cs.cs_start as u64);
let length = uleb128(cs.cs_length as u64);
let lp = uleb128(cs.cs_lp as u64);
let action = uleb128(cs.cs_action as u64);
buf.extend_from_slice(&start);
buf.extend_from_slice(&length);
buf.extend_from_slice(&lp);
buf.extend_from_slice(&action);
}
let cs_len = (buf.len() - cs_start) as u32;
buf[cs_length_offset..cs_length_offset + 4].copy_from_slice(&cs_len.to_le_bytes());
buf
}
}
impl X86GCEHFull {
pub fn build_unwind_info(&mut self) -> X86GCEHWinUnwindInfo {
X86GCEHWinUnwindInfo {
version: 1,
flags: if self.has_personality { 0x01 } else { 0x00 }, prologue_size: 0, count_of_codes: self.unwind_codes.len() as u8,
frame_register: if self.frame_pointer_reg != DW_RSP {
self.frame_pointer_reg as u8
} else {
0
},
frame_offset: 0,
unwind_codes: self.unwind_codes.clone(),
handler_rva: if self.has_personality { Some(0) } else { None },
handler_data_rva: if self.has_personality { Some(0) } else { None },
}
}
pub fn add_unwind_push_nonvol(&mut self, reg: u8, offset: u8) {
self.unwind_codes.push(X86GCEHUnwindCode {
code_offset: offset,
unwind_op: X86GCEHUnwindOp::PushNonVol,
op_info: reg,
});
}
pub fn add_unwind_alloc_small(&mut self, size: u8, offset: u8) {
self.unwind_codes.push(X86GCEHUnwindCode {
code_offset: offset,
unwind_op: X86GCEHUnwindOp::AllocSmall,
op_info: size / 8 - 1,
});
}
pub fn add_unwind_alloc_large(&mut self, size: u32, offset: u8) {
if size <= 512 * 1024 - 8 {
self.unwind_codes.push(X86GCEHUnwindCode {
code_offset: offset,
unwind_op: X86GCEHUnwindOp::AllocLarge,
op_info: 0, });
let size_u16: u16 = (size / 8) as u16;
self.unwind_codes.push(X86GCEHUnwindCode {
code_offset: (size_u16 & 0xFF) as u8,
unwind_op: X86GCEHUnwindOp::AllocLarge,
op_info: (size_u16 >> 8) as u8,
});
} else {
self.unwind_codes.push(X86GCEHUnwindCode {
code_offset: offset,
unwind_op: X86GCEHUnwindOp::AllocLarge,
op_info: 1, });
}
}
pub fn add_unwind_set_fp(&mut self, offset: u8) {
self.unwind_codes.push(X86GCEHUnwindCode {
code_offset: offset,
unwind_op: X86GCEHUnwindOp::SetFPReg,
op_info: 0,
});
}
pub fn add_unwind_save_nonvol(&mut self, reg: u8, stack_offset: u8, code_offset: u8) {
self.unwind_codes.push(X86GCEHUnwindCode {
code_offset,
unwind_op: X86GCEHUnwindOp::SaveNonVol,
op_info: (reg << 4) | ((stack_offset / 8) & 0xF),
});
}
pub fn serialize_unwind_info(info: &X86GCEHWinUnwindInfo) -> Vec<u8> {
let mut buf = Vec::new();
buf.push(info.version | (info.flags << 3));
buf.push(info.prologue_size);
buf.push(info.count_of_codes);
buf.push(info.frame_register | (info.frame_offset << 4));
let mut code_bytes = Vec::new();
for code in &info.unwind_codes {
code_bytes.push(code.code_offset);
code_bytes.push((code.unwind_op as u8) << 4 | (code.op_info & 0x0F));
}
while code_bytes.len() % 4 != 0 {
code_bytes.push(0);
}
buf.extend_from_slice(&code_bytes);
if info.flags & 0x01 != 0 {
buf.extend_from_slice(&info.handler_rva.unwrap_or(0).to_le_bytes());
buf.extend_from_slice(&info.handler_data_rva.unwrap_or(0).to_le_bytes());
}
buf
}
pub fn build_runtime_function(
begin_address: u32,
end_address: u32,
unwind_info_rva: u32,
) -> X86GCEHRuntimeFunction {
X86GCEHRuntimeFunction {
begin_address,
end_address,
unwind_info: unwind_info_rva,
}
}
pub fn serialize_runtime_function(rf: &X86GCEHRuntimeFunction) -> [u8; 12] {
let mut buf = [0u8; 12];
buf[0..4].copy_from_slice(&rf.begin_address.to_le_bytes());
buf[4..8].copy_from_slice(&rf.end_address.to_le_bytes());
buf[8..12].copy_from_slice(&rf.unwind_info.to_le_bytes());
buf
}
pub fn emit_seh_unwind_info(&mut self) {
let info = self.build_unwind_info();
self.xdata_buffer = X86GCEHFull::serialize_unwind_info(&info);
}
pub fn emit_pdata_entry(&mut self, begin: u32, end: u32, unwind_rva: u32) {
let rf = X86GCEHFull::build_runtime_function(begin, end, unwind_rva);
self.pdata_buffer
.extend_from_slice(&X86GCEHFull::serialize_runtime_function(&rf));
}
}
impl X86GCEHFull {
pub fn outline_landing_pads(&mut self) {
if !self.outline_landing_pads {
return;
}
for pad in &mut self.landing_pads {
if !pad.is_outlined && !pad.is_unreachable {
pad.is_outlined = true;
}
}
}
pub fn prune_unreachable_handlers(&mut self) -> Vec<u64> {
if !self.prune_unreachable_handlers {
return Vec::new();
}
let mut pruned = Vec::new();
for pad in &mut self.landing_pads {
if pad.is_unreachable {
pruned.push(pad.id);
}
}
pruned
}
pub fn is_catch_dead(&self, _type_name: &str) -> bool {
false
}
pub fn prepare_eh(&mut self) {
if !self.uses_eh {
return;
}
if self.outline_landing_pads {
self.outline_landing_pads();
}
if self.prune_unreachable_handlers {
self.prune_unreachable_handlers();
}
if self.merge_landing_pads {
self.merge_identical_landing_pads();
}
}
}
impl X86GCEHFull {
pub fn merge_identical_landing_pads(&mut self) -> u32 {
if !self.merge_landing_pads || self.landing_pads.len() < 2 {
return 0;
}
let mut merged_count = 0u32;
let mut seen: HashMap<Vec<X86GCEHClauseKind>, u64> = HashMap::new();
let pads = std::mem::take(&mut self.landing_pads);
let mut result = Vec::new();
for pad in pads {
if pad.is_cleanup && pad.clauses.is_empty() {
let key = vec![X86GCEHClauseKind::Cleanup];
if let Some(&existing_id) = seen.get(&key) {
if let Some(existing) = result
.iter_mut()
.find(|p: &&mut X86GCEHLandingPad| p.id == existing_id)
{
existing.merged_from.push(pad.id);
merged_count += 1;
continue;
}
}
seen.insert(key, pad.id);
} else {
let key: Vec<X86GCEHClauseKind> = pad.clauses.iter().map(|c| c.kind).collect();
if let Some(&existing_id) = seen.get(&key) {
let existing = result
.iter()
.find(|p: &&X86GCEHLandingPad| p.id == existing_id)
.unwrap();
let clauses_match = existing.clauses.len() == pad.clauses.len()
&& existing.clauses.iter().zip(&pad.clauses).all(|(a, b)| {
match (&a.type_info, &b.type_info) {
(Some(a_ti), Some(b_ti)) => a_ti.type_name == b_ti.type_name,
(None, None) => true,
_ => false,
}
});
if clauses_match {
result
.iter_mut()
.find(|p: &&mut X86GCEHLandingPad| p.id == existing_id)
.unwrap()
.merged_from
.push(pad.id);
merged_count += 1;
continue;
}
}
seen.insert(key, pad.id);
}
result.push(pad);
}
self.landing_pads = result;
merged_count
}
pub fn remove_dead_catches(&mut self) -> u32 {
let mut removed_count = 0u32;
let dead_types: Vec<(usize, Vec<bool>)> = self
.landing_pads
.iter()
.map(|pad| {
let dead: Vec<bool> = pad
.clauses
.iter()
.map(|c| match &c.type_info {
Some(ti) => self.is_catch_dead(&ti.type_name),
None => false,
})
.collect();
dead
})
.enumerate()
.map(|(i, dead)| (i, dead))
.collect();
for (i, dead_mask) in dead_types {
let pad = &mut self.landing_pads[i];
let before = pad.clauses.len();
let mut j = 0;
pad.clauses.retain(|_| {
let keep = !dead_mask.get(j).copied().unwrap_or(false);
j += 1;
keep
});
removed_count += (before - pad.clauses.len()) as u32;
}
removed_count
}
pub fn deduplicate_call_sites(&mut self) {
let mut i = 0;
while i + 1 < self.call_site_table.len() {
let a = &self.call_site_table[i];
let b = &self.call_site_table[i + 1];
if a.cs_lp == b.cs_lp
&& a.cs_action == b.cs_action
&& a.cs_start + a.cs_length == b.cs_start
{
let merged_length = a.cs_length + b.cs_length;
self.call_site_table[i].cs_length = merged_length;
self.call_site_table.remove(i + 1);
} else {
i += 1;
}
}
}
}
impl X86GCEHFull {
pub fn personality_gxx_v0(&mut self) {
self.personality = X86GCEHPersonality::GxxV0;
self.emit_dwarf_eh = true;
self.emit_seh = false;
self.personality_refs
.insert("__gxx_personality_v0".to_string());
}
pub fn personality_gcc_v0(&mut self) {
self.personality = X86GCEHPersonality::GccV0;
self.emit_dwarf_eh = true;
self.emit_seh = false;
self.personality_refs
.insert("__gcc_personality_v0".to_string());
}
pub fn personality_cxx_frame_handler3(&mut self) {
self.configure_cxx_frame_handler3();
}
pub fn personality_c_specific_handler(&mut self) {
self.configure_c_specific_handler();
}
pub fn personality_objc_v0(&mut self) {
self.personality = X86GCEHPersonality::ObjCV0;
self.emit_dwarf_eh = true;
self.emit_seh = false;
self.personality_refs
.insert("__objc_personality_v0".to_string());
}
pub fn personality_gxx_seh0(&mut self) {
self.personality = X86GCEHPersonality::GxxSeh0;
self.emit_dwarf_eh = true;
self.emit_seh = true;
self.is_mingw = true;
self.personality_refs
.insert("__gxx_personality_seh0".to_string());
}
pub fn get_personality_config(&self) -> X86GCEHPersonalityConfig {
match self.personality {
X86GCEHPersonality::GxxV0 => X86GCEHPersonalityConfig {
exc_ptr_reg: DW_RAX,
selector_reg: DW_RDX,
cleanups_in_phase1: false,
handles_foreign: true,
uses_two_phase: true,
match_catch_type_adjusted_ptr: true,
},
X86GCEHPersonality::GccV0 => X86GCEHPersonalityConfig {
exc_ptr_reg: DW_RAX,
selector_reg: DW_RDX,
cleanups_in_phase1: false,
handles_foreign: false,
uses_two_phase: true,
match_catch_type_adjusted_ptr: false,
},
X86GCEHPersonality::CxxFrameHandler3 => X86GCEHPersonalityConfig {
exc_ptr_reg: DW_RAX,
selector_reg: DW_RDX,
cleanups_in_phase1: true,
handles_foreign: true,
uses_two_phase: true,
match_catch_type_adjusted_ptr: true,
},
X86GCEHPersonality::CSpecificHandler => X86GCEHPersonalityConfig {
exc_ptr_reg: DW_RAX,
selector_reg: DW_RDX,
cleanups_in_phase1: true,
handles_foreign: true,
uses_two_phase: false,
match_catch_type_adjusted_ptr: false,
},
X86GCEHPersonality::ObjCV0 => X86GCEHPersonalityConfig {
exc_ptr_reg: DW_RAX,
selector_reg: DW_RDX,
cleanups_in_phase1: false,
handles_foreign: true,
uses_two_phase: true,
match_catch_type_adjusted_ptr: true,
},
X86GCEHPersonality::GxxSeh0 => X86GCEHPersonalityConfig {
exc_ptr_reg: DW_RAX,
selector_reg: DW_RDX,
cleanups_in_phase1: false,
handles_foreign: true,
uses_two_phase: true,
match_catch_type_adjusted_ptr: true,
},
X86GCEHPersonality::None => X86GCEHPersonalityConfig {
exc_ptr_reg: 0,
selector_reg: 0,
cleanups_in_phase1: false,
handles_foreign: false,
uses_two_phase: false,
match_catch_type_adjusted_ptr: false,
},
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct X86GCEHPersonalityConfig {
pub exc_ptr_reg: u16,
pub selector_reg: u16,
pub cleanups_in_phase1: bool,
pub handles_foreign: bool,
pub uses_two_phase: bool,
pub match_catch_type_adjusted_ptr: bool,
}
impl X86GCEHFull {
pub fn get_eh_return_data_regno(index: u32) -> Option<u16> {
X86GCEHExceptionPointerRegs::eh_return_data_regno(index)
}
pub fn extract_exception_pointer(&self) -> u16 {
let config = self.get_personality_config();
config.exc_ptr_reg
}
pub fn extract_selector(&self) -> u16 {
let config = self.get_personality_config();
config.selector_reg
}
pub fn lower_eh_extract_values(&self, _lp_reg: u16) -> (X86GCEHRoot, X86GCEHRoot) {
let exc_ptr = X86GCEHRoot {
kind: X86GCEHRootKind::ObjectPointer,
location: X86GCEHRootLocation::Register(self.extract_exception_pointer()),
is_derived: false,
derived_offset: 0,
is_live: true,
description: "exception pointer from landingpad".to_string(),
};
let selector = X86GCEHRoot {
kind: X86GCEHRootKind::ObjectPointer,
location: X86GCEHRootLocation::Register(self.extract_selector()),
is_derived: false,
derived_offset: 0,
is_live: true,
description: "selector from landingpad".to_string(),
};
(exc_ptr, selector)
}
}
impl X86GCEHFull {
pub fn emit_llvm_stackmaps_section(&self) -> Vec<u8> {
let mut section = Vec::new();
section.push(3);
section.push(0);
section.extend_from_slice(&[0u8, 0u8]);
section.extend_from_slice(&self.stack_map_buffer);
section
}
pub fn lower_patchpoint(&mut self, _patchpoint: &X86GCEHPatchpoint) -> X86GCEHStackMapRecord {
let id = self.next_stack_map_id();
let record = X86GCEHStackMapRecord {
id,
function_address: 0,
instruction_offset: id * 4,
num_locations: 0,
locations: Vec::new(),
num_live_outs: 0,
live_outs: Vec::new(),
padding: 0,
};
self.stack_maps.push(record.clone());
record
}
pub fn lower_statepoint(&mut self, statepoint: &X86GCEHStatepoint) -> X86GCEHStackMapRecord {
let record = self.build_stack_map_record(
statepoint.stack_map_id,
statepoint.pc_offset,
&statepoint.roots,
&statepoint.stack_slots,
);
self.stack_maps.push(record.clone());
record
}
pub fn get_stack_map_section(&self) -> Vec<u8> {
self.finalize_stack_map_section()
}
}
impl X86GCEHFull {
pub fn lower_llvm_patchpoint(
&mut self,
flags: u32,
num_bytes: u32,
num_args: u32,
callee: u64,
num_patch_args: u16,
cc: u32,
) -> X86GCEHPatchpoint {
let id = self.next_statepoint_id();
let stack_map_id = self.next_stack_map_id();
let _record = X86GCEHStackMapRecord {
id: stack_map_id,
function_address: 0,
instruction_offset: id * num_bytes as u64,
num_locations: num_patch_args,
locations: Vec::new(),
num_live_outs: 0,
live_outs: Vec::new(),
padding: 0,
};
X86GCEHPatchpoint {
id,
flags,
num_bytes,
num_args,
callee,
num_patch_args,
cc,
stack_map_id: Some(stack_map_id),
}
}
pub fn build_patchpoint_nop_sled(num_bytes: u32) -> Vec<u8> {
let mut sled = Vec::with_capacity(num_bytes as usize);
let mut remaining = num_bytes;
while remaining > 0 {
if remaining >= 9 {
sled.extend_from_slice(&[0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]);
remaining -= 9;
} else if remaining >= 8 {
sled.extend_from_slice(&[0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]);
remaining -= 8;
} else if remaining >= 7 {
sled.extend_from_slice(&[0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00]);
remaining -= 7;
} else if remaining >= 6 {
sled.extend_from_slice(&[0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00]);
remaining -= 6;
} else if remaining >= 5 {
sled.extend_from_slice(&[0x0F, 0x1F, 0x44, 0x00, 0x00]);
remaining -= 5;
} else if remaining >= 4 {
sled.extend_from_slice(&[0x0F, 0x1F, 0x40, 0x00]);
remaining -= 4;
} else if remaining >= 3 {
sled.extend_from_slice(&[0x0F, 0x1F, 0x00]);
remaining -= 3;
} else if remaining >= 2 {
sled.extend_from_slice(&[0x66, 0x90]);
remaining -= 2;
} else {
sled.push(0x90);
remaining -= 1;
}
}
sled
}
}
impl X86GCEHFull {
pub fn run_pipeline(&mut self) {
if self.uses_gc {
let _roots = self.enumerate_live_roots();
let _offsets = self.compute_safepoint_placement();
if self.enable_statepoints {
}
}
if self.uses_eh {
self.prepare_eh();
}
if self.enable_stack_maps && self.uses_gc {
}
self.end_function();
}
pub fn summary(&self) -> String {
format!(
"X86GCEHFull: function={}, gc_strategy={}, personality={}, \
safepoints={}, landing_pads={}, stack_maps={}, roots={}, \
uses_gc={}, uses_eh={}",
self.current_function,
self.gc_strategy,
self.personality.symbol_name(),
self.safepoints.len(),
self.landing_pads.len(),
self.stack_maps.len(),
self.active_roots.len(),
self.uses_gc,
self.uses_eh,
)
}
pub fn validate(&self) -> Result<(), String> {
if self.uses_gc && self.gc_strategy == X86GCEHStrategy::None {
return Err(format!(
"function '{}' uses GC but no GC strategy selected",
self.current_function
));
}
if self.uses_eh && self.personality == X86GCEHPersonality::None {
return Err(format!(
"function '{}' uses EH but no personality selected",
self.current_function
));
}
if self.enable_stack_maps && self.stack_maps.len() > GC_EH_MAX_SAFEPOINTS {
return Err(format!(
"function '{}' exceeds maximum safepoints ({} > {})",
self.current_function,
self.stack_maps.len(),
GC_EH_MAX_SAFEPOINTS,
));
}
Ok(())
}
}
fn uleb128(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 sleb128(mut value: i64) -> Vec<u8> {
let mut buf = Vec::new();
loop {
let mut byte = (value & 0x7F) as u8;
value >>= 7;
let more = !((value == 0 && (byte & 0x40) == 0) || (value == -1 && (byte & 0x40) != 0));
if more {
byte |= 0x80;
}
buf.push(byte);
if !more {
break;
}
}
buf
}
pub fn decode_uleb128(data: &[u8]) -> Option<(u64, usize)> {
let mut value: u64 = 0;
let mut shift = 0u32;
for (i, &byte) in data.iter().enumerate() {
value |= ((byte & 0x7F) as u64) << shift;
if byte & 0x80 == 0 {
return Some((value, i + 1));
}
shift += 7;
if shift >= 64 {
return None; }
}
None
}
pub fn compute_cfa(rsp: u64, stack_size: u64) -> u64 {
rsp + stack_size
}
pub fn compute_return_address_location(cfa: u64) -> u64 {
cfa - 8 }
impl X86GCEHFull {
pub fn build_machine_safepoint_poll(&self) -> MachineInstr {
MachineInstr::default()
}
pub fn build_machine_eh_return(&self, _stack_adjust: u64, _target_addr: u64) -> MachineInstr {
MachineInstr::default()
}
pub fn build_machine_patchpoint_call(&self, _patchpoint: &X86GCEHPatchpoint) -> MachineInstr {
MachineInstr::default()
}
}
impl X86GCEHFull {
pub fn call_may_trigger_gc(&self, callee: &str) -> bool {
if !self.uses_gc {
return false;
}
match self.gc_strategy {
X86GCEHStrategy::Statepoint => {
!self.call_needs_statepoint(callee, true)
}
X86GCEHStrategy::CoreCLR => {
callee.starts_with("JIT_")
|| callee.starts_with("GC_")
|| callee == "new"
|| callee == "alloc"
}
X86GCEHStrategy::OCaml => {
callee == "caml_call_gc" || callee == "caml_alloc" || callee.starts_with("caml_")
}
X86GCEHStrategy::Erlang => {
callee == "erts_gc" || callee.starts_with("erts_")
}
X86GCEHStrategy::ShadowStack => {
true
}
_ => false,
}
}
pub fn compute_call_gc_roots(&self, _call_target: &str) -> Vec<X86GCEHRoot> {
self.enumerate_live_roots()
}
}
#[derive(Debug, Default)]
pub struct X86GCEHFullBuilder {
inner: X86GCEHFull,
}
impl X86GCEHFullBuilder {
pub fn new() -> Self {
Self {
inner: X86GCEHFull::default(),
}
}
pub fn gc_strategy(mut self, strategy: X86GCEHStrategy) -> Self {
self.inner.set_gc_strategy(strategy);
self
}
pub fn personality(mut self, personality: X86GCEHPersonality) -> Self {
self.inner.personality = personality;
self
}
pub fn target_triple(mut self, triple: &str) -> Self {
self.inner.target_triple = triple.to_string();
self
}
pub fn data_layout(mut self, layout: &str) -> Self {
self.inner.data_layout = layout.to_string();
self
}
pub fn safepoints_at_backedges(mut self, v: bool) -> Self {
self.inner.safepoints_at_backedges = v;
self
}
pub fn safepoints_at_calls(mut self, v: bool) -> Self {
self.inner.safepoints_at_calls = v;
self
}
pub fn enable_statepoints(mut self, v: bool) -> Self {
self.inner.enable_statepoints = v;
self
}
pub fn enable_stack_maps(mut self, v: bool) -> Self {
self.inner.enable_stack_maps = v;
self
}
pub fn emit_dwarf_eh(mut self, v: bool) -> Self {
self.inner.emit_dwarf_eh = v;
self
}
pub fn emit_seh(mut self, v: bool) -> Self {
self.inner.emit_seh = v;
self
}
pub fn outline_landing_pads(mut self, v: bool) -> Self {
self.inner.outline_landing_pads = v;
self
}
pub fn merge_landing_pads(mut self, v: bool) -> Self {
self.inner.merge_landing_pads = v;
self
}
pub fn prune_unreachable_handlers(mut self, v: bool) -> Self {
self.inner.prune_unreachable_handlers = v;
self
}
pub fn build(self) -> X86GCEHFull {
self.inner
}
}
impl fmt::Display for X86GCEHFull {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "X86GCEHFull {{")?;
writeln!(f, " gc_strategy: {}", self.gc_strategy)?;
writeln!(f, " personality: {}", self.personality.symbol_name())?;
writeln!(f, " target_triple: {}", self.target_triple)?;
writeln!(
f,
" is_64bit: {}, is_windows: {}, is_mingw: {}",
self.is_64bit, self.is_windows, self.is_mingw
)?;
writeln!(
f,
" enable_statepoints: {}, enable_stack_maps: {}",
self.enable_statepoints, self.enable_stack_maps
)?;
writeln!(f, " safepoints: {}", self.safepoints.len())?;
writeln!(f, " landing_pads: {}", self.landing_pads.len())?;
writeln!(f, " stack_maps: {}", self.stack_maps.len())?;
writeln!(f, " active_roots: {}", self.active_roots.len())?;
writeln!(f, " current_function: {}", self.current_function)?;
write!(f, "}}")
}
}
impl fmt::Display for X86GCEHLandingPad {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"LandingPad(id={}, kind={:?}, clauses={}, cleanup={})",
self.id,
self.kind,
self.clauses.len(),
self.is_cleanup
)
}
}
impl fmt::Display for X86GCEHStatepoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Statepoint(id={}, sm={}, roots={})",
self.id, self.stack_map_id, self.num_roots
)
}
}
impl fmt::Display for X86GCEHStackMapRecord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"StackMap(id={}, locs={}, liveouts={})",
self.id, self.num_locations, self.num_live_outs
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_default_state() -> X86GCEHFull {
let mut state = X86GCEHFull::default();
state.begin_function("test_fn", true, true);
state
}
fn make_linux_gc_state() -> X86GCEHFull {
X86GCEHFullBuilder::new()
.gc_strategy(X86GCEHStrategy::Statepoint)
.personality(X86GCEHPersonality::GxxV0)
.target_triple("x86_64-unknown-linux-gnu")
.enable_statepoints(true)
.enable_stack_maps(true)
.emit_dwarf_eh(true)
.emit_seh(false)
.build()
}
fn make_windows_eh_state() -> X86GCEHFull {
X86GCEHFullBuilder::new()
.gc_strategy(X86GCEHStrategy::None)
.personality(X86GCEHPersonality::CxxFrameHandler3)
.target_triple("x86_64-pc-windows-msvc")
.emit_dwarf_eh(true)
.emit_seh(true)
.build()
}
#[test]
fn test_default_construction() {
let state = X86GCEHFull::default();
assert_eq!(state.gc_strategy, X86GCEHStrategy::Statepoint);
assert_eq!(state.personality, X86GCEHPersonality::GxxV0);
assert!(state.enable_statepoints);
assert!(state.enable_stack_maps);
assert!(state.is_64bit);
assert!(!state.is_windows);
assert!(!state.is_mingw);
assert_eq!(state.pointer_size, 8);
assert_eq!(state.frame_pointer_reg, DW_RBP);
assert_eq!(state.stack_pointer_reg, DW_RSP);
assert!(state.active_roots.is_empty());
assert!(state.stack_roots.is_empty());
assert!(state.stack_maps.is_empty());
assert!(state.safepoints.is_empty());
assert!(state.landing_pads.is_empty());
}
#[test]
fn test_begin_function_resets_state() {
let mut state = make_default_state();
state.register_root(
DW_RAX,
X86GCEHRootKind::ObjectPointer,
false,
0,
"test_root",
);
state.register_stack_root(-8, 8, false, "stack_root");
state.lower_landingpad(vec![], true);
state.begin_function("new_fn", true, false);
assert_eq!(state.current_function, "new_fn");
assert!(state.active_roots.is_empty());
assert!(state.stack_roots.is_empty());
assert!(state.call_site_table.is_empty());
assert!(state.action_records.is_empty());
assert!(!state.uses_eh);
}
#[test]
fn test_id_generation_monotonic() {
let mut state = make_default_state();
let a = state.next_statepoint_id();
let b = state.next_statepoint_id();
let c = state.next_statepoint_id();
assert!(a < b);
assert!(b < c);
let s1 = state.next_safepoint_id();
let s2 = state.next_safepoint_id();
assert!(s1 < s2);
let lp1 = state.next_landingpad_id();
let lp2 = state.next_landingpad_id();
assert!(lp1 < lp2);
}
#[test]
fn test_reset() {
let mut state = make_default_state();
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "root");
state.safepoints.push(X86GCEHSafepoint {
id: 1,
kind: X86GCEHSafepointKind::PreCall,
pc_offset: 0,
may_gc: true,
statepoint_id: None,
stack_map_id: None,
});
state.reset();
assert_eq!(state.gc_strategy, X86GCEHStrategy::Statepoint);
assert!(state.active_roots.is_empty());
assert!(state.safepoints.is_empty());
assert_eq!(state.next_statepoint_id, 0);
}
#[test]
fn test_gc_strategy_statepoint() {
let s = X86GCEHStrategy::Statepoint;
assert!(s.uses_statepoints());
assert!(!s.uses_shadow_stack());
assert!(s.needs_stack_maps());
assert!(s.requires_write_barriers());
assert_eq!(s.llvm_attr_name(), "statepoint-example");
}
#[test]
fn test_gc_strategy_coreclr() {
let s = X86GCEHStrategy::CoreCLR;
assert!(!s.uses_statepoints());
assert!(!s.uses_shadow_stack());
assert!(s.needs_stack_maps());
assert!(s.requires_write_barriers());
assert_eq!(s.llvm_attr_name(), "coreclr");
}
#[test]
fn test_gc_strategy_ocaml() {
let s = X86GCEHStrategy::OCaml;
assert!(!s.uses_statepoints());
assert!(!s.uses_shadow_stack());
assert!(s.needs_stack_maps());
assert!(s.requires_write_barriers());
assert_eq!(s.llvm_attr_name(), "ocaml");
}
#[test]
fn test_gc_strategy_erlang() {
let s = X86GCEHStrategy::Erlang;
assert!(!s.uses_statepoints());
assert!(!s.uses_shadow_stack());
assert!(!s.needs_stack_maps());
assert!(s.requires_write_barriers());
assert_eq!(s.llvm_attr_name(), "erlang");
}
#[test]
fn test_gc_strategy_shadow_stack() {
let s = X86GCEHStrategy::ShadowStack;
assert!(!s.uses_statepoints());
assert!(s.uses_shadow_stack());
assert!(!s.needs_stack_maps());
assert!(!s.requires_write_barriers());
assert_eq!(s.llvm_attr_name(), "shadow-stack");
}
#[test]
fn test_set_gc_strategy_from_attr() {
let mut state = X86GCEHFull::default();
state.set_gc_strategy_from_attr("statepoint-example");
assert_eq!(state.gc_strategy, X86GCEHStrategy::Statepoint);
state.set_gc_strategy_from_attr("coreclr");
assert_eq!(state.gc_strategy, X86GCEHStrategy::CoreCLR);
state.set_gc_strategy_from_attr("ocaml");
assert_eq!(state.gc_strategy, X86GCEHStrategy::OCaml);
state.set_gc_strategy_from_attr("unknown");
assert_eq!(state.gc_strategy, X86GCEHStrategy::None);
}
#[test]
fn test_needs_gc_support() {
let mut state = X86GCEHFull::default();
state.uses_gc = true;
state.gc_strategy = X86GCEHStrategy::Statepoint;
assert!(state.needs_gc_support());
state.gc_strategy = X86GCEHStrategy::None;
assert!(!state.needs_gc_support());
state.uses_gc = false;
assert!(!state.needs_gc_support());
}
#[test]
fn test_needs_eh_support() {
let mut state = X86GCEHFull::default();
state.uses_eh = true;
state.personality = X86GCEHPersonality::GxxV0;
assert!(state.needs_eh_support());
state.personality = X86GCEHPersonality::None;
assert!(!state.needs_eh_support());
state.uses_eh = false;
assert!(!state.needs_eh_support());
}
#[test]
fn test_register_root() {
let mut state = make_default_state();
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax_root");
assert_eq!(state.active_roots.len(), 1);
let root = state.active_roots.get(&DW_RAX).unwrap();
assert_eq!(root.kind, X86GCEHRootKind::ObjectPointer);
assert!(matches!(
root.location,
X86GCEHRootLocation::Register(DW_RAX)
));
assert!(root.is_live);
assert!(!root.is_derived);
}
#[test]
fn test_register_derived_root() {
let mut state = make_default_state();
state.register_root(DW_R12, X86GCEHRootKind::DerivedPointer, true, 16, "derived");
let root = state.active_roots.get(&DW_R12).unwrap();
assert!(root.is_derived);
assert_eq!(root.derived_offset, 16);
assert_eq!(root.kind, X86GCEHRootKind::DerivedPointer);
}
#[test]
fn test_stack_root() {
let mut state = make_default_state();
state.register_stack_root(-8, 8, false, "local_ptr");
assert_eq!(state.stack_roots.len(), 1);
assert_eq!(state.stack_roots[0].offset, -8);
assert_eq!(state.stack_roots[0].size, 8);
assert!(!state.stack_roots[0].is_derived);
assert!(state.stack_roots[0].is_live);
}
#[test]
fn test_kill_root() {
let mut state = make_default_state();
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
assert!(state.active_roots.get(&DW_RAX).unwrap().is_live);
state.kill_root(DW_RAX);
assert!(!state.active_roots.get(&DW_RAX).unwrap().is_live);
}
#[test]
fn test_kill_stack_root() {
let mut state = make_default_state();
state.register_stack_root(-8, 8, false, "slot1");
state.register_stack_root(-16, 8, false, "slot2");
state.kill_stack_root(-8);
assert!(!state.stack_roots[0].is_live);
assert!(state.stack_roots[1].is_live);
}
#[test]
fn test_enumerate_live_roots() {
let mut state = make_default_state();
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
state.register_root(DW_RBX, X86GCEHRootKind::ObjectPointer, false, 0, "rbx");
state.kill_root(DW_RBX);
state.register_stack_root(-8, 8, false, "slot1");
let roots = state.enumerate_live_roots();
assert_eq!(roots.len(), 2); let has_rax = roots
.iter()
.any(|r| matches!(r.location, X86GCEHRootLocation::Register(DW_RAX)));
assert!(has_rax);
}
#[test]
fn test_enumerate_callee_saved_roots() {
let mut state = make_default_state();
state.register_root(DW_RBX, X86GCEHRootKind::ObjectPointer, false, 0, "rbx");
state.register_root(DW_R12, X86GCEHRootKind::ObjectPointer, false, 0, "r12");
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
let callee = state.enumerate_callee_saved_roots();
assert_eq!(callee.len(), 2);
assert!(callee.iter().any(|r| r.description == "rbx"));
assert!(callee.iter().any(|r| r.description == "r12"));
}
#[test]
fn test_enumerate_caller_saved_roots() {
let mut state = make_default_state();
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
state.register_root(DW_RDI, X86GCEHRootKind::ObjectPointer, false, 0, "rdi");
state.register_root(DW_RBX, X86GCEHRootKind::ObjectPointer, false, 0, "rbx"); let caller = state.enumerate_caller_saved_roots();
assert_eq!(caller.len(), 2);
}
#[test]
fn test_lower_gc_statepoint() {
let mut state = make_default_state();
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
let sp = state.lower_gc_statepoint(
Some("malloc"),
2,
&[],
X86GCEHGCTransition::Relocation,
&[0x42, 0x43],
0,
);
assert_eq!(sp.call_target.as_deref(), Some("malloc"));
assert_eq!(sp.num_call_args, 2);
assert_eq!(sp.gc_transition, X86GCEHGCTransition::Relocation);
assert_eq!(sp.deopt_state, vec![0x42, 0x43]);
assert_eq!(sp.num_deopt_bytes, 2);
}
#[test]
fn test_lower_gc_statepoint_creates_stack_map() {
let mut state = make_default_state();
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
let sp = state.lower_gc_statepoint(None, 1, &[], X86GCEHGCTransition::Safepoint, &[], 0);
assert!(!state.stack_maps.is_empty());
let sm = &state.stack_maps[0];
assert_eq!(sm.id, sp.stack_map_id);
assert!(sm.num_locations >= 1);
assert!(sm.num_live_outs >= 1);
}
#[test]
fn test_lower_gc_statepoint_creates_safepoint() {
let mut state = make_default_state();
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
let sp =
state.lower_gc_statepoint(Some("foo"), 0, &[], X86GCEHGCTransition::Relocation, &[], 0);
assert!(!state.safepoints.is_empty());
let sf = &state.safepoints[0];
assert_eq!(sf.statepoint_id, Some(sp.id));
assert_eq!(sf.may_gc, true);
assert_eq!(sf.kind, X86GCEHSafepointKind::PreCall);
}
#[test]
fn test_lower_gc_statepoint_no_gc_transition() {
let mut state = make_default_state();
let sp =
state.lower_gc_statepoint(Some("leaf_fn"), 0, &[], X86GCEHGCTransition::None, &[], 0);
let sf = &state.safepoints[0];
assert!(!sf.may_gc);
}
#[test]
fn test_lower_gc_result() {
let state = make_default_state();
let root = state.lower_gc_result(42, DW_R8);
assert_eq!(root.kind, X86GCEHRootKind::ObjectPointer);
assert!(matches!(
root.location,
X86GCEHRootLocation::Register(DW_R8)
));
assert!(root.description.contains("gc.result"));
assert!(root.description.contains("42"));
assert!(root.description.contains("D8"));
}
#[test]
fn test_lower_gc_relocate() {
let state = make_default_state();
let reloc = state.lower_gc_relocate(10, 7, 7, 0x1000, 0x2000);
assert_eq!(reloc.statepoint_id, 10);
assert_eq!(reloc.base_offset, 7);
assert_eq!(reloc.derived_offset, 7);
assert_eq!(reloc.original_value, 0x1000);
assert_eq!(reloc.relocated_value, 0x2000);
}
#[test]
fn test_emit_gc_relocates() {
let mut state = make_default_state();
let relocs =
state.emit_gc_relocates(5, &[(DW_RAX as u16, 0x1000), (DW_RBX as u16, 0x2000)]);
assert_eq!(relocs.len(), 2);
assert_eq!(relocs[0].statepoint_id, 5);
assert_eq!(relocs[1].statepoint_id, 5);
}
#[test]
fn test_insert_loop_backedge_safepoint() {
let mut state = make_default_state();
let sp = state.insert_loop_backedge_safepoint(0x100);
assert!(sp.is_some());
assert_eq!(sp.unwrap().kind, X86GCEHSafepointKind::LoopBackedge);
assert_eq!(state.safepoints.len(), 1);
}
#[test]
fn test_insert_loop_backedge_safepoint_disabled() {
let mut state = make_default_state();
state.safepoints_at_backedges = false;
let sp = state.insert_loop_backedge_safepoint(0x100);
assert!(sp.is_none());
}
#[test]
fn test_insert_pre_call_safepoint() {
let mut state = make_default_state();
let sp = state.insert_pre_call_safepoint(0x200);
assert!(sp.is_some());
assert_eq!(state.safepoints.len(), 1);
}
#[test]
fn test_insert_pre_call_safepoint_disabled() {
let mut state = make_default_state();
state.safepoints_at_calls = false;
let sp = state.insert_pre_call_safepoint(0x200);
assert!(sp.is_none());
}
#[test]
fn test_insert_polling_safepoint() {
let mut state = make_default_state();
state.cooperative_polling = true;
let sp = state.insert_polling_safepoint(0x300);
assert!(sp.is_some());
assert_eq!(sp.unwrap().kind, X86GCEHSafepointKind::Poll);
}
#[test]
fn test_insert_polling_safepoint_disabled() {
let mut state = make_default_state();
state.cooperative_polling = false;
let sp = state.insert_polling_safepoint(0x300);
assert!(sp.is_none());
}
#[test]
fn test_compute_safepoint_placement() {
let mut state = make_default_state();
state.insert_loop_backedge_safepoint(0x100);
state.insert_pre_call_safepoint(0x100); state.insert_entry_safepoint(0x50);
let offsets = state.compute_safepoint_placement();
assert_eq!(offsets.len(), 2); assert_eq!(offsets[0], 0x50);
assert_eq!(offsets[1], 0x100);
}
#[test]
fn test_call_needs_statepoint() {
let mut state = make_default_state();
state.uses_gc = true;
assert!(!state.call_needs_statepoint("memcpy", true));
assert!(!state.call_needs_statepoint("llvm.memcpy", true));
assert!(!state.call_needs_statepoint("llvm.sqrt", true));
assert!(state.call_needs_statepoint("malloc", false));
assert!(state.call_needs_statepoint("operator_new", false));
}
#[test]
fn test_call_needs_statepoint_no_gc() {
let mut state = make_default_state();
state.uses_gc = false;
assert!(!state.call_needs_statepoint("malloc", false));
}
#[test]
fn test_rewrite_statepoints_for_gc() {
let mut state = make_default_state();
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
let roots = state.enumerate_live_roots();
let statepoints =
state.rewrite_statepoints_for_gc(&[(0x100, Some("malloc".to_string()), 2)], &roots);
assert_eq!(statepoints.len(), 1);
assert_eq!(statepoints[0].call_target.as_deref(), Some("malloc"));
}
#[test]
fn test_lower_landingpad_cleanup_only() {
let mut state = make_default_state();
let lp = state.lower_landingpad(vec![], true);
assert_eq!(lp.kind, X86GCEHLandingPadKind::LandingPad);
assert!(lp.is_cleanup);
assert!(lp.clauses.is_empty());
}
#[test]
fn test_lower_landingpad_with_catch() {
let mut state = make_default_state();
let ti = X86GCEHTypeInfoEntry {
encoding: 0,
type_info_ptr: 0xDEAD,
type_name: "std::runtime_error".to_string(),
};
let clause = X86GCEHClause {
kind: X86GCEHClauseKind::Catch,
type_info: Some(ti),
filter_fn: None,
handler: Some("catch_handler".to_string()),
is_catch_all: false,
};
let lp = state.lower_landingpad(vec![clause], false);
assert_eq!(lp.clauses.len(), 1);
assert!(!lp.is_cleanup);
}
#[test]
fn test_lower_landingpad_catch_all() {
let mut state = make_default_state();
let clause = X86GCEHClause {
kind: X86GCEHClauseKind::CatchAll,
type_info: None,
filter_fn: None,
handler: None,
is_catch_all: true,
};
let lp = state.lower_landingpad(vec![clause], true);
assert_eq!(lp.clauses.len(), 1);
assert!(lp.clauses[0].is_catch_all);
}
#[test]
fn test_lower_resume() {
let mut state = make_default_state();
let resume = state.lower_resume(0);
assert_eq!(resume.kind, X86GCEHLandingPadKind::Resume);
}
#[test]
fn test_lower_cleanupret() {
let mut state = make_default_state();
let ret = state.lower_cleanupret(1, Some(2));
assert_eq!(ret.kind, X86GCEHLandingPadKind::CleanupRet);
assert!(ret.is_cleanup);
}
#[test]
fn test_lower_catchret() {
let mut state = make_default_state();
let ret = state.lower_catchret(1, "continue.bb");
assert_eq!(ret.kind, X86GCEHLandingPadKind::CatchRet);
}
#[test]
fn test_lower_catchswitch() {
let mut state = make_default_state();
let cs = state.lower_catchswitch(&[10, 20], None);
assert_eq!(cs.kind, X86GCEHLandingPadKind::CatchSwitch);
}
#[test]
fn test_lower_catchpad() {
let mut state = make_default_state();
let pad = state.lower_catchpad(1, None);
assert_eq!(pad.kind, X86GCEHLandingPadKind::CatchPad);
}
#[test]
fn test_lower_cleanuppad() {
let mut state = make_default_state();
let pad = state.lower_cleanuppad();
assert_eq!(pad.kind, X86GCEHLandingPadKind::CleanupPad);
assert!(pad.is_cleanup);
}
#[test]
fn test_configure_c_specific_handler() {
let mut state = X86GCEHFull::default();
state.configure_c_specific_handler();
assert_eq!(state.personality, X86GCEHPersonality::CSpecificHandler);
assert!(state.emit_seh);
assert!(!state.emit_dwarf_eh);
assert!(state.personality_refs.contains("__C_specific_handler"));
}
#[test]
fn test_configure_cxx_frame_handler3() {
let mut state = X86GCEHFull::default();
state.configure_cxx_frame_handler3();
assert_eq!(state.personality, X86GCEHPersonality::CxxFrameHandler3);
assert!(state.emit_seh);
assert!(state.personality_refs.contains("__CxxFrameHandler3"));
}
#[test]
fn test_lower_seh_filter() {
let mut state = make_default_state();
let filter =
state.lower_seh_filter("my_filter", 0x4000, X86GCEHFilterAction::ExecuteHandler);
assert_eq!(filter.name, "my_filter");
assert_eq!(filter.address, 0x4000);
assert_eq!(filter.filter_action, X86GCEHFilterAction::ExecuteHandler);
}
#[test]
fn test_add_seh_scope() {
let mut state = make_default_state();
state.add_seh_scope(0x100, 0x200, Some("filter_fn"), 0x300);
assert_eq!(state.call_site_table.len(), 1);
assert!(state.personality_refs.contains("filter_fn"));
let cs = &state.call_site_table[0];
assert_eq!(cs.cs_start, 0x100);
assert_eq!(cs.cs_length, 0x100);
assert_eq!(cs.cs_lp, 0x300);
}
#[test]
fn test_emit_seh_scope_table() {
let mut state = make_default_state();
state.add_seh_scope(0x100, 0x200, Some("filter_fn"), 0x300);
state.add_seh_scope(0x300, 0x400, None, 0x500);
let table = state.emit_seh_scope_table();
assert_eq!(table.len(), 36);
}
#[test]
fn test_build_dwarf_cie() {
let mut state = make_default_state();
let cie = state.build_dwarf_cie();
assert_eq!(cie.version, 3);
assert_eq!(cie.cie_id, 0);
assert_eq!(cie.code_alignment_factor, 1);
assert_eq!(cie.data_alignment_factor, -8);
assert_eq!(cie.return_address_register, DW_RIP as u8);
assert!(cie.augmentation.contains('z'));
assert!(cie.augmentation.contains('L'));
assert!(cie.augmentation.contains('P'));
assert!(cie.augmentation.contains('R'));
}
#[test]
fn test_serialize_cie() {
let mut state = make_default_state();
let cie = state.build_dwarf_cie();
let bytes = X86GCEHFull::serialize_cie(&cie);
assert!(!bytes.is_empty());
assert_eq!(&bytes[4..8], &[0u8, 0, 0, 0]);
}
#[test]
fn test_build_dwarf_fde() {
let mut state = make_default_state();
let fde = state.build_dwarf_fde(0x400000, 0x100, None);
assert_eq!(fde.initial_location, 0x400000);
assert_eq!(fde.address_range, 0x100);
}
#[test]
fn test_serialize_fde() {
let mut state = make_default_state();
let fde = state.build_dwarf_fde(0x400000, 0x100, None);
let bytes = X86GCEHFull::serialize_fde(&fde);
assert!(!bytes.is_empty());
}
#[test]
fn test_build_unwind_info() {
let mut state = make_default_state();
let info = state.build_unwind_info();
assert_eq!(info.version, 1);
assert_eq!(info.flags, 1); }
#[test]
fn test_add_unwind_push_nonvol() {
let mut state = make_default_state();
state.add_unwind_push_nonvol(3, 1); assert_eq!(state.unwind_codes.len(), 1);
let code = &state.unwind_codes[0];
assert_eq!(code.unwind_op, X86GCEHUnwindOp::PushNonVol);
assert_eq!(code.op_info, 3);
}
#[test]
fn test_add_unwind_alloc_small() {
let mut state = make_default_state();
state.add_unwind_alloc_small(64, 2);
let code = &state.unwind_codes[0];
assert_eq!(code.unwind_op, X86GCEHUnwindOp::AllocSmall);
assert_eq!(code.op_info, 64 / 8 - 1); }
#[test]
fn test_serialize_unwind_info() {
let mut state = make_default_state();
state.add_unwind_push_nonvol(3, 1);
let info = state.build_unwind_info();
let bytes = X86GCEHFull::serialize_unwind_info(&info);
assert!(!bytes.is_empty());
}
#[test]
fn test_build_and_serialize_runtime_function() {
let rf = X86GCEHFull::build_runtime_function(0x1000, 0x1200, 0x2000);
assert_eq!(rf.begin_address, 0x1000);
assert_eq!(rf.end_address, 0x1200);
assert_eq!(rf.unwind_info, 0x2000);
let bytes = X86GCEHFull::serialize_runtime_function(&rf);
assert_eq!(bytes.len(), 12);
}
#[test]
fn test_outline_landing_pads() {
let mut state = make_default_state();
state.lower_landingpad(vec![], true);
state.lower_landingpad(vec![], false);
state.outline_landing_pads();
for pad in &state.landing_pads {
assert!(pad.is_outlined);
}
}
#[test]
fn test_outline_landing_pads_disabled() {
let mut state = make_default_state();
state.outline_landing_pads = false;
state.lower_landingpad(vec![], true);
state.outline_landing_pads();
assert!(!state.landing_pads[0].is_outlined);
}
#[test]
fn test_prune_unreachable_handlers() {
let mut state = make_default_state();
let mut lp1 = state.lower_landingpad(vec![], true);
lp1.is_unreachable = true;
state.landing_pads[0].is_unreachable = true;
let pruned = state.prune_unreachable_handlers();
assert_eq!(pruned.len(), 1);
}
#[test]
fn test_is_catch_dead() {
let state = make_default_state();
assert!(!state.is_catch_dead("std::bad_alloc"));
assert!(!state.is_catch_dead("int"));
}
#[test]
fn test_merge_identical_landing_pads() {
let mut state = make_default_state();
let pad1 = state.lower_cleanuppad();
let pad2 = state.lower_cleanuppad();
let merged = state.merge_identical_landing_pads();
assert_eq!(merged, 1);
assert_eq!(state.landing_pads.len(), 1);
assert!(state.landing_pads[0].merged_from.contains(&pad2.id));
}
#[test]
fn test_merge_identical_landing_pads_no_merge() {
let mut state = make_default_state();
let lp1 = state.lower_landingpad(vec![], true);
let ti = X86GCEHTypeInfoEntry {
encoding: 0,
type_info_ptr: 0,
type_name: "int".to_string(),
};
let lp2 = state.lower_landingpad(
vec![X86GCEHClause {
kind: X86GCEHClauseKind::Catch,
type_info: Some(ti),
filter_fn: None,
handler: None,
is_catch_all: false,
}],
false,
);
let merged = state.merge_identical_landing_pads();
assert_eq!(merged, 0);
assert_eq!(state.landing_pads.len(), 2);
}
#[test]
fn test_merge_landing_pads_disabled() {
let mut state = make_default_state();
state.merge_landing_pads = false;
state.lower_cleanuppad();
state.lower_cleanuppad();
let merged = state.merge_identical_landing_pads();
assert_eq!(merged, 0);
}
#[test]
fn test_remove_dead_catches() {
let mut state = make_default_state();
let ti = X86GCEHTypeInfoEntry {
encoding: 0,
type_info_ptr: 0,
type_name: "std::bad_alloc".to_string(),
};
state.lower_landingpad(
vec![X86GCEHClause {
kind: X86GCEHClauseKind::Catch,
type_info: Some(ti),
filter_fn: None,
handler: None,
is_catch_all: false,
}],
false,
);
let removed = state.remove_dead_catches();
assert_eq!(removed, 0);
}
#[test]
fn test_deduplicate_call_sites() {
let mut state = make_default_state();
state.call_site_table.push(X86GCEHCallSiteEntry {
cs_start: 0,
cs_length: 4,
cs_lp: 1,
cs_action: 0,
});
state.call_site_table.push(X86GCEHCallSiteEntry {
cs_start: 4,
cs_length: 4,
cs_lp: 1,
cs_action: 0,
});
state.call_site_table.push(X86GCEHCallSiteEntry {
cs_start: 8,
cs_length: 4,
cs_lp: 2,
cs_action: 1,
});
assert_eq!(state.call_site_table.len(), 3);
state.deduplicate_call_sites();
assert_eq!(state.call_site_table.len(), 2);
assert_eq!(state.call_site_table[0].cs_length, 8);
}
#[test]
fn test_personality_symbol_names() {
assert_eq!(
X86GCEHPersonality::GxxV0.symbol_name(),
"__gxx_personality_v0"
);
assert_eq!(
X86GCEHPersonality::GccV0.symbol_name(),
"__gcc_personality_v0"
);
assert_eq!(
X86GCEHPersonality::CxxFrameHandler3.symbol_name(),
"__CxxFrameHandler3"
);
assert_eq!(
X86GCEHPersonality::CSpecificHandler.symbol_name(),
"__C_specific_handler"
);
assert_eq!(
X86GCEHPersonality::ObjCV0.symbol_name(),
"__objc_personality_v0"
);
assert_eq!(
X86GCEHPersonality::GxxSeh0.symbol_name(),
"__gxx_personality_seh0"
);
assert_eq!(X86GCEHPersonality::None.symbol_name(), "");
}
#[test]
fn test_is_itanium() {
assert!(X86GCEHPersonality::GxxV0.is_itanium());
assert!(X86GCEHPersonality::GccV0.is_itanium());
assert!(X86GCEHPersonality::ObjCV0.is_itanium());
assert!(!X86GCEHPersonality::CxxFrameHandler3.is_itanium());
assert!(!X86GCEHPersonality::CSpecificHandler.is_itanium());
}
#[test]
fn test_is_seh() {
assert!(X86GCEHPersonality::CxxFrameHandler3.is_seh());
assert!(X86GCEHPersonality::CSpecificHandler.is_seh());
assert!(X86GCEHPersonality::GxxSeh0.is_seh());
assert!(!X86GCEHPersonality::GxxV0.is_seh());
}
#[test]
fn test_personality_gxx_v0() {
let mut state = X86GCEHFull::default();
state.personality_gxx_v0();
assert_eq!(state.personality, X86GCEHPersonality::GxxV0);
assert!(state.emit_dwarf_eh);
assert!(!state.emit_seh);
assert!(state.personality_refs.contains("__gxx_personality_v0"));
}
#[test]
fn test_personality_gcc_v0() {
let mut state = X86GCEHFull::default();
state.personality_gcc_v0();
assert_eq!(state.personality, X86GCEHPersonality::GccV0);
}
#[test]
fn test_personality_cxx_frame_handler3() {
let mut state = X86GCEHFull::default();
state.personality_cxx_frame_handler3();
assert_eq!(state.personality, X86GCEHPersonality::CxxFrameHandler3);
assert!(state.emit_seh);
}
#[test]
fn test_personality_c_specific_handler() {
let mut state = X86GCEHFull::default();
state.personality_c_specific_handler();
assert_eq!(state.personality, X86GCEHPersonality::CSpecificHandler);
}
#[test]
fn test_personality_objc_v0() {
let mut state = X86GCEHFull::default();
state.personality_objc_v0();
assert_eq!(state.personality, X86GCEHPersonality::ObjCV0);
}
#[test]
fn test_personality_gxx_seh0() {
let mut state = X86GCEHFull::default();
state.personality_gxx_seh0();
assert_eq!(state.personality, X86GCEHPersonality::GxxSeh0);
assert!(state.is_mingw);
assert!(state.emit_seh);
}
#[test]
fn test_get_personality_config_gxx() {
let mut state = X86GCEHFull::default();
state.personality_gxx_v0();
let cfg = state.get_personality_config();
assert_eq!(cfg.exc_ptr_reg, DW_RAX);
assert_eq!(cfg.selector_reg, DW_RDX);
assert!(cfg.handles_foreign);
assert!(cfg.uses_two_phase);
assert!(!cfg.cleanups_in_phase1);
}
#[test]
fn test_get_personality_config_cxxframe() {
let mut state = X86GCEHFull::default();
state.personality_cxx_frame_handler3();
let cfg = state.get_personality_config();
assert!(cfg.cleanups_in_phase1);
assert!(cfg.handles_foreign);
}
#[test]
fn test_get_eh_return_data_regno() {
assert_eq!(X86GCEHFull::get_eh_return_data_regno(0), Some(DW_RAX));
assert_eq!(X86GCEHFull::get_eh_return_data_regno(1), Some(DW_RDX));
assert_eq!(X86GCEHFull::get_eh_return_data_regno(2), None);
assert_eq!(X86GCEHFull::get_eh_return_data_regno(42), None);
}
#[test]
fn test_extract_exception_pointer() {
let mut state = X86GCEHFull::default();
state.personality_gxx_v0();
assert_eq!(state.extract_exception_pointer(), DW_RAX);
}
#[test]
fn test_extract_selector() {
let mut state = X86GCEHFull::default();
state.personality_gxx_v0();
assert_eq!(state.extract_selector(), DW_RDX);
}
#[test]
fn test_lower_eh_extract_values() {
let state = X86GCEHFull::default();
let (exc_ptr, selector) = state.lower_eh_extract_values(0);
assert!(matches!(
exc_ptr.location,
X86GCEHRootLocation::Register(DW_RAX)
));
assert!(matches!(
selector.location,
X86GCEHRootLocation::Register(DW_RDX)
));
}
#[test]
fn test_build_stack_map_record() {
let state = make_default_state();
let roots = vec![X86GCEHRoot {
kind: X86GCEHRootKind::ObjectPointer,
location: X86GCEHRootLocation::Register(DW_RAX),
is_derived: false,
derived_offset: 0,
is_live: true,
description: "rax".to_string(),
}];
let stack_slots = vec![X86GCEHStackSlot {
offset: -8,
size: 8,
is_derived: false,
is_live: true,
name: "slot1".to_string(),
}];
let record = state.build_stack_map_record(1, 0x100, &roots, &stack_slots);
assert_eq!(record.id, 1);
assert_eq!(record.num_locations, 2);
assert_eq!(record.num_live_outs, 1);
assert_eq!(record.live_outs[0].dwarf_reg, DW_RAX);
}
#[test]
fn test_emit_function_stack_map() {
let mut state = make_default_state();
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
state.lower_gc_statepoint(Some("bar"), 0, &[], X86GCEHGCTransition::Relocation, &[], 0);
state.emit_function_stack_map();
assert!(!state.stack_map_buffer.is_empty());
}
#[test]
fn test_finalize_stack_map_section() {
let mut state = make_default_state();
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
state.lower_gc_statepoint(None, 0, &[], X86GCEHGCTransition::Safepoint, &[], 0);
state.emit_function_stack_map();
let section = state.finalize_stack_map_section();
assert_eq!(section[0], 3); assert_eq!(section[1], 0); assert_eq!(section[2], 0); assert_eq!(section[3], 0); }
#[test]
fn test_lower_llvm_patchpoint() {
let mut state = make_default_state();
let pp = state.lower_llvm_patchpoint(0, 16, 2, 0x5000, 2, 0x1234);
assert_eq!(pp.flags, 0);
assert_eq!(pp.num_bytes, 16);
assert_eq!(pp.num_args, 2);
assert_eq!(pp.callee, 0x5000);
assert_eq!(pp.num_patch_args, 2);
assert!(pp.stack_map_id.is_some());
}
#[test]
fn test_build_patchpoint_nop_sled() {
let sled = X86GCEHFull::build_patchpoint_nop_sled(16);
assert_eq!(sled.len(), 16);
for &byte in &sled {
assert!(
byte == 0x90
|| byte == 0x66
|| byte == 0x0F
|| byte == 0x1F
|| byte == 0x00
|| byte == 0x40
|| byte == 0x44
|| byte == 0x80
|| byte == 0x84,
"unexpected nop byte: 0x{:02X}",
byte
);
}
}
#[test]
fn test_build_patchpoint_nop_sled_odd_size() {
let sled = X86GCEHFull::build_patchpoint_nop_sled(7);
assert_eq!(sled.len(), 7);
}
#[test]
fn test_build_patchpoint_nop_sled_small() {
let sled = X86GCEHFull::build_patchpoint_nop_sled(2);
assert_eq!(sled.len(), 2);
assert_eq!(sled, vec![0x66, 0x90]);
}
#[test]
fn test_build_lsda() {
let mut state = make_default_state();
state.lower_landingpad(vec![], true);
let lsda = state.build_lsda();
assert!(lsda.gcc_except_table);
}
#[test]
fn test_encode_lsda() {
let mut state = make_default_state();
state.lower_landingpad(vec![], true);
let encoded = state.encode_lsda();
assert!(!encoded.is_empty());
assert_eq!(encoded[0], 0xFF);
assert_eq!(encoded[1], 0xFF);
}
#[test]
fn test_uleb128_zero() {
assert_eq!(uleb128(0), vec![0]);
}
#[test]
fn test_uleb128_small() {
assert_eq!(uleb128(1), vec![1]);
assert_eq!(uleb128(127), vec![127]);
}
#[test]
fn test_uleb128_large() {
assert_eq!(uleb128(128), vec![0x80, 0x01]);
assert_eq!(uleb128(16383), vec![0xFF, 0x7F]);
}
#[test]
fn test_sleb128_zero() {
assert_eq!(sleb128(0), vec![0]);
}
#[test]
fn test_sleb128_positive() {
assert_eq!(sleb128(1), vec![1]);
assert_eq!(sleb128(64), vec![0xC0, 0x00]);
}
#[test]
fn test_sleb128_negative() {
assert_eq!(sleb128(-1), vec![0x7F]);
assert_eq!(sleb128(-64), vec![0x40]);
}
#[test]
fn test_decode_uleb128() {
let encoded = uleb128(300);
let (value, consumed) = decode_uleb128(&encoded).unwrap();
assert_eq!(value, 300);
assert_eq!(consumed, encoded.len());
}
#[test]
fn test_decode_uleb128_zero() {
let (value, consumed) = decode_uleb128(&[0]).unwrap();
assert_eq!(value, 0);
assert_eq!(consumed, 1);
}
#[test]
fn test_decode_uleb128_invalid() {
let overflow = [0x80u8; 20];
assert!(decode_uleb128(&overflow).is_none());
}
#[test]
fn test_compute_cfa() {
let cfa = compute_cfa(0x7FFFFFFF0, 0x40);
assert_eq!(cfa, 0x7FFFFFFF0 + 0x40);
}
#[test]
fn test_compute_return_address_location() {
let cfa = 0x7FFFFFFF0;
let ra = compute_return_address_location(cfa);
assert_eq!(ra, cfa - 8);
}
#[test]
fn test_call_may_trigger_gc_statepoint() {
let state = make_linux_gc_state();
assert!(state.call_may_trigger_gc("malloc"));
}
#[test]
fn test_call_may_trigger_gc_leaf() {
let state = make_linux_gc_state();
assert!(!state.call_may_trigger_gc("memcpy"));
}
#[test]
fn test_call_may_trigger_gc_coreclr() {
let state = X86GCEHFullBuilder::new()
.gc_strategy(X86GCEHStrategy::CoreCLR)
.build();
let mut s = state;
s.uses_gc = true;
assert!(s.call_may_trigger_gc("JIT_New"));
assert!(!s.call_may_trigger_gc("printf"));
}
#[test]
fn test_call_may_trigger_gc_no_gc() {
let mut state = X86GCEHFull::default();
state.uses_gc = false;
assert!(!state.call_may_trigger_gc("malloc"));
}
#[test]
fn test_builder_defaults() {
let state = X86GCEHFullBuilder::new().build();
assert_eq!(state.gc_strategy, X86GCEHStrategy::Statepoint);
assert_eq!(state.personality, X86GCEHPersonality::GxxV0);
}
#[test]
fn test_builder_custom() {
let state = X86GCEHFullBuilder::new()
.gc_strategy(X86GCEHStrategy::OCaml)
.personality(X86GCEHPersonality::ObjCV0)
.target_triple("x86_64-apple-darwin")
.safepoints_at_backedges(false)
.safepoints_at_calls(false)
.emit_dwarf_eh(true)
.emit_seh(false)
.outline_landing_pads(false)
.merge_landing_pads(false)
.prune_unreachable_handlers(false)
.build();
assert_eq!(state.gc_strategy, X86GCEHStrategy::OCaml);
assert_eq!(state.personality, X86GCEHPersonality::ObjCV0);
assert_eq!(state.target_triple, "x86_64-apple-darwin");
assert!(!state.safepoints_at_backedges);
assert!(!state.safepoints_at_calls);
assert!(!state.outline_landing_pads);
assert!(!state.merge_landing_pads);
assert!(!state.prune_unreachable_handlers);
}
#[test]
fn test_run_pipeline_gc_only() {
let mut state = X86GCEHFullBuilder::new()
.gc_strategy(X86GCEHStrategy::Statepoint)
.personality(X86GCEHPersonality::None)
.build();
state.begin_function("gc_test", true, false);
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
state.run_pipeline();
}
#[test]
fn test_run_pipeline_eh_only() {
let mut state = X86GCEHFullBuilder::new()
.gc_strategy(X86GCEHStrategy::None)
.personality(X86GCEHPersonality::GxxV0)
.build();
state.begin_function("eh_test", false, true);
state.lower_landingpad(vec![], true);
state.run_pipeline();
}
#[test]
fn test_run_pipeline_both() {
let mut state = make_linux_gc_state();
state.begin_function("full_test", true, true);
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
state.lower_landingpad(vec![], true);
state.run_pipeline();
}
#[test]
fn test_summary() {
let mut state = make_default_state();
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
let summary = state.summary();
assert!(summary.contains("X86GCEHFull"));
assert!(summary.contains("test_fn"));
assert!(summary.contains("Statepoint"));
assert!(summary.contains("__gxx_personality_v0"));
}
#[test]
fn test_validate_ok() {
let mut state = make_default_state();
state.uses_gc = true;
state.gc_strategy = X86GCEHStrategy::Statepoint;
state.uses_eh = true;
state.personality = X86GCEHPersonality::GxxV0;
assert!(state.validate().is_ok());
}
#[test]
fn test_validate_no_gc_strategy() {
let mut state = make_default_state();
state.uses_gc = true;
state.gc_strategy = X86GCEHStrategy::None;
let result = state.validate();
assert!(result.is_err());
assert!(result.unwrap_err().contains("no GC strategy"));
}
#[test]
fn test_validate_no_personality() {
let mut state = make_default_state();
state.uses_eh = true;
state.personality = X86GCEHPersonality::None;
let result = state.validate();
assert!(result.is_err());
assert!(result.unwrap_err().contains("no personality"));
}
#[test]
fn test_display_x86_full() {
let state = make_default_state();
let s = format!("{}", state);
assert!(s.contains("X86GCEHFull"));
assert!(s.contains("test_fn"));
assert!(s.contains("Statepoint"));
}
#[test]
fn test_display_landing_pad() {
let lp = X86GCEHLandingPad {
id: 1,
kind: X86GCEHLandingPadKind::LandingPad,
exc_ptr_reg: DW_RAX,
filter_ptr_reg: DW_RDX,
clauses: vec![],
is_cleanup: true,
is_unreachable: false,
is_outlined: false,
call_site_entries: vec![],
merged_from: vec![],
};
let s = format!("{}", lp);
assert!(s.contains("LandingPad"));
assert!(s.contains("cleanup=true"));
}
#[test]
fn test_display_statepoint() {
let sp = X86GCEHStatepoint {
id: 42,
pc_offset: 0,
stack_map_id: 7,
call_target: Some("foo".to_string()),
num_call_args: 3,
flags: 0,
num_deopt_bytes: 0,
deopt_state: vec![],
gc_transition: X86GCEHGCTransition::Relocation,
num_roots: 5,
roots: vec![],
stack_slots: vec![],
};
let s = format!("{}", sp);
assert!(s.contains("Statepoint"));
assert!(s.contains("42"));
}
#[test]
fn test_display_stack_map() {
let sm = X86GCEHStackMapRecord {
id: 1,
function_address: 0,
instruction_offset: 0,
num_locations: 10,
locations: vec![],
num_live_outs: 3,
live_outs: vec![],
padding: 0,
};
let s = format!("{}", sm);
assert!(s.contains("StackMap"));
assert!(s.contains("locs=10"));
}
#[test]
fn test_gc_strategy_display() {
assert_eq!(
format!("{}", X86GCEHStrategy::Statepoint),
"statepoint-example"
);
assert_eq!(format!("{}", X86GCEHStrategy::CoreCLR), "coreclr");
assert_eq!(format!("{}", X86GCEHStrategy::OCaml), "ocaml");
assert_eq!(format!("{}", X86GCEHStrategy::Erlang), "erlang");
assert_eq!(format!("{}", X86GCEHStrategy::ShadowStack), "shadow-stack");
assert_eq!(format!("{}", X86GCEHStrategy::Custom), "custom");
assert_eq!(format!("{}", X86GCEHStrategy::None), "none");
}
#[test]
fn test_safepoint_kind_display() {
assert_eq!(format!("{}", X86GCEHSafepointKind::PreCall), "pre-call");
assert_eq!(format!("{}", X86GCEHSafepointKind::PostCall), "post-call");
assert_eq!(
format!("{}", X86GCEHSafepointKind::LoopBackedge),
"loop-backedge"
);
assert_eq!(format!("{}", X86GCEHSafepointKind::FunctionEntry), "entry");
assert_eq!(
format!("{}", X86GCEHSafepointKind::FunctionReturn),
"return"
);
assert_eq!(format!("{}", X86GCEHSafepointKind::Poll), "poll");
assert_eq!(
format!("{}", X86GCEHSafepointKind::MachineSafepoint),
"machine"
);
}
#[test]
fn test_root_location_display() {
assert_eq!(
format!("{}", X86GCEHRootLocation::Register(DW_RAX)),
"reg(D0)"
);
assert_eq!(format!("{}", X86GCEHRootLocation::StackOffset(-8)), "bp-8");
assert_eq!(
format!("{}", X86GCEHRootLocation::StackSPOffset(16)),
"sp+16"
);
assert_eq!(
format!("{}", X86GCEHRootLocation::FixedAddress(0xDEADBEEF)),
"addr(0xdeadbeef)"
);
assert_eq!(format!("{}", X86GCEHRootLocation::Unknown), "unknown");
}
#[test]
fn test_augmentation_parse_full() {
let aug = X86GCEHAugmentation::parse("zLPRSBG");
assert!(aug.has_aug_length);
assert!(aug.has_lsda);
assert!(aug.has_personality);
assert!(aug.has_fde_encoding);
assert!(aug.is_signal_frame);
assert!(aug.is_bfd_cie);
assert!(aug.is_gnu);
}
#[test]
fn test_augmentation_parse_empty() {
let aug = X86GCEHAugmentation::parse("");
assert!(!aug.has_aug_length);
assert!(!aug.has_lsda);
assert!(!aug.has_personality);
}
#[test]
fn test_augmentation_parse_unknown() {
let aug = X86GCEHAugmentation::parse("zRxy");
assert!(aug.has_aug_length);
assert!(aug.has_fde_encoding);
assert!(!aug.has_lsda);
}
#[test]
fn test_augmentation_to_string() {
let aug = X86GCEHAugmentation {
has_aug_length: true,
has_lsda: true,
has_personality: true,
has_fde_encoding: true,
is_signal_frame: false,
is_bfd_cie: false,
is_gnu: false,
};
assert_eq!(aug.to_string(), "zLPR");
}
#[test]
fn test_unwind_op_from_u8() {
assert_eq!(
X86GCEHUnwindOp::from_u8(0),
Some(X86GCEHUnwindOp::PushNonVol)
);
assert_eq!(X86GCEHUnwindOp::from_u8(3), Some(X86GCEHUnwindOp::SetFPReg));
assert_eq!(
X86GCEHUnwindOp::from_u8(10),
Some(X86GCEHUnwindOp::PushMachFrame)
);
assert_eq!(
X86GCEHUnwindOp::from_u8(0x1E),
Some(X86GCEHUnwindOp::SetMxcsr)
);
assert_eq!(X86GCEHUnwindOp::from_u8(99), None);
}
#[test]
fn test_empty_function() {
let mut state = make_default_state();
state.run_pipeline();
assert!(state.stack_maps.is_empty());
assert!(state.landing_pads.is_empty());
}
#[test]
fn test_many_roots() {
let mut state = make_default_state();
for i in 0..100 {
let reg = (DW_RAX + i as u16) % 16;
state.register_root(
reg,
X86GCEHRootKind::ObjectPointer,
false,
0,
&format!("root_{}", i),
);
}
let roots = state.enumerate_live_roots();
assert_eq!(roots.len(), 100);
}
#[test]
fn test_many_safepoints() {
let mut state = make_default_state();
for i in 0..50 {
state.insert_loop_backedge_safepoint(i * 10);
state.insert_pre_call_safepoint(i * 10 + 5);
}
assert_eq!(state.safepoints.len(), 100);
}
#[test]
fn test_many_landing_pads() {
let mut state = make_default_state();
for i in 0..20 {
state.lower_landingpad(vec![], true);
}
assert_eq!(state.landing_pads.len(), 20);
}
#[test]
fn test_replace_with_relocated() {
let mut state = make_default_state();
let reloc = state.replace_with_relocated(DW_RAX, 42);
assert_eq!(reloc.statepoint_id, 42);
assert_eq!(reloc.base_offset, DW_RAX as u32);
}
#[test]
fn test_gc_transition_enum() {
assert_eq!(X86GCEHGCTransition::None as u32, 0); let _ = X86GCEHGCTransition::None;
let _ = X86GCEHGCTransition::Safepoint;
let _ = X86GCEHGCTransition::Relocation;
let _ = X86GCEHGCTransition::Deopt;
}
#[test]
fn test_suspension_enum() {
assert_eq!(
format!("{}", X86GCEHSuspension::default()),
"polling" );
}
#[test]
fn test_call_compute_gc_roots() {
let mut state = make_default_state();
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "rax");
state.register_root(DW_RDI, X86GCEHRootKind::ObjectPointer, false, 0, "rdi");
let roots = state.compute_call_gc_roots("malloc");
assert_eq!(roots.len(), 2);
}
#[test]
fn test_exception_pointer_regs_x86_32() {
let regs = X86GCEHExceptionPointerRegs::for_x86_32();
assert_eq!(regs.exception_pointer_reg, 0);
assert_eq!(regs.selector_reg, 1);
}
#[test]
fn test_exception_pointer_regs_x86_64() {
let regs = X86GCEHExceptionPointerRegs::for_x86_64();
assert_eq!(regs.exception_pointer_reg, DW_RAX);
assert_eq!(regs.selector_reg, DW_RDX);
}
#[test]
fn test_full_gc_eh_pipeline_integration() {
let mut state = make_linux_gc_state();
state.begin_function("integrated_test", true, true);
state.register_root(DW_RAX, X86GCEHRootKind::ObjectPointer, false, 0, "obj_ptr");
state.register_root(
DW_R12,
X86GCEHRootKind::DerivedPointer,
true,
8,
"derived_ptr",
);
state.register_stack_root(-8, 8, false, "local_root");
state.register_stack_root(-16, 8, true, "local_derived");
let _sp = state.lower_gc_statepoint(
Some("allocate"),
2,
&[],
X86GCEHGCTransition::Relocation,
&[0xAA, 0xBB],
0x01,
);
state.insert_entry_safepoint(0);
state.insert_loop_backedge_safepoint(0x40);
state.insert_pre_call_safepoint(0x80);
state.personality_gxx_v0();
let ti_std = X86GCEHTypeInfoEntry {
encoding: 0,
type_info_ptr: 0xDEAD,
type_name: "std::exception".to_string(),
};
let _lp = state.lower_landingpad(
vec![
X86GCEHClause {
kind: X86GCEHClauseKind::Catch,
type_info: Some(ti_std),
filter_fn: None,
handler: Some("catch_h".to_string()),
is_catch_all: false,
},
X86GCEHClause {
kind: X86GCEHClauseKind::Cleanup,
type_info: None,
filter_fn: None,
handler: None,
is_catch_all: false,
},
],
true,
);
state.outline_landing_pads();
state.deduplicate_call_sites();
let _pruned = state.prune_unreachable_handlers();
assert!(state.validate().is_ok());
}
#[test]
fn test_windows_full_pipeline() {
let mut state = make_windows_eh_state();
state.begin_function("windows_test", false, true);
state.configure_cxx_frame_handler3();
state.add_seh_scope(0x100, 0x200, Some("__except_filter"), 0x300);
state.add_seh_scope(0x240, 0x2A0, None, 0x350);
let _table = state.emit_seh_scope_table();
state.add_unwind_push_nonvol(DW_RBP as u8, 1);
state.add_unwind_alloc_small(32, 2);
state.add_unwind_set_fp(3);
let unwind_info = state.build_unwind_info();
assert_eq!(unwind_info.count_of_codes, 3);
let serialized = X86GCEHFull::serialize_unwind_info(&unwind_info);
assert!(!serialized.is_empty());
state.emit_pdata_entry(0x1000, 0x1200, 0x2000);
assert!(!state.pdata_buffer.is_empty());
}
#[test]
fn test_dwarf_full_pipeline() {
let mut state = make_linux_gc_state();
state.begin_function("dwarf_test", false, true);
state.personality_gxx_v0();
state.lower_landingpad(vec![], true);
let _cie = state.build_dwarf_cie();
state.emit_dwarf_eh_frame();
let fde = state.build_dwarf_fde(0x400000, 0x200, None);
let fde_bytes = X86GCEHFull::serialize_fde(&fde);
assert!(!fde_bytes.is_empty());
let _lsda_bytes = state.encode_lsda();
}
}