Skip to content

Commit b9de635

Browse files
authored
Merge pull request #7706 from roc-lang/tracy
Add support for using Tracy with Roc
2 parents 3db3935 + 7c463d0 commit b9de635

13 files changed

+630
-55
lines changed

Diff for: .github/workflows/ci_zig.yml

+12-3
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,20 @@ jobs:
1919
2020
zig build check-fmt
2121
22+
# We run zig check with tracy enabled.
23+
# This ensure it compiles and doesn't go stale.
24+
- name: Checkout Tracy
25+
uses: actions/checkout@v4
26+
with:
27+
path: tracy
28+
repository: wolfpld/tracy
29+
ref: v0.11.0
30+
2231
- name: zig check
2332
run: |
2433
# -Dllvm incurs a costly download step, leave that for later.
2534
# Just the do super fast check step for now.
26-
zig build -Dno-bin -Dfuzz
35+
zig build -Dno-bin -Dfuzz -Dtracy=./tracy
2736
2837
zig-tests:
2938
needs: check-zig
@@ -41,7 +50,7 @@ jobs:
4150

4251
- name: build roc
4352
run: |
44-
zig build -Dllvm -Dfuzz
53+
zig build -Dllvm -Dfuzz -Dsystem-afl=false
4554
4655
- name: roc executable minimal check (Unix)
4756
if: runner.os != 'Windows'
@@ -55,7 +64,7 @@ jobs:
5564
5665
- name: zig tests
5766
run: |
58-
zig build test -Dllvm -Dfuzz
67+
zig build test -Dllvm -Dfuzz -Dsystem-afl=false
5968
6069
- name: zig snapshot tests
6170
run: zig build snapshot

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,4 @@ src/fuzz-corpus/
133133
perf.data
134134
perf.data.old
135135
profile.json
136+
flamegraph.svg

Diff for: build.zig

+119-51
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,57 @@ const ResolvedTarget = std.Build.ResolvedTarget;
99
const Step = std.Build.Step;
1010

