From 38b5ddd39b22dbdf9f004090146c561327ed51e7 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Tue, 16 Jan 2018 09:12:54 +0100 Subject: [PATCH] Add a variant to ConstVal for storing miri results --- src/librustc/ich/impls_ty.rs | 24 +++++++++++ src/librustc/lib.rs | 1 + src/librustc/middle/const_val.rs | 3 ++ src/librustc/mir/interpret/mod.rs | 11 ++++-- src/librustc/mir/interpret/value.rs | 8 ++-- src/librustc/mir/mod.rs | 3 +- src/librustc/ty/context.rs | 19 +++++++++ src/librustc/ty/flags.rs | 1 + src/librustc/ty/instance.rs | 4 +- src/librustc/ty/layout.rs | 2 +- src/librustc/ty/structural_impls.rs | 2 + src/librustc/ty/walk.rs | 1 + src/librustc/ty/wf.rs | 1 + src/librustc_const_eval/pattern.rs | 1 + src/librustc_metadata/decoder.rs | 46 +++++++++++++++++++++- src/librustc_metadata/encoder.rs | 39 +++++++++++++++++- src/librustc_mir/interpret/eval_context.rs | 23 +++++------ src/librustc_trans/mir/constant.rs | 1 + 18 files changed, 165 insertions(+), 25 deletions(-) diff --git a/src/librustc/ich/impls_ty.rs b/src/librustc/ich/impls_ty.rs index a8ed885e78d5..06a60bfb0eba 100644 --- a/src/librustc/ich/impls_ty.rs +++ b/src/librustc/ich/impls_ty.rs @@ -21,6 +21,7 @@ use std::mem; use middle::region; use traits; use ty; +use mir; impl<'gcx, T> HashStable> for &'gcx ty::Slice @@ -388,6 +389,9 @@ for ::middle::const_val::ConstVal<'gcx> { def_id.hash_stable(hcx, hasher); substs.hash_stable(hcx, hasher); } + Value(ref value) => { + value.hash_stable(hcx, hasher); + } } } } @@ -396,6 +400,26 @@ impl_stable_hash_for!(struct ::middle::const_val::ByteArray<'tcx> { data }); +impl_stable_hash_for!(enum mir::interpret::Value { + ByVal(v), + ByValPair(a, b), + ByRef(ptr, align) +}); + +impl_stable_hash_for!(struct mir::interpret::MemoryPointer { + alloc_id, + offset +}); + +impl_stable_hash_for!(tuple_struct mir::interpret::AllocId{id}); +impl_stable_hash_for!(struct mir::interpret::Pointer{primval}); + +impl_stable_hash_for!(enum mir::interpret::PrimVal { + Bytes(b), + Ptr(p), + Undef +}); + impl_stable_hash_for!(struct ty::Const<'tcx> { ty, val diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs index d08a41010ab1..56de2939ffae 100644 --- a/src/librustc/lib.rs +++ b/src/librustc/lib.rs @@ -49,6 +49,7 @@ #![feature(core_intrinsics)] #![feature(drain_filter)] #![feature(dyn_trait)] +#![feature(entry_or_default)] #![feature(from_ref)] #![feature(fs_read_write)] #![feature(i128)] diff --git a/src/librustc/middle/const_val.rs b/src/librustc/middle/const_val.rs index 440af39a0d46..2309131c57a9 100644 --- a/src/librustc/middle/const_val.rs +++ b/src/librustc/middle/const_val.rs @@ -14,6 +14,7 @@ use hir::def_id::DefId; use ty::{self, TyCtxt, layout}; use ty::subst::Substs; use rustc_const_math::*; +use mir::interpret::Value; use graphviz::IntoCow; use errors::DiagnosticBuilder; @@ -38,6 +39,8 @@ pub enum ConstVal<'tcx> { Function(DefId, &'tcx Substs<'tcx>), Aggregate(ConstAggregate<'tcx>), Unevaluated(DefId, &'tcx Substs<'tcx>), + /// A miri value, currently only produced if old ctfe fails, but miri succeeds + Value(Value), } #[derive(Copy, Clone, Debug, Hash, RustcEncodable, Eq, PartialEq)] diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs index a80695ec9b98..8b4f56e1dba5 100644 --- a/src/librustc/mir/interpret/mod.rs +++ b/src/librustc/mir/interpret/mod.rs @@ -41,7 +41,7 @@ pub enum AccessKind { } /// Uniquely identifies a specific constant or static. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, RustcEncodable, RustcDecodable)] pub struct GlobalId<'tcx> { /// For a constant or static, the `Instance` of the item itself. /// For a promoted global, the `Instance` of the function they belong to. @@ -101,7 +101,7 @@ pub trait PointerArithmetic: layout::HasDataLayout { impl PointerArithmetic for T {} -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, RustcEncodable, RustcDecodable, Hash)] pub struct MemoryPointer { pub alloc_id: AllocId, pub offset: u64, @@ -148,13 +148,16 @@ impl<'tcx> MemoryPointer { #[derive(Copy, Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Debug)] pub struct AllocId(pub u64); +impl ::rustc_serialize::UseSpecializedEncodable for AllocId {} +impl ::rustc_serialize::UseSpecializedDecodable for AllocId {} + impl fmt::Display for AllocId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } } -#[derive(Debug, Eq, PartialEq, Hash)] +#[derive(Debug, Eq, PartialEq, Hash, RustcEncodable, RustcDecodable)] pub struct Allocation { /// The actual bytes of the allocation. /// Note that the bytes of a pointer represent the offset of the pointer @@ -188,7 +191,7 @@ impl Allocation { type Block = u64; const BLOCK_SIZE: u64 = 64; -#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Debug, Eq, PartialEq, Hash, RustcEncodable, RustcDecodable)] pub struct UndefMask { blocks: Vec, len: u64, diff --git a/src/librustc/mir/interpret/value.rs b/src/librustc/mir/interpret/value.rs index 0bfff2a80e67..8d67856c0df8 100644 --- a/src/librustc/mir/interpret/value.rs +++ b/src/librustc/mir/interpret/value.rs @@ -29,7 +29,7 @@ pub fn bytes_to_f64(bits: u128) -> ConstFloat { /// For optimization of a few very common cases, there is also a representation for a pair of /// primitive values (`ByValPair`). It allows Miri to avoid making allocations for checked binary /// operations and fat pointers. This idea was taken from rustc's trans. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, RustcEncodable, RustcDecodable, Hash)] pub enum Value { ByRef(Pointer, Align), ByVal(PrimVal), @@ -43,9 +43,9 @@ pub enum Value { /// I (@oli-obk) believe it is less easy to mix up generic primvals and primvals that are just /// the representation of pointers. Also all the sites that convert between primvals and pointers /// are explicit now (and rare!) -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, RustcEncodable, RustcDecodable, Hash)] pub struct Pointer { - primval: PrimVal, + pub primval: PrimVal, } impl<'tcx> Pointer { @@ -138,7 +138,7 @@ impl ::std::convert::From for Pointer { /// `memory::Allocation`. It is in many ways like a small chunk of a `Allocation`, up to 8 bytes in /// size. Like a range of bytes in an `Allocation`, a `PrimVal` can either represent the raw bytes /// of a simple value, a pointer into another `Allocation`, or be undefined. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, RustcEncodable, RustcDecodable, Hash)] pub enum PrimVal { /// The raw bytes of a simple value. Bytes(u128), diff --git a/src/librustc/mir/mod.rs b/src/librustc/mir/mod.rs index 7c9feb506afd..4c1b8cb79ed1 100644 --- a/src/librustc/mir/mod.rs +++ b/src/librustc/mir/mod.rs @@ -1881,7 +1881,8 @@ fn fmt_const_val(fmt: &mut W, const_val: &ConstVal) -> fmt::Result { Variant(def_id) | Function(def_id, _) => write!(fmt, "{}", item_path_str(def_id)), Aggregate(_) => bug!("`ConstVal::{:?}` should not be in MIR", const_val), - Unevaluated(..) => write!(fmt, "{:?}", const_val) + Unevaluated(..) => write!(fmt, "{:?}", const_val), + Value(val) => write!(fmt, "{:?}", val), } } diff --git a/src/librustc/ty/context.rs b/src/librustc/ty/context.rs index 47a3580e8676..520da34c40ac 100644 --- a/src/librustc/ty/context.rs +++ b/src/librustc/ty/context.rs @@ -905,6 +905,11 @@ pub struct InterpretInterner<'tcx> { /// Allows obtaining const allocs via a unique identifier alloc_by_id: FxHashMap, + /// Reverse map of `alloc_cache` + /// + /// Multiple globals may share the same memory + global_cache: FxHashMap>>, + /// The AllocId to assign to the next new regular allocation. /// Always incremented, never gets smaller. next_id: interpret::AllocId, @@ -955,11 +960,25 @@ impl<'tcx> InterpretInterner<'tcx> { global_id: interpret::GlobalId<'tcx>, ptr: interpret::AllocId, ) { + if let interpret::PrimVal::Ptr(ptr) = ptr.primval { + assert!(ptr.offset == 0); + } + self.global_cache.entry(ptr).or_default().push(global_id); if let Some(old) = self.alloc_cache.insert(global_id, ptr) { bug!("tried to cache {:?}, but was already existing as {:#?}", global_id, old); } } + pub fn get_globals( + &self, + ptr: interpret::Pointer, + ) -> &[interpret::GlobalId<'tcx>] { + match self.global_cache.get(&ptr) { + Some(v) => v, + None => &[], + } + } + pub fn intern_at_reserved( &mut self, id: interpret::AllocId, diff --git a/src/librustc/ty/flags.rs b/src/librustc/ty/flags.rs index 2889322a1ce7..60bf4afc2fc8 100644 --- a/src/librustc/ty/flags.rs +++ b/src/librustc/ty/flags.rs @@ -224,6 +224,7 @@ impl FlagComputation { ConstVal::ByteStr(_) | ConstVal::Bool(_) | ConstVal::Char(_) | + ConstVal::Value(_) | ConstVal::Variant(_) => {} ConstVal::Function(_, substs) => { self.add_substs(substs); diff --git a/src/librustc/ty/instance.rs b/src/librustc/ty/instance.rs index a5f0abb9bc05..614158bafa63 100644 --- a/src/librustc/ty/instance.rs +++ b/src/librustc/ty/instance.rs @@ -17,13 +17,13 @@ use util::ppaux; use std::fmt; -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, RustcEncodable, RustcDecodable)] pub struct Instance<'tcx> { pub def: InstanceDef<'tcx>, pub substs: &'tcx Substs<'tcx>, } -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, RustcEncodable, RustcDecodable)] pub enum InstanceDef<'tcx> { Item(DefId), Intrinsic(DefId), diff --git a/src/librustc/ty/layout.rs b/src/librustc/ty/layout.rs index 5069c5956267..1aa7f671ad39 100644 --- a/src/librustc/ty/layout.rs +++ b/src/librustc/ty/layout.rs @@ -342,7 +342,7 @@ impl AddAssign for Size { /// Each field is a power of two, giving the alignment a maximum /// value of 2(28 - 1), which is limited by LLVM to a i32, with /// a maximum capacity of 231 - 1 or 2147483647. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, RustcEncodable, RustcDecodable)] pub struct Align { abi: u8, pref: u8, diff --git a/src/librustc/ty/structural_impls.rs b/src/librustc/ty/structural_impls.rs index 055835ed69c1..3e7468d2ccab 100644 --- a/src/librustc/ty/structural_impls.rs +++ b/src/librustc/ty/structural_impls.rs @@ -1249,6 +1249,7 @@ impl<'tcx> TypeFoldable<'tcx> for ConstVal<'tcx> { ConstVal::ByteStr(b) => ConstVal::ByteStr(b), ConstVal::Bool(b) => ConstVal::Bool(b), ConstVal::Char(c) => ConstVal::Char(c), + ConstVal::Value(v) => ConstVal::Value(v), ConstVal::Variant(def_id) => ConstVal::Variant(def_id), ConstVal::Function(def_id, substs) => { ConstVal::Function(def_id, substs.fold_with(folder)) @@ -1304,6 +1305,7 @@ impl<'tcx> TypeFoldable<'tcx> for ConstVal<'tcx> { ConstVal::ByteStr(_) | ConstVal::Bool(_) | ConstVal::Char(_) | + ConstVal::Value(_) | ConstVal::Variant(_) => false, ConstVal::Function(_, substs) => substs.visit_with(visitor), ConstVal::Aggregate(ConstAggregate::Struct(fields)) => { diff --git a/src/librustc/ty/walk.rs b/src/librustc/ty/walk.rs index 45f0ad1cf1a5..4ef7706c45e3 100644 --- a/src/librustc/ty/walk.rs +++ b/src/librustc/ty/walk.rs @@ -146,6 +146,7 @@ fn push_const<'tcx>(stack: &mut TypeWalkerStack<'tcx>, constant: &'tcx ty::Const ConstVal::ByteStr(_) | ConstVal::Bool(_) | ConstVal::Char(_) | + ConstVal::Value(_) | ConstVal::Variant(_) => {} ConstVal::Function(_, substs) => { stack.extend(substs.types().rev()); diff --git a/src/librustc/ty/wf.rs b/src/librustc/ty/wf.rs index ea99bd39e879..443e0e857a7a 100644 --- a/src/librustc/ty/wf.rs +++ b/src/librustc/ty/wf.rs @@ -224,6 +224,7 @@ impl<'a, 'gcx, 'tcx> WfPredicates<'a, 'gcx, 'tcx> { ConstVal::Bool(_) | ConstVal::Char(_) | ConstVal::Variant(_) | + ConstVal::Value(_) | ConstVal::Function(..) => {} ConstVal::Aggregate(ConstAggregate::Struct(fields)) => { for &(_, v) in fields { diff --git a/src/librustc_const_eval/pattern.rs b/src/librustc_const_eval/pattern.rs index bdb1001124de..a09e2f2edd59 100644 --- a/src/librustc_const_eval/pattern.rs +++ b/src/librustc_const_eval/pattern.rs @@ -121,6 +121,7 @@ fn print_const_val(value: &ConstVal, f: &mut fmt::Formatter) -> fmt::Result { ConstVal::Variant(_) | ConstVal::Function(..) | ConstVal::Aggregate(_) | + ConstVal::Value(_) | ConstVal::Unevaluated(..) => bug!("{:?} not printable in a pattern", value) } } diff --git a/src/librustc_metadata/decoder.rs b/src/librustc_metadata/decoder.rs index 60a0d4e03b54..3c3c489d0ff7 100644 --- a/src/librustc_metadata/decoder.rs +++ b/src/librustc_metadata/decoder.rs @@ -24,11 +24,12 @@ use rustc::hir::def_id::{CrateNum, DefId, DefIndex, CRATE_DEF_INDEX, LOCAL_CRATE}; use rustc::ich::Fingerprint; use rustc::middle::lang_items; -use rustc::mir; +use rustc::mir::{self, interpret}; use rustc::session::Session; use rustc::ty::{self, Ty, TyCtxt}; use rustc::ty::codec::TyDecoder; use rustc::mir::Mir; +use rustc::util::nodemap::FxHashMap; use std::cell::Ref; use std::collections::BTreeMap; @@ -54,6 +55,9 @@ pub struct DecodeContext<'a, 'tcx: 'a> { last_filemap_index: usize, lazy_state: LazyState, + + // interpreter allocation cache + interpret_alloc_cache: FxHashMap, } /// Abstract over the various ways one can create metadata decoders. @@ -72,6 +76,7 @@ pub trait Metadata<'a, 'tcx>: Copy { tcx, last_filemap_index: 0, lazy_state: LazyState::NoNode, + interpret_alloc_cache: FxHashMap::default(), } } } @@ -268,6 +273,45 @@ impl<'a, 'tcx> SpecializedDecoder for DecodeContext<'a, 'tcx> { } } +impl<'a, 'tcx> SpecializedDecoder for DecodeContext<'a, 'tcx> { + fn specialized_decode(&mut self) -> Result { + const MAX1: usize = usize::max_value() - 1; + let mut interpret_interner = self.tcx.unwrap().interpret_interner.borrow_mut(); + let pos = self.position(); + match self.read_usize()? { + ::std::usize::MAX => { + let allocation = interpret::Allocation::decode(self)?; + let id = interpret_interner.reserve(); + let allocation = self.tcx.unwrap().intern_const_alloc(allocation); + interpret_interner.intern_at_reserved(id, allocation); + let id = interpret::AllocId(id); + self.interpret_alloc_cache.insert(pos, id); + + let num = usize::decode(self)?; + let ptr = interpret::Pointer { + primval: interpret::PrimVal::Ptr(interpret::MemoryPointer { + alloc_id: id, + offset: 0, + }), + }; + for _ in 0..num { + let glob = interpret::GlobalId::decode(self)?; + interpret_interner.cache(glob, ptr); + } + + Ok(id) + }, + MAX1 => { + let instance = ty::Instance::decode(self)?; + let id = interpret::AllocId(interpret_interner.create_fn_alloc(instance)); + self.interpret_alloc_cache.insert(pos, id); + Ok(id) + }, + shorthand => Ok(self.interpret_alloc_cache[&shorthand]), + } + } +} + impl<'a, 'tcx> SpecializedDecoder for DecodeContext<'a, 'tcx> { fn specialized_decode(&mut self) -> Result { let tag = u8::decode(self)?; diff --git a/src/librustc_metadata/encoder.rs b/src/librustc_metadata/encoder.rs index 830121b446fc..928bf0a56ae5 100644 --- a/src/librustc_metadata/encoder.rs +++ b/src/librustc_metadata/encoder.rs @@ -23,7 +23,7 @@ use rustc::middle::dependency_format::Linkage; use rustc::middle::exported_symbols::{ExportedSymbol, SymbolExportLevel, metadata_symbol_name}; use rustc::middle::lang_items; -use rustc::mir; +use rustc::mir::{self, interpret}; use rustc::traits::specialization_graph; use rustc::ty::{self, Ty, TyCtxt, ReprOptions, SymbolName}; use rustc::ty::codec::{self as ty_codec, TyEncoder}; @@ -59,6 +59,7 @@ pub struct EncodeContext<'a, 'tcx: 'a> { lazy_state: LazyState, type_shorthands: FxHashMap, usize>, predicate_shorthands: FxHashMap, usize>, + interpret_alloc_shorthands: FxHashMap, // This is used to speed up Span encoding. filemap_cache: Lrc, @@ -186,6 +187,41 @@ impl<'a, 'tcx> SpecializedEncoder> for EncodeContext<'a, 'tcx> { } } +impl<'a, 'tcx> SpecializedEncoder for EncodeContext<'a, 'tcx> { + fn specialized_encode(&mut self, alloc_id: &interpret::AllocId) -> Result<(), Self::Error> { + if let Some(shorthand) = self.interpret_alloc_shorthands.get(alloc_id).cloned() { + return self.emit_usize(shorthand); + } + let start = self.position(); + let interpret_interner = self.tcx.interpret_interner.borrow(); + if let Some(alloc) = interpret_interner.get_alloc(alloc_id.0) { + usize::max_value().encode(self)?; + alloc.encode(self)?; + let globals = interpret_interner.get_globals(interpret::Pointer { + primval: interpret::PrimVal::Ptr(interpret::MemoryPointer { + alloc_id: *alloc_id, + offset: 0, + }), + }); + globals.len().encode(self)?; + for glob in globals { + glob.encode(self)?; + } + } else if let Some(fn_instance) = interpret_interner.get_fn(alloc_id.0) { + (usize::max_value() - 1).encode(self)?; + fn_instance.encode(self)?; + } else { + bug!("alloc id without corresponding allocation: {}", alloc_id.0); + } + let len = self.position() - start * 7; + // Check that the shorthand is a not longer than the + // full encoding itself, i.e. it's an obvious win. + assert!(len >= 64 || (start as u64) < (1 << len)); + self.interpret_alloc_shorthands.insert(*alloc_id, start); + Ok(()) + } +} + impl<'a, 'tcx> SpecializedEncoder> for EncodeContext<'a, 'tcx> { fn specialized_encode(&mut self, predicates: &ty::GenericPredicates<'tcx>) @@ -1699,6 +1735,7 @@ pub fn encode_metadata<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, type_shorthands: Default::default(), predicate_shorthands: Default::default(), filemap_cache: tcx.sess.codemap().files()[0].clone(), + interpret_alloc_shorthands: Default::default(), }; // Encode the rustc version string in a predictable location. diff --git a/src/librustc_mir/interpret/eval_context.rs b/src/librustc_mir/interpret/eval_context.rs index 25f933c5da6e..e9e8ccd03b10 100644 --- a/src/librustc_mir/interpret/eval_context.rs +++ b/src/librustc_mir/interpret/eval_context.rs @@ -241,24 +241,24 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> { } pub(super) fn const_to_value(&mut self, const_val: &ConstVal<'tcx>, ty: Ty<'tcx>) -> EvalResult<'tcx, Value> { - use rustc::middle::const_val::ConstVal::*; + use rustc::middle::const_val::ConstVal; let primval = match *const_val { - Integral(const_int) => PrimVal::Bytes(const_int.to_u128_unchecked()), + ConstVal::Integral(const_int) => PrimVal::Bytes(const_int.to_u128_unchecked()), - Float(val) => PrimVal::Bytes(val.bits), + ConstVal::Float(val) => PrimVal::Bytes(val.bits), - Bool(b) => PrimVal::from_bool(b), - Char(c) => PrimVal::from_char(c), + ConstVal::Bool(b) => PrimVal::from_bool(b), + ConstVal::Char(c) => PrimVal::from_char(c), - Str(ref s) => return self.str_to_value(s), + ConstVal::Str(ref s) => return self.str_to_value(s), - ByteStr(ref bs) => { + ConstVal::ByteStr(ref bs) => { let ptr = self.memory.allocate_cached(bs.data); PrimVal::Ptr(ptr) } - Unevaluated(def_id, substs) => { + ConstVal::Unevaluated(def_id, substs) => { let instance = self.resolve(def_id, substs)?; return Ok(self.read_global_as_value(GlobalId { instance, @@ -266,10 +266,11 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> { }, self.layout_of(ty)?)); } - Aggregate(..) | - Variant(_) => bug!("should not have aggregate or variant constants in MIR"), + ConstVal::Aggregate(..) | + ConstVal::Variant(_) => bug!("should not have aggregate or variant constants in MIR"), // function items are zero sized and thus have no readable value - Function(..) => PrimVal::Undef, + ConstVal::Function(..) => PrimVal::Undef, + ConstVal::Value(val) => return Ok(val), }; Ok(Value::ByVal(primval)) diff --git a/src/librustc_trans/mir/constant.rs b/src/librustc_trans/mir/constant.rs index d470f92b7523..c853230b15ab 100644 --- a/src/librustc_trans/mir/constant.rs +++ b/src/librustc_trans/mir/constant.rs @@ -108,6 +108,7 @@ impl<'a, 'tcx> Const<'tcx> { ConstVal::Unevaluated(..) => { bug!("MIR must not use `{:?}` (aggregates are expanded to MIR rvalues)", cv) } + ConstVal::Value(_) => unimplemented!(), }; assert!(!ty.has_erasable_regions());