|
1 | 1 | use std::fmt;
|
2 | 2 |
|
3 |
| -use arrayvec::ArrayVec; |
4 |
| -use either::Either; |
5 | 3 | use rustc_abi as abi;
|
6 |
| -use rustc_abi::{Align, BackendRepr, FIRST_VARIANT, Primitive, Size, TagEncoding, Variants}; |
| 4 | +use rustc_abi::{ |
| 5 | + Align, BackendRepr, FIRST_VARIANT, FieldIdx, Primitive, Size, TagEncoding, VariantIdx, Variants, |
| 6 | +}; |
7 | 7 | use rustc_middle::mir::interpret::{Pointer, Scalar, alloc_range};
|
8 | 8 | use rustc_middle::mir::{self, ConstValue};
|
9 | 9 | use rustc_middle::ty::Ty;
|
10 | 10 | use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
|
11 | 11 | use rustc_middle::{bug, span_bug};
|
| 12 | +use rustc_session::config::OptLevel; |
12 | 13 | use tracing::{debug, instrument};
|
13 | 14 |
|
14 | 15 | use super::place::{PlaceRef, PlaceValue};
|
@@ -62,31 +63,6 @@ pub enum OperandValue<V> {
|
62 | 63 | }
|
63 | 64 |
|
64 | 65 | impl<V: CodegenObject> OperandValue<V> {
|
65 |
| - /// If this is ZeroSized/Immediate/Pair, return an array of the 0/1/2 values. |
66 |
| - /// If this is Ref, return the place. |
67 |
| - #[inline] |
68 |
| - pub(crate) fn immediates_or_place(self) -> Either<ArrayVec<V, 2>, PlaceValue<V>> { |
69 |
| - match self { |
70 |
| - OperandValue::ZeroSized => Either::Left(ArrayVec::new()), |
71 |
| - OperandValue::Immediate(a) => Either::Left(ArrayVec::from_iter([a])), |
72 |
| - OperandValue::Pair(a, b) => Either::Left([a, b].into()), |
73 |
| - OperandValue::Ref(p) => Either::Right(p), |
74 |
| - } |
75 |
| - } |
76 |
| - |
77 |
| - /// Given an array of 0/1/2 immediate values, return ZeroSized/Immediate/Pair. |
78 |
| - #[inline] |
79 |
| - pub(crate) fn from_immediates(immediates: ArrayVec<V, 2>) -> Self { |
80 |
| - let mut it = immediates.into_iter(); |
81 |
| - let Some(a) = it.next() else { |
82 |
| - return OperandValue::ZeroSized; |
83 |
| - }; |
84 |
| - let Some(b) = it.next() else { |
85 |
| - return OperandValue::Immediate(a); |
86 |
| - }; |
87 |
| - OperandValue::Pair(a, b) |
88 |
| - } |
89 |
| - |
90 | 66 | /// Treat this value as a pointer and return the data pointer and
|
91 | 67 | /// optional metadata as backend values.
|
92 | 68 | ///
|
@@ -559,6 +535,123 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
|
559 | 535 | }
|
560 | 536 | }
|
561 | 537 | }
|
| 538 | + |
| 539 | + pub(crate) fn builder(layout: TyAndLayout<'tcx>) -> OperandRef<'tcx, Result<V, abi::Scalar>> { |
| 540 | + let val = match layout.backend_repr { |
| 541 | + BackendRepr::Memory { .. } if layout.is_zst() => OperandValue::ZeroSized, |
| 542 | + BackendRepr::Scalar(s) => OperandValue::Immediate(Err(s)), |
| 543 | + BackendRepr::ScalarPair(a, b) => OperandValue::Pair(Err(a), Err(b)), |
| 544 | + _ => bug!("Cannot use type in operand builder: {layout:?}"), |
| 545 | + }; |
| 546 | + OperandRef { val, layout } |
| 547 | + } |
| 548 | + |
| 549 | + pub(crate) fn supports_builder(layout: TyAndLayout<'tcx>) -> bool { |
| 550 | + match layout.backend_repr { |
| 551 | + BackendRepr::Memory { .. } if layout.is_zst() => true, |
| 552 | + BackendRepr::Scalar(_) | BackendRepr::ScalarPair(_, _) => true, |
| 553 | + _ => false, |
| 554 | + } |
| 555 | + } |
| 556 | +} |
| 557 | + |
| 558 | +impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, Result<V, abi::Scalar>> { |
| 559 | + pub(crate) fn insert_field<Bx: BuilderMethods<'a, 'tcx, Value = V>>( |
| 560 | + &mut self, |
| 561 | + bx: &mut Bx, |
| 562 | + v: VariantIdx, |
| 563 | + f: FieldIdx, |
| 564 | + operand: OperandRef<'tcx, V>, |
| 565 | + ) { |
| 566 | + let (expect_zst, is_zero_offset) = if let abi::FieldsShape::Primitive = self.layout.fields { |
| 567 | + // Don't ask for field layout for primitives, because that will panic. |
| 568 | + if !self.layout.uninhabited { |
| 569 | + // Real primitives only have one variant, but weird types like |
| 570 | + // `Result<!, !>` turn out to also be "Primitive", and dead code |
| 571 | + // like `Err(never)` needs to not ICE. |
| 572 | + assert_eq!(v, FIRST_VARIANT); |
| 573 | + } |
| 574 | + let first_field = f == FieldIdx::ZERO; |
| 575 | + (self.layout.is_zst() || !first_field, first_field) |
| 576 | + } else { |
| 577 | + let variant_layout = self.layout.for_variant(bx.cx(), v); |
| 578 | + let field_layout = variant_layout.field(bx.cx(), f.as_usize()); |
| 579 | + let field_offset = variant_layout.fields.offset(f.as_usize()); |
| 580 | + (field_layout.is_zst(), field_offset == Size::ZERO) |
| 581 | + }; |
| 582 | + |
| 583 | + let mut update = |tgt: &mut Result<V, abi::Scalar>, src, from_scalar| { |
| 584 | + let from_bty = bx.cx().type_from_scalar(from_scalar); |
| 585 | + let to_scalar = tgt.unwrap_err(); |
| 586 | + let to_bty = bx.cx().type_from_scalar(to_scalar); |
| 587 | + let imm = transmute_immediate(bx, src, from_scalar, from_bty, to_scalar, to_bty); |
| 588 | + *tgt = Ok(imm); |
| 589 | + }; |
| 590 | + |
| 591 | + match (operand.val, operand.layout.backend_repr) { |
| 592 | + (OperandValue::ZeroSized, _) if expect_zst => {} |
| 593 | + (OperandValue::Immediate(v), BackendRepr::Scalar(from_scalar)) => match &mut self.val { |
| 594 | + OperandValue::Immediate(val @ Err(_)) if is_zero_offset => { |
| 595 | + update(val, v, from_scalar); |
| 596 | + } |
| 597 | + OperandValue::Pair(fst @ Err(_), _) if is_zero_offset => { |
| 598 | + update(fst, v, from_scalar); |
| 599 | + } |
| 600 | + OperandValue::Pair(_, snd @ Err(_)) if !is_zero_offset => { |
| 601 | + update(snd, v, from_scalar); |
| 602 | + } |
| 603 | + _ => bug!("Tried to insert {operand:?} into {v:?}.{f:?} of {self:?}"), |
| 604 | + }, |
| 605 | + (OperandValue::Pair(a, b), BackendRepr::ScalarPair(from_sa, from_sb)) => { |
| 606 | + match &mut self.val { |
| 607 | + OperandValue::Pair(fst @ Err(_), snd @ Err(_)) => { |
| 608 | + update(fst, a, from_sa); |
| 609 | + update(snd, b, from_sb); |
| 610 | + } |
| 611 | + _ => bug!("Tried to insert {operand:?} into {v:?}.{f:?} of {self:?}"), |
| 612 | + } |
| 613 | + } |
| 614 | + _ => bug!("Unsupported operand {operand:?} inserting into {v:?}.{f:?} of {self:?}"), |
| 615 | + } |
| 616 | + } |
| 617 | + |
| 618 | + pub(super) fn insert_imm(&mut self, f: FieldIdx, imm: V) { |
| 619 | + let field_offset = self.layout.fields.offset(f.as_usize()); |
| 620 | + let is_zero_offset = field_offset == Size::ZERO; |
| 621 | + match &mut self.val { |
| 622 | + OperandValue::Immediate(val @ Err(_)) if is_zero_offset => { |
| 623 | + *val = Ok(imm); |
| 624 | + } |
| 625 | + OperandValue::Pair(fst @ Err(_), _) if is_zero_offset => { |
| 626 | + *fst = Ok(imm); |
| 627 | + } |
| 628 | + OperandValue::Pair(_, snd @ Err(_)) if !is_zero_offset => { |
| 629 | + *snd = Ok(imm); |
| 630 | + } |
| 631 | + _ => bug!("Tried to insert {imm:?} into field {f:?} of {self:?}"), |
| 632 | + } |
| 633 | + } |
| 634 | + |
| 635 | + pub fn finalize(&self, cx: &impl CodegenMethods<'tcx, Value = V>) -> OperandRef<'tcx, V> { |
| 636 | + let OperandRef { val, layout } = *self; |
| 637 | + |
| 638 | + let unwrap = |r: Result<V, abi::Scalar>| match r { |
| 639 | + Ok(v) => v, |
| 640 | + Err(s) if s.is_uninit_valid() => { |
| 641 | + let bty = cx.type_from_scalar(s); |
| 642 | + cx.const_undef(bty) |
| 643 | + } |
| 644 | + Err(_) => bug!("OperandRef::finalize called while fields are missing {self:?}"), |
| 645 | + }; |
| 646 | + |
| 647 | + let val = match val { |
| 648 | + OperandValue::ZeroSized => OperandValue::ZeroSized, |
| 649 | + OperandValue::Immediate(v) => OperandValue::Immediate(unwrap(v)), |
| 650 | + OperandValue::Pair(a, b) => OperandValue::Pair(unwrap(a), unwrap(b)), |
| 651 | + OperandValue::Ref(_) => bug!(), |
| 652 | + }; |
| 653 | + OperandRef { val, layout } |
| 654 | + } |
562 | 655 | }
|
563 | 656 |
|
564 | 657 | impl<'a, 'tcx, V: CodegenObject> OperandValue<V> {
|
@@ -808,3 +901,93 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
808 | 901 | }
|
809 | 902 | }
|
810 | 903 | }
|
| 904 | + |
| 905 | +/// Transmutes one of the immediates from an [`OperandValue::Immediate`] |
| 906 | +/// or an [`OperandValue::Pair`] to an immediate of the target type. |
| 907 | +/// |
| 908 | +/// `to_backend_ty` must be the *non*-immediate backend type (so it will be |
| 909 | +/// `i8`, not `i1`, for `bool`-like types.) |
| 910 | +pub(super) fn transmute_immediate<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( |
| 911 | + bx: &mut Bx, |
| 912 | + mut imm: Bx::Value, |
| 913 | + from_scalar: abi::Scalar, |
| 914 | + from_backend_ty: Bx::Type, |
| 915 | + to_scalar: abi::Scalar, |
| 916 | + to_backend_ty: Bx::Type, |
| 917 | +) -> Bx::Value { |
| 918 | + assert_eq!(from_scalar.size(bx.cx()), to_scalar.size(bx.cx())); |
| 919 | + |
| 920 | + // While optimizations will remove no-op transmutes, they might still be |
| 921 | + // there in debug or things that aren't no-op in MIR because they change |
| 922 | + // the Rust type but not the underlying layout/niche. |
| 923 | + if from_scalar == to_scalar && from_backend_ty == to_backend_ty { |
| 924 | + return imm; |
| 925 | + } |
| 926 | + |
| 927 | + use abi::Primitive::*; |
| 928 | + imm = bx.from_immediate(imm); |
| 929 | + |
| 930 | + // If we have a scalar, we must already know its range. Either |
| 931 | + // |
| 932 | + // 1) It's a parameter with `range` parameter metadata, |
| 933 | + // 2) It's something we `load`ed with `!range` metadata, or |
| 934 | + // 3) After a transmute we `assume`d the range (see below). |
| 935 | + // |
| 936 | + // That said, last time we tried removing this, it didn't actually help |
| 937 | + // the rustc-perf results, so might as well keep doing it |
| 938 | + // <https://github.com/rust-lang/rust/pull/135610#issuecomment-2599275182> |
| 939 | + assume_scalar_range(bx, imm, from_scalar, from_backend_ty); |
| 940 | + |
| 941 | + imm = match (from_scalar.primitive(), to_scalar.primitive()) { |
| 942 | + (Int(..) | Float(_), Int(..) | Float(_)) => bx.bitcast(imm, to_backend_ty), |
| 943 | + (Pointer(..), Pointer(..)) => bx.pointercast(imm, to_backend_ty), |
| 944 | + (Int(..), Pointer(..)) => bx.ptradd(bx.const_null(bx.type_ptr()), imm), |
| 945 | + (Pointer(..), Int(..)) => { |
| 946 | + // FIXME: this exposes the provenance, which shouldn't be necessary. |
| 947 | + bx.ptrtoint(imm, to_backend_ty) |
| 948 | + } |
| 949 | + (Float(_), Pointer(..)) => { |
| 950 | + let int_imm = bx.bitcast(imm, bx.cx().type_isize()); |
| 951 | + bx.ptradd(bx.const_null(bx.type_ptr()), int_imm) |
| 952 | + } |
| 953 | + (Pointer(..), Float(_)) => { |
| 954 | + // FIXME: this exposes the provenance, which shouldn't be necessary. |
| 955 | + let int_imm = bx.ptrtoint(imm, bx.cx().type_isize()); |
| 956 | + bx.bitcast(int_imm, to_backend_ty) |
| 957 | + } |
| 958 | + }; |
| 959 | + |
| 960 | + // This `assume` remains important for cases like (a conceptual) |
| 961 | + // transmute::<u32, NonZeroU32>(x) == 0 |
| 962 | + // since it's never passed to something with parameter metadata (especially |
| 963 | + // after MIR inlining) so the only way to tell the backend about the |
| 964 | + // constraint that the `transmute` introduced is to `assume` it. |
| 965 | + assume_scalar_range(bx, imm, to_scalar, to_backend_ty); |
| 966 | + |
| 967 | + imm = bx.to_immediate_scalar(imm, to_scalar); |
| 968 | + imm |
| 969 | +} |
| 970 | + |
| 971 | +pub(super) fn assume_scalar_range<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( |
| 972 | + bx: &mut Bx, |
| 973 | + imm: Bx::Value, |
| 974 | + scalar: abi::Scalar, |
| 975 | + backend_ty: Bx::Type, |
| 976 | +) { |
| 977 | + if matches!(bx.cx().sess().opts.optimize, OptLevel::No) || scalar.is_always_valid(bx.cx()) { |
| 978 | + return; |
| 979 | + } |
| 980 | + |
| 981 | + match scalar.primitive() { |
| 982 | + abi::Primitive::Int(..) => { |
| 983 | + let range = scalar.valid_range(bx.cx()); |
| 984 | + bx.assume_integer_range(imm, backend_ty, range); |
| 985 | + } |
| 986 | + abi::Primitive::Pointer(abi::AddressSpace::DATA) |
| 987 | + if !scalar.valid_range(bx.cx()).contains(0) => |
| 988 | + { |
| 989 | + bx.assume_nonnull(imm); |
| 990 | + } |
| 991 | + abi::Primitive::Pointer(..) | abi::Primitive::Float(..) => {} |
| 992 | + } |
| 993 | +} |
0 commit comments