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

Add basic support for struct DSTs #504

Merged
merged 10 commits into from
Mar 29, 2021

Conversation

Hentropy
Copy link
Contributor

Adds support for the following:

#[spirv(compute(threads(32)))]
pub fn shader(
    #[spirv(descriptor_set = 0, binding = 0)] slice: Uniform<Slice<f32>>,
)

pub struct Slice {
    rta: [T],
}
               OpEntryPoint GLCompute %1 "main_cs" %2
               OpExecutionMode %1 LocalSize 32 1 1
               OpMemberDecorate %_struct_3 0 Offset 0
               OpDecorate %2 DescriptorSet 0
               OpDecorate %2 Binding 0
       %void = OpTypeVoid
      %float = OpTypeFloat 32
%_runtimearr_float = OpTypeRuntimeArray %float
  %_struct_3 = OpTypeStruct %_runtimearr_float
%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
       %uint = OpTypeInt 32 0
          %9 = OpTypeFunction %void
          %2 = OpVariable %_ptr_Uniform__struct_3 Uniform
          %1 = OpFunction %void None %9
         %10 = OpLabel
         %11 = OpArrayLength %uint %2 0
               OpReturn
               OpFunctionEnd

However, asm doesn't seem to allow &Slice or &[T], and storage class inference has some issues when trying to use asm to implement indexing into the array:

impl<T: Copy> Slice<T> {
    unsafe fn index(&self, idx: usize) -> f32 {
        let mut res = 0.0;

        asm! {
            "%uint = OpTypeInt 32 0",
            "%uint_0 = OpConstant %uint 0",
            "%rta = OpLoad _ {0}",
            "%element = OpAccessChain typeof{2} %rta %uint_0 {1}",
            "OpStore {2} %element",
            in(reg) &self,
            in(reg) idx,
            in(reg) &mut res,
        };
        res
    }
}

produces:

inference conflict: Instance(Instance { generic_id: 4, generic_args: InferVar(7)..InferVar(8) }) vs Var(InferVar(8))
    in %64 = OpAccessChain 58 63 16 35

Trying to do &self.rta or using self with no reference produces:

error: cannot use value of type `*const Slice<T>` for inline assembly
// ...
note: only integers, floats, SIMD vectors, pointers and function pointers can be used as arguments for inline assembly

@khyperia
Copy link
Contributor

Wait, we shouldn't need to use asm! to support indexing, this compiles fine with this PR:

// Simple single entrypoint function test.
// build-pass

use spirv_std::storage_class::{Output, Uniform};

#[spirv(fragment)]
pub fn shader(
    #[spirv(descriptor_set = 0, binding = 0)] slice: Uniform<Slice<f32>>,
    mut out: Output<f32>,
) {
    let x = slice.data[5];
    *out = x;
}

pub struct Slice<T> {
    blah: f32,
    data: [T],
}

However, there's issues with the pointercast->GEP conversion code (recover_access_chain_from_offset) that need to be fixed, this surprisingly doesn't work:

// Simple single entrypoint function test.
// build-pass

use spirv_std::storage_class::{Output, Uniform};

#[spirv(fragment)]
pub fn shader(
    #[spirv(descriptor_set = 0, binding = 0)] slice: Uniform<Slice<f32>>,
    mut out: Output<f32>,
) {
    let x = slice.data[5];
    *out = x;
}

pub struct Slice<T> {
    data: [T],
}

which fails with

error: Cannot cast between pointer types
  --> $DIR/hello_world_2.rs:11:13
   |
11 |     let x = slice.data[5];
   |             ^^^^^^^^^^^^^
   |
   = note: from: *struct Slice<f32> { data: [f32] }
   = note: to: *[f32]

so that needs to be fixed

@khyperia
Copy link
Contributor

Fixed the issue in PR 510 ^

@Hentropy
Copy link
Contributor Author

Sweet! Thanks 😄 I'll add a couple tests to this.

