Skip to content

Commit 7273e82

Browse files
committed
Merge remote-tracking branch 'origin/main' into omit-inline-debug-effects
2 parents a4238fb + 2de0983 commit 7273e82

62 files changed

Lines changed: 2079 additions & 26730 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci_zig.yml

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,18 @@ jobs:
118118
roc check CONTRIBUTING/profiling/exec_bench.roc
119119
roc test CONTRIBUTING/profiling/exec_bench.roc
120120
121+
# All LLVM-backend end-to-end coverage lives here, on every OS: eval parity
122+
# tests, the platforms suite built with roc build --opt=size/speed (linked by
123+
# the embedded lld and executed, with the fx host's leak detection), and the
124+
# dylib/archive build-and-run fixtures. The strJoinWithC &strDecref leak was
125+
# an lld-COFF misresolution that only this pipeline can catch.
121126
eval-llvm:
122127
needs: check-once
123-
runs-on: ubuntu-24.04
128+
runs-on: ${{ matrix.os }}
129+
strategy:
130+
fail-fast: false
131+
matrix:
132+
os: [ubuntu-24.04, macos-15, windows-2025]
124133
steps:
125134
- name: Checkout
126135
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6.0.2
@@ -129,10 +138,51 @@ jobs:
129138
version: 0.16.0
130139
use-cache: true
131140

141+
# The embedded lld-link resolves /defaultlib via the LIB env and roc's
142+
# linker locates the Windows SDK/MSVC libpaths at runtime.
143+
- name: Setup MSVC (Windows)
144+
if: runner.os == 'Windows'
145+
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # ratchet:ilammy/msvc-dev-cmd@v1.13.0
146+
with:
147+
arch: x64
148+
132149
- name: Run eval tests with LLVM backend
150+
if: runner.os != 'Windows'
133151
run: |
134152
zig build run-test-eval -- --llvm
135153
154+
# Windows has no fork(), so the runner spawns a fresh worker process per
155+
# test that compiles the whole program (front-end + LIR lowering) before
156+
# running any backend. The TRMC/TCE "stack gate" stress tests have a
157+
# multi-second lowering cost (measured ~9-17s locally; backend eval itself
158+
# is sub-millisecond) that, on the slower hosted Windows runner under
159+
# full-suite concurrency, overruns the 30s default per-test timeout and is
160+
# killed as a HANG. POSIX uses fork-based per-backend isolation on faster
161+
# runners where 30s is plenty. Give Windows a larger hang budget.
162+
- name: Run eval tests with LLVM backend (Windows)
163+
if: runner.os == 'Windows'
164+
run: |
165+
zig build run-test-eval -- --llvm --timeout 120000
166+
167+
- name: Run CLI platform tests (LLVM size/speed backends)
168+
if: runner.os != 'Windows'
169+
uses: ./.github/actions/flaky-retry
170+
with:
171+
command: zig build run-test-cli -- --include-llvm --filter "[size]" --filter "[speed]"
172+
error_string_contains: "build.zig.zon"
173+
retry_count: 3
174+
175+
- name: Run CLI platform tests (LLVM size/speed backends, Windows)
176+
if: runner.os == 'Windows'
177+
run: |
178+
zig build run-test-cli -- --include-llvm --filter "[size]" --filter "[speed]"
179+
180+
- name: Shared-library build-and-run test (roc build --opt=size)
181+
run: zig build run-test-dylib
182+
183+
- name: Static-archive build-and-run test
184+
run: zig build run-test-archive
185+
136186
zig-tests:
137187
needs: check-once
138188
runs-on: ${{ matrix.os }}
@@ -144,32 +194,26 @@ jobs:
144194
cpu_flag: -Dcpu=x86_64_v3
145195
target_flag: ''
146196
release_jobs_flag: ''
147-
cli_test_llvm_flag: ''
148197
- os: macos-15
149198
cpu_flag: ''
150199
target_flag: ''
151200
release_jobs_flag: ''
152-
cli_test_llvm_flag: -Dcli-test-llvm=true
153201
- os: ubuntu-24.04
154202
cpu_flag: ""
155203
target_flag: -Dtarget=x86_64-linux-musl
156204
release_jobs_flag: ''
157-
cli_test_llvm_flag: ''
158205
- os: ubuntu-24.04-arm
159206
cpu_flag: ''
160207
target_flag: '' # Native build for kcov (Zig 0.15.2 x86_64 has DWARF bug) # TODO ZIG 16: re-check if DWARF bug is fixed in 0.16
161208
release_jobs_flag: -j4 # Avoid hosted ARM runner disconnects under full ReleaseFast parallelism.
162-
cli_test_llvm_flag: ''
163209
- os: windows-2022
164210
cpu_flag: -Dcpu=x86_64_v3
165211
target_flag: ''
166212
release_jobs_flag: ''
167-
cli_test_llvm_flag: ''
168213
- os: windows-2025
169214
cpu_flag: -Dcpu=x86_64_v3
170215
target_flag: ''
171216
release_jobs_flag: ''
172-
cli_test_llvm_flag: ''
173217

