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

Gather all interface OpVariables into OpEntryPoints. #793

Merged
merged 3 commits into from
Nov 4, 2021
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: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:

- name: compiletest
if: ${{ matrix.target != 'aarch64-linux-android' }}
run: cargo run -p compiletests --release --no-default-features --features "use-installed-tools" -- --target-env vulkan1.1,spv1.3
run: cargo run -p compiletests --release --no-default-features --features "use-installed-tools" -- --target-env vulkan1.1,vulkan1.2,spv1.3

# Examples
- name: cargo check examples
Expand Down
104 changes: 104 additions & 0 deletions crates/rustc_codegen_spirv/src/linker/entry_interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//! Passes that pertain to `OpEntryPoint`'s "interface variables".

use crate::linker::ipo::CallGraph;
use indexmap::{IndexMap, IndexSet};
use rspirv::dr::{Module, Operand};
use rspirv::spirv::{Op, StorageClass, Word};
use std::mem;

type Id = Word;

/// Update `OpEntryPoint`s to contain all of the `OpVariable`s they reference,
/// whether directly or through some function in their call graph.
///
/// This is needed for (arguably-not-interface) `Private` in SPIR-V >= 1.4,
/// but also any interface variables declared "out of band" (e.g. via `asm!`).
pub fn gather_all_interface_vars_from_uses(module: &mut Module) {
// Start by mapping out which global (i.e. `OpVariable` or constants) IDs
// can be used to access any interface-relevant `OpVariable`s
// (where "interface-relevant" depends on the version, see comments below).
let mut used_vars_per_global_id: IndexMap<Id, IndexSet<Id>> = IndexMap::new();
let version = module.header.as_ref().unwrap().version();
for inst in &module.types_global_values {
let mut used_vars = IndexSet::new();

// Base case: the global itself is an interface-relevant `OpVariable`.
let interface_relevant_var = inst.class.opcode == Op::Variable && {
if version > (1, 3) {
// SPIR-V >= v1.4 includes all OpVariables in the interface.
true
} else {
let storage_class = inst.operands[0].unwrap_storage_class();
// SPIR-V <= v1.3 only includes Input and Output in the interface.
storage_class == StorageClass::Input || storage_class == StorageClass::Output
}
};
if interface_relevant_var {
used_vars.insert(inst.result_id.unwrap());
}

// Nested constant refs (e.g. `&&&0`) can create chains of `OpVariable`s
// where only the outer-most `OpVariable` may be accessed directly,
// but the interface variables need to include all the nesting levels.
used_vars.extend(
inst.operands
.iter()
.filter_map(|operand| operand.id_ref_any())
.filter_map(|id| used_vars_per_global_id.get(&id))
.flatten(),
);

if !used_vars.is_empty() {
used_vars_per_global_id.insert(inst.result_id.unwrap(), used_vars);
}
}

// Initial uses come from functions directly referencing global instructions.
let mut used_vars_per_fn_idx: Vec<IndexSet<Id>> = module
.functions
.iter()
.map(|func| {
func.all_inst_iter()
.flat_map(|inst| &inst.operands)
.filter_map(|operand| operand.id_ref_any())
.filter_map(|id| used_vars_per_global_id.get(&id))
.flatten()
.copied()
.collect()
})
.collect();

// Uses can then be propagated through the call graph, from callee to caller.
let call_graph = CallGraph::collect(module);
for caller_idx in call_graph.post_order() {
let mut used_vars = mem::take(&mut used_vars_per_fn_idx[caller_idx]);
for &callee_idx in &call_graph.callees[caller_idx] {
used_vars.extend(&used_vars_per_fn_idx[callee_idx]);
}
used_vars_per_fn_idx[caller_idx] = used_vars;
}

// All transitive uses are available, add them to `OpEntryPoint`s.
for (i, entry) in module.entry_points.iter_mut().enumerate() {
assert_eq!(entry.class.opcode, Op::EntryPoint);
let &entry_func_idx = call_graph.entry_points.get_index(i).unwrap();
assert_eq!(
module.functions[entry_func_idx].def_id().unwrap(),
entry.operands[1].unwrap_id_ref()
);

// NOTE(eddyb) it might be better to remove any unused vars, or warn
// the user about their presence, but for now this keeps them around.
let mut interface_vars: IndexSet<Id> = entry.operands[3..]
.iter()
.map(|operand| operand.unwrap_id_ref())
.collect();

interface_vars.extend(&used_vars_per_fn_idx[entry_func_idx]);

entry.operands.truncate(3);
entry
.operands
.extend(interface_vars.iter().map(|&id| Operand::IdRef(id)));
}
}
2 changes: 1 addition & 1 deletion crates/rustc_codegen_spirv/src/linker/ipo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub struct CallGraph {
pub entry_points: IndexSet<FuncIdx>,

/// `callees[i].contains(j)` implies `functions[i]` calls `functions[j]`.
callees: Vec<IndexSet<FuncIdx>>,
pub callees: Vec<IndexSet<FuncIdx>>,
}

