Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
X
xrc
Manage
Activity
Members
Labels
Plan
Issues
0
Issue boards
Milestones
Wiki
Requirements
Code
Merge requests
0
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Lukas Markeffsky
xrc
Commits
43d87f69
Commit
43d87f69
authored
1 year ago
by
Lukas Markeffsky
Browse files
Options
Downloads
Patches
Plain Diff
add safety comments
parent
4fabf7f7
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
src/lib.rs
+141
-86
141 additions, 86 deletions
src/lib.rs
with
141 additions
and
86 deletions
src/lib.rs
+
141
−
86
View file @
43d87f69
#![no_std]
#![deny(unsafe_op_in_unsafe_fn)]
use
core
::
cell
::
Cell
;
use
core
::
hint
;
use
core
::
marker
::
PhantomPinned
;
use
core
::
mem
::{
self
,
MaybeUninit
};
use
core
::
ops
::{
Deref
,
DerefMut
};
...
...
@@ -13,11 +13,13 @@ pub trait Reclaim: Sized {
}
unsafe
fn
reclaim_shim
<
T
:
Reclaim
>
(
erased_ptr
:
NonNull
<
Root
<
Erased
>>
)
{
let
ptr
=
erased_ptr
.cast
::
<
Root
<
T
>>
();
let
root_
ptr
=
erased_ptr
.cast
::
<
Root
<
T
>>
();
let
value
=
unsafe
{
ptr
::
addr_of!
((
*
(
*
ptr
.as_ptr
())
.target
.as_ptr
())
.value
)
.read
()
};
// SAFETY: The caller guarantees that this value is initialized.
let
value
=
unsafe
{
ptr
::
addr_of!
((
*
(
*
root_ptr
.as_ptr
())
.anchor
.as_ptr
())
.value
)
.read
()
};
let
unclaimed
=
Unclaimed
{
ptr
};
// SAFETY: The caller guarantees the state is "unclaimed".
let
unclaimed
=
Unclaimed
{
root_ptr
};
value
.reclaim
(
unclaimed
);
}
...
...
@@ -33,17 +35,18 @@ const STATE_UNCLAIMED: usize = usize::MAX - 1;
const
STATE_SHARED_MAX
:
usize
=
isize
::
MAX
as
usize
;
const
STATE_SHARED_INIT
:
usize
=
0
;
// Fixed layout is required so that the header is in the same place for all T.
#[repr(C)]
pub
struct
Root
<
T
>
{
state
:
Cell
<
usize
>
,
reclaim_shim
:
unsafe
fn
(
NonNull
<
Root
<
Erased
>>
),
_pin
:
PhantomPinned
,
target
:
MaybeUninit
<
Project
Target
<
T
>>
,
anchor
:
MaybeUninit
<
Project
Anchor
<
T
>>
,
}
impl
<
T
>
Drop
for
Root
<
T
>
{
fn
drop
(
&
mut
self
)
{
let
state
=
self
.state
.get
();
let
state
=
*
self
.state
.get
_mut
();
if
state
!=
STATE_UNPINNED
{
struct
Abort
;
impl
Drop
for
Abort
{
...
...
@@ -67,25 +70,33 @@ impl<T> Root<T> {
state
:
Cell
::
new
(
STATE_UNPINNED
),
reclaim_shim
:
reclaim_shim
::
<
T
>
,
_pin
:
PhantomPinned
,
target
:
MaybeUninit
::
uninit
(),
anchor
:
MaybeUninit
::
uninit
(),
}
}
pub
fn
unclaimed
(
self
:
Pin
<&
mut
Self
>
)
->
Unclaimed
<
T
>
{
if
self
.state
.get
()
!=
STATE_UNPINNED
{
// SAFETY: We don't move out of this reference.
let
root
=
unsafe
{
self
.get_unchecked_mut
()
};
let
state
=
root
.state
.get_mut
();
if
*
state
!=
STATE_UNPINNED
{
panic!
(
"unclaimed called multiple times"
);
}
self
.state
.set
(
STATE_UNCLAIMED
)
;
*
state
=
STATE_UNCLAIMED
;
let
ptr
=
unsafe
{
NonNull
::
from
(
self
.get_unchecked_mut
())
}
;
let
ptr
_erased
=
ptr
.cast
::
<
Root
<
Erased
>>
();
let
root_
ptr
=
NonNull
::
from
(
root
)
;
let
root
_erased
=
root_
ptr
.cast
::
<
Root
<
Erased
>>
();
// SAFETY: This is a partial write into a `MaybeUninit` behind a mutable reference.
unsafe
{
let
target_root
=
ptr
::
addr_of_mut!
((
*
(
*
ptr
.as_ptr
())
.target
.as_mut_ptr
())
.root
);
target_root
.write
(
Cell
::
new
(
Some
(
ptr_erased
)));
let
anchor_root
=
ptr
::
addr_of_mut!
((
*
(
*
root_ptr
.as_ptr
())
.anchor
.as_mut_ptr
())
.root_ptr
);
anchor_root
.write
(
Cell
::
new
(
Some
(
root_erased
)));
}
Unclaimed
{
ptr
}
// SAFETY: The pointer is a pinned shared read-write borrow and we set the
// state to "unclaimed" above.
Unclaimed
{
root_ptr
}
}
}
...
...
@@ -95,21 +106,22 @@ impl<T: Reclaim> Default for Root<T> {
}
}
pub
struct
ProjectTarget
<
T
:
?
Sized
>
{
root
:
Cell
<
Option
<
NonNull
<
Root
<
Erased
>>>>
,
pub
struct
ProjectAnchor
<
T
:
?
Sized
>
{
// pinned shared read-write borrow
root_ptr
:
Cell
<
Option
<
NonNull
<
Root
<
Erased
>>>>
,
value
:
T
,
}
impl
<
T
>
Project
Target
<
T
>
{
impl
<
T
>
Project
Anchor
<
T
>
{
pub
const
fn
new
(
value
:
T
)
->
Self
{
Self
{
root
:
Cell
::
new
(
None
),
root
_ptr
:
Cell
::
new
(
None
),
value
,
}
}
}
impl
<
T
:
?
Sized
>
Deref
for
Project
Target
<
T
>
{
impl
<
T
:
?
Sized
>
Deref
for
Project
Anchor
<
T
>
{
type
Target
=
T
;
fn
deref
(
&
self
)
->
&
T
{
...
...
@@ -117,7 +129,7 @@ impl<T: ?Sized> Deref for ProjectTarget<T> {
}
}
impl
<
T
:
?
Sized
>
DerefMut
for
Project
Target
<
T
>
{
impl
<
T
:
?
Sized
>
DerefMut
for
Project
Anchor
<
T
>
{
fn
deref_mut
(
&
mut
self
)
->
&
mut
T
{
&
mut
self
.value
}
...
...
@@ -125,87 +137,106 @@ impl<T: ?Sized> DerefMut for ProjectTarget<T> {
#[repr(transparent)]
pub
struct
Unclaimed
<
T
>
{
ptr
:
NonNull
<
Root
<
T
>>
,
// pinned shared read-write borrow
root_ptr
:
NonNull
<
Root
<
T
>>
,
}
impl
<
T
>
Drop
for
Unclaimed
<
T
>
{
fn
drop
(
&
mut
self
)
{
unsafe
{
debug_assert_eq!
((
*
self
.ptr
.as_ptr
())
.state
.get
(),
STATE_UNCLAIMED
);
(
*
self
.ptr
.as_ptr
())
.state
.set
(
STATE_UNPINNED
);
}
// SAFETY: The pointer is a pinned shared read-write borrow and we don't
// move out of it.
let
root
=
unsafe
{
self
.root_ptr
.as_mut
()
};
let
state
=
root
.state
.get_mut
();
debug_assert_eq!
(
*
state
,
STATE_UNCLAIMED
);
*
state
=
STATE_UNPINNED
;
}
}
impl
<
T
>
Unclaimed
<
T
>
{
pub
fn
claim
(
self
,
value
:
T
)
->
Xrc
<
T
>
{
unsafe
{
debug_assert_eq!
((
*
self
.ptr
.as_ptr
())
.state
.get
(),
STATE_UNCLAIMED
);
(
*
self
.ptr
.as_ptr
())
.state
.set
(
STATE_SHARED_INIT
);
}
pub
fn
claim
(
mut
self
,
value
:
T
)
->
Xrc
<
T
>
{
// SAFETY: The pointer is a pinned shared read-write borrow and we don't
// move out of it.
let
root
=
unsafe
{
self
.root_ptr
.as_mut
()
};
let
state
=
root
.state
.get_mut
();
debug_assert_eq!
(
*
state
,
STATE_UNCLAIMED
);
*
state
=
STATE_SHARED_INIT
;
// SAFETY: This is a partial write into a `MaybeUninit` behind a mutable reference.
unsafe
{
let
target
_value
=
ptr
::
addr_of_mut!
((
*
(
*
self
.ptr
.as_ptr
())
.target
.as_mut_ptr
())
.value
);
target
_value
.write
(
value
);
let
anchor
_value
=
ptr
::
addr_of_mut!
((
*
root
.anchor
.as_mut_ptr
())
.value
);
anchor
_value
.write
(
value
);
}
// shared read-only reborrow
let
target
=
unsafe
{
(
*
self
.ptr
.as_ptr
())
.target
.assume_init_ref
()
};
let
ptr
=
NonNull
::
from
(
target
);
// SAFETY: `root.anchor` is fully initialized, because we initialized
// `.root_ptr` in `Root::unclaimed` and `.value` above.
// Note that we intentionally create a shared read-only borrow here,
// because `Xrc` must work with read-only borrows.
let
anchor
=
unsafe
{
root
.anchor
.assume_init_ref
()
};
let
anchor_ptr
=
NonNull
::
from
(
anchor
);
mem
::
forget
(
self
);
Xrc
{
ptr
}
// SAFETY:
// * The pointer is a shared read-only borrow.
// * We set the state to "shared" above.
// * The `anchor` is derived from `root`, `root` is derived from
// `self.root_ptr` and `anchor.root_ptr` is `Some` and equal to
// `self.root_ptr`, because we set it up that way in `Root::unclaimed`.
// Therefore, `anchor` is derived from `anchor.root_ptr`.
// Because we don't do any (non-interior) mutable accesses of
// the root after this point, the anchor will remain valid.
Xrc
{
anchor_ptr
}
}
}
#[repr(transparent)]
pub
struct
Xrc
<
T
:
?
Sized
>
{
ptr
:
NonNull
<
ProjectTarget
<
T
>>
,
// shared read-only borrow that outlives `anchor.root_ptr`, which must be `Some`
// Note that `anchor_ptr` may be derived from `anchor.root_ptr` with an equal
// lifetime, which means that we can only write to interior mutable parts of the
// root, because otherwise the anchor may get invalidated.
anchor_ptr
:
NonNull
<
ProjectAnchor
<
T
>>
,
}
impl
<
T
:
?
Sized
>
Drop
for
Xrc
<
T
>
{
fn
drop
(
&
mut
self
)
{
let
Some
(
root
)
=
Self
::
inner
(
self
)
.root
.get
()
else
{
debug_assert!
(
false
,
"missing root"
);
unsafe
{
hint
::
unreachable_unchecked
();
}
};
let
root_header
=
Self
::
root_header
(
self
);
let
state_ref
=
unsafe
{
&
(
*
root
.as_ptr
())
.state
};
let
state
=
state_ref
.get
();
let
state
=
root_header
.state
.get
();
debug_assert!
(
state
<=
STATE_SHARED_MAX
);
if
state
!=
STATE_SHARED_INIT
{
state
_ref
.set
(
state
-
1
);
root_header
.
state
.set
(
state
-
1
);
return
;
}
state
_ref
.set
(
STATE_UNCLAIMED
);
root_header
.
state
.set
(
STATE_UNCLAIMED
);
let
reclaim_shim
=
unsafe
{
(
*
root
.as_ptr
())
.reclaim_shim
};
let
reclaim_shim
=
root_header
.reclaim_shim
;
let
root_erased
=
Self
::
root_ptr
(
self
);
// SAFETY:
// * We checked that there are no other `Xrc`s pointing to the same data.
// * We set the state to "unclaimed" and the old value is still in place.
unsafe
{
reclaim_shim
(
root
);
reclaim_shim
(
root
_erased
);
}
}
}
impl
<
T
>
Clone
for
Xrc
<
T
>
{
fn
clone
(
&
self
)
->
Self
{
let
root
=
Self
::
inner
(
self
)
.root
.get
();
let
Some
(
root
)
=
root
else
{
debug_assert!
(
false
,
"missing root"
);
unsafe
{
hint
::
unreachable_unchecked
();
}
};
let
root
=
Self
::
root_header
(
self
);
let
state_ref
=
unsafe
{
&
(
*
root
.as_ptr
())
.state
};
let
state
=
state_ref
.get
();
let
state
=
root
.state
.get
();
debug_assert!
(
state
<=
STATE_SHARED_MAX
);
if
state
==
STATE_SHARED_MAX
{
panic!
(
"ref count overflow"
);
}
state
_ref
.set
(
state
+
1
);
root
.
state
.set
(
state
+
1
);
Self
{
ptr
:
self
.ptr
}
// SAFETY: We increased the reference count above.
Self
{
anchor_ptr
:
self
.anchor_ptr
,
}
}
}
...
...
@@ -213,7 +244,7 @@ impl<T: ?Sized> Deref for Xrc<T> {
type
Target
=
T
;
fn
deref
(
&
self
)
->
&
T
{
&
Self
::
inne
r
(
self
)
.value
&
Self
::
ancho
r
(
self
)
.value
}
}
...
...
@@ -221,33 +252,56 @@ impl<T: ?Sized> Xrc<T> {
pub
fn
project
<
U
,
F
>
(
this
:
Self
,
f
:
F
)
->
Xrc
<
U
>
where
U
:
?
Sized
,
F
:
FnOnce
(
&
Project
Target
<
T
>
)
->
&
Project
Target
<
U
>
,
F
:
FnOnce
(
&
Project
Anchor
<
T
>
)
->
&
Project
Anchor
<
U
>
,
{
let
source
=
Self
::
inner
(
&
this
);
let
Some
(
source_root
)
=
source
.root
.get
()
else
{
debug_assert!
(
false
,
"missing root"
);
unsafe
{
hint
::
unreachable_unchecked
();
}
};
let
source
=
Self
::
anchor
(
&
this
);
let
target
=
f
(
source
);
if
let
Some
(
target_root
)
=
target
.root
.get
()
{
let
source_root
=
Self
::
root_ptr
(
&
this
);
if
let
Some
(
target_root
)
=
target
.root_ptr
.get
()
{
if
target_root
!=
source_root
{
panic!
(
"projected from different roots"
);
}
}
else
{
target
.root
.set
(
Some
(
source_root
));
target
.root
_ptr
.set
(
Some
(
source_root
));
}
let
ptr
=
NonNull
::
from
(
target
);
let
anchor_
ptr
=
NonNull
::
from
(
target
);
mem
::
forget
(
this
);
Xrc
{
ptr
}
// SAFETY:
// * We ensure that `target.root_ptr == source.root_ptr` above.
// * Because `source.root_ptr` is `Some`, `target.root_ptr` is also `Some`.
// * The higher-ranked lifetime bound on the closure ensures that `target`
// outlives `source`, which outlives `source.root_ptr`.
// Therefore, `target` also outlives `target.root_ptr`.
// * We forget `self` above, so the reference count is unchanged.
Xrc
{
anchor_ptr
}
}
fn
anchor
(
this
:
&
Self
)
->
&
ProjectAnchor
<
T
>
{
// SAFETY: The pointer is a shared read-write borrow.
unsafe
{
this
.anchor_ptr
.as_ref
()
}
}
/// Returns a read-write pointer to the root with full provenance.
fn
root_ptr
(
this
:
&
Self
)
->
NonNull
<
Root
<
Erased
>>
{
let
root_ptr
=
Self
::
anchor
(
this
)
.root_ptr
.get
();
// SAFETY: The root pointer must be set to `Some` before this
// `Xrc` is constructed.
unsafe
{
root_ptr
.unwrap_unchecked
()
}
}
fn
inner
(
this
:
&
Self
)
->
&
ProjectTarget
<
T
>
{
unsafe
{
this
.ptr
.as_ref
()
}
/// Returns a read-only reborrow of the erased root.
fn
root_header
(
this
:
&
Self
)
->
&
Root
<
Erased
>
{
// SAFETY: We can derive a new shared read-only borrow without invalidating
// other derived shared read-only borrows, including the anchor pointer.
// Because `Root<T>` has a fixed layout, `T` is at the end, and `Erased`
// is a zero-sized type with alignment 1, we can reinterpret `Root<T>`
// as `Root<Erased>`.
unsafe
{
Self
::
root_ptr
(
this
)
.as_ref
()
}
}
}
...
...
@@ -264,6 +318,7 @@ mod tests {
use
std
::
rc
::
Rc
;
fn
iter_pinned_mut
<
T
>
(
slice
:
Pin
<&
mut
[
T
]
>
)
->
impl
Iterator
<
Item
=
Pin
<&
mut
T
>>
{
// SAFETY: The elements of a slice are structurally pinned.
unsafe
{
slice
.get_unchecked_mut
()
...
...
@@ -370,13 +425,13 @@ mod tests {
fn
project
()
{
struct
Value
{
a
:
i32
,
b
:
Project
Target
<
i32
>
,
b
:
Project
Anchor
<
i32
>
,
}
let
root
=
pin!
(
Root
::
new
());
let
xrc
=
root
.unclaimed
()
.claim
(
NoReclaim
(
Value
{
a
:
1
,
b
:
Project
Target
::
new
(
2
),
b
:
Project
Anchor
::
new
(
2
),
}));
assert_eq!
(
xrc
.0
.a
,
1
);
assert_eq!
(
*
xrc
.0
.b
,
2
);
...
...
@@ -397,13 +452,13 @@ mod tests {
#[test]
#[should_panic
=
"projected from different roots"
]
fn
project_multiple
()
{
let
target
=
Rc
::
new
(
Project
Target
::
new
(
0
));
let
anchor
=
Rc
::
new
(
Project
Anchor
::
new
(
0
));
let
root1
=
pin!
(
Root
::
new
());
let
xrc1
=
root1
.unclaimed
()
.claim
(
NoReclaim
(
target
.clone
()));
let
xrc1
=
root1
.unclaimed
()
.claim
(
NoReclaim
(
anchor
.clone
()));
let
root2
=
pin!
(
Root
::
new
());
let
xrc2
=
root2
.unclaimed
()
.claim
(
NoReclaim
(
target
));
let
xrc2
=
root2
.unclaimed
()
.claim
(
NoReclaim
(
anchor
));
let
_
=
Xrc
::
project
(
xrc1
,
|
xrc
|
&*
xrc
.0
);
let
_
=
Xrc
::
project
(
xrc2
,
|
xrc
|
&*
xrc
.0
);
...
...
@@ -450,8 +505,8 @@ mod tests {
}
struct
Value
{
number
:
Project
Target
<
i32
>
,
string
:
Project
Target
<
String
>
,
number
:
Project
Anchor
<
i32
>
,
string
:
Project
Anchor
<
String
>
,
}
#[allow(clippy::declare_interior_mutable_const)]
...
...
@@ -466,8 +521,8 @@ mod tests {
let
mut
values
=
Vec
::
new
();
for
n
in
0
..
8
{
let
value
=
Value
{
number
:
Project
Target
::
new
(
n
),
string
:
Project
Target
::
new
(
n
.to_string
()),
number
:
Project
Anchor
::
new
(
n
),
string
:
Project
Anchor
::
new
(
n
.to_string
()),
};
let
xrc
=
alloc
.try_alloc
(
value
)
.ok
()
.unwrap
();
values
.push
(
xrc
);
...
...
@@ -490,8 +545,8 @@ mod tests {
.eq
(
0
..
8
));
let
value
=
Value
{
number
:
Project
Target
::
new
(
0
),
string
:
Project
Target
::
new
(
String
::
new
()),
number
:
Project
Anchor
::
new
(
0
),
string
:
Project
Anchor
::
new
(
String
::
new
()),
};
let
value
=
alloc
.try_alloc
(
value
)
.err
()
.unwrap
();
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment