Merge pull request #9440 from ziglang/emit-bc

add -femit-llvm-bc CLI option and implement it, and improve -fcompiler-rt support
This commit is contained in:
Andrew Kelley 2021-07-23 02:22:23 -04:00 committed by GitHub
commit e3fe3acce0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 454 additions and 235 deletions

View File

@ -796,6 +796,7 @@ set(BUILD_ZIG1_ARGS
--name zig1
--zig-lib-dir "${CMAKE_SOURCE_DIR}/lib"
"-femit-bin=${ZIG1_OBJECT}"
-fcompiler-rt
"${ZIG1_RELEASE_ARG}"
"${ZIG1_SINGLE_THREADED_ARG}"
-lc

View File

@ -1,5 +1,5 @@
const std = @import("std");
const builtin = std.builtin;
const builtin = @import("builtin");
const io = std.io;
const fs = std.fs;
const process = std.process;
@ -13,7 +13,7 @@ const Allocator = std.mem.Allocator;
const max_doc_file_size = 10 * 1024 * 1024;
const exe_ext = @as(std.zig.CrossTarget, .{}).exeFileExt();
const obj_ext = @as(std.zig.CrossTarget, .{}).oFileExt();
const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch);
const tmp_dir_name = "docgen_tmp";
const test_out_path = tmp_dir_name ++ fs.path.sep_str ++ "test" ++ exe_ext;
@ -281,7 +281,7 @@ const Code = struct {
name: []const u8,
source_token: Token,
is_inline: bool,
mode: builtin.Mode,
mode: std.builtin.Mode,
link_objects: []const []const u8,
target_str: ?[]const u8,
link_libc: bool,
@ -531,7 +531,7 @@ fn genToc(allocator: *Allocator, tokenizer: *Tokenizer) !Toc {
return parseError(tokenizer, code_kind_tok, "unrecognized code kind: {s}", .{code_kind_str});
}
var mode: builtin.Mode = .Debug;
var mode: std.builtin.Mode = .Debug;
var link_objects = std.ArrayList([]const u8).init(allocator);
defer link_objects.deinit();
var target_str: ?[]const u8 = null;

View File

@ -549,16 +549,36 @@ pub const Target = struct {
};
pub const ObjectFormat = enum {
/// Common Object File Format (Windows)
coff,
pe,
/// Executable and Linking Format
elf,
/// macOS relocatables
macho,
/// WebAssembly
wasm,
/// C source code
c,
/// Standard, Portable Intermediate Representation V
spirv,
/// Intel IHEX
hex,
/// Machine code with no metadata.
raw,
/// Plan 9 from Bell Labs
plan9,
pub fn fileExt(of: ObjectFormat, cpu_arch: Cpu.Arch) [:0]const u8 {
return switch (of) {
.coff => ".obj",
.elf, .macho, .wasm => ".o",
.c => ".c",
.spirv => ".spv",
.hex => ".ihex",
.raw => ".bin",
.plan9 => plan9Ext(cpu_arch),
};
}
};
pub const SubSystem = enum {
@ -1290,30 +1310,16 @@ pub const Target = struct {
return linuxTripleSimple(allocator, self.cpu.arch, self.os.tag, self.abi);
}
pub fn oFileExt_os_abi(os_tag: Os.Tag, abi: Abi) [:0]const u8 {
if (abi == .msvc) {
return ".obj";
}
switch (os_tag) {
.windows, .uefi => return ".obj",
else => return ".o",
}
}
pub fn oFileExt(self: Target) [:0]const u8 {
return oFileExt_os_abi(self.os.tag, self.abi);
}
pub fn exeFileExtSimple(cpu_arch: Cpu.Arch, os_tag: Os.Tag) [:0]const u8 {
switch (os_tag) {
.windows => return ".exe",
.uefi => return ".efi",
else => if (cpu_arch.isWasm()) {
return ".wasm";
} else {
return "";
return switch (os_tag) {
.windows => ".exe",
.uefi => ".efi",
.plan9 => plan9Ext(cpu_arch),
else => switch (cpu_arch) {
.wasm32, .wasm64 => ".wasm",
else => "",
},
}
};
}
pub fn exeFileExt(self: Target) [:0]const u8 {
@ -1353,20 +1359,16 @@ pub const Target = struct {
}
pub fn getObjectFormatSimple(os_tag: Os.Tag, cpu_arch: Cpu.Arch) ObjectFormat {
if (os_tag == .windows or os_tag == .uefi) {
return .coff;
} else if (os_tag.isDarwin()) {
return .macho;
}
if (cpu_arch.isWasm()) {
return .wasm;
}
if (cpu_arch.isSPIRV()) {
return .spirv;
}
if (os_tag == .plan9)
return .plan9;
return .elf;
return switch (os_tag) {
.windows, .uefi => .coff,
.ios, .macos, .watchos, .tvos => .macho,
.plan9 => .plan9,
else => return switch (cpu_arch) {
.wasm32, .wasm64 => .wasm,
.spirv32, .spirv64 => .spirv,
else => .elf,
},
};
}
pub fn getObjectFormat(self: Target) ObjectFormat {
@ -1677,6 +1679,30 @@ pub const Target = struct {
return false;
}
/// 0c spim little-endian MIPS 3000 family
/// 1c 68000 Motorola MC68000
/// 2c 68020 Motorola MC68020
/// 5c arm little-endian ARM
/// 6c amd64 AMD64 and compatibles (e.g., Intel EM64T)
/// 7c arm64 ARM64 (ARMv8)
/// 8c 386 Intel i386, i486, Pentium, etc.
/// kc sparc Sun SPARC
/// qc power Power PC
/// vc mips big-endian MIPS 3000 family
pub fn plan9Ext(cpu_arch: Cpu.Arch) [:0]const u8 {
return switch (cpu_arch) {
.arm => ".5",
.x86_64 => ".6",
.aarch64 => ".7",
.i386 => ".8",
.sparc => ".k",
.powerpc, .powerpcle => ".q",
.mips, .mipsel => ".v",
// ISAs without designated characters get 'X' for lack of a better option.
else => ".X",
};
}
};
test {

View File

@ -108,8 +108,9 @@ pub const BinNameOptions = struct {
pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) error{OutOfMemory}![]u8 {
const root_name = options.root_name;
const target = options.target;
switch (options.object_format orelse target.getObjectFormat()) {
.coff, .pe => switch (options.output_mode) {
const ofmt = options.object_format orelse target.getObjectFormat();
switch (ofmt) {
.coff => switch (options.output_mode) {
.Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.exeFileExt() }),
.Lib => {
const suffix = switch (options.link_mode orelse .Static) {
@ -118,7 +119,7 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro
};
return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, suffix });
},
.Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.oFileExt() }),
.Obj => return std.fmt.allocPrint(allocator, "{s}.obj", .{root_name}),
},
.elf => switch (options.output_mode) {
.Exe => return allocator.dupe(u8, root_name),
@ -140,7 +141,7 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro
},
}
},
.Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.oFileExt() }),
.Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
},
.macho => switch (options.output_mode) {
.Exe => return allocator.dupe(u8, root_name),
@ -163,7 +164,7 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro
}
return std.fmt.allocPrint(allocator, "{s}{s}{s}", .{ target.libPrefix(), root_name, suffix });
},
.Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.oFileExt() }),
.Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
},
.wasm => switch (options.output_mode) {
.Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.exeFileExt() }),
@ -175,36 +176,15 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro
.Dynamic => return std.fmt.allocPrint(allocator, "{s}.wasm", .{root_name}),
}
},
.Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.oFileExt() }),
.Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
},
.c => return std.fmt.allocPrint(allocator, "{s}.c", .{root_name}),
.spirv => return std.fmt.allocPrint(allocator, "{s}.spv", .{root_name}),
.hex => return std.fmt.allocPrint(allocator, "{s}.ihex", .{root_name}),
.raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}),
.plan9 => {
// copied from 2c(1)
// 0c spim little-endian MIPS 3000 family
// 1c 68000 Motorola MC68000
// 2c 68020 Motorola MC68020
// 5c arm little-endian ARM
// 6c amd64 AMD64 and compatibles (e.g., Intel EM64T)
// 7c arm64 ARM64 (ARMv8)
// 8c 386 Intel i386, i486, Pentium, etc.
// kc sparc Sun SPARC
// qc power Power PC
// vc mips big-endian MIPS 3000 family
const char: u8 = switch (target.cpu.arch) {
.arm => '5',
.x86_64 => '6',
.aarch64 => '7',
.i386 => '8',
.sparc => 'k',
.powerpc, .powerpcle => 'q',
.mips, .mipsel => 'v',
else => 'X', // this arch does not have a char or maybe was not ported to plan9 so we just use X
};
return std.fmt.allocPrint(allocator, "{s}.{c}", .{ root_name, char });
},
.plan9 => return std.fmt.allocPrint(allocator, "{s}{s}", .{
root_name, ofmt.fileExt(target.cpu.arch),
}),
}
}

