Merge branch 'stage2 error notes'

Closes #7555

There was still some extra work @Vexu did in that PR having to do with
adding more compile errors and notes for switch expressions, but that
can be added in a follow-up commit.
This commit is contained in:
Andrew Kelley 2021-01-17 00:16:10 -07:00
commit 44135ea84e
19 changed files with 1038 additions and 2640 deletions

View File

@ -239,12 +239,23 @@ pub fn ArrayHashMap(
return self.unmanaged.orderedRemove(key);
}
/// Asserts there is an `Entry` with matching key, deletes it from the hash map,
/// and discards it.
/// TODO: deprecated: call swapRemoveAssertDiscard instead.
pub fn removeAssertDiscard(self: *Self, key: K) void {
return self.unmanaged.removeAssertDiscard(key);
}
/// Asserts there is an `Entry` with matching key, deletes it from the hash map
/// by swapping it with the last element, and discards it.
pub fn swapRemoveAssertDiscard(self: *Self, key: K) void {
return self.unmanaged.swapRemoveAssertDiscard(key);
}
/// Asserts there is an `Entry` with matching key, deletes it from the hash map
/// by by shifting all elements forward thereby maintaining the current ordering.
pub fn orderedRemoveAssertDiscard(self: *Self, key: K) void {
return self.unmanaged.orderedRemoveAssertDiscard(key);
}
pub fn items(self: Self) []Entry {
return self.unmanaged.items();
}
@ -602,12 +613,23 @@ pub fn ArrayHashMapUnmanaged(
return self.removeInternal(key, .ordered);
}
/// Asserts there is an `Entry` with matching key, deletes it from the hash map,
/// and discards it.
/// TODO deprecated: call swapRemoveAssertDiscard instead.
pub fn removeAssertDiscard(self: *Self, key: K) void {
return self.swapRemoveAssertDiscard(key);
}
/// Asserts there is an `Entry` with matching key, deletes it from the hash map
/// by swapping it with the last element, and discards it.
pub fn swapRemoveAssertDiscard(self: *Self, key: K) void {
assert(self.swapRemove(key) != null);
}
/// Asserts there is an `Entry` with matching key, deletes it from the hash map
/// by by shifting all elements forward thereby maintaining the current ordering.
pub fn orderedRemoveAssertDiscard(self: *Self, key: K) void {
assert(self.orderedRemove(key) != null);
}
pub fn items(self: Self) []Entry {
return self.entries.items;
}

View File

