@@ -0,0 +1,419 @@
1+-- | This module performs basic inlining of known functions
2+module CodeGen.IL.Optimizer.Inliner
4+ , inlineCommonOperators
5+ -- , inlineFnComposition
12+import Control.Monad.Supply.Class (MonadSupply)
14+import Data.Either (rights)
15+import Data.Maybe (fromMaybe)
16+import Data.String (IsString, fromString)
17+import Data.Text (Text)
18+import qualified Data.Text as T
20+import Language.PureScript.PSString (PSString, decodeString, mkString)
21+import Language.PureScript.CoreImp.AST
22+import Language.PureScript.CoreImp.Optimizer.Common hiding (isDict, isDict')
23+import Language.PureScript.AST (SourceSpan(..))
24+import qualified Language.PureScript.Constants as C
26+import CodeGen.IL.Common
27+import CodeGen.IL.Optimizer.Common
29+-- TODO: Potential bug:
30+-- Shouldn't just inline this case: { var x = 0; x.toFixed(10); }
31+-- Needs to be: { 0..toFixed(10); }
32+-- Probably needs to be fixed in pretty-printer instead.
33+shouldInline :: AST -> Bool
34+shouldInline (Var _ _) = True
35+shouldInline (NumericLiteral _ _) = True
36+shouldInline (StringLiteral _ _) = True
37+shouldInline (BooleanLiteral _ _) = True
38+shouldInline (Indexer _ index val) = shouldInline index && shouldInline val
39+shouldInline _ = False
41+inlineCommonValues :: AST -> AST
42+inlineCommonValues = everywhere convert
44+ convert :: AST -> AST
45+ convert (App ss fn [dict])
46+ | isDict' [semiringNumber, semiringInt] dict && isDict fnZero fn = NumericLiteral ss (Left 0)
47+ | isDict' [semiringNumber, semiringInt] dict && isDict fnOne fn = NumericLiteral ss (Left 1)
48+ | isDict boundedBoolean dict && isDict fnBottom fn = BooleanLiteral ss False
49+ | isDict boundedBoolean dict && isDict fnTop fn = BooleanLiteral ss True
50+ convert (App ss (App _ fn [dict]) [x])
51+ | isDict ringInt dict && isDict fnNegate fn = Unary ss Negate x -- (unbox x $ snd ringInt)
52+ convert (App ss (App _ (App _ fn [dict]) [x]) [y])
53+ | isDict semiringInt dict && isDict fnAdd fn = intOp ss Add semiringInt x y
54+ | isDict semiringInt dict && isDict fnMultiply fn = intOp ss Multiply semiringInt x y
55+ | isDict ringInt dict && isDict fnSubtract fn = intOp ss Subtract ringInt x y
56+ convert other = other
57+ fnZero = (C.dataSemiring, C.zero)
58+ fnOne = (C.dataSemiring, C.one)
59+ fnBottom = (C.dataBounded, C.bottom)
60+ fnTop = (C.dataBounded, C.top)
61+ fnAdd = (C.dataSemiring, C.add)
62+ fnMultiply = (C.dataSemiring, C.mul)
63+ fnSubtract = (C.dataRing, C.sub)
64+ fnNegate = (C.dataRing, C.negate)
65+ intOp ss op (_, d) x y = Binary ss op (unbox x d) (unbox y d)
67+inlineCommonOperators :: AST -> AST
68+inlineCommonOperators = everywhereTopDown $ applyAll $
69+ [ binary semiringNumber opAdd Add
70+ , binary semiringNumber opMul Multiply
72+ , binary ringNumber opSub Subtract
73+ , unary ringNumber opNegate Negate
75+ , binary euclideanRingNumber opDiv Divide
77+ -- , binary eqNumber opEq EqualTo
78+ -- , binary eqNumber opNotEq NotEqualTo
79+ , binary eqInt opEq EqualTo
80+ , binary eqInt opNotEq NotEqualTo
81+ , binary eqString opEq EqualTo
82+ , binary eqString opNotEq NotEqualTo
83+ , binary eqChar opEq EqualTo
84+ , binary eqChar opNotEq NotEqualTo
85+ , binary eqBoolean opEq EqualTo
86+ , binary eqBoolean opNotEq NotEqualTo
88+ , binary ordBoolean opLessThan LessThan
89+ , binary ordBoolean opLessThanOrEq LessThanOrEqualTo
90+ , binary ordBoolean opGreaterThan GreaterThan
91+ , binary ordBoolean opGreaterThanOrEq GreaterThanOrEqualTo
92+ , binary ordChar opLessThan LessThan
93+ , binary ordChar opLessThanOrEq LessThanOrEqualTo
94+ , binary ordChar opGreaterThan GreaterThan
95+ , binary ordChar opGreaterThanOrEq GreaterThanOrEqualTo
96+ , binary ordInt opLessThan LessThan
97+ , binary ordInt opLessThanOrEq LessThanOrEqualTo
98+ , binary ordInt opGreaterThan GreaterThan
99+ , binary ordInt opGreaterThanOrEq GreaterThanOrEqualTo
100+ , binary ordNumber opLessThan LessThan
101+ , binary ordNumber opLessThanOrEq LessThanOrEqualTo
102+ , binary ordNumber opGreaterThan GreaterThan
103+ , binary ordNumber opGreaterThanOrEq GreaterThanOrEqualTo
104+ , binary ordString opLessThan LessThan
105+ , binary ordString opLessThanOrEq LessThanOrEqualTo
106+ , binary ordString opGreaterThan GreaterThan
107+ , binary ordString opGreaterThanOrEq GreaterThanOrEqualTo
109+ , binary semigroupString opAppend Add
111+ , binary heytingAlgebraBoolean opConj And
112+ , binary heytingAlgebraBoolean opDisj Or
113+ , unary heytingAlgebraBoolean opNot Not
115+ , binary' C.dataIntBits C.or BitwiseOr
116+ , binary' C.dataIntBits C.and BitwiseAnd
117+ , binary' C.dataIntBits C.xor BitwiseXor
118+ , binary' C.dataIntBits C.shl ShiftLeft
119+ , binary' C.dataIntBits C.shr ShiftRight
120+ , binary' C.dataIntBits C.zshr ZeroFillShiftRight
121+ , unary' C.dataIntBits C.complement BitwiseNot
123+ , inlineNonClassFunction (isModFn (C.dataFunction, C.apply)) $ \f x -> App Nothing f [x]
124+ , inlineNonClassFunction (isModFn (C.dataFunction, C.applyFlipped)) $ \x f -> App Nothing f [x]
125+ , inlineNonClassFunction (isModFnWithDict (C.dataArray, C.unsafeIndex)) $ flip (Indexer Nothing)
127+ [ fn | i <- [0..10], fn <- [ mkFn i, runFn i ] ] ++
128+ [ fn | i <- [0..10], fn <- [ mkEffFn C.controlMonadEffUncurried C.mkEffFn i, runEffFn C.controlMonadEffUncurried C.runEffFn i ] ] ++
129+ [ fn | i <- [0..10], fn <- [ mkEffFn C.effectUncurried C.mkEffectFn i, runEffFn C.effectUncurried C.runEffectFn i ] ]
131+ binary :: (Text, PSString) -> (Text, PSString) -> BinaryOperator -> AST -> AST
132+ binary dict@(_, d) fns op = convert where
133+ convert :: AST -> AST
134+ convert (App ss (App _ (App _ fn [dict']) [x]) [y])
135+ | isDict dict dict' && isDict fns fn = Binary ss op (unbox x d) (unbox y d)
136+ convert other = other
137+ binary' :: Text -> PSString -> BinaryOperator -> AST -> AST
138+ binary' moduleName opString op = convert where
139+ convert :: AST -> AST
140+ convert (App ss (App _ fn [x]) [y]) | isDict (moduleName, opString) fn = Binary ss op (unbox x opString) (unbox y opString)
141+ convert other = other
142+ unary :: (Text, PSString) -> (Text, PSString) -> UnaryOperator -> AST -> AST
143+ unary dicts@(_, d) fns op = convert where
144+ convert :: AST -> AST
145+ convert (App ss (App _ fn [dict']) [x]) | isDict dicts dict' && isDict fns fn = Unary ss op (unbox x d)
146+ convert other = other
147+ unary' :: Text -> PSString -> UnaryOperator -> AST -> AST
148+ unary' moduleName fnName op = convert where
149+ convert :: AST -> AST
150+ convert (App ss fn [x]) | isDict (moduleName, fnName) fn = Unary ss op (unbox x fnName)
151+ convert other = other
153+ mkFn :: Int -> AST -> AST
154+ mkFn = mkFn' C.dataFunctionUncurried C.mkFn $ \ss1 ss2 ss3 args il ->
155+ Function ss1 Nothing args (Block ss2 [Return ss3 il])
157+ mkEffFn :: Text -> Text -> Int -> AST -> AST
158+ mkEffFn modName fnName = mkFn' modName fnName $ \ss1 ss2 ss3 args il ->
159+ Function ss1 Nothing args (Block ss2 [Return ss3 (App ss3 il [])])
161+ mkFn' :: Text -> Text -> (Maybe SourceSpan -> Maybe SourceSpan -> Maybe SourceSpan -> [Text] -> AST -> AST) -> Int -> AST -> AST
162+ mkFn' modName fnName res 0 = convert where
163+ convert :: AST -> AST
164+ convert (App _ mkFnN [Function s1 Nothing [_] (Block s2 [Return s3 il])]) | isNFn modName fnName 0 mkFnN =
166+ convert other = other
167+ mkFn' modName fnName res n = convert where
168+ convert :: AST -> AST
169+ convert orig@(App ss mkFnN [fn]) | isNFn modName fnName n mkFnN =
170+ case collectArgs n [] fn of
171+ Just (args, [Return ss' ret]) -> res ss ss ss' args ret
173+ convert other = other
174+ collectArgs :: Int -> [Text] -> AST -> Maybe ([Text], [AST])
175+ collectArgs 1 acc (Function _ Nothing [oneArg] (Block _ il)) | length acc == n - 1 = Just (reverse (oneArg : acc), il)
176+ collectArgs m acc (Function _ Nothing [oneArg] (Block _ [Return _ ret])) = collectArgs (m - 1) (oneArg : acc) ret
177+ collectArgs _ _ _ = Nothing
179+ isNFn :: Text -> Text -> Int -> AST -> Bool
180+ isNFn expectMod prefix n (Indexer _ (StringLiteral _ name) (Var _ modName)) | modName == expectMod =
181+ name == fromString (T.unpack prefix <> show n)
182+ isNFn _ _ _ _ = False
184+ runFn :: Int -> AST -> AST
185+ runFn = runFn' C.dataFunctionUncurried C.runFn App
187+ runEffFn :: Text -> Text -> Int -> AST -> AST
188+ runEffFn modName fnName = runFn' modName fnName $ \ss fn acc ->
189+ Function ss Nothing [] (Block ss [Return ss (App ss fn acc)])
191+ runFn' :: Text -> Text -> (Maybe SourceSpan -> AST -> [AST] -> AST) -> Int -> AST -> AST
192+ runFn' modName runFnName res n = convert where
193+ convert :: AST -> AST
194+ convert il = fromMaybe il $ go n [] il
196+ go :: Int -> [AST] -> AST -> Maybe AST
197+ go 0 acc (App ss runFnN [fn]) | isNFn modName runFnName n runFnN && length acc == n =
198+ Just $ res ss fn acc
199+ go m acc (App _ lhs [arg]) = go (m - 1) (arg : acc) lhs
202+ inlineNonClassFunction :: (AST -> Bool) -> (AST -> AST -> AST) -> AST -> AST
203+ inlineNonClassFunction p f = convert where
204+ convert :: AST -> AST
205+ convert (App _ (App _ op' [x]) [y]) | p op' = f x y
206+ convert other = other
208+ isModFn :: (Text, PSString) -> AST -> Bool
209+ isModFn (m, op) (Indexer _ (StringLiteral _ op') (Var _ m')) =
210+ m == m' && op == op'
213+ isModFnWithDict :: (Text, PSString) -> AST -> Bool
214+ isModFnWithDict (m, op) (App _ (Indexer _ (StringLiteral _ op') (Var _ m')) [Var _ _]) =
215+ m == m' && op == op'
216+ isModFnWithDict _ _ = False
218+-- (f <<< g $ x) = f (g x)
219+-- (f <<< g) = \x -> f (g x)
220+inlineFnComposition :: forall m. MonadSupply m => AST -> m AST
221+inlineFnComposition = everywhereTopDownM convert where
222+ convert :: AST -> m AST
223+ convert (App s1 (App s2 (App _ (App _ fn [dict']) [x]) [y]) [z])
224+ | isFnCompose dict' fn = return $ App s1 x [App s2 y [z]]
225+ | isFnComposeFlipped dict' fn = return $ App s2 y [App s1 x [z]]
226+ convert app@(App ss (App _ (App _ fn [dict']) _) _)
227+ | isFnCompose dict' fn || isFnComposeFlipped dict' fn = mkApps ss <$> goApps app <*> freshName'
228+ convert other = return other
230+ mkApps :: Maybe SourceSpan -> [Either AST (Text, AST)] -> Text -> AST
231+ mkApps ss fns a = App ss (Function ss Nothing [] (Block ss $ vars <> [Return Nothing comp])) []
233+ vars = uncurry (VariableIntroduction ss) . fmap Just <$> rights fns
234+ comp = Function ss Nothing [a] (Block ss [Return Nothing apps])
235+ apps = foldr (\fn acc -> App ss (mkApp fn) [acc]) (Var ss a) fns
237+ mkApp :: Either AST (Text, AST) -> AST
238+ mkApp = either id $ \(name, arg) -> Var (getSourceSpan arg) name
240+ goApps :: AST -> m [Either AST (Text, AST)]
241+ goApps (App _ (App _ (App _ fn [dict']) [x]) [y])
242+ | isFnCompose dict' fn = mappend <$> goApps x <*> goApps y
243+ | isFnComposeFlipped dict' fn = mappend <$> goApps y <*> goApps x
244+ goApps app@(App {}) = pure . Right . (,app) <$> freshName'
245+ goApps other = pure [Left other]
247+ isFnCompose :: AST -> AST -> Bool
248+ isFnCompose dict' fn = isDict semigroupoidFn dict' && isDict fnCompose fn
250+ isFnComposeFlipped :: AST -> AST -> Bool
251+ isFnComposeFlipped dict' fn = isDict semigroupoidFn dict' && isDict fnComposeFlipped fn
253+ fnCompose :: forall a b. (IsString a, IsString b) => (a, b)
254+ fnCompose = (C.controlSemigroupoid, C.compose)
256+ fnComposeFlipped :: forall a b. (IsString a, IsString b) => (a, b)
257+ fnComposeFlipped = (C.controlSemigroupoid, C.composeFlipped)
259+inlineUnsafeCoerce :: AST -> AST
260+inlineUnsafeCoerce = everywhereTopDown convert where
261+ convert (App _ (Indexer _ (Var _ unsafeCoerceFn) (Var _ unsafeCoerce)) [ comp ])
262+ | unsafeCoerceFn == C.unsafeCoerceFn && unsafeCoerce == C.unsafeCoerce
264+ convert other = other
266+inlineUnsafePartial :: AST -> AST
267+inlineUnsafePartial = everywhereTopDown convert where
268+ convert (App ss (Indexer _ (Var _ unsafePartial) (Var _ partialUnsafe)) [ comp ])
269+ | unsafePartial == C.unsafePartial && partialUnsafe == C.partialUnsafe
270+ -- Apply to undefined here, the application should be optimized away
271+ -- if it is safe to do so
272+ = App ss comp [ Var ss undefinedName ]
273+ convert other = other
275+semiringNumber :: forall a b. (IsString a, IsString b) => (a, b)
276+semiringNumber = (C.dataSemiring, C.semiringNumber)
278+semiringInt :: forall a b. (IsString a, IsString b) => (a, b)
279+semiringInt = (C.dataSemiring, C.semiringInt)
281+ringNumber :: forall a b. (IsString a, IsString b) => (a, b)
282+ringNumber = (C.dataRing, C.ringNumber)
284+ringInt :: forall a b. (IsString a, IsString b) => (a, b)
285+ringInt = (C.dataRing, C.ringInt)
287+euclideanRingNumber :: forall a b. (IsString a, IsString b) => (a, b)
288+euclideanRingNumber = (C.dataEuclideanRing, C.euclideanRingNumber)
290+-- eqNumber :: forall a b. (IsString a, IsString b) => (a, b)
291+-- eqNumber = (C.dataEq, C.eqNumber)
293+eqInt :: forall a b. (IsString a, IsString b) => (a, b)
294+eqInt = (C.dataEq, C.eqInt)
296+eqString :: forall a b. (IsString a, IsString b) => (a, b)
297+eqString = (C.dataEq, C.eqString)
299+eqChar :: forall a b. (IsString a, IsString b) => (a, b)
300+eqChar = (C.dataEq, C.eqChar)
302+eqBoolean :: forall a b. (IsString a, IsString b) => (a, b)
303+eqBoolean = (C.dataEq, C.eqBoolean)
305+ordBoolean :: forall a b. (IsString a, IsString b) => (a, b)
306+ordBoolean = (C.dataOrd, C.ordBoolean)
308+ordNumber :: forall a b. (IsString a, IsString b) => (a, b)
309+ordNumber = (C.dataOrd, C.ordNumber)
311+ordInt :: forall a b. (IsString a, IsString b) => (a, b)
312+ordInt = (C.dataOrd, C.ordInt)
314+ordString :: forall a b. (IsString a, IsString b) => (a, b)
315+ordString = (C.dataOrd, C.ordString)
317+ordChar :: forall a b. (IsString a, IsString b) => (a, b)
318+ordChar = (C.dataOrd, C.ordChar)
320+semigroupString :: forall a b. (IsString a, IsString b) => (a, b)
321+semigroupString = (C.dataSemigroup, C.semigroupString)
323+boundedBoolean :: forall a b. (IsString a, IsString b) => (a, b)
324+boundedBoolean = (C.dataBounded, C.boundedBoolean)
326+heytingAlgebraBoolean :: forall a b. (IsString a, IsString b) => (a, b)
327+heytingAlgebraBoolean = (C.dataHeytingAlgebra, C.heytingAlgebraBoolean)
329+semigroupoidFn :: forall a b. (IsString a, IsString b) => (a, b)
330+semigroupoidFn = (C.controlSemigroupoid, C.semigroupoidFn)
332+opAdd :: forall a b. (IsString a, IsString b) => (a, b)
333+opAdd = (C.dataSemiring, C.add)
335+opMul :: forall a b. (IsString a, IsString b) => (a, b)
336+opMul = (C.dataSemiring, C.mul)
338+opEq :: forall a b. (IsString a, IsString b) => (a, b)
339+opEq = (C.dataEq, C.eq)
341+opNotEq :: forall a b. (IsString a, IsString b) => (a, b)
342+opNotEq = (C.dataEq, C.notEq)
344+opLessThan :: forall a b. (IsString a, IsString b) => (a, b)
345+opLessThan = (C.dataOrd, C.lessThan)
347+opLessThanOrEq :: forall a b. (IsString a, IsString b) => (a, b)
348+opLessThanOrEq = (C.dataOrd, C.lessThanOrEq)
350+opGreaterThan :: forall a b. (IsString a, IsString b) => (a, b)
351+opGreaterThan = (C.dataOrd, C.greaterThan)
353+opGreaterThanOrEq :: forall a b. (IsString a, IsString b) => (a, b)
354+opGreaterThanOrEq = (C.dataOrd, C.greaterThanOrEq)
356+opAppend :: forall a b. (IsString a, IsString b) => (a, b)
357+opAppend = (C.dataSemigroup, C.append)
359+opSub :: forall a b. (IsString a, IsString b) => (a, b)
360+opSub = (C.dataRing, C.sub)
362+opNegate :: forall a b. (IsString a, IsString b) => (a, b)
363+opNegate = (C.dataRing, C.negate)
365+opDiv :: forall a b. (IsString a, IsString b) => (a, b)
366+opDiv = (C.dataEuclideanRing, C.div)
368+opConj :: forall a b. (IsString a, IsString b) => (a, b)
369+opConj = (C.dataHeytingAlgebra, C.conj)
371+opDisj :: forall a b. (IsString a, IsString b) => (a, b)
372+opDisj = (C.dataHeytingAlgebra, C.disj)
374+opNot :: forall a b. (IsString a, IsString b) => (a, b)
375+opNot = (C.dataHeytingAlgebra, C.not)
377+unbox :: AST -> PSString -> AST
378+unbox e d = select . inlineCommonValues $ inlineCommonOperators e
380+ select :: AST -> AST
385+ Indexer{} -> unbox' e'
387+ unbox' :: AST -> AST
388+ unbox' e' = maybe e' (\t -> App Nothing (StringLiteral Nothing t) [e']) $ unboxType d
390+unboxType :: PSString -> Maybe PSString
394+ return $ mkString t'
396+ unboxType' :: Text -> Maybe Text
399+ t == C.semiringInt ||
404+ | t == C.semiringNumber ||
405+ t == C.ringNumber ||
407+ t == C.euclideanRingNumber
409+ | t == C.eqBoolean ||
410+ t == C.ordBoolean ||
411+ t == C.heytingAlgebraBoolean
417+ t == C.semigroupString
419+ | otherwise = Nothing