Skip to content
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
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ itertools = "0.10.3"
lazy_static = "1.4.0"
longest-increasing-subsequence = "0.1.0"
rustc-hash = "1.1.0"
rustc_apfloat = "0.2.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
smallvec = { version = "1.7.0", features = ["serde", "union"] }
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,10 @@ global_var GV0 in spv.StorageClass.Output: s32

func F0() -> spv.OpTypeVoid {
(_: s32, _: s32, v0: s32) = loop(v1: s32 <- 1s32, v2: s32 <- 1s32, _: s32 <- undef: s32) {
v3 = spv.OpSLessThan(v2, 10s32): bool
v3 = s.lt(v2, 10s32): bool
(v4: s32, v5: s32) = if v3 {
v6 = spv.OpIMul(v1, v2): s32
v7 = spv.OpIAdd(v2, 1s32): s32
v6 = i.mul(v1, v2): s32
v7 = i.add(v2, 1s32): s32
(v6, v7)
} else {
(undef: s32, undef: s32)
Expand Down
1 change: 1 addition & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ allow = [
"MIT",
"Apache-2.0",
"Unicode-DFS-2016",
"Apache-2.0 WITH LLVM-exception",
]

# This section is considered when running `cargo deny check bans`.
Expand Down
15 changes: 13 additions & 2 deletions src/cf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,32 @@
//
// FIXME(eddyb) consider moving more definitions into this module.

use crate::spv;
use crate::{scalar, spv};

// NOTE(eddyb) all the modules are declared here, but they're documented "inside"
// (i.e. using inner doc comments).
pub mod cfgssa;
pub mod structurize;
pub mod unstructured;

// FIXME(eddyb) consider interning this.
#[derive(Clone, PartialEq, Eq, Hash)]
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>,
},
}

#[derive(Clone, PartialEq, Eq, Hash)]
Expand Down
35 changes: 7 additions & 28 deletions src/cf/structurize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::transform::{InnerInPlaceTransform as _, Transformed, Transformer};
use crate::{
AttrSet, Const, ConstDef, ConstKind, Context, DbgSrcLoc, EntityOrientedDenseMap, FuncDefBody,
FxIndexMap, FxIndexSet, Node, NodeDef, NodeKind, Region, RegionDef, Type, TypeKind, Value, Var,
VarDecl, VarKind, spv,
VarDecl, VarKind, scalar,
};
use itertools::{Either, Itertools};
use smallvec::SmallVec;
Expand Down Expand Up @@ -555,32 +555,9 @@ impl<'a> Structurizer<'a> {
unreachable!();
};

// 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 @@ -625,7 +602,9 @@ impl<'a> Structurizer<'a> {

func_ret_types: {
let is_void = match &cx[func_decl.ret_type].kind {
TypeKind::SpvInst { spv_inst, .. } => spv_inst.opcode == wk.OpTypeVoid,
TypeKind::SpvInst { spv_inst, .. } => {
spv_inst.opcode == crate::spv::spec::Spec::get().well_known.OpTypeVoid
}
_ => false,
};
if is_void { &[][..] } else { std::slice::from_ref(&func_decl.ret_type) }
Expand Down
43 changes: 43 additions & 0 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,49 @@ impl<E: sealed::Entity<Def = EntityListNode<E, D>>, D> EntityList<E> {
}
}

/// Insert `new_node` (defined in `defs`) into `self`, after `prev`.
//
// FIXME(eddyb) unify this with the other insert methods, maybe with a new
// "insert position" type?
#[track_caller]
pub fn insert_after(&mut self, new_node: E, prev: E, defs: &mut EntityDefs<E>) {
let next = defs[prev].next.replace(new_node);

let new_node_def = &mut defs[new_node];
assert!(
new_node_def.next.is_none() && new_node_def.prev.is_none(),
"EntityList::insert_before: new node already linked into a (different?) list"
);

new_node_def.next = next;
new_node_def.prev = Some(prev);

match next {
Some(next) => {
let old_next_prev = defs[next].prev.replace(new_node);

// FIXME(eddyb) this situation should be impossible anyway, as it
// involves the `EntityListNode`s links, which should be unforgeable.
assert!(
old_next_prev == Some(prev),
"invalid EntityListNode: `node->next->prev != node`"
);
}
None => {
// FIXME(eddyb) this situation should be impossible anyway, as it
// involves the `EntityListNode`s links, which should be unforgeable,
// but it's still possible to keep around outdated `EntityList`s
// (should `EntityList` not implement `Copy`/`Clone` *at all*?)
assert!(
self.0.map(|this| this.last) == Some(prev),
"invalid EntityList: `node->next == None` but `node != last`"
);

self.0.as_mut().unwrap().last = new_node;
}
}
}

/// Insert all of `list_to_prepend`'s nodes at the start of `self`.
#[track_caller]
pub fn prepend(&mut self, list_to_prepend: Self, defs: &mut EntityDefs<E>) {
Expand Down
86 changes: 78 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ pub mod passes {
}
pub mod mem;
pub mod qptr;
pub mod scalar;
pub mod spv;

use smallvec::SmallVec;
Expand Down Expand Up @@ -526,16 +527,23 @@ 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),

/// "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 @@ -566,12 +574,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);

// 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 @@ -581,6 +595,16 @@ 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,
}
}
}

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

Expand All @@ -594,14 +618,26 @@ 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),

// FIXME(eddyb) maybe merge these? however, their connection is somewhat
// tenuous (being one of the LLVM-isms SPIR-V inherited, among other things),
// there's still the need to rename "global variable" post-`Var`-refactor,
Expand All @@ -622,6 +658,34 @@ 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);

// 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,
}
}
}

/// 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 @@ -898,6 +962,12 @@ pub enum NodeKind {

// NOTE(eddyb) all variants below used to be in `DataInstKind`.
//
/// Scalar (`bool`, integer, and floating-point) pure operations.
///
/// See also the [`scalar`] module for more documentation and definitions.
#[from]
Scalar(scalar::Op),

FuncCall(Func),

/// Memory-specific operations (see [`mem::MemOp`]).
Expand Down
5 changes: 4 additions & 1 deletion src/mem/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -931,7 +931,8 @@ impl<'a> GatherAccesses<'a> {
continue;
}

DataInstKind::FuncCall(_)
DataInstKind::Scalar(_)
| DataInstKind::FuncCall(_)
| DataInstKind::Mem(_)
| DataInstKind::QPtr(_)
| DataInstKind::ThunkBind(_)
Expand All @@ -950,6 +951,8 @@ impl<'a> GatherAccesses<'a> {
unreachable!()
}

DataInstKind::Scalar(_) => {}

&DataInstKind::FuncCall(callee) => {
match self.gather_accesses_in_func(module, callee) {
FuncGatherAccessesState::Complete(callee_results) => {
Expand Down
Loading
Loading