mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
Merge pull request #23907 from mlugg/ref-trace
compiler: reference trace fixes
This commit is contained in:
commit
9064907b34
29 changed files with 253 additions and 150 deletions
|
|
@ -750,7 +750,7 @@ fn runStepNames(
|
||||||
if (run.prominent_compile_errors and total_compile_errors > 0) {
|
if (run.prominent_compile_errors and total_compile_errors > 0) {
|
||||||
for (step_stack.keys()) |s| {
|
for (step_stack.keys()) |s| {
|
||||||
if (s.result_error_bundle.errorMessageCount() > 0) {
|
if (s.result_error_bundle.errorMessageCount() > 0) {
|
||||||
s.result_error_bundle.renderToStdErr(.{ .ttyconf = ttyconf, .include_reference_trace = (b.reference_trace orelse 0) > 0 });
|
s.result_error_bundle.renderToStdErr(.{ .ttyconf = ttyconf });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1129,11 +1129,7 @@ fn workerMakeOneStep(
|
||||||
defer std.debug.unlockStdErr();
|
defer std.debug.unlockStdErr();
|
||||||
|
|
||||||
const gpa = b.allocator;
|
const gpa = b.allocator;
|
||||||
const options: std.zig.ErrorBundle.RenderOptions = .{
|
printErrorMessages(gpa, s, .{ .ttyconf = run.ttyconf }, run.stderr, run.prominent_compile_errors) catch {};
|
||||||
.ttyconf = run.ttyconf,
|
|
||||||
.include_reference_trace = (b.reference_trace orelse 0) > 0,
|
|
||||||
};
|
|
||||||
printErrorMessages(gpa, s, options, run.stderr, run.prominent_compile_errors) catch {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_result: {
|
handle_result: {
|
||||||
|
|
|
||||||
|
|
@ -3328,7 +3328,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
|
||||||
if (comp.zcu) |zcu| zcu_errors: {
|
if (comp.zcu) |zcu| zcu_errors: {
|
||||||
for (zcu.failed_files.keys(), zcu.failed_files.values()) |file, error_msg| {
|
for (zcu.failed_files.keys(), zcu.failed_files.values()) |file, error_msg| {
|
||||||
if (error_msg) |msg| {
|
if (error_msg) |msg| {
|
||||||
try addModuleErrorMsg(zcu, &bundle, msg.*);
|
try addModuleErrorMsg(zcu, &bundle, msg.*, false);
|
||||||
} else {
|
} else {
|
||||||
// Must be ZIR or Zoir errors. Note that this may include AST errors.
|
// Must be ZIR or Zoir errors. Note that this may include AST errors.
|
||||||
_ = try file.getTree(gpa); // Tree must be loaded.
|
_ = try file.getTree(gpa); // Tree must be loaded.
|
||||||
|
|
@ -3378,6 +3378,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
|
||||||
break :s entries.slice();
|
break :s entries.slice();
|
||||||
};
|
};
|
||||||
defer sorted_failed_analysis.deinit(gpa);
|
defer sorted_failed_analysis.deinit(gpa);
|
||||||
|
var added_any_analysis_error = false;
|
||||||
for (sorted_failed_analysis.items(.key), sorted_failed_analysis.items(.value)) |anal_unit, error_msg| {
|
for (sorted_failed_analysis.items(.key), sorted_failed_analysis.items(.value)) |anal_unit, error_msg| {
|
||||||
if (comp.incremental) {
|
if (comp.incremental) {
|
||||||
const refs = try zcu.resolveReferences();
|
const refs = try zcu.resolveReferences();
|
||||||
|
|
@ -3389,7 +3390,9 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
|
||||||
zcu.fmtAnalUnit(anal_unit),
|
zcu.fmtAnalUnit(anal_unit),
|
||||||
});
|
});
|
||||||
|
|
||||||
try addModuleErrorMsg(zcu, &bundle, error_msg.*);
|
try addModuleErrorMsg(zcu, &bundle, error_msg.*, added_any_analysis_error);
|
||||||
|
added_any_analysis_error = true;
|
||||||
|
|
||||||
if (zcu.cimport_errors.get(anal_unit)) |errors| {
|
if (zcu.cimport_errors.get(anal_unit)) |errors| {
|
||||||
for (errors.getMessages()) |err_msg_index| {
|
for (errors.getMessages()) |err_msg_index| {
|
||||||
const err_msg = errors.getErrorMessage(err_msg_index);
|
const err_msg = errors.getErrorMessage(err_msg_index);
|
||||||
|
|
@ -3412,13 +3415,13 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (zcu.failed_codegen.values()) |error_msg| {
|
for (zcu.failed_codegen.values()) |error_msg| {
|
||||||
try addModuleErrorMsg(zcu, &bundle, error_msg.*);
|
try addModuleErrorMsg(zcu, &bundle, error_msg.*, false);
|
||||||
}
|
}
|
||||||
for (zcu.failed_types.values()) |error_msg| {
|
for (zcu.failed_types.values()) |error_msg| {
|
||||||
try addModuleErrorMsg(zcu, &bundle, error_msg.*);
|
try addModuleErrorMsg(zcu, &bundle, error_msg.*, false);
|
||||||
}
|
}
|
||||||
for (zcu.failed_exports.values()) |value| {
|
for (zcu.failed_exports.values()) |value| {
|
||||||
try addModuleErrorMsg(zcu, &bundle, value.*);
|
try addModuleErrorMsg(zcu, &bundle, value.*, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const actual_error_count = zcu.intern_pool.global_error_set.getNamesFromMainThread().len;
|
const actual_error_count = zcu.intern_pool.global_error_set.getNamesFromMainThread().len;
|
||||||
|
|
@ -3527,7 +3530,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
|
||||||
// We don't actually include the error here if `!include_compile_log_sources`.
|
// We don't actually include the error here if `!include_compile_log_sources`.
|
||||||
// The sorting above was still necessary, though, to get `log_text` in the right order.
|
// The sorting above was still necessary, though, to get `log_text` in the right order.
|
||||||
if (include_compile_log_sources) {
|
if (include_compile_log_sources) {
|
||||||
try addModuleErrorMsg(zcu, &bundle, messages.items[0]);
|
try addModuleErrorMsg(zcu, &bundle, messages.items[0], false);
|
||||||
}
|
}
|
||||||
|
|
||||||
break :compile_log_text try log_text.toOwnedSlice(gpa);
|
break :compile_log_text try log_text.toOwnedSlice(gpa);
|
||||||
|
|
@ -3631,10 +3634,14 @@ pub const ErrorNoteHashContext = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const default_reference_trace_len = 2;
|
||||||
pub fn addModuleErrorMsg(
|
pub fn addModuleErrorMsg(
|
||||||
zcu: *Zcu,
|
zcu: *Zcu,
|
||||||
eb: *ErrorBundle.Wip,
|
eb: *ErrorBundle.Wip,
|
||||||
module_err_msg: Zcu.ErrorMsg,
|
module_err_msg: Zcu.ErrorMsg,
|
||||||
|
/// If `-freference-trace` is not specified, we only want to show the one reference trace.
|
||||||
|
/// So, this is whether we have already emitted an error with a reference trace.
|
||||||
|
already_added_error: bool,
|
||||||
) !void {
|
) !void {
|
||||||
const gpa = eb.gpa;
|
const gpa = eb.gpa;
|
||||||
const ip = &zcu.intern_pool;
|
const ip = &zcu.intern_pool;
|
||||||
|
|
@ -3657,45 +3664,44 @@ pub fn addModuleErrorMsg(
|
||||||
var ref_traces: std.ArrayListUnmanaged(ErrorBundle.ReferenceTrace) = .empty;
|
var ref_traces: std.ArrayListUnmanaged(ErrorBundle.ReferenceTrace) = .empty;
|
||||||
defer ref_traces.deinit(gpa);
|
defer ref_traces.deinit(gpa);
|
||||||
|
|
||||||
if (module_err_msg.reference_trace_root.unwrap()) |rt_root| {
|
rt: {
|
||||||
|
const rt_root = module_err_msg.reference_trace_root.unwrap() orelse break :rt;
|
||||||
|
const max_references = zcu.comp.reference_trace orelse refs: {
|
||||||
|
if (already_added_error) break :rt;
|
||||||
|
break :refs default_reference_trace_len;
|
||||||
|
};
|
||||||
|
|
||||||
const all_references = try zcu.resolveReferences();
|
const all_references = try zcu.resolveReferences();
|
||||||
|
|
||||||
var seen: std.AutoHashMapUnmanaged(InternPool.AnalUnit, void) = .empty;
|
var seen: std.AutoHashMapUnmanaged(InternPool.AnalUnit, void) = .empty;
|
||||||
defer seen.deinit(gpa);
|
defer seen.deinit(gpa);
|
||||||
|
|
||||||
const max_references = zcu.comp.reference_trace orelse Sema.default_reference_trace_len;
|
|
||||||
|
|
||||||
var referenced_by = rt_root;
|
var referenced_by = rt_root;
|
||||||
while (all_references.get(referenced_by)) |maybe_ref| {
|
while (all_references.get(referenced_by)) |maybe_ref| {
|
||||||
const ref = maybe_ref orelse break;
|
const ref = maybe_ref orelse break;
|
||||||
const gop = try seen.getOrPut(gpa, ref.referencer);
|
const gop = try seen.getOrPut(gpa, ref.referencer);
|
||||||
if (gop.found_existing) break;
|
if (gop.found_existing) break;
|
||||||
if (ref_traces.items.len < max_references) skip: {
|
if (ref_traces.items.len < max_references) {
|
||||||
const src = ref.src.upgrade(zcu);
|
var last_call_src = ref.src;
|
||||||
const source = try src.file_scope.getSource(gpa);
|
var opt_inline_frame = ref.inline_frame;
|
||||||
const span = try src.span(gpa);
|
while (opt_inline_frame.unwrap()) |inline_frame| {
|
||||||
const loc = std.zig.findLineColumn(source.bytes, span.main);
|
const f = inline_frame.ptr(zcu).*;
|
||||||
const rt_file_path = try src.file_scope.fullPath(gpa);
|
const func_nav = ip.indexToKey(f.callee).func.owner_nav;
|
||||||
defer gpa.free(rt_file_path);
|
const func_name = ip.getNav(func_nav).name.toSlice(ip);
|
||||||
const name = switch (ref.referencer.unwrap()) {
|
try addReferenceTraceFrame(zcu, eb, &ref_traces, func_name, last_call_src, true);
|
||||||
|
last_call_src = f.call_src;
|
||||||
|
opt_inline_frame = f.parent;
|
||||||
|
}
|
||||||
|
const root_name: ?[]const u8 = switch (ref.referencer.unwrap()) {
|
||||||
.@"comptime" => "comptime",
|
.@"comptime" => "comptime",
|
||||||
.nav_val, .nav_ty => |nav| ip.getNav(nav).name.toSlice(ip),
|
.nav_val, .nav_ty => |nav| ip.getNav(nav).name.toSlice(ip),
|
||||||
.type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip),
|
.type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip),
|
||||||
.func => |f| ip.getNav(zcu.funcInfo(f).owner_nav).name.toSlice(ip),
|
.func => |f| ip.getNav(zcu.funcInfo(f).owner_nav).name.toSlice(ip),
|
||||||
.memoized_state => break :skip,
|
.memoized_state => null,
|
||||||
};
|
};
|
||||||
try ref_traces.append(gpa, .{
|
if (root_name) |n| {
|
||||||
.decl_name = try eb.addString(name),
|
try addReferenceTraceFrame(zcu, eb, &ref_traces, n, last_call_src, false);
|
||||||
.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,
|
|
||||||
.line = @intCast(loc.line),
|
|
||||||
.column = @intCast(loc.column),
|
|
||||||
.source_line = 0,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
referenced_by = ref.referencer;
|
referenced_by = ref.referencer;
|
||||||
}
|
}
|
||||||
|
|
@ -3775,6 +3781,35 @@ pub fn addModuleErrorMsg(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn addReferenceTraceFrame(
|
||||||
|
zcu: *Zcu,
|
||||||
|
eb: *ErrorBundle.Wip,
|
||||||
|
ref_traces: *std.ArrayListUnmanaged(ErrorBundle.ReferenceTrace),
|
||||||
|
name: []const u8,
|
||||||
|
lazy_src: Zcu.LazySrcLoc,
|
||||||
|
inlined: bool,
|
||||||
|
) !void {
|
||||||
|
const gpa = zcu.gpa;
|
||||||
|
const src = lazy_src.upgrade(zcu);
|
||||||
|
const source = try src.file_scope.getSource(gpa);
|
||||||
|
const span = try src.span(gpa);
|
||||||
|
const loc = std.zig.findLineColumn(source.bytes, span.main);
|
||||||
|
const rt_file_path = try src.file_scope.fullPath(gpa);
|
||||||
|
defer gpa.free(rt_file_path);
|
||||||
|
try ref_traces.append(gpa, .{
|
||||||
|
.decl_name = try eb.printString("{s}{s}", .{ name, if (inlined) " [inlined]" else "" }),
|
||||||
|
.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,
|
||||||
|
.line = @intCast(loc.line),
|
||||||
|
.column = @intCast(loc.column),
|
||||||
|
.source_line = 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void {
|
pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void {
|
||||||
const gpa = eb.gpa;
|
const gpa = eb.gpa;
|
||||||
const src_path = try file.fullPath(gpa);
|
const src_path = try file.fullPath(gpa);
|
||||||
|
|
|
||||||
126
src/Sema.zig
126
src/Sema.zig
|
|
@ -191,7 +191,6 @@ const LowerZon = @import("Sema/LowerZon.zig");
|
||||||
const arith = @import("Sema/arith.zig");
|
const arith = @import("Sema/arith.zig");
|
||||||
|
|
||||||
pub const default_branch_quota = 1000;
|
pub const default_branch_quota = 1000;
|
||||||
pub const default_reference_trace_len = 2;
|
|
||||||
|
|
||||||
pub const InferredErrorSet = struct {
|
pub const InferredErrorSet = struct {
|
||||||
/// The function body from which this error set originates.
|
/// The function body from which this error set originates.
|
||||||
|
|
@ -445,10 +444,31 @@ pub const Block = struct {
|
||||||
pub const Inlining = struct {
|
pub const Inlining = struct {
|
||||||
call_block: *Block,
|
call_block: *Block,
|
||||||
call_src: LazySrcLoc,
|
call_src: LazySrcLoc,
|
||||||
has_comptime_args: bool,
|
|
||||||
func: InternPool.Index,
|
func: InternPool.Index,
|
||||||
|
|
||||||
|
/// Populated lazily by `refFrame`.
|
||||||
|
ref_frame: Zcu.InlineReferenceFrame.Index.Optional = .none,
|
||||||
|
|
||||||
|
/// If `true`, the following fields are `undefined`. This doesn't represent a true inline
|
||||||
|
/// call, but rather a generic call analyzing the instantiation's generic type bodies.
|
||||||
|
is_generic_instantiation: bool,
|
||||||
|
|
||||||
|
has_comptime_args: bool,
|
||||||
comptime_result: Air.Inst.Ref,
|
comptime_result: Air.Inst.Ref,
|
||||||
merges: Merges,
|
merges: Merges,
|
||||||
|
|
||||||
|
fn refFrame(inlining: *Inlining, zcu: *Zcu) Allocator.Error!Zcu.InlineReferenceFrame.Index {
|
||||||
|
if (inlining.ref_frame == .none) {
|
||||||
|
inlining.ref_frame = (try zcu.addInlineReferenceFrame(.{
|
||||||
|
.callee = inlining.func,
|
||||||
|
.call_src = inlining.call_src,
|
||||||
|
.parent = if (inlining.call_block.inlining) |parent_inlining| p: {
|
||||||
|
break :p (try parent_inlining.refFrame(zcu)).toOptional();
|
||||||
|
} else .none,
|
||||||
|
})).toOptional();
|
||||||
|
}
|
||||||
|
return inlining.ref_frame.unwrap().?;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Merges = struct {
|
pub const Merges = struct {
|
||||||
|
|
@ -2580,7 +2600,7 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg
|
||||||
if (build_options.enable_debug_extensions and zcu.comp.debug_compile_errors) {
|
if (build_options.enable_debug_extensions and zcu.comp.debug_compile_errors) {
|
||||||
var wip_errors: std.zig.ErrorBundle.Wip = undefined;
|
var wip_errors: std.zig.ErrorBundle.Wip = undefined;
|
||||||
wip_errors.init(gpa) catch @panic("out of memory");
|
wip_errors.init(gpa) catch @panic("out of memory");
|
||||||
Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*) catch @panic("out of memory");
|
Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, false) catch @panic("out of memory");
|
||||||
std.debug.print("compile error during Sema:\n", .{});
|
std.debug.print("compile error during Sema:\n", .{});
|
||||||
var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory");
|
var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory");
|
||||||
error_bundle.renderToStdErr(.{ .ttyconf = .no_color });
|
error_bundle.renderToStdErr(.{ .ttyconf = .no_color });
|
||||||
|
|
@ -2590,20 +2610,17 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg
|
||||||
if (block) |start_block| {
|
if (block) |start_block| {
|
||||||
var block_it = start_block;
|
var block_it = start_block;
|
||||||
while (block_it.inlining) |inlining| {
|
while (block_it.inlining) |inlining| {
|
||||||
try sema.errNote(
|
const note_str = note: {
|
||||||
inlining.call_src,
|
if (inlining.is_generic_instantiation) break :note "generic function instantiated here";
|
||||||
err_msg,
|
if (inlining.call_block.isComptime()) break :note "called at comptime here";
|
||||||
"called from here",
|
break :note "called inline here";
|
||||||
.{},
|
};
|
||||||
);
|
try sema.errNote(inlining.call_src, err_msg, "{s}", .{note_str});
|
||||||
block_it = inlining.call_block;
|
block_it = inlining.call_block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const use_ref_trace = if (zcu.comp.reference_trace) |n| n > 0 else zcu.failed_analysis.count() == 0;
|
|
||||||
if (use_ref_trace) {
|
|
||||||
err_msg.reference_trace_root = sema.owner.toOptional();
|
err_msg.reference_trace_root = sema.owner.toOptional();
|
||||||
}
|
|
||||||
|
|
||||||
const gop = try zcu.failed_analysis.getOrPut(gpa, sema.owner);
|
const gop = try zcu.failed_analysis.getOrPut(gpa, sema.owner);
|
||||||
if (gop.found_existing) {
|
if (gop.found_existing) {
|
||||||
|
|
@ -4291,7 +4308,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
|
||||||
if (zcu.intern_pool.isFuncBody(val)) {
|
if (zcu.intern_pool.isFuncBody(val)) {
|
||||||
const ty = Type.fromInterned(zcu.intern_pool.typeOf(val));
|
const ty = Type.fromInterned(zcu.intern_pool.typeOf(val));
|
||||||
if (try ty.fnHasRuntimeBitsSema(pt)) {
|
if (try ty.fnHasRuntimeBitsSema(pt)) {
|
||||||
try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .func = val }));
|
try sema.addReferenceEntry(block, src, AnalUnit.wrap(.{ .func = val }));
|
||||||
try zcu.ensureFuncBodyAnalysisQueued(val);
|
try zcu.ensureFuncBodyAnalysisQueued(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6619,7 +6636,7 @@ pub fn analyzeExport(
|
||||||
if (options.linkage == .internal)
|
if (options.linkage == .internal)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try sema.ensureNavResolved(src, orig_nav_index, .fully);
|
try sema.ensureNavResolved(block, src, orig_nav_index, .fully);
|
||||||
|
|
||||||
const exported_nav_index = switch (ip.indexToKey(ip.getNav(orig_nav_index).status.fully_resolved.val)) {
|
const exported_nav_index = switch (ip.indexToKey(ip.getNav(orig_nav_index).status.fully_resolved.val)) {
|
||||||
.variable => |v| v.owner_nav,
|
.variable => |v| v.owner_nav,
|
||||||
|
|
@ -6648,7 +6665,7 @@ pub fn analyzeExport(
|
||||||
return sema.fail(block, src, "export target cannot be extern", .{});
|
return sema.fail(block, src, "export target cannot be extern", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
try sema.maybeQueueFuncBodyAnalysis(src, exported_nav_index);
|
try sema.maybeQueueFuncBodyAnalysis(block, src, exported_nav_index);
|
||||||
|
|
||||||
try sema.exports.append(gpa, .{
|
try sema.exports.append(gpa, .{
|
||||||
.opts = options,
|
.opts = options,
|
||||||
|
|
@ -6896,7 +6913,7 @@ fn zirDeclRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
|
||||||
.no_embedded_nulls,
|
.no_embedded_nulls,
|
||||||
);
|
);
|
||||||
const nav_index = try sema.lookupIdentifier(block, src, decl_name);
|
const nav_index = try sema.lookupIdentifier(block, src, decl_name);
|
||||||
return sema.analyzeNavRef(src, nav_index);
|
return sema.analyzeNavRef(block, src, nav_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zirDeclVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
|
fn zirDeclVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
|
||||||
|
|
@ -6992,7 +7009,7 @@ fn lookupInNamespace(
|
||||||
}
|
}
|
||||||
|
|
||||||
for (usingnamespaces.items) |sub_ns_nav| {
|
for (usingnamespaces.items) |sub_ns_nav| {
|
||||||
try sema.ensureNavResolved(src, sub_ns_nav, .fully);
|
try sema.ensureNavResolved(block, src, sub_ns_nav, .fully);
|
||||||
const sub_ns_ty = Type.fromInterned(ip.getNav(sub_ns_nav).status.fully_resolved.val);
|
const sub_ns_ty = Type.fromInterned(ip.getNav(sub_ns_nav).status.fully_resolved.val);
|
||||||
const sub_ns = zcu.namespacePtr(sub_ns_ty.getNamespaceIndex(zcu));
|
const sub_ns = zcu.namespacePtr(sub_ns_ty.getNamespaceIndex(zcu));
|
||||||
try checked_namespaces.put(gpa, sub_ns, {});
|
try checked_namespaces.put(gpa, sub_ns, {});
|
||||||
|
|
@ -7724,10 +7741,11 @@ fn analyzeCall(
|
||||||
var generic_inlining: Block.Inlining = if (func_ty_info.is_generic) .{
|
var generic_inlining: Block.Inlining = if (func_ty_info.is_generic) .{
|
||||||
.call_block = block,
|
.call_block = block,
|
||||||
.call_src = call_src,
|
.call_src = call_src,
|
||||||
.has_comptime_args = false, // unused by error reporting
|
.func = func_val.?.toIntern(),
|
||||||
.func = .none, // unused by error reporting
|
.is_generic_instantiation = true, // this allows the following fields to be `undefined`
|
||||||
.comptime_result = .none, // unused by error reporting
|
.has_comptime_args = undefined,
|
||||||
.merges = undefined, // unused because we'll never `return`
|
.comptime_result = undefined,
|
||||||
|
.merges = undefined,
|
||||||
} else undefined;
|
} else undefined;
|
||||||
|
|
||||||
// This is the block in which we evaluate generic function components: that is, generic parameter
|
// This is the block in which we evaluate generic function components: that is, generic parameter
|
||||||
|
|
@ -8003,7 +8021,7 @@ fn analyzeCall(
|
||||||
ref_func: {
|
ref_func: {
|
||||||
const runtime_func_val = try sema.resolveValue(runtime_func) orelse break :ref_func;
|
const runtime_func_val = try sema.resolveValue(runtime_func) orelse break :ref_func;
|
||||||
if (!ip.isFuncBody(runtime_func_val.toIntern())) break :ref_func;
|
if (!ip.isFuncBody(runtime_func_val.toIntern())) break :ref_func;
|
||||||
try sema.addReferenceEntry(call_src, .wrap(.{ .func = runtime_func_val.toIntern() }));
|
try sema.addReferenceEntry(block, call_src, .wrap(.{ .func = runtime_func_val.toIntern() }));
|
||||||
try zcu.ensureFuncBodyAnalysisQueued(runtime_func_val.toIntern());
|
try zcu.ensureFuncBodyAnalysisQueued(runtime_func_val.toIntern());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -8205,10 +8223,11 @@ fn analyzeCall(
|
||||||
var inlining: Block.Inlining = .{
|
var inlining: Block.Inlining = .{
|
||||||
.call_block = block,
|
.call_block = block,
|
||||||
.call_src = call_src,
|
.call_src = call_src,
|
||||||
|
.func = func_val.?.toIntern(),
|
||||||
|
.is_generic_instantiation = false,
|
||||||
.has_comptime_args = for (args) |a| {
|
.has_comptime_args = for (args) |a| {
|
||||||
if (try sema.isComptimeKnown(a)) break true;
|
if (try sema.isComptimeKnown(a)) break true;
|
||||||
} else false,
|
} else false,
|
||||||
.func = func_val.?.toIntern(),
|
|
||||||
.comptime_result = undefined,
|
.comptime_result = undefined,
|
||||||
.merges = .{
|
.merges = .{
|
||||||
.block_inst = block_inst,
|
.block_inst = block_inst,
|
||||||
|
|
@ -8239,7 +8258,10 @@ fn analyzeCall(
|
||||||
if (!inlining.has_comptime_args) {
|
if (!inlining.has_comptime_args) {
|
||||||
var block_it = block;
|
var block_it = block;
|
||||||
while (block_it.inlining) |parent_inlining| {
|
while (block_it.inlining) |parent_inlining| {
|
||||||
if (!parent_inlining.has_comptime_args and parent_inlining.func == func_val.?.toIntern()) {
|
if (!parent_inlining.is_generic_instantiation and
|
||||||
|
!parent_inlining.has_comptime_args and
|
||||||
|
parent_inlining.func == func_val.?.toIntern())
|
||||||
|
{
|
||||||
return sema.fail(block, call_src, "inline call is recursive", .{});
|
return sema.fail(block, call_src, "inline call is recursive", .{});
|
||||||
}
|
}
|
||||||
block_it = parent_inlining.call_block;
|
block_it = parent_inlining.call_block;
|
||||||
|
|
@ -17258,7 +17280,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
|
||||||
.@"comptime" => |index| return Air.internedToRef(index),
|
.@"comptime" => |index| return Air.internedToRef(index),
|
||||||
.runtime => |index| index,
|
.runtime => |index| index,
|
||||||
.nav_val => |nav| return sema.analyzeNavVal(block, src, nav),
|
.nav_val => |nav| return sema.analyzeNavVal(block, src, nav),
|
||||||
.nav_ref => |nav| return sema.analyzeNavRef(src, nav),
|
.nav_ref => |nav| return sema.analyzeNavRef(block, src, nav),
|
||||||
};
|
};
|
||||||
|
|
||||||
// The comptime case is handled already above. Runtime case below.
|
// The comptime case is handled already above. Runtime case below.
|
||||||
|
|
@ -18411,7 +18433,7 @@ fn typeInfoNamespaceDecls(
|
||||||
if (zcu.analysis_in_progress.contains(.wrap(.{ .nav_val = nav }))) {
|
if (zcu.analysis_in_progress.contains(.wrap(.{ .nav_val = nav }))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try sema.ensureNavResolved(src, nav, .fully);
|
try sema.ensureNavResolved(block, src, nav, .fully);
|
||||||
const namespace_ty = Type.fromInterned(ip.getNav(nav).status.fully_resolved.val);
|
const namespace_ty = Type.fromInterned(ip.getNav(nav).status.fully_resolved.val);
|
||||||
try sema.typeInfoNamespaceDecls(block, src, namespace_ty.getNamespaceIndex(zcu).toOptional(), declaration_ty, decl_vals, seen_namespaces);
|
try sema.typeInfoNamespaceDecls(block, src, namespace_ty.getNamespaceIndex(zcu).toOptional(), declaration_ty, decl_vals, seen_namespaces);
|
||||||
}
|
}
|
||||||
|
|
@ -19443,6 +19465,7 @@ fn analyzeRet(
|
||||||
};
|
};
|
||||||
|
|
||||||
if (block.inlining) |inlining| {
|
if (block.inlining) |inlining| {
|
||||||
|
assert(!inlining.is_generic_instantiation); // can't `return` in a generic param/ret ty expr
|
||||||
if (block.isComptime()) {
|
if (block.isComptime()) {
|
||||||
const ret_val = try sema.resolveConstValue(block, operand_src, operand, null);
|
const ret_val = try sema.resolveConstValue(block, operand_src, operand, null);
|
||||||
inlining.comptime_result = operand;
|
inlining.comptime_result = operand;
|
||||||
|
|
@ -27936,7 +27959,7 @@ fn namespaceLookupRef(
|
||||||
decl_name: InternPool.NullTerminatedString,
|
decl_name: InternPool.NullTerminatedString,
|
||||||
) CompileError!?Air.Inst.Ref {
|
) CompileError!?Air.Inst.Ref {
|
||||||
const nav = try sema.namespaceLookup(block, src, namespace, decl_name) orelse return null;
|
const nav = try sema.namespaceLookup(block, src, namespace, decl_name) orelse return null;
|
||||||
return try sema.analyzeNavRef(src, nav);
|
return try sema.analyzeNavRef(block, src, nav);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn namespaceLookupVal(
|
fn namespaceLookupVal(
|
||||||
|
|
@ -29099,7 +29122,7 @@ fn coerceExtra(
|
||||||
.@"extern" => |e| e.owner_nav,
|
.@"extern" => |e| e.owner_nav,
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
const inst_as_ptr = try sema.analyzeNavRef(inst_src, fn_nav);
|
const inst_as_ptr = try sema.analyzeNavRef(block, inst_src, fn_nav);
|
||||||
return sema.coerce(block, dest_ty, inst_as_ptr, inst_src);
|
return sema.coerce(block, dest_ty, inst_as_ptr, inst_src);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30752,7 +30775,7 @@ fn coerceVarArgParam(
|
||||||
.@"fn" => fn_ptr: {
|
.@"fn" => fn_ptr: {
|
||||||
const fn_val = try sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inst, undefined);
|
const fn_val = try sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inst, undefined);
|
||||||
const fn_nav = zcu.funcInfo(fn_val.toIntern()).owner_nav;
|
const fn_nav = zcu.funcInfo(fn_val.toIntern()).owner_nav;
|
||||||
break :fn_ptr try sema.analyzeNavRef(inst_src, fn_nav);
|
break :fn_ptr try sema.analyzeNavRef(block, inst_src, fn_nav);
|
||||||
},
|
},
|
||||||
.array => return sema.fail(block, inst_src, "arrays must be passed by reference to variadic function", .{}),
|
.array => return sema.fail(block, inst_src, "arrays must be passed by reference to variadic function", .{}),
|
||||||
.float => float: {
|
.float => float: {
|
||||||
|
|
@ -31762,12 +31785,13 @@ fn analyzeNavVal(
|
||||||
src: LazySrcLoc,
|
src: LazySrcLoc,
|
||||||
nav_index: InternPool.Nav.Index,
|
nav_index: InternPool.Nav.Index,
|
||||||
) CompileError!Air.Inst.Ref {
|
) CompileError!Air.Inst.Ref {
|
||||||
const ref = try sema.analyzeNavRefInner(src, nav_index, false);
|
const ref = try sema.analyzeNavRefInner(block, src, nav_index, false);
|
||||||
return sema.analyzeLoad(block, src, ref, src);
|
return sema.analyzeLoad(block, src, ref, src);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn addReferenceEntry(
|
fn addReferenceEntry(
|
||||||
sema: *Sema,
|
sema: *Sema,
|
||||||
|
opt_block: ?*Block,
|
||||||
src: LazySrcLoc,
|
src: LazySrcLoc,
|
||||||
referenced_unit: AnalUnit,
|
referenced_unit: AnalUnit,
|
||||||
) !void {
|
) !void {
|
||||||
|
|
@ -31775,10 +31799,12 @@ fn addReferenceEntry(
|
||||||
if (!zcu.comp.incremental and zcu.comp.reference_trace == 0) return;
|
if (!zcu.comp.incremental and zcu.comp.reference_trace == 0) return;
|
||||||
const gop = try sema.references.getOrPut(sema.gpa, referenced_unit);
|
const gop = try sema.references.getOrPut(sema.gpa, referenced_unit);
|
||||||
if (gop.found_existing) return;
|
if (gop.found_existing) return;
|
||||||
// TODO: we need to figure out how to model inline calls here.
|
try zcu.addUnitReference(sema.owner, referenced_unit, src, inline_frame: {
|
||||||
// They aren't references in the analysis sense, but ought to show up in the reference trace!
|
const block = opt_block orelse break :inline_frame .none;
|
||||||
// Would representing inline calls in the reference table cause excessive memory usage?
|
const inlining = block.inlining orelse break :inline_frame .none;
|
||||||
try zcu.addUnitReference(sema.owner, referenced_unit, src);
|
const frame = try inlining.refFrame(zcu);
|
||||||
|
break :inline_frame frame.toOptional();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addTypeReferenceEntry(
|
pub fn addTypeReferenceEntry(
|
||||||
|
|
@ -31797,7 +31823,7 @@ fn ensureMemoizedStateResolved(sema: *Sema, src: LazySrcLoc, stage: InternPool.M
|
||||||
const pt = sema.pt;
|
const pt = sema.pt;
|
||||||
|
|
||||||
const unit: AnalUnit = .wrap(.{ .memoized_state = stage });
|
const unit: AnalUnit = .wrap(.{ .memoized_state = stage });
|
||||||
try sema.addReferenceEntry(src, unit);
|
try sema.addReferenceEntry(null, src, unit);
|
||||||
try sema.declareDependency(.{ .memoized_state = stage });
|
try sema.declareDependency(.{ .memoized_state = stage });
|
||||||
|
|
||||||
if (pt.zcu.analysis_in_progress.contains(unit)) {
|
if (pt.zcu.analysis_in_progress.contains(unit)) {
|
||||||
|
|
@ -31806,7 +31832,7 @@ fn ensureMemoizedStateResolved(sema: *Sema, src: LazySrcLoc, stage: InternPool.M
|
||||||
try pt.ensureMemoizedStateUpToDate(stage);
|
try pt.ensureMemoizedStateUpToDate(stage);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensureNavResolved(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav.Index, kind: enum { type, fully }) CompileError!void {
|
pub fn ensureNavResolved(sema: *Sema, block: *Block, src: LazySrcLoc, nav_index: InternPool.Nav.Index, kind: enum { type, fully }) CompileError!void {
|
||||||
const pt = sema.pt;
|
const pt = sema.pt;
|
||||||
const zcu = pt.zcu;
|
const zcu = pt.zcu;
|
||||||
const ip = &zcu.intern_pool;
|
const ip = &zcu.intern_pool;
|
||||||
|
|
@ -31829,7 +31855,7 @@ pub fn ensureNavResolved(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav
|
||||||
.type => .{ .nav_ty = nav_index },
|
.type => .{ .nav_ty = nav_index },
|
||||||
.fully => .{ .nav_val = nav_index },
|
.fully => .{ .nav_val = nav_index },
|
||||||
});
|
});
|
||||||
try sema.addReferenceEntry(src, anal_unit);
|
try sema.addReferenceEntry(block, src, anal_unit);
|
||||||
|
|
||||||
if (zcu.analysis_in_progress.contains(anal_unit)) {
|
if (zcu.analysis_in_progress.contains(anal_unit)) {
|
||||||
return sema.failWithOwnedErrorMsg(null, try sema.errMsg(.{
|
return sema.failWithOwnedErrorMsg(null, try sema.errMsg(.{
|
||||||
|
|
@ -31859,25 +31885,25 @@ fn optRefValue(sema: *Sema, opt_val: ?Value) !Value {
|
||||||
} }));
|
} }));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn analyzeNavRef(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav.Index) CompileError!Air.Inst.Ref {
|
fn analyzeNavRef(sema: *Sema, block: *Block, src: LazySrcLoc, nav_index: InternPool.Nav.Index) CompileError!Air.Inst.Ref {
|
||||||
return sema.analyzeNavRefInner(src, nav_index, true);
|
return sema.analyzeNavRefInner(block, src, nav_index, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Analyze a reference to the `Nav` at the given index. Ensures the underlying `Nav` is analyzed.
|
/// Analyze a reference to the `Nav` at the given index. Ensures the underlying `Nav` is analyzed.
|
||||||
/// If this pointer will be used directly, `is_ref` must be `true`.
|
/// If this pointer will be used directly, `is_ref` must be `true`.
|
||||||
/// If this pointer will be immediately loaded (i.e. a `decl_val` instruction), `is_ref` must be `false`.
|
/// If this pointer will be immediately loaded (i.e. a `decl_val` instruction), `is_ref` must be `false`.
|
||||||
fn analyzeNavRefInner(sema: *Sema, src: LazySrcLoc, orig_nav_index: InternPool.Nav.Index, is_ref: bool) CompileError!Air.Inst.Ref {
|
fn analyzeNavRefInner(sema: *Sema, block: *Block, src: LazySrcLoc, orig_nav_index: InternPool.Nav.Index, is_ref: bool) CompileError!Air.Inst.Ref {
|
||||||
const pt = sema.pt;
|
const pt = sema.pt;
|
||||||
const zcu = pt.zcu;
|
const zcu = pt.zcu;
|
||||||
const ip = &zcu.intern_pool;
|
const ip = &zcu.intern_pool;
|
||||||
|
|
||||||
try sema.ensureNavResolved(src, orig_nav_index, if (is_ref) .type else .fully);
|
try sema.ensureNavResolved(block, src, orig_nav_index, if (is_ref) .type else .fully);
|
||||||
|
|
||||||
const nav_index = nav: {
|
const nav_index = nav: {
|
||||||
if (ip.getNav(orig_nav_index).isExternOrFn(ip)) {
|
if (ip.getNav(orig_nav_index).isExternOrFn(ip)) {
|
||||||
// Getting a pointer to this `Nav` might mean we actually get a pointer to something else!
|
// Getting a pointer to this `Nav` might mean we actually get a pointer to something else!
|
||||||
// We need to resolve the value to know for sure.
|
// We need to resolve the value to know for sure.
|
||||||
if (is_ref) try sema.ensureNavResolved(src, orig_nav_index, .fully);
|
if (is_ref) try sema.ensureNavResolved(block, src, orig_nav_index, .fully);
|
||||||
switch (ip.indexToKey(ip.getNav(orig_nav_index).status.fully_resolved.val)) {
|
switch (ip.indexToKey(ip.getNav(orig_nav_index).status.fully_resolved.val)) {
|
||||||
.func => |f| break :nav f.owner_nav,
|
.func => |f| break :nav f.owner_nav,
|
||||||
.@"extern" => |e| break :nav e.owner_nav,
|
.@"extern" => |e| break :nav e.owner_nav,
|
||||||
|
|
@ -31901,7 +31927,7 @@ fn analyzeNavRefInner(sema: *Sema, src: LazySrcLoc, orig_nav_index: InternPool.N
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (is_ref) {
|
if (is_ref) {
|
||||||
try sema.maybeQueueFuncBodyAnalysis(src, nav_index);
|
try sema.maybeQueueFuncBodyAnalysis(block, src, nav_index);
|
||||||
}
|
}
|
||||||
return Air.internedToRef((try pt.intern(.{ .ptr = .{
|
return Air.internedToRef((try pt.intern(.{ .ptr = .{
|
||||||
.ty = ptr_ty.toIntern(),
|
.ty = ptr_ty.toIntern(),
|
||||||
|
|
@ -31910,7 +31936,7 @@ fn analyzeNavRefInner(sema: *Sema, src: LazySrcLoc, orig_nav_index: InternPool.N
|
||||||
} })));
|
} })));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybeQueueFuncBodyAnalysis(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav.Index) !void {
|
fn maybeQueueFuncBodyAnalysis(sema: *Sema, block: *Block, src: LazySrcLoc, nav_index: InternPool.Nav.Index) !void {
|
||||||
const pt = sema.pt;
|
const pt = sema.pt;
|
||||||
const zcu = pt.zcu;
|
const zcu = pt.zcu;
|
||||||
const ip = &zcu.intern_pool;
|
const ip = &zcu.intern_pool;
|
||||||
|
|
@ -31918,16 +31944,16 @@ fn maybeQueueFuncBodyAnalysis(sema: *Sema, src: LazySrcLoc, nav_index: InternPoo
|
||||||
// To avoid forcing too much resolution, let's first resolve the type, and check if it's a function.
|
// To avoid forcing too much resolution, let's first resolve the type, and check if it's a function.
|
||||||
// If it is, we can resolve the *value*, and queue analysis as needed.
|
// If it is, we can resolve the *value*, and queue analysis as needed.
|
||||||
|
|
||||||
try sema.ensureNavResolved(src, nav_index, .type);
|
try sema.ensureNavResolved(block, src, nav_index, .type);
|
||||||
const nav_ty: Type = .fromInterned(ip.getNav(nav_index).typeOf(ip));
|
const nav_ty: Type = .fromInterned(ip.getNav(nav_index).typeOf(ip));
|
||||||
if (nav_ty.zigTypeTag(zcu) != .@"fn") return;
|
if (nav_ty.zigTypeTag(zcu) != .@"fn") return;
|
||||||
if (!try nav_ty.fnHasRuntimeBitsSema(pt)) return;
|
if (!try nav_ty.fnHasRuntimeBitsSema(pt)) return;
|
||||||
|
|
||||||
try sema.ensureNavResolved(src, nav_index, .fully);
|
try sema.ensureNavResolved(block, src, nav_index, .fully);
|
||||||
const nav_val = zcu.navValue(nav_index);
|
const nav_val = zcu.navValue(nav_index);
|
||||||
if (!ip.isFuncBody(nav_val.toIntern())) return;
|
if (!ip.isFuncBody(nav_val.toIntern())) return;
|
||||||
|
|
||||||
try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .func = nav_val.toIntern() }));
|
try sema.addReferenceEntry(block, src, AnalUnit.wrap(.{ .func = nav_val.toIntern() }));
|
||||||
try zcu.ensureFuncBodyAnalysisQueued(nav_val.toIntern());
|
try zcu.ensureFuncBodyAnalysisQueued(nav_val.toIntern());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31943,8 +31969,8 @@ fn analyzeRef(
|
||||||
|
|
||||||
if (try sema.resolveValue(operand)) |val| {
|
if (try sema.resolveValue(operand)) |val| {
|
||||||
switch (zcu.intern_pool.indexToKey(val.toIntern())) {
|
switch (zcu.intern_pool.indexToKey(val.toIntern())) {
|
||||||
.@"extern" => |e| return sema.analyzeNavRef(src, e.owner_nav),
|
.@"extern" => |e| return sema.analyzeNavRef(block, src, e.owner_nav),
|
||||||
.func => |f| return sema.analyzeNavRef(src, f.owner_nav),
|
.func => |f| return sema.analyzeNavRef(block, src, f.owner_nav),
|
||||||
else => return uavRef(sema, val.toIntern()),
|
else => return uavRef(sema, val.toIntern()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -35508,7 +35534,7 @@ fn resolveInferredErrorSet(
|
||||||
}
|
}
|
||||||
// In this case we are dealing with the actual InferredErrorSet object that
|
// In this case we are dealing with the actual InferredErrorSet object that
|
||||||
// corresponds to the function, not one created to track an inline/comptime call.
|
// corresponds to the function, not one created to track an inline/comptime call.
|
||||||
try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .func = func_index }));
|
try sema.addReferenceEntry(block, src, AnalUnit.wrap(.{ .func = func_index }));
|
||||||
try pt.ensureFuncBodyUpToDate(func_index);
|
try pt.ensureFuncBodyUpToDate(func_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -228,7 +228,7 @@ fn loadComptimePtrInner(
|
||||||
|
|
||||||
const base_val: MutableValue = switch (ptr.base_addr) {
|
const base_val: MutableValue = switch (ptr.base_addr) {
|
||||||
.nav => |nav| val: {
|
.nav => |nav| val: {
|
||||||
try sema.ensureNavResolved(src, nav, .fully);
|
try sema.ensureNavResolved(block, src, nav, .fully);
|
||||||
const val = ip.getNav(nav).status.fully_resolved.val;
|
const val = ip.getNav(nav).status.fully_resolved.val;
|
||||||
switch (ip.indexToKey(val)) {
|
switch (ip.indexToKey(val)) {
|
||||||
.variable => return .runtime_load,
|
.variable => return .runtime_load,
|
||||||
|
|
|
||||||
80
src/Zcu.zig
80
src/Zcu.zig
|
|
@ -215,6 +215,9 @@ all_references: std.ArrayListUnmanaged(Reference) = .empty,
|
||||||
/// Freelist of indices in `all_references`.
|
/// Freelist of indices in `all_references`.
|
||||||
free_references: std.ArrayListUnmanaged(u32) = .empty,
|
free_references: std.ArrayListUnmanaged(u32) = .empty,
|
||||||
|
|
||||||
|
inline_reference_frames: std.ArrayListUnmanaged(InlineReferenceFrame) = .empty,
|
||||||
|
free_inline_reference_frames: std.ArrayListUnmanaged(InlineReferenceFrame.Index) = .empty,
|
||||||
|
|
||||||
/// Key is the `AnalUnit` *performing* the reference. This representation allows
|
/// Key is the `AnalUnit` *performing* the reference. This representation allows
|
||||||
/// incremental updates to quickly delete references caused by a specific `AnalUnit`.
|
/// incremental updates to quickly delete references caused by a specific `AnalUnit`.
|
||||||
/// Value is index into `all_type_reference` of the first reference triggered by the unit.
|
/// Value is index into `all_type_reference` of the first reference triggered by the unit.
|
||||||
|
|
@ -583,6 +586,42 @@ pub const Reference = struct {
|
||||||
next: u32,
|
next: u32,
|
||||||
/// The source location of the reference.
|
/// The source location of the reference.
|
||||||
src: LazySrcLoc,
|
src: LazySrcLoc,
|
||||||
|
/// If not `.none`, this is the index of the `InlineReferenceFrame` which should appear
|
||||||
|
/// between the referencer and `referenced` in the reference trace. These frames represent
|
||||||
|
/// inline calls, which do not create actual references (since they happen in the caller's
|
||||||
|
/// `AnalUnit`), but do show in the reference trace.
|
||||||
|
inline_frame: InlineReferenceFrame.Index.Optional,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const InlineReferenceFrame = struct {
|
||||||
|
/// The inline *callee*; that is, the function which was called inline.
|
||||||
|
/// The *caller* is either `parent`, or else the unit causing the original `Reference`.
|
||||||
|
callee: InternPool.Index,
|
||||||
|
/// The source location of the inline call, in the *caller*.
|
||||||
|
call_src: LazySrcLoc,
|
||||||
|
/// If not `.none`, a frame which should appear directly below this one.
|
||||||
|
/// This will be the "parent" inline call; this frame's `callee` is our caller.
|
||||||
|
parent: InlineReferenceFrame.Index.Optional,
|
||||||
|
|
||||||
|
pub const Index = enum(u32) {
|
||||||
|
_,
|
||||||
|
pub fn ptr(idx: Index, zcu: *Zcu) *InlineReferenceFrame {
|
||||||
|
return &zcu.inline_reference_frames.items[@intFromEnum(idx)];
|
||||||
|
}
|
||||||
|
pub fn toOptional(idx: Index) Optional {
|
||||||
|
return @enumFromInt(@intFromEnum(idx));
|
||||||
|
}
|
||||||
|
pub const Optional = enum(u32) {
|
||||||
|
none = std.math.maxInt(u32),
|
||||||
|
_,
|
||||||
|
pub fn unwrap(opt: Optional) ?Index {
|
||||||
|
return switch (opt) {
|
||||||
|
.none => null,
|
||||||
|
_ => @enumFromInt(@intFromEnum(opt)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const TypeReference = struct {
|
pub const TypeReference = struct {
|
||||||
|
|
@ -3440,12 +3479,28 @@ pub fn deleteUnitReferences(zcu: *Zcu, anal_unit: AnalUnit) void {
|
||||||
var idx = kv.value;
|
var idx = kv.value;
|
||||||
|
|
||||||
while (idx != std.math.maxInt(u32)) {
|
while (idx != std.math.maxInt(u32)) {
|
||||||
|
const ref = zcu.all_references.items[idx];
|
||||||
zcu.free_references.append(gpa, idx) catch {
|
zcu.free_references.append(gpa, idx) catch {
|
||||||
// This space will be reused eventually, so we need not propagate this error.
|
// This space will be reused eventually, so we need not propagate this error.
|
||||||
// Just leak it for now, and let GC reclaim it later on.
|
// Just leak it for now, and let GC reclaim it later on.
|
||||||
break :unit_refs;
|
break :unit_refs;
|
||||||
};
|
};
|
||||||
idx = zcu.all_references.items[idx].next;
|
idx = ref.next;
|
||||||
|
|
||||||
|
var opt_inline_frame = ref.inline_frame;
|
||||||
|
while (opt_inline_frame.unwrap()) |inline_frame| {
|
||||||
|
// The same inline frame could be used multiple times by one unit. We need to
|
||||||
|
// detect this case to avoid adding it to `free_inline_reference_frames` more
|
||||||
|
// than once. We do that by setting `parent` to itself as a marker.
|
||||||
|
if (inline_frame.ptr(zcu).parent == inline_frame.toOptional()) break;
|
||||||
|
zcu.free_inline_reference_frames.append(gpa, inline_frame) catch {
|
||||||
|
// This space will be reused eventually, so we need not propagate this error.
|
||||||
|
// Just leak it for now, and let GC reclaim it later on.
|
||||||
|
break :unit_refs;
|
||||||
|
};
|
||||||
|
opt_inline_frame = inline_frame.ptr(zcu).parent;
|
||||||
|
inline_frame.ptr(zcu).parent = inline_frame.toOptional(); // signal to code above
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3480,7 +3535,22 @@ pub fn deleteUnitCompileLogs(zcu: *Zcu, anal_unit: AnalUnit) void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addUnitReference(zcu: *Zcu, src_unit: AnalUnit, referenced_unit: AnalUnit, ref_src: LazySrcLoc) Allocator.Error!void {
|
pub fn addInlineReferenceFrame(zcu: *Zcu, frame: InlineReferenceFrame) Allocator.Error!Zcu.InlineReferenceFrame.Index {
|
||||||
|
const frame_idx: InlineReferenceFrame.Index = zcu.free_inline_reference_frames.pop() orelse idx: {
|
||||||
|
_ = try zcu.inline_reference_frames.addOne(zcu.gpa);
|
||||||
|
break :idx @enumFromInt(zcu.inline_reference_frames.items.len - 1);
|
||||||
|
};
|
||||||
|
frame_idx.ptr(zcu).* = frame;
|
||||||
|
return frame_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addUnitReference(
|
||||||
|
zcu: *Zcu,
|
||||||
|
src_unit: AnalUnit,
|
||||||
|
referenced_unit: AnalUnit,
|
||||||
|
ref_src: LazySrcLoc,
|
||||||
|
inline_frame: InlineReferenceFrame.Index.Optional,
|
||||||
|
) Allocator.Error!void {
|
||||||
const gpa = zcu.gpa;
|
const gpa = zcu.gpa;
|
||||||
|
|
||||||
zcu.clearCachedResolvedReferences();
|
zcu.clearCachedResolvedReferences();
|
||||||
|
|
@ -3500,6 +3570,7 @@ pub fn addUnitReference(zcu: *Zcu, src_unit: AnalUnit, referenced_unit: AnalUnit
|
||||||
.referenced = referenced_unit,
|
.referenced = referenced_unit,
|
||||||
.next = if (gop.found_existing) gop.value_ptr.* else std.math.maxInt(u32),
|
.next = if (gop.found_existing) gop.value_ptr.* else std.math.maxInt(u32),
|
||||||
.src = ref_src,
|
.src = ref_src,
|
||||||
|
.inline_frame = inline_frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
gop.value_ptr.* = @intCast(ref_idx);
|
gop.value_ptr.* = @intCast(ref_idx);
|
||||||
|
|
@ -3828,7 +3899,10 @@ pub fn unionTagFieldIndex(zcu: *const Zcu, loaded_union: InternPool.LoadedUnionT
|
||||||
|
|
||||||
pub const ResolvedReference = struct {
|
pub const ResolvedReference = struct {
|
||||||
referencer: AnalUnit,
|
referencer: AnalUnit,
|
||||||
|
/// If `inline_frame` is not `.none`, this is the *deepest* source location in the chain of
|
||||||
|
/// inline calls. For source locations further up the inline call stack, consult `inline_frame`.
|
||||||
src: LazySrcLoc,
|
src: LazySrcLoc,
|
||||||
|
inline_frame: InlineReferenceFrame.Index.Optional,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Returns a mapping from an `AnalUnit` to where it is referenced.
|
/// Returns a mapping from an `AnalUnit` to where it is referenced.
|
||||||
|
|
@ -4037,6 +4111,7 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
|
||||||
try unit_queue.put(gpa, ref.referenced, .{
|
try unit_queue.put(gpa, ref.referenced, .{
|
||||||
.referencer = unit,
|
.referencer = unit,
|
||||||
.src = ref.src,
|
.src = ref.src,
|
||||||
|
.inline_frame = ref.inline_frame,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ref_idx = ref.next;
|
ref_idx = ref.next;
|
||||||
|
|
@ -4055,6 +4130,7 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
|
||||||
try type_queue.put(gpa, ref.referenced, .{
|
try type_queue.put(gpa, ref.referenced, .{
|
||||||
.referencer = unit,
|
.referencer = unit,
|
||||||
.src = ref.src,
|
.src = ref.src,
|
||||||
|
.inline_frame = .none,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ref_idx = ref.next;
|
ref_idx = ref.next;
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ export fn entry() usize {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :3:14: error: overflow of integer type 'u16' with value '65540'
|
// :3:14: error: overflow of integer type 'u16' with value '65540'
|
||||||
// :1:14: note: called from here
|
// :1:14: note: called at comptime here
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@ pub export fn entry() void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :11:5: error: expected 0 argument(s), found 1
|
// :11:5: error: expected 0 argument(s), found 1
|
||||||
// :1:12: note: function declared here
|
// :1:12: note: function declared here
|
||||||
// :17:19: note: called from here
|
// :17:19: note: called inline here
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,4 @@ export fn entry() usize {
|
||||||
// error
|
// error
|
||||||
//
|
//
|
||||||
// :3:16: error: division by zero here causes illegal behavior
|
// :3:16: error: division by zero here causes illegal behavior
|
||||||
// :1:14: note: called from here
|
// :1:14: note: called at comptime here
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ pub fn bar() u8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :6:12: error: expected error union type, found 'u8'
|
// :6:12: error: expected error union type, found 'u8'
|
||||||
// :2:8: note: called from here
|
// :2:8: note: called at comptime here
|
||||||
|
|
|
||||||
|
|
@ -22,4 +22,4 @@ comptime {
|
||||||
//
|
//
|
||||||
// :7:16: error: captured value contains reference to comptime var
|
// :7:16: error: captured value contains reference to comptime var
|
||||||
// :16:30: note: 'wrapper.ptr' points to comptime var declared here
|
// :16:30: note: 'wrapper.ptr' points to comptime var declared here
|
||||||
// :17:29: note: called from here
|
// :17:29: note: called at comptime here
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,8 @@ export fn entry() void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :4:5: error: unreachable code
|
// :4:5: error: unreachable code
|
||||||
// :4:25: note: control flow is diverted here
|
// :4:25: note: control flow is diverted here
|
||||||
// :4:25: error: aoeu
|
// :4:25: error: aoeu
|
||||||
// :1:36: note: called from here
|
// :1:36: note: called at comptime here
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ pub export fn entry() void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :9:48: error: caught unexpected error 'InvalidVersion'
|
// :9:48: error: caught unexpected error 'InvalidVersion'
|
||||||
// :?:?: note: error returned here
|
// :?:?: note: error returned here
|
||||||
|
|
@ -24,4 +22,4 @@ pub export fn entry() void {
|
||||||
// :?:?: note: error returned here
|
// :?:?: note: error returned here
|
||||||
// :?:?: note: error returned here
|
// :?:?: note: error returned here
|
||||||
// :?:?: note: error returned here
|
// :?:?: note: error returned here
|
||||||
// :12:37: note: called from here
|
// :12:37: note: called at comptime here
|
||||||
|
|
|
||||||
|
|
@ -25,5 +25,5 @@ fn Type(comptime n: usize) type {
|
||||||
//
|
//
|
||||||
// :21:16: error: evaluation exceeded 1001 backwards branches
|
// :21:16: error: evaluation exceeded 1001 backwards branches
|
||||||
// :21:16: note: use @setEvalBranchQuota() to raise the branch limit from 1001
|
// :21:16: note: use @setEvalBranchQuota() to raise the branch limit from 1001
|
||||||
// :16:34: note: called from here
|
// :16:34: note: called at comptime here
|
||||||
// :8:15: note: called from here
|
// :8:15: note: generic function instantiated here
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,6 @@ pub fn is(comptime id: std.builtin.TypeId) TraitFn {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :8:48: error: expected type 'type', found 'bool'
|
// :8:48: error: expected type 'type', found 'bool'
|
||||||
// :5:21: note: called from here
|
// :5:21: note: generic function instantiated here
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=x86_64-linux
|
// target=x86_64-linux
|
||||||
// output_mode=Exe
|
// output_mode=Exe
|
||||||
//
|
//
|
||||||
// : error: root source file struct 'tmp' has no member named 'main'
|
// : error: root source file struct 'tmp' has no member named 'main'
|
||||||
// : note: struct declared here
|
// : note: struct declared here
|
||||||
// : note: called from here
|
// : note: called inline here
|
||||||
// : note: called from here
|
// : note: called inline here
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,7 @@ comptime {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :5:17: error: missing struct field: b
|
// :5:17: error: missing struct field: b
|
||||||
// :1:11: note: struct declared here
|
// :1:11: note: struct declared here
|
||||||
// :9:15: note: called from here
|
// :9:15: note: called at comptime here
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ export fn entry() usize {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :3:14: error: overflow of integer type 'u16' with value '1800000'
|
// :3:14: error: overflow of integer type 'u16' with value '1800000'
|
||||||
// :1:14: note: called from here
|
// :1:14: note: called at comptime here
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ export fn entry() usize {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :3:12: error: overflow of integer type 'i8' with value '128'
|
// :3:12: error: overflow of integer type 'i8' with value '128'
|
||||||
// :1:14: note: called from here
|
// :1:14: note: called at comptime here
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,4 @@ fn makeLlamas(count: usize) [count]u8 {}
|
||||||
//
|
//
|
||||||
// :8:30: error: unable to resolve comptime value
|
// :8:30: error: unable to resolve comptime value
|
||||||
// :8:30: note: array length must be comptime-known
|
// :8:30: note: array length must be comptime-known
|
||||||
// :2:31: note: called from here
|
// :2:31: note: generic function instantiated here
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
fn main() void {}
|
fn main() void {}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=x86_64-linux
|
// target=x86_64-linux
|
||||||
// output_mode=Exe
|
// output_mode=Exe
|
||||||
//
|
//
|
||||||
// : error: 'main' is not marked 'pub'
|
// : error: 'main' is not marked 'pub'
|
||||||
// :1:1: note: declared here
|
// :1:1: note: declared here
|
||||||
// : note: called from here
|
// : note: called inline here
|
||||||
// : note: called from here
|
// : note: called inline here
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ pub export fn entry2() void {
|
||||||
// error
|
// error
|
||||||
//
|
//
|
||||||
// :5:27: error: inline call is recursive
|
// :5:27: error: inline call is recursive
|
||||||
// :12:12: note: called from here
|
// :12:12: note: called inline here
|
||||||
// :24:10: error: inline call is recursive
|
// :24:10: error: inline call is recursive
|
||||||
// :20:10: note: called from here
|
// :20:10: note: called inline here
|
||||||
// :16:11: note: called from here
|
// :16:11: note: called inline here
|
||||||
// :28:10: note: called from here
|
// :28:10: note: called inline here
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ fn assert(ok: bool) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :10:14: error: reached unreachable code
|
// :10:14: error: reached unreachable code
|
||||||
// :6:20: note: called from here
|
// :6:20: note: called at comptime here
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,7 @@ comptime {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :2:12: error: expected type 'fn () void', found 'type'
|
// :2:12: error: expected type 'fn () void', found 'type'
|
||||||
// :1:10: note: function return type declared here
|
// :1:10: note: function return type declared here
|
||||||
// :5:12: note: called from here
|
// :5:12: note: called at comptime here
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ var rt: u32 = undefined;
|
||||||
// :10:12: note: call to function with comptime-only return type 'type' is evaluated at comptime
|
// :10:12: note: call to function with comptime-only return type 'type' is evaluated at comptime
|
||||||
// :13:10: note: return type declared here
|
// :13:10: note: return type declared here
|
||||||
// :10:12: note: types are not available at runtime
|
// :10:12: note: types are not available at runtime
|
||||||
// :2:8: note: called from here
|
// :2:8: note: called inline here
|
||||||
// :19:8: error: unable to evaluate comptime expression
|
// :19:8: error: unable to evaluate comptime expression
|
||||||
// :19:5: note: operation is runtime due to this operand
|
// :19:5: note: operation is runtime due to this operand
|
||||||
// :6:8: note: called at comptime from here
|
// :6:8: note: called at comptime from here
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@ export fn entry() void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :13:30: error: expected type 'u32', found 'i32'
|
// :13:30: error: expected type 'u32', found 'i32'
|
||||||
// :13:30: note: unsigned 32-bit int cannot represent all possible signed 32-bit values
|
// :13:30: note: unsigned 32-bit int cannot represent all possible signed 32-bit values
|
||||||
// :17:33: note: called from here
|
// :17:33: note: called inline here
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,9 @@ export fn d() callconv(.naked) noreturn {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
//
|
//
|
||||||
// :2:5: error: local variable in naked function
|
// :2:5: error: local variable in naked function
|
||||||
// :10:5: error: local variable in naked function
|
// :10:5: error: local variable in naked function
|
||||||
// :23:5: error: local variable in naked function
|
// :23:5: error: local variable in naked function
|
||||||
// :30:13: error: local variable in naked function
|
// :30:13: error: local variable in naked function
|
||||||
// :35:12: note: called from here
|
// :35:12: note: called inline here
|
||||||
|
|
|
||||||
|
|
@ -12,4 +12,4 @@ fn incr(x: *comptime_int) void {
|
||||||
//
|
//
|
||||||
// :8:9: error: store to comptime variable depends on runtime condition
|
// :8:9: error: store to comptime variable depends on runtime condition
|
||||||
// :3:9: note: runtime condition here
|
// :3:9: note: runtime condition here
|
||||||
// :4:22: note: called from here
|
// :4:22: note: called at comptime here
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ export fn entry() usize {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :3:14: error: overflow of integer type 'u16' with value '-10'
|
// :3:14: error: overflow of integer type 'u16' with value '-10'
|
||||||
// :1:14: note: called from here
|
// :1:14: note: called at comptime here
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@ export fn entry() void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// error
|
// error
|
||||||
// backend=stage2
|
|
||||||
// target=native
|
|
||||||
//
|
//
|
||||||
// :4:9: error: reached unreachable code
|
// :4:9: error: reached unreachable code
|
||||||
// :8:21: note: called from here
|
// :8:21: note: called at comptime here
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue