diff options
Diffstat (limited to 'src/codegen/llvm.zig')
| -rw-r--r-- | src/codegen/llvm.zig | 752 |
1 files changed, 750 insertions, 2 deletions
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index ea8e25c214..3caa95d466 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1,7 +1,21 @@ 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"); +const link = @import("../link.zig"); +const log = std.log.scoped(.codegen); +const math = std.math; -pub fn targetTriple(allocator: *Allocator, target: std.Target) ![]u8 { +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", @@ -56,6 +70,8 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![]u8 { .renderscript64 => "renderscript64", .ve => "ve", .spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII, + .spirv32 => return error.LLVMBackendDoesNotSupportSPIRV, + .spirv64 => return error.LLVMBackendDoesNotSupportSPIRV, }; // TODO Add a sub-arch for some architectures depending on CPU features. @@ -96,6 +112,9 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![]u8 { .wasi => "wasi", .emscripten => "emscripten", .uefi => "windows", + .opencl => return error.LLVMBackendDoesNotSupportOpenCL, + .glsl450 => return error.LLVMBackendDoesNotSupportGLSL450, + .vulkan => return error.LLVMBackendDoesNotSupportVulkan, .other => "unknown", }; @@ -122,5 +141,734 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![]u8 { .macabi => "macabi", }; - return std.fmt.allocPrint(allocator, "{}-unknown-{}-{}", .{ llvm_arch, llvm_os, llvm_abi }); + return std.fmt.allocPrintZ(allocator, "{s}-unknown-{s}-{s}", .{ llvm_arch, llvm_os, llvm_abi }); } + +pub const LLVMIRModule = struct { + module: *Module, + llvm_module: *const llvm.Module, + context: *const llvm.Context, + target_machine: *const llvm.TargetMachine, + builder: *const llvm.Builder, + + object_path: []const u8, + + gpa: *Allocator, + err_msg: ?*Module.ErrorMsg = null, + + // TODO: The fields below should really move into a different struct, + // because they are only valid when generating a function + + /// 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. + /// TODO: Change this to a stack of Branch. Currently we store all the values from all the blocks + /// in here, however if a block ends, the instructions can be thrown away. + func_inst_table: std.AutoHashMapUnmanaged(*Inst, *const llvm.Value) = .{}, + + /// These fields are used to refer to the LLVM value of the function paramaters in an Arg instruction. + args: []*const llvm.Value = &[_]*const llvm.Value{}, + arg_index: usize = 0, + + entry_block: *const llvm.BasicBlock = undefined, + /// This fields stores the last alloca instruction, such that we can append more alloca instructions + /// to the top of the function. + latest_alloca_inst: ?*const llvm.Value = null, + + llvm_func: *const llvm.Value = undefined, + + /// This data structure is used to implement breaking to blocks. + blocks: std.AutoHashMapUnmanaged(*Inst.Block, struct { + parent_bb: *const llvm.BasicBlock, + break_bbs: *BreakBasicBlocks, + break_vals: *BreakValues, + }) = .{}, + + src_loc: Module.SrcLoc, + + const BreakBasicBlocks = std.ArrayListUnmanaged(*const llvm.BasicBlock); + const BreakValues = std.ArrayListUnmanaged(*const llvm.Value); + + 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); + + const context = llvm.Context.create(); + errdefer context.dispose(); + + initializeLLVMTargets(); + + 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(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)) { + 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}" + \\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.TargetMachine.create( + target, + llvm_target_triple.ptr, + "", + "", + opt_level, + .Static, + .Default, + ); + errdefer target_machine.dispose(); + + const builder = context.createBuilder(); + errdefer builder.dispose(); + + self.* = .{ + .module = options.module.?, + .llvm_module = llvm_module, + .context = context, + .target_machine = target_machine, + .builder = builder, + .object_path = object_path, + .gpa = gpa, + // TODO move this field into a struct that is only instantiated per gen() call + .src_loc = undefined, + }; + return self; + } + + pub fn deinit(self: *LLVMIRModule, allocator: *Allocator) void { + self.builder.dispose(); + self.target_machine.dispose(); + self.llvm_module.dispose(); + self.context.dispose(); + + self.func_inst_table.deinit(self.gpa); + self.gpa.free(self.object_path); + + self.blocks.deinit(self.gpa); + + allocator.destroy(self); + } + + fn initializeLLVMTargets() void { + llvm.initializeAllTargets(); + llvm.initializeAllTargetInfos(); + llvm.initializeAllTargetMCs(); + llvm.initializeAllAsmPrinters(); + llvm.initializeAllAsmParsers(); + } + + pub fn flushModule(self: *LLVMIRModule, 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)); + } + + { + 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)) { + 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; + } + } + + const object_pathZ = try self.gpa.dupeZ(u8, self.object_path); + defer self.gpa.free(object_pathZ); + + var error_message: [*:0]const u8 = undefined; + if (self.target_machine.emitToFile( + self.llvm_module, + object_pathZ.ptr, + .ObjectFile, + &error_message, + )) { + defer llvm.disposeMessage(error_message); + + const stderr = std.io.getStdErr().writer(); + 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 { + self.gen(module, decl) catch |err| switch (err) { + 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, + }; + } + + fn gen(self: *LLVMIRModule, module: *Module, decl: *Module.Decl) !void { + const typed_value = decl.typed_value.most_recent.typed_value; + const src = decl.src(); + + self.src_loc = decl.srcLoc(); + + log.debug("gen: {s} type: {}, value: {}", .{ decl.name, typed_value.ty, typed_value.val }); + + if (typed_value.val.castTag(.function)) |func_payload| { + const func = func_payload.data; + + const llvm_func = try self.resolveLLVMFunction(func.owner_decl, src); + + // 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.Value, fn_param_len); + defer self.gpa.free(args); + + for (args) |*arg, i| { + arg.* = llvm.getParam(llvm_func, @intCast(c_uint, i)); + } + self.args = args; + self.arg_index = 0; + + // 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(); + } + + self.entry_block = self.context.appendBasicBlock(llvm_func, "Entry"); + self.builder.positionBuilderAtEnd(self.entry_block); + self.latest_alloca_inst = null; + self.llvm_func = llvm_func; + + try self.genBody(func.body); + } else if (typed_value.val.castTag(.extern_fn)) |extern_fn| { + _ = try self.resolveLLVMFunction(extern_fn.data, src); + } else { + _ = try self.resolveGlobalDecl(decl, src); + } + } + + fn genBody(self: *LLVMIRModule, body: ir.Body) error{ OutOfMemory, CodegenFail }!void { + for (body.instructions) |inst| { + const opt_value = 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).?), + .block => try self.genBlock(inst.castTag(.block).?), + .br => try self.genBr(inst.castTag(.br).?), + .breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?), + .call => try self.genCall(inst.castTag(.call).?), + .cmp_eq => try self.genCmp(inst.castTag(.cmp_eq).?, .eq), + .cmp_gt => try self.genCmp(inst.castTag(.cmp_gt).?, .gt), + .cmp_gte => try self.genCmp(inst.castTag(.cmp_gte).?, .gte), + .cmp_lt => try self.genCmp(inst.castTag(.cmp_lt).?, .lt), + .cmp_lte => try self.genCmp(inst.castTag(.cmp_lte).?, .lte), + .cmp_neq => try self.genCmp(inst.castTag(.cmp_neq).?, .neq), + .condbr => try self.genCondBr(inst.castTag(.condbr).?), + .intcast => try self.genIntCast(inst.castTag(.intcast).?), + .load => try self.genLoad(inst.castTag(.load).?), + .loop => try self.genLoop(inst.castTag(.loop).?), + .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(inst.src, "TODO implement LLVM codegen for Zir instruction: {}", .{tag}), + }; + if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa, inst, val); + } + } + + fn genCall(self: *LLVMIRModule, inst: *Inst.Call) !?*const llvm.Value { + if (inst.func.value()) |func_value| { + const fn_decl = if (func_value.castTag(.extern_fn)) |extern_fn| + extern_fn.data + else if (func_value.castTag(.function)) |func_payload| + func_payload.data.owner_decl + else + unreachable; + + const zig_fn_type = fn_decl.typed_value.most_recent.typed_value.ty; + const llvm_fn = try self.resolveLLVMFunction(fn_decl, inst.base.src); + + const num_args = inst.args.len; + + const llvm_param_vals = try self.gpa.alloc(*const llvm.Value, num_args); + defer self.gpa.free(llvm_param_vals); + + for (inst.args) |arg, i| { + llvm_param_vals[i] = try self.resolveInst(arg); + } + + // TODO: LLVMBuildCall2 handles opaque function pointers, according to llvm docs + // Do we need that? + const call = self.builder.buildCall( + llvm_fn, + if (num_args == 0) null else llvm_param_vals.ptr, + @intCast(c_uint, num_args), + "", + ); + + 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; + } else { + return self.fail(inst.base.src, "TODO implement calling runtime known function pointer LLVM backend", .{}); + } + } + + fn genRetVoid(self: *LLVMIRModule, inst: *Inst.NoOp) ?*const llvm.Value { + _ = self.builder.buildRetVoid(); + return null; + } + + fn genRet(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { + _ = self.builder.buildRet(try self.resolveInst(inst.operand)); + return null; + } + + fn genCmp(self: *LLVMIRModule, inst: *Inst.BinOp, op: math.CompareOperator) !?*const llvm.Value { + const lhs = try self.resolveInst(inst.lhs); + const rhs = try self.resolveInst(inst.rhs); + + if (!inst.base.ty.isInt()) + if (inst.base.ty.tag() != .bool) + return self.fail(inst.base.src, "TODO implement 'genCmp' for type {}", .{inst.base.ty}); + + const is_signed = inst.base.ty.isSignedInt(); + const operation = switch (op) { + .eq => .EQ, + .neq => .NE, + .lt => @as(llvm.IntPredicate, if (is_signed) .SLT else .ULT), + .lte => @as(llvm.IntPredicate, if (is_signed) .SLE else .ULE), + .gt => @as(llvm.IntPredicate, if (is_signed) .SGT else .UGT), + .gte => @as(llvm.IntPredicate, if (is_signed) .SGE else .UGE), + }; + + return self.builder.buildICmp(operation, lhs, rhs, ""); + } + + fn genBlock(self: *LLVMIRModule, inst: *Inst.Block) !?*const llvm.Value { + const parent_bb = self.context.createBasicBlock("Block"); + + // 5 breaks to a block seems like a reasonable default. + var break_bbs = try BreakBasicBlocks.initCapacity(self.gpa, 5); + var break_vals = try BreakValues.initCapacity(self.gpa, 5); + try self.blocks.putNoClobber(self.gpa, inst, .{ + .parent_bb = parent_bb, + .break_bbs = &break_bbs, + .break_vals = &break_vals, + }); + defer { + self.blocks.removeAssertDiscard(inst); + break_bbs.deinit(self.gpa); + break_vals.deinit(self.gpa); + } + + try self.genBody(inst.body); + + self.llvm_func.appendExistingBasicBlock(parent_bb); + self.builder.positionBuilderAtEnd(parent_bb); + + // If the block does not return a value, we dont have to create a phi node. + if (!inst.base.ty.hasCodeGenBits()) return null; + + const phi_node = self.builder.buildPhi(try self.getLLVMType(inst.base.ty, inst.base.src), ""); + phi_node.addIncoming( + break_vals.items.ptr, + break_bbs.items.ptr, + @intCast(c_uint, break_vals.items.len), + ); + return phi_node; + } + + fn genBr(self: *LLVMIRModule, inst: *Inst.Br) !?*const llvm.Value { + // Get the block that we want to break to. + var block = self.blocks.get(inst.block).?; + _ = self.builder.buildBr(block.parent_bb); + + // If the break doesn't break a value, then we don't have to add + // the values to the lists. + if (!inst.operand.ty.hasCodeGenBits()) return null; + + // For the phi node, we need the basic blocks and the values of the + // break instructions. + try block.break_bbs.append(self.gpa, self.builder.getInsertBlock()); + + const val = try self.resolveInst(inst.operand); + try block.break_vals.append(self.gpa, val); + + return null; + } + + fn genCondBr(self: *LLVMIRModule, inst: *Inst.CondBr) !?*const llvm.Value { + const condition_value = try self.resolveInst(inst.condition); + + const then_block = self.context.appendBasicBlock(self.llvm_func, "Then"); + const else_block = self.context.appendBasicBlock(self.llvm_func, "Else"); + { + const prev_block = self.builder.getInsertBlock(); + defer self.builder.positionBuilderAtEnd(prev_block); + + self.builder.positionBuilderAtEnd(then_block); + try self.genBody(inst.then_body); + + self.builder.positionBuilderAtEnd(else_block); + try self.genBody(inst.else_body); + } + _ = self.builder.buildCondBr(condition_value, then_block, else_block); + return null; + } + + fn genLoop(self: *LLVMIRModule, inst: *Inst.Loop) !?*const llvm.Value { + const loop_block = self.context.appendBasicBlock(self.llvm_func, "Loop"); + _ = self.builder.buildBr(loop_block); + + self.builder.positionBuilderAtEnd(loop_block); + try self.genBody(inst.body); + + _ = self.builder.buildBr(loop_block); + return null; + } + + fn genNot(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { + return self.builder.buildNot(try self.resolveInst(inst.operand), ""); + } + + fn genUnreach(self: *LLVMIRModule, inst: *Inst.NoOp) ?*const llvm.Value { + _ = self.builder.buildUnreachable(); + return null; + } + + fn genAdd(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value { + 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 genSub(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value { + 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.Value { + 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.Value { + 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.Value { + const arg_val = self.args[self.arg_index]; + self.arg_index += 1; + + const ptr_val = self.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.Value { + // 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.buildAlloca(try self.getLLVMType(pointee_type, inst.base.src)); + } + + /// Use this instead of builder.buildAlloca, because this function makes sure to + /// put the alloca instruction at the top of the function! + fn buildAlloca(self: *LLVMIRModule, t: *const llvm.Type) *const llvm.Value { + const prev_block = self.builder.getInsertBlock(); + defer self.builder.positionBuilderAtEnd(prev_block); + + if (self.latest_alloca_inst) |latest_alloc| { + // builder.positionBuilder adds it before the instruction, + // but we want to put it after the last alloca instruction. + self.builder.positionBuilder(self.entry_block, latest_alloc.getNextInstruction().?); + } else { + // There might have been other instructions emitted before the + // first alloca has been generated. However the alloca should still + // be first in the function. + if (self.entry_block.getFirstInstruction()) |first_inst| { + self.builder.positionBuilder(self.entry_block, first_inst); + } + } + + const val = self.builder.buildAlloca(t, ""); + self.latest_alloca_inst = val; + return val; + } + + fn genStore(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value { + 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.Value { + const ptr_val = try self.resolveInst(inst.operand); + return self.builder.buildLoad(ptr_val, ""); + } + + fn genBreakpoint(self: *LLVMIRModule, inst: *Inst.NoOp) !?*const llvm.Value { + 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.Value { + 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.Value { + if (inst.value()) |val| { + return self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = val }); + } + 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, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value { + const llvm_type = try self.getLLVMType(tv.ty, src); + + if (tv.val.isUndef()) + return llvm_type.getUndef(); + + 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; + }, + .Pointer => switch (tv.val.tag()) { + .decl_ref => { + const decl = tv.val.castTag(.decl_ref).?.data; + const val = try self.resolveGlobalDecl(decl, src); + + const usize_type = try self.getLLVMType(Type.initTag(.usize), src); + + // TODO: second index should be the index into the memory! + var indices: [2]*const llvm.Value = .{ + usize_type.constNull(), + usize_type.constNull(), + }; + + // TODO: consider using buildInBoundsGEP2 for opaque pointers + return self.builder.buildInBoundsGEP(val, &indices, 2, ""); + }, + else => return self.fail(src, "TODO implement const of pointer type '{}'", .{tv.ty}), + }, + .Array => { + if (tv.val.castTag(.bytes)) |payload| { + const zero_sentinel = if (tv.ty.sentinel()) |sentinel| blk: { + if (sentinel.tag() == .zero) break :blk true; + return self.fail(src, "TODO handle other sentinel values", .{}); + } else false; + + return self.context.constString(payload.data.ptr, @intCast(c_uint, payload.data.len), !zero_sentinel); + } else { + return self.fail(src, "TODO handle more array values", .{}); + } + }, + else => return self.fail(src, "TODO implement const of type '{}'", .{tv.ty}), + } + } + + fn getLLVMType(self: *LLVMIRModule, t: Type, src: usize) error{ OutOfMemory, CodegenFail }!*const llvm.Type { + switch (t.zigTypeTag()) { + .Void => return self.context.voidType(), + .NoReturn => return self.context.voidType(), + .Int => { + const info = t.intInfo(self.module.getTarget()); + return self.context.intType(info.bits); + }, + .Bool => return self.context.intType(1), + .Pointer => { + if (t.isSlice()) { + return self.fail(src, "TODO: LLVM backend: implement slices", .{}); + } else { + const elem_type = try self.getLLVMType(t.elemType(), src); + return elem_type.pointerType(0); + } + }, + .Array => { + const elem_type = try self.getLLVMType(t.elemType(), src); + return elem_type.arrayType(@intCast(c_uint, t.abiSize(self.module.getTarget()))); + }, + else => return self.fail(src, "TODO implement getLLVMType for type '{}'", .{t}), + } + } + + fn resolveGlobalDecl(self: *LLVMIRModule, decl: *Module.Decl, src: usize) error{ OutOfMemory, CodegenFail }!*const llvm.Value { + // TODO: do we want to store this in our own datastructure? + if (self.llvm_module.getNamedGlobal(decl.name)) |val| return val; + + const typed_value = decl.typed_value.most_recent.typed_value; + + // TODO: remove this redundant `getLLVMType`, it is also called in `genTypedValue`. + const llvm_type = try self.getLLVMType(typed_value.ty, src); + const val = try self.genTypedValue(src, typed_value); + const global = self.llvm_module.addGlobal(llvm_type, decl.name); + llvm.setInitializer(global, val); + + // TODO ask the Decl if it is const + // https://github.com/ziglang/zig/issues/7582 + + return global; + } + + /// If the llvm function does not exist, create it + fn resolveLLVMFunction(self: *LLVMIRModule, func: *Module.Decl, src: usize) !*const llvm.Value { + // TODO: do we want to store this in our own datastructure? + if (self.llvm_module.getNamedFunction(func.name)) |llvm_fn| return llvm_fn; + + const zig_fn_type = func.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 self.gpa.alloc(Type, fn_param_len); + defer self.gpa.free(fn_param_types); + zig_fn_type.fnParamTypes(fn_param_types); + + const llvm_param = try self.gpa.alloc(*const llvm.Type, fn_param_len); + defer self.gpa.free(llvm_param); + + for (fn_param_types) |fn_param, i| { + llvm_param[i] = try self.getLLVMType(fn_param, src); + } + + const fn_type = llvm.Type.functionType( + 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.name, fn_type); + + if (return_type.tag() == .noreturn) { + self.addFnAttr(llvm_fn, "noreturn"); + } + + return llvm_fn; + } + + // Helper functions + fn addAttr(self: LLVMIRModule, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void { + const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len); + assert(kind_id != 0); + const llvm_attr = self.context.createEnumAttribute(kind_id, 0); + val.addAttributeAtIndex(index, llvm_attr); + } + + fn addFnAttr(self: *LLVMIRModule, val: *const llvm.Value, attr_name: []const u8) void { + // TODO: improve this API, `addAttr(-1, attr_name)` + self.addAttr(val, std.math.maxInt(llvm.AttributeIndex), attr_name); + } + + pub fn fail(self: *LLVMIRModule, src: usize, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { + @setCold(true); + assert(self.err_msg == null); + self.err_msg = try Module.ErrorMsg.create(self.gpa, .{ + .file_scope = self.src_loc.file_scope, + .byte_offset = src, + }, format, args); + return error.CodegenFail; + } +}; |