View File

@ -473,10 +473,6 @@ pub const CrossTarget = struct {
return self.getOsTag() == .windows;
}
pub fn oFileExt(self: CrossTarget) [:0]const u8 {
return Target.oFileExt_os_abi(self.getOsTag(), self.getAbi());
}
pub fn exeFileExt(self: CrossTarget) [:0]const u8 {
return Target.exeFileExtSimple(self.getCpuArch(), self.getOsTag());
}

View File

@ -143,6 +143,7 @@ debug_compiler_runtime_libs: bool,
emit_asm: ?EmitLoc,
emit_llvm_ir: ?EmitLoc,
emit_llvm_bc: ?EmitLoc,
emit_analysis: ?EmitLoc,
emit_docs: ?EmitLoc,
@ -586,6 +587,17 @@ pub const Directory = struct {
return std.fs.path.join(allocator, paths);
}
}
pub fn joinZ(self: Directory, allocator: *Allocator, paths: []const []const u8) ![:0]u8 {
if (self.path) |p| {
// TODO clean way to do this with only 1 allocation
const part2 = try std.fs.path.join(allocator, paths);
defer allocator.free(part2);
return std.fs.path.joinZ(allocator, &[_][]const u8{ p, part2 });
} else {
return std.fs.path.joinZ(allocator, paths);
}
}
};
pub const EmitLoc = struct {
@ -623,6 +635,8 @@ pub const InitOptions = struct {
emit_asm: ?EmitLoc = null,
/// `null` means to not emit LLVM IR.
emit_llvm_ir: ?EmitLoc = null,
/// `null` means to not emit LLVM module bitcode.
emit_llvm_bc: ?EmitLoc = null,
/// `null` means to not emit semantic analysis JSON.
emit_analysis: ?EmitLoc = null,
/// `null` means to not emit docs.
@ -812,6 +826,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
const ofmt = options.object_format orelse options.target.getObjectFormat();
const use_stage1 = options.use_stage1 orelse blk: {
// Even though we may have no Zig code to compile (depending on `options.root_pkg`),
// we may need to use stage1 for building compiler-rt and other dependencies.
if (build_options.omit_stage2)
break :blk true;
if (options.use_llvm) |use_llvm| {
@ -819,6 +836,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
break :blk false;
}
}
break :blk build_options.is_stage1;
};
@ -835,6 +853,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
if (ofmt == .c)
break :blk false;
// If emitting to LLVM bitcode object format, must use LLVM backend.
if (options.emit_llvm_ir != null or options.emit_llvm_bc != null)
break :blk true;
// The stage1 compiler depends on the stage1 C++ LLVM backend
// to compile zig code.
if (use_stage1)
@ -853,6 +875,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
if (options.machine_code_model != .default) {
return error.MachineCodeModelNotSupportedWithoutLlvm;
}
if (options.emit_llvm_ir != null or options.emit_llvm_bc != null) {
return error.EmittingLlvmModuleRequiresUsingLlvmBackend;
}
}
const tsan = options.want_tsan orelse false;
@ -1381,6 +1406,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
.bin_file = bin_file,
.emit_asm = options.emit_asm,
.emit_llvm_ir = options.emit_llvm_ir,
.emit_llvm_bc = options.emit_llvm_bc,
.emit_analysis = options.emit_analysis,
.emit_docs = options.emit_docs,
.work_queue = std.fifo.LinearFifo(Job, .Dynamic).init(gpa),
@ -1513,24 +1539,19 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
}
// The `use_stage1` condition is here only because stage2 cannot yet build compiler-rt.
// Once it is capable this condition should be removed.
// Once it is capable this condition should be removed. When removing this condition,
// also test the use case of `build-obj -fcompiler-rt` with the self-hosted compiler
// and make sure the compiler-rt symbols are emitted. Currently this is hooked up for
// stage1 but not stage2.
if (comp.bin_file.options.use_stage1) {
if (comp.bin_file.options.include_compiler_rt) {
if (is_exe_or_dyn_lib) {
try comp.work_queue.writeItem(.{ .compiler_rt_lib = {} });
} else {
} else if (options.output_mode != .Obj) {
// If build-obj with -fcompiler-rt is requested, that is handled specially
// elsewhere. In this case we are making a static library, so we ask
// for a compiler-rt object to put in it.
try comp.work_queue.writeItem(.{ .compiler_rt_obj = {} });
if (comp.bin_file.options.object_format != .elf and
comp.bin_file.options.output_mode == .Obj)
{
// For ELF we can rely on using -r to link multiple objects together into one,
// but to truly support `build-obj -fcompiler-rt` will require virtually
// injecting `_ = @import("compiler_rt.zig")` into the root source file of
// the compilation.
fatal("Embedding compiler-rt into {s} objects is not yet implemented.", .{
@tagName(comp.bin_file.options.object_format),
});
}
}
}
if (needs_c_symbols) {
@ -2728,7 +2749,10 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
comp.bin_file.options.root_name
else
c_source_basename[0 .. c_source_basename.len - std.fs.path.extension(c_source_basename).len];
const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{ o_basename_noext, comp.getTarget().oFileExt() });
const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{
o_basename_noext,
comp.bin_file.options.object_format.fileExt(comp.bin_file.options.target.cpu.arch),
});
const digest = if (!comp.disable_c_depfile and try man.hit()) man.final() else blk: {
var argv = std.ArrayList([]const u8).init(comp.gpa);
@ -3023,7 +3047,7 @@ pub fn addCCArgs(
if (!comp.bin_file.options.strip) {
try argv.append("-g");
switch (comp.bin_file.options.object_format) {
.coff, .pe => try argv.append("-gcodeview"),
.coff => try argv.append("-gcodeview"),
else => {},
}
}
@ -3949,6 +3973,16 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
const id_symlink_basename = "stage1.id";
const libs_txt_basename = "libs.txt";
// The include_compiler_rt stored in the bin file options here means that we need
// compiler-rt symbols *somehow*. However, in the context of using the stage1 backend
// we need to tell stage1 to include compiler-rt only if stage1 is the place that
// needs to provide those symbols. Otherwise the stage2 infrastructure will take care
// of it in the linker, by putting compiler_rt.o into a static archive, or linking
// compiler_rt.a against an executable. In other words we only want to set this flag
// for stage1 if we are using build-obj.
const include_compiler_rt = comp.bin_file.options.output_mode == .Obj and
comp.bin_file.options.include_compiler_rt;
// We are about to obtain this lock, so here we give other processes a chance first.
comp.releaseStage1Lock();
@ -3970,6 +4004,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
man.hash.add(target.os.getVersionRange());
man.hash.add(comp.bin_file.options.dll_export_fns);
man.hash.add(comp.bin_file.options.function_sections);
man.hash.add(include_compiler_rt);
man.hash.add(comp.bin_file.options.is_test);
man.hash.add(comp.bin_file.options.emit != null);
man.hash.add(mod.emit_h != null);
@ -3978,6 +4013,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
}
man.hash.addOptionalEmitLoc(comp.emit_asm);
man.hash.addOptionalEmitLoc(comp.emit_llvm_ir);
man.hash.addOptionalEmitLoc(comp.emit_llvm_bc);
man.hash.addOptionalEmitLoc(comp.emit_analysis);
man.hash.addOptionalEmitLoc(comp.emit_docs);
man.hash.add(comp.test_evented_io);
@ -4083,13 +4119,14 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
) orelse return error.OutOfMemory;
const emit_bin_path = if (comp.bin_file.options.emit != null) blk: {
const bin_basename = try std.zig.binNameAlloc(arena, .{
const obj_basename = try std.zig.binNameAlloc(arena, .{
.root_name = comp.bin_file.options.root_name,
.target = target,
.output_mode = .Obj,
});
break :blk try directory.join(arena, &[_][]const u8{bin_basename});
break :blk try directory.join(arena, &[_][]const u8{obj_basename});
} else "";
if (mod.emit_h != null) {
log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{});
}
@ -4097,6 +4134,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
const emit_h_path = try stage1LocPath(arena, emit_h_loc, directory);
const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory);
const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory);
const emit_llvm_bc_path = try stage1LocPath(arena, comp.emit_llvm_bc, directory);
const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory);
const emit_docs_path = try stage1LocPath(arena, comp.emit_docs, directory);
const stage1_pkg = try createStage1Pkg(arena, "root", mod.root_pkg, null);
@ -4117,6 +4155,8 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
.emit_asm_len = emit_asm_path.len,
.emit_llvm_ir_ptr = emit_llvm_ir_path.ptr,
.emit_llvm_ir_len = emit_llvm_ir_path.len,
.emit_bitcode_ptr = emit_llvm_bc_path.ptr,
.emit_bitcode_len = emit_llvm_bc_path.len,
.emit_analysis_json_ptr = emit_analysis_path.ptr,
.emit_analysis_json_len = emit_analysis_path.len,
.emit_docs_ptr = emit_docs_path.ptr,
@ -4145,6 +4185,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
.valgrind_enabled = comp.bin_file.options.valgrind,
.tsan_enabled = comp.bin_file.options.tsan,
.function_sections = comp.bin_file.options.function_sections,
.include_compiler_rt = include_compiler_rt,
.enable_stack_probing = comp.bin_file.options.stack_check,
.red_zone = comp.bin_file.options.red_zone,
.enable_time_report = comp.time_report,

