diff --git a/src/librustc_trans/adt.rs b/src/librustc_trans/adt.rs index 2fb7a69d3618..f11378c3140a 100644 --- a/src/librustc_trans/adt.rs +++ b/src/librustc_trans/adt.rs @@ -82,25 +82,37 @@ pub enum Repr<'tcx> { /// General-case enums: for each case there is a struct, and they /// all start with a field for the discriminant. General(IntType, Vec>), - /// Two cases distinguished by a nullable pointer: the case with discriminant - /// `nndiscr` must have single field which is known to be nonnull due to its type. - /// The other case is known to be zero sized. Hence we represent the enum - /// as simply a nullable pointer: if not null it indicates the `nndiscr` variant, - /// otherwise it indicates the other case. - RawNullablePointer { - nndiscr: Disr, - nnty: Ty<'tcx>, - nullfields: Vec> + /// Two cases distinguished by a known-to-be-forbidden value. + /// + /// Example: `Option<&T>` (a `&T` cannot be null) + /// Example: `Option` (a `char` is large enough to hold 2^32 - 1, + /// but this value is forbidden by definition) + /// Example: `Result<&T, ()>` (a `&T` cannot be null) + /// + /// One of the cases (the "unit case") must be known to be + /// zero-sized (e.g. `None`). The other case (the "payload case") + /// must be known to be a single field that cannot adopt a + /// specific value (in the above examples, 0 for `&T` or 2^32 - 1 + /// for `char`). + /// + /// We may safely represent the enum by its payload case and + /// differentiate between cases by checking for the forbidden + /// value. + RawForbiddenValue { + /// Unit case (e.g. `None` or `Either((), ())`) + unit_fields: Vec>, + /// Case holding a payload: the constructor + payload_discr: Disr, + /// Case holding a payload: the type + payload_ty: Ty<'tcx>, + /// A value that the payload can never hold. + forbidden_value: ValueRef, }, /// Two cases distinguished by a nullable pointer: the case with discriminant /// `nndiscr` is represented by the struct `nonnull`, where the `discrfield`th /// field is known to be nonnull due to its type; if that field is null, then /// it represents the other case, which is inhabited by at most one value /// (and all other fields are undefined/unused). - /// - /// For example, `std::option::Option` instantiated at a safe pointer type - /// is represented such that `None` is a null pointer and `Some` is the - /// identity function. StructWrappedNullablePointer { nonnull: Struct<'tcx>, nndiscr: Disr, @@ -217,34 +229,50 @@ fn represent_type_uncached<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, } if cases.len() == 2 && hint == attr::ReprAny { - // Nullable pointer optimization - let mut discr = 0; - while discr < 2 { + // Two cases, so it might be possible to turn this + // into a `RawForbiddenValue` or a + // `StructWrappedNullablePointer`, if all conditions + // are met. + for discr in 0 .. 2 { if cases[1 - discr].is_zerolen(cx, t) { + // One of the cases has zero length. We are on the right track. let st = mk_struct(cx, &cases[discr].tys, false, t); - match cases[discr].find_ptr(cx) { - Some(ref df) if df.len() == 1 && st.fields.len() == 1 => { - return RawNullablePointer { - nndiscr: Disr::from(discr), - nnty: st.fields[0], - nullfields: cases[1 - discr].tys.clone() + + // For the moment, we can only apply these + // optimizations to safe pointers. + match cases[discr].find_forbidden_value(cx) { + Some((ref df, forbidden_value)) + if df.len() == 1 && st.fields.len() == 1 => { + let payload_ty = st.fields[0]; + return RawForbiddenValue { + payload_discr: Disr::from(discr), + payload_ty: payload_ty, + forbidden_value: forbidden_value, + unit_fields: cases[1 - discr].tys.clone() }; } - Some(mut discrfield) => { - discrfield.push(0); - discrfield.reverse(); - return StructWrappedNullablePointer { - nndiscr: Disr::from(discr), - nonnull: st, - discrfield: discrfield, - nullfields: cases[1 - discr].tys.clone() - }; + Some((mut discrfield, forbidden)) => { + if is_null(forbidden) { + discrfield.push(0); + discrfield.reverse(); + return StructWrappedNullablePointer { + nndiscr: Disr::from(discr), + nonnull: st, + discrfield: discrfield, + nullfields: cases[1 - discr].tys.clone() + }; + } } None => {} } + // No need to continue the loop. If both cases + // have zero length, we can apply neither + // `RawForbiddenValue` nor + // `StructWrappedNullablePointer`. + break; + } - discr += 1; } } @@ -340,24 +368,34 @@ struct Case<'tcx> { /// This represents the (GEP) indices to follow to get to the discriminant field pub type DiscrField = Vec; -fn find_discr_field_candidate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, +fn find_discr_field_candidate<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, + tcx: TyCtxt<'a, 'tcx, 'tcx>, ty: Ty<'tcx>, mut path: DiscrField) - -> Option { + -> Option<(DiscrField, ValueRef)> { match ty.sty { - // Fat &T/&mut T/Box i.e. T is [T], str, or Trait + // Fat &T/&mut T/Box i.e. T is [T], str, or Trait; null is forbidden ty::TyRef(_, ty::TypeAndMut { ty, .. }) | ty::TyBox(ty) if !type_is_sized(tcx, ty) => { path.push(FAT_PTR_ADDR); - Some(path) + Some((path, C_null(type_of::sizing_type_of(cx, ty)))) }, - // Regular thin pointer: &T/&mut T/Box - ty::TyRef(..) | ty::TyBox(..) => Some(path), + // Regular thin pointer: &T/&mut T/Box; null is forbidden + ty::TyRef(..) | ty::TyBox(..) => Some((path, C_null(type_of::sizing_type_of(cx, ty)))), + + // Function pointer: `fn() -> i32`; null is forbidden + ty::TyFnPtr(_) => Some((path, C_null(type_of::sizing_type_of(cx, ty)))), - // Function pointer: `fn() -> i32` - ty::TyFnPtr(_) => Some(path), + // Is this a char or a bool? If so, std::uXX:MAX is forbidden. + ty::TyChar => Some((path, + C_integral(type_of::sizing_type_of(cx, ty), + std::u32::MAX as u64, false))), + ty::TyBool => Some((path, + C_integral(type_of::sizing_type_of(cx, ty), + std::u8::MAX as u64, false))), // Is this the NonZero lang item wrapping a pointer or integer type? + // If so, null is forbidden. ty::TyStruct(def, substs) if Some(def.did) == tcx.lang_items.non_zero() => { let nonzero_fields = &def.struct_variant().fields; assert_eq!(nonzero_fields.len(), 1); @@ -365,36 +403,38 @@ fn find_discr_field_candidate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, match field_ty.sty { ty::TyRawPtr(ty::TypeAndMut { ty, .. }) if !type_is_sized(tcx, ty) => { path.extend_from_slice(&[0, FAT_PTR_ADDR]); - Some(path) + Some((path, C_null(Type::i8p(cx)))) }, ty::TyRawPtr(..) | ty::TyInt(..) | ty::TyUint(..) => { path.push(0); - Some(path) + Some((path, C_null(type_of::sizing_type_of(cx, field_ty)))) }, _ => None } }, - // Perhaps one of the fields of this struct is non-zero + // Perhaps one of the fields of this struct has a forbidden value. // let's recurse and find out ty::TyStruct(def, substs) => { for (j, field) in def.struct_variant().fields.iter().enumerate() { let field_ty = monomorphize::field_ty(tcx, substs, field); - if let Some(mut fpath) = find_discr_field_candidate(tcx, field_ty, path.clone()) { + if let Some((mut fpath, forbidden)) = + find_discr_field_candidate(cx, field_ty, path.clone()) { fpath.push(j); - return Some(fpath); + return Some((fpath, forbidden)); } } None }, - // Perhaps one of the upvars of this struct is non-zero + // Perhaps one of the upvars of this struct has a forbidden value. // Let's recurse and find out! ty::TyClosure(_, ref substs) => { for (j, &ty) in substs.upvar_tys.iter().enumerate() { - if let Some(mut fpath) = find_discr_field_candidate(tcx, ty, path.clone()) { + if let Some((mut fpath, forbidden)) = + find_discr_field_candidate(cx, ty, path.clone()) { fpath.push(j); - return Some(fpath); + return Some((fpath, forbidden)); } } None @@ -403,26 +443,27 @@ fn find_discr_field_candidate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, // Can we use one of the fields in this tuple? ty::TyTuple(ref tys) => { for (j, &ty) in tys.iter().enumerate() { - if let Some(mut fpath) = find_discr_field_candidate(tcx, ty, path.clone()) { + if let Some((mut fpath, forbidden)) = + find_discr_field_candidate(cx, ty, path.clone()) { fpath.push(j); - return Some(fpath); + return Some((fpath, forbidden)); } } None }, - // Is this a fixed-size array of something non-zero + // Is this a fixed-size array of something with a forbidden value // with at least one element? ty::TyArray(ety, d) if d > 0 => { - if let Some(mut vpath) = find_discr_field_candidate(tcx, ety, path) { + if let Some((mut vpath, forbidden)) = find_discr_field_candidate(cx, ety, path) { vpath.push(0); - Some(vpath) + Some((vpath, forbidden)) } else { None } }, - // Anything else is not a pointer + // Anything else doesn't have a known-to-be-safe forbidden value. _ => None } } @@ -432,11 +473,22 @@ impl<'tcx> Case<'tcx> { mk_struct(cx, &self.tys, false, scapegoat).size == 0 } - fn find_ptr<'a>(&self, cx: &CrateContext<'a, 'tcx>) -> Option { + /// Find a forbidden value that may be used to discriminate in a + /// RawForbiddenValue or StructWrappedNullablePointer. + /// + /// Example: In `Option<&T>`, since `&T` has a forbidden value 0, + /// this method will return the path to `&T`, with a value of 0. + /// + /// Example: In `Option<(u64, char)>`, since `char` has a + /// forbidden value 2^32 - 1, this method will return the path to + /// the `char` field in the tuple, with a value of 2^32 - 1. + fn find_forbidden_value<'a>(&self, cx: &CrateContext<'a, 'tcx>) -> + Option<(DiscrField, ValueRef)> { for (i, &ty) in self.tys.iter().enumerate() { - if let Some(mut path) = find_discr_field_candidate(cx.tcx(), ty, vec![]) { + if let Some((mut path, forbidden)) = + find_discr_field_candidate(cx, ty, vec![]) { path.push(i); - return Some(path); + return Some((path, forbidden)); } } None @@ -643,7 +695,7 @@ pub fn incomplete_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, pub fn finish_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, r: &Repr<'tcx>, llty: &mut Type) { match *r { - CEnum(..) | General(..) | RawNullablePointer { .. } => { } + CEnum(..) | General(..) | RawForbiddenValue { .. } => { } Univariant(ref st) | StructWrappedNullablePointer { nonnull: ref st, .. } => llty.set_struct_body(&struct_llfields(cx, st, false, false), st.packed) @@ -659,8 +711,8 @@ fn generic_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, r, name, sizing, dst); match *r { CEnum(ity, _, _) => ll_inttype(cx, ity), - RawNullablePointer { nnty, .. } => - type_of::sizing_type_of(cx, nnty), + RawForbiddenValue { payload_ty, .. } => + type_of::sizing_type_of(cx, payload_ty), StructWrappedNullablePointer { nonnull: ref st, .. } => { match name { None => { @@ -756,7 +808,7 @@ pub fn trans_switch<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, -> (BranchKind, Option) { match *r { CEnum(..) | General(..) | - RawNullablePointer { .. } | StructWrappedNullablePointer { .. } => { + RawForbiddenValue{ .. } | StructWrappedNullablePointer { .. } => { (BranchKind::Switch, Some(trans_get_discr(bcx, r, scrutinee, None, range_assert))) } Univariant(..) => { @@ -771,7 +823,7 @@ pub fn is_discr_signed<'tcx>(r: &Repr<'tcx>) -> bool { CEnum(ity, _, _) => ity.is_signed(), General(ity, _) => ity.is_signed(), Univariant(..) => false, - RawNullablePointer { .. } => false, + RawForbiddenValue { payload_ty, .. } => payload_ty.is_signed(), StructWrappedNullablePointer { .. } => false, } } @@ -792,10 +844,9 @@ pub fn trans_get_discr<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, r: &Repr<'tcx>, range_assert) } Univariant(..) => C_u8(bcx.ccx(), 0), - RawNullablePointer { nndiscr, nnty, .. } => { - let cmp = if nndiscr == Disr(0) { IntEQ } else { IntNE }; - let llptrty = type_of::sizing_type_of(bcx.ccx(), nnty); - ICmp(bcx, cmp, Load(bcx, scrutinee), C_null(llptrty), DebugLoc::None) + RawForbiddenValue { payload_discr, forbidden_value, .. } => { + let cmp = if payload_discr == Disr(0) { IntEQ } else { IntNE }; + ICmp(bcx, cmp, Load(bcx, scrutinee), forbidden_value, DebugLoc::None) } StructWrappedNullablePointer { nndiscr, ref discrfield, .. } => { struct_wrapped_nullable_bitdiscr(bcx, nndiscr, discrfield, scrutinee) @@ -856,7 +907,7 @@ pub fn trans_case<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, r: &Repr, discr: Disr) Univariant(..) => { bug!("no cases for univariants or structs") } - RawNullablePointer { .. } | + RawForbiddenValue { .. } | StructWrappedNullablePointer { .. } => { assert!(discr == Disr(0) || discr == Disr(1)); C_bool(bcx.ccx(), discr != Disr(0)) @@ -881,10 +932,9 @@ pub fn trans_set_discr<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, r: &Repr<'tcx>, Univariant(_) => { assert_eq!(discr, Disr(0)); } - RawNullablePointer { nndiscr, nnty, ..} => { - if discr != nndiscr { - let llptrty = type_of::sizing_type_of(bcx.ccx(), nnty); - Store(bcx, C_null(llptrty), val); + RawForbiddenValue { payload_discr, forbidden_value, ..} => { + if discr != payload_discr { + Store(bcx, forbidden_value, val); } } StructWrappedNullablePointer { nndiscr, ref discrfield, .. } => { @@ -936,7 +986,7 @@ pub fn trans_field_ptr_builder<'blk, 'tcx>(bcx: &BlockAndBuilder<'blk, 'tcx>, General(_, ref cases) => { struct_field_ptr(bcx, &cases[discr.0 as usize], val, ix + 1, true) } - RawNullablePointer { nndiscr, ref nullfields, .. } | + RawForbiddenValue { payload_discr: nndiscr, unit_fields: ref nullfields, .. } | StructWrappedNullablePointer { nndiscr, ref nullfields, .. } if discr != nndiscr => { // The unit-like case might have a nonzero number of unit-like fields. // (e.d., Result of Either with (), as one side.) @@ -947,10 +997,11 @@ pub fn trans_field_ptr_builder<'blk, 'tcx>(bcx: &BlockAndBuilder<'blk, 'tcx>, if bcx.is_unreachable() { return C_undef(ty.ptr_to()); } bcx.pointercast(val.value, ty.ptr_to()) } - RawNullablePointer { nndiscr, nnty, .. } => { + RawForbiddenValue { payload_discr, payload_ty, .. } => { + // By definition, the payload of RawForbiddenValue has a single field. assert_eq!(ix, 0); - assert_eq!(discr, nndiscr); - let ty = type_of::type_of(bcx.ccx(), nnty); + assert_eq!(discr, payload_discr); + let ty = type_of::type_of(bcx.ccx(), payload_ty); if bcx.is_unreachable() { return C_undef(ty.ptr_to()); } bcx.pointercast(val.value, ty.ptr_to()) } @@ -1102,12 +1153,12 @@ pub fn trans_const<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>, r: &Repr<'tcx>, discr let contents = build_const_struct(ccx, st, vals); C_struct(ccx, &contents[..], st.packed) } - RawNullablePointer { nndiscr, nnty, .. } => { - if discr == nndiscr { - assert_eq!(vals.len(), 1); + RawForbiddenValue { payload_discr, forbidden_value, .. } => { + if discr == payload_discr { + assert_eq!(vals.len(), 1); // By definition, the payload has only a single field. vals[0] } else { - C_null(type_of::sizing_type_of(ccx, nnty)) + forbidden_value } } StructWrappedNullablePointer { ref nonnull, nndiscr, .. } => { @@ -1203,14 +1254,14 @@ fn roundup(x: u64, a: u32) -> u64 { let a = a as u64; ((x + (a - 1)) / a) * a } /// /// (Not to be confused with `common::const_get_elt`, which operates on /// raw LLVM-level structs and arrays.) -pub fn const_get_field(r: &Repr, val: ValueRef, _discr: Disr, +pub fn const_get_field(r: &Repr, val: ValueRef, discr: Disr, ix: usize) -> ValueRef { match *r { CEnum(..) => bug!("element access in C-like enum const"), Univariant(..) => const_struct_field(val, ix), General(..) => const_struct_field(val, ix + 1), - RawNullablePointer { .. } => { - assert_eq!(ix, 0); + RawForbiddenValue { .. } => { + assert_eq!(ix, 0); // By definition, the payload only has a single field. val }, StructWrappedNullablePointer{ .. } => const_struct_field(val, ix) diff --git a/src/librustc_trans/debuginfo/metadata.rs b/src/librustc_trans/debuginfo/metadata.rs index 67d4a0e044c9..9cd81ce1ad09 100644 --- a/src/librustc_trans/debuginfo/metadata.rs +++ b/src/librustc_trans/debuginfo/metadata.rs @@ -1295,36 +1295,36 @@ impl<'tcx> EnumMemberDescriptionFactory<'tcx> { ] } } - adt::RawNullablePointer { nndiscr: non_null_variant_index, nnty, .. } => { + adt::RawForbiddenValue { payload_discr: payload_variant_index, payload_ty, .. } => { // As far as debuginfo is concerned, the pointer this enum // represents is still wrapped in a struct. This is to make the // DWARF representation of enums uniform. // First create a description of the artificial wrapper struct: - let non_null_variant = &adt.variants[non_null_variant_index.0 as usize]; - let non_null_variant_name = non_null_variant.name.as_str(); + let payload_variant = &adt.variants[payload_variant_index.0 as usize]; + let payload_variant_name = payload_variant.name.as_str(); // The llvm type and metadata of the pointer - let non_null_llvm_type = type_of::type_of(cx, nnty); - let non_null_type_metadata = type_metadata(cx, nnty, self.span); + let payload_llvm_type = type_of::type_of(cx, payload_ty); + let payload_type_metadata = type_metadata(cx, payload_ty, self.span); // The type of the artificial struct wrapping the pointer let artificial_struct_llvm_type = Type::struct_(cx, - &[non_null_llvm_type], + &[payload_llvm_type], false); // For the metadata of the wrapper struct, we need to create a // MemberDescription of the struct's single field. let sole_struct_member_description = MemberDescription { - name: match non_null_variant.kind { + name: match payload_variant.kind { ty::VariantKind::Tuple => "__0".to_string(), ty::VariantKind::Struct => { - non_null_variant.fields[0].name.to_string() + payload_variant.fields[0].name.to_string() } ty::VariantKind::Unit => bug!() }, - llvm_type: non_null_llvm_type, - type_metadata: non_null_type_metadata, + llvm_type: payload_llvm_type, + type_metadata: payload_type_metadata, offset: FixedMemberOffset { bytes: 0 }, flags: FLAGS_NONE }; @@ -1334,13 +1334,13 @@ impl<'tcx> EnumMemberDescriptionFactory<'tcx> { .get_unique_type_id_of_enum_variant( cx, self.enum_type, - &non_null_variant_name); + &payload_variant_name); // Now we can create the metadata of the artificial struct let artificial_struct_metadata = composite_type_metadata(cx, artificial_struct_llvm_type, - &non_null_variant_name, + &payload_variant_name, unique_type_id, &[sole_struct_member_description], self.containing_scope, @@ -1349,11 +1349,11 @@ impl<'tcx> EnumMemberDescriptionFactory<'tcx> { // Encode the information about the null variant in the union // member's name. - let null_variant_index = (1 - non_null_variant_index.0) as usize; - let null_variant_name = adt.variants[null_variant_index].name; + let unit_variant_index = (1 - payload_variant_index.0) as usize; + let unit_variant_name = adt.variants[unit_variant_index].name; let union_member_name = format!("RUST$ENCODED$ENUM${}${}", 0, - null_variant_name); + unit_variant_name); // Finally create the (singleton) list of descriptions of union // members. @@ -1607,7 +1607,7 @@ fn prepare_enum_metadata<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, adt::CEnum(inttype, _, _) => { return FinalMetadata(discriminant_type_metadata(inttype)) }, - adt::RawNullablePointer { .. } | + adt::RawForbiddenValue { .. } | adt::StructWrappedNullablePointer { .. } | adt::Univariant(..) => None, adt::General(inttype, _) => Some(discriminant_type_metadata(inttype)), diff --git a/src/librustc_trans/disr.rs b/src/librustc_trans/disr.rs index fc79fa813aa5..c0d1e53c9447 100644 --- a/src/librustc_trans/disr.rs +++ b/src/librustc_trans/disr.rs @@ -8,6 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +/// Representation of single value in a C-style enum. #[derive(Debug, Eq, PartialEq, Copy, Clone)] pub struct Disr(pub u64); diff --git a/src/test/run-pass/enum-null-pointer-opt.rs b/src/test/run-pass/enum-null-pointer-opt.rs index e296aff2782c..d4aaf4143c9d 100644 --- a/src/test/run-pass/enum-null-pointer-opt.rs +++ b/src/test/run-pass/enum-null-pointer-opt.rs @@ -38,6 +38,14 @@ fn main() { assert_eq!(size_of::<&Trait>(), size_of::>()); assert_eq!(size_of::<&mut Trait>(), size_of::>()); + // Chars + assert_eq!(size_of::(), size_of::>()); + assert_eq!(size_of::(), size_of::>()); + + // Bools + assert_eq!(size_of::(), size_of::>()); + assert_eq!(size_of::(), size_of::>()); + // Pointers - Box assert_eq!(size_of::>(), size_of::>>()); @@ -45,14 +53,18 @@ fn main() { assert!(size_of::>() != size_of::<*const isize>()); assert!(Some(0 as *const isize).is_some()); // Can't collapse None to null - struct Foo { - _a: Box + // The optimization can't apply to raw u32 + assert!(size_of::>() != size_of::()); + + struct Foo { + _a: T } - struct Bar(Box); + struct Bar(T); // Should apply through structs - assert_eq!(size_of::(), size_of::>()); - assert_eq!(size_of::(), size_of::>()); + assert_eq!(size_of::>>(), size_of::>>>()); + assert_eq!(size_of::>>(), size_of::>>>()); + // and tuples assert_eq!(size_of::<(u8, Box)>(), size_of::)>>()); // and fixed-size arrays