mirror of
https://github.com/EmbarkStudios/rust-gpu.git
synced 2024-11-22 06:45:13 +00:00
Move disassemble tests to compiletest (#609)
* Move disassemble tests to compiletest * Fix problematic tests * Add newlines
This commit is contained in:
parent
7a6806c17b
commit
1431c18b9d
1
.github/workflows/test.sh
vendored
1
.github/workflows/test.sh
vendored
@ -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
2
Cargo.lock
generated
@ -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",
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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"
|
||||
|
@ -53,9 +53,6 @@
|
||||
// crate-specific exceptions:
|
||||
#![allow()]
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
mod depfile;
|
||||
|
||||
use raw_string::{RawStr, RawString};
|
||||
|
@ -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"#,
|
||||
)
|
||||
}
|
@ -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
|
||||
}
|
12
tests/ui/dis/add_two_ints.rs
Normal file
12
tests/ui/dis/add_two_ints.rs
Normal 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);
|
||||
}
|
7
tests/ui/dis/add_two_ints.stderr
Normal file
7
tests/ui/dis/add_two_ints.stderr
Normal 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
19
tests/ui/dis/asm.rs
Normal 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
5
tests/ui/dis/asm.stderr
Normal file
@ -0,0 +1,5 @@
|
||||
%1 = OpFunction %2 None %3
|
||||
%4 = OpLabel
|
||||
OpMemoryBarrier %5 %6
|
||||
OpReturn
|
||||
OpFunctionEnd
|
21
tests/ui/dis/asm_add_two_ints.rs
Normal file
21
tests/ui/dis/asm_add_two_ints.rs
Normal 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);
|
||||
}
|
7
tests/ui/dis/asm_add_two_ints.stderr
Normal file
7
tests/ui/dis/asm_add_two_ints.stderr
Normal 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
|
40
tests/ui/dis/asm_op_decorate.rs
Normal file
40
tests/ui/dis/asm_op_decorate.rs
Normal 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();
|
||||
}
|
22
tests/ui/dis/asm_op_decorate.stderr
Normal file
22
tests/ui/dis/asm_op_decorate.stderr
Normal 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
|
62
tests/ui/dis/complex_image_sample_inst.rs
Normal file
62
tests/ui/dis/complex_image_sample_inst.rs
Normal 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);
|
||||
}
|
12
tests/ui/dis/complex_image_sample_inst.stderr
Normal file
12
tests/ui/dis/complex_image_sample_inst.stderr
Normal 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
|
10
tests/ui/dis/custom_entry_point.rs
Normal file
10
tests/ui/dis/custom_entry_point.rs
Normal 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() {}
|
7
tests/ui/dis/custom_entry_point.stderr
Normal file
7
tests/ui/dis/custom_entry_point.stderr
Normal 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
|
10
tests/ui/dis/index_user_dst.rs
Normal file
10
tests/ui/dis/index_user_dst.rs
Normal 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;
|
||||
}
|
30
tests/ui/dis/index_user_dst.stderr
Normal file
30
tests/ui/dis/index_user_dst.stderr
Normal 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
17
tests/ui/dis/ptr_copy.rs
Normal 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);
|
||||
}
|
7
tests/ui/dis/ptr_copy.stderr
Normal file
7
tests/ui/dis/ptr_copy.stderr
Normal 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
17
tests/ui/dis/ptr_read.rs
Normal 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);
|
||||
}
|
11
tests/ui/dis/ptr_read.stderr
Normal file
11
tests/ui/dis/ptr_read.stderr
Normal 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_read_method.rs
Normal file
17
tests/ui/dis/ptr_read_method.rs
Normal 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);
|
||||
}
|
11
tests/ui/dis/ptr_read_method.stderr
Normal file
11
tests/ui/dis/ptr_read_method.stderr
Normal 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
17
tests/ui/dis/ptr_write.rs
Normal 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);
|
||||
}
|
10
tests/ui/dis/ptr_write.stderr
Normal file
10
tests/ui/dis/ptr_write.stderr
Normal 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
|
17
tests/ui/dis/ptr_write_method.rs
Normal file
17
tests/ui/dis/ptr_write_method.rs
Normal 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);
|
||||
}
|
10
tests/ui/dis/ptr_write_method.stderr
Normal file
10
tests/ui/dis/ptr_write_method.stderr
Normal 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
|
18
tests/ui/dis/unroll_loops.rs
Normal file
18
tests/ui/dis/unroll_loops.rs
Normal 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);
|
||||
}
|
32
tests/ui/dis/unroll_loops.stderr
Normal file
32
tests/ui/dis/unroll_loops.stderr
Normal 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
|
Loading…
Reference in New Issue
Block a user