Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
22a926f
Add `--dump-spirt` for dumping only the final SPIR-T module (unlike `…
eddyb Oct 30, 2025
bf3a0ee
builder: use SPIR-V instructions for `checked_{add,sub,mul}` and `sat…
eddyb Oct 28, 2025
583666b
builder: implement pointer atomics (missing in SPIR-V) via integers.
eddyb Oct 13, 2025
3c3a118
Revert "linker/inline: use `OpPhi` instead of `OpVariable` for return…
eddyb Sep 9, 2025
8ae4bae
Revert "WIP: mem2reg speedup"
eddyb Sep 9, 2025
2ce3487
Revert "WIP: couple of inliner things that need to be disentangled"
eddyb Sep 9, 2025
f4d690b
Revert "WIP: (TODO: finish bottom-up cleanups) bottom-up inlining"
eddyb Sep 9, 2025
e796516
Revert "linker/inline: fix `OpVariable` debuginfo collection and inse…
eddyb Sep 9, 2025
95ee5bc
[2024] linker/inline: fix `OpVariable` debuginfo collection and inser…
eddyb Sep 9, 2025
fdd29c1
[2024] linker/inline: use bottom-up inlining to minimize redundancy.
eddyb Sep 9, 2025
fe22a3b
[2024] linker/mem2reg: index SPIR-V blocks by their label IDs for O(1…
eddyb Sep 9, 2025
5db1f6b
[2024] linker/inline: use `OpPhi` instead of `OpVariable` for return …
eddyb Sep 9, 2025
1e44474
linker/inline: fix typos in comments.
eddyb Sep 9, 2025
c08b579
[TODO(eddyb) this is probably sound but requires looping for rewrites…
eddyb Sep 9, 2025
4d10050
linker/inline: also run `remove_duplicate_debuginfo` on every fully-i…
eddyb Sep 9, 2025
5040aa2
linker/inline: also run `mem2reg` on every fully-inlined function.
eddyb Sep 9, 2025
d02aa41
(placeholder)
eddyb Sep 9, 2025
6f54c86
linker/inline: don't assume `OpFunctionParameter`s are valid pointers…
eddyb Sep 9, 2025
696080e
[TODO(eddyb) recursion needs this!!!] [HACK(eddyb) this works now but…
eddyb Sep 9, 2025
0546ebd
Replace `SpirvValueKind::IllegalTypeUsed` with mere `undef`.
eddyb Sep 9, 2025
a9d4494
Always register zombie messages, only at most defer their `Span`s.
eddyb Sep 9, 2025
ac5c2d8
Remove `SpirvValueKind::IllegalConst`.
eddyb Sep 9, 2025
9f04dde
Reduce `SpirvValue` lossiness around `strip_ptrcasts` and `const_fold…
eddyb Sep 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions crates/rustc_codegen_spirv/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -616,15 +616,6 @@ fn trans_aggregate<'tcx>(cx: &CodegenCx<'tcx>, span: Span, ty: TyAndLayout<'tcx>
}
}

#[cfg_attr(
not(rustc_codegen_spirv_disable_pqp_cg_ssa),
expect(
unused,
reason = "actually used from \
`<rustc_codegen_ssa::traits::ConstCodegenMethods for CodegenCx<'_>>::const_struct`, \
but `rustc_codegen_ssa` being `pqp_cg_ssa` makes that trait unexported"
)
)]
// returns (field_offsets, size, align)
pub fn auto_struct_layout(
cx: &CodegenCx<'_>,
Expand Down
206 changes: 143 additions & 63 deletions crates/rustc_codegen_spirv/src/builder/builder_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use rustc_codegen_ssa::traits::{
};
use rustc_middle::bug;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::{self, AtomicOrdering, Ty};
use rustc_span::Span;
use rustc_target::callconv::FnAbi;
Expand Down Expand Up @@ -1721,30 +1722,15 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
fn checked_binop(
&mut self,
oop: OverflowOp,
ty: Ty<'_>,
ty: Ty<'tcx>,
lhs: Self::Value,
rhs: Self::Value,
) -> (Self::Value, Self::Value) {
// adopted partially from https://github.com/ziglang/zig/blob/master/src/codegen/spirv.zig
let is_add = match oop {
OverflowOp::Add => true,
OverflowOp::Sub => false,
OverflowOp::Mul => {
// NOTE(eddyb) this needs to be `undef`, not `false`/`true`, because
// we don't want the user's boolean constants to keep the zombie alive.
let bool = SpirvType::Bool.def(self.span(), self);
let overflowed = self.undef(bool);

let result = (self.mul(lhs, rhs), overflowed);
self.zombie(result.1.def(self), "checked mul is not supported yet");
return result;
}
};
let signed = match ty.kind() {
ty::Int(_) => true,
ty::Uint(_) => false,
other => self.fatal(format!(
"Unexpected {} type: {other:#?}",
_ => self.fatal(format!(
"unexpected {} type: {ty}",
match oop {
OverflowOp::Add => "checked add",
OverflowOp::Sub => "checked sub",
Expand All @@ -1753,13 +1739,17 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
)),
};

let result = if is_add {
self.add(lhs, rhs)
} else {
self.sub(lhs, rhs)
};
// HACK(eddyb) SPIR-V `OpIAddCarry`/`OpISubBorrow` are specifically for
// unsigned overflow, so signed overflow still needs this custom logic.
if signed && let OverflowOp::Add | OverflowOp::Sub = oop {
let result = match oop {
OverflowOp::Add => self.add(lhs, rhs),
OverflowOp::Sub => self.sub(lhs, rhs),
OverflowOp::Mul => unreachable!(),
};

// adopted partially from https://github.com/ziglang/zig/blob/master/src/codegen/spirv.zig

let overflowed = if signed {
// when adding, overflow could happen if
// - rhs is positive and result < lhs; or
// - rhs is negative and result > lhs
Expand All @@ -1771,30 +1761,80 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
// this is equivalent to (rhs < 0) == (result < lhs)
let rhs_lt_zero = self.icmp(IntPredicate::IntSLT, rhs, self.constant_int(rhs.ty, 0));
let result_gt_lhs = self.icmp(
if is_add {
IntPredicate::IntSGT
} else {
IntPredicate::IntSLT
match oop {
OverflowOp::Add => IntPredicate::IntSGT,
OverflowOp::Sub => IntPredicate::IntSLT,
OverflowOp::Mul => unreachable!(),
},
result,
lhs,
);
self.icmp(IntPredicate::IntEQ, rhs_lt_zero, result_gt_lhs)
} else {
// for unsigned addition, overflow occurred if the result is less than any of the operands.
// for subtraction, overflow occurred if the result is greater.
self.icmp(
if is_add {
IntPredicate::IntULT

let overflowed = self.icmp(IntPredicate::IntEQ, rhs_lt_zero, result_gt_lhs);

return (result, overflowed);
}

let result_type = self.layout_of(ty).spirv_type(self.span(), self);
let pair_result_type = {
let field_types = [result_type, result_type];
let (field_offsets, size, align) = crate::abi::auto_struct_layout(self, &field_types);
SpirvType::Adt {
def_id: None,
size,
align,
field_types: &field_types,
field_offsets: &field_offsets,
field_names: None,
}
.def(self.span(), self)
};

let lhs = lhs.def(self);
let rhs = rhs.def(self);
let pair_result = match oop {
OverflowOp::Add => self
.emit()
.i_add_carry(pair_result_type, None, lhs, rhs)
.unwrap(),
OverflowOp::Sub => self
.emit()
.i_sub_borrow(pair_result_type, None, lhs, rhs)
.unwrap(),
OverflowOp::Mul => {
if signed {
self.emit()
.s_mul_extended(pair_result_type, None, lhs, rhs)
.unwrap()
} else {
IntPredicate::IntUGT
},
result,
lhs,
)
self.emit()
.u_mul_extended(pair_result_type, None, lhs, rhs)
.unwrap()
}
}
}
.with_type(pair_result_type);
let result_lo = self.extract_value(pair_result, 0);
let result_hi = self.extract_value(pair_result, 1);

// HACK(eddyb) SPIR-V lacks any `(T, T) -> (T, bool)` instructions,
// so instead `result_hi` is compared with the value expected in the
// non-overflow case (`0`, or `-1` for negative signed multiply result).
let expected_nonoverflowing_hi = match (oop, signed) {
(OverflowOp::Add | OverflowOp::Sub, _) | (OverflowOp::Mul, false) => {
self.const_uint(result_type, 0)
}
(OverflowOp::Mul, true) => {
// HACK(eddyb) `(x: iN) >> (N - 1)` will spread the sign bit
// across all `N` bits of `iN`, and should be equivalent to
// `if x < 0 { -1 } else { 0 }`, without needing compare+select).
let result_width = u32::try_from(self.int_width(result_type)).unwrap();
self.ashr(result_lo, self.const_u32(result_width - 1))
}
};
let overflowed = self.icmp(IntPredicate::IntNE, result_hi, expected_nonoverflowing_hi);

(result, overflowed)
(result_lo, overflowed)
}

// rustc has the concept of an immediate vs. memory type - bools are compiled to LLVM bools as
Expand Down Expand Up @@ -1854,7 +1894,12 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
order: AtomicOrdering,
_size: Size,
) -> Self::Value {
let (ptr, access_ty) = self.adjust_pointer_for_typed_access(ptr, ty);
// HACK(eddyb) SPIR-V lacks pointer atomics, have to use integers instead.
let atomic_ty = match self.lookup_type(ty) {
SpirvType::Pointer { .. } => self.type_usize(),
_ => ty,
};
let (ptr, access_ty) = self.adjust_pointer_for_typed_access(ptr, atomic_ty);

// TODO: Default to device scope
let memory = self.constant_u32(self.span(), Scope::Device as u32);
Expand Down Expand Up @@ -1991,7 +2036,12 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
order: AtomicOrdering,
_size: Size,
) {
let (ptr, access_ty) = self.adjust_pointer_for_typed_access(ptr, val.ty);
// HACK(eddyb) SPIR-V lacks pointer atomics, have to use integers instead.
let atomic_ty = match self.lookup_type(val.ty) {
SpirvType::Pointer { .. } => self.type_usize(),
_ => val.ty,
};
let (ptr, access_ty) = self.adjust_pointer_for_typed_access(ptr, atomic_ty);
let val = self.bitcast(val, access_ty);

// TODO: Default to device scope
Expand Down Expand Up @@ -2390,13 +2440,6 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {

#[instrument(level = "trace", skip(self), fields(ptr, ptr_ty = ?self.debug_type(ptr.ty), dest_ty = ?self.debug_type(dest_ty)))]
fn pointercast(&mut self, ptr: Self::Value, dest_ty: Self::Type) -> Self::Value {
// HACK(eddyb) reuse the special-casing in `const_bitcast`, which relies
// on adding a pointer type to an untyped pointer (to some const data).
if let SpirvValueKind::IllegalConst(_) = ptr.kind {
trace!("illegal const");
return self.const_bitcast(ptr, dest_ty);
}

if ptr.ty == dest_ty {
trace!("ptr.ty == dest_ty");
return ptr;
Expand Down Expand Up @@ -2455,13 +2498,43 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
self.debug_type(ptr_pointee),
self.debug_type(dest_pointee),
);

// HACK(eddyb) reuse the special-casing in `const_bitcast`, which relies
// on adding a pointer type to an untyped pointer (to some const data).
if self.builder.lookup_const(ptr).is_some() {
// FIXME(eddyb) remove the condition on `zombie_waiting_for_span`,
// and constant-fold all pointer bitcasts, regardless of "legality",
// once `strip_ptrcasts` can undo `const_bitcast`, as well.
if ptr.zombie_waiting_for_span {
trace!("illegal const");
return self.const_bitcast(ptr, dest_ty);
}
}

// Defer the cast so that it has a chance to be avoided.
let original_ptr = ptr.def(self);
let ptr_id = ptr.def(self);
let bitcast_result_id = self.emit().bitcast(dest_ty, None, ptr_id).unwrap();

self.zombie(
bitcast_result_id,
&format!(
"cannot cast between pointer types\
\nfrom `{}`\
\n to `{}`",
self.debug_type(ptr.ty),
self.debug_type(dest_ty)
),
);

SpirvValue {
kind: SpirvValueKind::LogicalPtrCast {
original_ptr,
original_ptr_ty: ptr.ty,
bitcast_result_id: self.emit().bitcast(dest_ty, None, original_ptr).unwrap(),
zombie_waiting_for_span: false,
kind: SpirvValueKind::Def {
id: bitcast_result_id,
original_ptr_before_casts: Some(SpirvValue {
zombie_waiting_for_span: ptr.zombie_waiting_for_span,
kind: ptr_id,
ty: ptr.ty,
}),
},
ty: dest_ty,
}
Expand Down Expand Up @@ -3091,7 +3164,12 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
assert_ty_eq!(self, cmp.ty, src.ty);
let ty = src.ty;

let (dst, access_ty) = self.adjust_pointer_for_typed_access(dst, ty);
// HACK(eddyb) SPIR-V lacks pointer atomics, have to use integers instead.
let atomic_ty = match self.lookup_type(ty) {
SpirvType::Pointer { .. } => self.type_usize(),
_ => ty,
};
let (dst, access_ty) = self.adjust_pointer_for_typed_access(dst, atomic_ty);
let cmp = self.bitcast(cmp, access_ty);
let src = self.bitcast(src, access_ty);

Expand All @@ -3117,7 +3195,7 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
.with_type(access_ty);

let val = self.bitcast(result, ty);
let success = self.icmp(IntPredicate::IntEQ, val, cmp);
let success = self.icmp(IntPredicate::IntEQ, result, cmp);

(val, success)
}
Expand All @@ -3131,7 +3209,12 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
) -> Self::Value {
let ty = src.ty;

let (dst, access_ty) = self.adjust_pointer_for_typed_access(dst, ty);
// HACK(eddyb) SPIR-V lacks pointer atomics, have to use integers instead.
let atomic_ty = match self.lookup_type(ty) {
SpirvType::Pointer { .. } => self.type_usize(),
_ => ty,
};
let (dst, access_ty) = self.adjust_pointer_for_typed_access(dst, atomic_ty);
let src = self.bitcast(src, access_ty);

self.validate_atomic(access_ty, dst.def(self));
Expand Down Expand Up @@ -3279,7 +3362,7 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
return_type,
arguments,
} => (
if let SpirvValueKind::FnAddr { function } = callee.kind {
if let SpirvValueKind::FnAddr { function, .. } = callee.kind {
assert_ty_eq!(self, callee_ty, pointee);
function
}
Expand Down Expand Up @@ -3351,10 +3434,7 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
if buffer_store_intrinsic {
self.codegen_buffer_store_intrinsic(fn_abi, args);
let void_ty = SpirvType::Void.def(rustc_span::DUMMY_SP, self);
return SpirvValue {
kind: SpirvValueKind::IllegalTypeUsed(void_ty),
ty: void_ty,
};
return self.undef(void_ty);
}

if let Some((source_ty, target_ty)) = from_trait_impl {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use crate::maybe_pqp_cg_ssa as rustc_codegen_ssa;

use super::Builder;
use crate::builder_spirv::{SpirvValue, SpirvValueExt, SpirvValueKind};
use crate::builder_spirv::{SpirvValue, SpirvValueExt};
use crate::spirv_type::SpirvType;
use rspirv::spirv::{Decoration, Word};
use rustc_abi::{Align, Size};
Expand Down Expand Up @@ -188,12 +188,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
) -> SpirvValue {
let pass_mode = &fn_abi.unwrap().ret.mode;
match pass_mode {
PassMode::Ignore => {
return SpirvValue {
kind: SpirvValueKind::IllegalTypeUsed(result_type),
ty: result_type,
};
}
PassMode::Ignore => return self.undef(result_type),

// PassMode::Pair is identical to PassMode::Direct - it's returned as a struct
PassMode::Direct(_) | PassMode::Pair(_, _) => (),
PassMode::Cast { .. } => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ impl<'tcx> DecodedFormatArgs<'tcx> {
// HACK(eddyb) some entry-points only take a `&str`, not `fmt::Arguments`.
if let [
SpirvValue {
kind: SpirvValueKind::Def(a_id),
kind: SpirvValueKind::Def { id: a_id, .. },
..
},
SpirvValue {
kind: SpirvValueKind::Def(b_id),
kind: SpirvValueKind::Def { id: b_id, .. },
..
},
ref other_args @ ..,
Expand All @@ -116,14 +116,20 @@ impl<'tcx> DecodedFormatArgs<'tcx> {
// HACK(eddyb) `panic_nounwind_fmt` takes an extra argument.
[
SpirvValue {
kind: SpirvValueKind::Def(format_args_id),
kind:
SpirvValueKind::Def {
id: format_args_id, ..
},
..
},
_, // `&'static panic::Location<'static>`
]
| [
SpirvValue {
kind: SpirvValueKind::Def(format_args_id),
kind:
SpirvValueKind::Def {
id: format_args_id, ..
},
..
},
_, // `force_no_backtrace: bool`
Expand Down
Loading