@@ -22,23 +22,27 @@ public static boolean isActive() {
2222 }
2323
2424 /*
25- * tagId bit layout: [63 intercepted] [62-48 globalSerial (15 bits)] [47-32 fieldPos]
26- * [31-0 nameHash]. Bit 63 (the sign bit) marks a tag the tag interceptor must see, so the check
27- * is a single {@code tagId < 0}. globalSerial is globally unique per known tag; fieldPos is its
28- * slot in the global positional layout (TagMap.knownEntries index); nameHash is
29- * TagMap.Entry#_hash(name) and is layout-independent. Unknown (string-only) tags have the upper
30- * 32 bits zero. NOTE: TagMap.Entry decodes nameHash inline as (int) tagId on its hot path, so the
31- * low-32 encoding here must stay in sync with that.
25+ * tagId bit layout: [63 intercepted] [62-48 globalSerial (15 bits)] [47-42 group-decl (6 bits)]
26+ * [41-32 field-decl (10 bits)] [31-0 reserved, zero]. Bit 63 (the sign bit) marks a tag the tag
27+ * interceptor must see, so the check is a single {@code tagId < 0}. globalSerial is globally
28+ * unique per known tag. The middle 16 bits carry the tag's DECLARATION coordinate: group-decl is
29+ * the declaration group (the trace-level tier / each span type / each mixin is a group),
30+ * field-decl is the tag's ordinal within that group (restarts per group). Together they drive the dense
31+ * store's two-tier presence fast path (per-map group mask, then field bloom) — see {@link
32+ * TagMap}. The low 32 bits are unused for known ids (the whole id is fully determined by
33+ * serial + coordinate, so the generator can emit a literal); they are free for a wider
34+ * group-decl/field-decl split later. Unknown (string-only) custom tags are NOT known ids — they
35+ * key off {@code TagMap.Entry#_hash(name)} in their own bucket path and never enter here.
3236 */
3337 public static int globalSerial (long tagId ) {
3438 return (int ) ((tagId >>> 48 ) & 0x7FFF );
3539 }
3640
3741 /**
38- * Flag bit (the sign bit) marking a tag the tag interceptor must process — reserved/"virtual"
39- * tags AND intercepted-but-stored tags (e.g. http.method, which the interceptor side-effects and
40- * also stores). Encoded in the id so {@code DDSpanContext.setTag(long)} can route with a single
41- * sign test ({@link #isIntercepted}) instead of resolving the name. Non-intercepted tags (peer.*,
42+ * Flag bit (the sign bit) marking a tag the tag interceptor must process — reserved tags AND
43+ * intercepted-but-stored tags (e.g. http.method, which the interceptor side-effects and also
44+ * stores). Encoded in the id so {@code DDSpanContext.setTag(long)} can route with a single sign
45+ * test ({@link #isIntercepted}) instead of resolving the name. Non-intercepted tags (peer.*,
4246 * base.service, …) leave it clear and take the fast store path. Must agree with the interceptor's
4347 * name-based {@code needsIntercept} for every assigned id.
4448 */
@@ -54,25 +58,54 @@ public static long intercepted(long tagId) {
5458 return tagId | INTERCEPTED ;
5559 }
5660
57- public static int fieldPos (long tagId ) {
58- return (int ) ((tagId >>> 32 ) & 0xFFFF );
61+ // The middle 16 bits [47-32] hold the declaration coordinate, split so the two-tier presence
62+ // fast path can read each half cheaply: group-decl (high 6 bits, [47-42]) selects the tag's
63+ // declaration group (<=64 groups -> one long group mask, where a clear bit proves the whole
64+ // group absent and enables a bulk fast insert); field-decl (low 10 bits, [41-32]) is the tag's
65+ // ordinal within that group (<=1024 tags/group), which the field bloom hashes (field-decl & 63)
66+ // once a group is present. See TagMap's dense-store fast path.
67+ static final int GROUP_DECL_SHIFT = 42 ;
68+ static final int GROUP_DECL_MASK = 0x3F ; // 6 bits
69+ static final int FIELD_DECL_SHIFT = 32 ;
70+ static final int FIELD_DECL_MASK = 0x3FF ; // 10 bits
71+
72+ /**
73+ * The tag's declaration group: the trace-level tier, a span type, or a mixin. Assigned by the tag
74+ * registry; drives the two-tier presence fast path's group mask.
75+ */
76+ public static int groupDecl (long tagId ) {
77+ return (int ) ((tagId >>> GROUP_DECL_SHIFT ) & GROUP_DECL_MASK );
5978 }
6079
61- public static int nameHash (long tagId ) {
62- return (int ) tagId ;
80+ /** The tag's ordinal within its declaration group. */
81+ public static int fieldDecl (long tagId ) {
82+ return (int ) ((tagId >>> FIELD_DECL_SHIFT ) & FIELD_DECL_MASK );
6383 }
6484
6585 /**
66- * globalSerial partition. {@code [1, FIRST_STORED_SERIAL)} is reserved for "virtual" tags that
67- * are specially handled (redirected to span fields or processed by the tag interceptor) and are
68- * NOT stored in the TagMap — these are hand-assigned in tracer core. {@code [FIRST_STORED_SERIAL,
69- * ..]} is for generated convention tags that ARE stored (slotted/bucketed). {@code globalSerial
70- * == 0} means unknown / string-only. Both core and the code generator must agree on this
71- * boundary.
86+ * globalSerial partition. {@code [1, FIRST_STORED_SERIAL)} is the RESERVED tier and {@code
87+ * [FIRST_STORED_SERIAL, ..]} is the STORED tier; {@code globalSerial == 0} means unknown /
88+ * string-only. Both core and the code generator must agree on this boundary.
89+ *
90+ * <p><b>Reserved</b> is the shared mechanism: the tracer reserves the key and handles it itself
91+ * instead of putting it in the TagMap. It says nothing about whether a value exists — that splits
92+ * into two kinds (the {@code kind:} in the overlay):
93+ *
94+ * <ul>
95+ * <li><b>structural</b> — the value <i>does</i> exist, it just lives in a first-class
96+ * span/trace field (service, resource.name, error, span.type, origin), not the tag map.
97+ * <li><b>directive</b> — there is <i>no</i> stored value; the key is a command that triggers
98+ * trace behavior (sampling.priority, manual.keep, measured).
99+ * </ul>
100+ *
101+ * "virtual" over-claims non-existence (wrong for structural) and "built-in" over-claims existence
102+ * (wrong for directive), so the tier is named for the mechanism they share: reserved. These are
103+ * hand-assigned in the overlay. <b>Stored</b> tags are the generated convention tags that ARE put
104+ * in the map (slotted/bucketed).
72105 */
73106 public static final int FIRST_STORED_SERIAL = 256 ;
74107
75- /** True if the tagId names a reserved "virtual"/specially-handled tag (not stored in the map) . */
108+ /** True if the tagId names a reserved (structural/directive) tag — handled, not stored . */
76109 public static boolean isReserved (long tagId ) {
77110 int globalSerial = globalSerial (tagId );
78111 return globalSerial > 0 && globalSerial < FIRST_STORED_SERIAL ;
@@ -84,48 +117,53 @@ public static boolean isStored(long tagId) {
84117 }
85118
86119 /**
87- * Sentinel {@code fieldPos } meaning "no positional slot". It is the maximum value the 16 -bit
88- * fieldPos field can hold, so it always compares {@code >= slotCount()} and routes to the hash
120+ * Sentinel {@code field-decl } meaning "no positional slot". It is the maximum value the 10 -bit
121+ * field-decl field can hold, so it always compares {@code >= slotCount()} and routes to the hash
89122 * buckets rather than the fast positional array. Two kinds of tagId use it:
90123 *
91124 * <ul>
92- * <li>Reserved/virtual tags ({@code globalSerial < FIRST_STORED_SERIAL}) — not stored at all;
93- * the sentinel just guarantees an incidental store never lands in a slot.
125+ * <li>Reserved tags ({@code globalSerial < FIRST_STORED_SERIAL}) — not stored at all; the
126+ * sentinel just guarantees an incidental store never lands in a slot.
94127 * <li>Unslotted stored tags ({@code globalSerial >= FIRST_STORED_SERIAL}) — "low-priority" tags
95128 * that get a stable id (and so {@code keyOf}/{@code nameOf} unification with their string
96- * form) but are deliberately not given a slot, so they live in the buckets and don't widen
97- * {@code knownEntries[]} for every span. {@code getEntry(long)} for these resolves the name
98- * and rehashes — the cost of not owning a slot.
129+ * form) but are deliberately not given a slot, so they live in the buckets. {@code
130+ * getEntry(long)} for these resolves the name and rehashes — the cost of not owning a slot.
99131 * </ul>
100132 */
101- public static final int NO_SLOT = 0xFFFF ;
133+ public static final int NO_SLOT = FIELD_DECL_MASK ; // field-decl all-ones sentinel
102134
103135 /**
104136 * True if the tagId names a stored tag that deliberately has no positional slot (bucket-only).
105137 */
106138 public static boolean isUnslotted (long tagId ) {
107- return isStored (tagId ) && fieldPos (tagId ) == NO_SLOT ;
139+ return isStored (tagId ) && fieldDecl (tagId ) == NO_SLOT ;
108140 }
109141
110142 /**
111- * Builds a tagId from its parts: {@code globalSerial} (globally unique per known tag), {@code
112- * fieldPos } (the tag's slot within its span type's positional table ), and the tag {@code name}
113- * (whose hash is computed via the same function the runtime uses, so the low 32 bits match {@link
114- * TagMap.Entry#hash()}). Inverse of {@link #globalSerial}/{@link #fieldPos }/{@link #nameHash }.
143+ * Builds a tagId from all three parts: {@code globalSerial} (globally unique per known tag),
144+ * {@code groupDecl } (its declaration group ), and {@code fieldDecl} (its ordinal within that
145+ * group). The low 32 bits are zero, so the id is fully determined by these parts — the generator
146+ * emits it as a literal. Inverse of {@link #globalSerial}/{@link #groupDecl }/{@link #fieldDecl }.
115147 * Intended for the code generator and tests.
116148 */
117- public static long tagId (int globalSerial , int fieldPos , String name ) {
118- long nameHash = TagMap .Entry ._hash (name ) & 0xFFFFFFFFL ;
119- return ((long ) globalSerial << 48 ) | ((long ) (fieldPos & 0xFFFF ) << 32 ) | nameHash ;
149+ public static long tagId (int globalSerial , int groupDecl , int fieldDecl ) {
150+ return ((long ) globalSerial << 48 )
151+ | ((long ) (groupDecl & GROUP_DECL_MASK ) << GROUP_DECL_SHIFT )
152+ | ((long ) (fieldDecl & FIELD_DECL_MASK ) << FIELD_DECL_SHIFT );
153+ }
154+
155+ /** Builds a tagId in group 0 — shorthand for {@link #tagId(int, int, int)} with group-decl 0. */
156+ public static long tagId (int globalSerial , int fieldDecl ) {
157+ return tagId (globalSerial , 0 , fieldDecl );
120158 }
121159
122160 /**
123- * Builds a tagId with no positional slot ({@code fieldPos == }{@link #NO_SLOT}). Use for reserved
124- * "virtual" tags and for "low-priority" stored tags that get a stable id but are intentionally
161+ * Builds a tagId with no positional slot ({@code field-decl == }{@link #NO_SLOT}). Use for
162+ * reserved tags and for "low-priority" stored tags that get a stable id but are intentionally
125163 * kept out of the fast slot array (they route to the hash buckets). See {@link #NO_SLOT}.
126164 */
127- public static long tagId (int globalSerial , String name ) {
128- return tagId (globalSerial , NO_SLOT , name );
165+ public static long tagId (int globalSerial ) {
166+ return tagId (globalSerial , NO_SLOT );
129167 }
130168
131169 // Number of positional slots in the global layout = (max stored fieldPos) + 1, declared by the
0 commit comments