From 76eb7df9f020ab933370a7387393406134d62447 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 22 Sep 2017 11:16:07 -0700 Subject: [PATCH] Add an isa::StackRef type. This contains encoding details for a stack reference: The base register and offset to use in the specific instruction encoding. Generate StackRef objects called in_stk0 etc for the binemit recipe code. All binemit recipes need to compute base pointer offsets for stack references, so have the automatically generated code do it. --- cranelift/src/filetest/binemit.rs | 7 +++ lib/cretonne/meta/cdsl/registers.py | 10 ++++ lib/cretonne/meta/gen_binemit.py | 32 +++++++---- lib/cretonne/src/isa/mod.rs | 2 + lib/cretonne/src/isa/riscv/binemit.rs | 2 +- lib/cretonne/src/isa/stack.rs | 82 +++++++++++++++++++++++++++ 6 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 lib/cretonne/src/isa/stack.rs diff --git a/cranelift/src/filetest/binemit.rs b/cranelift/src/filetest/binemit.rs index 0ea634548a..f1a493c749 100644 --- a/cranelift/src/filetest/binemit.rs +++ b/cranelift/src/filetest/binemit.rs @@ -104,6 +104,13 @@ impl SubTest for TestBinEmit { // value locations. The current error reporting is just crashing... let mut func = func.into_owned(); + // Fix the stack frame layout so we can test spill/fill encodings. + let min_offset = func.stack_slots + .keys() + .map(|ss| func.stack_slots[ss].offset) + .min(); + func.stack_slots.frame_size = min_offset.map(|off| (-off) as u32); + let is_compressed = isa.flags().is_compressed(); // Give an encoding to any instruction that doesn't already have one. diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py index 880f9096d4..89c6db0a28 100644 --- a/lib/cretonne/meta/cdsl/registers.py +++ b/lib/cretonne/meta/cdsl/registers.py @@ -352,3 +352,13 @@ class Stack(object): def __init__(self, rc): # type: (RegClass) -> None self.regclass = rc + + def stack_base_mask(self): + # type: () -> str + """ + Get the StackBaseMask to use for this operand. + + This is a mask of base registers that can be supported by this operand. + """ + # TODO: Make this configurable instead of just using the SP. + return 'StackBaseMask(1)' diff --git a/lib/cretonne/meta/gen_binemit.py b/lib/cretonne/meta/gen_binemit.py index fddadf64e5..39cab5ab20 100644 --- a/lib/cretonne/meta/gen_binemit.py +++ b/lib/cretonne/meta/gen_binemit.py @@ -66,11 +66,16 @@ def gen_recipe(recipe, fmt): 'let {} = divert.reg(args[{}], &func.locations);' .format(v, i)) elif isinstance(arg, Stack): - v = 'in_ss{}'.format(i) + v = 'in_stk{}'.format(i) args += ', ' + v - fmt.line( - 'let {} = func.locations[args[{}]].unwrap_stack();' - .format(v, i)) + with fmt.indented( + 'let {} = StackRef::masked('.format(v), + ').unwrap();'): + fmt.format( + 'func.locations[args[{}]].unwrap_stack(),', + i) + fmt.format('{},', arg.stack_base_mask()) + fmt.line('&func.stack_slots,') # Pass arguments in this order: inputs, imm_fields, outputs. for f in iform.imm_fields: @@ -86,15 +91,20 @@ def gen_recipe(recipe, fmt): if isinstance(res, RegClass): v = 'out_reg{}'.format(i) args += ', ' + v - fmt.line( - 'let {} = func.locations[results[{}]].unwrap_reg();' - .format(v, i)) + fmt.format( + 'let {} = func.locations[results[{}]].unwrap_reg();', + v, i) elif isinstance(res, Stack): - v = 'out_ss{}'.format(i) + v = 'out_stk{}'.format(i) args += ', ' + v - fmt.line( - 'let {} = func.locations[results[{}]].unwrap_stack();' - .format(v, i)) + with fmt.indented( + 'let {} = StackRef::masked('.format(v), + ').unwrap();'): + fmt.format( + 'func.locations[results[{}]].unwrap_stack(),', + i) + fmt.format('{},', res.stack_base_mask()) + fmt.line('&func.stack_slots,') # Special handling for regmove instructions. Update the register # diversion tracker. diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 1ccacce8b1..f6c95edec8 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -43,6 +43,7 @@ pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind, BranchRange}; pub use isa::encoding::{Encoding, EncInfo}; pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex, regs_overlap}; +pub use isa::stack::{StackBase, StackBaseMask, StackRef}; use binemit; use flowgraph; @@ -68,6 +69,7 @@ pub mod registers; mod encoding; mod enc_tables; mod constraints; +mod stack; /// Returns a builder that can create a corresponding `TargetIsa` /// or `Err(LookupError::Unsupported)` if not enabled. diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index 7a658267b9..61a497000f 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -2,7 +2,7 @@ use binemit::{CodeSink, Reloc, bad_encoding}; use ir::{Function, Inst, InstructionData}; -use isa::RegUnit; +use isa::{RegUnit, StackRef, StackBaseMask}; use predicates::is_signed_int; use regalloc::RegDiversions; diff --git a/lib/cretonne/src/isa/stack.rs b/lib/cretonne/src/isa/stack.rs new file mode 100644 index 0000000000..cfb054dd66 --- /dev/null +++ b/lib/cretonne/src/isa/stack.rs @@ -0,0 +1,82 @@ +//! Low-level details of stack accesses. +//! +//! The `ir::StackSlots` type deals with stack slots and stack frame layout. The `StackRef` type +//! defined in this module expresses the low-level details of accessing a stack slot from an +//! encoded instruction. + +use ir::stackslot::{StackSlots, StackOffset}; +use ir::StackSlot; + +/// A method for referencing a stack slot in the current stack frame. +/// +/// Stack slots are addressed with a constant offset from a base register. The base can be the +/// stack pointer, the frame pointer, or (in the future) a zone register pointing to an inner zone +/// of a large stack frame. +#[derive(Clone, Copy, Debug)] +pub struct StackRef { + /// The base register to use for addressing. + pub base: StackBase, + + /// Immediate offset from the base register to the first byte of the stack slot. + pub offset: StackOffset, +} + +impl StackRef { + /// Get a reference to the stack slot `ss` using one of the base pointers in `mask`. + pub fn masked(ss: StackSlot, mask: StackBaseMask, frame: &StackSlots) -> Option { + let size = frame.frame_size.expect( + "Stack layout must be computed before referencing stack slots", + ); + // Offsets relative to the caller's stack pointer. + let offset = frame[ss].offset; + + // Try an SP-relative reference. + if mask.contains(StackBase::SP) { + // Offset where SP is pointing. (All ISAs have stacks growing downwards.) + let sp_offset = -(size as StackOffset); + return Some(StackRef { + base: StackBase::SP, + offset: offset - sp_offset, + }); + } + + // No reference possible with this mask. + None + } +} + +/// Generic base register for referencing stack slots. +/// +/// Most ISAs have a stack pointer and an optional frame pointer, so provide generic names for +/// those two base pointers. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum StackBase { + /// Use the stack pointer. + SP = 0, + + /// Use the frame pointer (if one is present). + FP = 1, + + /// Use an explicit zone pointer in a general-purpose register. + Zone = 2, +} + +/// Bit mask of supported stack bases. +/// +/// Many instruction encodings can use different base registers while others only work with the +/// stack pointer, say. A `StackBaseMask` is a bit mask of supported stack bases for a given +/// instruction encoding. +/// +/// This behaves like a set of `StackBase` variants. +/// +/// The internal representation as a `u8` is public because stack base masks are used in constant +/// tables generated from the Python encoding definitions. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StackBaseMask(pub u8); + +impl StackBaseMask { + /// Check if this mask contains the `base` variant. + pub fn contains(self, base: StackBase) -> bool { + self.0 & (1 << base as usize) != 0 + } +}