mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
cbe: fix for export changes
This commit is contained in:
parent
0e5335aaf5
commit
00da182e68
6 changed files with 286 additions and 276 deletions
|
|
@ -207,16 +207,16 @@ typedef char bool;
|
|||
__asm(zig_mangle_c(name) " = " zig_mangle_c(symbol))
|
||||
#endif
|
||||
|
||||
#define zig_mangled_tentative zig_mangled
|
||||
#define zig_mangled_final zig_mangled
|
||||
#if _MSC_VER
|
||||
#define zig_mangled_tentative(mangled, unmangled)
|
||||
#define zig_mangled_final(mangled, unmangled) ; \
|
||||
#define zig_mangled(mangled, unmangled) ; \
|
||||
zig_export(#mangled, unmangled)
|
||||
#define zig_mangled_export(mangled, unmangled, symbol) \
|
||||
zig_export(unmangled, #mangled) \
|
||||
zig_export(symbol, unmangled)
|
||||
#else /* _MSC_VER */
|
||||
#define zig_mangled_tentative(mangled, unmangled) __asm(zig_mangle_c(unmangled))
|
||||
#define zig_mangled_final(mangled, unmangled) zig_mangled_tentative(mangled, unmangled)
|
||||
#define zig_mangled(mangled, unmangled) __asm(zig_mangle_c(unmangled))
|
||||
#define zig_mangled_export(mangled, unmangled, symbol) \
|
||||
zig_mangled_final(mangled, unmangled) \
|
||||
zig_export(symbol, unmangled)
|
||||
|
|
|
|||
|
|
@ -3466,6 +3466,9 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: std.Progress.Node) !vo
|
|||
};
|
||||
},
|
||||
.emit_h_decl => |decl_index| {
|
||||
if (true) @panic("regressed compiler feature: emit-h should hook into updateExports, " ++
|
||||
"not decl analysis, which is too early to know about @export calls");
|
||||
|
||||
const module = comp.module.?;
|
||||
const decl = module.declPtr(decl_index);
|
||||
|
||||
|
|
|
|||
14
src/Zcu.zig
14
src/Zcu.zig
|
|
@ -268,6 +268,20 @@ pub const Exported = union(enum) {
|
|||
decl_index: Decl.Index,
|
||||
/// Constant value being exported.
|
||||
value: InternPool.Index,
|
||||
|
||||
pub fn getValue(exported: Exported, zcu: *Zcu) Value {
|
||||
return switch (exported) {
|
||||
.decl_index => |decl_index| zcu.declPtr(decl_index).val,
|
||||
.value => |value| Value.fromInterned(value),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getAlign(exported: Exported, zcu: *Zcu) Alignment {
|
||||
return switch (exported) {
|
||||
.decl_index => |decl_index| zcu.declPtr(decl_index).alignment,
|
||||
.value => .none,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Export = struct {
|
||||
|
|
|
|||
|
|
@ -731,8 +731,6 @@ pub const DeclGen = struct {
|
|||
if (decl.val.getExternFunc(zcu)) |extern_func| if (extern_func.decl != decl_index)
|
||||
return dg.renderDeclValue(writer, extern_func.decl, location);
|
||||
|
||||
if (decl.val.getVariable(zcu)) |variable| try dg.renderFwdDecl(decl_index, variable, .tentative);
|
||||
|
||||
// We shouldn't cast C function pointers as this is UB (when you call
|
||||
// them). The analysis until now should ensure that the C function
|
||||
// pointers are compatible. If they are not, then there is a bug
|
||||
|
|
@ -748,7 +746,7 @@ pub const DeclGen = struct {
|
|||
try writer.writeByte(')');
|
||||
}
|
||||
try writer.writeByte('&');
|
||||
try dg.renderDeclName(writer, decl_index, 0);
|
||||
try dg.renderDeclName(writer, decl_index);
|
||||
if (need_cast) try writer.writeByte(')');
|
||||
}
|
||||
|
||||
|
|
@ -1765,19 +1763,22 @@ pub const DeclGen = struct {
|
|||
fn renderFunctionSignature(
|
||||
dg: *DeclGen,
|
||||
w: anytype,
|
||||
fn_decl_index: InternPool.DeclIndex,
|
||||
fn_val: Value,
|
||||
fn_align: InternPool.Alignment,
|
||||
kind: CType.Kind,
|
||||
name: union(enum) {
|
||||
export_index: u32,
|
||||
ident: []const u8,
|
||||
decl: InternPool.DeclIndex,
|
||||
fmt_ctype_pool_string: std.fmt.Formatter(formatCTypePoolString),
|
||||
@"export": struct {
|
||||
main_name: InternPool.NullTerminatedString,
|
||||
extern_name: InternPool.NullTerminatedString,
|
||||
},
|
||||
},
|
||||
) !void {
|
||||
const zcu = dg.zcu;
|
||||
const ip = &zcu.intern_pool;
|
||||
|
||||
const fn_decl = zcu.declPtr(fn_decl_index);
|
||||
const fn_ty = fn_decl.typeOf(zcu);
|
||||
const fn_ty = fn_val.typeOf(zcu);
|
||||
const fn_ctype = try dg.ctypeFromType(fn_ty, kind);
|
||||
|
||||
const fn_info = zcu.typeToFunc(fn_ty).?;
|
||||
|
|
@ -1788,7 +1789,7 @@ pub const DeclGen = struct {
|
|||
else => unreachable,
|
||||
}
|
||||
}
|
||||
if (fn_decl.val.getFunction(zcu)) |func| if (func.analysis(ip).is_cold)
|
||||
if (fn_val.getFunction(zcu)) |func| if (func.analysis(ip).is_cold)
|
||||
try w.writeAll("zig_cold ");
|
||||
if (fn_info.return_type == .noreturn_type) try w.writeAll("zig_noreturn ");
|
||||
|
||||
|
|
@ -1799,22 +1800,11 @@ pub const DeclGen = struct {
|
|||
trailing = .maybe_space;
|
||||
}
|
||||
|
||||
switch (kind) {
|
||||
.forward => {},
|
||||
.complete => if (fn_decl.alignment.toByteUnits()) |a| {
|
||||
try w.print("{}zig_align_fn({})", .{ trailing, a });
|
||||
trailing = .maybe_space;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
try w.print("{}", .{trailing});
|
||||
switch (name) {
|
||||
.export_index => |export_index| {
|
||||
try w.print("{}", .{trailing});
|
||||
try dg.renderDeclName(w, fn_decl_index, export_index);
|
||||
},
|
||||
.ident => |ident| try w.print("{}{ }", .{ trailing, fmtIdent(ident) }),
|
||||
.fmt_ctype_pool_string => |fmt| try w.print("{}{ }", .{ trailing, fmt }),
|
||||
.decl => |decl_index| try dg.renderDeclName(w, decl_index),
|
||||
.fmt_ctype_pool_string => |fmt| try w.print("{ }", .{fmt}),
|
||||
.@"export" => |@"export"| try w.print("{ }", .{fmtIdent(@"export".extern_name.toSlice(ip))}),
|
||||
}
|
||||
|
||||
try renderTypeSuffix(
|
||||
|
|
@ -1833,44 +1823,30 @@ pub const DeclGen = struct {
|
|||
|
||||
switch (kind) {
|
||||
.forward => {
|
||||
if (fn_decl.alignment.toByteUnits()) |a| {
|
||||
try w.print(" zig_align_fn({})", .{a});
|
||||
}
|
||||
if (fn_align.toByteUnits()) |a| try w.print(" zig_align_fn({})", .{a});
|
||||
switch (name) {
|
||||
.export_index => |export_index| mangled: {
|
||||
const maybe_exports = zcu.decl_exports.get(fn_decl_index);
|
||||
const external_name = (if (maybe_exports) |exports|
|
||||
exports.items[export_index].opts.name
|
||||
else if (fn_decl.isExtern(zcu))
|
||||
fn_decl.name
|
||||
else
|
||||
break :mangled).toSlice(ip);
|
||||
const is_mangled = isMangledIdent(external_name, true);
|
||||
const is_export = export_index > 0;
|
||||
.decl, .fmt_ctype_pool_string => {},
|
||||
.@"export" => |@"export"| {
|
||||
const extern_name = @"export".extern_name.toSlice(ip);
|
||||
const is_mangled = isMangledIdent(extern_name, true);
|
||||
const is_export = @"export".extern_name != @"export".main_name;
|
||||
if (is_mangled and is_export) {
|
||||
try w.print(" zig_mangled_export({ }, {s}, {s})", .{
|
||||
fmtIdent(external_name),
|
||||
fmtStringLiteral(external_name, null),
|
||||
fmtStringLiteral(
|
||||
maybe_exports.?.items[0].opts.name.toSlice(ip),
|
||||
null,
|
||||
),
|
||||
fmtIdent(extern_name),
|
||||
fmtStringLiteral(extern_name, null),
|
||||
fmtStringLiteral(@"export".main_name.toSlice(ip), null),
|
||||
});
|
||||
} else if (is_mangled) {
|
||||
try w.print(" zig_mangled_final({ }, {s})", .{
|
||||
fmtIdent(external_name), fmtStringLiteral(external_name, null),
|
||||
try w.print(" zig_mangled({ }, {s})", .{
|
||||
fmtIdent(extern_name), fmtStringLiteral(extern_name, null),
|
||||
});
|
||||
} else if (is_export) {
|
||||
try w.print(" zig_export({s}, {s})", .{
|
||||
fmtStringLiteral(
|
||||
maybe_exports.?.items[0].opts.name.toSlice(ip),
|
||||
null,
|
||||
),
|
||||
fmtStringLiteral(external_name, null),
|
||||
fmtStringLiteral(@"export".main_name.toSlice(ip), null),
|
||||
fmtStringLiteral(extern_name, null),
|
||||
});
|
||||
}
|
||||
},
|
||||
.ident, .fmt_ctype_pool_string => {},
|
||||
}
|
||||
},
|
||||
.complete => {},
|
||||
|
|
@ -2085,21 +2061,11 @@ pub const DeclGen = struct {
|
|||
try renderTypeSuffix(dg.pass, &dg.ctype_pool, dg.zcu, w, ctype, .suffix, .{});
|
||||
}
|
||||
|
||||
fn declIsGlobal(dg: *DeclGen, val: Value) bool {
|
||||
const zcu = dg.zcu;
|
||||
return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
|
||||
.variable => |variable| zcu.decl_exports.contains(variable.decl),
|
||||
.extern_func => true,
|
||||
.func => |func| zcu.decl_exports.contains(func.owner_decl),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
fn writeName(dg: *DeclGen, w: anytype, c_value: CValue) !void {
|
||||
switch (c_value) {
|
||||
.new_local, .local => |i| try w.print("t{d}", .{i}),
|
||||
.constant => |val| try renderAnonDeclName(w, val),
|
||||
.decl => |decl| try dg.renderDeclName(w, decl, 0),
|
||||
.decl => |decl| try dg.renderDeclName(w, decl),
|
||||
.identifier => |ident| try w.print("{ }", .{fmtIdent(ident)}),
|
||||
else => unreachable,
|
||||
}
|
||||
|
|
@ -2111,10 +2077,10 @@ pub const DeclGen = struct {
|
|||
.constant => |val| try renderAnonDeclName(w, val),
|
||||
.arg, .arg_array => unreachable,
|
||||
.field => |i| try w.print("f{d}", .{i}),
|
||||
.decl => |decl| try dg.renderDeclName(w, decl, 0),
|
||||
.decl => |decl| try dg.renderDeclName(w, decl),
|
||||
.decl_ref => |decl| {
|
||||
try w.writeByte('&');
|
||||
try dg.renderDeclName(w, decl, 0);
|
||||
try dg.renderDeclName(w, decl);
|
||||
},
|
||||
.undef => |ty| try dg.renderUndefValue(w, ty, .Other),
|
||||
.identifier => |ident| try w.print("{ }", .{fmtIdent(ident)}),
|
||||
|
|
@ -2142,10 +2108,10 @@ pub const DeclGen = struct {
|
|||
.field => |i| try w.print("f{d}", .{i}),
|
||||
.decl => |decl| {
|
||||
try w.writeAll("(*");
|
||||
try dg.renderDeclName(w, decl, 0);
|
||||
try dg.renderDeclName(w, decl);
|
||||
try w.writeByte(')');
|
||||
},
|
||||
.decl_ref => |decl| try dg.renderDeclName(w, decl, 0),
|
||||
.decl_ref => |decl| try dg.renderDeclName(w, decl),
|
||||
.undef => unreachable,
|
||||
.identifier => |ident| try w.print("(*{ })", .{fmtIdent(ident)}),
|
||||
.payload_identifier => |ident| try w.print("(*{ }.{ })", .{
|
||||
|
|
@ -2195,19 +2161,12 @@ pub const DeclGen = struct {
|
|||
dg: *DeclGen,
|
||||
decl_index: InternPool.DeclIndex,
|
||||
variable: InternPool.Key.Variable,
|
||||
fwd_kind: enum { tentative, final },
|
||||
) !void {
|
||||
const zcu = dg.zcu;
|
||||
const decl = zcu.declPtr(decl_index);
|
||||
const fwd = dg.fwdDeclWriter();
|
||||
const is_global = variable.is_extern or dg.declIsGlobal(decl.val);
|
||||
try fwd.writeAll(if (is_global) "zig_extern " else "static ");
|
||||
const maybe_exports = zcu.decl_exports.get(decl_index);
|
||||
const export_weak_linkage = if (maybe_exports) |exports|
|
||||
exports.items[0].opts.linkage == .weak
|
||||
else
|
||||
false;
|
||||
if (variable.is_weak_linkage or export_weak_linkage) try fwd.writeAll("zig_weak_linkage ");
|
||||
try fwd.writeAll(if (variable.is_extern) "zig_extern " else "static ");
|
||||
if (variable.is_weak_linkage) try fwd.writeAll("zig_weak_linkage ");
|
||||
if (variable.is_threadlocal and !dg.mod.single_threaded) try fwd.writeAll("zig_threadlocal ");
|
||||
try dg.renderTypeAndName(
|
||||
fwd,
|
||||
|
|
@ -2217,38 +2176,17 @@ pub const DeclGen = struct {
|
|||
decl.alignment,
|
||||
.complete,
|
||||
);
|
||||
mangled: {
|
||||
const external_name = (if (maybe_exports) |exports|
|
||||
exports.items[0].opts.name
|
||||
else if (variable.is_extern)
|
||||
decl.name
|
||||
else
|
||||
break :mangled).toSlice(&zcu.intern_pool);
|
||||
if (isMangledIdent(external_name, true)) {
|
||||
try fwd.print(" zig_mangled_{s}({ }, {s})", .{
|
||||
@tagName(fwd_kind),
|
||||
fmtIdent(external_name),
|
||||
fmtStringLiteral(external_name, null),
|
||||
});
|
||||
}
|
||||
}
|
||||
try fwd.writeAll(";\n");
|
||||
}
|
||||
|
||||
fn renderDeclName(dg: *DeclGen, writer: anytype, decl_index: InternPool.DeclIndex, export_index: u32) !void {
|
||||
fn renderDeclName(dg: *DeclGen, writer: anytype, decl_index: InternPool.DeclIndex) !void {
|
||||
const zcu = dg.zcu;
|
||||
const ip = &zcu.intern_pool;
|
||||
const decl = zcu.declPtr(decl_index);
|
||||
|
||||
if (zcu.decl_exports.get(decl_index)) |exports| {
|
||||
try writer.print("{ }", .{
|
||||
fmtIdent(exports.items[export_index].opts.name.toSlice(ip)),
|
||||
});
|
||||
} else if (decl.getExternDecl(zcu).unwrap()) |extern_decl_index| {
|
||||
try writer.print("{ }", .{
|
||||
fmtIdent(zcu.declPtr(extern_decl_index).name.toSlice(ip)),
|
||||
});
|
||||
} else {
|
||||
if (decl.getExternDecl(zcu).unwrap()) |extern_decl_index| try writer.print("{ }", .{
|
||||
fmtIdent(zcu.declPtr(extern_decl_index).name.toSlice(ip)),
|
||||
}) else {
|
||||
// MSVC has a limit of 4095 character token length limit, and fmtIdent can (worst case),
|
||||
// expand to 3x the length of its input, but let's cut it off at a much shorter limit.
|
||||
var name: [100]u8 = undefined;
|
||||
|
|
@ -2761,69 +2699,6 @@ pub fn genErrDecls(o: *Object) !void {
|
|||
try writer.writeAll("};\n");
|
||||
}
|
||||
|
||||
fn genExports(o: *Object) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const zcu = o.dg.zcu;
|
||||
const ip = &zcu.intern_pool;
|
||||
const decl_index = switch (o.dg.pass) {
|
||||
.decl => |decl| decl,
|
||||
.anon, .flush => return,
|
||||
};
|
||||
const decl = zcu.declPtr(decl_index);
|
||||
const fwd = o.dg.fwdDeclWriter();
|
||||
|
||||
const exports = zcu.decl_exports.get(decl_index) orelse return;
|
||||
if (exports.items.len < 2) return;
|
||||
|
||||
const is_variable_const = switch (ip.indexToKey(decl.val.toIntern())) {
|
||||
.func => return for (exports.items[1..], 1..) |@"export", i| {
|
||||
try fwd.writeAll("zig_extern ");
|
||||
if (@"export".opts.linkage == .weak) try fwd.writeAll("zig_weak_linkage_fn ");
|
||||
try o.dg.renderFunctionSignature(
|
||||
fwd,
|
||||
decl_index,
|
||||
.forward,
|
||||
.{ .export_index = @intCast(i) },
|
||||
);
|
||||
try fwd.writeAll(";\n");
|
||||
},
|
||||
.extern_func => {
|
||||
// TODO: when sema allows re-exporting extern decls
|
||||
unreachable;
|
||||
},
|
||||
.variable => |variable| variable.is_const,
|
||||
else => true,
|
||||
};
|
||||
for (exports.items[1..]) |@"export"| {
|
||||
try fwd.writeAll("zig_extern ");
|
||||
if (@"export".opts.linkage == .weak) try fwd.writeAll("zig_weak_linkage ");
|
||||
const export_name = @"export".opts.name.toSlice(ip);
|
||||
try o.dg.renderTypeAndName(
|
||||
fwd,
|
||||
decl.typeOf(zcu),
|
||||
.{ .identifier = export_name },
|
||||
CQualifiers.init(.{ .@"const" = is_variable_const }),
|
||||
decl.alignment,
|
||||
.complete,
|
||||
);
|
||||
if (isMangledIdent(export_name, true)) {
|
||||
try fwd.print(" zig_mangled_export({ }, {s}, {s})", .{
|
||||
fmtIdent(export_name),
|
||||
fmtStringLiteral(export_name, null),
|
||||
fmtStringLiteral(exports.items[0].opts.name.toSlice(ip), null),
|
||||
});
|
||||
} else {
|
||||
try fwd.print(" zig_export({s}, {s})", .{
|
||||
fmtStringLiteral(exports.items[0].opts.name.toSlice(ip), null),
|
||||
fmtStringLiteral(export_name, null),
|
||||
});
|
||||
}
|
||||
try fwd.writeAll(";\n");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn genLazyFn(o: *Object, lazy_ctype_pool: *const CType.Pool, lazy_fn: LazyFnMap.Entry) !void {
|
||||
const zcu = o.dg.zcu;
|
||||
const ip = &zcu.intern_pool;
|
||||
|
|
@ -2885,19 +2760,19 @@ pub fn genLazyFn(o: *Object, lazy_ctype_pool: *const CType.Pool, lazy_fn: LazyFn
|
|||
const fn_info = fn_ctype.info(ctype_pool).function;
|
||||
const fn_name = fmtCTypePoolString(val.fn_name, lazy_ctype_pool);
|
||||
|
||||
const fwd_decl_writer = o.dg.fwdDeclWriter();
|
||||
try fwd_decl_writer.print("static zig_{s} ", .{@tagName(key)});
|
||||
try o.dg.renderFunctionSignature(fwd_decl_writer, fn_decl_index, .forward, .{
|
||||
const fwd = o.dg.fwdDeclWriter();
|
||||
try fwd.print("static zig_{s} ", .{@tagName(key)});
|
||||
try o.dg.renderFunctionSignature(fwd, fn_decl.val, fn_decl.alignment, .forward, .{
|
||||
.fmt_ctype_pool_string = fn_name,
|
||||
});
|
||||
try fwd_decl_writer.writeAll(";\n");
|
||||
try fwd.writeAll(";\n");
|
||||
|
||||
try w.print("static zig_{s} ", .{@tagName(key)});
|
||||
try o.dg.renderFunctionSignature(w, fn_decl_index, .complete, .{
|
||||
try w.print("zig_{s} ", .{@tagName(key)});
|
||||
try o.dg.renderFunctionSignature(w, fn_decl.val, .none, .complete, .{
|
||||
.fmt_ctype_pool_string = fn_name,
|
||||
});
|
||||
try w.writeAll(" {\n return ");
|
||||
try o.dg.renderDeclName(w, fn_decl_index, 0);
|
||||
try o.dg.renderDeclName(w, fn_decl_index);
|
||||
try w.writeByte('(');
|
||||
for (0..fn_info.param_ctypes.len) |arg| {
|
||||
if (arg > 0) try w.writeAll(", ");
|
||||
|
|
@ -2921,21 +2796,26 @@ pub fn genFunc(f: *Function) !void {
|
|||
o.code_header = std.ArrayList(u8).init(gpa);
|
||||
defer o.code_header.deinit();
|
||||
|
||||
const is_global = o.dg.declIsGlobal(decl.val);
|
||||
const fwd_decl_writer = o.dg.fwdDeclWriter();
|
||||
try fwd_decl_writer.writeAll(if (is_global) "zig_extern " else "static ");
|
||||
const fwd = o.dg.fwdDeclWriter();
|
||||
try fwd.writeAll("static ");
|
||||
try o.dg.renderFunctionSignature(
|
||||
fwd,
|
||||
decl.val,
|
||||
decl.alignment,
|
||||
.forward,
|
||||
.{ .decl = decl_index },
|
||||
);
|
||||
try fwd.writeAll(";\n");
|
||||
|
||||
if (zcu.decl_exports.get(decl_index)) |exports|
|
||||
if (exports.items[0].opts.linkage == .weak) try fwd_decl_writer.writeAll("zig_weak_linkage_fn ");
|
||||
try o.dg.renderFunctionSignature(fwd_decl_writer, decl_index, .forward, .{ .export_index = 0 });
|
||||
try fwd_decl_writer.writeAll(";\n");
|
||||
try genExports(o);
|
||||
|
||||
try o.indent_writer.insertNewline();
|
||||
if (!is_global) try o.writer().writeAll("static ");
|
||||
if (decl.@"linksection".toSlice(&zcu.intern_pool)) |s|
|
||||
try o.writer().print("zig_linksection_fn({s}) ", .{fmtStringLiteral(s, null)});
|
||||
try o.dg.renderFunctionSignature(o.writer(), decl_index, .complete, .{ .export_index = 0 });
|
||||
try o.dg.renderFunctionSignature(
|
||||
o.writer(),
|
||||
decl.val,
|
||||
.none,
|
||||
.complete,
|
||||
.{ .decl = decl_index },
|
||||
);
|
||||
try o.writer().writeByte(' ');
|
||||
|
||||
// In case we need to use the header, populate it with a copy of the function
|
||||
|
|
@ -2949,7 +2829,6 @@ pub fn genFunc(f: *Function) !void {
|
|||
|
||||
const main_body = f.air.getMainBody();
|
||||
try genBodyResolveState(f, undefined, &.{}, main_body, false);
|
||||
|
||||
try o.indent_writer.insertNewline();
|
||||
|
||||
// Take advantage of the free_locals map to bucket locals per type. All
|
||||
|
|
@ -3007,20 +2886,25 @@ pub fn genDecl(o: *Object) !void {
|
|||
|
||||
if (!decl_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) return;
|
||||
if (decl.val.getExternFunc(zcu)) |_| {
|
||||
const fwd_decl_writer = o.dg.fwdDeclWriter();
|
||||
try fwd_decl_writer.writeAll("zig_extern ");
|
||||
try o.dg.renderFunctionSignature(fwd_decl_writer, decl_index, .forward, .{ .export_index = 0 });
|
||||
try fwd_decl_writer.writeAll(";\n");
|
||||
try genExports(o);
|
||||
const fwd = o.dg.fwdDeclWriter();
|
||||
try fwd.writeAll("zig_extern ");
|
||||
try o.dg.renderFunctionSignature(
|
||||
fwd,
|
||||
decl.val,
|
||||
decl.alignment,
|
||||
.forward,
|
||||
.{ .@"export" = .{
|
||||
.main_name = decl.name,
|
||||
.extern_name = decl.name,
|
||||
} },
|
||||
);
|
||||
try fwd.writeAll(";\n");
|
||||
} else if (decl.val.getVariable(zcu)) |variable| {
|
||||
try o.dg.renderFwdDecl(decl_index, variable, .final);
|
||||
try genExports(o);
|
||||
try o.dg.renderFwdDecl(decl_index, variable);
|
||||
|
||||
if (variable.is_extern) return;
|
||||
|
||||
const is_global = variable.is_extern or o.dg.declIsGlobal(decl.val);
|
||||
const w = o.writer();
|
||||
if (!is_global) try w.writeAll("static ");
|
||||
if (variable.is_weak_linkage) try w.writeAll("zig_weak_linkage ");
|
||||
if (variable.is_threadlocal and !o.dg.mod.single_threaded) try w.writeAll("zig_threadlocal ");
|
||||
if (decl.@"linksection".toSlice(&zcu.intern_pool)) |s|
|
||||
|
|
@ -3032,46 +2916,27 @@ pub fn genDecl(o: *Object) !void {
|
|||
try w.writeByte(';');
|
||||
try o.indent_writer.insertNewline();
|
||||
} else {
|
||||
const is_global = o.dg.zcu.decl_exports.contains(decl_index);
|
||||
const decl_c_value = .{ .decl = decl_index };
|
||||
try genDeclValue(o, decl.val, is_global, decl_c_value, decl.alignment, decl.@"linksection");
|
||||
try genDeclValue(o, decl.val, decl_c_value, decl.alignment, decl.@"linksection");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn genDeclValue(
|
||||
o: *Object,
|
||||
val: Value,
|
||||
is_global: bool,
|
||||
decl_c_value: CValue,
|
||||
alignment: Alignment,
|
||||
@"linksection": InternPool.OptionalNullTerminatedString,
|
||||
) !void {
|
||||
const zcu = o.dg.zcu;
|
||||
const fwd_decl_writer = o.dg.fwdDeclWriter();
|
||||
|
||||
const ty = val.typeOf(zcu);
|
||||
|
||||
try fwd_decl_writer.writeAll(if (is_global) "zig_extern " else "static ");
|
||||
try o.dg.renderTypeAndName(fwd_decl_writer, ty, decl_c_value, Const, alignment, .complete);
|
||||
switch (o.dg.pass) {
|
||||
.decl => |decl_index| {
|
||||
if (zcu.decl_exports.get(decl_index)) |exports| {
|
||||
const export_name = exports.items[0].opts.name.toSlice(&zcu.intern_pool);
|
||||
if (isMangledIdent(export_name, true)) {
|
||||
try fwd_decl_writer.print(" zig_mangled_final({ }, {s})", .{
|
||||
fmtIdent(export_name), fmtStringLiteral(export_name, null),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
.anon => {},
|
||||
.flush => unreachable,
|
||||
}
|
||||
try fwd_decl_writer.writeAll(";\n");
|
||||
try genExports(o);
|
||||
const fwd = o.dg.fwdDeclWriter();
|
||||
try fwd.writeAll("static ");
|
||||
try o.dg.renderTypeAndName(fwd, ty, decl_c_value, Const, alignment, .complete);
|
||||
try fwd.writeAll(";\n");
|
||||
|
||||
const w = o.writer();
|
||||
if (!is_global) try w.writeAll("static ");
|
||||
if (@"linksection".toSlice(&zcu.intern_pool)) |s|
|
||||
try w.print("zig_linksection({s}) ", .{fmtStringLiteral(s, null)});
|
||||
try o.dg.renderTypeAndName(w, ty, decl_c_value, Const, alignment, .complete);
|
||||
|
|
@ -3080,24 +2945,73 @@ pub fn genDeclValue(
|
|||
try w.writeAll(";\n");
|
||||
}
|
||||
|
||||
pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void {
|
||||
if (true) @panic("TODO jacobly");
|
||||
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
pub fn genExports(dg: *DeclGen, exported: Zcu.Exported, export_indices: []const u32) !void {
|
||||
const zcu = dg.zcu;
|
||||
const decl_index = dg.pass.decl;
|
||||
const decl = zcu.declPtr(decl_index);
|
||||
const writer = dg.fwdDeclWriter();
|
||||
const ip = &zcu.intern_pool;
|
||||
const fwd = dg.fwdDeclWriter();
|
||||
|
||||
switch (decl.typeOf(zcu).zigTypeTag(zcu)) {
|
||||
.Fn => if (dg.declIsGlobal(decl.val)) {
|
||||
try writer.writeAll("zig_extern ");
|
||||
try dg.renderFunctionSignature(writer, dg.pass.decl, .complete, .{ .export_index = 0 });
|
||||
try dg.fwd_decl.appendSlice(";\n");
|
||||
const main_name = zcu.all_exports.items[export_indices[0]].opts.name;
|
||||
try fwd.writeAll("#define ");
|
||||
switch (exported) {
|
||||
.decl_index => |decl_index| try dg.renderDeclName(fwd, decl_index),
|
||||
.value => |value| try DeclGen.renderAnonDeclName(fwd, Value.fromInterned(value)),
|
||||
}
|
||||
try fwd.writeByte(' ');
|
||||
try fwd.print("{ }", .{fmtIdent(main_name.toSlice(ip))});
|
||||
try fwd.writeByte('\n');
|
||||
|
||||
const is_const = switch (ip.indexToKey(exported.getValue(zcu).toIntern())) {
|
||||
.func, .extern_func => return for (export_indices) |export_index| {
|
||||
const @"export" = &zcu.all_exports.items[export_index];
|
||||
try fwd.writeAll("zig_extern ");
|
||||
if (@"export".opts.linkage == .weak) try fwd.writeAll("zig_weak_linkage_fn ");
|
||||
try dg.renderFunctionSignature(
|
||||
fwd,
|
||||
exported.getValue(zcu),
|
||||
exported.getAlign(zcu),
|
||||
.forward,
|
||||
.{ .@"export" = .{
|
||||
.main_name = main_name,
|
||||
.extern_name = @"export".opts.name,
|
||||
} },
|
||||
);
|
||||
try fwd.writeAll(";\n");
|
||||
},
|
||||
else => {},
|
||||
.variable => |variable| variable.is_const,
|
||||
else => true,
|
||||
};
|
||||
for (export_indices) |export_index| {
|
||||
const @"export" = &zcu.all_exports.items[export_index];
|
||||
try fwd.writeAll("zig_extern ");
|
||||
if (@"export".opts.linkage == .weak) try fwd.writeAll("zig_weak_linkage ");
|
||||
const extern_name = @"export".opts.name.toSlice(ip);
|
||||
const is_mangled = isMangledIdent(extern_name, true);
|
||||
const is_export = @"export".opts.name != main_name;
|
||||
try dg.renderTypeAndName(
|
||||
fwd,
|
||||
exported.getValue(zcu).typeOf(zcu),
|
||||
.{ .identifier = extern_name },
|
||||
CQualifiers.init(.{ .@"const" = is_const }),
|
||||
exported.getAlign(zcu),
|
||||
.complete,
|
||||
);
|
||||
if (is_mangled and is_export) {
|
||||
try fwd.print(" zig_mangled_export({ }, {s}, {s})", .{
|
||||
fmtIdent(extern_name),
|
||||
fmtStringLiteral(extern_name, null),
|
||||
fmtStringLiteral(main_name.toSlice(ip), null),
|
||||
});
|
||||
} else if (is_mangled) {
|
||||
try fwd.print(" zig_mangled({ }, {s})", .{
|
||||
fmtIdent(extern_name), fmtStringLiteral(extern_name, null),
|
||||
});
|
||||
} else if (is_export) {
|
||||
try fwd.print(" zig_export({s}, {s})", .{
|
||||
fmtStringLiteral(main_name.toSlice(ip), null),
|
||||
fmtStringLiteral(extern_name, null),
|
||||
});
|
||||
}
|
||||
try fwd.writeAll(";\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4554,7 +4468,7 @@ fn airCall(
|
|||
};
|
||||
};
|
||||
switch (modifier) {
|
||||
.auto, .always_tail => try f.object.dg.renderDeclName(writer, fn_decl, 0),
|
||||
.auto, .always_tail => try f.object.dg.renderDeclName(writer, fn_decl),
|
||||
inline .never_tail, .never_inline => |m| try writer.writeAll(try f.getLazyFnName(
|
||||
@unionInit(LazyFnKey, @tagName(m), fn_decl),
|
||||
@unionInit(LazyFnValue.Data, @tagName(m), {}),
|
||||
|
|
|
|||
|
|
@ -679,7 +679,6 @@ pub const File = struct {
|
|||
if (build_options.only_c) @compileError("unreachable");
|
||||
switch (base.tag) {
|
||||
.plan9,
|
||||
.c,
|
||||
.spirv,
|
||||
.nvptx,
|
||||
=> {},
|
||||
|
|
|
|||
158
src/link/C.zig
158
src/link/C.zig
|
|
@ -39,6 +39,9 @@ anon_decls: std.AutoArrayHashMapUnmanaged(InternPool.Index, DeclBlock) = .{},
|
|||
/// the keys of `anon_decls`.
|
||||
aligned_anon_decls: std.AutoArrayHashMapUnmanaged(InternPool.Index, Alignment) = .{},
|
||||
|
||||
exported_decls: std.AutoArrayHashMapUnmanaged(InternPool.DeclIndex, ExportedBlock) = .{},
|
||||
exported_values: std.AutoArrayHashMapUnmanaged(InternPool.Index, ExportedBlock) = .{},
|
||||
|
||||
/// Optimization, `updateDecl` reuses this buffer rather than creating a new
|
||||
/// one with every call.
|
||||
fwd_decl_buf: std.ArrayListUnmanaged(u8) = .{},
|
||||
|
|
@ -80,6 +83,11 @@ pub const DeclBlock = struct {
|
|||
}
|
||||
};
|
||||
|
||||
/// Per-exported-symbol data.
|
||||
pub const ExportedBlock = struct {
|
||||
fwd_decl: String = String.empty,
|
||||
};
|
||||
|
||||
pub fn getString(this: C, s: String) []const u8 {
|
||||
return this.string_bytes.items[s.start..][0..s.len];
|
||||
}
|
||||
|
|
@ -183,8 +191,6 @@ pub fn updateFunc(
|
|||
air: Air,
|
||||
liveness: Liveness,
|
||||
) !void {
|
||||
if (true) @panic("TODO jacobly");
|
||||
|
||||
const gpa = self.base.comp.gpa;
|
||||
|
||||
const func = zcu.funcInfo(func_index);
|
||||
|
|
@ -240,9 +246,13 @@ pub fn updateFunc(
|
|||
function.deinit();
|
||||
}
|
||||
|
||||
try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
|
||||
codegen.genFunc(&function) catch |err| switch (err) {
|
||||
error.AnalysisFail => {
|
||||
try zcu.failed_decls.put(gpa, decl_index, function.object.dg.error_msg.?);
|
||||
zcu.failed_analysis.putAssumeCapacityNoClobber(
|
||||
InternPool.AnalUnit.wrap(.{ .decl = decl_index }),
|
||||
function.object.dg.error_msg.?,
|
||||
);
|
||||
return;
|
||||
},
|
||||
else => |e| return e,
|
||||
|
|
@ -252,8 +262,6 @@ pub fn updateFunc(
|
|||
}
|
||||
|
||||
fn updateAnonDecl(self: *C, zcu: *Zcu, i: usize) !void {
|
||||
if (true) @panic("TODO jacobly");
|
||||
|
||||
const gpa = self.base.comp.gpa;
|
||||
const anon_decl = self.anon_decls.keys()[i];
|
||||
|
||||
|
|
@ -292,7 +300,7 @@ fn updateAnonDecl(self: *C, zcu: *Zcu, i: usize) !void {
|
|||
|
||||
const c_value: codegen.CValue = .{ .constant = Value.fromInterned(anon_decl) };
|
||||
const alignment: Alignment = self.aligned_anon_decls.get(anon_decl) orelse .none;
|
||||
codegen.genDeclValue(&object, c_value.constant, false, c_value, alignment, .none) catch |err| switch (err) {
|
||||
codegen.genDeclValue(&object, c_value.constant, c_value, alignment, .none) catch |err| switch (err) {
|
||||
error.AnalysisFail => {
|
||||
@panic("TODO: C backend AnalysisFail on anonymous decl");
|
||||
//try zcu.failed_decls.put(gpa, decl_index, object.dg.error_msg.?);
|
||||
|
|
@ -310,8 +318,6 @@ fn updateAnonDecl(self: *C, zcu: *Zcu, i: usize) !void {
|
|||
}
|
||||
|
||||
pub fn updateDecl(self: *C, zcu: *Zcu, decl_index: InternPool.DeclIndex) !void {
|
||||
if (true) @panic("TODO jacobly");
|
||||
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
|
|
@ -357,9 +363,13 @@ pub fn updateDecl(self: *C, zcu: *Zcu, decl_index: InternPool.DeclIndex) !void {
|
|||
code.* = object.code.moveToUnmanaged();
|
||||
}
|
||||
|
||||
try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
|
||||
codegen.genDecl(&object) catch |err| switch (err) {
|
||||
error.AnalysisFail => {
|
||||
try zcu.failed_decls.put(gpa, decl_index, object.dg.error_msg.?);
|
||||
zcu.failed_analysis.putAssumeCapacityNoClobber(
|
||||
InternPool.AnalUnit.wrap(.{ .decl = decl_index }),
|
||||
object.dg.error_msg.?,
|
||||
);
|
||||
return;
|
||||
},
|
||||
else => |e| return e,
|
||||
|
|
@ -396,8 +406,6 @@ fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) {
|
|||
}
|
||||
|
||||
pub fn flushModule(self: *C, arena: Allocator, prog_node: std.Progress.Node) !void {
|
||||
if (true) @panic("TODO jacobly");
|
||||
|
||||
_ = arena; // Has the same lifetime as the call to Compilation.update.
|
||||
|
||||
const tracy = trace(@src());
|
||||
|
|
@ -460,26 +468,39 @@ pub fn flushModule(self: *C, arena: Allocator, prog_node: std.Progress.Node) !vo
|
|||
var export_names: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
|
||||
defer export_names.deinit(gpa);
|
||||
try export_names.ensureTotalCapacity(gpa, @intCast(zcu.single_exports.count()));
|
||||
for (zcu.single_exports.values()) |export_idx| {
|
||||
export_names.putAssumeCapacity(gpa, zcu.all_exports.items[export_idx].opts.name, {});
|
||||
for (zcu.single_exports.values()) |export_index| {
|
||||
export_names.putAssumeCapacity(zcu.all_exports.items[export_index].opts.name, {});
|
||||
}
|
||||
for (zcu.multi_exports.values()) |info| {
|
||||
try export_names.ensureUnusedCapacity(info.len);
|
||||
for (zcu.all_exports.items[info.index..][0..info.len]) |export_idx| {
|
||||
export_names.putAssumeCapacity(gpa, zcu.all_exports.items[export_idx].opts.name, {});
|
||||
try export_names.ensureUnusedCapacity(gpa, info.len);
|
||||
for (zcu.all_exports.items[info.index..][0..info.len]) |@"export"| {
|
||||
export_names.putAssumeCapacity(@"export".opts.name, {});
|
||||
}
|
||||
}
|
||||
|
||||
for (self.anon_decls.values()) |*decl_block| {
|
||||
try self.flushDeclBlock(zcu, zcu.root_mod, &f, decl_block, export_names, .none);
|
||||
}
|
||||
for (self.anon_decls.keys(), self.anon_decls.values()) |value, *decl_block| try self.flushDeclBlock(
|
||||
zcu,
|
||||
zcu.root_mod,
|
||||
&f,
|
||||
decl_block,
|
||||
self.exported_values.getPtr(value),
|
||||
export_names,
|
||||
.none,
|
||||
);
|
||||
|
||||
for (self.decl_table.keys(), self.decl_table.values()) |decl_index, *decl_block| {
|
||||
const decl = zcu.declPtr(decl_index);
|
||||
assert(decl.has_tv);
|
||||
const extern_symbol_name = if (decl.isExtern(zcu)) decl.name.toOptional() else .none;
|
||||
const extern_name = if (decl.isExtern(zcu)) decl.name.toOptional() else .none;
|
||||
const mod = zcu.namespacePtr(decl.src_namespace).file_scope.mod;
|
||||
try self.flushDeclBlock(zcu, mod, &f, decl_block, export_names, extern_symbol_name);
|
||||
try self.flushDeclBlock(
|
||||
zcu,
|
||||
mod,
|
||||
&f,
|
||||
decl_block,
|
||||
self.exported_decls.getPtr(decl_index),
|
||||
export_names,
|
||||
extern_name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -512,12 +533,16 @@ pub fn flushModule(self: *C, arena: Allocator, prog_node: std.Progress.Node) !vo
|
|||
f.file_size += lazy_fwd_decl_len;
|
||||
|
||||
// Now the code.
|
||||
const anon_decl_values = self.anon_decls.values();
|
||||
const decl_values = self.decl_table.values();
|
||||
try f.all_buffers.ensureUnusedCapacity(gpa, 1 + anon_decl_values.len + decl_values.len);
|
||||
try f.all_buffers.ensureUnusedCapacity(gpa, 1 + (self.anon_decls.count() + self.decl_table.count()) * 2);
|
||||
f.appendBufAssumeCapacity(self.lazy_code_buf.items);
|
||||
for (anon_decl_values) |db| f.appendBufAssumeCapacity(self.getString(db.code));
|
||||
for (decl_values) |db| f.appendBufAssumeCapacity(self.getString(db.code));
|
||||
for (self.anon_decls.keys(), self.anon_decls.values()) |anon_decl, decl_block| f.appendCodeAssumeCapacity(
|
||||
self.exported_values.contains(anon_decl),
|
||||
self.getString(decl_block.code),
|
||||
);
|
||||
for (self.decl_table.keys(), self.decl_table.values()) |decl_index, decl_block| f.appendCodeAssumeCapacity(
|
||||
self.exported_decls.contains(decl_index),
|
||||
self.getString(decl_block.code),
|
||||
);
|
||||
|
||||
const file = self.base.file.?;
|
||||
try file.setEndPos(f.file_size);
|
||||
|
|
@ -547,6 +572,12 @@ const Flush = struct {
|
|||
f.file_size += buf.len;
|
||||
}
|
||||
|
||||
fn appendCodeAssumeCapacity(f: *Flush, is_extern: bool, code: []const u8) void {
|
||||
if (code.len == 0) return;
|
||||
f.appendBufAssumeCapacity(if (is_extern) "\nzig_extern " else "\nstatic ");
|
||||
f.appendBufAssumeCapacity(code);
|
||||
}
|
||||
|
||||
fn deinit(f: *Flush, gpa: Allocator) void {
|
||||
f.all_buffers.deinit(gpa);
|
||||
f.asm_buf.deinit(gpa);
|
||||
|
|
@ -734,19 +765,20 @@ fn flushDeclBlock(
|
|||
zcu: *Zcu,
|
||||
mod: *Module,
|
||||
f: *Flush,
|
||||
decl_block: *DeclBlock,
|
||||
decl_block: *const DeclBlock,
|
||||
exported_block: ?*const ExportedBlock,
|
||||
export_names: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void),
|
||||
extern_symbol_name: InternPool.OptionalNullTerminatedString,
|
||||
extern_name: InternPool.OptionalNullTerminatedString,
|
||||
) FlushDeclError!void {
|
||||
const gpa = self.base.comp.gpa;
|
||||
try self.flushLazyFns(zcu, mod, f, &decl_block.ctype_pool, decl_block.lazy_fns);
|
||||
try f.all_buffers.ensureUnusedCapacity(gpa, 1);
|
||||
fwd_decl: {
|
||||
if (extern_symbol_name.unwrap()) |name| {
|
||||
if (export_names.contains(name)) break :fwd_decl;
|
||||
}
|
||||
f.appendBufAssumeCapacity(self.getString(decl_block.fwd_decl));
|
||||
}
|
||||
// avoid emitting extern decls that are already exported
|
||||
if (extern_name.unwrap()) |name| if (export_names.contains(name)) return;
|
||||
f.appendBufAssumeCapacity(self.getString(if (exported_block) |exported|
|
||||
exported.fwd_decl
|
||||
else
|
||||
decl_block.fwd_decl));
|
||||
}
|
||||
|
||||
pub fn flushEmitH(zcu: *Zcu) !void {
|
||||
|
|
@ -798,8 +830,56 @@ pub fn updateExports(
|
|||
exported: Zcu.Exported,
|
||||
export_indices: []const u32,
|
||||
) !void {
|
||||
_ = self;
|
||||
_ = zcu;
|
||||
_ = exported;
|
||||
_ = export_indices;
|
||||
const gpa = self.base.comp.gpa;
|
||||
const mod, const pass: codegen.DeclGen.Pass, const decl_block, const exported_block = switch (exported) {
|
||||
.decl_index => |decl_index| .{
|
||||
zcu.namespacePtr(zcu.declPtr(decl_index).src_namespace).file_scope.mod,
|
||||
.{ .decl = decl_index },
|
||||
self.decl_table.getPtr(decl_index).?,
|
||||
(try self.exported_decls.getOrPut(gpa, decl_index)).value_ptr,
|
||||
},
|
||||
.value => |value| .{
|
||||
zcu.root_mod,
|
||||
.{ .anon = value },
|
||||
self.anon_decls.getPtr(value).?,
|
||||
(try self.exported_values.getOrPut(gpa, value)).value_ptr,
|
||||
},
|
||||
};
|
||||
const ctype_pool = &decl_block.ctype_pool;
|
||||
const fwd_decl = &self.fwd_decl_buf;
|
||||
fwd_decl.clearRetainingCapacity();
|
||||
var dg: codegen.DeclGen = .{
|
||||
.gpa = gpa,
|
||||
.zcu = zcu,
|
||||
.mod = mod,
|
||||
.error_msg = null,
|
||||
.pass = pass,
|
||||
.is_naked_fn = false,
|
||||
.fwd_decl = fwd_decl.toManaged(gpa),
|
||||
.ctype_pool = decl_block.ctype_pool,
|
||||
.scratch = .{},
|
||||
.anon_decl_deps = .{},
|
||||
.aligned_anon_decls = .{},
|
||||
};
|
||||
defer {
|
||||
assert(dg.anon_decl_deps.count() == 0);
|
||||
assert(dg.aligned_anon_decls.count() == 0);
|
||||
fwd_decl.* = dg.fwd_decl.moveToUnmanaged();
|
||||
ctype_pool.* = dg.ctype_pool.move();
|
||||
ctype_pool.freeUnusedCapacity(gpa);
|
||||
dg.scratch.deinit(gpa);
|
||||
}
|
||||
try codegen.genExports(&dg, exported, export_indices);
|
||||
exported_block.* = .{ .fwd_decl = try self.addString(dg.fwd_decl.items) };
|
||||
}
|
||||
|
||||
pub fn deleteExport(
|
||||
self: *C,
|
||||
exported: Zcu.Exported,
|
||||
_: InternPool.NullTerminatedString,
|
||||
) void {
|
||||
switch (exported) {
|
||||
.decl_index => |decl_index| _ = self.exported_decls.swapRemove(decl_index),
|
||||
.value => |value| _ = self.exported_values.swapRemove(value),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue