Files
zig/src/glibc.zig
Andrew Kelley 572cb24d1a progress towards semantic error serialization
Introduces std.zig.ErrorBundle which is a trivially serializeable set
of compilation errors. This is in the standard library so that both
the compiler and the build runner can use it. The idea is they will
use it to communicate compilation errors over a binary protocol.

The binary encoding of ErrorBundle is a bit problematic - I got a little
too aggressive with compaction. I need to change it in a follow-up
commit to use some indirection in the error message list, otherwise
iteration is too unergonomic. In fact it's so problematic right now that
the logic getAllErrorsAlloc() actually fails to produce a viable
ErrorBundle because it puts SourceLocation data in between the root
level ErrorMessage data.

This commit has a simplification - redundant logic for rendering AST
errors to stderr has been removed in favor of moving the logic for
lowering AST errors into AstGen. So even if we get parse errors, the
errors will get lowered into ZIR before being reported. I believe this
will be useful when working on --autofix. Either way, some redundant
brittle logic was happily deleted.

In Compilation, updateSubCompilation() is improved to properly perform
error reporting when a sub-compilation object fails. It no longer dumps
directly to stderr; instead it populates an ErrorBundle object, which
gets added to the parent one during getAllErrorsAlloc().

In package fetching code, instead of dumping directly to stderr, it now
populates an ErrorBundle object, and gets properly reported at the CLI
layer of abstraction.
2023-03-15 10:48:12 -07:00

1118 lines
44 KiB
Zig

const std = @import("std");
const Allocator = std.mem.Allocator;
const mem = std.mem;
const log = std.log;
const fs = std.fs;
const path = fs.path;
const assert = std.debug.assert;
const Version = std.builtin.Version;
const target_util = @import("target.zig");
const Compilation = @import("Compilation.zig");
const build_options = @import("build_options");
const trace = @import("tracy.zig").trace;
const Cache = std.Build.Cache;
const Package = @import("Package.zig");
pub const Lib = struct {
name: []const u8,
sover: u8,
};
pub const ABI = struct {
all_versions: []const Version,
all_targets: []const target_util.ArchOsAbi,
/// The bytes from the file verbatim, starting from the u16 number
/// of function inclusions.
inclusions: []const u8,
arena_state: std.heap.ArenaAllocator.State,
pub fn destroy(abi: *ABI, gpa: Allocator) void {
abi.arena_state.promote(gpa).deinit();
}
};
// The order of the elements in this array defines the linking order.
pub const libs = [_]Lib{
.{ .name = "m", .sover = 6 },
.{ .name = "pthread", .sover = 0 },
.{ .name = "c", .sover = 6 },
.{ .name = "dl", .sover = 2 },
.{ .name = "rt", .sover = 1 },
.{ .name = "ld", .sover = 2 },
.{ .name = "util", .sover = 1 },
.{ .name = "resolv", .sover = 2 },
};
pub const LoadMetaDataError = error{
/// The files that ship with the Zig compiler were unable to be read, or otherwise had malformed data.
ZigInstallationCorrupt,
OutOfMemory,
};
pub const abilists_path = "libc" ++ path.sep_str ++ "glibc" ++ path.sep_str ++ "abilists";
pub const abilists_max_size = 800 * 1024; // Bigger than this and something is definitely borked.
/// This function will emit a log error when there is a problem with the zig
/// installation and then return `error.ZigInstallationCorrupt`.
pub fn loadMetaData(gpa: Allocator, contents: []const u8) LoadMetaDataError!*ABI {
const tracy = trace(@src());
defer tracy.end();
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
errdefer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var index: usize = 0;
{
const libs_len = contents[index];
index += 1;
var i: u8 = 0;
while (i < libs_len) : (i += 1) {
const lib_name = mem.sliceTo(contents[index..], 0);
index += lib_name.len + 1;
if (i >= libs.len or !mem.eql(u8, libs[i].name, lib_name)) {
log.err("libc" ++ path.sep_str ++ "glibc" ++ path.sep_str ++
"abilists: invalid library name or index ({d}): '{s}'", .{ i, lib_name });
return error.ZigInstallationCorrupt;
}
}
}
const versions = b: {
const versions_len = contents[index];
index += 1;
const versions = try arena.alloc(Version, versions_len);
var i: u8 = 0;
while (i < versions.len) : (i += 1) {
versions[i] = .{
.major = contents[index + 0],
.minor = contents[index + 1],
.patch = contents[index + 2],
};
index += 3;
}
break :b versions;
};
const targets = b: {
const targets_len = contents[index];
index += 1;
const targets = try arena.alloc(target_util.ArchOsAbi, targets_len);
var i: u8 = 0;
while (i < targets.len) : (i += 1) {
const target_name = mem.sliceTo(contents[index..], 0);
index += target_name.len + 1;
var component_it = mem.tokenize(u8, target_name, "-");
const arch_name = component_it.next() orelse {
log.err("abilists: expected arch name", .{});
return error.ZigInstallationCorrupt;
};
const os_name = component_it.next() orelse {
log.err("abilists: expected OS name", .{});
return error.ZigInstallationCorrupt;
};
const abi_name = component_it.next() orelse {
log.err("abilists: expected ABI name", .{});
return error.ZigInstallationCorrupt;
};
const arch_tag = std.meta.stringToEnum(std.Target.Cpu.Arch, arch_name) orelse {
log.err("abilists: unrecognized arch: '{s}'", .{arch_name});
return error.ZigInstallationCorrupt;
};
if (!mem.eql(u8, os_name, "linux")) {
log.err("abilists: expected OS 'linux', found '{s}'", .{os_name});
return error.ZigInstallationCorrupt;
}
const abi_tag = std.meta.stringToEnum(std.Target.Abi, abi_name) orelse {
log.err("abilists: unrecognized ABI: '{s}'", .{abi_name});
return error.ZigInstallationCorrupt;
};
targets[i] = .{
.arch = arch_tag,
.os = .linux,
.abi = abi_tag,
};
}
break :b targets;
};
const abi = try arena.create(ABI);
abi.* = .{
.all_versions = versions,
.all_targets = targets,
.inclusions = contents[index..],
.arena_state = arena_allocator.state,
};
return abi;
}
pub const CRTFile = enum {
crti_o,
crtn_o,
scrt1_o,
libc_nonshared_a,
};
pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
if (!build_options.have_llvm) {
return error.ZigCompilerNotBuiltWithLLVMExtensions;
}
const gpa = comp.gpa;
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
const target = comp.getTarget();
const target_ver = target.os.version_range.linux.glibc;
const start_old_init_fini = target_ver.order(.{ .major = 2, .minor = 33 }) != .gt;
// In all cases in this function, we add the C compiler flags to
// cache_exempt_flags rather than extra_flags, because these arguments
// depend on only properties that are already covered by the cache
// manifest. Including these arguments in the cache could only possibly
// waste computation and create false negatives.
switch (crt_file) {
.crti_o => {
var args = std.ArrayList([]const u8).init(arena);
try add_include_dirs(comp, arena, &args);
try args.appendSlice(&[_][]const u8{
"-D_LIBC_REENTRANT",
"-include",
try lib_path(comp, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-modules.h"),
"-DMODULE_NAME=libc",
"-Wno-nonportable-include-path",
"-include",
try lib_path(comp, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-symbols.h"),
"-DTOP_NAMESPACE=glibc",
"-DASSEMBLER",
"-Wa,--noexecstack",
});
return comp.build_crt_file("crti", .Obj, .@"glibc crti.o", &[1]Compilation.CSourceFile{
.{
.src_path = try start_asm_path(comp, arena, "crti.S"),
.cache_exempt_flags = args.items,
},
});
},
.crtn_o => {
var args = std.ArrayList([]const u8).init(arena);
try add_include_dirs(comp, arena, &args);
try args.appendSlice(&[_][]const u8{
"-D_LIBC_REENTRANT",
"-DMODULE_NAME=libc",
"-include",
try lib_path(comp, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-symbols.h"),
"-DTOP_NAMESPACE=glibc",
"-DASSEMBLER",
"-Wa,--noexecstack",
});
return comp.build_crt_file("crtn", .Obj, .@"glibc crtn.o", &[1]Compilation.CSourceFile{
.{
.src_path = try start_asm_path(comp, arena, "crtn.S"),
.cache_exempt_flags = args.items,
},
});
},
.scrt1_o => {
const start_o: Compilation.CSourceFile = blk: {
var args = std.ArrayList([]const u8).init(arena);
try add_include_dirs(comp, arena, &args);
try args.appendSlice(&[_][]const u8{
"-D_LIBC_REENTRANT",
"-include",
try lib_path(comp, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-modules.h"),
"-DMODULE_NAME=libc",
"-Wno-nonportable-include-path",
"-include",
try lib_path(comp, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-symbols.h"),
"-DPIC",
"-DSHARED",
"-DTOP_NAMESPACE=glibc",
"-DASSEMBLER",
"-Wa,--noexecstack",
});
const src_path = if (start_old_init_fini) "start-2.33.S" else "start.S";
break :blk .{
.src_path = try start_asm_path(comp, arena, src_path),
.cache_exempt_flags = args.items,
};
};
const abi_note_o: Compilation.CSourceFile = blk: {
var args = std.ArrayList([]const u8).init(arena);
try args.appendSlice(&[_][]const u8{
"-I",
try lib_path(comp, arena, lib_libc_glibc ++ "csu"),
});
try add_include_dirs(comp, arena, &args);
try args.appendSlice(&[_][]const u8{
"-D_LIBC_REENTRANT",
"-DMODULE_NAME=libc",
"-DTOP_NAMESPACE=glibc",
"-DASSEMBLER",
"-Wa,--noexecstack",
});
break :blk .{
.src_path = try lib_path(comp, arena, lib_libc_glibc ++ "csu" ++ path.sep_str ++ "abi-note.S"),
.cache_exempt_flags = args.items,
};
};
return comp.build_crt_file("Scrt1", .Obj, .@"glibc Scrt1.o", &[_]Compilation.CSourceFile{ start_o, abi_note_o });
},
.libc_nonshared_a => {
const s = path.sep_str;
const linux_prefix = lib_libc_glibc ++
"sysdeps" ++ s ++ "unix" ++ s ++ "sysv" ++ s ++ "linux" ++ s;
const Flavor = enum { nonshared, shared };
const Dep = struct {
path: []const u8,
flavor: Flavor = .shared,
exclude: bool = false,
};
const deps = [_]Dep{
.{
.path = lib_libc_glibc ++ "stdlib" ++ s ++ "atexit.c",
.flavor = .nonshared,
},
.{
.path = lib_libc_glibc ++ "stdlib" ++ s ++ "at_quick_exit.c",
.flavor = .nonshared,
},
.{
.path = lib_libc_glibc ++ "sysdeps" ++ s ++ "pthread" ++ s ++ "pthread_atfork.c",
.flavor = .nonshared,
},
.{
.path = lib_libc_glibc ++ "debug" ++ s ++ "stack_chk_fail_local.c",
.flavor = .nonshared,
},
.{ .path = lib_libc_glibc ++ "csu" ++ s ++ "errno.c" },
.{
.path = lib_libc_glibc ++ "csu" ++ s ++ "elf-init-2.33.c",
.exclude = !start_old_init_fini,
},
.{ .path = linux_prefix ++ "stat.c" },
.{ .path = linux_prefix ++ "fstat.c" },
.{ .path = linux_prefix ++ "lstat.c" },
.{ .path = linux_prefix ++ "stat64.c" },
.{ .path = linux_prefix ++ "fstat64.c" },
.{ .path = linux_prefix ++ "lstat64.c" },
.{ .path = linux_prefix ++ "fstatat.c" },
.{ .path = linux_prefix ++ "fstatat64.c" },
.{ .path = linux_prefix ++ "mknodat.c" },
.{ .path = lib_libc_glibc ++ "io" ++ s ++ "mknod.c" },
.{ .path = linux_prefix ++ "stat_t64_cp.c" },
};
var files_buf: [deps.len]Compilation.CSourceFile = undefined;
var files_index: usize = 0;
for (deps) |dep| {
if (dep.exclude) continue;
var args = std.ArrayList([]const u8).init(arena);
try args.appendSlice(&[_][]const u8{
"-std=gnu11",
"-fgnu89-inline",
"-fmerge-all-constants",
// glibc sets this flag but clang does not support it.
// "-frounding-math",
"-fno-stack-protector",
"-fno-common",
"-fmath-errno",
"-ftls-model=initial-exec",
"-Wno-ignored-attributes",
});
try add_include_dirs(comp, arena, &args);
if (target.cpu.arch == .x86) {
// This prevents i386/sysdep.h from trying to do some
// silly and unnecessary inline asm hack that uses weird
// syntax that clang does not support.
try args.append("-DCAN_USE_REGISTER_ASM_EBP");
}
const shared_def = switch (dep.flavor) {
.nonshared => "-DLIBC_NONSHARED=1",
// glibc passes `-DSHARED` for these. However, empirically if
// we do that here we will see undefined symbols such as `__GI_memcpy`.
// So we pass the same thing as for nonshared.
.shared => "-DLIBC_NONSHARED=1",
};
try args.appendSlice(&[_][]const u8{
"-D_LIBC_REENTRANT",
"-include",
try lib_path(comp, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-modules.h"),
"-DMODULE_NAME=libc",
"-Wno-nonportable-include-path",
"-include",
try lib_path(comp, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-symbols.h"),
"-DPIC",
shared_def,
"-DTOP_NAMESPACE=glibc",
});
files_buf[files_index] = .{
.src_path = try lib_path(comp, arena, dep.path),
.cache_exempt_flags = args.items,
};
files_index += 1;
}
const files = files_buf[0..files_index];
return comp.build_crt_file("c_nonshared", .Lib, .@"glibc libc_nonshared.a", files);
},
}
}
fn start_asm_path(comp: *Compilation, arena: Allocator, basename: []const u8) ![]const u8 {
const arch = comp.getTarget().cpu.arch;
const is_ppc = arch == .powerpc or arch == .powerpc64 or arch == .powerpc64le;
const is_aarch64 = arch == .aarch64 or arch == .aarch64_be;
const is_sparc = arch == .sparc or arch == .sparcel or arch == .sparc64;
const is_64 = arch.ptrBitWidth() == 64;
const s = path.sep_str;
var result = std.ArrayList(u8).init(arena);
try result.appendSlice(comp.zig_lib_directory.path.?);
try result.appendSlice(s ++ "libc" ++ s ++ "glibc" ++ s ++ "sysdeps" ++ s);
if (is_sparc) {
if (mem.eql(u8, basename, "crti.S") or mem.eql(u8, basename, "crtn.S")) {
try result.appendSlice("sparc");
} else {
if (is_64) {
try result.appendSlice("sparc" ++ s ++ "sparc64");
} else {
try result.appendSlice("sparc" ++ s ++ "sparc32");
}
}
} else if (arch.isARM()) {
try result.appendSlice("arm");
} else if (arch.isMIPS()) {
if (!mem.eql(u8, basename, "crti.S") and !mem.eql(u8, basename, "crtn.S")) {
try result.appendSlice("mips");
} else {
if (is_64) {
const abi_dir = if (comp.getTarget().abi == .gnuabin32)
"n32"
else
"n64";
try result.appendSlice("mips" ++ s ++ "mips64" ++ s);
try result.appendSlice(abi_dir);
} else {
try result.appendSlice("mips" ++ s ++ "mips32");
}
}
} else if (arch == .x86_64) {
try result.appendSlice("x86_64");
} else if (arch == .x86) {
try result.appendSlice("i386");
} else if (is_aarch64) {
try result.appendSlice("aarch64");
} else if (arch.isRISCV()) {
try result.appendSlice("riscv");
} else if (is_ppc) {
if (is_64) {
try result.appendSlice("powerpc" ++ s ++ "powerpc64");
} else {
try result.appendSlice("powerpc" ++ s ++ "powerpc32");
}
}
try result.appendSlice(s);
try result.appendSlice(basename);
return result.items;
}
fn add_include_dirs(comp: *Compilation, arena: Allocator, args: *std.ArrayList([]const u8)) error{OutOfMemory}!void {
const target = comp.getTarget();
const arch = target.cpu.arch;
const opt_nptl: ?[]const u8 = if (target.os.tag == .linux) "nptl" else "htl";
const s = path.sep_str;
try args.append("-I");
try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "include"));
if (target.os.tag == .linux) {
try add_include_dirs_arch(arena, args, arch, null, try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix" ++ s ++ "sysv" ++ s ++ "linux"));
}
if (opt_nptl) |nptl| {
try add_include_dirs_arch(arena, args, arch, nptl, try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps"));
}
if (target.os.tag == .linux) {
try args.append("-I");
try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++
"unix" ++ s ++ "sysv" ++ s ++ "linux" ++ s ++ "generic"));
try args.append("-I");
try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++
"unix" ++ s ++ "sysv" ++ s ++ "linux" ++ s ++ "include"));
try args.append("-I");
try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++
"unix" ++ s ++ "sysv" ++ s ++ "linux"));
}
if (opt_nptl) |nptl| {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ comp.zig_lib_directory.path.?, lib_libc_glibc ++ "sysdeps", nptl }));
}
try args.append("-I");
try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "pthread"));
try args.append("-I");
try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix" ++ s ++ "sysv"));
try add_include_dirs_arch(arena, args, arch, null, try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix"));
try args.append("-I");
try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix"));
try add_include_dirs_arch(arena, args, arch, null, try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps"));
try args.append("-I");
try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "generic"));
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ comp.zig_lib_directory.path.?, lib_libc ++ "glibc" }));
try args.append("-I");
try args.append(try std.fmt.allocPrint(arena, "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-{s}", .{
comp.zig_lib_directory.path.?, @tagName(arch), @tagName(target.os.tag), @tagName(target.abi),
}));
try args.append("-I");
try args.append(try lib_path(comp, arena, lib_libc ++ "include" ++ s ++ "generic-glibc"));
const arch_name = target_util.osArchName(target);
try args.append("-I");
try args.append(try std.fmt.allocPrint(arena, "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-linux-any", .{
comp.zig_lib_directory.path.?, arch_name,
}));
try args.append("-I");
try args.append(try lib_path(comp, arena, lib_libc ++ "include" ++ s ++ "any-linux-any"));
}
fn add_include_dirs_arch(
arena: Allocator,
args: *std.ArrayList([]const u8),
arch: std.Target.Cpu.Arch,
opt_nptl: ?[]const u8,
dir: []const u8,
) error{OutOfMemory}!void {
const is_x86 = arch == .x86 or arch == .x86_64;
const is_aarch64 = arch == .aarch64 or arch == .aarch64_be;
const is_ppc = arch == .powerpc or arch == .powerpc64 or arch == .powerpc64le;
const is_sparc = arch == .sparc or arch == .sparcel or arch == .sparc64;
const is_64 = arch.ptrBitWidth() == 64;
const s = path.sep_str;
if (is_x86) {
if (arch == .x86_64) {
if (opt_nptl) |nptl| {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "x86_64", nptl }));
} else {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "x86_64" }));
}
} else if (arch == .x86) {
if (opt_nptl) |nptl| {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "i386", nptl }));
} else {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "i386" }));
}
}
if (opt_nptl) |nptl| {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "x86", nptl }));
} else {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "x86" }));
}
} else if (arch.isARM()) {
if (opt_nptl) |nptl| {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "arm", nptl }));
} else {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "arm" }));
}
} else if (arch.isMIPS()) {
if (opt_nptl) |nptl| {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "mips", nptl }));
} else {
if (is_64) {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "mips" ++ s ++ "mips64" }));
} else {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "mips" ++ s ++ "mips32" }));
}
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "mips" }));
}
} else if (is_sparc) {
if (opt_nptl) |nptl| {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "sparc", nptl }));
} else {
if (is_64) {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "sparc" ++ s ++ "sparc64" }));
} else {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "sparc" ++ s ++ "sparc32" }));
}
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "sparc" }));
}
} else if (is_aarch64) {
if (opt_nptl) |nptl| {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "aarch64", nptl }));
} else {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "aarch64" }));
}
} else if (is_ppc) {
if (opt_nptl) |nptl| {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "powerpc", nptl }));
} else {
if (is_64) {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "powerpc" ++ s ++ "powerpc64" }));
} else {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "powerpc" ++ s ++ "powerpc32" }));
}
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "powerpc" }));
}
} else if (arch.isRISCV()) {
if (opt_nptl) |nptl| {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "riscv", nptl }));
} else {
try args.append("-I");
try args.append(try path.join(arena, &[_][]const u8{ dir, "riscv" }));
}
}
}
fn path_from_lib(comp: *Compilation, arena: Allocator, sub_path: []const u8) ![]const u8 {
return path.join(arena, &[_][]const u8{ comp.zig_lib_directory.path.?, sub_path });
}
const lib_libc = "libc" ++ path.sep_str;
const lib_libc_glibc = lib_libc ++ "glibc" ++ path.sep_str;
fn lib_path(comp: *Compilation, arena: Allocator, sub_path: []const u8) ![]const u8 {
return path.join(arena, &[_][]const u8{ comp.zig_lib_directory.path.?, sub_path });
}
pub const BuiltSharedObjects = struct {
lock: Cache.Lock,
dir_path: []u8,
pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void {
self.lock.release();
gpa.free(self.dir_path);
self.* = undefined;
}
};
const all_map_basename = "all.map";
pub fn buildSharedObjects(comp: *Compilation) !void {
const tracy = trace(@src());
defer tracy.end();
if (!build_options.have_llvm) {
return error.ZigCompilerNotBuiltWithLLVMExtensions;
}
var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
const target = comp.getTarget();
const target_version = target.os.version_range.linux.glibc;
// Use the global cache directory.
var cache: Cache = .{
.gpa = comp.gpa,
.manifest_dir = try comp.global_cache_directory.handle.makeOpenPath("h", .{}),
};
cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
cache.addPrefix(comp.zig_lib_directory);
cache.addPrefix(comp.global_cache_directory);
defer cache.manifest_dir.close();
var man = cache.obtain();
defer man.deinit();
man.hash.addBytes(build_options.version);
man.hash.add(target.cpu.arch);
man.hash.add(target.abi);
man.hash.add(target_version);
const full_abilists_path = try comp.zig_lib_directory.join(arena, &.{abilists_path});
const abilists_index = try man.addFile(full_abilists_path, abilists_max_size);
if (try man.hit()) {
const digest = man.final();
assert(comp.glibc_so_files == null);
comp.glibc_so_files = BuiltSharedObjects{
.lock = man.toOwnedLock(),
.dir_path = try comp.global_cache_directory.join(comp.gpa, &.{ "o", &digest }),
};
return;
}
const digest = man.final();
const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
var o_directory: Compilation.Directory = .{
.handle = try comp.global_cache_directory.handle.makeOpenPath(o_sub_path, .{}),
.path = try comp.global_cache_directory.join(arena, &.{o_sub_path}),
};
defer o_directory.handle.close();
const abilists_contents = man.files.items[abilists_index].contents.?;
const metadata = try loadMetaData(comp.gpa, abilists_contents);
defer metadata.destroy(comp.gpa);
const target_targ_index = for (metadata.all_targets, 0..) |targ, i| {
if (targ.arch == target.cpu.arch and
targ.os == target.os.tag and
targ.abi == target.abi)
{
break i;
}
} else {
unreachable; // target_util.available_libcs prevents us from getting here
};
const target_ver_index = for (metadata.all_versions, 0..) |ver, i| {
switch (ver.order(target_version)) {
.eq => break i,
.lt => continue,
.gt => {
// TODO Expose via compile error mechanism instead of log.
log.warn("invalid target glibc version: {}", .{target_version});
return error.InvalidTargetGLibCVersion;
},
}
} else blk: {
const latest_index = metadata.all_versions.len - 1;
log.warn("zig cannot build new glibc version {}; providing instead {}", .{
target_version, metadata.all_versions[latest_index],
});
break :blk latest_index;
};
{
var map_contents = std.ArrayList(u8).init(arena);
for (metadata.all_versions[0 .. target_ver_index + 1]) |ver| {
if (ver.patch == 0) {
try map_contents.writer().print("GLIBC_{d}.{d} {{ }};\n", .{ ver.major, ver.minor });
} else {
try map_contents.writer().print("GLIBC_{d}.{d}.{d} {{ }};\n", .{ ver.major, ver.minor, ver.patch });
}
}
try o_directory.handle.writeFile(all_map_basename, map_contents.items);
map_contents.deinit(); // The most recent allocation of an arena can be freed :)
}
var stubs_asm = std.ArrayList(u8).init(comp.gpa);
defer stubs_asm.deinit();
for (libs, 0..) |lib, lib_i| {
stubs_asm.shrinkRetainingCapacity(0);
try stubs_asm.appendSlice(".text\n");
var inc_i: usize = 0;
const fn_inclusions_len = mem.readIntLittle(u16, metadata.inclusions[inc_i..][0..2]);
inc_i += 2;
var sym_i: usize = 0;
var opt_symbol_name: ?[]const u8 = null;
var versions_buffer: [32]u8 = undefined;
var versions_len: usize = undefined;
while (sym_i < fn_inclusions_len) : (sym_i += 1) {
const sym_name = opt_symbol_name orelse n: {
const name = mem.sliceTo(metadata.inclusions[inc_i..], 0);
inc_i += name.len + 1;
opt_symbol_name = name;
versions_buffer = undefined;
versions_len = 0;
break :n name;
};
const targets = mem.readIntLittle(u32, metadata.inclusions[inc_i..][0..4]);
inc_i += 4;
const lib_index = metadata.inclusions[inc_i];
inc_i += 1;
const is_terminal = (targets & (1 << 31)) != 0;
if (is_terminal) opt_symbol_name = null;
// Test whether the inclusion applies to our current library and target.
const ok_lib_and_target =
(lib_index == lib_i) and
((targets & (@as(u32, 1) << @intCast(u5, target_targ_index))) != 0);
while (true) {
const byte = metadata.inclusions[inc_i];
inc_i += 1;
const last = (byte & 0b1000_0000) != 0;
const ver_i = @truncate(u7, byte);
if (ok_lib_and_target and ver_i <= target_ver_index) {
versions_buffer[versions_len] = ver_i;
versions_len += 1;
}
if (last) break;
}
if (!is_terminal) continue;
// Pick the default symbol version:
// - If there are no versions, don't emit it
// - Take the greatest one <= than the target one
// - If none of them is <= than the
// specified one don't pick any default version
if (versions_len == 0) continue;
var chosen_def_ver_index: u8 = 255;
{
var ver_buf_i: u8 = 0;
while (ver_buf_i < versions_len) : (ver_buf_i += 1) {
const ver_index = versions_buffer[ver_buf_i];
if (chosen_def_ver_index == 255 or ver_index > chosen_def_ver_index) {
chosen_def_ver_index = ver_index;
}
}
}
{
var ver_buf_i: u8 = 0;
while (ver_buf_i < versions_len) : (ver_buf_i += 1) {
// Example:
// .globl _Exit_2_2_5
// .type _Exit_2_2_5, %function;
// .symver _Exit_2_2_5, _Exit@@GLIBC_2.2.5
// _Exit_2_2_5:
const ver_index = versions_buffer[ver_buf_i];
const ver = metadata.all_versions[ver_index];
// Default symbol version definition vs normal symbol version definition
const want_default = chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index;
const at_sign_str: []const u8 = if (want_default) "@@" else "@";
if (ver.patch == 0) {
const sym_plus_ver = if (want_default)
sym_name
else
try std.fmt.allocPrint(
arena,
"{s}_GLIBC_{d}_{d}",
.{ sym_name, ver.major, ver.minor },
);
try stubs_asm.writer().print(
\\.globl {s}
\\.type {s}, %function;
\\.symver {s}, {s}{s}GLIBC_{d}.{d}
\\{s}:
\\
, .{
sym_plus_ver,
sym_plus_ver,
sym_plus_ver,
sym_name,
at_sign_str,
ver.major,
ver.minor,
sym_plus_ver,
});
} else {
const sym_plus_ver = if (want_default)
sym_name
else
try std.fmt.allocPrint(
arena,
"{s}_GLIBC_{d}_{d}_{d}",
.{ sym_name, ver.major, ver.minor, ver.patch },
);
try stubs_asm.writer().print(
\\.globl {s}
\\.type {s}, %function;
\\.symver {s}, {s}{s}GLIBC_{d}.{d}.{d}
\\{s}:
\\
, .{
sym_plus_ver,
sym_plus_ver,
sym_plus_ver,
sym_name,
at_sign_str,
ver.major,
ver.minor,
ver.patch,
sym_plus_ver,
});
}
}
}
}
try stubs_asm.appendSlice(".data\n");
const obj_inclusions_len = mem.readIntLittle(u16, metadata.inclusions[inc_i..][0..2]);
inc_i += 2;
sym_i = 0;
opt_symbol_name = null;
versions_buffer = undefined;
versions_len = undefined;
while (sym_i < obj_inclusions_len) : (sym_i += 1) {
const sym_name = opt_symbol_name orelse n: {
const name = mem.sliceTo(metadata.inclusions[inc_i..], 0);
inc_i += name.len + 1;
opt_symbol_name = name;
versions_buffer = undefined;
versions_len = 0;
break :n name;
};
const targets = mem.readIntLittle(u32, metadata.inclusions[inc_i..][0..4]);
inc_i += 4;
const size = mem.readIntLittle(u16, metadata.inclusions[inc_i..][0..2]);
inc_i += 2;
const lib_index = metadata.inclusions[inc_i];
inc_i += 1;
const is_terminal = (targets & (1 << 31)) != 0;
if (is_terminal) opt_symbol_name = null;
// Test whether the inclusion applies to our current library and target.
const ok_lib_and_target =
(lib_index == lib_i) and
((targets & (@as(u32, 1) << @intCast(u5, target_targ_index))) != 0);
while (true) {
const byte = metadata.inclusions[inc_i];
inc_i += 1;
const last = (byte & 0b1000_0000) != 0;
const ver_i = @truncate(u7, byte);
if (ok_lib_and_target and ver_i <= target_ver_index) {
versions_buffer[versions_len] = ver_i;
versions_len += 1;
}
if (last) break;
}
if (!is_terminal) continue;
// Pick the default symbol version:
// - If there are no versions, don't emit it
// - Take the greatest one <= than the target one
// - If none of them is <= than the
// specified one don't pick any default version
if (versions_len == 0) continue;
var chosen_def_ver_index: u8 = 255;
{
var ver_buf_i: u8 = 0;
while (ver_buf_i < versions_len) : (ver_buf_i += 1) {
const ver_index = versions_buffer[ver_buf_i];
if (chosen_def_ver_index == 255 or ver_index > chosen_def_ver_index) {
chosen_def_ver_index = ver_index;
}
}
}
{
var ver_buf_i: u8 = 0;
while (ver_buf_i < versions_len) : (ver_buf_i += 1) {
// Example:
// .globl environ_2_2_5
// .type environ_2_2_5, %object;
// .size environ_2_2_5, 4;
// .symver environ_2_2_5, environ@@GLIBC_2.2.5
// environ_2_2_5:
const ver_index = versions_buffer[ver_buf_i];
const ver = metadata.all_versions[ver_index];
// Default symbol version definition vs normal symbol version definition
const want_default = chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index;
const at_sign_str: []const u8 = if (want_default) "@@" else "@";
if (ver.patch == 0) {
const sym_plus_ver = if (want_default)
sym_name
else
try std.fmt.allocPrint(
arena,
"{s}_GLIBC_{d}_{d}",
.{ sym_name, ver.major, ver.minor },
);
try stubs_asm.writer().print(
\\.globl {s}
\\.type {s}, %object;
\\.size {s}, {d};
\\.symver {s}, {s}{s}GLIBC_{d}.{d}
\\{s}:
\\
, .{
sym_plus_ver,
sym_plus_ver,
sym_plus_ver,
size,
sym_plus_ver,
sym_name,
at_sign_str,
ver.major,
ver.minor,
sym_plus_ver,
});
} else {
const sym_plus_ver = if (want_default)
sym_name
else
try std.fmt.allocPrint(
arena,
"{s}_GLIBC_{d}_{d}_{d}",
.{ sym_name, ver.major, ver.minor, ver.patch },
);
try stubs_asm.writer().print(
\\.globl {s}
\\.type {s}, %object;
\\.size {s}, {d};
\\.symver {s}, {s}{s}GLIBC_{d}.{d}.{d}
\\{s}:
\\
, .{
sym_plus_ver,
sym_plus_ver,
sym_plus_ver,
size,
sym_plus_ver,
sym_name,
at_sign_str,
ver.major,
ver.minor,
ver.patch,
sym_plus_ver,
});
}
}
}
}
var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "pthread", etc.
const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable;
try o_directory.handle.writeFile(asm_file_basename, stubs_asm.items);
try buildSharedLib(comp, arena, comp.global_cache_directory, o_directory, asm_file_basename, lib);
}
man.writeManifest() catch |err| {
log.warn("failed to write cache manifest for glibc stubs: {s}", .{@errorName(err)});
};
assert(comp.glibc_so_files == null);
comp.glibc_so_files = BuiltSharedObjects{
.lock = man.toOwnedLock(),
.dir_path = try comp.global_cache_directory.join(comp.gpa, &.{ "o", &digest }),
};
}
// zig fmt: on
fn buildSharedLib(
comp: *Compilation,
arena: Allocator,
zig_cache_directory: Compilation.Directory,
bin_directory: Compilation.Directory,
asm_file_basename: []const u8,
lib: Lib,
) !void {
const tracy = trace(@src());
defer tracy.end();
const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
const emit_bin = Compilation.EmitLoc{
.directory = bin_directory,
.basename = basename,
};
const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename;
const map_file_path = try path.join(arena, &[_][]const u8{ bin_directory.path.?, all_map_basename });
const c_source_files = [1]Compilation.CSourceFile{
.{
.src_path = try path.join(arena, &[_][]const u8{ bin_directory.path.?, asm_file_basename }),
},
};
const sub_compilation = try Compilation.create(comp.gpa, .{
.local_cache_directory = zig_cache_directory,
.global_cache_directory = comp.global_cache_directory,
.zig_lib_directory = comp.zig_lib_directory,
.cache_mode = .whole,
.target = comp.getTarget(),
.root_name = lib.name,
.main_pkg = null,
.output_mode = .Lib,
.link_mode = .Dynamic,
.thread_pool = comp.thread_pool,
.libc_installation = comp.bin_file.options.libc_installation,
.emit_bin = emit_bin,
.optimize_mode = comp.compilerRtOptMode(),
.want_sanitize_c = false,
.want_stack_check = false,
.want_stack_protector = 0,
.want_red_zone = comp.bin_file.options.red_zone,
.omit_frame_pointer = comp.bin_file.options.omit_frame_pointer,
.want_valgrind = false,
.want_tsan = false,
.emit_h = null,
.strip = comp.compilerRtStrip(),
.is_native_os = false,
.is_native_abi = false,
.self_exe_path = comp.self_exe_path,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.bin_file.options.verbose_link,
.verbose_air = comp.verbose_air,
.verbose_llvm_ir = comp.verbose_llvm_ir,
.verbose_cimport = comp.verbose_cimport,
.verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,
.clang_passthrough_mode = comp.clang_passthrough_mode,
.version = version,
.version_script = map_file_path,
.soname = soname,
.c_source_files = &c_source_files,
.skip_linker_dependencies = true,
});
defer sub_compilation.destroy();
try comp.updateSubCompilation(sub_compilation, .@"glibc shared object");
}
// Return true if glibc has crti/crtn sources for that architecture.
pub fn needsCrtiCrtn(target: std.Target) bool {
return switch (target.cpu.arch) {
.riscv32, .riscv64 => false,
else => true,
};
}