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

Introduce qptr ("quasi-pointer") type and associated lower->analyze->lift passes. #24

Merged
merged 5 commits into from
Apr 21, 2023
Merged
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
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);
}
}
}
136 changes: 130 additions & 6 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,9 @@ impl<K: EntityOrientedMapKey<V>, V> EntityOrientedDenseMap<K, V> {
Self::default()
}

pub fn insert(&mut self, key: K, value: V) -> Option<V> {
// FIXME(eddyb) this should not allocate space unconditionally, but offer an
// API where "vacant entry" may or may not have a `&mut Option<V>` in it.
pub fn entry(&mut self, key: K) -> &mut Option<V> {
let entity = K::to_entity(key);
let (chunk_start, intra_chunk_idx) = entity.to_chunk_start_and_intra_chunk_idx();
let chunk_value_slots = self
Expand All @@ -417,7 +419,11 @@ impl<K: EntityOrientedMapKey<V>, V> EntityOrientedDenseMap<K, V> {
}

let value_slots = &mut chunk_value_slots[intra_chunk_idx];
K::get_dense_value_slot_mut(key, value_slots).replace(value)
K::get_dense_value_slot_mut(key, value_slots)
}

pub fn insert(&mut self, key: K, value: V) -> Option<V> {
self.entry(key).replace(value)
}

pub fn get(&self, key: K) -> Option<&V> {
Expand All @@ -438,6 +444,7 @@ impl<K: EntityOrientedMapKey<V>, V> EntityOrientedDenseMap<K, V> {
self.get_slot_mut(key)?.take()
}

// FIXME(eddyb) deduplicate with `entry`.
fn get_slot_mut(&mut self, key: K) -> Option<&mut Option<V>> {
let entity = K::to_entity(key);
let (chunk_start, intra_chunk_idx) = entity.to_chunk_start_and_intra_chunk_idx();
Expand Down Expand Up @@ -514,7 +521,9 @@ impl<E: sealed::Entity<Def = EntityListNode<E, D>>, D> EntityList<E> {
let old_first_def = &mut defs[old_first];

// FIXME(eddyb) this situation should be impossible anyway, as it
// involves the `EntityListNode`s links, which should be unforgeable.
// 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!(
old_first_def.prev.is_none(),
"invalid EntityList: `first->prev != None`"
Expand Down Expand Up @@ -543,7 +552,9 @@ impl<E: sealed::Entity<Def = EntityListNode<E, D>>, D> EntityList<E> {
let old_last_def = &mut defs[old_last];

// FIXME(eddyb) this situation should be impossible anyway, as it
// involves the `EntityListNode`s links, which should be unforgeable.
// 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!(
old_last_def.next.is_none(),
"invalid EntityList: `last->next != None`"
Expand All @@ -558,6 +569,49 @@ impl<E: sealed::Entity<Def = EntityListNode<E, D>>, D> EntityList<E> {
});
}

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

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

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

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

// FIXME(eddyb) this situation should be impossible anyway, as it
// involves the `EntityListNode`s links, which should be unforgeable.
assert!(
old_prev_next == Some(next),
"invalid EntityListNode: `node->prev->next != 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.first) == Some(next),
"invalid EntityList: `node->prev == None` but `node != first`"
);

self.0.as_mut().unwrap().first = 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 All @@ -582,7 +636,9 @@ impl<E: sealed::Entity<Def = EntityListNode<E, D>>, D> EntityList<E> {
let a_last_def = &mut defs[a.last];

// FIXME(eddyb) this situation should be impossible anyway, as it
// involves the `EntityListNode`s links, which should be unforgeable.
// 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!(
a_last_def.next.is_none(),
"invalid EntityList: `last->next != None`"
Expand All @@ -594,7 +650,9 @@ impl<E: sealed::Entity<Def = EntityListNode<E, D>>, D> EntityList<E> {
let b_first_def = &mut defs[b.first];

// FIXME(eddyb) this situation should be impossible anyway, as it
// involves the `EntityListNode`s links, which should be unforgeable.
// 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!(
b_first_def.prev.is_none(),
"invalid EntityList: `first->prev != None`"
Expand All @@ -608,6 +666,72 @@ impl<E: sealed::Entity<Def = EntityListNode<E, D>>, D> EntityList<E> {
last: b.last,
}))
}

/// Remove `node` (defined in `defs`) from `self`.
#[track_caller]
pub fn remove(&mut self, node: E, defs: &mut EntityDefs<E>) {
// Unlink `node->{prev,next}` first (also allowing re-insertion elsewhere).
let (prev, next) = {
let node_def = &mut defs[node];
(node_def.prev.take(), node_def.next.take())
};

// Unlink `prev->next = node` (or validate `first = node`).
match prev {
Some(prev) => {
let old_prev_next = mem::replace(&mut defs[prev].next, next);

// FIXME(eddyb) this situation should be impossible anyway, as it
// involves the `EntityListNode`s links, which should be unforgeable.
assert!(
old_prev_next == Some(node),
"invalid EntityListNode: `node->prev->next != 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.first) == Some(node),
"invalid EntityList: `node->prev == None` but `node != first`"
);
}
}

// Unlink `next->prev = node` (or validate `last = node`).
match next {
Some(next) => {
let old_next_prev = mem::replace(&mut defs[next].prev, prev);

// FIXME(eddyb) this situation should be impossible anyway, as it
// involves the `EntityListNode`s links, which should be unforgeable.
assert!(
old_next_prev == Some(node),
"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(node),
"invalid EntityList: `node->next == None` but `node != last`"
);
}
}

// Update list end-points (overwritten `first`/`last` validated above).
match (prev, next) {
(Some(_), Some(_)) => {}
(None, Some(next)) => self.0.as_mut().unwrap().first = next,
(Some(prev), None) => self.0.as_mut().unwrap().last = prev,
(None, None) => self.0 = None,
}
}
}

/// [`EntityList<E>`] iterator, but with a different API than [`Iterator`].
Expand Down
26 changes: 26 additions & 0 deletions src/func_at.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ impl<'a> Iterator for FuncAt<'a, EntityListIter<DataInst>> {
}
}

impl<'a> DoubleEndedIterator for FuncAt<'a, EntityListIter<DataInst>> {
fn next_back(&mut self) -> Option<Self::Item> {
let (prev, rest) = self.position.split_last(self.data_insts)?;
self.position = rest;
Some(self.at(prev))
}
}

impl<'a> FuncAt<'a, DataInst> {
pub fn def(self) -> &'a DataInstDef {
&self.data_insts[self.position]
Expand Down Expand Up @@ -146,6 +154,24 @@ impl<'a, P: Copy> FuncAtMut<'a, P> {
position: new_position,
}
}

/// Demote to a `FuncAt`, with the same `position`.
//
// FIXME(eddyb) maybe find a better name for this?
pub fn freeze(self) -> FuncAt<'a, P> {
let FuncAtMut {
control_regions,
control_nodes,
data_insts,
position,
} = self;
FuncAt {
control_regions,
control_nodes,
data_insts,
position,
}
}
}

impl<'a> FuncAtMut<'a, ControlRegion> {
Expand Down
Loading