{-# LANGUAGE ViewPatterns #-} ----------------------------------------------------------------------------- -- | -- Module : GHC.JS.Opt.Expr -- Copyright : (c) The University of Glasgow 2001 -- License : BSD-style (see the file LICENSE) -- -- Maintainer : Jeffrey Young -- Luite Stegeman -- Sylvain Henry -- Josh Meredith -- Stability : experimental -- -- -- This module contains a simple expression optimizer that performs constant -- folding and some boolean expression optimizations. ----------------------------------------------------------------------------- module GHC.JS.Opt.Expr (optExprs) where import GHC.Prelude hiding (shiftL, shiftR) import GHC.JS.Syntax import Data.Bifunctor (second) import Data.Bits (shiftL, shiftR, (.^.)) import Data.Int (Int32) {- Optimize expressions in a statement. This is best done after running the simple optimizer in GHC.JS.Opt.Simple, which eliminates redundant assignments and produces expressions that can be optimized more effectively. -} optExprs :: JStat -> JStat optExprs s = go s where go (DeclStat v mb_e) = DeclStat v (fmap opt mb_e) go (AssignStat lhs op rhs) = AssignStat (opt lhs) op (opt rhs) go (ReturnStat e) = ReturnStat (opt e) go (BlockStat ss) = BlockStat (map go ss) go (IfStat e s1 s2) = IfStat (optCond e) (go s1) (go s2) go (WhileStat b e s) = WhileStat b (optCond e) (go s) go (ForStat s1 e s2 s3) = ForStat (go s1) (optCond e) (go s2) (go s3) go (ForInStat b v e s) = ForInStat b v (opt e) (go s) go (SwitchStat e cases s) = SwitchStat (opt e) (map (second go) cases) (go s) go (TryStat s1 v s2 s3) = TryStat (go s1) v (go s2) (go s3) go (ApplStat e es) = ApplStat (opt e) (map opt es) go (UOpStat op e) = UOpStat op (opt e) go (LabelStat lbl s) = LabelStat lbl (go s) go s@(BreakStat{}) = s go s@(ContinueStat{}) = s go (FuncStat n vs s) = FuncStat n vs (go s) -- remove double negation if we're using the expression in a loop/if condition optCond :: JExpr -> JExpr optCond e = let f (UOpExpr NotOp (UOpExpr NotOp e')) = f e' f e' = e' in f (opt e) opt :: JExpr -> JExpr opt (ValExpr v) = ValExpr v opt (SelExpr e i) = SelExpr (opt e) i opt (IdxExpr e1 e2) = IdxExpr (opt e1) (opt e2) -- ((c_e ? 1 : 0) === 1) ==> !!c_e -- ((c_e ? 1 : 0) === 0) ==> !c_e opt(InfixExpr StrictEqOp (IfExpr c_e (opt -> t_e) (opt -> f_e)) (opt -> e)) | ValExpr t_v <- t_e , ValExpr v <- e , eqVal t_v v = UOpExpr NotOp (UOpExpr NotOp c_e) | ValExpr f_v <- f_e , ValExpr v <- e , eqVal f_v v = UOpExpr NotOp (opt c_e) | otherwise = InfixExpr StrictEqOp (IfExpr c_e t_e f_e) e -- (1 === (c_e ? 1 : 0)) ==> !!c_e -- (0 === (c_e ? 1 : 0)) ==> !c_e opt(InfixExpr StrictEqOp (opt -> e) (IfExpr (opt -> c_e) (opt -> t_e) (opt -> f_e))) | ValExpr t_v <- t_e , ValExpr v <- e , eqVal t_v v = UOpExpr NotOp (UOpExpr NotOp c_e) | ValExpr f_v <- f_e , ValExpr v <- e , eqVal f_v v = UOpExpr NotOp c_e | otherwise = InfixExpr StrictEqOp e (IfExpr c_e t_e f_e) opt (InfixExpr op (opt -> e1) (opt -> e2)) | (ValExpr (JInt n1)) <- e1 , (ValExpr (JInt n2)) <- e2 , Just v <- optInt op n1 n2 = ValExpr v | (ValExpr (JBool b1)) <- e1 , (ValExpr (JBool b2)) <- e2 , Just v <- optBool op b1 b2 = ValExpr v | otherwise = InfixExpr op e1 e2 opt (UOpExpr op e) = UOpExpr op (opt e) opt (IfExpr e1 e2 e3) = IfExpr (optCond e1) (opt e2) (opt e3) opt (ApplExpr e es) = ApplExpr (opt e) (map opt es) {- Optimizations for operations on two known boolean values -} optBool :: Op -> Bool -> Bool -> Maybe JVal optBool LAndOp x y = Just (JBool (x && y)) optBool LOrOp x y = Just (JBool (x || y)) optBool EqOp x y = Just (JBool (x == y)) optBool StrictEqOp x y = Just (JBool (x == y)) optBool NeqOp x y = Just (JBool (x /= y)) optBool StrictNeqOp x y = Just (JBool (x /= y)) optBool _ _ _ = Nothing {- Optimizations for operations on two known integer values -} optInt :: Op -> Integer -> Integer -> Maybe JVal optInt ZRightShiftOp n m = Just $ JInt (toInteger $ (n .&. 0xffffffff) `shiftR` fromInteger (m .&. 0x1f)) optInt BOrOp n m = Just (truncOp (.|.) n m) optInt BAndOp n m = Just (truncOp (.&.) n m) optInt BXorOp n m = Just (truncOp (.^.) n m) optInt RightShiftOp n m = Just (shiftOp shiftR n m) optInt LeftShiftOp n m = Just (shiftOp shiftL n m) optInt AddOp n m = smallIntOp (+) n m optInt SubOp n m = smallIntOp (-) n m optInt MulOp n m = smallIntOp (*) n m optInt op n m | Just cmp <- getCmpOp op, isSmall52 n && isSmall52 m = Just (JBool (cmp n m)) optInt _ _ _ = Nothing smallIntOp :: (Integer -> Integer -> Integer) -> Integer -> Integer -> Maybe JVal smallIntOp op n m | isSmall52 n && isSmall52 m && isSmall52 r = Just (JInt r) | otherwise = Nothing where r = op n m getCmpOp :: Op -> Maybe (Integer -> Integer -> Bool) getCmpOp EqOp = Just (==) getCmpOp StrictEqOp = Just (==) getCmpOp NeqOp = Just (/=) getCmpOp StrictNeqOp = Just (/=) getCmpOp GtOp = Just (>) getCmpOp GeOp = Just (>=) getCmpOp LtOp = Just (<) getCmpOp LeOp = Just (<=) getCmpOp _ = Nothing shiftOp :: (Int32 -> Int -> Int32) -> Integer -> Integer -> JVal shiftOp op n m = JInt $ toInteger (fromInteger n `op` (fromInteger m .&. 0x1f)) {- JavaScript bitwise operations truncate numbers to 32 bit signed integers. Here we do the same when constant folding with this kind of operators. -} truncOp :: (Int32 -> Int32 -> Int32) -> Integer -> Integer -> JVal truncOp op n m = JInt $ toInteger (fromInteger n `op` fromInteger m) {- JavaScript numbers are IEEE 754 double precision floats, which have a 52-bit mantissa. This returns True if the given integer can definitely be represented without loss of precision in a JavaScript number. -} isSmall52 :: Integer -> Bool isSmall52 n = n >= -0x10000000000000 && n <= 0xfffffffffffff {- In JavaScript, e1 === e2 is not always true even if expressions e1 and e2 are syntactically equal, examples: - NaN !== NaN (NaN is not equal to itself) - [1] !== [1] (different arrays allocated) - f() !== f() This returns True if the values are definitely equal in JavaScript -} eqVal :: JVal -> JVal -> Bool eqVal (JInt n1) (JInt n2) = n1 == n2 eqVal (JStr s1) (JStr s2) = s1 == s2 eqVal (JBool b1) (JBool b2) = b1 == b2 eqVal (JDouble (SaneDouble d1)) (JDouble (SaneDouble d2)) | not (isNaN d1) && not (isNaN d2) = d1 == d2 eqVal _ _ = False