[RFC] Dynamic Vector Support (#4200)

Introduce a new concept in the IR that allows a producer to create
dynamic vector types. An IR function can now contain global value(s)
that represent a dynamic scaling factor, for a given fixed-width
vector type. A dynamic type is then created by 'multiplying' the
corresponding global value with a fixed-width type. These new types
can be used just like the existing types and the type system has a
set of hard-coded dynamic types, such as I32X4XN, which the user
defined types map onto. The dynamic types are also used explicitly
to create dynamic stack slots, which have no set size like their
existing counterparts. New IR instructions are added to access these
new stack entities.

Currently, during codegen, the dynamic scaling factor has to be
lowered to a constant so the dynamic slots do eventually have a
compile-time known size, as do spill slots.

The current lowering for aarch64 just targets Neon, using a dynamic
scale of 1.

Copyright (c) 2022, Arm Limited.
This commit is contained in:
Sam Parker
2022-07-07 20:54:39 +01:00
committed by GitHub
parent 9ae060a12a
commit 9c43749dfe
69 changed files with 2422 additions and 294 deletions

View File

@@ -15,40 +15,43 @@ use std::u16;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Token<'a> {
Comment(&'a str),
LPar, // '('
RPar, // ')'
LBrace, // '{'
RBrace, // '}'
LBracket, // '['
RBracket, // ']'
Minus, // '-'
Plus, // '+'
Comma, // ','
Dot, // '.'
Colon, // ':'
Equal, // '='
Not, // '!'
Arrow, // '->'
Float(&'a str), // Floating point immediate
Integer(&'a str), // Integer immediate
Type(types::Type), // i32, f32, b32x4, ...
Value(Value), // v12, v7
Block(Block), // block3
Cold, // cold (flag on block)
StackSlot(u32), // ss3
GlobalValue(u32), // gv3
Heap(u32), // heap2
Table(u32), // table2
JumpTable(u32), // jt2
Constant(u32), // const2
FuncRef(u32), // fn2
SigRef(u32), // sig2
UserRef(u32), // u345
Name(&'a str), // %9arbitrary_alphanum, %x3, %0, %function ...
String(&'a str), // "arbitrary quoted string with no escape" ...
HexSequence(&'a str), // #89AF
Identifier(&'a str), // Unrecognized identifier (opcode, enumerator, ...)
SourceLoc(&'a str), // @00c7
LPar, // '('
RPar, // ')'
LBrace, // '{'
RBrace, // '}'
LBracket, // '['
RBracket, // ']'
Minus, // '-'
Plus, // '+'
Multiply, // '*'
Comma, // ','
Dot, // '.'
Colon, // ':'
Equal, // '='
Not, // '!'
Arrow, // '->'
Float(&'a str), // Floating point immediate
Integer(&'a str), // Integer immediate
Type(types::Type), // i32, f32, b32x4, ...
DynamicType(u32), // dt5
Value(Value), // v12, v7
Block(Block), // block3
Cold, // cold (flag on block)
StackSlot(u32), // ss3
DynamicStackSlot(u32), // dss4
GlobalValue(u32), // gv3
Heap(u32), // heap2
Table(u32), // table2
JumpTable(u32), // jt2
Constant(u32), // const2
FuncRef(u32), // fn2
SigRef(u32), // sig2
UserRef(u32), // u345
Name(&'a str), // %9arbitrary_alphanum, %x3, %0, %function ...
String(&'a str), // "arbitrary quoted string with no escape" ...
HexSequence(&'a str), // #89AF
Identifier(&'a str), // Unrecognized identifier (opcode, enumerator, ...)
SourceLoc(&'a str), // @00c7
}
/// A `Token` with an associated location.
@@ -341,6 +344,8 @@ impl<'a> Lexer<'a> {
"v" => Value::with_number(number).map(Token::Value),
"block" => Block::with_number(number).map(Token::Block),
"ss" => Some(Token::StackSlot(number)),
"dss" => Some(Token::DynamicStackSlot(number)),
"dt" => Some(Token::DynamicType(number)),
"gv" => Some(Token::GlobalValue(number)),
"heap" => Some(Token::Heap(number)),
"table" => Some(Token::Table(number)),
@@ -482,6 +487,7 @@ impl<'a> Lexer<'a> {
Some('=') => Some(self.scan_char(Token::Equal)),
Some('!') => Some(self.scan_char(Token::Not)),
Some('+') => Some(self.scan_number()),
Some('*') => Some(self.scan_char(Token::Multiply)),
Some('-') => {
if self.looking_at("->") {
Some(self.scan_chars(2, Token::Arrow))

View File

@@ -11,16 +11,17 @@ use crate::testfile::{Comment, Details, Feature, TestFile};
use cranelift_codegen::data_value::DataValue;
use cranelift_codegen::entity::EntityRef;
use cranelift_codegen::ir;
use cranelift_codegen::ir::entities::AnyEntity;
use cranelift_codegen::ir::entities::{AnyEntity, DynamicType};
use cranelift_codegen::ir::immediates::{Ieee32, Ieee64, Imm64, Offset32, Uimm32, Uimm64};
use cranelift_codegen::ir::instructions::{InstructionData, InstructionFormat, VariableArgs};
use cranelift_codegen::ir::types::INVALID;
use cranelift_codegen::ir::types::*;
use cranelift_codegen::ir::{
AbiParam, ArgumentExtension, ArgumentPurpose, Block, Constant, ConstantData, ExtFuncData,
ExternalName, FuncRef, Function, GlobalValue, GlobalValueData, Heap, HeapData, HeapStyle,
JumpTable, JumpTableData, MemFlags, Opcode, SigRef, Signature, StackSlot, StackSlotData,
StackSlotKind, Table, TableData, Type, Value,
AbiParam, ArgumentExtension, ArgumentPurpose, Block, Constant, ConstantData, DynamicStackSlot,
DynamicStackSlotData, DynamicTypeData, ExtFuncData, ExternalName, FuncRef, Function,
GlobalValue, GlobalValueData, Heap, HeapData, HeapStyle, JumpTable, JumpTableData, MemFlags,
Opcode, SigRef, Signature, StackSlot, StackSlotData, StackSlotKind, Table, TableData, Type,
Value,
};
use cranelift_codegen::isa::{self, CallConv};
use cranelift_codegen::packed_option::ReservedValue;
@@ -249,11 +250,11 @@ impl Context {
// Allocate a new stack slot.
fn add_ss(&mut self, ss: StackSlot, data: StackSlotData, loc: Location) -> ParseResult<()> {
self.map.def_ss(ss, loc)?;
while self.function.stack_slots.next_key().index() <= ss.index() {
while self.function.sized_stack_slots.next_key().index() <= ss.index() {
self.function
.create_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 0));
.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 0));
}
self.function.stack_slots[ss] = data;
self.function.sized_stack_slots[ss] = data;
Ok(())
}
@@ -266,6 +267,47 @@ impl Context {
}
}
// Allocate a new stack slot.
fn add_dss(
&mut self,
ss: DynamicStackSlot,
data: DynamicStackSlotData,
loc: Location,
) -> ParseResult<()> {
self.map.def_dss(ss, loc)?;
while self.function.dynamic_stack_slots.next_key().index() <= ss.index() {
self.function
.create_dynamic_stack_slot(DynamicStackSlotData::new(
StackSlotKind::ExplicitDynamicSlot,
data.dyn_ty,
));
}
self.function.dynamic_stack_slots[ss] = data;
Ok(())
}
// Resolve a reference to a dynamic stack slot.
fn check_dss(&self, dss: DynamicStackSlot, loc: Location) -> ParseResult<()> {
if !self.map.contains_dss(dss) {
err!(loc, "undefined dynamic stack slot {}", dss)
} else {
Ok(())
}
}
// Allocate a new dynamic type.
fn add_dt(&mut self, dt: DynamicType, data: DynamicTypeData, loc: Location) -> ParseResult<()> {
self.map.def_dt(dt, loc)?;
while self.function.dfg.dynamic_types.next_key().index() <= dt.index() {
self.function.dfg.make_dynamic_ty(DynamicTypeData::new(
data.base_vector_ty,
data.dynamic_scale,
));
}
self.function.dfg.dynamic_types[dt] = data;
Ok(())
}
// Allocate a global value slot.
fn add_gv(&mut self, gv: GlobalValue, data: GlobalValueData, loc: Location) -> ParseResult<()> {
self.map.def_gv(gv, loc)?;
@@ -597,6 +639,33 @@ impl<'a> Parser<'a> {
err!(self.loc, err_msg)
}
// Match and consume a dynamic stack slot reference.
fn match_dss(&mut self, err_msg: &str) -> ParseResult<DynamicStackSlot> {
if let Some(Token::DynamicStackSlot(ss)) = self.token() {
self.consume();
if let Some(ss) = DynamicStackSlot::with_number(ss) {
return Ok(ss);
}
}
err!(self.loc, err_msg)
}
// Match and consume a dynamic type reference.
fn match_dt(&mut self, err_msg: &str) -> ParseResult<DynamicType> {
if let Some(Token::DynamicType(dt)) = self.token() {
self.consume();
if let Some(dt) = DynamicType::with_number(dt) {
return Ok(dt);
}
}
err!(self.loc, err_msg)
}
// Extract Type from DynamicType
fn concrete_from_dt(&mut self, dt: DynamicType, ctx: &mut Context) -> Option<Type> {
ctx.function.get_concrete_dynamic_ty(dt)
}
// Match and consume a global value reference.
fn match_gv(&mut self, err_msg: &str) -> ParseResult<GlobalValue> {
if let Some(Token::GlobalValue(gv)) = self.token() {
@@ -986,7 +1055,7 @@ impl<'a> Parser<'a> {
vec![value; lane_size as usize]
}
if !ty.is_vector() {
if !ty.is_vector() && !ty.is_dynamic_vector() {
err!(self.loc, "Expected a controlling vector type, not {}", ty)
} else {
let constant_data = match ty.lane_type() {
@@ -1386,6 +1455,18 @@ impl<'a> Parser<'a> {
self.parse_stack_slot_decl()
.and_then(|(ss, dat)| ctx.add_ss(ss, dat, loc))
}
Some(Token::DynamicStackSlot(..)) => {
self.start_gathering_comments();
let loc = self.loc;
self.parse_dynamic_stack_slot_decl()
.and_then(|(dss, dat)| ctx.add_dss(dss, dat, loc))
}
Some(Token::DynamicType(..)) => {
self.start_gathering_comments();
let loc = self.loc;
self.parse_dynamic_type_decl()
.and_then(|(dt, dat)| ctx.add_dt(dt, dat, loc))
}
Some(Token::GlobalValue(..)) => {
self.start_gathering_comments();
self.parse_global_value_decl()
@@ -1465,6 +1546,39 @@ impl<'a> Parser<'a> {
Ok((ss, data))
}
fn parse_dynamic_stack_slot_decl(
&mut self,
) -> ParseResult<(DynamicStackSlot, DynamicStackSlotData)> {
let dss = self.match_dss("expected stack slot number: dss«n»")?;
self.match_token(Token::Equal, "expected '=' in stack slot declaration")?;
let kind = self.match_enum("expected stack slot kind")?;
let dt = self.match_dt("expected dynamic type")?;
let data = DynamicStackSlotData::new(kind, dt);
// Collect any trailing comments.
self.token();
self.claim_gathered_comments(dss);
// TBD: stack-slot-decl ::= StackSlot(ss) "=" stack-slot-kind Bytes * {"," stack-slot-flag}
Ok((dss, data))
}
fn parse_dynamic_type_decl(&mut self) -> ParseResult<(DynamicType, DynamicTypeData)> {
let dt = self.match_dt("expected dynamic type number: dt«n»")?;
self.match_token(Token::Equal, "expected '=' in stack slot declaration")?;
let vector_base_ty = self.match_type("expected base type")?;
assert!(vector_base_ty.is_vector(), "expected vector type");
self.match_token(
Token::Multiply,
"expected '*' followed by a dynamic scale value",
)?;
let dyn_scale = self.match_gv("expected dynamic scale global value")?;
let data = DynamicTypeData::new(vector_base_ty, dyn_scale);
// Collect any trailing comments.
self.token();
self.claim_gathered_comments(dt);
Ok((dt, data))
}
// Parse a global value decl.
//
// global-val-decl ::= * GlobalValue(gv) "=" global-val-desc
@@ -1472,6 +1586,7 @@ impl<'a> Parser<'a> {
// | "load" "." type "notrap" "aligned" GlobalValue(base) [offset]
// | "iadd_imm" "(" GlobalValue(base) ")" imm64
// | "symbol" ["colocated"] name + imm64
// | "dyn_scale_target_const" "." type
//
fn parse_global_value_decl(&mut self) -> ParseResult<(GlobalValue, GlobalValueData)> {
let gv = self.match_gv("expected global value number: gv«n»")?;
@@ -1530,6 +1645,15 @@ impl<'a> Parser<'a> {
tls,
}
}
"dyn_scale_target_const" => {
self.match_token(
Token::Dot,
"expected '.' followed by type in dynamic scale global value decl",
)?;
let vector_type = self.match_type("expected load type")?;
assert!(vector_type.is_vector(), "Expected vector type");
GlobalValueData::DynScaleTargetConst { vector_type }
}
other => return err!(self.loc, "Unknown global value kind '{}'", other),
};
@@ -2095,7 +2219,12 @@ impl<'a> Parser<'a> {
// Look for a controlling type variable annotation.
// instruction ::= [inst-results "="] Opcode(opc) * ["." Type] ...
let explicit_ctrl_type = if self.optional(Token::Dot) {
Some(self.match_type("expected type after 'opcode.'")?)
if let Some(Token::Type(_t)) = self.token() {
Some(self.match_type("expected type after 'opcode.'")?)
} else {
let dt = self.match_dt("expected dynamic type")?;
self.concrete_from_dt(dt, ctx)
}
} else {
None
};
@@ -2489,7 +2618,7 @@ impl<'a> Parser<'a> {
I128 => DataValue::from(self.match_imm128("expected an i128")?),
F32 => DataValue::from(self.match_ieee32("expected an f32")?),
F64 => DataValue::from(self.match_ieee64("expected an f64")?),
_ if ty.is_vector() => {
_ if (ty.is_vector() || ty.is_dynamic_vector()) => {
let as_vec = self.match_uimm128(ty)?.into_vec();
if as_vec.len() == 16 {
let mut as_array = [0; 16];
@@ -2824,6 +2953,25 @@ impl<'a> Parser<'a> {
offset,
}
}
InstructionFormat::DynamicStackLoad => {
let dss = self.match_dss("expected dynamic stack slot number: dss«n»")?;
ctx.check_dss(dss, self.loc)?;
InstructionData::DynamicStackLoad {
opcode,
dynamic_stack_slot: dss,
}
}
InstructionFormat::DynamicStackStore => {
let arg = self.match_value("expected SSA value operand")?;
self.match_token(Token::Comma, "expected ',' between operands")?;
let dss = self.match_dss("expected dynamic stack slot number: dss«n»")?;
ctx.check_dss(dss, self.loc)?;
InstructionData::DynamicStackStore {
opcode,
arg,
dynamic_stack_slot: dss,
}
}
InstructionFormat::HeapAddr => {
let heap = self.match_heap("expected heap identifier")?;
ctx.check_heap(heap, self.loc)?;
@@ -3080,17 +3228,23 @@ mod tests {
.parse_function()
.unwrap();
assert_eq!(func.name.to_string(), "%foo");
let mut iter = func.stack_slots.keys();
let mut iter = func.sized_stack_slots.keys();
let _ss0 = iter.next().unwrap();
let ss1 = iter.next().unwrap();
assert_eq!(ss1.to_string(), "ss1");
assert_eq!(func.stack_slots[ss1].kind, StackSlotKind::ExplicitSlot);
assert_eq!(func.stack_slots[ss1].size, 1);
assert_eq!(
func.sized_stack_slots[ss1].kind,
StackSlotKind::ExplicitSlot
);
assert_eq!(func.sized_stack_slots[ss1].size, 1);
let _ss2 = iter.next().unwrap();
let ss3 = iter.next().unwrap();
assert_eq!(ss3.to_string(), "ss3");
assert_eq!(func.stack_slots[ss3].kind, StackSlotKind::ExplicitSlot);
assert_eq!(func.stack_slots[ss3].size, 13);
assert_eq!(
func.sized_stack_slots[ss3].kind,
StackSlotKind::ExplicitSlot
);
assert_eq!(func.sized_stack_slots[ss3].size, 13);
assert_eq!(iter.next(), None);
// Catch duplicate definitions.

View File

@@ -8,9 +8,10 @@
use crate::error::{Location, ParseResult};
use crate::lexer::split_entity_name;
use cranelift_codegen::ir::entities::AnyEntity;
use cranelift_codegen::ir::entities::{AnyEntity, DynamicType};
use cranelift_codegen::ir::{
Block, Constant, FuncRef, GlobalValue, Heap, JumpTable, SigRef, StackSlot, Table, Value,
Block, Constant, DynamicStackSlot, FuncRef, GlobalValue, Heap, JumpTable, SigRef, StackSlot,
Table, Value,
};
use std::collections::HashMap;
@@ -38,6 +39,11 @@ impl SourceMap {
self.locations.contains_key(&ss.into())
}
/// Look up a dynamic stack slot entity.
pub fn contains_dss(&self, dss: DynamicStackSlot) -> bool {
self.locations.contains_key(&dss.into())
}
/// Look up a global value entity.
pub fn contains_gv(&self, gv: GlobalValue) -> bool {
self.locations.contains_key(&gv.into())
@@ -173,6 +179,16 @@ impl SourceMap {
self.def_entity(entity.into(), loc)
}
/// Define the dynamic stack slot `entity`.
pub fn def_dss(&mut self, entity: DynamicStackSlot, loc: Location) -> ParseResult<()> {
self.def_entity(entity.into(), loc)
}
/// Define the dynamic type `entity`.
pub fn def_dt(&mut self, entity: DynamicType, loc: Location) -> ParseResult<()> {
self.def_entity(entity.into(), loc)
}
/// Define the global value `entity`.
pub fn def_gv(&mut self, entity: GlobalValue, loc: Location) -> ParseResult<()> {
self.def_entity(entity.into(), loc)