add Borrow tag to pointers; remove old locking code

This commit is contained in:
Ralf Jung 2018-10-16 11:21:38 +02:00
parent 38ed191d28
commit b84f7e2029
8 changed files with 119 additions and 171 deletions

View file

@ -14,8 +14,8 @@ pub trait EvalContextExt<'tcx, 'mir> {
fn emulate_foreign_item(
&mut self,
def_id: DefId,
args: &[OpTy<'tcx>],
dest: PlaceTy<'tcx>,
args: &[OpTy<'tcx, Borrow>],
dest: PlaceTy<'tcx, Borrow>,
ret: mir::BasicBlock,
) -> EvalResult<'tcx>;
@ -28,28 +28,28 @@ pub trait EvalContextExt<'tcx, 'mir> {
fn emulate_missing_fn(
&mut self,
path: String,
args: &[OpTy<'tcx>],
dest: Option<PlaceTy<'tcx>>,
args: &[OpTy<'tcx, Borrow>],
dest: Option<PlaceTy<'tcx, Borrow>>,
ret: Option<mir::BasicBlock>,
) -> EvalResult<'tcx>;
fn find_fn(
&mut self,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx>],
dest: Option<PlaceTy<'tcx>>,
args: &[OpTy<'tcx, Borrow>],
dest: Option<PlaceTy<'tcx, Borrow>>,
ret: Option<mir::BasicBlock>,
) -> EvalResult<'tcx, Option<&'mir mir::Mir<'tcx>>>;
fn write_null(&mut self, dest: PlaceTy<'tcx>) -> EvalResult<'tcx>;
fn write_null(&mut self, dest: PlaceTy<'tcx, Borrow>) -> EvalResult<'tcx>;
}
impl<'a, 'mir, 'tcx: 'mir + 'a> EvalContextExt<'tcx, 'mir> for EvalContext<'a, 'mir, 'tcx, super::Evaluator<'tcx>> {
fn find_fn(
&mut self,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx>],
dest: Option<PlaceTy<'tcx>>,
args: &[OpTy<'tcx, Borrow>],
dest: Option<PlaceTy<'tcx, Borrow>>,
ret: Option<mir::BasicBlock>,
) -> EvalResult<'tcx, Option<&'mir mir::Mir<'tcx>>> {
trace!("eval_fn_call: {:#?}, {:?}", instance, dest.map(|place| *place));
@ -108,8 +108,8 @@ impl<'a, 'mir, 'tcx: 'mir + 'a> EvalContextExt<'tcx, 'mir> for EvalContext<'a, '
fn emulate_foreign_item(
&mut self,
def_id: DefId,
args: &[OpTy<'tcx>],
dest: PlaceTy<'tcx>,
args: &[OpTy<'tcx, Borrow>],
dest: PlaceTy<'tcx, Borrow>,
ret: mir::BasicBlock,
) -> EvalResult<'tcx> {
let attrs = self.tcx.get_attrs(def_id);
@ -675,8 +675,8 @@ impl<'a, 'mir, 'tcx: 'mir + 'a> EvalContextExt<'tcx, 'mir> for EvalContext<'a, '
fn emulate_missing_fn(
&mut self,
path: String,
_args: &[OpTy<'tcx>],
dest: Option<PlaceTy<'tcx>>,
_args: &[OpTy<'tcx, Borrow>],
dest: Option<PlaceTy<'tcx, Borrow>>,
ret: Option<mir::BasicBlock>,
) -> EvalResult<'tcx> {
// In some cases in non-MIR libstd-mode, not having a destination is legit. Handle these early.
@ -724,7 +724,7 @@ impl<'a, 'mir, 'tcx: 'mir + 'a> EvalContextExt<'tcx, 'mir> for EvalContext<'a, '
Ok(())
}
fn write_null(&mut self, dest: PlaceTy<'tcx>) -> EvalResult<'tcx> {
fn write_null(&mut self, dest: PlaceTy<'tcx, Borrow>) -> EvalResult<'tcx> {
self.write_scalar(Scalar::from_int(0, dest.layout.size), dest)
}
}

View file

@ -7,7 +7,7 @@ pub trait FalibleScalarExt {
fn to_bytes(self) -> EvalResult<'static, u128>;
}
impl FalibleScalarExt for Scalar {
impl<Tag> FalibleScalarExt for Scalar<Tag> {
fn to_bytes(self) -> EvalResult<'static, u128> {
match self {
Scalar::Bits { bits, size } => {
@ -19,7 +19,7 @@ impl FalibleScalarExt for Scalar {
}
}
impl FalibleScalarExt for ScalarMaybeUndef {
impl<Tag> FalibleScalarExt for ScalarMaybeUndef<Tag> {
fn to_bytes(self) -> EvalResult<'static, u128> {
self.not_undef()?.to_bytes()
}

View file

@ -6,7 +6,7 @@ use rustc::mir::interpret::{EvalResult, PointerArithmetic};
use rustc_mir::interpret::{EvalContext, PlaceTy, OpTy};
use super::{
Value, Scalar, ScalarMaybeUndef,
Value, Scalar, ScalarMaybeUndef, Borrow,
FalibleScalarExt, OperatorEvalContextExt
};
@ -14,8 +14,8 @@ pub trait EvalContextExt<'tcx> {
fn call_intrinsic(
&mut self,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx>],
dest: PlaceTy<'tcx>,
args: &[OpTy<'tcx, Borrow>],
dest: PlaceTy<'tcx, Borrow>,
) -> EvalResult<'tcx>;
}
@ -23,8 +23,8 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for EvalContext<'a, 'mir, 'tcx, super:
fn call_intrinsic(
&mut self,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx>],
dest: PlaceTy<'tcx>,
args: &[OpTy<'tcx, Borrow>],
dest: PlaceTy<'tcx, Borrow>,
) -> EvalResult<'tcx> {
if self.emulate_intrinsic(instance, args, dest)? {
return Ok(());

View file

@ -21,11 +21,9 @@ use rustc::ty::layout::{TyLayout, LayoutOf, Size};
use rustc::hir::def_id::DefId;
use rustc::mir;
use syntax::ast::Mutability;
use syntax::attr;
pub use rustc::mir::interpret::*;
pub use rustc_mir::interpret::*;
pub use rustc_mir::interpret::{self, AllocMap}; // resolve ambiguity
@ -34,9 +32,9 @@ mod operator;
mod intrinsic;
mod helpers;
mod tls;
mod locks;
mod range_map;
mod mono_hash_map;
mod stacked_borrows;
use fn_call::EvalContextExt as MissingFnsEvalContextExt;
use operator::EvalContextExt as OperatorEvalContextExt;
@ -45,6 +43,7 @@ use tls::{EvalContextExt as TlsEvalContextExt, TlsData};
use range_map::RangeMap;
use helpers::FalibleScalarExt;
use mono_hash_map::MonoHashMap;
use stacked_borrows::Borrow;
pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
@ -56,7 +55,6 @@ pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>(
tcx.at(syntax::source_map::DUMMY_SP),
ty::ParamEnv::reveal_all(),
Evaluator::new(validate),
Default::default(),
);
let main_instance = ty::Instance::mono(ecx.tcx.tcx, main_id);
@ -118,9 +116,9 @@ pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>(
let foo = ecx.memory.allocate_static_bytes(b"foo\0");
let foo_ty = ecx.tcx.mk_imm_ptr(ecx.tcx.types.u8);
let foo_layout = ecx.layout_of(foo_ty)?;
let foo_place = ecx.allocate(foo_layout, MemoryKind::Stack)?; // will be interned in just a second
let foo_place = ecx.allocate(foo_layout, MiriMemoryKind::Env.into())?;
ecx.write_scalar(Scalar::Ptr(foo), foo_place.into())?;
ecx.memory.intern_static(foo_place.to_ptr()?.alloc_id, Mutability::Immutable)?;
ecx.memory.mark_immutable(foo_place.to_ptr()?.alloc_id)?;
ecx.write_scalar(foo_place.ptr, dest)?;
assert!(args.next().is_none(), "start lang item has more arguments than expected");
@ -227,7 +225,7 @@ impl Into<MemoryKind<MiriMemoryKind>> for MiriMemoryKind {
pub struct Evaluator<'tcx> {
/// Environment variables set by `setenv`
/// Miri does not expose env vars from the host to the emulated program
pub(crate) env_vars: HashMap<Vec<u8>, Pointer>,
pub(crate) env_vars: HashMap<Vec<u8>, Pointer<Borrow>>,
/// TLS state
pub(crate) tls: TlsData<'tcx>,
@ -247,11 +245,11 @@ impl<'tcx> Evaluator<'tcx> {
}
impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> {
type MemoryData = ();
type MemoryKinds = MiriMemoryKind;
type PointerTag = (); // still WIP
type AllocExtra = ();
type PointerTag = Borrow;
type MemoryMap = MonoHashMap<AllocId, (MemoryKind<MiriMemoryKind>, Allocation<()>)>;
type MemoryMap = MonoHashMap<AllocId, (MemoryKind<MiriMemoryKind>, Allocation<Borrow, Self::AllocExtra>)>;
const STATIC_KIND: Option<MiriMemoryKind> = Some(MiriMemoryKind::MutStatic);
@ -282,8 +280,8 @@ impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> {
fn find_fn(
ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx>],
dest: Option<PlaceTy<'tcx>>,
args: &[OpTy<'tcx, Borrow>],
dest: Option<PlaceTy<'tcx, Borrow>>,
ret: Option<mir::BasicBlock>,
) -> EvalResult<'tcx, Option<&'mir mir::Mir<'tcx>>> {
ecx.find_fn(instance, args, dest, ret)
@ -292,8 +290,8 @@ impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> {
fn call_intrinsic(
ecx: &mut rustc_mir::interpret::EvalContext<'a, 'mir, 'tcx, Self>,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx>],
dest: PlaceTy<'tcx>,
args: &[OpTy<'tcx, Borrow>],
dest: PlaceTy<'tcx, Borrow>,
) -> EvalResult<'tcx> {
ecx.call_intrinsic(instance, args, dest)
}
@ -301,17 +299,17 @@ impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> {
fn ptr_op(
ecx: &rustc_mir::interpret::EvalContext<'a, 'mir, 'tcx, Self>,
bin_op: mir::BinOp,
left: Scalar,
left: Scalar<Borrow>,
left_layout: TyLayout<'tcx>,
right: Scalar,
right: Scalar<Borrow>,
right_layout: TyLayout<'tcx>,
) -> EvalResult<'tcx, (Scalar, bool)> {
) -> EvalResult<'tcx, (Scalar<Borrow>, bool)> {
ecx.ptr_op(bin_op, left, left_layout, right, right_layout)
}
fn box_alloc(
ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
dest: PlaceTy<'tcx>,
dest: PlaceTy<'tcx, Borrow>,
) -> EvalResult<'tcx> {
trace!("box_alloc for {:?}", dest.layout.ty);
// Call the `exchange_malloc` lang item
@ -351,7 +349,7 @@ impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> {
fn find_foreign_static(
tcx: TyCtxtAt<'a, 'tcx, 'tcx>,
def_id: DefId,
) -> EvalResult<'tcx, Cow<'tcx, Allocation>> {
) -> EvalResult<'tcx, Cow<'tcx, Allocation<Borrow, Self::AllocExtra>>> {
let attrs = tcx.get_attrs(def_id);
let link_name = match attr::first_attr_value_str_by_name(&attrs, "link_name") {
Some(name) => name.as_str(),
@ -371,16 +369,6 @@ impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> {
Ok(Cow::Owned(alloc))
}
fn validation_op(
_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
_op: ::rustc::mir::ValidationOp,
_operand: &::rustc::mir::ValidationOperand<'tcx, ::rustc::mir::Place<'tcx>>,
) -> EvalResult<'tcx> {
// FIXME: prevent this from ICEing
//ecx.validation_op(op, operand)
Ok(())
}
fn before_terminator(_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>) -> EvalResult<'tcx>
{
// We are not interested in detecting loops
@ -389,8 +377,19 @@ impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> {
fn static_with_default_tag(
alloc: &'_ Allocation
) -> Cow<'_, Allocation<Self::PointerTag>> {
let alloc = alloc.clone();
) -> Cow<'_, Allocation<Borrow, Self::AllocExtra>> {
let alloc: Allocation<Borrow, Self::AllocExtra> = Allocation {
bytes: alloc.bytes.clone(),
relocations: Relocations::from_presorted(
alloc.relocations.iter()
.map(|&(offset, ((), alloc))| (offset, (Borrow::default(), alloc)))
.collect()
),
undef_mask: alloc.undef_mask.clone(),
align: alloc.align,
mutability: alloc.mutability,
extra: Self::AllocExtra::default(),
};
Cow::Owned(alloc)
}
}

View file

@ -1,94 +0,0 @@
#![allow(unused)]
use super::*;
use rustc::middle::region;
use rustc::ty::layout::Size;
////////////////////////////////////////////////////////////////////////////////
// Locks
////////////////////////////////////////////////////////////////////////////////
// Just some dummy to keep this compiling; I think some of this will be useful later
type AbsPlace<'tcx> = ::rustc::ty::Ty<'tcx>;
/// Information about a lock that is currently held.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LockInfo<'tcx> {
/// Stores for which lifetimes (of the original write lock) we got
/// which suspensions.
suspended: HashMap<WriteLockId<'tcx>, Vec<region::Scope>>,
/// The current state of the lock that's actually effective.
pub active: Lock,
}
/// Write locks are identified by a stack frame and an "abstract" (untyped) place.
/// It may be tempting to use the lifetime as identifier, but that does not work
/// for two reasons:
/// * First of all, due to subtyping, the same lock may be referred to with different
/// lifetimes.
/// * Secondly, different write locks may actually have the same lifetime. See `test2`
/// in `run-pass/many_shr_bor.rs`.
/// The Id is "captured" when the lock is first suspended; at that point, the borrow checker
/// considers the path frozen and hence the Id remains stable.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct WriteLockId<'tcx> {
frame: usize,
path: AbsPlace<'tcx>,
}
use rustc::mir::interpret::Lock::*;
use rustc::mir::interpret::Lock;
impl<'tcx> Default for LockInfo<'tcx> {
fn default() -> Self {
LockInfo::new(NoLock)
}
}
impl<'tcx> LockInfo<'tcx> {
fn new(lock: Lock) -> LockInfo<'tcx> {
LockInfo {
suspended: HashMap::new(),
active: lock,
}
}
fn access_permitted(&self, frame: Option<usize>, access: AccessKind) -> bool {
use super::AccessKind::*;
match (&self.active, access) {
(&NoLock, _) => true,
(&ReadLock(ref lfts), Read) => {
assert!(!lfts.is_empty(), "Someone left an empty read lock behind.");
// Read access to read-locked region is okay, no matter who's holding the read lock.
true
}
(&WriteLock(ref lft), _) => {
// All access is okay if we are the ones holding it
Some(lft.frame) == frame
}
_ => false, // Nothing else is okay.
}
}
}
impl<'tcx> RangeMap<LockInfo<'tcx>> {
pub fn check(
&self,
frame: Option<usize>,
offset: u64,
len: u64,
access: AccessKind,
) -> Result<(), LockInfo<'tcx>> {
if len == 0 {
return Ok(());
}
for lock in self.iter(offset, len) {
// Check if the lock is in conflict with the access.
if !lock.access_permitted(frame, access) {
return Err(lock.clone());
}
}
Ok(())
}
}

View file

@ -7,44 +7,44 @@ pub trait EvalContextExt<'tcx> {
fn ptr_op(
&self,
bin_op: mir::BinOp,
left: Scalar,
left: Scalar<Borrow>,
left_layout: TyLayout<'tcx>,
right: Scalar,
right: Scalar<Borrow>,
right_layout: TyLayout<'tcx>,
) -> EvalResult<'tcx, (Scalar, bool)>;
) -> EvalResult<'tcx, (Scalar<Borrow>, bool)>;
fn ptr_int_arithmetic(
&self,
bin_op: mir::BinOp,
left: Pointer,
left: Pointer<Borrow>,
right: u128,
signed: bool,
) -> EvalResult<'tcx, (Scalar, bool)>;
) -> EvalResult<'tcx, (Scalar<Borrow>, bool)>;
fn ptr_eq(
&self,
left: Scalar,
right: Scalar,
left: Scalar<Borrow>,
right: Scalar<Borrow>,
size: Size,
) -> EvalResult<'tcx, bool>;
fn pointer_offset_inbounds(
&self,
ptr: Scalar,
ptr: Scalar<Borrow>,
pointee_ty: Ty<'tcx>,
offset: i64,
) -> EvalResult<'tcx, Scalar>;
) -> EvalResult<'tcx, Scalar<Borrow>>;
}
impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for EvalContext<'a, 'mir, 'tcx, super::Evaluator<'tcx>> {
fn ptr_op(
&self,
bin_op: mir::BinOp,
left: Scalar,
left: Scalar<Borrow>,
left_layout: TyLayout<'tcx>,
right: Scalar,
right: Scalar<Borrow>,
right_layout: TyLayout<'tcx>,
) -> EvalResult<'tcx, (Scalar, bool)> {
) -> EvalResult<'tcx, (Scalar<Borrow>, bool)> {
use rustc::mir::BinOp::*;
trace!("ptr_op: {:?} {:?} {:?}", left, bin_op, right);
@ -124,8 +124,8 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for EvalContext<'a, 'mir, 'tcx, super:
fn ptr_eq(
&self,
left: Scalar,
right: Scalar,
left: Scalar<Borrow>,
right: Scalar<Borrow>,
size: Size,
) -> EvalResult<'tcx, bool> {
Ok(match (left, right) {
@ -203,13 +203,13 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for EvalContext<'a, 'mir, 'tcx, super:
fn ptr_int_arithmetic(
&self,
bin_op: mir::BinOp,
left: Pointer,
left: Pointer<Borrow>,
right: u128,
signed: bool,
) -> EvalResult<'tcx, (Scalar, bool)> {
) -> EvalResult<'tcx, (Scalar<Borrow>, bool)> {
use rustc::mir::BinOp::*;
fn map_to_primval((res, over): (Pointer, bool)) -> (Scalar, bool) {
fn map_to_primval((res, over): (Pointer<Borrow>, bool)) -> (Scalar<Borrow>, bool) {
(Scalar::Ptr(res), over)
}
@ -237,7 +237,14 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for EvalContext<'a, 'mir, 'tcx, super:
if right & base_mask == base_mask {
// Case 1: The base address bits are all preserved, i.e., right is all-1 there
let offset = (left.offset.bytes() as u128 & right) as u64;
(Scalar::Ptr(Pointer::new(left.alloc_id, Size::from_bytes(offset))), false)
(
Scalar::Ptr(Pointer::new_with_tag(
left.alloc_id,
Size::from_bytes(offset),
left.tag,
)),
false,
)
} else if right & base_mask == 0 {
// Case 2: The base address bits are all taken away, i.e., right is all-0 there
(Scalar::Bits { bits: (left.offset.bytes() as u128) & right, size: ptr_size }, false)
@ -277,10 +284,10 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for EvalContext<'a, 'mir, 'tcx, super:
/// allocation.
fn pointer_offset_inbounds(
&self,
ptr: Scalar,
ptr: Scalar<Borrow>,
pointee_ty: Ty<'tcx>,
offset: i64,
) -> EvalResult<'tcx, Scalar> {
) -> EvalResult<'tcx, Scalar<Borrow>> {
// FIXME: assuming here that type size is < i64::max_value()
let pointee_size = self.layout_of(pointee_ty)?.size.bytes() as i64;
let offset = offset.checked_mul(pointee_size).ok_or_else(|| EvalErrorKind::Overflow(mir::BinOp::Mul))?;

36
src/stacked_borrows.rs Normal file
View file

@ -0,0 +1,36 @@
use super::RangeMap;
pub type Timestamp = u64;
/// Information about a potentially mutable borrow
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum Mut {
/// A unique, mutable reference
Uniq(Timestamp),
/// Any raw pointer, or a shared borrow with interior mutability
Raw,
}
/// Information about any kind of borrow
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum Borrow {
/// A mutable borrow, a raw pointer, or a shared borrow with interior mutability
Mut(Mut),
/// A shared borrow without interior mutability
Frz(Timestamp)
}
/// An item in the borrow stack
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum BorStackItem {
/// Defines which references are permitted to mutate *if* the location is not frozen
Mut(Mut),
/// A barrier, tracking the function it belongs to by its index on the call stack
FnBarrier(usize)
}
impl Default for Borrow {
fn default() -> Self {
Borrow::Mut(Mut::Raw)
}
}

View file

@ -5,14 +5,14 @@ use rustc::{ty, ty::layout::HasDataLayout, mir};
use super::{
EvalResult, EvalErrorKind, StackPopCleanup, EvalContext, Evaluator,
MPlaceTy, Scalar,
MPlaceTy, Scalar, Borrow,
};
pub type TlsKey = u128;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct TlsEntry<'tcx> {
pub(crate) data: Scalar, // Will eventually become a map from thread IDs to `Scalar`s, if we ever support more than one thread.
pub(crate) data: Scalar<Borrow>, // Will eventually become a map from thread IDs to `Scalar`s, if we ever support more than one thread.
pub(crate) dtor: Option<ty::Instance<'tcx>>,
}
@ -67,7 +67,7 @@ impl<'tcx> TlsData<'tcx> {
}
}
pub fn load_tls(&mut self, key: TlsKey) -> EvalResult<'tcx, Scalar> {
pub fn load_tls(&mut self, key: TlsKey) -> EvalResult<'tcx, Scalar<Borrow>> {
match self.keys.get(&key) {
Some(&TlsEntry { data, .. }) => {
trace!("TLS key {} loaded: {:?}", key, data);
@ -77,7 +77,7 @@ impl<'tcx> TlsData<'tcx> {
}
}
pub fn store_tls(&mut self, key: TlsKey, new_data: Scalar) -> EvalResult<'tcx> {
pub fn store_tls(&mut self, key: TlsKey, new_data: Scalar<Borrow>) -> EvalResult<'tcx> {
match self.keys.get_mut(&key) {
Some(&mut TlsEntry { ref mut data, .. }) => {
trace!("TLS key {} stored: {:?}", key, new_data);
@ -110,7 +110,7 @@ impl<'tcx> TlsData<'tcx> {
&mut self,
key: Option<TlsKey>,
cx: impl HasDataLayout,
) -> Option<(ty::Instance<'tcx>, Scalar, TlsKey)> {
) -> Option<(ty::Instance<'tcx>, Scalar<Borrow>, TlsKey)> {
use std::collections::Bound::*;
let thread_local = &mut self.keys;