Rollup merge of #142383 - scottmcm:operandref-builder, r=workingjubilee
CodeGen: rework Aggregate implemention for rvalue_creates_operand cases
A non-trivial refactor pulled out from rust-lang/rust#138759
r? workingjubilee
The previous implementation I'd written here based on `index_by_increasing_offset` is complicated to follow and difficult to extend to non-structs.
This changes the implementation, without actually changing any codegen (thus no test changes either), to be more like the existing `extract_field` (<2b0274c71d/compiler/rustc_codegen_ssa/src/mir/operand.rs (L345-L425)>) in that it allows setting a particular field directly.
Notably I've found this one much easier to get right, in particular because having the `OperandRef<Result<V, Scalar>>` gives a really useful thing to include in ICE messages if something did happen to go wrong.
This commit is contained in:
@@ -3474,11 +3474,9 @@ name = "rustc_codegen_ssa"
|
|||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ar_archive_writer",
|
"ar_archive_writer",
|
||||||
"arrayvec",
|
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"bstr",
|
"bstr",
|
||||||
"cc",
|
"cc",
|
||||||
"either",
|
|
||||||
"itertools",
|
"itertools",
|
||||||
"libc",
|
"libc",
|
||||||
"object 0.37.0",
|
"object 0.37.0",
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
# tidy-alphabetical-start
|
# tidy-alphabetical-start
|
||||||
ar_archive_writer = "0.4.2"
|
ar_archive_writer = "0.4.2"
|
||||||
arrayvec = { version = "0.7", default-features = false }
|
|
||||||
bitflags = "2.4.1"
|
bitflags = "2.4.1"
|
||||||
bstr = "1.11.3"
|
bstr = "1.11.3"
|
||||||
# Pinned so `cargo update` bumps don't cause breakage. Please also update the
|
# Pinned so `cargo update` bumps don't cause breakage. Please also update the
|
||||||
# `cc` in `rustc_llvm` if you update the `cc` here.
|
# `cc` in `rustc_llvm` if you update the `cc` here.
|
||||||
cc = "=1.2.16"
|
cc = "=1.2.16"
|
||||||
either = "1.5.0"
|
|
||||||
itertools = "0.12"
|
itertools = "0.12"
|
||||||
pathdiff = "0.2.0"
|
pathdiff = "0.2.0"
|
||||||
regex = "1.4"
|
regex = "1.4"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use arrayvec::ArrayVec;
|
|
||||||
use either::Either;
|
|
||||||
use rustc_abi as abi;
|
use rustc_abi as abi;
|
||||||
use rustc_abi::{Align, BackendRepr, FIRST_VARIANT, Primitive, Size, TagEncoding, Variants};
|
use rustc_abi::{
|
||||||
|
Align, BackendRepr, FIRST_VARIANT, FieldIdx, Primitive, Size, TagEncoding, VariantIdx, Variants,
|
||||||
|
};
|
||||||
use rustc_middle::mir::interpret::{Pointer, Scalar, alloc_range};
|
use rustc_middle::mir::interpret::{Pointer, Scalar, alloc_range};
|
||||||
use rustc_middle::mir::{self, ConstValue};
|
use rustc_middle::mir::{self, ConstValue};
|
||||||
use rustc_middle::ty::Ty;
|
use rustc_middle::ty::Ty;
|
||||||
@@ -13,6 +13,7 @@ use rustc_session::config::OptLevel;
|
|||||||
use tracing::{debug, instrument};
|
use tracing::{debug, instrument};
|
||||||
|
|
||||||
use super::place::{PlaceRef, PlaceValue};
|
use super::place::{PlaceRef, PlaceValue};
|
||||||
|
use super::rvalue::transmute_immediate;
|
||||||
use super::{FunctionCx, LocalRef};
|
use super::{FunctionCx, LocalRef};
|
||||||
use crate::common::IntPredicate;
|
use crate::common::IntPredicate;
|
||||||
use crate::traits::*;
|
use crate::traits::*;
|
||||||
@@ -69,31 +70,6 @@ pub enum OperandValue<V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<V: CodegenObject> OperandValue<V> {
|
impl<V: CodegenObject> OperandValue<V> {
|
||||||
/// If this is ZeroSized/Immediate/Pair, return an array of the 0/1/2 values.
|
|
||||||
/// If this is Ref, return the place.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn immediates_or_place(self) -> Either<ArrayVec<V, 2>, PlaceValue<V>> {
|
|
||||||
match self {
|
|
||||||
OperandValue::ZeroSized => Either::Left(ArrayVec::new()),
|
|
||||||
OperandValue::Immediate(a) => Either::Left(ArrayVec::from_iter([a])),
|
|
||||||
OperandValue::Pair(a, b) => Either::Left([a, b].into()),
|
|
||||||
OperandValue::Ref(p) => Either::Right(p),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given an array of 0/1/2 immediate values, return ZeroSized/Immediate/Pair.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn from_immediates(immediates: ArrayVec<V, 2>) -> Self {
|
|
||||||
let mut it = immediates.into_iter();
|
|
||||||
let Some(a) = it.next() else {
|
|
||||||
return OperandValue::ZeroSized;
|
|
||||||
};
|
|
||||||
let Some(b) = it.next() else {
|
|
||||||
return OperandValue::Immediate(a);
|
|
||||||
};
|
|
||||||
OperandValue::Pair(a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Treat this value as a pointer and return the data pointer and
|
/// Treat this value as a pointer and return the data pointer and
|
||||||
/// optional metadata as backend values.
|
/// optional metadata as backend values.
|
||||||
///
|
///
|
||||||
@@ -595,6 +571,105 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an incomplete operand containing the [`abi::Scalar`]s expected based
|
||||||
|
/// on the `layout` passed. This is for use with [`OperandRef::insert_field`]
|
||||||
|
/// later to set the necessary immediate(s).
|
||||||
|
///
|
||||||
|
/// Returns `None` for `layout`s which cannot be built this way.
|
||||||
|
pub(crate) fn builder(
|
||||||
|
layout: TyAndLayout<'tcx>,
|
||||||
|
) -> Option<OperandRef<'tcx, Result<V, abi::Scalar>>> {
|
||||||
|
let val = match layout.backend_repr {
|
||||||
|
BackendRepr::Memory { .. } if layout.is_zst() => OperandValue::ZeroSized,
|
||||||
|
BackendRepr::Scalar(s) => OperandValue::Immediate(Err(s)),
|
||||||
|
BackendRepr::ScalarPair(a, b) => OperandValue::Pair(Err(a), Err(b)),
|
||||||
|
BackendRepr::Memory { .. } | BackendRepr::SimdVector { .. } => return None,
|
||||||
|
};
|
||||||
|
Some(OperandRef { val, layout })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, Result<V, abi::Scalar>> {
|
||||||
|
pub(crate) fn insert_field<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
|
||||||
|
&mut self,
|
||||||
|
bx: &mut Bx,
|
||||||
|
v: VariantIdx,
|
||||||
|
f: FieldIdx,
|
||||||
|
operand: OperandRef<'tcx, V>,
|
||||||
|
) {
|
||||||
|
let (expect_zst, is_zero_offset) = if let abi::FieldsShape::Primitive = self.layout.fields {
|
||||||
|
// The other branch looking at field layouts ICEs for primitives,
|
||||||
|
// so we need to handle them separately.
|
||||||
|
// Multiple fields is possible for cases such as aggregating
|
||||||
|
// a thin pointer, where the second field is the unit.
|
||||||
|
assert!(!self.layout.is_zst());
|
||||||
|
assert_eq!(v, FIRST_VARIANT);
|
||||||
|
let first_field = f == FieldIdx::ZERO;
|
||||||
|
(!first_field, first_field)
|
||||||
|
} else {
|
||||||
|
let variant_layout = self.layout.for_variant(bx.cx(), v);
|
||||||
|
let field_layout = variant_layout.field(bx.cx(), f.as_usize());
|
||||||
|
let field_offset = variant_layout.fields.offset(f.as_usize());
|
||||||
|
(field_layout.is_zst(), field_offset == Size::ZERO)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut update = |tgt: &mut Result<V, abi::Scalar>, src, from_scalar| {
|
||||||
|
let from_bty = bx.cx().type_from_scalar(from_scalar);
|
||||||
|
let to_scalar = tgt.unwrap_err();
|
||||||
|
let to_bty = bx.cx().type_from_scalar(to_scalar);
|
||||||
|
let imm = transmute_immediate(bx, src, from_scalar, from_bty, to_scalar, to_bty);
|
||||||
|
*tgt = Ok(imm);
|
||||||
|
};
|
||||||
|
|
||||||
|
match (operand.val, operand.layout.backend_repr) {
|
||||||
|
(OperandValue::ZeroSized, _) if expect_zst => {}
|
||||||
|
(OperandValue::Immediate(v), BackendRepr::Scalar(from_scalar)) => match &mut self.val {
|
||||||
|
OperandValue::Immediate(val @ Err(_)) if is_zero_offset => {
|
||||||
|
update(val, v, from_scalar);
|
||||||
|
}
|
||||||
|
OperandValue::Pair(fst @ Err(_), _) if is_zero_offset => {
|
||||||
|
update(fst, v, from_scalar);
|
||||||
|
}
|
||||||
|
OperandValue::Pair(_, snd @ Err(_)) if !is_zero_offset => {
|
||||||
|
update(snd, v, from_scalar);
|
||||||
|
}
|
||||||
|
_ => bug!("Tried to insert {operand:?} into {v:?}.{f:?} of {self:?}"),
|
||||||
|
},
|
||||||
|
(OperandValue::Pair(a, b), BackendRepr::ScalarPair(from_sa, from_sb)) => {
|
||||||
|
match &mut self.val {
|
||||||
|
OperandValue::Pair(fst @ Err(_), snd @ Err(_)) => {
|
||||||
|
update(fst, a, from_sa);
|
||||||
|
update(snd, b, from_sb);
|
||||||
|
}
|
||||||
|
_ => bug!("Tried to insert {operand:?} into {v:?}.{f:?} of {self:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => bug!("Unsupported operand {operand:?} inserting into {v:?}.{f:?} of {self:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// After having set all necessary fields, this converts the
|
||||||
|
/// `OperandValue<Result<V, _>>` (as obtained from [`OperandRef::builder`])
|
||||||
|
/// to the normal `OperandValue<V>`.
|
||||||
|
///
|
||||||
|
/// ICEs if any required fields were not set.
|
||||||
|
pub fn build(&self) -> OperandRef<'tcx, V> {
|
||||||
|
let OperandRef { val, layout } = *self;
|
||||||
|
|
||||||
|
let unwrap = |r: Result<V, abi::Scalar>| match r {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => bug!("OperandRef::build called while fields are missing {self:?}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let val = match val {
|
||||||
|
OperandValue::ZeroSized => OperandValue::ZeroSized,
|
||||||
|
OperandValue::Immediate(v) => OperandValue::Immediate(unwrap(v)),
|
||||||
|
OperandValue::Pair(a, b) => OperandValue::Pair(unwrap(a), unwrap(b)),
|
||||||
|
OperandValue::Ref(_) => bug!(),
|
||||||
|
};
|
||||||
|
OperandRef { val, layout }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx, V: CodegenObject> OperandValue<V> {
|
impl<'a, 'tcx, V: CodegenObject> OperandValue<V> {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::assert_matches::assert_matches;
|
use std::assert_matches::assert_matches;
|
||||||
|
|
||||||
use arrayvec::ArrayVec;
|
use rustc_abi::{self as abi, FIRST_VARIANT};
|
||||||
use rustc_abi::{self as abi, FIRST_VARIANT, FieldIdx};
|
|
||||||
use rustc_middle::ty::adjustment::PointerCoercion;
|
use rustc_middle::ty::adjustment::PointerCoercion;
|
||||||
use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutOf, TyAndLayout};
|
use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutOf, TyAndLayout};
|
||||||
use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
|
use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
|
||||||
@@ -708,38 +707,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||||||
|
|
||||||
// `rvalue_creates_operand` has arranged that we only get here if
|
// `rvalue_creates_operand` has arranged that we only get here if
|
||||||
// we can build the aggregate immediate from the field immediates.
|
// we can build the aggregate immediate from the field immediates.
|
||||||
let mut inputs = ArrayVec::<Bx::Value, 2>::new();
|
let Some(mut builder) = OperandRef::builder(layout) else {
|
||||||
let mut input_scalars = ArrayVec::<abi::Scalar, 2>::new();
|
bug!("Cannot use type in operand builder: {layout:?}")
|
||||||
for field_idx in layout.fields.index_by_increasing_offset() {
|
};
|
||||||
let field_idx = FieldIdx::from_usize(field_idx);
|
for (field_idx, field) in fields.iter_enumerated() {
|
||||||
let op = self.codegen_operand(bx, &fields[field_idx]);
|
let op = self.codegen_operand(bx, field);
|
||||||
let values = op.val.immediates_or_place().left_or_else(|p| {
|
builder.insert_field(bx, FIRST_VARIANT, field_idx, op);
|
||||||
bug!("Field {field_idx:?} is {p:?} making {layout:?}");
|
|
||||||
});
|
|
||||||
let scalars = self.value_kind(op.layout).scalars().unwrap();
|
|
||||||
assert_eq!(values.len(), scalars.len());
|
|
||||||
inputs.extend(values);
|
|
||||||
input_scalars.extend(scalars);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let output_scalars = self.value_kind(layout).scalars().unwrap();
|
builder.build()
|
||||||
itertools::izip!(&mut inputs, input_scalars, output_scalars).for_each(
|
|
||||||
|(v, in_s, out_s)| {
|
|
||||||
if in_s != out_s {
|
|
||||||
// We have to be really careful about bool here, because
|
|
||||||
// `(bool,)` stays i1 but `Cell<bool>` becomes i8.
|
|
||||||
*v = bx.from_immediate(*v);
|
|
||||||
*v = bx.to_immediate_scalar(*v, out_s);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let val = OperandValue::from_immediates(inputs);
|
|
||||||
assert!(
|
|
||||||
val.is_expected_variant_for_type(self.cx, layout),
|
|
||||||
"Made wrong variant {val:?} for type {layout:?}",
|
|
||||||
);
|
|
||||||
OperandRef { val, layout }
|
|
||||||
}
|
}
|
||||||
mir::Rvalue::ShallowInitBox(ref operand, content_ty) => {
|
mir::Rvalue::ShallowInitBox(ref operand, content_ty) => {
|
||||||
let operand = self.codegen_operand(bx, operand);
|
let operand = self.codegen_operand(bx, operand);
|
||||||
@@ -1085,7 +1061,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||||||
let ty = rvalue.ty(self.mir, self.cx.tcx());
|
let ty = rvalue.ty(self.mir, self.cx.tcx());
|
||||||
let ty = self.monomorphize(ty);
|
let ty = self.monomorphize(ty);
|
||||||
let layout = self.cx.spanned_layout_of(ty, span);
|
let layout = self.cx.spanned_layout_of(ty, span);
|
||||||
!self.cx.is_backend_ref(layout)
|
OperandRef::<Bx::Value>::builder(layout).is_some()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1129,23 +1105,12 @@ enum OperandValueKind {
|
|||||||
ZeroSized,
|
ZeroSized,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OperandValueKind {
|
|
||||||
fn scalars(self) -> Option<ArrayVec<abi::Scalar, 2>> {
|
|
||||||
Some(match self {
|
|
||||||
OperandValueKind::ZeroSized => ArrayVec::new(),
|
|
||||||
OperandValueKind::Immediate(a) => ArrayVec::from_iter([a]),
|
|
||||||
OperandValueKind::Pair(a, b) => [a, b].into(),
|
|
||||||
OperandValueKind::Ref => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transmutes one of the immediates from an [`OperandValue::Immediate`]
|
/// Transmutes one of the immediates from an [`OperandValue::Immediate`]
|
||||||
/// or an [`OperandValue::Pair`] to an immediate of the target type.
|
/// or an [`OperandValue::Pair`] to an immediate of the target type.
|
||||||
///
|
///
|
||||||
/// `to_backend_ty` must be the *non*-immediate backend type (so it will be
|
/// `to_backend_ty` must be the *non*-immediate backend type (so it will be
|
||||||
/// `i8`, not `i1`, for `bool`-like types.)
|
/// `i8`, not `i1`, for `bool`-like types.)
|
||||||
fn transmute_immediate<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
|
pub(super) fn transmute_immediate<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
|
||||||
bx: &mut Bx,
|
bx: &mut Bx,
|
||||||
mut imm: Bx::Value,
|
mut imm: Bx::Value,
|
||||||
from_scalar: abi::Scalar,
|
from_scalar: abi::Scalar,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use rustc_abi::{AddressSpace, Float, Integer, Reg};
|
use rustc_abi::{AddressSpace, Float, Integer, Primitive, Reg, Scalar};
|
||||||
use rustc_middle::bug;
|
use rustc_middle::bug;
|
||||||
use rustc_middle::ty::Ty;
|
use rustc_middle::ty::Ty;
|
||||||
use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, TyAndLayout};
|
use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, TyAndLayout};
|
||||||
@@ -84,6 +84,24 @@ pub trait DerivedTypeCodegenMethods<'tcx>:
|
|||||||
fn type_is_freeze(&self, ty: Ty<'tcx>) -> bool {
|
fn type_is_freeze(&self, ty: Ty<'tcx>) -> bool {
|
||||||
ty.is_freeze(self.tcx(), self.typing_env())
|
ty.is_freeze(self.tcx(), self.typing_env())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn type_from_primitive(&self, p: Primitive) -> Self::Type {
|
||||||
|
use Primitive::*;
|
||||||
|
match p {
|
||||||
|
Int(i, _) => self.type_from_integer(i),
|
||||||
|
Float(f) => self.type_from_float(f),
|
||||||
|
Pointer(address_space) => self.type_ptr_ext(address_space),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_from_scalar(&self, s: Scalar) -> Self::Type {
|
||||||
|
// `MaybeUninit` being `repr(transparent)` somewhat implies that the type
|
||||||
|
// of a scalar has to be the type of its primitive (which is true in LLVM,
|
||||||
|
// where noundef is a parameter attribute or metadata) but if we ever get
|
||||||
|
// a backend where that's no longer true, every use of this will need to
|
||||||
|
// to carefully scrutinized and re-evaluated.
|
||||||
|
self.type_from_primitive(s.primitive())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx, T> DerivedTypeCodegenMethods<'tcx> for T where
|
impl<'tcx, T> DerivedTypeCodegenMethods<'tcx> for T where
|
||||||
|
|||||||
Reference in New Issue
Block a user