stage2: building glibc shared objects
* caching system: use 16 bytes siphash final(), there was a bug in the std lib that wasn't catching undefined values for 18 bytes. fixed in master branch. * fix caching system unit test logic to not cause error.TextBusy on windows * port the logic from stage1 for building glibc shared objects * add is_native_os to the base cache hash * fix incorrectly freeing crt_files key (which is always a reference to global static constant data) * fix 2 use-after-free in loading glibc metadata * fix memory leak in buildCRTFile (errdefer instead of defer on arena)master
parent
da0fde59b6
commit
95941c4e70
11
BRANCH_TODO
11
BRANCH_TODO
|
@ -1,4 +1,7 @@
|
||||||
* glibc .so files
|
* glibc .so files
|
||||||
|
- stage1 C++ code integration
|
||||||
|
- ok file
|
||||||
|
* use hex for cache hash file paths
|
||||||
* support rpaths in ELF linker code
|
* support rpaths in ELF linker code
|
||||||
* build & link against compiler-rt
|
* build & link against compiler-rt
|
||||||
* build & link againstn freestanding libc
|
* build & link againstn freestanding libc
|
||||||
|
@ -21,7 +24,6 @@
|
||||||
* implement proper parsing of LLD stderr/stdout and exposing compile errors
|
* implement proper parsing of LLD stderr/stdout and exposing compile errors
|
||||||
* implement proper parsing of clang stderr/stdout and exposing compile errors
|
* implement proper parsing of clang stderr/stdout and exposing compile errors
|
||||||
* implement proper compile errors for failing to build glibc crt files and shared libs
|
* implement proper compile errors for failing to build glibc crt files and shared libs
|
||||||
* skip LLD caching when bin directory is not in the cache (so we don't put `id.txt` into the cwd)
|
|
||||||
* self-host link.cpp and building libcs (#4313 and #4314). using the `zig cc` command will set a flag indicating a preference for the llvm backend, which will include linking with LLD. At least for now. If zig's self-hosted linker ever gets on par with the likes of ld and lld, we can make it always be used even for zig cc.
|
* self-host link.cpp and building libcs (#4313 and #4314). using the `zig cc` command will set a flag indicating a preference for the llvm backend, which will include linking with LLD. At least for now. If zig's self-hosted linker ever gets on par with the likes of ld and lld, we can make it always be used even for zig cc.
|
||||||
* improve the stage2 tests to support testing with LLVM extensions enabled
|
* improve the stage2 tests to support testing with LLVM extensions enabled
|
||||||
* multi-thread building C objects
|
* multi-thread building C objects
|
||||||
|
@ -47,3 +49,10 @@
|
||||||
* libc_installation.zig: make it look for msvc only if msvc abi is chosen
|
* libc_installation.zig: make it look for msvc only if msvc abi is chosen
|
||||||
* switch the default C ABI for windows to be mingw-w64
|
* switch the default C ABI for windows to be mingw-w64
|
||||||
* port windows_sdk.cpp to zig
|
* port windows_sdk.cpp to zig
|
||||||
|
* change glibc log errors to normal exposed compile errors
|
||||||
|
* update Package to use Compilation.Directory in create()
|
||||||
|
- skip LLD caching when bin directory is not in the cache (so we don't put `id.txt` into the cwd)
|
||||||
|
(maybe make it an explicit option and have main.zig disable it)
|
||||||
|
- make it possible for Package to not openDir and reference already existing resources.
|
||||||
|
* rename src/ to src/stage1/
|
||||||
|
* rename src-self-hosted/ to src/
|
||||||
|
|
|
@ -18,4 +18,8 @@ cmake .. -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo $CMAKEFLAGS -DCMA
|
||||||
|
|
||||||
make -j$(nproc) install
|
make -j$(nproc) install
|
||||||
|
|
||||||
|
# I saw a failure due to `git diff` being > 400 KB instead of empty as expected so this is to debug it.
|
||||||
|
git status
|
||||||
|
git diff | head -n100
|
||||||
|
|
||||||
./zig build test-behavior -Dskip-non-native -Dskip-release
|
./zig build test-behavior -Dskip-non-native -Dskip-release
|
||||||
|
|
|
@ -26,9 +26,9 @@ pub fn obtain(cache: *const Cache) CacheHash {
|
||||||
|
|
||||||
pub const base64_encoder = fs.base64_encoder;
|
pub const base64_encoder = fs.base64_encoder;
|
||||||
pub const base64_decoder = fs.base64_decoder;
|
pub const base64_decoder = fs.base64_decoder;
|
||||||
/// 16 would be 128 bits - Even with 2^54 cache entries, the probably of a collision would be under 10^-6
|
/// This is 128 bits - Even with 2^54 cache entries, the probably of a collision would be under 10^-6
|
||||||
/// We round up to 18 to avoid the `==` padding after base64 encoding.
|
/// Currently we use SipHash and so this value must be 16 not any higher.
|
||||||
pub const BIN_DIGEST_LEN = 18;
|
pub const BIN_DIGEST_LEN = 16;
|
||||||
pub const BASE64_DIGEST_LEN = base64.Base64Encoder.calcSize(BIN_DIGEST_LEN);
|
pub const BASE64_DIGEST_LEN = base64.Base64Encoder.calcSize(BIN_DIGEST_LEN);
|
||||||
|
|
||||||
const MANIFEST_FILE_SIZE_MAX = 50 * 1024 * 1024;
|
const MANIFEST_FILE_SIZE_MAX = 50 * 1024 * 1024;
|
||||||
|
@ -87,14 +87,29 @@ pub const HashHelper = struct {
|
||||||
hh.add(x.major);
|
hh.add(x.major);
|
||||||
hh.add(x.minor);
|
hh.add(x.minor);
|
||||||
hh.add(x.patch);
|
hh.add(x.patch);
|
||||||
return;
|
|
||||||
},
|
},
|
||||||
else => {},
|
std.Target.Os.TaggedVersionRange => {
|
||||||
|
switch (x) {
|
||||||
|
.linux => |linux| {
|
||||||
|
hh.add(linux.range.min);
|
||||||
|
hh.add(linux.range.max);
|
||||||
|
hh.add(linux.glibc);
|
||||||
|
},
|
||||||
|
.windows => |windows| {
|
||||||
|
hh.add(windows.min);
|
||||||
|
hh.add(windows.max);
|
||||||
|
},
|
||||||
|
.semver => |semver| {
|
||||||
|
hh.add(semver.min);
|
||||||
|
hh.add(semver.max);
|
||||||
|
},
|
||||||
|
.none => {},
|
||||||
}
|
}
|
||||||
|
},
|
||||||
switch (@typeInfo(@TypeOf(x))) {
|
else => switch (@typeInfo(@TypeOf(x))) {
|
||||||
.Bool, .Int, .Enum, .Array => hh.addBytes(mem.asBytes(&x)),
|
.Bool, .Int, .Enum, .Array => hh.addBytes(mem.asBytes(&x)),
|
||||||
else => @compileError("unable to hash type " ++ @typeName(@TypeOf(x))),
|
else => @compileError("unable to hash type " ++ @typeName(@TypeOf(x))),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -613,6 +628,7 @@ test "cache file and then recall it" {
|
||||||
var digest1: [BASE64_DIGEST_LEN]u8 = undefined;
|
var digest1: [BASE64_DIGEST_LEN]u8 = undefined;
|
||||||
var digest2: [BASE64_DIGEST_LEN]u8 = undefined;
|
var digest2: [BASE64_DIGEST_LEN]u8 = undefined;
|
||||||
|
|
||||||
|
{
|
||||||
var cache = Cache{
|
var cache = Cache{
|
||||||
.gpa = testing.allocator,
|
.gpa = testing.allocator,
|
||||||
.manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
|
.manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
|
||||||
|
@ -651,6 +667,7 @@ test "cache file and then recall it" {
|
||||||
}
|
}
|
||||||
|
|
||||||
testing.expectEqual(digest1, digest2);
|
testing.expectEqual(digest1, digest2);
|
||||||
|
}
|
||||||
|
|
||||||
try cwd.deleteTree(temp_manifest_dir);
|
try cwd.deleteTree(temp_manifest_dir);
|
||||||
try cwd.deleteFile(temp_file);
|
try cwd.deleteFile(temp_file);
|
||||||
|
@ -693,6 +710,7 @@ test "check that changing a file makes cache fail" {
|
||||||
var digest1: [BASE64_DIGEST_LEN]u8 = undefined;
|
var digest1: [BASE64_DIGEST_LEN]u8 = undefined;
|
||||||
var digest2: [BASE64_DIGEST_LEN]u8 = undefined;
|
var digest2: [BASE64_DIGEST_LEN]u8 = undefined;
|
||||||
|
|
||||||
|
{
|
||||||
var cache = Cache{
|
var cache = Cache{
|
||||||
.gpa = testing.allocator,
|
.gpa = testing.allocator,
|
||||||
.manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
|
.manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
|
||||||
|
@ -737,6 +755,7 @@ test "check that changing a file makes cache fail" {
|
||||||
}
|
}
|
||||||
|
|
||||||
testing.expect(!mem.eql(u8, digest1[0..], digest2[0..]));
|
testing.expect(!mem.eql(u8, digest1[0..], digest2[0..]));
|
||||||
|
}
|
||||||
|
|
||||||
try cwd.deleteTree(temp_manifest_dir);
|
try cwd.deleteTree(temp_manifest_dir);
|
||||||
try cwd.deleteTree(temp_file);
|
try cwd.deleteTree(temp_file);
|
||||||
|
@ -749,7 +768,7 @@ test "no file inputs" {
|
||||||
}
|
}
|
||||||
const cwd = fs.cwd();
|
const cwd = fs.cwd();
|
||||||
const temp_manifest_dir = "no_file_inputs_manifest_dir";
|
const temp_manifest_dir = "no_file_inputs_manifest_dir";
|
||||||
defer cwd.deleteTree(temp_manifest_dir) catch unreachable;
|
defer cwd.deleteTree(temp_manifest_dir) catch {};
|
||||||
|
|
||||||
var digest1: [BASE64_DIGEST_LEN]u8 = undefined;
|
var digest1: [BASE64_DIGEST_LEN]u8 = undefined;
|
||||||
var digest2: [BASE64_DIGEST_LEN]u8 = undefined;
|
var digest2: [BASE64_DIGEST_LEN]u8 = undefined;
|
||||||
|
@ -810,6 +829,7 @@ test "CacheHashes with files added after initial hash work" {
|
||||||
var digest2: [BASE64_DIGEST_LEN]u8 = undefined;
|
var digest2: [BASE64_DIGEST_LEN]u8 = undefined;
|
||||||
var digest3: [BASE64_DIGEST_LEN]u8 = undefined;
|
var digest3: [BASE64_DIGEST_LEN]u8 = undefined;
|
||||||
|
|
||||||
|
{
|
||||||
var cache = Cache{
|
var cache = Cache{
|
||||||
.gpa = testing.allocator,
|
.gpa = testing.allocator,
|
||||||
.manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
|
.manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
|
||||||
|
@ -871,6 +891,7 @@ test "CacheHashes with files added after initial hash work" {
|
||||||
}
|
}
|
||||||
|
|
||||||
testing.expect(!mem.eql(u8, &digest1, &digest3));
|
testing.expect(!mem.eql(u8, &digest1, &digest3));
|
||||||
|
}
|
||||||
|
|
||||||
try cwd.deleteTree(temp_manifest_dir);
|
try cwd.deleteTree(temp_manifest_dir);
|
||||||
try cwd.deleteFile(temp_file1);
|
try cwd.deleteFile(temp_file1);
|
||||||
|
|
|
@ -68,7 +68,9 @@ libunwind_static_lib: ?[]const u8 = null,
|
||||||
/// and resolved before calling linker.flush().
|
/// and resolved before calling linker.flush().
|
||||||
libc_static_lib: ?[]const u8 = null,
|
libc_static_lib: ?[]const u8 = null,
|
||||||
|
|
||||||
/// For example `Scrt1.o` and `libc.so.6`. These are populated after building libc from source,
|
glibc_so_files: ?glibc.BuiltSharedObjects = null,
|
||||||
|
|
||||||
|
/// For example `Scrt1.o` and `libc_nonshared.a`. These are populated after building libc from source,
|
||||||
/// The set of needed CRT (C runtime) files differs depending on the target and compilation settings.
|
/// The set of needed CRT (C runtime) files differs depending on the target and compilation settings.
|
||||||
/// The key is the basename, and the value is the absolute path to the completed build artifact.
|
/// The key is the basename, and the value is the absolute path to the completed build artifact.
|
||||||
crt_files: std.StringHashMapUnmanaged(CRTFile) = .{},
|
crt_files: std.StringHashMapUnmanaged(CRTFile) = .{},
|
||||||
|
@ -111,8 +113,8 @@ const WorkItem = union(enum) {
|
||||||
|
|
||||||
/// one of the glibc static objects
|
/// one of the glibc static objects
|
||||||
glibc_crt_file: glibc.CRTFile,
|
glibc_crt_file: glibc.CRTFile,
|
||||||
/// one of the glibc shared objects
|
/// all of the glibc shared objects
|
||||||
glibc_so: *const glibc.Lib,
|
glibc_shared_objects,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CObject = struct {
|
pub const CObject = struct {
|
||||||
|
@ -272,6 +274,9 @@ pub const InitOptions = struct {
|
||||||
version: ?std.builtin.Version = null,
|
version: ?std.builtin.Version = null,
|
||||||
libc_installation: ?*const LibCInstallation = null,
|
libc_installation: ?*const LibCInstallation = null,
|
||||||
machine_code_model: std.builtin.CodeModel = .default,
|
machine_code_model: std.builtin.CodeModel = .default,
|
||||||
|
/// TODO Once self-hosted Zig is capable enough, we can remove this special-case
|
||||||
|
/// hack in favor of more general compilation options.
|
||||||
|
stage1_is_dummy_so: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
||||||
|
@ -421,6 +426,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
||||||
cache.hash.addBytes(options.target.cpu.model.name);
|
cache.hash.addBytes(options.target.cpu.model.name);
|
||||||
cache.hash.add(options.target.cpu.features.ints);
|
cache.hash.add(options.target.cpu.features.ints);
|
||||||
cache.hash.add(options.target.os.tag);
|
cache.hash.add(options.target.os.tag);
|
||||||
|
cache.hash.add(options.is_native_os);
|
||||||
cache.hash.add(options.target.abi);
|
cache.hash.add(options.target.abi);
|
||||||
cache.hash.add(ofmt);
|
cache.hash.add(ofmt);
|
||||||
cache.hash.add(pic);
|
cache.hash.add(pic);
|
||||||
|
@ -446,22 +452,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
||||||
hash.addOptionalBytes(root_pkg.root_src_directory.path);
|
hash.addOptionalBytes(root_pkg.root_src_directory.path);
|
||||||
hash.add(valgrind);
|
hash.add(valgrind);
|
||||||
hash.add(single_threaded);
|
hash.add(single_threaded);
|
||||||
switch (options.target.os.getVersionRange()) {
|
hash.add(options.target.os.getVersionRange());
|
||||||
.linux => |linux| {
|
|
||||||
hash.add(linux.range.min);
|
|
||||||
hash.add(linux.range.max);
|
|
||||||
hash.add(linux.glibc);
|
|
||||||
},
|
|
||||||
.windows => |windows| {
|
|
||||||
hash.add(windows.min);
|
|
||||||
hash.add(windows.max);
|
|
||||||
},
|
|
||||||
.semver => |semver| {
|
|
||||||
hash.add(semver.min);
|
|
||||||
hash.add(semver.max);
|
|
||||||
},
|
|
||||||
.none => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
const digest = hash.final();
|
const digest = hash.final();
|
||||||
const artifact_sub_dir = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest });
|
const artifact_sub_dir = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest });
|
||||||
|
@ -660,7 +651,6 @@ pub fn destroy(self: *Compilation) void {
|
||||||
{
|
{
|
||||||
var it = self.crt_files.iterator();
|
var it = self.crt_files.iterator();
|
||||||
while (it.next()) |entry| {
|
while (it.next()) |entry| {
|
||||||
gpa.free(entry.key);
|
|
||||||
entry.value.deinit(gpa);
|
entry.value.deinit(gpa);
|
||||||
}
|
}
|
||||||
self.crt_files.deinit(gpa);
|
self.crt_files.deinit(gpa);
|
||||||
|
@ -936,14 +926,15 @@ pub fn performAllTheWork(self: *Compilation) error{OutOfMemory}!void {
|
||||||
},
|
},
|
||||||
.glibc_crt_file => |crt_file| {
|
.glibc_crt_file => |crt_file| {
|
||||||
glibc.buildCRTFile(self, crt_file) catch |err| {
|
glibc.buildCRTFile(self, crt_file) catch |err| {
|
||||||
// This is a problem with the Zig installation. It's mostly OK to crash here,
|
// TODO Expose this as a normal compile error rather than crashing here.
|
||||||
// but TODO because it would be even better if we could recover gracefully
|
|
||||||
// from temporary problems such as out-of-disk-space.
|
|
||||||
fatal("unable to build glibc CRT file: {}", .{@errorName(err)});
|
fatal("unable to build glibc CRT file: {}", .{@errorName(err)});
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
.glibc_so => |glibc_lib| {
|
.glibc_shared_objects => {
|
||||||
fatal("TODO build glibc shared object '{}.so.{}'", .{ glibc_lib.name, glibc_lib.sover });
|
glibc.buildSharedObjects(self) catch |err| {
|
||||||
|
// TODO Expose this as a normal compile error rather than crashing here.
|
||||||
|
fatal("unable to build glibc shared objects: {}", .{@errorName(err)});
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1587,17 +1578,13 @@ pub fn get_libc_crt_file(comp: *Compilation, arena: *Allocator, basename: []cons
|
||||||
}
|
}
|
||||||
|
|
||||||
fn addBuildingGLibCWorkItems(comp: *Compilation) !void {
|
fn addBuildingGLibCWorkItems(comp: *Compilation) !void {
|
||||||
const static_file_work_items = [_]WorkItem{
|
try comp.work_queue.write(&[_]WorkItem{
|
||||||
.{ .glibc_crt_file = .crti_o },
|
.{ .glibc_crt_file = .crti_o },
|
||||||
.{ .glibc_crt_file = .crtn_o },
|
.{ .glibc_crt_file = .crtn_o },
|
||||||
.{ .glibc_crt_file = .scrt1_o },
|
.{ .glibc_crt_file = .scrt1_o },
|
||||||
.{ .glibc_crt_file = .libc_nonshared_a },
|
.{ .glibc_crt_file = .libc_nonshared_a },
|
||||||
};
|
.{ .glibc_shared_objects = {} },
|
||||||
try comp.work_queue.ensureUnusedCapacity(static_file_work_items.len + glibc.libs.len);
|
});
|
||||||
comp.work_queue.writeAssumeCapacity(&static_file_work_items);
|
|
||||||
for (glibc.libs) |*glibc_so| {
|
|
||||||
comp.work_queue.writeItemAssumeCapacity(.{ .glibc_so = glibc_so });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wantBuildGLibCFromSource(comp: *Compilation) bool {
|
fn wantBuildGLibCFromSource(comp: *Compilation) bool {
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const target_util = @import("target.zig");
|
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
const Compilation = @import("Compilation.zig");
|
|
||||||
const path = std.fs.path;
|
const path = std.fs.path;
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
const target_util = @import("target.zig");
|
||||||
|
const Compilation = @import("Compilation.zig");
|
||||||
const build_options = @import("build_options");
|
const build_options = @import("build_options");
|
||||||
const trace = @import("tracy.zig").trace;
|
const trace = @import("tracy.zig").trace;
|
||||||
|
const Cache = @import("Cache.zig");
|
||||||
|
const Package = @import("Package.zig");
|
||||||
|
|
||||||
pub const Lib = struct {
|
pub const Lib = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
|
@ -83,14 +87,14 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError!
|
||||||
};
|
};
|
||||||
defer gpa.free(vers_txt_contents);
|
defer gpa.free(vers_txt_contents);
|
||||||
|
|
||||||
const fns_txt_contents = glibc_dir.readFileAlloc(gpa, "fns.txt", max_txt_size) catch |err| switch (err) {
|
// Arena allocated because the result contains references to function names.
|
||||||
|
const fns_txt_contents = glibc_dir.readFileAlloc(arena, "fns.txt", max_txt_size) catch |err| switch (err) {
|
||||||
error.OutOfMemory => return error.OutOfMemory,
|
error.OutOfMemory => return error.OutOfMemory,
|
||||||
else => {
|
else => {
|
||||||
std.log.err("unable to read fns.txt: {}", .{@errorName(err)});
|
std.log.err("unable to read fns.txt: {}", .{@errorName(err)});
|
||||||
return error.ZigInstallationCorrupt;
|
return error.ZigInstallationCorrupt;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
defer gpa.free(fns_txt_contents);
|
|
||||||
|
|
||||||
const abi_txt_contents = glibc_dir.readFileAlloc(gpa, "abi.txt", max_txt_size) catch |err| switch (err) {
|
const abi_txt_contents = glibc_dir.readFileAlloc(gpa, "abi.txt", max_txt_size) catch |err| switch (err) {
|
||||||
error.OutOfMemory => return error.OutOfMemory,
|
error.OutOfMemory => return error.OutOfMemory,
|
||||||
|
@ -183,7 +187,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError!
|
||||||
.os = .linux,
|
.os = .linux,
|
||||||
.abi = abi_tag,
|
.abi = abi_tag,
|
||||||
};
|
};
|
||||||
try version_table.put(arena, triple, ver_list_base.ptr);
|
try version_table.put(gpa, triple, ver_list_base.ptr);
|
||||||
}
|
}
|
||||||
break :blk ver_list_base;
|
break :blk ver_list_base;
|
||||||
};
|
};
|
||||||
|
@ -250,7 +254,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
|
||||||
}
|
}
|
||||||
const gpa = comp.gpa;
|
const gpa = comp.gpa;
|
||||||
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
||||||
errdefer arena_allocator.deinit();
|
defer arena_allocator.deinit();
|
||||||
const arena = &arena_allocator.allocator;
|
const arena = &arena_allocator.allocator;
|
||||||
|
|
||||||
switch (crt_file) {
|
switch (crt_file) {
|
||||||
|
@ -713,6 +717,252 @@ fn build_crt_file(
|
||||||
});
|
});
|
||||||
defer sub_compilation.destroy();
|
defer sub_compilation.destroy();
|
||||||
|
|
||||||
|
try updateSubCompilation(sub_compilation);
|
||||||
|
|
||||||
|
try comp.crt_files.ensureCapacity(comp.gpa, comp.crt_files.count() + 1);
|
||||||
|
const artifact_path = if (sub_compilation.bin_file.options.directory.path) |p|
|
||||||
|
try path.join(comp.gpa, &[_][]const u8{ p, basename })
|
||||||
|
else
|
||||||
|
try comp.gpa.dupe(u8, basename);
|
||||||
|
|
||||||
|
comp.crt_files.putAssumeCapacityNoClobber(basename, .{
|
||||||
|
.full_object_path = artifact_path,
|
||||||
|
.lock = sub_compilation.bin_file.toOwnedLock(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// TODO use the global cache directory here
|
||||||
|
var cache_parent: Cache = .{
|
||||||
|
.gpa = comp.gpa,
|
||||||
|
.manifest_dir = comp.cache_parent.manifest_dir,
|
||||||
|
};
|
||||||
|
var cache = cache_parent.obtain();
|
||||||
|
defer cache.deinit();
|
||||||
|
cache.hash.addBytes(build_options.version);
|
||||||
|
cache.hash.addBytes(comp.zig_lib_directory.path orelse ".");
|
||||||
|
cache.hash.add(target.cpu.arch);
|
||||||
|
cache.hash.addBytes(target.cpu.model.name);
|
||||||
|
cache.hash.add(target.cpu.features.ints);
|
||||||
|
cache.hash.add(target.abi);
|
||||||
|
cache.hash.add(target_version);
|
||||||
|
|
||||||
|
const hit = try cache.hit();
|
||||||
|
const digest = cache.final();
|
||||||
|
const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
|
||||||
|
if (!hit) {
|
||||||
|
var o_directory: Compilation.Directory = .{
|
||||||
|
.handle = try comp.zig_cache_directory.handle.makeOpenPath(o_sub_path, .{}),
|
||||||
|
.path = try path.join(arena, &[_][]const u8{ comp.zig_cache_directory.path.?, o_sub_path }),
|
||||||
|
};
|
||||||
|
defer o_directory.handle.close();
|
||||||
|
|
||||||
|
const metadata = try loadMetaData(comp.gpa, comp.zig_lib_directory.handle);
|
||||||
|
defer metadata.destroy(comp.gpa);
|
||||||
|
|
||||||
|
const ver_list_base = metadata.version_table.get(.{
|
||||||
|
.arch = target.cpu.arch,
|
||||||
|
.os = target.os.tag,
|
||||||
|
.abi = target.abi,
|
||||||
|
}) orelse return error.GLibCUnavailableForThisTarget;
|
||||||
|
const target_ver_index = for (metadata.all_versions) |ver, i| {
|
||||||
|
switch (ver.order(target_version)) {
|
||||||
|
.eq => break i,
|
||||||
|
.lt => continue,
|
||||||
|
.gt => {
|
||||||
|
// TODO Expose via compile error mechanism instead of log.
|
||||||
|
std.log.warn("invalid target glibc version: {}", .{target_version});
|
||||||
|
return error.InvalidTargetGLibCVersion;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else blk: {
|
||||||
|
const latest_index = metadata.all_versions.len - 1;
|
||||||
|
std.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) |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 zig_body = std.ArrayList(u8).init(comp.gpa);
|
||||||
|
defer zig_body.deinit();
|
||||||
|
var zig_footer = std.ArrayList(u8).init(comp.gpa);
|
||||||
|
defer zig_footer.deinit();
|
||||||
|
for (libs) |*lib| {
|
||||||
|
zig_body.shrinkRetainingCapacity(0);
|
||||||
|
zig_footer.shrinkRetainingCapacity(0);
|
||||||
|
|
||||||
|
try zig_body.appendSlice(
|
||||||
|
\\comptime {
|
||||||
|
\\ asm (
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
for (metadata.all_functions) |*libc_fn, fn_i| {
|
||||||
|
if (libc_fn.lib != lib) continue;
|
||||||
|
|
||||||
|
const ver_list = ver_list_base[fn_i];
|
||||||
|
// 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 (ver_list.len == 0) continue;
|
||||||
|
var chosen_def_ver_index: u8 = 255;
|
||||||
|
{
|
||||||
|
var ver_i: u8 = 0;
|
||||||
|
while (ver_i < ver_list.len) : (ver_i += 1) {
|
||||||
|
const ver_index = ver_list.versions[ver_i];
|
||||||
|
if ((chosen_def_ver_index == 255 or ver_index > chosen_def_ver_index) and
|
||||||
|
target_ver_index >= ver_index)
|
||||||
|
{
|
||||||
|
chosen_def_ver_index = ver_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var ver_i: u8 = 0;
|
||||||
|
while (ver_i < ver_list.len) : (ver_i += 1) {
|
||||||
|
const ver_index = ver_list.versions[ver_i];
|
||||||
|
const ver = metadata.all_versions[ver_index];
|
||||||
|
const sym_name = libc_fn.name;
|
||||||
|
const stub_name = if (ver.patch == 0)
|
||||||
|
try std.fmt.allocPrint(arena, "{s}_{d}_{d}", .{ sym_name, ver.major, ver.minor })
|
||||||
|
else
|
||||||
|
try std.fmt.allocPrint(arena, "{s}_{d}_{d}_{d}", .{ sym_name, ver.major, ver.minor, ver.patch });
|
||||||
|
|
||||||
|
try zig_footer.writer().print("export fn {s}() void {{}}\n", .{stub_name});
|
||||||
|
|
||||||
|
// Default symbol version definition vs normal symbol version definition
|
||||||
|
const want_two_ats = chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index;
|
||||||
|
const at_sign_str = "@@"[0 .. @boolToInt(want_two_ats) + @as(usize, 1)];
|
||||||
|
if (ver.patch == 0) {
|
||||||
|
try zig_body.writer().print(" \\\\ .symver {s}, {s}{s}GLIBC_{d}.{d}\n", .{
|
||||||
|
stub_name, sym_name, at_sign_str, ver.major, ver.minor,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
try zig_body.writer().print(" \\\\ .symver {s}, {s}{s}GLIBC_{d}.{d}.{d}\n", .{
|
||||||
|
stub_name, sym_name, at_sign_str, ver.major, ver.minor, ver.patch,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Hide the stub to keep the symbol table clean
|
||||||
|
try zig_body.writer().print(" \\\\ .hidden {s}\n", .{stub_name});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try zig_body.appendSlice(
|
||||||
|
\\ );
|
||||||
|
\\}
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
try zig_body.appendSlice(zig_footer.items);
|
||||||
|
|
||||||
|
var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "pthread", etc.
|
||||||
|
const zig_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.zig", .{lib.name}) catch unreachable;
|
||||||
|
try o_directory.handle.writeFile(zig_file_basename, zig_body.items);
|
||||||
|
|
||||||
|
try buildSharedLib(comp, arena, comp.zig_cache_directory, o_directory, zig_file_basename, lib);
|
||||||
|
}
|
||||||
|
cache.writeManifest() catch |err| {
|
||||||
|
std.log.warn("glibc shared objects: failed to write cache manifest: {}", .{@errorName(err)});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(comp.glibc_so_files == null);
|
||||||
|
comp.glibc_so_files = BuiltSharedObjects{
|
||||||
|
.lock = cache.toOwnedLock(),
|
||||||
|
.dir_path = try path.join(comp.gpa, &[_][]const u8{ comp.zig_cache_directory.path.?, o_sub_path }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buildSharedLib(
|
||||||
|
comp: *Compilation,
|
||||||
|
arena: *Allocator,
|
||||||
|
zig_cache_directory: Compilation.Directory,
|
||||||
|
bin_directory: Compilation.Directory,
|
||||||
|
zig_file_basename: []const u8,
|
||||||
|
lib: *const Lib,
|
||||||
|
) !void {
|
||||||
|
const tracy = trace(@src());
|
||||||
|
defer tracy.end();
|
||||||
|
|
||||||
|
const emit_bin = Compilation.EmitLoc{
|
||||||
|
.directory = bin_directory,
|
||||||
|
.basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover }),
|
||||||
|
};
|
||||||
|
const version: std.builtin.Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
|
||||||
|
const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
|
||||||
|
const override_soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else null;
|
||||||
|
const map_file_path = try path.join(arena, &[_][]const u8{ bin_directory.path.?, all_map_basename });
|
||||||
|
// TODO we should be able to just give the open directory to Package
|
||||||
|
const root_pkg = try Package.create(comp.gpa, std.fs.cwd(), bin_directory.path.?, zig_file_basename);
|
||||||
|
defer root_pkg.destroy(comp.gpa);
|
||||||
|
const sub_compilation = try Compilation.create(comp.gpa, .{
|
||||||
|
.zig_cache_directory = zig_cache_directory,
|
||||||
|
.zig_lib_directory = comp.zig_lib_directory,
|
||||||
|
.target = comp.getTarget(),
|
||||||
|
.root_name = lib.name,
|
||||||
|
.root_pkg = null,
|
||||||
|
.output_mode = .Lib,
|
||||||
|
.link_mode = .Dynamic,
|
||||||
|
.rand = comp.rand,
|
||||||
|
.libc_installation = comp.bin_file.options.libc_installation,
|
||||||
|
.emit_bin = emit_bin,
|
||||||
|
.optimize_mode = comp.bin_file.options.optimize_mode,
|
||||||
|
.want_sanitize_c = false,
|
||||||
|
.want_stack_check = false,
|
||||||
|
.want_valgrind = false,
|
||||||
|
.emit_h = null,
|
||||||
|
.strip = comp.bin_file.options.strip,
|
||||||
|
.is_native_os = false,
|
||||||
|
.self_exe_path = comp.self_exe_path,
|
||||||
|
.debug_cc = comp.debug_cc,
|
||||||
|
.debug_link = comp.bin_file.options.debug_link,
|
||||||
|
.clang_passthrough_mode = comp.clang_passthrough_mode,
|
||||||
|
.version = version,
|
||||||
|
.stage1_is_dummy_so = true,
|
||||||
|
.version_script = map_file_path,
|
||||||
|
.override_soname = override_soname,
|
||||||
|
});
|
||||||
|
defer sub_compilation.destroy();
|
||||||
|
|
||||||
|
try updateSubCompilation(sub_compilation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateSubCompilation(sub_compilation: *Compilation) !void {
|
||||||
try sub_compilation.update();
|
try sub_compilation.update();
|
||||||
|
|
||||||
// Look for compilation errors in this sub_compilation
|
// Look for compilation errors in this sub_compilation
|
||||||
|
@ -730,15 +980,4 @@ fn build_crt_file(
|
||||||
}
|
}
|
||||||
return error.BuildingLibCObjectFailed;
|
return error.BuildingLibCObjectFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
try comp.crt_files.ensureCapacity(comp.gpa, comp.crt_files.count() + 1);
|
|
||||||
const artifact_path = if (sub_compilation.bin_file.options.directory.path) |p|
|
|
||||||
try std.fs.path.join(comp.gpa, &[_][]const u8{ p, basename })
|
|
||||||
else
|
|
||||||
try comp.gpa.dupe(u8, basename);
|
|
||||||
|
|
||||||
comp.crt_files.putAssumeCapacityNoClobber(basename, .{
|
|
||||||
.full_object_path = artifact_path,
|
|
||||||
.lock = sub_compilation.bin_file.toOwnedLock(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue