diff options
| author | Timon Kruiper <timonkruiper@gmail.com> | 2020-12-19 11:22:49 +0100 |
|---|---|---|
| committer | Timon Kruiper <timonkruiper@gmail.com> | 2020-12-28 21:19:40 +0100 |
| commit | 071417161d5d7ab91c32073693590ef161017dbe (patch) | |
| tree | 891bfd9fecd90b9b341d95dfb78399788f62c3ed /src/llvm_backend.zig | |
| parent | 4a0d64300bd9ab1a8c35c634856396e3413200f1 (diff) | |
| download | zig-071417161d5d7ab91c32073693590ef161017dbe.tar.gz zig-071417161d5d7ab91c32073693590ef161017dbe.zip | |
stage2: add initial impl of LLVM backend in self-hosted compiler
Diffstat (limited to 'src/llvm_backend.zig')
| -rw-r--r-- | src/llvm_backend.zig | 410 |
1 files changed, 410 insertions, 0 deletions
diff --git a/src/llvm_backend.zig b/src/llvm_backend.zig new file mode 100644 index 0000000000..0745a1fe8b --- /dev/null +++ b/src/llvm_backend.zig @@ -0,0 +1,410 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Compilation = @import("Compilation.zig"); +const llvm = @import("llvm_bindings.zig"); +const link = @import("link.zig"); + +const Module = @import("Module.zig"); +const TypedValue = @import("TypedValue.zig"); +const ir = @import("ir.zig"); +const Inst = ir.Inst; + +const Value = @import("value.zig").Value; +const Type = @import("type.zig").Type; + +pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { + const llvm_arch = switch (target.cpu.arch) { + .arm => "arm", + .armeb => "armeb", + .aarch64 => "aarch64", + .aarch64_be => "aarch64_be", + .aarch64_32 => "aarch64_32", + .arc => "arc", + .avr => "avr", + .bpfel => "bpfel", + .bpfeb => "bpfeb", + .hexagon => "hexagon", + .mips => "mips", + .mipsel => "mipsel", + .mips64 => "mips64", + .mips64el => "mips64el", + .msp430 => "msp430", + .powerpc => "powerpc", + .powerpc64 => "powerpc64", + .powerpc64le => "powerpc64le", + .r600 => "r600", + .amdgcn => "amdgcn", + .riscv32 => "riscv32", + .riscv64 => "riscv64", + .sparc => "sparc", + .sparcv9 => "sparcv9", + .sparcel => "sparcel", + .s390x => "s390x", + .tce => "tce", + .tcele => "tcele", + .thumb => "thumb", + .thumbeb => "thumbeb", + .i386 => "i386", + .x86_64 => "x86_64", + .xcore => "xcore", + .nvptx => "nvptx", + .nvptx64 => "nvptx64", + .le32 => "le32", + .le64 => "le64", + .amdil => "amdil", + .amdil64 => "amdil64", + .hsail => "hsail", + .hsail64 => "hsail64", + .spir => "spir", + .spir64 => "spir64", + .kalimba => "kalimba", + .shave => "shave", + .lanai => "lanai", + .wasm32 => "wasm32", + .wasm64 => "wasm64", + .renderscript32 => "renderscript32", + .renderscript64 => "renderscript64", + .ve => "ve", + .spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII, + }; + // TODO Add a sub-arch for some architectures depending on CPU features. + + const llvm_os = switch (target.os.tag) { + .freestanding => "unknown", + .ananas => "ananas", + .cloudabi => "cloudabi", + .dragonfly => "dragonfly", + .freebsd => "freebsd", + .fuchsia => "fuchsia", + .ios => "ios", + .kfreebsd => "kfreebsd", + .linux => "linux", + .lv2 => "lv2", + .macos => "macosx", + .netbsd => "netbsd", + .openbsd => "openbsd", + .solaris => "solaris", + .windows => "windows", + .haiku => "haiku", + .minix => "minix", + .rtems => "rtems", + .nacl => "nacl", + .cnk => "cnk", + .aix => "aix", + .cuda => "cuda", + .nvcl => "nvcl", + .amdhsa => "amdhsa", + .ps4 => "ps4", + .elfiamcu => "elfiamcu", + .tvos => "tvos", + .watchos => "watchos", + .mesa3d => "mesa3d", + .contiki => "contiki", + .amdpal => "amdpal", + .hermit => "hermit", + .hurd => "hurd", + .wasi => "wasi", + .emscripten => "emscripten", + .uefi => "windows", + .other => "unknown", + }; + + const llvm_abi = switch (target.abi) { + .none => "unknown", + .gnu => "gnu", + .gnuabin32 => "gnuabin32", + .gnuabi64 => "gnuabi64", + .gnueabi => "gnueabi", + .gnueabihf => "gnueabihf", + .gnux32 => "gnux32", + .code16 => "code16", + .eabi => "eabi", + .eabihf => "eabihf", + .android => "android", + .musl => "musl", + .musleabi => "musleabi", + .musleabihf => "musleabihf", + .msvc => "msvc", + .itanium => "itanium", + .cygnus => "cygnus", + .coreclr => "coreclr", + .simulator => "simulator", + .macabi => "macabi", + }; + + return std.fmt.allocPrintZ(allocator, "{}-unknown-{}-{}", .{ llvm_arch, llvm_os, llvm_abi }); +} + +pub const LLVMIRModule = struct { + llvm_module: *const llvm.ModuleRef, + target_machine: *const llvm.TargetMachineRef, + output_path: []const u8, + + gpa: *Allocator, + err_msg: ?*Compilation.ErrorMsg = null, + + 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; + + initializeLLVMTargets(); + + const root_nameZ = try gpa.dupeZ(u8, options.root_name); + defer gpa.free(root_nameZ); + const llvm_module = llvm.ModuleRef.createWithName(root_nameZ.ptr); + errdefer llvm_module.disposeModule(); + + const llvm_target_triple = try targetTriple(gpa, options.target); + defer gpa.free(llvm_target_triple); + + var error_message: [*:0]const u8 = undefined; + var target_ref: *const llvm.TargetRef = undefined; + if (llvm.TargetRef.getTargetFromTriple(llvm_target_triple.ptr, &target_ref, &error_message)) { + defer llvm.disposeMessage(error_message); + + const stderr = std.io.getStdErr().outStream(); + try stderr.print( + \\Zig is expecting LLVM to understand this target: '{s}' + \\However LLVM responded with: "{s}" + \\Zig is unable to continue. This is a bug in Zig: + \\https://github.com/ziglang/zig/issues/438 + \\ + , + .{ + llvm_target_triple, + error_message, + }, + ); + return error.InvalidLLVMTriple; + } + + const opt_level: llvm.CodeGenOptLevel = if (options.optimize_mode == .Debug) .None else .Aggressive; + const target_machine = llvm.TargetMachineRef.createTargetMachine( + target_ref, + llvm_target_triple.ptr, + "", + "", + opt_level, + .Static, + .Default, + ); + errdefer target_machine.disposeTargetMachine(); + + self.* = .{ + .llvm_module = llvm_module, + .target_machine = target_machine, + .output_path = sub_path, + .gpa = gpa, + }; + return self; + } + + pub fn deinit(self: *LLVMIRModule, allocator: *Allocator) void { + self.llvm_module.disposeModule(); + self.target_machine.disposeTargetMachine(); + allocator.destroy(self); + } + + fn initializeLLVMTargets() void { + llvm.initializeAllTargets(); + llvm.initializeAllTargetInfos(); + llvm.initializeAllTargetMCs(); + llvm.initializeAllAsmPrinters(); + llvm.initializeAllAsmParsers(); + } + + pub fn flushModule(self: *LLVMIRModule, comp: *Compilation) !void { + { + 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.verifyModule(.ReturnStatus, &error_message)) { + const stderr = std.io.getStdErr().outStream(); + try stderr.print("broken LLVM module found: {s}\nThis is a bug in the Zig compiler.", .{error_message}); + return error.BrokenLLVMModule; + } + } + + if (comp.verbose_llvm_ir) { + const dump = self.llvm_module.printToString(); + defer llvm.disposeMessage(dump); + + const stderr = std.io.getStdErr().outStream(); + try stderr.writeAll(std.mem.spanZ(dump)); + } + + const output_pathZ = try self.gpa.dupeZ(u8, self.output_path); + defer self.gpa.free(output_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, + .ObjectFile, + &error_message, + )) { + defer llvm.disposeMessage(error_message); + + const stderr = std.io.getStdErr().outStream(); + try stderr.print("LLVM failed to emit file: {s}\n", .{error_message}); + return error.FailedToEmit; + } + } + + pub fn updateDecl(self: *LLVMIRModule, module: *Module, decl: *Module.Decl) !void { + const typed_value = decl.typed_value.most_recent.typed_value; + self.generate(module, typed_value, decl.src()) catch |err| switch (err) { + error.CodegenFail => { + decl.analysis = .codegen_failure; + try module.failed_decls.put(module.gpa, decl, self.err_msg.?); + return; + }, + else => |e| return e, + }; + } + + fn generate(self: *LLVMIRModule, module: *Module, typed_value: TypedValue, src: usize) !void { + switch (typed_value.ty.zigTypeTag()) { + .Fn => { + const func = typed_value.val.cast(Value.Payload.Function).?.func; + + var codegen = CodeGen{ + .module = module, + .llvm_module = self.llvm_module, + .builder = llvm.BuilderRef.createBuilder(), + }; + defer codegen.builder.disposeBuilder(); + + const llvm_func = try codegen.resolveLLVMFunction(func); + + // 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"); + codegen.builder.positionBuilderAtEnd(entry_block); + + const instructions = func.analysis.success.instructions; + for (instructions) |inst| { + switch (inst.tag) { + .breakpoint => try codegen.generateBreakpoint(inst.castTag(.breakpoint).?), + .call => try codegen.generateCall(inst.castTag(.call).?), + .unreach => codegen.generateUnreach(inst.castTag(.unreach).?), + .retvoid => codegen.generateRetVoid(inst.castTag(.retvoid).?), + .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}), + } + } + + 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); + self.err_msg = try Compilation.ErrorMsg.create(self.gpa, src, format, args); + return error.CodegenFail; + } +}; + +const CodeGen = struct { + module: *Module, + llvm_module: *const llvm.ModuleRef, + builder: *const llvm.BuilderRef, + + fn generateCall(codegen: *CodeGen, inst: *Inst.Call) !void { + if (inst.func.cast(Inst.Constant)) |func_inst| { + if (func_inst.val.cast(Value.Payload.Function)) |func_val| { + const func = func_val.func; + const zig_fn_type = func.owner_decl.typed_value.most_recent.typed_value.ty; + const llvm_fn = try codegen.resolveLLVMFunction(func); + + // TODO: handle more arguments, inst.args + + // TODO: LLVMBuildCall2 handles opaque function pointers, according to llvm docs + // Do we need that? + const call = codegen.builder.buildCall(llvm_fn, null, 0, ""); + + if (zig_fn_type.fnReturnType().zigTypeTag() == .NoReturn) { + _ = codegen.builder.buildUnreachable(); + } + } + } + } + + fn generateRetVoid(codegen: *CodeGen, inst: *Inst.NoOp) void { + _ = codegen.builder.buildRetVoid(); + } + + fn generateUnreach(codegen: *CodeGen, inst: *Inst.NoOp) void { + _ = codegen.builder.buildUnreachable(); + } + + fn generateBreakpoint(codegen: *CodeGen, 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 = codegen.llvm_module.addFunction("llvm.debugtrap", fn_type); + // TODO: add assertion: LLVMGetIntrinsicID + _ = codegen.builder.buildCall(func, null, 0, ""); + } + + /// If the llvm function does not exist, create it + fn resolveLLVMFunction(codegen: *CodeGen, func: *Module.Fn) !*const llvm.ValueRef { + // TODO: do we want to store this in our own datastructure? + if (codegen.llvm_module.getNamedFunction(func.owner_decl.name)) |llvm_fn| return llvm_fn; + + const zig_fn_type = func.owner_decl.typed_value.most_recent.typed_value.ty; + const return_type = zig_fn_type.fnReturnType(); + + const fn_param_len = zig_fn_type.fnParamLen(); + + const fn_param_types = try codegen.module.gpa.alloc(Type, fn_param_len); + defer codegen.module.gpa.free(fn_param_types); + zig_fn_type.fnParamTypes(fn_param_types); + + const llvm_param = try codegen.module.gpa.alloc(*const llvm.TypeRef, fn_param_len); + defer codegen.module.gpa.free(llvm_param); + + for (fn_param_types) |fn_param, i| { + llvm_param[i] = codegen.getLLVMType(fn_param); + } + + const fn_type = llvm.TypeRef.functionType( + codegen.getLLVMType(return_type), + if (fn_param_len == 0) null else llvm_param.ptr, + @intCast(c_uint, fn_param_len), + false, + ); + const llvm_fn = codegen.llvm_module.addFunction(func.owner_decl.name, fn_type); + + if (return_type.zigTypeTag() == .NoReturn) { + llvm_fn.addFnAttr("noreturn"); + } + + return llvm_fn; + } + + fn getLLVMType(codegen: *CodeGen, t: Type) *const llvm.TypeRef { + switch (t.zigTypeTag()) { + .Void => return llvm.voidType(), + .NoReturn => return llvm.voidType(), + .Int => { + const info = t.intInfo(codegen.module.getTarget()); + return llvm.intType(info.bits); + }, + .Bool => return llvm.intType(1), + else => unreachable, + } + } +}; |
