diff options
author | Cheng Shao <astrohavoc@gmail.com> | 2022-10-24 14:20:31 +0000 |
---|---|---|
committer | Marge Bot <ben+marge-bot@smart-cactus.org> | 2022-11-11 00:26:55 -0500 |
commit | 36340328a6a26529b1eb4ca0413dc87eb91fe700 (patch) | |
tree | 7cdb9320d2720ed67a84b80908a55e76939027ce | |
parent | a8adc71e80734c6dc2e119596368f84e39fd1172 (diff) | |
download | haskell-36340328a6a26529b1eb4ca0413dc87eb91fe700.tar.gz |
compiler: wasm32 NCG
This patch adds the wasm32 NCG.
-rw-r--r-- | compiler/GHC/CmmToAsm.hs | 2 | ||||
-rw-r--r-- | compiler/GHC/CmmToAsm/Wasm.hs | 45 | ||||
-rw-r--r-- | compiler/GHC/CmmToAsm/Wasm/Asm.hs | 514 | ||||
-rw-r--r-- | compiler/GHC/CmmToAsm/Wasm/FromCmm.hs | 1666 | ||||
-rw-r--r-- | compiler/GHC/CmmToAsm/Wasm/Types.hs | 455 | ||||
-rw-r--r-- | compiler/GHC/CmmToAsm/Wasm/Utils.hs | 35 | ||||
-rw-r--r-- | compiler/ghc.cabal.in | 5 |
7 files changed, 2722 insertions, 0 deletions
diff --git a/compiler/GHC/CmmToAsm.hs b/compiler/GHC/CmmToAsm.hs index 9fed66053a..37d77900ba 100644 --- a/compiler/GHC/CmmToAsm.hs +++ b/compiler/GHC/CmmToAsm.hs @@ -83,6 +83,7 @@ import GHC.Prelude hiding (head) import qualified GHC.CmmToAsm.X86 as X86 import qualified GHC.CmmToAsm.PPC as PPC import qualified GHC.CmmToAsm.AArch64 as AArch64 +import qualified GHC.CmmToAsm.Wasm as Wasm32 import GHC.CmmToAsm.Reg.Liveness import qualified GHC.CmmToAsm.Reg.Linear as Linear @@ -169,6 +170,7 @@ nativeCodeGen logger config modLoc h us cmms ArchRISCV64 -> panic "nativeCodeGen: No NCG for RISCV64" ArchUnknown -> panic "nativeCodeGen: No NCG for unknown arch" ArchJavaScript-> panic "nativeCodeGen: No NCG for JavaScript" + ArchWasm32 -> Wasm32.ncgWasm platform us modLoc h cmms -- | Data accumulated during code generation. Mostly about statistics, -- but also collects debug data for DWARF generation. diff --git a/compiler/GHC/CmmToAsm/Wasm.hs b/compiler/GHC/CmmToAsm/Wasm.hs new file mode 100644 index 0000000000..6ea3244db4 --- /dev/null +++ b/compiler/GHC/CmmToAsm/Wasm.hs @@ -0,0 +1,45 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE Strict #-} + +module GHC.CmmToAsm.Wasm (ncgWasm) where + +import Data.ByteString.Builder +import Data.Maybe +import Data.Semigroup +import GHC.Cmm +import GHC.CmmToAsm.Wasm.Asm +import GHC.CmmToAsm.Wasm.FromCmm +import GHC.CmmToAsm.Wasm.Types +import GHC.Data.Stream (Stream, StreamS (..), runStream) +import GHC.Platform +import GHC.Prelude +import GHC.Types.Unique.Supply +import GHC.Unit +import System.IO + +ncgWasm :: + Platform -> + UniqSupply -> + ModLocation -> + Handle -> + Stream IO RawCmmGroup a -> + IO a +ncgWasm platform us loc h cmms = do + (r, s) <- streamCmmGroups platform us cmms + hPutBuilder h $ "# " <> string7 (fromJust $ ml_hs_file loc) <> "\n\n" + hPutBuilder h $ execWasmAsmM $ asmTellEverything TagI32 s + pure r + +streamCmmGroups :: + Platform -> + UniqSupply -> + Stream IO RawCmmGroup a -> + IO (a, WasmCodeGenState 'I32) +streamCmmGroups platform us cmms = + go (initialWasmCodeGenState platform us) $ + runStream cmms + where + go s (Done r) = pure (r, s) + go s (Effect m) = m >>= go s + go s (Yield cmm k) = go (wasmExecM (onCmmGroup cmm) s) k diff --git a/compiler/GHC/CmmToAsm/Wasm/Asm.hs b/compiler/GHC/CmmToAsm/Wasm/Asm.hs new file mode 100644 index 0000000000..2cc08a58b3 --- /dev/null +++ b/compiler/GHC/CmmToAsm/Wasm/Asm.hs @@ -0,0 +1,514 @@ +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE Strict #-} + +module GHC.CmmToAsm.Wasm.Asm (asmTellEverything, execWasmAsmM) where + +import Control.Monad +import Control.Monad.Trans.Reader +import Data.ByteString (ByteString) +import qualified Data.ByteString as BS +import Data.ByteString.Builder +import Data.Coerce +import Data.Foldable +import qualified Data.IntSet as IS +import Data.Maybe +import Data.Semigroup +import GHC.Cmm +import GHC.CmmToAsm.Ppr +import GHC.CmmToAsm.Wasm.FromCmm +import GHC.CmmToAsm.Wasm.Types +import GHC.CmmToAsm.Wasm.Utils +import GHC.Data.FastString +import GHC.Float +import GHC.Prelude +import GHC.Types.Basic +import GHC.Types.Unique +import GHC.Types.Unique.Map +import GHC.Utils.Monad.State.Strict +import GHC.Utils.Outputable hiding ((<>)) +import GHC.Utils.Panic (panic) + +-- | Reads current indentation, appends result to state +newtype WasmAsmM a = WasmAsmM (Builder -> State Builder a) + deriving + ( Functor, + Applicative, + Monad + ) + via (ReaderT Builder (State Builder)) + +instance Semigroup a => Semigroup (WasmAsmM a) where + (<>) = liftA2 (<>) + +instance Monoid a => Monoid (WasmAsmM a) where + mempty = pure mempty + +-- | Default indent level is none +execWasmAsmM :: WasmAsmM a -> Builder +execWasmAsmM (WasmAsmM m) = execState (m mempty) mempty + +-- | Increase indent level by a tab +asmWithTab :: WasmAsmM a -> WasmAsmM a +asmWithTab (WasmAsmM m) = WasmAsmM $ \t -> m $! char7 '\t' <> t + +-- | Writes a single line starting with the current indent +asmTellLine :: Builder -> WasmAsmM () +asmTellLine b = WasmAsmM $ \t -> modify $ \acc -> acc <> t <> b <> char7 '\n' + +-- | Writes a single line break +asmTellLF :: WasmAsmM () +asmTellLF = WasmAsmM $ \_ -> modify $ \acc -> acc <> char7 '\n' + +-- | Writes a line starting with a single tab, ignoring current indent +-- level +asmTellTabLine :: Builder -> WasmAsmM () +asmTellTabLine b = + WasmAsmM $ \_ -> modify $ \acc -> acc <> char7 '\t' <> b <> char7 '\n' + +asmFromWasmType :: WasmTypeTag t -> Builder +asmFromWasmType ty = case ty of + TagI32 -> "i32" + TagI64 -> "i64" + TagF32 -> "f32" + TagF64 -> "f64" + +asmFromSomeWasmType :: SomeWasmType -> Builder +asmFromSomeWasmType (SomeWasmType t) = asmFromWasmType t + +asmFromSomeWasmTypes :: [SomeWasmType] -> Builder +asmFromSomeWasmTypes ts = "(" <> builderCommas asmFromSomeWasmType ts <> ")" + +asmFromFuncType :: [SomeWasmType] -> [SomeWasmType] -> Builder +asmFromFuncType arg_tys ret_tys = + asmFromSomeWasmTypes arg_tys <> " -> " <> asmFromSomeWasmTypes ret_tys + +asmTellFuncType :: + SymName -> ([SomeWasmType], [SomeWasmType]) -> WasmAsmM () +asmTellFuncType sym (arg_tys, ret_tys) = + asmTellTabLine $ + ".functype " + <> asmFromSymName sym + <> " " + <> asmFromFuncType arg_tys ret_tys + +asmTellLocals :: [SomeWasmType] -> WasmAsmM () +asmTellLocals [] = mempty +asmTellLocals local_tys = + asmTellTabLine $ ".local " <> builderCommas asmFromSomeWasmType local_tys + +asmFromSymName :: SymName -> Builder +asmFromSymName = shortByteString . coerce fastStringToShortByteString + +asmTellDefSym :: SymName -> WasmAsmM () +asmTellDefSym sym = do + asmTellTabLine $ ".hidden " <> asm_sym + asmTellTabLine $ ".globl " <> asm_sym + where + asm_sym = asmFromSymName sym + +asmTellDataSectionContent :: WasmTypeTag w -> DataSectionContent -> WasmAsmM () +asmTellDataSectionContent ty_word c = asmTellTabLine $ case c of + DataI8 i -> ".int8 " <> integerDec i + DataI16 i -> ".int16 " <> integerDec i + DataI32 i -> ".int32 " <> integerDec i + DataI64 i -> ".int64 " <> integerDec i + DataF32 f -> ".int32 0x" <> word32Hex (castFloatToWord32 f) + DataF64 d -> ".int64 0x" <> word64Hex (castDoubleToWord64 d) + DataSym sym o -> + ( case ty_word of + TagI32 -> ".int32 " + TagI64 -> ".int64 " + _ -> panic "asmTellDataSectionContent: unreachable" + ) + <> asmFromSymName sym + <> ( case compare o 0 of + EQ -> mempty + GT -> "+" <> intDec o + LT -> intDec o + ) + DataSkip i -> ".skip " <> intDec i + DataASCII s + | not (BS.null s) && BS.last s == 0 -> + ".asciz \"" + <> string7 + (showSDocOneLine defaultSDocContext $ pprASCII $ BS.init s) + <> "\"" + | otherwise -> + ".ascii \"" + <> string7 + (showSDocOneLine defaultSDocContext $ pprASCII s) + <> "\"" + DataIncBin f _ -> + ".incbin " + <> string7 + (showSDocOneLine defaultSDocContext $ pprFilePathString f) + +dataSectionContentSize :: WasmTypeTag w -> DataSectionContent -> Int +dataSectionContentSize ty_word c = case c of + DataI8 {} -> 1 + DataI16 {} -> 2 + DataI32 {} -> 4 + DataI64 {} -> 8 + DataF32 {} -> 4 + DataF64 {} -> 8 + DataSym {} -> alignmentBytes $ alignmentFromWordType ty_word + DataSkip i -> i + DataASCII s -> BS.length s + DataIncBin _ l -> l + +dataSectionSize :: WasmTypeTag w -> [DataSectionContent] -> Int +dataSectionSize ty_word = + coerce + . foldMap' + (Sum . dataSectionContentSize ty_word) + +asmTellAlign :: Alignment -> WasmAsmM () +asmTellAlign a = case alignmentBytes a of + 1 -> mempty + i -> asmTellTabLine $ ".p2align " <> intDec (countTrailingZeros i) + +asmTellSectionHeader :: Builder -> WasmAsmM () +asmTellSectionHeader k = asmTellTabLine $ ".section " <> k <> ",\"\",@" + +asmTellDataSection :: + WasmTypeTag w -> IS.IntSet -> SymName -> DataSection -> WasmAsmM () +asmTellDataSection ty_word def_syms sym DataSection {..} = do + when (getKey (getUnique sym) `IS.member` def_syms) $ asmTellDefSym sym + asmTellSectionHeader sec_name + asmTellAlign dataSectionAlignment + asmTellTabLine asm_size + asmTellLine $ asm_sym <> ":" + for_ dataSectionContents $ asmTellDataSectionContent ty_word + asmTellLF + where + asm_sym = asmFromSymName sym + + sec_name = + ( case dataSectionKind of + SectionData -> ".data." + SectionROData -> ".rodata." + ) + <> asm_sym + + asm_size = + ".size " + <> asm_sym + <> ", " + <> intDec + (dataSectionSize ty_word dataSectionContents) + +asmFromWasmBlockType :: WasmTypeTag w -> WasmFunctionType pre post -> Builder +asmFromWasmBlockType + _ + (WasmFunctionType {ft_pops = TypeListNil, ft_pushes = TypeListNil}) = + mempty +asmFromWasmBlockType + TagI32 + ( WasmFunctionType + { ft_pops = TypeListNil, + ft_pushes = TypeListCons TagI32 TypeListNil + } + ) = + " i32" +asmFromWasmBlockType + TagI64 + ( WasmFunctionType + { ft_pops = TypeListNil, + ft_pushes = TypeListCons TagI64 TypeListNil + } + ) = + " i64" +asmFromWasmBlockType _ _ = panic "asmFromWasmBlockType: invalid block type" + +asmFromAlignmentSpec :: AlignmentSpec -> Builder +asmFromAlignmentSpec NaturallyAligned = mempty +asmFromAlignmentSpec Unaligned = ":p2align=0" + +asmTellWasmInstr :: WasmTypeTag w -> WasmInstr w pre post -> WasmAsmM () +asmTellWasmInstr ty_word instr = case instr of + WasmComment c -> asmTellLine $ stringUtf8 $ "# " <> c + WasmNop -> mempty + WasmDrop -> asmTellLine "drop" + WasmUnreachable -> asmTellLine "unreachable" + WasmConst TagI32 i -> asmTellLine $ "i32.const " <> integerDec i + WasmConst TagI64 i -> asmTellLine $ "i64.const " <> integerDec i + WasmConst {} -> panic "asmTellWasmInstr: unreachable" + WasmSymConst sym -> + asmTellLine $ + ( case ty_word of + TagI32 -> "i32.const " + TagI64 -> "i64.const " + _ -> panic "asmTellWasmInstr: unreachable" + ) + <> asmFromSymName sym + WasmLoad ty (Just w) s o align -> + asmTellLine $ + asmFromWasmType ty + <> ".load" + <> intDec w + <> ( case s of + Signed -> "_s" + Unsigned -> "_u" + ) + <> " " + <> intDec o + <> asmFromAlignmentSpec align + WasmLoad ty Nothing _ o align -> + asmTellLine $ + asmFromWasmType ty + <> ".load" + <> " " + <> intDec o + <> asmFromAlignmentSpec align + WasmStore ty (Just w) o align -> + asmTellLine $ + asmFromWasmType ty + <> ".store" + <> intDec w + <> " " + <> intDec o + <> asmFromAlignmentSpec align + WasmStore ty Nothing o align -> + asmTellLine $ + asmFromWasmType ty + <> ".store" + <> " " + <> intDec o + <> asmFromAlignmentSpec align + WasmGlobalGet _ sym -> asmTellLine $ "global.get " <> asmFromSymName sym + WasmGlobalSet _ sym -> asmTellLine $ "global.set " <> asmFromSymName sym + WasmLocalGet _ i -> asmTellLine $ "local.get " <> intDec i + WasmLocalSet _ i -> asmTellLine $ "local.set " <> intDec i + WasmLocalTee _ i -> asmTellLine $ "local.tee " <> intDec i + WasmCCall sym -> asmTellLine $ "call " <> asmFromSymName sym + WasmCCallIndirect arg_tys ret_tys -> + asmTellLine $ + "call_indirect " + <> asmFromFuncType + (someWasmTypesFromTypeList arg_tys) + (someWasmTypesFromTypeList ret_tys) + WasmConcat instr0 instr1 -> do + asmTellWasmInstr ty_word instr0 + asmTellWasmInstr ty_word instr1 + WasmReinterpret t0 t1 -> + asmTellLine $ + asmFromWasmType t1 <> ".reinterpret_" <> asmFromWasmType t0 + WasmTruncSat Signed t0 t1 -> + asmTellLine $ + asmFromWasmType t1 <> ".trunc_sat_" <> asmFromWasmType t0 <> "_s" + WasmTruncSat Unsigned t0 t1 -> + asmTellLine $ + asmFromWasmType t1 <> ".trunc_sat_" <> asmFromWasmType t0 <> "_u" + WasmConvert Signed t0 t1 -> + asmTellLine $ + asmFromWasmType t1 <> ".convert_" <> asmFromWasmType t0 <> "_s" + WasmConvert Unsigned t0 t1 -> + asmTellLine $ + asmFromWasmType t1 <> ".convert_" <> asmFromWasmType t0 <> "_u" + WasmClz ty -> asmTellLine $ asmFromWasmType ty <> ".clz" + WasmCtz ty -> asmTellLine $ asmFromWasmType ty <> ".ctz" + WasmPopcnt ty -> asmTellLine $ asmFromWasmType ty <> ".popcnt" + WasmAdd ty -> asmTellLine $ asmFromWasmType ty <> ".add" + WasmSub ty -> asmTellLine $ asmFromWasmType ty <> ".sub" + WasmMul ty -> asmTellLine $ asmFromWasmType ty <> ".mul" + WasmDiv _ TagF32 -> asmTellLine "f32.div" + WasmDiv _ TagF64 -> asmTellLine "f64.div" + WasmDiv Signed ty -> asmTellLine $ asmFromWasmType ty <> ".div_s" + WasmDiv Unsigned ty -> asmTellLine $ asmFromWasmType ty <> ".div_u" + WasmRem Signed ty -> asmTellLine $ asmFromWasmType ty <> ".rem_s" + WasmRem Unsigned ty -> asmTellLine $ asmFromWasmType ty <> ".rem_u" + WasmAnd ty -> asmTellLine $ asmFromWasmType ty <> ".and" + WasmOr ty -> asmTellLine $ asmFromWasmType ty <> ".or" + WasmXor ty -> asmTellLine $ asmFromWasmType ty <> ".xor" + WasmEq ty -> asmTellLine $ asmFromWasmType ty <> ".eq" + WasmNe ty -> asmTellLine $ asmFromWasmType ty <> ".ne" + WasmLt _ TagF32 -> asmTellLine "f32.lt" + WasmLt _ TagF64 -> asmTellLine "f64.lt" + WasmLt Signed ty -> asmTellLine $ asmFromWasmType ty <> ".lt_s" + WasmLt Unsigned ty -> asmTellLine $ asmFromWasmType ty <> ".lt_u" + WasmGt _ TagF32 -> asmTellLine "f32.gt" + WasmGt _ TagF64 -> asmTellLine "f64.gt" + WasmGt Signed ty -> asmTellLine $ asmFromWasmType ty <> ".gt_s" + WasmGt Unsigned ty -> asmTellLine $ asmFromWasmType ty <> ".gt_u" + WasmLe _ TagF32 -> asmTellLine "f32.le" + WasmLe _ TagF64 -> asmTellLine "f64.le" + WasmLe Signed ty -> asmTellLine $ asmFromWasmType ty <> ".le_s" + WasmLe Unsigned ty -> asmTellLine $ asmFromWasmType ty <> ".le_u" + WasmGe _ TagF32 -> asmTellLine "f32.ge" + WasmGe _ TagF64 -> asmTellLine "f64.ge" + WasmGe Signed ty -> asmTellLine $ asmFromWasmType ty <> ".ge_s" + WasmGe Unsigned ty -> asmTellLine $ asmFromWasmType ty <> ".ge_u" + WasmShl ty -> asmTellLine $ asmFromWasmType ty <> ".shl" + WasmShr Signed ty -> asmTellLine $ asmFromWasmType ty <> ".shr_s" + WasmShr Unsigned ty -> asmTellLine $ asmFromWasmType ty <> ".shr_u" + WasmI32Extend8S -> asmTellLine "i32.extend8_s" + WasmI32Extend16S -> asmTellLine "i32.extend16_s" + WasmI64Extend8S -> asmTellLine "i64.extend8_s" + WasmI64Extend16S -> asmTellLine "i64.extend16_s" + WasmI64Extend32S -> asmTellLine "i64.extend32_s" + WasmI64ExtendI32 Signed -> asmTellLine "i64.extend_i32_s" + WasmI64ExtendI32 Unsigned -> asmTellLine "i64.extend_i32_u" + WasmI32WrapI64 -> asmTellLine "i32.wrap_i64" + WasmF32DemoteF64 -> asmTellLine "f32.demote_f64" + WasmF64PromoteF32 -> asmTellLine "f64.promote_f32" + WasmAbs ty -> asmTellLine $ asmFromWasmType ty <> ".abs" + WasmCond t -> do + asmTellLine "if" + asmWithTab $ asmTellWasmInstr ty_word t + asmTellLine "end_if" + +asmTellWasmControl :: + WasmTypeTag w -> + WasmControl + (WasmStatements w) + (WasmExpr w a) + pre + post -> + WasmAsmM () +asmTellWasmControl ty_word c = case c of + WasmPush _ (WasmExpr e) -> asmTellWasmInstr ty_word e + WasmBlock bt c -> do + asmTellLine $ "block" <> asmFromWasmBlockType ty_word bt + asmWithTab $ asmTellWasmControl ty_word c + asmTellLine "end_block" + WasmLoop bt c -> do + asmTellLine $ "loop" <> asmFromWasmBlockType ty_word bt + asmWithTab $ asmTellWasmControl ty_word c + -- asmTellLine "br 0" + asmTellLine "end_loop" + WasmIfTop bt t f -> do + asmTellLine $ "if" <> asmFromWasmBlockType ty_word bt + asmWithTab $ asmTellWasmControl ty_word t + asmTellLine "else" + asmWithTab $ asmTellWasmControl ty_word f + asmTellLine "end_if" + WasmBr i -> asmTellLine $ "br " <> intDec i + WasmFallthrough -> mempty + WasmBrTable (WasmExpr e) _ ts t -> do + asmTellWasmInstr ty_word e + asmTellLine $ "br_table {" <> builderCommas intDec (ts <> [t]) <> "}" + WasmReturnTop _ -> asmTellLine "return" + WasmActions (WasmStatements a) -> asmTellWasmInstr ty_word a + WasmSeq c0 c1 -> do + asmTellWasmControl ty_word c0 + asmTellWasmControl ty_word c1 + +asmTellFunc :: + WasmTypeTag w -> + IS.IntSet -> + SymName -> + (([SomeWasmType], [SomeWasmType]), FuncBody w) -> + WasmAsmM () +asmTellFunc ty_word def_syms sym (func_ty, FuncBody {..}) = do + when (getKey (getUnique sym) `IS.member` def_syms) $ asmTellDefSym sym + asmTellSectionHeader $ ".text." <> asm_sym + asmTellLine $ asm_sym <> ":" + asmTellFuncType sym func_ty + asmTellLocals funcLocals + asmWithTab $ asmTellWasmControl ty_word funcBody + asmTellTabLine "end_function" + asmTellLF + where + asm_sym = asmFromSymName sym + +asmTellGlobals :: WasmTypeTag w -> WasmAsmM () +asmTellGlobals ty_word = do + for_ supportedCmmGlobalRegs $ \reg -> + let (sym, ty) = fromJust $ globalInfoFromCmmGlobalReg ty_word reg + in asmTellTabLine $ + ".globaltype " + <> asmFromSymName sym + <> ", " + <> asmFromSomeWasmType ty + asmTellLF + +asmTellCtors :: WasmTypeTag w -> [SymName] -> WasmAsmM () +asmTellCtors _ [] = mempty +asmTellCtors ty_word syms = do + asmTellSectionHeader ".init_array" + asmTellAlign $ alignmentFromWordType ty_word + for_ syms $ \sym -> + asmTellTabLine $ + ( case ty_word of + TagI32 -> ".int32 " + TagI64 -> ".int64 " + _ -> panic "asmTellCtors: unreachable" + ) + <> asmFromSymName sym + asmTellLF + +asmTellBS :: ByteString -> WasmAsmM () +asmTellBS s = do + asmTellTabLine $ ".int8 " <> intDec (BS.length s) + asmTellTabLine $ + ".ascii \"" + <> string7 + (showSDocOneLine defaultSDocContext $ pprASCII s) + <> "\"" + +asmTellVec :: [WasmAsmM ()] -> WasmAsmM () +asmTellVec xs = do + asmTellTabLine $ ".int8 " <> intDec (length xs) + sequence_ xs + +asmTellProducers :: WasmAsmM () +asmTellProducers = do + asmTellSectionHeader ".custom_section.producers" + asmTellVec + [ do + asmTellBS "processed-by" + asmTellVec + [ do + asmTellBS "ghc" + asmTellBS "9.6" + ] + ] + +asmTellTargetFeatures :: WasmAsmM () +asmTellTargetFeatures = do + asmTellSectionHeader ".custom_section.target_features" + asmTellVec + [ do + asmTellTabLine ".int8 0x2b" + asmTellBS feature + | feature <- + [ "bulk-memory", + "mutable-globals", + "nontrapping-fptoint", + "reference-types", + "sign-ext" + ] + ] + +asmTellEverything :: WasmTypeTag w -> WasmCodeGenState w -> WasmAsmM () +asmTellEverything ty_word WasmCodeGenState {..} = do + asmTellGlobals ty_word + asm_functypes + asm_funcs + asm_data_secs + asm_ctors + asmTellProducers + asmTellTargetFeatures + where + asm_functypes = do + for_ + (detEltsUniqMap $ funcTypes `minusUniqMap` funcBodies) + (uncurry asmTellFuncType) + asmTellLF + + asm_funcs = do + for_ + (detEltsUniqMap $ intersectUniqMap_C (,) funcTypes funcBodies) + (uncurry $ asmTellFunc ty_word defaultSyms) + asmTellLF + + asm_data_secs = do + for_ + (detEltsUniqMap dataSections) + (uncurry (asmTellDataSection ty_word defaultSyms)) + asmTellLF + + asm_ctors = asmTellCtors ty_word ctors diff --git a/compiler/GHC/CmmToAsm/Wasm/FromCmm.hs b/compiler/GHC/CmmToAsm/Wasm/FromCmm.hs new file mode 100644 index 0000000000..034a75aea2 --- /dev/null +++ b/compiler/GHC/CmmToAsm/Wasm/FromCmm.hs @@ -0,0 +1,1666 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE Strict #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE UnboxedTuples #-} +{-# LANGUAGE UndecidableInstances #-} +{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} + +{-# HLINT ignore "Use camelCase" #-} +module GHC.CmmToAsm.Wasm.FromCmm where + +import Control.Monad +import qualified Data.ByteString as BS +import Data.Foldable +import Data.Functor +import qualified Data.IntSet as IS +import Data.Semigroup +import Data.String +import Data.Traversable +import Data.Type.Equality +import GHC.Cmm +import GHC.Cmm.CLabel +import GHC.Cmm.Dataflow.Block +import GHC.Cmm.Dataflow.Label +import GHC.Cmm.InitFini +import GHC.CmmToAsm.Wasm.Types +import GHC.CmmToAsm.Wasm.Utils +import GHC.Float +import GHC.Platform +import GHC.Prelude +import GHC.StgToCmm.CgUtils +import GHC.Types.Basic +import GHC.Types.ForeignCall +import GHC.Types.Unique +import GHC.Types.Unique.FM +import GHC.Types.Unique.Map +import GHC.Utils.Outputable hiding ((<>)) +import GHC.Utils.Panic +import GHC.Wasm.ControlFlow.FromCmm + +-- | Calculate the wasm representation type from a 'CmmType'. This is +-- a lossy conversion, and sometimes we need to pass the original +-- 'CmmType' or at least its 'Width' around, so to properly add +-- subword truncation or extension logic. +someWasmTypeFromCmmType :: CmmType -> SomeWasmType +someWasmTypeFromCmmType t + | isWord32 t = SomeWasmType TagI32 + | isWord64 t = SomeWasmType TagI64 + | t `cmmEqType` b16 = SomeWasmType TagI32 + | t `cmmEqType` b8 = SomeWasmType TagI32 + | isFloat64 t = SomeWasmType TagF64 + | isFloat32 t = SomeWasmType TagF32 + | otherwise = + panic $ + "someWasmTypeFromCmmType: unsupported CmmType " + <> showSDocOneLine defaultSDocContext (ppr t) + +-- | Calculate the optional memory narrowing of a 'CmmLoad' or +-- 'CmmStore'. +wasmMemoryNarrowing :: WasmTypeTag t -> CmmType -> Maybe Int +wasmMemoryNarrowing ty ty_cmm = case (# ty, typeWidth ty_cmm #) of + (# TagI32, W8 #) -> Just 8 + (# TagI32, W16 #) -> Just 16 + (# TagI32, W32 #) -> Nothing + (# TagI64, W8 #) -> Just 8 + (# TagI64, W16 #) -> Just 16 + (# TagI64, W32 #) -> Just 32 + (# TagI64, W64 #) -> Nothing + (# TagF32, W32 #) -> Nothing + (# TagF64, W64 #) -> Nothing + _ -> panic "wasmMemoryNarrowing: unreachable" + +-- | Despite this is used by the WebAssembly native codegen, we use +-- 'pprCLabel' instead of 'pprAsmLabel' when emitting the textual +-- symbol name. Either one would work, but 'pprCLabel' makes the +-- output assembly code looks closer to the unregisterised codegen +-- output, which can be handy when using the unregisterised codegen as +-- a source of truth when debugging the native codegen. +symNameFromCLabel :: CLabel -> SymName +symNameFromCLabel lbl = + fromString $ + showSDocOneLine defaultSDocContext {sdocStyle = PprCode} $ + pprCLabel genericPlatform lbl + +-- | Calculate a symbol's visibility. +symVisibilityFromCLabel :: CLabel -> SymVisibility +symVisibilityFromCLabel lbl + | externallyVisibleCLabel lbl = SymDefault + | otherwise = SymStatic + +-- | Calculate a symbol's kind, see haddock docs of 'SymKind' for more +-- explanation. +symKindFromCLabel :: CLabel -> SymKind +symKindFromCLabel lbl + | isCFunctionLabel lbl = SymFunc + | otherwise = SymData + +-- | Calculate a data section's kind, see haddock docs of +-- 'DataSectionKind' for more explanation. +dataSectionKindFromCmmSection :: Section -> DataSectionKind +dataSectionKindFromCmmSection s = case sectionProtection s of + ReadWriteSection -> SectionData + _ -> SectionROData + +-- | Calculate the natural alignment size given the platform word +-- type. +alignmentFromWordType :: WasmTypeTag w -> Alignment +alignmentFromWordType TagI32 = mkAlignment 4 +alignmentFromWordType TagI64 = mkAlignment 8 +alignmentFromWordType _ = panic "alignmentFromWordType: unreachable" + +-- | Calculate a data section's alignment. Closures needs to be +-- naturally aligned; info tables need to align to 2, so to get 1 tag +-- bit as forwarding pointer marker. The rest have no alignment +-- requirements. +alignmentFromCmmSection :: WasmTypeTag w -> CLabel -> Alignment +alignmentFromCmmSection t lbl + | isStaticClosureLabel lbl = alignmentFromWordType t + | isInfoTableLabel lbl = mkAlignment 2 + | otherwise = mkAlignment 1 + +-- | Lower a 'CmmStatic'. +lower_CmmStatic :: CmmStatic -> WasmCodeGenM w DataSectionContent +lower_CmmStatic s = case s of + CmmStaticLit (CmmInt i W8) -> pure $ DataI8 $ naturalNarrowing W8 i + CmmStaticLit (CmmInt i W16) -> pure $ DataI16 $ naturalNarrowing W16 i + CmmStaticLit (CmmInt i W32) -> pure $ DataI32 $ naturalNarrowing W32 i + CmmStaticLit (CmmInt i W64) -> pure $ DataI64 $ naturalNarrowing W64 i + CmmStaticLit (CmmFloat f W32) -> pure $ DataF32 $ fromRational f + CmmStaticLit (CmmFloat d W64) -> pure $ DataF64 $ fromRational d + CmmStaticLit (CmmLabel lbl) -> + onAnySym lbl + $> DataSym + (symNameFromCLabel lbl) + 0 + CmmStaticLit (CmmLabelOff lbl o) -> + onAnySym lbl + $> DataSym + (symNameFromCLabel lbl) + o + CmmUninitialised i -> pure $ DataSkip i + CmmString b -> pure $ DataASCII b + CmmFileEmbed f l -> pure $ DataIncBin f l + _ -> panic "lower_CmmStatic: unreachable" + +{- +Note [Register mapping on WebAssembly] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Unlike typical ISAs, WebAssembly doesn't expose a fixed set of +registers. For now, we map each Cmm LocalReg to a wasm local, and each +Cmm GlobalReg to a wasm global. The wasm globals are defined in +rts/wasm/Wasm.S, and must be kept in sync with +'globalInfoFromCmmGlobalReg' and 'supportedCmmGlobalRegs' here. + +There are some other Cmm GlobalRegs which are still represented by +StgRegTable fields instead of wasm globals (e.g. HpAlloc). It's cheap +to add wasm globals, but other parts of rts logic only work with the +StgRegTable fields, so we also need to instrument StgRun/StgReturn to +sync the wasm globals with the StgRegTable. It's not really worth the +trouble. + +-} +globalInfoFromCmmGlobalReg :: WasmTypeTag w -> GlobalReg -> Maybe GlobalInfo +globalInfoFromCmmGlobalReg t reg = case reg of + VanillaReg i _ + | i >= 1 && i <= 10 -> Just (fromString $ "__R" <> show i, ty_word) + FloatReg i + | i >= 1 && i <= 6 -> + Just (fromString $ "__F" <> show i, SomeWasmType TagF32) + DoubleReg i + | i >= 1 && i <= 6 -> + Just (fromString $ "__D" <> show i, SomeWasmType TagF64) + LongReg i + | i == 1 -> Just (fromString $ "__L" <> show i, SomeWasmType TagI64) + Sp -> Just ("__Sp", ty_word) + SpLim -> Just ("__SpLim", ty_word) + Hp -> Just ("__Hp", ty_word) + HpLim -> Just ("__HpLim", ty_word) + CCCS -> Just ("__CCCS", ty_word) + _ -> Nothing + where + ty_word = SomeWasmType t + +supportedCmmGlobalRegs :: [GlobalReg] +supportedCmmGlobalRegs = + [VanillaReg i VGcPtr | i <- [1 .. 10]] + <> [FloatReg i | i <- [1 .. 6]] + <> [DoubleReg i | i <- [1 .. 6]] + <> [LongReg i | i <- [1 .. 1]] + <> [Sp, SpLim, Hp, HpLim, CCCS] + +-- | Allocate a fresh symbol for an internal data section. +allocDataSection :: DataSection -> WasmCodeGenM w SymName +allocDataSection sec = do + u <- wasmUniq + let sym = fromString $ ".L" <> show u + wasmModifyM $ \s -> + s + { dataSections = + addToUniqMap (dataSections s) sym sec + } + pure sym + +-- | Print a debug message to stderr by calling @fputs()@. We don't +-- bother to check @fputs()@ return value. +wasmDebugMsg :: String -> WasmCodeGenM w (WasmStatements w) +wasmDebugMsg msg = do + ty_word_cmm <- wasmWordCmmTypeM + sym_buf <- + allocDataSection + DataSection + { dataSectionKind = + SectionROData, + dataSectionAlignment = + mkAlignment 1, + dataSectionContents = + [DataASCII $ fromString $ msg <> "\NUL"] + } + onFuncSym "fputs" [ty_word_cmm, ty_word_cmm] [b32] + pure $ + WasmStatements $ + WasmSymConst sym_buf + `WasmConcat` WasmSymConst "__stderr_FILE" + `WasmConcat` WasmCCall "fputs" + `WasmConcat` WasmDrop + +-- | Truncate a subword. +truncSubword :: Width -> WasmTypeTag t -> WasmExpr w t -> WasmExpr w t +truncSubword W8 ty (WasmExpr instr) = + WasmExpr $ instr `WasmConcat` WasmConst ty 0xFF `WasmConcat` WasmAnd ty +truncSubword W16 ty (WasmExpr instr) = + WasmExpr $ instr `WasmConcat` WasmConst ty 0xFFFF `WasmConcat` WasmAnd ty +truncSubword _ _ expr = expr + +-- | Sign-extend a subword. +extendSubword :: Width -> WasmTypeTag t -> WasmExpr w t -> WasmExpr w t +extendSubword W8 TagI32 (WasmExpr instr) = + WasmExpr $ instr `WasmConcat` WasmI32Extend8S +extendSubword W16 TagI32 (WasmExpr instr) = + WasmExpr $ instr `WasmConcat` WasmI32Extend16S +extendSubword W8 TagI64 (WasmExpr instr) = + WasmExpr $ instr `WasmConcat` WasmI64Extend8S +extendSubword W16 TagI64 (WasmExpr instr) = + WasmExpr $ instr `WasmConcat` WasmI64Extend16S +extendSubword W32 TagI64 (WasmExpr instr) = + WasmExpr $ instr `WasmConcat` WasmI64Extend32S +extendSubword _ _ expr = expr + +-- | Lower a binary homogeneous operation. Homogeneous: result type is +-- the same with operand types. +lower_MO_Bin_Homo :: + ( forall pre t. + WasmTypeTag t -> + WasmInstr + w + (t : t : pre) + (t : pre) + ) -> + CLabel -> + CmmType -> + [CmmExpr] -> + WasmCodeGenM w (SomeWasmExpr w) +lower_MO_Bin_Homo op lbl t0 [x, y] = case someWasmTypeFromCmmType t0 of + SomeWasmType ty -> do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl ty x + WasmExpr y_instr <- lower_CmmExpr_Typed lbl ty y + pure $ + SomeWasmExpr ty $ + WasmExpr $ + x_instr `WasmConcat` y_instr `WasmConcat` op ty +lower_MO_Bin_Homo _ _ _ _ = panic "lower_MO_Bin_Homo: unreachable" + +-- | Lower a binary homogeneous operation, and truncate the result if +-- it's a subword. +lower_MO_Bin_Homo_Trunc :: + (forall pre t. WasmTypeTag t -> WasmInstr w (t : t : pre) (t : pre)) -> + CLabel -> + Width -> + [CmmExpr] -> + WasmCodeGenM w (SomeWasmExpr w) +lower_MO_Bin_Homo_Trunc op lbl w0 [x, y] = + case someWasmTypeFromCmmType (cmmBits w0) of + SomeWasmType ty -> do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl ty x + WasmExpr y_instr <- lower_CmmExpr_Typed lbl ty y + pure $ + SomeWasmExpr ty $ + truncSubword w0 ty $ + WasmExpr $ + x_instr `WasmConcat` y_instr `WasmConcat` op ty +lower_MO_Bin_Homo_Trunc _ _ _ _ = panic "lower_MO_Bin_Homo_Trunc: unreachable" + +-- | Lower a binary homogeneous operation, first sign extending the +-- operands, then truncating the result. +lower_MO_Bin_Homo_Ext_Trunc :: + (forall pre t. WasmTypeTag t -> WasmInstr w (t : t : pre) (t : pre)) -> + CLabel -> + Width -> + [CmmExpr] -> + WasmCodeGenM w (SomeWasmExpr w) +lower_MO_Bin_Homo_Ext_Trunc op lbl w0 [x, y] = + case someWasmTypeFromCmmType (cmmBits w0) of + SomeWasmType ty -> do + WasmExpr x_instr <- + extendSubword w0 ty <$> lower_CmmExpr_Typed lbl ty x + WasmExpr y_instr <- + extendSubword w0 ty <$> lower_CmmExpr_Typed lbl ty y + pure $ + SomeWasmExpr ty $ + truncSubword w0 ty $ + WasmExpr $ + x_instr `WasmConcat` y_instr `WasmConcat` op ty +lower_MO_Bin_Homo_Ext_Trunc _ _ _ _ = + panic "lower_MO_Bin_Homo_Ext_Trunc: unreachable" + +-- | Lower a relational binary operation, first sign extending the +-- operands. Relational: result type is a boolean (word type). +lower_MO_Bin_Rel_Ext :: + (forall pre t. WasmTypeTag t -> WasmInstr w (t : t : pre) (w : pre)) -> + CLabel -> + Width -> + [CmmExpr] -> + WasmCodeGenM w (SomeWasmExpr w) +lower_MO_Bin_Rel_Ext op lbl w0 [x, y] = + case someWasmTypeFromCmmType (cmmBits w0) of + SomeWasmType ty -> do + WasmExpr x_instr <- + extendSubword w0 ty <$> lower_CmmExpr_Typed lbl ty x + WasmExpr y_instr <- + extendSubword w0 ty <$> lower_CmmExpr_Typed lbl ty y + ty_word <- wasmWordTypeM + pure $ + SomeWasmExpr ty_word $ + WasmExpr $ + x_instr `WasmConcat` y_instr `WasmConcat` op ty +lower_MO_Bin_Rel_Ext _ _ _ _ = panic "lower_MO_Bin_Rel_Ext: unreachable" + +-- | Lower a relational binary operation. +lower_MO_Bin_Rel :: + ( forall pre t. + WasmTypeTag t -> + WasmInstr + w + (t : t : pre) + (w : pre) + ) -> + CLabel -> + CmmType -> + [CmmExpr] -> + WasmCodeGenM w (SomeWasmExpr w) +lower_MO_Bin_Rel op lbl t0 [x, y] = case someWasmTypeFromCmmType t0 of + SomeWasmType ty -> do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl ty x + WasmExpr y_instr <- lower_CmmExpr_Typed lbl ty y + ty_word <- wasmWordTypeM + pure $ + SomeWasmExpr ty_word $ + WasmExpr $ + x_instr `WasmConcat` y_instr `WasmConcat` op ty +lower_MO_Bin_Rel _ _ _ _ = panic "lower_MO_Bin_Rel: unreachable" + +-- | Cast a shiftL/shiftR RHS to the same type as LHS. Because we may +-- have a 64-bit LHS and 32-bit RHS, but wasm shift operators are +-- homogeneous. +shiftRHSCast :: + CLabel -> + WasmTypeTag t -> + CmmExpr -> + WasmCodeGenM + w + (WasmExpr w t) +shiftRHSCast lbl t1 x = do + SomeWasmExpr t0 (WasmExpr x_instr) <- lower_CmmExpr lbl x + if + | Just Refl <- t0 `testEquality` t1 -> pure $ WasmExpr x_instr + | TagI32 <- t0, + TagI64 <- t1 -> + pure $ WasmExpr $ x_instr `WasmConcat` WasmI64ExtendI32 Unsigned + | otherwise -> panic "shiftRHSCast: unreachable" + +-- | Lower a 'MO_Shl' operation, truncating the result. +lower_MO_Shl :: + CLabel -> + Width -> + [CmmExpr] -> + WasmCodeGenM + w + (SomeWasmExpr w) +lower_MO_Shl lbl w0 [x, y] = case someWasmTypeFromCmmType (cmmBits w0) of + SomeWasmType ty -> do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl ty x + WasmExpr y_instr <- shiftRHSCast lbl ty y + pure $ + SomeWasmExpr ty $ + truncSubword w0 ty $ + WasmExpr $ + x_instr `WasmConcat` y_instr `WasmConcat` WasmShl ty +lower_MO_Shl _ _ _ = panic "lower_MO_Shl: unreachable" + +-- | Lower a 'MO_U_Shr' operation. +lower_MO_U_Shr :: + CLabel -> + Width -> + [CmmExpr] -> + WasmCodeGenM + w + (SomeWasmExpr w) +lower_MO_U_Shr lbl w0 [x, y] = case someWasmTypeFromCmmType (cmmBits w0) of + SomeWasmType ty -> do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl ty x + WasmExpr y_instr <- shiftRHSCast lbl ty y + pure $ + SomeWasmExpr ty $ + WasmExpr $ + x_instr `WasmConcat` y_instr `WasmConcat` WasmShr Unsigned ty +lower_MO_U_Shr _ _ _ = panic "lower_MO_U_Shr: unreachable" + +-- | Lower a 'MO_S_Shr' operation, first sign-extending the LHS, then +-- truncating the result. +lower_MO_S_Shr :: + CLabel -> + Width -> + [CmmExpr] -> + WasmCodeGenM + w + (SomeWasmExpr w) +lower_MO_S_Shr lbl w0 [x, y] = case someWasmTypeFromCmmType (cmmBits w0) of + SomeWasmType ty -> do + WasmExpr x_instr <- extendSubword w0 ty <$> lower_CmmExpr_Typed lbl ty x + WasmExpr y_instr <- shiftRHSCast lbl ty y + pure $ + SomeWasmExpr ty $ + truncSubword w0 ty $ + WasmExpr $ + x_instr `WasmConcat` y_instr `WasmConcat` WasmShr Signed ty +lower_MO_S_Shr _ _ _ = panic "lower_MO_S_Shr: unreachable" + +-- | Lower a 'MO_MulMayOflo' operation. It's translated to a ccall to +-- one of the @hs_mulIntMayOflo@ functions in rts/wasm/Ops.c, +-- otherwise it's quite non-trivial to implement as inline assembly. +lower_MO_MulMayOflo :: + CLabel -> + Width -> + [CmmExpr] -> + WasmCodeGenM + w + (SomeWasmExpr w) +lower_MO_MulMayOflo lbl w0 [x, y] = case someWasmTypeFromCmmType ty_cmm of + SomeWasmType ty -> do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl ty x + WasmExpr y_instr <- lower_CmmExpr_Typed lbl ty y + onFuncSym f [ty_cmm, ty_cmm] [ty_cmm] + pure $ + SomeWasmExpr ty $ + WasmExpr $ + x_instr `WasmConcat` y_instr `WasmConcat` WasmCCall f + where + f = fromString $ "hs_mulIntMayOflo" <> show (widthInBits w0) + + ty_cmm = cmmBits w0 +lower_MO_MulMayOflo _ _ _ = panic "lower_MO_MulMayOflo: unreachable" + +-- | Lower an unary conversion operation. +lower_MO_Un_Conv :: + ( forall pre t0 t1. + WasmTypeTag t0 -> + WasmTypeTag t1 -> + WasmInstr w (t0 : pre) (t1 : pre) + ) -> + CLabel -> + CmmType -> + CmmType -> + [CmmExpr] -> + WasmCodeGenM w (SomeWasmExpr w) +lower_MO_Un_Conv op lbl t0 t1 [x] = + case (# someWasmTypeFromCmmType t0, someWasmTypeFromCmmType t1 #) of + (# SomeWasmType ty0, SomeWasmType ty1 #) -> do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl ty0 x + pure $ SomeWasmExpr ty1 $ WasmExpr $ x_instr `WasmConcat` op ty0 ty1 +lower_MO_Un_Conv _ _ _ _ _ = panic "lower_MO_Un_Conv: unreachable" + +-- | Lower a 'MO_SS_Conv' operation. +lower_MO_SS_Conv :: + CLabel -> + Width -> + Width -> + [CmmExpr] -> + WasmCodeGenM + w + (SomeWasmExpr w) +lower_MO_SS_Conv lbl w0 w1 [x] + | w0 == w1 = lower_CmmExpr lbl x +lower_MO_SS_Conv lbl w0 w1 [CmmLoad ptr _ align] + | w0 < w1, + w1 <= W32 = do + (WasmExpr ptr_instr, o) <- lower_CmmExpr_Ptr lbl ptr + pure $ + SomeWasmExpr TagI32 $ + truncSubword w1 TagI32 $ + WasmExpr $ + ptr_instr + `WasmConcat` WasmLoad + TagI32 + (wasmMemoryNarrowing TagI32 (cmmBits w0)) + Signed + o + align + | w0 > w1 = + SomeWasmExpr TagI32 + <$> lower_CmmLoad_Typed + lbl + ptr + TagI32 + (cmmBits w1) + align +lower_MO_SS_Conv lbl w0 W64 [CmmLoad ptr _ align] = do + (WasmExpr ptr_instr, o) <- lower_CmmExpr_Ptr lbl ptr + pure $ + SomeWasmExpr TagI64 $ + WasmExpr $ + ptr_instr + `WasmConcat` WasmLoad + TagI64 + (wasmMemoryNarrowing TagI64 (cmmBits w0)) + Signed + o + align +lower_MO_SS_Conv lbl w0 w1 [x] + | w0 < w1, + w1 <= W32 = do + x_expr <- lower_CmmExpr_Typed lbl TagI32 x + pure $ + SomeWasmExpr TagI32 $ + truncSubword w1 TagI32 $ + extendSubword w0 TagI32 x_expr + | W32 >= w0, + w0 > w1 = do + x_expr <- lower_CmmExpr_Typed lbl TagI32 x + pure $ SomeWasmExpr TagI32 $ truncSubword w1 TagI32 x_expr +lower_MO_SS_Conv lbl W32 W64 [x] = do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl TagI32 x + pure $ + SomeWasmExpr TagI64 $ + WasmExpr $ + x_instr `WasmConcat` WasmI64ExtendI32 Signed +lower_MO_SS_Conv lbl w0 W64 [x] = do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl TagI32 x + pure $ + SomeWasmExpr TagI64 $ + extendSubword w0 TagI64 $ + WasmExpr $ + x_instr `WasmConcat` WasmI64ExtendI32 Unsigned +lower_MO_SS_Conv lbl W64 w1 [x] = do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl TagI64 x + pure $ + SomeWasmExpr TagI32 $ + truncSubword w1 TagI32 $ + WasmExpr $ + x_instr `WasmConcat` WasmI32WrapI64 +lower_MO_SS_Conv _ _ _ _ = panic "lower_MO_SS_Conv: unreachable" + +-- | Lower a 'MO_UU_Conv' operation. +lower_MO_UU_Conv :: + CLabel -> + Width -> + Width -> + [CmmExpr] -> + WasmCodeGenM + w + (SomeWasmExpr w) +lower_MO_UU_Conv lbl w0 w1 [CmmLoad ptr _ align] = + case someWasmTypeFromCmmType (cmmBits w1) of + SomeWasmType ty -> + SomeWasmExpr ty + <$> lower_CmmLoad_Typed + lbl + ptr + ty + (cmmBits (min w0 w1)) + align +lower_MO_UU_Conv lbl w0 w1 [x] + | w0 == w1 = lower_CmmExpr lbl x + | w0 < w1, w1 <= W32 = lower_CmmExpr lbl x + | W32 >= w0, + w0 > w1 = do + x_expr <- lower_CmmExpr_Typed lbl TagI32 x + pure $ SomeWasmExpr TagI32 $ truncSubword w1 TagI32 x_expr +lower_MO_UU_Conv lbl _ W64 [x] = do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl TagI32 x + pure $ + SomeWasmExpr TagI64 $ + WasmExpr $ + x_instr `WasmConcat` WasmI64ExtendI32 Unsigned +lower_MO_UU_Conv lbl W64 w1 [x] = do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl TagI64 x + pure $ + SomeWasmExpr TagI32 $ + truncSubword w1 TagI32 $ + WasmExpr $ + x_instr `WasmConcat` WasmI32WrapI64 +lower_MO_UU_Conv _ _ _ _ = panic "lower_MO_UU_Conv: unreachable" + +-- | Lower a 'MO_FF_Conv' operation. +lower_MO_FF_Conv :: + CLabel -> + Width -> + Width -> + [CmmExpr] -> + WasmCodeGenM + w + (SomeWasmExpr w) +lower_MO_FF_Conv lbl W32 W64 [x] = do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl TagF32 x + pure $ + SomeWasmExpr TagF64 $ + WasmExpr $ + x_instr `WasmConcat` WasmF64PromoteF32 +lower_MO_FF_Conv lbl W64 W32 [x] = do + WasmExpr x_instr <- lower_CmmExpr_Typed lbl TagF64 x + pure $ + SomeWasmExpr TagF32 $ + WasmExpr $ + x_instr `WasmConcat` WasmF32DemoteF64 +lower_MO_FF_Conv _ _ _ _ = panic "lower_MO_FF_Conv: unreachable" + +-- | Lower a 'CmmMachOp'. +lower_CmmMachOp :: + CLabel -> + MachOp -> + [CmmExpr] -> + WasmCodeGenM + w + (SomeWasmExpr w) +lower_CmmMachOp lbl (MO_Add w0) xs = lower_MO_Bin_Homo_Trunc WasmAdd lbl w0 xs +lower_CmmMachOp lbl (MO_Sub w0) xs = lower_MO_Bin_Homo_Trunc WasmSub lbl w0 xs +lower_CmmMachOp lbl (MO_Eq w0) xs = lower_MO_Bin_Rel WasmEq lbl (cmmBits w0) xs +lower_CmmMachOp lbl (MO_Ne w0) xs = lower_MO_Bin_Rel WasmNe lbl (cmmBits w0) xs +lower_CmmMachOp lbl (MO_Mul w0) xs = lower_MO_Bin_Homo_Trunc WasmMul lbl w0 xs +lower_CmmMachOp lbl (MO_S_MulMayOflo w0) xs = lower_MO_MulMayOflo lbl w0 xs +lower_CmmMachOp lbl (MO_S_Quot w0) xs = + lower_MO_Bin_Homo_Ext_Trunc + (WasmDiv Signed) + lbl + w0 + xs +lower_CmmMachOp lbl (MO_S_Rem w0) xs = + lower_MO_Bin_Homo_Ext_Trunc + (WasmRem Signed) + lbl + w0 + xs +lower_CmmMachOp lbl (MO_S_Neg w0) [x] = + lower_CmmMachOp + lbl + (MO_Sub w0) + [CmmLit $ CmmInt 0 w0, x] +lower_CmmMachOp lbl (MO_U_MulMayOflo w0) xs = lower_MO_MulMayOflo lbl w0 xs +lower_CmmMachOp lbl (MO_U_Quot w0) xs = + lower_MO_Bin_Homo + (WasmDiv Unsigned) + lbl + (cmmBits w0) + xs +lower_CmmMachOp lbl (MO_U_Rem w0) xs = + lower_MO_Bin_Homo + (WasmRem Unsigned) + lbl + (cmmBits w0) + xs +lower_CmmMachOp lbl (MO_S_Ge w0) xs = + lower_MO_Bin_Rel_Ext + (WasmGe Signed) + lbl + w0 + xs +lower_CmmMachOp lbl (MO_S_Le w0) xs = + lower_MO_Bin_Rel_Ext + (WasmLe Signed) + lbl + w0 + xs +lower_CmmMachOp lbl (MO_S_Gt w0) xs = + lower_MO_Bin_Rel_Ext + (WasmGt Signed) + lbl + w0 + xs +lower_CmmMachOp lbl (MO_S_Lt w0) xs = + lower_MO_Bin_Rel_Ext + (WasmLt Signed) + lbl + w0 + xs +lower_CmmMachOp lbl (MO_U_Ge w0) xs = + lower_MO_Bin_Rel + (WasmGe Unsigned) + lbl + (cmmBits w0) + xs +lower_CmmMachOp lbl (MO_U_Le w0) xs = + lower_MO_Bin_Rel + (WasmLe Unsigned) + lbl + (cmmBits w0) + xs +lower_CmmMachOp lbl (MO_U_Gt w0) xs = + lower_MO_Bin_Rel + (WasmGt Unsigned) + lbl + (cmmBits w0) + xs +lower_CmmMachOp lbl (MO_U_Lt w0) xs = + lower_MO_Bin_Rel + (WasmLt Unsigned) + lbl + (cmmBits w0) + xs +lower_CmmMachOp lbl (MO_F_Add w0) xs = + lower_MO_Bin_Homo + WasmAdd + lbl + (cmmFloat w0) + xs +lower_CmmMachOp lbl (MO_F_Sub w0) xs = + lower_MO_Bin_Homo + WasmSub + lbl + (cmmFloat w0) + xs +lower_CmmMachOp lbl (MO_F_Neg w0) [x] = + lower_CmmMachOp + lbl + (MO_F_Sub w0) + [CmmLit $ CmmFloat 0 w0, x] +lower_CmmMachOp lbl (MO_F_Mul w0) xs = + lower_MO_Bin_Homo + WasmMul + lbl + (cmmFloat w0) + xs +lower_CmmMachOp lbl (MO_F_Quot w0) xs = + lower_MO_Bin_Homo + (WasmDiv Signed) + lbl + (cmmFloat w0) + xs +lower_CmmMachOp lbl (MO_F_Eq w0) xs = + lower_MO_Bin_Rel + WasmEq + lbl + (cmmFloat w0) + xs +lower_CmmMachOp lbl (MO_F_Ne w0) xs = + lower_MO_Bin_Rel + WasmNe + lbl + (cmmFloat w0) + xs +lower_CmmMachOp lbl (MO_F_Ge w0) xs = + lower_MO_Bin_Rel + (WasmGe Signed) + lbl + (cmmFloat w0) + xs +lower_CmmMachOp lbl (MO_F_Le w0) xs = + lower_MO_Bin_Rel + (WasmLe Signed) + lbl + (cmmFloat w0) + xs +lower_CmmMachOp lbl (MO_F_Gt w0) xs = + lower_MO_Bin_Rel + (WasmGt Signed) + lbl + (cmmFloat w0) + xs +lower_CmmMachOp lbl (MO_F_Lt w0) xs = + lower_MO_Bin_Rel + (WasmLt Signed) + lbl + (cmmFloat w0) + xs +lower_CmmMachOp lbl (MO_And w0) xs = + lower_MO_Bin_Homo + WasmAnd + lbl + (cmmBits w0) + xs +lower_CmmMachOp lbl (MO_Or w0) xs = lower_MO_Bin_Homo WasmOr lbl (cmmBits w0) xs +lower_CmmMachOp lbl (MO_Xor w0) xs = + lower_MO_Bin_Homo + WasmXor + lbl + (cmmBits w0) + xs +lower_CmmMachOp lbl (MO_Not w0) [x] = + lower_CmmMachOp + lbl + (MO_Xor w0) + [x, CmmLit $ CmmInt (widthMax w0) w0] +lower_CmmMachOp lbl (MO_Shl w0) xs = lower_MO_Shl lbl w0 xs +lower_CmmMachOp lbl (MO_U_Shr w0) xs = lower_MO_U_Shr lbl w0 xs +lower_CmmMachOp lbl (MO_S_Shr w0) xs = lower_MO_S_Shr lbl w0 xs +lower_CmmMachOp lbl (MO_SF_Conv w0 w1) xs = + lower_MO_Un_Conv + (WasmConvert Signed) + lbl + (cmmBits w0) + (cmmFloat w1) + xs +lower_CmmMachOp lbl (MO_FS_Conv w0 w1) xs = + lower_MO_Un_Conv + (WasmTruncSat Signed) + lbl + (cmmFloat w0) + (cmmBits w1) + xs +lower_CmmMachOp lbl (MO_SS_Conv w0 w1) xs = lower_MO_SS_Conv lbl w0 w1 xs +lower_CmmMachOp lbl (MO_UU_Conv w0 w1) xs = lower_MO_UU_Conv lbl w0 w1 xs +lower_CmmMachOp lbl (MO_XX_Conv w0 w1) xs = lower_MO_UU_Conv lbl w0 w1 xs +lower_CmmMachOp lbl (MO_FF_Conv w0 w1) xs = lower_MO_FF_Conv lbl w0 w1 xs +lower_CmmMachOp _ _ _ = panic "lower_CmmMachOp: unreachable" + +-- | Lower a 'CmmLit'. Note that we don't emit 'f32.const' or +-- 'f64.const' for the time being, and instead emit their relative bit +-- pattern as int literals, then use an reinterpret cast. This is +-- simpler than dealing with textual representation of floating point +-- values. +lower_CmmLit :: CmmLit -> WasmCodeGenM w (SomeWasmExpr w) +lower_CmmLit lit = do + ty_word <- wasmWordTypeM + case lit of + CmmInt i w -> case someWasmTypeFromCmmType (cmmBits w) of + SomeWasmType ty -> + pure $ + SomeWasmExpr ty $ + WasmExpr $ + WasmConst ty $ + naturalNarrowing w i + CmmFloat f W32 -> + pure $ + SomeWasmExpr TagF32 $ + WasmExpr $ + WasmConst + TagI32 + (toInteger $ castFloatToWord32 $ fromRational f) + `WasmConcat` WasmReinterpret TagI32 TagF32 + CmmFloat f W64 -> + pure $ + SomeWasmExpr TagF64 $ + WasmExpr $ + WasmConst + TagI64 + (toInteger $ castDoubleToWord64 $ fromRational f) + `WasmConcat` WasmReinterpret TagI64 TagF64 + CmmLabel lbl' -> do + onAnySym lbl' + let sym = symNameFromCLabel lbl' + pure $ SomeWasmExpr ty_word $ WasmExpr $ WasmSymConst sym + CmmLabelOff lbl' o -> do + onAnySym lbl' + let sym = symNameFromCLabel lbl' + pure $ + SomeWasmExpr ty_word $ + WasmExpr $ + WasmSymConst sym + `WasmConcat` WasmConst ty_word (toInteger o) + `WasmConcat` WasmAdd ty_word + _ -> panic "lower_CmmLit: unreachable" + +-- | Lower a 'CmmReg'. Some of the logic here wouldn't be needed if +-- we have run 'fixStgRegisters' on the wasm NCG's input Cmm, but we +-- haven't run it yet for certain reasons. +lower_CmmReg :: CLabel -> CmmReg -> WasmCodeGenM w (SomeWasmExpr w) +lower_CmmReg _ (CmmLocal reg) = do + (reg_i, SomeWasmType ty) <- onCmmLocalReg reg + pure $ SomeWasmExpr ty $ WasmExpr $ WasmLocalGet ty reg_i +lower_CmmReg _ (CmmGlobal EagerBlackholeInfo) = do + ty_word <- wasmWordTypeM + pure $ + SomeWasmExpr ty_word $ + WasmExpr $ + WasmSymConst "stg_EAGER_BLACKHOLE_info" +lower_CmmReg _ (CmmGlobal GCEnter1) = do + ty_word <- wasmWordTypeM + ty_word_cmm <- wasmWordCmmTypeM + onFuncSym "__stg_gc_enter_1" [] [ty_word_cmm] + pure $ SomeWasmExpr ty_word $ WasmExpr $ WasmSymConst "__stg_gc_enter_1" +lower_CmmReg _ (CmmGlobal GCFun) = do + ty_word <- wasmWordTypeM + ty_word_cmm <- wasmWordCmmTypeM + onFuncSym "__stg_gc_fun" [] [ty_word_cmm] + pure $ SomeWasmExpr ty_word $ WasmExpr $ WasmSymConst "__stg_gc_fun" +lower_CmmReg lbl (CmmGlobal BaseReg) = do + platform <- wasmPlatformM + lower_CmmExpr lbl $ regTableOffset platform 0 +lower_CmmReg lbl (CmmGlobal reg) = do + ty_word <- wasmWordTypeM + if + | Just (sym_global, SomeWasmType ty) <- + globalInfoFromCmmGlobalReg ty_word reg -> + pure $ SomeWasmExpr ty $ WasmExpr $ WasmGlobalGet ty sym_global + | otherwise -> do + platform <- wasmPlatformM + case someWasmTypeFromCmmType $ globalRegType platform reg of + SomeWasmType ty -> do + (WasmExpr ptr_instr, o) <- + lower_CmmExpr_Ptr lbl $ + get_GlobalReg_addr platform reg + pure $ + SomeWasmExpr ty $ + WasmExpr $ + ptr_instr + `WasmConcat` WasmLoad + ty + Nothing + Unsigned + o + NaturallyAligned + +-- | Lower a 'CmmRegOff'. +lower_CmmRegOff :: CLabel -> CmmReg -> Int -> WasmCodeGenM w (SomeWasmExpr w) +lower_CmmRegOff lbl reg 0 = lower_CmmReg lbl reg +lower_CmmRegOff lbl reg o = do + SomeWasmExpr ty (WasmExpr reg_instr) <- lower_CmmReg lbl reg + pure $ + SomeWasmExpr ty $ + WasmExpr $ + reg_instr + `WasmConcat` WasmConst + ty + (toInteger o) + `WasmConcat` WasmAdd ty + +-- | Lower a 'CmmLoad', passing in the expected wasm representation +-- type, and also the Cmm type (which contains width info needed for +-- memory narrowing). +-- +-- The Cmm type system doesn't track signedness, so all 'CmmLoad's are +-- unsigned loads. However, as an optimization, we do emit signed +-- loads when a 'CmmLoad' result is immediately used as a 'MO_SS_Conv' +-- operand. +lower_CmmLoad_Typed :: + CLabel -> + CmmExpr -> + WasmTypeTag t -> + CmmType -> + AlignmentSpec -> + WasmCodeGenM w (WasmExpr w t) +lower_CmmLoad_Typed lbl ptr_expr ty ty_cmm align = do + (WasmExpr ptr_instr, o) <- lower_CmmExpr_Ptr lbl ptr_expr + pure $ + WasmExpr $ + ptr_instr + `WasmConcat` WasmLoad + ty + (wasmMemoryNarrowing ty ty_cmm) + Unsigned + o + align + +-- | Lower a 'CmmLoad'. +lower_CmmLoad :: + CLabel -> + CmmExpr -> + CmmType -> + AlignmentSpec -> + WasmCodeGenM + w + (SomeWasmExpr w) +lower_CmmLoad lbl ptr_expr ty_cmm align = case someWasmTypeFromCmmType ty_cmm of + SomeWasmType ty -> + SomeWasmExpr ty <$> lower_CmmLoad_Typed lbl ptr_expr ty ty_cmm align + +-- | Lower a 'CmmExpr'. +lower_CmmExpr :: CLabel -> CmmExpr -> WasmCodeGenM w (SomeWasmExpr w) +lower_CmmExpr lbl expr = case expr of + CmmLit lit -> lower_CmmLit lit + CmmLoad ptr_expr ty_cmm align -> lower_CmmLoad lbl ptr_expr ty_cmm align + CmmReg reg -> lower_CmmReg lbl reg + CmmRegOff reg o -> lower_CmmRegOff lbl reg o + CmmMachOp op xs -> lower_CmmMachOp lbl op xs + _ -> panic "lower_CmmExpr: unreachable" + +-- | Lower a 'CmmExpr', passing in the expected wasm representation +-- type. +lower_CmmExpr_Typed :: + CLabel -> + WasmTypeTag t -> + CmmExpr -> + WasmCodeGenM + w + (WasmExpr w t) +lower_CmmExpr_Typed lbl ty expr = do + SomeWasmExpr ty' r <- lower_CmmExpr lbl expr + if + | Just Refl <- ty' `testEquality` ty -> pure r + | otherwise -> panic "lower_CmmExpr_Typed: unreachable" + +-- | Lower a 'CmmExpr' as a pointer, returning the pair of base +-- pointer and non-negative offset. +lower_CmmExpr_Ptr :: CLabel -> CmmExpr -> WasmCodeGenM w (WasmExpr w w, Int) +lower_CmmExpr_Ptr lbl ptr = do + ty_word <- wasmWordTypeM + case ptr of + CmmLit (CmmLabelOff lbl o) + | o >= 0 -> do + instrs <- + lower_CmmExpr_Typed + lbl + ty_word + (CmmLit $ CmmLabel lbl) + pure (instrs, o) + CmmMachOp (MO_Add _) [base, CmmLit (CmmInt o _)] + | o >= 0 -> do + instrs <- lower_CmmExpr_Typed lbl ty_word base + pure (instrs, fromInteger o) + _ -> do + instrs <- lower_CmmExpr_Typed lbl ty_word ptr + pure (instrs, 0) + +-- | Push a series of values onto the wasm value stack, returning the +-- result stack type. +type family + WasmPushes (ts :: [WasmType]) (pre :: [WasmType]) :: + [WasmType] + where + WasmPushes '[] pre = pre + WasmPushes (t : ts) pre = WasmPushes ts (t : pre) + +-- | Push the arguments onto the wasm value stack before a ccall. +data SomeWasmPreCCall w where + SomeWasmPreCCall :: + TypeList ts -> + (forall pre. WasmInstr w pre (WasmPushes ts pre)) -> + SomeWasmPreCCall w + +-- | Pop the results into locals after a ccall. +data SomeWasmPostCCall w where + SomeWasmPostCCall :: + TypeList ts -> + (forall post. WasmInstr w (WasmPushes ts post) post) -> + SomeWasmPostCCall w + +-- | Lower an unary homogeneous 'CallishMachOp' to a ccall. +lower_CMO_Un_Homo :: + CLabel -> + SymName -> + [CmmFormal] -> + [CmmActual] -> + WasmCodeGenM w (WasmStatements w) +lower_CMO_Un_Homo lbl op [reg] [x] = do + (ri, SomeWasmType ty) <- onCmmLocalReg reg + WasmExpr x_instr <- lower_CmmExpr_Typed lbl ty x + let ty_cmm = localRegType reg + onFuncSym op [ty_cmm] [ty_cmm] + pure $ + WasmStatements $ + x_instr `WasmConcat` WasmCCall op `WasmConcat` WasmLocalSet ty ri +lower_CMO_Un_Homo _ _ _ _ = panic "lower_CMO_Un_Homo: unreachable" + +-- | Lower an unary homogeneous 'CallishMachOp' to inline assembly. +lower_CMO_Un_Prim :: + CLabel -> + (forall pre t. WasmTypeTag t -> WasmInstr w (t : pre) (t : pre)) -> + [CmmFormal] -> + [CmmActual] -> + WasmCodeGenM w (WasmStatements w) +lower_CMO_Un_Prim lbl op [reg] [x] = do + (ri, SomeWasmType ty) <- onCmmLocalReg reg + SomeWasmExpr ty_x (WasmExpr x_instr) <- lower_CmmExpr lbl x + if + | Just Refl <- ty `testEquality` ty_x -> + pure $ + WasmStatements $ + x_instr `WasmConcat` op ty_x `WasmConcat` WasmLocalSet ty ri + | TagI32 <- ty, + TagI64 <- + ty_x -> + pure $ + WasmStatements $ + x_instr + `WasmConcat` op ty_x + `WasmConcat` WasmI32WrapI64 + `WasmConcat` WasmLocalSet ty ri + | otherwise -> panic "lower_CMO_Un_Prim: unreachable" +lower_CMO_Un_Prim _ _ _ _ = panic "lower_CMO_Un_Prim: unreachable" + +-- | Lower a binary homogeneous 'CallishMachOp' to a ccall. +lower_CMO_Bin_Homo :: + CLabel -> + SymName -> + [CmmFormal] -> + [CmmActual] -> + WasmCodeGenM w (WasmStatements w) +lower_CMO_Bin_Homo lbl op [reg] [x, y] = do + (ri, SomeWasmType ty) <- onCmmLocalReg reg + WasmExpr x_instr <- lower_CmmExpr_Typed lbl ty x + WasmExpr y_instr <- lower_CmmExpr_Typed lbl ty y + let ty_cmm = localRegType reg + onFuncSym op [ty_cmm, ty_cmm] [ty_cmm] + pure $ + WasmStatements $ + x_instr + `WasmConcat` y_instr + `WasmConcat` WasmCCall op + `WasmConcat` WasmLocalSet ty ri +lower_CMO_Bin_Homo _ _ _ _ = panic "lower_CMO_Bin_Homo: unreachable" + +-- | Lower a 'MO_UF_Conv' operation. +lower_MO_UF_Conv :: + CLabel -> + Width -> + [CmmFormal] -> + [CmmActual] -> + WasmCodeGenM w (WasmStatements w) +lower_MO_UF_Conv lbl W32 [reg] [x] = do + ri <- onCmmLocalReg_Typed TagF32 reg + SomeWasmExpr ty0 (WasmExpr x_instr) <- lower_CmmExpr lbl x + pure $ + WasmStatements $ + x_instr + `WasmConcat` WasmConvert Unsigned ty0 TagF32 + `WasmConcat` WasmLocalSet TagF32 ri +lower_MO_UF_Conv lbl W64 [reg] [x] = do + ri <- onCmmLocalReg_Typed TagF64 reg + SomeWasmExpr ty0 (WasmExpr x_instr) <- lower_CmmExpr lbl x + pure $ + WasmStatements $ + x_instr + `WasmConcat` WasmConvert Unsigned ty0 TagF64 + `WasmConcat` WasmLocalSet TagF64 ri +lower_MO_UF_Conv _ _ _ _ = panic "lower_MO_UF_Conv: unreachable" + +-- | Lower a 'MO_Cmpxchg' operation to inline assembly. Currently we +-- target wasm without atomics and threads, so it's just lowered to +-- regular memory loads and stores. +lower_MO_Cmpxchg :: + CLabel -> + Width -> + [CmmFormal] -> + [CmmActual] -> + WasmCodeGenM w (WasmStatements w) +lower_MO_Cmpxchg lbl w0 [reg] [ptr, expected, new] = + case someWasmTypeFromCmmType ty_cmm of + SomeWasmType ty -> do + reg_i <- onCmmLocalReg_Typed ty reg + let narrowing = wasmMemoryNarrowing ty ty_cmm + (WasmExpr ptr_instr, o) <- lower_CmmExpr_Ptr lbl ptr + WasmExpr expected_instr <- lower_CmmExpr_Typed lbl ty expected + WasmExpr new_instr <- lower_CmmExpr_Typed lbl ty new + pure $ + WasmStatements $ + ptr_instr + `WasmConcat` WasmLoad ty narrowing Unsigned o NaturallyAligned + `WasmConcat` WasmLocalTee ty reg_i + `WasmConcat` expected_instr + `WasmConcat` WasmEq ty + `WasmConcat` WasmCond + ( ptr_instr + `WasmConcat` new_instr + `WasmConcat` WasmStore ty narrowing o NaturallyAligned + ) + where + ty_cmm = cmmBits w0 +lower_MO_Cmpxchg _ _ _ _ = panic "lower_MO_Cmpxchg: unreachable" + +-- | Lower a 'CallishMachOp'. +lower_CallishMachOp :: + CLabel -> + CallishMachOp -> + [CmmFormal] -> + [CmmActual] -> + WasmCodeGenM w (WasmStatements w) +lower_CallishMachOp lbl MO_F64_Pwr rs xs = lower_CMO_Bin_Homo lbl "pow" rs xs +lower_CallishMachOp lbl MO_F64_Sin rs xs = lower_CMO_Un_Homo lbl "sin" rs xs +lower_CallishMachOp lbl MO_F64_Cos rs xs = lower_CMO_Un_Homo lbl "cos" rs xs +lower_CallishMachOp lbl MO_F64_Tan rs xs = lower_CMO_Un_Homo lbl "tan" rs xs +lower_CallishMachOp lbl MO_F64_Sinh rs xs = lower_CMO_Un_Homo lbl "sinh" rs xs +lower_CallishMachOp lbl MO_F64_Cosh rs xs = lower_CMO_Un_Homo lbl "cosh" rs xs +lower_CallishMachOp lbl MO_F64_Tanh rs xs = lower_CMO_Un_Homo lbl "tanh" rs xs +lower_CallishMachOp lbl MO_F64_Asin rs xs = lower_CMO_Un_Homo lbl "asin" rs xs +lower_CallishMachOp lbl MO_F64_Acos rs xs = lower_CMO_Un_Homo lbl "acos" rs xs +lower_CallishMachOp lbl MO_F64_Atan rs xs = lower_CMO_Un_Homo lbl "atan" rs xs +lower_CallishMachOp lbl MO_F64_Asinh rs xs = lower_CMO_Un_Homo lbl "asinh" rs xs +lower_CallishMachOp lbl MO_F64_Acosh rs xs = lower_CMO_Un_Homo lbl "acosh" rs xs +lower_CallishMachOp lbl MO_F64_Atanh rs xs = lower_CMO_Un_Homo lbl "atanh" rs xs +lower_CallishMachOp lbl MO_F64_Log rs xs = lower_CMO_Un_Homo lbl "log" rs xs +lower_CallishMachOp lbl MO_F64_Log1P rs xs = lower_CMO_Un_Homo lbl "log1p" rs xs +lower_CallishMachOp lbl MO_F64_Exp rs xs = lower_CMO_Un_Homo lbl "exp" rs xs +lower_CallishMachOp lbl MO_F64_ExpM1 rs xs = lower_CMO_Un_Homo lbl "expm1" rs xs +lower_CallishMachOp lbl MO_F64_Fabs rs xs = lower_CMO_Un_Homo lbl "fabs" rs xs +lower_CallishMachOp lbl MO_F64_Sqrt rs xs = lower_CMO_Un_Homo lbl "sqrt" rs xs +lower_CallishMachOp lbl MO_F32_Pwr rs xs = lower_CMO_Bin_Homo lbl "powf" rs xs +lower_CallishMachOp lbl MO_F32_Sin rs xs = lower_CMO_Un_Homo lbl "sinf" rs xs +lower_CallishMachOp lbl MO_F32_Cos rs xs = lower_CMO_Un_Homo lbl "cosf" rs xs +lower_CallishMachOp lbl MO_F32_Tan rs xs = lower_CMO_Un_Homo lbl "tanf" rs xs +lower_CallishMachOp lbl MO_F32_Sinh rs xs = lower_CMO_Un_Homo lbl "sinhf" rs xs +lower_CallishMachOp lbl MO_F32_Cosh rs xs = lower_CMO_Un_Homo lbl "coshf" rs xs +lower_CallishMachOp lbl MO_F32_Tanh rs xs = lower_CMO_Un_Homo lbl "tanhf" rs xs +lower_CallishMachOp lbl MO_F32_Asin rs xs = lower_CMO_Un_Homo lbl "asinf" rs xs +lower_CallishMachOp lbl MO_F32_Acos rs xs = lower_CMO_Un_Homo lbl "acosf" rs xs +lower_CallishMachOp lbl MO_F32_Atan rs xs = lower_CMO_Un_Homo lbl "atanf" rs xs +lower_CallishMachOp lbl MO_F32_Asinh rs xs = + lower_CMO_Un_Homo lbl "asinhf" rs xs +lower_CallishMachOp lbl MO_F32_Acosh rs xs = + lower_CMO_Un_Homo lbl "acoshf" rs xs +lower_CallishMachOp lbl MO_F32_Atanh rs xs = + lower_CMO_Un_Homo lbl "atanhf" rs xs +lower_CallishMachOp lbl MO_F32_Log rs xs = lower_CMO_Un_Homo lbl "logf" rs xs +lower_CallishMachOp lbl MO_F32_Log1P rs xs = + lower_CMO_Un_Homo lbl "log1pf" rs xs +lower_CallishMachOp lbl MO_F32_Exp rs xs = lower_CMO_Un_Homo lbl "expf" rs xs +lower_CallishMachOp lbl MO_F32_ExpM1 rs xs = + lower_CMO_Un_Homo lbl "expm1f" rs xs +lower_CallishMachOp lbl MO_F32_Fabs rs xs = lower_CMO_Un_Homo lbl "fabsf" rs xs +lower_CallishMachOp lbl MO_F32_Sqrt rs xs = lower_CMO_Un_Homo lbl "sqrtf" rs xs +lower_CallishMachOp lbl (MO_UF_Conv w0) rs xs = lower_MO_UF_Conv lbl w0 rs xs +lower_CallishMachOp _ MO_ReadBarrier _ _ = pure $ WasmStatements WasmNop +lower_CallishMachOp _ MO_WriteBarrier _ _ = pure $ WasmStatements WasmNop +lower_CallishMachOp _ MO_Touch _ _ = pure $ WasmStatements WasmNop +lower_CallishMachOp _ (MO_Prefetch_Data {}) _ _ = pure $ WasmStatements WasmNop +lower_CallishMachOp lbl (MO_Memcpy {}) [] xs = do + ty_word_cmm <- wasmWordCmmTypeM + lower_CmmUnsafeForeignCall_Drop lbl "memcpy" ty_word_cmm xs +lower_CallishMachOp lbl (MO_Memset {}) [] xs = do + ty_word_cmm <- wasmWordCmmTypeM + lower_CmmUnsafeForeignCall_Drop lbl "memset" ty_word_cmm xs +lower_CallishMachOp lbl (MO_Memmove {}) [] xs = do + ty_word_cmm <- wasmWordCmmTypeM + lower_CmmUnsafeForeignCall_Drop lbl "memmove" ty_word_cmm xs +lower_CallishMachOp lbl (MO_Memcmp {}) rs xs = + lower_CmmUnsafeForeignCall + lbl + (Left "memcmp") + CmmMayReturn + rs + xs +lower_CallishMachOp lbl (MO_PopCnt {}) rs xs = + lower_CMO_Un_Prim lbl WasmPopcnt rs xs +lower_CallishMachOp lbl (MO_Pdep w0) rs xs = + lower_CmmUnsafeForeignCall + lbl + (Left $ fromString $ "hs_pdep" <> show (widthInBits w0)) + CmmMayReturn + rs + xs +lower_CallishMachOp lbl (MO_Pext w0) rs xs = + lower_CmmUnsafeForeignCall + lbl + (Left $ fromString $ "hs_pext" <> show (widthInBits w0)) + CmmMayReturn + rs + xs +lower_CallishMachOp lbl (MO_Clz {}) rs xs = lower_CMO_Un_Prim lbl WasmClz rs xs +lower_CallishMachOp lbl (MO_Ctz {}) rs xs = lower_CMO_Un_Prim lbl WasmCtz rs xs +lower_CallishMachOp lbl (MO_BSwap w0) rs xs = + lower_CmmUnsafeForeignCall + lbl + (Left $ fromString $ "hs_bswap" <> show (widthInBits w0)) + CmmMayReturn + rs + xs +lower_CallishMachOp lbl (MO_BRev w0) rs xs = + lower_CmmUnsafeForeignCall + lbl + (Left $ fromString $ "hs_bitrev" <> show (widthInBits w0)) + CmmMayReturn + rs + xs +lower_CallishMachOp lbl (MO_AtomicRMW w0 op) rs xs = + lower_CmmUnsafeForeignCall + lbl + ( Left $ + fromString $ + ( case op of + AMO_Add -> "hs_atomic_add" + AMO_Sub -> "hs_atomic_sub" + AMO_And -> "hs_atomic_and" + AMO_Nand -> "hs_atomic_nand" + AMO_Or -> "hs_atomic_or" + AMO_Xor -> "hs_atomic_xor" + ) + <> show (widthInBits w0) + ) + CmmMayReturn + rs + xs +lower_CallishMachOp lbl (MO_AtomicRead w0) [reg] [ptr] = do + SomeWasmExpr ty (WasmExpr ret_instr) <- + lower_CmmLoad + lbl + ptr + (cmmBits w0) + NaturallyAligned + ri <- onCmmLocalReg_Typed ty reg + pure $ WasmStatements $ ret_instr `WasmConcat` WasmLocalSet ty ri +lower_CallishMachOp lbl (MO_AtomicWrite _) [] [ptr, val] = + lower_CmmStore lbl ptr val NaturallyAligned +lower_CallishMachOp lbl (MO_Cmpxchg w0) rs xs = lower_MO_Cmpxchg lbl w0 rs xs +lower_CallishMachOp lbl (MO_Xchg w0) rs xs = + lower_CmmUnsafeForeignCall + lbl + (Left $ fromString $ "hs_xchg" <> show (widthInBits w0)) + CmmMayReturn + rs + xs +lower_CallishMachOp lbl MO_SuspendThread rs xs = + lower_CmmUnsafeForeignCall + lbl + (Left "suspendThread") + CmmMayReturn + rs + xs +lower_CallishMachOp lbl MO_ResumeThread rs xs = + lower_CmmUnsafeForeignCall + lbl + (Left "resumeThread") + CmmMayReturn + rs + xs +lower_CallishMachOp _ _ _ _ = panic "lower_CallishMachOp: unreachable" + +-- | Lower a ccall, but drop the result by assigning it to an unused +-- local. This is only used for lowering 'MO_Memcpy' and such, where +-- the libc functions do have a return value, but the corresponding +-- 'CallishMachOp' does not expect one. +lower_CmmUnsafeForeignCall_Drop :: + CLabel -> + SymName -> + CmmType -> + [CmmActual] -> + WasmCodeGenM w (WasmStatements w) +lower_CmmUnsafeForeignCall_Drop lbl sym_callee ret_cmm_ty arg_exprs = do + ret_uniq <- wasmUniq + let ret_local = LocalReg ret_uniq ret_cmm_ty + lower_CmmUnsafeForeignCall + lbl + (Left sym_callee) + CmmMayReturn + [ret_local] + arg_exprs + +-- | Lower a 'CmmUnsafeForeignCall'. The target is 'Either' a symbol, +-- which translates to a direct @call@, or an expression, which +-- translates to a @call_indirect@. The callee function signature is +-- inferred from the passed in arguments here. +lower_CmmUnsafeForeignCall :: + CLabel -> + (Either SymName CmmExpr) -> + CmmReturnInfo -> + [CmmFormal] -> + [CmmActual] -> + WasmCodeGenM + w + (WasmStatements w) +lower_CmmUnsafeForeignCall lbl target ret_info ret_locals arg_exprs = do + SomeWasmPreCCall arg_tys args_instr <- + foldrM + ( \arg_expr (SomeWasmPreCCall acc_tys acc_instr) -> do + SomeWasmExpr arg_ty (WasmExpr arg_instr) <- + lower_CmmExpr lbl arg_expr + pure $ + SomeWasmPreCCall (arg_ty `TypeListCons` acc_tys) $ + arg_instr `WasmConcat` acc_instr + ) + (SomeWasmPreCCall TypeListNil WasmNop) + arg_exprs + SomeWasmPostCCall ret_tys ret_instr <- + foldrM + ( \reg (SomeWasmPostCCall acc_tys acc_instr) -> do + (reg_i, SomeWasmType reg_ty) <- onCmmLocalReg reg + pure $ + SomeWasmPostCCall (reg_ty `TypeListCons` acc_tys) $ + acc_instr `WasmConcat` WasmLocalSet reg_ty reg_i + ) + (SomeWasmPostCCall TypeListNil WasmNop) + ret_locals + case target of + Left sym_callee -> do + platform <- wasmPlatformM + let arg_cmm_tys = map (cmmExprType platform) arg_exprs + ret_cmm_tys = map localRegType ret_locals + onFuncSym sym_callee arg_cmm_tys ret_cmm_tys + pure $ + WasmStatements $ + args_instr + `WasmConcat` WasmCCall sym_callee + `WasmConcat` ( case ret_info of + CmmMayReturn -> ret_instr + CmmNeverReturns -> WasmUnreachable + ) + Right fptr_callee -> do + (WasmExpr instr_callee, _) <- lower_CmmExpr_Ptr lbl fptr_callee + pure $ + WasmStatements $ + args_instr + `WasmConcat` instr_callee + `WasmConcat` WasmCCallIndirect arg_tys ret_tys + `WasmConcat` ( case ret_info of + CmmMayReturn -> ret_instr + CmmNeverReturns -> WasmUnreachable + ) + +-- | Lower a 'CmmStore'. +lower_CmmStore :: + CLabel -> + CmmExpr -> + CmmExpr -> + AlignmentSpec -> + WasmCodeGenM + w + (WasmStatements w) +lower_CmmStore lbl ptr val align = do + platform <- wasmPlatformM + (WasmExpr ptr_instr, o) <- lower_CmmExpr_Ptr lbl ptr + let ty_cmm = cmmExprType platform val + SomeWasmExpr ty (WasmExpr val_instr) <- lower_CmmExpr lbl val + pure $ + WasmStatements $ + ptr_instr + `WasmConcat` val_instr + `WasmConcat` WasmStore ty (wasmMemoryNarrowing ty ty_cmm) o align + +-- | Lower a single Cmm action. +lower_CmmAction :: CLabel -> CmmNode O O -> WasmCodeGenM w (WasmStatements w) +lower_CmmAction lbl act = do + ty_word <- wasmWordTypeM + platform <- wasmPlatformM + case act of + CmmComment {} -> pure $ WasmStatements WasmNop + CmmTick {} -> pure $ WasmStatements WasmNop + CmmUnwind {} -> pure $ WasmStatements WasmNop + CmmAssign (CmmLocal reg) e -> do + (i, SomeWasmType ty_reg) <- onCmmLocalReg reg + WasmExpr instrs <- lower_CmmExpr_Typed lbl ty_reg e + pure $ WasmStatements $ instrs `WasmConcat` WasmLocalSet ty_reg i + CmmAssign (CmmGlobal reg) e + | BaseReg <- reg -> pure $ WasmStatements WasmNop + | Just (sym_global, SomeWasmType ty_reg) <- + globalInfoFromCmmGlobalReg ty_word reg -> do + WasmExpr instrs <- lower_CmmExpr_Typed lbl ty_reg e + pure $ + WasmStatements $ + instrs `WasmConcat` WasmGlobalSet ty_reg sym_global + | otherwise -> do + (WasmExpr ptr_instr, o) <- + lower_CmmExpr_Ptr lbl $ get_GlobalReg_addr platform reg + SomeWasmExpr ty_e (WasmExpr instrs) <- lower_CmmExpr lbl e + pure $ + WasmStatements $ + ptr_instr + `WasmConcat` instrs + `WasmConcat` WasmStore ty_e Nothing o NaturallyAligned + CmmStore ptr val align -> lower_CmmStore lbl ptr val align + CmmUnsafeForeignCall + ( ForeignTarget + (CmmLit (CmmLabel lbl_callee)) + (ForeignConvention conv _ _ ret_info) + ) + ret_locals + arg_exprs + | conv `elem` [CCallConv, CApiConv] -> + lower_CmmUnsafeForeignCall + lbl + (Left $ symNameFromCLabel lbl_callee) + ret_info + ret_locals + arg_exprs + CmmUnsafeForeignCall + (ForeignTarget target_expr (ForeignConvention conv _ _ ret_info)) + ret_locals + arg_exprs + | conv `elem` [CCallConv, CApiConv] -> + lower_CmmUnsafeForeignCall + lbl + (Right target_expr) + ret_info + ret_locals + arg_exprs + CmmUnsafeForeignCall (PrimTarget op) ret_locals arg_exprs -> + lower_CallishMachOp lbl op ret_locals arg_exprs + _ -> panic "lower_CmmAction: unreachable" + +-- | Lower a block of Cmm actions. +lower_CmmActions :: + CLabel -> + Label -> + Block CmmNode O O -> + WasmCodeGenM + w + (WasmStatements w) +lower_CmmActions lbl _ blk = + foldlM + ( \(WasmStatements acc) act -> + (\(WasmStatements stmts) -> WasmStatements $ acc `WasmConcat` stmts) + <$> lower_CmmAction lbl act + ) + (WasmStatements WasmNop) + acts + where + acts = blockToList blk + +-- | Lower a 'CmmGraph'. +lower_CmmGraph :: CLabel -> CmmGraph -> WasmCodeGenM w (FuncBody w) +lower_CmmGraph lbl g = do + ty_word <- wasmWordTypeM + platform <- wasmPlatformM + body <- + structuredControl + platform + (\_ -> lower_CmmExpr_Typed lbl ty_word) + (lower_CmmActions lbl) + g + locals <- wasmStateM $ \s -> + (# + map snd $ detEltsUFM $ localRegs s, + s {localRegs = emptyUFM, localRegsCount = 0} + #) + pure FuncBody {funcLocals = locals, funcBody = wasmControlCast $ body} + +-- | Invoked once for each 'CLabel' which indexes a 'CmmData' or +-- 'CmmProc'. +onTopSym :: CLabel -> WasmCodeGenM w () +onTopSym lbl = case sym_vis of + SymDefault -> wasmModifyM $ \s -> + s + { defaultSyms = + IS.insert + (getKey $ getUnique sym) + $ defaultSyms s + } + _ -> pure () + where + sym = symNameFromCLabel lbl + + sym_vis = symVisibilityFromCLabel lbl + +-- | Invoked for each function 'CLabel' with known type (e.g. a +-- 'CmmProc', or callee of 'CmmUnsafeForeignCall'). +onFuncSym :: SymName -> [CmmType] -> [CmmType] -> WasmCodeGenM w () +onFuncSym sym arg_tys ret_tys = wasmModifyM $ + \s@WasmCodeGenState {..} -> + s + { funcTypes = + addToUniqMap + funcTypes + sym + ( map someWasmTypeFromCmmType arg_tys, + map someWasmTypeFromCmmType ret_tys + ) + } + +-- | Invoked for all other 'CLabel's along the way, e.g. in +-- 'CmmStatic's or 'CmmExpr's. +onAnySym :: CLabel -> WasmCodeGenM w () +onAnySym lbl = case sym_kind of + SymFunc -> wasmModifyM $ \s@WasmCodeGenState {..} -> + s {funcTypes = addToUniqMap_C const funcTypes sym ([], [])} + _ -> pure () + where + sym = symNameFromCLabel lbl + + sym_kind = symKindFromCLabel lbl + +-- | Invoked for each 'LocalReg', returning its wasm local id and +-- representation type. +onCmmLocalReg :: LocalReg -> WasmCodeGenM w LocalInfo +onCmmLocalReg reg = wasmStateM $ \s@WasmCodeGenState {..} -> + let reg_info = + (localRegsCount, someWasmTypeFromCmmType $ localRegType reg) + in case addToUFM_L (\_ i _ -> i) reg reg_info localRegs of + (Just i, _) -> (# i, s #) + (_, localRegs') -> + (# + reg_info, + s + { localRegs = localRegs', + localRegsCount = + localRegsCount + 1 + } + #) + +-- | Invoked for each 'LocalReg' with expected representation type, +-- only returning its wasm local id. +onCmmLocalReg_Typed :: WasmTypeTag t -> LocalReg -> WasmCodeGenM w Int +onCmmLocalReg_Typed ty reg = do + (i, SomeWasmType ty') <- onCmmLocalReg reg + if + | Just Refl <- ty' `testEquality` ty -> pure i + | otherwise -> panic "onCmmLocalReg_Typed: unreachable" + +-- | Invoked for dtors. We don't bother to implement dtors yet; +-- there's no native @.fini_array@ support for wasm, and the way +-- @clang@ handles dtors is generating a ctor that calls @atexit()@ +-- for dtors. Which makes some sense, but we don't need to do the same +-- thing yet. +onFini :: [SymName] -> WasmCodeGenM w () +onFini syms = do + let n_finis = length syms + when (n_finis /= 0) $ panic "dtors unsupported by wasm32 NCG" + +-- | Invoked for ctors and dtors. +onCmmInitFini :: InitOrFini -> [CLabel] -> WasmCodeGenM w () +onCmmInitFini iof lbls = do + for_ lbls $ \lbl -> onFuncSym (symNameFromCLabel lbl) [] [] + case iof of + IsInitArray -> wasmModifyM $ \s -> s {ctors = syms <> ctors s} + IsFiniArray -> onFini syms + where + syms = map symNameFromCLabel lbls + +-- | Invoked for each data section. +onCmmData :: CLabel -> Section -> [CmmStatic] -> WasmCodeGenM w () +onCmmData lbl s statics = do + ty_word <- wasmWordTypeM + onTopSym lbl + cs <- for statics lower_CmmStatic + let sym = symNameFromCLabel lbl + sec = + DataSection + { dataSectionKind = + dataSectionKindFromCmmSection s, + dataSectionAlignment = + alignmentFromCmmSection ty_word lbl, + dataSectionContents = + case cs of + [DataASCII buf] -> [DataASCII $ buf `BS.snoc` 0] + [DataIncBin p l] -> [DataIncBin p l, DataI8 0] + _ -> cs + } + wasmModifyM $ \s -> + s + { dataSections = + addToUniqMap (dataSections s) sym sec + } + +-- | Invoked for each 'CmmProc'. +onCmmProc :: CLabel -> CmmGraph -> WasmCodeGenM w () +onCmmProc lbl g = do + ty_word <- wasmWordCmmTypeM + onTopSym lbl + onFuncSym sym [] [ty_word] + body <- lower_CmmGraph lbl g + wasmModifyM $ \s -> s {funcBodies = addToUniqMap (funcBodies s) sym body} + where + sym = symNameFromCLabel lbl + +-- | Invoked for each 'RawCmmDecl'. +onCmmDecl :: RawCmmDecl -> WasmCodeGenM w () +onCmmDecl decl + | Just (iof, lbls) <- isInitOrFiniArray decl = onCmmInitFini iof lbls +onCmmDecl (CmmData s (CmmStaticsRaw lbl statics)) = onCmmData lbl s statics +onCmmDecl (CmmProc _ lbl _ g) = onCmmProc lbl g + +-- | Invoked for each 'RawCmmGroup'. +onCmmGroup :: RawCmmGroup -> WasmCodeGenM w () +onCmmGroup cmms = wasmStateM $ \s0 -> + (# (), foldl' (\s cmm -> wasmExecM (onCmmDecl cmm) s) s0 cmms #) diff --git a/compiler/GHC/CmmToAsm/Wasm/Types.hs b/compiler/GHC/CmmToAsm/Wasm/Types.hs new file mode 100644 index 0000000000..ab052dc353 --- /dev/null +++ b/compiler/GHC/CmmToAsm/Wasm/Types.hs @@ -0,0 +1,455 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE Strict #-} +{-# LANGUAGE TypeFamilyDependencies #-} +{-# LANGUAGE UnboxedTuples #-} +{-# LANGUAGE UndecidableInstances #-} + +module GHC.CmmToAsm.Wasm.Types + ( WasmType (..), + WasmTypeTag (..), + SomeWasmType (..), + TypeList (..), + someWasmTypesFromTypeList, + WasmFunctionType (..), + SymName (..), + SymVisibility (..), + SymKind (..), + DataSectionKind (..), + DataSectionContent (..), + DataSection (..), + GlobalInfo, + LocalInfo, + FuncBody (..), + Signage (..), + WasmInstr (..), + WasmExpr (..), + SomeWasmExpr (..), + WasmStatements (..), + WasmControl (..), + BrTableInterval (..), + wasmControlCast, + WasmCodeGenState (..), + initialWasmCodeGenState, + WasmCodeGenM (..), + wasmGetsM, + wasmPlatformM, + wasmWordTypeM, + wasmWordCmmTypeM, + wasmStateM, + wasmModifyM, + wasmExecM, + wasmUniq, + ) +where + +import Control.Applicative +import Data.ByteString (ByteString) +import Data.Coerce +import Data.Functor +import qualified Data.IntSet as IS +import Data.Kind +import Data.String +import Data.Type.Equality +import GHC.Cmm +import GHC.Data.FastString +import GHC.Float +import GHC.Platform +import GHC.Prelude +import GHC.Types.Basic +import GHC.Types.Unique +import GHC.Types.Unique.FM +import GHC.Types.Unique.Map +import GHC.Types.Unique.Supply +import GHC.Utils.Monad.State.Strict +import GHC.Utils.Outputable hiding ((<>)) +import Unsafe.Coerce + +-- | WebAssembly type of a WebAssembly value that WebAssembly code +-- could either expect on the evaluation stack or leave on the +-- evaluation stack. +data WasmType = I32 | I64 | F32 | F64 + +-- | Singleton type useful for programming with `WasmType` at the type +-- level. +data WasmTypeTag :: WasmType -> Type where + TagI32 :: WasmTypeTag 'I32 + TagI64 :: WasmTypeTag 'I64 + TagF32 :: WasmTypeTag 'F32 + TagF64 :: WasmTypeTag 'F64 + +deriving instance Show (WasmTypeTag t) + +instance TestEquality WasmTypeTag where + TagI32 `testEquality` TagI32 = Just Refl + TagI64 `testEquality` TagI64 = Just Refl + TagF32 `testEquality` TagF32 = Just Refl + TagF64 `testEquality` TagF64 = Just Refl + _ `testEquality` _ = Nothing + +data SomeWasmType where + SomeWasmType :: WasmTypeTag t -> SomeWasmType + +instance Eq SomeWasmType where + SomeWasmType ty0 == SomeWasmType ty1 + | Just Refl <- ty0 `testEquality` ty1 = True + | otherwise = False + +-- | List of WebAssembly types used to describe the sequence of +-- WebAssembly values that a block of code may expect on the stack or +-- leave on the stack. +data TypeList :: [WasmType] -> Type where + TypeListNil :: TypeList '[] + TypeListCons :: WasmTypeTag t -> TypeList ts -> TypeList (t : ts) + +someWasmTypesFromTypeList :: TypeList ts -> [SomeWasmType] +someWasmTypesFromTypeList TypeListNil = [] +someWasmTypesFromTypeList (ty `TypeListCons` tys) = + SomeWasmType ty : someWasmTypesFromTypeList tys + +-- | The type of a WebAssembly function, loop, block, or conditional. +-- This type says what values the code expects to pop off the stack +-- and what values it promises to push. The WebAssembly standard +-- requires that this type appear explicitly in the code. +data WasmFunctionType pre post = WasmFunctionType {ft_pops :: TypeList pre, ft_pushes :: TypeList post} + +-- | For simplicity, we record other metadata in 'WasmCodeGenState' by +-- need, instead of carrying them along with 'SymName'. +newtype SymName = SymName FastString + deriving (Eq, IsString, Show, Uniquable) via FastString + deriving (Ord) via LexicalFastString + +data SymVisibility + = -- | Not defined in the current compilation unit. + -- + -- @[ undefined binding=global vis=default ]@ + SymUndefined + | -- | Defined, not visible to other compilation units. + -- + -- @[ binding=local vis=default ]@ + SymStatic + | -- | Defined, visible to other compilation units. + -- + -- Adds @.hidden@ & @.globl@ directives in the output assembly. + -- + -- @[ binding=global vis=hidden ]@ + SymDefault + +-- | Represents whether a symbol is a data symbol or a function +-- symbol. Unlike linkers for other targets, @wasm-ld@ does panic at +-- link-time if it finds symbol kind inconsistency between the +-- definition site and other use sites. +-- +-- Currently we solely rely on 'isCFunctionLabel' to determine a +-- symbol's kind, but it does take extra effort to make it work. The +-- main source of inconsistency arises from hand-written Cmm sources, +-- where it's possible to refer to external entities like @xxx_info@ +-- and @xxx_closure@ without explicit @import CLOSURE@ declarations. +-- The Cmm parser will implicitly assume those are foreign function +-- labels, and then this will break the WebAssembly backend. #22368 +-- provides more context on this issue. +-- +-- tl;dr for any GHC contributor that accidentally triggers @wasm-ld@ +-- errors when hacking Cmm: whatever data symbols are used in new +-- code, just add the corresponding @import CLOSURE@ declarations at +-- the top of that Cmm file. +data SymKind = SymData | SymFunc + deriving (Eq) + +-- | WebAssembly doesn't really have proper read-only memory regions +-- yet. Neverthless we add the .rodata logic here, wasm-ld will +-- aggregate all .rodata sections into a single one, which adds +-- possibility for runtime checks later, either via a customized +-- runtime, or via code instrumentation. See +-- <https://github.com/llvm/llvm-project/blob/b296aed8ae239c20ebdd7969e978f8d2a3b9c178/lld/wasm/Writer.cpp#L856> +data DataSectionKind = SectionData | SectionROData + +-- | Neither Cmm or Wasm type system takes integer signedness into +-- account, therefore we always round up a 'CmmLit' to the right width +-- and handle it as an untyped integer. +data DataSectionContent + = DataI8 Integer + | DataI16 Integer + | DataI32 Integer + | DataI64 Integer + | DataF32 Float + | DataF64 Double + | DataSym SymName Int + | DataSkip Int + | DataASCII ByteString + | DataIncBin FilePath Int + +data DataSection = DataSection + { dataSectionKind :: DataSectionKind, + dataSectionAlignment :: + Alignment, + dataSectionContents :: [DataSectionContent] + } + +-- | We need to remember the symbols. Determinism is achieved by +-- sorting symbols before writing the assembly. +type SymMap = UniqMap SymName + +-- | No need to remember the symbols. +type SymSet = IS.IntSet + +type GlobalInfo = (SymName, SomeWasmType) + +type LocalInfo = (Int, SomeWasmType) + +data FuncBody w = FuncBody + { funcLocals :: [SomeWasmType], + -- | Most are Cmm functions, but may also contain synthesized + -- function of other types, sigh. + funcBody :: WasmControl (WasmStatements w) (WasmExpr w w) '[] '[w] + } + +data Signage = Signed | Unsigned + +-- | The @w@ type variable in the Wasm IR stands for "platform word +-- type", so 'TagI32' on wasm32, and 'TagI64' on wasm64. This way, we +-- can make the codegen logic work on both wasm32/wasm64 in a +-- type-safe manner. +data WasmInstr :: WasmType -> [WasmType] -> [WasmType] -> Type where + WasmComment :: String -> WasmInstr w pre pre + WasmNop :: WasmInstr w pre pre + WasmDrop :: WasmInstr w (t : pre) pre + WasmUnreachable :: WasmInstr w pre post + WasmConst :: WasmTypeTag t -> Integer -> WasmInstr w pre (t : pre) + WasmSymConst :: SymName -> WasmInstr w pre (w : pre) + WasmLoad :: + WasmTypeTag t -> + Maybe Int -> + Signage -> + Int -> + AlignmentSpec -> + WasmInstr w (w : pre) (t : pre) + WasmStore :: + WasmTypeTag t -> + Maybe Int -> + Int -> + AlignmentSpec -> + WasmInstr + w + (t : w : pre) + pre + WasmGlobalGet :: WasmTypeTag t -> SymName -> WasmInstr w pre (t : pre) + WasmGlobalSet :: WasmTypeTag t -> SymName -> WasmInstr w (t : pre) pre + WasmLocalGet :: WasmTypeTag t -> Int -> WasmInstr w pre (t : pre) + WasmLocalSet :: WasmTypeTag t -> Int -> WasmInstr w (t : pre) pre + WasmLocalTee :: WasmTypeTag t -> Int -> WasmInstr w (t : pre) (t : pre) + WasmCCall :: SymName -> WasmInstr w pre post + WasmCCallIndirect :: + TypeList arg_tys -> + TypeList ret_tys -> + WasmInstr + w + (w : pre) + post + WasmConcat :: + WasmInstr w pre mid -> + WasmInstr w mid post -> + WasmInstr w pre post + WasmReinterpret :: + WasmTypeTag t0 -> + WasmTypeTag t1 -> + WasmInstr + w + (t0 : pre) + (t1 : pre) + WasmTruncSat :: + Signage -> + WasmTypeTag t0 -> + WasmTypeTag t1 -> + WasmInstr + w + (t0 : pre) + (t1 : pre) + WasmConvert :: + Signage -> + WasmTypeTag t0 -> + WasmTypeTag t1 -> + WasmInstr + w + (t0 : pre) + (t1 : pre) + WasmClz :: WasmTypeTag t -> WasmInstr w (t : pre) (t : pre) + WasmCtz :: WasmTypeTag t -> WasmInstr w (t : pre) (t : pre) + WasmPopcnt :: WasmTypeTag t -> WasmInstr w (t : pre) (t : pre) + WasmAdd :: WasmTypeTag t -> WasmInstr w (t : t : pre) (t : pre) + WasmSub :: WasmTypeTag t -> WasmInstr w (t : t : pre) (t : pre) + WasmMul :: WasmTypeTag t -> WasmInstr w (t : t : pre) (t : pre) + WasmDiv :: Signage -> WasmTypeTag t -> WasmInstr w (t : t : pre) (t : pre) + WasmRem :: Signage -> WasmTypeTag t -> WasmInstr w (t : t : pre) (t : pre) + WasmAnd :: WasmTypeTag t -> WasmInstr w (t : t : pre) (t : pre) + WasmOr :: WasmTypeTag t -> WasmInstr w (t : t : pre) (t : pre) + WasmXor :: WasmTypeTag t -> WasmInstr w (t : t : pre) (t : pre) + WasmEq :: WasmTypeTag t -> WasmInstr w (t : t : pre) (w : pre) + WasmNe :: WasmTypeTag t -> WasmInstr w (t : t : pre) (w : pre) + WasmLt :: Signage -> WasmTypeTag t -> WasmInstr w (t : t : pre) (w : pre) + WasmGt :: Signage -> WasmTypeTag t -> WasmInstr w (t : t : pre) (w : pre) + WasmLe :: Signage -> WasmTypeTag t -> WasmInstr w (t : t : pre) (w : pre) + WasmGe :: Signage -> WasmTypeTag t -> WasmInstr w (t : t : pre) (w : pre) + WasmShl :: WasmTypeTag t -> WasmInstr w (t : t : pre) (t : pre) + WasmShr :: Signage -> WasmTypeTag t -> WasmInstr w (t : t : pre) (t : pre) + WasmI32Extend8S :: WasmInstr w ('I32 : pre) ('I32 : pre) + WasmI32Extend16S :: WasmInstr w ('I32 : pre) ('I32 : pre) + WasmI64Extend8S :: WasmInstr w ('I64 : pre) ('I64 : pre) + WasmI64Extend16S :: WasmInstr w ('I64 : pre) ('I64 : pre) + WasmI64Extend32S :: WasmInstr w ('I64 : pre) ('I64 : pre) + WasmI64ExtendI32 :: Signage -> WasmInstr w ('I32 : pre) ('I64 : pre) + WasmI32WrapI64 :: WasmInstr w ('I64 : pre) ('I32 : pre) + WasmF32DemoteF64 :: WasmInstr w ('F64 : pre) ('F32 : pre) + WasmF64PromoteF32 :: WasmInstr w ('F32 : pre) ('F64 : pre) + WasmAbs :: WasmTypeTag t -> WasmInstr w (t : pre) (t : pre) + WasmCond :: WasmInstr w pre pre -> WasmInstr w (w : pre) pre + +newtype WasmExpr w t = WasmExpr (forall pre. WasmInstr w pre (t : pre)) + +data SomeWasmExpr w where + SomeWasmExpr :: WasmTypeTag t -> WasmExpr w t -> SomeWasmExpr w + +newtype WasmStatements w = WasmStatements (forall pre. WasmInstr w pre pre) + +-- | Representation of WebAssembly control flow. +-- Normally written as +-- @ +-- WasmControl s e pre post +-- @ +-- Type parameter `s` is the type of (unspecified) statements. +-- It might be instantiated with an open Cmm block or with a sequence +-- of Wasm instructions. +-- Parameter `e` is the type of expressions. +-- Parameter `pre` represents the values that are expected on the +-- WebAssembly stack when the code runs, and `post` represents +-- the state of the stack on completion. +data WasmControl :: Type -> Type -> [WasmType] -> [WasmType] -> Type where + WasmPush :: WasmTypeTag t -> e -> WasmControl s e stack (t : stack) + WasmBlock :: + WasmFunctionType pre post -> + WasmControl s e pre post -> + WasmControl s e pre post + WasmLoop :: + WasmFunctionType pre post -> + WasmControl s e pre post -> + WasmControl s e pre post + WasmIfTop :: + WasmFunctionType pre post -> + WasmControl s e pre post -> + WasmControl s e pre post -> + WasmControl s e ('I32 : pre) post + WasmBr :: Int -> WasmControl s e dropped destination -- not typechecked + WasmFallthrough :: WasmControl s e dropped destination + -- generates no code, but has the same type as a branch + WasmBrTable :: + e -> + BrTableInterval -> -- for testing + [Int] -> -- targets + Int -> -- default target + WasmControl s e dropped destination + -- invariant: the table interval is contained + -- within [0 .. pred (length targets)] + WasmReturnTop :: + WasmTypeTag t -> + WasmControl s e (t : t1star) t2star -- as per type system + WasmActions :: + s -> + WasmControl s e stack stack -- basic block: one entry, one exit + WasmSeq :: + WasmControl s e pre mid -> + WasmControl s e mid post -> + WasmControl s e pre post + +data BrTableInterval = BrTableInterval {bti_lo :: Integer, bti_count :: Integer} + deriving (Show) + +instance Outputable BrTableInterval where + ppr range = + brackets $ + hcat + [integer (bti_lo range), text "..", integer hi] + where + hi = bti_lo range + bti_count range - 1 + +wasmControlCast :: WasmControl s e pre post -> WasmControl s e pre' post' +wasmControlCast = unsafeCoerce + +data WasmCodeGenState w = WasmCodeGenState + { -- | Target platform + wasmPlatform :: Platform, + -- | Defined symbols with 'SymDefault' visibility. + defaultSyms :: SymSet, + -- | Function types, defined or not. There may exist a function + -- whose type is unknown (e.g. as a function pointer), in that + -- case we fall back to () -> (), it's imperfect but works with + -- wasm-ld. + funcTypes :: SymMap ([SomeWasmType], [SomeWasmType]), + -- | Defined function bodies. + funcBodies :: SymMap (FuncBody w), + -- | Defined data sections. + dataSections :: SymMap DataSection, + -- | ctors in the current compilation unit. + ctors :: [SymName], + localRegs :: + UniqFM LocalReg LocalInfo, + localRegsCount :: + Int, + wasmUniqSupply :: UniqSupply + } + +initialWasmCodeGenState :: Platform -> UniqSupply -> WasmCodeGenState w +initialWasmCodeGenState platform us = + WasmCodeGenState + { wasmPlatform = + platform, + defaultSyms = IS.empty, + funcTypes = emptyUniqMap, + funcBodies = + emptyUniqMap, + dataSections = emptyUniqMap, + ctors = + [], + localRegs = emptyUFM, + localRegsCount = 0, + wasmUniqSupply = us + } + +newtype WasmCodeGenM w a = WasmCodeGenM (State (WasmCodeGenState w) a) + deriving newtype (Functor, Applicative, Monad) + +wasmGetsM :: (WasmCodeGenState w -> a) -> WasmCodeGenM w a +wasmGetsM = coerce . gets + +wasmPlatformM :: WasmCodeGenM w Platform +wasmPlatformM = wasmGetsM wasmPlatform + +wasmWordTypeM :: WasmCodeGenM w (WasmTypeTag w) +wasmWordTypeM = wasmGetsM $ \s -> + if target32Bit $ wasmPlatform s + then unsafeCoerce TagI32 + else unsafeCoerce TagI64 + +wasmWordCmmTypeM :: WasmCodeGenM w CmmType +wasmWordCmmTypeM = wasmGetsM (bWord . wasmPlatform) + +wasmStateM :: + (WasmCodeGenState w -> (# a, WasmCodeGenState w #)) -> + WasmCodeGenM w a +wasmStateM = coerce . State + +wasmModifyM :: (WasmCodeGenState w -> WasmCodeGenState w) -> WasmCodeGenM w () +wasmModifyM = coerce . modify + +wasmExecM :: WasmCodeGenM w a -> WasmCodeGenState w -> WasmCodeGenState w +wasmExecM (WasmCodeGenM s) = execState s + +wasmUniq :: WasmCodeGenM w Unique +wasmUniq = wasmStateM $ + \s@WasmCodeGenState {..} -> case takeUniqFromSupply wasmUniqSupply of + (u, us) -> (# u, s {wasmUniqSupply = us} #) diff --git a/compiler/GHC/CmmToAsm/Wasm/Utils.hs b/compiler/GHC/CmmToAsm/Wasm/Utils.hs new file mode 100644 index 0000000000..b794c7f5b7 --- /dev/null +++ b/compiler/GHC/CmmToAsm/Wasm/Utils.hs @@ -0,0 +1,35 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE Strict #-} + +module GHC.CmmToAsm.Wasm.Utils + ( naturalNarrowing, + widthMax, + detEltsUFM, + detEltsUniqMap, + builderCommas, + ) +where + +import Data.ByteString.Builder +import Data.List (intersperse, sortOn) +import GHC.Cmm +import GHC.Prelude +import GHC.Types.Unique.FM +import GHC.Types.Unique.Map + +naturalNarrowing :: Width -> Integer -> Integer +naturalNarrowing w i + | i < 0 = narrowS w i + | otherwise = narrowU w i + +widthMax :: Width -> Integer +widthMax w = (1 `shiftL` widthInBits w) - 1 + +detEltsUFM :: Ord k => UniqFM k0 (k, a) -> [(k, a)] +detEltsUFM = sortOn fst . nonDetEltsUFM + +detEltsUniqMap :: Ord k => UniqMap k a -> [(k, a)] +detEltsUniqMap = sortOn fst . nonDetEltsUniqMap + +builderCommas :: (a -> Builder) -> [a] -> Builder +builderCommas f xs = mconcat (intersperse ", " (map f xs)) diff --git a/compiler/ghc.cabal.in b/compiler/ghc.cabal.in index b7ff590a11..de013e5d32 100644 --- a/compiler/ghc.cabal.in +++ b/compiler/ghc.cabal.in @@ -816,6 +816,11 @@ Library GHC.Utils.Trace GHC.Wasm.ControlFlow GHC.Wasm.ControlFlow.FromCmm + GHC.CmmToAsm.Wasm + GHC.CmmToAsm.Wasm.Asm + GHC.CmmToAsm.Wasm.FromCmm + GHC.CmmToAsm.Wasm.Types + GHC.CmmToAsm.Wasm.Utils Language.Haskell.Syntax Language.Haskell.Syntax.Basic |