Skip to content

Commit 443365a

Browse files
Automatic arithmetic promotion (#588)
* Port `inc'` * Port `dec'` * Port `+'` * Port `-'` * Port `*'` * Move `inc'` & `dec'` to LLVM overflow checks * Add missing cases * Remove promotion support for `jank::runtime::real` values After discussing with the Clojure core team we found out that Clojure intentionally chooses not to support automatic promotion for `real` values to `big_decimal`. - Clojure Q/A: https://ask.clojure.org/index.php/14752/support-for-automatic-promotion-to-bigdecimal-in-etc. - Slack thread: https://clojurians.slack.com/archives/C053AK3F9/p1762684740564009. * Port `num` Resolves #604. * Enable `+'` tests * Enable `*'` tests * Disable blocked tests * Enable the `ratio_qmark` tests
1 parent 7a79722 commit 443365a

File tree

4 files changed

+190
-24
lines changed

4 files changed

+190
-24
lines changed

compiler+runtime/include/cpp/jank/runtime/core/math.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ namespace jank::runtime
4141
object_ref add(obj::ratio_ref l, obj::integer_ref r);
4242
obj::ratio_ref add(obj::integer_ref l, obj::ratio_ref r);
4343

44+
object_ref promoting_add(object_ref const l, object_ref const r);
45+
4446
object_ref sub(object_ref l, object_ref r);
4547
object_ref sub(obj::integer_ref l, object_ref r);
4648
object_ref sub(object_ref l, obj::integer_ref r);
@@ -64,6 +66,8 @@ namespace jank::runtime
6466
object_ref sub(i64 l, object_ref r);
6567
i64 sub(i64 l, i64 r);
6668

69+
object_ref promoting_sub(object_ref const l, object_ref const r);
70+
6771
object_ref div(object_ref l, object_ref r);
6872
object_ref div(obj::integer_ref l, object_ref r);
6973
object_ref div(object_ref l, obj::integer_ref r);
@@ -110,6 +114,8 @@ namespace jank::runtime
110114
object_ref mul(i64 l, object_ref r);
111115
i64 mul(i64 l, i64 r);
112116

117+
object_ref promoting_mul(object_ref const l, object_ref const r);
118+
113119
bool lt(object_ref l, object_ref r);
114120
bool lt(obj::integer_ref l, object_ref r);
115121
bool lt(object_ref l, obj::integer_ref r);
@@ -232,7 +238,9 @@ namespace jank::runtime
232238
object_ref rem(object_ref l, object_ref r);
233239
object_ref quot(object_ref l, object_ref r);
234240
object_ref inc(object_ref l);
241+
object_ref promoting_inc(object_ref const l);
235242
object_ref dec(object_ref l);
243+
object_ref promoting_dec(object_ref const l);
236244

237245
bool is_zero(object_ref l);
238246
bool is_pos(object_ref l);
@@ -269,6 +277,8 @@ namespace jank::runtime
269277
f64 to_real(object_ref o);
270278

271279
bool is_number(object_ref o);
280+
object_ref number(object_ref const o);
281+
272282
bool is_integer(object_ref o);
273283
bool is_real(object_ref o);
274284
bool is_ratio(object_ref o);
@@ -279,6 +289,7 @@ namespace jank::runtime
279289
i64 parse_long(object_ref o);
280290
f64 parse_double(object_ref o);
281291

292+
bool is_big_integer(object_ref const o);
282293
obj::big_integer_ref to_big_integer(object_ref const o);
283294

284295
bool is_big_decimal(object_ref const o);

compiler+runtime/src/cpp/jank/runtime/core/math.cpp

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,40 @@ namespace jank::runtime
171171
return l + r;
172172
}
173173

