impl<I: Iterator<Item = u32>> Frontend<I> { /// 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. pub(incrate::front::spv) fn next_block(
&mutself,
block_id: spirv::Word,
ctx: &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()
.get_const_val(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,
gather: 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,
gather: false,
};
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,
gather: 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,
gather: false,
};
self.parse_image_sample(
extra,
options,
ctx,
&mut emitter,
&mut block,
block_id,
body_idx,
)?;
}
Op::ImageGather => {
let extra = inst.expect_at_least(6)?;
let options = image::SamplingOptions {
compare: false,
project: false,
gather: true,
};
self.parse_image_sample(
extra,
options,
ctx,
&mut emitter,
&mut block,
block_id,
body_idx,
)?;
}
Op::ImageDrefGather => {
let extra = inst.expect_at_least(6)?;
let options = image::SamplingOptions {
compare: true,
project: false,
gather: 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();
let mut max_component = 0;
for _ in 5..inst.wc as usize {
let mut 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 {
use crate::SwizzleComponent as Sc;
let size = match self.temp_bytes.len() { 2 => crate::VectorSize::Bi, 3 => crate::VectorSize::Tri,
_ => crate::VectorSize::Quad,
};
let mut 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
let mut components = Vec::with_capacity(self.temp_bytes.len());
for index in self.temp_bytes.drain(..).map(|i| i as u32) {
let expr = if index < n1 {
crate::Expression::AccessIndex {
base: v1_handle,
index,
}
} else if 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()?;
let mut arguments = Vec::with_capacity(inst.wc as usize - 4);
for _ in 0..arguments.capacity() {
let arg_id = self.next()?;
let lexp = self.lookup_expression.lookup(arg_id)?;
arguments.push(get_expr_handle!(arg_id, lexp));
}
block.extend(emitter.finish(ctx.expressions));
// We just need an unique handle here, nothing more.
let function = self.add_call(ctx.function_id, func_id);
let result = if self.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 => {
use crate::MathFunction as Mf;
use spirv::GlslStd450Op 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_non_semantic_id {
for _ in 0..inst.wc - 4 {
self.next()?;
}
continue;
} else 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.
if let 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 _ in 4..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: ref mut 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;
}
if let Some(break_if_cond) = break_if_cond {
*break_if_slot = Some(break_if_cond);
// 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();
let mut 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.
if let 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();
let mut reject_block = Body::with_parent(body_idx);
if let Some(info) = false_target.merge_info {
merger(&mut reject_block, &info)
} else {
let prev = ctx.body_for_label.insert(false_target.label_id, reject);
debug_assert!(prev.is_none());
}
ctx.bodies.push(reject_block);
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`
if let 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 _ in 0..(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.
if let Some(&mut (_, ref mut literals)) = self.switch_cases.get_mut(&target)
{
literals.push(literal as i32);
continue;
}
let mut body = Body::with_parent(body_idx);
if let Some(info) = ctx.mergers.get(&target) {
merger(&mut body, info);
}
// 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.
let mut cases = Vec::with_capacity((inst.wc as usize - 3) / 2);
for &(case_body_idx, ref literals) in self.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 _ in 0..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 {
mode: crate::GatherMode::QuadSwap(direction),
result: result_handle,
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.
let crate::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)
};
emitter.start(ctx.expressions);
// 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,
},
);
}
// 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);
block.extend(emitter.finish(ctx.expressions));
if let Some(stmt) = terminator {
block.push(stmt, crate::Span::default());
}
// 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(())
}
}
let literal = match ctx.module.types[index_type].inner.scalar_kind() {
Some(crate::ScalarKind::Uint) => crate::Literal::U32(index),
Some(crate::ScalarKind::Sint) => crate::Literal::I32(index as i32),
_ => return Err(Error::InvalidIndexType(index_type_id)),
};
let expr = ctx
.expressions
.append(crate::Expression::Literal(literal), span);
emitter.start(ctx.expressions);
Ok(expr)
}
Messung V0.5 in Prozent
¤ 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.0.200Bemerkung:
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-08-26)
¤
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.