master
  1const std = @import("std");
  2const Allocator = std.mem.Allocator;
  3const assert = std.debug.assert;
  4const BigIntConst = std.math.big.int.Const;
  5const BigIntMutable = std.math.big.int.Mutable;
  6const Hash = std.hash.Wyhash;
  7const Limb = std.math.big.Limb;
  8
  9const Interner = @This();
 10
 11map: std.AutoArrayHashMapUnmanaged(void, void) = .empty,
 12items: std.MultiArrayList(struct {
 13    tag: Tag,
 14    data: u32,
 15}) = .empty,
 16extra: std.ArrayList(u32) = .empty,
 17limbs: std.ArrayList(Limb) = .empty,
 18strings: std.ArrayList(u8) = .empty,
 19
 20const KeyAdapter = struct {
 21    interner: *const Interner,
 22
 23    pub fn eql(adapter: KeyAdapter, a: Key, b_void: void, b_map_index: usize) bool {
 24        _ = b_void;
 25        return adapter.interner.get(@as(Ref, @enumFromInt(b_map_index))).eql(a);
 26    }
 27
 28    pub fn hash(adapter: KeyAdapter, a: Key) u32 {
 29        _ = adapter;
 30        return a.hash();
 31    }
 32};
 33
 34pub const Key = union(enum) {
 35    int_ty: u16,
 36    float_ty: u16,
 37    complex_ty: u16,
 38    ptr_ty,
 39    noreturn_ty,
 40    void_ty,
 41    func_ty,
 42    array_ty: struct {
 43        len: u64,
 44        child: Ref,
 45    },
 46    vector_ty: struct {
 47        len: u32,
 48        child: Ref,
 49    },
 50    record_ty: []const Ref,
 51    /// May not be zero
 52    null,
 53    int: union(enum) {
 54        u64: u64,
 55        i64: i64,
 56        big_int: BigIntConst,
 57
 58        pub fn toBigInt(repr: @This(), space: *Tag.Int.BigIntSpace) BigIntConst {
 59            return switch (repr) {
 60                .big_int => |x| x,
 61                inline .u64, .i64 => |x| BigIntMutable.init(&space.limbs, x).toConst(),
 62            };
 63        }
 64    },
 65    float: Float,
 66    complex: Complex,
 67    bytes: []const u8,
 68    pointer: Pointer,
 69
 70    pub const Float = union(enum) {
 71        f16: f16,
 72        f32: f32,
 73        f64: f64,
 74        f80: f80,
 75        f128: f128,
 76    };
 77    pub const Complex = union(enum) {
 78        cf16: [2]f16,
 79        cf32: [2]f32,
 80        cf64: [2]f64,
 81        cf80: [2]f80,
 82        cf128: [2]f128,
 83    };
 84    pub const Pointer = struct {
 85        /// NodeIndex of decl or compound literal whose address we are offsetting from
 86        node: u32,
 87        /// Offset in bytes
 88        offset: Ref,
 89    };
 90
 91    pub fn hash(key: Key) u32 {
 92        var hasher = Hash.init(0);
 93        const tag = std.meta.activeTag(key);
 94        std.hash.autoHash(&hasher, tag);
 95        switch (key) {
 96            .bytes => |bytes| {
 97                hasher.update(bytes);
 98            },
 99            .record_ty => |elems| for (elems) |elem| {
100                std.hash.autoHash(&hasher, elem);
101            },
102            .float => |repr| switch (repr) {
103                inline else => |data| std.hash.autoHash(
104                    &hasher,
105                    @as(std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(data))), @bitCast(data)),
106                ),
107            },
108            .complex => |repr| switch (repr) {
109                inline else => |data| std.hash.autoHash(
110                    &hasher,
111                    @as(std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(data))), @bitCast(data)),
112                ),
113            },
114            .int => |repr| {
115                var space: Tag.Int.BigIntSpace = undefined;
116                const big = repr.toBigInt(&space);
117                std.hash.autoHash(&hasher, big.positive);
118                for (big.limbs) |limb| std.hash.autoHash(&hasher, limb);
119            },
120            inline else => |info| {
121                std.hash.autoHash(&hasher, info);
122            },
123        }
124        return @truncate(hasher.final());
125    }
126
127    pub fn eql(a: Key, b: Key) bool {
128        const KeyTag = std.meta.Tag(Key);
129        const a_tag: KeyTag = a;
130        const b_tag: KeyTag = b;
131        if (a_tag != b_tag) return false;
132        switch (a) {
133            .record_ty => |a_elems| {
134                const b_elems = b.record_ty;
135                if (a_elems.len != b_elems.len) return false;
136                for (a_elems, b_elems) |a_elem, b_elem| {
137                    if (a_elem != b_elem) return false;
138                }
139                return true;
140            },
141            .bytes => |a_bytes| {
142                const b_bytes = b.bytes;
143                return std.mem.eql(u8, a_bytes, b_bytes);
144            },
145            .int => |a_repr| {
146                var a_space: Tag.Int.BigIntSpace = undefined;
147                const a_big = a_repr.toBigInt(&a_space);
148                var b_space: Tag.Int.BigIntSpace = undefined;
149                const b_big = b.int.toBigInt(&b_space);
150
151                return a_big.eql(b_big);
152            },
153            inline else => |a_info, tag| {
154                const b_info = @field(b, @tagName(tag));
155                return std.meta.eql(a_info, b_info);
156            },
157        }
158    }
159
160    fn toRef(key: Key) ?Ref {
161        switch (key) {
162            .int_ty => |bits| switch (bits) {
163                1 => return .i1,
164                8 => return .i8,
165                16 => return .i16,
166                32 => return .i32,
167                64 => return .i64,
168                128 => return .i128,
169                else => {},
170            },
171            .float_ty => |bits| switch (bits) {
172                16 => return .f16,
173                32 => return .f32,
174                64 => return .f64,
175                80 => return .f80,
176                128 => return .f128,
177                else => unreachable,
178            },
179            .complex_ty => |bits| switch (bits) {
180                16 => return .cf16,
181                32 => return .cf32,
182                64 => return .cf64,
183                80 => return .cf80,
184                128 => return .cf128,
185                else => unreachable,
186            },
187            .ptr_ty => return .ptr,
188            .func_ty => return .func,
189            .noreturn_ty => return .noreturn,
190            .void_ty => return .void,
191            .int => |repr| {
192                var space: Tag.Int.BigIntSpace = undefined;
193                const big = repr.toBigInt(&space);
194                if (big.eqlZero()) return .zero;
195                const big_one = BigIntConst{ .limbs = &.{1}, .positive = true };
196                if (big.eql(big_one)) return .one;
197            },
198            .float => |repr| switch (repr) {
199                inline else => |data| {
200                    if (std.math.isPositiveZero(data)) return .zero;
201                    if (data == 1) return .one;
202                },
203            },
204            .null => return .null,
205            else => {},
206        }
207        return null;
208    }
209
210    pub fn toBigInt(key: Key, space: *Tag.Int.BigIntSpace) BigIntConst {
211        return key.int.toBigInt(space);
212    }
213};
214
215pub const Ref = enum(u32) {
216    const max = std.math.maxInt(u32);
217
218    ptr = max - 1,
219    noreturn = max - 2,
220    void = max - 3,
221    i1 = max - 4,
222    i8 = max - 5,
223    i16 = max - 6,
224    i32 = max - 7,
225    i64 = max - 8,
226    i128 = max - 9,
227    f16 = max - 10,
228    f32 = max - 11,
229    f64 = max - 12,
230    f80 = max - 13,
231    f128 = max - 14,
232    func = max - 15,
233    zero = max - 16,
234    one = max - 17,
235    null = max - 18,
236    cf16 = max - 19,
237    cf32 = max - 20,
238    cf64 = max - 21,
239    cf80 = max - 22,
240    cf128 = max - 23,
241    _,
242};
243
244pub const OptRef = enum(u32) {
245    const max = std.math.maxInt(u32);
246
247    none = max - 0,
248    ptr = max - 1,
249    noreturn = max - 2,
250    void = max - 3,
251    i1 = max - 4,
252    i8 = max - 5,
253    i16 = max - 6,
254    i32 = max - 7,
255    i64 = max - 8,
256    i128 = max - 9,
257    f16 = max - 10,
258    f32 = max - 11,
259    f64 = max - 12,
260    f80 = max - 13,
261    f128 = max - 14,
262    func = max - 15,
263    zero = max - 16,
264    one = max - 17,
265    null = max - 18,
266    cf16 = max - 19,
267    cf32 = max - 20,
268    cf64 = max - 21,
269    cf80 = max - 22,
270    cf128 = max - 23,
271    _,
272};
273
274pub const Tag = enum(u8) {
275    /// `data` is `u16`
276    int_ty,
277    /// `data` is `u16`
278    float_ty,
279    /// `data` is `u16`
280    complex_ty,
281    /// `data` is index to `Array`
282    array_ty,
283    /// `data` is index to `Vector`
284    vector_ty,
285    /// `data` is `u32`
286    u32,
287    /// `data` is `i32`
288    i32,
289    /// `data` is `Int`
290    int_positive,
291    /// `data` is `Int`
292    int_negative,
293    /// `data` is `f16`
294    f16,
295    /// `data` is `f32`
296    f32,
297    /// `data` is `F64`
298    f64,
299    /// `data` is `F80`
300    f80,
301    /// `data` is `F128`
302    f128,
303    /// `data` is `CF16`
304    cf16,
305    /// `data` is `CF32`
306    cf32,
307    /// `data` is `CF64`
308    cf64,
309    /// `data` is `CF80`
310    cf80,
311    /// `data` is `CF128`
312    cf128,
313    /// `data` is `Bytes`
314    bytes,
315    /// `data` is `Record`
316    record_ty,
317    /// `data` is Pointer
318    pointer,
319
320    pub const Array = struct {
321        len0: u32,
322        len1: u32,
323        child: Ref,
324
325        pub fn getLen(a: Array) u64 {
326            return (PackedU64{
327                .a = a.len0,
328                .b = a.len1,
329            }).get();
330        }
331    };
332
333    pub const Vector = struct {
334        len: u32,
335        child: Ref,
336    };
337
338    pub const Pointer = struct {
339        node: u32,
340        offset: Ref,
341    };
342
343    pub const Int = struct {
344        limbs_index: u32,
345        limbs_len: u32,
346
347        /// Big enough to fit any non-BigInt value
348        pub const BigIntSpace = struct {
349            /// The +1 is headroom so that operations such as incrementing once
350            /// or decrementing once are possible without using an allocator.
351            limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb,
352        };
353    };
354
355    pub const F64 = struct {
356        piece0: u32,
357        piece1: u32,
358
359        pub fn get(self: F64) f64 {
360            const int_bits = @as(u64, self.piece0) | (@as(u64, self.piece1) << 32);
361            return @bitCast(int_bits);
362        }
363
364        fn pack(val: f64) F64 {
365            const bits = @as(u64, @bitCast(val));
366            return .{
367                .piece0 = @as(u32, @truncate(bits)),
368                .piece1 = @as(u32, @truncate(bits >> 32)),
369            };
370        }
371    };
372
373    pub const F80 = struct {
374        piece0: u32,
375        piece1: u32,
376        piece2: u32, // u16 part, top bits
377
378        pub fn get(self: F80) f80 {
379            const int_bits = @as(u80, self.piece0) |
380                (@as(u80, self.piece1) << 32) |
381                (@as(u80, self.piece2) << 64);
382            return @bitCast(int_bits);
383        }
384
385        fn pack(val: f80) F80 {
386            const bits = @as(u80, @bitCast(val));
387            return .{
388                .piece0 = @as(u32, @truncate(bits)),
389                .piece1 = @as(u32, @truncate(bits >> 32)),
390                .piece2 = @as(u16, @truncate(bits >> 64)),
391            };
392        }
393    };
394
395    pub const F128 = struct {
396        piece0: u32,
397        piece1: u32,
398        piece2: u32,
399        piece3: u32,
400
401        pub fn get(self: F128) f128 {
402            const int_bits = @as(u128, self.piece0) |
403                (@as(u128, self.piece1) << 32) |
404                (@as(u128, self.piece2) << 64) |
405                (@as(u128, self.piece3) << 96);
406            return @bitCast(int_bits);
407        }
408
409        fn pack(val: f128) F128 {
410            const bits = @as(u128, @bitCast(val));
411            return .{
412                .piece0 = @as(u32, @truncate(bits)),
413                .piece1 = @as(u32, @truncate(bits >> 32)),
414                .piece2 = @as(u32, @truncate(bits >> 64)),
415                .piece3 = @as(u32, @truncate(bits >> 96)),
416            };
417        }
418    };
419
420    pub const CF16 = struct {
421        piece0: u32,
422
423        pub fn get(self: CF16) [2]f16 {
424            const real: f16 = @bitCast(@as(u16, @truncate(self.piece0 >> 16)));
425            const imag: f16 = @bitCast(@as(u16, @truncate(self.piece0)));
426            return .{
427                real,
428                imag,
429            };
430        }
431
432        fn pack(val: [2]f16) CF16 {
433            const real: u16 = @bitCast(val[0]);
434            const imag: u16 = @bitCast(val[1]);
435            return .{
436                .piece0 = (@as(u32, real) << 16) | @as(u32, imag),
437            };
438        }
439    };
440
441    pub const CF32 = struct {
442        piece0: u32,
443        piece1: u32,
444
445        pub fn get(self: CF32) [2]f32 {
446            return .{
447                @bitCast(self.piece0),
448                @bitCast(self.piece1),
449            };
450        }
451
452        fn pack(val: [2]f32) CF32 {
453            return .{
454                .piece0 = @bitCast(val[0]),
455                .piece1 = @bitCast(val[1]),
456            };
457        }
458    };
459
460    pub const CF64 = struct {
461        piece0: u32,
462        piece1: u32,
463        piece2: u32,
464        piece3: u32,
465
466        pub fn get(self: CF64) [2]f64 {
467            return .{
468                (F64{ .piece0 = self.piece0, .piece1 = self.piece1 }).get(),
469                (F64{ .piece0 = self.piece2, .piece1 = self.piece3 }).get(),
470            };
471        }
472
473        fn pack(val: [2]f64) CF64 {
474            const real = F64.pack(val[0]);
475            const imag = F64.pack(val[1]);
476            return .{
477                .piece0 = real.piece0,
478                .piece1 = real.piece1,
479                .piece2 = imag.piece0,
480                .piece3 = imag.piece1,
481            };
482        }
483    };
484
485    /// TODO pack into 5 pieces
486    pub const CF80 = struct {
487        piece0: u32,
488        piece1: u32,
489        piece2: u32, // u16 part, top bits
490        piece3: u32,
491        piece4: u32,
492        piece5: u32, // u16 part, top bits
493
494        pub fn get(self: CF80) [2]f80 {
495            return .{
496                (F80{ .piece0 = self.piece0, .piece1 = self.piece1, .piece2 = self.piece2 }).get(),
497                (F80{ .piece0 = self.piece3, .piece1 = self.piece4, .piece2 = self.piece5 }).get(),
498            };
499        }
500
501        fn pack(val: [2]f80) CF80 {
502            const real = F80.pack(val[0]);
503            const imag = F80.pack(val[1]);
504            return .{
505                .piece0 = real.piece0,
506                .piece1 = real.piece1,
507                .piece2 = real.piece2,
508                .piece3 = imag.piece0,
509                .piece4 = imag.piece1,
510                .piece5 = imag.piece2,
511            };
512        }
513    };
514
515    pub const CF128 = struct {
516        piece0: u32,
517        piece1: u32,
518        piece2: u32,
519        piece3: u32,
520        piece4: u32,
521        piece5: u32,
522        piece6: u32,
523        piece7: u32,
524
525        pub fn get(self: CF128) [2]f128 {
526            return .{
527                (F128{ .piece0 = self.piece0, .piece1 = self.piece1, .piece2 = self.piece2, .piece3 = self.piece3 }).get(),
528                (F128{ .piece0 = self.piece4, .piece1 = self.piece5, .piece2 = self.piece6, .piece3 = self.piece7 }).get(),
529            };
530        }
531
532        fn pack(val: [2]f128) CF128 {
533            const real = F128.pack(val[0]);
534            const imag = F128.pack(val[1]);
535            return .{
536                .piece0 = real.piece0,
537                .piece1 = real.piece1,
538                .piece2 = real.piece2,
539                .piece3 = real.piece3,
540                .piece4 = imag.piece0,
541                .piece5 = imag.piece1,
542                .piece6 = imag.piece2,
543                .piece7 = imag.piece3,
544            };
545        }
546    };
547
548    pub const Bytes = struct {
549        strings_index: u32,
550        len: u32,
551    };
552
553    pub const Record = struct {
554        elements_len: u32,
555        // trailing
556        // [elements_len]Ref
557    };
558};
559
560pub const PackedU64 = packed struct(u64) {
561    a: u32,
562    b: u32,
563
564    pub fn get(x: PackedU64) u64 {
565        return @bitCast(x);
566    }
567
568    pub fn init(x: u64) PackedU64 {
569        return @bitCast(x);
570    }
571};
572
573pub fn deinit(i: *Interner, gpa: Allocator) void {
574    i.map.deinit(gpa);
575    i.items.deinit(gpa);
576    i.extra.deinit(gpa);
577    i.limbs.deinit(gpa);
578    i.strings.deinit(gpa);
579}
580
581pub fn put(i: *Interner, gpa: Allocator, key: Key) !Ref {
582    if (key.toRef()) |some| return some;
583    const adapter: KeyAdapter = .{ .interner = i };
584    const gop = try i.map.getOrPutAdapted(gpa, key, adapter);
585    if (gop.found_existing) return @enumFromInt(gop.index);
586    try i.items.ensureUnusedCapacity(gpa, 1);
587
588    switch (key) {
589        .int_ty => |bits| {
590            i.items.appendAssumeCapacity(.{
591                .tag = .int_ty,
592                .data = bits,
593            });
594        },
595        .float_ty => |bits| {
596            i.items.appendAssumeCapacity(.{
597                .tag = .float_ty,
598                .data = bits,
599            });
600        },
601        .complex_ty => |bits| {
602            i.items.appendAssumeCapacity(.{
603                .tag = .complex_ty,
604                .data = bits,
605            });
606        },
607        .array_ty => |info| {
608            const split_len = PackedU64.init(info.len);
609            i.items.appendAssumeCapacity(.{
610                .tag = .array_ty,
611                .data = try i.addExtra(gpa, Tag.Array{
612                    .len0 = split_len.a,
613                    .len1 = split_len.b,
614                    .child = info.child,
615                }),
616            });
617        },
618        .vector_ty => |info| {
619            i.items.appendAssumeCapacity(.{
620                .tag = .vector_ty,
621                .data = try i.addExtra(gpa, Tag.Vector{
622                    .len = info.len,
623                    .child = info.child,
624                }),
625            });
626        },
627        .pointer => |info| {
628            i.items.appendAssumeCapacity(.{
629                .tag = .pointer,
630                .data = try i.addExtra(gpa, Tag.Pointer{
631                    .node = info.node,
632                    .offset = info.offset,
633                }),
634            });
635        },
636        .int => |repr| int: {
637            var space: Tag.Int.BigIntSpace = undefined;
638            const big = repr.toBigInt(&space);
639            switch (repr) {
640                .u64 => |data| if (std.math.cast(u32, data)) |small| {
641                    i.items.appendAssumeCapacity(.{
642                        .tag = .u32,
643                        .data = small,
644                    });
645                    break :int;
646                },
647                .i64 => |data| if (std.math.cast(i32, data)) |small| {
648                    i.items.appendAssumeCapacity(.{
649                        .tag = .i32,
650                        .data = @bitCast(small),
651                    });
652                    break :int;
653                },
654                .big_int => |data| {
655                    if (data.fitsInTwosComp(.unsigned, 32)) {
656                        i.items.appendAssumeCapacity(.{
657                            .tag = .u32,
658                            .data = data.toInt(u32) catch unreachable,
659                        });
660                        break :int;
661                    } else if (data.fitsInTwosComp(.signed, 32)) {
662                        i.items.appendAssumeCapacity(.{
663                            .tag = .i32,
664                            .data = @bitCast(data.toInt(i32) catch unreachable),
665                        });
666                        break :int;
667                    }
668                },
669            }
670            const limbs_index: u32 = @intCast(i.limbs.items.len);
671            try i.limbs.appendSlice(gpa, big.limbs);
672            i.items.appendAssumeCapacity(.{
673                .tag = if (big.positive) .int_positive else .int_negative,
674                .data = try i.addExtra(gpa, Tag.Int{
675                    .limbs_index = limbs_index,
676                    .limbs_len = @intCast(big.limbs.len),
677                }),
678            });
679        },
680        .float => |repr| switch (repr) {
681            .f16 => |data| i.items.appendAssumeCapacity(.{
682                .tag = .f16,
683                .data = @as(u16, @bitCast(data)),
684            }),
685            .f32 => |data| i.items.appendAssumeCapacity(.{
686                .tag = .f32,
687                .data = @as(u32, @bitCast(data)),
688            }),
689            .f64 => |data| i.items.appendAssumeCapacity(.{
690                .tag = .f64,
691                .data = try i.addExtra(gpa, Tag.F64.pack(data)),
692            }),
693            .f80 => |data| i.items.appendAssumeCapacity(.{
694                .tag = .f80,
695                .data = try i.addExtra(gpa, Tag.F80.pack(data)),
696            }),
697            .f128 => |data| i.items.appendAssumeCapacity(.{
698                .tag = .f128,
699                .data = try i.addExtra(gpa, Tag.F128.pack(data)),
700            }),
701        },
702        .complex => |repr| switch (repr) {
703            .cf16 => |data| i.items.appendAssumeCapacity(.{
704                .tag = .cf16,
705                .data = try i.addExtra(gpa, Tag.CF16.pack(data)),
706            }),
707            .cf32 => |data| i.items.appendAssumeCapacity(.{
708                .tag = .cf32,
709                .data = try i.addExtra(gpa, Tag.CF32.pack(data)),
710            }),
711            .cf64 => |data| i.items.appendAssumeCapacity(.{
712                .tag = .cf64,
713                .data = try i.addExtra(gpa, Tag.CF64.pack(data)),
714            }),
715            .cf80 => |data| i.items.appendAssumeCapacity(.{
716                .tag = .cf80,
717                .data = try i.addExtra(gpa, Tag.CF80.pack(data)),
718            }),
719            .cf128 => |data| i.items.appendAssumeCapacity(.{
720                .tag = .cf128,
721                .data = try i.addExtra(gpa, Tag.CF128.pack(data)),
722            }),
723        },
724        .bytes => |bytes| {
725            const strings_index: u32 = @intCast(i.strings.items.len);
726            try i.strings.appendSlice(gpa, bytes);
727            i.items.appendAssumeCapacity(.{
728                .tag = .bytes,
729                .data = try i.addExtra(gpa, Tag.Bytes{
730                    .strings_index = strings_index,
731                    .len = @intCast(bytes.len),
732                }),
733            });
734        },
735        .record_ty => |elems| {
736            try i.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.Record).@"struct".fields.len +
737                elems.len);
738            i.items.appendAssumeCapacity(.{
739                .tag = .record_ty,
740                .data = i.addExtraAssumeCapacity(Tag.Record{
741                    .elements_len = @intCast(elems.len),
742                }),
743            });
744            i.extra.appendSliceAssumeCapacity(@ptrCast(elems));
745        },
746        .ptr_ty,
747        .noreturn_ty,
748        .void_ty,
749        .func_ty,
750        .null,
751        => unreachable,
752    }
753
754    return @enumFromInt(gop.index);
755}
756
757fn addExtra(i: *Interner, gpa: Allocator, extra: anytype) Allocator.Error!u32 {
758    const fields = @typeInfo(@TypeOf(extra)).@"struct".fields;
759    try i.extra.ensureUnusedCapacity(gpa, fields.len);
760    return i.addExtraAssumeCapacity(extra);
761}
762
763fn addExtraAssumeCapacity(i: *Interner, extra: anytype) u32 {
764    const result = @as(u32, @intCast(i.extra.items.len));
765    inline for (@typeInfo(@TypeOf(extra)).@"struct".fields) |field| {
766        i.extra.appendAssumeCapacity(switch (field.type) {
767            Ref => @intFromEnum(@field(extra, field.name)),
768            u32 => @field(extra, field.name),
769            else => @compileError("bad field type: " ++ @typeName(field.type)),
770        });
771    }
772    return result;
773}
774
775pub fn get(i: *const Interner, ref: Ref) Key {
776    switch (ref) {
777        .ptr => return .ptr_ty,
778        .func => return .func_ty,
779        .noreturn => return .noreturn_ty,
780        .void => return .void_ty,
781        .i1 => return .{ .int_ty = 1 },
782        .i8 => return .{ .int_ty = 8 },
783        .i16 => return .{ .int_ty = 16 },
784        .i32 => return .{ .int_ty = 32 },
785        .i64 => return .{ .int_ty = 64 },
786        .i128 => return .{ .int_ty = 128 },
787        .f16 => return .{ .float_ty = 16 },
788        .f32 => return .{ .float_ty = 32 },
789        .f64 => return .{ .float_ty = 64 },
790        .f80 => return .{ .float_ty = 80 },
791        .f128 => return .{ .float_ty = 128 },
792        .zero => return .{ .int = .{ .u64 = 0 } },
793        .one => return .{ .int = .{ .u64 = 1 } },
794        .null => return .null,
795        .cf16 => return .{ .complex_ty = 16 },
796        .cf32 => return .{ .complex_ty = 32 },
797        .cf64 => return .{ .complex_ty = 64 },
798        .cf80 => return .{ .complex_ty = 80 },
799        else => {},
800    }
801
802    const item = i.items.get(@intFromEnum(ref));
803    const data = item.data;
804    return switch (item.tag) {
805        .int_ty => .{ .int_ty = @intCast(data) },
806        .float_ty => .{ .float_ty = @intCast(data) },
807        .complex_ty => .{ .complex_ty = @intCast(data) },
808        .array_ty => {
809            const array_ty = i.extraData(Tag.Array, data);
810            return .{ .array_ty = .{
811                .len = array_ty.getLen(),
812                .child = array_ty.child,
813            } };
814        },
815        .vector_ty => {
816            const vector_ty = i.extraData(Tag.Vector, data);
817            return .{ .vector_ty = .{
818                .len = vector_ty.len,
819                .child = vector_ty.child,
820            } };
821        },
822        .pointer => {
823            const pointer = i.extraData(Tag.Pointer, data);
824            return .{ .pointer = .{
825                .node = pointer.node,
826                .offset = pointer.offset,
827            } };
828        },
829        .u32 => .{ .int = .{ .u64 = data } },
830        .i32 => .{ .int = .{ .i64 = @as(i32, @bitCast(data)) } },
831        .int_positive, .int_negative => {
832            const int_info = i.extraData(Tag.Int, data);
833            const limbs = i.limbs.items[int_info.limbs_index..][0..int_info.limbs_len];
834            return .{ .int = .{
835                .big_int = .{
836                    .positive = item.tag == .int_positive,
837                    .limbs = limbs,
838                },
839            } };
840        },
841        .f16 => .{ .float = .{ .f16 = @bitCast(@as(u16, @intCast(data))) } },
842        .f32 => .{ .float = .{ .f32 = @bitCast(data) } },
843        .f64 => {
844            const float = i.extraData(Tag.F64, data);
845            return .{ .float = .{ .f64 = float.get() } };
846        },
847        .f80 => {
848            const float = i.extraData(Tag.F80, data);
849            return .{ .float = .{ .f80 = float.get() } };
850        },
851        .f128 => {
852            const float = i.extraData(Tag.F128, data);
853            return .{ .float = .{ .f128 = float.get() } };
854        },
855        .cf16 => {
856            const components = i.extraData(Tag.CF16, data);
857            return .{ .complex = .{ .cf16 = components.get() } };
858        },
859        .cf32 => {
860            const components = i.extraData(Tag.CF32, data);
861            return .{ .complex = .{ .cf32 = components.get() } };
862        },
863        .cf64 => {
864            const components = i.extraData(Tag.CF64, data);
865            return .{ .complex = .{ .cf64 = components.get() } };
866        },
867        .cf80 => {
868            const components = i.extraData(Tag.CF80, data);
869            return .{ .complex = .{ .cf80 = components.get() } };
870        },
871        .cf128 => {
872            const components = i.extraData(Tag.CF128, data);
873            return .{ .complex = .{ .cf128 = components.get() } };
874        },
875        .bytes => {
876            const bytes = i.extraData(Tag.Bytes, data);
877            return .{ .bytes = i.strings.items[bytes.strings_index..][0..bytes.len] };
878        },
879        .record_ty => {
880            const extra = i.extraDataTrail(Tag.Record, data);
881            return .{
882                .record_ty = @ptrCast(i.extra.items[extra.end..][0..extra.data.elements_len]),
883            };
884        },
885    };
886}
887
888fn extraData(i: *const Interner, comptime T: type, index: usize) T {
889    return i.extraDataTrail(T, index).data;
890}
891
892fn extraDataTrail(i: *const Interner, comptime T: type, index: usize) struct { data: T, end: u32 } {
893    var result: T = undefined;
894    const fields = @typeInfo(T).@"struct".fields;
895    inline for (fields, 0..) |field, field_i| {
896        const int32 = i.extra.items[field_i + index];
897        @field(result, field.name) = switch (field.type) {
898            Ref => @enumFromInt(int32),
899            u32 => int32,
900            else => @compileError("bad field type: " ++ @typeName(field.type)),
901        };
902    }
903    return .{
904        .data = result,
905        .end = @intCast(index + fields.len),
906    };
907}