174+
object_ref promoting_add(object_ref const l, object_ref const r)
175+
{
176+
return visit_number_like(
177+
[](auto const typed_l, auto const r) -> object_ref {
178+
using LT = typename decltype(typed_l)::value_type;
179+
180+
return visit_number_like(
181+
[](auto const typed_r, auto const &l_val) -> object_ref {
182+
using RT = typename decltype(typed_r)::value_type;
183+
184+
if constexpr(std::same_as<LT, obj::integer> && std::same_as<RT, obj::integer>)
185+
{
186+
i64 res{};
187+
188+
if(static_cast<bool>(__builtin_add_overflow(l_val, typed_r->data, &res)))
189+
{
190+
native_big_integer const l{ l_val };
191+
return make_box<obj::big_integer>(l + typed_r->data);
192+
}
193+
194+
return make_box<obj::integer>(res);
195+
}
196+
else
197+
{
198+
return make_box(l_val + typed_r->data);
199+
}
200+
},
201+
r,
202+
typed_l->data);
203+
},
204+
l,
205+
r);
206+
}
207+
174208
object_ref sub(object_ref const l, object_ref const r)
175209
{
176210
return visit_number_like(
@@ -306,6 +340,40 @@ namespace jank::runtime
306340
return l - r;
307341
}
308342

343+
object_ref promoting_sub(object_ref const l, object_ref const r)
344+
{
345+
return visit_number_like(
346+
[](auto const typed_l, auto const r) -> object_ref {
347+
using LT = typename decltype(typed_l)::value_type;
348+
349+
return visit_number_like(
350+
[](auto const typed_r, auto const &l_val) -> object_ref {
351+
using RT = typename decltype(typed_r)::value_type;
352+
353+
if constexpr(std::same_as<LT, obj::integer> && std::same_as<RT, obj::integer>)
354+
{
355+
i64 res{};
356+
357+
if(__builtin_sub_overflow(l_val, typed_r->data, &res))
358+
{
359+
native_big_integer const l{ l_val };
360+
return make_box<obj::big_integer>(l_val - typed_r->data);
361+
}
362+
363+
return make_box<obj::integer>(res);
364+
}
365+
else
366+
{
367+
return make_box(l_val - typed_r->data);
368+
}
369+
},
370+
r,
371+
typed_l->data);
372+
},
373+
l,
374+
r);
375+
}
376+
309377
object_ref div(object_ref const l, object_ref const r)
310378
{
311379
return visit_number_like(
@@ -576,6 +644,40 @@ namespace jank::runtime
576644
return l * r;
577645
}
578646

647+
object_ref promoting_mul(object_ref const l, object_ref const r)
648+
{
649+
return visit_number_like(
650+
[](auto const typed_l, auto const r) -> object_ref {
651+
using LT = typename decltype(typed_l)::value_type;
652+
653+
return visit_number_like(
654+
[](auto const typed_r, auto const &l_val) -> object_ref {
655+
using RT = typename decltype(typed_r)::value_type;
656+
657+
if constexpr(std::same_as<LT, obj::integer> && std::same_as<RT, obj::integer>)
658+
{
659+
i64 res{};
660+
661+
if(__builtin_mul_overflow(l_val, typed_r->data, &res))
662+
{
663+
native_big_integer const l{ l_val };
664+
return make_box<obj::big_integer>(l * typed_r->data);
665+
}
666+
667+
return make_box<obj::integer>(res);
668+
}
669+
else
670+
{
671+
return make_box(l_val * typed_r->data);
672+
}
673+
},
674+
r,
675+
typed_l->data);
676+
},
677+
l,
678+
r);
679+
}
680+
579681
object_ref rem(object_ref const l, object_ref const r)
580682
{
581683
return visit_number_like(
@@ -659,13 +761,65 @@ namespace jank::runtime
659761
l);
660762
}
661763

764+
object_ref promoting_inc(object_ref const l)
765+
{
766+
return visit_number_like(
767+
[](auto const typed_l) -> object_ref {
768+
using T = typename decltype(typed_l)::value_type;
769+
770+
if constexpr(std::same_as<T, obj::integer>)
771+
{
772+
i64 res{};
773+
774+
if(__builtin_add_overflow(typed_l->data, 1ll, &res))
775+
{
776+
native_big_integer const v{ typed_l->data };
777+
return make_box<obj::big_integer>(v + 1ll);
778+
}
779+
780+
return make_box<obj::integer>(res);
781+
}
782+
else
783+
{
784+
return make_box(typed_l->data + 1ll);
785+
}
786+
},
787+
l);
788+
}
789+
662790
object_ref dec(object_ref const l)
663791
{
664792
return visit_number_like(
665793
[](auto const typed_l) -> object_ref { return make_box(typed_l->data - 1ll).erase(); },
666794
l);
667795
}
668796

797+
object_ref promoting_dec(object_ref const l)
798+
{
799+
return visit_number_like(
800+
[](auto const typed_l) -> object_ref {
801+
using T = typename decltype(typed_l)::value_type;
802+
803+
if constexpr(std::same_as<T, obj::integer>)
804+
{
805+
i64 res{};
806+
807+
if(__builtin_sub_overflow(typed_l->data, 1ll, &res))
808+
{
809+
native_big_integer const v{ typed_l->data };
810+
return make_box<obj::big_integer>(v - 1ll);
811+
}
812+
813+
return make_box<obj::integer>(res);
814+
}
815+
else
816+
{
817+
return make_box(typed_l->data - 1ll);
818+
}
819+
},
820+
l);
821+
}
822+
669823
bool is_zero(object_ref const l)
670824
{
671825
return visit_number_like(
@@ -1710,6 +1864,11 @@ namespace jank::runtime
17101864
o);
17111865
}
17121866

1867+
object_ref number(object_ref const o)
1868+
{
1869+
return visit_number_like([](auto const typed_l) -> object_ref { return typed_l; }, o);
1870+
}
1871+
17131872
bool is_integer(object_ref const o)
17141873
{
17151874
return o->type == object_type::integer;
@@ -1793,6 +1952,11 @@ namespace jank::runtime
17931952
}
17941953
}
17951954

1955+
bool is_big_integer(object_ref const o)
1956+
{
1957+
return o->type == object_type::big_integer;
1958+
}
1959+
17961960
obj::big_integer_ref to_big_integer(object_ref const o)
17971961
{
17981962
return visit_number_like(

compiler+runtime/src/jank/clojure/core.jank

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,7 +1126,7 @@
11261126
([]
11271127
0)
11281128
([x]
1129-
x)
1129+
(cpp/jank.runtime.number x))
11301130
([l r]
11311131
(cpp/jank.runtime.add l r))
11321132
([l r & args]
@@ -1156,7 +1156,7 @@
11561156
([]
11571157
1)
11581158
([x]
1159-
x)
1159+
(cpp/jank.runtime.number x))
11601160
([l r]
11611161
(cpp/jank.runtime.mul l r))
11621162
([l r & args]
@@ -4482,8 +4482,7 @@
44824482
"Returns a number one greater than num. Supports arbitrary precision.
44834483
See also: inc"
44844484
[x]
4485-
;; (. clojure.lang.Numbers (incP x))
4486-
(throw "TODO: port inc'"))
4485+
(cpp/jank.runtime.promoting_inc x))
44874486

44884487
;; ;;math stuff
44894488
;; (defn ^:private nary-inline
@@ -4509,11 +4508,9 @@
45094508
See also: +"
45104509
([] 0)
45114510
([x]
4512-
;; (cast Number x)
4513-
(throw "TODO: port +'"))
4511+
(cpp/jank.runtime.number x))
45144512
([x y]
4515-
;; (. clojure.lang.Numbers (addP x y))
4516-
(throw "TODO: port +'"))
4513+
(cpp/jank.runtime.promoting_add x y))
45174514
([x y & more]
45184515
(reduce +' (+' x y) more)))
45194516