174218
steps:
175219
- name: Checkout
@@ -195,7 +239,7 @@ jobs:
195239
if: runner.os != 'Windows'
196240
uses: ./.github/actions/flaky-retry
197241
with:
198-
command: "zig build run-test-cli ${{ matrix.target_flag }} ${{ matrix.cli_test_llvm_flag }}"
242+
command: "zig build run-test-cli ${{ matrix.target_flag }}"
199243
error_string_contains: "unhandled file type|build executable runs correctly|undefined symbol"
200244
retry_count: 3
201245

build.zig

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ fn mustUseLlvm(target: ResolvedTarget) bool {
4747
}
4848

4949
fn testHostNeedsCompilerRt(target: ResolvedTarget) bool {
50-
return mustUseLlvm(target) or
50+
return target.result.os.tag == .linux or
51+
mustUseLlvm(target) or
5152
(target.result.os.tag == .windows and target.result.cpu.arch == .aarch64);
5253
}
5354

@@ -1658,6 +1659,13 @@ fn createTestPlatformHostLib(
16581659
}),
16591660
});
16601661
configureBackend(lib, target);
1662+
if (target.result.os.tag == .linux) {
1663+
// The symbol-ABI platform tests depend on the visibility declared in
1664+
// @export options: default-visibility host functions are public shared
1665+
// library exports, while hidden runtime and hosted symbols are internal
1666+
// link inputs. Zig's LLVM backend emits that ELF visibility metadata.
1667+
lib.use_llvm = true;
1668+
}
16611669
if (options.uses_stack_handler) {
16621670
lib.root_module.addImport("base", roc_modules.base);
16631671
}
@@ -1666,10 +1674,10 @@ fn createTestPlatformHostLib(
16661674
lib.root_module.addImport("shim_io", b.addModule("shim_io", .{
16671675
.root_source_file = b.path("src/shim_io.zig"),
16681676
}));
1669-
// Bundle compiler_rt when the generated host object may call compiler_rt
1670-
// routines that are not supplied by the OS libraries. ARM64 Windows Zig code
1671-
// can emit stack-protector calls to __stack_chk_fail; x86_64 macOS (LLVM)
1672-
// needs symbols like __zig_probe_stack.
1677+
// Bundle compiler_rt when generated host object code may call compiler_rt
1678+
// routines that are not supplied by the OS libraries. Linux and x86_64
1679+
// macOS LLVM builds can emit symbols like __zig_probe_stack; ARM64 Windows
1680+
// Zig code can emit stack-protector calls to __stack_chk_fail.
16731681
lib.bundle_compiler_rt = testHostNeedsCompilerRt(target);
16741682
// Per-function/data sections so symbol-ABI links can strip unused host code.
16751683
lib.link_function_sections = true;
@@ -2013,8 +2021,10 @@ fn setupTestPlatforms(
20132021
clear_cache_step.dependOn(copy_step);
20142022
}
20152023

2016-
// Cross-compile for musl targets (glibc is not needed for native CLI platform tests)
2017-
for (musl_cross_targets) |cross_target| {
2024+
// Cross-compile for all Linux targets. `roc build` selects the host
2025+
// platform's native target by default, which is commonly x64glibc even
2026+
// when this Zig build compiles the `roc` binary as native-musl.
2027+
for (linux_cross_targets) |cross_target| {
20182028
const cross_resolved_target = b.resolveTargetQuery(cross_target.query);
20192029

20202030
for (all_test_platform_dirs) |platform_dir| {
@@ -3475,9 +3485,13 @@ pub fn build(b: *std.Build) void {
34753485
run_dylib_test.step.dependOn(&build_dylib_app.step);
34763486
run_test_dylib_step.dependOn(&run_dylib_test.step);
34773487

3478-
// Unused host code (the canary function and its constant) must be
3479-
// dead-code-eliminated from the linked library, while the used hosted
3480-
// function survives.
3488+
// Dead host code must be dead-code-eliminated while live host code
3489+
// survives, checked by byte-scanning for marker data blobs (data works
3490+
// on every object format, unlike symbol names — PE retains no internal
3491+
// symbol names): the dead-hosted and dead-only-helper blobs must be
3492+
// absent, and the blob shared with the live Host.double! path must be
3493+
// present. (That roc_run_app/roc_main are exported and the hidden
3494+
// roc_host_double is not is checked separately by the loader.)
34813495
const dylib_dce_check_exe = b.addExecutable(.{
34823496
.name = "dylib_dce_check",
34833497
.root_module = b.createModule(.{
@@ -3495,7 +3509,6 @@ pub fn build(b: *std.Build) void {
34953509
"--absent",
34963510
"ROC_DCE_DEAD_HELPER_BLOB_28d0aa",
34973511
"ROC_DCE_SHARED_BLOB_93e2c1",
3498-
"roc_host_double",
34993512
});
35003513
run_dylib_dce_check.step.dependOn(&build_dylib_app.step);
35013514
run_test_dylib_step.dependOn(&run_dylib_dce_check.step);
@@ -3526,6 +3539,10 @@ pub fn build(b: *std.Build) void {
35263539
.target = output_target.resolved,
35273540
.optimize = optimize,
35283541
.link_libc = true,
3542+
// A COFF /DEBUG link disables lld-link's /OPT:REF default, so
3543+
// an unstripped Windows Debug build would keep the DCE canary
3544+
// sections this test asserts are eliminated.
3545+
.strip = true,
35293546
}),
35303547
});
35313548
configureBackend(archive_consumer_exe, output_target.resolved);
@@ -5484,7 +5501,10 @@ fn getCompilerArtifactHash(b: *std.Build, compiler_version: []const u8) [32]u8 {
54845501
hasher.update("roc-checked-artifact-v1");
54855502
hasher.update(compiler_version);
54865503

5487-
const builtin_source = std.Io.Dir.cwd().readFileAlloc(
5504+
// Resolve against the build root rather than cwd so the hash works both for
5505+
// standalone builds and when roc is consumed as a dependency (cwd is then the
5506+
// consumer's directory, not roc's).
5507+
const builtin_source = b.build_root.handle.readFileAlloc(
54885508
b.graph.io,
54895509
"src/build/roc/Builtin.roc",
54905510
b.allocator,

build.zig.zon

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949
.lazy = true,
5050
},
5151
.bytebox = .{
52-
.path = "vendor/bytebox",
52+
.url = "git+https://github.com/lukewilliamboswell/bytebox.git?ref=zig-0.16.0#88a4b79230eece88030e6779f69ed9683906c02a",
53+
.hash = "bytebox-0.0.1-SXc2saRyDwDEw8AkDhp4Hfmd9ZhRmMnIzS7FfzDCmc8r",
5354
},
5455
.zstd = .{
5556
.url = "git+https://github.com/allyourcodebase/zstd?ref=1.5.7-1#e1a501be57f42c541e8a5597e4b59a074dfd09a3", // 1.5.7-1

build.zig.zon.nix

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ linkFarm name (map unpackZig [
132132
hash = "sha256-8jPIAh01og2FZ8DUAor0hHZ3EDWWIhbXa0X1+CvQG0s=";
133133
};
134134
}
135+
{
136+
name = "bytebox-0.0.1-SXc2saRyDwDEw8AkDhp4Hfmd9ZhRmMnIzS7FfzDCmc8r.tar.gz";
137+
path = fetchZig {
138+
name = "bytebox";
139+
url = "git+https://github.com/lukewilliamboswell/bytebox.git?ref=zig-0.16.0#88a4b79230eece88030e6779f69ed9683906c02a";
140+
hash = "sha256-Cbkw9Q7JFEQGRS12IjBw581fgth+HJIAo3Ppvd2XvuM=";
141+
};
142+
}
135143
{
136144
name = "stable_array-0.1.0-3ihgvd9eAAA5ozV4aOQZ6GI3d_gTyiR9tS6mwav2w18o.tar.gz";
137145
path = fetchZig {

src/backend/llvm/MonoLlvmCodeGen.zig

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2144,7 +2144,9 @@ pub const MonoLlvmCodeGen = struct {
21442144
.list_prepend => try self.emitListPrepend(target, arg_locals, unique_args),
21452145
.list_sublist, .list_drop_first, .list_drop_last, .list_take_first, .list_take_last => try self.emitListSublist(target, op, arg_locals, unique_args),
21462146
.list_drop_at => try self.emitListDropAt(target, arg_locals, unique_args),
2147+
.list_swap => try self.emitListSwap(target, arg_locals, unique_args),
21472148
.list_set => try self.emitListSet(target, arg_locals, unique_args),
2149+
.list_replace_unsafe => try self.emitListReplaceUnsafe(target, arg_locals, unique_args),
21482150
.list_map_can_reuse => try self.emitListMapCanReuse(target, arg_locals),
21492151
.list_map_cast_unsafe => try self.copyBytes(self.slot(target).ptr, self.slot(arg_locals[0]).ptr, self.slot(target).size, self.slot(target).alignment),
21502152
.list_map_extract_unsafe => try self.emitListMapExtractUnsafe(target, arg_locals),
@@ -3274,6 +3276,22 @@ pub const MonoLlvmCodeGen = struct {
32743276
try self.callBuiltinVoid("roc_builtins_list_drop_at", call_args.types.items, call_args.values.items);
32753277
}
32763278

3279+
fn emitListSwap(self: *MonoLlvmCodeGen, target: LocalId, args: []const LocalId, unique_args: u64) Error!void {
3280+
const builder = self.builder orelse return error.CompilationFailed;
3281+
const abi = self.layouts().builtinListAbi(self.localLayout(args[0]));
3282+
var call_args = try self.rocListArgs1(args[0]);
3283+
defer call_args.deinit(self.allocator);
3284+
try call_args.prepend(self.allocator, try self.ptrType(), self.slot(target).ptr);
3285+
try call_args.append(self.allocator, .i32, builder.intValue(.i32, abi.elem_alignment) catch return error.OutOfMemory);
3286+
try call_args.append(self.allocator, self.ptrSizedIntType(), builder.intValue(self.ptrSizedIntType(), abi.elem_size) catch return error.OutOfMemory);
3287+
try call_args.append(self.allocator, .i64, try self.coerceScalar(try self.loadScalar(self.slot(args[1]).ptr, self.localLayout(args[1])), .i64, false));
3288+
try call_args.append(self.allocator, .i64, try self.coerceScalar(try self.loadScalar(self.slot(args[2]).ptr, self.localLayout(args[2])), .i64, false));
3289+
try self.appendListElementRcArgs(&call_args, abi, true, true);
3290+
try self.appendUpdateModeArg(&call_args, unique_args);
3291+
try call_args.append(self.allocator, try self.ptrType(), self.rocOps());
3292+
try self.callBuiltinVoid("roc_builtins_list_swap", call_args.types.items, call_args.values.items);
3293+
}
3294+
32773295
fn emitListSet(self: *MonoLlvmCodeGen, target: LocalId, args: []const LocalId, unique_args: u64) Error!void {
32783296
const builder = self.builder orelse return error.CompilationFailed;
32793297
const abi = self.layouts().builtinListAbi(self.localLayout(args[0]));
@@ -3291,6 +3309,44 @@ pub const MonoLlvmCodeGen = struct {
32913309
try self.callBuiltinVoid("roc_builtins_list_replace", call_args.types.items, call_args.values.items);
32923310
}
32933311

3312+
fn emitListReplaceUnsafe(self: *MonoLlvmCodeGen, target: LocalId, args: []const LocalId, unique_args: u64) Error!void {
3313+
const builder = self.builder orelse return error.CompilationFailed;
3314+
// The result is a { list, prev } record. Reuse roc_builtins_list_replace
3315+
// and aim its (out_list, out_element) outputs directly at the record's
3316+
// fields, disambiguated by layout tag like the dev backend does.
3317+
const record_layout_val = self.layoutValue(self.localLayout(target));
3318+
if (record_layout_val.tag != .struct_) return error.CompilationFailed;
3319+
const rec_idx = record_layout_val.getStruct().idx;
3320+
const f0_layout = self.layoutValue(self.layouts().getStructFieldLayoutByOriginalIndex(rec_idx, 0));
3321+
const f0_offset = self.layouts().getStructFieldOffsetByOriginalIndex(rec_idx, 0);
3322+
const f1_offset = self.layouts().getStructFieldOffsetByOriginalIndex(rec_idx, 1);
3323+
const f0_is_list = f0_layout.tag == .list or f0_layout.tag == .list_of_zst;
3324+
const list_out_ptr = try self.offsetPtr(self.slot(target).ptr, if (f0_is_list) f0_offset else f1_offset);
3325+
const value_out_ptr = try self.offsetPtr(self.slot(target).ptr, if (f0_is_list) f1_offset else f0_offset);
3326+
3327+
const abi = self.layouts().builtinListAbi(self.localLayout(args[0]));
3328+
if (abi.elem_size == 0) {
3329+
// listReplace would dereference a NULL element pointer for ZST
3330+
// elements; the result list is the input unchanged and the prev
3331+
// field is zero-sized.
3332+
try self.copyBytes(list_out_ptr, self.slot(args[0]).ptr, self.slot(args[0]).size, self.slot(args[0]).alignment);
3333+
return;
3334+
}
3335+
3336+
var call_args = try self.rocListArgs1(args[0]);
3337+
defer call_args.deinit(self.allocator);
3338+
try call_args.prepend(self.allocator, try self.ptrType(), list_out_ptr);
3339+
try call_args.append(self.allocator, .i32, builder.intValue(.i32, abi.elem_alignment) catch return error.OutOfMemory);
3340+
try call_args.append(self.allocator, .i64, try self.coerceScalar(try self.loadScalar(self.slot(args[1]).ptr, self.localLayout(args[1])), .i64, false));
3341+
try call_args.append(self.allocator, try self.ptrType(), self.slot(args[2]).ptr);
3342+
try call_args.append(self.allocator, self.ptrSizedIntType(), builder.intValue(self.ptrSizedIntType(), abi.elem_size) catch return error.OutOfMemory);
3343+
try call_args.append(self.allocator, try self.ptrType(), value_out_ptr);
3344+
try self.appendListElementRcArgs(&call_args, abi, true, true);
3345+
try self.appendUpdateModeArg(&call_args, unique_args);
3346+
try call_args.append(self.allocator, try self.ptrType(), self.rocOps());
3347+
try self.callBuiltinVoid("roc_builtins_list_replace", call_args.types.items, call_args.values.items);
3348+
}
3349+
32943350
fn emitListReserve(self: *MonoLlvmCodeGen, target: LocalId, args: []const LocalId, unique_args: u64) Error!void {
32953351
const builder = self.builder orelse return error.CompilationFailed;
32963352
const abi = self.layouts().builtinListAbi(self.localLayout(args[0]));

src/build/embedded_lld.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ const std = @import("std");
44
const builtin = @import("builtin");
55
const collections = @import("collections");
66

7+
/// COFF stack-probe (___chkstk_ms) object generation, shared by every
8+
/// embedded-lld COFF link of Roc-generated code.
9+
pub const stack_probe = @import("stack_probe.zig");
10+
711
/// Object format handled by one of the embedded LLD frontends.
812
pub const Format = enum {
913
elf,

0 commit comments

Comments
 (0)