1const extra = @This();
2
3pub const Mutability = enum { @"const", @"var" };
4
5fn setAsU32(items: []u32, comptime T: type, i: usize, payload: T) void {
6 switch (T) {
7 u32 => items[i] = payload,
8 i32 => items[i] = @bitCast(payload),
9
10 [16]u8 => {
11 const u32s: [4]u32 = @bitCast(payload);
12 items[i] = u32s[0];
13 items[i + 1] = u32s[1];
14 items[i + 2] = u32s[2];
15 items[i + 3] = u32s[3];
16 },
17
18 else => brk: {
19 const info = @typeInfo(T);
20 if (info == .@"enum") {
21 items[i] = @intFromEnum(payload);
22 break :brk;
23 }
24
25 const struct_data = info.@"struct";
26 if (struct_data.layout == .@"packed") {
27 items[i] = @bitCast(payload);
28 break :brk;
29 }
30
31 const fields = std.meta.fields(@TypeOf(payload));
32 comptime var idx: usize = 0;
33 inline for (fields) |field| {
34 const value = @field(payload, field.name);
35 @call(.always_inline, setAsU32, .{ items, field.type, i + idx, value });
36 idx += comptime extraSizeOf(field.type);
37 }
38 },
39 }
40}
41
42fn getAsU32(items: []const u32, comptime T: type, index: usize) T {
43 return switch (T) {
44 u32 => items[index],
45 i32 => @bitCast(items[index]),
46
47 [16]u8 => @bitCast(items[index..][0..4].*),
48
49 else => brk: {
50 const info = @typeInfo(T);
51 if (info == .@"enum") {
52 break :brk @enumFromInt(items[index]);
53 }
54
55 const struct_data = info.@"struct";
56 if (struct_data.layout == .@"packed") {
57 break :brk @bitCast(items[index]);
58 }
59
60 const fields = std.meta.fields(T);
61 var i = index;
62 var result: T = undefined;
63
64 inline for (fields) |field| {
65 @field(result, field.name) = @call(
66 .always_inline,
67 getAsU32,
68 .{ items, field.type, i },
69 );
70 i += comptime extraSizeOf(field.type);
71 }
72
73 break :brk result;
74 },
75 };
76}
77
78pub const StoreKind = enum {
79 intern_pool,
80 hir,
81 air,
82 js_tree,
83
84 pub fn resolveStore(
85 comptime kind: StoreKind,
86 owner: anytype,
87 ) kind.ResolvedStorePtr(@TypeOf(owner)) {
88 return &owner.extra;
89 }
90
91 pub fn ResolvedStore(kind: StoreKind, Owner: type) type {
92 const ptr = @typeInfo(Owner).pointer;
93 assert(ptr.size == .one);
94 const mutability = brk: for (@typeInfo(ptr.child).@"struct".fields) |field| {
95 if (std.mem.eql(u8, field.name, "extra")) {
96 break :brk field.type.mutability;
97 }
98 } else unreachable;
99 return ExtraStore(kind, mutability);
100 }
101
102 pub fn ResolvedStorePtr(kind: StoreKind, Owner: type) type {
103 return kind.ItemPointer(Owner, .one, kind.ResolvedStore(Owner));
104 }
105
106 pub fn ItemPointer(
107 kind: StoreKind,
108 Owner: type,
109 size: std.builtin.Type.Pointer.Size,
110 child: type,
111 ) type {
112 const ptr = @typeInfo(Owner).pointer;
113 return @Type(.{ .pointer = .{
114 .size = size,
115 .is_const = ptr.is_const or ResolvedStore(kind, Owner).mutability == .@"const",
116 .is_volatile = false,
117 .is_allowzero = false,
118 .alignment = @alignOf(child),
119 .address_space = .generic,
120 .child = child,
121 .sentinel_ptr = null,
122 } });
123 }
124};
125
126pub fn EncodedData(comptime Tag: type, backing_type: type) type {
127 return enum(backing_type) {
128 _,
129
130 pub fn decode(data: @This(), comptime tag: Tag, proof: Tag) tag.Data() {
131 assert(proof == tag); // incorrect memory access
132 return data.decodeUnsafe(tag.Data(), proof);
133 }
134
135 pub fn decodeUnsafe(data: @This(), comptime T: type, proof: Tag) T {
136 if (core.runtime_safety) {
137 const values = comptime brk: {
138 const values = std.enums.values(Tag);
139 var allowed = std.enums.EnumArray(Tag, bool).initFill(false);
140 for (values) |val| {
141 if (Tag == InternPool.Tag and val == .removed) continue;
142
143 allowed.set(val, val.Data() == T);
144 }
145 break :brk allowed;
146 };
147 assert(values.get(proof)); // incorrect memory access
148 }
149 const info = @typeInfo(T);
150 if (info == .@"enum") {
151 return @enumFromInt(@intFromEnum(data));
152 }
153 if (info == .@"struct") {
154 assert(backing_type == u64);
155 var fields: [2]u32 = @bitCast(@intFromEnum(data));
156 return getAsU32(&fields, T, 0);
157 }
158 return @bitCast(@intFromEnum(data));
159 }
160
161 pub fn encode(comptime tag: Tag, data: tag.Data()) @This() {
162 const Data = tag.Data();
163 const info = @typeInfo(Data);
164 if (info == .@"enum") {
165 return @enumFromInt(@intFromEnum(data));
166 }
167 if (info == .@"struct") {
168 comptime assert(backing_type == u64);
169 comptime assert(@sizeOf(Data) == 8);
170 var fields: [2]u32 = undefined;
171 setAsU32(&fields, Data, 0, data);
172 return @enumFromInt(@as(backing_type, @bitCast(fields)));
173 }
174 return @enumFromInt(@as(backing_type, @bitCast(data)));
175 }
176 };
177}
178
179/// The number of 'u32's this type consumes in `ip.extra`.
180pub inline fn extraSizeOf(comptime T: type) usize {
181 return switch (T) {
182 u32,
183 i32,
184 => return 1,
185
186 [16]u8 => return 4,
187
188 else => {
189 const info = @typeInfo(T);
190 if (info == .@"enum") {
191 comptime assert(@sizeOf(info.@"enum".tag_type) <= 32);
192 return 1;
193 }
194
195 const struct_data = info.@"struct";
196 if (struct_data.layout == .@"packed") {
197 comptime assert(struct_data.backing_integer == u32);
198 return 1;
199 }
200
201 const fields = std.meta.fields(T);
202 var size: usize = 0;
203 inline for (fields) |field| {
204 size += extraSizeOf(field.type);
205 }
206 return size;
207 },
208 };
209}
210
211pub const ExtraStore = Store;
212
213/// A data structure to manage a `[]u32` with mixed data types. Provides
214/// `.Index` and `.Slice` to keep strongly typed indices to items within the
215/// store.
216pub fn Store(set_kind: StoreKind, set_mutability: Mutability) type {
217 // Separate struct to avoid capturing set_mutability
218 const wrappers = struct {
219 pub const kind = set_kind;
220
221 pub fn Index(T: type) type {
222 return enum(u32) {
223 _,
224
225 pub fn get(index: @This(), owner: anytype) T {
226 return kind.resolveStore(owner).get(T, @intFromEnum(index));
227 }
228
229 pub fn set(index: @This(), owner: anytype, payload: T) void {
230 return kind.resolveStore(owner).set(T, @intFromEnum(index), payload);
231 }
232
233 /// Returns data with a trailing reader to read data stored immediately after
234 /// the main data structure. What is stored should be documented on `T`
235 pub fn getTrail(index: @This(), owner: anytype) struct {
236 T,
237 kind.ResolvedStore(@TypeOf(owner)).TrailReader,
238 } {
239 const advance = comptime extraSizeOf(T);
240 return .{
241 index.get(owner),
242 .{ .index = @intFromEnum(index) + advance },
243 };
244 }
245
246 pub fn jsonStringify(index: @This(), jw: anytype) !void {
247 try jw.write(@intFromEnum(index));
248 }
249 };
250 }
251
252 /// An array of `T` existing within the `extra` array.
253 /// This type exists to provide a struct with lifetime that is
254 /// not invalidated when items are added to the `InternPool`.
255 pub fn Slice(T: type) type {
256 return struct {
257 start: u32,
258 len: u32,
259
260 pub const empty: @This() = .{
261 .start = 0,
262 .len = 0,
263 };
264
265 pub fn get(slice: @This(), owner: anytype) kind.ItemPointer(@TypeOf(owner), .slice, T) {
266 return @ptrCast(kind.resolveStore(owner).list.items[slice.start..][0..slice.len]);
267 }
268
269 pub fn at(slice: @This(), owner: anytype, index: usize) T {
270 return slice.get(owner)[index];
271 }
272 };
273 }
274 };
275
276 return struct {
277 const Self = @This();
278
279 list: Inner,
280
281 pub const empty: @This() = .{ .list = .empty };
282
283 pub const kind = set_kind;
284 pub const mutability = set_mutability;
285
286 pub const Index = wrappers.Index;
287 pub const Slice = wrappers.Slice;
288
289 pub const Inner = switch (mutability) {
290 .@"const" => struct {
291 items: []const u32,
292
293 pub const empty = .{ .items = &.{} };
294
295 pub fn deinit(slice: *@This(), gpa: Allocator) void {
296 gpa.free(slice.items);
297 }
298 },
299 .@"var" => std.ArrayListUnmanaged(u32),
300 };
301
302 pub fn deinit(store: *Self, gpa: Allocator) void {
303 store.list.deinit(gpa);
304 store.* = undefined;
305 }
306
307 pub const TrailReader = struct {
308 index: usize,
309
310 pub fn read(trail: *TrailReader, ip: *const Self, comptime T: type) T {
311 defer trail.index += comptime extraSizeOf(T);
312 return ip.get(T, trail.index);
313 }
314
315 pub fn at(trail: *const TrailReader, ip: *const Self, comptime T: type, index: u32) T {
316 return ip.get(T, trail.index + index);
317 }
318
319 pub fn readValueSlice(trail: *TrailReader, len: u32) InternPool.Value.Slice {
320 defer trail.index += len;
321 return .{
322 .start = @intCast(trail.index),
323 .len = len,
324 };
325 }
326
327 pub fn readTypeSlice(trail: *TrailReader, len: u32) InternPool.Type.Slice {
328 defer trail.index += len;
329 return .{
330 .start = @intCast(trail.index),
331 .len = len,
332 };
333 }
334
335 pub fn readNullTerminatedStringSlice(
336 trail: *TrailReader,
337 len: u32,
338 ) InternPool.NullTerminatedString.Slice {
339 defer trail.index += len;
340 return .{
341 .start = @intCast(trail.index),
342 .len = len,
343 };
344 }
345 };
346
347 pub const TrailWriter = struct {
348 store: *Self,
349 leftover: usize,
350
351 pub fn init(store: *Self, len: usize) TrailWriter {
352 return .{ .store = store, .leftover = len };
353 }
354
355 pub fn end(w: *TrailWriter) void {
356 assert(w.leftover == 0);
357 }
358
359 pub fn writeU32(w: *TrailWriter, value: u32) void {
360 assert(w.leftover > 0);
361 w.store.list.appendAssumeCapacity(value);
362 w.leftover -= 1;
363 }
364
365 pub fn writeSlice(w: *TrailWriter, slice: anytype) void {
366 const Child = std.meta.Child(@TypeOf(slice));
367 const child = @typeInfo(Child);
368 comptime assert(Child == u32 or (child == .@"enum" and child.@"enum".tag_type == u32));
369 assert(w.leftover >= slice.len);
370 w.store.list.appendSliceAssumeCapacity(@ptrCast(slice));
371 w.leftover -= slice.len;
372 }
373
374 pub fn writeStruct(w: *TrailWriter, comptime T: type, data: T) void {
375 _ = appendAssumeCapacity(w.store, T, data);
376 }
377 };
378
379 pub fn append(store: *Self, gpa: Allocator, comptime T: type, payload: T) Allocator.Error!Index(T) {
380 comptime assert(mutability == .@"var");
381 try store.list.ensureUnusedCapacity(gpa, extraSizeOf(@TypeOf(payload)));
382 return store.appendAssumeCapacity(T, payload);
383 }
384
385 pub fn appendAssumeCapacity(ip: *Self, comptime T: type, payload: T) Index(T) {
386 comptime assert(mutability == .@"var");
387 const extra_index: u32 = @intCast(ip.list.items.len);
388 ip.list.items.len += extraSizeOf(@TypeOf(payload));
389 ip.set(T, extra_index, payload);
390 return @enumFromInt(extra_index);
391 }
392
393 pub fn appendTrailing(
394 store: *Self,
395 gpa: Allocator,
396 comptime T: type,
397 payload: T,
398 trailing_len: usize,
399 ) Allocator.Error!struct { Index(T), TrailWriter } {
400 comptime assert(mutability == .@"var");
401 try store.list.ensureUnusedCapacity(gpa, extraSizeOf(@TypeOf(payload)) + trailing_len);
402 const extra_index: u32 = @intCast(store.list.items.len);
403 store.list.items.len += extraSizeOf(@TypeOf(payload));
404 store.set(T, extra_index, payload);
405 return .{
406 @enumFromInt(extra_index),
407 TrailWriter.init(store, trailing_len),
408 };
409 }
410
411 // TODO: make setAsU32 allow any type
412 pub fn setU32(store: *Self, index: usize, payload: u32) void {
413 comptime assert(mutability == .@"var");
414 store.list.items[index] = payload;
415 }
416
417 pub fn set(store: *Self, comptime T: type, index: usize, payload: T) void {
418 comptime assert(mutability == .@"var");
419 setAsU32(store.list.items, T, index, payload);
420 }
421
422 pub fn get(store: *const Self, comptime T: type, index: usize) T {
423 return getAsU32(store.list.items, T, index);
424 }
425
426 pub fn toOwnedStore(store: *Self, gpa: Allocator) !ExtraStore(set_kind, .@"const") {
427 comptime assert(mutability == .@"var");
428 return .{ .list = .{ .items = try store.list.toOwnedSlice(gpa) } };
429 }
430
431 /// Returns a `const` store pointing at the mutable's data.
432 /// Invalidated when the mutable store is mutated.
433 pub fn constant(store: *const Self) ExtraStore(set_kind, .@"const") {
434 return .{ .list = .{ .items = store.list.items } };
435 }
436
437 // TODO: Add some safety to the Index returned from this
438 /// Allocate a number of u32s into this store, but they are left uninitialized.
439 pub fn alloc(store: *Self, gpa: Allocator, len: usize) !u32 {
440 comptime assert(mutability == .@"var");
441 const idx = store.list.items.len;
442 try store.list.ensureUnusedCapacity(gpa, len);
443 store.list.items.len += len;
444 return @intCast(idx);
445 }
446
447 pub fn appendSlice(store: *Self, gpa: Allocator, data: anytype) !Slice(std.meta.Child(@TypeOf(data))) {
448 const idx = store.list.items.len;
449 comptime assert(@typeInfo(@TypeOf(data)).pointer.size == .slice);
450 comptime assert(@typeInfo(@TypeOf(data)).pointer.alignment == @alignOf(u32));
451 comptime assert(@sizeOf(@TypeOf(data[0])) == @sizeOf(u32));
452 comptime assert(@alignOf(@TypeOf(data[0])) == @alignOf(u32));
453 try store.list.appendSlice(gpa, @ptrCast(data));
454 store.list.items.len += data.len;
455 return .{ .start = @intCast(idx), .len = @intCast(data.len) };
456 }
457 };
458}
459
460pub const Float = struct {
461 low: u32,
462 high: u32,
463
464 pub const pure_nan = Float{ .low = 0x7f800000, .high = 0 };
465
466 pub fn purifyNaN(f: f64) f64 {
467 if (std.math.isNan(f)) {
468 return comptime Float.pure_nan.decode();
469 }
470 return f;
471 }
472
473 pub fn assertPure(f: f64) f64 {
474 if (std.math.isNan(f)) {
475 assert(Float.pure_nan.eql(encode(f)));
476 }
477 return f;
478 }
479
480 pub fn encode(decoded: f64) Float {
481 if (std.math.isNan(decoded)) {
482 return pure_nan;
483 }
484 const as_bytes: [2]u32 = @bitCast(decoded);
485 return .{
486 .low = as_bytes[0],
487 .high = as_bytes[1],
488 };
489 }
490
491 pub fn decode(encoded: Float) f64 {
492 const as_bytes: [2]u32 = .{
493 encoded.low,
494 encoded.high,
495 };
496 return @bitCast(as_bytes);
497 }
498
499 pub fn eql(a: Float, b: Float) bool {
500 return a.low == b.low and a.high == b.high;
501 }
502};
503
504const core = @import("mod.zig");
505
506const std = @import("std");
507const assert = std.debug.assert;
508const Allocator = std.mem.Allocator;
509const InternPool = @import("InternPool.zig");