authorgravatar for john.schmidt.h@gmail.comJohn Schmidt <john.schmidt.h@gmail.com> 2022-02-04 20:21:15+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-02-07 16:52:19-07:00
log722d4a11bbba4052558f6f69b7e710d1206f3355
tree5c98c05bb228555a7b7e9bf3c71d48538c3b84e4
parentdd49ed1c642d917af40bf4a0e03d013f40b3903b

stage2: implement @sqrt for f{16,32,64}

Support for f128, comptime_float, and c_longdouble require improvements to compiler_rt and will implemented in a later PR. Some of the code in this commit could be made more generic, for instance `llvm.airSqrt` could probably be `llvm.airUnaryMath`, but let's cross that bridge when we get to it.

15 files changed, 217 insertions(+), 55 deletions(-)

src/Air.zig+6
......@@ -237,6 +237,10 @@ pub const Inst = struct {
237237 /// Uses the `ty_op` field.
238238 popcount,
239239
240 /// Computes the square root of a floating point number.
241 /// Uses the `un_op` field.
242 sqrt,
243
240244 /// `<`. Result type is always bool.
241245 /// Uses the `bin_op` field.
242246 cmp_lt,
......@@ -749,6 +753,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
749753 .max,
750754 => return air.typeOf(datas[inst].bin_op.lhs),
751755
756 .sqrt => return air.typeOf(datas[inst].un_op),
757
752758 .cmp_lt,
753759 .cmp_lte,
754760 .cmp_eq,
src/Liveness.zig+1
......@@ -338,6 +338,7 @@ fn analyzeInst(
338338 .ret_load,
339339 .tag_name,
340340 .error_name,
341 .sqrt,
341342 => {
342343 const operand = inst_datas[inst].un_op;
343344 return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none });
src/Sema.zig+69-15
......@@ -745,19 +745,19 @@ fn analyzeBodyInner(
745745 .clz => try sema.zirClzCtz(block, inst, .clz, Value.clz),
746746 .ctz => try sema.zirClzCtz(block, inst, .ctz, Value.ctz),
747747
748 .sqrt => try sema.zirUnaryMath(block, inst),
749 .sin => try sema.zirUnaryMath(block, inst),
750 .cos => try sema.zirUnaryMath(block, inst),
751 .exp => try sema.zirUnaryMath(block, inst),
752 .exp2 => try sema.zirUnaryMath(block, inst),
753 .log => try sema.zirUnaryMath(block, inst),
754 .log2 => try sema.zirUnaryMath(block, inst),
755 .log10 => try sema.zirUnaryMath(block, inst),
756 .fabs => try sema.zirUnaryMath(block, inst),
757 .floor => try sema.zirUnaryMath(block, inst),
758 .ceil => try sema.zirUnaryMath(block, inst),
759 .trunc => try sema.zirUnaryMath(block, inst),
760 .round => try sema.zirUnaryMath(block, inst),
748 .sqrt => try sema.zirUnaryMath(block, inst, .sqrt),
749 .sin => try sema.zirUnaryMath(block, inst, .sin),
750 .cos => try sema.zirUnaryMath(block, inst, .cos),
751 .exp => try sema.zirUnaryMath(block, inst, .exp),
752 .exp2 => try sema.zirUnaryMath(block, inst, .exp2),
753 .log => try sema.zirUnaryMath(block, inst, .log),
754 .log2 => try sema.zirUnaryMath(block, inst, .log2),
755 .log10 => try sema.zirUnaryMath(block, inst, .log10),
756 .fabs => try sema.zirUnaryMath(block, inst, .fabs),
757 .floor => try sema.zirUnaryMath(block, inst, .floor),
758 .ceil => try sema.zirUnaryMath(block, inst, .ceil),
759 .trunc => try sema.zirUnaryMath(block, inst, .trunc),
760 .round => try sema.zirUnaryMath(block, inst, .round),
761761
762762 .error_set_decl => try sema.zirErrorSetDecl(block, inst, .parent),
763763 .error_set_decl_anon => try sema.zirErrorSetDecl(block, inst, .anon),
......@@ -11010,10 +11010,64 @@ fn zirErrorName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
1101011010 return block.addUnOp(.error_name, operand);
1101111011}
1101211012
11013fn zirUnaryMath(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
11013fn zirUnaryMath(
11014 sema: *Sema,
11015 block: *Block,
11016 inst: Zir.Inst.Index,
11017 zir_tag: Zir.Inst.Tag,
11018) CompileError!Air.Inst.Ref {
11019 const tracy = trace(@src());
11020 defer tracy.end();
11021
1101411022 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
1101511023 const src = inst_data.src();
11016 return sema.fail(block, src, "TODO: Sema.zirUnaryMath", .{});
11024 const operand = sema.resolveInst(inst_data.operand);
11025 const operand_ty = sema.typeOf(operand);
11026 const operand_zig_ty_tag = operand_ty.zigTypeTag();
11027
11028 const is_float = operand_zig_ty_tag == .Float or operand_zig_ty_tag == .ComptimeFloat;
11029 if (!is_float) {
11030 return sema.fail(block, src, "expected float type, found '{s}'", .{@tagName(operand_zig_ty_tag)});
11031 }
11032
11033 switch (zir_tag) {
11034 .sqrt => {
11035 switch (operand_ty.tag()) {
11036 .f128,
11037 .comptime_float,
11038 .c_longdouble,
11039 => |t| return sema.fail(block, src, "TODO implement @sqrt for type '{s}'", .{@tagName(t)}),
11040 else => {},
11041 }
11042
11043 const maybe_operand_val = try sema.resolveMaybeUndefVal(block, src, operand);
11044 if (maybe_operand_val) |val| {
11045 if (val.isUndef())
11046 return sema.addConstUndef(operand_ty);
11047 const result_val = try val.sqrt(operand_ty, sema.arena);
11048 return sema.addConstant(operand_ty, result_val);
11049 }
11050
11051 try sema.requireRuntimeBlock(block, src);
11052 return block.addUnOp(.sqrt, operand);
11053 },
11054
11055 .sin,
11056 .cos,
11057 .exp,
11058 .exp2,
11059 .log,
11060 .log2,
11061 .log10,
11062 .fabs,
11063 .floor,
11064 .ceil,
11065 .trunc,
11066 .round,
11067 => return sema.fail(block, src, "TODO: implement zirUnaryMath for ZIR tag '{s}'", .{@tagName(zir_tag)}),
11068
11069 else => unreachable,
11070 }
1101711071}
1101811072
1101911073fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
src/arch/aarch64/CodeGen.zig+11
......@@ -528,6 +528,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
528528 .max => try self.airMax(inst),
529529 .slice => try self.airSlice(inst),
530530
531 .sqrt => try self.airUnaryMath(inst),
532
531533 .add_with_overflow => try self.airAddWithOverflow(inst),
532534 .sub_with_overflow => try self.airSubWithOverflow(inst),
533535 .mul_with_overflow => try self.airMulWithOverflow(inst),
......@@ -1223,6 +1225,15 @@ fn airPopcount(self: *Self, inst: Air.Inst.Index) !void {
12231225 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
12241226}
12251227
1228fn airUnaryMath(self: *Self, inst: Air.Inst.Index) !void {
1229 const un_op = self.air.instructions.items(.data)[inst].un_op;
1230 const result: MCValue = if (self.liveness.isUnused(inst))
1231 .dead
1232 else
1233 return self.fail("TODO implement airUnaryMath for {}", .{self.target.cpu.arch});
1234 return self.finishAir(inst, result, .{ un_op, .none, .none });
1235}
1236
12261237fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
12271238 if (!self.liveness.operandDies(inst, op_index))
12281239 return false;
src/arch/arm/CodeGen.zig+11
......@@ -520,6 +520,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
520520 .max => try self.airMax(inst),
521521 .slice => try self.airSlice(inst),
522522
523 .sqrt => try self.airUnaryMath(inst),
524
523525 .add_with_overflow => try self.airAddWithOverflow(inst),
524526 .sub_with_overflow => try self.airSubWithOverflow(inst),
525527 .mul_with_overflow => try self.airMulWithOverflow(inst),
......@@ -1377,6 +1379,15 @@ fn airPopcount(self: *Self, inst: Air.Inst.Index) !void {
13771379 // return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
13781380}
13791381
1382fn airUnaryMath(self: *Self, inst: Air.Inst.Index) !void {
1383 const un_op = self.air.instructions.items(.data)[inst].un_op;
1384 const result: MCValue = if (self.liveness.isUnused(inst))
1385 .dead
1386 else
1387 return self.fail("TODO implement airUnaryMath for {}", .{self.target.cpu.arch});
1388 return self.finishAir(inst, result, .{ un_op, .none, .none });
1389}
1390
13801391fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
13811392 if (!self.liveness.operandDies(inst, op_index))
13821393 return false;
src/arch/riscv64/CodeGen.zig+11
......@@ -507,6 +507,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
507507 .max => try self.airMax(inst),
508508 .slice => try self.airSlice(inst),
509509
510 .sqrt => try self.airUnaryMath(inst),
511
510512 .add_with_overflow => try self.airAddWithOverflow(inst),
511513 .sub_with_overflow => try self.airSubWithOverflow(inst),
512514 .mul_with_overflow => try self.airMulWithOverflow(inst),
......@@ -1166,6 +1168,15 @@ fn airPopcount(self: *Self, inst: Air.Inst.Index) !void {
11661168 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
11671169}
11681170
1171fn airUnaryMath(self: *Self, inst: Air.Inst.Index) !void {
1172 const un_op = self.air.instructions.items(.data)[inst].un_op;
1173 const result: MCValue = if (self.liveness.isUnused(inst))
1174 .dead
1175 else
1176 return self.fail("TODO implement airUnaryMath for {}", .{self.target.cpu.arch});
1177 return self.finishAir(inst, result, .{ un_op, .none, .none });
1178}
1179
11691180fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
11701181 if (!self.liveness.operandDies(inst, op_index))
11711182 return false;
src/arch/wasm/CodeGen.zig+2
......@@ -1681,6 +1681,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
16811681 .unwrap_errunion_payload_ptr,
16821682 .unwrap_errunion_err_ptr,
16831683
1684 .sqrt,
1685
16841686 .ptr_slice_len_ptr,
16851687 .ptr_slice_ptr_ptr,
16861688 .int_to_float,
src/arch/x86_64/CodeGen.zig+11
......@@ -599,6 +599,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
599599 .max => try self.airMax(inst),
600600 .slice => try self.airSlice(inst),
601601
602 .sqrt => try self.airUnaryMath(inst),
603
602604 .add_with_overflow => try self.airAddWithOverflow(inst),
603605 .sub_with_overflow => try self.airSubWithOverflow(inst),
604606 .mul_with_overflow => try self.airMulWithOverflow(inst),
......@@ -1578,6 +1580,15 @@ fn airPopcount(self: *Self, inst: Air.Inst.Index) !void {
15781580 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
15791581}
15801582
1583fn airUnaryMath(self: *Self, inst: Air.Inst.Index) !void {
1584 const un_op = self.air.instructions.items(.data)[inst].un_op;
1585 const result: MCValue = if (self.liveness.isUnused(inst))
1586 .dead
1587 else
1588 return self.fail("TODO implement airUnaryMath for {}", .{self.target.cpu.arch});
1589 return self.finishAir(inst, result, .{ un_op, .none, .none });
1590}
1591
15811592fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
15821593 if (!self.liveness.operandDies(inst, op_index))
15831594 return false;
src/codegen/c.zig+8
......@@ -1446,6 +1446,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
14461446 .mul_sat => try airSatOp(f, inst, "muls_"),
14471447 .shl_sat => try airSatOp(f, inst, "shls_"),
14481448
1449 .sqrt => try airSqrt(f, inst),
1450
14491451 .add_with_overflow => try airAddWithOverflow(f, inst),
14501452 .sub_with_overflow => try airSubWithOverflow(f, inst),
14511453 .mul_with_overflow => try airMulWithOverflow(f, inst),
......@@ -3393,6 +3395,12 @@ fn airPrefetch(f: *Function, inst: Air.Inst.Index) !CValue {
33933395 return CValue.none;
33943396}
33953397
3398fn airSqrt(f: *Function, inst: Air.Inst.Index) !CValue {
3399 _ = f;
3400 _ = inst;
3401 return f.fail("TODO: C backend: implement sqrt", .{});
3402}
3403
33963404fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 {
33973405 return switch (order) {
33983406 .Unordered => "memory_order_relaxed",
src/codegen/llvm.zig+16
......@@ -2050,6 +2050,8 @@ pub const FuncGen = struct {
20502050 .shr => try self.airShr(inst, false),
20512051 .shr_exact => try self.airShr(inst, true),
20522052
2053 .sqrt => try self.airSqrt(inst),
2054
20532055 .cmp_eq => try self.airCmp(inst, .eq),
20542056 .cmp_gt => try self.airCmp(inst, .gt),
20552057 .cmp_gte => try self.airCmp(inst, .gte),
......@@ -4211,6 +4213,20 @@ pub const FuncGen = struct {
42114213 }
42124214 }
42134215
4216 fn airSqrt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
4217 if (self.liveness.isUnused(inst)) return null;
4218
4219 const un_op = self.air.instructions.items(.data)[inst].un_op;
4220 const operand = try self.resolveInst(un_op);
4221 const operand_ty = self.air.typeOf(un_op);
4222
4223 const operand_llvm_ty = try self.dg.llvmType(operand_ty);
4224 const fn_val = self.getIntrinsic("llvm.sqrt", &.{operand_llvm_ty});
4225 const params = [_]*const llvm.Value{operand};
4226
4227 return self.builder.buildCall(fn_val, &params, params.len, .C, .Auto, "");
4228 }
4229
42144230 fn airClzCtz(self: *FuncGen, inst: Air.Inst.Index, prefix: [*:0]const u8) !?*const llvm.Value {
42154231 if (self.liveness.isUnused(inst)) return null;
42164232
src/print_air.zig+1
......@@ -158,6 +158,7 @@ const Writer = struct {
158158 .ret_load,
159159 .tag_name,
160160 .error_name,
161 .sqrt,
161162 => try w.writeUnOp(s, inst),
162163
163164 .breakpoint,
src/value.zig+22
......@@ -3265,6 +3265,28 @@ pub const Value = extern union {
32653265 }
32663266 }
32673267
3268 pub fn sqrt(val: Value, float_type: Type, arena: Allocator) !Value {
3269 switch (float_type.tag()) {
3270 .f16 => {
3271 const f = val.toFloat(f16);
3272 return Value.Tag.float_16.create(arena, @sqrt(f));
3273 },
3274 .f32 => {
3275 const f = val.toFloat(f32);
3276 return Value.Tag.float_32.create(arena, @sqrt(f));
3277 },
3278 .f64 => {
3279 const f = val.toFloat(f64);
3280 return Value.Tag.float_64.create(arena, @sqrt(f));
3281 },
3282
3283 // TODO: implement @sqrt for these types
3284 .f128, .comptime_float, .c_longdouble => unreachable,
3285
3286 else => unreachable,
3287 }
3288 }
3289
32683290 /// This type is not copyable since it may contain pointers to its inner data.
32693291 pub const Payload = struct {
32703292 tag: Tag,
test/behavior/floatop.zig+42
......@@ -72,3 +72,45 @@ test "negative f128 floatToInt at compile-time" {
7272 var b = @floatToInt(i64, a);
7373 try expect(@as(i64, -2) == b);
7474}
75
76test "@sqrt" {
77 comptime try testSqrt();
78 try testSqrt();
79}
80
81fn testSqrt() !void {
82 {
83 var a: f16 = 4;
84 try expect(@sqrt(a) == 2);
85 }
86 {
87 var a: f32 = 9;
88 try expect(@sqrt(a) == 3);
89 var b: f32 = 1.1;
90 try expect(math.approxEqAbs(f32, @sqrt(b), 1.0488088481701516, epsilon));
91 }
92 {
93 var a: f64 = 25;
94 try expect(@sqrt(a) == 5);
95 }
96}
97
98test "more @sqrt f16 tests" {
99 // TODO these are not all passing at comptime
100 try expect(@sqrt(@as(f16, 0.0)) == 0.0);
101 try expect(math.approxEqAbs(f16, @sqrt(@as(f16, 2.0)), 1.414214, epsilon));
102 try expect(math.approxEqAbs(f16, @sqrt(@as(f16, 3.6)), 1.897367, epsilon));
103 try expect(@sqrt(@as(f16, 4.0)) == 2.0);
104 try expect(math.approxEqAbs(f16, @sqrt(@as(f16, 7.539840)), 2.745877, epsilon));
105 try expect(math.approxEqAbs(f16, @sqrt(@as(f16, 19.230934)), 4.385309, epsilon));
106 try expect(@sqrt(@as(f16, 64.0)) == 8.0);
107 try expect(math.approxEqAbs(f16, @sqrt(@as(f16, 64.1)), 8.006248, epsilon));
108 try expect(math.approxEqAbs(f16, @sqrt(@as(f16, 8942.230469)), 94.563370, epsilon));
109
110 // special cases
111 try expect(math.isPositiveInf(@sqrt(@as(f16, math.inf(f16)))));
112 try expect(@sqrt(@as(f16, 0.0)) == 0.0);
113 try expect(@sqrt(@as(f16, -0.0)) == -0.0);
114 try expect(math.isNan(@sqrt(@as(f16, -1.0))));
115 try expect(math.isNan(@sqrt(@as(f16, math.nan(f16)))));
116}
test/behavior/floatop_stage1.zig-34
......@@ -14,20 +14,6 @@ test "@sqrt" {
1414}
1515
1616fn testSqrt() !void {
17 {
18 var a: f16 = 4;
19 try expect(@sqrt(a) == 2);
20 }
21 {
22 var a: f32 = 9;
23 try expect(@sqrt(a) == 3);
24 var b: f32 = 1.1;
25 try expect(math.approxEqAbs(f32, @sqrt(b), 1.0488088481701516, epsilon));
26 }
27 {
28 var a: f64 = 25;
29 try expect(@sqrt(a) == 5);
30 }
3117 if (has_f80_rt) {
3218 var a: f80 = 25;
3319 try expect(@sqrt(a) == 5);
......@@ -51,26 +37,6 @@ fn testSqrt() !void {
5137 }
5238}
5339
54test "more @sqrt f16 tests" {
55 // TODO these are not all passing at comptime
56 try expect(@sqrt(@as(f16, 0.0)) == 0.0);
57 try expect(math.approxEqAbs(f16, @sqrt(@as(f16, 2.0)), 1.414214, epsilon));
58 try expect(math.approxEqAbs(f16, @sqrt(@as(f16, 3.6)), 1.897367, epsilon));
59 try expect(@sqrt(@as(f16, 4.0)) == 2.0);
60 try expect(math.approxEqAbs(f16, @sqrt(@as(f16, 7.539840)), 2.745877, epsilon));
61 try expect(math.approxEqAbs(f16, @sqrt(@as(f16, 19.230934)), 4.385309, epsilon));
62 try expect(@sqrt(@as(f16, 64.0)) == 8.0);
63 try expect(math.approxEqAbs(f16, @sqrt(@as(f16, 64.1)), 8.006248, epsilon));
64 try expect(math.approxEqAbs(f16, @sqrt(@as(f16, 8942.230469)), 94.563370, epsilon));
65
66 // special cases
67 try expect(math.isPositiveInf(@sqrt(@as(f16, math.inf(f16)))));
68 try expect(@sqrt(@as(f16, 0.0)) == 0.0);
69 try expect(@sqrt(@as(f16, -0.0)) == -0.0);
70 try expect(math.isNan(@sqrt(@as(f16, -1.0))));
71 try expect(math.isNan(@sqrt(@as(f16, math.nan(f16)))));
72}
73
7440test "@sin" {
7541 comptime try testSin();
7642 try testSin();
test/behavior/math.zig+6-6
......@@ -792,8 +792,6 @@ fn remdiv(comptime T: type) !void {
792792}
793793
794794test "@sqrt" {
795 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
796
797795 try testSqrt(f64, 12.0);
798796 comptime try testSqrt(f64, 12.0);
799797 try testSqrt(f32, 13.0);
......@@ -801,10 +799,12 @@ test "@sqrt" {
801799 try testSqrt(f16, 13.0);
802800 comptime try testSqrt(f16, 13.0);
803801
804 const x = 14.0;
805 const y = x * x;
806 const z = @sqrt(y);
807 comptime try expect(z == x);
802 if (builtin.zig_backend == .stage1) {
803 const x = 14.0;
804 const y = x * x;
805 const z = @sqrt(y);
806 comptime try expect(z == x);
807 }
808808}
809809
810810fn testSqrt(comptime T: type, x: T) !void {