@ -51,7 +51,7 @@ c_object_work_queue: std.fifo.LinearFifo(*CObject, .Dynamic),
/// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator.
/// This data is accessed by multiple threads and is protected by `mutex`.
failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *ErrorMsg) = .{},
failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *CObject.ErrorMsg) = .{},
keep_source_files_loaded: bool,
use_clang: bool,
@ -215,13 +215,29 @@ pub const CObject = struct {
},
/// There will be a corresponding ErrorMsg in Compilation.failed_c_objects.
failure,
/// A transient failure happened when trying to compile the C Object; it may
/// succeed if we try again. There may be a corresponding ErrorMsg in
/// Compilation.failed_c_objects. If there is not, the failure is out of memory.
failure_retryable,
},
pub const ErrorMsg = struct {
msg: []const u8,
line: u32,
column: u32,
pub fn destroy(em: *ErrorMsg, gpa: *Allocator) void {
gpa.free(em.msg);
gpa.destroy(em);
em.* = undefined;
}
};
/// Returns if there was failure.
pub fn clearStatus(self: *CObject, gpa: *Allocator) bool {
switch (self.status) {
.new => return false,
.failure => {
.failure, .failure_retryable => {
self.status = .new;
return true;
},
@ -240,6 +256,11 @@ pub const CObject = struct {
}
};
/// To support incremental compilation, errors are stored in various places
/// so that they can be created and destroyed appropriately. This structure
/// is used to collect all the errors from the various places into one
/// convenient place for API users to consume. It is allocated into 1 heap
/// and freed all at once.
pub const AllErrors = struct {
arena: std.heap.ArenaAllocator.State,
list: []const Message,
@ -251,23 +272,32 @@ pub const AllErrors = struct {
column: usize,
byte_offset: usize,
msg: []const u8,
notes: []Message = &.{},
},
plain: struct {
msg: []const u8,
},
pub fn renderToStdErr(self: Message) void {
switch (self) {
pub fn renderToStdErr(msg: Message) void {
return msg.renderToStdErrInner("error");
}
fn renderToStdErrInner(msg: Message, kind: []const u8) void {
switch (msg) {
.src => |src| {
std.debug.print("{s}:{d}:{d}: error: {s}\n", .{
std.debug.print("{s}:{d}:{d}: {s}: {s}\n", .{
src.src_path,
src.line + 1,
src.column + 1,
kind,
src.msg,
});
for (src.notes) |note| {
note.renderToStdErrInner("note");
}
},
.plain => |plain| {
std.debug.print("error: {s}\n", .{plain.msg});
std.debug.print("{s}: {s}\n", .{ kind, plain.msg });
},
}
}
@ -278,20 +308,38 @@ pub const AllErrors = struct {
}
fn add(
module: *Module,
arena: *std.heap.ArenaAllocator,
errors: *std.ArrayList(Message),
sub_file_path: []const u8,
source: []const u8,
simple_err_msg: ErrorMsg,
module_err_msg: Module.ErrorMsg,
) !void {
const loc = std.zig.findLineColumn(source, simple_err_msg.byte_offset);
const notes = try arena.allocator.alloc(Message, module_err_msg.notes.len);
for (notes) |*note, i| {
const module_note = module_err_msg.notes[i];
const source = try module_note.src_loc.file_scope.getSource(module);
const loc = std.zig.findLineColumn(source, module_note.src_loc.byte_offset);
const sub_file_path = module_note.src_loc.file_scope.sub_file_path;
note.* = .{
.src = .{
.src_path = try arena.allocator.dupe(u8, sub_file_path),
.msg = try arena.allocator.dupe(u8, module_note.msg),
.byte_offset = module_note.src_loc.byte_offset,
.line = loc.line,
.column = loc.column,
},
};
}
const source = try module_err_msg.src_loc.file_scope.getSource(module);
const loc = std.zig.findLineColumn(source, module_err_msg.src_loc.byte_offset);
const sub_file_path = module_err_msg.src_loc.file_scope.sub_file_path;
try errors.append(.{
.src = .{
.src_path = try arena.allocator.dupe(u8, sub_file_path),
.msg = try arena.allocator.dupe(u8, simple_err_msg.msg),
.byte_offset = simple_err_msg.byte_offset,
.msg = try arena.allocator.dupe(u8, module_err_msg.msg),
.byte_offset = module_err_msg.src_loc.byte_offset,
.line = loc.line,
.column = loc.column,
.notes = notes,
},
});
}
@ -849,17 +897,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
.ty = struct_ty,
},
};
break :rs &root_scope.base;
break :rs root_scope;
} else if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) {
const root_scope = try gpa.create(Module.Scope.ZIRModule);
root_scope.* = .{
.sub_file_path = root_pkg.root_src_path,
.source = .{ .unloaded = {} },
.contents = .{ .not_available = {} },
.status = .never_loaded,
.decls = .{},
};
break :rs &root_scope.base;
return error.ZirFilesUnsupported;
} else {
unreachable;
}
@ -1258,32 +1298,23 @@ pub fn update(self: *Compilation) !void {
const use_stage1 = build_options.is_stage1 and self.bin_file.options.use_llvm;
if (!use_stage1) {
if (self.bin_file.options.module) |module| {
module.compile_log_text.shrinkAndFree(module.gpa, 0);
module.generation += 1;
// TODO Detect which source files changed.
// Until then we simulate a full cache miss. Source files could have been loaded for any reason;
// to force a refresh we unload now.
if (module.root_scope.cast(Module.Scope.File)) |zig_file| {
zig_file.unload(module.gpa);
module.failed_root_src_file = null;
module.analyzeContainer(&zig_file.root_container) catch |err| switch (err) {
error.AnalysisFail => {
assert(self.totalErrorCount() != 0);
},
error.OutOfMemory => return error.OutOfMemory,
else => |e| {
module.failed_root_src_file = e;
},
};
} else if (module.root_scope.cast(Module.Scope.ZIRModule)) |zir_module| {
zir_module.unload(module.gpa);
module.analyzeRootZIRModule(zir_module) catch |err| switch (err) {
error.AnalysisFail => {
assert(self.totalErrorCount() != 0);
},
else => |e| return e,
};
}
module.root_scope.unload(module.gpa);
module.failed_root_src_file = null;
module.analyzeContainer(&module.root_scope.root_container) catch |err| switch (err) {
error.AnalysisFail => {
assert(self.totalErrorCount() != 0);
},
error.OutOfMemory => return error.OutOfMemory,
else => |e| {
module.failed_root_src_file = e;
},
};
// TODO only analyze imports if they are still referenced
for (module.import_table.items()) |entry| {
@ -1359,14 +1390,18 @@ pub fn totalErrorCount(self: *Compilation) usize {
module.failed_exports.items().len +
module.failed_files.items().len +
@boolToInt(module.failed_root_src_file != null);
for (module.compile_log_decls.items()) |entry| {
total += entry.value.items.len;
}
}
// The "no entry point found" error only counts if there are no other errors.
if (total == 0) {
return @boolToInt(self.link_error_flags.no_entry_point_found);
total += @boolToInt(self.link_error_flags.no_entry_point_found);
}
// Compile log errors only count if there are no other errors.
if (total == 0) {
if (self.bin_file.options.module) |module| {
total += @boolToInt(module.compile_log_decls.items().len != 0);
}
}
return total;
@ -1382,32 +1417,32 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
for (self.failed_c_objects.items()) |entry| {
const c_object = entry.key;
const err_msg = entry.value;
try AllErrors.add(&arena, &errors, c_object.src.src_path, "", err_msg.*);
// TODO these fields will need to be adjusted when we have proper
// C error reporting bubbling up.
try errors.append(.{
.src = .{
.src_path = try arena.allocator.dupe(u8, c_object.src.src_path),
.msg = try std.fmt.allocPrint(&arena.allocator, "unable to build C object: {s}", .{
err_msg.msg,
}),
.byte_offset = 0,
.line = err_msg.line,
.column = err_msg.column,
},
});
}
if (self.bin_file.options.module) |module| {
for (module.failed_files.items()) |entry| {
const scope = entry.key;
const err_msg = entry.value;
const source = try scope.getSource(module);
try AllErrors.add(&arena, &errors, scope.subFilePath(), source, err_msg.*);
try AllErrors.add(module, &arena, &errors, entry.value.*);
}
for (module.failed_decls.items()) |entry| {
const decl = entry.key;
const err_msg = entry.value;
const source = try decl.scope.getSource(module);
try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*);
try AllErrors.add(module, &arena, &errors, entry.value.*);
}
for (module.emit_h_failed_decls.items()) |entry| {
const decl = entry.key;
const err_msg = entry.value;
const source = try decl.scope.getSource(module);
try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*);
try AllErrors.add(module, &arena, &errors, entry.value.*);
}
for (module.failed_exports.items()) |entry| {
const decl = entry.key.owner_decl;
const err_msg = entry.value;
const source = try decl.scope.getSource(module);
try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*);
try AllErrors.add(module, &arena, &errors, entry.value.*);
}
if (module.failed_root_src_file) |err| {
const file_path = try module.root_pkg.root_src_directory.join(&arena.allocator, &[_][]const u8{
@ -1418,15 +1453,6 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
});
try AllErrors.addPlain(&arena, &errors, msg);
}
for (module.compile_log_decls.items()) |entry| {
const decl = entry.key;
const path = decl.scope.subFilePath();
const source = try decl.scope.getSource(module);
for (entry.value.items) |src_loc| {
const err_msg = ErrorMsg{ .byte_offset = src_loc, .msg = "found compile log statement" };
try AllErrors.add(&arena, &errors, path, source, err_msg);
}
}
}
if (errors.items.len == 0 and self.link_error_flags.no_entry_point_found) {
@ -1437,6 +1463,28 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
});
}
if (self.bin_file.options.module) |module| {
const compile_log_items = module.compile_log_decls.items();
if (errors.items.len == 0 and compile_log_items.len != 0) {
// First one will be the error; subsequent ones will be notes.
const err_msg = Module.ErrorMsg{
.src_loc = compile_log_items[0].value,
.msg = "found compile log statement",
.notes = try self.gpa.alloc(Module.ErrorMsg, compile_log_items.len - 1),
};
defer self.gpa.free(err_msg.notes);
for (compile_log_items[1..]) |entry, i| {
err_msg.notes[i] = .{
.src_loc = entry.value,
.msg = "also here",
};
}
try AllErrors.add(module, &arena, &errors, err_msg);
}
}
assert(errors.items.len == self.totalErrorCount());
return AllErrors{
@ -1445,6 +1493,11 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
};
}
pub fn getCompileLogOutput(self: *Compilation) []const u8 {
const module = self.bin_file.options.module orelse return &[0]u8{};
return module.compile_log_text.items;
}
pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemory }!void {
var progress: std.Progress = .{};
var main_progress_node = try progress.start("", 0);
@ -1517,9 +1570,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
},
else => {
try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create(
module.gpa,
decl.src(),
decl.srcLoc(),
"unable to codegen: {s}",
.{@errorName(err)},
));
@ -1586,9 +1639,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
const module = self.bin_file.options.module.?;
self.bin_file.updateDeclLineNumber(module, decl) catch |err| {
try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create(
module.gpa,
decl.src(),
decl.srcLoc(),
"unable to update line number: {s}",
.{@errorName(err)},
));
@ -1858,26 +1911,38 @@ fn workerUpdateCObject(
comp.updateCObject(c_object, progress_node) catch |err| switch (err) {
error.AnalysisFail => return,
else => {
{
const lock = comp.mutex.acquire();
defer lock.release();
comp.failed_c_objects.ensureCapacity(comp.gpa, comp.failed_c_objects.items().len + 1) catch {
fatal("TODO handle this by setting c_object.status = oom failure", .{});
};
comp.failed_c_objects.putAssumeCapacityNoClobber(c_object, ErrorMsg.create(
comp.gpa,
0,
"unable to build C object: {s}",
.{@errorName(err)},
) catch {
fatal("TODO handle this by setting c_object.status = oom failure", .{});
});
}
c_object.status = .{ .failure = {} };
comp.reportRetryableCObjectError(c_object, err) catch |oom| switch (oom) {
// Swallowing this error is OK because it's implied to be OOM when
// there is a missing failed_c_objects error message.
error.OutOfMemory => {},
};
},
};
}
fn reportRetryableCObjectError(
comp: *Compilation,
c_object: *CObject,
err: anyerror,
) error{OutOfMemory}!void {
c_object.status = .failure_retryable;
const c_obj_err_msg = try comp.gpa.create(CObject.ErrorMsg);
errdefer comp.gpa.destroy(c_obj_err_msg);
const msg = try std.fmt.allocPrint(comp.gpa, "unable to build C object: {s}", .{@errorName(err)});
errdefer comp.gpa.free(msg);
c_obj_err_msg.* = .{
.msg = msg,
.line = 0,
.column = 0,
};
{
const lock = comp.mutex.acquire();
defer lock.release();
try comp.failed_c_objects.putNoClobber(comp.gpa, c_object, c_obj_err_msg);
}
}
fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *std.Progress.Node) !void {
if (!build_options.have_llvm) {
return comp.failCObj(c_object, "clang not available: compiler built without LLVM extensions", .{});
@ -1892,7 +1957,9 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *
// There was previous failure.
const lock = comp.mutex.acquire();
defer lock.release();
comp.failed_c_objects.removeAssertDiscard(c_object);
// If the failure was OOM, there will not be an entry here, so we do
// not assert discard.
_ = comp.failed_c_objects.swapRemove(c_object);
}
var man = comp.obtainCObjectCacheManifest();
@ -2343,11 +2410,27 @@ pub fn addCCArgs(
fn failCObj(comp: *Compilation, c_object: *CObject, comptime format: []const u8, args: anytype) InnerError {
@setCold(true);
const err_msg = try ErrorMsg.create(comp.gpa, 0, "unable to build C object: " ++ format, args);
const err_msg = blk: {
const msg = try std.fmt.allocPrint(comp.gpa, format, args);
errdefer comp.gpa.free(msg);
const err_msg = try comp.gpa.create(CObject.ErrorMsg);
errdefer comp.gpa.destroy(err_msg);
err_msg.* = .{
.msg = msg,
.line = 0,
.column = 0,
};
break :blk err_msg;
};
return comp.failCObjWithOwnedErrorMsg(c_object, err_msg);
}
fn failCObjWithOwnedErrorMsg(comp: *Compilation, c_object: *CObject, err_msg: *ErrorMsg) InnerError {
fn failCObjWithOwnedErrorMsg(
comp: *Compilation,
c_object: *CObject,
err_msg: *CObject.ErrorMsg,
) InnerError {
@setCold(true);
{
const lock = comp.mutex.acquire();
defer lock.release();
@ -2361,36 +2444,6 @@ fn failCObjWithOwnedErrorMsg(comp: *Compilation, c_object: *CObject, err_msg: *E
return error.AnalysisFail;
}
pub const ErrorMsg = struct {
byte_offset: usize,
msg: []const u8,
pub fn create(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: anytype) !*ErrorMsg {
const self = try gpa.create(ErrorMsg);
errdefer gpa.destroy(self);
self.* = try init(gpa, byte_offset, format, args);
return self;
}
/// Assumes the ErrorMsg struct and msg were both allocated with allocator.
pub fn destroy(self: *ErrorMsg, gpa: *Allocator) void {
self.deinit(gpa);
gpa.destroy(self);
}
pub fn init(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: anytype) !ErrorMsg {
return ErrorMsg{
.byte_offset = byte_offset,
.msg = try std.fmt.allocPrint(gpa, format, args),
};
}
pub fn deinit(self: *ErrorMsg, gpa: *Allocator) void {
gpa.free(self.msg);
self.* = undefined;
}
};
pub const FileExt = enum {
c,
cpp,

File diff suppressed because it is too large Load Diff

View File

@ -318,7 +318,7 @@ pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *as
// Make a scope to collect generated instructions in the sub-expression.
var block_scope: Scope.GenZIR = .{
.parent = parent_scope,
.decl = parent_scope.decl().?,
.decl = parent_scope.ownerDecl().?,
.arena = parent_scope.arena(),
.instructions = .{},
};
@ -442,6 +442,49 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block
try blockExprStmts(mod, parent_scope, &block_node.base, block_node.statements());
}
fn checkLabelRedefinition(mod: *Module, parent_scope: *Scope, label: ast.TokenIndex) !void {
// Look for the label in the scope.
var scope = parent_scope;
while (true) {
switch (scope.tag) {
.gen_zir => {
const gen_zir = scope.cast(Scope.GenZIR).?;
if (gen_zir.label) |prev_label| {
if (try tokenIdentEql(mod, parent_scope, label, prev_label.token)) {
const tree = parent_scope.tree();
const label_src = tree.token_locs[label].start;
const prev_label_src = tree.token_locs[prev_label.token].start;
const label_name = try mod.identifierTokenString(parent_scope, label);
const msg = msg: {
const msg = try mod.errMsg(
parent_scope,
label_src,
"redefinition of label '{s}'",
.{label_name},
);
errdefer msg.destroy(mod.gpa);
try mod.errNote(
parent_scope,
prev_label_src,
msg,
"previous definition is here",
.{},
);
break :msg msg;
};
return mod.failWithOwnedErrorMsg(parent_scope, msg);
}
}
scope = gen_zir.parent;
},
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
else => return,
}
}
}
fn labeledBlockExpr(
mod: *Module,
parent_scope: *Scope,
@ -457,6 +500,8 @@ fn labeledBlockExpr(
const tree = parent_scope.tree();
const src = tree.token_locs[block_node.lbrace].start;
try checkLabelRedefinition(mod, parent_scope, block_node.label);
// Create the Block ZIR instruction so that we can put it into the GenZIR struct
// so that break statements can reference it.
const gen_zir = parent_scope.getGenZIR();
@ -474,7 +519,7 @@ fn labeledBlockExpr(
var block_scope: Scope.GenZIR = .{
.parent = parent_scope,
.decl = parent_scope.decl().?,
.decl = parent_scope.ownerDecl().?,
.arena = gen_zir.arena,
.instructions = .{},
.break_result_loc = rl,
@ -560,14 +605,30 @@ fn varDecl(
.local_val => {
const local_val = s.cast(Scope.LocalVal).?;
if (mem.eql(u8, local_val.name, ident_name)) {
return mod.fail(scope, name_src, "redefinition of '{s}'", .{ident_name});
const msg = msg: {
const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{
ident_name,
});
errdefer msg.destroy(mod.gpa);
try mod.errNote(scope, local_val.inst.src, msg, "previous definition is here", .{});
break :msg msg;
};
return mod.failWithOwnedErrorMsg(scope, msg);
}
s = local_val.parent;
},
.local_ptr => {
const local_ptr = s.cast(Scope.LocalPtr).?;
if (mem.eql(u8, local_ptr.name, ident_name)) {
return mod.fail(scope, name_src, "redefinition of '{s}'", .{ident_name});
const msg = msg: {
const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{
ident_name,
});
errdefer msg.destroy(mod.gpa);
try mod.errNote(scope, local_ptr.ptr.src, msg, "previous definition is here", .{});
break :msg msg;
};
return mod.failWithOwnedErrorMsg(scope, msg);
}
s = local_ptr.parent;
},
@ -899,7 +960,7 @@ fn containerDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Con
var gen_scope: Scope.GenZIR = .{
.parent = scope,
.decl = scope.decl().?,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
.instructions = .{},
};
@ -1028,7 +1089,13 @@ fn containerDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Con
.ty = Type.initTag(.type),
.val = val,
});
return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}));
if (rl == .ref) {
return addZIRInst(mod, scope, src, zir.Inst.DeclRef, .{ .decl = decl }, .{});
} else {
return rlWrap(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclVal, .{
.decl = decl,
}, .{}));
}
}
fn errorSetDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.ErrorSetDecl) InnerError!*zir.Inst {
@ -1084,7 +1151,7 @@ fn orelseCatchExpr(
var block_scope: Scope.GenZIR = .{
.parent = scope,
.decl = scope.decl().?,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
.instructions = .{},
};
@ -1160,8 +1227,10 @@ fn orelseCatchExpr(
return rlWrapPtr(mod, scope, rl, &block.base);
}
/// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating.
/// OK in theory it could do it without allocating. This implementation allocates when the @"" form is used.
/// Return whether the identifier names of two tokens are equal. Resolves @""
/// tokens without allocating.
/// OK in theory it could do it without allocating. This implementation
/// allocates when the @"" form is used.
fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: ast.TokenIndex) !bool {
const ident_name_1 = try mod.identifierTokenString(scope, token1);
const ident_name_2 = try mod.identifierTokenString(scope, token2);
@ -1266,7 +1335,7 @@ fn boolBinOp(
var block_scope: Scope.GenZIR = .{
.parent = scope,
.decl = scope.decl().?,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
.instructions = .{},
};
@ -1412,7 +1481,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
}
var block_scope: Scope.GenZIR = .{
.parent = scope,
.decl = scope.decl().?,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
.instructions = .{},
};
@ -1508,12 +1577,16 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
}
}
if (while_node.label) |label| {
try checkLabelRedefinition(mod, scope, label);
}
if (while_node.inline_token) |tok|
return mod.failTok(scope, tok, "TODO inline while", .{});
var expr_scope: Scope.GenZIR = .{
.parent = scope,
.decl = scope.decl().?,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
.instructions = .{},
};
@ -1643,13 +1716,22 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
return &while_block.base;
}
fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) InnerError!*zir.Inst {
fn forExpr(
mod: *Module,
scope: *Scope,
rl: ResultLoc,
for_node: *ast.Node.For,
) InnerError!*zir.Inst {
if (for_node.label) |label| {
try checkLabelRedefinition(mod, scope, label);
}
if (for_node.inline_token) |tok|
return mod.failTok(scope, tok, "TODO inline for", .{});
var for_scope: Scope.GenZIR = .{
.parent = scope,
.decl = scope.decl().?,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
.instructions = .{},
};
@ -1843,7 +1925,7 @@ fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp {
fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst {
var block_scope: Scope.GenZIR = .{
.parent = scope,
.decl = scope.decl().?,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
.instructions = .{},
};
@ -1885,7 +1967,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
var item_scope: Scope.GenZIR = .{
.parent = scope,
.decl = scope.decl().?,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
.instructions = .{},
};
@ -1922,8 +2004,18 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
// Check for else/_ prong, those are handled last.
if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) {
if (else_src) |src| {
return mod.fail(scope, case_src, "multiple else prongs in switch expression", .{});
// TODO notes "previous else prong is here"
const msg = msg: {
const msg = try mod.errMsg(
scope,
case_src,
"multiple else prongs in switch expression",
.{},
);
errdefer msg.destroy(mod.gpa);
try mod.errNote(scope, src, msg, "previous else prong is here", .{});
break :msg msg;
};
return mod.failWithOwnedErrorMsg(scope, msg);
}
else_src = case_src;
special_case = case;
@ -1932,8 +2024,18 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_"))
{
if (underscore_src) |src| {
return mod.fail(scope, case_src, "multiple '_' prongs in switch expression", .{});
// TODO notes "previous '_' prong is here"
const msg = msg: {
const msg = try mod.errMsg(
scope,
case_src,
"multiple '_' prongs in switch expression",
.{},
);
errdefer msg.destroy(mod.gpa);
try mod.errNote(scope, src, msg, "previous '_' prong is here", .{});
break :msg msg;
};
return mod.failWithOwnedErrorMsg(scope, msg);
}
underscore_src = case_src;
special_case = case;
@ -1942,9 +2044,19 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
if (else_src) |some_else| {
if (underscore_src) |some_underscore| {
return mod.fail(scope, switch_src, "else and '_' prong in switch expression", .{});
// TODO notes "else prong is here"
// TODO notes "'_' prong is here"
const msg = msg: {
const msg = try mod.errMsg(
scope,
switch_src,
"else and '_' prong in switch expression",
.{},
);
errdefer msg.destroy(mod.gpa);
try mod.errNote(scope, some_else, msg, "else prong is here", .{});
try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{});
break :msg msg;
};
return mod.failWithOwnedErrorMsg(scope, msg);
}
}
@ -2162,7 +2274,13 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo
}
if (mod.lookupDeclName(scope, ident_name)) |decl| {
return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}));
if (rl == .ref) {
return addZIRInst(mod, scope, src, zir.Inst.DeclRef, .{ .decl = decl }, .{});
} else {
return rlWrap(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclVal, .{
.decl = decl,
}, .{}));
}
}
return mod.failNode(scope, &ident.base, "use of undeclared identifier '{s}'", .{ident_name});
@ -2927,6 +3045,8 @@ fn rlWrapVoid(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node, resul
return rlWrap(mod, scope, rl, void_inst);
}
/// TODO go over all the callsites and see where we can introduce "by-value" ZIR instructions
/// to save ZIR memory. For example, see DeclVal vs DeclRef.
fn rlWrapPtr(mod: *Module, scope: *Scope, rl: ResultLoc, ptr: *zir.Inst) InnerError!*zir.Inst {
if (rl == .ref) return ptr;
@ -3032,7 +3152,7 @@ pub fn addZIRInstBlock(
scope: *Scope,
src: usize,
tag: zir.Inst.Tag,
body: zir.Module.Body,
body: zir.Body,
) !*zir.Inst.Block {
const gen_zir = scope.getGenZIR();
try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
@ -3070,7 +3190,7 @@ pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: Typ
}
/// TODO The existence of this function is a workaround for a bug in stage1.
pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Loop {
pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Body) !*zir.Inst.Loop {
const P = std.meta.fieldInfo(zir.Inst.Loop, .positionals).field_type;
return addZIRInstSpecial(mod, scope, src, zir.Inst.Loop, P{ .body = body }, .{});
}

View File

@ -9,7 +9,7 @@ const TypedValue = @import("TypedValue.zig");
const link = @import("link.zig");
const Module = @import("Module.zig");
const Compilation = @import("Compilation.zig");
const ErrorMsg = Compilation.ErrorMsg;
const ErrorMsg = Module.ErrorMsg;
const Target = std.Target;
const Allocator = mem.Allocator;
const trace = @import("tracy.zig").trace;
@ -74,7 +74,7 @@ pub const DebugInfoOutput = union(enum) {
pub fn generateSymbol(
bin_file: *link.File,
src: usize,
src_loc: Module.SrcLoc,
typed_value: TypedValue,
code: *std.ArrayList(u8),
debug_output: DebugInfoOutput,
@ -87,56 +87,56 @@ pub fn generateSymbol(
switch (bin_file.options.target.cpu.arch) {
.wasm32 => unreachable, // has its own code path
.wasm64 => unreachable, // has its own code path
.arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, debug_output),
.armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, debug_output),
.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, debug_output),
.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, debug_output),
.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.hexagon => return Function(.hexagon).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.mips => return Function(.mips).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.mipsel => return Function(.mipsel).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.mips64 => return Function(.mips64).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.mips64el => return Function(.mips64el).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.msp430 => return Function(.msp430).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.powerpc => return Function(.powerpc).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.r600 => return Function(.r600).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.riscv32 => return Function(.riscv32).generateSymbol(bin_file, src, typed_value, code, debug_output),
.riscv64 => return Function(.riscv64).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.sparc => return Function(.sparc).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, debug_output),
.spu_2 => return Function(.spu_2).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.i386 => return Function(.i386).generateSymbol(bin_file, src, typed_value, code, debug_output),
.x86_64 => return Function(.x86_64).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.xcore => return Function(.xcore).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.nvptx => return Function(.nvptx).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.le32 => return Function(.le32).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.le64 => return Function(.le64).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.amdil => return Function(.amdil).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.amdil64 => return Function(.amdil64).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.hsail => return Function(.hsail).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.hsail64 => return Function(.hsail64).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.spir => return Function(.spir).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.spir64 => return Function(.spir64).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.kalimba => return Function(.kalimba).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.shave => return Function(.shave).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.lanai => return Function(.lanai).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.ve => return Function(.ve).generateSymbol(bin_file, src, typed_value, code, debug_output),
.arm => return Function(.arm).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
.armeb => return Function(.armeb).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.arc => return Function(.arc).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.avr => return Function(.avr).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.bpfel => return Function(.bpfel).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.hexagon => return Function(.hexagon).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.mips => return Function(.mips).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.mipsel => return Function(.mipsel).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.mips64 => return Function(.mips64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.mips64el => return Function(.mips64el).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.msp430 => return Function(.msp430).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.powerpc => return Function(.powerpc).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.r600 => return Function(.r600).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.riscv32 => return Function(.riscv32).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
.riscv64 => return Function(.riscv64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.sparc => return Function(.sparc).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.sparcel => return Function(.sparcel).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.s390x => return Function(.s390x).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
.spu_2 => return Function(.spu_2).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.tce => return Function(.tce).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.tcele => return Function(.tcele).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.thumb => return Function(.thumb).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.i386 => return Function(.i386).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
.x86_64 => return Function(.x86_64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.xcore => return Function(.xcore).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.nvptx => return Function(.nvptx).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.le32 => return Function(.le32).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.le64 => return Function(.le64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.amdil => return Function(.amdil).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.amdil64 => return Function(.amdil64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.hsail => return Function(.hsail).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.hsail64 => return Function(.hsail64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.spir => return Function(.spir).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.spir64 => return Function(.spir64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.kalimba => return Function(.kalimba).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.shave => return Function(.shave).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.lanai => return Function(.lanai).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
//.ve => return Function(.ve).generateSymbol(bin_file, src_loc, typed_value, code, debug_output),
else => @panic("Backend architectures that don't have good support yet are commented out, to improve compilation performance. If you are interested in one of these other backends feel free to uncomment them. Eventually these will be completed, but stage1 is slow and a memory hog."),
}
},
@ -147,7 +147,7 @@ pub fn generateSymbol(
try code.ensureCapacity(code.items.len + payload.data.len + 1);
code.appendSliceAssumeCapacity(payload.data);
const prev_len = code.items.len;
switch (try generateSymbol(bin_file, src, .{
switch (try generateSymbol(bin_file, src_loc, .{
.ty = typed_value.ty.elemType(),
.val = sentinel,
}, code, debug_output)) {
@ -165,7 +165,7 @@ pub fn generateSymbol(
return Result{
.fail = try ErrorMsg.create(
bin_file.allocator,
src,
src_loc,
"TODO implement generateSymbol for more kinds of arrays",
.{},
),
@ -200,7 +200,7 @@ pub fn generateSymbol(
return Result{
.fail = try ErrorMsg.create(
bin_file.allocator,
src,
src_loc,
"TODO implement generateSymbol for pointer {}",
.{typed_value.val},
),
@ -217,7 +217,7 @@ pub fn generateSymbol(
return Result{
.fail = try ErrorMsg.create(
bin_file.allocator,
src,
src_loc,
"TODO implement generateSymbol for int type '{}'",
.{typed_value.ty},
),
@ -227,7 +227,7 @@ pub fn generateSymbol(
return Result{
.fail = try ErrorMsg.create(
bin_file.allocator,
src,
src_loc,
"TODO implement generateSymbol for type '{s}'",
.{@tagName(t)},
),
@ -259,7 +259,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
ret_mcv: MCValue,
fn_type: Type,
arg_index: usize,
src: usize,
src_loc: Module.SrcLoc,
stack_align: u32,
/// Byte offset within the source file.
@ -428,7 +428,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
fn generateSymbol(
bin_file: *link.File,
src: usize,
src_loc: Module.SrcLoc,
typed_value: TypedValue,
code: *std.ArrayList(u8),
debug_output: DebugInfoOutput,
@ -450,19 +450,17 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
try branch_stack.append(.{});
const src_data: struct { lbrace_src: usize, rbrace_src: usize, source: []const u8 } = blk: {
if (module_fn.owner_decl.scope.cast(Module.Scope.Container)) |container_scope| {
const tree = container_scope.file_scope.contents.tree;
const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?;
const block = fn_proto.getBodyNode().?.castTag(.Block).?;
const lbrace_src = tree.token_locs[block.lbrace].start;
const rbrace_src = tree.token_locs[block.rbrace].start;
break :blk .{ .lbrace_src = lbrace_src, .rbrace_src = rbrace_src, .source = tree.source };
} else if (module_fn.owner_decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| {
const byte_off = zir_module.contents.module.decls[module_fn.owner_decl.src_index].inst.src;
break :blk .{ .lbrace_src = byte_off, .rbrace_src = byte_off, .source = zir_module.source.bytes };
} else {
unreachable;
}
const container_scope = module_fn.owner_decl.container;
const tree = container_scope.file_scope.contents.tree;
const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?;
const block = fn_proto.getBodyNode().?.castTag(.Block).?;
const lbrace_src = tree.token_locs[block.lbrace].start;
const rbrace_src = tree.token_locs[block.rbrace].start;
break :blk .{
.lbrace_src = lbrace_src,
.rbrace_src = rbrace_src,
.source = tree.source,
};
};
var function = Self{
@ -478,7 +476,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.fn_type = fn_type,
.arg_index = 0,
.branch_stack = &branch_stack,
.src = src,
.src_loc = src_loc,
.stack_align = undefined,
.prev_di_pc = 0,
.prev_di_src = src_data.lbrace_src,
@ -489,7 +487,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
defer function.stack.deinit(bin_file.allocator);
defer function.exitlude_jump_relocs.deinit(bin_file.allocator);
var call_info = function.resolveCallingConventionValues(src, fn_type) catch |err| switch (err) {
var call_info = function.resolveCallingConventionValues(src_loc.byte_offset, fn_type) catch |err| switch (err) {
error.CodegenFail => return Result{ .fail = function.err_msg.? },
else => |e| return e,
};
@ -536,12 +534,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const stack_end = self.max_end_stack;
if (stack_end > math.maxInt(i32))
return self.fail(self.src, "too much stack used in call parameters", .{});
return self.failSymbol("too much stack used in call parameters", .{});
const aligned_stack_end = mem.alignForward(stack_end, self.stack_align);
mem.writeIntLittle(u32, self.code.items[reloc_index..][0..4], @intCast(u32, aligned_stack_end));
if (self.code.items.len >= math.maxInt(i32)) {
return self.fail(self.src, "unable to perform relocation: jump too far", .{});
return self.failSymbol("unable to perform relocation: jump too far", .{});
}
if (self.exitlude_jump_relocs.items.len == 1) {
self.code.items.len -= 5;
@ -598,7 +596,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
if (Instruction.Operand.fromU32(@intCast(u32, aligned_stack_end))) |op| {
writeInt(u32, self.code.items[backpatch_reloc..][0..4], Instruction.sub(.al, .sp, .sp, op).toU32());
} else {
return self.fail(self.src, "TODO ARM: allow larger stacks", .{});
return self.failSymbol("TODO ARM: allow larger stacks", .{});
}
try self.dbgSetEpilogueBegin();
@ -624,7 +622,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
if (math.cast(i26, amt)) |offset| {
writeInt(u32, self.code.items[jmp_reloc..][0..4], Instruction.b(.al, offset).toU32());
} else |err| {
return self.fail(self.src, "exitlude jump is too large", .{});
return self.failSymbol("exitlude jump is too large", .{});
}
}
}
@ -3678,7 +3676,17 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
fn fail(self: *Self, src: usize, comptime format: []const u8, args: anytype) InnerError {
@setCold(true);
assert(self.err_msg == null);
self.err_msg = try ErrorMsg.create(self.bin_file.allocator, src, format, args);
self.err_msg = try ErrorMsg.create(self.bin_file.allocator, .{
.file_scope = self.src_loc.file_scope,
.byte_offset = src,
}, format, args);
return error.CodegenFail;
}
fn failSymbol(self: *Self, comptime format: []const u8, args: anytype) InnerError {
@setCold(true);
assert(self.err_msg == null);
self.err_msg = try ErrorMsg.create(self.bin_file.allocator, self.src_loc, format, args);
return error.CodegenFail;
}

View File

@ -114,10 +114,13 @@ pub const DeclGen = struct {
module: *Module,
decl: *Decl,
fwd_decl: std.ArrayList(u8),
error_msg: ?*Compilation.ErrorMsg,
error_msg: ?*Module.ErrorMsg,
fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
dg.error_msg = try Compilation.ErrorMsg.create(dg.module.gpa, src, format, args);
dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{
.file_scope = dg.decl.getFileScope(),
.byte_offset = src,
}, format, args);
return error.AnalysisFail;
}

View File

@ -148,7 +148,7 @@ pub const LLVMIRModule = struct {
object_path: []const u8,
gpa: *Allocator,
err_msg: ?*Compilation.ErrorMsg = null,
err_msg: ?*Module.ErrorMsg = null,
// TODO: The fields below should really move into a different struct,
// because they are only valid when generating a function
@ -177,6 +177,8 @@ pub const LLVMIRModule = struct {
break_vals: *BreakValues,
}) = .{},
src_loc: Module.SrcLoc,
const BreakBasicBlocks = std.ArrayListUnmanaged(*const llvm.BasicBlock);
const BreakValues = std.ArrayListUnmanaged(*const llvm.Value);
@ -254,6 +256,8 @@ pub const LLVMIRModule = struct {
.builder = builder,
.object_path = object_path,
.gpa = gpa,
// TODO move this field into a struct that is only instantiated per gen() call
.src_loc = undefined,
};
return self;
}
@ -335,6 +339,8 @@ pub const LLVMIRModule = struct {
const typed_value = decl.typed_value.most_recent.typed_value;
const src = decl.src();
self.src_loc = decl.srcLoc();
log.debug("gen: {s} type: {}, value: {}", .{ decl.name, typed_value.ty, typed_value.val });
if (typed_value.val.castTag(.function)) |func_payload| {
@ -853,7 +859,10 @@ pub const LLVMIRModule = struct {
pub fn fail(self: *LLVMIRModule, src: usize, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } {
@setCold(true);
assert(self.err_msg == null);
self.err_msg = try Compilation.ErrorMsg.create(self.gpa, src, format, args);
self.err_msg = try Module.ErrorMsg.create(self.gpa, .{
.file_scope = self.src_loc.file_scope,
.byte_offset = src,
}, format, args);
return error.CodegenFail;
}
};

View File

@ -670,7 +670,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void {
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
defer code_buffer.deinit();
const res = try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .none);
const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .none);
const code = switch (res) {
.externally_managed => |x| x,
.appended => code_buffer.items,
@ -732,7 +732,7 @@ pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl,
try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1);
module.failed_exports.putAssumeCapacityNoClobber(
exp,
try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: ExportOptions.section", .{}),
try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}),
);
continue;
}
@ -743,7 +743,7 @@ pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl,
try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1);
module.failed_exports.putAssumeCapacityNoClobber(
exp,
try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: Exports other than '_start'", .{}),
try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than '_start'", .{}),
);
continue;
}

View File

@ -2189,22 +2189,14 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
try dbg_line_buffer.ensureCapacity(26);
const line_off: u28 = blk: {
if (decl.scope.cast(Module.Scope.Container)) |container_scope| {
const tree = container_scope.file_scope.contents.tree;
const file_ast_decls = tree.root_node.decls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
const block = fn_proto.getBodyNode().?.castTag(.Block).?;
const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
break :blk @intCast(u28, line_delta);
} else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| {
const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src;
const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off);
break :blk @intCast(u28, line_delta);
} else {
unreachable;
}
const tree = decl.container.file_scope.contents.tree;
const file_ast_decls = tree.root_node.decls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
const block = fn_proto.getBodyNode().?.castTag(.Block).?;
const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
break :blk @intCast(u28, line_delta);
};
const ptr_width_bytes = self.ptrWidthBytes();
@ -2268,7 +2260,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
} else {
// TODO implement .debug_info for global variables
}
const res = try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .{
const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .{
.dwarf = .{
.dbg_line = &dbg_line_buffer,
.dbg_info = &dbg_info_buffer,
@ -2642,7 +2634,7 @@ pub fn updateDeclExports(
try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1);
module.failed_exports.putAssumeCapacityNoClobber(
exp,
try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: ExportOptions.section", .{}),
try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}),
);
continue;
}
@ -2660,7 +2652,7 @@ pub fn updateDeclExports(
try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1);
module.failed_exports.putAssumeCapacityNoClobber(
exp,
try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}),
try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: GlobalLinkage.LinkOnce", .{}),
);
continue;
},
@ -2703,8 +2695,7 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec
if (self.llvm_ir_module) |_| return;
const container_scope = decl.scope.cast(Module.Scope.Container).?;
const tree = container_scope.file_scope.contents.tree;
const tree = decl.container.file_scope.contents.tree;
const file_ast_decls = tree.root_node.decls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.

View File

@ -1148,7 +1148,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
}
const res = if (debug_buffers) |*dbg|
try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .{
try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .{
.dwarf = .{
.dbg_line = &dbg.dbg_line_buffer,
.dbg_info = &dbg.dbg_info_buffer,
@ -1156,7 +1156,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
},
})
else
try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .none);
try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .none);
const code = switch (res) {
.externally_managed => |x| x,
@ -1316,7 +1316,7 @@ pub fn updateDeclExports(
try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1);
module.failed_exports.putAssumeCapacityNoClobber(
exp,
try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: ExportOptions.section", .{}),
try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}),
);
continue;
}
@ -1334,7 +1334,7 @@ pub fn updateDeclExports(
try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1);
module.failed_exports.putAssumeCapacityNoClobber(
exp,
try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}),
try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: GlobalLinkage.LinkOnce", .{}),
);
continue;
},

View File

@ -906,8 +906,7 @@ pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const M
const tracy = trace(@src());
defer tracy.end();
const container_scope = decl.scope.cast(Module.Scope.Container).?;
const tree = container_scope.file_scope.contents.tree;
const tree = decl.container.file_scope.contents.tree;
const file_ast_decls = tree.root_node.decls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
@ -951,22 +950,14 @@ pub fn initDeclDebugBuffers(
try dbg_line_buffer.ensureCapacity(26);
const line_off: u28 = blk: {
if (decl.scope.cast(Module.Scope.Container)) |container_scope| {
const tree = container_scope.file_scope.contents.tree;
const file_ast_decls = tree.root_node.decls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
const block = fn_proto.getBodyNode().?.castTag(.Block).?;
const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
break :blk @intCast(u28, line_delta);
} else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| {
const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src;
const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off);
break :blk @intCast(u28, line_delta);
} else {
unreachable;
}
const tree = decl.container.file_scope.contents.tree;
const file_ast_decls = tree.root_node.decls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
const block = fn_proto.getBodyNode().?.castTag(.Block).?;
const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
break :blk @intCast(u28, line_delta);
};
dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{

View File

@ -221,7 +221,6 @@ const usage_build_generic =
\\
\\Supported file types:
\\ .zig Zig source code
\\ .zir Zig Intermediate Representation code
\\ .o ELF object file
\\ .o MACH-O (macOS) object file
\\ .obj COFF (Windows) object file
@ -245,8 +244,6 @@ const usage_build_generic =
\\ -fno-emit-bin Do not output machine code
\\ -femit-asm[=path] Output .s (assembly code)
\\ -fno-emit-asm (default) Do not output .s (assembly code)
\\ -femit-zir[=path] Produce a .zir file with Zig IR
\\ -fno-emit-zir (default) Do not produce a .zir file with Zig IR
\\ -femit-llvm-ir[=path] Produce a .ll file with LLVM IR (requires LLVM extensions)
\\ -fno-emit-llvm-ir (default) Do not produce a .ll file with LLVM IR
\\ -femit-h[=path] Generate a C header file (.h)
@ -1631,18 +1628,12 @@ fn buildOutputType(
var emit_docs_resolved = try emit_docs.resolve("docs");
defer emit_docs_resolved.deinit();
const zir_out_path: ?[]const u8 = switch (emit_zir) {
.no => null,
.yes_default_path => blk: {
if (root_src_file) |rsf| {
if (mem.endsWith(u8, rsf, ".zir")) {
break :blk try std.fmt.allocPrint(arena, "{s}.out.zir", .{root_name});
}
}
break :blk try std.fmt.allocPrint(arena, "{s}.zir", .{root_name});
switch (emit_zir) {
.no => {},
.yes_default_path, .yes => {
fatal("The -femit-zir implementation has been intentionally deleted so that it can be rewritten as a proper backend.", .{});
},
.yes => |p| p,
};
}
const root_pkg: ?*Package = if (root_src_file) |src_path| blk: {
if (main_pkg_path) |p| {
@ -1753,7 +1744,7 @@ fn buildOutputType(
.dll_export_fns = dll_export_fns,
.object_format = object_format,
.optimize_mode = optimize_mode,
.keep_source_files_loaded = zir_out_path != null,
.keep_source_files_loaded = false,
.clang_argv = clang_argv.items,
.lld_argv = lld_argv.items,
.lib_dirs = lib_dirs.items,
@ -1845,7 +1836,7 @@ fn buildOutputType(
}
};
updateModule(gpa, comp, zir_out_path, hook) catch |err| switch (err) {
updateModule(gpa, comp, hook) catch |err| switch (err) {
error.SemanticAnalyzeFail => if (!watch) process.exit(1),
else => |e| return e,
};
@ -1980,7 +1971,7 @@ fn buildOutputType(
if (output_mode == .Exe) {
try comp.makeBinFileWritable();
}
updateModule(gpa, comp, zir_out_path, hook) catch |err| switch (err) {
updateModule(gpa, comp, hook) catch |err| switch (err) {
error.SemanticAnalyzeFail => continue,
else => |e| return e,
};
@ -2003,7 +1994,7 @@ const AfterUpdateHook = union(enum) {
update: []const u8,
};
fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8, hook: AfterUpdateHook) !void {
fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !void {
try comp.update();
var errors = try comp.getAllErrorsAlloc();
@ -2013,6 +2004,10 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8,
for (errors.list) |full_err_msg| {
full_err_msg.renderToStdErr();
}
const log_text = comp.getCompileLogOutput();
if (log_text.len != 0) {
std.debug.print("\nCompile Log Output:\n{s}", .{log_text});
}
return error.SemanticAnalyzeFail;
} else switch (hook) {
.none => {},
@ -2024,20 +2019,6 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8,
.{},
),
}
if (zir_out_path) |zop| {
const module = comp.bin_file.options.module orelse
fatal("-femit-zir with no zig source code", .{});
var new_zir_module = try zir.emit(gpa, module);
defer new_zir_module.deinit(gpa);
const baf = try io.BufferedAtomicFile.create(gpa, fs.cwd(), zop, .{});
defer baf.destroy();
try new_zir_module.writeToStream(gpa, baf.writer());
try baf.finish();
}
}
fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !void {
@ -2506,7 +2487,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
};
defer comp.destroy();
try updateModule(gpa, comp, null, .none);
try updateModule(gpa, comp, .none);
try comp.makeBinFileExecutable();
child_argv.items[argv_index_exe] = try comp.bin_file.options.emit.?.directory.join(

View File

@ -15,6 +15,8 @@ const CrossTarget = std.zig.CrossTarget;
const zig_h = link.File.C.zig_h;
const hr = "=" ** 40;
test "self-hosted" {
var ctx = TestContext.init();
defer ctx.deinit();
@ -29,23 +31,32 @@ const ErrorMsg = union(enum) {
msg: []const u8,
line: u32,
column: u32,
kind: Kind,
},
plain: struct {
msg: []const u8,
kind: Kind,
},
fn init(other: Compilation.AllErrors.Message) ErrorMsg {
const Kind = enum {
@"error",
note,
};
fn init(other: Compilation.AllErrors.Message, kind: Kind) ErrorMsg {
switch (other) {
.src => |src| return .{
.src = .{
.msg = src.msg,
.line = @intCast(u32, src.line),
.column = @intCast(u32, src.column),
.kind = kind,
},
},
.plain => |plain| return .{
.plain = .{
.msg = plain.msg,
.kind = kind,
},
},
}
@ -59,14 +70,15 @@ const ErrorMsg = union(enum) {
) !void {
switch (self) {
.src => |src| {
return writer.print(":{d}:{d}: error: {s}", .{
return writer.print(":{d}:{d}: {s}: {s}", .{
src.line + 1,
src.column + 1,
@tagName(src.kind),
src.msg,
});
},
.plain => |plain| {
return writer.print("error: {s}", .{plain.msg});
return writer.print("{s}: {s}", .{ plain.msg, @tagName(plain.kind) });
},
}
}
@ -86,9 +98,6 @@ pub const TestContext = struct {
/// effects of the incremental compilation.
src: [:0]const u8,
case: union(enum) {
/// A transformation update transforms the input and tests against
/// the expected output ZIR.
Transformation: [:0]const u8,
/// Check the main binary output file against an expected set of bytes.
/// This is most useful with, for example, `-ofmt=c`.
CompareObjectFile: []const u8,
@ -139,15 +148,6 @@ pub const TestContext = struct {
files: std.ArrayList(File),
/// Adds a subcase in which the module is updated with `src`, and the
/// resulting ZIR is validated against `result`.
pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) void {
self.updates.append(.{
.src = src,
.case = .{ .Transformation = result },
}) catch unreachable;
}
/// Adds a subcase in which the module is updated with `src`, and a C
/// header is generated.
pub fn addHeader(self: *Case, src: [:0]const u8, result: [:0]const u8) void {
@ -182,31 +182,37 @@ pub const TestContext = struct {
/// the form `:line:column: error: message`.
pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void {
var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable;
for (errors) |e, i| {
if (e[0] != ':') {
array[i] = .{ .plain = .{ .msg = e } };
for (errors) |err_msg_line, i| {
if (std.mem.startsWith(u8, err_msg_line, "error: ")) {
array[i] = .{
.plain = .{ .msg = err_msg_line["error: ".len..], .kind = .@"error" },
};
continue;
} else if (std.mem.startsWith(u8, err_msg_line, "note: ")) {
array[i] = .{
.plain = .{ .msg = err_msg_line["note: ".len..], .kind = .note },
};
continue;
}
var cur = e[1..];
var line_index = std.mem.indexOf(u8, cur, ":");
if (line_index == null) {
@panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n");
}
const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number");
cur = cur[line_index.? + 1 ..];
const column_index = std.mem.indexOf(u8, cur, ":");
if (column_index == null) {
@panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n");
}
const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number");
cur = cur[column_index.? + 2 ..];
if (!std.mem.eql(u8, cur[0..7], "error: ")) {
@panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n");
}
const msg = cur[7..];
// example: ":1:2: error: bad thing happened"
var it = std.mem.split(err_msg_line, ":");
_ = it.next() orelse @panic("missing colon");
const line_text = it.next() orelse @panic("missing line");
const col_text = it.next() orelse @panic("missing column");
const kind_text = it.next() orelse @panic("missing 'error'/'note'");
const msg = it.rest()[1..]; // skip over the space at end of "error: "
const line = std.fmt.parseInt(u32, line_text, 10) catch @panic("bad line number");
const column = std.fmt.parseInt(u32, col_text, 10) catch @panic("bad column number");
const kind: ErrorMsg.Kind = if (std.mem.eql(u8, kind_text, " error"))
.@"error"
else if (std.mem.eql(u8, kind_text, " note"))
.note
else
@panic("expected 'error'/'note'");
if (line == 0 or column == 0) {
@panic("Invalid test: error line and column must be specified starting at one!");
@panic("line and column must be specified starting at one");
}
array[i] = .{
@ -214,6 +220,7 @@ pub const TestContext = struct {
.msg = msg,
.line = line - 1,
.column = column - 1,
.kind = kind,
},
};
}
@ -689,25 +696,20 @@ pub const TestContext = struct {
var all_errors = try comp.getAllErrorsAlloc();
defer all_errors.deinit(allocator);
if (all_errors.list.len != 0) {
std.debug.print("\nErrors occurred updating the compilation:\n================\n", .{});
std.debug.print("\nErrors occurred updating the compilation:\n{s}\n", .{hr});
for (all_errors.list) |err_msg| {
switch (err_msg) {
.src => |src| {
std.debug.print(":{d}:{d}: error: {s}\n================\n", .{
src.line + 1, src.column + 1, src.msg,
std.debug.print(":{d}:{d}: error: {s}\n{s}\n", .{
src.line + 1, src.column + 1, src.msg, hr,
});
},
.plain => |plain| {
std.debug.print("error: {s}\n================\n", .{plain.msg});
std.debug.print("error: {s}\n{s}\n", .{ plain.msg, hr });
},
}
}
// TODO print generated C code
//if (comp.bin_file.cast(link.File.C)) |c_file| {
// std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{
// c_file.main.items,
// });
//}
std.debug.print("Test failed.\n", .{});
std.process.exit(1);
}
@ -728,48 +730,36 @@ pub const TestContext = struct {
std.testing.expectEqualStrings(expected_output, out);
},
.Transformation => |expected_output| {
update_node.setEstimatedTotalItems(5);
var emit_node = update_node.start("emit", 0);
emit_node.activate();
var new_zir_module = try zir.emit(allocator, comp.bin_file.options.module.?);
defer new_zir_module.deinit(allocator);
emit_node.end();
var write_node = update_node.start("write", 0);
write_node.activate();
var out_zir = std.ArrayList(u8).init(allocator);
defer out_zir.deinit();
try new_zir_module.writeToStream(allocator, out_zir.writer());
write_node.end();
.Error => |case_error_list| {
var test_node = update_node.start("assert", 0);
test_node.activate();
defer test_node.end();
std.testing.expectEqualStrings(expected_output, out_zir.items);
},
.Error => |e| {
var test_node = update_node.start("assert", 0);
test_node.activate();
defer test_node.end();
var handled_errors = try arena.alloc(bool, e.len);
for (handled_errors) |*handled| {
handled.* = false;
}
var all_errors = try comp.getAllErrorsAlloc();
defer all_errors.deinit(allocator);
for (all_errors.list) |a| {
for (e) |ex, i| {
const a_tag: @TagType(@TypeOf(a)) = a;
const ex_tag: @TagType(@TypeOf(ex)) = ex;
switch (a) {
.src => |src| {
const handled_errors = try arena.alloc(bool, case_error_list.len);
std.mem.set(bool, handled_errors, false);
var actual_errors = try comp.getAllErrorsAlloc();
defer actual_errors.deinit(allocator);
var any_failed = false;
var notes_to_check = std.ArrayList(*const Compilation.AllErrors.Message).init(allocator);
defer notes_to_check.deinit();
for (actual_errors.list) |actual_error| {
for (case_error_list) |case_msg, i| {
const ex_tag: @TagType(@TypeOf(case_msg)) = case_msg;
switch (actual_error) {
.src => |actual_msg| {
for (actual_msg.notes) |*note| {
try notes_to_check.append(note);
}
if (ex_tag != .src) continue;
if (src.line == ex.src.line and
src.column == ex.src.column and
std.mem.eql(u8, ex.src.msg, src.msg))
if (actual_msg.line == case_msg.src.line and
actual_msg.column == case_msg.src.column and
std.mem.eql(u8, case_msg.src.msg, actual_msg.msg) and
case_msg.src.kind == .@"error")
{
handled_errors[i] = true;
break;
@ -778,7 +768,9 @@ pub const TestContext = struct {
.plain => |plain| {
if (ex_tag != .plain) continue;
if (std.mem.eql(u8, ex.plain.msg, plain.msg)) {
if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and
case_msg.plain.kind == .@"error")
{
handled_errors[i] = true;
break;
}
@ -786,23 +778,67 @@ pub const TestContext = struct {
}
} else {
std.debug.print(
"{s}\nUnexpected error:\n================\n{}\n================\nTest failed.\n",
.{ case.name, ErrorMsg.init(a) },
"\nUnexpected error:\n{s}\n{}\n{s}",
.{ hr, ErrorMsg.init(actual_error, .@"error"), hr },
);
std.process.exit(1);
any_failed = true;
}
}
while (notes_to_check.popOrNull()) |note| {
for (case_error_list) |case_msg, i| {
const ex_tag: @TagType(@TypeOf(case_msg)) = case_msg;
switch (note.*) {
.src => |actual_msg| {
for (actual_msg.notes) |*sub_note| {
try notes_to_check.append(sub_note);
}
if (ex_tag != .src) continue;
if (actual_msg.line == case_msg.src.line and
actual_msg.column == case_msg.src.column and
std.mem.eql(u8, case_msg.src.msg, actual_msg.msg) and
case_msg.src.kind == .note)
{
handled_errors[i] = true;
break;
}
},
.plain => |plain| {
if (ex_tag != .plain) continue;
if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and
case_msg.plain.kind == .note)
{
handled_errors[i] = true;
break;
}
},
}
} else {
std.debug.print(
"\nUnexpected note:\n{s}\n{}\n{s}",
.{ hr, ErrorMsg.init(note.*, .note), hr },
);
any_failed = true;
}
}
for (handled_errors) |handled, i| {
if (!handled) {
const er = e[i];
std.debug.print(
"{s}\nDid not receive error:\n================\n{}\n================\nTest failed.\n",
.{ case.name, er },
"\nExpected error not found:\n{s}\n{}\n{s}",
.{ hr, case_error_list[i], hr },
);
std.process.exit(1);
any_failed = true;
}
}
if (any_failed) {
std.debug.print("\nTest case '{s}' failed, update_index={d}.\n", .{
case.name, update_index,
});
std.process.exit(1);
}
},
.Execution => |expected_stdout| {
update_node.setEstimatedTotalItems(4);

View File

@ -21,7 +21,7 @@ pub const Field = struct {
};
pub const Zir = struct {
body: zir.Module.Body,
body: zir.Body,
inst: *zir.Inst,
};

View File

@ -24,7 +24,7 @@ pub const Field = struct {
};
pub const Zir = struct {
body: zir.Module.Body,
body: zir.Body,
inst: *zir.Inst,
};

View File

@ -24,7 +24,7 @@ pub const Field = struct {
};
pub const Zir = struct {
body: zir.Module.Body,
body: zir.Body,
inst: *zir.Inst,
};

File diff suppressed because it is too large Load Diff

View File

@ -63,7 +63,6 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
.declref => return analyzeInstDeclRef(mod, scope, old_inst.castTag(.declref).?),
.declref_str => return analyzeInstDeclRefStr(mod, scope, old_inst.castTag(.declref_str).?),
.declval => return analyzeInstDeclVal(mod, scope, old_inst.castTag(.declval).?),
.declval_in_module => return analyzeInstDeclValInModule(mod, scope, old_inst.castTag(.declval_in_module).?),
.ensure_result_used => return analyzeInstEnsureResultUsed(mod, scope, old_inst.castTag(.ensure_result_used).?),
.ensure_result_non_error => return analyzeInstEnsureResultNonError(mod, scope, old_inst.castTag(.ensure_result_non_error).?),
.ensure_indexable => return analyzeInstEnsureIndexable(mod, scope, old_inst.castTag(.ensure_indexable).?),
@ -166,7 +165,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
}
}
pub fn analyzeBody(mod: *Module, block: *Scope.Block, body: zir.Module.Body) !void {
pub fn analyzeBody(mod: *Module, block: *Scope.Block, body: zir.Body) !void {
const tracy = trace(@src());
defer tracy.end();
@ -183,7 +182,7 @@ pub fn analyzeBodyValueAsType(
mod: *Module,
block_scope: *Scope.Block,
zir_result_inst: *zir.Inst,
body: zir.Module.Body,
body: zir.Body,
) !Type {
try analyzeBody(mod, block_scope, body);
const result_inst = block_scope.inst_table.get(zir_result_inst).?;
@ -191,84 +190,6 @@ pub fn analyzeBodyValueAsType(
return val.toType(block_scope.base.arena());
}
pub fn analyzeZirDecl(mod: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool {
var decl_scope: Scope.DeclAnalysis = .{
.decl = decl,
.arena = std.heap.ArenaAllocator.init(mod.gpa),
};
errdefer decl_scope.arena.deinit();
decl.analysis = .in_progress;
const typed_value = try analyzeConstInst(mod, &decl_scope.base, src_decl.inst);
const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State);
var prev_type_has_bits = false;
var type_changed = true;
if (decl.typedValueManaged()) |tvm| {
prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits();
type_changed = !tvm.typed_value.ty.eql(typed_value.ty);
tvm.deinit(mod.gpa);
}
arena_state.* = decl_scope.arena.state;
decl.typed_value = .{
.most_recent = .{
.typed_value = typed_value,
.arena = arena_state,
},
};
decl.analysis = .complete;
decl.generation = mod.generation;
if (typed_value.ty.hasCodeGenBits()) {
// We don't fully codegen the decl until later, but we do need to reserve a global
// offset table index for it. This allows us to codegen decls out of dependency order,
// increasing how many computations can be done in parallel.
try mod.comp.bin_file.allocateDeclIndexes(decl);
try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl });
} else if (prev_type_has_bits) {
mod.comp.bin_file.freeDecl(decl);
}
return type_changed;
}
pub fn resolveZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl {
const zir_module = mod.root_scope.cast(Scope.ZIRModule).?;
const entry = zir_module.contents.module.findDecl(src_decl.name).?;
return resolveZirDeclHavingIndex(mod, scope, src_decl, entry.index);
}
fn resolveZirDeclHavingIndex(mod: *Module, scope: *Scope, src_decl: *zir.Decl, src_index: usize) InnerError!*Decl {
const name_hash = scope.namespace().fullyQualifiedNameHash(src_decl.name);
const decl = mod.decl_table.get(name_hash).?;
decl.src_index = src_index;
try mod.ensureDeclAnalyzed(decl);
return decl;
}
/// Declares a dependency on the decl.
fn resolveCompleteZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl {
const decl = try resolveZirDecl(mod, scope, src_decl);
switch (decl.analysis) {
.unreferenced => unreachable,
.in_progress => unreachable,
.outdated => unreachable,
.dependency_failure,
.sema_failure,
.sema_failure_retryable,
.codegen_failure,
.codegen_failure_retryable,
=> return error.AnalysisFail,
.complete => {},
}
return decl;
}
pub fn resolveInst(mod: *Module, scope: *Scope, zir_inst: *zir.Inst) InnerError!*Inst {
const block = scope.cast(Scope.Block).?;
return block.inst_table.get(zir_inst).?; // Instruction does not dominate all uses!
@ -640,22 +561,28 @@ fn analyzeInstCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) In
}
fn analyzeInstCompileLog(mod: *Module, scope: *Scope, inst: *zir.Inst.CompileLog) InnerError!*Inst {
std.debug.print("| ", .{});
for (inst.positionals.to_log) |item, i| {
const to_log = try resolveInst(mod, scope, item);
if (to_log.value()) |val| {
std.debug.print("{}", .{val});
} else {
std.debug.print("(runtime value)", .{});
}
if (i != inst.positionals.to_log.len - 1) std.debug.print(", ", .{});
}
std.debug.print("\n", .{});
if (!inst.kw_args.seen) {
var managed = mod.compile_log_text.toManaged(mod.gpa);
defer mod.compile_log_text = managed.moveToUnmanaged();
const writer = managed.writer();
// so that we do not give multiple compile errors if it gets evaled twice
inst.kw_args.seen = true;
try mod.failCompileLog(scope, inst.base.src);
for (inst.positionals.to_log) |arg_inst, i| {
if (i != 0) try writer.print(", ", .{});
const arg = try resolveInst(mod, scope, arg_inst);
if (arg.value()) |val| {
try writer.print("@as({}, {})", .{ arg.ty, val });
} else {
try writer.print("@as({}, [runtime value])", .{arg.ty});
}
}
try writer.print("\n", .{});
const gop = try mod.compile_log_decls.getOrPut(mod.gpa, scope.ownerDecl().?);
if (!gop.found_existing) {
gop.entry.value = .{
.file_scope = scope.getFileScope(),
.byte_offset = inst.base.src,
};
}
return mod.constVoid(scope, inst.base.src);
}
@ -705,7 +632,8 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError
.parent = parent_block,
.inst_table = parent_block.inst_table,
.func = parent_block.func,
.decl = parent_block.decl,
.owner_decl = parent_block.owner_decl,
.src_decl = parent_block.src_decl,
.instructions = .{},
.arena = parent_block.arena,
.inlining = parent_block.inlining,
@ -732,7 +660,8 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c
.parent = parent_block,
.inst_table = parent_block.inst_table,
.func = parent_block.func,
.decl = parent_block.decl,
.owner_decl = parent_block.owner_decl,
.src_decl = parent_block.src_decl,
.instructions = .{},
.arena = parent_block.arena,
.label = null,
@ -744,13 +673,14 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c
try analyzeBody(mod, &child_block, inst.positionals.body);
try parent_block.instructions.appendSlice(mod.gpa, child_block.instructions.items);
// Move the analyzed instructions into the parent block arena.
const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items);
try parent_block.instructions.appendSlice(mod.gpa, copied_instructions);
// comptime blocks won't generate any runtime values
if (child_block.instructions.items.len == 0)
return mod.constVoid(scope, inst.base.src);
return parent_block.instructions.items[parent_block.instructions.items.len - 1];
// The result of a flat block is the last instruction.
const zir_inst_list = inst.positionals.body.instructions;
const last_zir_inst = zir_inst_list[zir_inst_list.len - 1];
return resolveInst(mod, scope, last_zir_inst);
}
fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime: bool) InnerError!*Inst {
@ -775,7 +705,8 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_compt
.parent = parent_block,
.inst_table = parent_block.inst_table,
.func = parent_block.func,
.decl = parent_block.decl,
.owner_decl = parent_block.owner_decl,
.src_decl = parent_block.src_decl,
.instructions = .{},
.arena = parent_block.arena,
// TODO @as here is working around a stage1 miscompilation bug :(
@ -890,22 +821,15 @@ fn analyzeInstDeclRefStr(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr
fn analyzeInstDeclRef(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
return mod.analyzeDeclRefByName(scope, inst.base.src, inst.positionals.name);
return mod.analyzeDeclRef(scope, inst.base.src, inst.positionals.decl);
}
fn analyzeInstDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
const decl = try analyzeDeclVal(mod, scope, inst);
const ptr = try mod.analyzeDeclRef(scope, inst.base.src, decl);
return mod.analyzeDeref(scope, inst.base.src, ptr, inst.base.src);
}
fn analyzeInstDeclValInModule(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclValInModule) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
const decl = inst.positionals.decl;
return mod.analyzeDeclRef(scope, inst.base.src, decl);
const decl_ref = try mod.analyzeDeclRef(scope, inst.base.src, inst.positionals.decl);
// TODO look into avoiding the call to analyzeDeref here
return mod.analyzeDeref(scope, inst.base.src, decl_ref, inst.base.src);
}
fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst {
@ -1032,9 +956,8 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError
.parent = null,
.inst_table = &inst_table,
.func = module_fn,
// Note that we pass the caller's Decl, not the callee. This causes
// compile errors to be attached (correctly) to the caller's Decl.
.decl = scope.decl().?,
.owner_decl = scope.ownerDecl().?,
.src_decl = module_fn.owner_decl,
.instructions = .{},
.arena = scope.arena(),
.label = null,
@ -1069,7 +992,7 @@ fn analyzeInstFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!
.state = if (fn_inst.kw_args.is_inline) .inline_only else .queued,
.zir = fn_inst.positionals.body,
.body = undefined,
.owner_decl = scope.decl().?,
.owner_decl = scope.ownerDecl().?,
};
return mod.constInst(scope, fn_inst.base.src, .{
.ty = fn_type,
@ -1391,7 +1314,7 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr
return mod.analyzeDeclRef(scope, fieldptr.base.src, decl);
}
if (&container_scope.file_scope.base == mod.root_scope) {
if (container_scope.file_scope == mod.root_scope) {
return mod.fail(scope, fieldptr.base.src, "root source file has no member called '{s}'", .{field_name});
} else {
return mod.fail(scope, fieldptr.base.src, "container '{}' has no member called '{s}'", .{ child_type, field_name });
@ -1606,7 +1529,8 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In
.parent = parent_block,
.inst_table = parent_block.inst_table,
.func = parent_block.func,
.decl = parent_block.decl,
.owner_decl = parent_block.owner_decl,
.src_decl = parent_block.src_decl,
.instructions = .{},
.arena = parent_block.arena,
.inlining = parent_block.inlining,
@ -2182,7 +2106,8 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE
.parent = parent_block,
.inst_table = parent_block.inst_table,
.func = parent_block.func,
.decl = parent_block.decl,
.owner_decl = parent_block.owner_decl,
.src_decl = parent_block.src_decl,
.instructions = .{},
.arena = parent_block.arena,
.inlining = parent_block.inlining,
@ -2196,7 +2121,8 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE
.parent = parent_block,
.inst_table = parent_block.inst_table,
.func = parent_block.func,
.decl = parent_block.decl,
.owner_decl = parent_block.owner_decl,
.src_decl = parent_block.src_decl,
.instructions = .{},
.arena = parent_block.arena,
.inlining = parent_block.inlining,
@ -2294,17 +2220,6 @@ fn analyzeBreak(
} else unreachable;
}
fn analyzeDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Decl {
const decl_name = inst.positionals.name;
const zir_module = scope.namespace().cast(Scope.ZIRModule).?;
const src_decl = zir_module.contents.module.findDecl(decl_name) orelse
return mod.fail(scope, inst.base.src, "use of undeclared identifier '{s}'", .{decl_name});
const decl = try resolveCompleteZirDecl(mod, scope, src_decl.decl);
return decl;
}
fn analyzeInstSimplePtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, mutable: bool, size: std.builtin.TypeInfo.Pointer.Size) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();

View File

@ -36,7 +36,7 @@ pub fn addCases(ctx: *TestContext) !void {
{
var case = ctx.exe("hello world with updates", linux_x64);
case.addError("", &[_][]const u8{"no entry point found"});
case.addError("", &[_][]const u8{"error: no entry point found"});
// Incorrect return type
case.addError(
@ -147,7 +147,7 @@ pub fn addCases(ctx: *TestContext) !void {
{
var case = ctx.exe("hello world with updates", macosx_x64);
case.addError("", &[_][]const u8{"no entry point found"});
case.addError("", &[_][]const u8{"error: no entry point found"});
// Incorrect return type
case.addError(
@ -1234,7 +1234,10 @@ pub fn addCases(ctx: *TestContext) !void {
\\ var i: u32 = 10;
\\ unreachable;
\\}
, &[_][]const u8{":3:9: error: redefinition of 'i'"});
, &[_][]const u8{
":3:9: error: redefinition of 'i'",
":2:9: note: previous definition is here",
});
case.addError(
\\var testing: i64 = 10;
\\export fn _start() noreturn {
@ -1243,24 +1246,46 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
, &[_][]const u8{":3:9: error: redefinition of 'testing'"});
}
ctx.compileError("compileLog", linux_x64,
\\export fn _start() noreturn {
\\ const b = true;
\\ var f: u32 = 1;
\\ @compileLog(b, 20, f, x);
\\ @compileLog(1000);
\\ var bruh: usize = true;
\\ unreachable;
\\}
\\fn x() void {}
, &[_][]const u8{
":4:3: error: found compile log statement",
":5:3: error: found compile log statement",
":6:21: error: expected usize, found bool",
});
// TODO if this is here it invalidates the compile error checker:
// "| true, 20, (runtime value), (function)"
// "| 1000"
{
// TODO make the test harness support checking the compile log output too
var case = ctx.obj("@compileLog", linux_x64);
// The other compile error prevents emission of a "found compile log" statement.
case.addError(
\\export fn _start() noreturn {
\\ const b = true;
\\ var f: u32 = 1;
\\ @compileLog(b, 20, f, x);
\\ @compileLog(1000);
\\ var bruh: usize = true;
\\ unreachable;
\\}
\\export fn other() void {
\\ @compileLog(1234);
\\}
\\fn x() void {}
, &[_][]const u8{
":6:23: error: expected usize, found bool",
});
// Now only compile log statements remain. One per Decl.
case.addError(
\\export fn _start() noreturn {
\\ const b = true;
\\ var f: u32 = 1;
\\ @compileLog(b, 20, f, x);
\\ @compileLog(1000);
\\ unreachable;
\\}
\\export fn other() void {
\\ @compileLog(1234);
\\}
\\fn x() void {}
, &[_][]const u8{
":11:8: error: found compile log statement",
":4:5: note: also here",
});
}
{
var case = ctx.obj("extern variable has no type", linux_x64);
@ -1387,6 +1412,14 @@ pub fn addCases(ctx: *TestContext) !void {
\\ foo: for ("foo") |_| {}
\\}
, &[_][]const u8{":2:5: error: unused for label"});
case.addError(
\\comptime {
\\ blk: {blk: {}}
\\}
, &[_][]const u8{
":2:11: error: redefinition of label 'blk'",
":2:5: note: previous definition is here",
});
}
{