@@ -4522,11 +4519,9 @@
45224519
See also: *"
45234520
([] 1)
45244521
([x]
4525-
;; (cast Number x)
4526-
(throw "TODO: port *'"))
4522+
(cpp/jank.runtime.number x))
45274523
([x y]
4528-
;; (. clojure.lang.Numbers (multiplyP x y))
4529-
(throw "TODO: port *'"))
4524+
(cpp/jank.runtime.promoting_mul x y))
45304525
([x y & more]
45314526
(reduce *' (*' x y) more)))
45324527

@@ -4535,11 +4530,9 @@
45354530
the ys from x and returns the result. Supports arbitrary precision.
45364531
See also: -"
45374532
([x]
4538-
;; (. clojure.lang.Numbers (minusP x))
4539-
(throw "TODO: port -'"))
4533+
(-' 0 x))
45404534
([x y]
4541-
;; (. clojure.lang.Numbers (minusP x y))
4542-
(throw "TODO: port -'"))
4535+
(cpp/jank.runtime.promoting_sub x y))
45434536
([x y & more]
45444537
(reduce -' (-' x y) more)))
45454538

@@ -4556,8 +4549,7 @@
45564549
"Returns a number one less than num. Supports arbitrary precision.
45574550
See also: dec"
45584551
[x]
4559-
;; (. clojure.lang.Numbers (decP x))
4560-
(throw "TODO: port dec'"))
4552+
(cpp/jank.runtime.promoting_dec x))
45614553

45624554
(defn unchecked-inc-int
45634555
"Returns a number one greater than x, an int.
@@ -5340,8 +5332,7 @@
53405332
(defn num
53415333
"Coerce to Number"
53425334
[x]
5343-
;; (. clojure.lang.Numbers (num x))
5344-
(throw "TODO: port num"))
5335+
(cpp/jank.runtime.number x))
53455336

