Skip to main content

rt/
x87.rs

1// x87 -- the extended-precision steps of the decimal <-> double conversions   [src/core/ini.s]
2//
3//   scale(m, e) -> f64          m * 10^e (e < 0: m / 10^-e), one rounding to double  (f64_parse)
4//   scale_round(v, k) -> i64    round(v * 10^k) (k < 0: v / 10^-k), to nearest       (f64_format)
5//   log10_round(v) -> i32       round(log10 v) as fyl2x computes it                  (f64_format)
6//   pow(x, y) -> f64            x^y as fx_pow computes it (R6-icc)       [src/fx/fx_lut.s]
7//   exp2 / log2 / cos / sin     fx_exp2 / fx_log2 / fx_cos / fx_sin (r10-fx)  [src/fx/fx_lut.s]
8//   rz_exp2 / rz_log2           PaintCraft's rz_exp2 / rz_log2 (pn-ui; other rounding)  [dlg_rotzoom.s]
9//
10// ASMLIB computes these on the x87 unit with 64-bit precision and round to nearest (the
11// control word is saved and restored in every call), 10^n as fld1 times the double table
12// 1e0..1e22 (1e22 repeated for larger n).  The same instructions run here, so the text a
13// double is written as and the double a text reads as are bit-identical to the assembly.
14
15use core::arch::asm;
16
17static POW10: [f64; 23] = [
18    1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17,
19    1e18, 1e19, 1e20, 1e21, 1e22,
20];
21
22/// m * 10^e or m / 10^-e (m < 2^63), rounded once to double  [f64_parse .fp_calc]
23pub fn scale(m: u64, e: i32) -> f64 {
24    let mi = m as i64;
25    let mut out = 0f64;
26    let mut cw = [0u16; 2];
27    // SAFETY: the block reads mi / POW10 and writes cw / out through valid pointers, leaves
28    // the x87 stack empty and restores the control word.
29    unsafe {
30        asm!(
31            "fnstcw word ptr [{cw}]",
32            "movzx {t:e}, word ptr [{cw}]",
33            "and {t:e}, 0xF3FF",
34            "or {t:e}, 0x0300",
35            "mov word ptr [{cw} + 2], {t:x}",
36            "fldcw word ptr [{cw} + 2]",
37            "fld1",
38            "2:",
39            "cmp {n:e}, 22",
40            "jbe 3f",
41            "fmul qword ptr [{tab} + 176]",
42            "sub {n:e}, 22",
43            "jmp 2b",
44            "3:",
45            "fmul qword ptr [{tab} + {n} * 8]",
46            "fild qword ptr [{m}]",
47            "test {neg:e}, {neg:e}",
48            "jnz 4f",
49            "fmul st(0), st(1)",
50            "jmp 5f",
51            "4:",
52            "fdiv st(0), st(1)",
53            "5:",
54            "fstp st(1)",
55            "fstp qword ptr [{out}]",
56            "fldcw word ptr [{cw}]",
57            cw = in(reg) cw.as_mut_ptr(),
58            t = out(reg) _,
59            n = inout(reg) e.clamp(-5000, 5000).unsigned_abs() as u64 => _,
60            tab = in(reg) POW10.as_ptr(),
61            m = in(reg) &mi,
62            neg = in(reg) (e < 0) as u32,
63            out = in(reg) &mut out,
64            out("st(0)") _, out("st(1)") _, out("st(2)") _, out("st(3)") _,
65            out("st(4)") _, out("st(5)") _, out("st(6)") _, out("st(7)") _,
66        );
67    }
68    out
69}
70
71/// frndint(v * 10^k) or frndint(v / 10^-k) as a 64-bit integer  [f64_format .ff_scale]
72pub fn scale_round(v: f64, k: i32) -> i64 {
73    let mut out = 0i64;
74    let mut cw = [0u16; 2];
75    // SAFETY: as in scale.
76    unsafe {
77        asm!(
78            "fnstcw word ptr [{cw}]",
79            "movzx {t:e}, word ptr [{cw}]",
80            "and {t:e}, 0xF3FF",
81            "or {t:e}, 0x0300",
82            "mov word ptr [{cw} + 2], {t:x}",
83            "fldcw word ptr [{cw} + 2]",
84            "fld1",
85            "2:",
86            "cmp {n:e}, 22",
87            "jbe 3f",
88            "fmul qword ptr [{tab} + 176]",
89            "sub {n:e}, 22",
90            "jmp 2b",
91            "3:",
92            "fmul qword ptr [{tab} + {n} * 8]",
93            "fld qword ptr [{v}]",
94            "test {neg:e}, {neg:e}",
95            "jnz 4f",
96            "fmul st(0), st(1)",
97            "jmp 5f",
98            "4:",
99            "fdiv st(0), st(1)",
100            "5:",
101            "fstp st(1)",
102            "frndint",
103            "fistp qword ptr [{out}]",
104            "fldcw word ptr [{cw}]",
105            cw = in(reg) cw.as_mut_ptr(),
106            t = out(reg) _,
107            n = inout(reg) k.clamp(-5000, 5000).unsigned_abs() as u64 => _,
108            tab = in(reg) POW10.as_ptr(),
109            v = in(reg) &v,
110            neg = in(reg) (k < 0) as u32,
111            out = in(reg) &mut out,
112            out("st(0)") _, out("st(1)") _, out("st(2)") _, out("st(3)") _,
113            out("st(4)") _, out("st(5)") _, out("st(6)") _, out("st(7)") _,
114        );
115    }
116    out
117}
118
119/// fldlg2 / fyl2x / fistp: log10 v rounded to the nearest integer  [f64_format]
120pub fn log10_round(v: f64) -> i32 {
121    let mut out = 0i32;
122    let mut cw = [0u16; 2];
123    // SAFETY: as in scale.
124    unsafe {
125        asm!(
126            "fnstcw word ptr [{cw}]",
127            "movzx {t:e}, word ptr [{cw}]",
128            "and {t:e}, 0xF3FF",
129            "or {t:e}, 0x0300",
130            "mov word ptr [{cw} + 2], {t:x}",
131            "fldcw word ptr [{cw} + 2]",
132            "fldlg2",
133            "fld qword ptr [{v}]",
134            "fyl2x",
135            "fistp dword ptr [{out}]",
136            "fldcw word ptr [{cw}]",
137            cw = in(reg) cw.as_mut_ptr(),
138            t = out(reg) _,
139            v = in(reg) &v,
140            out = in(reg) &mut out,
141            out("st(0)") _, out("st(1)") _, out("st(2)") _, out("st(3)") _,
142            out("st(4)") _, out("st(5)") _, out("st(6)") _, out("st(7)") _,
143        );
144    }
145    out
146}
147
148/// fpatan: atan2(y, x) on the x87 unit, rounded once to double (the precision control does
149/// not apply to fpatan)  [pathdata.s pd_atan2, dash.s dc_atan2]
150pub fn atan2(y: f64, x: f64) -> f64 {
151    let mut out = 0f64;
152    // SAFETY: reads y / x and writes out through valid pointers; the x87 stack ends empty.
153    unsafe {
154        asm!(
155            "fld qword ptr [{y}]",
156            "fld qword ptr [{x}]",
157            "fpatan",
158            "fstp qword ptr [{out}]",
159            y = in(reg) &y,
160            x = in(reg) &x,
161            out = in(reg) &mut out,
162            out("st(0)") _, out("st(1)") _, out("st(2)") _, out("st(3)") _,
163            out("st(4)") _, out("st(5)") _, out("st(6)") _, out("st(7)") _,
164        );
165    }
166    out
167}
168
169/// fsincos: (cos x, sin x) on the x87 unit, each rounded once to double (the precision
170/// control does not apply to fsincos)  [ui_value.s deg_sincos]
171// ui-list: added for the angle dial and the colour wheel marker (styles/winui value.s, misc.s)
172pub fn sincos(x: f64) -> (f64, f64) {
173    let mut c = 0f64;
174    let mut s = 0f64;
175    // SAFETY: reads x and writes c / s through valid pointers; the x87 stack ends empty.
176    unsafe {
177        asm!(
178            "fld qword ptr [{x}]",
179            "fsincos",
180            "fstp qword ptr [{c}]",
181            "fstp qword ptr [{s}]",
182            x = in(reg) &x,
183            c = in(reg) &mut c,
184            s = in(reg) &mut s,
185            out("st(0)") _, out("st(1)") _, out("st(2)") _, out("st(3)") _,
186            out("st(4)") _, out("st(5)") _, out("st(6)") _, out("st(7)") _,
187        );
188    }
189    (c, s)
190}
191
192/// fx_pow: x^y -- NaN when x or y is NaN, 1 for y == 0, 0 for x <= 0, x for y == 1, else
193/// 2^(y log2 x) on the x87 unit with 64-bit precision and round to nearest (fyl2x, then
194/// frndint / f2xm1 / fscale), rounded once to double  [fx_lut.s fx_pow]
195// R6-icc: added for the ICC curves and the Lab maths (icc_xform.s cv_eval / lab_f)
196pub fn pow(x: f64, y: f64) -> f64 {
197    const NAN: u64 = 0x7FF8000000000000;
198    if y.is_nan() {
199        return f64::from_bits(NAN);
200    }
201    if y == 0.0 {
202        return 1.0;
203    }
204    if x.is_nan() {
205        return f64::from_bits(NAN);
206    }
207    if x <= 0.0 {
208        return 0.0;
209    }
210    if y == 1.0 {
211        return x;
212    }
213    let mut out = 0f64;
214    let mut cw = [0u16; 2];
215    // SAFETY: reads x / y and writes cw / out through valid pointers, leaves the x87 stack
216    // empty and restores the control word.
217    unsafe {
218        asm!(
219            "fnstcw word ptr [{cw}]",
220            "movzx {t:e}, word ptr [{cw}]",
221            "and {t:e}, 0xF3FF",
222            "or {t:e}, 0x0300",
223            "mov word ptr [{cw} + 2], {t:x}",
224            "fldcw word ptr [{cw} + 2]",
225            "fld qword ptr [{y}]",
226            "fld qword ptr [{x}]",
227            "fyl2x",
228            "fld st(0)",
229            "frndint",
230            "fsub st(1), st(0)",
231            "fxch",
232            "f2xm1",
233            "fld1",
234            "faddp",
235            "fscale",
236            "fstp st(1)",
237            "fstp qword ptr [{out}]",
238            "fldcw word ptr [{cw}]",
239            cw = in(reg) cw.as_mut_ptr(),
240            t = out(reg) _,
241            x = in(reg) &x,
242            y = in(reg) &y,
243            out = in(reg) &mut out,
244            out("st(0)") _, out("st(1)") _, out("st(2)") _, out("st(3)") _,
245            out("st(4)") _, out("st(5)") _, out("st(6)") _, out("st(7)") _,
246        );
247    }
248    out
249}
250
251/// fyl2x with y = 1: log2(x) on the x87 unit, rounded once to double (fyl2x is not subject
252/// to the precision control)  [PaintCraft dlg_rotzoom.s rz_log2]
253// pn-ui: added for the Rotationszoom zoom slider (fx's log2 below sets the precision control)
254pub fn rz_log2(x: f64) -> f64 {
255    let mut out = 0f64;
256    // SAFETY: reads x and writes out through valid pointers; the x87 stack ends empty.
257    unsafe {
258        asm!(
259            "fld1",
260            "fld qword ptr [{x}]",
261            "fyl2x",
262            "fstp qword ptr [{out}]",
263            x = in(reg) &x,
264            out = in(reg) &mut out,
265            out("st(0)") _, out("st(1)") _, out("st(2)") _, out("st(3)") _,
266            out("st(4)") _, out("st(5)") _, out("st(6)") _, out("st(7)") _,
267        );
268    }
269    out
270}
271
272/// 2^x as rz_exp2 computes it (|x| < 2^30): i = cvtsd2si x, f = x - i (SSE), then on the x87
273/// unit f2xm1 f, + 1 (faddp: rounded by the process's precision control, as in the
274/// assembly), fscale by i, rounded to double  [PaintCraft dlg_rotzoom.s rz_exp2]
275// pn-ui: added for the Rotationszoom zoom slider (fx's exp2 below rounds with frndint)
276pub fn rz_exp2(x: f64) -> f64 {
277    let i = crate::fx::cvtd(x);
278    let f = x - i as f64;
279    let mut out = 0f64;
280    // SAFETY: reads i / f and writes out through valid pointers; the x87 stack ends empty.
281    unsafe {
282        asm!(
283            "fild dword ptr [{i}]",
284            "fld qword ptr [{f}]",
285            "f2xm1",
286            "fld1",
287            "faddp",
288            "fscale",
289            "fstp qword ptr [{out}]",
290            "fstp st(0)",
291            i = in(reg) &i,
292            f = in(reg) &f,
293            out = in(reg) &mut out,
294            out("st(0)") _, out("st(1)") _, out("st(2)") _, out("st(3)") _,
295            out("st(4)") _, out("st(5)") _, out("st(6)") _, out("st(7)") _,
296        );
297    }
298    out
299}
300
301// r10-fx: the remaining x87 helpers of the effect kernels (fx_lut.s fx_exp2 / fx_log2 /
302// fx_cos / fx_sin): 64-bit precision and round to nearest (the control word is set and
303// restored as X87_ENTER / X87_LEAVE do), the result rounded once to double
304macro_rules! x87_unary {
305    ($(#[$m:meta])* $name:ident, $($ins:literal),*) => {
306        $(#[$m])*
307        pub fn $name(x: f64) -> f64 {
308            let mut out = 0f64;
309            let mut cw = [0u16; 2];
310            // SAFETY: reads x and writes cw / out through valid pointers, leaves the x87
311            // stack empty and restores the control word.
312            unsafe {
313                asm!(
314                    "fnstcw word ptr [{cw}]",
315                    "movzx {t:e}, word ptr [{cw}]",
316                    "and {t:e}, 0xF3FF",
317                    "or {t:e}, 0x0300",
318                    "mov word ptr [{cw} + 2], {t:x}",
319                    "fldcw word ptr [{cw} + 2]",
320                    $($ins,)*
321                    "fstp qword ptr [{out}]",
322                    "fldcw word ptr [{cw}]",
323                    cw = in(reg) cw.as_mut_ptr(),
324                    t = out(reg) _,
325                    x = in(reg) &x,
326                    out = in(reg) &mut out,
327                    out("st(0)") _, out("st(1)") _, out("st(2)") _, out("st(3)") _,
328                    out("st(4)") _, out("st(5)") _, out("st(6)") _, out("st(7)") _,
329                );
330            }
331            out
332        }
333    };
334}
335
336x87_unary!(
337    /// fx_exp2: 2^x (frndint / f2xm1 / fscale)  [fx_lut.s fx_exp2]
338    exp2, "fld qword ptr [{x}]", "fld st(0)", "frndint", "fsub st(1), st(0)", "fxch", "f2xm1", "fld1",
339    "faddp", "fscale", "fstp st(1)"
340);
341x87_unary!(
342    /// fx_log2: log2 x (fld1 / fyl2x; x <= 0: -inf / NaN as the FPU delivers)  [fx_lut.s fx_log2]
343    log2, "fld1", "fld qword ptr [{x}]", "fyl2x"
344);
345x87_unary!(
346    /// fx_cos: fcos (|x| < 2^62)  [fx_lut.s fx_cos]
347    cos, "fld qword ptr [{x}]", "fcos"
348);
349x87_unary!(
350    /// fx_sin: fsin (|x| < 2^62)  [fx_lut.s fx_sin]
351    sin, "fld qword ptr [{x}]", "fsin"
352);