authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-01-03 20:03:22-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-01-03 20:03:22-07:00
logff66a18555dc6c00bd928f48462c3fc44f86ab6c
treee30f1af8d95b1e634d4076cbc521ee2b35d5c90c
parent81fa31c05456facea1d1963a1e7f665351fb248d

linker: fix build-obj and -fno-emit-bin

This commit fixes two problems: * `zig build-obj` regressed from the cache-mode branch. It would crash because it assumed that dirname on the emit bin path would not be null. This assumption was invalid when outputting to the current working directory - a pretty common use case for `zig build-obj`. * When using the LLVM backend, `-fno-emit-bin` combined with any other kind of emitting, such as `-femit-asm`, emitted nothing. Both issues are now fixed.

7 files changed, 129 insertions(+), 73 deletions(-)

src/codegen/llvm.zig+14-18
......@@ -181,8 +181,6 @@ pub const Object = struct {
181181 /// The backing memory for `type_map`. Periodically garbage collected after flush().
182182 /// The code for doing the periodical GC is not yet implemented.
183183 type_map_arena: std.heap.ArenaAllocator,
184 /// Where to put the output object file, relative to bin_file.options.emit directory.
185 sub_path: []const u8,
186184
187185 pub const TypeMap = std.HashMapUnmanaged(
188186 Type,
......@@ -191,14 +189,14 @@ pub const Object = struct {
191189 std.hash_map.default_max_load_percentage,
192190 );
193191
194 pub fn create(gpa: Allocator, sub_path: []const u8, options: link.Options) !*Object {
192 pub fn create(gpa: Allocator, options: link.Options) !*Object {
195193 const obj = try gpa.create(Object);
196194 errdefer gpa.destroy(obj);
197 obj.* = try Object.init(gpa, sub_path, options);
195 obj.* = try Object.init(gpa, options);
198196 return obj;
199197 }
200198
201 pub fn init(gpa: Allocator, sub_path: []const u8, options: link.Options) !Object {
199 pub fn init(gpa: Allocator, options: link.Options) !Object {
202200 const context = llvm.Context.create();
203201 errdefer context.dispose();
204202
......@@ -271,7 +269,6 @@ pub const Object = struct {
271269 .decl_map = .{},
272270 .type_map = .{},
273271 .type_map_arena = std.heap.ArenaAllocator.init(gpa),
274 .sub_path = sub_path,
275272 };
276273 }
277274
......@@ -324,19 +321,22 @@ pub const Object = struct {
324321 const mod = comp.bin_file.options.module.?;
325322 const cache_dir = mod.zig_cache_artifact_directory;
326323
327 const emit_bin_path: ?[*:0]const u8 = if (comp.bin_file.options.emit) |emit| blk: {
328 const full_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path});
329 break :blk try std.fs.path.joinZ(arena, &.{
330 std.fs.path.dirname(full_out_path).?, self.sub_path,
331 });
332 } else null;
324 const emit_bin_path: ?[*:0]const u8 = if (comp.bin_file.options.emit) |emit|
325 try emit.basenamePath(arena, try arena.dupeZ(u8, comp.bin_file.intermediary_basename.?))
326 else
327 null;
333328
334329 const emit_asm_path = try locPath(arena, comp.emit_asm, cache_dir);
335330 const emit_llvm_ir_path = try locPath(arena, comp.emit_llvm_ir, cache_dir);
336331 const emit_llvm_bc_path = try locPath(arena, comp.emit_llvm_bc, cache_dir);
337332
338 const debug_emit_path = emit_bin_path orelse "(none)";
339 log.debug("emit LLVM object to {s}", .{debug_emit_path});
333 const emit_asm_msg = emit_asm_path orelse "(none)";
334 const emit_bin_msg = emit_bin_path orelse "(none)";
335 const emit_llvm_ir_msg = emit_llvm_ir_path orelse "(none)";
336 const emit_llvm_bc_msg = emit_llvm_bc_path orelse "(none)";
337 log.debug("emit LLVM object asm={s} bin={s} ir={s} bc={s}", .{
338 emit_asm_msg, emit_bin_msg, emit_llvm_ir_msg, emit_llvm_bc_msg,
339 });
340340
341341 var error_message: [*:0]const u8 = undefined;
342342 if (self.target_machine.emitToFile(
......@@ -354,10 +354,6 @@ pub const Object = struct {
354354 )) {
355355 defer llvm.disposeMessage(error_message);
356356
357 const emit_asm_msg = emit_asm_path orelse "(none)";
358 const emit_bin_msg = emit_bin_path orelse "(none)";
359 const emit_llvm_ir_msg = emit_llvm_ir_path orelse "(none)";
360 const emit_llvm_bc_msg = emit_llvm_bc_path orelse "(none)";
361357 log.err("LLVM failed to emit asm={s} bin={s} ir={s} bc={s}: {s}", .{
362358 emit_asm_msg, emit_bin_msg, emit_llvm_ir_msg, emit_llvm_bc_msg,
363359 error_message,
src/glibc.zig+2-2
......@@ -225,7 +225,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
225225 });
226226 },
227227 .scrt1_o => {
228 const start_os: Compilation.CSourceFile = blk: {
228 const start_o: Compilation.CSourceFile = blk: {
229229 var args = std.ArrayList([]const u8).init(arena);
230230 try add_include_dirs(comp, arena, &args);
231231 try args.appendSlice(&[_][]const u8{
......@@ -266,7 +266,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
266266 .extra_flags = args.items,
267267 };
268268 };
269 return comp.build_crt_file("Scrt1", .Obj, &[_]Compilation.CSourceFile{ start_os, abi_note_o });
269 return comp.build_crt_file("Scrt1", .Obj, &[_]Compilation.CSourceFile{ start_o, abi_note_o });
270270 },
271271 .libc_nonshared_a => {
272272 const target = comp.getTarget();
src/link.zig+16-2
......@@ -43,6 +43,21 @@ pub const Emit = struct {
4343 directory: Compilation.Directory,
4444 /// Path to the output file, relative to `directory`.
4545 sub_path: []const u8,
46
47 /// Returns the full path to `basename` if it were in the same directory as the
48 /// `Emit` sub_path.
49 pub fn basenamePath(emit: Emit, arena: Allocator, basename: [:0]const u8) ![:0]const u8 {
50 const full_path = if (emit.directory.path) |p|
51 try fs.path.join(arena, &[_][]const u8{ p, emit.sub_path })
52 else
53 emit.sub_path;
54
55 if (fs.path.dirname(full_path)) |dirname| {
56 return try fs.path.joinZ(arena, &.{ dirname, basename });
57 } else {
58 return basename;
59 }
60 }
4661};
4762
4863pub const Options = struct {
......@@ -533,9 +548,8 @@ pub const File = struct {
533548 /// Commit pending changes and write headers. Takes into account final output mode
534549 /// and `use_lld`, not only `effectiveOutputMode`.
535550 pub fn flush(base: *File, comp: *Compilation) !void {
536 const emit = base.options.emit orelse return; // -fno-emit-bin
537
538551 if (comp.clang_preprocessor_mode == .yes) {
552 const emit = base.options.emit orelse return; // -fno-emit-bin
539553 // TODO: avoid extra link step when it's just 1 object file (the `zig cc -c` case)
540554 // Until then, we do `lld -r -o output.o input.o` even though the output is the same
541555 // as the input. For the preprocessing case (`zig cc -E -o foo`) we copy the file
src/link/Coff.zig+18-8
......@@ -129,11 +129,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
129129 assert(options.object_format == .coff);
130130
131131 if (build_options.have_llvm and options.use_llvm) {
132 const self = try createEmpty(allocator, options);
133 errdefer self.base.destroy();
134
135 self.llvm_object = try LlvmObject.create(allocator, sub_path, options);
136 return self;
132 return createEmpty(allocator, options);
137133 }
138134
139135 const file = try options.emit.?.directory.handle.createFile(sub_path, .{
......@@ -403,6 +399,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff {
403399 else => return error.UnsupportedCOFFArchitecture,
404400 };
405401 const self = try gpa.create(Coff);
402 errdefer gpa.destroy(self);
406403 self.* = .{
407404 .base = .{
408405 .tag = .coff,
......@@ -412,6 +409,9 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff {
412409 },
413410 .ptr_width = ptr_width,
414411 };
412 if (build_options.have_llvm and options.use_llvm) {
413 self.llvm_object = try LlvmObject.create(gpa, options);
414 }
415415 return self;
416416}
417417
......@@ -817,6 +817,14 @@ pub fn updateDeclExports(
817817}
818818
819819pub fn flush(self: *Coff, comp: *Compilation) !void {
820 if (self.base.options.emit == null) {
821 if (build_options.have_llvm) {
822 if (self.llvm_object) |llvm_object| {
823 return try llvm_object.flushModule(comp);
824 }
825 }
826 return;
827 }
820828 if (build_options.have_llvm and self.base.options.use_lld) {
821829 return self.linkWithLLD(comp);
822830 } else {
......@@ -905,9 +913,11 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void {
905913
906914 try self.flushModule(comp);
907915
908 break :blk try fs.path.join(arena, &.{
909 fs.path.dirname(full_out_path).?, self.base.intermediary_basename.?,
910 });
916 if (fs.path.dirname(full_out_path)) |dirname| {
917 break :blk try fs.path.join(arena, &.{ dirname, self.base.intermediary_basename.? });
918 } else {
919 break :blk self.base.intermediary_basename.?;
920 }
911921 } else null;
912922
913923 const is_lib = self.base.options.output_mode == .Lib;
src/link/Elf.zig+36-25
......@@ -241,11 +241,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
241241 assert(options.object_format == .elf);
242242
243243 if (build_options.have_llvm and options.use_llvm) {
244 const self = try createEmpty(allocator, options);
245 errdefer self.base.destroy();
246
247 self.llvm_object = try LlvmObject.create(allocator, sub_path, options);
248 return self;
244 return createEmpty(allocator, options);
249245 }
250246
251247 const file = try options.emit.?.directory.handle.createFile(sub_path, .{
......@@ -298,6 +294,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Elf {
298294 };
299295 const self = try gpa.create(Elf);
300296 errdefer gpa.destroy(self);
297
301298 self.* = .{
302299 .base = .{
303300 .tag = .elf,
......@@ -307,9 +304,9 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Elf {
307304 },
308305 .ptr_width = ptr_width,
309306 };
310 // TODO get rid of the sub_path parameter to LlvmObject.create
311 // and create the llvm_object here. Also openPath needs to
312 // not override this field or there will be a memory leak.
307 if (build_options.have_llvm and options.use_llvm) {
308 self.llvm_object = try LlvmObject.create(gpa, options);
309 }
313310 return self;
314311}
315312
......@@ -788,14 +785,21 @@ pub const abbrev_pad1 = 5;
788785pub const abbrev_parameter = 6;
789786
790787pub fn flush(self: *Elf, comp: *Compilation) !void {
791 if (build_options.have_llvm and self.base.options.use_lld) {
792 return self.linkWithLLD(comp);
793 } else {
794 switch (self.base.options.effectiveOutputMode()) {
795 .Exe, .Obj => {},
796 .Lib => return error.TODOImplementWritingLibFiles,
788 if (self.base.options.emit == null) {
789 if (build_options.have_llvm) {
790 if (self.llvm_object) |llvm_object| {
791 return try llvm_object.flushModule(comp);
792 }
797793 }
798 return self.flushModule(comp);
794 return;
795 }
796 const use_lld = build_options.have_llvm and self.base.options.use_lld;
797 if (use_lld) {
798 return self.linkWithLLD(comp);
799 }
800 switch (self.base.options.output_mode) {
801 .Exe, .Obj => return self.flushModule(comp),
802 .Lib => return error.TODOImplementWritingLibFiles,
799803 }
800804}
801805
......@@ -803,8 +807,11 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void {
803807 const tracy = trace(@src());
804808 defer tracy.end();
805809
806 if (build_options.have_llvm)
807 if (self.llvm_object) |llvm_object| return try llvm_object.flushModule(comp);
810 if (build_options.have_llvm) {
811 if (self.llvm_object) |llvm_object| {
812 return try llvm_object.flushModule(comp);
813 }
814 }
808815
809816 // TODO This linker code currently assumes there is only 1 compilation unit and it
810817 // corresponds to the Zig source code.
......@@ -1327,9 +1334,11 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
13271334
13281335 try self.flushModule(comp);
13291336
1330 break :blk try fs.path.join(arena, &.{
1331 fs.path.dirname(full_out_path).?, self.base.intermediary_basename.?,
1332 });
1337 if (fs.path.dirname(full_out_path)) |dirname| {
1338 break :blk try fs.path.join(arena, &.{ dirname, self.base.intermediary_basename.? });
1339 } else {
1340 break :blk self.base.intermediary_basename.?;
1341 }
13331342 } else null;
13341343
13351344 const is_obj = self.base.options.output_mode == .Obj;
......@@ -1446,10 +1455,13 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
14461455 };
14471456 }
14481457
1449 // Due to a deficiency in LLD, we need to special-case BPF to a simple file copy when generating
1450 // relocatables. Normally, we would expect `lld -r` to work. However, because LLD wants to resolve
1451 // BPF relocations which it shouldn't, it fails before even generating the relocatable.
1452 if (self.base.options.output_mode == .Obj and (self.base.options.lto or target.isBpfFreestanding())) {
1458 // Due to a deficiency in LLD, we need to special-case BPF to a simple file
1459 // copy when generating relocatables. Normally, we would expect `lld -r` to work.
1460 // However, because LLD wants to resolve BPF relocations which it shouldn't, it fails
1461 // before even generating the relocatable.
1462 if (self.base.options.output_mode == .Obj and
1463 (self.base.options.lto or target.isBpfFreestanding()))
1464 {
14531465 // In this case we must do a simple file copy
14541466 // here. TODO: think carefully about how we can avoid this redundant operation when doing
14551467 // build-obj. See also the corresponding TODO in linkAsArchive.
......@@ -1473,7 +1485,6 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
14731485 try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{});
14741486 }
14751487 } else {
1476
14771488 // Create an LLD command line and invoke it.
14781489 var argv = std.ArrayList([]const u8).init(self.base.allocator);
14791490 defer argv.deinit();
src/link/MachO.zig+22-7
......@@ -313,11 +313,10 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO {
313313 // TODO this intermediary_basename isn't enough; in the case of `zig build-exe`,
314314 // we also want to put the intermediary object file in the cache while the
315315 // main emit directory is the cwd.
316 const sub_path = try std.fmt.allocPrint(allocator, "{s}{s}", .{
316 self.llvm_object = try LlvmObject.create(allocator, options);
317 self.base.intermediary_basename = try std.fmt.allocPrint(allocator, "{s}{s}", .{
317318 emit.sub_path, options.object_format.fileExt(options.target.cpu.arch),
318319 });
319 self.llvm_object = try LlvmObject.create(allocator, sub_path, options);
320 self.base.intermediary_basename = sub_path;
321320 }
322321
323322 if (options.output_mode == .Lib and
......@@ -373,7 +372,6 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO {
373372}
374373
375374pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO {
376 const self = try gpa.create(MachO);
377375 const cpu_arch = options.target.cpu.arch;
378376 const os_tag = options.target.os.tag;
379377 const abi = options.target.abi;
......@@ -383,6 +381,9 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO {
383381 const requires_adhoc_codesig = cpu_arch == .aarch64 and (os_tag == .macos or abi == .simulator);
384382 const needs_prealloc = !(build_options.is_stage1 and options.use_stage1);
385383
384 const self = try gpa.create(MachO);
385 errdefer gpa.destroy(self);
386
386387 self.* = .{
387388 .base = .{
388389 .tag = .macho,
......@@ -395,10 +396,22 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO {
395396 .needs_prealloc = needs_prealloc,
396397 };
397398
399 if (build_options.have_llvm and options.use_llvm) {
400 self.llvm_object = try LlvmObject.create(gpa, options);
401 }
402
398403 return self;
399404}
400405
401406pub fn flush(self: *MachO, comp: *Compilation) !void {
407 if (self.base.options.emit == null) {
408 if (build_options.have_llvm) {
409 if (self.llvm_object) |llvm_object| {
410 return try llvm_object.flushModule(comp);
411 }
412 }
413 return;
414 }
402415 if (self.base.options.output_mode == .Lib and self.base.options.link_mode == .Static) {
403416 if (build_options.have_llvm) {
404417 return self.base.linkAsArchive(comp);
......@@ -449,9 +462,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
449462
450463 try self.flushObject(comp);
451464
452 break :blk try fs.path.join(arena, &.{
453 fs.path.dirname(full_out_path).?, obj_basename,
454 });
465 if (fs.path.dirname(full_out_path)) |dirname| {
466 break :blk try fs.path.join(arena, &.{ dirname, obj_basename });
467 } else {
468 break :blk obj_basename;
469 }
455470 } else null;
456471
457472 const is_lib = self.base.options.output_mode == .Lib;
src/link/Wasm.zig+21-11
......@@ -101,11 +101,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
101101 assert(options.object_format == .wasm);
102102
103103 if (build_options.have_llvm and options.use_llvm) {
104 const self = try createEmpty(allocator, options);
105 errdefer self.base.destroy();
106
107 self.llvm_object = try LlvmObject.create(allocator, sub_path, options);
108 return self;
104 return createEmpty(allocator, options);
109105 }
110106
111107 // TODO: read the file and keep valid parts instead of truncating
......@@ -139,8 +135,9 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
139135}
140136
141137pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm {
142 const wasm_bin = try gpa.create(Wasm);
143 wasm_bin.* = .{
138 const self = try gpa.create(Wasm);
139 errdefer gpa.destroy(self);
140 self.* = .{
144141 .base = .{
145142 .tag = .wasm,
146143 .options = options,
......@@ -148,7 +145,10 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm {
148145 .allocator = gpa,
149146 },
150147 };
151 return wasm_bin;
148 if (build_options.have_llvm and options.use_llvm) {
149 self.llvm_object = try LlvmObject.create(gpa, options);
150 }
151 return self;
152152}
153153
154154pub fn deinit(self: *Wasm) void {
......@@ -576,6 +576,14 @@ fn resetState(self: *Wasm) void {
576576}
577577
578578pub fn flush(self: *Wasm, comp: *Compilation) !void {
579 if (self.base.options.emit == null) {
580 if (build_options.have_llvm) {
581 if (self.llvm_object) |llvm_object| {
582 return try llvm_object.flushModule(comp);
583 }
584 }
585 return;
586 }
579587 if (build_options.have_llvm and self.base.options.use_lld) {
580588 return self.linkWithLLD(comp);
581589 } else {
......@@ -1075,9 +1083,11 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
10751083
10761084 try self.flushModule(comp);
10771085
1078 break :blk try fs.path.join(arena, &.{
1079 fs.path.dirname(full_out_path).?, self.base.intermediary_basename.?,
1080 });
1086 if (fs.path.dirname(full_out_path)) |dirname| {
1087 break :blk try fs.path.join(arena, &.{ dirname, self.base.intermediary_basename.? });
1088 } else {
1089 break :blk self.base.intermediary_basename.?;
1090 }
10811091 } else null;
10821092
10831093 const is_obj = self.base.options.output_mode == .Obj;