Track stack slot kinds.

Add a StackSlotKind enumeration to help keep track of the different
kinds of stack slots supported:

- Incoming and outgoing function arguments on the stack.
- Spill slots and locals.

Change the text format syntax for declaring a stack slot to use a kind
keyword rather than just 'stack_slot'.
This commit is contained in:
Jakob Stoklund Olesen
2017-06-16 10:35:01 -07:00
parent 66bc0a9c8b
commit 91d919c11a
9 changed files with 110 additions and 38 deletions

View File

@@ -1,7 +1,7 @@
test verifier
function %average(i32, i32) -> f32 {
ss1 = stack_slot 8 ; Stack slot for ``sum``.
ss1 = local 8 ; Stack slot for ``sum``.
ebb1(v1: i32, v2: i32):
v3 = f64const 0x0.0

View File

@@ -465,9 +465,9 @@ frame. The stack frame is divided into fixed-size stack slots that are
allocated in the :term:`function preamble`. Stack slots are not typed, they
simply represent a contiguous sequence of bytes in the stack frame.
.. inst:: SS = stack_slot Bytes, Flags...
.. inst:: SS = local Bytes, Flags...
Allocate a stack slot in the preamble.
Allocate a stack slot for a local variable in the preamble.
If no alignment is specified, Cretonne will pick an appropriate alignment
for the stack slot based on its size and access patterns.

View File

@@ -96,8 +96,10 @@ ebb0(v90: i32, v91: f32):
; Stack slot references
function %stack() {
ss10 = stack_slot 8
ss2 = stack_slot 4
ss10 = spill_slot 8
ss2 = local 4
ss3 = incoming_arg 4
ss4 = outgoing_arg 4
ebb0:
v1 = stack_load.i32 ss10
@@ -106,8 +108,10 @@ ebb0:
stack_store v2, ss2
}
; sameln: function %stack() {
; nextln: $ss10 = stack_slot 8
; nextln: $ss2 = stack_slot 4
; nextln: $ss10 = spill_slot 8
; nextln: $ss2 = local 4
; nextln: $ss3 = incoming_arg 4
; nextln: $ss4 = outgoing_arg 4
; check: ebb0:
; nextln: $v1 = stack_load.i32 $ss10

View File

@@ -22,7 +22,7 @@ pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension, ArgumentPurpos
pub use ir::types::Type;
pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable, FuncRef, SigRef};
pub use ir::instructions::{Opcode, InstructionData, VariableArgs, ValueList, ValueListPool};
pub use ir::stackslot::StackSlotData;
pub use ir::stackslot::{StackSlotKind, StackSlotData};
pub use ir::jumptable::JumpTableData;
pub use ir::valueloc::{ValueLoc, ArgumentLoc};
pub use ir::dfg::{DataFlowGraph, ValueDef};

View File

@@ -3,43 +3,104 @@
//! The `StackSlotData` struct keeps track of a single stack slot in a function.
//!
use std::fmt::{self, Display, Formatter};
use std::fmt;
use std::str::FromStr;
/// The kind of a stack slot.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StackSlotKind {
/// A spill slot. This is a stack slot created by the register allocator.
SpillSlot,
/// A local variable. This is a chunk of local stack memory for use by the `stack_load` and
/// `stack_store` instructions.
Local,
/// An incoming function argument.
///
/// If the current function has more arguments than fits in registers, the remaining arguments
/// are passed on the stack by the caller. These incoming arguments are represented as SSA
/// values assigned to incoming stack slots.
IncomingArg,
/// An outgoing function argument.
///
/// When preparing to call a function whose arguments don't fit in registers, outgoing argument
/// stack slots are used to represent individual arguments in the outgoing call frame. These
/// stack slots are only valid while setting up a call.
OutgoingArg,
}
impl FromStr for StackSlotKind {
type Err = ();
fn from_str(s: &str) -> Result<StackSlotKind, ()> {
use self::StackSlotKind::*;
match s {
"local" => Ok(Local),
"spill_slot" => Ok(SpillSlot),
"incoming_arg" => Ok(IncomingArg),
"outgoing_arg" => Ok(OutgoingArg),
_ => Err(()),
}
}
}
impl fmt::Display for StackSlotKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::StackSlotKind::*;
f.write_str(match *self {
Local => "local",
SpillSlot => "spill_slot",
IncomingArg => "incoming_arg",
OutgoingArg => "outgoing_arg",
})
}
}
/// Contents of a stack slot.
#[derive(Clone, Debug)]
pub struct StackSlotData {
/// The kind of stack slot.
pub kind: StackSlotKind,
/// Size of stack slot in bytes.
pub size: u32,
}
impl StackSlotData {
/// Create a stack slot with the specified byte size.
pub fn new(size: u32) -> StackSlotData {
StackSlotData { size: size }
pub fn new(kind: StackSlotKind, size: u32) -> StackSlotData {
StackSlotData { kind, size }
}
}
impl Display for StackSlotData {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
write!(fmt, "stack_slot {}", self.size)
impl fmt::Display for StackSlotData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", self.kind, self.size)
}
}
#[cfg(test)]
mod tests {
use ir::Function;
use super::StackSlotData;
use super::*;
#[test]
fn stack_slot() {
let mut func = Function::new();
let ss0 = func.stack_slots.push(StackSlotData::new(4));
let ss1 = func.stack_slots.push(StackSlotData::new(8));
let ss0 = func.stack_slots
.push(StackSlotData::new(StackSlotKind::IncomingArg, 4));
let ss1 = func.stack_slots
.push(StackSlotData::new(StackSlotKind::SpillSlot, 8));
assert_eq!(ss0.to_string(), "ss0");
assert_eq!(ss1.to_string(), "ss1");
assert_eq!(func.stack_slots[ss0].size, 4);
assert_eq!(func.stack_slots[ss1].size, 8);
assert_eq!(func.stack_slots[ss0].to_string(), "incoming_arg 4");
assert_eq!(func.stack_slots[ss1].to_string(), "spill_slot 8");
}
}

