Move disassemble tests to compiletest (#609)

* Move disassemble tests to compiletest

* Fix problematic tests

* Add newlines
This commit is contained in:
Ashley Hauck 2021-04-30 09:07:45 +02:00 committed by GitHub
parent 7a6806c17b
commit 1431c18b9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 564 additions and 716 deletions

View File

@ -38,7 +38,6 @@ function cargo_test_no_features() {
# Core crates
cargo_test crates/rustc_codegen_spirv
cargo_test crates/spirv-builder
# Examples
# See: https://github.com/EmbarkStudios/rust-gpu/issues/84

2
Cargo.lock generated
View File

@ -2332,9 +2332,7 @@ dependencies = [
name = "spirv-builder"
version = "0.4.0-alpha.7"
dependencies = [
"lazy_static",
"memchr",
"pretty_assertions",
"raw-string",
"rustc_codegen_spirv",
"serde",

View File

@ -214,6 +214,10 @@ impl<'tcx> CodegenCx<'tcx> {
pub struct CodegenArgs {
pub module_output_type: ModuleOutputType,
pub disassemble: bool,
pub disassemble_fn: Option<String>,
pub disassemble_entry: Option<String>,
pub disassemble_globals: bool,
}
impl CodegenArgs {
@ -233,10 +237,118 @@ impl CodegenArgs {
"single output or multiple output",
"[single|multiple]",
);
opts.optflagopt("", "disassemble", "print module to stderr", "");
opts.optopt("", "disassemble-fn", "print function to stderr", "NAME");
opts.optopt(
"",
"disassemble-entry",
"print entry point to stderr",
"NAME",
);
opts.optflagopt("", "disassemble-globals", "print globals to stderr", "");
let matches = opts.parse(args)?;
let module_output_type =
matches.opt_get_default("module-output", ModuleOutputType::Single)?;
Ok(Self { module_output_type })
let disassemble = matches.opt_present("disassemble");
let disassemble_fn = matches.opt_str("disassemble-fn");
let disassemble_entry = matches.opt_str("disassemble-entry");
let disassemble_globals = matches.opt_present("disassemble-globals");
Ok(Self {
module_output_type,
disassemble,
disassemble_fn,
disassemble_entry,
disassemble_globals,
})
}
pub fn do_disassemble(&self, module: &Module) {
fn compact_ids(module: &mut rspirv::dr::Function) -> u32 {
let mut remap = std::collections::HashMap::new();
let mut insert = |current_id: &mut u32| {
let len = remap.len();
*current_id = *remap.entry(*current_id).or_insert_with(|| len as u32 + 1)
};
module.all_inst_iter_mut().for_each(|inst| {
if let Some(ref mut result_id) = &mut inst.result_id {
insert(result_id)
}
if let Some(ref mut result_type) = &mut inst.result_type {
insert(result_type)
}
inst.operands.iter_mut().for_each(|op| {
if let Some(w) = op.id_ref_any_mut() {
insert(w)
}
})
});
remap.len() as u32 + 1
}
use rspirv::binary::Disassemble;
if self.disassemble {
eprintln!("{}", module.disassemble());
}
if let Some(func) = &self.disassemble_fn {
let id = module
.debugs
.iter()
.find(|inst| {
inst.class.opcode == rspirv::spirv::Op::Name
&& inst.operands[1].unwrap_literal_string() == func
})
.unwrap_or_else(|| {
panic!(
"no function with the name `{}` found in:\n{}\n",
func,
module.disassemble()
)
})
.operands[0]
.unwrap_id_ref();
let mut func = module
.functions
.iter()
.find(|f| f.def_id().unwrap() == id)
.unwrap()
.clone();
// Compact to make IDs more stable
compact_ids(&mut func);
eprintln!("{}", func.disassemble());
}
if let Some(entry) = &self.disassemble_entry {
let id = module
.entry_points
.iter()
.find(|inst| inst.operands.last().unwrap().unwrap_literal_string() == entry)
.unwrap_or_else(|| {
panic!(
"no entry point with the name `{}` found in:\n{}\n",
entry,
module.disassemble()
)
})
.operands[1]
.unwrap_id_ref();
let mut func = module
.functions
.iter()
.find(|f| f.def_id().unwrap() == id)
.unwrap()
.clone();
// Compact to make IDs more stable
compact_ids(&mut func);
eprintln!("{}", func.disassemble());
}
if self.disassemble_globals {
for inst in module.global_inst_iter() {
eprintln!("{}", inst.disassemble());
}
}
}
}

View File

@ -131,10 +131,13 @@ fn link_exe(
let spv_binary = do_link(sess, &objects, &rlibs, legalize, emit_multiple_modules);
let cg_args = crate::codegen_cx::CodegenArgs::from_session(sess);
use rspirv::binary::Assemble;
match spv_binary {
linker::LinkResult::SingleModule(spv_binary) => {
post_link_single_module(sess, spv_binary.assemble(), out_filename);
cg_args.do_disassemble(&spv_binary);
}
linker::LinkResult::MultipleModules(map) => {
let mut root_file_name = out_filename.file_name().unwrap().to_owned();

View File

@ -18,7 +18,3 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# See comment in lib.rs invoke_rustc for why this is here
rustc_codegen_spirv = { path = "../rustc_codegen_spirv", default-features = false }
[dev-dependencies]
lazy_static = "1.4"
pretty_assertions = "0.7"

View File

@ -53,9 +53,6 @@
// crate-specific exceptions:
#![allow()]
#[cfg(test)]
mod test;
mod depfile;
use raw_string::{RawStr, RawString};

View File

@ -1,496 +0,0 @@
use super::{dis_entry_fn, dis_fn, dis_globals, val};
use std::ffi::OsStr;
struct SetEnvVar<'a> {
k: &'a OsStr,
}
impl<'a> SetEnvVar<'a> {
fn new(k: &'a impl AsRef<OsStr>, v: impl AsRef<OsStr>) -> Self {
let k = k.as_ref();
std::env::set_var(k, v);
Self { k }
}
}
impl<'a> Drop for SetEnvVar<'a> {
fn drop(&mut self) {
std::env::remove_var(self.k)
}
}
#[test]
fn custom_entry_point() {
dis_globals(
r#"
#[spirv(fragment(entry_point_name="hello_world"))]
pub fn main() { }
"#,
r#"OpCapability Shader
OpMemoryModel Logical Simple
OpEntryPoint Fragment %1 "hello_world"
OpExecutionMode %1 OriginUpperLeft
OpName %2 "test_project::main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3"#,
);
}
#[test]
// blocked on: https://github.com/EmbarkStudios/rust-gpu/issues/69
#[ignore]
fn no_dce() {
let _var = SetEnvVar::new(&"NO_DCE", "1");
val(r#"
#[spirv(fragment)]
pub fn no_dce() {
}
"#);
}
#[test]
fn add_two_ints() {
dis_fn(
r#"
fn add_two_ints(x: u32, y: u32) -> u32 {
x + y
}
#[spirv(fragment)]
pub fn main() {
add_two_ints(2, 3);
}
"#,
"add_two_ints",
r#"%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %2
%5 = OpFunctionParameter %2
%6 = OpLabel
%7 = OpIAdd %2 %4 %5
OpReturnValue %7
OpFunctionEnd"#,
);
}
#[test]
fn asm() {
dis_fn(
r#"
fn asm() {
unsafe {
asm!(
"%int = OpTypeInt 32 0",
"%scope = OpConstant %int 2",
"%semantics = OpConstant %int 16",
"OpMemoryBarrier %scope %semantics",
);
}
}
#[spirv(fragment)]
pub fn main() {
asm();
}
"#,
"asm",
// note: the OpConstants get hoisted out to global in the linker merge pass
r#"%1 = OpFunction %2 None %3
%4 = OpLabel
OpMemoryBarrier %5 %6
OpReturn
OpFunctionEnd"#,
);
}
#[test]
fn asm_add_two_ints() {
dis_fn(
r#"
fn add_two_ints(x: u32, y: u32) -> u32 {
let result;
unsafe {
asm!(
"{0} = OpIAdd typeof{0} {1} {2}",
out(reg) result,
in(reg) x,
in(reg) y,
);
}
result
}
#[spirv(fragment)]
pub fn main() {
add_two_ints(2, 3);
}
"#,
"add_two_ints",
r#"%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %2
%5 = OpFunctionParameter %2
%6 = OpLabel
%7 = OpIAdd %2 %4 %5
OpReturnValue %7
OpFunctionEnd"#,
);
}
#[test]
fn asm_op_decorate() {
// Tests that OpDecorate gets parsed and emitted properly since it's a vararg style instruction
dis_globals(
r#"
fn add_decorate() {
unsafe {
let offset = 1u32;
asm!(
"OpExtension \"SPV_EXT_descriptor_indexing\"",
"OpCapability RuntimeDescriptorArray",
"OpDecorate %image_2d_var DescriptorSet 0",
"OpDecorate %image_2d_var Binding 0",
"%uint = OpTypeInt 32 0",
"%float = OpTypeFloat 32",
"%uint_0 = OpConstant %uint 0",
"%image_2d = OpTypeImage %float Dim2D 0 0 0 1 Unknown",
"%sampled_image_2d = OpTypeSampledImage %image_2d",
"%image_array = OpTypeRuntimeArray %sampled_image_2d",
// NOTE(eddyb) `Generic` is used here because it's the placeholder
// for storage class inference - both of the two `OpTypePointer`
// types below should end up inferring to `UniformConstant`.
"%ptr_image_array = OpTypePointer Generic %image_array",
"%image_2d_var = OpVariable %ptr_image_array UniformConstant",
"%ptr_sampled_image_2d = OpTypePointer Generic %sampled_image_2d",
"", // ^^ type preamble
"%offset = OpLoad _ {0}",
"%24 = OpAccessChain %ptr_sampled_image_2d %image_2d_var %offset",
"%25 = OpLoad %sampled_image_2d %24",
in(reg) &offset,
);
}
}
#[spirv(fragment)]
pub fn main() {
add_decorate();
}"#,
r#"OpCapability Shader
OpCapability RuntimeDescriptorArray
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical Simple
OpEntryPoint Fragment %1 "main"
OpExecutionMode %1 OriginUpperLeft
OpName %2 "test_project::add_decorate"
OpName %3 "test_project::main"
OpDecorate %4 ArrayStride 4
OpDecorate %5 DescriptorSet 0
OpDecorate %5 Binding 0
%6 = OpTypeVoid
%7 = OpTypeFunction %6
%8 = OpTypeInt 32 0
%9 = OpConstant %8 1
%10 = OpTypeFloat 32
%11 = OpTypeImage %10 2D 0 0 0 1 Unknown
%12 = OpTypeSampledImage %11
%4 = OpTypeRuntimeArray %12
%13 = OpTypePointer UniformConstant %4
%5 = OpVariable %13 UniformConstant
%14 = OpTypePointer UniformConstant %12"#,
);
}
#[test]
fn unroll_loops() {
dis_fn(
// FIXME(eddyb) use `for _ in 0..10` here when that works.
r#"
#[spirv(unroll_loops)]
fn java_hash_ten_times(mut x: u32, y: u32) -> u32 {
let mut i = 0;
while i < 10 {
x = 31 * x + y;
i += 1;
}
x
}
#[spirv(fragment)]
pub fn main() {
java_hash_ten_times(7, 42);
}
"#,
"java_hash_ten_times",
// NOTE(eddyb) this is very verbose because of the new structurizer
// producing messier control-flow than necessary, but the important part
// being tested is `OpLoopMerge` having `Unroll` as its "Loop Control".
r#"%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %2
%5 = OpFunctionParameter %2
%6 = OpLabel
OpBranch %7
%7 = OpLabel
OpBranch %8
%8 = OpLabel
%9 = OpPhi %10 %11 %7 %12 %13
%14 = OpPhi %2 %4 %7 %15 %13
%16 = OpPhi %17 %18 %7 %19 %13
OpLoopMerge %20 %13 Unroll
OpBranchConditional %16 %21 %20
%21 = OpLabel
%22 = OpSLessThan %17 %9 %23
OpSelectionMerge %24 None
OpBranchConditional %22 %25 %26
%25 = OpLabel
%27 = OpIMul %2 %28 %14
%15 = OpIAdd %2 %27 %5
%12 = OpIAdd %10 %9 %29
OpBranch %24
%26 = OpLabel
OpReturnValue %14
%24 = OpLabel
%19 = OpPhi %17 %18 %25
OpBranch %13
%13 = OpLabel
OpBranch %8
%20 = OpLabel
OpUnreachable
OpFunctionEnd"#,
);
}
#[test]
fn complex_image_sample_inst() {
dis_fn(
r#"
fn sample_proj_lod(coord: glam::Vec4, ddx: glam::Vec2, ddy: glam::Vec2, offset_x: i32, offset_y: i32) -> glam::Vec4 {
unsafe {
let mut result = glam::Vec4::default();
let index = 0u32;
asm!(
"OpExtension \"SPV_EXT_descriptor_indexing\"",
"OpCapability RuntimeDescriptorArray",
"OpDecorate %image_2d_var DescriptorSet 0",
"OpDecorate %image_2d_var Binding 0",
"%uint = OpTypeInt 32 0",
"%int = OpTypeInt 32 1",
"%float = OpTypeFloat 32",
"%v2int = OpTypeVector %int 2",
"%uint_0 = OpConstant %uint 0",
"%int_0 = OpConstant %int 0",
"%image_2d = OpTypeImage %float Dim2D 0 0 0 1 Unknown",
"%sampled_image_2d = OpTypeSampledImage %image_2d",
"%image_array = OpTypeRuntimeArray %sampled_image_2d",
// NOTE(eddyb) `Generic` is used here because it's the placeholder
// for storage class inference - both of the two `OpTypePointer`
// types below should end up inferring to `UniformConstant`.
"%ptr_image_array = OpTypePointer Generic %image_array",
"%image_2d_var = OpVariable %ptr_image_array UniformConstant",
"%ptr_sampled_image_2d = OpTypePointer Generic %sampled_image_2d",
"", // ^^ type preamble
"%offset = OpLoad _ {1}",
"%24 = OpAccessChain %ptr_sampled_image_2d %image_2d_var %offset",
"%25 = OpLoad %sampled_image_2d %24",
"%coord = OpLoad _ {0}",
"%ddx = OpLoad _ {3}",
"%ddy = OpLoad _ {4}",
"%offset_x = OpLoad _ {5}",
"%offset_y = OpLoad _ {6}",
"%const_offset = OpConstantComposite %v2int %int_0 %int_0",
"%result = OpImageSampleProjExplicitLod _ %25 %coord Grad|ConstOffset %ddx %ddy %const_offset",
"OpStore {2} %result",
in(reg) &coord,
in(reg) &index,
in(reg) &mut result,
in(reg) &ddx,
in(reg) &ddy,
in(reg) &offset_x,
in(reg) &offset_y,
);
result
}
}
#[spirv(fragment)]
pub fn main() {
sample_proj_lod(glam::Vec4::ZERO, glam::Vec2::ZERO, glam::Vec2::ZERO, 0, 0);
}"#,
"sample_proj_lod",
"%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %2
%5 = OpFunctionParameter %6
%7 = OpFunctionParameter %6
%8 = OpFunctionParameter %9
%10 = OpFunctionParameter %9
%11 = OpLabel
%12 = OpAccessChain %13 %14 %15
%16 = OpLoad %17 %12
%18 = OpImageSampleProjExplicitLod %2 %16 %4 Grad|ConstOffset %5 %7 %19
OpReturnValue %18
OpFunctionEnd",
);
}
/// Helper to generate all of the `ptr_*` tests below, which test that the various
/// ways to use raw pointer `read`/`write`/`copy`, to copy a single value, work,
/// and that the resulting SPIR-V uses either a pair of `OpLoad` and `OpStore`,
/// and/or the `OpCopyMemory` instruction, but *not* `OpCopyMemorySized`.
macro_rules! test_copy_via_raw_ptr {
($copy_expr:literal => $spirv:literal) => {
dis_fn(
concat!(
r#"
fn copy_via_raw_ptr(src: &f32, dst: &mut f32) {
unsafe {
"#,
$copy_expr,
r#"
}
}
#[spirv(fragment)]
pub fn main(i: f32, o: &mut f32) {
copy_via_raw_ptr(&i, o);
// FIXME(eddyb) above call results in inlining `copy_via_raw_ptr`,
// due to the to `Output` storage classe, so to get the disassembled
// function we also need `Function`-local pointers:
let (src, mut dst) = (0.0, 0.0);
copy_via_raw_ptr(&src, &mut dst);
}
"#
),
"copy_via_raw_ptr",
concat!(
r#"%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %5
%6 = OpFunctionParameter %5
%7 = OpLabel"#,
$spirv,
r#"OpReturn
OpFunctionEnd"#
),
);
};
}
#[test]
fn ptr_read() {
test_copy_via_raw_ptr!(
"*dst = core::ptr::read(src)"=> r#"
%8 = OpVariable %5 Function
OpStore %8 %9
OpCopyMemory %8 %4
%10 = OpLoad %11 %8
OpStore %6 %10
"#
);
}
#[test]
fn ptr_read_method() {
test_copy_via_raw_ptr!(
"*dst = (src as *const f32).read()" => r#"
%8 = OpVariable %5 Function
OpStore %8 %9
OpCopyMemory %8 %4
%10 = OpLoad %11 %8
OpStore %6 %10
"#
);
}
#[test]
fn ptr_write() {
test_copy_via_raw_ptr!(
"core::ptr::write(dst, *src)" => r#"
%8 = OpVariable %5 Function
%9 = OpLoad %10 %4
OpStore %8 %9
OpCopyMemory %6 %8
"#
);
}
#[test]
fn ptr_write_method() {
test_copy_via_raw_ptr!(
"(dst as *mut f32).write(*src)" => r#"
%8 = OpVariable %5 Function
%9 = OpLoad %10 %4
OpStore %8 %9
OpCopyMemory %6 %8
"#
);
}
#[test]
fn ptr_copy() {
test_copy_via_raw_ptr!(
"core::ptr::copy(src, dst, 1)" => r#"
OpCopyMemory %6 %4
"#
);
}
#[test]
// FIXME(eddyb) doesn't work because `<*const T>::copy_to` is a method that wraps
// the actual `core::ptr::copy` intrinsic - this requires either MIR inlining, or
// making the methods themselves intrinsic (via attributes instead of pseudo-ABI).
#[ignore]
fn ptr_copy_to_method() {
test_copy_via_raw_ptr!(
"(src as *const f32).copy_to(dst, 1)" => r#"
OpCopyMemory %6 %4
"#
);
}
#[test]
// FIXME(eddyb) doesn't work because `<*mut T>::copy_from` is a method that wraps
// the actual `core::ptr::copy` intrinsic - this requires either MIR inlining, or
// making the methods themselves intrinsic (via attributes instead of pseudo-ABI).
#[ignore]
fn ptr_copy_from_method() {
test_copy_via_raw_ptr!(
"(dst as *mut f32).copy_from(src, 1)" => r#"
OpCopyMemory %6 %4
"#
);
}
#[test]
fn index_user_dst() {
dis_entry_fn(
r#"
#[spirv(fragment)]
pub fn main(
#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slice: &mut [f32],
) {
let float: f32 = slice[0];
let _ = float;
}
"#,
"main",
r#"%1 = OpFunction %2 None %3
%4 = OpLabel
%5 = OpAccessChain %6 %7 %8
%9 = OpArrayLength %10 %7 0
%11 = OpCompositeInsert %12 %5 %13 0
%14 = OpCompositeInsert %12 %9 %11 1
%15 = OpULessThan %16 %8 %9
OpSelectionMerge %17 None
OpBranchConditional %15 %18 %19
%18 = OpLabel
%20 = OpInBoundsAccessChain %21 %5 %8
%22 = OpLoad %23 %20
OpReturn
%19 = OpLabel
OpBranch %24
%24 = OpLabel
OpBranch %25
%25 = OpLabel
%26 = OpPhi %16 %27 %24 %27 %28
OpLoopMerge %29 %28 None
OpBranchConditional %26 %30 %29
%30 = OpLabel
OpBranch %28
%28 = OpLabel
OpBranch %25
%29 = OpLabel
OpUnreachable
%17 = OpLabel
OpUnreachable
OpFunctionEnd"#,
)
}

View File

@ -1,209 +0,0 @@
mod basic;
use lazy_static::lazy_static;
use rustc_codegen_spirv::rspirv;
use std::error::Error;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, MutexGuard};
// https://github.com/colin-kiegel/rust-pretty-assertions/issues/24
#[derive(PartialEq, Eq)]
pub struct PrettyString<'a>(pub &'a str);
/// Make diff to display string as multi-line string
impl<'a> std::fmt::Debug for PrettyString<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0)
}
}
// Tests need to run serially, since they write project files to disk and whatnot. We don't want to
// create a new temp dir for every test, though, since then every test would need to build libcore.
// We could require the user to pass --thread-count 1 to cargo test, but that affects other tests.
// So make a global mutex wrapping every test.
lazy_static! {
static ref GLOBAL_MUTEX: Mutex<()> = Mutex::new(());
}
fn global_lock() -> MutexGuard<'static, ()> {
match GLOBAL_MUTEX.lock() {
Ok(guard) => guard,
// we don't care about poison - a previous test may have paniced, but that's okay
Err(poisoned) => poisoned.into_inner(),
}
}
static CARGO_TOML: &str = r#"[package]
name = "test-project"
version = "0.1.0"
authors = ["Embark <opensource@embark-studios.com>"]
edition = "2018"
[lib]
crate-type = ["dylib"]
[profile.dev]
overflow-checks = false
debug-assertions = false
[dependencies]
spirv-std = { path = "../../crates/spirv-std", features=["const-generics"] }
glam = { git = "https://github.com/EmbarkStudios/glam-rs.git", rev="7476a96", default-features=false, features = ["libm", "scalar-math"] }
[patch.crates-io.spirv-std]
path="../../crates/spirv-std"
[workspace]
"#;
static SRC_PREFIX: &str = r#"#![no_std]
#![feature(register_attr, asm, ptr_internals)]
#![register_attr(spirv)]
#![deny(warnings)]
#[allow(unused_imports)]
use spirv_std::{*, num_traits::Float as _ };
"#;
fn setup(src: &str) -> Result<PathBuf, Box<dyn Error>> {
let project = Path::new("../../target/test-spirv").to_owned();
let cargo_toml = project.join("Cargo.toml");
let lib_rs = project.join("src/lib.rs");
std::fs::create_dir_all(lib_rs.parent().unwrap())?;
// don't write cargo.toml if unchanged, so it doesn't get deps refreshed
if std::fs::read(&cargo_toml).map_or(true, |b| b != CARGO_TOML.as_bytes()) {
std::fs::write(cargo_toml, CARGO_TOML)?;
}
std::fs::write(lib_rs, format!("{}{}", SRC_PREFIX, src))?;
Ok(project)
}
fn build(src: &str) -> PathBuf {
let project = setup(src).expect("Failed to set up project");
crate::SpirvBuilder::new(&project, "spirv-unknown-spv1.3")
.print_metadata(false)
.release(false)
.build()
.expect("Failed to build test")
}
fn read_module(path: &Path) -> Result<rspirv::dr::Module, Box<dyn Error>> {
let bytes = std::fs::read(path)?;
let mut loader = rspirv::dr::Loader::new();
rspirv::binary::parse_bytes(&bytes, &mut loader)?;
Ok(loader.module())
}
fn val(src: &str) {
let _lock = global_lock();
// spirv-val is included in building
build(src);
}
fn assert_str_eq(expected: &str, result: &str) {
let expected = expected
.split('\n')
.map(|l| l.trim())
.collect::<Vec<_>>()
.join("\n");
let result = result
.split('\n')
.map(|l| l.trim().replace(" ", " ")) // rspirv outputs multiple spaces between operands
.collect::<Vec<_>>()
.join("\n");
pretty_assertions::assert_eq!(PrettyString(&expected), PrettyString(&result))
}
fn dis_fn(src: &str, func: &str, expect: &str) {
let _lock = global_lock();
let module = read_module(&build(src)).unwrap();
let abs_func_path = format!("test_project::{}", func);
let id = module
.debugs
.iter()
.find(|inst| {
inst.class.opcode == rspirv::spirv::Op::Name
&& inst.operands[1].unwrap_literal_string() == abs_func_path
})
.unwrap_or_else(|| {
panic!(
"no function with the name `{}` found in:\n{}\n",
abs_func_path,
module.disassemble()
)
})
.operands[0]
.unwrap_id_ref();
let mut func = module
.functions
.into_iter()
.find(|f| f.def_id().unwrap() == id)
.unwrap();
// Compact to make IDs more stable
compact_ids(&mut func);
use rspirv::binary::Disassemble;
assert_str_eq(expect, &func.disassemble())
}
fn dis_entry_fn(src: &str, func: &str, expect: &str) {
let _lock = global_lock();
let module = read_module(&build(src)).unwrap();
let id = module
.entry_points
.iter()
.find(|inst| inst.operands.last().unwrap().unwrap_literal_string() == func)
.unwrap_or_else(|| {
panic!(
"no entry point with the name `{}` found in:\n{}\n",
func,
module.disassemble()
)
})
.operands[1]
.unwrap_id_ref();
let mut func = module
.functions
.into_iter()
.find(|f| f.def_id().unwrap() == id)
.unwrap();
// Compact to make IDs more stable
compact_ids(&mut func);
use rspirv::binary::Disassemble;
assert_str_eq(expect, &func.disassemble())
}
fn dis_globals(src: &str, expect: &str) {
let _lock = global_lock();
let module = read_module(&build(src)).unwrap();
use rspirv::binary::Disassemble;
let dis = module
.global_inst_iter()
.map(|inst| inst.disassemble())
.collect::<Vec<String>>()
.join("\n");
assert_str_eq(expect, &dis);
}
fn compact_ids(module: &mut rspirv::dr::Function) -> u32 {
let mut remap = std::collections::HashMap::new();
let mut insert = |current_id: &mut u32| {
let len = remap.len();
*current_id = *remap.entry(*current_id).or_insert_with(|| len as u32 + 1)
};
module.all_inst_iter_mut().for_each(|inst| {
if let Some(ref mut result_id) = &mut inst.result_id {
insert(result_id)
}
if let Some(ref mut result_type) = &mut inst.result_type {
insert(result_type)
}
inst.operands.iter_mut().for_each(|op| {
if let Some(w) = op.id_ref_any_mut() {
insert(w)
}
})
});
remap.len() as u32 + 1
}

View File

@ -0,0 +1,12 @@
// build-pass
// compile-flags: -C llvm-args=--disassemble-fn=add_two_ints::add_two_ints
use spirv_std as _;
fn add_two_ints(x: u32, y: u32) -> u32 {
x + y
}
#[spirv(fragment)]
pub fn main() {
add_two_ints(2, 3);
}

View File

@ -0,0 +1,7 @@
%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %2
%5 = OpFunctionParameter %2
%6 = OpLabel
%7 = OpIAdd %2 %4 %5
OpReturnValue %7
OpFunctionEnd

19
tests/ui/dis/asm.rs Normal file
View File

@ -0,0 +1,19 @@
// build-pass
// compile-flags: -C llvm-args=--disassemble-fn=asm::asm
use spirv_std as _;
fn asm() {
unsafe {
asm!(
"%int = OpTypeInt 32 0",
"%scope = OpConstant %int 3",
"%semantics = OpConstant %int 72",
"OpMemoryBarrier %scope %semantics",
);
}
}
#[spirv(fragment)]
pub fn main() {
asm();
}

5
tests/ui/dis/asm.stderr Normal file
View File

@ -0,0 +1,5 @@
%1 = OpFunction %2 None %3
%4 = OpLabel
OpMemoryBarrier %5 %6
OpReturn
OpFunctionEnd

View File

@ -0,0 +1,21 @@
// build-pass
// compile-flags: -C llvm-args=--disassemble-fn=asm_add_two_ints::add_two_ints
use spirv_std as _;
fn add_two_ints(x: u32, y: u32) -> u32 {
let result;
unsafe {
asm!(
"{0} = OpIAdd typeof{0} {1} {2}",
out(reg) result,
in(reg) x,
in(reg) y,
);
}
result
}
#[spirv(fragment)]
pub fn main() {
add_two_ints(2, 3);
}

View File

@ -0,0 +1,7 @@
%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %2
%5 = OpFunctionParameter %2
%6 = OpLabel
%7 = OpIAdd %2 %4 %5
OpReturnValue %7
OpFunctionEnd

View File

@ -0,0 +1,40 @@
// build-pass
// compile-flags: -C llvm-args=--disassemble-globals
// normalize-stderr-test "OpCapability VulkanMemoryModel\n" -> ""
// normalize-stderr-test "OpExtension .SPV_KHR_vulkan_memory_model.\n" -> ""
// normalize-stderr-test "OpMemoryModel Logical Vulkan" -> "OpMemoryModel Logical Simple"
use spirv_std as _;
fn add_decorate() {
unsafe {
let offset = 1u32;
asm!(
"OpExtension \"SPV_EXT_descriptor_indexing\"",
"OpCapability RuntimeDescriptorArray",
"OpDecorate %image_2d_var DescriptorSet 0",
"OpDecorate %image_2d_var Binding 0",
"%uint = OpTypeInt 32 0",
"%float = OpTypeFloat 32",
"%uint_0 = OpConstant %uint 0",
"%image_2d = OpTypeImage %float Dim2D 0 0 0 1 Unknown",
"%sampled_image_2d = OpTypeSampledImage %image_2d",
"%image_array = OpTypeRuntimeArray %sampled_image_2d",
// NOTE(eddyb) `Generic` is used here because it's the placeholder
// for storage class inference - both of the two `OpTypePointer`
// types below should end up inferring to `UniformConstant`.
"%ptr_image_array = OpTypePointer Generic %image_array",
"%image_2d_var = OpVariable %ptr_image_array UniformConstant",
"%ptr_sampled_image_2d = OpTypePointer Generic %sampled_image_2d",
"", // ^^ type preamble
"%offset = OpLoad _ {0}",
"%24 = OpAccessChain %ptr_sampled_image_2d %image_2d_var %offset",
"%25 = OpLoad %sampled_image_2d %24",
in(reg) &offset,
);
}
}
#[spirv(fragment)]
pub fn main() {
add_decorate();
}

View File

@ -0,0 +1,22 @@
OpCapability Shader
OpCapability RuntimeDescriptorArray
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical Simple
OpEntryPoint Fragment %1 "main"
OpExecutionMode %1 OriginUpperLeft
OpName %2 "asm_op_decorate::add_decorate"
OpName %3 "asm_op_decorate::main"
OpDecorate %4 ArrayStride 4
OpDecorate %5 DescriptorSet 0
OpDecorate %5 Binding 0
%6 = OpTypeVoid
%7 = OpTypeFunction %6
%8 = OpTypeInt 32 0
%9 = OpConstant %8 1
%10 = OpTypeFloat 32
%11 = OpTypeImage %10 2D 0 0 0 1 Unknown
%12 = OpTypeSampledImage %11
%4 = OpTypeRuntimeArray %12
%13 = OpTypePointer UniformConstant %4
%5 = OpVariable %13 UniformConstant
%14 = OpTypePointer UniformConstant %12

View File

@ -0,0 +1,62 @@
// build-pass
// compile-flags: -C llvm-args=--disassemble-fn=complex_image_sample_inst::sample_proj_lod
use spirv_std as _;
fn sample_proj_lod(
coord: glam::Vec4,
ddx: glam::Vec2,
ddy: glam::Vec2,
offset_x: i32,
offset_y: i32,
) -> glam::Vec4 {
unsafe {
let mut result = glam::Vec4::default();
let index = 0u32;
asm!(
"OpExtension \"SPV_EXT_descriptor_indexing\"",
"OpCapability RuntimeDescriptorArray",
"OpDecorate %image_2d_var DescriptorSet 0",
"OpDecorate %image_2d_var Binding 0",
"%uint = OpTypeInt 32 0",
"%int = OpTypeInt 32 1",
"%float = OpTypeFloat 32",
"%v2int = OpTypeVector %int 2",
"%uint_0 = OpConstant %uint 0",
"%int_0 = OpConstant %int 0",
"%image_2d = OpTypeImage %float Dim2D 0 0 0 1 Unknown",
"%sampled_image_2d = OpTypeSampledImage %image_2d",
"%image_array = OpTypeRuntimeArray %sampled_image_2d",
// NOTE(eddyb) `Generic` is used here because it's the placeholder
// for storage class inference - both of the two `OpTypePointer`
// types below should end up inferring to `UniformConstant`.
"%ptr_image_array = OpTypePointer Generic %image_array",
"%image_2d_var = OpVariable %ptr_image_array UniformConstant",
"%ptr_sampled_image_2d = OpTypePointer Generic %sampled_image_2d",
"", // ^^ type preamble
"%offset = OpLoad _ {1}",
"%24 = OpAccessChain %ptr_sampled_image_2d %image_2d_var %offset",
"%25 = OpLoad %sampled_image_2d %24",
"%coord = OpLoad _ {0}",
"%ddx = OpLoad _ {3}",
"%ddy = OpLoad _ {4}",
"%offset_x = OpLoad _ {5}",
"%offset_y = OpLoad _ {6}",
"%const_offset = OpConstantComposite %v2int %int_0 %int_0",
"%result = OpImageSampleProjExplicitLod _ %25 %coord Grad|ConstOffset %ddx %ddy %const_offset",
"OpStore {2} %result",
in(reg) &coord,
in(reg) &index,
in(reg) &mut result,
in(reg) &ddx,
in(reg) &ddy,
in(reg) &offset_x,
in(reg) &offset_y,
);
result
}
}
#[spirv(fragment)]
pub fn main() {
sample_proj_lod(glam::Vec4::ZERO, glam::Vec2::ZERO, glam::Vec2::ZERO, 0, 0);
}

View File

@ -0,0 +1,12 @@
%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %2
%5 = OpFunctionParameter %6
%7 = OpFunctionParameter %6
%8 = OpFunctionParameter %9
%10 = OpFunctionParameter %9
%11 = OpLabel
%12 = OpAccessChain %13 %14 %15
%16 = OpLoad %17 %12
%18 = OpImageSampleProjExplicitLod %2 %16 %4 Grad|ConstOffset %5 %7 %19
OpReturnValue %18
OpFunctionEnd

View File

@ -0,0 +1,10 @@
// build-pass
// compile-flags: -C llvm-args=--disassemble-globals
// normalize-stderr-test "OpCapability VulkanMemoryModel\n" -> ""
// normalize-stderr-test "OpExtension .SPV_KHR_vulkan_memory_model.\n" -> ""
// normalize-stderr-test "OpMemoryModel Logical Vulkan" -> "OpMemoryModel Logical Simple"
use spirv_std as _;
#[spirv(fragment(entry_point_name = "hello_world"))]
pub fn main() {}

View File

@ -0,0 +1,7 @@
OpCapability Shader
OpMemoryModel Logical Simple
OpEntryPoint Fragment %1 "hello_world"
OpExecutionMode %1 OriginUpperLeft
OpName %2 "custom_entry_point::main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3

View File

@ -0,0 +1,10 @@
// build-pass
// compile-flags: -C llvm-args=--disassemble-entry=main
use spirv_std as _;
#[spirv(fragment)]
pub fn main(#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slice: &mut [f32]) {
let float: f32 = slice[0];
let _ = float;
}

View File

@ -0,0 +1,30 @@
%1 = OpFunction %2 None %3
%4 = OpLabel
%5 = OpAccessChain %6 %7 %8
%9 = OpArrayLength %10 %7 0
%11 = OpCompositeInsert %12 %5 %13 0
%14 = OpCompositeInsert %12 %9 %11 1
%15 = OpULessThan %16 %8 %9
OpSelectionMerge %17 None
OpBranchConditional %15 %18 %19
%18 = OpLabel
%20 = OpInBoundsAccessChain %21 %5 %8
%22 = OpLoad %23 %20
OpReturn
%19 = OpLabel
OpBranch %24
%24 = OpLabel
OpBranch %25
%25 = OpLabel
%26 = OpPhi %16 %27 %24 %27 %28
OpLoopMerge %29 %28 None
OpBranchConditional %26 %30 %29
%30 = OpLabel
OpBranch %28
%28 = OpLabel
OpBranch %25
%29 = OpLabel
OpUnreachable
%17 = OpLabel
OpUnreachable
OpFunctionEnd

17
tests/ui/dis/ptr_copy.rs Normal file
View File

@ -0,0 +1,17 @@
// build-pass
// compile-flags: -C llvm-args=--disassemble-fn=ptr_copy::copy_via_raw_ptr
use spirv_std as _;
fn copy_via_raw_ptr(src: &f32, dst: &mut f32) {
unsafe { core::ptr::copy(src, dst, 1) }
}
#[spirv(fragment)]
pub fn main(i: f32, o: &mut f32) {
copy_via_raw_ptr(&i, o);
// FIXME(eddyb) above call results in inlining `copy_via_raw_ptr`,
// due to the to `Output` storage classe, so to get the disassembled
// function we also need `Function`-local pointers:
let (src, mut dst) = (0.0, 0.0);
copy_via_raw_ptr(&src, &mut dst);
}

View File

@ -0,0 +1,7 @@
%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %5
%6 = OpFunctionParameter %5
%7 = OpLabel
OpCopyMemory %6 %4
OpReturn
OpFunctionEnd

17
tests/ui/dis/ptr_read.rs Normal file
View File

@ -0,0 +1,17 @@
// build-pass
// compile-flags: -C llvm-args=--disassemble-fn=ptr_read::copy_via_raw_ptr
use spirv_std as _;
fn copy_via_raw_ptr(src: &f32, dst: &mut f32) {
unsafe { *dst = core::ptr::read(src) }
}
#[spirv(fragment)]
pub fn main(i: f32, o: &mut f32) {
copy_via_raw_ptr(&i, o);
// FIXME(eddyb) above call results in inlining `copy_via_raw_ptr`,
// due to the to `Output` storage classe, so to get the disassembled
// function we also need `Function`-local pointers:
let (src, mut dst) = (0.0, 0.0);
copy_via_raw_ptr(&src, &mut dst);
}

View File

@ -0,0 +1,11 @@
%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %5
%6 = OpFunctionParameter %5
%7 = OpLabel
%8 = OpVariable %5 Function
OpStore %8 %9
OpCopyMemory %8 %4
%10 = OpLoad %11 %8
OpStore %6 %10
OpReturn
OpFunctionEnd

View File

@ -0,0 +1,17 @@
// build-pass
// compile-flags: -C llvm-args=--disassemble-fn=ptr_read_method::copy_via_raw_ptr
use spirv_std as _;
fn copy_via_raw_ptr(src: &f32, dst: &mut f32) {
unsafe { *dst = (src as *const f32).read() }
}
#[spirv(fragment)]
pub fn main(i: f32, o: &mut f32) {
copy_via_raw_ptr(&i, o);
// FIXME(eddyb) above call results in inlining `copy_via_raw_ptr`,
// due to the to `Output` storage classe, so to get the disassembled
// function we also need `Function`-local pointers:
let (src, mut dst) = (0.0, 0.0);
copy_via_raw_ptr(&src, &mut dst);
}

View File

@ -0,0 +1,11 @@
%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %5
%6 = OpFunctionParameter %5
%7 = OpLabel
%8 = OpVariable %5 Function
OpStore %8 %9
OpCopyMemory %8 %4
%10 = OpLoad %11 %8
OpStore %6 %10
OpReturn
OpFunctionEnd

17
tests/ui/dis/ptr_write.rs Normal file
View File

@ -0,0 +1,17 @@
// build-pass
// compile-flags: -C llvm-args=--disassemble-fn=ptr_write::copy_via_raw_ptr
use spirv_std as _;
fn copy_via_raw_ptr(src: &f32, dst: &mut f32) {
unsafe { core::ptr::write(dst, *src) }
}
#[spirv(fragment)]
pub fn main(i: f32, o: &mut f32) {
copy_via_raw_ptr(&i, o);
// FIXME(eddyb) above call results in inlining `copy_via_raw_ptr`,
// due to the to `Output` storage classe, so to get the disassembled
// function we also need `Function`-local pointers:
let (src, mut dst) = (0.0, 0.0);
copy_via_raw_ptr(&src, &mut dst);
}

View File

@ -0,0 +1,10 @@
%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %5
%6 = OpFunctionParameter %5
%7 = OpLabel
%8 = OpVariable %5 Function
%9 = OpLoad %10 %4
OpStore %8 %9
OpCopyMemory %6 %8
OpReturn
OpFunctionEnd

View File

@ -0,0 +1,17 @@
// build-pass
// compile-flags: -C llvm-args=--disassemble-fn=ptr_write_method::copy_via_raw_ptr
use spirv_std as _;
fn copy_via_raw_ptr(src: &f32, dst: &mut f32) {
unsafe { (dst as *mut f32).write(*src) }
}
#[spirv(fragment)]
pub fn main(i: f32, o: &mut f32) {
copy_via_raw_ptr(&i, o);
// FIXME(eddyb) above call results in inlining `copy_via_raw_ptr`,
// due to the to `Output` storage classe, so to get the disassembled
// function we also need `Function`-local pointers:
let (src, mut dst) = (0.0, 0.0);
copy_via_raw_ptr(&src, &mut dst);
}

View File

@ -0,0 +1,10 @@
%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %5
%6 = OpFunctionParameter %5
%7 = OpLabel
%8 = OpVariable %5 Function
%9 = OpLoad %10 %4
OpStore %8 %9
OpCopyMemory %6 %8
OpReturn
OpFunctionEnd

View File

@ -0,0 +1,18 @@
// build-pass
// compile-flags: -C llvm-args=--disassemble-fn=unroll_loops::java_hash_ten_times
use spirv_std as _;
#[spirv(unroll_loops)]
fn java_hash_ten_times(mut x: u32, y: u32) -> u32 {
let mut i = 0;
while i < 10 {
x = 31 * x + y;
i += 1;
}
x
}
#[spirv(fragment)]
pub fn main() {
java_hash_ten_times(7, 42);
}

View File

@ -0,0 +1,32 @@
%1 = OpFunction %2 None %3
%4 = OpFunctionParameter %2
%5 = OpFunctionParameter %2
%6 = OpLabel
OpBranch %7
%7 = OpLabel
OpBranch %8
%8 = OpLabel
%9 = OpPhi %10 %11 %7 %12 %13
%14 = OpPhi %2 %4 %7 %15 %13
%16 = OpPhi %17 %18 %7 %19 %13
OpLoopMerge %20 %13 Unroll
OpBranchConditional %16 %21 %20
%21 = OpLabel
%22 = OpSLessThan %17 %9 %23
OpSelectionMerge %24 None
OpBranchConditional %22 %25 %26
%25 = OpLabel
%27 = OpIMul %2 %28 %14
%15 = OpIAdd %2 %27 %5
%12 = OpIAdd %10 %9 %29
OpBranch %24
%26 = OpLabel
OpReturnValue %14
%24 = OpLabel
%19 = OpPhi %17 %18 %25
OpBranch %13
%13 = OpLabel
OpBranch %8
%20 = OpLabel
OpUnreachable
OpFunctionEnd