forked from nulang-org/nulang
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalue_layout.rs
More file actions
305 lines (270 loc) · 9.98 KB
/
Copy pathvalue_layout.rs
File metadata and controls
305 lines (270 loc) · 9.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
//! Canonical i64-tagged value layout for Nulang.
//!
//! This module owns the single source of truth for the bit layout used by the
//! VM, JIT runtime helpers, typed compiler, WASM backend, and Python
//! marshalling layer. Keeping the constants in one place prevents tag
//! collisions and silent divergence between interpretation and compiled code.
//!
//! # Layout
//!
//! All values are represented as `u64` with the upper 16 bits encoding the
//! type tag and the lower 48 bits carrying the payload:
//!
//! ```text
//! |---- tag (16 bits) ----|-------- payload (48 bits) --------|
//! ```
//!
//! This scheme replaces the original NaN-boxing approach. The bit patterns
//! are identical to the old NaN-boxed layout (the tags occupy the IEEE 754
//! quiet-NaN range), but we now treat values as `i64`/`u64` integers rather
//! than `f64` bit patterns. This makes the representation immune to NaN
//! canonicalization in WASM engines while preserving the 8-byte value
//! footprint and the single-register property.
//!
//! # Rationale
//!
//! WASM engines normalize (canonicalize) floating-point NaN bit patterns,
//! which would silently corrupt type tags stored in NaN payload bits.
//! By treating values as `i64` integers and using integer shift/mask
//! operations for tag extraction, the representation is fully deterministic
//! across all targets: native, WASM, and any future backend.
//!
//! Floats are stored as their raw IEEE-754 bit pattern. Any bit pattern whose
//! upper 16 bits do not match a known type tag is interpreted as a float
//! (the current tag set occupies the quiet-NaN range 0x7FF6–0x7FFE, so no
//! valid non-NaN float will collide).
// ---------------------------------------------------------------------------
// Masks
// ---------------------------------------------------------------------------
/// Mask for the upper 16 tag bits.
pub const TAG_MASK: u64 = 0xFFFF_0000_0000_0000;
/// Mask for the lower 48 payload bits.
pub const PAYLOAD_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
/// Bit 47 of the payload, used for sign-extending 48-bit signed integers.
pub const SIGN_BIT: u64 = 0x0000_8000_0000_0000;
/// Number of bits to shift right to extract the tag (upper 16 bits → low 16).
pub const TAG_SHIFT: u32 = 48;
// ---------------------------------------------------------------------------
// Type tags (upper 16 bits of the i64 value)
// ---------------------------------------------------------------------------
/// Tag for `nil`.
pub const TAG_NIL: u64 = 0x7FF8_0000_0000_0000;
/// Tag for `unit`.
pub const TAG_UNIT: u64 = 0x7FF9_0000_0000_0000;
/// Tag for booleans. Payload bit 0: false=0, true=1.
pub const TAG_BOOL: u64 = 0x7FFA_0000_0000_0000;
/// Tag for integers. Payload is a 48-bit signed value.
pub const TAG_INT: u64 = 0x7FFB_0000_0000_0000;
/// Tag for heap pointers. Payload is a heap offset.
pub const TAG_PTR: u64 = 0x7FFC_0000_0000_0000;
/// Tag for actor references.
pub const TAG_ACTOR: u64 = 0x7FFD_0000_0000_0000;
/// Tag for interned string IDs.
pub const TAG_STRING: u64 = 0x7FFE_0000_0000_0000;
/// Tag for closure references.
pub const TAG_CLOSURE: u64 = 0x7FF7_0000_0000_0000;
// ---------------------------------------------------------------------------
// Tag extraction helpers (i64-based — no f64 bit-casting)
// ---------------------------------------------------------------------------
/// Extract the upper 16 tag bits from a raw value.
#[inline]
pub fn tag_of(raw: u64) -> u64 {
raw >> TAG_SHIFT
}
/// True when `raw` carries an integer tag.
#[inline]
pub fn is_int_raw(raw: u64) -> bool {
(raw & TAG_MASK) == TAG_INT
}
/// True when `raw` carries a heap-pointer tag.
#[inline]
pub fn is_ptr_raw(raw: u64) -> bool {
(raw & TAG_MASK) == TAG_PTR
}
/// Sign-extend a 48-bit signed payload to a full `i64`.
#[inline]
pub fn sext48(bits: u64) -> i64 {
if bits & SIGN_BIT != 0 {
(bits | 0xFFFF_0000_0000_0000) as i64
} else {
bits as i64
}
}
/// Extract the integer payload from a tagged value (assumes `is_int_raw`).
#[inline]
pub fn as_int_raw(raw: u64) -> i64 {
sext48(raw & PAYLOAD_MASK)
}
/// Extract the heap-pointer payload from a tagged value (assumes `is_ptr_raw`).
#[inline]
pub fn as_ptr_raw(raw: u64) -> u32 {
(raw & 0xFFFF_FFFF) as u32
}
/// Pack a 48-bit signed integer payload into a tagged value.
#[inline]
pub fn tag_int(payload: i64) -> u64 {
TAG_INT | ((payload as u64) & PAYLOAD_MASK)
}
/// Pack a boolean into a tagged value.
#[inline]
pub fn tag_bool(b: bool) -> u64 {
TAG_BOOL | (b as u64)
}
/// Pack a heap offset into a tagged pointer value.
#[inline]
pub fn tag_ptr(offset: u32) -> u64 {
TAG_PTR | (offset as u64)
}
/// Pack a raw u64 bit pattern into a tagged closure value.
#[inline]
pub fn tag_closure(payload: u64) -> u64 {
TAG_CLOSURE | (payload & PAYLOAD_MASK)
}
// ---------------------------------------------------------------------------
// Float detection
// ---------------------------------------------------------------------------
/// Mask for the IEEE 754 NaN exponent (bits 52–62). Any NaN or infinity
/// has these bits set to 0x7FF; non-NaN/non-infinity floats do not.
const EXPONENT_MASK: u64 = 0x7FF0_0000_0000_0000;
/// Mantissa bits (0–51). A NaN has exponent = 0x7FF and non-zero mantissa;
/// infinity has exponent = 0x7FF and zero mantissa.
const MANTISSA_MASK: u64 = 0x000F_FFFF_FFFF_FFFF;
/// True when `raw` represents a real IEEE-754 float (any bit pattern that is
/// not a NaN). Infinity is a valid float, so it returns true.
///
/// All tagged values (0x7FF6–0x7FFE) occupy the quiet-NaN range, so this
/// integer bitmask test is equivalent to `!f64::from_bits(raw).is_nan()` but
/// avoids the FPU domain-crossing penalty of `vmovq` + `ucomisd`.
#[inline]
pub fn is_float_raw(raw: u64) -> bool {
// NaN: exponent all 1s AND mantissa non-zero.
// Infinity: exponent all 1s AND mantissa zero → it IS a float.
(raw & EXPONENT_MASK) != EXPONENT_MASK || (raw & MANTISSA_MASK) == 0
}
// ---------------------------------------------------------------------------
// Tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tags_are_unique() {
let tags = [
TAG_NIL,
TAG_UNIT,
TAG_BOOL,
TAG_INT,
TAG_PTR,
TAG_ACTOR,
TAG_STRING,
TAG_CLOSURE,
];
for i in 0..tags.len() {
for j in (i + 1)..tags.len() {
assert_ne!(tags[i], tags[j], "tags {} and {} collide", i, j);
}
}
}
#[test]
fn test_tags_distinct_upper_bits() {
// Each tag must have a unique value in the upper 16 bits so that
// `tag_of()` can discriminate types. No tag may collide with another
// or with the float range (0x0000–0x7FF5, 0x7FFF).
let tags: &[(u64, &str)] = &[
(TAG_NIL, "nil"),
(TAG_UNIT, "unit"),
(TAG_BOOL, "bool"),
(TAG_INT, "int"),
(TAG_PTR, "ptr"),
(TAG_ACTOR, "actor"),
(TAG_STRING, "string"),
(TAG_CLOSURE, "closure"),
];
let mut seen = std::collections::HashSet::new();
for &(tag, name) in tags {
let upper = tag >> TAG_SHIFT;
assert!(
seen.insert(upper),
"tag {} ({:#018x}) upper 16 bits {:#06x} collide with another tag",
name,
tag,
upper
);
}
}
#[test]
fn test_tag_of() {
assert_eq!(tag_of(TAG_INT), TAG_INT >> TAG_SHIFT);
assert_eq!(tag_of(TAG_NIL), TAG_NIL >> TAG_SHIFT);
assert_eq!(tag_of(TAG_PTR | 0x1234), TAG_PTR >> TAG_SHIFT);
}
#[test]
fn test_is_int_raw() {
assert!(is_int_raw(tag_int(42)));
assert!(is_int_raw(tag_int(-1)));
assert!(!is_int_raw(TAG_NIL));
assert!(!is_int_raw(TAG_PTR | 0x100));
}
#[test]
fn test_is_ptr_raw() {
assert!(is_ptr_raw(TAG_PTR | 0x1000));
assert!(!is_ptr_raw(tag_int(0)));
assert!(!is_ptr_raw(TAG_NIL));
}
#[test]
fn test_as_int_raw() {
assert_eq!(as_int_raw(tag_int(42)), 42);
assert_eq!(as_int_raw(tag_int(-1)), -1);
assert_eq!(as_int_raw(tag_int(0)), 0);
}
#[test]
fn test_as_ptr_raw() {
assert_eq!(as_ptr_raw(TAG_PTR | 0xDEAD_BEEF), 0xDEAD_BEEF);
assert_eq!(as_ptr_raw(TAG_PTR), 0);
}
#[test]
fn test_tag_ptr() {
let raw = tag_ptr(0xABCD);
assert!(is_ptr_raw(raw));
assert_eq!(as_ptr_raw(raw), 0xABCD);
}
#[test]
fn test_tag_closure() {
let raw = tag_closure(0x5555);
assert_eq!(raw & TAG_MASK, TAG_CLOSURE);
assert_eq!(raw & PAYLOAD_MASK, 0x5555);
}
#[test]
fn test_is_float_raw() {
// Real floats (non-NaN) should be detected.
assert!(is_float_raw(0u64)); // +0.0
assert!(is_float_raw(0x3FF0_0000_0000_0000)); // 1.0
assert!(is_float_raw(0x4000_0000_0000_0000)); // 2.0
assert!(is_float_raw(0x7FF0_0000_0000_0000)); // +inf
// Tagged values should NOT be detected as floats.
assert!(!is_float_raw(tag_int(1)));
assert!(!is_float_raw(TAG_NIL));
assert!(!is_float_raw(TAG_PTR | 0x1000));
assert!(!is_float_raw(TAG_CLOSURE | 0x10));
// NaN values (even with upper bits outside the known tag range) are NOT floats.
assert!(!is_float_raw(0x7FF5_0000_0000_0000)); // NaN, not a tag, still NaN
}
#[test]
fn test_sext48_positive() {
assert_eq!(sext48(42), 42);
assert_eq!(sext48(0), 0);
}
#[test]
fn test_sext48_negative() {
let bits: u64 = 0x0000_FFFF_FFFF_FFFF; // -1 in 48 bits
assert_eq!(sext48(bits), -1);
}
#[test]
fn test_tag_int_roundtrip() {
for n in [0, 1, -1, i16::MAX as i64, i16::MIN as i64] {
let raw = tag_int(n);
let payload = raw & PAYLOAD_MASK;
assert_eq!(sext48(payload), n);
assert_eq!(raw & TAG_MASK, TAG_INT);
}
}
}