mirror of
https://github.com/ziglang/zig.git
synced 2026-01-26 09:15:24 +00:00
Merge pull request #7598 from FireFox317/more-llvm-stage2
stage2: More improvements to self-hosted LLVM backend
This commit is contained in:
commit
f6644255f5
200
build.zig
200
build.zig
@ -91,6 +91,7 @@ pub fn build(b: *Builder) !void {
|
||||
exe.addBuildOption(bool, "have_llvm", enable_llvm);
|
||||
if (enable_llvm) {
|
||||
const cmake_cfg = if (static_llvm) null else findAndParseConfigH(b, config_h_path_option);
|
||||
|
||||
if (is_stage1) {
|
||||
exe.addIncludeDir("src");
|
||||
exe.addIncludeDir("deps/SoftFloat-3e/source/include");
|
||||
@ -109,28 +110,8 @@ pub fn build(b: *Builder) !void {
|
||||
softfloat.addCSourceFiles(&softfloat_sources, &[_][]const u8{ "-std=c99", "-O3" });
|
||||
exe.linkLibrary(softfloat);
|
||||
|
||||
const exe_cflags = [_][]const u8{
|
||||
"-std=c++14",
|
||||
"-D__STDC_CONSTANT_MACROS",
|
||||
"-D__STDC_FORMAT_MACROS",
|
||||
"-D__STDC_LIMIT_MACROS",
|
||||
"-D_GNU_SOURCE",
|
||||
"-fvisibility-inlines-hidden",
|
||||
"-fno-exceptions",
|
||||
"-fno-rtti",
|
||||
"-Werror=type-limits",
|
||||
"-Wno-missing-braces",
|
||||
"-Wno-comment",
|
||||
};
|
||||
exe.addCSourceFiles(&stage1_sources, &exe_cflags);
|
||||
exe.addCSourceFiles(&optimized_c_sources, &[_][]const u8{ "-std=c99", "-O3" });
|
||||
if (cmake_cfg == null) {
|
||||
// We need this because otherwise zig_clang_cc1_main.cpp ends up pulling
|
||||
// in a dependency on llvm::cfg::Update<llvm::BasicBlock*>::dump() which is
|
||||
// unavailable when LLVM is compiled in Release mode.
|
||||
const zig_cpp_cflags = exe_cflags ++ [_][]const u8{"-DNDEBUG=1"};
|
||||
exe.addCSourceFiles(&zig_cpp_sources, &zig_cpp_cflags);
|
||||
}
|
||||
}
|
||||
if (cmake_cfg) |cfg| {
|
||||
// Inside this code path, we have to coordinate with system packaged LLVM, Clang, and LLD.
|
||||
@ -139,79 +120,14 @@ pub fn build(b: *Builder) !void {
|
||||
if (cfg.cmake_prefix_path.len > 0) {
|
||||
b.addSearchPrefix(cfg.cmake_prefix_path);
|
||||
}
|
||||
exe.addObjectFile(fs.path.join(b.allocator, &[_][]const u8{
|
||||
cfg.cmake_binary_dir,
|
||||
"zigcpp",
|
||||
b.fmt("{s}{s}{s}", .{ exe.target.libPrefix(), "zigcpp", exe.target.staticLibSuffix() }),
|
||||
}) catch unreachable);
|
||||
assert(cfg.lld_include_dir.len != 0);
|
||||
exe.addIncludeDir(cfg.lld_include_dir);
|
||||
addCMakeLibraryList(exe, cfg.clang_libraries);
|
||||
addCMakeLibraryList(exe, cfg.lld_libraries);
|
||||
addCMakeLibraryList(exe, cfg.llvm_libraries);
|
||||
|
||||
const need_cpp_includes = tracy != null;
|
||||
|
||||
// System -lc++ must be used because in this code path we are attempting to link
|
||||
// against system-provided LLVM, Clang, LLD.
|
||||
if (exe.target.getOsTag() == .linux) {
|
||||
// First we try to static link against gcc libstdc++. If that doesn't work,
|
||||
// we fall back to -lc++ and cross our fingers.
|
||||
addCxxKnownPath(b, cfg, exe, "libstdc++.a", "", need_cpp_includes) catch |err| switch (err) {
|
||||
error.RequiredLibraryNotFound => {
|
||||
exe.linkSystemLibrary("c++");
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
exe.linkSystemLibrary("pthread");
|
||||
} else if (exe.target.isFreeBSD()) {
|
||||
try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes);
|
||||
exe.linkSystemLibrary("pthread");
|
||||
} else if (exe.target.getOsTag() == .openbsd) {
|
||||
try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes);
|
||||
try addCxxKnownPath(b, cfg, exe, "libc++abi.a", null, need_cpp_includes);
|
||||
} else if (exe.target.isDarwin()) {
|
||||
if (addCxxKnownPath(b, cfg, exe, "libgcc_eh.a", "", need_cpp_includes)) {
|
||||
// Compiler is GCC.
|
||||
try addCxxKnownPath(b, cfg, exe, "libstdc++.a", null, need_cpp_includes);
|
||||
exe.linkSystemLibrary("pthread");
|
||||
// TODO LLD cannot perform this link.
|
||||
// Set ZIG_SYSTEM_LINKER_HACK env var to use system linker ld instead.
|
||||
// See https://github.com/ziglang/zig/issues/1535
|
||||
} else |err| switch (err) {
|
||||
error.RequiredLibraryNotFound => {
|
||||
// System compiler, not gcc.
|
||||
exe.linkSystemLibrary("c++");
|
||||
},
|
||||
else => |e| return e,
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg.dia_guids_lib.len != 0) {
|
||||
exe.addObjectFile(cfg.dia_guids_lib);
|
||||
}
|
||||
try addCmakeCfgOptionsToExe(b, cfg, tracy, exe);
|
||||
try addCmakeCfgOptionsToExe(b, cfg, tracy, test_stage2);
|
||||
} else {
|
||||
// Here we are -Denable-llvm but no cmake integration.
|
||||
for (clang_libs) |lib_name| {
|
||||
exe.linkSystemLibrary(lib_name);
|
||||
}
|
||||
|
||||
for (lld_libs) |lib_name| {
|
||||
exe.linkSystemLibrary(lib_name);
|
||||
}
|
||||
|
||||
for (llvm_libs) |lib_name| {
|
||||
exe.linkSystemLibrary(lib_name);
|
||||
}
|
||||
|
||||
// This means we rely on clang-or-zig-built LLVM, Clang, LLD libraries.
|
||||
exe.linkSystemLibrary("c++");
|
||||
|
||||
if (target.getOs().tag == .windows) {
|
||||
exe.linkSystemLibrary("version");
|
||||
exe.linkSystemLibrary("uuid");
|
||||
}
|
||||
try addStaticLlvmOptionsToExe(exe);
|
||||
try addStaticLlvmOptionsToExe(test_stage2);
|
||||
}
|
||||
}
|
||||
if (link_libc) {
|
||||
@ -357,6 +273,112 @@ pub fn build(b: *Builder) !void {
|
||||
test_step.dependOn(docs_step);
|
||||
}
|
||||
|
||||
const exe_cflags = [_][]const u8{
|
||||
"-std=c++14",
|
||||
"-D__STDC_CONSTANT_MACROS",
|
||||
"-D__STDC_FORMAT_MACROS",
|
||||
"-D__STDC_LIMIT_MACROS",
|
||||
"-D_GNU_SOURCE",
|
||||
"-fvisibility-inlines-hidden",
|
||||
"-fno-exceptions",
|
||||
"-fno-rtti",
|
||||
"-Werror=type-limits",
|
||||
"-Wno-missing-braces",
|
||||
"-Wno-comment",
|
||||
};
|
||||
|
||||
fn addCmakeCfgOptionsToExe(
|
||||
b: *Builder,
|
||||
cfg: CMakeConfig,
|
||||
tracy: ?[]const u8,
|
||||
exe: *std.build.LibExeObjStep,
|
||||
) !void {
|
||||
exe.addObjectFile(fs.path.join(b.allocator, &[_][]const u8{
|
||||
cfg.cmake_binary_dir,
|
||||
"zigcpp",
|
||||
b.fmt("{s}{s}{s}", .{ exe.target.libPrefix(), "zigcpp", exe.target.staticLibSuffix() }),
|
||||
}) catch unreachable);
|
||||
assert(cfg.lld_include_dir.len != 0);
|
||||
exe.addIncludeDir(cfg.lld_include_dir);
|
||||
addCMakeLibraryList(exe, cfg.clang_libraries);
|
||||
addCMakeLibraryList(exe, cfg.lld_libraries);
|
||||
addCMakeLibraryList(exe, cfg.llvm_libraries);
|
||||
|
||||
const need_cpp_includes = tracy != null;
|
||||
|
||||
// System -lc++ must be used because in this code path we are attempting to link
|
||||
// against system-provided LLVM, Clang, LLD.
|
||||
if (exe.target.getOsTag() == .linux) {
|
||||
// First we try to static link against gcc libstdc++. If that doesn't work,
|
||||
// we fall back to -lc++ and cross our fingers.
|
||||
addCxxKnownPath(b, cfg, exe, "libstdc++.a", "", need_cpp_includes) catch |err| switch (err) {
|
||||
error.RequiredLibraryNotFound => {
|
||||
exe.linkSystemLibrary("c++");
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
exe.linkSystemLibrary("pthread");
|
||||
} else if (exe.target.isFreeBSD()) {
|
||||
try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes);
|
||||
exe.linkSystemLibrary("pthread");
|
||||
} else if (exe.target.getOsTag() == .openbsd) {
|
||||
try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes);
|
||||
try addCxxKnownPath(b, cfg, exe, "libc++abi.a", null, need_cpp_includes);
|
||||
} else if (exe.target.isDarwin()) {
|
||||
if (addCxxKnownPath(b, cfg, exe, "libgcc_eh.a", "", need_cpp_includes)) {
|
||||
// Compiler is GCC.
|
||||
try addCxxKnownPath(b, cfg, exe, "libstdc++.a", null, need_cpp_includes);
|
||||
exe.linkSystemLibrary("pthread");
|
||||
// TODO LLD cannot perform this link.
|
||||
// Set ZIG_SYSTEM_LINKER_HACK env var to use system linker ld instead.
|
||||
// See https://github.com/ziglang/zig/issues/1535
|
||||
} else |err| switch (err) {
|
||||
error.RequiredLibraryNotFound => {
|
||||
// System compiler, not gcc.
|
||||
exe.linkSystemLibrary("c++");
|
||||
},
|
||||
else => |e| return e,
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg.dia_guids_lib.len != 0) {
|
||||
exe.addObjectFile(cfg.dia_guids_lib);
|
||||
}
|
||||
}
|
||||
|
||||
fn addStaticLlvmOptionsToExe(
|
||||
exe: *std.build.LibExeObjStep,
|
||||
) !void {
|
||||
// Adds the Zig C++ sources which both stage1 and stage2 need.
|
||||
//
|
||||
// We need this because otherwise zig_clang_cc1_main.cpp ends up pulling
|
||||
// in a dependency on llvm::cfg::Update<llvm::BasicBlock*>::dump() which is
|
||||
// unavailable when LLVM is compiled in Release mode.
|
||||
const zig_cpp_cflags = exe_cflags ++ [_][]const u8{"-DNDEBUG=1"};
|
||||
exe.addCSourceFiles(&zig_cpp_sources, &zig_cpp_cflags);
|
||||
|
||||
for (clang_libs) |lib_name| {
|
||||
exe.linkSystemLibrary(lib_name);
|
||||
}
|
||||
|
||||
for (lld_libs) |lib_name| {
|
||||
exe.linkSystemLibrary(lib_name);
|
||||
}
|
||||
|
||||
for (llvm_libs) |lib_name| {
|
||||
exe.linkSystemLibrary(lib_name);
|
||||
}
|
||||
|
||||
// This means we rely on clang-or-zig-built LLVM, Clang, LLD libraries.
|
||||
exe.linkSystemLibrary("c++");
|
||||
|
||||
if (exe.target.getOs().tag == .windows) {
|
||||
exe.linkSystemLibrary("version");
|
||||
exe.linkSystemLibrary("uuid");
|
||||
}
|
||||
}
|
||||
|
||||
fn addCxxKnownPath(
|
||||
b: *Builder,
|
||||
ctx: CMakeConfig,
|
||||
|
||||
@ -16,7 +16,8 @@ const Allocator = std.mem.Allocator;
|
||||
/// This protection is conditionally compiled depending on `want_debug_deadlock`.
|
||||
var all_cache_digest_set: std.AutoHashMapUnmanaged(BinDigest, void) = .{};
|
||||
var all_cache_digest_lock: std.Mutex = .{};
|
||||
const want_debug_deadlock = std.debug.runtime_safety;
|
||||
// TODO: Figure out how to make sure that `all_cache_digest_set` does not leak memory!
|
||||
pub const want_debug_deadlock = false;
|
||||
const DebugBinDigest = if (want_debug_deadlock) BinDigest else void;
|
||||
const null_debug_bin_digest = if (want_debug_deadlock) ([1]u8{0} ** bin_digest_len) else {};
|
||||
|
||||
|
||||
@ -1173,6 +1173,7 @@ pub fn destroy(self: *Compilation) void {
|
||||
|
||||
const gpa = self.gpa;
|
||||
self.work_queue.deinit();
|
||||
self.c_object_work_queue.deinit();
|
||||
|
||||
{
|
||||
var it = self.crt_files.iterator();
|
||||
@ -1202,6 +1203,10 @@ pub fn destroy(self: *Compilation) void {
|
||||
crt_file.deinit(gpa);
|
||||
}
|
||||
|
||||
if (self.glibc_so_files) |*glibc_file| {
|
||||
glibc_file.deinit(gpa);
|
||||
}
|
||||
|
||||
for (self.c_object_table.items()) |entry| {
|
||||
entry.key.destroy(gpa);
|
||||
}
|
||||
|
||||
@ -927,7 +927,7 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void {
|
||||
try out.writeAll("\n");
|
||||
try printSection(out, "<<<< input", input);
|
||||
try printSection(out, "==== expect", expect);
|
||||
try printSection(out, ">>>> got", got);
|
||||
try printSection(out, ">>>> got", buffer.items);
|
||||
try printRuler(out);
|
||||
|
||||
testing.expect(false);
|
||||
|
||||
@ -811,8 +811,11 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void {
|
||||
// If there is no Zig code to compile, then we should skip flushing the output file because it
|
||||
// will not be part of the linker line anyway.
|
||||
const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: {
|
||||
const use_stage1 = build_options.is_stage1 and self.base.options.use_llvm;
|
||||
if (use_stage1) {
|
||||
// Both stage1 and stage2 LLVM backend put the object file in the cache directory.
|
||||
if (self.base.options.use_llvm) {
|
||||
// Stage2 has to call flushModule since that outputs the LLVM object file.
|
||||
if (!build_options.is_stage1) try self.flushModule(comp);
|
||||
|
||||
const obj_basename = try std.zig.binNameAlloc(arena, .{
|
||||
.root_name = self.base.options.root_name,
|
||||
.target = self.base.options.target,
|
||||
|
||||
@ -1251,8 +1251,11 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
|
||||
// If there is no Zig code to compile, then we should skip flushing the output file because it
|
||||
// will not be part of the linker line anyway.
|
||||
const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: {
|
||||
const use_stage1 = build_options.is_stage1 and self.base.options.use_llvm;
|
||||
if (use_stage1) {
|
||||
// Both stage1 and stage2 LLVM backend put the object file in the cache directory.
|
||||
if (self.base.options.use_llvm) {
|
||||
// Stage2 has to call flushModule since that outputs the LLVM object file.
|
||||
if (!build_options.is_stage1) try self.flushModule(comp);
|
||||
|
||||
const obj_basename = try std.zig.binNameAlloc(arena, .{
|
||||
.root_name = self.base.options.root_name,
|
||||
.target = self.base.options.target,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Compilation = @import("Compilation.zig");
|
||||
const llvm = @import("llvm_bindings.zig");
|
||||
@ -141,17 +142,36 @@ pub const LLVMIRModule = struct {
|
||||
target_machine: *const llvm.TargetMachineRef,
|
||||
builder: *const llvm.BuilderRef,
|
||||
|
||||
output_path: []const u8,
|
||||
object_path: []const u8,
|
||||
|
||||
gpa: *Allocator,
|
||||
err_msg: ?*Compilation.ErrorMsg = null,
|
||||
|
||||
/// This stores the LLVM values used in a function, such that they can be
|
||||
/// referred to in other instructions. This table is cleared before every function is generated.
|
||||
func_inst_table: std.AutoHashMapUnmanaged(*Inst, *const llvm.ValueRef) = .{},
|
||||
|
||||
/// These fields are used to refer to the LLVM value of the function paramaters in an Arg instruction.
|
||||
args: []*const llvm.ValueRef = &[_]*const llvm.ValueRef{},
|
||||
arg_index: usize = 0,
|
||||
|
||||
pub fn create(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*LLVMIRModule {
|
||||
const self = try allocator.create(LLVMIRModule);
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
const gpa = options.module.?.gpa;
|
||||
|
||||
const obj_basename = try std.zig.binNameAlloc(gpa, .{
|
||||
.root_name = options.root_name,
|
||||
.target = options.target,
|
||||
.output_mode = .Obj,
|
||||
});
|
||||
defer gpa.free(obj_basename);
|
||||
|
||||
const o_directory = options.module.?.zig_cache_artifact_directory;
|
||||
const object_path = try o_directory.join(gpa, &[_][]const u8{obj_basename});
|
||||
errdefer gpa.free(object_path);
|
||||
|
||||
initializeLLVMTargets();
|
||||
|
||||
const root_nameZ = try gpa.dupeZ(u8, options.root_name);
|
||||
@ -203,7 +223,7 @@ pub const LLVMIRModule = struct {
|
||||
.llvm_module = llvm_module,
|
||||
.target_machine = target_machine,
|
||||
.builder = builder,
|
||||
.output_path = sub_path,
|
||||
.object_path = object_path,
|
||||
.gpa = gpa,
|
||||
};
|
||||
return self;
|
||||
@ -213,6 +233,10 @@ pub const LLVMIRModule = struct {
|
||||
self.builder.disposeBuilder();
|
||||
self.target_machine.disposeTargetMachine();
|
||||
self.llvm_module.disposeModule();
|
||||
|
||||
self.func_inst_table.deinit(self.gpa);
|
||||
self.gpa.free(self.object_path);
|
||||
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
@ -245,15 +269,13 @@ pub const LLVMIRModule = struct {
|
||||
}
|
||||
}
|
||||
|
||||
const output_pathZ = try self.gpa.dupeZ(u8, self.output_path);
|
||||
defer self.gpa.free(output_pathZ);
|
||||
const object_pathZ = try self.gpa.dupeZ(u8, self.object_path);
|
||||
defer self.gpa.free(object_pathZ);
|
||||
|
||||
var error_message: [*:0]const u8 = undefined;
|
||||
// TODO: where to put the output object, zig-cache something?
|
||||
// TODO: caching?
|
||||
if (self.target_machine.emitToFile(
|
||||
self.llvm_module,
|
||||
output_pathZ.ptr,
|
||||
object_pathZ.ptr,
|
||||
.ObjectFile,
|
||||
&error_message,
|
||||
)) {
|
||||
@ -271,6 +293,7 @@ pub const LLVMIRModule = struct {
|
||||
error.CodegenFail => {
|
||||
decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, decl, self.err_msg.?);
|
||||
self.err_msg = null;
|
||||
return;
|
||||
},
|
||||
else => |e| return e,
|
||||
@ -278,47 +301,71 @@ pub const LLVMIRModule = struct {
|
||||
}
|
||||
|
||||
fn gen(self: *LLVMIRModule, module: *Module, typed_value: TypedValue, src: usize) !void {
|
||||
switch (typed_value.ty.zigTypeTag()) {
|
||||
.Fn => {
|
||||
const func = typed_value.val.castTag(.function).?.data;
|
||||
if (typed_value.val.castTag(.function)) |func_inst| {
|
||||
const func = func_inst.data;
|
||||
|
||||
const llvm_func = try self.resolveLLVMFunction(func);
|
||||
const llvm_func = try self.resolveLLVMFunction(func, src);
|
||||
|
||||
// We remove all the basic blocks of a function to support incremental
|
||||
// compilation!
|
||||
// TODO: remove all basic blocks if functions can have more than one
|
||||
if (llvm_func.getFirstBasicBlock()) |bb| {
|
||||
bb.deleteBasicBlock();
|
||||
}
|
||||
// This gets the LLVM values from the function and stores them in `self.args`.
|
||||
const fn_param_len = func.owner_decl.typed_value.most_recent.typed_value.ty.fnParamLen();
|
||||
var args = try self.gpa.alloc(*const llvm.ValueRef, fn_param_len);
|
||||
defer self.gpa.free(args);
|
||||
|
||||
const entry_block = llvm_func.appendBasicBlock("Entry");
|
||||
self.builder.positionBuilderAtEnd(entry_block);
|
||||
for (args) |*arg, i| {
|
||||
arg.* = llvm.getParam(llvm_func, @intCast(c_uint, i));
|
||||
}
|
||||
self.args = args;
|
||||
self.arg_index = 0;
|
||||
|
||||
const instructions = func.body.instructions;
|
||||
for (instructions) |inst| {
|
||||
switch (inst.tag) {
|
||||
.breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?),
|
||||
.call => try self.genCall(inst.castTag(.call).?),
|
||||
.unreach => self.genUnreach(inst.castTag(.unreach).?),
|
||||
.retvoid => self.genRetVoid(inst.castTag(.retvoid).?),
|
||||
.arg => self.genArg(inst.castTag(.arg).?),
|
||||
.dbg_stmt => {
|
||||
// TODO: implement debug info
|
||||
},
|
||||
else => |tag| return self.fail(src, "TODO implement LLVM codegen for Zir instruction: {}", .{tag}),
|
||||
}
|
||||
}
|
||||
},
|
||||
else => |ty| return self.fail(src, "TODO implement LLVM codegen for top-level decl type: {}", .{ty}),
|
||||
// Make sure no other LLVM values from other functions can be referenced
|
||||
self.func_inst_table.clearRetainingCapacity();
|
||||
|
||||
// We remove all the basic blocks of a function to support incremental
|
||||
// compilation!
|
||||
// TODO: remove all basic blocks if functions can have more than one
|
||||
if (llvm_func.getFirstBasicBlock()) |bb| {
|
||||
bb.deleteBasicBlock();
|
||||
}
|
||||
|
||||
const entry_block = llvm_func.appendBasicBlock("Entry");
|
||||
self.builder.positionBuilderAtEnd(entry_block);
|
||||
|
||||
const instructions = func.body.instructions;
|
||||
for (instructions) |inst| {
|
||||
const opt_llvm_val: ?*const llvm.ValueRef = switch (inst.tag) {
|
||||
.add => try self.genAdd(inst.castTag(.add).?),
|
||||
.alloc => try self.genAlloc(inst.castTag(.alloc).?),
|
||||
.arg => try self.genArg(inst.castTag(.arg).?),
|
||||
.bitcast => try self.genBitCast(inst.castTag(.bitcast).?),
|
||||
.breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?),
|
||||
.call => try self.genCall(inst.castTag(.call).?),
|
||||
.intcast => try self.genIntCast(inst.castTag(.intcast).?),
|
||||
.load => try self.genLoad(inst.castTag(.load).?),
|
||||
.not => try self.genNot(inst.castTag(.not).?),
|
||||
.ret => try self.genRet(inst.castTag(.ret).?),
|
||||
.retvoid => self.genRetVoid(inst.castTag(.retvoid).?),
|
||||
.store => try self.genStore(inst.castTag(.store).?),
|
||||
.sub => try self.genSub(inst.castTag(.sub).?),
|
||||
.unreach => self.genUnreach(inst.castTag(.unreach).?),
|
||||
.dbg_stmt => blk: {
|
||||
// TODO: implement debug info
|
||||
break :blk null;
|
||||
},
|
||||
else => |tag| return self.fail(src, "TODO implement LLVM codegen for Zir instruction: {}", .{tag}),
|
||||
};
|
||||
if (opt_llvm_val) |llvm_val| try self.func_inst_table.putNoClobber(self.gpa, inst, llvm_val);
|
||||
}
|
||||
} else {
|
||||
return self.fail(src, "TODO implement LLVM codegen for top-level decl type: {}", .{typed_value.ty});
|
||||
}
|
||||
}
|
||||
|
||||
fn genCall(self: *LLVMIRModule, inst: *Inst.Call) !void {
|
||||
fn genCall(self: *LLVMIRModule, inst: *Inst.Call) !?*const llvm.ValueRef {
|
||||
if (inst.func.value()) |func_value| {
|
||||
if (func_value.castTag(.function)) |func_payload| {
|
||||
const func = func_payload.data;
|
||||
const zig_fn_type = func.owner_decl.typed_value.most_recent.typed_value.ty;
|
||||
const llvm_fn = try self.resolveLLVMFunction(func);
|
||||
const llvm_fn = try self.resolveLLVMFunction(func, inst.base.src);
|
||||
|
||||
const num_args = inst.args.len;
|
||||
|
||||
@ -338,54 +385,165 @@ pub const LLVMIRModule = struct {
|
||||
"",
|
||||
);
|
||||
|
||||
if (zig_fn_type.fnReturnType().zigTypeTag() == .NoReturn) {
|
||||
const return_type = zig_fn_type.fnReturnType();
|
||||
if (return_type.tag() == .noreturn) {
|
||||
_ = self.builder.buildUnreachable();
|
||||
}
|
||||
|
||||
// No need to store the LLVM value if the return type is void or noreturn
|
||||
if (!return_type.hasCodeGenBits()) return null;
|
||||
|
||||
return call;
|
||||
}
|
||||
}
|
||||
return self.fail(inst.base.src, "TODO implement calling runtime known function pointer LLVM backend", .{});
|
||||
}
|
||||
|
||||
fn genRetVoid(self: *LLVMIRModule, inst: *Inst.NoOp) void {
|
||||
fn genRetVoid(self: *LLVMIRModule, inst: *Inst.NoOp) ?*const llvm.ValueRef {
|
||||
_ = self.builder.buildRetVoid();
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genUnreach(self: *LLVMIRModule, inst: *Inst.NoOp) void {
|
||||
fn genRet(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.ValueRef {
|
||||
_ = self.builder.buildRet(try self.resolveInst(inst.operand));
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genNot(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.ValueRef {
|
||||
return self.builder.buildNot(try self.resolveInst(inst.operand), "");
|
||||
}
|
||||
|
||||
fn genUnreach(self: *LLVMIRModule, inst: *Inst.NoOp) ?*const llvm.ValueRef {
|
||||
_ = self.builder.buildUnreachable();
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genArg(self: *LLVMIRModule, inst: *Inst.Arg) void {
|
||||
// TODO: implement this
|
||||
fn genAdd(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.ValueRef {
|
||||
const lhs = try self.resolveInst(inst.lhs);
|
||||
const rhs = try self.resolveInst(inst.rhs);
|
||||
|
||||
if (!inst.base.ty.isInt())
|
||||
return self.fail(inst.base.src, "TODO implement 'genAdd' for type {}", .{inst.base.ty});
|
||||
|
||||
return if (inst.base.ty.isSignedInt())
|
||||
self.builder.buildNSWAdd(lhs, rhs, "")
|
||||
else
|
||||
self.builder.buildNUWAdd(lhs, rhs, "");
|
||||
}
|
||||
|
||||
fn genBreakpoint(self: *LLVMIRModule, inst: *Inst.NoOp) !void {
|
||||
// TODO: Store this function somewhere such that we dont have to add it again
|
||||
const fn_type = llvm.TypeRef.functionType(llvm.voidType(), null, 0, false);
|
||||
const func = self.llvm_module.addFunction("llvm.debugtrap", fn_type);
|
||||
// TODO: add assertion: LLVMGetIntrinsicID
|
||||
_ = self.builder.buildCall(func, null, 0, "");
|
||||
fn genSub(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.ValueRef {
|
||||
const lhs = try self.resolveInst(inst.lhs);
|
||||
const rhs = try self.resolveInst(inst.rhs);
|
||||
|
||||
if (!inst.base.ty.isInt())
|
||||
return self.fail(inst.base.src, "TODO implement 'genSub' for type {}", .{inst.base.ty});
|
||||
|
||||
return if (inst.base.ty.isSignedInt())
|
||||
self.builder.buildNSWSub(lhs, rhs, "")
|
||||
else
|
||||
self.builder.buildNUWSub(lhs, rhs, "");
|
||||
}
|
||||
|
||||
fn genIntCast(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.ValueRef {
|
||||
const val = try self.resolveInst(inst.operand);
|
||||
|
||||
const signed = inst.base.ty.isSignedInt();
|
||||
// TODO: Should we use intcast here or just a simple bitcast?
|
||||
// LLVM does truncation vs bitcast (+signed extension) in the intcast depending on the sizes
|
||||
return self.builder.buildIntCast2(val, try self.getLLVMType(inst.base.ty, inst.base.src), signed, "");
|
||||
}
|
||||
|
||||
fn genBitCast(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.ValueRef {
|
||||
const val = try self.resolveInst(inst.operand);
|
||||
const dest_type = try self.getLLVMType(inst.base.ty, inst.base.src);
|
||||
|
||||
return self.builder.buildBitCast(val, dest_type, "");
|
||||
}
|
||||
|
||||
fn genArg(self: *LLVMIRModule, inst: *Inst.Arg) !?*const llvm.ValueRef {
|
||||
const arg_val = self.args[self.arg_index];
|
||||
self.arg_index += 1;
|
||||
|
||||
const ptr_val = self.builder.buildAlloca(try self.getLLVMType(inst.base.ty, inst.base.src), "");
|
||||
_ = self.builder.buildStore(arg_val, ptr_val);
|
||||
return self.builder.buildLoad(ptr_val, "");
|
||||
}
|
||||
|
||||
fn genAlloc(self: *LLVMIRModule, inst: *Inst.NoOp) !?*const llvm.ValueRef {
|
||||
// buildAlloca expects the pointee type, not the pointer type, so assert that
|
||||
// a Payload.PointerSimple is passed to the alloc instruction.
|
||||
const pointee_type = inst.base.ty.castPointer().?.data;
|
||||
|
||||
// TODO: figure out a way to get the name of the var decl.
|
||||
// TODO: set alignment and volatile
|
||||
return self.builder.buildAlloca(try self.getLLVMType(pointee_type, inst.base.src), "");
|
||||
}
|
||||
|
||||
fn genStore(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.ValueRef {
|
||||
const val = try self.resolveInst(inst.rhs);
|
||||
const ptr = try self.resolveInst(inst.lhs);
|
||||
_ = self.builder.buildStore(val, ptr);
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genLoad(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.ValueRef {
|
||||
const ptr_val = try self.resolveInst(inst.operand);
|
||||
return self.builder.buildLoad(ptr_val, "");
|
||||
}
|
||||
|
||||
fn genBreakpoint(self: *LLVMIRModule, inst: *Inst.NoOp) !?*const llvm.ValueRef {
|
||||
const llvn_fn = self.getIntrinsic("llvm.debugtrap");
|
||||
_ = self.builder.buildCall(llvn_fn, null, 0, "");
|
||||
return null;
|
||||
}
|
||||
|
||||
fn getIntrinsic(self: *LLVMIRModule, name: []const u8) *const llvm.ValueRef {
|
||||
const id = llvm.lookupIntrinsicID(name.ptr, name.len);
|
||||
assert(id != 0);
|
||||
// TODO: add support for overload intrinsics by passing the prefix of the intrinsic
|
||||
// to `lookupIntrinsicID` and then passing the correct types to
|
||||
// `getIntrinsicDeclaration`
|
||||
return self.llvm_module.getIntrinsicDeclaration(id, null, 0);
|
||||
}
|
||||
|
||||
fn resolveInst(self: *LLVMIRModule, inst: *ir.Inst) !*const llvm.ValueRef {
|
||||
if (inst.castTag(.constant)) |const_inst| {
|
||||
return self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val });
|
||||
if (inst.value()) |val| {
|
||||
return self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = val });
|
||||
}
|
||||
return self.fail(inst.src, "TODO implement resolveInst", .{});
|
||||
if (self.func_inst_table.get(inst)) |value| return value;
|
||||
|
||||
return self.fail(inst.src, "TODO implement global llvm values (or the value is not in the func_inst_table table)", .{});
|
||||
}
|
||||
|
||||
fn genTypedValue(self: *LLVMIRModule, src: usize, typed_value: TypedValue) !*const llvm.ValueRef {
|
||||
const llvm_type = self.getLLVMType(typed_value.ty);
|
||||
fn genTypedValue(self: *LLVMIRModule, src: usize, tv: TypedValue) !*const llvm.ValueRef {
|
||||
const llvm_type = try self.getLLVMType(tv.ty, src);
|
||||
|
||||
if (typed_value.val.isUndef())
|
||||
if (tv.val.isUndef())
|
||||
return llvm_type.getUndef();
|
||||
|
||||
switch (typed_value.ty.zigTypeTag()) {
|
||||
.Bool => return if (typed_value.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(),
|
||||
else => return self.fail(src, "TODO implement const of type '{}'", .{typed_value.ty}),
|
||||
switch (tv.ty.zigTypeTag()) {
|
||||
.Bool => return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(),
|
||||
.Int => {
|
||||
var bigint_space: Value.BigIntSpace = undefined;
|
||||
const bigint = tv.val.toBigInt(&bigint_space);
|
||||
|
||||
if (bigint.eqZero()) return llvm_type.constNull();
|
||||
|
||||
if (bigint.limbs.len != 1) {
|
||||
return self.fail(src, "TODO implement bigger bigint", .{});
|
||||
}
|
||||
const llvm_int = llvm_type.constInt(bigint.limbs[0], false);
|
||||
if (!bigint.positive) {
|
||||
return llvm.constNeg(llvm_int);
|
||||
}
|
||||
return llvm_int;
|
||||
},
|
||||
else => return self.fail(src, "TODO implement const of type '{}'", .{tv.ty}),
|
||||
}
|
||||
}
|
||||
|
||||
/// If the llvm function does not exist, create it
|
||||
fn resolveLLVMFunction(self: *LLVMIRModule, func: *Module.Fn) !*const llvm.ValueRef {
|
||||
fn resolveLLVMFunction(self: *LLVMIRModule, func: *Module.Fn, src: usize) !*const llvm.ValueRef {
|
||||
// TODO: do we want to store this in our own datastructure?
|
||||
if (self.llvm_module.getNamedFunction(func.owner_decl.name)) |llvm_fn| return llvm_fn;
|
||||
|
||||
@ -402,25 +560,25 @@ pub const LLVMIRModule = struct {
|
||||
defer self.gpa.free(llvm_param);
|
||||
|
||||
for (fn_param_types) |fn_param, i| {
|
||||
llvm_param[i] = self.getLLVMType(fn_param);
|
||||
llvm_param[i] = try self.getLLVMType(fn_param, src);
|
||||
}
|
||||
|
||||
const fn_type = llvm.TypeRef.functionType(
|
||||
self.getLLVMType(return_type),
|
||||
try self.getLLVMType(return_type, src),
|
||||
if (fn_param_len == 0) null else llvm_param.ptr,
|
||||
@intCast(c_uint, fn_param_len),
|
||||
false,
|
||||
);
|
||||
const llvm_fn = self.llvm_module.addFunction(func.owner_decl.name, fn_type);
|
||||
|
||||
if (return_type.zigTypeTag() == .NoReturn) {
|
||||
if (return_type.tag() == .noreturn) {
|
||||
llvm_fn.addFnAttr("noreturn");
|
||||
}
|
||||
|
||||
return llvm_fn;
|
||||
}
|
||||
|
||||
fn getLLVMType(self: *LLVMIRModule, t: Type) *const llvm.TypeRef {
|
||||
fn getLLVMType(self: *LLVMIRModule, t: Type, src: usize) error{ OutOfMemory, CodegenFail }!*const llvm.TypeRef {
|
||||
switch (t.zigTypeTag()) {
|
||||
.Void => return llvm.voidType(),
|
||||
.NoReturn => return llvm.voidType(),
|
||||
@ -429,13 +587,18 @@ pub const LLVMIRModule = struct {
|
||||
return llvm.intType(info.bits);
|
||||
},
|
||||
.Bool => return llvm.intType(1),
|
||||
else => unreachable,
|
||||
.Pointer => {
|
||||
const pointer = t.castPointer().?;
|
||||
const elem_type = try self.getLLVMType(pointer.data, src);
|
||||
return elem_type.pointerType(0);
|
||||
},
|
||||
else => return self.fail(src, "TODO implement getLLVMType for type '{}'", .{t}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fail(self: *LLVMIRModule, src: usize, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } {
|
||||
@setCold(true);
|
||||
std.debug.assert(self.err_msg == null);
|
||||
assert(self.err_msg == null);
|
||||
self.err_msg = try Compilation.ErrorMsg.create(self.gpa, src, format, args);
|
||||
return error.CodegenFail;
|
||||
}
|
||||
|
||||
@ -43,8 +43,14 @@ pub const TypeRef = opaque {
|
||||
pub const constAllOnes = LLVMConstAllOnes;
|
||||
extern fn LLVMConstAllOnes(Ty: *const TypeRef) *const ValueRef;
|
||||
|
||||
pub const constInt = LLVMConstInt;
|
||||
extern fn LLVMConstInt(IntTy: *const TypeRef, N: c_ulonglong, SignExtend: LLVMBool) *const ValueRef;
|
||||
|
||||
pub const getUndef = LLVMGetUndef;
|
||||
extern fn LLVMGetUndef(Ty: *const TypeRef) *const ValueRef;
|
||||
|
||||
pub const pointerType = LLVMPointerType;
|
||||
extern fn LLVMPointerType(ElementType: *const TypeRef, AddressSpace: c_uint) *const TypeRef;
|
||||
};
|
||||
|
||||
pub const ModuleRef = opaque {
|
||||
@ -63,10 +69,16 @@ pub const ModuleRef = opaque {
|
||||
pub const getNamedFunction = LLVMGetNamedFunction;
|
||||
extern fn LLVMGetNamedFunction(*const ModuleRef, Name: [*:0]const u8) ?*const ValueRef;
|
||||
|
||||
pub const getIntrinsicDeclaration = LLVMGetIntrinsicDeclaration;
|
||||
extern fn LLVMGetIntrinsicDeclaration(Mod: *const ModuleRef, ID: c_uint, ParamTypes: ?[*]*const TypeRef, ParamCount: usize) *const ValueRef;
|
||||
|
||||
pub const printToString = LLVMPrintModuleToString;
|
||||
extern fn LLVMPrintModuleToString(*const ModuleRef) [*:0]const u8;
|
||||
};
|
||||
|
||||
pub const lookupIntrinsicID = LLVMLookupIntrinsicID;
|
||||
extern fn LLVMLookupIntrinsicID(Name: [*]const u8, NameLen: usize) c_uint;
|
||||
|
||||
pub const disposeMessage = LLVMDisposeMessage;
|
||||
extern fn LLVMDisposeMessage(Message: [*:0]const u8) void;
|
||||
|
||||
@ -76,9 +88,15 @@ pub const VerifierFailureAction = extern enum {
|
||||
ReturnStatus,
|
||||
};
|
||||
|
||||
pub const constNeg = LLVMConstNeg;
|
||||
extern fn LLVMConstNeg(ConstantVal: *const ValueRef) *const ValueRef;
|
||||
|
||||
pub const voidType = LLVMVoidType;
|
||||
extern fn LLVMVoidType() *const TypeRef;
|
||||
|
||||
pub const getParam = LLVMGetParam;
|
||||
extern fn LLVMGetParam(Fn: *const ValueRef, Index: c_uint) *const ValueRef;
|
||||
|
||||
pub const getEnumAttributeKindForName = LLVMGetEnumAttributeKindForName;
|
||||
extern fn LLVMGetEnumAttributeKindForName(Name: [*]const u8, SLen: usize) c_uint;
|
||||
|
||||
@ -117,11 +135,41 @@ pub const BuilderRef = opaque {
|
||||
pub const buildRetVoid = LLVMBuildRetVoid;
|
||||
extern fn LLVMBuildRetVoid(*const BuilderRef) *const ValueRef;
|
||||
|
||||
pub const buildRet = LLVMBuildRet;
|
||||
extern fn LLVMBuildRet(*const BuilderRef, V: *const ValueRef) *const ValueRef;
|
||||
|
||||
pub const buildUnreachable = LLVMBuildUnreachable;
|
||||
extern fn LLVMBuildUnreachable(*const BuilderRef) *const ValueRef;
|
||||
|
||||
pub const buildAlloca = LLVMBuildAlloca;
|
||||
extern fn LLVMBuildAlloca(*const BuilderRef, Ty: *const TypeRef, Name: [*:0]const u8) *const ValueRef;
|
||||
|
||||
pub const buildStore = LLVMBuildStore;
|
||||
extern fn LLVMBuildStore(*const BuilderRef, Val: *const ValueRef, Ptr: *const ValueRef) *const ValueRef;
|
||||
|
||||
pub const buildLoad = LLVMBuildLoad;
|
||||
extern fn LLVMBuildLoad(*const BuilderRef, PointerVal: *const ValueRef, Name: [*:0]const u8) *const ValueRef;
|
||||
|
||||
pub const buildNot = LLVMBuildNot;
|
||||
extern fn LLVMBuildNot(*const BuilderRef, V: *const ValueRef, Name: [*:0]const u8) *const ValueRef;
|
||||
|
||||
pub const buildNSWAdd = LLVMBuildNSWAdd;
|
||||
extern fn LLVMBuildNSWAdd(*const BuilderRef, LHS: *const ValueRef, RHS: *const ValueRef, Name: [*:0]const u8) *const ValueRef;
|
||||
|
||||
pub const buildNUWAdd = LLVMBuildNUWAdd;
|
||||
extern fn LLVMBuildNUWAdd(*const BuilderRef, LHS: *const ValueRef, RHS: *const ValueRef, Name: [*:0]const u8) *const ValueRef;
|
||||
|
||||
pub const buildNSWSub = LLVMBuildNSWSub;
|
||||
extern fn LLVMBuildNSWSub(*const BuilderRef, LHS: *const ValueRef, RHS: *const ValueRef, Name: [*:0]const u8) *const ValueRef;
|
||||
|
||||
pub const buildNUWSub = LLVMBuildNUWSub;
|
||||
extern fn LLVMBuildNUWSub(*const BuilderRef, LHS: *const ValueRef, RHS: *const ValueRef, Name: [*:0]const u8) *const ValueRef;
|
||||
|
||||
pub const buildIntCast2 = LLVMBuildIntCast2;
|
||||
extern fn LLVMBuildIntCast2(*const BuilderRef, Val: *const ValueRef, DestTy: *const TypeRef, IsSigned: LLVMBool, Name: [*:0]const u8) *const ValueRef;
|
||||
|
||||
pub const buildBitCast = LLVMBuildBitCast;
|
||||
extern fn LLVMBuildBitCast(*const BuilderRef, Val: *const ValueRef, DestTy: *const TypeRef, Name: [*:0]const u8) *const ValueRef;
|
||||
};
|
||||
|
||||
pub const BasicBlockRef = opaque {
|
||||
|
||||
43
src/test.zig
43
src/test.zig
@ -135,6 +135,7 @@ pub const TestContext = struct {
|
||||
extension: Extension,
|
||||
object_format: ?std.builtin.ObjectFormat = null,
|
||||
emit_h: bool = false,
|
||||
llvm_backend: bool = false,
|
||||
|
||||
files: std.ArrayList(File),
|
||||
|
||||
@ -266,6 +267,21 @@ pub const TestContext = struct {
|
||||
return &ctx.cases.items[ctx.cases.items.len - 1];
|
||||
}
|
||||
|
||||
/// Adds a test case that uses the LLVM backend to emit an executable.
|
||||
/// Currently this implies linking libc, because only then we can generate a testable executable.
|
||||
pub fn exeUsingLlvmBackend(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
|
||||
ctx.cases.append(Case{
|
||||
.name = name,
|
||||
.target = target,
|
||||
.updates = std.ArrayList(Update).init(ctx.cases.allocator),
|
||||
.output_mode = .Exe,
|
||||
.extension = .Zig,
|
||||
.files = std.ArrayList(File).init(ctx.cases.allocator),
|
||||
.llvm_backend = true,
|
||||
}) catch unreachable;
|
||||
return &ctx.cases.items[ctx.cases.items.len - 1];
|
||||
}
|
||||
|
||||
pub fn addObj(
|
||||
ctx: *TestContext,
|
||||
name: []const u8,
|
||||
@ -518,10 +534,30 @@ pub const TestContext = struct {
|
||||
try thread_pool.init(std.testing.allocator);
|
||||
defer thread_pool.deinit();
|
||||
|
||||
// Use the same global cache dir for all the tests, such that we for example don't have to
|
||||
// rebuild musl libc for every case (when LLVM backend is enabled).
|
||||
var global_tmp = std.testing.tmpDir(.{});
|
||||
defer global_tmp.cleanup();
|
||||
|
||||
var cache_dir = try global_tmp.dir.makeOpenPath("zig-cache", .{});
|
||||
defer cache_dir.close();
|
||||
const tmp_dir_path = try std.fs.path.join(std.testing.allocator, &[_][]const u8{ ".", "zig-cache", "tmp", &global_tmp.sub_path });
|
||||
defer std.testing.allocator.free(tmp_dir_path);
|
||||
|
||||
const global_cache_directory: Compilation.Directory = .{
|
||||
.handle = cache_dir,
|
||||
.path = try std.fs.path.join(std.testing.allocator, &[_][]const u8{ tmp_dir_path, "zig-cache" }),
|
||||
};
|
||||
defer std.testing.allocator.free(global_cache_directory.path.?);
|
||||
|
||||
for (self.cases.items) |case| {
|
||||
if (build_options.skip_non_native and case.target.getCpuArch() != std.Target.current.cpu.arch)
|
||||
continue;
|
||||
|
||||
// Skip tests that require LLVM backend when it is not available
|
||||
if (!build_options.have_llvm and case.llvm_backend)
|
||||
continue;
|
||||
|
||||
var prg_node = root_node.start(case.name, case.updates.items.len);
|
||||
prg_node.activate();
|
||||
defer prg_node.end();
|
||||
@ -537,6 +573,7 @@ pub const TestContext = struct {
|
||||
case,
|
||||
zig_lib_directory,
|
||||
&thread_pool,
|
||||
global_cache_directory,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -548,6 +585,7 @@ pub const TestContext = struct {
|
||||
case: Case,
|
||||
zig_lib_directory: Compilation.Directory,
|
||||
thread_pool: *ThreadPool,
|
||||
global_cache_directory: Compilation.Directory,
|
||||
) !void {
|
||||
const target_info = try std.zig.system.NativeTargetInfo.detect(allocator, case.target);
|
||||
const target = target_info.target;
|
||||
@ -601,7 +639,7 @@ pub const TestContext = struct {
|
||||
null;
|
||||
const comp = try Compilation.create(allocator, .{
|
||||
.local_cache_directory = zig_cache_directory,
|
||||
.global_cache_directory = zig_cache_directory,
|
||||
.global_cache_directory = global_cache_directory,
|
||||
.zig_lib_directory = zig_lib_directory,
|
||||
.thread_pool = thread_pool,
|
||||
.root_name = "test_case",
|
||||
@ -619,6 +657,9 @@ pub const TestContext = struct {
|
||||
.object_format = case.object_format,
|
||||
.is_native_os = case.target.isNativeOs(),
|
||||
.is_native_abi = case.target.isNativeAbi(),
|
||||
.link_libc = case.llvm_backend,
|
||||
.use_llvm = case.llvm_backend,
|
||||
.self_exe_path = std.testing.zig_exe_path,
|
||||
});
|
||||
defer comp.destroy();
|
||||
|
||||
|
||||
@ -8,14 +8,32 @@
|
||||
#ifndef ZIG_ZIG_CLANG_H
|
||||
#define ZIG_ZIG_CLANG_H
|
||||
|
||||
#include "stage1/stage2.h"
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define ZIG_EXTERN_C extern "C"
|
||||
#else
|
||||
#define ZIG_EXTERN_C
|
||||
#endif
|
||||
|
||||
// ATTENTION: If you modify this file, be sure to update the corresponding
|
||||
// extern function declarations in the self-hosted compiler file
|
||||
// src/clang.zig.
|
||||
|
||||
// ABI warning
|
||||
struct Stage2ErrorMsg {
|
||||
const char *filename_ptr; // can be null
|
||||
size_t filename_len;
|
||||
const char *msg_ptr;
|
||||
size_t msg_len;
|
||||
const char *source; // valid until the ASTUnit is freed. can be null
|
||||
unsigned line; // 0 based
|
||||
unsigned column; // 0 based
|
||||
unsigned offset; // byte offset into source
|
||||
};
|
||||
|
||||
struct ZigClangSourceLocation {
|
||||
unsigned ID;
|
||||
};
|
||||
|
||||
30
test/stage2/llvm_backend.zig
Normal file
30
test/stage2/llvm_backend.zig
Normal file
@ -0,0 +1,30 @@
|
||||
const std = @import("std");
|
||||
const TestContext = @import("../../src/test.zig").TestContext;
|
||||
const build_options = @import("build_options");
|
||||
|
||||
// These tests should work with all platforms, but we're using linux_x64 for
|
||||
// now for consistency. Will be expanded eventually.
|
||||
const linux_x64 = std.zig.CrossTarget{
|
||||
.cpu_arch = .x86_64,
|
||||
.os_tag = .linux,
|
||||
};
|
||||
|
||||
pub fn addCases(ctx: *TestContext) !void {
|
||||
{
|
||||
var case = ctx.exeUsingLlvmBackend("simple addition and subtraction", linux_x64);
|
||||
|
||||
case.addCompareOutput(
|
||||
\\fn add(a: i32, b: i32) i32 {
|
||||
\\ return a + b;
|
||||
\\}
|
||||
\\
|
||||
\\export fn main() c_int {
|
||||
\\ var a: i32 = -5;
|
||||
\\ const x = add(a, 7);
|
||||
\\ var y = add(2, 0);
|
||||
\\ y -= x;
|
||||
\\ return y;
|
||||
\\}
|
||||
, "");
|
||||
}
|
||||
}
|
||||
@ -31,6 +31,7 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
try @import("spu-ii.zig").addCases(ctx);
|
||||
try @import("arm.zig").addCases(ctx);
|
||||
try @import("aarch64.zig").addCases(ctx);
|
||||
try @import("llvm_backend.zig").addCases(ctx);
|
||||
|
||||
{
|
||||
var case = ctx.exe("hello world with updates", linux_x64);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user