diff --git a/build.zig b/build.zig index fc97fc91bd79b2686a1d3569546dfa2de7e6f24a..0ca2e0d7d7814807f85796e39daabadcdffa5e69 100644 --- a/build.zig +++ b/build.zig @@ -489,6 +489,7 @@ pub fn build(b: *Builder) !void { toolchain_step.dependOn(tests.addCompareOutputTests(b, test_filter, modes)); toolchain_step.dependOn(tests.addStandaloneTests(b, test_filter, modes, skip_non_native, enable_macos_sdk, target)); + toolchain_step.dependOn(tests.addLinkTests(b, test_filter, modes, enable_macos_sdk)); toolchain_step.dependOn(tests.addStackTraceTests(b, test_filter, modes)); toolchain_step.dependOn(tests.addCliTests(b, test_filter, modes)); toolchain_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, modes)); diff --git a/ci/azure/macos_script b/ci/azure/macos_script index e958dc28dec2a87ae70200937fb3feed3ae58a2a..149ae9245e3a210583841b8a334bf3ed266e0830 100755 --- a/ci/azure/macos_script +++ b/ci/azure/macos_script @@ -76,6 +76,7 @@ release/bin/zig build test-run-translated-c -Denable-macos-sdk release/bin/zig build docs -Denable-macos-sdk release/bin/zig build test-fmt -Denable-macos-sdk release/bin/zig build test-cases -Denable-macos-sdk -Dsingle-threaded +release/bin/zig build test-link -Denable-macos-sdk if [ "${BUILD_REASON}" != "PullRequest" ]; then mv ../LICENSE release/ diff --git a/lib/std/build.zig b/lib/std/build.zig index ce527ff021f9fd9395fb2352b48fad203f5f8771..57304242cd6353b28d82e7ca961cc9023e9a27fa 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -24,6 +24,7 @@ pub const TranslateCStep = @import("build/TranslateCStep.zig"); pub const WriteFileStep = @import("build/WriteFileStep.zig"); pub const RunStep = @import("build/RunStep.zig"); pub const CheckFileStep = @import("build/CheckFileStep.zig"); +pub const CheckObjectStep = @import("build/CheckObjectStep.zig"); pub const InstallRawStep = @import("build/InstallRawStep.zig"); pub const OptionsStep = @import("build/OptionsStep.zig"); @@ -1582,6 +1583,9 @@ pub const LibExeObjStep = struct { /// (Darwin) Path to entitlements file entitlements: ?[]const u8 = null, + /// (Darwin) Size of the pagezero segment. + pagezero_size: ?u64 = null, + /// Position Independent Code force_pic: ?bool = null, @@ -1861,6 +1865,10 @@ pub const LibExeObjStep = struct { return run_step; } + pub fn checkObject(self: *LibExeObjStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep { + return CheckObjectStep.create(self.builder, self.getOutputSource(), obj_format); + } + pub fn setLinkerScriptPath(self: *LibExeObjStep, source: FileSource) void { self.linker_script = source.dupe(self.builder); source.addStepDependencies(&self.step); @@ -2638,6 +2646,10 @@ pub const LibExeObjStep = struct { if (self.entitlements) |entitlements| { try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements }); } + if (self.pagezero_size) |pagezero_size| { + const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{pagezero_size}); + try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size }); + } if (self.bundle_compiler_rt) |x| { if (x) { @@ -3443,6 +3455,7 @@ pub const Step = struct { write_file, run, check_file, + check_object, install_raw, options, custom, diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig new file mode 100644 index 0000000000000000000000000000000000000000..65a57f8832af8b57f6e47404792b2c647450df7b --- /dev/null +++ b/lib/std/build/CheckObjectStep.zig @@ -0,0 +1,392 @@ +const std = @import("../std.zig"); +const assert = std.debug.assert; +const build = std.build; +const fs = std.fs; +const macho = std.macho; +const mem = std.mem; +const testing = std.testing; + +const CheckObjectStep = @This(); + +const Allocator = mem.Allocator; +const Builder = build.Builder; +const Step = build.Step; + +pub const base_id = .check_obj; + +step: Step, +builder: *Builder, +source: build.FileSource, +max_bytes: usize = 20 * 1024 * 1024, +checks: std.ArrayList(Check), +dump_symtab: bool = false, +obj_format: std.Target.ObjectFormat, + +pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Target.ObjectFormat) *CheckObjectStep { + const gpa = builder.allocator; + const self = gpa.create(CheckObjectStep) catch unreachable; + self.* = .{ + .builder = builder, + .step = Step.init(.check_file, "CheckObject", gpa, make), + .source = source.dupe(builder), + .checks = std.ArrayList(Check).init(gpa), + .obj_format = obj_format, + }; + self.source.addStepDependencies(&self.step); + return self; +} + +const Action = union(enum) { + match: MatchAction, + compute_eq: ComputeEqAction, +}; + +/// MatchAction is the main building block of standard matchers with optional eat-all token `{*}` +/// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature +/// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use +/// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc. +/// it should be plenty useful in its current form. +const MatchAction = struct { + needle: []const u8, + + /// Will return true if the `needle` was found in the `haystack`. + /// Some examples include: + /// + /// LC 0 => will match in its entirety + /// vmaddr {vmaddr} => will match `vmaddr` and then extract the following value as u64 + /// and save under `vmaddr` global name (see `global_vars` param) + /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib` + /// in that order with other letters in between + fn match(act: MatchAction, haystack: []const u8, global_vars: anytype) !bool { + var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " "); + var needle_it = mem.tokenize(u8, mem.trim(u8, act.needle, " "), " "); + + while (needle_it.next()) |needle_tok| { + const hay_tok = hay_it.next() orelse return false; + + if (mem.indexOf(u8, needle_tok, "{*}")) |index| { + // We have fuzzy matchers within the search pattern, so we match substrings. + var start = index; + var n_tok = needle_tok; + var h_tok = hay_tok; + while (true) { + n_tok = n_tok[start + 3 ..]; + const inner = if (mem.indexOf(u8, n_tok, "{*}")) |sub_end| + n_tok[0..sub_end] + else + n_tok; + if (mem.indexOf(u8, h_tok, inner) == null) return false; + start = mem.indexOf(u8, n_tok, "{*}") orelse break; + } + } else if (mem.startsWith(u8, needle_tok, "{")) { + const closing_brace = mem.indexOf(u8, needle_tok, "}") orelse return error.MissingClosingBrace; + if (closing_brace != needle_tok.len - 1) return error.ClosingBraceNotLast; + + const name = needle_tok[1..closing_brace]; + if (name.len == 0) return error.MissingBraceValue; + const value = try std.fmt.parseInt(u64, hay_tok, 16); + try global_vars.putNoClobber(name, value); + } else { + if (!mem.eql(u8, hay_tok, needle_tok)) return false; + } + } + + return true; + } +}; + +/// ComputeEqAction can be used to perform an operation on the extracted global variables +/// using the MatchAction. It currently only supports an addition. The operation is required +/// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well, +/// to avoid any parsing really). +/// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively +/// they could then be added with this simple program `vmaddr entryoff +`. +const ComputeEqAction = struct { + expected: []const u8, + var_stack: std.ArrayList([]const u8), + op_stack: std.ArrayList(Op), + + const Op = enum { + add, + }; +}; + +const Check = struct { + builder: *Builder, + actions: std.ArrayList(Action), + + fn create(b: *Builder) Check { + return .{ + .builder = b, + .actions = std.ArrayList(Action).init(b.allocator), + }; + } + + fn match(self: *Check, needle: []const u8) void { + self.actions.append(.{ + .match = .{ .needle = self.builder.dupe(needle) }, + }) catch unreachable; + } + + fn computeEq(self: *Check, act: ComputeEqAction) void { + self.actions.append(.{ + .compute_eq = act, + }) catch unreachable; + } +}; + +/// Creates a new sequence of actions with `phrase` as the first anchor searched phrase. +pub fn checkStart(self: *CheckObjectStep, phrase: []const u8) void { + var new_check = Check.create(self.builder); + new_check.match(phrase); + self.checks.append(new_check) catch unreachable; +} + +/// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)`. +/// Asserts at least one check already exists. +pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { + assert(self.checks.items.len > 0); + const last = &self.checks.items[self.checks.items.len - 1]; + last.match(phrase); +} + +/// Creates a new check checking specifically symbol table parsed and dumped from the object +/// file. +/// Issuing this check will force parsing and dumping of the symbol table. +pub fn checkInSymtab(self: *CheckObjectStep) void { + self.dump_symtab = true; + const symtab_label = switch (self.obj_format) { + .macho => MachODumper.symtab_label, + else => @panic("TODO other parsers"), + }; + self.checkStart(symtab_label); +} + +/// Creates a new standalone, singular check which allows running simple binary operations +/// on the extracted variables. It will then compare the reduced program with the value of +/// the expected variable. +pub fn checkComputeEq(self: *CheckObjectStep, program: []const u8, expected: []const u8) void { + const gpa = self.builder.allocator; + var ca = ComputeEqAction{ + .expected = expected, + .var_stack = std.ArrayList([]const u8).init(gpa), + .op_stack = std.ArrayList(ComputeEqAction.Op).init(gpa), + }; + + var it = mem.tokenize(u8, program, " "); + while (it.next()) |next| { + if (mem.eql(u8, next, "+")) { + ca.op_stack.append(.add) catch unreachable; + } else { + ca.var_stack.append(self.builder.dupe(next)) catch unreachable; + } + } + + var new_check = Check.create(self.builder); + new_check.computeEq(ca); + self.checks.append(new_check) catch unreachable; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(CheckObjectStep, "step", step); + + const gpa = self.builder.allocator; + const src_path = self.source.getPath(self.builder); + const contents = try fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); + + const output = switch (self.obj_format) { + .macho => try MachODumper.parseAndDump(contents, .{ + .gpa = gpa, + .dump_symtab = self.dump_symtab, + }), + .elf => @panic("TODO elf parser"), + .coff => @panic("TODO coff parser"), + .wasm => @panic("TODO wasm parser"), + else => unreachable, + }; + + var vars = std.StringHashMap(u64).init(gpa); + + for (self.checks.items) |chk| { + var it = mem.tokenize(u8, output, "\r\n"); + for (chk.actions.items) |act| { + switch (act) { + .match => |match_act| { + while (it.next()) |line| { + if (try match_act.match(line, &vars)) break; + } else { + std.debug.print( + \\ + \\========= Expected to find: ========================== + \\{s} + \\========= But parsed file does not contain it: ======= + \\{s} + \\ + , .{ match_act.needle, output }); + return error.TestFailed; + } + }, + .compute_eq => |c_eq| { + var values = std.ArrayList(u64).init(gpa); + try values.ensureTotalCapacity(c_eq.var_stack.items.len); + for (c_eq.var_stack.items) |vv| { + const val = vars.get(vv) orelse { + std.debug.print( + \\ + \\========= Variable was not extracted: =========== + \\{s} + \\========= From parsed file: ===================== + \\{s} + \\ + , .{ vv, output }); + return error.TestFailed; + }; + values.appendAssumeCapacity(val); + } + + var op_i: usize = 1; + var reduced: u64 = values.items[0]; + for (c_eq.op_stack.items) |op| { + const other = values.items[op_i]; + switch (op) { + .add => { + reduced += other; + }, + } + } + + const expected = vars.get(c_eq.expected) orelse { + std.debug.print( + \\ + \\========= Variable was not extracted: =========== + \\{s} + \\========= From parsed file: ===================== + \\{s} + \\ + , .{ c_eq.expected, output }); + return error.TestFailed; + }; + try testing.expectEqual(reduced, expected); + }, + } + } + } +} + +const Opts = struct { + gpa: ?Allocator = null, + dump_symtab: bool = false, +}; + +const MachODumper = struct { + const symtab_label = "symtab"; + + fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 { + const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator + var stream = std.io.fixedBufferStream(bytes); + const reader = stream.reader(); + + const hdr = try reader.readStruct(macho.mach_header_64); + if (hdr.magic != macho.MH_MAGIC_64) { + return error.InvalidMagicNumber; + } + + var output = std.ArrayList(u8).init(gpa); + const writer = output.writer(); + + var symtab_cmd: ?macho.symtab_command = null; + var i: u16 = 0; + while (i < hdr.ncmds) : (i += 1) { + var cmd = try macho.LoadCommand.read(gpa, reader); + + if (opts.dump_symtab and cmd.cmd() == .SYMTAB) { + symtab_cmd = cmd.symtab; + } + + try dumpLoadCommand(cmd, i, writer); + try writer.writeByte('\n'); + } + + if (symtab_cmd) |cmd| { + try writer.writeAll(symtab_label ++ "\n"); + const strtab = bytes[cmd.stroff..][0..cmd.strsize]; + const raw_symtab = bytes[cmd.symoff..][0 .. cmd.nsyms * @sizeOf(macho.nlist_64)]; + const symtab = mem.bytesAsSlice(macho.nlist_64, raw_symtab); + + for (symtab) |sym| { + if (sym.stab()) continue; + const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); + try writer.print("{s} {x}\n", .{ sym_name, sym.n_value }); + } + } + + return output.toOwnedSlice(); + } + + fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void { + // print header first + try writer.print( + \\LC {d} + \\cmd {s} + \\cmdsize {d} + , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); + + switch (lc.cmd()) { + .SEGMENT_64 => { + // TODO dump section headers + const seg = lc.segment.inner; + try writer.writeByte('\n'); + try writer.print( + \\segname {s} + \\vmaddr {x} + \\vmsize {x} + \\fileoff {x} + \\filesz {x} + , .{ + seg.segName(), + seg.vmaddr, + seg.vmsize, + seg.fileoff, + seg.filesize, + }); + }, + + .ID_DYLIB, + .LOAD_DYLIB, + => { + const dylib = lc.dylib.inner.dylib; + try writer.writeByte('\n'); + try writer.print( + \\name {s} + \\timestamp {d} + \\current version {x} + \\compatibility version {x} + , .{ + mem.sliceTo(lc.dylib.data, 0), + dylib.timestamp, + dylib.current_version, + dylib.compatibility_version, + }); + }, + + .MAIN => { + try writer.writeByte('\n'); + try writer.print( + \\entryoff {x} + \\stacksize {x} + , .{ lc.main.entryoff, lc.main.stacksize }); + }, + + .RPATH => { + try writer.writeByte('\n'); + try writer.print( + \\path {s} + , .{ + mem.sliceTo(lc.rpath.data, 0), + }); + }, + + else => {}, + } + } +}; diff --git a/lib/std/build/RunStep.zig b/lib/std/build/RunStep.zig index e8cf87a441ad2c12b1a443573cb666b7941e4b2e..0b8c233bfa713e0d23f22dc16d5b22a1360cb46f 100644 --- a/lib/std/build/RunStep.zig +++ b/lib/std/build/RunStep.zig @@ -149,6 +149,7 @@ fn make(step: *Step) !void { const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root; var argv_list = ArrayList([]const u8).init(self.builder.allocator); + for (self.argv.items) |arg| { switch (arg) { .bytes => |bytes| try argv_list.append(bytes), diff --git a/src/link/MachO.zig b/src/link/MachO.zig index aea44d93571e7260a7f02f099f4489512e84bdc0..a7681e976dd8aa4a78afc070d2b783618618a935 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -934,6 +934,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{pagezero_size})); } + if (self.base.options.entry) |entry| { + try argv.append("-e"); + try argv.append(entry); + } + try argv.appendSlice(positionals.items); try argv.append("-o"); @@ -3371,13 +3376,12 @@ fn addCodeSignatureLC(self: *MachO) !void { fn setEntryPoint(self: *MachO) !void { if (self.base.options.output_mode != .Exe) return; - // 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 n_strx = self.strtab_dir.getKeyAdapted(@as([]const u8, "_main"), StringIndexAdapter{ + const entry_name = self.base.options.entry orelse "_main"; + const n_strx = self.strtab_dir.getKeyAdapted(entry_name, StringIndexAdapter{ .bytes = &self.strtab, }) orelse { - log.err("'_main' export not found", .{}); + log.err("entrypoint '{s}' not found", .{entry_name}); return error.MissingMainEntrypoint; }; const resolv = self.symbol_resolver.get(n_strx) orelse unreachable; @@ -5711,28 +5715,46 @@ fn writeDyldInfoData(self: *MachO) !void { const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].dyld_info_only; + + const rebase_off = mem.alignForwardGeneric(u64, seg.inner.fileoff, @alignOf(u64)); const rebase_size = try bind.rebaseInfoSize(rebase_pointers.items); + dyld_info.rebase_off = @intCast(u32, rebase_off); + dyld_info.rebase_size = @intCast(u32, 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, + }); + + const bind_off = mem.alignForwardGeneric(u64, dyld_info.rebase_off + dyld_info.rebase_size, @alignOf(u64)); const bind_size = try bind.bindInfoSize(bind_pointers.items); + dyld_info.bind_off = @intCast(u32, bind_off); + dyld_info.bind_size = @intCast(u32, bind_size); + log.debug("writing bind info from 0x{x} to 0x{x}", .{ + dyld_info.bind_off, + dyld_info.bind_off + dyld_info.bind_size, + }); + + const lazy_bind_off = mem.alignForwardGeneric(u64, dyld_info.bind_off + dyld_info.bind_size, @alignOf(u64)); const lazy_bind_size = try bind.lazyBindInfoSize(lazy_bind_pointers.items); + dyld_info.lazy_bind_off = @intCast(u32, lazy_bind_off); + dyld_info.lazy_bind_size = @intCast(u32, lazy_bind_size); + log.debug("writing lazy bind info from 0x{x} to 0x{x}", .{ + dyld_info.lazy_bind_off, + dyld_info.lazy_bind_off + dyld_info.lazy_bind_size, + }); + + const export_off = mem.alignForwardGeneric(u64, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size, @alignOf(u64)); const export_size = trie.size; + dyld_info.export_off = @intCast(u32, export_off); + dyld_info.export_size = @intCast(u32, export_size); + log.debug("writing export trie from 0x{x} to 0x{x}", .{ + dyld_info.export_off, + dyld_info.export_off + dyld_info.export_size, + }); - dyld_info.rebase_off = @intCast(u32, seg.inner.fileoff); - dyld_info.rebase_size = @intCast(u32, mem.alignForwardGeneric(u64, rebase_size, @alignOf(u64))); - seg.inner.filesize += dyld_info.rebase_size; + seg.inner.filesize = dyld_info.export_off + dyld_info.export_size - seg.inner.fileoff; - dyld_info.bind_off = dyld_info.rebase_off + dyld_info.rebase_size; - dyld_info.bind_size = @intCast(u32, mem.alignForwardGeneric(u64, bind_size, @alignOf(u64))); - seg.inner.filesize += dyld_info.bind_size; - - dyld_info.lazy_bind_off = dyld_info.bind_off + dyld_info.bind_size; - dyld_info.lazy_bind_size = @intCast(u32, mem.alignForwardGeneric(u64, lazy_bind_size, @alignOf(u64))); - seg.inner.filesize += dyld_info.lazy_bind_size; - - dyld_info.export_off = dyld_info.lazy_bind_off + dyld_info.lazy_bind_size; - dyld_info.export_size = @intCast(u32, mem.alignForwardGeneric(u64, export_size, @alignOf(u64))); - seg.inner.filesize += dyld_info.export_size; - - const needed_size = dyld_info.rebase_size + dyld_info.bind_size + dyld_info.lazy_bind_size + dyld_info.export_size; + const needed_size = dyld_info.export_off + dyld_info.export_size - dyld_info.rebase_off; var buffer = try self.base.allocator.alloc(u8, needed_size); defer self.base.allocator.free(buffer); mem.set(u8, buffer, 0); @@ -5740,14 +5762,15 @@ fn writeDyldInfoData(self: *MachO) !void { var stream = std.io.fixedBufferStream(buffer); const writer = stream.writer(); + const base_off = dyld_info.rebase_off; try bind.writeRebaseInfo(rebase_pointers.items, writer); - try stream.seekBy(@intCast(i64, dyld_info.rebase_size) - @intCast(i64, rebase_size)); + try stream.seekTo(dyld_info.bind_off - base_off); try bind.writeBindInfo(bind_pointers.items, writer); - try stream.seekBy(@intCast(i64, dyld_info.bind_size) - @intCast(i64, bind_size)); + try stream.seekTo(dyld_info.lazy_bind_off - base_off); try bind.writeLazyBindInfo(lazy_bind_pointers.items, writer); - try stream.seekBy(@intCast(i64, dyld_info.lazy_bind_size) - @intCast(i64, lazy_bind_size)); + try stream.seekTo(dyld_info.export_off - base_off); _ = try trie.write(writer); @@ -5758,7 +5781,7 @@ fn writeDyldInfoData(self: *MachO) !void { try self.base.file.?.pwriteAll(buffer, dyld_info.rebase_off); try self.populateLazyBindOffsetsInStubHelper( - buffer[dyld_info.rebase_size + dyld_info.bind_size ..][0..dyld_info.lazy_bind_size], + buffer[dyld_info.lazy_bind_off - base_off ..][0..dyld_info.lazy_bind_size], ); self.load_commands_dirty = true; } @@ -5928,32 +5951,31 @@ fn writeFunctionStarts(self: *MachO) !void { } else break; } + var buffer = std.ArrayList(u8).init(self.base.allocator); + defer buffer.deinit(); + const max_size = @intCast(usize, offsets.items.len * @sizeOf(u64)); - var buffer = try self.base.allocator.alloc(u8, max_size); - defer self.base.allocator.free(buffer); - mem.set(u8, buffer, 0); - - var stream = std.io.fixedBufferStream(buffer); - const writer = stream.writer(); + try buffer.ensureTotalCapacity(max_size); for (offsets.items) |offset| { - try std.leb.writeULEB128(writer, offset); + try std.leb.writeULEB128(buffer.writer(), offset); } - const needed_size = @intCast(u32, mem.alignForwardGeneric(u64, stream.pos, @sizeOf(u64))); const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment; const fn_cmd = &self.load_commands.items[self.function_starts_cmd_index.?].linkedit_data; - fn_cmd.dataoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); - fn_cmd.datasize = needed_size; - seg.inner.filesize += needed_size; + const dataoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(u64)); + const datasize = buffer.items.len; + fn_cmd.dataoff = @intCast(u32, dataoff); + fn_cmd.datasize = @intCast(u32, datasize); + seg.inner.filesize = fn_cmd.dataoff + fn_cmd.datasize - seg.inner.fileoff; log.debug("writing function starts info from 0x{x} to 0x{x}", .{ fn_cmd.dataoff, fn_cmd.dataoff + fn_cmd.datasize, }); - try self.base.file.?.pwriteAll(buffer[0..needed_size], fn_cmd.dataoff); + try self.base.file.?.pwriteAll(buffer.items, fn_cmd.dataoff); self.load_commands_dirty = true; } @@ -6001,11 +6023,12 @@ fn writeDices(self: *MachO) !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.?].linkedit_data; - const needed_size = @intCast(u32, buf.items.len); - dice_cmd.dataoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); - dice_cmd.datasize = needed_size; - seg.inner.filesize += needed_size; + const dataoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(u64)); + const datasize = buf.items.len; + dice_cmd.dataoff = @intCast(u32, dataoff); + dice_cmd.datasize = @intCast(u32, datasize); + seg.inner.filesize = dice_cmd.dataoff + dice_cmd.datasize - seg.inner.fileoff; log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ dice_cmd.dataoff, @@ -6022,7 +6045,8 @@ fn writeSymbolTable(self: *MachO) !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.symoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + const symoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(macho.nlist_64)); + symtab.symoff = @intCast(u32, symoff); var locals = std.ArrayList(macho.nlist_64).init(self.base.allocator); defer locals.deinit(); @@ -6122,7 +6146,7 @@ fn writeSymbolTable(self: *MachO) !void { try self.base.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off); symtab.nsyms = @intCast(u32, nlocals + nexports + nundefs); - seg.inner.filesize += locals_size + exports_size + undefs_size; + seg.inner.filesize = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64) - seg.inner.fileoff; // Update dynamic symbol table. const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].dysymtab; @@ -6142,22 +6166,21 @@ fn writeSymbolTable(self: *MachO) !void { const nstubs = @intCast(u32, self.stubs_table.keys().len); const ngot_entries = @intCast(u32, self.got_entries_table.keys().len); - dysymtab.indirectsymoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + const indirectsymoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(u64)); + dysymtab.indirectsymoff = @intCast(u32, indirectsymoff); dysymtab.nindirectsyms = nstubs * 2 + ngot_entries; - const needed_size = dysymtab.nindirectsyms * @sizeOf(u32); - seg.inner.filesize += needed_size; + seg.inner.filesize = dysymtab.indirectsymoff + dysymtab.nindirectsyms * @sizeOf(u32) - seg.inner.fileoff; log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{ dysymtab.indirectsymoff, - dysymtab.indirectsymoff + needed_size, + dysymtab.indirectsymoff + dysymtab.nindirectsyms * @sizeOf(u32), }); - var buf = try self.base.allocator.alloc(u8, needed_size); - defer self.base.allocator.free(buf); - - var stream = std.io.fixedBufferStream(buf); - var writer = stream.writer(); + var buf = std.ArrayList(u8).init(self.base.allocator); + defer buf.deinit(); + try buf.ensureTotalCapacity(dysymtab.nindirectsyms * @sizeOf(u32)); + const writer = buf.writer(); stubs.reserved1 = 0; for (self.stubs_table.keys()) |key| { @@ -6191,7 +6214,9 @@ fn writeSymbolTable(self: *MachO) !void { } } - try self.base.file.?.pwriteAll(buf, dysymtab.indirectsymoff); + assert(buf.items.len == dysymtab.nindirectsyms * @sizeOf(u32)); + + try self.base.file.?.pwriteAll(buf.items, dysymtab.indirectsymoff); self.load_commands_dirty = true; } @@ -6201,18 +6226,16 @@ fn writeStringTable(self: *MachO) !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; + const stroff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(u64)); + const strsize = self.strtab.items.len; + symtab.stroff = @intCast(u32, stroff); + symtab.strsize = @intCast(u32, strsize); + seg.inner.filesize = symtab.stroff + symtab.strsize - seg.inner.fileoff; log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); try self.base.file.?.pwriteAll(self.strtab.items, symtab.stroff); - if (symtab.strsize > self.strtab.items.len) { - // This is potentially the last section, so we need to pad it out. - try self.base.file.?.pwriteAll(&[_]u8{0}, seg.inner.fileoff + seg.inner.filesize - 1); - } self.load_commands_dirty = true; } @@ -6236,25 +6259,22 @@ fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void { const tracy = trace(@src()); defer tracy.end(); - const linkedit_segment = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment; - const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].linkedit_data; + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment; + const cs_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].linkedit_data; // Code signature data has to be 16-bytes aligned for Apple tools to recognize the file // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271 - const fileoff = mem.alignForwardGeneric(u64, linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize, 16); - const padding = fileoff - (linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize); - const needed_size = code_sig.estimateSize(fileoff); - code_sig_cmd.dataoff = @intCast(u32, fileoff); - code_sig_cmd.datasize = needed_size; + const dataoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, 16); + const datasize = code_sig.estimateSize(dataoff); + cs_cmd.dataoff = @intCast(u32, dataoff); + cs_cmd.datasize = @intCast(u32, code_sig.estimateSize(dataoff)); // Advance size of __LINKEDIT segment - linkedit_segment.inner.filesize += needed_size + padding; - if (linkedit_segment.inner.vmsize < linkedit_segment.inner.filesize) { - linkedit_segment.inner.vmsize = mem.alignForwardGeneric(u64, linkedit_segment.inner.filesize, self.page_size); - } - log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); + seg.inner.filesize = cs_cmd.dataoff + cs_cmd.datasize - seg.inner.fileoff; + seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size); + log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ dataoff, dataoff + datasize }); // 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.base.file.?.pwriteAll(&[_]u8{0}, fileoff + needed_size - 1); + try self.base.file.?.pwriteAll(&[_]u8{0}, dataoff + datasize - 1); self.load_commands_dirty = true; } diff --git a/test/link.zig b/test/link.zig new file mode 100644 index 0000000000000000000000000000000000000000..42ce12f73c7fcce0001865b1953a942043f0a5e9 --- /dev/null +++ b/test/link.zig @@ -0,0 +1,64 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const tests = @import("tests.zig"); + +pub fn addCases(cases: *tests.StandaloneContext) void { + cases.addBuildFile("test/link/bss/build.zig", .{ + .build_modes = false, // we only guarantee zerofill for undefined in Debug + }); + + cases.addBuildFile("test/link/common_symbols/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/common_symbols_alignment/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/interdependent_static_c_libs/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/static_lib_as_system_lib/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/tls/build.zig", .{ + .build_modes = true, + }); + + if (builtin.os.tag == .macos) { + cases.addBuildFile("test/link/macho/entry/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/macho/pagezero/build.zig", .{ + .build_modes = false, + }); + + cases.addBuildFile("test/link/macho/dylib/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/macho/frameworks/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + // Try to build and run an Objective-C executable. + cases.addBuildFile("test/link/macho/objc/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + // Try to build and run an Objective-C++ executable. + cases.addBuildFile("test/link/macho/objcpp/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + cases.addBuildFile("test/link/macho/stack_size/build.zig", .{ + .build_modes = true, + }); + } +} diff --git a/test/link/bss/build.zig b/test/link/bss/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..76e9bdb305f50e5e875a63dca3fc310e206a4dd4 --- /dev/null +++ b/test/link/bss/build.zig @@ -0,0 +1,14 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + const test_step = b.step("test", "Test"); + + const exe = b.addExecutable("bss", "main.zig"); + b.default_step.dependOn(&exe.step); + exe.setBuildMode(mode); + + const run = exe.run(); + run.expectStdOutEqual("0, 1, 0\n"); + test_step.dependOn(&run.step); +} diff --git a/test/link/bss/main.zig b/test/link/bss/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..c901f0bb27363ef9e6ade0684b6cf5350662947c --- /dev/null +++ b/test/link/bss/main.zig @@ -0,0 +1,13 @@ +const std = @import("std"); + +// Stress test zerofill layout +var buffer: [0x1000000]u64 = undefined; + +pub fn main() anyerror!void { + buffer[0x10] = 1; + try std.io.getStdOut().writer().print("{d}, {d}, {d}\n", .{ + buffer[0], + buffer[0x10], + buffer[0x1000000 - 1], + }); +} diff --git a/test/link/common_symbols/a.c b/test/link/common_symbols/a.c new file mode 100644 index 0000000000000000000000000000000000000000..829a96e50725062964e97574c713a1cf783eca84 --- /dev/null +++ b/test/link/common_symbols/a.c @@ -0,0 +1,6 @@ +int i; +int j; + +int add_to_i_and_j(int x) { + return x + i + j; +} diff --git a/test/link/common_symbols/b.c b/test/link/common_symbols/b.c new file mode 100644 index 0000000000000000000000000000000000000000..18e8a8c23babf363a3da7c405cba5b6f4b8ea370 --- /dev/null +++ b/test/link/common_symbols/b.c @@ -0,0 +1,7 @@ +long i; +int j = 2; +int k; + +void incr_i() { + i++; +} diff --git a/test/link/common_symbols/build.zig b/test/link/common_symbols/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..2f9f892e86c9f69bbd12df6712194bacd724ec50 --- /dev/null +++ b/test/link/common_symbols/build.zig @@ -0,0 +1,16 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const lib_a = b.addStaticLibrary("a", null); + lib_a.addCSourceFiles(&.{ "c.c", "a.c", "b.c" }, &.{"-fcommon"}); + lib_a.setBuildMode(mode); + + const test_exe = b.addTest("main.zig"); + test_exe.setBuildMode(mode); + test_exe.linkLibrary(lib_a); + + const test_step = b.step("test", "Test it"); + test_step.dependOn(&test_exe.step); +} diff --git a/test/link/common_symbols/c.c b/test/link/common_symbols/c.c new file mode 100644 index 0000000000000000000000000000000000000000..fdf60b9ca84495d78d27e642eba94e64a3f64a54 --- /dev/null +++ b/test/link/common_symbols/c.c @@ -0,0 +1,5 @@ +extern int k; + +int common_defined_externally() { + return k; +} diff --git a/test/link/common_symbols/main.zig b/test/link/common_symbols/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..255b5aa6215612f1a169c3169f9064d0e3fe3516 --- /dev/null +++ b/test/link/common_symbols/main.zig @@ -0,0 +1,16 @@ +const std = @import("std"); +const expect = std.testing.expect; + +extern fn common_defined_externally() c_int; +extern fn incr_i() void; +extern fn add_to_i_and_j(x: c_int) c_int; + +test "undef shadows common symbol: issue #9937" { + try expect(common_defined_externally() == 0); +} + +test "import C common symbols" { + incr_i(); + const res = add_to_i_and_j(2); + try expect(res == 5); +} diff --git a/test/link/common_symbols_alignment/a.c b/test/link/common_symbols_alignment/a.c new file mode 100644 index 0000000000000000000000000000000000000000..adff9d15f3003fb9ffaf2ae4e267b8f0ba8bf509 --- /dev/null +++ b/test/link/common_symbols_alignment/a.c @@ -0,0 +1,2 @@ +int foo; +__attribute__((aligned(4096))) int bar; diff --git a/test/link/common_symbols_alignment/build.zig b/test/link/common_symbols_alignment/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..a62d86af4fdfb9cf2142336ba91240a64b8a7bdd --- /dev/null +++ b/test/link/common_symbols_alignment/build.zig @@ -0,0 +1,16 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const lib_a = b.addStaticLibrary("a", null); + lib_a.addCSourceFiles(&.{"a.c"}, &.{"-fcommon"}); + lib_a.setBuildMode(mode); + + const test_exe = b.addTest("main.zig"); + test_exe.setBuildMode(mode); + test_exe.linkLibrary(lib_a); + + const test_step = b.step("test", "Test it"); + test_step.dependOn(&test_exe.step); +} diff --git a/test/link/common_symbols_alignment/main.zig b/test/link/common_symbols_alignment/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..3d3457c76494a2a39aadc697362447124c182b28 --- /dev/null +++ b/test/link/common_symbols_alignment/main.zig @@ -0,0 +1,9 @@ +const std = @import("std"); + +extern var foo: i32; +extern var bar: i32; + +test { + try std.testing.expect(@ptrToInt(&foo) % 4 == 0); + try std.testing.expect(@ptrToInt(&bar) % 4096 == 0); +} diff --git a/test/link/interdependent_static_c_libs/a.c b/test/link/interdependent_static_c_libs/a.c new file mode 100644 index 0000000000000000000000000000000000000000..ee9da97a3a8fea8449d0147736eb3093368af7a4 --- /dev/null +++ b/test/link/interdependent_static_c_libs/a.c @@ -0,0 +1,4 @@ +#include "a.h" +int32_t add(int32_t a, int32_t b) { + return a + b; +} diff --git a/test/link/interdependent_static_c_libs/a.h b/test/link/interdependent_static_c_libs/a.h new file mode 100644 index 0000000000000000000000000000000000000000..7b45d54d5675b148ee5a6caf92848909753f0f23 --- /dev/null +++ b/test/link/interdependent_static_c_libs/a.h @@ -0,0 +1,2 @@ +#include +int32_t add(int32_t a, int32_t b); diff --git a/test/link/interdependent_static_c_libs/b.c b/test/link/interdependent_static_c_libs/b.c new file mode 100644 index 0000000000000000000000000000000000000000..ac2cc4f9372484a3ab71b7e01bf12112d1396cf0 --- /dev/null +++ b/test/link/interdependent_static_c_libs/b.c @@ -0,0 +1,6 @@ +#include "a.h" +#include "b.h" + +int32_t sub(int32_t a, int32_t b) { + return add(a, -1 * b); +} diff --git a/test/link/interdependent_static_c_libs/b.h b/test/link/interdependent_static_c_libs/b.h new file mode 100644 index 0000000000000000000000000000000000000000..6047163145bbab2fbb7ac08379d4e4f39ac0d6e1 --- /dev/null +++ b/test/link/interdependent_static_c_libs/b.h @@ -0,0 +1,2 @@ +#include +int32_t sub(int32_t a, int32_t b); diff --git a/test/link/interdependent_static_c_libs/build.zig b/test/link/interdependent_static_c_libs/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..bd1b6100daffd71d53876dcd8002866f9fac2f04 --- /dev/null +++ b/test/link/interdependent_static_c_libs/build.zig @@ -0,0 +1,24 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const lib_a = b.addStaticLibrary("a", null); + lib_a.addCSourceFile("a.c", &[_][]const u8{}); + lib_a.setBuildMode(mode); + lib_a.addIncludePath("."); + + const lib_b = b.addStaticLibrary("b", null); + lib_b.addCSourceFile("b.c", &[_][]const u8{}); + lib_b.setBuildMode(mode); + lib_b.addIncludePath("."); + + const test_exe = b.addTest("main.zig"); + test_exe.setBuildMode(mode); + test_exe.linkLibrary(lib_a); + test_exe.linkLibrary(lib_b); + test_exe.addIncludePath("."); + + const test_step = b.step("test", "Test it"); + test_step.dependOn(&test_exe.step); +} diff --git a/test/link/interdependent_static_c_libs/main.zig b/test/link/interdependent_static_c_libs/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..cb5d2e7b77d0bab90d4d2585fc5d9e9e7a54f03e --- /dev/null +++ b/test/link/interdependent_static_c_libs/main.zig @@ -0,0 +1,8 @@ +const std = @import("std"); +const expect = std.testing.expect; +const c = @cImport(@cInclude("b.h")); + +test "import C sub" { + const result = c.sub(2, 1); + try expect(result == 1); +} diff --git a/test/link/macho/dylib/a.c b/test/link/macho/dylib/a.c new file mode 100644 index 0000000000000000000000000000000000000000..199b31e1a0a213affd535ef60e80b52ce040a32a --- /dev/null +++ b/test/link/macho/dylib/a.c @@ -0,0 +1,7 @@ +#include + +char world[] = "world"; + +char* hello() { + return "Hello"; +} diff --git a/test/link/macho/dylib/build.zig b/test/link/macho/dylib/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..1587def9b8153c81ed13b469bca4c602f43e30d6 --- /dev/null +++ b/test/link/macho/dylib/build.zig @@ -0,0 +1,49 @@ +const std = @import("std"); +const Builder = std.build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test"); + test_step.dependOn(b.getInstallStep()); + + const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); + dylib.setBuildMode(mode); + dylib.addCSourceFile("a.c", &.{}); + dylib.linkLibC(); + dylib.install(); + + const check_dylib = dylib.checkObject(.macho); + check_dylib.checkStart("cmd ID_DYLIB"); + check_dylib.checkNext("name @rpath/liba.dylib"); + check_dylib.checkNext("timestamp 2"); + check_dylib.checkNext("current version 10000"); + check_dylib.checkNext("compatibility version 10000"); + + test_step.dependOn(&check_dylib.step); + + const exe = b.addExecutable("main", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkSystemLibrary("a"); + exe.linkLibC(); + exe.addLibraryPath(b.pathFromRoot("zig-out/lib/")); + exe.addRPath(b.pathFromRoot("zig-out/lib")); + + const check_exe = exe.checkObject(.macho); + check_exe.checkStart("cmd LOAD_DYLIB"); + check_exe.checkNext("name @rpath/liba.dylib"); + check_exe.checkNext("timestamp 2"); + check_exe.checkNext("current version 10000"); + check_exe.checkNext("compatibility version 10000"); + + check_exe.checkStart("cmd RPATH"); + check_exe.checkNext(std.fmt.allocPrint(b.allocator, "path {s}", .{b.pathFromRoot("zig-out/lib")}) catch unreachable); + + test_step.dependOn(&check_exe.step); + + const run = exe.run(); + run.cwd = b.pathFromRoot("."); + run.expectStdOutEqual("Hello world"); + test_step.dependOn(&run.step); +} diff --git a/test/link/macho/dylib/main.c b/test/link/macho/dylib/main.c new file mode 100644 index 0000000000000000000000000000000000000000..be1647ddad887303b79225be54523615fcc251e4 --- /dev/null +++ b/test/link/macho/dylib/main.c @@ -0,0 +1,9 @@ +#include + +char* hello(); +extern char world[]; + +int main() { + printf("%s %s", hello(), world); + return 0; +} diff --git a/test/link/macho/entry/build.zig b/test/link/macho/entry/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..f0ac084e6a383aeb81cd88c977aa5497bddb95c9 --- /dev/null +++ b/test/link/macho/entry/build.zig @@ -0,0 +1,34 @@ +const std = @import("std"); +const Builder = std.build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test"); + test_step.dependOn(b.getInstallStep()); + + const exe = b.addExecutable("main", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkLibC(); + exe.entry_symbol_name = "_non_main"; + + const check_exe = exe.checkObject(.macho); + + check_exe.checkStart("segname __TEXT"); + check_exe.checkNext("vmaddr {vmaddr}"); + + check_exe.checkStart("cmd MAIN"); + check_exe.checkNext("entryoff {entryoff}"); + + check_exe.checkInSymtab(); + check_exe.checkNext("_non_main {n_value}"); + + check_exe.checkComputeEq("vmaddr entryoff +", "n_value"); + + test_step.dependOn(&check_exe.step); + + const run = exe.run(); + run.expectStdOutEqual("42"); + test_step.dependOn(&run.step); +} diff --git a/test/link/macho/entry/main.c b/test/link/macho/entry/main.c new file mode 100644 index 0000000000000000000000000000000000000000..5fc58fc4659591c264eded51bd29ccf7c3e1ada3 --- /dev/null +++ b/test/link/macho/entry/main.c @@ -0,0 +1,6 @@ +#include + +int non_main() { + printf("%d", 42); + return 0; +} diff --git a/test/link/macho/frameworks/build.zig b/test/link/macho/frameworks/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..7086606f30e5f13c5a80f841ada970499c05c142 --- /dev/null +++ b/test/link/macho/frameworks/build.zig @@ -0,0 +1,32 @@ +const std = @import("std"); +const Builder = std.build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test the program"); + + const exe = b.addExecutable("test", null); + b.default_step.dependOn(&exe.step); + exe.addCSourceFile("main.c", &[0][]const u8{}); + exe.setBuildMode(mode); + exe.linkLibC(); + exe.linkFramework("Cocoa"); + + const check = exe.checkObject(.macho); + check.checkStart("cmd LOAD_DYLIB"); + check.checkNext("name {*}Cocoa"); + + switch (mode) { + .Debug, .ReleaseSafe => { + check.checkStart("cmd LOAD_DYLIB"); + check.checkNext("name {*}libobjc{*}.dylib"); + }, + else => {}, + } + + test_step.dependOn(&check.step); + + const run_cmd = exe.run(); + test_step.dependOn(&run_cmd.step); +} diff --git a/test/link/macho/frameworks/main.c b/test/link/macho/frameworks/main.c new file mode 100644 index 0000000000000000000000000000000000000000..b9dab990b2ffb70ec0616bf41a05c49b32f83ec5 --- /dev/null +++ b/test/link/macho/frameworks/main.c @@ -0,0 +1,7 @@ +#include +#include + +int main() { + assert(objc_getClass("NSObject") > 0); + assert(objc_getClass("NSApplication") > 0); +} diff --git a/test/link/macho/objc/Foo.h b/test/link/macho/objc/Foo.h new file mode 100644 index 0000000000000000000000000000000000000000..05cb7df39b9dc2cdb4b93a144dd5e9104f873772 --- /dev/null +++ b/test/link/macho/objc/Foo.h @@ -0,0 +1,7 @@ +#import + +@interface Foo : NSObject + +- (NSString *)name; + +@end diff --git a/test/link/macho/objc/Foo.m b/test/link/macho/objc/Foo.m new file mode 100644 index 0000000000000000000000000000000000000000..6fc9b1edf0a9b0911062ae04ad4e17a11e9fa263 --- /dev/null +++ b/test/link/macho/objc/Foo.m @@ -0,0 +1,11 @@ +#import "Foo.h" + +@implementation Foo + +- (NSString *)name +{ + NSString *str = [[NSString alloc] initWithFormat:@"Zig"]; + return str; +} + +@end diff --git a/test/link/macho/objc/build.zig b/test/link/macho/objc/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..e41fd48e7164bae47d27534b7201140c8262b857 --- /dev/null +++ b/test/link/macho/objc/build.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const Builder = std.build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test the program"); + + const exe = b.addExecutable("test", null); + b.default_step.dependOn(&exe.step); + exe.addIncludePath("."); + exe.addCSourceFile("Foo.m", &[0][]const u8{}); + exe.addCSourceFile("test.m", &[0][]const u8{}); + exe.setBuildMode(mode); + exe.linkLibC(); + // TODO when we figure out how to ship framework stubs for cross-compilation, + // populate paths to the sysroot here. + exe.linkFramework("Foundation"); + + const run_cmd = exe.run(); + test_step.dependOn(&run_cmd.step); +} diff --git a/test/link/macho/objc/test.m b/test/link/macho/objc/test.m new file mode 100644 index 0000000000000000000000000000000000000000..3c81316788d075a4eb3328b90e21a270c06257c8 --- /dev/null +++ b/test/link/macho/objc/test.m @@ -0,0 +1,12 @@ +#import "Foo.h" +#import + +int main(int argc, char *argv[]) +{ + @autoreleasepool { + Foo *foo = [[Foo alloc] init]; + NSString *result = [foo name]; + assert([result isEqualToString:@"Zig"]); + return 0; + } +} diff --git a/test/link/macho/objcpp/Foo.h b/test/link/macho/objcpp/Foo.h new file mode 100644 index 0000000000000000000000000000000000000000..05cb7df39b9dc2cdb4b93a144dd5e9104f873772 --- /dev/null +++ b/test/link/macho/objcpp/Foo.h @@ -0,0 +1,7 @@ +#import + +@interface Foo : NSObject + +- (NSString *)name; + +@end diff --git a/test/link/macho/objcpp/Foo.mm b/test/link/macho/objcpp/Foo.mm new file mode 100644 index 0000000000000000000000000000000000000000..6fc9b1edf0a9b0911062ae04ad4e17a11e9fa263 --- /dev/null +++ b/test/link/macho/objcpp/Foo.mm @@ -0,0 +1,11 @@ +#import "Foo.h" + +@implementation Foo + +- (NSString *)name +{ + NSString *str = [[NSString alloc] initWithFormat:@"Zig"]; + return str; +} + +@end diff --git a/test/link/macho/objcpp/build.zig b/test/link/macho/objcpp/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..767578e225adce2a787cb7c7bf07567ef2b2d6dc --- /dev/null +++ b/test/link/macho/objcpp/build.zig @@ -0,0 +1,24 @@ +const std = @import("std"); +const Builder = std.build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test the program"); + + const exe = b.addExecutable("test", null); + b.default_step.dependOn(&exe.step); + exe.addIncludePath("."); + exe.addCSourceFile("Foo.mm", &[0][]const u8{}); + exe.addCSourceFile("test.mm", &[0][]const u8{}); + exe.setBuildMode(mode); + exe.linkLibCpp(); + // TODO when we figure out how to ship framework stubs for cross-compilation, + // populate paths to the sysroot here. + exe.linkFramework("Foundation"); + + const run_cmd = exe.run(); + run_cmd.expectStdOutEqual("Hello from C++ and Zig"); + + test_step.dependOn(&run_cmd.step); +} diff --git a/test/link/macho/objcpp/test.mm b/test/link/macho/objcpp/test.mm new file mode 100644 index 0000000000000000000000000000000000000000..d27c543cdf351ad38903624991b3b6d9a4f5f07a --- /dev/null +++ b/test/link/macho/objcpp/test.mm @@ -0,0 +1,14 @@ +#import "Foo.h" +#import +#include + +int main(int argc, char *argv[]) +{ + @autoreleasepool { + Foo *foo = [[Foo alloc] init]; + NSString *result = [foo name]; + std::cout << "Hello from C++ and " << [result UTF8String]; + assert([result isEqualToString:@"Zig"]); + return 0; + } +} diff --git a/test/link/macho/pagezero/build.zig b/test/link/macho/pagezero/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..e858d1f4d884e6472558615099c75bcdaa1c16eb --- /dev/null +++ b/test/link/macho/pagezero/build.zig @@ -0,0 +1,43 @@ +const std = @import("std"); +const Builder = std.build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test"); + test_step.dependOn(b.getInstallStep()); + + { + const exe = b.addExecutable("pagezero", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkLibC(); + exe.pagezero_size = 0x4000; + + const check = exe.checkObject(.macho); + check.checkStart("LC 0"); + check.checkNext("segname __PAGEZERO"); + check.checkNext("vmaddr 0"); + check.checkNext("vmsize 4000"); + + check.checkStart("segname __TEXT"); + check.checkNext("vmaddr 4000"); + + test_step.dependOn(&check.step); + } + + { + const exe = b.addExecutable("no_pagezero", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkLibC(); + exe.pagezero_size = 0; + + const check = exe.checkObject(.macho); + check.checkStart("LC 0"); + check.checkNext("segname __TEXT"); + check.checkNext("vmaddr 0"); + + test_step.dependOn(&check.step); + } +} diff --git a/test/link/macho/pagezero/main.c b/test/link/macho/pagezero/main.c new file mode 100644 index 0000000000000000000000000000000000000000..ca68d24cc70460626b8cb3168417e66ddc4404ea --- /dev/null +++ b/test/link/macho/pagezero/main.c @@ -0,0 +1,3 @@ +int main(int argc, char* argv[]) { + return 0; +} diff --git a/test/link/macho/stack_size/build.zig b/test/link/macho/stack_size/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..8da59dcb53282765e7a6d2bb0d8921bdfc82b4ca --- /dev/null +++ b/test/link/macho/stack_size/build.zig @@ -0,0 +1,24 @@ +const std = @import("std"); +const Builder = std.build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test"); + test_step.dependOn(b.getInstallStep()); + + const exe = b.addExecutable("main", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkLibC(); + exe.stack_size = 0x100000000; + + const check_exe = exe.checkObject(.macho); + check_exe.checkStart("cmd MAIN"); + check_exe.checkNext("stacksize 100000000"); + + test_step.dependOn(&check_exe.step); + + const run = exe.run(); + test_step.dependOn(&run.step); +} diff --git a/test/link/macho/stack_size/main.c b/test/link/macho/stack_size/main.c new file mode 100644 index 0000000000000000000000000000000000000000..ca68d24cc70460626b8cb3168417e66ddc4404ea --- /dev/null +++ b/test/link/macho/stack_size/main.c @@ -0,0 +1,3 @@ +int main(int argc, char* argv[]) { + return 0; +} diff --git a/test/link/static_lib_as_system_lib/a.c b/test/link/static_lib_as_system_lib/a.c new file mode 100644 index 0000000000000000000000000000000000000000..ee9da97a3a8fea8449d0147736eb3093368af7a4 --- /dev/null +++ b/test/link/static_lib_as_system_lib/a.c @@ -0,0 +1,4 @@ +#include "a.h" +int32_t add(int32_t a, int32_t b) { + return a + b; +} diff --git a/test/link/static_lib_as_system_lib/a.h b/test/link/static_lib_as_system_lib/a.h new file mode 100644 index 0000000000000000000000000000000000000000..7b45d54d5675b148ee5a6caf92848909753f0f23 --- /dev/null +++ b/test/link/static_lib_as_system_lib/a.h @@ -0,0 +1,2 @@ +#include +int32_t add(int32_t a, int32_t b); diff --git a/test/link/static_lib_as_system_lib/build.zig b/test/link/static_lib_as_system_lib/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..f39f3fac2ae871ba7f229e0e652fd89b55c4cd33 --- /dev/null +++ b/test/link/static_lib_as_system_lib/build.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const Builder = std.build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const lib_a = b.addStaticLibrary("a", null); + lib_a.addCSourceFile("a.c", &[_][]const u8{}); + lib_a.setBuildMode(mode); + lib_a.addIncludePath("."); + lib_a.install(); + + const test_exe = b.addTest("main.zig"); + test_exe.setBuildMode(mode); + test_exe.linkSystemLibrary("a"); // force linking liba.a as -la + test_exe.addSystemIncludePath("."); + const search_path = std.fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "lib" }) catch unreachable; + test_exe.addLibraryPath(search_path); + + const test_step = b.step("test", "Test it"); + test_step.dependOn(b.getInstallStep()); + test_step.dependOn(&test_exe.step); +} diff --git a/test/link/static_lib_as_system_lib/main.zig b/test/link/static_lib_as_system_lib/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..0b9c46217fa05106ea8676cc33215930f69a5c19 --- /dev/null +++ b/test/link/static_lib_as_system_lib/main.zig @@ -0,0 +1,8 @@ +const std = @import("std"); +const expect = std.testing.expect; +const c = @cImport(@cInclude("a.h")); + +test "import C add" { + const result = c.add(2, 1); + try expect(result == 3); +} diff --git a/test/link/tls/a.c b/test/link/tls/a.c new file mode 100644 index 0000000000000000000000000000000000000000..8602d0241945132d6e1248398d0986f15e6255ac --- /dev/null +++ b/test/link/tls/a.c @@ -0,0 +1,5 @@ +_Thread_local int a; + +int getA() { + return a; +} diff --git a/test/link/tls/build.zig b/test/link/tls/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..ebf15ca4399a349e29c8c9c5722b70dc25774cc6 --- /dev/null +++ b/test/link/tls/build.zig @@ -0,0 +1,18 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const lib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); + lib.setBuildMode(mode); + lib.addCSourceFile("a.c", &.{}); + lib.linkLibC(); + + const test_exe = b.addTest("main.zig"); + test_exe.setBuildMode(mode); + test_exe.linkLibrary(lib); + test_exe.linkLibC(); + + const test_step = b.step("test", "Test it"); + test_step.dependOn(&test_exe.step); +} diff --git a/test/link/tls/main.zig b/test/link/tls/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..ab01616e311ee7706b688fc2008ab7b056d4b124 --- /dev/null +++ b/test/link/tls/main.zig @@ -0,0 +1,15 @@ +const std = @import("std"); + +extern threadlocal var a: i32; +extern fn getA() i32; + +fn getA2() i32 { + return a; +} + +test { + a = 2; + try std.testing.expect(getA() == 2); + try std.testing.expect(2 == getA2()); + try std.testing.expect(getA() == getA2()); +} diff --git a/test/standalone.zig b/test/standalone.zig index c34f9467d674631abad52358a80760795cbecb92..92d0ea4aa3aef7fc9401bc5584b64d761e9a12e6 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -13,31 +13,12 @@ pub fn addCases(cases: *tests.StandaloneContext) void { cases.addBuildFile("test/standalone/main_pkg_path/build.zig", .{}); cases.addBuildFile("test/standalone/shared_library/build.zig", .{}); cases.addBuildFile("test/standalone/mix_o_files/build.zig", .{}); - if (builtin.os.tag == .macos) { - // Zig's macOS linker does not yet support LTO for LLVM IR files: - // https://github.com/ziglang/zig/issues/8680 - cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{ - .build_modes = false, - .cross_targets = true, - }); - } else { - cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{ - .build_modes = true, - .cross_targets = true, - }); - } + cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{ + .build_modes = true, + .cross_targets = true, + }); cases.addBuildFile("test/standalone/global_linkage/build.zig", .{}); cases.addBuildFile("test/standalone/static_c_lib/build.zig", .{}); - cases.addBuildFile("test/standalone/link_interdependent_static_c_libs/build.zig", .{}); - cases.addBuildFile("test/standalone/link_static_lib_as_system_lib/build.zig", .{}); - cases.addBuildFile("test/standalone/link_common_symbols/build.zig", .{}); - cases.addBuildFile("test/standalone/link_frameworks/build.zig", .{ - .requires_macos_sdk = true, - }); - cases.addBuildFile("test/standalone/link_common_symbols_alignment/build.zig", .{}); - if (builtin.os.tag == .macos) { - cases.addBuildFile("test/standalone/link_import_tls_dylib/build.zig", .{}); - } cases.addBuildFile("test/standalone/issue_339/build.zig", .{}); cases.addBuildFile("test/standalone/issue_8550/build.zig", .{}); cases.addBuildFile("test/standalone/issue_794/build.zig", .{}); @@ -69,16 +50,6 @@ pub fn addCases(cases: *tests.StandaloneContext) void { if (builtin.os.tag == .linux) { cases.addBuildFile("test/standalone/pie/build.zig", .{}); } - // Try to build and run an Objective-C executable. - cases.addBuildFile("test/standalone/objc/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); - // Try to build and run an Objective-C++ executable. - cases.addBuildFile("test/standalone/objcpp/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); // Ensure the development tools are buildable. cases.add("tools/gen_spirv_spec.zig"); diff --git a/test/standalone/link_common_symbols/a.c b/test/standalone/link_common_symbols/a.c deleted file mode 100644 index 829a96e50725062964e97574c713a1cf783eca84..0000000000000000000000000000000000000000 --- a/test/standalone/link_common_symbols/a.c +++ /dev/null @@ -1,6 +0,0 @@ -int i; -int j; - -int add_to_i_and_j(int x) { - return x + i + j; -} diff --git a/test/standalone/link_common_symbols/b.c b/test/standalone/link_common_symbols/b.c deleted file mode 100644 index 18e8a8c23babf363a3da7c405cba5b6f4b8ea370..0000000000000000000000000000000000000000 --- a/test/standalone/link_common_symbols/b.c +++ /dev/null @@ -1,7 +0,0 @@ -long i; -int j = 2; -int k; - -void incr_i() { - i++; -} diff --git a/test/standalone/link_common_symbols/build.zig b/test/standalone/link_common_symbols/build.zig deleted file mode 100644 index 2f9f892e86c9f69bbd12df6712194bacd724ec50..0000000000000000000000000000000000000000 --- a/test/standalone/link_common_symbols/build.zig +++ /dev/null @@ -1,16 +0,0 @@ -const Builder = @import("std").build.Builder; - -pub fn build(b: *Builder) void { - const mode = b.standardReleaseOptions(); - - const lib_a = b.addStaticLibrary("a", null); - lib_a.addCSourceFiles(&.{ "c.c", "a.c", "b.c" }, &.{"-fcommon"}); - lib_a.setBuildMode(mode); - - const test_exe = b.addTest("main.zig"); - test_exe.setBuildMode(mode); - test_exe.linkLibrary(lib_a); - - const test_step = b.step("test", "Test it"); - test_step.dependOn(&test_exe.step); -} diff --git a/test/standalone/link_common_symbols/c.c b/test/standalone/link_common_symbols/c.c deleted file mode 100644 index fdf60b9ca84495d78d27e642eba94e64a3f64a54..0000000000000000000000000000000000000000 --- a/test/standalone/link_common_symbols/c.c +++ /dev/null @@ -1,5 +0,0 @@ -extern int k; - -int common_defined_externally() { - return k; -} diff --git a/test/standalone/link_common_symbols/main.zig b/test/standalone/link_common_symbols/main.zig deleted file mode 100644 index 255b5aa6215612f1a169c3169f9064d0e3fe3516..0000000000000000000000000000000000000000 --- a/test/standalone/link_common_symbols/main.zig +++ /dev/null @@ -1,16 +0,0 @@ -const std = @import("std"); -const expect = std.testing.expect; - -extern fn common_defined_externally() c_int; -extern fn incr_i() void; -extern fn add_to_i_and_j(x: c_int) c_int; - -test "undef shadows common symbol: issue #9937" { - try expect(common_defined_externally() == 0); -} - -test "import C common symbols" { - incr_i(); - const res = add_to_i_and_j(2); - try expect(res == 5); -} diff --git a/test/standalone/link_common_symbols_alignment/a.c b/test/standalone/link_common_symbols_alignment/a.c deleted file mode 100644 index adff9d15f3003fb9ffaf2ae4e267b8f0ba8bf509..0000000000000000000000000000000000000000 --- a/test/standalone/link_common_symbols_alignment/a.c +++ /dev/null @@ -1,2 +0,0 @@ -int foo; -__attribute__((aligned(4096))) int bar; diff --git a/test/standalone/link_common_symbols_alignment/build.zig b/test/standalone/link_common_symbols_alignment/build.zig deleted file mode 100644 index a62d86af4fdfb9cf2142336ba91240a64b8a7bdd..0000000000000000000000000000000000000000 --- a/test/standalone/link_common_symbols_alignment/build.zig +++ /dev/null @@ -1,16 +0,0 @@ -const Builder = @import("std").build.Builder; - -pub fn build(b: *Builder) void { - const mode = b.standardReleaseOptions(); - - const lib_a = b.addStaticLibrary("a", null); - lib_a.addCSourceFiles(&.{"a.c"}, &.{"-fcommon"}); - lib_a.setBuildMode(mode); - - const test_exe = b.addTest("main.zig"); - test_exe.setBuildMode(mode); - test_exe.linkLibrary(lib_a); - - const test_step = b.step("test", "Test it"); - test_step.dependOn(&test_exe.step); -} diff --git a/test/standalone/link_common_symbols_alignment/main.zig b/test/standalone/link_common_symbols_alignment/main.zig deleted file mode 100644 index 3d3457c76494a2a39aadc697362447124c182b28..0000000000000000000000000000000000000000 --- a/test/standalone/link_common_symbols_alignment/main.zig +++ /dev/null @@ -1,9 +0,0 @@ -const std = @import("std"); - -extern var foo: i32; -extern var bar: i32; - -test { - try std.testing.expect(@ptrToInt(&foo) % 4 == 0); - try std.testing.expect(@ptrToInt(&bar) % 4096 == 0); -} diff --git a/test/standalone/link_frameworks/build.zig b/test/standalone/link_frameworks/build.zig deleted file mode 100644 index 460e1675e0da83332eff6c3e451efdff3b8d2fa1..0000000000000000000000000000000000000000 --- a/test/standalone/link_frameworks/build.zig +++ /dev/null @@ -1,34 +0,0 @@ -const std = @import("std"); -const Builder = std.build.Builder; -const CrossTarget = std.zig.CrossTarget; - -fn isRunnableTarget(t: CrossTarget) bool { - // TODO I think we might be able to run this on Linux via Darling. - // Add a check for that here, and return true if Darling is available. - if (t.isNative() and t.getOsTag() == .macos) - return true - else - return false; -} - -pub fn build(b: *Builder) void { - const mode = b.standardReleaseOptions(); - const target = b.standardTargetOptions(.{}); - - const test_step = b.step("test", "Test the program"); - - const exe = b.addExecutable("test", null); - b.default_step.dependOn(&exe.step); - exe.addCSourceFile("main.c", &[0][]const u8{}); - exe.setBuildMode(mode); - exe.setTarget(target); - exe.linkLibC(); - // TODO when we figure out how to ship framework stubs for cross-compilation, - // populate paths to the sysroot here. - exe.linkFramework("Cocoa"); - - if (isRunnableTarget(target)) { - const run_cmd = exe.run(); - test_step.dependOn(&run_cmd.step); - } -} diff --git a/test/standalone/link_frameworks/main.c b/test/standalone/link_frameworks/main.c deleted file mode 100644 index b9dab990b2ffb70ec0616bf41a05c49b32f83ec5..0000000000000000000000000000000000000000 --- a/test/standalone/link_frameworks/main.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include - -int main() { - assert(objc_getClass("NSObject") > 0); - assert(objc_getClass("NSApplication") > 0); -} diff --git a/test/standalone/link_import_tls_dylib/a.c b/test/standalone/link_import_tls_dylib/a.c deleted file mode 100644 index 5c5aa5bae491cedb4a21a0f5b65931214bd6a94f..0000000000000000000000000000000000000000 --- a/test/standalone/link_import_tls_dylib/a.c +++ /dev/null @@ -1 +0,0 @@ -_Thread_local int a; diff --git a/test/standalone/link_import_tls_dylib/build.zig b/test/standalone/link_import_tls_dylib/build.zig deleted file mode 100644 index 332173fbb6ff9a6295cc80a7b08d2934d0b125b2..0000000000000000000000000000000000000000 --- a/test/standalone/link_import_tls_dylib/build.zig +++ /dev/null @@ -1,16 +0,0 @@ -const Builder = @import("std").build.Builder; - -pub fn build(b: *Builder) void { - const mode = b.standardReleaseOptions(); - - const lib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); - lib.setBuildMode(mode); - lib.addCSourceFile("a.c", &.{}); - - const test_exe = b.addTest("main.zig"); - test_exe.setBuildMode(mode); - test_exe.linkLibrary(lib); - - const test_step = b.step("test", "Test it"); - test_step.dependOn(&test_exe.step); -} diff --git a/test/standalone/link_import_tls_dylib/main.zig b/test/standalone/link_import_tls_dylib/main.zig deleted file mode 100644 index 354c6f545e3ab33e679b777dd7fff9e0f0ac7a19..0000000000000000000000000000000000000000 --- a/test/standalone/link_import_tls_dylib/main.zig +++ /dev/null @@ -1,7 +0,0 @@ -const std = @import("std"); - -extern threadlocal var a: i32; - -test { - try std.testing.expect(a == 0); -} diff --git a/test/standalone/link_interdependent_static_c_libs/a.c b/test/standalone/link_interdependent_static_c_libs/a.c deleted file mode 100644 index ee9da97a3a8fea8449d0147736eb3093368af7a4..0000000000000000000000000000000000000000 --- a/test/standalone/link_interdependent_static_c_libs/a.c +++ /dev/null @@ -1,4 +0,0 @@ -#include "a.h" -int32_t add(int32_t a, int32_t b) { - return a + b; -} diff --git a/test/standalone/link_interdependent_static_c_libs/a.h b/test/standalone/link_interdependent_static_c_libs/a.h deleted file mode 100644 index 7b45d54d5675b148ee5a6caf92848909753f0f23..0000000000000000000000000000000000000000 --- a/test/standalone/link_interdependent_static_c_libs/a.h +++ /dev/null @@ -1,2 +0,0 @@ -#include -int32_t add(int32_t a, int32_t b); diff --git a/test/standalone/link_interdependent_static_c_libs/b.c b/test/standalone/link_interdependent_static_c_libs/b.c deleted file mode 100644 index ac2cc4f9372484a3ab71b7e01bf12112d1396cf0..0000000000000000000000000000000000000000 --- a/test/standalone/link_interdependent_static_c_libs/b.c +++ /dev/null @@ -1,6 +0,0 @@ -#include "a.h" -#include "b.h" - -int32_t sub(int32_t a, int32_t b) { - return add(a, -1 * b); -} diff --git a/test/standalone/link_interdependent_static_c_libs/b.h b/test/standalone/link_interdependent_static_c_libs/b.h deleted file mode 100644 index 6047163145bbab2fbb7ac08379d4e4f39ac0d6e1..0000000000000000000000000000000000000000 --- a/test/standalone/link_interdependent_static_c_libs/b.h +++ /dev/null @@ -1,2 +0,0 @@ -#include -int32_t sub(int32_t a, int32_t b); diff --git a/test/standalone/link_interdependent_static_c_libs/build.zig b/test/standalone/link_interdependent_static_c_libs/build.zig deleted file mode 100644 index bd1b6100daffd71d53876dcd8002866f9fac2f04..0000000000000000000000000000000000000000 --- a/test/standalone/link_interdependent_static_c_libs/build.zig +++ /dev/null @@ -1,24 +0,0 @@ -const Builder = @import("std").build.Builder; - -pub fn build(b: *Builder) void { - const mode = b.standardReleaseOptions(); - - const lib_a = b.addStaticLibrary("a", null); - lib_a.addCSourceFile("a.c", &[_][]const u8{}); - lib_a.setBuildMode(mode); - lib_a.addIncludePath("."); - - const lib_b = b.addStaticLibrary("b", null); - lib_b.addCSourceFile("b.c", &[_][]const u8{}); - lib_b.setBuildMode(mode); - lib_b.addIncludePath("."); - - const test_exe = b.addTest("main.zig"); - test_exe.setBuildMode(mode); - test_exe.linkLibrary(lib_a); - test_exe.linkLibrary(lib_b); - test_exe.addIncludePath("."); - - const test_step = b.step("test", "Test it"); - test_step.dependOn(&test_exe.step); -} diff --git a/test/standalone/link_interdependent_static_c_libs/main.zig b/test/standalone/link_interdependent_static_c_libs/main.zig deleted file mode 100644 index cb5d2e7b77d0bab90d4d2585fc5d9e9e7a54f03e..0000000000000000000000000000000000000000 --- a/test/standalone/link_interdependent_static_c_libs/main.zig +++ /dev/null @@ -1,8 +0,0 @@ -const std = @import("std"); -const expect = std.testing.expect; -const c = @cImport(@cInclude("b.h")); - -test "import C sub" { - const result = c.sub(2, 1); - try expect(result == 1); -} diff --git a/test/standalone/link_static_lib_as_system_lib/a.c b/test/standalone/link_static_lib_as_system_lib/a.c deleted file mode 100644 index ee9da97a3a8fea8449d0147736eb3093368af7a4..0000000000000000000000000000000000000000 --- a/test/standalone/link_static_lib_as_system_lib/a.c +++ /dev/null @@ -1,4 +0,0 @@ -#include "a.h" -int32_t add(int32_t a, int32_t b) { - return a + b; -} diff --git a/test/standalone/link_static_lib_as_system_lib/a.h b/test/standalone/link_static_lib_as_system_lib/a.h deleted file mode 100644 index 7b45d54d5675b148ee5a6caf92848909753f0f23..0000000000000000000000000000000000000000 --- a/test/standalone/link_static_lib_as_system_lib/a.h +++ /dev/null @@ -1,2 +0,0 @@ -#include -int32_t add(int32_t a, int32_t b); diff --git a/test/standalone/link_static_lib_as_system_lib/build.zig b/test/standalone/link_static_lib_as_system_lib/build.zig deleted file mode 100644 index f39f3fac2ae871ba7f229e0e652fd89b55c4cd33..0000000000000000000000000000000000000000 --- a/test/standalone/link_static_lib_as_system_lib/build.zig +++ /dev/null @@ -1,23 +0,0 @@ -const std = @import("std"); -const Builder = std.build.Builder; - -pub fn build(b: *Builder) void { - const mode = b.standardReleaseOptions(); - - const lib_a = b.addStaticLibrary("a", null); - lib_a.addCSourceFile("a.c", &[_][]const u8{}); - lib_a.setBuildMode(mode); - lib_a.addIncludePath("."); - lib_a.install(); - - const test_exe = b.addTest("main.zig"); - test_exe.setBuildMode(mode); - test_exe.linkSystemLibrary("a"); // force linking liba.a as -la - test_exe.addSystemIncludePath("."); - const search_path = std.fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "lib" }) catch unreachable; - test_exe.addLibraryPath(search_path); - - const test_step = b.step("test", "Test it"); - test_step.dependOn(b.getInstallStep()); - test_step.dependOn(&test_exe.step); -} diff --git a/test/standalone/link_static_lib_as_system_lib/main.zig b/test/standalone/link_static_lib_as_system_lib/main.zig deleted file mode 100644 index 0b9c46217fa05106ea8676cc33215930f69a5c19..0000000000000000000000000000000000000000 --- a/test/standalone/link_static_lib_as_system_lib/main.zig +++ /dev/null @@ -1,8 +0,0 @@ -const std = @import("std"); -const expect = std.testing.expect; -const c = @cImport(@cInclude("a.h")); - -test "import C add" { - const result = c.add(2, 1); - try expect(result == 3); -} diff --git a/test/standalone/objc/Foo.h b/test/standalone/objc/Foo.h deleted file mode 100644 index 05cb7df39b9dc2cdb4b93a144dd5e9104f873772..0000000000000000000000000000000000000000 --- a/test/standalone/objc/Foo.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -@interface Foo : NSObject - -- (NSString *)name; - -@end diff --git a/test/standalone/objc/Foo.m b/test/standalone/objc/Foo.m deleted file mode 100644 index 6fc9b1edf0a9b0911062ae04ad4e17a11e9fa263..0000000000000000000000000000000000000000 --- a/test/standalone/objc/Foo.m +++ /dev/null @@ -1,11 +0,0 @@ -#import "Foo.h" - -@implementation Foo - -- (NSString *)name -{ - NSString *str = [[NSString alloc] initWithFormat:@"Zig"]; - return str; -} - -@end diff --git a/test/standalone/objc/build.zig b/test/standalone/objc/build.zig deleted file mode 100644 index 1b3a90f90fe363e22b3f2f04a02f2210150f7683..0000000000000000000000000000000000000000 --- a/test/standalone/objc/build.zig +++ /dev/null @@ -1,36 +0,0 @@ -const std = @import("std"); -const Builder = std.build.Builder; -const CrossTarget = std.zig.CrossTarget; - -fn isRunnableTarget(t: CrossTarget) bool { - // TODO I think we might be able to run this on Linux via Darling. - // Add a check for that here, and return true if Darling is available. - if (t.isNative() and t.getOsTag() == .macos) - return true - else - return false; -} - -pub fn build(b: *Builder) void { - const mode = b.standardReleaseOptions(); - const target = b.standardTargetOptions(.{}); - - const test_step = b.step("test", "Test the program"); - - const exe = b.addExecutable("test", null); - b.default_step.dependOn(&exe.step); - exe.addIncludePath("."); - exe.addCSourceFile("Foo.m", &[0][]const u8{}); - exe.addCSourceFile("test.m", &[0][]const u8{}); - exe.setBuildMode(mode); - exe.setTarget(target); - exe.linkLibC(); - // TODO when we figure out how to ship framework stubs for cross-compilation, - // populate paths to the sysroot here. - exe.linkFramework("Foundation"); - - if (isRunnableTarget(target)) { - const run_cmd = exe.run(); - test_step.dependOn(&run_cmd.step); - } -} diff --git a/test/standalone/objc/test.m b/test/standalone/objc/test.m deleted file mode 100644 index 3c81316788d075a4eb3328b90e21a270c06257c8..0000000000000000000000000000000000000000 --- a/test/standalone/objc/test.m +++ /dev/null @@ -1,12 +0,0 @@ -#import "Foo.h" -#import - -int main(int argc, char *argv[]) -{ - @autoreleasepool { - Foo *foo = [[Foo alloc] init]; - NSString *result = [foo name]; - assert([result isEqualToString:@"Zig"]); - return 0; - } -} diff --git a/test/standalone/objcpp/Foo.h b/test/standalone/objcpp/Foo.h deleted file mode 100644 index 05cb7df39b9dc2cdb4b93a144dd5e9104f873772..0000000000000000000000000000000000000000 --- a/test/standalone/objcpp/Foo.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -@interface Foo : NSObject - -- (NSString *)name; - -@end diff --git a/test/standalone/objcpp/Foo.mm b/test/standalone/objcpp/Foo.mm deleted file mode 100644 index 6fc9b1edf0a9b0911062ae04ad4e17a11e9fa263..0000000000000000000000000000000000000000 --- a/test/standalone/objcpp/Foo.mm +++ /dev/null @@ -1,11 +0,0 @@ -#import "Foo.h" - -@implementation Foo - -- (NSString *)name -{ - NSString *str = [[NSString alloc] initWithFormat:@"Zig"]; - return str; -} - -@end diff --git a/test/standalone/objcpp/build.zig b/test/standalone/objcpp/build.zig deleted file mode 100644 index 688592793afbf531382ec733d8f5fbdd88adbc6e..0000000000000000000000000000000000000000 --- a/test/standalone/objcpp/build.zig +++ /dev/null @@ -1,36 +0,0 @@ -const std = @import("std"); -const Builder = std.build.Builder; -const CrossTarget = std.zig.CrossTarget; - -fn isRunnableTarget(t: CrossTarget) bool { - // TODO I think we might be able to run this on Linux via Darling. - // Add a check for that here, and return true if Darling is available. - if (t.isNative() and t.getOsTag() == .macos) - return true - else - return false; -} - -pub fn build(b: *Builder) void { - const mode = b.standardReleaseOptions(); - const target = b.standardTargetOptions(.{}); - - const test_step = b.step("test", "Test the program"); - - const exe = b.addExecutable("test", null); - b.default_step.dependOn(&exe.step); - exe.addIncludePath("."); - exe.addCSourceFile("Foo.mm", &[0][]const u8{}); - exe.addCSourceFile("test.mm", &[0][]const u8{}); - exe.setBuildMode(mode); - exe.setTarget(target); - exe.linkLibCpp(); - // TODO when we figure out how to ship framework stubs for cross-compilation, - // populate paths to the sysroot here. - exe.linkFramework("Foundation"); - - if (isRunnableTarget(target)) { - const run_cmd = exe.run(); - test_step.dependOn(&run_cmd.step); - } -} diff --git a/test/standalone/objcpp/test.mm b/test/standalone/objcpp/test.mm deleted file mode 100644 index d27c543cdf351ad38903624991b3b6d9a4f5f07a..0000000000000000000000000000000000000000 --- a/test/standalone/objcpp/test.mm +++ /dev/null @@ -1,14 +0,0 @@ -#import "Foo.h" -#import -#include - -int main(int argc, char *argv[]) -{ - @autoreleasepool { - Foo *foo = [[Foo alloc] init]; - NSString *result = [foo name]; - std::cout << "Hello from C++ and " << [result UTF8String]; - assert([result isEqualToString:@"Zig"]); - return 0; - } -} diff --git a/test/tests.zig b/test/tests.zig index 3666ef10289f888d728be96de0939ff1a1d13ab2..d1f319673cbac3e21f131c28bc6213b318869d8e 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -21,6 +21,7 @@ const assemble_and_link = @import("assemble_and_link.zig"); const translate_c = @import("translate_c.zig"); const run_translated_c = @import("run_translated_c.zig"); const gen_h = @import("gen_h.zig"); +const link = @import("link.zig"); // Implementations pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext; @@ -479,6 +480,27 @@ pub fn addStandaloneTests( return cases.step; } +pub fn addLinkTests( + b: *build.Builder, + test_filter: ?[]const u8, + modes: []const Mode, + enable_macos_sdk: bool, +) *build.Step { + const cases = b.allocator.create(StandaloneContext) catch unreachable; + cases.* = StandaloneContext{ + .b = b, + .step = b.step("test-link", "Run the linker tests"), + .test_index = 0, + .test_filter = test_filter, + .modes = modes, + .skip_non_native = true, + .enable_macos_sdk = enable_macos_sdk, + .target = .{}, + }; + link.addCases(cases); + return cases.step; +} + pub fn addCliTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { _ = test_filter; _ = modes; @@ -973,7 +995,8 @@ pub const StandaloneContext = struct { } if (features.cross_targets and !self.target.isNative()) { - const target_arg = fmt.allocPrint(b.allocator, "-Dtarget={s}", .{self.target.zigTriple(b.allocator) catch unreachable}) catch unreachable; + const target_triple = self.target.zigTriple(b.allocator) catch unreachable; + const target_arg = fmt.allocPrint(b.allocator, "-Dtarget={s}", .{target_triple}) catch unreachable; zig_args.append(target_arg) catch unreachable; }