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

Minimally integrate SPIR-T (opt-in via RUSTGPU_CODEGEN_ARGS=--spirt). #940

Merged
merged 2 commits into from
Dec 12, 2022
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `RUSTGPU_RUSTFLAGS="..."` for shader `RUSTFLAGS="..."`
- `RUSTGPU_CODEGEN_ARGS="..."` for shader "codegen args" (i.e. `RUSTFLAGS=-Cllvm-args="..."`)
(check out ["codegen args" docs](docs/src/codegen-args.md), or run with `RUSTGPU_CODEGEN_ARGS=--help` to see the full list of options)
- [PR#940](https://github.com/EmbarkStudios/rust-gpu/pull/940) integrated the experimental [`SPIR-🇹` shader IR framework](https://github.com/EmbarkStudios/spirt) into the linker
(opt-in via `RUSTGPU_CODEGEN_ARGS=--spirt`, see also [the `--spirt` docs](docs/src/codegen-args.md#--spirt), for more details)

### Changed 🛠️
- [PR#958](https://github.com/EmbarkStudios/rust-gpu/pull/958) updated toolchain to `nightly-2022-10-29`
Expand Down
46 changes: 46 additions & 0 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ codegen-units = 256
opt-level = 3
incremental = true
codegen-units = 256

[patch.crates-io]
# HACK(eddyb) only needed until `spirt 0.1.0` is released.
spirt = { git = "https://github.com/EmbarkStudios/spirt.git", rev = "cda161b7e7b336685448ab0a7f8cfe96bd90e752" }
1 change: 1 addition & 0 deletions crates/rustc_codegen_spirv/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ serde_json = "1.0"
smallvec = { version = "1.6.1", features = ["union"] }
spirv-tools = { version = "0.9", default-features = false }
rustc_codegen_spirv-types.workspace = true
spirt = "0.1.0"

[dev-dependencies]
pipe = "0.4"
Expand Down
14 changes: 14 additions & 0 deletions crates/rustc_codegen_spirv/src/codegen_cx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,12 @@ impl CodegenArgs {
);
opts.optflag("", "no-structurize", "disables CFG structurization");

opts.optflag(
"",
"spirt",
"use SPIR-T for legalization (see also `docs/src/codegen-args.md`)",
);

// NOTE(eddyb) these are debugging options that used to be env vars
// (for more information see `docs/src/codegen-args.md`).
opts.optopt(
Expand All @@ -365,6 +371,12 @@ impl CodegenArgs {
"dump modules immediately after multimodule splitting, to files in DIR",
"DIR",
);
opts.optopt(
"",
"dump-spirt-passes",
"dump the SPIR-T module across passes, to FILE and FILE.html",
"FILE",
);
opts.optflag(
"",
"specializer-debug",
Expand Down Expand Up @@ -511,6 +523,7 @@ impl CodegenArgs {
dce: !matches.opt_present("no-dce"),
compact_ids: !matches.opt_present("no-compact-ids"),
structurize: !matches.opt_present("no-structurize"),
spirt: matches.opt_present("spirt"),

// FIXME(eddyb) deduplicate between `CodegenArgs` and `linker::Options`.
emit_multiple_modules: module_output_type == ModuleOutputType::Multiple,
Expand All @@ -521,6 +534,7 @@ impl CodegenArgs {
// (for more information see `docs/src/codegen-args.md`).
dump_post_merge: matches_opt_path("dump-post-merge"),
dump_post_split: matches_opt_dump_dir_path("dump-post-split"),
dump_spirt_passes: matches_opt_path("dump-spirt-passes"),
specializer_debug: matches.opt_present("specializer-debug"),
specializer_dump_instances: matches_opt_path("specializer-dump-instances"),
print_all_zombie: matches.opt_present("print-all-zombie"),
Expand Down
137 changes: 132 additions & 5 deletions crates/rustc_codegen_spirv/src/linker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub struct Options {
pub compact_ids: bool,
pub dce: bool,
pub structurize: bool,
pub spirt: bool,

pub emit_multiple_modules: bool,
pub spirv_metadata: SpirvMetadata,
Expand All @@ -48,6 +49,7 @@ pub struct Options {
// (for more information see `docs/src/codegen-args.md`).
pub dump_post_merge: Option<PathBuf>,
pub dump_post_split: Option<PathBuf>,
pub dump_spirt_passes: Option<PathBuf>,
pub specializer_debug: bool,
pub specializer_dump_instances: Option<PathBuf>,
pub print_all_zombie: bool,
Expand Down Expand Up @@ -141,10 +143,10 @@ pub fn link(sess: &Session, mut inputs: Vec<Module>, opts: &Options) -> Result<L
bound += module.header.as_ref().unwrap().bound - 1;
let this_version = module.header.as_ref().unwrap().version();
if version != this_version {
sess.fatal(format!(
return Err(sess.err(format!(
"cannot link two modules with different SPIR-V versions: v{}.{} and v{}.{}",
version.0, version.1, this_version.0, this_version.1
))
)));
}
}

Expand Down Expand Up @@ -234,25 +236,70 @@ pub fn link(sess: &Session, mut inputs: Vec<Module>, opts: &Options) -> Result<L
);
}

// NOTE(eddyb) with SPIR-T, we can do `mem2reg` before inlining, too!
if opts.spirt {
if opts.dce {
let _timer = sess.timer("link_dce-before-inlining");
dce::dce(&mut output);
}

let _timer = sess.timer("link_block_ordering_pass_and_mem2reg-before-inlining");
let mut pointer_to_pointee = FxHashMap::default();
let mut constants = FxHashMap::default();
let mut u32 = None;
for inst in &output.types_global_values {
match inst.class.opcode {
Op::TypePointer => {
pointer_to_pointee
.insert(inst.result_id.unwrap(), inst.operands[1].unwrap_id_ref());
}
Op::TypeInt
if inst.operands[0].unwrap_literal_int32() == 32
&& inst.operands[1].unwrap_literal_int32() == 0 =>
{
assert!(u32.is_none());
u32 = Some(inst.result_id.unwrap());
}
Op::Constant if u32.is_some() && inst.result_type == u32 => {
let value = inst.operands[0].unwrap_literal_int32();
constants.insert(inst.result_id.unwrap(), value);
}
_ => {}
}
}
for func in &mut output.functions {
simple_passes::block_ordering_pass(func);
// Note: mem2reg requires functions to be in RPO order (i.e. block_ordering_pass)
mem2reg::mem2reg(
output.header.as_mut().unwrap(),
&mut output.types_global_values,
&pointer_to_pointee,
&constants,
func,
);
destructure_composites::destructure_composites(func);
}
}

{
let _timer = sess.timer("link_inline");
inline::inline(sess, &mut output)?;
}

if opts.dce {
let _timer = sess.timer("link_dce");
let _timer = sess.timer("link_dce-after-inlining");
dce::dce(&mut output);
}

let mut output = if opts.structurize {
let mut output = if opts.structurize && !opts.spirt {
let _timer = sess.timer("link_structurize");
structurizer::structurize(output)
} else {
output
};

{
let _timer = sess.timer("link_block_ordering_pass_and_mem2reg");
let _timer = sess.timer("link_block_ordering_pass_and_mem2reg-after-inlining");
let mut pointer_to_pointee = FxHashMap::default();
let mut constants = FxHashMap::default();
let mut u32 = None;
Expand Down Expand Up @@ -290,6 +337,86 @@ pub fn link(sess: &Session, mut inputs: Vec<Module>, opts: &Options) -> Result<L
}
}

if opts.spirt {
let mut per_pass_module_for_dumping = vec![];
let mut after_pass = |pass, module: &spirt::Module| {
if opts.dump_spirt_passes.is_some() {
per_pass_module_for_dumping.push((pass, module.clone()));
}
};

let spv_bytes = {
let _timer = sess.timer("assemble-to-spv_bytes-for-spirt");
spirv_tools::binary::from_binary(&output.assemble()).to_vec()
};
let cx = std::rc::Rc::new(spirt::Context::new());
let mut module = {
let _timer = sess.timer("spirt::Module::lower_from_spv_file");
match spirt::Module::lower_from_spv_bytes(cx.clone(), spv_bytes) {
Ok(module) => module,
Err(e) => {
use rspirv::binary::Disassemble;

return Err(sess
.struct_err(format!("{e}"))
.note(format!(
"while lowering this SPIR-V module to SPIR-T:\n{}",
output.disassemble()
))
.emit());
}
}
};
after_pass("lower_from_spv", &module);

if opts.structurize {
{
let _timer = sess.timer("spirt::legalize::structurize_func_cfgs");
spirt::passes::legalize::structurize_func_cfgs(&mut module);
}
after_pass("structurize_func_cfgs", &module);
}

// NOTE(eddyb) this should be *before* `lift_to_spv` below,
// so if that fails, the dump could be used to debug it.
if let Some(dump_file) = &opts.dump_spirt_passes {
// HACK(eddyb) using `.with_extension(...)` may replace part of a
// `.`-containing file name, but we want to always append `.html`.
let mut dump_file_html = dump_file.as_os_str().to_owned();
dump_file_html.push(".html");

let plan = spirt::print::Plan::for_versions(
&cx,
per_pass_module_for_dumping
.iter()
.map(|(pass, module)| (format!("after {pass}"), module)),
);
let pretty = plan.pretty_print();

// FIXME(eddyb) don't allocate whole `String`s here.
std::fs::write(dump_file, pretty.to_string()).unwrap();
std::fs::write(
dump_file_html,
pretty
.render_to_html()
.with_dark_mode_support()
.to_html_doc(),
)
.unwrap();
}

let spv_words = {
let _timer = sess.timer("spirt::Module::lift_to_spv_module_emitter");
module.lift_to_spv_module_emitter().unwrap().words
};
output = {
let _timer = sess.timer("parse-spv_words-from-spirt");
let mut loader = Loader::new();
rspirv::binary::parse_words(&spv_words, &mut loader).unwrap();
loader.module()
};
}

{
let _timer = sess.timer("peephole_opts");
let types = peephole_opts::collect_types(&output);
Expand Down
Loading