This is an automated email from the ASF dual-hosted git repository.
ruihangl pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 6c7e2eeea1 [FFI][REFACTOR] Update Map ABI to enable flexible smallMap
switch (#18200)
6c7e2eeea1 is described below
commit 6c7e2eeea12ea327226ec56332229301a2139ab6
Author: Tianqi Chen <[email protected]>
AuthorDate: Sat Aug 9 16:43:24 2025 -0400
[FFI][REFACTOR] Update Map ABI to enable flexible smallMap switch (#18200)
This PR updates the Map ABI to use MSB in slots_ to indicate SmallMap.
The change would open doors for future changes to small map boundary
switch.
---
ffi/include/tvm/ffi/container/map.h | 96 ++++++++++++++++++++++++++-----------
1 file changed, 68 insertions(+), 28 deletions(-)
diff --git a/ffi/include/tvm/ffi/container/map.h
b/ffi/include/tvm/ffi/container/map.h
index 7892e20085..b1ca4f805e 100644
--- a/ffi/include/tvm/ffi/container/map.h
+++ b/ffi/include/tvm/ffi/container/map.h
@@ -221,6 +221,16 @@ class MapObj : public Object {
uint64_t size_;
/*! \brief number of slots */
uint64_t slots_;
+ /*!
+ * \brief Small layout tag mask
+ * \note The most significant bit is used to indicate the small map layout.
+ */
+ static constexpr uint64_t kSmallTagMask = static_cast<uint64_t>(1) << 63;
+ /*!
+ * \brief Check if the map is a small map
+ * \return True if the map is a small map
+ */
+ bool IsSmallMap() const { return (slots_ & kSmallTagMask) != 0ull; }
/*!
* \brief Optional data deleter when data is allocated separately
* and its deletion is not managed by MapObj::deleter_.
@@ -242,6 +252,13 @@ class SmallMapObj : public MapObj,
using MapObj::iterator;
using MapObj::KVType;
+ // Return the number of usable slots for Small layout (mask off tag).
+ /*!
+ * \brief Return the number of usable slots for Small layout (mask off tag).
+ * \return The number of usable slots
+ */
+ uint64_t NumSlots() const { return slots_ & ~kSmallTagMask; }
+
~SmallMapObj() {
KVType* begin = static_cast<KVType*>(data_);
for (uint64_t index = 0; index < size_; ++index) {
@@ -310,6 +327,11 @@ class SmallMapObj : public MapObj,
void erase(const iterator& position) { Erase(position.index); }
private:
+ /*!
+ * \brief Set the number of slots and attach tags bit.
+ * \param n The number of slots
+ */
+ void SetSlotsAndSmallLayoutTag(uint64_t n) { slots_ = (n & ~kSmallTagMask) |
kSmallTagMask; }
/*!
* \brief Remove a position in SmallMapObj
* \param index The position to be removed
@@ -344,7 +366,7 @@ class SmallMapObj : public MapObj,
ObjectPtr<SmallMapObj> p = make_inplace_array_object<SmallMapObj,
KVType>(n);
p->data_ = p->AddressOf(0);
p->size_ = 0;
- p->slots_ = n;
+ p->SetSlotsAndSmallLayoutTag(n);
return p;
}
/*!
@@ -386,15 +408,15 @@ class SmallMapObj : public MapObj,
itr->second = kv.second;
return;
}
- if (map_node->size_ < map_node->slots_) {
+ if (map_node->size_ < map_node->NumSlots()) {
KVType* ptr = static_cast<KVType*>(map_node->data_) + map_node->size_;
new (ptr) KVType(std::move(kv));
++map_node->size_;
return;
}
- uint64_t next_size = std::max(map_node->slots_ * 2, uint64_t(kInitSize));
+ uint64_t next_size = std::max(map_node->NumSlots() * 2,
uint64_t(kInitSize));
next_size = std::min(next_size, uint64_t(kMaxSize));
- TVM_FFI_ICHECK_GT(next_size, map_node->slots_);
+ TVM_FFI_ICHECK_GT(next_size, map_node->NumSlots());
ObjectPtr<Object> new_map = CreateFromRange(next_size, map_node->begin(),
map_node->end());
InsertMaybeReHash(std::move(kv), &new_map);
*map = std::move(new_map);
@@ -525,6 +547,12 @@ class DenseMapObj : public MapObj {
public:
using MapObj::iterator;
+ /*!
+ * \brief Return the number of usable slots for Dense layout (MSB clear =>
identity).
+ * \return The number of usable slots
+ */
+ uint64_t NumSlots() const { return slots_; }
+
/*!
* \brief Destroy the DenseMapObj
*/
@@ -558,7 +586,7 @@ class DenseMapObj : public MapObj {
*/
void erase(const iterator& position) {
uint64_t index = position.index;
- if (position.self != nullptr && index <= this->slots_) {
+ if (position.self != nullptr && index <= this->NumSlots()) {
Erase(ListNode(index, this));
}
}
@@ -817,7 +845,7 @@ class DenseMapObj : public MapObj {
}
/*! \brief Clear the container to empty, release all entries and memory
acquired */
void Reset() {
- uint64_t n_blocks = CalcNumBlocks(this->slots_);
+ uint64_t n_blocks = CalcNumBlocks(this->NumSlots());
for (uint64_t bi = 0; bi < n_blocks; ++bi) {
uint8_t* meta_ptr = GetBlock(bi)->bytes;
ItemType* data_ptr = reinterpret_cast<ItemType*>(GetBlock(bi)->bytes +
kBlockCap);
@@ -852,6 +880,8 @@ class DenseMapObj : public MapObj {
*/
static ObjectPtr<DenseMapObj> Empty(uint32_t fib_shift, uint64_t n_slots) {
TVM_FFI_ICHECK_GT(n_slots, uint64_t(SmallMapObj::kMaxSize));
+ // Ensure even slot count (power-of-two expected by callers; this guard
+ // makes the method robust if a non-even value slips through).
ObjectPtr<DenseMapObj> p = make_object<DenseMapObj>();
uint64_t n_blocks = CalcNumBlocks(n_slots);
Block* block = new Block[n_blocks];
@@ -860,7 +890,7 @@ class DenseMapObj : public MapObj {
// in another shared-lib that may have different malloc/free behavior
// it will still be safe.
p->data_deleter_ = BlockDeleter;
- p->slots_ = n_slots;
+ p->SetSlotsAndDenseLayoutTag(n_slots);
p->size_ = 0;
p->fib_shift_ = fib_shift;
p->iter_list_head_ = kInvalidIndex;
@@ -877,13 +907,13 @@ class DenseMapObj : public MapObj {
*/
static ObjectPtr<DenseMapObj> CopyFrom(DenseMapObj* from) {
ObjectPtr<DenseMapObj> p = make_object<DenseMapObj>();
- uint64_t n_blocks = CalcNumBlocks(from->slots_);
+ uint64_t n_blocks = CalcNumBlocks(from->NumSlots());
p->data_ = new Block[n_blocks];
// assign block deleter so even if we take re-alloc data
// in another shared-lib that may have different malloc/free behavior
// it will still be safe.
p->data_deleter_ = BlockDeleter;
- p->slots_ = from->slots_;
+ p->SetSlotsAndDenseLayoutTag(from->NumSlots());
p->size_ = from->size_;
p->fib_shift_ = from->fib_shift_;
p->iter_list_head_ = from->iter_list_head_;
@@ -919,9 +949,9 @@ class DenseMapObj : public MapObj {
map_node->IterListPushBack(iter);
return;
}
- TVM_FFI_ICHECK_GT(map_node->slots_, uint64_t(SmallMapObj::kMaxSize));
+ TVM_FFI_ICHECK(!map_node->IsSmallMap());
// Otherwise, start rehash
- ObjectPtr<Object> p = Empty(map_node->fib_shift_ - 1, map_node->slots_ *
2);
+ ObjectPtr<Object> p = Empty(map_node->fib_shift_ - 1, map_node->NumSlots()
* 2);
// need to insert in the same order as the original map
for (uint64_t index = map_node->iter_list_head_; index != kInvalidIndex;) {
@@ -947,7 +977,7 @@ class DenseMapObj : public MapObj {
* \brief Check whether the hash table is full
* \return A boolean indicating whether hash table is full
*/
- bool IsFull() const { return size_ + 1 > slots_ * kMaxLoadFactor; }
+ bool IsFull() const { return size_ + 1 > NumSlots() * kMaxLoadFactor; }
/*!
* \brief Increment the pointer
* \param index The pointer to be incremented
@@ -1089,7 +1119,7 @@ class DenseMapObj : public MapObj {
}
// the probing will go to next position and round back to stay within the
// correct range of the slots
- index = (index + offset) % self->slots_;
+ index = (index + offset) % self->NumSlots();
block = self->GetBlock(index / kBlockCap);
return true;
}
@@ -1110,7 +1140,7 @@ class DenseMapObj : public MapObj {
for (uint8_t idx = 1; idx < kNumJumpDists; ++idx) {
// the probing will go to next position and round back to stay within
the
// correct range of the slots
- ListNode candidate((index + NextProbeLocation(idx)) % self->slots_,
self);
+ ListNode candidate((index + NextProbeLocation(idx)) %
self->NumSlots(), self);
if (candidate.IsEmpty()) {
*jump = idx;
*result = candidate;
@@ -1164,14 +1194,23 @@ class DenseMapObj : public MapObj {
return kNextProbeLocation[index];
}
friend class MapObj;
+
+ private:
+ /*!
+ * \brief Set the number of slots and attach tags bit.
+ * \param n The number of slots
+ */
+ void SetSlotsAndDenseLayoutTag(uint64_t n) {
+ TVM_FFI_ICHECK(((n & kSmallTagMask) == 0ull)) << "DenseMap expects MSB
clear";
+ slots_ = n;
+ }
};
#define TVM_FFI_DISPATCH_MAP(base, var, body) \
{ \
using TSmall = SmallMapObj*; \
using TDense = DenseMapObj*; \
- uint64_t slots = base->slots_; \
- if (slots <= SmallMapObj::kMaxSize) { \
+ if (base->IsSmallMap()) { \
TSmall var = static_cast<TSmall>(base); \
body; \
} else { \
@@ -1184,8 +1223,7 @@ class DenseMapObj : public MapObj {
{ \
using TSmall = const SmallMapObj*; \
using TDense = const DenseMapObj*; \
- uint64_t slots = base->slots_; \
- if (slots <= SmallMapObj::kMaxSize) { \
+ if (base->IsSmallMap()) { \
TSmall var = static_cast<TSmall>(base); \
body; \
} else { \
@@ -1249,7 +1287,7 @@ inline void MapObj::erase(const MapObj::iterator&
position) {
inline ObjectPtr<MapObj> MapObj::Empty() { return SmallMapObj::Empty(); }
inline ObjectPtr<MapObj> MapObj::CopyFrom(MapObj* from) {
- if (from->slots_ <= SmallMapObj::kMaxSize) {
+ if (from->IsSmallMap()) {
return SmallMapObj::CopyFrom(static_cast<SmallMapObj*>(from));
} else {
return DenseMapObj::CopyFrom(static_cast<DenseMapObj*>(from));
@@ -1288,20 +1326,22 @@ inline ObjectPtr<Object>
MapObj::CreateFromRange(IterType first, IterType last)
}
inline void MapObj::InsertMaybeReHash(KVType&& kv, ObjectPtr<Object>* map) {
- constexpr uint64_t kSmallMapMaxSize = SmallMapObj::kMaxSize;
MapObj* base = static_cast<MapObj*>(map->get());
#if TVM_FFI_DEBUG_WITH_ABI_CHANGE
base->state_marker++;
#endif // TVM_FFI_DEBUG_WITH_ABI_CHANGE
- if (base->slots_ < kSmallMapMaxSize) {
- SmallMapObj::InsertMaybeReHash(std::move(kv), map);
- } else if (base->slots_ == kSmallMapMaxSize) {
- if (base->size_ < base->slots_) {
+ if (base->IsSmallMap()) {
+ SmallMapObj* sm = static_cast<SmallMapObj*>(base);
+ if (sm->NumSlots() < SmallMapObj::kMaxSize) {
SmallMapObj::InsertMaybeReHash(std::move(kv), map);
- } else {
- ObjectPtr<Object> new_map = MapObj::CreateFromRange(base->begin(),
base->end());
- DenseMapObj::InsertMaybeReHash(std::move(kv), &new_map);
- *map = std::move(new_map);
+ } else if (sm->NumSlots() == SmallMapObj::kMaxSize) {
+ if (base->size_ < sm->NumSlots()) {
+ SmallMapObj::InsertMaybeReHash(std::move(kv), map);
+ } else {
+ ObjectPtr<Object> new_map = MapObj::CreateFromRange(base->begin(),
base->end());
+ DenseMapObj::InsertMaybeReHash(std::move(kv), &new_map);
+ *map = std::move(new_map);
+ }
}
} else {
DenseMapObj::InsertMaybeReHash(std::move(kv), map);