Commit 4c7507cceb

Andrew Kelley <andrew@ziglang.org>
2020-04-21 06:56:30
ir: semantic analysis skeleton
1 parent cc1c2bd
Changed files (4)
src-self-hosted/ir/text.zig
@@ -1,4 +1,5 @@
 //! This file has to do with parsing and rendering the ZIR text format.
+
 const std = @import("std");
 const mem = std.mem;
 const Allocator = std.mem.Allocator;
@@ -11,6 +12,7 @@ const BigInt = std.math.big.Int;
 /// in-memory, analyzed instructions with types and values.
 pub const Inst = struct {
     tag: Tag,
+    src_offset: usize,
 
     /// These names are used directly as the instruction names in the text format.
     pub const Tag = enum {
@@ -46,15 +48,15 @@ pub const Inst = struct {
     }
 
     pub fn cast(base: *Inst, comptime T: type) ?*T {
-        const expected_tag = std.meta.fieldInfo(T, "base").default_value.?.tag;
-        if (base.tag != expected_tag)
+        if (base.tag != T.base_tag)
             return null;
 
         return @fieldParentPtr(T, "base", base);
     }
 
     pub const Str = struct {
-        base: Inst = Inst{ .tag = .str },
+        pub const base_tag = Tag.str;
+        base: Inst,
 
         positionals: struct {
             bytes: []u8,
@@ -63,7 +65,8 @@ pub const Inst = struct {
     };
 
     pub const Int = struct {
-        base: Inst = Inst{ .tag = .int },
+        pub const base_tag = Tag.int;
+        base: Inst,
 
         positionals: struct {
             int: BigInt,
@@ -72,7 +75,8 @@ pub const Inst = struct {
     };
 
     pub const PtrToInt = struct {
-        base: Inst = Inst{ .tag = .ptrtoint },
+        pub const base_tag = Tag.ptrtoint;
+        base: Inst,
 
         positionals: struct {
             ptr: *Inst,
@@ -81,7 +85,8 @@ pub const Inst = struct {
     };
 
     pub const FieldPtr = struct {
-        base: Inst = Inst{ .tag = .fieldptr },
+        pub const base_tag = Tag.fieldptr;
+        base: Inst,
 
         positionals: struct {
             object_ptr: *Inst,
@@ -91,7 +96,8 @@ pub const Inst = struct {
     };
 
     pub const Deref = struct {
-        base: Inst = Inst{ .tag = .deref },
+        pub const base_tag = Tag.deref;
+        base: Inst,
 
         positionals: struct {
             ptr: *Inst,
@@ -100,7 +106,8 @@ pub const Inst = struct {
     };
 
     pub const As = struct {
-        base: Inst = Inst{ .tag = .as },
+        pub const base_tag = Tag.as;
+        base: Inst,
 
         positionals: struct {
             dest_type: *Inst,
@@ -110,7 +117,8 @@ pub const Inst = struct {
     };
 
     pub const Assembly = struct {
-        base: Inst = Inst{ .tag = .@"asm" },
+        pub const base_tag = Tag.@"asm";
+        base: Inst,
 
         positionals: struct {
             asm_source: *Inst,
@@ -126,14 +134,16 @@ pub const Inst = struct {
     };
 
     pub const Unreachable = struct {
-        base: Inst = Inst{ .tag = .@"unreachable" },
+        pub const base_tag = Tag.@"unreachable";
+        base: Inst,
 
         positionals: struct {},
         kw_args: struct {},
     };
 
     pub const Fn = struct {
-        base: Inst = Inst{ .tag = .@"fn" },
+        pub const base_tag = Tag.@"fn";
+        base: Inst,
 
         positionals: struct {
             fn_type: *Inst,
@@ -147,7 +157,8 @@ pub const Inst = struct {
     };
 
     pub const Export = struct {
-        base: Inst = Inst{ .tag = .@"export" },
+        pub const base_tag = Tag.@"export";
+        base: Inst,
 
         positionals: struct {
             symbol_name: *Inst,
@@ -157,7 +168,8 @@ pub const Inst = struct {
     };
 
     pub const Primitive = struct {
-        base: Inst = Inst{ .tag = .primitive },
+        pub const base_tag = Tag.primitive;
+        base: Inst,
 
         positionals: struct {
             tag: BuiltinType,
@@ -192,7 +204,8 @@ pub const Inst = struct {
     };
 
     pub const FnType = struct {
-        base: Inst = Inst{ .tag = .fntype },
+        pub const base_tag = Tag.fntype;
+        base: Inst,
 
         positionals: struct {
             param_types: []*Inst,
@@ -212,9 +225,12 @@ pub const ErrorMsg = struct {
 pub const Module = struct {
     decls: []*Inst,
     errors: []ErrorMsg,
+    arena: std.heap.ArenaAllocator,
 
-    pub fn deinit(self: *Module) void {
-        // TODO resource deallocation
+    pub fn deinit(self: *Module, allocator: *Allocator) void {
+        allocator.free(self.decls);
+        allocator.free(self.errors);
+        self.arena.deinit();
         self.* = undefined;
     }
 
@@ -225,6 +241,8 @@ pub const Module = struct {
 
     const InstPtrTable = std.AutoHashMap(*Inst, struct { index: usize, fn_body: ?*Inst.Fn.Body });
 
+    /// The allocator is used for temporary storage, but this function always returns
+    /// with no resources allocated.
     pub fn writeToStream(self: Module, allocator: *Allocator, stream: var) !void {
         // First, build a map of *Inst to @ or % indexes
         var inst_table = InstPtrTable.init(allocator);
@@ -359,6 +377,7 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module
 
     var parser: Parser = .{
         .allocator = allocator,
+        .arena = std.heap.ArenaAllocator.init(allocator),
         .i = 0,
         .source = source,
         .decls = std.ArrayList(*Inst).init(allocator),
@@ -374,11 +393,13 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module
     return Module{
         .decls = parser.decls.toOwnedSlice(),
         .errors = parser.errors.toOwnedSlice(),
+        .arena = parser.arena,
     };
 }
 
 const Parser = struct {
     allocator: *Allocator,
+    arena: std.heap.ArenaAllocator,
     i: usize,
     source: [:0]const u8,
     errors: std.ArrayList(ErrorMsg),
@@ -439,7 +460,7 @@ const Parser = struct {
                 self.i += 1;
                 const span = self.source[start..self.i];
                 var bad_index: usize = undefined;
-                const parsed = std.zig.parseStringLiteral(self.allocator, span, &bad_index) catch |err| switch (err) {
+                const parsed = std.zig.parseStringLiteral(&self.arena.allocator, span, &bad_index) catch |err| switch (err) {
                     error.InvalidCharacter => {
                         self.i = start + bad_index;
                         const bad_byte = self.source[self.i];
@@ -466,7 +487,7 @@ const Parser = struct {
             else => break,
         };
         const number_text = self.source[start..self.i];
-        var result = try BigInt.init(self.allocator);
+        var result = try BigInt.init(&self.arena.allocator);
         result.setString(10, number_text) catch |err| {
             self.i = start;
             switch (err) {
@@ -551,7 +572,7 @@ const Parser = struct {
 
     fn fail(self: *Parser, comptime format: []const u8, args: var) InnerError {
         @setCold(true);
-        const msg = try std.fmt.allocPrint(self.allocator, format, args);
+        const msg = try std.fmt.allocPrint(&self.arena.allocator, format, args);
         (try self.errors.addOne()).* = .{
             .byte_offset = self.i,
             .msg = msg,
@@ -576,8 +597,11 @@ const Parser = struct {
         comptime InstType: type,
         body_ctx: ?*Body,
     ) !*Inst {
-        const inst_specific = try self.allocator.create(InstType);
-        inst_specific.base = std.meta.fieldInfo(InstType, "base").default_value.?;
+        const inst_specific = try self.arena.allocator.create(InstType);
+        inst_specific.base = .{
+            .src_offset = self.i,
+            .tag = InstType.base_tag,
+        };
 
         if (@hasField(InstType, "ty")) {
             inst_specific.ty = opt_type orelse {
@@ -657,8 +681,7 @@ const Parser = struct {
                 skipSpace(self);
                 if (eatByte(self, ']')) return &[0]*Inst{};
 
-                var instructions = std.ArrayList(*Inst).init(self.allocator);
-                defer instructions.deinit();
+                var instructions = std.ArrayList(*Inst).init(&self.arena.allocator);
                 while (true) {
                     skipSpace(self);
                     try instructions.append(try parseParameterInst(self, body_ctx));
src-self-hosted/ir.zig
@@ -8,106 +8,221 @@ const text = @import("ir/text.zig");
 
 /// These are in-memory, analyzed instructions. See `text.Inst` for the representation
 /// of instructions that correspond to the ZIR text format.
+/// This struct owns the `Value` and `Type` memory. When the struct is deallocated,
+/// so are the `Value` and `Type`. The value of a constant must be copied into
+/// a memory location for the value to survive after a const instruction.
 pub const Inst = struct {
-    pub fn ty(base: *Inst) ?Type {
-        switch (base.tag) {
-            .constant => return base.cast(Constant).?.ty,
-            .@"asm" => return base.cast(Assembly).?.ty,
-            .@"fn" => return base.cast(Fn).?.ty,
-
-            .ptrtoint => return Type.initTag(.@"usize"),
-            .@"unreachable" => return Type.initTag(.@"noreturn"),
-            .@"export" => return Type.initTag(.@"void"),
-            .fntype, .primitive => return Type.initTag(.@"type"),
-
-            .fieldptr,
-            .deref,
-            => return null,
-        }
+    tag: Tag,
+    ty: Type,
+    src_offset: usize,
+
+    pub const Tag = enum {
+        unreach,
+        constant,
+        assembly,
+    };
+
+    pub fn cast(base: *Inst, comptime T: type) ?*T {
+        if (base.tag != T.base_tag)
+            return null;
+
+        return @fieldParentPtr(T, "base", base);
     }
 
-    /// This struct owns the `Value` memory. When the struct is deallocated,
-    /// so is the `Value`. The value of a constant must be copied into
-    /// a memory location for the value to survive after a const instruction.
     pub const Constant = struct {
-        base: Inst = Inst{ .tag = .constant },
-        ty: Type,
+        pub const base_tag = Tag.constant;
+        base: Inst,
 
-        positionals: struct {
-            value: Value,
-        },
-        kw_args: struct {},
+        val: Value,
+    };
+
+    pub const Assembly = struct {
+        pub const base_tag = Tag.assembly;
+        base: Inst,
+
+        asm_source: []const u8,
+        is_volatile: bool,
+        output: []const u8,
+        inputs: []const []const u8,
+        clobbers: []const []const u8,
+        args: []const []const u8,
     };
 };
 
-const Analyze = struct {
-    allocator: *Allocator,
-    old_tree: *const Module,
-    errors: std.ArrayList(ErrorMsg),
-    decls: std.ArrayList(*Inst),
+const TypedValue = struct {
+    ty: Type,
+    val: Value,
+};
 
-    const NewInst = struct {
-        ptr: *Inst,
+pub const Module = struct {
+    exports: []Export,
+    errors: []ErrorMsg,
+    arena: std.heap.ArenaAllocator,
+
+    pub const Export = struct {
+        name: []const u8,
+        typed_value: TypedValue,
     };
+
+    pub fn deinit(self: *Module, allocator: *Allocator) void {
+        allocator.free(self.exports);
+        allocator.free(self.errors);
+        self.arena.deinit();
+        self.* = undefined;
+    }
+
+    pub fn emit_zir(self: Module, allocator: *Allocator) !text.Module {
+        return error.TodoImplementEmitToZIR;
+    }
 };
 
-pub fn analyze(allocator: *Allocator, old_tree: Module) !Module {
+pub const ErrorMsg = struct {
+    byte_offset: usize,
+    msg: []const u8,
+};
+
+pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module {
     var ctx = Analyze{
         .allocator = allocator,
-        .old_tree = &old_tree,
-        .decls = std.ArrayList(*Inst).init(allocator),
+        .arena = std.heap.ArenaAllocator.init(allocator),
+        .old_module = &old_module,
         .errors = std.ArrayList(ErrorMsg).init(allocator),
-        .inst_table = std.HashMap(*Inst, Analyze.InstData).init(allocator),
+        .inst_table = std.AutoHashMap(*text.Inst, Analyze.NewInst).init(allocator),
+        .exports = std.ArrayList(Module.Export).init(allocator),
     };
-    defer ctx.decls.deinit();
     defer ctx.errors.deinit();
-    defer inst_table.deinit();
+    defer ctx.inst_table.deinit();
+    defer ctx.exports.deinit();
 
-    analyzeRoot(&ctx) catch |err| switch (err) {
-        error.AnalyzeFailure => {
+    ctx.analyzeRoot() catch |err| switch (err) {
+        error.AnalysisFail => {
             assert(ctx.errors.items.len != 0);
         },
         else => |e| return e,
     };
     return Module{
-        .decls = ctx.decls.toOwnedSlice(),
+        .exports = ctx.exports.toOwnedSlice(),
         .errors = ctx.errors.toOwnedSlice(),
+        .arena = ctx.arena,
     };
 }
 
-fn analyzeRoot(ctx: *Analyze) !void {
-    for (old_tree.decls) |decl| {
-        if (decl.cast(Inst.Export)) |export_inst| {
-            try analyzeExport(ctx, export_inst);
+const Analyze = struct {
+    allocator: *Allocator,
+    arena: std.heap.ArenaAllocator,
+    old_module: *const text.Module,
+    errors: std.ArrayList(ErrorMsg),
+    inst_table: std.AutoHashMap(*text.Inst, NewInst),
+    exports: std.ArrayList(Module.Export),
+
+    const NewInst = struct {
+        /// null means a semantic analysis error happened
+        ptr: ?*Inst,
+    };
+
+    const InnerError = error{ OutOfMemory, AnalysisFail };
+
+    fn analyzeRoot(self: *Analyze) !void {
+        for (self.old_module.decls) |decl| {
+            if (decl.cast(text.Inst.Export)) |export_inst| {
+                try analyzeExport(self, export_inst);
+            }
         }
     }
-}
 
-fn analyzeExport(ctx: *Analyze, export_inst: *Inst.Export) !void {
-    const old_decl = export_inst.positionals.value;
-    const new_info = ctx.inst_table.get(old_exp_target) orelse blk: {
-        const new_decl = try analyzeDecl(ctx, old_decl);
-        const new_info: Analyze.NewInst = .{ .ptr = new_decl };
-        try ctx.inst_table.put(old_decl, new_info);
-        break :blk new_info;
-    };
+    fn resolveInst(self: *Analyze, old_inst: *text.Inst) InnerError!*Inst {
+        if (self.inst_table.get(old_inst)) |kv| {
+            return kv.value.ptr orelse return error.AnalysisFail;
+        } else {
+            const new_inst = self.analyzeDecl(old_inst) catch |err| switch (err) {
+                error.AnalysisFail => {
+                    try self.inst_table.putNoClobber(old_inst, .{ .ptr = null });
+                    return error.AnalysisFail;
+                },
+                else => |e| return e,
+            };
+            try self.inst_table.putNoClobber(old_inst, .{ .ptr = new_inst });
+            return new_inst;
+        }
+    }
 
-    //const exp_type = new_info.ptr.ty();
-    //switch (exp_type.zigTypeTag()) {
-    //    .Fn => {
-    //        if () |kv| {
-    //            kv.value
-    //        }
-    //        return analyzeExportFn(ctx, exp_target.cast(Inst.,
-    //    },
-    //    else => return ctx.fail("unable to export type '{}'", .{exp_type}),
-    //}
-}
+    fn resolveInstConst(self: *Analyze, old_inst: *text.Inst) InnerError!TypedValue {
+        const new_inst = try self.resolveInst(old_inst);
+        const val = try self.resolveConstValue(new_inst);
+        return TypedValue{
+            .ty = new_inst.ty,
+            .val = val,
+        };
+    }
+
+    fn resolveConstValue(self: *Analyze, base: *Inst) !Value {
+        const const_inst = base.cast(Inst.Constant) orelse
+            return self.fail(base.src_offset, "unable to resolve comptime value", .{});
+        return const_inst.val;
+    }
+
+    fn resolveConstString(self: *Analyze, old_inst: *text.Inst) ![]u8 {
+        const new_inst = try self.resolveInst(old_inst);
+        const wanted_type = Type.initTag(.const_slice_u8);
+        const coerced_inst = try self.coerce(wanted_type, new_inst);
+        const val = try self.resolveConstValue(coerced_inst);
+        return val.toAllocatedBytes(&self.arena.allocator);
+    }
+
+    fn analyzeExport(self: *Analyze, export_inst: *text.Inst.Export) !void {
+        const symbol_name = try self.resolveConstString(export_inst.positionals.symbol_name);
+        const typed_value = try self.resolveInstConst(export_inst.positionals.value);
+
+        switch (typed_value.ty.zigTypeTag()) {
+            .Fn => {},
+            else => return self.fail(
+                export_inst.positionals.value.src_offset,
+                "unable to export type '{}'",
+                .{typed_value.ty},
+            ),
+        }
+        try self.exports.append(.{
+            .name = symbol_name,
+            .typed_value = typed_value,
+        });
+    }
+
+    fn analyzeDecl(self: *Analyze, old_inst: *text.Inst) !*Inst {
+        switch (old_inst.tag) {
+            .str => return self.fail(old_inst.src_offset, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
+            .int => return self.fail(old_inst.src_offset, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
+            .ptrtoint => return self.fail(old_inst.src_offset, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
+            .fieldptr => return self.fail(old_inst.src_offset, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
+            .deref => return self.fail(old_inst.src_offset, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
+            .as => return self.fail(old_inst.src_offset, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
+            .@"asm" => return self.fail(old_inst.src_offset, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
+            .@"unreachable" => return self.fail(old_inst.src_offset, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
+            .@"fn" => return self.fail(old_inst.src_offset, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
+            .@"export" => return self.fail(old_inst.src_offset, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
+            .primitive => return self.fail(old_inst.src_offset, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
+            .fntype => return self.fail(old_inst.src_offset, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
+        }
+    }
+
+    fn coerce(self: *Analyze, dest_type: Type, inst: *Inst) !*Inst {
+        return self.fail(inst.src_offset, "TODO implement type coercion", .{});
+    }
+
+    fn fail(self: *Analyze, src_offset: usize, comptime format: []const u8, args: var) InnerError {
+        @setCold(true);
+        const msg = try std.fmt.allocPrint(&self.arena.allocator, format, args);
+        (try self.errors.addOne()).* = .{
+            .byte_offset = src_offset,
+            .msg = msg,
+        };
+        return error.AnalysisFail;
+    }
+};
 
 pub fn main() anyerror!void {
     var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
     defer arena.deinit();
-    const allocator = &arena.allocator;
+    const allocator = if (std.builtin.link_libc) std.heap.c_allocator else &arena.allocator;
 
     const args = try std.process.argsAlloc(allocator);
 
@@ -116,11 +231,11 @@ pub fn main() anyerror!void {
 
     const source = try std.fs.cwd().readFileAllocOptions(allocator, src_path, std.math.maxInt(u32), 1, 0);
 
-    var tree = try text.parse(allocator, source);
-    defer tree.deinit();
+    var zir_module = try text.parse(allocator, source);
+    defer zir_module.deinit(allocator);
 
-    if (tree.errors.len != 0) {
-        for (tree.errors) |err_msg| {
+    if (zir_module.errors.len != 0) {
+        for (zir_module.errors) |err_msg| {
             const loc = findLineColumn(source, err_msg.byte_offset);
             std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
         }
@@ -128,21 +243,22 @@ pub fn main() anyerror!void {
         std.process.exit(1);
     }
 
-    tree.dump();
+    var analyzed_module = try analyze(allocator, zir_module);
+    defer analyzed_module.deinit(allocator);
 
-    //const new_tree = try analyze(allocator, tree);
-    //defer new_tree.deinit();
+    if (analyzed_module.errors.len != 0) {
+        for (analyzed_module.errors) |err_msg| {
+            const loc = findLineColumn(source, err_msg.byte_offset);
+            std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
+        }
+        if (debug_error_trace) return error.ParseFailure;
+        std.process.exit(1);
+    }
 
-    //if (new_tree.errors.len != 0) {
-    //    for (new_tree.errors) |err_msg| {
-    //        const loc = findLineColumn(source, err_msg.byte_offset);
-    //        std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
-    //    }
-    //    if (debug_error_trace) return error.ParseFailure;
-    //    std.process.exit(1);
-    //}
+    var new_zir_module = try analyzed_module.emit_zir(allocator);
+    defer new_zir_module.deinit(allocator);
 
-    //new_tree.dump();
+    new_zir_module.dump();
 }
 
 fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, column: usize } {
src-self-hosted/type.zig
@@ -18,9 +18,39 @@ pub const Type = extern union {
 
     pub fn zigTypeTag(self: Type) std.builtin.TypeId {
         switch (self.tag()) {
-            .@"u8", .@"usize" => return .Int,
-            .array_u8, .array_u8_sentinel_0 => return .Array,
+            .@"u8",
+            .@"i8",
+            .@"isize",
+            .@"usize",
+            .@"c_short",
+            .@"c_ushort",
+            .@"c_int",
+            .@"c_uint",
+            .@"c_long",
+            .@"c_ulong",
+            .@"c_longlong",
+            .@"c_ulonglong",
+            .@"c_longdouble",
+            => return .Int,
+
+            .@"f16",
+            .@"f32",
+            .@"f64",
+            .@"f128",
+            => return .Float,
+
+            .@"c_void" => return .Opaque,
+            .@"bool" => return .Bool,
+            .@"void" => return .Void,
+            .@"type" => return .Type,
+            .@"anyerror" => return .ErrorSet,
+            .@"comptime_int" => return .ComptimeInt,
+            .@"comptime_float" => return .ComptimeFloat,
+            .@"noreturn" => return .NoReturn,
+
+            .array, .array_u8_sentinel_0 => return .Array,
             .single_const_pointer => return .Pointer,
+            .const_slice_u8 => return .Pointer,
         }
     }
 
@@ -51,35 +81,36 @@ pub const Type = extern union {
         comptime assert(fmt.len == 0);
         var ty = self;
         while (true) {
-            switch (ty.tag()) {
-                @"u8",
-                @"i8",
-                @"isize",
-                @"usize",
-                @"noreturn",
-                @"void",
-                @"c_short",
-                @"c_ushort",
-                @"c_int",
-                @"c_uint",
-                @"c_long",
-                @"c_ulong",
-                @"c_longlong",
-                @"c_ulonglong",
-                @"c_longdouble",
-                @"c_void",
-                @"f16",
-                @"f32",
-                @"f64",
-                @"f128",
-                @"bool",
-                @"void",
-                @"type",
-                @"anyerror",
-                @"comptime_int",
-                @"comptime_float",
-                @"noreturn",
-                => |t| return out_stream.writeAll(@tagName(t)),
+            const t = ty.tag();
+            switch (t) {
+                .@"u8",
+                .@"i8",
+                .@"isize",
+                .@"usize",
+                .@"c_short",
+                .@"c_ushort",
+                .@"c_int",
+                .@"c_uint",
+                .@"c_long",
+                .@"c_ulong",
+                .@"c_longlong",
+                .@"c_ulonglong",
+                .@"c_longdouble",
+                .@"c_void",
+                .@"f16",
+                .@"f32",
+                .@"f64",
+                .@"f128",
+                .@"bool",
+                .@"void",
+                .@"type",
+                .@"anyerror",
+                .@"comptime_int",
+                .@"comptime_float",
+                .@"noreturn",
+                => return out_stream.writeAll(@tagName(t)),
+
+                .const_slice_u8 => return out_stream.writeAll("[]const u8"),
 
                 .array_u8_sentinel_0 => {
                     const payload = @fieldParentPtr(Payload.Array_u8_Sentinel0, "base", ty.ptr_otherwise);
@@ -110,6 +141,7 @@ pub const Type = extern union {
     /// See `zigTypeTag` for the function that corresponds to `std.builtin.TypeId`.
     pub const Tag = enum {
         // The first section of this enum are tags that require no payload.
+        const_slice_u8,
         @"u8",
         @"i8",
         @"isize",
src-self-hosted/value.zig
@@ -91,6 +91,15 @@ pub const Value = extern union {
         }
     }
 
+    /// Asserts that the value is representable as an array of bytes.
+    /// Copies the value into a freshly allocated slice of memory, which is owned by the caller.
+    pub fn toAllocatedBytes(self: Value, allocator: *std.mem.Allocator) error{OutOfMemory}![]u8 {
+        if (self.cast(Payload.Bytes)) |bytes| {
+            return std.mem.dupe(allocator, u8, bytes.data);
+        }
+        unreachable;
+    }
+
     /// This type is not copyable since it may contain pointers to its inner data.
     pub const Payload = struct {
         tag: Tag,