#![allow(non_upper_case_globals, dead_code)]
use crate::codegen::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand, VirtReg};
use crate::x86::x86_instr_info::{X86InstrInfo, X86Opcode};
use crate::x86::x86_register_info::{
EAX, R10, R11, R12, R13, R14, R15, R8, R9, RAX, RBP, RBX, RCX, RDI, RDX, RSI, RSP,
};
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque};
pub const MIN_STACK_ALIGNMENT: u32 = 16;
pub const MAX_STACK_ALIGNMENT: u32 = 64;
pub const WIN64_SHADOW_SPACE: i64 = 32;
pub const DEFAULT_EMERGENCY_SLOTS: usize = 2;
pub const EMERGENCY_SLOT_SIZE: i64 = 8;
pub const DEFAULT_PROBE_INTERVAL: i64 = 4096;
pub const MIN_STACK_FOR_PROBING: i64 = 4096;
pub const SMALL_FRAME_THRESHOLD: i64 = 1024;
pub const DRAP_REGISTER_ALT: u16 = R10;
pub const DRAP_REGISTER_32: u16 = EAX;
pub const SHRINK_WRAP_CANDIDATES_SYSV: &[u16] = &[RBX, R12, R13, R14, R15];
pub const SHRINK_WRAP_CANDIDATES_WIN64: &[u16] = &[RBX, RSI, RDI, R12, R13, R14, R15];
pub const MAX_SLOT_COLORS: usize = 64;
pub const SLOT_MERGE_SIZE_THRESHOLD: u32 = 16;
pub const MAX_SLOTS_FOR_MERGE: usize = 256;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum FrameCallConv {
SystemVAMD64,
Win64,
Cdecl32,
Stdcall32,
Fastcall32,
Thiscall32,
Vectorcall32,
Vectorcall64,
}
impl FrameCallConv {
pub fn has_red_zone(&self) -> bool {
matches!(self, Self::SystemVAMD64)
}
pub fn has_shadow_space(&self) -> bool {
matches!(self, Self::Win64)
}
pub fn is_register_based(&self) -> bool {
matches!(
self,
Self::SystemVAMD64
| Self::Win64
| Self::Fastcall32
| Self::Vectorcall32
| Self::Vectorcall64
)
}
pub fn is_64bit(&self) -> bool {
matches!(self, Self::SystemVAMD64 | Self::Win64 | Self::Vectorcall64)
}
pub fn stack_alignment(&self) -> u32 {
if self.is_64bit() {
16
} else {
16 }
}
pub fn red_zone_size(&self) -> i64 {
if self.has_red_zone() {
128
} else {
0
}
}
pub fn shadow_space_size(&self) -> i64 {
if self.has_shadow_space() {
32
} else {
0
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SaveRestoreRegion {
pub reg: u16,
pub save_block: usize,
pub restore_block: usize,
pub in_loop: bool,
pub loop_depth: u32,
pub can_hoist_save: bool,
pub can_sink_restore: bool,
}
impl SaveRestoreRegion {
pub fn new(reg: u16, save_block: usize, restore_block: usize) -> Self {
Self {
reg,
save_block,
restore_block,
in_loop: false,
loop_depth: 0,
can_hoist_save: true,
can_sink_restore: true,
}
}
pub fn is_save_optimal(&self) -> bool {
!self.in_loop && self.can_hoist_save
}
pub fn is_restore_optimal(&self) -> bool {
!self.in_loop && self.can_sink_restore
}
}
#[derive(Debug, Clone)]
pub struct DomTreeNode {
pub block: usize,
pub idom: Option<usize>,
pub children: Vec<usize>,
pub depth: u32,
}
impl DomTreeNode {
pub fn new(block: usize) -> Self {
Self {
block,
idom: None,
children: Vec::new(),
depth: 0,
}
}
pub fn dominates(&self, other: &DomTreeNode, dom_tree: &[DomTreeNode]) -> bool {
let mut current = other.block;
loop {
if current == self.block {
return true;
}
match dom_tree[current].idom {
Some(idom) => current = idom,
None => return false,
}
}
}
}
#[derive(Debug, Clone)]
pub struct PostDomTreeNode {
pub block: usize,
pub ipdom: Option<usize>,
pub children: Vec<usize>,
}
impl PostDomTreeNode {
pub fn new(block: usize) -> Self {
Self {
block,
ipdom: None,
children: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct ShrinkWrapConfig {
pub enabled: bool,
pub loop_aware: bool,
pub min_saves_for_wrapping: usize,
pub group_saves: bool,
pub max_loop_depth: u32,
pub call_conv: FrameCallConv,
}
impl Default for ShrinkWrapConfig {
fn default() -> Self {
Self {
enabled: true,
loop_aware: true,
min_saves_for_wrapping: 2,
group_saves: true,
max_loop_depth: 8,
call_conv: FrameCallConv::SystemVAMD64,
}
}
}
#[derive(Debug, Clone)]
pub struct ShrinkWrapResult {
pub regions: Vec<SaveRestoreRegion>,
pub save_blocks: HashSet<usize>,
pub restore_blocks: HashSet<usize>,
pub applied: bool,
pub savings: usize,
}
#[derive(Debug, Clone)]
pub struct SlotLiveRange {
pub slot_id: usize,
pub size: u32,
pub alignment: u32,
pub live_blocks: BTreeSet<usize>,
pub start: usize,
pub end: usize,
pub is_vector: bool,
pub is_emergency: bool,
pub is_callee_saved: bool,
pub spill_weight: f64,
}
impl SlotLiveRange {
pub fn new(slot_id: usize, size: u32, alignment: u32) -> Self {
Self {
slot_id,
size,
alignment,
start: usize::MAX,
end: 0,
live_blocks: BTreeSet::new(),
is_vector: false,
is_emergency: false,
is_callee_saved: false,
spill_weight: 0.0,
}
}
pub fn overlaps(&self, other: &SlotLiveRange) -> bool {
if self.start >= other.end || other.start >= self.end {
return false;
}
self.live_blocks
.intersection(&other.live_blocks)
.next()
.is_some()
}
pub fn extend(&mut self, other: &SlotLiveRange) {
self.start = self.start.min(other.start);
self.end = self.end.max(other.end);
self.live_blocks.extend(&other.live_blocks);
}
}
#[derive(Debug, Clone)]
pub struct SlotInterferenceGraph {
pub nodes: Vec<usize>,
pub edges: HashMap<usize, HashSet<usize>>,
pub live_ranges: HashMap<usize, SlotLiveRange>,
}
impl SlotInterferenceGraph {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
edges: HashMap::new(),
live_ranges: HashMap::new(),
}
}
pub fn add_slot(&mut self, range: SlotLiveRange) {
let slot_id = range.slot_id;
self.nodes.push(slot_id);
self.edges.entry(slot_id).or_insert_with(HashSet::new);
self.live_ranges.insert(slot_id, range);
}
pub fn add_edge(&mut self, a: usize, b: usize) {
if a != b {
self.edges.entry(a).or_insert_with(HashSet::new).insert(b);
self.edges.entry(b).or_insert_with(HashSet::new).insert(a);
}
}
pub fn build(&mut self) {
let slot_ids: Vec<usize> = self.nodes.clone();
for i in 0..slot_ids.len() {
for j in (i + 1)..slot_ids.len() {
let a = slot_ids[i];
let b = slot_ids[j];
if let (Some(lr_a), Some(lr_b)) =
(self.live_ranges.get(&a), self.live_ranges.get(&b))
{
if lr_a.overlaps(lr_b) {
self.add_edge(a, b);
}
}
}
}
}
pub fn greedy_color(&self) -> HashMap<usize, usize> {
let mut colors: HashMap<usize, usize> = HashMap::new();
let mut sorted_nodes = self.nodes.clone();
sorted_nodes.sort_by(|a, b| {
let wa = self
.live_ranges
.get(a)
.map(|lr| lr.spill_weight)
.unwrap_or(0.0);
let wb = self
.live_ranges
.get(b)
.map(|lr| lr.spill_weight)
.unwrap_or(0.0);
wb.partial_cmp(&wa).unwrap_or(std::cmp::Ordering::Equal)
});
for &node in &sorted_nodes {
let neighbors = self.edges.get(&node).map(|n| n.clone()).unwrap_or_default();
let used_colors: HashSet<usize> = neighbors
.iter()
.filter_map(|n| colors.get(n).copied())
.collect();
let mut color = 0;
while used_colors.contains(&color) && color < MAX_SLOT_COLORS {
color += 1;
}
colors.insert(node, color);
}
colors
}
pub fn degree(&self, node: usize) -> usize {
self.edges.get(&node).map(|n| n.len()).unwrap_or(0)
}
pub fn neighbors(&self, node: usize) -> Vec<usize> {
self.edges
.get(&node)
.map(|n| n.iter().copied().collect())
.unwrap_or_default()
}
}
#[derive(Debug, Clone)]
pub struct SlotAllocation {
pub offsets: HashMap<usize, i32>,
pub sizes: HashMap<usize, u32>,
pub total_local_size: i64,
pub local_alignment: u32,
pub merged_slots: bool,
pub slot_count_before: usize,
pub slot_count_after: usize,
}
impl SlotAllocation {
pub fn new() -> Self {
Self {
offsets: HashMap::new(),
sizes: HashMap::new(),
total_local_size: 0,
local_alignment: 1,
merged_slots: false,
slot_count_before: 0,
slot_count_after: 0,
}
}
pub fn offset_of(&self, slot_id: usize) -> Option<i32> {
self.offsets.get(&slot_id).copied()
}
pub fn size_of(&self, slot_id: usize) -> Option<u32> {
self.sizes.get(&slot_id).copied()
}
}
#[derive(Debug, Clone)]
pub struct SlotMergeConfig {
pub max_merge_size: u32,
pub merge_different_sizes: bool,
pub alignment_aware: bool,
pub max_candidates: usize,
}
impl Default for SlotMergeConfig {
fn default() -> Self {
Self {
max_merge_size: SLOT_MERGE_SIZE_THRESHOLD,
merge_different_sizes: false,
alignment_aware: true,
max_candidates: MAX_SLOTS_FOR_MERGE,
}
}
}
#[derive(Debug, Clone)]
pub struct MergeCandidate {
pub slot_a: usize,
pub slot_b: usize,
pub merged_size: u32,
pub merged_alignment: u32,
pub adjacent: bool,
pub savings: u32,
}
impl MergeCandidate {
pub fn new(slot_a: usize, slot_b: usize) -> Self {
Self {
slot_a,
slot_b,
merged_size: 0,
merged_alignment: 1,
adjacent: false,
savings: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct SlotMergeResult {
pub merged_groups: Vec<Vec<usize>>,
pub total_savings: u32,
pub slots_eliminated: usize,
}
impl SlotMergeResult {
pub fn new() -> Self {
Self {
merged_groups: Vec::new(),
total_savings: 0,
slots_eliminated: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameAccessMode {
FramePointer,
StackPointer,
Automatic,
}
#[derive(Debug, Clone)]
pub struct FrameAccessConfig {
pub preferred_mode: FrameAccessMode,
pub allow_fp_elim: bool,
pub max_rsp_offset: i32,
pub has_dynamic_alloca: bool,
pub has_vla: bool,
pub has_alloca: bool,
pub is_leaf: bool,
}
impl Default for FrameAccessConfig {
fn default() -> Self {
Self {
preferred_mode: FrameAccessMode::Automatic,
allow_fp_elim: true,
max_rsp_offset: 128,
has_dynamic_alloca: false,
has_vla: false,
has_alloca: false,
is_leaf: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameAccessDecision {
FpRelative { offset: i32 },
SpRelative { offset: i32 },
DrapRelative { offset: i32 },
}
impl FrameAccessDecision {
pub fn base_register(&self) -> u16 {
match self {
Self::FpRelative { .. } => RBP,
Self::SpRelative { .. } => RSP,
Self::DrapRelative { .. } => DRAP_REGISTER_ALT,
}
}
pub fn offset(&self) -> i32 {
match self {
Self::FpRelative { offset }
| Self::SpRelative { offset }
| Self::DrapRelative { offset } => *offset,
}
}
pub fn is_fp_relative(&self) -> bool {
matches!(self, Self::FpRelative { .. })
}
pub fn is_sp_relative(&self) -> bool {
matches!(self, Self::SpRelative { .. })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EmergencySpillSlot {
pub index: usize,
pub offset: i32,
pub size: i32,
pub in_use: bool,
pub spilled_reg: u16,
pub spill_at: usize,
}
impl EmergencySpillSlot {
pub fn new(index: usize, offset: i32, size: i32) -> Self {
Self {
index,
offset,
size,
in_use: false,
spilled_reg: 0,
spill_at: 0,
}
}
pub fn allocate(&mut self, reg: u16, at_inst: usize) {
self.in_use = true;
self.spilled_reg = reg;
self.spill_at = at_inst;
}
pub fn free(&mut self) {
self.in_use = false;
self.spilled_reg = 0;
self.spill_at = 0;
}
pub fn is_available(&self) -> bool {
!self.in_use
}
}
#[derive(Debug, Clone)]
pub struct EmergencySpillSlotManager {
pub slots: Vec<EmergencySpillSlot>,
pub total_slots: usize,
pub base_offset: i32,
pub allocation_count: u64,
pub exhaustion_count: u64,
}
impl EmergencySpillSlotManager {
pub fn new(count: usize, base_offset: i32) -> Self {
let mut slots = Vec::with_capacity(count);
for i in 0..count {
let offset = base_offset - (i as i32 + 1) * EMERGENCY_SLOT_SIZE as i32;
slots.push(EmergencySpillSlot::new(
i,
offset,
EMERGENCY_SLOT_SIZE as i32,
));
}
Self {
slots,
total_slots: count,
base_offset,
allocation_count: 0,
exhaustion_count: 0,
}
}
pub fn find_available(&self) -> Option<usize> {
self.slots.iter().position(|s| s.is_available())
}
pub fn allocate(&mut self, reg: u16, at_inst: usize) -> Option<usize> {
if let Some(idx) = self.find_available() {
self.slots[idx].allocate(reg, at_inst);
self.allocation_count += 1;
Some(idx)
} else {
self.exhaustion_count += 1;
let oldest = self
.slots
.iter()
.enumerate()
.min_by_key(|(_, s)| s.spill_at)
.map(|(i, _)| i);
if let Some(idx) = oldest {
self.slots[idx].allocate(reg, at_inst);
Some(idx)
} else {
None
}
}
}
pub fn free(&mut self, index: usize) -> bool {
if index < self.slots.len() {
self.slots[index].free();
true
} else {
false
}
}
pub fn free_all(&mut self) {
for slot in &mut self.slots {
slot.free();
}
}
pub fn total_size(&self) -> i64 {
self.total_slots as i64 * EMERGENCY_SLOT_SIZE
}
}
#[derive(Debug, Clone)]
pub struct StackRealignConfig {
pub needed: bool,
pub required_alignment: u32,
pub use_drap: bool,
pub drap_register: u16,
pub has_variable_objects: bool,
}
impl Default for StackRealignConfig {
fn default() -> Self {
Self {
needed: false,
required_alignment: 16,
use_drap: false,
drap_register: DRAP_REGISTER_ALT,
has_variable_objects: false,
}
}
}
#[derive(Debug, Clone)]
pub struct RealignPrologue {
pub instructions: Vec<AlignInstr>,
pub final_adjustment: i64,
pub drap_setup: bool,
}
#[derive(Debug, Clone)]
pub struct RealignEpilogue {
pub instructions: Vec<AlignInstr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AlignInstr {
Push { reg: u16 },
Pop { reg: u16 },
MovRR { dst: u16, src: u16 },
AndRI { reg: u16, imm: i64 },
SubRI { reg: u16, imm: i64 },
AddRI { reg: u16, imm: i64 },
Leave,
Lea { dst: u16, base: u16, offset: i32 },
Nop,
}
pub fn alignment_mask(align: u32) -> i64 {
assert!(align.is_power_of_two(), "Alignment must be power of 2");
!((align - 1) as i64)
}
pub fn generate_realign_prologue(config: &StackRealignConfig, frame_size: i64) -> RealignPrologue {
let mut instrs = Vec::new();
let mut final_adjustment = 0i64;
let mut drap_setup = false;
if config.use_drap {
instrs.push(AlignInstr::Push { reg: RBP });
instrs.push(AlignInstr::MovRR { dst: RBP, src: RSP });
instrs.push(AlignInstr::Push {
reg: config.drap_register,
});
instrs.push(AlignInstr::Lea {
dst: config.drap_register,
base: RSP,
offset: 8, });
let mask = alignment_mask(config.required_alignment);
instrs.push(AlignInstr::AndRI {
reg: config.drap_register,
imm: mask,
});
instrs.push(AlignInstr::MovRR {
dst: RSP,
src: config.drap_register,
});
drap_setup = true;
final_adjustment = 0; } else {
instrs.push(AlignInstr::Push { reg: RBP });
instrs.push(AlignInstr::MovRR { dst: RBP, src: RSP });
instrs.push(AlignInstr::SubRI {
reg: RSP,
imm: frame_size,
});
let mask = alignment_mask(config.required_alignment);
instrs.push(AlignInstr::AndRI {
reg: RSP,
imm: mask,
});
final_adjustment = 0;
}
RealignPrologue {
instructions: instrs,
final_adjustment,
drap_setup,
}
}
pub fn generate_realign_epilogue(config: &StackRealignConfig) -> RealignEpilogue {
let mut instrs = Vec::new();
if config.use_drap {
instrs.push(AlignInstr::Lea {
dst: RSP,
base: config.drap_register,
offset: -16,
});
instrs.push(AlignInstr::Pop {
reg: config.drap_register,
});
instrs.push(AlignInstr::Pop { reg: RBP });
} else {
instrs.push(AlignInstr::MovRR { dst: RSP, src: RBP });
instrs.push(AlignInstr::Pop { reg: RBP });
}
RealignEpilogue {
instructions: instrs,
}
}
#[derive(Debug, Clone)]
pub struct SegmentedStackConfig {
pub enabled: bool,
pub segment_size: i64,
pub morestack_symbol: String,
pub tls_stack_limit: bool,
pub tls_stack_limit_offset: i64,
pub segment_alignment: u32,
}
impl Default for SegmentedStackConfig {
fn default() -> Self {
Self {
enabled: false,
segment_size: 4096 * 256, morestack_symbol: "__morestack".to_string(),
tls_stack_limit: true,
tls_stack_limit_offset: 0,
segment_alignment: 16,
}
}
}
#[derive(Debug, Clone)]
pub struct SegmentedStackPrologue {
pub check_instructions: Vec<StackCheckInstr>,
pub needs_expansion: bool,
pub bytes_needed: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StackCheckInstr {
CmpStackLimit { limit_ref: String },
Jae { target: String },
CallMorestack,
PushRegs { regs: Vec<u16> },
PopRegs { regs: Vec<u16> },
Label { name: String },
SubRsp { amount: i64 },
Nop,
}
pub fn generate_segmented_prologue(
config: &SegmentedStackConfig,
frame_size: i64,
) -> SegmentedStackPrologue {
let label_skip = "L_segstack_skip".to_string();
let label_more = "L_segstack_more".to_string();
let mut check_instrs = Vec::new();
if config.tls_stack_limit {
check_instrs.push(StackCheckInstr::CmpStackLimit {
limit_ref: format!("%fs:{}", config.tls_stack_limit_offset),
});
} else {
check_instrs.push(StackCheckInstr::CmpStackLimit {
limit_ref: "__stack_limit".to_string(),
});
}
check_instrs.push(StackCheckInstr::Jae {
target: label_skip.clone(),
});
check_instrs.push(StackCheckInstr::Label { name: label_more });
check_instrs.push(StackCheckInstr::CallMorestack);
check_instrs.push(StackCheckInstr::SubRsp { amount: frame_size });
check_instrs.push(StackCheckInstr::Label { name: label_skip });
SegmentedStackPrologue {
check_instructions: check_instrs,
needs_expansion: frame_size > config.segment_size,
bytes_needed: frame_size,
}
}
#[derive(Debug, Clone)]
pub struct SafeStackConfig {
pub enabled: bool,
pub safe_stack_size: i64,
pub unsafe_stack_size: i64,
pub unsafe_stack_alignment: u32,
pub unsafe_stack_ptr_tls_offset: i64,
}
impl Default for SafeStackConfig {
fn default() -> Self {
Self {
enabled: false,
safe_stack_size: 4096 * 8, unsafe_stack_size: 4096 * 64, unsafe_stack_alignment: 16,
unsafe_stack_ptr_tls_offset: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct SafeStackFrame {
pub safe_objects: Vec<SafeStackObject>,
pub unsafe_objects: Vec<SafeStackObject>,
pub safe_frame_size: i64,
pub unsafe_frame_size: i64,
pub unsafe_sp_loaded: bool,
}
#[derive(Debug, Clone)]
pub struct SafeStackObject {
pub id: usize,
pub size: u32,
pub alignment: u32,
pub offset: i32,
pub address_taken: bool,
pub has_safe_pointers: bool,
}
impl SafeStackObject {
pub fn new(id: usize, size: u32, alignment: u32) -> Self {
Self {
id,
size,
alignment,
offset: 0,
address_taken: false,
has_safe_pointers: false,
}
}
}
pub fn classify_safe_stack_object(obj: &SafeStackObject) -> bool {
!obj.address_taken
}
pub fn layout_safe_stack(objects: &[SafeStackObject], config: &SafeStackConfig) -> SafeStackFrame {
let mut safe_objects = Vec::new();
let mut unsafe_objects = Vec::new();
let mut safe_cur_offset = 0i32;
let mut unsafe_cur_offset = 0i32;
for obj in objects {
if classify_safe_stack_object(obj) {
let mut placed = obj.clone();
let align_mask = (obj.alignment as i32) - 1;
safe_cur_offset = (safe_cur_offset + align_mask) & !align_mask;
placed.offset = safe_cur_offset;
safe_cur_offset += obj.size as i32;
safe_objects.push(placed);
} else {
let mut placed = obj.clone();
let align_mask = (obj.alignment as i32) - 1;
unsafe_cur_offset = (unsafe_cur_offset + align_mask) & !align_mask;
placed.offset = unsafe_cur_offset;
unsafe_cur_offset += obj.size as i32;
unsafe_objects.push(placed);
}
}
let unsafe_objects_empty = unsafe_objects.is_empty();
SafeStackFrame {
safe_objects,
unsafe_objects,
safe_frame_size: safe_cur_offset as i64,
unsafe_frame_size: unsafe_cur_offset as i64,
unsafe_sp_loaded: !unsafe_objects_empty,
}
}
#[derive(Debug, Clone)]
pub struct StackClashProtectionConfig {
pub enabled: bool,
pub probe_interval: i64,
pub min_probe_size: i64,
pub use_loop_probe: bool,
pub probe_allocas: bool,
pub probe_tail_calls: bool,
pub target_os: TargetOS,
}
impl Default for StackClashProtectionConfig {
fn default() -> Self {
Self {
enabled: true,
probe_interval: DEFAULT_PROBE_INTERVAL,
min_probe_size: MIN_STACK_FOR_PROBING,
use_loop_probe: true,
probe_allocas: true,
probe_tail_calls: true,
target_os: TargetOS::Linux,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TargetOS {
Linux,
Windows,
MacOS,
FreeBSD,
NetBSD,
OpenBSD,
Solaris,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProbeInstr {
pub kind: ProbeKind,
pub offset: i32,
pub base_reg: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProbeKind {
OrImm,
MovImm,
Cmp,
LoopProbe,
}
pub fn generate_stack_probes(
config: &StackClashProtectionConfig,
frame_size: i64,
) -> Vec<ProbeInstr> {
if !config.enabled || frame_size < config.min_probe_size {
return Vec::new();
}
let mut probes = Vec::new();
let interval = config.probe_interval;
if config.use_loop_probe && frame_size > interval * 4 {
probes.push(ProbeInstr {
kind: ProbeKind::LoopProbe,
offset: 0,
base_reg: RSP,
});
} else {
let mut offset = -interval as i32;
while offset > -frame_size as i32 {
probes.push(ProbeInstr {
kind: ProbeKind::OrImm,
offset,
base_reg: RSP,
});
offset -= interval as i32;
}
}
probes
}
pub fn needs_stack_clash_protection(config: &StackClashProtectionConfig, frame_size: i64) -> bool {
config.enabled && frame_size >= config.min_probe_size
}
#[derive(Debug, Clone)]
pub struct StackMapEntry {
pub id: u64,
pub code_offset: u64,
pub live_locations: Vec<StackMapLocation>,
pub num_locations: u32,
pub frame_size: u64,
pub flags: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StackMapLocation {
pub kind: StackMapLocKind,
pub size: u32,
pub reg: u16,
pub offset: i32,
pub constant: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StackMapLocKind {
Register = 1,
Direct = 2,
Indirect = 3,
Constant = 4,
ConstantIndex = 5,
}
impl StackMapLocation {
pub fn new_register(reg: u16, size: u32) -> Self {
Self {
kind: StackMapLocKind::Register,
size,
reg,
offset: 0,
constant: 0,
}
}
pub fn new_direct(reg: u16, offset: i32, size: u32) -> Self {
Self {
kind: StackMapLocKind::Direct,
size,
reg,
offset,
constant: 0,
}
}
pub fn new_indirect(reg: u16, offset: i32, size: u32) -> Self {
Self {
kind: StackMapLocKind::Indirect,
size,
reg,
offset,
constant: 0,
}
}
pub fn new_constant(value: i64) -> Self {
Self {
kind: StackMapLocKind::Constant,
size: 8,
reg: 0,
offset: 0,
constant: value,
}
}
}
#[derive(Debug, Clone)]
pub struct PatchPointConfig {
pub emit_stackmap: bool,
pub is_patchpoint: bool,
pub is_statepoint: bool,
pub nop_bytes: u32,
pub call_conv: FrameCallConv,
}
impl Default for PatchPointConfig {
fn default() -> Self {
Self {
emit_stackmap: true,
is_patchpoint: false,
is_statepoint: false,
nop_bytes: 12,
call_conv: FrameCallConv::SystemVAMD64,
}
}
}
pub fn build_stack_map_entry(
id: u64,
code_offset: u64,
live_locations: Vec<StackMapLocation>,
frame_size: u64,
flags: u32,
) -> StackMapEntry {
let num_locations = live_locations.len() as u32;
StackMapEntry {
id,
code_offset,
live_locations,
num_locations,
frame_size,
flags,
}
}
#[derive(Debug, Clone)]
pub struct VarArgsConfig {
pub call_conv: FrameCallConv,
pub has_varargs: bool,
pub num_fixed_args: u32,
pub save_int_regs: bool,
pub save_xmm_regs: bool,
pub save_area_alignment: u32,
}
impl Default for VarArgsConfig {
fn default() -> Self {
Self {
call_conv: FrameCallConv::SystemVAMD64,
has_varargs: false,
num_fixed_args: 0,
save_int_regs: true,
save_xmm_regs: true,
save_area_alignment: 16,
}
}
}
#[derive(Debug, Clone)]
pub struct SysVVarArgsFrame {
pub num_gp_slots: u32,
pub num_xmm_slots: u32,
pub gp_save_offset: i32,
pub xmm_save_offset: i32,
pub overflow_offset: i32,
pub total_save_area_size: i64,
pub gp_regs: Vec<u16>,
pub xmm_regs: Vec<u16>,
}
impl SysVVarArgsFrame {
pub const STD_GP_REGS: [u16; 6] = [RDI, RSI, RDX, RCX, R8, R9];
pub const STD_XMM_REGS: [u16; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
pub fn new(num_fixed_args: u32) -> Self {
let gp_regs: Vec<u16> = Self::STD_GP_REGS
.iter()
.skip(num_fixed_args as usize)
.copied()
.collect();
let xmm_regs: Vec<u16> = Self::STD_XMM_REGS
.iter()
.skip(num_fixed_args as usize)
.copied()
.collect();
let num_gp_slots = gp_regs.len() as u32;
let num_xmm_slots = xmm_regs.len() as u32;
let gp_size = num_gp_slots * 8;
let xmm_size = num_xmm_slots * 16;
Self {
num_gp_slots,
num_xmm_slots,
gp_save_offset: -(gp_size as i32),
xmm_save_offset: -((gp_size + xmm_size) as i32),
overflow_offset: 16, total_save_area_size: (gp_size + xmm_size) as i64,
gp_regs,
xmm_regs,
}
}
}
#[derive(Debug, Clone)]
pub struct Win64VarArgsFrame {
pub shadow_space_offset: i32,
pub overflow_offset: i32,
pub shadow_space_size: i64,
pub num_overflow_slots: u32,
}
impl Win64VarArgsFrame {
pub fn new() -> Self {
Self {
shadow_space_offset: 16, overflow_offset: 48, shadow_space_size: WIN64_SHADOW_SPACE,
num_overflow_slots: 0,
}
}
}
pub fn build_varargs_frame(config: &VarArgsConfig) -> VarArgsFrameResult {
match config.call_conv {
FrameCallConv::SystemVAMD64 => {
let sysv = SysVVarArgsFrame::new(config.num_fixed_args);
VarArgsFrameResult::SystemV(sysv)
}
FrameCallConv::Win64 => {
let win = Win64VarArgsFrame::new();
VarArgsFrameResult::Win64(win)
}
_ => VarArgsFrameResult::Unsupported,
}
}
#[derive(Debug, Clone)]
pub enum VarArgsFrameResult {
SystemV(SysVVarArgsFrame),
Win64(Win64VarArgsFrame),
Unsupported,
}
#[derive(Debug, Clone)]
pub struct SretDemotionConfig {
pub needed: bool,
pub struct_size: u32,
pub struct_alignment: u32,
pub sret_in_reg: bool,
pub sret_reg: u16,
pub allocate_local: bool,
}
impl Default for SretDemotionConfig {
fn default() -> Self {
Self {
needed: false,
struct_size: 0,
struct_alignment: 8,
sret_in_reg: true,
sret_reg: RAX, allocate_local: true,
}
}
}
#[derive(Debug, Clone)]
pub struct SretFrameSlot {
pub offset: i32,
pub size: u32,
pub alignment: u32,
pub initialized: bool,
}
impl SretFrameSlot {
pub fn new(offset: i32, size: u32, alignment: u32) -> Self {
Self {
offset,
size,
alignment,
initialized: false,
}
}
}
pub fn demote_sret(config: &SretDemotionConfig, local_offset: &mut i32) -> SretFrameSlot {
let align = config.struct_alignment;
let align_mask = (align as i32) - 1;
*local_offset = (*local_offset + align_mask) & !align_mask;
let slot_offset = *local_offset;
*local_offset += config.struct_size as i32;
SretFrameSlot::new(slot_offset, config.struct_size, align)
}
#[derive(Debug, Clone)]
pub struct CalleeSavedReg {
pub reg: u16,
pub frame_offset: i32,
pub size: u32,
pub is_used: bool,
pub can_eliminate: bool,
pub allocated: bool,
}
impl CalleeSavedReg {
pub fn new(reg: u16, frame_offset: i32, size: u32) -> Self {
Self {
reg,
frame_offset,
size,
is_used: false,
can_eliminate: false,
allocated: false,
}
}
}
#[derive(Debug, Clone)]
pub struct CalleeSavedAssignment {
pub saved_regs: Vec<CalleeSavedReg>,
pub saved_area_size: i64,
pub has_conditional_saves: bool,
pub base_offset: i32,
}
impl CalleeSavedAssignment {
pub fn new() -> Self {
Self {
saved_regs: Vec::new(),
saved_area_size: 0,
has_conditional_saves: false,
base_offset: 0,
}
}
pub fn add_reg(&mut self, reg: u16, size: u32) {
let offset = self.base_offset - self.saved_area_size as i32 - size as i32;
let csr = CalleeSavedReg::new(reg, offset, size);
self.saved_regs.push(csr);
self.saved_area_size += size as i64;
}
pub fn offset_for(&self, reg: u16) -> Option<i32> {
self.saved_regs
.iter()
.find(|csr| csr.reg == reg)
.map(|csr| csr.frame_offset)
}
pub fn eliminate_unused(&mut self) {
self.saved_regs.retain(|csr| csr.is_used);
let mut cur_offset = self.base_offset;
for csr in &mut self.saved_regs {
cur_offset -= csr.size as i32;
csr.frame_offset = cur_offset;
}
self.saved_area_size = (self.base_offset - cur_offset) as i64;
}
pub fn used_regs(&self) -> Vec<u16> {
self.saved_regs
.iter()
.filter(|csr| csr.is_used)
.map(|csr| csr.reg)
.collect()
}
pub fn is_saved(&self, reg: u16) -> bool {
self.saved_regs
.iter()
.any(|csr| csr.reg == reg && csr.is_used)
}
}
#[derive(Debug, Clone)]
pub struct X86FrameLoweringExt {
pub call_conv: FrameCallConv,
pub frame_size: i64,
pub uses_frame_pointer: bool,
pub shrink_wrap: ShrinkWrapConfig,
pub shrink_result: Option<ShrinkWrapResult>,
pub slot_graph: SlotInterferenceGraph,
pub slot_allocation: SlotAllocation,
pub frame_access: FrameAccessConfig,
pub access_decisions: HashMap<usize, FrameAccessDecision>,
pub emergency_slots: EmergencySpillSlotManager,
pub realign_config: StackRealignConfig,
pub segmented_config: SegmentedStackConfig,
pub safestack_config: SafeStackConfig,
pub stack_clash_config: StackClashProtectionConfig,
pub patchpoint_config: PatchPointConfig,
pub varargs_config: VarArgsConfig,
pub sret_config: SretDemotionConfig,
pub callee_saved: CalleeSavedAssignment,
pub reserved_emergency_slots: Vec<EmergencySpillSlot>,
pub stack_probes: Vec<ProbeInstr>,
pub varargs_frame: Option<VarArgsFrameResult>,
pub sret_slot: Option<SretFrameSlot>,
pub stats: FrameLoweringExtStats,
}
#[derive(Debug, Clone, Default)]
pub struct FrameLoweringExtStats {
pub shrink_wrapped_saves: usize,
pub slots_merged: usize,
pub colors_used: usize,
pub emergency_spills: u64,
pub probes_emitted: usize,
pub fp_eliminated: bool,
pub final_frame_size: i64,
pub realignment_applied: bool,
pub segmented_active: bool,
pub safestack_active: bool,
pub stack_clash_active: bool,
}
impl X86FrameLoweringExt {
pub fn new(call_conv: FrameCallConv) -> Self {
Self {
call_conv,
frame_size: 0,
uses_frame_pointer: true,
shrink_wrap: ShrinkWrapConfig::default(),
shrink_result: None,
slot_graph: SlotInterferenceGraph::new(),
slot_allocation: SlotAllocation::new(),
frame_access: FrameAccessConfig::default(),
access_decisions: HashMap::new(),
emergency_slots: EmergencySpillSlotManager::new(DEFAULT_EMERGENCY_SLOTS, 0),
realign_config: StackRealignConfig::default(),
segmented_config: SegmentedStackConfig::default(),
safestack_config: SafeStackConfig::default(),
stack_clash_config: StackClashProtectionConfig::default(),
patchpoint_config: PatchPointConfig::default(),
varargs_config: VarArgsConfig::default(),
sret_config: SretDemotionConfig::default(),
callee_saved: CalleeSavedAssignment::new(),
reserved_emergency_slots: Vec::new(),
stack_probes: Vec::new(),
varargs_frame: None,
sret_slot: None,
stats: FrameLoweringExtStats::default(),
}
}
pub fn analyze_register_usage(
&self,
blocks: &[MachineBasicBlock],
) -> Vec<(u16, BTreeSet<usize>)> {
let candidates = if self.call_conv.is_64bit() {
if self.call_conv == FrameCallConv::Win64 {
SHRINK_WRAP_CANDIDATES_WIN64
} else {
SHRINK_WRAP_CANDIDATES_SYSV
}
} else {
&[]
};
candidates
.iter()
.map(|®| {
let mut used_blocks = BTreeSet::new();
for (i, block) in blocks.iter().enumerate() {
for instr in &block.instructions {
if Self::instr_uses_reg(instr, reg) {
used_blocks.insert(i);
break;
}
}
}
(reg, used_blocks)
})
.collect()
}
fn instr_uses_reg(instr: &MachineInstr, reg: u16) -> bool {
instr
.operands
.iter()
.any(|op| matches!(op, MachineOperand::PhysReg(r) if *r == reg as u32))
}
pub fn compute_dominators(&self, blocks: &[MachineBasicBlock]) -> Vec<DomTreeNode> {
let n = blocks.len();
if n == 0 {
return Vec::new();
}
let mut doms: Vec<Option<usize>> = vec![None; n];
doms[0] = Some(0);
let mut changed = true;
while changed {
changed = false;
for i in 1..n {
let preds = &blocks[i].predecessors;
let mut new_idom: Option<usize> = None;
for &pred in preds {
if let Some(pdom) = doms[pred] {
new_idom = Some(match new_idom {
None => pdom,
Some(cur) => Self::intersect_dominators(pdom, cur, &doms),
});
}
}
if new_idom != doms[i] {
doms[i] = new_idom;
changed = true;
}
}
}
let mut nodes: Vec<DomTreeNode> = (0..n).map(DomTreeNode::new).collect();
for (i, dom) in doms.iter().enumerate() {
if let Some(idom) = dom {
if i != *idom {
nodes[*idom].children.push(i);
}
nodes[i].idom = Some(*idom);
}
}
Self::compute_dom_depths(&mut nodes, 0, 0);
nodes
}
fn intersect_dominators(a: usize, b: usize, doms: &[Option<usize>]) -> usize {
let mut finger1 = a;
let mut finger2 = b;
while finger1 != finger2 {
while finger1 > finger2 {
finger1 = doms[finger1].unwrap_or(0);
}
while finger2 > finger1 {
finger2 = doms[finger2].unwrap_or(0);
}
}
finger1
}
fn compute_dom_depths(nodes: &mut [DomTreeNode], node: usize, depth: u32) {
nodes[node].depth = depth;
let children = nodes[node].children.clone();
for child in children {
Self::compute_dom_depths(nodes, child, depth + 1);
}
}
pub fn compute_save_restore_placement(
&self,
reg_usage: &[(u16, BTreeSet<usize>)],
dom_tree: &[DomTreeNode],
blocks: &[MachineBasicBlock],
) -> ShrinkWrapResult {
let mut regions = Vec::new();
let mut save_blocks = HashSet::new();
let mut restore_blocks = HashSet::new();
for &(reg, ref used_blocks) in reg_usage {
if used_blocks.is_empty() {
continue;
}
let save_block = self.find_earliest_dominated_block(used_blocks, dom_tree, blocks);
let restore_block = self.find_latest_post_dominated_block(used_blocks, blocks);
let in_loop = false; let loop_depth = 0u32;
regions.push(SaveRestoreRegion {
reg,
save_block,
restore_block,
in_loop,
loop_depth,
can_hoist_save: !in_loop,
can_sink_restore: true,
});
save_blocks.insert(save_block);
restore_blocks.insert(restore_block);
}
let applied = !regions.is_empty();
let savings = regions.iter().filter(|r| r.is_save_optimal()).count()
+ regions.iter().filter(|r| r.is_restore_optimal()).count();
ShrinkWrapResult {
regions,
save_blocks,
restore_blocks,
applied,
savings,
}
}
fn find_earliest_dominated_block(
&self,
used_blocks: &BTreeSet<usize>,
dom_tree: &[DomTreeNode],
blocks: &[MachineBasicBlock],
) -> usize {
if used_blocks.is_empty() {
return 0;
}
let mut common_dom = *used_blocks.iter().next().unwrap();
for &block in used_blocks.iter().skip(1) {
common_dom = Self::intersect_block_dominator(common_dom, block, dom_tree);
}
common_dom
}
fn find_latest_post_dominated_block(
&self,
used_blocks: &BTreeSet<usize>,
blocks: &[MachineBasicBlock],
) -> usize {
used_blocks.iter().last().copied().unwrap_or(0)
}
fn intersect_block_dominator(a: usize, b: usize, dom_tree: &[DomTreeNode]) -> usize {
let mut finger1 = a;
let mut finger2 = b;
while finger1 != finger2 {
while finger1 > finger2 {
finger1 = dom_tree[finger1].idom.unwrap_or(0);
}
while finger2 > finger1 {
finger2 = dom_tree[finger2].idom.unwrap_or(0);
}
}
finger1
}
pub fn run_shrink_wrapping(&mut self, blocks: &[MachineBasicBlock]) {
if !self.shrink_wrap.enabled {
return;
}
let reg_usage = self.analyze_register_usage(blocks);
let dom_tree = self.compute_dominators(blocks);
let result = self.compute_save_restore_placement(®_usage, &dom_tree, blocks);
self.stats.shrink_wrapped_saves = result.savings;
self.shrink_result = Some(result);
}
pub fn build_slot_interference_graph(&mut self, slots: &[SlotLiveRange]) {
self.slot_graph = SlotInterferenceGraph::new();
for slot in slots {
self.slot_graph.add_slot(slot.clone());
}
self.slot_graph.build();
}
pub fn color_stack_slots(&mut self) {
let colors = self.slot_graph.greedy_color();
self.stats.colors_used = colors.values().collect::<HashSet<_>>().len();
let mut color_sizes: HashMap<usize, u32> = HashMap::new();
for (&slot_id, &color) in &colors {
let size = self
.slot_graph
.live_ranges
.get(&slot_id)
.map(|lr| lr.size)
.unwrap_or(8);
let entry = color_sizes.entry(color).or_insert(0);
*entry = (*entry).max(size);
}
let mut cur_offset = 0i32;
let mut sorted_colors: Vec<usize> = color_sizes.keys().copied().collect();
sorted_colors.sort();
let mut color_offsets: HashMap<usize, i32> = HashMap::new();
for color in sorted_colors {
let size = color_sizes[&color];
let align_mask = (size as i32) - 1;
cur_offset = (cur_offset + align_mask) & !align_mask;
color_offsets.insert(color, -cur_offset - size as i32);
cur_offset += size as i32;
}
for (&slot_id, &color) in &colors {
if let Some(&offset) = color_offsets.get(&color) {
self.slot_allocation.offsets.insert(slot_id, offset);
if let Some(lr) = self.slot_graph.live_ranges.get(&slot_id) {
self.slot_allocation.sizes.insert(slot_id, lr.size);
}
}
}
self.slot_allocation.total_local_size = cur_offset as i64;
self.slot_allocation.local_alignment = 16;
self.slot_allocation.slot_count_before = colors.len();
self.slot_allocation.slot_count_after = color_offsets.len();
self.slot_allocation.merged_slots = color_offsets.len() < colors.len();
self.stats.slots_merged =
self.slot_allocation.slot_count_before - self.slot_allocation.slot_count_after;
}
pub fn select_frame_access(&mut self, blocks: &[MachineBasicBlock]) {
if self.frame_access.preferred_mode == FrameAccessMode::StackPointer
|| (self.frame_access.preferred_mode == FrameAccessMode::Automatic
&& self.frame_access.allow_fp_elim
&& !self.frame_access.has_dynamic_alloca
&& !self.frame_access.has_vla)
{
for (&slot_id, &offset) in &self.slot_allocation.offsets {
let abs_offset = offset.unsigned_abs() as i32;
if abs_offset <= self.frame_access.max_rsp_offset {
self.access_decisions.insert(
slot_id,
FrameAccessDecision::SpRelative { offset: abs_offset },
);
} else {
self.access_decisions
.insert(slot_id, FrameAccessDecision::FpRelative { offset });
}
}
self.stats.fp_eliminated = true;
} else {
for (&slot_id, &offset) in &self.slot_allocation.offsets {
self.access_decisions
.insert(slot_id, FrameAccessDecision::FpRelative { offset });
}
self.stats.fp_eliminated = false;
}
}
pub fn init_emergency_slots(&mut self, count: usize) {
let base_offset = -(self.callee_saved.saved_area_size as i32) - EMERGENCY_SLOT_SIZE as i32;
self.emergency_slots = EmergencySpillSlotManager::new(count, base_offset);
}
pub fn allocate_emergency_slot(&mut self, reg: u16, at_inst: usize) -> Option<usize> {
let result = self.emergency_slots.allocate(reg, at_inst);
self.stats.emergency_spills = self.emergency_slots.allocation_count;
result
}
pub fn enable_dynamic_realignment(&mut self, alignment: u32) {
self.realign_config.needed = true;
self.realign_config.required_alignment = alignment;
self.stats.realignment_applied = true;
}
pub fn gen_realign_prologue(&self) -> RealignPrologue {
generate_realign_prologue(&self.realign_config, self.frame_size)
}
pub fn gen_realign_epilogue(&self) -> RealignEpilogue {
generate_realign_epilogue(&self.realign_config)
}
pub fn enable_segmented_stacks(&mut self, segment_size: i64) {
self.segmented_config.enabled = true;
self.segmented_config.segment_size = segment_size;
self.stats.segmented_active = true;
}
pub fn gen_segmented_prologue(&self) -> SegmentedStackPrologue {
generate_segmented_prologue(&self.segmented_config, self.frame_size)
}
pub fn enable_safestack(&mut self) {
self.safestack_config.enabled = true;
self.stats.safestack_active = true;
}
pub fn layout_safestack(&self, objects: &[SafeStackObject]) -> SafeStackFrame {
layout_safe_stack(objects, &self.safestack_config)
}
pub fn enable_stack_clash_protection(&mut self) {
self.stack_clash_config.enabled = true;
self.stats.stack_clash_active = true;
}
pub fn gen_stack_probes(&mut self) {
self.stack_probes = generate_stack_probes(&self.stack_clash_config, self.frame_size);
self.stats.probes_emitted = self.stack_probes.len();
}
pub fn setup_varargs_frame(&mut self, config: VarArgsConfig) {
self.varargs_config = config;
self.varargs_frame = Some(build_varargs_frame(&self.varargs_config));
}
pub fn demote_sret_to_frame(&mut self, config: SretDemotionConfig) {
let mut local_offset = -self.callee_saved.saved_area_size as i32;
let slot = demote_sret(&config, &mut local_offset);
self.sret_slot = Some(slot);
self.sret_config = config;
}
pub fn assign_callee_saved_regs(&mut self, used_regs: &[u16]) {
let all_candidates = if self.call_conv.is_64bit() {
if self.call_conv == FrameCallConv::Win64 {
SHRINK_WRAP_CANDIDATES_WIN64
} else {
SHRINK_WRAP_CANDIDATES_SYSV
}
} else {
&[]
};
let used_set: HashSet<u16> = used_regs.iter().copied().collect();
for ® in all_candidates {
let mut csr = CalleeSavedReg::new(reg, 0, 8);
csr.is_used = used_set.contains(®);
self.callee_saved.saved_regs.push(csr);
}
let mut cur_offset = self.callee_saved.base_offset;
for csr in &mut self.callee_saved.saved_regs {
if csr.is_used {
cur_offset -= csr.size as i32;
csr.frame_offset = cur_offset;
}
}
self.callee_saved.saved_area_size = (self.callee_saved.base_offset - cur_offset) as i64;
}
pub fn compute_final_frame_size(&mut self) -> i64 {
let mut total = 0i64;
total += 8;
if self.uses_frame_pointer {
total += 8;
}
total += self.callee_saved.saved_area_size;
total += self.emergency_slots.total_size();
total += self.slot_allocation.total_local_size;
if let Some(ref vf) = self.varargs_frame {
match vf {
VarArgsFrameResult::SystemV(sysv) => {
total += sysv.total_save_area_size;
}
VarArgsFrameResult::Win64(win) => {
total += win.shadow_space_size;
}
_ => {}
}
}
if let Some(ref sret) = self.sret_slot {
total += sret.size as i64;
}
let alignment = self.call_conv.stack_alignment() as i64;
total = (total + alignment - 1) & !(alignment - 1);
if self.call_conv.has_shadow_space() {
total += self.call_conv.shadow_space_size();
}
self.frame_size = total;
self.stats.final_frame_size = total;
total
}
}
pub fn make_x86_64_frame_lowering_ext_sysv() -> X86FrameLoweringExt {
X86FrameLoweringExt::new(FrameCallConv::SystemVAMD64)
}
pub fn make_x86_64_frame_lowering_ext_win64() -> X86FrameLoweringExt {
X86FrameLoweringExt::new(FrameCallConv::Win64)
}
pub fn make_x86_32_frame_lowering_ext_cdecl() -> X86FrameLoweringExt {
X86FrameLoweringExt::new(FrameCallConv::Cdecl32)
}
pub fn make_frame_lowering_ext_with_config(
call_conv: FrameCallConv,
shrink_wrap: ShrinkWrapConfig,
frame_access: FrameAccessConfig,
stack_clash: StackClashProtectionConfig,
) -> X86FrameLoweringExt {
let mut ext = X86FrameLoweringExt::new(call_conv);
ext.shrink_wrap = shrink_wrap;
ext.frame_access = frame_access;
ext.stack_clash_config = stack_clash;
ext
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_call_conv_properties() {
let sysv = FrameCallConv::SystemVAMD64;
assert!(sysv.has_red_zone());
assert!(!sysv.has_shadow_space());
assert!(sysv.is_64bit());
assert_eq!(sysv.red_zone_size(), 128);
assert_eq!(sysv.shadow_space_size(), 0);
assert_eq!(sysv.stack_alignment(), 16);
let win64 = FrameCallConv::Win64;
assert!(!win64.has_red_zone());
assert!(win64.has_shadow_space());
assert!(win64.is_64bit());
assert_eq!(win64.red_zone_size(), 0);
assert_eq!(win64.shadow_space_size(), 32);
let cdecl = FrameCallConv::Cdecl32;
assert!(!cdecl.has_red_zone());
assert!(!cdecl.has_shadow_space());
assert!(!cdecl.is_64bit());
}
#[test]
fn test_frame_call_conv_is_register_based() {
assert!(FrameCallConv::SystemVAMD64.is_register_based());
assert!(FrameCallConv::Win64.is_register_based());
assert!(FrameCallConv::Fastcall32.is_register_based());
assert!(!FrameCallConv::Cdecl32.is_register_based());
assert!(!FrameCallConv::Stdcall32.is_register_based());
}
#[test]
fn test_empty_interference_graph() {
let graph = SlotInterferenceGraph::new();
assert!(graph.nodes.is_empty());
assert!(graph.edges.is_empty());
}
#[test]
fn test_add_slot() {
let mut graph = SlotInterferenceGraph::new();
let range = SlotLiveRange {
slot_id: 1,
size: 8,
alignment: 8,
live_blocks: [0, 1].iter().copied().collect(),
start: 0,
end: 10,
is_vector: false,
is_emergency: false,
is_callee_saved: false,
spill_weight: 1.0,
};
graph.add_slot(range);
assert_eq!(graph.nodes.len(), 1);
assert!(graph.edges.contains_key(&1));
}
#[test]
fn test_interference_non_overlapping() {
let mut graph = SlotInterferenceGraph::new();
let range1 = SlotLiveRange {
slot_id: 1,
size: 8,
alignment: 8,
live_blocks: [0].iter().copied().collect(),
start: 0,
end: 5,
is_vector: false,
is_emergency: false,
is_callee_saved: false,
spill_weight: 1.0,
};
let range2 = SlotLiveRange {
slot_id: 2,
size: 8,
alignment: 8,
live_blocks: [1].iter().copied().collect(),
start: 5,
end: 10,
is_vector: false,
is_emergency: false,
is_callee_saved: false,
spill_weight: 1.0,
};
graph.add_slot(range1);
graph.add_slot(range2);
graph.build();
let edges = graph.edges.get(&1).unwrap();
assert!(!edges.contains(&2));
}
#[test]
fn test_interference_overlapping() {
let mut graph = SlotInterferenceGraph::new();
let range1 = SlotLiveRange {
slot_id: 1,
size: 8,
alignment: 8,
live_blocks: [0, 1].iter().copied().collect(),
start: 0,
end: 10,
is_vector: false,
is_emergency: false,
is_callee_saved: false,
spill_weight: 1.0,
};
let range2 = SlotLiveRange {
slot_id: 2,
size: 8,
alignment: 8,
live_blocks: [0, 1].iter().copied().collect(),
start: 2,
end: 8,
is_vector: false,
is_emergency: false,
is_callee_saved: false,
spill_weight: 1.0,
};
graph.add_slot(range1);
graph.add_slot(range2);
graph.build();
let edges = graph.edges.get(&1).unwrap();
assert!(edges.contains(&2));
}
#[test]
fn test_greedy_coloring_basic() {
let mut graph = SlotInterferenceGraph::new();
for i in 0..4 {
let range = SlotLiveRange {
slot_id: i,
size: 8,
alignment: 8,
live_blocks: [i].iter().copied().collect(),
start: i * 10,
end: i * 10 + 5,
is_vector: false,
is_emergency: false,
is_callee_saved: false,
spill_weight: (i + 1) as f64,
};
graph.add_slot(range);
}
let colors = graph.greedy_color();
assert_eq!(colors.len(), 4);
}
#[test]
fn test_dominates() {
let mut nodes = vec![
DomTreeNode::new(0),
DomTreeNode::new(1),
DomTreeNode::new(2),
];
nodes[1].idom = Some(0);
nodes[2].idom = Some(1);
nodes[0].children.push(1);
nodes[1].children.push(2);
assert!(nodes[0].dominates(&nodes[1], &nodes));
assert!(nodes[0].dominates(&nodes[2], &nodes));
assert!(nodes[1].dominates(&nodes[2], &nodes));
assert!(!nodes[1].dominates(&nodes[0], &nodes));
assert!(!nodes[2].dominates(&nodes[0], &nodes));
}
#[test]
fn test_compute_dominators_simple() {
let ext = X86FrameLoweringExt::new(FrameCallConv::SystemVAMD64);
let mut b0 = MachineBasicBlock::new(0);
let mut b1 = MachineBasicBlock::new(1);
let mut b2 = MachineBasicBlock::new(2);
b0.successors.push(1);
b0.successors.push(2);
b1.predecessors.push(0);
b2.predecessors.push(0);
let blocks = vec![b0, b1, b2];
let doms = ext.compute_dominators(&blocks);
assert_eq!(doms.len(), 3);
assert_eq!(doms[0].idom, Some(0));
assert_eq!(doms[1].idom, Some(0));
assert_eq!(doms[2].idom, Some(0));
}
#[test]
fn test_emergency_slot_allocate_free() {
let mut mgr = EmergencySpillSlotManager::new(2, -16);
assert_eq!(mgr.slots.len(), 2);
let idx = mgr.allocate(RBX, 5);
assert!(idx.is_some());
assert!(!mgr.slots[idx.unwrap()].is_available());
mgr.free(idx.unwrap());
assert!(mgr.slots[idx.unwrap()].is_available());
}
#[test]
fn test_emergency_slot_exhaustion() {
let mut mgr = EmergencySpillSlotManager::new(1, -16);
let _i1 = mgr.allocate(RBX, 1);
let i2 = mgr.allocate(R12, 2);
assert!(i2.is_some());
assert_eq!(mgr.exhaustion_count, 1);
}
#[test]
fn test_alignment_mask() {
assert_eq!(alignment_mask(16), !15i64);
assert_eq!(alignment_mask(32), !31i64);
assert_eq!(alignment_mask(64), !63i64);
}
#[test]
fn test_generate_realign_prologue() {
let config = StackRealignConfig {
needed: true,
required_alignment: 32,
use_drap: false,
drap_register: DRAP_REGISTER_ALT,
has_variable_objects: false,
};
let prologue = generate_realign_prologue(&config, 64);
assert!(!prologue.instructions.is_empty());
assert!(!prologue.drap_setup);
}
#[test]
fn test_generate_realign_prologue_with_drap() {
let config = StackRealignConfig {
needed: true,
required_alignment: 64,
use_drap: true,
drap_register: R10,
has_variable_objects: true,
};
let prologue = generate_realign_prologue(&config, 128);
assert!(prologue.drap_setup);
}
#[test]
fn test_segmented_prologue_small_frame() {
let config = SegmentedStackConfig::default();
let prologue = generate_segmented_prologue(&config, 512);
assert!(!prologue.needs_expansion);
}
#[test]
fn test_segmented_prologue_large_frame() {
let mut config = SegmentedStackConfig::default();
config.segment_size = 4096;
let prologue = generate_segmented_prologue(&config, 16384);
assert!(prologue.needs_expansion);
}
#[test]
fn test_classify_safe_stack_object() {
let mut obj = SafeStackObject::new(0, 8, 8);
assert!(classify_safe_stack_object(&obj));
obj.address_taken = true;
assert!(!classify_safe_stack_object(&obj));
}
#[test]
fn test_layout_safe_stack() {
let objects = vec![
SafeStackObject {
id: 0,
size: 8,
alignment: 8,
offset: 0,
address_taken: false,
has_safe_pointers: false,
},
SafeStackObject {
id: 1,
size: 16,
alignment: 16,
offset: 0,
address_taken: true,
has_safe_pointers: false,
},
];
let config = SafeStackConfig::default();
let frame = layout_safe_stack(&objects, &config);
assert_eq!(frame.safe_objects.len(), 1);
assert_eq!(frame.unsafe_objects.len(), 1);
assert!(frame.safe_frame_size > 0);
assert!(frame.unsafe_frame_size > 0);
}
#[test]
fn test_no_probes_for_small_frame() {
let config = StackClashProtectionConfig::default();
let probes = generate_stack_probes(&config, 512);
assert!(probes.is_empty());
}
#[test]
fn test_probes_for_large_frame() {
let config = StackClashProtectionConfig::default();
let probes = generate_stack_probes(&config, 16384);
assert!(!probes.is_empty());
assert!(probes.len() >= 2);
}
#[test]
fn test_needs_stack_clash_protection() {
let config = StackClashProtectionConfig::default();
assert!(!needs_stack_clash_protection(&config, 2048));
assert!(needs_stack_clash_protection(&config, 8192));
}
#[test]
fn test_stack_map_location() {
let reg_loc = StackMapLocation::new_register(RAX, 8);
assert_eq!(reg_loc.kind, StackMapLocKind::Register);
assert_eq!(reg_loc.reg, RAX);
let direct = StackMapLocation::new_direct(RSP, -16, 8);
assert_eq!(direct.kind, StackMapLocKind::Direct);
assert_eq!(direct.offset, -16);
let constant = StackMapLocation::new_constant(42);
assert_eq!(constant.kind, StackMapLocKind::Constant);
assert_eq!(constant.constant, 42);
}
#[test]
fn test_build_stack_map_entry() {
let locs = vec![StackMapLocation::new_register(R10, 8)];
let entry = build_stack_map_entry(1, 0x100, locs.clone(), 64, 0);
assert_eq!(entry.id, 1);
assert_eq!(entry.code_offset, 0x100);
assert_eq!(entry.num_locations, 1);
assert_eq!(entry.frame_size, 64);
}
#[test]
fn test_sysv_varargs_frame_no_fixed_args() {
let frame = SysVVarArgsFrame::new(0);
assert_eq!(frame.num_gp_slots, 6);
assert_eq!(frame.num_xmm_slots, 8);
assert!(frame.total_save_area_size > 0);
}
#[test]
fn test_sysv_varargs_frame_with_fixed_args() {
let frame = SysVVarArgsFrame::new(2);
assert_eq!(frame.num_gp_slots, 4); assert_eq!(frame.num_xmm_slots, 6); }
#[test]
fn test_win64_varargs_frame() {
let frame = Win64VarArgsFrame::new();
assert_eq!(frame.shadow_space_size, 32);
}
#[test]
fn test_build_varargs_frame_sysv() {
let config = VarArgsConfig {
call_conv: FrameCallConv::SystemVAMD64,
has_varargs: true,
num_fixed_args: 1,
..Default::default()
};
let result = build_varargs_frame(&config);
match result {
VarArgsFrameResult::SystemV(sysv) => {
assert_eq!(sysv.num_gp_slots, 5);
}
_ => panic!("Expected SystemV varargs frame"),
}
}
#[test]
fn test_demote_sret() {
let config = SretDemotionConfig {
needed: true,
struct_size: 32,
struct_alignment: 16,
..Default::default()
};
let mut local_offset = -16i32;
let slot = demote_sret(&config, &mut local_offset);
assert_eq!(slot.size, 32);
assert_eq!(slot.alignment, 16);
assert_eq!(local_offset, -48);
}
#[test]
fn test_callee_saved_assignment_add() {
let mut csa = CalleeSavedAssignment::new();
csa.add_reg(RBX, 8);
csa.add_reg(R12, 8);
assert_eq!(csa.saved_regs.len(), 2);
assert_eq!(csa.saved_area_size, 16);
}
#[test]
fn test_callee_saved_offset_for() {
let mut csa = CalleeSavedAssignment::new();
csa.add_reg(RBX, 8);
csa.add_reg(R12, 8);
let rbx_off = csa.offset_for(RBX);
assert!(rbx_off.is_some());
assert!(rbx_off.unwrap() < 0);
let r13_off = csa.offset_for(R13);
assert!(r13_off.is_none());
}
#[test]
fn test_make_x86_64_frame_lowering_ext() {
let ext = make_x86_64_frame_lowering_ext_sysv();
assert_eq!(ext.call_conv, FrameCallConv::SystemVAMD64);
assert!(ext.uses_frame_pointer);
}
#[test]
fn test_make_frame_lowering_ext_with_config() {
let ext = make_frame_lowering_ext_with_config(
FrameCallConv::Win64,
ShrinkWrapConfig::default(),
FrameAccessConfig::default(),
StackClashProtectionConfig::default(),
);
assert_eq!(ext.call_conv, FrameCallConv::Win64);
}
#[test]
fn test_compute_final_frame_size_basic() {
let mut ext = X86FrameLoweringExt::new(FrameCallConv::SystemVAMD64);
let used_regs = vec![RBX, R12];
ext.assign_callee_saved_regs(&used_regs);
let slots: Vec<SlotLiveRange> = vec![SlotLiveRange {
slot_id: 0,
size: 8,
alignment: 8,
live_blocks: [0].iter().copied().collect(),
start: 0,
end: 1,
is_vector: false,
is_emergency: false,
is_callee_saved: false,
spill_weight: 1.0,
}];
ext.build_slot_interference_graph(&slots);
ext.color_stack_slots();
let size = ext.compute_final_frame_size();
assert!(size > 0);
assert_eq!(size % 16, 0); }
#[test]
fn test_run_shrink_wrapping_analyzes_usage() {
let mut ext = X86FrameLoweringExt::new(FrameCallConv::SystemVAMD64);
let blocks = vec![MachineBasicBlock::new(0), MachineBasicBlock::new(1)];
ext.run_shrink_wrapping(&blocks);
if let Some(ref result) = ext.shrink_result {
assert_eq!(result.savings, 0);
}
}
#[test]
fn test_select_frame_access_sp_relative() {
let mut ext = X86FrameLoweringExt::new(FrameCallConv::SystemVAMD64);
ext.frame_access = FrameAccessConfig {
preferred_mode: FrameAccessMode::Automatic,
allow_fp_elim: true,
max_rsp_offset: 128,
has_dynamic_alloca: false,
has_vla: false,
has_alloca: false,
is_leaf: true,
};
ext.slot_allocation.offsets.insert(0, -8);
ext.slot_allocation.sizes.insert(0, 8);
ext.slot_allocation.total_local_size = 8;
ext.select_frame_access(&[]);
let decision = ext.access_decisions.get(&0);
assert!(decision.is_some());
assert!(decision.unwrap().is_sp_relative());
}
}