| author | |
| committer | |
| log | ab46b8235450cd1d8cb1a483859c2265588a1863 |
| tree | 1dfa78312ed920a1356469464e9956e3801a3903 |
| parent | c6d0c68141ebd8059bf5e7cdc5f1b4117e62e64a |
4 files changed, 293 insertions(+), 0 deletions(-)
build.zig+9| ... | ... | @@ -629,6 +629,15 @@ pub fn build(b: *std.Build) !void { |
| 629 | 629 | .skip_llvm = skip_llvm, |
| 630 | 630 | .max_rss = 3_300_000_000, |
| 631 | 631 | })); |
| 632 | test_step.dependOn(tests.addLinkTests(b, .{ | |
| 633 | .test_target_filters = test_target_filters, | |
| 634 | .test_filters = test_filters, | |
| 635 | .optimize_modes = optimize_modes, | |
| 636 | .skip_non_native = skip_non_native, | |
| 637 | .skip_windows = skip_windows, | |
| 638 | .skip_llvm = skip_llvm, | |
| 639 | .max_rss = 100_000_000, | |
| 640 | })); | |
| 632 | 641 | test_step.dependOn(tests.addStackTraceTests(b, test_filters, skip_non_native)); |
| 633 | 642 | test_step.dependOn(tests.addErrorTraceTests(b, test_filters, optimize_modes, skip_non_native)); |
| 634 | 643 | test_step.dependOn(tests.addCliTests(b)); |
test/link.zig created+17| ... | ... | @@ -0,0 +1,17 @@ |
| 1 | pub fn addCases(cases: @import("tests.zig").LinkContext) void { | |
| 2 | if (cases.addTestStep("static-lib-exports")) |name| { | |
| 3 | const lib = cases.addStaticLibrary(.{ | |
| 4 | .name = "lib", | |
| 5 | .zig_source_bytes = | |
| 6 | \\export fn foo() void {} | |
| 7 | \\var bar: u32 = 1234; | |
| 8 | \\comptime { @export(&bar, .{ .name = "bar", .linkage = .strong }); } | |
| 9 | \\const baz: u64 = 5678; | |
| 10 | \\comptime { @export(&baz, .{ .name = "baz", .linkage = .strong }); } | |
| 11 | , | |
| 12 | }); | |
| 13 | cases.verifyObjdump(name, lib, &.{"--symbols"}, .{ .os = true }); | |
| 14 | } | |
| 15 | } | |
| 16 | ||
| 17 | const std = @import("std"); |
test/src/Link.zig created+159| ... | ... | @@ -0,0 +1,159 @@ |
| 1 | b: *Build, | |
| 2 | step: *Step, | |
| 3 | optimize: std.builtin.OptimizeMode, | |
| 4 | target: std.Build.ResolvedTarget, | |
| 5 | use_llvm: bool, | |
| 6 | use_lld: bool, | |
| 7 | link_libc: bool, | |
| 8 | suffix: []const u8, | |
| 9 | test_filters: []const []const u8, | |
| 10 | max_rss: usize, | |
| 11 | ||
| 12 | pub fn addTestStep(self: *const Link, prefix: []const u8) ?[]const u8 { | |
| 13 | if (for (self.test_filters) |filter| { | |
| 14 | if (std.mem.containsAtLeast(u8, prefix, 1, filter)) break false; | |
| 15 | } else self.test_filters.len > 0) return null; | |
| 16 | ||
| 17 | return std.fmt.allocPrint(self.b.allocator, "test-{s}", .{prefix}) catch @panic("OOM"); | |
| 18 | } | |
| 19 | ||
| 20 | pub fn addStaticLibrary(self: *const Link, overlay: OverlayOptions) *Step.Compile { | |
| 21 | return self.b.addLibrary(.{ | |
| 22 | .linkage = .static, | |
| 23 | .name = overlay.name, | |
| 24 | .root_module = self.createModule(overlay), | |
| 25 | .use_llvm = self.use_llvm, | |
| 26 | .use_lld = self.use_lld, | |
| 27 | }); | |
| 28 | } | |
| 29 | ||
| 30 | // TODO: Use std.meta.FieldEnum on TargetQuery? | |
| 31 | const SnapshotScope = packed struct { | |
| 32 | arch: bool = false, | |
| 33 | os: bool = false, | |
| 34 | abi: bool = false, | |
| 35 | optimize: bool = false, | |
| 36 | use_llvm: bool = false, | |
| 37 | use_lld: bool = false, | |
| 38 | link_libc: bool = false, | |
| 39 | }; | |
| 40 | ||
| 41 | pub fn verifyObjdump( | |
| 42 | self: *const Link, | |
| 43 | name: []const u8, | |
| 44 | compile: *Step.Compile, | |
| 45 | args: []const []const u8, | |
| 46 | scope: SnapshotScope, | |
| 47 | ) void { | |
| 48 | const snapshot_name = self.snapshotName(name, compile.name, scope) catch @panic("OOM"); | |
| 49 | const run_step = Step.Run.create(self.b, self.b.fmt("objdump {s}", .{snapshot_name})); | |
| 50 | run_step.addArgs(&.{ self.b.graph.zig_exe, "objdump" }); | |
| 51 | run_step.addArtifactArg(compile); | |
| 52 | run_step.addArgs(args); | |
| 53 | run_step.addCheck(.{ .expect_term = .{ .exited = 0 } }); | |
| 54 | ||
| 55 | const actual_path = run_step.captureStdOut(.{ .trim_whitespace = .none }); | |
| 56 | const expected_path = self.b.path(self.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name })); | |
| 57 | ||
| 58 | const check_step = self.b.addCheckFile(actual_path, .{ | |
| 59 | .expected_file = .{ | |
| 60 | .file = expected_path, | |
| 61 | .if_missing = .fail, | |
| 62 | // TODO: Option to do UpdateSourceFiles if not matching / missing? | |
| 63 | // TODO: Option to output to <name>-<self.suffix>.actual.dmp file? | |
| 64 | }, | |
| 65 | }); | |
| 66 | ||
| 67 | self.step.dependOn(&check_step.step); | |
| 68 | } | |
| 69 | ||
| 70 | fn snapshotName( | |
| 71 | self: *const Link, | |
| 72 | test_name: []const u8, | |
| 73 | compile_name: []const u8, | |
| 74 | scope: SnapshotScope, | |
| 75 | ) ![]const u8 { | |
| 76 | var snapshot_name: std.Io.Writer.Allocating = .init(self.b.allocator); | |
| 77 | const w = &snapshot_name.writer; | |
| 78 | ||
| 79 | try w.print("{s}.{s}", .{ test_name, compile_name }); | |
| 80 | if (scope.arch) try w.print("-{t}", .{self.target.result.cpu.arch}); | |
| 81 | if (scope.os) try w.print("-{t}", .{self.target.result.os.tag}); | |
| 82 | if (scope.abi) try w.print("-{t}", .{self.target.result.abi}); | |
| 83 | if (scope.optimize) try w.print("-{t}", .{self.optimize}); | |
| 84 | if (scope.use_llvm and self.use_llvm) try w.writeAll("-llvm"); | |
| 85 | if (scope.use_lld and self.use_lld) try w.writeAll("-lld"); | |
| 86 | if (scope.link_libc and self.link_libc) try w.writeAll("-libc"); | |
| 87 | try w.writeAll(".dmp"); | |
| 88 | ||
| 89 | return try snapshot_name.toOwnedSlice(); | |
| 90 | } | |
| 91 | ||
| 92 | fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module { | |
| 93 | const write_files = self.b.addWriteFiles(); | |
| 94 | ||
| 95 | const mod = self.b.createModule(.{ | |
| 96 | .target = self.target, | |
| 97 | .optimize = self.optimize, | |
| 98 | .root_source_file = rsf: { | |
| 99 | const bytes = overlay.zig_source_bytes orelse break :rsf null; | |
| 100 | const name = self.b.fmt("{s}.zig", .{overlay.name}); | |
| 101 | break :rsf write_files.add(name, bytes); | |
| 102 | }, | |
| 103 | .link_libc = self.link_libc, // TODO: Should this be in overlay instead? | |
| 104 | .pic = overlay.pic, | |
| 105 | .strip = overlay.strip, | |
| 106 | }); | |
| 107 | ||
| 108 | if (overlay.objcpp_source_bytes) |bytes| { | |
| 109 | mod.addCSourceFile(.{ | |
| 110 | .file = write_files.add("a.mm", bytes), | |
| 111 | .flags = overlay.objcpp_source_flags, | |
| 112 | }); | |
| 113 | } | |
| 114 | if (overlay.objc_source_bytes) |bytes| { | |
| 115 | mod.addCSourceFile(.{ | |
| 116 | .file = write_files.add("a.m", bytes), | |
| 117 | .flags = overlay.objc_source_flags, | |
| 118 | }); | |
| 119 | } | |
| 120 | if (overlay.cpp_source_bytes) |bytes| { | |
| 121 | mod.addCSourceFile(.{ | |
| 122 | .file = write_files.add("a.cpp", bytes), | |
| 123 | .flags = overlay.cpp_source_flags, | |
| 124 | }); | |
| 125 | } | |
| 126 | if (overlay.c_source_bytes) |bytes| { | |
| 127 | mod.addCSourceFile(.{ | |
| 128 | .file = write_files.add("a.c", bytes), | |
| 129 | .flags = overlay.c_source_flags, | |
| 130 | }); | |
| 131 | } | |
| 132 | if (overlay.asm_source_bytes) |bytes| { | |
| 133 | mod.addAssemblyFile(write_files.add("a.s", bytes)); | |
| 134 | } | |
| 135 | ||
| 136 | return mod; | |
| 137 | } | |
| 138 | ||
| 139 | const OverlayOptions = struct { | |
| 140 | name: []const u8, | |
| 141 | asm_source_bytes: ?[]const u8 = null, | |
| 142 | c_source_bytes: ?[]const u8 = null, | |
| 143 | c_source_flags: []const []const u8 = &.{}, | |
| 144 | cpp_source_bytes: ?[]const u8 = null, | |
| 145 | cpp_source_flags: []const []const u8 = &.{}, | |
| 146 | objc_source_bytes: ?[]const u8 = null, | |
| 147 | objc_source_flags: []const []const u8 = &.{}, | |
| 148 | objcpp_source_bytes: ?[]const u8 = null, | |
| 149 | objcpp_source_flags: []const []const u8 = &.{}, | |
| 150 | zig_source_bytes: ?[]const u8 = null, | |
| 151 | pic: ?bool = null, | |
| 152 | strip: ?bool = null, | |
| 153 | }; | |
| 154 | ||
| 155 | const std = @import("std"); | |
| 156 | const Build = std.Build; | |
| 157 | const Step = Build.Step; | |
| 158 | ||
| 159 | const Link = @This(); |
test/tests.zig+108| ... | ... | @@ -10,6 +10,7 @@ const error_traces = @import("error_traces.zig"); |
| 10 | 10 | const stack_traces = @import("stack_traces.zig"); |
| 11 | 11 | const llvm_ir = @import("llvm_ir.zig"); |
| 12 | 12 | const libc = @import("libc.zig"); |
| 13 | const link = @import("link.zig"); | |
| 13 | 14 | |
| 14 | 15 | // Implementations |
| 15 | 16 | pub const ErrorTracesContext = @import("src/ErrorTrace.zig"); |
| ... | ... | @@ -17,6 +18,7 @@ pub const StackTracesContext = @import("src/StackTrace.zig"); |
| 17 | 18 | pub const DebuggerContext = @import("src/Debugger.zig"); |
| 18 | 19 | pub const LlvmIrContext = @import("src/LlvmIr.zig"); |
| 19 | 20 | pub const LibcContext = @import("src/Libc.zig"); |
| 21 | pub const LinkContext = @import("src/Link.zig"); | |
| 20 | 22 | |
| 21 | 23 | const ModuleTestTarget = struct { |
| 22 | 24 | linkage: ?std.builtin.LinkMode = null, |
| ... | ... | @@ -2059,6 +2061,57 @@ const c_abi_targets = blk: { |
| 2059 | 2061 | }; |
| 2060 | 2062 | }; |
| 2061 | 2063 | |
| 2064 | const LinkTarget = struct { | |
| 2065 | target: std.Target.Query = .{}, | |
| 2066 | link_libc: bool = false, | |
| 2067 | use_llvm: bool = false, | |
| 2068 | use_lld: bool = false, | |
| 2069 | }; | |
| 2070 | ||
| 2071 | const link_targets = blk: { | |
| 2072 | @setEvalBranchQuota(30000); | |
| 2073 | break :blk [_]LinkTarget{ | |
| 2074 | // Native Targets | |
| 2075 | ||
| 2076 | // .{ | |
| 2077 | // .use_llvm = true, | |
| 2078 | // }, | |
| 2079 | ||
| 2080 | // Windows Targets | |
| 2081 | ||
| 2082 | .{ | |
| 2083 | .target = .{ | |
| 2084 | .cpu_arch = .x86_64, | |
| 2085 | .os_tag = .windows, | |
| 2086 | .abi = .gnu, | |
| 2087 | }, | |
| 2088 | }, | |
| 2089 | .{ | |
| 2090 | .target = .{ | |
| 2091 | .cpu_arch = .x86_64, | |
| 2092 | .os_tag = .windows, | |
| 2093 | .abi = .gnu, | |
| 2094 | }, | |
| 2095 | .link_libc = true, | |
| 2096 | }, | |
| 2097 | .{ | |
| 2098 | .target = .{ | |
| 2099 | .cpu_arch = .x86_64, | |
| 2100 | .os_tag = .windows, | |
| 2101 | .abi = .msvc, | |
| 2102 | }, | |
| 2103 | }, | |
| 2104 | .{ | |
| 2105 | .target = .{ | |
| 2106 | .cpu_arch = .x86_64, | |
| 2107 | .os_tag = .windows, | |
| 2108 | .abi = .msvc, | |
| 2109 | }, | |
| 2110 | .link_libc = true, | |
| 2111 | }, | |
| 2112 | }; | |
| 2113 | }; | |
| 2114 | ||
| 2062 | 2115 | /// Unlike `test_targets` and `c_abi_targets`, these targets are just simple strings which we pass |
| 2063 | 2116 | /// directly to `incr-check`. They include the target triple and the compiler backend. |
| 2064 | 2117 | /// |
| ... | ... | @@ -3083,6 +3136,61 @@ pub fn addCAbiTests(b: *std.Build, options: CAbiTestOptions) *Step { |
| 3083 | 3136 | return step; |
| 3084 | 3137 | } |
| 3085 | 3138 | |
| 3139 | const LinkTestOptions = struct { | |
| 3140 | test_target_filters: []const []const u8, | |
| 3141 | test_filters: []const []const u8, | |
| 3142 | optimize_modes: []const OptimizeMode, | |
| 3143 | skip_non_native: bool, | |
| 3144 | skip_windows: bool, | |
| 3145 | skip_llvm: bool, | |
| 3146 | max_rss: usize, | |
| 3147 | }; | |
| 3148 | ||
| 3149 | pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step { | |
| 3150 | const step = b.step("test-link", "Run the linker tests"); | |
| 3151 | ||
| 3152 | for (link_targets) |link_target| { | |
| 3153 | if (options.skip_non_native and !link_target.target.isNative()) continue; | |
| 3154 | if (options.skip_windows and link_target.target.os_tag == .windows) continue; | |
| 3155 | ||
| 3156 | const resolved_target = b.resolveTargetQuery(link_target.target); | |
| 3157 | const triple_txt = resolved_target.query.zigTriple(b.allocator) catch @panic("OOM"); | |
| 3158 | const target = &resolved_target.result; | |
| 3159 | ||
| 3160 | if (options.test_target_filters.len > 0) { | |
| 3161 | for (options.test_target_filters) |filter| { | |
| 3162 | if (std.mem.indexOf(u8, triple_txt, filter) != null) break; | |
| 3163 | } else continue; | |
| 3164 | } | |
| 3165 | ||
| 3166 | for (options.optimize_modes) |optimize_mode| { | |
| 3167 | const would_use_llvm = wouldUseLlvm(link_target.use_llvm, link_target.target, optimize_mode); | |
| 3168 | if (options.skip_llvm and would_use_llvm) continue; | |
| 3169 | if (link_target.link_libc and target.abi == .msvc and b.graph.host.result.os.tag != .windows) continue; | |
| 3170 | ||
| 3171 | link.addCases(.{ | |
| 3172 | .b = b, | |
| 3173 | .step = step, | |
| 3174 | .optimize = optimize_mode, | |
| 3175 | .target = resolved_target, | |
| 3176 | .suffix = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{ | |
| 3177 | target.zigTriple(b.allocator) catch @panic("OOM"), | |
| 3178 | optimize_mode, | |
| 3179 | if (link_target.use_llvm) "-llvm" else "", | |
| 3180 | if (link_target.use_lld) "-lld" else "", | |
| 3181 | if (link_target.link_libc) "-libc" else "", | |
| 3182 | }) catch @panic("OOM"), | |
| 3183 | .use_llvm = link_target.use_llvm, | |
| 3184 | .use_lld = link_target.use_lld, | |
| 3185 | .link_libc = link_target.link_libc, | |
| 3186 | .test_filters = options.test_filters, | |
| 3187 | .max_rss = options.max_rss, | |
| 3188 | }); | |
| 3189 | } | |
| 3190 | } | |
| 3191 | return step; | |
| 3192 | } | |
| 3193 | ||
| 3086 | 3194 | pub fn addCases( |
| 3087 | 3195 | b: *std.Build, |
| 3088 | 3196 | parent_step: *Step, |