Skip to content

Commit

Permalink
Introduce qptr ("quasi-pointer") type and associated lower->analyze…
Browse files Browse the repository at this point in the history
…->lift passes.
  • Loading branch information
eddyb committed Apr 21, 2023
1 parent 5e76c86 commit afebdee
Show file tree
Hide file tree
Showing 17 changed files with 5,240 additions and 49 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] - ReleaseDate

### Added ⭐
- [PR#24](https://github.com/EmbarkStudios/spirt/pull/24) added `qptr` ("quasi-pointer") type
and associated passes to destroy and recreate pointer-related type information
(see [PR#24](https://github.com/EmbarkStudios/spirt/pull/24) for a much more detailed overview)
- [PR#22](https://github.com/EmbarkStudios/spirt/pull/22) added `Diag` and `Attr::Diagnostics`,
for embedding diagnostics (errors or warnings) in SPIR-T itself
- [PR#18](https://github.com/EmbarkStudios/spirt/pull/18) added anchor-based alignment
Expand Down
138 changes: 138 additions & 0 deletions examples/spv-lower-link-qptr-lift.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use std::fs;
use std::path::Path;
use std::rc::Rc;

fn main() -> std::io::Result<()> {
match &std::env::args().collect::<Vec<_>>()[..] {
[_, in_file] => {
let in_file_path = Path::new(in_file);

let save_print_plan = |suffix: &str, plan: spirt::print::Plan| {
let pretty = plan.pretty_print();
let ext = format!("{suffix}.spirt");

// FIXME(eddyb) don't allocate whole `String`s here.
fs::write(in_file_path.with_extension(&ext), pretty.to_string())?;
fs::write(
in_file_path.with_extension(ext + ".html"),
pretty
.render_to_html()
.with_dark_mode_support()
.to_html_doc(),
)
};

// FIXME(eddyb) adapt the other examples to this style.

fn eprint_duration<R>(f: impl FnOnce() -> R) -> R {
let start = std::time::Instant::now();
let r = f();
eprint!("[{:8.3}ms] ", start.elapsed().as_secs_f64() * 1000.0);
r
}

eprint_duration(|| {
let _ = spirt::spv::spec::Spec::get();
});
eprintln!("spv::spec::Spec::get");

let cx = Rc::new(spirt::Context::new());

let multi_version_printing = true;
let mut per_pass_module = vec![];
let mut after_pass = |pass, module: &spirt::Module| {
if multi_version_printing {
per_pass_module.push((pass, module.clone()));
Ok(())
} else {
save_print_plan(
&format!("after.{pass}"),
spirt::print::Plan::for_module(module),
)
}
};

let mut module =
eprint_duration(|| spirt::Module::lower_from_spv_file(cx.clone(), in_file_path))?;
eprintln!("Module::lower_from_spv_file({})", in_file_path.display());

let original_export_count = module.exports.len();
eprint_duration(|| {
spirt::passes::link::minimize_exports(&mut module, |export_key| {
matches!(export_key, spirt::ExportKey::SpvEntryPoint { .. })
})
});
eprintln!(
"link::minimize_exports: {} -> {} exports",
original_export_count,
module.exports.len()
);
//after_pass("minimize_exports", &module)?;

// HACK(eddyb) do this late enough to avoid spending time on unused
// functions, which `link::minimize_exports` makes unreachable.
eprint_duration(|| spirt::passes::legalize::structurize_func_cfgs(&mut module));
eprintln!("legalize::structurize_func_cfgs");
//after_pass("structurize_func_cfgs", &module)?;

eprint_duration(|| spirt::passes::link::resolve_imports(&mut module));
eprintln!("link::resolve_imports");
//after_pass("resolve_imports", &module)?;

// HACK(eddyb)
after_pass("", &module)?;

// HACK(eddyb) this is roughly what Rust-GPU would need.
let layout_config = &spirt::qptr::LayoutConfig {
abstract_bool_size_align: (1, 1),
logical_ptr_size_align: (4, 4),
..spirt::qptr::LayoutConfig::VULKAN_SCALAR_LAYOUT
};

eprint_duration(|| {
spirt::passes::qptr::lower_from_spv_ptrs(&mut module, layout_config)
});
eprintln!("qptr::lower_from_spv_ptrs");
after_pass("qptr::lower_from_spv_ptrs", &module)?;

eprint_duration(|| spirt::passes::qptr::analyze_uses(&mut module, layout_config));
eprintln!("qptr::analyze_uses");
after_pass("qptr::analyze_uses", &module)?;

eprint_duration(|| spirt::passes::qptr::lift_to_spv_ptrs(&mut module, layout_config));
eprintln!("qptr::lift_to_spv_ptrs");
after_pass("qptr::lift_to_spv_ptrs", &module)?;

if multi_version_printing {
// FIXME(eddyb) use a better suffix than `qptr` (or none).
save_print_plan(
"qptr",
spirt::print::Plan::for_versions(
&cx,
per_pass_module.iter().map(|(pass, module)| {
(
// HACK(eddyb)
if pass.is_empty() {
"initial".into()
} else {
format!("after {pass}")
},
module,
)
}),
),
)?;
}

//let out_file_path = in_file_path.with_extension("qptr.spv");
//eprint_duration(|| module.lift_to_spv_file(&out_file_path))?;
//eprintln!("Module::lift_to_spv_file({})", out_file_path.display());

Ok(())
}
args => {
eprintln!("Usage: {} IN", args[0]);
std::process::exit(1);
}
}
}
49 changes: 47 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ pub mod passes {

pub mod legalize;
pub mod link;
pub mod qptr;
}
pub mod qptr;
pub mod spv;

use smallvec::SmallVec;
Expand Down Expand Up @@ -338,6 +340,9 @@ impl AttrSet {
// FIXME(eddyb) consider interning individual attrs, not just `AttrSet`s.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Attr {
/// `QPtr`-specific attributes (see [`qptr::QPtrAttr`]).
QPtr(qptr::QPtrAttr),

SpvAnnotation(spv::Inst),

SpvDebugLine {
Expand All @@ -348,6 +353,7 @@ pub enum Attr {

/// Some SPIR-V instructions, like `OpFunction`, take a bitflags operand
/// that is effectively an optimization over using `OpDecorate`.
//
// FIXME(eddyb) handle flags having further operands as parameters.
SpvBitflagsOperand(spv::Imm),

Expand Down Expand Up @@ -412,6 +418,7 @@ pub enum DiagMsgPart {
Attrs(AttrSet),
Type(Type),
Const(Const),
QPtrUsage(qptr::QPtrUsage),
}

// FIXME(eddyb) move this out of `lib.rs` and/or define with a macro.
Expand Down Expand Up @@ -445,6 +452,12 @@ impl From<Const> for DiagMsgPart {
}
}

impl From<qptr::QPtrUsage> for DiagMsgPart {
fn from(usage: qptr::QPtrUsage) -> Self {
Self::QPtrUsage(usage)
}
}

/// Wrapper to limit `Ord` for interned index types (e.g. [`InternedStr`])
/// to only situations where the interned index reflects contents (i.e. equality).
//
Expand Down Expand Up @@ -488,6 +501,22 @@ pub struct TypeDef {
/// [`Type`] "constructor": a [`TypeDef`] wiithout any [`TypeCtorArg`]s ([`Type`]s/[`Const`]s).
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum TypeCtor {
/// "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").
///
/// This flexibility can be used to represent pointers from source languages
/// that expect/are defined to operate on untyped memory (C, C++, Rust, etc.),
/// that can then be legalized away (e.g. via inlining) or even emulated.
///
/// Information narrowing down how values of the type may be created/used
/// (e.g. "points to variable `x`" or "accessed at offset `y`") can be found
/// attached as `Attr`s on those `Value`s (see [`Attr::QPtr`]).
//
// FIXME(eddyb) a "refinement system" that's orthogonal from types, and kept
// separately in e.g. `ControlRegionInputDecl`, might be a better approach?
QPtr,

SpvInst(spv::Inst),

/// The type of a [`ConstCtor::SpvStringLiteralForExtInst`] constant, i.e.
Expand Down Expand Up @@ -560,14 +589,24 @@ pub struct GlobalVarDecl {
// FIXME(eddyb) try to replace with value type (or at least have that too).
pub type_of_ptr_to: Type,

/// When `type_of_ptr_to` is `QPtr`, `shape` must be used to describe the
/// global variable (see `GlobalVarShape`'s documentation for more details).
pub shape: Option<qptr::shapes::GlobalVarShape>,

/// The address space the global variable will be allocated into.
pub addr_space: AddrSpace,

pub def: DeclDef<GlobalVarDefBody>,
}

#[derive(Copy, Clone)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum AddrSpace {
/// Placeholder for `GlobalVar`s with `GlobalVarShape::Handles`.
///
/// In SPIR-V, this corresponds to `UniformConstant` for `Handle::Opaque`,
/// or the buffer's storage class for `Handle::Buffer`.
Handles,

SpvStorageClass(u32),
}

Expand Down Expand Up @@ -845,8 +884,14 @@ pub enum DataInstKind {
// to avoid needing special handling for recursion where it's impossible.
FuncCall(Func),

/// `QPtr`-specific operations (see [`qptr::QPtrOp`]).
QPtr(qptr::QPtrOp),

SpvInst(spv::Inst),
SpvExtInst { ext_set: InternedStr, inst: u32 },
SpvExtInst {
ext_set: InternedStr,
inst: u32,
},
}

#[derive(Copy, Clone, PartialEq, Eq)]
Expand Down
3 changes: 2 additions & 1 deletion src/passes/legalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ pub fn structurize_func_cfgs(module: &mut Module) {
seen_global_vars: FxIndexSet::default(),
seen_funcs: FxIndexSet::default(),
};
for &exportee in module.exports.values() {
for (export_key, &exportee) in &module.exports {
export_key.inner_visit_with(&mut collector);
exportee.inner_visit_with(&mut collector);
}

Expand Down
104 changes: 104 additions & 0 deletions src/passes/qptr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//! [`QPtr`](crate::TypeCtor::QPtr) transforms.
use crate::qptr;
use crate::visit::{InnerVisit, Visitor};
use crate::{AttrSet, Const, Context, Func, FxIndexSet, GlobalVar, Module, Type};

pub fn lower_from_spv_ptrs(module: &mut Module, layout_config: &qptr::LayoutConfig) {
let cx = &module.cx();

let (seen_global_vars, seen_funcs) = {
// FIXME(eddyb) reuse this collection work in some kind of "pass manager".
let mut collector = ReachableUseCollector {
cx,
module,

seen_types: FxIndexSet::default(),
seen_consts: FxIndexSet::default(),
seen_global_vars: FxIndexSet::default(),
seen_funcs: FxIndexSet::default(),
};
for (export_key, &exportee) in &module.exports {
export_key.inner_visit_with(&mut collector);
exportee.inner_visit_with(&mut collector);
}
(collector.seen_global_vars, collector.seen_funcs)
};

let lowerer = qptr::lower::LowerFromSpvPtrs::new(cx.clone(), layout_config);
for &global_var in &seen_global_vars {
lowerer.lower_global_var(&mut module.global_vars[global_var]);
}
for &func in &seen_funcs {
lowerer.lower_func(&mut module.funcs[func]);
}
}

pub fn analyze_uses(module: &mut Module, layout_config: &qptr::LayoutConfig) {
qptr::analyze::InferUsage::new(module.cx(), layout_config).infer_usage_in_module(module);
}

pub fn lift_to_spv_ptrs(module: &mut Module, layout_config: &qptr::LayoutConfig) {
let cx = &module.cx();

let (seen_global_vars, seen_funcs) = {
// FIXME(eddyb) reuse this collection work in some kind of "pass manager".
let mut collector = ReachableUseCollector {
cx,
module,

seen_types: FxIndexSet::default(),
seen_consts: FxIndexSet::default(),
seen_global_vars: FxIndexSet::default(),
seen_funcs: FxIndexSet::default(),
};
for (export_key, &exportee) in &module.exports {
export_key.inner_visit_with(&mut collector);
exportee.inner_visit_with(&mut collector);
}
(collector.seen_global_vars, collector.seen_funcs)
};

let lifter = qptr::lift::LiftToSpvPtrs::new(cx.clone(), layout_config);
for &global_var in &seen_global_vars {
lifter.lift_global_var(&mut module.global_vars[global_var]);
}
lifter.lift_all_funcs(module, seen_funcs);
}

struct ReachableUseCollector<'a> {
cx: &'a Context,
module: &'a Module,

// FIXME(eddyb) build some automation to avoid ever repeating these.
seen_types: FxIndexSet<Type>,
seen_consts: FxIndexSet<Const>,
seen_global_vars: FxIndexSet<GlobalVar>,
seen_funcs: FxIndexSet<Func>,
}

impl Visitor<'_> for ReachableUseCollector<'_> {
// FIXME(eddyb) build some automation to avoid ever repeating these.
fn visit_attr_set_use(&mut self, _attrs: AttrSet) {}
fn visit_type_use(&mut self, ty: Type) {
if self.seen_types.insert(ty) {
self.visit_type_def(&self.cx[ty]);
}
}
fn visit_const_use(&mut self, ct: Const) {
if self.seen_consts.insert(ct) {
self.visit_const_def(&self.cx[ct]);
}
}

fn visit_global_var_use(&mut self, gv: GlobalVar) {
if self.seen_global_vars.insert(gv) {
self.visit_global_var_decl(&self.module.global_vars[gv]);
}
}
fn visit_func_use(&mut self, func: Func) {
if self.seen_funcs.insert(func) {
self.visit_func_decl(&self.module.funcs[func]);
}
}
}
Loading

0 comments on commit afebdee

Please sign in to comment.