Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Introduce vectors as "native" SPIR-T types. #52

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,13 @@ global_var GV0 in spv.StorageClass.Output: s32

func F0() -> spv.OpTypeVoid {
loop(v0: s32 <- 1s32, v1: s32 <- 1s32) {
v2 = spv.OpSLessThan(v1, 10s32): bool
v2 = s.lt(v1, 10s32): bool
(v3: s32, v4: s32) = if v2 {
v5 = spv.OpIMul(v0, v1): s32
v6 = spv.OpIAdd(v1, 1s32): s32
v5 = i.mul(v0, v1): s32
v6 = i.add(v1, 1s32): s32
(v5, v6)
} else {
(spv.OpUndef: s32, spv.OpUndef: s32)
(undef: s32, undef: s32)
}
(v3, v4) -> (v0, v1)
} while v2
Expand Down
45 changes: 6 additions & 39 deletions src/cfg.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
//! Control-flow graph (CFG) abstractions and utilities.

use crate::{
spv, AttrSet, Const, ConstDef, ConstKind, Context, ControlNode, ControlNodeDef,
scalar, spv, AttrSet, Const, ConstDef, ConstKind, Context, ControlNode, ControlNodeDef,
ControlNodeKind, ControlNodeOutputDecl, ControlRegion, ControlRegionDef,
EntityOrientedDenseMap, FuncDefBody, FxIndexMap, FxIndexSet, SelectionKind, Type, TypeKind,
Value,
EntityOrientedDenseMap, FuncDefBody, FxIndexMap, FxIndexSet, SelectionKind, Type, Value,
};
use itertools::{Either, Itertools};
use smallvec::SmallVec;
use std::mem;
use std::rc::Rc;

/// The control-flow graph (CFG) of a function, as control-flow instructions
/// ([`ControlInst`]s) attached to [`ControlRegion`]s, as an "action on exit", i.e.
Expand Down Expand Up @@ -593,32 +591,9 @@ struct PartialControlRegion {

impl<'a> Structurizer<'a> {
pub fn new(cx: &'a Context, func_def_body: &'a mut FuncDefBody) -> Self {
// FIXME(eddyb) SPIR-T should have native booleans itself.
let wk = &spv::spec::Spec::get().well_known;
let type_bool = cx.intern(TypeKind::SpvInst {
spv_inst: wk.OpTypeBool.into(),
type_and_const_inputs: [].into_iter().collect(),
});
let const_true = cx.intern(ConstDef {
attrs: AttrSet::default(),
ty: type_bool,
kind: ConstKind::SpvInst {
spv_inst_and_const_inputs: Rc::new((
wk.OpConstantTrue.into(),
[].into_iter().collect(),
)),
},
});
let const_false = cx.intern(ConstDef {
attrs: AttrSet::default(),
ty: type_bool,
kind: ConstKind::SpvInst {
spv_inst_and_const_inputs: Rc::new((
wk.OpConstantFalse.into(),
[].into_iter().collect(),
)),
},
});
let type_bool = cx.intern(scalar::Type::Bool);
let const_true = cx.intern(scalar::Const::TRUE);
let const_false = cx.intern(scalar::Const::FALSE);

let (loop_header_to_exit_targets, incoming_edge_counts_including_loop_exits) =
func_def_body
Expand Down Expand Up @@ -1568,14 +1543,6 @@ impl<'a> Structurizer<'a> {
/// Create an undefined constant (as a placeholder where a value needs to be
/// present, but won't actually be used), of type `ty`.
fn const_undef(&self, ty: Type) -> Const {
// FIXME(eddyb) SPIR-T should have native undef itself.
let wk = &spv::spec::Spec::get().well_known;
self.cx.intern(ConstDef {
attrs: AttrSet::default(),
ty,
kind: ConstKind::SpvInst {
spv_inst_and_const_inputs: Rc::new((wk.OpUndef.into(), [].into_iter().collect())),
},
})
self.cx.intern(ConstDef { attrs: AttrSet::default(), ty, kind: ConstKind::Undef })
}
}
143 changes: 134 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@ pub mod passes {
pub mod qptr;
}
pub mod qptr;
pub mod scalar;
pub mod spv;
pub mod vector;

use smallvec::SmallVec;
use std::borrow::Cow;
Expand Down Expand Up @@ -453,16 +455,30 @@ impl<T: Eq> Ord for OrdAssertEq<T> {
pub use context::Type;

/// Definition for a [`Type`].
//
// FIXME(eddyb) maybe special-case some basic types like integers.
#[derive(PartialEq, Eq, Hash)]
pub struct TypeDef {
pub attrs: AttrSet,
pub kind: TypeKind,
}

#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Clone, PartialEq, Eq, Hash, derive_more::From)]
pub enum TypeKind {
/// Scalar (`bool`, integer, and floating-point) type, with limitations
/// on the supported bit-widths (power-of-two multiples of a byte).
///
/// **Note**: pointers are never scalars (like SPIR-V, but unlike other IRs).
///
/// See also the [`scalar`] module for more documentation and definitions.
#[from]
Scalar(scalar::Type),

/// Vector (small array of [`scalar`]s) type, with some limitations on the
/// supported component counts (but all standard ones should be included).
///
/// See also the [`vector`] module for more documentation and definitions.
#[from]
Vector(vector::Type),

/// "Quasi-pointer", an untyped pointer-like abstract scalar that can represent
/// both memory locations (in any address space) and other kinds of locations
/// (e.g. SPIR-V `OpVariable`s in non-memory "storage classes").
Expand Down Expand Up @@ -490,12 +506,18 @@ pub enum TypeKind {
SpvStringLiteralForExtInst,
}

// HACK(eddyb) this behaves like an implicit conversion for `cx.intern(...)`.
impl context::InternInCx<Type> for TypeKind {
fn intern_in_cx(self, cx: &Context) -> Type {
cx.intern(TypeDef { attrs: Default::default(), kind: self })
// HACK(eddyb) this behaves like an implicit conversion for `cx.intern(...)`,
// and the macro is only used because coherence bans `impl<T: Into<TypeKind>>`.
macro_rules! impl_intern_type_kind {
($($kind:ty),+ $(,)?) => {
$(impl context::InternInCx<Type> for $kind {
fn intern_in_cx(self, cx: &Context) -> Type {
cx.intern(TypeDef { attrs: Default::default(), kind: self.into() })
}
})+
}
}
impl_intern_type_kind!(TypeKind, scalar::Type, vector::Type);

// HACK(eddyb) this is like `Either<Type, Const>`, only used in `TypeKind::SpvInst`,
// and only because SPIR-V type definitions can references both types and consts.
Expand All @@ -505,6 +527,22 @@ pub enum TypeOrConst {
Const(Const),
}

// HACK(eddyb) on `Type` instead of `TypeDef` for ergonomics reasons.
impl Type {
pub fn as_scalar(self, cx: &Context) -> Option<scalar::Type> {
match cx[self].kind {
TypeKind::Scalar(ty) => Some(ty),
_ => None,
}
}
pub fn as_vector(self, cx: &Context) -> Option<vector::Type> {
match cx[self].kind {
TypeKind::Vector(ty) => Some(ty),
_ => None,
}
}
}

/// Interned handle for a [`ConstDef`](crate::ConstDef) (a constant value).
pub use context::Const;

Expand All @@ -518,8 +556,38 @@ pub struct ConstDef {
pub kind: ConstKind,
}

#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Clone, PartialEq, Eq, Hash, derive_more::From)]
pub enum ConstKind {
/// Undeterminate value (i.e. SPIR-V `OpUndef`, LLVM `undef`).
//
// FIXME(eddyb) could it be possible to adopt LLVM's newer `poison`+`freeze`
// model, without being forced to never lift back to `OpUndef`?
Undef,

/// Scalar (`bool`, integer, and floating-point) constant, which must have
/// a type of [`TypeKind::Scalar`] (of the same [`scalar::Type`]).
///
/// See also the [`scalar`] module for more documentation and definitions.
//
// FIXME(eddyb) maybe document the 128-bit limitation?.
// FIXME(eddyb) this technically makes the `scalar::Type` redundant, could
// it get out of sync? (perhaps "forced canonicalization" could be used to
// enforce that interning simply doesn't allow such scenarios?).
#[from]
Scalar(scalar::Const),

/// Vector (small array of [`scalar`]s) constant, which must have
/// a type of [`TypeKind::Vector`] (of the same [`vector::Type`]).
///
/// See also the [`vector`] module for more documentation and definitions.
//
// FIXME(eddyb) maybe document the 128-bit limitation inherited from `scalar::Const`?
// FIXME(eddyb) this technically makes the `vector::Type` redundant, could
// it get out of sync? (perhaps "forced canonicalization" could be used to
// enforce that interning simply doesn't allow such scenarios?).
#[from]
Vector(vector::Const),

PtrToGlobalVar(GlobalVar),

// HACK(eddyb) this is a fallback case that should become increasingly rare
Expand All @@ -534,6 +602,40 @@ pub enum ConstKind {
SpvStringLiteralForExtInst(InternedStr),
}

// HACK(eddyb) this behaves like an implicit conversion for `cx.intern(...)`,
// like the `TypeKind` one, but this one is even weirder because it also interns
// the inherent type of the constant, as a `Type` (with empty attributes).
macro_rules! impl_intern_const_kind {
($($kind:ty),+ $(,)?) => {
$(impl context::InternInCx<Const> for $kind {
fn intern_in_cx(self, cx: &Context) -> Const {
cx.intern(ConstDef {
attrs: Default::default(),
ty: cx.intern(self.ty()),
kind: self.into(),
})
}
})+
}
}
impl_intern_const_kind!(scalar::Const, vector::Const);

// HACK(eddyb) on `Const` instead of `ConstDef` for ergonomics reasons.
impl Const {
pub fn as_scalar(self, cx: &Context) -> Option<&scalar::Const> {
match &cx[self].kind {
ConstKind::Scalar(ct) => Some(ct),
_ => None,
}
}
pub fn as_vector(self, cx: &Context) -> Option<&vector::Const> {
match &cx[self].kind {
ConstKind::Vector(ct) => Some(ct),
_ => None,
}
}
}

/// Declarations ([`GlobalVarDecl`], [`FuncDecl`]) can contain a full definition,
/// or only be an import of a definition (e.g. from another module).
#[derive(Clone)]
Expand Down Expand Up @@ -825,13 +927,24 @@ pub enum ControlNodeKind {
},
}

// FIXME(eddyb) consider interning this, perhaps in a similar vein to `DataInstForm`.
#[derive(Clone)]
pub enum SelectionKind {
/// Two-case selection based on boolean condition, i.e. `if`-`else`, with
/// the two cases being "then" and "else" (in that order).
BoolCond,

SpvInst(spv::Inst),
/// `N+1`-case selection based on comparing an integer scrutinee against
/// `N` constants, i.e. `switch`, with the last case being the "default"
/// (making it the only case without a matching entry in `case_consts`).
Switch {
// FIXME(eddyb) avoid some of the `scalar::Const` overhead here, as there
// is only a single type and we shouldn't need to store more bits per case,
// than the actual width of the integer type.
// FIXME(eddyb) consider storing this more like sorted compressed keyset,
// as there can be no duplicates, and in many cases it may be contiguous.
case_consts: Vec<scalar::Const>,
},
}

/// Entity handle for a [`DataInstDef`](crate::DataInstDef) (an SSA instruction).
Expand Down Expand Up @@ -868,6 +981,18 @@ pub struct DataInstFormDef {

#[derive(Clone, PartialEq, Eq, Hash, derive_more::From)]
pub enum DataInstKind {
/// Scalar (`bool`, integer, and floating-point) pure operations.
///
/// See also the [`scalar`] module for more documentation and definitions.
#[from]
Scalar(scalar::Op),

/// Vector (small array of [`scalar`]s) pure operations.
///
/// See also the [`vector`] module for more documentation and definitions.
#[from]
Vector(vector::Op),

// FIXME(eddyb) try to split this into recursive and non-recursive calls,
// to avoid needing special handling for recursion where it's impossible.
FuncCall(Func),
Expand Down
Loading
Loading