@@ -36,6 +36,7 @@ bool locator_list_node_t::mark_for_deletion()
3636 {
3737 return false ;
3838 }
39+
3940 uint64_t desired_word{data_word | c_deleted_flag_mask};
4041 if (data_word.compare_exchange_strong (expected_word, desired_word))
4142 {
@@ -46,45 +47,93 @@ bool locator_list_node_t::mark_for_deletion()
4647 return true ;
4748}
4849
49- void type_index_t::register_type (common::gaia_type_t type)
50+ std::atomic< type_index_entry_t >& type_index_t::get_type_index_entry (common::gaia_type_t type)
5051{
51- if (type_index_entries_count >= c_max_types)
52- {
53- throw type_limit_exceeded_internal ();
54- }
55- type_index_entries[type_index_entries_count++].type = type;
56- }
52+ ASSERT_PRECONDITION (type.is_valid (), " Cannot call get_type_index_entry() with an invalid type!" );
5753
58- gaia_locator_t type_index_t::get_first_locator (common::gaia_type_t type)
59- {
6054 // REVIEW: With our current limit of 64 types, linear search should be
6155 // fine (the whole array is at most 8 cache lines, so should almost
6256 // always be in L1 cache), but with more types we'll eventually need
63- // sublinear search complexity.
64- for (size_t i = 0 ; i < type_index_entries_count; ++i)
57+ // sublinear search complexity (e.g., a hash table).
58+
59+ // Scan until the end of the array. (We could stop at the first
60+ // uninitialized entry, but the branch is likely not worth it for such a
61+ // small array; see e.g.
62+ // https://dirtyhandscoding.wordpress.com/2017/08/25/performance-comparison-linear-search-vs-binary-search/.)
63+ for (size_t i = 0 ; i < std::size (type_index_entries); ++i)
6564 {
66- if (type_index_entries[i].type == type)
65+ auto & entry_ref = type_index_entries[i];
66+ auto entry_val = entry_ref.load ();
67+ if (entry_val.type == type)
6768 {
68- return gaia_locator_t (type_index_entries[i]. first_locator ) ;
69+ return entry_ref ;
6970 }
7071 }
71- ASSERT_UNREACHABLE (" Type must be registered before accessing its locator list!" );
72+ // If we reach the end of the array without finding the entry for this type,
73+ // the precondition has been violated.
74+ ASSERT_UNREACHABLE (" Type must be registered before calling get_type_index_entry()!" );
7275}
7376
74- bool type_index_t::set_first_locator (
75- common::gaia_type_t type, gaia_locator_t expected_locator, gaia_locator_t desired_locator)
77+ bool type_index_t::register_type (common::gaia_type_t type)
7678{
77- gaia_locator_t ::value_type expected_value = expected_locator.value ();
78- gaia_locator_t ::value_type desired_value = desired_locator.value ();
79-
80- for (size_t i = 0 ; i < type_index_entries_count; ++i)
79+ ASSERT_PRECONDITION (type.is_valid (), " Cannot call register_type() with an invalid type!" );
80+
81+ // This implements the insert operation on a lock-free set. Inserting a
82+ // duplicate element is prevented by CAS semantics: each concurrent insert
83+ // uses the next uninitialized array entry (there can be no "holes" in the
84+ // array because entries only go from zero to nonzero, and we never scan
85+ // past an entry initially read as zero until a CAS shows it is nonzero), so
86+ // for any two concurrent inserts, one of them (the one that initializes the
87+ // higher-indexed entry) must see the other's insert, and abort if has the
88+ // same value.
89+ //
90+ // Scan until the first uninitialized entry or the end of the array,
91+ // whichever comes first.
92+ for (size_t i = 0 ; i < std::size (type_index_entries); ++i)
8193 {
82- if (type_index_entries[i].type == type)
94+ auto & entry_ref = type_index_entries[i];
95+ auto entry_val = entry_ref.load ();
96+ // The type was already registered.
97+ if (entry_val.type == type)
8398 {
84- return type_index_entries[i].first_locator .compare_exchange_strong (expected_value, desired_value);
99+ return false ;
100+ }
101+
102+ // Try to initialize the first uninitialized entry.
103+ //
104+ // REVIEW: This could technically be a relaxed load, because the
105+ // subsequent CAS will detect a stale read. However, we don't currently
106+ // specify non-default memory orderings anywhere, and I think we should
107+ // only change this policy on the basis of profiling data.
108+ if (entry_val.type == common::c_invalid_gaia_type)
109+ {
110+ type_index_entry_t expected_entry{common::c_invalid_gaia_type, c_invalid_gaia_locator};
111+ type_index_entry_t desired_entry{type, c_invalid_gaia_locator};
112+
113+ // If the CAS succeeds, we are done, otherwise try the next entry.
114+ if (entry_ref.compare_exchange_strong (expected_entry, desired_entry))
115+ {
116+ return true ;
117+ }
85118 }
86119 }
87- ASSERT_UNREACHABLE (" Type must be registered before accessing its locator list!" );
120+
121+ // We reached the end of the array without finding an uninitialized entry.
122+ throw type_limit_exceeded_internal ();
123+ }
124+
125+ gaia_locator_t type_index_t::get_first_locator (common::gaia_type_t type)
126+ {
127+ return get_type_index_entry (type).load ().first_locator ;
128+ }
129+
130+ bool type_index_t::try_set_first_locator (
131+ common::gaia_type_t type, gaia_locator_t expected_locator, gaia_locator_t desired_locator)
132+ {
133+ type_index_entry_t expected_entry{type, expected_locator};
134+ type_index_entry_t desired_entry{type, desired_locator};
135+
136+ return get_type_index_entry (type).compare_exchange_strong (expected_entry, desired_entry);
88137}
89138
90139locator_list_node_t * type_index_t::get_list_node (gaia_locator_t locator)
@@ -127,7 +176,7 @@ void type_index_t::add_locator(common::gaia_type_t type, gaia_locator_t locator)
127176
128177 // Now try to point the list head to the new node, retrying if it
129178 // was concurrently pointed to another node.
130- if (set_first_locator (type, first_locator, locator))
179+ if (try_set_first_locator (type, first_locator, locator))
131180 {
132181 break ;
133182 }
0 commit comments