impl CallGraph {
Expand Down
6 changes: 6 additions & 0 deletions crates/rustc_codegen_spirv/src/linker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod test;
mod dce;
mod destructure_composites;
mod duplicates;
mod entry_interface;
mod import_export_link;
mod inline;
mod ipo;
Expand Down Expand Up @@ -270,6 +271,11 @@ pub fn link(sess: &Session, mut inputs: Vec<Module>, opts: &Options) -> Result<L
}
}

{
let _timer = sess.timer("link_gather_all_interface_vars_from_uses");
entry_interface::gather_all_interface_vars_from_uses(&mut output);
}

if opts.spirv_metadata == SpirvMetadata::NameVariables {
let _timer = sess.timer("link_name_variables");
simple_passes::name_variables_pass(&mut output);
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/arch/ray_query_initialize_khr.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// build-pass
// compile-flags: -Ctarget-feature=+RayQueryKHR,+ext:SPV_KHR_ray_query
// compile-flags: -Ctarget-feature=+RayTracingKHR,+RayQueryKHR,+ext:SPV_KHR_ray_tracing,+ext:SPV_KHR_ray_query

use glam::Vec3;
use spirv_std::ray_tracing::{AccelerationStructure, RayFlags, RayQuery};
Expand Down
5 changes: 5 additions & 0 deletions tests/ui/dis/asm_op_decorate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
// normalize-stderr-test "OpExtension .SPV_KHR_vulkan_memory_model.\n" -> ""
// normalize-stderr-test "OpMemoryModel Logical Vulkan" -> "OpMemoryModel Logical Simple"

// FIXME(eddyb) this should use revisions to track both the `vulkan1.2` output
// and the pre-`vulkan1.2` output, but per-revisions `{only,ignore}-*` directives
// are not supported in `compiletest-rs`.
// ignore-vulkan1.2

use spirv_std as _;

fn add_decorate() {
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/image/sample_depth_reference/sample_lod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ pub fn main(
#[spirv(descriptor_set = 0, binding = 0)] image: &Image!(2D, type=f32, sampled),
#[spirv(descriptor_set = 1, binding = 1)] image_array: &Image!(2D, type=f32, arrayed, sampled),
#[spirv(descriptor_set = 2, binding = 2)] sampler: &Sampler,
#[spirv(descriptor_set = 3, binding = 3)] cubemap: &Image!(3D, type=f32),
#[spirv(descriptor_set = 3, binding = 3)] cubemap: &Image!(3D, type=f32, sampled),
output: &mut f32,
) {
let v2 = glam::Vec2::new(0.0, 1.0);
let v3 = glam::Vec3A::new(0.0, 0.0, 1.0);
*output = image.sample_depth_reference_by_lod(*sampler, v2, 1.0, 0.0);
*output += image_array.sample_depth_reference_by_lod(*sampler, v3, 1.0, 0.0);
*output += image_array.sample_depth_reference_by_lod(*sampler, v3, 1.0, 0.0);
*output += cubemap.sample_depth_reference_by_lod(*sampler, v3, 1.0, 0.0);
}