diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2022-08-30 13:02:43 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2022-08-30 13:02:43 -0700 |
| commit | 1e21876de25f5725dd093f2243bcfdb3f6c6c44e (patch) | |
| tree | 1482b1d8b7cb025006c02b9705295d3e50cf44e1 /src | |
| parent | 1cb8065a52c9bf74f913c46a011ab0118c92f0fa (diff) | |
| parent | f559ea95b1c37fd6ede8fff6ffb2d74d5c2abc4e (diff) | |
| download | zig-1e21876de25f5725dd093f2243bcfdb3f6c6c44e.tar.gz zig-1e21876de25f5725dd093f2243bcfdb3f6c6c44e.zip | |
Merge remote-tracking branch 'origin/master' into llvm15
Diffstat (limited to 'src')
| -rw-r--r-- | src/AstGen.zig | 33 | ||||
| -rw-r--r-- | src/Autodoc.zig | 2 | ||||
| -rw-r--r-- | src/Compilation.zig | 37 | ||||
| -rw-r--r-- | src/Module.zig | 11 | ||||
| -rw-r--r-- | src/Sema.zig | 175 | ||||
| -rw-r--r-- | src/Zir.zig | 15 | ||||
| -rw-r--r-- | src/arch/aarch64/CodeGen.zig | 16 | ||||
| -rw-r--r-- | src/arch/arm/CodeGen.zig | 14 | ||||
| -rw-r--r-- | src/arch/riscv64/CodeGen.zig | 16 | ||||
| -rw-r--r-- | src/arch/x86_64/CodeGen.zig | 66 | ||||
| -rw-r--r-- | src/arch/x86_64/Emit.zig | 40 | ||||
| -rw-r--r-- | src/arch/x86_64/Mir.zig | 13 | ||||
| -rw-r--r-- | src/codegen/llvm.zig | 137 | ||||
| -rw-r--r-- | src/link.zig | 13 | ||||
| -rw-r--r-- | src/link/Coff.zig | 2433 | ||||
| -rw-r--r-- | src/link/Coff/Atom.zig | 110 | ||||
| -rw-r--r-- | src/link/Coff/Object.zig | 12 | ||||
| -rw-r--r-- | src/link/Coff/lld.zig | 602 | ||||
| -rw-r--r-- | src/link/MachO.zig | 3 | ||||
| -rw-r--r-- | src/link/MachO/DebugSymbols.zig | 1 | ||||
| -rw-r--r-- | src/link/Wasm.zig | 21 | ||||
| -rw-r--r-- | src/link/strtab.zig | 4 | ||||
| -rw-r--r-- | src/main.zig | 6 | ||||
| -rw-r--r-- | src/mingw.zig | 8 | ||||
| -rw-r--r-- | src/print_zir.zig | 11 | ||||
| -rw-r--r-- | src/target.zig | 114 | ||||
| -rw-r--r-- | src/type.zig | 4 |
27 files changed, 2443 insertions, 1474 deletions
diff --git a/src/AstGen.zig b/src/AstGen.zig index 943d0aad08..79e5ad963e 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -226,6 +226,8 @@ pub const ResultLoc = union(enum) { ref, /// The expression will be coerced into this type, but it will be evaluated as an rvalue. ty: Zir.Inst.Ref, + /// Same as `ty` but for shift operands. + ty_shift_operand: Zir.Inst.Ref, /// Same as `ty` but it is guaranteed that Sema will additionally perform the coercion, /// so no `as` instruction needs to be emitted. coerced_ty: Zir.Inst.Ref, @@ -259,7 +261,7 @@ pub const ResultLoc = union(enum) { fn strategy(rl: ResultLoc, block_scope: *GenZir) Strategy { switch (rl) { // In this branch there will not be any store_to_block_ptr instructions. - .none, .ty, .coerced_ty, .ref => return .{ + .none, .ty, .ty_shift_operand, .coerced_ty, .ref => return .{ .tag = .break_operand, .elide_store_to_block_ptr_instructions = false, }, @@ -302,6 +304,14 @@ pub const ResultLoc = union(enum) { else => rl, }; } + + fn zirTag(rl: ResultLoc) Zir.Inst.Tag { + return switch (rl) { + .ty => .as_node, + .ty_shift_operand => .as_shift_operand, + else => unreachable, + }; + } }; pub const align_rl: ResultLoc = .{ .ty = .u29_type }; @@ -1385,7 +1395,7 @@ fn arrayInitExpr( const tag: Zir.Inst.Tag = if (types.array != .none) .array_init else .array_init_anon; return arrayInitExprInner(gz, scope, node, array_init.ast.elements, types.array, types.elem, tag); }, - .ty, .coerced_ty => { + .ty, .ty_shift_operand, .coerced_ty => { const tag: Zir.Inst.Tag = if (types.array != .none) .array_init else .array_init_anon; const result = try arrayInitExprInner(gz, scope, node, array_init.ast.elements, types.array, types.elem, tag); return rvalue(gz, rl, result, node); @@ -1631,7 +1641,7 @@ fn structInitExpr( return structInitExprRlNone(gz, scope, node, struct_init, .none, .struct_init_anon); } }, - .ty, .coerced_ty => |ty_inst| { + .ty, .ty_shift_operand, .coerced_ty => |ty_inst| { if (struct_init.ast.type_expr == 0) { const result = try structInitExprRlNone(gz, scope, node, struct_init, ty_inst, .struct_init_anon); return rvalue(gz, rl, result, node); @@ -2327,6 +2337,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .anyframe_type, .as, .as_node, + .as_shift_operand, .bit_and, .bitcast, .bit_or, @@ -2497,7 +2508,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .field_parent_ptr, .maximum, .minimum, - .builtin_async_call, .c_import, .@"resume", .@"await", @@ -7278,7 +7288,7 @@ fn as( ) InnerError!Zir.Inst.Ref { const dest_type = try typeExpr(gz, scope, lhs); switch (rl) { - .none, .discard, .ref, .ty, .coerced_ty => { + .none, .discard, .ref, .ty, .ty_shift_operand, .coerced_ty => { const result = try reachableExpr(gz, scope, .{ .ty = dest_type }, rhs, node); return rvalue(gz, rl, result, node); }, @@ -7959,7 +7969,8 @@ fn builtinCall( return rvalue(gz, rl, result, node); }, .async_call => { - const result = try gz.addPlNode(.builtin_async_call, node, Zir.Inst.AsyncCall{ + const result = try gz.addExtendedPayload(.builtin_async_call, Zir.Inst.AsyncCall{ + .node = gz.nodeIndexToRelative(node), .frame_buffer = try expr(gz, scope, .none, params[0]), .result_ptr = try expr(gz, scope, .none, params[1]), .fn_ptr = try expr(gz, scope, .none, params[2]), @@ -8178,7 +8189,7 @@ fn shiftOp( ) InnerError!Zir.Inst.Ref { const lhs = try expr(gz, scope, .none, lhs_node); const log2_int_type = try gz.addUnNode(.typeof_log2_int_type, lhs, lhs_node); - const rhs = try expr(gz, scope, .{ .ty = log2_int_type }, rhs_node); + const rhs = try expr(gz, scope, .{ .ty_shift_operand = log2_int_type }, rhs_node); const result = try gz.addPlNode(tag, node, Zir.Inst.Bin{ .lhs = lhs, .rhs = rhs, @@ -9409,7 +9420,7 @@ fn rvalue( } return indexToRef(gop.value_ptr.*); }, - .ty => |ty_inst| { + .ty, .ty_shift_operand => |ty_inst| { // Quickly eliminate some common, unnecessary type coercion. const as_ty = @as(u64, @enumToInt(Zir.Inst.Ref.type_type)) << 32; const as_comptime_int = @as(u64, @enumToInt(Zir.Inst.Ref.comptime_int_type)) << 32; @@ -9470,7 +9481,7 @@ fn rvalue( => return result, // type of result is already correct // Need an explicit type coercion instruction. - else => return gz.addPlNode(.as_node, src_node, Zir.Inst.As{ + else => return gz.addPlNode(rl.zirTag(), src_node, Zir.Inst.As{ .dest_type = ty_inst, .operand = result, }), @@ -10350,7 +10361,7 @@ const GenZir = struct { // we emit ZIR for the block break instructions to have the result values, // and then rvalue() on that to pass the value to the result location. switch (parent_rl) { - .ty, .coerced_ty => |ty_inst| { + .ty, .ty_shift_operand, .coerced_ty => |ty_inst| { gz.rl_ty_inst = ty_inst; gz.break_result_loc = parent_rl; }, @@ -11506,7 +11517,7 @@ const GenZir = struct { fn addRet(gz: *GenZir, rl: ResultLoc, operand: Zir.Inst.Ref, node: Ast.Node.Index) !void { switch (rl) { .ptr => |ret_ptr| _ = try gz.addUnNode(.ret_load, ret_ptr, node), - .ty => _ = try gz.addUnNode(.ret_node, operand, node), + .ty, .ty_shift_operand => _ = try gz.addUnNode(.ret_node, operand, node), else => unreachable, } } diff --git a/src/Autodoc.zig b/src/Autodoc.zig index db681157ae..0e056c093f 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -1888,7 +1888,7 @@ fn walkInstruction( .expr = .{ .typeInfo = operand_index }, }; }, - .as_node => { + .as_node, .as_shift_operand => { const pl_node = data[inst_index].pl_node; const extra = file.zir.extraData(Zir.Inst.As, pl_node.payload_index); const dest_type_walk = try self.walkRef( diff --git a/src/Compilation.zig b/src/Compilation.zig index 32d06e5907..c1321e40cf 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1127,7 +1127,6 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { link_eh_frame_hdr or options.link_emit_relocs or options.output_mode == .Lib or - options.image_base_override != null or options.linker_script != null or options.version_script != null or options.emit_implib != null or build_id) @@ -4767,6 +4766,24 @@ pub fn dump_argv(argv: []const []const u8) void { std.debug.print("{s}\n", .{argv[argv.len - 1]}); } +pub fn getZigBackend(comp: Compilation) std.builtin.CompilerBackend { + const use_stage1 = build_options.have_stage1 and comp.bin_file.options.use_stage1; + if (use_stage1) return .stage1; + if (build_options.have_llvm and comp.bin_file.options.use_llvm) return .stage2_llvm; + const target = comp.bin_file.options.target; + if (target.ofmt == .c) return .stage2_c; + return switch (target.cpu.arch) { + .wasm32, .wasm64 => std.builtin.CompilerBackend.stage2_wasm, + .arm, .armeb, .thumb, .thumbeb => .stage2_arm, + .x86_64 => .stage2_x86_64, + .i386 => .stage2_x86, + .aarch64, .aarch64_be, .aarch64_32 => .stage2_aarch64, + .riscv64 => .stage2_riscv64, + .sparc64 => .stage2_sparc64, + else => .other, + }; +} + pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Allocator.Error![:0]u8 { const tracy_trace = trace(@src()); defer tracy_trace.end(); @@ -4776,23 +4793,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca const target = comp.getTarget(); const generic_arch_name = target.cpu.arch.genericName(); - const use_stage1 = build_options.have_stage1 and comp.bin_file.options.use_stage1; - - const zig_backend: std.builtin.CompilerBackend = blk: { - if (use_stage1) break :blk .stage1; - if (build_options.have_llvm and comp.bin_file.options.use_llvm) break :blk .stage2_llvm; - if (target.ofmt == .c) break :blk .stage2_c; - break :blk switch (target.cpu.arch) { - .wasm32, .wasm64 => std.builtin.CompilerBackend.stage2_wasm, - .arm, .armeb, .thumb, .thumbeb => .stage2_arm, - .x86_64 => .stage2_x86_64, - .i386 => .stage2_x86, - .aarch64, .aarch64_be, .aarch64_32 => .stage2_aarch64, - .riscv64 => .stage2_riscv64, - .sparc64 => .stage2_sparc64, - else => .other, - }; - }; + const zig_backend = comp.getZigBackend(); @setEvalBranchQuota(4000); try buffer.writer().print( diff --git a/src/Module.zig b/src/Module.zig index a92849e127..66c36b939b 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -5259,9 +5259,9 @@ pub fn clearDecl( // TODO instead of a union, put this memory trailing Decl objects, // and allow it to be variably sized. decl.link = switch (mod.comp.bin_file.tag) { - .coff => .{ .coff = link.File.Coff.TextBlock.empty }, + .coff => .{ .coff = link.File.Coff.Atom.empty }, .elf => .{ .elf = link.File.Elf.TextBlock.empty }, - .macho => .{ .macho = link.File.MachO.TextBlock.empty }, + .macho => .{ .macho = link.File.MachO.Atom.empty }, .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty }, .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty }, @@ -5391,6 +5391,9 @@ fn deleteDeclExports(mod: *Module, decl_index: Decl.Index) void { if (mod.comp.bin_file.cast(link.File.Wasm)) |wasm| { wasm.deleteExport(exp.link.wasm); } + if (mod.comp.bin_file.cast(link.File.Coff)) |coff| { + coff.deleteExport(exp.link.coff); + } if (mod.failed_exports.fetchSwapRemove(exp)) |failed_kv| { failed_kv.value.destroy(mod.gpa); } @@ -5680,9 +5683,9 @@ pub fn allocateNewDecl( .zir_decl_index = 0, .src_scope = src_scope, .link = switch (mod.comp.bin_file.tag) { - .coff => .{ .coff = link.File.Coff.TextBlock.empty }, + .coff => .{ .coff = link.File.Coff.Atom.empty }, .elf => .{ .elf = link.File.Elf.TextBlock.empty }, - .macho => .{ .macho = link.File.MachO.TextBlock.empty }, + .macho => .{ .macho = link.File.MachO.Atom.empty }, .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty }, .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty }, diff --git a/src/Sema.zig b/src/Sema.zig index f884684d73..0626fd30ee 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -712,6 +712,7 @@ fn analyzeBodyInner( .vector_type => try sema.zirVectorType(block, inst), .as => try sema.zirAs(block, inst), .as_node => try sema.zirAsNode(block, inst), + .as_shift_operand => try sema.zirAsShiftOperand(block, inst), .bit_and => try sema.zirBitwise(block, inst, .bit_and), .bit_not => try sema.zirBitNot(block, inst), .bit_or => try sema.zirBitwise(block, inst, .bit_or), @@ -848,7 +849,6 @@ fn analyzeBodyInner( .mul_add => try sema.zirMulAdd(block, inst), .builtin_call => try sema.zirBuiltinCall(block, inst), .field_parent_ptr => try sema.zirFieldParentPtr(block, inst), - .builtin_async_call => try sema.zirBuiltinAsyncCall(block, inst), .@"resume" => try sema.zirResume(block, inst), .@"await" => try sema.zirAwait(block, inst), .array_base_ptr => try sema.zirArrayBasePtr(block, inst), @@ -956,6 +956,7 @@ fn analyzeBodyInner( .error_to_int => try sema.zirErrorToInt( block, extended), .int_to_error => try sema.zirIntToError( block, extended), .reify => try sema.zirReify( block, extended, inst), + .builtin_async_call => try sema.zirBuiltinAsyncCall( block, extended), // zig fmt: on .fence => { try sema.zirFence(block, extended); @@ -5076,7 +5077,7 @@ pub fn analyzeExport( }, .src = src, .link = switch (mod.comp.bin_file.tag) { - .coff => .{ .coff = {} }, + .coff => .{ .coff = .{} }, .elf => .{ .elf = .{} }, .macho => .{ .macho = .{} }, .plan9 => .{ .plan9 = null }, @@ -6152,9 +6153,30 @@ fn analyzeCall( if (ensure_result_used) { try sema.ensureResultUsed(block, result, call_src); } + if (call_tag == .call_always_tail) { + return sema.handleTailCall(block, call_src, func_ty, result); + } return result; } +fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref { + const target = sema.mod.getTarget(); + const backend = sema.mod.comp.getZigBackend(); + if (!target_util.supportsTailCall(target, backend)) { + return sema.fail(block, call_src, "unable to perform tail call: compiler backend '{s}' does not support tail calls on target architecture '{s}' with the selected CPU feature flags", .{ + @tagName(backend), @tagName(target.cpu.arch), + }); + } + const func_decl = sema.mod.declPtr(sema.owner_func.?.owner_decl); + if (!func_ty.eql(func_decl.ty, sema.mod)) { + return sema.fail(block, call_src, "unable to perform tail call: type of function being called '{}' does not match type of calling function '{}'", .{ + func_ty.fmt(sema.mod), func_decl.ty.fmt(sema.mod), + }); + } + _ = try block.addUnOp(.ret, result); + return Air.Inst.Ref.unreachable_value; +} + fn analyzeInlineCallArg( sema: *Sema, arg_block: *Block, @@ -6670,7 +6692,8 @@ fn instantiateGenericCall( try sema.requireFunctionBlock(block, call_src); const comptime_args = callee.comptime_args.?; - const new_fn_info = mod.declPtr(callee.owner_decl).ty.fnInfo(); + const func_ty = mod.declPtr(callee.owner_decl).ty; + const new_fn_info = func_ty.fnInfo(); const runtime_args_len = @intCast(u32, new_fn_info.param_types.len); const runtime_args = try sema.arena.alloc(Air.Inst.Ref, runtime_args_len); { @@ -6717,7 +6740,7 @@ fn instantiateGenericCall( try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).Struct.fields.len + runtime_args_len); - const func_inst = try block.addInst(.{ + const result = try block.addInst(.{ .tag = call_tag, .data = .{ .pl_op = .{ .operand = callee_inst, @@ -6729,9 +6752,12 @@ fn instantiateGenericCall( sema.appendRefsAssumeCapacity(runtime_args); if (ensure_result_used) { - try sema.ensureResultUsed(block, func_inst, call_src); + try sema.ensureResultUsed(block, result, call_src); } - return func_inst; + if (call_tag == .call_always_tail) { + return sema.handleTailCall(block, call_src, func_ty, result); + } + return result; } fn emitDbgInline( @@ -8177,8 +8203,22 @@ fn zirParam( .is_comptime = comptime_syntax, .name = param_name, }); - const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison)); - try sema.inst_map.putNoClobber(sema.gpa, inst, result); + + if (is_comptime) { + // If this is a comptime parameter we can add a constant generic_poison + // since this is also a generic parameter. + const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison)); + try sema.inst_map.putNoClobber(sema.gpa, inst, result); + } else { + // Otherwise we need a dummy runtime instruction. + const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len); + try sema.air_instructions.append(sema.gpa, .{ + .tag = .alloc, + .data = .{ .ty = param_ty }, + }); + const result = Air.indexToRef(result_index); + try sema.inst_map.putNoClobber(sema.gpa, inst, result); + } } fn zirParamAnytype( @@ -8225,7 +8265,7 @@ fn zirAs(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst defer tracy.end(); const bin_inst = sema.code.instructions.items(.data)[inst].bin; - return sema.analyzeAs(block, sema.src, bin_inst.lhs, bin_inst.rhs); + return sema.analyzeAs(block, sema.src, bin_inst.lhs, bin_inst.rhs, false); } fn zirAsNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -8235,7 +8275,17 @@ fn zirAsNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); const extra = sema.code.extraData(Zir.Inst.As, inst_data.payload_index).data; - return sema.analyzeAs(block, src, extra.dest_type, extra.operand); + return sema.analyzeAs(block, src, extra.dest_type, extra.operand, false); +} + +fn zirAsShiftOperand(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const tracy = trace(@src()); + defer tracy.end(); + + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const extra = sema.code.extraData(Zir.Inst.As, inst_data.payload_index).data; + return sema.analyzeAs(block, src, extra.dest_type, extra.operand, true); } fn analyzeAs( @@ -8244,6 +8294,7 @@ fn analyzeAs( src: LazySrcLoc, zir_dest_type: Zir.Inst.Ref, zir_operand: Zir.Inst.Ref, + no_cast_to_comptime_int: bool, ) CompileError!Air.Inst.Ref { const is_ret = if (Zir.refToIndex(zir_dest_type)) |ptr_index| sema.code.instructions.items(.tag)[ptr_index] == .ret_type @@ -8255,7 +8306,7 @@ fn analyzeAs( if (dest_ty.zigTypeTag() == .NoReturn) { return sema.fail(block, src, "cannot cast to noreturn", .{}); } - return sema.coerceExtra(block, dest_ty, operand, src, true, is_ret) catch |err| switch (err) { + return sema.coerceExtra(block, dest_ty, operand, src, .{ .is_ret = is_ret, .no_cast_to_comptime_int = no_cast_to_comptime_int }) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -10459,7 +10510,12 @@ fn zirShl( const runtime_src = if (maybe_lhs_val) |lhs_val| rs: { if (lhs_val.isUndef()) return sema.addConstUndef(lhs_ty); - const rhs_val = maybe_rhs_val orelse break :rs rhs_src; + const rhs_val = maybe_rhs_val orelse { + if (scalar_ty.zigTypeTag() == .ComptimeInt) { + return sema.fail(block, src, "LHS of shift must be a fixed-width integer type, or RHS must be a comptime known", .{}); + } + break :rs rhs_src; + }; const val = switch (air_tag) { .shl_exact => val: { @@ -10583,7 +10639,10 @@ fn zirShr( const target = sema.mod.getTarget(); const scalar_ty = lhs_ty.scalarType(); - const runtime_src = if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| rs: { + const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs); + + const runtime_src = if (maybe_rhs_val) |rhs_val| rs: { if (rhs_val.isUndef()) { return sema.addConstUndef(lhs_ty); } @@ -10615,7 +10674,7 @@ fn zirShr( }); } } - if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { + if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { return sema.addConstUndef(lhs_ty); } @@ -10633,6 +10692,10 @@ fn zirShr( } } else rhs_src; + if (maybe_rhs_val == null and scalar_ty.zigTypeTag() == .ComptimeInt) { + return sema.fail(block, src, "LHS of shift must be a fixed-width integer type, or RHS must be a comptime known", .{}); + } + try sema.requireRuntimeBlock(block, src, runtime_src); const result = try block.addBinOp(air_tag, lhs, rhs); if (block.wantSafety()) { @@ -15353,7 +15416,7 @@ fn analyzeRet( if (sema.fn_ret_ty.zigTypeTag() == .ErrorUnion) { try sema.addToInferredErrorSet(uncasted_operand); } - const operand = sema.coerceExtra(block, sema.fn_ret_ty, uncasted_operand, src, true, true) catch |err| switch (err) { + const operand = sema.coerceExtra(block, sema.fn_ret_ty, uncasted_operand, src, .{ .is_ret = true }) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -19262,7 +19325,7 @@ fn resolveCallOptions( return wanted_modifier; }, // These can be upgraded to comptime. nosuspend bit can be safely ignored. - .always_tail, .always_inline, .compile_time => { + .always_inline, .compile_time => { _ = (try sema.resolveDefinedValue(block, func_src, func)) orelse { return sema.fail(block, func_src, "modifier '{s}' requires a comptime-known function", .{@tagName(wanted_modifier)}); }; @@ -19272,6 +19335,12 @@ fn resolveCallOptions( } return wanted_modifier; }, + .always_tail => { + if (is_comptime) { + return .compile_time; + } + return wanted_modifier; + }, .async_kw => { if (is_nosuspend) { return sema.fail(block, modifier_src, "modifier 'async_kw' cannot be used inside nosuspend block", .{}); @@ -19614,9 +19683,9 @@ fn zirMemset(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void }); } -fn zirBuiltinAsyncCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); +fn zirBuiltinAsyncCall(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref { + const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; + const src = LazySrcLoc.nodeOffset(extra.node); return sema.failWithUseOfAsync(block, src); } @@ -21871,6 +21940,7 @@ fn unionFieldPtr( .mutable = union_ptr_ty.ptrIsMutable(), .@"addrspace" = union_ptr_ty.ptrAddressSpace(), }); + const enum_field_index = @intCast(u32, union_obj.tag_ty.enumFieldIndex(field_name).?); if (initializing and field.ty.zigTypeTag() == .NoReturn) { const msg = msg: { @@ -21892,11 +21962,10 @@ fn unionFieldPtr( if (union_val.isUndef()) { return sema.failWithUseOfUndef(block, src); } - const enum_field_index = union_obj.tag_ty.enumFieldIndex(field_name).?; const tag_and_val = union_val.castTag(.@"union").?.data; var field_tag_buf: Value.Payload.U32 = .{ .base = .{ .tag = .enum_field_index }, - .data = @intCast(u32, enum_field_index), + .data = enum_field_index, }; const field_tag = Value.initPayload(&field_tag_buf.base); const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, sema.mod); @@ -21928,7 +21997,7 @@ fn unionFieldPtr( if (!initializing and union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null and union_obj.fields.count() > 1) { - const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index); const wanted_tag = try sema.addConstant(union_obj.tag_ty, wanted_tag_val); // TODO would it be better if get_union_tag supported pointers to unions? const union_val = try block.addTyOp(.load, union_ty, union_ptr); @@ -21958,15 +22027,15 @@ fn unionFieldVal( const union_obj = union_ty.cast(Type.Payload.Union).?.data; const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_name_src); const field = union_obj.fields.values()[field_index]; + const enum_field_index = @intCast(u32, union_obj.tag_ty.enumFieldIndex(field_name).?); if (try sema.resolveMaybeUndefVal(block, src, union_byval)) |union_val| { if (union_val.isUndef()) return sema.addConstUndef(field.ty); const tag_and_val = union_val.castTag(.@"union").?.data; - const enum_field_index = union_obj.tag_ty.enumFieldIndex(field_name).?; var field_tag_buf: Value.Payload.U32 = .{ .base = .{ .tag = .enum_field_index }, - .data = @intCast(u32, enum_field_index), + .data = enum_field_index, }; const field_tag = Value.initPayload(&field_tag_buf.base); const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, sema.mod); @@ -22002,7 +22071,7 @@ fn unionFieldVal( if (union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null and union_obj.fields.count() > 1) { - const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index); const wanted_tag = try sema.addConstant(union_obj.tag_ty, wanted_tag_val); const active_tag = try block.addTyOp(.get_union_tag, union_obj.tag_ty, union_byval); const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag); @@ -22504,7 +22573,7 @@ fn coerce( inst: Air.Inst.Ref, inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - return sema.coerceExtra(block, dest_ty_unresolved, inst, inst_src, true, false) catch |err| switch (err) { + return sema.coerceExtra(block, dest_ty_unresolved, inst, inst_src, .{}) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -22516,14 +22585,22 @@ const CoersionError = CompileError || error{ NotCoercible, }; +const CoerceOpts = struct { + /// Should coerceExtra emit error messages. + report_err: bool = true, + /// Ignored if `report_err == false`. + is_ret: bool = false, + /// Should coercion to comptime_int ermit an error message. + no_cast_to_comptime_int: bool = false, +}; + fn coerceExtra( sema: *Sema, block: *Block, dest_ty_unresolved: Type, inst: Air.Inst.Ref, inst_src: LazySrcLoc, - report_err: bool, - is_ret: bool, + opts: CoerceOpts, ) CoersionError!Air.Inst.Ref { switch (dest_ty_unresolved.tag()) { .var_args_param => return sema.coerceVarArgParam(block, inst, inst_src), @@ -22575,7 +22652,7 @@ fn coerceExtra( // T to ?T const child_type = try dest_ty.optionalChildAlloc(sema.arena); - const intermediate = sema.coerceExtra(block, child_type, inst, inst_src, false, is_ret) catch |err| switch (err) { + const intermediate = sema.coerceExtra(block, child_type, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) { error.NotCoercible => { if (in_memory_result == .no_match) { // Try to give more useful notes @@ -22691,7 +22768,7 @@ fn coerceExtra( return sema.addConstant(dest_ty, Value.@"null"); }, .ComptimeInt => { - const addr = sema.coerceExtra(block, Type.usize, inst, inst_src, false, is_ret) catch |err| switch (err) { + const addr = sema.coerceExtra(block, Type.usize, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) { error.NotCoercible => break :pointer, else => |e| return e, }; @@ -22702,7 +22779,7 @@ fn coerceExtra( .signed => Type.isize, .unsigned => Type.usize, }; - const addr = sema.coerceExtra(block, ptr_size_ty, inst, inst_src, false, is_ret) catch |err| switch (err) { + const addr = sema.coerceExtra(block, ptr_size_ty, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) { error.NotCoercible => { // Try to give more useful notes in_memory_result = try sema.coerceInMemoryAllowed(block, ptr_size_ty, inst_ty, false, target, dest_ty_src, inst_src); @@ -22828,7 +22905,13 @@ fn coerceExtra( }, .Int, .ComptimeInt => switch (inst_ty.zigTypeTag()) { .Float, .ComptimeFloat => float: { - const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse break :float; + const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse { + if (dest_ty.zigTypeTag() == .ComptimeInt) { + if (!opts.report_err) return error.NotCoercible; + return sema.failWithNeededComptime(block, inst_src, "value being casted to 'comptime_int' must be comptime known"); + } + break :float; + }; if (val.floatHasFraction()) { return sema.fail( @@ -22845,11 +22928,16 @@ fn coerceExtra( if (try sema.resolveDefinedValue(block, inst_src, inst)) |val| { // comptime known integer to other number if (!(try sema.intFitsInType(block, inst_src, val, dest_ty, null))) { - if (!report_err) return error.NotCoercible; + if (!opts.report_err) return error.NotCoercible; return sema.fail(block, inst_src, "type '{}' cannot represent integer value '{}'", .{ dest_ty.fmt(sema.mod), val.fmtValue(inst_ty, sema.mod) }); } return try sema.addConstant(dest_ty, val); } + if (dest_ty.zigTypeTag() == .ComptimeInt) { + if (!opts.report_err) return error.NotCoercible; + if (opts.no_cast_to_comptime_int) return inst; + return sema.failWithNeededComptime(block, inst_src, "value being casted to 'comptime_int' must be comptime known"); + } // integer widening const dst_info = dest_ty.intInfo(target); @@ -22886,6 +22974,7 @@ fn coerceExtra( } return try sema.addConstant(dest_ty, result_val); } else if (dest_ty.zigTypeTag() == .ComptimeFloat) { + if (!opts.report_err) return error.NotCoercible; return sema.failWithNeededComptime(block, inst_src, "value being casted to 'comptime_float' must be comptime known"); } @@ -22898,7 +22987,13 @@ fn coerceExtra( } }, .Int, .ComptimeInt => int: { - const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse break :int; + const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse { + if (dest_ty.zigTypeTag() == .ComptimeFloat) { + if (!opts.report_err) return error.NotCoercible; + return sema.failWithNeededComptime(block, inst_src, "value being casted to 'comptime_float' must be comptime known"); + } + break :int; + }; const result_val = try val.intToFloat(sema.arena, inst_ty, dest_ty, target); // TODO implement this compile error //const int_again_val = try result_val.floatToInt(sema.arena, inst_ty); @@ -23050,9 +23145,9 @@ fn coerceExtra( return sema.addConstUndef(dest_ty); } - if (!report_err) return error.NotCoercible; + if (!opts.report_err) return error.NotCoercible; - if (is_ret and dest_ty.zigTypeTag() == .NoReturn) { + if (opts.is_ret and dest_ty.zigTypeTag() == .NoReturn) { const msg = msg: { const msg = try sema.errMsg(block, inst_src, "function declared 'noreturn' returns", .{}); errdefer msg.destroy(sema.gpa); @@ -23089,7 +23184,7 @@ fn coerceExtra( try in_memory_result.report(sema, block, inst_src, msg); // Add notes about function return type - if (is_ret and sema.mod.test_functions.get(sema.func.?.owner_decl) == null) { + if (opts.is_ret and sema.mod.test_functions.get(sema.func.?.owner_decl) == null) { const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = 0 }; const src_decl = sema.mod.declPtr(sema.func.?.owner_decl); if (inst_ty.isError() and !dest_ty.isError()) { @@ -24050,7 +24145,7 @@ fn storePtr2( // https://github.com/ziglang/zig/issues/11154 if (sema.obtainBitCastedVectorPtr(ptr)) |vector_ptr| { const vector_ty = sema.typeOf(vector_ptr).childType(); - const vector = sema.coerceExtra(block, vector_ty, uncasted_operand, operand_src, true, is_ret) catch |err| switch (err) { + const vector = sema.coerceExtra(block, vector_ty, uncasted_operand, operand_src, .{ .is_ret = is_ret }) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -24058,7 +24153,7 @@ fn storePtr2( return; } - const operand = sema.coerceExtra(block, elem_ty, uncasted_operand, operand_src, true, is_ret) catch |err| switch (err) { + const operand = sema.coerceExtra(block, elem_ty, uncasted_operand, operand_src, .{ .is_ret = is_ret }) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -26793,7 +26888,7 @@ fn wrapErrorUnionPayload( inst_src: LazySrcLoc, ) !Air.Inst.Ref { const dest_payload_ty = dest_ty.errorUnionPayload(); - const coerced = try sema.coerceExtra(block, dest_payload_ty, inst, inst_src, false, false); + const coerced = try sema.coerceExtra(block, dest_payload_ty, inst, inst_src, .{ .report_err = false }); if (try sema.resolveMaybeUndefVal(block, inst_src, coerced)) |val| { return sema.addConstant(dest_ty, try Value.Tag.eu_payload.create(sema.arena, val)); } diff --git a/src/Zir.zig b/src/Zir.zig index ec9ddfcffb..3ce086e10b 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -242,6 +242,8 @@ pub const Inst = struct { /// Type coercion to the function's return type. /// Uses the `pl_node` field. Payload is `As`. AST node could be many things. as_node, + /// Same as `as_node` but ignores runtime to comptime int error. + as_shift_operand, /// Bitwise AND. `&` bit_and, /// Reinterpret the memory representation of a value as a different type. @@ -942,9 +944,6 @@ pub const Inst = struct { /// Implements the `@maximum` builtin. /// Uses the `pl_node` union field with payload `Bin` maximum, - /// Implements the `@asyncCall` builtin. - /// Uses the `pl_node` union field with payload `AsyncCall`. - builtin_async_call, /// Implements the `@cImport` builtin. /// Uses the `pl_node` union field with payload `Block`. c_import, @@ -1029,6 +1028,7 @@ pub const Inst = struct { .anyframe_type, .as, .as_node, + .as_shift_operand, .bit_and, .bitcast, .bit_or, @@ -1231,7 +1231,6 @@ pub const Inst = struct { .memcpy, .memset, .minimum, - .builtin_async_call, .c_import, .@"resume", .@"await", @@ -1339,6 +1338,7 @@ pub const Inst = struct { .anyframe_type, .as, .as_node, + .as_shift_operand, .bit_and, .bitcast, .bit_or, @@ -1513,7 +1513,6 @@ pub const Inst = struct { .field_parent_ptr, .maximum, .minimum, - .builtin_async_call, .c_import, .@"resume", .@"await", @@ -1577,6 +1576,7 @@ pub const Inst = struct { .anyframe_type = .un_node, .as = .bin, .as_node = .pl_node, + .as_shift_operand = .pl_node, .bit_and = .pl_node, .bitcast = .pl_node, .bit_not = .un_node, @@ -1801,7 +1801,6 @@ pub const Inst = struct { .memcpy = .pl_node, .memset = .pl_node, .minimum = .pl_node, - .builtin_async_call = .pl_node, .c_import = .pl_node, .alloc = .un_node, @@ -1972,6 +1971,9 @@ pub const Inst = struct { /// `operand` is payload index to `UnNode`. /// `small` contains `NameStrategy reify, + /// Implements the `@asyncCall` builtin. + /// `operand` is payload index to `AsyncCall`. + builtin_async_call, pub const InstData = struct { opcode: Extended, @@ -3454,6 +3456,7 @@ pub const Inst = struct { }; pub const AsyncCall = struct { + node: i32, frame_buffer: Ref, result_ptr: Ref, fn_ptr: Ref, diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index d256f9a558..884fd68d55 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -3466,19 +3466,16 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // on linking. const mod = self.bin_file.options.module.?; if (self.air.value(callee)) |func_value| { - if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) { + if (self.bin_file.cast(link.File.Elf)) |elf_file| { if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); const fn_owner_decl = mod.declPtr(func.owner_decl); - const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { + const got_addr = blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes - else - unreachable; + }; try self.genSetReg(Type.initTag(.usize), .x30, .{ .memory = got_addr }); @@ -3546,6 +3543,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. } else { return self.fail("TODO implement calling bitcasted functions", .{}); } + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO implement calling in COFF for {}", .{self.target.cpu.arch}); } else unreachable; } else { assert(ty.zigTypeTag() == .Pointer); @@ -5109,9 +5108,8 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne // the linker has enough info to perform relocations. assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO codegen COFF const Decl pointer", .{}); } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index e8f0507614..cefcf3b114 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -3698,7 +3698,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // Due to incremental compilation, how function calls are generated depends // on linking. switch (self.bin_file.tag) { - .elf, .coff => { + .elf => { if (self.air.value(callee)) |func_value| { if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; @@ -3709,11 +3709,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes - else - unreachable; - + } else unreachable; try self.genSetReg(Type.initTag(.usize), .lr, .{ .memory = got_addr }); } else if (func_value.castTag(.extern_fn)) |_| { return self.fail("TODO implement calling extern functions", .{}); @@ -3751,6 +3747,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. } }, .macho => unreachable, // unsupported architecture for MachO + .coff => return self.fail("TODO implement call in COFF for {}", .{self.target.cpu.arch}), .plan9 => return self.fail("TODO implement call on plan9 for {}", .{self.target.cpu.arch}), else => unreachable, } @@ -5548,9 +5545,8 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.MachO)) |_| { unreachable; // unsupported architecture for MachO - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO codegen COFF const Decl pointer", .{}); } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 06adcff6d4..cd1d0e4050 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1718,7 +1718,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // Due to incremental compilation, how function calls are generated depends // on linking. - if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) { + if (self.bin_file.cast(link.File.Elf)) |elf_file| { for (info.args) |mc_arg, arg_i| { const arg = args[arg_i]; const arg_ty = self.air.typeOf(arg); @@ -1752,13 +1752,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const ptr_bytes: u64 = @divExact(ptr_bits, 8); const mod = self.bin_file.options.module.?; const fn_owner_decl = mod.declPtr(func.owner_decl); - const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { + const got_addr = blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes - else - unreachable; + }; try self.genSetReg(Type.initTag(.usize), .ra, .{ .memory = got_addr }); _ = try self.addInst(.{ @@ -1777,6 +1774,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. } else { return self.fail("TODO implement calling runtime known function pointer", .{}); } + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO implement calling in COFF for {}", .{self.target.cpu.arch}); } else if (self.bin_file.cast(link.File.MachO)) |_| { unreachable; // unsupported architecture for MachO } else if (self.bin_file.cast(link.File.Plan9)) |_| { @@ -2591,9 +2590,8 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne // TODO I'm hacking my way through here by repurposing .memory for storing // index to the GOT target symbol index. return MCValue{ .memory = decl.link.macho.sym_index }; - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO codegen COFF const Decl pointer", .{}); } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 106d2feec0..e5d47e589a 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2657,22 +2657,26 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue .direct_load, => |sym_index| { const abi_size = @intCast(u32, ptr_ty.abiSize(self.target.*)); + const mod = self.bin_file.options.module.?; + const fn_owner_decl = mod.declPtr(self.mod_fn.owner_decl); + const atom_index = if (self.bin_file.tag == link.File.MachO.base_tag) + fn_owner_decl.link.macho.sym_index + else + fn_owner_decl.link.coff.sym_index; const flags: u2 = switch (ptr) { .got_load => 0b00, .direct_load => 0b01, else => unreachable, }; - const mod = self.bin_file.options.module.?; - const fn_owner_decl = mod.declPtr(self.mod_fn.owner_decl); _ = try self.addInst(.{ - .tag = .lea_pie, + .tag = .lea_pic, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size), .flags = flags, }), .data = .{ .relocation = .{ - .atom_index = fn_owner_decl.link.macho.sym_index, + .atom_index = atom_index, .sym_index = sym_index, }, }, @@ -3961,20 +3965,17 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // Due to incremental compilation, how function calls are generated depends // on linking. const mod = self.bin_file.options.module.?; - if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) { + if (self.bin_file.cast(link.File.Elf)) |elf_file| { if (self.air.value(callee)) |func_value| { if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); const fn_owner_decl = mod.declPtr(func.owner_decl); - const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { + const got_addr = blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - @intCast(u32, coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes) - else - unreachable; + }; _ = try self.addInst(.{ .tag = .call, .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), @@ -3998,14 +3999,47 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. .data = undefined, }); } - } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { + } else if (self.bin_file.cast(link.File.Coff)) |_| { if (self.air.value(callee)) |func_value| { if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; const fn_owner_decl = mod.declPtr(func.owner_decl); try self.genSetReg(Type.initTag(.usize), .rax, .{ - .got_load = fn_owner_decl.link.macho.sym_index, + .got_load = fn_owner_decl.link.coff.sym_index, }); + _ = try self.addInst(.{ + .tag = .call, + .ops = Mir.Inst.Ops.encode(.{ + .reg1 = .rax, + .flags = 0b01, + }), + .data = undefined, + }); + } else if (func_value.castTag(.extern_fn)) |_| { + return self.fail("TODO implement calling extern functions", .{}); + } else { + return self.fail("TODO implement calling bitcasted functions", .{}); + } + } else { + assert(ty.zigTypeTag() == .Pointer); + const mcv = try self.resolveInst(callee); + try self.genSetReg(Type.initTag(.usize), .rax, mcv); + _ = try self.addInst(.{ + .tag = .call, + .ops = Mir.Inst.Ops.encode(.{ + .reg1 = .rax, + .flags = 0b01, + }), + .data = undefined, + }); + } + } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { + if (self.air.value(callee)) |func_value| { + if (func_value.castTag(.function)) |func_payload| { + const func = func_payload.data; + const fn_owner_decl = mod.declPtr(func.owner_decl); + const sym_index = fn_owner_decl.link.macho.sym_index; + try self.genSetReg(Type.initTag(.usize), .rax, .{ .got_load = sym_index }); // callq *%rax _ = try self.addInst(.{ .tag = .call, @@ -6842,13 +6876,11 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.MachO)) |_| { - // Because MachO is PIE-always-on, we defer memory address resolution until - // the linker has enough info to perform relocations. assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + } else if (self.bin_file.cast(link.File.Coff)) |_| { + assert(decl.link.coff.sym_index != 0); + return MCValue{ .got_load = decl.link.coff.sym_index }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 8b61c6077c..12f3e9118f 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -137,7 +137,7 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .fld => try emit.mirFld(inst), .lea => try emit.mirLea(inst), - .lea_pie => try emit.mirLeaPie(inst), + .lea_pic => try emit.mirLeaPic(inst), .shl => try emit.mirShift(.shl, inst), .sal => try emit.mirShift(.sal, inst), @@ -338,7 +338,7 @@ fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { .base = ops.reg1, }), emit.code); }, - 0b11 => return emit.fail("TODO unused JMP/CALL variant 0b11", .{}), + 0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}), } } @@ -784,7 +784,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { // FD return lowerToFdEnc(.mov, ops.reg1, imm, emit.code); }, - else => return emit.fail("TODO unused variant: movabs 0b{b}", .{ops.flags}), + else => return emit.fail("TODO unused movabs variant", .{}), } } @@ -978,12 +978,17 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } -fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .lea_pie); + assert(tag == .lea_pic); const ops = emit.mir.instructions.items(.ops)[inst].decode(); const relocation = emit.mir.instructions.items(.data)[inst].relocation; + switch (ops.flags) { + 0b00, 0b01 => {}, + else => return emit.fail("TODO unused LEA PIC variants 0b10 and 0b11", .{}), + } + // lea reg1, [rip + reloc] // RM try lowerToRmEnc( @@ -994,16 +999,17 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { ); const end_offset = emit.code.items.len; + const gpa = emit.bin_file.allocator; if (emit.bin_file.cast(link.File.MachO)) |macho_file| { const reloc_type = switch (ops.flags) { 0b00 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), 0b01 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED), - else => return emit.fail("TODO unused LEA PIE variants 0b10 and 0b11", .{}), + else => unreachable, }; const atom = macho_file.atom_by_index_table.get(relocation.atom_index).?; log.debug("adding reloc of type {} to local @{d}", .{ reloc_type, relocation.sym_index }); - try atom.relocs.append(emit.bin_file.allocator, .{ + try atom.relocs.append(gpa, .{ .offset = @intCast(u32, end_offset - 4), .target = .{ .sym_index = relocation.sym_index, .file = null }, .addend = 0, @@ -1012,11 +1018,23 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .length = 2, .@"type" = reloc_type, }); + } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { + const atom = coff_file.atom_by_index_table.get(relocation.atom_index).?; + try atom.addRelocation(coff_file, .{ + .@"type" = switch (ops.flags) { + 0b00 => .got, + 0b01 => .direct, + else => unreachable, + }, + .target = .{ .sym_index = relocation.sym_index, .file = null }, + .offset = @intCast(u32, end_offset - 4), + .addend = 0, + .pcrel = true, + .length = 2, + .prev_vaddr = atom.getSymbol(coff_file).value, + }); } else { - return emit.fail( - "TODO implement lea reg, [rip + reloc] for linking backends different than MachO", - .{}, - ); + return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{}); } } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index f67b48a271..71aecc5e85 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -178,11 +178,11 @@ pub const Inst = struct { lea, /// ops flags: form: - /// 0b00 reg1, [rip + reloc] // via GOT emits X86_64_RELOC_GOT relocation - /// 0b01 reg1, [rip + reloc] // direct load emits X86_64_RELOC_SIGNED relocation + /// 0b00 reg1, [rip + reloc] // via GOT PIC + /// 0b01 reg1, [rip + reloc] // direct load PIC /// Notes: /// * `Data` contains `relocation` - lea_pie, + lea_pic, /// ops flags: form: /// 0b00 reg1, 1 @@ -242,15 +242,14 @@ pub const Inst = struct { imul_complex, /// ops flags: form: - /// 0bX0 reg1, imm64 - /// 0bX1 rax, moffs64 + /// 0b00 reg1, imm64 + /// 0b01 rax, moffs64 /// Notes: /// * If reg1 is 64-bit, the immediate is 64-bit and stored /// within extra data `Imm64`. - /// * For 0bX1, reg1 (or reg2) need to be + /// * For 0b01, reg1 (or reg2) need to be /// a version of rax. If reg1 == .none, then reg2 == .rax, /// or vice versa. - /// TODO handle scaling movabs, /// ops flags: form: diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index e8e895e1fc..b241c1cbcb 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -198,6 +198,122 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![:0]u8 { return llvm_triple.toOwnedSliceSentinel(0); } +pub fn targetOs(os_tag: std.Target.Os.Tag) llvm.OSType { + return switch (os_tag) { + .freestanding, .other, .opencl, .glsl450, .vulkan, .plan9 => .UnknownOS, + .windows, .uefi => .Win32, + .ananas => .Ananas, + .cloudabi => .CloudABI, + .dragonfly => .DragonFly, + .freebsd => .FreeBSD, + .fuchsia => .Fuchsia, + .ios => .IOS, + .kfreebsd => .KFreeBSD, + .linux => .Linux, + .lv2 => .Lv2, + .macos => .MacOSX, + .netbsd => .NetBSD, + .openbsd => .OpenBSD, + .solaris => .Solaris, + .zos => .ZOS, + .haiku => .Haiku, + .minix => .Minix, + .rtems => .RTEMS, + .nacl => .NaCl, + .aix => .AIX, + .cuda => .CUDA, + .nvcl => .NVCL, + .amdhsa => .AMDHSA, + .ps4 => .PS4, + .ps5 => .PS5, + .elfiamcu => .ELFIAMCU, + .tvos => .TvOS, + .watchos => .WatchOS, + .mesa3d => .Mesa3D, + .contiki => .Contiki, + .amdpal => .AMDPAL, + .hermit => .HermitCore, + .hurd => .Hurd, + .wasi => .WASI, + .emscripten => .Emscripten, + .driverkit => .DriverKit, + .shadermodel => .ShaderModel, + }; +} + +pub fn targetArch(arch_tag: std.Target.Cpu.Arch) llvm.ArchType { + return switch (arch_tag) { + .arm => .arm, + .armeb => .armeb, + .aarch64 => .aarch64, + .aarch64_be => .aarch64_be, + .aarch64_32 => .aarch64_32, + .arc => .arc, + .avr => .avr, + .bpfel => .bpfel, + .bpfeb => .bpfeb, + .csky => .csky, + .dxil => .dxil, + .hexagon => .hexagon, + .loongarch32 => .loongarch32, + .loongarch64 => .loongarch64, + .m68k => .m68k, + .mips => .mips, + .mipsel => .mipsel, + .mips64 => .mips64, + .mips64el => .mips64el, + .msp430 => .msp430, + .powerpc => .ppc, + .powerpcle => .ppcle, + .powerpc64 => .ppc64, + .powerpc64le => .ppc64le, + .r600 => .r600, + .amdgcn => .amdgcn, + .riscv32 => .riscv32, + .riscv64 => .riscv64, + .sparc => .sparc, + .sparc64 => .sparcv9, // In LLVM, sparc64 == sparcv9. + .sparcel => .sparcel, + .s390x => .systemz, + .tce => .tce, + .tcele => .tcele, + .thumb => .thumb, + .thumbeb => .thumbeb, + .i386 => .x86, + .x86_64 => .x86_64, + .xcore => .xcore, + .nvptx => .nvptx, + .nvptx64 => .nvptx64, + .le32 => .le32, + .le64 => .le64, + .amdil => .amdil, + .amdil64 => .amdil64, + .hsail => .hsail, + .hsail64 => .hsail64, + .spir => .spir, + .spir64 => .spir64, + .kalimba => .kalimba, + .shave => .shave, + .lanai => .lanai, + .wasm32 => .wasm32, + .wasm64 => .wasm64, + .renderscript32 => .renderscript32, + .renderscript64 => .renderscript64, + .ve => .ve, + .spu_2, .spirv32, .spirv64 => .UnknownArch, + }; +} + +pub fn supportsTailCall(target: std.Target) bool { + switch (target.cpu.arch) { + .wasm32, .wasm64 => return std.Target.wasm.featureSetHas(target.cpu.features, .tail_call), + // Although these ISAs support tail calls, LLVM does not support tail calls on them. + .mips, .mipsel, .mips64, .mips64el => return false, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => return false, + else => return true, + } +} + pub const Object = struct { gpa: Allocator, module: *Module, @@ -4559,7 +4675,7 @@ pub const FuncGen = struct { "", ); - if (return_type.isNoReturn()) { + if (return_type.isNoReturn() and attr != .AlwaysTail) { _ = self.builder.buildUnreachable(); return null; } @@ -8644,12 +8760,26 @@ pub const FuncGen = struct { const union_llvm_ty = try self.dg.lowerType(union_ty); const target = self.dg.module.getTarget(); const layout = union_ty.unionGetLayout(target); + const union_obj = union_ty.cast(Type.Payload.Union).?.data; + const tag_int = blk: { + const tag_ty = union_ty.unionTagTypeHypothetical(); + const union_field_name = union_obj.fields.keys()[extra.field_index]; + const enum_field_index = tag_ty.enumFieldIndex(union_field_name).?; + var tag_val_payload: Value.Payload.U32 = .{ + .base = .{ .tag = .enum_field_index }, + .data = @intCast(u32, enum_field_index), + }; + const tag_val = Value.initPayload(&tag_val_payload.base); + var int_payload: Value.Payload.U64 = undefined; + const tag_int_val = tag_val.enumToInt(tag_ty, &int_payload); + break :blk tag_int_val.toUnsignedInt(target); + }; if (layout.payload_size == 0) { if (layout.tag_size == 0) { return null; } assert(!isByRef(union_ty)); - return union_llvm_ty.constInt(extra.field_index, .False); + return union_llvm_ty.constInt(tag_int, .False); } assert(isByRef(union_ty)); // The llvm type of the alloca will the the named LLVM union type, which will not @@ -8658,7 +8788,6 @@ pub const FuncGen = struct { // then set the fields appropriately. const result_ptr = self.buildAlloca(union_llvm_ty); const llvm_payload = try self.resolveInst(extra.init); - const union_obj = union_ty.cast(Type.Payload.Union).?.data; assert(union_obj.haveFieldTypes()); const field = union_obj.fields.values()[extra.field_index]; const field_llvm_ty = try self.dg.lowerType(field.ty); @@ -8742,7 +8871,7 @@ pub const FuncGen = struct { }; const field_ptr = self.builder.buildInBoundsGEP(llvm_union_ty, casted_ptr, &indices, indices.len, ""); const tag_llvm_ty = try self.dg.lowerType(union_obj.tag_ty); - const llvm_tag = tag_llvm_ty.constInt(extra.field_index, .False); + const llvm_tag = tag_llvm_ty.constInt(tag_int, .False); const store_inst = self.builder.buildStore(llvm_tag, field_ptr); store_inst.setAlignment(union_obj.tag_ty.abiAlignment(target)); } diff --git a/src/link.zig b/src/link.zig index 7f5f1ebc4b..1a0689381b 100644 --- a/src/link.zig +++ b/src/link.zig @@ -245,8 +245,8 @@ pub const File = struct { pub const LinkBlock = union { elf: Elf.TextBlock, - coff: Coff.TextBlock, - macho: MachO.TextBlock, + coff: Coff.Atom, + macho: MachO.Atom, plan9: Plan9.DeclBlock, c: void, wasm: Wasm.DeclBlock, @@ -267,7 +267,7 @@ pub const File = struct { pub const Export = union { elf: Elf.Export, - coff: void, + coff: Coff.Export, macho: MachO.Export, plan9: Plan9.Export, c: void, @@ -934,9 +934,10 @@ pub const File = struct { std.debug.print("\n", .{}); } - const llvm = @import("codegen/llvm/bindings.zig"); - const os_type = @import("target.zig").osToLLVM(base.options.target.os.tag); - const bad = llvm.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_type); + const llvm_bindings = @import("codegen/llvm/bindings.zig"); + const llvm = @import("codegen/llvm.zig"); + const os_tag = llvm.targetOs(base.options.target.os.tag); + const bad = llvm_bindings.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_tag); if (bad) return error.UnableToWriteArchive; if (!base.options.disable_lld_caching) { diff --git a/src/link/Coff.zig b/src/link/Coff.zig index e4f0227aec..36ddfc4e2a 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1,131 +1,179 @@ const Coff = @This(); const std = @import("std"); +const build_options = @import("build_options"); const builtin = @import("builtin"); -const log = std.log.scoped(.link); -const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const fs = std.fs; -const allocPrint = std.fmt.allocPrint; +const coff = std.coff; +const fmt = std.fmt; +const log = std.log.scoped(.link); +const math = std.math; const mem = std.mem; -const lldMain = @import("../main.zig").lldMain; -const trace = @import("../tracy.zig").trace; -const Module = @import("../Module.zig"); -const Compilation = @import("../Compilation.zig"); +const Allocator = std.mem.Allocator; + const codegen = @import("../codegen.zig"); const link = @import("../link.zig"); -const build_options = @import("build_options"); -const Cache = @import("../Cache.zig"); -const mingw = @import("../mingw.zig"); +const lld = @import("Coff/lld.zig"); +const trace = @import("../tracy.zig").trace; + const Air = @import("../Air.zig"); +pub const Atom = @import("Coff/Atom.zig"); +const Compilation = @import("../Compilation.zig"); const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; +const Module = @import("../Module.zig"); +const Object = @import("Coff/Object.zig"); +const StringTable = @import("strtab.zig").StringTable; const TypedValue = @import("../TypedValue.zig"); -const allocation_padding = 4 / 3; -const minimum_text_block_size = 64 * allocation_padding; - -const section_alignment = 4096; -const file_alignment = 512; -const default_image_base = 0x400_000; -const section_table_size = 2 * 40; -comptime { - assert(mem.isAligned(default_image_base, section_alignment)); -} - pub const base_tag: link.File.Tag = .coff; const msdos_stub = @embedFile("msdos-stub.bin"); +const N_DATA_DIRS: u5 = 16; /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. llvm_object: ?*LlvmObject = null, base: link.File, -ptr_width: PtrWidth, error_flags: link.File.ErrorFlags = .{}, -text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{}, -last_text_block: ?*TextBlock = null, - -/// Section table file pointer. -section_table_offset: u32 = 0, -/// Section data file pointer. -section_data_offset: u32 = 0, -/// Optional header file pointer. -optional_header_offset: u32 = 0, - -/// Absolute virtual address of the offset table when the executable is loaded in memory. -offset_table_virtual_address: u32 = 0, -/// Current size of the offset table on disk, must be a multiple of `file_alignment` -offset_table_size: u32 = 0, -/// Contains absolute virtual addresses -offset_table: std.ArrayListUnmanaged(u64) = .{}, -/// Free list of offset table indices -offset_table_free_list: std.ArrayListUnmanaged(u32) = .{}, +ptr_width: PtrWidth, +page_size: u32, + +objects: std.ArrayListUnmanaged(Object) = .{}, + +sections: std.MultiArrayList(Section) = .{}, +data_directories: [N_DATA_DIRS]coff.ImageDataDirectory, + +text_section_index: ?u16 = null, +got_section_index: ?u16 = null, +rdata_section_index: ?u16 = null, +data_section_index: ?u16 = null, + +locals: std.ArrayListUnmanaged(coff.Symbol) = .{}, +globals: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{}, + +locals_free_list: std.ArrayListUnmanaged(u32) = .{}, + +strtab: StringTable(.strtab) = .{}, +strtab_offset: ?u32 = null, + +got_entries: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{}, +got_entries_free_list: std.ArrayListUnmanaged(u32) = .{}, /// Virtual address of the entry point procedure relative to image base. entry_addr: ?u32 = null, -/// Absolute virtual address of the text section when the executable is loaded in memory. -text_section_virtual_address: u32 = 0, -/// Current size of the `.text` section on disk, must be a multiple of `file_alignment` -text_section_size: u32 = 0, +/// Table of Decls that are currently alive. +/// We store them here so that we can properly dispose of any allocated +/// memory within the atom in the incremental linker. +/// TODO consolidate this. +decls: std.AutoHashMapUnmanaged(Module.Decl.Index, ?u16) = .{}, + +/// List of atoms that are either synthetic or map directly to the Zig source program. +managed_atoms: std.ArrayListUnmanaged(*Atom) = .{}, + +/// Table of atoms indexed by the symbol index. +atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{}, + +/// Table of unnamed constants associated with a parent `Decl`. +/// We store them here so that we can free the constants whenever the `Decl` +/// needs updating or is freed. +/// +/// For example, +/// +/// ```zig +/// const Foo = struct{ +/// a: u8, +/// }; +/// +/// pub fn main() void { +/// var foo = Foo{ .a = 1 }; +/// _ = foo; +/// } +/// ``` +/// +/// value assigned to label `foo` is an unnamed constant belonging/associated +/// with `Decl` `main`, and lives as long as that `Decl`. +unnamed_const_atoms: UnnamedConstTable = .{}, + +/// A table of relocations indexed by the owning them `TextBlock`. +/// Note that once we refactor `TextBlock`'s lifetime and ownership rules, +/// this will be a table indexed by index into the list of Atoms. +relocs: RelocTable = .{}, + +pub const Reloc = struct { + @"type": enum { + got, + direct, + }, + target: SymbolWithLoc, + offset: u32, + addend: u32, + pcrel: bool, + length: u2, + prev_vaddr: u32, +}; -offset_table_size_dirty: bool = false, -text_section_size_dirty: bool = false, -/// This flag is set when the virtual size of the whole image file when loaded in memory has changed -/// and needs to be updated in the optional header. -size_of_image_dirty: bool = false, +const RelocTable = std.AutoHashMapUnmanaged(*Atom, std.ArrayListUnmanaged(Reloc)); +const UnnamedConstTable = std.AutoHashMapUnmanaged(Module.Decl.Index, std.ArrayListUnmanaged(*Atom)); + +const default_file_alignment: u16 = 0x200; +const default_image_base_dll: u64 = 0x10000000; +const default_image_base_exe: u64 = 0x400000; +const default_size_of_stack_reserve: u32 = 0x1000000; +const default_size_of_stack_commit: u32 = 0x1000; +const default_size_of_heap_reserve: u32 = 0x100000; +const default_size_of_heap_commit: u32 = 0x1000; + +const Section = struct { + header: coff.SectionHeader, + + last_atom: ?*Atom = null, + + /// A list of atoms that have surplus capacity. This list can have false + /// positives, as functions grow and shrink over time, only sometimes being added + /// or removed from the freelist. + /// + /// An atom has surplus capacity when its overcapacity value is greater than + /// padToIdeal(minimum_atom_size). That is, when it has so + /// much extra capacity, that we could fit a small new symbol in it, itself with + /// ideal_capacity or more. + /// + /// Ideal capacity is defined by size + (size / ideal_factor). + /// + /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that + /// overcapacity can be negative. A simple way to have negative overcapacity is to + /// allocate a fresh atom, which will have ideal capacity, and then grow it + /// by 1 byte. It will then have -1 overcapacity. + free_list: std.ArrayListUnmanaged(*Atom) = .{}, +}; pub const PtrWidth = enum { p32, p64 }; +pub const SrcFn = void; -pub const TextBlock = struct { - /// Offset of the code relative to the start of the text section - text_offset: u32, - /// Used size of the text block - size: u32, - /// This field is undefined for symbols with size = 0. - offset_table_index: u32, - /// Points to the previous and next neighbors, based on the `text_offset`. - /// This can be used to find, for example, the capacity of this `TextBlock`. - prev: ?*TextBlock, - next: ?*TextBlock, - - pub const empty = TextBlock{ - .text_offset = 0, - .size = 0, - .offset_table_index = undefined, - .prev = null, - .next = null, - }; - - /// Returns how much room there is to grow in virtual address space. - fn capacity(self: TextBlock) u64 { - if (self.next) |next| { - return next.text_offset - self.text_offset; - } - // This is the last block, the capacity is only limited by the address space. - return std.math.maxInt(u32) - self.text_offset; - } +pub const Export = struct { + sym_index: ?u32 = null, +}; - fn freeListEligible(self: TextBlock) bool { - // No need to keep a free list node for the last block. - const next = self.next orelse return false; - const cap = next.text_offset - self.text_offset; - const ideal_cap = self.size * allocation_padding; - if (cap <= ideal_cap) return false; - const surplus = cap - ideal_cap; - return surplus >= minimum_text_block_size; - } +pub const SymbolWithLoc = struct { + // Index into the respective symbol table. + sym_index: u32, - /// Absolute virtual address of the text block when the file is loaded in memory. - fn getVAddr(self: TextBlock, coff: Coff) u32 { - return coff.text_section_virtual_address + self.text_offset; - } + // null means it's a synthetic global or Zig source. + file: ?u32 = null, }; -pub const SrcFn = void; +/// When allocating, the ideal_capacity is calculated by +/// actual_capacity + (actual_capacity / ideal_factor) +const ideal_factor = 3; + +/// In order for a slice of bytes to be considered eligible to keep metadata pointing at +/// it as a possible place to put new symbols, it must have enough room for this many bytes +/// (plus extra for reserved capacity). +const minimum_text_block_size = 64; +pub const min_text_capacity = padToIdeal(minimum_text_block_size); pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Coff { assert(options.target.ofmt == .coff); @@ -144,257 +192,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }); self.base.file = file; - // TODO Write object specific relocations, COFF symbol table, then enable object file output. - switch (options.output_mode) { - .Exe => {}, - .Obj => return error.TODOImplementWritingObjFiles, - .Lib => return error.TODOImplementWritingLibFiles, - } - - var coff_file_header_offset: u32 = 0; - if (options.output_mode == .Exe) { - // Write the MS-DOS stub and the PE signature - try self.base.file.?.pwriteAll(msdos_stub ++ "PE\x00\x00", 0); - coff_file_header_offset = msdos_stub.len + 4; - } - - // COFF file header - const data_directory_count = 0; - var hdr_data: [112 + data_directory_count * 8 + section_table_size]u8 = undefined; - var index: usize = 0; - - const machine = self.base.options.target.cpu.arch.toCoffMachine(); - if (machine == .Unknown) { - return error.UnsupportedCOFFArchitecture; - } - mem.writeIntLittle(u16, hdr_data[0..2], @enumToInt(machine)); - index += 2; - - // Number of sections (we only use .got, .text) - mem.writeIntLittle(u16, hdr_data[index..][0..2], 2); - index += 2; - // TimeDateStamp (u32), PointerToSymbolTable (u32), NumberOfSymbols (u32) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - - const optional_header_size = switch (options.output_mode) { - .Exe => data_directory_count * 8 + switch (self.ptr_width) { - .p32 => @as(u16, 96), - .p64 => 112, - }, - else => 0, - }; - - const section_table_offset = coff_file_header_offset + 20 + optional_header_size; - const default_offset_table_size = file_alignment; - const default_size_of_code = 0; - - self.section_data_offset = mem.alignForwardGeneric(u32, self.section_table_offset + section_table_size, file_alignment); - const section_data_relative_virtual_address = mem.alignForwardGeneric(u32, self.section_table_offset + section_table_size, section_alignment); - self.offset_table_virtual_address = default_image_base + section_data_relative_virtual_address; - self.offset_table_size = default_offset_table_size; - self.section_table_offset = section_table_offset; - self.text_section_virtual_address = default_image_base + section_data_relative_virtual_address + section_alignment; - self.text_section_size = default_size_of_code; - - // Size of file when loaded in memory - const size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + default_size_of_code, section_alignment); - - mem.writeIntLittle(u16, hdr_data[index..][0..2], optional_header_size); - index += 2; - - // Characteristics - var characteristics: std.coff.CoffHeaderFlags = .{ - .DEBUG_STRIPPED = 1, // TODO remove debug info stripped flag when necessary - .RELOCS_STRIPPED = 1, - }; - if (options.output_mode == .Exe) { - characteristics.EXECUTABLE_IMAGE = 1; - } - switch (self.ptr_width) { - .p32 => characteristics.@"32BIT_MACHINE" = 1, - .p64 => characteristics.LARGE_ADDRESS_AWARE = 1, - } - mem.writeIntLittle(u16, hdr_data[index..][0..2], @bitCast(u16, characteristics)); - index += 2; - - assert(index == 20); - try self.base.file.?.pwriteAll(hdr_data[0..index], coff_file_header_offset); - - if (options.output_mode == .Exe) { - self.optional_header_offset = coff_file_header_offset + 20; - // Optional header - index = 0; - mem.writeIntLittle(u16, hdr_data[0..2], switch (self.ptr_width) { - .p32 => @as(u16, 0x10b), - .p64 => 0x20b, - }); - index += 2; - - // Linker version (u8 + u8) - mem.set(u8, hdr_data[index..][0..2], 0); - index += 2; - - // SizeOfCode (UNUSED, u32), SizeOfInitializedData (u32), SizeOfUninitializedData (u32), AddressOfEntryPoint (u32), BaseOfCode (UNUSED, u32) - mem.set(u8, hdr_data[index..][0..20], 0); - index += 20; - - if (self.ptr_width == .p32) { - // Base of data relative to the image base (UNUSED) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - - // Image base address - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_image_base); - index += 4; - } else { - // Image base address - mem.writeIntLittle(u64, hdr_data[index..][0..8], default_image_base); - index += 8; - } - - // Section alignment - mem.writeIntLittle(u32, hdr_data[index..][0..4], section_alignment); - index += 4; - // File alignment - mem.writeIntLittle(u32, hdr_data[index..][0..4], file_alignment); - index += 4; - // Required OS version, 6.0 is vista - mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); - index += 2; - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); - index += 2; - // Image version - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - // Required subsystem version, same as OS version - mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); - index += 2; - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); - index += 2; - // Reserved zeroes (u32) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], size_of_image); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); - index += 4; - // CheckSum (u32) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - // Subsystem, TODO: Let users specify the subsystem, always CUI for now - mem.writeIntLittle(u16, hdr_data[index..][0..2], 3); - index += 2; - // DLL characteristics - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0x0); - index += 2; - - switch (self.ptr_width) { - .p32 => { - // Size of stack reserve + commit - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000_000); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); - index += 4; - // Size of heap reserve + commit - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x100_000); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); - index += 4; - }, - .p64 => { - // Size of stack reserve + commit - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000_000); - index += 8; - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); - index += 8; - // Size of heap reserve + commit - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x100_000); - index += 8; - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); - index += 8; - }, - } - - // Reserved zeroes - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - - // Number of data directories - mem.writeIntLittle(u32, hdr_data[index..][0..4], data_directory_count); - index += 4; - // Initialize data directories to zero - mem.set(u8, hdr_data[index..][0 .. data_directory_count * 8], 0); - index += data_directory_count * 8; - - assert(index == optional_header_size); - } - - // Write section table. - // First, the .got section - hdr_data[index..][0..8].* = ".got\x00\x00\x00\x00".*; - index += 8; - if (options.output_mode == .Exe) { - // Virtual size (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); - index += 4; - // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.offset_table_virtual_address - default_image_base); - index += 4; - } else { - mem.set(u8, hdr_data[index..][0..8], 0); - index += 8; - } - // Size of raw data (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); - index += 4; - // File pointer to the start of the section - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); - index += 4; - // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ - .CNT_INITIALIZED_DATA = 1, - .MEM_READ = 1, - })); - index += 4; - // Then, the .text section - hdr_data[index..][0..8].* = ".text\x00\x00\x00".*; - index += 8; - if (options.output_mode == .Exe) { - // Virtual size (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); - index += 4; - // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.text_section_virtual_address - default_image_base); - index += 4; - } else { - mem.set(u8, hdr_data[index..][0..8], 0); - index += 8; - } - // Size of raw data (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); - index += 4; - // File pointer to the start of the section - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset + default_offset_table_size); - index += 4; - // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ - .CNT_CODE = 1, - .MEM_EXECUTE = 1, - .MEM_READ = 1, - .MEM_WRITE = 1, - })); - index += 4; - - assert(index == optional_header_size + section_table_size); - try self.base.file.?.pwriteAll(hdr_data[0..index], self.optional_header_offset); - try self.base.file.?.setEndPos(self.section_data_offset + default_offset_table_size + default_size_of_code); + try self.populateMissingMetadata(); return self; } @@ -405,6 +203,9 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { 33...64 => .p64, else => return error.UnsupportedCOFFArchitecture, }; + const page_size: u32 = switch (options.target.cpu.arch) { + else => 0x1000, + }; const self = try gpa.create(Coff); errdefer gpa.destroy(self); self.* = .{ @@ -415,6 +216,8 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { .file = null, }, .ptr_width = ptr_width, + .page_size = page_size, + .data_directories = comptime mem.zeroes([N_DATA_DIRS]coff.ImageDataDirectory), }; const use_llvm = build_options.have_llvm and options.use_llvm; @@ -425,245 +228,530 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { return self; } -pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { - if (self.llvm_object) |_| return; +pub fn deinit(self: *Coff) void { + const gpa = self.base.allocator; + + if (build_options.have_llvm) { + if (self.llvm_object) |llvm_object| llvm_object.destroy(gpa); + } - try self.offset_table.ensureUnusedCapacity(self.base.allocator, 1); + for (self.objects.items) |*object| { + object.deinit(gpa); + } + self.objects.deinit(gpa); - const decl = self.base.options.module.?.declPtr(decl_index); - if (self.offset_table_free_list.popOrNull()) |i| { - decl.link.coff.offset_table_index = i; - } else { - decl.link.coff.offset_table_index = @intCast(u32, self.offset_table.items.len); - _ = self.offset_table.addOneAssumeCapacity(); + for (self.sections.items(.free_list)) |*free_list| { + free_list.deinit(gpa); + } + self.sections.deinit(gpa); + + for (self.managed_atoms.items) |atom| { + gpa.destroy(atom); + } + self.managed_atoms.deinit(gpa); - const entry_size = self.base.options.target.cpu.arch.ptrBitWidth() / 8; - if (self.offset_table.items.len > self.offset_table_size / entry_size) { - self.offset_table_size_dirty = true; + self.locals.deinit(gpa); + self.globals.deinit(gpa); + self.locals_free_list.deinit(gpa); + self.strtab.deinit(gpa); + self.got_entries.deinit(gpa); + self.got_entries_free_list.deinit(gpa); + self.decls.deinit(gpa); + self.atom_by_index_table.deinit(gpa); + + { + var it = self.unnamed_const_atoms.valueIterator(); + while (it.next()) |atoms| { + atoms.deinit(gpa); } + self.unnamed_const_atoms.deinit(gpa); } - self.offset_table.items[decl.link.coff.offset_table_index] = 0; + { + var it = self.relocs.valueIterator(); + while (it.next()) |relocs| { + relocs.deinit(gpa); + } + self.relocs.deinit(gpa); + } } -fn allocateTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { - const new_block_min_capacity = new_block_size * allocation_padding; +fn populateMissingMetadata(self: *Coff) !void { + assert(self.llvm_object == null); + const gpa = self.base.allocator; + + if (self.text_section_index == null) { + self.text_section_index = @intCast(u16, self.sections.slice().len); + const file_size = @intCast(u32, self.base.options.program_code_size_hint); + const off = self.findFreeSpace(file_size, self.page_size); // TODO we are over-aligning in file; we should track both in file and in memory pointers + log.debug("found .text free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = file_size, + .virtual_address = off, + .size_of_raw_data = file_size, + .pointer_to_raw_data = off, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_CODE = 1, + .MEM_EXECUTE = 1, + .MEM_READ = 1, + }, + }; + try self.setSectionName(&header, ".text"); + try self.sections.append(gpa, .{ .header = header }); + } + + if (self.got_section_index == null) { + self.got_section_index = @intCast(u16, self.sections.slice().len); + const file_size = @intCast(u32, self.base.options.symbol_count_hint); + const off = self.findFreeSpace(file_size, self.page_size); + log.debug("found .got free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = file_size, + .virtual_address = off, + .size_of_raw_data = file_size, + .pointer_to_raw_data = off, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + }, + }; + try self.setSectionName(&header, ".got"); + try self.sections.append(gpa, .{ .header = header }); + } + + if (self.rdata_section_index == null) { + self.rdata_section_index = @intCast(u16, self.sections.slice().len); + const file_size: u32 = 1024; + const off = self.findFreeSpace(file_size, self.page_size); + log.debug("found .rdata free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = file_size, + .virtual_address = off, + .size_of_raw_data = file_size, + .pointer_to_raw_data = off, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + }, + }; + try self.setSectionName(&header, ".rdata"); + try self.sections.append(gpa, .{ .header = header }); + } + + if (self.data_section_index == null) { + self.data_section_index = @intCast(u16, self.sections.slice().len); + const file_size: u32 = 1024; + const off = self.findFreeSpace(file_size, self.page_size); + log.debug("found .data free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = file_size, + .virtual_address = off, + .size_of_raw_data = file_size, + .pointer_to_raw_data = off, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + .MEM_WRITE = 1, + }, + }; + try self.setSectionName(&header, ".data"); + try self.sections.append(gpa, .{ .header = header }); + } - // We use these to indicate our intention to update metadata, placing the new block, + if (self.strtab_offset == null) { + try self.strtab.buffer.append(gpa, 0); + self.strtab_offset = self.findFreeSpace(@intCast(u32, self.strtab.len()), 1); + log.debug("found strtab free space 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + self.strtab.len() }); + } + + // Index 0 is always a null symbol. + try self.locals.append(gpa, .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = @intToEnum(coff.SectionNumber, 0), + .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }); + + { + // We need to find out what the max file offset is according to section headers. + // Otherwise, we may end up with an COFF binary with file size not matching the final section's + // offset + it's filesize. + // TODO I don't like this here one bit + var max_file_offset: u64 = 0; + for (self.sections.items(.header)) |header| { + if (header.pointer_to_raw_data + header.size_of_raw_data > max_file_offset) { + max_file_offset = header.pointer_to_raw_data + header.size_of_raw_data; + } + } + try self.base.file.?.pwriteAll(&[_]u8{0}, max_file_offset); + } +} + +pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { + if (self.llvm_object) |_| return; + const decl = self.base.options.module.?.declPtr(decl_index); + if (decl.link.coff.sym_index != 0) return; + decl.link.coff.sym_index = try self.allocateSymbol(); + const gpa = self.base.allocator; + try self.atom_by_index_table.putNoClobber(gpa, decl.link.coff.sym_index, &decl.link.coff); + try self.decls.putNoClobber(gpa, decl_index, null); +} + +fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32) !u32 { + const tracy = trace(@src()); + defer tracy.end(); + + const sect_id = @enumToInt(atom.getSymbol(self).section_number) - 1; + const header = &self.sections.items(.header)[sect_id]; + const free_list = &self.sections.items(.free_list)[sect_id]; + const maybe_last_atom = &self.sections.items(.last_atom)[sect_id]; + const new_atom_ideal_capacity = if (header.isCode()) padToIdeal(new_atom_size) else new_atom_size; + + // We use these to indicate our intention to update metadata, placing the new atom, // and possibly removing a free list node. // It would be simpler to do it inside the for loop below, but that would cause a // problem if an error was returned later in the function. So this action // is actually carried out at the end of the function, when errors are no longer possible. - var block_placement: ?*TextBlock = null; + var atom_placement: ?*Atom = null; var free_list_removal: ?usize = null; - const vaddr = blk: { + // First we look for an appropriately sized free list node. + // The list is unordered. We'll just take the first thing that works. + var vaddr = blk: { var i: usize = 0; - while (i < self.text_block_free_list.items.len) { - const free_block = self.text_block_free_list.items[i]; - - const next_block_text_offset = free_block.text_offset + free_block.capacity(); - const new_block_text_offset = mem.alignForwardGeneric(u64, free_block.getVAddr(self.*) + free_block.size, alignment) - self.text_section_virtual_address; - if (new_block_text_offset < next_block_text_offset and next_block_text_offset - new_block_text_offset >= new_block_min_capacity) { - block_placement = free_block; - - const remaining_capacity = next_block_text_offset - new_block_text_offset - new_block_min_capacity; - if (remaining_capacity < minimum_text_block_size) { - free_list_removal = i; - } - - break :blk new_block_text_offset + self.text_section_virtual_address; - } else { - if (!free_block.freeListEligible()) { - _ = self.text_block_free_list.swapRemove(i); + while (i < free_list.items.len) { + const big_atom = free_list.items[i]; + // We now have a pointer to a live atom that has too much capacity. + // Is it enough that we could fit this new atom? + const sym = big_atom.getSymbol(self); + const capacity = big_atom.capacity(self); + const ideal_capacity = if (header.isCode()) padToIdeal(capacity) else capacity; + const ideal_capacity_end_vaddr = math.add(u32, sym.value, ideal_capacity) catch ideal_capacity; + const capacity_end_vaddr = sym.value + capacity; + const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity; + const new_start_vaddr = mem.alignBackwardGeneric(u32, new_start_vaddr_unaligned, alignment); + if (new_start_vaddr < ideal_capacity_end_vaddr) { + // Additional bookkeeping here to notice if this free list node + // should be deleted because the atom that it points to has grown to take up + // more of the extra capacity. + if (!big_atom.freeListEligible(self)) { + _ = free_list.swapRemove(i); } else { i += 1; } continue; } - } else if (self.last_text_block) |last| { - const new_block_vaddr = mem.alignForwardGeneric(u64, last.getVAddr(self.*) + last.size, alignment); - block_placement = last; - break :blk new_block_vaddr; + // At this point we know that we will place the new atom here. But the + // remaining question is whether there is still yet enough capacity left + // over for there to still be a free list node. + const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr; + const keep_free_list_node = remaining_capacity >= min_text_capacity; + + // Set up the metadata to be updated, after errors are no longer possible. + atom_placement = big_atom; + if (!keep_free_list_node) { + free_list_removal = i; + } + break :blk new_start_vaddr; + } else if (maybe_last_atom.*) |last| { + const last_symbol = last.getSymbol(self); + const ideal_capacity = if (header.isCode()) padToIdeal(last.size) else last.size; + const ideal_capacity_end_vaddr = last_symbol.value + ideal_capacity; + const new_start_vaddr = mem.alignForwardGeneric(u32, ideal_capacity_end_vaddr, alignment); + atom_placement = last; + break :blk new_start_vaddr; } else { - break :blk self.text_section_virtual_address; + break :blk mem.alignForwardGeneric(u32, header.virtual_address, alignment); } }; - const expand_text_section = block_placement == null or block_placement.?.next == null; - if (expand_text_section) { - const needed_size = @intCast(u32, mem.alignForwardGeneric(u64, vaddr + new_block_size - self.text_section_virtual_address, file_alignment)); - if (needed_size > self.text_section_size) { - const current_text_section_virtual_size = mem.alignForwardGeneric(u32, self.text_section_size, section_alignment); - const new_text_section_virtual_size = mem.alignForwardGeneric(u32, needed_size, section_alignment); - if (current_text_section_virtual_size != new_text_section_virtual_size) { - self.size_of_image_dirty = true; - // Write new virtual size - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, new_text_section_virtual_size); - try self.base.file.?.pwriteAll(&buf, self.section_table_offset + 40 + 8); - } - - self.text_section_size = needed_size; - self.text_section_size_dirty = true; + const expand_section = atom_placement == null or atom_placement.?.next == null; + if (expand_section) { + const sect_capacity = self.allocatedSize(header.pointer_to_raw_data); + const needed_size: u32 = (vaddr + new_atom_size) - header.virtual_address; + if (needed_size > sect_capacity) { + @panic("TODO move section"); } - self.last_text_block = text_block; + maybe_last_atom.* = atom; + // header.virtual_size = needed_size; + // header.size_of_raw_data = mem.alignForwardGeneric(u32, needed_size, default_file_alignment); } - text_block.text_offset = @intCast(u32, vaddr - self.text_section_virtual_address); - text_block.size = @intCast(u32, new_block_size); - // This function can also reallocate a text block. - // In this case we need to "unplug" it from its previous location before - // plugging it in to its new location. - if (text_block.prev) |prev| { - prev.next = text_block.next; + // if (header.getAlignment().? < alignment) { + // header.setAlignment(alignment); + // } + atom.size = new_atom_size; + atom.alignment = alignment; + + if (atom.prev) |prev| { + prev.next = atom.next; } - if (text_block.next) |next| { - next.prev = text_block.prev; + if (atom.next) |next| { + next.prev = atom.prev; } - if (block_placement) |big_block| { - text_block.prev = big_block; - text_block.next = big_block.next; - big_block.next = text_block; + if (atom_placement) |big_atom| { + atom.prev = big_atom; + atom.next = big_atom.next; + big_atom.next = atom; } else { - text_block.prev = null; - text_block.next = null; + atom.prev = null; + atom.next = null; } if (free_list_removal) |i| { - _ = self.text_block_free_list.swapRemove(i); + _ = free_list.swapRemove(i); } + return vaddr; } -fn growTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { - const block_vaddr = text_block.getVAddr(self.*); - const align_ok = mem.alignBackwardGeneric(u64, block_vaddr, alignment) == block_vaddr; - const need_realloc = !align_ok or new_block_size > text_block.capacity(); - if (!need_realloc) return @as(u64, block_vaddr); - return self.allocateTextBlock(text_block, new_block_size, alignment); +fn allocateSymbol(self: *Coff) !u32 { + const gpa = self.base.allocator; + try self.locals.ensureUnusedCapacity(gpa, 1); + + const index = blk: { + if (self.locals_free_list.popOrNull()) |index| { + log.debug(" (reusing symbol index {d})", .{index}); + break :blk index; + } else { + log.debug(" (allocating symbol index {d})", .{self.locals.items.len}); + const index = @intCast(u32, self.locals.items.len); + _ = self.locals.addOneAssumeCapacity(); + break :blk index; + } + }; + + self.locals.items[index] = .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = @intToEnum(coff.SectionNumber, 0), + .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }; + + return index; } -fn shrinkTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64) void { - text_block.size = @intCast(u32, new_block_size); - if (text_block.capacity() - text_block.size >= minimum_text_block_size) { - self.text_block_free_list.append(self.base.allocator, text_block) catch {}; +pub fn allocateGotEntry(self: *Coff, target: SymbolWithLoc) !u32 { + const gpa = self.base.allocator; + try self.got_entries.ensureUnusedCapacity(gpa, 1); + const index: u32 = blk: { + if (self.got_entries_free_list.popOrNull()) |index| { + log.debug(" (reusing GOT entry index {d})", .{index}); + if (self.got_entries.getIndex(target)) |existing| { + assert(existing == index); + } + break :blk index; + } else { + log.debug(" (allocating GOT entry at index {d})", .{self.got_entries.keys().len}); + const index = @intCast(u32, self.got_entries.keys().len); + self.got_entries.putAssumeCapacityNoClobber(target, 0); + break :blk index; + } + }; + self.got_entries.keys()[index] = target; + return index; +} + +fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom { + const gpa = self.base.allocator; + const atom = try gpa.create(Atom); + errdefer gpa.destroy(atom); + atom.* = Atom.empty; + atom.sym_index = try self.allocateSymbol(); + atom.size = @sizeOf(u64); + atom.alignment = @alignOf(u64); + + try self.managed_atoms.append(gpa, atom); + try self.atom_by_index_table.putNoClobber(gpa, atom.sym_index, atom); + self.got_entries.getPtr(target).?.* = atom.sym_index; + + const sym = atom.getSymbolPtr(self); + sym.section_number = @intToEnum(coff.SectionNumber, self.got_section_index.? + 1); + sym.value = try self.allocateAtom(atom, atom.size, atom.alignment); + + log.debug("allocated GOT atom at 0x{x}", .{sym.value}); + + try atom.addRelocation(self, .{ + .@"type" = .direct, + .target = target, + .offset = 0, + .addend = 0, + .pcrel = false, + .length = 3, + .prev_vaddr = sym.value, + }); + + return atom; +} + +fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32) !u32 { + const sym = atom.getSymbol(self); + const align_ok = mem.alignBackwardGeneric(u32, sym.value, alignment) == sym.value; + const need_realloc = !align_ok or new_atom_size > atom.capacity(self); + if (!need_realloc) return sym.value; + return self.allocateAtom(atom, new_atom_size, alignment); +} + +fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u32) void { + _ = self; + _ = atom; + _ = new_block_size; + // TODO check the new capacity, and if it crosses the size threshold into a big enough + // capacity, insert a free list node for it. +} + +fn writeAtom(self: *Coff, atom: *Atom, code: []const u8) !void { + const sym = atom.getSymbol(self); + const section = self.sections.get(@enumToInt(sym.section_number) - 1); + const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address; + log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset }); + try self.base.file.?.pwriteAll(code, file_offset); + try self.resolveRelocs(atom); +} + +fn writeGotAtom(self: *Coff, atom: *Atom) !void { + switch (self.ptr_width) { + .p32 => { + var buffer: [@sizeOf(u32)]u8 = [_]u8{0} ** @sizeOf(u32); + try self.writeAtom(atom, &buffer); + }, + .p64 => { + var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64); + try self.writeAtom(atom, &buffer); + }, } } -fn freeTextBlock(self: *Coff, text_block: *TextBlock) void { +fn resolveRelocs(self: *Coff, atom: *Atom) !void { + const relocs = self.relocs.get(atom) orelse return; + const source_sym = atom.getSymbol(self); + const source_section = self.sections.get(@enumToInt(source_sym.section_number) - 1).header; + const file_offset = source_section.pointer_to_raw_data + source_sym.value - source_section.virtual_address; + + log.debug("relocating '{s}'", .{atom.getName(self)}); + + for (relocs.items) |*reloc| { + const target_vaddr = switch (reloc.@"type") { + .got => blk: { + const got_atom = self.getGotAtomForSymbol(reloc.target) orelse continue; + break :blk got_atom.getSymbol(self).value; + }, + .direct => self.getSymbol(reloc.target).value, + }; + const target_vaddr_with_addend = target_vaddr + reloc.addend; + + if (target_vaddr_with_addend == reloc.prev_vaddr) continue; + + log.debug(" ({x}: [() => 0x{x} ({s})) ({s})", .{ + reloc.offset, + target_vaddr_with_addend, + self.getSymbolName(reloc.target), + @tagName(reloc.@"type"), + }); + + if (reloc.pcrel) { + const source_vaddr = source_sym.value + reloc.offset; + const disp = target_vaddr_with_addend - source_vaddr - 4; + try self.base.file.?.pwriteAll(mem.asBytes(&@intCast(u32, disp)), file_offset + reloc.offset); + return; + } + + switch (self.ptr_width) { + .p32 => try self.base.file.?.pwriteAll( + mem.asBytes(&@intCast(u32, target_vaddr_with_addend + default_image_base_exe)), + file_offset + reloc.offset, + ), + .p64 => switch (reloc.length) { + 2 => try self.base.file.?.pwriteAll( + mem.asBytes(&@truncate(u32, target_vaddr_with_addend + default_image_base_exe)), + file_offset + reloc.offset, + ), + 3 => try self.base.file.?.pwriteAll( + mem.asBytes(&(target_vaddr_with_addend + default_image_base_exe)), + file_offset + reloc.offset, + ), + else => unreachable, + }, + } + + reloc.prev_vaddr = target_vaddr_with_addend; + } +} + +fn freeAtom(self: *Coff, atom: *Atom) void { + log.debug("freeAtom {*}", .{atom}); + + const sym = atom.getSymbol(self); + const sect_id = @enumToInt(sym.section_number) - 1; + const free_list = &self.sections.items(.free_list)[sect_id]; var already_have_free_list_node = false; { var i: usize = 0; - // TODO turn text_block_free_list into a hash map - while (i < self.text_block_free_list.items.len) { - if (self.text_block_free_list.items[i] == text_block) { - _ = self.text_block_free_list.swapRemove(i); + // TODO turn free_list into a hash map + while (i < free_list.items.len) { + if (free_list.items[i] == atom) { + _ = free_list.swapRemove(i); continue; } - if (self.text_block_free_list.items[i] == text_block.prev) { + if (free_list.items[i] == atom.prev) { already_have_free_list_node = true; } i += 1; } } - if (self.last_text_block == text_block) { - self.last_text_block = text_block.prev; + + const maybe_last_atom = &self.sections.items(.last_atom)[sect_id]; + if (maybe_last_atom.*) |last_atom| { + if (last_atom == atom) { + if (atom.prev) |prev| { + // TODO shrink the section size here + maybe_last_atom.* = prev; + } else { + maybe_last_atom.* = null; + } + } } - if (text_block.prev) |prev| { - prev.next = text_block.next; - if (!already_have_free_list_node and prev.freeListEligible()) { + if (atom.prev) |prev| { + prev.next = atom.next; + + if (!already_have_free_list_node and prev.freeListEligible(self)) { // The free list is heuristics, it doesn't have to be perfect, so we can // ignore the OOM here. - self.text_block_free_list.append(self.base.allocator, prev) catch {}; + free_list.append(self.base.allocator, prev) catch {}; } + } else { + atom.prev = null; } - if (text_block.next) |next| { - next.prev = text_block.prev; - } -} - -fn writeOffsetTableEntry(self: *Coff, index: usize) !void { - const entry_size = self.base.options.target.cpu.arch.ptrBitWidth() / 8; - const endian = self.base.options.target.cpu.arch.endian(); - - const offset_table_start = self.section_data_offset; - if (self.offset_table_size_dirty) { - const current_raw_size = self.offset_table_size; - const new_raw_size = self.offset_table_size * 2; - log.debug("growing offset table from raw size {} to {}\n", .{ current_raw_size, new_raw_size }); - - // Move the text section to a new place in the executable - const current_text_section_start = self.section_data_offset + current_raw_size; - const new_text_section_start = self.section_data_offset + new_raw_size; - - const amt = try self.base.file.?.copyRangeAll(current_text_section_start, self.base.file.?, new_text_section_start, self.text_section_size); - if (amt != self.text_section_size) return error.InputOutput; - - // Write the new raw size in the .got header - var buf: [8]u8 = undefined; - mem.writeIntLittle(u32, buf[0..4], new_raw_size); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 16); - // Write the new .text section file offset in the .text section header - mem.writeIntLittle(u32, buf[0..4], new_text_section_start); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 40 + 20); - - const current_virtual_size = mem.alignForwardGeneric(u32, self.offset_table_size, section_alignment); - const new_virtual_size = mem.alignForwardGeneric(u32, new_raw_size, section_alignment); - // If we had to move in the virtual address space, we need to fix the VAs in the offset table, as well as the virtual address of the `.text` section - // and the virtual size of the `.got` section - - if (new_virtual_size != current_virtual_size) { - log.debug("growing offset table from virtual size {} to {}\n", .{ current_virtual_size, new_virtual_size }); - self.size_of_image_dirty = true; - const va_offset = new_virtual_size - current_virtual_size; - - // Write .got virtual size - mem.writeIntLittle(u32, buf[0..4], new_virtual_size); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 8); - - // Write .text new virtual address - self.text_section_virtual_address = self.text_section_virtual_address + va_offset; - mem.writeIntLittle(u32, buf[0..4], self.text_section_virtual_address - default_image_base); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 40 + 12); - - // Fix the VAs in the offset table - for (self.offset_table.items) |*va, idx| { - if (va.* != 0) { - va.* += va_offset; - - switch (entry_size) { - 4 => { - mem.writeInt(u32, buf[0..4], @intCast(u32, va.*), endian); - try self.base.file.?.pwriteAll(buf[0..4], offset_table_start + idx * entry_size); - }, - 8 => { - mem.writeInt(u64, &buf, va.*, endian); - try self.base.file.?.pwriteAll(&buf, offset_table_start + idx * entry_size); - }, - else => unreachable, - } - } - } - } - self.offset_table_size = new_raw_size; - self.offset_table_size_dirty = false; - } - // Write the new entry - switch (entry_size) { - 4 => { - var buf: [4]u8 = undefined; - mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian); - try self.base.file.?.pwriteAll(&buf, offset_table_start + index * entry_size); - }, - 8 => { - var buf: [8]u8 = undefined; - mem.writeInt(u64, &buf, self.offset_table.items[index], endian); - try self.base.file.?.pwriteAll(&buf, offset_table_start + index * entry_size); - }, - else => unreachable, + if (atom.next) |next| { + next.prev = atom.prev; + } else { + atom.next = null; } } @@ -702,15 +790,18 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live }, }; - return self.finishUpdateDecl(module, func.owner_decl, code); + try self.updateDeclCode(decl_index, code, .FUNCTION); + + // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. + const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; + return self.updateDeclExports(module, decl_index, decl_exports); } pub fn lowerUnnamedConst(self: *Coff, tv: TypedValue, decl_index: Module.Decl.Index) !u32 { _ = self; _ = tv; _ = decl_index; - log.debug("TODO lowerUnnamedConst for Coff", .{}); - return error.AnalysisFail; + @panic("TODO lowerUnnamedConst"); } pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) !void { @@ -728,16 +819,20 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! if (decl.val.tag() == .extern_fn) { return; // TODO Should we do more when front-end analyzed extern decl? } - - // TODO COFF/PE debug information - // TODO Implement exports + if (decl.val.castTag(.variable)) |payload| { + const variable = payload.data; + if (variable.is_extern) { + return; // TODO Should we do more when front-end analyzed extern decl? + } + } var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); + const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val; const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{ .ty = decl.ty, - .val = decl.val, + .val = decl_val, }, &code_buffer, .none, .{ .parent_atom_index = 0, }); @@ -751,47 +846,98 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! }, }; - return self.finishUpdateDecl(module, decl_index, code); + try self.updateDeclCode(decl_index, code, .NULL); + + // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. + const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; + return self.updateDeclExports(module, decl_index, decl_exports); } -fn finishUpdateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index, code: []const u8) !void { - const decl = module.declPtr(decl_index); - const required_alignment = decl.ty.abiAlignment(self.base.options.target); - const curr_size = decl.link.coff.size; - if (curr_size != 0) { - const capacity = decl.link.coff.capacity(); - const need_realloc = code.len > capacity or - !mem.isAlignedGeneric(u32, decl.link.coff.text_offset, required_alignment); +fn getDeclOutputSection(self: *Coff, decl: *Module.Decl) u16 { + const ty = decl.ty; + const zig_ty = ty.zigTypeTag(); + const val = decl.val; + const index: u16 = blk: { + if (val.isUndefDeep()) { + // TODO in release-fast and release-small, we should put undef in .bss + break :blk self.data_section_index.?; + } + + switch (zig_ty) { + .Fn => break :blk self.text_section_index.?, + else => { + if (val.castTag(.variable)) |_| { + break :blk self.data_section_index.?; + } + break :blk self.rdata_section_index.?; + }, + } + }; + return index; +} + +fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8, complex_type: coff.ComplexType) !void { + const gpa = self.base.allocator; + const mod = self.base.options.module.?; + const decl = mod.declPtr(decl_index); + + const decl_name = try decl.getFullyQualifiedName(mod); + defer gpa.free(decl_name); + + log.debug("updateDeclCode {s}{*}", .{ decl_name, decl }); + const required_alignment = decl.getAlignment(self.base.options.target); + + const decl_ptr = self.decls.getPtr(decl_index).?; + if (decl_ptr.* == null) { + decl_ptr.* = self.getDeclOutputSection(decl); + } + const sect_index = decl_ptr.*.?; + + const code_len = @intCast(u32, code.len); + const atom = &decl.link.coff; + assert(atom.sym_index != 0); // Caller forgot to allocateDeclIndexes() + if (atom.size != 0) { + const sym = atom.getSymbolPtr(self); + try self.setSymbolName(sym, decl_name); + sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); + sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; + + const capacity = atom.capacity(self); + const need_realloc = code.len > capacity or !mem.isAlignedGeneric(u64, sym.value, required_alignment); if (need_realloc) { - const curr_vaddr = self.text_section_virtual_address + decl.link.coff.text_offset; - const vaddr = try self.growTextBlock(&decl.link.coff, code.len, required_alignment); - log.debug("growing {s} from 0x{x} to 0x{x}\n", .{ decl.name, curr_vaddr, vaddr }); - if (vaddr != curr_vaddr) { - log.debug(" (writing new offset table entry)\n", .{}); - self.offset_table.items[decl.link.coff.offset_table_index] = vaddr; - try self.writeOffsetTableEntry(decl.link.coff.offset_table_index); + const vaddr = try self.growAtom(atom, code_len, required_alignment); + log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl_name, sym.value, vaddr }); + log.debug(" (required alignment 0x{x}", .{required_alignment}); + + if (vaddr != sym.value) { + sym.value = vaddr; + log.debug(" (updating GOT entry)", .{}); + const got_atom = self.getGotAtomForSymbol(.{ .sym_index = atom.sym_index, .file = null }).?; + try self.writeGotAtom(got_atom); } - } else if (code.len < curr_size) { - self.shrinkTextBlock(&decl.link.coff, code.len); + } else if (code_len < atom.size) { + self.shrinkAtom(atom, code_len); } + atom.size = code_len; } else { - const vaddr = try self.allocateTextBlock(&decl.link.coff, code.len, required_alignment); - log.debug("allocated text block for {s} at 0x{x} (size: {Bi})\n", .{ - mem.sliceTo(decl.name, 0), - vaddr, - std.fmt.fmtIntSizeDec(code.len), - }); - errdefer self.freeTextBlock(&decl.link.coff); - self.offset_table.items[decl.link.coff.offset_table_index] = vaddr; - try self.writeOffsetTableEntry(decl.link.coff.offset_table_index); + const sym = atom.getSymbolPtr(self); + try self.setSymbolName(sym, decl_name); + sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); + sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; + + const vaddr = try self.allocateAtom(atom, code_len, required_alignment); + errdefer self.freeAtom(atom); + log.debug("allocated atom for {s} at 0x{x}", .{ decl_name, vaddr }); + atom.size = code_len; + sym.value = vaddr; + + const got_target = SymbolWithLoc{ .sym_index = atom.sym_index, .file = null }; + _ = try self.allocateGotEntry(got_target); + const got_atom = try self.createGotAtom(got_target); + try self.writeGotAtom(got_atom); } - // Write the code into the file - try self.base.file.?.pwriteAll(code, self.section_data_offset + self.offset_table_size + decl.link.coff.text_offset); - - // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. - const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; - return self.updateDeclExports(module, decl_index, decl_exports); + try self.writeAtom(atom, code); } pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { @@ -802,9 +948,31 @@ pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { const mod = self.base.options.module.?; const decl = mod.declPtr(decl_index); + log.debug("freeDecl {*}", .{decl}); + + const kv = self.decls.fetchRemove(decl_index); + if (kv.?.value) |_| { + self.freeAtom(&decl.link.coff); + } + // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. - self.freeTextBlock(&decl.link.coff); - self.offset_table_free_list.append(self.base.allocator, decl.link.coff.offset_table_index) catch {}; + const gpa = self.base.allocator; + const sym_index = decl.link.coff.sym_index; + if (sym_index != 0) { + self.locals_free_list.append(gpa, sym_index) catch {}; + + // Try freeing GOT atom if this decl had one + const got_target = SymbolWithLoc{ .sym_index = sym_index, .file = null }; + if (self.got_entries.getIndex(got_target)) |got_index| { + self.got_entries_free_list.append(gpa, @intCast(u32, got_index)) catch {}; + self.got_entries.values()[got_index] = 0; + log.debug(" adding GOT index {d} to free list (target local@{d})", .{ got_index, sym_index }); + } + + self.locals.items[sym_index].section_number = @intToEnum(coff.SectionNumber, 0); + _ = self.atom_by_index_table.remove(sym_index); + decl.link.coff.sym_index = 0; + } } pub fn updateDeclExports( @@ -817,64 +985,157 @@ pub fn updateDeclExports( @panic("Attempted to compile for object format that was disabled by build configuration"); } - // Even in the case of LLVM, we need to notice certain exported symbols in order to - // detect the default subsystem. - for (exports) |exp| { - const exported_decl = module.declPtr(exp.exported_decl); - if (exported_decl.getFunction() == null) continue; - const winapi_cc = switch (self.base.options.target.cpu.arch) { - .i386 => std.builtin.CallingConvention.Stdcall, - else => std.builtin.CallingConvention.C, - }; - const decl_cc = exported_decl.ty.fnCallingConvention(); - if (decl_cc == .C and mem.eql(u8, exp.options.name, "main") and - self.base.options.link_libc) - { - module.stage1_flags.have_c_main = true; - } else if (decl_cc == winapi_cc and self.base.options.target.os.tag == .windows) { - if (mem.eql(u8, exp.options.name, "WinMain")) { - module.stage1_flags.have_winmain = true; - } else if (mem.eql(u8, exp.options.name, "wWinMain")) { - module.stage1_flags.have_wwinmain = true; - } else if (mem.eql(u8, exp.options.name, "WinMainCRTStartup")) { - module.stage1_flags.have_winmain_crt_startup = true; - } else if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) { - module.stage1_flags.have_wwinmain_crt_startup = true; - } else if (mem.eql(u8, exp.options.name, "DllMainCRTStartup")) { - module.stage1_flags.have_dllmain_crt_startup = true; + if (build_options.have_llvm) { + // Even in the case of LLVM, we need to notice certain exported symbols in order to + // detect the default subsystem. + for (exports) |exp| { + const exported_decl = module.declPtr(exp.exported_decl); + if (exported_decl.getFunction() == null) continue; + const winapi_cc = switch (self.base.options.target.cpu.arch) { + .i386 => std.builtin.CallingConvention.Stdcall, + else => std.builtin.CallingConvention.C, + }; + const decl_cc = exported_decl.ty.fnCallingConvention(); + if (decl_cc == .C and mem.eql(u8, exp.options.name, "main") and + self.base.options.link_libc) + { + module.stage1_flags.have_c_main = true; + } else if (decl_cc == winapi_cc and self.base.options.target.os.tag == .windows) { + if (mem.eql(u8, exp.options.name, "WinMain")) { + module.stage1_flags.have_winmain = true; + } else if (mem.eql(u8, exp.options.name, "wWinMain")) { + module.stage1_flags.have_wwinmain = true; + } else if (mem.eql(u8, exp.options.name, "WinMainCRTStartup")) { + module.stage1_flags.have_winmain_crt_startup = true; + } else if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) { + module.stage1_flags.have_wwinmain_crt_startup = true; + } else if (mem.eql(u8, exp.options.name, "DllMainCRTStartup")) { + module.stage1_flags.have_dllmain_crt_startup = true; + } } } - } - if (build_options.have_llvm) { if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl_index, exports); } + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = self.base.allocator; + const decl = module.declPtr(decl_index); + const atom = &decl.link.coff; + if (atom.sym_index == 0) return; + const decl_sym = atom.getSymbol(self); + for (exports) |exp| { + log.debug("adding new export '{s}'", .{exp.options.name}); + if (exp.options.section) |section_name| { if (!mem.eql(u8, section_name, ".text")) { - try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); - module.failed_exports.putAssumeCapacityNoClobber( + try module.failed_exports.putNoClobber( + module.gpa, exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), + try Module.ErrorMsg.create( + gpa, + decl.srcLoc(), + "Unimplemented: ExportOptions.section", + .{}, + ), ); continue; } } - if (mem.eql(u8, exp.options.name, "_start")) { - self.entry_addr = decl.link.coff.getVAddr(self.*) - default_image_base; - } else { - try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); - module.failed_exports.putAssumeCapacityNoClobber( + + if (exp.options.linkage == .LinkOnce) { + try module.failed_exports.putNoClobber( + module.gpa, exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than '_start'", .{}), + try Module.ErrorMsg.create( + gpa, + decl.srcLoc(), + "Unimplemented: GlobalLinkage.LinkOnce", + .{}, + ), ); continue; } + + const sym_index = exp.link.coff.sym_index orelse blk: { + const sym_index = try self.allocateSymbol(); + exp.link.coff.sym_index = sym_index; + break :blk sym_index; + }; + const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null }; + const sym = self.getSymbolPtr(sym_loc); + try self.setSymbolName(sym, exp.options.name); + sym.value = decl_sym.value; + sym.section_number = @intToEnum(coff.SectionNumber, self.text_section_index.? + 1); + sym.@"type" = .{ .complex_type = .FUNCTION, .base_type = .NULL }; + + switch (exp.options.linkage) { + .Strong => { + sym.storage_class = .EXTERNAL; + }, + .Internal => @panic("TODO Internal"), + .Weak => @panic("TODO WeakExternal"), + else => unreachable, + } + + try self.resolveGlobalSymbol(sym_loc); + } +} + +pub fn deleteExport(self: *Coff, exp: Export) void { + if (self.llvm_object) |_| return; + const sym_index = exp.sym_index orelse return; + + const gpa = self.base.allocator; + + const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null }; + const sym = self.getSymbolPtr(sym_loc); + const sym_name = self.getSymbolName(sym_loc); + log.debug("deleting export '{s}'", .{sym_name}); + assert(sym.storage_class == .EXTERNAL); + sym.* = .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = @intToEnum(coff.SectionNumber, 0), + .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }; + self.locals_free_list.append(gpa, sym_index) catch {}; + + if (self.globals.get(sym_name)) |global| blk: { + if (global.sym_index != sym_index) break :blk; + if (global.file != null) break :blk; + const kv = self.globals.fetchSwapRemove(sym_name); + gpa.free(kv.?.key); } } +fn resolveGlobalSymbol(self: *Coff, current: SymbolWithLoc) !void { + const gpa = self.base.allocator; + const sym = self.getSymbol(current); + _ = sym; + const sym_name = self.getSymbolName(current); + + const name = try gpa.dupe(u8, sym_name); + const global_index = @intCast(u32, self.globals.values().len); + _ = global_index; + const gop = try self.globals.getOrPut(gpa, name); + defer if (gop.found_existing) gpa.free(name); + + if (!gop.found_existing) { + gop.value_ptr.* = current; + // TODO undef + tentative + return; + } + + log.debug("TODO finish resolveGlobalSymbols implementation", .{}); +} + pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { if (self.base.options.emit == null) { if (build_options.have_llvm) { @@ -884,14 +1145,13 @@ pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !vo } return; } - if (build_options.have_llvm and self.base.options.use_lld) { - return self.linkWithLLD(comp, prog_node); - } else { - switch (self.base.options.effectiveOutputMode()) { - .Exe, .Obj => {}, - .Lib => return error.TODOImplementWritingLibFiles, - } - return self.flushModule(comp, prog_node); + const use_lld = build_options.have_llvm and self.base.options.use_lld; + if (use_lld) { + return lld.linkWithLLD(self, comp, prog_node); + } + switch (self.base.options.output_mode) { + .Exe, .Obj => return self.flushModule(comp, prog_node), + .Lib => return error.TODOImplementWritingLibFiles, } } @@ -909,648 +1169,449 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); - if (self.text_section_size_dirty) { - // Write the new raw size in the .text header - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, self.text_section_size); - try self.base.file.?.pwriteAll(&buf, self.section_table_offset + 40 + 16); - try self.base.file.?.setEndPos(self.section_data_offset + self.offset_table_size + self.text_section_size); - self.text_section_size_dirty = false; + if (build_options.enable_logging) { + self.logSymtab(); } - if (self.base.options.output_mode == .Exe and self.size_of_image_dirty) { - const new_size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + self.text_section_size, section_alignment); - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, new_size_of_image); - try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 56); - self.size_of_image_dirty = false; + { + var it = self.relocs.keyIterator(); + while (it.next()) |atom| { + try self.resolveRelocs(atom.*); + } } + if (self.getEntryPoint()) |entry_sym_loc| { + self.entry_addr = self.getSymbol(entry_sym_loc).value; + } + + try self.writeStrtab(); + try self.writeDataDirectoriesHeaders(); + try self.writeSectionHeaders(); + if (self.entry_addr == null and self.base.options.output_mode == .Exe) { log.debug("flushing. no_entry_point_found = true\n", .{}); self.error_flags.no_entry_point_found = true; } else { log.debug("flushing. no_entry_point_found = false\n", .{}); self.error_flags.no_entry_point_found = false; - - if (self.base.options.output_mode == .Exe) { - // Write AddressOfEntryPoint - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, self.entry_addr.?); - try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 16); - } + try self.writeHeader(); } } -fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { - const tracy = trace(@src()); - defer tracy.end(); - - var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type. - const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); - - // If there is no Zig code to compile, then we should skip flushing the output file because it - // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: { - const use_stage1 = build_options.have_stage1 and self.base.options.use_stage1; - if (use_stage1) { - const obj_basename = try std.zig.binNameAlloc(arena, .{ - .root_name = self.base.options.root_name, - .target = self.base.options.target, - .output_mode = .Obj, - }); - switch (self.base.options.cache_mode) { - .incremental => break :blk try module.zig_cache_artifact_directory.join( - arena, - &[_][]const u8{obj_basename}, - ), - .whole => break :blk try fs.path.join(arena, &.{ - fs.path.dirname(full_out_path).?, obj_basename, - }), - } - } - - try self.flushModule(comp, prog_node); +pub fn getDeclVAddr( + self: *Coff, + decl_index: Module.Decl.Index, + reloc_info: link.File.RelocInfo, +) !u64 { + _ = self; + _ = decl_index; + _ = reloc_info; + @panic("TODO getDeclVAddr"); +} - if (fs.path.dirname(full_out_path)) |dirname| { - break :blk try fs.path.join(arena, &.{ dirname, self.base.intermediary_basename.? }); - } else { - break :blk self.base.intermediary_basename.?; - } - } else null; +pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !void { + _ = self; + _ = module; + _ = decl; + log.debug("TODO implement updateDeclLineNumber", .{}); +} - var sub_prog_node = prog_node.start("LLD Link", 0); - sub_prog_node.activate(); - sub_prog_node.context.refresh(); - defer sub_prog_node.end(); +fn writeStrtab(self: *Coff) !void { + const allocated_size = self.allocatedSize(self.strtab_offset.?); + const needed_size = @intCast(u32, self.strtab.len()); - const is_lib = self.base.options.output_mode == .Lib; - const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib; - const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe; - const link_in_crt = self.base.options.link_libc and is_exe_or_dyn_lib; - const target = self.base.options.target; + if (needed_size > allocated_size) { + self.strtab_offset = null; + self.strtab_offset = @intCast(u32, self.findFreeSpace(needed_size, 1)); + } - // See link/Elf.zig for comments on how this mechanism works. - const id_symlink_basename = "lld.id"; + log.debug("writing strtab from 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + needed_size }); + try self.base.file.?.pwriteAll(self.strtab.buffer.items, self.strtab_offset.?); +} - var man: Cache.Manifest = undefined; - defer if (!self.base.options.disable_lld_caching) man.deinit(); +fn writeSectionHeaders(self: *Coff) !void { + const offset = self.getSectionHeadersOffset(); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items(.header)), offset); +} - var digest: [Cache.hex_digest_len]u8 = undefined; +fn writeDataDirectoriesHeaders(self: *Coff) !void { + const offset = self.getDataDirectoryHeadersOffset(); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(&self.data_directories), offset); +} - if (!self.base.options.disable_lld_caching) { - man = comp.cache_parent.obtain(); - self.base.releaseLock(); +fn writeHeader(self: *Coff) !void { + const gpa = self.base.allocator; + var buffer = std.ArrayList(u8).init(gpa); + defer buffer.deinit(); + const writer = buffer.writer(); - comptime assert(Compilation.link_hash_implementation_version == 7); + try buffer.ensureTotalCapacity(self.getSizeOfHeaders()); + writer.writeAll(msdos_stub) catch unreachable; + mem.writeIntLittle(u32, buffer.items[0x3c..][0..4], msdos_stub.len); - for (self.base.options.objects) |obj| { - _ = try man.addFile(obj.path, null); - man.hash.add(obj.must_link); - } - for (comp.c_object_table.keys()) |key| { - _ = try man.addFile(key.status.success.object_path, null); - } - try man.addOptionalFile(module_obj_path); - man.hash.addOptionalBytes(self.base.options.entry); - man.hash.addOptional(self.base.options.stack_size_override); - man.hash.addOptional(self.base.options.image_base_override); - man.hash.addListOfBytes(self.base.options.lib_dirs); - man.hash.add(self.base.options.skip_linker_dependencies); - if (self.base.options.link_libc) { - man.hash.add(self.base.options.libc_installation != null); - if (self.base.options.libc_installation) |libc_installation| { - man.hash.addBytes(libc_installation.crt_dir.?); - if (target.abi == .msvc) { - man.hash.addBytes(libc_installation.msvc_lib_dir.?); - man.hash.addBytes(libc_installation.kernel32_lib_dir.?); - } - } - } - link.hashAddSystemLibs(&man.hash, self.base.options.system_libs); - man.hash.addListOfBytes(self.base.options.force_undefined_symbols.keys()); - man.hash.addOptional(self.base.options.subsystem); - man.hash.add(self.base.options.is_test); - man.hash.add(self.base.options.tsaware); - man.hash.add(self.base.options.nxcompat); - man.hash.add(self.base.options.dynamicbase); - // strip does not need to go into the linker hash because it is part of the hash namespace - man.hash.addOptional(self.base.options.major_subsystem_version); - man.hash.addOptional(self.base.options.minor_subsystem_version); - - // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. - _ = try man.hit(); - digest = man.final(); - var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = Cache.readSmallFile( - directory.handle, - id_symlink_basename, - &prev_digest_buf, - ) catch |err| blk: { - log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); - // Handle this as a cache miss. - break :blk prev_digest_buf[0..0]; - }; - if (mem.eql(u8, prev_digest, &digest)) { - log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); - // Hot diggity dog! The output binary is already there. - self.base.lock = man.toOwnedLock(); - return; - } - log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); - - // We are about to change the output file to be different, so we invalidate the build hash now. - directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { - error.FileNotFound => {}, - else => |e| return e, - }; + writer.writeAll("PE\x00\x00") catch unreachable; + var flags = coff.CoffHeaderFlags{ + .EXECUTABLE_IMAGE = 1, + .DEBUG_STRIPPED = 1, // TODO + }; + switch (self.ptr_width) { + .p32 => flags.@"32BIT_MACHINE" = 1, + .p64 => flags.LARGE_ADDRESS_AWARE = 1, + } + if (self.base.options.output_mode == .Lib and self.base.options.link_mode == .Dynamic) { + flags.DLL = 1; } - if (self.base.options.output_mode == .Obj) { - // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy - // here. TODO: think carefully about how we can avoid this redundant operation when doing - // build-obj. See also the corresponding TODO in linkAsArchive. - const the_object_path = blk: { - if (self.base.options.objects.len != 0) - break :blk self.base.options.objects[0].path; + const timestamp = std.time.timestamp(); + const size_of_optional_header = @intCast(u16, self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize()); + var coff_header = coff.CoffHeader{ + .machine = coff.MachineType.fromTargetCpuArch(self.base.options.target.cpu.arch), + .number_of_sections = @intCast(u16, self.sections.slice().len), // TODO what if we prune a section + .time_date_stamp = @truncate(u32, @bitCast(u64, timestamp)), + .pointer_to_symbol_table = self.strtab_offset orelse 0, + .number_of_symbols = 0, + .size_of_optional_header = size_of_optional_header, + .flags = flags, + }; - if (comp.c_object_table.count() != 0) - break :blk comp.c_object_table.keys()[0].status.success.object_path; + writer.writeAll(mem.asBytes(&coff_header)) catch unreachable; - if (module_obj_path) |p| - break :blk p; + const dll_flags: coff.DllFlags = .{ + .HIGH_ENTROPY_VA = 0, //@boolToInt(self.base.options.pie), + .DYNAMIC_BASE = 0, + .TERMINAL_SERVER_AWARE = 1, // We are not a legacy app + .NX_COMPAT = 1, // We are compatible with Data Execution Prevention + }; + const subsystem: coff.Subsystem = .WINDOWS_CUI; + const size_of_image: u32 = self.getSizeOfImage(); + const size_of_headers: u32 = mem.alignForwardGeneric(u32, self.getSizeOfHeaders(), default_file_alignment); + const image_base = self.base.options.image_base_override orelse switch (self.base.options.output_mode) { + .Exe => default_image_base_exe, + .Lib => default_image_base_dll, + else => unreachable, + }; - // TODO I think this is unreachable. Audit this situation when solving the above TODO - // regarding eliding redundant object -> object transformations. - return error.NoObjectsToLink; - }; - // This can happen when using --enable-cache and using the stage1 backend. In this case - // we can skip the file copy. - if (!mem.eql(u8, the_object_path, full_out_path)) { - try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); - } - } else { - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(self.base.allocator); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "lld-link" }); - - try argv.append("-ERRORLIMIT:0"); - try argv.append("-NOLOGO"); - if (!self.base.options.strip) { - try argv.append("-DEBUG"); - } - if (self.base.options.lto) { - switch (self.base.options.optimize_mode) { - .Debug => {}, - .ReleaseSmall => try argv.append("-OPT:lldlto=2"), - .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), - } + const base_of_code = self.sections.get(self.text_section_index.?).header.virtual_address; + const base_of_data = self.sections.get(self.data_section_index.?).header.virtual_address; + + var size_of_code: u32 = 0; + var size_of_initialized_data: u32 = 0; + var size_of_uninitialized_data: u32 = 0; + for (self.sections.items(.header)) |header| { + if (header.flags.CNT_CODE == 1) { + size_of_code += header.size_of_raw_data; } - if (self.base.options.output_mode == .Exe) { - const stack_size = self.base.options.stack_size_override orelse 16777216; - try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size})); + if (header.flags.CNT_INITIALIZED_DATA == 1) { + size_of_initialized_data += header.size_of_raw_data; } - if (self.base.options.image_base_override) |image_base| { - try argv.append(try std.fmt.allocPrint(arena, "-BASE:{d}", .{image_base})); + if (header.flags.CNT_UNINITIALIZED_DATA == 1) { + size_of_uninitialized_data += header.size_of_raw_data; } + } - if (target.cpu.arch == .i386) { - try argv.append("-MACHINE:X86"); - } else if (target.cpu.arch == .x86_64) { - try argv.append("-MACHINE:X64"); - } else if (target.cpu.arch.isARM()) { - if (target.cpu.arch.ptrBitWidth() == 32) { - try argv.append("-MACHINE:ARM"); - } else { - try argv.append("-MACHINE:ARM64"); - } - } + switch (self.ptr_width) { + .p32 => { + var opt_header = coff.OptionalHeaderPE32{ + .magic = coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC, + .major_linker_version = 0, + .minor_linker_version = 0, + .size_of_code = size_of_code, + .size_of_initialized_data = size_of_initialized_data, + .size_of_uninitialized_data = size_of_uninitialized_data, + .address_of_entry_point = self.entry_addr orelse 0, + .base_of_code = base_of_code, + .base_of_data = base_of_data, + .image_base = @intCast(u32, image_base), + .section_alignment = self.page_size, + .file_alignment = default_file_alignment, + .major_operating_system_version = 6, + .minor_operating_system_version = 0, + .major_image_version = 0, + .minor_image_version = 0, + .major_subsystem_version = 6, + .minor_subsystem_version = 0, + .win32_version_value = 0, + .size_of_image = size_of_image, + .size_of_headers = size_of_headers, + .checksum = 0, + .subsystem = subsystem, + .dll_flags = dll_flags, + .size_of_stack_reserve = default_size_of_stack_reserve, + .size_of_stack_commit = default_size_of_stack_commit, + .size_of_heap_reserve = default_size_of_heap_reserve, + .size_of_heap_commit = default_size_of_heap_commit, + .loader_flags = 0, + .number_of_rva_and_sizes = @intCast(u32, self.data_directories.len), + }; + writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; + }, + .p64 => { + var opt_header = coff.OptionalHeaderPE64{ + .magic = coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC, + .major_linker_version = 0, + .minor_linker_version = 0, + .size_of_code = size_of_code, + .size_of_initialized_data = size_of_initialized_data, + .size_of_uninitialized_data = size_of_uninitialized_data, + .address_of_entry_point = self.entry_addr orelse 0, + .base_of_code = base_of_code, + .image_base = image_base, + .section_alignment = self.page_size, + .file_alignment = default_file_alignment, + .major_operating_system_version = 6, + .minor_operating_system_version = 0, + .major_image_version = 0, + .minor_image_version = 0, + .major_subsystem_version = 6, + .minor_subsystem_version = 0, + .win32_version_value = 0, + .size_of_image = size_of_image, + .size_of_headers = size_of_headers, + .checksum = 0, + .subsystem = subsystem, + .dll_flags = dll_flags, + .size_of_stack_reserve = default_size_of_stack_reserve, + .size_of_stack_commit = default_size_of_stack_commit, + .size_of_heap_reserve = default_size_of_heap_reserve, + .size_of_heap_commit = default_size_of_heap_commit, + .loader_flags = 0, + .number_of_rva_and_sizes = @intCast(u32, self.data_directories.len), + }; + writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; + }, + } - for (self.base.options.force_undefined_symbols.keys()) |symbol| { - try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol})); - } + try self.base.file.?.pwriteAll(buffer.items, 0); +} - if (is_dyn_lib) { - try argv.append("-DLL"); - } +pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { + // TODO https://github.com/ziglang/zig/issues/1284 + return math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch + math.maxInt(@TypeOf(actual_size)); +} - if (self.base.options.entry) |entry| { - try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{entry})); - } +fn detectAllocCollision(self: *Coff, start: u32, size: u32) ?u32 { + const headers_size = self.getSizeOfHeaders(); + if (start < headers_size) + return headers_size; - if (self.base.options.tsaware) { - try argv.append("-tsaware"); - } - if (self.base.options.nxcompat) { - try argv.append("-nxcompat"); - } - if (self.base.options.dynamicbase) { - try argv.append("-dynamicbase"); - } + const end = start + size; - try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); + if (self.strtab_offset) |off| { + const increased_size = @intCast(u32, self.strtab.len()); + const test_end = off + increased_size; + if (end > off and start < test_end) { + return test_end; + } + } - if (self.base.options.implib_emit) |emit| { - const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path}); - try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); + for (self.sections.items(.header)) |header| { + const increased_size = header.size_of_raw_data; + const test_end = header.pointer_to_raw_data + increased_size; + if (end > header.pointer_to_raw_data and start < test_end) { + return test_end; } + } - if (self.base.options.link_libc) { - if (self.base.options.libc_installation) |libc_installation| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); + return null; +} - if (target.abi == .msvc) { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?})); - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?})); - } - } - } +pub fn allocatedSize(self: *Coff, start: u32) u32 { + if (start == 0) + return 0; + var min_pos: u32 = std.math.maxInt(u32); + if (self.strtab_offset) |off| { + if (off > start and off < min_pos) min_pos = off; + } + for (self.sections.items(.header)) |header| { + if (header.pointer_to_raw_data <= start) continue; + if (header.pointer_to_raw_data < min_pos) min_pos = header.pointer_to_raw_data; + } + return min_pos - start; +} - for (self.base.options.lib_dirs) |lib_dir| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir})); - } +pub fn findFreeSpace(self: *Coff, object_size: u32, min_alignment: u32) u32 { + var start: u32 = 0; + while (self.detectAllocCollision(start, object_size)) |item_end| { + start = mem.alignForwardGeneric(u32, item_end, min_alignment); + } + return start; +} - try argv.ensureUnusedCapacity(self.base.options.objects.len); - for (self.base.options.objects) |obj| { - if (obj.must_link) { - argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{s}", .{obj.path})); - } else { - argv.appendAssumeCapacity(obj.path); - } - } +inline fn getSizeOfHeaders(self: Coff) u32 { + const msdos_hdr_size = msdos_stub.len + 4; + return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize() + + self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize()); +} - for (comp.c_object_table.keys()) |key| { - try argv.append(key.status.success.object_path); - } +inline fn getOptionalHeaderSize(self: Coff) u32 { + return switch (self.ptr_width) { + .p32 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE32)), + .p64 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE64)), + }; +} - if (module_obj_path) |p| { - try argv.append(p); - } +inline fn getDataDirectoryHeadersSize(self: Coff) u32 { + return @intCast(u32, self.data_directories.len * @sizeOf(coff.ImageDataDirectory)); +} - const resolved_subsystem: ?std.Target.SubSystem = blk: { - if (self.base.options.subsystem) |explicit| break :blk explicit; - switch (target.os.tag) { - .windows => { - if (self.base.options.module) |module| { - if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib) - break :blk null; - if (module.stage1_flags.have_c_main or self.base.options.is_test or - module.stage1_flags.have_winmain_crt_startup or - module.stage1_flags.have_wwinmain_crt_startup) - { - break :blk .Console; - } - if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain) - break :blk .Windows; - } - }, - .uefi => break :blk .EfiApplication, - else => {}, - } - break :blk null; - }; +inline fn getSectionHeadersSize(self: Coff) u32 { + return @intCast(u32, self.sections.slice().len * @sizeOf(coff.SectionHeader)); +} - const Mode = enum { uefi, win32 }; - const mode: Mode = mode: { - if (resolved_subsystem) |subsystem| { - const subsystem_suffix = ss: { - if (self.base.options.major_subsystem_version) |major| { - if (self.base.options.minor_subsystem_version) |minor| { - break :ss try allocPrint(arena, ",{d}.{d}", .{ major, minor }); - } else { - break :ss try allocPrint(arena, ",{d}", .{major}); - } - } - break :ss ""; - }; - - switch (subsystem) { - .Console => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .EfiApplication => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiBootServiceDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRom => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRuntimeDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .Native => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Posix => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Windows => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - } - } else if (target.os.tag == .uefi) { - break :mode .uefi; - } else { - break :mode .win32; - } - }; +inline fn getDataDirectoryHeadersOffset(self: Coff) u32 { + const msdos_hdr_size = msdos_stub.len + 4; + return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize()); +} - switch (mode) { - .uefi => try argv.appendSlice(&[_][]const u8{ - "-BASE:0", - "-ENTRY:EfiMain", - "-OPT:REF", - "-SAFESEH:NO", - "-MERGE:.rdata=.data", - "-ALIGN:32", - "-NODEFAULTLIB", - "-SECTION:.xdata,D", - }), - .win32 => { - if (link_in_crt) { - if (target.abi.isGnu()) { - try argv.append("-lldmingw"); - - if (target.cpu.arch == .i386) { - try argv.append("-ALTERNATENAME:__image_base__=___ImageBase"); - } else { - try argv.append("-ALTERNATENAME:__image_base__=__ImageBase"); - } - - if (is_dyn_lib) { - try argv.append(try comp.get_libc_crt_file(arena, "dllcrt2.obj")); - if (target.cpu.arch == .i386) { - try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12"); - } else { - try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup"); - } - } else { - try argv.append(try comp.get_libc_crt_file(arena, "crt2.obj")); - } - - try argv.append(try comp.get_libc_crt_file(arena, "mingw32.lib")); - try argv.append(try comp.get_libc_crt_file(arena, "mingwex.lib")); - try argv.append(try comp.get_libc_crt_file(arena, "msvcrt-os.lib")); - - for (mingw.always_link_libs) |name| { - if (!self.base.options.system_libs.contains(name)) { - const lib_basename = try allocPrint(arena, "{s}.lib", .{name}); - try argv.append(try comp.get_libc_crt_file(arena, lib_basename)); - } - } - } else { - const lib_str = switch (self.base.options.link_mode) { - .Dynamic => "", - .Static => "lib", - }; - const d_str = switch (self.base.options.optimize_mode) { - .Debug => "d", - else => "", - }; - switch (self.base.options.link_mode) { - .Static => try argv.append(try allocPrint(arena, "libcmt{s}.lib", .{d_str})), - .Dynamic => try argv.append(try allocPrint(arena, "msvcrt{s}.lib", .{d_str})), - } - - try argv.append(try allocPrint(arena, "{s}vcruntime{s}.lib", .{ lib_str, d_str })); - try argv.append(try allocPrint(arena, "{s}ucrt{s}.lib", .{ lib_str, d_str })); - - //Visual C++ 2015 Conformance Changes - //https://msdn.microsoft.com/en-us/library/bb531344.aspx - try argv.append("legacy_stdio_definitions.lib"); - - // msvcrt depends on kernel32 and ntdll - try argv.append("kernel32.lib"); - try argv.append("ntdll.lib"); - } - } else { - try argv.append("-NODEFAULTLIB"); - if (!is_lib) { - if (self.base.options.module) |module| { - if (module.stage1_flags.have_winmain_crt_startup) { - try argv.append("-ENTRY:WinMainCRTStartup"); - } else { - try argv.append("-ENTRY:wWinMainCRTStartup"); - } - } else { - try argv.append("-ENTRY:wWinMainCRTStartup"); - } - } - } - }, - } +inline fn getSectionHeadersOffset(self: Coff) u32 { + return self.getDataDirectoryHeadersOffset() + self.getDataDirectoryHeadersSize(); +} - // libc++ dep - if (self.base.options.link_libcpp) { - try argv.append(comp.libcxxabi_static_lib.?.full_object_path); - try argv.append(comp.libcxx_static_lib.?.full_object_path); - } +inline fn getSizeOfImage(self: Coff) u32 { + var image_size: u32 = mem.alignForwardGeneric(u32, self.getSizeOfHeaders(), self.page_size); + for (self.sections.items(.header)) |header| { + image_size += mem.alignForwardGeneric(u32, header.virtual_size, self.page_size); + } + return image_size; +} - // libunwind dep - if (self.base.options.link_libunwind) { - try argv.append(comp.libunwind_static_lib.?.full_object_path); - } +/// Returns symbol location corresponding to the set entrypoint (if any). +pub fn getEntryPoint(self: Coff) ?SymbolWithLoc { + const entry_name = self.base.options.entry orelse "_start"; // TODO this is incomplete + return self.globals.get(entry_name); +} - if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies) { - if (!self.base.options.link_libc) { - if (comp.libc_static_lib) |lib| { - try argv.append(lib.full_object_path); - } - } - // MinGW doesn't provide libssp symbols - if (target.abi.isGnu()) { - if (comp.libssp_static_lib) |lib| { - try argv.append(lib.full_object_path); - } - } - // MSVC compiler_rt is missing some stuff, so we build it unconditionally but - // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. - if (comp.compiler_rt_lib) |lib| { - try argv.append(lib.full_object_path); - } - } +/// Returns pointer-to-symbol described by `sym_with_loc` descriptor. +pub fn getSymbolPtr(self: *Coff, sym_loc: SymbolWithLoc) *coff.Symbol { + assert(sym_loc.file == null); // TODO linking object files + return &self.locals.items[sym_loc.sym_index]; +} - try argv.ensureUnusedCapacity(self.base.options.system_libs.count()); - for (self.base.options.system_libs.keys()) |key| { - const lib_basename = try allocPrint(arena, "{s}.lib", .{key}); - if (comp.crt_files.get(lib_basename)) |crt_file| { - argv.appendAssumeCapacity(crt_file.full_object_path); - continue; - } - if (try self.findLib(arena, lib_basename)) |full_path| { - argv.appendAssumeCapacity(full_path); - continue; - } - if (target.abi.isGnu()) { - const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); - if (try self.findLib(arena, fallback_name)) |full_path| { - argv.appendAssumeCapacity(full_path); - continue; - } - } - log.err("DLL import library for -l{s} not found", .{key}); - return error.DllImportLibraryNotFound; - } +/// Returns symbol described by `sym_with_loc` descriptor. +pub fn getSymbol(self: *const Coff, sym_loc: SymbolWithLoc) *const coff.Symbol { + assert(sym_loc.file == null); // TODO linking object files + return &self.locals.items[sym_loc.sym_index]; +} - if (self.base.options.verbose_link) { - // Skip over our own name so that the LLD linker name is the first argv item. - Compilation.dump_argv(argv.items[1..]); - } +/// Returns name of the symbol described by `sym_with_loc` descriptor. +pub fn getSymbolName(self: *const Coff, sym_loc: SymbolWithLoc) []const u8 { + assert(sym_loc.file == null); // TODO linking object files + const sym = self.getSymbol(sym_loc); + const offset = sym.getNameOffset() orelse return sym.getName().?; + return self.strtab.get(offset).?; +} - if (std.process.can_spawn) { - // If possible, we run LLD as a child process because it does not always - // behave properly as a library, unfortunately. - // https://github.com/ziglang/zig/issues/3825 - var child = std.ChildProcess.init(argv.items, arena); - if (comp.clang_passthrough_mode) { - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; - - const term = child.spawnAndWait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; - switch (term) { - .Exited => |code| { - if (code != 0) { - std.process.exit(code); - } - }, - else => std.process.abort(), - } - } else { - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; - - try child.spawn(); - - const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); - - const term = child.wait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; - - switch (term) { - .Exited => |code| { - if (code != 0) { - // TODO parse this output and surface with the Compilation API rather than - // directly outputting to stderr here. - std.debug.print("{s}", .{stderr}); - return error.LLDReportedFailure; - } - }, - else => { - log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); - return error.LLDCrashed; - }, - } +/// Returns atom if there is an atom referenced by the symbol described by `sym_with_loc` descriptor. +/// Returns null on failure. +pub fn getAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom { + assert(sym_loc.file == null); // TODO linking with object files + return self.atom_by_index_table.get(sym_loc.sym_index); +} - if (stderr.len != 0) { - log.warn("unexpected LLD stderr:\n{s}", .{stderr}); - } - } - } else { - const exit_code = try lldMain(arena, argv.items, false); - if (exit_code != 0) { - if (comp.clang_passthrough_mode) { - std.process.exit(exit_code); - } else { - return error.LLDReportedFailure; - } - } - } - } +/// Returns GOT atom that references `sym_with_loc` if one exists. +/// Returns null otherwise. +pub fn getGotAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom { + const got_index = self.got_entries.get(sym_loc) orelse return null; + return self.atom_by_index_table.get(got_index); +} - if (!self.base.options.disable_lld_caching) { - // Update the file with the digest. If it fails we can continue; it only - // means that the next invocation will have an unnecessary cache miss. - Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); - }; - // Again failure here only means an unnecessary cache miss. - man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); - }; - // We hang on to this lock so that the output file path can be used without - // other processes clobbering it. - self.base.lock = man.toOwnedLock(); +fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !void { + if (name.len <= 8) { + mem.copy(u8, &header.name, name); + mem.set(u8, header.name[name.len..], 0); + return; } + const offset = try self.strtab.insert(self.base.allocator, name); + const name_offset = fmt.bufPrint(&header.name, "/{d}", .{offset}) catch unreachable; + mem.set(u8, header.name[name_offset.len..], 0); } -fn findLib(self: *Coff, arena: Allocator, name: []const u8) !?[]const u8 { - for (self.base.options.lib_dirs) |lib_dir| { - const full_path = try fs.path.join(arena, &.{ lib_dir, name }); - fs.cwd().access(full_path, .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => |e| return e, - }; - return full_path; +fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void { + if (name.len <= 8) { + mem.copy(u8, &symbol.name, name); + mem.set(u8, symbol.name[name.len..], 0); + return; } - return null; + const offset = try self.strtab.insert(self.base.allocator, name); + mem.set(u8, symbol.name[0..4], 0); + mem.writeIntLittle(u32, symbol.name[4..8], offset); } -pub fn getDeclVAddr( - self: *Coff, - decl_index: Module.Decl.Index, - reloc_info: link.File.RelocInfo, -) !u64 { - _ = reloc_info; - const mod = self.base.options.module.?; - const decl = mod.declPtr(decl_index); - assert(self.llvm_object == null); - return self.text_section_virtual_address + decl.link.coff.text_offset; +fn logSymAttributes(sym: *const coff.Symbol, buf: *[4]u8) []const u8 { + mem.set(u8, buf[0..4], '_'); + switch (sym.section_number) { + .UNDEFINED => { + buf[3] = 'u'; + switch (sym.storage_class) { + .EXTERNAL => buf[1] = 'e', + .WEAK_EXTERNAL => buf[1] = 'w', + .NULL => {}, + else => unreachable, + } + }, + .ABSOLUTE => unreachable, // handle ABSOLUTE + .DEBUG => unreachable, + else => { + buf[0] = 's'; + switch (sym.storage_class) { + .EXTERNAL => buf[1] = 'e', + .WEAK_EXTERNAL => buf[1] = 'w', + .NULL => {}, + else => unreachable, + } + }, + } + return buf[0..]; } -pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !void { - _ = self; - _ = module; - _ = decl; - // TODO Implement this -} +fn logSymtab(self: *Coff) void { + var buf: [4]u8 = undefined; + + log.debug("symtab:", .{}); + log.debug(" object(null)", .{}); + for (self.locals.items) |*sym, sym_id| { + const where = if (sym.section_number == .UNDEFINED) "ord" else "sect"; + const def_index: u16 = switch (sym.section_number) { + .UNDEFINED => 0, // TODO + .ABSOLUTE => unreachable, // TODO + .DEBUG => unreachable, // TODO + else => @enumToInt(sym.section_number), + }; + log.debug(" %{d}: {?s} @{x} in {s}({d}), {s}", .{ + sym_id, + self.getSymbolName(.{ .sym_index = @intCast(u32, sym_id), .file = null }), + sym.value, + where, + def_index, + logSymAttributes(sym, &buf), + }); + } -pub fn deinit(self: *Coff) void { - if (build_options.have_llvm) { - if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator); + log.debug("globals table:", .{}); + for (self.globals.keys()) |name, id| { + const value = self.globals.values()[id]; + log.debug(" {s} => %{d} in object({?d})", .{ name, value.sym_index, value.file }); } - self.text_block_free_list.deinit(self.base.allocator); - self.offset_table.deinit(self.base.allocator); - self.offset_table_free_list.deinit(self.base.allocator); + log.debug("GOT entries:", .{}); + for (self.got_entries.keys()) |target, i| { + const got_sym = self.getSymbol(.{ .sym_index = self.got_entries.values()[i], .file = null }); + const target_sym = self.getSymbol(target); + if (target_sym.section_number == .UNDEFINED) { + log.debug(" {d}@{x} => import('{s}')", .{ + i, + got_sym.value, + self.getSymbolName(target), + }); + } else { + log.debug(" {d}@{x} => local(%{d}) in object({?d}) {s}", .{ + i, + got_sym.value, + target.sym_index, + target.file, + logSymAttributes(target_sym, &buf), + }); + } + } } diff --git a/src/link/Coff/Atom.zig b/src/link/Coff/Atom.zig new file mode 100644 index 0000000000..6c085a8f58 --- /dev/null +++ b/src/link/Coff/Atom.zig @@ -0,0 +1,110 @@ +const Atom = @This(); + +const std = @import("std"); +const coff = std.coff; + +const Allocator = std.mem.Allocator; + +const Coff = @import("../Coff.zig"); +const Reloc = Coff.Reloc; +const SymbolWithLoc = Coff.SymbolWithLoc; + +/// Each decl always gets a local symbol with the fully qualified name. +/// The vaddr and size are found here directly. +/// The file offset is found by computing the vaddr offset from the section vaddr +/// the symbol references, and adding that to the file offset of the section. +/// If this field is 0, it means the codegen size = 0 and there is no symbol or +/// offset table entry. +sym_index: u32, + +/// null means symbol defined by Zig source. +file: ?u32, + +/// Used size of the atom +size: u32, + +/// Alignment of the atom +alignment: u32, + +/// Points to the previous and next neighbors, based on the `text_offset`. +/// This can be used to find, for example, the capacity of this `Atom`. +prev: ?*Atom, +next: ?*Atom, + +pub const empty = Atom{ + .sym_index = 0, + .file = null, + .size = 0, + .alignment = 0, + .prev = null, + .next = null, +}; + +pub fn deinit(self: *Atom, gpa: Allocator) void { + _ = self; + _ = gpa; +} + +/// Returns symbol referencing this atom. +pub fn getSymbol(self: Atom, coff_file: *const Coff) *const coff.Symbol { + return coff_file.getSymbol(.{ + .sym_index = self.sym_index, + .file = self.file, + }); +} + +/// Returns pointer-to-symbol referencing this atom. +pub fn getSymbolPtr(self: Atom, coff_file: *Coff) *coff.Symbol { + return coff_file.getSymbolPtr(.{ + .sym_index = self.sym_index, + .file = self.file, + }); +} + +pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc { + return .{ .sym_index = self.sym_index, .file = self.file }; +} + +/// Returns the name of this atom. +pub fn getName(self: Atom, coff_file: *const Coff) []const u8 { + return coff_file.getSymbolName(.{ + .sym_index = self.sym_index, + .file = self.file, + }); +} + +/// Returns how much room there is to grow in virtual address space. +pub fn capacity(self: Atom, coff_file: *const Coff) u32 { + const self_sym = self.getSymbol(coff_file); + if (self.next) |next| { + const next_sym = next.getSymbol(coff_file); + return next_sym.value - self_sym.value; + } else { + // We are the last atom. + // The capacity is limited only by virtual address space. + return std.math.maxInt(u32) - self_sym.value; + } +} + +pub fn freeListEligible(self: Atom, coff_file: *const Coff) bool { + // No need to keep a free list node for the last atom. + const next = self.next orelse return false; + const self_sym = self.getSymbol(coff_file); + const next_sym = next.getSymbol(coff_file); + const cap = next_sym.value - self_sym.value; + const ideal_cap = Coff.padToIdeal(self.size); + if (cap <= ideal_cap) return false; + const surplus = cap - ideal_cap; + return surplus >= Coff.min_text_capacity; +} + +pub fn addRelocation(self: *Atom, coff_file: *Coff, reloc: Reloc) !void { + const gpa = coff_file.base.allocator; + // TODO causes a segfault on Windows + // log.debug("adding reloc of type {s} to target %{d}", .{ @tagName(reloc.@"type"), reloc.target.sym_index }); + const gop = try coff_file.relocs.getOrPut(gpa, self); + if (!gop.found_existing) { + gop.value_ptr.* = .{}; + } + try gop.value_ptr.append(gpa, reloc); +} diff --git a/src/link/Coff/Object.zig b/src/link/Coff/Object.zig new file mode 100644 index 0000000000..63bbd07463 --- /dev/null +++ b/src/link/Coff/Object.zig @@ -0,0 +1,12 @@ +const Object = @This(); + +const std = @import("std"); +const mem = std.mem; + +const Allocator = mem.Allocator; + +name: []const u8, + +pub fn deinit(self: *Object, gpa: Allocator) void { + gpa.free(self.name); +} diff --git a/src/link/Coff/lld.zig b/src/link/Coff/lld.zig new file mode 100644 index 0000000000..feae0134e9 --- /dev/null +++ b/src/link/Coff/lld.zig @@ -0,0 +1,602 @@ +const std = @import("std"); +const build_options = @import("build_options"); +const allocPrint = std.fmt.allocPrint; +const assert = std.debug.assert; +const fs = std.fs; +const log = std.log.scoped(.link); +const mem = std.mem; + +const mingw = @import("../../mingw.zig"); +const link = @import("../../link.zig"); +const lldMain = @import("../../main.zig").lldMain; +const trace = @import("../../tracy.zig").trace; + +const Allocator = mem.Allocator; + +const Cache = @import("../../Cache.zig"); +const Coff = @import("../Coff.zig"); +const Compilation = @import("../../Compilation.zig"); + +pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { + const tracy = trace(@src()); + defer tracy.end(); + + var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); + + // If there is no Zig code to compile, then we should skip flushing the output file because it + // will not be part of the linker line anyway. + const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: { + const use_stage1 = build_options.have_stage1 and self.base.options.use_stage1; + if (use_stage1) { + const obj_basename = try std.zig.binNameAlloc(arena, .{ + .root_name = self.base.options.root_name, + .target = self.base.options.target, + .output_mode = .Obj, + }); + switch (self.base.options.cache_mode) { + .incremental => break :blk try module.zig_cache_artifact_directory.join( + arena, + &[_][]const u8{obj_basename}, + ), + .whole => break :blk try fs.path.join(arena, &.{ + fs.path.dirname(full_out_path).?, obj_basename, + }), + } + } + + try self.flushModule(comp, prog_node); + + if (fs.path.dirname(full_out_path)) |dirname| { + break :blk try fs.path.join(arena, &.{ dirname, self.base.intermediary_basename.? }); + } else { + break :blk self.base.intermediary_basename.?; + } + } else null; + + var sub_prog_node = prog_node.start("LLD Link", 0); + sub_prog_node.activate(); + sub_prog_node.context.refresh(); + defer sub_prog_node.end(); + + const is_lib = self.base.options.output_mode == .Lib; + const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib; + const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe; + const link_in_crt = self.base.options.link_libc and is_exe_or_dyn_lib; + const target = self.base.options.target; + + // See link/Elf.zig for comments on how this mechanism works. + const id_symlink_basename = "lld.id"; + + var man: Cache.Manifest = undefined; + defer if (!self.base.options.disable_lld_caching) man.deinit(); + + var digest: [Cache.hex_digest_len]u8 = undefined; + + if (!self.base.options.disable_lld_caching) { + man = comp.cache_parent.obtain(); + self.base.releaseLock(); + + comptime assert(Compilation.link_hash_implementation_version == 7); + + for (self.base.options.objects) |obj| { + _ = try man.addFile(obj.path, null); + man.hash.add(obj.must_link); + } + for (comp.c_object_table.keys()) |key| { + _ = try man.addFile(key.status.success.object_path, null); + } + try man.addOptionalFile(module_obj_path); + man.hash.addOptionalBytes(self.base.options.entry); + man.hash.addOptional(self.base.options.stack_size_override); + man.hash.addOptional(self.base.options.image_base_override); + man.hash.addListOfBytes(self.base.options.lib_dirs); + man.hash.add(self.base.options.skip_linker_dependencies); + if (self.base.options.link_libc) { + man.hash.add(self.base.options.libc_installation != null); + if (self.base.options.libc_installation) |libc_installation| { + man.hash.addBytes(libc_installation.crt_dir.?); + if (target.abi == .msvc) { + man.hash.addBytes(libc_installation.msvc_lib_dir.?); + man.hash.addBytes(libc_installation.kernel32_lib_dir.?); + } + } + } + link.hashAddSystemLibs(&man.hash, self.base.options.system_libs); + man.hash.addListOfBytes(self.base.options.force_undefined_symbols.keys()); + man.hash.addOptional(self.base.options.subsystem); + man.hash.add(self.base.options.is_test); + man.hash.add(self.base.options.tsaware); + man.hash.add(self.base.options.nxcompat); + man.hash.add(self.base.options.dynamicbase); + // strip does not need to go into the linker hash because it is part of the hash namespace + man.hash.addOptional(self.base.options.major_subsystem_version); + man.hash.addOptional(self.base.options.minor_subsystem_version); + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); + // Handle this as a cache miss. + break :blk prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); + // Hot diggity dog! The output binary is already there. + self.base.lock = man.toOwnedLock(); + return; + } + log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + } + + if (self.base.options.output_mode == .Obj) { + // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (self.base.options.objects.len != 0) + break :blk self.base.options.objects[0].path; + + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.keys()[0].status.success.object_path; + + if (module_obj_path) |p| + break :blk p; + + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + // This can happen when using --enable-cache and using the stage1 backend. In this case + // we can skip the file copy. + if (!mem.eql(u8, the_object_path, full_out_path)) { + try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); + } + } else { + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(self.base.allocator); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "lld-link" }); + + try argv.append("-ERRORLIMIT:0"); + try argv.append("-NOLOGO"); + if (!self.base.options.strip) { + try argv.append("-DEBUG"); + } + if (self.base.options.lto) { + switch (self.base.options.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-OPT:lldlto=2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), + } + } + if (self.base.options.output_mode == .Exe) { + const stack_size = self.base.options.stack_size_override orelse 16777216; + try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size})); + } + if (self.base.options.image_base_override) |image_base| { + try argv.append(try std.fmt.allocPrint(arena, "-BASE:{d}", .{image_base})); + } + + if (target.cpu.arch == .i386) { + try argv.append("-MACHINE:X86"); + } else if (target.cpu.arch == .x86_64) { + try argv.append("-MACHINE:X64"); + } else if (target.cpu.arch.isARM()) { + if (target.cpu.arch.ptrBitWidth() == 32) { + try argv.append("-MACHINE:ARM"); + } else { + try argv.append("-MACHINE:ARM64"); + } + } + + for (self.base.options.force_undefined_symbols.keys()) |symbol| { + try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol})); + } + + if (is_dyn_lib) { + try argv.append("-DLL"); + } + + if (self.base.options.entry) |entry| { + try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{entry})); + } + + if (self.base.options.tsaware) { + try argv.append("-tsaware"); + } + if (self.base.options.nxcompat) { + try argv.append("-nxcompat"); + } + if (self.base.options.dynamicbase) { + try argv.append("-dynamicbase"); + } + + try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); + + if (self.base.options.implib_emit) |emit| { + const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path}); + try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); + } + + if (self.base.options.link_libc) { + if (self.base.options.libc_installation) |libc_installation| { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); + + if (target.abi == .msvc) { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?})); + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?})); + } + } + } + + for (self.base.options.lib_dirs) |lib_dir| { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir})); + } + + try argv.ensureUnusedCapacity(self.base.options.objects.len); + for (self.base.options.objects) |obj| { + if (obj.must_link) { + argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{s}", .{obj.path})); + } else { + argv.appendAssumeCapacity(obj.path); + } + } + + for (comp.c_object_table.keys()) |key| { + try argv.append(key.status.success.object_path); + } + + if (module_obj_path) |p| { + try argv.append(p); + } + + const resolved_subsystem: ?std.Target.SubSystem = blk: { + if (self.base.options.subsystem) |explicit| break :blk explicit; + switch (target.os.tag) { + .windows => { + if (self.base.options.module) |module| { + if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib) + break :blk null; + if (module.stage1_flags.have_c_main or self.base.options.is_test or + module.stage1_flags.have_winmain_crt_startup or + module.stage1_flags.have_wwinmain_crt_startup) + { + break :blk .Console; + } + if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain) + break :blk .Windows; + } + }, + .uefi => break :blk .EfiApplication, + else => {}, + } + break :blk null; + }; + + const Mode = enum { uefi, win32 }; + const mode: Mode = mode: { + if (resolved_subsystem) |subsystem| { + const subsystem_suffix = ss: { + if (self.base.options.major_subsystem_version) |major| { + if (self.base.options.minor_subsystem_version) |minor| { + break :ss try allocPrint(arena, ",{d}.{d}", .{ major, minor }); + } else { + break :ss try allocPrint(arena, ",{d}", .{major}); + } + } + break :ss ""; + }; + + switch (subsystem) { + .Console => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .EfiApplication => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiBootServiceDriver => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiRom => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiRuntimeDriver => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .Native => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .Posix => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .Windows => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + } + } else if (target.os.tag == .uefi) { + break :mode .uefi; + } else { + break :mode .win32; + } + }; + + switch (mode) { + .uefi => try argv.appendSlice(&[_][]const u8{ + "-BASE:0", + "-ENTRY:EfiMain", + "-OPT:REF", + "-SAFESEH:NO", + "-MERGE:.rdata=.data", + "-ALIGN:32", + "-NODEFAULTLIB", + "-SECTION:.xdata,D", + }), + .win32 => { + if (link_in_crt) { + if (target.abi.isGnu()) { + try argv.append("-lldmingw"); + + if (target.cpu.arch == .i386) { + try argv.append("-ALTERNATENAME:__image_base__=___ImageBase"); + } else { + try argv.append("-ALTERNATENAME:__image_base__=__ImageBase"); + } + + if (is_dyn_lib) { + try argv.append(try comp.get_libc_crt_file(arena, "dllcrt2.obj")); + if (target.cpu.arch == .i386) { + try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12"); + } else { + try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup"); + } + } else { + try argv.append(try comp.get_libc_crt_file(arena, "crt2.obj")); + } + + try argv.append(try comp.get_libc_crt_file(arena, "mingw32.lib")); + try argv.append(try comp.get_libc_crt_file(arena, "mingwex.lib")); + try argv.append(try comp.get_libc_crt_file(arena, "msvcrt-os.lib")); + + for (mingw.always_link_libs) |name| { + if (!self.base.options.system_libs.contains(name)) { + const lib_basename = try allocPrint(arena, "{s}.lib", .{name}); + try argv.append(try comp.get_libc_crt_file(arena, lib_basename)); + } + } + } else { + const lib_str = switch (self.base.options.link_mode) { + .Dynamic => "", + .Static => "lib", + }; + const d_str = switch (self.base.options.optimize_mode) { + .Debug => "d", + else => "", + }; + switch (self.base.options.link_mode) { + .Static => try argv.append(try allocPrint(arena, "libcmt{s}.lib", .{d_str})), + .Dynamic => try argv.append(try allocPrint(arena, "msvcrt{s}.lib", .{d_str})), + } + + try argv.append(try allocPrint(arena, "{s}vcruntime{s}.lib", .{ lib_str, d_str })); + try argv.append(try allocPrint(arena, "{s}ucrt{s}.lib", .{ lib_str, d_str })); + + //Visual C++ 2015 Conformance Changes + //https://msdn.microsoft.com/en-us/library/bb531344.aspx + try argv.append("legacy_stdio_definitions.lib"); + + // msvcrt depends on kernel32 and ntdll + try argv.append("kernel32.lib"); + try argv.append("ntdll.lib"); + } + } else { + try argv.append("-NODEFAULTLIB"); + if (!is_lib) { + if (self.base.options.module) |module| { + if (module.stage1_flags.have_winmain_crt_startup) { + try argv.append("-ENTRY:WinMainCRTStartup"); + } else { + try argv.append("-ENTRY:wWinMainCRTStartup"); + } + } else { + try argv.append("-ENTRY:wWinMainCRTStartup"); + } + } + } + }, + } + + // libc++ dep + if (self.base.options.link_libcpp) { + try argv.append(comp.libcxxabi_static_lib.?.full_object_path); + try argv.append(comp.libcxx_static_lib.?.full_object_path); + } + + // libunwind dep + if (self.base.options.link_libunwind) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + } + + if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies) { + if (!self.base.options.link_libc) { + if (comp.libc_static_lib) |lib| { + try argv.append(lib.full_object_path); + } + } + // MinGW doesn't provide libssp symbols + if (target.abi.isGnu()) { + if (comp.libssp_static_lib) |lib| { + try argv.append(lib.full_object_path); + } + } + // MSVC compiler_rt is missing some stuff, so we build it unconditionally but + // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. + if (comp.compiler_rt_lib) |lib| { + try argv.append(lib.full_object_path); + } + } + + try argv.ensureUnusedCapacity(self.base.options.system_libs.count()); + for (self.base.options.system_libs.keys()) |key| { + const lib_basename = try allocPrint(arena, "{s}.lib", .{key}); + if (comp.crt_files.get(lib_basename)) |crt_file| { + argv.appendAssumeCapacity(crt_file.full_object_path); + continue; + } + if (try findLib(arena, lib_basename, self.base.options.lib_dirs)) |full_path| { + argv.appendAssumeCapacity(full_path); + continue; + } + if (target.abi.isGnu()) { + const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); + if (try findLib(arena, fallback_name, self.base.options.lib_dirs)) |full_path| { + argv.appendAssumeCapacity(full_path); + continue; + } + } + log.err("DLL import library for -l{s} not found", .{key}); + return error.DllImportLibraryNotFound; + } + + if (self.base.options.verbose_link) { + // Skip over our own name so that the LLD linker name is the first argv item. + Compilation.dump_argv(argv.items[1..]); + } + + if (std.process.can_spawn) { + // If possible, we run LLD as a child process because it does not always + // behave properly as a library, unfortunately. + // https://github.com/ziglang/zig/issues/3825 + var child = std.ChildProcess.init(argv.items, arena); + if (comp.clang_passthrough_mode) { + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + + const term = child.spawnAndWait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + switch (term) { + .Exited => |code| { + if (code != 0) { + std.process.exit(code); + } + }, + else => std.process.abort(), + } + } else { + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Ignore; + child.stderr_behavior = .Pipe; + + try child.spawn(); + + const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); + + const term = child.wait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + + switch (term) { + .Exited => |code| { + if (code != 0) { + // TODO parse this output and surface with the Compilation API rather than + // directly outputting to stderr here. + std.debug.print("{s}", .{stderr}); + return error.LLDReportedFailure; + } + }, + else => { + log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); + return error.LLDCrashed; + }, + } + + if (stderr.len != 0) { + log.warn("unexpected LLD stderr:\n{s}", .{stderr}); + } + } + } else { + const exit_code = try lldMain(arena, argv.items, false); + if (exit_code != 0) { + if (comp.clang_passthrough_mode) { + std.process.exit(exit_code); + } else { + return error.LLDReportedFailure; + } + } + } + } + + if (!self.base.options.disable_lld_caching) { + // Update the file with the digest. If it fails we can continue; it only + // means that the next invocation will have an unnecessary cache miss. + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); + }; + // Again failure here only means an unnecessary cache miss. + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); + }; + // We hang on to this lock so that the output file path can be used without + // other processes clobbering it. + self.base.lock = man.toOwnedLock(); + } +} + +fn findLib(arena: Allocator, name: []const u8, lib_dirs: []const []const u8) !?[]const u8 { + for (lib_dirs) |lib_dir| { + const full_path = try fs.path.join(arena, &.{ lib_dir, name }); + fs.cwd().access(full_path, .{}) catch |err| switch (err) { + error.FileNotFound => continue, + else => |e| return e, + }; + return full_path; + } + return null; +} diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 764e4e71b2..af25441066 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -26,7 +26,7 @@ const trace = @import("../tracy.zig").trace; const Air = @import("../Air.zig"); const Allocator = mem.Allocator; const Archive = @import("MachO/Archive.zig"); -const Atom = @import("MachO/Atom.zig"); +pub const Atom = @import("MachO/Atom.zig"); const Cache = @import("../Cache.zig"); const CodeSignature = @import("MachO/CodeSignature.zig"); const Compilation = @import("../Compilation.zig"); @@ -44,7 +44,6 @@ const Type = @import("../type.zig").Type; const TypedValue = @import("../TypedValue.zig"); const Value = @import("../value.zig").Value; -pub const TextBlock = Atom; pub const DebugSymbols = @import("MachO/DebugSymbols.zig"); pub const base_tag: File.Tag = File.Tag.macho; diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index c2aa562db5..a7dc6391c2 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -18,7 +18,6 @@ const Dwarf = @import("../Dwarf.zig"); const MachO = @import("../MachO.zig"); const Module = @import("../../Module.zig"); const StringTable = @import("../strtab.zig").StringTable; -const TextBlock = MachO.TextBlock; const Type = @import("../../type.zig").Type; base: *MachO, diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index df4ac11635..51ede6f699 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -2836,24 +2836,19 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) ! try argv.append(entry); } - if (self.base.options.output_mode == .Exe) { - // Increase the default stack size to a more reasonable value of 1MB instead of - // the default of 1 Wasm page being 64KB, unless overridden by the user. - try argv.append("-z"); - const stack_size = self.base.options.stack_size_override orelse 1048576; - const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size}); - try argv.append(arg); + // Increase the default stack size to a more reasonable value of 1MB instead of + // the default of 1 Wasm page being 64KB, unless overridden by the user. + try argv.append("-z"); + const stack_size = self.base.options.stack_size_override orelse wasm.page_size * 16; + const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size}); + try argv.append(arg); + if (self.base.options.output_mode == .Exe) { if (self.base.options.wasi_exec_model == .reactor) { // Reactor execution model does not have _start so lld doesn't look for it. try argv.append("--no-entry"); } - } else { - if (self.base.options.stack_size_override) |stack_size| { - try argv.append("-z"); - const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size}); - try argv.append(arg); - } + } else if (self.base.options.entry == null) { try argv.append("--no-entry"); // So lld doesn't look for _start. } try argv.appendSlice(&[_][]const u8{ diff --git a/src/link/strtab.zig b/src/link/strtab.zig index ae9b00027e..8e314f189f 100644 --- a/src/link/strtab.zig +++ b/src/link/strtab.zig @@ -109,5 +109,9 @@ pub fn StringTable(comptime log_scope: @Type(.EnumLiteral)) type { pub fn getAssumeExists(self: Self, off: u32) []const u8 { return self.get(off) orelse unreachable; } + + pub fn len(self: Self) usize { + return self.buffer.items.len; + } }; } diff --git a/src/main.zig b/src/main.zig index dd3a7e797b..e8a16e194a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3734,9 +3734,9 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi const self_exe_path = try introspect.findZigExePath(arena); var build_file: ?[]const u8 = null; - var override_lib_dir: ?[]const u8 = null; - var override_global_cache_dir: ?[]const u8 = null; - var override_local_cache_dir: ?[]const u8 = null; + var override_lib_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LIB_DIR"); + var override_global_cache_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_GLOBAL_CACHE_DIR"); + var override_local_cache_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LOCAL_CACHE_DIR"); var child_argv = std.ArrayList([]const u8).init(arena); const argv_index_exe = child_argv.items.len; diff --git a/src/mingw.zig b/src/mingw.zig index b50cc4b009..23035fe72b 100644 --- a/src/mingw.zig +++ b/src/mingw.zig @@ -6,7 +6,6 @@ const assert = std.debug.assert; const log = std.log.scoped(.mingw); const builtin = @import("builtin"); -const target_util = @import("target.zig"); const Compilation = @import("Compilation.zig"); const build_options = @import("build_options"); const Cache = @import("Cache.zig"); @@ -404,11 +403,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { }); errdefer comp.gpa.free(lib_final_path); - const llvm = @import("codegen/llvm/bindings.zig"); - const arch_type = target_util.archToLLVM(target.cpu.arch); + const llvm_bindings = @import("codegen/llvm/bindings.zig"); + const llvm = @import("codegen/llvm.zig"); + const arch_tag = llvm.targetArch(target.cpu.arch); const def_final_path_z = try arena.dupeZ(u8, def_final_path); const lib_final_path_z = try arena.dupeZ(u8, lib_final_path); - if (llvm.WriteImportLibrary(def_final_path_z.ptr, arch_type, lib_final_path_z.ptr, true)) { + if (llvm_bindings.WriteImportLibrary(def_final_path_z.ptr, arch_tag, lib_final_path_z.ptr, true)) { // TODO surface a proper error here log.err("unable to turn {s}.def into {s}.lib", .{ lib_name, lib_name }); return error.WritingImportLibFailed; diff --git a/src/print_zir.zig b/src/print_zir.zig index f315d7f014..976d012138 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -283,7 +283,6 @@ const Writer = struct { .mul_add => try self.writeMulAdd(stream, inst), .field_parent_ptr => try self.writeFieldParentPtr(stream, inst), .builtin_call => try self.writeBuiltinCall(stream, inst), - .builtin_async_call => try self.writeBuiltinAsyncCall(stream, inst), .struct_init_anon, .struct_init_anon_ref, @@ -397,7 +396,7 @@ const Writer = struct { .field_val_named, => try self.writePlNodeFieldNamed(stream, inst), - .as_node => try self.writeAs(stream, inst), + .as_node, .as_shift_operand => try self.writeAs(stream, inst), .repeat, .repeat_inline, @@ -531,6 +530,7 @@ const Writer = struct { try stream.writeAll(") "); try self.writeSrc(stream, src); }, + .builtin_async_call => try self.writeBuiltinAsyncCall(stream, extended), } } @@ -814,9 +814,8 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } - fn writeBuiltinAsyncCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Zir.Inst.AsyncCall, inst_data.payload_index).data; + fn writeBuiltinAsyncCall(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const extra = self.code.extraData(Zir.Inst.AsyncCall, extended.operand).data; try self.writeInstRef(stream, extra.frame_buffer); try stream.writeAll(", "); try self.writeInstRef(stream, extra.result_ptr); @@ -825,7 +824,7 @@ const Writer = struct { try stream.writeAll(", "); try self.writeInstRef(stream, extra.args); try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); + try self.writeSrc(stream, LazySrcLoc.nodeOffset(extra.node)); } fn writeParam(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { diff --git a/src/target.zig b/src/target.zig index 55238a6e86..b7da04e548 100644 --- a/src/target.zig +++ b/src/target.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const llvm = @import("codegen/llvm/bindings.zig"); const Type = @import("type.zig").Type; pub const ArchOsAbi = struct { @@ -338,112 +337,6 @@ pub fn supportsReturnAddress(target: std.Target) bool { }; } -pub fn osToLLVM(os_tag: std.Target.Os.Tag) llvm.OSType { - return switch (os_tag) { - .freestanding, .other, .opencl, .glsl450, .vulkan, .plan9 => .UnknownOS, - .windows, .uefi => .Win32, - .ananas => .Ananas, - .cloudabi => .CloudABI, - .dragonfly => .DragonFly, - .freebsd => .FreeBSD, - .fuchsia => .Fuchsia, - .ios => .IOS, - .kfreebsd => .KFreeBSD, - .linux => .Linux, - .lv2 => .Lv2, - .macos => .MacOSX, - .netbsd => .NetBSD, - .openbsd => .OpenBSD, - .solaris => .Solaris, - .zos => .ZOS, - .haiku => .Haiku, - .minix => .Minix, - .rtems => .RTEMS, - .nacl => .NaCl, - .aix => .AIX, - .cuda => .CUDA, - .nvcl => .NVCL, - .amdhsa => .AMDHSA, - .ps4 => .PS4, - .ps5 => .PS5, - .elfiamcu => .ELFIAMCU, - .tvos => .TvOS, - .watchos => .WatchOS, - .mesa3d => .Mesa3D, - .contiki => .Contiki, - .amdpal => .AMDPAL, - .hermit => .HermitCore, - .hurd => .Hurd, - .wasi => .WASI, - .emscripten => .Emscripten, - .driverkit => .DriverKit, - .shadermodel => .ShaderModel, - }; -} - -pub fn archToLLVM(arch_tag: std.Target.Cpu.Arch) llvm.ArchType { - return switch (arch_tag) { - .arm => .arm, - .armeb => .armeb, - .aarch64 => .aarch64, - .aarch64_be => .aarch64_be, - .aarch64_32 => .aarch64_32, - .arc => .arc, - .avr => .avr, - .bpfel => .bpfel, - .bpfeb => .bpfeb, - .csky => .csky, - .dxil => .dxil, - .hexagon => .hexagon, - .loongarch32 => .loongarch32, - .loongarch64 => .loongarch64, - .m68k => .m68k, - .mips => .mips, - .mipsel => .mipsel, - .mips64 => .mips64, - .mips64el => .mips64el, - .msp430 => .msp430, - .powerpc => .ppc, - .powerpcle => .ppcle, - .powerpc64 => .ppc64, - .powerpc64le => .ppc64le, - .r600 => .r600, - .amdgcn => .amdgcn, - .riscv32 => .riscv32, - .riscv64 => .riscv64, - .sparc => .sparc, - .sparc64 => .sparcv9, // In LLVM, sparc64 == sparcv9. - .sparcel => .sparcel, - .s390x => .systemz, - .tce => .tce, - .tcele => .tcele, - .thumb => .thumb, - .thumbeb => .thumbeb, - .i386 => .x86, - .x86_64 => .x86_64, - .xcore => .xcore, - .nvptx => .nvptx, - .nvptx64 => .nvptx64, - .le32 => .le32, - .le64 => .le64, - .amdil => .amdil, - .amdil64 => .amdil64, - .hsail => .hsail, - .hsail64 => .hsail64, - .spir => .spir, - .spir64 => .spir64, - .kalimba => .kalimba, - .shave => .shave, - .lanai => .lanai, - .wasm32 => .wasm32, - .wasm64 => .wasm64, - .renderscript32 => .renderscript32, - .renderscript64 => .renderscript64, - .ve => .ve, - .spu_2, .spirv32, .spirv64 => .UnknownArch, - }; -} - fn eqlIgnoreCase(ignore_case: bool, a: []const u8, b: []const u8) bool { if (ignore_case) { return std.ascii.eqlIgnoreCase(a, b); @@ -800,3 +693,10 @@ pub fn supportsFunctionAlignment(target: std.Target) bool { else => true, }; } + +pub fn supportsTailCall(target: std.Target, backend: std.builtin.CompilerBackend) bool { + switch (backend) { + .stage1, .stage2_llvm => return @import("codegen/llvm.zig").supportsTailCall(target), + else => return false, + } +} diff --git a/src/type.zig b/src/type.zig index d516015d39..5c9d3f60fc 100644 --- a/src/type.zig +++ b/src/type.zig @@ -6598,6 +6598,8 @@ pub const CType = enum { .powerpcle, .powerpc64, .powerpc64le, + .wasm32, + .wasm64, => return 128, else => return 64, @@ -6646,6 +6648,8 @@ pub const CType = enum { .powerpcle, .powerpc64, .powerpc64le, + .wasm32, + .wasm64, => return 128, else => return 64, |
