authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-07-07 18:23:07-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-07-07 18:24:34-07:00
log3a03872af76652515e467c1f33d918ead2c0a6b0
tree121d0c50ccd1342fccc13a9304880a92e3b7b7b5
parent8d6011361fb088e3e4d1ad649ac196bec101ee78

LLVM: more robust implementation of C ABI for multiple_llvm_ints

The previous code here was potentially more optimal for some cases, however, I never tested the perf, so it might not actually matter. This code handles more cases. We can go back and re-evaluate that other implementation if it seems worthwhile in the future.

1 files changed, 49 insertions(+), 193 deletions(-)

src/codegen/llvm.zig+49-193
...@@ -858,71 +858,34 @@ pub const Object = struct {...@@ -858,71 +858,34 @@ pub const Object = struct {
858 try args.append(aggregate);858 try args.append(aggregate);
859 },859 },
860 .multiple_llvm_ints => {860 .multiple_llvm_ints => {
861 const param_ty = fn_info.param_types[it.zig_index - 1];
862 const llvm_ints = it.llvm_types_buffer[0..it.llvm_types_len];861 const llvm_ints = it.llvm_types_buffer[0..it.llvm_types_len];
863 switch (param_ty.zigTypeTag()) {862 const param_ty = fn_info.param_types[it.zig_index - 1];
864 .Struct => {863 const param_llvm_ty = try dg.lowerType(param_ty);
865 const fields = param_ty.structFields().values();864 const param_alignment = param_ty.abiAlignment(target);
866 const param_llvm_ty = try dg.lowerType(param_ty);865 const arg_ptr = buildAllocaInner(builder, llvm_func, false, param_llvm_ty);
867 const param_alignment = param_ty.abiAlignment(target);866 arg_ptr.setAlignment(param_alignment);
868 const arg_ptr = buildAllocaInner(builder, llvm_func, false, param_llvm_ty);867 var field_types_buf: [8]*const llvm.Type = undefined;
869 arg_ptr.setAlignment(param_alignment);868 const field_types = field_types_buf[0..llvm_ints.len];
870869 for (llvm_ints) |int_bits, i| {
871 var field_i: u32 = 0;870 field_types[i] = dg.context.intType(int_bits);
872 var field_offset: u32 = 0;871 }
873 for (llvm_ints) |int_bits| {872 const ints_llvm_ty = dg.context.structType(field_types.ptr, @intCast(c_uint, field_types.len), .False);
874 const param = llvm_func.getParam(llvm_arg_i);873 const casted_ptr = builder.buildBitCast(arg_ptr, ints_llvm_ty.pointerType(0), "");
875 llvm_arg_i += 1;874 for (llvm_ints) |_, i_usize| {
876875 const i = @intCast(c_uint, i_usize);
877 const big_int_ty = dg.context.intType(int_bits);876 const param = llvm_func.getParam(i);
878 var bits_used: u32 = 0;877 const field_ptr = builder.buildStructGEP(casted_ptr, i, "");
879 while (bits_used < int_bits) {878 const store_inst = builder.buildStore(param, field_ptr);
880 const field = fields[field_i];879 store_inst.setAlignment(target.cpu.arch.ptrBitWidth() / 8);
881 const field_alignment = field.normalAlignment(target);
882 const prev_offset = field_offset;
883 field_offset = std.mem.alignForwardGeneric(u32, field_offset, field_alignment);
884 if (field_offset > prev_offset) {
885 // Padding counts as bits used.
886 bits_used += (field_offset - prev_offset) * 8;
887 if (bits_used >= int_bits) break;
888 }
889 const field_size = @intCast(u16, field.ty.abiSize(target));
890 const field_abi_bits = field_size * 8;
891 const field_int_ty = dg.context.intType(field_abi_bits);
892 const shifted = if (bits_used == 0) param else s: {
893 const shift_amt = big_int_ty.constInt(bits_used, .False);
894 break :s builder.buildLShr(param, shift_amt, "");
895 };
896 const field_as_int = builder.buildTrunc(shifted, field_int_ty, "");
897 var ty_buf: Type.Payload.Pointer = undefined;
898 const llvm_i = llvmFieldIndex(param_ty, field_i, target, &ty_buf).?;
899 const field_ptr = builder.buildStructGEP(arg_ptr, llvm_i, "");
900 const casted_ptr = builder.buildBitCast(field_ptr, field_int_ty.pointerType(0), "");
901 const store_inst = builder.buildStore(field_as_int, casted_ptr);
902 store_inst.setAlignment(field_alignment);
903
904 field_i += 1;
905 if (field_i >= fields.len) break;
906
907 bits_used += field_abi_bits;
908 field_offset += field_size;
909 }
910 if (field_i >= fields.len) break;
911 }
912
913 const is_by_ref = isByRef(param_ty);
914 const loaded = if (is_by_ref) arg_ptr else l: {
915 const load_inst = builder.buildLoad(arg_ptr, "");
916 load_inst.setAlignment(param_alignment);
917 break :l load_inst;
918 };
919 try args.append(loaded);
920 },
921 .Union => {
922 @panic("TODO: LLVM backend: implement C calling convention on x86_64 with union parameter");
923 },
924 else => unreachable,
925 }880 }
881
882 const is_by_ref = isByRef(param_ty);
883 const loaded = if (is_by_ref) arg_ptr else l: {
884 const load_inst = builder.buildLoad(arg_ptr, "");
885 load_inst.setAlignment(param_alignment);
886 break :l load_inst;
887 };
888 try args.append(loaded);
926 },889 },
927 };890 };
928 }891 }
...@@ -2821,65 +2784,11 @@ pub const DeclGen = struct {...@@ -2821,65 +2784,11 @@ pub const DeclGen = struct {
2821 llvm_params.appendAssumeCapacity(len_llvm_ty);2784 llvm_params.appendAssumeCapacity(len_llvm_ty);
2822 },2785 },
2823 .multiple_llvm_ints => {2786 .multiple_llvm_ints => {
2824 const param_ty = fn_info.param_types[it.zig_index - 1];
2825 const llvm_ints = it.llvm_types_buffer[0..it.llvm_types_len];2787 const llvm_ints = it.llvm_types_buffer[0..it.llvm_types_len];
2826 try llvm_params.ensureUnusedCapacity(it.llvm_types_len);2788 try llvm_params.ensureUnusedCapacity(it.llvm_types_len);
28272789 for (llvm_ints) |int_bits| {
2828 // The reason we have all this logic instead of simply appending2790 const big_int_ty = dg.context.intType(int_bits);
2829 // big_int_ty is for the special case of a pointer type;2791 llvm_params.appendAssumeCapacity(big_int_ty);
2830 // we want to use a pointer type instead of inttoptr at the callsites,
2831 // which may prevent optimization.
2832 switch (param_ty.zigTypeTag()) {
2833 .Struct => {
2834 const fields = param_ty.structFields().values();
2835 var field_i: u32 = 0;
2836 var field_offset: u32 = 0;
2837 llvm_arg: for (llvm_ints) |int_bits| {
2838 const big_int_ty = dg.context.intType(int_bits);
2839 var bits_used: u32 = 0;
2840 while (bits_used < int_bits) {
2841 const field = fields[field_i];
2842 const field_alignment = field.normalAlignment(target);
2843 const prev_offset = field_offset;
2844 field_offset = std.mem.alignForwardGeneric(u32, field_offset, field_alignment);
2845 if (field_offset > prev_offset) {
2846 // Padding counts as bits used.
2847 bits_used += (field_offset - prev_offset) * 8;
2848 if (bits_used >= int_bits) break;
2849 }
2850 const field_size = @intCast(u16, field.ty.abiSize(target));
2851 const field_abi_bits = field_size * 8;
2852
2853 // Special case for when the entire LLVM integer represents
2854 // one field; in this case keep the type information
2855 // to avoid the potentially costly ptrtoint/bitcast.
2856 if (bits_used == 0 and field_abi_bits == int_bits) {
2857 const llvm_field_ty = try dg.lowerType(field.ty);
2858 llvm_params.appendAssumeCapacity(llvm_field_ty);
2859 field_i += 1;
2860 if (field_i >= fields.len) {
2861 break :llvm_arg;
2862 } else {
2863 continue :llvm_arg;
2864 }
2865 }
2866
2867 field_i += 1;
2868 if (field_i >= fields.len) break;
2869
2870 bits_used += field_abi_bits;
2871 field_offset += field_size;
2872 }
2873 llvm_params.appendAssumeCapacity(big_int_ty);
2874 if (field_i >= fields.len) break;
2875 }
2876 },
2877 else => {
2878 for (llvm_ints) |int_bits| {
2879 const big_int_ty = dg.context.intType(int_bits);
2880 llvm_params.appendAssumeCapacity(big_int_ty);
2881 }
2882 },
2883 }2792 }
2884 },2793 },
2885 };2794 };
...@@ -4299,80 +4208,27 @@ pub const FuncGen = struct {...@@ -4299,80 +4208,27 @@ pub const FuncGen = struct {
4299 const llvm_ints = it.llvm_types_buffer[0..it.llvm_types_len];4208 const llvm_ints = it.llvm_types_buffer[0..it.llvm_types_len];
4300 const llvm_arg = try self.resolveInst(arg);4209 const llvm_arg = try self.resolveInst(arg);
4301 const is_by_ref = isByRef(param_ty);4210 const is_by_ref = isByRef(param_ty);
4302 try llvm_args.ensureUnusedCapacity(it.llvm_types_len);4211 const arg_ptr = if (is_by_ref) llvm_arg else p: {
4303 switch (param_ty.zigTypeTag()) {4212 const p = self.buildAlloca(llvm_arg.typeOf());
4304 .Struct => {4213 const store_inst = self.builder.buildStore(llvm_arg, p);
4305 const fields = param_ty.structFields().values();4214 store_inst.setAlignment(param_ty.abiAlignment(target));
4306 var field_i: u32 = 0;4215 break :p p;
4307 var field_offset: u32 = 0;4216 };
4308 for (llvm_ints) |int_bits| {
4309 const big_int_ty = self.dg.context.intType(int_bits);
4310 var int_arg: *const llvm.Value = undefined;
4311 var bits_used: u32 = 0;
4312 while (bits_used < int_bits) {
4313 const field = fields[field_i];
4314 const field_alignment = field.normalAlignment(target);
4315 const prev_offset = field_offset;
4316 field_offset = std.mem.alignForwardGeneric(u32, field_offset, field_alignment);
4317 if (field_offset > prev_offset) {
4318 // Padding counts as bits used.
4319 bits_used += (field_offset - prev_offset) * 8;
4320 if (bits_used >= int_bits) break;
4321 }
4322 var ty_buf: Type.Payload.Pointer = undefined;
4323 const llvm_i = llvmFieldIndex(param_ty, field_i, target, &ty_buf).?;
4324 const field_size = @intCast(u16, field.ty.abiSize(target));
4325 const field_abi_bits = field_size * 8;
4326
4327 // Special case for when the entire LLVM integer represents
4328 // one field; in this case keep the type information
4329 // to avoid the potentially costly ptrtoint/bitcast.
4330 if (bits_used == 0 and field_abi_bits == int_bits) {
4331 int_arg = if (is_by_ref) f: {
4332 const field_ptr = self.builder.buildStructGEP(llvm_arg, llvm_i, "");
4333 const load_inst = self.builder.buildLoad(field_ptr, "");
4334 load_inst.setAlignment(field_alignment);
4335 break :f load_inst;
4336 } else self.builder.buildExtractValue(llvm_arg, llvm_i, "");
4337 field_i += 1;
4338 break;
4339 }
4340
4341 const field_int_ty = self.dg.context.intType(field_abi_bits);
4342 const llvm_field = if (is_by_ref) f: {
4343 const field_ptr = self.builder.buildStructGEP(llvm_arg, llvm_i, "");
4344 const casted_ptr = self.builder.buildBitCast(field_ptr, field_int_ty.pointerType(0), "");
4345 const load_inst = self.builder.buildLoad(casted_ptr, "");
4346 load_inst.setAlignment(field_alignment);
4347 break :f load_inst;
4348 } else f: {
4349 const llvm_field = self.builder.buildExtractValue(llvm_arg, llvm_i, "");
4350 break :f self.builder.buildBitCast(llvm_field, field_int_ty, "");
4351 };
4352
4353 const extended = self.builder.buildZExt(llvm_field, big_int_ty, "");
4354 if (bits_used == 0) {
4355 int_arg = extended;
4356 } else {
4357 const shift_amt = big_int_ty.constInt(bits_used, .False);
4358 const shifted = self.builder.buildShl(extended, shift_amt, "");
4359 int_arg = self.builder.buildOr(int_arg, shifted, "");
4360 }
4361
4362 field_i += 1;
4363 if (field_i >= fields.len) break;
43644217
4365 bits_used += field_abi_bits;4218 var field_types_buf: [8]*const llvm.Type = undefined;
4366 field_offset += field_size;4219 const field_types = field_types_buf[0..llvm_ints.len];
4367 }4220 for (llvm_ints) |int_bits, i| {
4368 llvm_args.appendAssumeCapacity(int_arg);4221 field_types[i] = self.dg.context.intType(int_bits);
4369 if (field_i >= fields.len) break;4222 }
4370 }4223 const ints_llvm_ty = self.dg.context.structType(field_types.ptr, @intCast(c_uint, field_types.len), .False);
4371 },4224 const casted_ptr = self.builder.buildBitCast(arg_ptr, ints_llvm_ty.pointerType(0), "");
4372 .Union => {4225 try llvm_args.ensureUnusedCapacity(it.llvm_types_len);
4373 return self.todo("airCall C calling convention on x86_64 with union argument ", .{});4226 for (llvm_ints) |_, i_usize| {
4374 },4227 const i = @intCast(c_uint, i_usize);
4375 else => unreachable,4228 const field_ptr = self.builder.buildStructGEP(casted_ptr, i, "");
4229 const load_inst = self.builder.buildLoad(field_ptr, "");
4230 load_inst.setAlignment(target.cpu.arch.ptrBitWidth() / 8);
4231 llvm_args.appendAssumeCapacity(load_inst);
4376 }4232 }
4377 },4233 },
4378 };4234 };