View File

@@ -359,7 +359,7 @@ impl<'a> fmt::Display for DisplayValues<'a> {
#[cfg(test)]
mod tests {
use ir::{Function, FunctionName, StackSlotData};
use ir::{Function, FunctionName, StackSlotData, StackSlotKind};
use ir::types;
#[test]
@@ -370,21 +370,21 @@ mod tests {
f.name = FunctionName::new("foo");
assert_eq!(f.to_string(), "function %foo() {\n}\n");
f.stack_slots.push(StackSlotData::new(4));
assert_eq!(f.to_string(),
"function %foo() {\n ss0 = stack_slot 4\n}\n");
f.stack_slots
.push(StackSlotData::new(StackSlotKind::Local, 4));
assert_eq!(f.to_string(), "function %foo() {\n ss0 = local 4\n}\n");
let ebb = f.dfg.make_ebb();
f.layout.append_ebb(ebb);
assert_eq!(f.to_string(),
"function %foo() {\n ss0 = stack_slot 4\n\nebb0:\n}\n");
"function %foo() {\n ss0 = local 4\n\nebb0:\n}\n");
f.dfg.append_ebb_arg(ebb, types::I8);
assert_eq!(f.to_string(),
"function %foo() {\n ss0 = stack_slot 4\n\nebb0(v0: i8):\n}\n");
"function %foo() {\n ss0 = local 4\n\nebb0(v0: i8):\n}\n");
f.dfg.append_ebb_arg(ebb, types::F32.by(4).unwrap());
assert_eq!(f.to_string(),
"function %foo() {\n ss0 = stack_slot 4\n\nebb0(v0: i8, v1: f32x4):\n}\n");
"function %foo() {\n ss0 = local 4\n\nebb0(v0: i8, v1: f32x4):\n}\n");
}
}

View File

@@ -896,13 +896,17 @@ impl<'a> Parser<'a> {
// Parse a stack slot decl.
//
// stack-slot-decl ::= * StackSlot(ss) "=" "stack_slot" Bytes {"," stack-slot-flag}
// stack-slot-decl ::= * StackSlot(ss) "=" stack-slot-kind Bytes {"," stack-slot-flag}
// stack-slot-kind ::= "local"
// | "spill_slot"
// | "incoming_arg"
// | "outgoing_arg"
fn parse_stack_slot_decl(&mut self) -> Result<(u32, StackSlotData)> {
let number = self.match_ss("expected stack slot number: ss«n»")?;
self.match_token(Token::Equal, "expected '=' in stack_slot decl")?;
self.match_identifier("stack_slot", "expected 'stack_slot'")?;
self.match_token(Token::Equal, "expected '=' in stack slot declaration")?;
let kind = self.match_enum("expected stack slot kind")?;
// stack-slot-decl ::= StackSlot(ss) "=" "stack_slot" * Bytes {"," stack-slot-flag}
// stack-slot-decl ::= StackSlot(ss) "=" stack-slot-kind * Bytes {"," stack-slot-flag}
let bytes: i64 = self.match_imm64("expected byte-size in stack_slot decl")?
.into();
if bytes < 0 {
@@ -911,9 +915,9 @@ impl<'a> Parser<'a> {
if bytes > u32::MAX as i64 {
return err!(self.loc, "stack slot too large");
}
let data = StackSlotData::new(bytes as u32);
let data = StackSlotData::new(kind, bytes as u32);
// TBD: stack-slot-decl ::= StackSlot(ss) "=" "stack_slot" Bytes * {"," stack-slot-flag}
// TBD: stack-slot-decl ::= StackSlot(ss) "=" stack-slot-kind Bytes * {"," stack-slot-flag}
Ok((number, data))
}
@@ -1719,6 +1723,7 @@ mod tests {
use super::*;
use cretonne::ir::{ArgumentExtension, ArgumentPurpose};
use cretonne::ir::types;
use cretonne::ir::StackSlotKind;
use cretonne::ir::entities::AnyEntity;
use testfile::{Details, Comment};
use isaspec::IsaSpec;
@@ -1793,8 +1798,8 @@ mod tests {
#[test]
fn stack_slot_decl() {
let (func, _) = Parser::new("function %foo() {
ss3 = stack_slot 13
ss1 = stack_slot 1
ss3 = incoming_arg 13
ss1 = spill_slot 1
}")
.parse_function(None)
.unwrap();
@@ -1802,16 +1807,18 @@ mod tests {
let mut iter = func.stack_slots.keys();
let ss0 = iter.next().unwrap();
assert_eq!(ss0.to_string(), "ss0");
assert_eq!(func.stack_slots[ss0].kind, StackSlotKind::IncomingArg);
assert_eq!(func.stack_slots[ss0].size, 13);
let ss1 = iter.next().unwrap();
assert_eq!(ss1.to_string(), "ss1");
assert_eq!(func.stack_slots[ss1].kind, StackSlotKind::SpillSlot);
assert_eq!(func.stack_slots[ss1].size, 1);
assert_eq!(iter.next(), None);
// Catch duplicate definitions.
assert_eq!(Parser::new("function %bar() {
ss1 = stack_slot 13
ss1 = stack_slot 1
ss1 = spill_slot 13
ss1 = spill_slot 1
}")
.parse_function(None)
.unwrap_err()
@@ -1844,7 +1851,7 @@ mod tests {
fn comments() {
let (func, Details { comments, .. }) = Parser::new("; before
function %comment() { ; decl
ss10 = stack_slot 13 ; stackslot.
ss10 = outgoing_arg 13 ; stackslot.
; Still stackslot.
jt10 = jump_table ebb0
; Jumptable

View File

@@ -223,7 +223,7 @@ mod tests {
#[test]
fn details() {
let tf = parse_test("function %detail() {
ss10 = stack_slot 13
ss10 = incoming_arg 13
jt10 = jump_table ebb0
ebb0(v4: i32, v7: i32):
v10 = iadd v4, v7

View File

@@ -1,7 +1,7 @@
" Vim syntax file
" Language: Cretonne
" Maintainer: Jakob Stoklund Olesen <stoklund@2pi.dk
" Last Change: Sep 22, 2016
" Last Change: Jun 16, 2017
if version < 600
syntax clear
@@ -14,7 +14,7 @@ endif
syn spell notoplevel
syn keyword ctonHeader test isa set
syn keyword ctonDecl function stack_slot jump_table
syn keyword ctonDecl function jump_table incoming_arg outgoing_arg spill_slot local
syn keyword ctonFilecheck check sameln nextln unordered not regex contained
syn match ctonType /\<[bif]\d\+\(x\d\+\)\?\>/