53465337
(defn long
53475338
"Coerce to long"

compiler+runtime/test/bash/clojure-test-suite/src/jank_test/run_clojure_test_suite.cljc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,15 @@
7575
;clojure.core-test.nth ; Uncaught exception: index out of bounds: -1, In clojure.core-test.nth$fn_2-64, duplicate definition of symbol '_fn_2_0', https://github.com/jank-lang/jank/issues/244, Exception: index out of bounds: -1
7676
;clojure.core-test.nthnext ; Uncaught exception: not a number: nil, In clojure.core-test.nthnext$fn_2-83, duplicate definition of symbol '_fn_2_0', https://github.com/jank-lang/jank/issues/243 , https://github.com/jank-lang/jank/issues/244
7777
;clojure.core-test.nthrest ; Uncaught exception: not a number: nil, In clojure.core-test.nthrest$fn_2-85, duplicate definition of symbol '_fn_2_0', https://github.com/jank-lang/jank/issues/243 , https://github.com/jank-lang/jank/issues/244 , https://github.com/jank-lang/jank/issues/247
78-
clojure.core-test.num ; Expecting whitespace after the last token. due to M.
78+
clojure.core-test.num
7979
clojure.core-test.number-qmark ; Expecting whitespace after the last token. due to M.
8080
clojure.core-test.number-range
8181
;clojure.core-test.numerator ; Failed a test. Expecting whitespace after the last token. due to M.
8282
;clojure.core-test.odd-qmark ; Uncaught exception: invalid object type: 3. Expecting whitespace after the last token. due to M.
8383
;clojure.core-test.or ; unloadable, In clojure.core-test.or$fn_2-159, duplicate definition of symbol '_fn_2_0', unloadable
8484
;clojure.core-test.partial ;unloadable
8585
;clojure.core-test.plus ; error: Unable to resolve symbol 'Exception'
86-
;clojure.core-test.plus-squote ; error: Unable to resolve symbol 'clojure.lang.BigInt'.
86+
; clojure.core-test.plus-squote
8787
clojure.core-test.pos-int-qmark ; Expecting whitespace after the last token. due to M.
8888
;clojure.core-test.pos-qmark ; Uncaught exception: not a number: nil. Expecting whitespace after the last token. due to M.
8989
;clojure.core-test.pr-str ; Uncaught exception: invalid call to clojure.core/pr-str with 2 args provided, In clojure.core-test.pr-str$fn_2-49, duplicate definition of symbol '_fn_2_0', unloadable
@@ -96,7 +96,7 @@
9696
;clojure.core-test.quot ; Failed tests. Expecting whitespace after the last token. due to M.
9797
;clojure.core-test.rand ; unloadable, In clojure.core-test.rand$fn_2-71, duplicate definition of symbol '_jank_global_init_70', unloadable
9898
;clojure.core-test.rand-int ; unloadable, In clojure.core-test.rand-int$fn_2-56, duplicate definition of symbol '_fn_2_0', unloadable
99-
clojure.core-test.ratio-qmark ; Expecting whitespace after the last token. due to M.
99+
clojure.core-test.ratio-qmark
100100
;clojure.core-test.rational-qmark ; (= true (rational? 0N))
101101
;clojure.core-test.rationalize ; TODO: port rationalize, Expecting whitespace after the last token. due to M.
102102
;clojure.core-test.rem ; Fails the test cases.
@@ -112,7 +112,7 @@
112112
;clojure.core-test.slash ; Unable to resolve symbol 'Exception'., Expecting whitespace after the last token. due to M.
113113
;clojure.core-test.some-qmark ;Read error (437 - 437): unsupported reader macro
114114
;clojure.core-test.star ; error: Unable to resolve symbol 'Exception'.
115-
;clojure.core-test.star-squote ; error: Unable to resolve symbol 'clojure.lang.BigInt'.
115+
; clojure.core-test.star-squote
116116
;clojure.core-test.str ; TODO: port double, Expecting whitespace after the last token. due to M.
117117
clojure.core-test.string-qmark
118118
;clojure.core-test.subs ; Uncaught exception: end index 1 is less than start 2, In clojure.core-test.subs$fn_2-70, duplicate definition of symbol '_fn_2_0', https://github.com/jank-lang/jank/issues/244

0 commit comments

Comments
 (0)