Commit 6f717b18f0

Andrew Kelley <andrew@ziglang.org>
2023-02-24 03:21:44
std.zig.ErrorBundle: rework binary encoding
* Separate into a "WIP" struct and a "finished" struct. * Use a bit of indirection for error notes to simplify ergonomics of this data structure.
1 parent 572cb24
lib/std/zig/ErrorBundle.zig
@@ -3,24 +3,22 @@
 //! is used to collect all the errors from the various places into one
 //! convenient place for API users to consume.
 
-string_bytes: std.ArrayListUnmanaged(u8),
-/// The first thing in this array is a ErrorMessageListIndex.
-extra: std.ArrayListUnmanaged(u32),
+string_bytes: []const u8,
+/// The first thing in this array is an `ErrorMessageList`.
+extra: []const u32,
 
 // An index into `extra` pointing at an `ErrorMessage`.
 pub const MessageIndex = enum(u32) {
     _,
 };
 
-/// After the header is:
-/// * string_bytes
-/// * extra (little endian)
-pub const Header = struct {
-    string_bytes_len: u32,
-    extra_len: u32,
+// An index into `extra` pointing at an `SourceLocation`.
+pub const SourceLocationIndex = enum(u32) {
+    none = 0,
+    _,
 };
 
-/// Trailing: ErrorMessage for each len
+/// There will be a MessageIndex for each len at start.
 pub const ErrorMessageList = struct {
     len: u32,
     start: u32,
@@ -46,14 +44,13 @@ pub const SourceLocation = struct {
 };
 
 /// Trailing:
-/// * ErrorMessage for each notes_len.
+/// * MessageIndex for each notes_len.
 pub const ErrorMessage = struct {
     /// null terminated string index
     msg: u32,
     /// Usually one, but incremented for redundant messages.
     count: u32 = 1,
-    /// 0 or the index into extra of a SourceLocation
-    src_loc: u32 = 0,
+    src_loc: SourceLocationIndex = .none,
     notes_len: u32 = 0,
 };
 
@@ -65,170 +62,41 @@ pub const ReferenceTrace = struct {
     decl_name: u32,
     /// Index into extra of a SourceLocation
     /// If this is 0, this is the sentinel ReferenceTrace element.
-    src_loc: u32,
+    src_loc: SourceLocationIndex,
 };
 
-pub fn init(eb: *ErrorBundle, gpa: Allocator) !void {
-    eb.* = .{
-        .string_bytes = .{},
-        .extra = .{},
-    };
-
-    // So that 0 can be used to indicate a null string.
-    try eb.string_bytes.append(gpa, 0);
-
-    _ = try addExtra(eb, gpa, ErrorMessageList{
-        .len = 0,
-        .start = 0,
-    });
-}
-
 pub fn deinit(eb: *ErrorBundle, gpa: Allocator) void {
-    eb.string_bytes.deinit(gpa);
-    eb.extra.deinit(gpa);
+    gpa.free(eb.string_bytes);
+    gpa.free(eb.extra);
     eb.* = undefined;
 }
 
-pub fn addString(eb: *ErrorBundle, gpa: Allocator, s: []const u8) !u32 {
-    const index = @intCast(u32, eb.string_bytes.items.len);
-    try eb.string_bytes.ensureUnusedCapacity(gpa, s.len + 1);
-    eb.string_bytes.appendSliceAssumeCapacity(s);
-    eb.string_bytes.appendAssumeCapacity(0);
-    return index;
-}
-
-pub fn printString(eb: *ErrorBundle, gpa: Allocator, comptime fmt: []const u8, args: anytype) !u32 {
-    const index = @intCast(u32, eb.string_bytes.items.len);
-    try eb.string_bytes.writer(gpa).print(fmt, args);
-    try eb.string_bytes.append(gpa, 0);
-    return index;
-}
-
-pub fn addErrorMessage(eb: *ErrorBundle, gpa: Allocator, em: ErrorMessage) !void {
-    if (eb.errorMessageCount() == 0) {
-        eb.setStartIndex(@intCast(u32, eb.extra.items.len));
-    }
-    _ = try addExtra(eb, gpa, em);
-}
-
-pub fn addSourceLocation(eb: *ErrorBundle, gpa: Allocator, sl: SourceLocation) !u32 {
-    return addExtra(eb, gpa, sl);
-}
-
-pub fn addReferenceTrace(eb: *ErrorBundle, gpa: Allocator, rt: ReferenceTrace) !void {
-    _ = try addExtra(eb, gpa, rt);
-}
-
-pub fn addBundle(eb: *ErrorBundle, gpa: Allocator, other: ErrorBundle) !void {
-    // Skip over the initial ErrorMessageList len field.
-    const root_fields_len = @typeInfo(ErrorMessageList).Struct.fields.len;
-    const other_list = other.extraData(ErrorMessageList, 0).data;
-    const other_extra = other.extra.items[root_fields_len..];
-
-    try eb.string_bytes.ensureUnusedCapacity(gpa, other.string_bytes.items.len);
-    try eb.extra.ensureUnusedCapacity(gpa, other_extra.len);
-
-    const new_string_base = @intCast(u32, eb.string_bytes.items.len);
-    const new_data_base = @intCast(u32, eb.extra.items.len - root_fields_len);
-
-    eb.string_bytes.appendSliceAssumeCapacity(other.string_bytes.items);
-    eb.extra.appendSliceAssumeCapacity(other_extra);
-
-    // Now we must offset the string indexes and extra indexes of the newly
-    // added extra.
-    var index = new_data_base + other_list.start;
-    for (0..other_list.len) |_| {
-        index = try patchMessage(eb, index, new_string_base, new_data_base);
-    }
-}
-
-fn patchMessage(eb: *ErrorBundle, msg_idx: usize, new_string_base: u32, new_data_base: u32) !u32 {
-    var msg = eb.extraData(ErrorMessage, msg_idx);
-    if (msg.data.msg != 0) msg.data.msg += new_string_base;
-    if (msg.data.src_loc != 0) msg.data.src_loc += new_data_base;
-    eb.setExtra(msg_idx, msg.data);
-
-    try patchSrcLoc(eb, msg.data.src_loc, new_string_base, new_data_base);
-
-    var index = @intCast(u32, msg.end);
-    for (0..msg.data.notes_len) |_| {
-        index = try patchMessage(eb, index, new_string_base, new_data_base);
-    }
-    return index;
-}
-
-fn patchSrcLoc(eb: *ErrorBundle, idx: usize, new_string_base: u32, new_data_base: u32) !void {
-    if (idx == 0) return;
-
-    var src_loc = eb.extraData(SourceLocation, idx);
-    if (src_loc.data.src_path != 0) src_loc.data.src_path += new_string_base;
-    if (src_loc.data.source_line != 0) src_loc.data.source_line += new_string_base;
-    eb.setExtra(idx, src_loc.data);
-
-    var index = src_loc.end;
-    for (0..src_loc.data.reference_trace_len) |_| {
-        var ref_trace = eb.extraData(ReferenceTrace, index);
-        if (ref_trace.data.decl_name != 0) ref_trace.data.decl_name += new_string_base;
-        if (ref_trace.data.src_loc != 0) ref_trace.data.src_loc += new_data_base;
-        eb.setExtra(index, ref_trace.data);
-        try patchSrcLoc(eb, ref_trace.data.src_loc, new_string_base, new_data_base);
-        index = ref_trace.end;
-    }
-}
-
-fn addExtra(eb: *ErrorBundle, gpa: Allocator, extra: anytype) Allocator.Error!u32 {
-    const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
-    try eb.extra.ensureUnusedCapacity(gpa, fields.len);
-    return addExtraAssumeCapacity(eb, extra);
-}
-
-fn addExtraAssumeCapacity(eb: *ErrorBundle, extra: anytype) u32 {
-    const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
-    const result = @intCast(u32, eb.extra.items.len);
-    eb.extra.items.len += fields.len;
-    setExtra(eb, result, extra);
-    return result;
-}
-
-fn setExtra(eb: *ErrorBundle, index: usize, extra: anytype) void {
-    const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
-    var i = index;
-    inline for (fields) |field| {
-        eb.extra.items[i] = switch (field.type) {
-            u32 => @field(extra, field.name),
-            else => @compileError("bad field type"),
-        };
-        i += 1;
-    }
-}
-
 pub fn errorMessageCount(eb: ErrorBundle) u32 {
-    return eb.extra.items[0];
-}
-
-pub fn setErrorMessageCount(eb: *ErrorBundle, count: u32) void {
-    eb.extra.items[0] = count;
+    return eb.getErrorMessageList().len;
 }
 
-pub fn incrementCount(eb: *ErrorBundle, delta: u32) void {
-    eb.extra.items[0] += delta;
+pub fn getErrorMessageList(eb: ErrorBundle) ErrorMessageList {
+    return eb.extraData(ErrorMessageList, 0).data;
 }
 
-pub fn getStartIndex(eb: ErrorBundle) u32 {
-    return eb.extra.items[1];
-}
-
-pub fn setStartIndex(eb: *ErrorBundle, index: u32) void {
-    eb.extra.items[1] = index;
+pub fn getMessages(eb: ErrorBundle) []const MessageIndex {
+    const list = eb.getErrorMessageList();
+    return @ptrCast([]const MessageIndex, eb.extra[list.start..][0..list.len]);
 }
 
 pub fn getErrorMessage(eb: ErrorBundle, index: MessageIndex) ErrorMessage {
     return eb.extraData(ErrorMessage, @enumToInt(index)).data;
 }
 
-pub fn getSourceLocation(eb: ErrorBundle, index: u32) SourceLocation {
-    assert(index != 0);
-    return eb.extraData(SourceLocation, index).data;
+pub fn getSourceLocation(eb: ErrorBundle, index: SourceLocationIndex) SourceLocation {
+    assert(index != .none);
+    return eb.extraData(SourceLocation, @enumToInt(index)).data;
+}
+
+pub fn getNotes(eb: ErrorBundle, index: MessageIndex) []const MessageIndex {
+    const notes_len = eb.getErrorMessage(index).notes_len;
+    const start = @enumToInt(index) + @typeInfo(ErrorMessage).Struct.fields.len;
+    return @ptrCast([]const MessageIndex, eb.extra[start..][0..notes_len]);
 }
 
 /// Returns the requested data, as well as the new index which is at the start of the
@@ -239,7 +107,9 @@ fn extraData(eb: ErrorBundle, comptime T: type, index: usize) struct { data: T,
     var result: T = undefined;
     inline for (fields) |field| {
         @field(result, field.name) = switch (field.type) {
-            u32 => eb.extra.items[i],
+            u32 => eb.extra[i],
+            MessageIndex => @intToEnum(MessageIndex, eb.extra[i]),
+            SourceLocationIndex => @intToEnum(SourceLocationIndex, eb.extra[i]),
             else => @compileError("bad field type"),
         };
         i += 1;
@@ -252,7 +122,7 @@ fn extraData(eb: ErrorBundle, comptime T: type, index: usize) struct { data: T,
 
 /// Given an index into `string_bytes` returns the null-terminated string found there.
 pub fn nullTerminatedString(eb: ErrorBundle, index: usize) [:0]const u8 {
-    const string_bytes = eb.string_bytes.items;
+    const string_bytes = eb.string_bytes;
     var end: usize = index;
     while (string_bytes[end] != 0) {
         end += 1;
@@ -272,28 +142,25 @@ pub fn renderToWriter(
     ttyconf: std.debug.TTY.Config,
     writer: anytype,
 ) anyerror!void {
-    const list = eb.extraData(ErrorMessageList, 0).data;
-    var index: usize = list.start;
-    for (0..list.len) |_| {
-        const err_msg = eb.extraData(ErrorMessage, index);
-        index = try renderErrorMessageToWriter(eb, err_msg.data, err_msg.end, ttyconf, writer, "error", .Red, 0);
+    for (eb.getMessages()) |err_msg| {
+        try renderErrorMessageToWriter(eb, err_msg, ttyconf, writer, "error", .Red, 0);
     }
 }
 
 fn renderErrorMessageToWriter(
     eb: ErrorBundle,
-    err_msg: ErrorMessage,
-    end_index: usize,
+    err_msg_index: MessageIndex,
     ttyconf: std.debug.TTY.Config,
     stderr: anytype,
     kind: []const u8,
     color: std.debug.TTY.Color,
     indent: usize,
-) anyerror!usize {
+) anyerror!void {
     var counting_writer = std.io.countingWriter(stderr);
     const counting_stderr = counting_writer.writer();
-    if (err_msg.src_loc != 0) {
-        const src = eb.extraData(SourceLocation, err_msg.src_loc);
+    const err_msg = eb.getErrorMessage(err_msg_index);
+    if (err_msg.src_loc != .none) {
+        const src = eb.extraData(SourceLocation, @enumToInt(err_msg.src_loc));
         try counting_stderr.writeByteNTimes(' ', indent);
         try ttyconf.setColor(stderr, .Bold);
         try counting_stderr.print("{s}:{d}:{d}: ", .{
@@ -337,10 +204,8 @@ fn renderErrorMessageToWriter(
             try stderr.writeByte('\n');
             try ttyconf.setColor(stderr, .Reset);
         }
-        var index = end_index;
-        for (0..err_msg.notes_len) |_| {
-            const note = eb.extraData(ErrorMessage, index);
-            index = try renderErrorMessageToWriter(eb, note.data, note.end, ttyconf, stderr, "note", .Cyan, indent);
+        for (eb.getNotes(err_msg_index)) |note| {
+            try renderErrorMessageToWriter(eb, note, ttyconf, stderr, "note", .Cyan, indent);
         }
         if (src.data.reference_trace_len > 0) {
             try ttyconf.setColor(stderr, .Reset);
@@ -350,7 +215,7 @@ fn renderErrorMessageToWriter(
             for (0..src.data.reference_trace_len) |_| {
                 const ref_trace = eb.extraData(ReferenceTrace, ref_index);
                 ref_index = ref_trace.end;
-                if (ref_trace.data.src_loc != 0) {
+                if (ref_trace.data.src_loc != .none) {
                     const ref_src = eb.getSourceLocation(ref_trace.data.src_loc);
                     try stderr.print("    {s}: {s}:{d}:{d}\n", .{
                         eb.nullTerminatedString(ref_trace.data.decl_name),
@@ -374,7 +239,6 @@ fn renderErrorMessageToWriter(
             try stderr.writeByte('\n');
             try ttyconf.setColor(stderr, .Reset);
         }
-        return index;
     } else {
         try ttyconf.setColor(stderr, color);
         try stderr.writeByteNTimes(' ', indent);
@@ -390,12 +254,9 @@ fn renderErrorMessageToWriter(
             try stderr.print(" ({d} times)\n", .{err_msg.count});
         }
         try ttyconf.setColor(stderr, .Reset);
-        var index = end_index;
-        for (0..err_msg.notes_len) |_| {
-            const note = eb.extraData(ErrorMessage, index);
-            index = try renderErrorMessageToWriter(eb, note.data, note.end, ttyconf, stderr, "note", .Cyan, indent + 4);
+        for (eb.getNotes(err_msg_index)) |note| {
+            try renderErrorMessageToWriter(eb, note, ttyconf, stderr, "note", .Cyan, indent + 4);
         }
-        return index;
     }
 }
 
@@ -417,3 +278,186 @@ const std = @import("std");
 const ErrorBundle = @This();
 const Allocator = std.mem.Allocator;
 const assert = std.debug.assert;
+
+pub const Wip = struct {
+    gpa: Allocator,
+    string_bytes: std.ArrayListUnmanaged(u8),
+    /// The first thing in this array is a ErrorMessageList.
+    extra: std.ArrayListUnmanaged(u32),
+    root_list: std.ArrayListUnmanaged(MessageIndex),
+
+    pub fn init(wip: *Wip, gpa: Allocator) !void {
+        wip.* = .{
+            .gpa = gpa,
+            .string_bytes = .{},
+            .extra = .{},
+            .root_list = .{},
+        };
+
+        // So that 0 can be used to indicate a null string.
+        try wip.string_bytes.append(gpa, 0);
+
+        assert(0 == try addExtra(wip, ErrorMessageList{
+            .len = 0,
+            .start = 0,
+        }));
+    }
+
+    pub fn deinit(wip: *Wip) void {
+        const gpa = wip.gpa;
+        wip.root_list.deinit(gpa);
+        wip.string_bytes.deinit(gpa);
+        wip.extra.deinit(gpa);
+        wip.* = undefined;
+    }
+
+    pub fn toOwnedBundle(wip: *Wip) !ErrorBundle {
+        const gpa = wip.gpa;
+        wip.setExtra(0, ErrorMessageList{
+            .len = @intCast(u32, wip.root_list.items.len),
+            .start = @intCast(u32, wip.extra.items.len),
+        });
+        try wip.extra.appendSlice(gpa, @ptrCast([]const u32, wip.root_list.items));
+        wip.root_list.clearAndFree(gpa);
+        return .{
+            .string_bytes = try wip.string_bytes.toOwnedSlice(gpa),
+            .extra = try wip.extra.toOwnedSlice(gpa),
+        };
+    }
+
+    pub fn tmpBundle(wip: Wip) ErrorBundle {
+        return .{
+            .string_bytes = wip.string_bytes.items,
+            .extra = wip.extra.items,
+        };
+    }
+
+    pub fn addString(wip: *Wip, s: []const u8) !u32 {
+        const gpa = wip.gpa;
+        const index = @intCast(u32, wip.string_bytes.items.len);
+        try wip.string_bytes.ensureUnusedCapacity(gpa, s.len + 1);
+        wip.string_bytes.appendSliceAssumeCapacity(s);
+        wip.string_bytes.appendAssumeCapacity(0);
+        return index;
+    }
+
+    pub fn printString(wip: *Wip, comptime fmt: []const u8, args: anytype) !u32 {
+        const gpa = wip.gpa;
+        const index = @intCast(u32, wip.string_bytes.items.len);
+        try wip.string_bytes.writer(gpa).print(fmt, args);
+        try wip.string_bytes.append(gpa, 0);
+        return index;
+    }
+
+    pub fn addRootErrorMessage(wip: *Wip, em: ErrorMessage) !void {
+        try wip.root_list.ensureUnusedCapacity(wip.gpa, 1);
+        wip.root_list.appendAssumeCapacity(try addErrorMessage(wip, em));
+    }
+
+    pub fn addErrorMessage(wip: *Wip, em: ErrorMessage) !MessageIndex {
+        return @intToEnum(MessageIndex, try addExtra(wip, em));
+    }
+
+    pub fn addErrorMessageAssumeCapacity(wip: *Wip, em: ErrorMessage) MessageIndex {
+        return @intToEnum(MessageIndex, addExtraAssumeCapacity(wip, em));
+    }
+
+    pub fn addSourceLocation(wip: *Wip, sl: SourceLocation) !SourceLocationIndex {
+        return @intToEnum(SourceLocationIndex, try addExtra(wip, sl));
+    }
+
+    pub fn addReferenceTrace(wip: *Wip, rt: ReferenceTrace) !void {
+        _ = try addExtra(wip, rt);
+    }
+
+    pub fn addBundle(wip: *Wip, other: ErrorBundle) !void {
+        const gpa = wip.gpa;
+
+        try wip.string_bytes.ensureUnusedCapacity(gpa, other.string_bytes.len);
+        try wip.extra.ensureUnusedCapacity(gpa, other.extra.len);
+
+        const other_list = other.getMessages();
+
+        // The ensureUnusedCapacity call above guarantees this.
+        const notes_start = wip.reserveNotes(@intCast(u32, other_list.len)) catch unreachable;
+        for (notes_start.., other_list) |note, message| {
+            wip.extra.items[note] = @enumToInt(wip.addOtherMessage(other, message) catch unreachable);
+        }
+    }
+
+    pub fn reserveNotes(wip: *Wip, notes_len: u32) !u32 {
+        try wip.extra.ensureUnusedCapacity(wip.gpa, notes_len +
+            notes_len * @typeInfo(ErrorBundle.ErrorMessage).Struct.fields.len);
+        wip.extra.items.len += notes_len;
+        return @intCast(u32, wip.extra.items.len - notes_len);
+    }
+
+    fn addOtherMessage(wip: *Wip, other: ErrorBundle, msg_index: MessageIndex) !MessageIndex {
+        const other_msg = other.getErrorMessage(msg_index);
+        const src_loc = try wip.addOtherSourceLocation(other, other_msg.src_loc);
+        const msg = try wip.addErrorMessage(.{
+            .msg = try wip.addString(other.nullTerminatedString(other_msg.msg)),
+            .count = other_msg.count,
+            .src_loc = src_loc,
+            .notes_len = other_msg.notes_len,
+        });
+        const notes_start = try wip.reserveNotes(other_msg.notes_len);
+        for (notes_start.., other.getNotes(msg_index)) |note, other_note| {
+            wip.extra.items[note] = @enumToInt(try wip.addOtherMessage(other, other_note));
+        }
+        return msg;
+    }
+
+    fn addOtherSourceLocation(
+        wip: *Wip,
+        other: ErrorBundle,
+        index: SourceLocationIndex,
+    ) !SourceLocationIndex {
+        if (index == .none) return .none;
+        const other_sl = other.getSourceLocation(index);
+
+        const src_loc = try wip.addSourceLocation(.{
+            .src_path = try wip.addString(other.nullTerminatedString(other_sl.src_path)),
+            .line = other_sl.line,
+            .column = other_sl.column,
+            .span_start = other_sl.span_start,
+            .span_main = other_sl.span_main,
+            .span_end = other_sl.span_end,
+            .source_line = try wip.addString(other.nullTerminatedString(other_sl.source_line)),
+            .reference_trace_len = other_sl.reference_trace_len,
+        });
+
+        // TODO: also add the reference trace
+
+        return src_loc;
+    }
+
+    fn addExtra(wip: *Wip, extra: anytype) Allocator.Error!u32 {
+        const gpa = wip.gpa;
+        const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
+        try wip.extra.ensureUnusedCapacity(gpa, fields.len);
+        return addExtraAssumeCapacity(wip, extra);
+    }
+
+    fn addExtraAssumeCapacity(wip: *Wip, extra: anytype) u32 {
+        const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
+        const result = @intCast(u32, wip.extra.items.len);
+        wip.extra.items.len += fields.len;
+        setExtra(wip, result, extra);
+        return result;
+    }
+
+    fn setExtra(wip: *Wip, index: usize, extra: anytype) void {
+        const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
+        var i = index;
+        inline for (fields) |field| {
+            wip.extra.items[i] = switch (field.type) {
+                u32 => @field(extra, field.name),
+                MessageIndex => @enumToInt(@field(extra, field.name)),
+                SourceLocationIndex => @enumToInt(@field(extra, field.name)),
+                else => @compileError("bad field type"),
+            };
+            i += 1;
+        }
+    }
+};
src/Compilation.zig
@@ -2546,9 +2546,9 @@ pub fn totalErrorCount(self: *Compilation) u32 {
 pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
     const gpa = self.gpa;
 
-    var bundle: ErrorBundle = undefined;
+    var bundle: ErrorBundle.Wip = undefined;
     try bundle.init(gpa);
-    errdefer bundle.deinit(gpa);
+    defer bundle.deinit();
 
     {
         var it = self.failed_c_objects.iterator();
@@ -2557,12 +2557,10 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
             const err_msg = entry.value_ptr.*;
             // TODO these fields will need to be adjusted when we have proper
             // C error reporting bubbling up.
-            try bundle.addErrorMessage(gpa, .{
-                .msg = try bundle.printString(gpa, "unable to build C object: {s}", .{
-                    err_msg.msg,
-                }),
-                .src_loc = try bundle.addSourceLocation(gpa, .{
-                    .src_path = try bundle.addString(gpa, c_object.src.src_path),
+            try bundle.addRootErrorMessage(.{
+                .msg = try bundle.printString("unable to build C object: {s}", .{err_msg.msg}),
+                .src_loc = try bundle.addSourceLocation(.{
+                    .src_path = try bundle.addString(c_object.src.src_path),
                     .span_start = 0,
                     .span_main = 0,
                     .span_end = 1,
@@ -2571,49 +2569,46 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
                     .source_line = 0, // TODO
                 }),
             });
-            bundle.incrementCount(1);
         }
     }
 
     for (self.lld_errors.items) |lld_error| {
-        try bundle.addErrorMessage(gpa, .{
-            .msg = try bundle.addString(gpa, lld_error.msg),
-            .notes_len = @intCast(u32, lld_error.context_lines.len),
-        });
-        bundle.incrementCount(1);
+        const notes_len = @intCast(u32, lld_error.context_lines.len);
 
-        for (lld_error.context_lines) |context_line| {
-            try bundle.addErrorMessage(gpa, .{
-                .msg = try bundle.addString(gpa, context_line),
-            });
+        try bundle.addRootErrorMessage(.{
+            .msg = try bundle.addString(lld_error.msg),
+            .notes_len = notes_len,
+        });
+        const notes_start = try bundle.reserveNotes(notes_len);
+        for (notes_start.., lld_error.context_lines) |note, context_line| {
+            bundle.extra.items[note] = @enumToInt(bundle.addErrorMessageAssumeCapacity(.{
+                .msg = try bundle.addString(context_line),
+            }));
         }
     }
     for (self.misc_failures.values()) |*value| {
-        try bundle.addErrorMessage(gpa, .{
-            .msg = try bundle.addString(gpa, value.msg),
+        try bundle.addRootErrorMessage(.{
+            .msg = try bundle.addString(value.msg),
             .notes_len = if (value.children) |b| b.errorMessageCount() else 0,
         });
-        if (value.children) |b| try bundle.addBundle(gpa, b);
-        bundle.incrementCount(1);
+        if (value.children) |b| try bundle.addBundle(b);
     }
     if (self.alloc_failure_occurred) {
-        try bundle.addErrorMessage(gpa, .{
-            .msg = try bundle.addString(gpa, "memory allocation failure"),
+        try bundle.addRootErrorMessage(.{
+            .msg = try bundle.addString("memory allocation failure"),
         });
-        bundle.incrementCount(1);
     }
     if (self.bin_file.options.module) |module| {
         {
             var it = module.failed_files.iterator();
             while (it.next()) |entry| {
                 if (entry.value_ptr.*) |msg| {
-                    try addModuleErrorMsg(gpa, &bundle, msg.*);
+                    try addModuleErrorMsg(&bundle, msg.*);
                 } else {
-                    // Must be ZIR errors. In order for ZIR errors to exist, the parsing
-                    // must have completed successfully.
-                    const tree = try entry.key_ptr.*.getTree(module.gpa);
-                    assert(tree.errors.len == 0);
-                    try addZirErrorMessages(gpa, &bundle, entry.key_ptr.*);
+                    // Must be ZIR errors. Note that this may include AST errors.
+                    // addZirErrorMessages asserts that the tree is loaded.
+                    _ = try entry.key_ptr.*.getTree(gpa);
+                    try addZirErrorMessages(&bundle, entry.key_ptr.*);
                 }
             }
         }
@@ -2621,7 +2616,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
             var it = module.failed_embed_files.iterator();
             while (it.next()) |entry| {
                 const msg = entry.value_ptr.*;
-                try addModuleErrorMsg(gpa, &bundle, msg.*);
+                try addModuleErrorMsg(&bundle, msg.*);
             }
         }
         {
@@ -2631,21 +2626,20 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
                 // Skip errors for Decls within files that had a parse failure.
                 // We'll try again once parsing succeeds.
                 if (decl.getFileScope().okToReportErrors()) {
-                    try addModuleErrorMsg(gpa, &bundle, entry.value_ptr.*.*);
+                    try addModuleErrorMsg(&bundle, entry.value_ptr.*.*);
                     if (module.cimport_errors.get(entry.key_ptr.*)) |cimport_errors| for (cimport_errors) |c_error| {
-                        try bundle.addErrorMessage(gpa, .{
-                            .msg = try bundle.addString(gpa, std.mem.span(c_error.msg)),
-                            .src_loc = if (c_error.path) |some| try bundle.addSourceLocation(gpa, .{
-                                .src_path = try bundle.addString(gpa, std.mem.span(some)),
+                        try bundle.addRootErrorMessage(.{
+                            .msg = try bundle.addString(std.mem.span(c_error.msg)),
+                            .src_loc = if (c_error.path) |some| try bundle.addSourceLocation(.{
+                                .src_path = try bundle.addString(std.mem.span(some)),
                                 .span_start = c_error.offset,
                                 .span_main = c_error.offset,
                                 .span_end = c_error.offset + 1,
                                 .line = c_error.line,
                                 .column = c_error.column,
-                                .source_line = if (c_error.source_line) |line| try bundle.addString(gpa, std.mem.span(line)) else 0,
-                            }) else 0,
+                                .source_line = if (c_error.source_line) |line| try bundle.addString(std.mem.span(line)) else 0,
+                            }) else .none,
                         });
-                        bundle.incrementCount(1);
                     };
                 }
             }
@@ -2657,40 +2651,39 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
                 // Skip errors for Decls within files that had a parse failure.
                 // We'll try again once parsing succeeds.
                 if (decl.getFileScope().okToReportErrors()) {
-                    try addModuleErrorMsg(gpa, &bundle, entry.value_ptr.*.*);
+                    try addModuleErrorMsg(&bundle, entry.value_ptr.*.*);
                 }
             }
         }
         for (module.failed_exports.values()) |value| {
-            try addModuleErrorMsg(gpa, &bundle, value.*);
+            try addModuleErrorMsg(&bundle, value.*);
         }
     }
 
-    if (bundle.errorMessageCount() == 0) {
+    if (bundle.root_list.items.len == 0) {
         if (self.link_error_flags.no_entry_point_found) {
-            try bundle.addErrorMessage(gpa, .{
-                .msg = try bundle.addString(gpa, "no entry point found"),
+            try bundle.addRootErrorMessage(.{
+                .msg = try bundle.addString("no entry point found"),
             });
-            bundle.incrementCount(1);
         }
     }
 
     if (self.link_error_flags.missing_libc) {
-        try bundle.addErrorMessage(gpa, .{
-            .msg = try bundle.addString(gpa, "libc not available"),
+        try bundle.addRootErrorMessage(.{
+            .msg = try bundle.addString("libc not available"),
             .notes_len = 2,
         });
-        try bundle.addErrorMessage(gpa, .{
-            .msg = try bundle.addString(gpa, "run 'zig libc -h' to learn about libc installations"),
-        });
-        try bundle.addErrorMessage(gpa, .{
-            .msg = try bundle.addString(gpa, "run 'zig targets' to see the targets for which zig can always provide libc"),
-        });
-        bundle.incrementCount(1);
+        const notes_start = try bundle.reserveNotes(2);
+        bundle.extra.items[notes_start + 0] = @enumToInt(try bundle.addErrorMessage(.{
+            .msg = try bundle.addString("run 'zig libc -h' to learn about libc installations"),
+        }));
+        bundle.extra.items[notes_start + 1] = @enumToInt(try bundle.addErrorMessage(.{
+            .msg = try bundle.addString("run 'zig targets' to see the targets for which zig can always provide libc"),
+        }));
     }
 
     if (self.bin_file.options.module) |module| {
-        if (bundle.errorMessageCount() == 0 and module.compile_log_decls.count() != 0) {
+        if (bundle.root_list.items.len == 0 and module.compile_log_decls.count() != 0) {
             const keys = module.compile_log_decls.keys();
             const values = module.compile_log_decls.values();
             // First one will be the error; subsequent ones will be notes.
@@ -2699,9 +2692,9 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
             const err_msg = Module.ErrorMsg{
                 .src_loc = src_loc,
                 .msg = "found compile log statement",
-                .notes = try self.gpa.alloc(Module.ErrorMsg, module.compile_log_decls.count() - 1),
+                .notes = try gpa.alloc(Module.ErrorMsg, module.compile_log_decls.count() - 1),
             };
-            defer self.gpa.free(err_msg.notes);
+            defer gpa.free(err_msg.notes);
 
             for (keys[1..], 0..) |key, i| {
                 const note_decl = module.declPtr(key);
@@ -2711,25 +2704,26 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
                 };
             }
 
-            try addModuleErrorMsg(gpa, &bundle, err_msg);
+            try addModuleErrorMsg(&bundle, err_msg);
         }
     }
 
-    assert(self.totalErrorCount() == bundle.errorMessageCount());
+    assert(self.totalErrorCount() == bundle.root_list.items.len);
 
-    return bundle;
+    return bundle.toOwnedBundle();
 }
 
 pub const ErrorNoteHashContext = struct {
-    eb: *const ErrorBundle,
+    eb: *const ErrorBundle.Wip,
 
     pub fn hash(ctx: ErrorNoteHashContext, key: ErrorBundle.ErrorMessage) u32 {
         var hasher = std.hash.Wyhash.init(0);
+        const eb = ctx.eb.tmpBundle();
 
-        hasher.update(ctx.eb.nullTerminatedString(key.msg));
-        if (key.src_loc != 0) {
-            const src = ctx.eb.getSourceLocation(key.src_loc);
-            hasher.update(ctx.eb.nullTerminatedString(src.src_path));
+        hasher.update(eb.nullTerminatedString(key.msg));
+        if (key.src_loc != .none) {
+            const src = eb.getSourceLocation(key.src_loc);
+            hasher.update(eb.nullTerminatedString(src.src_path));
             std.hash.autoHash(&hasher, src.line);
             std.hash.autoHash(&hasher, src.column);
             std.hash.autoHash(&hasher, src.span_main);
@@ -2745,17 +2739,18 @@ pub const ErrorNoteHashContext = struct {
         b_index: usize,
     ) bool {
         _ = b_index;
-        const msg_a = ctx.eb.nullTerminatedString(a.msg);
-        const msg_b = ctx.eb.nullTerminatedString(b.msg);
+        const eb = ctx.eb.tmpBundle();
+        const msg_a = eb.nullTerminatedString(a.msg);
+        const msg_b = eb.nullTerminatedString(b.msg);
         if (!std.mem.eql(u8, msg_a, msg_b)) return false;
 
-        if (a.src_loc == 0 and b.src_loc == 0) return true;
-        if (a.src_loc == 0 or b.src_loc == 0) return false;
-        const src_a = ctx.eb.getSourceLocation(a.src_loc);
-        const src_b = ctx.eb.getSourceLocation(b.src_loc);
+        if (a.src_loc == .none and b.src_loc == .none) return true;
+        if (a.src_loc == .none or b.src_loc == .none) return false;
+        const src_a = eb.getSourceLocation(a.src_loc);
+        const src_b = eb.getSourceLocation(b.src_loc);
 
-        const src_path_a = ctx.eb.nullTerminatedString(src_a.src_path);
-        const src_path_b = ctx.eb.nullTerminatedString(src_b.src_path);
+        const src_path_a = eb.nullTerminatedString(src_a.src_path);
+        const src_path_b = eb.nullTerminatedString(src_b.src_path);
 
         return std.mem.eql(u8, src_path_a, src_path_b) and
             src_a.line == src_b.line and
@@ -2764,16 +2759,16 @@ pub const ErrorNoteHashContext = struct {
     }
 };
 
-pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Module.ErrorMsg) !void {
+pub fn addModuleErrorMsg(eb: *ErrorBundle.Wip, module_err_msg: Module.ErrorMsg) !void {
+    const gpa = eb.gpa;
     const err_source = module_err_msg.src_loc.file_scope.getSource(gpa) catch |err| {
         const file_path = try module_err_msg.src_loc.file_scope.fullPath(gpa);
         defer gpa.free(file_path);
-        try eb.addErrorMessage(gpa, .{
-            .msg = try eb.printString(gpa, "unable to load '{s}': {s}", .{
+        try eb.addRootErrorMessage(.{
+            .msg = try eb.printString("unable to load '{s}': {s}", .{
                 file_path, @errorName(err),
             }),
         });
-        eb.incrementCount(1);
         return;
     };
     const err_span = try module_err_msg.src_loc.span(gpa);
@@ -2788,13 +2783,13 @@ pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Modul
         if (module_reference.hidden != 0) {
             try ref_traces.append(gpa, .{
                 .decl_name = module_reference.hidden,
-                .src_loc = 0,
+                .src_loc = .none,
             });
             break;
         } else if (module_reference.decl == null) {
             try ref_traces.append(gpa, .{
                 .decl_name = 0,
-                .src_loc = 0,
+                .src_loc = .none,
             });
             break;
         }
@@ -2804,9 +2799,9 @@ pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Modul
         const rt_file_path = try module_reference.src_loc.file_scope.fullPath(gpa);
         defer gpa.free(rt_file_path);
         try ref_traces.append(gpa, .{
-            .decl_name = try eb.addString(gpa, std.mem.sliceTo(module_reference.decl.?, 0)),
-            .src_loc = try eb.addSourceLocation(gpa, .{
-                .src_path = try eb.addString(gpa, rt_file_path),
+            .decl_name = try eb.addString(std.mem.sliceTo(module_reference.decl.?, 0)),
+            .src_loc = try eb.addSourceLocation(.{
+                .src_path = try eb.addString(rt_file_path),
                 .span_start = span.start,
                 .span_main = span.main,
                 .span_end = span.end,
@@ -2817,8 +2812,8 @@ pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Modul
         });
     }
 
-    const src_loc = try eb.addSourceLocation(gpa, .{
-        .src_path = try eb.addString(gpa, file_path),
+    const src_loc = try eb.addSourceLocation(.{
+        .src_path = try eb.addString(file_path),
         .span_start = err_span.start,
         .span_main = err_span.main,
         .span_end = err_span.end,
@@ -2827,12 +2822,12 @@ pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Modul
         .source_line = if (module_err_msg.src_loc.lazy == .entire_file)
             0
         else
-            try eb.addString(gpa, err_loc.source_line),
+            try eb.addString(err_loc.source_line),
         .reference_trace_len = @intCast(u32, ref_traces.items.len),
     });
 
     for (ref_traces.items) |rt| {
-        try eb.addReferenceTrace(gpa, rt);
+        try eb.addReferenceTrace(rt);
     }
 
     // De-duplicate error notes. The main use case in mind for this is
@@ -2848,15 +2843,15 @@ pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Modul
         defer gpa.free(note_file_path);
 
         const gop = try notes.getOrPutContext(gpa, .{
-            .msg = try eb.addString(gpa, module_note.msg),
-            .src_loc = try eb.addSourceLocation(gpa, .{
-                .src_path = try eb.addString(gpa, note_file_path),
+            .msg = try eb.addString(module_note.msg),
+            .src_loc = try eb.addSourceLocation(.{
+                .src_path = try eb.addString(note_file_path),
                 .span_start = span.start,
                 .span_main = span.main,
                 .span_end = span.end,
                 .line = @intCast(u32, loc.line),
                 .column = @intCast(u32, loc.column),
-                .source_line = if (err_loc.eql(loc)) 0 else try eb.addString(gpa, loc.source_line),
+                .source_line = if (err_loc.eql(loc)) 0 else try eb.addString(loc.source_line),
             }),
         }, .{ .eb = eb });
         if (gop.found_existing) {
@@ -2864,24 +2859,28 @@ pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Modul
         }
     }
 
-    try eb.addErrorMessage(gpa, .{
-        .msg = try eb.addString(gpa, module_err_msg.msg),
+    const notes_len = @intCast(u32, notes.entries.len);
+
+    try eb.addRootErrorMessage(.{
+        .msg = try eb.addString(module_err_msg.msg),
         .src_loc = src_loc,
-        .notes_len = @intCast(u32, notes.entries.len),
+        .notes_len = notes_len,
     });
-    eb.incrementCount(1);
 
-    for (notes.keys()) |note| {
-        try eb.addErrorMessage(gpa, note);
+    const notes_start = try eb.reserveNotes(notes_len);
+
+    for (notes_start.., notes.keys()) |i, note| {
+        eb.extra.items[i] = @enumToInt(try eb.addErrorMessage(note));
     }
 }
 
-pub fn addZirErrorMessages(gpa: Allocator, eb: *ErrorBundle, file: *Module.File) !void {
+pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Module.File) !void {
     assert(file.zir_loaded);
     assert(file.tree_loaded);
     assert(file.source_loaded);
     const payload_index = file.zir.extra[@enumToInt(Zir.ExtraIndex.compile_errors)];
     assert(payload_index != 0);
+    const gpa = eb.gpa;
 
     const header = file.zir.extraData(Zir.Inst.CompileErrors, payload_index);
     const items_len = header.data.items_len;
@@ -2900,14 +2899,30 @@ pub fn addZirErrorMessages(gpa: Allocator, eb: *ErrorBundle, file: *Module.File)
         };
         const err_loc = std.zig.findLineColumn(file.source, err_span.main);
 
-        var notes: []ErrorBundle.ErrorMessage = &.{};
-        defer gpa.free(notes);
+        {
+            const msg = file.zir.nullTerminatedString(item.data.msg);
+            const src_path = try file.fullPath(gpa);
+            defer gpa.free(src_path);
+            try eb.addRootErrorMessage(.{
+                .msg = try eb.addString(msg),
+                .src_loc = try eb.addSourceLocation(.{
+                    .src_path = try eb.addString(src_path),
+                    .span_start = err_span.start,
+                    .span_main = err_span.main,
+                    .span_end = err_span.end,
+                    .line = @intCast(u32, err_loc.line),
+                    .column = @intCast(u32, err_loc.column),
+                    .source_line = try eb.addString(err_loc.source_line),
+                }),
+                .notes_len = item.data.notes,
+            });
+        }
 
         if (item.data.notes != 0) {
+            const notes_start = try eb.reserveNotes(item.data.notes);
             const block = file.zir.extraData(Zir.Inst.Block, item.data.notes);
             const body = file.zir.extra[block.end..][0..block.data.body_len];
-            notes = try gpa.alloc(ErrorBundle.ErrorMessage, body.len);
-            for (notes, body) |*note, body_elem| {
+            for (notes_start.., body) |note_i, body_elem| {
                 const note_item = file.zir.extraData(Zir.Inst.CompileErrors.Item, body_elem);
                 const msg = file.zir.nullTerminatedString(note_item.data.msg);
                 const span = blk: {
@@ -2923,10 +2938,10 @@ pub fn addZirErrorMessages(gpa: Allocator, eb: *ErrorBundle, file: *Module.File)
                 const src_path = try file.fullPath(gpa);
                 defer gpa.free(src_path);
 
-                note.* = .{
-                    .msg = try eb.addString(gpa, msg),
-                    .src_loc = try eb.addSourceLocation(gpa, .{
-                        .src_path = try eb.addString(gpa, src_path),
+                eb.extra.items[note_i] = @enumToInt(try eb.addErrorMessage(.{
+                    .msg = try eb.addString(msg),
+                    .src_loc = try eb.addSourceLocation(.{
+                        .src_path = try eb.addString(src_path),
                         .span_start = span.start,
                         .span_main = span.main,
                         .span_end = span.end,
@@ -2935,35 +2950,13 @@ pub fn addZirErrorMessages(gpa: Allocator, eb: *ErrorBundle, file: *Module.File)
                         .source_line = if (loc.eql(err_loc))
                             0
                         else
-                            try eb.addString(gpa, loc.source_line),
+                            try eb.addString(loc.source_line),
                     }),
                     .notes_len = 0, // TODO rework this function to be recursive
-                };
+                }));
             }
         }
-
-        const msg = file.zir.nullTerminatedString(item.data.msg);
-        const src_path = try file.fullPath(gpa);
-        defer gpa.free(src_path);
-        try eb.addErrorMessage(gpa, .{
-            .msg = try eb.addString(gpa, msg),
-            .src_loc = try eb.addSourceLocation(gpa, .{
-                .src_path = try eb.addString(gpa, src_path),
-                .span_start = err_span.start,
-                .span_main = err_span.main,
-                .span_end = err_span.end,
-                .line = @intCast(u32, err_loc.line),
-                .column = @intCast(u32, err_loc.column),
-                .source_line = try eb.addString(gpa, err_loc.source_line),
-            }),
-            .notes_len = @intCast(u32, notes.len),
-        });
-
-        for (notes) |note| {
-            try eb.addErrorMessage(gpa, note);
-        }
     }
-    eb.incrementCount(items_len);
 }
 
 pub fn getCompileLogOutput(self: *Compilation) []const u8 {
src/main.zig
@@ -4436,9 +4436,9 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
             var all_modules: Package.AllModules = .{};
             defer all_modules.deinit(gpa);
 
-            var errors: std.zig.ErrorBundle = undefined;
-            try errors.init(gpa);
-            defer errors.deinit(gpa);
+            var wip_errors: std.zig.ErrorBundle.Wip = undefined;
+            try wip_errors.init(gpa);
+            defer wip_errors.deinit();
 
             // Here we borrow main package's table and will replace it with a fresh
             // one after this process completes.
@@ -4453,15 +4453,17 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
                 &dependencies_source,
                 &build_roots_source,
                 "",
-                &errors,
+                &wip_errors,
                 &all_modules,
             );
-            if (errors.errorMessageCount() > 0) {
+            if (wip_errors.root_list.items.len > 0) {
                 const ttyconf: std.debug.TTY.Config = switch (color) {
                     .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
                     .on => .escape_codes,
                     .off => .no_color,
                 };
+                var errors = try wip_errors.toOwnedBundle();
+                defer errors.deinit(gpa);
                 errors.renderToStdErr(ttyconf);
                 process.exit(1);
             }
@@ -4721,16 +4723,18 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
             defer file.zir.deinit(gpa);
 
             if (file.zir.hasCompileErrors()) {
-                var errors: std.zig.ErrorBundle = undefined;
-                try errors.init(gpa);
-                defer errors.deinit(gpa);
-                try Compilation.addZirErrorMessages(gpa, &errors, &file);
+                var wip_errors: std.zig.ErrorBundle.Wip = undefined;
+                try wip_errors.init(gpa);
+                defer wip_errors.deinit();
+                try Compilation.addZirErrorMessages(&wip_errors, &file);
                 const ttyconf: std.debug.TTY.Config = switch (color) {
                     .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
                     .on => .escape_codes,
                     .off => .no_color,
                 };
-                errors.renderToStdErr(ttyconf);
+                var error_bundle = try wip_errors.toOwnedBundle();
+                defer error_bundle.deinit(gpa);
+                error_bundle.renderToStdErr(ttyconf);
                 has_ast_error = true;
             }
         }
@@ -4930,16 +4934,18 @@ fn fmtPathFile(
         defer file.zir.deinit(gpa);
 
         if (file.zir.hasCompileErrors()) {
-            var errors: std.zig.ErrorBundle = undefined;
-            try errors.init(gpa);
-            defer errors.deinit(gpa);
-            try Compilation.addZirErrorMessages(gpa, &errors, &file);
+            var wip_errors: std.zig.ErrorBundle.Wip = undefined;
+            try wip_errors.init(gpa);
+            defer wip_errors.deinit();
+            try Compilation.addZirErrorMessages(&wip_errors, &file);
             const ttyconf: std.debug.TTY.Config = switch (fmt.color) {
                 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
                 .on => .escape_codes,
                 .off => .no_color,
             };
-            errors.renderToStdErr(ttyconf);
+            var error_bundle = try wip_errors.toOwnedBundle();
+            defer error_bundle.deinit(gpa);
+            error_bundle.renderToStdErr(ttyconf);
             fmt.any_error = true;
         }
     }
@@ -4968,17 +4974,19 @@ fn fmtPathFile(
 }
 
 fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void {
-    var error_bundle: std.zig.ErrorBundle = undefined;
-    try error_bundle.init(gpa);
-    defer error_bundle.deinit(gpa);
+    var wip_errors: std.zig.ErrorBundle.Wip = undefined;
+    try wip_errors.init(gpa);
+    defer wip_errors.deinit();
 
-    try putAstErrorsIntoBundle(gpa, tree, path, &error_bundle);
+    try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors);
 
     const ttyconf: std.debug.TTY.Config = switch (color) {
         .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
         .on => .escape_codes,
         .off => .no_color,
     };
+    var error_bundle = try wip_errors.toOwnedBundle();
+    defer error_bundle.deinit(gpa);
     error_bundle.renderToStdErr(ttyconf);
 }
 
@@ -4986,7 +4994,7 @@ pub fn putAstErrorsIntoBundle(
     gpa: Allocator,
     tree: Ast,
     path: []const u8,
-    error_bundle: *std.zig.ErrorBundle,
+    wip_errors: *std.zig.ErrorBundle.Wip,
 ) !void {
     var file: Module.File = .{
         .status = .never_loaded,
@@ -5013,7 +5021,7 @@ pub fn putAstErrorsIntoBundle(
     file.zir_loaded = true;
     defer file.zir.deinit(gpa);
 
-    try Compilation.addZirErrorMessages(gpa, error_bundle, &file);
+    try Compilation.addZirErrorMessages(wip_errors, &file);
 }
 
 pub const info_zen =
@@ -5595,16 +5603,18 @@ pub fn cmdAstCheck(
     defer file.zir.deinit(gpa);
 
     if (file.zir.hasCompileErrors()) {
-        var errors: std.zig.ErrorBundle = undefined;
-        try errors.init(gpa);
-        defer errors.deinit(gpa);
-        try Compilation.addZirErrorMessages(gpa, &errors, &file);
+        var wip_errors: std.zig.ErrorBundle.Wip = undefined;
+        try wip_errors.init(gpa);
+        defer wip_errors.deinit();
+        try Compilation.addZirErrorMessages(&wip_errors, &file);
         const ttyconf: std.debug.TTY.Config = switch (color) {
             .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
             .on => .escape_codes,
             .off => .no_color,
         };
-        errors.renderToStdErr(ttyconf);
+        var error_bundle = try wip_errors.toOwnedBundle();
+        defer error_bundle.deinit(gpa);
+        error_bundle.renderToStdErr(ttyconf);
         process.exit(1);
     }
 
@@ -5719,12 +5729,14 @@ pub fn cmdChangelist(
     defer file.zir.deinit(gpa);
 
     if (file.zir.hasCompileErrors()) {
-        var errors: std.zig.ErrorBundle = undefined;
-        try errors.init(gpa);
-        defer errors.deinit(gpa);
-        try Compilation.addZirErrorMessages(gpa, &errors, &file);
+        var wip_errors: std.zig.ErrorBundle.Wip = undefined;
+        try wip_errors.init(gpa);
+        defer wip_errors.deinit();
+        try Compilation.addZirErrorMessages(&wip_errors, &file);
         const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr());
-        errors.renderToStdErr(ttyconf);
+        var error_bundle = try wip_errors.toOwnedBundle();
+        defer error_bundle.deinit(gpa);
+        error_bundle.renderToStdErr(ttyconf);
         process.exit(1);
     }
 
@@ -5758,12 +5770,14 @@ pub fn cmdChangelist(
     file.zir_loaded = true;
 
     if (file.zir.hasCompileErrors()) {
-        var errors: std.zig.ErrorBundle = undefined;
-        try errors.init(gpa);
-        defer errors.deinit(gpa);
-        try Compilation.addZirErrorMessages(gpa, &errors, &file);
+        var wip_errors: std.zig.ErrorBundle.Wip = undefined;
+        try wip_errors.init(gpa);
+        defer wip_errors.deinit();
+        try Compilation.addZirErrorMessages(&wip_errors, &file);
         const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr());
-        errors.renderToStdErr(ttyconf);
+        var error_bundle = try wip_errors.toOwnedBundle();
+        defer error_bundle.deinit(gpa);
+        error_bundle.renderToStdErr(ttyconf);
         process.exit(1);
     }
 
src/Package.zig
@@ -225,7 +225,7 @@ pub fn fetchAndAddDependencies(
     dependencies_source: *std.ArrayList(u8),
     build_roots_source: *std.ArrayList(u8),
     name_prefix: []const u8,
-    error_bundle: *std.zig.ErrorBundle,
+    error_bundle: *std.zig.ErrorBundle.Wip,
     all_modules: *AllModules,
 ) !void {
     const max_bytes = 10 * 1024 * 1024;
@@ -260,13 +260,12 @@ pub fn fetchAndAddDependencies(
     if (manifest.errors.len > 0) {
         const file_path = try directory.join(arena, &.{Manifest.basename});
         for (manifest.errors) |msg| {
-            try Report.addErrorMessage(gpa, ast, file_path, error_bundle, 0, msg);
+            try Report.addErrorMessage(ast, file_path, error_bundle, 0, msg);
         }
         return error.PackageFetchFailed;
     }
 
     const report: Report = .{
-        .gpa = gpa,
         .ast = &ast,
         .directory = directory,
         .error_bundle = error_bundle,
@@ -343,10 +342,9 @@ pub fn createFilePkg(
 }
 
 const Report = struct {
-    gpa: Allocator,
     ast: *const std.zig.Ast,
     directory: Compilation.Directory,
-    error_bundle: *std.zig.ErrorBundle,
+    error_bundle: *std.zig.ErrorBundle.Wip,
 
     fn fail(
         report: Report,
@@ -354,7 +352,7 @@ const Report = struct {
         comptime fmt_string: []const u8,
         fmt_args: anytype,
     ) error{ PackageFetchFailed, OutOfMemory } {
-        const gpa = report.gpa;
+        const gpa = report.error_bundle.gpa;
 
         const file_path = try report.directory.join(gpa, &.{Manifest.basename});
         defer gpa.free(file_path);
@@ -362,7 +360,7 @@ const Report = struct {
         const msg = try std.fmt.allocPrint(gpa, fmt_string, fmt_args);
         defer gpa.free(msg);
 
-        try addErrorMessage(report.gpa, report.ast.*, file_path, report.error_bundle, 0, .{
+        try addErrorMessage(report.ast.*, file_path, report.error_bundle, 0, .{
             .tok = tok,
             .off = 0,
             .msg = msg,
@@ -372,30 +370,28 @@ const Report = struct {
     }
 
     fn addErrorMessage(
-        gpa: Allocator,
         ast: std.zig.Ast,
         file_path: []const u8,
-        eb: *std.zig.ErrorBundle,
+        eb: *std.zig.ErrorBundle.Wip,
         notes_len: u32,
         msg: Manifest.ErrorMessage,
     ) error{OutOfMemory}!void {
         const token_starts = ast.tokens.items(.start);
         const start_loc = ast.tokenLocation(0, msg.tok);
 
-        try eb.addErrorMessage(gpa, .{
-            .msg = try eb.addString(gpa, msg.msg),
-            .src_loc = try eb.addSourceLocation(gpa, .{
-                .src_path = try eb.addString(gpa, file_path),
+        try eb.addRootErrorMessage(.{
+            .msg = try eb.addString(msg.msg),
+            .src_loc = try eb.addSourceLocation(.{
+                .src_path = try eb.addString(file_path),
                 .span_start = token_starts[msg.tok],
                 .span_end = @intCast(u32, token_starts[msg.tok] + ast.tokenSlice(msg.tok).len),
                 .span_main = token_starts[msg.tok] + msg.off,
                 .line = @intCast(u32, start_loc.line),
                 .column = @intCast(u32, start_loc.column),
-                .source_line = try eb.addString(gpa, ast.source[start_loc.line_start..start_loc.line_end]),
+                .source_line = try eb.addString(ast.source[start_loc.line_start..start_loc.line_end]),
             }),
             .notes_len = notes_len,
         });
-        eb.incrementCount(1);
     }
 };
 
@@ -526,14 +522,16 @@ fn fetchAndUnpack(
         defer gpa.free(file_path);
 
         const eb = report.error_bundle;
-        try Report.addErrorMessage(gpa, report.ast.*, file_path, eb, 1, .{
+        const notes_len = 1;
+        try Report.addErrorMessage(report.ast.*, file_path, eb, notes_len, .{
             .tok = dep.url_tok,
             .off = 0,
             .msg = "url field is missing corresponding hash field",
         });
-        try eb.addErrorMessage(gpa, .{
-            .msg = try eb.printString(gpa, "expected .hash = \"{s}\",", .{&actual_hex}),
-        });
+        const notes_start = try eb.reserveNotes(notes_len);
+        eb.extra.items[notes_start] = @enumToInt(try eb.addErrorMessage(.{
+            .msg = try eb.printString("expected .hash = \"{s}\",", .{&actual_hex}),
+        }));
         return error.PackageFetchFailed;
     }
 
src/Sema.zig
@@ -2215,11 +2215,12 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
 
     if (crash_report.is_enabled and sema.mod.comp.debug_compile_errors) {
         if (err_msg.src_loc.lazy == .unneeded) return error.NeededSourceLocation;
-        var errors: std.zig.ErrorBundle = undefined;
-        errors.init(gpa) catch unreachable;
-        Compilation.addModuleErrorMsg(gpa, &errors, err_msg.*) catch unreachable;
+        var wip_errors: std.zig.ErrorBundle.Wip = undefined;
+        wip_errors.init(gpa) catch unreachable;
+        Compilation.addModuleErrorMsg(&wip_errors, err_msg.*) catch unreachable;
         std.debug.print("compile error during Sema:\n", .{});
-        errors.renderToStdErr(.no_color);
+        var error_bundle = wip_errors.toOwnedBundle() catch unreachable;
+        error_bundle.renderToStdErr(.no_color);
         crash_report.compilerPanic("unexpected compile error occurred", null, null);
     }
 
src/test.zig
@@ -1242,7 +1242,7 @@ pub const TestContext = struct {
         defer self.gpa.free(zig_lib_directory.path.?);
 
         var aux_thread_pool: ThreadPool = undefined;
-        try aux_thread_pool.init(self.gpa);
+        try aux_thread_pool.init(.{ .allocator = self.gpa });
         defer aux_thread_pool.deinit();
 
         // Use the same global cache dir for all the tests, such that we for example don't have to
@@ -1614,23 +1614,8 @@ pub const TestContext = struct {
             if (update.case != .Error) {
                 var all_errors = try comp.getAllErrorsAlloc();
                 defer all_errors.deinit(allocator);
-                if (all_errors.list.len != 0) {
-                    print(
-                        "\nCase '{s}': unexpected errors at update_index={d}:\n{s}\n",
-                        .{ case.name, update_index, hr },
-                    );
-                    for (all_errors.list) |err_msg| {
-                        switch (err_msg) {
-                            .src => |src| {
-                                print("{s}:{d}:{d}: error: {s}\n{s}\n", .{
-                                    src.src_path, src.line + 1, src.column + 1, src.msg, hr,
-                                });
-                            },
-                            .plain => |plain| {
-                                print("error: {s}\n{s}\n", .{ plain.msg, hr });
-                            },
-                        }
-                    }
+                if (all_errors.errorMessageCount() > 0) {
+                    all_errors.renderToStdErr(std.debug.detectTTYConfig(std.io.getStdErr()));
                     // TODO print generated C code
                     return error.UnexpectedCompileErrors;
                 }