From 6d6cf598475ab8d2c3259002655ba04f1d056b2e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 11 Oct 2021 11:00:32 -0700 Subject: stage2: support nested structs and arrays and sret * Add AIR instructions: ret_ptr, ret_load - This allows Sema to be blissfully unaware of the backend's decision to implement by-val/by-ref semantics for struct/union/array types. Backends can lower these simply as alloc, load, ret instructions, or they can take advantage of them to use a result pointer. * Add AIR instruction: array_elem_val - Allows for better codegen for `Sema.elemVal`. * Implement calculation of ABI alignment and ABI size for unions. * Before appending the following AIR instructions to a block, resolveTypeLayout is called on the type: - call - return type - ret - return type - store_ptr - elem type * Sema: fix memory leak in `zirArrayInit` and other cleanups to this function. * x86_64: implement the full x86_64 C ABI according to the spec * Type: implement `intInfo` for error sets. * Type: implement `intTagType` for tagged unions. The Zig type tag `Fn` is now used exclusively for function bodies. Function pointers are modeled as `*const T` where `T` is a `Fn` type. * The `call` AIR instruction now allows a function pointer operand as well as a function operand. * Sema now has a coercion from function body to function pointer. * Function type syntax, e.g. `fn()void`, now returns zig tag type of Pointer with child Fn, rather than Fn directly. - I think this should probably be reverted. Will discuss the lang specs before doing this. Idea being that function pointers would need to be specified as `*const fn()void` rather than `fn() void`. LLVM backend: * Enable calling the panic handler (previously this just emitted `@breakpoint()` since the backend could not handle the panic function). * Implement sret * Introduce `isByRef` and implement it for structs and arrays. Types that are `isByRef` are now passed as pointers to functions, and e.g. `elem_val` will return a pointer instead of doing a load. * Move the function type creating code from `resolveLlvmFunction` to `llvmType` where it belongs; now there is only 1 instance of this logic instead of two. * Add the `nonnull` attribute to non-optional pointer parameters. * Fix `resolveGlobalDecl` not using fully-qualified names and not using the `decl_map`. * Implement `genTypedValue` for pointer-like optionals. * Fix memory leak when lowering `block` instruction and OOM occurs. * Implement volatile checks where relevant. --- src/codegen/c.zig | 84 ++++-- src/codegen/llvm.zig | 622 +++++++++++++++++++++++++++++------------- src/codegen/llvm/bindings.zig | 18 +- 3 files changed, 506 insertions(+), 218 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 6fcde5eead..466322c0a3 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -384,12 +384,6 @@ pub const DeclGen = struct { } }, .Fn => switch (val.tag()) { - .null_value, .zero => try writer.writeAll("NULL"), - .one => try writer.writeAll("1"), - .decl_ref => { - const decl = val.castTag(.decl_ref).?.data; - return dg.renderDeclValue(writer, ty, val, decl); - }, .function => { const decl = val.castTag(.function).?.data.owner_decl; return dg.renderDeclValue(writer, ty, val, decl); @@ -1026,6 +1020,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .is_non_null_ptr => try airIsNull(f, inst, "!=", "[0]"), .alloc => try airAlloc(f, inst), + .ret_ptr => try airRetPtr(f, inst), .assembly => try airAsm(f, inst), .block => try airBlock(f, inst), .bitcast => try airBitcast(f, inst), @@ -1036,6 +1031,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .bool_to_int => try airBoolToInt(f, inst), .load => try airLoad(f, inst), .ret => try airRet(f, inst), + .ret_load => try airRetLoad(f, inst), .store => try airStore(f, inst), .loop => try airLoop(f, inst), .cond_br => try airCondBr(f, inst), @@ -1081,6 +1077,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .ptr_elem_ptr => try airPtrElemPtr(f, inst), .slice_elem_val => try airSliceElemVal(f, inst, "["), .ptr_slice_elem_val => try airSliceElemVal(f, inst, "[0]["), + .array_elem_val => try airArrayElemVal(f, inst), .unwrap_errunion_payload => try airUnwrapErrUnionPay(f, inst), .unwrap_errunion_err => try airUnwrapErrUnionErr(f, inst), @@ -1148,6 +1145,22 @@ fn airSliceElemVal(f: *Function, inst: Air.Inst.Index, prefix: []const u8) !CVal return local; } +fn airArrayElemVal(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; + + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const array = try f.resolveInst(bin_op.lhs); + const index = try f.resolveInst(bin_op.rhs); + const writer = f.object.writer(); + const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); + try writer.writeAll(" = "); + try f.writeCValue(writer, array); + try writer.writeAll("["); + try f.writeCValue(writer, index); + try writer.writeAll("];\n"); + return local; +} + fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue { const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); @@ -1161,6 +1174,18 @@ fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue { return CValue{ .local_ref = local.local }; } +fn airRetPtr(f: *Function, inst: Air.Inst.Index) !CValue { + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); + + // First line: the variable used as data storage. + const elem_type = inst_ty.elemType(); + const local = try f.allocLocal(elem_type, .Mut); + try writer.writeAll(";\n"); + + return CValue{ .local_ref = local.local }; +} + fn airArg(f: *Function) CValue { const i = f.next_arg_index; f.next_arg_index += 1; @@ -1212,6 +1237,21 @@ fn airRet(f: *Function, inst: Air.Inst.Index) !CValue { return CValue.none; } +fn airRetLoad(f: *Function, inst: Air.Inst.Index) !CValue { + const un_op = f.air.instructions.items(.data)[inst].un_op; + const writer = f.object.writer(); + const ptr_ty = f.air.typeOf(un_op); + const ret_ty = ptr_ty.childType(); + if (!ret_ty.hasCodeGenBits()) { + try writer.writeAll("return;\n"); + } + const ptr = try f.resolveInst(un_op); + try writer.writeAll("return *"); + try f.writeCValue(writer, ptr); + try writer.writeAll(";\n"); + return CValue.none; +} + fn airIntCast(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; @@ -1559,7 +1599,12 @@ fn airCall(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const extra = f.air.extraData(Air.Call, pl_op.payload); const args = @bitCast([]const Air.Inst.Ref, f.air.extra[extra.end..][0..extra.data.args_len]); - const fn_ty = f.air.typeOf(pl_op.operand); + const callee_ty = f.air.typeOf(pl_op.operand); + const fn_ty = switch (callee_ty.zigTypeTag()) { + .Fn => callee_ty, + .Pointer => callee_ty.childType(), + else => unreachable, + }; const ret_ty = fn_ty.fnReturnType(); const unused_result = f.liveness.isUnused(inst); const writer = f.object.writer(); @@ -1574,16 +1619,21 @@ fn airCall(f: *Function, inst: Air.Inst.Index) !CValue { try writer.writeAll(" = "); } - if (f.air.value(pl_op.operand)) |func_val| { - const fn_decl = if (func_val.castTag(.extern_fn)) |extern_fn| - extern_fn.data - else if (func_val.castTag(.function)) |func_payload| - func_payload.data.owner_decl - else - unreachable; - - try f.object.dg.renderDeclName(fn_decl, writer); - } else { + callee: { + known: { + const fn_decl = fn_decl: { + const callee_val = f.air.value(pl_op.operand) orelse break :known; + break :fn_decl switch (callee_val.tag()) { + .extern_fn => callee_val.castTag(.extern_fn).?.data, + .function => callee_val.castTag(.function).?.data.owner_decl, + .decl_ref => callee_val.castTag(.decl_ref).?.data, + else => break :known, + }; + }; + try f.object.dg.renderDeclName(fn_decl, writer); + break :callee; + } + // Fall back to function pointer call. const callee = try f.resolveInst(pl_op.operand); try f.writeCValue(writer, callee); } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index ceaca07a49..b76a136d6f 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -21,6 +21,8 @@ const Type = @import("../type.zig").Type; const LazySrcLoc = Module.LazySrcLoc; +const Error = error{ OutOfMemory, CodegenFail }; + pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { const llvm_arch = switch (target.cpu.arch) { .arm => "arm", @@ -410,10 +412,18 @@ pub const Object = struct { // This gets the LLVM values from the function and stores them in `dg.args`. const fn_info = decl.ty.fnInfo(); - var args = try dg.gpa.alloc(*const llvm.Value, fn_info.param_types.len); + const ret_ty_by_ref = isByRef(fn_info.return_type); + const ret_ptr = if (ret_ty_by_ref) llvm_func.getParam(0) else null; + + var args = std.ArrayList(*const llvm.Value).init(dg.gpa); + defer args.deinit(); - for (args) |*arg, i| { - arg.* = llvm.getParam(llvm_func, @intCast(c_uint, i)); + const param_offset: c_uint = @boolToInt(ret_ptr != null); + for (fn_info.param_types) |param_ty| { + if (!param_ty.hasCodeGenBits()) continue; + + const llvm_arg_i = @intCast(c_uint, args.items.len) + param_offset; + try args.append(llvm_func.getParam(llvm_arg_i)); } // Remove all the basic blocks of a function in order to start over, generating @@ -434,7 +444,8 @@ pub const Object = struct { .context = dg.context, .dg = &dg, .builder = builder, - .args = args, + .ret_ptr = ret_ptr, + .args = args.toOwnedSlice(), .arg_index = 0, .func_inst_table = .{}, .entry_block = entry_block, @@ -556,7 +567,7 @@ pub const DeclGen = struct { gpa: *Allocator, err_msg: ?*Module.ErrorMsg, - fn todo(self: *DeclGen, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { + fn todo(self: *DeclGen, comptime format: []const u8, args: anytype) Error { @setCold(true); assert(self.err_msg == null); const src_loc = @as(LazySrcLoc, .{ .node_offset = 0 }).toSrcLoc(self.decl); @@ -591,50 +602,33 @@ pub const DeclGen = struct { }; const llvm_init = try self.genTypedValue(.{ .ty = decl.ty, .val = init_val }); - llvm.setInitializer(global, llvm_init); + global.setInitializer(llvm_init); } } /// If the llvm function does not exist, create it. /// Note that this can be called before the function's semantic analysis has /// completed, so if any attributes rely on that, they must be done in updateFunc, not here. - fn resolveLlvmFunction(self: *DeclGen, decl: *Module.Decl) !*const llvm.Value { - const gop = try self.object.decl_map.getOrPut(self.gpa, decl); + fn resolveLlvmFunction(dg: *DeclGen, decl: *Module.Decl) !*const llvm.Value { + const gop = try dg.object.decl_map.getOrPut(dg.gpa, decl); if (gop.found_existing) return gop.value_ptr.*; assert(decl.has_tv); const zig_fn_type = decl.ty; const fn_info = zig_fn_type.fnInfo(); - const return_type = fn_info.return_type; - - const llvm_param_buffer = try self.gpa.alloc(*const llvm.Type, fn_info.param_types.len); - defer self.gpa.free(llvm_param_buffer); - - var llvm_params_len: c_uint = 0; - for (fn_info.param_types) |param_ty| { - if (param_ty.hasCodeGenBits()) { - llvm_param_buffer[llvm_params_len] = try self.llvmType(param_ty); - llvm_params_len += 1; - } - } + const target = dg.module.getTarget(); + const sret = firstParamSRet(fn_info, target); - const llvm_ret_ty = if (!return_type.hasCodeGenBits()) - self.context.voidType() - else - try self.llvmType(return_type); + const return_type = fn_info.return_type; + const raw_llvm_ret_ty = try dg.llvmType(return_type); - const fn_type = llvm.functionType( - llvm_ret_ty, - llvm_param_buffer.ptr, - llvm_params_len, - .False, - ); - const llvm_addrspace = self.llvmAddressSpace(decl.@"addrspace"); + const fn_type = try dg.llvmType(zig_fn_type); - const fqn = try decl.getFullyQualifiedName(self.gpa); - defer self.gpa.free(fqn); + const fqn = try decl.getFullyQualifiedName(dg.gpa); + defer dg.gpa.free(fqn); - const llvm_fn = self.llvmModule().addFunctionInAddressSpace(fqn, fn_type, llvm_addrspace); + const llvm_addrspace = dg.llvmAddressSpace(decl.@"addrspace"); + const llvm_fn = dg.llvmModule().addFunctionInAddressSpace(fqn, fn_type, llvm_addrspace); gop.value_ptr.* = llvm_fn; const is_extern = decl.val.tag() == .extern_fn; @@ -643,53 +637,76 @@ pub const DeclGen = struct { llvm_fn.setUnnamedAddr(.True); } - if (self.module.comp.bin_file.options.skip_linker_dependencies) { + if (sret) { + dg.addArgAttr(llvm_fn, 0, "nonnull"); // Sret pointers must not be address 0 + dg.addArgAttr(llvm_fn, 0, "noalias"); + llvm_fn.addSretAttr(0, raw_llvm_ret_ty); + } + + // Set parameter attributes. + var llvm_param_i: c_uint = @boolToInt(sret); + for (fn_info.param_types) |param_ty| { + if (!param_ty.hasCodeGenBits()) continue; + + if (isByRef(param_ty)) { + dg.addArgAttr(llvm_fn, llvm_param_i, "nonnull"); + // TODO readonly, noalias, align + } + llvm_param_i += 1; + } + + if (dg.module.comp.bin_file.options.skip_linker_dependencies) { // The intent here is for compiler-rt and libc functions to not generate // infinite recursion. For example, if we are compiling the memcpy function, // and llvm detects that the body is equivalent to memcpy, it may replace the // body of memcpy with a call to memcpy, which would then cause a stack // overflow instead of performing memcpy. - self.addFnAttr(llvm_fn, "nobuiltin"); + dg.addFnAttr(llvm_fn, "nobuiltin"); } // TODO: more attributes. see codegen.cpp `make_fn_llvm_value`. - const target = self.module.getTarget(); if (fn_info.cc == .Naked) { - self.addFnAttr(llvm_fn, "naked"); + dg.addFnAttr(llvm_fn, "naked"); } else { llvm_fn.setFunctionCallConv(toLlvmCallConv(fn_info.cc, target)); } // Function attributes that are independent of analysis results of the function body. - if (!self.module.comp.bin_file.options.red_zone) { - self.addFnAttr(llvm_fn, "noredzone"); + if (!dg.module.comp.bin_file.options.red_zone) { + dg.addFnAttr(llvm_fn, "noredzone"); } - self.addFnAttr(llvm_fn, "nounwind"); - if (self.module.comp.unwind_tables) { - self.addFnAttr(llvm_fn, "uwtable"); + dg.addFnAttr(llvm_fn, "nounwind"); + if (dg.module.comp.unwind_tables) { + dg.addFnAttr(llvm_fn, "uwtable"); } - if (self.module.comp.bin_file.options.optimize_mode == .ReleaseSmall) { - self.addFnAttr(llvm_fn, "minsize"); - self.addFnAttr(llvm_fn, "optsize"); + if (dg.module.comp.bin_file.options.optimize_mode == .ReleaseSmall) { + dg.addFnAttr(llvm_fn, "minsize"); + dg.addFnAttr(llvm_fn, "optsize"); } - if (self.module.comp.bin_file.options.tsan) { - self.addFnAttr(llvm_fn, "sanitize_thread"); + if (dg.module.comp.bin_file.options.tsan) { + dg.addFnAttr(llvm_fn, "sanitize_thread"); } // TODO add target-cpu and target-features fn attributes if (return_type.isNoReturn()) { - self.addFnAttr(llvm_fn, "noreturn"); + dg.addFnAttr(llvm_fn, "noreturn"); } return llvm_fn; } - fn resolveGlobalDecl(self: *DeclGen, decl: *Module.Decl) error{ OutOfMemory, CodegenFail }!*const llvm.Value { - const llvm_module = self.object.llvm_module; - if (llvm_module.getNamedGlobal(decl.name)) |val| return val; - // TODO: remove this redundant `llvmType`, it is also called in `genTypedValue`. - const llvm_type = try self.llvmType(decl.ty); - const llvm_addrspace = self.llvmAddressSpace(decl.@"addrspace"); - return llvm_module.addGlobalInAddressSpace(llvm_type, decl.name, llvm_addrspace); + fn resolveGlobalDecl(dg: *DeclGen, decl: *Module.Decl) Error!*const llvm.Value { + const gop = try dg.object.decl_map.getOrPut(dg.gpa, decl); + if (gop.found_existing) return gop.value_ptr.*; + errdefer assert(dg.object.decl_map.remove(decl)); + + const fqn = try decl.getFullyQualifiedName(dg.gpa); + defer dg.gpa.free(fqn); + + const llvm_type = try dg.llvmType(decl.ty); + const llvm_addrspace = dg.llvmAddressSpace(decl.@"addrspace"); + const llvm_global = dg.object.llvm_module.addGlobalInAddressSpace(llvm_type, fqn, llvm_addrspace); + gop.value_ptr.* = llvm_global; + return llvm_global; } fn llvmAddressSpace(self: DeclGen, address_space: std.builtin.AddressSpace) c_uint { @@ -708,87 +725,87 @@ pub const DeclGen = struct { }; } - fn llvmType(self: *DeclGen, t: Type) error{ OutOfMemory, CodegenFail }!*const llvm.Type { - const gpa = self.gpa; + fn llvmType(dg: *DeclGen, t: Type) Error!*const llvm.Type { + const gpa = dg.gpa; log.debug("llvmType for {}", .{t}); switch (t.zigTypeTag()) { - .Void, .NoReturn => return self.context.voidType(), + .Void, .NoReturn => return dg.context.voidType(), .Int => { - const info = t.intInfo(self.module.getTarget()); - return self.context.intType(info.bits); + const info = t.intInfo(dg.module.getTarget()); + return dg.context.intType(info.bits); }, .Enum => { var buffer: Type.Payload.Bits = undefined; const int_ty = t.intTagType(&buffer); - const bit_count = int_ty.intInfo(self.module.getTarget()).bits; - return self.context.intType(bit_count); + const bit_count = int_ty.intInfo(dg.module.getTarget()).bits; + return dg.context.intType(bit_count); }, - .Float => switch (t.floatBits(self.module.getTarget())) { - 16 => return self.context.halfType(), - 32 => return self.context.floatType(), - 64 => return self.context.doubleType(), - 80 => return self.context.x86FP80Type(), - 128 => return self.context.fp128Type(), + .Float => switch (t.floatBits(dg.module.getTarget())) { + 16 => return dg.context.halfType(), + 32 => return dg.context.floatType(), + 64 => return dg.context.doubleType(), + 80 => return dg.context.x86FP80Type(), + 128 => return dg.context.fp128Type(), else => unreachable, }, - .Bool => return self.context.intType(1), + .Bool => return dg.context.intType(1), .Pointer => { if (t.isSlice()) { var buf: Type.SlicePtrFieldTypeBuffer = undefined; const ptr_type = t.slicePtrFieldType(&buf); const fields: [2]*const llvm.Type = .{ - try self.llvmType(ptr_type), - try self.llvmType(Type.initTag(.usize)), + try dg.llvmType(ptr_type), + try dg.llvmType(Type.initTag(.usize)), }; - return self.context.structType(&fields, fields.len, .False); + return dg.context.structType(&fields, fields.len, .False); } else { - const elem_type = try self.llvmType(t.elemType()); - const llvm_addrspace = self.llvmAddressSpace(t.ptrAddressSpace()); + const elem_type = try dg.llvmType(t.elemType()); + const llvm_addrspace = dg.llvmAddressSpace(t.ptrAddressSpace()); return elem_type.pointerType(llvm_addrspace); } }, .Array => { - const elem_type = try self.llvmType(t.elemType()); + const elem_type = try dg.llvmType(t.elemType()); const total_len = t.arrayLen() + @boolToInt(t.sentinel() != null); return elem_type.arrayType(@intCast(c_uint, total_len)); }, .Optional => { var buf: Type.Payload.ElemType = undefined; const child_type = t.optionalChild(&buf); - const payload_llvm_ty = try self.llvmType(child_type); + const payload_llvm_ty = try dg.llvmType(child_type); if (t.isPtrLikeOptional()) { return payload_llvm_ty; } const fields: [2]*const llvm.Type = .{ - payload_llvm_ty, self.context.intType(1), + payload_llvm_ty, dg.context.intType(1), }; - return self.context.structType(&fields, fields.len, .False); + return dg.context.structType(&fields, fields.len, .False); }, .ErrorUnion => { const error_type = t.errorUnionSet(); const payload_type = t.errorUnionPayload(); - const llvm_error_type = try self.llvmType(error_type); + const llvm_error_type = try dg.llvmType(error_type); if (!payload_type.hasCodeGenBits()) { return llvm_error_type; } - const llvm_payload_type = try self.llvmType(payload_type); + const llvm_payload_type = try dg.llvmType(payload_type); const fields: [2]*const llvm.Type = .{ llvm_error_type, llvm_payload_type }; - return self.context.structType(&fields, fields.len, .False); + return dg.context.structType(&fields, fields.len, .False); }, .ErrorSet => { - return self.context.intType(16); + return dg.context.intType(16); }, .Struct => { - const gop = try self.object.type_map.getOrPut(gpa, t); + const gop = try dg.object.type_map.getOrPut(gpa, t); if (gop.found_existing) return gop.value_ptr.*; // The Type memory is ephemeral; since we want to store a longer-lived // reference, we need to copy it here. - gop.key_ptr.* = try t.copy(&self.object.type_map_arena.allocator); + gop.key_ptr.* = try t.copy(&dg.object.type_map_arena.allocator); const struct_obj = t.castTag(.@"struct").?.data; assert(struct_obj.haveFieldTypes()); @@ -796,7 +813,7 @@ pub const DeclGen = struct { const name = try struct_obj.getFullyQualifiedName(gpa); defer gpa.free(name); - const llvm_struct_ty = self.context.structCreateNamed(name); + const llvm_struct_ty = dg.context.structCreateNamed(name); gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls var llvm_field_types: std.ArrayListUnmanaged(*const llvm.Type) = .{}; @@ -805,7 +822,7 @@ pub const DeclGen = struct { for (struct_obj.fields.values()) |field| { if (!field.ty.hasCodeGenBits()) continue; - llvm_field_types.appendAssumeCapacity(try self.llvmType(field.ty)); + llvm_field_types.appendAssumeCapacity(try dg.llvmType(field.ty)); } llvm_struct_ty.structSetBody( @@ -821,42 +838,56 @@ pub const DeclGen = struct { assert(union_obj.haveFieldTypes()); const enum_tag_ty = union_obj.tag_ty; - const enum_tag_llvm_ty = try self.llvmType(enum_tag_ty); + const enum_tag_llvm_ty = try dg.llvmType(enum_tag_ty); if (union_obj.onlyTagHasCodegenBits()) { return enum_tag_llvm_ty; } - const target = self.module.getTarget(); + const target = dg.module.getTarget(); const most_aligned_field_index = union_obj.mostAlignedField(target); const most_aligned_field = union_obj.fields.values()[most_aligned_field_index]; // TODO handle when the most aligned field is different than the // biggest sized field. const llvm_fields = [_]*const llvm.Type{ - try self.llvmType(most_aligned_field.ty), + try dg.llvmType(most_aligned_field.ty), enum_tag_llvm_ty, }; - return self.context.structType(&llvm_fields, llvm_fields.len, .False); + return dg.context.structType(&llvm_fields, llvm_fields.len, .False); }, .Fn => { - const ret_ty = try self.llvmType(t.fnReturnType()); - const params_len = t.fnParamLen(); - const llvm_params = try gpa.alloc(*const llvm.Type, params_len); - defer gpa.free(llvm_params); - for (llvm_params) |*llvm_param, i| { - llvm_param.* = try self.llvmType(t.fnParamType(i)); + const fn_info = t.fnInfo(); + const target = dg.module.getTarget(); + const sret = firstParamSRet(fn_info, target); + const return_type = fn_info.return_type; + const raw_llvm_ret_ty = try dg.llvmType(return_type); + const llvm_ret_ty = if (!return_type.hasCodeGenBits() or sret) + dg.context.voidType() + else + raw_llvm_ret_ty; + + var llvm_params = std.ArrayList(*const llvm.Type).init(dg.gpa); + defer llvm_params.deinit(); + + if (sret) { + try llvm_params.append(raw_llvm_ret_ty.pointerType(0)); + } + + for (fn_info.param_types) |param_ty| { + if (!param_ty.hasCodeGenBits()) continue; + + const raw_llvm_ty = try dg.llvmType(param_ty); + const actual_llvm_ty = if (!isByRef(param_ty)) raw_llvm_ty else raw_llvm_ty.pointerType(0); + try llvm_params.append(actual_llvm_ty); } - const is_var_args = t.fnIsVarArgs(); - const llvm_fn_ty = llvm.functionType( - ret_ty, - llvm_params.ptr, - @intCast(c_uint, llvm_params.len), - llvm.Bool.fromBool(is_var_args), + + return llvm.functionType( + llvm_ret_ty, + llvm_params.items.ptr, + @intCast(c_uint, llvm_params.items.len), + llvm.Bool.fromBool(fn_info.is_var_args), ); - // TODO make .Fn not both a pointer type and a prototype - const llvm_addrspace = self.llvmAddressSpace(.generic); - return llvm_fn_ty.pointerType(llvm_addrspace); }, .ComptimeInt => unreachable, .ComptimeFloat => unreachable, @@ -871,11 +902,11 @@ pub const DeclGen = struct { .Frame, .AnyFrame, .Vector, - => return self.todo("implement llvmType for type '{}'", .{t}), + => return dg.todo("implement llvmType for type '{}'", .{t}), } } - fn genTypedValue(self: *DeclGen, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value { + fn genTypedValue(self: *DeclGen, tv: TypedValue) Error!*const llvm.Value { if (tv.val.isUndef()) { const llvm_type = try self.llvmType(tv.ty); return llvm_type.getUndef(); @@ -961,9 +992,12 @@ pub const DeclGen = struct { } else { const decl = tv.val.castTag(.decl_ref).?.data; decl.alive = true; - const val = try self.resolveGlobalDecl(decl); const llvm_type = try self.llvmType(tv.ty); - return val.constBitCast(llvm_type); + const llvm_val = if (decl.ty.zigTypeTag() == .Fn) + try self.resolveLlvmFunction(decl) + else + try self.resolveGlobalDecl(decl); + return llvm_val.constBitCast(llvm_type); } }, .variable => { @@ -1047,17 +1081,23 @@ pub const DeclGen = struct { return self.todo("handle more array values", .{}); }, .Optional => { + var buf: Type.Payload.ElemType = undefined; + const payload_ty = tv.ty.optionalChild(&buf); + if (tv.ty.isPtrLikeOptional()) { - return self.todo("implement const of optional pointer", .{}); + if (tv.val.castTag(.opt_payload)) |payload| { + return self.genTypedValue(.{ .ty = payload_ty, .val = payload.data }); + } else { + const llvm_ty = try self.llvmType(tv.ty); + return llvm_ty.constNull(); + } } - var buf: Type.Payload.ElemType = undefined; - const payload_type = tv.ty.optionalChild(&buf); const is_pl = !tv.val.isNull(); const llvm_i1 = self.context.intType(1); const fields: [2]*const llvm.Value = .{ try self.genTypedValue(.{ - .ty = payload_type, + .ty = payload_ty, .val = if (tv.val.castTag(.opt_payload)) |pl| pl.data else Value.initTag(.undef), }), if (is_pl) llvm_i1.constAllOnes() else llvm_i1.constNull(), @@ -1068,7 +1108,6 @@ pub const DeclGen = struct { const fn_decl = switch (tv.val.tag()) { .extern_fn => tv.val.castTag(.extern_fn).?.data, .function => tv.val.castTag(.function).?.data.owner_decl, - .decl_ref => tv.val.castTag(.decl_ref).?.data, else => unreachable, }; fn_decl.alive = true; @@ -1153,10 +1192,14 @@ pub const DeclGen = struct { } } - fn addAttr(dg: *DeclGen, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void { + fn addAttr(dg: DeclGen, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void { return dg.addAttrInt(val, index, name, 0); } + fn addArgAttr(dg: DeclGen, fn_val: *const llvm.Value, param_index: u32, attr_name: []const u8) void { + return dg.addAttr(fn_val, param_index + 1, attr_name); + } + fn removeAttr(val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void { const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len); assert(kind_id != 0); @@ -1164,7 +1207,7 @@ pub const DeclGen = struct { } fn addAttrInt( - dg: *DeclGen, + dg: DeclGen, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8, @@ -1176,7 +1219,7 @@ pub const DeclGen = struct { val.addAttributeAtIndex(index, llvm_attr); } - fn addFnAttr(dg: *DeclGen, val: *const llvm.Value, name: []const u8) void { + fn addFnAttr(dg: DeclGen, val: *const llvm.Value, name: []const u8) void { dg.addAttr(val, std.math.maxInt(llvm.AttributeIndex), name); } @@ -1184,7 +1227,7 @@ pub const DeclGen = struct { removeAttr(fn_val, std.math.maxInt(llvm.AttributeIndex), name); } - fn addFnAttrInt(dg: *DeclGen, fn_val: *const llvm.Value, name: []const u8, int: u64) void { + fn addFnAttrInt(dg: DeclGen, fn_val: *const llvm.Value, name: []const u8, int: u64) void { return dg.addAttrInt(fn_val, std.math.maxInt(llvm.AttributeIndex), name, int); } @@ -1227,8 +1270,12 @@ pub const FuncGen = struct { /// in other instructions. This table is cleared before every function is generated. func_inst_table: std.AutoHashMapUnmanaged(Air.Inst.Index, *const llvm.Value), + /// If the return type isByRef, this is the result pointer. Otherwise null. + ret_ptr: ?*const llvm.Value, /// These fields are used to refer to the LLVM value of the function parameters /// in an Arg instruction. + /// This list may be shorter than the list according to the zig type system; + /// it omits 0-bit types. args: []*const llvm.Value, arg_index: usize, @@ -1258,7 +1305,7 @@ pub const FuncGen = struct { self.blocks.deinit(self.gpa); } - fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { + fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error { @setCold(true); return self.dg.todo(format, args); } @@ -1269,13 +1316,25 @@ pub const FuncGen = struct { fn resolveInst(self: *FuncGen, inst: Air.Inst.Ref) !*const llvm.Value { if (self.air.value(inst)) |val| { - return self.dg.genTypedValue(.{ .ty = self.air.typeOf(inst), .val = val }); + const ty = self.air.typeOf(inst); + const llvm_val = try self.dg.genTypedValue(.{ .ty = ty, .val = val }); + if (!isByRef(ty)) return llvm_val; + + // We have an LLVM value but we need to create a global constant and + // set the value as its initializer, and then return a pointer to the global. + const target = self.dg.module.getTarget(); + const global = self.dg.object.llvm_module.addGlobal(llvm_val.typeOf(), ""); + global.setInitializer(llvm_val); + global.setLinkage(.Private); + global.setGlobalConstant(.True); + global.setAlignment(ty.abiAlignment(target)); + return global; } const inst_index = Air.refToIndex(inst).?; return self.func_inst_table.get(inst_index).?; } - fn genBody(self: *FuncGen, body: []const Air.Inst.Index) error{ OutOfMemory, CodegenFail }!void { + fn genBody(self: *FuncGen, body: []const Air.Inst.Index) Error!void { const air_tags = self.air.instructions.items(.tag); for (body) |inst| { const opt_value: ?*const llvm.Value = switch (air_tags[inst]) { @@ -1320,6 +1379,7 @@ pub const FuncGen = struct { .is_err_ptr => try self.airIsErr(inst, .NE, true), .alloc => try self.airAlloc(inst), + .ret_ptr => try self.airRetPtr(inst), .arg => try self.airArg(inst), .bitcast => try self.airBitCast(inst), .bool_to_int => try self.airBoolToInt(inst), @@ -1338,6 +1398,7 @@ pub const FuncGen = struct { .loop => try self.airLoop(inst), .not => try self.airNot(inst), .ret => try self.airRet(inst), + .ret_load => try self.airRetLoad(inst), .store => try self.airStore(inst), .assembly => try self.airAssembly(inst), .slice_ptr => try self.airSliceField(inst, 0), @@ -1370,6 +1431,7 @@ pub const FuncGen = struct { .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2), .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3), + .array_elem_val => try self.airArrayElemVal(inst), .slice_elem_val => try self.airSliceElemVal(inst), .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst), .ptr_elem_val => try self.airPtrElemVal(inst), @@ -1405,40 +1467,73 @@ pub const FuncGen = struct { const pl_op = self.air.instructions.items(.data)[inst].pl_op; const extra = self.air.extraData(Air.Call, pl_op.payload); const args = @bitCast([]const Air.Inst.Ref, self.air.extra[extra.end..][0..extra.data.args_len]); - const zig_fn_type = self.air.typeOf(pl_op.operand); - const return_type = zig_fn_type.fnReturnType(); + const callee_ty = self.air.typeOf(pl_op.operand); + const zig_fn_ty = switch (callee_ty.zigTypeTag()) { + .Fn => callee_ty, + .Pointer => callee_ty.childType(), + else => unreachable, + }; + const fn_info = zig_fn_ty.fnInfo(); + const return_type = fn_info.return_type; + const llvm_ret_ty = try self.dg.llvmType(return_type); const llvm_fn = try self.resolveInst(pl_op.operand); const target = self.dg.module.getTarget(); + const sret = firstParamSRet(fn_info, target); - const llvm_param_vals = try self.gpa.alloc(*const llvm.Value, args.len); - defer self.gpa.free(llvm_param_vals); + var llvm_args = std.ArrayList(*const llvm.Value).init(self.gpa); + defer llvm_args.deinit(); + + const ret_ptr = if (!sret) null else blk: { + const ret_ptr = self.buildAlloca(llvm_ret_ty); + ret_ptr.setAlignment(return_type.abiAlignment(target)); + try llvm_args.append(ret_ptr); + break :blk ret_ptr; + }; for (args) |arg, i| { - llvm_param_vals[i] = try self.resolveInst(arg); + const param_ty = fn_info.param_types[i]; + if (!param_ty.hasCodeGenBits()) continue; + + try llvm_args.append(try self.resolveInst(arg)); } const call = self.builder.buildCall( llvm_fn, - llvm_param_vals.ptr, - @intCast(c_uint, args.len), - toLlvmCallConv(zig_fn_type.fnCallingConvention(), target), + llvm_args.items.ptr, + @intCast(c_uint, llvm_args.items.len), + toLlvmCallConv(zig_fn_ty.fnCallingConvention(), target), .Auto, "", ); if (return_type.isNoReturn()) { _ = self.builder.buildUnreachable(); + return null; + } else if (self.liveness.isUnused(inst) or !return_type.hasCodeGenBits()) { + return null; + } else if (sret) { + call.setCallSret(llvm_ret_ty); + return ret_ptr; + } else { + return call; } - - // No need to store the LLVM value if the return type is void or noreturn - if (!return_type.hasCodeGenBits()) return null; - - return call; } fn airRet(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { const un_op = self.air.instructions.items(.data)[inst].un_op; - if (!self.air.typeOf(un_op).hasCodeGenBits()) { + const ret_ty = self.air.typeOf(un_op); + if (self.ret_ptr) |ret_ptr| { + const operand = try self.resolveInst(un_op); + var ptr_ty_payload: Type.Payload.ElemType = .{ + .base = .{ .tag = .single_mut_pointer }, + .data = ret_ty, + }; + const ptr_ty = Type.initPayload(&ptr_ty_payload.base); + self.store(ret_ptr, ptr_ty, operand, .NotAtomic); + _ = self.builder.buildRetVoid(); + return null; + } + if (!ret_ty.hasCodeGenBits()) { _ = self.builder.buildRetVoid(); return null; } @@ -1447,6 +1542,20 @@ pub const FuncGen = struct { return null; } + fn airRetLoad(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const ptr_ty = self.air.typeOf(un_op); + const ret_ty = ptr_ty.childType(); + if (!ret_ty.hasCodeGenBits() or isByRef(ret_ty)) { + _ = self.builder.buildRetVoid(); + return null; + } + const ptr = try self.resolveInst(un_op); + const loaded = self.builder.buildLoad(ptr, ""); + _ = self.builder.buildRet(loaded); + return null; + } + fn airCmp(self: *FuncGen, inst: Air.Inst.Index, op: math.CompareOperator) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -1491,19 +1600,18 @@ pub const FuncGen = struct { const body = self.air.extra[extra.end..][0..extra.data.body_len]; 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); + var break_bbs: BreakBasicBlocks = .{}; + defer break_bbs.deinit(self.gpa); + + var break_vals: BreakValues = .{}; + defer break_vals.deinit(self.gpa); + try self.blocks.putNoClobber(self.gpa, inst, .{ .parent_bb = parent_bb, .break_bbs = &break_bbs, .break_vals = &break_vals, }); - defer { - assert(self.blocks.remove(inst)); - break_bbs.deinit(self.gpa); - break_vals.deinit(self.gpa); - } + defer assert(self.blocks.remove(inst)); try self.genBody(body); @@ -1514,7 +1622,18 @@ pub const FuncGen = struct { const inst_ty = self.air.typeOfIndex(inst); if (!inst_ty.hasCodeGenBits()) return null; - const phi_node = self.builder.buildPhi(try self.dg.llvmType(inst_ty), ""); + const raw_llvm_ty = try self.dg.llvmType(inst_ty); + + // If the zig tag type is a function, this represents an actual function body; not + // a pointer to it. LLVM IR allows the call instruction to use function bodies instead + // of function pointers, however the phi makes it a runtime value and therefore + // the LLVM type has to be wrapped in a pointer. + const llvm_ty = if (inst_ty.zigTypeTag() == .Fn) + raw_llvm_ty.pointerType(0) + else + raw_llvm_ty; + + const phi_node = self.builder.buildPhi(llvm_ty, ""); phi_node.addIncoming( break_vals.items.ptr, break_bbs.items.ptr, @@ -1657,25 +1776,23 @@ pub const FuncGen = struct { } fn airSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - const is_volatile = false; // TODO - if (!is_volatile and self.liveness.isUnused(inst)) - return null; - const bin_op = self.air.instructions.items(.data)[inst].bin_op; - const lhs = try self.resolveInst(bin_op.lhs); - const rhs = try self.resolveInst(bin_op.rhs); - const base_ptr = self.builder.buildExtractValue(lhs, 0, ""); - const indices: [1]*const llvm.Value = .{rhs}; + const slice_ty = self.air.typeOf(bin_op.lhs); + if (!slice_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; + + const slice = try self.resolveInst(bin_op.lhs); + const index = try self.resolveInst(bin_op.rhs); + const base_ptr = self.builder.buildExtractValue(slice, 0, ""); + const indices: [1]*const llvm.Value = .{index}; const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); - return self.builder.buildLoad(ptr, ""); + return self.load(ptr, slice_ty); } fn airPtrSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - const is_volatile = false; // TODO - if (!is_volatile and self.liveness.isUnused(inst)) - return null; - const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const slice_ty = self.air.typeOf(bin_op.lhs).childType(); + if (!slice_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; + const lhs = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); @@ -1686,18 +1803,35 @@ pub const FuncGen = struct { const indices: [1]*const llvm.Value = .{rhs}; const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); - return self.builder.buildLoad(ptr, ""); + return self.load(ptr, slice_ty); } - fn airPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - const is_volatile = false; // TODO - if (!is_volatile and self.liveness.isUnused(inst)) - return null; + fn airArrayElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const array_ty = self.air.typeOf(bin_op.lhs); + const array_llvm_val = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + assert(isByRef(array_ty)); + const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs }; + const elem_ptr = self.builder.buildInBoundsGEP(array_llvm_val, &indices, indices.len, ""); + const elem_ty = array_ty.childType(); + if (isByRef(elem_ty)) { + return elem_ptr; + } else { + return self.builder.buildLoad(elem_ptr, ""); + } + } + + fn airPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const ptr_ty = self.air.typeOf(bin_op.lhs); + if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; + const base_ptr = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); - const ptr = if (self.air.typeOf(bin_op.lhs).isSinglePointer()) ptr: { + const ptr = if (ptr_ty.isSinglePointer()) ptr: { // If this is a single-item pointer to an array, we need another index in the GEP. const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs }; break :ptr self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); @@ -1705,7 +1839,7 @@ pub const FuncGen = struct { const indices: [1]*const llvm.Value = .{rhs}; break :ptr self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); }; - return self.builder.buildLoad(ptr, ""); + return self.load(ptr, ptr_ty); } fn airPtrElemPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { @@ -1727,17 +1861,16 @@ pub const FuncGen = struct { } fn airPtrPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - const is_volatile = false; // TODO - if (!is_volatile and self.liveness.isUnused(inst)) - return null; - const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const ptr_ty = self.air.typeOf(bin_op.lhs).childType(); + if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; + const lhs = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); const base_ptr = self.builder.buildLoad(lhs, ""); const indices: [1]*const llvm.Value = .{rhs}; const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); - return self.builder.buildLoad(ptr, ""); + return self.load(ptr, ptr_ty); } fn airStructFieldPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { @@ -1770,9 +1903,19 @@ pub const FuncGen = struct { const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data; const struct_ty = self.air.typeOf(struct_field.struct_operand); - const struct_byval = try self.resolveInst(struct_field.struct_operand); + const struct_llvm_val = try self.resolveInst(struct_field.struct_operand); const field_index = llvmFieldIndex(struct_ty, struct_field.field_index); - return self.builder.buildExtractValue(struct_byval, field_index, ""); + if (isByRef(struct_ty)) { + const field_ptr = self.builder.buildStructGEP(struct_llvm_val, field_index, ""); + const field_ty = struct_ty.structFieldType(struct_field.field_index); + if (isByRef(field_ty)) { + return field_ptr; + } else { + return self.builder.buildLoad(field_ptr, ""); + } + } else { + return self.builder.buildExtractValue(struct_llvm_val, field_index, ""); + } } fn airNot(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { @@ -2465,17 +2608,21 @@ pub const FuncGen = struct { self.arg_index += 1; const inst_ty = self.air.typeOfIndex(inst); - const ptr_val = self.buildAlloca(try self.dg.llvmType(inst_ty)); - _ = self.builder.buildStore(arg_val, ptr_val); - return self.builder.buildLoad(ptr_val, ""); + if (isByRef(inst_ty)) { + // TODO declare debug variable + return arg_val; + } else { + const ptr_val = self.buildAlloca(try self.dg.llvmType(inst_ty)); + _ = self.builder.buildStore(arg_val, ptr_val); + // TODO declare debug variable + return arg_val; + } } fn airAlloc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; - // buildAlloca expects the pointee type, not the pointer type, so assert that - // a Payload.PointerSimple is passed to the alloc instruction. const ptr_ty = self.air.typeOfIndex(inst); - const pointee_type = ptr_ty.elemType(); + const pointee_type = ptr_ty.childType(); if (!pointee_type.hasCodeGenBits()) return null; const pointee_llvm_ty = try self.dg.llvmType(pointee_type); const target = self.dg.module.getTarget(); @@ -2484,6 +2631,19 @@ pub const FuncGen = struct { return alloca_inst; } + fn airRetPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + const ptr_ty = self.air.typeOfIndex(inst); + const ret_ty = ptr_ty.childType(); + if (!ret_ty.hasCodeGenBits()) return null; + if (self.ret_ptr) |ret_ptr| return ret_ptr; + const ret_llvm_ty = try self.dg.llvmType(ret_ty); + const target = self.dg.module.getTarget(); + const alloca_inst = self.buildAlloca(ret_llvm_ty); + alloca_inst.setAlignment(ptr_ty.ptrAlignment(target)); + return alloca_inst; + } + /// 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: *FuncGen, t: *const llvm.Type) *const llvm.Value { @@ -2513,7 +2673,7 @@ pub const FuncGen = struct { const dest_ptr = try self.resolveInst(bin_op.lhs); const ptr_ty = self.air.typeOf(bin_op.lhs); const src_operand = try self.resolveInst(bin_op.rhs); - _ = self.store(dest_ptr, ptr_ty, src_operand); + self.store(dest_ptr, ptr_ty, src_operand, .NotAtomic); return null; } @@ -2658,11 +2818,11 @@ pub const FuncGen = struct { if (opt_abi_ty) |abi_ty| { // operand needs widening and truncating const casted_ptr = self.builder.buildBitCast(ptr, abi_ty.pointerType(0), ""); - const load_inst = self.load(casted_ptr, ptr_ty); + const load_inst = self.load(casted_ptr, ptr_ty).?; load_inst.setOrdering(ordering); return self.builder.buildTrunc(load_inst, try self.dg.llvmType(operand_ty), ""); } - const load_inst = self.load(ptr, ptr_ty); + const load_inst = self.load(ptr, ptr_ty).?; load_inst.setOrdering(ordering); return load_inst; } @@ -2673,10 +2833,11 @@ pub const FuncGen = struct { ordering: llvm.AtomicOrdering, ) !?*const llvm.Value { const bin_op = self.air.instructions.items(.data)[inst].bin_op; - var ptr = try self.resolveInst(bin_op.lhs); const ptr_ty = self.air.typeOf(bin_op.lhs); + const operand_ty = ptr_ty.childType(); + if (!operand_ty.hasCodeGenBits()) return null; + var ptr = try self.resolveInst(bin_op.lhs); var element = try self.resolveInst(bin_op.rhs); - const operand_ty = ptr_ty.elemType(); const opt_abi_ty = self.dg.getAtomicAbiType(operand_ty, false); if (opt_abi_ty) |abi_ty| { @@ -2688,8 +2849,7 @@ pub const FuncGen = struct { element = self.builder.buildZExt(element, abi_ty, ""); } } - const store_inst = self.store(ptr, ptr_ty, element); - store_inst.setOrdering(ordering); + self.store(ptr, ptr_ty, element, ordering); return null; } @@ -2724,10 +2884,9 @@ pub const FuncGen = struct { const src_ptr = try self.resolveInst(extra.lhs); const src_ptr_ty = self.air.typeOf(extra.lhs); const len = try self.resolveInst(extra.rhs); - const u8_llvm_ty = self.context.intType(8); - const ptr_u8_llvm_ty = u8_llvm_ty.pointerType(0); - const dest_ptr_u8 = self.builder.buildBitCast(dest_ptr, ptr_u8_llvm_ty, ""); - const src_ptr_u8 = self.builder.buildBitCast(src_ptr, ptr_u8_llvm_ty, ""); + const llvm_ptr_u8 = self.context.intType(8).pointerType(0); + const dest_ptr_u8 = self.builder.buildBitCast(dest_ptr, llvm_ptr_u8, ""); + const src_ptr_u8 = self.builder.buildBitCast(src_ptr, llvm_ptr_u8, ""); const is_volatile = src_ptr_ty.isVolatilePtr() or dest_ptr_ty.isVolatilePtr(); const target = self.dg.module.getTarget(); _ = self.builder.buildMemCpy( @@ -2843,7 +3002,10 @@ pub const FuncGen = struct { return self.llvmModule().getIntrinsicDeclaration(id, null, 0); } - fn load(self: *FuncGen, ptr: *const llvm.Value, ptr_ty: Type) *const llvm.Value { + fn load(self: *FuncGen, ptr: *const llvm.Value, ptr_ty: Type) ?*const llvm.Value { + const pointee_ty = ptr_ty.childType(); + if (!pointee_ty.hasCodeGenBits()) return null; + if (isByRef(pointee_ty)) return ptr; const llvm_inst = self.builder.buildLoad(ptr, ""); const target = self.dg.module.getTarget(); llvm_inst.setAlignment(ptr_ty.ptrAlignment(target)); @@ -2856,12 +3018,31 @@ pub const FuncGen = struct { ptr: *const llvm.Value, ptr_ty: Type, elem: *const llvm.Value, - ) *const llvm.Value { - const llvm_inst = self.builder.buildStore(elem, ptr); + ordering: llvm.AtomicOrdering, + ) void { + const elem_ty = ptr_ty.childType(); + if (!elem_ty.hasCodeGenBits()) { + return; + } const target = self.dg.module.getTarget(); - llvm_inst.setAlignment(ptr_ty.ptrAlignment(target)); - llvm_inst.setVolatile(llvm.Bool.fromBool(ptr_ty.isVolatilePtr())); - return llvm_inst; + if (!isByRef(elem_ty)) { + const store_inst = self.builder.buildStore(elem, ptr); + store_inst.setOrdering(ordering); + store_inst.setAlignment(ptr_ty.ptrAlignment(target)); + store_inst.setVolatile(llvm.Bool.fromBool(ptr_ty.isVolatilePtr())); + return; + } + assert(ordering == .NotAtomic); + const llvm_ptr_u8 = self.context.intType(8).pointerType(0); + const size_bytes = elem_ty.abiSize(target); + _ = self.builder.buildMemCpy( + self.builder.buildBitCast(ptr, llvm_ptr_u8, ""), + ptr_ty.ptrAlignment(target), + self.builder.buildBitCast(elem, llvm_ptr_u8, ""), + elem_ty.abiAlignment(target), + self.context.intType(Type.usize.intInfo(target).bits).constInt(size_bytes, .False), + ptr_ty.isVolatilePtr(), + ); } }; @@ -3113,3 +3294,54 @@ fn llvmFieldIndex(ty: Type, index: u32) c_uint { } return result; } + +fn firstParamSRet(fn_info: Type.Payload.Function.Data, target: std.Target) bool { + switch (fn_info.cc) { + .Unspecified, .Inline => return isByRef(fn_info.return_type), + .C => {}, + else => return false, + } + switch (target.cpu.arch) { + .mips, .mipsel => return false, + .x86_64 => switch (target.os.tag) { + .windows => return @import("../arch/x86_64/abi.zig").classifyWindows(fn_info.return_type, target) == .memory, + else => return @import("../arch/x86_64/abi.zig").classifySystemV(fn_info.return_type, target)[0] == .memory, + }, + else => return false, // TODO investigate C ABI for other architectures + } +} + +fn isByRef(ty: Type) bool { + switch (ty.zigTypeTag()) { + .Type, + .ComptimeInt, + .ComptimeFloat, + .EnumLiteral, + .Undefined, + .Null, + .BoundFn, + .Opaque, + => unreachable, + + .NoReturn, + .Void, + .Bool, + .Int, + .Float, + .Pointer, + .ErrorSet, + .Fn, + .Enum, + .Vector, + .AnyFrame, + => return false, + + .Array, .Struct, .Frame => return ty.hasCodeGenBits(), + .Union => return ty.hasCodeGenBits(), + .ErrorUnion => return isByRef(ty.errorUnionPayload()), + .Optional => { + var buf: Type.Payload.ElemType = undefined; + return isByRef(ty.optionalChild(&buf)); + }, + } +} diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index a3ccea0951..f4a6634efb 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -163,6 +163,18 @@ pub const Value = opaque { pub const deleteFunction = LLVMDeleteFunction; extern fn LLVMDeleteFunction(Fn: *const Value) void; + + pub const addSretAttr = ZigLLVMAddSretAttr; + extern fn ZigLLVMAddSretAttr(fn_ref: *const Value, ArgNo: c_uint, type_val: *const Type) void; + + pub const setCallSret = ZigLLVMSetCallSret; + extern fn ZigLLVMSetCallSret(Call: *const Value, return_type: *const Type) void; + + pub const getParam = LLVMGetParam; + extern fn LLVMGetParam(Fn: *const Value, Index: c_uint) *const Value; + + pub const setInitializer = LLVMSetInitializer; + extern fn LLVMSetInitializer(GlobalVar: *const Value, ConstantVal: *const Value) void; }; pub const Type = opaque { @@ -292,12 +304,6 @@ pub const VerifierFailureAction = enum(c_int) { pub const constNeg = LLVMConstNeg; extern fn LLVMConstNeg(ConstantVal: *const Value) *const Value; -pub const setInitializer = LLVMSetInitializer; -extern fn LLVMSetInitializer(GlobalVar: *const Value, ConstantVal: *const Value) void; - -pub const getParam = LLVMGetParam; -extern fn LLVMGetParam(Fn: *const Value, Index: c_uint) *const Value; - pub const getEnumAttributeKindForName = LLVMGetEnumAttributeKindForName; extern fn LLVMGetEnumAttributeKindForName(Name: [*]const u8, SLen: usize) c_uint; -- cgit v1.2.3