/// Information about SPIR-V result ids, stored in `Frontend::lookup_expression`. #[derive(Clone, Debug)] struct LookupExpression { /// The `Expression` constructed for this result. /// /// Note that, while a SPIR-V result id can be used in any block dominated /// by its definition, a Naga `Expression` is only in scope for the rest of /// its subtree. `Frontend::get_expr_handle` takes care of spilling the result /// to a `LocalVariable` which can then be used anywhere.
handle: Handle<crate::Expression>,
/// The SPIR-V type of this result.
type_id: spirv::Word,
/// The label id of the block that defines this expression. /// /// This is zero for globals, constants, and function parameters, since they /// originate outside any function's block.
block_id: spirv::Word,
}
#[derive(Debug)] struct LookupMember {
type_id: spirv::Word, // This is true for either matrices, or arrays of matrices (yikes).
row_major: bool,
}
#[derive(Clone, Debug)] enum LookupLoadOverride { /// For arrays of matrices, we track them but not loading yet.
Pending, /// For matrices, vectors, and scalars, we pre-load the data.
Loaded(Handle<crate::Expression>),
}
#[derive(Clone, Debug)] pubstruct Options { /// The IR coordinate space matches all the APIs except SPIR-V, /// so by default we flip the Y coordinate of the `BuiltIn::Position`. /// This flag can be used to avoid this. pub adjust_coordinate_space: bool, /// Only allow shaders with the known set of capabilities. pub strict_capabilities: bool, pub block_ctx_dump_prefix: Option<PathBuf>,
}
/// An index into the `BlockContext::bodies` table. type BodyIndex = usize;
/// An intermediate representation of a Naga [`Statement`]. /// /// `Body` and `BodyFragment` values form a tree: the `BodyIndex` fields of the /// variants are indices of the child `Body` values in [`BlockContext::bodies`]. /// The `lower` function assembles the final `Statement` tree from this `Body` /// tree. See [`BlockContext`] for details. /// /// [`Statement`]: crate::Statement #[derive(Debug)] enum BodyFragment {
BlockId(spirv::Word), If {
condition: Handle<crate::Expression>,
accept: BodyIndex,
reject: BodyIndex,
}, Loop { /// The body of the loop. Its [`Body::parent`] is the block containing /// this `Loop` fragment.
body: BodyIndex,
/// The loop's continuing block. This is a grandchild: its /// [`Body::parent`] is the loop body block, whose index is above.
continuing: BodyIndex,
/// If the SPIR-V loop's back-edge branch is conditional, this is the /// expression that must be `false` for the back-edge to be taken, with /// `true` being for the "loop merge" (which breaks out of the loop).
break_if: Option<Handle<crate::Expression>>,
},
Switch {
selector: Handle<crate::Expression>,
cases: Vec<(i32, BodyIndex)>,
default: BodyIndex,
}, Break, Continue,
}
/// An intermediate representation of a Naga [`Block`]. /// /// This will be assembled into a `Block` once we've added spills for phi nodes /// and out-of-scope expressions. See [`BlockContext`] for details. /// /// [`Block`]: crate::Block #[derive(Debug)] struct Body { /// The index of the direct parent of this body
parent: usize,
data: Vec<BodyFragment>,
}
impl Body { /// Creates a new empty `Body` with the specified `parent` pubconstfn with_parent(parent: usize) -> Self {
Body {
parent,
data: Vec::new(),
}
}
}
#[derive(Debug)] struct PhiExpression { /// The local variable used for the phi node
local: Handle<crate::LocalVariable>, /// List of (expression, block)
expressions: Vec<(spirv::Word, spirv::Word)>,
}
/// Fragments of Naga IR, to be assembled into `Statements` once data flow is /// resolved. /// /// We can't build a Naga `Statement` tree directly from SPIR-V blocks for three /// main reasons: /// /// - We parse a function's SPIR-V blocks in the order they appear in the file. /// Within a function, SPIR-V requires that a block must precede any blocks it /// structurally dominates, but doesn't say much else about the order in which /// they must appear. So while we know we'll see control flow header blocks /// before their child constructs and merge blocks, those children and the /// merge blocks may appear in any order - perhaps even intermingled with /// children of other constructs. /// /// - A SPIR-V expression can be used in any SPIR-V block dominated by its /// definition, whereas Naga expressions are scoped to the rest of their /// subtree. This means that discovering an expression use later in the /// function retroactively requires us to have spilled that expression into a /// local variable back before we left its scope. (The docs for /// [`Frontend::get_expr_handle`] explain this in more detail.) /// /// - We translate SPIR-V OpPhi expressions as Naga local variables in which we /// store the appropriate value before jumping to the OpPhi's block. /// /// All these cases require us to go back and amend previously generated Naga IR /// based on things we discover later. But modifying old blocks in arbitrary /// spots in a `Statement` tree is awkward. /// /// Instead, as we iterate through the function's body, we accumulate /// control-flow-free fragments of Naga IR in the [`blocks`] table, while /// building a skeleton of the Naga `Statement` tree in [`bodies`]. We note any /// spills and temporaries we must introduce in [`phis`]. /// /// Finally, once we've processed the entire function, we add temporaries and /// spills to the fragmentary `Blocks` as directed by `phis`, and assemble them /// into the final Naga `Statement` tree as directed by `bodies`. /// /// [`blocks`]: BlockContext::blocks /// [`bodies`]: BlockContext::bodies /// [`phis`]: BlockContext::phis /// [`lower`]: function::lower #[derive(Debug)] struct BlockContext<'function> { /// Phi nodes encountered when parsing the function, used to generate spills /// to local variables.
phis: Vec<PhiExpression>,
/// Fragments of control-flow-free Naga IR. /// /// These will be stitched together into a proper [`Statement`] tree according /// to `bodies`, once parsing is complete. /// /// [`Statement`]: crate::Statement
blocks: FastHashMap<spirv::Word, crate::Block>,
/// Map from each SPIR-V block's label id to the index of the [`Body`] in /// [`bodies`] the block should append its contents to. /// /// Since each statement in a Naga [`Block`] dominates the next, we are sure /// to encounter their SPIR-V blocks in order. Thus, by having this table /// map a SPIR-V structured control flow construct's merge block to the same /// body index as its header block, when we encounter the merge block, we /// will simply pick up building the [`Body`] where the header left off. /// /// A function's first block is special: it is the only block we encounter /// without having seen its label mentioned in advance. (It's simply the /// first `OpLabel` after the `OpFunction`.) We thus assume that any block /// missing an entry here must be the first block, which always has body /// index zero. /// /// [`bodies`]: BlockContext::bodies /// [`Block`]: crate::Block
body_for_label: FastHashMap<spirv::Word, BodyIndex>,
/// SPIR-V metadata about merge/continue blocks.
mergers: FastHashMap<spirv::Word, MergeBlockInformation>,
/// A table of `Body` values, each representing a block in the final IR. /// /// The first element is always the function's top-level block.
bodies: Vec<Body>,
/// The module we're building.
module: &'function mut crate::Module,
/// Id of the function currently being processed
function_id: spirv::Word, /// Expression arena of the function currently being processed
expressions: &'function mut Arena<crate::Expression>, /// Local variables arena of the function currently being processed
local_arena: &'function mut Arena<crate::LocalVariable>, /// Arguments of the function currently being processed
arguments: &'function [crate::FunctionArgument], /// Metadata about the usage of function parameters as sampling objects
parameter_sampling: &'function mut [image::SamplingFlags],
}
/// A record of what is accessed by [`Atomic`] statements we've /// generated, so we can upgrade the types of their operands. /// /// [`Atomic`]: crate::Statement::Atomic
upgrade_atomics: Upgrades,
lookup_type: FastHashMap<spirv::Word, LookupType>,
lookup_void_type: Option<spirv::Word>,
lookup_storage_buffer_types: FastHashMap<Handle<crate::Type>, crate::StorageAccess>,
lookup_constant: FastHashMap<spirv::Word, LookupConstant>,
lookup_variable: FastHashMap<spirv::Word, LookupVariable>,
lookup_expression: FastHashMap<spirv::Word, LookupExpression>, // Load overrides are used to work around row-major matrices
lookup_load_override: FastHashMap<spirv::Word, LookupLoadOverride>,
lookup_sampled_image: FastHashMap<spirv::Word, image::LookupSampledImage>,
lookup_function_type: FastHashMap<spirv::Word, LookupFunctionType>,
lookup_function: FastHashMap<spirv::Word, LookupFunction>,
lookup_entry_point: FastHashMap<spirv::Word, EntryPoint>, // When parsing functions, each entry point function gets an entry here so that additional // processing for them can be performed after all function parsing.
deferred_entry_points: Vec<(EntryPoint, spirv::Word)>, //Note: each `OpFunctionCall` gets a single entry here, indexed by the // dummy `Handle<crate::Function>` of the call site.
deferred_function_calls: Vec<spirv::Word>,
dummy_functions: Arena<crate::Function>, // Graph of all function calls through the module. // It's used to sort the functions (as nodes) topologically, // so that in the IR any called function is already known.
function_call_graph: GraphMap<spirv::Word, (), petgraph::Directed>,
options: Options,
/// Maps for a switch from a case target to the respective body and associated literals that /// use that target block id. /// /// Used to preserve allocations between instruction parsing.
switch_cases: FastIndexMap<spirv::Word, (BodyIndex, Vec<i32>)>,
/// Tracks access to gl_PerVertex's builtins, it is used to cull unused builtins since initializing those can /// affect performance and the mere presence of some of these builtins might cause backends to error since they /// might be unsupported. /// /// The problematic builtins are: PointSize, ClipDistance and CullDistance. /// /// glslang declares those by default even though they are never written to /// (see <https://github.com/KhronosGroup/glslang/issues/1868>)
gl_per_vertex_builtin_access: FastHashSet<crate::BuiltIn>,
}
fn next_inst(&mutself) -> Result<Instruction, Error> { let word = self.next()?; let (wc, opcode) = ((word >> 16) as u16, (word & 0xffff) as u16); if wc == 0 { return Err(Error::InvalidWordCount);
} let op = spirv::Op::from_u32(opcode as u32).ok_or(Error::UnknownInstruction(opcode))?;
/// Return the Naga [`Expression`] to use in `body_idx` to refer to the SPIR-V result `id`. /// /// Ideally, we would just have a map from each SPIR-V instruction id to the /// [`Handle`] for the Naga [`Expression`] we generated for it. /// Unfortunately, SPIR-V and Naga IR are different enough that such a /// straightforward relationship isn't possible. /// /// In SPIR-V, an instruction's result id can be used by any instruction /// dominated by that instruction. In Naga, an [`Expression`] is only in /// scope for the remainder of its [`Block`]. In pseudocode: /// /// ```ignore /// loop { /// a = f(); /// g(a); /// break; /// } /// h(a); /// ``` /// /// Suppose the calls to `f`, `g`, and `h` are SPIR-V instructions. In /// SPIR-V, both the `g` and `h` instructions are allowed to refer to `a`, /// because the loop body, including `f`, dominates both of them. /// /// But if `a` is a Naga [`Expression`], its scope ends at the end of the /// block it's evaluated in: the loop body. Thus, while the [`Expression`] /// we generate for `g` can refer to `a`, the one we generate for `h` /// cannot. /// /// Instead, the SPIR-V front end must generate Naga IR like this: /// /// ```ignore /// var temp; // INTRODUCED /// loop { /// a = f(); /// g(a); /// temp = a; // INTRODUCED /// } /// h(temp); // ADJUSTED /// ``` /// /// In other words, where `a` is in scope, [`Expression`]s can refer to it /// directly; but once it is out of scope, we need to spill it to a /// temporary and refer to that instead. /// /// Given a SPIR-V expression `id` and the index `body_idx` of the [body] /// that wants to refer to it: /// /// - If the Naga [`Expression`] we generated for `id` is in scope in /// `body_idx`, then we simply return its `Handle<Expression>`. /// /// - Otherwise, introduce a new [`LocalVariable`], and add an entry to /// [`BlockContext::phis`] to arrange for `id`'s value to be spilled to /// it. Then emit a fresh [`Load`] of that temporary variable for use in /// `body_idx`'s block, and return its `Handle`. /// /// The SPIR-V domination rule ensures that the introduced [`LocalVariable`] /// will always have been initialized before it is used. /// /// `lookup` must be the [`LookupExpression`] for `id`. /// /// `body_idx` argument must be the index of the [`Body`] that hopes to use /// `id`'s [`Expression`]. /// /// [`Expression`]: crate::Expression /// [`Handle`]: crate::Handle /// [`Block`]: crate::Block /// [body]: BlockContext::bodies /// [`LocalVariable`]: crate::LocalVariable /// [`Load`]: crate::Expression::Load fn get_expr_handle(
&self,
id: spirv::Word,
lookup: &LookupExpression,
ctx: &mut BlockContext,
emitter: &mutcrate::proc::Emitter,
block: &mutcrate::Block,
body_idx: BodyIndex,
) -> Handle<crate::Expression> { // What `Body` was `id` defined in? let expr_body_idx = ctx
.body_for_label
.get(&lookup.block_id)
.copied()
.unwrap_or(0);
// Don't need to do a load/store if the expression is in the main body // or if the expression is in the same body as where the query was // requested. The body_idx might actually not be the final one if a loop // or conditional occurs but in those cases we know that the new body // will be a subscope of the body that was passed so we can still reuse // the handle and not issue a load/store. if is_parent(body_idx, expr_body_idx, ctx) {
lookup.handle
} else { // Add a temporary variable of the same type which will be used to // store the original expression and used in the current block let ty = self.lookup_type[&lookup.type_id].handle; let local = ctx.local_arena.append( crate::LocalVariable {
name: None,
ty,
init: None,
}, crate::Span::default(),
);
block.extend(emitter.finish(ctx.expressions)); let pointer = ctx.expressions.append( crate::Expression::LocalVariable(local), crate::Span::default(),
);
emitter.start(ctx.expressions); let expr = ctx
.expressions
.append(crate::Expression::Load { pointer }, crate::Span::default());
// Add a slightly odd entry to the phi table, so that while `id`'s // `Expression` is still in scope, the usual phi processing will // spill its value to `local`, where we can find it later. // // This pretends that the block in which `id` is defined is the // predecessor of some other block with a phi in it that cites id as // one of its sources, and uses `local` as its variable. There is no // such phi, but nobody needs to know that.
ctx.phis.push(PhiExpression {
local,
expressions: vec![(id, lookup.block_id)],
});
expr
}
}
fn parse_expr_unary_op(
&mutself,
ctx: &mut BlockContext,
emitter: &mutcrate::proc::Emitter,
block: &mutcrate::Block,
block_id: spirv::Word,
body_idx: usize,
op: crate::UnaryOperator,
) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let p_id = self.next()?;
let p_lexp = self.lookup_expression.lookup(p_id)?; let handle = self.get_expr_handle(p_id, p_lexp, ctx, emitter, block, body_idx);
fn parse_expr_binary_op(
&mutself,
ctx: &mut BlockContext,
emitter: &mutcrate::proc::Emitter,
block: &mutcrate::Block,
block_id: spirv::Word,
body_idx: usize,
op: crate::BinaryOperator,
) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let p1_id = self.next()?; let p2_id = self.next()?;
let p1_lexp = self.lookup_expression.lookup(p1_id)?; let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx); let p2_lexp = self.lookup_expression.lookup(p2_id)?; let right = self.get_expr_handle(p2_id, p2_lexp, ctx, emitter, block, body_idx);
/// A more complicated version of the unary op, /// where we force the operand to have the same type as the result. fn parse_expr_unary_op_sign_adjusted(
&mutself,
ctx: &mut BlockContext,
emitter: &mutcrate::proc::Emitter,
block: &mutcrate::Block,
block_id: spirv::Word,
body_idx: usize,
op: crate::UnaryOperator,
) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let p1_id = self.next()?; let span = self.span_from_with_op(start);
let p1_lexp = self.lookup_expression.lookup(p1_id)?; let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx);
let result_lookup_ty = self.lookup_type.lookup(result_type_id)?; let kind = ctx.module.types[result_lookup_ty.handle]
.inner
.scalar_kind()
.unwrap();
let expr = crate::Expression::Unary {
op,
expr: if p1_lexp.type_id == result_type_id {
left
} else {
ctx.expressions.append( crate::Expression::As {
expr: left,
kind,
convert: None,
},
span,
)
},
};
/// A more complicated version of the binary op, /// where we force the operand to have the same type as the result. /// This is mostly needed for "i++" and "i--" coming from GLSL. #[allow(clippy::too_many_arguments)] fn parse_expr_binary_op_sign_adjusted(
&mutself,
ctx: &mut BlockContext,
emitter: &mutcrate::proc::Emitter,
block: &mutcrate::Block,
block_id: spirv::Word,
body_idx: usize,
op: crate::BinaryOperator, // For arithmetic operations, we need the sign of operands to match the result. // For boolean operations, however, the operands need to match the signs, but // result is always different - a boolean.
anchor: SignAnchor,
) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let p1_id = self.next()?; let p2_id = self.next()?; let span = self.span_from_with_op(start);
let p1_lexp = self.lookup_expression.lookup(p1_id)?; let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx); let p2_lexp = self.lookup_expression.lookup(p2_id)?; let right = self.get_expr_handle(p2_id, p2_lexp, ctx, emitter, block, body_idx);
let expected_type_id = match anchor {
SignAnchor::Result => result_type_id,
SignAnchor::Operand => p1_lexp.type_id,
}; let expected_lookup_ty = self.lookup_type.lookup(expected_type_id)?; let kind = ctx.module.types[expected_lookup_ty.handle]
.inner
.scalar_kind()
.unwrap();
/// A version of the binary op where one or both of the arguments might need to be casted to a /// specific integer kind (unsigned or signed), used for operations like OpINotEqual or /// OpUGreaterThan. #[allow(clippy::too_many_arguments)] fn parse_expr_int_comparison(
&mutself,
ctx: &mut BlockContext,
emitter: &mutcrate::proc::Emitter,
block: &mutcrate::Block,
block_id: spirv::Word,
body_idx: usize,
op: crate::BinaryOperator,
kind: crate::ScalarKind,
) -> Result<(), Error> { let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let p1_id = self.next()?; let p2_id = self.next()?; let span = self.span_from_with_op(start);
let p1_lexp = self.lookup_expression.lookup(p1_id)?; let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx); let p1_lookup_ty = self.lookup_type.lookup(p1_lexp.type_id)?; let p1_kind = ctx.module.types[p1_lookup_ty.handle]
.inner
.scalar_kind()
.unwrap(); let p2_lexp = self.lookup_expression.lookup(p2_id)?; let right = self.get_expr_handle(p2_id, p2_lexp, ctx, emitter, block, body_idx); let p2_lookup_ty = self.lookup_type.lookup(p2_lexp.type_id)?; let p2_kind = ctx.module.types[p2_lookup_ty.handle]
.inner
.scalar_kind()
.unwrap();
/// Return the Naga [`Expression`] for `pointer_id`, and its referent [`Type`]. /// /// Return a [`Handle`] for a Naga [`Expression`] that holds the value of /// the SPIR-V instruction `pointer_id`, along with the [`Type`] to which it /// is a pointer. /// /// This may entail spilling `pointer_id`'s value to a temporary: /// see [`get_expr_handle`]'s documentation. /// /// [`Expression`]: crate::Expression /// [`Type`]: crate::Type /// [`Handle`]: crate::Handle /// [`get_expr_handle`]: Frontend::get_expr_handle fn get_exp_and_base_ty_handles(
&self,
pointer_id: spirv::Word,
ctx: &mut BlockContext,
emitter: &mutcrate::proc::Emitter,
block: &mutcrate::Block,
body_idx: usize,
) -> Result<(Handle<crate::Expression>, Handle<crate::Type>), Error> {
log::trace!("\t\t\tlooking up pointer expr {:?}", pointer_id); let p_lexp_handle; let p_lexp_ty_id;
{ let lexp = self.lookup_expression.lookup(pointer_id)?;
p_lexp_handle = self.get_expr_handle(pointer_id, lexp, ctx, emitter, block, body_idx);
p_lexp_ty_id = lexp.type_id;
};
log::trace!("\t\t\tlooking up pointer type {pointer_id:?}"); let p_ty = self.lookup_type.lookup(p_lexp_ty_id)?; let p_ty_base_id = p_ty.base_id.ok_or(Error::InvalidAccessType(p_lexp_ty_id))?;
log::trace!("\t\t\tlooking up pointer base type {p_ty_base_id:?} of {p_ty:?}"); let p_base_ty = self.lookup_type.lookup(p_ty_base_id)?;
Ok((p_lexp_handle, p_base_ty.handle))
}
#[allow(clippy::too_many_arguments)] fn parse_atomic_expr_with_value(
&mutself,
inst: Instruction,
emitter: &mutcrate::proc::Emitter,
ctx: &mut BlockContext,
block: &mutcrate::Block,
block_id: spirv::Word,
body_idx: usize,
atomic_function: crate::AtomicFunction,
) -> Result<(), Error> {
inst.expect(7)?; let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let pointer_id = self.next()?; let _scope_id = self.next()?; let _memory_semantics_id = self.next()?; let value_id = self.next()?; let span = self.span_from_with_op(start);
let (p_lexp_handle, p_base_ty_handle) = self.get_exp_and_base_ty_handles(pointer_id, ctx, emitter, block, body_idx)?;
log::trace!("\t\t\tlooking up value expr {value_id:?}"); let v_lexp_handle = self.lookup_expression.lookup(value_id)?.handle;
block.extend(emitter.finish(ctx.expressions)); // Create an expression for our result let r_lexp_handle = { let expr = crate::Expression::AtomicResult {
ty: p_base_ty_handle,
comparison: false,
}; let handle = ctx.expressions.append(expr, span); self.lookup_expression.insert(
result_id,
LookupExpression {
handle,
type_id: result_type_id,
block_id,
},
);
handle
};
emitter.start(ctx.expressions);
// Create a statement for the op itself let stmt = crate::Statement::Atomic {
pointer: p_lexp_handle,
fun: atomic_function,
value: v_lexp_handle,
result: Some(r_lexp_handle),
};
block.push(stmt, span);
// Store any associated global variables so we can upgrade their types later self.record_atomic_access(ctx, p_lexp_handle)?;
Ok(())
}
/// Add the next SPIR-V block's contents to `block_ctx`. /// /// Except for the function's entry block, `block_id` should be the label of /// a block we've seen mentioned before, with an entry in /// `block_ctx.body_for_label` to tell us which `Body` it contributes to. fn next_block(&mutself, block_id: spirv::Word, ctx: &'color:red'>mut BlockContext) -> Result<(), Error> { // Extend `body` with the correct form for a branch to `target`. fn merger(body: &mut Body, target: &MergeBlockInformation) {
body.data.push(match *target {
MergeBlockInformation::LoopContinue => BodyFragment::Continue,
MergeBlockInformation::LoopMerge | MergeBlockInformation::SwitchMerge => {
BodyFragment::Break
}
// Finishing a selection merge means just falling off the end of // the `accept` or `reject` block of the `If` statement.
MergeBlockInformation::SelectionMerge => return,
})
}
// Find the `Body` to which this block contributes. // // If this is some SPIR-V structured control flow construct's merge // block, then `body_idx` will refer to the same `Body` as the header, // so that we simply pick up accumulating the `Body` where the header // left off. Each of the statements in a block dominates the next, so // we're sure to encounter their SPIR-V blocks in order, ensuring that // the `Body` will be assembled in the proper order. // // Note that, unlike every other kind of SPIR-V block, we don't know the // function's first block's label in advance. Thus, we assume that if // this block has no entry in `ctx.body_for_label`, it must be the // function's first block. This always has body index zero. letmut body_idx = *ctx.body_for_label.entry(block_id).or_default();
// The Naga IR block this call builds. This will end up as // `ctx.blocks[&block_id]`, and `ctx.bodies[body_idx]` will refer to it // via a `BodyFragment::BlockId`. letmut block = crate::Block::new();
// Stores the merge block as defined by a `OpSelectionMerge` otherwise is `None` // // This is used in `OpSwitch` to promote the `MergeBlockInformation` from // `SelectionMerge` to `SwitchMerge` to allow `Break`s this isn't desirable for // `LoopMerge`s because otherwise `Continue`s wouldn't be allowed letmut selection_merge_block = None;
let terminator = loop { use spirv::Op; let start = self.data_offset; let inst = self.next_inst()?; let span = crate::Span::from(start..(start + 4 * (inst.wc as usize)));
log::debug!("\t\t{:?} [{}]", inst.op, inst.wc);
match inst.op {
Op::Line => {
inst.expect(4)?; let _file_id = self.next()?; let _row_id = self.next()?; let _col_id = self.next()?;
}
Op::NoLine => inst.expect(1)?,
Op::Undef => {
inst.expect(3)?; let type_id = self.next()?; let id = self.next()?; let type_lookup = self.lookup_type.lookup(type_id)?; let ty = type_lookup.handle;
// Associate the lookup with an actual value, which is emitted // into the current block. self.lookup_expression.insert(
result_id,
LookupExpression {
handle: ctx
.expressions
.append(crate::Expression::Load { pointer }, span),
type_id: result_type_id,
block_id,
},
);
}
Op::AccessChain | Op::InBoundsAccessChain => { struct AccessExpression {
base_handle: Handle<crate::Expression>,
type_id: spirv::Word,
load_override: Option<LookupLoadOverride>,
}
inst.expect_at_least(4)?;
let result_type_id = self.next()?; let result_id = self.next()?; let base_id = self.next()?;
log::trace!("\t\t\tlooking up expr {:?}", base_id);
letmut acex = { let lexp = self.lookup_expression.lookup(base_id)?; let lty = self.lookup_type.lookup(lexp.type_id)?;
// HACK `OpAccessChain` and `OpInBoundsAccessChain` // require for the result type to be a pointer, but if // we're given a pointer to an image / sampler, it will // be *already* dereferenced, since we do that early // during `parse_type_pointer()`. // // This can happen only through `BindingArray`, since // that's the only case where one can obtain a pointer // to an image / sampler, and so let's match on that: let dereference = match ctx.module.types[lty.handle].inner { crate::TypeInner::BindingArray { .. } => false,
_ => true,
};
let type_id = if dereference {
lty.base_id.ok_or(Error::InvalidAccessType(lexp.type_id))?
} else {
lexp.type_id
};
for _ in4..inst.wc { let access_id = self.next()?;
log::trace!("\t\t\tlooking up index expr {:?}", access_id); let index_expr = self.lookup_expression.lookup(access_id)?.clone(); let index_expr_handle = get_expr_handle!(access_id, &index_expr); let index_expr_data = &ctx.expressions[index_expr.handle]; let index_maybe = match *index_expr_data { crate::Expression::Constant(const_handle) => Some(
ctx.gctx()
.eval_expr_to_u32(ctx.module.constants[const_handle].init)
.map_err(|_| {
Error::InvalidAccess(crate::Expression::Constant(
const_handle,
))
})?,
),
_ => None,
};
log::trace!("\t\t\tlooking up type {:?}", acex.type_id); let type_lookup = self.lookup_type.lookup(acex.type_id)?; let ty = &ctx.module.types[type_lookup.handle];
acex = match ty.inner { // can only index a struct with a constant crate::TypeInner::Struct { ref members, .. } => { let index = index_maybe
.ok_or_else(|| Error::InvalidAccess(index_expr_data.clone()))?;
let lookup_member = self
.lookup_member
.get(&(type_lookup.handle, index))
.ok_or(Error::InvalidAccessType(acex.type_id))?; let base_handle = ctx.expressions.append( crate::Expression::AccessIndex {
base: acex.base_handle,
index,
},
span,
);
iflet Some(crate::Binding::BuiltIn(built_in)) =
members[index as usize].binding
{ self.gl_per_vertex_builtin_access.insert(built_in);
}
AccessExpression {
base_handle,
type_id: lookup_member.type_id,
load_override: if lookup_member.row_major {
debug_assert!(acex.load_override.is_none()); let sub_type_lookup = self.lookup_type.lookup(lookup_member.type_id)?;
Some(match ctx.module.types[sub_type_lookup.handle].inner { // load it transposed, to match column major expectations crate::TypeInner::Matrix { .. } => { let loaded = ctx.expressions.append( crate::Expression::Load {
pointer: base_handle,
},
span,
); let transposed = ctx.expressions.append( crate::Expression::Math {
fun: crate::MathFunction::Transpose,
arg: loaded,
arg1: None,
arg2: None,
arg3: None,
},
span,
);
LookupLoadOverride::Loaded(transposed)
}
_ => LookupLoadOverride::Pending,
})
} else {
None
},
}
} crate::TypeInner::Matrix { .. } => { let load_override = match acex.load_override { // We are indexing inside a row-major matrix
Some(LookupLoadOverride::Loaded(load_expr)) => { let index = index_maybe.ok_or_else(|| {
Error::InvalidAccess(index_expr_data.clone())
})?; let sub_handle = ctx.expressions.append( crate::Expression::AccessIndex {
base: load_expr,
index,
},
span,
);
Some(LookupLoadOverride::Loaded(sub_handle))
}
_ => None,
}; let sub_expr = match index_maybe {
Some(index) => crate::Expression::AccessIndex {
base: acex.base_handle,
index,
},
None => crate::Expression::Access {
base: acex.base_handle,
index: index_expr_handle,
},
};
AccessExpression {
base_handle: ctx.expressions.append(sub_expr, span),
type_id: type_lookup
.base_id
.ok_or(Error::InvalidAccessType(acex.type_id))?,
load_override,
}
} // This must be a vector or an array.
_ => { let base_handle = ctx.expressions.append( crate::Expression::Access {
base: acex.base_handle,
index: index_expr_handle,
},
span,
); let load_override = match acex.load_override { // If there is a load override in place, then we always end up // with a side-loaded value here.
Some(lookup_load_override) => { let sub_expr = match lookup_load_override { // We must be indexing into the array of row-major matrices. // Let's load the result of indexing and transpose it.
LookupLoadOverride::Pending => { let loaded = ctx.expressions.append( crate::Expression::Load {
pointer: base_handle,
},
span,
);
ctx.expressions.append( crate::Expression::Math {
fun: crate::MathFunction::Transpose,
arg: loaded,
arg1: None,
arg2: None,
arg3: None,
},
span,
)
} // We are indexing inside a row-major matrix.
LookupLoadOverride::Loaded(load_expr) => {
ctx.expressions.append( crate::Expression::Access {
base: load_expr,
index: index_expr_handle,
},
span,
)
}
};
Some(LookupLoadOverride::Loaded(sub_expr))
}
None => None,
};
AccessExpression {
base_handle,
type_id: type_lookup
.base_id
.ok_or(Error::InvalidAccessType(acex.type_id))?,
load_override,
}
}
};
}
let result_type_id = self.next()?; let id = self.next()?; let composite_id = self.next()?; let index_id = self.next()?;
let root_lexp = self.lookup_expression.lookup(composite_id)?; let root_handle = get_expr_handle!(composite_id, root_lexp); let root_type_lookup = self.lookup_type.lookup(root_lexp.type_id)?; let index_lexp = self.lookup_expression.lookup(index_id)?; let index_handle = get_expr_handle!(index_id, index_lexp); let index_type = self.lookup_type.lookup(index_lexp.type_id)?.handle;
let num_components = match ctx.module.types[root_type_lookup.handle].inner { crate::TypeInner::Vector { size, .. } => size as u32,
_ => return Err(Error::InvalidVectorType(root_type_lookup.handle)),
};
let result_type_id = self.next()?; let id = self.next()?; let composite_id = self.next()?; let object_id = self.next()?; let index_id = self.next()?;
let object_lexp = self.lookup_expression.lookup(object_id)?; let object_handle = get_expr_handle!(object_id, object_lexp); let root_lexp = self.lookup_expression.lookup(composite_id)?; let root_handle = get_expr_handle!(composite_id, root_lexp); let root_type_lookup = self.lookup_type.lookup(root_lexp.type_id)?; let index_lexp = self.lookup_expression.lookup(index_id)?; let index_handle = get_expr_handle!(index_id, index_lexp); let index_type = self.lookup_type.lookup(index_lexp.type_id)?.handle;
let num_components = match ctx.module.types[root_type_lookup.handle].inner { crate::TypeInner::Vector { size, .. } => size as u32,
_ => return Err(Error::InvalidVectorType(root_type_lookup.handle)),
};
let result_type_id = self.next()?; let id = self.next()?; let object_id = self.next()?; let composite_id = self.next()?; letmut selections = Vec::with_capacity(inst.wc as usize - 5); for _ in5..inst.wc {
selections.push(self.next()?);
}
let object_lexp = self.lookup_expression.lookup(object_id)?.clone(); let object_handle = get_expr_handle!(object_id, &object_lexp); let root_lexp = self.lookup_expression.lookup(composite_id)?.clone(); let root_handle = get_expr_handle!(composite_id, &root_lexp); let handle = self.insert_composite(
root_handle,
result_type_id,
object_handle,
&selections,
&ctx.module.types,
ctx.expressions,
span,
)?;
let result_type_id = self.next()?; let id = self.next()?; letmut components = Vec::with_capacity(inst.wc as usize - 2); for _ in3..inst.wc { let comp_id = self.next()?;
log::trace!("\t\t\tlooking up expr {:?}", comp_id); let lexp = self.lookup_expression.lookup(comp_id)?; let handle = get_expr_handle!(comp_id, lexp);
components.push(handle);
} let ty = self.lookup_type.lookup(result_type_id)?.handle; let first = components[0]; let expr = match ctx.module.types[ty].inner { // this is an optimization to detect the splat crate::TypeInner::Vector { size, .. } if components.len() == size as usize
&& components[1..].iter().all(|&c| c == first) =>
{ crate::Expression::Splat { size, value: first }
}
_ => crate::Expression::Compose { ty, components },
}; self.lookup_expression.insert(
id,
LookupExpression {
handle: ctx.expressions.append(expr, span),
type_id: result_type_id,
block_id,
},
);
}
Op::Load => {
inst.expect_at_least(4)?;
let result_type_id = self.next()?; let result_id = self.next()?; let pointer_id = self.next()?; if inst.wc != 4 {
inst.expect(5)?; let _memory_access = self.next()?;
}
let base_lexp = self.lookup_expression.lookup(pointer_id)?; let base_handle = get_expr_handle!(pointer_id, base_lexp); let type_lookup = self.lookup_type.lookup(base_lexp.type_id)?; let handle = match ctx.module.types[type_lookup.handle].inner { crate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } => {
base_handle
}
_ => matchself.lookup_load_override.get(&pointer_id) {
Some(&LookupLoadOverride::Loaded(handle)) => handle, //Note: we aren't handling `LookupLoadOverride::Pending` properly here
_ => ctx.expressions.append( crate::Expression::Load {
pointer: base_handle,
},
span,
),
},
};
let pointer_id = self.next()?; let value_id = self.next()?; if inst.wc != 3 {
inst.expect(4)?; let _memory_access = self.next()?;
} let base_expr = self.lookup_expression.lookup(pointer_id)?; let base_handle = get_expr_handle!(pointer_id, base_expr); let value_expr = self.lookup_expression.lookup(value_id)?; let value_handle = get_expr_handle!(value_id, value_expr);
let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let p1_id = self.next()?; let p2_id = self.next()?; let span = self.span_from_with_op(start);
let p1_lexp = self.lookup_expression.lookup(p1_id)?; let left = self.get_expr_handle(
p1_id,
p1_lexp,
ctx,
&mut emitter,
&mut block,
body_idx,
); let p2_lexp = self.lookup_expression.lookup(p2_id)?; let right = self.get_expr_handle(
p2_id,
p2_lexp,
ctx,
&mut emitter,
&mut block,
body_idx,
);
let result_ty = self.lookup_type.lookup(result_type_id)?; let inner = &ctx.module.types[result_ty.handle].inner; let kind = inner.scalar_kind().unwrap(); let size = inner.size(ctx.gctx()) as u8;
let result_type_id = self.next()?; let result_id = self.next()?; let matrix_id = self.next()?; let matrix_lexp = self.lookup_expression.lookup(matrix_id)?; let matrix_handle = get_expr_handle!(matrix_id, matrix_lexp); let expr = crate::Expression::Math {
fun: crate::MathFunction::Transpose,
arg: matrix_handle,
arg1: None,
arg2: None,
arg3: None,
}; self.lookup_expression.insert(
result_id,
LookupExpression {
handle: ctx.expressions.append(expr, span),
type_id: result_type_id,
block_id,
},
);
}
Op::Dot => {
inst.expect(5)?;
let result_type_id = self.next()?; let result_id = self.next()?; let left_id = self.next()?; let right_id = self.next()?; let left_lexp = self.lookup_expression.lookup(left_id)?; let left_handle = get_expr_handle!(left_id, left_lexp); let right_lexp = self.lookup_expression.lookup(right_id)?; let right_handle = get_expr_handle!(right_id, right_lexp); let expr = crate::Expression::Math {
fun: crate::MathFunction::Dot,
arg: left_handle,
arg1: Some(right_handle),
arg2: None,
arg3: None,
}; self.lookup_expression.insert(
result_id,
LookupExpression {
handle: ctx.expressions.append(expr, span),
type_id: result_type_id,
block_id,
},
);
}
Op::BitFieldInsert => {
inst.expect(7)?;
let start = self.data_offset; let span = self.span_from_with_op(start);
let result_type_id = self.next()?; let result_id = self.next()?; let base_id = self.next()?; let insert_id = self.next()?; let offset_id = self.next()?; let count_id = self.next()?; let base_lexp = self.lookup_expression.lookup(base_id)?; let base_handle = get_expr_handle!(base_id, base_lexp); let insert_lexp = self.lookup_expression.lookup(insert_id)?; let insert_handle = get_expr_handle!(insert_id, insert_lexp); let offset_lexp = self.lookup_expression.lookup(offset_id)?; let offset_handle = get_expr_handle!(offset_id, offset_lexp); let offset_lookup_ty = self.lookup_type.lookup(offset_lexp.type_id)?; let count_lexp = self.lookup_expression.lookup(count_id)?; let count_handle = get_expr_handle!(count_id, count_lexp); let count_lookup_ty = self.lookup_type.lookup(count_lexp.type_id)?;
let offset_kind = ctx.module.types[offset_lookup_ty.handle]
.inner
.scalar_kind()
.unwrap(); let count_kind = ctx.module.types[count_lookup_ty.handle]
.inner
.scalar_kind()
.unwrap();
let result_type_id = self.next()?; let result_id = self.next()?; let base_id = self.next()?; let offset_id = self.next()?; let count_id = self.next()?; let base_lexp = self.lookup_expression.lookup(base_id)?; let base_handle = get_expr_handle!(base_id, base_lexp); let offset_lexp = self.lookup_expression.lookup(offset_id)?; let offset_handle = get_expr_handle!(offset_id, offset_lexp); let offset_lookup_ty = self.lookup_type.lookup(offset_lexp.type_id)?; let count_lexp = self.lookup_expression.lookup(count_id)?; let count_handle = get_expr_handle!(count_id, count_lexp); let count_lookup_ty = self.lookup_type.lookup(count_lexp.type_id)?;
let offset_kind = ctx.module.types[offset_lookup_ty.handle]
.inner
.scalar_kind()
.unwrap(); let count_kind = ctx.module.types[count_lookup_ty.handle]
.inner
.scalar_kind()
.unwrap();
let result_type_id = self.next()?; let result_id = self.next()?; let base_id = self.next()?; let base_lexp = self.lookup_expression.lookup(base_id)?; let base_handle = get_expr_handle!(base_id, base_lexp); let expr = crate::Expression::Math {
fun: match inst.op {
Op::BitReverse => crate::MathFunction::ReverseBits,
Op::BitCount => crate::MathFunction::CountOneBits,
_ => unreachable!(),
},
arg: base_handle,
arg1: None,
arg2: None,
arg3: None,
}; self.lookup_expression.insert(
result_id,
LookupExpression {
handle: ctx.expressions.append(expr, span),
type_id: result_type_id,
block_id,
},
);
}
Op::OuterProduct => {
inst.expect(5)?;
let result_type_id = self.next()?; let result_id = self.next()?; let left_id = self.next()?; let right_id = self.next()?; let left_lexp = self.lookup_expression.lookup(left_id)?; let left_handle = get_expr_handle!(left_id, left_lexp); let right_lexp = self.lookup_expression.lookup(right_id)?; let right_handle = get_expr_handle!(right_id, right_lexp); let expr = crate::Expression::Math {
fun: crate::MathFunction::Outer,
arg: left_handle,
arg1: Some(right_handle),
arg2: None,
arg3: None,
}; self.lookup_expression.insert(
result_id,
LookupExpression {
handle: ctx.expressions.append(expr, span),
type_id: result_type_id,
block_id,
},
);
} // Bitwise instructions
Op::Not => {
inst.expect(4)?; self.parse_expr_unary_op_sign_adjusted(
ctx,
&mut emitter,
&mut block,
block_id,
body_idx, crate::UnaryOperator::BitwiseNot,
)?;
}
Op::ShiftRightLogical => {
inst.expect(5)?; //TODO: convert input and result to unsigned
parse_expr_op!(crate::BinaryOperator::ShiftRight, SHIFT)?;
}
Op::ShiftRightArithmetic => {
inst.expect(5)?; //TODO: convert input and result to signed
parse_expr_op!(crate::BinaryOperator::ShiftRight, SHIFT)?;
}
Op::ShiftLeftLogical => {
inst.expect(5)?;
parse_expr_op!(crate::BinaryOperator::ShiftLeft, SHIFT)?;
} // Sampling
Op::Image => {
inst.expect(4)?; self.parse_image_uncouple(block_id)?;
}
Op::SampledImage => {
inst.expect(5)?; self.parse_image_couple()?;
}
Op::ImageWrite => { let extra = inst.expect_at_least(4)?; let stmt = self.parse_image_write(extra, ctx, &mut emitter, &mut block, body_idx)?;
block.extend(emitter.finish(ctx.expressions));
block.push(stmt, span);
emitter.start(ctx.expressions);
}
Op::ImageFetch | Op::ImageRead => { let extra = inst.expect_at_least(5)?; self.parse_image_load(
extra,
ctx,
&mut emitter,
&mut block,
block_id,
body_idx,
)?;
}
Op::ImageSampleImplicitLod | Op::ImageSampleExplicitLod => { let extra = inst.expect_at_least(5)?; let options = image::SamplingOptions {
compare: false,
project: false,
}; self.parse_image_sample(
extra,
options,
ctx,
&mut emitter,
&mut block,
block_id,
body_idx,
)?;
}
Op::ImageSampleProjImplicitLod | Op::ImageSampleProjExplicitLod => { let extra = inst.expect_at_least(5)?; let options = image::SamplingOptions {
compare: false,
project: true,
}; self.parse_image_sample(
extra,
options,
ctx,
&mut emitter,
&mut block,
block_id,
body_idx,
)?;
}
Op::ImageSampleDrefImplicitLod | Op::ImageSampleDrefExplicitLod => { let extra = inst.expect_at_least(6)?; let options = image::SamplingOptions {
compare: true,
project: false,
}; self.parse_image_sample(
extra,
options,
ctx,
&mut emitter,
&mut block,
block_id,
body_idx,
)?;
}
Op::ImageSampleProjDrefImplicitLod | Op::ImageSampleProjDrefExplicitLod => { let extra = inst.expect_at_least(6)?; let options = image::SamplingOptions {
compare: true,
project: true,
}; self.parse_image_sample(
extra,
options,
ctx,
&mut emitter,
&mut block,
block_id,
body_idx,
)?;
}
Op::ImageQuerySize => {
inst.expect(4)?; self.parse_image_query_size( false,
ctx,
&mut emitter,
&mut block,
block_id,
body_idx,
)?;
}
Op::ImageQuerySizeLod => {
inst.expect(5)?; self.parse_image_query_size( true,
ctx,
&mut emitter,
&mut block,
block_id,
body_idx,
)?;
}
Op::ImageQueryLevels => {
inst.expect(4)?; self.parse_image_query_other(crate::ImageQuery::NumLevels, ctx, block_id)?;
}
Op::ImageQuerySamples => {
inst.expect(4)?; self.parse_image_query_other(crate::ImageQuery::NumSamples, ctx, block_id)?;
} // other ops
Op::Select => {
inst.expect(6)?; let result_type_id = self.next()?; let result_id = self.next()?; let condition = self.next()?; let o1_id = self.next()?; let o2_id = self.next()?;
let cond_lexp = self.lookup_expression.lookup(condition)?; let cond_handle = get_expr_handle!(condition, cond_lexp); let o1_lexp = self.lookup_expression.lookup(o1_id)?; let o1_handle = get_expr_handle!(o1_id, o1_lexp); let o2_lexp = self.lookup_expression.lookup(o2_id)?; let o2_handle = get_expr_handle!(o2_id, o2_lexp);
let expr = crate::Expression::Select {
condition: cond_handle,
accept: o1_handle,
reject: o2_handle,
}; self.lookup_expression.insert(
result_id,
LookupExpression {
handle: ctx.expressions.append(expr, span),
type_id: result_type_id,
block_id,
},
);
}
Op::VectorShuffle => {
inst.expect_at_least(5)?; let result_type_id = self.next()?; let result_id = self.next()?; let v1_id = self.next()?; let v2_id = self.next()?;
let v1_lexp = self.lookup_expression.lookup(v1_id)?; let v1_lty = self.lookup_type.lookup(v1_lexp.type_id)?; let v1_handle = get_expr_handle!(v1_id, v1_lexp); let n1 = match ctx.module.types[v1_lty.handle].inner { crate::TypeInner::Vector { size, .. } => size as u32,
_ => return Err(Error::InvalidInnerType(v1_lexp.type_id)),
}; let v2_lexp = self.lookup_expression.lookup(v2_id)?; let v2_lty = self.lookup_type.lookup(v2_lexp.type_id)?; let v2_handle = get_expr_handle!(v2_id, v2_lexp); let n2 = match ctx.module.types[v2_lty.handle].inner { crate::TypeInner::Vector { size, .. } => size as u32,
_ => return Err(Error::InvalidInnerType(v2_lexp.type_id)),
};
self.temp_bytes.clear(); letmut max_component = 0; for _ in5..inst.wc as usize { letmut index = self.next()?; if index == u32::MAX { // treat Undefined as X
index = 0;
}
max_component = max_component.max(index); self.temp_bytes.push(index as u8);
}
// Check for swizzle first. let expr = if max_component < n1 { usecrate::SwizzleComponent as Sc; let size = matchself.temp_bytes.len() { 2 => crate::VectorSize::Bi, 3 => crate::VectorSize::Tri,
_ => crate::VectorSize::Quad,
}; letmut pattern = [Sc::X; 4]; for (pat, index) in pattern.iter_mut().zip(self.temp_bytes.drain(..)) {
*pat = match index { 0 => Sc::X, 1 => Sc::Y, 2 => Sc::Z,
_ => Sc::W,
};
} crate::Expression::Swizzle {
size,
vector: v1_handle,
pattern,
}
} else { // Fall back to access + compose letmut components = Vec::with_capacity(self.temp_bytes.len()); for index inself.temp_bytes.drain(..).map(|i| i as u32) { let expr = if index < n1 { crate::Expression::AccessIndex {
base: v1_handle,
index,
}
} elseif index < n1 + n2 { crate::Expression::AccessIndex {
base: v2_handle,
index: index - n1,
}
} else { return Err(Error::InvalidAccessIndex(index));
};
components.push(ctx.expressions.append(expr, span));
} crate::Expression::Compose {
ty: self.lookup_type.lookup(result_type_id)?.handle,
components,
}
};
let result_type_id = self.next()?; let result_id = self.next()?; let func_id = self.next()?;
letmut arguments = Vec::with_capacity(inst.wc as usize - 4); for _ in0..arguments.capacity() { let arg_id = self.next()?; let lexp = self.lookup_expression.lookup(arg_id)?;
arguments.push(get_expr_handle!(arg_id, lexp));
}
// We just need an unique handle here, nothing more. let function = self.add_call(ctx.function_id, func_id);
let result = ifself.lookup_void_type == Some(result_type_id) {
None
} else { let expr_handle = ctx
.expressions
.append(crate::Expression::CallResult(function), span); self.lookup_expression.insert(
result_id,
LookupExpression {
handle: expr_handle,
type_id: result_type_id,
block_id,
},
);
Some(expr_handle)
};
block.push( crate::Statement::Call {
function,
arguments,
result,
},
span,
);
emitter.start(ctx.expressions);
}
Op::ExtInst => { usecrate::MathFunction as Mf; use spirv::GLOp as Glo;
let base_wc = 5;
inst.expect_at_least(base_wc)?;
let result_type_id = self.next()?; let result_id = self.next()?; let set_id = self.next()?; if Some(set_id) != self.ext_glsl_id { return Err(Error::UnsupportedExtInstSet(set_id));
} let inst_id = self.next()?; let gl_op = Glo::from_u32(inst_id).ok_or(Error::UnsupportedExtInst(inst_id))?;
// If this is a branch to a merge or continue block, then // that ends the current body. // // Why can we count on finding an entry here when it's // needed? SPIR-V requires dominators to appear before // blocks they dominate, so we will have visited a // structured control construct's header block before // anything that could exit it. iflet Some(info) = ctx.mergers.get(&target_id) {
block.extend(emitter.finish(ctx.expressions));
ctx.blocks.insert(block_id, block); let body = &mut ctx.bodies[body_idx];
body.data.push(BodyFragment::BlockId(block_id));
merger(body, info);
return Ok(());
}
// If `target_id` has no entry in `ctx.body_for_label`, then // this must be the only branch to it: // // - We've already established that it's not anybody's merge // block. // // - It can't be a switch case. Only switch header blocks // and other switch cases can branch to a switch case. // Switch header blocks must dominate all their cases, so // they must appear in the file before them, and when we // see `Op::Switch` we populate `ctx.body_for_label` for // every switch case. // // Thus, `target_id` must be a simple extension of the // current block, which we dominate, so we know we'll // encounter it later in the file.
ctx.body_for_label.entry(target_id).or_insert(body_idx);
let condition = { let condition_id = self.next()?; let lexp = self.lookup_expression.lookup(condition_id)?;
get_expr_handle!(condition_id, lexp)
};
// HACK(eddyb) Naga doesn't seem to have this helper, // so it's declared on the fly here for convenience. #[derive(Copy, Clone)] struct BranchTarget {
label_id: spirv::Word,
merge_info: Option<MergeBlockInformation>,
} let branch_target = |label_id| BranchTarget {
label_id,
merge_info: ctx.mergers.get(&label_id).copied(),
};
let true_target = branch_target(self.next()?); let false_target = branch_target(self.next()?);
// Consume branch weights for _ in4..inst.wc { let _ = self.next()?;
}
// Handle `OpBranchConditional`s used at the end of a loop // body's "continuing" section as a "conditional backedge", // i.e. a `do`-`while` condition, or `break if` in WGSL.
// HACK(eddyb) this has to go to the parent *twice*, because // `OpLoopMerge` left the "continuing" section nested in the // loop body in terms of `parent`, but not `BodyFragment`. let parent_body_idx = ctx.bodies[body_idx].parent; let parent_parent_body_idx = ctx.bodies[parent_body_idx].parent; match ctx.bodies[parent_parent_body_idx].data[..] { // The `OpLoopMerge`'s `continuing` block and the loop's // backedge block may not be the same, but they'll both // belong to the same body.
[.., BodyFragment::Loop {
body: loop_body_idx,
continuing: loop_continuing_idx,
break_if: refmut break_if_slot @ None,
}] if body_idx == loop_continuing_idx => { // Try both orderings of break-vs-backedge, because // SPIR-V is symmetrical here, unlike WGSL `break if`. let break_if_cond = [true, false].into_iter().find_map(|true_breaks| { let (break_candidate, backedge_candidate) = if true_breaks {
(true_target, false_target)
} else {
(false_target, true_target)
};
if break_candidate.merge_info
!= Some(MergeBlockInformation::LoopMerge)
{ return None;
}
// HACK(eddyb) since Naga doesn't explicitly track // backedges, this is checking for the outcome of // `OpLoopMerge` below (even if it looks weird). let backedge_candidate_is_backedge =
backedge_candidate.merge_info.is_none()
&& ctx.body_for_label.get(&backedge_candidate.label_id)
== Some(&loop_body_idx); if !backedge_candidate_is_backedge { return None;
}
// This `OpBranchConditional` ends the "continuing" // section of the loop body as normal, with the // `break if` condition having been stashed above. break None;
}
}
_ => {}
}
block.extend(emitter.finish(ctx.expressions));
ctx.blocks.insert(block_id, block); let body = &mut ctx.bodies[body_idx];
body.data.push(BodyFragment::BlockId(block_id));
let same_target = true_target.label_id == false_target.label_id;
// Start a body block for the `accept` branch. let accept = ctx.bodies.len(); letmut accept_block = Body::with_parent(body_idx);
// If the `OpBranchConditional` target is somebody else's // merge or continue block, then put a `Break` or `Continue` // statement in this new body block. iflet Some(info) = true_target.merge_info {
merger( match same_target { true => &mut ctx.bodies[body_idx], false => &mut accept_block,
},
&info,
)
} else { // Note the body index for the block we're branching to. let prev = ctx.body_for_label.insert(
true_target.label_id, match same_target { true => body_idx, false => accept,
},
);
debug_assert!(prev.is_none());
}
if same_target { return Ok(());
}
ctx.bodies.push(accept_block);
// Handle the `reject` branch just like the `accept` block. let reject = ctx.bodies.len(); letmut reject_block = Body::with_parent(body_idx);
let body = &mut ctx.bodies[body_idx];
body.data.push(BodyFragment::If {
condition,
accept,
reject,
});
return Ok(());
}
Op::Switch => {
inst.expect_at_least(3)?; let selector = self.next()?; let default_id = self.next()?;
// If the previous instruction was a `OpSelectionMerge` then we must // promote the `MergeBlockInformation` to a `SwitchMerge` iflet Some(merge) = selection_merge_block {
ctx.mergers
.insert(merge, MergeBlockInformation::SwitchMerge);
}
let default = ctx.bodies.len();
ctx.bodies.push(Body::with_parent(body_idx));
ctx.body_for_label.entry(default_id).or_insert(default);
let selector_lexp = &self.lookup_expression[&selector]; let selector_lty = self.lookup_type.lookup(selector_lexp.type_id)?; let selector_handle = get_expr_handle!(selector, selector_lexp); let selector = match ctx.module.types[selector_lty.handle].inner { crate::TypeInner::Scalar(crate::Scalar {
kind: crate::ScalarKind::Uint,
width: _,
}) => { // IR expects a signed integer, so do a bitcast
ctx.expressions.append( crate::Expression::As {
kind: crate::ScalarKind::Sint,
expr: selector_handle,
convert: None,
},
span,
)
} crate::TypeInner::Scalar(crate::Scalar {
kind: crate::ScalarKind::Sint,
width: _,
}) => selector_handle, ref other => unimplemented!("Unexpected selector {:?}", other),
};
// Clear past switch cases to prevent them from entering this one self.switch_cases.clear();
for _ in0..(inst.wc - 3) / 2 { let literal = self.next()?; let target = self.next()?;
let case_body_idx = ctx.bodies.len();
// Check if any previous case already used this target block id, if so // group them together to reorder them later so that no weird // fallthrough cases happen. iflet Some(&mut (_, refmut literals)) = self.switch_cases.get_mut(&target)
{
literals.push(literal as i32); continue;
}
// Register this target block id as already having been processed and // the respective body index assigned and the first case value self.switch_cases
.insert(target, (case_body_idx, vec![literal as i32]));
}
// Loop through the collected target blocks creating a new case for each // literal pointing to it, only one case will have the true body and all the // others will be empty fallthrough so that they all execute the same body // without duplicating code. // // Since `switch_cases` is an indexmap the order of insertion is preserved // this is needed because spir-v defines fallthrough order in the switch // instruction. letmut cases = Vec::with_capacity((inst.wc as usize - 3) / 2); for &(case_body_idx, ref literals) inself.switch_cases.values() { let value = literals[0];
for &literal in literals.iter().skip(1) { let empty_body_idx = ctx.bodies.len(); let body = Body::with_parent(body_idx);
ctx.bodies.push(body);
cases.push((literal, empty_body_idx));
}
cases.push((value, case_body_idx));
}
block.extend(emitter.finish(ctx.expressions));
let body = &mut ctx.bodies[body_idx];
ctx.blocks.insert(block_id, block); // Make sure the vector has space for at least two more allocations
body.data.reserve(2);
body.data.push(BodyFragment::BlockId(block_id));
body.data.push(BodyFragment::Switch {
selector,
cases,
default,
});
return Ok(());
}
Op::SelectionMerge => {
inst.expect(3)?; let merge_block_id = self.next()?; // TODO: Selection Control Mask let _selection_control = self.next()?;
// Indicate that the merge block is a continuation of the // current `Body`.
ctx.body_for_label.entry(merge_block_id).or_insert(body_idx);
// Let subsequent branches to the merge block know that // they've reached the end of the selection construct.
ctx.mergers
.insert(merge_block_id, MergeBlockInformation::SelectionMerge);
selection_merge_block = Some(merge_block_id);
}
Op::LoopMerge => {
inst.expect_at_least(4)?; let merge_block_id = self.next()?; let continuing = self.next()?;
// TODO: Loop Control Parameters for _ in0..inst.wc - 3 { self.next()?;
}
// Indicate that the merge block is a continuation of the // current `Body`.
ctx.body_for_label.entry(merge_block_id).or_insert(body_idx); // Let subsequent branches to the merge block know that // they're `Break` statements.
ctx.mergers
.insert(merge_block_id, MergeBlockInformation::LoopMerge);
let loop_body_idx = ctx.bodies.len();
ctx.bodies.push(Body::with_parent(body_idx));
let continue_idx = ctx.bodies.len(); // The continue block inherits the scope of the loop body
ctx.bodies.push(Body::with_parent(loop_body_idx));
ctx.body_for_label.entry(continuing).or_insert(continue_idx); // Let subsequent branches to the continue block know that // they're `Continue` statements.
ctx.mergers
.insert(continuing, MergeBlockInformation::LoopContinue);
// The loop header always belongs to the loop body
ctx.body_for_label.insert(block_id, loop_body_idx);
let length = ctx
.expressions
.append(crate::Expression::ArrayLength(member_ptr), span);
self.lookup_expression.insert(
result_id,
LookupExpression {
handle: length,
type_id: result_type_id,
block_id,
},
);
}
Op::CopyMemory => {
inst.expect_at_least(3)?; let target_id = self.next()?; let source_id = self.next()?; let _memory_access = if inst.wc != 3 {
inst.expect(4)?;
spirv::MemoryAccess::from_bits(self.next()?)
.ok_or(Error::InvalidParameter(Op::CopyMemory))?
} else {
spirv::MemoryAccess::NONE
};
// TODO: check if the source and target types are the same? let target = self.lookup_expression.lookup(target_id)?; let target_handle = get_expr_handle!(target_id, target); let source = self.lookup_expression.lookup(source_id)?; let source_handle = get_expr_handle!(source_id, source);
// This operation is practically the same as loading and then storing, I think. let value_expr = ctx.expressions.append( crate::Expression::Load {
pointer: source_handle,
},
span,
);
block.push( crate::Statement::SubgroupGather {
result: result_handle,
mode,
argument: argument_handle,
},
span,
);
emitter.start(ctx.expressions);
}
Op::AtomicLoad => {
inst.expect(6)?; let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let pointer_id = self.next()?; let _scope_id = self.next()?; let _memory_semantics_id = self.next()?; let span = self.span_from_with_op(start);
log::trace!("\t\t\tlooking up expr {:?}", pointer_id); let p_lexp_handle =
get_expr_handle!(pointer_id, self.lookup_expression.lookup(pointer_id)?);
// Create an expression for our result let expr = crate::Expression::Load {
pointer: p_lexp_handle,
}; let handle = ctx.expressions.append(expr, span); self.lookup_expression.insert(
result_id,
LookupExpression {
handle,
type_id: result_type_id,
block_id,
},
);
// Store any associated global variables so we can upgrade their types later self.record_atomic_access(ctx, p_lexp_handle)?;
}
Op::AtomicStore => {
inst.expect(5)?; let start = self.data_offset; let pointer_id = self.next()?; let _scope_id = self.next()?; let _memory_semantics_id = self.next()?; let value_id = self.next()?; let span = self.span_from_with_op(start);
log::trace!("\t\t\tlooking up pointer expr {:?}", pointer_id); let p_lexp_handle =
get_expr_handle!(pointer_id, self.lookup_expression.lookup(pointer_id)?);
log::trace!("\t\t\tlooking up value expr {:?}", pointer_id); let v_lexp_handle =
get_expr_handle!(value_id, self.lookup_expression.lookup(value_id)?);
block.extend(emitter.finish(ctx.expressions)); // Create a statement for the op itself let stmt = crate::Statement::Store {
pointer: p_lexp_handle,
value: v_lexp_handle,
};
block.push(stmt, span);
emitter.start(ctx.expressions);
// Store any associated global variables so we can upgrade their types later self.record_atomic_access(ctx, p_lexp_handle)?;
}
Op::AtomicIIncrement | Op::AtomicIDecrement => {
inst.expect(6)?; let start = self.data_offset; let result_type_id = self.next()?; let result_id = self.next()?; let pointer_id = self.next()?; let _scope_id = self.next()?; let _memory_semantics_id = self.next()?; let span = self.span_from_with_op(start);
block.extend(emitter.finish(ctx.expressions)); // Create an expression for our result let r_lexp_handle = { let expr = crate::Expression::AtomicResult {
ty: p_base_ty_h,
comparison: false,
}; let handle = ctx.expressions.append(expr, span); self.lookup_expression.insert(
result_id,
LookupExpression {
handle,
type_id: result_type_id,
block_id,
},
);
handle
};
emitter.start(ctx.expressions);
// Create a literal "1" to use as our value let one_lexp_handle = make_index_literal(
ctx, 1,
&mut block,
&mut emitter,
p_base_ty_h,
result_type_id,
span,
)?;
// Create a statement for the op itself let stmt = crate::Statement::Atomic {
pointer: p_exp_h,
fun: match inst.op {
Op::AtomicIIncrement => crate::AtomicFunction::Add,
_ => crate::AtomicFunction::Subtract,
},
value: one_lexp_handle,
result: Some(r_lexp_handle),
};
block.push(stmt, span);
// Store any associated global variables so we can upgrade their types later self.record_atomic_access(ctx, p_exp_h)?;
}
Op::AtomicCompareExchange => {
inst.expect(9)?;
let start = self.data_offset; let span = self.span_from_with_op(start); let result_type_id = self.next()?; let result_id = self.next()?; let pointer_id = self.next()?; let _memory_scope_id = self.next()?; let _equal_memory_semantics_id = self.next()?; let _unequal_memory_semantics_id = self.next()?; let value_id = self.next()?; let comparator_id = self.next()?;
log::trace!("\t\t\tlooking up value expr {:?}", value_id); let v_lexp_handle =
get_expr_handle!(value_id, self.lookup_expression.lookup(value_id)?);
log::trace!("\t\t\tlooking up comparator expr {:?}", value_id); let c_lexp_handle = get_expr_handle!(
comparator_id, self.lookup_expression.lookup(comparator_id)?
);
// We know from the SPIR-V spec that the result type must be an integer // scalar, and we'll need the type itself to get a handle to the atomic // result struct. letcrate::TypeInner::Scalar(scalar) = ctx.module.types[p_base_ty_h].inner else { return Err( crate::front::atomic_upgrade::Error::CompareExchangeNonScalarBaseType
.into(),
);
};
// Get a handle to the atomic result struct type. let atomic_result_struct_ty_h = ctx.module.generate_predeclared_type( crate::PredeclaredType::AtomicCompareExchangeWeakResult(scalar),
);
block.extend(emitter.finish(ctx.expressions));
// Create an expression for our atomic result let atomic_lexp_handle = { let expr = crate::Expression::AtomicResult {
ty: atomic_result_struct_ty_h,
comparison: true,
};
ctx.expressions.append(expr, span)
};
// Create an dot accessor to extract the value from the // result struct __atomic_compare_exchange_result<T> and use that // as the expression for the result_id
{ let expr = crate::Expression::AccessIndex {
base: atomic_lexp_handle,
index: 0,
}; let handle = ctx.expressions.append(expr, span); // Use this dot accessor as the result id's expression let _ = self.lookup_expression.insert(
result_id,
LookupExpression {
handle,
type_id: result_type_id,
block_id,
},
);
}
emitter.start(ctx.expressions);
// Create a statement for the op itself let stmt = crate::Statement::Atomic {
pointer: p_exp_h,
fun: crate::AtomicFunction::Exchange {
compare: Some(c_lexp_handle),
},
value: v_lexp_handle,
result: Some(atomic_lexp_handle),
};
block.push(stmt, span);
// Save this block fragment in `block_ctx.blocks`, and mark it to be // incorporated into the current body at `Statement` assembly time.
ctx.blocks.insert(block_id, block); let body = &mut ctx.bodies[body_idx];
body.data.push(BodyFragment::BlockId(block_id));
Ok(())
}
fn make_expression_storage(
&mutself,
globals: &Arena<crate::GlobalVariable>,
constants: &Arena<crate::Constant>,
overrides: &Arena<crate::Override>,
) -> Arena<crate::Expression> { letmut expressions = Arena::new(); #[allow(clippy::panic)]
{
assert!(self.lookup_expression.is_empty());
} // register global variables for (&id, var) inself.lookup_variable.iter() { let span = globals.get_span(var.handle); let handle = expressions.append(crate::Expression::GlobalVariable(var.handle), span); self.lookup_expression.insert(
id,
LookupExpression {
type_id: var.type_id,
handle, // Setting this to an invalid id will cause get_expr_handle // to default to the main body making sure no load/stores // are added.
block_id: 0,
},
);
} // register constants for (&id, con) inself.lookup_constant.iter() { let (expr, span) = match con.inner {
Constant::Constant(c) => (crate::Expression::Constant(c), constants.get_span(c)),
Constant::Override(o) => (crate::Expression::Override(o), overrides.get_span(o)),
}; let handle = expressions.append(expr, span); self.lookup_expression.insert(
id,
LookupExpression {
type_id: con.type_id,
handle, // Setting this to an invalid id will cause get_expr_handle // to default to the main body making sure no load/stores // are added.
block_id: 0,
},
);
} // done
expressions
}
/// Walk the statement tree and patch it in the following cases: /// 1. Function call targets are replaced by `deferred_function_calls` map fn patch_statements(
&mutself,
statements: &mutcrate::Block,
expressions: &mut Arena<crate::Expression>,
fun_parameter_sampling: &mut [image::SamplingFlags],
) -> Result<(), Error> { usecrate::Statement as S; letmut i = 0usize; while i < statements.len() { match statements[i] {
S::Emit(_) => {}
S::Block(refmut block) => { self.patch_statements(block, expressions, fun_parameter_sampling)?;
}
S::If {
condition: _, refmut accept, refmut reject,
} => { self.patch_statements(reject, expressions, fun_parameter_sampling)?; self.patch_statements(accept, expressions, fun_parameter_sampling)?;
}
S::Switch {
selector: _, refmut cases,
} => { for case in cases.iter_mut() { self.patch_statements(&mut case.body, expressions, fun_parameter_sampling)?;
}
}
S::Loop { refmut body, refmut continuing,
break_if: _,
} => { self.patch_statements(body, expressions, fun_parameter_sampling)?; self.patch_statements(continuing, expressions, fun_parameter_sampling)?;
}
S::Break
| S::Continue
| S::Return { .. }
| S::Kill
| S::Barrier(_)
| S::Store { .. }
| S::ImageStore { .. }
| S::Atomic { .. }
| S::ImageAtomic { .. }
| S::RayQuery { .. }
| S::SubgroupBallot { .. }
| S::SubgroupCollectiveOperation { .. }
| S::SubgroupGather { .. } => {}
S::Call {
function: refmut callee, ref arguments,
..
} => { let fun_id = self.deferred_function_calls[callee.index()]; let fun_lookup = self.lookup_function.lookup(fun_id)?;
*callee = fun_lookup.handle;
// Patch sampling flags for (arg_index, arg) in arguments.iter().enumerate() { let flags = match fun_lookup.parameters_sampling.get(arg_index) {
Some(&flags) if !flags.is_empty() => flags,
_ => continue,
};
if !self.upgrade_atomics.is_empty() {
log::info!("Upgrading atomic pointers...");
module.upgrade_atomics(&self.upgrade_atomics)?;
}
// Do entry point specific processing after all functions are parsed so that we can // cull unused problematic builtins of gl_PerVertex. for (ep, fun_id) in mem::take(&mutself.deferred_entry_points) { self.process_entry_point(&mut module, ep, fun_id)?;
}
log::info!("Patching...");
{ letmut nodes = petgraph::algo::toposort(&self.function_call_graph, None)
.map_err(|cycle| Error::FunctionCallCycle(cycle.node_id()))?;
nodes.reverse(); // we need dominated first letmut functions = mem::take(&mut module.functions); for fun_id in nodes { if fun_id > !(functions.len() as u32) { // skip all the fake IDs registered for the entry points continue;
} let lookup = self.lookup_function.get_mut(&fun_id).unwrap(); // take out the function from the old array let fun = mem::take(&mut functions[lookup.handle]); // add it to the newly formed arena, and adjust the lookup
lookup.handle = module
.functions
.append(fun, functions.get_span(lookup.handle));
}
} // patch all the functions for (handle, fun) in module.functions.iter_mut() { self.patch_function(Some(handle), fun)?;
} for ep in module.entry_points.iter_mut() { self.patch_function(None, &mut ep.function)?;
}
// Check all the images and samplers to have consistent comparison property. for (handle, flags) inself.handle_sampling.drain() { if !image::patch_comparison_type(
flags,
module.global_variables.get_mut(handle),
&mut module.types,
) { return Err(Error::InconsistentComparisonSampling(handle));
}
}
if !self.future_decor.is_empty() {
log::warn!("Unused item decorations: {:?}", self.future_decor); self.future_decor.clear();
} if !self.future_member_decor.is_empty() {
log::warn!("Unused member decorations: {:?}", self.future_member_decor); self.future_member_decor.clear();
}
Ok(module)
}
fn parse_capability(&mutself, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Capability, inst.op)?;
inst.expect(2)?; let capability = self.next()?; let cap =
spirv::Capability::from_u32(capability).ok_or(Error::UnknownCapability(capability))?; if !SUPPORTED_CAPABILITIES.contains(&cap) { ifself.options.strict_capabilities { return Err(Error::UnsupportedCapability(cap));
} else {
log::warn!("Unknown capability {:?}", cap);
}
}
Ok(())
}
fn parse_extension(&mutself, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Extension, inst.op)?;
inst.expect_at_least(2)?; let (name, left) = self.next_string(inst.wc - 1)?; if left != 0 { return Err(Error::InvalidOperand);
} if !SUPPORTED_EXTENSIONS.contains(&name.as_str()) { return Err(Error::UnsupportedExtension(name));
}
Ok(())
}
fn parse_ext_inst_import(&mutself, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Extension, inst.op)?;
inst.expect_at_least(3)?; let result_id = self.next()?; let (name, left) = self.next_string(inst.wc - 2)?; if left != 0 { return Err(Error::InvalidOperand);
} if !SUPPORTED_EXT_SETS.contains(&name.as_str()) { return Err(Error::UnsupportedExtSet(name));
} self.ext_glsl_id = Some(result_id);
Ok(())
}
let ep_id = self.next()?; let mode_id = self.next()?; let args: Vec<spirv::Word> = self.data.by_ref().take(inst.wc as usize - 3).collect();
let ep = self
.lookup_entry_point
.get_mut(&ep_id)
.ok_or(Error::InvalidId(ep_id))?; let mode =
ExecutionMode::from_u32(mode_id).ok_or(Error::UnsupportedExecutionMode(mode_id))?;
match mode {
ExecutionMode::EarlyFragmentTests => { if ep.early_depth_test.is_none() {
ep.early_depth_test = Some(crate::EarlyDepthTest { conservative: None });
}
}
ExecutionMode::DepthUnchanged => {
ep.early_depth_test = Some(crate::EarlyDepthTest {
conservative: Some(crate::ConservativeDepth::Unchanged),
});
}
ExecutionMode::DepthGreater => {
ep.early_depth_test = Some(crate::EarlyDepthTest {
conservative: Some(crate::ConservativeDepth::GreaterEqual),
});
}
ExecutionMode::DepthLess => {
ep.early_depth_test = Some(crate::EarlyDepthTest {
conservative: Some(crate::ConservativeDepth::LessEqual),
});
}
ExecutionMode::DepthReplacing => { // Ignored because it can be deduced from the IR.
}
ExecutionMode::OriginUpperLeft => { // Ignored because the other option (OriginLowerLeft) is not valid in Vulkan mode.
}
ExecutionMode::LocalSize => {
ep.workgroup_size = [args[0], args[1], args[2]];
}
_ => { return Err(Error::UnsupportedExecutionMode(mode_id));
}
}
fn parse_name(&mutself, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Name, inst.op)?;
inst.expect_at_least(3)?; let id = self.next()?; let (name, left) = self.next_string(inst.wc - 2)?; if left != 0 { return Err(Error::InvalidOperand);
} self.future_decor.entry(id).or_default().name = Some(name);
Ok(())
}
fn parse_member_name(&mutself, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Name, inst.op)?;
inst.expect_at_least(4)?; let id = self.next()?; let member = self.next()?; let (name, left) = self.next_string(inst.wc - 3)?; if left != 0 { return Err(Error::InvalidOperand);
}
fn parse_module_processed(&mutself, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Name, inst.op)?;
inst.expect_at_least(2)?; let (_info, left) = self.next_string(inst.wc - 1)?; //Note: string is ignored if left != 0 { return Err(Error::InvalidOperand);
}
Ok(())
}
fn parse_decorate(&mutself, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Annotation, inst.op)?;
inst.expect_at_least(3)?; let id = self.next()?; letmut dec = self.future_decor.remove(&id).unwrap_or_default(); self.next_decoration(inst, 2, &mut dec)?; self.future_decor.insert(id, dec);
Ok(())
}
fn parse_member_decorate(&mutself, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Annotation, inst.op)?;
inst.expect_at_least(4)?; let id = self.next()?; let member = self.next()?;
fn parse_type_function(&mutself, inst: Instruction) -> Result<(), Error> { self.switch(ModuleState::Type, inst.op)?;
inst.expect_at_least(3)?; let id = self.next()?; let return_type_id = self.next()?; let parameter_type_ids = self.data.by_ref().take(inst.wc as usize - 3).collect(); self.lookup_function_type.insert(
id,
LookupFunctionType {
parameter_type_ids,
return_type_id,
},
);
Ok(())
}
fn parse_type_pointer(
&mutself,
inst: Instruction,
module: &mutcrate::Module,
) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?;
inst.expect(4)?; let id = self.next()?; let storage_class = self.next()?; let type_id = self.next()?;
let decor = self.future_decor.remove(&id); let base_lookup_ty = self.lookup_type.lookup(type_id)?; let base_inner = &module.types[base_lookup_ty.handle].inner;
let space = iflet Some(space) = base_inner.pointer_space() {
space
} elseifself
.lookup_storage_buffer_types
.contains_key(&base_lookup_ty.handle)
{ crate::AddressSpace::Storage {
access: crate::StorageAccess::default(),
}
} else { match map_storage_class(storage_class)? {
ExtendedClass::Global(space) => space,
ExtendedClass::Input | ExtendedClass::Output => crate::AddressSpace::Private,
}
};
// We don't support pointers to runtime-sized arrays in the `Uniform` // storage class with the `BufferBlock` decoration. Runtime-sized arrays // should be in the StorageBuffer class. ifletcrate::TypeInner::Array {
size: crate::ArraySize::Dynamic,
..
} = *base_inner
{ match space { crate::AddressSpace::Storage { .. } => {}
_ => { return Err(Error::UnsupportedRuntimeArrayStorageClass);
}
}
}
// Don't bother with pointer stuff for `Handle` types. let lookup_ty = if space == crate::AddressSpace::Handle {
base_lookup_ty.clone()
} else {
LookupType {
handle: module.types.insert( crate::Type {
name: decor.and_then(|dec| dec.name),
inner: crate::TypeInner::Pointer {
base: base_lookup_ty.handle,
space,
},
}, self.span_from_with_op(start),
),
base_id: Some(type_id),
}
}; self.lookup_type.insert(id, lookup_ty);
Ok(())
}
fn parse_type_array(
&mutself,
inst: Instruction,
module: &mutcrate::Module,
) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?;
inst.expect(4)?; let id = self.next()?; let type_id = self.next()?; let length_id = self.next()?; let length_const = self.lookup_constant.lookup(length_id)?;
let size = resolve_constant(module.to_ctx(), &length_const.inner)
.and_then(NonZeroU32::new)
.ok_or(Error::InvalidArraySize(length_id))?;
let decor = self.future_decor.remove(&id).unwrap_or_default(); let base = self.lookup_type.lookup(type_id)?.handle;
self.layouter.update(module.to_ctx()).unwrap();
// HACK if the underlying type is an image or a sampler, let's assume // that we're dealing with a binding-array // // Note that it's not a strictly correct assumption, but rather a trade // off caused by an impedance mismatch between SPIR-V's and Naga's type // systems - Naga distinguishes between arrays and binding-arrays via // types (i.e. both kinds of arrays are just different types), while // SPIR-V distinguishes between them through usage - e.g. given: // // ``` // %image = OpTypeImage %float 2D 2 0 0 2 Rgba16f // %uint_256 = OpConstant %uint 256 // %image_array = OpTypeArray %image %uint_256 // ``` // // ``` // %image = OpTypeImage %float 2D 2 0 0 2 Rgba16f // %uint_256 = OpConstant %uint 256 // %image_array = OpTypeArray %image %uint_256 // %image_array_ptr = OpTypePointer UniformConstant %image_array // ``` // // ... in the first case, `%image_array` should technically correspond // to `TypeInner::Array`, while in the second case it should say // `TypeInner::BindingArray` (kinda, depending on whether `%image_array` // is ever used as a freestanding type or rather always through the // pointer-indirection). // // Anyway, at the moment we don't support other kinds of image / sampler // arrays than those binding-based, so this assumption is pretty safe // for now. let inner = ifletcrate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } =
module.types[base].inner
{ crate::TypeInner::BindingArray {
base,
size: crate::ArraySize::Constant(size),
}
} else { crate::TypeInner::Array {
base,
size: crate::ArraySize::Constant(size),
stride: match decor.array_stride {
Some(stride) => stride.get(),
None => self.layouter[base].to_stride(),
},
}
};
let id = self.next()?; let sample_type_id = self.next()?; let dim = self.next()?; let is_depth = self.next()?; let is_array = self.next()? != 0; let is_msaa = self.next()? != 0; let _is_sampled = self.next()?; let format = self.next()?;
let dim = map_image_dim(dim)?; let decor = self.future_decor.remove(&id).unwrap_or_default();
// ensure there is a type for texture coordinate without extra components
module.types.insert( crate::Type {
name: None,
inner: { let scalar = crate::Scalar::F32; match dim.required_coordinate_size() {
None => crate::TypeInner::Scalar(scalar),
Some(size) => crate::TypeInner::Vector { size, scalar },
}
},
},
Default::default(),
);
let base_handle = self.lookup_type.lookup(sample_type_id)?.handle; let kind = module.types[base_handle]
.inner
.scalar_kind()
.ok_or(Error::InvalidImageBaseType(base_handle))?;
fn parse_constant(
&mutself,
inst: Instruction,
module: &mutcrate::Module,
) -> Result<(), Error> { let start = self.data_offset; self.switch(ModuleState::Type, inst.op)?;
inst.expect_at_least(4)?; let type_id = self.next()?; let id = self.next()?; let type_lookup = self.lookup_type.lookup(type_id)?; let ty = type_lookup.handle;
let literal = match module.types[ty].inner { crate::TypeInner::Scalar(crate::Scalar {
kind: crate::ScalarKind::Uint,
width,
}) => { let low = self.next()?; match width { 4 => crate::Literal::U32(low), 8 => {
inst.expect(5)?; let high = self.next()?; crate::Literal::U64(u64::from(high) << 32 | u64::from(low))
}
_ => return Err(Error::InvalidTypeWidth(width as u32)),
}
} crate::TypeInner::Scalar(crate::Scalar {
kind: crate::ScalarKind::Sint,
width,
}) => { let low = self.next()?; match width { 4 => crate::Literal::I32(low as i32), 8 => {
inst.expect(5)?; let high = self.next()?; crate::Literal::I64((u64::from(high) << 32 | u64::from(low)) as i64)
}
_ => return Err(Error::InvalidTypeWidth(width as u32)),
}
} crate::TypeInner::Scalar(crate::Scalar {
kind: crate::ScalarKind::Float,
width,
}) => { let low = self.next()?; match width { 4 => crate::Literal::F32(f32::from_bits(low)), 8 => {
inst.expect(5)?; let high = self.next()?; crate::Literal::F64(f64::from_bits(
(u64::from(high) << 32) | u64::from(low),
))
}
_ => return Err(Error::InvalidTypeWidth(width as u32)),
}
}
_ => return Err(Error::UnsupportedType(type_lookup.handle)),
};
let span = self.span_from_with_op(start);
let init = module
.global_expressions
.append(crate::Expression::Literal(literal), span);
ifletcrate::TypeInner::BindingArray { .. } = module.types[original_ty].inner { // Inside `parse_type_array()` we guess that an array of images or // samplers must be a binding array, and here we validate that guess if dec.desc_set.is_none() || dec.desc_index.is_none() { return Err(Error::NonBindingArrayOfImageOrSamplers);
}
}
ifletcrate::TypeInner::Image {
dim,
arrayed,
class: crate::ImageClass::Storage { format, access: _ },
} = module.types[ty].inner
{ // Storage image types in IR have to contain the access, but not in the SPIR-V. // The same image type in SPIR-V can be used (and has to be used) for multiple images. // So we copy the type out and apply the variable access decorations. let access = dec.flags.to_storage_access();
/// Record an atomic access to some component of a global variable. /// /// Given `handle`, an expression referring to a scalar that has had an /// atomic operation applied to it, descend into the expression, noting /// which global variable it ultimately refers to, and which struct fields /// of that global's value it accesses. /// /// Return the handle of the type of the expression. /// /// If the expression doesn't actually refer to something in a global /// variable, we can't upgrade its type in a way that Naga validation would /// pass, so reject the input instead. fn record_atomic_access(
&mutself,
ctx: &BlockContext,
handle: Handle<crate::Expression>,
) -> Result<Handle<crate::Type>, Error> {
log::debug!("\t\tlocating global variable in {handle:?}"); match ctx.expressions[handle] { crate::Expression::Access { base, index } => {
log::debug!("\t\t access {handle:?} {index:?}"); let ty = self.record_atomic_access(ctx, base)?; letcrate::TypeInner::Array { base, .. } = ctx.module.types[ty].inner else {
unreachable!("Atomic operations on Access expressions only work for arrays");
};
Ok(base)
} crate::Expression::AccessIndex { base, index } => {
log::debug!("\t\t access index {handle:?} {index:?}"); let ty = self.record_atomic_access(ctx, base)?; match ctx.module.types[ty].inner { crate::TypeInner::Struct { ref members, .. } => { let index = index as usize; self.upgrade_atomics.insert_field(ty, index);
Ok(members[index].ty)
} crate::TypeInner::Array { base, .. } => {
Ok(base)
}
_ => unreachable!("Atomic operations on AccessIndex expressions only work for structs and arrays"),
}
} crate::Expression::GlobalVariable(h) => {
log::debug!("\t\t found {h:?}"); self.upgrade_atomics.insert_global(h);
Ok(ctx.module.global_variables[h].ty)
}
_ => Err(Error::AtomicUpgradeError( crate::front::atomic_upgrade::Error::GlobalVariableMissing,
)),
}
}
}
let words = data
.chunks(4)
.map(|c| u32::from_le_bytes(c.try_into().unwrap()));
Frontend::new(words, options).parse()
}
/// Helper function to check if `child` is in the scope of `parent` fn is_parent(mut child: usize, parent: usize, block_ctx: &BlockContext) -> bool { loop { if child == parent { // The child is in the scope parent breaktrue;
} elseif child == 0 { // Searched finished at the root the child isn't in the parent's body breakfalse;
}
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.212Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-06-18)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.