View File

@ -72,9 +72,9 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 {
.renderscript32 => "renderscript32",
.renderscript64 => "renderscript64",
.ve => "ve",
.spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII,
.spirv32 => return error.LLVMBackendDoesNotSupportSPIRV,
.spirv64 => return error.LLVMBackendDoesNotSupportSPIRV,
.spu_2 => return error.@"LLVM backend does not support SPU Mark II",
.spirv32 => return error.@"LLVM backend does not support SPIR-V",
.spirv64 => return error.@"LLVM backend does not support SPIR-V",
};
const llvm_os = switch (target.os.tag) {
@ -114,11 +114,13 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 {
.wasi => "wasi",
.emscripten => "emscripten",
.uefi => "windows",
.opencl => return error.LLVMBackendDoesNotSupportOpenCL,
.glsl450 => return error.LLVMBackendDoesNotSupportGLSL450,
.vulkan => return error.LLVMBackendDoesNotSupportVulkan,
.plan9 => return error.LLVMBackendDoesNotSupportPlan9,
.other => "unknown",
.opencl,
.glsl450,
.vulkan,
.plan9,
.other,
=> "unknown",
};
const llvm_abi = switch (target.abi) {
@ -152,84 +154,105 @@ pub const Object = struct {
llvm_module: *const llvm.Module,
context: *const llvm.Context,
target_machine: *const llvm.TargetMachine,
object_pathZ: [:0]const u8,
pub fn create(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Object {
_ = sub_path;
const self = try allocator.create(Object);
errdefer allocator.destroy(self);
const obj_basename = try std.zig.binNameAlloc(allocator, .{
.root_name = options.root_name,
.target = options.target,
.output_mode = .Obj,
});
defer allocator.free(obj_basename);
const o_directory = options.module.?.zig_cache_artifact_directory;
const object_path = try o_directory.join(allocator, &[_][]const u8{obj_basename});
defer allocator.free(object_path);
const object_pathZ = try allocator.dupeZ(u8, object_path);
errdefer allocator.free(object_pathZ);
pub fn create(gpa: *Allocator, options: link.Options) !*Object {
const obj = try gpa.create(Object);
errdefer gpa.destroy(obj);
obj.* = try Object.init(gpa, options);
return obj;
}
pub fn init(gpa: *Allocator, options: link.Options) !Object {
const context = llvm.Context.create();
errdefer context.dispose();
initializeLLVMTargets();
const root_nameZ = try allocator.dupeZ(u8, options.root_name);
defer allocator.free(root_nameZ);
const root_nameZ = try gpa.dupeZ(u8, options.root_name);
defer gpa.free(root_nameZ);
const llvm_module = llvm.Module.createWithName(root_nameZ.ptr, context);
errdefer llvm_module.dispose();
const llvm_target_triple = try targetTriple(allocator, options.target);
defer allocator.free(llvm_target_triple);
const llvm_target_triple = try targetTriple(gpa, options.target);
defer gpa.free(llvm_target_triple);
var error_message: [*:0]const u8 = undefined;
var target: *const llvm.Target = undefined;
if (llvm.Target.getFromTriple(llvm_target_triple.ptr, &target, &error_message).toBool()) {
defer llvm.disposeMessage(error_message);
const stderr = std.io.getStdErr().writer();
try stderr.print(
\\Zig is expecting LLVM to understand this target: '{s}'
\\However LLVM responded with: "{s}"
\\
,
.{ llvm_target_triple, error_message },
);
return error.InvalidLLVMTriple;
log.err("LLVM failed to parse '{s}': {s}", .{ llvm_target_triple, error_message });
return error.InvalidLlvmTriple;
}
const opt_level: llvm.CodeGenOptLevel = if (options.optimize_mode == .Debug) .None else .Aggressive;
const opt_level: llvm.CodeGenOptLevel = if (options.optimize_mode == .Debug)
.None
else
.Aggressive;
const reloc_mode: llvm.RelocMode = if (options.pic)
.PIC
else if (options.link_mode == .Dynamic)
llvm.RelocMode.DynamicNoPIC
else
.Static;
const code_model: llvm.CodeModel = switch (options.machine_code_model) {
.default => .Default,
.tiny => .Tiny,
.small => .Small,
.kernel => .Kernel,
.medium => .Medium,
.large => .Large,
};
// TODO handle float ABI better- it should depend on the ABI portion of std.Target
const float_abi: llvm.ABIType = .Default;
// TODO a way to override this as part of std.Target ABI?
const abi_name: ?[*:0]const u8 = switch (options.target.cpu.arch) {
.riscv32 => switch (options.target.os.tag) {
.linux => "ilp32d",
else => "ilp32",
},
.riscv64 => switch (options.target.os.tag) {
.linux => "lp64d",
else => "lp64",
},
else => null,
};
const target_machine = llvm.TargetMachine.create(
target,
llvm_target_triple.ptr,
"",
"",
if (options.target.cpu.model.llvm_name) |s| s.ptr else null,
options.llvm_cpu_features,
opt_level,
.Static,
.Default,
reloc_mode,
code_model,
options.function_sections,
float_abi,
abi_name,
);
errdefer target_machine.dispose();
self.* = .{
return Object{
.llvm_module = llvm_module,
.context = context,
.target_machine = target_machine,
.object_pathZ = object_pathZ,
};
return self;
}
pub fn deinit(self: *Object, allocator: *Allocator) void {
pub fn deinit(self: *Object) void {
self.target_machine.dispose();
self.llvm_module.dispose();
self.context.dispose();
self.* = undefined;
}
allocator.free(self.object_pathZ);
allocator.destroy(self);
pub fn destroy(self: *Object, gpa: *Allocator) void {
self.deinit();
gpa.destroy(self);
}
fn initializeLLVMTargets() void {
@ -240,38 +263,81 @@ pub const Object = struct {
llvm.initializeAllAsmParsers();
}
fn locPath(
arena: *Allocator,
opt_loc: ?Compilation.EmitLoc,
cache_directory: Compilation.Directory,
) !?[*:0]u8 {
const loc = opt_loc orelse return null;
const directory = loc.directory orelse cache_directory;
const slice = try directory.joinZ(arena, &[_][]const u8{loc.basename});
return slice.ptr;
}
pub fn flushModule(self: *Object, comp: *Compilation) !void {
if (comp.verbose_llvm_ir) {
const dump = self.llvm_module.printToString();
defer llvm.disposeMessage(dump);
const stderr = std.io.getStdErr().writer();
try stderr.writeAll(std.mem.spanZ(dump));
self.llvm_module.dump();
}
{
if (std.debug.runtime_safety) {
var error_message: [*:0]const u8 = undefined;
// verifyModule always allocs the error_message even if there is no error
defer llvm.disposeMessage(error_message);
if (self.llvm_module.verify(.ReturnStatus, &error_message).toBool()) {
const stderr = std.io.getStdErr().writer();
try stderr.print("broken LLVM module found: {s}\nThis is a bug in the Zig compiler.", .{error_message});
return error.BrokenLLVMModule;
std.debug.print("\n{s}\n", .{error_message});
@panic("LLVM module verification failed");
}
}
var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
defer arena_allocator.deinit();
const arena = &arena_allocator.allocator;
const mod = comp.bin_file.options.module.?;
const cache_dir = mod.zig_cache_artifact_directory;
const emit_bin_path: ?[*:0]const u8 = if (comp.bin_file.options.emit != null) blk: {
const obj_basename = try std.zig.binNameAlloc(arena, .{
.root_name = comp.bin_file.options.root_name,
.target = comp.bin_file.options.target,
.output_mode = .Obj,
});
if (cache_dir.joinZ(arena, &[_][]const u8{obj_basename})) |p| {
break :blk p.ptr;
} else |err| {
return err;
}
} else null;
const emit_asm_path = try locPath(arena, comp.emit_asm, cache_dir);
const emit_llvm_ir_path = try locPath(arena, comp.emit_llvm_ir, cache_dir);
const emit_llvm_bc_path = try locPath(arena, comp.emit_llvm_bc, cache_dir);
var error_message: [*:0]const u8 = undefined;
if (self.target_machine.emitToFile(
self.llvm_module,
self.object_pathZ.ptr,
.ObjectFile,
&error_message,
).toBool()) {
comp.bin_file.options.optimize_mode == .Debug,
comp.bin_file.options.optimize_mode == .ReleaseSmall,
comp.time_report,
comp.bin_file.options.tsan,
comp.bin_file.options.lto,
emit_asm_path,
emit_bin_path,
emit_llvm_ir_path,
emit_llvm_bc_path,
)) {
defer llvm.disposeMessage(error_message);
const stderr = std.io.getStdErr().writer();
try stderr.print("LLVM failed to emit file: {s}\n", .{error_message});
const emit_asm_msg = emit_asm_path orelse "(none)";
const emit_bin_msg = emit_bin_path orelse "(none)";
const emit_llvm_ir_msg = emit_llvm_ir_path orelse "(none)";
const emit_llvm_bc_msg = emit_llvm_bc_path orelse "(none)";
log.err("LLVM failed to emit asm={s} bin={s} ir={s} bc={s}: {s}", .{
emit_asm_msg, emit_bin_msg, emit_llvm_ir_msg, emit_llvm_bc_msg,
error_message,
});
return error.FailedToEmit;
}
}

View File

@ -123,6 +123,9 @@ pub const Module = opaque {
pub const getNamedGlobal = LLVMGetNamedGlobal;
extern fn LLVMGetNamedGlobal(M: *const Module, Name: [*:0]const u8) ?*const Value;
pub const dump = LLVMDumpModule;
extern fn LLVMDumpModule(M: *const Module) void;
};
pub const lookupIntrinsicID = LLVMLookupIntrinsicID;
@ -250,31 +253,41 @@ pub const BasicBlock = opaque {
};
pub const TargetMachine = opaque {
pub const create = LLVMCreateTargetMachine;
extern fn LLVMCreateTargetMachine(
pub const create = ZigLLVMCreateTargetMachine;
extern fn ZigLLVMCreateTargetMachine(
T: *const Target,
Triple: [*:0]const u8,
CPU: [*:0]const u8,
Features: [*:0]const u8,
CPU: ?[*:0]const u8,
Features: ?[*:0]const u8,
Level: CodeGenOptLevel,
Reloc: RelocMode,
CodeModel: CodeMode,
CodeModel: CodeModel,
function_sections: bool,
float_abi: ABIType,
abi_name: ?[*:0]const u8,
) *const TargetMachine;
pub const dispose = LLVMDisposeTargetMachine;
extern fn LLVMDisposeTargetMachine(T: *const TargetMachine) void;
pub const emitToFile = LLVMTargetMachineEmitToFile;
extern fn LLVMTargetMachineEmitToFile(
*const TargetMachine,
pub const emitToFile = ZigLLVMTargetMachineEmitToFile;
extern fn ZigLLVMTargetMachineEmitToFile(
T: *const TargetMachine,
M: *const Module,
Filename: [*:0]const u8,
codegen: CodeGenFileType,
ErrorMessage: *[*:0]const u8,
) Bool;
is_debug: bool,
is_small: bool,
time_report: bool,
tsan: bool,
lto: bool,
asm_filename: ?[*:0]const u8,
bin_filename: ?[*:0]const u8,
llvm_ir_filename: ?[*:0]const u8,
bitcode_filename: ?[*:0]const u8,
) bool;
};
pub const CodeMode = enum(c_int) {
pub const CodeModel = enum(c_int) {
Default,
JITDefault,
Tiny,
@ -295,7 +308,7 @@ pub const RelocMode = enum(c_int) {
Default,
Static,
PIC,
DynamicNoPic,
DynamicNoPIC,
ROPI,
RWPI,
ROPI_RWPI,
@ -306,6 +319,15 @@ pub const CodeGenFileType = enum(c_int) {
ObjectFile,
};
pub const ABIType = enum(c_int) {
/// Target-specific (either soft or hard depending on triple, etc).
Default,
/// Soft float.
Soft,
// Hard float.
Hard,
};
pub const Target = opaque {
pub const getFromTriple = LLVMGetTargetFromTriple;
extern fn LLVMGetTargetFromTriple(Triple: [*:0]const u8, T: **const Target, ErrorMessage: *[*:0]const u8) Bool;

View File

@ -191,7 +191,7 @@ pub const File = struct {
const use_stage1 = build_options.is_stage1 and options.use_stage1;
if (use_stage1 or options.emit == null) {
return switch (options.object_format) {
.coff, .pe => &(try Coff.createEmpty(allocator, options)).base,
.coff => &(try Coff.createEmpty(allocator, options)).base,
.elf => &(try Elf.createEmpty(allocator, options)).base,
.macho => &(try MachO.createEmpty(allocator, options)).base,
.wasm => &(try Wasm.createEmpty(allocator, options)).base,
@ -206,9 +206,10 @@ pub const File = struct {
const use_lld = build_options.have_llvm and options.use_lld; // comptime known false when !have_llvm
const sub_path = if (use_lld) blk: {
if (options.module == null) {
// No point in opening a file, we would not write anything to it. Initialize with empty.
// No point in opening a file, we would not write anything to it.
// Initialize with empty.
return switch (options.object_format) {
.coff, .pe => &(try Coff.createEmpty(allocator, options)).base,
.coff => &(try Coff.createEmpty(allocator, options)).base,
.elf => &(try Elf.createEmpty(allocator, options)).base,
.macho => &(try MachO.createEmpty(allocator, options)).base,
.plan9 => &(try Plan9.createEmpty(allocator, options)).base,
@ -219,13 +220,16 @@ pub const File = struct {
.raw => return error.RawObjectFormatUnimplemented,
};
}
// Open a temporary object file, not the final output file because we want to link with LLD.
break :blk try std.fmt.allocPrint(allocator, "{s}{s}", .{ emit.sub_path, options.target.oFileExt() });
// Open a temporary object file, not the final output file because we
// want to link with LLD.
break :blk try std.fmt.allocPrint(allocator, "{s}{s}", .{
emit.sub_path, options.object_format.fileExt(options.target.cpu.arch),
});
} else emit.sub_path;
errdefer if (use_lld) allocator.free(sub_path);
const file: *File = switch (options.object_format) {
.coff, .pe => &(try Coff.openPath(allocator, sub_path, options)).base,
.coff => &(try Coff.openPath(allocator, sub_path, options)).base,
.elf => &(try Elf.openPath(allocator, sub_path, options)).base,
.macho => &(try MachO.openPath(allocator, sub_path, options)).base,
.plan9 => &(try Plan9.openPath(allocator, sub_path, options)).base,

View File

@ -17,9 +17,9 @@ const link = @import("../link.zig");
const build_options = @import("build_options");
const Cache = @import("../Cache.zig");
const mingw = @import("../mingw.zig");
const llvm_backend = @import("../codegen/llvm.zig");
const Air = @import("../Air.zig");
const Liveness = @import("../Liveness.zig");
const LlvmObject = @import("../codegen/llvm.zig").Object;
const allocation_padding = 4 / 3;
const minimum_text_block_size = 64 * allocation_padding;
@ -37,7 +37,7 @@ pub const base_tag: link.File.Tag = .coff;
const msdos_stub = @embedFile("msdos-stub.bin");
/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
llvm_object: ?*llvm_backend.Object = null,
llvm_object: ?*LlvmObject = null,
base: link.File,
ptr_width: PtrWidth,
@ -132,7 +132,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
const self = try createEmpty(allocator, options);
errdefer self.base.destroy();
self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options);
self.llvm_object = try LlvmObject.create(allocator, options);
return self;
}
@ -657,10 +657,7 @@ fn writeOffsetTableEntry(self: *Coff, index: usize) !void {
}
pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
if (build_options.skip_non_native and
builtin.object_format != .coff and
builtin.object_format != .pe)
{
if (build_options.skip_non_native and builtin.object_format != .coff) {
@panic("Attempted to compile for object format that was disabled by build configuration");
}
if (build_options.have_llvm) {
@ -697,7 +694,7 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live
}
pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void {
if (build_options.skip_non_native and builtin.object_format != .coff and builtin.object_format != .pe) {
if (build_options.skip_non_native and builtin.object_format != .coff) {
@panic("Attempted to compile for object format that was disabled by build configuration");
}
if (build_options.have_llvm) {
@ -823,8 +820,11 @@ pub fn flushModule(self: *Coff, comp: *Compilation) !void {
const tracy = trace(@src());
defer tracy.end();
if (build_options.have_llvm)
if (self.llvm_object) |llvm_object| return try llvm_object.flushModule(comp);
if (build_options.have_llvm) {
if (self.llvm_object) |llvm_object| {
return try llvm_object.flushModule(comp);
}
}
if (self.text_section_size_dirty) {
// Write the new raw size in the .text header
@ -1398,8 +1398,9 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !v
}
pub fn deinit(self: *Coff) void {
if (build_options.have_llvm)
if (self.llvm_object) |ir_module| ir_module.deinit(self.base.allocator);
if (build_options.have_llvm) {
if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);
}
self.text_block_free_list.deinit(self.base.allocator);
self.offset_table.deinit(self.base.allocator);

View File

@ -25,9 +25,9 @@ const target_util = @import("../target.zig");
const glibc = @import("../glibc.zig");
const musl = @import("../musl.zig");
const Cache = @import("../Cache.zig");
const llvm_backend = @import("../codegen/llvm.zig");
const Air = @import("../Air.zig");
const Liveness = @import("../Liveness.zig");
const LlvmObject = @import("../codegen/llvm.zig").Object;
const default_entry_addr = 0x8000000;
@ -38,7 +38,7 @@ base: File,
ptr_width: PtrWidth,
/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
llvm_object: ?*llvm_backend.Object = null,
llvm_object: ?*LlvmObject = null,
/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
/// Same order as in the file.
@ -235,7 +235,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
const self = try createEmpty(allocator, options);
errdefer self.base.destroy();
self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options);
self.llvm_object = try LlvmObject.create(allocator, options);
return self;
}
@ -301,9 +301,9 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Elf {
}
pub fn deinit(self: *Elf) void {
if (build_options.have_llvm)
if (self.llvm_object) |ir_module|
ir_module.deinit(self.base.allocator);
if (build_options.have_llvm) {
if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);
}
self.sections.deinit(self.base.allocator);
self.program_headers.deinit(self.base.allocator);
@ -750,8 +750,8 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void {
if (build_options.have_llvm)
if (self.llvm_object) |llvm_object| return try llvm_object.flushModule(comp);
// TODO This linker code currently assumes there is only 1 compilation unit and it corresponds to the
// Zig source code.
// TODO This linker code currently assumes there is only 1 compilation unit and it
// corresponds to the Zig source code.
const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented;
const target_endian = self.base.options.target.cpu.arch.endian();
@ -1289,6 +1289,10 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
// TODO: remove when stage2 can build compiler_rt.zig
if (!build_options.is_stage1) break :blk null;
// In the case of build-obj we include the compiler-rt symbols directly alongside
// the symbols of the root source file, in the same compilation unit.
if (is_obj) break :blk null;
if (is_exe_or_dyn_lib) {
break :blk comp.compiler_rt_static_lib.?.full_object_path;
} else {

View File

@ -30,7 +30,7 @@ const DebugSymbols = @import("MachO/DebugSymbols.zig");
const Trie = @import("MachO/Trie.zig");
const CodeSignature = @import("MachO/CodeSignature.zig");
const Zld = @import("MachO/Zld.zig");
const llvm_backend = @import("../codegen/llvm.zig");
const LlvmObject = @import("../codegen/llvm.zig").Object;
usingnamespace @import("MachO/commands.zig");
@ -39,7 +39,7 @@ pub const base_tag: File.Tag = File.Tag.macho;
base: File,
/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
llvm_object: ?*llvm_backend.Object = null,
llvm_object: ?*LlvmObject = null,
/// Debug symbols bundle (or dSym).
d_sym: ?DebugSymbols = null,
@ -355,7 +355,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
const self = try createEmpty(allocator, options);
errdefer self.base.destroy();
self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options);
self.llvm_object = try LlvmObject.create(allocator, options);
return self;
}
@ -989,6 +989,9 @@ fn darwinArchString(arch: std.Target.Cpu.Arch) []const u8 {
}
pub fn deinit(self: *MachO) void {
if (build_options.have_llvm) {
if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);
}
if (self.d_sym) |*ds| {
ds.deinit(self.base.allocator);
}

View File

@ -19,7 +19,7 @@ const build_options = @import("build_options");
const wasi_libc = @import("../wasi_libc.zig");
const Cache = @import("../Cache.zig");
const TypedValue = @import("../TypedValue.zig");
const llvm_backend = @import("../codegen/llvm.zig");
const LlvmObject = @import("../codegen/llvm.zig").Object;
const Air = @import("../Air.zig");
const Liveness = @import("../Liveness.zig");
@ -27,7 +27,7 @@ pub const base_tag = link.File.Tag.wasm;
base: link.File,
/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
llvm_object: ?*llvm_backend.Object = null,
llvm_object: ?*LlvmObject = null,
/// List of all function Decls to be written to the output file. The index of
/// each Decl in this list at the time of writing the binary is used as the
/// function index. In the event where ext_funcs' size is not 0, the index of
@ -121,7 +121,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
const self = try createEmpty(allocator, options);
errdefer self.base.destroy();
self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options);
self.llvm_object = try LlvmObject.create(allocator, options);
return self;
}
@ -153,6 +153,9 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Wasm {
}
pub fn deinit(self: *Wasm) void {
if (build_options.have_llvm) {
if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);
}
for (self.symbols.items) |decl| {
decl.fn_link.wasm.functype.deinit(self.base.allocator);
decl.fn_link.wasm.code.deinit(self.base.allocator);
@ -642,7 +645,9 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
break :blk full_obj_path;
} else null;
const compiler_rt_path: ?[]const u8 = if (self.base.options.include_compiler_rt)
const is_obj = self.base.options.output_mode == .Obj;
const compiler_rt_path: ?[]const u8 = if (self.base.options.include_compiler_rt and !is_obj)
comp.compiler_rt_static_lib.?.full_object_path
else
null;

View File

@ -287,9 +287,9 @@ const usage_build_generic =
\\ .s Target-specific assembly source code
\\ .S Assembly with C preprocessor (requires LLVM extensions)
\\ .c C source code (requires LLVM extensions)
\\ .cpp C++ source code (requires LLVM extensions)
\\ Other C++ extensions: .C .cc .cxx
\\ .cxx .cc .C .cpp C++ source code (requires LLVM extensions)
\\ .m Objective-C source code (requires LLVM extensions)
\\ .bc LLVM IR Module (requires LLVM extensions)
\\
\\General Options:
\\ -h, --help Print this help and exit
@ -301,6 +301,8 @@ const usage_build_generic =
\\ -fno-emit-asm (default) Do not output .s (assembly code)
\\ -femit-llvm-ir[=path] Produce a .ll file with LLVM IR (requires LLVM extensions)
\\ -fno-emit-llvm-ir (default) Do not produce a .ll file with LLVM IR
\\ -femit-llvm-bc[=path] Produce a LLVM module as a .bc file (requires LLVM extensions)
\\ -fno-emit-llvm-bc (default) Do not produce a LLVM module as a .bc file
\\ -femit-h[=path] Generate a C header file (.h)
\\ -fno-emit-h (default) Do not generate a C header file (.h)
\\ -femit-docs[=path] Create a docs/ dir with html documentation
@ -359,15 +361,14 @@ const usage_build_generic =
\\ --single-threaded Code assumes it is only used single-threaded
\\ -ofmt=[mode] Override target object format
\\ elf Executable and Linking Format
\\ c Compile to C source code
\\ c C source code
\\ wasm WebAssembly
\\ pe Portable Executable (Windows)
\\ coff Common Object File Format (Windows)
\\ macho macOS relocatables
\\ spirv Standard, Portable Intermediate Representation V (SPIR-V)
\\ plan9 Plan 9 from Bell Labs object format
\\ hex (planned) Intel IHEX
\\ raw (planned) Dump machine code directly
\\ hex (planned feature) Intel IHEX
\\ raw (planned feature) Dump machine code directly
\\ -dirafter [dir] Add directory to AFTER include search path
\\ -isystem [dir] Add directory to SYSTEM include search path
\\ -I[dir] Add directory to include search path
@ -384,8 +385,8 @@ const usage_build_generic =
\\ --dynamic-linker [path] Set the dynamic interpreter path (usually ld.so)
\\ --sysroot [path] Set the system root directory (usually /)
\\ --version [ver] Dynamic library semver
\\ -fsoname[=name] (Linux) Override the default SONAME value
\\ -fno-soname (Linux) Disable emitting a SONAME
\\ -fsoname[=name] Override the default SONAME value
\\ -fno-soname Disable emitting a SONAME
\\ -fLLD Force using LLD as the linker
\\ -fno-LLD Prevent using LLD as the linker
\\ -fcompiler-rt Always include compiler-rt symbols in output
@ -552,6 +553,7 @@ fn buildOutputType(
var emit_bin: EmitBin = .yes_default_path;
var emit_asm: Emit = .no;
var emit_llvm_ir: Emit = .no;
var emit_llvm_bc: Emit = .no;
var emit_docs: Emit = .no;
var emit_analysis: Emit = .no;
var target_arch_os_abi: []const u8 = "native";
@ -1011,6 +1013,12 @@ fn buildOutputType(
emit_llvm_ir = .{ .yes = arg["-femit-llvm-ir=".len..] };
} else if (mem.eql(u8, arg, "-fno-emit-llvm-ir")) {
emit_llvm_ir = .no;
} else if (mem.eql(u8, arg, "-femit-llvm-bc")) {
emit_llvm_bc = .yes_default_path;
} else if (mem.startsWith(u8, arg, "-femit-llvm-bc=")) {
emit_llvm_bc = .{ .yes = arg["-femit-llvm-bc=".len..] };
} else if (mem.eql(u8, arg, "-fno-emit-llvm-bc")) {
emit_llvm_bc = .no;
} else if (mem.eql(u8, arg, "-femit-docs")) {
emit_docs = .yes_default_path;
} else if (mem.startsWith(u8, arg, "-femit-docs=")) {
@ -1708,8 +1716,6 @@ fn buildOutputType(
break :blk .c;
} else if (mem.eql(u8, ofmt, "coff")) {
break :blk .coff;
} else if (mem.eql(u8, ofmt, "pe")) {
break :blk .pe;
} else if (mem.eql(u8, ofmt, "macho")) {
break :blk .macho;
} else if (mem.eql(u8, ofmt, "wasm")) {
@ -1753,7 +1759,7 @@ fn buildOutputType(
};
const a_out_basename = switch (object_format) {
.pe, .coff => "a.exe",
.coff => "a.exe",
else => "a.out",
};
@ -1818,10 +1824,10 @@ fn buildOutputType(
var emit_h_resolved = emit_h.resolve(default_h_basename) catch |err| {
switch (emit_h) {
.yes => {
fatal("unable to open directory from argument 'femit-h', '{s}': {s}", .{ emit_h.yes, @errorName(err) });
fatal("unable to open directory from argument '-femit-h', '{s}': {s}", .{ emit_h.yes, @errorName(err) });
},
.yes_default_path => {
fatal("unable to open directory from arguments 'name' or 'soname', '{s}': {s}", .{ default_h_basename, @errorName(err) });
fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_h_basename, @errorName(err) });
},
.no => unreachable,
}
@ -1832,10 +1838,10 @@ fn buildOutputType(
var emit_asm_resolved = emit_asm.resolve(default_asm_basename) catch |err| {
switch (emit_asm) {
.yes => {
fatal("unable to open directory from argument 'femit-asm', '{s}': {s}", .{ emit_asm.yes, @errorName(err) });
fatal("unable to open directory from argument '-femit-asm', '{s}': {s}", .{ emit_asm.yes, @errorName(err) });
},
.yes_default_path => {
fatal("unable to open directory from arguments 'name' or 'soname', '{s}': {s}", .{ default_asm_basename, @errorName(err) });
fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_asm_basename, @errorName(err) });
},
.no => unreachable,
}
@ -1846,16 +1852,30 @@ fn buildOutputType(
var emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename) catch |err| {
switch (emit_llvm_ir) {
.yes => {
fatal("unable to open directory from argument 'femit-llvm-ir', '{s}': {s}", .{ emit_llvm_ir.yes, @errorName(err) });
fatal("unable to open directory from argument '-femit-llvm-ir', '{s}': {s}", .{ emit_llvm_ir.yes, @errorName(err) });
},
.yes_default_path => {
fatal("unable to open directory from arguments 'name' or 'soname', '{s}': {s}", .{ default_llvm_ir_basename, @errorName(err) });
fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_llvm_ir_basename, @errorName(err) });
},
.no => unreachable,
}
};
defer emit_llvm_ir_resolved.deinit();
const default_llvm_bc_basename = try std.fmt.allocPrint(arena, "{s}.bc", .{root_name});
var emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename) catch |err| {
switch (emit_llvm_bc) {
.yes => {
fatal("unable to open directory from argument '-femit-llvm-bc', '{s}': {s}", .{ emit_llvm_bc.yes, @errorName(err) });
},
.yes_default_path => {
fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_llvm_bc_basename, @errorName(err) });
},
.no => unreachable,
}
};
defer emit_llvm_bc_resolved.deinit();
const default_analysis_basename = try std.fmt.allocPrint(arena, "{s}-analysis.json", .{root_name});
var emit_analysis_resolved = emit_analysis.resolve(default_analysis_basename) catch |err| {
switch (emit_analysis) {
@ -1991,6 +2011,7 @@ fn buildOutputType(
.emit_h = emit_h_resolved.data,
.emit_asm = emit_asm_resolved.data,
.emit_llvm_ir = emit_llvm_ir_resolved.data,
.emit_llvm_bc = emit_llvm_bc_resolved.data,
.emit_docs = emit_docs_resolved.data,
.emit_analysis = emit_analysis_resolved.data,
.link_mode = link_mode,
@ -2396,11 +2417,8 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !voi
// If a .pdb file is part of the expected output, we must also copy
// it into place here.
const coff_or_pe = switch (comp.bin_file.options.object_format) {
.coff, .pe => true,
else => false,
};
const have_pdb = coff_or_pe and !comp.bin_file.options.strip;
const is_coff = comp.bin_file.options.object_format == .coff;
const have_pdb = is_coff and !comp.bin_file.options.strip;
if (have_pdb) {
// Replace `.out` or `.exe` with `.pdb` on both the source and destination
const src_bin_ext = fs.path.extension(bin_sub_path);

View File

@ -21,7 +21,6 @@ comptime {
assert(build_options.is_stage1);
assert(build_options.have_llvm);
if (!builtin.is_test) {
_ = @import("compiler_rt");
@export(main, .{ .name = "main" });
}
}
@ -95,6 +94,8 @@ pub const Module = extern struct {
emit_asm_len: usize,
emit_llvm_ir_ptr: [*]const u8,
emit_llvm_ir_len: usize,
emit_bitcode_ptr: [*]const u8,
emit_bitcode_len: usize,
emit_analysis_json_ptr: [*]const u8,
emit_analysis_json_len: usize,
emit_docs_ptr: [*]const u8,
@ -124,6 +125,7 @@ pub const Module = extern struct {
valgrind_enabled: bool,
tsan_enabled: bool,
function_sections: bool,
include_compiler_rt: bool,
enable_stack_probing: bool,
red_zone: bool,
enable_time_report: bool,

View File

@ -2090,6 +2090,7 @@ struct CodeGen {
Buf h_file_output_path;
Buf asm_file_output_path;
Buf llvm_ir_file_output_path;
Buf bitcode_file_output_path;
Buf analysis_json_output_path;
Buf docs_output_path;
@ -2149,6 +2150,7 @@ struct CodeGen {
bool have_stack_probing;
bool red_zone;
bool function_sections;
bool include_compiler_rt;
bool test_is_evented;
bool valgrind_enabled;
bool tsan_enabled;

View File

@ -8506,19 +8506,22 @@ static void zig_llvm_emit_output(CodeGen *g) {
const char *asm_filename = nullptr;
const char *bin_filename = nullptr;
const char *llvm_ir_filename = nullptr;
const char *bitcode_filename = nullptr;
if (buf_len(&g->o_file_output_path) != 0) bin_filename = buf_ptr(&g->o_file_output_path);
if (buf_len(&g->asm_file_output_path) != 0) asm_filename = buf_ptr(&g->asm_file_output_path);
if (buf_len(&g->llvm_ir_file_output_path) != 0) llvm_ir_filename = buf_ptr(&g->llvm_ir_file_output_path);
if (buf_len(&g->bitcode_file_output_path) != 0) bitcode_filename = buf_ptr(&g->bitcode_file_output_path);
// Unfortunately, LLVM shits the bed when we ask for both binary and assembly. So we call the entire
// pipeline multiple times if this is requested.
// Unfortunately, LLVM shits the bed when we ask for both binary and assembly.
// So we call the entire pipeline multiple times if this is requested.
if (asm_filename != nullptr && bin_filename != nullptr) {
if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, &err_msg,
g->build_mode == BuildModeDebug, is_small, g->enable_time_report, g->tsan_enabled,
g->have_lto, nullptr, bin_filename, llvm_ir_filename))
g->have_lto, nullptr, bin_filename, llvm_ir_filename, nullptr))
{
fprintf(stderr, "LLVM failed to emit file: %s\n", err_msg);
fprintf(stderr, "LLVM failed to emit bin=%s, ir=%s: %s\n",
bin_filename, llvm_ir_filename, err_msg);
exit(1);
}
bin_filename = nullptr;
@ -8527,9 +8530,11 @@ static void zig_llvm_emit_output(CodeGen *g) {
if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, &err_msg,
g->build_mode == BuildModeDebug, is_small, g->enable_time_report, g->tsan_enabled,
g->have_lto, asm_filename, bin_filename, llvm_ir_filename))
g->have_lto, asm_filename, bin_filename, llvm_ir_filename, bitcode_filename))
{
fprintf(stderr, "LLVM failed to emit file: %s\n", err_msg);
fprintf(stderr, "LLVM failed to emit asm=%s, bin=%s, ir=%s, bc=%s: %s\n",
asm_filename, bin_filename, llvm_ir_filename, bitcode_filename,
err_msg);
exit(1);
}
@ -9537,6 +9542,22 @@ static void gen_root_source(CodeGen *g) {
g->panic_fn = panic_fn_val->data.x_ptr.data.fn.fn_entry;
assert(g->panic_fn != nullptr);
if (g->include_compiler_rt) {
Buf *import_target_path;
Buf full_path = BUF_INIT;
ZigType *compiler_rt_import;
if ((err = analyze_import(g, std_import, buf_create_from_str("./special/compiler_rt.zig"),
&compiler_rt_import, &import_target_path, &full_path)))
{
if (err == ErrorFileNotFound) {
fprintf(stderr, "unable to find '%s'", buf_ptr(import_target_path));
} else {
fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(&full_path), err_str(err));
}
exit(1);
}
}
if (!g->error_during_imports) {
semantic_analyze(g);
}

View File

@ -73,6 +73,7 @@ void zig_stage1_build_object(struct ZigStage1 *stage1) {
buf_init_from_mem(&g->h_file_output_path, stage1->emit_h_ptr, stage1->emit_h_len);
buf_init_from_mem(&g->asm_file_output_path, stage1->emit_asm_ptr, stage1->emit_asm_len);
buf_init_from_mem(&g->llvm_ir_file_output_path, stage1->emit_llvm_ir_ptr, stage1->emit_llvm_ir_len);
buf_init_from_mem(&g->bitcode_file_output_path, stage1->emit_bitcode_ptr, stage1->emit_bitcode_len);
buf_init_from_mem(&g->analysis_json_output_path, stage1->emit_analysis_json_ptr, stage1->emit_analysis_json_len);
buf_init_from_mem(&g->docs_output_path, stage1->emit_docs_ptr, stage1->emit_docs_len);
@ -100,6 +101,7 @@ void zig_stage1_build_object(struct ZigStage1 *stage1) {
g->link_libc = stage1->link_libc;
g->link_libcpp = stage1->link_libcpp;
g->function_sections = stage1->function_sections;
g->include_compiler_rt = stage1->include_compiler_rt;
g->subsystem = stage1->subsystem;

View File

@ -157,6 +157,9 @@ struct ZigStage1 {
const char *emit_llvm_ir_ptr;
size_t emit_llvm_ir_len;
const char *emit_bitcode_ptr;
size_t emit_bitcode_len;
const char *emit_analysis_json_ptr;
size_t emit_analysis_json_len;
@ -193,6 +196,7 @@ struct ZigStage1 {
bool valgrind_enabled;
bool tsan_enabled;
bool function_sections;
bool include_compiler_rt;
bool enable_stack_probing;
bool red_zone;
bool enable_time_report;

View File

@ -39,6 +39,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
" --color [auto|off|on] enable or disable colored error messages\n"
" --name [name] override output name\n"
" -femit-bin=[path] Output machine code\n"
" -fcompiler-rt Always include compiler-rt symbols in output\n"
" --pkg-begin [name] [path] make pkg available to import and push current pkg\n"
" --pkg-end pop current pkg\n"
" -ODebug build with optimizations off and safety on\n"
@ -266,6 +267,7 @@ int main(int argc, char **argv) {
const char *mcpu = nullptr;
bool single_threaded = false;
bool is_test_build = false;
bool include_compiler_rt = false;
for (int i = 1; i < argc; i += 1) {
char *arg = argv[i];
@ -334,6 +336,8 @@ int main(int argc, char **argv) {
mcpu = arg + strlen("-mcpu=");
} else if (str_starts_with(arg, "-femit-bin=")) {
emit_bin_path = arg + strlen("-femit-bin=");
} else if (strcmp(arg, "-fcompiler-rt") == 0) {
include_compiler_rt = true;
} else if (i + 1 >= argc) {
fprintf(stderr, "Expected another argument after %s\n", arg);
return print_error_usage(arg0);
@ -468,6 +472,7 @@ int main(int argc, char **argv) {
stage1->subsystem = subsystem;
stage1->pic = true;
stage1->is_single_threaded = single_threaded;
stage1->include_compiler_rt = include_compiler_rt;
zig_stage1_build_object(stage1);

View File

@ -229,12 +229,14 @@ struct TimeTracerRAII {
bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref,
char **error_message, bool is_debug,
bool is_small, bool time_report, bool tsan, bool lto,
const char *asm_filename, const char *bin_filename, const char *llvm_ir_filename)
const char *asm_filename, const char *bin_filename,
const char *llvm_ir_filename, const char *bitcode_filename)
{
TimePassesIsEnabled = time_report;
raw_fd_ostream *dest_asm_ptr = nullptr;
raw_fd_ostream *dest_bin_ptr = nullptr;
raw_fd_ostream *dest_bitcode_ptr = nullptr;
if (asm_filename) {
std::error_code EC;
@ -252,9 +254,19 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM
return true;
}
}
if (bitcode_filename) {
std::error_code EC;
dest_bitcode_ptr = new(std::nothrow) raw_fd_ostream(bitcode_filename, EC, sys::fs::F_None);
if (EC) {
*error_message = strdup((const char *)StringRef(EC.message()).bytes_begin());
return true;
}
}
std::unique_ptr<raw_fd_ostream> dest_asm(dest_asm_ptr),
dest_bin(dest_bin_ptr);
dest_bin(dest_bin_ptr),
dest_bitcode(dest_bitcode_ptr);
auto PID = sys::Process::getProcessId();
std::string ProcName = "zig-";
@ -389,6 +401,9 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM
if (dest_bin && lto) {
WriteBitcodeToFile(module, *dest_bin);
}
if (dest_bitcode) {
WriteBitcodeToFile(module, *dest_bitcode);
}
if (time_report) {
TimerGroup::printAll(errs());

View File

@ -49,7 +49,8 @@ ZIG_EXTERN_C char *ZigLLVMGetNativeFeatures(void);
ZIG_EXTERN_C bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref,
char **error_message, bool is_debug,
bool is_small, bool time_report, bool tsan, bool lto,
const char *asm_filename, const char *bin_filename, const char *llvm_ir_filename);
const char *asm_filename, const char *bin_filename,
const char *llvm_ir_filename, const char *bitcode_filename);
enum ZigLLVMABIType {