authorgravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-10-08 17:13:16-07:00
committergravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-10-09 11:09:41-07:00
log05915b85dd3c00c40bf6645e0f09157be04dc829
treeeadc479bc345db45d66f16f888a217bc68f2b041
parenteac1e613be56875a08102bd06e333a0621600ee6

compiler-rt: Implement mulXc3 and divXc3 functions

These are the standard complex multiplication/division functions required by the C standard (Annex G). Don't get me started on the standard's handling of complex-infinity...

16 files changed, 439 insertions(+), 1 deletions(-)

lib/compiler_rt.zig+15-1
...@@ -15,11 +15,25 @@ comptime {...@@ -15,11 +15,25 @@ comptime {
15 _ = @import("compiler_rt/subxf3.zig");15 _ = @import("compiler_rt/subxf3.zig");
1616
17 _ = @import("compiler_rt/mulf3.zig");17 _ = @import("compiler_rt/mulf3.zig");
18 _ = @import("compiler_rt/muldf3.zig");
19 _ = @import("compiler_rt/mulsf3.zig");18 _ = @import("compiler_rt/mulsf3.zig");
19 _ = @import("compiler_rt/muldf3.zig");
20 _ = @import("compiler_rt/multf3.zig");20 _ = @import("compiler_rt/multf3.zig");
21 _ = @import("compiler_rt/mulxf3.zig");21 _ = @import("compiler_rt/mulxf3.zig");
2222
23 _ = @import("compiler_rt/mulc3.zig");
24 _ = @import("compiler_rt/mulhc3.zig");
25 _ = @import("compiler_rt/mulsc3.zig");
26 _ = @import("compiler_rt/muldc3.zig");
27 _ = @import("compiler_rt/mulxc3.zig");
28 _ = @import("compiler_rt/multc3.zig");
29
30 _ = @import("compiler_rt/divc3.zig");
31 _ = @import("compiler_rt/divhc3.zig");
32 _ = @import("compiler_rt/divsc3.zig");
33 _ = @import("compiler_rt/divdc3.zig");
34 _ = @import("compiler_rt/divxc3.zig");
35 _ = @import("compiler_rt/divtc3.zig");
36
23 _ = @import("compiler_rt/negsf2.zig");37 _ = @import("compiler_rt/negsf2.zig");
24 _ = @import("compiler_rt/negdf2.zig");38 _ = @import("compiler_rt/negdf2.zig");
25 _ = @import("compiler_rt/negtf2.zig");39 _ = @import("compiler_rt/negtf2.zig");
lib/compiler_rt/divc3.zig created+62
...@@ -0,0 +1,62 @@
1const std = @import("std");
2const isNan = std.math.isNan;
3const isInf = std.math.isInf;
4const scalbn = std.math.scalbn;
5const ilogb = std.math.ilogb;
6const max = std.math.max;
7const fabs = std.math.fabs;
8const maxInt = std.math.maxInt;
9const minInt = std.math.minInt;
10const isFinite = std.math.isFinite;
11const copysign = std.math.copysign;
12const Complex = @import("mulc3.zig").Complex;
13
14/// Implementation based on Annex G of C17 Standard (N2176)
15pub inline fn divc3(comptime T: type, a: T, b: T, c_in: T, d_in: T) Complex(T) {
16 var c = c_in;
17 var d = d_in;
18
19 // logbw used to prevent under/over-flow
20 const logbw = ilogb(max(fabs(c), fabs(d)));
21 const logbw_finite = logbw != maxInt(i32) and logbw != minInt(i32);
22 const ilogbw = if (logbw_finite) b: {
23 c = scalbn(c, -logbw);
24 d = scalbn(d, -logbw);
25 break :b logbw;
26 } else 0;
27 const denom = c * c + d * d;
28 const result = Complex(T){
29 .real = scalbn((a * c + b * d) / denom, -ilogbw),
30 .imag = scalbn((b * c - a * d) / denom, -ilogbw),
31 };
32
33 // Recover infinities and zeros that computed as NaN+iNaN;
34 // the only cases are non-zero/zero, infinite/finite, and finite/infinite, ...
35 if (isNan(result.real) and isNan(result.imag)) {
36 const zero: T = 0.0;
37 const one: T = 1.0;
38
39 if ((denom == 0.0) and (!isNan(a) or !isNan(b))) {
40 return .{
41 .real = copysign(std.math.inf(T), c) * a,
42 .imag = copysign(std.math.inf(T), c) * b,
43 };
44 } else if ((isInf(a) or isInf(b)) and isFinite(c) and isFinite(d)) {
45 const boxed_a = copysign(if (isInf(a)) one else zero, a);
46 const boxed_b = copysign(if (isInf(b)) one else zero, b);
47 return .{
48 .real = std.math.inf(T) * (boxed_a * c - boxed_b * d),
49 .imag = std.math.inf(T) * (boxed_b * c - boxed_a * d),
50 };
51 } else if (logbw == maxInt(i32) and isFinite(a) and isFinite(b)) {
52 const boxed_c = copysign(if (isInf(c)) one else zero, c);
53 const boxed_d = copysign(if (isInf(d)) one else zero, d);
54 return .{
55 .real = 0.0 * (a * boxed_c + b * boxed_d),
56 .imag = 0.0 * (b * boxed_c - a * boxed_d),
57 };
58 }
59 }
60
61 return result;
62}
lib/compiler_rt/divc3_test.zig created+77
...@@ -0,0 +1,77 @@
1const std = @import("std");
2const math = std.math;
3const expect = std.testing.expect;
4
5const Complex = @import("./mulc3.zig").Complex;
6const __divhc3 = @import("./divhc3.zig").__divhc3;
7const __divsc3 = @import("./divsc3.zig").__divsc3;
8const __divdc3 = @import("./divdc3.zig").__divdc3;
9const __divxc3 = @import("./divxc3.zig").__divxc3;
10const __divtc3 = @import("./divtc3.zig").__divtc3;
11
12test {
13 try testDiv(f16, __divhc3);
14 try testDiv(f32, __divsc3);
15 try testDiv(f64, __divdc3);
16 try testDiv(f80, __divxc3);
17 try testDiv(f128, __divtc3);
18}
19
20fn testDiv(comptime T: type, comptime f: fn (T, T, T, T) callconv(.C) Complex(T)) !void {
21 {
22 var a: T = 1.0;
23 var b: T = 0.0;
24 var c: T = -1.0;
25 var d: T = 0.0;
26
27 const result = f(a, b, c, d);
28 try expect(result.real == -1.0);
29 try expect(result.imag == 0.0);
30 }
31 {
32 var a: T = 1.0;
33 var b: T = 0.0;
34 var c: T = -4.0;
35 var d: T = 0.0;
36
37 const result = f(a, b, c, d);
38 try expect(result.real == -0.25);
39 try expect(result.imag == 0.0);
40 }
41 {
42 // if the first operand is an infinity and the second operand is a finite number, then the
43 // result of the / operator is an infinity;
44 var a: T = -math.inf(T);
45 var b: T = 0.0;
46 var c: T = -4.0;
47 var d: T = 1.0;
48
49 const result = f(a, b, c, d);
50 try expect(result.real == math.inf(T));
51 try expect(result.imag == math.inf(T));
52 }
53 {
54 // if the first operand is a finite number and the second operand is an infinity, then the
55 // result of the / operator is a zero;
56 var a: T = 17.2;
57 var b: T = 0.0;
58 var c: T = -math.inf(T);
59 var d: T = 0.0;
60
61 const result = f(a, b, c, d);
62 try expect(result.real == -0.0);
63 try expect(result.imag == 0.0);
64 }
65 {
66 // if the first operand is a nonzero finite number or an infinity and the second operand is
67 // a zero, then the result of the / operator is an infinity
68 var a: T = 1.1;
69 var b: T = 0.1;
70 var c: T = 0.0;
71 var d: T = 0.0;
72
73 const result = f(a, b, c, d);
74 try expect(result.real == math.inf(T));
75 try expect(result.imag == math.inf(T));
76 }
77}
lib/compiler_rt/divdc3.zig created+11
...@@ -0,0 +1,11 @@
1const common = @import("./common.zig");
2const divc3 = @import("./divc3.zig");
3const Complex = @import("./mulc3.zig").Complex;
4
5comptime {
6 @export(__divdc3, .{ .name = "__divdc3", .linkage = common.linkage });
7}
8
9pub fn __divdc3(a: f64, b: f64, c: f64, d: f64) callconv(.C) Complex(f64) {
10 return divc3.divc3(f64, a, b, c, d);
11}
lib/compiler_rt/divhc3.zig created+11
...@@ -0,0 +1,11 @@
1const common = @import("./common.zig");
2const divc3 = @import("./divc3.zig");
3const Complex = @import("./mulc3.zig").Complex;
4
5comptime {
6 @export(__divhc3, .{ .name = "__divhc3", .linkage = common.linkage });
7}
8
9pub fn __divhc3(a: f16, b: f16, c: f16, d: f16) callconv(.C) Complex(f16) {
10 return divc3.divc3(f16, a, b, c, d);
11}
lib/compiler_rt/divsc3.zig created+11
...@@ -0,0 +1,11 @@
1const common = @import("./common.zig");
2const divc3 = @import("./divc3.zig");
3const Complex = @import("./mulc3.zig").Complex;
4
5comptime {
6 @export(__divsc3, .{ .name = "__divsc3", .linkage = common.linkage });
7}
8
9pub fn __divsc3(a: f32, b: f32, c: f32, d: f32) callconv(.C) Complex(f32) {
10 return divc3.divc3(f32, a, b, c, d);
11}
lib/compiler_rt/divtc3.zig created+11
...@@ -0,0 +1,11 @@
1const common = @import("./common.zig");
2const divc3 = @import("./divc3.zig");
3const Complex = @import("./mulc3.zig").Complex;
4
5comptime {
6 @export(__divtc3, .{ .name = "__divtc3", .linkage = common.linkage });
7}
8
9pub fn __divtc3(a: f128, b: f128, c: f128, d: f128) callconv(.C) Complex(f128) {
10 return divc3.divc3(f128, a, b, c, d);
11}
lib/compiler_rt/divxc3.zig created+11
...@@ -0,0 +1,11 @@
1const common = @import("./common.zig");
2const divc3 = @import("./divc3.zig");
3const Complex = @import("./mulc3.zig").Complex;
4
5comptime {
6 @export(__divxc3, .{ .name = "__divxc3", .linkage = common.linkage });
7}
8
9pub fn __divxc3(a: f80, b: f80, c: f80, d: f80) callconv(.C) Complex(f80) {
10 return divc3.divc3(f80, a, b, c, d);
11}
lib/compiler_rt/mulc3.zig created+79
...@@ -0,0 +1,79 @@
1const std = @import("std");
2const isNan = std.math.isNan;
3const isInf = std.math.isInf;
4const copysign = std.math.copysign;
5
6pub fn Complex(comptime T: type) type {
7 return extern struct {
8 real: T,
9 imag: T,
10 };
11}
12
13/// Implementation based on Annex G of C17 Standard (N2176)
14pub inline fn mulc3(comptime T: type, a_in: T, b_in: T, c_in: T, d_in: T) Complex(T) {
15 var a = a_in;
16 var b = b_in;
17 var c = c_in;
18 var d = d_in;
19
20 const ac = a * c;
21 const bd = b * d;
22 const ad = a * d;
23 const bc = b * c;
24
25 const zero: T = 0.0;
26 const one: T = 1.0;
27
28 var z = Complex(T){
29 .real = ac - bd,
30 .imag = ad + bc,
31 };
32 if (isNan(z.real) and isNan(z.imag)) {
33 var recalc: bool = false;
34
35 if (isInf(a) or isInf(b)) { // (a + ib) is infinite
36
37 // "Box" the infinity (+/-inf goes to +/-1, all finite values go to 0)
38 a = copysign(if (isInf(a)) one else zero, a);
39 b = copysign(if (isInf(b)) one else zero, b);
40
41 // Replace NaNs in the other factor with (signed) 0
42 if (isNan(c)) c = copysign(zero, c);
43 if (isNan(d)) d = copysign(zero, d);
44
45 recalc = true;
46 }
47
48 if (isInf(c) or isInf(d)) { // (c + id) is infinite
49
50 // "Box" the infinity (+/-inf goes to +/-1, all finite values go to 0)
51 c = copysign(if (isInf(c)) one else zero, c);
52 d = copysign(if (isInf(d)) one else zero, d);
53
54 // Replace NaNs in the other factor with (signed) 0
55 if (isNan(a)) a = copysign(zero, a);
56 if (isNan(b)) b = copysign(zero, b);
57
58 recalc = true;
59 }
60
61 if (!recalc and (isInf(ac) or isInf(bd) or isInf(ad) or isInf(bc))) {
62
63 // Recover infinities from overflow by changing NaNs to 0
64 if (isNan(a)) a = copysign(zero, a);
65 if (isNan(b)) b = copysign(zero, b);
66 if (isNan(c)) c = copysign(zero, c);
67 if (isNan(d)) d = copysign(zero, d);
68
69 recalc = true;
70 }
71 if (recalc) {
72 return .{
73 .real = std.math.inf(T) * (a * c - b * d),
74 .imag = std.math.inf(T) * (a * d + b * c),
75 };
76 }
77 }
78 return z;
79}
lib/compiler_rt/mulc3_test.zig created+65
...@@ -0,0 +1,65 @@
1const std = @import("std");
2const math = std.math;
3const expect = std.testing.expect;
4
5const Complex = @import("./mulc3.zig").Complex;
6const __mulhc3 = @import("./mulhc3.zig").__mulhc3;
7const __mulsc3 = @import("./mulsc3.zig").__mulsc3;
8const __muldc3 = @import("./muldc3.zig").__muldc3;
9const __mulxc3 = @import("./mulxc3.zig").__mulxc3;
10const __multc3 = @import("./multc3.zig").__multc3;
11
12test {
13 try testMul(f16, __mulhc3);
14 try testMul(f32, __mulsc3);
15 try testMul(f64, __muldc3);
16 try testMul(f80, __mulxc3);
17 try testMul(f128, __multc3);
18}
19
20fn testMul(comptime T: type, comptime f: fn (T, T, T, T) callconv(.C) Complex(T)) !void {
21 {
22 var a: T = 1.0;
23 var b: T = 0.0;
24 var c: T = -1.0;
25 var d: T = 0.0;
26
27 const result = f(a, b, c, d);
28 try expect(result.real == -1.0);
29 try expect(result.imag == 0.0);
30 }
31 {
32 var a: T = 1.0;
33 var b: T = 0.0;
34 var c: T = -4.0;
35 var d: T = 0.0;
36
37 const result = f(a, b, c, d);
38 try expect(result.real == -4.0);
39 try expect(result.imag == 0.0);
40 }
41 {
42 // if one operand is an infinity and the other operand is a nonzero finite number or an infinity,
43 // then the result of the * operator is an infinity;
44 var a: T = math.inf(T);
45 var b: T = -math.inf(T);
46 var c: T = 1.0;
47 var d: T = 0.0;
48
49 const result = f(a, b, c, d);
50 try expect(result.real == math.inf(T));
51 try expect(result.imag == -math.inf(T));
52 }
53 {
54 // if one operand is an infinity and the other operand is a nonzero finite number or an infinity,
55 // then the result of the * operator is an infinity;
56 var a: T = math.inf(T);
57 var b: T = -1.0;
58 var c: T = 1.0;
59 var d: T = math.inf(T);
60
61 const result = f(a, b, c, d);
62 try expect(result.real == math.inf(T));
63 try expect(result.imag == math.inf(T));
64 }
65}
lib/compiler_rt/muldc3.zig created+12
...@@ -0,0 +1,12 @@
1const common = @import("./common.zig");
2const mulc3 = @import("./mulc3.zig");
3
4pub const panic = common.panic;
5
6comptime {
7 @export(__muldc3, .{ .name = "__muldc3", .linkage = common.linkage });
8}
9
10pub fn __muldc3(a: f64, b: f64, c: f64, d: f64) callconv(.C) mulc3.Complex(f64) {
11 return mulc3.mulc3(f64, a, b, c, d);
12}
lib/compiler_rt/mulhc3.zig created+12
...@@ -0,0 +1,12 @@
1const common = @import("./common.zig");
2const mulc3 = @import("./mulc3.zig");
3
4pub const panic = common.panic;
5
6comptime {
7 @export(__mulhc3, .{ .name = "__mulhc3", .linkage = common.linkage });
8}
9
10pub fn __mulhc3(a: f16, b: f16, c: f16, d: f16) callconv(.C) mulc3.Complex(f16) {
11 return mulc3.mulc3(f16, a, b, c, d);
12}
lib/compiler_rt/mulsc3.zig created+12
...@@ -0,0 +1,12 @@
1const common = @import("./common.zig");
2const mulc3 = @import("./mulc3.zig");
3
4pub const panic = common.panic;
5
6comptime {
7 @export(__mulsc3, .{ .name = "__mulsc3", .linkage = common.linkage });
8}
9
10pub fn __mulsc3(a: f32, b: f32, c: f32, d: f32) callconv(.C) mulc3.Complex(f32) {
11 return mulc3.mulc3(f32, a, b, c, d);
12}
lib/compiler_rt/multc3.zig created+12
...@@ -0,0 +1,12 @@
1const common = @import("./common.zig");
2const mulc3 = @import("./mulc3.zig");
3
4pub const panic = common.panic;
5
6comptime {
7 @export(__multc3, .{ .name = "__multc3", .linkage = common.linkage });
8}
9
10pub fn __multc3(a: f128, b: f128, c: f128, d: f128) callconv(.C) mulc3.Complex(f128) {
11 return mulc3.mulc3(f128, a, b, c, d);
12}
lib/compiler_rt/mulxc3.zig created+12
...@@ -0,0 +1,12 @@
1const common = @import("./common.zig");
2const mulc3 = @import("./mulc3.zig");
3
4pub const panic = common.panic;
5
6comptime {
7 @export(__mulxc3, .{ .name = "__mulxc3", .linkage = common.linkage });
8}
9
10pub fn __mulxc3(a: f80, b: f80, c: f80, d: f80) callconv(.C) mulc3.Complex(f80) {
11 return mulc3.mulc3(f80, a, b, c, d);
12}
test/standalone/c_compiler/test.c+26
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1#include <assert.h>1#include <assert.h>
2#include <complex.h>
2#include <stdio.h>3#include <stdio.h>
3#include <stdlib.h>4#include <stdlib.h>
45
...@@ -24,5 +25,30 @@ int main (int argc, char *argv[])...@@ -24,5 +25,30 @@ int main (int argc, char *argv[])
2425
25 if (!ok) abort();26 if (!ok) abort();
2627
28 // Test some basic arithmetic from compiler-rt
29 {
30 double complex z = 0.0 + I * 4.0;
31 double complex w = 0.0 + I * 16.0;
32 double complex product = z * w;
33 double complex quotient = z / w;
34
35 if (!(creal(product) == -64.0)) abort();
36 if (!(cimag(product) == 0.0)) abort();
37 if (!(creal(quotient) == 0.25)) abort();
38 if (!(cimag(quotient) == 0.0)) abort();
39 }
40
41 {
42 float complex z = 4.0 + I * 4.0;
43 float complex w = 2.0 - I * 2.0;
44 float complex product = z * w;
45 float complex quotient = z / w;
46
47 if (!(creal(product) == 16.0)) abort();
48 if (!(cimag(product) == 0.0)) abort();
49 if (!(creal(quotient) == 0.0)) abort();
50 if (!(cimag(quotient) == 2.0)) abort();
51 }
52
27 return EXIT_SUCCESS;53 return EXIT_SUCCESS;
28}54}