authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-04-29 11:24:30+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-05-05 22:29:30-07:00
logedb3adaa33f943d0c64071fbf9d43aadadaa1e95
tree0f29acb927328d61104e940d7a1d1d63d8013bfa
parent413b789e06bfc98bd285f0a340bd537b1d2c9dec

stage2,llvm: handle softfloats in @intToFloat and @floatToInt

If the hw doesn't have support for exotic floating-point types such as `f80`, we lower the call to a compiler-rt function call instead. I've added a behavior test specifically targeting this use case which now passes on `aarch64-macos`. Additionally, this commit makes it possible to successfully build stage3 on `aarch64-macos`. We can print the compiler's help message, however, building with it needs a little bit more love still.

2 files changed, 185 insertions(+), 14 deletions(-)

src/codegen/llvm.zig+149-14
......@@ -4229,41 +4229,166 @@ pub const FuncGen = struct {
42294229 return self.builder.buildInsertValue(partial, len, 1, "");
42304230 }
42314231
4232 inline fn isPowerOfTwo(bits: u64) bool {
4233 return bits != 0 and ((bits & (~bits + 1)) == bits);
4234 }
4235
4236 fn intTypeFromBitsAndSignRounded(self: *FuncGen, bits: u16, signed: bool) error{OutOfMemory}!Type {
4237 const next_pow_two = math.log2_int_ceil(u16, bits);
4238 const rounded_bits = @as(u32, 1) << next_pow_two;
4239 return switch (rounded_bits) {
4240 8, 16, 32 => if (signed) Type.initTag(.i32) else Type.initTag(.u32),
4241 64 => if (signed) Type.initTag(.i64) else Type.initTag(.u64),
4242 128 => if (signed) Type.initTag(.i128) else Type.initTag(.u128),
4243 else => |big| if (signed)
4244 Type.Tag.int_signed.create(
4245 self.dg.object.type_map_arena.allocator(),
4246 @intCast(u16, big),
4247 )
4248 else
4249 Type.Tag.int_unsigned.create(
4250 self.dg.object.type_map_arena.allocator(),
4251 @intCast(u16, big),
4252 ),
4253 };
4254 }
4255
42324256 fn airIntToFloat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
42334257 if (self.liveness.isUnused(inst))
42344258 return null;
42354259
4260 const target = self.dg.module.getTarget();
42364261 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
4237 const operand = try self.resolveInst(ty_op.operand);
4238 const operand_ty = self.air.typeOf(ty_op.operand);
4239 const operand_scalar_ty = operand_ty.scalarType();
4262
4263 var operand = try self.resolveInst(ty_op.operand);
4264 var operand_ty = self.air.typeOf(ty_op.operand);
4265 var operand_scalar_ty = operand_ty.scalarType();
4266
4267 {
4268 const operand_bits = @intCast(u16, operand_scalar_ty.bitSize(target));
4269 const is_signed = operand_scalar_ty.isSignedInt();
4270
4271 if (!isPowerOfTwo(operand_bits) or operand_bits < 32) {
4272 const wider_ty = try self.intTypeFromBitsAndSignRounded(operand_bits, is_signed);
4273 const wider_llvm_ty = try self.dg.llvmType(wider_ty);
4274 if (is_signed) {
4275 operand = self.builder.buildSExt(operand, wider_llvm_ty, "");
4276 } else {
4277 operand = self.builder.buildZExt(operand, wider_llvm_ty, "");
4278 }
4279 operand_ty = wider_ty;
4280 operand_scalar_ty = operand_ty.scalarType();
4281 }
4282 }
4283
42404284 const dest_ty = self.air.typeOfIndex(inst);
4285 const dest_scalar_ty = dest_ty.scalarType();
42414286 const dest_llvm_ty = try self.dg.llvmType(dest_ty);
42424287
4243 if (operand_scalar_ty.isSignedInt()) {
4244 return self.builder.buildSIToFP(operand, dest_llvm_ty, "");
4245 } else {
4246 return self.builder.buildUIToFP(operand, dest_llvm_ty, "");
4288 if (intrinsicsAllowed(dest_scalar_ty, target)) {
4289 if (operand_scalar_ty.isSignedInt()) {
4290 return self.builder.buildSIToFP(operand, dest_llvm_ty, "");
4291 } else {
4292 return self.builder.buildUIToFP(operand, dest_llvm_ty, "");
4293 }
42474294 }
4295
4296 const operand_bits = @intCast(u16, operand_scalar_ty.bitSize(target));
4297 const compiler_rt_operand_abbrev = compilerRtIntAbbrev(operand_bits);
4298
4299 const dest_bits = dest_scalar_ty.floatBits(target);
4300 const compiler_rt_dest_abbrev = compilerRtFloatAbbrev(dest_bits);
4301
4302 var fn_name_buf: [64]u8 = undefined;
4303 const fn_name = if (operand_scalar_ty.isSignedInt())
4304 std.fmt.bufPrintZ(&fn_name_buf, "__float{s}i{s}f", .{
4305 compiler_rt_operand_abbrev,
4306 compiler_rt_dest_abbrev,
4307 }) catch unreachable
4308 else
4309 std.fmt.bufPrintZ(&fn_name_buf, "__floatun{s}i{s}f", .{
4310 compiler_rt_operand_abbrev,
4311 compiler_rt_dest_abbrev,
4312 }) catch unreachable;
4313
4314 const operand_llvm_ty = try self.dg.llvmType(operand_ty);
4315 const param_types = [1]*const llvm.Type{operand_llvm_ty};
4316 const libc_fn = self.getLibcFunction(fn_name, &param_types, dest_llvm_ty);
4317 const params = [1]*const llvm.Value{operand};
4318
4319 return self.builder.buildCall(libc_fn, &params, params.len, .C, .Auto, "");
42484320 }
42494321
42504322 fn airFloatToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
42514323 if (self.liveness.isUnused(inst))
42524324 return null;
42534325
4326 const target = self.dg.module.getTarget();
42544327 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
4328
42554329 const operand = try self.resolveInst(ty_op.operand);
4256 const dest_ty = self.air.typeOfIndex(inst);
4257 const dest_scalar_ty = dest_ty.scalarType();
4330 const operand_ty = self.air.typeOf(ty_op.operand);
4331 const operand_scalar_ty = operand_ty.scalarType();
4332
4333 var dest_ty = self.air.typeOfIndex(inst);
4334 var dest_scalar_ty = dest_ty.scalarType();
4335
4336 if (intrinsicsAllowed(operand_scalar_ty, target)) {
4337 // TODO set fast math flag
4338 const dest_llvm_ty = try self.dg.llvmType(dest_ty);
4339 if (dest_scalar_ty.isSignedInt()) {
4340 return self.builder.buildFPToSI(operand, dest_llvm_ty, "");
4341 } else {
4342 return self.builder.buildFPToUI(operand, dest_llvm_ty, "");
4343 }
4344 }
4345
4346 const needs_truncating = blk: {
4347 const dest_bits = @intCast(u16, dest_scalar_ty.bitSize(target));
4348
4349 if (!isPowerOfTwo(dest_bits) or dest_bits < 32) {
4350 dest_ty = try self.intTypeFromBitsAndSignRounded(dest_bits, dest_scalar_ty.isSignedInt());
4351 dest_scalar_ty = dest_ty.scalarType();
4352 break :blk true;
4353 }
4354
4355 break :blk false;
4356 };
4357
42584358 const dest_llvm_ty = try self.dg.llvmType(dest_ty);
42594359
4260 // TODO set fast math flag
4360 const operand_bits = operand_scalar_ty.floatBits(target);
4361 const compiler_rt_operand_abbrev = compilerRtFloatAbbrev(operand_bits);
42614362
4262 if (dest_scalar_ty.isSignedInt()) {
4263 return self.builder.buildFPToSI(operand, dest_llvm_ty, "");
4264 } else {
4265 return self.builder.buildFPToUI(operand, dest_llvm_ty, "");
4363 const dest_bits = @intCast(u16, dest_scalar_ty.bitSize(target));
4364 const compiler_rt_dest_abbrev = compilerRtIntAbbrev(dest_bits);
4365
4366 var fn_name_buf: [64]u8 = undefined;
4367 const fn_name = if (dest_scalar_ty.isSignedInt())
4368 std.fmt.bufPrintZ(&fn_name_buf, "__fix{s}f{s}i", .{
4369 compiler_rt_operand_abbrev,
4370 compiler_rt_dest_abbrev,
4371 }) catch unreachable
4372 else
4373 std.fmt.bufPrintZ(&fn_name_buf, "__fixun{s}f{s}i", .{
4374 compiler_rt_operand_abbrev,
4375 compiler_rt_dest_abbrev,
4376 }) catch unreachable;
4377
4378 const operand_llvm_ty = try self.dg.llvmType(operand_ty);
4379 const param_types = [1]*const llvm.Type{operand_llvm_ty};
4380 const libc_fn = self.getLibcFunction(fn_name, &param_types, dest_llvm_ty);
4381 const params = [1]*const llvm.Value{operand};
4382
4383 const result = self.builder.buildCall(libc_fn, &params, params.len, .C, .Auto, "");
4384
4385 if (needs_truncating) {
4386 const requested_ty = self.air.typeOfIndex(inst);
4387 const requested_llvm_ty = try self.dg.llvmType(requested_ty);
4388 return self.builder.buildTrunc(result, requested_llvm_ty, "");
42664389 }
4390
4391 return result;
42674392 }
42684393
42694394 fn airSliceField(self: *FuncGen, inst: Air.Inst.Index, index: c_uint) !?*const llvm.Value {
......@@ -5615,6 +5740,16 @@ pub const FuncGen = struct {
56155740 };
56165741 }
56175742
5743 fn compilerRtIntAbbrev(bits: u16) []const u8 {
5744 return switch (bits) {
5745 16 => "h",
5746 32 => "s",
5747 64 => "d",
5748 128 => "t",
5749 else => "o", // Non-standard
5750 };
5751 }
5752
56185753 /// Creates a floating point comparison by lowering to the appropriate
56195754 /// hardware instruction or softfloat routine for the target
56205755 fn buildFloatCmp(
test/behavior/cast.zig+36
......@@ -112,6 +112,42 @@ test "@intToFloat" {
112112 comptime try S.doTheTest();
113113}
114114
115test "@intToFloat(f80)" {
116 if (builtin.zig_backend == .stage1) return error.SkipZigTest;
117
118 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
119 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
120 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
121 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
122 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
123
124 const S = struct {
125 fn doTheTest(comptime Int: type) !void {
126 try testIntToFloat(Int, -2);
127 }
128
129 fn testIntToFloat(comptime Int: type, k: Int) !void {
130 const f = @intToFloat(f80, k);
131 const i = @floatToInt(Int, f);
132 try expect(i == k);
133 }
134 };
135 try S.doTheTest(i31);
136 try S.doTheTest(i32);
137 try S.doTheTest(i45);
138 try S.doTheTest(i64);
139 try S.doTheTest(i80);
140 try S.doTheTest(i128);
141 // try S.doTheTest(i256); // TODO missing compiler_rt symbols
142 comptime try S.doTheTest(i31);
143 comptime try S.doTheTest(i32);
144 comptime try S.doTheTest(i45);
145 comptime try S.doTheTest(i64);
146 comptime try S.doTheTest(i80);
147 comptime try S.doTheTest(i128);
148 comptime try S.doTheTest(i256);
149}
150
115151test "@floatToInt" {
116152 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
117153 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO