git.iko.soy/rowtype-yoga/purescript-backend-optimizer
on main help sign in
Improvements to number and int const eval (#74)

* Improvements to number and int const eval Uses specialized operators for Number Ord ops rather than going the the Ord dictionary indirection. This means const eval for Number matches the runtime behavior in the presence of NaN, which is undefined for the purposes of Number Ord. Adds overflow checks to Int operators, and bails on const eval in the case that overflow is detected. * Add nan to snapshot

commit5ddaf523b1901d0af3e0d37c9a31784f07c230c7 authorNathan Faubion <natefaubion@users.noreply.github.com> dateSat 29 Jul 2023, 15:53 (3 years ago) parent7550209
5 files changed+103−41
backend-es/test/snapshots-out/Snapshot.PrimOpInt03.js+5 backend-es/test/snapshots-out/Snapshot.PrimOpNumber02.js+13−12 backend-es/test/snapshots/Snapshot.PrimOpInt03.purs+15 backend-es/test/snapshots/Snapshot.PrimOpNumber02.purs+4 src/PureScript/Backend/Optimizer/Semantics.purs+66−29
backend-es/test/snapshots-out/Snapshot.PrimOpInt03.jsnew file+5
@@ -0,0 +1,5 @@
1+const test4 = a => (2000000000 + a | 0) + 2000000000 | 0;
2+const test3 = /* #__PURE__ */ (() => 2000000000 * 2000000000 | 0)();
3+const test2 = /* #__PURE__ */ (() => -2000000000 - 2000000000 | 0)();
4+const test1 = /* #__PURE__ */ (() => 2000000000 + 2000000000 | 0)();
5+export {test1, test2, test3, test4};
backend-es/test/snapshots-out/Snapshot.PrimOpNumber02.js+13−12
@@ -1,13 +1,14 @@
11const test11 = [-1.5, 1.5];
2−const numValues = op => [op(1.5)(1.0), op(1.5)(2.0), op(2.5)(1.0), op(1.5)(-2.0), op(-1.5)(2.0), op(-1.5)(-1.0)];
3−const test1 = [2.5, 3.5, 3.5, -0.5, 0.5, -2.5];
4−const test10 = [1.5, 0.75, 2.5, -0.75, -0.75, 1.5];
5−const test2 = [0.5, -0.5, 1.5, 3.5, -3.5, -0.5];
6−const test3 = [false, false, false, false, false, false];
7−const test4 = [true, true, true, true, true, true];
8−const test5 = [false, true, false, false, true, true];
9−const test6 = [true, false, true, true, false, false];
10−const test7 = [false, true, false, false, true, true];
11−const test8 = [true, false, true, true, false, false];
12−const test9 = [1.5, 3.0, 2.5, -3.0, -3.0, 1.5];
13−export {numValues, test1, test10, test11, test2, test3, test4, test5, test6, test7, test8, test9};
2+const nan = NaN;
3+const numValues = op => [op(1.5)(1.0), op(1.5)(2.0), op(2.5)(1.0), op(1.5)(-2.0), op(-1.5)(2.0), op(-1.5)(-1.0), op(1.0)(NaN)];
4+const test1 = [2.5, 3.5, 3.5, -0.5, 0.5, -2.5, NaN];
5+const test10 = [1.5, 0.75, 2.5, -0.75, -0.75, 1.5, NaN];
6+const test2 = [0.5, -0.5, 1.5, 3.5, -3.5, -0.5, NaN];
7+const test3 = [false, false, false, false, false, false, false];
8+const test4 = [true, true, true, true, true, true, true];
9+const test5 = [false, true, false, false, true, true, false];
10+const test6 = [true, false, true, true, false, false, false];
11+const test7 = [false, true, false, false, true, true, false];
12+const test8 = [true, false, true, true, false, false, false];
13+const test9 = [1.5, 3.0, 2.5, -3.0, -3.0, 1.5, NaN];
14+export {nan, numValues, test1, test10, test11, test2, test3, test4, test5, test6, test7, test8, test9};
backend-es/test/snapshots/Snapshot.PrimOpInt03.pursnew file+15
@@ -0,0 +1,15 @@
1+module Snapshot.PrimOpInt03 where
2+
3+import Prelude
4+
5+test1 :: Int
6+test1 = 2000000000 + 2000000000
7+
8+test2 :: Int
9+test2 = -2000000000 - 2000000000
10+
11+test3 :: Int
12+test3 = 2000000000 * 2000000000
13+
14+test4 :: Int -> Int
15+test4 a = 2000000000 + a + 2000000000
backend-es/test/snapshots/Snapshot.PrimOpNumber02.purs+4
@@ -2,6 +2,9 @@ module Snapshot.PrimOpNumber02 where
22
33import Prelude
44
5+nan :: Number
6+nan = 0.0/0.0
7+
58numValues op =
69 [ 1.5 `op` 1.0
710 , 1.5 `op` 2.0
@@ -9,6 +12,7 @@ numValues op =
912 , 1.5 `op` -2.0
1013 , -1.5 `op` 2.0
1114 , -1.5 `op` -1.0
15+ , 1.0 `op` nan
1216 ]
1317
1418test1 = numValues (+)
src/PureScript/Backend/Optimizer/Semantics.purs+66−29
@@ -697,7 +697,7 @@ evalPrimOp env = case _ of
697697 | NeutLit (LitInt 0) <- x ->
698698 evalPrimOp env (Op1 OpIntNegate y)
699699 OpIntNum op
700− | Just result <- evalPrimOpNum OpIntNum liftInt caseInt op x y ->
700+ | Just result <- evalPrimOpNumInt op x y ->
701701 result
702702 OpIntOrd op
703703 | NeutLit (LitInt a) <- x
@@ -707,18 +707,18 @@ evalPrimOp env = case _ of
707707 | NeutLit (LitNumber 0.0) <- x ->
708708 evalPrimOp env (Op1 OpNumberNegate y)
709709 OpNumberNum op
710− | Just result <- evalPrimOpNum OpNumberNum liftNumber caseNumber op x y ->
710+ | Just result <- evalPrimOpNumNumber op x y ->
711711 result
712712 OpNumberOrd op
713713 | NeutLit (LitNumber a) <- x
714714 , NeutLit (LitNumber b) <- y ->
715− liftBoolean (evalPrimOpOrd op a b)
715+ liftBoolean (evalPrimOpOrdNumber op a b)
716716 OpStringOrd op
717717 | NeutLit (LitString a) <- x
718718 , NeutLit (LitString b) <- y ->
719719 liftBoolean (evalPrimOpOrd op a b)
720720 OpStringAppend
721− | Just result <- evalPrimOpAssocL OpStringAppend caseString (\a b -> liftString (a <> b)) x y ->
721+ | Just result <- evalPrimOpAssocL OpStringAppend caseString (\a b -> Just $ liftString (a <> b)) x y ->
722722 result
723723 OpArrayIndex
724724 | NeutLit (LitInt n) <- y ->
@@ -743,19 +743,19 @@ evalPrimOp env = case _ of
743743 evalAssocLet2 env x y \_ x' y' ->
744744 NeutPrimOp (Op2 op2 x' y')
745745
746−evalPrimOpAssocL :: forall a. BackendOperator2 -> (BackendSemantics -> Maybe a) -> (a -> a -> BackendSemantics) -> BackendSemantics -> BackendSemantics -> Maybe BackendSemantics
746+evalPrimOpAssocL :: forall a. BackendOperator2 -> (BackendSemantics -> Maybe a) -> (a -> a -> Maybe BackendSemantics) -> BackendSemantics -> BackendSemantics -> Maybe BackendSemantics
747747evalPrimOpAssocL op match combine a b = case match a of
748748 Just lhs
749749 | Just rhs <- match b ->
750− Just $ combine lhs rhs
750+ combine lhs rhs
751751 | Just (Tuple x y) <- decompose b ->
752752 case match x of
753753 Just rhs ->
754− Just $ liftOp2 op (combine lhs rhs) y
754+ (\x' -> liftOp2 op x' y) <$> combine lhs rhs
755755 Nothing
756756 | Just (Tuple v w) <- decompose x
757757 , Just rhs <- match v ->
758− Just $ liftOp2 op (liftOp2 op (combine lhs rhs) w) y
758+ (\v' -> liftOp2 op (liftOp2 op v' w) y) <$> combine lhs rhs
759759 _ ->
760760 Nothing
761761 Nothing
@@ -763,11 +763,11 @@ evalPrimOpAssocL op match combine a b = case match a of
763763 , Just (Tuple v w) <- decompose a ->
764764 case match w of
765765 Just lhs ->
766− Just $ liftOp2 op v (combine lhs rhs)
766+ liftOp2 op v <$> combine lhs rhs
767767 Nothing
768768 | Just (Tuple x y) <- decompose w
769769 , Just lhs <- match y ->
770− Just $ liftOp2 op (liftOp2 op v x) (combine lhs rhs)
770+ liftOp2 op (liftOp2 op v x) <$> combine lhs rhs
771771 _ ->
772772 Nothing
773773 _ ->
@@ -788,29 +788,66 @@ evalPrimOpOrd op x y = case op of
788788 OpLt -> x < y
789789 OpLte -> x <= y
790790
791−evalPrimOpNum
792− :: forall a
793− . EuclideanRing a
794− => (BackendOperatorNum -> BackendOperator2)
795− -> (a -> BackendSemantics)
796− -> (BackendSemantics -> Maybe a)
797− -> BackendOperatorNum
798− -> BackendSemantics
799− -> BackendSemantics
800− -> Maybe BackendSemantics
801−evalPrimOpNum injOp inj prj op x y = case op of
791+-- Duplicate because inlined operators behave differently with NaN.
792+-- This just ensures we get the same behavior for const eval.
793+evalPrimOpOrdNumber :: BackendOperatorOrd -> Number -> Number -> Boolean
794+evalPrimOpOrdNumber op x y = case op of
795+ OpEq -> x == y
796+ OpNotEq -> x /= y
797+ OpGt -> x > y
798+ OpGte -> x >= y
799+ OpLt -> x < y
800+ OpLte -> x <= y
801+
802+evalPrimOpNumNumber :: BackendOperatorNum -> BackendSemantics -> BackendSemantics -> Maybe BackendSemantics
803+evalPrimOpNumNumber op x y = case op of
804+ OpAdd ->
805+ evalPrimOpAssocL (OpNumberNum OpAdd) caseNumber (\a b -> Just $ liftNumber (a + b)) x y
806+ OpMultiply ->
807+ evalPrimOpAssocL (OpNumberNum OpMultiply) caseNumber (\a b -> Just $ liftNumber (a * b)) x y
808+ OpSubtract
809+ | NeutLit (LitNumber a) <- x
810+ , NeutLit (LitNumber b) <- y ->
811+ Just $ liftNumber (a - b)
812+ OpDivide
813+ | NeutLit (LitNumber a) <- x
814+ , NeutLit (LitNumber b) <- y ->
815+ Just $ liftNumber (a / b)
816+ _ ->
817+ Nothing
818+
819+evalPrimOpNumInt :: BackendOperatorNum -> BackendSemantics -> BackendSemantics -> Maybe BackendSemantics
820+evalPrimOpNumInt op x y = case op of
802821 OpAdd ->
803− evalPrimOpAssocL (injOp op) prj (\a b -> inj (a + b)) x y
822+ evalPrimOpAssocL (OpIntNum OpAdd) caseInt addOverflow x y
823+ where
824+ addOverflow a b = do
825+ let res = a + b
826+ if b > 0 && res < a || b < 0 && res > a then
827+ Nothing
828+ else
829+ Just $ liftInt res
804830 OpMultiply ->
805− evalPrimOpAssocL (injOp op) prj (\a b -> inj (a * b)) x y
831+ evalPrimOpAssocL (OpIntNum OpMultiply) caseInt mulOverflow x y
832+ where
833+ mulOverflow a b = do
834+ let res = a * b
835+ if a /= (res / b) then
836+ Nothing
837+ else
838+ Just $ liftInt res
806839 OpSubtract
807− | Just a <- prj x
808− , Just b <- prj y ->
809− Just $ inj (a - b)
840+ | NeutLit (LitInt a) <- x
841+ , NeutLit (LitInt b) <- y -> do
842+ let res = a - b
843+ if b > 0 && res > a || b < 0 && res < a then
844+ Nothing
845+ else
846+ Just $ liftInt res
810847 OpDivide
811− | Just a <- prj x
812− , Just b <- prj y ->
813− Just $ inj (a / b)
848+ | NeutLit (LitInt a) <- x
849+ , NeutLit (LitInt b) <- y ->
850+ Just $ liftInt (a / b)
814851 _ ->
815852 Nothing
816853