From 277b01a089e059d63b6a01de011b666cc0702c38 Mon Sep 17 00:00:00 2001 From: Tadeo Kondrak Date: Sun, 14 Mar 2021 22:32:26 -0600 Subject: [PATCH 01/56] stage2 llvm bindings: rename LLVMBool to Bool --- src/codegen/llvm/bindings.zig | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index ccba3d9973..ff385efd08 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -1,7 +1,7 @@ //! We do this instead of @cImport because the self-hosted compiler is easier //! to bootstrap if it does not depend on translate-c. -const LLVMBool = bool; +const Bool = bool; pub const AttributeIndex = c_uint; /// Make sure to use the *InContext functions instead of the global ones. @@ -22,13 +22,13 @@ pub const Context = opaque { extern fn LLVMVoidTypeInContext(C: *const Context) *const Type; pub const structType = LLVMStructTypeInContext; - extern fn LLVMStructTypeInContext(C: *const Context, ElementTypes: [*]*const Type, ElementCount: c_uint, Packed: LLVMBool) *const Type; + extern fn LLVMStructTypeInContext(C: *const Context, ElementTypes: [*]*const Type, ElementCount: c_uint, Packed: Bool) *const Type; pub const constString = LLVMConstStringInContext; - extern fn LLVMConstStringInContext(C: *const Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: LLVMBool) *const Value; + extern fn LLVMConstStringInContext(C: *const Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: Bool) *const Value; pub const constStruct = LLVMConstStructInContext; - extern fn LLVMConstStructInContext(C: *const Context, ConstantVals: [*]*const Value, Count: c_uint, Packed: LLVMBool) *const Value; + extern fn LLVMConstStructInContext(C: *const Context, ConstantVals: [*]*const Value, Count: c_uint, Packed: Bool) *const Value; pub const createBasicBlock = LLVMCreateBasicBlockInContext; extern fn LLVMCreateBasicBlockInContext(C: *const Context, Name: [*:0]const u8) *const BasicBlock; @@ -59,7 +59,7 @@ pub const Value = opaque { pub const Type = opaque { pub const functionType = LLVMFunctionType; - extern fn LLVMFunctionType(ReturnType: *const Type, ParamTypes: ?[*]*const Type, ParamCount: c_uint, IsVarArg: LLVMBool) *const Type; + extern fn LLVMFunctionType(ReturnType: *const Type, ParamTypes: ?[*]*const Type, ParamCount: c_uint, IsVarArg: Bool) *const Type; pub const constNull = LLVMConstNull; extern fn LLVMConstNull(Ty: *const Type) *const Value; @@ -68,7 +68,7 @@ pub const Type = opaque { extern fn LLVMConstAllOnes(Ty: *const Type) *const Value; pub const constInt = LLVMConstInt; - extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: LLVMBool) *const Value; + extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: Bool) *const Value; pub const constArray = LLVMConstArray; extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: ?[*]*const Value, Length: c_uint) *const Value; @@ -91,7 +91,7 @@ pub const Module = opaque { extern fn LLVMDisposeModule(*const Module) void; pub const verify = LLVMVerifyModule; - extern fn LLVMVerifyModule(*const Module, Action: VerifierFailureAction, OutMessage: *[*:0]const u8) LLVMBool; + extern fn LLVMVerifyModule(*const Module, Action: VerifierFailureAction, OutMessage: *[*:0]const u8) Bool; pub const addFunction = LLVMAddFunction; extern fn LLVMAddFunction(*const Module, Name: [*:0]const u8, FunctionTy: *const Type) *const Value; @@ -191,7 +191,7 @@ pub const Builder = opaque { extern fn LLVMBuildNUWSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; pub const buildIntCast2 = LLVMBuildIntCast2; - extern fn LLVMBuildIntCast2(*const Builder, Val: *const Value, DestTy: *const Type, IsSigned: LLVMBool, Name: [*:0]const u8) *const Value; + extern fn LLVMBuildIntCast2(*const Builder, Val: *const Value, DestTy: *const Type, IsSigned: Bool, Name: [*:0]const u8) *const Value; pub const buildBitCast = LLVMBuildBitCast; extern fn LLVMBuildBitCast(*const Builder, Val: *const Value, DestTy: *const Type, Name: [*:0]const u8) *const Value; @@ -258,7 +258,7 @@ pub const TargetMachine = opaque { Filename: [*:0]const u8, codegen: CodeGenFileType, ErrorMessage: *[*:0]const u8, - ) LLVMBool; + ) Bool; }; pub const CodeMode = extern enum { @@ -295,7 +295,7 @@ pub const CodeGenFileType = extern enum { pub const Target = opaque { pub const getFromTriple = LLVMGetTargetFromTriple; - extern fn LLVMGetTargetFromTriple(Triple: [*:0]const u8, T: **const Target, ErrorMessage: *[*:0]const u8) LLVMBool; + extern fn LLVMGetTargetFromTriple(Triple: [*:0]const u8, T: **const Target, ErrorMessage: *[*:0]const u8) Bool; }; extern fn LLVMInitializeAArch64TargetInfo() void; From 5ecf8bddaeebf26ac6cd174f5f79e02ca243e501 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Tue, 16 Mar 2021 09:22:16 +0100 Subject: [PATCH 02/56] zig fmt: Respect line breaks in struct default value decls Bring this in line with how variable declarations are handled. Open a new indentation level for the initialization expression to handle nested expressions like blocks. Closes #7618 --- lib/std/zig/parser_test.zig | 25 +++++++++++++++++++++++++ lib/std/zig/render.zig | 25 +++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index a83e95e431..bc01fa3324 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -4,6 +4,31 @@ // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. +test "zig fmt: respect line breaks in struct field value declaration" { + try testCanonical( + \\const Foo = struct { + \\ bar: u32 = + \\ 42, + \\ bar: u32 = + \\ // a comment + \\ 42, + \\ bar: u32 = + \\ 42, + \\ // a comment + \\ bar: []const u8 = + \\ \\ foo + \\ \\ bar + \\ \\ baz + \\ , + \\ bar: u32 = + \\ blk: { + \\ break :blk 42; + \\ }, + \\}; + \\ + ); +} + // TODO Remove this after zig 0.9.0 is released. test "zig fmt: rewrite inline functions as callconv(.Inline)" { try testTransform( diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 9fe9b96f00..3b38091994 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -1159,8 +1159,29 @@ fn renderContainerField( try renderToken(ais, tree, rparen_token, .space); // ) } const eq_token = tree.firstToken(field.ast.value_expr) - 1; - try renderToken(ais, tree, eq_token, .space); // = - return renderExpressionComma(gpa, ais, tree, field.ast.value_expr, space); // value + const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline; + { + ais.pushIndent(); + try renderToken(ais, tree, eq_token, eq_space); // = + ais.popIndent(); + } + + if (eq_space == .space) + return renderExpressionComma(gpa, ais, tree, field.ast.value_expr, space); // value + + const token_tags = tree.tokens.items(.tag); + const maybe_comma = tree.lastToken(field.ast.value_expr) + 1; + + if (token_tags[maybe_comma] == .comma) { + ais.pushIndent(); + try renderExpression(gpa, ais, tree, field.ast.value_expr, .none); // value + ais.popIndent(); + try renderToken(ais, tree, maybe_comma, space); + } else { + ais.pushIndent(); + try renderExpression(gpa, ais, tree, field.ast.value_expr, space); // value + ais.popIndent(); + } } fn renderBuiltinCall( From 6787f163eb6db2b8b89c2ea6cb51d63606487e12 Mon Sep 17 00:00:00 2001 From: Lewis Gaul Date: Tue, 16 Mar 2021 08:26:28 +0000 Subject: [PATCH 03/56] zig fmt: don't add trailing whitespace on switch case --- lib/std/zig/parser_test.zig | 48 +++++++++++++++++++++++++++++++++++++ lib/std/zig/render.zig | 13 +++++++--- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index bc01fa3324..2a343a6edc 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -3063,6 +3063,54 @@ test "zig fmt: switch" { \\} \\ ); + + try testTransform( + \\test { + \\ switch (x) { + \\ foo => + \\ "bar", + \\ } + \\} + \\ + , + \\test { + \\ switch (x) { + \\ foo => "bar", + \\ } + \\} + \\ + ); +} + +test "zig fmt: switch multiline string" { + try testCanonical( + \\test "switch multiline string" { + \\ const x: u32 = 0; + \\ const str = switch (x) { + \\ 1 => "one", + \\ 2 => + \\ \\ Comma after the multiline string + \\ \\ is needed + \\ , + \\ 3 => "three", + \\ else => "else", + \\ }; + \\ + \\ const Union = union(enum) { + \\ Int: i64, + \\ Float: f64, + \\ }; + \\ + \\ const str = switch (u) { + \\ Union.Int => |int| + \\ \\ Comma after the multiline string + \\ \\ is needed + \\ , + \\ Union.Float => |*float| unreachable, + \\ }; + \\} + \\ + ); } test "zig fmt: while" { diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 3b38091994..640f25829a 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -1444,6 +1444,7 @@ fn renderSwitchCase( switch_case: ast.full.SwitchCase, space: Space, ) Error!void { + const node_tags = tree.nodes.items(.tag); const token_tags = tree.tokens.items(.tag); const trailing_comma = token_tags[switch_case.ast.arrow_token - 1] == .comma; @@ -1466,17 +1467,23 @@ fn renderSwitchCase( } // Render the arrow and everything after it - try renderToken(ais, tree, switch_case.ast.arrow_token, .space); + const pre_target_space = if (node_tags[switch_case.ast.target_expr] == .multiline_string_literal) + // Newline gets inserted when rendering the target expr. + Space.none + else + Space.space; + const after_arrow_space: Space = if (switch_case.payload_token == null) pre_target_space else .space; + try renderToken(ais, tree, switch_case.ast.arrow_token, after_arrow_space); if (switch_case.payload_token) |payload_token| { try renderToken(ais, tree, payload_token - 1, .none); // pipe if (token_tags[payload_token] == .asterisk) { try renderToken(ais, tree, payload_token, .none); // asterisk try renderToken(ais, tree, payload_token + 1, .none); // identifier - try renderToken(ais, tree, payload_token + 2, .space); // pipe + try renderToken(ais, tree, payload_token + 2, pre_target_space); // pipe } else { try renderToken(ais, tree, payload_token, .none); // identifier - try renderToken(ais, tree, payload_token + 1, .space); // pipe + try renderToken(ais, tree, payload_token + 1, pre_target_space); // pipe } } From d1b1e542a032b8f2323c0cdd808088310c693051 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Tue, 16 Mar 2021 18:34:02 +0100 Subject: [PATCH 04/56] crypto/pbkdf2: simplify the check for the max number of iterations --- lib/std/crypto/pbkdf2.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index ce1912091b..ae09f139a4 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -67,8 +67,8 @@ pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: // 1. If dkLen > maxInt(u32) * hLen, output "derived key too long" and // stop. // - if (comptime (maxInt(usize) > maxInt(u32) * hLen) and (dkLen > @as(usize, maxInt(u32) * hLen))) { - // If maxInt(usize) is less than `maxInt(u32) * hLen` then dkLen is always inbounds + if (dkLen / hLen >= maxInt(u32)) { + // Counter starts at 1 and is 32 bit, so if we have to return more blocks, we would overflow return error.OutputTooLong; } From f609c4ddb36c27166a13afde4e80fae000774820 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Tue, 16 Mar 2021 19:08:38 +0100 Subject: [PATCH 05/56] crypto/pbkdf2: use snake_case for variables like everywhere else --- lib/std/crypto/pbkdf2.zig | 135 +++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 69 deletions(-) diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index ae09f139a4..52f9da5cd7 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -20,20 +20,20 @@ const Error = std.crypto.Error; // pseudorandom function. See Appendix B.1 for further discussion.) // PBKDF2 is recommended for new applications. // -// PBKDF2 (P, S, c, dkLen) +// PBKDF2 (P, S, c, dk_len) // -// Options: PRF underlying pseudorandom function (hLen +// Options: PRF underlying pseudorandom function (h_len // denotes the length in octets of the // pseudorandom function output) // // Input: P password, an octet string // S salt, an octet string // c iteration count, a positive integer -// dkLen intended length in octets of the derived +// dk_len intended length in octets of the derived // key, a positive integer, at most -// (2^32 - 1) * hLen +// (2^32 - 1) * h_len // -// Output: DK derived key, a dkLen-octet string +// Output: DK derived key, a dk_len-octet string // Based on Apple's CommonKeyDerivation, based originally on code by Damien Bergamini. @@ -41,7 +41,7 @@ const Error = std.crypto.Error; /// /// PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132. /// -/// derivedKey: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length. +/// dk: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length. /// May be uninitialized. All bytes will be overwritten. /// Maximum size is `maxInt(u32) * Hash.digest_length` /// It is a programming error to pass buffer longer than the maximum size. @@ -52,43 +52,38 @@ const Error = std.crypto.Error; /// /// rounds: Iteration count. Must be greater than 0. Common values range from 1,000 to 100,000. /// Larger iteration counts improve security by increasing the time required to compute -/// the derivedKey. It is common to tune this parameter to achieve approximately 100ms. +/// the dk. It is common to tune this parameter to achieve approximately 100ms. /// /// Prf: Pseudo-random function to use. A common choice is `std.crypto.auth.hmac.HmacSha256`. -pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) Error!void { +pub fn pbkdf2(dk: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) Error!void { if (rounds < 1) return error.WeakParameters; - const dkLen = derivedKey.len; - const hLen = Prf.mac_length; - comptime std.debug.assert(hLen >= 1); + const dk_len = dk.len; + const h_len = Prf.mac_length; + comptime std.debug.assert(h_len >= 1); // FromSpec: // - // 1. If dkLen > maxInt(u32) * hLen, output "derived key too long" and + // 1. If dk_len > maxInt(u32) * h_len, output "derived key too long" and // stop. // - if (dkLen / hLen >= maxInt(u32)) { + if (dk_len / h_len >= maxInt(u32)) { // Counter starts at 1 and is 32 bit, so if we have to return more blocks, we would overflow return error.OutputTooLong; } // FromSpec: // - // 2. Let l be the number of hLen-long blocks of bytes in the derived key, + // 2. Let l be the number of h_len-long blocks of bytes in the derived key, // rounding up, and let r be the number of bytes in the last // block // - // l will not overflow, proof: - // let `L(dkLen, hLen) = (dkLen + hLen - 1) / hLen` - // then `L^-1(l, hLen) = l*hLen - hLen + 1` - // 1) L^-1(maxInt(u32), hLen) <= maxInt(u32)*hLen - // 2) maxInt(u32)*hLen - hLen + 1 <= maxInt(u32)*hLen // subtract maxInt(u32)*hLen + 1 - // 3) -hLen <= -1 // multiply by -1 - // 4) hLen >= 1 - const r_ = dkLen % hLen; - const l = @intCast(u32, (dkLen / hLen) + @as(u1, if (r_ == 0) 0 else 1)); // original: (dkLen + hLen - 1) / hLen - const r = if (r_ == 0) hLen else r_; + const blocks_count = (dk_len + h_len - 1) / h_len; + var r = dk_len % h_len; + if (r == 0) { + r = h_len; + } // FromSpec: // @@ -118,37 +113,38 @@ pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: // Here, INT (i) is a four-octet encoding of the integer i, most // significant octet first. // - // 4. Concatenate the blocks and extract the first dkLen octets to + // 4. Concatenate the blocks and extract the first dk_len octets to // produce a derived key DK: // // DK = T_1 || T_2 || ... || T_l<0..r-1> - var block: u32 = 0; // Spec limits to u32 - while (block < l) : (block += 1) { - var prevBlock: [hLen]u8 = undefined; - var newBlock: [hLen]u8 = undefined; + + var block: u32 = 0; + while (block < blocks_count) : (block += 1) { + var prev_block: [h_len]u8 = undefined; + var new_block: [h_len]u8 = undefined; // U_1 = PRF (P, S || INT (i)) - const blockIndex = mem.toBytes(mem.nativeToBig(u32, block + 1)); // Block index starts at 0001 + const block_index = mem.toBytes(mem.nativeToBig(u32, block + 1)); // Block index starts at 0001 var ctx = Prf.init(password); ctx.update(salt); - ctx.update(blockIndex[0..]); - ctx.final(prevBlock[0..]); + ctx.update(block_index[0..]); + ctx.final(prev_block[0..]); // Choose portion of DK to write into (T_n) and initialize - const offset = block * hLen; - const blockLen = if (block != l - 1) hLen else r; - const dkBlock: []u8 = derivedKey[offset..][0..blockLen]; - mem.copy(u8, dkBlock, prevBlock[0..dkBlock.len]); + const offset = block * h_len; + const block_len = if (block != blocks_count - 1) h_len else r; + const dk_block: []u8 = dk[offset..][0..block_len]; + mem.copy(u8, dk_block, prev_block[0..dk_block.len]); var i: u32 = 1; while (i < rounds) : (i += 1) { // U_c = PRF (P, U_{c-1}) - Prf.create(&newBlock, prevBlock[0..], password); - mem.copy(u8, prevBlock[0..], newBlock[0..]); + Prf.create(&new_block, prev_block[0..], password); + mem.copy(u8, prev_block[0..], new_block[0..]); // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c - for (dkBlock) |_, j| { - dkBlock[j] ^= newBlock[j]; + for (dk_block) |_, j| { + dk_block[j] ^= new_block[j]; } } } @@ -158,49 +154,50 @@ const htest = @import("test.zig"); const HmacSha1 = std.crypto.auth.hmac.HmacSha1; // RFC 6070 PBKDF2 HMAC-SHA1 Test Vectors + test "RFC 6070 one iteration" { const p = "password"; const s = "salt"; const c = 1; - const dkLen = 20; + const dk_len = 20; - var derivedKey: [dkLen]u8 = undefined; + var dk: [dk_len]u8 = undefined; - try pbkdf2(&derivedKey, p, s, c, HmacSha1); + try pbkdf2(&dk, p, s, c, HmacSha1); const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; - htest.assertEqual(expected, derivedKey[0..]); + htest.assertEqual(expected, dk[0..]); } test "RFC 6070 two iterations" { const p = "password"; const s = "salt"; const c = 2; - const dkLen = 20; + const dk_len = 20; - var derivedKey: [dkLen]u8 = undefined; + var dk: [dk_len]u8 = undefined; - try pbkdf2(&derivedKey, p, s, c, HmacSha1); + try pbkdf2(&dk, p, s, c, HmacSha1); const expected = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"; - htest.assertEqual(expected, derivedKey[0..]); + htest.assertEqual(expected, dk[0..]); } test "RFC 6070 4096 iterations" { const p = "password"; const s = "salt"; const c = 4096; - const dkLen = 20; + const dk_len = 20; - var derivedKey: [dkLen]u8 = undefined; + var dk: [dk_len]u8 = undefined; - try pbkdf2(&derivedKey, p, s, c, HmacSha1); + try pbkdf2(&dk, p, s, c, HmacSha1); const expected = "4b007901b765489abead49d926f721d065a429c1"; - htest.assertEqual(expected, derivedKey[0..]); + htest.assertEqual(expected, dk[0..]); } test "RFC 6070 16,777,216 iterations" { @@ -212,48 +209,48 @@ test "RFC 6070 16,777,216 iterations" { const p = "password"; const s = "salt"; const c = 16777216; - const dkLen = 20; + const dk_len = 20; - var derivedKey = [_]u8{0} ** dkLen; + var dk = [_]u8{0} ** dk_len; - try pbkdf2(&derivedKey, p, s, c, HmacSha1); + try pbkdf2(&dk, p, s, c, HmacSha1); const expected = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; - htest.assertEqual(expected, derivedKey[0..]); + htest.assertEqual(expected, dk[0..]); } test "RFC 6070 multi-block salt and password" { const p = "passwordPASSWORDpassword"; const s = "saltSALTsaltSALTsaltSALTsaltSALTsalt"; const c = 4096; - const dkLen = 25; + const dk_len = 25; - var derivedKey: [dkLen]u8 = undefined; + var dk: [dk_len]u8 = undefined; - try pbkdf2(&derivedKey, p, s, c, HmacSha1); + try pbkdf2(&dk, p, s, c, HmacSha1); const expected = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"; - htest.assertEqual(expected, derivedKey[0..]); + htest.assertEqual(expected, dk[0..]); } test "RFC 6070 embedded NUL" { const p = "pass\x00word"; const s = "sa\x00lt"; const c = 4096; - const dkLen = 16; + const dk_len = 16; - var derivedKey: [dkLen]u8 = undefined; + var dk: [dk_len]u8 = undefined; - try pbkdf2(&derivedKey, p, s, c, HmacSha1); + try pbkdf2(&dk, p, s, c, HmacSha1); const expected = "56fa6aa75548099dcc37d7f03425e0c3"; - htest.assertEqual(expected, derivedKey[0..]); + htest.assertEqual(expected, dk[0..]); } -test "Very large dkLen" { +test "Very large dk_len" { // This test allocates 8GB of memory and is expected to take several hours to run. if (true) { return error.SkipZigTest; @@ -261,13 +258,13 @@ test "Very large dkLen" { const p = "password"; const s = "salt"; const c = 1; - const dkLen = 1 << 33; + const dk_len = 1 << 33; - var derivedKey = try std.testing.allocator.alloc(u8, dkLen); + var dk = try std.testing.allocator.alloc(u8, dk_len); defer { - std.testing.allocator.free(derivedKey); + std.testing.allocator.free(dk); } - try pbkdf2(derivedKey, p, s, c, HmacSha1); // Just verify this doesn't crash with an overflow + try pbkdf2(dk, p, s, c, HmacSha1); } From 83d0c2ed67622ba30a56da0356f4dcd23c126273 Mon Sep 17 00:00:00 2001 From: jacob gw Date: Tue, 16 Mar 2021 17:14:00 -0400 Subject: [PATCH 06/56] std: make all errors in os.zig public --- lib/std/os.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index 362a58f7fb..8b552d05a7 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2879,7 +2879,7 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi unreachable; } -const ListenError = error{ +pub const ListenError = error{ /// Another socket is already listening on the same port. /// For Internet domain sockets, the socket referred to by sockfd had not previously /// been bound to an address and, upon attempting to bind it to an ephemeral port, it @@ -5827,7 +5827,7 @@ pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) Termio } } -const IoCtl_SIOCGIFINDEX_Error = error{ +pub const IoCtl_SIOCGIFINDEX_Error = error{ FileSystem, InterfaceNotFound, } || UnexpectedError; From 9a9441568070320d6549fe286300b45ffffd7b4d Mon Sep 17 00:00:00 2001 From: xackus <14938807+xackus@users.noreply.github.com> Date: Sat, 13 Mar 2021 17:36:38 +0100 Subject: [PATCH 07/56] translate-c: improve std.meta.cast --- lib/std/meta.zig | 69 +++++++++++++++++++++++++++++++++------------ src/translate_c.zig | 1 + 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/lib/std/meta.zig b/lib/std/meta.zig index fd3e03bdbd..cdc93e5d33 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -888,19 +888,20 @@ pub fn Vector(comptime len: u32, comptime child: type) type { /// Given a type and value, cast the value to the type as c would. /// This is for translate-c and is not intended for general use. pub fn cast(comptime DestType: type, target: anytype) DestType { - const TargetType = @TypeOf(target); + // this function should behave like transCCast in translate-c, except it's for macros + const SourceType = @TypeOf(target); switch (@typeInfo(DestType)) { - .Pointer => |dest_ptr| { - switch (@typeInfo(TargetType)) { + .Pointer => { + switch (@typeInfo(SourceType)) { .Int, .ComptimeInt => { return @intToPtr(DestType, target); }, - .Pointer => |ptr| { - return @ptrCast(DestType, @alignCast(dest_ptr.alignment, target)); + .Pointer => { + return castPtr(DestType, target); }, .Optional => |opt| { if (@typeInfo(opt.child) == .Pointer) { - return @ptrCast(DestType, @alignCast(dest_ptr.alignment, target)); + return castPtr(DestType, target); } }, else => {}, @@ -908,17 +909,16 @@ pub fn cast(comptime DestType: type, target: anytype) DestType { }, .Optional => |dest_opt| { if (@typeInfo(dest_opt.child) == .Pointer) { - const dest_ptr = @typeInfo(dest_opt.child).Pointer; - switch (@typeInfo(TargetType)) { + switch (@typeInfo(SourceType)) { .Int, .ComptimeInt => { return @intToPtr(DestType, target); }, .Pointer => { - return @ptrCast(DestType, @alignCast(dest_ptr.alignment, target)); + return castPtr(DestType, target); }, .Optional => |target_opt| { if (@typeInfo(target_opt.child) == .Pointer) { - return @ptrCast(DestType, @alignCast(dest_ptr.alignment, target)); + return castPtr(DestType, target); } }, else => {}, @@ -926,25 +926,25 @@ pub fn cast(comptime DestType: type, target: anytype) DestType { } }, .Enum => { - if (@typeInfo(TargetType) == .Int or @typeInfo(TargetType) == .ComptimeInt) { + if (@typeInfo(SourceType) == .Int or @typeInfo(SourceType) == .ComptimeInt) { return @intToEnum(DestType, target); } }, - .Int, .ComptimeInt => { - switch (@typeInfo(TargetType)) { + .Int => { + switch (@typeInfo(SourceType)) { .Pointer => { - return @intCast(DestType, @ptrToInt(target)); + return castInt(DestType, @ptrToInt(target)); }, .Optional => |opt| { if (@typeInfo(opt.child) == .Pointer) { - return @intCast(DestType, @ptrToInt(target)); + return castInt(DestType, @ptrToInt(target)); } }, .Enum => { - return @intCast(DestType, @enumToInt(target)); + return castInt(DestType, @enumToInt(target)); }, - .Int, .ComptimeInt => { - return @intCast(DestType, target); + .Int => { + return castInt(DestType, target); }, else => {}, } @@ -954,6 +954,34 @@ pub fn cast(comptime DestType: type, target: anytype) DestType { return @as(DestType, target); } +fn castInt(comptime DestType: type, target: anytype) DestType { + const dest = @typeInfo(DestType).Int; + const source = @typeInfo(@TypeOf(target)).Int; + + if (dest.bits < source.bits) + return @bitCast(DestType, @truncate(Int(source.signedness, dest.bits), target)) + else + return @bitCast(DestType, @as(Int(source.signedness, dest.bits), target)); +} + +fn castPtr(comptime DestType: type, target: anytype) DestType { + const dest = ptrInfo(DestType); + const source = ptrInfo(@TypeOf(target)); + + if (source.is_const and !dest.is_const or source.is_volatile and !dest.is_volatile) + return @intToPtr(DestType, @ptrToInt(target)) + else + return @ptrCast(DestType, @alignCast(dest.alignment, target)); +} + +fn ptrInfo(comptime PtrType: type) TypeInfo.Pointer { + return switch(@typeInfo(PtrType)){ + .Optional => |opt_info| @typeInfo(opt_info.child).Pointer, + .Pointer => |ptr_info| ptr_info, + else => unreachable, + }; +} + test "std.meta.cast" { const E = enum(u2) { Zero, @@ -977,6 +1005,11 @@ test "std.meta.cast" { testing.expectEqual(@as(u32, 4), cast(u32, @intToPtr(?*u32, 4))); testing.expectEqual(@as(u32, 10), cast(u32, @as(u64, 10))); testing.expectEqual(@as(u8, 2), cast(u8, E.Two)); + + testing.expectEqual(@bitCast(i32, @as(u32, 0x8000_0000)), cast(i32, @as(u32, 0x8000_0000))); + + testing.expectEqual(@intToPtr(*u8, 2), cast(*u8, @intToPtr(*const u8, 2))); + testing.expectEqual(@intToPtr(*u8, 2), cast(*u8, @intToPtr(*volatile u8, 2))); } /// Given a value returns its size as C's sizeof operator would. diff --git a/src/translate_c.zig b/src/translate_c.zig index c5ff56f32a..bd6c306960 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -1836,6 +1836,7 @@ fn cIntTypeForEnum(enum_qt: clang.QualType) clang.QualType { return enum_decl.getIntegerType(); } +// when modifying this function, make sure to also update std.meta.cast fn transCCast( c: *Context, scope: *Scope, From c558a1ae26eaa8c59e97e80ec01990e35ef50f3a Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 9 Mar 2021 20:49:24 -0800 Subject: [PATCH 08/56] translate-c: Implement generic selection expressions Enables translation of C code that uses the `_Generic` keyword --- src/clang.zig | 5 +++++ src/translate_c.zig | 12 ++++++++++++ src/zig_clang.cpp | 5 +++++ src/zig_clang.h | 2 ++ test/run_translated_c.zig | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+) diff --git a/src/clang.zig b/src/clang.zig index 60afa28cf1..0d18ae42b3 100644 --- a/src/clang.zig +++ b/src/clang.zig @@ -537,6 +537,11 @@ pub const FunctionType = opaque { extern fn ZigClangFunctionType_getReturnType(*const FunctionType) QualType; }; +pub const GenericSelectionExpr = opaque { + pub const getResultExpr = ZigClangGenericSelectionExpr_getResultExpr; + extern fn ZigClangGenericSelectionExpr_getResultExpr(*const GenericSelectionExpr) *const Expr; +}; + pub const IfStmt = opaque { pub const getThen = ZigClangIfStmt_getThen; extern fn ZigClangIfStmt_getThen(*const IfStmt) *const Stmt; diff --git a/src/translate_c.zig b/src/translate_c.zig index bd6c306960..d2c5cd6233 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -1058,6 +1058,10 @@ fn transStmt( const compound_literal = @ptrCast(*const clang.CompoundLiteralExpr, stmt); return transExpr(c, scope, compound_literal.getInitializer(), result_used); }, + .GenericSelectionExprClass => { + const gen_sel = @ptrCast(*const clang.GenericSelectionExpr, stmt); + return transExpr(c, scope, gen_sel.getResultExpr(), result_used); + }, else => { return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO implement translation of stmt class {s}", .{@tagName(sc)}); }, @@ -1582,6 +1586,10 @@ fn exprIsNarrowStringLiteral(expr: *const clang.Expr) bool { const op_expr = @ptrCast(*const clang.ParenExpr, expr).getSubExpr(); return exprIsNarrowStringLiteral(op_expr); }, + .GenericSelectionExprClass => { + const gen_sel = @ptrCast(*const clang.GenericSelectionExpr, expr); + return exprIsNarrowStringLiteral(gen_sel.getResultExpr()); + }, else => return false, } } @@ -2726,6 +2734,10 @@ fn cIsFunctionDeclRef(expr: *const clang.Expr) bool { const opcode = un_op.getOpcode(); return (opcode == .AddrOf or opcode == .Deref) and cIsFunctionDeclRef(un_op.getSubExpr()); }, + .GenericSelectionExprClass => { + const gen_sel = @ptrCast(*const clang.GenericSelectionExpr, expr); + return cIsFunctionDeclRef(gen_sel.getResultExpr()); + }, else => return false, } } diff --git a/src/zig_clang.cpp b/src/zig_clang.cpp index b789df0764..95e9e390a1 100644 --- a/src/zig_clang.cpp +++ b/src/zig_clang.cpp @@ -2445,6 +2445,11 @@ struct ZigClangQualType ZigClangFunctionType_getReturnType(const struct ZigClang return bitcast(casted->getReturnType()); } +const struct ZigClangExpr *ZigClangGenericSelectionExpr_getResultExpr(const struct ZigClangGenericSelectionExpr *self) { + auto casted = reinterpret_cast(self); + return reinterpret_cast(casted->getResultExpr()); +} + bool ZigClangFunctionProtoType_isVariadic(const struct ZigClangFunctionProtoType *self) { auto casted = reinterpret_cast(self); return casted->isVariadic(); diff --git a/src/zig_clang.h b/src/zig_clang.h index 34e2d5afb7..59eacf7587 100644 --- a/src/zig_clang.h +++ b/src/zig_clang.h @@ -1116,6 +1116,8 @@ ZIG_EXTERN_C bool ZigClangFunctionType_getNoReturnAttr(const struct ZigClangFunc ZIG_EXTERN_C enum ZigClangCallingConv ZigClangFunctionType_getCallConv(const struct ZigClangFunctionType *self); ZIG_EXTERN_C struct ZigClangQualType ZigClangFunctionType_getReturnType(const struct ZigClangFunctionType *self); +ZIG_EXTERN_C const struct ZigClangExpr *ZigClangGenericSelectionExpr_getResultExpr(const struct ZigClangGenericSelectionExpr *self); + ZIG_EXTERN_C bool ZigClangFunctionProtoType_isVariadic(const struct ZigClangFunctionProtoType *self); ZIG_EXTERN_C unsigned ZigClangFunctionProtoType_getNumParams(const struct ZigClangFunctionProtoType *self); ZIG_EXTERN_C struct ZigClangQualType ZigClangFunctionProtoType_getParamType(const struct ZigClangFunctionProtoType *self, unsigned i); diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index 01df88c852..bda401d08c 100644 --- a/test/run_translated_c.zig +++ b/test/run_translated_c.zig @@ -1187,4 +1187,38 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { \\ return 0; \\} , ""); + + cases.add("Generic selections", + \\#include + \\#include + \\#include + \\#define my_generic_fn(X) _Generic((X), \ + \\ int: abs, \ + \\ char *: strlen, \ + \\ size_t: malloc, \ + \\ default: free \ + \\)(X) + \\#define my_generic_val(X) _Generic((X), \ + \\ int: 1, \ + \\ const char *: "bar" \ + \\) + \\int main(void) { + \\ if (my_generic_val(100) != 1) abort(); + \\ + \\ const char *foo = "foo"; + \\ const char *bar = my_generic_val(foo); + \\ if (strcmp(bar, "bar") != 0) abort(); + \\ + \\ if (my_generic_fn(-42) != 42) abort(); + \\ if (my_generic_fn("hello") != 5) abort(); + \\ + \\ size_t size = 8192; + \\ uint8_t *mem = my_generic_fn(size); + \\ memset(mem, 42, size); + \\ if (mem[size - 1] != 42) abort(); + \\ my_generic_fn(mem); + \\ + \\ return 0; + \\} + , ""); } From e8aa6f90d6e8c69f3af32942d0ae2bdf44e34783 Mon Sep 17 00:00:00 2001 From: Tadeo Kondrak Date: Sun, 14 Mar 2021 22:43:03 -0600 Subject: [PATCH 09/56] stage2 llvm bindings: use correct type for LLVMBool for ABI compat --- src/codegen/llvm.zig | 22 +++++++++++----------- src/codegen/llvm/bindings.zig | 15 ++++++++++++++- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index f087957f1d..7233dbdd07 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -219,7 +219,7 @@ pub const LLVMIRModule = struct { var error_message: [*:0]const u8 = undefined; var target: *const llvm.Target = undefined; - if (llvm.Target.getFromTriple(llvm_target_triple.ptr, &target, &error_message)) { + if (llvm.Target.getFromTriple(llvm_target_triple.ptr, &target, &error_message).toBool()) { defer llvm.disposeMessage(error_message); const stderr = std.io.getStdErr().writer(); @@ -303,7 +303,7 @@ pub const LLVMIRModule = struct { // verifyModule always allocs the error_message even if there is no error defer llvm.disposeMessage(error_message); - if (self.llvm_module.verify(.ReturnStatus, &error_message)) { + if (self.llvm_module.verify(.ReturnStatus, &error_message).toBool()) { const stderr = std.io.getStdErr().writer(); try stderr.print("broken LLVM module found: {s}\nThis is a bug in the Zig compiler.", .{error_message}); return error.BrokenLLVMModule; @@ -319,7 +319,7 @@ pub const LLVMIRModule = struct { object_pathZ.ptr, .ObjectFile, &error_message, - )) { + ).toBool()) { defer llvm.disposeMessage(error_message); const stderr = std.io.getStdErr().writer(); @@ -614,7 +614,7 @@ pub const LLVMIRModule = struct { var indices: [2]*const llvm.Value = .{ index_type.constNull(), - index_type.constInt(1, false), + index_type.constInt(1, .False), }; return self.builder.buildLoad(self.builder.buildInBoundsGEP(operand, &indices, 2, ""), ""); @@ -676,7 +676,7 @@ pub const LLVMIRModule = struct { const signed = inst.base.ty.isSignedInt(); // TODO: Should we use intcast here or just a simple bitcast? // LLVM does truncation vs bitcast (+signed extension) in the intcast depending on the sizes - return self.builder.buildIntCast2(val, try self.getLLVMType(inst.base.ty, inst.base.src), signed, ""); + return self.builder.buildIntCast2(val, try self.getLLVMType(inst.base.ty, inst.base.src), llvm.Bool.fromBool(signed), ""); } fn genBitCast(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { @@ -782,7 +782,7 @@ pub const LLVMIRModule = struct { if (bigint.limbs.len != 1) { return self.fail(src, "TODO implement bigger bigint", .{}); } - const llvm_int = llvm_type.constInt(bigint.limbs[0], false); + const llvm_int = llvm_type.constInt(bigint.limbs[0], .False); if (!bigint.positive) { return llvm.constNeg(llvm_int); } @@ -820,7 +820,7 @@ pub const LLVMIRModule = struct { return self.fail(src, "TODO handle other sentinel values", .{}); } else false; - return self.context.constString(payload.data.ptr, @intCast(c_uint, payload.data.len), !zero_sentinel); + return self.context.constString(payload.data.ptr, @intCast(c_uint, payload.data.len), llvm.Bool.fromBool(!zero_sentinel)); } else { return self.fail(src, "TODO handle more array values", .{}); } @@ -836,13 +836,13 @@ pub const LLVMIRModule = struct { llvm_child_type.constNull(), self.context.intType(1).constNull(), }; - return self.context.constStruct(&optional_values, 2, false); + return self.context.constStruct(&optional_values, 2, .False); } else { var optional_values: [2]*const llvm.Value = .{ try self.genTypedValue(src, .{ .ty = child_type, .val = tv.val }), self.context.intType(1).constAllOnes(), }; - return self.context.constStruct(&optional_values, 2, false); + return self.context.constStruct(&optional_values, 2, .False); } } else { return self.fail(src, "TODO implement const of optional pointer", .{}); @@ -882,7 +882,7 @@ pub const LLVMIRModule = struct { try self.getLLVMType(child_type, src), self.context.intType(1), }; - return self.context.structType(&optional_types, 2, false); + return self.context.structType(&optional_types, 2, .False); } else { return self.fail(src, "TODO implement optional pointers as actual pointers", .{}); } @@ -934,7 +934,7 @@ pub const LLVMIRModule = struct { try self.getLLVMType(return_type, src), if (fn_param_len == 0) null else llvm_param.ptr, @intCast(c_uint, fn_param_len), - false, + .False, ); const llvm_fn = self.llvm_module.addFunction(func.name, fn_type); diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index ff385efd08..7217ca381e 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -1,7 +1,20 @@ //! We do this instead of @cImport because the self-hosted compiler is easier //! to bootstrap if it does not depend on translate-c. -const Bool = bool; +/// Do not compare directly to .True, use toBool() instead. +pub const Bool = enum(c_int) { + False, + True, + _, + + pub fn fromBool(b: bool) Bool { + return @intToEnum(Bool, @boolToInt(b)); + } + + pub fn toBool(b: Bool) bool { + return b != .False; + } +}; pub const AttributeIndex = c_uint; /// Make sure to use the *InContext functions instead of the global ones. From 715370a10a1d8cd2b553e6b1b8a328a637707375 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sun, 7 Mar 2021 20:47:13 -0800 Subject: [PATCH 10/56] translate-c: demote usage of un-implemented builtins --- lib/std/c/builtins.zig | 6 ++++++ src/translate_c.zig | 23 ++++++++++++++++++++++- test/run_translated_c.zig | 12 ++++++++++++ test/translate_c.zig | 7 +++++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/std/c/builtins.zig b/lib/std/c/builtins.zig index 2c03c1ceac..1d100369b9 100644 --- a/lib/std/c/builtins.zig +++ b/lib/std/c/builtins.zig @@ -188,3 +188,9 @@ pub fn __builtin_memcpy( pub fn __builtin_expect(expr: c_long, c: c_long) callconv(.Inline) c_long { return expr; } + +// __builtin_alloca_with_align is not currently implemented. +// It is used in a run-translated-c test and a test-translate-c test to ensure that non-implemented +// builtins are correctly demoted. If you implement __builtin_alloca_with_align, please update the +// run-translated-c test and the test-translate-c test to use a different non-implemented builtin. +// pub fn __builtin_alloca_with_align(size: usize, alignment: usize) callconv(.Inline) *c_void {} diff --git a/src/translate_c.zig b/src/translate_c.zig index d2c5cd6233..e63ced58e9 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -11,6 +11,7 @@ const math = std.math; const ast = @import("translate_c/ast.zig"); const Node = ast.Node; const Tag = Node.Tag; +const c_builtins = std.c.builtins; const CallingConvention = std.builtin.CallingConvention; @@ -1526,7 +1527,7 @@ fn transImplicitCastExpr( return maybeSuppressResult(c, scope, result_used, ne); }, .BuiltinFnToFnPtr => { - return transExpr(c, scope, sub_expr, result_used); + return transBuiltinFnExpr(c, scope, sub_expr, result_used); }, .ToVoid => { // Should only appear in the rhs and lhs of a ConditionalOperator @@ -1542,6 +1543,22 @@ fn transImplicitCastExpr( } } +fn isBuiltinDefined(name: []const u8) bool { + inline for (std.meta.declarations(c_builtins)) |decl| { + if (std.mem.eql(u8, name, decl.name)) return true; + } + return false; +} + +fn transBuiltinFnExpr(c: *Context, scope: *Scope, expr: *const clang.Expr, used: ResultUsed) TransError!Node { + const node = try transExpr(c, scope, expr, used); + if (node.castTag(.identifier)) |ident| { + const name = ident.data; + if (!isBuiltinDefined(name)) return fail(c, error.UnsupportedTranslation, expr.getBeginLoc(), "TODO implement function '{s}' in std.c.builtins", .{name}); + } + return node; +} + fn transBoolExpr( c: *Context, scope: *Scope, @@ -4759,6 +4776,10 @@ fn parseCPrimaryExprInner(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!N }, .Identifier => { const mangled_name = scope.getAlias(slice); + if (mem.startsWith(u8, mangled_name, "__builtin_") and !isBuiltinDefined(mangled_name)) { + try m.fail(c, "TODO implement function '{s}' in std.c.builtins", .{mangled_name}); + return error.ParseError; + } return Tag.identifier.create(c.arena, builtin_typedef_map.get(mangled_name) orelse mangled_name); }, .LParen => { diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index bda401d08c..10c3a04540 100644 --- a/test/run_translated_c.zig +++ b/test/run_translated_c.zig @@ -1221,4 +1221,16 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { \\ return 0; \\} , ""); + + // See __builtin_alloca_with_align comment in std.c.builtins + cases.add("use of unimplemented builtin in unused function does not prevent compilation", + \\#include + \\void unused() { + \\ __builtin_alloca_with_align(1, 8); + \\} + \\int main(void) { + \\ if (__builtin_sqrt(1.0) != 1.0) abort(); + \\ return 0; + \\} + , ""); } diff --git a/test/translate_c.zig b/test/translate_c.zig index 47d7c5d9eb..6aab4736a3 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -3418,4 +3418,11 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const MAY_NEED_PROMOTION_HEX = @import("std").meta.promoteIntLiteral(c_int, 0x80000000, .hexadecimal); \\pub const MAY_NEED_PROMOTION_OCT = @import("std").meta.promoteIntLiteral(c_int, 0o20000000000, .octal); }); + + // See __builtin_alloca_with_align comment in std.c.builtins + cases.add("demote un-implemented builtins", + \\#define FOO(X) __builtin_alloca_with_align((X), 8) + , &[_][]const u8{ + \\pub const FOO = @compileError("TODO implement function '__builtin_alloca_with_align' in std.c.builtins"); + }); } From f76bd56588e556ea580c1faa63667cc9264cc218 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 9 Mar 2021 07:34:21 -0800 Subject: [PATCH 11/56] translate-c: fix __builtin_object_size Previous code assumed `c_long` and `usize` were the same size. Use `isize` instead. --- lib/std/c/builtins.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/c/builtins.zig b/lib/std/c/builtins.zig index 1d100369b9..99721a150c 100644 --- a/lib/std/c/builtins.zig +++ b/lib/std/c/builtins.zig @@ -140,7 +140,7 @@ pub fn __builtin_object_size(ptr: ?*const c_void, ty: c_int) callconv(.Inline) u // If it is not possible to determine which objects ptr points to at compile time, // __builtin_object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0 // for type 2 or 3. - if (ty == 0 or ty == 1) return @bitCast(usize, -@as(c_long, 1)); + if (ty == 0 or ty == 1) return @bitCast(usize, -@as(isize, 1)); if (ty == 2 or ty == 3) return 0; unreachable; } From 6d9b3e7b19f268fe24e6f14d289fe147745c7a62 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Wed, 17 Mar 2021 10:11:47 +0100 Subject: [PATCH 12/56] pbkdf2: use std.math.divCeil() --- lib/std/crypto/pbkdf2.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index 52f9da5cd7..575fb83006 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -79,7 +79,7 @@ pub fn pbkdf2(dk: []u8, password: []const u8, salt: []const u8, rounds: u32, com // block // - const blocks_count = (dk_len + h_len - 1) / h_len; + const blocks_count = @intCast(u32, std.math.divCeil(usize, dk_len, h_len) catch unreachable); var r = dk_len % h_len; if (r == 0) { r = h_len; From a3540000909bdc6a59ba07c85d21afeb3a7e54e2 Mon Sep 17 00:00:00 2001 From: Ersikan Date: Sun, 14 Mar 2021 07:03:22 +0100 Subject: [PATCH 13/56] zig fmt: fix non-UTF-8 encoding #2820 Fixes #2820 After reading the source code, the first two bytes are inspected, and if they correspond to a UTF-16 BOM in little-endian order, the source code is converted to UTF-8. --- src/main.zig | 62 +++++++++++++++++++++++++++++++++++++++------------- test/cli.zig | 9 ++++++++ 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/main.zig b/src/main.zig index bc4f209b45..1625b7213f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2708,9 +2708,22 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { fatal("cannot use --stdin with positional arguments", .{}); } - const stdin = io.getStdIn().reader(); + const stdin = io.getStdIn(); - const source_code = try stdin.readAllAlloc(gpa, max_src_size); + const source_code = blk: { + const source_code = try stdin.readToEndAllocOptions(gpa, max_src_size, null, @alignOf(u16), null); + errdefer gpa.free(source_code); + + // If the file starts with a UTF-16 BOM, translate it to UTF-8 + if (mem.startsWith(u8, source_code, "\xff\xfe")) { + const source_code_utf16_le = mem.bytesAsSlice(u16, source_code); + const source_code_utf8 = try std.unicode.utf16leToUtf8Alloc(gpa, source_code_utf16_le); + gpa.free(source_code); + break :blk source_code_utf8; + } else { + break :blk source_code; + } + }; defer gpa.free(source_code); var tree = std.zig.parse(gpa, source_code) catch |err| { @@ -2785,6 +2798,7 @@ const FmtError = error{ EndOfStream, Unseekable, NotOpenForWriting, + UnknownTextFormat, } || fs.File.OpenError; fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void { @@ -2850,20 +2864,38 @@ fn fmtPathFile( if (stat.kind == .Directory) return error.IsDir; - const source_code = source_file.readToEndAllocOptions( - fmt.gpa, - max_src_size, - std.math.cast(usize, stat.size) catch return error.FileTooBig, - @alignOf(u8), - null, - ) catch |err| switch (err) { - error.ConnectionResetByPeer => unreachable, - error.ConnectionTimedOut => unreachable, - error.NotOpenForReading => unreachable, - else => |e| return e, + const source_code = blk: { + const source_code = source_file.readToEndAllocOptions( + fmt.gpa, + max_src_size, + std.math.cast(usize, stat.size) catch return error.FileTooBig, + @alignOf(u16), + null, + ) catch |err| switch (err) { + error.ConnectionResetByPeer => unreachable, + error.ConnectionTimedOut => unreachable, + error.NotOpenForReading => unreachable, + else => |e| return e, + }; + source_file.close(); + file_closed = true; + errdefer fmt.gpa.free(source_code); + + // If the file starts with a UTF-16 BOM, translate it to UTF-8 + if (mem.eql(u8, source_code[0..2], "\xff\xfe")) { + const source_code_utf16_le = mem.bytesAsSlice(u16, source_code); + const source_code_utf8 = std.unicode.utf16leToUtf8Alloc(fmt.gpa, source_code_utf16_le) catch |err| return switch (err) { + error.DanglingSurrogateHalf => FmtError.UnknownTextFormat, + error.ExpectedSecondSurrogateHalf => FmtError.UnknownTextFormat, + error.UnexpectedSecondSurrogateHalf => FmtError.UnknownTextFormat, + else => |e| e, + }; + fmt.gpa.free(source_code); + break :blk source_code_utf8; + } else { + break :blk source_code; + } }; - source_file.close(); - file_closed = true; defer fmt.gpa.free(source_code); // Add to set after no longer possible to get error.IsDir. diff --git a/test/cli.zig b/test/cli.zig index c0702fa54c..db58511d14 100644 --- a/test/cli.zig +++ b/test/cli.zig @@ -174,4 +174,13 @@ fn testZigFmt(zig_exe: []const u8, dir_path: []const u8) !void { const run_result3 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path }); // both files have been formatted, nothing should change now testing.expect(run_result3.stdout.len == 0); + + // Check UTF-16 decoding + const fmt4_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt4.zig" }); + var unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00"; + try fs.cwd().writeFile(fmt4_zig_path, unformatted_code_utf16); + + const run_result4 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path }); + testing.expect(std.mem.startsWith(u8, run_result4.stdout, fmt4_zig_path)); + testing.expect(run_result4.stdout.len == fmt4_zig_path.len + 1 and run_result4.stdout[run_result4.stdout.len - 1] == '\n'); } From 8942243f7a825e42c16c8d210f5f9dc3baa76b2f Mon Sep 17 00:00:00 2001 From: Ersikan Date: Sun, 14 Mar 2021 18:07:09 +0100 Subject: [PATCH 14/56] zig fmt: factorize source file reading and decoding Now reading a source file and decoding it from UTF-16LE to UTF-8 is done in a single function. Error messages are improved, and an error is emitted when the source file has a BOM not supported (UTF-16BE, UTF-32). Please note that the BOM of UTF-32 is composed of the same bytes as the BOM of UTF-16 followed by a null character. Therefore a source file in UTF-16LE starting with a null byte will be interpreted as an UTF-32, and rejeted because of an invalid format. In pratice this is not a problem, as the code would have been rejected later anyway because of the null character. --- src/main.zig | 102 +++++++++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 47 deletions(-) diff --git a/src/main.zig b/src/main.zig index 1625b7213f..be227a2895 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2637,6 +2637,50 @@ fn argvCmd(allocator: *Allocator, argv: []const []const u8) ![]u8 { return cmd.toOwnedSlice(); } +fn readSourceFileToEndAlloc(allocator: *mem.Allocator, input: *const fs.File, size_hint: ?usize) ![]const u8 { + const source_code = input.readToEndAllocOptions( + allocator, + max_src_size, + size_hint, + @alignOf(u16), + null, + ) catch |err| switch (err) { + error.ConnectionResetByPeer => unreachable, + error.ConnectionTimedOut => unreachable, + error.NotOpenForReading => unreachable, + else => |e| return e, + }; + errdefer allocator.free(source_code); + + // Detect unsupported file types with their Byte Order Mark + const unsupported_boms = [_][]const u8{ + "\xff\xfe\x00\x00", // UTF-32 little endian + "\xfe\xff\x00\x00", // UTF-32 big endian + "\xfe\xff", // UTF-16 big endian + }; + for (unsupported_boms) |bom| { + if (mem.startsWith(u8, source_code, bom)) { + return error.UnsupportedEncoding; + } + } + + // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8 + if (mem.startsWith(u8, source_code, "\xff\xfe")) { + const source_code_utf16_le = mem.bytesAsSlice(u16, source_code); + const source_code_utf8 = std.unicode.utf16leToUtf8Alloc(allocator, source_code_utf16_le) catch |err| switch (err) { + error.DanglingSurrogateHalf => error.UnsupportedEncoding, + error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding, + error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding, + else => |e| return e, + }; + + allocator.free(source_code); + return source_code_utf8; + } + + return source_code; +} + pub const usage_fmt = \\Usage: zig fmt [file]... \\ @@ -2709,20 +2753,8 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { } const stdin = io.getStdIn(); - - const source_code = blk: { - const source_code = try stdin.readToEndAllocOptions(gpa, max_src_size, null, @alignOf(u16), null); - errdefer gpa.free(source_code); - - // If the file starts with a UTF-16 BOM, translate it to UTF-8 - if (mem.startsWith(u8, source_code, "\xff\xfe")) { - const source_code_utf16_le = mem.bytesAsSlice(u16, source_code); - const source_code_utf8 = try std.unicode.utf16leToUtf8Alloc(gpa, source_code_utf16_le); - gpa.free(source_code); - break :blk source_code_utf8; - } else { - break :blk source_code; - } + const source_code = readSourceFileToEndAlloc(gpa, &stdin, null) catch |err| { + fatal("unable to read stdin: {s}", .{err}); }; defer gpa.free(source_code); @@ -2798,7 +2830,7 @@ const FmtError = error{ EndOfStream, Unseekable, NotOpenForWriting, - UnknownTextFormat, + UnsupportedEncoding, } || fs.File.OpenError; fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void { @@ -2864,40 +2896,16 @@ fn fmtPathFile( if (stat.kind == .Directory) return error.IsDir; - const source_code = blk: { - const source_code = source_file.readToEndAllocOptions( - fmt.gpa, - max_src_size, - std.math.cast(usize, stat.size) catch return error.FileTooBig, - @alignOf(u16), - null, - ) catch |err| switch (err) { - error.ConnectionResetByPeer => unreachable, - error.ConnectionTimedOut => unreachable, - error.NotOpenForReading => unreachable, - else => |e| return e, - }; - source_file.close(); - file_closed = true; - errdefer fmt.gpa.free(source_code); - - // If the file starts with a UTF-16 BOM, translate it to UTF-8 - if (mem.eql(u8, source_code[0..2], "\xff\xfe")) { - const source_code_utf16_le = mem.bytesAsSlice(u16, source_code); - const source_code_utf8 = std.unicode.utf16leToUtf8Alloc(fmt.gpa, source_code_utf16_le) catch |err| return switch (err) { - error.DanglingSurrogateHalf => FmtError.UnknownTextFormat, - error.ExpectedSecondSurrogateHalf => FmtError.UnknownTextFormat, - error.UnexpectedSecondSurrogateHalf => FmtError.UnknownTextFormat, - else => |e| e, - }; - fmt.gpa.free(source_code); - break :blk source_code_utf8; - } else { - break :blk source_code; - } - }; + const source_code = try readSourceFileToEndAlloc( + fmt.gpa, + &source_file, + std.math.cast(usize, stat.size) catch return error.FileTooBig, + ); defer fmt.gpa.free(source_code); + source_file.close(); + file_closed = true; + // Add to set after no longer possible to get error.IsDir. if (try fmt.seen.fetchPut(stat.inode, {})) |_| return; From 36db4b7cc48e9e2b1bed5a977f36ef3a0f158ee8 Mon Sep 17 00:00:00 2001 From: Ersikan Date: Sun, 14 Mar 2021 18:12:42 +0100 Subject: [PATCH 15/56] test-cli: Remove temporary directory after tests --- test/cli.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/cli.zig b/test/cli.zig index db58511d14..dedea67a59 100644 --- a/test/cli.zig +++ b/test/cli.zig @@ -28,6 +28,8 @@ pub fn main() !void { const zig_exe = try fs.path.resolve(a, &[_][]const u8{zig_exe_rel}); const dir_path = try fs.path.join(a, &[_][]const u8{ cache_root, "clitest" }); + defer fs.cwd().deleteTree(dir_path) catch {}; + const TestFn = fn ([]const u8, []const u8) anyerror!void; const test_fns = [_]TestFn{ testZigInitLib, From 3019676440c5ea30be8a9a185fb1346622f7cbdb Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 16 Mar 2021 22:25:50 +0100 Subject: [PATCH 16/56] macho: apply some renames to bring closer to zld --- src/codegen.zig | 7 +- src/link/MachO.zig | 156 ++++++++++++++++---------------- src/link/MachO/DebugSymbols.zig | 8 +- 3 files changed, 86 insertions(+), 85 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index a508885576..04bb7741b7 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2152,8 +2152,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const decl = func_payload.data; const decl_name = try std.fmt.allocPrint(self.bin_file.allocator, "_{s}", .{decl.name}); defer self.bin_file.allocator.free(decl_name); - const already_defined = macho_file.extern_lazy_symbols.contains(decl_name); - const symbol: u32 = if (macho_file.extern_lazy_symbols.getIndex(decl_name)) |index| + const already_defined = macho_file.lazy_imports.contains(decl_name); + const symbol: u32 = if (macho_file.lazy_imports.getIndex(decl_name)) |index| @intCast(u32, index) else try macho_file.addExternSymbol(decl_name); @@ -3111,7 +3111,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { 4, 8 => { const offset = if (math.cast(i9, adj_off)) |imm| Instruction.LoadStoreOffset.imm_post_index(-imm) - else |_| Instruction.LoadStoreOffset.reg(try self.copyToTmpRegister(src, Type.initTag(.u64), MCValue{ .immediate = adj_off })); + else |_| + Instruction.LoadStoreOffset.reg(try self.copyToTmpRegister(src, Type.initTag(.u64), MCValue{ .immediate = adj_off })); const rn: Register = switch (arch) { .aarch64, .aarch64_be => .x29, .aarch64_32 => .w29, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 52d434da2f..1240ae8481 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -104,16 +104,16 @@ entry_addr: ?u64 = null, /// Table of all local symbols /// Internally references string table for names (which are optional). -local_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, +locals: std.ArrayListUnmanaged(macho.nlist_64) = .{}, /// Table of all global symbols -global_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, +globals: std.ArrayListUnmanaged(macho.nlist_64) = .{}, /// Table of all extern nonlazy symbols, indexed by name. -extern_nonlazy_symbols: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{}, +nonlazy_imports: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{}, /// Table of all extern lazy symbols, indexed by name. -extern_lazy_symbols: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{}, +lazy_imports: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{}, -local_symbol_free_list: std.ArrayListUnmanaged(u32) = .{}, -global_symbol_free_list: std.ArrayListUnmanaged(u32) = .{}, +locals_free_list: std.ArrayListUnmanaged(u32) = .{}, +globals_free_list: std.ArrayListUnmanaged(u32) = .{}, offset_table_free_list: std.ArrayListUnmanaged(u32) = .{}, stub_helper_stubs_start_off: ?u64 = null, @@ -260,9 +260,9 @@ pub const TextBlock = struct { /// File offset relocation happens transparently, so it is not included in /// this calculation. fn capacity(self: TextBlock, macho_file: MachO) u64 { - const self_sym = macho_file.local_symbols.items[self.local_sym_index]; + const self_sym = macho_file.locals.items[self.local_sym_index]; if (self.next) |next| { - const next_sym = macho_file.local_symbols.items[next.local_sym_index]; + const next_sym = macho_file.locals.items[next.local_sym_index]; return next_sym.n_value - self_sym.n_value; } else { // We are the last block. @@ -274,8 +274,8 @@ pub const TextBlock = struct { fn freeListEligible(self: TextBlock, macho_file: MachO) bool { // No need to keep a free list node for the last block. const next = self.next orelse return false; - const self_sym = macho_file.local_symbols.items[self.local_sym_index]; - const next_sym = macho_file.local_symbols.items[next.local_sym_index]; + const self_sym = macho_file.locals.items[self.local_sym_index]; + const next_sym = macho_file.locals.items[next.local_sym_index]; const cap = next_sym.n_value - self_sym.n_value; const ideal_cap = padToIdeal(self.size); if (cap <= ideal_cap) return false; @@ -344,7 +344,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio }; // Index 0 is always a null symbol. - try self.local_symbols.append(allocator, .{ + try self.locals.append(allocator, .{ .n_strx = 0, .n_type = 0, .n_sect = 0, @@ -834,7 +834,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { } }, else => { - log.err("{s} terminated", .{ argv.items[0] }); + log.err("{s} terminated", .{argv.items[0]}); return error.LLDCrashed; }, } @@ -1019,14 +1019,14 @@ pub fn deinit(self: *MachO) void { if (self.d_sym) |*ds| { ds.deinit(self.base.allocator); } - for (self.extern_lazy_symbols.items()) |*entry| { + for (self.lazy_imports.items()) |*entry| { self.base.allocator.free(entry.key); } - self.extern_lazy_symbols.deinit(self.base.allocator); - for (self.extern_nonlazy_symbols.items()) |*entry| { + self.lazy_imports.deinit(self.base.allocator); + for (self.nonlazy_imports.items()) |*entry| { self.base.allocator.free(entry.key); } - self.extern_nonlazy_symbols.deinit(self.base.allocator); + self.nonlazy_imports.deinit(self.base.allocator); self.pie_fixups.deinit(self.base.allocator); self.stub_fixups.deinit(self.base.allocator); self.text_block_free_list.deinit(self.base.allocator); @@ -1040,10 +1040,10 @@ pub fn deinit(self: *MachO) void { } self.string_table_directory.deinit(self.base.allocator); self.string_table.deinit(self.base.allocator); - self.global_symbols.deinit(self.base.allocator); - self.global_symbol_free_list.deinit(self.base.allocator); - self.local_symbols.deinit(self.base.allocator); - self.local_symbol_free_list.deinit(self.base.allocator); + self.globals.deinit(self.base.allocator); + self.globals_free_list.deinit(self.base.allocator); + self.locals.deinit(self.base.allocator); + self.locals_free_list.deinit(self.base.allocator); for (self.load_commands.items) |*lc| { lc.deinit(self.base.allocator); } @@ -1098,7 +1098,7 @@ fn shrinkTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64) vo } fn growTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { - const sym = self.local_symbols.items[text_block.local_sym_index]; + const sym = self.locals.items[text_block.local_sym_index]; const align_ok = mem.alignBackwardGeneric(u64, sym.n_value, alignment) == sym.n_value; const need_realloc = !align_ok or new_block_size > text_block.capacity(self.*); if (!need_realloc) return sym.n_value; @@ -1108,16 +1108,16 @@ fn growTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alig pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void { if (decl.link.macho.local_sym_index != 0) return; - try self.local_symbols.ensureCapacity(self.base.allocator, self.local_symbols.items.len + 1); + try self.locals.ensureCapacity(self.base.allocator, self.locals.items.len + 1); try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); - if (self.local_symbol_free_list.popOrNull()) |i| { + if (self.locals_free_list.popOrNull()) |i| { log.debug("reusing symbol index {d} for {s}", .{ i, decl.name }); decl.link.macho.local_sym_index = i; } else { - log.debug("allocating symbol index {d} for {s}", .{ self.local_symbols.items.len, decl.name }); - decl.link.macho.local_sym_index = @intCast(u32, self.local_symbols.items.len); - _ = self.local_symbols.addOneAssumeCapacity(); + log.debug("allocating symbol index {d} for {s}", .{ self.locals.items.len, decl.name }); + decl.link.macho.local_sym_index = @intCast(u32, self.locals.items.len); + _ = self.locals.addOneAssumeCapacity(); } if (self.offset_table_free_list.popOrNull()) |i| { @@ -1128,7 +1128,7 @@ pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void { self.offset_table_count_dirty = true; } - self.local_symbols.items[decl.link.macho.local_sym_index] = .{ + self.locals.items[decl.link.macho.local_sym_index] = .{ .n_strx = 0, .n_type = 0, .n_sect = 0, @@ -1189,7 +1189,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); assert(decl.link.macho.local_sym_index != 0); // Caller forgot to call allocateDeclIndexes() - const symbol = &self.local_symbols.items[decl.link.macho.local_sym_index]; + const symbol = &self.locals.items[decl.link.macho.local_sym_index]; if (decl.link.macho.size != 0) { const capacity = decl.link.macho.capacity(self.*); @@ -1285,7 +1285,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { try self.writeStubInStubHelper(fixup.symbol); try self.writeLazySymbolPointer(fixup.symbol); - const extern_sym = &self.extern_lazy_symbols.items()[fixup.symbol].value; + const extern_sym = &self.lazy_imports.items()[fixup.symbol].value; extern_sym.segment = self.data_segment_cmd_index.?; extern_sym.offset = fixup.symbol * @sizeOf(u64); self.rebase_info_dirty = true; @@ -1329,9 +1329,9 @@ pub fn updateDeclExports( const tracy = trace(@src()); defer tracy.end(); - try self.global_symbols.ensureCapacity(self.base.allocator, self.global_symbols.items.len + exports.len); + try self.globals.ensureCapacity(self.base.allocator, self.globals.items.len + exports.len); if (decl.link.macho.local_sym_index == 0) return; - const decl_sym = &self.local_symbols.items[decl.link.macho.local_sym_index]; + const decl_sym = &self.locals.items[decl.link.macho.local_sym_index]; for (exports) |exp| { if (exp.options.section) |section_name| { @@ -1364,7 +1364,7 @@ pub fn updateDeclExports( }; const n_type = decl_sym.n_type | macho.N_EXT; if (exp.link.macho.sym_index) |i| { - const sym = &self.global_symbols.items[i]; + const sym = &self.globals.items[i]; sym.* = .{ .n_strx = try self.updateString(sym.n_strx, exp.options.name), .n_type = n_type, @@ -1374,12 +1374,12 @@ pub fn updateDeclExports( }; } else { const name_str_index = try self.makeString(exp.options.name); - const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: { - _ = self.global_symbols.addOneAssumeCapacity(); + const i = if (self.globals_free_list.popOrNull()) |i| i else blk: { + _ = self.globals.addOneAssumeCapacity(); self.export_info_dirty = true; - break :blk self.global_symbols.items.len - 1; + break :blk self.globals.items.len - 1; }; - self.global_symbols.items[i] = .{ + self.globals.items[i] = .{ .n_strx = name_str_index, .n_type = n_type, .n_sect = @intCast(u8, self.text_section_index.?) + 1, @@ -1394,18 +1394,18 @@ pub fn updateDeclExports( pub fn deleteExport(self: *MachO, exp: Export) void { const sym_index = exp.sym_index orelse return; - self.global_symbol_free_list.append(self.base.allocator, sym_index) catch {}; - self.global_symbols.items[sym_index].n_type = 0; + self.globals_free_list.append(self.base.allocator, sym_index) catch {}; + self.globals.items[sym_index].n_type = 0; } pub fn freeDecl(self: *MachO, decl: *Module.Decl) void { // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. self.freeTextBlock(&decl.link.macho); if (decl.link.macho.local_sym_index != 0) { - self.local_symbol_free_list.append(self.base.allocator, decl.link.macho.local_sym_index) catch {}; + self.locals_free_list.append(self.base.allocator, decl.link.macho.local_sym_index) catch {}; self.offset_table_free_list.append(self.base.allocator, decl.link.macho.offset_table_index) catch {}; - self.local_symbols.items[decl.link.macho.local_sym_index].n_type = 0; + self.locals.items[decl.link.macho.local_sym_index].n_type = 0; decl.link.macho.local_sym_index = 0; } @@ -1413,7 +1413,7 @@ pub fn freeDecl(self: *MachO, decl: *Module.Decl) void { pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { assert(decl.link.macho.local_sym_index != 0); - return self.local_symbols.items[decl.link.macho.local_sym_index].n_value; + return self.locals.items[decl.link.macho.local_sym_index].n_value; } pub fn populateMissingMetadata(self: *MachO) !void { @@ -2060,11 +2060,11 @@ pub fn populateMissingMetadata(self: *MachO) !void { self.header_dirty = true; self.load_commands_dirty = true; } - if (!self.extern_nonlazy_symbols.contains("dyld_stub_binder")) { - const index = @intCast(u32, self.extern_nonlazy_symbols.items().len); + if (!self.nonlazy_imports.contains("dyld_stub_binder")) { + const index = @intCast(u32, self.nonlazy_imports.items().len); const name = try self.base.allocator.dupe(u8, "dyld_stub_binder"); const offset = try self.makeString("dyld_stub_binder"); - try self.extern_nonlazy_symbols.putNoClobber(self.base.allocator, name, .{ + try self.nonlazy_imports.putNoClobber(self.base.allocator, name, .{ .inner = .{ .n_strx = offset, .n_type = std.macho.N_UNDF | std.macho.N_EXT, @@ -2159,7 +2159,7 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, const big_block = self.text_block_free_list.items[i]; // We now have a pointer to a live text block that has too much capacity. // Is it enough that we could fit this new text block? - const sym = self.local_symbols.items[big_block.local_sym_index]; + const sym = self.locals.items[big_block.local_sym_index]; const capacity = big_block.capacity(self.*); const ideal_capacity = padToIdeal(capacity); const ideal_capacity_end_vaddr = sym.n_value + ideal_capacity; @@ -2190,7 +2190,7 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, } break :blk new_start_vaddr; } else if (self.last_text_block) |last| { - const last_symbol = self.local_symbols.items[last.local_sym_index]; + const last_symbol = self.locals.items[last.local_sym_index]; // TODO We should pad out the excess capacity with NOPs. For executables, // no padding seems to be OK, but it will probably not be for objects. const ideal_capacity = padToIdeal(last.size); @@ -2288,11 +2288,11 @@ fn updateString(self: *MachO, old_str_off: u32, new_name: []const u8) !u32 { } pub fn addExternSymbol(self: *MachO, name: []const u8) !u32 { - const index = @intCast(u32, self.extern_lazy_symbols.items().len); + const index = @intCast(u32, self.lazy_imports.items().len); const offset = try self.makeString(name); const sym_name = try self.base.allocator.dupe(u8, name); const dylib_ordinal = 1; // TODO this is now hardcoded, since we only support libSystem. - try self.extern_lazy_symbols.putNoClobber(self.base.allocator, sym_name, .{ + try self.lazy_imports.putNoClobber(self.base.allocator, sym_name, .{ .inner = .{ .n_strx = offset, .n_type = macho.N_UNDF | macho.N_EXT, @@ -2591,9 +2591,9 @@ fn writeStubInStubHelper(self: *MachO, index: u32) !void { fn relocateSymbolTable(self: *MachO) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const nlocals = self.local_symbols.items.len; - const nglobals = self.global_symbols.items.len; - const nundefs = self.extern_lazy_symbols.items().len + self.extern_nonlazy_symbols.items().len; + const nlocals = self.locals.items.len; + const nglobals = self.globals.items.len; + const nundefs = self.lazy_imports.items().len + self.nonlazy_imports.items().len; const nsyms = nlocals + nglobals + nundefs; if (symtab.nsyms < nsyms) { @@ -2628,7 +2628,7 @@ fn writeLocalSymbol(self: *MachO, index: usize) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; log.debug("writing local symbol {} at 0x{x}", .{ index, off }); - try self.base.file.?.pwriteAll(mem.asBytes(&self.local_symbols.items[index]), off); + try self.base.file.?.pwriteAll(mem.asBytes(&self.locals.items[index]), off); } fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { @@ -2637,17 +2637,17 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { try self.relocateSymbolTable(); const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const nlocals = self.local_symbols.items.len; - const nglobals = self.global_symbols.items.len; + const nlocals = self.locals.items.len; + const nglobals = self.globals.items.len; - const nundefs = self.extern_lazy_symbols.items().len + self.extern_nonlazy_symbols.items().len; + const nundefs = self.lazy_imports.items().len + self.nonlazy_imports.items().len; var undefs = std.ArrayList(macho.nlist_64).init(self.base.allocator); defer undefs.deinit(); try undefs.ensureCapacity(nundefs); - for (self.extern_lazy_symbols.items()) |entry| { + for (self.lazy_imports.items()) |entry| { undefs.appendAssumeCapacity(entry.value.inner); } - for (self.extern_nonlazy_symbols.items()) |entry| { + for (self.nonlazy_imports.items()) |entry| { undefs.appendAssumeCapacity(entry.value.inner); } @@ -2657,7 +2657,7 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { const globals_off = locals_off + locals_size; const globals_size = nglobals * @sizeOf(macho.nlist_64); log.debug("writing global symbols from 0x{x} to 0x{x}", .{ globals_off, globals_size + globals_off }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.global_symbols.items), globals_off); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.globals.items), globals_off); const undefs_off = globals_off + globals_size; const undefs_size = nundefs * @sizeOf(macho.nlist_64); @@ -2688,8 +2688,8 @@ fn writeIndirectSymbolTable(self: *MachO) !void { const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; - const lazy = self.extern_lazy_symbols.items(); - const nonlazy = self.extern_nonlazy_symbols.items(); + const lazy = self.lazy_imports.items(); + const nonlazy = self.nonlazy_imports.items(); const allocated_size = self.allocatedSizeLinkedit(dysymtab.indirectsymoff); const nindirectsyms = @intCast(u32, lazy.len * 2 + nonlazy.len); const needed_size = @intCast(u32, nindirectsyms * @sizeOf(u32)); @@ -2710,20 +2710,20 @@ fn writeIndirectSymbolTable(self: *MachO) !void { var writer = stream.writer(); stubs.reserved1 = 0; - for (self.extern_lazy_symbols.items()) |_, i| { + for (self.lazy_imports.items()) |_, i| { const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); try writer.writeIntLittle(u32, symtab_idx); } const base_id = @intCast(u32, lazy.len); got.reserved1 = base_id; - for (self.extern_nonlazy_symbols.items()) |_, i| { + for (self.nonlazy_imports.items()) |_, i| { const symtab_idx = @intCast(u32, dysymtab.iundefsym + i + base_id); try writer.writeIntLittle(u32, symtab_idx); } la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, nonlazy.len); - for (self.extern_lazy_symbols.items()) |_, i| { + for (self.lazy_imports.items()) |_, i| { const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); try writer.writeIntLittle(u32, symtab_idx); } @@ -2789,7 +2789,7 @@ fn writeCodeSignature(self: *MachO) !void { fn writeExportTrie(self: *MachO) !void { if (!self.export_info_dirty) return; - if (self.global_symbols.items.len == 0) return; + if (self.globals.items.len == 0) return; const tracy = trace(@src()); defer tracy.end(); @@ -2798,7 +2798,7 @@ fn writeExportTrie(self: *MachO) !void { defer trie.deinit(); const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; - for (self.global_symbols.items) |symbol| { + for (self.globals.items) |symbol| { // TODO figure out if we should put all global symbols into the export trie const name = self.getString(symbol.n_strx); assert(symbol.n_value >= text_segment.inner.vmaddr); @@ -2840,12 +2840,12 @@ fn writeRebaseInfoTable(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const size = try rebaseInfoSize(self.extern_lazy_symbols.items()); + const size = try rebaseInfoSize(self.lazy_imports.items()); var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); - try writeRebaseInfo(self.extern_lazy_symbols.items(), stream.writer()); + try writeRebaseInfo(self.lazy_imports.items(), stream.writer()); const linkedit_segment = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; @@ -2872,12 +2872,12 @@ fn writeBindingInfoTable(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const size = try bindInfoSize(self.extern_nonlazy_symbols.items()); + const size = try bindInfoSize(self.nonlazy_imports.items()); var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); - try writeBindInfo(self.extern_nonlazy_symbols.items(), stream.writer()); + try writeBindInfo(self.nonlazy_imports.items(), stream.writer()); const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; @@ -2901,12 +2901,12 @@ fn writeBindingInfoTable(self: *MachO) !void { fn writeLazyBindingInfoTable(self: *MachO) !void { if (!self.lazy_binding_info_dirty) return; - const size = try lazyBindInfoSize(self.extern_lazy_symbols.items()); + const size = try lazyBindInfoSize(self.lazy_imports.items()); var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); - try writeLazyBindInfo(self.extern_lazy_symbols.items(), stream.writer()); + try writeLazyBindInfo(self.lazy_imports.items(), stream.writer()); const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; @@ -2929,7 +2929,7 @@ fn writeLazyBindingInfoTable(self: *MachO) !void { } fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void { - if (self.extern_lazy_symbols.items().len == 0) return; + if (self.lazy_imports.items().len == 0) return; var stream = std.io.fixedBufferStream(buffer); var reader = stream.reader(); @@ -2975,7 +2975,7 @@ fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void { else => {}, } } - assert(self.extern_lazy_symbols.items().len <= offsets.items.len); + assert(self.lazy_imports.items().len <= offsets.items.len); const stub_size: u4 = switch (self.base.options.target.cpu.arch) { .x86_64 => 10, @@ -2988,7 +2988,7 @@ fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void { else => unreachable, }; var buf: [@sizeOf(u32)]u8 = undefined; - for (self.extern_lazy_symbols.items()) |_, i| { + for (self.lazy_imports.items()) |_, i| { const placeholder_off = self.stub_helper_stubs_start_off.? + i * stub_size + off; mem.writeIntLittle(u32, &buf, offsets.items[i]); try self.base.file.?.pwriteAll(&buf, placeholder_off); @@ -3193,12 +3193,12 @@ fn parseSymbolTable(self: *MachO) !void { const nread = try self.base.file.?.preadAll(@ptrCast([*]u8, buffer)[0 .. symtab.nsyms * @sizeOf(macho.nlist_64)], symtab.symoff); assert(@divExact(nread, @sizeOf(macho.nlist_64)) == buffer.len); - try self.local_symbols.ensureCapacity(self.base.allocator, dysymtab.nlocalsym); - try self.global_symbols.ensureCapacity(self.base.allocator, dysymtab.nextdefsym); + try self.locals.ensureCapacity(self.base.allocator, dysymtab.nlocalsym); + try self.globals.ensureCapacity(self.base.allocator, dysymtab.nextdefsym); try self.undef_symbols.ensureCapacity(self.base.allocator, dysymtab.nundefsym); - self.local_symbols.appendSliceAssumeCapacity(buffer[dysymtab.ilocalsym .. dysymtab.ilocalsym + dysymtab.nlocalsym]); - self.global_symbols.appendSliceAssumeCapacity(buffer[dysymtab.iextdefsym .. dysymtab.iextdefsym + dysymtab.nextdefsym]); + self.locals.appendSliceAssumeCapacity(buffer[dysymtab.ilocalsym .. dysymtab.ilocalsym + dysymtab.nlocalsym]); + self.globals.appendSliceAssumeCapacity(buffer[dysymtab.iextdefsym .. dysymtab.iextdefsym + dysymtab.nextdefsym]); self.undef_symbols.appendSliceAssumeCapacity(buffer[dysymtab.iundefsym .. dysymtab.iundefsym + dysymtab.nundefsym]); } diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 042c1a12cf..a81fd00c0a 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -839,8 +839,8 @@ fn findFreeSpaceLinkedit(self: *DebugSymbols, object_size: u64, min_alignment: u fn relocateSymbolTable(self: *DebugSymbols) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const nlocals = self.base.local_symbols.items.len; - const nglobals = self.base.global_symbols.items.len; + const nlocals = self.base.locals.items.len; + const nglobals = self.base.globals.items.len; const nsyms = nlocals + nglobals; if (symtab.nsyms < nsyms) { @@ -875,7 +875,7 @@ pub fn writeLocalSymbol(self: *DebugSymbols, index: usize) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; log.debug("writing dSym local symbol {} at 0x{x}", .{ index, off }); - try self.file.pwriteAll(mem.asBytes(&self.base.local_symbols.items[index]), off); + try self.file.pwriteAll(mem.asBytes(&self.base.locals.items[index]), off); } fn writeStringTable(self: *DebugSymbols) !void { @@ -1057,7 +1057,7 @@ pub fn commitDeclDebugInfo( var dbg_info_buffer = &debug_buffers.dbg_info_buffer; var dbg_info_type_relocs = &debug_buffers.dbg_info_type_relocs; - const symbol = self.base.local_symbols.items[decl.link.macho.local_sym_index]; + const symbol = self.base.locals.items[decl.link.macho.local_sym_index]; const text_block = &decl.link.macho; // If the Decl is a function, we need to update the __debug_line program. const typed_value = decl.typed_value.most_recent.typed_value; From 643b4898f592c7193402dcf9a7ca465edb2d430a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 16 Mar 2021 23:00:10 +0100 Subject: [PATCH 17/56] macho: handle all jumps in stubs on aarch64 --- src/link/MachO.zig | 55 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 1240ae8481..a69ce31b85 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1597,7 +1597,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { }; const stub_size: u4 = switch (self.base.options.target.cpu.arch) { .x86_64 => 6, - .aarch64 => 2 * @sizeOf(u32), + .aarch64 => 3 * @sizeOf(u32), else => unreachable, // unhandled architecture type }; const flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; @@ -2525,9 +2525,12 @@ fn writeStub(self: *MachO, index: u32) !void { const stub_off = stubs.offset + index * stubs.reserved2; const stub_addr = stubs.addr + index * stubs.reserved2; const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64); + log.debug("writing stub at 0x{x}", .{stub_off}); + var code = try self.base.allocator.alloc(u8, stubs.reserved2); defer self.base.allocator.free(code); + switch (self.base.options.target.cpu.arch) { .x86_64 => { assert(la_ptr_addr >= stub_addr + stubs.reserved2); @@ -2539,12 +2542,50 @@ fn writeStub(self: *MachO, index: u32) !void { }, .aarch64 => { assert(la_ptr_addr >= stub_addr); - const displacement = try math.divExact(u64, la_ptr_addr - stub_addr, 4); - const literal = try math.cast(u19, displacement); - mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.x16, .{ - .literal = literal, - }).toU32()); - mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.br(.x16).toU32()); + outer: { + const this_addr = stub_addr; + const target_addr = la_ptr_addr; + inner: { + const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :inner; + const literal = math.cast(u18, displacement) catch |_| break :inner; + // ldr x16, literal + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); + // nop + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); + break :outer; + } + inner: { + const new_this_addr = this_addr + @sizeOf(u32); + const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :inner; + const literal = math.cast(u18, displacement) catch |_| break :inner; + // nop + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); + // ldr x16, literal + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); + break :outer; + } + // Use adrp followed by ldr(register). + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @intCast(i21, target_page - this_page); + // adrp x16, pages + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x16, pages).toU32()); + const narrowed = @truncate(u12, target_addr); + const offset = try math.divExact(u12, narrowed, 8); + // ldr x16, x16, offset + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{ + .register = .{ + .rn = .x16, + .offset = aarch64.Instruction.LoadStoreOffset.imm(offset), + }, + }).toU32()); + } + // br x16 + mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.br(.x16).toU32()); }, else => unreachable, } From 3df2ae1f9da3655462d668ec16088f43a4a11ce4 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 16 Mar 2021 23:07:23 +0100 Subject: [PATCH 18/56] macho: clean up writing of stub helper section --- src/link/MachO.zig | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index a69ce31b85..80afdc6408 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -2602,8 +2602,10 @@ fn writeStubInStubHelper(self: *MachO, index: u32) !void { else => unreachable, }; const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size; + var code = try self.base.allocator.alloc(u8, stub_size); defer self.base.allocator.free(code); + switch (self.base.options.target.cpu.arch) { .x86_64 => { const displacement = try math.cast( @@ -2618,12 +2620,19 @@ fn writeStubInStubHelper(self: *MachO, index: u32) !void { mem.writeIntLittle(u32, code[6..][0..4], @bitCast(u32, displacement)); }, .aarch64 => { - const displacement = try math.cast(i28, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - 4); + const literal = blk: { + const div_res = try math.divExact(u64, stub_size - @sizeOf(u32), 4); + break :blk try math.cast(u18, div_res); + }; + // ldr w16, literal mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.w16, .{ - .literal = @divExact(stub_size - @sizeOf(u32), 4), + .literal = literal, }).toU32()); + const displacement = try math.cast(i28, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - 4); + // b disp mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.b(displacement).toU32()); - mem.writeIntLittle(u32, code[8..12], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. + // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. + mem.writeIntLittle(u32, code[8..12], 0x0); }, else => unreachable, } From 1181543dadc48e296f0de88673a46ba37841ae24 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 16 Mar 2021 23:37:31 +0100 Subject: [PATCH 19/56] macho: extract writing stub helper preamble into fn --- src/link/MachO.zig | 184 +++++++++++++++++++++++++++++++-------------- 1 file changed, 128 insertions(+), 56 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 80afdc6408..e41454fe4e 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -2079,62 +2079,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { self.binding_info_dirty = true; } if (self.stub_helper_stubs_start_off == null) { - const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?]; - const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; - const data = &data_segment.sections.items[self.data_section_index.?]; - const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; - const got = &data_const_segment.sections.items[self.data_got_section_index.?]; - switch (self.base.options.target.cpu.arch) { - .x86_64 => { - const code_size = 15; - var code: [code_size]u8 = undefined; - // lea %r11, [rip + disp] - code[0] = 0x4c; - code[1] = 0x8d; - code[2] = 0x1d; - { - const displacement = try math.cast(u32, data.addr - stub_helper.addr - 7); - mem.writeIntLittle(u32, code[3..7], displacement); - } - // push %r11 - code[7] = 0x41; - code[8] = 0x53; - // jmp [rip + disp] - code[9] = 0xff; - code[10] = 0x25; - { - const displacement = try math.cast(u32, got.addr - stub_helper.addr - code_size); - mem.writeIntLittle(u32, code[11..], displacement); - } - self.stub_helper_stubs_start_off = stub_helper.offset + code_size; - try self.base.file.?.pwriteAll(&code, stub_helper.offset); - }, - .aarch64 => { - var code: [4 * @sizeOf(u32)]u8 = undefined; - { - const displacement = try math.cast(i21, data.addr - stub_helper.addr); - mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, displacement).toU32()); - } - mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.stp( - .x16, - .x17, - aarch64.Register.sp, - aarch64.Instruction.LoadStorePairOffset.pre_index(-16), - ).toU32()); - { - const displacement = try math.divExact(u64, got.addr - stub_helper.addr - 2 * @sizeOf(u32), 4); - const literal = try math.cast(u19, displacement); - mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.ldr(.x16, .{ - .literal = literal, - }).toU32()); - } - mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.br(.x16).toU32()); - self.stub_helper_stubs_start_off = stub_helper.offset + 4 * @sizeOf(u32); - try self.base.file.?.pwriteAll(&code, stub_helper.offset); - }, - else => unreachable, - } + try self.writeStubHelperPreamble(); } } @@ -2516,6 +2461,133 @@ fn writeLazySymbolPointer(self: *MachO, index: u32) !void { try self.base.file.?.pwriteAll(&buf, off); } +fn writeStubHelperPreamble(self: *MachO) !void { + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?]; + const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = &data_const_segment.sections.items[self.data_got_section_index.?]; + const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const data = &data_segment.sections.items[self.data_section_index.?]; + + switch (self.base.options.target.cpu.arch) { + .x86_64 => { + const code_size = 15; + var code: [code_size]u8 = undefined; + // lea %r11, [rip + disp] + code[0] = 0x4c; + code[1] = 0x8d; + code[2] = 0x1d; + { + const target_addr = data.addr; + const displacement = try math.cast(u32, target_addr - stub_helper.addr - 7); + mem.writeIntLittle(u32, code[3..7], displacement); + } + // push %r11 + code[7] = 0x41; + code[8] = 0x53; + // jmp [rip + disp] + code[9] = 0xff; + code[10] = 0x25; + { + const displacement = try math.cast(u32, got.addr - stub_helper.addr - code_size); + mem.writeIntLittle(u32, code[11..], displacement); + } + try self.base.file.?.pwriteAll(&code, stub_helper.offset); + self.stub_helper_stubs_start_off = stub_helper.offset + code_size; + }, + .aarch64 => { + var code: [6 * @sizeOf(u32)]u8 = undefined; + + data_blk_outer: { + const this_addr = stub_helper.addr; + const target_addr = data.addr; + data_blk: { + const displacement = math.cast(i21, target_addr - this_addr) catch |_| break :data_blk; + // adr x17, disp + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, displacement).toU32()); + // nop + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); + break :data_blk_outer; + } + data_blk: { + const new_this_addr = this_addr + @sizeOf(u32); + const displacement = math.cast(i21, target_addr - new_this_addr) catch |_| break :data_blk; + // nop + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); + // adr x17, disp + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.adr(.x17, displacement).toU32()); + break :data_blk_outer; + } + // Jump is too big, replace adr with adrp and add. + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @intCast(i21, target_page - this_page); + // adrp x17, pages + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x17, pages).toU32()); + const narrowed = @truncate(u12, target_addr); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.add(.x17, .x17, narrowed, false).toU32()); + } + + // stp x16, x17, [sp, #-16]! + mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.stp( + .x16, + .x17, + aarch64.Register.sp, + aarch64.Instruction.LoadStorePairOffset.pre_index(-16), + ).toU32()); + + binder_blk_outer: { + const this_addr = stub_helper.addr + 3 * @sizeOf(u32); + const target_addr = got.addr; + binder_blk: { + const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :binder_blk; + const literal = math.cast(u18, displacement) catch |_| break :binder_blk; + // ldr x16, label + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); + // nop + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.nop().toU32()); + break :binder_blk_outer; + } + binder_blk: { + const new_this_addr = this_addr + @sizeOf(u32); + const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :binder_blk; + const literal = math.cast(u18, displacement) catch |_| break :binder_blk; + // nop + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.nop().toU32()); + // ldr x16, label + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); + break :binder_blk_outer; + } + // Jump is too big, replace ldr with adrp and ldr(register). + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @intCast(i21, target_page - this_page); + // adrp x16, pages + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.adrp(.x16, pages).toU32()); + const narrowed = @truncate(u12, target_addr); + const offset = try math.divExact(u12, narrowed, 8); + // ldr x16, x16, offset + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{ + .register = .{ + .rn = .x16, + .offset = aarch64.Instruction.LoadStoreOffset.imm(offset), + }, + }).toU32()); + } + + // br x16 + mem.writeIntLittle(u32, code[20..24], aarch64.Instruction.br(.x16).toU32()); + try self.base.file.?.pwriteAll(&code, stub_helper.offset); + self.stub_helper_stubs_start_off = stub_helper.offset + code.len; + }, + else => unreachable, + } +} + fn writeStub(self: *MachO, index: u32) !void { const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const stubs = text_segment.sections.items[self.stubs_section_index.?]; From b9fa80e5880ed04f0cfd29f753297cbfebf77f2d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 16 Mar 2021 23:55:52 +0100 Subject: [PATCH 20/56] macho: use latest bind functionality --- src/link/MachO.zig | 116 ++++++++++++++++++++++------ src/link/MachO/bind.zig | 145 +++++++++++++++++++++++++++++++++++ src/link/MachO/imports.zig | 152 ------------------------------------- 3 files changed, 237 insertions(+), 176 deletions(-) create mode 100644 src/link/MachO/bind.zig delete mode 100644 src/link/MachO/imports.zig diff --git a/src/link/MachO.zig b/src/link/MachO.zig index e41454fe4e..a16bdc7284 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -12,6 +12,7 @@ const aarch64 = @import("../codegen/aarch64.zig"); const math = std.math; const mem = std.mem; +const bind = @import("MachO/bind.zig"); const trace = @import("../tracy.zig").trace; const build_options = @import("build_options"); const Module = @import("../Module.zig"); @@ -26,7 +27,6 @@ const Trie = @import("MachO/Trie.zig"); const CodeSignature = @import("MachO/CodeSignature.zig"); usingnamespace @import("MachO/commands.zig"); -usingnamespace @import("MachO/imports.zig"); pub const base_tag: File.Tag = File.Tag.macho; @@ -108,9 +108,9 @@ locals: std.ArrayListUnmanaged(macho.nlist_64) = .{}, /// Table of all global symbols globals: std.ArrayListUnmanaged(macho.nlist_64) = .{}, /// Table of all extern nonlazy symbols, indexed by name. -nonlazy_imports: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{}, +nonlazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, /// Table of all extern lazy symbols, indexed by name. -lazy_imports: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{}, +lazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, locals_free_list: std.ArrayListUnmanaged(u32) = .{}, globals_free_list: std.ArrayListUnmanaged(u32) = .{}, @@ -169,6 +169,17 @@ pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, /// rather than sitting in the global scope. stub_fixups: std.ArrayListUnmanaged(StubFixup) = .{}, +pub const Import = struct { + /// MachO symbol table entry. + symbol: macho.nlist_64, + + /// Id of the dynamic library where the specified entries can be found. + dylib_ordinal: i64, + + /// Index of this import within the import list. + index: u32, +}; + pub const PieFixup = struct { /// Target address we wanted to address in absolute terms. address: u64, @@ -1285,9 +1296,6 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { try self.writeStubInStubHelper(fixup.symbol); try self.writeLazySymbolPointer(fixup.symbol); - const extern_sym = &self.lazy_imports.items()[fixup.symbol].value; - extern_sym.segment = self.data_segment_cmd_index.?; - extern_sym.offset = fixup.symbol * @sizeOf(u64); self.rebase_info_dirty = true; self.lazy_binding_info_dirty = true; } @@ -2065,7 +2073,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { const name = try self.base.allocator.dupe(u8, "dyld_stub_binder"); const offset = try self.makeString("dyld_stub_binder"); try self.nonlazy_imports.putNoClobber(self.base.allocator, name, .{ - .inner = .{ + .symbol = .{ .n_strx = offset, .n_type = std.macho.N_UNDF | std.macho.N_EXT, .n_sect = 0, @@ -2073,8 +2081,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .n_value = 0, }, .dylib_ordinal = 1, // TODO this is currently hardcoded. - .segment = self.data_const_segment_cmd_index.?, - .offset = index * @sizeOf(u64), + .index = index, }); self.binding_info_dirty = true; } @@ -2238,7 +2245,7 @@ pub fn addExternSymbol(self: *MachO, name: []const u8) !u32 { const sym_name = try self.base.allocator.dupe(u8, name); const dylib_ordinal = 1; // TODO this is now hardcoded, since we only support libSystem. try self.lazy_imports.putNoClobber(self.base.allocator, sym_name, .{ - .inner = .{ + .symbol = .{ .n_strx = offset, .n_type = macho.N_UNDF | macho.N_EXT, .n_sect = 0, @@ -2246,6 +2253,7 @@ pub fn addExternSymbol(self: *MachO, name: []const u8) !u32 { .n_value = 0, }, .dylib_ordinal = dylib_ordinal, + .index = index, }); log.debug("adding new extern symbol '{s}' with dylib ordinal '{}'", .{ name, dylib_ordinal }); return index; @@ -2767,10 +2775,10 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { defer undefs.deinit(); try undefs.ensureCapacity(nundefs); for (self.lazy_imports.items()) |entry| { - undefs.appendAssumeCapacity(entry.value.inner); + undefs.appendAssumeCapacity(entry.value.symbol); } for (self.nonlazy_imports.items()) |entry| { - undefs.appendAssumeCapacity(entry.value.inner); + undefs.appendAssumeCapacity(entry.value.symbol); } const locals_off = symtab.symoff; @@ -2832,20 +2840,20 @@ fn writeIndirectSymbolTable(self: *MachO) !void { var writer = stream.writer(); stubs.reserved1 = 0; - for (self.lazy_imports.items()) |_, i| { + for (lazy) |_, i| { const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); try writer.writeIntLittle(u32, symtab_idx); } const base_id = @intCast(u32, lazy.len); got.reserved1 = base_id; - for (self.nonlazy_imports.items()) |_, i| { + for (nonlazy) |_, i| { const symtab_idx = @intCast(u32, dysymtab.iundefsym + i + base_id); try writer.writeIntLittle(u32, symtab_idx); } la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, nonlazy.len); - for (self.lazy_imports.items()) |_, i| { + for (lazy) |_, i| { const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); try writer.writeIntLittle(u32, symtab_idx); } @@ -2962,14 +2970,33 @@ fn writeRebaseInfoTable(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const size = try rebaseInfoSize(self.lazy_imports.items()); + var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); + defer pointers.deinit(); + + if (self.la_symbol_ptr_section_index) |idx| { + try pointers.ensureCapacity(pointers.items.len + self.lazy_imports.items().len); + const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_segment_cmd_index.?); + + for (self.lazy_imports.items()) |entry| { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + entry.value.index * @sizeOf(u64), + .segment_id = segment_id, + }); + } + } + + std.sort.sort(bind.Pointer, pointers.items, {}, bind.pointerCmp); + + const size = try bind.rebaseInfoSize(pointers.items); var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); - try writeRebaseInfo(self.lazy_imports.items(), stream.writer()); + try bind.writeRebaseInfo(pointers.items, stream.writer()); - const linkedit_segment = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; const allocated_size = self.allocatedSizeLinkedit(dyld_info.rebase_off); const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)); @@ -2994,14 +3021,33 @@ fn writeBindingInfoTable(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const size = try bindInfoSize(self.nonlazy_imports.items()); + var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); + defer pointers.deinit(); + + if (self.data_got_section_index) |idx| { + try pointers.ensureCapacity(pointers.items.len + self.nonlazy_imports.items().len); + const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); + + for (self.nonlazy_imports.items()) |entry| { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + entry.value.index * @sizeOf(u64), + .segment_id = segment_id, + .dylib_ordinal = entry.value.dylib_ordinal, + .name = entry.key, + }); + } + } + + const size = try bind.bindInfoSize(pointers.items); var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); - try writeBindInfo(self.nonlazy_imports.items(), stream.writer()); + try bind.writeBindInfo(pointers.items, stream.writer()); - const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; const allocated_size = self.allocatedSizeLinkedit(dyld_info.bind_off); const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)); @@ -3023,14 +3069,36 @@ fn writeBindingInfoTable(self: *MachO) !void { fn writeLazyBindingInfoTable(self: *MachO) !void { if (!self.lazy_binding_info_dirty) return; - const size = try lazyBindInfoSize(self.lazy_imports.items()); + const tracy = trace(@src()); + defer tracy.end(); + + var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); + defer pointers.deinit(); + + if (self.la_symbol_ptr_section_index) |idx| { + try pointers.ensureCapacity(self.lazy_imports.items().len); + const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_segment_cmd_index.?); + + for (self.lazy_imports.items()) |entry| { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + entry.value.index * @sizeOf(u64), + .segment_id = segment_id, + .dylib_ordinal = entry.value.dylib_ordinal, + .name = entry.key, + }); + } + } + + const size = try bind.lazyBindInfoSize(pointers.items); var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); - try writeLazyBindInfo(self.lazy_imports.items(), stream.writer()); + try bind.writeLazyBindInfo(pointers.items, stream.writer()); - const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; const allocated_size = self.allocatedSizeLinkedit(dyld_info.lazy_bind_off); const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)); diff --git a/src/link/MachO/bind.zig b/src/link/MachO/bind.zig new file mode 100644 index 0000000000..d234fa8242 --- /dev/null +++ b/src/link/MachO/bind.zig @@ -0,0 +1,145 @@ +const std = @import("std"); +const leb = std.leb; +const macho = std.macho; + +pub const Pointer = struct { + offset: u64, + segment_id: u16, + dylib_ordinal: ?i64 = null, + name: ?[]const u8 = null, +}; + +pub fn pointerCmp(context: void, a: Pointer, b: Pointer) bool { + if (a.segment_id < b.segment_id) return true; + if (a.segment_id == b.segment_id) { + return a.offset < b.offset; + } + return false; +} + +pub fn rebaseInfoSize(pointers: []const Pointer) !u64 { + var stream = std.io.countingWriter(std.io.null_writer); + var writer = stream.writer(); + var size: u64 = 0; + + for (pointers) |pointer| { + size += 2; + try leb.writeILEB128(writer, pointer.offset); + size += 1; + } + + size += 1 + stream.bytes_written; + return size; +} + +pub fn writeRebaseInfo(pointers: []const Pointer, writer: anytype) !void { + for (pointers) |pointer| { + try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.REBASE_TYPE_POINTER)); + try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, pointer.segment_id)); + + try leb.writeILEB128(writer, pointer.offset); + try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, 1)); + } + try writer.writeByte(macho.REBASE_OPCODE_DONE); +} + +pub fn bindInfoSize(pointers: []const Pointer) !u64 { + var stream = std.io.countingWriter(std.io.null_writer); + var writer = stream.writer(); + var size: u64 = 0; + + for (pointers) |pointer| { + size += 1; + if (pointer.dylib_ordinal.? > 15) { + try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?)); + } + size += 1; + + size += 1; + size += pointer.name.?.len; + size += 1; + + size += 1; + + try leb.writeILEB128(writer, pointer.offset); + size += 1; + } + + size += stream.bytes_written + 1; + return size; +} + +pub fn writeBindInfo(pointers: []const Pointer, writer: anytype) !void { + for (pointers) |pointer| { + if (pointer.dylib_ordinal.? > 15) { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); + try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?)); + } else if (pointer.dylib_ordinal.? > 0) { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?))); + } else { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?))); + } + try writer.writeByte(macho.BIND_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.BIND_TYPE_POINTER)); + + try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. + try writer.writeAll(pointer.name.?); + try writer.writeByte(0); + + try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, pointer.segment_id)); + + try leb.writeILEB128(writer, pointer.offset); + try writer.writeByte(macho.BIND_OPCODE_DO_BIND); + } + + try writer.writeByte(macho.BIND_OPCODE_DONE); +} + +pub fn lazyBindInfoSize(pointers: []const Pointer) !u64 { + var stream = std.io.countingWriter(std.io.null_writer); + var writer = stream.writer(); + var size: u64 = 0; + + for (pointers) |pointer| { + size += 1; + + try leb.writeILEB128(writer, pointer.offset); + + size += 1; + if (pointer.dylib_ordinal.? > 15) { + try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?)); + } + + size += 1; + size += pointer.name.?.len; + size += 1; + + size += 2; + } + + size += stream.bytes_written; + return size; +} + +pub fn writeLazyBindInfo(pointers: []const Pointer, writer: anytype) !void { + for (pointers) |pointer| { + try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, pointer.segment_id)); + + try leb.writeILEB128(writer, pointer.offset); + + if (pointer.dylib_ordinal.? > 15) { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); + try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?)); + } else if (pointer.dylib_ordinal.? > 0) { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?))); + } else { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?))); + } + + try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. + try writer.writeAll(pointer.name.?); + try writer.writeByte(0); + + try writer.writeByte(macho.BIND_OPCODE_DO_BIND); + try writer.writeByte(macho.BIND_OPCODE_DONE); + } +} diff --git a/src/link/MachO/imports.zig b/src/link/MachO/imports.zig deleted file mode 100644 index 2bc34d14c5..0000000000 --- a/src/link/MachO/imports.zig +++ /dev/null @@ -1,152 +0,0 @@ -const std = @import("std"); -const leb = std.leb; -const macho = std.macho; -const mem = std.mem; - -const assert = std.debug.assert; -const Allocator = mem.Allocator; - -pub const ExternSymbol = struct { - /// MachO symbol table entry. - inner: macho.nlist_64, - - /// Id of the dynamic library where the specified entries can be found. - /// Id of 0 means self. - /// TODO this should really be an id into the table of all defined - /// dylibs. - dylib_ordinal: i64 = 0, - - /// Id of the segment where this symbol is defined (will have its address - /// resolved). - segment: u16 = 0, - - /// Offset relative to the start address of the `segment`. - offset: u32 = 0, -}; - -pub fn rebaseInfoSize(symbols: anytype) !u64 { - var stream = std.io.countingWriter(std.io.null_writer); - var writer = stream.writer(); - var size: u64 = 0; - - for (symbols) |entry| { - size += 2; - try leb.writeILEB128(writer, entry.value.offset); - size += 1; - } - - size += 1 + stream.bytes_written; - return size; -} - -pub fn writeRebaseInfo(symbols: anytype, writer: anytype) !void { - for (symbols) |entry| { - const symbol = entry.value; - try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.REBASE_TYPE_POINTER)); - try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); - try leb.writeILEB128(writer, symbol.offset); - try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, 1)); - } - try writer.writeByte(macho.REBASE_OPCODE_DONE); -} - -pub fn bindInfoSize(symbols: anytype) !u64 { - var stream = std.io.countingWriter(std.io.null_writer); - var writer = stream.writer(); - var size: u64 = 0; - - for (symbols) |entry| { - const symbol = entry.value; - - size += 1; - if (symbol.dylib_ordinal > 15) { - try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); - } - size += 1; - - size += 1; - size += entry.key.len; - size += 1; - - size += 1; - try leb.writeILEB128(writer, symbol.offset); - size += 2; - } - - size += stream.bytes_written; - return size; -} - -pub fn writeBindInfo(symbols: anytype, writer: anytype) !void { - for (symbols) |entry| { - const symbol = entry.value; - - if (symbol.dylib_ordinal > 15) { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); - try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); - } else if (symbol.dylib_ordinal > 0) { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); - } else { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); - } - try writer.writeByte(macho.BIND_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.BIND_TYPE_POINTER)); - - try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. - try writer.writeAll(entry.key); - try writer.writeByte(0); - - try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); - try leb.writeILEB128(writer, symbol.offset); - try writer.writeByte(macho.BIND_OPCODE_DO_BIND); - try writer.writeByte(macho.BIND_OPCODE_DONE); - } -} - -pub fn lazyBindInfoSize(symbols: anytype) !u64 { - var stream = std.io.countingWriter(std.io.null_writer); - var writer = stream.writer(); - var size: u64 = 0; - - for (symbols) |entry| { - const symbol = entry.value; - size += 1; - try leb.writeILEB128(writer, symbol.offset); - size += 1; - if (symbol.dylib_ordinal > 15) { - try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); - } - - size += 1; - size += entry.key.len; - size += 1; - - size += 2; - } - - size += stream.bytes_written; - return size; -} - -pub fn writeLazyBindInfo(symbols: anytype, writer: anytype) !void { - for (symbols) |entry| { - const symbol = entry.value; - try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); - try leb.writeILEB128(writer, symbol.offset); - - if (symbol.dylib_ordinal > 15) { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); - try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); - } else if (symbol.dylib_ordinal > 0) { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); - } else { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); - } - - try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. - try writer.writeAll(entry.key); - try writer.writeByte(0); - - try writer.writeByte(macho.BIND_OPCODE_DO_BIND); - try writer.writeByte(macho.BIND_OPCODE_DONE); - } -} From e5234c0e9ee1d60b7a87df8de0350fee2d4e6c55 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 17 Mar 2021 00:21:56 +0100 Subject: [PATCH 21/56] macho: offset table part of GOT --- lib/std/macho.zig | 8 ++ src/codegen.zig | 188 ++++++++++---------------------- src/link/MachO.zig | 261 ++++++++++++++++++++++++++------------------- 3 files changed, 215 insertions(+), 242 deletions(-) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 6785abffca..bca222b5b7 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -1422,6 +1422,14 @@ pub const EXPORT_SYMBOL_FLAGS_KIND_WEAK_DEFINITION: u8 = 0x04; pub const EXPORT_SYMBOL_FLAGS_REEXPORT: u8 = 0x08; pub const EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER: u8 = 0x10; +// An indirect symbol table entry is simply a 32bit index into the symbol table +// to the symbol that the pointer or stub is refering to. Unless it is for a +// non-lazy symbol pointer section for a defined symbol which strip(1) as +// removed. In which case it has the value INDIRECT_SYMBOL_LOCAL. If the +// symbol was also absolute INDIRECT_SYMBOL_ABS is or'ed with that. +pub const INDIRECT_SYMBOL_LOCAL: u32 = 0x80000000; +pub const INDIRECT_SYMBOL_ABS: u32 = 0x40000000; + // Codesign consts and structs taken from: // https://opensource.apple.com/source/xnu/xnu-6153.81.5/osfmk/kern/cs_blobs.h.auto.html diff --git a/src/codegen.zig b/src/codegen.zig index 04bb7741b7..858a1e6559 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2132,9 +2132,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (inst.func.value()) |func_value| { if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; - const text_segment = &macho_file.load_commands.items[macho_file.text_segment_cmd_index.?].Segment; - const got = &text_segment.sections.items[macho_file.got_section_index.?]; - const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64); + const got_addr = blk: { + const seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment; + const got = seg.sections.items[macho_file.got_section_index.?]; + break :blk got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64); + }; + log.debug("got_addr = 0x{x}", .{got_addr}); switch (arch) { .x86_64 => { try self.genSetReg(inst.base.src, Type.initTag(.u32), .rax, .{ .memory = got_addr }); @@ -3303,80 +3306,32 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, .memory => |addr| { if (self.bin_file.options.pie) { - // For MachO, the binary, with the exception of object files, has to be a PIE. - // Therefore we cannot load an absolute address. - // Instead, we need to make use of PC-relative addressing. - if (reg.id() == 0) { // x0 is special-cased - // TODO This needs to be optimised in the stack usage (perhaps use a shadow stack - // like described here: - // https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/using-the-stack-in-aarch64-implementing-push-and-pop) - // str x28, [sp, #-16] - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x28, Register.sp, .{ - .offset = Instruction.LoadStoreOffset.imm_pre_index(-16), - }).toU32()); - // adr x28, #8 - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32()); - if (self.bin_file.cast(link.File.MachO)) |macho_file| { - try macho_file.pie_fixups.append(self.bin_file.allocator, .{ - .address = addr, - .start = self.code.items.len, - .len = 4, - }); - } else { - return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{}); - } - // b [label] - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32()); - // mov r, x0 - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr( - reg, - .xzr, - .x0, - Instruction.Shift.none, - ).toU32()); - // ldr x28, [sp], #16 - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x28, .{ - .register = .{ - .rn = Register.sp, - .offset = Instruction.LoadStoreOffset.imm_post_index(16), - }, - }).toU32()); + // PC-relative displacement to the entry in the GOT table. + // TODO we should come up with our own, backend independent relocation types + // which each backend (Elf, MachO, etc.) would then translate into an actual + // fixup when linking. + // adrp reg, pages + if (self.bin_file.cast(link.File.MachO)) |macho_file| { + try macho_file.pie_fixups.append(self.bin_file.allocator, .{ + .target_addr = addr, + .offset = self.code.items.len, + .size = 4, + }); } else { - // stp x0, x28, [sp, #-16] - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.stp( - .x0, - .x28, - Register.sp, - Instruction.LoadStorePairOffset.pre_index(-16), - ).toU32()); - // adr x28, #8 - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32()); - if (self.bin_file.cast(link.File.MachO)) |macho_file| { - try macho_file.pie_fixups.append(self.bin_file.allocator, .{ - .address = addr, - .start = self.code.items.len, - .len = 4, - }); - } else { - return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{}); - } - // b [label] - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32()); - // mov r, x0 - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr( - reg, - .xzr, - .x0, - Instruction.Shift.none, - ).toU32()); - // ldp x0, x28, [sp, #16] - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldp( - .x0, - .x28, - Register.sp, - Instruction.LoadStorePairOffset.post_index(16), - ).toU32()); + return self.fail(src, "TODO implement genSetReg for PIE GOT indirection on this platform", .{}); } + mem.writeIntLittle( + u32, + try self.code.addManyAsArray(4), + Instruction.adrp(reg, 0).toU32(), + ); + // ldr reg, reg, offset + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ + .register = .{ + .rn = reg, + .offset = Instruction.LoadStoreOffset.imm(0), + }, + }).toU32()); } else { // The value is in memory at a hard-coded address. // If the type is a pointer, it means the pointer address is at this memory location. @@ -3560,62 +3515,31 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, .memory => |x| { if (self.bin_file.options.pie) { - // For MachO, the binary, with the exception of object files, has to be a PIE. - // Therefore, we cannot load an absolute address. - assert(x > math.maxInt(u32)); // 32bit direct addressing is not supported by MachO. - // The plan here is to use unconditional relative jump to GOT entry, where we store - // pre-calculated and stored effective address to load into the target register. - // We leave the actual displacement information empty (0-padded) and fixing it up - // later in the linker. - if (reg.id() == 0) { // %rax is special-cased - try self.code.ensureCapacity(self.code.items.len + 5); - if (self.bin_file.cast(link.File.MachO)) |macho_file| { - try macho_file.pie_fixups.append(self.bin_file.allocator, .{ - .address = x, - .start = self.code.items.len, - .len = 5, - }); - } else { - return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{}); - } - // call [label] - self.code.appendSliceAssumeCapacity(&[_]u8{ - 0xE8, - 0x0, - 0x0, - 0x0, - 0x0, + // RIP-relative displacement to the entry in the GOT table. + // TODO we should come up with our own, backend independent relocation types + // which each backend (Elf, MachO, etc.) would then translate into an actual + // fixup when linking. + if (self.bin_file.cast(link.File.MachO)) |macho_file| { + try macho_file.pie_fixups.append(self.bin_file.allocator, .{ + .target_addr = x, + .offset = self.code.items.len + 3, + .size = 4, }); } else { - try self.code.ensureCapacity(self.code.items.len + 10); - // push %rax - self.code.appendSliceAssumeCapacity(&[_]u8{0x50}); - if (self.bin_file.cast(link.File.MachO)) |macho_file| { - try macho_file.pie_fixups.append(self.bin_file.allocator, .{ - .address = x, - .start = self.code.items.len, - .len = 5, - }); - } else { - return self.fail(src, "TODO implement genSetReg for PIE on this platform", .{}); - } - // call [label] - self.code.appendSliceAssumeCapacity(&[_]u8{ - 0xE8, - 0x0, - 0x0, - 0x0, - 0x0, - }); - // mov %r, %rax - self.code.appendSliceAssumeCapacity(&[_]u8{ - 0x48, - 0x89, - 0xC0 | @as(u8, reg.id()), - }); - // pop %rax - self.code.appendSliceAssumeCapacity(&[_]u8{0x58}); + return self.fail(src, "TODO implement genSetReg for PIE GOT indirection on this platform", .{}); } + try self.code.ensureCapacity(self.code.items.len + 7); + self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() }); + self.code.appendSliceAssumeCapacity(&[_]u8{ + 0x8D, + 0x05 | (@as(u8, reg.id() & 0b111) << 3), + }); + mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), 0); + + try self.code.ensureCapacity(self.code.items.len + 3); + self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended(), .r = reg.isExtended() }); + const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id()); + self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM }); } else if (x <= math.maxInt(u32)) { // Moving from memory to a register is a variant of `8B /r`. // Since we're using 64-bit moves, we require a REX. @@ -3778,9 +3702,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { const decl = payload.data; - const text_segment = &macho_file.load_commands.items[macho_file.text_segment_cmd_index.?].Segment; - const got = &text_segment.sections.items[macho_file.got_section_index.?]; - const got_addr = got.addr + decl.link.macho.offset_table_index * ptr_bytes; + const got_addr = blk: { + const seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment; + const got = seg.sections.items[macho_file.got_section_index.?]; + break :blk got.addr + decl.link.macho.offset_table_index * ptr_bytes; + }; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { const decl = payload.data; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index a16bdc7284..349c2e4644 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -11,6 +11,7 @@ const codegen = @import("../codegen.zig"); const aarch64 = @import("../codegen/aarch64.zig"); const math = std.math; const mem = std.mem; +const meta = std.meta; const bind = @import("MachO/bind.zig"); const trace = @import("../tracy.zig").trace; @@ -87,14 +88,12 @@ code_signature_cmd_index: ?u16 = null, /// Index into __TEXT,__text section. text_section_index: ?u16 = null, -/// Index into __TEXT,__ziggot section. -got_section_index: ?u16 = null, /// Index into __TEXT,__stubs section. stubs_section_index: ?u16 = null, /// Index into __TEXT,__stub_helper section. stub_helper_section_index: ?u16 = null, /// Index into __DATA_CONST,__got section. -data_got_section_index: ?u16 = null, +got_section_index: ?u16 = null, /// Index into __DATA,__la_symbol_ptr section. la_symbol_ptr_section_index: ?u16 = null, /// Index into __DATA,__data section. @@ -122,8 +121,8 @@ stub_helper_stubs_start_off: ?u64 = null, string_table: std.ArrayListUnmanaged(u8) = .{}, string_table_directory: std.StringHashMapUnmanaged(u32) = .{}, -/// Table of trampolines to the actual symbols in __text section. -offset_table: std.ArrayListUnmanaged(u64) = .{}, +/// Table of GOT entries. +offset_table: std.ArrayListUnmanaged(GOTEntry) = .{}, error_flags: File.ErrorFlags = File.ErrorFlags{}, @@ -154,14 +153,19 @@ string_table_needs_relocation: bool = false, /// allocate a fresh text block, which will have ideal capacity, and then grow it /// by 1 byte. It will then have -1 overcapacity. text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{}, + /// Pointer to the last allocated text block last_text_block: ?*TextBlock = null, + /// A list of all PIE fixups required for this run of the linker. /// Warning, this is currently NOT thread-safe. See the TODO below. /// TODO Move this list inside `updateDecl` where it should be allocated /// prior to calling `generateSymbol`, and then immediately deallocated /// rather than sitting in the global scope. -pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, +/// TODO We should also rewrite this using generic relocations common to all +/// backends. +pie_fixups: std.ArrayListUnmanaged(PIEFixup) = .{}, + /// A list of all stub (extern decls) fixups required for this run of the linker. /// Warning, this is currently NOT thread-safe. See the TODO below. /// TODO Move this list inside `updateDecl` where it should be allocated @@ -169,6 +173,22 @@ pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, /// rather than sitting in the global scope. stub_fixups: std.ArrayListUnmanaged(StubFixup) = .{}, +pub const GOTEntry = struct { + /// GOT entry can either be a local pointer or an extern (nonlazy) import. + kind: enum { + Local, + Extern, + }, + + /// Id to the macho.nlist_64 from the respective table: either locals or nonlazy imports. + /// TODO I'm more and more inclined to just manage a single, max two symbol tables + /// rather than 4 as we currently do, but I'll follow up in the future PR. + symbol: u32, + + /// Index of this entry in the GOT. + index: u32, +}; + pub const Import = struct { /// MachO symbol table entry. symbol: macho.nlist_64, @@ -180,14 +200,15 @@ pub const Import = struct { index: u32, }; -pub const PieFixup = struct { - /// Target address we wanted to address in absolute terms. - address: u64, - /// Where in the byte stream we should perform the fixup. - start: usize, - /// The length of the byte stream. For x86_64, this will be - /// variable. For aarch64, it will be fixed at 4 bytes. - len: usize, +pub const PIEFixup = struct { + /// Target VM address of this relocation. + target_addr: u64, + + /// Offset within the byte stream. + offset: usize, + + /// Size of the relocation. + size: usize, }; pub const StubFixup = struct { @@ -1132,11 +1153,14 @@ pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void { } if (self.offset_table_free_list.popOrNull()) |i| { + log.debug("reusing offset table entry index {d} for {s}", .{ i, decl.name }); decl.link.macho.offset_table_index = i; } else { + log.debug("allocating offset table entry index {d} for {s}", .{ self.offset_table.items.len, decl.name }); decl.link.macho.offset_table_index = @intCast(u32, self.offset_table.items.len); _ = self.offset_table.addOneAssumeCapacity(); self.offset_table_count_dirty = true; + self.rebase_info_dirty = true; } self.locals.items[decl.link.macho.local_sym_index] = .{ @@ -1146,7 +1170,11 @@ pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void { .n_desc = 0, .n_value = 0, }; - self.offset_table.items[decl.link.macho.offset_table_index] = 0; + self.offset_table.items[decl.link.macho.offset_table_index] = .{ + .kind = .Local, + .symbol = decl.link.macho.local_sym_index, + .index = decl.link.macho.offset_table_index, + }; } pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { @@ -1189,8 +1217,9 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { .externally_managed => |x| x, .appended => code_buffer.items, .fail => |em| { - // Clear any PIE fixups and stub fixups for this decl. + // Clear any PIE fixups for this decl. self.pie_fixups.shrinkRetainingCapacity(0); + // Clear any stub fixups for this decl. self.stub_fixups.shrinkRetainingCapacity(0); decl.analysis = .codegen_failure; try module.failed_decls.put(module.gpa, decl, em); @@ -1209,9 +1238,12 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { const vaddr = try self.growTextBlock(&decl.link.macho, code.len, required_alignment); log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl.name, symbol.n_value, vaddr }); if (vaddr != symbol.n_value) { - symbol.n_value = vaddr; log.debug(" (writing new offset table entry)", .{}); - self.offset_table.items[decl.link.macho.offset_table_index] = vaddr; + self.offset_table.items[decl.link.macho.offset_table_index] = .{ + .kind = .Local, + .symbol = decl.link.macho.local_sym_index, + .index = decl.link.macho.offset_table_index, + }; try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); } } else if (code.len < decl.link.macho.size) { @@ -1240,7 +1272,11 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { .n_desc = 0, .n_value = addr, }; - self.offset_table.items[decl.link.macho.offset_table_index] = addr; + self.offset_table.items[decl.link.macho.offset_table_index] = .{ + .kind = .Local, + .symbol = decl.link.macho.local_sym_index, + .index = decl.link.macho.offset_table_index, + }; try self.writeLocalSymbol(decl.link.macho.local_sym_index); if (self.d_sym) |*ds| @@ -1248,30 +1284,48 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); } - // Perform PIE fixups (if any) - const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const got_section = text_segment.sections.items[self.got_section_index.?]; + // Calculate displacements to target addr (if any). while (self.pie_fixups.popOrNull()) |fixup| { - const target_addr = fixup.address; - const this_addr = symbol.n_value + fixup.start; + assert(fixup.size == 4); + const this_addr = symbol.n_value + fixup.offset; + const target_addr = fixup.target_addr; + switch (self.base.options.target.cpu.arch) { .x86_64 => { - assert(target_addr >= this_addr + fixup.len); - const displacement = try math.cast(u32, target_addr - this_addr - fixup.len); - var placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)]; - mem.writeIntSliceLittle(u32, placeholder, displacement); + const displacement = try math.cast(u32, target_addr - this_addr - 4); + mem.writeIntLittle(u32, code_buffer.items[fixup.offset..][0..4], displacement); }, .aarch64 => { - assert(target_addr >= this_addr); - const displacement = try math.cast(u27, target_addr - this_addr); - var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; - mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.b(@as(i28, displacement)).toU32()); + // TODO optimize instruction based on jump length (use ldr(literal) + nop if possible). + { + const inst = code_buffer.items[fixup.offset..][0..4]; + var parsed = mem.bytesAsValue(meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.PCRelativeAddress, + ), inst); + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); + parsed.immhi = @truncate(u19, pages >> 2); + parsed.immlo = @truncate(u2, pages); + } + { + const inst = code_buffer.items[fixup.offset + 4 ..][0..4]; + var parsed = mem.bytesAsValue(meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.LoadStoreRegister, + ), inst); + const narrowed = @truncate(u12, target_addr); + const offset = try math.divExact(u12, narrowed, 8); + parsed.offset = offset; + } }, else => unreachable, // unsupported target architecture } } // Resolve stubs (if any) + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const stubs = text_segment.sections.items[self.stubs_section_index.?]; for (self.stub_fixups.items) |fixup| { const stub_addr = stubs.addr + fixup.symbol * stubs.reserved2; @@ -1561,39 +1615,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { self.header_dirty = true; self.load_commands_dirty = true; } - if (self.got_section_index == null) { - const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - self.got_section_index = @intCast(u16, text_segment.sections.items.len); - - const alignment: u2 = switch (self.base.options.target.cpu.arch) { - .x86_64 => 0, - .aarch64 => 2, - else => unreachable, // unhandled architecture type - }; - const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; - const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; - const off = text_segment.findFreeSpace(needed_size, @alignOf(u64), self.header_pad); - assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); // TODO Must expand __TEXT segment. - - log.debug("found __ziggot section free space 0x{x} to 0x{x}", .{ off, off + needed_size }); - - try text_segment.addSection(self.base.allocator, .{ - .sectname = makeStaticString("__ziggot"), - .segname = makeStaticString("__TEXT"), - .addr = text_segment.inner.vmaddr + off, - .size = needed_size, - .offset = @intCast(u32, off), - .@"align" = alignment, - .reloff = 0, - .nreloc = 0, - .flags = flags, - .reserved1 = 0, - .reserved2 = 0, - .reserved3 = 0, - }); - self.header_dirty = true; - self.load_commands_dirty = true; - } if (self.stubs_section_index == null) { const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; self.stubs_section_index = @intCast(u16, text_segment.sections.items.len); @@ -1694,9 +1715,9 @@ pub fn populateMissingMetadata(self: *MachO) !void { self.header_dirty = true; self.load_commands_dirty = true; } - if (self.data_got_section_index == null) { + if (self.got_section_index == null) { const dc_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; - self.data_got_section_index = @intCast(u16, dc_segment.sections.items.len); + self.got_section_index = @intCast(u16, dc_segment.sections.items.len); const flags = macho.S_NON_LAZY_SYMBOL_POINTERS; const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; @@ -2083,6 +2104,13 @@ pub fn populateMissingMetadata(self: *MachO) !void { .dylib_ordinal = 1, // TODO this is currently hardcoded. .index = index, }); + const off_index = @intCast(u32, self.offset_table.items.len); + try self.offset_table.append(self.base.allocator, .{ + .kind = .Extern, + .symbol = index, + .index = off_index, + }); + try self.writeOffsetTableEntry(off_index); self.binding_info_dirty = true; } if (self.stub_helper_stubs_start_off == null) { @@ -2412,41 +2440,29 @@ fn findFreeSpaceLinkedit(self: *MachO, object_size: u64, min_alignment: u16, sta } fn writeOffsetTableEntry(self: *MachO, index: usize) !void { - const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const sect = &text_segment.sections.items[self.got_section_index.?]; + const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const sect = &seg.sections.items[self.got_section_index.?]; const off = sect.offset + @sizeOf(u64) * index; - const vmaddr = sect.addr + @sizeOf(u64) * index; if (self.offset_table_count_dirty) { // TODO relocate. self.offset_table_count_dirty = false; } - var code: [8]u8 = undefined; - switch (self.base.options.target.cpu.arch) { - .x86_64 => { - const pos_symbol_off = try math.cast(u31, vmaddr - self.offset_table.items[index] + 7); - const symbol_off = @bitCast(u32, @as(i32, pos_symbol_off) * -1); - // lea %rax, [rip - disp] - code[0] = 0x48; - code[1] = 0x8D; - code[2] = 0x5; - mem.writeIntLittle(u32, code[3..7], symbol_off); - // ret - code[7] = 0xC3; - }, - .aarch64 => { - const pos_symbol_off = try math.cast(u20, vmaddr - self.offset_table.items[index]); - const symbol_off = @as(i21, pos_symbol_off) * -1; - // adr x0, #-disp - mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x0, symbol_off).toU32()); - // ret x28 - mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ret(.x28).toU32()); - }, - else => unreachable, // unsupported target architecture - } - log.debug("writing offset table entry 0x{x} at 0x{x}", .{ self.offset_table.items[index], off }); - try self.base.file.?.pwriteAll(&code, off); + const got_entry = self.offset_table.items[index]; + const sym = blk: { + switch (got_entry.kind) { + .Local => { + break :blk self.locals.items[got_entry.symbol]; + }, + .Extern => { + break :blk self.nonlazy_imports.items()[got_entry.symbol].value.symbol; + }, + } + }; + const sym_name = self.getString(sym.n_strx); + log.debug("writing offset table entry [ 0x{x} => 0x{x} ({s}) ]", .{ off, sym.n_value, sym_name }); + try self.base.file.?.pwriteAll(mem.asBytes(&sym.n_value), off); } fn writeLazySymbolPointer(self: *MachO, index: u32) !void { @@ -2473,7 +2489,7 @@ fn writeStubHelperPreamble(self: *MachO) !void { const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?]; const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; - const got = &data_const_segment.sections.items[self.data_got_section_index.?]; + const got = &data_const_segment.sections.items[self.got_section_index.?]; const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; const data = &data_segment.sections.items[self.data_section_index.?]; @@ -2813,15 +2829,15 @@ fn writeIndirectSymbolTable(self: *MachO) !void { const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; const stubs = &text_segment.sections.items[self.stubs_section_index.?]; const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; - const got = &data_const_seg.sections.items[self.data_got_section_index.?]; + const got = &data_const_seg.sections.items[self.got_section_index.?]; const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; const lazy = self.lazy_imports.items(); - const nonlazy = self.nonlazy_imports.items(); + const got_entries = self.offset_table.items; const allocated_size = self.allocatedSizeLinkedit(dysymtab.indirectsymoff); - const nindirectsyms = @intCast(u32, lazy.len * 2 + nonlazy.len); + const nindirectsyms = @intCast(u32, lazy.len * 2 + got_entries.len); const needed_size = @intCast(u32, nindirectsyms * @sizeOf(u32)); if (needed_size > allocated_size) { @@ -2847,12 +2863,19 @@ fn writeIndirectSymbolTable(self: *MachO) !void { const base_id = @intCast(u32, lazy.len); got.reserved1 = base_id; - for (nonlazy) |_, i| { - const symtab_idx = @intCast(u32, dysymtab.iundefsym + i + base_id); - try writer.writeIntLittle(u32, symtab_idx); + for (got_entries) |entry| { + switch (entry.kind) { + .Local => { + try writer.writeIntLittle(u32, macho.INDIRECT_SYMBOL_LOCAL); + }, + .Extern => { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + entry.index + base_id); + try writer.writeIntLittle(u32, symtab_idx); + }, + } } - la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, nonlazy.len); + la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, got_entries.len); for (lazy) |_, i| { const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); try writer.writeIntLittle(u32, symtab_idx); @@ -2973,12 +2996,27 @@ fn writeRebaseInfoTable(self: *MachO) !void { var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); defer pointers.deinit(); + if (self.got_section_index) |idx| { + const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = self.data_const_segment_cmd_index.?; + + for (self.offset_table.items) |entry| { + if (entry.kind == .Extern) continue; + try pointers.append(.{ + .offset = base_offset + entry.index * @sizeOf(u64), + .segment_id = segment_id, + }); + } + } + if (self.la_symbol_ptr_section_index) |idx| { try pointers.ensureCapacity(pointers.items.len + self.lazy_imports.items().len); const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; const sect = seg.sections.items[idx]; const base_offset = sect.addr - seg.inner.vmaddr; - const segment_id = @intCast(u16, self.data_segment_cmd_index.?); + const segment_id = self.data_segment_cmd_index.?; for (self.lazy_imports.items()) |entry| { pointers.appendAssumeCapacity(.{ @@ -3024,19 +3062,20 @@ fn writeBindingInfoTable(self: *MachO) !void { var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); defer pointers.deinit(); - if (self.data_got_section_index) |idx| { - try pointers.ensureCapacity(pointers.items.len + self.nonlazy_imports.items().len); + if (self.got_section_index) |idx| { const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; const sect = seg.sections.items[idx]; const base_offset = sect.addr - seg.inner.vmaddr; const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); - for (self.nonlazy_imports.items()) |entry| { - pointers.appendAssumeCapacity(.{ - .offset = base_offset + entry.value.index * @sizeOf(u64), + for (self.offset_table.items) |entry| { + if (entry.kind == .Local) continue; + const import = self.nonlazy_imports.items()[entry.symbol]; + try pointers.append(.{ + .offset = base_offset + entry.index * @sizeOf(u64), .segment_id = segment_id, - .dylib_ordinal = entry.value.dylib_ordinal, - .name = entry.key, + .dylib_ordinal = import.value.dylib_ordinal, + .name = import.key, }); } } From 119fc318a753f57b55809e9256e823accba6b56a Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sun, 14 Mar 2021 22:30:25 +0100 Subject: [PATCH 22/56] std/crypto/chacha20: add round-reduced versions & cleanup internals See https://eprint.iacr.org/2019/1492.pdf for justification. 8 rounds ChaCha20 provides a 2.5x speedup, and is still believed to be safe. Round-reduced versions are actually deployed (ex: Android filesystem encryption), and thanks to the magic of comptime, it doesn't take much to support them. This also makes the ChaCha20 code more consistent with the Salsa20 code, removing internal functions that were not part of the public API any more. No breaking changes; the public API remains backwards compatible. --- lib/std/crypto.zig | 14 +- lib/std/crypto/benchmark.zig | 1 + lib/std/crypto/chacha20.zig | 1191 +++++++++++++++++----------------- 3 files changed, 622 insertions(+), 584 deletions(-) diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 1ec7674999..457b9130d9 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -24,8 +24,12 @@ pub const aead = struct { pub const Gimli = @import("crypto/gimli.zig").Aead; pub const chacha_poly = struct { - pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").Chacha20Poly1305; - pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChacha20Poly1305; + pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").ChaCha20Poly1305; + pub const ChaCha12Poly1305 = @import("crypto/chacha20.zig").ChaCha12Poly1305; + pub const ChaCha8Poly1305 = @import("crypto/chacha20.zig").ChaCha8Poly1305; + pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChaCha20Poly1305; + pub const XChaCha12Poly1305 = @import("crypto/chacha20.zig").XChaCha12Poly1305; + pub const XChaCha8Poly1305 = @import("crypto/chacha20.zig").XChaCha8Poly1305; }; pub const isap = @import("crypto/isap.zig"); @@ -119,8 +123,14 @@ pub const sign = struct { pub const stream = struct { pub const chacha = struct { pub const ChaCha20IETF = @import("crypto/chacha20.zig").ChaCha20IETF; + pub const ChaCha12IETF = @import("crypto/chacha20.zig").ChaCha12IETF; + pub const ChaCha8IETF = @import("crypto/chacha20.zig").ChaCha8IETF; pub const ChaCha20With64BitNonce = @import("crypto/chacha20.zig").ChaCha20With64BitNonce; + pub const ChaCha12With64BitNonce = @import("crypto/chacha20.zig").ChaCha12With64BitNonce; + pub const ChaCha8With64BitNonce = @import("crypto/chacha20.zig").ChaCha8With64BitNonce; pub const XChaCha20IETF = @import("crypto/chacha20.zig").XChaCha20IETF; + pub const XChaCha12IETF = @import("crypto/chacha20.zig").XChaCha12IETF; + pub const XChaCha8IETF = @import("crypto/chacha20.zig").XChaCha8IETF; }; pub const salsa = struct { diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index e3ffa62ed1..49d5b15820 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -202,6 +202,7 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime const aeads = [_]Crypto{ Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" }, Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" }, + Crypto{ .ty = crypto.aead.chacha_poly.XChaCha8Poly1305, .name = "xchacha8Poly1305" }, Crypto{ .ty = crypto.aead.salsa_poly.XSalsa20Poly1305, .name = "xsalsa20Poly1305" }, Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" }, Crypto{ .ty = crypto.aead.aegis.Aegis128L, .name = "aegis-128l" }, diff --git a/lib/std/crypto/chacha20.zig b/lib/std/crypto/chacha20.zig index 4611923892..e1fe3e232d 100644 --- a/lib/std/crypto/chacha20.zig +++ b/lib/std/crypto/chacha20.zig @@ -15,286 +15,357 @@ const Vector = std.meta.Vector; const Poly1305 = std.crypto.onetimeauth.Poly1305; const Error = std.crypto.Error; +/// IETF-variant of the ChaCha20 stream cipher, as designed for TLS. +pub const ChaCha20IETF = ChaChaIETF(20); + +/// IETF-variant of the ChaCha20 stream cipher, reduced to 12 rounds. +/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. +/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. +pub const ChaCha12IETF = ChaChaIETF(12); + +/// IETF-variant of the ChaCha20 stream cipher, reduced to 8 rounds. +/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. +/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. +pub const ChaCha8IETF = ChaChaIETF(8); + +/// Original ChaCha20 stream cipher. +pub const ChaCha20With64BitNonce = ChaChaWith64BitNonce(20); + +/// Original ChaCha20 stream cipher, reduced to 12 rounds. +/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. +/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. +pub const ChaCha12With64BitNonce = ChaChaWith64BitNonce(12); + +/// Original ChaCha20 stream cipher, reduced to 8 rounds. +/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. +/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. +pub const ChaCha8With64BitNonce = ChaChaWith64BitNonce(8); + +/// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher +pub const XChaCha20IETF = XChaChaIETF(20); + +/// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher, reduced to 12 rounds +/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. +/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. +pub const XChaCha12IETF = XChaChaIETF(12); + +/// XChaCha20 (nonce-extended version of the IETF ChaCha20 variant) stream cipher, reduced to 8 rounds +/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. +/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. +pub const XChaCha8IETF = XChaChaIETF(8); + +/// ChaCha20-Poly1305 authenticated cipher, as designed for TLS +pub const ChaCha20Poly1305 = ChaChaPoly1305(20); + +/// ChaCha20-Poly1305 authenticated cipher, reduced to 12 rounds +/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. +/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. +pub const ChaCha12Poly1305 = ChaChaPoly1305(12); + +/// ChaCha20-Poly1305 authenticated cipher, reduced to 8 rounds +/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. +/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. +pub const ChaCha8Poly1305 = ChaChaPoly1305(8); + +/// XChaCha20-Poly1305 authenticated cipher +pub const XChaCha20Poly1305 = XChaChaPoly1305(20); + +/// XChaCha20-Poly1305 authenticated cipher +/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. +/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. +pub const XChaCha12Poly1305 = XChaChaPoly1305(12); + +/// XChaCha20-Poly1305 authenticated cipher +/// Reduced-rounds versions are faster than the full-round version, but have a lower security margin. +/// However, ChaCha is still believed to have a comfortable security even with only with 8 rounds. +pub const XChaCha8Poly1305 = XChaChaPoly1305(8); + // Vectorized implementation of the core function -const ChaCha20VecImpl = struct { - const Lane = Vector(4, u32); - const BlockVec = [4]Lane; +fn ChaChaVecImpl(comptime rounds_nb: usize) type { + return struct { + const Lane = Vector(4, u32); + const BlockVec = [4]Lane; - fn initContext(key: [8]u32, d: [4]u32) BlockVec { - const c = "expand 32-byte k"; - const constant_le = comptime Lane{ - mem.readIntLittle(u32, c[0..4]), - mem.readIntLittle(u32, c[4..8]), - mem.readIntLittle(u32, c[8..12]), - mem.readIntLittle(u32, c[12..16]), - }; - return BlockVec{ - constant_le, - Lane{ key[0], key[1], key[2], key[3] }, - Lane{ key[4], key[5], key[6], key[7] }, - Lane{ d[0], d[1], d[2], d[3] }, - }; - } - - fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void { - x.* = input; - - var r: usize = 0; - while (r < 20) : (r += 2) { - x[0] +%= x[1]; - x[3] ^= x[0]; - x[3] = math.rotl(Lane, x[3], 16); - - x[2] +%= x[3]; - x[1] ^= x[2]; - x[1] = math.rotl(Lane, x[1], 12); - - x[0] +%= x[1]; - x[3] ^= x[0]; - x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 3, 0, 1, 2 }); - x[3] = math.rotl(Lane, x[3], 8); - - x[2] +%= x[3]; - x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 }); - x[1] ^= x[2]; - x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 1, 2, 3, 0 }); - x[1] = math.rotl(Lane, x[1], 7); - - x[0] +%= x[1]; - x[3] ^= x[0]; - x[3] = math.rotl(Lane, x[3], 16); - - x[2] +%= x[3]; - x[1] ^= x[2]; - x[1] = math.rotl(Lane, x[1], 12); - - x[0] +%= x[1]; - x[3] ^= x[0]; - x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 1, 2, 3, 0 }); - x[3] = math.rotl(Lane, x[3], 8); - - x[2] +%= x[3]; - x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 }); - x[1] ^= x[2]; - x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 3, 0, 1, 2 }); - x[1] = math.rotl(Lane, x[1], 7); + fn initContext(key: [8]u32, d: [4]u32) BlockVec { + const c = "expand 32-byte k"; + const constant_le = comptime Lane{ + mem.readIntLittle(u32, c[0..4]), + mem.readIntLittle(u32, c[4..8]), + mem.readIntLittle(u32, c[8..12]), + mem.readIntLittle(u32, c[12..16]), + }; + return BlockVec{ + constant_le, + Lane{ key[0], key[1], key[2], key[3] }, + Lane{ key[4], key[5], key[6], key[7] }, + Lane{ d[0], d[1], d[2], d[3] }, + }; } - } - fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void { - var i: usize = 0; - while (i < 4) : (i += 1) { - mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i][0]); - mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i][1]); - mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i][2]); - mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i][3]); + fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void { + x.* = input; + + var r: usize = 0; + while (r < rounds_nb) : (r += 2) { + x[0] +%= x[1]; + x[3] ^= x[0]; + x[3] = math.rotl(Lane, x[3], 16); + + x[2] +%= x[3]; + x[1] ^= x[2]; + x[1] = math.rotl(Lane, x[1], 12); + + x[0] +%= x[1]; + x[3] ^= x[0]; + x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 3, 0, 1, 2 }); + x[3] = math.rotl(Lane, x[3], 8); + + x[2] +%= x[3]; + x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 }); + x[1] ^= x[2]; + x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 1, 2, 3, 0 }); + x[1] = math.rotl(Lane, x[1], 7); + + x[0] +%= x[1]; + x[3] ^= x[0]; + x[3] = math.rotl(Lane, x[3], 16); + + x[2] +%= x[3]; + x[1] ^= x[2]; + x[1] = math.rotl(Lane, x[1], 12); + + x[0] +%= x[1]; + x[3] ^= x[0]; + x[0] = @shuffle(u32, x[0], undefined, [_]i32{ 1, 2, 3, 0 }); + x[3] = math.rotl(Lane, x[3], 8); + + x[2] +%= x[3]; + x[3] = @shuffle(u32, x[3], undefined, [_]i32{ 2, 3, 0, 1 }); + x[1] ^= x[2]; + x[2] = @shuffle(u32, x[2], undefined, [_]i32{ 3, 0, 1, 2 }); + x[1] = math.rotl(Lane, x[1], 7); + } } - } - fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void { - x[0] +%= ctx[0]; - x[1] +%= ctx[1]; - x[2] +%= ctx[2]; - x[3] +%= ctx[3]; - } + fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void { + var i: usize = 0; + while (i < 4) : (i += 1) { + mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i][0]); + mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i][1]); + mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i][2]); + mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i][3]); + } + } - fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void { - var ctx = initContext(key, counter); - var x: BlockVec = undefined; - var buf: [64]u8 = undefined; - var i: usize = 0; - while (i + 64 <= in.len) : (i += 64) { + fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void { + x[0] +%= ctx[0]; + x[1] +%= ctx[1]; + x[2] +%= ctx[2]; + x[3] +%= ctx[3]; + } + + fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void { + var ctx = initContext(key, counter); + var x: BlockVec = undefined; + var buf: [64]u8 = undefined; + var i: usize = 0; + while (i + 64 <= in.len) : (i += 64) { + chacha20Core(x[0..], ctx); + contextFeedback(&x, ctx); + hashToBytes(buf[0..], x); + + var xout = out[i..]; + const xin = in[i..]; + var j: usize = 0; + while (j < 64) : (j += 1) { + xout[j] = xin[j]; + } + j = 0; + while (j < 64) : (j += 1) { + xout[j] ^= buf[j]; + } + ctx[3][0] += 1; + } + if (i < in.len) { + chacha20Core(x[0..], ctx); + contextFeedback(&x, ctx); + hashToBytes(buf[0..], x); + + var xout = out[i..]; + const xin = in[i..]; + var j: usize = 0; + while (j < in.len % 64) : (j += 1) { + xout[j] = xin[j] ^ buf[j]; + } + } + } + + fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 { + var c: [4]u32 = undefined; + for (c) |_, i| { + c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]); + } + const ctx = initContext(keyToWords(key), c); + var x: BlockVec = undefined; chacha20Core(x[0..], ctx); - contextFeedback(&x, ctx); - hashToBytes(buf[0..], x); - - var xout = out[i..]; - const xin = in[i..]; - var j: usize = 0; - while (j < 64) : (j += 1) { - xout[j] = xin[j]; - } - j = 0; - while (j < 64) : (j += 1) { - xout[j] ^= buf[j]; - } - ctx[3][0] += 1; + var out: [32]u8 = undefined; + mem.writeIntLittle(u32, out[0..4], x[0][0]); + mem.writeIntLittle(u32, out[4..8], x[0][1]); + mem.writeIntLittle(u32, out[8..12], x[0][2]); + mem.writeIntLittle(u32, out[12..16], x[0][3]); + mem.writeIntLittle(u32, out[16..20], x[3][0]); + mem.writeIntLittle(u32, out[20..24], x[3][1]); + mem.writeIntLittle(u32, out[24..28], x[3][2]); + mem.writeIntLittle(u32, out[28..32], x[3][3]); + return out; } - if (i < in.len) { - chacha20Core(x[0..], ctx); - contextFeedback(&x, ctx); - hashToBytes(buf[0..], x); - - var xout = out[i..]; - const xin = in[i..]; - var j: usize = 0; - while (j < in.len % 64) : (j += 1) { - xout[j] = xin[j] ^ buf[j]; - } - } - } - - fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 { - var c: [4]u32 = undefined; - for (c) |_, i| { - c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]); - } - const ctx = initContext(keyToWords(key), c); - var x: BlockVec = undefined; - chacha20Core(x[0..], ctx); - var out: [32]u8 = undefined; - mem.writeIntLittle(u32, out[0..4], x[0][0]); - mem.writeIntLittle(u32, out[4..8], x[0][1]); - mem.writeIntLittle(u32, out[8..12], x[0][2]); - mem.writeIntLittle(u32, out[12..16], x[0][3]); - mem.writeIntLittle(u32, out[16..20], x[3][0]); - mem.writeIntLittle(u32, out[20..24], x[3][1]); - mem.writeIntLittle(u32, out[24..28], x[3][2]); - mem.writeIntLittle(u32, out[28..32], x[3][3]); - return out; - } -}; + }; +} // Non-vectorized implementation of the core function -const ChaCha20NonVecImpl = struct { - const BlockVec = [16]u32; +fn ChaChaNonVecImpl(comptime rounds_nb: usize) type { + return struct { + const BlockVec = [16]u32; - fn initContext(key: [8]u32, d: [4]u32) BlockVec { - const c = "expand 32-byte k"; - const constant_le = comptime [4]u32{ - mem.readIntLittle(u32, c[0..4]), - mem.readIntLittle(u32, c[4..8]), - mem.readIntLittle(u32, c[8..12]), - mem.readIntLittle(u32, c[12..16]), - }; - return BlockVec{ - constant_le[0], constant_le[1], constant_le[2], constant_le[3], - key[0], key[1], key[2], key[3], - key[4], key[5], key[6], key[7], - d[0], d[1], d[2], d[3], - }; - } + fn initContext(key: [8]u32, d: [4]u32) BlockVec { + const c = "expand 32-byte k"; + const constant_le = comptime [4]u32{ + mem.readIntLittle(u32, c[0..4]), + mem.readIntLittle(u32, c[4..8]), + mem.readIntLittle(u32, c[8..12]), + mem.readIntLittle(u32, c[12..16]), + }; + return BlockVec{ + constant_le[0], constant_le[1], constant_le[2], constant_le[3], + key[0], key[1], key[2], key[3], + key[4], key[5], key[6], key[7], + d[0], d[1], d[2], d[3], + }; + } - const QuarterRound = struct { - a: usize, - b: usize, - c: usize, - d: usize, + const QuarterRound = struct { + a: usize, + b: usize, + c: usize, + d: usize, + }; + + fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound { + return QuarterRound{ + .a = a, + .b = b, + .c = c, + .d = d, + }; + } + + fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void { + x.* = input; + + const rounds = comptime [_]QuarterRound{ + Rp(0, 4, 8, 12), + Rp(1, 5, 9, 13), + Rp(2, 6, 10, 14), + Rp(3, 7, 11, 15), + Rp(0, 5, 10, 15), + Rp(1, 6, 11, 12), + Rp(2, 7, 8, 13), + Rp(3, 4, 9, 14), + }; + + comptime var j: usize = 0; + inline while (j < rounds_nb) : (j += 2) { + inline for (rounds) |r| { + x[r.a] +%= x[r.b]; + x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16)); + x[r.c] +%= x[r.d]; + x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12)); + x[r.a] +%= x[r.b]; + x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8)); + x[r.c] +%= x[r.d]; + x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7)); + } + } + } + + fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void { + var i: usize = 0; + while (i < 4) : (i += 1) { + mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]); + mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]); + mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]); + mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]); + } + } + + fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void { + var i: usize = 0; + while (i < 16) : (i += 1) { + x[i] +%= ctx[i]; + } + } + + fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void { + var ctx = initContext(key, counter); + var x: BlockVec = undefined; + var buf: [64]u8 = undefined; + var i: usize = 0; + while (i + 64 <= in.len) : (i += 64) { + chacha20Core(x[0..], ctx); + contextFeedback(&x, ctx); + hashToBytes(buf[0..], x); + + var xout = out[i..]; + const xin = in[i..]; + var j: usize = 0; + while (j < 64) : (j += 1) { + xout[j] = xin[j]; + } + j = 0; + while (j < 64) : (j += 1) { + xout[j] ^= buf[j]; + } + ctx[12] += 1; + } + if (i < in.len) { + chacha20Core(x[0..], ctx); + contextFeedback(&x, ctx); + hashToBytes(buf[0..], x); + + var xout = out[i..]; + const xin = in[i..]; + var j: usize = 0; + while (j < in.len % 64) : (j += 1) { + xout[j] = xin[j] ^ buf[j]; + } + } + } + + fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 { + var c: [4]u32 = undefined; + for (c) |_, i| { + c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]); + } + const ctx = initContext(keyToWords(key), c); + var x: BlockVec = undefined; + chacha20Core(x[0..], ctx); + var out: [32]u8 = undefined; + mem.writeIntLittle(u32, out[0..4], x[0]); + mem.writeIntLittle(u32, out[4..8], x[1]); + mem.writeIntLittle(u32, out[8..12], x[2]); + mem.writeIntLittle(u32, out[12..16], x[3]); + mem.writeIntLittle(u32, out[16..20], x[12]); + mem.writeIntLittle(u32, out[20..24], x[13]); + mem.writeIntLittle(u32, out[24..28], x[14]); + mem.writeIntLittle(u32, out[28..32], x[15]); + return out; + } }; +} - fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound { - return QuarterRound{ - .a = a, - .b = b, - .c = c, - .d = d, - }; - } - - fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void { - x.* = input; - - const rounds = comptime [_]QuarterRound{ - Rp(0, 4, 8, 12), - Rp(1, 5, 9, 13), - Rp(2, 6, 10, 14), - Rp(3, 7, 11, 15), - Rp(0, 5, 10, 15), - Rp(1, 6, 11, 12), - Rp(2, 7, 8, 13), - Rp(3, 4, 9, 14), - }; - - comptime var j: usize = 0; - inline while (j < 20) : (j += 2) { - inline for (rounds) |r| { - x[r.a] +%= x[r.b]; - x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16)); - x[r.c] +%= x[r.d]; - x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12)); - x[r.a] +%= x[r.b]; - x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8)); - x[r.c] +%= x[r.d]; - x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7)); - } - } - } - - fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void { - var i: usize = 0; - while (i < 4) : (i += 1) { - mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]); - mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]); - mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]); - mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]); - } - } - - fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void { - var i: usize = 0; - while (i < 16) : (i += 1) { - x[i] +%= ctx[i]; - } - } - - fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void { - var ctx = initContext(key, counter); - var x: BlockVec = undefined; - var buf: [64]u8 = undefined; - var i: usize = 0; - while (i + 64 <= in.len) : (i += 64) { - chacha20Core(x[0..], ctx); - contextFeedback(&x, ctx); - hashToBytes(buf[0..], x); - - var xout = out[i..]; - const xin = in[i..]; - var j: usize = 0; - while (j < 64) : (j += 1) { - xout[j] = xin[j]; - } - j = 0; - while (j < 64) : (j += 1) { - xout[j] ^= buf[j]; - } - ctx[12] += 1; - } - if (i < in.len) { - chacha20Core(x[0..], ctx); - contextFeedback(&x, ctx); - hashToBytes(buf[0..], x); - - var xout = out[i..]; - const xin = in[i..]; - var j: usize = 0; - while (j < in.len % 64) : (j += 1) { - xout[j] = xin[j] ^ buf[j]; - } - } - } - - fn hchacha20(input: [16]u8, key: [32]u8) [32]u8 { - var c: [4]u32 = undefined; - for (c) |_, i| { - c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]); - } - const ctx = initContext(keyToWords(key), c); - var x: BlockVec = undefined; - chacha20Core(x[0..], ctx); - var out: [32]u8 = undefined; - mem.writeIntLittle(u32, out[0..4], x[0]); - mem.writeIntLittle(u32, out[4..8], x[1]); - mem.writeIntLittle(u32, out[8..12], x[2]); - mem.writeIntLittle(u32, out[12..16], x[3]); - mem.writeIntLittle(u32, out[16..20], x[12]); - mem.writeIntLittle(u32, out[20..24], x[13]); - mem.writeIntLittle(u32, out[24..28], x[14]); - mem.writeIntLittle(u32, out[28..32], x[15]); - return out; - } -}; - -const ChaCha20Impl = if (std.Target.current.cpu.arch == .x86_64) ChaCha20VecImpl else ChaCha20NonVecImpl; +fn ChaChaImpl(comptime rounds_nb: usize) type { + return if (std.Target.current.cpu.arch == .x86_64) ChaChaVecImpl(rounds_nb) else ChaChaNonVecImpl(rounds_nb); +} fn keyToWords(key: [32]u8) [8]u32 { var k: [8]u32 = undefined; @@ -305,68 +376,239 @@ fn keyToWords(key: [32]u8) [8]u32 { return k; } -/// ChaCha20 avoids the possibility of timing attacks, as there are no branches -/// on secret key data. -/// -/// in and out should be the same length. -/// counter should generally be 0 or 1 -/// -/// ChaCha20 is self-reversing. To decrypt just run the cipher with the same -/// counter, nonce, and key. -pub const ChaCha20IETF = struct { - pub fn xor(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: [12]u8) void { - assert(in.len == out.len); - assert((in.len >> 6) + counter <= maxInt(u32)); +fn extend(key: [32]u8, nonce: [24]u8, comptime rounds_nb: usize) struct { key: [32]u8, nonce: [12]u8 } { + var subnonce: [12]u8 = undefined; + mem.set(u8, subnonce[0..4], 0); + mem.copy(u8, subnonce[4..], nonce[16..24]); + return .{ + .key = ChaChaImpl(rounds_nb).hchacha20(nonce[0..16].*, key), + .nonce = subnonce, + }; +} - var c: [4]u32 = undefined; - c[0] = counter; - c[1] = mem.readIntLittle(u32, nonce[0..4]); - c[2] = mem.readIntLittle(u32, nonce[4..8]); - c[3] = mem.readIntLittle(u32, nonce[8..12]); - ChaCha20Impl.chacha20Xor(out, in, keyToWords(key), c); - } -}; +fn ChaChaIETF(comptime rounds_nb: usize) type { + return struct { + /// Nonce length in bytes. + pub const nonce_length = 12; + /// Key length in bytes. + pub const key_length = 32; -/// This is the original ChaCha20 before RFC 7539, which recommends using the -/// orgininal version on applications such as disk or file encryption that might -/// exceed the 256 GiB limit of the 96-bit nonce version. -pub const ChaCha20With64BitNonce = struct { - pub fn xor(out: []u8, in: []const u8, counter: u64, key: [32]u8, nonce: [8]u8) void { - assert(in.len == out.len); - assert(counter +% (in.len >> 6) >= counter); + /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`. + /// WARNING: This function doesn't provide authenticated encryption. + /// Using the AEAD or one of the `box` versions is usually preferred. + pub fn xor(out: []u8, in: []const u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void { + assert(in.len == out.len); + assert(in.len / 64 <= (1 << 32 - 1) - counter); - var cursor: usize = 0; - const k = keyToWords(key); - var c: [4]u32 = undefined; - c[0] = @truncate(u32, counter); - c[1] = @truncate(u32, counter >> 32); - c[2] = mem.readIntLittle(u32, nonce[0..4]); - c[3] = mem.readIntLittle(u32, nonce[4..8]); + var d: [4]u32 = undefined; + d[0] = counter; + d[1] = mem.readIntLittle(u32, nonce[0..4]); + d[2] = mem.readIntLittle(u32, nonce[4..8]); + d[3] = mem.readIntLittle(u32, nonce[8..12]); + ChaChaImpl(rounds_nb).chacha20Xor(out, in, keyToWords(key), d); + } + }; +} - const block_length = (1 << 6); - // The full block size is greater than the address space on a 32bit machine - const big_block = if (@sizeOf(usize) > 4) (block_length << 32) else maxInt(usize); +fn ChaChaWith64BitNonce(comptime rounds_nb: usize) type { + return struct { + /// Nonce length in bytes. + pub const nonce_length = 8; + /// Key length in bytes. + pub const key_length = 32; - // first partial big block - if (((@intCast(u64, maxInt(u32) - @truncate(u32, counter)) + 1) << 6) < in.len) { - ChaCha20Impl.chacha20Xor(out[cursor..big_block], in[cursor..big_block], k, c); - cursor = big_block - cursor; - c[1] += 1; - if (comptime @sizeOf(usize) > 4) { - // A big block is giant: 256 GiB, but we can avoid this limitation - var remaining_blocks: u32 = @intCast(u32, (in.len / big_block)); - var i: u32 = 0; - while (remaining_blocks > 0) : (remaining_blocks -= 1) { - ChaCha20Impl.chacha20Xor(out[cursor .. cursor + big_block], in[cursor .. cursor + big_block], k, c); - c[1] += 1; // upper 32-bit of counter, generic chacha20Xor() doesn't know about this. - cursor += big_block; + /// Add the output of the ChaCha20 stream cipher to `in` and stores the result into `out`. + /// WARNING: This function doesn't provide authenticated encryption. + /// Using the AEAD or one of the `box` versions is usually preferred. + pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void { + assert(in.len == out.len); + assert(in.len / 64 <= (1 << 64 - 1) - counter); + + var cursor: usize = 0; + const k = keyToWords(key); + var c: [4]u32 = undefined; + c[0] = @truncate(u32, counter); + c[1] = @truncate(u32, counter >> 32); + c[2] = mem.readIntLittle(u32, nonce[0..4]); + c[3] = mem.readIntLittle(u32, nonce[4..8]); + + const block_length = (1 << 6); + // The full block size is greater than the address space on a 32bit machine + const big_block = if (@sizeOf(usize) > 4) (block_length << 32) else maxInt(usize); + + // first partial big block + if (((@intCast(u64, maxInt(u32) - @truncate(u32, counter)) + 1) << 6) < in.len) { + ChaChaImpl(rounds_nb).chacha20Xor(out[cursor..big_block], in[cursor..big_block], k, c); + cursor = big_block - cursor; + c[1] += 1; + if (comptime @sizeOf(usize) > 4) { + // A big block is giant: 256 GiB, but we can avoid this limitation + var remaining_blocks: u32 = @intCast(u32, (in.len / big_block)); + var i: u32 = 0; + while (remaining_blocks > 0) : (remaining_blocks -= 1) { + ChaChaImpl(rounds_nb).chacha20Xor(out[cursor .. cursor + big_block], in[cursor .. cursor + big_block], k, c); + c[1] += 1; // upper 32-bit of counter, generic chacha20Xor() doesn't know about this. + cursor += big_block; + } } } + ChaChaImpl(rounds_nb).chacha20Xor(out[cursor..], in[cursor..], k, c); + } + }; +} + +fn XChaChaIETF(comptime rounds_nb: usize) type { + return struct { + /// Nonce length in bytes. + pub const nonce_length = 24; + /// Key length in bytes. + pub const key_length = 32; + + /// Add the output of the XChaCha20 stream cipher to `in` and stores the result into `out`. + /// WARNING: This function doesn't provide authenticated encryption. + /// Using the AEAD or one of the `box` versions is usually preferred. + pub fn xor(out: []u8, in: []const u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void { + const extended = extend(key, nonce, rounds_nb); + ChaChaIETF(rounds_nb).xor(out, in, counter, extended.key, extended.nonce); + } + }; +} + +fn ChaChaPoly1305(comptime rounds_nb: usize) type { + return struct { + pub const tag_length = 16; + pub const nonce_length = 12; + pub const key_length = 32; + + /// c: ciphertext: output buffer should be of size m.len + /// tag: authentication tag: output MAC + /// m: message + /// ad: Associated Data + /// npub: public nonce + /// k: private key + pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { + assert(c.len == m.len); + + var polyKey = [_]u8{0} ** 32; + ChaChaIETF(rounds_nb).xor(polyKey[0..], polyKey[0..], 0, k, npub); + + ChaChaIETF(rounds_nb).xor(c[0..m.len], m, 1, k, npub); + + var mac = Poly1305.init(polyKey[0..]); + mac.update(ad); + if (ad.len % 16 != 0) { + const zeros = [_]u8{0} ** 16; + const padding = 16 - (ad.len % 16); + mac.update(zeros[0..padding]); + } + mac.update(c[0..m.len]); + if (m.len % 16 != 0) { + const zeros = [_]u8{0} ** 16; + const padding = 16 - (m.len % 16); + mac.update(zeros[0..padding]); + } + var lens: [16]u8 = undefined; + mem.writeIntLittle(u64, lens[0..8], ad.len); + mem.writeIntLittle(u64, lens[8..16], m.len); + mac.update(lens[0..]); + mac.final(tag); } - ChaCha20Impl.chacha20Xor(out[cursor..], in[cursor..], k, c); + /// m: message: output buffer should be of size c.len + /// c: ciphertext + /// tag: authentication tag + /// ad: Associated Data + /// npub: public nonce + /// k: private key + /// NOTE: the check of the authentication tag is currently not done in constant time + pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void { + assert(c.len == m.len); + + var polyKey = [_]u8{0} ** 32; + ChaChaIETF(rounds_nb).xor(polyKey[0..], polyKey[0..], 0, k, npub); + + var mac = Poly1305.init(polyKey[0..]); + + mac.update(ad); + if (ad.len % 16 != 0) { + const zeros = [_]u8{0} ** 16; + const padding = 16 - (ad.len % 16); + mac.update(zeros[0..padding]); + } + mac.update(c); + if (c.len % 16 != 0) { + const zeros = [_]u8{0} ** 16; + const padding = 16 - (c.len % 16); + mac.update(zeros[0..padding]); + } + var lens: [16]u8 = undefined; + mem.writeIntLittle(u64, lens[0..8], ad.len); + mem.writeIntLittle(u64, lens[8..16], c.len); + mac.update(lens[0..]); + var computedTag: [16]u8 = undefined; + mac.final(computedTag[0..]); + + var acc: u8 = 0; + for (computedTag) |_, i| { + acc |= computedTag[i] ^ tag[i]; + } + if (acc != 0) { + return error.AuthenticationFailed; + } + ChaChaIETF(rounds_nb).xor(m[0..c.len], c, 1, k, npub); + } + }; +} + +fn XChaChaPoly1305(comptime rounds_nb: usize) type { + return struct { + pub const tag_length = 16; + pub const nonce_length = 24; + pub const key_length = 32; + + /// c: ciphertext: output buffer should be of size m.len + /// tag: authentication tag: output MAC + /// m: message + /// ad: Associated Data + /// npub: public nonce + /// k: private key + pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { + const extended = extend(k, npub, rounds_nb); + return ChaChaPoly1305(rounds_nb).encrypt(c, tag, m, ad, extended.nonce, extended.key); + } + + /// m: message: output buffer should be of size c.len + /// c: ciphertext + /// tag: authentication tag + /// ad: Associated Data + /// npub: public nonce + /// k: private key + pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void { + const extended = extend(k, npub, rounds_nb); + return ChaChaPoly1305(rounds_nb).decrypt(m, c, tag, ad, extended.nonce, extended.key); + } + }; +} + +test "chacha20 AEAD API" { + const aeads = [_]type{ ChaCha20Poly1305, XChaCha20Poly1305 }; + const m = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; + const ad = "Additional data"; + + inline for (aeads) |aead| { + const key = [_]u8{69} ** aead.key_length; + const nonce = [_]u8{42} ** aead.nonce_length; + var c: [m.len]u8 = undefined; + var tag: [aead.tag_length]u8 = undefined; + var out: [m.len]u8 = undefined; + + aead.encrypt(c[0..], tag[0..], m, ad, nonce, key); + try aead.decrypt(out[0..], c[0..], tag, ad[0..], nonce, key); + testing.expectEqualSlices(u8, out[0..], m); + c[0] += 1; + testing.expectError(error.AuthenticationFailed, aead.decrypt(out[0..], c[0..], tag, ad[0..], nonce, key)); } -}; +} // https://tools.ietf.org/html/rfc7539#section-2.4.2 test "crypto.chacha20 test vector sunscreen" { @@ -387,7 +629,7 @@ test "crypto.chacha20 test vector sunscreen" { 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42, 0x87, 0x4d, }; - const input = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; + const m = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; var result: [114]u8 = undefined; const key = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, @@ -401,13 +643,12 @@ test "crypto.chacha20 test vector sunscreen" { 0, 0, 0, 0, }; - ChaCha20IETF.xor(result[0..], input[0..], 1, key, nonce); + ChaCha20IETF.xor(result[0..], m[0..], 1, key, nonce); testing.expectEqualSlices(u8, &expected_result, &result); - // Chacha20 is self-reversing. - var plaintext: [114]u8 = undefined; - ChaCha20IETF.xor(plaintext[0..], result[0..], 1, key, nonce); - testing.expect(mem.order(u8, input, &plaintext) == .eq); + var m2: [114]u8 = undefined; + ChaCha20IETF.xor(m2[0..], result[0..], 1, key, nonce); + testing.expect(mem.order(u8, m, &m2) == .eq); } // https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7 @@ -422,7 +663,7 @@ test "crypto.chacha20 test vector 1" { 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86, }; - const input = [_]u8{ + const m = [_]u8{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -441,7 +682,7 @@ test "crypto.chacha20 test vector 1" { }; const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }; - ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce); + ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce); testing.expectEqualSlices(u8, &expected_result, &result); } @@ -456,7 +697,7 @@ test "crypto.chacha20 test vector 2" { 0x53, 0xd7, 0x92, 0xb1, 0xc4, 0x3f, 0xea, 0x81, 0x7e, 0x9a, 0xd2, 0x75, 0xae, 0x54, 0x69, 0x63, }; - const input = [_]u8{ + const m = [_]u8{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -475,7 +716,7 @@ test "crypto.chacha20 test vector 2" { }; const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }; - ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce); + ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce); testing.expectEqualSlices(u8, &expected_result, &result); } @@ -490,7 +731,7 @@ test "crypto.chacha20 test vector 3" { 0x52, 0x77, 0x06, 0x2e, 0xb7, 0xa0, 0x43, 0x3e, 0x44, 0x5f, 0x41, 0xe3, }; - const input = [_]u8{ + const m = [_]u8{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -509,7 +750,7 @@ test "crypto.chacha20 test vector 3" { }; const nonce = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 1 }; - ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce); + ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce); testing.expectEqualSlices(u8, &expected_result, &result); } @@ -524,7 +765,7 @@ test "crypto.chacha20 test vector 4" { 0x5d, 0xdc, 0x49, 0x7a, 0x0b, 0x46, 0x6e, 0x7d, 0x6b, 0xbd, 0xb0, 0x04, 0x1b, 0x2f, 0x58, 0x6b, }; - const input = [_]u8{ + const m = [_]u8{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -543,7 +784,7 @@ test "crypto.chacha20 test vector 4" { }; const nonce = [_]u8{ 1, 0, 0, 0, 0, 0, 0, 0 }; - ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce); + ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce); testing.expectEqualSlices(u8, &expected_result, &result); } @@ -585,7 +826,7 @@ test "crypto.chacha20 test vector 5" { 0x87, 0x46, 0xd4, 0x52, 0x4d, 0x38, 0x40, 0x7a, 0x6d, 0xeb, 0x3a, 0xb7, 0x8f, 0xab, 0x78, 0xc9, }; - const input = [_]u8{ + const m = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -615,147 +856,14 @@ test "crypto.chacha20 test vector 5" { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, }; - ChaCha20With64BitNonce.xor(result[0..], input[0..], 0, key, nonce); + ChaCha20With64BitNonce.xor(result[0..], m[0..], 0, key, nonce); testing.expectEqualSlices(u8, &expected_result, &result); } -pub const chacha20poly1305_tag_length = 16; - -fn chacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_length]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void { - assert(ciphertext.len == plaintext.len); - - // derive poly1305 key - var polyKey = [_]u8{0} ** 32; - ChaCha20IETF.xor(polyKey[0..], polyKey[0..], 0, key, nonce); - - // encrypt plaintext - ChaCha20IETF.xor(ciphertext[0..plaintext.len], plaintext, 1, key, nonce); - - // construct mac - var mac = Poly1305.init(polyKey[0..]); - mac.update(data); - if (data.len % 16 != 0) { - const zeros = [_]u8{0} ** 16; - const padding = 16 - (data.len % 16); - mac.update(zeros[0..padding]); - } - mac.update(ciphertext[0..plaintext.len]); - if (plaintext.len % 16 != 0) { - const zeros = [_]u8{0} ** 16; - const padding = 16 - (plaintext.len % 16); - mac.update(zeros[0..padding]); - } - var lens: [16]u8 = undefined; - mem.writeIntLittle(u64, lens[0..8], data.len); - mem.writeIntLittle(u64, lens[8..16], plaintext.len); - mac.update(lens[0..]); - mac.final(tag); -} - -fn chacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) void { - return chacha20poly1305SealDetached(ciphertextAndTag[0..plaintext.len], ciphertextAndTag[plaintext.len..][0..chacha20poly1305_tag_length], plaintext, data, key, nonce); -} - -/// Verifies and decrypts an authenticated message produced by chacha20poly1305SealDetached. -fn chacha20poly1305OpenDetached(dst: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_length]u8, data: []const u8, key: [32]u8, nonce: [12]u8) Error!void { - // split ciphertext and tag - assert(dst.len == ciphertext.len); - - // derive poly1305 key - var polyKey = [_]u8{0} ** 32; - ChaCha20IETF.xor(polyKey[0..], polyKey[0..], 0, key, nonce); - - // construct mac - var mac = Poly1305.init(polyKey[0..]); - - mac.update(data); - if (data.len % 16 != 0) { - const zeros = [_]u8{0} ** 16; - const padding = 16 - (data.len % 16); - mac.update(zeros[0..padding]); - } - mac.update(ciphertext); - if (ciphertext.len % 16 != 0) { - const zeros = [_]u8{0} ** 16; - const padding = 16 - (ciphertext.len % 16); - mac.update(zeros[0..padding]); - } - var lens: [16]u8 = undefined; - mem.writeIntLittle(u64, lens[0..8], data.len); - mem.writeIntLittle(u64, lens[8..16], ciphertext.len); - mac.update(lens[0..]); - var computedTag: [16]u8 = undefined; - mac.final(computedTag[0..]); - - // verify mac in constant time - // TODO: we can't currently guarantee that this will run in constant time. - // See https://github.com/ziglang/zig/issues/1776 - var acc: u8 = 0; - for (computedTag) |_, i| { - acc |= computedTag[i] ^ tag[i]; - } - if (acc != 0) { - return error.AuthenticationFailed; - } - - // decrypt ciphertext - ChaCha20IETF.xor(dst[0..ciphertext.len], ciphertext, 1, key, nonce); -} - -/// Verifies and decrypts an authenticated message produced by chacha20poly1305Seal. -fn chacha20poly1305Open(dst: []u8, ciphertextAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [12]u8) Error!void { - if (ciphertextAndTag.len < chacha20poly1305_tag_length) { - return error.AuthenticationFailed; - } - const ciphertextLen = ciphertextAndTag.len - chacha20poly1305_tag_length; - return try chacha20poly1305OpenDetached(dst, ciphertextAndTag[0..ciphertextLen], ciphertextAndTag[ciphertextLen..][0..chacha20poly1305_tag_length], data, key, nonce); -} - -fn extend(key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [12]u8 } { - var subnonce: [12]u8 = undefined; - mem.set(u8, subnonce[0..4], 0); - mem.copy(u8, subnonce[4..], nonce[16..24]); - return .{ - .key = ChaCha20Impl.hchacha20(nonce[0..16].*, key), - .nonce = subnonce, - }; -} - -pub const XChaCha20IETF = struct { - pub fn xor(out: []u8, in: []const u8, counter: u32, key: [32]u8, nonce: [24]u8) void { - const extended = extend(key, nonce); - ChaCha20IETF.xor(out, in, counter, extended.key, extended.nonce); - } -}; - -pub const xchacha20poly1305_tag_length = 16; - -fn xchacha20poly1305SealDetached(ciphertext: []u8, tag: *[chacha20poly1305_tag_length]u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void { - const extended = extend(key, nonce); - return chacha20poly1305SealDetached(ciphertext, tag, plaintext, data, extended.key, extended.nonce); -} - -fn xchacha20poly1305Seal(ciphertextAndTag: []u8, plaintext: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) void { - const extended = extend(key, nonce); - return chacha20poly1305Seal(ciphertextAndTag, plaintext, data, extended.key, extended.nonce); -} - -/// Verifies and decrypts an authenticated message produced by xchacha20poly1305SealDetached. -fn xchacha20poly1305OpenDetached(plaintext: []u8, ciphertext: []const u8, tag: *const [chacha20poly1305_tag_length]u8, data: []const u8, key: [32]u8, nonce: [24]u8) Error!void { - const extended = extend(key, nonce); - return try chacha20poly1305OpenDetached(plaintext, ciphertext, tag, data, extended.key, extended.nonce); -} - -/// Verifies and decrypts an authenticated message produced by xchacha20poly1305Seal. -fn xchacha20poly1305Open(ciphertextAndTag: []u8, msgAndTag: []const u8, data: []const u8, key: [32]u8, nonce: [24]u8) Error!void { - const extended = extend(key, nonce); - return try chacha20poly1305Open(ciphertextAndTag, msgAndTag, data, extended.key, extended.nonce); -} - test "seal" { { - const plaintext = ""; - const data = ""; + const m = ""; + const ad = ""; const key = [_]u8{ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, @@ -764,11 +872,11 @@ test "seal" { const exp_out = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 }; var out: [exp_out.len]u8 = undefined; - chacha20poly1305Seal(out[0..], plaintext, data, key, nonce); + ChaCha20Poly1305.encrypt(out[0..m.len], out[m.len..], m, ad, nonce, key); testing.expectEqualSlices(u8, exp_out[0..], out[0..]); } { - const plaintext = [_]u8{ + const m = [_]u8{ 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, @@ -778,7 +886,7 @@ test "seal" { 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, 0x74, 0x2e, }; - const data = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 }; + const ad = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 }; const key = [_]u8{ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, @@ -797,15 +905,15 @@ test "seal" { }; var out: [exp_out.len]u8 = undefined; - chacha20poly1305Seal(out[0..], plaintext[0..], data[0..], key, nonce); + ChaCha20Poly1305.encrypt(out[0..m.len], out[m.len..], m[0..], ad[0..], nonce, key); testing.expectEqualSlices(u8, exp_out[0..], out[0..]); } } test "open" { { - const ciphertext = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 }; - const data = ""; + const c = [_]u8{ 0xa0, 0x78, 0x4d, 0x7a, 0x47, 0x16, 0xf3, 0xfe, 0xb4, 0xf6, 0x4e, 0x7f, 0x4b, 0x39, 0xbf, 0x4 }; + const ad = ""; const key = [_]u8{ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, @@ -814,11 +922,11 @@ test "open" { const exp_out = ""; var out: [exp_out.len]u8 = undefined; - try chacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce); + try ChaCha20Poly1305.decrypt(out[0..], c[0..exp_out.len], c[exp_out.len..].*, ad[0..], nonce, key); testing.expectEqualSlices(u8, exp_out[0..], out[0..]); } { - const ciphertext = [_]u8{ + const c = [_]u8{ 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2, 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x8, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6, 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b, @@ -829,7 +937,7 @@ test "open" { 0x61, 0x16, 0x1a, 0xe1, 0xb, 0x59, 0x4f, 0x9, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x6, 0x91, }; - const data = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 }; + const ad = [_]u8{ 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 }; const key = [_]u8{ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, @@ -847,126 +955,45 @@ test "open" { }; var out: [exp_out.len]u8 = undefined; - try chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], key, nonce); + try ChaCha20Poly1305.decrypt(out[0..], c[0..exp_out.len], c[exp_out.len..].*, ad[0..], nonce, key); testing.expectEqualSlices(u8, exp_out[0..], out[0..]); // corrupting the ciphertext, data, key, or nonce should cause a failure - var bad_ciphertext = ciphertext; - bad_ciphertext[0] ^= 1; - testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], bad_ciphertext[0..], data[0..], key, nonce)); - var bad_data = data; - bad_data[0] ^= 1; - testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], bad_data[0..], key, nonce)); + var bad_c = c; + bad_c[0] ^= 1; + testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], bad_c[0..out.len], bad_c[out.len..].*, ad[0..], nonce, key)); + var bad_ad = ad; + bad_ad[0] ^= 1; + testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, bad_ad[0..], nonce, key)); var bad_key = key; bad_key[0] ^= 1; - testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], bad_key, nonce)); + testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, ad[0..], nonce, bad_key)); var bad_nonce = nonce; bad_nonce[0] ^= 1; - testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], ciphertext[0..], data[0..], key, bad_nonce)); - - // a short ciphertext should result in a different error - testing.expectError(error.AuthenticationFailed, chacha20poly1305Open(out[0..], "", data[0..], key, bad_nonce)); + testing.expectError(error.AuthenticationFailed, ChaCha20Poly1305.decrypt(out[0..], c[0..out.len], c[out.len..].*, ad[0..], bad_nonce, key)); } } test "crypto.xchacha20" { const key = [_]u8{69} ** 32; const nonce = [_]u8{42} ** 24; - const input = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; + const m = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; { - var ciphertext: [input.len]u8 = undefined; - XChaCha20IETF.xor(ciphertext[0..], input[0..], 0, key, nonce); - var buf: [2 * ciphertext.len]u8 = undefined; - testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&ciphertext)}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D"); + var c: [m.len]u8 = undefined; + XChaCha20IETF.xor(c[0..], m[0..], 0, key, nonce); + var buf: [2 * c.len]u8 = undefined; + testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&c)}), "E0A1BCF939654AFDBDC1746EC49832647C19D891F0D1A81FC0C1703B4514BDEA584B512F6908C2C5E9DD18D5CBC1805DE5803FE3B9CA5F193FB8359E91FAB0C3BB40309A292EB1CF49685C65C4A3ADF4F11DB0CD2B6B67FBC174BC2E860E8F769FD3565BBFAD1C845E05A0FED9BE167C240D"); } { - const data = "Additional data"; - var ciphertext: [input.len + xchacha20poly1305_tag_length]u8 = undefined; - xchacha20poly1305Seal(ciphertext[0..], input, data, key, nonce); - var out: [input.len]u8 = undefined; - try xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce); - var buf: [2 * ciphertext.len]u8 = undefined; - testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&ciphertext)}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234"); - testing.expectEqualSlices(u8, out[0..], input); - ciphertext[0] += 1; - testing.expectError(error.AuthenticationFailed, xchacha20poly1305Open(out[0..], ciphertext[0..], data, key, nonce)); - } -} - -pub const Chacha20Poly1305 = struct { - pub const tag_length = 16; - pub const nonce_length = 12; - pub const key_length = 32; - - /// c: ciphertext: output buffer should be of size m.len - /// tag: authentication tag: output MAC - /// m: message - /// ad: Associated Data - /// npub: public nonce - /// k: private key - pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { - assert(c.len == m.len); - return chacha20poly1305SealDetached(c, tag, m, ad, k, npub); - } - - /// m: message: output buffer should be of size c.len - /// c: ciphertext - /// tag: authentication tag - /// ad: Associated Data - /// npub: public nonce - /// k: private key - /// NOTE: the check of the authentication tag is currently not done in constant time - pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void { - assert(c.len == m.len); - return try chacha20poly1305OpenDetached(m, c, tag[0..], ad, k, npub); - } -}; - -pub const XChacha20Poly1305 = struct { - pub const tag_length = 16; - pub const nonce_length = 24; - pub const key_length = 32; - - /// c: ciphertext: output buffer should be of size m.len - /// tag: authentication tag: output MAC - /// m: message - /// ad: Associated Data - /// npub: public nonce - /// k: private key - pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { - assert(c.len == m.len); - return xchacha20poly1305SealDetached(c, tag, m, ad, k, npub); - } - - /// m: message: output buffer should be of size c.len - /// c: ciphertext - /// tag: authentication tag - /// ad: Associated Data - /// npub: public nonce - /// k: private key - /// NOTE: the check of the authentication tag is currently not done in constant time - pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) Error!void { - assert(c.len == m.len); - return try xchacha20poly1305OpenDetached(m, c, tag[0..], ad, k, npub); - } -}; - -test "chacha20 AEAD API" { - const aeads = [_]type{ Chacha20Poly1305, XChacha20Poly1305 }; - const input = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; - const data = "Additional data"; - - inline for (aeads) |aead| { - const key = [_]u8{69} ** aead.key_length; - const nonce = [_]u8{42} ** aead.nonce_length; - var ciphertext: [input.len]u8 = undefined; - var tag: [aead.tag_length]u8 = undefined; - var out: [input.len]u8 = undefined; - - aead.encrypt(ciphertext[0..], tag[0..], input, data, nonce, key); - try aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key); - testing.expectEqualSlices(u8, out[0..], input); - ciphertext[0] += 1; - testing.expectError(error.AuthenticationFailed, aead.decrypt(out[0..], ciphertext[0..], tag, data[0..], nonce, key)); + const ad = "Additional data"; + var c: [m.len + XChaCha20Poly1305.tag_length]u8 = undefined; + XChaCha20Poly1305.encrypt(c[0..m.len], c[m.len..], m, ad, nonce, key); + var out: [m.len]u8 = undefined; + try XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key); + var buf: [2 * c.len]u8 = undefined; + testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&c)}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234"); + testing.expectEqualSlices(u8, out[0..], m); + c[0] += 1; + testing.expectError(error.AuthenticationFailed, XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key)); } } From f52f23618d51367bfd498403e303090039f3b6b6 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Feb 2021 23:45:36 +0100 Subject: [PATCH 23/56] macho: start upstreaming zld --- src/Compilation.zig | 2 + src/link.zig | 2 + src/link/MachO.zig | 26 + src/link/MachO/Archive.zig | 247 ++++ src/link/MachO/Object.zig | 204 ++++ src/link/MachO/Zld.zig | 2301 +++++++++++++++++++++++++++++++++++ src/link/MachO/commands.zig | 5 + src/link/MachO/reloc.zig | 159 +++ src/main.zig | 4 + 9 files changed, 2950 insertions(+) create mode 100644 src/link/MachO/Archive.zig create mode 100644 src/link/MachO/Object.zig create mode 100644 src/link/MachO/Zld.zig create mode 100644 src/link/MachO/reloc.zig diff --git a/src/Compilation.zig b/src/Compilation.zig index 786280f9ef..e2ecc44fdb 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -447,6 +447,7 @@ pub const InitOptions = struct { want_lto: ?bool = null, use_llvm: ?bool = null, use_lld: ?bool = null, + use_zld: ?bool = null, use_clang: ?bool = null, rdynamic: bool = false, strip: bool = false, @@ -1020,6 +1021,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .link_mode = link_mode, .object_format = ofmt, .optimize_mode = options.optimize_mode, + .use_zld = options.use_zld orelse false, .use_lld = use_lld, .use_llvm = use_llvm, .system_linker_hack = darwin_options.system_linker_hack, diff --git a/src/link.zig b/src/link.zig index db3e973f84..6767b8d1b3 100644 --- a/src/link.zig +++ b/src/link.zig @@ -61,6 +61,8 @@ pub const Options = struct { /// Darwin-only. If this is true, `use_llvm` is true, and `is_native_os` is true, this link code will /// use system linker `ld` instead of the LLD. system_linker_hack: bool, + /// Experimental Zig linker. + use_zld: bool, link_libc: bool, link_libcpp: bool, function_sections: bool, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 349c2e4644..8f599a64a3 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -26,6 +26,7 @@ const target_util = @import("../target.zig"); const DebugSymbols = @import("MachO/DebugSymbols.zig"); const Trie = @import("MachO/Trie.zig"); const CodeSignature = @import("MachO/CodeSignature.zig"); +const Zld = @import("MachO/Zld.zig"); usingnamespace @import("MachO/commands.zig"); @@ -637,6 +638,31 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { var argv = std.ArrayList([]const u8).init(self.base.allocator); defer argv.deinit(); + if (true) { + // if (self.base.options.use_zld) { + var zld = Zld.init(self.base.allocator); + defer zld.deinit(); + zld.arch = target.cpu.arch; + + var input_files = std.ArrayList([]const u8).init(self.base.allocator); + defer input_files.deinit(); + // Positional arguments to the linker such as object files. + try input_files.appendSlice(self.base.options.objects); + for (comp.c_object_table.items()) |entry| { + try input_files.append(entry.key.status.success.object_path); + } + if (module_obj_path) |p| { + try input_files.append(p); + } + try input_files.append(comp.compiler_rt_static_lib.?.full_object_path); + // libc++ dep + if (self.base.options.link_libcpp) { + try input_files.append(comp.libcxxabi_static_lib.?.full_object_path); + try input_files.append(comp.libcxx_static_lib.?.full_object_path); + } + return zld.link(input_files.items, full_out_path); + } + // TODO https://github.com/ziglang/zig/issues/6971 // Note that there is no need to check if running natively since we do that already // when setting `system_linker_hack` in Compilation struct. diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig new file mode 100644 index 0000000000..bbb36c09fd --- /dev/null +++ b/src/link/MachO/Archive.zig @@ -0,0 +1,247 @@ +const Archive = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const fs = std.fs; +const log = std.log.scoped(.archive); +const macho = std.macho; +const mem = std.mem; + +const Allocator = mem.Allocator; +const Object = @import("Object.zig"); +const parseName = @import("Zld.zig").parseName; + +usingnamespace @import("commands.zig"); + +allocator: *Allocator, +file: fs.File, +header: ar_hdr, +name: []u8, + +objects: std.ArrayListUnmanaged(Object) = .{}, + +// Archive files start with the ARMAG identifying string. Then follows a +// `struct ar_hdr', and as many bytes of member file data as its `ar_size' +// member indicates, for each member file. +/// String that begins an archive file. +const ARMAG: *const [SARMAG:0]u8 = "!\n"; +/// Size of that string. +const SARMAG: u4 = 8; + +/// String in ar_fmag at the end of each header. +const ARFMAG: *const [2:0]u8 = "`\n"; + +const ar_hdr = extern struct { + /// Member file name, sometimes / terminated. + ar_name: [16]u8, + + /// File date, decimal seconds since Epoch. + ar_date: [12]u8, + + /// User ID, in ASCII format. + ar_uid: [6]u8, + + /// Group ID, in ASCII format. + ar_gid: [6]u8, + + /// File mode, in ASCII octal. + ar_mode: [8]u8, + + /// File size, in ASCII decimal. + ar_size: [10]u8, + + /// Always contains ARFMAG. + ar_fmag: [2]u8, + + const NameOrLength = union(enum) { + Name: []const u8, + Length: u64, + }; + pub fn nameOrLength(self: ar_hdr) !NameOrLength { + const value = getValue(&self.ar_name); + const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive; + const len = value.len; + if (slash_index == len - 1) { + // Name stored directly + return NameOrLength{ .Name = value }; + } else { + // Name follows the header directly and its length is encoded in + // the name field. + const length = try std.fmt.parseInt(u64, value[slash_index + 1 ..], 10); + return NameOrLength{ .Length = length }; + } + } + + pub fn size(self: ar_hdr) !u64 { + const value = getValue(&self.ar_size); + return std.fmt.parseInt(u64, value, 10); + } + + fn getValue(raw: []const u8) []const u8 { + return mem.trimRight(u8, raw, &[_]u8{@as(u8, 0x20)}); + } +}; + +pub fn deinit(self: *Archive) void { + self.allocator.free(self.name); + for (self.objects.items) |*object| { + object.deinit(); + } + self.objects.deinit(self.allocator); + self.file.close(); +} + +/// Caller owns the returned Archive instance and is responsible for calling +/// `deinit` to free allocated memory. +pub fn initFromFile(allocator: *Allocator, arch: std.Target.Cpu.Arch, ar_name: []const u8, file: fs.File) !Archive { + var reader = file.reader(); + var magic = try readMagic(allocator, reader); + defer allocator.free(magic); + + if (!mem.eql(u8, magic, ARMAG)) { + // Reset file cursor. + try file.seekTo(0); + return error.NotArchive; + } + + const header = try reader.readStruct(ar_hdr); + + if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) + return error.MalformedArchive; + + var embedded_name = try getName(allocator, header, reader); + log.debug("parsing archive '{s}' at '{s}'", .{ embedded_name, ar_name }); + defer allocator.free(embedded_name); + + var name = try allocator.dupe(u8, ar_name); + var self = Archive{ + .allocator = allocator, + .file = file, + .header = header, + .name = name, + }; + + var object_offsets = try self.readTableOfContents(reader); + defer self.allocator.free(object_offsets); + + var i: usize = 1; + while (i < object_offsets.len) : (i += 1) { + const offset = object_offsets[i]; + try reader.context.seekTo(offset); + try self.readObject(arch, ar_name, reader); + } + + return self; +} + +fn readTableOfContents(self: *Archive, reader: anytype) ![]u32 { + const symtab_size = try reader.readIntLittle(u32); + var symtab = try self.allocator.alloc(u8, symtab_size); + defer self.allocator.free(symtab); + try reader.readNoEof(symtab); + + const strtab_size = try reader.readIntLittle(u32); + var strtab = try self.allocator.alloc(u8, strtab_size); + defer self.allocator.free(strtab); + try reader.readNoEof(strtab); + + var symtab_stream = std.io.fixedBufferStream(symtab); + var symtab_reader = symtab_stream.reader(); + + var object_offsets = std.ArrayList(u32).init(self.allocator); + try object_offsets.append(0); + var last: usize = 0; + + while (true) { + const n_strx = symtab_reader.readIntLittle(u32) catch |err| switch (err) { + error.EndOfStream => break, + else => |e| return e, + }; + const object_offset = try symtab_reader.readIntLittle(u32); + + // TODO Store the table of contents for later reuse. + + // Here, we assume that symbols are NOT sorted in any way, and + // they point to objects in sequence. + if (object_offsets.items[last] != object_offset) { + try object_offsets.append(object_offset); + last += 1; + } + } + + return object_offsets.toOwnedSlice(); +} + +fn readObject(self: *Archive, arch: std.Target.Cpu.Arch, ar_name: []const u8, reader: anytype) !void { + const object_header = try reader.readStruct(ar_hdr); + + if (!mem.eql(u8, &object_header.ar_fmag, ARFMAG)) + return error.MalformedArchive; + + var object_name = try getName(self.allocator, object_header, reader); + log.debug("extracting object '{s}' from archive '{s}'", .{ object_name, self.name }); + + const offset = @intCast(u32, try reader.context.getPos()); + const header = try reader.readStruct(macho.mach_header_64); + + const this_arch: std.Target.Cpu.Arch = switch (header.cputype) { + macho.CPU_TYPE_ARM64 => .aarch64, + macho.CPU_TYPE_X86_64 => .x86_64, + else => |value| { + log.err("unsupported cpu architecture 0x{x}", .{value}); + return error.UnsupportedCpuArchitecture; + }, + }; + if (this_arch != arch) { + log.err("mismatched cpu architecture: found {s}, expected {s}", .{ this_arch, arch }); + return error.MismatchedCpuArchitecture; + } + + // TODO Implement std.fs.File.clone() or similar. + var new_file = try fs.cwd().openFile(ar_name, .{}); + var object = Object{ + .allocator = self.allocator, + .name = object_name, + .file = new_file, + .header = header, + }; + + try object.readLoadCommands(reader, .{ .offset = offset }); + try object.readSymtab(); + try object.readStrtab(); + + log.debug("\n\n", .{}); + log.debug("{s} defines symbols", .{object.name}); + for (object.symtab.items) |sym| { + const symname = object.getString(sym.n_strx); + log.debug("'{s}': {}", .{ symname, sym }); + } + + try self.objects.append(self.allocator, object); +} + +fn readMagic(allocator: *Allocator, reader: anytype) ![]u8 { + var magic = std.ArrayList(u8).init(allocator); + try magic.ensureCapacity(SARMAG); + var i: usize = 0; + while (i < SARMAG) : (i += 1) { + const next = try reader.readByte(); + magic.appendAssumeCapacity(next); + } + return magic.toOwnedSlice(); +} + +fn getName(allocator: *Allocator, header: ar_hdr, reader: anytype) ![]u8 { + const name_or_length = try header.nameOrLength(); + var name: []u8 = undefined; + switch (name_or_length) { + .Name => |n| { + name = try allocator.dupe(u8, n); + }, + .Length => |len| { + name = try allocator.alloc(u8, len); + try reader.readNoEof(name); + }, + } + return name; +} diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig new file mode 100644 index 0000000000..c79869a5a7 --- /dev/null +++ b/src/link/MachO/Object.zig @@ -0,0 +1,204 @@ +const Object = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const fs = std.fs; +const log = std.log.scoped(.object); +const macho = std.macho; +const mem = std.mem; + +const Allocator = mem.Allocator; +const parseName = @import("Zld.zig").parseName; + +usingnamespace @import("commands.zig"); + +allocator: *Allocator, +file: fs.File, +name: []u8, + +header: macho.mach_header_64, + +load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, + +segment_cmd_index: ?u16 = null, +symtab_cmd_index: ?u16 = null, +dysymtab_cmd_index: ?u16 = null, +build_version_cmd_index: ?u16 = null, + +text_section_index: ?u16 = null, + +dwarf_debug_info_index: ?u16 = null, +dwarf_debug_abbrev_index: ?u16 = null, +dwarf_debug_str_index: ?u16 = null, +dwarf_debug_line_index: ?u16 = null, +dwarf_debug_ranges_index: ?u16 = null, + +symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{}, +strtab: std.ArrayListUnmanaged(u8) = .{}, + +directory: std.AutoHashMapUnmanaged(DirectoryKey, u16) = .{}, + +pub const DirectoryKey = struct { + segname: [16]u8, + sectname: [16]u8, +}; + +pub fn deinit(self: *Object) void { + for (self.load_commands.items) |*lc| { + lc.deinit(self.allocator); + } + self.load_commands.deinit(self.allocator); + self.symtab.deinit(self.allocator); + self.strtab.deinit(self.allocator); + self.directory.deinit(self.allocator); + self.allocator.free(self.name); + self.file.close(); +} + +/// Caller owns the returned Object instance and is responsible for calling +/// `deinit` to free allocated memory. +pub fn initFromFile(allocator: *Allocator, arch: std.Target.Cpu.Arch, name: []const u8, file: fs.File) !Object { + var reader = file.reader(); + const header = try reader.readStruct(macho.mach_header_64); + + if (header.filetype != macho.MH_OBJECT) { + // Reset file cursor. + try file.seekTo(0); + return error.NotObject; + } + + const this_arch: std.Target.Cpu.Arch = switch (header.cputype) { + macho.CPU_TYPE_ARM64 => .aarch64, + macho.CPU_TYPE_X86_64 => .x86_64, + else => |value| { + log.err("unsupported cpu architecture 0x{x}", .{value}); + return error.UnsupportedCpuArchitecture; + }, + }; + if (this_arch != arch) { + log.err("mismatched cpu architecture: found {s}, expected {s}", .{ this_arch, arch }); + return error.MismatchedCpuArchitecture; + } + + var self = Object{ + .allocator = allocator, + .name = try allocator.dupe(u8, name), + .file = file, + .header = header, + }; + + try self.readLoadCommands(reader, .{}); + try self.readSymtab(); + try self.readStrtab(); + + log.debug("\n\n", .{}); + log.debug("{s} defines symbols", .{self.name}); + for (self.symtab.items) |sym| { + const symname = self.getString(sym.n_strx); + log.debug("'{s}': {}", .{ symname, sym }); + } + + return self; +} + +pub const ReadOffset = struct { + offset: ?u32 = null, +}; + +pub fn readLoadCommands(self: *Object, reader: anytype, offset: ReadOffset) !void { + const offset_mod = offset.offset orelse 0; + try self.load_commands.ensureCapacity(self.allocator, self.header.ncmds); + + var i: u16 = 0; + while (i < self.header.ncmds) : (i += 1) { + var cmd = try LoadCommand.read(self.allocator, reader); + switch (cmd.cmd()) { + macho.LC_SEGMENT_64 => { + self.segment_cmd_index = i; + var seg = cmd.Segment; + for (seg.sections.items) |*sect, j| { + const index = @intCast(u16, j); + const segname = parseName(§.segname); + const sectname = parseName(§.sectname); + if (mem.eql(u8, segname, "__DWARF")) { + if (mem.eql(u8, sectname, "__debug_info")) { + self.dwarf_debug_info_index = index; + } else if (mem.eql(u8, sectname, "__debug_abbrev")) { + self.dwarf_debug_abbrev_index = index; + } else if (mem.eql(u8, sectname, "__debug_str")) { + self.dwarf_debug_str_index = index; + } else if (mem.eql(u8, sectname, "__debug_line")) { + self.dwarf_debug_line_index = index; + } else if (mem.eql(u8, sectname, "__debug_ranges")) { + self.dwarf_debug_ranges_index = index; + } + } else if (mem.eql(u8, segname, "__TEXT")) { + if (mem.eql(u8, sectname, "__text")) { + self.text_section_index = index; + } + } + + try self.directory.putNoClobber(self.allocator, .{ + .segname = sect.segname, + .sectname = sect.sectname, + }, index); + + sect.offset += offset_mod; + if (sect.reloff > 0) + sect.reloff += offset_mod; + } + + seg.inner.fileoff += offset_mod; + }, + macho.LC_SYMTAB => { + self.symtab_cmd_index = i; + cmd.Symtab.symoff += offset_mod; + cmd.Symtab.stroff += offset_mod; + }, + macho.LC_DYSYMTAB => { + self.dysymtab_cmd_index = i; + }, + macho.LC_BUILD_VERSION => { + self.build_version_cmd_index = i; + }, + else => { + log.info("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); + }, + } + self.load_commands.appendAssumeCapacity(cmd); + } +} + +pub fn readSymtab(self: *Object) !void { + const symtab_cmd = self.load_commands.items[self.symtab_cmd_index.?].Symtab; + var buffer = try self.allocator.alloc(u8, @sizeOf(macho.nlist_64) * symtab_cmd.nsyms); + defer self.allocator.free(buffer); + _ = try self.file.preadAll(buffer, symtab_cmd.symoff); + try self.symtab.ensureCapacity(self.allocator, symtab_cmd.nsyms); + // TODO this align case should not be needed. + // Probably a bug in stage1. + const slice = @alignCast(@alignOf(macho.nlist_64), mem.bytesAsSlice(macho.nlist_64, buffer)); + self.symtab.appendSliceAssumeCapacity(slice); +} + +pub fn readStrtab(self: *Object) !void { + const symtab_cmd = self.load_commands.items[self.symtab_cmd_index.?].Symtab; + var buffer = try self.allocator.alloc(u8, symtab_cmd.strsize); + defer self.allocator.free(buffer); + _ = try self.file.preadAll(buffer, symtab_cmd.stroff); + try self.strtab.ensureCapacity(self.allocator, symtab_cmd.strsize); + self.strtab.appendSliceAssumeCapacity(buffer); +} + +pub fn getString(self: *const Object, str_off: u32) []const u8 { + assert(str_off < self.strtab.items.len); + return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + str_off)); +} + +pub fn readSection(self: Object, allocator: *Allocator, index: u16) ![]u8 { + const seg = self.load_commands.items[self.segment_cmd_index.?].Segment; + const sect = seg.sections.items[index]; + var buffer = try allocator.alloc(u8, sect.size); + _ = try self.file.preadAll(buffer, sect.offset); + return buffer; +} diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig new file mode 100644 index 0000000000..47614e37b9 --- /dev/null +++ b/src/link/MachO/Zld.zig @@ -0,0 +1,2301 @@ +const Zld = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const dwarf = std.dwarf; +const leb = std.leb; +const mem = std.mem; +const meta = std.meta; +const fs = std.fs; +const macho = std.macho; +const math = std.math; +const log = std.log.scoped(.zld); + +const Allocator = mem.Allocator; +const CodeSignature = @import("CodeSignature.zig"); +const Archive = @import("Archive.zig"); +const Object = @import("Object.zig"); +const Trie = @import("Trie.zig"); + +usingnamespace @import("commands.zig"); +usingnamespace @import("bind.zig"); +usingnamespace @import("reloc.zig"); + +allocator: *Allocator, + +arch: ?std.Target.Cpu.Arch = null, +page_size: ?u16 = null, +file: ?fs.File = null, +out_path: ?[]const u8 = null, + +// TODO Eventually, we will want to keep track of the archives themselves to be able to exclude objects +// contained within from landing in the final artifact. For now however, since we don't optimise the binary +// at all, we just move all objects from the archives into the final artifact. +objects: std.ArrayListUnmanaged(Object) = .{}, + +load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, + +pagezero_segment_cmd_index: ?u16 = null, +text_segment_cmd_index: ?u16 = null, +data_segment_cmd_index: ?u16 = null, +linkedit_segment_cmd_index: ?u16 = null, +dyld_info_cmd_index: ?u16 = null, +symtab_cmd_index: ?u16 = null, +dysymtab_cmd_index: ?u16 = null, +dylinker_cmd_index: ?u16 = null, +libsystem_cmd_index: ?u16 = null, +data_in_code_cmd_index: ?u16 = null, +function_starts_cmd_index: ?u16 = null, +main_cmd_index: ?u16 = null, +version_min_cmd_index: ?u16 = null, +source_version_cmd_index: ?u16 = null, +uuid_cmd_index: ?u16 = null, +code_signature_cmd_index: ?u16 = null, + +text_section_index: ?u16 = null, +stubs_section_index: ?u16 = null, +stub_helper_section_index: ?u16 = null, +got_section_index: ?u16 = null, +tlv_section_index: ?u16 = null, +la_symbol_ptr_section_index: ?u16 = null, +data_section_index: ?u16 = null, + +locals: std.StringArrayHashMapUnmanaged(macho.nlist_64) = .{}, +exports: std.StringArrayHashMapUnmanaged(macho.nlist_64) = .{}, +nonlazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, +lazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, +threadlocal_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, +local_rebases: std.ArrayListUnmanaged(Pointer) = .{}, + +strtab: std.ArrayListUnmanaged(u8) = .{}, + +stub_helper_stubs_start_off: ?u64 = null, + +segments_directory: std.AutoHashMapUnmanaged([16]u8, u16) = .{}, +directory: std.AutoHashMapUnmanaged(DirectoryKey, DirectoryEntry) = .{}, + +const DirectoryKey = struct { + segname: [16]u8, + sectname: [16]u8, +}; + +const DirectoryEntry = struct { + seg_index: u16, + sect_index: u16, +}; + +const DebugInfo = struct { + inner: dwarf.DwarfInfo, + debug_info: []u8, + debug_abbrev: []u8, + debug_str: []u8, + debug_line: []u8, + debug_ranges: []u8, + + pub fn parseFromObject(allocator: *Allocator, object: Object) !?DebugInfo { + var debug_info = blk: { + const index = object.dwarf_debug_info_index orelse return null; + break :blk try object.readSection(allocator, index); + }; + var debug_abbrev = blk: { + const index = object.dwarf_debug_abbrev_index orelse return null; + break :blk try object.readSection(allocator, index); + }; + var debug_str = blk: { + const index = object.dwarf_debug_str_index orelse return null; + break :blk try object.readSection(allocator, index); + }; + var debug_line = blk: { + const index = object.dwarf_debug_line_index orelse return null; + break :blk try object.readSection(allocator, index); + }; + var debug_ranges = blk: { + if (object.dwarf_debug_ranges_index) |ind| { + break :blk try object.readSection(allocator, ind); + } + break :blk try allocator.alloc(u8, 0); + }; + + var inner: dwarf.DwarfInfo = .{ + .endian = .Little, + .debug_info = debug_info, + .debug_abbrev = debug_abbrev, + .debug_str = debug_str, + .debug_line = debug_line, + .debug_ranges = debug_ranges, + }; + try dwarf.openDwarfDebugInfo(&inner, allocator); + + return DebugInfo{ + .inner = inner, + .debug_info = debug_info, + .debug_abbrev = debug_abbrev, + .debug_str = debug_str, + .debug_line = debug_line, + .debug_ranges = debug_ranges, + }; + } + + pub fn deinit(self: *DebugInfo, allocator: *Allocator) void { + allocator.free(self.debug_info); + allocator.free(self.debug_abbrev); + allocator.free(self.debug_str); + allocator.free(self.debug_line); + allocator.free(self.debug_ranges); + self.inner.abbrev_table_list.deinit(); + self.inner.compile_unit_list.deinit(); + self.inner.func_list.deinit(); + } +}; + +pub const Import = struct { + /// MachO symbol table entry. + symbol: macho.nlist_64, + + /// Id of the dynamic library where the specified entries can be found. + dylib_ordinal: i64, + + /// Index of this import within the import list. + index: u32, +}; + +/// Default path to dyld +/// TODO instead of hardcoding it, we should probably look through some env vars and search paths +/// instead but this will do for now. +const DEFAULT_DYLD_PATH: [*:0]const u8 = "/usr/lib/dyld"; + +/// Default lib search path +/// TODO instead of hardcoding it, we should probably look through some env vars and search paths +/// instead but this will do for now. +const DEFAULT_LIB_SEARCH_PATH: []const u8 = "/usr/lib"; + +const LIB_SYSTEM_NAME: [*:0]const u8 = "System"; +/// TODO we should search for libSystem and fail if it doesn't exist, instead of hardcoding it +const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.dylib"; + +pub fn init(allocator: *Allocator) Zld { + return .{ .allocator = allocator }; +} + +pub fn deinit(self: *Zld) void { + self.strtab.deinit(self.allocator); + self.local_rebases.deinit(self.allocator); + for (self.lazy_imports.items()) |*entry| { + self.allocator.free(entry.key); + } + self.lazy_imports.deinit(self.allocator); + for (self.threadlocal_imports.items()) |*entry| { + self.allocator.free(entry.key); + } + self.threadlocal_imports.deinit(self.allocator); + for (self.nonlazy_imports.items()) |*entry| { + self.allocator.free(entry.key); + } + self.nonlazy_imports.deinit(self.allocator); + for (self.exports.items()) |*entry| { + self.allocator.free(entry.key); + } + self.exports.deinit(self.allocator); + for (self.locals.items()) |*entry| { + self.allocator.free(entry.key); + } + self.locals.deinit(self.allocator); + for (self.objects.items) |*object| { + object.deinit(); + } + self.objects.deinit(self.allocator); + for (self.load_commands.items) |*lc| { + lc.deinit(self.allocator); + } + self.load_commands.deinit(self.allocator); + self.segments_directory.deinit(self.allocator); + self.directory.deinit(self.allocator); + if (self.file) |*f| f.close(); +} + +pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8) !void { + if (files.len == 0) return error.NoInputFiles; + if (out_path.len == 0) return error.EmptyOutputPath; + + if (self.arch == null) { + // Try inferring the arch from the object files. + self.arch = blk: { + const file = try fs.cwd().openFile(files[0], .{}); + defer file.close(); + var reader = file.reader(); + const header = try reader.readStruct(macho.mach_header_64); + const arch: std.Target.Cpu.Arch = switch (header.cputype) { + macho.CPU_TYPE_X86_64 => .x86_64, + macho.CPU_TYPE_ARM64 => .aarch64, + else => |value| { + log.err("unsupported cpu architecture 0x{x}", .{value}); + return error.UnsupportedCpuArchitecture; + }, + }; + break :blk arch; + }; + } + + self.page_size = switch (self.arch.?) { + .aarch64 => 0x4000, + .x86_64 => 0x1000, + else => unreachable, + }; + self.out_path = out_path; + self.file = try fs.cwd().createFile(out_path, .{ + .truncate = true, + .read = true, + .mode = if (std.Target.current.os.tag == .windows) 0 else 0o777, + }); + + try self.populateMetadata(); + try self.parseInputFiles(files); + try self.resolveImports(); + self.allocateTextSegment(); + self.allocateDataSegment(); + self.allocateLinkeditSegment(); + try self.writeStubHelperCommon(); + try self.resolveSymbols(); + try self.doRelocs(); + try self.flush(); +} + +fn parseInputFiles(self: *Zld, files: []const []const u8) !void { + for (files) |file_name| { + const file = try fs.cwd().openFile(file_name, .{}); + + try_object: { + var object = Object.initFromFile(self.allocator, self.arch.?, file_name, file) catch |err| switch (err) { + error.NotObject => break :try_object, + else => |e| return e, + }; + const index = self.objects.items.len; + try self.objects.append(self.allocator, object); + const p_object = &self.objects.items[index]; + try self.parseObjectFile(p_object); + continue; + } + + try_archive: { + var archive = Archive.initFromFile(self.allocator, self.arch.?, file_name, file) catch |err| switch (err) { + error.NotArchive => break :try_archive, + else => |e| return e, + }; + defer archive.deinit(); + while (archive.objects.popOrNull()) |object| { + const index = self.objects.items.len; + try self.objects.append(self.allocator, object); + const p_object = &self.objects.items[index]; + try self.parseObjectFile(p_object); + } + continue; + } + + log.err("unexpected file type: expected object '.o' or archive '.a': {s}", .{file_name}); + return error.UnexpectedInputFileType; + } +} + +fn parseObjectFile(self: *Zld, object: *const Object) !void { + const seg_cmd = object.load_commands.items[object.segment_cmd_index.?].Segment; + for (seg_cmd.sections.items) |sect| { + const sectname = parseName(§.sectname); + + const seg_index = self.segments_directory.get(sect.segname) orelse { + log.info("segname {s} not found in the output artifact", .{sect.segname}); + continue; + }; + const seg = &self.load_commands.items[seg_index].Segment; + const res = try self.directory.getOrPut(self.allocator, .{ + .segname = sect.segname, + .sectname = sect.sectname, + }); + if (!res.found_existing) { + const sect_index = @intCast(u16, seg.sections.items.len); + if (mem.eql(u8, sectname, "__thread_vars")) { + self.tlv_section_index = sect_index; + } + try seg.append(self.allocator, .{ + .sectname = makeStaticString(§.sectname), + .segname = makeStaticString(§.segname), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = sect.@"align", + .reloff = 0, + .nreloc = 0, + .flags = sect.flags, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + res.entry.value = .{ + .seg_index = seg_index, + .sect_index = sect_index, + }; + } + const dest_sect = &seg.sections.items[res.entry.value.sect_index]; + dest_sect.size += sect.size; + seg.inner.filesize += sect.size; + } +} + +fn resolveImports(self: *Zld) !void { + var imports = std.StringArrayHashMap(bool).init(self.allocator); + defer imports.deinit(); + + for (self.objects.items) |object| { + for (object.symtab.items) |sym| { + if (isLocal(&sym)) continue; + + const name = object.getString(sym.n_strx); + const res = try imports.getOrPut(name); + if (isExport(&sym)) { + res.entry.value = false; + continue; + } + if (res.found_existing and !res.entry.value) + continue; + res.entry.value = true; + } + } + + for (imports.items()) |entry| { + if (!entry.value) continue; + + const sym_name = entry.key; + const n_strx = try self.makeString(sym_name); + var new_sym: macho.nlist_64 = .{ + .n_strx = n_strx, + .n_type = macho.N_UNDF | macho.N_EXT, + .n_value = 0, + .n_desc = macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | macho.N_SYMBOL_RESOLVER, + .n_sect = 0, + }; + var key = try self.allocator.dupe(u8, sym_name); + // TODO handle symbol resolution from non-libc dylibs. + const dylib_ordinal = 1; + + // TODO need to rework this. Perhaps should create a set of all possible libc + // symbols which are expected to be nonlazy? + if (mem.eql(u8, sym_name, "___stdoutp") or + mem.eql(u8, sym_name, "___stderrp") or + mem.eql(u8, sym_name, "___stdinp") or + mem.eql(u8, sym_name, "___stack_chk_guard") or + mem.eql(u8, sym_name, "_environ")) + { + log.debug("writing nonlazy symbol '{s}'", .{sym_name}); + const index = @intCast(u32, self.nonlazy_imports.items().len); + try self.nonlazy_imports.putNoClobber(self.allocator, key, .{ + .symbol = new_sym, + .dylib_ordinal = dylib_ordinal, + .index = index, + }); + } else if (mem.eql(u8, sym_name, "__tlv_bootstrap")) { + log.debug("writing threadlocal symbol '{s}'", .{sym_name}); + const index = @intCast(u32, self.threadlocal_imports.items().len); + try self.threadlocal_imports.putNoClobber(self.allocator, key, .{ + .symbol = new_sym, + .dylib_ordinal = dylib_ordinal, + .index = index, + }); + } else { + log.debug("writing lazy symbol '{s}'", .{sym_name}); + const index = @intCast(u32, self.lazy_imports.items().len); + try self.lazy_imports.putNoClobber(self.allocator, key, .{ + .symbol = new_sym, + .dylib_ordinal = dylib_ordinal, + .index = index, + }); + } + } + + const n_strx = try self.makeString("dyld_stub_binder"); + const name = try self.allocator.dupe(u8, "dyld_stub_binder"); + log.debug("writing nonlazy symbol 'dyld_stub_binder'", .{}); + const index = @intCast(u32, self.nonlazy_imports.items().len); + try self.nonlazy_imports.putNoClobber(self.allocator, name, .{ + .symbol = .{ + .n_strx = n_strx, + .n_type = std.macho.N_UNDF | std.macho.N_EXT, + .n_sect = 0, + .n_desc = std.macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | std.macho.N_SYMBOL_RESOLVER, + .n_value = 0, + }, + .dylib_ordinal = 1, + .index = index, + }); +} + +fn allocateTextSegment(self: *Zld) void { + const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const nexterns = @intCast(u32, self.lazy_imports.items().len); + + // Set stubs and stub_helper sizes + const stubs = &seg.sections.items[self.stubs_section_index.?]; + const stub_helper = &seg.sections.items[self.stub_helper_section_index.?]; + stubs.size += nexterns * stubs.reserved2; + + const stub_size: u4 = switch (self.arch.?) { + .x86_64 => 10, + .aarch64 => 3 * @sizeOf(u32), + else => unreachable, + }; + stub_helper.size += nexterns * stub_size; + + var sizeofcmds: u64 = 0; + for (self.load_commands.items) |lc| { + sizeofcmds += lc.cmdsize(); + } + + self.allocateSegment(self.text_segment_cmd_index.?, 0, sizeofcmds, true); +} + +fn allocateDataSegment(self: *Zld) void { + const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const nonlazy = @intCast(u32, self.nonlazy_imports.items().len); + const lazy = @intCast(u32, self.lazy_imports.items().len); + + // Set got size + const got = &seg.sections.items[self.got_section_index.?]; + got.size += nonlazy * @sizeOf(u64); + + // Set la_symbol_ptr and data size + const la_symbol_ptr = &seg.sections.items[self.la_symbol_ptr_section_index.?]; + const data = &seg.sections.items[self.data_section_index.?]; + la_symbol_ptr.size += lazy * @sizeOf(u64); + data.size += @sizeOf(u64); // TODO when do we need more? + + const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const offset = text_seg.inner.fileoff + text_seg.inner.filesize; + self.allocateSegment(self.data_segment_cmd_index.?, offset, 0, false); +} + +fn allocateLinkeditSegment(self: *Zld) void { + const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const offset = data_seg.inner.fileoff + data_seg.inner.filesize; + self.allocateSegment(self.linkedit_segment_cmd_index.?, offset, 0, false); +} + +fn allocateSegment(self: *Zld, index: u16, offset: u64, start: u64, reverse: bool) void { + const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].Segment.inner.vmsize; + const seg = &self.load_commands.items[index].Segment; + + // Calculate segment size + var total_size = start; + for (seg.sections.items) |sect| { + total_size += sect.size; + } + const aligned_size = mem.alignForwardGeneric(u64, total_size, self.page_size.?); + seg.inner.vmaddr = base_vmaddr + offset; + seg.inner.vmsize = aligned_size; + seg.inner.fileoff = offset; + seg.inner.filesize = aligned_size; + + // Allocate section offsets + if (reverse) { + var end_off: u64 = seg.inner.fileoff + seg.inner.filesize; + var count: usize = seg.sections.items.len; + while (count > 0) : (count -= 1) { + const sec = &seg.sections.items[count - 1]; + end_off -= mem.alignForwardGeneric(u64, sec.size, @sizeOf(u32)); // TODO Should we always align to 4? + sec.offset = @intCast(u32, end_off); + sec.addr = base_vmaddr + end_off; + } + } else { + var next_off: u64 = seg.inner.fileoff; + for (seg.sections.items) |*sect| { + sect.offset = @intCast(u32, next_off); + sect.addr = base_vmaddr + next_off; + next_off += mem.alignForwardGeneric(u64, sect.size, @sizeOf(u32)); // TODO Should we always align to 4? + } + } +} + +fn writeStubHelperCommon(self: *Zld) !void { + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?]; + const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const data = &data_segment.sections.items[self.data_section_index.?]; + const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?]; + const got = &data_segment.sections.items[self.got_section_index.?]; + + self.stub_helper_stubs_start_off = blk: { + switch (self.arch.?) { + .x86_64 => { + const code_size = 15; + var code: [code_size]u8 = undefined; + // lea %r11, [rip + disp] + code[0] = 0x4c; + code[1] = 0x8d; + code[2] = 0x1d; + { + const target_addr = data.addr + data.size - @sizeOf(u64); + const displacement = try math.cast(u32, target_addr - stub_helper.addr - 7); + mem.writeIntLittle(u32, code[3..7], displacement); + } + // push %r11 + code[7] = 0x41; + code[8] = 0x53; + // jmp [rip + disp] + code[9] = 0xff; + code[10] = 0x25; + { + const dyld_stub_binder = self.nonlazy_imports.get("dyld_stub_binder").?; + const addr = (got.addr + dyld_stub_binder.index * @sizeOf(u64)); + const displacement = try math.cast(u32, addr - stub_helper.addr - code_size); + mem.writeIntLittle(u32, code[11..], displacement); + } + try self.file.?.pwriteAll(&code, stub_helper.offset); + break :blk stub_helper.offset + code_size; + }, + .aarch64 => { + var code: [4 * @sizeOf(u32)]u8 = undefined; + { + const target_addr = data.addr + data.size - @sizeOf(u64); + const displacement = @bitCast(u21, try math.cast(i21, target_addr - stub_helper.addr)); + // adr x17, disp + mem.writeIntLittle(u32, code[0..4], Arm64.adr(17, displacement).toU32()); + } + // stp x16, x17, [sp, #-16]! + code[4] = 0xf0; + code[5] = 0x47; + code[6] = 0xbf; + code[7] = 0xa9; + { + const dyld_stub_binder = self.nonlazy_imports.get("dyld_stub_binder").?; + const addr = (got.addr + dyld_stub_binder.index * @sizeOf(u64)); + const displacement = try math.divExact(u64, addr - stub_helper.addr - 2 * @sizeOf(u32), 4); + const literal = try math.cast(u19, displacement); + // ldr x16, label + mem.writeIntLittle(u32, code[8..12], Arm64.ldr(16, literal, 1).toU32()); + } + // br x16 + code[12] = 0x00; + code[13] = 0x02; + code[14] = 0x1f; + code[15] = 0xd6; + try self.file.?.pwriteAll(&code, stub_helper.offset); + break :blk stub_helper.offset + 4 * @sizeOf(u32); + }, + else => unreachable, + } + }; + + for (self.lazy_imports.items()) |_, i| { + const index = @intCast(u32, i); + try self.writeLazySymbolPointer(index); + try self.writeStub(index); + try self.writeStubInStubHelper(index); + } +} + +fn writeLazySymbolPointer(self: *Zld, index: u32) !void { + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?]; + const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?]; + + const stub_size: u4 = switch (self.arch.?) { + .x86_64 => 10, + .aarch64 => 3 * @sizeOf(u32), + else => unreachable, + }; + const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size; + const end = stub_helper.addr + stub_off - stub_helper.offset; + var buf: [@sizeOf(u64)]u8 = undefined; + mem.writeIntLittle(u64, &buf, end); + const off = la_symbol_ptr.offset + index * @sizeOf(u64); + log.debug("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off }); + try self.file.?.pwriteAll(&buf, off); +} + +fn writeStub(self: *Zld, index: u32) !void { + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stubs = text_segment.sections.items[self.stubs_section_index.?]; + const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?]; + + const stub_off = stubs.offset + index * stubs.reserved2; + const stub_addr = stubs.addr + index * stubs.reserved2; + const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64); + log.debug("writing stub at 0x{x}", .{stub_off}); + var code = try self.allocator.alloc(u8, stubs.reserved2); + defer self.allocator.free(code); + switch (self.arch.?) { + .x86_64 => { + assert(la_ptr_addr >= stub_addr + stubs.reserved2); + const displacement = try math.cast(u32, la_ptr_addr - stub_addr - stubs.reserved2); + // jmp + code[0] = 0xff; + code[1] = 0x25; + mem.writeIntLittle(u32, code[2..][0..4], displacement); + }, + .aarch64 => { + assert(la_ptr_addr >= stub_addr); + const displacement = try math.divExact(u64, la_ptr_addr - stub_addr, 4); + const literal = try math.cast(u19, displacement); + // ldr x16, literal + mem.writeIntLittle(u32, code[0..4], Arm64.ldr(16, literal, 1).toU32()); + // br x16 + mem.writeIntLittle(u32, code[4..8], Arm64.br(16).toU32()); + }, + else => unreachable, + } + try self.file.?.pwriteAll(code, stub_off); +} + +fn writeStubInStubHelper(self: *Zld, index: u32) !void { + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?]; + + const stub_size: u4 = switch (self.arch.?) { + .x86_64 => 10, + .aarch64 => 3 * @sizeOf(u32), + else => unreachable, + }; + const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size; + var code = try self.allocator.alloc(u8, stub_size); + defer self.allocator.free(code); + switch (self.arch.?) { + .x86_64 => { + const displacement = try math.cast( + i32, + @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - stub_size, + ); + // pushq + code[0] = 0x68; + mem.writeIntLittle(u32, code[1..][0..4], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. + // jmpq + code[5] = 0xe9; + mem.writeIntLittle(u32, code[6..][0..4], @bitCast(u32, displacement)); + }, + .aarch64 => { + const displacement = try math.cast(i28, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - 4); + const literal = @divExact(stub_size - @sizeOf(u32), 4); + // ldr w16, literal + mem.writeIntLittle(u32, code[0..4], Arm64.ldr(16, literal, 0).toU32()); + // b disp + mem.writeIntLittle(u32, code[4..8], Arm64.b(displacement).toU32()); + mem.writeIntLittle(u32, code[8..12], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. + }, + else => unreachable, + } + try self.file.?.pwriteAll(code, stub_off); +} + +fn resolveSymbols(self: *Zld) !void { + const Address = struct { + addr: u64, + size: u64, + }; + var next_address = std.AutoHashMap(DirectoryKey, Address).init(self.allocator); + defer next_address.deinit(); + + for (self.objects.items) |object| { + const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + + for (seg.sections.items) |sect| { + const key: DirectoryKey = .{ + .segname = sect.segname, + .sectname = sect.sectname, + }; + const indices = self.directory.get(key) orelse continue; + const out_seg = self.load_commands.items[indices.seg_index].Segment; + const out_sect = out_seg.sections.items[indices.sect_index]; + + const res = try next_address.getOrPut(key); + const next = &res.entry.value; + if (res.found_existing) { + next.addr += next.size; + } else { + next.addr = out_sect.addr; + } + next.size = sect.size; + } + + for (object.symtab.items) |sym| { + if (isImport(&sym)) continue; + + const sym_name = object.getString(sym.n_strx); + + if (isLocal(&sym) and self.locals.get(sym_name) != null) { + log.debug("symbol '{s}' already exists; skipping", .{sym_name}); + continue; + } + + const sect = seg.sections.items[sym.n_sect - 1]; + const key: DirectoryKey = .{ + .segname = sect.segname, + .sectname = sect.sectname, + }; + const res = self.directory.get(key) orelse continue; + + const n_strx = try self.makeString(sym_name); + const n_value = sym.n_value - sect.addr + next_address.get(key).?.addr; + + log.debug("resolving '{s}' as local symbol at 0x{x}", .{ sym_name, n_value }); + + var n_sect = res.sect_index + 1; + for (self.load_commands.items) |sseg, i| { + if (i == res.seg_index) { + break; + } + n_sect += @intCast(u16, sseg.Segment.sections.items.len); + } + + var out_name = try self.allocator.dupe(u8, sym_name); + try self.locals.putNoClobber(self.allocator, out_name, .{ + .n_strx = n_strx, + .n_value = n_value, + .n_type = macho.N_SECT, + .n_desc = sym.n_desc, + .n_sect = @intCast(u8, n_sect), + }); + } + } +} + +fn doRelocs(self: *Zld) !void { + const Space = struct { + address: u64, + offset: u64, + size: u64, + }; + var next_space = std.AutoHashMap(DirectoryKey, Space).init(self.allocator); + defer next_space.deinit(); + + for (self.objects.items) |object| { + log.debug("\n\n", .{}); + log.debug("relocating object {s}", .{object.name}); + + const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + + for (seg.sections.items) |sect| { + const key: DirectoryKey = .{ + .segname = sect.segname, + .sectname = sect.sectname, + }; + const indices = self.directory.get(key) orelse continue; + const out_seg = self.load_commands.items[indices.seg_index].Segment; + const out_sect = out_seg.sections.items[indices.sect_index]; + + const res = try next_space.getOrPut(key); + const next = &res.entry.value; + if (res.found_existing) { + next.offset += next.size; + next.address += next.size; + } else { + next.offset = out_sect.offset; + next.address = out_sect.addr; + } + next.size = sect.size; + } + + for (seg.sections.items) |sect| { + const segname = parseName(§.segname); + const sectname = parseName(§.sectname); + + const key: DirectoryKey = .{ + .segname = sect.segname, + .sectname = sect.sectname, + }; + const next = next_space.get(key) orelse continue; + + var code = try self.allocator.alloc(u8, sect.size); + defer self.allocator.free(code); + _ = try object.file.preadAll(code, sect.offset); + + // Parse relocs (if any) + var raw_relocs = try self.allocator.alloc(u8, @sizeOf(macho.relocation_info) * sect.nreloc); + defer self.allocator.free(raw_relocs); + _ = try object.file.preadAll(raw_relocs, sect.reloff); + const relocs = mem.bytesAsSlice(macho.relocation_info, raw_relocs); + + var addend: ?u64 = null; + var sub: ?i64 = null; + + for (relocs) |rel| { + const off = @intCast(u32, rel.r_address); + const this_addr = next.address + off; + + switch (self.arch.?) { + .aarch64 => { + const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); + log.debug("{s}", .{rel_type}); + log.debug(" | source address 0x{x}", .{this_addr}); + log.debug(" | offset 0x{x}", .{off}); + + if (rel_type == .ARM64_RELOC_ADDEND) { + addend = rel.r_symbolnum; + log.debug(" | calculated addend = 0x{x}", .{addend}); + // TODO followed by either PAGE21 or PAGEOFF12 only. + continue; + } + }, + .x86_64 => { + const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); + log.debug("{s}", .{rel_type}); + log.debug(" | source address 0x{x}", .{this_addr}); + log.debug(" | offset 0x{x}", .{off}); + }, + else => {}, + } + + const target_addr = try self.relocTargetAddr(object, rel, next_space); + log.debug(" | target address 0x{x}", .{target_addr}); + if (rel.r_extern == 1) { + const target_symname = object.getString(object.symtab.items[rel.r_symbolnum].n_strx); + log.debug(" | target symbol '{s}'", .{target_symname}); + } else { + const target_sectname = seg.sections.items[rel.r_symbolnum - 1].sectname; + log.debug(" | target section '{s}'", .{parseName(&target_sectname)}); + } + + switch (self.arch.?) { + .x86_64 => { + const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); + + switch (rel_type) { + .X86_64_RELOC_BRANCH, + .X86_64_RELOC_GOT_LOAD, + .X86_64_RELOC_GOT, + => { + assert(rel.r_length == 2); + const inst = code[off..][0..4]; + const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); + mem.writeIntLittle(u32, inst, displacement); + }, + .X86_64_RELOC_TLV => { + assert(rel.r_length == 2); + // We need to rewrite the opcode from movq to leaq. + code[off - 2] = 0x8d; + // Add displacement. + const inst = code[off..][0..4]; + const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); + mem.writeIntLittle(u32, inst, displacement); + }, + .X86_64_RELOC_SIGNED, + .X86_64_RELOC_SIGNED_1, + .X86_64_RELOC_SIGNED_2, + .X86_64_RELOC_SIGNED_4, + => { + assert(rel.r_length == 2); + const inst = code[off..][0..4]; + const offset: i32 = blk: { + if (rel.r_extern == 1) { + break :blk mem.readIntLittle(i32, inst); + } else { + // TODO it might be required here to parse the offset from the instruction placeholder, + // compare the displacement with the original displacement in the .o file, and adjust + // the displacement in the resultant binary file. + const correction: i4 = switch (rel_type) { + .X86_64_RELOC_SIGNED => 0, + .X86_64_RELOC_SIGNED_1 => 1, + .X86_64_RELOC_SIGNED_2 => 2, + .X86_64_RELOC_SIGNED_4 => 4, + else => unreachable, + }; + break :blk correction; + } + }; + log.debug(" | calculated addend 0x{x}", .{offset}); + const result = @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4 + offset; + const displacement = @bitCast(u32, @intCast(i32, result)); + mem.writeIntLittle(u32, inst, displacement); + }, + .X86_64_RELOC_SUBTRACTOR => { + sub = @intCast(i64, target_addr); + }, + .X86_64_RELOC_UNSIGNED => { + switch (rel.r_length) { + 3 => { + const inst = code[off..][0..8]; + const offset = mem.readIntLittle(i64, inst); + log.debug(" | calculated addend 0x{x}", .{offset}); + const result = if (sub) |s| + @intCast(i64, target_addr) - s + offset + else + @intCast(i64, target_addr) + offset; + mem.writeIntLittle(u64, inst, @bitCast(u64, result)); + sub = null; + + // TODO should handle this better. + if (mem.eql(u8, segname, "__DATA")) outer: { + if (!mem.eql(u8, sectname, "__data") and + !mem.eql(u8, sectname, "__const") and + !mem.eql(u8, sectname, "__mod_init_func")) break :outer; + const this_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const this_offset = next.address + off - this_seg.inner.vmaddr; + try self.local_rebases.append(self.allocator, .{ + .offset = this_offset, + .segment_id = @intCast(u16, self.data_segment_cmd_index.?), + }); + } + }, + 2 => { + const inst = code[off..][0..4]; + const offset = mem.readIntLittle(i32, inst); + log.debug(" | calculated addend 0x{x}", .{offset}); + const result = if (sub) |s| + @intCast(i64, target_addr) - s + offset + else + @intCast(i64, target_addr) + offset; + mem.writeIntLittle(u32, inst, @truncate(u32, @bitCast(u64, result))); + sub = null; + }, + else => |len| { + log.err("unexpected relocation length 0x{x}", .{len}); + return error.UnexpectedRelocationLength; + }, + } + }, + } + }, + .aarch64 => { + const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); + + switch (rel_type) { + .ARM64_RELOC_BRANCH26 => { + assert(rel.r_length == 2); + const inst = code[off..][0..4]; + const displacement = @intCast(i28, @intCast(i64, target_addr) - @intCast(i64, this_addr)); + var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Branch), inst); + parsed.disp = @truncate(u26, @bitCast(u28, displacement) >> 2); + }, + .ARM64_RELOC_PAGE21, + .ARM64_RELOC_GOT_LOAD_PAGE21, + .ARM64_RELOC_TLVP_LOAD_PAGE21, + => { + assert(rel.r_length == 2); + const inst = code[off..][0..4]; + const ta = if (addend) |a| target_addr + a else target_addr; + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, ta >> 12); + const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); + log.debug(" | moving by {} pages", .{pages}); + var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Address), inst); + parsed.immhi = @truncate(u19, pages >> 2); + parsed.immlo = @truncate(u2, pages); + addend = null; + }, + .ARM64_RELOC_PAGEOFF12, + .ARM64_RELOC_GOT_LOAD_PAGEOFF12, + => { + const inst = code[off..][0..4]; + if (Arm64.isArithmetic(inst)) { + log.debug(" | detected ADD opcode", .{}); + // add + var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Add), inst); + const ta = if (addend) |a| target_addr + a else target_addr; + const narrowed = @truncate(u12, ta); + parsed.offset = narrowed; + } else { + log.debug(" | detected LDR/STR opcode", .{}); + // ldr/str + var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.LoadRegister), inst); + const ta = if (addend) |a| target_addr + a else target_addr; + const narrowed = @truncate(u12, ta); + const offset = if (parsed.size == 1) @divExact(narrowed, 8) else @divExact(narrowed, 4); + parsed.offset = @truncate(u12, offset); + } + addend = null; + }, + .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => { + // TODO why is this necessary? + const RegInfo = struct { + rt: u5, + rn: u5, + size: u1, + }; + const inst = code[off..][0..4]; + const parsed: RegInfo = blk: { + if (Arm64.isArithmetic(inst)) { + const curr = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Add), inst); + break :blk .{ .rt = curr.rt, .rn = curr.rn, .size = curr.size }; + } else { + const curr = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.LoadRegister), inst); + break :blk .{ .rt = curr.rt, .rn = curr.rn, .size = curr.size }; + } + }; + const ta = if (addend) |a| target_addr + a else target_addr; + const narrowed = @truncate(u12, ta); + log.debug(" | rewriting TLV access to ADD opcode", .{}); + // For TLV, we always generate an add instruction. + mem.writeIntLittle(u32, inst, Arm64.add(parsed.rt, parsed.rn, narrowed, parsed.size).toU32()); + }, + .ARM64_RELOC_SUBTRACTOR => { + sub = @intCast(i64, target_addr); + }, + .ARM64_RELOC_UNSIGNED => { + switch (rel.r_length) { + 3 => { + const inst = code[off..][0..8]; + const offset = mem.readIntLittle(i64, inst); + log.debug(" | calculated addend 0x{x}", .{offset}); + const result = if (sub) |s| + @intCast(i64, target_addr) - s + offset + else + @intCast(i64, target_addr) + offset; + mem.writeIntLittle(u64, inst, @bitCast(u64, result)); + sub = null; + + // TODO should handle this better. + if (mem.eql(u8, segname, "__DATA")) outer: { + if (!mem.eql(u8, sectname, "__data") and + !mem.eql(u8, sectname, "__const") and + !mem.eql(u8, sectname, "__mod_init_func")) break :outer; + const this_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const this_offset = next.address + off - this_seg.inner.vmaddr; + try self.local_rebases.append(self.allocator, .{ + .offset = this_offset, + .segment_id = @intCast(u16, self.data_segment_cmd_index.?), + }); + } + }, + 2 => { + const inst = code[off..][0..4]; + const offset = mem.readIntLittle(i32, inst); + log.debug(" | calculated addend 0x{x}", .{offset}); + const result = if (sub) |s| + @intCast(i64, target_addr) - s + offset + else + @intCast(i64, target_addr) + offset; + mem.writeIntLittle(u32, inst, @truncate(u32, @bitCast(u64, result))); + sub = null; + }, + else => |len| { + log.err("unexpected relocation length 0x{x}", .{len}); + return error.UnexpectedRelocationLength; + }, + } + }, + .ARM64_RELOC_POINTER_TO_GOT => return error.TODOArm64RelocPointerToGot, + else => unreachable, + } + }, + else => unreachable, + } + } + + log.debug("writing contents of '{s},{s}' section from '{s}' from 0x{x} to 0x{x}", .{ + segname, + sectname, + object.name, + next.offset, + next.offset + next.size, + }); + + if (mem.eql(u8, sectname, "__bss") or + mem.eql(u8, sectname, "__thread_bss") or + mem.eql(u8, sectname, "__thread_vars")) + { + // Zero-out the space + var zeroes = try self.allocator.alloc(u8, next.size); + defer self.allocator.free(zeroes); + mem.set(u8, zeroes, 0); + try self.file.?.pwriteAll(zeroes, next.offset); + } else { + try self.file.?.pwriteAll(code, next.offset); + } + } + } +} + +fn relocTargetAddr(self: *Zld, object: Object, rel: macho.relocation_info, next_space: anytype) !u64 { + const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + const target_addr = blk: { + if (rel.r_extern == 1) { + const sym = object.symtab.items[rel.r_symbolnum]; + if (isLocal(&sym) or isExport(&sym)) { + // Relocate using section offsets only. + const source_sect = seg.sections.items[sym.n_sect - 1]; + const target_space = next_space.get(.{ + .segname = source_sect.segname, + .sectname = source_sect.sectname, + }).?; + break :blk target_space.address + sym.n_value - source_sect.addr; + } else if (isImport(&sym)) { + // Relocate to either the artifact's local symbol, or an import from + // shared library. + const sym_name = object.getString(sym.n_strx); + if (self.locals.get(sym_name)) |loc| { + break :blk loc.n_value; + } else if (self.lazy_imports.get(sym_name)) |ext| { + const segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stubs = segment.sections.items[self.stubs_section_index.?]; + break :blk stubs.addr + ext.index * stubs.reserved2; + } else if (self.nonlazy_imports.get(sym_name)) |ext| { + const segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const got = segment.sections.items[self.got_section_index.?]; + break :blk got.addr + ext.index * @sizeOf(u64); + } else if (self.threadlocal_imports.get(sym_name)) |ext| { + const segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const tlv = segment.sections.items[self.tlv_section_index.?]; + break :blk tlv.addr + ext.index * @sizeOf(u64); + } else { + log.err("failed to resolve symbol '{s}' as a relocation target", .{sym_name}); + return error.FailedToResolveRelocationTarget; + } + } else { + log.err("unexpected symbol {}, {s}", .{ sym, object.getString(sym.n_strx) }); + return error.UnexpectedSymbolWhenRelocating; + } + } else { + // TODO I think we need to reparse the relocation_info as scattered_relocation_info + // here to get the actual section plus offset into that section of the relocated + // symbol. Unless the fine-grained location is encoded within the cell in the code + // buffer? + const source_sectname = seg.sections.items[rel.r_symbolnum - 1]; + const target_space = next_space.get(.{ + .segname = source_sectname.segname, + .sectname = source_sectname.sectname, + }).?; + break :blk target_space.address; + } + }; + return target_addr; +} + +fn populateMetadata(self: *Zld) !void { + if (self.pagezero_segment_cmd_index == null) { + self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__PAGEZERO"), + .vmaddr = 0, + .vmsize = 0x100000000, // size always set to 4GB + .fileoff = 0, + .filesize = 0, + .maxprot = 0, + .initprot = 0, + .nsects = 0, + .flags = 0, + }), + }); + try self.addSegmentToDir(0); + } + + if (self.text_segment_cmd_index == null) { + self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__TEXT"), + .vmaddr = 0x100000000, // always starts at 4GB + .vmsize = 0, + .fileoff = 0, + .filesize = 0, + .maxprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE, + .initprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE, + .nsects = 0, + .flags = 0, + }), + }); + try self.addSegmentToDir(self.text_segment_cmd_index.?); + } + + if (self.text_section_index == null) { + const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + self.text_section_index = @intCast(u16, text_seg.sections.items.len); + const alignment: u2 = switch (self.arch.?) { + .x86_64 => 0, + .aarch64 => 2, + else => unreachable, // unhandled architecture type + }; + try text_seg.append(self.allocator, .{ + .sectname = makeStaticString("__text"), + .segname = makeStaticString("__TEXT"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = alignment, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + try self.addSectionToDir(.{ + .seg_index = self.text_segment_cmd_index.?, + .sect_index = self.text_section_index.?, + }); + } + + if (self.stubs_section_index == null) { + const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + self.stubs_section_index = @intCast(u16, text_seg.sections.items.len); + const alignment: u2 = switch (self.arch.?) { + .x86_64 => 0, + .aarch64 => 2, + else => unreachable, // unhandled architecture type + }; + const stub_size: u4 = switch (self.arch.?) { + .x86_64 => 6, + .aarch64 => 2 * @sizeOf(u32), + else => unreachable, // unhandled architecture type + }; + try text_seg.append(self.allocator, .{ + .sectname = makeStaticString("__stubs"), + .segname = makeStaticString("__TEXT"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = alignment, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, + .reserved1 = 0, + .reserved2 = stub_size, + .reserved3 = 0, + }); + try self.addSectionToDir(.{ + .seg_index = self.text_segment_cmd_index.?, + .sect_index = self.stubs_section_index.?, + }); + } + + if (self.stub_helper_section_index == null) { + const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + self.stub_helper_section_index = @intCast(u16, text_seg.sections.items.len); + const alignment: u2 = switch (self.arch.?) { + .x86_64 => 0, + .aarch64 => 2, + else => unreachable, // unhandled architecture type + }; + const stub_helper_size: u5 = switch (self.arch.?) { + .x86_64 => 15, + .aarch64 => 6 * @sizeOf(u32), + else => unreachable, + }; + try text_seg.append(self.allocator, .{ + .sectname = makeStaticString("__stub_helper"), + .segname = makeStaticString("__TEXT"), + .addr = 0, + .size = stub_helper_size, + .offset = 0, + .@"align" = alignment, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + try self.addSectionToDir(.{ + .seg_index = self.text_segment_cmd_index.?, + .sect_index = self.stub_helper_section_index.?, + }); + } + + if (self.data_segment_cmd_index == null) { + self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__DATA"), + .vmaddr = 0, + .vmsize = 0, + .fileoff = 0, + .filesize = 0, + .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE, + .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE, + .nsects = 0, + .flags = 0, + }), + }); + try self.addSegmentToDir(self.data_segment_cmd_index.?); + } + + if (self.got_section_index == null) { + const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + self.got_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.append(self.allocator, .{ + .sectname = makeStaticString("__got"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 3, // 2^3 = @sizeOf(u64) + .reloff = 0, + .nreloc = 0, + .flags = macho.S_NON_LAZY_SYMBOL_POINTERS, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + try self.addSectionToDir(.{ + .seg_index = self.data_segment_cmd_index.?, + .sect_index = self.got_section_index.?, + }); + } + + if (self.la_symbol_ptr_section_index == null) { + const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + self.la_symbol_ptr_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.append(self.allocator, .{ + .sectname = makeStaticString("__la_symbol_ptr"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 3, // 2^3 = @sizeOf(u64) + .reloff = 0, + .nreloc = 0, + .flags = macho.S_LAZY_SYMBOL_POINTERS, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + try self.addSectionToDir(.{ + .seg_index = self.data_segment_cmd_index.?, + .sect_index = self.la_symbol_ptr_section_index.?, + }); + } + + if (self.data_section_index == null) { + const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + self.data_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.append(self.allocator, .{ + .sectname = makeStaticString("__data"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 3, // 2^3 = @sizeOf(u64) + .reloff = 0, + .nreloc = 0, + .flags = macho.S_REGULAR, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + try self.addSectionToDir(.{ + .seg_index = self.data_segment_cmd_index.?, + .sect_index = self.data_section_index.?, + }); + } + + if (self.linkedit_segment_cmd_index == null) { + self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__LINKEDIT"), + .vmaddr = 0, + .vmsize = 0, + .fileoff = 0, + .filesize = 0, + .maxprot = macho.VM_PROT_READ, + .initprot = macho.VM_PROT_READ, + .nsects = 0, + .flags = 0, + }), + }); + try self.addSegmentToDir(self.linkedit_segment_cmd_index.?); + } + + if (self.dyld_info_cmd_index == null) { + self.dyld_info_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .DyldInfoOnly = .{ + .cmd = macho.LC_DYLD_INFO_ONLY, + .cmdsize = @sizeOf(macho.dyld_info_command), + .rebase_off = 0, + .rebase_size = 0, + .bind_off = 0, + .bind_size = 0, + .weak_bind_off = 0, + .weak_bind_size = 0, + .lazy_bind_off = 0, + .lazy_bind_size = 0, + .export_off = 0, + .export_size = 0, + }, + }); + } + + if (self.symtab_cmd_index == null) { + self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Symtab = .{ + .cmd = macho.LC_SYMTAB, + .cmdsize = @sizeOf(macho.symtab_command), + .symoff = 0, + .nsyms = 0, + .stroff = 0, + .strsize = 0, + }, + }); + try self.strtab.append(self.allocator, 0); + } + + if (self.dysymtab_cmd_index == null) { + self.dysymtab_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Dysymtab = .{ + .cmd = macho.LC_DYSYMTAB, + .cmdsize = @sizeOf(macho.dysymtab_command), + .ilocalsym = 0, + .nlocalsym = 0, + .iextdefsym = 0, + .nextdefsym = 0, + .iundefsym = 0, + .nundefsym = 0, + .tocoff = 0, + .ntoc = 0, + .modtaboff = 0, + .nmodtab = 0, + .extrefsymoff = 0, + .nextrefsyms = 0, + .indirectsymoff = 0, + .nindirectsyms = 0, + .extreloff = 0, + .nextrel = 0, + .locreloff = 0, + .nlocrel = 0, + }, + }); + } + + if (self.dylinker_cmd_index == null) { + self.dylinker_cmd_index = @intCast(u16, self.load_commands.items.len); + const cmdsize = @intCast(u32, mem.alignForwardGeneric( + u64, + @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH), + @sizeOf(u64), + )); + var dylinker_cmd = emptyGenericCommandWithData(macho.dylinker_command{ + .cmd = macho.LC_LOAD_DYLINKER, + .cmdsize = cmdsize, + .name = @sizeOf(macho.dylinker_command), + }); + dylinker_cmd.data = try self.allocator.alloc(u8, cmdsize - dylinker_cmd.inner.name); + mem.set(u8, dylinker_cmd.data, 0); + mem.copy(u8, dylinker_cmd.data, mem.spanZ(DEFAULT_DYLD_PATH)); + try self.load_commands.append(self.allocator, .{ .Dylinker = dylinker_cmd }); + } + + if (self.libsystem_cmd_index == null) { + self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len); + const cmdsize = @intCast(u32, mem.alignForwardGeneric( + u64, + @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH), + @sizeOf(u64), + )); + // TODO Find a way to work out runtime version from the OS version triple stored in std.Target. + // In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0. + const min_version = 0x0; + var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{ + .cmd = macho.LC_LOAD_DYLIB, + .cmdsize = cmdsize, + .dylib = .{ + .name = @sizeOf(macho.dylib_command), + .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files + .current_version = min_version, + .compatibility_version = min_version, + }, + }); + dylib_cmd.data = try self.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name); + mem.set(u8, dylib_cmd.data, 0); + mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH)); + try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd }); + } + + if (self.main_cmd_index == null) { + self.main_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Main = .{ + .cmd = macho.LC_MAIN, + .cmdsize = @sizeOf(macho.entry_point_command), + .entryoff = 0x0, + .stacksize = 0, + }, + }); + } + + if (self.source_version_cmd_index == null) { + self.source_version_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .SourceVersion = .{ + .cmd = macho.LC_SOURCE_VERSION, + .cmdsize = @sizeOf(macho.source_version_command), + .version = 0x0, + }, + }); + } + + if (self.uuid_cmd_index == null) { + self.uuid_cmd_index = @intCast(u16, self.load_commands.items.len); + var uuid_cmd: macho.uuid_command = .{ + .cmd = macho.LC_UUID, + .cmdsize = @sizeOf(macho.uuid_command), + .uuid = undefined, + }; + std.crypto.random.bytes(&uuid_cmd.uuid); + try self.load_commands.append(self.allocator, .{ .Uuid = uuid_cmd }); + } + + if (self.code_signature_cmd_index == null and self.arch.? == .aarch64) { + self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .LinkeditData = .{ + .cmd = macho.LC_CODE_SIGNATURE, + .cmdsize = @sizeOf(macho.linkedit_data_command), + .dataoff = 0, + .datasize = 0, + }, + }); + } +} + +fn flush(self: *Zld) !void { + { + const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + for (seg.sections.items) |*sect| { + const sectname = parseName(§.sectname); + if (mem.eql(u8, sectname, "__bss") or mem.eql(u8, sectname, "__thread_bss")) { + sect.offset = 0; + } + } + } + { + const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + for (seg.sections.items) |*sect| { + if (mem.eql(u8, parseName(§.sectname), "__eh_frame")) { + sect.flags = 0; + } + } + } + try self.setEntryPoint(); + try self.writeRebaseInfoTable(); + try self.writeBindInfoTable(); + try self.writeLazyBindInfoTable(); + try self.writeExportInfo(); + + { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + symtab.symoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + } + + try self.writeDebugInfo(); + try self.writeSymbolTable(); + try self.writeDynamicSymbolTable(); + try self.writeStringTable(); + + { + // Seal __LINKEDIT size + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?); + } + + if (self.arch.? == .aarch64) { + try self.writeCodeSignaturePadding(); + } + + try self.writeLoadCommands(); + try self.writeHeader(); + + if (self.arch.? == .aarch64) { + try self.writeCodeSignature(); + } + + if (comptime std.Target.current.isDarwin() and std.Target.current.cpu.arch == .aarch64) { + try fs.cwd().copyFile(self.out_path.?, fs.cwd(), self.out_path.?, .{}); + } +} + +fn setEntryPoint(self: *Zld) !void { + // TODO we should respect the -entry flag passed in by the user to set a custom + // entrypoint. For now, assume default of `_main`. + const seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const text = seg.sections.items[self.text_section_index.?]; + const entry_sym = self.locals.get("_main") orelse return error.MissingMainEntrypoint; + + const name = try self.allocator.dupe(u8, "_main"); + try self.exports.putNoClobber(self.allocator, name, .{ + .n_strx = entry_sym.n_strx, + .n_value = entry_sym.n_value, + .n_type = macho.N_SECT | macho.N_EXT, + .n_desc = entry_sym.n_desc, + .n_sect = entry_sym.n_sect, + }); + + const ec = &self.load_commands.items[self.main_cmd_index.?].Main; + ec.entryoff = @intCast(u32, entry_sym.n_value - seg.inner.vmaddr); +} + +fn writeRebaseInfoTable(self: *Zld) !void { + const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + + var pointers = std.ArrayList(Pointer).init(self.allocator); + defer pointers.deinit(); + try pointers.ensureCapacity(self.lazy_imports.items().len); + + if (self.la_symbol_ptr_section_index) |idx| { + const sect = data_seg.sections.items[idx]; + const base_offset = sect.addr - data_seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_segment_cmd_index.?); + for (self.lazy_imports.items()) |entry| { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + entry.value.index * @sizeOf(u64), + .segment_id = segment_id, + }); + } + } + + try pointers.ensureCapacity(pointers.items.len + self.local_rebases.items.len); + + const nlocals = self.local_rebases.items.len; + var i = nlocals; + while (i > 0) : (i -= 1) { + pointers.appendAssumeCapacity(self.local_rebases.items[i - 1]); + } + + const size = try rebaseInfoSize(pointers.items); + var buffer = try self.allocator.alloc(u8, @intCast(usize, size)); + defer self.allocator.free(buffer); + + var stream = std.io.fixedBufferStream(buffer); + try writeRebaseInfo(pointers.items, stream.writer()); + + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; + dyld_info.rebase_off = @intCast(u32, seg.inner.fileoff); + dyld_info.rebase_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @sizeOf(u64))); + seg.inner.filesize += dyld_info.rebase_size; + + log.debug("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size }); + + try self.file.?.pwriteAll(buffer, dyld_info.rebase_off); +} + +fn writeBindInfoTable(self: *Zld) !void { + const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + + var pointers = std.ArrayList(Pointer).init(self.allocator); + defer pointers.deinit(); + try pointers.ensureCapacity(self.nonlazy_imports.items().len + self.threadlocal_imports.items().len); + + if (self.got_section_index) |idx| { + const sect = data_seg.sections.items[idx]; + const base_offset = sect.addr - data_seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_segment_cmd_index.?); + for (self.nonlazy_imports.items()) |entry| { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + entry.value.index * @sizeOf(u64), + .segment_id = segment_id, + .dylib_ordinal = entry.value.dylib_ordinal, + .name = entry.key, + }); + } + } + + if (self.tlv_section_index) |idx| { + const sect = data_seg.sections.items[idx]; + const base_offset = sect.addr - data_seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_segment_cmd_index.?); + for (self.threadlocal_imports.items()) |entry| { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + entry.value.index * @sizeOf(u64), + .segment_id = segment_id, + .dylib_ordinal = entry.value.dylib_ordinal, + .name = entry.key, + }); + } + } + + const size = try bindInfoSize(pointers.items); + var buffer = try self.allocator.alloc(u8, @intCast(usize, size)); + defer self.allocator.free(buffer); + + var stream = std.io.fixedBufferStream(buffer); + try writeBindInfo(pointers.items, stream.writer()); + + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; + dyld_info.bind_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + dyld_info.bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); + seg.inner.filesize += dyld_info.bind_size; + + log.debug("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size }); + + try self.file.?.pwriteAll(buffer, dyld_info.bind_off); +} + +fn writeLazyBindInfoTable(self: *Zld) !void { + const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + + var pointers = std.ArrayList(Pointer).init(self.allocator); + defer pointers.deinit(); + try pointers.ensureCapacity(self.lazy_imports.items().len); + + if (self.la_symbol_ptr_section_index) |idx| { + const sect = data_seg.sections.items[idx]; + const base_offset = sect.addr - data_seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_segment_cmd_index.?); + for (self.lazy_imports.items()) |entry| { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + entry.value.index * @sizeOf(u64), + .segment_id = segment_id, + .dylib_ordinal = entry.value.dylib_ordinal, + .name = entry.key, + }); + } + } + + const size = try lazyBindInfoSize(pointers.items); + var buffer = try self.allocator.alloc(u8, @intCast(usize, size)); + defer self.allocator.free(buffer); + + var stream = std.io.fixedBufferStream(buffer); + try writeLazyBindInfo(pointers.items, stream.writer()); + + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; + dyld_info.lazy_bind_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + dyld_info.lazy_bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); + seg.inner.filesize += dyld_info.lazy_bind_size; + + log.debug("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); + + try self.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); + try self.populateLazyBindOffsetsInStubHelper(buffer); +} + +fn populateLazyBindOffsetsInStubHelper(self: *Zld, buffer: []const u8) !void { + var stream = std.io.fixedBufferStream(buffer); + var reader = stream.reader(); + var offsets = std.ArrayList(u32).init(self.allocator); + try offsets.append(0); + defer offsets.deinit(); + var valid_block = false; + + while (true) { + const inst = reader.readByte() catch |err| switch (err) { + error.EndOfStream => break, + else => return err, + }; + const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK; + const opcode: u8 = inst & macho.BIND_OPCODE_MASK; + + switch (opcode) { + macho.BIND_OPCODE_DO_BIND => { + valid_block = true; + }, + macho.BIND_OPCODE_DONE => { + if (valid_block) { + const offset = try stream.getPos(); + try offsets.append(@intCast(u32, offset)); + } + valid_block = false; + }, + macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => { + var next = try reader.readByte(); + while (next != @as(u8, 0)) { + next = try reader.readByte(); + } + }, + macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => { + _ = try leb.readULEB128(u64, reader); + }, + macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => { + _ = try leb.readULEB128(u64, reader); + }, + macho.BIND_OPCODE_SET_ADDEND_SLEB => { + _ = try leb.readILEB128(i64, reader); + }, + else => {}, + } + } + assert(self.lazy_imports.items().len <= offsets.items.len); + + const stub_size: u4 = switch (self.arch.?) { + .x86_64 => 10, + .aarch64 => 3 * @sizeOf(u32), + else => unreachable, + }; + const off: u4 = switch (self.arch.?) { + .x86_64 => 1, + .aarch64 => 2 * @sizeOf(u32), + else => unreachable, + }; + var buf: [@sizeOf(u32)]u8 = undefined; + for (self.lazy_imports.items()) |entry| { + const symbol = entry.value; + const placeholder_off = self.stub_helper_stubs_start_off.? + symbol.index * stub_size + off; + mem.writeIntLittle(u32, &buf, offsets.items[symbol.index]); + try self.file.?.pwriteAll(&buf, placeholder_off); + } +} + +fn writeExportInfo(self: *Zld) !void { + var trie = Trie.init(self.allocator); + defer trie.deinit(); + + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + for (self.exports.items()) |entry| { + const name = entry.key; + const symbol = entry.value; + // TODO figure out if we should put all exports into the export trie + assert(symbol.n_value >= text_segment.inner.vmaddr); + try trie.put(.{ + .name = name, + .vmaddr_offset = symbol.n_value - text_segment.inner.vmaddr, + .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR, + }); + } + + try trie.finalize(); + var buffer = try self.allocator.alloc(u8, @intCast(usize, trie.size)); + defer self.allocator.free(buffer); + var stream = std.io.fixedBufferStream(buffer); + const nwritten = try trie.write(stream.writer()); + assert(nwritten == trie.size); + + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; + dyld_info.export_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + dyld_info.export_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); + seg.inner.filesize += dyld_info.export_size; + + log.debug("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); + + try self.file.?.pwriteAll(buffer, dyld_info.export_off); +} + +fn writeDebugInfo(self: *Zld) !void { + var stabs = std.ArrayList(macho.nlist_64).init(self.allocator); + defer stabs.deinit(); + + for (self.objects.items) |object| { + var debug_info = blk: { + var di = try DebugInfo.parseFromObject(self.allocator, object); + break :blk di orelse continue; + }; + defer debug_info.deinit(self.allocator); + + const compile_unit = try debug_info.inner.findCompileUnit(0x0); // We assume there is only one CU. + const name = try compile_unit.die.getAttrString(&debug_info.inner, dwarf.AT_name); + const comp_dir = try compile_unit.die.getAttrString(&debug_info.inner, dwarf.AT_comp_dir); + + { + const tu_path = try std.fs.path.join(self.allocator, &[_][]const u8{ comp_dir, name }); + defer self.allocator.free(tu_path); + const dirname = std.fs.path.dirname(tu_path) orelse "./"; + // Current dir + try stabs.append(.{ + .n_strx = try self.makeString(tu_path[0 .. dirname.len + 1]), + .n_type = macho.N_SO, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }); + // Artifact name + try stabs.append(.{ + .n_strx = try self.makeString(tu_path[dirname.len + 1 ..]), + .n_type = macho.N_SO, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }); + // Path to object file with debug info + var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const path = object.name; + const full_path = try std.os.realpath(path, &buffer); + const stat = try object.file.stat(); + const mtime = @intCast(u64, @divFloor(stat.mtime, 1_000_000_000)); + try stabs.append(.{ + .n_strx = try self.makeString(full_path), + .n_type = macho.N_OSO, + .n_sect = 0, + .n_desc = 1, + .n_value = mtime, + }); + } + + for (object.symtab.items) |source_sym| { + const symname = object.getString(source_sym.n_strx); + const source_addr = source_sym.n_value; + const target_sym = self.locals.get(symname) orelse continue; + + const maybe_size = blk: for (debug_info.inner.func_list.items) |func| { + if (func.pc_range) |range| { + if (source_addr >= range.start and source_addr < range.end) { + break :blk range.end - range.start; + } + } + } else null; + + if (maybe_size) |size| { + try stabs.append(.{ + .n_strx = 0, + .n_type = macho.N_BNSYM, + .n_sect = target_sym.n_sect, + .n_desc = 0, + .n_value = target_sym.n_value, + }); + try stabs.append(.{ + .n_strx = target_sym.n_strx, + .n_type = macho.N_FUN, + .n_sect = target_sym.n_sect, + .n_desc = 0, + .n_value = target_sym.n_value, + }); + try stabs.append(.{ + .n_strx = 0, + .n_type = macho.N_FUN, + .n_sect = 0, + .n_desc = 0, + .n_value = size, + }); + try stabs.append(.{ + .n_strx = 0, + .n_type = macho.N_ENSYM, + .n_sect = target_sym.n_sect, + .n_desc = 0, + .n_value = size, + }); + } else { + // TODO need a way to differentiate symbols: global, static, local, etc. + try stabs.append(.{ + .n_strx = target_sym.n_strx, + .n_type = macho.N_STSYM, + .n_sect = target_sym.n_sect, + .n_desc = 0, + .n_value = target_sym.n_value, + }); + } + } + + // Close the source file! + try stabs.append(.{ + .n_strx = 0, + .n_type = macho.N_SO, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }); + } + + if (stabs.items.len == 0) return; + + // Write stabs into the symbol table + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + + symtab.nsyms = @intCast(u32, stabs.items.len); + + const stabs_off = symtab.symoff; + const stabs_size = symtab.nsyms * @sizeOf(macho.nlist_64); + log.debug("writing symbol stabs from 0x{x} to 0x{x}", .{ stabs_off, stabs_size + stabs_off }); + try self.file.?.pwriteAll(mem.sliceAsBytes(stabs.items), stabs_off); + + linkedit.inner.filesize += stabs_size; + + // Update dynamic symbol table. + const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; + dysymtab.nlocalsym = symtab.nsyms; +} + +fn writeSymbolTable(self: *Zld) !void { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + + const nlocals = self.locals.items().len; + var locals = std.ArrayList(macho.nlist_64).init(self.allocator); + defer locals.deinit(); + + try locals.ensureCapacity(nlocals); + for (self.locals.items()) |entry| { + locals.appendAssumeCapacity(entry.value); + } + + const nexports = self.exports.items().len; + var exports = std.ArrayList(macho.nlist_64).init(self.allocator); + defer exports.deinit(); + + try exports.ensureCapacity(nexports); + for (self.exports.items()) |entry| { + exports.appendAssumeCapacity(entry.value); + } + + const nundefs = self.lazy_imports.items().len + self.nonlazy_imports.items().len + self.threadlocal_imports.items().len; + var undefs = std.ArrayList(macho.nlist_64).init(self.allocator); + defer undefs.deinit(); + + try undefs.ensureCapacity(nundefs); + for (self.lazy_imports.items()) |entry| { + undefs.appendAssumeCapacity(entry.value.symbol); + } + for (self.nonlazy_imports.items()) |entry| { + undefs.appendAssumeCapacity(entry.value.symbol); + } + for (self.threadlocal_imports.items()) |entry| { + undefs.appendAssumeCapacity(entry.value.symbol); + } + + const locals_off = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); + const locals_size = nlocals * @sizeOf(macho.nlist_64); + log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off }); + try self.file.?.pwriteAll(mem.sliceAsBytes(locals.items), locals_off); + + const exports_off = locals_off + locals_size; + const exports_size = nexports * @sizeOf(macho.nlist_64); + log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off }); + try self.file.?.pwriteAll(mem.sliceAsBytes(exports.items), exports_off); + + const undefs_off = exports_off + exports_size; + const undefs_size = nundefs * @sizeOf(macho.nlist_64); + log.debug("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); + try self.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off); + + symtab.nsyms += @intCast(u32, nlocals + nexports + nundefs); + seg.inner.filesize += locals_size + exports_size + undefs_size; + + // Update dynamic symbol table. + const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; + dysymtab.nlocalsym += @intCast(u32, nlocals); + dysymtab.iextdefsym = dysymtab.nlocalsym; + dysymtab.nextdefsym = @intCast(u32, nexports); + dysymtab.iundefsym = dysymtab.nlocalsym + dysymtab.nextdefsym; + dysymtab.nundefsym = @intCast(u32, nundefs); +} + +fn writeDynamicSymbolTable(self: *Zld) !void { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stubs = &text_segment.sections.items[self.stubs_section_index.?]; + const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const got = &data_segment.sections.items[self.got_section_index.?]; + const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; + const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; + + const lazy = self.lazy_imports.items(); + const nonlazy = self.nonlazy_imports.items(); + dysymtab.indirectsymoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + dysymtab.nindirectsyms = @intCast(u32, lazy.len * 2 + nonlazy.len); + const needed_size = dysymtab.nindirectsyms * @sizeOf(u32); + seg.inner.filesize += needed_size; + + log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{ + dysymtab.indirectsymoff, + dysymtab.indirectsymoff + needed_size, + }); + + var buf = try self.allocator.alloc(u8, needed_size); + defer self.allocator.free(buf); + var stream = std.io.fixedBufferStream(buf); + var writer = stream.writer(); + + stubs.reserved1 = 0; + for (self.lazy_imports.items()) |_, i| { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); + try writer.writeIntLittle(u32, symtab_idx); + } + + const base_id = @intCast(u32, lazy.len); + got.reserved1 = base_id; + for (self.nonlazy_imports.items()) |_, i| { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + i + base_id); + try writer.writeIntLittle(u32, symtab_idx); + } + + la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, nonlazy.len); + for (self.lazy_imports.items()) |_, i| { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); + try writer.writeIntLittle(u32, symtab_idx); + } + + try self.file.?.pwriteAll(buf, dysymtab.indirectsymoff); +} + +fn writeStringTable(self: *Zld) !void { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + symtab.stroff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.items.len, @alignOf(u64))); + seg.inner.filesize += symtab.strsize; + + log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); + + try self.file.?.pwriteAll(self.strtab.items, symtab.stroff); + + if (symtab.strsize > self.strtab.items.len and self.arch.? == .x86_64) { + // This is the last section, so we need to pad it out. + try self.file.?.pwriteAll(&[_]u8{0}, seg.inner.fileoff + seg.inner.filesize - 1); + } +} + +fn writeCodeSignaturePadding(self: *Zld) !void { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; + const fileoff = seg.inner.fileoff + seg.inner.filesize; + const needed_size = CodeSignature.calcCodeSignaturePaddingSize( + self.out_path.?, + fileoff, + self.page_size.?, + ); + code_sig_cmd.dataoff = @intCast(u32, fileoff); + code_sig_cmd.datasize = needed_size; + + // Advance size of __LINKEDIT segment + seg.inner.filesize += needed_size; + seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?); + + log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); + + // Pad out the space. We need to do this to calculate valid hashes for everything in the file + // except for code signature data. + try self.file.?.pwriteAll(&[_]u8{0}, fileoff + needed_size - 1); +} + +fn writeCodeSignature(self: *Zld) !void { + const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const code_sig_cmd = self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; + + var code_sig = CodeSignature.init(self.allocator, self.page_size.?); + defer code_sig.deinit(); + try code_sig.calcAdhocSignature( + self.file.?, + self.out_path.?, + text_seg.inner, + code_sig_cmd, + .Exe, + ); + + var buffer = try self.allocator.alloc(u8, code_sig.size()); + defer self.allocator.free(buffer); + var stream = std.io.fixedBufferStream(buffer); + try code_sig.write(stream.writer()); + + log.debug("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); + + try self.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); +} + +fn writeLoadCommands(self: *Zld) !void { + var sizeofcmds: u32 = 0; + for (self.load_commands.items) |lc| { + sizeofcmds += lc.cmdsize(); + } + + var buffer = try self.allocator.alloc(u8, sizeofcmds); + defer self.allocator.free(buffer); + var writer = std.io.fixedBufferStream(buffer).writer(); + for (self.load_commands.items) |lc| { + try lc.write(writer); + } + + const off = @sizeOf(macho.mach_header_64); + log.debug("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds }); + try self.file.?.pwriteAll(buffer, off); +} + +fn writeHeader(self: *Zld) !void { + var header: macho.mach_header_64 = undefined; + header.magic = macho.MH_MAGIC_64; + + const CpuInfo = struct { + cpu_type: macho.cpu_type_t, + cpu_subtype: macho.cpu_subtype_t, + }; + + const cpu_info: CpuInfo = switch (self.arch.?) { + .aarch64 => .{ + .cpu_type = macho.CPU_TYPE_ARM64, + .cpu_subtype = macho.CPU_SUBTYPE_ARM_ALL, + }, + .x86_64 => .{ + .cpu_type = macho.CPU_TYPE_X86_64, + .cpu_subtype = macho.CPU_SUBTYPE_X86_64_ALL, + }, + else => return error.UnsupportedCpuArchitecture, + }; + header.cputype = cpu_info.cpu_type; + header.cpusubtype = cpu_info.cpu_subtype; + header.filetype = macho.MH_EXECUTE; + header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK | macho.MH_PIE | macho.MH_TWOLEVEL; + header.reserved = 0; + + if (self.tlv_section_index) |_| + header.flags |= macho.MH_HAS_TLV_DESCRIPTORS; + + header.ncmds = @intCast(u32, self.load_commands.items.len); + header.sizeofcmds = 0; + for (self.load_commands.items) |cmd| { + header.sizeofcmds += cmd.cmdsize(); + } + log.debug("writing Mach-O header {}", .{header}); + try self.file.?.pwriteAll(mem.asBytes(&header), 0); +} + +pub fn makeStaticString(bytes: []const u8) [16]u8 { + var buf = [_]u8{0} ** 16; + assert(bytes.len <= buf.len); + mem.copy(u8, &buf, bytes); + return buf; +} + +fn makeString(self: *Zld, bytes: []const u8) !u32 { + try self.strtab.ensureCapacity(self.allocator, self.strtab.items.len + bytes.len + 1); + const offset = @intCast(u32, self.strtab.items.len); + log.debug("writing new string '{s}' into string table at offset 0x{x}", .{ bytes, offset }); + self.strtab.appendSliceAssumeCapacity(bytes); + self.strtab.appendAssumeCapacity(0); + return offset; +} + +fn getString(self: *const Zld, str_off: u32) []const u8 { + assert(str_off < self.strtab.items.len); + return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + str_off)); +} + +pub fn parseName(name: *const [16]u8) []const u8 { + const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len; + return name[0..len]; +} + +fn addSegmentToDir(self: *Zld, idx: u16) !void { + const segment_cmd = self.load_commands.items[idx].Segment; + return self.segments_directory.putNoClobber(self.allocator, segment_cmd.inner.segname, idx); +} + +fn addSectionToDir(self: *Zld, value: DirectoryEntry) !void { + const seg = self.load_commands.items[value.seg_index].Segment; + const sect = seg.sections.items[value.sect_index]; + return self.directory.putNoClobber(self.allocator, .{ + .segname = sect.segname, + .sectname = sect.sectname, + }, value); +} + +fn isLocal(sym: *const macho.nlist_64) callconv(.Inline) bool { + if (isExtern(sym)) return false; + const tt = macho.N_TYPE & sym.n_type; + return tt == macho.N_SECT; +} + +fn isExport(sym: *const macho.nlist_64) callconv(.Inline) bool { + if (!isExtern(sym)) return false; + const tt = macho.N_TYPE & sym.n_type; + return tt == macho.N_SECT; +} + +fn isImport(sym: *const macho.nlist_64) callconv(.Inline) bool { + if (!isExtern(sym)) return false; + const tt = macho.N_TYPE & sym.n_type; + return tt == macho.N_UNDF; +} + +fn isExtern(sym: *const macho.nlist_64) callconv(.Inline) bool { + if ((sym.n_type & macho.N_EXT) == 0) return false; + return (sym.n_type & macho.N_PEXT) == 0; +} diff --git a/src/link/MachO/commands.zig b/src/link/MachO/commands.zig index 67b808d856..175f1a8d9c 100644 --- a/src/link/MachO/commands.zig +++ b/src/link/MachO/commands.zig @@ -166,6 +166,11 @@ pub const SegmentCommand = struct { return .{ .inner = inner }; } + // TODO remove me, I'm just a temp! + pub fn append(self: *SegmentCommand, alloc: *Allocator, section: macho.section_64) !void { + return self.addSection(alloc, section); + } + pub fn addSection(self: *SegmentCommand, alloc: *Allocator, section: macho.section_64) !void { try self.sections.append(alloc, section); self.inner.cmdsize += @sizeOf(macho.section_64); diff --git a/src/link/MachO/reloc.zig b/src/link/MachO/reloc.zig new file mode 100644 index 0000000000..deb07dc42a --- /dev/null +++ b/src/link/MachO/reloc.zig @@ -0,0 +1,159 @@ +const std = @import("std"); +const log = std.log.scoped(.reloc); + +pub const Arm64 = union(enum) { + Branch: packed struct { + disp: u26, + fixed: u5 = 0b00101, + link: u1, + }, + BranchRegister: packed struct { + _1: u5 = 0b0000_0, + reg: u5, + _2: u11 = 0b1111_1000_000, + link: u1, + _3: u10 = 0b1101_0110_00, + }, + Address: packed struct { + reg: u5, + immhi: u19, + _1: u5 = 0b10000, + immlo: u2, + page: u1, + }, + LoadRegister: packed struct { + rt: u5, + rn: u5, + offset: u12, + _1: u8 = 0b111_0_01_01, + size: u1, + _2: u1 = 0b1, + }, + LoadLiteral: packed struct { + reg: u5, + literal: u19, + _1: u6 = 0b011_0_00, + size: u1, + _2: u1 = 0b0, + }, + Add: packed struct { + rt: u5, + rn: u5, + offset: u12, + _1: u9 = 0b0_0_100010_0, + size: u1, + }, + + pub fn toU32(self: Arm64) u32 { + const as_u32 = switch (self) { + .Branch => |x| @bitCast(u32, x), + .BranchRegister => |x| @bitCast(u32, x), + .Address => |x| @bitCast(u32, x), + .LoadRegister => |x| @bitCast(u32, x), + .LoadLiteral => |x| @bitCast(u32, x), + .Add => |x| @bitCast(u32, x), + }; + return as_u32; + } + + pub fn b(disp: i28) Arm64 { + return Arm64{ + .Branch = .{ + .disp = @truncate(u26, @bitCast(u28, disp) >> 2), + .link = 0, + }, + }; + } + + pub fn bl(disp: i28) Arm64 { + return Arm64{ + .Branch = .{ + .disp = @truncate(u26, @bitCast(u28, disp) >> 2), + .link = 1, + }, + }; + } + + pub fn br(reg: u5) Arm64 { + return Arm64{ + .BranchRegister = .{ + .reg = reg, + .link = 0, + }, + }; + } + + pub fn blr(reg: u5) Arm64 { + return Arm64{ + .BranchRegister = .{ + .reg = reg, + .link = 1, + }, + }; + } + + pub fn adr(reg: u5, disp: u21) Arm64 { + return Arm64{ + .Address = .{ + .reg = reg, + .immhi = @truncate(u19, disp >> 2), + .immlo = @truncate(u2, disp), + .page = 0, + }, + }; + } + + pub fn adrp(reg: u5, disp: u21) Arm64 { + return Arm64{ + .Address = .{ + .reg = reg, + .immhi = @truncate(u19, disp >> 2), + .immlo = @truncate(u2, disp), + .page = 1, + }, + }; + } + + pub fn ldr(reg: u5, literal: u19, size: u1) Arm64 { + return Arm64{ + .LoadLiteral = .{ + .reg = reg, + .literal = literal, + .size = size, + }, + }; + } + + pub fn add(rt: u5, rn: u5, offset: u12, size: u1) Arm64 { + return Arm64{ + .Add = .{ + .rt = rt, + .rn = rn, + .offset = offset, + .size = size, + }, + }; + } + + pub fn ldrr(rt: u5, rn: u5, offset: u12, size: u1) Arm64 { + return Arm64{ + .LoadRegister = .{ + .rt = rt, + .rn = rn, + .offset = offset, + .size = size, + }, + }; + } + + pub fn isArithmetic(inst: *const [4]u8) bool { + const group_decode = @truncate(u5, inst[3]); + log.debug("{b}", .{group_decode}); + return ((group_decode >> 2) == 4); + // if ((group_decode >> 2) == 4) { + // log.debug("Arithmetic imm", .{}); + // } else if (((group_decode & 0b01010) >> 3) == 1) { + // log.debug("Load/store", .{}); + // } + } +}; diff --git a/src/main.zig b/src/main.zig index bc4f209b45..4549f6f954 100644 --- a/src/main.zig +++ b/src/main.zig @@ -547,6 +547,7 @@ fn buildOutputType( var image_base_override: ?u64 = null; var use_llvm: ?bool = null; var use_lld: ?bool = null; + var use_zld: ?bool = null; var use_clang: ?bool = null; var link_eh_frame_hdr = false; var link_emit_relocs = false; @@ -906,6 +907,8 @@ fn buildOutputType( use_lld = true; } else if (mem.eql(u8, arg, "-fno-LLD")) { use_lld = false; + } else if (mem.eql(u8, arg, "-fZLD")) { + use_zld = true; } else if (mem.eql(u8, arg, "-fClang")) { use_clang = true; } else if (mem.eql(u8, arg, "-fno-Clang")) { @@ -1864,6 +1867,7 @@ fn buildOutputType( .want_compiler_rt = want_compiler_rt, .use_llvm = use_llvm, .use_lld = use_lld, + .use_zld = use_zld, .use_clang = use_clang, .rdynamic = rdynamic, .linker_script = linker_script, From e825a15b0588d2ae4d3c6ab28fb8d8fd2ae05288 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 25 Feb 2021 09:47:26 +0100 Subject: [PATCH 24/56] zld: replace ldr with add if indivisible --- src/link/MachO/Zld.zig | 157 ++++++++++++++++++++++++----------------- 1 file changed, 94 insertions(+), 63 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 47614e37b9..838e16b0e5 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -16,6 +16,7 @@ const CodeSignature = @import("CodeSignature.zig"); const Archive = @import("Archive.zig"); const Object = @import("Object.zig"); const Trie = @import("Trie.zig"); +const aarch64 = @import("../../codegen/aarch64.zig"); usingnamespace @import("commands.zig"); usingnamespace @import("bind.zig"); @@ -299,6 +300,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void { fn parseObjectFile(self: *Zld, object: *const Object) !void { const seg_cmd = object.load_commands.items[object.segment_cmd_index.?].Segment; for (seg_cmd.sections.items) |sect| { + const segname = parseName(§.segname); const sectname = parseName(§.sectname); const seg_index = self.segments_directory.get(sect.segname) orelse { @@ -384,7 +386,7 @@ fn resolveImports(self: *Zld) !void { mem.eql(u8, sym_name, "___stack_chk_guard") or mem.eql(u8, sym_name, "_environ")) { - log.debug("writing nonlazy symbol '{s}'", .{sym_name}); + log.warn("writing nonlazy symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.nonlazy_imports.items().len); try self.nonlazy_imports.putNoClobber(self.allocator, key, .{ .symbol = new_sym, @@ -392,7 +394,7 @@ fn resolveImports(self: *Zld) !void { .index = index, }); } else if (mem.eql(u8, sym_name, "__tlv_bootstrap")) { - log.debug("writing threadlocal symbol '{s}'", .{sym_name}); + log.warn("writing threadlocal symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.threadlocal_imports.items().len); try self.threadlocal_imports.putNoClobber(self.allocator, key, .{ .symbol = new_sym, @@ -400,7 +402,7 @@ fn resolveImports(self: *Zld) !void { .index = index, }); } else { - log.debug("writing lazy symbol '{s}'", .{sym_name}); + log.warn("writing lazy symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.lazy_imports.items().len); try self.lazy_imports.putNoClobber(self.allocator, key, .{ .symbol = new_sym, @@ -412,7 +414,7 @@ fn resolveImports(self: *Zld) !void { const n_strx = try self.makeString("dyld_stub_binder"); const name = try self.allocator.dupe(u8, "dyld_stub_binder"); - log.debug("writing nonlazy symbol 'dyld_stub_binder'", .{}); + log.warn("writing nonlazy symbol 'dyld_stub_binder'", .{}); const index = @intCast(u32, self.nonlazy_imports.items().len); try self.nonlazy_imports.putNoClobber(self.allocator, name, .{ .symbol = .{ @@ -606,7 +608,7 @@ fn writeLazySymbolPointer(self: *Zld, index: u32) !void { var buf: [@sizeOf(u64)]u8 = undefined; mem.writeIntLittle(u64, &buf, end); const off = la_symbol_ptr.offset + index * @sizeOf(u64); - log.debug("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off }); + log.warn("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off }); try self.file.?.pwriteAll(&buf, off); } @@ -619,7 +621,7 @@ fn writeStub(self: *Zld, index: u32) !void { const stub_off = stubs.offset + index * stubs.reserved2; const stub_addr = stubs.addr + index * stubs.reserved2; const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64); - log.debug("writing stub at 0x{x}", .{stub_off}); + log.warn("writing stub at 0x{x}", .{stub_off}); var code = try self.allocator.alloc(u8, stubs.reserved2); defer self.allocator.free(code); switch (self.arch.?) { @@ -720,7 +722,7 @@ fn resolveSymbols(self: *Zld) !void { const sym_name = object.getString(sym.n_strx); if (isLocal(&sym) and self.locals.get(sym_name) != null) { - log.debug("symbol '{s}' already exists; skipping", .{sym_name}); + log.warn("symbol '{s}' already exists; skipping", .{sym_name}); continue; } @@ -734,7 +736,7 @@ fn resolveSymbols(self: *Zld) !void { const n_strx = try self.makeString(sym_name); const n_value = sym.n_value - sect.addr + next_address.get(key).?.addr; - log.debug("resolving '{s}' as local symbol at 0x{x}", .{ sym_name, n_value }); + log.warn("resolving '{s}' as local symbol at 0x{x}", .{ sym_name, n_value }); var n_sect = res.sect_index + 1; for (self.load_commands.items) |sseg, i| { @@ -766,8 +768,8 @@ fn doRelocs(self: *Zld) !void { defer next_space.deinit(); for (self.objects.items) |object| { - log.debug("\n\n", .{}); - log.debug("relocating object {s}", .{object.name}); + log.warn("\n\n", .{}); + log.warn("relocating object {s}", .{object.name}); const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; @@ -802,9 +804,12 @@ fn doRelocs(self: *Zld) !void { }; const next = next_space.get(key) orelse continue; - var code = try self.allocator.alloc(u8, sect.size); - defer self.allocator.free(code); - _ = try object.file.preadAll(code, sect.offset); + var code = blk: { + var buf = try self.allocator.alloc(u8, sect.size); + _ = try object.file.preadAll(buf, sect.offset); + break :blk std.ArrayList(u8).fromOwnedSlice(self.allocator, buf); + }; + defer code.deinit(); // Parse relocs (if any) var raw_relocs = try self.allocator.alloc(u8, @sizeOf(macho.relocation_info) * sect.nreloc); @@ -822,34 +827,34 @@ fn doRelocs(self: *Zld) !void { switch (self.arch.?) { .aarch64 => { const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); - log.debug("{s}", .{rel_type}); - log.debug(" | source address 0x{x}", .{this_addr}); - log.debug(" | offset 0x{x}", .{off}); + log.warn("{s}", .{rel_type}); + log.warn(" | source address 0x{x}", .{this_addr}); + log.warn(" | offset 0x{x}", .{off}); if (rel_type == .ARM64_RELOC_ADDEND) { addend = rel.r_symbolnum; - log.debug(" | calculated addend = 0x{x}", .{addend}); + log.warn(" | calculated addend = 0x{x}", .{addend}); // TODO followed by either PAGE21 or PAGEOFF12 only. continue; } }, .x86_64 => { const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); - log.debug("{s}", .{rel_type}); - log.debug(" | source address 0x{x}", .{this_addr}); - log.debug(" | offset 0x{x}", .{off}); + log.warn("{s}", .{rel_type}); + log.warn(" | source address 0x{x}", .{this_addr}); + log.warn(" | offset 0x{x}", .{off}); }, else => {}, } const target_addr = try self.relocTargetAddr(object, rel, next_space); - log.debug(" | target address 0x{x}", .{target_addr}); + log.warn(" | target address 0x{x}", .{target_addr}); if (rel.r_extern == 1) { const target_symname = object.getString(object.symtab.items[rel.r_symbolnum].n_strx); - log.debug(" | target symbol '{s}'", .{target_symname}); + log.warn(" | target symbol '{s}'", .{target_symname}); } else { const target_sectname = seg.sections.items[rel.r_symbolnum - 1].sectname; - log.debug(" | target section '{s}'", .{parseName(&target_sectname)}); + log.warn(" | target section '{s}'", .{parseName(&target_sectname)}); } switch (self.arch.?) { @@ -862,16 +867,16 @@ fn doRelocs(self: *Zld) !void { .X86_64_RELOC_GOT, => { assert(rel.r_length == 2); - const inst = code[off..][0..4]; + const inst = code.items[off..][0..4]; const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); mem.writeIntLittle(u32, inst, displacement); }, .X86_64_RELOC_TLV => { assert(rel.r_length == 2); // We need to rewrite the opcode from movq to leaq. - code[off - 2] = 0x8d; + code.items[off - 2] = 0x8d; // Add displacement. - const inst = code[off..][0..4]; + const inst = code.items[off..][0..4]; const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); mem.writeIntLittle(u32, inst, displacement); }, @@ -881,7 +886,7 @@ fn doRelocs(self: *Zld) !void { .X86_64_RELOC_SIGNED_4, => { assert(rel.r_length == 2); - const inst = code[off..][0..4]; + const inst = code.items[off..][0..4]; const offset: i32 = blk: { if (rel.r_extern == 1) { break :blk mem.readIntLittle(i32, inst); @@ -899,7 +904,7 @@ fn doRelocs(self: *Zld) !void { break :blk correction; } }; - log.debug(" | calculated addend 0x{x}", .{offset}); + log.warn(" | calculated addend 0x{x}", .{offset}); const result = @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4 + offset; const displacement = @bitCast(u32, @intCast(i32, result)); mem.writeIntLittle(u32, inst, displacement); @@ -910,9 +915,9 @@ fn doRelocs(self: *Zld) !void { .X86_64_RELOC_UNSIGNED => { switch (rel.r_length) { 3 => { - const inst = code[off..][0..8]; + const inst = code.items[off..][0..8]; const offset = mem.readIntLittle(i64, inst); - log.debug(" | calculated addend 0x{x}", .{offset}); + log.warn(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -934,9 +939,9 @@ fn doRelocs(self: *Zld) !void { } }, 2 => { - const inst = code[off..][0..4]; + const inst = code.items[off..][0..4]; const offset = mem.readIntLittle(i32, inst); - log.debug(" | calculated addend 0x{x}", .{offset}); + log.warn(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -958,7 +963,7 @@ fn doRelocs(self: *Zld) !void { switch (rel_type) { .ARM64_RELOC_BRANCH26 => { assert(rel.r_length == 2); - const inst = code[off..][0..4]; + const inst = code.items[off..][0..4]; const displacement = @intCast(i28, @intCast(i64, target_addr) - @intCast(i64, this_addr)); var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Branch), inst); parsed.disp = @truncate(u26, @bitCast(u28, displacement) >> 2); @@ -968,12 +973,18 @@ fn doRelocs(self: *Zld) !void { .ARM64_RELOC_TLVP_LOAD_PAGE21, => { assert(rel.r_length == 2); - const inst = code[off..][0..4]; + const inst = code.items[off..][0..4]; const ta = if (addend) |a| target_addr + a else target_addr; const this_page = @intCast(i32, this_addr >> 12); const target_page = @intCast(i32, ta >> 12); const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); - log.debug(" | moving by {} pages", .{pages}); + if (pages == 0) { + // No need to execute adrp. Instead, replace with a nop. + log.warn(" | replacing ADRP with NOP", .{}); + mem.writeIntLittle(u32, inst, aarch64.Instruction.nop().toU32()); + continue; + } + log.warn(" | moving by {} pages", .{pages}); var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Address), inst); parsed.immhi = @truncate(u19, pages >> 2); parsed.immlo = @truncate(u2, pages); @@ -982,22 +993,42 @@ fn doRelocs(self: *Zld) !void { .ARM64_RELOC_PAGEOFF12, .ARM64_RELOC_GOT_LOAD_PAGEOFF12, => { - const inst = code[off..][0..4]; + const inst = code.items[off..][0..4]; if (Arm64.isArithmetic(inst)) { - log.debug(" | detected ADD opcode", .{}); + log.warn(" | detected ADD opcode", .{}); // add var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Add), inst); const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); + if (narrowed == 0) { + // No need to execute add. Instead, replace with a nop. + log.warn(" | replacing ADD with NOP", .{}); + mem.writeIntLittle(u32, inst, aarch64.Instruction.nop().toU32()); + continue; + } parsed.offset = narrowed; } else { - log.debug(" | detected LDR/STR opcode", .{}); + log.warn(" | detected LDR/STR opcode", .{}); // ldr/str var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.LoadRegister), inst); const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); - const offset = if (parsed.size == 1) @divExact(narrowed, 8) else @divExact(narrowed, 4); - parsed.offset = @truncate(u12, offset); + if (narrowed == 0) { + // No need to execute ldr/str. Instead, replace with a nop. + log.warn(" | replacing LDR/STR with NOP", .{}); + mem.writeIntLittle(u32, inst, aarch64.Instruction.nop().toU32()); + continue; + } + const denom: u12 = if (parsed.size == 1) 8 else 4; + const offset = math.divExact(u12, narrowed, denom) catch |_| { + // If we are here, then this means we are not able to divide the offset + // exactly by the required denominator. Therefore, we will use add instead of + // ldr as we expect ldr to follow this instruction nonetheless. + // TODO I believe ldr/str can only occur for GOT_LOAD_PAGEOFF12. + mem.writeIntLittle(u32, inst, Arm64.add(parsed.rn, parsed.rn, narrowed, parsed.size).toU32()); + continue; + }; + parsed.offset = offset; } addend = null; }, @@ -1008,7 +1039,7 @@ fn doRelocs(self: *Zld) !void { rn: u5, size: u1, }; - const inst = code[off..][0..4]; + const inst = code.items[off..][0..4]; const parsed: RegInfo = blk: { if (Arm64.isArithmetic(inst)) { const curr = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Add), inst); @@ -1020,7 +1051,7 @@ fn doRelocs(self: *Zld) !void { }; const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); - log.debug(" | rewriting TLV access to ADD opcode", .{}); + log.warn(" | rewriting TLV access to ADD opcode", .{}); // For TLV, we always generate an add instruction. mem.writeIntLittle(u32, inst, Arm64.add(parsed.rt, parsed.rn, narrowed, parsed.size).toU32()); }, @@ -1030,9 +1061,9 @@ fn doRelocs(self: *Zld) !void { .ARM64_RELOC_UNSIGNED => { switch (rel.r_length) { 3 => { - const inst = code[off..][0..8]; + const inst = code.items[off..][0..8]; const offset = mem.readIntLittle(i64, inst); - log.debug(" | calculated addend 0x{x}", .{offset}); + log.warn(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -1054,9 +1085,9 @@ fn doRelocs(self: *Zld) !void { } }, 2 => { - const inst = code[off..][0..4]; + const inst = code.items[off..][0..4]; const offset = mem.readIntLittle(i32, inst); - log.debug(" | calculated addend 0x{x}", .{offset}); + log.warn(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -1078,7 +1109,7 @@ fn doRelocs(self: *Zld) !void { } } - log.debug("writing contents of '{s},{s}' section from '{s}' from 0x{x} to 0x{x}", .{ + log.warn("writing contents of '{s},{s}' section from '{s}' from 0x{x} to 0x{x}", .{ segname, sectname, object.name, @@ -1096,7 +1127,7 @@ fn doRelocs(self: *Zld) !void { mem.set(u8, zeroes, 0); try self.file.?.pwriteAll(zeroes, next.offset); } else { - try self.file.?.pwriteAll(code, next.offset); + try self.file.?.pwriteAll(code.items, next.offset); } } } @@ -1672,7 +1703,7 @@ fn writeRebaseInfoTable(self: *Zld) !void { dyld_info.rebase_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @sizeOf(u64))); seg.inner.filesize += dyld_info.rebase_size; - log.debug("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size }); + log.warn("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size }); try self.file.?.pwriteAll(buffer, dyld_info.rebase_off); } @@ -1725,7 +1756,7 @@ fn writeBindInfoTable(self: *Zld) !void { dyld_info.bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); seg.inner.filesize += dyld_info.bind_size; - log.debug("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size }); + log.warn("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size }); try self.file.?.pwriteAll(buffer, dyld_info.bind_off); } @@ -1764,7 +1795,7 @@ fn writeLazyBindInfoTable(self: *Zld) !void { dyld_info.lazy_bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); seg.inner.filesize += dyld_info.lazy_bind_size; - log.debug("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); + log.warn("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); try self.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); try self.populateLazyBindOffsetsInStubHelper(buffer); @@ -1866,7 +1897,7 @@ fn writeExportInfo(self: *Zld) !void { dyld_info.export_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); seg.inner.filesize += dyld_info.export_size; - log.debug("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); + log.warn("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); try self.file.?.pwriteAll(buffer, dyld_info.export_off); } @@ -1995,7 +2026,7 @@ fn writeDebugInfo(self: *Zld) !void { const stabs_off = symtab.symoff; const stabs_size = symtab.nsyms * @sizeOf(macho.nlist_64); - log.debug("writing symbol stabs from 0x{x} to 0x{x}", .{ stabs_off, stabs_size + stabs_off }); + log.warn("writing symbol stabs from 0x{x} to 0x{x}", .{ stabs_off, stabs_size + stabs_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(stabs.items), stabs_off); linkedit.inner.filesize += stabs_size; @@ -2044,17 +2075,17 @@ fn writeSymbolTable(self: *Zld) !void { const locals_off = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); const locals_size = nlocals * @sizeOf(macho.nlist_64); - log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off }); + log.warn("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(locals.items), locals_off); const exports_off = locals_off + locals_size; const exports_size = nexports * @sizeOf(macho.nlist_64); - log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off }); + log.warn("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(exports.items), exports_off); const undefs_off = exports_off + exports_size; const undefs_size = nundefs * @sizeOf(macho.nlist_64); - log.debug("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); + log.warn("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off); symtab.nsyms += @intCast(u32, nlocals + nexports + nundefs); @@ -2085,7 +2116,7 @@ fn writeDynamicSymbolTable(self: *Zld) !void { const needed_size = dysymtab.nindirectsyms * @sizeOf(u32); seg.inner.filesize += needed_size; - log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{ + log.warn("writing indirect symbol table from 0x{x} to 0x{x}", .{ dysymtab.indirectsymoff, dysymtab.indirectsymoff + needed_size, }); @@ -2124,7 +2155,7 @@ fn writeStringTable(self: *Zld) !void { symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.items.len, @alignOf(u64))); seg.inner.filesize += symtab.strsize; - log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); + log.warn("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); try self.file.?.pwriteAll(self.strtab.items, symtab.stroff); @@ -2150,7 +2181,7 @@ fn writeCodeSignaturePadding(self: *Zld) !void { seg.inner.filesize += needed_size; seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?); - log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); + log.warn("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); // Pad out the space. We need to do this to calculate valid hashes for everything in the file // except for code signature data. @@ -2176,7 +2207,7 @@ fn writeCodeSignature(self: *Zld) !void { var stream = std.io.fixedBufferStream(buffer); try code_sig.write(stream.writer()); - log.debug("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); + log.warn("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); try self.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); } @@ -2195,7 +2226,7 @@ fn writeLoadCommands(self: *Zld) !void { } const off = @sizeOf(macho.mach_header_64); - log.debug("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds }); + log.warn("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds }); try self.file.?.pwriteAll(buffer, off); } @@ -2233,7 +2264,7 @@ fn writeHeader(self: *Zld) !void { for (self.load_commands.items) |cmd| { header.sizeofcmds += cmd.cmdsize(); } - log.debug("writing Mach-O header {}", .{header}); + log.warn("writing Mach-O header {}", .{header}); try self.file.?.pwriteAll(mem.asBytes(&header), 0); } @@ -2247,7 +2278,7 @@ pub fn makeStaticString(bytes: []const u8) [16]u8 { fn makeString(self: *Zld, bytes: []const u8) !u32 { try self.strtab.ensureCapacity(self.allocator, self.strtab.items.len + bytes.len + 1); const offset = @intCast(u32, self.strtab.items.len); - log.debug("writing new string '{s}' into string table at offset 0x{x}", .{ bytes, offset }); + log.warn("writing new string '{s}' into string table at offset 0x{x}", .{ bytes, offset }); self.strtab.appendSliceAssumeCapacity(bytes); self.strtab.appendAssumeCapacity(0); return offset; From 586c704212b5453281cc27626f1ae46982a62d16 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 25 Feb 2021 22:04:28 +0100 Subject: [PATCH 25/56] zld: pass stage2 tests linked with zld! --- src/link/MachO/Zld.zig | 170 ++++++++++++++++++++++----------------- src/link/MachO/reloc.zig | 27 ++++++- 2 files changed, 119 insertions(+), 78 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 838e16b0e5..0a587538fe 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -252,9 +252,9 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8) !void { try self.populateMetadata(); try self.parseInputFiles(files); try self.resolveImports(); - self.allocateTextSegment(); - self.allocateDataSegment(); - self.allocateLinkeditSegment(); + try self.allocateTextSegment(); + try self.allocateDataSegment(); + try self.allocateLinkeditSegment(); try self.writeStubHelperCommon(); try self.resolveSymbols(); try self.doRelocs(); @@ -317,13 +317,20 @@ fn parseObjectFile(self: *Zld, object: *const Object) !void { if (mem.eql(u8, sectname, "__thread_vars")) { self.tlv_section_index = sect_index; } + log.warn("{s} align 0x{x}", .{ sectname, sect.@"align" }); + const alignment = switch (sect.flags) { + macho.S_4BYTE_LITERALS => 2, + macho.S_8BYTE_LITERALS => 3, + macho.S_16BYTE_LITERALS => 4, + else => sect.@"align", + }; try seg.append(self.allocator, .{ .sectname = makeStaticString(§.sectname), .segname = makeStaticString(§.segname), .addr = 0, .size = 0, .offset = 0, - .@"align" = sect.@"align", + .@"align" = alignment, .reloff = 0, .nreloc = 0, .flags = sect.flags, @@ -429,7 +436,7 @@ fn resolveImports(self: *Zld) !void { }); } -fn allocateTextSegment(self: *Zld) void { +fn allocateTextSegment(self: *Zld) !void { const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; const nexterns = @intCast(u32, self.lazy_imports.items().len); @@ -450,10 +457,16 @@ fn allocateTextSegment(self: *Zld) void { sizeofcmds += lc.cmdsize(); } - self.allocateSegment(self.text_segment_cmd_index.?, 0, sizeofcmds, true); + try self.allocateSegment( + self.text_segment_cmd_index.?, + 0, + // sizeofcmds + 10 * 4 * @sizeOf(u32), + 3140, + true, + ); } -fn allocateDataSegment(self: *Zld) void { +fn allocateDataSegment(self: *Zld) !void { const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; const nonlazy = @intCast(u32, self.nonlazy_imports.items().len); const lazy = @intCast(u32, self.lazy_imports.items().len); @@ -470,16 +483,16 @@ fn allocateDataSegment(self: *Zld) void { const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const offset = text_seg.inner.fileoff + text_seg.inner.filesize; - self.allocateSegment(self.data_segment_cmd_index.?, offset, 0, false); + try self.allocateSegment(self.data_segment_cmd_index.?, offset, 0, false); } -fn allocateLinkeditSegment(self: *Zld) void { +fn allocateLinkeditSegment(self: *Zld) !void { const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; const offset = data_seg.inner.fileoff + data_seg.inner.filesize; - self.allocateSegment(self.linkedit_segment_cmd_index.?, offset, 0, false); + try self.allocateSegment(self.linkedit_segment_cmd_index.?, offset, 0, false); } -fn allocateSegment(self: *Zld, index: u16, offset: u64, start: u64, reverse: bool) void { +fn allocateSegment(self: *Zld, index: u16, offset: u64, start: u64, reverse: bool) !void { const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].Segment.inner.vmsize; const seg = &self.load_commands.items[index].Segment; @@ -495,23 +508,27 @@ fn allocateSegment(self: *Zld, index: u16, offset: u64, start: u64, reverse: boo seg.inner.filesize = aligned_size; // Allocate section offsets - if (reverse) { - var end_off: u64 = seg.inner.fileoff + seg.inner.filesize; - var count: usize = seg.sections.items.len; - while (count > 0) : (count -= 1) { - const sec = &seg.sections.items[count - 1]; - end_off -= mem.alignForwardGeneric(u64, sec.size, @sizeOf(u32)); // TODO Should we always align to 4? - sec.offset = @intCast(u32, end_off); - sec.addr = base_vmaddr + end_off; - } - } else { - var next_off: u64 = seg.inner.fileoff; - for (seg.sections.items) |*sect| { - sect.offset = @intCast(u32, next_off); - sect.addr = base_vmaddr + next_off; - next_off += mem.alignForwardGeneric(u64, sect.size, @sizeOf(u32)); // TODO Should we always align to 4? - } + // if (reverse) { + // var end_off: u64 = seg.inner.fileoff + seg.inner.filesize; + // var count: usize = seg.sections.items.len; + // while (count > 0) : (count -= 1) { + // const sec = &seg.sections.items[count - 1]; + // const alignment = math.max(@alignOf(u32), try std.math.powi(u32, 2, sec.@"align")); + // log.warn("{s} 0x{x} alignment = 0x{x}", .{ parseName(&sec.sectname), sec.@"align", alignment }); + // end_off -= mem.alignForwardGeneric(u64, sec.size, alignment); + // sec.offset = @intCast(u32, end_off); + // sec.addr = base_vmaddr + end_off; + // } + // } else { + var next_off: u64 = seg.inner.fileoff + start; + for (seg.sections.items) |*sect| { + const alignment = math.max(@alignOf(u32), try std.math.powi(u32, 2, sect.@"align")); + log.warn("{s} 0x{x} alignment = 0x{x}", .{ parseName(§.sectname), sect.@"align", alignment }); + sect.offset = @intCast(u32, next_off); + sect.addr = base_vmaddr + next_off; + next_off += mem.alignForwardGeneric(u64, sect.size, alignment); } + // } } fn writeStubHelperCommon(self: *Zld) !void { @@ -552,33 +569,48 @@ fn writeStubHelperCommon(self: *Zld) !void { break :blk stub_helper.offset + code_size; }, .aarch64 => { - var code: [4 * @sizeOf(u32)]u8 = undefined; + var code: [6 * @sizeOf(u32)]u8 = undefined; { const target_addr = data.addr + data.size - @sizeOf(u64); const displacement = @bitCast(u21, try math.cast(i21, target_addr - stub_helper.addr)); // adr x17, disp mem.writeIntLittle(u32, code[0..4], Arm64.adr(17, displacement).toU32()); + // TODO check if adr is enough and expand into adrp + add if not. + // nop in case we need to expand adr for adrp followed by add. + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); } // stp x16, x17, [sp, #-16]! - code[4] = 0xf0; - code[5] = 0x47; - code[6] = 0xbf; - code[7] = 0xa9; - { + code[8] = 0xf0; + code[9] = 0x47; + code[10] = 0xbf; + code[11] = 0xa9; + binder: { const dyld_stub_binder = self.nonlazy_imports.get("dyld_stub_binder").?; const addr = (got.addr + dyld_stub_binder.index * @sizeOf(u64)); - const displacement = try math.divExact(u64, addr - stub_helper.addr - 2 * @sizeOf(u32), 4); - const literal = try math.cast(u19, displacement); + const displacement = math.divExact(u64, addr - stub_helper.addr - 3 * @sizeOf(u32), 4) catch |_| { + log.warn("0x{x}", .{addr - stub_helper.addr - 3 * @sizeOf(u32)}); + // Pad with nop to please division. + // nop + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.nop().toU32()); + // ldr x16, label + const disp = try math.divExact(u64, addr - stub_helper.addr - 4 * @sizeOf(u32), 4); + const literal = try math.cast(u19, disp); // TODO use adrp + add if we exceed the range. + mem.writeIntLittle(u32, code[16..20], Arm64.ldr(16, literal, 1).toU32()); + break :binder; + }; + const literal = try math.cast(u19, displacement); // TODO use adrp + add if we exceed the range. // ldr x16, label - mem.writeIntLittle(u32, code[8..12], Arm64.ldr(16, literal, 1).toU32()); + mem.writeIntLittle(u32, code[12..16], Arm64.ldr(16, literal, 1).toU32()); + // nop + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.nop().toU32()); } // br x16 - code[12] = 0x00; - code[13] = 0x02; - code[14] = 0x1f; - code[15] = 0xd6; + code[20] = 0x00; + code[21] = 0x02; + code[22] = 0x1f; + code[23] = 0xd6; try self.file.?.pwriteAll(&code, stub_helper.offset); - break :blk stub_helper.offset + 4 * @sizeOf(u32); + break :blk stub_helper.offset + 6 * @sizeOf(u32); }, else => unreachable, } @@ -635,12 +667,14 @@ fn writeStub(self: *Zld, index: u32) !void { }, .aarch64 => { assert(la_ptr_addr >= stub_addr); - const displacement = try math.divExact(u64, la_ptr_addr - stub_addr, 4); + const displacement = try math.divExact(u64, la_ptr_addr - stub_addr - @sizeOf(u32), 4); const literal = try math.cast(u19, displacement); + // nop + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); // ldr x16, literal - mem.writeIntLittle(u32, code[0..4], Arm64.ldr(16, literal, 1).toU32()); + mem.writeIntLittle(u32, code[4..8], Arm64.ldr(16, literal, 1).toU32()); // br x16 - mem.writeIntLittle(u32, code[4..8], Arm64.br(16).toU32()); + mem.writeIntLittle(u32, code[8..12], Arm64.br(16).toU32()); }, else => unreachable, } @@ -722,7 +756,8 @@ fn resolveSymbols(self: *Zld) !void { const sym_name = object.getString(sym.n_strx); if (isLocal(&sym) and self.locals.get(sym_name) != null) { - log.warn("symbol '{s}' already exists; skipping", .{sym_name}); + log.warn("local symbol '{s}' defined multiple times; removing", .{sym_name}); + self.locals.swapRemoveAssertDiscard(sym_name); continue; } @@ -978,12 +1013,6 @@ fn doRelocs(self: *Zld) !void { const this_page = @intCast(i32, this_addr >> 12); const target_page = @intCast(i32, ta >> 12); const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); - if (pages == 0) { - // No need to execute adrp. Instead, replace with a nop. - log.warn(" | replacing ADRP with NOP", .{}); - mem.writeIntLittle(u32, inst, aarch64.Instruction.nop().toU32()); - continue; - } log.warn(" | moving by {} pages", .{pages}); var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Address), inst); parsed.immhi = @truncate(u19, pages >> 2); @@ -1000,12 +1029,6 @@ fn doRelocs(self: *Zld) !void { var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Add), inst); const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); - if (narrowed == 0) { - // No need to execute add. Instead, replace with a nop. - log.warn(" | replacing ADD with NOP", .{}); - mem.writeIntLittle(u32, inst, aarch64.Instruction.nop().toU32()); - continue; - } parsed.offset = narrowed; } else { log.warn(" | detected LDR/STR opcode", .{}); @@ -1013,20 +1036,18 @@ fn doRelocs(self: *Zld) !void { var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.LoadRegister), inst); const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); - if (narrowed == 0) { - // No need to execute ldr/str. Instead, replace with a nop. - log.warn(" | replacing LDR/STR with NOP", .{}); - mem.writeIntLittle(u32, inst, aarch64.Instruction.nop().toU32()); - continue; - } - const denom: u12 = if (parsed.size == 1) 8 else 4; - const offset = math.divExact(u12, narrowed, denom) catch |_| { - // If we are here, then this means we are not able to divide the offset - // exactly by the required denominator. Therefore, we will use add instead of - // ldr as we expect ldr to follow this instruction nonetheless. - // TODO I believe ldr/str can only occur for GOT_LOAD_PAGEOFF12. - mem.writeIntLittle(u32, inst, Arm64.add(parsed.rn, parsed.rn, narrowed, parsed.size).toU32()); - continue; + const offset: u12 = blk: { + if (parsed.size == 0) { + break :blk narrowed; + } else { + const denom: u4 = try math.powi(u4, 2, parsed.size); + const offf = math.divExact(u12, narrowed, denom) catch |_| { + log.warn(" | narrowed 0x{x}", .{narrowed}); + log.warn(" | denom 0x{x}", .{denom}); + continue; + }; + break :blk offf; + } }; parsed.offset = offset; } @@ -1046,7 +1067,7 @@ fn doRelocs(self: *Zld) !void { break :blk .{ .rt = curr.rt, .rn = curr.rn, .size = curr.size }; } else { const curr = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.LoadRegister), inst); - break :blk .{ .rt = curr.rt, .rn = curr.rn, .size = curr.size }; + break :blk .{ .rt = curr.rt, .rn = curr.rn, .size = @truncate(u1, curr.size) }; } }; const ta = if (addend) |a| target_addr + a else target_addr; @@ -1145,6 +1166,7 @@ fn relocTargetAddr(self: *Zld, object: Object, rel: macho.relocation_info, next_ .segname = source_sect.segname, .sectname = source_sect.sectname, }).?; + log.warn(" | symbol local to object", .{}); break :blk target_space.address + sym.n_value - source_sect.addr; } else if (isImport(&sym)) { // Relocate to either the artifact's local symbol, or an import from @@ -1267,7 +1289,7 @@ fn populateMetadata(self: *Zld) !void { }; const stub_size: u4 = switch (self.arch.?) { .x86_64 => 6, - .aarch64 => 2 * @sizeOf(u32), + .aarch64 => 3 * @sizeOf(u32), else => unreachable, // unhandled architecture type }; try text_seg.append(self.allocator, .{ @@ -1298,7 +1320,7 @@ fn populateMetadata(self: *Zld) !void { .aarch64 => 2, else => unreachable, // unhandled architecture type }; - const stub_helper_size: u5 = switch (self.arch.?) { + const stub_helper_size: u6 = switch (self.arch.?) { .x86_64 => 15, .aarch64 => 6 * @sizeOf(u32), else => unreachable, diff --git a/src/link/MachO/reloc.zig b/src/link/MachO/reloc.zig index deb07dc42a..9809eddfb8 100644 --- a/src/link/MachO/reloc.zig +++ b/src/link/MachO/reloc.zig @@ -26,8 +26,7 @@ pub const Arm64 = union(enum) { rn: u5, offset: u12, _1: u8 = 0b111_0_01_01, - size: u1, - _2: u1 = 0b1, + size: u2, }, LoadLiteral: packed struct { reg: u5, @@ -135,13 +134,33 @@ pub const Arm64 = union(enum) { }; } - pub fn ldrr(rt: u5, rn: u5, offset: u12, size: u1) Arm64 { + pub fn ldrq(rt: u5, rn: u5, offset: u12) Arm64 { return Arm64{ .LoadRegister = .{ .rt = rt, .rn = rn, .offset = offset, - .size = size, + .size = 0b11, + }, + }; + } + pub fn ldrh(rt: u5, rn: u5, offset: u12) Arm64 { + return Arm64{ + .LoadRegister = .{ + .rt = rt, + .rn = rn, + .offset = offset, + .size = 0b01, + }, + }; + } + pub fn ldrb(rt: u5, rn: u5, offset: u12) Arm64 { + return Arm64{ + .LoadRegister = .{ + .rt = rt, + .rn = rn, + .offset = offset, + .size = 0b00, }, }; } From 14590795b18585582c79d77afa65ce0cb9bf4744 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 26 Feb 2021 23:38:58 +0100 Subject: [PATCH 26/56] zld: cleanup section alignment when allocating --- src/link/MachO/Zld.zig | 46 +++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 0a587538fe..3519e138b2 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -460,8 +460,7 @@ fn allocateTextSegment(self: *Zld) !void { try self.allocateSegment( self.text_segment_cmd_index.?, 0, - // sizeofcmds + 10 * 4 * @sizeOf(u32), - 3140, + sizeofcmds, true, ); } @@ -508,27 +507,23 @@ fn allocateSegment(self: *Zld, index: u16, offset: u64, start: u64, reverse: boo seg.inner.filesize = aligned_size; // Allocate section offsets - // if (reverse) { - // var end_off: u64 = seg.inner.fileoff + seg.inner.filesize; - // var count: usize = seg.sections.items.len; - // while (count > 0) : (count -= 1) { - // const sec = &seg.sections.items[count - 1]; - // const alignment = math.max(@alignOf(u32), try std.math.powi(u32, 2, sec.@"align")); - // log.warn("{s} 0x{x} alignment = 0x{x}", .{ parseName(&sec.sectname), sec.@"align", alignment }); - // end_off -= mem.alignForwardGeneric(u64, sec.size, alignment); - // sec.offset = @intCast(u32, end_off); - // sec.addr = base_vmaddr + end_off; - // } - // } else { - var next_off: u64 = seg.inner.fileoff + start; - for (seg.sections.items) |*sect| { - const alignment = math.max(@alignOf(u32), try std.math.powi(u32, 2, sect.@"align")); - log.warn("{s} 0x{x} alignment = 0x{x}", .{ parseName(§.sectname), sect.@"align", alignment }); - sect.offset = @intCast(u32, next_off); - sect.addr = base_vmaddr + next_off; - next_off += mem.alignForwardGeneric(u64, sect.size, alignment); + if (reverse) { + var end_off: u64 = seg.inner.fileoff + seg.inner.filesize; + var count: usize = seg.sections.items.len; + while (count > 0) : (count -= 1) { + const sec = &seg.sections.items[count - 1]; + end_off -= mem.alignForwardGeneric(u64, sec.size, @alignOf(u64)); // TODO is 8-byte aligned correct? + sec.offset = @intCast(u32, end_off); + sec.addr = base_vmaddr + end_off; + } + } else { + var next_off: u64 = seg.inner.fileoff + start; + for (seg.sections.items) |*sect| { + sect.offset = @intCast(u32, next_off); + sect.addr = base_vmaddr + next_off; + next_off += mem.alignForwardGeneric(u64, sect.size, @alignOf(u64)); // TODO is 8-byte aligned correct? + } } - // } } fn writeStubHelperCommon(self: *Zld) !void { @@ -1041,12 +1036,7 @@ fn doRelocs(self: *Zld) !void { break :blk narrowed; } else { const denom: u4 = try math.powi(u4, 2, parsed.size); - const offf = math.divExact(u12, narrowed, denom) catch |_| { - log.warn(" | narrowed 0x{x}", .{narrowed}); - log.warn(" | denom 0x{x}", .{denom}); - continue; - }; - break :blk offf; + break :blk try math.divExact(u12, narrowed, denom); } }; parsed.offset = offset; From d2008db6235f2544a1aa3dbd419db911bc858cbd Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 27 Feb 2021 01:03:36 +0100 Subject: [PATCH 27/56] zld: bullet-proof stubs for long jumps --- src/link/MachO/Zld.zig | 139 ++++++++++++++++++++++++++++------------- 1 file changed, 95 insertions(+), 44 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 3519e138b2..a1bcc6a09a 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -252,9 +252,9 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8) !void { try self.populateMetadata(); try self.parseInputFiles(files); try self.resolveImports(); - try self.allocateTextSegment(); - try self.allocateDataSegment(); - try self.allocateLinkeditSegment(); + self.allocateTextSegment(); + self.allocateDataSegment(); + self.allocateLinkeditSegment(); try self.writeStubHelperCommon(); try self.resolveSymbols(); try self.doRelocs(); @@ -317,20 +317,13 @@ fn parseObjectFile(self: *Zld, object: *const Object) !void { if (mem.eql(u8, sectname, "__thread_vars")) { self.tlv_section_index = sect_index; } - log.warn("{s} align 0x{x}", .{ sectname, sect.@"align" }); - const alignment = switch (sect.flags) { - macho.S_4BYTE_LITERALS => 2, - macho.S_8BYTE_LITERALS => 3, - macho.S_16BYTE_LITERALS => 4, - else => sect.@"align", - }; try seg.append(self.allocator, .{ .sectname = makeStaticString(§.sectname), .segname = makeStaticString(§.segname), .addr = 0, .size = 0, .offset = 0, - .@"align" = alignment, + .@"align" = sect.@"align", .reloff = 0, .nreloc = 0, .flags = sect.flags, @@ -436,7 +429,7 @@ fn resolveImports(self: *Zld) !void { }); } -fn allocateTextSegment(self: *Zld) !void { +fn allocateTextSegment(self: *Zld) void { const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; const nexterns = @intCast(u32, self.lazy_imports.items().len); @@ -457,7 +450,7 @@ fn allocateTextSegment(self: *Zld) !void { sizeofcmds += lc.cmdsize(); } - try self.allocateSegment( + self.allocateSegment( self.text_segment_cmd_index.?, 0, sizeofcmds, @@ -465,7 +458,7 @@ fn allocateTextSegment(self: *Zld) !void { ); } -fn allocateDataSegment(self: *Zld) !void { +fn allocateDataSegment(self: *Zld) void { const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; const nonlazy = @intCast(u32, self.nonlazy_imports.items().len); const lazy = @intCast(u32, self.lazy_imports.items().len); @@ -482,16 +475,16 @@ fn allocateDataSegment(self: *Zld) !void { const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const offset = text_seg.inner.fileoff + text_seg.inner.filesize; - try self.allocateSegment(self.data_segment_cmd_index.?, offset, 0, false); + self.allocateSegment(self.data_segment_cmd_index.?, offset, 0, false); } -fn allocateLinkeditSegment(self: *Zld) !void { +fn allocateLinkeditSegment(self: *Zld) void { const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; const offset = data_seg.inner.fileoff + data_seg.inner.filesize; - try self.allocateSegment(self.linkedit_segment_cmd_index.?, offset, 0, false); + self.allocateSegment(self.linkedit_segment_cmd_index.?, offset, 0, false); } -fn allocateSegment(self: *Zld, index: u16, offset: u64, start: u64, reverse: bool) !void { +fn allocateSegment(self: *Zld, index: u16, offset: u64, start: u64, reverse: bool) void { const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].Segment.inner.vmsize; const seg = &self.load_commands.items[index].Segment; @@ -565,39 +558,72 @@ fn writeStubHelperCommon(self: *Zld) !void { }, .aarch64 => { var code: [6 * @sizeOf(u32)]u8 = undefined; - { + data_blk_outer: { + const this_addr = stub_helper.addr; const target_addr = data.addr + data.size - @sizeOf(u64); - const displacement = @bitCast(u21, try math.cast(i21, target_addr - stub_helper.addr)); - // adr x17, disp - mem.writeIntLittle(u32, code[0..4], Arm64.adr(17, displacement).toU32()); - // TODO check if adr is enough and expand into adrp + add if not. - // nop in case we need to expand adr for adrp followed by add. - mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); + data_blk: { + const displacement = math.cast(i21, target_addr - this_addr) catch |_| break :data_blk; + // adr x17, disp + mem.writeIntLittle(u32, code[0..4], Arm64.adr(17, @bitCast(u21, displacement)).toU32()); + // nop + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); + break :data_blk_outer; + } + data_blk: { + const new_this_addr = this_addr + @sizeOf(u32); + const displacement = math.cast(i21, target_addr - new_this_addr) catch |_| break :data_blk; + // nop + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); + // adr x17, disp + mem.writeIntLittle(u32, code[4..8], Arm64.adr(17, @bitCast(u21, displacement)).toU32()); + break :data_blk_outer; + } + // Jump is too big, replace adr with adrp and add. + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); + mem.writeIntLittle(u32, code[0..4], Arm64.adrp(17, pages).toU32()); + const narrowed = @truncate(u12, target_addr); + mem.writeIntLittle(u32, code[4..8], Arm64.add(17, 17, narrowed, 1).toU32()); } // stp x16, x17, [sp, #-16]! code[8] = 0xf0; code[9] = 0x47; code[10] = 0xbf; code[11] = 0xa9; - binder: { + binder_blk_outer: { const dyld_stub_binder = self.nonlazy_imports.get("dyld_stub_binder").?; - const addr = (got.addr + dyld_stub_binder.index * @sizeOf(u64)); - const displacement = math.divExact(u64, addr - stub_helper.addr - 3 * @sizeOf(u32), 4) catch |_| { - log.warn("0x{x}", .{addr - stub_helper.addr - 3 * @sizeOf(u32)}); + const this_addr = stub_helper.addr + 3 * @sizeOf(u32); + const target_addr = (got.addr + dyld_stub_binder.index * @sizeOf(u64)); + binder_blk: { + const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :binder_blk; + const literal = math.cast(u18, displacement) catch |_| break :binder_blk; + // ldr x16, label + mem.writeIntLittle(u32, code[12..16], Arm64.ldr(16, literal, 1).toU32()); + // nop + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.nop().toU32()); + break :binder_blk_outer; + } + binder_blk: { + const new_this_addr = this_addr + @sizeOf(u32); + const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :binder_blk; + const literal = math.cast(u18, displacement) catch |_| break :binder_blk; + log.warn("2: disp=0x{x}, literal=0x{x}", .{ displacement, literal }); // Pad with nop to please division. // nop mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.nop().toU32()); // ldr x16, label - const disp = try math.divExact(u64, addr - stub_helper.addr - 4 * @sizeOf(u32), 4); - const literal = try math.cast(u19, disp); // TODO use adrp + add if we exceed the range. mem.writeIntLittle(u32, code[16..20], Arm64.ldr(16, literal, 1).toU32()); - break :binder; - }; - const literal = try math.cast(u19, displacement); // TODO use adrp + add if we exceed the range. - // ldr x16, label - mem.writeIntLittle(u32, code[12..16], Arm64.ldr(16, literal, 1).toU32()); - // nop - mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.nop().toU32()); + break :binder_blk_outer; + } + // Use adrp followed by ldr(immediate). + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); + mem.writeIntLittle(u32, code[12..16], Arm64.adrp(16, pages).toU32()); + const narrowed = @truncate(u12, target_addr); + const offset = try math.divExact(u12, narrowed, 8); + mem.writeIntLittle(u32, code[16..20], Arm64.ldrq(16, 16, offset).toU32()); } // br x16 code[20] = 0x00; @@ -662,12 +688,37 @@ fn writeStub(self: *Zld, index: u32) !void { }, .aarch64 => { assert(la_ptr_addr >= stub_addr); - const displacement = try math.divExact(u64, la_ptr_addr - stub_addr - @sizeOf(u32), 4); - const literal = try math.cast(u19, displacement); - // nop - mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); - // ldr x16, literal - mem.writeIntLittle(u32, code[4..8], Arm64.ldr(16, literal, 1).toU32()); + outer: { + const this_addr = stub_addr; + const target_addr = la_ptr_addr; + inner: { + const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :inner; + const literal = math.cast(u18, displacement) catch |_| break :inner; + // ldr x16, literal + mem.writeIntLittle(u32, code[0..4], Arm64.ldr(16, literal, 1).toU32()); + // nop + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); + break :outer; + } + inner: { + const new_this_addr = this_addr + @sizeOf(u32); + const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :inner; + const literal = math.cast(u18, displacement) catch |_| break :inner; + // nop + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); + // ldr x16, literal + mem.writeIntLittle(u32, code[4..8], Arm64.ldr(16, literal, 1).toU32()); + break :outer; + } + // Use adrp followed by ldr(immediate). + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); + mem.writeIntLittle(u32, code[0..4], Arm64.adrp(16, pages).toU32()); + const narrowed = @truncate(u12, target_addr); + const offset = try math.divExact(u12, narrowed, 8); + mem.writeIntLittle(u32, code[4..8], Arm64.ldrq(16, 16, offset).toU32()); + } // br x16 mem.writeIntLittle(u32, code[8..12], Arm64.br(16).toU32()); }, From 7e329478718ef5545083b1123d1df437101b8a5b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 27 Feb 2021 21:32:03 +0100 Subject: [PATCH 28/56] zld: add nop to reloc module --- src/link/MachO/Zld.zig | 13 ++++++------- src/link/MachO/reloc.zig | 10 ++++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index a1bcc6a09a..d49e379a38 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -16,7 +16,6 @@ const CodeSignature = @import("CodeSignature.zig"); const Archive = @import("Archive.zig"); const Object = @import("Object.zig"); const Trie = @import("Trie.zig"); -const aarch64 = @import("../../codegen/aarch64.zig"); usingnamespace @import("commands.zig"); usingnamespace @import("bind.zig"); @@ -566,14 +565,14 @@ fn writeStubHelperCommon(self: *Zld) !void { // adr x17, disp mem.writeIntLittle(u32, code[0..4], Arm64.adr(17, @bitCast(u21, displacement)).toU32()); // nop - mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); + mem.writeIntLittle(u32, code[4..8], Arm64.nop().toU32()); break :data_blk_outer; } data_blk: { const new_this_addr = this_addr + @sizeOf(u32); const displacement = math.cast(i21, target_addr - new_this_addr) catch |_| break :data_blk; // nop - mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); + mem.writeIntLittle(u32, code[0..4], Arm64.nop().toU32()); // adr x17, disp mem.writeIntLittle(u32, code[4..8], Arm64.adr(17, @bitCast(u21, displacement)).toU32()); break :data_blk_outer; @@ -601,7 +600,7 @@ fn writeStubHelperCommon(self: *Zld) !void { // ldr x16, label mem.writeIntLittle(u32, code[12..16], Arm64.ldr(16, literal, 1).toU32()); // nop - mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.nop().toU32()); + mem.writeIntLittle(u32, code[16..20], Arm64.nop().toU32()); break :binder_blk_outer; } binder_blk: { @@ -611,7 +610,7 @@ fn writeStubHelperCommon(self: *Zld) !void { log.warn("2: disp=0x{x}, literal=0x{x}", .{ displacement, literal }); // Pad with nop to please division. // nop - mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.nop().toU32()); + mem.writeIntLittle(u32, code[12..16], Arm64.nop().toU32()); // ldr x16, label mem.writeIntLittle(u32, code[16..20], Arm64.ldr(16, literal, 1).toU32()); break :binder_blk_outer; @@ -697,7 +696,7 @@ fn writeStub(self: *Zld, index: u32) !void { // ldr x16, literal mem.writeIntLittle(u32, code[0..4], Arm64.ldr(16, literal, 1).toU32()); // nop - mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); + mem.writeIntLittle(u32, code[4..8], Arm64.nop().toU32()); break :outer; } inner: { @@ -705,7 +704,7 @@ fn writeStub(self: *Zld, index: u32) !void { const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :inner; const literal = math.cast(u18, displacement) catch |_| break :inner; // nop - mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); + mem.writeIntLittle(u32, code[0..4], Arm64.nop().toU32()); // ldr x16, literal mem.writeIntLittle(u32, code[4..8], Arm64.ldr(16, literal, 1).toU32()); break :outer; diff --git a/src/link/MachO/reloc.zig b/src/link/MachO/reloc.zig index 9809eddfb8..26af40443e 100644 --- a/src/link/MachO/reloc.zig +++ b/src/link/MachO/reloc.zig @@ -42,6 +42,9 @@ pub const Arm64 = union(enum) { _1: u9 = 0b0_0_100010_0, size: u1, }, + Nop: packed struct { + fixed: u32 = 0b1101010100_0_00_011_0010_0000_000_11111, + }, pub fn toU32(self: Arm64) u32 { const as_u32 = switch (self) { @@ -51,6 +54,7 @@ pub const Arm64 = union(enum) { .LoadRegister => |x| @bitCast(u32, x), .LoadLiteral => |x| @bitCast(u32, x), .Add => |x| @bitCast(u32, x), + .Nop => |x| @bitCast(u32, x), }; return as_u32; } @@ -165,6 +169,12 @@ pub const Arm64 = union(enum) { }; } + pub fn nop() Arm64 { + return Arm64{ + .Nop = .{}, + }; + } + pub fn isArithmetic(inst: *const [4]u8) bool { const group_decode = @truncate(u5, inst[3]); log.debug("{b}", .{group_decode}); From 7cbdbab3765bf1eb5f213b04054b82c46d876588 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 27 Feb 2021 22:50:05 +0100 Subject: [PATCH 29/56] zld: differentiate locals from globals --- src/link/MachO/Zld.zig | 177 ++++++++++++++++++++++++++++++++--------- 1 file changed, 138 insertions(+), 39 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index d49e379a38..579fc0f548 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -60,7 +60,7 @@ tlv_section_index: ?u16 = null, la_symbol_ptr_section_index: ?u16 = null, data_section_index: ?u16 = null, -locals: std.StringArrayHashMapUnmanaged(macho.nlist_64) = .{}, +locals: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(Symbol)) = .{}, exports: std.StringArrayHashMapUnmanaged(macho.nlist_64) = .{}, nonlazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, lazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, @@ -74,6 +74,18 @@ stub_helper_stubs_start_off: ?u64 = null, segments_directory: std.AutoHashMapUnmanaged([16]u8, u16) = .{}, directory: std.AutoHashMapUnmanaged(DirectoryKey, DirectoryEntry) = .{}, +const Symbol = struct { + inner: macho.nlist_64, + tt: Type, + object: *Object, + + const Type = enum { + Local, + WeakGlobal, + Global, + }; +}; + const DirectoryKey = struct { segname: [16]u8, sectname: [16]u8, @@ -198,6 +210,7 @@ pub fn deinit(self: *Zld) void { self.exports.deinit(self.allocator); for (self.locals.items()) |*entry| { self.allocator.free(entry.key); + entry.value.deinit(self.allocator); } self.locals.deinit(self.allocator); for (self.objects.items) |*object| { @@ -773,7 +786,7 @@ fn resolveSymbols(self: *Zld) !void { var next_address = std.AutoHashMap(DirectoryKey, Address).init(self.allocator); defer next_address.deinit(); - for (self.objects.items) |object| { + for (self.objects.items) |*object| { const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; for (seg.sections.items) |sect| { @@ -799,11 +812,32 @@ fn resolveSymbols(self: *Zld) !void { if (isImport(&sym)) continue; const sym_name = object.getString(sym.n_strx); + const out_name = try self.allocator.dupe(u8, sym_name); + const locs = try self.locals.getOrPut(self.allocator, out_name); + defer { + if (locs.found_existing) self.allocator.free(out_name); + } - if (isLocal(&sym) and self.locals.get(sym_name) != null) { - log.warn("local symbol '{s}' defined multiple times; removing", .{sym_name}); - self.locals.swapRemoveAssertDiscard(sym_name); - continue; + if (!locs.found_existing) { + locs.entry.value = .{}; + } + + const tt: Symbol.Type = blk: { + if (isLocal(&sym)) { + break :blk .Local; + } else if (isWeakDef(&sym)) { + break :blk .WeakGlobal; + } else { + break :blk .Global; + } + }; + if (tt == .Global) { + for (locs.entry.value.items) |ss| { + if (ss.tt == .Global) { + log.err("symbol '{s}' defined multiple times", .{sym_name}); + return error.MultipleSymbolDefinitions; + } + } } const sect = seg.sections.items[sym.n_sect - 1]; @@ -813,10 +847,9 @@ fn resolveSymbols(self: *Zld) !void { }; const res = self.directory.get(key) orelse continue; - const n_strx = try self.makeString(sym_name); const n_value = sym.n_value - sect.addr + next_address.get(key).?.addr; - log.warn("resolving '{s}' as local symbol at 0x{x}", .{ sym_name, n_value }); + log.warn("resolving '{s}':{} as {s} symbol at 0x{x}", .{ sym_name, sym, tt, n_value }); var n_sect = res.sect_index + 1; for (self.load_commands.items) |sseg, i| { @@ -826,13 +859,17 @@ fn resolveSymbols(self: *Zld) !void { n_sect += @intCast(u16, sseg.Segment.sections.items.len); } - var out_name = try self.allocator.dupe(u8, sym_name); - try self.locals.putNoClobber(self.allocator, out_name, .{ - .n_strx = n_strx, - .n_value = n_value, - .n_type = macho.N_SECT, - .n_desc = sym.n_desc, - .n_sect = @intCast(u8, n_sect), + const n_strx = try self.makeString(sym_name); + try locs.entry.value.append(self.allocator, .{ + .inner = .{ + .n_strx = n_strx, + .n_value = n_value, + .n_type = macho.N_SECT, + .n_desc = sym.n_desc, + .n_sect = @intCast(u8, n_sect), + }, + .tt = tt, + .object = object, }); } } @@ -1212,8 +1249,25 @@ fn relocTargetAddr(self: *Zld, object: Object, rel: macho.relocation_info, next_ // Relocate to either the artifact's local symbol, or an import from // shared library. const sym_name = object.getString(sym.n_strx); - if (self.locals.get(sym_name)) |loc| { - break :blk loc.n_value; + if (self.locals.get(sym_name)) |locs| { + var n_value: ?u64 = null; + for (locs.items) |loc| { + switch (loc.tt) { + .Global => { + n_value = loc.inner.n_value; + break; + }, + .WeakGlobal => { + n_value = loc.inner.n_value; + }, + .Local => {}, + } + } + if (n_value) |v| { + break :blk v; + } + log.err("local symbol export '{s}' not found", .{sym_name}); + return error.LocalSymbolExportNotFound; } else if (self.lazy_imports.get(sym_name)) |ext| { const segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const stubs = segment.sections.items[self.stubs_section_index.?]; @@ -1710,19 +1764,37 @@ fn setEntryPoint(self: *Zld) !void { // entrypoint. For now, assume default of `_main`. const seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const text = seg.sections.items[self.text_section_index.?]; - const entry_sym = self.locals.get("_main") orelse return error.MissingMainEntrypoint; + const entry_syms = self.locals.get("_main") orelse return error.MissingMainEntrypoint; + + var entry_sym: ?macho.nlist_64 = null; + for (entry_syms.items) |es| { + switch (es.tt) { + .Global => { + entry_sym = es.inner; + break; + }, + .WeakGlobal => { + entry_sym = es.inner; + }, + .Local => {}, + } + } + if (entry_sym == null) { + log.err("no (weak) global definition of _main found", .{}); + return error.MissingMainEntrypoint; + } const name = try self.allocator.dupe(u8, "_main"); try self.exports.putNoClobber(self.allocator, name, .{ - .n_strx = entry_sym.n_strx, - .n_value = entry_sym.n_value, + .n_strx = entry_sym.?.n_strx, + .n_value = entry_sym.?.n_value, .n_type = macho.N_SECT | macho.N_EXT, - .n_desc = entry_sym.n_desc, - .n_sect = entry_sym.n_sect, + .n_desc = entry_sym.?.n_desc, + .n_sect = entry_sym.?.n_sect, }); const ec = &self.load_commands.items[self.main_cmd_index.?].Main; - ec.entryoff = @intCast(u32, entry_sym.n_value - seg.inner.vmaddr); + ec.entryoff = @intCast(u32, entry_sym.?.n_value - seg.inner.vmaddr); } fn writeRebaseInfoTable(self: *Zld) !void { @@ -1968,9 +2040,9 @@ fn writeDebugInfo(self: *Zld) !void { var stabs = std.ArrayList(macho.nlist_64).init(self.allocator); defer stabs.deinit(); - for (self.objects.items) |object| { + for (self.objects.items) |*object| { var debug_info = blk: { - var di = try DebugInfo.parseFromObject(self.allocator, object); + var di = try DebugInfo.parseFromObject(self.allocator, object.*); break :blk di orelse continue; }; defer debug_info.deinit(self.allocator); @@ -2017,7 +2089,12 @@ fn writeDebugInfo(self: *Zld) !void { for (object.symtab.items) |source_sym| { const symname = object.getString(source_sym.n_strx); const source_addr = source_sym.n_value; - const target_sym = self.locals.get(symname) orelse continue; + const target_syms = self.locals.get(symname) orelse continue; + const target_sym: Symbol = blk: { + for (target_syms.items) |ts| { + if (ts.object == object) break :blk ts; + } else continue; + }; const maybe_size = blk: for (debug_info.inner.func_list.items) |func| { if (func.pc_range) |range| { @@ -2031,16 +2108,16 @@ fn writeDebugInfo(self: *Zld) !void { try stabs.append(.{ .n_strx = 0, .n_type = macho.N_BNSYM, - .n_sect = target_sym.n_sect, + .n_sect = target_sym.inner.n_sect, .n_desc = 0, - .n_value = target_sym.n_value, + .n_value = target_sym.inner.n_value, }); try stabs.append(.{ - .n_strx = target_sym.n_strx, + .n_strx = target_sym.inner.n_strx, .n_type = macho.N_FUN, - .n_sect = target_sym.n_sect, + .n_sect = target_sym.inner.n_sect, .n_desc = 0, - .n_value = target_sym.n_value, + .n_value = target_sym.inner.n_value, }); try stabs.append(.{ .n_strx = 0, @@ -2052,18 +2129,18 @@ fn writeDebugInfo(self: *Zld) !void { try stabs.append(.{ .n_strx = 0, .n_type = macho.N_ENSYM, - .n_sect = target_sym.n_sect, + .n_sect = target_sym.inner.n_sect, .n_desc = 0, .n_value = size, }); } else { // TODO need a way to differentiate symbols: global, static, local, etc. try stabs.append(.{ - .n_strx = target_sym.n_strx, + .n_strx = target_sym.inner.n_strx, .n_type = macho.N_STSYM, - .n_sect = target_sym.n_sect, + .n_sect = target_sym.inner.n_sect, .n_desc = 0, - .n_value = target_sym.n_value, + .n_value = target_sym.inner.n_value, }); } } @@ -2102,14 +2179,32 @@ fn writeSymbolTable(self: *Zld) !void { const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const nlocals = self.locals.items().len; var locals = std.ArrayList(macho.nlist_64).init(self.allocator); defer locals.deinit(); - try locals.ensureCapacity(nlocals); - for (self.locals.items()) |entry| { - locals.appendAssumeCapacity(entry.value); + for (self.locals.items()) |entries| { + log.warn("'{s}': {} entries", .{ entries.key, entries.value.items.len }); + var symbol: ?macho.nlist_64 = null; + for (entries.value.items) |entry| { + log.warn(" | {}", .{entry.inner}); + log.warn(" | {}", .{entry.tt}); + log.warn(" | {s}", .{entry.object.name}); + switch (entry.tt) { + .Global => { + symbol = entry.inner; + break; + }, + .WeakGlobal => { + symbol = entry.inner; + }, + .Local => {}, + } + } + if (symbol) |s| { + try locals.append(s); + } } + const nlocals = locals.items.len; const nexports = self.exports.items().len; var exports = std.ArrayList(macho.nlist_64).init(self.allocator); @@ -2392,3 +2487,7 @@ fn isExtern(sym: *const macho.nlist_64) callconv(.Inline) bool { if ((sym.n_type & macho.N_EXT) == 0) return false; return (sym.n_type & macho.N_PEXT) == 0; } + +fn isWeakDef(sym: *const macho.nlist_64) callconv(.Inline) bool { + return (sym.n_desc & macho.N_WEAK_DEF) != 0; +} From 7c22f4f85165965e5843a9f6bd800f0463f19122 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Feb 2021 00:25:41 +0100 Subject: [PATCH 30/56] zld: pass test-std and test-compiler-rt --- src/link/MachO/Zld.zig | 38 ++++++++++++++++++++++---------------- src/link/MachO/reloc.zig | 11 ++++++++++- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 579fc0f548..5344691bdf 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -517,7 +517,7 @@ fn allocateSegment(self: *Zld, index: u16, offset: u64, start: u64, reverse: boo var count: usize = seg.sections.items.len; while (count > 0) : (count -= 1) { const sec = &seg.sections.items[count - 1]; - end_off -= mem.alignForwardGeneric(u64, sec.size, @alignOf(u64)); // TODO is 8-byte aligned correct? + end_off -= mem.alignForwardGeneric(u64, sec.size, @alignOf(u128)); // TODO is 8-byte aligned correct? sec.offset = @intCast(u32, end_off); sec.addr = base_vmaddr + end_off; } @@ -526,7 +526,7 @@ fn allocateSegment(self: *Zld, index: u16, offset: u64, start: u64, reverse: boo for (seg.sections.items) |*sect| { sect.offset = @intCast(u32, next_off); sect.addr = base_vmaddr + next_off; - next_off += mem.alignForwardGeneric(u64, sect.size, @alignOf(u64)); // TODO is 8-byte aligned correct? + next_off += mem.alignForwardGeneric(u64, sect.size, @alignOf(u128)); // TODO is 8-byte aligned correct? } } } @@ -1120,6 +1120,11 @@ fn doRelocs(self: *Zld) !void { const narrowed = @truncate(u12, ta); const offset: u12 = blk: { if (parsed.size == 0) { + if (parsed.v == 1) { + // 128-bit SIMD is scaled by 16. + break :blk try math.divExact(u12, narrowed, 16); + } + // Otherwise, 8-bit SIMD or ldrb. break :blk narrowed; } else { const denom: u4 = try math.powi(u4, 2, parsed.size); @@ -2184,25 +2189,26 @@ fn writeSymbolTable(self: *Zld) !void { for (self.locals.items()) |entries| { log.warn("'{s}': {} entries", .{ entries.key, entries.value.items.len }); - var symbol: ?macho.nlist_64 = null; + // var symbol: ?macho.nlist_64 = null; for (entries.value.items) |entry| { log.warn(" | {}", .{entry.inner}); log.warn(" | {}", .{entry.tt}); log.warn(" | {s}", .{entry.object.name}); - switch (entry.tt) { - .Global => { - symbol = entry.inner; - break; - }, - .WeakGlobal => { - symbol = entry.inner; - }, - .Local => {}, - } - } - if (symbol) |s| { - try locals.append(s); + // switch (entry.tt) { + // .Global => { + // symbol = entry.inner; + // break; + // }, + // .WeakGlobal => { + // symbol = entry.inner; + // }, + // .Local => {}, + // } + try locals.append(entry.inner); } + // if (symbol) |s| { + // try locals.append(s); + // } } const nlocals = locals.items.len; diff --git a/src/link/MachO/reloc.zig b/src/link/MachO/reloc.zig index 26af40443e..d428b191a6 100644 --- a/src/link/MachO/reloc.zig +++ b/src/link/MachO/reloc.zig @@ -25,7 +25,10 @@ pub const Arm64 = union(enum) { rt: u5, rn: u5, offset: u12, - _1: u8 = 0b111_0_01_01, + opc: u2, + _2: u2 = 0b01, + v: u1, + _1: u3 = 0b111, size: u2, }, LoadLiteral: packed struct { @@ -144,6 +147,8 @@ pub const Arm64 = union(enum) { .rt = rt, .rn = rn, .offset = offset, + .opc = 0b01, + .v = 0b0, .size = 0b11, }, }; @@ -154,6 +159,8 @@ pub const Arm64 = union(enum) { .rt = rt, .rn = rn, .offset = offset, + .opc = 0b01, + .v = 0b0, .size = 0b01, }, }; @@ -164,6 +171,8 @@ pub const Arm64 = union(enum) { .rt = rt, .rn = rn, .offset = offset, + .opc = 0b01, + .v = 0b0, .size = 0b00, }, }; From 44ebf4863131fbf822caf6548a347abc52e5ce3b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Feb 2021 11:36:39 +0100 Subject: [PATCH 31/56] zld: fix handling of section alignment --- src/link/MachO/Zld.zig | 97 +++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 43 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 5344691bdf..7389558573 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -264,8 +264,8 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8) !void { try self.populateMetadata(); try self.parseInputFiles(files); try self.resolveImports(); - self.allocateTextSegment(); - self.allocateDataSegment(); + try self.allocateTextSegment(); + try self.allocateDataSegment(); self.allocateLinkeditSegment(); try self.writeStubHelperCommon(); try self.resolveSymbols(); @@ -349,6 +349,7 @@ fn parseObjectFile(self: *Zld, object: *const Object) !void { }; } const dest_sect = &seg.sections.items[res.entry.value.sect_index]; + dest_sect.@"align" = math.max(dest_sect.@"align", sect.@"align"); dest_sect.size += sect.size; seg.inner.filesize += sect.size; } @@ -441,10 +442,14 @@ fn resolveImports(self: *Zld) !void { }); } -fn allocateTextSegment(self: *Zld) void { +fn allocateTextSegment(self: *Zld) !void { const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; const nexterns = @intCast(u32, self.lazy_imports.items().len); + const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].Segment.inner.vmsize; + seg.inner.fileoff = 0; + seg.inner.vmaddr = base_vmaddr; + // Set stubs and stub_helper sizes const stubs = &seg.sections.items[self.stubs_section_index.?]; const stub_helper = &seg.sections.items[self.stub_helper_section_index.?]; @@ -462,19 +467,41 @@ fn allocateTextSegment(self: *Zld) void { sizeofcmds += lc.cmdsize(); } - self.allocateSegment( - self.text_segment_cmd_index.?, - 0, - sizeofcmds, - true, - ); + try self.allocateSegment(self.text_segment_cmd_index.?, sizeofcmds); + + // Shift all sections to the back to minimize jump size between __TEXT and __DATA segments. + var min_alignment: u32 = 0; + for (seg.sections.items) |sect| { + const alignment = try math.powi(u32, 2, sect.@"align"); + min_alignment = math.max(min_alignment, alignment); + } + + assert(min_alignment > 0); + const last_sect_idx = seg.sections.items.len - 1; + const last_sect = seg.sections.items[last_sect_idx]; + const shift: u32 = blk: { + const diff = seg.inner.filesize - last_sect.offset - last_sect.size; + const factor = @divTrunc(diff, min_alignment); + break :blk @intCast(u32, factor * min_alignment); + }; + + if (shift > 0) { + for (seg.sections.items) |*sect| { + sect.offset += shift; + sect.addr += shift; + } + } } -fn allocateDataSegment(self: *Zld) void { +fn allocateDataSegment(self: *Zld) !void { const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; const nonlazy = @intCast(u32, self.nonlazy_imports.items().len); const lazy = @intCast(u32, self.lazy_imports.items().len); + const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + seg.inner.fileoff = text_seg.inner.fileoff + text_seg.inner.filesize; + seg.inner.vmaddr = text_seg.inner.vmaddr + text_seg.inner.vmsize; + // Set got size const got = &seg.sections.items[self.got_section_index.?]; got.size += nonlazy * @sizeOf(u64); @@ -485,50 +512,34 @@ fn allocateDataSegment(self: *Zld) void { la_symbol_ptr.size += lazy * @sizeOf(u64); data.size += @sizeOf(u64); // TODO when do we need more? - const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const offset = text_seg.inner.fileoff + text_seg.inner.filesize; - self.allocateSegment(self.data_segment_cmd_index.?, offset, 0, false); + try self.allocateSegment(self.data_segment_cmd_index.?, 0); } fn allocateLinkeditSegment(self: *Zld) void { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; - const offset = data_seg.inner.fileoff + data_seg.inner.filesize; - self.allocateSegment(self.linkedit_segment_cmd_index.?, offset, 0, false); + seg.inner.fileoff = data_seg.inner.fileoff + data_seg.inner.filesize; + seg.inner.vmaddr = data_seg.inner.vmaddr + data_seg.inner.vmsize; } -fn allocateSegment(self: *Zld, index: u16, offset: u64, start: u64, reverse: bool) void { +fn allocateSegment(self: *Zld, index: u16, offset: u64) !void { const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].Segment.inner.vmsize; const seg = &self.load_commands.items[index].Segment; - // Calculate segment size - var total_size = start; - for (seg.sections.items) |sect| { - total_size += sect.size; + // Allocate the sections according to their alignment at the beginning of the segment. + var start: u64 = offset; + for (seg.sections.items) |*sect| { + const alignment = try math.powi(u32, 2, sect.@"align"); + const start_aligned = mem.alignForwardGeneric(u64, start, alignment); + const end_aligned = mem.alignForwardGeneric(u64, start_aligned + sect.size, alignment); + sect.offset = @intCast(u32, seg.inner.fileoff + start_aligned); + sect.addr = seg.inner.vmaddr + start_aligned; + start = end_aligned; } - const aligned_size = mem.alignForwardGeneric(u64, total_size, self.page_size.?); - seg.inner.vmaddr = base_vmaddr + offset; - seg.inner.vmsize = aligned_size; - seg.inner.fileoff = offset; - seg.inner.filesize = aligned_size; - // Allocate section offsets - if (reverse) { - var end_off: u64 = seg.inner.fileoff + seg.inner.filesize; - var count: usize = seg.sections.items.len; - while (count > 0) : (count -= 1) { - const sec = &seg.sections.items[count - 1]; - end_off -= mem.alignForwardGeneric(u64, sec.size, @alignOf(u128)); // TODO is 8-byte aligned correct? - sec.offset = @intCast(u32, end_off); - sec.addr = base_vmaddr + end_off; - } - } else { - var next_off: u64 = seg.inner.fileoff + start; - for (seg.sections.items) |*sect| { - sect.offset = @intCast(u32, next_off); - sect.addr = base_vmaddr + next_off; - next_off += mem.alignForwardGeneric(u64, sect.size, @alignOf(u128)); // TODO is 8-byte aligned correct? - } - } + const seg_size_aligned = mem.alignForwardGeneric(u64, start, self.page_size.?); + seg.inner.filesize = seg_size_aligned; + seg.inner.vmsize = seg_size_aligned; } fn writeStubHelperCommon(self: *Zld) !void { From b0ee480177c5f146cc2a5540745572436e8ca510 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 1 Mar 2021 00:16:01 +0100 Subject: [PATCH 32/56] zld: merge and sort sections --- lib/std/macho.zig | 18 + src/link/MachO/Object.zig | 15 +- src/link/MachO/Zld.zig | 825 ++++++++++++++++++++++++++------------ 3 files changed, 593 insertions(+), 265 deletions(-) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index bca222b5b7..4cdb9dc40e 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -1227,6 +1227,24 @@ pub const S_ATTR_EXT_RELOC = 0x200; /// section has local relocation entries pub const S_ATTR_LOC_RELOC = 0x100; +/// template of initial values for TLVs +pub const S_THREAD_LOCAL_REGULAR = 0x11; + +/// template of initial values for TLVs +pub const S_THREAD_LOCAL_ZEROFILL = 0x12; + +/// TLV descriptors +pub const S_THREAD_LOCAL_VARIABLES = 0x13; + +/// pointers to TLV descriptors +pub const S_THREAD_LOCAL_VARIABLE_POINTERS = 0x14; + +/// functions to call to initialize TLV values +pub const S_THREAD_LOCAL_INIT_FUNCTION_POINTERS = 0x15; + +/// 32-bit offsets to initializers +pub const S_INIT_FUNC_OFFSETS = 0x16; + pub const cpu_type_t = integer_t; pub const cpu_subtype_t = integer_t; pub const integer_t = c_int; diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index c79869a5a7..6337f85a80 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -24,9 +24,9 @@ segment_cmd_index: ?u16 = null, symtab_cmd_index: ?u16 = null, dysymtab_cmd_index: ?u16 = null, build_version_cmd_index: ?u16 = null, - text_section_index: ?u16 = null, +// __DWARF segment sections dwarf_debug_info_index: ?u16 = null, dwarf_debug_abbrev_index: ?u16 = null, dwarf_debug_str_index: ?u16 = null, @@ -36,13 +36,6 @@ dwarf_debug_ranges_index: ?u16 = null, symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{}, strtab: std.ArrayListUnmanaged(u8) = .{}, -directory: std.AutoHashMapUnmanaged(DirectoryKey, u16) = .{}, - -pub const DirectoryKey = struct { - segname: [16]u8, - sectname: [16]u8, -}; - pub fn deinit(self: *Object) void { for (self.load_commands.items) |*lc| { lc.deinit(self.allocator); @@ -50,7 +43,6 @@ pub fn deinit(self: *Object) void { self.load_commands.deinit(self.allocator); self.symtab.deinit(self.allocator); self.strtab.deinit(self.allocator); - self.directory.deinit(self.allocator); self.allocator.free(self.name); self.file.close(); } @@ -138,11 +130,6 @@ pub fn readLoadCommands(self: *Object, reader: anytype, offset: ReadOffset) !voi } } - try self.directory.putNoClobber(self.allocator, .{ - .segname = sect.segname, - .sectname = sect.sectname, - }, index); - sect.offset += offset_mod; if (sect.reloff > 0) sect.reloff += offset_mod; diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 7389558573..f8cda5e9ad 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -52,13 +52,22 @@ source_version_cmd_index: ?u16 = null, uuid_cmd_index: ?u16 = null, code_signature_cmd_index: ?u16 = null, +// __TEXT segment sections text_section_index: ?u16 = null, stubs_section_index: ?u16 = null, stub_helper_section_index: ?u16 = null, +text_const_section_index: ?u16 = null, +cstring_section_index: ?u16 = null, + +// __DATA segment sections got_section_index: ?u16 = null, tlv_section_index: ?u16 = null, +tlv_data_section_index: ?u16 = null, +tlv_bss_section_index: ?u16 = null, la_symbol_ptr_section_index: ?u16 = null, +data_const_section_index: ?u16 = null, data_section_index: ?u16 = null, +bss_section_index: ?u16 = null, locals: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(Symbol)) = .{}, exports: std.StringArrayHashMapUnmanaged(macho.nlist_64) = .{}, @@ -71,13 +80,25 @@ strtab: std.ArrayListUnmanaged(u8) = .{}, stub_helper_stubs_start_off: ?u64 = null, -segments_directory: std.AutoHashMapUnmanaged([16]u8, u16) = .{}, -directory: std.AutoHashMapUnmanaged(DirectoryKey, DirectoryEntry) = .{}, +mappings: std.AutoHashMapUnmanaged(MappingKey, SectionMapping) = .{}, +unhandled_sections: std.AutoHashMapUnmanaged(MappingKey, u0) = .{}, + +const MappingKey = struct { + object_id: u16, + source_sect_id: u16, +}; + +const SectionMapping = struct { + source_sect_id: u16, + target_seg_id: u16, + target_sect_id: u16, + offset: u32, +}; const Symbol = struct { inner: macho.nlist_64, tt: Type, - object: *Object, + object_id: u16, const Type = enum { Local, @@ -86,16 +107,6 @@ const Symbol = struct { }; }; -const DirectoryKey = struct { - segname: [16]u8, - sectname: [16]u8, -}; - -const DirectoryEntry = struct { - seg_index: u16, - sect_index: u16, -}; - const DebugInfo = struct { inner: dwarf.DwarfInfo, debug_info: []u8, @@ -221,8 +232,8 @@ pub fn deinit(self: *Zld) void { lc.deinit(self.allocator); } self.load_commands.deinit(self.allocator); - self.segments_directory.deinit(self.allocator); - self.directory.deinit(self.allocator); + self.mappings.deinit(self.allocator); + self.unhandled_sections.deinit(self.allocator); if (self.file) |*f| f.close(); } @@ -263,6 +274,7 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8) !void { try self.populateMetadata(); try self.parseInputFiles(files); + try self.sortSections(); try self.resolveImports(); try self.allocateTextSegment(); try self.allocateDataSegment(); @@ -282,10 +294,9 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void { error.NotObject => break :try_object, else => |e| return e, }; - const index = self.objects.items.len; + const index = @intCast(u16, self.objects.items.len); try self.objects.append(self.allocator, object); - const p_object = &self.objects.items[index]; - try self.parseObjectFile(p_object); + try self.updateMetadata(index); continue; } @@ -296,10 +307,9 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void { }; defer archive.deinit(); while (archive.objects.popOrNull()) |object| { - const index = self.objects.items.len; + const index = @intCast(u16, self.objects.items.len); try self.objects.append(self.allocator, object); - const p_object = &self.objects.items[index]; - try self.parseObjectFile(p_object); + try self.updateMetadata(index); } continue; } @@ -309,49 +319,425 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void { } } -fn parseObjectFile(self: *Zld, object: *const Object) !void { - const seg_cmd = object.load_commands.items[object.segment_cmd_index.?].Segment; - for (seg_cmd.sections.items) |sect| { - const segname = parseName(§.segname); - const sectname = parseName(§.sectname); +fn mapAndUpdateSections( + self: *Zld, + object_id: u16, + source_sect_id: u16, + target_seg_id: u16, + target_sect_id: u16, +) !void { + const object = self.objects.items[object_id]; + const source_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + const source_sect = source_seg.sections.items[source_sect_id]; + const target_seg = &self.load_commands.items[target_seg_id].Segment; + const target_sect = &target_seg.sections.items[target_sect_id]; + log.warn("{}", .{target_sect}); - const seg_index = self.segments_directory.get(sect.segname) orelse { - log.info("segname {s} not found in the output artifact", .{sect.segname}); - continue; - }; - const seg = &self.load_commands.items[seg_index].Segment; - const res = try self.directory.getOrPut(self.allocator, .{ - .segname = sect.segname, - .sectname = sect.sectname, - }); - if (!res.found_existing) { - const sect_index = @intCast(u16, seg.sections.items.len); - if (mem.eql(u8, sectname, "__thread_vars")) { - self.tlv_section_index = sect_index; - } - try seg.append(self.allocator, .{ - .sectname = makeStaticString(§.sectname), - .segname = makeStaticString(§.segname), - .addr = 0, - .size = 0, - .offset = 0, - .@"align" = sect.@"align", - .reloff = 0, - .nreloc = 0, - .flags = sect.flags, - .reserved1 = 0, - .reserved2 = 0, - .reserved3 = 0, - }); - res.entry.value = .{ - .seg_index = seg_index, - .sect_index = sect_index, - }; + const alignment = try math.powi(u32, 2, source_sect.@"align"); + const offset = mem.alignForwardGeneric(u64, target_sect.size, alignment); + const size = mem.alignForwardGeneric(u64, source_sect.size, alignment); + const key = MappingKey{ + .object_id = object_id, + .source_sect_id = source_sect_id, + }; + try self.mappings.putNoClobber(self.allocator, key, .{ + .source_sect_id = source_sect_id, + .target_seg_id = target_seg_id, + .target_sect_id = target_sect_id, + .offset = @intCast(u32, offset), + }); + log.warn("{s}: {s},{s} mapped to {s},{s} from 0x{x} to 0x{x}", .{ + object.name, + parseName(&source_sect.segname), + parseName(&source_sect.sectname), + parseName(&target_sect.segname), + parseName(&target_sect.sectname), + offset, + offset + size, + }); + + target_sect.@"align" = math.max(target_sect.@"align", source_sect.@"align"); + target_sect.size = offset + size; +} + +fn updateMetadata(self: *Zld, object_id: u16) !void { + const object = self.objects.items[object_id]; + const object_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + + // Create missing metadata + for (object_seg.sections.items) |source_sect, id| { + if (id == object.text_section_index.?) continue; + const segname = parseName(&source_sect.segname); + const sectname = parseName(&source_sect.sectname); + const flags = source_sect.flags; + + switch (flags) { + macho.S_REGULAR, macho.S_4BYTE_LITERALS, macho.S_8BYTE_LITERALS, macho.S_16BYTE_LITERALS => { + if (mem.eql(u8, segname, "__TEXT")) { + if (self.text_const_section_index != null) continue; + + self.text_const_section_index = @intCast(u16, text_seg.sections.items.len); + try text_seg.append(self.allocator, .{ + .sectname = makeStaticString("__const"), + .segname = makeStaticString("__TEXT"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_REGULAR, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + } else if (mem.eql(u8, segname, "__DATA")) { + if (!mem.eql(u8, sectname, "__const")) continue; + if (self.data_const_section_index != null) continue; + + self.data_const_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.append(self.allocator, .{ + .sectname = makeStaticString("__const"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_REGULAR, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + } + }, + macho.S_CSTRING_LITERALS => { + if (!mem.eql(u8, segname, "__TEXT")) continue; + if (self.cstring_section_index != null) continue; + + self.cstring_section_index = @intCast(u16, text_seg.sections.items.len); + try text_seg.append(self.allocator, .{ + .sectname = makeStaticString("__cstring"), + .segname = makeStaticString("__TEXT"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_CSTRING_LITERALS, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + }, + macho.S_ZEROFILL => { + if (!mem.eql(u8, segname, "__DATA")) continue; + if (self.bss_section_index != null) continue; + + self.bss_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.append(self.allocator, .{ + .sectname = makeStaticString("__bss"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_ZEROFILL, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + }, + macho.S_THREAD_LOCAL_VARIABLES => { + if (!mem.eql(u8, segname, "__DATA")) continue; + if (self.tlv_section_index != null) continue; + + self.tlv_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.append(self.allocator, .{ + .sectname = makeStaticString("__thread_vars"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_THREAD_LOCAL_VARIABLES, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + }, + macho.S_THREAD_LOCAL_REGULAR => { + if (!mem.eql(u8, segname, "__DATA")) continue; + if (self.tlv_data_section_index != null) continue; + + self.tlv_data_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.append(self.allocator, .{ + .sectname = makeStaticString("__thread_data"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_THREAD_LOCAL_REGULAR, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + }, + macho.S_THREAD_LOCAL_ZEROFILL => { + if (!mem.eql(u8, segname, "__DATA")) continue; + if (self.tlv_bss_section_index != null) continue; + + self.tlv_bss_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.append(self.allocator, .{ + .sectname = makeStaticString("__thread_bss"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_THREAD_LOCAL_ZEROFILL, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + }, + else => { + log.warn("unhandled section type 0x{x} for '{s}/{s}'", .{ flags, segname, sectname }); + }, } - const dest_sect = &seg.sections.items[res.entry.value.sect_index]; - dest_sect.@"align" = math.max(dest_sect.@"align", sect.@"align"); - dest_sect.size += sect.size; - seg.inner.filesize += sect.size; + } + + // Update section mappings + // __TEXT,__text has to be always defined! + try self.mapAndUpdateSections( + object_id, + object.text_section_index.?, + self.text_segment_cmd_index.?, + self.text_section_index.?, + ); + + for (object_seg.sections.items) |source_sect, id| { + const source_sect_id = @intCast(u16, id); + if (id == object.text_section_index.?) continue; + + const segname = parseName(&source_sect.segname); + const sectname = parseName(&source_sect.sectname); + const flags = source_sect.flags; + + switch (flags) { + macho.S_4BYTE_LITERALS, macho.S_8BYTE_LITERALS, macho.S_16BYTE_LITERALS => { + try self.mapAndUpdateSections( + object_id, + source_sect_id, + self.text_segment_cmd_index.?, + self.text_const_section_index.?, + ); + }, + macho.S_CSTRING_LITERALS => { + try self.mapAndUpdateSections( + object_id, + source_sect_id, + self.text_segment_cmd_index.?, + self.cstring_section_index.?, + ); + }, + macho.S_ZEROFILL => { + try self.mapAndUpdateSections( + object_id, + source_sect_id, + self.data_segment_cmd_index.?, + self.bss_section_index.?, + ); + }, + macho.S_THREAD_LOCAL_VARIABLES => { + try self.mapAndUpdateSections( + object_id, + source_sect_id, + self.data_segment_cmd_index.?, + self.tlv_section_index.?, + ); + }, + macho.S_THREAD_LOCAL_REGULAR => { + try self.mapAndUpdateSections( + object_id, + source_sect_id, + self.data_segment_cmd_index.?, + self.tlv_data_section_index.?, + ); + }, + macho.S_THREAD_LOCAL_ZEROFILL => { + try self.mapAndUpdateSections( + object_id, + source_sect_id, + self.data_segment_cmd_index.?, + self.tlv_bss_section_index.?, + ); + }, + macho.S_REGULAR => { + if (mem.eql(u8, segname, "__TEXT")) { + try self.mapAndUpdateSections( + object_id, + source_sect_id, + self.text_segment_cmd_index.?, + self.text_const_section_index.?, + ); + continue; + } else if (mem.eql(u8, segname, "__DATA")) { + if (mem.eql(u8, sectname, "__data")) { + try self.mapAndUpdateSections( + object_id, + source_sect_id, + self.data_segment_cmd_index.?, + self.data_section_index.?, + ); + continue; + } else if (mem.eql(u8, sectname, "__const")) { + try self.mapAndUpdateSections( + object_id, + source_sect_id, + self.data_segment_cmd_index.?, + self.data_const_section_index.?, + ); + continue; + } + } + log.warn("section '{s}/{s}' will be unmapped", .{ segname, sectname }); + try self.unhandled_sections.putNoClobber(self.allocator, .{ + .object_id = object_id, + .source_sect_id = source_sect_id, + }, 0); + }, + else => { + log.warn("section '{s}/{s}' will be unmapped", .{ segname, sectname }); + try self.unhandled_sections.putNoClobber(self.allocator, .{ + .object_id = object_id, + .source_sect_id = source_sect_id, + }, 0); + }, + } + } +} + +fn sortSections(self: *Zld) !void { + const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + var text_sections = text_seg.sections.toOwnedSlice(self.allocator); + defer self.allocator.free(text_sections); + try text_seg.sections.ensureCapacity(self.allocator, text_sections.len); + + const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + var data_sections = data_seg.sections.toOwnedSlice(self.allocator); + defer self.allocator.free(data_sections); + try data_seg.sections.ensureCapacity(self.allocator, data_sections.len); + + var text_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator); + defer text_index_mapping.deinit(); + + var data_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator); + defer data_index_mapping.deinit(); + + if (self.text_section_index) |index| { + const new_index = @intCast(u16, text_seg.sections.items.len); + self.text_section_index = new_index; + text_seg.sections.appendAssumeCapacity(text_sections[index]); + try text_index_mapping.putNoClobber(index, new_index); + } + if (self.stubs_section_index) |index| { + const new_index = @intCast(u16, text_seg.sections.items.len); + self.stubs_section_index = new_index; + text_seg.sections.appendAssumeCapacity(text_sections[index]); + try text_index_mapping.putNoClobber(index, new_index); + } + if (self.stub_helper_section_index) |index| { + const new_index = @intCast(u16, text_seg.sections.items.len); + self.stub_helper_section_index = new_index; + text_seg.sections.appendAssumeCapacity(text_sections[index]); + try text_index_mapping.putNoClobber(index, new_index); + } + if (self.text_const_section_index) |index| { + const new_index = @intCast(u16, text_seg.sections.items.len); + self.text_const_section_index = new_index; + text_seg.sections.appendAssumeCapacity(text_sections[index]); + try text_index_mapping.putNoClobber(index, new_index); + } + if (self.cstring_section_index) |index| { + const new_index = @intCast(u16, text_seg.sections.items.len); + self.cstring_section_index = new_index; + text_seg.sections.appendAssumeCapacity(text_sections[index]); + try text_index_mapping.putNoClobber(index, new_index); + } + + if (self.got_section_index) |index| { + const new_index = @intCast(u16, data_seg.sections.items.len); + self.got_section_index = new_index; + data_seg.sections.appendAssumeCapacity(data_sections[index]); + try data_index_mapping.putNoClobber(index, new_index); + } + if (self.data_const_section_index) |index| { + const new_index = @intCast(u16, data_seg.sections.items.len); + self.data_const_section_index = new_index; + data_seg.sections.appendAssumeCapacity(data_sections[index]); + try data_index_mapping.putNoClobber(index, new_index); + } + if (self.la_symbol_ptr_section_index) |index| { + const new_index = @intCast(u16, data_seg.sections.items.len); + self.la_symbol_ptr_section_index = new_index; + data_seg.sections.appendAssumeCapacity(data_sections[index]); + try data_index_mapping.putNoClobber(index, new_index); + } + if (self.tlv_section_index) |index| { + const new_index = @intCast(u16, data_seg.sections.items.len); + self.tlv_section_index = new_index; + data_seg.sections.appendAssumeCapacity(data_sections[index]); + try data_index_mapping.putNoClobber(index, new_index); + } + if (self.data_section_index) |index| { + const new_index = @intCast(u16, data_seg.sections.items.len); + self.data_section_index = new_index; + data_seg.sections.appendAssumeCapacity(data_sections[index]); + try data_index_mapping.putNoClobber(index, new_index); + } + if (self.tlv_data_section_index) |index| { + const new_index = @intCast(u16, data_seg.sections.items.len); + self.tlv_data_section_index = new_index; + data_seg.sections.appendAssumeCapacity(data_sections[index]); + try data_index_mapping.putNoClobber(index, new_index); + } + if (self.tlv_bss_section_index) |index| { + const new_index = @intCast(u16, data_seg.sections.items.len); + self.tlv_bss_section_index = new_index; + data_seg.sections.appendAssumeCapacity(data_sections[index]); + try data_index_mapping.putNoClobber(index, new_index); + } + if (self.bss_section_index) |index| { + const new_index = @intCast(u16, data_seg.sections.items.len); + self.bss_section_index = new_index; + data_seg.sections.appendAssumeCapacity(data_sections[index]); + try data_index_mapping.putNoClobber(index, new_index); + } + + var it = self.mappings.iterator(); + while (it.next()) |entry| { + const mapping = &entry.value; + if (self.text_segment_cmd_index.? == mapping.target_seg_id) { + const new_index = text_index_mapping.get(mapping.target_sect_id) orelse unreachable; + mapping.target_sect_id = new_index; + } else if (self.data_segment_cmd_index.? == mapping.target_seg_id) { + const new_index = data_index_mapping.get(mapping.target_sect_id) orelse unreachable; + mapping.target_sect_id = new_index; + } else unreachable; } } @@ -790,35 +1176,9 @@ fn writeStubInStubHelper(self: *Zld, index: u32) !void { } fn resolveSymbols(self: *Zld) !void { - const Address = struct { - addr: u64, - size: u64, - }; - var next_address = std.AutoHashMap(DirectoryKey, Address).init(self.allocator); - defer next_address.deinit(); - - for (self.objects.items) |*object| { + for (self.objects.items) |object, object_id| { const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; - for (seg.sections.items) |sect| { - const key: DirectoryKey = .{ - .segname = sect.segname, - .sectname = sect.sectname, - }; - const indices = self.directory.get(key) orelse continue; - const out_seg = self.load_commands.items[indices.seg_index].Segment; - const out_sect = out_seg.sections.items[indices.sect_index]; - - const res = try next_address.getOrPut(key); - const next = &res.entry.value; - if (res.found_existing) { - next.addr += next.size; - } else { - next.addr = out_sect.addr; - } - next.size = sect.size; - } - for (object.symtab.items) |sym| { if (isImport(&sym)) continue; @@ -851,24 +1211,36 @@ fn resolveSymbols(self: *Zld) !void { } } - const sect = seg.sections.items[sym.n_sect - 1]; - const key: DirectoryKey = .{ - .segname = sect.segname, - .sectname = sect.sectname, - }; - const res = self.directory.get(key) orelse continue; + const source_sect_id = sym.n_sect - 1; + const target_mapping = self.mappings.get(.{ + .object_id = @intCast(u16, object_id), + .source_sect_id = source_sect_id, + }) orelse { + if (self.unhandled_sections.get(.{ + .object_id = @intCast(u16, object_id), + .source_sect_id = source_sect_id, + }) != null) continue; - const n_value = sym.n_value - sect.addr + next_address.get(key).?.addr; + log.err("section not mapped for symbol '{s}': {}", .{ sym_name, sym }); + return error.SectionNotMappedForSymbol; + }; + const source_sect = seg.sections.items[source_sect_id]; + const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; + const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; + const target_addr = target_sect.addr + target_mapping.offset; + const n_value = sym.n_value - source_sect.addr + target_addr; log.warn("resolving '{s}':{} as {s} symbol at 0x{x}", .{ sym_name, sym, tt, n_value }); - var n_sect = res.sect_index + 1; - for (self.load_commands.items) |sseg, i| { - if (i == res.seg_index) { - break; + // TODO this assumes only two symbol-filled segments. Also, there might be a more + // generic way of doing this. + const n_sect = blk: { + if (self.text_segment_cmd_index.? == target_mapping.target_seg_id) { + break :blk target_mapping.target_sect_id + 1; } - n_sect += @intCast(u16, sseg.Segment.sections.items.len); - } + const prev_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + break :blk @intCast(u16, prev_seg.sections.items.len + target_mapping.target_sect_id + 1); + }; const n_strx = try self.makeString(sym_name); try locs.entry.value.append(self.allocator, .{ @@ -880,64 +1252,26 @@ fn resolveSymbols(self: *Zld) !void { .n_sect = @intCast(u8, n_sect), }, .tt = tt, - .object = object, + .object_id = @intCast(u16, object_id), }); } } } fn doRelocs(self: *Zld) !void { - const Space = struct { - address: u64, - offset: u64, - size: u64, - }; - var next_space = std.AutoHashMap(DirectoryKey, Space).init(self.allocator); - defer next_space.deinit(); - - for (self.objects.items) |object| { + for (self.objects.items) |object, object_id| { log.warn("\n\n", .{}); log.warn("relocating object {s}", .{object.name}); const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; - for (seg.sections.items) |sect| { - const key: DirectoryKey = .{ - .segname = sect.segname, - .sectname = sect.sectname, - }; - const indices = self.directory.get(key) orelse continue; - const out_seg = self.load_commands.items[indices.seg_index].Segment; - const out_sect = out_seg.sections.items[indices.sect_index]; - - const res = try next_space.getOrPut(key); - const next = &res.entry.value; - if (res.found_existing) { - next.offset += next.size; - next.address += next.size; - } else { - next.offset = out_sect.offset; - next.address = out_sect.addr; - } - next.size = sect.size; - } - - for (seg.sections.items) |sect| { + for (seg.sections.items) |sect, source_sect_id| { const segname = parseName(§.segname); const sectname = parseName(§.sectname); - const key: DirectoryKey = .{ - .segname = sect.segname, - .sectname = sect.sectname, - }; - const next = next_space.get(key) orelse continue; - - var code = blk: { - var buf = try self.allocator.alloc(u8, sect.size); - _ = try object.file.preadAll(buf, sect.offset); - break :blk std.ArrayList(u8).fromOwnedSlice(self.allocator, buf); - }; - defer code.deinit(); + var code = try self.allocator.alloc(u8, sect.size); + _ = try object.file.preadAll(code, sect.offset); + defer self.allocator.free(code); // Parse relocs (if any) var raw_relocs = try self.allocator.alloc(u8, @sizeOf(macho.relocation_info) * sect.nreloc); @@ -945,12 +1279,25 @@ fn doRelocs(self: *Zld) !void { _ = try object.file.preadAll(raw_relocs, sect.reloff); const relocs = mem.bytesAsSlice(macho.relocation_info, raw_relocs); + // Get mapping + const target_mapping = self.mappings.get(.{ + .object_id = @intCast(u16, object_id), + .source_sect_id = @intCast(u16, source_sect_id), + }) orelse { + log.warn("no mapping for {s},{s}; skipping", .{ segname, sectname }); + continue; + }; + const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; + const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; + const target_sect_addr = target_sect.addr + target_mapping.offset; + const target_sect_off = target_sect.offset + target_mapping.offset; + var addend: ?u64 = null; var sub: ?i64 = null; for (relocs) |rel| { const off = @intCast(u32, rel.r_address); - const this_addr = next.address + off; + const this_addr = target_sect_addr + off; switch (self.arch.?) { .aarch64 => { @@ -975,7 +1322,7 @@ fn doRelocs(self: *Zld) !void { else => {}, } - const target_addr = try self.relocTargetAddr(object, rel, next_space); + const target_addr = try self.relocTargetAddr(@intCast(u16, object_id), rel); log.warn(" | target address 0x{x}", .{target_addr}); if (rel.r_extern == 1) { const target_symname = object.getString(object.symtab.items[rel.r_symbolnum].n_strx); @@ -995,16 +1342,16 @@ fn doRelocs(self: *Zld) !void { .X86_64_RELOC_GOT, => { assert(rel.r_length == 2); - const inst = code.items[off..][0..4]; + const inst = code[off..][0..4]; const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); mem.writeIntLittle(u32, inst, displacement); }, .X86_64_RELOC_TLV => { assert(rel.r_length == 2); // We need to rewrite the opcode from movq to leaq. - code.items[off - 2] = 0x8d; + code[off - 2] = 0x8d; // Add displacement. - const inst = code.items[off..][0..4]; + const inst = code[off..][0..4]; const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); mem.writeIntLittle(u32, inst, displacement); }, @@ -1014,7 +1361,7 @@ fn doRelocs(self: *Zld) !void { .X86_64_RELOC_SIGNED_4, => { assert(rel.r_length == 2); - const inst = code.items[off..][0..4]; + const inst = code[off..][0..4]; const offset: i32 = blk: { if (rel.r_extern == 1) { break :blk mem.readIntLittle(i32, inst); @@ -1043,7 +1390,7 @@ fn doRelocs(self: *Zld) !void { .X86_64_RELOC_UNSIGNED => { switch (rel.r_length) { 3 => { - const inst = code.items[off..][0..8]; + const inst = code[off..][0..8]; const offset = mem.readIntLittle(i64, inst); log.warn(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @@ -1054,12 +1401,19 @@ fn doRelocs(self: *Zld) !void { sub = null; // TODO should handle this better. - if (mem.eql(u8, segname, "__DATA")) outer: { - if (!mem.eql(u8, sectname, "__data") and - !mem.eql(u8, sectname, "__const") and - !mem.eql(u8, sectname, "__mod_init_func")) break :outer; + outer: { + var hit: bool = false; + if (self.data_section_index) |index| inner: { + if (index != target_mapping.target_sect_id) break :inner; + hit = true; + } + if (self.data_const_section_index) |index| inner: { + if (index != target_mapping.target_sect_id) break :inner; + hit = true; + } + if (!hit) break :outer; const this_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; - const this_offset = next.address + off - this_seg.inner.vmaddr; + const this_offset = target_sect_addr + off - this_seg.inner.vmaddr; try self.local_rebases.append(self.allocator, .{ .offset = this_offset, .segment_id = @intCast(u16, self.data_segment_cmd_index.?), @@ -1067,7 +1421,7 @@ fn doRelocs(self: *Zld) !void { } }, 2 => { - const inst = code.items[off..][0..4]; + const inst = code[off..][0..4]; const offset = mem.readIntLittle(i32, inst); log.warn(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @@ -1091,7 +1445,7 @@ fn doRelocs(self: *Zld) !void { switch (rel_type) { .ARM64_RELOC_BRANCH26 => { assert(rel.r_length == 2); - const inst = code.items[off..][0..4]; + const inst = code[off..][0..4]; const displacement = @intCast(i28, @intCast(i64, target_addr) - @intCast(i64, this_addr)); var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Branch), inst); parsed.disp = @truncate(u26, @bitCast(u28, displacement) >> 2); @@ -1101,7 +1455,7 @@ fn doRelocs(self: *Zld) !void { .ARM64_RELOC_TLVP_LOAD_PAGE21, => { assert(rel.r_length == 2); - const inst = code.items[off..][0..4]; + const inst = code[off..][0..4]; const ta = if (addend) |a| target_addr + a else target_addr; const this_page = @intCast(i32, this_addr >> 12); const target_page = @intCast(i32, ta >> 12); @@ -1115,7 +1469,7 @@ fn doRelocs(self: *Zld) !void { .ARM64_RELOC_PAGEOFF12, .ARM64_RELOC_GOT_LOAD_PAGEOFF12, => { - const inst = code.items[off..][0..4]; + const inst = code[off..][0..4]; if (Arm64.isArithmetic(inst)) { log.warn(" | detected ADD opcode", .{}); // add @@ -1153,7 +1507,7 @@ fn doRelocs(self: *Zld) !void { rn: u5, size: u1, }; - const inst = code.items[off..][0..4]; + const inst = code[off..][0..4]; const parsed: RegInfo = blk: { if (Arm64.isArithmetic(inst)) { const curr = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Add), inst); @@ -1175,7 +1529,7 @@ fn doRelocs(self: *Zld) !void { .ARM64_RELOC_UNSIGNED => { switch (rel.r_length) { 3 => { - const inst = code.items[off..][0..8]; + const inst = code[off..][0..8]; const offset = mem.readIntLittle(i64, inst); log.warn(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @@ -1186,12 +1540,19 @@ fn doRelocs(self: *Zld) !void { sub = null; // TODO should handle this better. - if (mem.eql(u8, segname, "__DATA")) outer: { - if (!mem.eql(u8, sectname, "__data") and - !mem.eql(u8, sectname, "__const") and - !mem.eql(u8, sectname, "__mod_init_func")) break :outer; + outer: { + var hit: bool = false; + if (self.data_section_index) |index| inner: { + if (index != target_mapping.target_sect_id) break :inner; + hit = true; + } + if (self.data_const_section_index) |index| inner: { + if (index != target_mapping.target_sect_id) break :inner; + hit = true; + } + if (!hit) break :outer; const this_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; - const this_offset = next.address + off - this_seg.inner.vmaddr; + const this_offset = target_sect_addr + off - this_seg.inner.vmaddr; try self.local_rebases.append(self.allocator, .{ .offset = this_offset, .segment_id = @intCast(u16, self.data_segment_cmd_index.?), @@ -1199,7 +1560,7 @@ fn doRelocs(self: *Zld) !void { } }, 2 => { - const inst = code.items[off..][0..4]; + const inst = code[off..][0..4]; const offset = mem.readIntLittle(i32, inst); log.warn(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @@ -1227,40 +1588,50 @@ fn doRelocs(self: *Zld) !void { segname, sectname, object.name, - next.offset, - next.offset + next.size, + target_sect_off, + target_sect_off + code.len, }); - if (mem.eql(u8, sectname, "__bss") or - mem.eql(u8, sectname, "__thread_bss") or - mem.eql(u8, sectname, "__thread_vars")) + if (target_sect.flags == macho.S_ZEROFILL or + target_sect.flags == macho.S_THREAD_LOCAL_ZEROFILL or + target_sect.flags == macho.S_THREAD_LOCAL_VARIABLES) { + log.warn("zeroing out '{s},{s}' from 0x{x} to 0x{x}", .{ + parseName(&target_sect.segname), + parseName(&target_sect.sectname), + target_sect_off, + target_sect_off + code.len, + }); // Zero-out the space - var zeroes = try self.allocator.alloc(u8, next.size); + var zeroes = try self.allocator.alloc(u8, code.len); defer self.allocator.free(zeroes); mem.set(u8, zeroes, 0); - try self.file.?.pwriteAll(zeroes, next.offset); + try self.file.?.pwriteAll(zeroes, target_sect_off); } else { - try self.file.?.pwriteAll(code.items, next.offset); + try self.file.?.pwriteAll(code, target_sect_off); } } } } -fn relocTargetAddr(self: *Zld, object: Object, rel: macho.relocation_info, next_space: anytype) !u64 { +fn relocTargetAddr(self: *Zld, object_id: u16, rel: macho.relocation_info) !u64 { + const object = self.objects.items[object_id]; const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; const target_addr = blk: { if (rel.r_extern == 1) { const sym = object.symtab.items[rel.r_symbolnum]; if (isLocal(&sym) or isExport(&sym)) { // Relocate using section offsets only. - const source_sect = seg.sections.items[sym.n_sect - 1]; - const target_space = next_space.get(.{ - .segname = source_sect.segname, - .sectname = source_sect.sectname, - }).?; + const target_mapping = self.mappings.get(.{ + .object_id = object_id, + .source_sect_id = sym.n_sect - 1, + }) orelse unreachable; + const source_sect = seg.sections.items[target_mapping.source_sect_id]; + const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; + const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; + const target_sect_addr = target_sect.addr + target_mapping.offset; log.warn(" | symbol local to object", .{}); - break :blk target_space.address + sym.n_value - source_sect.addr; + break :blk target_sect_addr + sym.n_value - source_sect.addr; } else if (isImport(&sym)) { // Relocate to either the artifact's local symbol, or an import from // shared library. @@ -1309,12 +1680,13 @@ fn relocTargetAddr(self: *Zld, object: Object, rel: macho.relocation_info, next_ // here to get the actual section plus offset into that section of the relocated // symbol. Unless the fine-grained location is encoded within the cell in the code // buffer? - const source_sectname = seg.sections.items[rel.r_symbolnum - 1]; - const target_space = next_space.get(.{ - .segname = source_sectname.segname, - .sectname = source_sectname.sectname, - }).?; - break :blk target_space.address; + const target_mapping = self.mappings.get(.{ + .object_id = object_id, + .source_sect_id = @intCast(u16, rel.r_symbolnum - 1), + }) orelse unreachable; + const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; + const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; + break :blk target_sect.addr + target_mapping.offset; } }; return target_addr; @@ -1338,7 +1710,6 @@ fn populateMetadata(self: *Zld) !void { .flags = 0, }), }); - try self.addSegmentToDir(0); } if (self.text_segment_cmd_index == null) { @@ -1358,7 +1729,6 @@ fn populateMetadata(self: *Zld) !void { .flags = 0, }), }); - try self.addSegmentToDir(self.text_segment_cmd_index.?); } if (self.text_section_index == null) { @@ -1383,10 +1753,6 @@ fn populateMetadata(self: *Zld) !void { .reserved2 = 0, .reserved3 = 0, }); - try self.addSectionToDir(.{ - .seg_index = self.text_segment_cmd_index.?, - .sect_index = self.text_section_index.?, - }); } if (self.stubs_section_index == null) { @@ -1416,10 +1782,6 @@ fn populateMetadata(self: *Zld) !void { .reserved2 = stub_size, .reserved3 = 0, }); - try self.addSectionToDir(.{ - .seg_index = self.text_segment_cmd_index.?, - .sect_index = self.stubs_section_index.?, - }); } if (self.stub_helper_section_index == null) { @@ -1449,10 +1811,6 @@ fn populateMetadata(self: *Zld) !void { .reserved2 = 0, .reserved3 = 0, }); - try self.addSectionToDir(.{ - .seg_index = self.text_segment_cmd_index.?, - .sect_index = self.stub_helper_section_index.?, - }); } if (self.data_segment_cmd_index == null) { @@ -1472,7 +1830,6 @@ fn populateMetadata(self: *Zld) !void { .flags = 0, }), }); - try self.addSegmentToDir(self.data_segment_cmd_index.?); } if (self.got_section_index == null) { @@ -1492,10 +1849,6 @@ fn populateMetadata(self: *Zld) !void { .reserved2 = 0, .reserved3 = 0, }); - try self.addSectionToDir(.{ - .seg_index = self.data_segment_cmd_index.?, - .sect_index = self.got_section_index.?, - }); } if (self.la_symbol_ptr_section_index == null) { @@ -1515,10 +1868,6 @@ fn populateMetadata(self: *Zld) !void { .reserved2 = 0, .reserved3 = 0, }); - try self.addSectionToDir(.{ - .seg_index = self.data_segment_cmd_index.?, - .sect_index = self.la_symbol_ptr_section_index.?, - }); } if (self.data_section_index == null) { @@ -1538,10 +1887,6 @@ fn populateMetadata(self: *Zld) !void { .reserved2 = 0, .reserved3 = 0, }); - try self.addSectionToDir(.{ - .seg_index = self.data_segment_cmd_index.?, - .sect_index = self.data_section_index.?, - }); } if (self.linkedit_segment_cmd_index == null) { @@ -1561,7 +1906,6 @@ fn populateMetadata(self: *Zld) !void { .flags = 0, }), }); - try self.addSegmentToDir(self.linkedit_segment_cmd_index.?); } if (self.dyld_info_cmd_index == null) { @@ -1719,22 +2063,15 @@ fn populateMetadata(self: *Zld) !void { } fn flush(self: *Zld) !void { - { + if (self.bss_section_index) |index| { const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; - for (seg.sections.items) |*sect| { - const sectname = parseName(§.sectname); - if (mem.eql(u8, sectname, "__bss") or mem.eql(u8, sectname, "__thread_bss")) { - sect.offset = 0; - } - } + const sect = &seg.sections.items[index]; + sect.offset = 0; } - { - const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - for (seg.sections.items) |*sect| { - if (mem.eql(u8, parseName(§.sectname), "__eh_frame")) { - sect.flags = 0; - } - } + if (self.tlv_bss_section_index) |index| { + const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = &seg.sections.items[index]; + sect.offset = 0; } try self.setEntryPoint(); try self.writeRebaseInfoTable(); @@ -2056,9 +2393,9 @@ fn writeDebugInfo(self: *Zld) !void { var stabs = std.ArrayList(macho.nlist_64).init(self.allocator); defer stabs.deinit(); - for (self.objects.items) |*object| { + for (self.objects.items) |object, object_id| { var debug_info = blk: { - var di = try DebugInfo.parseFromObject(self.allocator, object.*); + var di = try DebugInfo.parseFromObject(self.allocator, object); break :blk di orelse continue; }; defer debug_info.deinit(self.allocator); @@ -2108,7 +2445,7 @@ fn writeDebugInfo(self: *Zld) !void { const target_syms = self.locals.get(symname) orelse continue; const target_sym: Symbol = blk: { for (target_syms.items) |ts| { - if (ts.object == object) break :blk ts; + if (ts.object_id == @intCast(u16, object_id)) break :blk ts; } else continue; }; @@ -2204,7 +2541,7 @@ fn writeSymbolTable(self: *Zld) !void { for (entries.value.items) |entry| { log.warn(" | {}", .{entry.inner}); log.warn(" | {}", .{entry.tt}); - log.warn(" | {s}", .{entry.object.name}); + log.warn(" | {s}", .{self.objects.items[entry.object_id].name}); // switch (entry.tt) { // .Global => { // symbol = entry.inner; @@ -2468,20 +2805,6 @@ pub fn parseName(name: *const [16]u8) []const u8 { return name[0..len]; } -fn addSegmentToDir(self: *Zld, idx: u16) !void { - const segment_cmd = self.load_commands.items[idx].Segment; - return self.segments_directory.putNoClobber(self.allocator, segment_cmd.inner.segname, idx); -} - -fn addSectionToDir(self: *Zld, value: DirectoryEntry) !void { - const seg = self.load_commands.items[value.seg_index].Segment; - const sect = seg.sections.items[value.sect_index]; - return self.directory.putNoClobber(self.allocator, .{ - .segname = sect.segname, - .sectname = sect.sectname, - }, value); -} - fn isLocal(sym: *const macho.nlist_64) callconv(.Inline) bool { if (isExtern(sym)) return false; const tt = macho.N_TYPE & sym.n_type; From 066c1386a3dfe0acf4c9d11ba436e7e339d2310b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 1 Mar 2021 21:14:33 +0100 Subject: [PATCH 33/56] zld: demote logs from warn to debug --- src/link/MachO/Object.zig | 2 +- src/link/MachO/Zld.zig | 113 +++++++++++++++++++------------------- 2 files changed, 57 insertions(+), 58 deletions(-) diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 6337f85a80..e657c07b76 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -149,7 +149,7 @@ pub fn readLoadCommands(self: *Object, reader: anytype, offset: ReadOffset) !voi self.build_version_cmd_index = i; }, else => { - log.info("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); + log.debug("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); }, } self.load_commands.appendAssumeCapacity(cmd); diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index f8cda5e9ad..b16fd2134d 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -331,7 +331,6 @@ fn mapAndUpdateSections( const source_sect = source_seg.sections.items[source_sect_id]; const target_seg = &self.load_commands.items[target_seg_id].Segment; const target_sect = &target_seg.sections.items[target_sect_id]; - log.warn("{}", .{target_sect}); const alignment = try math.powi(u32, 2, source_sect.@"align"); const offset = mem.alignForwardGeneric(u64, target_sect.size, alignment); @@ -346,7 +345,7 @@ fn mapAndUpdateSections( .target_sect_id = target_sect_id, .offset = @intCast(u32, offset), }); - log.warn("{s}: {s},{s} mapped to {s},{s} from 0x{x} to 0x{x}", .{ + log.debug("{s}: {s},{s} mapped to {s},{s} from 0x{x} to 0x{x}", .{ object.name, parseName(&source_sect.segname), parseName(&source_sect.sectname), @@ -515,7 +514,7 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { }); }, else => { - log.warn("unhandled section type 0x{x} for '{s}/{s}'", .{ flags, segname, sectname }); + log.debug("unhandled section type 0x{x} for '{s}/{s}'", .{ flags, segname, sectname }); }, } } @@ -614,14 +613,14 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { continue; } } - log.warn("section '{s}/{s}' will be unmapped", .{ segname, sectname }); + log.debug("section '{s}/{s}' will be unmapped", .{ segname, sectname }); try self.unhandled_sections.putNoClobber(self.allocator, .{ .object_id = object_id, .source_sect_id = source_sect_id, }, 0); }, else => { - log.warn("section '{s}/{s}' will be unmapped", .{ segname, sectname }); + log.debug("section '{s}/{s}' will be unmapped", .{ segname, sectname }); try self.unhandled_sections.putNoClobber(self.allocator, .{ .object_id = object_id, .source_sect_id = source_sect_id, @@ -785,7 +784,7 @@ fn resolveImports(self: *Zld) !void { mem.eql(u8, sym_name, "___stack_chk_guard") or mem.eql(u8, sym_name, "_environ")) { - log.warn("writing nonlazy symbol '{s}'", .{sym_name}); + log.debug("writing nonlazy symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.nonlazy_imports.items().len); try self.nonlazy_imports.putNoClobber(self.allocator, key, .{ .symbol = new_sym, @@ -793,7 +792,7 @@ fn resolveImports(self: *Zld) !void { .index = index, }); } else if (mem.eql(u8, sym_name, "__tlv_bootstrap")) { - log.warn("writing threadlocal symbol '{s}'", .{sym_name}); + log.debug("writing threadlocal symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.threadlocal_imports.items().len); try self.threadlocal_imports.putNoClobber(self.allocator, key, .{ .symbol = new_sym, @@ -801,7 +800,7 @@ fn resolveImports(self: *Zld) !void { .index = index, }); } else { - log.warn("writing lazy symbol '{s}'", .{sym_name}); + log.debug("writing lazy symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.lazy_imports.items().len); try self.lazy_imports.putNoClobber(self.allocator, key, .{ .symbol = new_sym, @@ -813,7 +812,7 @@ fn resolveImports(self: *Zld) !void { const n_strx = try self.makeString("dyld_stub_binder"); const name = try self.allocator.dupe(u8, "dyld_stub_binder"); - log.warn("writing nonlazy symbol 'dyld_stub_binder'", .{}); + log.debug("writing nonlazy symbol 'dyld_stub_binder'", .{}); const index = @intCast(u32, self.nonlazy_imports.items().len); try self.nonlazy_imports.putNoClobber(self.allocator, name, .{ .symbol = .{ @@ -1017,7 +1016,7 @@ fn writeStubHelperCommon(self: *Zld) !void { const new_this_addr = this_addr + @sizeOf(u32); const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :binder_blk; const literal = math.cast(u18, displacement) catch |_| break :binder_blk; - log.warn("2: disp=0x{x}, literal=0x{x}", .{ displacement, literal }); + log.debug("2: disp=0x{x}, literal=0x{x}", .{ displacement, literal }); // Pad with nop to please division. // nop mem.writeIntLittle(u32, code[12..16], Arm64.nop().toU32()); @@ -1070,7 +1069,7 @@ fn writeLazySymbolPointer(self: *Zld, index: u32) !void { var buf: [@sizeOf(u64)]u8 = undefined; mem.writeIntLittle(u64, &buf, end); const off = la_symbol_ptr.offset + index * @sizeOf(u64); - log.warn("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off }); + log.debug("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off }); try self.file.?.pwriteAll(&buf, off); } @@ -1083,7 +1082,7 @@ fn writeStub(self: *Zld, index: u32) !void { const stub_off = stubs.offset + index * stubs.reserved2; const stub_addr = stubs.addr + index * stubs.reserved2; const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64); - log.warn("writing stub at 0x{x}", .{stub_off}); + log.debug("writing stub at 0x{x}", .{stub_off}); var code = try self.allocator.alloc(u8, stubs.reserved2); defer self.allocator.free(code); switch (self.arch.?) { @@ -1230,7 +1229,7 @@ fn resolveSymbols(self: *Zld) !void { const target_addr = target_sect.addr + target_mapping.offset; const n_value = sym.n_value - source_sect.addr + target_addr; - log.warn("resolving '{s}':{} as {s} symbol at 0x{x}", .{ sym_name, sym, tt, n_value }); + log.debug("resolving '{s}':{} as {s} symbol at 0x{x}", .{ sym_name, sym, tt, n_value }); // TODO this assumes only two symbol-filled segments. Also, there might be a more // generic way of doing this. @@ -1260,8 +1259,8 @@ fn resolveSymbols(self: *Zld) !void { fn doRelocs(self: *Zld) !void { for (self.objects.items) |object, object_id| { - log.warn("\n\n", .{}); - log.warn("relocating object {s}", .{object.name}); + log.debug("\n\n", .{}); + log.debug("relocating object {s}", .{object.name}); const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; @@ -1284,7 +1283,7 @@ fn doRelocs(self: *Zld) !void { .object_id = @intCast(u16, object_id), .source_sect_id = @intCast(u16, source_sect_id), }) orelse { - log.warn("no mapping for {s},{s}; skipping", .{ segname, sectname }); + log.debug("no mapping for {s},{s}; skipping", .{ segname, sectname }); continue; }; const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; @@ -1302,34 +1301,34 @@ fn doRelocs(self: *Zld) !void { switch (self.arch.?) { .aarch64 => { const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); - log.warn("{s}", .{rel_type}); - log.warn(" | source address 0x{x}", .{this_addr}); - log.warn(" | offset 0x{x}", .{off}); + log.debug("{s}", .{rel_type}); + log.debug(" | source address 0x{x}", .{this_addr}); + log.debug(" | offset 0x{x}", .{off}); if (rel_type == .ARM64_RELOC_ADDEND) { addend = rel.r_symbolnum; - log.warn(" | calculated addend = 0x{x}", .{addend}); + log.debug(" | calculated addend = 0x{x}", .{addend}); // TODO followed by either PAGE21 or PAGEOFF12 only. continue; } }, .x86_64 => { const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); - log.warn("{s}", .{rel_type}); - log.warn(" | source address 0x{x}", .{this_addr}); - log.warn(" | offset 0x{x}", .{off}); + log.debug("{s}", .{rel_type}); + log.debug(" | source address 0x{x}", .{this_addr}); + log.debug(" | offset 0x{x}", .{off}); }, else => {}, } const target_addr = try self.relocTargetAddr(@intCast(u16, object_id), rel); - log.warn(" | target address 0x{x}", .{target_addr}); + log.debug(" | target address 0x{x}", .{target_addr}); if (rel.r_extern == 1) { const target_symname = object.getString(object.symtab.items[rel.r_symbolnum].n_strx); - log.warn(" | target symbol '{s}'", .{target_symname}); + log.debug(" | target symbol '{s}'", .{target_symname}); } else { const target_sectname = seg.sections.items[rel.r_symbolnum - 1].sectname; - log.warn(" | target section '{s}'", .{parseName(&target_sectname)}); + log.debug(" | target section '{s}'", .{parseName(&target_sectname)}); } switch (self.arch.?) { @@ -1379,7 +1378,7 @@ fn doRelocs(self: *Zld) !void { break :blk correction; } }; - log.warn(" | calculated addend 0x{x}", .{offset}); + log.debug(" | calculated addend 0x{x}", .{offset}); const result = @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4 + offset; const displacement = @bitCast(u32, @intCast(i32, result)); mem.writeIntLittle(u32, inst, displacement); @@ -1392,7 +1391,7 @@ fn doRelocs(self: *Zld) !void { 3 => { const inst = code[off..][0..8]; const offset = mem.readIntLittle(i64, inst); - log.warn(" | calculated addend 0x{x}", .{offset}); + log.debug(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -1423,7 +1422,7 @@ fn doRelocs(self: *Zld) !void { 2 => { const inst = code[off..][0..4]; const offset = mem.readIntLittle(i32, inst); - log.warn(" | calculated addend 0x{x}", .{offset}); + log.debug(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -1460,7 +1459,7 @@ fn doRelocs(self: *Zld) !void { const this_page = @intCast(i32, this_addr >> 12); const target_page = @intCast(i32, ta >> 12); const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); - log.warn(" | moving by {} pages", .{pages}); + log.debug(" | moving by {} pages", .{pages}); var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Address), inst); parsed.immhi = @truncate(u19, pages >> 2); parsed.immlo = @truncate(u2, pages); @@ -1471,14 +1470,14 @@ fn doRelocs(self: *Zld) !void { => { const inst = code[off..][0..4]; if (Arm64.isArithmetic(inst)) { - log.warn(" | detected ADD opcode", .{}); + log.debug(" | detected ADD opcode", .{}); // add var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Add), inst); const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); parsed.offset = narrowed; } else { - log.warn(" | detected LDR/STR opcode", .{}); + log.debug(" | detected LDR/STR opcode", .{}); // ldr/str var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.LoadRegister), inst); const ta = if (addend) |a| target_addr + a else target_addr; @@ -1519,7 +1518,7 @@ fn doRelocs(self: *Zld) !void { }; const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); - log.warn(" | rewriting TLV access to ADD opcode", .{}); + log.debug(" | rewriting TLV access to ADD opcode", .{}); // For TLV, we always generate an add instruction. mem.writeIntLittle(u32, inst, Arm64.add(parsed.rt, parsed.rn, narrowed, parsed.size).toU32()); }, @@ -1531,7 +1530,7 @@ fn doRelocs(self: *Zld) !void { 3 => { const inst = code[off..][0..8]; const offset = mem.readIntLittle(i64, inst); - log.warn(" | calculated addend 0x{x}", .{offset}); + log.debug(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -1562,7 +1561,7 @@ fn doRelocs(self: *Zld) !void { 2 => { const inst = code[off..][0..4]; const offset = mem.readIntLittle(i32, inst); - log.warn(" | calculated addend 0x{x}", .{offset}); + log.debug(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -1584,7 +1583,7 @@ fn doRelocs(self: *Zld) !void { } } - log.warn("writing contents of '{s},{s}' section from '{s}' from 0x{x} to 0x{x}", .{ + log.debug("writing contents of '{s},{s}' section from '{s}' from 0x{x} to 0x{x}", .{ segname, sectname, object.name, @@ -1596,7 +1595,7 @@ fn doRelocs(self: *Zld) !void { target_sect.flags == macho.S_THREAD_LOCAL_ZEROFILL or target_sect.flags == macho.S_THREAD_LOCAL_VARIABLES) { - log.warn("zeroing out '{s},{s}' from 0x{x} to 0x{x}", .{ + log.debug("zeroing out '{s},{s}' from 0x{x} to 0x{x}", .{ parseName(&target_sect.segname), parseName(&target_sect.sectname), target_sect_off, @@ -1630,7 +1629,7 @@ fn relocTargetAddr(self: *Zld, object_id: u16, rel: macho.relocation_info) !u64 const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; const target_sect_addr = target_sect.addr + target_mapping.offset; - log.warn(" | symbol local to object", .{}); + log.debug(" | symbol local to object", .{}); break :blk target_sect_addr + sym.n_value - source_sect.addr; } else if (isImport(&sym)) { // Relocate to either the artifact's local symbol, or an import from @@ -2190,7 +2189,7 @@ fn writeRebaseInfoTable(self: *Zld) !void { dyld_info.rebase_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @sizeOf(u64))); seg.inner.filesize += dyld_info.rebase_size; - log.warn("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size }); + log.debug("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size }); try self.file.?.pwriteAll(buffer, dyld_info.rebase_off); } @@ -2243,7 +2242,7 @@ fn writeBindInfoTable(self: *Zld) !void { dyld_info.bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); seg.inner.filesize += dyld_info.bind_size; - log.warn("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size }); + log.debug("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size }); try self.file.?.pwriteAll(buffer, dyld_info.bind_off); } @@ -2282,7 +2281,7 @@ fn writeLazyBindInfoTable(self: *Zld) !void { dyld_info.lazy_bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); seg.inner.filesize += dyld_info.lazy_bind_size; - log.warn("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); + log.debug("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); try self.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); try self.populateLazyBindOffsetsInStubHelper(buffer); @@ -2384,7 +2383,7 @@ fn writeExportInfo(self: *Zld) !void { dyld_info.export_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); seg.inner.filesize += dyld_info.export_size; - log.warn("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); + log.debug("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); try self.file.?.pwriteAll(buffer, dyld_info.export_off); } @@ -2518,7 +2517,7 @@ fn writeDebugInfo(self: *Zld) !void { const stabs_off = symtab.symoff; const stabs_size = symtab.nsyms * @sizeOf(macho.nlist_64); - log.warn("writing symbol stabs from 0x{x} to 0x{x}", .{ stabs_off, stabs_size + stabs_off }); + log.debug("writing symbol stabs from 0x{x} to 0x{x}", .{ stabs_off, stabs_size + stabs_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(stabs.items), stabs_off); linkedit.inner.filesize += stabs_size; @@ -2536,12 +2535,12 @@ fn writeSymbolTable(self: *Zld) !void { defer locals.deinit(); for (self.locals.items()) |entries| { - log.warn("'{s}': {} entries", .{ entries.key, entries.value.items.len }); + log.debug("'{s}': {} entries", .{ entries.key, entries.value.items.len }); // var symbol: ?macho.nlist_64 = null; for (entries.value.items) |entry| { - log.warn(" | {}", .{entry.inner}); - log.warn(" | {}", .{entry.tt}); - log.warn(" | {s}", .{self.objects.items[entry.object_id].name}); + log.debug(" | {}", .{entry.inner}); + log.debug(" | {}", .{entry.tt}); + log.debug(" | {s}", .{self.objects.items[entry.object_id].name}); // switch (entry.tt) { // .Global => { // symbol = entry.inner; @@ -2586,17 +2585,17 @@ fn writeSymbolTable(self: *Zld) !void { const locals_off = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); const locals_size = nlocals * @sizeOf(macho.nlist_64); - log.warn("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off }); + log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(locals.items), locals_off); const exports_off = locals_off + locals_size; const exports_size = nexports * @sizeOf(macho.nlist_64); - log.warn("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off }); + log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(exports.items), exports_off); const undefs_off = exports_off + exports_size; const undefs_size = nundefs * @sizeOf(macho.nlist_64); - log.warn("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); + log.debug("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off); symtab.nsyms += @intCast(u32, nlocals + nexports + nundefs); @@ -2627,7 +2626,7 @@ fn writeDynamicSymbolTable(self: *Zld) !void { const needed_size = dysymtab.nindirectsyms * @sizeOf(u32); seg.inner.filesize += needed_size; - log.warn("writing indirect symbol table from 0x{x} to 0x{x}", .{ + log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{ dysymtab.indirectsymoff, dysymtab.indirectsymoff + needed_size, }); @@ -2666,7 +2665,7 @@ fn writeStringTable(self: *Zld) !void { symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.items.len, @alignOf(u64))); seg.inner.filesize += symtab.strsize; - log.warn("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); + log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); try self.file.?.pwriteAll(self.strtab.items, symtab.stroff); @@ -2692,7 +2691,7 @@ fn writeCodeSignaturePadding(self: *Zld) !void { seg.inner.filesize += needed_size; seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?); - log.warn("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); + log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); // Pad out the space. We need to do this to calculate valid hashes for everything in the file // except for code signature data. @@ -2718,7 +2717,7 @@ fn writeCodeSignature(self: *Zld) !void { var stream = std.io.fixedBufferStream(buffer); try code_sig.write(stream.writer()); - log.warn("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); + log.debug("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); try self.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); } @@ -2737,7 +2736,7 @@ fn writeLoadCommands(self: *Zld) !void { } const off = @sizeOf(macho.mach_header_64); - log.warn("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds }); + log.debug("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds }); try self.file.?.pwriteAll(buffer, off); } @@ -2775,7 +2774,7 @@ fn writeHeader(self: *Zld) !void { for (self.load_commands.items) |cmd| { header.sizeofcmds += cmd.cmdsize(); } - log.warn("writing Mach-O header {}", .{header}); + log.debug("writing Mach-O header {}", .{header}); try self.file.?.pwriteAll(mem.asBytes(&header), 0); } @@ -2789,7 +2788,7 @@ pub fn makeStaticString(bytes: []const u8) [16]u8 { fn makeString(self: *Zld, bytes: []const u8) !u32 { try self.strtab.ensureCapacity(self.allocator, self.strtab.items.len + bytes.len + 1); const offset = @intCast(u32, self.strtab.items.len); - log.warn("writing new string '{s}' into string table at offset 0x{x}", .{ bytes, offset }); + log.debug("writing new string '{s}' into string table at offset 0x{x}", .{ bytes, offset }); self.strtab.appendSliceAssumeCapacity(bytes); self.strtab.appendAssumeCapacity(0); return offset; From a1b0ec5277c08b82411f830ab0e82487a6a00184 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 3 Mar 2021 01:52:21 +0100 Subject: [PATCH 34/56] zld: start bringing x64 up to speed --- lib/std/macho.zig | 14 ++ src/link/MachO/Archive.zig | 2 + src/link/MachO/Object.zig | 30 +++ src/link/MachO/Zld.zig | 438 ++++++++++++++++++++++++------------- 4 files changed, 333 insertions(+), 151 deletions(-) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 4cdb9dc40e..3cf6914ad9 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -1615,3 +1615,17 @@ pub const GenericBlob = extern struct { /// Total length of blob length: u32, }; + +/// The LC_DATA_IN_CODE load commands uses a linkedit_data_command +/// to point to an array of data_in_code_entry entries. Each entry +/// describes a range of data in a code section. +pub const data_in_code_entry = extern struct { + /// From mach_header to start of data range. + offset: u32, + + /// Number of bytes in data range. + length: u16, + + /// A DICE_KIND value. + kind: u16, +}; diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index bbb36c09fd..a96c01d649 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -210,6 +210,8 @@ fn readObject(self: *Archive, arch: std.Target.Cpu.Arch, ar_name: []const u8, re try object.readSymtab(); try object.readStrtab(); + if (object.data_in_code_cmd_index != null) try object.readDataInCode(); + log.debug("\n\n", .{}); log.debug("{s} defines symbols", .{object.name}); for (object.symtab.items) |sym| { diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index e657c07b76..3393b1f773 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -3,6 +3,7 @@ const Object = @This(); const std = @import("std"); const assert = std.debug.assert; const fs = std.fs; +const io = std.io; const log = std.log.scoped(.object); const macho = std.macho; const mem = std.mem; @@ -24,6 +25,7 @@ segment_cmd_index: ?u16 = null, symtab_cmd_index: ?u16 = null, dysymtab_cmd_index: ?u16 = null, build_version_cmd_index: ?u16 = null, +data_in_code_cmd_index: ?u16 = null, text_section_index: ?u16 = null, // __DWARF segment sections @@ -36,6 +38,8 @@ dwarf_debug_ranges_index: ?u16 = null, symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{}, strtab: std.ArrayListUnmanaged(u8) = .{}, +data_in_code_entries: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{}, + pub fn deinit(self: *Object) void { for (self.load_commands.items) |*lc| { lc.deinit(self.allocator); @@ -43,6 +47,7 @@ pub fn deinit(self: *Object) void { self.load_commands.deinit(self.allocator); self.symtab.deinit(self.allocator); self.strtab.deinit(self.allocator); + self.data_in_code_entries.deinit(self.allocator); self.allocator.free(self.name); self.file.close(); } @@ -83,6 +88,8 @@ pub fn initFromFile(allocator: *Allocator, arch: std.Target.Cpu.Arch, name: []co try self.readSymtab(); try self.readStrtab(); + if (self.data_in_code_cmd_index != null) try self.readDataInCode(); + log.debug("\n\n", .{}); log.debug("{s} defines symbols", .{self.name}); for (self.symtab.items) |sym| { @@ -148,6 +155,9 @@ pub fn readLoadCommands(self: *Object, reader: anytype, offset: ReadOffset) !voi macho.LC_BUILD_VERSION => { self.build_version_cmd_index = i; }, + macho.LC_DATA_IN_CODE => { + self.data_in_code_cmd_index = i; + }, else => { log.debug("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); }, @@ -189,3 +199,23 @@ pub fn readSection(self: Object, allocator: *Allocator, index: u16) ![]u8 { _ = try self.file.preadAll(buffer, sect.offset); return buffer; } + +pub fn readDataInCode(self: *Object) !void { + const index = self.data_in_code_cmd_index orelse return; + const data_in_code = self.load_commands.items[index].LinkeditData; + + var buffer = try self.allocator.alloc(u8, data_in_code.datasize); + defer self.allocator.free(buffer); + + _ = try self.file.preadAll(buffer, data_in_code.dataoff); + + var stream = io.fixedBufferStream(buffer); + var reader = stream.reader(); + while (true) { + const dice = reader.readStruct(macho.data_in_code_entry) catch |err| switch (err) { + error.EndOfStream => break, + else => |e| return e, + }; + try self.data_in_code_entries.append(self.allocator, dice); + } +} diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index b16fd2134d..23bf7d4bfd 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -332,7 +332,7 @@ fn mapAndUpdateSections( const target_seg = &self.load_commands.items[target_seg_id].Segment; const target_sect = &target_seg.sections.items[target_sect_id]; - const alignment = try math.powi(u32, 2, source_sect.@"align"); + const alignment = try math.powi(u32, 2, target_sect.@"align"); const offset = mem.alignForwardGeneric(u64, target_sect.size, alignment); const size = mem.alignForwardGeneric(u64, source_sect.size, alignment); const key = MappingKey{ @@ -345,7 +345,7 @@ fn mapAndUpdateSections( .target_sect_id = target_sect_id, .offset = @intCast(u32, offset), }); - log.debug("{s}: {s},{s} mapped to {s},{s} from 0x{x} to 0x{x}", .{ + log.warn("{s}: {s},{s} mapped to {s},{s} from 0x{x} to 0x{x}", .{ object.name, parseName(&source_sect.segname), parseName(&source_sect.sectname), @@ -355,7 +355,6 @@ fn mapAndUpdateSections( offset + size, }); - target_sect.@"align" = math.max(target_sect.@"align", source_sect.@"align"); target_sect.size = offset + size; } @@ -514,120 +513,117 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { }); }, else => { - log.debug("unhandled section type 0x{x} for '{s}/{s}'", .{ flags, segname, sectname }); + log.warn("unhandled section type 0x{x} for '{s}/{s}'", .{ flags, segname, sectname }); }, } } + // Find ideal section alignment. + for (object_seg.sections.items) |source_sect, id| { + if (self.getMatchingSection(source_sect)) |res| { + const target_seg = &self.load_commands.items[res.seg].Segment; + const target_sect = &target_seg.sections.items[res.sect]; + target_sect.@"align" = math.max(target_sect.@"align", source_sect.@"align"); + } + } + // Update section mappings - // __TEXT,__text has to be always defined! - try self.mapAndUpdateSections( - object_id, - object.text_section_index.?, - self.text_segment_cmd_index.?, - self.text_section_index.?, - ); - for (object_seg.sections.items) |source_sect, id| { const source_sect_id = @intCast(u16, id); - if (id == object.text_section_index.?) continue; + if (self.getMatchingSection(source_sect)) |res| { + try self.mapAndUpdateSections(object_id, source_sect_id, res.seg, res.sect); + continue; + } const segname = parseName(&source_sect.segname); const sectname = parseName(&source_sect.sectname); - const flags = source_sect.flags; + log.warn("section '{s}/{s}' will be unmapped", .{ segname, sectname }); + try self.unhandled_sections.putNoClobber(self.allocator, .{ + .object_id = object_id, + .source_sect_id = source_sect_id, + }, 0); + } +} - switch (flags) { +const MatchingSection = struct { + seg: u16, + sect: u16, +}; + +fn getMatchingSection(self: *Zld, section: macho.section_64) ?MatchingSection { + const segname = parseName(§ion.segname); + const sectname = parseName(§ion.sectname); + const res: ?MatchingSection = blk: { + switch (section.flags) { macho.S_4BYTE_LITERALS, macho.S_8BYTE_LITERALS, macho.S_16BYTE_LITERALS => { - try self.mapAndUpdateSections( - object_id, - source_sect_id, - self.text_segment_cmd_index.?, - self.text_const_section_index.?, - ); + break :blk .{ + .seg = self.text_segment_cmd_index.?, + .sect = self.text_const_section_index.?, + }; }, macho.S_CSTRING_LITERALS => { - try self.mapAndUpdateSections( - object_id, - source_sect_id, - self.text_segment_cmd_index.?, - self.cstring_section_index.?, - ); + break :blk .{ + .seg = self.text_segment_cmd_index.?, + .sect = self.cstring_section_index.?, + }; }, macho.S_ZEROFILL => { - try self.mapAndUpdateSections( - object_id, - source_sect_id, - self.data_segment_cmd_index.?, - self.bss_section_index.?, - ); + break :blk .{ + .seg = self.data_segment_cmd_index.?, + .sect = self.bss_section_index.?, + }; }, macho.S_THREAD_LOCAL_VARIABLES => { - try self.mapAndUpdateSections( - object_id, - source_sect_id, - self.data_segment_cmd_index.?, - self.tlv_section_index.?, - ); + break :blk .{ + .seg = self.data_segment_cmd_index.?, + .sect = self.tlv_section_index.?, + }; }, macho.S_THREAD_LOCAL_REGULAR => { - try self.mapAndUpdateSections( - object_id, - source_sect_id, - self.data_segment_cmd_index.?, - self.tlv_data_section_index.?, - ); + break :blk .{ + .seg = self.data_segment_cmd_index.?, + .sect = self.tlv_data_section_index.?, + }; }, macho.S_THREAD_LOCAL_ZEROFILL => { - try self.mapAndUpdateSections( - object_id, - source_sect_id, - self.data_segment_cmd_index.?, - self.tlv_bss_section_index.?, - ); + break :blk .{ + .seg = self.data_segment_cmd_index.?, + .sect = self.tlv_bss_section_index.?, + }; + }, + macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS => { + break :blk .{ + .seg = self.text_segment_cmd_index.?, + .sect = self.text_section_index.?, + }; }, macho.S_REGULAR => { if (mem.eql(u8, segname, "__TEXT")) { - try self.mapAndUpdateSections( - object_id, - source_sect_id, - self.text_segment_cmd_index.?, - self.text_const_section_index.?, - ); - continue; + break :blk .{ + .seg = self.text_segment_cmd_index.?, + .sect = self.text_const_section_index.?, + }; } else if (mem.eql(u8, segname, "__DATA")) { if (mem.eql(u8, sectname, "__data")) { - try self.mapAndUpdateSections( - object_id, - source_sect_id, - self.data_segment_cmd_index.?, - self.data_section_index.?, - ); - continue; + break :blk .{ + .seg = self.data_segment_cmd_index.?, + .sect = self.data_section_index.?, + }; } else if (mem.eql(u8, sectname, "__const")) { - try self.mapAndUpdateSections( - object_id, - source_sect_id, - self.data_segment_cmd_index.?, - self.data_const_section_index.?, - ); - continue; + break :blk .{ + .seg = self.data_segment_cmd_index.?, + .sect = self.data_const_section_index.?, + }; } } - log.debug("section '{s}/{s}' will be unmapped", .{ segname, sectname }); - try self.unhandled_sections.putNoClobber(self.allocator, .{ - .object_id = object_id, - .source_sect_id = source_sect_id, - }, 0); + break :blk null; }, else => { - log.debug("section '{s}/{s}' will be unmapped", .{ segname, sectname }); - try self.unhandled_sections.putNoClobber(self.allocator, .{ - .object_id = object_id, - .source_sect_id = source_sect_id, - }, 0); + break :blk null; }, } - } + }; + return res; } fn sortSections(self: *Zld) !void { @@ -784,7 +780,7 @@ fn resolveImports(self: *Zld) !void { mem.eql(u8, sym_name, "___stack_chk_guard") or mem.eql(u8, sym_name, "_environ")) { - log.debug("writing nonlazy symbol '{s}'", .{sym_name}); + log.warn("writing nonlazy symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.nonlazy_imports.items().len); try self.nonlazy_imports.putNoClobber(self.allocator, key, .{ .symbol = new_sym, @@ -792,7 +788,7 @@ fn resolveImports(self: *Zld) !void { .index = index, }); } else if (mem.eql(u8, sym_name, "__tlv_bootstrap")) { - log.debug("writing threadlocal symbol '{s}'", .{sym_name}); + log.warn("writing threadlocal symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.threadlocal_imports.items().len); try self.threadlocal_imports.putNoClobber(self.allocator, key, .{ .symbol = new_sym, @@ -800,7 +796,7 @@ fn resolveImports(self: *Zld) !void { .index = index, }); } else { - log.debug("writing lazy symbol '{s}'", .{sym_name}); + log.warn("writing lazy symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.lazy_imports.items().len); try self.lazy_imports.putNoClobber(self.allocator, key, .{ .symbol = new_sym, @@ -812,7 +808,7 @@ fn resolveImports(self: *Zld) !void { const n_strx = try self.makeString("dyld_stub_binder"); const name = try self.allocator.dupe(u8, "dyld_stub_binder"); - log.debug("writing nonlazy symbol 'dyld_stub_binder'", .{}); + log.warn("writing nonlazy symbol 'dyld_stub_binder'", .{}); const index = @intCast(u32, self.nonlazy_imports.items().len); try self.nonlazy_imports.putNoClobber(self.allocator, name, .{ .symbol = .{ @@ -1016,7 +1012,7 @@ fn writeStubHelperCommon(self: *Zld) !void { const new_this_addr = this_addr + @sizeOf(u32); const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :binder_blk; const literal = math.cast(u18, displacement) catch |_| break :binder_blk; - log.debug("2: disp=0x{x}, literal=0x{x}", .{ displacement, literal }); + log.warn("2: disp=0x{x}, literal=0x{x}", .{ displacement, literal }); // Pad with nop to please division. // nop mem.writeIntLittle(u32, code[12..16], Arm64.nop().toU32()); @@ -1069,7 +1065,7 @@ fn writeLazySymbolPointer(self: *Zld, index: u32) !void { var buf: [@sizeOf(u64)]u8 = undefined; mem.writeIntLittle(u64, &buf, end); const off = la_symbol_ptr.offset + index * @sizeOf(u64); - log.debug("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off }); + log.warn("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off }); try self.file.?.pwriteAll(&buf, off); } @@ -1082,7 +1078,7 @@ fn writeStub(self: *Zld, index: u32) !void { const stub_off = stubs.offset + index * stubs.reserved2; const stub_addr = stubs.addr + index * stubs.reserved2; const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64); - log.debug("writing stub at 0x{x}", .{stub_off}); + log.warn("writing stub at 0x{x}", .{stub_off}); var code = try self.allocator.alloc(u8, stubs.reserved2); defer self.allocator.free(code); switch (self.arch.?) { @@ -1229,7 +1225,7 @@ fn resolveSymbols(self: *Zld) !void { const target_addr = target_sect.addr + target_mapping.offset; const n_value = sym.n_value - source_sect.addr + target_addr; - log.debug("resolving '{s}':{} as {s} symbol at 0x{x}", .{ sym_name, sym, tt, n_value }); + log.warn("resolving '{s}':{} as {s} symbol at 0x{x}", .{ sym_name, sym, tt, n_value }); // TODO this assumes only two symbol-filled segments. Also, there might be a more // generic way of doing this. @@ -1259,8 +1255,8 @@ fn resolveSymbols(self: *Zld) !void { fn doRelocs(self: *Zld) !void { for (self.objects.items) |object, object_id| { - log.debug("\n\n", .{}); - log.debug("relocating object {s}", .{object.name}); + log.warn("\n\n", .{}); + log.warn("relocating object {s}", .{object.name}); const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; @@ -1283,7 +1279,7 @@ fn doRelocs(self: *Zld) !void { .object_id = @intCast(u16, object_id), .source_sect_id = @intCast(u16, source_sect_id), }) orelse { - log.debug("no mapping for {s},{s}; skipping", .{ segname, sectname }); + log.warn("no mapping for {s},{s}; skipping", .{ segname, sectname }); continue; }; const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; @@ -1301,34 +1297,34 @@ fn doRelocs(self: *Zld) !void { switch (self.arch.?) { .aarch64 => { const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); - log.debug("{s}", .{rel_type}); - log.debug(" | source address 0x{x}", .{this_addr}); - log.debug(" | offset 0x{x}", .{off}); + log.warn("{s}", .{rel_type}); + log.warn(" | source address 0x{x}", .{this_addr}); + log.warn(" | offset 0x{x}", .{off}); if (rel_type == .ARM64_RELOC_ADDEND) { addend = rel.r_symbolnum; - log.debug(" | calculated addend = 0x{x}", .{addend}); + log.warn(" | calculated addend = 0x{x}", .{addend}); // TODO followed by either PAGE21 or PAGEOFF12 only. continue; } }, .x86_64 => { const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); - log.debug("{s}", .{rel_type}); - log.debug(" | source address 0x{x}", .{this_addr}); - log.debug(" | offset 0x{x}", .{off}); + log.warn("{s}", .{rel_type}); + log.warn(" | source address 0x{x}", .{this_addr}); + log.warn(" | offset 0x{x}", .{off}); }, else => {}, } const target_addr = try self.relocTargetAddr(@intCast(u16, object_id), rel); - log.debug(" | target address 0x{x}", .{target_addr}); + log.warn(" | target address 0x{x}", .{target_addr}); if (rel.r_extern == 1) { const target_symname = object.getString(object.symtab.items[rel.r_symbolnum].n_strx); - log.debug(" | target symbol '{s}'", .{target_symname}); + log.warn(" | target symbol '{s}'", .{target_symname}); } else { const target_sectname = seg.sections.items[rel.r_symbolnum - 1].sectname; - log.debug(" | target section '{s}'", .{parseName(&target_sectname)}); + log.warn(" | target section '{s}'", .{parseName(&target_sectname)}); } switch (self.arch.?) { @@ -1361,13 +1357,12 @@ fn doRelocs(self: *Zld) !void { => { assert(rel.r_length == 2); const inst = code[off..][0..4]; - const offset: i32 = blk: { + const offset = @intCast(i64, mem.readIntLittle(i32, inst)); + log.warn(" | calculated addend 0x{x}", .{offset}); + const actual_target_addr = blk: { if (rel.r_extern == 1) { - break :blk mem.readIntLittle(i32, inst); + break :blk @intCast(i64, target_addr) + offset; } else { - // TODO it might be required here to parse the offset from the instruction placeholder, - // compare the displacement with the original displacement in the .o file, and adjust - // the displacement in the resultant binary file. const correction: i4 = switch (rel_type) { .X86_64_RELOC_SIGNED => 0, .X86_64_RELOC_SIGNED_1 => 1, @@ -1375,11 +1370,28 @@ fn doRelocs(self: *Zld) !void { .X86_64_RELOC_SIGNED_4 => 4, else => unreachable, }; - break :blk correction; + log.warn(" | calculated correction 0x{x}", .{correction}); + + // The value encoded in the instruction is a displacement - 4 - correction. + // To obtain the adjusted target address in the final binary, we need + // calculate the original target address within the object file, establish + // what the offset from the original target section was, and apply this + // offset to the resultant target section with this relocated binary. + const orig_sect_id = @intCast(u16, rel.r_symbolnum - 1); + const target_map = self.mappings.get(.{ + .object_id = @intCast(u16, object_id), + .source_sect_id = orig_sect_id, + }) orelse unreachable; + const orig_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + const orig_sect = orig_seg.sections.items[orig_sect_id]; + const orig_offset = off + offset + 4 + correction - @intCast(i64, orig_sect.addr); + log.warn(" | original offset 0x{x}", .{orig_offset}); + const adjusted = @intCast(i64, target_addr) + orig_offset; + log.warn(" | adjusted target address 0x{x}", .{adjusted}); + break :blk adjusted - correction; } }; - log.debug(" | calculated addend 0x{x}", .{offset}); - const result = @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4 + offset; + const result = actual_target_addr - @intCast(i64, this_addr) - 4; const displacement = @bitCast(u32, @intCast(i32, result)); mem.writeIntLittle(u32, inst, displacement); }, @@ -1391,11 +1403,40 @@ fn doRelocs(self: *Zld) !void { 3 => { const inst = code[off..][0..8]; const offset = mem.readIntLittle(i64, inst); - log.debug(" | calculated addend 0x{x}", .{offset}); - const result = if (sub) |s| - @intCast(i64, target_addr) - s + offset - else - @intCast(i64, target_addr) + offset; + + const result = outer: { + if (rel.r_extern == 1) { + log.warn(" | calculated addend 0x{x}", .{offset}); + if (sub) |s| { + break :outer @intCast(i64, target_addr) - s + offset; + } else { + break :outer @intCast(i64, target_addr) + offset; + } + } else { + // The value encoded in the instruction is an absolute offset + // from the start of MachO header to the target address in the + // object file. To extract the address, we calculate the offset from + // the beginning of the source section to the address, and apply it to + // the target address value. + const orig_sect_id = @intCast(u16, rel.r_symbolnum - 1); + const target_map = self.mappings.get(.{ + .object_id = @intCast(u16, object_id), + .source_sect_id = orig_sect_id, + }) orelse unreachable; + const orig_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + const orig_sect = orig_seg.sections.items[orig_sect_id]; + const orig_offset = offset - @intCast(i64, orig_sect.addr); + const actual_target_addr = inner: { + if (sub) |s| { + break :inner @intCast(i64, target_addr) - s + orig_offset; + } else { + break :inner @intCast(i64, target_addr) + orig_offset; + } + }; + log.warn(" | adjusted target address 0x{x}", .{actual_target_addr}); + break :outer actual_target_addr; + } + }; mem.writeIntLittle(u64, inst, @bitCast(u64, result)); sub = null; @@ -1422,7 +1463,7 @@ fn doRelocs(self: *Zld) !void { 2 => { const inst = code[off..][0..4]; const offset = mem.readIntLittle(i32, inst); - log.debug(" | calculated addend 0x{x}", .{offset}); + log.warn(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -1459,7 +1500,7 @@ fn doRelocs(self: *Zld) !void { const this_page = @intCast(i32, this_addr >> 12); const target_page = @intCast(i32, ta >> 12); const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); - log.debug(" | moving by {} pages", .{pages}); + log.warn(" | moving by {} pages", .{pages}); var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Address), inst); parsed.immhi = @truncate(u19, pages >> 2); parsed.immlo = @truncate(u2, pages); @@ -1470,14 +1511,14 @@ fn doRelocs(self: *Zld) !void { => { const inst = code[off..][0..4]; if (Arm64.isArithmetic(inst)) { - log.debug(" | detected ADD opcode", .{}); + log.warn(" | detected ADD opcode", .{}); // add var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Add), inst); const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); parsed.offset = narrowed; } else { - log.debug(" | detected LDR/STR opcode", .{}); + log.warn(" | detected LDR/STR opcode", .{}); // ldr/str var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.LoadRegister), inst); const ta = if (addend) |a| target_addr + a else target_addr; @@ -1518,7 +1559,7 @@ fn doRelocs(self: *Zld) !void { }; const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); - log.debug(" | rewriting TLV access to ADD opcode", .{}); + log.warn(" | rewriting TLV access to ADD opcode", .{}); // For TLV, we always generate an add instruction. mem.writeIntLittle(u32, inst, Arm64.add(parsed.rt, parsed.rn, narrowed, parsed.size).toU32()); }, @@ -1530,7 +1571,7 @@ fn doRelocs(self: *Zld) !void { 3 => { const inst = code[off..][0..8]; const offset = mem.readIntLittle(i64, inst); - log.debug(" | calculated addend 0x{x}", .{offset}); + log.warn(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -1561,7 +1602,7 @@ fn doRelocs(self: *Zld) !void { 2 => { const inst = code[off..][0..4]; const offset = mem.readIntLittle(i32, inst); - log.debug(" | calculated addend 0x{x}", .{offset}); + log.warn(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -1583,7 +1624,7 @@ fn doRelocs(self: *Zld) !void { } } - log.debug("writing contents of '{s},{s}' section from '{s}' from 0x{x} to 0x{x}", .{ + log.warn("writing contents of '{s},{s}' section from '{s}' from 0x{x} to 0x{x}", .{ segname, sectname, object.name, @@ -1595,7 +1636,7 @@ fn doRelocs(self: *Zld) !void { target_sect.flags == macho.S_THREAD_LOCAL_ZEROFILL or target_sect.flags == macho.S_THREAD_LOCAL_VARIABLES) { - log.debug("zeroing out '{s},{s}' from 0x{x} to 0x{x}", .{ + log.warn("zeroing out '{s},{s}' from 0x{x} to 0x{x}", .{ parseName(&target_sect.segname), parseName(&target_sect.sectname), target_sect_off, @@ -1629,7 +1670,7 @@ fn relocTargetAddr(self: *Zld, object_id: u16, rel: macho.relocation_info) !u64 const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; const target_sect_addr = target_sect.addr + target_mapping.offset; - log.debug(" | symbol local to object", .{}); + log.warn(" | symbol local to object", .{}); break :blk target_sect_addr + sym.n_value - source_sect.addr; } else if (isImport(&sym)) { // Relocate to either the artifact's local symbol, or an import from @@ -2059,6 +2100,18 @@ fn populateMetadata(self: *Zld) !void { }, }); } + + if (self.data_in_code_cmd_index == null and self.arch.? == .x86_64) { + self.data_in_code_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .LinkeditData = .{ + .cmd = macho.LC_DATA_IN_CODE, + .cmdsize = @sizeOf(macho.linkedit_data_command), + .dataoff = 0, + .datasize = 0, + }, + }); + } } fn flush(self: *Zld) !void { @@ -2077,6 +2130,9 @@ fn flush(self: *Zld) !void { try self.writeBindInfoTable(); try self.writeLazyBindInfoTable(); try self.writeExportInfo(); + if (self.arch.? == .x86_64) { + try self.writeDataInCode(); + } { const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; @@ -2169,12 +2225,42 @@ fn writeRebaseInfoTable(self: *Zld) !void { } try pointers.ensureCapacity(pointers.items.len + self.local_rebases.items.len); + pointers.appendSliceAssumeCapacity(self.local_rebases.items); - const nlocals = self.local_rebases.items.len; - var i = nlocals; - while (i > 0) : (i -= 1) { - pointers.appendAssumeCapacity(self.local_rebases.items[i - 1]); - } + // const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + // const base_id = text_seg.sections.items.len; + // for (self.locals.items()) |entry| { + // for (entry.value.items) |symbol| { + // const local = symbol.inner; + + // if (self.data_const_section_index) |index| { + // if (local.n_sect == base_id + index) { + // const offset = local.n_value - data_seg.inner.vmaddr; + // try pointers.append(.{ + // .offset = offset, + // .segment_id = @intCast(u16, self.data_segment_cmd_index.?), + // }); + // } + // } + // if (self.data_section_index) |index| { + // if (local.n_sect == base_id + index) { + // const offset = local.n_value - data_seg.inner.vmaddr; + // try pointers.append(.{ + // .offset = offset, + // .segment_id = @intCast(u16, self.data_segment_cmd_index.?), + // }); + // } + // } + // } + // } + + std.sort.sort(Pointer, pointers.items, {}, pointerCmp); + + // const nlocals = self.local_rebases.items.len; + // var i = nlocals; + // while (i > 0) : (i -= 1) { + // pointers.appendAssumeCapacity(self.local_rebases.items[i - 1]); + // } const size = try rebaseInfoSize(pointers.items); var buffer = try self.allocator.alloc(u8, @intCast(usize, size)); @@ -2189,11 +2275,19 @@ fn writeRebaseInfoTable(self: *Zld) !void { dyld_info.rebase_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @sizeOf(u64))); seg.inner.filesize += dyld_info.rebase_size; - log.debug("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size }); + log.warn("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size }); try self.file.?.pwriteAll(buffer, dyld_info.rebase_off); } +fn pointerCmp(context: void, a: Pointer, b: Pointer) bool { + if (a.segment_id < b.segment_id) return true; + if (a.segment_id == b.segment_id) { + return a.offset < b.offset; + } + return false; +} + fn writeBindInfoTable(self: *Zld) !void { const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; @@ -2242,7 +2336,7 @@ fn writeBindInfoTable(self: *Zld) !void { dyld_info.bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); seg.inner.filesize += dyld_info.bind_size; - log.debug("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size }); + log.warn("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size }); try self.file.?.pwriteAll(buffer, dyld_info.bind_off); } @@ -2281,7 +2375,7 @@ fn writeLazyBindInfoTable(self: *Zld) !void { dyld_info.lazy_bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); seg.inner.filesize += dyld_info.lazy_bind_size; - log.debug("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); + log.warn("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); try self.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); try self.populateLazyBindOffsetsInStubHelper(buffer); @@ -2383,7 +2477,7 @@ fn writeExportInfo(self: *Zld) !void { dyld_info.export_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); seg.inner.filesize += dyld_info.export_size; - log.debug("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); + log.warn("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); try self.file.?.pwriteAll(buffer, dyld_info.export_off); } @@ -2517,7 +2611,7 @@ fn writeDebugInfo(self: *Zld) !void { const stabs_off = symtab.symoff; const stabs_size = symtab.nsyms * @sizeOf(macho.nlist_64); - log.debug("writing symbol stabs from 0x{x} to 0x{x}", .{ stabs_off, stabs_size + stabs_off }); + log.warn("writing symbol stabs from 0x{x} to 0x{x}", .{ stabs_off, stabs_size + stabs_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(stabs.items), stabs_off); linkedit.inner.filesize += stabs_size; @@ -2535,12 +2629,12 @@ fn writeSymbolTable(self: *Zld) !void { defer locals.deinit(); for (self.locals.items()) |entries| { - log.debug("'{s}': {} entries", .{ entries.key, entries.value.items.len }); + log.warn("'{s}': {} entries", .{ entries.key, entries.value.items.len }); // var symbol: ?macho.nlist_64 = null; for (entries.value.items) |entry| { - log.debug(" | {}", .{entry.inner}); - log.debug(" | {}", .{entry.tt}); - log.debug(" | {s}", .{self.objects.items[entry.object_id].name}); + log.warn(" | {}", .{entry.inner}); + log.warn(" | {}", .{entry.tt}); + log.warn(" | {s}", .{self.objects.items[entry.object_id].name}); // switch (entry.tt) { // .Global => { // symbol = entry.inner; @@ -2585,17 +2679,17 @@ fn writeSymbolTable(self: *Zld) !void { const locals_off = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); const locals_size = nlocals * @sizeOf(macho.nlist_64); - log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off }); + log.warn("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(locals.items), locals_off); const exports_off = locals_off + locals_size; const exports_size = nexports * @sizeOf(macho.nlist_64); - log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off }); + log.warn("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(exports.items), exports_off); const undefs_off = exports_off + exports_size; const undefs_size = nundefs * @sizeOf(macho.nlist_64); - log.debug("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); + log.warn("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off); symtab.nsyms += @intCast(u32, nlocals + nexports + nundefs); @@ -2626,7 +2720,7 @@ fn writeDynamicSymbolTable(self: *Zld) !void { const needed_size = dysymtab.nindirectsyms * @sizeOf(u32); seg.inner.filesize += needed_size; - log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{ + log.warn("writing indirect symbol table from 0x{x} to 0x{x}", .{ dysymtab.indirectsymoff, dysymtab.indirectsymoff + needed_size, }); @@ -2665,7 +2759,7 @@ fn writeStringTable(self: *Zld) !void { symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.items.len, @alignOf(u64))); seg.inner.filesize += symtab.strsize; - log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); + log.warn("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); try self.file.?.pwriteAll(self.strtab.items, symtab.stroff); @@ -2675,6 +2769,48 @@ fn writeStringTable(self: *Zld) !void { } } +fn writeDataInCode(self: *Zld) !void { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dice_cmd = &self.load_commands.items[self.data_in_code_cmd_index.?].LinkeditData; + const fileoff = seg.inner.fileoff + seg.inner.filesize; + + var buf = std.ArrayList(u8).init(self.allocator); + defer buf.deinit(); + + const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const text_sect = text_seg.sections.items[self.text_section_index.?]; + for (self.objects.items) |object, object_id| { + const source_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + const source_sect = source_seg.sections.items[object.text_section_index.?]; + const target_mapping = self.mappings.get(.{ + .object_id = @intCast(u16, object_id), + .source_sect_id = object.text_section_index.?, + }) orelse continue; + + // TODO Currently assume that Dice will always be within the __TEXT,__text section. + try buf.ensureCapacity( + buf.items.len + object.data_in_code_entries.items.len * @sizeOf(macho.data_in_code_entry), + ); + for (object.data_in_code_entries.items) |dice| { + const new_dice: macho.data_in_code_entry = .{ + .offset = text_sect.offset + target_mapping.offset + dice.offset - source_sect.offset, + .length = dice.length, + .kind = dice.kind, + }; + buf.appendSliceAssumeCapacity(mem.asBytes(&new_dice)); + } + } + const datasize = @intCast(u32, buf.items.len); + + dice_cmd.dataoff = @intCast(u32, fileoff); + dice_cmd.datasize = datasize; + seg.inner.filesize += datasize; + + log.warn("writing data-in-code from 0x{x} to 0x{x}", .{ fileoff, fileoff + datasize }); + + try self.file.?.pwriteAll(buf.items, fileoff); +} + fn writeCodeSignaturePadding(self: *Zld) !void { const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; @@ -2691,7 +2827,7 @@ fn writeCodeSignaturePadding(self: *Zld) !void { seg.inner.filesize += needed_size; seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?); - log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); + log.warn("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); // Pad out the space. We need to do this to calculate valid hashes for everything in the file // except for code signature data. @@ -2717,7 +2853,7 @@ fn writeCodeSignature(self: *Zld) !void { var stream = std.io.fixedBufferStream(buffer); try code_sig.write(stream.writer()); - log.debug("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); + log.warn("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); try self.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); } @@ -2736,7 +2872,7 @@ fn writeLoadCommands(self: *Zld) !void { } const off = @sizeOf(macho.mach_header_64); - log.debug("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds }); + log.warn("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds }); try self.file.?.pwriteAll(buffer, off); } @@ -2774,7 +2910,7 @@ fn writeHeader(self: *Zld) !void { for (self.load_commands.items) |cmd| { header.sizeofcmds += cmd.cmdsize(); } - log.debug("writing Mach-O header {}", .{header}); + log.warn("writing Mach-O header {}", .{header}); try self.file.?.pwriteAll(mem.asBytes(&header), 0); } @@ -2788,7 +2924,7 @@ pub fn makeStaticString(bytes: []const u8) [16]u8 { fn makeString(self: *Zld, bytes: []const u8) !u32 { try self.strtab.ensureCapacity(self.allocator, self.strtab.items.len + bytes.len + 1); const offset = @intCast(u32, self.strtab.items.len); - log.debug("writing new string '{s}' into string table at offset 0x{x}", .{ bytes, offset }); + log.warn("writing new string '{s}' into string table at offset 0x{x}", .{ bytes, offset }); self.strtab.appendSliceAssumeCapacity(bytes); self.strtab.appendAssumeCapacity(0); return offset; From 5d8944edc1361aeb36a55d8435ef1feecf714958 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 4 Mar 2021 18:35:26 +0100 Subject: [PATCH 35/56] Revert log.warn hack --- src/link/MachO/Zld.zig | 120 ++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 23bf7d4bfd..3714427e4e 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -345,7 +345,7 @@ fn mapAndUpdateSections( .target_sect_id = target_sect_id, .offset = @intCast(u32, offset), }); - log.warn("{s}: {s},{s} mapped to {s},{s} from 0x{x} to 0x{x}", .{ + log.debug("{s}: {s},{s} mapped to {s},{s} from 0x{x} to 0x{x}", .{ object.name, parseName(&source_sect.segname), parseName(&source_sect.sectname), @@ -513,7 +513,7 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { }); }, else => { - log.warn("unhandled section type 0x{x} for '{s}/{s}'", .{ flags, segname, sectname }); + log.debug("unhandled section type 0x{x} for '{s}/{s}'", .{ flags, segname, sectname }); }, } } @@ -537,7 +537,7 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { const segname = parseName(&source_sect.segname); const sectname = parseName(&source_sect.sectname); - log.warn("section '{s}/{s}' will be unmapped", .{ segname, sectname }); + log.debug("section '{s}/{s}' will be unmapped", .{ segname, sectname }); try self.unhandled_sections.putNoClobber(self.allocator, .{ .object_id = object_id, .source_sect_id = source_sect_id, @@ -780,7 +780,7 @@ fn resolveImports(self: *Zld) !void { mem.eql(u8, sym_name, "___stack_chk_guard") or mem.eql(u8, sym_name, "_environ")) { - log.warn("writing nonlazy symbol '{s}'", .{sym_name}); + log.debug("writing nonlazy symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.nonlazy_imports.items().len); try self.nonlazy_imports.putNoClobber(self.allocator, key, .{ .symbol = new_sym, @@ -788,7 +788,7 @@ fn resolveImports(self: *Zld) !void { .index = index, }); } else if (mem.eql(u8, sym_name, "__tlv_bootstrap")) { - log.warn("writing threadlocal symbol '{s}'", .{sym_name}); + log.debug("writing threadlocal symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.threadlocal_imports.items().len); try self.threadlocal_imports.putNoClobber(self.allocator, key, .{ .symbol = new_sym, @@ -796,7 +796,7 @@ fn resolveImports(self: *Zld) !void { .index = index, }); } else { - log.warn("writing lazy symbol '{s}'", .{sym_name}); + log.debug("writing lazy symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.lazy_imports.items().len); try self.lazy_imports.putNoClobber(self.allocator, key, .{ .symbol = new_sym, @@ -808,7 +808,7 @@ fn resolveImports(self: *Zld) !void { const n_strx = try self.makeString("dyld_stub_binder"); const name = try self.allocator.dupe(u8, "dyld_stub_binder"); - log.warn("writing nonlazy symbol 'dyld_stub_binder'", .{}); + log.debug("writing nonlazy symbol 'dyld_stub_binder'", .{}); const index = @intCast(u32, self.nonlazy_imports.items().len); try self.nonlazy_imports.putNoClobber(self.allocator, name, .{ .symbol = .{ @@ -1012,7 +1012,7 @@ fn writeStubHelperCommon(self: *Zld) !void { const new_this_addr = this_addr + @sizeOf(u32); const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :binder_blk; const literal = math.cast(u18, displacement) catch |_| break :binder_blk; - log.warn("2: disp=0x{x}, literal=0x{x}", .{ displacement, literal }); + log.debug("2: disp=0x{x}, literal=0x{x}", .{ displacement, literal }); // Pad with nop to please division. // nop mem.writeIntLittle(u32, code[12..16], Arm64.nop().toU32()); @@ -1065,7 +1065,7 @@ fn writeLazySymbolPointer(self: *Zld, index: u32) !void { var buf: [@sizeOf(u64)]u8 = undefined; mem.writeIntLittle(u64, &buf, end); const off = la_symbol_ptr.offset + index * @sizeOf(u64); - log.warn("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off }); + log.debug("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off }); try self.file.?.pwriteAll(&buf, off); } @@ -1078,7 +1078,7 @@ fn writeStub(self: *Zld, index: u32) !void { const stub_off = stubs.offset + index * stubs.reserved2; const stub_addr = stubs.addr + index * stubs.reserved2; const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64); - log.warn("writing stub at 0x{x}", .{stub_off}); + log.debug("writing stub at 0x{x}", .{stub_off}); var code = try self.allocator.alloc(u8, stubs.reserved2); defer self.allocator.free(code); switch (self.arch.?) { @@ -1225,7 +1225,7 @@ fn resolveSymbols(self: *Zld) !void { const target_addr = target_sect.addr + target_mapping.offset; const n_value = sym.n_value - source_sect.addr + target_addr; - log.warn("resolving '{s}':{} as {s} symbol at 0x{x}", .{ sym_name, sym, tt, n_value }); + log.debug("resolving '{s}':{} as {s} symbol at 0x{x}", .{ sym_name, sym, tt, n_value }); // TODO this assumes only two symbol-filled segments. Also, there might be a more // generic way of doing this. @@ -1255,8 +1255,8 @@ fn resolveSymbols(self: *Zld) !void { fn doRelocs(self: *Zld) !void { for (self.objects.items) |object, object_id| { - log.warn("\n\n", .{}); - log.warn("relocating object {s}", .{object.name}); + log.debug("\n\n", .{}); + log.debug("relocating object {s}", .{object.name}); const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; @@ -1279,7 +1279,7 @@ fn doRelocs(self: *Zld) !void { .object_id = @intCast(u16, object_id), .source_sect_id = @intCast(u16, source_sect_id), }) orelse { - log.warn("no mapping for {s},{s}; skipping", .{ segname, sectname }); + log.debug("no mapping for {s},{s}; skipping", .{ segname, sectname }); continue; }; const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; @@ -1297,34 +1297,34 @@ fn doRelocs(self: *Zld) !void { switch (self.arch.?) { .aarch64 => { const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); - log.warn("{s}", .{rel_type}); - log.warn(" | source address 0x{x}", .{this_addr}); - log.warn(" | offset 0x{x}", .{off}); + log.debug("{s}", .{rel_type}); + log.debug(" | source address 0x{x}", .{this_addr}); + log.debug(" | offset 0x{x}", .{off}); if (rel_type == .ARM64_RELOC_ADDEND) { addend = rel.r_symbolnum; - log.warn(" | calculated addend = 0x{x}", .{addend}); + log.debug(" | calculated addend = 0x{x}", .{addend}); // TODO followed by either PAGE21 or PAGEOFF12 only. continue; } }, .x86_64 => { const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); - log.warn("{s}", .{rel_type}); - log.warn(" | source address 0x{x}", .{this_addr}); - log.warn(" | offset 0x{x}", .{off}); + log.debug("{s}", .{rel_type}); + log.debug(" | source address 0x{x}", .{this_addr}); + log.debug(" | offset 0x{x}", .{off}); }, else => {}, } const target_addr = try self.relocTargetAddr(@intCast(u16, object_id), rel); - log.warn(" | target address 0x{x}", .{target_addr}); + log.debug(" | target address 0x{x}", .{target_addr}); if (rel.r_extern == 1) { const target_symname = object.getString(object.symtab.items[rel.r_symbolnum].n_strx); - log.warn(" | target symbol '{s}'", .{target_symname}); + log.debug(" | target symbol '{s}'", .{target_symname}); } else { const target_sectname = seg.sections.items[rel.r_symbolnum - 1].sectname; - log.warn(" | target section '{s}'", .{parseName(&target_sectname)}); + log.debug(" | target section '{s}'", .{parseName(&target_sectname)}); } switch (self.arch.?) { @@ -1358,7 +1358,7 @@ fn doRelocs(self: *Zld) !void { assert(rel.r_length == 2); const inst = code[off..][0..4]; const offset = @intCast(i64, mem.readIntLittle(i32, inst)); - log.warn(" | calculated addend 0x{x}", .{offset}); + log.debug(" | calculated addend 0x{x}", .{offset}); const actual_target_addr = blk: { if (rel.r_extern == 1) { break :blk @intCast(i64, target_addr) + offset; @@ -1370,7 +1370,7 @@ fn doRelocs(self: *Zld) !void { .X86_64_RELOC_SIGNED_4 => 4, else => unreachable, }; - log.warn(" | calculated correction 0x{x}", .{correction}); + log.debug(" | calculated correction 0x{x}", .{correction}); // The value encoded in the instruction is a displacement - 4 - correction. // To obtain the adjusted target address in the final binary, we need @@ -1385,9 +1385,9 @@ fn doRelocs(self: *Zld) !void { const orig_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; const orig_sect = orig_seg.sections.items[orig_sect_id]; const orig_offset = off + offset + 4 + correction - @intCast(i64, orig_sect.addr); - log.warn(" | original offset 0x{x}", .{orig_offset}); + log.debug(" | original offset 0x{x}", .{orig_offset}); const adjusted = @intCast(i64, target_addr) + orig_offset; - log.warn(" | adjusted target address 0x{x}", .{adjusted}); + log.debug(" | adjusted target address 0x{x}", .{adjusted}); break :blk adjusted - correction; } }; @@ -1406,7 +1406,7 @@ fn doRelocs(self: *Zld) !void { const result = outer: { if (rel.r_extern == 1) { - log.warn(" | calculated addend 0x{x}", .{offset}); + log.debug(" | calculated addend 0x{x}", .{offset}); if (sub) |s| { break :outer @intCast(i64, target_addr) - s + offset; } else { @@ -1433,7 +1433,7 @@ fn doRelocs(self: *Zld) !void { break :inner @intCast(i64, target_addr) + orig_offset; } }; - log.warn(" | adjusted target address 0x{x}", .{actual_target_addr}); + log.debug(" | adjusted target address 0x{x}", .{actual_target_addr}); break :outer actual_target_addr; } }; @@ -1463,7 +1463,7 @@ fn doRelocs(self: *Zld) !void { 2 => { const inst = code[off..][0..4]; const offset = mem.readIntLittle(i32, inst); - log.warn(" | calculated addend 0x{x}", .{offset}); + log.debug(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -1500,7 +1500,7 @@ fn doRelocs(self: *Zld) !void { const this_page = @intCast(i32, this_addr >> 12); const target_page = @intCast(i32, ta >> 12); const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); - log.warn(" | moving by {} pages", .{pages}); + log.debug(" | moving by {} pages", .{pages}); var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Address), inst); parsed.immhi = @truncate(u19, pages >> 2); parsed.immlo = @truncate(u2, pages); @@ -1511,14 +1511,14 @@ fn doRelocs(self: *Zld) !void { => { const inst = code[off..][0..4]; if (Arm64.isArithmetic(inst)) { - log.warn(" | detected ADD opcode", .{}); + log.debug(" | detected ADD opcode", .{}); // add var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Add), inst); const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); parsed.offset = narrowed; } else { - log.warn(" | detected LDR/STR opcode", .{}); + log.debug(" | detected LDR/STR opcode", .{}); // ldr/str var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.LoadRegister), inst); const ta = if (addend) |a| target_addr + a else target_addr; @@ -1559,7 +1559,7 @@ fn doRelocs(self: *Zld) !void { }; const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); - log.warn(" | rewriting TLV access to ADD opcode", .{}); + log.debug(" | rewriting TLV access to ADD opcode", .{}); // For TLV, we always generate an add instruction. mem.writeIntLittle(u32, inst, Arm64.add(parsed.rt, parsed.rn, narrowed, parsed.size).toU32()); }, @@ -1571,7 +1571,7 @@ fn doRelocs(self: *Zld) !void { 3 => { const inst = code[off..][0..8]; const offset = mem.readIntLittle(i64, inst); - log.warn(" | calculated addend 0x{x}", .{offset}); + log.debug(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -1602,7 +1602,7 @@ fn doRelocs(self: *Zld) !void { 2 => { const inst = code[off..][0..4]; const offset = mem.readIntLittle(i32, inst); - log.warn(" | calculated addend 0x{x}", .{offset}); + log.debug(" | calculated addend 0x{x}", .{offset}); const result = if (sub) |s| @intCast(i64, target_addr) - s + offset else @@ -1624,7 +1624,7 @@ fn doRelocs(self: *Zld) !void { } } - log.warn("writing contents of '{s},{s}' section from '{s}' from 0x{x} to 0x{x}", .{ + log.debug("writing contents of '{s},{s}' section from '{s}' from 0x{x} to 0x{x}", .{ segname, sectname, object.name, @@ -1636,7 +1636,7 @@ fn doRelocs(self: *Zld) !void { target_sect.flags == macho.S_THREAD_LOCAL_ZEROFILL or target_sect.flags == macho.S_THREAD_LOCAL_VARIABLES) { - log.warn("zeroing out '{s},{s}' from 0x{x} to 0x{x}", .{ + log.debug("zeroing out '{s},{s}' from 0x{x} to 0x{x}", .{ parseName(&target_sect.segname), parseName(&target_sect.sectname), target_sect_off, @@ -1670,7 +1670,7 @@ fn relocTargetAddr(self: *Zld, object_id: u16, rel: macho.relocation_info) !u64 const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; const target_sect_addr = target_sect.addr + target_mapping.offset; - log.warn(" | symbol local to object", .{}); + log.debug(" | symbol local to object", .{}); break :blk target_sect_addr + sym.n_value - source_sect.addr; } else if (isImport(&sym)) { // Relocate to either the artifact's local symbol, or an import from @@ -2275,7 +2275,7 @@ fn writeRebaseInfoTable(self: *Zld) !void { dyld_info.rebase_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @sizeOf(u64))); seg.inner.filesize += dyld_info.rebase_size; - log.warn("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size }); + log.debug("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size }); try self.file.?.pwriteAll(buffer, dyld_info.rebase_off); } @@ -2336,7 +2336,7 @@ fn writeBindInfoTable(self: *Zld) !void { dyld_info.bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); seg.inner.filesize += dyld_info.bind_size; - log.warn("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size }); + log.debug("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size }); try self.file.?.pwriteAll(buffer, dyld_info.bind_off); } @@ -2375,7 +2375,7 @@ fn writeLazyBindInfoTable(self: *Zld) !void { dyld_info.lazy_bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); seg.inner.filesize += dyld_info.lazy_bind_size; - log.warn("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); + log.debug("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); try self.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); try self.populateLazyBindOffsetsInStubHelper(buffer); @@ -2477,7 +2477,7 @@ fn writeExportInfo(self: *Zld) !void { dyld_info.export_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); seg.inner.filesize += dyld_info.export_size; - log.warn("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); + log.debug("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); try self.file.?.pwriteAll(buffer, dyld_info.export_off); } @@ -2611,7 +2611,7 @@ fn writeDebugInfo(self: *Zld) !void { const stabs_off = symtab.symoff; const stabs_size = symtab.nsyms * @sizeOf(macho.nlist_64); - log.warn("writing symbol stabs from 0x{x} to 0x{x}", .{ stabs_off, stabs_size + stabs_off }); + log.debug("writing symbol stabs from 0x{x} to 0x{x}", .{ stabs_off, stabs_size + stabs_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(stabs.items), stabs_off); linkedit.inner.filesize += stabs_size; @@ -2629,12 +2629,12 @@ fn writeSymbolTable(self: *Zld) !void { defer locals.deinit(); for (self.locals.items()) |entries| { - log.warn("'{s}': {} entries", .{ entries.key, entries.value.items.len }); + log.debug("'{s}': {} entries", .{ entries.key, entries.value.items.len }); // var symbol: ?macho.nlist_64 = null; for (entries.value.items) |entry| { - log.warn(" | {}", .{entry.inner}); - log.warn(" | {}", .{entry.tt}); - log.warn(" | {s}", .{self.objects.items[entry.object_id].name}); + log.debug(" | {}", .{entry.inner}); + log.debug(" | {}", .{entry.tt}); + log.debug(" | {s}", .{self.objects.items[entry.object_id].name}); // switch (entry.tt) { // .Global => { // symbol = entry.inner; @@ -2679,17 +2679,17 @@ fn writeSymbolTable(self: *Zld) !void { const locals_off = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); const locals_size = nlocals * @sizeOf(macho.nlist_64); - log.warn("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off }); + log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(locals.items), locals_off); const exports_off = locals_off + locals_size; const exports_size = nexports * @sizeOf(macho.nlist_64); - log.warn("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off }); + log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(exports.items), exports_off); const undefs_off = exports_off + exports_size; const undefs_size = nundefs * @sizeOf(macho.nlist_64); - log.warn("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); + log.debug("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); try self.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off); symtab.nsyms += @intCast(u32, nlocals + nexports + nundefs); @@ -2720,7 +2720,7 @@ fn writeDynamicSymbolTable(self: *Zld) !void { const needed_size = dysymtab.nindirectsyms * @sizeOf(u32); seg.inner.filesize += needed_size; - log.warn("writing indirect symbol table from 0x{x} to 0x{x}", .{ + log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{ dysymtab.indirectsymoff, dysymtab.indirectsymoff + needed_size, }); @@ -2759,7 +2759,7 @@ fn writeStringTable(self: *Zld) !void { symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.items.len, @alignOf(u64))); seg.inner.filesize += symtab.strsize; - log.warn("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); + log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); try self.file.?.pwriteAll(self.strtab.items, symtab.stroff); @@ -2806,7 +2806,7 @@ fn writeDataInCode(self: *Zld) !void { dice_cmd.datasize = datasize; seg.inner.filesize += datasize; - log.warn("writing data-in-code from 0x{x} to 0x{x}", .{ fileoff, fileoff + datasize }); + log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ fileoff, fileoff + datasize }); try self.file.?.pwriteAll(buf.items, fileoff); } @@ -2827,7 +2827,7 @@ fn writeCodeSignaturePadding(self: *Zld) !void { seg.inner.filesize += needed_size; seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?); - log.warn("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); + log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); // Pad out the space. We need to do this to calculate valid hashes for everything in the file // except for code signature data. @@ -2853,7 +2853,7 @@ fn writeCodeSignature(self: *Zld) !void { var stream = std.io.fixedBufferStream(buffer); try code_sig.write(stream.writer()); - log.warn("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); + log.debug("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); try self.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); } @@ -2872,7 +2872,7 @@ fn writeLoadCommands(self: *Zld) !void { } const off = @sizeOf(macho.mach_header_64); - log.warn("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds }); + log.debug("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds }); try self.file.?.pwriteAll(buffer, off); } @@ -2910,7 +2910,7 @@ fn writeHeader(self: *Zld) !void { for (self.load_commands.items) |cmd| { header.sizeofcmds += cmd.cmdsize(); } - log.warn("writing Mach-O header {}", .{header}); + log.debug("writing Mach-O header {}", .{header}); try self.file.?.pwriteAll(mem.asBytes(&header), 0); } @@ -2924,7 +2924,7 @@ pub fn makeStaticString(bytes: []const u8) [16]u8 { fn makeString(self: *Zld, bytes: []const u8) !u32 { try self.strtab.ensureCapacity(self.allocator, self.strtab.items.len + bytes.len + 1); const offset = @intCast(u32, self.strtab.items.len); - log.warn("writing new string '{s}' into string table at offset 0x{x}", .{ bytes, offset }); + log.debug("writing new string '{s}' into string table at offset 0x{x}", .{ bytes, offset }); self.strtab.appendSliceAssumeCapacity(bytes); self.strtab.appendAssumeCapacity(0); return offset; From dc34ac2b9e283ac4ca6c07ed9f4e201f860639d0 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 5 Mar 2021 00:37:25 +0100 Subject: [PATCH 36/56] zld: fix incorrect offset calc for DICE --- src/link/MachO/Zld.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 3714427e4e..ebddcebd26 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -2787,13 +2787,12 @@ fn writeDataInCode(self: *Zld) !void { .source_sect_id = object.text_section_index.?, }) orelse continue; - // TODO Currently assume that Dice will always be within the __TEXT,__text section. try buf.ensureCapacity( buf.items.len + object.data_in_code_entries.items.len * @sizeOf(macho.data_in_code_entry), ); for (object.data_in_code_entries.items) |dice| { const new_dice: macho.data_in_code_entry = .{ - .offset = text_sect.offset + target_mapping.offset + dice.offset - source_sect.offset, + .offset = text_sect.offset + target_mapping.offset + dice.offset, .length = dice.length, .kind = dice.kind, }; From d484b3b3cbebddc3e1e8b160152e3f8e3be93b63 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 7 Mar 2021 08:18:35 +0100 Subject: [PATCH 37/56] zld: use aarch64 for opcodes --- src/codegen/aarch64.zig | 5 +- src/link/MachO/Zld.zig | 150 +++++++++++++++++++++-------- src/link/MachO/reloc.zig | 197 --------------------------------------- 3 files changed, 113 insertions(+), 239 deletions(-) delete mode 100644 src/link/MachO/reloc.zig diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index 8abc616e2f..d06abeac07 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -221,7 +221,8 @@ pub const Instruction = union(enum) { offset: u12, opc: u2, op1: u2, - fixed: u4 = 0b111_0, + v: u1, + fixed: u3 = 0b111, size: u2, }, LoadStorePairOfRegisters: packed struct { @@ -505,6 +506,7 @@ pub const Instruction = union(enum) { .offset = offset.toU12(), .opc = opc, .op1 = op1, + .v = 0, .size = 0b10, }, }; @@ -517,6 +519,7 @@ pub const Instruction = union(enum) { .offset = offset.toU12(), .opc = opc, .op1 = op1, + .v = 0, .size = 0b11, }, }; diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index ebddcebd26..f586fa06ed 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -10,6 +10,7 @@ const fs = std.fs; const macho = std.macho; const math = std.math; const log = std.log.scoped(.zld); +const aarch64 = @import("../../codegen/aarch64.zig"); const Allocator = mem.Allocator; const CodeSignature = @import("CodeSignature.zig"); @@ -19,7 +20,6 @@ const Trie = @import("Trie.zig"); usingnamespace @import("commands.zig"); usingnamespace @import("bind.zig"); -usingnamespace @import("reloc.zig"); allocator: *Allocator, @@ -968,27 +968,27 @@ fn writeStubHelperCommon(self: *Zld) !void { data_blk: { const displacement = math.cast(i21, target_addr - this_addr) catch |_| break :data_blk; // adr x17, disp - mem.writeIntLittle(u32, code[0..4], Arm64.adr(17, @bitCast(u21, displacement)).toU32()); + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, displacement).toU32()); // nop - mem.writeIntLittle(u32, code[4..8], Arm64.nop().toU32()); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); break :data_blk_outer; } data_blk: { const new_this_addr = this_addr + @sizeOf(u32); const displacement = math.cast(i21, target_addr - new_this_addr) catch |_| break :data_blk; // nop - mem.writeIntLittle(u32, code[0..4], Arm64.nop().toU32()); + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); // adr x17, disp - mem.writeIntLittle(u32, code[4..8], Arm64.adr(17, @bitCast(u21, displacement)).toU32()); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.adr(.x17, displacement).toU32()); break :data_blk_outer; } // Jump is too big, replace adr with adrp and add. const this_page = @intCast(i32, this_addr >> 12); const target_page = @intCast(i32, target_addr >> 12); - const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); - mem.writeIntLittle(u32, code[0..4], Arm64.adrp(17, pages).toU32()); + const pages = @intCast(i21, target_page - this_page); + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x17, pages).toU32()); const narrowed = @truncate(u12, target_addr); - mem.writeIntLittle(u32, code[4..8], Arm64.add(17, 17, narrowed, 1).toU32()); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.add(.x17, .x17, narrowed, false).toU32()); } // stp x16, x17, [sp, #-16]! code[8] = 0xf0; @@ -1003,9 +1003,11 @@ fn writeStubHelperCommon(self: *Zld) !void { const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :binder_blk; const literal = math.cast(u18, displacement) catch |_| break :binder_blk; // ldr x16, label - mem.writeIntLittle(u32, code[12..16], Arm64.ldr(16, literal, 1).toU32()); + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); // nop - mem.writeIntLittle(u32, code[16..20], Arm64.nop().toU32()); + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.nop().toU32()); break :binder_blk_outer; } binder_blk: { @@ -1015,19 +1017,26 @@ fn writeStubHelperCommon(self: *Zld) !void { log.debug("2: disp=0x{x}, literal=0x{x}", .{ displacement, literal }); // Pad with nop to please division. // nop - mem.writeIntLittle(u32, code[12..16], Arm64.nop().toU32()); + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.nop().toU32()); // ldr x16, label - mem.writeIntLittle(u32, code[16..20], Arm64.ldr(16, literal, 1).toU32()); + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); break :binder_blk_outer; } // Use adrp followed by ldr(immediate). const this_page = @intCast(i32, this_addr >> 12); const target_page = @intCast(i32, target_addr >> 12); - const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); - mem.writeIntLittle(u32, code[12..16], Arm64.adrp(16, pages).toU32()); + const pages = @intCast(i21, target_page - this_page); + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.adrp(.x16, pages).toU32()); const narrowed = @truncate(u12, target_addr); const offset = try math.divExact(u12, narrowed, 8); - mem.writeIntLittle(u32, code[16..20], Arm64.ldrq(16, 16, offset).toU32()); + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{ + .register = .{ + .rn = .x16, + .offset = aarch64.Instruction.LoadStoreOffset.imm(offset), + }, + }).toU32()); } // br x16 code[20] = 0x00; @@ -1099,9 +1108,11 @@ fn writeStub(self: *Zld, index: u32) !void { const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :inner; const literal = math.cast(u18, displacement) catch |_| break :inner; // ldr x16, literal - mem.writeIntLittle(u32, code[0..4], Arm64.ldr(16, literal, 1).toU32()); + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); // nop - mem.writeIntLittle(u32, code[4..8], Arm64.nop().toU32()); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); break :outer; } inner: { @@ -1109,22 +1120,29 @@ fn writeStub(self: *Zld, index: u32) !void { const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :inner; const literal = math.cast(u18, displacement) catch |_| break :inner; // nop - mem.writeIntLittle(u32, code[0..4], Arm64.nop().toU32()); + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); // ldr x16, literal - mem.writeIntLittle(u32, code[4..8], Arm64.ldr(16, literal, 1).toU32()); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); break :outer; } // Use adrp followed by ldr(immediate). const this_page = @intCast(i32, this_addr >> 12); const target_page = @intCast(i32, target_addr >> 12); - const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); - mem.writeIntLittle(u32, code[0..4], Arm64.adrp(16, pages).toU32()); + const pages = @intCast(i21, target_page - this_page); + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x16, pages).toU32()); const narrowed = @truncate(u12, target_addr); const offset = try math.divExact(u12, narrowed, 8); - mem.writeIntLittle(u32, code[4..8], Arm64.ldrq(16, 16, offset).toU32()); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{ + .register = .{ + .rn = .x16, + .offset = aarch64.Instruction.LoadStoreOffset.imm(offset), + }, + }).toU32()); } // br x16 - mem.writeIntLittle(u32, code[8..12], Arm64.br(16).toU32()); + mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.br(.x16).toU32()); }, else => unreachable, } @@ -1160,9 +1178,11 @@ fn writeStubInStubHelper(self: *Zld, index: u32) !void { const displacement = try math.cast(i28, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - 4); const literal = @divExact(stub_size - @sizeOf(u32), 4); // ldr w16, literal - mem.writeIntLittle(u32, code[0..4], Arm64.ldr(16, literal, 0).toU32()); + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.w16, .{ + .literal = literal, + }).toU32()); // b disp - mem.writeIntLittle(u32, code[4..8], Arm64.b(displacement).toU32()); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.b(displacement).toU32()); mem.writeIntLittle(u32, code[8..12], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. }, else => unreachable, @@ -1486,9 +1506,18 @@ fn doRelocs(self: *Zld) !void { .ARM64_RELOC_BRANCH26 => { assert(rel.r_length == 2); const inst = code[off..][0..4]; - const displacement = @intCast(i28, @intCast(i64, target_addr) - @intCast(i64, this_addr)); - var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Branch), inst); - parsed.disp = @truncate(u26, @bitCast(u28, displacement) >> 2); + const displacement = @intCast( + i28, + @intCast(i64, target_addr) - @intCast(i64, this_addr), + ); + var parsed = mem.bytesAsValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.UnconditionalBranchImmediate, + ), + inst, + ); + parsed.imm26 = @truncate(u26, @bitCast(u28, displacement) >> 2); }, .ARM64_RELOC_PAGE21, .ARM64_RELOC_GOT_LOAD_PAGE21, @@ -1501,7 +1530,13 @@ fn doRelocs(self: *Zld) !void { const target_page = @intCast(i32, ta >> 12); const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); log.debug(" | moving by {} pages", .{pages}); - var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Address), inst); + var parsed = mem.bytesAsValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.PCRelativeAddress, + ), + inst, + ); parsed.immhi = @truncate(u19, pages >> 2); parsed.immlo = @truncate(u2, pages); addend = null; @@ -1510,17 +1545,29 @@ fn doRelocs(self: *Zld) !void { .ARM64_RELOC_GOT_LOAD_PAGEOFF12, => { const inst = code[off..][0..4]; - if (Arm64.isArithmetic(inst)) { + if (aarch64IsArithmetic(inst)) { log.debug(" | detected ADD opcode", .{}); // add - var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Add), inst); + var parsed = mem.bytesAsValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.AddSubtractImmediate, + ), + inst, + ); const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); - parsed.offset = narrowed; + parsed.imm12 = narrowed; } else { log.debug(" | detected LDR/STR opcode", .{}); // ldr/str - var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.LoadRegister), inst); + var parsed = mem.bytesAsValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.LoadStoreRegister, + ), + inst, + ); const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); const offset: u12 = blk: { @@ -1541,27 +1588,43 @@ fn doRelocs(self: *Zld) !void { addend = null; }, .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => { - // TODO why is this necessary? const RegInfo = struct { - rt: u5, + rd: u5, rn: u5, size: u1, }; const inst = code[off..][0..4]; const parsed: RegInfo = blk: { - if (Arm64.isArithmetic(inst)) { - const curr = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Add), inst); - break :blk .{ .rt = curr.rt, .rn = curr.rn, .size = curr.size }; + if (aarch64IsArithmetic(inst)) { + const curr = mem.bytesAsValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.AddSubtractImmediate, + ), + inst, + ); + break :blk .{ .rd = curr.rd, .rn = curr.rn, .size = curr.sf }; } else { - const curr = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.LoadRegister), inst); - break :blk .{ .rt = curr.rt, .rn = curr.rn, .size = @truncate(u1, curr.size) }; + const curr = mem.bytesAsValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.LoadStoreRegister, + ), + inst, + ); + break :blk .{ .rd = curr.rt, .rn = curr.rn, .size = @truncate(u1, curr.size) }; } }; const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); log.debug(" | rewriting TLV access to ADD opcode", .{}); // For TLV, we always generate an add instruction. - mem.writeIntLittle(u32, inst, Arm64.add(parsed.rt, parsed.rn, narrowed, parsed.size).toU32()); + mem.writeIntLittle(u32, inst, aarch64.Instruction.add( + @intToEnum(aarch64.Register, parsed.rd), + @intToEnum(aarch64.Register, parsed.rn), + narrowed, + false, + ).toU32()); }, .ARM64_RELOC_SUBTRACTOR => { sub = @intCast(i64, target_addr); @@ -2965,3 +3028,8 @@ fn isExtern(sym: *const macho.nlist_64) callconv(.Inline) bool { fn isWeakDef(sym: *const macho.nlist_64) callconv(.Inline) bool { return (sym.n_desc & macho.N_WEAK_DEF) != 0; } + +fn aarch64IsArithmetic(inst: *const [4]u8) callconv(.Inline) bool { + const group_decode = @truncate(u5, inst[3]); + return ((group_decode >> 2) == 4); +} diff --git a/src/link/MachO/reloc.zig b/src/link/MachO/reloc.zig deleted file mode 100644 index d428b191a6..0000000000 --- a/src/link/MachO/reloc.zig +++ /dev/null @@ -1,197 +0,0 @@ -const std = @import("std"); -const log = std.log.scoped(.reloc); - -pub const Arm64 = union(enum) { - Branch: packed struct { - disp: u26, - fixed: u5 = 0b00101, - link: u1, - }, - BranchRegister: packed struct { - _1: u5 = 0b0000_0, - reg: u5, - _2: u11 = 0b1111_1000_000, - link: u1, - _3: u10 = 0b1101_0110_00, - }, - Address: packed struct { - reg: u5, - immhi: u19, - _1: u5 = 0b10000, - immlo: u2, - page: u1, - }, - LoadRegister: packed struct { - rt: u5, - rn: u5, - offset: u12, - opc: u2, - _2: u2 = 0b01, - v: u1, - _1: u3 = 0b111, - size: u2, - }, - LoadLiteral: packed struct { - reg: u5, - literal: u19, - _1: u6 = 0b011_0_00, - size: u1, - _2: u1 = 0b0, - }, - Add: packed struct { - rt: u5, - rn: u5, - offset: u12, - _1: u9 = 0b0_0_100010_0, - size: u1, - }, - Nop: packed struct { - fixed: u32 = 0b1101010100_0_00_011_0010_0000_000_11111, - }, - - pub fn toU32(self: Arm64) u32 { - const as_u32 = switch (self) { - .Branch => |x| @bitCast(u32, x), - .BranchRegister => |x| @bitCast(u32, x), - .Address => |x| @bitCast(u32, x), - .LoadRegister => |x| @bitCast(u32, x), - .LoadLiteral => |x| @bitCast(u32, x), - .Add => |x| @bitCast(u32, x), - .Nop => |x| @bitCast(u32, x), - }; - return as_u32; - } - - pub fn b(disp: i28) Arm64 { - return Arm64{ - .Branch = .{ - .disp = @truncate(u26, @bitCast(u28, disp) >> 2), - .link = 0, - }, - }; - } - - pub fn bl(disp: i28) Arm64 { - return Arm64{ - .Branch = .{ - .disp = @truncate(u26, @bitCast(u28, disp) >> 2), - .link = 1, - }, - }; - } - - pub fn br(reg: u5) Arm64 { - return Arm64{ - .BranchRegister = .{ - .reg = reg, - .link = 0, - }, - }; - } - - pub fn blr(reg: u5) Arm64 { - return Arm64{ - .BranchRegister = .{ - .reg = reg, - .link = 1, - }, - }; - } - - pub fn adr(reg: u5, disp: u21) Arm64 { - return Arm64{ - .Address = .{ - .reg = reg, - .immhi = @truncate(u19, disp >> 2), - .immlo = @truncate(u2, disp), - .page = 0, - }, - }; - } - - pub fn adrp(reg: u5, disp: u21) Arm64 { - return Arm64{ - .Address = .{ - .reg = reg, - .immhi = @truncate(u19, disp >> 2), - .immlo = @truncate(u2, disp), - .page = 1, - }, - }; - } - - pub fn ldr(reg: u5, literal: u19, size: u1) Arm64 { - return Arm64{ - .LoadLiteral = .{ - .reg = reg, - .literal = literal, - .size = size, - }, - }; - } - - pub fn add(rt: u5, rn: u5, offset: u12, size: u1) Arm64 { - return Arm64{ - .Add = .{ - .rt = rt, - .rn = rn, - .offset = offset, - .size = size, - }, - }; - } - - pub fn ldrq(rt: u5, rn: u5, offset: u12) Arm64 { - return Arm64{ - .LoadRegister = .{ - .rt = rt, - .rn = rn, - .offset = offset, - .opc = 0b01, - .v = 0b0, - .size = 0b11, - }, - }; - } - pub fn ldrh(rt: u5, rn: u5, offset: u12) Arm64 { - return Arm64{ - .LoadRegister = .{ - .rt = rt, - .rn = rn, - .offset = offset, - .opc = 0b01, - .v = 0b0, - .size = 0b01, - }, - }; - } - pub fn ldrb(rt: u5, rn: u5, offset: u12) Arm64 { - return Arm64{ - .LoadRegister = .{ - .rt = rt, - .rn = rn, - .offset = offset, - .opc = 0b01, - .v = 0b0, - .size = 0b00, - }, - }; - } - - pub fn nop() Arm64 { - return Arm64{ - .Nop = .{}, - }; - } - - pub fn isArithmetic(inst: *const [4]u8) bool { - const group_decode = @truncate(u5, inst[3]); - log.debug("{b}", .{group_decode}); - return ((group_decode >> 2) == 4); - // if ((group_decode >> 2) == 4) { - // log.debug("Arithmetic imm", .{}); - // } else if (((group_decode & 0b01010) >> 3) == 1) { - // log.debug("Load/store", .{}); - // } - } -}; From 62f43fbc068ae63b6492af20a7cfd38b7426cb92 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 7 Mar 2021 12:06:31 +0100 Subject: [PATCH 38/56] zld: clean up use of commands.zig module --- src/link/MachO/Zld.zig | 72 +++++++------------------------------ src/link/MachO/commands.zig | 5 --- 2 files changed, 13 insertions(+), 64 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index f586fa06ed..e802a8fbdc 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -377,7 +377,7 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { if (self.text_const_section_index != null) continue; self.text_const_section_index = @intCast(u16, text_seg.sections.items.len); - try text_seg.append(self.allocator, .{ + try text_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__const"), .segname = makeStaticString("__TEXT"), .addr = 0, @@ -396,7 +396,7 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { if (self.data_const_section_index != null) continue; self.data_const_section_index = @intCast(u16, data_seg.sections.items.len); - try data_seg.append(self.allocator, .{ + try data_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__const"), .segname = makeStaticString("__DATA"), .addr = 0, @@ -417,7 +417,7 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { if (self.cstring_section_index != null) continue; self.cstring_section_index = @intCast(u16, text_seg.sections.items.len); - try text_seg.append(self.allocator, .{ + try text_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__cstring"), .segname = makeStaticString("__TEXT"), .addr = 0, @@ -437,7 +437,7 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { if (self.bss_section_index != null) continue; self.bss_section_index = @intCast(u16, data_seg.sections.items.len); - try data_seg.append(self.allocator, .{ + try data_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__bss"), .segname = makeStaticString("__DATA"), .addr = 0, @@ -457,7 +457,7 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { if (self.tlv_section_index != null) continue; self.tlv_section_index = @intCast(u16, data_seg.sections.items.len); - try data_seg.append(self.allocator, .{ + try data_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__thread_vars"), .segname = makeStaticString("__DATA"), .addr = 0, @@ -477,7 +477,7 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { if (self.tlv_data_section_index != null) continue; self.tlv_data_section_index = @intCast(u16, data_seg.sections.items.len); - try data_seg.append(self.allocator, .{ + try data_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__thread_data"), .segname = makeStaticString("__DATA"), .addr = 0, @@ -497,7 +497,7 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { if (self.tlv_bss_section_index != null) continue; self.tlv_bss_section_index = @intCast(u16, data_seg.sections.items.len); - try data_seg.append(self.allocator, .{ + try data_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__thread_bss"), .segname = makeStaticString("__DATA"), .addr = 0, @@ -1842,7 +1842,7 @@ fn populateMetadata(self: *Zld) !void { .aarch64 => 2, else => unreachable, // unhandled architecture type }; - try text_seg.append(self.allocator, .{ + try text_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__text"), .segname = makeStaticString("__TEXT"), .addr = 0, @@ -1871,7 +1871,7 @@ fn populateMetadata(self: *Zld) !void { .aarch64 => 3 * @sizeOf(u32), else => unreachable, // unhandled architecture type }; - try text_seg.append(self.allocator, .{ + try text_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__stubs"), .segname = makeStaticString("__TEXT"), .addr = 0, @@ -1900,7 +1900,7 @@ fn populateMetadata(self: *Zld) !void { .aarch64 => 6 * @sizeOf(u32), else => unreachable, }; - try text_seg.append(self.allocator, .{ + try text_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__stub_helper"), .segname = makeStaticString("__TEXT"), .addr = 0, @@ -1938,7 +1938,7 @@ fn populateMetadata(self: *Zld) !void { if (self.got_section_index == null) { const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; self.got_section_index = @intCast(u16, data_seg.sections.items.len); - try data_seg.append(self.allocator, .{ + try data_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__got"), .segname = makeStaticString("__DATA"), .addr = 0, @@ -1957,7 +1957,7 @@ fn populateMetadata(self: *Zld) !void { if (self.la_symbol_ptr_section_index == null) { const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; self.la_symbol_ptr_section_index = @intCast(u16, data_seg.sections.items.len); - try data_seg.append(self.allocator, .{ + try data_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__la_symbol_ptr"), .segname = makeStaticString("__DATA"), .addr = 0, @@ -1976,7 +1976,7 @@ fn populateMetadata(self: *Zld) !void { if (self.data_section_index == null) { const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; self.data_section_index = @intCast(u16, data_seg.sections.items.len); - try data_seg.append(self.allocator, .{ + try data_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__data"), .segname = makeStaticString("__DATA"), .addr = 0, @@ -2290,41 +2290,8 @@ fn writeRebaseInfoTable(self: *Zld) !void { try pointers.ensureCapacity(pointers.items.len + self.local_rebases.items.len); pointers.appendSliceAssumeCapacity(self.local_rebases.items); - // const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; - // const base_id = text_seg.sections.items.len; - // for (self.locals.items()) |entry| { - // for (entry.value.items) |symbol| { - // const local = symbol.inner; - - // if (self.data_const_section_index) |index| { - // if (local.n_sect == base_id + index) { - // const offset = local.n_value - data_seg.inner.vmaddr; - // try pointers.append(.{ - // .offset = offset, - // .segment_id = @intCast(u16, self.data_segment_cmd_index.?), - // }); - // } - // } - // if (self.data_section_index) |index| { - // if (local.n_sect == base_id + index) { - // const offset = local.n_value - data_seg.inner.vmaddr; - // try pointers.append(.{ - // .offset = offset, - // .segment_id = @intCast(u16, self.data_segment_cmd_index.?), - // }); - // } - // } - // } - // } - std.sort.sort(Pointer, pointers.items, {}, pointerCmp); - // const nlocals = self.local_rebases.items.len; - // var i = nlocals; - // while (i > 0) : (i -= 1) { - // pointers.appendAssumeCapacity(self.local_rebases.items[i - 1]); - // } - const size = try rebaseInfoSize(pointers.items); var buffer = try self.allocator.alloc(u8, @intCast(usize, size)); defer self.allocator.free(buffer); @@ -2698,21 +2665,8 @@ fn writeSymbolTable(self: *Zld) !void { log.debug(" | {}", .{entry.inner}); log.debug(" | {}", .{entry.tt}); log.debug(" | {s}", .{self.objects.items[entry.object_id].name}); - // switch (entry.tt) { - // .Global => { - // symbol = entry.inner; - // break; - // }, - // .WeakGlobal => { - // symbol = entry.inner; - // }, - // .Local => {}, - // } try locals.append(entry.inner); } - // if (symbol) |s| { - // try locals.append(s); - // } } const nlocals = locals.items.len; diff --git a/src/link/MachO/commands.zig b/src/link/MachO/commands.zig index 175f1a8d9c..67b808d856 100644 --- a/src/link/MachO/commands.zig +++ b/src/link/MachO/commands.zig @@ -166,11 +166,6 @@ pub const SegmentCommand = struct { return .{ .inner = inner }; } - // TODO remove me, I'm just a temp! - pub fn append(self: *SegmentCommand, alloc: *Allocator, section: macho.section_64) !void { - return self.addSection(alloc, section); - } - pub fn addSection(self: *SegmentCommand, alloc: *Allocator, section: macho.section_64) !void { try self.sections.append(alloc, section); self.inner.cmdsize += @sizeOf(macho.section_64); From 349f878ecf0b6ad5eee6b1cdfdba90014cbcb619 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 8 Mar 2021 17:21:25 +0100 Subject: [PATCH 39/56] zld: mimick Apple and add __DATA_CONST seg --- src/link/MachO/Zld.zig | 351 ++++++++++++++++++++++------------------- 1 file changed, 187 insertions(+), 164 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index e802a8fbdc..aa7ef56278 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -37,6 +37,7 @@ load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, pagezero_segment_cmd_index: ?u16 = null, text_segment_cmd_index: ?u16 = null, +data_const_segment_cmd_index: ?u16 = null, data_segment_cmd_index: ?u16 = null, linkedit_segment_cmd_index: ?u16 = null, dyld_info_cmd_index: ?u16 = null, @@ -277,6 +278,7 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8) !void { try self.sortSections(); try self.resolveImports(); try self.allocateTextSegment(); + try self.allocateDataConstSegment(); try self.allocateDataSegment(); self.allocateLinkeditSegment(); try self.writeStubHelperCommon(); @@ -362,6 +364,7 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { const object = self.objects.items[object_id]; const object_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; // Create missing metadata @@ -395,10 +398,10 @@ fn updateMetadata(self: *Zld, object_id: u16) !void { if (!mem.eql(u8, sectname, "__const")) continue; if (self.data_const_section_index != null) continue; - self.data_const_section_index = @intCast(u16, data_seg.sections.items.len); - try data_seg.addSection(self.allocator, .{ + self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len); + try data_const_seg.addSection(self.allocator, .{ .sectname = makeStaticString("__const"), - .segname = makeStaticString("__DATA"), + .segname = makeStaticString("__DATA_CONST"), .addr = 0, .size = 0, .offset = 0, @@ -611,7 +614,7 @@ fn getMatchingSection(self: *Zld, section: macho.section_64) ?MatchingSection { }; } else if (mem.eql(u8, sectname, "__const")) { break :blk .{ - .seg = self.data_segment_cmd_index.?, + .seg = self.data_const_segment_cmd_index.?, .sect = self.data_const_section_index.?, }; } @@ -627,100 +630,85 @@ fn getMatchingSection(self: *Zld, section: macho.section_64) ?MatchingSection { } fn sortSections(self: *Zld) !void { - const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - var text_sections = text_seg.sections.toOwnedSlice(self.allocator); - defer self.allocator.free(text_sections); - try text_seg.sections.ensureCapacity(self.allocator, text_sections.len); - - const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; - var data_sections = data_seg.sections.toOwnedSlice(self.allocator); - defer self.allocator.free(data_sections); - try data_seg.sections.ensureCapacity(self.allocator, data_sections.len); - var text_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator); defer text_index_mapping.deinit(); - + var data_const_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator); + defer data_const_index_mapping.deinit(); var data_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator); defer data_index_mapping.deinit(); - if (self.text_section_index) |index| { - const new_index = @intCast(u16, text_seg.sections.items.len); - self.text_section_index = new_index; - text_seg.sections.appendAssumeCapacity(text_sections[index]); - try text_index_mapping.putNoClobber(index, new_index); - } - if (self.stubs_section_index) |index| { - const new_index = @intCast(u16, text_seg.sections.items.len); - self.stubs_section_index = new_index; - text_seg.sections.appendAssumeCapacity(text_sections[index]); - try text_index_mapping.putNoClobber(index, new_index); - } - if (self.stub_helper_section_index) |index| { - const new_index = @intCast(u16, text_seg.sections.items.len); - self.stub_helper_section_index = new_index; - text_seg.sections.appendAssumeCapacity(text_sections[index]); - try text_index_mapping.putNoClobber(index, new_index); - } - if (self.text_const_section_index) |index| { - const new_index = @intCast(u16, text_seg.sections.items.len); - self.text_const_section_index = new_index; - text_seg.sections.appendAssumeCapacity(text_sections[index]); - try text_index_mapping.putNoClobber(index, new_index); - } - if (self.cstring_section_index) |index| { - const new_index = @intCast(u16, text_seg.sections.items.len); - self.cstring_section_index = new_index; - text_seg.sections.appendAssumeCapacity(text_sections[index]); - try text_index_mapping.putNoClobber(index, new_index); + { + // __TEXT segment + const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + var sections = seg.sections.toOwnedSlice(self.allocator); + defer self.allocator.free(sections); + try seg.sections.ensureCapacity(self.allocator, sections.len); + + const indices = &[_]*?u16{ + &self.text_section_index, + &self.stubs_section_index, + &self.stub_helper_section_index, + &self.text_const_section_index, + &self.cstring_section_index, + }; + for (indices) |maybe_index| { + const new_index: u16 = if (maybe_index.*) |index| blk: { + const idx = @intCast(u16, seg.sections.items.len); + seg.sections.appendAssumeCapacity(sections[index]); + try text_index_mapping.putNoClobber(index, idx); + break :blk idx; + } else continue; + maybe_index.* = new_index; + } } - if (self.got_section_index) |index| { - const new_index = @intCast(u16, data_seg.sections.items.len); - self.got_section_index = new_index; - data_seg.sections.appendAssumeCapacity(data_sections[index]); - try data_index_mapping.putNoClobber(index, new_index); + { + // __DATA_CONST segment + const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + var sections = seg.sections.toOwnedSlice(self.allocator); + defer self.allocator.free(sections); + try seg.sections.ensureCapacity(self.allocator, sections.len); + + const indices = &[_]*?u16{ + &self.got_section_index, + &self.data_const_section_index, + }; + for (indices) |maybe_index| { + const new_index: u16 = if (maybe_index.*) |index| blk: { + const idx = @intCast(u16, seg.sections.items.len); + seg.sections.appendAssumeCapacity(sections[index]); + try data_const_index_mapping.putNoClobber(index, idx); + break :blk idx; + } else continue; + maybe_index.* = new_index; + } } - if (self.data_const_section_index) |index| { - const new_index = @intCast(u16, data_seg.sections.items.len); - self.data_const_section_index = new_index; - data_seg.sections.appendAssumeCapacity(data_sections[index]); - try data_index_mapping.putNoClobber(index, new_index); - } - if (self.la_symbol_ptr_section_index) |index| { - const new_index = @intCast(u16, data_seg.sections.items.len); - self.la_symbol_ptr_section_index = new_index; - data_seg.sections.appendAssumeCapacity(data_sections[index]); - try data_index_mapping.putNoClobber(index, new_index); - } - if (self.tlv_section_index) |index| { - const new_index = @intCast(u16, data_seg.sections.items.len); - self.tlv_section_index = new_index; - data_seg.sections.appendAssumeCapacity(data_sections[index]); - try data_index_mapping.putNoClobber(index, new_index); - } - if (self.data_section_index) |index| { - const new_index = @intCast(u16, data_seg.sections.items.len); - self.data_section_index = new_index; - data_seg.sections.appendAssumeCapacity(data_sections[index]); - try data_index_mapping.putNoClobber(index, new_index); - } - if (self.tlv_data_section_index) |index| { - const new_index = @intCast(u16, data_seg.sections.items.len); - self.tlv_data_section_index = new_index; - data_seg.sections.appendAssumeCapacity(data_sections[index]); - try data_index_mapping.putNoClobber(index, new_index); - } - if (self.tlv_bss_section_index) |index| { - const new_index = @intCast(u16, data_seg.sections.items.len); - self.tlv_bss_section_index = new_index; - data_seg.sections.appendAssumeCapacity(data_sections[index]); - try data_index_mapping.putNoClobber(index, new_index); - } - if (self.bss_section_index) |index| { - const new_index = @intCast(u16, data_seg.sections.items.len); - self.bss_section_index = new_index; - data_seg.sections.appendAssumeCapacity(data_sections[index]); - try data_index_mapping.putNoClobber(index, new_index); + + { + // __DATA segment + const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + var sections = seg.sections.toOwnedSlice(self.allocator); + defer self.allocator.free(sections); + try seg.sections.ensureCapacity(self.allocator, sections.len); + + // __DATA segment + const indices = &[_]*?u16{ + &self.la_symbol_ptr_section_index, + &self.tlv_section_index, + &self.data_section_index, + &self.tlv_data_section_index, + &self.tlv_bss_section_index, + &self.bss_section_index, + }; + for (indices) |maybe_index| { + const new_index: u16 = if (maybe_index.*) |index| blk: { + const idx = @intCast(u16, seg.sections.items.len); + seg.sections.appendAssumeCapacity(sections[index]); + try data_index_mapping.putNoClobber(index, idx); + break :blk idx; + } else continue; + maybe_index.* = new_index; + } } var it = self.mappings.iterator(); @@ -729,6 +717,9 @@ fn sortSections(self: *Zld) !void { if (self.text_segment_cmd_index.? == mapping.target_seg_id) { const new_index = text_index_mapping.get(mapping.target_sect_id) orelse unreachable; mapping.target_sect_id = new_index; + } else if (self.data_const_segment_cmd_index.? == mapping.target_seg_id) { + const new_index = data_const_index_mapping.get(mapping.target_sect_id) orelse unreachable; + mapping.target_sect_id = new_index; } else if (self.data_segment_cmd_index.? == mapping.target_seg_id) { const new_index = data_index_mapping.get(mapping.target_sect_id) orelse unreachable; mapping.target_sect_id = new_index; @@ -874,10 +865,9 @@ fn allocateTextSegment(self: *Zld) !void { } } -fn allocateDataSegment(self: *Zld) !void { - const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; +fn allocateDataConstSegment(self: *Zld) !void { + const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; const nonlazy = @intCast(u32, self.nonlazy_imports.items().len); - const lazy = @intCast(u32, self.lazy_imports.items().len); const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; seg.inner.fileoff = text_seg.inner.fileoff + text_seg.inner.filesize; @@ -887,6 +877,17 @@ fn allocateDataSegment(self: *Zld) !void { const got = &seg.sections.items[self.got_section_index.?]; got.size += nonlazy * @sizeOf(u64); + try self.allocateSegment(self.data_const_segment_cmd_index.?, 0); +} + +fn allocateDataSegment(self: *Zld) !void { + const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const lazy = @intCast(u32, self.lazy_imports.items().len); + + const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + seg.inner.fileoff = data_const_seg.inner.fileoff + data_const_seg.inner.filesize; + seg.inner.vmaddr = data_const_seg.inner.vmaddr + data_const_seg.inner.vmsize; + // Set la_symbol_ptr and data size const la_symbol_ptr = &seg.sections.items[self.la_symbol_ptr_section_index.?]; const data = &seg.sections.items[self.data_section_index.?]; @@ -926,10 +927,11 @@ fn allocateSegment(self: *Zld, index: u16, offset: u64) !void { fn writeStubHelperCommon(self: *Zld) !void { const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?]; + const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = &data_const_segment.sections.items[self.got_section_index.?]; const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; const data = &data_segment.sections.items[self.data_section_index.?]; const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?]; - const got = &data_segment.sections.items[self.got_section_index.?]; self.stub_helper_stubs_start_off = blk: { switch (self.arch.?) { @@ -1247,15 +1249,16 @@ fn resolveSymbols(self: *Zld) !void { log.debug("resolving '{s}':{} as {s} symbol at 0x{x}", .{ sym_name, sym, tt, n_value }); - // TODO this assumes only two symbol-filled segments. Also, there might be a more - // generic way of doing this. - const n_sect = blk: { - if (self.text_segment_cmd_index.? == target_mapping.target_seg_id) { - break :blk target_mapping.target_sect_id + 1; + // TODO there might be a more generic way of doing this. + var n_sect: u16 = 0; + for (self.load_commands.items) |cmd, cmd_id| { + if (cmd != .Segment) break; + if (cmd_id == target_mapping.target_seg_id) { + n_sect += target_mapping.target_sect_id + 1; + break; } - const prev_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; - break :blk @intCast(u16, prev_seg.sections.items.len + target_mapping.target_sect_id + 1); - }; + n_sect += @intCast(u16, cmd.Segment.sections.items.len); + } const n_strx = try self.makeString(sym_name); try locs.entry.value.append(self.allocator, .{ @@ -1460,23 +1463,24 @@ fn doRelocs(self: *Zld) !void { mem.writeIntLittle(u64, inst, @bitCast(u64, result)); sub = null; - // TODO should handle this better. outer: { var hit: bool = false; - if (self.data_section_index) |index| inner: { - if (index != target_mapping.target_sect_id) break :inner; - hit = true; + if (target_mapping.target_seg_id == self.data_segment_cmd_index.?) { + if (self.data_section_index) |index| { + if (index == target_mapping.target_sect_id) hit = true; + } } - if (self.data_const_section_index) |index| inner: { - if (index != target_mapping.target_sect_id) break :inner; - hit = true; + if (target_mapping.target_seg_id == self.data_const_segment_cmd_index.?) { + if (self.data_const_section_index) |index| { + if (index == target_mapping.target_sect_id) hit = true; + } } + if (!hit) break :outer; - const this_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; - const this_offset = target_sect_addr + off - this_seg.inner.vmaddr; + try self.local_rebases.append(self.allocator, .{ - .offset = this_offset, - .segment_id = @intCast(u16, self.data_segment_cmd_index.?), + .offset = this_addr - target_seg.inner.vmaddr, + .segment_id = target_mapping.target_seg_id, }); } }, @@ -1642,23 +1646,24 @@ fn doRelocs(self: *Zld) !void { mem.writeIntLittle(u64, inst, @bitCast(u64, result)); sub = null; - // TODO should handle this better. outer: { var hit: bool = false; - if (self.data_section_index) |index| inner: { - if (index != target_mapping.target_sect_id) break :inner; - hit = true; + if (target_mapping.target_seg_id == self.data_segment_cmd_index.?) { + if (self.data_section_index) |index| { + if (index == target_mapping.target_sect_id) hit = true; + } } - if (self.data_const_section_index) |index| inner: { - if (index != target_mapping.target_sect_id) break :inner; - hit = true; + if (target_mapping.target_seg_id == self.data_const_segment_cmd_index.?) { + if (self.data_const_section_index) |index| { + if (index == target_mapping.target_sect_id) hit = true; + } } + if (!hit) break :outer; - const this_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; - const this_offset = target_sect_addr + off - this_seg.inner.vmaddr; + try self.local_rebases.append(self.allocator, .{ - .offset = this_offset, - .segment_id = @intCast(u16, self.data_segment_cmd_index.?), + .offset = this_addr - target_seg.inner.vmaddr, + .segment_id = target_mapping.target_seg_id, }); } }, @@ -1763,7 +1768,7 @@ fn relocTargetAddr(self: *Zld, object_id: u16, rel: macho.relocation_info) !u64 const stubs = segment.sections.items[self.stubs_section_index.?]; break :blk stubs.addr + ext.index * stubs.reserved2; } else if (self.nonlazy_imports.get(sym_name)) |ext| { - const segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const segment = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; const got = segment.sections.items[self.got_section_index.?]; break :blk got.addr + ext.index * @sizeOf(u64); } else if (self.threadlocal_imports.get(sym_name)) |ext| { @@ -1916,6 +1921,44 @@ fn populateMetadata(self: *Zld) !void { }); } + if (self.data_const_segment_cmd_index == null) { + self.data_const_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__DATA_CONST"), + .vmaddr = 0, + .vmsize = 0, + .fileoff = 0, + .filesize = 0, + .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE, + .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE, + .nsects = 0, + .flags = 0, + }), + }); + } + + if (self.got_section_index == null) { + const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + self.got_section_index = @intCast(u16, data_const_seg.sections.items.len); + try data_const_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__got"), + .segname = makeStaticString("__DATA_CONST"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 3, // 2^3 = @sizeOf(u64) + .reloff = 0, + .nreloc = 0, + .flags = macho.S_NON_LAZY_SYMBOL_POINTERS, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + } + if (self.data_segment_cmd_index == null) { self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len); try self.load_commands.append(self.allocator, .{ @@ -1935,25 +1978,6 @@ fn populateMetadata(self: *Zld) !void { }); } - if (self.got_section_index == null) { - const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; - self.got_section_index = @intCast(u16, data_seg.sections.items.len); - try data_seg.addSection(self.allocator, .{ - .sectname = makeStaticString("__got"), - .segname = makeStaticString("__DATA"), - .addr = 0, - .size = 0, - .offset = 0, - .@"align" = 3, // 2^3 = @sizeOf(u64) - .reloff = 0, - .nreloc = 0, - .flags = macho.S_NON_LAZY_SYMBOL_POINTERS, - .reserved1 = 0, - .reserved2 = 0, - .reserved3 = 0, - }); - } - if (self.la_symbol_ptr_section_index == null) { const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; self.la_symbol_ptr_section_index = @intCast(u16, data_seg.sections.items.len); @@ -2269,15 +2293,17 @@ fn setEntryPoint(self: *Zld) !void { } fn writeRebaseInfoTable(self: *Zld) !void { - const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; - var pointers = std.ArrayList(Pointer).init(self.allocator); defer pointers.deinit(); - try pointers.ensureCapacity(self.lazy_imports.items().len); + + try pointers.ensureCapacity(pointers.items.len + self.local_rebases.items.len); + pointers.appendSliceAssumeCapacity(self.local_rebases.items); if (self.la_symbol_ptr_section_index) |idx| { - const sect = data_seg.sections.items[idx]; - const base_offset = sect.addr - data_seg.inner.vmaddr; + try pointers.ensureCapacity(pointers.items.len + self.lazy_imports.items().len); + const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; const segment_id = @intCast(u16, self.data_segment_cmd_index.?); for (self.lazy_imports.items()) |entry| { pointers.appendAssumeCapacity(.{ @@ -2287,9 +2313,6 @@ fn writeRebaseInfoTable(self: *Zld) !void { } } - try pointers.ensureCapacity(pointers.items.len + self.local_rebases.items.len); - pointers.appendSliceAssumeCapacity(self.local_rebases.items); - std.sort.sort(Pointer, pointers.items, {}, pointerCmp); const size = try rebaseInfoSize(pointers.items); @@ -2319,16 +2342,15 @@ fn pointerCmp(context: void, a: Pointer, b: Pointer) bool { } fn writeBindInfoTable(self: *Zld) !void { - const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; - var pointers = std.ArrayList(Pointer).init(self.allocator); defer pointers.deinit(); try pointers.ensureCapacity(self.nonlazy_imports.items().len + self.threadlocal_imports.items().len); if (self.got_section_index) |idx| { - const sect = data_seg.sections.items[idx]; - const base_offset = sect.addr - data_seg.inner.vmaddr; - const segment_id = @intCast(u16, self.data_segment_cmd_index.?); + const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); for (self.nonlazy_imports.items()) |entry| { pointers.appendAssumeCapacity(.{ .offset = base_offset + entry.value.index * @sizeOf(u64), @@ -2340,8 +2362,9 @@ fn writeBindInfoTable(self: *Zld) !void { } if (self.tlv_section_index) |idx| { - const sect = data_seg.sections.items[idx]; - const base_offset = sect.addr - data_seg.inner.vmaddr; + const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; const segment_id = @intCast(u16, self.data_segment_cmd_index.?); for (self.threadlocal_imports.items()) |entry| { pointers.appendAssumeCapacity(.{ @@ -2372,15 +2395,14 @@ fn writeBindInfoTable(self: *Zld) !void { } fn writeLazyBindInfoTable(self: *Zld) !void { - const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; - var pointers = std.ArrayList(Pointer).init(self.allocator); defer pointers.deinit(); try pointers.ensureCapacity(self.lazy_imports.items().len); if (self.la_symbol_ptr_section_index) |idx| { - const sect = data_seg.sections.items[idx]; - const base_offset = sect.addr - data_seg.inner.vmaddr; + const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; const segment_id = @intCast(u16, self.data_segment_cmd_index.?); for (self.lazy_imports.items()) |entry| { pointers.appendAssumeCapacity(.{ @@ -2725,8 +2747,9 @@ fn writeDynamicSymbolTable(self: *Zld) !void { const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; const stubs = &text_segment.sections.items[self.stubs_section_index.?]; + const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = &data_const_segment.sections.items[self.got_section_index.?]; const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; - const got = &data_segment.sections.items[self.got_section_index.?]; const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; From de209afbba0984c66fc5c9d379192edec87f0681 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 9 Mar 2021 18:42:00 +0100 Subject: [PATCH 40/56] zld: fix TLV initializers --- src/link/MachO/Zld.zig | 115 +++++++++++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 27 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index aa7ef56278..67251080cf 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -74,7 +74,8 @@ locals: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(Symbol)) = .{}, exports: std.StringArrayHashMapUnmanaged(macho.nlist_64) = .{}, nonlazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, lazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, -threadlocal_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, +tlv_bootstrap: ?Import = null, +threadlocal_offsets: std.ArrayListUnmanaged(u64) = .{}, local_rebases: std.ArrayListUnmanaged(Pointer) = .{}, strtab: std.ArrayListUnmanaged(u8) = .{}, @@ -202,16 +203,13 @@ pub fn init(allocator: *Allocator) Zld { } pub fn deinit(self: *Zld) void { + self.threadlocal_offsets.deinit(self.allocator); self.strtab.deinit(self.allocator); self.local_rebases.deinit(self.allocator); for (self.lazy_imports.items()) |*entry| { self.allocator.free(entry.key); } self.lazy_imports.deinit(self.allocator); - for (self.threadlocal_imports.items()) |*entry| { - self.allocator.free(entry.key); - } - self.threadlocal_imports.deinit(self.allocator); for (self.nonlazy_imports.items()) |*entry| { self.allocator.free(entry.key); } @@ -780,12 +778,11 @@ fn resolveImports(self: *Zld) !void { }); } else if (mem.eql(u8, sym_name, "__tlv_bootstrap")) { log.debug("writing threadlocal symbol '{s}'", .{sym_name}); - const index = @intCast(u32, self.threadlocal_imports.items().len); - try self.threadlocal_imports.putNoClobber(self.allocator, key, .{ + self.tlv_bootstrap = .{ .symbol = new_sym, .dylib_ordinal = dylib_ordinal, - .index = index, - }); + .index = 0, + }; } else { log.debug("writing lazy symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.lazy_imports.items().len); @@ -1463,7 +1460,7 @@ fn doRelocs(self: *Zld) !void { mem.writeIntLittle(u64, inst, @bitCast(u64, result)); sub = null; - outer: { + rebases: { var hit: bool = false; if (target_mapping.target_seg_id == self.data_segment_cmd_index.?) { if (self.data_section_index) |index| { @@ -1476,13 +1473,33 @@ fn doRelocs(self: *Zld) !void { } } - if (!hit) break :outer; + if (!hit) break :rebases; try self.local_rebases.append(self.allocator, .{ .offset = this_addr - target_seg.inner.vmaddr, .segment_id = target_mapping.target_seg_id, }); } + // TLV is handled via a separate offset mechanism. + // Calculate the offset to the initializer. + if (target_sect.flags == macho.S_THREAD_LOCAL_VARIABLES) tlv: { + assert(rel.r_extern == 1); + const sym = object.symtab.items[rel.r_symbolnum]; + if (isImport(&sym)) break :tlv; + + const base_addr = blk: { + if (self.tlv_data_section_index) |index| { + const tlv_data = target_seg.sections.items[index]; + break :blk tlv_data.addr; + } else { + const tlv_bss = target_seg.sections.items[self.tlv_bss_section_index.?]; + break :blk tlv_bss.addr; + } + }; + // Since we require TLV data to always preceed TLV bss section, we calculate + // offsets wrt to the former if it is defined; otherwise, wrt to the latter. + try self.threadlocal_offsets.append(self.allocator, target_addr - base_addr); + } }, 2 => { const inst = code[off..][0..4]; @@ -1646,7 +1663,7 @@ fn doRelocs(self: *Zld) !void { mem.writeIntLittle(u64, inst, @bitCast(u64, result)); sub = null; - outer: { + rebases: { var hit: bool = false; if (target_mapping.target_seg_id == self.data_segment_cmd_index.?) { if (self.data_section_index) |index| { @@ -1659,13 +1676,33 @@ fn doRelocs(self: *Zld) !void { } } - if (!hit) break :outer; + if (!hit) break :rebases; try self.local_rebases.append(self.allocator, .{ .offset = this_addr - target_seg.inner.vmaddr, .segment_id = target_mapping.target_seg_id, }); } + // TLV is handled via a separate offset mechanism. + // Calculate the offset to the initializer. + if (target_sect.flags == macho.S_THREAD_LOCAL_VARIABLES) tlv: { + assert(rel.r_extern == 1); + const sym = object.symtab.items[rel.r_symbolnum]; + if (isImport(&sym)) break :tlv; + + const base_addr = blk: { + if (self.tlv_data_section_index) |index| { + const tlv_data = target_seg.sections.items[index]; + break :blk tlv_data.addr; + } else { + const tlv_bss = target_seg.sections.items[self.tlv_bss_section_index.?]; + break :blk tlv_bss.addr; + } + }; + // Since we require TLV data to always preceed TLV bss section, we calculate + // offsets wrt to the former if it is defined; otherwise, wrt to the latter. + try self.threadlocal_offsets.append(self.allocator, target_addr - base_addr); + } }, 2 => { const inst = code[off..][0..4]; @@ -1771,10 +1808,10 @@ fn relocTargetAddr(self: *Zld, object_id: u16, rel: macho.relocation_info) !u64 const segment = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; const got = segment.sections.items[self.got_section_index.?]; break :blk got.addr + ext.index * @sizeOf(u64); - } else if (self.threadlocal_imports.get(sym_name)) |ext| { + } else if (mem.eql(u8, sym_name, "__tlv_bootstrap")) { const segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; const tlv = segment.sections.items[self.tlv_section_index.?]; - break :blk tlv.addr + ext.index * @sizeOf(u64); + break :blk tlv.addr + self.tlv_bootstrap.?.index * @sizeOf(u64); } else { log.err("failed to resolve symbol '{s}' as a relocation target", .{sym_name}); return error.FailedToResolveRelocationTarget; @@ -2207,11 +2244,33 @@ fn flush(self: *Zld) !void { const sect = &seg.sections.items[index]; sect.offset = 0; } + if (self.tlv_bss_section_index) |index| { const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; const sect = &seg.sections.items[index]; sect.offset = 0; } + + if (self.tlv_section_index) |index| { + const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = &seg.sections.items[index]; + + var buffer = try self.allocator.alloc(u8, sect.size); + defer self.allocator.free(buffer); + _ = try self.file.?.preadAll(buffer, sect.offset); + + var stream = std.io.fixedBufferStream(buffer); + var writer = stream.writer(); + + const seek_amt = 2 * @sizeOf(u64); + while (self.threadlocal_offsets.popOrNull()) |offset| { + try writer.context.seekBy(seek_amt); + try writer.writeIntLittle(u64, offset); + } + + try self.file.?.pwriteAll(buffer, sect.offset); + } + try self.setEntryPoint(); try self.writeRebaseInfoTable(); try self.writeBindInfoTable(); @@ -2344,9 +2403,9 @@ fn pointerCmp(context: void, a: Pointer, b: Pointer) bool { fn writeBindInfoTable(self: *Zld) !void { var pointers = std.ArrayList(Pointer).init(self.allocator); defer pointers.deinit(); - try pointers.ensureCapacity(self.nonlazy_imports.items().len + self.threadlocal_imports.items().len); if (self.got_section_index) |idx| { + try pointers.ensureCapacity(pointers.items.len + self.nonlazy_imports.items().len); const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; const sect = seg.sections.items[idx]; const base_offset = sect.addr - seg.inner.vmaddr; @@ -2366,14 +2425,12 @@ fn writeBindInfoTable(self: *Zld) !void { const sect = seg.sections.items[idx]; const base_offset = sect.addr - seg.inner.vmaddr; const segment_id = @intCast(u16, self.data_segment_cmd_index.?); - for (self.threadlocal_imports.items()) |entry| { - pointers.appendAssumeCapacity(.{ - .offset = base_offset + entry.value.index * @sizeOf(u64), - .segment_id = segment_id, - .dylib_ordinal = entry.value.dylib_ordinal, - .name = entry.key, - }); - } + try pointers.append(.{ + .offset = base_offset + self.tlv_bootstrap.?.index * @sizeOf(u64), + .segment_id = segment_id, + .dylib_ordinal = self.tlv_bootstrap.?.dylib_ordinal, + .name = "__tlv_bootstrap", + }); } const size = try bindInfoSize(pointers.items); @@ -2701,7 +2758,11 @@ fn writeSymbolTable(self: *Zld) !void { exports.appendAssumeCapacity(entry.value); } - const nundefs = self.lazy_imports.items().len + self.nonlazy_imports.items().len + self.threadlocal_imports.items().len; + const has_tlv: bool = self.tlv_bootstrap != null; + + var nundefs = self.lazy_imports.items().len + self.nonlazy_imports.items().len; + if (has_tlv) nundefs += 1; + var undefs = std.ArrayList(macho.nlist_64).init(self.allocator); defer undefs.deinit(); @@ -2712,8 +2773,8 @@ fn writeSymbolTable(self: *Zld) !void { for (self.nonlazy_imports.items()) |entry| { undefs.appendAssumeCapacity(entry.value.symbol); } - for (self.threadlocal_imports.items()) |entry| { - undefs.appendAssumeCapacity(entry.value.symbol); + if (has_tlv) { + undefs.appendAssumeCapacity(self.tlv_bootstrap.?.symbol); } const locals_off = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); From ac0c669473b20a1fdcb818b92381d5ac5d70b64e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 12 Mar 2021 17:58:54 +0100 Subject: [PATCH 41/56] zld: add/fix more issues * fix debug info for static archives * allow handling of empty object files * fix some relocs for GOT loads --- src/link/MachO/Archive.zig | 8 +++++-- src/link/MachO/Object.zig | 11 ++++++++-- src/link/MachO/Zld.zig | 45 +++++++++++++++++++++++++++++++++----- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index a96c01d649..d144344ed9 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -202,13 +202,17 @@ fn readObject(self: *Archive, arch: std.Target.Cpu.Arch, ar_name: []const u8, re var object = Object{ .allocator = self.allocator, .name = object_name, + .ar_name = try mem.dupe(self.allocator, u8, ar_name), .file = new_file, .header = header, }; try object.readLoadCommands(reader, .{ .offset = offset }); - try object.readSymtab(); - try object.readStrtab(); + + if (object.symtab_cmd.index != null) { + try object.readSymtab(); + try object.readStrtab(); + } if (object.data_in_code_cmd_index != null) try object.readDataInCode(); diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 3393b1f773..bb3da944bb 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -16,6 +16,7 @@ usingnamespace @import("commands.zig"); allocator: *Allocator, file: fs.File, name: []u8, +ar_name: ?[]u8 = null, header: macho.mach_header_64, @@ -49,6 +50,9 @@ pub fn deinit(self: *Object) void { self.strtab.deinit(self.allocator); self.data_in_code_entries.deinit(self.allocator); self.allocator.free(self.name); + if (self.ar_name) |v| { + self.allocator.free(v); + } self.file.close(); } @@ -85,8 +89,11 @@ pub fn initFromFile(allocator: *Allocator, arch: std.Target.Cpu.Arch, name: []co }; try self.readLoadCommands(reader, .{}); - try self.readSymtab(); - try self.readStrtab(); + + if (self.symtab_cmd_index != null) { + try self.readSymtab(); + try self.readStrtab(); + } if (self.data_in_code_cmd_index != null) try self.readDataInCode(); diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 67251080cf..4d844fbd4a 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -767,7 +767,9 @@ fn resolveImports(self: *Zld) !void { mem.eql(u8, sym_name, "___stderrp") or mem.eql(u8, sym_name, "___stdinp") or mem.eql(u8, sym_name, "___stack_chk_guard") or - mem.eql(u8, sym_name, "_environ")) + mem.eql(u8, sym_name, "_environ") or + mem.eql(u8, sym_name, "__DefaultRuneLocale") or + mem.eql(u8, sym_name, "_mach_task_self_")) { log.debug("writing nonlazy symbol '{s}'", .{sym_name}); const index = @intCast(u32, self.nonlazy_imports.items().len); @@ -1192,6 +1194,8 @@ fn writeStubInStubHelper(self: *Zld, index: u32) !void { fn resolveSymbols(self: *Zld) !void { for (self.objects.items) |object, object_id| { const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + log.debug("\n\n", .{}); + log.debug("resolving symbols in {s}", .{object.name}); for (object.symtab.items) |sym| { if (isImport(&sym)) continue; @@ -1219,8 +1223,10 @@ fn resolveSymbols(self: *Zld) !void { if (tt == .Global) { for (locs.entry.value.items) |ss| { if (ss.tt == .Global) { - log.err("symbol '{s}' defined multiple times", .{sym_name}); - return error.MultipleSymbolDefinitions; + log.debug("symbol already defined '{s}'", .{sym_name}); + continue; + // log.err("symbol '{s}' defined multiple times: {}", .{ sym_name, sym }); + // return error.MultipleSymbolDefinitions; } } } @@ -1589,8 +1595,28 @@ fn doRelocs(self: *Zld) !void { ), inst, ); + const ta = if (addend) |a| target_addr + a else target_addr; const narrowed = @truncate(u12, ta); + log.debug(" | narrowed 0x{x}", .{narrowed}); + log.debug(" | parsed.size 0x{x}", .{parsed.size}); + + if (rel_type == .ARM64_RELOC_GOT_LOAD_PAGEOFF12) blk: { + const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = data_const_seg.sections.items[self.got_section_index.?]; + if (got.addr <= target_addr and target_addr < got.addr + got.size) break :blk; + + log.debug(" | rewriting to add", .{}); + mem.writeIntLittle(u32, inst, aarch64.Instruction.add( + @intToEnum(aarch64.Register, parsed.rt), + @intToEnum(aarch64.Register, parsed.rn), + narrowed, + false, + ).toU32()); + addend = null; + continue; + } + const offset: u12 = blk: { if (parsed.size == 0) { if (parsed.v == 1) { @@ -2628,8 +2654,16 @@ fn writeDebugInfo(self: *Zld) !void { }); // Path to object file with debug info var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; - const path = object.name; - const full_path = try std.os.realpath(path, &buffer); + const full_path = blk: { + if (object.ar_name) |prefix| { + const path = try std.os.realpath(prefix, &buffer); + break :blk try std.fmt.allocPrint(self.allocator, "{s}({s})", .{ path, object.name }); + } else { + const path = try std.os.realpath(object.name, &buffer); + break :blk try mem.dupe(self.allocator, u8, path); + } + }; + defer self.allocator.free(full_path); const stat = try object.file.stat(); const mtime = @intCast(u64, @divFloor(stat.mtime, 1_000_000_000)); try stabs.append(.{ @@ -2640,6 +2674,7 @@ fn writeDebugInfo(self: *Zld) !void { .n_value = mtime, }); } + log.debug("analyzing debug info in '{s}'", .{object.name}); for (object.symtab.items) |source_sym| { const symname = object.getString(source_sym.n_strx); From 1ec620be62e9acdafca8182b2452c3e3a1dc0ea9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 13 Mar 2021 13:16:34 +0100 Subject: [PATCH 42/56] zld: fix GOT loads and indirection on x86_64 --- lib/std/macho.zig | 2 +- src/link/MachO/Archive.zig | 9 ++- src/link/MachO/Zld.zig | 112 +++++++++++++++++++++++++++++++++---- 3 files changed, 109 insertions(+), 14 deletions(-) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 3cf6914ad9..f66626bafe 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -1616,7 +1616,7 @@ pub const GenericBlob = extern struct { length: u32, }; -/// The LC_DATA_IN_CODE load commands uses a linkedit_data_command +/// The LC_DATA_IN_CODE load commands uses a linkedit_data_command /// to point to an array of data_in_code_entry entries. Each entry /// describes a range of data in a code section. pub const data_in_code_entry = extern struct { diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index d144344ed9..8fa0457a16 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -209,7 +209,7 @@ fn readObject(self: *Archive, arch: std.Target.Cpu.Arch, ar_name: []const u8, re try object.readLoadCommands(reader, .{ .offset = offset }); - if (object.symtab_cmd.index != null) { + if (object.symtab_cmd_index != null) { try object.readSymtab(); try object.readStrtab(); } @@ -245,8 +245,11 @@ fn getName(allocator: *Allocator, header: ar_hdr, reader: anytype) ![]u8 { name = try allocator.dupe(u8, n); }, .Length => |len| { - name = try allocator.alloc(u8, len); - try reader.readNoEof(name); + var n = try allocator.alloc(u8, len); + defer allocator.free(n); + try reader.readNoEof(n); + const actual_len = mem.indexOfScalar(u8, n, @as(u8, 0)); + name = try allocator.dupe(u8, n[0..actual_len.?]); }, } return name; diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 4d844fbd4a..6719a86683 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -77,6 +77,7 @@ lazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, tlv_bootstrap: ?Import = null, threadlocal_offsets: std.ArrayListUnmanaged(u64) = .{}, local_rebases: std.ArrayListUnmanaged(Pointer) = .{}, +nonlazy_pointers: std.StringArrayHashMapUnmanaged(GotEntry) = .{}, strtab: std.ArrayListUnmanaged(u8) = .{}, @@ -85,6 +86,16 @@ stub_helper_stubs_start_off: ?u64 = null, mappings: std.AutoHashMapUnmanaged(MappingKey, SectionMapping) = .{}, unhandled_sections: std.AutoHashMapUnmanaged(MappingKey, u0) = .{}, +// TODO this will require scanning the relocations at least one to work out +// the exact amount of local GOT indirections. For the time being, set some +// default value. +const max_local_got_indirections: u16 = 1000; + +const GotEntry = struct { + index: u32, + target_addr: u64, +}; + const MappingKey = struct { object_id: u16, source_sect_id: u16, @@ -214,6 +225,10 @@ pub fn deinit(self: *Zld) void { self.allocator.free(entry.key); } self.nonlazy_imports.deinit(self.allocator); + for (self.nonlazy_pointers.items()) |*entry| { + self.allocator.free(entry.key); + } + self.nonlazy_pointers.deinit(self.allocator); for (self.exports.items()) |*entry| { self.allocator.free(entry.key); } @@ -874,7 +889,10 @@ fn allocateDataConstSegment(self: *Zld) !void { // Set got size const got = &seg.sections.items[self.got_section_index.?]; - got.size += nonlazy * @sizeOf(u64); + // TODO this will require scanning the relocations at least one to work out + // the exact amount of local GOT indirections. For the time being, set some + // default value. + got.size += (max_local_got_indirections + nonlazy) * @sizeOf(u64); try self.allocateSegment(self.data_const_segment_cmd_index.?, 0); } @@ -1358,15 +1376,67 @@ fn doRelocs(self: *Zld) !void { const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); switch (rel_type) { - .X86_64_RELOC_BRANCH, - .X86_64_RELOC_GOT_LOAD, - .X86_64_RELOC_GOT, - => { + .X86_64_RELOC_BRANCH => { assert(rel.r_length == 2); const inst = code[off..][0..4]; const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); mem.writeIntLittle(u32, inst, displacement); }, + .X86_64_RELOC_GOT_LOAD => { + assert(rel.r_length == 2); + const inst = code[off..][0..4]; + const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); + + blk: { + const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = data_const_seg.sections.items[self.got_section_index.?]; + if (got.addr <= target_addr and target_addr < got.addr + got.size) break :blk; + log.debug(" | rewriting to leaq", .{}); + code[off - 2] = 0x8d; + } + + mem.writeIntLittle(u32, inst, displacement); + }, + .X86_64_RELOC_GOT => { + assert(rel.r_length == 2); + // TODO Instead of referring to the target symbol directly, we refer to it + // indirectly via GOT. Getting actual target address should be done in the + // helper relocTargetAddr function rather than here. + const sym = object.symtab.items[rel.r_symbolnum]; + const sym_name = try self.allocator.dupe(u8, object.getString(sym.n_strx)); + const res = try self.nonlazy_pointers.getOrPut(self.allocator, sym_name); + defer if (res.found_existing) self.allocator.free(sym_name); + + const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = data_const_seg.sections.items[self.got_section_index.?]; + + if (!res.found_existing) { + const index = @intCast(u32, self.nonlazy_pointers.items().len) - 1; + assert(index < max_local_got_indirections); // TODO This is just a temp solution. + res.entry.value = .{ + .index = index, + .target_addr = target_addr, + }; + var buf: [@sizeOf(u64)]u8 = undefined; + mem.writeIntLittle(u64, &buf, target_addr); + const got_offset = got.offset + (index + self.nonlazy_imports.items().len) * @sizeOf(u64); + + log.debug(" | GOT off 0x{x}", .{got.offset}); + log.debug(" | writing GOT entry 0x{x} at 0x{x}", .{ target_addr, got_offset }); + + try self.file.?.pwriteAll(&buf, got_offset); + } + + const index = res.entry.value.index + self.nonlazy_imports.items().len; + const actual_target_addr = got.addr + index * @sizeOf(u64); + + log.debug(" | GOT addr 0x{x}", .{got.addr}); + log.debug(" | actual target address in GOT 0x{x}", .{actual_target_addr}); + + const inst = code[off..][0..4]; + const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, actual_target_addr) - @intCast(i64, this_addr) - 4)); + mem.writeIntLittle(u32, inst, displacement); + }, .X86_64_RELOC_TLV => { assert(rel.r_length == 2); // We need to rewrite the opcode from movq to leaq. @@ -2384,6 +2454,23 @@ fn writeRebaseInfoTable(self: *Zld) !void { try pointers.ensureCapacity(pointers.items.len + self.local_rebases.items.len); pointers.appendSliceAssumeCapacity(self.local_rebases.items); + if (self.got_section_index) |idx| { + // TODO this should be cleaned up! + try pointers.ensureCapacity(pointers.items.len + self.nonlazy_pointers.items().len); + const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); + const index_offset = @intCast(u32, self.nonlazy_imports.items().len); + for (self.nonlazy_pointers.items()) |entry| { + const index = index_offset + entry.value.index; + pointers.appendAssumeCapacity(.{ + .offset = base_offset + index * @sizeOf(u64), + .segment_id = segment_id, + }); + } + } + if (self.la_symbol_ptr_section_index) |idx| { try pointers.ensureCapacity(pointers.items.len + self.lazy_imports.items().len); const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; @@ -2851,8 +2938,9 @@ fn writeDynamicSymbolTable(self: *Zld) !void { const lazy = self.lazy_imports.items(); const nonlazy = self.nonlazy_imports.items(); + const got_locals = self.nonlazy_pointers.items(); dysymtab.indirectsymoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); - dysymtab.nindirectsyms = @intCast(u32, lazy.len * 2 + nonlazy.len); + dysymtab.nindirectsyms = @intCast(u32, lazy.len * 2 + nonlazy.len + got_locals.len); const needed_size = dysymtab.nindirectsyms * @sizeOf(u32); seg.inner.filesize += needed_size; @@ -2867,20 +2955,24 @@ fn writeDynamicSymbolTable(self: *Zld) !void { var writer = stream.writer(); stubs.reserved1 = 0; - for (self.lazy_imports.items()) |_, i| { + for (lazy) |_, i| { const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); try writer.writeIntLittle(u32, symtab_idx); } const base_id = @intCast(u32, lazy.len); got.reserved1 = base_id; - for (self.nonlazy_imports.items()) |_, i| { + for (nonlazy) |_, i| { const symtab_idx = @intCast(u32, dysymtab.iundefsym + i + base_id); try writer.writeIntLittle(u32, symtab_idx); } + // TODO there should be one common set of GOT entries. + for (got_locals) |_| { + try writer.writeIntLittle(u32, macho.INDIRECT_SYMBOL_LOCAL); + } - la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, nonlazy.len); - for (self.lazy_imports.items()) |_, i| { + la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, nonlazy.len) + @intCast(u32, got_locals.len); + for (lazy) |_, i| { const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); try writer.writeIntLittle(u32, symtab_idx); } From 900658a85d57f4a6a554f8a8ed9d89fcd5483d5a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 17 Mar 2021 20:05:29 +0100 Subject: [PATCH 43/56] rebase with master --- src/link/MachO/Zld.zig | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 6719a86683..0e6e869231 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -2505,14 +2505,6 @@ fn writeRebaseInfoTable(self: *Zld) !void { try self.file.?.pwriteAll(buffer, dyld_info.rebase_off); } -fn pointerCmp(context: void, a: Pointer, b: Pointer) bool { - if (a.segment_id < b.segment_id) return true; - if (a.segment_id == b.segment_id) { - return a.offset < b.offset; - } - return false; -} - fn writeBindInfoTable(self: *Zld) !void { var pointers = std.ArrayList(Pointer).init(self.allocator); defer pointers.deinit(); From 7516dfff83368df4d67e3c10923c3d6da1b72879 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 17 Mar 2021 22:14:55 +0100 Subject: [PATCH 44/56] zld: use zld when linking aarch64 by default and cross-comp --- CMakeLists.txt | 7 +++++++ lib/std/debug.zig | 18 ------------------ src/Compilation.zig | 2 -- src/link.zig | 2 -- src/link/MachO.zig | 28 +++++++++++++++++++++++----- src/main.zig | 7 ++----- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 55198f3581..5c68791872 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -564,7 +564,14 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link/Coff.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" + "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig" + "${CMAKE_SOURCE_DIR}/src/link/MachO/CodeSignature.zig" + "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig" + "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig" + "${CMAKE_SOURCE_DIR}/src/link/MachO/Zld.zig" + "${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig" + "${CMAKE_SOURCE_DIR}/src/link/MachO/commands.zig" "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig" "${CMAKE_SOURCE_DIR}/src/link/C/zig.h" "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 74fb95ffa8..a7badf7ed1 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -250,24 +250,6 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c resetSegfaultHandler(); } - if (comptime std.Target.current.isDarwin() and std.Target.current.cpu.arch == .aarch64) - nosuspend { - // As a workaround for not having threadlocal variable support in LLD for this target, - // we have a simpler panic implementation that does not use threadlocal variables. - // TODO https://github.com/ziglang/zig/issues/7527 - const stderr = io.getStdErr().writer(); - if (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst) == 0) { - stderr.print("panic: " ++ format ++ "\n", args) catch os.abort(); - if (trace) |t| { - dumpStackTrace(t.*); - } - dumpCurrentStackTrace(first_trace_addr); - } else { - stderr.print("Panicked during a panic. Aborting.\n", .{}) catch os.abort(); - } - os.abort(); - }; - nosuspend switch (panic_stage) { 0 => { panic_stage = 1; diff --git a/src/Compilation.zig b/src/Compilation.zig index e2ecc44fdb..786280f9ef 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -447,7 +447,6 @@ pub const InitOptions = struct { want_lto: ?bool = null, use_llvm: ?bool = null, use_lld: ?bool = null, - use_zld: ?bool = null, use_clang: ?bool = null, rdynamic: bool = false, strip: bool = false, @@ -1021,7 +1020,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .link_mode = link_mode, .object_format = ofmt, .optimize_mode = options.optimize_mode, - .use_zld = options.use_zld orelse false, .use_lld = use_lld, .use_llvm = use_llvm, .system_linker_hack = darwin_options.system_linker_hack, diff --git a/src/link.zig b/src/link.zig index 6767b8d1b3..db3e973f84 100644 --- a/src/link.zig +++ b/src/link.zig @@ -61,8 +61,6 @@ pub const Options = struct { /// Darwin-only. If this is true, `use_llvm` is true, and `is_native_os` is true, this link code will /// use system linker `ld` instead of the LLD. system_linker_hack: bool, - /// Experimental Zig linker. - use_zld: bool, link_libc: bool, link_libcpp: bool, function_sections: bool, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 8f599a64a3..78e38ed8a3 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -634,12 +634,26 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { 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(); + const use_zld = blk: { + if (self.base.options.is_native_os and self.base.options.system_linker_hack) { + break :blk false; + } - if (true) { - // if (self.base.options.use_zld) { + if (self.base.options.target.cpu.arch == .aarch64) { + break :blk true; + } + + if (self.base.options.link_libcpp or + self.base.options.output_mode == .Lib or + self.base.options.linker_script != null) + { + break :blk false; + } + + break :blk true; + }; + + if (use_zld) { var zld = Zld.init(self.base.allocator); defer zld.deinit(); zld.arch = target.cpu.arch; @@ -663,6 +677,10 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { return zld.link(input_files.items, full_out_path); } + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(self.base.allocator); + defer argv.deinit(); + // TODO https://github.com/ziglang/zig/issues/6971 // Note that there is no need to check if running natively since we do that already // when setting `system_linker_hack` in Compilation struct. diff --git a/src/main.zig b/src/main.zig index 4549f6f954..19248d1a44 100644 --- a/src/main.zig +++ b/src/main.zig @@ -547,7 +547,6 @@ fn buildOutputType( var image_base_override: ?u64 = null; var use_llvm: ?bool = null; var use_lld: ?bool = null; - var use_zld: ?bool = null; var use_clang: ?bool = null; var link_eh_frame_hdr = false; var link_emit_relocs = false; @@ -907,8 +906,6 @@ fn buildOutputType( use_lld = true; } else if (mem.eql(u8, arg, "-fno-LLD")) { use_lld = false; - } else if (mem.eql(u8, arg, "-fZLD")) { - use_zld = true; } else if (mem.eql(u8, arg, "-fClang")) { use_clang = true; } else if (mem.eql(u8, arg, "-fno-Clang")) { @@ -1867,7 +1864,6 @@ fn buildOutputType( .want_compiler_rt = want_compiler_rt, .use_llvm = use_llvm, .use_lld = use_lld, - .use_zld = use_zld, .use_clang = use_clang, .rdynamic = rdynamic, .linker_script = linker_script, @@ -3245,7 +3241,8 @@ pub const ClangArgIterator = struct { self.zig_equivalent = clang_arg.zig_equivalent; break :find_clang_arg; }, - } else { + } + else { fatal("Unknown Clang option: '{s}'", .{arg}); } } From dfeca48e35ffd768813d8e595962005e80593e2b Mon Sep 17 00:00:00 2001 From: Dimenus Date: Mon, 15 Mar 2021 20:39:23 -0500 Subject: [PATCH 45/56] @intCast takes two args --- src/BuiltinFn.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BuiltinFn.zig b/src/BuiltinFn.zig index deb1cbfa76..1710169dc7 100644 --- a/src/BuiltinFn.zig +++ b/src/BuiltinFn.zig @@ -477,7 +477,7 @@ pub const list = list: { "@intCast", .{ .tag = .int_cast, - .param_count = 1, + .param_count = 2, }, }, .{ From 861ea640090f5c4a36889cf65f32c575cbe3b505 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 18 Mar 2021 08:32:27 +0100 Subject: [PATCH 46/56] macho: remove now obsolete LLD fixups --- src/link/MachO.zig | 284 --------------------------------------------- 1 file changed, 284 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 78e38ed8a3..4c5d405074 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -947,119 +947,6 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { log.warn("unexpected LLD stderr:\n{s}", .{stderr}); } } - - // At this stage, LLD has done its job. It is time to patch the resultant - // binaries up! - const out_file = try directory.handle.openFile(self.base.options.emit.?.sub_path, .{ .write = true }); - try self.parseFromFile(out_file); - - if (self.libsystem_cmd_index == null and self.header.?.filetype == macho.MH_EXECUTE) { - const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const text_section = text_segment.sections.items[self.text_section_index.?]; - const after_last_cmd_offset = self.header.?.sizeofcmds + @sizeOf(macho.mach_header_64); - const needed_size = padToIdeal(@sizeOf(macho.linkedit_data_command)); - - if (needed_size + after_last_cmd_offset > text_section.offset) { - log.err("Unable to extend padding between the end of load commands and start of __text section.", .{}); - log.err("Re-run the linker with '-headerpad 0x{x}' option if available, or", .{needed_size}); - log.err("fall back to the system linker by exporting 'ZIG_SYSTEM_LINKER_HACK=1'.", .{}); - return error.NotEnoughPadding; - } - - // Calculate next available dylib ordinal. - const next_ordinal = blk: { - var ordinal: u32 = 1; - for (self.load_commands.items) |cmd| { - switch (cmd) { - .Dylib => ordinal += 1, - else => {}, - } - } - break :blk ordinal; - }; - - // Add load dylib load command - self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len); - const cmdsize = @intCast(u32, mem.alignForwardGeneric( - u64, - @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH), - @sizeOf(u64), - )); - // TODO Find a way to work out runtime version from the OS version triple stored in std.Target. - // In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0. - const min_version = 0x0; - var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{ - .cmd = macho.LC_LOAD_DYLIB, - .cmdsize = cmdsize, - .dylib = .{ - .name = @sizeOf(macho.dylib_command), - .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files - .current_version = min_version, - .compatibility_version = min_version, - }, - }); - dylib_cmd.data = try self.base.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name); - mem.set(u8, dylib_cmd.data, 0); - mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH)); - try self.load_commands.append(self.base.allocator, .{ .Dylib = dylib_cmd }); - self.header_dirty = true; - self.load_commands_dirty = true; - - if (self.symtab_cmd_index == null or self.dysymtab_cmd_index == null) { - log.err("Incomplete Mach-O binary: no LC_SYMTAB or LC_DYSYMTAB load command found!", .{}); - log.err("Without the symbol table, it is not possible to patch up the binary for cross-compilation.", .{}); - return error.NoSymbolTableFound; - } - - // Patch dyld info - try self.fixupBindInfo(next_ordinal); - try self.fixupLazyBindInfo(next_ordinal); - - // Write updated load commands and the header - try self.writeLoadCommands(); - try self.writeHeader(); - - assert(!self.header_dirty); - assert(!self.load_commands_dirty); - } - if (self.code_signature_cmd_index == null) outer: { - if (target.cpu.arch != .aarch64) break :outer; // This is currently needed only for aarch64 targets. - const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const text_section = text_segment.sections.items[self.text_section_index.?]; - const after_last_cmd_offset = self.header.?.sizeofcmds + @sizeOf(macho.mach_header_64); - const needed_size = padToIdeal(@sizeOf(macho.linkedit_data_command)); - - if (needed_size + after_last_cmd_offset > text_section.offset) { - log.err("Unable to extend padding between the end of load commands and start of __text section.", .{}); - log.err("Re-run the linker with '-headerpad 0x{x}' option if available, or", .{needed_size}); - log.err("fall back to the system linker by exporting 'ZIG_SYSTEM_LINKER_HACK=1'.", .{}); - return error.NotEnoughPadding; - } - - // Add code signature load command - self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len); - try self.load_commands.append(self.base.allocator, .{ - .LinkeditData = .{ - .cmd = macho.LC_CODE_SIGNATURE, - .cmdsize = @sizeOf(macho.linkedit_data_command), - .dataoff = 0, - .datasize = 0, - }, - }); - self.header_dirty = true; - self.load_commands_dirty = true; - - // Pad out space for code signature - try self.writeCodeSignaturePadding(); - // Write updated load commands and the header - try self.writeLoadCommands(); - try self.writeHeader(); - // Generate adhoc code signature - try self.writeCodeSignature(); - - assert(!self.header_dirty); - assert(!self.load_commands_dirty); - } } } @@ -3375,177 +3262,6 @@ fn writeHeader(self: *MachO) !void { self.header_dirty = false; } -/// Parse MachO contents from existing binary file. -fn parseFromFile(self: *MachO, file: fs.File) !void { - self.base.file = file; - var reader = file.reader(); - const header = try reader.readStruct(macho.mach_header_64); - try self.load_commands.ensureCapacity(self.base.allocator, header.ncmds); - var i: u16 = 0; - while (i < header.ncmds) : (i += 1) { - const cmd = try LoadCommand.read(self.base.allocator, reader); - switch (cmd.cmd()) { - macho.LC_SEGMENT_64 => { - const x = cmd.Segment; - if (parseAndCmpName(&x.inner.segname, "__PAGEZERO")) { - self.pagezero_segment_cmd_index = i; - } else if (parseAndCmpName(&x.inner.segname, "__LINKEDIT")) { - self.linkedit_segment_cmd_index = i; - } else if (parseAndCmpName(&x.inner.segname, "__TEXT")) { - self.text_segment_cmd_index = i; - for (x.sections.items) |sect, j| { - if (parseAndCmpName(§.sectname, "__text")) { - self.text_section_index = @intCast(u16, j); - } - } - } else if (parseAndCmpName(&x.inner.segname, "__DATA")) { - self.data_segment_cmd_index = i; - } else if (parseAndCmpName(&x.inner.segname, "__DATA_CONST")) { - self.data_const_segment_cmd_index = i; - } - }, - macho.LC_DYLD_INFO_ONLY => { - self.dyld_info_cmd_index = i; - }, - macho.LC_SYMTAB => { - self.symtab_cmd_index = i; - }, - macho.LC_DYSYMTAB => { - self.dysymtab_cmd_index = i; - }, - macho.LC_LOAD_DYLINKER => { - self.dylinker_cmd_index = i; - }, - macho.LC_VERSION_MIN_MACOSX, macho.LC_VERSION_MIN_IPHONEOS, macho.LC_VERSION_MIN_WATCHOS, macho.LC_VERSION_MIN_TVOS => { - self.version_min_cmd_index = i; - }, - macho.LC_SOURCE_VERSION => { - self.source_version_cmd_index = i; - }, - macho.LC_UUID => { - self.uuid_cmd_index = i; - }, - macho.LC_MAIN => { - self.main_cmd_index = i; - }, - macho.LC_LOAD_DYLIB => { - const x = cmd.Dylib; - if (parseAndCmpName(x.data, mem.spanZ(LIB_SYSTEM_PATH))) { - self.libsystem_cmd_index = i; - } - }, - macho.LC_FUNCTION_STARTS => { - self.function_starts_cmd_index = i; - }, - macho.LC_DATA_IN_CODE => { - self.data_in_code_cmd_index = i; - }, - macho.LC_CODE_SIGNATURE => { - self.code_signature_cmd_index = i; - }, - else => { - log.warn("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); - }, - } - self.load_commands.appendAssumeCapacity(cmd); - } - self.header = header; -} - -fn parseAndCmpName(name: []const u8, needle: []const u8) bool { - const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len; - return mem.eql(u8, name[0..len], needle); -} - -fn parseSymbolTable(self: *MachO) !void { - const symtab = self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const dysymtab = self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; - - var buffer = try self.base.allocator.alloc(macho.nlist_64, symtab.nsyms); - defer self.base.allocator.free(buffer); - const nread = try self.base.file.?.preadAll(@ptrCast([*]u8, buffer)[0 .. symtab.nsyms * @sizeOf(macho.nlist_64)], symtab.symoff); - assert(@divExact(nread, @sizeOf(macho.nlist_64)) == buffer.len); - - try self.locals.ensureCapacity(self.base.allocator, dysymtab.nlocalsym); - try self.globals.ensureCapacity(self.base.allocator, dysymtab.nextdefsym); - try self.undef_symbols.ensureCapacity(self.base.allocator, dysymtab.nundefsym); - - self.locals.appendSliceAssumeCapacity(buffer[dysymtab.ilocalsym .. dysymtab.ilocalsym + dysymtab.nlocalsym]); - self.globals.appendSliceAssumeCapacity(buffer[dysymtab.iextdefsym .. dysymtab.iextdefsym + dysymtab.nextdefsym]); - self.undef_symbols.appendSliceAssumeCapacity(buffer[dysymtab.iundefsym .. dysymtab.iundefsym + dysymtab.nundefsym]); -} - -fn parseStringTable(self: *MachO) !void { - const symtab = self.load_commands.items[self.symtab_cmd_index.?].Symtab; - - var buffer = try self.base.allocator.alloc(u8, symtab.strsize); - defer self.base.allocator.free(buffer); - const nread = try self.base.file.?.preadAll(buffer, symtab.stroff); - assert(nread == buffer.len); - - try self.string_table.ensureCapacity(self.base.allocator, symtab.strsize); - self.string_table.appendSliceAssumeCapacity(buffer); -} - -fn fixupBindInfo(self: *MachO, dylib_ordinal: u32) !void { - const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; - var buffer = try self.base.allocator.alloc(u8, dyld_info.bind_size); - defer self.base.allocator.free(buffer); - const nread = try self.base.file.?.preadAll(buffer, dyld_info.bind_off); - assert(nread == buffer.len); - try self.fixupInfoCommon(buffer, dylib_ordinal); - try self.base.file.?.pwriteAll(buffer, dyld_info.bind_off); -} - -fn fixupLazyBindInfo(self: *MachO, dylib_ordinal: u32) !void { - const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; - var buffer = try self.base.allocator.alloc(u8, dyld_info.lazy_bind_size); - defer self.base.allocator.free(buffer); - const nread = try self.base.file.?.preadAll(buffer, dyld_info.lazy_bind_off); - assert(nread == buffer.len); - try self.fixupInfoCommon(buffer, dylib_ordinal); - try self.base.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); -} - -fn fixupInfoCommon(self: *MachO, buffer: []u8, dylib_ordinal: u32) !void { - var stream = std.io.fixedBufferStream(buffer); - var reader = stream.reader(); - - while (true) { - const inst = reader.readByte() catch |err| switch (err) { - error.EndOfStream => break, - else => return err, - }; - const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK; - const opcode: u8 = inst & macho.BIND_OPCODE_MASK; - - switch (opcode) { - macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => { - var next = try reader.readByte(); - while (next != @as(u8, 0)) { - next = try reader.readByte(); - } - }, - macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => { - _ = try std.leb.readULEB128(u64, reader); - }, - macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => { - // Perform the fixup. - try stream.seekBy(-1); - var writer = stream.writer(); - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, dylib_ordinal)); - }, - macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => { - _ = try std.leb.readULEB128(u64, reader); - }, - macho.BIND_OPCODE_SET_ADDEND_SLEB => { - _ = try std.leb.readILEB128(i64, reader); - }, - else => {}, - } - } -} - pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { // TODO https://github.com/ziglang/zig/issues/1284 return std.math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch From 2cf1c1b96b4869a61d2bfb8d2e9725e2adacde17 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 18 Mar 2021 08:42:07 +0100 Subject: [PATCH 47/56] macho: honor verbose_link when linking with zld --- src/link/MachO.zig | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 4c5d405074..517fabaf3e 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -633,13 +633,15 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { if (!mem.eql(u8, the_object_path, full_out_path)) { try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); } - } else { + } else outer: { const use_zld = blk: { if (self.base.options.is_native_os and self.base.options.system_linker_hack) { + // If the user forces the use of ld64, make sure we are running native! break :blk false; } if (self.base.options.target.cpu.arch == .aarch64) { + // On aarch64, always use zld. break :blk true; } @@ -647,6 +649,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { self.base.options.output_mode == .Lib or self.base.options.linker_script != null) { + // Fallback to LLD in this handful of cases on x86_64 only. break :blk false; } @@ -674,7 +677,28 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { try input_files.append(comp.libcxxabi_static_lib.?.full_object_path); try input_files.append(comp.libcxx_static_lib.?.full_object_path); } - return zld.link(input_files.items, full_out_path); + + if (self.base.options.verbose_link) { + var argv = std.ArrayList([]const u8).init(self.base.allocator); + defer argv.deinit(); + + try argv.append("zig"); + try argv.append("ld"); + + try argv.ensureCapacity(input_files.items.len); + for (input_files.items) |f| { + argv.appendAssumeCapacity(f); + } + + try argv.append("-o"); + try argv.append(full_out_path); + + Compilation.dump_argv(argv.items); + } + + try zld.link(input_files.items, full_out_path); + + break :outer; } // Create an LLD command line and invoke it. From f3b4f79c7fb2aafd2812c077469121f27d9f0018 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 18 Mar 2021 08:55:08 +0100 Subject: [PATCH 48/56] zld: temporarily disable testing shared lib linking --- src/link/MachO/Zld.zig | 2 +- test/standalone.zig | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 0e6e869231..c98bacc08a 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -853,7 +853,7 @@ fn allocateTextSegment(self: *Zld) !void { sizeofcmds += lc.cmdsize(); } - try self.allocateSegment(self.text_segment_cmd_index.?, sizeofcmds); + try self.allocateSegment(self.text_segment_cmd_index.?, @sizeOf(macho.mach_header_64) + sizeofcmds); // Shift all sections to the back to minimize jump size between __TEXT and __DATA segments. var min_alignment: u32 = 0; diff --git a/test/standalone.zig b/test/standalone.zig index 3ad0659f09..d8c08a6b9c 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -9,7 +9,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void { cases.add("test/standalone/main_return_error/error_u8.zig"); cases.add("test/standalone/main_return_error/error_u8_non_zero.zig"); cases.addBuildFile("test/standalone/main_pkg_path/build.zig"); - cases.addBuildFile("test/standalone/shared_library/build.zig"); + if (std.Target.current.os.tag != .macos) { + // TODO zld cannot link shared libraries yet. + cases.addBuildFile("test/standalone/shared_library/build.zig"); + } cases.addBuildFile("test/standalone/mix_o_files/build.zig"); cases.addBuildFile("test/standalone/global_linkage/build.zig"); cases.addBuildFile("test/standalone/static_c_lib/build.zig"); From 75a7abb0c47d9f5e99b1f69250776528fa60f569 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 18 Mar 2021 09:33:08 +1100 Subject: [PATCH 49/56] std: Fix std.fs.path.joinZ --- lib/std/fs/path.zig | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index 776cb4040c..0bba522fb6 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -92,7 +92,7 @@ pub fn join(allocator: *Allocator, paths: []const []const u8) ![]u8 { /// Naively combines a series of paths with the native path seperator and null terminator. /// Allocates memory for the result, which must be freed by the caller. pub fn joinZ(allocator: *Allocator, paths: []const []const u8) ![:0]u8 { - const out = joinSepMaybeZ(allocator, sep, isSep, paths, true); + const out = try joinSepMaybeZ(allocator, sep, isSep, paths, true); return out[0 .. out.len - 1 :0]; } @@ -119,6 +119,16 @@ fn testJoinMaybeZPosix(paths: []const []const u8, expected: []const u8, zero: bo } test "join" { + { + const actual: []u8 = try join(testing.allocator, &[_][]const u8{}); + defer testing.allocator.free(actual); + testing.expectEqualSlices(u8, "", actual); + } + { + const actual: [:0]u8 = try joinZ(testing.allocator, &[_][]const u8{}); + defer testing.allocator.free(actual); + testing.expectEqualSlices(u8, "", actual); + } for (&[_]bool{ false, true }) |zero| { testJoinMaybeZWindows(&[_][]const u8{}, "", zero); testJoinMaybeZWindows(&[_][]const u8{ "c:\\a\\b", "c" }, "c:\\a\\b\\c", zero); From b54514d9dd15225ef2578b33c4c384db4680b90b Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Thu, 18 Mar 2021 05:41:04 -0700 Subject: [PATCH 50/56] translate-c: Use [N:0] arrays when initializer is a string literal (#8264) * translate-c: Use [N:0] arrays when initializer is a string literal Translate incomplete arrays as [N:0] when initialized by a string literal. This preserves a bit more of the type information from the original C program. Fixes #8215 --- src/translate_c.zig | 126 ++++++++++++++++++++++++---------------- src/translate_c/ast.zig | 86 ++++++++++++++++++++++++++- test/translate_c.zig | 81 ++++++++++++++++---------- 3 files changed, 209 insertions(+), 84 deletions(-) diff --git a/src/translate_c.zig b/src/translate_c.zig index e63ced58e9..16881eef5d 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -636,7 +636,7 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co if (has_init) trans_init: { if (decl_init) |expr| { const node_or_error = if (expr.getStmtClass() == .StringLiteralClass) - transStringLiteralAsArray(c, scope, @ptrCast(*const clang.StringLiteral, expr), zigArraySize(c, type_node) catch 0) + transStringLiteralInitializer(c, scope, @ptrCast(*const clang.StringLiteral, expr), type_node) else transExprCoercing(c, scope, expr, .used); init_node = node_or_error catch |err| switch (err) { @@ -1412,7 +1412,7 @@ fn transDeclStmtOne( var init_node = if (decl_init) |expr| if (expr.getStmtClass() == .StringLiteralClass) - try transStringLiteralAsArray(c, scope, @ptrCast(*const clang.StringLiteral, expr), try zigArraySize(c, type_node)) + try transStringLiteralInitializer(c, scope, @ptrCast(*const clang.StringLiteral, expr), type_node) else try transExprCoercing(c, scope, expr, .used) else @@ -1758,6 +1758,20 @@ fn transReturnStmt( return Tag.@"return".create(c.arena, rhs); } +fn transNarrowStringLiteral( + c: *Context, + scope: *Scope, + stmt: *const clang.StringLiteral, + result_used: ResultUsed, +) TransError!Node { + var len: usize = undefined; + const bytes_ptr = stmt.getString_bytes_begin_size(&len); + + const str = try std.fmt.allocPrint(c.arena, "\"{}\"", .{std.zig.fmtEscapes(bytes_ptr[0..len])}); + const node = try Tag.string_literal.create(c.arena, str); + return maybeSuppressResult(c, scope, result_used, node); +} + fn transStringLiteral( c: *Context, scope: *Scope, @@ -1766,19 +1780,14 @@ fn transStringLiteral( ) TransError!Node { const kind = stmt.getKind(); switch (kind) { - .Ascii, .UTF8 => { - var len: usize = undefined; - const bytes_ptr = stmt.getString_bytes_begin_size(&len); - - const str = try std.fmt.allocPrint(c.arena, "\"{}\"", .{std.zig.fmtEscapes(bytes_ptr[0..len])}); - const node = try Tag.string_literal.create(c.arena, str); - return maybeSuppressResult(c, scope, result_used, node); - }, + .Ascii, .UTF8 => return transNarrowStringLiteral(c, scope, stmt, result_used), .UTF16, .UTF32, .Wide => { const str_type = @tagName(stmt.getKind()); const name = try std.fmt.allocPrint(c.arena, "zig.{s}_string_{d}", .{ str_type, c.getMangle() }); - const lit_array = try transStringLiteralAsArray(c, scope, stmt, stmt.getLength() + 1); + const expr_base = @ptrCast(*const clang.Expr, stmt); + const array_type = try transQualTypeInitialized(c, scope, expr_base.getType(), expr_base, expr_base.getBeginLoc()); + const lit_array = try transStringLiteralInitializer(c, scope, stmt, array_type); const decl = try Tag.var_simple.create(c.arena, .{ .name = name, .init = lit_array }); try scope.appendNode(decl); const node = try Tag.identifier.create(c.arena, name); @@ -1787,52 +1796,67 @@ fn transStringLiteral( } } -/// Parse the size of an array back out from an ast Node. -fn zigArraySize(c: *Context, node: Node) TransError!usize { - if (node.castTag(.array_type)) |array| { - return array.data.len; - } - return error.UnsupportedTranslation; +fn getArrayPayload(array_type: Node) ast.Payload.Array.ArrayTypeInfo { + return (array_type.castTag(.array_type) orelse array_type.castTag(.null_sentinel_array_type).?).data; } -/// Translate a string literal to an array of integers. Used when an -/// array is initialized from a string literal. `array_size` is the -/// size of the array being initialized. If the string literal is larger -/// than the array, truncate the string. If the array is larger than the -/// string literal, pad the array with 0's -fn transStringLiteralAsArray( +/// Translate a string literal that is initializing an array. In general narrow string +/// literals become `"".*` or `""[0..].*` if they need truncation. +/// Wide string literals become an array of integers. zero-fillers pad out the array to +/// the appropriate length, if necessary. +fn transStringLiteralInitializer( c: *Context, scope: *Scope, stmt: *const clang.StringLiteral, - array_size: usize, + array_type: Node, ) TransError!Node { - if (array_size == 0) return error.UnsupportedType; + assert(array_type.tag() == .array_type or array_type.tag() == .null_sentinel_array_type); + + const is_narrow = stmt.getKind() == .Ascii or stmt.getKind() == .UTF8; const str_length = stmt.getLength(); + const payload = getArrayPayload(array_type); + const array_size = payload.len; + const elem_type = payload.elem_type; - const expr_base = @ptrCast(*const clang.Expr, stmt); - const ty = expr_base.getType().getTypePtr(); - const const_arr_ty = @ptrCast(*const clang.ConstantArrayType, ty); + if (array_size == 0) return Tag.empty_array.create(c.arena, elem_type); - const elem_type = try transQualType(c, scope, const_arr_ty.getElementType(), expr_base.getBeginLoc()); - const arr_type = try Tag.array_type.create(c.arena, .{ .len = array_size, .elem_type = elem_type }); - const init_list = try c.arena.alloc(Node, array_size); + const num_inits = math.min(str_length, array_size); + const init_node = if (num_inits > 0) blk: { + if (is_narrow) { + // "string literal".* or string literal"[0..num_inits].* + var str = try transNarrowStringLiteral(c, scope, stmt, .used); + if (str_length != array_size) str = try Tag.string_slice.create(c.arena, .{ .string = str, .end = num_inits }); + break :blk try Tag.deref.create(c.arena, str); + } else { + const init_list = try c.arena.alloc(Node, num_inits); + var i: c_uint = 0; + while (i < num_inits) : (i += 1) { + init_list[i] = try transCreateCharLitNode(c, false, stmt.getCodeUnit(i)); + } + const init_args = .{ .len = num_inits, .elem_type = elem_type }; + const init_array_type = try if (array_type.tag() == .array_type) Tag.array_type.create(c.arena, init_args) else Tag.null_sentinel_array_type.create(c.arena, init_args); + break :blk try Tag.array_init.create(c.arena, .{ + .cond = init_array_type, + .cases = init_list, + }); + } + } else null; - var i: c_uint = 0; - const kind = stmt.getKind(); - const narrow = kind == .Ascii or kind == .UTF8; - while (i < str_length and i < array_size) : (i += 1) { - const code_unit = stmt.getCodeUnit(i); - init_list[i] = try transCreateCharLitNode(c, narrow, code_unit); - } - while (i < array_size) : (i += 1) { - init_list[i] = try transCreateNodeNumber(c, 0, .int); - } + if (num_inits == array_size) return init_node.?; // init_node is only null if num_inits == 0; but if num_inits == array_size == 0 we've already returned + assert(array_size > str_length); // If array_size <= str_length, `num_inits == array_size` and we've already returned. - return Tag.array_init.create(c.arena, .{ - .cond = arr_type, - .cases = init_list, + const filler_node = try Tag.array_filler.create(c.arena, .{ + .type = elem_type, + .filler = Tag.zero_literal.init(), + .count = array_size - str_length, }); + + if (init_node) |some| { + return Tag.array_cat.create(c.arena, .{ .lhs = some, .rhs = filler_node }); + } else { + return filler_node; + } } /// determine whether `stmt` is a "pointer subtraction expression" - a subtraction where @@ -3342,9 +3366,8 @@ fn addTopLevelDecl(c: *Context, name: []const u8, decl_node: Node) !void { try c.global_scope.nodes.append(decl_node); } -/// Translate a qual type for a variable with an initializer. The initializer -/// only matters for incomplete arrays, since the size of the array is determined -/// by the size of the initializer +/// Translate a qualtype for a variable with an initializer. This only matters +/// for incomplete arrays, since the initializer determines the size of the array. fn transQualTypeInitialized( c: *Context, scope: *Scope, @@ -3360,9 +3383,14 @@ fn transQualTypeInitialized( switch (decl_init.getStmtClass()) { .StringLiteralClass => { const string_lit = @ptrCast(*const clang.StringLiteral, decl_init); - const string_lit_size = string_lit.getLength() + 1; // +1 for null terminator + const string_lit_size = string_lit.getLength(); const array_size = @intCast(usize, string_lit_size); - return Tag.array_type.create(c.arena, .{ .len = array_size, .elem_type = elem_ty }); + + // incomplete array initialized with empty string, will be translated as [1]T{0} + // see https://github.com/ziglang/zig/issues/8256 + if (array_size == 0) return Tag.array_type.create(c.arena, .{ .len = 1, .elem_type = elem_ty }); + + return Tag.null_sentinel_array_type.create(c.arena, .{ .len = array_size, .elem_type = elem_ty }); }, .InitListExprClass => { const init_expr = @ptrCast(*const clang.InitListExpr, decl_init); diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index e5f76cc1de..4b595a7940 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -40,6 +40,8 @@ pub const Node = extern union { string_literal, char_literal, enum_literal, + /// "string"[0..end] + string_slice, identifier, @"if", /// if (!operand) break; @@ -176,6 +178,7 @@ pub const Node = extern union { c_pointer, single_pointer, array_type, + null_sentinel_array_type, /// @import("std").meta.sizeof(operand) std_meta_sizeof, @@ -334,7 +337,7 @@ pub const Node = extern union { .std_meta_promoteIntLiteral => Payload.PromoteIntLiteral, .block => Payload.Block, .c_pointer, .single_pointer => Payload.Pointer, - .array_type => Payload.Array, + .array_type, .null_sentinel_array_type => Payload.Array, .arg_redecl, .alias, .fail_decl => Payload.ArgRedecl, .log2_int_type => Payload.Log2IntType, .var_simple, .pub_var_simple => Payload.SimpleVarDecl, @@ -342,6 +345,7 @@ pub const Node = extern union { .array_filler => Payload.ArrayFiller, .pub_inline_fn => Payload.PubInlineFn, .field_access => Payload.FieldAccess, + .string_slice => Payload.StringSlice, }; } @@ -584,10 +588,12 @@ pub const Payload = struct { pub const Array = struct { base: Payload, - data: struct { + data: ArrayTypeInfo, + + pub const ArrayTypeInfo = struct { elem_type: Node, len: usize, - }, + }; }; pub const Pointer = struct { @@ -664,6 +670,14 @@ pub const Payload = struct { radix: Node, }, }; + + pub const StringSlice = struct { + base: Payload, + data: struct { + string: Node, + end: usize, + }, + }; }; /// Converts the nodes into a Zig ast. @@ -1015,6 +1029,36 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { .data = undefined, }); }, + .string_slice => { + const payload = node.castTag(.string_slice).?.data; + + const string = try renderNode(c, payload.string); + const l_bracket = try c.addToken(.l_bracket, "["); + const start = try c.addNode(.{ + .tag = .integer_literal, + .main_token = try c.addToken(.integer_literal, "0"), + .data = undefined, + }); + _ = try c.addToken(.ellipsis2, ".."); + const end = try c.addNode(.{ + .tag = .integer_literal, + .main_token = try c.addTokenFmt(.integer_literal, "{d}", .{payload.end}), + .data = undefined, + }); + _ = try c.addToken(.r_bracket, "]"); + + return c.addNode(.{ + .tag = .slice, + .main_token = l_bracket, + .data = .{ + .lhs = string, + .rhs = try c.addExtra(std.zig.ast.Node.Slice{ + .start = start, + .end = end, + }), + }, + }); + }, .fail_decl => { const payload = node.castTag(.fail_decl).?.data; // pub const name = @compileError(msg); @@ -1581,6 +1625,10 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { const payload = node.castTag(.array_type).?.data; return renderArrayType(c, payload.len, payload.elem_type); }, + .null_sentinel_array_type => { + const payload = node.castTag(.null_sentinel_array_type).?.data; + return renderNullSentinelArrayType(c, payload.len, payload.elem_type); + }, .array_filler => { const payload = node.castTag(.array_filler).?.data; @@ -1946,6 +1994,36 @@ fn renderArrayType(c: *Context, len: usize, elem_type: Node) !NodeIndex { }); } +fn renderNullSentinelArrayType(c: *Context, len: usize, elem_type: Node) !NodeIndex { + const l_bracket = try c.addToken(.l_bracket, "["); + const len_expr = try c.addNode(.{ + .tag = .integer_literal, + .main_token = try c.addTokenFmt(.integer_literal, "{d}", .{len}), + .data = undefined, + }); + _ = try c.addToken(.colon, ":"); + + const sentinel_expr = try c.addNode(.{ + .tag = .integer_literal, + .main_token = try c.addToken(.integer_literal, "0"), + .data = undefined, + }); + + _ = try c.addToken(.r_bracket, "]"); + const elem_type_expr = try renderNode(c, elem_type); + return c.addNode(.{ + .tag = .array_type_sentinel, + .main_token = l_bracket, + .data = .{ + .lhs = len_expr, + .rhs = try c.addExtra(std.zig.ast.Node.ArrayTypeSentinel { + .sentinel = sentinel_expr, + .elem_type = elem_type_expr, + }), + }, + }); +} + fn addSemicolonIfNeeded(c: *Context, node: Node) !void { switch (node.tag()) { .warning => unreachable, @@ -2014,6 +2092,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .integer_literal, .float_literal, .string_literal, + .string_slice, .char_literal, .enum_literal, .identifier, @@ -2035,6 +2114,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .func, .call, .array_type, + .null_sentinel_array_type, .bool_to_int, .div_exact, .byte_offset_of, diff --git a/test/translate_c.zig b/test/translate_c.zig index 6aab4736a3..1e89de78a1 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -745,14 +745,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ static const char v2[] = "2.2.2"; \\} , &[_][]const u8{ - \\const v2: [6]u8 = [6]u8{ - \\ '2', - \\ '.', - \\ '2', - \\ '.', - \\ '2', - \\ 0, - \\}; + \\const v2: [5:0]u8 = "2.2.2".*; \\pub export fn foo() void {} }); @@ -1600,30 +1593,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\static char arr1[] = "hello"; \\char arr2[] = "hello"; , &[_][]const u8{ - \\pub export var arr0: [6]u8 = [6]u8{ - \\ 'h', - \\ 'e', - \\ 'l', - \\ 'l', - \\ 'o', - \\ 0, - \\}; - \\pub var arr1: [6]u8 = [6]u8{ - \\ 'h', - \\ 'e', - \\ 'l', - \\ 'l', - \\ 'o', - \\ 0, - \\}; - \\pub export var arr2: [6]u8 = [6]u8{ - \\ 'h', - \\ 'e', - \\ 'l', - \\ 'l', - \\ 'o', - \\ 0, - \\}; + \\pub export var arr0: [5:0]u8 = "hello".*; + \\pub var arr1: [5:0]u8 = "hello".*; + \\pub export var arr2: [5:0]u8 = "hello".*; }); cases.add("array initializer expr", @@ -3425,4 +3397,49 @@ pub fn addCases(cases: *tests.TranslateCContext) void { , &[_][]const u8{ \\pub const FOO = @compileError("TODO implement function '__builtin_alloca_with_align' in std.c.builtins"); }); + + cases.add("null sentinel arrays when initialized from string literal. Issue #8256", + \\#include + \\char zero[0] = "abc"; + \\uint32_t zero_w[0] = U"💯💯💯"; + \\char empty_incomplete[] = ""; + \\uint32_t empty_incomplete_w[] = U""; + \\char empty_constant[100] = ""; + \\uint32_t empty_constant_w[100] = U""; + \\char incomplete[] = "abc"; + \\uint32_t incomplete_w[] = U"💯💯💯"; + \\char truncated[1] = "abc"; + \\uint32_t truncated_w[1] = U"💯💯💯"; + \\char extend[5] = "a"; + \\uint32_t extend_w[5] = U"💯"; + \\char no_null[3] = "abc"; + \\uint32_t no_null_w[3] = U"💯💯💯"; + , &[_][]const u8{ + \\pub export var zero: [0]u8 = [0]u8{}; + \\pub export var zero_w: [0]u32 = [0]u32{}; + \\pub export var empty_incomplete: [1]u8 = [1]u8{0} ** 1; + \\pub export var empty_incomplete_w: [1]u32 = [1]u32{0} ** 1; + \\pub export var empty_constant: [100]u8 = [1]u8{0} ** 100; + \\pub export var empty_constant_w: [100]u32 = [1]u32{0} ** 100; + \\pub export var incomplete: [3:0]u8 = "abc".*; + \\pub export var incomplete_w: [3:0]u32 = [3:0]u32{ + \\ '\u{1f4af}', + \\ '\u{1f4af}', + \\ '\u{1f4af}', + \\}; + \\pub export var truncated: [1]u8 = "abc"[0..1].*; + \\pub export var truncated_w: [1]u32 = [1]u32{ + \\ '\u{1f4af}', + \\}; + \\pub export var extend: [5]u8 = "a"[0..1].* ++ [1]u8{0} ** 4; + \\pub export var extend_w: [5]u32 = [1]u32{ + \\ '\u{1f4af}', + \\} ++ [1]u32{0} ** 4; + \\pub export var no_null: [3]u8 = "abc".*; + \\pub export var no_null_w: [3]u32 = [3]u32{ + \\ '\u{1f4af}', + \\ '\u{1f4af}', + \\ '\u{1f4af}', + \\}; + }); } From 5cbb642525fa92e16dd3c9e978a9f8a34734de4c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Mar 2021 13:13:35 -0700 Subject: [PATCH 51/56] stage1: small mem usage improvement for IR move a boolean field to be represented implicitly with the enum tag. Just borrowing one of the many strategies of stage2. This simple change took the peak mem usage from std lib tests on my machine from 8.21 GiB to 8.11 GiB. --- src/stage1/all_types.hpp | 4 ++-- src/stage1/ir.cpp | 35 ++++++++++++++++++++++------------- src/stage1/ir_print.cpp | 21 ++++++++++++++++----- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index 7ad585a524..5e1d4969db 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -2629,7 +2629,8 @@ enum IrInstSrcId { IrInstSrcIdResolveResult, IrInstSrcIdResetResult, IrInstSrcIdSetAlignStack, - IrInstSrcIdArgType, + IrInstSrcIdArgTypeAllowVarFalse, + IrInstSrcIdArgTypeAllowVarTrue, IrInstSrcIdExport, IrInstSrcIdExtern, IrInstSrcIdErrorReturnTrace, @@ -4144,7 +4145,6 @@ struct IrInstSrcArgType { IrInstSrc *fn_type; IrInstSrc *arg_index; - bool allow_var; }; struct IrInstSrcExport { diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index 7906df3b0d..c004e49e48 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -514,7 +514,8 @@ static void destroy_instruction_src(IrInstSrc *inst) { return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdSetAlignStack: return heap::c_allocator.destroy(reinterpret_cast(inst)); - case IrInstSrcIdArgType: + case IrInstSrcIdArgTypeAllowVarFalse: + case IrInstSrcIdArgTypeAllowVarTrue: return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdExport: return heap::c_allocator.destroy(reinterpret_cast(inst)); @@ -1546,10 +1547,6 @@ static constexpr IrInstSrcId ir_inst_id(IrInstSrcSetAlignStack *) { return IrInstSrcIdSetAlignStack; } -static constexpr IrInstSrcId ir_inst_id(IrInstSrcArgType *) { - return IrInstSrcIdArgType; -} - static constexpr IrInstSrcId ir_inst_id(IrInstSrcExport *) { return IrInstSrcIdExport; } @@ -4590,10 +4587,17 @@ static IrInstSrc *ir_build_set_align_stack(IrBuilderSrc *irb, Scope *scope, AstN static IrInstSrc *ir_build_arg_type(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, IrInstSrc *fn_type, IrInstSrc *arg_index, bool allow_var) { - IrInstSrcArgType *instruction = ir_build_instruction(irb, scope, source_node); + IrInstSrcArgType *instruction = heap::c_allocator.create(); + instruction->base.id = allow_var ? + IrInstSrcIdArgTypeAllowVarTrue : IrInstSrcIdArgTypeAllowVarFalse; + instruction->base.base.scope = scope; + instruction->base.base.source_node = source_node; + instruction->base.base.debug_id = exec_next_debug_id(irb->exec); + instruction->base.owner_bb = irb->current_basic_block; + ir_instruction_append(irb->current_basic_block, &instruction->base); + instruction->fn_type = fn_type; instruction->arg_index = arg_index; - instruction->allow_var = allow_var; ir_ref_instruction(fn_type, irb->current_basic_block); ir_ref_instruction(arg_index, irb->current_basic_block); @@ -30976,7 +30980,9 @@ static IrInstGen *ir_analyze_instruction_set_align_stack(IrAnalyze *ira, IrInstS return ir_const_void(ira, &instruction->base.base); } -static IrInstGen *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstSrcArgType *instruction) { +static IrInstGen *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstSrcArgType *instruction, + bool allow_var) +{ IrInstGen *fn_type_inst = instruction->fn_type->child; ZigType *fn_type = ir_resolve_type(ira, fn_type_inst); if (type_is_invalid(fn_type)) @@ -30998,7 +31004,7 @@ static IrInstGen *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstSrcArgTy FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id; if (arg_index >= fn_type_id->param_count) { - if (instruction->allow_var) { + if (allow_var) { // TODO remove this with var args return ir_const_type(ira, &instruction->base.base, ira->codegen->builtin_types.entry_anytype); } @@ -31013,7 +31019,7 @@ static IrInstGen *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstSrcArgTy // Args are only unresolved if our function is generic. ir_assert(fn_type->data.fn.is_generic, &instruction->base.base); - if (instruction->allow_var) { + if (allow_var) { return ir_const_type(ira, &instruction->base.base, ira->codegen->builtin_types.entry_anytype); } else { ir_add_error(ira, &arg_index_inst->base, @@ -32383,8 +32389,10 @@ static IrInstGen *ir_analyze_instruction_base(IrAnalyze *ira, IrInstSrc *instruc return ir_analyze_instruction_reset_result(ira, (IrInstSrcResetResult *)instruction); case IrInstSrcIdSetAlignStack: return ir_analyze_instruction_set_align_stack(ira, (IrInstSrcSetAlignStack *)instruction); - case IrInstSrcIdArgType: - return ir_analyze_instruction_arg_type(ira, (IrInstSrcArgType *)instruction); + case IrInstSrcIdArgTypeAllowVarFalse: + return ir_analyze_instruction_arg_type(ira, (IrInstSrcArgType *)instruction, false); + case IrInstSrcIdArgTypeAllowVarTrue: + return ir_analyze_instruction_arg_type(ira, (IrInstSrcArgType *)instruction, true); case IrInstSrcIdExport: return ir_analyze_instruction_export(ira, (IrInstSrcExport *)instruction); case IrInstSrcIdExtern: @@ -32826,7 +32834,8 @@ bool ir_inst_src_has_side_effects(IrInstSrc *instruction) { case IrInstSrcIdAlignCast: case IrInstSrcIdImplicitCast: case IrInstSrcIdResolveResult: - case IrInstSrcIdArgType: + case IrInstSrcIdArgTypeAllowVarFalse: + case IrInstSrcIdArgTypeAllowVarTrue: case IrInstSrcIdErrorReturnTrace: case IrInstSrcIdErrorUnion: case IrInstSrcIdFloatOp: diff --git a/src/stage1/ir_print.cpp b/src/stage1/ir_print.cpp index 98d349012e..6750b6178c 100644 --- a/src/stage1/ir_print.cpp +++ b/src/stage1/ir_print.cpp @@ -308,8 +308,10 @@ const char* ir_inst_src_type_str(IrInstSrcId id) { return "SrcResetResult"; case IrInstSrcIdSetAlignStack: return "SrcSetAlignStack"; - case IrInstSrcIdArgType: - return "SrcArgType"; + case IrInstSrcIdArgTypeAllowVarFalse: + return "SrcArgTypeAllowVarFalse"; + case IrInstSrcIdArgTypeAllowVarTrue: + return "SrcArgTypeAllowVarTrue"; case IrInstSrcIdExport: return "SrcExport"; case IrInstSrcIdExtern: @@ -2344,11 +2346,17 @@ static void ir_print_set_align_stack(IrPrintSrc *irp, IrInstSrcSetAlignStack *in fprintf(irp->f, ")"); } -static void ir_print_arg_type(IrPrintSrc *irp, IrInstSrcArgType *instruction) { +static void ir_print_arg_type(IrPrintSrc *irp, IrInstSrcArgType *instruction, bool allow_var) { fprintf(irp->f, "@ArgType("); ir_print_other_inst_src(irp, instruction->fn_type); fprintf(irp->f, ","); ir_print_other_inst_src(irp, instruction->arg_index); + fprintf(irp->f, ","); + if (allow_var) { + fprintf(irp->f, "allow_var=true"); + } else { + fprintf(irp->f, "allow_var=false"); + } fprintf(irp->f, ")"); } @@ -2942,8 +2950,11 @@ static void ir_print_inst_src(IrPrintSrc *irp, IrInstSrc *instruction, bool trai case IrInstSrcIdSetAlignStack: ir_print_set_align_stack(irp, (IrInstSrcSetAlignStack *)instruction); break; - case IrInstSrcIdArgType: - ir_print_arg_type(irp, (IrInstSrcArgType *)instruction); + case IrInstSrcIdArgTypeAllowVarFalse: + ir_print_arg_type(irp, (IrInstSrcArgType *)instruction, false); + break; + case IrInstSrcIdArgTypeAllowVarTrue: + ir_print_arg_type(irp, (IrInstSrcArgType *)instruction, true); break; case IrInstSrcIdExport: ir_print_export(irp, (IrInstSrcExport *)instruction); From 7c6eb4161912f1c66acbdd341339e6cca8c410d8 Mon Sep 17 00:00:00 2001 From: jacob gw Date: Thu, 18 Mar 2021 08:05:31 -0400 Subject: [PATCH 52/56] cbe tests: fix test on windows "\n" -> std.cstr.line_sep --- test/stage2/cbe.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index e9082f57fa..8a8a8ca224 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -51,7 +51,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = printf("Hello, %s!\n", "world"); \\ return 0; \\} - , "Hello, world!\n"); + , "Hello, world!" ++ std.cstr.line_sep); } { From ac7217e1f5ff15a6fc8248b9c1c651b318f472ad Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 17 Mar 2021 11:32:11 +0200 Subject: [PATCH 53/56] translate-c: preserve zero fractional part in float literals --- src/translate_c.zig | 8 ++++---- test/run_translated_c.zig | 11 +++++++++++ test/translate_c.zig | 12 ++++++------ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/translate_c.zig b/src/translate_c.zig index 16881eef5d..8aabc30754 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -3248,11 +3248,11 @@ fn transFloatingLiteral(c: *Context, scope: *Scope, stmt: *const clang.FloatingL var dbl = stmt.getValueAsApproximateDouble(); const is_negative = dbl < 0; if (is_negative) dbl = -dbl; - const str = try std.fmt.allocPrint(c.arena, "{d}", .{dbl}); - var node = if (dbl == std.math.floor(dbl)) - try Tag.integer_literal.create(c.arena, str) + const str = if (dbl == std.math.floor(dbl)) + try std.fmt.allocPrint(c.arena, "{d}.0", .{dbl}) else - try Tag.float_literal.create(c.arena, str); + try std.fmt.allocPrint(c.arena, "{d}", .{dbl}); + var node = try Tag.float_literal.create(c.arena, str); if (is_negative) node = try Tag.negate.create(c.arena, node); return maybeSuppressResult(c, scope, used, node); } diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index 10c3a04540..44c3956a12 100644 --- a/test/run_translated_c.zig +++ b/test/run_translated_c.zig @@ -3,6 +3,17 @@ const tests = @import("tests.zig"); const nl = std.cstr.line_sep; pub fn addCases(cases: *tests.RunTranslatedCContext) void { + cases.add("division of floating literals", + \\#define _NO_CRT_STDIO_INLINE 1 + \\#include + \\#define PI 3.14159265358979323846f + \\#define DEG2RAD (PI/180.0f) + \\int main(void) { + \\ printf("DEG2RAD is: %f\n", DEG2RAD); + \\ return 0; + \\} + , "DEG2RAD is: 0.017453" ++ nl); + cases.add("use global scope for record/enum/typedef type transalation if needed", \\void bar(void); \\void baz(void); diff --git a/test/translate_c.zig b/test/translate_c.zig index 1e89de78a1..2d5d838033 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -2428,7 +2428,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ b: c_int, \\}; \\pub extern var a: struct_Foo; - \\pub export var b: f32 = 2; + \\pub export var b: f32 = 2.0; \\pub export fn foo() void { \\ var c: [*c]struct_Foo = undefined; \\ _ = a.b; @@ -2992,17 +2992,17 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub extern fn fn_bool(x: bool) void; \\pub extern fn fn_ptr(x: ?*c_void) void; \\pub export fn call() void { - \\ fn_int(@floatToInt(c_int, 3)); - \\ fn_int(@floatToInt(c_int, 3)); - \\ fn_int(@floatToInt(c_int, 3)); + \\ fn_int(@floatToInt(c_int, 3.0)); + \\ fn_int(@floatToInt(c_int, 3.0)); + \\ fn_int(@floatToInt(c_int, 3.0)); \\ fn_int(@as(c_int, 1094861636)); \\ fn_f32(@intToFloat(f32, @as(c_int, 3))); \\ fn_f64(@intToFloat(f64, @as(c_int, 3))); \\ fn_char(@bitCast(u8, @truncate(i8, @as(c_int, '3')))); \\ fn_char(@bitCast(u8, @truncate(i8, @as(c_int, '\x01')))); \\ fn_char(@bitCast(u8, @truncate(i8, @as(c_int, 0)))); - \\ fn_f32(3); - \\ fn_f64(3); + \\ fn_f32(3.0); + \\ fn_f64(3.0); \\ fn_bool(@as(c_int, 123) != 0); \\ fn_bool(@as(c_int, 0) != 0); \\ fn_bool(@ptrToInt(fn_int) != 0); From a6f5aa71ac1e8c9a27ecfa6a50f0445a50545a5d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Mar 2021 13:37:49 -0700 Subject: [PATCH 54/56] stage1: small IR memory optimization on CheckSwitchProngs --- src/stage1/all_types.hpp | 4 ++-- src/stage1/ir.cpp | 32 +++++++++++++++++++------------- src/stage1/ir_print.cpp | 19 ++++++++++++++----- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index 5e1d4969db..e0cbf1c0c5 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -2610,7 +2610,8 @@ enum IrInstSrcId { IrInstSrcIdEnumToInt, IrInstSrcIdIntToErr, IrInstSrcIdErrToInt, - IrInstSrcIdCheckSwitchProngs, + IrInstSrcIdCheckSwitchProngsUnderYes, + IrInstSrcIdCheckSwitchProngsUnderNo, IrInstSrcIdCheckStatementIsVoid, IrInstSrcIdTypeName, IrInstSrcIdDeclRef, @@ -4021,7 +4022,6 @@ struct IrInstSrcCheckSwitchProngs { IrInstSrcCheckSwitchProngsRange *ranges; size_t range_count; AstNode* else_prong; - bool have_underscore_prong; }; struct IrInstSrcCheckStatementIsVoid { diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index c004e49e48..ad45d7682c 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -476,7 +476,8 @@ static void destroy_instruction_src(IrInstSrc *inst) { return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdErrToInt: return heap::c_allocator.destroy(reinterpret_cast(inst)); - case IrInstSrcIdCheckSwitchProngs: + case IrInstSrcIdCheckSwitchProngsUnderNo: + case IrInstSrcIdCheckSwitchProngsUnderYes: return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdCheckStatementIsVoid: return heap::c_allocator.destroy(reinterpret_cast(inst)); @@ -1471,10 +1472,6 @@ static constexpr IrInstSrcId ir_inst_id(IrInstSrcErrToInt *) { return IrInstSrcIdErrToInt; } -static constexpr IrInstSrcId ir_inst_id(IrInstSrcCheckSwitchProngs *) { - return IrInstSrcIdCheckSwitchProngs; -} - static constexpr IrInstSrcId ir_inst_id(IrInstSrcCheckStatementIsVoid *) { return IrInstSrcIdCheckStatementIsVoid; } @@ -4351,13 +4348,19 @@ static IrInstSrc *ir_build_check_switch_prongs(IrBuilderSrc *irb, Scope *scope, IrInstSrc *target_value, IrInstSrcCheckSwitchProngsRange *ranges, size_t range_count, AstNode* else_prong, bool have_underscore_prong) { - IrInstSrcCheckSwitchProngs *instruction = ir_build_instruction( - irb, scope, source_node); + IrInstSrcCheckSwitchProngs *instruction = heap::c_allocator.create(); + instruction->base.id = have_underscore_prong ? + IrInstSrcIdCheckSwitchProngsUnderYes : IrInstSrcIdCheckSwitchProngsUnderNo; + instruction->base.base.scope = scope; + instruction->base.base.source_node = source_node; + instruction->base.base.debug_id = exec_next_debug_id(irb->exec); + instruction->base.owner_bb = irb->current_basic_block; + ir_instruction_append(irb->current_basic_block, &instruction->base); + instruction->target_value = target_value; instruction->ranges = ranges; instruction->range_count = range_count; instruction->else_prong = else_prong; - instruction->have_underscore_prong = have_underscore_prong; ir_ref_instruction(target_value, irb->current_basic_block); for (size_t i = 0; i < range_count; i += 1) { @@ -29706,7 +29709,7 @@ static IrInstGen *ir_analyze_instruction_test_comptime(IrAnalyze *ira, IrInstSrc } static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, - IrInstSrcCheckSwitchProngs *instruction) + IrInstSrcCheckSwitchProngs *instruction, bool have_underscore_prong) { IrInstGen *target_value = instruction->target_value->child; ZigType *switch_type = target_value->value->type; @@ -29771,7 +29774,7 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, bigint_incr(&field_index); } } - if (instruction->have_underscore_prong) { + if (have_underscore_prong) { if (!switch_type->data.enumeration.non_exhaustive) { ir_add_error(ira, &instruction->base.base, buf_sprintf("switch on exhaustive enum has `_` prong")); @@ -32347,8 +32350,10 @@ static IrInstGen *ir_analyze_instruction_base(IrAnalyze *ira, IrInstSrc *instruc return ir_analyze_instruction_fn_proto(ira, (IrInstSrcFnProto *)instruction); case IrInstSrcIdTestComptime: return ir_analyze_instruction_test_comptime(ira, (IrInstSrcTestComptime *)instruction); - case IrInstSrcIdCheckSwitchProngs: - return ir_analyze_instruction_check_switch_prongs(ira, (IrInstSrcCheckSwitchProngs *)instruction); + case IrInstSrcIdCheckSwitchProngsUnderNo: + return ir_analyze_instruction_check_switch_prongs(ira, (IrInstSrcCheckSwitchProngs *)instruction, false); + case IrInstSrcIdCheckSwitchProngsUnderYes: + return ir_analyze_instruction_check_switch_prongs(ira, (IrInstSrcCheckSwitchProngs *)instruction, true); case IrInstSrcIdCheckStatementIsVoid: return ir_analyze_instruction_check_statement_is_void(ira, (IrInstSrcCheckStatementIsVoid *)instruction); case IrInstSrcIdDeclRef: @@ -32745,7 +32750,8 @@ bool ir_inst_src_has_side_effects(IrInstSrc *instruction) { case IrInstSrcIdMemcpy: case IrInstSrcIdBreakpoint: case IrInstSrcIdOverflowOp: // TODO when we support multiple returns this can be side effect free - case IrInstSrcIdCheckSwitchProngs: + case IrInstSrcIdCheckSwitchProngsUnderNo: + case IrInstSrcIdCheckSwitchProngsUnderYes: case IrInstSrcIdCheckStatementIsVoid: case IrInstSrcIdCheckRuntimeScope: case IrInstSrcIdPanic: diff --git a/src/stage1/ir_print.cpp b/src/stage1/ir_print.cpp index 6750b6178c..cdbada4f20 100644 --- a/src/stage1/ir_print.cpp +++ b/src/stage1/ir_print.cpp @@ -270,8 +270,10 @@ const char* ir_inst_src_type_str(IrInstSrcId id) { return "SrcIntToErr"; case IrInstSrcIdErrToInt: return "SrcErrToInt"; - case IrInstSrcIdCheckSwitchProngs: - return "SrcCheckSwitchProngs"; + case IrInstSrcIdCheckSwitchProngsUnderNo: + return "SrcCheckSwitchProngsUnderNo"; + case IrInstSrcIdCheckSwitchProngsUnderYes: + return "SrcCheckSwitchProngsUnderYes"; case IrInstSrcIdCheckStatementIsVoid: return "SrcCheckStatementIsVoid"; case IrInstSrcIdTypeName: @@ -2189,7 +2191,9 @@ static void ir_print_err_to_int(IrPrintGen *irp, IrInstGenErrToInt *instruction) ir_print_other_inst_gen(irp, instruction->target); } -static void ir_print_check_switch_prongs(IrPrintSrc *irp, IrInstSrcCheckSwitchProngs *instruction) { +static void ir_print_check_switch_prongs(IrPrintSrc *irp, IrInstSrcCheckSwitchProngs *instruction, + bool have_underscore_prong) +{ fprintf(irp->f, "@checkSwitchProngs("); ir_print_other_inst_src(irp, instruction->target_value); fprintf(irp->f, ","); @@ -2202,6 +2206,8 @@ static void ir_print_check_switch_prongs(IrPrintSrc *irp, IrInstSrcCheckSwitchPr } const char *have_else_str = instruction->else_prong != nullptr ? "yes" : "no"; fprintf(irp->f, ")else:%s", have_else_str); + const char *have_under_str = have_underscore_prong ? "yes" : "no"; + fprintf(irp->f, " _:%s", have_under_str); } static void ir_print_check_statement_is_void(IrPrintSrc *irp, IrInstSrcCheckStatementIsVoid *instruction) { @@ -2893,8 +2899,11 @@ static void ir_print_inst_src(IrPrintSrc *irp, IrInstSrc *instruction, bool trai case IrInstSrcIdErrToInt: ir_print_err_to_int(irp, (IrInstSrcErrToInt *)instruction); break; - case IrInstSrcIdCheckSwitchProngs: - ir_print_check_switch_prongs(irp, (IrInstSrcCheckSwitchProngs *)instruction); + case IrInstSrcIdCheckSwitchProngsUnderNo: + ir_print_check_switch_prongs(irp, (IrInstSrcCheckSwitchProngs *)instruction, false); + break; + case IrInstSrcIdCheckSwitchProngsUnderYes: + ir_print_check_switch_prongs(irp, (IrInstSrcCheckSwitchProngs *)instruction, true); break; case IrInstSrcIdCheckStatementIsVoid: ir_print_check_statement_is_void(irp, (IrInstSrcCheckStatementIsVoid *)instruction); From 5e5b35f1077d5cf77a89aee5dde939de35f2e247 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Mar 2021 14:03:06 -0700 Subject: [PATCH 55/56] stage1: small memory optimization for simple pointer types Avoid storing extra IR instruction data for simple pointer types. --- src/stage1/all_types.hpp | 17 +++++++ src/stage1/analyze.cpp | 49 +++++++++++++++++++- src/stage1/ir.cpp | 99 ++++++++++++++++++++++++++++++++++++++++ src/stage1/ir_print.cpp | 19 ++++++++ 4 files changed, 183 insertions(+), 1 deletion(-) diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index e0cbf1c0c5..b1d0bd2ce1 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -391,6 +391,8 @@ enum LazyValueId { LazyValueIdAlignOf, LazyValueIdSizeOf, LazyValueIdPtrType, + LazyValueIdPtrTypeSimple, + LazyValueIdPtrTypeSimpleConst, LazyValueIdOptType, LazyValueIdSliceType, LazyValueIdFnType, @@ -467,6 +469,13 @@ struct LazyValuePtrType { bool is_allowzero; }; +struct LazyValuePtrTypeSimple { + LazyValue base; + + IrAnalyze *ira; + IrInstGen *elem_type; +}; + struct LazyValueOptType { LazyValue base; @@ -2625,6 +2634,8 @@ enum IrInstSrcId { IrInstSrcIdHasField, IrInstSrcIdSetEvalBranchQuota, IrInstSrcIdPtrType, + IrInstSrcIdPtrTypeSimple, + IrInstSrcIdPtrTypeSimpleConst, IrInstSrcIdAlignCast, IrInstSrcIdImplicitCast, IrInstSrcIdResolveResult, @@ -3296,6 +3307,12 @@ struct IrInstSrcArrayType { IrInstSrc *child_type; }; +struct IrInstSrcPtrTypeSimple { + IrInstSrc base; + + IrInstSrc *child_type; +}; + struct IrInstSrcPtrType { IrInstSrc base; diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index a4e368288e..d46faac2f0 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -1237,6 +1237,22 @@ Error type_val_resolve_zero_bits(CodeGen *g, ZigValue *type_val, ZigType *parent parent_type_val, is_zero_bits); } } + case LazyValueIdPtrTypeSimple: + case LazyValueIdPtrTypeSimpleConst: { + LazyValuePtrTypeSimple *lazy_ptr_type = reinterpret_cast(type_val->data.x_lazy); + + if (parent_type_val == lazy_ptr_type->elem_type->value) { + // Does a struct which contains a pointer field to itself have bits? Yes. + *is_zero_bits = false; + return ErrorNone; + } else { + if (parent_type_val == nullptr) { + parent_type_val = type_val; + } + return type_val_resolve_zero_bits(g, lazy_ptr_type->elem_type->value, parent_type, + parent_type_val, is_zero_bits); + } + } case LazyValueIdArrayType: { LazyValueArrayType *lazy_array_type = reinterpret_cast(type_val->data.x_lazy); @@ -1285,6 +1301,8 @@ Error type_val_resolve_is_opaque_type(CodeGen *g, ZigValue *type_val, bool *is_o zig_unreachable(); case LazyValueIdSliceType: case LazyValueIdPtrType: + case LazyValueIdPtrTypeSimple: + case LazyValueIdPtrTypeSimpleConst: case LazyValueIdFnType: case LazyValueIdOptType: case LazyValueIdErrUnionType: @@ -1313,6 +1331,11 @@ static ReqCompTime type_val_resolve_requires_comptime(CodeGen *g, ZigValue *type LazyValuePtrType *lazy_ptr_type = reinterpret_cast(type_val->data.x_lazy); return type_val_resolve_requires_comptime(g, lazy_ptr_type->elem_type->value); } + case LazyValueIdPtrTypeSimple: + case LazyValueIdPtrTypeSimpleConst: { + LazyValuePtrTypeSimple *lazy_ptr_type = reinterpret_cast(type_val->data.x_lazy); + return type_val_resolve_requires_comptime(g, lazy_ptr_type->elem_type->value); + } case LazyValueIdOptType: { LazyValueOptType *lazy_opt_type = reinterpret_cast(type_val->data.x_lazy); return type_val_resolve_requires_comptime(g, lazy_opt_type->payload_type->value); @@ -1413,6 +1436,24 @@ start_over: } return ErrorNone; } + case LazyValueIdPtrTypeSimple: + case LazyValueIdPtrTypeSimpleConst: { + LazyValuePtrTypeSimple *lazy_ptr_type = reinterpret_cast(type_val->data.x_lazy); + bool is_zero_bits; + if ((err = type_val_resolve_zero_bits(g, lazy_ptr_type->elem_type->value, nullptr, + nullptr, &is_zero_bits))) + { + return err; + } + if (is_zero_bits) { + *abi_size = 0; + *size_in_bits = 0; + } else { + *abi_size = g->builtin_types.entry_usize->abi_size; + *size_in_bits = g->builtin_types.entry_usize->size_in_bits; + } + return ErrorNone; + } case LazyValueIdFnType: *abi_size = g->builtin_types.entry_usize->abi_size; *size_in_bits = g->builtin_types.entry_usize->size_in_bits; @@ -1449,6 +1490,8 @@ Error type_val_resolve_abi_align(CodeGen *g, AstNode *source_node, ZigValue *typ zig_unreachable(); case LazyValueIdSliceType: case LazyValueIdPtrType: + case LazyValueIdPtrTypeSimple: + case LazyValueIdPtrTypeSimpleConst: case LazyValueIdFnType: *abi_align = g->builtin_types.entry_usize->abi_align; return ErrorNone; @@ -1506,7 +1549,9 @@ static OnePossibleValue type_val_resolve_has_one_possible_value(CodeGen *g, ZigV return OnePossibleValueYes; return type_val_resolve_has_one_possible_value(g, lazy_array_type->elem_type->value); } - case LazyValueIdPtrType: { + case LazyValueIdPtrType: + case LazyValueIdPtrTypeSimple: + case LazyValueIdPtrTypeSimpleConst: { Error err; bool zero_bits; if ((err = type_val_resolve_zero_bits(g, type_val, nullptr, nullptr, &zero_bits))) { @@ -5758,6 +5803,8 @@ static bool can_mutate_comptime_var_state(ZigValue *value) { case LazyValueIdAlignOf: case LazyValueIdSizeOf: case LazyValueIdPtrType: + case LazyValueIdPtrTypeSimple: + case LazyValueIdPtrTypeSimpleConst: case LazyValueIdOptType: case LazyValueIdSliceType: case LazyValueIdFnType: diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index ad45d7682c..16536dc71f 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -487,6 +487,9 @@ static void destroy_instruction_src(IrInstSrc *inst) { return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdPtrType: return heap::c_allocator.destroy(reinterpret_cast(inst)); + case IrInstSrcIdPtrTypeSimple: + case IrInstSrcIdPtrTypeSimpleConst: + return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdDeclRef: return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdPanic: @@ -2609,11 +2612,35 @@ static IrInstGen *ir_build_br_gen(IrAnalyze *ira, IrInst *source_instr, IrBasicB return &inst->base; } +static IrInstSrc *ir_build_ptr_type_simple(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, + IrInstSrc *child_type, bool is_const) +{ + IrInstSrcPtrTypeSimple *inst = heap::c_allocator.create(); + inst->base.id = is_const ? IrInstSrcIdPtrTypeSimpleConst : IrInstSrcIdPtrTypeSimple; + inst->base.base.scope = scope; + inst->base.base.source_node = source_node; + inst->base.base.debug_id = exec_next_debug_id(irb->exec); + inst->base.owner_bb = irb->current_basic_block; + ir_instruction_append(irb->current_basic_block, &inst->base); + + inst->child_type = child_type; + + ir_ref_instruction(child_type, irb->current_basic_block); + + return &inst->base; +} + static IrInstSrc *ir_build_ptr_type(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, IrInstSrc *child_type, bool is_const, bool is_volatile, PtrLen ptr_len, IrInstSrc *sentinel, IrInstSrc *align_value, uint32_t bit_offset_start, uint32_t host_int_bytes, bool is_allow_zero) { + if (!is_volatile && ptr_len == PtrLenSingle && sentinel == nullptr && align_value == nullptr && + bit_offset_start == 0 && host_int_bytes == 0 && is_allow_zero == 0) + { + return ir_build_ptr_type_simple(irb, scope, source_node, child_type, is_const); + } + IrInstSrcPtrType *inst = ir_build_instruction(irb, scope, source_node); inst->sentinel = sentinel; inst->align_value = align_value; @@ -30878,6 +30905,24 @@ static IrInstGen *ir_analyze_instruction_ptr_to_int(IrAnalyze *ira, IrInstSrcPtr return ir_build_ptr_to_int_gen(ira, &instruction->base.base, target); } +static IrInstGen *ir_analyze_instruction_ptr_type_simple(IrAnalyze *ira, + IrInstSrcPtrTypeSimple *instruction, bool is_const) +{ + IrInstGen *result = ir_const(ira, &instruction->base.base, ira->codegen->builtin_types.entry_type); + result->value->special = ConstValSpecialLazy; + + LazyValuePtrTypeSimple *lazy_ptr_type = heap::c_allocator.create(); + lazy_ptr_type->ira = ira; ira_ref(ira); + result->value->data.x_lazy = &lazy_ptr_type->base; + lazy_ptr_type->base.id = is_const ? LazyValueIdPtrTypeSimpleConst : LazyValueIdPtrTypeSimple; + + lazy_ptr_type->elem_type = instruction->child_type->child; + if (ir_resolve_type_lazy(ira, lazy_ptr_type->elem_type) == nullptr) + return ira->codegen->invalid_inst_gen; + + return result; +} + static IrInstGen *ir_analyze_instruction_ptr_type(IrAnalyze *ira, IrInstSrcPtrType *instruction) { IrInstGen *result = ir_const(ira, &instruction->base.base, ira->codegen->builtin_types.entry_type); result->value->special = ConstValSpecialLazy; @@ -32384,6 +32429,10 @@ static IrInstGen *ir_analyze_instruction_base(IrAnalyze *ira, IrInstSrc *instruc return ir_analyze_instruction_set_eval_branch_quota(ira, (IrInstSrcSetEvalBranchQuota *)instruction); case IrInstSrcIdPtrType: return ir_analyze_instruction_ptr_type(ira, (IrInstSrcPtrType *)instruction); + case IrInstSrcIdPtrTypeSimple: + return ir_analyze_instruction_ptr_type_simple(ira, (IrInstSrcPtrTypeSimple *)instruction, false); + case IrInstSrcIdPtrTypeSimpleConst: + return ir_analyze_instruction_ptr_type_simple(ira, (IrInstSrcPtrTypeSimple *)instruction, true); case IrInstSrcIdAlignCast: return ir_analyze_instruction_align_cast(ira, (IrInstSrcAlignCast *)instruction); case IrInstSrcIdImplicitCast: @@ -32757,6 +32806,8 @@ bool ir_inst_src_has_side_effects(IrInstSrc *instruction) { case IrInstSrcIdPanic: case IrInstSrcIdSetEvalBranchQuota: case IrInstSrcIdPtrType: + case IrInstSrcIdPtrTypeSimple: + case IrInstSrcIdPtrTypeSimpleConst: case IrInstSrcIdSetAlignStack: case IrInstSrcIdExport: case IrInstSrcIdExtern: @@ -33264,6 +33315,54 @@ static Error ir_resolve_lazy_raw(AstNode *source_node, ZigValue *val) { // We can't free the lazy value here, because multiple other ZigValues might be pointing to it. return ErrorNone; } + case LazyValueIdPtrTypeSimple: { + LazyValuePtrTypeSimple *lazy_ptr_type = reinterpret_cast(val->data.x_lazy); + IrAnalyze *ira = lazy_ptr_type->ira; + + ZigType *elem_type = ir_resolve_type(ira, lazy_ptr_type->elem_type); + if (type_is_invalid(elem_type)) + return ErrorSemanticAnalyzeFail; + + if (elem_type->id == ZigTypeIdUnreachable) { + ir_add_error(ira, &lazy_ptr_type->elem_type->base, + buf_create_from_str("pointer to noreturn not allowed")); + return ErrorSemanticAnalyzeFail; + } + + assert(val->type->id == ZigTypeIdMetaType); + val->data.x_type = get_pointer_to_type_extra2(ira->codegen, elem_type, + false, false, PtrLenSingle, 0, + 0, 0, + false, VECTOR_INDEX_NONE, nullptr, nullptr); + val->special = ConstValSpecialStatic; + + // We can't free the lazy value here, because multiple other ZigValues might be pointing to it. + return ErrorNone; + } + case LazyValueIdPtrTypeSimpleConst: { + LazyValuePtrTypeSimple *lazy_ptr_type = reinterpret_cast(val->data.x_lazy); + IrAnalyze *ira = lazy_ptr_type->ira; + + ZigType *elem_type = ir_resolve_type(ira, lazy_ptr_type->elem_type); + if (type_is_invalid(elem_type)) + return ErrorSemanticAnalyzeFail; + + if (elem_type->id == ZigTypeIdUnreachable) { + ir_add_error(ira, &lazy_ptr_type->elem_type->base, + buf_create_from_str("pointer to noreturn not allowed")); + return ErrorSemanticAnalyzeFail; + } + + assert(val->type->id == ZigTypeIdMetaType); + val->data.x_type = get_pointer_to_type_extra2(ira->codegen, elem_type, + true, false, PtrLenSingle, 0, + 0, 0, + false, VECTOR_INDEX_NONE, nullptr, nullptr); + val->special = ConstValSpecialStatic; + + // We can't free the lazy value here, because multiple other ZigValues might be pointing to it. + return ErrorNone; + } case LazyValueIdArrayType: { LazyValueArrayType *lazy_array_type = reinterpret_cast(val->data.x_lazy); IrAnalyze *ira = lazy_array_type->ira; diff --git a/src/stage1/ir_print.cpp b/src/stage1/ir_print.cpp index cdbada4f20..22b6c1ed5f 100644 --- a/src/stage1/ir_print.cpp +++ b/src/stage1/ir_print.cpp @@ -300,6 +300,10 @@ const char* ir_inst_src_type_str(IrInstSrcId id) { return "SrcSetEvalBranchQuota"; case IrInstSrcIdPtrType: return "SrcPtrType"; + case IrInstSrcIdPtrTypeSimple: + return "SrcPtrTypeSimple"; + case IrInstSrcIdPtrTypeSimpleConst: + return "SrcPtrTypeSimpleConst"; case IrInstSrcIdAlignCast: return "SrcAlignCast"; case IrInstSrcIdImplicitCast: @@ -2245,6 +2249,15 @@ static void ir_print_ptr_type(IrPrintSrc *irp, IrInstSrcPtrType *instruction) { ir_print_other_inst_src(irp, instruction->child_type); } +static void ir_print_ptr_type_simple(IrPrintSrc *irp, IrInstSrcPtrTypeSimple *instruction, + bool is_const) +{ + fprintf(irp->f, "&"); + const char *const_str = is_const ? "const " : ""; + fprintf(irp->f, "*%s", const_str); + ir_print_other_inst_src(irp, instruction->child_type); +} + static void ir_print_decl_ref(IrPrintSrc *irp, IrInstSrcDeclRef *instruction) { const char *ptr_str = (instruction->lval != LValNone) ? "ptr " : ""; fprintf(irp->f, "declref %s%s", ptr_str, buf_ptr(instruction->tld->name)); @@ -2917,6 +2930,12 @@ static void ir_print_inst_src(IrPrintSrc *irp, IrInstSrc *instruction, bool trai case IrInstSrcIdPtrType: ir_print_ptr_type(irp, (IrInstSrcPtrType *)instruction); break; + case IrInstSrcIdPtrTypeSimple: + ir_print_ptr_type_simple(irp, (IrInstSrcPtrTypeSimple *)instruction, false); + break; + case IrInstSrcIdPtrTypeSimpleConst: + ir_print_ptr_type_simple(irp, (IrInstSrcPtrTypeSimple *)instruction, true); + break; case IrInstSrcIdDeclRef: ir_print_decl_ref(irp, (IrInstSrcDeclRef *)instruction); break; From 96ae451bbe78cd35a62e00e3fb48a32f24ebd315 Mon Sep 17 00:00:00 2001 From: Martin Wickham Date: Sat, 6 Mar 2021 18:25:04 -0600 Subject: [PATCH 56/56] Add some enum utilities --- lib/std/bit_set.zig | 28 +- lib/std/enums.zig | 1281 ++++++++++++++++++++++++++++++++++++++++ lib/std/meta/trait.zig | 78 +++ lib/std/std.zig | 4 + 4 files changed, 1384 insertions(+), 7 deletions(-) create mode 100644 lib/std/enums.zig diff --git a/lib/std/bit_set.zig b/lib/std/bit_set.zig index 29ad0d7963..80cdd5c79c 100644 --- a/lib/std/bit_set.zig +++ b/lib/std/bit_set.zig @@ -176,7 +176,7 @@ pub fn IntegerBitSet(comptime size: u16) type { /// The default options (.{}) will iterate indices of set bits in /// ascending order. Modifications to the underlying bit set may /// or may not be observed by the iterator. - pub fn iterator(self: *const Self, comptime options: IteratorOptions) Iterator(options.direction) { + pub fn iterator(self: *const Self, comptime options: IteratorOptions) Iterator(options) { return .{ .bits_remain = switch (options.kind) { .set => self.mask, @@ -185,7 +185,11 @@ pub fn IntegerBitSet(comptime size: u16) type { }; } - fn Iterator(comptime direction: IteratorOptions.Direction) type { + pub fn Iterator(comptime options: IteratorOptions) type { + return SingleWordIterator(options.direction); + } + + fn SingleWordIterator(comptime direction: IteratorOptions.Direction) type { return struct { const IterSelf = @This(); // all bits which have not yet been iterated over @@ -425,8 +429,12 @@ pub fn ArrayBitSet(comptime MaskIntType: type, comptime size: usize) type { /// The default options (.{}) will iterate indices of set bits in /// ascending order. Modifications to the underlying bit set may /// or may not be observed by the iterator. - pub fn iterator(self: *const Self, comptime options: IteratorOptions) BitSetIterator(MaskInt, options) { - return BitSetIterator(MaskInt, options).init(&self.masks, last_item_mask); + pub fn iterator(self: *const Self, comptime options: IteratorOptions) Iterator(options) { + return Iterator(options).init(&self.masks, last_item_mask); + } + + pub fn Iterator(comptime options: IteratorOptions) type { + return BitSetIterator(MaskInt, options); } fn maskBit(index: usize) MaskInt { @@ -700,11 +708,15 @@ pub const DynamicBitSetUnmanaged = struct { /// ascending order. Modifications to the underlying bit set may /// or may not be observed by the iterator. Resizing the underlying /// bit set invalidates the iterator. - pub fn iterator(self: *const Self, comptime options: IteratorOptions) BitSetIterator(MaskInt, options) { + pub fn iterator(self: *const Self, comptime options: IteratorOptions) Iterator(options) { const num_masks = numMasks(self.bit_length); const padding_bits = num_masks * @bitSizeOf(MaskInt) - self.bit_length; const last_item_mask = (~@as(MaskInt, 0)) >> @intCast(ShiftInt, padding_bits); - return BitSetIterator(MaskInt, options).init(self.masks[0..num_masks], last_item_mask); + return Iterator(options).init(self.masks[0..num_masks], last_item_mask); + } + + pub fn Iterator(comptime options: IteratorOptions) type { + return BitSetIterator(MaskInt, options); } fn maskBit(index: usize) MaskInt { @@ -858,9 +870,11 @@ pub const DynamicBitSet = struct { /// ascending order. Modifications to the underlying bit set may /// or may not be observed by the iterator. Resizing the underlying /// bit set invalidates the iterator. - pub fn iterator(self: *Self, comptime options: IteratorOptions) BitSetIterator(MaskInt, options) { + pub fn iterator(self: *Self, comptime options: IteratorOptions) Iterator(options) { return self.unmanaged.iterator(options); } + + pub const Iterator = DynamicBitSetUnmanaged.Iterator; }; /// Options for configuring an iterator over a bit set diff --git a/lib/std/enums.zig b/lib/std/enums.zig new file mode 100644 index 0000000000..bddda38c9f --- /dev/null +++ b/lib/std/enums.zig @@ -0,0 +1,1281 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! This module contains utilities and data structures for working with enums. + +const std = @import("std.zig"); +const assert = std.debug.assert; +const testing = std.testing; +const EnumField = std.builtin.TypeInfo.EnumField; + +/// Returns a struct with a field matching each unique named enum element. +/// If the enum is extern and has multiple names for the same value, only +/// the first name is used. Each field is of type Data and has the provided +/// default, which may be undefined. +pub fn EnumFieldStruct(comptime E: type, comptime Data: type, comptime field_default: ?Data) type { + const StructField = std.builtin.TypeInfo.StructField; + var fields: []const StructField = &[_]StructField{}; + for (uniqueFields(E)) |field, i| { + fields = fields ++ &[_]StructField{.{ + .name = field.name, + .field_type = Data, + .default_value = field_default, + .is_comptime = false, + .alignment = if (@sizeOf(Data) > 0) @alignOf(Data) else 0, + }}; + } + return @Type(.{ .Struct = .{ + .layout = .Auto, + .fields = fields, + .decls = &[_]std.builtin.TypeInfo.Declaration{}, + .is_tuple = false, + }}); +} + +/// Looks up the supplied fields in the given enum type. +/// Uses only the field names, field values are ignored. +/// The result array is in the same order as the input. +pub fn valuesFromFields(comptime E: type, comptime fields: []const EnumField) []const E { + comptime { + var result: [fields.len]E = undefined; + for (fields) |f, i| { + result[i] = @field(E, f.name); + } + return &result; + } +} + +test "std.enums.valuesFromFields" { + const E = extern enum { a, b, c, d = 0 }; + const fields = valuesFromFields(E, &[_]EnumField{ + .{ .name = "b", .value = undefined }, + .{ .name = "a", .value = undefined }, + .{ .name = "a", .value = undefined }, + .{ .name = "d", .value = undefined }, + }); + testing.expectEqual(E.b, fields[0]); + testing.expectEqual(E.a, fields[1]); + testing.expectEqual(E.d, fields[2]); // a == d + testing.expectEqual(E.d, fields[3]); +} + +/// Returns the set of all named values in the given enum, in +/// declaration order. +pub fn values(comptime E: type) []const E { + return comptime valuesFromFields(E, @typeInfo(E).Enum.fields); +} + +test "std.enum.values" { + const E = extern enum { a, b, c, d = 0 }; + testing.expectEqualSlices(E, &.{.a, .b, .c, .d}, values(E)); +} + +/// Returns the set of all unique named values in the given enum, in +/// declaration order. For repeated values in extern enums, only the +/// first name for each value is included. +pub fn uniqueValues(comptime E: type) []const E { + return comptime valuesFromFields(E, uniqueFields(E)); +} + +test "std.enum.uniqueValues" { + const E = extern enum { a, b, c, d = 0, e, f = 3 }; + testing.expectEqualSlices(E, &.{.a, .b, .c, .f}, uniqueValues(E)); + + const F = enum { a, b, c }; + testing.expectEqualSlices(F, &.{.a, .b, .c}, uniqueValues(F)); +} + +/// Returns the set of all unique field values in the given enum, in +/// declaration order. For repeated values in extern enums, only the +/// first name for each value is included. +pub fn uniqueFields(comptime E: type) []const EnumField { + comptime { + const info = @typeInfo(E).Enum; + const raw_fields = info.fields; + // Only extern enums can contain duplicates, + // so fast path other types. + if (info.layout != .Extern) { + return raw_fields; + } + + var unique_fields: []const EnumField = &[_]EnumField{}; + outer: + for (raw_fields) |candidate| { + for (unique_fields) |u| { + if (u.value == candidate.value) + continue :outer; + } + unique_fields = unique_fields ++ &[_]EnumField{candidate}; + } + + return unique_fields; + } +} + +/// Determines the length of a direct-mapped enum array, indexed by +/// @intCast(usize, @enumToInt(enum_value)). The enum must be exhaustive. +/// If the enum contains any fields with values that cannot be represented +/// by usize, a compile error is issued. The max_unused_slots parameter limits +/// the total number of items which have no matching enum key (holes in the enum +/// numbering). So for example, if an enum has values 1, 2, 5, and 6, max_unused_slots +/// must be at least 3, to allow unused slots 0, 3, and 4. +fn directEnumArrayLen(comptime E: type, comptime max_unused_slots: comptime_int) comptime_int { + const info = @typeInfo(E).Enum; + if (!info.is_exhaustive) { + @compileError("Cannot create direct array of non-exhaustive enum "++@typeName(E)); + } + + var max_value: comptime_int = -1; + const max_usize: comptime_int = ~@as(usize, 0); + const fields = uniqueFields(E); + for (fields) |f| { + if (f.value < 0) { + @compileError("Cannot create a direct enum array for "++@typeName(E)++", field ."++f.name++" has a negative value."); + } + if (f.value > max_value) { + if (f.value > max_usize) { + @compileError("Cannot create a direct enum array for "++@typeName(E)++", field ."++f.name++" is larger than the max value of usize."); + } + max_value = f.value; + } + } + + const unused_slots = max_value + 1 - fields.len; + if (unused_slots > max_unused_slots) { + const unused_str = std.fmt.comptimePrint("{d}", .{unused_slots}); + const allowed_str = std.fmt.comptimePrint("{d}", .{max_unused_slots}); + @compileError("Cannot create a direct enum array for "++@typeName(E)++". It would have "++unused_str++" unused slots, but only "++allowed_str++" are allowed."); + } + + return max_value + 1; +} + +/// Initializes an array of Data which can be indexed by +/// @intCast(usize, @enumToInt(enum_value)). The enum must be exhaustive. +/// If the enum contains any fields with values that cannot be represented +/// by usize, a compile error is issued. The max_unused_slots parameter limits +/// the total number of items which have no matching enum key (holes in the enum +/// numbering). So for example, if an enum has values 1, 2, 5, and 6, max_unused_slots +/// must be at least 3, to allow unused slots 0, 3, and 4. +/// The init_values parameter must be a struct with field names that match the enum values. +/// If the enum has multiple fields with the same value, the name of the first one must +/// be used. +pub fn directEnumArray( + comptime E: type, + comptime Data: type, + comptime max_unused_slots: comptime_int, + init_values: EnumFieldStruct(E, Data, null), +) [directEnumArrayLen(E, max_unused_slots)]Data { + return directEnumArrayDefault(E, Data, null, max_unused_slots, init_values); +} + +test "std.enums.directEnumArray" { + const E = enum(i4) { a = 4, b = 6, c = 2 }; + var runtime_false: bool = false; + const array = directEnumArray(E, bool, 4, .{ + .a = true, + .b = runtime_false, + .c = true, + }); + + testing.expectEqual([7]bool, @TypeOf(array)); + testing.expectEqual(true, array[4]); + testing.expectEqual(false, array[6]); + testing.expectEqual(true, array[2]); +} + +/// Initializes an array of Data which can be indexed by +/// @intCast(usize, @enumToInt(enum_value)). The enum must be exhaustive. +/// If the enum contains any fields with values that cannot be represented +/// by usize, a compile error is issued. The max_unused_slots parameter limits +/// the total number of items which have no matching enum key (holes in the enum +/// numbering). So for example, if an enum has values 1, 2, 5, and 6, max_unused_slots +/// must be at least 3, to allow unused slots 0, 3, and 4. +/// The init_values parameter must be a struct with field names that match the enum values. +/// If the enum has multiple fields with the same value, the name of the first one must +/// be used. +pub fn directEnumArrayDefault( + comptime E: type, + comptime Data: type, + comptime default: ?Data, + comptime max_unused_slots: comptime_int, + init_values: EnumFieldStruct(E, Data, default), +) [directEnumArrayLen(E, max_unused_slots)]Data { + const len = comptime directEnumArrayLen(E, max_unused_slots); + var result: [len]Data = if (default) |d| [_]Data{d} ** len else undefined; + inline for (@typeInfo(@TypeOf(init_values)).Struct.fields) |f, i| { + const enum_value = @field(E, f.name); + const index = @intCast(usize, @enumToInt(enum_value)); + result[index] = @field(init_values, f.name); + } + return result; +} + +test "std.enums.directEnumArrayDefault" { + const E = enum(i4) { a = 4, b = 6, c = 2 }; + var runtime_false: bool = false; + const array = directEnumArrayDefault(E, bool, false, 4, .{ + .a = true, + .b = runtime_false, + }); + + testing.expectEqual([7]bool, @TypeOf(array)); + testing.expectEqual(true, array[4]); + testing.expectEqual(false, array[6]); + testing.expectEqual(false, array[2]); +} + +/// Cast an enum literal, value, or string to the enum value of type E +/// with the same name. +pub fn nameCast(comptime E: type, comptime value: anytype) E { + comptime { + const V = @TypeOf(value); + if (V == E) return value; + var name: ?[]const u8 = switch (@typeInfo(V)) { + .EnumLiteral, .Enum => @tagName(value), + .Pointer => if (std.meta.trait.isZigString(V)) value else null, + else => null, + }; + if (name) |n| { + if (@hasField(E, n)) { + return @field(E, n); + } + @compileError("Enum "++@typeName(E)++" has no field named "++n); + } + @compileError("Cannot cast from "++@typeName(@TypeOf(value))++" to "++@typeName(E)); + } +} + +test "std.enums.nameCast" { + const A = enum { a = 0, b = 1 }; + const B = enum { a = 1, b = 0 }; + testing.expectEqual(A.a, nameCast(A, .a)); + testing.expectEqual(A.a, nameCast(A, A.a)); + testing.expectEqual(A.a, nameCast(A, B.a)); + testing.expectEqual(A.a, nameCast(A, "a")); + testing.expectEqual(A.a, nameCast(A, @as(*const[1]u8, "a"))); + testing.expectEqual(A.a, nameCast(A, @as([:0]const u8, "a"))); + testing.expectEqual(A.a, nameCast(A, @as([]const u8, "a"))); + + testing.expectEqual(B.a, nameCast(B, .a)); + testing.expectEqual(B.a, nameCast(B, A.a)); + testing.expectEqual(B.a, nameCast(B, B.a)); + testing.expectEqual(B.a, nameCast(B, "a")); + + testing.expectEqual(B.b, nameCast(B, .b)); + testing.expectEqual(B.b, nameCast(B, A.b)); + testing.expectEqual(B.b, nameCast(B, B.b)); + testing.expectEqual(B.b, nameCast(B, "b")); +} + +/// A set of enum elements, backed by a bitfield. If the enum +/// is not dense, a mapping will be constructed from enum values +/// to dense indices. This type does no dynamic allocation and +/// can be copied by value. +pub fn EnumSet(comptime E: type) type { + const mixin = struct { + fn EnumSetExt(comptime Self: type) type { + const Indexer = Self.Indexer; + return struct { + /// Initializes the set using a struct of bools + pub fn init(init_values: EnumFieldStruct(E, bool, false)) Self { + var result = Self{}; + comptime var i: usize = 0; + inline while (i < Self.len) : (i += 1) { + comptime const key = Indexer.keyForIndex(i); + comptime const tag = @tagName(key); + if (@field(init_values, tag)) { + result.bits.set(i); + } + } + return result; + } + }; + } + }; + return IndexedSet(EnumIndexer(E), mixin.EnumSetExt); +} + +/// A map keyed by an enum, backed by a bitfield and a dense array. +/// If the enum is not dense, a mapping will be constructed from +/// enum values to dense indices. This type does no dynamic +/// allocation and can be copied by value. +pub fn EnumMap(comptime E: type, comptime V: type) type { + const mixin = struct { + fn EnumMapExt(comptime Self: type) type { + const Indexer = Self.Indexer; + return struct { + /// Initializes the map using a sparse struct of optionals + pub fn init(init_values: EnumFieldStruct(E, ?V, @as(?V, null))) Self { + var result = Self{}; + comptime var i: usize = 0; + inline while (i < Self.len) : (i += 1) { + comptime const key = Indexer.keyForIndex(i); + comptime const tag = @tagName(key); + if (@field(init_values, tag)) |*v| { + result.bits.set(i); + result.values[i] = v.*; + } + } + return result; + } + /// Initializes a full mapping with all keys set to value. + /// Consider using EnumArray instead if the map will remain full. + pub fn initFull(value: V) Self { + var result = Self{ + .bits = Self.BitSet.initFull(), + .values = undefined, + }; + std.mem.set(V, &result.values, value); + return result; + } + /// Initializes a full mapping with supplied values. + /// Consider using EnumArray instead if the map will remain full. + pub fn initFullWith(init_values: EnumFieldStruct(E, V, @as(?V, null))) Self { + return initFullWithDefault(@as(?V, null), init_values); + } + /// Initializes a full mapping with a provided default. + /// Consider using EnumArray instead if the map will remain full. + pub fn initFullWithDefault(comptime default: ?V, init_values: EnumFieldStruct(E, V, default)) Self { + var result = Self{ + .bits = Self.BitSet.initFull(), + .values = undefined, + }; + comptime var i: usize = 0; + inline while (i < Self.len) : (i += 1) { + comptime const key = Indexer.keyForIndex(i); + comptime const tag = @tagName(key); + result.values[i] = @field(init_values, tag); + } + return result; + } + }; + } + }; + return IndexedMap(EnumIndexer(E), V, mixin.EnumMapExt); +} + +/// An array keyed by an enum, backed by a dense array. +/// If the enum is not dense, a mapping will be constructed from +/// enum values to dense indices. This type does no dynamic +/// allocation and can be copied by value. +pub fn EnumArray(comptime E: type, comptime V: type) type { + const mixin = struct { + fn EnumArrayExt(comptime Self: type) type { + const Indexer = Self.Indexer; + return struct { + /// Initializes all values in the enum array + pub fn init(init_values: EnumFieldStruct(E, V, @as(?V, null))) Self { + return initDefault(@as(?V, null), init_values); + } + + /// Initializes values in the enum array, with the specified default. + pub fn initDefault(comptime default: ?V, init_values: EnumFieldStruct(E, V, default)) Self { + var result = Self{ .values = undefined }; + comptime var i: usize = 0; + inline while (i < Self.len) : (i += 1) { + const key = comptime Indexer.keyForIndex(i); + const tag = @tagName(key); + result.values[i] = @field(init_values, tag); + } + return result; + } + }; + } + }; + return IndexedArray(EnumIndexer(E), V, mixin.EnumArrayExt); +} + +/// Pass this function as the Ext parameter to Indexed* if you +/// do not want to attach any extensions. This parameter was +/// originally an optional, but optional generic functions +/// seem to be broken at the moment. +/// TODO: Once #8169 is fixed, consider switching this param +/// back to an optional. +pub fn NoExtension(comptime Self: type) type { + return NoExt; +} +const NoExt = struct{}; + +/// A set type with an Indexer mapping from keys to indices. +/// Presence or absence is stored as a dense bitfield. This +/// type does no allocation and can be copied by value. +pub fn IndexedSet(comptime I: type, comptime Ext: fn(type)type) type { + comptime ensureIndexer(I); + return struct { + const Self = @This(); + + pub usingnamespace Ext(Self); + + /// The indexing rules for converting between keys and indices. + pub const Indexer = I; + /// The element type for this set. + pub const Key = Indexer.Key; + + const BitSet = std.StaticBitSet(Indexer.count); + + /// The maximum number of items in this set. + pub const len = Indexer.count; + + bits: BitSet = BitSet.initEmpty(), + + /// Returns a set containing all possible keys. + pub fn initFull() Self { + return .{ .bits = BitSet.initFull() }; + } + + /// Returns the number of keys in the set. + pub fn count(self: Self) usize { + return self.bits.count(); + } + + /// Checks if a key is in the set. + pub fn contains(self: Self, key: Key) bool { + return self.bits.isSet(Indexer.indexOf(key)); + } + + /// Puts a key in the set. + pub fn insert(self: *Self, key: Key) void { + self.bits.set(Indexer.indexOf(key)); + } + + /// Removes a key from the set. + pub fn remove(self: *Self, key: Key) void { + self.bits.unset(Indexer.indexOf(key)); + } + + /// Changes the presence of a key in the set to match the passed bool. + pub fn setPresent(self: *Self, key: Key, present: bool) void { + self.bits.setValue(Indexer.indexOf(key), present); + } + + /// Toggles the presence of a key in the set. If the key is in + /// the set, removes it. Otherwise adds it. + pub fn toggle(self: *Self, key: Key) void { + self.bits.toggle(Indexer.indexOf(key)); + } + + /// Toggles the presence of all keys in the passed set. + pub fn toggleSet(self: *Self, other: Self) void { + self.bits.toggleSet(other.bits); + } + + /// Toggles all possible keys in the set. + pub fn toggleAll(self: *Self) void { + self.bits.toggleAll(); + } + + /// Adds all keys in the passed set to this set. + pub fn setUnion(self: *Self, other: Self) void { + self.bits.setUnion(other.bits); + } + + /// Removes all keys which are not in the passed set. + pub fn setIntersection(self: *Self, other: Self) void { + self.bits.setIntersection(other.bits); + } + + /// Returns an iterator over this set, which iterates in + /// index order. Modifications to the set during iteration + /// may or may not be observed by the iterator, but will + /// not invalidate it. + pub fn iterator(self: *Self) Iterator { + return .{ .inner = self.bits.iterator(.{}) }; + } + + pub const Iterator = struct { + inner: BitSet.Iterator(.{}), + + pub fn next(self: *Iterator) ?Key { + return if (self.inner.next()) |index| + Indexer.keyForIndex(index) + else null; + } + }; + }; +} + +/// A map from keys to values, using an index lookup. Uses a +/// bitfield to track presence and a dense array of values. +/// This type does no allocation and can be copied by value. +pub fn IndexedMap(comptime I: type, comptime V: type, comptime Ext: fn(type)type) type { + comptime ensureIndexer(I); + return struct { + const Self = @This(); + + pub usingnamespace Ext(Self); + + /// The index mapping for this map + pub const Indexer = I; + /// The key type used to index this map + pub const Key = Indexer.Key; + /// The value type stored in this map + pub const Value = V; + /// The number of possible keys in the map + pub const len = Indexer.count; + + const BitSet = std.StaticBitSet(Indexer.count); + + /// Bits determining whether items are in the map + bits: BitSet = BitSet.initEmpty(), + /// Values of items in the map. If the associated + /// bit is zero, the value is undefined. + values: [Indexer.count]Value = undefined, + + /// The number of items in the map. + pub fn count(self: Self) usize { + return self.bits.count(); + } + + /// Checks if the map contains an item. + pub fn contains(self: Self, key: Key) bool { + return self.bits.isSet(Indexer.indexOf(key)); + } + + /// Gets the value associated with a key. + /// If the key is not in the map, returns null. + pub fn get(self: Self, key: Key) ?Value { + const index = Indexer.indexOf(key); + return if (self.bits.isSet(index)) self.values[index] else null; + } + + /// Gets the value associated with a key, which must + /// exist in the map. + pub fn getAssertContains(self: Self, key: Key) Value { + const index = Indexer.indexOf(key); + assert(self.bits.isSet(index)); + return self.values[index]; + } + + /// Gets the address of the value associated with a key. + /// If the key is not in the map, returns null. + pub fn getPtr(self: *Self, key: Key) ?*Value { + const index = Indexer.indexOf(key); + return if (self.bits.isSet(index)) &self.values[index] else null; + } + + /// Gets the address of the const value associated with a key. + /// If the key is not in the map, returns null. + pub fn getPtrConst(self: *const Self, key: Key) ?*const Value { + const index = Indexer.indexOf(key); + return if (self.bits.isSet(index)) &self.values[index] else null; + } + + /// Gets the address of the value associated with a key. + /// The key must be present in the map. + pub fn getPtrAssertContains(self: *Self, key: Key) *Value { + const index = Indexer.indexOf(key); + assert(self.bits.isSet(index)); + return &self.values[index]; + } + + /// Adds the key to the map with the supplied value. + /// If the key is already in the map, overwrites the value. + pub fn put(self: *Self, key: Key, value: Value) void { + const index = Indexer.indexOf(key); + self.bits.set(index); + self.values[index] = value; + } + + /// Adds the key to the map with an undefined value. + /// If the key is already in the map, the value becomes undefined. + /// A pointer to the value is returned, which should be + /// used to initialize the value. + pub fn putUninitialized(self: *Self, key: Key) *Value { + const index = Indexer.indexOf(key); + self.bits.set(index); + self.values[index] = undefined; + return &self.values[index]; + } + + /// Sets the value associated with the key in the map, + /// and returns the old value. If the key was not in + /// the map, returns null. + pub fn fetchPut(self: *Self, key: Key, value: Value) ?Value { + const index = Indexer.indexOf(key); + const result: ?Value = if (self.bits.isSet(index)) self.values[index] else null; + self.bits.set(index); + self.values[index] = value; + return result; + } + + /// Removes a key from the map. If the key was not in the map, + /// does nothing. + pub fn remove(self: *Self, key: Key) void { + const index = Indexer.indexOf(key); + self.bits.unset(index); + self.values[index] = undefined; + } + + /// Removes a key from the map, and returns the old value. + /// If the key was not in the map, returns null. + pub fn fetchRemove(self: *Self, key: Key) ?Value { + const index = Indexer.indexOf(key); + const result: ?Value = if (self.bits.isSet(index)) self.values[index] else null; + self.bits.unset(index); + self.values[index] = undefined; + return result; + } + + /// Returns an iterator over the map, which visits items in index order. + /// Modifications to the underlying map may or may not be observed by + /// the iterator, but will not invalidate it. + pub fn iterator(self: *Self) Iterator { + return .{ + .inner = self.bits.iterator(.{}), + .values = &self.values, + }; + } + + /// An entry in the map. + pub const Entry = struct { + /// The key associated with this entry. + /// Modifying this key will not change the map. + key: Key, + + /// A pointer to the value in the map associated + /// with this key. Modifications through this + /// pointer will modify the underlying data. + value: *Value, + }; + + pub const Iterator = struct { + inner: BitSet.Iterator(.{}), + values: *[Indexer.count]Value, + + pub fn next(self: *Iterator) ?Entry { + return if (self.inner.next()) |index| + Entry{ + .key = Indexer.keyForIndex(index), + .value = &self.values[index], + } + else null; + } + }; + }; +} + +/// A dense array of values, using an indexed lookup. +/// This type does no allocation and can be copied by value. +pub fn IndexedArray(comptime I: type, comptime V: type, comptime Ext: fn(type)type) type { + comptime ensureIndexer(I); + return struct { + const Self = @This(); + + pub usingnamespace Ext(Self); + + /// The index mapping for this map + pub const Indexer = I; + /// The key type used to index this map + pub const Key = Indexer.Key; + /// The value type stored in this map + pub const Value = V; + /// The number of possible keys in the map + pub const len = Indexer.count; + + values: [Indexer.count]Value, + + pub fn initUndefined() Self { + return Self{ .values = undefined }; + } + + pub fn initFill(v: Value) Self { + var self: Self = undefined; + std.mem.set(Value, &self.values, v); + return self; + } + + /// Returns the value in the array associated with a key. + pub fn get(self: Self, key: Key) Value { + return self.values[Indexer.indexOf(key)]; + } + + /// Returns a pointer to the slot in the array associated with a key. + pub fn getPtr(self: *Self, key: Key) *Value { + return &self.values[Indexer.indexOf(key)]; + } + + /// Returns a const pointer to the slot in the array associated with a key. + pub fn getPtrConst(self: *const Self, key: Key) *const Value { + return &self.values[Indexer.indexOf(key)]; + } + + /// Sets the value in the slot associated with a key. + pub fn set(self: *Self, key: Key, value: Value) void { + self.values[Indexer.indexOf(key)] = value; + } + + /// Iterates over the items in the array, in index order. + pub fn iterator(self: *Self) Iterator { + return .{ + .values = &self.values, + }; + } + + /// An entry in the array. + pub const Entry = struct { + /// The key associated with this entry. + /// Modifying this key will not change the array. + key: Key, + + /// A pointer to the value in the array associated + /// with this key. Modifications through this + /// pointer will modify the underlying data. + value: *Value, + }; + + pub const Iterator = struct { + index: usize = 0, + values: *[Indexer.count]Value, + + pub fn next(self: *Iterator) ?Entry { + const index = self.index; + if (index < Indexer.count) { + self.index += 1; + return Entry{ + .key = Indexer.keyForIndex(index), + .value = &self.values[index], + }; + } + return null; + } + }; + }; +} + +/// Verifies that a type is a valid Indexer, providing a helpful +/// compile error if not. An Indexer maps a comptime known set +/// of keys to a dense set of zero-based indices. +/// The indexer interface must look like this: +/// ``` +/// struct { +/// /// The key type which this indexer converts to indices +/// pub const Key: type, +/// /// The number of indexes in the dense mapping +/// pub const count: usize, +/// /// Converts from a key to an index +/// pub fn indexOf(Key) usize; +/// /// Converts from an index to a key +/// pub fn keyForIndex(usize) Key; +/// } +/// ``` +pub fn ensureIndexer(comptime T: type) void { + comptime { + if (!@hasDecl(T, "Key")) @compileError("Indexer must have decl Key: type."); + if (@TypeOf(T.Key) != type) @compileError("Indexer.Key must be a type."); + if (!@hasDecl(T, "count")) @compileError("Indexer must have decl count: usize."); + if (@TypeOf(T.count) != usize) @compileError("Indexer.count must be a usize."); + if (!@hasDecl(T, "indexOf")) @compileError("Indexer.indexOf must be a fn(Key)usize."); + if (@TypeOf(T.indexOf) != fn(T.Key)usize) @compileError("Indexer must have decl indexOf: fn(Key)usize."); + if (!@hasDecl(T, "keyForIndex")) @compileError("Indexer must have decl keyForIndex: fn(usize)Key."); + if (@TypeOf(T.keyForIndex) != fn(usize)T.Key) @compileError("Indexer.keyForIndex must be a fn(usize)Key."); + } +} + +test "std.enums.ensureIndexer" { + ensureIndexer(struct { + pub const Key = u32; + pub const count: usize = 8; + pub fn indexOf(k: Key) usize { + return @intCast(usize, k); + } + pub fn keyForIndex(index: usize) Key { + return @intCast(Key, index); + } + }); +} + +fn ascByValue(ctx: void, comptime a: EnumField, comptime b: EnumField) bool { + return a.value < b.value; +} +pub fn EnumIndexer(comptime E: type) type { + if (!@typeInfo(E).Enum.is_exhaustive) { + @compileError("Cannot create an enum indexer for a non-exhaustive enum."); + } + + const const_fields = uniqueFields(E); + var fields = const_fields[0..const_fields.len].*; + if (fields.len == 0) { + return struct { + pub const Key = E; + pub const count: usize = 0; + pub fn indexOf(e: E) usize { unreachable; } + pub fn keyForIndex(i: usize) E { unreachable; } + }; + } + std.sort.sort(EnumField, &fields, {}, ascByValue); + const min = fields[0].value; + const max = fields[fields.len-1].value; + if (max - min == fields.len-1) { + return struct { + pub const Key = E; + pub const count = fields.len; + pub fn indexOf(e: E) usize { + return @intCast(usize, @enumToInt(e) - min); + } + pub fn keyForIndex(i: usize) E { + // TODO fix addition semantics. This calculation + // gives up some safety to avoid artificially limiting + // the range of signed enum values to max_isize. + const enum_value = if (min < 0) @bitCast(isize, i) +% min else i + min; + return @intToEnum(E, @intCast(std.meta.Tag(E), enum_value)); + } + }; + } + + const keys = valuesFromFields(E, &fields); + + return struct { + pub const Key = E; + pub const count = fields.len; + pub fn indexOf(e: E) usize { + for (keys) |k, i| { + if (k == e) return i; + } + unreachable; + } + pub fn keyForIndex(i: usize) E { + return keys[i]; + } + }; +} + +test "std.enums.EnumIndexer dense zeroed" { + const E = enum{ b = 1, a = 0, c = 2 }; + const Indexer = EnumIndexer(E); + ensureIndexer(Indexer); + testing.expectEqual(E, Indexer.Key); + testing.expectEqual(@as(usize, 3), Indexer.count); + + testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); + testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); + testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); + + testing.expectEqual(E.a, Indexer.keyForIndex(0)); + testing.expectEqual(E.b, Indexer.keyForIndex(1)); + testing.expectEqual(E.c, Indexer.keyForIndex(2)); +} + +test "std.enums.EnumIndexer dense positive" { + const E = enum(u4) { c = 6, a = 4, b = 5 }; + const Indexer = EnumIndexer(E); + ensureIndexer(Indexer); + testing.expectEqual(E, Indexer.Key); + testing.expectEqual(@as(usize, 3), Indexer.count); + + testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); + testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); + testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); + + testing.expectEqual(E.a, Indexer.keyForIndex(0)); + testing.expectEqual(E.b, Indexer.keyForIndex(1)); + testing.expectEqual(E.c, Indexer.keyForIndex(2)); +} + +test "std.enums.EnumIndexer dense negative" { + const E = enum(i4) { a = -6, c = -4, b = -5 }; + const Indexer = EnumIndexer(E); + ensureIndexer(Indexer); + testing.expectEqual(E, Indexer.Key); + testing.expectEqual(@as(usize, 3), Indexer.count); + + testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); + testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); + testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); + + testing.expectEqual(E.a, Indexer.keyForIndex(0)); + testing.expectEqual(E.b, Indexer.keyForIndex(1)); + testing.expectEqual(E.c, Indexer.keyForIndex(2)); +} + +test "std.enums.EnumIndexer sparse" { + const E = enum(i4) { a = -2, c = 6, b = 4 }; + const Indexer = EnumIndexer(E); + ensureIndexer(Indexer); + testing.expectEqual(E, Indexer.Key); + testing.expectEqual(@as(usize, 3), Indexer.count); + + testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); + testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); + testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); + + testing.expectEqual(E.a, Indexer.keyForIndex(0)); + testing.expectEqual(E.b, Indexer.keyForIndex(1)); + testing.expectEqual(E.c, Indexer.keyForIndex(2)); +} + +test "std.enums.EnumIndexer repeats" { + const E = extern enum{ a = -2, c = 6, b = 4, b2 = 4 }; + const Indexer = EnumIndexer(E); + ensureIndexer(Indexer); + testing.expectEqual(E, Indexer.Key); + testing.expectEqual(@as(usize, 3), Indexer.count); + + testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); + testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); + testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); + + testing.expectEqual(E.a, Indexer.keyForIndex(0)); + testing.expectEqual(E.b, Indexer.keyForIndex(1)); + testing.expectEqual(E.c, Indexer.keyForIndex(2)); +} + +test "std.enums.EnumSet" { + const E = extern enum { a, b, c, d, e = 0 }; + const Set = EnumSet(E); + testing.expectEqual(E, Set.Key); + testing.expectEqual(EnumIndexer(E), Set.Indexer); + testing.expectEqual(@as(usize, 4), Set.len); + + // Empty sets + const empty = Set{}; + comptime testing.expect(empty.count() == 0); + + var empty_b = Set.init(.{}); + testing.expect(empty_b.count() == 0); + + const empty_c = comptime Set.init(.{}); + comptime testing.expect(empty_c.count() == 0); + + const full = Set.initFull(); + testing.expect(full.count() == Set.len); + + const full_b = comptime Set.initFull(); + comptime testing.expect(full_b.count() == Set.len); + + testing.expectEqual(false, empty.contains(.a)); + testing.expectEqual(false, empty.contains(.b)); + testing.expectEqual(false, empty.contains(.c)); + testing.expectEqual(false, empty.contains(.d)); + testing.expectEqual(false, empty.contains(.e)); + { + var iter = empty_b.iterator(); + testing.expectEqual(@as(?E, null), iter.next()); + } + + var mut = Set.init(.{ + .a=true, .c=true, + }); + testing.expectEqual(@as(usize, 2), mut.count()); + testing.expectEqual(true, mut.contains(.a)); + testing.expectEqual(false, mut.contains(.b)); + testing.expectEqual(true, mut.contains(.c)); + testing.expectEqual(false, mut.contains(.d)); + testing.expectEqual(true, mut.contains(.e)); // aliases a + { + var it = mut.iterator(); + testing.expectEqual(@as(?E, .a), it.next()); + testing.expectEqual(@as(?E, .c), it.next()); + testing.expectEqual(@as(?E, null), it.next()); + } + + mut.toggleAll(); + testing.expectEqual(@as(usize, 2), mut.count()); + testing.expectEqual(false, mut.contains(.a)); + testing.expectEqual(true, mut.contains(.b)); + testing.expectEqual(false, mut.contains(.c)); + testing.expectEqual(true, mut.contains(.d)); + testing.expectEqual(false, mut.contains(.e)); // aliases a + { + var it = mut.iterator(); + testing.expectEqual(@as(?E, .b), it.next()); + testing.expectEqual(@as(?E, .d), it.next()); + testing.expectEqual(@as(?E, null), it.next()); + } + + mut.toggleSet(Set.init(.{ .a=true, .b=true })); + testing.expectEqual(@as(usize, 2), mut.count()); + testing.expectEqual(true, mut.contains(.a)); + testing.expectEqual(false, mut.contains(.b)); + testing.expectEqual(false, mut.contains(.c)); + testing.expectEqual(true, mut.contains(.d)); + testing.expectEqual(true, mut.contains(.e)); // aliases a + + mut.setUnion(Set.init(.{ .a=true, .b=true })); + testing.expectEqual(@as(usize, 3), mut.count()); + testing.expectEqual(true, mut.contains(.a)); + testing.expectEqual(true, mut.contains(.b)); + testing.expectEqual(false, mut.contains(.c)); + testing.expectEqual(true, mut.contains(.d)); + + mut.remove(.c); + mut.remove(.b); + testing.expectEqual(@as(usize, 2), mut.count()); + testing.expectEqual(true, mut.contains(.a)); + testing.expectEqual(false, mut.contains(.b)); + testing.expectEqual(false, mut.contains(.c)); + testing.expectEqual(true, mut.contains(.d)); + + mut.setIntersection(Set.init(.{ .a=true, .b=true })); + testing.expectEqual(@as(usize, 1), mut.count()); + testing.expectEqual(true, mut.contains(.a)); + testing.expectEqual(false, mut.contains(.b)); + testing.expectEqual(false, mut.contains(.c)); + testing.expectEqual(false, mut.contains(.d)); + + mut.insert(.a); + mut.insert(.b); + testing.expectEqual(@as(usize, 2), mut.count()); + testing.expectEqual(true, mut.contains(.a)); + testing.expectEqual(true, mut.contains(.b)); + testing.expectEqual(false, mut.contains(.c)); + testing.expectEqual(false, mut.contains(.d)); + + mut.setPresent(.a, false); + mut.toggle(.b); + mut.toggle(.c); + mut.setPresent(.d, true); + testing.expectEqual(@as(usize, 2), mut.count()); + testing.expectEqual(false, mut.contains(.a)); + testing.expectEqual(false, mut.contains(.b)); + testing.expectEqual(true, mut.contains(.c)); + testing.expectEqual(true, mut.contains(.d)); +} + +test "std.enums.EnumArray void" { + const E = extern enum { a, b, c, d, e = 0 }; + const ArrayVoid = EnumArray(E, void); + testing.expectEqual(E, ArrayVoid.Key); + testing.expectEqual(EnumIndexer(E), ArrayVoid.Indexer); + testing.expectEqual(void, ArrayVoid.Value); + testing.expectEqual(@as(usize, 4), ArrayVoid.len); + + const undef = ArrayVoid.initUndefined(); + var inst = ArrayVoid.initFill({}); + const inst2 = ArrayVoid.init(.{ .a = {}, .b = {}, .c = {}, .d = {} }); + const inst3 = ArrayVoid.initDefault({}, .{}); + + _ = inst.get(.a); + _ = inst.getPtr(.b); + _ = inst.getPtrConst(.c); + inst.set(.a, {}); + + var it = inst.iterator(); + testing.expectEqual(E.a, it.next().?.key); + testing.expectEqual(E.b, it.next().?.key); + testing.expectEqual(E.c, it.next().?.key); + testing.expectEqual(E.d, it.next().?.key); + testing.expect(it.next() == null); +} + +test "std.enums.EnumArray sized" { + const E = extern enum { a, b, c, d, e = 0 }; + const Array = EnumArray(E, usize); + testing.expectEqual(E, Array.Key); + testing.expectEqual(EnumIndexer(E), Array.Indexer); + testing.expectEqual(usize, Array.Value); + testing.expectEqual(@as(usize, 4), Array.len); + + const undef = Array.initUndefined(); + var inst = Array.initFill(5); + const inst2 = Array.init(.{ .a = 1, .b = 2, .c = 3, .d = 4 }); + const inst3 = Array.initDefault(6, .{.b = 4, .c = 2}); + + testing.expectEqual(@as(usize, 5), inst.get(.a)); + testing.expectEqual(@as(usize, 5), inst.get(.b)); + testing.expectEqual(@as(usize, 5), inst.get(.c)); + testing.expectEqual(@as(usize, 5), inst.get(.d)); + + testing.expectEqual(@as(usize, 1), inst2.get(.a)); + testing.expectEqual(@as(usize, 2), inst2.get(.b)); + testing.expectEqual(@as(usize, 3), inst2.get(.c)); + testing.expectEqual(@as(usize, 4), inst2.get(.d)); + + testing.expectEqual(@as(usize, 6), inst3.get(.a)); + testing.expectEqual(@as(usize, 4), inst3.get(.b)); + testing.expectEqual(@as(usize, 2), inst3.get(.c)); + testing.expectEqual(@as(usize, 6), inst3.get(.d)); + + testing.expectEqual(&inst.values[0], inst.getPtr(.a)); + testing.expectEqual(&inst.values[1], inst.getPtr(.b)); + testing.expectEqual(&inst.values[2], inst.getPtr(.c)); + testing.expectEqual(&inst.values[3], inst.getPtr(.d)); + + testing.expectEqual(@as(*const usize, &inst.values[0]), inst.getPtrConst(.a)); + testing.expectEqual(@as(*const usize, &inst.values[1]), inst.getPtrConst(.b)); + testing.expectEqual(@as(*const usize, &inst.values[2]), inst.getPtrConst(.c)); + testing.expectEqual(@as(*const usize, &inst.values[3]), inst.getPtrConst(.d)); + + inst.set(.c, 8); + testing.expectEqual(@as(usize, 5), inst.get(.a)); + testing.expectEqual(@as(usize, 5), inst.get(.b)); + testing.expectEqual(@as(usize, 8), inst.get(.c)); + testing.expectEqual(@as(usize, 5), inst.get(.d)); + + var it = inst.iterator(); + const Entry = Array.Entry; + testing.expectEqual(@as(?Entry, Entry{ + .key = .a, + .value = &inst.values[0], + }), it.next()); + testing.expectEqual(@as(?Entry, Entry{ + .key = .b, + .value = &inst.values[1], + }), it.next()); + testing.expectEqual(@as(?Entry, Entry{ + .key = .c, + .value = &inst.values[2], + }), it.next()); + testing.expectEqual(@as(?Entry, Entry{ + .key = .d, + .value = &inst.values[3], + }), it.next()); + testing.expectEqual(@as(?Entry, null), it.next()); +} + +test "std.enums.EnumMap void" { + const E = extern enum { a, b, c, d, e = 0 }; + const Map = EnumMap(E, void); + testing.expectEqual(E, Map.Key); + testing.expectEqual(EnumIndexer(E), Map.Indexer); + testing.expectEqual(void, Map.Value); + testing.expectEqual(@as(usize, 4), Map.len); + + const b = Map.initFull({}); + testing.expectEqual(@as(usize, 4), b.count()); + + const c = Map.initFullWith(.{ .a = {}, .b = {}, .c = {}, .d = {} }); + testing.expectEqual(@as(usize, 4), c.count()); + + const d = Map.initFullWithDefault({}, .{ .b = {} }); + testing.expectEqual(@as(usize, 4), d.count()); + + var a = Map.init(.{ .b = {}, .d = {} }); + testing.expectEqual(@as(usize, 2), a.count()); + testing.expectEqual(false, a.contains(.a)); + testing.expectEqual(true, a.contains(.b)); + testing.expectEqual(false, a.contains(.c)); + testing.expectEqual(true, a.contains(.d)); + testing.expect(a.get(.a) == null); + testing.expect(a.get(.b) != null); + testing.expect(a.get(.c) == null); + testing.expect(a.get(.d) != null); + testing.expect(a.getPtr(.a) == null); + testing.expect(a.getPtr(.b) != null); + testing.expect(a.getPtr(.c) == null); + testing.expect(a.getPtr(.d) != null); + testing.expect(a.getPtrConst(.a) == null); + testing.expect(a.getPtrConst(.b) != null); + testing.expect(a.getPtrConst(.c) == null); + testing.expect(a.getPtrConst(.d) != null); + _ = a.getPtrAssertContains(.b); + _ = a.getAssertContains(.d); + + a.put(.a, {}); + a.put(.a, {}); + a.putUninitialized(.c).* = {}; + a.putUninitialized(.c).* = {}; + + testing.expectEqual(@as(usize, 4), a.count()); + testing.expect(a.get(.a) != null); + testing.expect(a.get(.b) != null); + testing.expect(a.get(.c) != null); + testing.expect(a.get(.d) != null); + + a.remove(.a); + _ = a.fetchRemove(.c); + + var iter = a.iterator(); + const Entry = Map.Entry; + testing.expectEqual(E.b, iter.next().?.key); + testing.expectEqual(E.d, iter.next().?.key); + testing.expect(iter.next() == null); +} + +test "std.enums.EnumMap sized" { + const E = extern enum { a, b, c, d, e = 0 }; + const Map = EnumMap(E, usize); + testing.expectEqual(E, Map.Key); + testing.expectEqual(EnumIndexer(E), Map.Indexer); + testing.expectEqual(usize, Map.Value); + testing.expectEqual(@as(usize, 4), Map.len); + + const b = Map.initFull(5); + testing.expectEqual(@as(usize, 4), b.count()); + testing.expect(b.contains(.a)); + testing.expect(b.contains(.b)); + testing.expect(b.contains(.c)); + testing.expect(b.contains(.d)); + testing.expectEqual(@as(?usize, 5), b.get(.a)); + testing.expectEqual(@as(?usize, 5), b.get(.b)); + testing.expectEqual(@as(?usize, 5), b.get(.c)); + testing.expectEqual(@as(?usize, 5), b.get(.d)); + + const c = Map.initFullWith(.{ .a = 1, .b = 2, .c = 3, .d = 4 }); + testing.expectEqual(@as(usize, 4), c.count()); + testing.expect(c.contains(.a)); + testing.expect(c.contains(.b)); + testing.expect(c.contains(.c)); + testing.expect(c.contains(.d)); + testing.expectEqual(@as(?usize, 1), c.get(.a)); + testing.expectEqual(@as(?usize, 2), c.get(.b)); + testing.expectEqual(@as(?usize, 3), c.get(.c)); + testing.expectEqual(@as(?usize, 4), c.get(.d)); + + const d = Map.initFullWithDefault(6, .{ .b = 2, .c = 4 }); + testing.expectEqual(@as(usize, 4), d.count()); + testing.expect(d.contains(.a)); + testing.expect(d.contains(.b)); + testing.expect(d.contains(.c)); + testing.expect(d.contains(.d)); + testing.expectEqual(@as(?usize, 6), d.get(.a)); + testing.expectEqual(@as(?usize, 2), d.get(.b)); + testing.expectEqual(@as(?usize, 4), d.get(.c)); + testing.expectEqual(@as(?usize, 6), d.get(.d)); + + var a = Map.init(.{ .b = 2, .d = 4 }); + testing.expectEqual(@as(usize, 2), a.count()); + testing.expectEqual(false, a.contains(.a)); + testing.expectEqual(true, a.contains(.b)); + testing.expectEqual(false, a.contains(.c)); + testing.expectEqual(true, a.contains(.d)); + + testing.expectEqual(@as(?usize, null), a.get(.a)); + testing.expectEqual(@as(?usize, 2), a.get(.b)); + testing.expectEqual(@as(?usize, null), a.get(.c)); + testing.expectEqual(@as(?usize, 4), a.get(.d)); + + testing.expectEqual(@as(?*usize, null), a.getPtr(.a)); + testing.expectEqual(@as(?*usize, &a.values[1]), a.getPtr(.b)); + testing.expectEqual(@as(?*usize, null), a.getPtr(.c)); + testing.expectEqual(@as(?*usize, &a.values[3]), a.getPtr(.d)); + + testing.expectEqual(@as(?*const usize, null), a.getPtrConst(.a)); + testing.expectEqual(@as(?*const usize, &a.values[1]), a.getPtrConst(.b)); + testing.expectEqual(@as(?*const usize, null), a.getPtrConst(.c)); + testing.expectEqual(@as(?*const usize, &a.values[3]), a.getPtrConst(.d)); + + testing.expectEqual(@as(*const usize, &a.values[1]), a.getPtrAssertContains(.b)); + testing.expectEqual(@as(*const usize, &a.values[3]), a.getPtrAssertContains(.d)); + testing.expectEqual(@as(usize, 2), a.getAssertContains(.b)); + testing.expectEqual(@as(usize, 4), a.getAssertContains(.d)); + + a.put(.a, 3); + a.put(.a, 5); + a.putUninitialized(.c).* = 7; + a.putUninitialized(.c).* = 9; + + testing.expectEqual(@as(usize, 4), a.count()); + testing.expectEqual(@as(?usize, 5), a.get(.a)); + testing.expectEqual(@as(?usize, 2), a.get(.b)); + testing.expectEqual(@as(?usize, 9), a.get(.c)); + testing.expectEqual(@as(?usize, 4), a.get(.d)); + + a.remove(.a); + testing.expectEqual(@as(?usize, null), a.fetchRemove(.a)); + testing.expectEqual(@as(?usize, 9), a.fetchRemove(.c)); + a.remove(.c); + + var iter = a.iterator(); + const Entry = Map.Entry; + testing.expectEqual(@as(?Entry, Entry{ + .key = .b, .value = &a.values[1], + }), iter.next()); + testing.expectEqual(@as(?Entry, Entry{ + .key = .d, .value = &a.values[3], + }), iter.next()); + testing.expectEqual(@as(?Entry, null), iter.next()); +} diff --git a/lib/std/meta/trait.zig b/lib/std/meta/trait.zig index e67f9b9bc4..481bfe212b 100644 --- a/lib/std/meta/trait.zig +++ b/lib/std/meta/trait.zig @@ -408,6 +408,84 @@ test "std.meta.trait.isTuple" { testing.expect(isTuple(@TypeOf(t3))); } +/// Returns true if the passed type will coerce to []const u8. +/// Any of the following are considered strings: +/// ``` +/// []const u8, [:S]const u8, *const [N]u8, *const [N:S]u8, +/// []u8, [:S]u8, *[:S]u8, *[N:S]u8. +/// ``` +/// These types are not considered strings: +/// ``` +/// u8, [N]u8, [*]const u8, [*:0]const u8, +/// [*]const [N]u8, []const u16, []const i8, +/// *const u8, ?[]const u8, ?*const [N]u8. +/// ``` +pub fn isZigString(comptime T: type) bool { + comptime { + // Only pointer types can be strings, no optionals + const info = @typeInfo(T); + if (info != .Pointer) return false; + + const ptr = &info.Pointer; + // Check for CV qualifiers that would prevent coerction to []const u8 + if (ptr.is_volatile or ptr.is_allowzero) return false; + + // If it's already a slice, simple check. + if (ptr.size == .Slice) { + return ptr.child == u8; + } + + // Otherwise check if it's an array type that coerces to slice. + if (ptr.size == .One) { + const child = @typeInfo(ptr.child); + if (child == .Array) { + const arr = &child.Array; + return arr.child == u8; + } + } + + return false; + } +} + +test "std.meta.trait.isZigString" { + testing.expect(isZigString([]const u8)); + testing.expect(isZigString([]u8)); + testing.expect(isZigString([:0]const u8)); + testing.expect(isZigString([:0]u8)); + testing.expect(isZigString([:5]const u8)); + testing.expect(isZigString([:5]u8)); + testing.expect(isZigString(*const [0]u8)); + testing.expect(isZigString(*[0]u8)); + testing.expect(isZigString(*const [0:0]u8)); + testing.expect(isZigString(*[0:0]u8)); + testing.expect(isZigString(*const [0:5]u8)); + testing.expect(isZigString(*[0:5]u8)); + testing.expect(isZigString(*const [10]u8)); + testing.expect(isZigString(*[10]u8)); + testing.expect(isZigString(*const [10:0]u8)); + testing.expect(isZigString(*[10:0]u8)); + testing.expect(isZigString(*const [10:5]u8)); + testing.expect(isZigString(*[10:5]u8)); + + testing.expect(!isZigString(u8)); + testing.expect(!isZigString([4]u8)); + testing.expect(!isZigString([4:0]u8)); + testing.expect(!isZigString([*]const u8)); + testing.expect(!isZigString([*]const [4]u8)); + testing.expect(!isZigString([*c]const u8)); + testing.expect(!isZigString([*c]const [4]u8)); + testing.expect(!isZigString([*:0]const u8)); + testing.expect(!isZigString([*:0]const u8)); + testing.expect(!isZigString(*[]const u8)); + testing.expect(!isZigString(?[]const u8)); + testing.expect(!isZigString(?*const [4]u8)); + testing.expect(!isZigString([]allowzero u8)); + testing.expect(!isZigString([]volatile u8)); + testing.expect(!isZigString(*allowzero [4]u8)); + testing.expect(!isZigString(*volatile [4]u8)); +} + pub fn hasDecls(comptime T: type, comptime names: anytype) bool { inline for (names) |name| { if (!@hasDecl(T, name)) diff --git a/lib/std/std.zig b/lib/std/std.zig index a7e5bcb682..82249af157 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -20,6 +20,9 @@ pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringM pub const DynLib = @import("dynamic_library.zig").DynLib; pub const DynamicBitSet = bit_set.DynamicBitSet; pub const DynamicBitSetUnmanaged = bit_set.DynamicBitSetUnmanaged; +pub const EnumArray = enums.EnumArray; +pub const EnumMap = enums.EnumMap; +pub const EnumSet = enums.EnumSet; pub const HashMap = hash_map.HashMap; pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; pub const MultiArrayList = @import("multi_array_list.zig").MultiArrayList; @@ -54,6 +57,7 @@ pub const cstr = @import("cstr.zig"); pub const debug = @import("debug.zig"); pub const dwarf = @import("dwarf.zig"); pub const elf = @import("elf.zig"); +pub const enums = @import("enums.zig"); pub const event = @import("event.zig"); pub const fifo = @import("fifo.zig"); pub const fmt = @import("fmt.zig");