fa9fbd5e300d8ec94e9c1f75160d0e86740e434b
[folly.git] / folly / PackedSyncPtr.h
1 /*
2  * Copyright 2014 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #ifndef FOLLY_PACKEDSYNCPTR_H_
18 #define FOLLY_PACKEDSYNCPTR_H_
19
20 #include "folly/Portability.h"
21
22 #if !FOLLY_X64
23 # error "PackedSyncPtr is x64-specific code."
24 #endif
25
26 /*
27  * An 8-byte pointer with an integrated spin lock and 15-bit integer
28  * (you can use this for a size of the allocation, if you want, or
29  * something else, or nothing).
30  *
31  * This is using an x64-specific detail about the effective virtual
32  * address space.  Long story short: the upper two bytes of all our
33  * pointers will be zero in reality---and if you have a couple billion
34  * such pointers in core, it makes pretty good sense to try to make
35  * use of that memory.  The exact details can be perused here:
36  *
37  *    http://en.wikipedia.org/wiki/X86-64#Canonical_form_addresses
38  *
39  * This is not a "smart" pointer: nothing automagical is going on
40  * here.  Locking is up to the user.  Resource deallocation is up to
41  * the user.  Locks are never acquired or released outside explicit
42  * calls to lock() and unlock().
43  *
44  * Change the value of the raw pointer with set(), but you must hold
45  * the lock when calling this function if multiple threads could be
46  * using this class.
47  *
48  * TODO(jdelong): should we use the low order bit for the lock, so we
49  * get a whole 16-bits for our integer?  (There's also 2 more bits
50  * down there if the pointer comes from malloc.)
51  *
52  * @author Spencer Ahrens <sahrens@fb.com>
53  * @author Jordan DeLong <delong.j@fb.com>
54  */
55
56 #include "folly/SmallLocks.h"
57 #include <type_traits>
58 #include <glog/logging.h>
59
60 namespace folly {
61
62 template<class T>
63 class PackedSyncPtr {
64   // This just allows using this class even with T=void.  Attempting
65   // to use the operator* or operator[] on a PackedSyncPtr<void> will
66   // still properly result in a compile error.
67   typedef typename std::add_lvalue_reference<T>::type reference;
68
69 public:
70   /*
71    * If you default construct one of these, you must call this init()
72    * function before using it.
73    *
74    * (We are avoiding a constructor to ensure gcc allows us to put
75    * this class in packed structures.)
76    */
77   void init(T* initialPtr = 0, uint16_t initialExtra = 0) {
78     auto intPtr = reinterpret_cast<uintptr_t>(initialPtr);
79     CHECK(!(intPtr >> 48));
80     data_.init(intPtr);
81     setExtra(initialExtra);
82   }
83
84   /*
85    * Sets a new pointer.  You must hold the lock when calling this
86    * function, or else be able to guarantee no other threads could be
87    * using this PackedSyncPtr<>.
88    */
89   void set(T* t) {
90     auto intPtr = reinterpret_cast<uintptr_t>(t);
91     auto shiftedExtra = uintptr_t(extra()) << 48;
92     CHECK(!(intPtr >> 48));
93     data_.setData(intPtr | shiftedExtra);
94   }
95
96   /*
97    * Get the pointer.
98    *
99    * You can call any of these without holding the lock, with the
100    * normal types of behavior you'll get on x64 from reading a pointer
101    * without locking.
102    */
103   T* get() const {
104     return reinterpret_cast<T*>(data_.getData() & (-1ull >> 16));
105   }
106   T* operator->() const { return get(); }
107   reference operator*() const { return *get(); }
108   reference operator[](std::ptrdiff_t i) const { return get()[i]; }
109
110   // Synchronization (logically const, even though this mutates our
111   // locked state: you can lock a const PackedSyncPtr<T> to read it).
112   void lock() const { data_.lock(); }
113   void unlock() const { data_.unlock(); }
114   bool try_lock() const { return data_.try_lock(); }
115
116   /*
117    * Access extra data stored in unused bytes of the pointer.
118    *
119    * It is ok to call this without holding the lock.
120    */
121   uint16_t extra() const {
122     return data_.getData() >> 48;
123   }
124
125   /*
126    * Don't try to put anything into this that has the high bit set:
127    * that's what we're using for the mutex.
128    *
129    * Don't call this without holding the lock.
130    */
131   void setExtra(uint16_t extra) {
132     CHECK(!(extra & 0x8000));
133     auto ptr = data_.getData() & (-1ull >> 16);
134     data_.setData((uintptr_t(extra) << 48) | ptr);
135   }
136
137   // Logically private, but we can't have private data members and
138   // still be considered a POD.  (In C++11 we are still a standard
139   // layout struct if this is private, but it doesn't matter, since
140   // gcc (4.6) won't let us use this with attribute packed still in
141   // that case.)
142   PicoSpinLock<uintptr_t> data_;
143 };
144
145 static_assert(sizeof(PackedSyncPtr<void>) == 8,
146               "PackedSyncPtr should be only 8 bytes---something is "
147               "messed up");
148
149 }
150
151 #endif
152