authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-09-28 23:20:14-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-09-28 23:20:14-07:00
loged06a78f35e7281289249b0d0c119bd64845dd51
tree0cde7797b9ef6fe3711834cd574d65428d3e2d63
parent73167e80f8a1d440ebc5725a165b7cf484b96170

stage2: WASM LLD linking


4 files changed, 212 insertions(+), 5 deletions(-)

BRANCH_TODO-1
...@@ -1,5 +1,4 @@...@@ -1,5 +1,4 @@
1 * MachO LLD linking1 * MachO LLD linking
2 * WASM LLD linking
3 * audit the CLI options for stage22 * audit the CLI options for stage2
4 * audit the base cache hash3 * audit the base cache hash
5 * On operating systems that support it, do an execve for `zig test` and `zig run` rather than child process.4 * On operating systems that support it, do an execve for `zig test` and `zig run` rather than child process.
lib/std/target.zig+1-1
...@@ -1486,7 +1486,7 @@ pub const Target = struct {...@@ -1486,7 +1486,7 @@ pub const Target = struct {
14861486
1487 /// Return whether or not the given host target is capable of executing natively executables1487 /// Return whether or not the given host target is capable of executing natively executables
1488 /// of the other target.1488 /// of the other target.
1489 pub fn canExecBinariesOf(host_target: std.Target, binary_target: std.Target) bool {1489 pub fn canExecBinariesOf(host_target: Target, binary_target: Target) bool {
1490 if (host_target.os.tag != binary_target.os.tag)1490 if (host_target.os.tag != binary_target.os.tag)
1491 return false;1491 return false;
14921492
src/Compilation.zig+2-2
...@@ -2418,7 +2418,7 @@ fn buildStaticLibFromZig(comp: *Compilation, src_basename: []const u8, out: *?CR...@@ -2418,7 +2418,7 @@ fn buildStaticLibFromZig(comp: *Compilation, src_basename: []const u8, out: *?CR
2418 const bin_basename = try std.zig.binNameAlloc(comp.gpa, .{2418 const bin_basename = try std.zig.binNameAlloc(comp.gpa, .{
2419 .root_name = root_name,2419 .root_name = root_name,
2420 .target = target,2420 .target = target,
2421 .output_mode = .Lib,2421 .output_mode = .Obj,
2422 });2422 });
2423 defer comp.gpa.free(bin_basename);2423 defer comp.gpa.free(bin_basename);
24242424
...@@ -2441,7 +2441,7 @@ fn buildStaticLibFromZig(comp: *Compilation, src_basename: []const u8, out: *?CR...@@ -2441,7 +2441,7 @@ fn buildStaticLibFromZig(comp: *Compilation, src_basename: []const u8, out: *?CR
2441 .target = target,2441 .target = target,
2442 .root_name = root_name,2442 .root_name = root_name,
2443 .root_pkg = &root_pkg,2443 .root_pkg = &root_pkg,
2444 .output_mode = .Lib,2444 .output_mode = .Obj,
2445 .rand = comp.rand,2445 .rand = comp.rand,
2446 .libc_installation = comp.bin_file.options.libc_installation,2446 .libc_installation = comp.bin_file.options.libc_installation,
2447 .emit_bin = emit_bin,2447 .emit_bin = emit_bin,
src/link/Wasm.zig+209-1
...@@ -1,10 +1,12 @@...@@ -1,10 +1,12 @@
1const Wasm = @This();1const Wasm = @This();
22
3const std = @import("std");3const std = @import("std");
4const mem = std.mem;
4const Allocator = std.mem.Allocator;5const Allocator = std.mem.Allocator;
5const assert = std.debug.assert;6const assert = std.debug.assert;
6const fs = std.fs;7const fs = std.fs;
7const leb = std.debug.leb;8const leb = std.debug.leb;
9const log = std.log.scoped(.link);
810
9const Module = @import("../Module.zig");11const Module = @import("../Module.zig");
10const Compilation = @import("../Compilation.zig");12const Compilation = @import("../Compilation.zig");
...@@ -12,6 +14,7 @@ const codegen = @import("../codegen/wasm.zig");...@@ -12,6 +14,7 @@ const codegen = @import("../codegen/wasm.zig");
12const link = @import("../link.zig");14const link = @import("../link.zig");
13const trace = @import("../tracy.zig").trace;15const trace = @import("../tracy.zig").trace;
14const build_options = @import("build_options");16const build_options = @import("build_options");
17const Cache = @import("../Cache.zig");
1518
16/// Various magic numbers defined by the wasm spec19/// Various magic numbers defined by the wasm spec
17const spec = struct {20const spec = struct {
...@@ -137,7 +140,7 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {...@@ -137,7 +140,7 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
137140
138pub fn flush(self: *Wasm, comp: *Compilation) !void {141pub fn flush(self: *Wasm, comp: *Compilation) !void {
139 if (build_options.have_llvm and self.base.options.use_lld) {142 if (build_options.have_llvm and self.base.options.use_lld) {
140 return error.WasmLinkingWithLLDUnimplemented;143 return self.linkWithLLD(comp);
141 } else {144 } else {
142 return self.flushModule(comp);145 return self.flushModule(comp);
143 }146 }
...@@ -248,6 +251,211 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -248,6 +251,211 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
248 }251 }
249}252}
250253
254fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
255 const tracy = trace(@src());
256 defer tracy.end();
257
258 var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator);
259 defer arena_allocator.deinit();
260 const arena = &arena_allocator.allocator;
261
262 const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type.
263
264 // If there is no Zig code to compile, then we should skip flushing the output file because it
265 // will not be part of the linker line anyway.
266 const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: {
267 const use_stage1 = build_options.is_stage1 and self.base.options.use_llvm;
268 if (use_stage1) {
269 const obj_basename = try std.zig.binNameAlloc(arena, .{
270 .root_name = self.base.options.root_name,
271 .target = self.base.options.target,
272 .output_mode = .Obj,
273 });
274 const o_directory = self.base.options.module.?.zig_cache_artifact_directory;
275 const full_obj_path = try o_directory.join(arena, &[_][]const u8{obj_basename});
276 break :blk full_obj_path;
277 }
278
279 try self.flushModule(comp);
280 const obj_basename = self.base.intermediary_basename.?;
281 const full_obj_path = try directory.join(arena, &[_][]const u8{obj_basename});
282 break :blk full_obj_path;
283 } else null;
284
285 const target = self.base.options.target;
286
287 const id_symlink_basename = "lld.id";
288
289 var man: Cache.Manifest = undefined;
290 defer if (!self.base.options.disable_lld_caching) man.deinit();
291
292 var digest: [Cache.hex_digest_len]u8 = undefined;
293
294 if (!self.base.options.disable_lld_caching) {
295 man = comp.cache_parent.obtain();
296
297 // We are about to obtain this lock, so here we give other processes a chance first.
298 self.base.releaseLock();
299
300 try man.addListOfFiles(self.base.options.objects);
301 for (comp.c_object_table.items()) |entry| {
302 _ = try man.addFile(entry.key.status.success.object_path, null);
303 }
304 try man.addOptionalFile(module_obj_path);
305 man.hash.addOptional(self.base.options.stack_size_override);
306 man.hash.addListOfBytes(self.base.options.extra_lld_args);
307
308 // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
309 _ = try man.hit();
310 digest = man.final();
311
312 var prev_digest_buf: [digest.len]u8 = undefined;
313 const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| blk: {
314 log.debug("WASM LLD new_digest={} readlink error: {}", .{ digest, @errorName(err) });
315 // Handle this as a cache miss.
316 break :blk prev_digest_buf[0..0];
317 };
318 if (mem.eql(u8, prev_digest, &digest)) {
319 log.debug("WASM LLD digest={} match - skipping invocation", .{digest});
320 // Hot diggity dog! The output binary is already there.
321 self.base.lock = man.toOwnedLock();
322 return;
323 }
324 log.debug("WASM LLD prev_digest={} new_digest={}", .{ prev_digest, digest });
325
326 // We are about to change the output file to be different, so we invalidate the build hash now.
327 directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
328 error.FileNotFound => {},
329 else => |e| return e,
330 };
331 }
332
333 const is_obj = self.base.options.output_mode == .Obj;
334
335 // Create an LLD command line and invoke it.
336 var argv = std.ArrayList([]const u8).init(self.base.allocator);
337 defer argv.deinit();
338 // Even though we're calling LLD as a library it thinks the first argument is its own exe name.
339 try argv.append("lld");
340 if (is_obj) {
341 try argv.append("-r");
342 }
343
344 try argv.append("-error-limit=0");
345
346 if (self.base.options.output_mode == .Exe) {
347 // Increase the default stack size to a more reasonable value of 1MB instead of
348 // the default of 1 Wasm page being 64KB, unless overriden by the user.
349 try argv.append("-z");
350 const stack_size = self.base.options.stack_size_override orelse 1048576;
351 const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size});
352 try argv.append(arg);
353
354 // Put stack before globals so that stack overflow results in segfault immediately
355 // before corrupting globals. See https://github.com/ziglang/zig/issues/4496
356 try argv.append("--stack-first");
357 } else {
358 try argv.append("--no-entry"); // So lld doesn't look for _start.
359 try argv.append("--export-all");
360 }
361 try argv.appendSlice(&[_][]const u8{
362 "--allow-undefined",
363 "-o",
364 try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}),
365 });
366
367 // Positional arguments to the linker such as object files.
368 try argv.appendSlice(self.base.options.objects);
369
370 for (comp.c_object_table.items()) |entry| {
371 try argv.append(entry.key.status.success.object_path);
372 }
373 if (module_obj_path) |p| {
374 try argv.append(p);
375 }
376
377 if (self.base.options.output_mode == .Exe and !self.base.options.is_compiler_rt_or_libc) {
378 if (!self.base.options.link_libc) {
379 try argv.append(comp.libc_static_lib.?.full_object_path);
380 }
381 try argv.append(comp.compiler_rt_static_lib.?.full_object_path);
382 }
383
384 if (self.base.options.verbose_link) {
385 Compilation.dump_argv(argv.items);
386 }
387
388 // TODO allocSentinel crashed stage1 so this is working around it.
389 const new_argv_with_sentinel = try arena.alloc(?[*:0]const u8, argv.items.len + 1);
390 new_argv_with_sentinel[argv.items.len] = null;
391 const new_argv = new_argv_with_sentinel[0..argv.items.len :null];
392 for (argv.items) |arg, i| {
393 new_argv[i] = try arena.dupeZ(u8, arg);
394 }
395
396 var stderr_context: LLDContext = .{
397 .wasm = self,
398 .data = std.ArrayList(u8).init(self.base.allocator),
399 };
400 defer stderr_context.data.deinit();
401 var stdout_context: LLDContext = .{
402 .wasm = self,
403 .data = std.ArrayList(u8).init(self.base.allocator),
404 };
405 defer stdout_context.data.deinit();
406 const llvm = @import("../llvm.zig");
407 const ok = llvm.Link(
408 .Wasm,
409 new_argv.ptr,
410 new_argv.len,
411 append_diagnostic,
412 @ptrToInt(&stdout_context),
413 @ptrToInt(&stderr_context),
414 );
415 if (stderr_context.oom or stdout_context.oom) return error.OutOfMemory;
416 if (stdout_context.data.items.len != 0) {
417 std.log.warn("unexpected LLD stdout: {}", .{stdout_context.data.items});
418 }
419 if (!ok) {
420 // TODO parse this output and surface with the Compilation API rather than
421 // directly outputting to stderr here.
422 std.debug.print("{}", .{stderr_context.data.items});
423 return error.LLDReportedFailure;
424 }
425 if (stderr_context.data.items.len != 0) {
426 std.log.warn("unexpected LLD stderr: {}", .{stderr_context.data.items});
427 }
428
429 if (!self.base.options.disable_lld_caching) {
430 // Update the dangling symlink with the digest. If it fails we can continue; it only
431 // means that the next invocation will have an unnecessary cache miss.
432 directory.handle.symLink(&digest, id_symlink_basename, .{}) catch |err| {
433 std.log.warn("failed to save linking hash digest symlink: {}", .{@errorName(err)});
434 };
435 // Again failure here only means an unnecessary cache miss.
436 man.writeManifest() catch |err| {
437 std.log.warn("failed to write cache manifest when linking: {}", .{@errorName(err)});
438 };
439 // We hang on to this lock so that the output file path can be used without
440 // other processes clobbering it.
441 self.base.lock = man.toOwnedLock();
442 }
443}
444
445const LLDContext = struct {
446 data: std.ArrayList(u8),
447 wasm: *Wasm,
448 oom: bool = false,
449};
450
451fn append_diagnostic(context: usize, ptr: [*]const u8, len: usize) callconv(.C) void {
452 const lld_context = @intToPtr(*LLDContext, context);
453 const msg = ptr[0..len];
454 lld_context.data.appendSlice(msg) catch |err| switch (err) {
455 error.OutOfMemory => lld_context.oom = true,
456 };
457}
458
251/// Get the current index of a given Decl in the function list459/// Get the current index of a given Decl in the function list
252/// TODO: we could maintain a hash map to potentially make this460/// TODO: we could maintain a hash map to potentially make this
253fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 {461fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 {