diff options
author | Sylvain Henry <sylvain@haskus.fr> | 2022-03-16 18:17:58 +0100 |
---|---|---|
committer | doyougnu <jeffrey.young@iohk.io> | 2022-06-13 13:42:36 -0400 |
commit | 59f1466fe0c814a03d151731bd2615d0eebb03aa (patch) | |
tree | f7bb8f3224c4fc483e2cad5bf3e3840649be61e0 /compiler/GHC/StgToJS | |
parent | a8ab253c39554e034ba2c5c8c02ad3c90e587fb4 (diff) | |
download | haskell-59f1466fe0c814a03d151731bd2615d0eebb03aa.tar.gz |
JS.Rts; refactoring and move to StgToJS
* add closure manipulation helpers and use them in Apply
* add cache (Array) for pre-generated PAP names
* reduce line length:
* use BlockArguments instead of parens
* remove implicit mconcat in jVar's body
Rts: more refactorings
Rts: move into StgToJS hierarchy
Diffstat (limited to 'compiler/GHC/StgToJS')
-rw-r--r-- | compiler/GHC/StgToJS/DataCon.hs | 14 | ||||
-rw-r--r-- | compiler/GHC/StgToJS/Heap.hs | 91 | ||||
-rw-r--r-- | compiler/GHC/StgToJS/Rts/Apply.hs | 746 | ||||
-rw-r--r-- | compiler/GHC/StgToJS/Rts/Rts.hs | 712 | ||||
-rw-r--r-- | compiler/GHC/StgToJS/Rts/Types.hs | 105 |
5 files changed, 1654 insertions, 14 deletions
diff --git a/compiler/GHC/StgToJS/DataCon.hs b/compiler/GHC/StgToJS/DataCon.hs index e68b22605b..662515456b 100644 --- a/compiler/GHC/StgToJS/DataCon.hs +++ b/compiler/GHC/StgToJS/DataCon.hs @@ -72,13 +72,13 @@ allocUnboxedCon con = \case allocDynamicE :: StgToJSConfig -> JExpr -> [JExpr] -> Maybe JExpr -> JExpr allocDynamicE s entry free cc - | csInlineAlloc s || length free > 24 = - ValExpr . jhFromList $ [ (closureEntry_ , entry) - , (closureExtra1_, fillObj1) - , (closureExtra2_, fillObj2) - , (closureMeta_ , ValExpr (JInt 0)) - ] ++ - maybe [] (\cid -> [("cc", cid)]) cc + | csInlineAlloc s || length free > 24 = newClosure $ Closure + { clEntry = entry + , clExtra1 = fillObj1 + , clExtra2 = fillObj2 + , clMeta = ValExpr (JInt 0) + , clCC = cc + } | otherwise = ApplExpr allocFun (toJExpr entry : free ++ maybeToList cc) where allocFun = allocClsA (length free) diff --git a/compiler/GHC/StgToJS/Heap.hs b/compiler/GHC/StgToJS/Heap.hs index 44ed2fdc46..2fdc135491 100644 --- a/compiler/GHC/StgToJS/Heap.hs +++ b/compiler/GHC/StgToJS/Heap.hs @@ -1,5 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE RecordWildCards #-} module GHC.StgToJS.Heap ( closureType @@ -16,14 +17,24 @@ module GHC.StgToJS.Heap , isCon' , conTag , conTag' - , entry + , closureEntry + , closureMeta + , closureExtra1 + , closureExtra2 + , closureCC , funArity , funArity' , papArity , funOrPapArity + , Closure (..) + , newClosure + , assignClosure + , CopyCC (..) + , copyClosure -- * Field names , closureEntry_ , closureMeta_ + , closureCC_ , closureExtra1_ , closureExtra2_ -- * Javascript Type literals @@ -38,6 +49,8 @@ import GHC.JS.Make import GHC.StgToJS.Types import GHC.Data.ShortText (ShortText) +import Data.Monoid + -- FIXME: Jeff (2022,03): These helpers are a classic case of using a newtype -- over a type synonym to leverage GHC's type checker. Basically we never want -- to mix these up, and so we should have: @@ -65,6 +78,9 @@ closureExtra2_ = "d2" closureMeta_ :: ShortText closureMeta_ = "m" +closureCC_ :: ShortText +closureCC_ = "cc" + entryClosureType_ :: ShortText entryClosureType_ = "t" @@ -78,7 +94,7 @@ jTyObject :: JExpr jTyObject = jString "object" closureType :: JExpr -> JExpr -closureType = entryClosureType . entry +closureType = entryClosureType . closureEntry entryClosureType :: JExpr -> JExpr entryClosureType f = f .^ entryClosureType_ @@ -114,17 +130,34 @@ isCon' :: JExpr -> JExpr isCon' f = entryClosureType f .===. toJExpr Con conTag :: JExpr -> JExpr -conTag = conTag' . entry +conTag = conTag' . closureEntry conTag' :: JExpr -> JExpr conTag' f = f .^ entryConTag_ -entry :: JExpr -> JExpr -entry p = p .^ closureEntry_ +-- | Get closure entry function +closureEntry :: JExpr -> JExpr +closureEntry p = p .^ closureEntry_ + +-- | Get closure metadata +closureMeta :: JExpr -> JExpr +closureMeta p = p .^ closureMeta_ + +-- | Get closure cost-center +closureCC :: JExpr -> JExpr +closureCC p = p .^ closureCC_ + +-- | Get closure extra field 1 +closureExtra1 :: JExpr -> JExpr +closureExtra1 p = p .^ closureExtra1_ + +-- | Get closure extra field 2 +closureExtra2 :: JExpr -> JExpr +closureExtra2 p = p .^ closureExtra2_ -- number of arguments (arity & 0xff = arguments, arity >> 8 = number of registers) funArity :: JExpr -> JExpr -funArity = funArity' . entry +funArity = funArity' . closureEntry -- function arity with raw reference to the entry funArity' :: JExpr -> JExpr @@ -132,7 +165,7 @@ funArity' f = f .^ entryFunArity_ -- arity of a partial application papArity :: JExpr -> JExpr -papArity cp = cp .^ closureExtra2_ .^ closureExtra1_ +papArity cp = closureExtra1 (closureExtra2 cp) funOrPapArity :: JExpr -- ^ heap object @@ -143,3 +176,47 @@ funOrPapArity c = \case (toJExpr (papArity c)) Just f -> ((IfExpr (toJExpr (isFun' f))) (toJExpr (funArity' f))) (toJExpr (papArity c)) + +-- | Used to pass arguments to newClosure with some safety +data Closure = Closure + { clEntry :: JExpr + , clExtra1 :: JExpr + , clExtra2 :: JExpr + , clMeta :: JExpr + , clCC :: Maybe JExpr + } + +newClosure :: Closure -> JExpr +newClosure Closure{..} = + let xs = [ (closureEntry_ , clEntry) + , (closureExtra1_, clExtra1) + , (closureExtra2_, clExtra2) + , (closureMeta_ , clMeta) + ] + in case clCC of + -- CC field is optional (probably to minimize code size as we could assign + -- null_, but we get the same effect implicitly) + Nothing -> ValExpr (jhFromList xs) + Just cc -> ValExpr (jhFromList $ (closureCC_,cc) : xs) + +assignClosure :: JExpr -> Closure -> JStat +assignClosure t Closure{..} = BlockStat + [ closureEntry t |= clEntry + , closureExtra1 t |= clExtra1 + , closureExtra2 t |= clExtra2 + , closureMeta t |= clMeta + ] <> case clCC of + Nothing -> mempty + Just cc -> closureCC t |= cc + +data CopyCC = CopyCC | DontCopyCC + +copyClosure :: CopyCC -> JExpr -> JExpr -> JStat +copyClosure copy_cc t s = BlockStat + [ closureEntry t |= closureEntry s + , closureExtra1 t |= closureExtra1 s + , closureExtra2 t |= closureExtra2 s + , closureMeta t |= closureMeta s + ] <> case copy_cc of + DontCopyCC -> mempty + CopyCC -> closureCC t |= closureCC s diff --git a/compiler/GHC/StgToJS/Rts/Apply.hs b/compiler/GHC/StgToJS/Rts/Apply.hs new file mode 100644 index 0000000000..6c5cb15d54 --- /dev/null +++ b/compiler/GHC/StgToJS/Rts/Apply.hs @@ -0,0 +1,746 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE BlockArguments #-} + +----------------------------------------------------------------------------- +-- | +-- Module : GHC.StgToJS.Rts.Apply +-- Copyright : (c) The University of Glasgow 2001 +-- License : BSD-style (see the file LICENSE) +-- +-- Maintainer : Jeffrey Young <jeffrey.young@iohk.io> +-- Luite Stegeman <luite.stegeman@iohk.io> +-- Sylvain Henry <sylvain.henry@iohk.io> +-- Stability : experimental +-- +-- Generate various apply functions for the RTS, for speeding up +-- function application in the most common cases. The code is generated +-- because it contains lots of repeating patterns, and to make it more +-- flexible when changing the RTS (for example how arguments are passed) +-- +-- The code in here can be a bit hard to read due to all the generated +-- low-level access things. Reading rts.js for a compiled program can be +-- easier (the file is always the same unless you change low-level RTS +-- options) +-- +-- FIXME: add selector thunks and let the gc follow them +----------------------------------------------------------------------------- + +module GHC.StgToJS.Rts.Apply where + +import GHC.Prelude hiding ((.|.)) + +import GHC.JS.Syntax +import GHC.JS.Make + +import GHC.StgToJS.Rts.Types +import GHC.StgToJS.DataCon +import GHC.StgToJS.Heap +import GHC.StgToJS.Monad +import GHC.StgToJS.Profiling +import GHC.StgToJS.Regs +import GHC.StgToJS.Types + +import GHC.Types.CostCentre +import GHC.Data.ShortText + +import qualified Data.Bits as Bits +import Data.Semigroup ((<>)) +import Data.Array + +rtsApply :: StgToJSConfig -> JStat +rtsApply cfg = BlockStat $ + map (uncurry (stackApply cfg)) applySpec + ++ map (uncurry (fastApply cfg)) applySpec + ++ map (pap cfg) specPap + ++ [ mkApplyArr + , genericStackApply cfg + , genericFastApply cfg + , zeroApply cfg + , updates cfg + , papGen cfg + , moveRegs2 + , selectors cfg + ] + +-- specialized apply for these +-- make sure that once you are in spec, you stay there +applySpec :: [(Int,Int)] -- regs,arity +applySpec = [ (regs,arity) | arity <- [1..4], regs <- [max 0 (arity-1)..(arity*2)]] + +specApply :: Bool -> Int -> Int -> Maybe JExpr +specApply fast n r + | (r,n) == (0,0) = Just (toJExpr . TxtI . pack $ "h$ap_0_0" ++ fastSuff) + | (r,n) == (0,1) = Just (toJExpr . TxtI . pack $ "h$ap_1_0" ++ fastSuff) + | (r,n) `elem` applySpec = + Just (toJExpr . TxtI . pack $ "h$ap_" ++ show n ++ "_" ++ show r ++ fastSuff) + | otherwise = Nothing + where + fastSuff | fast = "_fast" + | otherwise = "" +{- + Build arrays to quickly lookup apply functions, getting the fast variant when possible + - h$apply[r << 8 | n] = function application for r regs, n args + - h$paps[r] = partial application for r registers (number of args is in the object) + -} + -- FIXME (Jeff, 2022/03): Perf: This code would benefit a great deal by using + -- a datastucture that supports fast merging. +mkApplyArr :: JStat +mkApplyArr = BlockStat + [ TxtI "h$apply" ||= toJExpr (JList mempty) + , TxtI "h$paps" ||= toJExpr (JList mempty) + , (var "h$initStatic" .^ "push") `ApplStat` + [ValExpr $ JFunc [] $ jVar \i -> + [ i |= 0 + , WhileStat False (i .<. 65536) + ((var "h$apply" .! i |= var "h$ap_gen") + <> UOpStat PreIncOp i) + , i |= 0 + , WhileStat False (i .<. 128) + ((var "h$paps" .! i |= var "h$pap_gen") + <> UOpStat PreIncOp i) + , var "h$apply" .! 0 |= var "h$ap_0_0" + , mconcat (map assignSpec applySpec) + , mconcat (map assignPap specPap) + ] + ]] + where + assignSpec :: (Int, Int) -> JStat + assignSpec (r,n) = + var "h$apply" .! toJExpr (Bits.shiftL r 8 Bits..|. n) |= + toJExpr (TxtI . pack $ "h$ap_" ++ show n ++ "_" ++ show r) + + assignPap :: Int -> JStat + assignPap p = var "h$paps" .! toJExpr p |= + toJExpr (TxtI . pack $ "h$pap_" ++ show p) + +-- generic stack apply that can do everything, but less efficiently +-- on stack: tag: (regs << 8 | arity) +-- fixme: set closure info of stack frame +genericStackApply :: StgToJSConfig -> JStat +genericStackApply s = + closure (ClosureInfo "h$ap_gen" (CIRegs 0 [PtrV]) "h$ap_gen" CILayoutVariable CIStackFrame mempty) + (jVar \cf -> + [ traceRts s (jString "h$ap_gen") + , cf |= closureEntry r1 + , SwitchStat (entryClosureType cf) + [ (toJExpr Thunk, profStat s pushRestoreCCS <> returnS cf) + , (toJExpr Fun, funCase cf (funArity' cf)) + , (toJExpr Pap, funCase cf (papArity r1)) + , (toJExpr Blackhole, push' s [r1, var "h$return"] + <> returnS (app "h$blockOnBlackhole" [r1])) + ] (appS "throw" [jString "h$ap_gen: unexpected closure type " + (cf .^ "t")]) + ] + ) + where + funCase c arity = jVar \myArity ar myAr myRegs regs newTag newAp p dat -> + [ myArity |= stack .! (sp - 1) + , ar |= mask8 arity + , myAr |= mask8 myArity + , myRegs |= myArity .>>. 8 + , traceRts s (jString "h$ap_gen: args: " + myAr + + jString " regs: " + myRegs) + , ifBlockS (myAr .===. ar) + -- then + [ traceRts s (jString "h$ap_gen: exact") + , loop 0 (.<. myRegs) + (\i -> appS "h$setReg" [i+2, stack .! (sp-2-i)] + <> postIncrS i) + , (sp |= sp - myRegs - 2) + , returnS c + ] + -- else + [ ifBlockS (myAr .>. ar) + --then + [ regs |= arity .>>. 8 + , traceRts s (jString "h$ap_gen: oversat: arity: " + ar + + jString " regs: " + regs) + , loop 0 (.<. regs) + (\i -> traceRts s (jString "h$ap_gen: loading register: " + i) + <> appS "h$setReg" [i+2, stack .! (sp-2-i)] + <> postIncrS i) + , newTag |= ((myRegs-regs).<<.8).|.myAr - ar + , newAp |= var "h$apply" .! newTag + , traceRts s (jString "h$ap_gen: next: " + (newAp .^ "n")) + , ifS (newAp .===. var "h$ap_gen") + ((sp |= sp - regs) <> (stack .! (sp - 1) |= newTag)) + (sp |= sp - regs - 1) + , stack .! sp |= newAp + , profStat s pushRestoreCCS + , returnS c + ] + -- else + [ traceRts s (jString "h$ap_gen: undersat") + , p |= var "h$paps" .! myRegs + , dat |= toJExpr [r1, ((arity .>>. 8)-myRegs)*256+ar-myAr] + , loop 0 (.<. myRegs) + (\i -> (dat .^ "push") `ApplStat` [stack .! (sp - i - 2)] + <> postIncrS i) + , sp |= sp - myRegs - 2 + , r1 |= initClosure s p dat jCurrentCCS + , returnStack + ] + ] + ] + +{- + generic fast apply: can handle anything (slowly) + signature tag in argument +-} +genericFastApply :: StgToJSConfig -> JStat +genericFastApply s = + TxtI "h$ap_gen_fast" ||= jLam \tag -> jVar \c -> + [traceRts s (jString "h$ap_gen_fast: " + tag) + , c |= closureEntry r1 + , SwitchStat (entryClosureType c) + [ (toJExpr Thunk, traceRts s (jString "h$ap_gen_fast: thunk") + <> pushStackApply c tag + <> returnS c) + , (toJExpr Fun, jVar \farity -> + [ farity |= funArity' c + , traceRts s (jString "h$ap_gen_fast: fun " + farity) + , funCase c tag farity + ]) + , (toJExpr Pap, jVar \parity -> + [ parity |= papArity r1 + , traceRts s (jString "h$ap_gen_fast: pap " + parity) + , funCase c tag parity + ]) + , (toJExpr Con, traceRts s (jString "h$ap_gen_fast: con") + <> jwhenS (tag .!=. 0) + (appS "throw" [jString "h$ap_gen_fast: invalid apply"]) + <> returnS c) + , (toJExpr Blackhole, traceRts s (jString "h$ap_gen_fast: blackhole") + <> pushStackApply c tag + <> push' s [r1, var "h$return"] + <> returnS (app "h$blockOnBlackhole" [r1])) + ] $ appS "throw" [jString "h$ap_gen_fast: unexpected closure type: " + c .^ "t"] + ] + + where + -- thunk: push everything to stack frame, enter thunk first + pushStackApply :: JExpr -> JExpr -> JStat + pushStackApply _c tag = + jVar \ap -> + [ pushAllRegs tag + , ap |= var "h$apply" .! tag + , ifS (ap .===. var "h$ap_gen") + ((sp |= sp + 2) <> (stack .! (sp-1) |= tag)) + (sp |= sp + 1) + , stack .! sp |= ap + , profStat s pushRestoreCCS + ] + + funCase :: JExpr -> JExpr -> JExpr -> JStat + funCase c tag arity = + jVar \ar myAr myRegs regsStart newTag newAp dat p -> + [ ar |= mask8 arity + , myAr |= mask8 tag + , myRegs |= tag .>>. 8 + , traceRts s (jString "h$ap_gen_fast: args: " + myAr + + jString " regs: " + myRegs) + , ifS (myAr .===. ar) + -- call the function directly + (traceRts s (jString "h$ap_gen_fast: exact") <> returnS c) + (ifBlockS (myAr .>. ar) + -- push stack frame with remaining args, then call fun + [ traceRts s (jString "h$ap_gen_fast: oversat " + sp) + , regsStart |= (arity .>>. 8) + 1 + , sp |= sp + myRegs - regsStart + 1 + , traceRts s (jString "h$ap_gen_fast: oversat " + sp) + , pushArgs regsStart myRegs + , newTag |= ((myRegs-( arity.>>.8)).<<.8).|.myAr-ar + , newAp |= var "h$apply" .! newTag + , ifS (newAp .===. var "h$ap_gen") + ((sp |= sp + 2) <> (stack .! (sp - 1) |= newTag)) + (sp |= sp + 1) + , stack .! sp |= newAp + , profStat s pushRestoreCCS + , returnS c + ] + -- else + [traceRts s (jString "h$ap_gen_fast: undersat: " + myRegs + jString " " + tag) + , jwhenS (tag .!=. 0) $ mconcat + [ p |= var "h$paps" .! myRegs + , dat |= toJExpr [r1, ((arity .>>. 8)-myRegs)*256+ar-myAr] + , loop 0 (.<. myRegs) + (\i -> (dat .^ "push") + `ApplStat` [app "h$getReg" [i+2]] <> postIncrS i) + , r1 |= initClosure s p dat jCurrentCCS + ] + , returnStack + ]) + ] + + + pushAllRegs :: JExpr -> JStat + pushAllRegs tag = + jVar \regs -> + [ regs |= tag .>>. 8 + , sp |= sp + regs + , SwitchStat regs (map pushReg [65,64..2]) mempty + ] + where + pushReg :: Int -> (JExpr, JStat) + pushReg r = (toJExpr (r-1), stack .! (sp - toJExpr (r - 2)) |= toJExpr (intToJSReg r)) + + pushArgs :: JExpr -> JExpr -> JStat + pushArgs start end = + loop end (.>=.start) (\i -> traceRts s (jString "pushing register: " + i) + <> (stack .! (sp + start - i) |= app "h$getReg" [i+1]) + <> postDecrS i + ) + +stackApply :: StgToJSConfig + -> Int -- ^ number of registers in stack frame + -> Int -- ^ number of arguments + -> JStat +stackApply s r n = + closure (ClosureInfo funcName (CIRegs 0 [PtrV]) funcName layout CIStackFrame mempty) + body + where + layout = CILayoutUnknown r + + funcName = pack ("h$ap_" ++ show n ++ "_" ++ show r) + + body = jVar \c -> + [ c |= closureEntry r1 + , traceRts s (toJExpr funcName + + jString " " + + (c .^ "n") + + jString " sp: " + sp + + jString " a: " + (c .^ "a")) + , SwitchStat (entryClosureType c) + [ (toJExpr Thunk, traceRts s (toJExpr $ funcName <> ": thunk") <> profStat s pushRestoreCCS <> returnS c) + , (toJExpr Fun, traceRts s (toJExpr $ funcName <> ": fun") <> funCase c) + , (toJExpr Pap, traceRts s (toJExpr $ funcName <> ": pap") <> papCase c) + , (toJExpr Blackhole, push' s [r1, var "h$return"] <> returnS (app "h$blockOnBlackhole" [r1])) + ] (appS "throw" [toJExpr ("panic: " <> funcName <> ", unexpected closure type: ") + (c .^ "t")]) + ] + + funExact c = popSkip' 1 (reverse $ take r (map toJExpr $ enumFrom R2)) <> returnS c + stackArgs = map (\x -> stack .! (sp - toJExpr x)) [1..r] + + papCase :: JExpr -> JStat + papCase c = jVar \expr arity0 arity -> + case expr of + ValExpr (JVar pap) -> [ arity0 |= papArity r1 + , arity |= mask8 arity0 + , traceRts s (toJExpr (funcName <> ": found pap, arity: ") + arity) + , ifS (toJExpr n .===. arity) + --then + (traceRts s (toJExpr (funcName <> ": exact")) <> funExact c) + -- else + (ifS (toJExpr n .>. arity) + (traceRts s (toJExpr (funcName <> ": oversat")) <> oversatCase c arity0 arity) + (traceRts s (toJExpr (funcName <> ": undersat")) + <> mkPap s pap r1 (toJExpr n) stackArgs -- FIXME do we want double pap? + <> (sp |= sp - toJExpr (r + 1)) + <> (r1 |= toJExpr pap) + <> returnStack)) + ] + _ -> mempty -- FIXME: Jeff (2022,03), just quieting non-exhaustive + -- patterns. That the code wants to do this + -- means we should be encoding that funCase is + -- only callable on ValExpr (JVar pap)'s in + -- the type system, perhaps with a GADT or + -- phantom + + + funCase :: JExpr -> JStat + funCase c = jVar \expr ar0 ar -> + case expr of + ValExpr (JVar pap) -> [ ar0 |= funArity' c + , ar |= mask8 ar0 + , ifS (toJExpr n .===. ar) + (traceRts s (toJExpr (funcName <> ": exact")) <> funExact c) + (ifS (toJExpr n .>. ar) + (traceRts s (toJExpr (funcName <> ": oversat")) + <> oversatCase c ar0 ar) + (traceRts s (toJExpr (funcName <> ": undersat")) + <> mkPap s pap (toJExpr R1) (toJExpr n) stackArgs + <> (sp |= sp - toJExpr (r+1)) + <> (r1 |= toJExpr pap) + <> returnStack)) + ] + _ -> mempty -- FIXME: Jeff (2022,03), just quieting non-exhaustive + -- patterns. That the code wants to do this + -- means we should be encoding that funCase is + -- only callable on ValExpr (JVar pap)'s in + -- the type system, perhaps with a GADT or + -- phantom + + + -- oversat: call the function but keep enough on the stack for the next + oversatCase :: JExpr -- function + -> JExpr -- the arity tag + -> JExpr -- real arity (arity & 0xff) + -> JStat + oversatCase c arity arity0 = + jVar \rs newAp -> + [ rs |= (arity .>>. 8) + , loadRegs rs + , sp |= sp - rs + , newAp |= (var "h$apply" .! (toJExpr n-arity0.|.((toJExpr r-rs).<<.8))) + , stack .! sp |= newAp + , profStat s pushRestoreCCS + , traceRts s (toJExpr (funcName <> ": new stack frame: ") + (newAp .^ "n")) + , returnS c + ] + where + loadRegs rs = SwitchStat rs switchAlts mempty + where + switchAlts = map (\x -> (toJExpr x, toJExpr (intToJSReg (x+1)) |= stack .! (sp - toJExpr x))) [r,r-1..1] + +{- + stg_ap_r_n_fast is entered if a function of unknown arity + is called, n arguments are already in r registers +-} +fastApply :: StgToJSConfig -> Int -> Int -> JStat +fastApply s r n = func ||= toJExpr (JFunc myFunArgs body) + where + funName = pack ("h$ap_" ++ show n ++ "_" ++ show r ++ "_fast") + func = TxtI funName + + myFunArgs = [] + + regArgs = take r (enumFrom R2) + + mkAp :: Int -> Int -> [JExpr] + mkAp n' r' = [ var . pack $ "h$ap_" ++ show n' ++ "_" ++ show r' ] + + body = + jVar \c farity arity -> + [ c |= closureEntry r1 + , traceRts s (toJExpr (funName <> ": sp ") + sp) + -- TODO: Jeff (2022,03): factor our and dry out this code + , SwitchStat (entryClosureType c) + [(toJExpr Fun, traceRts s (toJExpr (funName <> ": ") + + clName c + + jString " (arity: " + (c .^ "a") + jString ")") + <> (farity |= funArity' c) + <> funCase c farity) + ,(toJExpr Pap, traceRts s (toJExpr (funName <> ": pap")) <> (arity |= papArity r1) <> funCase c arity) + ,(toJExpr Thunk, traceRts s (toJExpr (funName <> ": thunk")) <> push' s (reverse (map toJExpr $ take r (enumFrom R2)) ++ mkAp n r) <> profStat s pushRestoreCCS <> returnS c) + ,(toJExpr Blackhole, traceRts s (toJExpr (funName <> ": blackhole")) <> push' s (reverse (map toJExpr $ take r (enumFrom R2)) ++ mkAp n r) <> push' s [r1, var "h$return"] <> returnS (app "h$blockOnBlackhole" [r1]))] + (appS "throw" [toJExpr (funName <> ": unexpected closure type: ") + c .^ "t"]) + ] + + funCase :: JExpr -> JExpr -> JStat + funCase c arity = jVar \arg ar -> case arg of + ValExpr (JVar pap) -> [ ar |= mask8 arity + , ifS (toJExpr n .===. ar) + -- then + (traceRts s (toJExpr (funName <> ": exact")) <> returnS c) + -- else + (ifS (toJExpr n .>. ar) + --then + (traceRts s (toJExpr (funName <> ": oversat")) <> oversatCase c arity) + -- else + (traceRts s (toJExpr (funName <> ": undersat")) + <> mkPap s pap r1 (toJExpr n) (map toJExpr regArgs) + <> (r1 |= toJExpr pap) + <> returnStack)) + ] + _ -> mempty -- FIXME: Jeff (2022,03), just quieting non-exhaustive + -- patterns. That the code wants to do this + -- means we should be encoding that funCase is + -- only callable on ValExpr (JVar pap)'s in + -- the type system, perhaps with a GADT or + -- phantom + + oversatCase :: JExpr -> JExpr -> JStat + oversatCase c arity = + jVar \rs rsRemain -> + [ rs |= arity .>>. 8 + , rsRemain |= toJExpr r - rs + , traceRts s (toJExpr + (funName <> " regs oversat ") + + rs + + jString " remain: " + + rsRemain) + , saveRegs rs + , sp |= sp + rsRemain + 1 + , stack .! sp |= var "h$apply" .! ((rsRemain.<<.8).|. toJExpr n - mask8 arity) + , profStat s pushRestoreCCS + , returnS c + ] + where + saveRegs n = SwitchStat n switchAlts mempty + where + switchAlts = map (\x -> (toJExpr x, stack .! (sp + toJExpr (r-x)) |= toJExpr (intToJSReg (x+2)))) [0..r-1] + +zeroApply :: StgToJSConfig -> JStat +zeroApply s = mconcat + [ TxtI "h$ap_0_0_fast" ||= jLam (enter s r1) + , closure (ClosureInfo "h$ap_0_0" (CIRegs 0 [PtrV]) "h$ap_0_0" (CILayoutFixed 0 []) CIStackFrame mempty) + (adjSpN' 1 <> enter s r1) + , TxtI "h$e" ||= jLam (\c -> (r1 |= c) <> enter s c) + ] + +-- carefully enter a closure that might be a thunk or a function + +-- ex may be a local var, but must've been copied to R1 before calling this +enter :: StgToJSConfig -> JExpr -> JStat +enter s ex = jVar \c -> + [ jwhenS (app "typeof" [ex] .!==. jTyObject) returnStack + , c |= closureEntry ex + , jwhenS (c .===. var "h$unbox_e") ((r1 |= closureExtra1 ex) <> returnStack) + , SwitchStat (entryClosureType c) + [ (toJExpr Con, mempty) + , (toJExpr Fun, mempty) + , (toJExpr Pap, returnStack) + , (toJExpr Blackhole, push' s [var "h$ap_0_0", ex, var "h$return"] + <> returnS (app "h$blockOnBlackhole" [ex])) + ] (returnS c) + ] + +updates :: StgToJSConfig -> JStat +updates s = BlockStat + [ closure + (ClosureInfo "h$upd_frame" (CIRegs 0 [PtrV]) "h$upd_frame" (CILayoutFixed 1 [PtrV]) CIStackFrame mempty) + $ jVar \updatee waiters ss si sir -> + let unbox_closure = Closure + { clEntry = var "h$unbox_e" + , clExtra1 = sir + , clExtra2 = null_ + , clMeta = 0 + , clCC = Nothing + } + updateCC updatee = closureCC updatee |= jCurrentCCS + in [ updatee |= stack .! (sp - 1) + , traceRts s (jString "h$upd_frame updatee alloc: " + updatee .^ "alloc") + , -- wake up threads blocked on blackhole + waiters |= closureExtra2 updatee + , jwhenS (waiters .!==. null_) + (loop 0 (.<. waiters .^ "length") + (\i -> appS "h$wakeupThread" [waiters .! i] <> postIncrS i)) + , -- update selectors + jwhenS ((app "typeof" [closureMeta updatee] .===. jTyObject) .&&. (closureMeta updatee .^ "sel")) + ((ss |= closureMeta updatee .^ "sel") + <> loop 0 (.<. ss .^ "length") \i -> mconcat + [ si |= ss .! i + , sir |= (closureExtra2 si) `ApplExpr` [r1] + , ifS (app "typeof" [sir] .===. jTyObject) + (copyClosure DontCopyCC si sir) + (assignClosure si unbox_closure) + , postIncrS i + ]) + , -- overwrite the object + ifS (app "typeof" [r1] .===. jTyObject) + (mconcat [ traceRts s (jString "$upd_frame: boxed: " + ((closureEntry r1) .^ "n")) + , copyClosure DontCopyCC updatee r1 + ]) + (assignClosure updatee unbox_closure) + , profStat s (updateCC updatee) + , adjSpN' 2 + , traceRts s (jString "h$upd_frame: updating: " + + updatee + + jString " -> " + + r1) + , returnStack + ] + + , closure + (ClosureInfo "h$upd_frame_lne" (CIRegs 0 [PtrV]) "h$upd_frame_lne" (CILayoutFixed 1 [PtrV]) CIStackFrame mempty) + $ jVar \updateePos -> + [ updateePos |= stack .! (sp - 1) + , (stack .! updateePos |= r1) + , adjSpN' 2 + , traceRts s (jString "h$upd_frame_lne: updating: " + + updateePos + + jString " -> " + + r1) + , returnStack + ] + ] + +selectors :: StgToJSConfig -> JStat +selectors s = + mkSel "1" closureExtra1 + <> mkSel "2a" closureExtra2 + <> mkSel "2b" (closureExtra1 . closureExtra2) + <> mconcat (map mkSelN [3..16]) + where + mkSelN :: Int -> JStat + mkSelN x = mkSel (pack $ show x) + (\e -> SelExpr (closureExtra2 (toJExpr e)) + (TxtI $ pack ("d" ++ show (x-1)))) + + + mkSel :: ShortText -> (JExpr -> JExpr) -> JStat + mkSel name sel = mconcat + [TxtI createName ||= jLam \r -> mconcat + [ traceRts s (toJExpr ("selector create: " <> name <> " for ") + (r .^ "alloc")) + , ifS (isThunk r .||. isBlackhole r) + (returnS (app "h$mkSelThunk" [r, toJExpr (v entryName), toJExpr (v resName)])) + (returnS (sel r)) + ] + , TxtI resName ||= jLam \r -> mconcat + [ traceRts s (toJExpr ("selector result: " <> name <> " for ") + (r .^ "alloc")) + , returnS (sel r) + ] + , closure + (ClosureInfo entryName (CIRegs 0 [PtrV]) ("select " <> name) (CILayoutFixed 1 [PtrV]) CIThunk mempty) + (jVar \tgt -> + [ tgt |= closureExtra1 r1 + , traceRts s (toJExpr ("selector entry: " <> name <> " for ") + (tgt .^ "alloc")) + , ifS (isThunk tgt .||. isBlackhole tgt) + (preIncrS sp + <> (stack .! sp |= var frameName) + <> returnS (app "h$e" [tgt])) + (returnS (app "h$e" [sel tgt])) + ]) + , closure + (ClosureInfo frameName (CIRegs 0 [PtrV]) ("select " <> name <> " frame") (CILayoutFixed 0 []) CIStackFrame mempty) + $ mconcat [ traceRts s (toJExpr ("selector frame: " <> name)) + , postDecrS sp + , returnS (app "h$e" [sel r1]) + ] + ] + + where + v x = JVar (TxtI x) + n ext = "h$c_sel_" <> name <> ext + createName = n "" + resName = n "_res" + entryName = n "_e" + frameName = n "_frame_e" + + +-- arity is the remaining arity after our supplied arguments are applied +mkPap :: StgToJSConfig + -> Ident -- ^ id of the pap object + -> JExpr -- ^ the function that's called (can be a second pap) + -> JExpr -- ^ number of arguments in pap + -> [JExpr] -- ^ values for the supplied arguments + -> JStat +mkPap s tgt fun n values = + traceRts s (toJExpr $ "making pap with: " ++ show (length values) ++ " items") + `mappend` + allocDynamic s True tgt (toJExpr entry) (fun:papAr:map toJExpr values') + (if csProf s then Just jCurrentCCS else Nothing) + where + papAr = funOrPapArity fun Nothing - toJExpr (length values * 256) - n + + values' | GHC.Prelude.null values = [null_] + | otherwise = values + entry | length values > numSpecPap = TxtI "h$pap_gen" + | otherwise = specPapIdents ! length values + +-- | Number of specialized PAPs (pre-generated for a given number of args) +numSpecPap :: Int +numSpecPap = 6 + +-- specialized (faster) pap generated for [0..numSpecPap] +-- others use h$pap_gen +specPap :: [Int] +specPap = [0..numSpecPap] + +-- | Cache of specialized PAP idents +specPapIdents :: Array Int Ident +specPapIdents = listArray (0,numSpecPap) $ map (TxtI . pack . ("h$pap_"++) . show) specPap + +pap :: StgToJSConfig + -> Int + -> JStat +pap s r = closure (ClosureInfo funcName CIRegsUnknown funcName (CILayoutUnknown (r+2)) CIPap mempty) body + where + funcName = pack ("h$pap_" ++ show r) + + body = jVar \c d f extra -> + [ c |= closureExtra1 r1 + , d |= closureExtra2 r1 + , f |= closureEntry c + , assertRts s (isFun' f .||. isPap' f) (funcName <> ": expected function or pap") + , profStat s (enterCostCentreFun currentCCS) + , extra |= (funOrPapArity c (Just f) .>>. 8) - toJExpr r + , traceRts s (toJExpr (funcName <> ": pap extra args moving: ") + extra) + , moveBy extra + , loadOwnArgs d + , r1 |= c + , returnS f + ] + moveBy extra = SwitchStat extra + (reverse $ map moveCase [1..maxReg-r-1]) mempty + moveCase m = (toJExpr m, toJExpr (intToJSReg (m+r+1)) |= toJExpr (intToJSReg (m+1))) + loadOwnArgs d = mconcat $ map (\r -> + toJExpr (intToJSReg (r+1)) |= dField d (r+2)) [1..r] + dField d n = SelExpr d (TxtI . pack $ ('d':show (n-1))) + +-- Construct a generic PAP +papGen :: StgToJSConfig -> JStat +papGen cfg = + closure (ClosureInfo funcName CIRegsUnknown funcName CILayoutVariable CIPap mempty) + (jVar \c f d pr or r -> + [ c |= closureExtra1 r1 + , d |= closureExtra2 r1 + , f |= closureEntry c + , pr |= funOrPapArity c (Just f) .>>. 8 + , or |= papArity r1 .>>. 8 + , r |= pr - or + , assertRts cfg + (isFun' f .||. isPap' f) + (jString "h$pap_gen: expected function or pap") + , profStat cfg (enterCostCentreFun currentCCS) + , traceRts cfg (jString "h$pap_gen: generic pap extra args moving: " + or) + , appS "h$moveRegs2" [or, r] + , loadOwnArgs d r + , r1 |= c + , returnS f + ]) + + + where + funcName = "h$pap_gen" + loadOwnArgs d r = + let prop n = d .^ ("d" <> pack (show $ n+1)) + loadOwnArg n = (toJExpr n, toJExpr (intToJSReg (n+1)) |= prop n) + in SwitchStat r (map loadOwnArg [127,126..1]) mempty + +-- general utilities +-- move the first n registers, starting at R2, m places up (do not use with negative m) +-- FIXME (Jeff, 2022/03): pick a better name, e.g., `r2moveRegs` +moveRegs2 :: JStat +moveRegs2 = TxtI "h$moveRegs2" ||= jLam moveSwitch + where + moveSwitch n m = SwitchStat ((n .<<. 8) .|. m) switchCases (defaultCase n m) + -- fast cases + -- TODO: tune the parameteters for performance and size + switchCases = [switchCase n m | n <- [1..5], m <- [1..4]] + switchCase :: Int -> Int -> (JExpr, JStat) + switchCase n m = (toJExpr $ + (n `Bits.shiftL` 8) Bits..|. m + , mconcat (map (`moveRegFast` m) [n+1,n..2]) + <> BreakStat Nothing {-[j| break; |]-}) + moveRegFast n m = toJExpr (intToJSReg (n+m)) |= toJExpr (intToJSReg n) + -- fallback + defaultCase n m = + loop n (.>.0) (\i -> appS "h$setReg" [i+1+m, app "h$getReg" [i+1]] `mappend` postDecrS i) + + +-- Initalize a variable sized object from an array of values +initClosure :: StgToJSConfig -> JExpr -> JExpr -> JExpr -> JExpr +initClosure cfg entry values ccs = + let cc | csProf cfg = Just ccs + | otherwise = Nothing + in app "h$init_closure" [ newClosure $ Closure + { clEntry = entry + , clExtra1 = null_ + , clExtra2 = null_ + , clMeta = 0 + , clCC = cc + } + , values + ] + +-- FIXME: where to put this +closure :: ClosureInfo -- ^ object being info'd see @ciVar@ in @ClosureInfo@ + -> JStat -- ^ rhs + -> JStat +closure ci body = (TxtI (ciVar ci)||= jLam body) `mappend` toStat ci + +-- FIXME: where to put this +conClosure :: ShortText -> ShortText -> CILayout -> Int -> JStat +conClosure symbol name layout constr = + closure (ClosureInfo symbol (CIRegs 0 [PtrV]) name layout (CICon constr) mempty) + (returnS (stack .! sp)) diff --git a/compiler/GHC/StgToJS/Rts/Rts.hs b/compiler/GHC/StgToJS/Rts/Rts.hs new file mode 100644 index 0000000000..519518832c --- /dev/null +++ b/compiler/GHC/StgToJS/Rts/Rts.hs @@ -0,0 +1,712 @@ +{-# LANGUAGE OverloadedStrings #-} + +{-# OPTIONS_GHC -O0 #-} + +----------------------------------------------------------------------------- +-- | +-- Module : GHC.StgToJS.Rts.Rts +-- Copyright : (c) The University of Glasgow 2001 +-- License : BSD-style (see the file LICENSE) +-- +-- Maintainer : Jeffrey Young <jeffrey.young@iohk.io> +-- Luite Stegeman <luite.stegeman@iohk.io> +-- Sylvain Henry <sylvain.henry@iohk.io> +-- Stability : experimental +-- +-- Top level driver of the JavaScript Backend RTS. This file is an +-- implementation of the JS RTS for the JS backend written as an EDSL in +-- Haskell. It assumes the existence of pre-generated JS functions, included as +-- js-sources... +-- +-- FIXME: Jeff (2022,03): Finish module description. Specifically: +-- 1. Since this is the top level module for the RTS, what is the architecture +-- of the RTS? How does it all hold together? Describe the memory layout, any +-- other tricks the RTS plays, and relevant sibling modules +-- Sylvain (2022/03): memory layout is described in GHC.StgToJS (WIP) +-- +----------------------------------------------------------------------------- + +module GHC.StgToJS.Rts.Rts where + +import GHC.Prelude + +import GHC.JS.Syntax +import GHC.JS.Make +import GHC.JS.Transform + +import GHC.StgToJS.Heap +import GHC.StgToJS.Monad +import GHC.StgToJS.Profiling +import GHC.StgToJS.Regs +import GHC.StgToJS.Types +import GHC.StgToJS.Rts.Apply + +import qualified GHC.Data.ShortText as T + +import Data.Array +import Data.Monoid +import Data.Char (toLower, toUpper) +import qualified Data.Bits as Bits +import qualified Data.Map as M + + +----------------------------------------------------------------------------- +-- +-- Pre-generated RTS for the JS backend +-- +-- TODO: Jeff (2022,03): + +-- 1. There are numerous string literals sprinkled throughout the RTS, these +-- should be moved and isolated into a single module and then used throughout +-- the RTS and StgToJS Pipeline +-- +-- 2. The RTS makes a lot of use of the Monoid instances on lists since the +-- Haskell portion is essentially building a JavaScript AST for the JS Rts and +-- then pretty printing it so it can be used by the js backend. However, all +-- this merging on lists is going to be extremely inefficient. (++) is O(n^2) +-- and furthermore we have nested list structures. This implies a better data +-- structure with an emphasis on fast merging is likely to reduce compile times +-- for this RTS. +-- +-- 3. Similar to (2), most of the RTS is a function foo :: <something> with +-- definition foo [...<something>...] = mconcat ...<the body is JS land>... This +-- is fine, however it implies a monadic design for this EDSL might lead to more +-- readable code. Or in other words, `mconcat` and friends are just boiler +-- plate, and what we really have is a monadic EDSL where the monad is a kind of +-- Writer monad. Which we have essentially recreated here since bind in the +-- Writer monad is mapConcat and you'll notice that most of the functions in the +-- RTS do exactly that, i.e., apply a function which generates a list, then +-- concats. So we are dealing with a Writer monad but aren't using Haskell's +-- language facilities to be explicit about it. Hence all the boilerplate. Side +-- note, we might also consider two alternative approaches if we go with a +-- monadic design: +-- -- a. Continuation passing style so that intermediate lists fuse +-- -- b. A writer monad with a difference list, this would essentially be a +-- -- zipper but whether it is worth it or not depend on how often children need +-- -- to access their siblings, if they do that a lot then we'll have huge +-- -- speedups, if not then we likely won't gain anything + +----------------------------------------------------------------------------- + +garbageCollector :: JStat +garbageCollector = + mconcat [ TxtI "h$resetRegisters" ||= jLam (mconcat $ map resetRegister [minBound..maxBound]) + , TxtI "h$resetResultVars" ||= jLam (mconcat $ map resetResultVar [minBound..maxBound]) + ] + + +resetRegister :: StgReg -> JStat +resetRegister r = toJExpr r |= null_ + +resetResultVar :: StgRet -> JStat +resetResultVar r = toJExpr r |= null_ + +{- + use h$c1, h$c2, h$c3, ... h$c24 instead of making objects manually so layouts + and fields can be changed more easily + -} +closureConstructors :: StgToJSConfig -> JStat +closureConstructors s = BlockStat + [ declClsConstr "h$c" ["f"] $ Closure + { clEntry = var "f" + , clExtra1 = null_ + , clExtra2 = null_ + , clMeta = 0 + , clCC = ccVal + } + -- FIXME: same as h$c, maybe remove one of them? + , declClsConstr "h$c0" ["f"] $ Closure + { clEntry = var "f" + , clExtra1 = null_ + , clExtra2 = null_ + , clMeta = 0 + , clCC = ccVal + } + , declClsConstr "h$c1" ["f", "x1"] $ Closure + { clEntry = var "f" + , clExtra1 = var "x1" + , clExtra2 = null_ + , clMeta = 0 + , clCC = ccVal + } + , declClsConstr "h$c2" ["f", "x1", "x2"] $ Closure + { clEntry = var "f" + , clExtra1 = var "x1" + , clExtra2 = var "v2" + , clMeta = 0 + , clCC = ccVal + } + , mconcat (map mkClosureCon [3..24]) + , mconcat (map mkDataFill [1..24]) + ] + where + prof = csProf s + (ccArg,ccVal) + -- the cc argument happens to be named just like the cc field... + | prof = ([TxtI closureCC_], Just (var closureCC_)) + | otherwise = ([], Nothing) + addCCArg as = map TxtI as ++ ccArg + addCCArg' as = as ++ ccArg + + declClsConstr i as cl = TxtI i ||= ValExpr (JFunc (addCCArg as) + ( jVar $ \x -> + [ checkC + , x |= newClosure cl + , notifyAlloc x + , traceAlloc x + , returnS x + ] + )) + + traceAlloc x | csTraceRts s = appS "h$traceAlloc" [x] + | otherwise = mempty + + notifyAlloc x | csDebugAlloc s = appS "h$debugAlloc_notifyAlloc" [x] + | otherwise = mempty + + -- only JSVal can typically contain undefined or null + -- although it's possible (and legal) to make other Haskell types + -- to contain JS refs directly + -- this can cause false positives here + checkC :: JStat + checkC | csAssertRts s = + jVar $ \msg -> + jwhenS (var "arguments" .! 0 .!==. jString "h$ghcjszmprimZCGHCJSziPrimziJSVal_con_e") + (loop 1 (.<. var "arguments" .^ "length") + (\i -> + mconcat [msg |= jString "warning: undefined or null in argument: " + + i + + jString " allocating closure: " + (var "arguments" .! 0 .^ "n") + , appS "h$log" [msg] + , jwhenS (var "console" .&&. (var "console" .^ "trace")) ((var "console" .^ "trace") `ApplStat` [msg]) + , postIncrS i + ]) + + ) + | otherwise = mempty + + -- h$d is never used for JSVal (since it's only for constructors with + -- at least three fields, so we always warn here + checkD | csAssertRts s = + loop 0 (.<. var "arguments" .^ "length") + (\i -> jwhenS ((var "arguments" .! i .===. null_) + .||. (var "arguments" .! i .===. undefined_)) + (jVar $ \msg -> + mconcat [ msg |= jString "warning: undefined or null in argument: " + i + jString " allocating fields" + , jwhenS (var "console" .&&. (var "console" .^ "trace")) + ((var "console" .^ "trace") `ApplStat` [msg]) + ])) + + | otherwise = mempty + + mkClosureCon :: Int -> JStat + mkClosureCon n = funName ||= toJExpr fun + where + funName = TxtI $ T.pack ("h$c" ++ show n) -- FIXME (Sylvain 2022-03): cache this + -- args are: f x1 x2 .. xn [cc] + args = TxtI "f" : addCCArg' (map (TxtI . T.pack . ('x':) . show) [(1::Int)..n]) + fun = JFunc args funBod + -- x1 goes into closureExtra1. All the other args are bundled into an + -- object in closureExtra2: { d1 = x2, d2 = x3, ... } + -- + -- FIXME (Sylvain 2022-03): share code and comment with mkDataFill + extra_args = ValExpr . JHash . M.fromList $ zip + -- FIXME (Sylvain 2002-03): use dataFieldCache and another + -- cache for "xN" names + (map (T.pack . ('d':) . show) [(1::Int)..]) + (map (toJExpr . TxtI . T.pack . ('x':) . show) [2..n]) + + funBod = jVar $ \x -> + [ checkC + , x |= newClosure Closure + { clEntry = var "f" + , clExtra1 = var "x1" + , clExtra2 = extra_args + , clMeta = 0 + , clCC = ccVal + } + , notifyAlloc x + , traceAlloc x + , returnS x + ] + + mkDataFill :: Int -> JStat + mkDataFill n = funName ||= toJExpr fun + where + -- FIXME (Sylvain 2002-03): use dataFieldCache and dataCache + funName = TxtI $ T.pack ("h$d" ++ show n) + ds = map (T.pack . ('d':) . show) [(1::Int)..n] + extra_args = ValExpr . JHash . M.fromList . zip ds $ map (toJExpr . TxtI) ds + fun = JFunc (map TxtI ds) (checkD <> returnS extra_args) + +stackManip :: JStat +stackManip = mconcat (map mkPush [1..32]) <> + mconcat (map mkPpush [1..255]) + where + mkPush :: Int -> JStat + mkPush n = let funName = TxtI $ T.pack ("h$p" ++ show n) + as = map (TxtI . T.pack . ('x':) . show) [1..n] + fun = JFunc as ((sp |= sp + toJExpr n) + <> mconcat (zipWith (\i a -> stack .! (sp - toJExpr (n-i)) |= toJExpr a) + [1..] as)) + in funName ||= toJExpr fun + + -- | partial pushes, based on bitmap, increases Sp by highest bit + mkPpush :: Integer -> JStat + mkPpush sig | sig Bits..&. (sig+1) == 0 = mempty -- already handled by h$p + mkPpush sig = let funName = TxtI $ T.pack ("h$pp" ++ show sig) + bits = bitsIdx sig + n = length bits + h = last bits + args = map (TxtI . T.pack . ('x':) . show) [1..n] + fun = JFunc args $ + mconcat [ sp |= sp + toJExpr (h+1) + , mconcat (zipWith (\b a -> stack .! (sp - toJExpr (h-b)) |= toJExpr a) bits args) + ] + in funName ||= toJExpr fun + +bitsIdx :: Integer -> [Int] +bitsIdx n | n < 0 = error "bitsIdx: negative" + | otherwise = go n 0 + where + go 0 _ = [] + go m b | Bits.testBit m b = b : go (Bits.clearBit m b) (b+1) + | otherwise = go (Bits.clearBit m b) (b+1) + +bhLneStats :: StgToJSConfig -> JExpr -> JExpr -> JStat +bhLneStats _s p frameSize = + jVar $ \v -> + mconcat [ v |= stack .! p + , ifS v + ((sp |= sp - frameSize) + <> ifS (v .===. var "h$blackhole") + (returnS $ app "h$throw" [var "h$baseZCControlziExceptionziBasezinonTermination", false_]) + (mconcat [r1 |= v + , sp |= sp - frameSize + , returnStack + ])) + ((stack .! p |= var "h$blackhole") <> returnS null_) + ] + + +-- FIXME move somewhere else +declRegs :: JStat +-- FIXME prevent holes +declRegs = + mconcat [ TxtI "h$regs" ||= toJExpr (JList []) + , mconcat (map declReg (enumFromTo R1 R32)) + , regGettersSetters + , loadRegs + ] + where + declReg r = (decl . TxtI . T.pack . ("h$"++) . map toLower . show) r + <> BlockStat [AssignStat (toJExpr r) (ValExpr (JInt 0))] -- [j| `r` = 0; |] + +regGettersSetters :: JStat +regGettersSetters = + mconcat [ TxtI "h$getReg" ||= jLam (\n -> SwitchStat n getRegCases mempty) + , TxtI "h$setReg" ||= jLam (\n v -> SwitchStat n (setRegCases v) mempty) + ] + where + getRegCases = + map (\r -> (toJExpr (jsRegToInt r) , returnS (toJExpr r))) (enumFrom R1) + setRegCases v = + map (\r -> (toJExpr (jsRegToInt r), (toJExpr r |= toJExpr v) <> returnS undefined_)) (enumFrom R1) + +loadRegs :: JStat +loadRegs = mconcat $ map mkLoad [1..32] + where + mkLoad :: Int -> JStat + mkLoad n = let args = map (TxtI . T.pack . ("x"++) . show) [1..n] + assign = zipWith (\a r -> toJExpr r |= toJExpr a) + -- FIXME: Jeff (2022,03) the use of reverse, + -- take, and enumFrom here heavily implies + -- Data.Sequence would be a better data + -- structure to hold the regs. Or perhaps we + -- steal the indices from the registers array? + -- Either way we can avoid allocating this + -- intermediate `enumFrom R1` list + args (reverse $ take n (enumFrom R1)) + fname = TxtI $ T.pack ("h$l" ++ show n) + fun = JFunc args (mconcat assign) + in fname ||= toJExpr fun + +-- assign registers R1 ... Rn +-- assigns Rn first +assignRegs :: StgToJSConfig -> [JExpr] -> JStat +assignRegs _ [] = mempty +assignRegs s xs + | l <= 32 && not (csInlineLoadRegs s) + = ApplStat (ValExpr (JVar $ assignRegs'!l)) (reverse xs) + | otherwise = mconcat . reverse $ + zipWith (\r ex -> toJExpr r |= ex) (take l $ enumFrom R1) xs + where + l = length xs + +assignRegs' :: Array Int Ident +assignRegs' = listArray (1,32) (map (TxtI . T.pack . ("h$l"++) . show) [(1::Int)..32]) + +declRets :: JStat +declRets = mconcat $ map (decl . TxtI . T.pack . ("h$"++) . map toLower . show) (enumFrom Ret1) + +trace :: ToJExpr a => a -> JStat +trace ex = appS "h$log" [toJExpr ex] + +closureTypes :: JStat +closureTypes = mconcat (map mkClosureType (enumFromTo minBound maxBound)) <> closureTypeName + where + mkClosureType :: ClosureType -> JStat + mkClosureType c = let s = TxtI . T.pack $ "h$" ++ map toUpper (show c) ++ "_CLOSURE" + in s ||= toJExpr c + closureTypeName :: JStat + closureTypeName = + TxtI "h$closureTypeName" ||= jLam (\c -> + mconcat (map (ifCT c) [minBound..maxBound]) + <> returnS (jString "InvalidClosureType")) + + ifCT :: JExpr -> ClosureType -> JStat + ifCT arg ct = jwhenS (arg .===. toJExpr ct) (returnS (toJExpr (show ct))) + +rtsDecls :: JStat +rtsDecls = jsSaturate (Just "h$RTSD") $ + mconcat [ TxtI "h$currentThread" ||= null_ -- thread state object for current thread + , TxtI "h$stack" ||= null_ -- stack for the current thread + , TxtI "h$sp" ||= 0 -- stack pointer for the current thread + , TxtI "h$initStatic" ||= toJExpr (JList []) -- we need delayed initialization for static objects, push functions here to be initialized just before haskell runs + , TxtI "h$staticThunks" ||= toJExpr (jhFromList []) -- funcName -> heapidx map for srefs + , TxtI "h$staticThunksArr" ||= toJExpr (JList []) -- indices of updatable thunks in static heap + -- stg registers + , declRegs + , declRets] + +rts :: StgToJSConfig -> JStat +rts = jsSaturate (Just "h$RTS") . rts' + +rts' :: StgToJSConfig -> JStat +rts' s = + mconcat [ closureConstructors s + , garbageCollector + , stackManip + -- settings (FIXME should be const) + , TxtI "h$rts_traceForeign" ||= toJExpr (csTraceForeign s) + , TxtI "h$rts_profiling" ||= toJExpr (csProf s) + -- closure types (FIXME should be const) + , TxtI "h$ct_fun" ||= toJExpr Fun + , TxtI "h$ct_con" ||= toJExpr Con + , TxtI "h$ct_thunk" ||= toJExpr Thunk + , TxtI "h$ct_pap" ||= toJExpr Pap + , TxtI "h$ct_blackhole" ||= toJExpr Blackhole + , TxtI "h$ct_stackframe" ||= toJExpr StackFrame + -- var / closure field types (FIXME should be const) + , TxtI "h$vt_ptr" ||= toJExpr PtrV + , TxtI "h$vt_void" ||= toJExpr VoidV + , TxtI "h$vt_double" ||= toJExpr IntV + , TxtI "h$vt_long" ||= toJExpr LongV + , TxtI "h$vt_addr" ||= toJExpr AddrV + , TxtI "h$vt_rtsobj" ||= toJExpr RtsObjV + , TxtI "h$vt_obj" ||= toJExpr ObjV + , TxtI "h$vt_arr" ||= toJExpr ArrV + , TxtI "h$bh" ||= jLam (bhStats s True) + , TxtI "h$bh_lne" ||= jLam (\x frameSize -> bhLneStats s x frameSize) + , closure (ClosureInfo "h$blackhole" (CIRegs 0 []) "blackhole" (CILayoutUnknown 2) CIBlackhole mempty) + (appS "throw" [jString "oops: entered black hole"]) + , closure (ClosureInfo "h$blackholeTrap" (CIRegs 0 []) "blackhole" (CILayoutUnknown 2) CIThunk mempty) + (appS "throw" [jString "oops: entered multiple times"]) + , closure (ClosureInfo "h$done" (CIRegs 0 [PtrV]) "done" (CILayoutUnknown 0) CIStackFrame mempty) + (appS "h$finishThread" [var "h$currentThread"] <> returnS (var "h$reschedule")) + , closure (ClosureInfo "h$doneMain_e" (CIRegs 0 [PtrV]) "doneMain" (CILayoutUnknown 0) CIStackFrame mempty) + (returnS (var "h$doneMain")) + , conClosure "h$false_e" "GHC.Types.False" (CILayoutFixed 0 []) 1 + , conClosure "h$true_e" "GHC.Types.True" (CILayoutFixed 0 []) 2 + , conClosure "h$integerzmwiredzminZCGHCziIntegerziTypeziSzh_con_e" "GHC.Integer.Type.S#" (CILayoutFixed 1 [IntV]) 1 + , conClosure "h$integerzmwiredzminZCGHCziIntegerziTypeziJpzh_con_e" "GHC.Integer.Type.Jp#" (CILayoutFixed 1 [ObjV]) 2 + , conClosure "h$integerzmwiredzminZCGHCziIntegerziTypeziJnzh_con_e" "GHC.Integer.Type.Jn#" (CILayoutFixed 1 [ObjV]) 3 + -- generic data constructor with 1 non-heapobj field + , conClosure "h$data1_e" "data1" (CILayoutFixed 1 [ObjV]) 1 + -- generic data constructor with 2 non-heapobj fields + , conClosure "h$data2_e" "data2" (CILayoutFixed 2 [ObjV,ObjV]) 1 + , closure (ClosureInfo "h$noop_e" (CIRegs 1 [PtrV]) "no-op IO ()" (CILayoutFixed 0 []) (CIFun 1 0) mempty) + (returnS (stack .! sp)) + <> (TxtI "h$noop" ||= ApplExpr (var "h$c0") (var "h$noop_e" : [jSystemCCS | csProf s])) + , closure (ClosureInfo "h$catch_e" (CIRegs 0 [PtrV]) "exception handler" (CILayoutFixed 2 [PtrV,IntV]) CIStackFrame mempty) + (adjSpN' 3 <> returnS (stack .! sp)) + , closure (ClosureInfo "h$dataToTag_e" (CIRegs 0 [PtrV]) "data to tag" (CILayoutFixed 0 []) CIStackFrame mempty) + $ mconcat [ r1 |= if_ (r1 .===. true_) 1 (if_ (typeof r1 .===. jTyObject) (r1 .^ "f" .^ "a" - 1) 0) + , adjSpN' 1 + , returnS (stack .! sp) + ] + -- function application to one argument + , closure (ClosureInfo "h$ap1_e" (CIRegs 0 [PtrV]) "apply1" (CILayoutFixed 2 [PtrV, PtrV]) CIThunk mempty) + (jVar $ \d1 d2 -> + mconcat [ d1 |= closureExtra1 r1 + , d2 |= closureExtra2 r1 + , appS "h$bh" [] + , profStat s enterCostCentreThunk + , r1 |= d1 + , r2 |= d2 + , returnS (app "h$ap_1_1_fast" []) + ]) + -- function application to two arguments + , closure (ClosureInfo "h$ap2_e" (CIRegs 0 [PtrV]) "apply2" (CILayoutFixed 3 [PtrV, PtrV, PtrV]) CIThunk mempty) + (jVar $ \d1 d2 d3 -> + mconcat [ d1 |= closureExtra1 r1 + , d2 |= closureExtra2 r1 .^ "d1" -- FIXME (Sylvain 2022-03): extra args are named like closureExtraN... not so good! Find something else + , d3 |= closureExtra2 r1 .^ "d2" + , appS "h$bh" [] + , profStat s enterCostCentreThunk + , r1 |= d1 + , r2 |= d2 + , r3 |= d3 + , returnS (app "h$ap_2_2_fast" []) + ]) + -- function application to three arguments + , closure (ClosureInfo "h$ap3_e" (CIRegs 0 [PtrV]) "apply3" (CILayoutFixed 4 [PtrV, PtrV, PtrV, PtrV]) CIThunk mempty) + (jVar $ \d1 d2 d3 d4 -> + mconcat [ d1 |= closureExtra1 r1 + , d2 |= closureExtra2 r1 .^ "d1" + , d3 |= closureExtra2 r1 .^ "d2" + , d4 |= closureExtra2 r1 .^ "d3" + , appS "h$bh" [] + , r1 |= d1 + , r2 |= d2 + , r3 |= d3 + , r4 |= d4 + , returnS (app "h$ap_3_3_fast" []) + ]) + -- select first field + , closure (ClosureInfo "h$select1_e" (CIRegs 0 [PtrV]) "select1" (CILayoutFixed 1 [PtrV]) CIThunk mempty) + (jVar $ \t -> + mconcat [ t |= closureExtra1 r1 + , adjSp' 3 + , stack .! (sp - 2) |= r1 + , stack .! (sp - 1) |= var "h$upd_frame" + , stack .! sp |= var "h$select1_ret" + , closureEntry r1 |= var "h$blackhole" + , closureExtra1 r1 |= var "h$currentThread" + , closureExtra2 r1 |= null_ + , r1 |= t + , returnS (app "h$ap_0_0_fast" []) + ]) + , closure (ClosureInfo "h$select1_ret" (CIRegs 0 [PtrV]) "select1ret" (CILayoutFixed 0 []) CIStackFrame mempty) + ((r1 |= closureExtra1 r1) + <> adjSpN' 1 + <> returnS (app "h$ap_0_0_fast" []) + ) + -- select second field of a two-field constructor + , closure (ClosureInfo "h$select2_e" (CIRegs 0 [PtrV]) "select2" (CILayoutFixed 1 [PtrV]) CIThunk mempty) + (jVar $ \t -> + mconcat [t |= closureExtra1 r1 + , adjSp' 3 + , stack .! (sp - 2) |= r1 + , stack .! (sp - 1) |= var "h$upd_frame" + , stack .! sp |= var "h$select2_ret" + , closureEntry r1 |= var "h$blackhole" + , closureExtra1 r1 |= var "h$currentThread" + , closureExtra2 r1 |= null_ + , r1 |= t + , returnS (app "h$ap_0_0_fast" []) + ] + ) + , closure (ClosureInfo "h$select2_ret" (CIRegs 0 [PtrV]) "select2ret" (CILayoutFixed 0 []) CIStackFrame mempty) + $ mconcat [ r1 |= closureExtra2 r1 + , adjSpN' 1 + , returnS (app "h$ap_0_0_fast" []) + ] + -- a thunk that just raises a synchronous exception + , closure (ClosureInfo "h$raise_e" (CIRegs 0 [PtrV]) "h$raise_e" (CILayoutFixed 0 []) CIThunk mempty) + (returnS (app "h$throw" [closureExtra1 r1, false_])) + , closure (ClosureInfo "h$raiseAsync_e" (CIRegs 0 [PtrV]) "h$raiseAsync_e" (CILayoutFixed 0 []) CIThunk mempty) + (returnS (app "h$throw" [closureExtra1 r1, true_])) + , closure (ClosureInfo "h$raiseAsync_frame" (CIRegs 0 []) "h$raiseAsync_frame" (CILayoutFixed 1 []) CIStackFrame mempty) + (jVar $ \ex -> + mconcat [ ex |= stack .! (sp - 1) + , adjSpN' 2 + , returnS (app "h$throw" [ex, true_]) + ]) + {- reduce result if it's a thunk, follow if it's an ind + add this to the stack if you want the outermost result + to always be reduced to whnf, and not an ind + -} + , closure (ClosureInfo "h$reduce" (CIRegs 0 [PtrV]) "h$reduce" (CILayoutFixed 0 []) CIStackFrame mempty) + (ifS (isThunk r1) + (returnS (r1 .^ "f")) + (adjSpN' 1 <> returnS (stack .! sp)) + ) + , rtsApply s + , closureTypes + , closure (ClosureInfo "h$runio_e" (CIRegs 0 [PtrV]) "runio" (CILayoutFixed 1 [PtrV]) CIThunk mempty) + $ mconcat [ r1 |= closureExtra1 r1 + , stack .! PreInc sp |= var "h$ap_1_0" + , returnS (var "h$ap_1_0") + ] + , closure (ClosureInfo "h$flushStdout_e" (CIRegs 0 []) "flushStdout" (CILayoutFixed 0 []) CIThunk mempty) + $ mconcat [ r1 |= var "h$baseZCGHCziIOziHandlezihFlush" + , r2 |= var "h$baseZCGHCziIOziHandleziFDzistdout" + , returnS (app "h$ap_1_1_fast" []) + ] + , TxtI "h$flushStdout" ||= app "h$static_thunk" [var "h$flushStdout_e"] + -- the scheduler pushes this frame when suspending a thread that + -- has not called h$reschedule explicitly + , closure (ClosureInfo "h$restoreThread" (CIRegs 0 []) "restoreThread" CILayoutVariable CIStackFrame mempty) + (jVar $ \f frameSize nregs -> + mconcat [f |= stack .! (sp - 2) + , frameSize |= stack .! (sp - 1) + , nregs |= frameSize - 3 + , loop 1 (.<=. nregs) + (\i -> appS "h$setReg" [i, stack .! (sp - 2 - i)] <> postIncrS i) + , sp |= sp - frameSize + , returnS f + ]) + -- return a closure in the stack frame to the next thing on the stack + , closure (ClosureInfo "h$return" (CIRegs 0 []) "return" (CILayoutFixed 1 [PtrV]) CIStackFrame mempty) + ((r1 |= stack .! (sp - 1)) + <> adjSpN' 2 + <> returnS (stack .! sp)) + -- return a function in the stack frame for the next call + , closure (ClosureInfo "h$returnf" (CIRegs 0 [PtrV]) "returnf" (CILayoutFixed 1 [ObjV]) CIStackFrame mempty) + (jVar $ \r -> + mconcat [ r |= stack .! (sp - 1) + , adjSpN' 2 + , returnS r + ]) + -- return this function when the scheduler needs to come into action + -- (yield, delay etc), returning thread needs to push all relevant + -- registers to stack frame, thread will be resumed by calling the stack top + , closure (ClosureInfo "h$reschedule" (CIRegs 0 []) "reschedule" (CILayoutFixed 0 []) CIThunk mempty) + (returnS $ var "h$reschedule") + -- debug thing, insert on stack to dump current result, should be boxed + , closure (ClosureInfo "h$dumpRes" (CIRegs 0 [PtrV]) "dumpRes" (CILayoutFixed 1 [ObjV]) CIThunk mempty) + (jVar $ \re -> + mconcat [ appS "h$log" [jString "h$dumpRes result: " + stack .! (sp-1)] + , appS "h$log" [r1] + , appS "h$log" [app "h$collectProps" [r1]] + , jwhenS ((r1 .^ "f") .&&. (r1 .^ "f" .^ "n")) + (appS "h$log" [jString "name: " + r1 .^ "f" .^ "n"]) + , jwhenS (ApplExpr (r1 .^ "hasOwnProperty") [jString closureExtra1_]) + (appS "h$log" [jString "d1: " + closureExtra1 r1]) + , jwhenS (ApplExpr (r1 .^ "hasOwnProperty") [jString closureExtra2_]) + (appS "h$log" [jString "d2: " + closureExtra2 r1]) + , jwhenS (r1 .^ "f") $ mconcat + [ re |= New (app "RegExp" [jString "([^\\n]+)\\n(.|\\n)*"]) + , appS "h$log" [jString "function" + + ApplExpr (ApplExpr ((jString "" + r1 .^ "f") .^ "substring") [0, 50] .^ "replace") [r1, jString "$1"]] + ] + , adjSpN' 2 + , r1 |= null_ + , returnS (stack .! sp) + ]) + , closure (ClosureInfo "h$resume_e" (CIRegs 0 [PtrV]) "resume" (CILayoutFixed 0 []) CIThunk mempty) + (jVar $ \ss -> + mconcat [ss |= closureExtra1 r1 + , updateThunk' s + , loop 0 (.<. ss .^ "length") (\i -> (stack .! (sp+1+i) |= ss .! i) + <> postIncrS i) + , sp |= sp + ss .^ "length" + , r1 |= null_ + , returnS (stack .! sp) + ]) + , closure (ClosureInfo "h$unmaskFrame" (CIRegs 0 [PtrV]) "unmask" (CILayoutFixed 0 []) CIStackFrame mempty) + ((var "h$currentThread" .^ "mask" |= 0) + <> adjSpN' 1 + -- back to scheduler to give us async exception if pending + <> ifS (var "h$currentThread" .^ "excep" .^ "length" .>. 0) + (push' s [r1, var "h$return"] <> returnS (var "h$reschedule")) + (returnS (stack .! sp))) + , closure (ClosureInfo "h$maskFrame" (CIRegs 0 [PtrV]) "mask" (CILayoutFixed 0 []) CIStackFrame mempty) + ((var "h$currentThread" .^ "mask" |= 2) + <> adjSpN' 1 + <> returnS (stack .! sp)) + , closure (ClosureInfo "h$maskUnintFrame" (CIRegs 0 [PtrV]) "maskUnint" (CILayoutFixed 0 []) CIStackFrame mempty) + ((var "h$currentThread" .^ "mask" |= 1) + <> adjSpN' 1 + <> returnS (stack .! sp)) + , closure (ClosureInfo "h$unboxFFIResult" (CIRegs 0 [PtrV]) "unboxFFI" (CILayoutFixed 0 []) CIStackFrame mempty) + (jVar $ \d -> + mconcat [d |= closureExtra1 r1 + , loop 0 (.<. d .^ "length") (\i -> appS "h$setReg" [i + 1, d .! i] <> postIncrS i) + , adjSpN' 1 + , returnS (stack .! sp) + ]) + , closure (ClosureInfo "h$unbox_e" (CIRegs 0 [PtrV]) "unboxed value" (CILayoutFixed 1 [DoubleV]) CIThunk mempty) + ((r1 |= closureExtra1 r1) <> returnS (stack .! sp)) + , closure (ClosureInfo "h$retryInterrupted" (CIRegs 0 [ObjV]) "retry interrupted operation" (CILayoutFixed 1 [ObjV]) CIStackFrame mempty) + (jVar $ \a -> + mconcat [ a |= stack .! (sp - 1) + , adjSpN' 2 + , returnS (ApplExpr (a .! 0 .^ "apply") [var "this", ApplExpr (a .^ "slice") [1]]) + ]) + , closure (ClosureInfo "h$atomically_e" (CIRegs 0 [PtrV]) "atomic operation" (CILayoutFixed 1 [PtrV]) CIStackFrame mempty) + (ifS (app "h$stmValidateTransaction" []) + (appS "h$stmCommitTransaction" [] + <> adjSpN' 2 + <> returnS (stack .! sp)) + (push' s [var "h$checkInvariants_e"] + <> returnS (app "h$stmStartTransaction" [stack .! (sp - 2)]))) + , closure (ClosureInfo "h$checkInvariants_e" (CIRegs 0 [PtrV]) "check transaction invariants" (CILayoutFixed 0 []) CIStackFrame mempty) + (adjSpN' 1 + <> returnS (app "h$stmCheckInvariants" [])) + , closure (ClosureInfo "h$stmCheckInvariantStart_e" (CIRegs 0 []) "start checking invariant" (CILayoutFixed 2 [ObjV, RtsObjV]) CIStackFrame mempty) + (jVar $ \t inv m t1 -> + mconcat [ t |= stack .! (sp - 2) + , inv |= stack .! (sp - 1) + , m |= var "h$currentThread" .^ "mask" + , adjSpN' 3 + , t1 |= UOpExpr NewOp (app "h$Transaction" [inv .^ "action", t]) + , t1 .^ "checkRead" |= UOpExpr NewOp (app "h$Set" []) + , var "h$currentTread" .^ "transaction" |= t1 + , push' s [t1, m, var "h$stmInvariantViolatedHandler", var "h$catchStm_e"] + , r1 |= inv .^ "action" + , returnS (app "h$ap_1_0_fast" []) + ]) + , closure (ClosureInfo "h$stmCheckInvariantResult_e" (CIRegs 0 [PtrV]) "finish checking invariant" (CILayoutFixed 1 [ObjV]) CIStackFrame mempty) + (jVar $ \inv -> + mconcat [ inv |= stack .! (sp -1) + , adjSpN' 2 + , appS "h$stmUpdateInvariantDependencies" [inv] + , appS "h$stmAbortTransaction" [] + , returnS (stack .! sp) + ]) + + -- update invariant TVar dependencies and rethrow exception + -- handler must be pushed above h$stmCheckInvariantResult_e frame + , closure (ClosureInfo "h$stmInvariantViolatedHandler_e" (CIRegs 0 [PtrV]) "finish checking invariant" (CILayoutFixed 0 []) (CIFun 2 1) mempty) + (jVar $ \inv -> + mconcat [ jwhenS (stack .! sp .===. var "h$stmCheckInvariantResult_e") + (appS "throw" [jString "h$stmInvariantViolatedHandler_e: unexpected value on stack"]) + , inv |= stack .! (sp - 2) + , adjSpN' 2 + , appS "h$stmUpdateInvariantDependencies" [] + , appS "h$stmAbortTransaction" [] + , returnS (app "h$throw" [r2, false_]) + ]) + , TxtI "h$stmInvariantViolatedHandler" ||= app "h$c" (var "h$stmInvariantViolatedHandler_e" : [jSystemCCS | csProf s]) + , closure (ClosureInfo "h$stmCatchRetry_e" (CIRegs 0 [PtrV]) "catch retry" (CILayoutFixed 1 [PtrV]) CIStackFrame mempty) + (adjSpN' 2 + <> appS "h$stmCommitTransaction" [] + <> returnS (stack .! sp)) + , closure (ClosureInfo "h$catchStm_e" (CIRegs 0 [PtrV]) "STM catch" (CILayoutFixed 3 [ObjV,PtrV,ObjV]) CIStackFrame mempty) + (adjSpN' 4 <> returnS (stack .! sp)) + , closure (ClosureInfo "h$stmResumeRetry_e" (CIRegs 0 [PtrV]) "resume retry" (CILayoutFixed 0 []) CIStackFrame mempty) + (jVar $ \blocked -> + mconcat [ jwhenS (stack .! (sp - 2) .!==. var "h$atomically_e") + (appS "throw" [jString "h$stmResumeRetry_e: unexpected value on stack"]) + , blocked |= stack .! (sp - 1) + , adjSpN' 2 + , push' s [var "h$checkInvariants_e"] + , appS "h$stmRemoveBlockedThread" [blocked, var "h$currentThread"] + , returnS (app "h$stmStartTransaction" [stack .! (sp - 2)]) + ]) + , closure (ClosureInfo "h$lazy_e" (CIRegs 0 [PtrV]) "generic lazy value" (CILayoutFixed 0 []) CIThunk mempty) + (jVar $ \x -> + mconcat [x |= ApplExpr (closureExtra1 r1) [] + , appS "h$bh" [] + , profStat s enterCostCentreThunk + , r1 |= x + , returnS (stack .! sp) + ]) + -- Top-level statements to generate only in profiling mode + , profStat s (closure (ClosureInfo "h$setCcs_e" (CIRegs 0 [PtrV]) "set cost centre stack" (CILayoutFixed 1 [ObjV]) CIStackFrame mempty) + (appS "h$restoreCCS" [ stack .! (sp - 1)] + <> adjSpN' 2 + <> returnS (stack .! sp))) + ] diff --git a/compiler/GHC/StgToJS/Rts/Types.hs b/compiler/GHC/StgToJS/Rts/Types.hs new file mode 100644 index 0000000000..db6868a5ea --- /dev/null +++ b/compiler/GHC/StgToJS/Rts/Types.hs @@ -0,0 +1,105 @@ +{-# LANGUAGE CPP, + FlexibleInstances, + OverloadedStrings #-} + +----------------------------------------------------------------------------- +-- | +-- Module : GHC.StgToJS.Rts.Apply +-- Copyright : (c) The University of Glasgow 2001 +-- License : BSD-style (see the file LICENSE) +-- +-- Maintainer : Jeffrey Young <jeffrey.young@iohk.io> +-- Luite Stegeman <luite.stegeman@iohk.io> +-- Sylvain Henry <sylvain.henry@iohk.io> +-- Stability : experimental +-- +-- Types and utility functions used in the JS RTS. +-- FIXME: Jeff (2022,03): Add more details +----------------------------------------------------------------------------- + +module GHC.StgToJS.Rts.Types where + +import GHC.Prelude + +import GHC.JS.Make +import GHC.JS.Syntax +import GHC.StgToJS.Regs +import GHC.StgToJS.Types + +import GHC.Utils.Monad.State.Strict +import qualified GHC.Data.ShortText as T + + + +traceRts :: StgToJSConfig -> JExpr -> JStat +traceRts s ex = jStatIf (csTraceRts s) (appS "h$log" [ex]) + +assertRts :: ToJExpr a => StgToJSConfig -> JExpr -> a -> JStat +assertRts s ex m = jStatIf (csAssertRts s) + (jwhenS (UOpExpr NotOp ex) (appS "throw" [toJExpr m])) + +jStatIf :: Bool -> JStat -> JStat +jStatIf True s = s +jStatIf _ _ = mempty + +clName :: JExpr -> JExpr +clName c = c .^ "n" + +clTypeName :: JExpr -> JExpr +clTypeName c = app "h$closureTypeName" [c .^ "t"] + +type C = State GenState JStat + +assertRtsStat :: C -> C +assertRtsStat stat = do + s <- gets gsSettings + if csAssertRts s then stat else return mempty + +-- number of arguments (arity & 0xff = arguments, arity >> 8 = number of registers) +stackFrameSize :: JExpr -- ^ assign frame size to this + -> JExpr -- ^ stack frame header function + -> JStat -- ^ size of the frame, including header +stackFrameSize tgt f = + ifS (f .===. var "h$ap_gen") -- h$ap_gen is special + (tgt |= (stack .! (sp - 1) .>>. 8) + 2) -- special case, FIXME (Jeff, 2022/03): what and why is + -- it special and how does its + -- special-ness change this code + (jVar (\tag -> + mconcat + [tag |= f .^ "size" + , ifS (tag .<. 0) -- if tag is less than 0 + (tgt |= stack .! (sp - 1)) -- set target to stack pointer - 1 + (tgt |= mask8 tag + 1) -- else set to mask'd tag + 1 + ] + )) + +-- some utilities do do something with a range of regs +-- start or end possibly supplied as javascript expr +withRegs :: StgReg -> StgReg -> (StgReg -> JStat) -> JStat +withRegs start end f = mconcat $ map f [start..end] + +withRegs' :: StgReg -> StgReg -> (StgReg -> JStat) -> JStat +withRegs' start end = withRegs start end + +-- start from js expr, start is guaranteed to be at least min +-- from low to high (fallthrough!) +withRegsS :: JExpr -> StgReg -> StgReg -> Bool -> (StgReg -> JStat) -> JStat +withRegsS start min end fallthrough f = + SwitchStat start (map mkCase [min..end]) mempty + where + brk | fallthrough = mempty + | otherwise = BreakStat Nothing + mkCase r = let stat = f r + in (toJExpr r, mconcat [stat , stat , brk]) + +-- end from js expr, from high to low +withRegsRE :: StgReg -> JExpr -> StgReg -> Bool -> (StgReg -> JStat) -> JStat +withRegsRE start end max fallthrough f = + SwitchStat end (reverse $ map mkCase [start..max]) mempty + where + brk | fallthrough = mempty + | otherwise = BreakStat Nothing + mkCase r = (toJExpr (fromEnum r), mconcat [f r , brk]) + +jsVar :: String -> JExpr +jsVar = ValExpr . JVar . TxtI . T.pack |