1111
pub fn build(b: *std.Build) void {
12-
const target = b.standardTargetOptions(.{ .default_target = .{
13-
.abi = if (builtin.target.os.tag == .linux) .musl else null,
14-
} });
15-
const optimize = b.standardOptimizeOption(.{});
16-
const strip = b.option(bool, "strip", "Omit debug information");
17-
12+
// build steps
1813
const run_step = b.step("run", "Build and run the roc cli");
14+
const roc_step = b.step("roc", "Build the roc compiler without running it");
1915
const test_step = b.step("test", "Run all tests included in src/tests.zig");
2016
const fmt_step = b.step("fmt", "Format all zig code");
2117
const check_fmt_step = b.step("check-fmt", "Check formatting of all zig code");
2218
const snapshot_step = b.step("snapshot", "Run the snapshot tool to update snapshot files");
2319

24-
// llvm configuration
20+
// general configuration
21+
const target = b.standardTargetOptions(.{ .default_target = .{
22+
.abi = if (builtin.target.os.tag == .linux) .musl else null,
23+
} });
24+
const optimize = b.standardOptimizeOption(.{});
25+
const strip = b.option(bool, "strip", "Omit debug information");
2526
const no_bin = b.option(bool, "no-bin", "Skip emitting binaries (important for fast incremental compilation)") orelse false;
27+
28+
// llvm configuration
2629
const use_system_llvm = b.option(bool, "system-llvm", "Attempt to automatically detect and use system installed llvm") orelse false;
2730
const enable_llvm = b.option(bool, "llvm", "Build roc with the llvm backend") orelse use_system_llvm;
2831
const user_llvm_path = b.option([]const u8, "llvm-path", "Path to llvm. This path must contain the bin, lib, and include directory.");
29-
const use_system_afl = b.option(bool, "system-afl", "Attempt to automatically detect and use system installed afl++") orelse false;
32+
// Since zig afl is broken currently, default to system afl.
33+
const use_system_afl = b.option(bool, "system-afl", "Attempt to automatically detect and use system installed afl++") orelse true;
3034

3135
if (user_llvm_path) |path| {
3236
// Even if the llvm backend is not enabled, still add the llvm path.
3337
// AFL++ may use it for building fuzzing executables.
3438
b.addSearchPrefix(b.pathJoin(&.{ path, "bin" }));
3539
}
3640

37-
const roc_exe = addMainExe(b, target, optimize, strip, enable_llvm, use_system_llvm, user_llvm_path) orelse return;
38-
39-
if (no_bin) {
40-
b.getInstallStep().dependOn(&roc_exe.step);
41-
} else {
42-
b.installArtifact(roc_exe);
43-
const run_cmd = b.addRunArtifact(roc_exe);
44-
run_cmd.step.dependOn(b.getInstallStep());
45-
if (b.args) |args| {
46-
run_cmd.addArgs(args);
47-
}
48-
run_step.dependOn(&run_cmd.step);
41+
// tracy profiler configuration
42+
const tracy = b.option([]const u8, "tracy", "Enable Tracy integration. Supply path to Tracy source");
43+
const tracy_callstack = b.option(bool, "tracy-callstack", "Include callstack information with Tracy data. Does nothing if -Dtracy is not provided") orelse (tracy != null);
44+
const tracy_allocation = b.option(bool, "tracy-allocation", "Include allocation information with Tracy data. Does nothing if -Dtracy is not provided") orelse (tracy != null);
45+
const tracy_callstack_depth: u32 = b.option(u32, "tracy-callstack-depth", "Declare callstack depth for Tracy data. Does nothing if -Dtracy_callstack is not provided") orelse 10;
46+
if (tracy != null and target.result.os.tag == .macos) {
47+
std.log.warn("Tracy has significantly more overhead on MacOS. Be cautious when generating timing and analyzing results.", .{});
4948
}
49+
if (tracy_callstack) {
50+
std.log.warn("Tracy callstack is enable. This can significantly skew timings, but is important for understanding source location. Be cautious when generating timing and analyzing results.", .{});
51+
}
52+
53+
// Create compile time build options
54+
const build_options = b.addOptions();
55+
build_options.addOption(bool, "enable_tracy", tracy != null);
56+
build_options.addOption(bool, "enable_tracy_callstack", tracy_callstack);
57+
build_options.addOption(bool, "enable_tracy_allocation", tracy_allocation);
58+
build_options.addOption(u32, "tracy_callstack_depth", tracy_callstack_depth);
59+
60+
// add main roc exe
61+
const roc_exe = addMainExe(b, build_options, target, optimize, strip, enable_llvm, use_system_llvm, user_llvm_path, tracy) orelse return;
62+
install_and_run(b, no_bin, roc_exe, roc_step, run_step);
5063

5164
// Add snapshot tool
5265
const snapshot_exe = b.addExecutable(.{
@@ -56,26 +69,16 @@ pub fn build(b: *std.Build) void {
5669
.optimize = optimize,
5770
.link_libc = true,
5871
});
59-
if (no_bin) {
60-
b.getInstallStep().dependOn(&snapshot_exe.step);
61-
} else {
62-
b.installArtifact(snapshot_exe);
63-
const run_snapshot = b.addRunArtifact(snapshot_exe);
64-
65-
// Add a step to run the snapshot tool
66-
run_snapshot.step.dependOn(b.getInstallStep());
67-
if (b.args) |args| {
68-
run_snapshot.addArgs(args);
69-
}
70-
snapshot_step.dependOn(&run_snapshot.step);
71-
}
72+
add_tracy(b, build_options, snapshot_exe, target, false, tracy);
73+
install_and_run(b, no_bin, snapshot_exe, snapshot_step, snapshot_step);
7274

7375
const all_tests = b.addTest(.{
7476
.root_source_file = b.path("src/test.zig"),
7577
.target = target,
7678
.optimize = optimize,
7779
.link_libc = true,
7880
});
81+
all_tests.root_module.addOptions("build_options", build_options);
7982

8083
if (!no_bin) {
8184
const run_tests = b.addRunArtifact(all_tests);
@@ -126,6 +129,8 @@ pub fn build(b: *std.Build) void {
126129
no_bin,
127130
target,
128131
optimize,
132+
build_options,
133+
tracy,
129134
name,
130135
);
131136
}
@@ -139,6 +144,8 @@ fn add_fuzz_target(
139144
no_bin: bool,
140145
target: ResolvedTarget,
141146
optimize: OptimizeMode,
147+
build_options: *Step.Options,
148+
tracy: ?[]const u8,
142149
name: []const u8,
143150
) void {
144151
// We always include the repro scripts (no dependencies).
@@ -151,10 +158,11 @@ fn add_fuzz_target(
151158
// Work around instrumentation bugs on mac without giving up perf on linux.
152159
.optimize = if (target.result.os.tag == .macos) .Debug else .ReleaseSafe,
153160
});
161+
add_tracy(b, build_options, fuzz_obj, target, false, tracy);
154162

155163
const name_exe = b.fmt("fuzz-{s}", .{name});
156164
const name_repro = b.fmt("repro-{s}", .{name});
157-
const run_repro_step = b.step(name_repro, b.fmt("run fuzz reproduction for {s}", .{name}));
165+
const repro_step = b.step(name_repro, b.fmt("run fuzz reproduction for {s}", .{name}));
158166
const repro_exe = b.addExecutable(.{
159167
.name = name_repro,
160168
.root_source_file = b.path("src/fuzz-repro.zig"),
@@ -163,37 +171,30 @@ fn add_fuzz_target(
163171
.link_libc = true,
164172
});
165173
repro_exe.root_module.addImport("fuzz_test", fuzz_obj.root_module);
166-
if (no_bin) {
167-
b.getInstallStep().dependOn(&repro_exe.step);
168-
} else {
169-
b.installArtifact(repro_exe);
170-
171-
const run_cmd = b.addRunArtifact(repro_exe);
172-
run_cmd.step.dependOn(b.getInstallStep());
173-
if (b.args) |args| {
174-
run_cmd.addArgs(args);
175-
}
176-
run_repro_step.dependOn(&run_cmd.step);
177-
}
174+
install_and_run(b, no_bin, repro_exe, repro_step, repro_step);
178175

179176
if (fuzz and build_afl and !no_bin) {
180177
const fuzz_step = b.step(name_exe, b.fmt("Generate fuzz executable for {s}", .{name}));
181178
b.default_step.dependOn(fuzz_step);
182179

183180
const afl = b.lazyImport(@This(), "afl_kit") orelse return;
184181
const fuzz_exe = afl.addInstrumentedExe(b, target, .ReleaseSafe, &.{}, use_system_afl, fuzz_obj) orelse return;
185-
fuzz_step.dependOn(&b.addInstallBinFile(fuzz_exe, name_exe).step);
182+
const install_fuzz = b.addInstallBinFile(fuzz_exe, name_exe);
183+
fuzz_step.dependOn(&install_fuzz.step);
184+
b.getInstallStep().dependOn(&install_fuzz.step);
186185
}
187186
}
188187

189188
fn addMainExe(
190189
b: *std.Build,
190+
build_options: *Step.Options,
191191
target: ResolvedTarget,
192192
optimize: OptimizeMode,
193193
strip: ?bool,
194194
enable_llvm: bool,
195195
use_system_llvm: bool,
196196
user_llvm_path: ?[]const u8,
197+
tracy: ?[]const u8,
197198
) ?*Step.Compile {
198199
const exe = b.addExecutable(.{
199200
.name = "roc",
@@ -216,9 +217,72 @@ fn addMainExe(
216217
try addStaticLlvmOptionsToModule(exe.root_module);
217218
}
218219

220+
add_tracy(b, build_options, exe, target, enable_llvm, tracy);
219221
return exe;
220222
}
221223

224+
fn install_and_run(
225+
b: *std.Build,
226+
no_bin: bool,
227+
exe: *Step.Compile,
228+
build_step: *Step,
229+
run_step: *Step,
230+
) void {
231+
if (run_step != build_step) {
232+
run_step.dependOn(build_step);
233+
}
234+
if (no_bin) {
235+
// No build, just build, don't actually install or run.
236+
build_step.dependOn(&exe.step);
237+
b.getInstallStep().dependOn(&exe.step);
238+
} else {
239+
const install = b.addInstallArtifact(exe, .{});
240+
build_step.dependOn(&install.step);
241+
b.getInstallStep().dependOn(&install.step);
242+
243+
const run = b.addRunArtifact(exe);
244+
run.step.dependOn(&install.step);
245+
if (b.args) |args| {
246+
run.addArgs(args);
247+
}
248+
run_step.dependOn(&run.step);
249+
}
250+
}
251+
252+
fn add_tracy(
253+
b: *std.Build,
254+
build_options: *Step.Options,
255+
base: *Step.Compile,
256+
target: ResolvedTarget,
257+
links_llvm: bool,
258+
tracy: ?[]const u8,
259+
) void {
260+
base.root_module.addOptions("build_options", build_options);
261+
if (tracy) |tracy_path| {
262+
const client_cpp = b.pathJoin(
263+
&[_][]const u8{ tracy_path, "public", "TracyClient.cpp" },
264+
);
265+
266+
// On mingw, we need to opt into windows 7+ to get some features required by tracy.
267+
const tracy_c_flags: []const []const u8 = if (target.result.os.tag == .windows and target.result.abi == .gnu)
268+
&[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined", "-D_WIN32_WINNT=0x601" }
269+
else
270+
&[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined" };
271+
272+
base.root_module.addIncludePath(.{ .cwd_relative = tracy_path });
273+
base.root_module.addCSourceFile(.{ .file = .{ .cwd_relative = client_cpp }, .flags = tracy_c_flags });
274+
if (!links_llvm) {
275+
base.root_module.linkSystemLibrary("c++", .{ .use_pkg_config = .no });
276+
}
277+
base.root_module.link_libc = true;
278+
279+
if (target.result.os.tag == .windows) {
280+
base.root_module.linkSystemLibrary("dbghelp", .{});
281+
base.root_module.linkSystemLibrary("ws2_32", .{});
282+
}
283+
}
284+
}
285+
222286
const LlvmPaths = struct {
223287
include: []const u8,
224288
lib: []const u8,
@@ -305,16 +369,20 @@ fn addStaticLlvmOptionsToModule(mod: *std.Build.Module) !void {
305369
.flags = &cpp_cflags,
306370
});
307371

372+
const link_static = std.Build.Module.LinkSystemLibraryOptions{
373+
.preferred_link_mode = .static,
374+
.search_strategy = .mode_first,
375+
};
308376
for (lld_libs) |lib_name| {
309-
mod.linkSystemLibrary(lib_name, .{});
377+
mod.linkSystemLibrary(lib_name, link_static);
310378
}
311379

312380
for (llvm_libs) |lib_name| {
313-
mod.linkSystemLibrary(lib_name, .{});
381+
mod.linkSystemLibrary(lib_name, link_static);
314382
}
315383

316-
mod.linkSystemLibrary("z", .{});
317-
mod.linkSystemLibrary("zstd", .{});
384+
mod.linkSystemLibrary("z", link_static);
385+
mod.linkSystemLibrary("zstd", link_static);
318386

319387
if (mod.resolved_target.?.result.os.tag != .windows or mod.resolved_target.?.result.abi != .msvc) {
320388
// TODO: Can this just be `mod.link_libcpp = true`? Does that make a difference?

0 commit comments

Comments
 (0)