#![allow(non_upper_case_globals, dead_code)]
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
use std::fmt;
use std::mem;
use crate::codegen::MachineInstr;
pub const X86_GC_LOWERING_MAX_ROOTS: usize = 1024;
pub const X86_GC_LOWERING_MAX_SAFEPOINTS: usize = 2048;
pub const X86_GC_LOWERING_MAX_DERIVED_PTRS: usize = 128;
pub const X86_GC_LOWERING_CARD_SIZE: u32 = 512;
pub const X86_GC_LOWERING_REMEMBERED_LOG_SIZE: usize = 10;
pub const X86_GC_LOWERING_STACK_MAP_VERSION: u8 = 3;
pub const X86_GC_LOWERING_STACK_MAP_SECTION: &str = ".llvm_stackmaps";
pub const X86_GC_LOWERING_MAX_PATCH_SIZE: u32 = 64;
pub const X86_GC_LOWERING_POLLING_PAGE_ADDR: u64 = 0x7FFF_FFFF_FFFF_F000;
pub const X86_GC_LOWERING_POLLING_PAGE_SIZE: u64 = 4096;
pub const X86_GC_LOWERING_MAX_LIVE_STATE: usize = 256;
pub const X86_GC_LOWERING_FRAME_ALIGNMENT: u32 = 8;
pub const X86_GC_LOWERING_RET_ADDR_OFFSET: i32 = 8;
pub const X86_GC_LOWERING_RECORD_HEADER_SIZE: u32 = 8;
pub const X86_GC_LOWERING_MAX_LOCATIONS: u16 = 64;
const DW_REG_RBX: u16 = 3;
const DW_REG_RBP: u16 = 6;
const DW_REG_R12: u16 = 12;
const DW_REG_R13: u16 = 13;
const DW_REG_R14: u16 = 14;
const DW_REG_R15: u16 = 15;
const DW_REG_RAX: u16 = 0;
const DW_REG_RCX: u16 = 2;
const DW_REG_RDX: u16 = 1;
const DW_REG_RSI: u16 = 4;
const DW_REG_RDI: u16 = 5;
const DW_REG_R8: u16 = 8;
const DW_REG_R9: u16 = 9;
const DW_REG_R10: u16 = 10;
const DW_REG_R11: u16 = 11;
const GC_POINTER_REGS: &[u16] = &[
DW_REG_RAX, DW_REG_RBX, DW_REG_RCX, DW_REG_RDX, DW_REG_RSI, DW_REG_RDI, DW_REG_R8, DW_REG_R9,
DW_REG_R10, DW_REG_R11, DW_REG_R12, DW_REG_R13, DW_REG_R14, DW_REG_R15, DW_REG_RBP,
];
const CALLEE_SAVED_GC_REGS: &[u16] = &[
DW_REG_RBX, DW_REG_RBP, DW_REG_R12, DW_REG_R13, DW_REG_R14, DW_REG_R15,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86GCStrategyKind {
StatepointExample,
CoreCLR,
OCaml,
Erlang,
ShadowStack,
}
impl fmt::Display for X86GCStrategyKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::StatepointExample => 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"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86GCRootKind {
BasePointer,
DerivedPointer,
StackSlot,
Register,
StaticRoot,
ThreadLocal,
CoroutineFrame,
}
#[derive(Debug, Clone)]
pub struct X86GCRoot {
pub root_id: u32,
pub kind: X86GCRootKind,
pub frame_offset: i32,
pub dwarf_reg: Option<u16>,
pub pointer_size: u32,
pub derived_offset: Option<i64>,
pub is_live: bool,
pub type_metadata: Option<u64>,
}
impl X86GCRoot {
pub fn new_base(root_id: u32, frame_offset: i32) -> Self {
Self {
root_id,
kind: X86GCRootKind::BasePointer,
frame_offset,
dwarf_reg: None,
pointer_size: 8,
derived_offset: None,
is_live: true,
type_metadata: None,
}
}
pub fn new_register(root_id: u32, dwarf_reg: u16) -> Self {
Self {
root_id,
kind: X86GCRootKind::Register,
frame_offset: 0,
dwarf_reg: Some(dwarf_reg),
pointer_size: 8,
derived_offset: None,
is_live: true,
type_metadata: None,
}
}
pub fn new_derived(root_id: u32, frame_offset: i32, derived_offset: i64) -> Self {
Self {
root_id,
kind: X86GCRootKind::DerivedPointer,
frame_offset,
dwarf_reg: None,
pointer_size: 8,
derived_offset: Some(derived_offset),
is_live: true,
type_metadata: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86GCSafepointKind {
Call,
AfterCall,
LoopBackedge,
FunctionEntry,
FunctionExit,
Explicit,
PatchPoint,
Statepoint,
CoroutineSuspend,
}
#[derive(Debug, Clone)]
pub struct X86GCSafepoint {
pub id: u32,
pub kind: X86GCSafepointKind,
pub function_offset: u32,
pub stack_map_id: u64,
pub live_roots: Vec<u32>,
pub derived_pointers: Vec<X86GCDerivedPtr>,
pub num_deopt_args: u16,
pub is_deopt: bool,
pub requires_full_stack_walk: bool,
}
#[derive(Debug, Clone)]
pub struct X86GCDerivedPtr {
pub derived_root_id: u32,
pub base_root_id: u32,
pub offset: i64,
pub from_gep: bool,
pub from_ptr_arith: bool,
pub creator_inst: Option<u64>,
pub in_bounds: bool,
}
#[derive(Debug, Clone)]
pub struct X86GCStackMapHeader {
pub version: u8,
pub _reserved: [u8; 3],
}
#[derive(Debug, Clone)]
pub struct X86GCStackMapFunction {
pub function_address: u64,
pub stack_size: u64,
pub record_count: u32,
}
#[derive(Debug, Clone)]
pub struct X86GCStackMapConstant {
pub value: u64,
}
#[derive(Debug, Clone)]
pub enum X86GCStackMapLocation {
Register {
dwarf_reg: u16,
_reserved: u16,
offset: i32,
size: u16,
},
Direct {
dwarf_reg: u16,
_reserved: u16,
offset: i32,
size: u16,
},
Indirect {
dwarf_reg: u16,
_reserved: u16,
offset: i32,
size: u16,
},
Constant {
value: i32,
},
LargeConstant {
constant_idx: u32,
},
}
#[derive(Debug, Clone)]
pub struct X86GCStackMapRecord {
pub record_id: u64,
pub instruction_offset: u32,
pub num_locations: u16,
pub locations: Vec<X86GCStackMapLocation>,
pub num_live_outs: u16,
pub live_outs: Vec<X86GCStackMapLocation>,
}
#[derive(Debug, Clone)]
pub struct X86GCStackMapSection {
pub header: X86GCStackMapHeader,
pub functions: Vec<X86GCStackMapFunction>,
pub constants: Vec<X86GCStackMapConstant>,
pub records: Vec<Vec<X86GCStackMapRecord>>,
}
impl X86GCStackMapSection {
pub fn new() -> Self {
Self {
header: X86GCStackMapHeader {
version: X86_GC_LOWERING_STACK_MAP_VERSION,
_reserved: [0; 3],
},
functions: Vec::new(),
constants: Vec::new(),
records: Vec::new(),
}
}
pub fn total_records(&self) -> usize {
self.records.iter().map(|r| r.len()).sum()
}
pub fn function_count(&self) -> usize {
self.functions.len()
}
pub fn add_function(
&mut self,
address: u64,
stack_size: u64,
records: Vec<X86GCStackMapRecord>,
) {
let idx = self.functions.len();
self.functions.push(X86GCStackMapFunction {
function_address: address,
stack_size,
record_count: records.len() as u32,
});
self.records.push(records);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86GCWriteBarrierKind {
CardMarking,
RememberedSet,
SATB,
Generational,
None,
}
#[derive(Debug, Clone)]
pub struct X86GCWriteBarrierSeq {
pub kind: X86GCWriteBarrierKind,
pub addr_reg: Option<u16>,
pub value_reg: Option<u16>,
pub card_table_base: Option<u64>,
pub remset_buffer_addr: Option<u64>,
pub is_inline: bool,
pub instruction_count: u32,
pub sequence: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86GCReadBarrierKind {
Brooks,
SATB,
None,
}
#[derive(Debug, Clone)]
pub struct X86GCReadBarrierSeq {
pub kind: X86GCReadBarrierKind,
pub result_reg: Option<u16>,
pub is_inline: bool,
pub instruction_count: u32,
pub sequence: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct X86GCPollingPage {
pub page_address: u64,
pub page_size: u64,
pub physical_page: Option<u64>,
pub is_faulting: bool,
pub polling_reg: u16,
}
impl Default for X86GCPollingPage {
fn default() -> Self {
Self {
page_address: X86_GC_LOWERING_POLLING_PAGE_ADDR,
page_size: X86_GC_LOWERING_POLLING_PAGE_SIZE,
physical_page: None,
is_faulting: false,
polling_reg: DW_REG_R11,
}
}
}
impl X86GCPollingPage {
pub fn generate_poll_sequence(&self) -> Vec<u8> {
let mut seq = Vec::new();
seq.push(0x49); seq.push(0xBB); seq.extend_from_slice(&self.page_address.to_le_bytes());
seq.push(0x41); seq.push(0xF6); seq.push(0x03); seq.push(0x00); seq
}
pub fn enable(&mut self) {
self.is_faulting = false;
self.physical_page = Some(self.page_address);
}
pub fn disable(&mut self) {
self.is_faulting = true;
self.physical_page = None;
}
}
#[derive(Debug, Clone)]
pub struct StatepointExampleConfig {
pub use_deopt: bool,
pub all_calls_are_safepoints: bool,
pub allow_derived_pointers: bool,
pub max_statepoints: usize,
pub emit_stack_map_section: bool,
}
impl Default for StatepointExampleConfig {
fn default() -> Self {
Self {
use_deopt: false,
all_calls_are_safepoints: true,
allow_derived_pointers: true,
max_statepoints: 512,
emit_stack_map_section: true,
}
}
}
pub struct StatepointExampleStrategy {
pub config: StatepointExampleConfig,
roots: HashMap<String, Vec<X86GCRoot>>,
safepoints: HashMap<String, Vec<X86GCSafepoint>>,
stack_map: X86GCStackMapSection,
derived_pointers: Vec<X86GCDerivedPtr>,
}
impl StatepointExampleStrategy {
pub fn new() -> Self {
Self {
config: StatepointExampleConfig::default(),
roots: HashMap::new(),
safepoints: HashMap::new(),
stack_map: X86GCStackMapSection::new(),
derived_pointers: Vec::new(),
}
}
pub fn with_config(config: StatepointExampleConfig) -> Self {
Self {
config,
roots: HashMap::new(),
safepoints: HashMap::new(),
stack_map: X86GCStackMapSection::new(),
derived_pointers: Vec::new(),
}
}
pub fn begin_function(&mut self, name: &str) {
self.roots.insert(name.to_string(), Vec::new());
self.safepoints.insert(name.to_string(), Vec::new());
}
pub fn add_root(&mut self, func_name: &str, root: X86GCRoot) {
if let Some(roots) = self.roots.get_mut(func_name) {
roots.push(root);
}
}
pub fn add_safepoint(&mut self, func_name: &str, offset: u32, kind: X86GCSafepointKind) {
if let Some(sps) = self.safepoints.get_mut(func_name) {
let id = sps.len() as u32;
sps.push(X86GCSafepoint {
id,
kind,
function_offset: offset,
stack_map_id: 0,
live_roots: Vec::new(),
derived_pointers: Vec::new(),
num_deopt_args: 0,
is_deopt: false,
requires_full_stack_walk: true,
});
}
}
pub fn compute_liveness(&mut self, func_name: &str) {
let roots = self.roots.get(func_name).cloned().unwrap_or_default();
let safepoints = self.safepoints.get_mut(func_name);
if safepoints.is_none() {
return;
}
let safepoints = safepoints.unwrap();
for sp in safepoints.iter_mut() {
sp.live_roots.clear();
for root in &roots {
if root.kind != X86GCRootKind::DerivedPointer {
sp.live_roots.push(root.root_id);
}
}
}
}
pub fn end_function(&mut self, func_name: &str, address: u64, stack_size: u64) {
let safepoints = self.safepoints.get(func_name).cloned().unwrap_or_default();
let mut records = Vec::new();
for sp in &safepoints {
let mut locations = Vec::new();
let roots = self.roots.get(func_name).cloned().unwrap_or_default();
for root_id in &sp.live_roots {
if let Some(root) = roots.iter().find(|r| r.root_id == *root_id) {
match root.kind {
X86GCRootKind::BasePointer | X86GCRootKind::StackSlot => {
locations.push(X86GCStackMapLocation::Direct {
dwarf_reg: DW_REG_RBP,
_reserved: 0,
offset: root.frame_offset,
size: root.pointer_size as u16,
});
}
X86GCRootKind::Register => {
if let Some(reg) = root.dwarf_reg {
locations.push(X86GCStackMapLocation::Register {
dwarf_reg: reg,
_reserved: 0,
offset: 0,
size: root.pointer_size as u16,
});
}
}
_ => {}
}
}
}
records.push(X86GCStackMapRecord {
record_id: sp.id as u64,
instruction_offset: sp.function_offset,
num_locations: locations.len() as u16,
locations,
num_live_outs: 0,
live_outs: Vec::new(),
});
}
self.stack_map.add_function(address, stack_size, records);
}
pub fn finalize_stack_maps(&self) -> &X86GCStackMapSection {
&self.stack_map
}
pub fn stack_map_mut(&mut self) -> &mut X86GCStackMapSection {
&mut self.stack_map
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoreCLRInterruptibility {
FullyInterruptible,
PartiallyInterruptible,
NotInterruptible,
}
#[derive(Debug, Clone)]
pub struct CoreCLRCodeRegion {
pub start_offset: u32,
pub end_offset: u32,
pub interruptibility: CoreCLRInterruptibility,
pub slot_changes: Vec<CoreCLRSlotChange>,
}
#[derive(Debug, Clone)]
pub struct CoreCLRSlotChange {
pub offset: u32,
pub is_live: bool,
pub slot_index: u32,
pub slot_flags: CoreCLRSlotFlags,
}
#[derive(Debug, Clone, Copy)]
pub struct CoreCLRSlotFlags {
pub is_pinned: bool,
pub is_byref: bool,
pub is_untracked: bool,
pub is_gc_pointer: bool,
}
impl Default for CoreCLRSlotFlags {
fn default() -> Self {
Self {
is_pinned: false,
is_byref: false,
is_untracked: false,
is_gc_pointer: true,
}
}
}
pub struct CoreCLRStrategy {
code_regions: Vec<CoreCLRCodeRegion>,
slot_map: BTreeMap<u32, X86GCStackMapLocation>,
use_return_address_hijacking: bool,
}
impl CoreCLRStrategy {
pub fn new() -> Self {
Self {
code_regions: Vec::new(),
slot_map: BTreeMap::new(),
use_return_address_hijacking: true,
}
}
pub fn add_region(&mut self, start: u32, end: u32, interruptibility: CoreCLRInterruptibility) {
self.code_regions.push(CoreCLRCodeRegion {
start_offset: start,
end_offset: end,
interruptibility,
slot_changes: Vec::new(),
});
}
pub fn register_slot(&mut self, slot_index: u32, frame_offset: i32) {
self.slot_map.insert(
slot_index,
X86GCStackMapLocation::Direct {
dwarf_reg: DW_REG_RBP,
_reserved: 0,
offset: frame_offset,
size: 8,
},
);
}
pub fn register_slot_in_reg(&mut self, slot_index: u32, dwarf_reg: u16) {
self.slot_map.insert(
slot_index,
X86GCStackMapLocation::Register {
dwarf_reg,
_reserved: 0,
offset: 0,
size: 8,
},
);
}
pub fn add_slot_change(
&mut self,
offset: u32,
slot_index: u32,
is_live: bool,
flags: CoreCLRSlotFlags,
) {
if let Some(region) = self.code_regions.last_mut() {
region.slot_changes.push(CoreCLRSlotChange {
offset,
is_live,
slot_index,
slot_flags: flags,
});
}
}
pub fn encode_gcinfo(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.push(2); buf.push(if self.use_return_address_hijacking {
1
} else {
0
});
buf.push(0);
for region in &self.code_regions {
let interruptibility_byte = match region.interruptibility {
CoreCLRInterruptibility::FullyInterruptible => 0,
CoreCLRInterruptibility::PartiallyInterruptible => 1,
CoreCLRInterruptibility::NotInterruptible => 2,
};
buf.push(interruptibility_byte);
buf.extend_from_slice(®ion.start_offset.to_le_bytes());
buf.extend_from_slice(®ion.end_offset.to_le_bytes());
buf.push(region.slot_changes.len() as u8);
for change in ®ion.slot_changes {
buf.extend_from_slice(&change.offset.to_le_bytes());
buf.push(if change.is_live { 1 } else { 0 });
buf.extend_from_slice(&change.slot_index.to_le_bytes());
let mut flags: u8 = 0;
if change.slot_flags.is_pinned {
flags |= 0x01;
}
if change.slot_flags.is_byref {
flags |= 0x02;
}
if change.slot_flags.is_untracked {
flags |= 0x04;
}
if change.slot_flags.is_gc_pointer {
flags |= 0x08;
}
buf.push(flags);
}
}
buf
}
pub fn generate_hijack_stub(&self) -> Vec<u8> {
vec![
0x50, 0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x48, 0x83, 0xEC,
0x20, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x20, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5A, 0x59,
0x58, 0xC3,
]
}
}
#[derive(Debug, Clone)]
pub struct OCamlFrameDescriptor {
pub ret_addr: u64,
pub frame_size: u16,
pub num_live: u16,
pub live_bitmap: Vec<u64>,
}
pub struct OCamlStrategy {
frame_descriptors: Vec<OCamlFrameDescriptor>,
current_offset: u16,
}
impl OCamlStrategy {
pub fn new() -> Self {
Self {
frame_descriptors: Vec::new(),
current_offset: 0,
}
}
pub fn emit_frame_descriptor(&mut self, ret_addr: u64, frame_size: u16, live_offsets: &[u16]) {
let num_words = (frame_size / 8) as usize;
let bitmap_entries = (num_words + 63) / 64;
let mut bitmap = vec![0u64; bitmap_entries];
for &offset in live_offsets {
let word_idx = (offset / 8) as usize;
if word_idx < num_words {
let entry = word_idx / 64;
let bit = word_idx % 64;
bitmap[entry] |= 1u64 << bit;
}
}
self.frame_descriptors.push(OCamlFrameDescriptor {
ret_addr,
frame_size,
num_live: live_offsets.len() as u16,
live_bitmap: bitmap,
});
}
pub fn encode_frame_table(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&(self.frame_descriptors.len() as u32).to_le_bytes());
for fd in &self.frame_descriptors {
buf.extend_from_slice(&fd.ret_addr.to_le_bytes());
buf.extend_from_slice(&fd.frame_size.to_le_bytes());
buf.extend_from_slice(&fd.num_live.to_le_bytes());
let num_bitmap_entries = fd.live_bitmap.len() as u16;
buf.extend_from_slice(&num_bitmap_entries.to_le_bytes());
for word in &fd.live_bitmap {
buf.extend_from_slice(&word.to_le_bytes());
}
}
buf
}
pub fn find_descriptor(&self, ret_addr: u64) -> Option<&OCamlFrameDescriptor> {
self.frame_descriptors
.iter()
.find(|fd| fd.ret_addr == ret_addr)
}
}
#[derive(Debug, Clone)]
pub struct ErlangFrameDescriptor {
pub cp: u64,
pub num_live_x: u16,
pub num_live_y: u16,
pub reg_bitmap: u64,
}
pub struct ErlangStrategy {
frame_descriptors: Vec<ErlangFrameDescriptor>,
reduction_counter: u64,
reduction_scheduling: bool,
}
impl ErlangStrategy {
pub fn new() -> Self {
Self {
frame_descriptors: Vec::new(),
reduction_counter: 4000,
reduction_scheduling: true,
}
}
pub fn emit_frame(&mut self, cp: u64, num_live_x: u16, num_live_y: u16, reg_bitmap: u64) {
self.frame_descriptors.push(ErlangFrameDescriptor {
cp,
num_live_x,
num_live_y,
reg_bitmap,
});
}
pub fn perform_reduction(&mut self) -> bool {
if self.reduction_counter > 0 {
self.reduction_counter -= 1;
false
} else {
self.reduction_counter = 4000;
true }
}
pub fn generate_reduction_check(&self) -> Vec<u8> {
vec![
0x48, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x84, 0x00, 0x00, 0x00, 0x00, ]
}
pub fn encode_frame_table(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&(self.frame_descriptors.len() as u32).to_le_bytes());
for fd in &self.frame_descriptors {
buf.extend_from_slice(&fd.cp.to_le_bytes());
buf.extend_from_slice(&fd.num_live_x.to_le_bytes());
buf.extend_from_slice(&fd.num_live_y.to_le_bytes());
buf.extend_from_slice(&fd.reg_bitmap.to_le_bytes());
}
buf
}
}
#[derive(Debug, Clone)]
pub struct ShadowStackEntry {
pub pointer: u64,
pub metadata: u64,
}
pub struct ShadowStackStrategy {
shadow_stack: Vec<ShadowStackEntry>,
base_address: u64,
stack_pointer: usize,
frame_bases: Vec<usize>,
}
impl ShadowStackStrategy {
pub fn new(base_address: u64, initial_capacity: usize) -> Self {
Self {
shadow_stack: Vec::with_capacity(initial_capacity),
base_address,
stack_pointer: 0,
frame_bases: Vec::new(),
}
}
pub fn push_frame(&mut self) -> usize {
self.frame_bases.push(self.stack_pointer);
self.frame_bases.len() - 1
}
pub fn pop_frame(&mut self) -> Option<usize> {
let base = self.frame_bases.pop()?;
self.stack_pointer = base;
Some(self.stack_pointer)
}
pub fn push_root(&mut self, pointer: u64, metadata: u64) {
if self.stack_pointer < self.shadow_stack.len() {
self.shadow_stack[self.stack_pointer] = ShadowStackEntry { pointer, metadata };
} else {
self.shadow_stack
.push(ShadowStackEntry { pointer, metadata });
}
self.stack_pointer += 1;
}
pub fn roots_in_current_frame(&self) -> &[ShadowStackEntry] {
if let Some(&base) = self.frame_bases.last() {
&self.shadow_stack[base..self.stack_pointer]
} else {
&[]
}
}
pub fn collect_all_roots(&self) -> Vec<u64> {
self.shadow_stack[..self.stack_pointer]
.iter()
.map(|e| e.pointer)
.collect()
}
pub fn generate_push_root_sequence(&self, root_reg: u16, metadata: u64) -> Vec<u8> {
let offset = (self.stack_pointer * mem::size_of::<ShadowStackEntry>()) as u32;
let mut seq = Vec::new();
seq.push(0x48); seq.push(0x89); let modrm: u8 = (0x04u16 | ((root_reg & 0x07) << 3)) as u8;
seq.push(modrm);
seq.push(0x25);
let addr = self.base_address + offset as u64;
seq.extend_from_slice(&(addr as u32).to_le_bytes());
seq
}
pub fn generate_pop_frame_sequence(&self) -> Vec<u8> {
let base = self.frame_bases.last().copied().unwrap_or(0);
let new_sp = (base * mem::size_of::<ShadowStackEntry>()) as u32;
let mut seq = Vec::new();
seq
}
}
pub struct X86GCBarrierLowering {
card_table_base: u64,
emit_inline: bool,
}
impl X86GCBarrierLowering {
pub fn new(card_table_base: u64) -> Self {
Self {
card_table_base,
emit_inline: true,
}
}
pub fn lower_card_mark(&self, addr_reg: u16, scratch_reg: u16) -> X86GCWriteBarrierSeq {
let card_shift = 9; let mut seq = X86GCWriteBarrierSeq {
kind: X86GCWriteBarrierKind::CardMarking,
addr_reg: Some(addr_reg),
value_reg: None,
card_table_base: Some(self.card_table_base),
remset_buffer_addr: None,
is_inline: self.emit_inline,
instruction_count: 3,
sequence: Vec::new(),
};
seq.sequence
.push(format!("shr r{}, {}", addr_reg, card_shift));
seq.sequence.push(format!(
"mov byte [0x{:x} + r{}], 0",
self.card_table_base, addr_reg
));
seq
}
pub fn lower_remembered_set(&self, addr_reg: u16, buffer_addr: u64) -> X86GCWriteBarrierSeq {
let mut seq = X86GCWriteBarrierSeq {
kind: X86GCWriteBarrierKind::RememberedSet,
addr_reg: Some(addr_reg),
value_reg: None,
card_table_base: None,
remset_buffer_addr: Some(buffer_addr),
is_inline: self.emit_inline,
instruction_count: 2,
sequence: Vec::new(),
};
seq.sequence.push(format!(
"mov [0x{:x} + r{}], r{}",
buffer_addr, addr_reg, addr_reg
));
seq
}
pub fn lower_satb(
&self,
addr_reg: u16,
old_val_reg: u16,
marking_flag_addr: u64,
buffer_ptr_addr: u64,
) -> X86GCWriteBarrierSeq {
let mut seq = X86GCWriteBarrierSeq {
kind: X86GCWriteBarrierKind::SATB,
addr_reg: Some(addr_reg),
value_reg: Some(old_val_reg),
card_table_base: None,
remset_buffer_addr: None,
is_inline: self.emit_inline,
instruction_count: 5,
sequence: Vec::new(),
};
seq.sequence
.push(format!("cmp byte [0x{:x}], 0", marking_flag_addr));
seq.sequence.push("je .Lskip_satb".to_string());
seq.sequence
.push(format!("mov r{}, [r{}]", old_val_reg, addr_reg));
seq.sequence
.push(format!("mov [r{}], r{}", buffer_ptr_addr, old_val_reg));
seq.sequence.push(".Lskip_satb:".to_string());
seq
}
pub fn lower_brooks_read_barrier(&self, result_reg: u16) -> X86GCReadBarrierSeq {
let mut seq = X86GCReadBarrierSeq {
kind: X86GCReadBarrierKind::Brooks,
result_reg: Some(result_reg),
is_inline: self.emit_inline,
instruction_count: 1,
sequence: Vec::new(),
};
seq.sequence
.push(format!("mov r{}, [r{}]", result_reg, result_reg));
seq
}
}
#[derive(Debug, Clone)]
pub struct X86GCSafepointInsertionConfig {
pub at_calls: bool,
pub at_loop_backedges: bool,
pub at_function_entry: bool,
pub at_function_exit: bool,
pub max_distance: u32,
pub polling_page: u64,
}
impl Default for X86GCSafepointInsertionConfig {
fn default() -> Self {
Self {
at_calls: true,
at_loop_backedges: true,
at_function_entry: false,
at_function_exit: false,
max_distance: 4096,
polling_page: X86_GC_LOWERING_POLLING_PAGE_ADDR,
}
}
}
pub struct X86GCSafepointInsertion {
pub config: X86GCSafepointInsertionConfig,
pub polling_page: X86GCPollingPage,
inserted_safepoints: HashMap<String, Vec<X86GCSafepoint>>,
}
impl X86GCSafepointInsertion {
pub fn new(config: X86GCSafepointInsertionConfig) -> Self {
Self {
config,
polling_page: X86GCPollingPage::default(),
inserted_safepoints: HashMap::new(),
}
}
pub fn begin_function(&mut self, name: &str) {
self.inserted_safepoints
.insert(name.to_string(), Vec::new());
}
pub fn needs_safepoint(&self, func_name: &str, offset: u32, kind: X86GCSafepointKind) -> bool {
match kind {
X86GCSafepointKind::Call | X86GCSafepointKind::AfterCall => self.config.at_calls,
X86GCSafepointKind::LoopBackedge => self.config.at_loop_backedges,
X86GCSafepointKind::FunctionEntry => self.config.at_function_entry,
X86GCSafepointKind::FunctionExit => self.config.at_function_exit,
_ => true,
}
}
pub fn insert_safepoint(
&mut self,
func_name: &str,
offset: u32,
kind: X86GCSafepointKind,
) -> Option<Vec<u8>> {
if !self.needs_safepoint(func_name, offset, kind) {
return None;
}
let id = self
.inserted_safepoints
.get(func_name)
.map(|v| v.len())
.unwrap_or(0) as u32;
let sp = X86GCSafepoint {
id,
kind,
function_offset: offset,
stack_map_id: id as u64,
live_roots: Vec::new(),
derived_pointers: Vec::new(),
num_deopt_args: 0,
is_deopt: false,
requires_full_stack_walk: kind != X86GCSafepointKind::Call,
};
self.inserted_safepoints
.get_mut(func_name)
.unwrap()
.push(sp);
Some(self.polling_page.generate_poll_sequence())
}
pub fn end_function(&mut self, func_name: &str) -> Vec<X86GCSafepoint> {
self.inserted_safepoints
.remove(func_name)
.unwrap_or_default()
}
}
#[derive(Debug, Clone)]
pub struct X86GCStackFrame {
pub saved_rbp: u64,
pub return_address: u64,
pub entry_rsp: u64,
pub frame_size: u64,
}
pub struct X86GCStackWalker {
current_rbp: Option<u64>,
current_rsp: u64,
stack_low: u64,
stack_high: u64,
}
impl X86GCStackWalker {
pub fn new(rsp: u64, rbp: u64, stack_low: u64, stack_high: u64) -> Self {
Self {
current_rbp: Some(rbp),
current_rsp: rsp,
stack_low,
stack_high,
}
}
pub fn next_frame(&mut self) -> Option<X86GCStackFrame> {
let rbp = self.current_rbp?;
if rbp < self.stack_low || rbp >= self.stack_high {
self.current_rbp = None;
return None;
}
let saved_rbp = self.read_stack(rbp)?;
let return_address = self.read_stack(rbp + 8)?;
let frame = X86GCStackFrame {
saved_rbp,
return_address,
entry_rsp: rbp + 16, frame_size: if saved_rbp > rbp { saved_rbp - rbp } else { 0 },
};
if saved_rbp == 0 {
self.current_rbp = None;
} else {
self.current_rbp = Some(saved_rbp);
}
Some(frame)
}
fn read_stack(&self, addr: u64) -> Option<u64> {
if addr < self.stack_low || addr + 8 > self.stack_high {
return None;
}
Some(0)
}
pub fn collect_return_addresses(&mut self) -> Vec<u64> {
let mut addrs = Vec::new();
while let Some(frame) = self.next_frame() {
addrs.push(frame.return_address);
}
addrs
}
}
#[derive(Debug, Clone)]
pub struct X86GCRelocation {
pub safepoint_id: u32,
pub root_id: u32,
pub old_address: u64,
pub new_address: u64,
pub is_base_pointer: bool,
}
pub struct X86GCRelocationTable {
entries: Vec<X86GCRelocation>,
}
impl X86GCRelocationTable {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn add_relocation(&mut self, reloc: X86GCRelocation) {
self.entries.push(reloc);
}
pub fn apply_relocations(&self, safepoint_id: u32, roots: &mut [X86GCRoot]) -> Vec<(u32, u64)> {
let mut updates = Vec::new();
for reloc in &self.entries {
if reloc.safepoint_id != safepoint_id {
continue;
}
if let Some(root) = roots.iter_mut().find(|r| r.root_id == reloc.root_id) {
updates.push((reloc.root_id, reloc.new_address));
}
}
updates
}
pub fn clear(&mut self) {
self.entries.clear();
}
}
#[derive(Debug, Clone)]
pub struct X86GCLoweringConfig {
pub strategy: X86GCStrategyKind,
pub emit_stack_maps: bool,
pub lower_write_barriers: bool,
pub lower_read_barriers: bool,
pub insert_safepoints: bool,
pub track_derived_pointers: bool,
pub card_table_base: u64,
pub cooperative_safepoints: bool,
pub polling_page_address: u64,
}
impl Default for X86GCLoweringConfig {
fn default() -> Self {
Self {
strategy: X86GCStrategyKind::StatepointExample,
emit_stack_maps: true,
lower_write_barriers: true,
lower_read_barriers: false,
insert_safepoints: true,
track_derived_pointers: true,
card_table_base: 0x1000_0000_0000,
cooperative_safepoints: true,
polling_page_address: X86_GC_LOWERING_POLLING_PAGE_ADDR,
}
}
}
pub struct X86GCLowering {
pub config: X86GCLoweringConfig,
statepoint_strategy: Option<StatepointExampleStrategy>,
coreclr_strategy: Option<CoreCLRStrategy>,
ocaml_strategy: Option<OCamlStrategy>,
erlang_strategy: Option<ErlangStrategy>,
shadow_stack_strategy: Option<ShadowStackStrategy>,
barrier_lowering: X86GCBarrierLowering,
safepoint_insertion: X86GCSafepointInsertion,
relocation_table: X86GCRelocationTable,
derived_pointers: Vec<X86GCDerivedPtr>,
current_function: Option<String>,
stack_map_section: X86GCStackMapSection,
}
impl X86GCLowering {
pub fn new(config: X86GCLoweringConfig) -> Self {
let safepoint_config = X86GCSafepointInsertionConfig {
at_calls: config.insert_safepoints,
at_loop_backedges: config.insert_safepoints,
at_function_entry: false,
at_function_exit: false,
max_distance: 4096,
polling_page: config.polling_page_address,
};
let mut lowering = Self {
config: config.clone(),
statepoint_strategy: None,
coreclr_strategy: None,
ocaml_strategy: None,
erlang_strategy: None,
shadow_stack_strategy: None,
barrier_lowering: X86GCBarrierLowering::new(config.card_table_base),
safepoint_insertion: X86GCSafepointInsertion::new(safepoint_config),
relocation_table: X86GCRelocationTable::new(),
derived_pointers: Vec::new(),
current_function: None,
stack_map_section: X86GCStackMapSection::new(),
};
match config.strategy {
X86GCStrategyKind::StatepointExample => {
lowering.statepoint_strategy = Some(StatepointExampleStrategy::new());
}
X86GCStrategyKind::CoreCLR => {
lowering.coreclr_strategy = Some(CoreCLRStrategy::new());
}
X86GCStrategyKind::OCaml => {
lowering.ocaml_strategy = Some(OCamlStrategy::new());
}
X86GCStrategyKind::Erlang => {
lowering.erlang_strategy = Some(ErlangStrategy::new());
}
X86GCStrategyKind::ShadowStack => {
lowering.shadow_stack_strategy =
Some(ShadowStackStrategy::new(0x7000_0000_0000, 4096));
}
}
lowering
}
pub fn begin_function(&mut self, name: &str, address: u64) {
self.current_function = Some(name.to_string());
if let Some(ref mut s) = self.statepoint_strategy {
s.begin_function(name);
}
self.safepoint_insertion.begin_function(name);
}
pub fn add_root(&mut self, root: X86GCRoot) {
let func_name = match &self.current_function {
Some(n) => n.clone(),
None => return,
};
if let Some(ref mut s) = self.statepoint_strategy {
s.add_root(&func_name, root);
}
}
pub fn add_derived_pointer(&mut self, derived: X86GCDerivedPtr) {
self.derived_pointers.push(derived);
}
pub fn lower_write_barrier(
&self,
kind: X86GCWriteBarrierKind,
addr_reg: u16,
value_reg: Option<u16>,
) -> X86GCWriteBarrierSeq {
match kind {
X86GCWriteBarrierKind::CardMarking => self
.barrier_lowering
.lower_card_mark(addr_reg, DW_REG_R11 as u16),
X86GCWriteBarrierKind::RememberedSet => self
.barrier_lowering
.lower_remembered_set(addr_reg, 0x1000_0000_1000),
X86GCWriteBarrierKind::SATB => self.barrier_lowering.lower_satb(
addr_reg,
value_reg.unwrap_or(DW_REG_RAX as u16),
0x6000_0000,
0x6000_0008,
),
X86GCWriteBarrierKind::Generational => {
self.barrier_lowering
.lower_card_mark(addr_reg, DW_REG_R11 as u16)
}
X86GCWriteBarrierKind::None => X86GCWriteBarrierSeq {
kind,
addr_reg: Some(addr_reg),
value_reg,
card_table_base: None,
remset_buffer_addr: None,
is_inline: true,
instruction_count: 0,
sequence: Vec::new(),
},
}
}
pub fn lower_read_barrier(
&self,
kind: X86GCReadBarrierKind,
result_reg: u16,
) -> X86GCReadBarrierSeq {
match kind {
X86GCReadBarrierKind::Brooks => {
self.barrier_lowering.lower_brooks_read_barrier(result_reg)
}
_ => X86GCReadBarrierSeq {
kind,
result_reg: Some(result_reg),
is_inline: true,
instruction_count: 0,
sequence: Vec::new(),
},
}
}
pub fn insert_safepoint(&mut self, offset: u32, kind: X86GCSafepointKind) -> Option<Vec<u8>> {
let func_name = match &self.current_function {
Some(n) => n.clone(),
None => return None,
};
self.safepoint_insertion
.insert_safepoint(&func_name, offset, kind)
}
pub fn end_function(&mut self, address: u64, stack_size: u64) {
let func_name = match self.current_function.take() {
Some(n) => n,
None => return,
};
if let Some(ref mut s) = self.statepoint_strategy {
s.compute_liveness(&func_name);
s.end_function(&func_name, address, stack_size);
}
let _ = self.safepoint_insertion.end_function(&func_name);
}
pub fn finalize(&mut self) -> X86GCStackMapSection {
if let Some(ref s) = self.statepoint_strategy {
s.finalize_stack_maps().clone()
} else {
self.stack_map_section.clone()
}
}
}
#[derive(Debug, Clone)]
pub struct X86GCPatchPoint {
pub id: u64,
pub offset: u32,
pub nop_sled_size: u16,
pub num_live_values: u16,
pub call_target: u64,
pub stack_map_record: X86GCStackMapRecord,
pub is_patched: bool,
}
pub struct X86GCPatchPointLowering {
patchpoints: Vec<X86GCPatchPoint>,
next_id: u64,
}
impl X86GCPatchPointLowering {
pub fn new() -> Self {
Self {
patchpoints: Vec::new(),
next_id: 0,
}
}
pub fn create_patchpoint(
&mut self,
offset: u32,
nop_sled_size: u16,
call_target: u64,
live_locations: Vec<X86GCStackMapLocation>,
) -> X86GCPatchPoint {
let id = self.next_id;
self.next_id += 1;
let pp = X86GCPatchPoint {
id,
offset,
nop_sled_size,
num_live_values: live_locations.len() as u16,
call_target,
stack_map_record: X86GCStackMapRecord {
record_id: id,
instruction_offset: offset,
num_locations: live_locations.len() as u16,
locations: live_locations,
num_live_outs: 0,
live_outs: Vec::new(),
},
is_patched: false,
};
self.patchpoints.push(pp.clone());
pp
}
pub fn generate_nop_sled(size: u16) -> Vec<u8> {
let mut sled = Vec::with_capacity(size as usize);
let mut remaining = size as usize;
while remaining > 0 {
match remaining {
1 => {
sled.push(0x90);
remaining -= 1;
}
2 => {
sled.extend_from_slice(&[0x66, 0x90]);
remaining -= 2;
}
3 => {
sled.extend_from_slice(&[0x0F, 0x1F, 0x00]);
remaining -= 3;
}
4 => {
sled.extend_from_slice(&[0x0F, 0x1F, 0x40, 0x00]);
remaining -= 4;
}
5 => {
sled.extend_from_slice(&[0x0F, 0x1F, 0x44, 0x00, 0x00]);
remaining -= 5;
}
6 => {
sled.extend_from_slice(&[0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00]);
remaining -= 6;
}
7 => {
sled.extend_from_slice(&[0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00]);
remaining -= 7;
}
_ => {
sled.extend_from_slice(&[0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]);
remaining -= 8;
}
}
}
sled
}
pub fn generate_patch(&self, new_target: u64) -> Vec<u8> {
let mut patch = vec![0xE9];
patch.extend_from_slice(&0u32.to_le_bytes()); patch
}
pub fn get_patchpoints(&self) -> &[X86GCPatchPoint] {
&self.patchpoints
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86GCAllocKind {
BumpPointer,
FreeList,
TLAB,
LargeObject,
}
pub struct X86GCAllocLowering {
bump_cursor_addr: u64,
bump_limit_addr: u64,
tlab_base_addr: u64,
slow_path_fn: u64,
}
impl X86GCAllocLowering {
pub fn new(bump_cursor_addr: u64, bump_limit_addr: u64, slow_path_fn: u64) -> Self {
Self {
bump_cursor_addr,
bump_limit_addr,
tlab_base_addr: bump_cursor_addr,
slow_path_fn,
}
}
pub fn generate_bump_alloc(
&self,
alloc_size: u32,
result_reg: u16,
scratch_reg: u16,
) -> Vec<u8> {
let mut seq = Vec::new();
seq.push(0x48);
seq.push(0x8B); seq.push((0x05u16 | ((result_reg & 0x07) << 3)) as u8); seq.extend_from_slice(&(self.bump_cursor_addr as u32).to_le_bytes());
seq.push(0x48);
seq.push(0x8D); let modrm = (0x80u16 | ((scratch_reg & 0x07) << 3) | (result_reg & 0x07)) as u8;
seq.push(modrm);
seq.extend_from_slice(&alloc_size.to_le_bytes());
seq.push(0x48);
seq.push(0x3B); let modrm2 = (0x0Du16 | ((scratch_reg & 0x07) << 3)) as u8;
seq.push(modrm2);
seq.extend_from_slice(&(self.bump_limit_addr as u32).to_le_bytes());
seq.push(0x77); seq.push(0x0C);
seq.push(0x48);
seq.push(0x89);
let modrm3 = (0x05u16 | ((scratch_reg & 0x07) << 3)) as u8;
seq.push(modrm3);
seq.extend_from_slice(&(self.bump_cursor_addr as u32).to_le_bytes());
seq.push(0xEB); seq.push(0x05);
seq.push(0xBF); seq.extend_from_slice(&alloc_size.to_le_bytes());
seq.push(0xE8); seq.extend_from_slice(&0u32.to_le_bytes());
seq
}
pub fn generate_free_list_alloc(&self, size_class: u8, result_reg: u16) -> Vec<u8> {
let mut seq = Vec::new();
let list_head = self.bump_cursor_addr + (size_class as u64) * 8;
seq.push(0x48);
seq.push(0x8B);
seq.push((0x05u16 | ((result_reg & 0x07) << 3)) as u8);
seq.extend_from_slice(&(list_head as u32).to_le_bytes());
seq.push(0x48);
seq.push(0x85);
let modrm = (0xC0u16 | (result_reg & 0x07) | ((result_reg & 0x07) << 3)) as u8;
seq.push(modrm);
seq.push(0x74); seq.push(0x10);
seq.push(0x48);
seq.push(0x8B);
seq.push((0x00u16 | (result_reg & 0x07)) as u8);
seq.push(0x48);
seq.push(0x89);
seq.push(0x05);
seq.extend_from_slice(&(list_head as u32).to_le_bytes());
seq.push(0xC3);
seq
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86GCCollectionAlgorithm {
MarkSweep,
MarkCompact,
SemiSpace,
Generational,
Concurrent,
}
#[derive(Debug, Clone)]
pub struct X86GCCollectorConfig {
pub algorithm: X86GCCollectionAlgorithm,
pub nursery_size: usize,
pub semispace_size: usize,
pub run_finalizers: bool,
pub process_weak_refs: bool,
pub max_pause_ms: u32,
}
impl Default for X86GCCollectorConfig {
fn default() -> Self {
Self {
algorithm: X86GCCollectionAlgorithm::MarkSweep,
nursery_size: 4 * 1024 * 1024,
semispace_size: 8 * 1024 * 1024,
run_finalizers: true,
process_weak_refs: true,
max_pause_ms: 10,
}
}
}
#[derive(Debug, Clone)]
pub struct X86GCFrameDescriptor {
pub return_address: u64,
pub frame_size: u32,
pub stack_roots: Vec<i32>,
pub register_roots: Vec<u16>,
pub has_frame_pointer: bool,
pub uses_red_zone: bool,
}
pub struct X86GCFrameRegistry {
frames: BTreeMap<u64, X86GCFrameDescriptor>,
}
impl X86GCFrameRegistry {
pub fn new() -> Self {
Self {
frames: BTreeMap::new(),
}
}
pub fn register(&mut self, desc: X86GCFrameDescriptor) {
self.frames.insert(desc.return_address, desc);
}
pub fn lookup(&self, return_address: u64) -> Option<&X86GCFrameDescriptor> {
self.frames.get(&return_address)
}
pub fn find_containing_frame(&self, pc: u64) -> Option<&X86GCFrameDescriptor> {
self.frames.range(..=pc).next_back().map(|(_, desc)| desc)
}
pub fn walk_stack(
&self,
mut rbp: u64,
rsp: u64,
stack_bottom: u64,
) -> Vec<(X86GCFrameDescriptor, u64)> {
let mut frames = Vec::new();
let mut depth = 0;
const MAX_DEPTH: usize = 1024;
while rbp > rsp && rbp < stack_bottom && depth < MAX_DEPTH {
let ret_addr = rbp + 8; if let Some(desc) = self.find_containing_frame(ret_addr) {
frames.push((desc.clone(), rbp));
}
rbp = rbp; depth += 1;
}
frames
}
}
pub struct X86GCBackEdgePollInserter {
polling_page: u64,
max_poll_distance: u32,
inst_counter: u32,
poll_inserted: bool,
}
impl X86GCBackEdgePollInserter {
pub fn new(polling_page: u64, max_distance: u32) -> Self {
Self {
polling_page,
max_poll_distance: max_distance,
inst_counter: 0,
poll_inserted: false,
}
}
pub fn advance(&mut self, num_insts: u32) -> bool {
self.inst_counter += num_insts;
self.poll_inserted = false;
if self.inst_counter >= self.max_poll_distance {
self.inst_counter = 0;
self.poll_inserted = true;
true
} else {
false
}
}
pub fn generate_poll(&self) -> Vec<u8> {
let mut seq = Vec::new();
seq.push(0x48);
seq.push(0xB8);
seq.extend_from_slice(&self.polling_page.to_le_bytes());
seq.push(0xF6);
seq.push(0x00);
seq.push(0x00);
seq
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_root_creation() {
let base = X86GCRoot::new_base(0, -8);
assert_eq!(base.root_id, 0);
assert_eq!(base.kind, X86GCRootKind::BasePointer);
assert_eq!(base.frame_offset, -8);
assert!(base.is_live);
let reg = X86GCRoot::new_register(1, DW_REG_RBX);
assert_eq!(reg.kind, X86GCRootKind::Register);
assert_eq!(reg.dwarf_reg, Some(DW_REG_RBX));
let derived = X86GCRoot::new_derived(2, -16, 32);
assert_eq!(derived.kind, X86GCRootKind::DerivedPointer);
assert_eq!(derived.derived_offset, Some(32));
}
#[test]
fn test_statepoint_strategy() {
let mut strat = StatepointExampleStrategy::new();
strat.begin_function("test_func");
strat.add_root("test_func", X86GCRoot::new_base(0, -8));
strat.add_root("test_func", X86GCRoot::new_register(1, DW_REG_RBX));
strat.add_root("test_func", X86GCRoot::new_base(2, -16));
strat.add_safepoint("test_func", 0x10, X86GCSafepointKind::Call);
strat.add_safepoint("test_func", 0x30, X86GCSafepointKind::LoopBackedge);
strat.compute_liveness("test_func");
strat.end_function("test_func", 0x400000, 64);
let sm = strat.finalize_stack_maps();
assert_eq!(sm.function_count(), 1);
assert_eq!(sm.total_records(), 2);
}
#[test]
fn test_coreclr_strategy() {
let mut strat = CoreCLRStrategy::new();
strat.add_region(0x00, 0x50, CoreCLRInterruptibility::FullyInterruptible);
strat.add_region(0x50, 0x80, CoreCLRInterruptibility::PartiallyInterruptible);
strat.register_slot(0, -8);
strat.register_slot(1, -16);
strat.register_slot_in_reg(2, DW_REG_RBX);
strat.add_slot_change(
0x60,
0,
true,
CoreCLRSlotFlags {
is_pinned: true,
..Default::default()
},
);
let gcinfo = strat.encode_gcinfo();
assert_eq!(gcinfo[0], 2);
assert_eq!(gcinfo[1], 1);
assert_eq!(gcinfo[2], 0);
assert!(gcinfo.len() > 3);
}
#[test]
fn test_ocaml_strategy() {
let mut strat = OCamlStrategy::new();
strat.emit_frame_descriptor(0x401000, 64, &[8, 16, 24]);
strat.emit_frame_descriptor(0x402000, 128, &[0, 8, 16, 32, 48]);
let table = strat.encode_frame_table();
assert!(table.len() >= 8);
let count = u32::from_le_bytes([table[0], table[1], table[2], table[3]]);
assert_eq!(count, 2);
}
#[test]
fn test_erlang_strategy() {
let mut strat = ErlangStrategy::new();
strat.emit_frame(0x400000, 3, 2, 0x0007);
strat.emit_frame(0x401000, 2, 4, 0x00FF);
assert!(!strat.perform_reduction()); for _ in 0..3999 {
strat.perform_reduction();
}
assert!(strat.perform_reduction());
let table = strat.encode_frame_table();
let count = u32::from_le_bytes([table[0], table[1], table[2], table[3]]);
assert_eq!(count, 2);
}
#[test]
fn test_shadow_stack_strategy() {
let mut strat = ShadowStackStrategy::new(0x70000000, 1024);
let frame_idx = strat.push_frame();
assert_eq!(frame_idx, 0);
strat.push_root(0x1000, 1);
strat.push_root(0x2000, 2);
strat.push_root(0x3000, 3);
let roots = strat.roots_in_current_frame();
assert_eq!(roots.len(), 3);
assert_eq!(roots[0].pointer, 0x1000);
assert_eq!(roots[2].pointer, 0x3000);
strat.push_frame();
strat.push_root(0x4000, 4);
assert_eq!(strat.roots_in_current_frame().len(), 1);
strat.pop_frame();
assert_eq!(strat.roots_in_current_frame().len(), 3);
}
#[test]
fn test_polling_page_sequence() {
let poll = X86GCPollingPage::default();
let seq = poll.generate_poll_sequence();
assert!(seq.len() >= 10);
}
#[test]
fn test_card_mark_barrier() {
let barrier = X86GCBarrierLowering::new(0x10000000);
let seq = barrier.lower_card_mark(DW_REG_RDI as u16, DW_REG_R11 as u16);
assert_eq!(seq.kind, X86GCWriteBarrierKind::CardMarking);
assert!(seq.is_inline);
assert!(!seq.sequence.is_empty());
}
#[test]
fn test_safepoint_insertion() {
let config = X86GCSafepointInsertionConfig::default();
let mut insertion = X86GCSafepointInsertion::new(config);
insertion.begin_function("test_safepoint");
let seq =
insertion.insert_safepoint("test_safepoint", 0x20, X86GCSafepointKind::LoopBackedge);
assert!(seq.is_some());
let safepoints = insertion.end_function("test_safepoint");
assert_eq!(safepoints.len(), 1);
assert_eq!(safepoints[0].kind, X86GCSafepointKind::LoopBackedge);
}
#[test]
fn test_stack_walker() {
let mut walker = X86GCStackWalker::new(
0x7FFF_0000_0F00, 0x7FFF_0000_0FE0, 0x7FFF_0000_0000, 0x7FFF_0000_1000, );
let addrs = walker.collect_return_addresses();
assert!(addrs.len() <= 1);
}
#[test]
fn test_relocation_table() {
let mut table = X86GCRelocationTable::new();
table.add_relocation(X86GCRelocation {
safepoint_id: 0,
root_id: 1,
old_address: 0x1000,
new_address: 0x2000,
is_base_pointer: true,
});
table.add_relocation(X86GCRelocation {
safepoint_id: 0,
root_id: 2,
old_address: 0x3000,
new_address: 0x4000,
is_base_pointer: false,
});
let mut roots = vec![X86GCRoot::new_base(1, -8), X86GCRoot::new_base(2, -16)];
let updates = table.apply_relocations(0, &mut roots);
assert_eq!(updates.len(), 2);
assert_eq!(updates[0].1, 0x2000);
assert_eq!(updates[1].1, 0x4000);
}
#[test]
fn test_gc_lowering_full_pipeline() {
let config = X86GCLoweringConfig::default();
let mut lowering = X86GCLowering::new(config);
lowering.begin_function("test_pipeline", 0x400000);
lowering.add_root(X86GCRoot::new_base(0, -8));
lowering.add_root(X86GCRoot::new_register(1, DW_REG_RBX));
lowering.add_derived_pointer(X86GCDerivedPtr {
derived_root_id: 10,
base_root_id: 0,
offset: 16,
from_gep: true,
from_ptr_arith: false,
creator_inst: None,
in_bounds: true,
});
let _ = lowering.insert_safepoint(0x20, X86GCSafepointKind::Call);
let _ = lowering.insert_safepoint(0x40, X86GCSafepointKind::LoopBackedge);
let barrier = lowering.lower_write_barrier(
X86GCWriteBarrierKind::CardMarking,
DW_REG_RDI as u16,
None,
);
assert_eq!(barrier.kind, X86GCWriteBarrierKind::CardMarking);
lowering.end_function(0x400000, 64);
let stack_map = lowering.finalize();
assert!(stack_map.total_records() > 0);
}
#[test]
fn test_gc_strategy_display() {
assert_eq!(
X86GCStrategyKind::StatepointExample.to_string(),
"statepoint-example"
);
assert_eq!(X86GCStrategyKind::CoreCLR.to_string(), "coreclr");
assert_eq!(X86GCStrategyKind::OCaml.to_string(), "ocaml");
assert_eq!(X86GCStrategyKind::Erlang.to_string(), "erlang");
assert_eq!(X86GCStrategyKind::ShadowStack.to_string(), "shadow-stack");
}
#[test]
fn test_stack_map_section_empty() {
let sm = X86GCStackMapSection::new();
assert_eq!(sm.header.version, 3);
assert_eq!(sm.total_records(), 0);
assert_eq!(sm.function_count(), 0);
}
#[test]
fn test_coreclr_hijack_stub() {
let strat = CoreCLRStrategy::new();
let stub = strat.generate_hijack_stub();
assert!(stub.len() > 20);
assert_eq!(stub.last(), Some(&0xC3));
}
#[test]
fn test_patchpoint_nop_sled() {
let sled = X86GCPatchPointLowering::generate_nop_sled(16);
assert_eq!(sled.len(), 16);
assert!(sled.windows(2).any(|w| w == [0x0F, 0x1F]) || sled[0] == 0x90);
}
#[test]
fn test_patchpoint_nop_sled_small() {
for size in 1..=16 {
let sled = X86GCPatchPointLowering::generate_nop_sled(size);
assert_eq!(sled.len(), size as usize);
}
}
#[test]
fn test_patchpoint_creation() {
let mut lowering = X86GCPatchPointLowering::new();
let pp = lowering.create_patchpoint(
0x50,
16,
0x401000,
vec![X86GCStackMapLocation::Direct {
dwarf_reg: DW_REG_RBP as u16,
_reserved: 0,
offset: -8,
size: 8,
}],
);
assert_eq!(pp.id, 0);
assert_eq!(pp.nop_sled_size, 16);
assert_eq!(pp.stack_map_record.num_locations, 1);
assert_eq!(lowering.get_patchpoints().len(), 1);
}
#[test]
fn test_bump_pointer_alloc() {
let alloc = X86GCAllocLowering::new(0x600000, 0x601000, 0x500000);
let seq = alloc.generate_bump_alloc(64, DW_REG_RAX as u16, DW_REG_RCX as u16);
assert!(seq.len() > 20);
assert!(seq.contains(&0x3B));
}
#[test]
fn test_free_list_alloc() {
let alloc = X86GCAllocLowering::new(0x600000, 0x601000, 0x500000);
let seq = alloc.generate_free_list_alloc(3, DW_REG_RAX as u16);
assert!(seq.len() > 5);
assert!(seq.contains(&0x85));
}
#[test]
fn test_collector_config_default() {
let config = X86GCCollectorConfig::default();
assert_eq!(config.algorithm, X86GCCollectionAlgorithm::MarkSweep);
assert_eq!(config.nursery_size, 4 * 1024 * 1024);
assert_eq!(config.max_pause_ms, 10);
}
#[test]
fn test_frame_registry_register_and_lookup() {
let mut registry = X86GCFrameRegistry::new();
let desc = X86GCFrameDescriptor {
return_address: 0x401050,
frame_size: 64,
stack_roots: vec![-8, -16],
register_roots: vec![DW_REG_RBX as u16],
has_frame_pointer: true,
uses_red_zone: false,
};
registry.register(desc.clone());
let found = registry.lookup(0x401050);
assert!(found.is_some());
assert_eq!(found.unwrap().frame_size, 64);
assert_eq!(found.unwrap().stack_roots.len(), 2);
}
#[test]
fn test_frame_registry_find_containing() {
let mut registry = X86GCFrameRegistry::new();
registry.register(X86GCFrameDescriptor {
return_address: 0x401000,
frame_size: 48,
stack_roots: vec![-8],
register_roots: vec![],
has_frame_pointer: true,
uses_red_zone: false,
});
registry.register(X86GCFrameDescriptor {
return_address: 0x402000,
frame_size: 96,
stack_roots: vec![-8, -16, -24],
register_roots: vec![DW_REG_R12 as u16],
has_frame_pointer: true,
uses_red_zone: false,
});
let found = registry.find_containing_frame(0x401500);
assert!(found.is_some());
assert_eq!(found.unwrap().return_address, 0x401000);
let found2 = registry.find_containing_frame(0x402000);
assert!(found2.is_some());
assert_eq!(found2.unwrap().frame_size, 96);
}
#[test]
fn test_back_edge_poll_inserter() {
let mut inserter = X86GCBackEdgePollInserter::new(0x7FFFFFFFFFF000, 100);
assert!(!inserter.advance(50));
assert!(inserter.advance(51));
assert!(inserter.poll_inserted);
let poll_seq = inserter.generate_poll();
assert!(poll_seq.len() > 5);
}
#[test]
fn test_alloc_kind_discriminants() {
assert_ne!(
X86GCAllocKind::BumpPointer as u8,
X86GCAllocKind::FreeList as u8
);
assert_ne!(
X86GCAllocKind::TLAB as u8,
X86GCAllocKind::LargeObject as u8
);
}
}