mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
add --z-dump-linker flag
This commit is contained in:
parent
3db5635c24
commit
457d979d8a
3 changed files with 190 additions and 10 deletions
|
|
@ -92,6 +92,7 @@ pub const BuildArgs = struct {
|
|||
wasm_stack_size: ?usize = null, // stack size for WASM targets (default: 8MB)
|
||||
z_bench_tokenize: ?[]const u8 = null, // benchmark tokenizer on a file or directory
|
||||
z_bench_parse: ?[]const u8 = null, // benchmark parser on a file or directory
|
||||
z_dump_linker: bool = false, // dump linker inputs to temp directory for debugging
|
||||
};
|
||||
|
||||
/// Arguments for `roc test`
|
||||
|
|
@ -271,6 +272,7 @@ fn parseBuild(args: []const []const u8) CliArgs {
|
|||
var wasm_stack_size: ?usize = null;
|
||||
var z_bench_tokenize: ?[]const u8 = null;
|
||||
var z_bench_parse: ?[]const u8 = null;
|
||||
var z_dump_linker: bool = false;
|
||||
for (args) |arg| {
|
||||
if (isHelpFlag(arg)) {
|
||||
return CliArgs{ .help =
|
||||
|
|
@ -291,6 +293,7 @@ fn parseBuild(args: []const []const u8) CliArgs {
|
|||
\\ --wasm-stack-size=<bytes> Stack size for WASM targets in bytes (default: 8388608 = 8MB)
|
||||
\\ --z-bench-tokenize=<path> Benchmark tokenizer on a file or directory
|
||||
\\ --z-bench-parse=<path> Benchmark parser on a file or directory
|
||||
\\ --z-dump-linker Dump linker inputs to temp directory for debugging
|
||||
\\ -h, --help Print help
|
||||
\\
|
||||
};
|
||||
|
|
@ -348,6 +351,8 @@ fn parseBuild(args: []const []const u8) CliArgs {
|
|||
} else {
|
||||
return CliArgs{ .problem = CliProblem{ .missing_flag_value = .{ .flag = "--wasm-stack-size" } } };
|
||||
}
|
||||
} else if (mem.eql(u8, arg, "--z-dump-linker")) {
|
||||
z_dump_linker = true;
|
||||
} else {
|
||||
if (path != null) {
|
||||
return CliArgs{ .problem = CliProblem{ .unexpected_argument = .{ .cmd = "build", .arg = arg } } };
|
||||
|
|
@ -355,7 +360,7 @@ fn parseBuild(args: []const []const u8) CliArgs {
|
|||
path = arg;
|
||||
}
|
||||
}
|
||||
return CliArgs{ .build = BuildArgs{ .path = path orelse "main.roc", .opt = opt, .target = target, .output = output, .debug = debug, .allow_errors = allow_errors, .wasm_memory = wasm_memory, .wasm_stack_size = wasm_stack_size, .z_bench_tokenize = z_bench_tokenize, .z_bench_parse = z_bench_parse } };
|
||||
return CliArgs{ .build = BuildArgs{ .path = path orelse "main.roc", .opt = opt, .target = target, .output = output, .debug = debug, .allow_errors = allow_errors, .wasm_memory = wasm_memory, .wasm_stack_size = wasm_stack_size, .z_bench_tokenize = z_bench_tokenize, .z_bench_parse = z_bench_parse, .z_dump_linker = z_dump_linker } };
|
||||
}
|
||||
|
||||
fn parseBundle(alloc: mem.Allocator, args: []const []const u8) std.mem.Allocator.Error!CliArgs {
|
||||
|
|
|
|||
|
|
@ -119,13 +119,10 @@ pub const LinkError = error{
|
|||
WindowsSDKNotFound,
|
||||
} || std.zig.system.DetectError;
|
||||
|
||||
/// Link object files into an executable using LLD
|
||||
pub fn link(allocs: *Allocators, config: LinkConfig) LinkError!void {
|
||||
// Check if LLVM is available at compile time
|
||||
if (comptime !llvm_available) {
|
||||
return LinkError.LLVMNotAvailable;
|
||||
}
|
||||
|
||||
/// Build the linker command arguments for the given configuration.
|
||||
/// Returns the args array that would be passed to LLD.
|
||||
/// This is used both by link() and formatLinkCommand().
|
||||
fn buildLinkArgs(allocs: *Allocators, config: LinkConfig) LinkError!std.array_list.Managed([]const u8) {
|
||||
// Use arena allocator for all temporary allocations
|
||||
// Pre-allocate capacity to avoid reallocations (typical command has 20-40 args)
|
||||
var args = std.array_list.Managed([]const u8).initCapacity(allocs.arena, 64) catch return LinkError.OutOfMemory;
|
||||
|
|
@ -356,6 +353,18 @@ pub fn link(allocs: *Allocators, config: LinkConfig) LinkError!void {
|
|||
try args.append(extra_arg);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
/// Link object files into an executable using LLD
|
||||
pub fn link(allocs: *Allocators, config: LinkConfig) LinkError!void {
|
||||
// Check if LLVM is available at compile time
|
||||
if (comptime !llvm_available) {
|
||||
return LinkError.LLVMNotAvailable;
|
||||
}
|
||||
|
||||
const args = try buildLinkArgs(allocs, config);
|
||||
|
||||
// Debug: Print the linker command
|
||||
std.log.debug("Linker command:", .{});
|
||||
for (args.items) |arg| {
|
||||
|
|
@ -371,7 +380,7 @@ pub fn link(allocs: *Allocators, config: LinkConfig) LinkError!void {
|
|||
}
|
||||
|
||||
// Call appropriate LLD function based on target format
|
||||
const success = if (comptime llvm_available) switch (config.target_format) {
|
||||
const success = switch (config.target_format) {
|
||||
.elf => llvm_externs.ZigLLDLinkELF(
|
||||
@intCast(c_args.len),
|
||||
c_args.ptr,
|
||||
|
|
@ -396,13 +405,45 @@ pub fn link(allocs: *Allocators, config: LinkConfig) LinkError!void {
|
|||
config.can_exit_early,
|
||||
config.disable_output,
|
||||
),
|
||||
} else false;
|
||||
};
|
||||
|
||||
if (!success) {
|
||||
return LinkError.LinkFailed;
|
||||
}
|
||||
}
|
||||
|
||||
/// Format link configuration as a shell command string for manual reproduction.
|
||||
/// Useful for debugging linking issues by allowing users to run the linker manually.
|
||||
pub fn formatLinkCommand(allocs: *Allocators, config: LinkConfig) LinkError![]const u8 {
|
||||
const args = try buildLinkArgs(allocs, config);
|
||||
|
||||
// Join args with spaces, quoting paths that contain spaces or special chars
|
||||
var result = std.array_list.Managed(u8).init(allocs.arena);
|
||||
|
||||
for (args.items, 0..) |arg, i| {
|
||||
if (i > 0) result.append(' ') catch return LinkError.OutOfMemory;
|
||||
|
||||
// Quote if contains spaces or shell metacharacters
|
||||
const needs_quoting = std.mem.indexOfAny(u8, arg, " \t'\"\\$`") != null;
|
||||
if (needs_quoting) {
|
||||
result.append('\'') catch return LinkError.OutOfMemory;
|
||||
// Escape single quotes within the string
|
||||
for (arg) |c| {
|
||||
if (c == '\'') {
|
||||
result.appendSlice("'\\''") catch return LinkError.OutOfMemory;
|
||||
} else {
|
||||
result.append(c) catch return LinkError.OutOfMemory;
|
||||
}
|
||||
}
|
||||
result.append('\'') catch return LinkError.OutOfMemory;
|
||||
} else {
|
||||
result.appendSlice(arg) catch return LinkError.OutOfMemory;
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice() catch return LinkError.OutOfMemory;
|
||||
}
|
||||
|
||||
/// Convenience function to link two object files into an executable
|
||||
pub fn linkTwoObjects(allocs: *Allocators, obj1: []const u8, obj2: []const u8, output: []const u8) LinkError!void {
|
||||
if (comptime !llvm_available) {
|
||||
|
|
|
|||
134
src/cli/main.zig
134
src/cli/main.zig
|
|
@ -3816,6 +3816,11 @@ fn rocBuildEmbedded(allocs: *Allocators, args: cli_args.BuildArgs) !void {
|
|||
.wasm_stack_size = args.wasm_stack_size orelse linker_mod.DEFAULT_WASM_STACK_SIZE,
|
||||
};
|
||||
|
||||
// Dump linker inputs to temp directory if requested
|
||||
if (args.z_dump_linker) {
|
||||
try dumpLinkerInputs(allocs, link_config);
|
||||
}
|
||||
|
||||
try linker_mod.link(allocs, link_config);
|
||||
|
||||
const output_type = switch (link_type) {
|
||||
|
|
@ -3826,6 +3831,135 @@ fn rocBuildEmbedded(allocs: *Allocators, args: cli_args.BuildArgs) !void {
|
|||
std.log.info("Successfully built {s}: {s}", .{ output_type, final_output_path });
|
||||
}
|
||||
|
||||
/// Dump linker inputs to a temp directory for debugging linking issues.
|
||||
/// Creates a directory with all input files copied and a README with the linker command.
|
||||
fn dumpLinkerInputs(allocs: *Allocators, link_config: linker.LinkConfig) !void {
|
||||
const stderr = stderrWriter();
|
||||
defer stderr.flush() catch {};
|
||||
|
||||
// Create temp directory with unique name based on timestamp
|
||||
const timestamp = std.time.timestamp();
|
||||
const dir_name = try std.fmt.allocPrint(allocs.arena, "roc-linker-debug-{d}", .{timestamp});
|
||||
const dump_dir = try std.fs.path.join(allocs.arena, &.{ "/tmp", dir_name });
|
||||
|
||||
std.fs.cwd().makePath(dump_dir) catch |err| {
|
||||
try stderr.print("Failed to create debug dump directory '{s}': {}\n", .{ dump_dir, err });
|
||||
return err;
|
||||
};
|
||||
|
||||
// Track copied files for the README
|
||||
var copied_files = try std.array_list.Managed(CopiedFile).initCapacity(allocs.arena, 16);
|
||||
|
||||
// Copy platform_files_pre
|
||||
for (link_config.platform_files_pre, 0..) |src, i| {
|
||||
const basename = std.fs.path.basename(src);
|
||||
const dest_name = try std.fmt.allocPrint(allocs.arena, "pre_{d}_{s}", .{ i, basename });
|
||||
const dest_path = try std.fs.path.join(allocs.arena, &.{ dump_dir, dest_name });
|
||||
std.fs.cwd().copyFile(src, std.fs.cwd(), dest_path, .{}) catch |err| {
|
||||
try stderr.print("Warning: Failed to copy '{s}': {}\n", .{ src, err });
|
||||
continue;
|
||||
};
|
||||
try copied_files.append(.{ .name = dest_name, .original = src, .category = "platform (pre-link)" });
|
||||
}
|
||||
|
||||
// Copy object_files
|
||||
for (link_config.object_files, 0..) |src, i| {
|
||||
const basename = std.fs.path.basename(src);
|
||||
const dest_name = try std.fmt.allocPrint(allocs.arena, "obj_{d}_{s}", .{ i, basename });
|
||||
const dest_path = try std.fs.path.join(allocs.arena, &.{ dump_dir, dest_name });
|
||||
std.fs.cwd().copyFile(src, std.fs.cwd(), dest_path, .{}) catch |err| {
|
||||
try stderr.print("Warning: Failed to copy '{s}': {}\n", .{ src, err });
|
||||
continue;
|
||||
};
|
||||
try copied_files.append(.{ .name = dest_name, .original = src, .category = "object file" });
|
||||
}
|
||||
|
||||
// Copy platform_files_post
|
||||
for (link_config.platform_files_post, 0..) |src, i| {
|
||||
const basename = std.fs.path.basename(src);
|
||||
const dest_name = try std.fmt.allocPrint(allocs.arena, "post_{d}_{s}", .{ i, basename });
|
||||
const dest_path = try std.fs.path.join(allocs.arena, &.{ dump_dir, dest_name });
|
||||
std.fs.cwd().copyFile(src, std.fs.cwd(), dest_path, .{}) catch |err| {
|
||||
try stderr.print("Warning: Failed to copy '{s}': {}\n", .{ src, err });
|
||||
continue;
|
||||
};
|
||||
try copied_files.append(.{ .name = dest_name, .original = src, .category = "platform (post-link)" });
|
||||
}
|
||||
|
||||
// Generate the linker command string
|
||||
const link_cmd = linker.formatLinkCommand(allocs, link_config) catch |err| {
|
||||
try stderr.print("Warning: Failed to format linker command: {}\n", .{err});
|
||||
return;
|
||||
};
|
||||
|
||||
// Build the file list for README
|
||||
var file_list = std.array_list.Managed(u8).init(allocs.arena);
|
||||
for (copied_files.items) |file| {
|
||||
try file_list.writer().print(" {s}\n <- {s} ({s})\n", .{ file.name, file.original, file.category });
|
||||
}
|
||||
|
||||
// Write README.txt with instructions
|
||||
const readme_content = try std.fmt.allocPrint(allocs.arena,
|
||||
\\Roc Linker Debug Dump
|
||||
\\=====================
|
||||
\\
|
||||
\\Target format: {s}
|
||||
\\Target OS: {s}
|
||||
\\Target arch: {s}
|
||||
\\Output: {s}
|
||||
\\
|
||||
\\Files ({d} copied):
|
||||
\\{s}
|
||||
\\
|
||||
\\To manually reproduce the link step:
|
||||
\\
|
||||
\\ {s}
|
||||
\\
|
||||
\\Note: The command above uses original file paths. The copied files
|
||||
\\in this directory preserve original filenames for inspection.
|
||||
\\
|
||||
, .{
|
||||
@tagName(link_config.target_format),
|
||||
if (link_config.target_os) |os| @tagName(os) else "native",
|
||||
if (link_config.target_arch) |arch| @tagName(arch) else "native",
|
||||
link_config.output_path,
|
||||
copied_files.items.len,
|
||||
file_list.items,
|
||||
link_cmd,
|
||||
});
|
||||
|
||||
const readme_path = try std.fs.path.join(allocs.arena, &.{ dump_dir, "README.txt" });
|
||||
const readme_file = std.fs.cwd().createFile(readme_path, .{}) catch |err| {
|
||||
try stderr.print("Warning: Failed to create README.txt: {}\n", .{err});
|
||||
return;
|
||||
};
|
||||
defer readme_file.close();
|
||||
readme_file.writeAll(readme_content) catch |err| {
|
||||
try stderr.print("Warning: Failed to write README.txt: {}\n", .{err});
|
||||
};
|
||||
|
||||
// Print summary to stderr
|
||||
try stderr.print(
|
||||
\\
|
||||
\\=== Linker debug dump ===
|
||||
\\Directory: {s}
|
||||
\\Files: {d} copied
|
||||
\\
|
||||
\\To reproduce:
|
||||
\\ {s}
|
||||
\\
|
||||
\\See {s}/README.txt for details
|
||||
\\=========================
|
||||
\\
|
||||
, .{ dump_dir, copied_files.items.len, link_cmd, dump_dir });
|
||||
}
|
||||
|
||||
const CopiedFile = struct {
|
||||
name: []const u8,
|
||||
original: []const u8,
|
||||
category: []const u8,
|
||||
};
|
||||
|
||||
/// Information about a test (expect statement) to be evaluated
|
||||
const ExpectTest = struct {
|
||||
expr_idx: can.CIR.Expr.Idx,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue