declare linker test bankruptcy

The active contributors and maintainers of Zig's linker code have
generally found the current linker test harness to be cumbersome. The
tests require a lot of maintenance, but do not provide a lot of
coverage, and when they fail it is painful to troubleshoot.

Furthermore, as part of working on #31691, I don't want to port over the
CheckObject step, because I don't like the code anyway.

The plan forward is to start enhancing `zig objdump` to assist in
linker development, as well as using it as the basis for snapshot
testing.

We absolutely need linker test coverage, but we need to try to improve
these things about the next attempt:
* less effort to create and maintain tests
* less CPU overhead - we should be able to add a lot of tests without
  adding a lot of CI time.
* more helpful failures. A failed linker test should provide the next
  steps a developer can take to understand why the test failed.
* a goal of porting over all of LLD's test suite, or at least the good
  ones.

I'm not going to open an issue to track the lost linker test coverage,
because there was already so much lack of coverage for linker stuff.

However I will open issues to track this lost coverage:
* the deleted checks from test/standalone/glibc_compat/build.zig
* the deleted checks from test/standalone/compiler_rt_panic/build.zig
* the deleted checks from test/standalone/ios/build.zig
This commit is contained in:
Andrew Kelley
2026-04-24 19:43:45 -07:00
parent 23bcb8148f
commit e7d74e49b0
60 changed files with 0 additions and 11870 deletions
-20
View File
@@ -1,20 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test");
b.default_step = test_step;
const exe = b.addExecutable(.{
.name = "bss",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = b.graph.host,
.optimize = .Debug,
}),
});
const run = b.addRunArtifact(exe);
run.expectStdOutEqual("0, 1, 0\n");
test_step.dependOn(&run.step);
}
-17
View File
@@ -1,17 +0,0 @@
const std = @import("std");
// Stress test zerofill layout
var buffer: [0x1000000]u64 = [1]u64{0} ** 0x1000000;
pub fn main() anyerror!void {
var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
buffer[0x10] = 1;
try stdout_writer.interface.print("{d}, {d}, {d}\n", .{
// workaround the dreaded decl_val
(&buffer)[0],
(&buffer)[0x10],
(&buffer)[0x1000000 - 1],
});
}
-54
View File
@@ -1,54 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const link = @import("link.zig");
pub fn build(b: *std.Build) void {
const step = b.step("test", "Run link test cases");
b.default_step = step;
const enable_ios_sdk = b.option(bool, "enable_ios_sdk", "Run tests requiring presence of iOS SDK and frameworks") orelse false;
const enable_macos_sdk = b.option(bool, "enable_macos_sdk", "Run tests requiring presence of macOS SDK and frameworks") orelse enable_ios_sdk;
const enable_symlinks_windows = b.option(bool, "enable_symlinks_windows", "Run tests requiring presence of symlinks on Windows") orelse false;
const has_symlinks = builtin.os.tag != .windows or enable_symlinks_windows;
const build_opts: link.BuildOptions = .{
.has_ios_sdk = enable_ios_sdk,
.has_macos_sdk = enable_macos_sdk,
.has_symlinks = has_symlinks,
};
step.dependOn(@import("elf.zig").testAll(b, build_opts));
step.dependOn(@import("macho.zig").testAll(b, build_opts));
add_dep_steps: for (b.available_deps) |available_dep| {
const dep_name, const dep_hash = available_dep;
const all_pkgs = @import("root").dependencies.packages;
inline for (@typeInfo(all_pkgs).@"struct".decls) |decl| {
const pkg_hash = decl.name;
if (std.mem.eql(u8, dep_hash, pkg_hash)) {
const pkg = @field(all_pkgs, pkg_hash);
if (!@hasDecl(pkg, "build_zig")) {
std.debug.panic("link test case '{s}' is missing a 'build.zig' file", .{dep_name});
}
const requires_ios_sdk = @hasDecl(pkg.build_zig, "requires_ios_sdk") and
pkg.build_zig.requires_ios_sdk;
const requires_macos_sdk = @hasDecl(pkg.build_zig, "requires_macos_sdk") and
pkg.build_zig.requires_macos_sdk;
const requires_symlinks = @hasDecl(pkg.build_zig, "requires_symlinks") and
pkg.build_zig.requires_symlinks;
if ((requires_symlinks and !has_symlinks) or
(requires_macos_sdk and !enable_macos_sdk) or
(requires_ios_sdk and !enable_ios_sdk))
{
continue :add_dep_steps;
}
break;
}
} else unreachable;
const dep = b.dependency(dep_name, .{});
const dep_step = dep.builder.default_step;
dep_step.name = b.fmt("link_test_cases.{s}", .{dep_name});
step.dependOn(dep_step);
}
}
-61
View File
@@ -1,61 +0,0 @@
.{
.name = .link_test_cases,
.fingerprint = 0x404f657576fec9f2,
.version = "0.0.0",
.dependencies = .{
.bss = .{
.path = "bss",
},
.common_symbols_alignment = .{
.path = "common_symbols_alignment",
},
.interdependent_static_c_libs = .{
.path = "interdependent_static_c_libs",
},
.static_libs_from_object_files = .{
.path = "static_libs_from_object_files",
},
// WASM Cases
.wasm_archive = .{
.path = "wasm/archive",
},
.wasm_basic_features = .{
.path = "wasm/basic-features",
},
.wasm_export = .{
.path = "wasm/export",
},
.wasm_export_data = .{
.path = "wasm/export-data",
},
.wasm_extern = .{
.path = "wasm/extern",
},
.wasm_extern_mangle = .{
.path = "wasm/extern-mangle",
},
.wasm_function_table = .{
.path = "wasm/function-table",
},
.wasm_infer_features = .{
.path = "wasm/infer-features",
},
.wasm_producers = .{
.path = "wasm/producers",
},
.wasm_shared_memory = .{
.path = "wasm/shared-memory",
},
.wasm_stack_pointer = .{
.path = "wasm/stack_pointer",
},
.wasm_type = .{
.path = "wasm/type",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"link.zig",
},
}
-6
View File
@@ -1,6 +0,0 @@
int i;
int j;
int add_to_i_and_j(int x) {
return x + i + j;
}
-7
View File
@@ -1,7 +0,0 @@
long i;
int j = 2;
int k;
void incr_i() {
i++;
}
-37
View File
@@ -1,37 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const lib_a = b.addLibrary(.{
.linkage = .static,
.name = "a",
.root_module = b.createModule(.{
.root_source_file = null,
.optimize = optimize,
.target = b.graph.host,
}),
});
lib_a.root_module.addCSourceFiles(.{
.files = &.{ "c.c", "a.c", "b.c" },
.flags = &.{"-fcommon"},
});
const test_exe = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = optimize,
}),
});
test_exe.root_module.linkLibrary(lib_a);
test_step.dependOn(&b.addRunArtifact(test_exe).step);
}
-5
View File
@@ -1,5 +0,0 @@
extern int k;
int common_defined_externally() {
return k;
}
-16
View File
@@ -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);
}
-2
View File
@@ -1,2 +0,0 @@
int foo;
__attribute__((aligned(4096))) int bar;
@@ -1,38 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const lib_a = b.addLibrary(.{
.linkage = .static,
.name = "a",
.root_module = b.createModule(.{
.root_source_file = null,
.optimize = optimize,
.target = b.graph.host,
}),
});
lib_a.root_module.addCSourceFiles(.{
.files = &.{"a.c"},
.flags = &.{"-fcommon"},
});
const test_exe = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = b.graph.host,
.optimize = optimize,
}),
});
test_exe.root_module.linkLibrary(lib_a);
test_step.dependOn(&b.addRunArtifact(test_exe).step);
}
@@ -1,9 +0,0 @@
const std = @import("std");
extern var foo: i32;
extern var bar: i32;
test {
try std.testing.expect(@intFromPtr(&foo) % 4 == 0);
try std.testing.expect(@intFromPtr(&bar) % 4096 == 0);
}
-4300
View File
@@ -1,4300 +0,0 @@
pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
_ = build_opts;
const elf_step = b.step("test-elf", "Run ELF tests");
// https://github.com/ziglang/zig/issues/25323
if (builtin.os.tag == .freebsd) return elf_step;
// https://github.com/ziglang/zig/issues/25961
if (comptime builtin.cpu.arch.endian() == .big) return elf_step;
const default_target = b.resolveTargetQuery(.{
.cpu_arch = .x86_64, // TODO relax this once ELF linker is able to handle other archs
.os_tag = .linux,
});
const x86_64_musl = b.resolveTargetQuery(.{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .musl,
});
const x86_64_gnu = b.resolveTargetQuery(.{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .gnu,
});
const aarch64_musl = b.resolveTargetQuery(.{
.cpu_arch = .aarch64,
.os_tag = .linux,
.abi = .musl,
});
const riscv64_musl = b.resolveTargetQuery(.{
.cpu_arch = .riscv64,
.os_tag = .linux,
.abi = .musl,
});
// Common tests
for (&[_]std.Target.Cpu.Arch{
.x86_64,
.aarch64,
}) |cpu_arch| {
const musl_target = b.resolveTargetQuery(.{
.cpu_arch = cpu_arch,
.os_tag = .linux,
.abi = .musl,
});
const gnu_target = b.resolveTargetQuery(.{
.cpu_arch = cpu_arch,
.os_tag = .linux,
.abi = .gnu,
});
// Exercise linker in -r mode
elf_step.dependOn(testEmitRelocatable(b, .{ .target = musl_target }));
elf_step.dependOn(testRelocatableArchive(b, .{ .target = musl_target }));
elf_step.dependOn(testRelocatableEhFrame(b, .{ .target = musl_target }));
elf_step.dependOn(testRelocatableEhFrameComdatHeavy(b, .{ .target = musl_target }));
elf_step.dependOn(testRelocatableNoEhFrame(b, .{ .target = musl_target }));
// Exercise linker in ar mode
elf_step.dependOn(testEmitStaticLib(b, .{ .target = musl_target }));
elf_step.dependOn(testEmitStaticLibZig(b, .{ .target = musl_target }));
// Exercise linker with LLVM backend
// musl tests
elf_step.dependOn(testAbsSymbols(b, .{ .target = musl_target }));
elf_step.dependOn(testComdatElimination(b, .{ .target = musl_target }));
elf_step.dependOn(testCommonSymbols(b, .{ .target = musl_target }));
elf_step.dependOn(testCommonSymbolsInArchive(b, .{ .target = musl_target }));
elf_step.dependOn(testCommentString(b, .{ .target = musl_target }));
elf_step.dependOn(testEmptyObject(b, .{ .target = musl_target }));
elf_step.dependOn(testEntryPoint(b, .{ .target = musl_target }));
elf_step.dependOn(testGcSections(b, .{ .target = musl_target }));
elf_step.dependOn(testGcSectionsZig(b, .{ .target = musl_target }));
elf_step.dependOn(testImageBase(b, .{ .target = musl_target }));
elf_step.dependOn(testInitArrayOrder(b, .{ .target = musl_target }));
elf_step.dependOn(testLargeAlignmentExe(b, .{ .target = musl_target }));
// https://codeberg.org/ziglang/zig/issues/31580
// elf_step.dependOn(testLargeBss(b, .{ .target = musl_target }));
elf_step.dependOn(testLinkingC(b, .{ .target = musl_target }));
elf_step.dependOn(testLinkingCpp(b, .{ .target = musl_target }));
elf_step.dependOn(testLinkingZig(b, .{ .target = musl_target }));
elf_step.dependOn(testLinksection(b, .{ .target = musl_target }));
elf_step.dependOn(testMergeStrings(b, .{ .target = musl_target }));
elf_step.dependOn(testMergeStrings2(b, .{ .target = musl_target }));
// https://github.com/ziglang/zig/issues/17451
// elf_step.dependOn(testNoEhFrameHdr(b, .{ .target = musl_target }));
elf_step.dependOn(testTlsStatic(b, .{ .target = musl_target }));
elf_step.dependOn(testStrip(b, .{ .target = musl_target }));
// glibc tests
elf_step.dependOn(testAsNeeded(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430
// elf_step.dependOn(testCanonicalPlt(b, .{ .target = gnu_target }));
elf_step.dependOn(testCommentString(b, .{ .target = gnu_target }));
elf_step.dependOn(testCopyrel(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430
// elf_step.dependOn(testCopyrelAlias(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430
// elf_step.dependOn(testCopyrelAlignment(b, .{ .target = gnu_target }));
elf_step.dependOn(testDsoPlt(b, .{ .target = gnu_target }));
elf_step.dependOn(testDsoUndef(b, .{ .target = gnu_target }));
elf_step.dependOn(testExportDynamic(b, .{ .target = gnu_target }));
elf_step.dependOn(testExportSymbolsFromExe(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430
// elf_step.dependOn(testFuncAddress(b, .{ .target = gnu_target }));
elf_step.dependOn(testHiddenWeakUndef(b, .{ .target = gnu_target }));
elf_step.dependOn(testIFuncAlias(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430
// elf_step.dependOn(testIFuncDlopen(b, .{ .target = gnu_target }));
elf_step.dependOn(testIFuncDso(b, .{ .target = gnu_target }));
elf_step.dependOn(testIFuncDynamic(b, .{ .target = gnu_target }));
elf_step.dependOn(testIFuncExport(b, .{ .target = gnu_target }));
elf_step.dependOn(testIFuncFuncPtr(b, .{ .target = gnu_target }));
elf_step.dependOn(testIFuncNoPlt(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430 ??
// elf_step.dependOn(testIFuncStatic(b, .{ .target = gnu_target }));
// elf_step.dependOn(testIFuncStaticPie(b, .{ .target = gnu_target }));
elf_step.dependOn(testInitArrayOrder(b, .{ .target = gnu_target }));
elf_step.dependOn(testLargeAlignmentDso(b, .{ .target = gnu_target }));
elf_step.dependOn(testLargeAlignmentExe(b, .{ .target = gnu_target }));
elf_step.dependOn(testLargeBss(b, .{ .target = gnu_target }));
elf_step.dependOn(testLinkOrder(b, .{ .target = gnu_target }));
elf_step.dependOn(testLdScript(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/23125
// elf_step.dependOn(testLdScriptPathError(b, .{ .target = gnu_target }));
elf_step.dependOn(testLdScriptAllowUndefinedVersion(b, .{ .target = gnu_target, .use_lld = true }));
elf_step.dependOn(testLdScriptDisallowUndefinedVersion(b, .{ .target = gnu_target, .use_lld = true }));
// https://github.com/ziglang/zig/issues/17451
// elf_step.dependOn(testNoEhFrameHdr(b, .{ .target = gnu_target }));
elf_step.dependOn(testPie(b, .{ .target = gnu_target }));
elf_step.dependOn(testPltGot(b, .{ .target = gnu_target }));
elf_step.dependOn(testPreinitArray(b, .{ .target = gnu_target }));
elf_step.dependOn(testSharedAbsSymbol(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsDfStaticTls(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsDso(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsGd(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsGdNoPlt(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsGdToIe(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsIe(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsLargeAlignment(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsLargeTbss(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsLargeStaticImage(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsLd(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsLdDso(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsLdNoPlt(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430
// elf_step.dependOn(testTlsNoPic(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsOffsetAlignment(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsPic(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsSmallAlignment(b, .{ .target = gnu_target }));
elf_step.dependOn(testUnknownFileTypeError(b, .{ .target = gnu_target }));
elf_step.dependOn(testUnresolvedError(b, .{ .target = gnu_target }));
elf_step.dependOn(testWeakExports(b, .{ .target = gnu_target }));
elf_step.dependOn(testWeakUndefsDso(b, .{ .target = gnu_target }));
elf_step.dependOn(testZNow(b, .{ .target = gnu_target }));
elf_step.dependOn(testZStackSize(b, .{ .target = gnu_target }));
}
// x86_64 specific tests
elf_step.dependOn(testMismatchedCpuArchitectureError(b, .{ .target = x86_64_musl }));
elf_step.dependOn(testZText(b, .{ .target = x86_64_gnu }));
// aarch64 specific tests
elf_step.dependOn(testThunks(b, .{ .target = aarch64_musl }));
// x86_64 self-hosted backend
elf_step.dependOn(testCommentString(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testCommentStringStaticLib(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testEmitRelocatable(b, .{ .use_llvm = false, .target = x86_64_musl }));
elf_step.dependOn(testEmitStaticLibZig(b, .{ .use_llvm = false, .target = x86_64_musl }));
elf_step.dependOn(testGcSectionsZig(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testLinkingObj(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testLinkingStaticLib(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testLinkingZig(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testLinksection(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testImportingDataDynamic(b, .{ .use_llvm = false, .target = x86_64_gnu }));
elf_step.dependOn(testImportingDataStatic(b, .{ .use_llvm = false, .target = x86_64_musl }));
// riscv64 linker backend is currently not complete enough to support more
elf_step.dependOn(testLinkingC(b, .{ .target = riscv64_musl }));
return elf_step;
}
fn testAbsSymbols(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "abs-symbols", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.asm_source_bytes =
\\.globl foo
\\foo = 0x800008
\\
,
});
const exe = addExecutable(b, opts, .{
.name = "test",
.c_source_bytes =
\\#include <signal.h>
\\#include <stdio.h>
\\#include <stdlib.h>
\\#include <ucontext.h>
\\#include <assert.h>
\\void handler(int signum, siginfo_t *info, void *ptr) {
\\ assert((size_t)info->si_addr == 0x800008);
\\ exit(0);
\\}
\\extern int foo;
\\int main() {
\\ struct sigaction act;
\\ act.sa_flags = SA_SIGINFO | SA_RESETHAND;
\\ act.sa_sigaction = handler;
\\ sigemptyset(&act.sa_mask);
\\ sigaction(SIGSEGV, &act, 0);
\\ foo = 5;
\\ return 0;
\\}
,
});
exe.root_module.addObject(obj);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testAsNeeded(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "as-needed", opts);
const main_o = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include <stdio.h>
\\int baz();
\\int main() {
\\ printf("%d\n", baz());
\\ return 0;
\\}
\\
,
});
main_o.root_module.link_libc = true;
const libfoo = addSharedLibrary(b, opts, .{ .name = "foo" });
addCSourceBytes(libfoo, "int foo() { return 42; }", &.{});
const libbar = addSharedLibrary(b, opts, .{ .name = "bar" });
addCSourceBytes(libbar, "int bar() { return 42; }", &.{});
const libbaz = addSharedLibrary(b, opts, .{ .name = "baz" });
addCSourceBytes(libbaz,
\\int foo();
\\int baz() { return foo(); }
, &.{});
{
const exe = addExecutable(b, opts, .{
.name = "test",
});
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("foo", .{ .needed = true });
exe.root_module.addLibraryPath(libfoo.getEmittedBinDirectory());
exe.root_module.addRPath(libfoo.getEmittedBinDirectory());
exe.root_module.linkSystemLibrary("bar", .{ .needed = true });
exe.root_module.addLibraryPath(libbar.getEmittedBinDirectory());
exe.root_module.addRPath(libbar.getEmittedBinDirectory());
exe.root_module.linkSystemLibrary("baz", .{ .needed = true });
exe.root_module.addLibraryPath(libbaz.getEmittedBinDirectory());
exe.root_module.addRPath(libbaz.getEmittedBinDirectory());
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInDynamicSection();
check.checkExact("NEEDED libfoo.so");
check.checkExact("NEEDED libbar.so");
check.checkExact("NEEDED libbaz.so");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{
.name = "test",
});
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("foo", .{ .needed = false });
exe.root_module.addLibraryPath(libfoo.getEmittedBinDirectory());
exe.root_module.addRPath(libfoo.getEmittedBinDirectory());
exe.root_module.linkSystemLibrary("bar", .{ .needed = false });
exe.root_module.addLibraryPath(libbar.getEmittedBinDirectory());
exe.root_module.addRPath(libbar.getEmittedBinDirectory());
exe.root_module.linkSystemLibrary("baz", .{ .needed = false });
exe.root_module.addLibraryPath(libbaz.getEmittedBinDirectory());
exe.root_module.addRPath(libbaz.getEmittedBinDirectory());
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInDynamicSection();
check.checkNotPresent("NEEDED libbar.so");
check.checkInDynamicSection();
check.checkExact("NEEDED libfoo.so");
check.checkExact("NEEDED libbaz.so");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testCanonicalPlt(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "canonical-plt", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\void *foo() {
\\ return foo;
\\}
\\void *bar() {
\\ return bar;
\\}
, &.{});
const b_o = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\void *bar();
\\void *baz() {
\\ return bar;
\\}
\\
,
.pic = true,
});
const main_o = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include <assert.h>
\\void *foo();
\\void *bar();
\\void *baz();
\\int main() {
\\ assert(foo == foo());
\\ assert(bar == bar());
\\ assert(bar == baz());
\\ return 0;
\\}
\\
,
.pic = false,
});
main_o.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{
.name = "main",
});
exe.root_module.addObject(main_o);
exe.root_module.addObject(b_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testComdatElimination(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "comdat-elimination", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.cpp_source_bytes =
\\#include <stdio.h>
\\inline void foo() {
\\ printf("calling foo in a\n");
\\}
\\void hello() {
\\ foo();
\\}
,
});
a_o.root_module.link_libcpp = true;
const main_o = addObject(b, opts, .{
.name = "main",
.cpp_source_bytes =
\\#include <stdio.h>
\\inline void foo() {
\\ printf("calling foo in main\n");
\\}
\\void hello();
\\int main() {
\\ foo();
\\ hello();
\\ return 0;
\\}
,
});
main_o.root_module.link_libcpp = true;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(main_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\calling foo in a
\\calling foo in a
\\
);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(a_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\calling foo in main
\\calling foo in main
\\
);
test_step.dependOn(&run.step);
}
{
const c_o = addObject(b, opts, .{ .name = "c" });
c_o.root_module.addObject(main_o);
c_o.root_module.addObject(a_o);
const exe = addExecutable(b, opts, .{ .name = "main3" });
exe.root_module.addObject(c_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\calling foo in main
\\calling foo in main
\\
);
test_step.dependOn(&run.step);
}
{
const d_o = addObject(b, opts, .{ .name = "d" });
d_o.root_module.addObject(a_o);
d_o.root_module.addObject(main_o);
const exe = addExecutable(b, opts, .{ .name = "main4" });
exe.root_module.addObject(d_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\calling foo in a
\\calling foo in a
\\
);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testCommentString(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "comment-string", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\pub fn main() void {}
});
const check = exe.checkObject();
check.dumpSection(".comment");
check.checkContains("zig");
test_step.dependOn(&check.step);
return test_step;
}
fn testCommentStringStaticLib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "comment-string-static-lib", opts);
const lib = addStaticLibrary(b, opts, .{ .name = "lib", .zig_source_bytes =
\\export fn foo() void {}
});
const check = lib.checkObject();
check.dumpSection(".comment");
check.checkContains("zig");
test_step.dependOn(&check.step);
return test_step;
}
fn testCommonSymbols(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "common-symbols", opts);
const exe = addExecutable(b, opts, .{
.name = "test",
});
addCSourceBytes(exe,
\\int foo;
\\int bar;
\\int baz = 42;
, &.{"-fcommon"});
addCSourceBytes(exe,
\\#include<stdio.h>
\\int foo;
\\int bar = 5;
\\int baz;
\\int main() {
\\ printf("%d %d %d\n", foo, bar, baz);
\\}
, &.{"-fcommon"});
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("0 5 42\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testCommonSymbolsInArchive(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "common-symbols-in-archive", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\#include <stdio.h>
\\int foo;
\\int bar;
\\extern int baz;
\\__attribute__((weak)) int two();
\\int main() {
\\ printf("%d %d %d %d\n", foo, bar, baz, two ? two() : -1);
\\}
\\
,
.c_source_flags = &.{"-fcommon"},
});
a_o.root_module.link_libc = true;
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes = "int foo = 5;",
.c_source_flags = &.{"-fcommon"},
});
{
const c_o = addObject(b, opts, .{
.name = "c",
.c_source_bytes =
\\int bar;
\\int two() { return 2; }
\\
,
.c_source_flags = &.{"-fcommon"},
});
const d_o = addObject(b, opts, .{
.name = "d",
.c_source_bytes = "int baz;",
.c_source_flags = &.{"-fcommon"},
});
const lib = addStaticLibrary(b, opts, .{ .name = "lib" });
lib.root_module.addObject(b_o);
lib.root_module.addObject(c_o);
lib.root_module.addObject(d_o);
const exe = addExecutable(b, opts, .{
.name = "test",
});
exe.root_module.addObject(a_o);
exe.root_module.linkLibrary(lib);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("5 0 0 -1\n");
test_step.dependOn(&run.step);
}
{
const e_o = addObject(b, opts, .{
.name = "e",
.c_source_bytes =
\\int bar = 0;
\\int baz = 7;
\\int two() { return 2; }
,
.c_source_flags = &.{"-fcommon"},
});
const lib = addStaticLibrary(b, opts, .{ .name = "lib" });
lib.root_module.addObject(b_o);
lib.root_module.addObject(e_o);
const exe = addExecutable(b, opts, .{
.name = "test",
});
exe.root_module.addObject(a_o);
exe.root_module.linkLibrary(lib);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("5 0 7 2\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testCopyrel(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "copyrel", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\int foo = 3;
\\int bar = 5;
, &.{});
const exe = addExecutable(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include<stdio.h>
\\extern int foo, bar;
\\int main() {
\\ printf("%d %d\n", foo, bar);
\\ return 0;
\\}
,
});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 5\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testCopyrelAlias(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "copyrel-alias", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\int bruh = 31;
\\int foo = 42;
\\extern int bar __attribute__((alias("foo")));
\\extern int baz __attribute__((alias("foo")));
, &.{});
const exe = addExecutable(b, opts, .{
.name = "main",
.pic = false,
});
addCSourceBytes(exe,
\\#include<stdio.h>
\\extern int foo;
\\extern int *get_bar();
\\int main() {
\\ printf("%d %d %d\n", foo, *get_bar(), &foo == get_bar());
\\ return 0;
\\}
, &.{});
addCSourceBytes(exe,
\\extern int bar;
\\int *get_bar() { return &bar; }
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42 42 1\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testCopyrelAlignment(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "copyrel-alignment", opts);
const a_so = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(a_so, "__attribute__((aligned(32))) int foo = 5;", &.{});
const b_so = addSharedLibrary(b, opts, .{ .name = "b" });
addCSourceBytes(b_so, "__attribute__((aligned(8))) int foo = 5;", &.{});
const c_so = addSharedLibrary(b, opts, .{ .name = "c" });
addCSourceBytes(c_so, "__attribute__((aligned(256))) int foo = 5;", &.{});
const obj = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include <stdio.h>
\\extern int foo;
\\int main() { printf("%d\n", foo); }
\\
,
.pic = false,
});
obj.root_module.link_libc = true;
const exp_stdout = "5\n";
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(a_so);
exe.root_module.link_libc = true;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("section headers");
check.checkExact("name .copyrel");
check.checkExact("addralign 20");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(b_so);
exe.root_module.link_libc = true;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("section headers");
check.checkExact("name .copyrel");
check.checkExact("addralign 8");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(c_so);
exe.root_module.link_libc = true;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("section headers");
check.checkExact("name .copyrel");
check.checkExact("addralign 100");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testDsoPlt(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dso-plt", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "dso" });
addCSourceBytes(dso,
\\#include<stdio.h>
\\void world() {
\\ printf("world\n");
\\}
\\void real_hello() {
\\ printf("Hello ");
\\ world();
\\}
\\void hello() {
\\ real_hello();
\\}
, &.{});
dso.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "test" });
addCSourceBytes(exe,
\\#include<stdio.h>
\\void world() {
\\ printf("WORLD\n");
\\}
\\void hello();
\\int main() {
\\ hello();
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello WORLD\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testDsoUndef(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dso-undef", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "dso" });
addCSourceBytes(dso,
\\extern int foo;
\\int bar = 5;
\\int baz() { return foo; }
, &.{});
dso.root_module.link_libc = true;
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes = "int foo = 3;",
});
const lib = addStaticLibrary(b, opts, .{ .name = "lib" });
lib.root_module.addObject(obj);
const exe = addExecutable(b, opts, .{ .name = "test" });
exe.root_module.linkLibrary(dso);
exe.root_module.linkLibrary(lib);
addCSourceBytes(exe,
\\extern int bar;
\\int main() {
\\ return bar - 5;
\\}
, &.{});
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInDynamicSymtab();
check.checkContains("foo");
test_step.dependOn(&check.step);
return test_step;
}
fn testEmitRelocatable(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "emit-relocatable", opts);
const a_o = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
\\const std = @import("std");
\\extern var bar: i32;
\\export fn foo() i32 {
\\ return bar;
\\}
\\export fn printFoo() void {
\\ std.debug.print("foo={d}\n", .{foo()});
\\}
});
a_o.root_module.link_libc = true;
const b_o = addObject(b, opts, .{ .name = "b", .c_source_bytes =
\\#include <stdio.h>
\\int bar = 42;
\\void printBar() {
\\ fprintf(stderr, "bar=%d\n", bar);
\\}
});
b_o.root_module.link_libc = true;
const c_o = addObject(b, opts, .{ .name = "c" });
c_o.root_module.addObject(a_o);
c_o.root_module.addObject(b_o);
const exe = addExecutable(b, opts, .{ .name = "test", .zig_source_bytes =
\\const std = @import("std");
\\extern fn printFoo() void;
\\extern fn printBar() void;
\\pub fn main() void {
\\ printFoo();
\\ printBar();
\\}
});
exe.root_module.addObject(c_o);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdErrEqual(
\\foo=42
\\bar=42
\\
);
test_step.dependOn(&run.step);
return test_step;
}
fn testEmitStaticLib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "emit-static-lib", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.c_source_bytes =
\\int foo = 0;
\\int bar = 2;
\\int fooBar() {
\\ return foo + bar;
\\}
,
});
const obj2 = addObject(b, opts, .{
.name = "obj2",
.c_source_bytes = "int tentative;",
.c_source_flags = &.{"-fcommon"},
});
const obj3 = addObject(b, opts, .{
.name = "a_very_long_file_name_so_that_it_ends_up_in_strtab",
.zig_source_bytes =
\\fn weakFoo() callconv(.c) usize {
\\ return 42;
\\}
\\export var strongBar: usize = 100;
\\comptime {
\\ @export(&weakFoo, .{ .name = "weakFoo", .linkage = .weak });
\\ @export(&strongBar, .{ .name = "strongBarAlias", .linkage = .strong });
\\}
,
});
const lib = addStaticLibrary(b, opts, .{ .name = "lib" });
lib.root_module.addObject(obj1);
lib.root_module.addObject(obj2);
lib.root_module.addObject(obj3);
const check = lib.checkObject();
check.checkInArchiveSymtab();
check.checkExact("in object obj1.o");
check.checkExact("foo");
check.checkInArchiveSymtab();
check.checkExact("in object obj1.o");
check.checkExact("bar");
check.checkInArchiveSymtab();
check.checkExact("in object obj1.o");
check.checkExact("fooBar");
check.checkInArchiveSymtab();
check.checkExact("in object obj2.o");
check.checkExact("tentative");
check.checkInArchiveSymtab();
check.checkExact("in object a_very_long_file_name_so_that_it_ends_up_in_strtab.o");
check.checkExact("weakFoo");
check.checkInArchiveSymtab();
check.checkExact("in object a_very_long_file_name_so_that_it_ends_up_in_strtab.o");
check.checkExact("strongBar");
check.checkInArchiveSymtab();
check.checkExact("in object a_very_long_file_name_so_that_it_ends_up_in_strtab.o");
check.checkExact("strongBarAlias");
test_step.dependOn(&check.step);
return test_step;
}
fn testEmitStaticLibZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "emit-static-lib-zig", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.zig_source_bytes =
\\export var foo: i32 = 42;
\\export var bar: i32 = 2;
,
});
const lib = addStaticLibrary(b, opts, .{
.name = "lib",
.zig_source_bytes =
\\extern var foo: i32;
\\extern var bar: i32;
\\export fn fooBar() i32 {
\\ return foo + bar;
\\}
,
});
lib.root_module.addObject(obj1);
const exe = addExecutable(b, opts, .{
.name = "test",
.zig_source_bytes =
\\const std = @import("std");
\\extern fn fooBar() i32;
\\pub fn main() void {
\\ std.debug.print("{d}", .{fooBar()});
\\}
,
});
exe.root_module.linkLibrary(lib);
const run = addRunArtifact(exe);
run.expectStdErrEqual("44");
test_step.dependOn(&run.step);
return test_step;
}
fn testEmptyObject(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "empty-object", opts);
const exe = addExecutable(b, opts, .{ .name = "test" });
addCSourceBytes(exe, "int main() { return 0; }", &.{});
addCSourceBytes(exe, "", &.{});
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testEntryPoint(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "entry-point", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.asm_source_bytes =
\\.globl foo, bar
\\foo = 0x1000
\\bar = 0x2000
\\
,
});
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes = "int main() { return 0; }",
});
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.entry = .{ .symbol_name = "foo" };
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("entry 1000");
test_step.dependOn(&check.step);
}
{
// TODO looks like not assigning a unique name to this executable will
// cause an artifact collision taking the cached executable from the above
// step instead of generating a new one.
const exe = addExecutable(b, opts, .{ .name = "other" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.entry = .{ .symbol_name = "bar" };
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("entry 2000");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testExportDynamic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "export-dynamic", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.asm_source_bytes =
\\.text
\\ .globl foo
\\ .hidden foo
\\foo:
\\ nop
\\ .globl bar
\\bar:
\\ nop
\\ .globl _start
\\_start:
\\ nop
\\
,
});
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso, "int baz = 10;", &.{});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\extern int baz;
\\int callBaz() {
\\ return baz;
\\}
, &.{});
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(dso);
exe.rdynamic = true;
const check = exe.checkObject();
check.checkInDynamicSymtab();
check.checkContains("bar");
check.checkInDynamicSymtab();
check.checkContains("_start");
test_step.dependOn(&check.step);
return test_step;
}
fn testExportSymbolsFromExe(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "export-symbols-from-exe", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\void expfn1();
\\void expfn2() {}
\\
\\void foo() {
\\ expfn1();
\\}
, &.{});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\void expfn1() {}
\\void expfn2() {}
\\void foo();
\\
\\int main() {
\\ expfn1();
\\ expfn2();
\\ foo();
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInDynamicSymtab();
check.checkContains("expfn2");
check.checkInDynamicSymtab();
check.checkContains("expfn1");
test_step.dependOn(&check.step);
return test_step;
}
fn testFuncAddress(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "func-address", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso, "void fn() {}", &.{});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <assert.h>
\\typedef void Func();
\\void fn();
\\Func *const ptr = fn;
\\int main() {
\\ assert(fn == ptr);
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.pic = false;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testGcSections(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "gc-sections", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.cpp_source_bytes =
\\#include <stdio.h>
\\int two() { return 2; }
\\int live_var1 = 1;
\\int live_var2 = two();
\\int dead_var1 = 3;
\\int dead_var2 = 4;
\\void live_fn1() {}
\\void live_fn2() { live_fn1(); }
\\void dead_fn1() {}
\\void dead_fn2() { dead_fn1(); }
\\int main() {
\\ printf("%d %d\n", live_var1, live_var2);
\\ live_fn2();
\\}
,
});
obj.link_function_sections = true;
obj.link_data_sections = true;
obj.root_module.link_libc = true;
obj.root_module.link_libcpp = true;
{
const exe = addExecutable(b, opts, .{ .name = "test" });
exe.root_module.addObject(obj);
exe.link_gc_sections = false;
exe.root_module.link_libc = true;
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("live_var1");
check.checkInSymtab();
check.checkContains("live_var2");
check.checkInSymtab();
check.checkContains("dead_var1");
check.checkInSymtab();
check.checkContains("dead_var2");
check.checkInSymtab();
check.checkContains("live_fn1");
check.checkInSymtab();
check.checkContains("live_fn2");
check.checkInSymtab();
check.checkContains("dead_fn1");
check.checkInSymtab();
check.checkContains("dead_fn2");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "test" });
exe.root_module.addObject(obj);
exe.link_gc_sections = true;
exe.root_module.link_libc = true;
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("live_var1");
check.checkInSymtab();
check.checkContains("live_var2");
check.checkInSymtab();
check.checkNotPresent("dead_var1");
check.checkInSymtab();
check.checkNotPresent("dead_var2");
check.checkInSymtab();
check.checkContains("live_fn1");
check.checkInSymtab();
check.checkContains("live_fn2");
check.checkInSymtab();
check.checkNotPresent("dead_fn1");
check.checkInSymtab();
check.checkNotPresent("dead_fn2");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testGcSectionsZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "gc-sections-zig", opts);
const obj = addObject(b, .{
.target = opts.target,
.use_llvm = true,
}, .{
.name = "obj",
.c_source_bytes =
\\int live_var1 = 1;
\\int live_var2 = 2;
\\int dead_var1 = 3;
\\int dead_var2 = 4;
\\void live_fn1() {}
\\void live_fn2() { live_fn1(); }
\\void dead_fn1() {}
\\void dead_fn2() { dead_fn1(); }
,
});
obj.link_function_sections = true;
obj.link_data_sections = true;
{
const exe = addExecutable(b, opts, .{
.name = "test1",
.zig_source_bytes =
\\const std = @import("std");
\\extern var live_var1: i32;
\\extern var live_var2: i32;
\\extern fn live_fn2() void;
\\pub fn main() void {
\\ var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
\\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail");
\\ live_fn2();
\\}
,
});
exe.root_module.addObject(obj);
exe.link_gc_sections = false;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("live_var1");
check.checkInSymtab();
check.checkContains("live_var2");
check.checkInSymtab();
check.checkContains("dead_var1");
check.checkInSymtab();
check.checkContains("dead_var2");
check.checkInSymtab();
check.checkContains("live_fn1");
check.checkInSymtab();
check.checkContains("live_fn2");
check.checkInSymtab();
check.checkContains("dead_fn1");
check.checkInSymtab();
check.checkContains("dead_fn2");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{
.name = "test2",
.zig_source_bytes =
\\const std = @import("std");
\\extern var live_var1: i32;
\\extern var live_var2: i32;
\\extern fn live_fn2() void;
\\pub fn main() void {
\\ var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
\\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail");
\\ live_fn2();
\\}
,
});
exe.root_module.addObject(obj);
exe.link_gc_sections = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("live_var1");
check.checkInSymtab();
check.checkContains("live_var2");
check.checkInSymtab();
check.checkNotPresent("dead_var1");
check.checkInSymtab();
check.checkNotPresent("dead_var2");
check.checkInSymtab();
check.checkContains("live_fn1");
check.checkInSymtab();
check.checkContains("live_fn2");
check.checkInSymtab();
check.checkNotPresent("dead_fn1");
check.checkInSymtab();
check.checkNotPresent("dead_fn2");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testHiddenWeakUndef(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "hidden-weak-undef", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\__attribute__((weak, visibility("hidden"))) void foo();
\\void bar() { foo(); }
, &.{});
const check = dso.checkObject();
check.checkInDynamicSymtab();
check.checkNotPresent("foo");
check.checkInDynamicSymtab();
check.checkContains("bar");
test_step.dependOn(&check.step);
return test_step;
}
fn testIFuncAlias(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-alias", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <assert.h>
\\void foo() {}
\\int bar() __attribute__((ifunc("resolve_bar")));
\\void *resolve_bar() { return foo; }
\\void *bar2 = bar;
\\int main() {
\\ assert(bar == bar2);
\\}
, &.{});
exe.root_module.pic = true;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testIFuncDlopen(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-dlopen", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\__attribute__((ifunc("resolve_foo")))
\\void foo(void);
\\static void real_foo(void) {
\\}
\\typedef void Func();
\\static Func *resolve_foo(void) {
\\ return real_foo;
\\}
, &.{});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <dlfcn.h>
\\#include <assert.h>
\\#include <stdlib.h>
\\typedef void Func();
\\void foo(void);
\\int main() {
\\ void *handle = dlopen(NULL, RTLD_NOW);
\\ Func *p = dlsym(handle, "foo");
\\
\\ foo();
\\ p();
\\ assert(foo == p);
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
exe.root_module.linkSystemLibrary("dl", .{});
exe.root_module.pic = false;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testIFuncDso(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-dso", opts);
const dso = addSharedLibrary(b, opts, .{
.name = "a",
.c_source_bytes =
\\#include<stdio.h>
\\__attribute__((ifunc("resolve_foobar")))
\\void foobar(void);
\\static void real_foobar(void) {
\\ printf("Hello world\n");
\\}
\\typedef void Func();
\\static Func *resolve_foobar(void) {
\\ return real_foobar;
\\}
,
});
dso.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{
.name = "main",
.c_source_bytes =
\\void foobar(void);
\\int main() {
\\ foobar();
\\}
,
});
exe.root_module.linkLibrary(dso);
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testIFuncDynamic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-dynamic", opts);
const main_c =
\\#include <stdio.h>
\\__attribute__((ifunc("resolve_foobar")))
\\static void foobar(void);
\\static void real_foobar(void) {
\\ printf("Hello world\n");
\\}
\\typedef void Func();
\\static Func *resolve_foobar(void) {
\\ return real_foobar;
\\}
\\int main() {
\\ foobar();
\\}
;
{
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe, main_c, &.{});
exe.root_module.link_libc = true;
exe.link_z_lazy = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "other" });
addCSourceBytes(exe, main_c, &.{});
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testIFuncExport(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-export", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\#include <stdio.h>
\\__attribute__((ifunc("resolve_foobar")))
\\void foobar(void);
\\void real_foobar(void) {
\\ printf("Hello world\n");
\\}
\\typedef void Func();
\\Func *resolve_foobar(void) {
\\ return real_foobar;
\\}
, &.{});
dso.root_module.link_libc = true;
const check = dso.checkObject();
check.checkInDynamicSymtab();
check.checkContains("IFUNC GLOBAL DEFAULT foobar");
test_step.dependOn(&check.step);
return test_step;
}
fn testIFuncFuncPtr(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-func-ptr", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\typedef int Fn();
\\int foo() __attribute__((ifunc("resolve_foo")));
\\int real_foo() { return 3; }
\\Fn *resolve_foo(void) {
\\ return real_foo;
\\}
, &.{});
addCSourceBytes(exe,
\\typedef int Fn();
\\int foo();
\\Fn *get_foo() { return foo; }
, &.{});
addCSourceBytes(exe,
\\#include <stdio.h>
\\typedef int Fn();
\\Fn *get_foo();
\\int main() {
\\ Fn *f = get_foo();
\\ printf("%d\n", f());
\\}
, &.{});
exe.root_module.pic = true;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testIFuncNoPlt(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-noplt", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\__attribute__((ifunc("resolve_foo")))
\\void foo(void);
\\void hello(void) {
\\ printf("Hello world\n");
\\}
\\typedef void Fn();
\\Fn *resolve_foo(void) {
\\ return hello;
\\}
\\int main() {
\\ foo();
\\}
, &.{"-fno-plt"});
exe.root_module.pic = true;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testIFuncStatic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-static", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\void foo() __attribute__((ifunc("resolve_foo")));
\\void hello() {
\\ printf("Hello world\n");
\\}
\\void *resolve_foo() {
\\ return hello;
\\}
\\int main() {
\\ foo();
\\ return 0;
\\}
, &.{});
exe.root_module.link_libc = true;
exe.linkage = .static;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testIFuncStaticPie(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-static-pie", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\void foo() __attribute__((ifunc("resolve_foo")));
\\void hello() {
\\ printf("Hello world\n");
\\}
\\void *resolve_foo() {
\\ return hello;
\\}
\\int main() {
\\ foo();
\\ return 0;
\\}
, &.{});
exe.linkage = .static;
exe.root_module.pic = true;
exe.pie = true;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("type DYN");
check.checkInHeaders();
check.checkExact("section headers");
check.checkExact("name .dynamic");
check.checkInHeaders();
check.checkExact("section headers");
check.checkNotPresent("name .interp");
test_step.dependOn(&check.step);
return test_step;
}
fn testImageBase(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "image-base", opts);
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\int main() {
\\ printf("Hello World!\n");
\\ return 0;
\\}
, &.{});
exe.root_module.link_libc = true;
exe.image_base = 0x8000000;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello World!\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExtract("entry {addr}");
check.checkComputeCompare("addr", .{ .op = .gte, .value = .{ .literal = 0x8000000 } });
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
addCSourceBytes(exe, "void _start() {}", &.{});
exe.image_base = 0xffffffff8000000;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExtract("entry {addr}");
check.checkComputeCompare("addr", .{ .op = .gte, .value = .{ .literal = 0xffffffff8000000 } });
test_step.dependOn(&check.step);
}
return test_step;
}
fn testImportingDataDynamic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "importing-data-dynamic", opts);
const dso = addSharedLibrary(b, .{
.target = opts.target,
.optimize = opts.optimize,
.use_llvm = true,
}, .{
.name = "a",
.c_source_bytes =
\\#include <stdio.h>
\\int foo = 42;
\\void printFoo() { fprintf(stderr, "lib foo=%d\n", foo); }
,
});
dso.root_module.link_libc = true;
const main = addExecutable(b, opts, .{
.name = "main",
.zig_source_bytes =
\\const std = @import("std");
\\extern var foo: i32;
\\extern fn printFoo() void;
\\pub fn main() void {
\\ std.debug.print("exe foo={d}\n", .{foo});
\\ printFoo();
\\ foo += 1;
\\ std.debug.print("exe foo={d}\n", .{foo});
\\ printFoo();
\\}
,
.strip = true, // TODO temp hack
});
main.pie = true;
main.root_module.linkLibrary(dso);
const run = addRunArtifact(main);
run.expectStdErrEqual(
\\exe foo=42
\\lib foo=42
\\exe foo=43
\\lib foo=43
\\
);
test_step.dependOn(&run.step);
return test_step;
}
fn testImportingDataStatic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "importing-data-static", opts);
const obj = addObject(b, .{
.target = opts.target,
.optimize = opts.optimize,
.use_llvm = true,
}, .{
.name = "a",
.c_source_bytes = "int foo = 42;",
});
const lib = addStaticLibrary(b, .{
.target = opts.target,
.optimize = opts.optimize,
.use_llvm = true,
}, .{
.name = "a",
});
lib.root_module.addObject(obj);
const main = addExecutable(b, opts, .{
.name = "main",
.zig_source_bytes =
\\extern var foo: i32;
\\pub fn main() void {
\\ @import("std").debug.print("{d}\n", .{foo});
\\}
,
.strip = true, // TODO temp hack
});
main.root_module.linkLibrary(lib);
main.root_module.link_libc = true;
const run = addRunArtifact(main);
run.expectStdErrEqual("42\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testInitArrayOrder(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "init-array-order", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((constructor(10000))) void init4() { printf("1"); }
,
});
a_o.root_module.link_libc = true;
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((constructor(1000))) void init3() { printf("2"); }
,
});
b_o.root_module.link_libc = true;
const c_o = addObject(b, opts, .{
.name = "c",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((constructor)) void init1() { printf("3"); }
,
});
c_o.root_module.link_libc = true;
const d_o = addObject(b, opts, .{
.name = "d",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((constructor)) void init2() { printf("4"); }
,
});
d_o.root_module.link_libc = true;
const e_o = addObject(b, opts, .{
.name = "e",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((destructor(10000))) void fini4() { printf("5"); }
,
});
e_o.root_module.link_libc = true;
const f_o = addObject(b, opts, .{
.name = "f",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((destructor(1000))) void fini3() { printf("6"); }
,
});
f_o.root_module.link_libc = true;
const g_o = addObject(b, opts, .{
.name = "g",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((destructor)) void fini1() { printf("7"); }
,
});
g_o.root_module.link_libc = true;
const h_o = addObject(b, opts, .{ .name = "h", .c_source_bytes =
\\#include <stdio.h>
\\__attribute__((destructor)) void fini2() { printf("8"); }
});
h_o.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe, "int main() { return 0; }", &.{});
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(c_o);
exe.root_module.addObject(d_o);
exe.root_module.addObject(e_o);
exe.root_module.addObject(f_o);
exe.root_module.addObject(g_o);
exe.root_module.addObject(h_o);
if (opts.target.result.isGnuLibC()) {
// TODO I think we need to clarify our use of `-fPIC -fPIE` flags for different targets
exe.pie = true;
}
const run = addRunArtifact(exe);
run.expectStdOutEqual("21348756");
test_step.dependOn(&run.step);
return test_step;
}
fn testLargeAlignmentDso(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "large-alignment-dso", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "dso" });
addCSourceBytes(dso,
\\#include <stdio.h>
\\#include <stdint.h>
\\void hello() __attribute__((aligned(32768), section(".hello")));
\\void world() __attribute__((aligned(32768), section(".world")));
\\void hello() {
\\ printf("Hello");
\\}
\\void world() {
\\ printf(" world");
\\}
\\void greet() {
\\ hello();
\\ world();
\\}
, &.{});
dso.link_function_sections = true;
dso.root_module.link_libc = true;
const check = dso.checkObject();
check.checkInSymtab();
check.checkExtract("{addr1} {size1} {shndx1} FUNC GLOBAL DEFAULT hello");
check.checkInSymtab();
check.checkExtract("{addr2} {size2} {shndx2} FUNC GLOBAL DEFAULT world");
check.checkComputeCompare("addr1 16 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("addr2 16 %", .{ .op = .eq, .value = .{ .literal = 0 } });
test_step.dependOn(&check.step);
const exe = addExecutable(b, opts, .{ .name = "test" });
addCSourceBytes(exe,
\\void greet();
\\int main() { greet(); }
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world");
test_step.dependOn(&run.step);
return test_step;
}
fn testLargeAlignmentExe(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "large-alignment-exe", opts);
const exe = addExecutable(b, opts, .{ .name = "test" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\#include <stdint.h>
\\
\\void hello() __attribute__((aligned(32768), section(".hello")));
\\void world() __attribute__((aligned(32768), section(".world")));
\\
\\void hello() {
\\ printf("Hello");
\\}
\\
\\void world() {
\\ printf(" world");
\\}
\\
\\int main() {
\\ hello();
\\ world();
\\}
, &.{});
exe.link_function_sections = true;
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInSymtab();
check.checkExtract("{addr1} {size1} {shndx1} FUNC LOCAL DEFAULT hello");
check.checkInSymtab();
check.checkExtract("{addr2} {size2} {shndx2} FUNC LOCAL DEFAULT world");
check.checkComputeCompare("addr1 16 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("addr2 16 %", .{ .op = .eq, .value = .{ .literal = 0 } });
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world");
test_step.dependOn(&run.step);
return test_step;
}
fn testLargeBss(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "large-bss", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\char arr[0x100000000];
\\int main() {
\\ return arr[2000];
\\}
, &.{});
exe.root_module.link_libc = true;
// Disabled to work around the ELF linker crashing.
// Can be reproduced on a x86_64-linux host by commenting out the line below.
exe.root_module.sanitize_c = .off;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testLinkOrder(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "link-order", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes = "void foo() {}",
.pic = true,
});
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(obj);
const lib = addStaticLibrary(b, opts, .{ .name = "b" });
lib.root_module.addObject(obj);
const main_o = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\void foo();
\\int main() {
\\ foo();
\\}
,
});
// https://github.com/ziglang/zig/issues/17450
// {
// const exe = addExecutable(b, opts, .{ .name = "main1"});
// exe.root_module.addObject(main_o);
// exe.root_module.linkSystemLibrary("a", .{});
// exe.root_module.addLibraryPath(dso.getEmittedBinDirectory());
// exe.root_module.addRPath(dso.getEmittedBinDirectory());
// exe.root_module.linkSystemLibrary("b", .{});
// exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
// exe.root_module.addRPath(lib.getEmittedBinDirectory());
// exe.root_module.link_libc = true;
// const check = exe.checkObject();
// check.checkInDynamicSection();
// check.checkContains("libb.so");
// test_step.dependOn(&check.step);
// }
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("b", .{});
exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
exe.root_module.addRPath(lib.getEmittedBinDirectory());
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(dso.getEmittedBinDirectory());
exe.root_module.addRPath(dso.getEmittedBinDirectory());
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInDynamicSection();
check.checkNotPresent("libb.so");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testLdScript(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ld-script", opts);
const bar = addSharedLibrary(b, opts, .{ .name = "bar" });
addCSourceBytes(bar, "int bar() { return 42; }", &.{});
const baz = addSharedLibrary(b, opts, .{ .name = "baz" });
addCSourceBytes(baz, "int baz() { return 42; }", &.{});
const scripts = WriteFile.create(b);
_ = scripts.add("liba.so", "INPUT(libfoo.so libfoo2.so.1)");
_ = scripts.add("libfoo.so", "GROUP(AS_NEEDED(-lbar))");
// Check finding a versioned .so file that is elsewhere in the library search paths.
const scripts2 = WriteFile.create(b);
_ = scripts2.add("libfoo2.so.1", "GROUP(AS_NEEDED(-lbaz))");
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\int bar();
\\int baz();
\\int main() {
\\ return bar() - baz();
\\}
, &.{});
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(scripts.getDirectory());
exe.root_module.addLibraryPath(scripts2.getDirectory());
exe.root_module.addLibraryPath(bar.getEmittedBinDirectory());
exe.root_module.addLibraryPath(baz.getEmittedBinDirectory());
exe.root_module.addRPath(bar.getEmittedBinDirectory());
exe.root_module.addRPath(baz.getEmittedBinDirectory());
exe.root_module.link_libc = true;
exe.allow_so_scripts = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testLdScriptPathError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ld-script-path-error", opts);
const scripts = WriteFile.create(b);
_ = scripts.add("liba.so", "INPUT(libfoo.so)");
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe, "int main() { return 0; }", &.{});
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(scripts.getDirectory());
exe.root_module.link_libc = true;
exe.allow_so_scripts = true;
// TODO: A future enhancement could make this error message also mention
// the file that references the missing library.
expectLinkErrors(exe, test_step, .{
.stderr_contains = "error: libfoo.so: file listed in linker script not found",
});
return test_step;
}
fn testLdScriptAllowUndefinedVersion(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ld-script-allow-undefined-version", opts);
const so = addSharedLibrary(b, opts, .{
.name = "add",
.zig_source_bytes =
\\export fn add(a: i32, b: i32) i32 {
\\ return a + b;
\\}
,
});
const ld = b.addWriteFiles().add("add.ld", "VERSION { ADD_1.0 { global: add; sub; local: *; }; }");
so.setLinkerScript(ld);
so.linker_allow_undefined_version = true;
const exe = addExecutable(b, opts, .{
.name = "main",
.zig_source_bytes =
\\const std = @import("std");
\\extern fn add(a: i32, b: i32) i32;
\\pub fn main() void {
\\ std.debug.print("{d}\n", .{add(1, 2)});
\\}
,
});
exe.root_module.linkLibrary(so);
exe.root_module.link_libc = true;
exe.allow_so_scripts = true;
const run = addRunArtifact(exe);
run.expectStdErrEqual("3\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testLdScriptDisallowUndefinedVersion(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ld-script-disallow-undefined-version", opts);
const so = addSharedLibrary(b, opts, .{
.name = "add",
.zig_source_bytes =
\\export fn add(a: i32, b: i32) i32 {
\\ return a + b;
\\}
,
});
const ld = b.addWriteFiles().add("add.ld", "VERSION { ADD_1.0 { global: add; sub; local: *; }; }");
so.setLinkerScript(ld);
so.linker_allow_undefined_version = false;
so.allow_so_scripts = true;
expectLinkErrors(
so,
test_step,
.{
.contains = "error: ld.lld: version script assignment of 'ADD_1.0' to symbol 'sub' failed: symbol not defined",
},
);
return test_step;
}
fn testMismatchedCpuArchitectureError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "mismatched-cpu-architecture-error", opts);
const obj = addObject(b, .{
.target = b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .gnu }),
}, .{
.name = "a",
.c_source_bytes = "int foo;",
.strip = true,
});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\extern int foo;
\\int main() {
\\ return foo;
\\}
, &.{});
exe.root_module.addObject(obj);
exe.root_module.link_libc = true;
expectLinkErrors(exe, test_step, .{ .exact = &.{
"invalid ELF machine type: AARCH64",
"note: while parsing /?/a.o",
} });
return test_step;
}
fn testLinkingC(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linking-c", opts);
const exe = addExecutable(b, opts, .{ .name = "test" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\int main() {
\\ printf("Hello World!\n");
\\ return 0;
\\}
, &.{});
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello World!\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("type EXEC");
check.checkInHeaders();
check.checkExact("section headers");
check.checkNotPresent("name .dynamic");
test_step.dependOn(&check.step);
return test_step;
}
fn testLinkingCpp(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linking-cpp", opts);
const exe = addExecutable(b, opts, .{ .name = "test" });
addCppSourceBytes(exe,
\\#include <iostream>
\\int main() {
\\ std::cout << "Hello World!" << std::endl;
\\ return 0;
\\}
, &.{});
exe.root_module.link_libc = true;
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello World!\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("type EXEC");
check.checkInHeaders();
check.checkExact("section headers");
check.checkNotPresent("name .dynamic");
test_step.dependOn(&check.step);
return test_step;
}
fn testLinkingObj(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linking-obj", opts);
const obj = addObject(b, opts, .{
.name = "aobj",
.zig_source_bytes =
\\extern var mod: usize;
\\export fn callMe() usize {
\\ return me * mod;
\\}
\\var me: usize = 42;
,
});
const exe = addExecutable(b, opts, .{
.name = "testobj",
.zig_source_bytes =
\\const std = @import("std");
\\extern fn callMe() usize;
\\export var mod: usize = 2;
\\pub fn main() void {
\\ std.debug.print("{d}\n", .{callMe()});
\\}
,
});
exe.root_module.addObject(obj);
const run = addRunArtifact(exe);
run.expectStdErrEqual("84\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testLinkingStaticLib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linking-static-lib", opts);
const obj = addObject(b, opts, .{
.name = "bobj",
.zig_source_bytes = "export var bar: i32 = -42;",
});
const lib = addStaticLibrary(b, opts, .{
.name = "alib",
.zig_source_bytes =
\\export fn foo() i32 {
\\ return 42;
\\}
,
});
lib.root_module.addObject(obj);
const exe = addExecutable(b, opts, .{
.name = "testlib",
.zig_source_bytes =
\\const std = @import("std");
\\extern fn foo() i32;
\\extern var bar: i32;
\\pub fn main() void {
\\ std.debug.print("{d}\n", .{foo() + bar});
\\}
,
});
exe.root_module.linkLibrary(lib);
const run = addRunArtifact(exe);
run.expectStdErrEqual("0\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testLinkingZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linking-zig-static", opts);
const exe = addExecutable(b, opts, .{
.name = "test",
.zig_source_bytes =
\\pub fn main() void {
\\ @import("std").debug.print("Hello World!\n", .{});
\\}
,
});
const run = addRunArtifact(exe);
run.expectStdErrEqual("Hello World!\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("type EXEC");
check.checkInHeaders();
check.checkExact("section headers");
check.checkNotPresent("name .dynamic");
test_step.dependOn(&check.step);
return test_step;
}
fn testLinksection(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linksection", opts);
const obj = addObject(b, opts, .{ .name = "main", .zig_source_bytes =
\\export var test_global: u32 linksection(".TestGlobal") = undefined;
\\export fn testFn() linksection(".TestFn") callconv(.c) void {
\\ TestGenericFn("A").f();
\\}
\\fn TestGenericFn(comptime suffix: []const u8) type {
\\ return struct {
\\ fn f() linksection(".TestGenFn" ++ suffix) void {}
\\ };
\\}
});
const check = obj.checkObject();
check.checkInSymtab();
check.checkContains("SECTION LOCAL DEFAULT .TestGlobal");
check.checkInSymtab();
check.checkContains("SECTION LOCAL DEFAULT .TestFn");
check.checkInSymtab();
check.checkContains("SECTION LOCAL DEFAULT .TestGenFnA");
check.checkInSymtab();
check.checkContains("OBJECT GLOBAL DEFAULT test_global");
check.checkInSymtab();
check.checkContains("FUNC GLOBAL DEFAULT testFn");
if (opts.optimize == .Debug) {
check.checkInSymtab();
check.checkContains("FUNC LOCAL DEFAULT main.TestGenericFn(");
}
test_step.dependOn(&check.step);
return test_step;
}
// Adapted from https://github.com/rui314/mold/blob/main/test/elf/mergeable-strings.sh
fn testMergeStrings(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-strings", opts);
const obj1 = addObject(b, opts, .{ .name = "a.o" });
addCSourceBytes(obj1,
\\#include <uchar.h>
\\#include <wchar.h>
\\char *cstr1 = "foo";
\\wchar_t *wide1 = L"foo";
\\char16_t *utf16_1 = u"foo";
\\char32_t *utf32_1 = U"foo";
, &.{"-O2"});
obj1.root_module.link_libc = true;
const obj2 = addObject(b, opts, .{ .name = "b.o" });
addCSourceBytes(obj2,
\\#include <stdio.h>
\\#include <assert.h>
\\#include <uchar.h>
\\#include <wchar.h>
\\extern char *cstr1;
\\extern wchar_t *wide1;
\\extern char16_t *utf16_1;
\\extern char32_t *utf32_1;
\\char *cstr2 = "foo";
\\wchar_t *wide2 = L"foo";
\\char16_t *utf16_2 = u"foo";
\\char32_t *utf32_2 = U"foo";
\\int main() {
\\ printf("%p %p %p %p %p %p %p %p\n",
\\ cstr1, cstr2, wide1, wide2, utf16_1, utf16_2, utf32_1, utf32_2);
\\ assert((void*)cstr1 == (void*)cstr2);
\\ assert((void*)wide1 == (void*)wide2);
\\ assert((void*)utf16_1 == (void*)utf16_2);
\\ assert((void*)utf32_1 == (void*)utf32_2);
\\ assert((void*)wide1 == (void*)utf32_1);
\\ assert((void*)cstr1 != (void*)wide1);
\\ assert((void*)cstr1 != (void*)utf32_1);
\\ assert((void*)wide1 != (void*)utf16_1);
\\}
, &.{"-O2"});
obj2.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj2);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testMergeStrings2(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-strings2", opts);
const obj1 = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
\\const std = @import("std");
\\export fn foo() void {
\\ var arr: [5:0]u16 = [_:0]u16{ 1, 2, 3, 4, 5 };
\\ const slice = std.mem.sliceTo(&arr, 3);
\\ std.testing.expectEqualSlices(u16, arr[0..2], slice) catch unreachable;
\\}
});
const obj2 = addObject(b, opts, .{ .name = "b", .zig_source_bytes =
\\const std = @import("std");
\\extern fn foo() void;
\\pub fn main() void {
\\ foo();
\\ var arr: [5:0]u16 = [_:0]u16{ 5, 4, 3, 2, 1 };
\\ const slice = std.mem.sliceTo(&arr, 3);
\\ std.testing.expectEqualSlices(u16, arr[0..2], slice) catch unreachable;
\\}
});
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj2);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.dumpSection(".rodata.str");
check.checkContains("\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x00\x00");
check.dumpSection(".rodata.str");
check.checkContains("\x05\x00\x04\x00\x03\x00\x02\x00\x01\x00\x00\x00");
test_step.dependOn(&check.step);
}
{
const obj3 = addObject(b, opts, .{ .name = "c" });
obj3.root_module.addObject(obj1);
obj3.root_module.addObject(obj2);
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(obj3);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.dumpSection(".rodata.str");
check.checkContains("\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x00\x00");
check.dumpSection(".rodata.str");
check.checkContains("\x05\x00\x04\x00\x03\x00\x02\x00\x01\x00\x00\x00");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testNoEhFrameHdr(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "no-eh-frame-hdr", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe, "int main() { return 0; }", &.{});
exe.link_eh_frame_hdr = false;
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("section headers");
check.checkNotPresent("name .eh_frame_hdr");
test_step.dependOn(&check.step);
return test_step;
}
fn testPie(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "hello-pie", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\int main() {
\\ printf("Hello!\n");
\\ return 0;
\\}
, &.{});
exe.root_module.link_libc = true;
exe.root_module.pic = true;
exe.pie = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello!\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("type DYN");
check.checkInHeaders();
check.checkExact("section headers");
check.checkExact("name .dynamic");
test_step.dependOn(&check.step);
return test_step;
}
fn testPltGot(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "plt-got", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\#include <stdio.h>
\\void ignore(void *foo) {}
\\void hello() {
\\ printf("Hello world\n");
\\}
, &.{});
dso.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\void ignore(void *);
\\int hello();
\\void foo() { ignore(hello); }
\\int main() { hello(); }
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.pic = true;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testPreinitArray(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "preinit-array", opts);
{
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes = "void _start() {}",
});
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(obj);
const check = exe.checkObject();
check.checkInDynamicSection();
check.checkNotPresent("PREINIT_ARRAY");
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
addCSourceBytes(exe,
\\void preinit_fn() {}
\\int main() {}
\\__attribute__((section(".preinit_array")))
\\void *preinit[] = { preinit_fn };
, &.{});
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInDynamicSection();
check.checkContains("PREINIT_ARRAY");
}
return test_step;
}
fn testRelocatableArchive(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable-archive", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.c_source_bytes =
\\void bar();
\\void foo() {
\\ bar();
\\}
,
});
const obj2 = addObject(b, opts, .{
.name = "obj2",
.c_source_bytes =
\\void bar() {}
,
});
const obj3 = addObject(b, opts, .{
.name = "obj3",
.c_source_bytes =
\\void baz();
,
});
const obj4 = addObject(b, opts, .{
.name = "obj4",
.c_source_bytes =
\\void foo();
\\int main() {
\\ foo();
\\}
,
});
const lib = addStaticLibrary(b, opts, .{ .name = "lib" });
lib.root_module.addObject(obj1);
lib.root_module.addObject(obj2);
lib.root_module.addObject(obj3);
const obj5 = addObject(b, opts, .{
.name = "obj5",
});
obj5.root_module.addObject(obj4);
obj5.root_module.linkLibrary(lib);
const check = obj5.checkObject();
check.checkInSymtab();
check.checkContains("foo");
check.checkInSymtab();
check.checkContains("bar");
check.checkInSymtab();
check.checkNotPresent("baz");
test_step.dependOn(&check.step);
return test_step;
}
fn testRelocatableEhFrame(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable-eh-frame", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.cpp_source_bytes =
\\#include <stdexcept>
\\int try_me() {
\\ throw std::runtime_error("Oh no!");
\\}
,
});
obj1.root_module.link_libcpp = true;
const obj2 = addObject(b, opts, .{
.name = "obj2",
.cpp_source_bytes =
\\extern int try_me();
\\int try_again() {
\\ return try_me();
\\}
,
});
obj2.root_module.link_libcpp = true;
const obj3 = addObject(b, opts, .{ .name = "obj3", .cpp_source_bytes =
\\#include <iostream>
\\#include <stdexcept>
\\extern int try_again();
\\int main() {
\\ try {
\\ try_again();
\\ } catch (const std::exception &e) {
\\ std::cout << "exception=" << e.what();
\\ }
\\ return 0;
\\}
});
obj3.root_module.link_libcpp = true;
{
const obj = addObject(b, opts, .{ .name = "obj" });
obj.root_module.addObject(obj1);
obj.root_module.addObject(obj2);
obj.root_module.link_libcpp = true;
const exe = addExecutable(b, opts, .{ .name = "test1" });
exe.root_module.addObject(obj3);
exe.root_module.addObject(obj);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("exception=Oh no!");
test_step.dependOn(&run.step);
}
{
// Flipping the order should not influence the end result.
const obj = addObject(b, opts, .{ .name = "obj" });
obj.root_module.addObject(obj2);
obj.root_module.addObject(obj1);
obj.root_module.link_libcpp = true;
const exe = addExecutable(b, opts, .{ .name = "test2" });
exe.root_module.addObject(obj3);
exe.root_module.addObject(obj);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("exception=Oh no!");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testRelocatableEhFrameComdatHeavy(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable-eh-frame-comdat-heavy", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.cpp_source_bytes =
\\#include <stdexcept>
\\int try_me() {
\\ throw std::runtime_error("Oh no!");
\\}
,
});
obj1.root_module.link_libcpp = true;
const obj2 = addObject(b, opts, .{
.name = "obj2",
.cpp_source_bytes =
\\extern int try_me();
\\int try_again() {
\\ return try_me();
\\}
,
});
obj2.root_module.link_libcpp = true;
const obj3 = addObject(b, opts, .{
.name = "obj3",
.cpp_source_bytes =
\\#include <iostream>
\\#include <stdexcept>
\\extern int try_again();
\\int main() {
\\ try {
\\ try_again();
\\ } catch (const std::exception &e) {
\\ std::cout << "exception=" << e.what();
\\ }
\\ return 0;
\\}
,
});
obj3.root_module.link_libcpp = true;
const obj = addObject(b, opts, .{ .name = "obj" });
obj.root_module.addObject(obj1);
obj.root_module.addObject(obj2);
obj.root_module.addObject(obj3);
obj.root_module.link_libcpp = true;
const exe = addExecutable(b, opts, .{ .name = "test2" });
exe.root_module.addObject(obj);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("exception=Oh no!");
test_step.dependOn(&run.step);
return test_step;
}
// Adapted from https://github.com/rui314/mold/blob/main/test/elf/relocatable-mergeable-sections.sh
fn testRelocatableMergeStrings(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable-merge-strings", opts);
const obj1 = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.section .rodata.str1.1,"aMS",@progbits,1
\\val1:
\\.ascii "Hello \0"
\\.section .rodata.str1.1,"aMS",@progbits,1
\\val5:
\\.ascii "World \0"
\\.section .rodata.str1.1,"aMS",@progbits,1
\\val7:
\\.ascii "Hello \0"
});
const obj2 = addObject(b, opts, .{ .name = "b" });
obj2.root_module.addObject(obj1);
const check = obj2.checkObject();
check.dumpSection(".rodata.str1.1");
check.checkExact("Hello \x00World \x00");
test_step.dependOn(&check.step);
return test_step;
}
fn testRelocatableNoEhFrame(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable-no-eh-frame", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.c_source_bytes = "int bar() { return 42; }",
.c_source_flags = &.{
"-fno-unwind-tables",
"-fno-asynchronous-unwind-tables",
},
});
const obj2 = addObject(b, opts, .{
.name = "obj2",
});
obj2.root_module.addObject(obj1);
const check1 = obj1.checkObject();
check1.checkInHeaders();
check1.checkExact("section headers");
check1.checkNotPresent(".eh_frame");
test_step.dependOn(&check1.step);
const check2 = obj2.checkObject();
check2.checkInHeaders();
check2.checkExact("section headers");
check2.checkNotPresent(".eh_frame");
test_step.dependOn(&check2.step);
return test_step;
}
fn testSharedAbsSymbol(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "shared-abs-symbol", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addAsmSourceBytes(dso,
\\.globl foo
\\foo = 3;
);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\#include <stdio.h>
\\extern char foo;
\\int main() { printf("foo=%p\n", &foo); }
,
.pic = true,
});
obj.root_module.link_libc = true;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(dso);
exe.pie = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("foo=0x3\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("type DYN");
// TODO fix/improve in CheckObject
// check.checkInSymtab();
// check.checkNotPresent("foo");
test_step.dependOn(&check.step);
}
// https://github.com/ziglang/zig/issues/17430
// {
// const exe = addExecutable(b, opts, .{ .name = "main2"});
// exe.root_module.addObject(obj);
// exe.root_module.linkLibrary(dso);
// exe.pie = false;
// const run = addRunArtifact(exe);
// run.expectStdOutEqual("foo=0x3\n");
// test_step.dependOn(&run.step);
// const check = exe.checkObject();
// check.checkInHeaders();
// check.checkExact("header");
// check.checkExact("type EXEC");
// // TODO fix/improve in CheckObject
// // check.checkInSymtab();
// // check.checkNotPresent("foo");
// test_step.dependOn(&check.step);
// }
return test_step;
}
fn testStrip(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "strip", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\#include <stdio.h>
\\int main() {
\\ printf("Hello!\n");
\\ return 0;
\\}
,
});
obj.root_module.link_libc = true;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(obj);
exe.root_module.strip = false;
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("section headers");
check.checkExact("name .debug_info");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(obj);
exe.root_module.strip = true;
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("section headers");
check.checkNotPresent("name .debug_info");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testThunks(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "thunks", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\void foo();
\\__attribute__((section(".bar"))) void bar() {
\\ return foo();
\\}
\\__attribute__((section(".foo"))) void foo() {
\\ return bar();
\\}
\\int main() {
\\ foo();
\\ bar();
\\ return 0;
\\}
});
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("foo$thunk");
check.checkInSymtab();
check.checkContains("bar$thunk");
test_step.dependOn(&check.step);
return test_step;
}
fn testTlsDfStaticTls(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-df-static-tls", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\static _Thread_local int foo = 5;
\\void mutate() { ++foo; }
\\int bar() { return foo; }
,
.c_source_flags = &.{"-ftls-model=initial-exec"},
.pic = true,
});
{
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(obj);
// dso.link_relax = true;
const check = dso.checkObject();
check.checkInDynamicSection();
check.checkContains("STATIC_TLS");
test_step.dependOn(&check.step);
}
// TODO add -Wl,--no-relax
// {
// const dso = addSharedLibrary(b, opts, .{ .name = "a"});
// dso.root_module.addObject(obj);
// dso.link_relax = false;
// const check = dso.checkObject();
// check.checkInDynamicSection();
// check.checkContains("STATIC_TLS");
// test_step.dependOn(&check.step);
// }
return test_step;
}
fn testTlsDso(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-dso", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\extern _Thread_local int foo;
\\_Thread_local int bar;
\\int get_foo1() { return foo; }
\\int get_bar1() { return bar; }
, &.{});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\_Thread_local int foo;
\\extern _Thread_local int bar;
\\int get_foo1();
\\int get_bar1();
\\int get_foo2() { return foo; }
\\int get_bar2() { return bar; }
\\int main() {
\\ foo = 5;
\\ bar = 3;
\\ printf("%d %d %d %d %d %d\n",
\\ foo, bar,
\\ get_foo1(), get_bar1(),
\\ get_foo2(), get_bar2());
\\ return 0;
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("5 3 5 3 5 3\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsGd(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-gd", opts);
const main_o = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x1 = 1;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x2;
\\__attribute__((tls_model("global-dynamic"))) extern _Thread_local int x3;
\\__attribute__((tls_model("global-dynamic"))) extern _Thread_local int x4;
\\int get_x5();
\\int get_x6();
\\int main() {
\\ x2 = 2;
\\ printf("%d %d %d %d %d %d\n", x1, x2, x3, x4, get_x5(), get_x6());
\\ return 0;
\\}
,
.pic = true,
});
main_o.root_module.link_libc = true;
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int x3 = 3;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x5 = 5;
\\int get_x5() { return x5; }
,
.pic = true,
});
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes =
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int x4 = 4;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x6 = 6;
\\int get_x6() { return x6; }
,
.pic = true,
});
const exp_stdout = "1 2 3 4 5 6\n";
const dso1 = addSharedLibrary(b, opts, .{ .name = "a" });
dso1.root_module.addObject(a_o);
const dso2 = addSharedLibrary(b, opts, .{ .name = "b" });
dso2.root_module.addObject(b_o);
// dso2.link_relax = false; // TODO
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.linkLibrary(dso1);
exe.root_module.linkLibrary(dso2);
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
// exe.link_relax = false; // TODO
exe.root_module.linkLibrary(dso1);
exe.root_module.linkLibrary(dso2);
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
// https://github.com/ziglang/zig/issues/17430 ??
// {
// const exe = addExecutable(b, opts, .{ .name = "main3"});
// exe.root_module.addObject(main_o);
// exe.root_module.linkLibrary(dso1);
// exe.root_module.linkLibrary(dso2);
// exe.linkage = .static;
// const run = addRunArtifact(exe);
// run.expectStdOutEqual(exp_stdout);
// test_step.dependOn(&run.step);
// }
// {
// const exe = addExecutable(b, opts, .{ .name = "main4"});
// exe.root_module.addObject(main_o);
// // exe.link_relax = false; // TODO
// exe.root_module.linkLibrary(dso1);
// exe.root_module.linkLibrary(dso2);
// exe.linkage = .static;
// const run = addRunArtifact(exe);
// run.expectStdOutEqual(exp_stdout);
// test_step.dependOn(&run.step);
// }
return test_step;
}
fn testTlsGdNoPlt(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-gd-no-plt", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x1 = 1;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x2;
\\__attribute__((tls_model("global-dynamic"))) extern _Thread_local int x3;
\\__attribute__((tls_model("global-dynamic"))) extern _Thread_local int x4;
\\int get_x5();
\\int get_x6();
\\int main() {
\\ x2 = 2;
\\
\\ printf("%d %d %d %d %d %d\n", x1, x2, x3, x4, get_x5(), get_x6());
\\ return 0;
\\}
,
.c_source_flags = &.{"-fno-plt"},
.pic = true,
});
obj.root_module.link_libc = true;
const a_so = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(a_so,
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int x3 = 3;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x5 = 5;
\\int get_x5() { return x5; }
, &.{"-fno-plt"});
const b_so = addSharedLibrary(b, opts, .{ .name = "b" });
addCSourceBytes(b_so,
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int x4 = 4;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x6 = 6;
\\int get_x6() { return x6; }
, &.{"-fno-plt"});
// b_so.link_relax = false; // TODO
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(a_so);
exe.root_module.linkLibrary(b_so);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2 3 4 5 6\n");
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(a_so);
exe.root_module.linkLibrary(b_so);
exe.root_module.link_libc = true;
// exe.link_relax = false; // TODO
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2 3 4 5 6\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testTlsGdToIe(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-gd-to-ie", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x1 = 1;
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int x2 = 2;
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int x3;
\\int foo() {
\\ x3 = 3;
\\
\\ printf("%d %d %d\n", x1, x2, x3);
\\ return 0;
\\}
,
.pic = true,
});
a_o.root_module.link_libc = true;
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes =
\\int foo();
\\int main() { foo(); }
,
.pic = true,
});
{
const dso = addSharedLibrary(b, opts, .{ .name = "a1" });
dso.root_module.addObject(a_o);
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(b_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2 3\n");
test_step.dependOn(&run.step);
}
{
const dso = addSharedLibrary(b, opts, .{ .name = "a2" });
dso.root_module.addObject(a_o);
// dso.link_relax = false; // TODO
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(b_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2 3\n");
test_step.dependOn(&run.step);
}
// {
// const dso = addSharedLibrary(b, opts, .{ .name = "a"});
// dso.root_module.addObject(a_o);
// dso.link_z_nodlopen = true;
// const exe = addExecutable(b, opts, .{ .name = "main"});
// exe.root_module.addObject(b_o);
// exe.root_module.linkLibrary(dso);
// const run = addRunArtifact(exe);
// run.expectStdOutEqual("1 2 3\n");
// test_step.dependOn(&run.step);
// }
// {
// const dso = addSharedLibrary(b, opts, .{ .name = "a"});
// dso.root_module.addObject(a_o);
// dso.link_relax = false;
// dso.link_z_nodlopen = true;
// const exe = addExecutable(b, opts, .{ .name = "main"});
// exe.root_module.addObject(b_o);
// exe.root_module.linkLibrary(dso);
// const run = addRunArtifact(exe);
// run.expectStdOutEqual("1 2 3\n");
// test_step.dependOn(&run.step);
// }
return test_step;
}
fn testTlsIe(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-ie", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\#include <stdio.h>
\\__attribute__((tls_model("initial-exec"))) static _Thread_local int foo;
\\__attribute__((tls_model("initial-exec"))) static _Thread_local int bar;
\\void set() {
\\ foo = 3;
\\ bar = 5;
\\}
\\void print() {
\\ printf("%d %d ", foo, bar);
\\}
, &.{});
dso.root_module.link_libc = true;
const main_o = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include <stdio.h>
\\_Thread_local int baz;
\\void set();
\\void print();
\\int main() {
\\ baz = 7;
\\ print();
\\ set();
\\ print();
\\ printf("%d\n", baz);
\\}
,
});
main_o.root_module.link_libc = true;
const exp_stdout = "0 0 3 5 7\n";
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
// exe.link_relax = false; // TODO
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testTlsLargeAlignment(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-large-alignment", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\__attribute__((section(".tdata1")))
\\_Thread_local int x = 42;
,
.c_source_flags = &.{"-std=c11"},
.pic = true,
});
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes =
\\__attribute__((section(".tdata2")))
\\_Alignas(256) _Thread_local int y[] = { 1, 2, 3 };
,
.c_source_flags = &.{"-std=c11"},
.pic = true,
});
const c_o = addObject(b, opts, .{
.name = "c",
.c_source_bytes =
\\#include <stdio.h>
\\extern _Thread_local int x;
\\extern _Thread_local int y[];
\\int main() {
\\ printf("%d %d %d %d\n", x, y[0], y[1], y[2]);
\\}
,
.pic = true,
});
c_o.root_module.link_libc = true;
{
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(a_o);
dso.root_module.addObject(b_o);
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(c_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42 1 2 3\n");
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(c_o);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42 1 2 3\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testTlsLargeTbss(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-large-tbss", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addAsmSourceBytes(exe,
\\.globl x, y
\\.section .tbss,"awT",@nobits
\\x:
\\.zero 1024
\\.section .tcommon,"awT",@nobits
\\y:
\\.zero 1024
);
addCSourceBytes(exe,
\\#include <stdio.h>
\\extern _Thread_local char x[1024000];
\\extern _Thread_local char y[1024000];
\\int main() {
\\ x[0] = 3;
\\ x[1023] = 5;
\\ printf("%d %d %d %d %d %d\n", x[0], x[1], x[1023], y[0], y[1], y[1023]);
\\}
, &.{});
exe.root_module.link_libc = true;
// Disabled to work around the ELF linker crashing.
// Can be reproduced on a x86_64-linux host by commenting out the line below.
exe.root_module.sanitize_c = .off;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 0 5 0 0 0\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsLargeStaticImage(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-large-static-image", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe, "_Thread_local int x[] = { 1, 2, 3, [10000] = 5 };", &.{});
addCSourceBytes(exe,
\\#include <stdio.h>
\\extern _Thread_local int x[];
\\int main() {
\\ printf("%d %d %d %d %d\n", x[0], x[1], x[2], x[3], x[10000]);
\\}
, &.{});
exe.root_module.pic = true;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2 3 0 5\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsLd(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-ld", opts);
const main_o = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include <stdio.h>
\\extern _Thread_local int foo;
\\static _Thread_local int bar;
\\int *get_foo_addr() { return &foo; }
\\int *get_bar_addr() { return &bar; }
\\int main() {
\\ bar = 5;
\\ printf("%d %d %d %d\n", *get_foo_addr(), *get_bar_addr(), foo, bar);
\\ return 0;
\\}
,
.c_source_flags = &.{"-ftls-model=local-dynamic"},
.pic = true,
});
main_o.root_module.link_libc = true;
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes = "_Thread_local int foo = 3;",
.c_source_flags = &.{"-ftls-model=local-dynamic"},
.pic = true,
});
const exp_stdout = "3 5 3 5\n";
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(a_o);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(a_o);
exe.root_module.link_libc = true;
// exe.link_relax = false; // TODO
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testTlsLdDso(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-ld-dso", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\static _Thread_local int def, def1;
\\int f0() { return ++def; }
\\int f1() { return ++def1 + def; }
, &.{"-ftls-model=local-dynamic"});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\extern int f0();
\\extern int f1();
\\int main() {
\\ int x = f0();
\\ int y = f1();
\\ printf("%d %d\n", x, y);
\\ return 0;
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsLdNoPlt(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-ld-no-plt", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\#include <stdio.h>
\\extern _Thread_local int foo;
\\static _Thread_local int bar;
\\int *get_foo_addr() { return &foo; }
\\int *get_bar_addr() { return &bar; }
\\int main() {
\\ bar = 5;
\\
\\ printf("%d %d %d %d\n", *get_foo_addr(), *get_bar_addr(), foo, bar);
\\ return 0;
\\}
,
.c_source_flags = &.{ "-ftls-model=local-dynamic", "-fno-plt" },
.pic = true,
});
a_o.root_module.link_libc = true;
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes = "_Thread_local int foo = 3;",
.c_source_flags = &.{ "-ftls-model=local-dynamic", "-fno-plt" },
.pic = true,
});
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 5 3 5\n");
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.link_libc = true;
// exe.link_relax = false; // TODO
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 5 3 5\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testTlsNoPic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-no-pic", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\__attribute__((tls_model("global-dynamic"))) extern _Thread_local int foo;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int bar;
\\int *get_foo_addr() { return &foo; }
\\int *get_bar_addr() { return &bar; }
\\int main() {
\\ foo = 3;
\\ bar = 5;
\\
\\ printf("%d %d %d %d\n", *get_foo_addr(), *get_bar_addr(), foo, bar);
\\ return 0;
\\}
, .{});
addCSourceBytes(exe,
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int foo;
, &.{});
exe.root_module.pic = false;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 5 3 5\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsOffsetAlignment(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-offset-alignment", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\#include <assert.h>
\\#include <stdlib.h>
\\
\\// .tdata
\\_Thread_local int x = 42;
\\// .tbss
\\__attribute__ ((aligned(64)))
\\_Thread_local int y = 0;
\\
\\void *verify(void *unused) {
\\ assert((unsigned long)(&y) % 64 == 0);
\\ return NULL;
\\}
, &.{});
dso.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <pthread.h>
\\#include <dlfcn.h>
\\#include <assert.h>
\\#include <stdio.h>
\\void *(*verify)(void *);
\\
\\int main() {
\\ void *handle = dlopen("liba.so", RTLD_NOW);
\\ if (!handle) {
\\ fprintf(stderr, "dlopen failed: %s\n", dlerror());
\\ return 1;
\\ }
\\ *(void**)(&verify) = dlsym(handle, "verify");
\\ assert(verify);
\\
\\ pthread_t thread;
\\
\\ verify(NULL);
\\
\\ pthread_create(&thread, NULL, verify, NULL);
\\ pthread_join(thread, NULL);
\\}
, &.{});
exe.root_module.addRPath(dso.getEmittedBinDirectory());
exe.root_module.link_libc = true;
exe.root_module.pic = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsPic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-pic", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((tls_model("global-dynamic"))) extern _Thread_local int foo;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int bar;
\\int *get_foo_addr() { return &foo; }
\\int *get_bar_addr() { return &bar; }
\\int main() {
\\ bar = 5;
\\
\\ printf("%d %d %d %d\n", *get_foo_addr(), *get_bar_addr(), foo, bar);
\\ return 0;
\\}
,
.pic = true,
});
obj.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int foo = 3;
, &.{});
exe.root_module.addObject(obj);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 5 3 5\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsSmallAlignment(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-small-alignment", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.asm_source_bytes =
\\.text
\\.byte 0
\\
,
.pic = true,
});
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes = "_Thread_local char x = 42;",
.c_source_flags = &.{"-std=c11"},
.pic = true,
});
const c_o = addObject(b, opts, .{
.name = "c",
.c_source_bytes =
\\#include <stdio.h>
\\extern _Thread_local char x;
\\int main() {
\\ printf("%d\n", x);
\\}
,
.pic = true,
});
c_o.root_module.link_libc = true;
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(c_o);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42\n");
test_step.dependOn(&run.step);
}
{
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(a_o);
dso.root_module.addObject(b_o);
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(c_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testTlsStatic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-static", opts);
const exe = addExecutable(b, opts, .{ .name = "test" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\_Thread_local int a = 10;
\\_Thread_local int b;
\\_Thread_local char c = 'a';
\\int main(int argc, char* argv[]) {
\\ printf("%d %d %c\n", a, b, c);
\\ a += 1;
\\ b += 1;
\\ c += 1;
\\ printf("%d %d %c\n", a, b, c);
\\ return 0;
\\}
, &.{});
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\10 0 a
\\11 1 b
\\
);
test_step.dependOn(&run.step);
return test_step;
}
fn testUnknownFileTypeError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unknown-file-type-error", opts);
const dylib = addSharedLibrary(b, .{
.target = b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .macos }),
}, .{
.name = "a",
.zig_source_bytes = "export var foo: i32 = 0;",
});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\extern int foo;
\\int main() {
\\ return foo;
\\}
, &.{});
exe.root_module.linkLibrary(dylib);
exe.root_module.link_libc = true;
expectLinkErrors(exe, test_step, .{
.contains = "error: failed to parse shared library: BadMagic",
});
return test_step;
}
fn testUnresolvedError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unresolved-error", opts);
const obj1 = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\#include <stdio.h>
\\int foo();
\\int bar() {
\\ return foo() + 1;
\\}
,
.c_source_flags = &.{"-ffunction-sections"},
});
obj1.root_module.link_libc = true;
const obj2 = addObject(b, opts, .{
.name = "b",
.c_source_bytes =
\\#include <stdio.h>
\\int foo();
\\int bar();
\\int main() {
\\ return foo() + bar();
\\}
,
.c_source_flags = &.{"-ffunction-sections"},
});
obj2.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj2);
exe.root_module.link_libc = true;
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: undefined symbol: foo",
"note: referenced by /?/a.o:.text.bar",
"note: referenced by /?/b.o:.text.main",
} });
return test_step;
}
fn testWeakExports(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "weak-exports", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((weak)) int foo();
\\int main() {
\\ printf("%d\n", foo ? foo() : 3);
\\}
,
.pic = true,
});
obj.root_module.link_libc = true;
{
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(obj);
dso.root_module.link_libc = true;
const check = dso.checkObject();
check.checkInDynamicSymtab();
check.checkContains("UND NOTYPE WEAK DEFAULT foo");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj);
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInDynamicSymtab();
check.checkNotPresent("UND NOTYPE WEAK DEFAULT foo");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("3\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testWeakUndefsDso(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "weak-undef-dso", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\__attribute__((weak)) int foo();
\\int bar() { return foo ? foo() : -1; }
, &.{});
{
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\int bar();
\\int main() { printf("bar=%d\n", bar()); }
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("bar=-1\n");
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\int foo() { return 5; }
\\int bar();
\\int main() { printf("bar=%d\n", bar()); }
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("bar=5\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testZNow(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "z-now", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes = "int main() { return 0; }",
.pic = true,
});
{
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(obj);
const check = dso.checkObject();
check.checkInDynamicSection();
check.checkContains("NOW");
test_step.dependOn(&check.step);
}
{
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(obj);
dso.link_z_lazy = true;
const check = dso.checkObject();
check.checkInDynamicSection();
check.checkNotPresent("NOW");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testZStackSize(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "z-stack-size", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe, "int main() { return 0; }", &.{});
exe.stack_size = 0x800000;
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("program headers");
check.checkExact("type GNU_STACK");
check.checkExact("memsz 800000");
test_step.dependOn(&check.step);
return test_step;
}
fn testZText(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "z-text", opts);
// Previously, following mold, this test tested text relocs present in a PIE executable.
// However, as we want to cover musl AND glibc, it is now modified to test presence of
// text relocs in a DSO which is then linked with an executable.
// According to Rich and this thread https://www.openwall.com/lists/musl/2020/09/25/4
// musl supports only a very limited number of text relocations and only in DSOs (and
// rightly so!).
const a_o = addObject(b, opts, .{
.name = "a",
.asm_source_bytes =
\\.globl fn1
\\fn1:
\\ sub $8, %rsp
\\ movabs ptr, %rax
\\ call *%rax
\\ add $8, %rsp
\\ ret
\\
,
});
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes =
\\int fn1();
\\int fn2() {
\\ return 3;
\\}
\\void *ptr = fn2;
\\int fnn() {
\\ return fn1();
\\}
,
.pic = true,
});
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(a_o);
dso.root_module.addObject(b_o);
dso.link_z_notext = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\int fnn();
\\int main() {
\\ printf("%d\n", fnn());
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3\n");
test_step.dependOn(&run.step);
// Check for DT_TEXTREL in a DSO
const check = dso.checkObject();
check.checkInDynamicSection();
// check.checkExact("TEXTREL 0"); // TODO fix in CheckObject parser
check.checkContains("FLAGS TEXTREL");
test_step.dependOn(&check.step);
return test_step;
}
fn addTestStep(b: *Build, comptime prefix: []const u8, opts: Options) *Step {
return link.addTestStep(b, "elf-" ++ prefix, opts);
}
const addAsmSourceBytes = link.addAsmSourceBytes;
const addCSourceBytes = link.addCSourceBytes;
const addCppSourceBytes = link.addCppSourceBytes;
const addExecutable = link.addExecutable;
const addObject = link.addObject;
const addRunArtifact = link.addRunArtifact;
const addSharedLibrary = link.addSharedLibrary;
const addStaticLibrary = link.addStaticLibrary;
const expectLinkErrors = link.expectLinkErrors;
const link = @import("link.zig");
const std = @import("std");
const builtin = @import("builtin");
const Build = std.Build;
const BuildOptions = link.BuildOptions;
const Options = link.Options;
const Step = Build.Step;
const WriteFile = Step.WriteFile;
@@ -1,4 +0,0 @@
#include "a.h"
int32_t add(int32_t a, int32_t b) {
return a + b;
}
@@ -1,2 +0,0 @@
#include <stdint.h>
int32_t add(int32_t a, int32_t b);
@@ -1,6 +0,0 @@
#include "a.h"
#include "b.h"
int32_t sub(int32_t a, int32_t b) {
return add(a, -1 * b);
}
@@ -1,2 +0,0 @@
#include <stdint.h>
int32_t sub(int32_t a, int32_t b);
@@ -1,50 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const lib_a = b.addLibrary(.{
.linkage = .static,
.name = "a",
.root_module = b.createModule(.{
.root_source_file = null,
.optimize = optimize,
.target = b.graph.host,
}),
});
lib_a.root_module.addCSourceFile(.{ .file = b.path("a.c"), .flags = &[_][]const u8{} });
lib_a.root_module.addIncludePath(b.path("."));
const lib_b = b.addLibrary(.{
.linkage = .static,
.name = "b",
.root_module = b.createModule(.{
.root_source_file = null,
.optimize = optimize,
.target = b.graph.host,
}),
});
lib_b.root_module.addCSourceFile(.{ .file = b.path("b.c"), .flags = &[_][]const u8{} });
lib_b.root_module.addIncludePath(b.path("."));
const test_exe = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = b.graph.host,
.optimize = optimize,
}),
});
test_exe.root_module.linkLibrary(lib_a);
test_exe.root_module.linkLibrary(lib_b);
test_exe.root_module.addIncludePath(b.path("."));
test_step.dependOn(&b.addRunArtifact(test_exe).step);
}
@@ -1,9 +0,0 @@
const std = @import("std");
const expect = std.testing.expect;
extern fn sub(a: i32, b: i32) i32;
test "import C sub" {
const result = sub(2, 1);
try expect(result == 1);
}
-171
View File
@@ -1,171 +0,0 @@
pub const BuildOptions = struct {
has_macos_sdk: bool,
has_ios_sdk: bool,
has_symlinks: bool,
};
pub const Options = struct {
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode = .Debug,
use_llvm: bool = true,
use_lld: bool = false,
strip: ?bool = null,
};
pub fn addTestStep(b: *Build, prefix: []const u8, opts: Options) *Step {
const target = opts.target.query.zigTriple(b.allocator) catch @panic("OOM");
const optimize = @tagName(opts.optimize);
const use_llvm = if (opts.use_llvm) "llvm" else "no-llvm";
const use_lld = if (opts.use_lld) "lld" else "no-lld";
if (opts.strip) |strip| {
const s = if (strip) "strip" else "no-strip";
const name = std.fmt.allocPrint(b.allocator, "test-{s}-{s}-{s}-{s}-{s}-{s}", .{
prefix, target, optimize, use_llvm, use_lld, s,
}) catch @panic("OOM");
return b.step(name, "");
}
const name = std.fmt.allocPrint(b.allocator, "test-{s}-{s}-{s}-{s}-{s}", .{
prefix, target, optimize, use_llvm, use_lld,
}) catch @panic("OOM");
return b.step(name, "");
}
const OverlayOptions = struct {
name: []const u8,
asm_source_bytes: ?[]const u8 = null,
c_source_bytes: ?[]const u8 = null,
c_source_flags: []const []const u8 = &.{},
cpp_source_bytes: ?[]const u8 = null,
cpp_source_flags: []const []const u8 = &.{},
objc_source_bytes: ?[]const u8 = null,
objc_source_flags: []const []const u8 = &.{},
objcpp_source_bytes: ?[]const u8 = null,
objcpp_source_flags: []const []const u8 = &.{},
zig_source_bytes: ?[]const u8 = null,
pic: ?bool = null,
strip: ?bool = null,
};
pub fn addExecutable(b: *std.Build, base: Options, overlay: OverlayOptions) *Compile {
return b.addExecutable(.{
.name = overlay.name,
.root_module = createModule(b, base, overlay),
.use_llvm = base.use_llvm,
.use_lld = base.use_lld,
});
}
pub fn addObject(b: *Build, base: Options, overlay: OverlayOptions) *Compile {
return b.addObject(.{
.name = overlay.name,
.root_module = createModule(b, base, overlay),
.use_llvm = base.use_llvm,
.use_lld = base.use_lld,
});
}
pub fn addStaticLibrary(b: *Build, base: Options, overlay: OverlayOptions) *Compile {
return b.addLibrary(.{
.linkage = .static,
.name = overlay.name,
.root_module = createModule(b, base, overlay),
.use_llvm = base.use_llvm,
.use_lld = base.use_lld,
});
}
pub fn addSharedLibrary(b: *Build, base: Options, overlay: OverlayOptions) *Compile {
return b.addLibrary(.{
.linkage = .dynamic,
.name = overlay.name,
.root_module = createModule(b, base, overlay),
.use_llvm = base.use_llvm,
.use_lld = base.use_lld,
});
}
fn createModule(b: *Build, base: Options, overlay: OverlayOptions) *Build.Module {
const write_files = b.addWriteFiles();
const mod = b.createModule(.{
.target = base.target,
.optimize = base.optimize,
.root_source_file = rsf: {
const bytes = overlay.zig_source_bytes orelse break :rsf null;
const name = b.fmt("{s}.zig", .{overlay.name});
break :rsf write_files.add(name, bytes);
},
.pic = overlay.pic,
.strip = if (base.strip) |s| s else overlay.strip,
});
if (overlay.objcpp_source_bytes) |bytes| {
mod.addCSourceFile(.{
.file = write_files.add("a.mm", bytes),
.flags = overlay.objcpp_source_flags,
});
}
if (overlay.objc_source_bytes) |bytes| {
mod.addCSourceFile(.{
.file = write_files.add("a.m", bytes),
.flags = overlay.objc_source_flags,
});
}
if (overlay.cpp_source_bytes) |bytes| {
mod.addCSourceFile(.{
.file = write_files.add("a.cpp", bytes),
.flags = overlay.cpp_source_flags,
});
}
if (overlay.c_source_bytes) |bytes| {
mod.addCSourceFile(.{
.file = write_files.add("a.c", bytes),
.flags = overlay.c_source_flags,
});
}
if (overlay.asm_source_bytes) |bytes| {
mod.addAssemblyFile(write_files.add("a.s", bytes));
}
return mod;
}
pub fn addRunArtifact(comp: *Compile) *Run {
const b = comp.step.owner;
const run = b.addRunArtifact(comp);
run.skip_foreign_checks = true;
return run;
}
pub fn addCSourceBytes(comp: *Compile, bytes: []const u8, flags: []const []const u8) void {
const b = comp.step.owner;
const file = WriteFile.create(b).add("a.c", bytes);
comp.root_module.addCSourceFile(.{ .file = file, .flags = flags });
}
pub fn addCppSourceBytes(comp: *Compile, bytes: []const u8, flags: []const []const u8) void {
const b = comp.step.owner;
const file = WriteFile.create(b).add("a.cpp", bytes);
comp.root_module.addCSourceFile(.{ .file = file, .flags = flags });
}
pub fn addAsmSourceBytes(comp: *Compile, bytes: []const u8) void {
const b = comp.step.owner;
const actual_bytes = std.fmt.allocPrint(b.allocator, "{s}\n", .{bytes}) catch @panic("OOM");
const file = WriteFile.create(b).add("a.s", actual_bytes);
comp.root_module.addAssemblyFile(file);
}
pub fn expectLinkErrors(comp: *Compile, test_step: *Step, expected_errors: Compile.ExpectedCompileErrors) void {
comp.expect_errors = expected_errors;
const bin_file = comp.getEmittedBin();
bin_file.addStepDependencies(test_step);
}
const std = @import("std");
const Build = std.Build;
const Compile = Step.Compile;
const Run = Step.Run;
const Step = Build.Step;
const WriteFile = Step.WriteFile;
-3291
View File
@@ -1,3291 +0,0 @@
//! Here we test our MachO linker for correctness and functionality.
pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
const macho_step = b.step("test-macho", "Run MachO tests");
// https://github.com/ziglang/zig/issues/25323
if (builtin.os.tag == .freebsd) return macho_step;
// https://github.com/ziglang/zig/issues/25961
if (comptime builtin.cpu.arch.endian() == .big) return macho_step;
const x86_64_target = b.resolveTargetQuery(.{
.cpu_arch = .x86_64,
.os_tag = .macos,
});
const aarch64_target = b.resolveTargetQuery(.{
.cpu_arch = .aarch64,
.os_tag = .macos,
});
const default_target = switch (builtin.cpu.arch) {
.x86_64, .aarch64 => b.resolveTargetQuery(.{
.os_tag = .macos,
}),
else => aarch64_target,
};
// Exercise linker with self-hosted backend (no LLVM)
macho_step.dependOn(testEmptyZig(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testHelloZig(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testLinkingStaticLib(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testReexportsZig(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testRelocatableZig(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testTlsZig(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testUnresolvedError(b, .{ .use_llvm = false, .target = x86_64_target }));
// Exercise linker with LLVM backend
macho_step.dependOn(testDeadStrip(b, .{ .target = default_target }));
macho_step.dependOn(testDuplicateDefinitions(b, .{ .target = default_target }));
macho_step.dependOn(testEmptyObject(b, .{ .target = default_target }));
macho_step.dependOn(testEmptyZig(b, .{ .target = default_target }));
macho_step.dependOn(testEntryPoint(b, .{ .target = default_target }));
macho_step.dependOn(testHeaderWeakFlags(b, .{ .target = default_target }));
macho_step.dependOn(testHelloC(b, .{ .target = default_target }));
macho_step.dependOn(testHelloZig(b, .{ .target = default_target }));
macho_step.dependOn(testLargeBss(b, .{ .target = default_target }));
macho_step.dependOn(testLayout(b, .{ .target = default_target }));
macho_step.dependOn(testLinkingStaticLib(b, .{ .target = default_target }));
macho_step.dependOn(testLinksection(b, .{ .target = default_target }));
macho_step.dependOn(testMergeLiteralsX64(b, .{ .target = x86_64_target }));
macho_step.dependOn(testMergeLiteralsArm64(b, .{ .target = aarch64_target }));
macho_step.dependOn(testMergeLiteralsArm642(b, .{ .target = aarch64_target }));
macho_step.dependOn(testMergeLiteralsAlignment(b, .{ .target = aarch64_target }));
macho_step.dependOn(testMhExecuteHeader(b, .{ .target = default_target }));
macho_step.dependOn(testNoDeadStrip(b, .{ .target = default_target }));
macho_step.dependOn(testNoExportsDylib(b, .{ .target = default_target }));
macho_step.dependOn(testPagezeroSize(b, .{ .target = default_target }));
macho_step.dependOn(testReexportsZig(b, .{ .target = default_target }));
macho_step.dependOn(testRelocatable(b, .{ .target = default_target }));
macho_step.dependOn(testRelocatableZig(b, .{ .target = default_target }));
macho_step.dependOn(testSectionBoundarySymbols(b, .{ .target = default_target }));
macho_step.dependOn(testSectionBoundarySymbols2(b, .{ .target = default_target }));
macho_step.dependOn(testSegmentBoundarySymbols(b, .{ .target = default_target }));
macho_step.dependOn(testSymbolStabs(b, .{ .target = default_target }));
macho_step.dependOn(testStackSize(b, .{ .target = default_target }));
macho_step.dependOn(testTentative(b, .{ .target = default_target }));
macho_step.dependOn(testThunks(b, .{ .target = aarch64_target }));
macho_step.dependOn(testTlsLargeTbss(b, .{ .target = default_target }));
macho_step.dependOn(testTlsZig(b, .{ .target = default_target }));
macho_step.dependOn(testUndefinedFlag(b, .{ .target = default_target }));
macho_step.dependOn(testUndefinedDynamicLookup(b, .{ .target = default_target }));
macho_step.dependOn(testDiscardLocalSymbols(b, .{ .target = default_target }));
macho_step.dependOn(testUnresolvedError(b, .{ .target = default_target }));
macho_step.dependOn(testUnresolvedError2(b, .{ .target = default_target }));
macho_step.dependOn(testUnwindInfo(b, .{ .target = default_target }));
macho_step.dependOn(testUnwindInfoNoSubsectionsX64(b, .{ .target = x86_64_target }));
macho_step.dependOn(testUnwindInfoNoSubsectionsArm64(b, .{ .target = aarch64_target }));
macho_step.dependOn(testEhFramePointerEncodingSdata4(b, .{ .target = aarch64_target }));
macho_step.dependOn(testWeakBind(b, .{ .target = x86_64_target }));
macho_step.dependOn(testWeakRef(b, .{ .target = b.resolveTargetQuery(.{
.cpu_arch = .x86_64,
.os_tag = .macos,
.os_version_min = .{ .semver = .{ .major = 10, .minor = 13, .patch = 0 } },
}) }));
// Tests requiring symlinks
if (build_opts.has_symlinks) {
macho_step.dependOn(testEntryPointArchive(b, .{ .target = default_target }));
macho_step.dependOn(testEntryPointDylib(b, .{ .target = default_target }));
macho_step.dependOn(testDylib(b, .{ .target = default_target }));
macho_step.dependOn(testDylibVersionTbd(b, .{ .target = default_target }));
macho_step.dependOn(testNeededLibrary(b, .{ .target = default_target }));
macho_step.dependOn(testSearchStrategy(b, .{ .target = default_target }));
macho_step.dependOn(testTbdv3(b, .{ .target = default_target }));
macho_step.dependOn(testTls(b, .{ .target = default_target }));
macho_step.dependOn(testTlsPointers(b, .{ .target = default_target }));
macho_step.dependOn(testTwoLevelNamespace(b, .{ .target = default_target }));
macho_step.dependOn(testWeakLibrary(b, .{ .target = default_target }));
// Tests requiring presence of macOS SDK in system path
if (build_opts.has_macos_sdk) {
macho_step.dependOn(testDeadStripDylibs(b, .{ .target = b.graph.host }));
macho_step.dependOn(testHeaderpad(b, .{ .target = b.graph.host }));
macho_step.dependOn(testLinkDirectlyCppTbd(b, .{ .target = b.graph.host }));
macho_step.dependOn(testMergeLiteralsObjc(b, .{ .target = b.graph.host }));
macho_step.dependOn(testNeededFramework(b, .{ .target = b.graph.host }));
macho_step.dependOn(testObjc(b, .{ .target = b.graph.host }));
macho_step.dependOn(testObjcpp(b, .{ .target = b.graph.host }));
macho_step.dependOn(testWeakFramework(b, .{ .target = b.graph.host }));
}
}
return macho_step;
}
fn testDeadStrip(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dead-strip", opts);
const obj = addObject(b, opts, .{ .name = "a", .cpp_source_bytes =
\\#include <stdio.h>
\\int two() { return 2; }
\\int live_var1 = 1;
\\int live_var2 = two();
\\int dead_var1 = 3;
\\int dead_var2 = 4;
\\void live_fn1() {}
\\void live_fn2() { live_fn1(); }
\\void dead_fn1() {}
\\void dead_fn2() { dead_fn1(); }
\\int main() {
\\ printf("%d %d\n", live_var1, live_var2);
\\ live_fn2();
\\}
});
{
const exe = addExecutable(b, opts, .{ .name = "no_dead_strip" });
exe.root_module.addObject(obj);
exe.link_gc_sections = false;
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("live_var1");
check.checkInSymtab();
check.checkContains("live_var2");
check.checkInSymtab();
check.checkContains("dead_var1");
check.checkInSymtab();
check.checkContains("dead_var2");
check.checkInSymtab();
check.checkContains("live_fn1");
check.checkInSymtab();
check.checkContains("live_fn2");
check.checkInSymtab();
check.checkContains("dead_fn1");
check.checkInSymtab();
check.checkContains("dead_fn2");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "yes_dead_strip" });
exe.root_module.addObject(obj);
exe.link_gc_sections = true;
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("live_var1");
check.checkInSymtab();
check.checkContains("live_var2");
check.checkInSymtab();
check.checkNotPresent("dead_var1");
check.checkInSymtab();
check.checkNotPresent("dead_var2");
check.checkInSymtab();
check.checkContains("live_fn1");
check.checkInSymtab();
check.checkContains("live_fn2");
check.checkInSymtab();
check.checkNotPresent("dead_fn1");
check.checkInSymtab();
check.checkNotPresent("dead_fn2");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testDuplicateDefinitions(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "duplicate-definitions", opts);
const obj = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
\\var x: usize = 1;
\\export fn strong() void { x += 1; }
\\export fn weak() void { x += 1; }
});
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\var x: usize = 1;
\\export fn strong() void { x += 1; }
\\comptime { @export(&weakImpl, .{ .name = "weak", .linkage = .weak }); }
\\fn weakImpl() callconv(.c) void { x += 1; }
\\extern fn weak() void;
\\pub fn main() void {
\\ weak();
\\ strong();
\\}
});
exe.root_module.addObject(obj);
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: duplicate symbol definition: _strong",
"note: defined by /?/a.o",
"note: defined by /?/main_zcu.o",
} });
return test_step;
}
fn testDeadStripDylibs(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dead-strip-dylibs", opts);
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <objc/runtime.h>
\\int main() {
\\ if (objc_getClass("NSObject") == 0) {
\\ return -1;
\\ }
\\ if (objc_getClass("NSApplication") == 0) {
\\ return -2;
\\ }
\\ return 0;
\\}
});
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.linkFramework("Cocoa", .{});
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkContains("Cocoa");
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkContains("libobjc");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.linkFramework("Cocoa", .{});
exe.dead_strip_dylibs = true;
const run = addRunArtifact(exe);
run.expectExitCode(@as(u8, @bitCast(@as(i8, -2))));
test_step.dependOn(&run.step);
}
return test_step;
}
fn testDylib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dylib", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes =
\\#include<stdio.h>
\\char world[] = "world";
\\char* hello() {
\\ return "Hello";
\\}
});
const check = dylib.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkNotPresent("PIE");
test_step.dependOn(&check.step);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include<stdio.h>
\\char* hello();
\\extern char world[];
\\int main() {
\\ printf("%s %s", hello(), world);
\\ return 0;
\\}
});
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world");
test_step.dependOn(&run.step);
return test_step;
}
fn testDylibVersionTbd(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dylib-version-tbd", opts);
const tbd = tbd: {
const wf = WriteFile.create(b);
break :tbd wf.add("liba.tbd",
\\--- !tapi-tbd
\\tbd-version: 4
\\targets: [ x86_64-macos, arm64-macos ]
\\uuids:
\\ - target: x86_64-macos
\\ value: DEADBEEF
\\ - target: arm64-macos
\\ value: BEEFDEAD
\\install-name: '@rpath/liba.dylib'
\\current-version: 1.2
\\exports:
\\ - targets: [ x86_64-macos, arm64-macos ]
\\ symbols: [ _foo ]
);
};
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() {}" });
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(tbd.dirname());
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkExact("name @rpath/liba.dylib");
check.checkExact("current version 10200");
test_step.dependOn(&check.step);
return test_step;
}
fn testEmptyObject(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "empty-object", opts);
const empty = addObject(b, opts, .{ .name = "empty", .c_source_bytes = "" });
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int main() {
\\ printf("Hello world!");
\\}
});
exe.root_module.addObject(empty);
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world!");
test_step.dependOn(&run.step);
return test_step;
}
fn testEmptyZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "empty-zig", opts);
const exe = addExecutable(b, opts, .{ .name = "empty", .zig_source_bytes = "pub fn main() void {}" });
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testEntryPoint(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "entry-point", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include<stdio.h>
\\int non_main() {
\\ printf("%d", 42);
\\ return 0;
\\}
});
exe.entry = .{ .symbol_name = "_non_main" };
const run = addRunArtifact(exe);
run.expectStdOutEqual("42");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("segname __TEXT");
check.checkExtract("vmaddr {vmaddr}");
check.checkInHeaders();
check.checkExact("cmd MAIN");
check.checkExtract("entryoff {entryoff}");
check.checkInSymtab();
check.checkExtract("{n_value} (__TEXT,__text) external _non_main");
check.checkComputeCompare("vmaddr entryoff +", .{ .op = .eq, .value = .{ .variable = "n_value" } });
test_step.dependOn(&check.step);
return test_step;
}
fn testEntryPointArchive(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "entry-point-archive", opts);
const lib = addStaticLibrary(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
{
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "" });
exe.root_module.linkSystemLibrary("main", .{});
exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "" });
exe.root_module.linkSystemLibrary("main", .{});
exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
exe.link_gc_sections = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testEntryPointDylib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "entry-point-dylib", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dylib,
\\extern int my_main();
\\int bootstrap() {
\\ return my_main();
\\}
, &.{});
dylib.linker_allow_shlib_undefined = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(dylib,
\\#include<stdio.h>
\\int my_main() {
\\ fprintf(stdout, "Hello!\n");
\\ return 0;
\\}
, &.{});
exe.root_module.linkLibrary(dylib);
exe.entry = .{ .symbol_name = "_bootstrap" };
exe.forceUndefinedSymbol("_my_main");
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("segname __TEXT");
check.checkExtract("vmaddr {text_vmaddr}");
check.checkInHeaders();
check.checkExact("sectname __stubs");
check.checkExtract("addr {stubs_vmaddr}");
check.checkInHeaders();
check.checkExact("sectname __stubs");
check.checkExtract("size {stubs_vmsize}");
check.checkInHeaders();
check.checkExact("cmd MAIN");
check.checkExtract("entryoff {entryoff}");
check.checkComputeCompare("text_vmaddr entryoff +", .{
.op = .gte,
.value = .{ .variable = "stubs_vmaddr" }, // The entrypoint should be a synthetic stub
});
check.checkComputeCompare("text_vmaddr entryoff + stubs_vmaddr -", .{
.op = .lt,
.value = .{ .variable = "stubs_vmsize" }, // The entrypoint should be a synthetic stub
});
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello!\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testHeaderpad(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "headerpad", opts);
const addExe = struct {
fn addExe(bb: *Build, o: Options, name: []const u8) *Compile {
const exe = addExecutable(bb, o, .{
.name = name,
.c_source_bytes = "int main() { return 0; }",
});
exe.root_module.linkFramework("CoreFoundation", .{});
exe.root_module.linkFramework("Foundation", .{});
exe.root_module.linkFramework("Cocoa", .{});
exe.root_module.linkFramework("CoreGraphics", .{});
exe.root_module.linkFramework("CoreHaptics", .{});
exe.root_module.linkFramework("CoreAudio", .{});
exe.root_module.linkFramework("AVFoundation", .{});
exe.root_module.linkFramework("CoreImage", .{});
exe.root_module.linkFramework("CoreLocation", .{});
exe.root_module.linkFramework("CoreML", .{});
exe.root_module.linkFramework("CoreVideo", .{});
exe.root_module.linkFramework("CoreText", .{});
exe.root_module.linkFramework("CryptoKit", .{});
exe.root_module.linkFramework("GameKit", .{});
exe.root_module.linkFramework("SwiftUI", .{});
exe.root_module.linkFramework("StoreKit", .{});
exe.root_module.linkFramework("SpriteKit", .{});
return exe;
}
}.addExe;
{
const exe = addExe(b, opts, "main1");
exe.headerpad_max_install_names = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("sectname __text");
check.checkExtract("offset {offset}");
switch (opts.target.result.cpu.arch) {
.aarch64 => check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } }),
.x86_64 => check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } }),
else => unreachable,
}
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
{
const exe = addExe(b, opts, "main2");
exe.headerpad_size = 0x10000;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("sectname __text");
check.checkExtract("offset {offset}");
check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
{
const exe = addExe(b, opts, "main3");
exe.headerpad_max_install_names = true;
exe.headerpad_size = 0x10000;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("sectname __text");
check.checkExtract("offset {offset}");
check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
{
const exe = addExe(b, opts, "main4");
exe.headerpad_max_install_names = true;
exe.headerpad_size = 0x1000;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("sectname __text");
check.checkExtract("offset {offset}");
switch (opts.target.result.cpu.arch) {
.aarch64 => check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } }),
.x86_64 => check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } }),
else => unreachable,
}
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
return test_step;
}
// Adapted from https://github.com/llvm/llvm-project/blob/main/lld/test/MachO/weak-header-flags.s
fn testHeaderWeakFlags(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "header-weak-flags", opts);
const obj1 = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _x
\\.weak_definition _x
\\_x:
\\ ret
});
const lib = addSharedLibrary(b, opts, .{ .name = "a" });
lib.root_module.addObject(obj1);
{
const exe = addExecutable(b, opts, .{ .name = "main1", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.addObject(obj1);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkContains("WEAK_DEFINES");
check.checkInHeaders();
check.checkExact("header");
check.checkContains("BINDS_TO_WEAK");
check.checkInExports();
check.checkExtract("[WEAK] {vmaddr} _x");
test_step.dependOn(&check.step);
}
{
const obj = addObject(b, opts, .{ .name = "b" });
switch (opts.target.result.cpu.arch) {
.aarch64 => addAsmSourceBytes(obj,
\\.globl _main
\\_main:
\\ bl _x
\\ ret
),
.x86_64 => addAsmSourceBytes(obj,
\\.globl _main
\\_main:
\\ callq _x
\\ ret
),
else => unreachable,
}
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.linkLibrary(lib);
exe.root_module.addObject(obj);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkNotPresent("WEAK_DEFINES");
check.checkInHeaders();
check.checkExact("header");
check.checkContains("BINDS_TO_WEAK");
check.checkInExports();
check.checkNotPresent("[WEAK] {vmaddr} _x");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main3", .asm_source_bytes =
\\.globl _main, _x
\\_x:
\\
\\_main:
\\ ret
});
exe.root_module.linkLibrary(lib);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkNotPresent("WEAK_DEFINES");
check.checkInHeaders();
check.checkExact("header");
check.checkNotPresent("BINDS_TO_WEAK");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testHelloC(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "hello-c", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int main() {
\\ printf("Hello world!\n");
\\ return 0;
\\}
});
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world!\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkContains("PIE");
test_step.dependOn(&check.step);
return test_step;
}
fn testHelloZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "hello-zig", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\const std = @import("std");
\\pub fn main() void {
\\ std.Io.File.stdout().writeStreamingAll(std.Options.debug_io, "Hello world!\n") catch @panic("fail");
\\}
});
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world!\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testLargeBss(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "large-bss", opts);
// TODO this test used use a 4GB zerofill section but this actually fails and causes every
// linker I tried misbehave in different ways. This only happened on arm64. I thought that
// maybe S_GB_ZEROFILL section is an answer to this but it doesn't seem supported by dyld
// anymore. When I get some free time I will re-investigate this.
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\char arr[0x1000000];
\\int main() {
\\ return arr[2000];
\\}
});
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testLayout(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "layout", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int main() {
\\ printf("Hello world!");
\\ return 0;
\\}
});
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd SEGMENT_64");
check.checkExact("segname __LINKEDIT");
check.checkExtract("fileoff {fileoff}");
check.checkExtract("filesz {filesz}");
check.checkInHeaders();
check.checkExact("cmd DYLD_INFO_ONLY");
check.checkExtract("rebaseoff {rebaseoff}");
check.checkExtract("rebasesize {rebasesize}");
check.checkExtract("bindoff {bindoff}");
check.checkExtract("bindsize {bindsize}");
check.checkExtract("lazybindoff {lazybindoff}");
check.checkExtract("lazybindsize {lazybindsize}");
check.checkExtract("exportoff {exportoff}");
check.checkExtract("exportsize {exportsize}");
check.checkInHeaders();
check.checkExact("cmd FUNCTION_STARTS");
check.checkExtract("dataoff {fstartoff}");
check.checkExtract("datasize {fstartsize}");
check.checkInHeaders();
check.checkExact("cmd DATA_IN_CODE");
check.checkExtract("dataoff {diceoff}");
check.checkExtract("datasize {dicesize}");
check.checkInHeaders();
check.checkExact("cmd SYMTAB");
check.checkExtract("symoff {symoff}");
check.checkExtract("nsyms {symnsyms}");
check.checkExtract("stroff {stroff}");
check.checkExtract("strsize {strsize}");
check.checkInHeaders();
check.checkExact("cmd DYSYMTAB");
check.checkExtract("indirectsymoff {dysymoff}");
check.checkExtract("nindirectsyms {dysymnsyms}");
switch (opts.target.result.cpu.arch) {
.aarch64 => {
check.checkInHeaders();
check.checkExact("cmd CODE_SIGNATURE");
check.checkExtract("dataoff {codesigoff}");
check.checkExtract("datasize {codesigsize}");
},
.x86_64 => {},
else => unreachable,
}
// DYLD_INFO_ONLY subsections are in order: rebase < bind < lazy < export,
// and there are no gaps between them
check.checkComputeCompare("rebaseoff rebasesize +", .{ .op = .eq, .value = .{ .variable = "bindoff" } });
check.checkComputeCompare("bindoff bindsize +", .{ .op = .eq, .value = .{ .variable = "lazybindoff" } });
check.checkComputeCompare("lazybindoff lazybindsize +", .{ .op = .eq, .value = .{ .variable = "exportoff" } });
// FUNCTION_STARTS directly follows DYLD_INFO_ONLY (no gap)
check.checkComputeCompare("exportoff exportsize +", .{ .op = .eq, .value = .{ .variable = "fstartoff" } });
// DATA_IN_CODE directly follows FUNCTION_STARTS (no gap)
check.checkComputeCompare("fstartoff fstartsize +", .{ .op = .eq, .value = .{ .variable = "diceoff" } });
// SYMTAB directly follows DATA_IN_CODE (no gap)
check.checkComputeCompare("diceoff dicesize +", .{ .op = .eq, .value = .{ .variable = "symoff" } });
// DYSYMTAB directly follows SYMTAB (no gap)
check.checkComputeCompare("symnsyms 16 symoff * +", .{ .op = .eq, .value = .{ .variable = "dysymoff" } });
// STRTAB follows DYSYMTAB with possible gap
check.checkComputeCompare("dysymnsyms 4 dysymoff * +", .{ .op = .lte, .value = .{ .variable = "stroff" } });
// all LINKEDIT sections apart from CODE_SIGNATURE are 8-bytes aligned
check.checkComputeCompare("rebaseoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("bindoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("lazybindoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("exportoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("fstartoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("diceoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("symoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("stroff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("dysymoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
switch (opts.target.result.cpu.arch) {
.aarch64 => {
// LINKEDIT segment does not extend beyond, or does not include, CODE_SIGNATURE data
check.checkComputeCompare("fileoff filesz codesigoff codesigsize + - -", .{
.op = .eq,
.value = .{ .literal = 0 },
});
// CODE_SIGNATURE data offset is 16-bytes aligned
check.checkComputeCompare("codesigoff 16 %", .{ .op = .eq, .value = .{ .literal = 0 } });
},
.x86_64 => {
// LINKEDIT segment does not extend beyond, or does not include, strtab data
check.checkComputeCompare("fileoff filesz stroff strsize + - -", .{
.op = .eq,
.value = .{ .literal = 0 },
});
},
else => unreachable,
}
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world!");
test_step.dependOn(&run.step);
return test_step;
}
fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step {
const io = b.graph.io;
const test_step = addTestStep(b, "link-directly-cpp-tbd", opts);
const sdk = std.zig.system.darwin.getSdk(b.allocator, io, &opts.target.result) orelse
@panic("macOS SDK is required to run the test");
const exe = addExecutable(b, opts, .{
.name = "main",
.cpp_source_bytes =
\\#include <new>
\\#include <cstdio>
\\int main() {
\\ int *x = new int;
\\ *x = 5;
\\ fprintf(stderr, "x: %d\n", *x);
\\ delete x;
\\}
,
.cpp_source_flags = &.{ "-nostdlib++", "-nostdinc++" },
});
exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) });
exe.root_module.addIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include/c++/v1" }) });
exe.root_module.addObjectFile(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/lib/libc++.tbd" }) });
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("[referenced dynamically] external __mh_execute_header");
test_step.dependOn(&check.step);
return test_step;
}
fn testLinkingStaticLib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linking-static-lib", opts);
const obj = addObject(b, opts, .{
.name = "bobj",
.zig_source_bytes = "export var bar: i32 = -42;",
.strip = true, // TODO for self-hosted, we don't really emit any valid DWARF yet since we only export a global
});
const lib = addStaticLibrary(b, opts, .{
.name = "alib",
.zig_source_bytes =
\\export fn foo() i32 {
\\ return 42;
\\}
,
});
lib.root_module.addObject(obj);
const exe = addExecutable(b, opts, .{
.name = "testlib",
.zig_source_bytes =
\\const std = @import("std");
\\extern fn foo() i32;
\\extern var bar: i32;
\\pub fn main() void {
\\ std.debug.print("{d}\n", .{foo() + bar});
\\}
,
});
exe.root_module.linkLibrary(lib);
const run = addRunArtifact(exe);
run.expectStdErrEqual("0\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testLinksection(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linksection", opts);
const obj = addObject(b, opts, .{ .name = "main", .zig_source_bytes =
\\export var test_global: u32 linksection("__DATA,__TestGlobal") = undefined;
\\export fn testFn() linksection("__TEXT,__TestFn") callconv(.c) void {
\\ TestGenericFn("A").f();
\\}
\\fn TestGenericFn(comptime suffix: []const u8) type {
\\ return struct {
\\ fn f() linksection("__TEXT,__TestGenFn" ++ suffix) void {}
\\ };
\\}
});
const check = obj.checkObject();
check.checkInSymtab();
check.checkContains("(__DATA,__TestGlobal) external _test_global");
check.checkInSymtab();
check.checkContains("(__TEXT,__TestFn) external _testFn");
if (opts.optimize == .Debug) {
check.checkInSymtab();
check.checkContains("(__TEXT,__TestGenFnA) _main.TestGenericFn(");
}
test_step.dependOn(&check.step);
return test_step;
}
fn testMergeLiteralsX64(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-literals-x64", opts);
const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _q1
\\.globl _s1
\\
\\.align 4
\\_q1:
\\ lea L._q1(%rip), %rax
\\ mov (%rax), %xmm0
\\ ret
\\
\\.section __TEXT,__cstring,cstring_literals
\\l._s1:
\\ .asciz "hello"
\\
\\.section __TEXT,__literal8,8byte_literals
\\.align 8
\\L._q1:
\\ .double 1.2345
\\
\\.section __DATA,__data
\\.align 8
\\_s1:
\\ .quad l._s1
});
const b_o = addObject(b, opts, .{ .name = "b", .asm_source_bytes =
\\.globl _q2
\\.globl _s2
\\.globl _s3
\\
\\.align 4
\\_q2:
\\ lea L._q2(%rip), %rax
\\ mov (%rax), %xmm0
\\ ret
\\
\\.section __TEXT,__cstring,cstring_literals
\\l._s2:
\\ .asciz "hello"
\\l._s3:
\\ .asciz "world"
\\
\\.section __TEXT,__literal8,8byte_literals
\\.align 8
\\L._q2:
\\ .double 1.2345
\\
\\.section __DATA,__data
\\.align 8
\\_s2:
\\ .quad l._s2
\\_s3:
\\ .quad l._s3
});
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\extern double q1();
\\extern double q2();
\\extern const char* s1;
\\extern const char* s2;
\\extern const char* s3;
\\int main() {
\\ printf("%s, %s, %s, %f, %f", s1, s2, s3, q1(), q2());
\\ return 0;
\\}
});
const runWithChecks = struct {
fn runWithChecks(step: *Step, exe: *Compile) void {
const run = addRunArtifact(exe);
run.expectStdOutEqual("hello, hello, world, 1.234500, 1.234500");
step.dependOn(&run.step);
const check = exe.checkObject();
check.dumpSection("__TEXT,__const");
check.checkContains("\x8d\x97n\x12\x83\xc0\xf3?");
check.dumpSection("__TEXT,__cstring");
check.checkContains("hello\x00world\x00%s, %s, %s, %f, %f\x00");
step.dependOn(&check.step);
}
}.runWithChecks;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(main_o);
runWithChecks(test_step, exe);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(b_o);
exe.root_module.addObject(a_o);
exe.root_module.addObject(main_o);
runWithChecks(test_step, exe);
}
{
const c_o = addObject(b, opts, .{ .name = "c" });
c_o.root_module.addObject(a_o);
c_o.root_module.addObject(b_o);
c_o.root_module.addObject(main_o);
const exe = addExecutable(b, opts, .{ .name = "main3" });
exe.root_module.addObject(c_o);
runWithChecks(test_step, exe);
}
return test_step;
}
fn testMergeLiteralsArm64(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-literals-arm64", opts);
const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _q1
\\.globl _s1
\\
\\.align 4
\\_q1:
\\ adrp x8, L._q1@PAGE
\\ ldr d0, [x8, L._q1@PAGEOFF]
\\ ret
\\
\\.section __TEXT,__cstring,cstring_literals
\\l._s1:
\\ .asciz "hello"
\\
\\.section __TEXT,__literal8,8byte_literals
\\.align 8
\\L._q1:
\\ .double 1.2345
\\
\\.section __DATA,__data
\\.align 8
\\_s1:
\\ .quad l._s1
});
const b_o = addObject(b, opts, .{ .name = "b", .asm_source_bytes =
\\.globl _q2
\\.globl _s2
\\.globl _s3
\\
\\.align 4
\\_q2:
\\ adrp x8, L._q2@PAGE
\\ ldr d0, [x8, L._q2@PAGEOFF]
\\ ret
\\
\\.section __TEXT,__cstring,cstring_literals
\\l._s2:
\\ .asciz "hello"
\\l._s3:
\\ .asciz "world"
\\
\\.section __TEXT,__literal8,8byte_literals
\\.align 8
\\L._q2:
\\ .double 1.2345
\\
\\.section __DATA,__data
\\.align 8
\\_s2:
\\ .quad l._s2
\\_s3:
\\ .quad l._s3
});
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\extern double q1();
\\extern double q2();
\\extern const char* s1;
\\extern const char* s2;
\\extern const char* s3;
\\int main() {
\\ printf("%s, %s, %s, %f, %f", s1, s2, s3, q1(), q2());
\\ return 0;
\\}
});
const runWithChecks = struct {
fn runWithChecks(step: *Step, exe: *Compile) void {
const run = addRunArtifact(exe);
run.expectStdOutEqual("hello, hello, world, 1.234500, 1.234500");
step.dependOn(&run.step);
const check = exe.checkObject();
check.dumpSection("__TEXT,__const");
check.checkContains("\x8d\x97n\x12\x83\xc0\xf3?");
check.dumpSection("__TEXT,__cstring");
check.checkContains("hello\x00world\x00%s, %s, %s, %f, %f\x00");
step.dependOn(&check.step);
}
}.runWithChecks;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(main_o);
runWithChecks(test_step, exe);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(b_o);
exe.root_module.addObject(a_o);
exe.root_module.addObject(main_o);
runWithChecks(test_step, exe);
}
{
const c_o = addObject(b, opts, .{ .name = "c" });
c_o.root_module.addObject(a_o);
c_o.root_module.addObject(b_o);
c_o.root_module.addObject(main_o);
const exe = addExecutable(b, opts, .{ .name = "main3" });
exe.root_module.addObject(c_o);
runWithChecks(test_step, exe);
}
return test_step;
}
/// This particular test case will generate invalid machine code that will segfault at runtime.
/// However, this is by design as we want to test that the linker does not panic when linking it
/// which is also the case for the system linker and lld - linking succeeds, runtime segfaults.
/// It should also be mentioned that runtime segfault is not due to the linker but faulty input asm.
fn testMergeLiteralsArm642(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-literals-arm64-2", opts);
const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _q1
\\.globl _s1
\\
\\.align 4
\\_q1:
\\ adrp x0, L._q1@PAGE
\\ ldr x0, [x0, L._q1@PAGEOFF]
\\ ret
\\
\\.section __TEXT,__cstring,cstring_literals
\\_s1:
\\ .asciz "hello"
\\
\\.section __TEXT,__literal8,8byte_literals
\\.align 8
\\L._q1:
\\ .double 1.2345
});
const b_o = addObject(b, opts, .{ .name = "b", .asm_source_bytes =
\\.globl _q2
\\.globl _s2
\\.globl _s3
\\
\\.align 4
\\_q2:
\\ adrp x0, L._q2@PAGE
\\ ldr x0, [x0, L._q2@PAGEOFF]
\\ ret
\\
\\.section __TEXT,__cstring,cstring_literals
\\_s2:
\\ .asciz "hello"
\\_s3:
\\ .asciz "world"
\\
\\.section __TEXT,__literal8,8byte_literals
\\.align 8
\\L._q2:
\\ .double 1.2345
});
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\extern double q1();
\\extern double q2();
\\extern const char* s1;
\\extern const char* s2;
\\extern const char* s3;
\\int main() {
\\ printf("%s, %s, %s, %f, %f", s1, s2, s3, q1(), q2());
\\ return 0;
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(main_o);
const check = exe.checkObject();
check.dumpSection("__TEXT,__const");
check.checkContains("\x8d\x97n\x12\x83\xc0\xf3?");
check.dumpSection("__TEXT,__cstring");
check.checkContains("hello\x00world\x00%s, %s, %s, %f, %f\x00");
test_step.dependOn(&check.step);
return test_step;
}
fn testMergeLiteralsAlignment(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-literals-alignment", opts);
const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _s1
\\.globl _s2
\\
\\.section __TEXT,__cstring,cstring_literals
\\.align 3
\\_s1:
\\ .asciz "str1"
\\_s2:
\\ .asciz "str2"
});
const b_o = addObject(b, opts, .{ .name = "b", .asm_source_bytes =
\\.globl _s3
\\.globl _s4
\\
\\.section __TEXT,__cstring,cstring_literals
\\.align 2
\\_s3:
\\ .asciz "str1"
\\_s4:
\\ .asciz "str2"
});
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <assert.h>
\\#include <stdint.h>
\\#include <stdio.h>
\\extern const char* s1;
\\extern const char* s2;
\\extern const char* s3;
\\extern const char* s4;
\\int main() {
\\ assert((uintptr_t)(&s1) % 8 == 0 && s1 == s3);
\\ assert((uintptr_t)(&s2) % 8 == 0 && s2 == s4);
\\ printf("%s%s%s%s", &s1, &s2, &s3, &s4);
\\ return 0;
\\}
, .c_source_flags = &.{"-Wno-format"} });
const runWithChecks = struct {
fn runWithChecks(step: *Step, exe: *Compile) void {
const run = addRunArtifact(exe);
run.expectStdOutEqual("str1str2str1str2");
step.dependOn(&run.step);
const check = exe.checkObject();
check.dumpSection("__TEXT,__cstring");
check.checkContains("str1\x00\x00\x00\x00str2\x00");
check.checkInHeaders();
check.checkExact("segname __TEXT");
check.checkExact("sectname __cstring");
check.checkExact("align 3");
step.dependOn(&check.step);
}
}.runWithChecks;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(main_o);
runWithChecks(test_step, exe);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(b_o);
exe.root_module.addObject(a_o);
exe.root_module.addObject(main_o);
runWithChecks(test_step, exe);
}
return test_step;
}
fn testMergeLiteralsObjc(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-literals-objc", opts);
const main_o = addObject(b, opts, .{ .name = "main", .objc_source_bytes =
\\#import <Foundation/Foundation.h>;
\\
\\extern void foo();
\\
\\int main() {
\\ NSString *thing = @"aaa";
\\
\\ SEL sel = @selector(lowercaseString);
\\ NSString *lower = (([thing respondsToSelector:sel]) ? @"YES" : @"NO");
\\ NSLog (@"Responds to lowercaseString: %@", lower);
\\ if ([thing respondsToSelector:sel]) //(lower == @"YES")
\\ NSLog(@"lowercaseString is: %@", [thing lowercaseString]);
\\
\\ foo();
\\}
});
const a_o = addObject(b, opts, .{ .name = "a", .objc_source_bytes =
\\#import <Foundation/Foundation.h>;
\\
\\void foo() {
\\ NSString *thing = @"aaa";
\\ SEL sel = @selector(lowercaseString);
\\ NSString *lower = (([thing respondsToSelector:sel]) ? @"YES" : @"NO");
\\ NSLog (@"Responds to lowercaseString in foo(): %@", lower);
\\ if ([thing respondsToSelector:sel]) //(lower == @"YES")
\\ NSLog(@"lowercaseString in foo() is: %@", [thing lowercaseString]);
\\ SEL sel2 = @selector(uppercaseString);
\\ NSString *upper = (([thing respondsToSelector:sel2]) ? @"YES" : @"NO");
\\ NSLog (@"Responds to uppercaseString in foo(): %@", upper);
\\ if ([thing respondsToSelector:sel2]) //(upper == @"YES")
\\ NSLog(@"uppercaseString in foo() is: %@", [thing uppercaseString]);
\\}
});
const runWithChecks = struct {
fn runWithChecks(step: *Step, exe: *Compile) void {
const builder = step.owner;
const run = addRunArtifact(exe);
run.addCheck(.{ .expect_stderr_match = builder.dupe("Responds to lowercaseString: YES") });
run.addCheck(.{ .expect_stderr_match = builder.dupe("lowercaseString is: aaa") });
run.addCheck(.{ .expect_stderr_match = builder.dupe("Responds to lowercaseString in foo(): YES") });
run.addCheck(.{ .expect_stderr_match = builder.dupe("lowercaseString in foo() is: aaa") });
run.addCheck(.{ .expect_stderr_match = builder.dupe("Responds to uppercaseString in foo(): YES") });
run.addCheck(.{ .expect_stderr_match = builder.dupe("uppercaseString in foo() is: AAA") });
step.dependOn(&run.step);
const check = exe.checkObject();
check.dumpSection("__TEXT,__objc_methname");
check.checkContains("lowercaseString\x00");
check.dumpSection("__TEXT,__objc_methname");
check.checkContains("uppercaseString\x00");
step.dependOn(&check.step);
}
}.runWithChecks;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(a_o);
exe.root_module.linkFramework("Foundation", .{});
runWithChecks(test_step, exe);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(main_o);
exe.root_module.linkFramework("Foundation", .{});
runWithChecks(test_step, exe);
}
{
const b_o = addObject(b, opts, .{ .name = "b" });
b_o.root_module.addObject(a_o);
b_o.root_module.addObject(main_o);
const exe = addExecutable(b, opts, .{ .name = "main3" });
exe.root_module.addObject(b_o);
exe.root_module.linkFramework("Foundation", .{});
runWithChecks(test_step, exe);
}
return test_step;
}
fn testMhExecuteHeader(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "mh-execute-header", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("[referenced dynamically] external __mh_execute_header");
test_step.dependOn(&check.step);
return test_step;
}
fn testNoDeadStrip(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "no-dead-strip", opts);
const exe = addExecutable(b, opts, .{ .name = "name", .c_source_bytes =
\\__attribute__((used)) int bogus1 = 0;
\\int bogus2 = 0;
\\int foo = 42;
\\int main() {
\\ return foo - 42;
\\}
});
exe.link_gc_sections = true;
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("external _bogus1");
check.checkInSymtab();
check.checkNotPresent("external _bogus2");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testNoExportsDylib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "no-exports-dylib", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes = "static void abc() {}" });
const check = dylib.checkObject();
check.checkInSymtab();
check.checkNotPresent("external _abc");
test_step.dependOn(&check.step);
return test_step;
}
fn testNeededFramework(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "needed-framework", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.linkFramework("Cocoa", .{ .needed = true });
exe.dead_strip_dylibs = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkContains("Cocoa");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testNeededLibrary(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "needed-library", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes = "int a = 42;" });
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.linkSystemLibrary("a", .{ .needed = true });
exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
exe.dead_strip_dylibs = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkContains("liba.dylib");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testObjc(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "objc", opts);
const lib = addStaticLibrary(b, opts, .{ .name = "a", .objc_source_bytes =
\\#import <Foundation/Foundation.h>
\\@interface Foo : NSObject
\\@end
\\@implementation Foo
\\@end
});
{
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.linkFramework("Foundation", .{});
exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("_OBJC_");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.linkFramework("Foundation", .{});
exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
exe.force_load_objc = true;
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("_OBJC_");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testObjcpp(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "objcpp", opts);
const foo_h = foo_h: {
const wf = WriteFile.create(b);
break :foo_h wf.add("Foo.h",
\\#import <Foundation/Foundation.h>
\\@interface Foo : NSObject
\\- (NSString *)name;
\\@end
);
};
const foo_o = addObject(b, opts, .{ .name = "foo", .objcpp_source_bytes =
\\#import "Foo.h"
\\@implementation Foo
\\- (NSString *)name
\\{
\\ NSString *str = [[NSString alloc] initWithFormat:@"Zig"];
\\ return str;
\\}
\\@end
});
foo_o.root_module.addIncludePath(foo_h.dirname());
foo_o.root_module.link_libcpp = true;
const exe = addExecutable(b, opts, .{ .name = "main", .objcpp_source_bytes =
\\#import "Foo.h"
\\#import <assert.h>
\\#include <iostream>
\\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;
\\ }
\\}
});
exe.root_module.addIncludePath(foo_h.dirname());
exe.root_module.addObject(foo_o);
exe.root_module.link_libcpp = true;
exe.root_module.linkFramework("Foundation", .{});
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello from C++ and Zig");
test_step.dependOn(&run.step);
return test_step;
}
fn testPagezeroSize(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "pagezero-size", opts);
{
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main () { return 0; }" });
exe.pagezero_size = 0x4000;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("LC 0");
check.checkExact("segname __PAGEZERO");
check.checkExact("vmaddr 0");
check.checkExact("vmsize 4000");
check.checkInHeaders();
check.checkExact("segname __TEXT");
check.checkExact("vmaddr 4000");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main () { return 0; }" });
exe.pagezero_size = 0;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("LC 0");
check.checkExact("segname __TEXT");
check.checkExact("vmaddr 0");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testReexportsZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "reexports-zig", opts);
const lib = addStaticLibrary(b, opts, .{ .name = "a", .zig_source_bytes =
\\const x: i32 = 42;
\\export fn foo() i32 {
\\ return x;
\\}
\\comptime {
\\ @export(&foo, .{ .name = "bar", .linkage = .strong });
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\extern int foo();
\\extern int bar();
\\int main() {
\\ return bar() - foo();
\\}
});
exe.root_module.linkLibrary(lib);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testRelocatable(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable", opts);
const a_o = addObject(b, opts, .{ .name = "a", .cpp_source_bytes =
\\#include <stdexcept>
\\int try_me() {
\\ throw std::runtime_error("Oh no!");
\\}
});
a_o.root_module.link_libcpp = true;
const b_o = addObject(b, opts, .{ .name = "b", .cpp_source_bytes =
\\extern int try_me();
\\int try_again() {
\\ return try_me();
\\}
});
const main_o = addObject(b, opts, .{ .name = "main", .cpp_source_bytes =
\\#include <iostream>
\\#include <stdexcept>
\\extern int try_again();
\\int main() {
\\ try {
\\ try_again();
\\ } catch (const std::exception &e) {
\\ std::cout << "exception=" << e.what();
\\ }
\\ return 0;
\\}
});
main_o.root_module.link_libcpp = true;
const exp_stdout = "exception=Oh no!";
{
const c_o = addObject(b, opts, .{ .name = "c" });
c_o.root_module.addObject(a_o);
c_o.root_module.addObject(b_o);
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(c_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
{
const d_o = addObject(b, opts, .{ .name = "d" });
d_o.root_module.addObject(a_o);
d_o.root_module.addObject(b_o);
d_o.root_module.addObject(main_o);
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(d_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testRelocatableZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable-zig", opts);
const a_o = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
\\const std = @import("std");
\\export var foo: i32 = 0;
\\export fn incrFoo() void {
\\ foo += 1;
\\ std.debug.print("incrFoo={d}\n", .{foo});
\\}
});
const b_o = addObject(b, opts, .{ .name = "b", .zig_source_bytes =
\\const std = @import("std");
\\extern var foo: i32;
\\export fn decrFoo() void {
\\ foo -= 1;
\\ std.debug.print("decrFoo={d}\n", .{foo});
\\}
});
const main_o = addObject(b, opts, .{ .name = "main", .zig_source_bytes =
\\const std = @import("std");
\\extern var foo: i32;
\\extern fn incrFoo() void;
\\extern fn decrFoo() void;
\\pub fn main() void {
\\ const init = foo;
\\ incrFoo();
\\ decrFoo();
\\ if (init == foo) @panic("Oh no!");
\\}
});
const c_o = addObject(b, opts, .{ .name = "c" });
c_o.root_module.addObject(a_o);
c_o.root_module.addObject(b_o);
c_o.root_module.addObject(main_o);
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(c_o);
const run = addRunArtifact(exe);
run.addCheck(.{ .expect_stderr_match = b.dupe("incrFoo=1") });
run.addCheck(.{ .expect_stderr_match = b.dupe("decrFoo=0") });
run.addCheck(.{ .expect_stderr_match = b.dupe("panic: Oh no!") });
test_step.dependOn(&run.step);
return test_step;
}
fn testSearchStrategy(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "search-strategy", opts);
const obj = addObject(b, opts, .{ .name = "a", .c_source_bytes =
\\#include<stdio.h>
\\char world[] = "world";
\\char* hello() {
\\ return "Hello";
\\}
});
const liba = addStaticLibrary(b, opts, .{ .name = "a" });
liba.root_module.addObject(obj);
const dylib = addSharedLibrary(b, opts, .{ .name = "a" });
dylib.root_module.addObject(obj);
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include<stdio.h>
\\char* hello();
\\extern char world[];
\\int main() {
\\ printf("%s %s", hello(), world);
\\ return 0;
\\}
});
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("a", .{ .use_pkg_config = .no, .search_strategy = .mode_first });
exe.root_module.addLibraryPath(liba.getEmittedBinDirectory());
exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkContains("liba.dylib");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("a", .{ .use_pkg_config = .no, .search_strategy = .paths_first });
exe.root_module.addLibraryPath(liba.getEmittedBinDirectory());
exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkNotPresent("liba.dylib");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testSectionBoundarySymbols(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "section-boundary-symbols", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.cpp_source_bytes =
\\constexpr const char* MESSAGE __attribute__((used, section("__DATA_CONST,__message_ptr"))) = "codebase";
,
});
const main_o = addObject(b, opts, .{
.name = "main",
.zig_source_bytes =
\\const std = @import("std");
\\extern fn interop() ?[*:0]const u8;
\\pub fn main() !void {
\\ std.debug.print("All your {s} are belong to us.\n", .{
\\ if (interop()) |ptr| std.mem.span(ptr) else "(null)",
\\ });
\\}
,
});
{
const obj2 = addObject(b, opts, .{
.name = "obj2",
.cpp_source_bytes =
\\extern const char* message_pointer __asm("section$start$__DATA_CONST$__message_ptr");
\\extern "C" const char* interop() {
\\ return message_pointer;
\\}
,
});
const exe = addExecutable(b, opts, .{ .name = "test" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj2);
exe.root_module.addObject(main_o);
const run = b.addRunArtifact(exe);
run.skip_foreign_checks = true;
run.expectStdErrEqual("All your codebase are belong to us.\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("external section$start$__DATA_CONST$__message_ptr");
test_step.dependOn(&check.step);
}
{
const obj3 = addObject(b, opts, .{
.name = "obj3",
.cpp_source_bytes =
\\extern const char* message_pointer __asm("section$start$__DATA_CONST$__not_present");
\\extern "C" const char* interop() {
\\ return message_pointer;
\\}
,
});
const exe = addExecutable(b, opts, .{ .name = "test" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj3);
exe.root_module.addObject(main_o);
const run = b.addRunArtifact(exe);
run.skip_foreign_checks = true;
run.expectStdErrEqual("All your (null) are belong to us.\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("external section$start$__DATA_CONST$__not_present");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testSectionBoundarySymbols2(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "section-boundary-symbols-2", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\struct pair { int a; int b; };
\\struct pair first __attribute__((section("__DATA,__pairs"))) = { 1, 2 };
\\struct pair second __attribute__((section("__DATA,__pairs"))) = { 3, 4 };
\\extern struct pair pairs_start __asm("section$start$__DATA$__pairs");
\\extern struct pair pairs_end __asm("section$end$__DATA$__pairs");
\\int main() {
\\ printf("%d,%d\n", first.a, first.b);
\\ printf("%d,%d\n", second.a, second.b);
\\ struct pair* p;
\\ for (p = &pairs_start; p < &pairs_end; p++) {
\\ p->a = 0;
\\ }
\\ printf("%d,%d\n", first.a, first.b);
\\ printf("%d,%d\n", second.a, second.b);
\\ return 0;
\\}
});
const run = b.addRunArtifact(exe);
run.skip_foreign_checks = true;
run.expectStdOutEqual(
\\1,2
\\3,4
\\0,2
\\0,4
\\
);
test_step.dependOn(&run.step);
return test_step;
}
fn testSegmentBoundarySymbols(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "segment-boundary-symbols", opts);
const obj1 = addObject(b, opts, .{ .name = "a", .cpp_source_bytes =
\\constexpr const char* MESSAGE __attribute__((used, section("__DATA_CONST_1,__message_ptr"))) = "codebase";
});
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\const char* interop();
\\int main() {
\\ printf("All your %s are belong to us.\n", interop());
\\ return 0;
\\}
});
{
const obj2 = addObject(b, opts, .{ .name = "b", .cpp_source_bytes =
\\extern const char* message_pointer __asm("segment$start$__DATA_CONST_1");
\\extern "C" const char* interop() {
\\ return message_pointer;
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj2);
exe.root_module.addObject(main_o);
const run = addRunArtifact(exe);
run.expectStdOutEqual("All your codebase are belong to us.\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("external segment$start$__DATA_CONST_1");
test_step.dependOn(&check.step);
}
{
const obj2 = addObject(b, opts, .{ .name = "c", .cpp_source_bytes =
\\extern const char* message_pointer __asm("segment$start$__DATA_1");
\\extern "C" const char* interop() {
\\ return message_pointer;
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj2);
exe.root_module.addObject(main_o);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd SEGMENT_64");
check.checkExact("segname __DATA_1");
check.checkExtract("vmsize {vmsize}");
check.checkExtract("filesz {filesz}");
check.checkComputeCompare("vmsize", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("filesz", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkInSymtab();
check.checkNotPresent("external segment$start$__DATA_1");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testSymbolStabs(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "symbol-stabs", opts);
const a_o = addObject(b, opts, .{ .name = "a", .c_source_bytes =
\\int foo = 42;
\\int getFoo() {
\\ return foo;
\\}
});
const b_o = addObject(b, opts, .{ .name = "b", .c_source_bytes =
\\int bar = 24;
\\int getBar() {
\\ return bar;
\\}
});
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\extern int getFoo();
\\extern int getBar();
\\int main() {
\\ printf("foo=%d,bar=%d", getFoo(), getBar());
\\ return 0;
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(main_o);
const run = addRunArtifact(exe);
run.expectStdOutEqual("foo=42,bar=24");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("a.o"); // TODO we really should do a fuzzy search like OSO <ignore>/a.o
check.checkInSymtab();
check.checkContains("b.o");
check.checkInSymtab();
check.checkContains("main.o");
test_step.dependOn(&check.step);
return test_step;
}
fn testStackSize(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "stack-size", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
exe.stack_size = 0x100000000;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd MAIN");
check.checkExact("stacksize 100000000");
test_step.dependOn(&check.step);
return test_step;
}
fn testTbdv3(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tbdv3", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes = "int getFoo() { return 42; }" });
const tbd = tbd: {
const wf = WriteFile.create(b);
break :tbd wf.add("liba.tbd",
\\--- !tapi-tbd-v3
\\archs: [ arm64, x86_64 ]
\\uuids: [ 'arm64: DEADBEEF', 'x86_64: BEEFDEAD' ]
\\platform: macos
\\install-name: @rpath/liba.dylib
\\current-version: 0
\\exports:
\\ - archs: [ arm64, x86_64 ]
\\ symbols: [ _getFoo ]
);
};
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int getFoo();
\\int main() {
\\ return getFoo() - 42;
\\}
});
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(tbd.dirname());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testTentative(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tentative", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\int foo;
\\int bar;
\\int baz = 42;
, &.{"-fcommon"});
addCSourceBytes(exe,
\\#include<stdio.h>
\\int foo;
\\int bar = 5;
\\int baz;
\\int main() {
\\ printf("%d %d %d\n", foo, bar, baz);
\\}
, &.{"-fcommon"});
const run = addRunArtifact(exe);
run.expectStdOutEqual("0 5 42\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testThunks(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "thunks", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\void bar() {
\\ printf("bar");
\\}
\\void foo() {
\\ fprintf(stdout, "foo");
\\}
\\int main() {
\\ foo();
\\ bar();
\\ return 0;
\\}
});
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("_printf__thunk");
check.checkInSymtab();
check.checkContains("_fprintf__thunk");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("foobar");
test_step.dependOn(&run.step);
return test_step;
}
fn testTls(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes =
\\_Thread_local int a;
\\int getA() {
\\ return a;
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include<stdio.h>
\\extern _Thread_local int a;
\\extern int getA();
\\int getA2() {
\\ return a;
\\}
\\int main() {
\\ a = 2;
\\ printf("%d %d %d", a, getA(), getA2());
\\ return 0;
\\}
});
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
const run = addRunArtifact(exe);
run.expectStdOutEqual("2 2 2");
test_step.dependOn(&run.step);
return test_step;
}
// https://github.com/ziglang/zig/issues/19221
fn testTlsPointers(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-pointers", opts);
const foo_h = foo_h: {
const wf = WriteFile.create(b);
break :foo_h wf.add("foo.h",
\\template<typename just4fun>
\\struct Foo {
\\
\\public:
\\ static int getVar() {
\\ static int thread_local var = 0;
\\ ++var;
\\ return var;
\\}
\\};
);
};
const bar_o = addObject(b, opts, .{ .name = "bar", .cpp_source_bytes =
\\#include "foo.h"
\\int bar() {
\\ int v1 = Foo<int>::getVar();
\\ return v1;
\\}
});
bar_o.root_module.addIncludePath(foo_h.dirname());
bar_o.root_module.link_libcpp = true;
const baz_o = addObject(b, opts, .{ .name = "baz", .cpp_source_bytes =
\\#include "foo.h"
\\int baz() {
\\ int v1 = Foo<unsigned>::getVar();
\\ return v1;
\\}
});
baz_o.root_module.addIncludePath(foo_h.dirname());
baz_o.root_module.link_libcpp = true;
const main_o = addObject(b, opts, .{ .name = "main", .cpp_source_bytes =
\\extern int bar();
\\extern int baz();
\\int main() {
\\ int v1 = bar();
\\ int v2 = baz();
\\ return v1 != v2;
\\}
});
main_o.root_module.addIncludePath(foo_h.dirname());
main_o.root_module.link_libcpp = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(bar_o);
exe.root_module.addObject(baz_o);
exe.root_module.addObject(main_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsLargeTbss(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-large-tbss", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\_Thread_local int x[0x8000];
\\_Thread_local int y[0x8000];
\\int main() {
\\ x[0] = 3;
\\ x[0x7fff] = 5;
\\ printf("%d %d %d %d %d %d\n", x[0], x[1], x[0x7fff], y[0], y[1], y[0x7fff]);
\\}
});
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 0 5 0 0 0\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-zig", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\const std = @import("std");
\\threadlocal var x: i32 = 0;
\\threadlocal var y: i32 = -1;
\\pub fn main() void {
\\ var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
\\ stdout_writer.interface.print("{d} {d}\n", .{x, y}) catch unreachable;
\\ x -= 1;
\\ y += 1;
\\ stdout_writer.interface.print("{d} {d}\n", .{x, y}) catch unreachable;
\\}
});
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\0 -1
\\-1 0
\\
);
test_step.dependOn(&run.step);
return test_step;
}
fn testTwoLevelNamespace(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "two-level-namespace", opts);
const liba = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes =
\\#include <stdio.h>
\\int foo = 1;
\\int* ptr_to_foo = &foo;
\\int getFoo() {
\\ return foo;
\\}
\\void printInA() {
\\ printf("liba: getFoo()=%d, ptr_to_foo=%d\n", getFoo(), *ptr_to_foo);
\\}
});
{
const check = liba.checkObject();
check.checkInDyldLazyBind();
check.checkNotPresent("(flat lookup) _getFoo");
check.checkInIndirectSymtab();
check.checkNotPresent("_getFoo");
test_step.dependOn(&check.step);
}
const libb = addSharedLibrary(b, opts, .{ .name = "b", .c_source_bytes =
\\#include <stdio.h>
\\int foo = 2;
\\int* ptr_to_foo = &foo;
\\int getFoo() {
\\ return foo;
\\}
\\void printInB() {
\\ printf("libb: getFoo()=%d, ptr_to_foo=%d\n", getFoo(), *ptr_to_foo);
\\}
});
{
const check = libb.checkObject();
check.checkInDyldLazyBind();
check.checkNotPresent("(flat lookup) _getFoo");
check.checkInIndirectSymtab();
check.checkNotPresent("_getFoo");
test_step.dependOn(&check.step);
}
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int getFoo();
\\extern int* ptr_to_foo;
\\void printInA();
\\void printInB();
\\int main() {
\\ printf("main: getFoo()=%d, ptr_to_foo=%d\n", getFoo(), *ptr_to_foo);
\\ printInA();
\\ printInB();
\\ return 0;
\\}
});
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.linkSystemLibrary("b", .{});
exe.root_module.addLibraryPath(liba.getEmittedBinDirectory());
exe.root_module.addLibraryPath(libb.getEmittedBinDirectory());
exe.root_module.addRPath(liba.getEmittedBinDirectory());
exe.root_module.addRPath(libb.getEmittedBinDirectory());
const check = exe.checkObject();
check.checkInSymtab();
check.checkExact("(undefined) external _getFoo (from liba)");
check.checkInSymtab();
check.checkExact("(undefined) external _printInA (from liba)");
check.checkInSymtab();
check.checkExact("(undefined) external _printInB (from libb)");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\main: getFoo()=1, ptr_to_foo=1
\\liba: getFoo()=1, ptr_to_foo=1
\\libb: getFoo()=2, ptr_to_foo=2
\\
);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("b", .{});
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(liba.getEmittedBinDirectory());
exe.root_module.addLibraryPath(libb.getEmittedBinDirectory());
exe.root_module.addRPath(liba.getEmittedBinDirectory());
exe.root_module.addRPath(libb.getEmittedBinDirectory());
const check = exe.checkObject();
check.checkInSymtab();
check.checkExact("(undefined) external _getFoo (from libb)");
check.checkInSymtab();
check.checkExact("(undefined) external _printInA (from liba)");
check.checkInSymtab();
check.checkExact("(undefined) external _printInB (from libb)");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\main: getFoo()=2, ptr_to_foo=2
\\liba: getFoo()=1, ptr_to_foo=1
\\libb: getFoo()=2, ptr_to_foo=2
\\
);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testDiscardLocalSymbols(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "discard-local-symbols", opts);
const obj = addObject(b, opts, .{ .name = "a", .c_source_bytes = "static int foo = 42;" });
const lib = addStaticLibrary(b, opts, .{ .name = "a" });
lib.root_module.addObject(obj);
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
{
const exe = addExecutable(b, opts, .{ .name = "main3" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(obj);
exe.discard_local_symbols = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("_foo");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main4" });
exe.root_module.addObject(main_o);
exe.root_module.linkLibrary(lib);
exe.discard_local_symbols = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("_foo");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testUndefinedFlag(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "undefined-flag", opts);
const obj = addObject(b, opts, .{ .name = "a", .c_source_bytes = "int foo = 42;" });
const lib = addStaticLibrary(b, opts, .{ .name = "a" });
lib.root_module.addObject(obj);
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.linkLibrary(lib);
exe.forceUndefinedSymbol("_foo");
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("_foo");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.linkLibrary(lib);
exe.forceUndefinedSymbol("_foo");
exe.link_gc_sections = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("_foo");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main3" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(obj);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("_foo");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main4" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(obj);
exe.link_gc_sections = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("_foo");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testUndefinedDynamicLookup(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "undefined-dynamic-lookup", opts);
// Create a dylib with an undefined external symbol reference
const dylib = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dylib,
\\extern int undefined_symbol(void);
\\int call_undefined(void) {
\\ return undefined_symbol();
\\}
, &.{});
dylib.linker_allow_shlib_undefined = true;
// Verify the Mach-O header does NOT contain NOUNDEFS flag
const check = dylib.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkNotPresent("NOUNDEFS");
test_step.dependOn(&check.step);
return test_step;
}
fn testUnresolvedError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unresolved-error", opts);
const obj = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
\\extern fn foo() i32;
\\export fn bar() i32 { return foo() + 1; }
});
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\const std = @import("std");
\\extern fn foo() i32;
\\extern fn bar() i32;
\\pub fn main() void {
\\ std.debug.print("foo() + bar() = {d}", .{foo() + bar()});
\\}
});
exe.root_module.addObject(obj);
// TODO order should match across backends if possible
if (opts.use_llvm) {
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: undefined symbol: _foo",
"note: referenced by /?/a.o:_bar",
"note: referenced by /?/main_zcu.o:_main.main",
} });
} else {
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: undefined symbol: _foo",
"note: referenced by /?/main.o:_main.main",
"note: referenced by /?/a.o:__TEXT$__text_zig",
} });
}
return test_step;
}
fn testUnresolvedError2(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unresolved-error-2", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\pub fn main() !void {
\\ const msg_send_fn = @extern(
\\ *const fn () callconv(.c) usize,
\\ .{ .name = "objc_msgSend$initWithContentRect:styleMask:backing:defer:screen:" },
\\ );
\\ _ = @call(
\\ .auto,
\\ msg_send_fn,
\\ .{},
\\ );
\\}
});
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: undefined symbol: _objc_msgSend",
"note: referenced implicitly",
} });
return test_step;
}
fn testUnwindInfo(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unwind-info", opts);
const all_h = all_h: {
const wf = WriteFile.create(b);
break :all_h wf.add("all.h",
\\#ifndef ALL
\\#define ALL
\\
\\#include <cstddef>
\\#include <string>
\\#include <stdexcept>
\\
\\struct SimpleString {
\\ SimpleString(size_t max_size);
\\ ~SimpleString();
\\
\\ void print(const char* tag) const;
\\ bool append_line(const char* x);
\\
\\private:
\\ size_t max_size;
\\ char* buffer;
\\ size_t length;
\\};
\\
\\struct SimpleStringOwner {
\\ SimpleStringOwner(const char* x);
\\ ~SimpleStringOwner();
\\
\\private:
\\ SimpleString string;
\\};
\\
\\class Error: public std::exception {
\\public:
\\ explicit Error(const char* msg) : msg{ msg } {}
\\ virtual ~Error() noexcept {}
\\ virtual const char* what() const noexcept {
\\ return msg.c_str();
\\ }
\\
\\protected:
\\ std::string msg;
\\};
\\
\\#endif
);
};
const main_o = addObject(b, opts, .{ .name = "main", .cpp_source_bytes =
\\#include "all.h"
\\#include <cstdio>
\\
\\void fn_c() {
\\ SimpleStringOwner c{ "cccccccccc" };
\\}
\\
\\void fn_b() {
\\ SimpleStringOwner b{ "b" };
\\ fn_c();
\\}
\\
\\int main() {
\\ try {
\\ SimpleStringOwner a{ "a" };
\\ fn_b();
\\ SimpleStringOwner d{ "d" };
\\ } catch (const Error& e) {
\\ printf("Error: %s\n", e.what());
\\ } catch(const std::exception& e) {
\\ printf("Exception: %s\n", e.what());
\\ }
\\ return 0;
\\}
});
main_o.root_module.addIncludePath(all_h.dirname());
main_o.root_module.link_libcpp = true;
const simple_string_o = addObject(b, opts, .{ .name = "simple_string", .cpp_source_bytes =
\\#include "all.h"
\\#include <cstdio>
\\#include <cstring>
\\
\\SimpleString::SimpleString(size_t max_size)
\\: max_size{ max_size }, length{} {
\\ if (max_size == 0) {
\\ throw Error{ "Max size must be at least 1." };
\\ }
\\ buffer = new char[max_size];
\\ buffer[0] = 0;
\\}
\\
\\SimpleString::~SimpleString() {
\\ delete[] buffer;
\\}
\\
\\void SimpleString::print(const char* tag) const {
\\ printf("%s: %s", tag, buffer);
\\}
\\
\\bool SimpleString::append_line(const char* x) {
\\ const auto x_len = strlen(x);
\\ if (x_len + length + 2 > max_size) return false;
\\ std::strncpy(buffer + length, x, max_size - length);
\\ length += x_len;
\\ buffer[length++] = '\n';
\\ buffer[length] = 0;
\\ return true;
\\}
});
simple_string_o.root_module.addIncludePath(all_h.dirname());
simple_string_o.root_module.link_libcpp = true;
const simple_string_owner_o = addObject(b, opts, .{ .name = "simple_string_owner", .cpp_source_bytes =
\\#include "all.h"
\\
\\SimpleStringOwner::SimpleStringOwner(const char* x) : string{ 10 } {
\\ if (!string.append_line(x)) {
\\ throw Error{ "Not enough memory!" };
\\ }
\\ string.print("Constructed");
\\}
\\
\\SimpleStringOwner::~SimpleStringOwner() {
\\ string.print("About to destroy");
\\}
});
simple_string_owner_o.root_module.addIncludePath(all_h.dirname());
simple_string_owner_o.root_module.link_libcpp = true;
const exp_stdout =
\\Constructed: a
\\Constructed: b
\\About to destroy: b
\\About to destroy: a
\\Error: Not enough memory!
\\
;
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(simple_string_o);
exe.root_module.addObject(simple_string_owner_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("(was private external) ___gxx_personality_v0");
test_step.dependOn(&check.step);
return test_step;
}
fn testEhFramePointerEncodingSdata4(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "eh_frame-pointer-encoding-sdata4", opts);
const a_o = addObject(b, opts, .{ .name = "foo", .asm_source_bytes =
\\.global _foo
\\.align 2
\\_foo:
\\ mov w0, #100
\\ ret
\\LEND_foo:
\\
\\.section __TEXT,__gcc_except_tab
\\LLSDA_foo:
\\ .byte 0xff
\\ .byte 0xff
\\ .byte 0x01
\\ .uleb128 0
\\
\\.section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
\\LCIE:
\\ .long LCIE_end - LCIE_start
\\LCIE_start:
\\ .long 0 ; CIE ID
\\ .byte 1 ; Version
\\ .asciz "zLR" ; Augmentation string
\\ .uleb128 1 ; Code alignment factor
\\ .sleb128 -8 ; Data alignment factor
\\ .byte 30 ; Return address register
\\ .uleb128 2 ; Augmentation data length
\\ .byte 0x1b ; LSDA pointer encoding (DW_EH_PE_pcrel | DW_EH_PE_sdata4)
\\ .byte 0x1b ; FDE pointer encoding (DW_EH_PE_pcrel | DW_EH_PE_sdata4)
\\ .byte 0x0c ; DW_CFA_def_cfa
\\ .uleb128 31 ; Reg 31
\\ .uleb128 0 ; Offset 0
\\ .align 3
\\LCIE_end:
\\LFDE:
\\ .long LFDE_end - LFDE_start
\\LFDE_start:
\\ .long LFDE_start - LCIE ; CIE pointer
\\ .long _foo - . ; PC begin
\\ .long LEND_foo - _foo ; PC range
\\ .uleb128 4 ; Augmentation data length
\\ .long LLSDA_foo - . ; LSDA pointer
\\ .align 3
\\LFDE_end:
});
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int foo();
\\int main() {
\\ printf("%d\n", foo());
\\ return 0;
\\}
});
exe.root_module.addObject(a_o);
const run = addRunArtifact(exe);
run.expectStdOutEqual("100\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testUnwindInfoNoSubsectionsArm64(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unwind-info-no-subsections-arm64", opts);
const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _foo
\\.align 4
\\_foo:
\\ .cfi_startproc
\\ stp x29, x30, [sp, #-32]!
\\ .cfi_def_cfa_offset 32
\\ .cfi_offset w30, -24
\\ .cfi_offset w29, -32
\\ mov x29, sp
\\ .cfi_def_cfa w29, 32
\\ bl _bar
\\ ldp x29, x30, [sp], #32
\\ .cfi_restore w29
\\ .cfi_restore w30
\\ .cfi_def_cfa_offset 0
\\ ret
\\ .cfi_endproc
\\
\\.globl _bar
\\.align 4
\\_bar:
\\ .cfi_startproc
\\ sub sp, sp, #32
\\ .cfi_def_cfa_offset -32
\\ stp x29, x30, [sp, #16]
\\ .cfi_offset w30, -24
\\ .cfi_offset w29, -32
\\ mov x29, sp
\\ .cfi_def_cfa w29, 32
\\ mov w0, #4
\\ ldp x29, x30, [sp, #16]
\\ .cfi_restore w29
\\ .cfi_restore w30
\\ add sp, sp, #32
\\ .cfi_def_cfa_offset 0
\\ ret
\\ .cfi_endproc
});
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int foo();
\\int main() {
\\ printf("%d\n", foo());
\\ return 0;
\\}
});
exe.root_module.addObject(a_o);
const run = addRunArtifact(exe);
run.expectStdOutEqual("4\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testUnwindInfoNoSubsectionsX64(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unwind-info-no-subsections-x64", opts);
const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _foo
\\_foo:
\\ .cfi_startproc
\\ push %rbp
\\ .cfi_def_cfa_offset 8
\\ .cfi_offset %rbp, -8
\\ mov %rsp, %rbp
\\ .cfi_def_cfa_register %rbp
\\ call _bar
\\ pop %rbp
\\ .cfi_restore %rbp
\\ .cfi_def_cfa_offset 0
\\ ret
\\ .cfi_endproc
\\
\\.globl _bar
\\_bar:
\\ .cfi_startproc
\\ push %rbp
\\ .cfi_def_cfa_offset 8
\\ .cfi_offset %rbp, -8
\\ mov %rsp, %rbp
\\ .cfi_def_cfa_register %rbp
\\ mov $4, %rax
\\ pop %rbp
\\ .cfi_restore %rbp
\\ .cfi_def_cfa_offset 0
\\ ret
\\ .cfi_endproc
});
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int foo();
\\int main() {
\\ printf("%d\n", foo());
\\ return 0;
\\}
});
exe.root_module.addObject(a_o);
const run = addRunArtifact(exe);
run.expectStdOutEqual("4\n");
test_step.dependOn(&run.step);
return test_step;
}
// Adapted from https://github.com/llvm/llvm-project/blob/main/lld/test/MachO/weak-binding.s
fn testWeakBind(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "weak-bind", opts);
const lib = addSharedLibrary(b, opts, .{ .name = "foo", .asm_source_bytes =
\\.globl _weak_dysym
\\.weak_definition _weak_dysym
\\_weak_dysym:
\\ .quad 0x1234
\\
\\.globl _weak_dysym_for_gotpcrel
\\.weak_definition _weak_dysym_for_gotpcrel
\\_weak_dysym_for_gotpcrel:
\\ .quad 0x1234
\\
\\.globl _weak_dysym_fn
\\.weak_definition _weak_dysym_fn
\\_weak_dysym_fn:
\\ ret
\\
\\.section __DATA,__thread_vars,thread_local_variables
\\
\\.globl _weak_dysym_tlv
\\.weak_definition _weak_dysym_tlv
\\_weak_dysym_tlv:
\\ .quad 0x1234
});
{
const check = lib.checkObject();
check.checkInExports();
check.checkExtract("[WEAK] {vmaddr1} _weak_dysym");
check.checkExtract("[WEAK] {vmaddr2} _weak_dysym_for_gotpcrel");
check.checkExtract("[WEAK] {vmaddr3} _weak_dysym_fn");
check.checkExtract("[THREAD_LOCAL, WEAK] {vmaddr4} _weak_dysym_tlv");
test_step.dependOn(&check.step);
}
const exe = addExecutable(b, opts, .{ .name = "main", .asm_source_bytes =
\\.globl _main, _weak_external, _weak_external_for_gotpcrel, _weak_external_fn
\\.weak_definition _weak_external, _weak_external_for_gotpcrel, _weak_external_fn, _weak_internal, _weak_internal_for_gotpcrel, _weak_internal_fn
\\
\\_main:
\\ mov _weak_dysym_for_gotpcrel@GOTPCREL(%rip), %rax
\\ mov _weak_external_for_gotpcrel@GOTPCREL(%rip), %rax
\\ mov _weak_internal_for_gotpcrel@GOTPCREL(%rip), %rax
\\ mov _weak_tlv@TLVP(%rip), %rax
\\ mov _weak_dysym_tlv@TLVP(%rip), %rax
\\ mov _weak_internal_tlv@TLVP(%rip), %rax
\\ callq _weak_dysym_fn
\\ callq _weak_external_fn
\\ callq _weak_internal_fn
\\ mov $0, %rax
\\ ret
\\
\\_weak_external:
\\ .quad 0x1234
\\
\\_weak_external_for_gotpcrel:
\\ .quad 0x1234
\\
\\_weak_external_fn:
\\ ret
\\
\\_weak_internal:
\\ .quad 0x1234
\\
\\_weak_internal_for_gotpcrel:
\\ .quad 0x1234
\\
\\_weak_internal_fn:
\\ ret
\\
\\.data
\\ .quad _weak_dysym
\\ .quad _weak_external + 2
\\ .quad _weak_internal
\\
\\.tbss _weak_tlv$tlv$init, 4, 2
\\.tbss _weak_internal_tlv$tlv$init, 4, 2
\\
\\.section __DATA,__thread_vars,thread_local_variables
\\.globl _weak_tlv
\\.weak_definition _weak_tlv, _weak_internal_tlv
\\
\\_weak_tlv:
\\ .quad __tlv_bootstrap
\\ .quad 0
\\ .quad _weak_tlv$tlv$init
\\
\\_weak_internal_tlv:
\\ .quad __tlv_bootstrap
\\ .quad 0
\\ .quad _weak_internal_tlv$tlv$init
});
exe.root_module.linkLibrary(lib);
{
const check = exe.checkObject();
check.checkInExports();
check.checkExtract("[WEAK] {vmaddr1} _weak_external");
check.checkExtract("[WEAK] {vmaddr2} _weak_external_for_gotpcrel");
check.checkExtract("[WEAK] {vmaddr3} _weak_external_fn");
check.checkExtract("[THREAD_LOCAL, WEAK] {vmaddr4} _weak_tlv");
check.checkInDyldBind();
check.checkContains("(libfoo.dylib) _weak_dysym_for_gotpcrel");
check.checkContains("(libfoo.dylib) _weak_dysym_fn");
check.checkContains("(libfoo.dylib) _weak_dysym");
check.checkContains("(libfoo.dylib) _weak_dysym_tlv");
check.checkInDyldWeakBind();
check.checkContains("_weak_external_for_gotpcrel");
check.checkContains("_weak_dysym_for_gotpcrel");
check.checkContains("_weak_external_fn");
check.checkContains("_weak_dysym_fn");
check.checkContains("_weak_dysym");
check.checkContains("_weak_external");
check.checkContains("_weak_tlv");
check.checkContains("_weak_dysym_tlv");
test_step.dependOn(&check.step);
}
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testWeakFramework(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "weak-framework", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.linkFramework("Cocoa", .{ .weak = true });
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_WEAK_DYLIB");
check.checkContains("Cocoa");
test_step.dependOn(&check.step);
return test_step;
}
fn testWeakLibrary(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "weak-library", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes =
\\#include<stdio.h>
\\int a = 42;
\\const char* asStr() {
\\ static char str[3];
\\ sprintf(str, "%d", 42);
\\ return str;
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include<stdio.h>
\\extern int a;
\\extern const char* asStr();
\\int main() {
\\ printf("%d %s", a, asStr());
\\ return 0;
\\}
});
exe.root_module.linkSystemLibrary("a", .{ .weak = true });
exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_WEAK_DYLIB");
check.checkContains("liba.dylib");
check.checkInSymtab();
check.checkExact("(undefined) weakref external _a (from liba)");
check.checkInSymtab();
check.checkExact("(undefined) weakref external _asStr (from liba)");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("42 42");
test_step.dependOn(&run.step);
return test_step;
}
fn testWeakRef(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "weak-ref", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\#include <sys/_types/_fd_def.h>
\\int main(int argc, char** argv) {
\\ printf("__darwin_check_fd_set_overflow: %p\n", __darwin_check_fd_set_overflow);
\\}
});
const check = exe.checkObject();
check.checkInSymtab();
check.checkExact("(undefined) weakref external ___darwin_check_fd_set_overflow (from libSystem.B)");
test_step.dependOn(&check.step);
return test_step;
}
fn addTestStep(b: *Build, comptime prefix: []const u8, opts: Options) *Step {
return link.addTestStep(b, "" ++ prefix, opts);
}
const builtin = @import("builtin");
const addAsmSourceBytes = link.addAsmSourceBytes;
const addCSourceBytes = link.addCSourceBytes;
const addRunArtifact = link.addRunArtifact;
const addObject = link.addObject;
const addExecutable = link.addExecutable;
const addStaticLibrary = link.addStaticLibrary;
const addSharedLibrary = link.addSharedLibrary;
const expectLinkErrors = link.expectLinkErrors;
const link = @import("link.zig");
const std = @import("std");
const Build = std.Build;
const BuildOptions = link.BuildOptions;
const Compile = Step.Compile;
const Options = link.Options;
const Step = Build.Step;
const WriteFile = Step.WriteFile;
@@ -1,163 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const Build = std.Build;
const LazyPath = Build.LazyPath;
const Step = Build.Step;
const Run = Step.Run;
const WriteFile = Step.WriteFile;
pub fn build(b: *Build) void {
const nb_files = b.option(u32, "nb_files", "Number of c files to generate.") orelse 10;
const test_step = b.step("test", "Test it");
b.default_step = test_step;
// generate c files
const files = b.allocator.alloc(LazyPath, nb_files) catch unreachable;
defer b.allocator.free(files);
{
for (files[0 .. nb_files - 1], 1..nb_files) |*file, i| {
const wf = WriteFile.create(b);
file.* = wf.add(b.fmt("src_{}.c", .{i}), b.fmt(
\\extern int foo_0();
\\extern int bar_{}();
\\extern int one_{};
\\int one_{} = 1;
\\int foo_{}() {{ return one_{} + foo_0(); }}
\\int bar_{}() {{ return bar_{}(); }}
, .{ i - 1, i - 1, i, i, i - 1, i, i - 1 }));
}
{
const wf = WriteFile.create(b);
files[nb_files - 1] = wf.add("src_last.c", b.fmt(
\\extern int foo_0();
\\extern int bar_{}();
\\extern int one_{};
\\int foo_last() {{ return one_{} + foo_0(); }}
\\int bar_last() {{ return bar_{}(); }}
, .{ nb_files - 1, nb_files - 1, nb_files - 1, nb_files - 1 }));
}
}
add(b, test_step, files, .Debug);
add(b, test_step, files, .ReleaseSafe);
add(b, test_step, files, .ReleaseSmall);
add(b, test_step, files, .ReleaseFast);
}
fn add(b: *Build, test_step: *Step, files: []const LazyPath, optimize: std.builtin.OptimizeMode) void {
const flags = [_][]const u8{
"-Wall",
"-std=c11",
};
// all files at once
{
const exe = b.addExecutable(.{
.name = "test1",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = optimize,
.target = b.graph.host,
}),
});
for (files) |file| {
exe.root_module.addCSourceFile(.{ .file = file, .flags = &flags });
}
const run_cmd = b.addRunArtifact(exe);
run_cmd.skip_foreign_checks = true;
run_cmd.expectExitCode(0);
test_step.dependOn(&run_cmd.step);
}
// using static librairies
{
const mod_a = b.createModule(.{ .target = b.graph.host, .optimize = optimize });
const mod_b = b.createModule(.{ .target = b.graph.host, .optimize = optimize });
for (files, 1..) |file, i| {
const mod = if (i & 1 == 0) mod_a else mod_b;
mod.addCSourceFile(.{ .file = file, .flags = &flags });
}
const lib_a = b.addLibrary(.{
.linkage = .static,
.name = "test2_a",
.root_module = mod_a,
});
const lib_b = b.addLibrary(.{
.linkage = .static,
.name = "test2_b",
.root_module = mod_b,
});
const exe = b.addExecutable(.{
.name = "test2",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = b.graph.host,
.optimize = optimize,
}),
});
exe.root_module.linkLibrary(lib_a);
exe.root_module.linkLibrary(lib_b);
const run_cmd = b.addRunArtifact(exe);
run_cmd.skip_foreign_checks = true;
run_cmd.expectExitCode(0);
test_step.dependOn(&run_cmd.step);
}
// using static librairies and object files
{
const mod_a = b.createModule(.{ .target = b.graph.host, .optimize = optimize });
const mod_b = b.createModule(.{ .target = b.graph.host, .optimize = optimize });
for (files, 1..) |file, i| {
const obj_mod = b.createModule(.{ .target = b.graph.host, .optimize = optimize });
obj_mod.addCSourceFile(.{ .file = file, .flags = &flags });
const obj = b.addObject(.{
.name = b.fmt("obj_{}", .{i}),
.root_module = obj_mod,
});
const lib_mod = if (i & 1 == 0) mod_a else mod_b;
lib_mod.addObject(obj);
}
const lib_a = b.addLibrary(.{
.linkage = .static,
.name = "test3_a",
.root_module = mod_a,
});
const lib_b = b.addLibrary(.{
.linkage = .static,
.name = "test3_b",
.root_module = mod_b,
});
const exe = b.addExecutable(.{
.name = "test3",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = b.graph.host,
.optimize = optimize,
}),
});
exe.root_module.linkLibrary(lib_a);
exe.root_module.linkLibrary(lib_b);
const run_cmd = b.addRunArtifact(exe);
run_cmd.skip_foreign_checks = true;
run_cmd.expectExitCode(0);
test_step.dependOn(&run_cmd.step);
}
}
@@ -1,20 +0,0 @@
const std = @import("std");
extern fn foo_last() i32;
extern fn bar_last() i32;
export const one_0: i32 = 1;
export fn foo_0() i32 {
return 1234;
}
export fn bar_0() i32 {
return 5678;
}
pub fn main() anyerror!void {
const foo_expected: i32 = 1 + 1234;
const bar_expected: i32 = 5678;
try std.testing.expectEqual(foo_expected, foo_last());
try std.testing.expectEqual(bar_expected, bar_last());
}
-36
View File
@@ -1,36 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
// The code in question will pull-in compiler-rt,
// and therefore link with its archive file.
const lib = b.addExecutable(.{
.name = "main",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = optimize,
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.strip = false,
}),
});
lib.entry = .disabled;
lib.use_llvm = false;
lib.use_lld = false;
lib.root_module.export_symbol_names = &.{"foo"};
const check = lib.checkObject();
check.checkInHeaders();
check.checkExact("Section custom");
check.checkExact("name __trunch"); // Ensure it was imported and resolved
test_step.dependOn(&check.step);
}
-7
View File
@@ -1,7 +0,0 @@
export fn foo() void {
var a: f16 = 2.2;
_ = &a;
// this will pull-in compiler-rt
const b = @trunc(a);
_ = b;
}
-32
View File
@@ -1,32 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
// Library with explicitly set cpu features
const lib = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = .Debug,
.target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
.cpu_features_add = std.Target.wasm.featureSet(&.{.atomics}),
.os_tag = .freestanding,
}),
}),
});
lib.entry = .disabled;
lib.use_llvm = false;
lib.use_lld = false;
// Verify the result contains the features explicitly set on the target for the library.
const check = lib.checkObject();
check.checkInHeaders();
check.checkExact("name target_features");
check.checkExact("features 1");
check.checkExact("+ atomics");
const test_step = b.step("test", "Run linker test");
test_step.dependOn(&check.step);
b.default_step = test_step;
}
-1
View File
@@ -1 +0,0 @@
export fn foo() void {}
-30
View File
@@ -1,30 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test");
b.default_step = test_step;
const lib = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.optimize = .Debug,
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
}),
});
lib.entry = .disabled;
// Disabled to work around the Wasm linker crashing.
// Can be reproduced by commenting out the line below.
lib.bundle_ubsan_rt = false;
lib.use_lld = false;
lib.root_module.export_symbol_names = &.{ "foo", "bar" };
// Object being linked has neither functions nor globals named "foo" or "bar" and
// so these names correctly fail to be exported when creating an executable.
lib.expect_errors = .{ .exact = &.{
"error: manually specified export name 'foo' undefined",
"error: manually specified export name 'bar' undefined",
} };
_ = lib.getEmittedBin();
test_step.dependOn(&lib.step);
}
-2
View File
@@ -1,2 +0,0 @@
export const foo: u32 = 0xbbbbbbbb;
export const bar: u32 = 0xbbbbbbbb;
-79
View File
@@ -1,79 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const no_export = b.addExecutable(.{
.name = "no-export",
.root_module = b.createModule(.{
.root_source_file = b.path("main-hidden.zig"),
.optimize = optimize,
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
}),
});
no_export.entry = .disabled;
no_export.use_llvm = false;
no_export.use_lld = false;
// Don't pull in ubsan, since we're just expecting a very minimal executable.
no_export.bundle_ubsan_rt = false;
const dynamic_export = b.addExecutable(.{
.name = "dynamic",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = optimize,
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
}),
});
dynamic_export.entry = .disabled;
dynamic_export.rdynamic = true;
dynamic_export.use_llvm = false;
dynamic_export.use_lld = false;
// Don't pull in ubsan, since we're just expecting a very minimal executable.
dynamic_export.bundle_ubsan_rt = false;
const force_export = b.addExecutable(.{
.name = "force",
.root_module = b.createModule(.{
.root_source_file = b.path("main-hidden.zig"),
.optimize = optimize,
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
}),
});
force_export.entry = .disabled;
force_export.root_module.export_symbol_names = &.{"foo"};
force_export.use_llvm = false;
force_export.use_lld = false;
// Don't pull in ubsan, since we're just expecting a very minimal executable.
force_export.bundle_ubsan_rt = false;
const check_no_export = no_export.checkObject();
check_no_export.checkInHeaders();
check_no_export.checkExact("Section export");
check_no_export.checkExact("entries 1");
check_no_export.checkExact("name memory");
check_no_export.checkExact("kind memory");
const check_dynamic_export = dynamic_export.checkObject();
check_dynamic_export.checkInHeaders();
check_dynamic_export.checkExact("Section export");
check_dynamic_export.checkExact("entries 2");
check_dynamic_export.checkExact("name foo");
check_dynamic_export.checkExact("kind function");
const check_force_export = force_export.checkObject();
check_force_export.checkInHeaders();
check_force_export.checkExact("Section export");
check_force_export.checkExact("entries 2");
check_force_export.checkExact("name foo");
check_force_export.checkExact("kind function");
test_step.dependOn(&check_no_export.step);
test_step.dependOn(&check_dynamic_export.step);
test_step.dependOn(&check_force_export.step);
}
-4
View File
@@ -1,4 +0,0 @@
fn foo() callconv(.c) void {}
comptime {
@export(&foo, .{ .name = "foo", .visibility = .hidden });
}
-1
View File
@@ -1 +0,0 @@
export fn foo() void {}
-1
View File
@@ -1 +0,0 @@
pub extern "a" fn hello() i32;
-1
View File
@@ -1 +0,0 @@
pub extern "b" fn hello() i32;
-36
View File
@@ -1,36 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const lib = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.optimize = optimize,
}),
});
lib.entry = .disabled;
lib.import_symbols = true; // import `a` and `b`
lib.rdynamic = true; // export `foo`
const check_lib = lib.checkObject();
check_lib.checkInHeaders();
check_lib.checkExact("Section import");
check_lib.checkExact("entries 2"); // a.hello & b.hello
check_lib.checkExact("module a");
check_lib.checkExact("name hello");
check_lib.checkExact("module b");
check_lib.checkExact("name hello");
test_step.dependOn(&check_lib.step);
}
-6
View File
@@ -1,6 +0,0 @@
const a = @import("a.zig").hello;
const b = @import("b.zig").hello;
export fn foo() void {
_ = a();
_ = b();
}
-28
View File
@@ -1,28 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const exe = b.addExecutable(.{
.name = "extern",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = optimize,
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .wasi }),
}),
});
exe.root_module.addCSourceFile(.{ .file = b.path("foo.c"), .flags = &.{} });
exe.use_llvm = false;
exe.use_lld = false;
const run = b.addRunArtifact(exe);
run.skip_foreign_checks = true;
run.expectStdOutEqual("Result: 30");
test_step.dependOn(&run.step);
}
-1
View File
@@ -1 +0,0 @@
int foo = 30;
-8
View File
@@ -1,8 +0,0 @@
const std = @import("std");
extern const foo: u32;
pub fn main() void {
var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
stdout_writer.interface.print("Result: {d}", .{foo}) catch {};
}
-67
View File
@@ -1,67 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const export_table = b.addExecutable(.{
.name = "export_table",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.optimize = optimize,
}),
});
export_table.entry = .disabled;
export_table.use_llvm = false;
export_table.use_lld = false;
export_table.export_table = true;
export_table.link_gc_sections = false;
// Don't pull in ubsan, since we're just expecting a very minimal executable.
export_table.bundle_ubsan_rt = false;
const regular_table = b.addExecutable(.{
.name = "regular_table",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.optimize = optimize,
}),
});
regular_table.entry = .disabled;
regular_table.use_llvm = false;
regular_table.use_lld = false;
regular_table.link_gc_sections = false; // Ensure function table is not empty
// Don't pull in ubsan, since we're just expecting a very minimal executable.
regular_table.bundle_ubsan_rt = false;
const check_export = export_table.checkObject();
const check_regular = regular_table.checkObject();
check_export.checkInHeaders();
check_export.checkExact("Section export");
check_export.checkExact("entries 3");
check_export.checkExact("name __indirect_function_table"); // as per linker specification
check_export.checkExact("kind table");
check_regular.checkInHeaders();
check_regular.checkExact("Section table");
check_regular.checkExact("entries 1");
check_regular.checkExact("type funcref");
check_regular.checkExact("min 2"); // index starts at 1 & 1 function pointer = 2.
check_regular.checkExact("max 2");
check_regular.checkInHeaders();
check_regular.checkExact("Section element");
check_regular.checkExact("entries 1");
check_regular.checkExact("table index 0");
check_regular.checkExact("i32.const 1"); // we want to start function indexes at 1
check_regular.checkExact("indexes 1"); // 1 function pointer
test_step.dependOn(&check_export.step);
test_step.dependOn(&check_regular.step);
}
-7
View File
@@ -1,7 +0,0 @@
var func: *const fn () void = &bar;
export fn foo() void {
func();
}
fn bar() void {}
-44
View File
@@ -1,44 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
// Wasm Object file which we will use to infer the features from
const c_obj = b.addObject(.{
.name = "c_obj",
.root_module = b.createModule(.{
.root_source_file = null,
.optimize = .Debug,
.target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.cpu_model = .{ .explicit = &std.Target.wasm.cpu.bleeding_edge },
.os_tag = .freestanding,
}),
}),
});
c_obj.root_module.addCSourceFile(.{ .file = b.path("foo.c"), .flags = &.{} });
// Wasm library that doesn't have any features specified. This will
// infer its featureset from other linked object files.
const lib = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = .Debug,
.target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
.os_tag = .freestanding,
}),
}),
});
lib.entry = .disabled;
lib.use_llvm = false;
lib.use_lld = false;
lib.root_module.addObject(c_obj);
lib.expect_errors = .{ .contains = "error: object requires atomics but specified target features exclude atomics" };
_ = lib.getEmittedBin();
const test_step = b.step("test", "Run linker test");
test_step.dependOn(&lib.step);
b.default_step = test_step;
}
-3
View File
@@ -1,3 +0,0 @@
int foo() {
return 5;
}
-1
View File
@@ -1 +0,0 @@
extern fn foo() c_int;
-45
View File
@@ -1,45 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const lib = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.optimize = optimize,
.strip = false,
}),
});
lib.entry = .disabled;
lib.use_llvm = false;
lib.use_lld = false;
b.installArtifact(lib);
const version_fmt = "version " ++ builtin.zig_version_string;
const check_lib = lib.checkObject();
check_lib.checkInHeaders();
check_lib.checkExact("name producers");
check_lib.checkExact("fields 2");
check_lib.checkExact("field_name language");
check_lib.checkExact("values 1");
check_lib.checkExact("value_name Zig");
check_lib.checkExact(version_fmt);
check_lib.checkExact("field_name processed-by");
check_lib.checkExact("values 1");
check_lib.checkExact("value_name Zig");
check_lib.checkExact(version_fmt);
test_step.dependOn(&check_lib.step);
}
-1
View File
@@ -1 +0,0 @@
export fn foo() void {}
-99
View File
@@ -1,99 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.OptimizeMode) void {
const exe = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
.cpu_features_add = std.Target.wasm.featureSet(&.{ .atomics, .bulk_memory }),
.os_tag = .freestanding,
}),
.optimize = optimize_mode,
.strip = false,
.single_threaded = false,
}),
});
exe.entry = .disabled;
exe.use_lld = false;
exe.import_memory = true;
exe.export_memory = true;
exe.shared_memory = true;
exe.max_memory = 67108864;
exe.root_module.export_symbol_names = &.{"foo"};
// Don't pull in ubsan, since we're just expecting a very minimal executable.
exe.bundle_ubsan_rt = false;
const check_exe = exe.checkObject();
check_exe.checkInHeaders();
check_exe.checkExact("Section import");
check_exe.checkExact("entries 1");
check_exe.checkExact("module env");
check_exe.checkExact("name memory"); // ensure we are importing memory
check_exe.checkInHeaders();
check_exe.checkExact("Section export");
check_exe.checkExact("entries 2");
check_exe.checkExact("name foo");
check_exe.checkExact("name memory"); // ensure we also export memory again
// This section *must* be emit as the start function is set to the index
// of __wasm_init_memory
// release modes will have the TLS segment optimized out in our test-case.
// This means we won't have __wasm_init_memory in such case, and therefore
// should also not have a section "start"
if (optimize_mode == .Debug) {
check_exe.checkInHeaders();
check_exe.checkExact("Section start");
}
// This section is only and *must* be emit when shared-memory is enabled
// release modes will have the TLS segment optimized out in our test-case.
if (optimize_mode == .Debug) {
check_exe.checkInHeaders();
check_exe.checkExact("Section data_count");
check_exe.checkExact("count 1");
}
check_exe.checkInHeaders();
check_exe.checkExact("Section custom");
check_exe.checkExact("name name");
check_exe.checkExact("type function");
if (optimize_mode == .Debug) {
check_exe.checkExact("name __wasm_init_memory");
check_exe.checkExact("name __wasm_init_tls");
}
check_exe.checkExact("type global");
// In debug mode the symbol __tls_base is resolved to an undefined symbol
// from the object file, hence its placement differs than in release modes
// where the entire tls segment is optimized away, and tls_base will have
// its original position.
if (optimize_mode == .Debug) {
check_exe.checkExact("name __tls_base");
check_exe.checkExact("name __tls_size");
check_exe.checkExact("name __tls_align");
check_exe.checkExact("type data_segment");
check_exe.checkExact("names 1");
check_exe.checkExact("index 0");
check_exe.checkExact("name .tdata");
} else {
check_exe.checkNotPresent("name __tls_base");
check_exe.checkNotPresent("name __tls_size");
check_exe.checkNotPresent("name __tls_align");
}
test_step.dependOn(&check_exe.step);
}
-5
View File
@@ -1,5 +0,0 @@
threadlocal var some_tls_global: u32 = 1;
export fn foo() void {
some_tls_global = 2;
}
-55
View File
@@ -1,55 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const lib = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.optimize = optimize,
.strip = false,
}),
});
lib.entry = .disabled;
lib.use_llvm = false;
lib.use_lld = false;
lib.stack_size = std.wasm.page_size * 2; // set an explicit stack size
lib.link_gc_sections = false;
b.installArtifact(lib);
const check_lib = lib.checkObject();
// ensure global exists and its initial value is equal to explitic stack size
check_lib.checkInHeaders();
check_lib.checkExact("Section global");
check_lib.checkExact("entries 1");
check_lib.checkExact("type i32"); // on wasm32 the stack pointer must be i32
check_lib.checkExact("mutable true"); // must be able to mutate the stack pointer
check_lib.checkExtract("i32.const {stack_pointer}");
check_lib.checkComputeCompare("stack_pointer", .{ .op = .eq, .value = .{ .literal = lib.stack_size.? } });
// validate memory section starts after virtual stack
check_lib.checkInHeaders();
check_lib.checkExact("Section data");
check_lib.checkExtract("i32.const {data_start}");
check_lib.checkComputeCompare("data_start", .{ .op = .eq, .value = .{ .variable = "stack_pointer" } });
// validate the name of the stack pointer
check_lib.checkInHeaders();
check_lib.checkExact("Section custom");
check_lib.checkExact("type global");
check_lib.checkExact("names 1");
check_lib.checkExact("index 0");
check_lib.checkExact("name __stack_pointer");
test_step.dependOn(&check_lib.step);
}
-1
View File
@@ -1 +0,0 @@
export fn foo() void {}
-43
View File
@@ -1,43 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const exe = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.optimize = optimize,
.strip = false,
}),
});
exe.entry = .disabled;
exe.use_llvm = false;
exe.use_lld = false;
exe.root_module.export_symbol_names = &.{"foo"};
// Don't pull in ubsan, since we're just expecting a very minimal executable.
exe.bundle_ubsan_rt = false;
b.installArtifact(exe);
const check_exe = exe.checkObject();
check_exe.checkInHeaders();
check_exe.checkExact("Section type");
// only 2 entries, although we have more functions.
// This is to test functions with the same function signature
// have their types deduplicated.
check_exe.checkExact("entries 2");
check_exe.checkExact("params 1");
check_exe.checkExact("type i32");
check_exe.checkExact("returns 1");
check_exe.checkExact("type i64");
check_exe.checkExact("params 0");
check_exe.checkExact("returns 0");
test_step.dependOn(&check_exe.step);
}
-10
View File
@@ -1,10 +0,0 @@
export fn foo(x: u32) u64 {
return bar(x);
}
fn bar(x: u32) u64 {
y();
return x;
}
fn y() void {}
@@ -25,10 +25,4 @@ pub fn build(b: *std.Build) void {
});
exe.link_gc_sections = false;
exe.bundle_compiler_rt = true;
// Verify compiler_rt hasn't pulled in any debug handlers
const check_exe = exe.checkObject();
check_exe.checkInSymtab();
check_exe.checkNotPresent("debug.readElfDebugInfo");
test_step.dependOn(&check_exe.step);
}
-121
View File
@@ -88,69 +88,6 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&run_cmd.step);
}
}
const check = exe.checkObject();
// __errno_location is always a dynamically linked symbol
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location");
// before v2.32 fstat redirects through __fxstat, afterwards its a
// normal dynamic symbol
check.checkInDynamicSymtab();
if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstat");
check.checkInSymtab();
check.checkContains("FUNC LOCAL HIDDEN fstat");
} else {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstat");
check.checkInSymtab();
check.checkNotPresent("__fxstat");
}
// before v2.26 reallocarray is not supported
check.checkInDynamicSymtab();
if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
check.checkNotPresent("reallocarray");
} else {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
}
// before v2.38 strlcpy is not supported
check.checkInDynamicSymtab();
if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
check.checkNotPresent("strlcpy");
} else {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy");
}
// v2.16 introduced getauxval()
check.checkInDynamicSymtab();
if (glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) {
check.checkNotPresent("getauxval");
} else {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
}
// Always have dynamic "exit", "pow", and "powf" references
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT pow");
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT powf");
if (target.result.cpu.arch != .s390x) {
// An atexit local symbol is defined, and depends on undefined dynamic
// __cxa_atexit.
check.checkInSymtab();
check.checkContains("FUNC LOCAL HIDDEN atexit");
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit");
}
test_step.dependOn(&check.step);
}
// Build & run a Zig test case against a sampling of supported glibc versions
@@ -236,63 +173,5 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&run_cmd.step);
}
}
const check = exe.checkObject();
// __errno_location is always a dynamically linked symbol
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location");
// before v2.32 fstatat redirects through __fxstatat, afterwards its a
// normal dynamic symbol
if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstatat");
check.checkInSymtab();
check.checkContains("FUNC LOCAL HIDDEN fstatat");
} else {
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstatat");
check.checkInSymtab();
check.checkNotPresent("FUNC LOCAL HIDDEN fstatat");
}
// before v2.26 reallocarray is not supported
if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
check.checkInDynamicSymtab();
check.checkNotPresent("reallocarray");
} else {
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
}
// before v2.38 strlcpy is not supported
if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
check.checkInDynamicSymtab();
check.checkNotPresent("strlcpy");
} else {
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy");
}
// v2.16 introduced getauxval(), so always present
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
// Always have a dynamic "exit" reference
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
if (target.result.cpu.arch != .s390x) {
// An atexit local symbol is defined, and depends on undefined dynamic
// __cxa_atexit.
check.checkInSymtab();
check.checkContains("FUNC LOCAL HIDDEN atexit");
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit");
}
test_step.dependOn(&check.step);
}
}
-6
View File
@@ -37,10 +37,4 @@ pub fn build(b: *std.Build) void {
exe.root_module.addCSourceFile(.{ .file = b.path("main.m"), .flags = &.{} });
exe.root_module.linkFramework("Foundation", .{});
exe.root_module.linkFramework("UIKit", .{});
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd BUILD_VERSION");
check.checkExact("platform IOS");
test_step.dependOn(&check.step);
}
-21
View File
@@ -2204,27 +2204,6 @@ pub fn addStandaloneTests(
return step;
}
pub fn addLinkTests(
b: *std.Build,
enable_macos_sdk: bool,
enable_ios_sdk: bool,
enable_symlinks_windows: bool,
) *Step {
const step = b.step("test-link", "Run the linker tests");
if (compilerHasPackageManager(b)) {
const test_cases_dep_name = "link_test_cases";
const test_cases_dep = b.dependency(test_cases_dep_name, .{
.enable_ios_sdk = enable_ios_sdk,
.enable_macos_sdk = enable_macos_sdk,
.enable_symlinks_windows = enable_symlinks_windows,
});
const test_cases_dep_step = test_cases_dep.builder.default_step;
test_cases_dep_step.name = b.dupe(test_cases_dep_name);
step.dependOn(test_cases_dep.builder.default_step);
}
return step;
}
pub fn addCliTests(b: *std.Build) *Step {
const step = b.step("test-cli", "Test the command line interface");
const s = std.fs.path.sep_str;