@Hentropy Hentropy marked this pull request as ready for review March 19, 2021 21:34
@Hentropy Hentropy requested review from eddyb and khyperia as code owners March 19, 2021 21:34
@@ -36,8 +38,27 @@ impl<'tcx> CodegenCx<'tcx> {
};
let fn_hir_id = self.tcx.hir().local_def_id_to_hir_id(local_id);
let body = self.tcx.hir().body(self.tcx.hir().body_owned_by(fn_hir_id));
const EMPTY: ArgAttribute = ArgAttribute::empty();
for (abi, arg) in fn_abi.args.iter().zip(body.params) {
if let PassMode::Direct(_) = abi.mode {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this should probably be turned into a match, the reason it's an if let is because there's only one case

arguments.push(u32::MAX);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems pretty ad-hoc and confusing to follow (splitting it up into functions would probably help), and has some issues with it. For example, this compiletest passes, but it shouldn't (no way to get the length of the slice):

// build-pass

use spirv_std::storage_class::{Output, Uniform};

#[spirv(fragment)]
pub fn shader(
    #[spirv(descriptor_set = 0, binding = 0)] slice: Uniform<&[f32]>,
    mut out: Output<f32>,
) {
    let x = slice[5];
    *out = x;
}

@Hentropy
Copy link
Contributor Author

#443 will simplify this, so I'll wait on that.

@Hentropy
Copy link
Contributor Author

Hentropy commented Mar 23, 2021

Changed to a much cleaner implementation now that #443 has landed, which addresses both review points.

This only adds support for DSTs that are structs whose last field is a slice.

pub struct MySlice<T>([T]);
pub struct TrailingSlice<T> {
    fields: u32,
    last_field: [T],
}
pub struct NestedSlice<T>(MySlice<T>);

pub fn my_shader(
    // supported
    my_slice: MySlice<f32>,
    trailing_slice: TrailingSlice<f32>,

    // error!
    slice: &[f32],
    nested_slice: &NestedSlice<f32>,
) {/* .. */}

Adding support for nested slices is a lil tricky. OpArrayLength doesn't take a chain of indices so you need to dig into the Adt's fields until you find the one whose last field is TyKind::Slice, collecting the field indices as you go. Then, loop over the field indices excluding the final one and generate OpConstants, pass the constants into OpAccessChain, and pass the result from that into the OpArrayLength with the index of the last field. I can do that in a follow up, but I wanted to keep this basic.

match entry_fn_arg.layout.ty.kind() {
TyKind::Ref(..) => var,

TyKind::Ref(_, ty, _) if ty.is_sized(self.tcx.at(span), self.param_env()) => iter::once(var).chain(None),
Copy link
Contributor

@khyperia khyperia Mar 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you doing chain(None) here (and below)?

Also, could we extract this into a function? 10 levels of indentation is giving me a headache

hir_param.ty_span,
"Trait objects are not supported.",
),
_ => unreachable!(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is unreachable. For example, here's a compiletest that hits it:

// build-pass
#![feature(extern_types)]
use spirv_std as _;
extern "C" {
    pub type Blah;
}
#[spirv(fragment)]
pub fn main(#[spirv(uniform)] x: &mut Blah) {}

@Hentropy
Copy link
Contributor Author

@khyperia I addressed your review. Let me know if there's anything else 🙂

@Hentropy
Copy link
Contributor Author

Added ArrayStride decoration to OpTypeRuntimeArray. I am getting a spurious test fail on my machine from a double define of panic_impl and eh_personality due to glam, however that isn't related to any changes I have made.

@XAMPPRocky XAMPPRocky merged commit 05ce407 into EmbarkStudios:main Mar 29, 2021
@XAMPPRocky
Copy link
Member

Thank you for your PR!

@Hentropy Hentropy deleted the Add-support-for-struct-DSTs branch March 29, 2021 16:02
Copy link
Contributor

@eddyb eddyb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only have a few nits/simplifications, overall I don't mind this having landed.

let id = module
.entry_points
.iter()
.find(|inst| inst.operands.last().unwrap().unwrap_literal_string() == func)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it always operands[1] for the name, whereas further operands are interface variables? https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpEntryPoint

@@ -159,6 +159,33 @@ fn dis_fn(src: &str, func: &str, expect: &str) {
assert_str_eq(expect, &func.disassemble())
}

fn dis_entry_fn(src: &str, func: &str, expect: &str) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was confused by this so maybe dis_entry_stub might be easier to get? A comment would also help, but the name made me think the high-level Rust fn, not the generated stub.

Comment on lines +178 to +186
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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part could maybe be shared between the two functions (so only the picking the function ID would be different).

Comment on lines +56 to +62
// DST struct with fields before the DST member
| PassMode::Pair(
ArgAttributes { .. },
ArgAttributes {
pointee_size: Size::ZERO,
..
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second element in a pair for &SomeUnsizedType is always the "metadata" like usize for anything based on slices. I assume pointee_size is just zero for non-pointers (and I'm not sure checking pointee_size even makes sense?).

You could probably just check that the type of the PassMode::Pair argument is TyKind::Ref, that should limit it to &SomeUnsizedType.

Comment on lines -85 to +106
entry_fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
arg_abis: &[ArgAbi<'tcx, Ty<'tcx>>],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is a good idea, especially if we want to do something with the return type at some point.

XAMPPRocky pushed a commit that referenced this pull request May 3, 2021
* Add basic support for struct DSTs

* Add tests

* cleanup tests

* Update with entry changes, address review

* Address review

* Update allocate_const_scalar.stderr

* Add ArrayStride decoration to OpTypeRuntimeArray
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants