Commit d968d9d103

Veikka Tuominen <git@vexu.eu>
2022-11-20 19:14:03
llvm: add attributes to the arguments of function pointer calls
Closes #13605
1 parent 9e276d3
Changed files (6)
src/codegen/llvm/bindings.zig
@@ -88,8 +88,8 @@ pub const Context = opaque {
 };
 
 pub const Value = opaque {
-    pub const addAttributeAtIndex = LLVMAddAttributeAtIndex;
-    extern fn LLVMAddAttributeAtIndex(*Value, Idx: AttributeIndex, A: *Attribute) void;
+    pub const addAttributeAtIndex = ZigLLVMAddAttributeAtIndex;
+    extern fn ZigLLVMAddAttributeAtIndex(*Value, Idx: AttributeIndex, A: *Attribute) void;
 
     pub const removeEnumAttributeAtIndex = LLVMRemoveEnumAttributeAtIndex;
     extern fn LLVMRemoveEnumAttributeAtIndex(F: *Value, Idx: AttributeIndex, KindID: c_uint) void;
src/codegen/llvm.zig
@@ -4700,9 +4700,9 @@ pub const FuncGen = struct {
             break :blk ret_ptr;
         };
 
-        if (fn_info.return_type.isError() and
-            self.dg.module.comp.bin_file.options.error_return_tracing)
-        {
+        const err_return_tracing = fn_info.return_type.isError() and
+            self.dg.module.comp.bin_file.options.error_return_tracing;
+        if (err_return_tracing) {
             try llvm_args.append(self.err_ret_trace.?);
         }
 
@@ -4890,6 +4890,66 @@ pub const FuncGen = struct {
             "",
         );
 
+        if (callee_ty.zigTypeTag() == .Pointer) {
+            // Add argument attributes for function pointer calls.
+            it = iterateParamTypes(self.dg, fn_info);
+            it.llvm_index += @boolToInt(sret);
+            it.llvm_index += @boolToInt(err_return_tracing);
+            while (it.next()) |lowering| switch (lowering) {
+                .byval => {
+                    const param_index = it.zig_index - 1;
+                    const param_ty = fn_info.param_types[param_index];
+                    if (!isByRef(param_ty)) {
+                        self.dg.addByValParamAttrs(call, param_ty, param_index, fn_info, it.llvm_index - 1);
+                    }
+                },
+                .byref => {
+                    const param_index = it.zig_index - 1;
+                    const param_ty = fn_info.param_types[param_index];
+                    const param_llvm_ty = try self.dg.lowerType(param_ty);
+                    const alignment = param_ty.abiAlignment(target);
+                    self.dg.addByRefParamAttrs(call, it.llvm_index - 1, alignment, it.byval_attr, param_llvm_ty);
+                },
+                .byref_mut => {
+                    self.dg.addArgAttr(call, it.llvm_index - 1, "noundef");
+                },
+                // No attributes needed for these.
+                .no_bits,
+                .abi_sized_int,
+                .multiple_llvm_types,
+                .as_u16,
+                .float_array,
+                .i32_array,
+                .i64_array,
+                => continue,
+
+                .slice => {
+                    assert(!it.byval_attr);
+                    const param_ty = fn_info.param_types[it.zig_index - 1];
+                    const ptr_info = param_ty.ptrInfo().data;
+                    const llvm_arg_i = it.llvm_index - 2;
+
+                    if (math.cast(u5, it.zig_index - 1)) |i| {
+                        if (@truncate(u1, fn_info.noalias_bits >> i) != 0) {
+                            self.dg.addArgAttr(call, llvm_arg_i, "noalias");
+                        }
+                    }
+                    if (param_ty.zigTypeTag() != .Optional) {
+                        self.dg.addArgAttr(call, llvm_arg_i, "nonnull");
+                    }
+                    if (!ptr_info.mutable) {
+                        self.dg.addArgAttr(call, llvm_arg_i, "readonly");
+                    }
+                    if (ptr_info.@"align" != 0) {
+                        self.dg.addArgAttrInt(call, llvm_arg_i, "align", ptr_info.@"align");
+                    } else {
+                        const elem_align = @max(ptr_info.pointee_type.abiAlignment(target), 1);
+                        self.dg.addArgAttrInt(call, llvm_arg_i, "align", elem_align);
+                    }
+                },
+            };
+        }
+
         if (return_type.isNoReturn() and attr != .AlwaysTail) {
             _ = self.builder.buildUnreachable();
             return null;
@@ -10469,6 +10529,7 @@ const ParamTypeIterator = struct {
                 it.llvm_index += 1;
                 var buf: Type.Payload.ElemType = undefined;
                 if (ty.isSlice() or (ty.zigTypeTag() == .Optional and ty.optionalChild(&buf).isSlice())) {
+                    it.llvm_index += 1;
                     return .slice;
                 } else if (isByRef(ty)) {
                     return .byref;
src/zig_llvm.cpp
@@ -444,6 +444,15 @@ LLVMValueRef ZigLLVMBuildCall(LLVMBuilderRef B, LLVMTypeRef Ty, LLVMValueRef Fn,
     return wrap(call_inst);
 }
 
+void ZigLLVMAddAttributeAtIndex(LLVMValueRef Val, unsigned Idx, LLVMAttributeRef A) {
+    if (isa<Function>(unwrap(Val))) {
+        unwrap<Function>(Val)->addAttributeAtIndex(Idx, unwrap(A));
+    } else {
+        unwrap<CallInst>(Val)->addAttributeAtIndex(Idx, unwrap(A));
+    }
+}
+
+
 LLVMValueRef ZigLLVMBuildMemCpy(LLVMBuilderRef B, LLVMValueRef Dst, unsigned DstAlign,
         LLVMValueRef Src, unsigned SrcAlign, LLVMValueRef Size, bool isVolatile)
 {
@@ -1065,12 +1074,21 @@ void ZigLLVMSetFastMath(LLVMBuilderRef builder_wrapped, bool on_state) {
     }
 }
 
-void ZigLLVMAddByValAttr(LLVMValueRef fn_ref, unsigned ArgNo, LLVMTypeRef type_val) {
-    Function *func = unwrap<Function>(fn_ref);
-    AttrBuilder attr_builder(func->getContext());
-    Type *llvm_type = unwrap<Type>(type_val);
-    attr_builder.addByValAttr(llvm_type);
-    func->addParamAttrs(ArgNo, attr_builder);
+void ZigLLVMAddByValAttr(LLVMValueRef Val, unsigned ArgNo, LLVMTypeRef type_val) {
+    if (isa<Function>(unwrap(Val))) {
+        Function *func = unwrap<Function>(Val);
+        AttrBuilder attr_builder(func->getContext());
+        Type *llvm_type = unwrap<Type>(type_val);
+        attr_builder.addByValAttr(llvm_type);
+        func->addParamAttrs(ArgNo, attr_builder);
+    } else {
+        CallInst *call = unwrap<CallInst>(Val);
+        AttrBuilder attr_builder(call->getContext());
+        Type *llvm_type = unwrap<Type>(type_val);
+        attr_builder.addByValAttr(llvm_type);
+        // NOTE: +1 here since index 0 refers to the return value
+        call->addAttributeAtIndex(ArgNo + 1, attr_builder.getAttribute(Attribute::ByVal));
+    }
 }
 
 void ZigLLVMAddSretAttr(LLVMValueRef fn_ref, LLVMTypeRef type_val) {
src/zig_llvm.h
@@ -129,6 +129,8 @@ ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildCall(LLVMBuilderRef B, LLVMTypeRef functio
         LLVMValueRef Fn, LLVMValueRef *Args, unsigned NumArgs, enum ZigLLVM_CallingConv CC,
         enum ZigLLVM_CallAttr attr, const char *Name);
 
+ZIG_EXTERN_C void ZigLLVMAddAttributeAtIndex(LLVMValueRef Val, unsigned Idx, LLVMAttributeRef A);
+
 ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildMemCpy(LLVMBuilderRef B, LLVMValueRef Dst, unsigned DstAlign,
         LLVMValueRef Src, unsigned SrcAlign, LLVMValueRef Size, bool isVolatile);
 
test/c_abi/cfuncs.c
@@ -842,3 +842,32 @@ struct ByRef c_modify_by_ref_param(struct ByRef in) {
     in.val = 42;
     return in;
 }
+
+struct ByVal {
+    struct {
+        unsigned long x;
+        unsigned long y;
+        unsigned long z;
+    } origin;
+    struct {
+        unsigned long width;
+        unsigned long height;
+        unsigned long depth;
+    } size;
+};
+
+void c_func_ptr_byval(void *a, void *b, struct ByVal in, unsigned long c, void *d, unsigned long e) {
+    assert_or_panic((intptr_t)a == 1);
+    assert_or_panic((intptr_t)b == 2);
+
+    assert_or_panic(in.origin.x == 9);
+    assert_or_panic(in.origin.y == 10);
+    assert_or_panic(in.origin.z == 11);
+    assert_or_panic(in.size.width == 12);
+    assert_or_panic(in.size.height == 13);
+    assert_or_panic(in.size.depth == 14);
+
+    assert_or_panic(c == 3);
+    assert_or_panic((intptr_t)d == 4);
+    assert_or_panic(e == 5);
+}
test/c_abi/main.zig
@@ -1001,3 +1001,34 @@ test "C function modifies by ref param" {
     const res = c_modify_by_ref_param(.{ .val = 1, .arr = undefined });
     try expect(res.val == 42);
 }
+
+const ByVal = extern struct {
+    origin: extern struct {
+        x: c_ulong,
+        y: c_ulong,
+        z: c_ulong,
+    },
+    size: extern struct {
+        width: c_ulong,
+        height: c_ulong,
+        depth: c_ulong,
+    },
+};
+
+extern fn c_func_ptr_byval(*anyopaque, *anyopaque, ByVal, c_ulong, *anyopaque, c_ulong) void;
+test "C function that takes byval struct called via function pointer" {
+    if (comptime builtin.cpu.arch.isPPC()) return error.SkipZigTest;
+
+    var fn_ptr = &c_func_ptr_byval;
+    fn_ptr(
+        @intToPtr(*anyopaque, 1),
+        @intToPtr(*anyopaque, 2),
+        ByVal{
+            .origin = .{ .x = 9, .y = 10, .z = 11 },
+            .size = .{ .width = 12, .height = 13, .depth = 14 },
+        },
+        @as(c_ulong, 3),
+        @intToPtr(*anyopaque, 4),
+        @as(c_ulong, 5),
+    );
+}