64 Commits

Author SHA1 Message Date
Ilya Rezvov 7fab53969a merge artifact 2023-08-21 21:23:07 -06:00
Ilya Rezvov a3de890895 Merge branch 'references-simd' of github.com:SPY/haskell-wasm into references-simd 2023-08-21 21:22:20 -06:00
Ilya Rezvov 9eadc50ee3 all tests are green 2023-08-21 21:15:53 -06:00
Ilya Rezvov 5b6b043049 more green test 2023-08-21 21:15:53 -06:00
Ilya Rezvov bb2a6cea7f fix reexports 2023-08-21 21:15:53 -06:00
Ilya Rezvov f23dc20d57 fix more tests 2023-08-21 21:15:44 -06:00
Ilya Rezvov 2b822a8d44 validate and interprete bulk memory instructions 2023-08-21 21:11:39 -06:00
Ilya Rezvov 46f95dd11d parse memory instructions 2023-08-21 21:11:39 -06:00
Ilya Rezvov 2d4ad42549 add syntax support for extended data segments 2023-08-21 21:11:39 -06:00
Ilya Rezvov c044f3f556 add return type for select 2023-08-21 21:11:39 -06:00
Ilya Rezvov d0403ad554 forbid special nan values for non-script contexts 2023-08-21 21:11:39 -06:00
Ilya Rezvov d321bf6a9c implement table.fill 2023-08-21 21:11:39 -06:00
Ilya Rezvov ec83b12ccd keep track of defined references and store global function address for table.set instructions 2023-08-21 21:11:39 -06:00
Ilya Rezvov 9f17885106 better bounds check for table.get and table.set 2023-08-21 21:11:39 -06:00
Ilya Rezvov ea50c54900 parse externref elems, but fail on validation 2023-08-21 21:11:39 -06:00
Ilya Rezvov 4e9cb99e69 change table storage format to allow growing and implement table.grow and table.size 2023-08-21 21:11:39 -06:00
Ilya Rezvov b2f001aea8 implement table.copy 2023-08-21 21:11:39 -06:00
Ilya Rezvov f445340568 add elem.drop and extend call_indirect to accept table index 2023-08-21 21:11:39 -06:00
Ilya Rezvov 2d15ddfa36 implement table.get and table.set and pass ref_is_null test suit 2023-08-21 21:11:39 -06:00
Ilya Rezvov 0b44ee13b8 pass tests in elem.wat test suit 2023-08-21 21:11:39 -06:00
Ilya Rezvov e9140ae70c drop flag for elems was added. initial table.init implementation 2023-08-21 21:11:39 -06:00
Ilya Rezvov cbeb4bb61d validate table.init and use mutable vector as a table storage 2023-08-21 21:11:39 -06:00
Ilya Rezvov 8c97e2c328 syntax support for table.init 2023-08-21 21:11:39 -06:00
Ilya Rezvov 6e9c4d969a update initialization sequence for elems 2023-08-21 21:11:39 -06:00
Ilya Rezvov 179b7ce180 fix validator for new elems 2023-08-21 21:11:39 -06:00
Ilya Rezvov fd72ee9d23 newtype for expressions to serialize in vectors 2023-08-21 21:11:39 -06:00
Ilya Rezvov b6c0ef4462 update text and binary parsers to new elem formats 2023-08-21 21:11:39 -06:00
Ilya Rezvov 51cf7e753b syntax support for ref.ops and pass ref_null.wast 2023-08-21 21:11:39 -06:00
Илья Резвов fe6c31984b update test specs to bring fixed SIMD and reference types 2023-08-21 21:11:39 -06:00
Ilya Rezvov f2f5fcc56f all tests are green 2023-08-21 21:11:14 -06:00
Ilya Rezvov a5ffe38a1c more green test 2023-08-21 19:28:54 -06:00
Ilya Rezvov 724973b15e fix reexports 2023-08-21 08:23:58 -06:00
Ilya Rezvov a58bb32d98 Merge pull request #22 from chrbauer/chrbauer/update-stack-lts
Stack lts-16.5 -> 20.23 and URLs in Cabal
2023-08-20 21:02:54 -06:00
Ilya Rezvov 0e0b1abbba fix more tests 2023-08-20 21:00:46 -06:00
Christoph Bauer 418847226e Stack lts-16.5 -> 20.23 and URLs in Cabal 2023-06-03 06:49:43 +02:00
Ilya Rezvov efba9c2b50 Merge pull request #21 from RyanGlScott/bump-dependencies
Allow building with `mtl-2.3`, `transformers-0.6`, `vector-0.13`
2023-02-16 09:30:48 -07:00
Ryan Scott f6d0783fd0 Allow building with mtl-2.3, transformers-0.6, vector-0.13 2023-02-13 07:57:03 -05:00
Ilya Rezvov 57c4184c18 Merge pull request #20 from carbolymer/master
Expose mutable memory access
2023-01-09 10:22:22 -07:00
Mateusz Galazyn 3be70781b8 Expose mutable memory access 2022-10-22 09:37:50 +02:00
Ilya Rezvov 5256d5063f validate and interprete bulk memory instructions 2022-06-12 21:12:19 -06:00
Ilya Rezvov 2bf6e88072 parse memory instructions 2022-06-10 22:29:31 -06:00
Ilya Rezvov 32392dcc29 add syntax support for extended data segments 2022-06-09 20:21:57 -06:00
Ilya Rezvov c743e11ffd add return type for select 2022-06-08 21:12:00 -06:00
Ilya Rezvov 32357d68fe forbid special nan values for non-script contexts 2022-06-07 20:28:02 -06:00
Ilya Rezvov 83fea36c02 implement table.fill 2022-06-07 19:41:03 -06:00
Ilya Rezvov 43cbfe653f keep track of defined references and store global function address for table.set instructions 2022-06-06 20:34:59 -06:00
Ilya Rezvov 71d332e3dc better bounds check for table.get and table.set 2022-06-06 18:44:42 -06:00
Ilya Rezvov 4e9105717b parse externref elems, but fail on validation 2022-06-06 18:41:22 -06:00
Ilya Rezvov df15d3c4d1 change table storage format to allow growing and implement table.grow and table.size 2022-06-06 18:27:26 -06:00
Ilya Rezvov b1da37ac03 implement table.copy 2022-06-05 21:45:58 -06:00
Ilya Rezvov 82defff076 add elem.drop and extend call_indirect to accept table index 2022-06-04 21:58:34 -06:00
Ilya Rezvov 6eb3acde17 implement table.get and table.set and pass ref_is_null test suit 2022-06-04 16:32:20 -06:00
Ilya Rezvov e388e21370 pass tests in elem.wat test suit 2022-06-02 20:54:15 -06:00
Ilya Rezvov 95fdcc2f80 drop flag for elems was added. initial table.init implementation 2022-05-31 19:39:06 -06:00
Ilya Rezvov 5bcd863671 validate table.init and use mutable vector as a table storage 2022-05-30 21:43:44 -06:00
Ilya Rezvov 4753ebceb4 syntax support for table.init 2022-05-30 18:57:21 -06:00
Ilya Rezvov de40134caf update initialization sequence for elems 2022-02-15 21:43:45 -07:00
Ilya Rezvov 286ee40489 fix validator for new elems 2022-02-09 21:49:03 -07:00
Ilya Rezvov c08e81fa40 newtype for expressions to serialize in vectors 2022-02-01 21:22:09 -07:00
Ilya Rezvov 8af7b45681 update text and binary parsers to new elem formats 2022-02-01 21:18:12 -07:00
Ilya Rezvov 960acac955 syntax support for ref.ops and pass ref_null.wast 2022-01-30 16:23:02 -07:00
Ilya Rezvov 99532adb63 bump version 2022-01-30 16:23:02 -07:00
Ilya Rezvov c8f1bc9186 bump version 2021-12-11 21:39:18 -07:00
Илья Резвов 66458e11f3 update test specs to bring fixed SIMD and reference types 2021-12-11 21:03:01 -07:00
15 changed files with 1245 additions and 389 deletions
+2
View File
@@ -8,3 +8,5 @@ dist-newstyle/
doc/ doc/
setup-config setup-config
wasm-*-docs.tar.gz wasm-*-docs.tar.gz
cache
packagedb
+2 -1
View File
@@ -20,9 +20,10 @@
* [ ] Text Representation pretty-printer * [ ] Text Representation pretty-printer
* [ ] Command line tool for calling interpreter/compiler/validator * [ ] Command line tool for calling interpreter/compiler/validator
* [ ] Codegen interface for type enforced generating valid WASM code * [ ] Codegen interface for type enforced generating valid WASM code
* [ ] Support for building if, loop, block
## Development ## Development
Clond sources to directory and use `stack` for running tests: Clone sources to directory and use `stack` for running tests:
``` ```
stack build && stack test stack build && stack test
``` ```
+151 -20
View File
@@ -244,7 +244,13 @@ instance Serialize FuncType where
instance Serialize ElemType where instance Serialize ElemType where
put FuncRef = putWord8 0x70 put FuncRef = putWord8 0x70
get = byteGuard 0x70 >> return FuncRef put ExternRef = putWord8 0x6F
get = do
op <- getWord8
case op of
0x70 -> return FuncRef
0x69 -> return ExternRef
_ -> fail "unknown reference type"
instance Serialize Limit where instance Serialize Limit where
put (Limit min Nothing) = putWord8 0x00 >> putULEB128 min put (Limit min Nothing) = putWord8 0x00 >> putULEB128 min
@@ -320,6 +326,12 @@ instance Serialize Index where
put (Index idx) = putULEB128 idx put (Index idx) = putULEB128 idx
get = Index <$> getULEB128 32 get = Index <$> getULEB128 32
newtype Expr = Expr { unExpr :: Expression } deriving (Show, Eq)
instance Serialize Expr where
put (Expr expr) = putExpression expr
get = Expr <$> getExpression
instance Serialize MemArg where instance Serialize MemArg where
put MemArg { align, offset } = putULEB128 align >> putULEB128 offset put MemArg { align, offset } = putULEB128 align >> putULEB128 offset
get = do get = do
@@ -353,16 +365,50 @@ instance Serialize (Instruction Natural) where
put (BrTable labels label) = putWord8 0x0E >> putVec (map Index labels) >> putULEB128 label put (BrTable labels label) = putWord8 0x0E >> putVec (map Index labels) >> putULEB128 label
put Return = putWord8 0x0F put Return = putWord8 0x0F
put (Call funcIdx) = putWord8 0x10 >> putULEB128 funcIdx put (Call funcIdx) = putWord8 0x10 >> putULEB128 funcIdx
put (CallIndirect typeIdx) = putWord8 0x11 >> putULEB128 typeIdx >> putWord8 0x00 put (CallIndirect tableIdx typeIdx) = putWord8 0x11 >> putULEB128 typeIdx >> putULEB128 tableIdx
-- Reference instructions
put (RefNull refType) = putWord8 0xD0 >> put refType
put RefIsNull = putWord8 0xD1
put (RefFunc index) = putWord8 0xD2 >> putULEB128 index
-- Parametric instructions -- Parametric instructions
put Drop = putWord8 0x1A put Drop = putWord8 0x1A
put Select = putWord8 0x1B put (Select Nothing) = putWord8 0x1B
put (Select (Just types)) = putWord8 0x1C >> putVec types
-- Variable instructions -- Variable instructions
put (GetLocal idx) = putWord8 0x20 >> putULEB128 idx put (GetLocal idx) = putWord8 0x20 >> putULEB128 idx
put (SetLocal idx) = putWord8 0x21 >> putULEB128 idx put (SetLocal idx) = putWord8 0x21 >> putULEB128 idx
put (TeeLocal idx) = putWord8 0x22 >> putULEB128 idx put (TeeLocal idx) = putWord8 0x22 >> putULEB128 idx
put (GetGlobal idx) = putWord8 0x23 >> putULEB128 idx put (GetGlobal idx) = putWord8 0x23 >> putULEB128 idx
put (SetGlobal idx) = putWord8 0x24 >> putULEB128 idx put (SetGlobal idx) = putWord8 0x24 >> putULEB128 idx
-- Table instructions
put (TableGet idx) = putWord8 0x25 >> putULEB128 idx
put (TableSet idx) = putWord8 0x26 >> putULEB128 idx
put (TableInit tableIdx elemIdx) = do
putWord8 0xFC
putULEB128 (0x0C :: Word32)
putULEB128 tableIdx
putULEB128 elemIdx
put (ElemDrop elemIdx) = do
putWord8 0xFC
putULEB128 (0x0D :: Word32)
putULEB128 elemIdx
put (TableCopy fromIdx toIdx) = do
putWord8 0xFC
putULEB128 (0x0E :: Word32)
putULEB128 fromIdx
putULEB128 toIdx
put (TableGrow tableIdx) = do
putWord8 0xFC
putULEB128 (0x0F :: Word32)
putULEB128 tableIdx
put (TableSize tableIdx) = do
putWord8 0xFC
putULEB128 (0x10 :: Word32)
putULEB128 tableIdx
put (TableFill tableIdx) = do
putWord8 0xFC
putULEB128 (0x11 :: Word32)
putULEB128 tableIdx
-- Memory instructions -- Memory instructions
put (I32Load memArg) = putWord8 0x28 >> put memArg put (I32Load memArg) = putWord8 0x28 >> put memArg
put (I64Load memArg) = putWord8 0x29 >> put memArg put (I64Load memArg) = putWord8 0x29 >> put memArg
@@ -387,8 +433,26 @@ instance Serialize (Instruction Natural) where
put (I64Store8 memArg) = putWord8 0x3C >> put memArg put (I64Store8 memArg) = putWord8 0x3C >> put memArg
put (I64Store16 memArg) = putWord8 0x3D >> put memArg put (I64Store16 memArg) = putWord8 0x3D >> put memArg
put (I64Store32 memArg) = putWord8 0x3E >> put memArg put (I64Store32 memArg) = putWord8 0x3E >> put memArg
put CurrentMemory = putWord8 0x3F >> putWord8 0x00 put MemorySize = putWord8 0x3F >> putWord8 0x00
put GrowMemory = putWord8 0x40 >> putWord8 0x00 put MemoryGrow = putWord8 0x40 >> putWord8 0x00
put (MemoryInit dataIdx) = do
putWord8 0xFC
putULEB128 (0x08 :: Word32)
putULEB128 dataIdx
putWord8 0
put (DataDrop dataIdx) = do
putWord8 0xFC
putULEB128 (0x09 :: Word32)
putULEB128 dataIdx
put MemoryCopy = do
putWord8 0xFC
putULEB128 (0x0A :: Word32)
putWord8 0
putWord8 0
put MemoryFill = do
putWord8 0xFC
putULEB128 (0x0B :: Word32)
putWord8 0
-- Numeric instructions -- Numeric instructions
put (I32Const val) = putWord8 0x41 >> putSLEB128 (asInt32 val) put (I32Const val) = putWord8 0x41 >> putSLEB128 (asInt32 val)
put (I64Const val) = putWord8 0x42 >> putSLEB128 (asInt64 val) put (I64Const val) = putWord8 0x42 >> putSLEB128 (asInt64 val)
@@ -553,11 +617,15 @@ instance Serialize (Instruction Natural) where
0x10 -> Call <$> getULEB128 32 0x10 -> Call <$> getULEB128 32
0x11 -> do 0x11 -> do
typeIdx <- getULEB128 32 typeIdx <- getULEB128 32
byteGuard 0x00 tableIdx <- getULEB128 32
return $ CallIndirect typeIdx return $ CallIndirect tableIdx typeIdx
-- Reference instructions
0xD0 -> RefNull <$> get
0xD1 -> return RefIsNull
0xD2 -> RefFunc <$> getULEB128 32
-- Parametric instructions -- Parametric instructions
0x1A -> return $ Drop 0x1A -> return $ Drop
0x1B -> return $ Select 0x1B -> return $ Select Nothing
-- Variable instructions -- Variable instructions
0x20 -> GetLocal <$> getULEB128 32 0x20 -> GetLocal <$> getULEB128 32
0x21 -> SetLocal <$> getULEB128 32 0x21 -> SetLocal <$> getULEB128 32
@@ -588,8 +656,8 @@ instance Serialize (Instruction Natural) where
0x3C -> I64Store8 <$> get 0x3C -> I64Store8 <$> get
0x3D -> I64Store16 <$> get 0x3D -> I64Store16 <$> get
0x3E -> I64Store32 <$> get 0x3E -> I64Store32 <$> get
0x3F -> byteGuard 0x00 >> (return $ CurrentMemory) 0x3F -> byteGuard 0x00 >> (return $ MemorySize)
0x40 -> byteGuard 0x00 >> (return $ GrowMemory) 0x40 -> byteGuard 0x00 >> (return $ MemoryGrow)
-- Numeric instructions -- Numeric instructions
0x41 -> I32Const <$> getSLEB128 32 0x41 -> I32Const <$> getSLEB128 32
0x42 -> I64Const <$> getSLEB128 64 0x42 -> I64Const <$> getSLEB128 64
@@ -735,7 +803,7 @@ instance Serialize (Instruction Natural) where
0x06 -> return $ ITruncSatFS BS64 BS64 0x06 -> return $ ITruncSatFS BS64 BS64
0x07 -> return $ ITruncSatFU BS64 BS64 0x07 -> return $ ITruncSatFU BS64 BS64
_ -> fail "Unknown byte value after misc instruction byte" _ -> fail "Unknown byte value after misc instruction byte"
_ -> fail "Unknown byte value in place of instruction opcode" byte -> fail $ "Unknown byte value in place of instruction opcode: " ++ (show byte)
putExpression :: Expression -> Put putExpression :: Expression -> Put
putExpression expr = do putExpression expr = do
@@ -793,11 +861,56 @@ instance Serialize Export where
get = Export <$> getName <*> get get = Export <$> getName <*> get
instance Serialize ElemSegment where instance Serialize ElemSegment where
put (ElemSegment tableIndex offset funcIndexes) = do put (ElemSegment elemType Passive elements) = do
putWord8 0x05
put elemType
putVec $ map Expr elements
put (ElemSegment elemType (Active tableIndex offset) elements) = do
putWord8 0x06
putULEB128 tableIndex putULEB128 tableIndex
putExpression offset putExpression offset
putVec $ map Index funcIndexes put elemType
get = ElemSegment <$> getULEB128 32 <*> getExpression <*> (map unIndex <$> getVec) putVec $ map Expr elements
put (ElemSegment elemType Declarative elements) = do
putWord8 0x07
put elemType
putVec $ map Expr elements
get = do
op <- getWord8
let funcIndexes = map ((:[]) . RefFunc . unIndex) <$> getVec
let elemKind = byteGuard 0x00 >> return FuncRef
case op of
0x00 -> do
offset <- getExpression
ElemSegment FuncRef (Active 0 offset) <$> funcIndexes
0x01 -> do
elemType <- elemKind
ElemSegment elemType Passive <$> funcIndexes
0x02 -> do
tableIndex <- getULEB128 32
offset <- getExpression
elemType <- elemKind
ElemSegment elemType (Active tableIndex offset) <$> funcIndexes
0x03 -> do
elemType <- elemKind
ElemSegment elemType Declarative <$> funcIndexes
0x04 -> do
offset <- getExpression
ElemSegment FuncRef (Active 0 offset) <$> funcIndexes
0x05 -> do
elemType <- get
ElemSegment elemType Passive . map unExpr <$> getVec
0x06 -> do
tableIndex <- getULEB128 32
offset <- getExpression
elemType <- get
ElemSegment elemType (Active tableIndex offset) <$> getVec
0x07 -> do
elemType <- get
ElemSegment elemType Declarative <$> getVec
_ ->
fail "unknown element segment type"
data LocalTypeRange = LocalTypeRange Natural ValueType deriving (Show, Eq) data LocalTypeRange = LocalTypeRange Natural ValueType deriving (Show, Eq)
@@ -824,17 +937,35 @@ instance Serialize Function where
return $ Function 0 locals body return $ Function 0 locals body
instance Serialize DataSegment where instance Serialize DataSegment where
put (DataSegment memIdx offset init) = do put (DataSegment (ActiveData memIdx offset) init) = do
putWord8 0x02
putULEB128 memIdx putULEB128 memIdx
putExpression offset putExpression offset
putULEB128 $ LBS.length init putULEB128 $ LBS.length init
putLazyByteString init putLazyByteString init
put (DataSegment PassiveData init) = do
putWord8 0x01
putULEB128 $ LBS.length init
putLazyByteString init
get = do get = do
memIdx <- getULEB128 32 op <- getULEB128 32
offset <- getExpression case (op :: Word8) of
len <- getULEB128 32 0x00 -> do
init <- getLazyByteString len offset <- getExpression
return $ DataSegment memIdx offset init len <- getULEB128 32
init <- getLazyByteString len
return $ DataSegment (ActiveData 0 offset) init
0x01 -> do
len <- getULEB128 32
init <- getLazyByteString len
return $ DataSegment PassiveData init
0x02 -> do
memIdx <- getULEB128 32
offset <- getExpression
len <- getULEB128 32
init <- getLazyByteString len
return $ DataSegment (ActiveData memIdx offset) init
byte -> fail $ "unknown data segment type: " ++ show byte
instance Serialize Module where instance Serialize Module where
put mod = do put mod = do
+5 -5
View File
@@ -197,7 +197,7 @@ select pred a b = select' (produce pred) (produce a) (produce b)
a a
res <- b res <- b
pred pred
appendExpr [Select] appendExpr [Select Nothing]
return res return res
iBinOp :: (Producer a, Producer b, OutType a ~ OutType b, IsInt (OutType a) ~ True) => IBinOp -> a -> b -> GenFun (OutType a) iBinOp :: (Producer a, Producer b, OutType a ~ OutType b, IsInt (OutType a) ~ True) => IBinOp -> a -> b -> GenFun (OutType a)
@@ -643,10 +643,10 @@ store32 addr val offset align = do
appendExpr [I64Store32 $ MemArg (fromIntegral offset) (fromIntegral align)] appendExpr [I64Store32 $ MemArg (fromIntegral offset) (fromIntegral align)]
memorySize :: GenFun (Proxy I32) memorySize :: GenFun (Proxy I32)
memorySize = appendExpr [CurrentMemory] >> return Proxy memorySize = appendExpr [MemorySize] >> return Proxy
growMemory :: (Producer size, OutType size ~ Proxy I32) => size -> GenFun () growMemory :: (Producer size, OutType size ~ Proxy I32) => size -> GenFun ()
growMemory size = produce size >> appendExpr [GrowMemory] growMemory size = produce size >> appendExpr [MemoryGrow]
call :: (Returnable res) => Fn res -> [GenFun a] -> GenFun res call :: (Returnable res) => Fn res -> [GenFun a] -> GenFun res
call (Fn idx) args = sequence_ args >> appendExpr [Call idx] >> return returnableValue call (Fn idx) args = sequence_ args >> appendExpr [Call idx] >> return returnableValue
@@ -655,7 +655,7 @@ callIndirect :: (Producer index, OutType index ~ Proxy I32, Returnable res) => T
callIndirect (TypeDef idx) index args = do callIndirect (TypeDef idx) index args = do
sequence_ args sequence_ args
produce index produce index
appendExpr [CallIndirect idx] appendExpr [CallIndirect 0 idx]
return returnableValue return returnableValue
br :: Label t -> GenFun () br :: Label t -> GenFun ()
@@ -975,7 +975,7 @@ table min max = do
dataSegment :: (Producer offset, OutType offset ~ Proxy I32) => offset -> LBS.ByteString -> GenMod () dataSegment :: (Producer offset, OutType offset ~ Proxy I32) => offset -> LBS.ByteString -> GenMod ()
dataSegment offset bytes = dataSegment offset bytes =
modify $ \(st@GenModState { target = m }) -> st { modify $ \(st@GenModState { target = m }) -> st {
target = m { datas = datas m ++ [DataSegment 0 (genExpr 0 (produce offset)) bytes] } target = m { datas = datas m ++ [DataSegment (ActiveData 0 (genExpr 0 (produce offset))) bytes] }
} }
asWord32 :: Int32 -> Word32 asWord32 :: Int32 -> Word32
+328 -70
View File
@@ -10,8 +10,11 @@ module Language.Wasm.Interpreter (
ExternalValue(..), ExternalValue(..),
ExportInstance(..), ExportInstance(..),
GlobalInstance(..), GlobalInstance(..),
MemoryInstance(..),
MemoryStore,
Imports, Imports,
HostItem(..), HostItem(..),
Address,
instantiate, instantiate,
invoke, invoke,
invokeExport, invokeExport,
@@ -20,7 +23,8 @@ module Language.Wasm.Interpreter (
emptyImports, emptyImports,
makeHostModule, makeHostModule,
makeMutGlobal, makeMutGlobal,
makeConstGlobal makeConstGlobal,
getMemory
) where ) where
import qualified Data.Map as Map import qualified Data.Map as Map
@@ -30,6 +34,8 @@ import Data.Maybe (fromMaybe, isNothing)
import Data.Vector (Vector, (!), (!?), (//)) import Data.Vector (Vector, (!), (!?), (//))
import qualified Data.Vector as Vector import qualified Data.Vector as Vector
import Data.Vector.Mutable (IOVector)
import qualified Data.Vector.Mutable as MVector
import qualified Data.Primitive.ByteArray as ByteArray import qualified Data.Primitive.ByteArray as ByteArray
import qualified Data.Primitive.Types as Primitive import qualified Data.Primitive.Types as Primitive
import qualified Control.Monad.Primitive as Primitive import qualified Control.Monad.Primitive as Primitive
@@ -65,11 +71,15 @@ import Language.Wasm.FloatUtils (
doubleToWord doubleToWord
) )
import Debug.Trace as Debug
data Value = data Value =
VI32 Word32 VI32 Word32
| VI64 Word64 | VI64 Word64
| VF32 Float | VF32 Float
| VF64 Double | VF64 Double
| RF (Maybe Natural)
| RE (Maybe Natural)
deriving (Eq, Show) deriving (Eq, Show)
asInt32 :: Word32 -> Int32 asInt32 :: Word32 -> Int32
@@ -162,9 +172,11 @@ data Label = Label ResultType deriving (Show, Eq)
type Address = Int type Address = Int
type TableStore = IORef (IOVector (Maybe Address))
data TableInstance = TableInstance { data TableInstance = TableInstance {
lim :: Limit, t :: TableType,
elements :: Vector (Maybe Address) items :: TableStore
} }
type MemoryStore = ByteArray.MutableByteArray (Primitive.PrimState IO) type MemoryStore = ByteArray.MutableByteArray (Primitive.PrimState IO)
@@ -187,6 +199,8 @@ getValueType (VI32 _) = I32
getValueType (VI64 _) = I64 getValueType (VI64 _) = I64
getValueType (VF32 _) = F32 getValueType (VF32 _) = F32
getValueType (VF64 _) = F64 getValueType (VF64 _) = F64
getValueType (RF _) = Func
genValueType (RE _) = Extern
data ExportInstance = ExportInstance TL.Text ExternalValue deriving (Eq, Show) data ExportInstance = ExportInstance TL.Text ExternalValue deriving (Eq, Show)
@@ -208,11 +222,30 @@ data FunctionInstance =
hostCode :: HostFunction hostCode :: HostFunction
} }
data ElemInstance = ElemInstance {
eiMode :: ElemMode,
eiType :: ElemType,
eiItems :: Vector Value,
isDropped :: IORef Bool
}
isDeclarative :: ElemMode -> Bool
isDeclarative Declarative = True
isDeclarative _ = False
data DataInstance = DataInstance {
dMode :: DataMode,
isDropped :: IORef Bool,
bytes :: LBS.ByteString
}
data Store = Store { data Store = Store {
funcInstances :: Vector FunctionInstance, funcInstances :: Vector FunctionInstance,
tableInstances :: Vector TableInstance, tableInstances :: Vector TableInstance,
memInstances :: Vector MemoryInstance, memInstances :: Vector MemoryInstance,
globalInstances :: Vector GlobalInstance globalInstances :: Vector GlobalInstance,
elemInstances :: Vector ElemInstance,
dataInstances :: Vector DataInstance
} }
emptyStore :: Store emptyStore :: Store
@@ -220,7 +253,9 @@ emptyStore = Store {
funcInstances = Vector.empty, funcInstances = Vector.empty,
tableInstances = Vector.empty, tableInstances = Vector.empty,
memInstances = Vector.empty, memInstances = Vector.empty,
globalInstances = Vector.empty globalInstances = Vector.empty,
elemInstances = Vector.empty,
dataInstances = Vector.empty
} }
type HostFunction = [Value] -> IO [Value] type HostFunction = [Value] -> IO [Value]
@@ -284,8 +319,8 @@ makeHostModule st items = do
makeHostTables :: (Store, ModuleInstance) -> IO (Store, ModuleInstance) makeHostTables :: (Store, ModuleInstance) -> IO (Store, ModuleInstance)
makeHostTables (st, inst) = do makeHostTables (st, inst) = do
let tableLen = Vector.length $ tableInstances st let tableLen = Vector.length $ tableInstances st
let (names, tables) = unzip [(name, Table (TableType lim FuncRef)) | (name, (HostTable lim)) <- items] let (names, tables) = unzip [(name, Table (TableType lim FuncRef)) | (name, HostTable lim) <- items]
let instances = allocTables tables instances <- allocTables tables
let exps = Vector.fromList $ zipWith (\name i -> ExportInstance name (ExternTable i)) names [tableLen..] let exps = Vector.fromList $ zipWith (\name i -> ExportInstance name (ExternTable i)) names [tableLen..]
let inst' = inst { let inst' = inst {
tableaddrs = Vector.fromList [tableLen..tableLen + length instances - 1], tableaddrs = Vector.fromList [tableLen..tableLen + length instances - 1],
@@ -300,6 +335,8 @@ data ModuleInstance = ModuleInstance {
tableaddrs :: Vector Address, tableaddrs :: Vector Address,
memaddrs :: Vector Address, memaddrs :: Vector Address,
globaladdrs :: Vector Address, globaladdrs :: Vector Address,
elemaddrs :: Vector Address,
dataaddrs :: Vector Address,
exports :: Vector ExportInstance exports :: Vector ExportInstance
} deriving (Eq, Show) } deriving (Eq, Show)
@@ -310,15 +347,20 @@ emptyModInstance = ModuleInstance {
tableaddrs = Vector.empty, tableaddrs = Vector.empty,
memaddrs = Vector.empty, memaddrs = Vector.empty,
globaladdrs = Vector.empty, globaladdrs = Vector.empty,
elemaddrs = Vector.empty,
dataaddrs = Vector.empty,
exports = Vector.empty exports = Vector.empty
} }
calcInstance :: Store -> Imports -> Module -> Initialize ModuleInstance calcInstance :: Store -> Imports -> Module -> Initialize ModuleInstance
calcInstance (Store fs ts ms gs) imps Module {functions, types, tables, mems, globals, exports, imports} = do calcInstance (Store fs ts ms gs es ds) imps mod = do
let Module {functions, types, tables, mems, globals, exports, imports, elems, datas} = mod
let funLen = length fs let funLen = length fs
let tableLen = length ts let tableLen = length ts
let memLen = length ms let memLen = length ms
let globalLen = length gs let globalLen = length gs
let elemLen = length es
let dataLen = length ds
funImps <- mapM checkImportType $ filter isFuncImport imports funImps <- mapM checkImportType $ filter isFuncImport imports
tableImps <- mapM checkImportType $ filter isTableImport imports tableImps <- mapM checkImportType $ filter isTableImport imports
memImps <- mapM checkImportType $ filter isMemImport imports memImps <- mapM checkImportType $ filter isMemImport imports
@@ -342,6 +384,8 @@ calcInstance (Store fs ts ms gs) imps Module {functions, types, tables, mems, gl
tableaddrs = tbls, tableaddrs = tbls,
memaddrs = memories, memaddrs = memories,
globaladdrs = globs, globaladdrs = globs,
elemaddrs = Vector.fromList [elemLen..elemLen + length elems - 1],
dataaddrs = Vector.fromList [dataLen..dataLen + length datas - 1],
exports = Vector.fromList $ map refExport exports exports = Vector.fromList $ map refExport exports
} }
where where
@@ -357,7 +401,7 @@ calcInstance (Store fs ts ms gs) imps Module {functions, types, tables, mems, gl
funcAddr <- case idx of funcAddr <- case idx of
ExternFunction funcAddr -> return funcAddr ExternFunction funcAddr -> return funcAddr
other -> throwError "incompatible import type" other -> throwError "incompatible import type"
let expectedType = types !! fromIntegral typeIdx let expectedType = types mod !! fromIntegral typeIdx
let actualType = Language.Wasm.Interpreter.funcType $ fs ! funcAddr let actualType = Language.Wasm.Interpreter.funcType $ fs ! funcAddr
if expectedType == actualType if expectedType == actualType
then return idx then return idx
@@ -379,17 +423,18 @@ calcInstance (Store fs ts ms gs) imps Module {functions, types, tables, mems, gl
memAddr <- case idx of memAddr <- case idx of
ExternMemory memAddr -> return memAddr ExternMemory memAddr -> return memAddr
_ -> throwError "incompatible import type" _ -> throwError "incompatible import type"
let MemoryInstance { lim } = ms ! memAddr let MemoryInstance { lim = Limit _ limMax, memory = mem } = ms ! memAddr
if limitMatch lim limit size <- liftIO $ (`quot` pageSize) <$> (readIORef mem >>= ByteArray.getSizeofMutableByteArray)
if limitMatch (Limit (fromIntegral size) limMax) limit
then return idx then return idx
else throwError "incompatible import type" else throwError "incompatible import type"
checkImportType imp@(Import _ _ (ImportTable (TableType limit _))) = do checkImportType imp@(Import _ _ (ImportTable (TableType limit et))) = do
idx <- getImpIdx imp idx <- getImpIdx imp
tableAddr <- case idx of tableAddr <- case idx of
ExternTable tableAddr -> return tableAddr ExternTable tableAddr -> return tableAddr
_ -> throwError "incompatible import type" _ -> throwError "incompatible import type"
let TableInstance { lim } = ts ! tableAddr let TableInstance { t = TableType lim et' } = ts ! tableAddr
if limitMatch lim limit if limitMatch lim limit && et == et'
then return idx then return idx
else throwError "incompatible import type" else throwError "incompatible import type"
@@ -422,6 +467,9 @@ evalConstExpr _ _ [I32Const v] = return $ VI32 v
evalConstExpr _ _ [I64Const v] = return $ VI64 v evalConstExpr _ _ [I64Const v] = return $ VI64 v
evalConstExpr _ _ [F32Const v] = return $ VF32 v evalConstExpr _ _ [F32Const v] = return $ VF32 v
evalConstExpr _ _ [F64Const v] = return $ VF64 v evalConstExpr _ _ [F64Const v] = return $ VF64 v
evalConstExpr _ _ [RefNull FuncRef] = return $ RF Nothing
evalConstExpr _ _ [RefNull ExternRef] = return $ RE Nothing
evalConstExpr _ _ [RefFunc idx] = return $ RF $ Just idx
evalConstExpr inst store [GetGlobal i] = getGlobalValue inst store i evalConstExpr inst store [GetGlobal i] = getGlobalValue inst store i
evalConstExpr _ _ instrs = error $ "Global initializer contains unsupported instructions: " ++ show instrs evalConstExpr _ _ instrs = error $ "Global initializer contains unsupported instructions: " ++ show instrs
@@ -439,15 +487,13 @@ allocAndInitGlobals inst store globs = Vector.fromList <$> mapM allocGlob globs
val <- runIniter initer val <- runIniter initer
GIMut vt <$> newIORef val GIMut vt <$> newIORef val
allocTables :: [Table] -> Vector TableInstance allocTables :: [Table] -> IO (Vector TableInstance)
allocTables tables = Vector.fromList $ map allocTable tables allocTables = fmap Vector.fromList . mapM allocTable
where where
allocTable :: Table -> TableInstance allocTable :: Table -> IO TableInstance
allocTable (Table (TableType lim@(Limit from to) _)) = allocTable (Table t@(TableType lim@(Limit from to) _)) =
TableInstance { let elements = MVector.replicate (fromIntegral from) Nothing in
lim, TableInstance t <$> (elements >>= newIORef)
elements = Vector.fromList $ replicate (fromIntegral from) Nothing
}
defaultBudget :: Natural defaultBudget :: Natural
defaultBudget = 300 defaultBudget = 300
@@ -469,12 +515,30 @@ allocMems mems = Vector.fromList <$> mapM allocMem mems
memory memory
} }
allocElems :: ModuleInstance -> Store -> [ElemSegment] -> IO (Vector ElemInstance)
allocElems inst st = fmap Vector.fromList . mapM allocElem
where
allocElem :: ElemSegment -> IO ElemInstance
allocElem (ElemSegment t mode refs) = do
indexes <- flip mapM refs $ \refExpr -> do
ref <- evalConstExpr inst st refExpr
return $ case ref of
RF v -> RF $ fromIntegral . (funcaddrs inst !) . fromIntegral <$> v
_ -> ref
ElemInstance mode t (Vector.fromList indexes)
<$> newIORef False -- is dropped
allocDatas :: [DataSegment] -> IO (Vector DataInstance)
allocDatas datas = Vector.fromList <$> Monad.forM datas (\DataSegment {dataMode, chunk} -> do
isDropped <- newIORef False
return $ DataInstance dataMode isDropped chunk)
type Initialize = ExceptT String (State.StateT Store IO) type Initialize = ExceptT String (State.StateT Store IO)
initialize :: ModuleInstance -> Module -> Initialize () initialize :: ModuleInstance -> Module -> Initialize ()
initialize inst Module {elems, datas, start} = do initialize inst Module {elems, datas, start} = do
checkedMems <- mapM checkData datas checkedMems <- mapM checkData datas
checkedTables <- mapM checkElem elems checkedTables <- mapM checkElem $ filter isActiveElem $ zip [0..] elems
mapM_ initData checkedMems mapM_ initData checkedMems
mapM_ initElem checkedTables mapM_ initElem checkedTables
st <- State.get st <- State.get
@@ -487,55 +551,72 @@ initialize inst Module {elems, datas, start} = do
_ -> throwError "Start function terminated with trap" _ -> throwError "Start function terminated with trap"
Nothing -> return () Nothing -> return ()
where where
checkElem :: ElemSegment -> Initialize (Address, Int, [Address]) isActiveElem :: (Int, ElemSegment) -> Bool
checkElem ElemSegment {tableIndex, offset, funcIndexes} = do isActiveElem (_, ElemSegment FuncRef (Active _ _) _) = True
isActiveElem _ = False
checkElem :: (Int, ElemSegment) -> Initialize (Address, Address, Int, [Maybe Address])
checkElem (elemN, ElemSegment {elemType, mode, elements}) = do
(tableIndex, offset) <- case mode of {
Active idx off -> return (idx, off);
_ -> throwError "only active mode element can be initialized"
}
st <- State.get st <- State.get
VI32 val <- liftIO $ evalConstExpr inst st offset VI32 val <- liftIO $ evalConstExpr inst st offset
let from = fromIntegral val let from = fromIntegral val
let funcs = map ((funcaddrs inst !) . fromIntegral) funcIndexes refs <- liftIO $ mapM (evalConstExpr inst st) elements
let funcs = map (\(RF ref) -> (funcaddrs inst !) . fromIntegral <$> ref) refs
let idx = tableaddrs inst ! fromIntegral tableIndex let idx = tableaddrs inst ! fromIntegral tableIndex
let last = from + length funcs return (idx, elemaddrs inst ! elemN, from, funcs)
let TableInstance lim elems = tableInstances st ! idx
let len = Vector.length elems
Monad.when (last > len) $ throwError "elements segment does not fit"
return (idx, from, funcs)
initElem :: (Address, Int, [Address]) -> Initialize () initElem :: (Address, Address, Int, [Maybe Address]) -> Initialize ()
initElem (idx, from, funcs) = State.modify $ \st -> initElem (tableIdx, elemIdx, from, funcs) = do
let TableInstance lim elems = tableInstances st ! idx in Store {tableInstances, elemInstances} <- State.get
let table = TableInstance lim (elems // zip [from..] (map Just funcs)) in elems <- liftIO $ readIORef $ items $ tableInstances ! tableIdx
st { tableInstances = tableInstances st Vector.// [(idx, table)] } if from + length funcs > MVector.length elems
then throwError "out of bounds table access"
else do
let ElemInstance {isDropped} = elemInstances ! elemIdx
liftIO $ writeIORef isDropped True
Monad.forM_ (zip [from..] funcs) $ uncurry $ MVector.unsafeWrite elems
checkData :: DataSegment -> Initialize (Int, MemoryStore, LBS.ByteString) checkData :: DataSegment -> Initialize (Maybe (Int, MemoryStore, LBS.ByteString))
checkData DataSegment {memIndex, offset, chunk} = do checkData DataSegment {dataMode = ActiveData memIndex offset, chunk} = do
st <- State.get st <- State.get
VI32 val <- liftIO $ evalConstExpr inst st offset VI32 val <- liftIO $ evalConstExpr inst st offset
let from = fromIntegral val let from = fromIntegral val
let idx = memaddrs inst ! fromIntegral memIndex let idx = memaddrs inst ! fromIntegral memIndex
let last = from + (fromIntegral $ LBS.length chunk)
let MemoryInstance _ memory = memInstances st ! idx let MemoryInstance _ memory = memInstances st ! idx
mem <- liftIO $ readIORef memory mem <- liftIO $ readIORef memory
len <- ByteArray.getSizeofMutableByteArray mem return $ Just (from, mem, chunk)
Monad.when (last > len) $ throwError "data segment does not fit" checkData DataSegment {dataMode = PassiveData, chunk} =
return (from, mem, chunk) return Nothing
initData :: (Int, MemoryStore, LBS.ByteString) -> Initialize () initData :: Maybe (Int, MemoryStore, LBS.ByteString) -> Initialize ()
initData (from, mem, chunk) = initData Nothing = return ()
initData (Just (from, mem, chunk)) = do
let last = from + (fromIntegral $ LBS.length chunk)
len <- ByteArray.getSizeofMutableByteArray mem
Monad.when (last > len) $ throwError "out of bounds memory access"
mapM_ (\(i,b) -> ByteArray.writeByteArray mem i b) $ zip [from..] $ LBS.unpack chunk mapM_ (\(i,b) -> ByteArray.writeByteArray mem i b) $ zip [from..] $ LBS.unpack chunk
instantiate :: Store -> Imports -> Valid.ValidModule -> IO (Either String ModuleInstance, Store) instantiate :: Store -> Imports -> Valid.ValidModule -> IO (Either String ModuleInstance, Store)
instantiate st imps mod = flip State.runStateT st $ runExceptT $ do instantiate st imps mod = flip State.runStateT st $ runExceptT $ do
let m = Valid.getModule mod let m = Valid.getModule mod
inst <- calcInstance st imps m inst <- calcInstance st imps m
let functions = funcInstances st <> (allocFunctions inst $ Struct.functions m) let functions = funcInstances st <> allocFunctions inst (Struct.functions m)
globals <- liftIO $ (globalInstances st <>) <$> (allocAndInitGlobals inst st $ Struct.globals m) globals <- liftIO $ (globalInstances st <>) <$> allocAndInitGlobals inst st (Struct.globals m)
let tables = tableInstances st <> (allocTables $ Struct.tables m) tables <- (tableInstances st <>) <$> liftIO (allocTables (Struct.tables m))
mems <- liftIO $ (memInstances st <>) <$> (allocMems $ Struct.mems m) mems <- liftIO $ (memInstances st <>) <$> allocMems (Struct.mems m)
elems <- liftIO $ (elemInstances st <>) <$> allocElems inst st (Struct.elems m)
datas <- liftIO $ (dataInstances st <>) <$> allocDatas (Struct.datas m)
State.put $ st { State.put $ st {
funcInstances = functions, funcInstances = functions,
tableInstances = tables, tableInstances = tables,
memInstances = mems, memInstances = mems,
globalInstances = globals globalInstances = globals,
elemInstances = elems,
dataInstances = datas
} }
initialize inst m initialize inst m
return inst return inst
@@ -579,6 +660,8 @@ eval budget store FunctionInstance { funcType, moduleInstance, code = Function {
checkValType I64 (VI64 v) = Just $ VI64 v checkValType I64 (VI64 v) = Just $ VI64 v
checkValType F32 (VF32 v) = Just $ VF32 v checkValType F32 (VF32 v) = Just $ VF32 v
checkValType F64 (VF64 v) = Just $ VF64 v checkValType F64 (VF64 v) = Just $ VF64 v
checkValType Func (RF v) = Just $ RF v
checkValType Extern (RE v) = Just $ RE v
checkValType _ _ = Nothing checkValType _ _ = Nothing
initLocal :: ValueType -> Value initLocal :: ValueType -> Value
@@ -699,27 +782,42 @@ eval budget store FunctionInstance { funcType, moduleInstance, code = Function {
Just res -> return $ Done ctx { stack = reverse res ++ (drop (length args) $ stack ctx) } Just res -> return $ Done ctx { stack = reverse res ++ (drop (length args) $ stack ctx) }
Nothing -> return Trap Nothing -> return Trap
Nothing -> return Trap Nothing -> return Trap
step ctx@EvalCtx{ stack = (VI32 v): rest } (CallIndirect typeIdx) = do step ctx@EvalCtx{ stack = (VI32 v): rest } (CallIndirect tableIdx typeIdx) = do
let funcType = funcTypes moduleInstance ! fromIntegral typeIdx let funcType = funcTypes moduleInstance ! fromIntegral typeIdx
let TableInstance { elements } = tableInstances store ! (tableaddrs moduleInstance ! 0) let TableInstance { items } = tableInstances store ! (tableaddrs moduleInstance ! fromIntegral tableIdx)
let checks = do let pos = fromIntegral v
addr <- Monad.join $ elements !? fromIntegral v funcs <- readIORef items
let funcInst = funcInstances store ! addr if pos >= MVector.length funcs
let targetType = Language.Wasm.Interpreter.funcType funcInst then return Trap
Monad.guard $ targetType == funcType else do
let args = params targetType maybeAddr <- MVector.unsafeRead funcs pos
Monad.guard $ length args <= length rest let checks = do
params <- sequence $ zipWith checkValType args $ reverse $ take (length args) rest addr <- maybeAddr
return (funcInst, params) let funcInst = funcInstances store ! addr
case checks of let targetType = Language.Wasm.Interpreter.funcType funcInst
Just (funcInst, params) -> do Monad.guard $ targetType == funcType
res <- eval (budget - 1) store funcInst params let args = params targetType
case res of Monad.guard $ length args <= length rest
Just res -> return $ Done ctx { stack = reverse res ++ (drop (length params) rest) } params <- sequence $ zipWith checkValType args $ reverse $ take (length args) rest
Nothing -> return Trap return (funcInst, params)
Nothing -> return Trap case checks of
Just (funcInst, params) -> do
res <- eval (budget - 1) store funcInst params
case res of
Just res -> return $ Done ctx { stack = reverse res ++ (drop (length params) rest) }
Nothing -> return Trap
Nothing -> return Trap
step ctx@EvalCtx{ stack = st } (RefNull FuncRef) =
return $ Done ctx { stack = RF Nothing : st }
step ctx@EvalCtx{ stack = st } (RefNull ExternRef) =
return $ Done ctx { stack = RE Nothing : st }
step ctx@EvalCtx{ stack = v:rest } RefIsNull =
let r = case v of { RE Nothing -> 1; RF Nothing -> 1; _ -> 0 } in
return $ Done ctx { stack = VI32 r : rest }
step ctx@EvalCtx{ stack = st } (RefFunc index) =
return $ Done ctx { stack = RF (Just index) : st }
step ctx@EvalCtx{ stack = (_:rest) } Drop = return $ Done ctx { stack = rest } step ctx@EvalCtx{ stack = (_:rest) } Drop = return $ Done ctx { stack = rest }
step ctx@EvalCtx{ stack = (VI32 test:val2:val1:rest) } Select = step ctx@EvalCtx{ stack = (VI32 test:val2:val1:rest) } (Select _) =
if test == 0 if test == 0
then return $ Done ctx { stack = val2 : rest } then return $ Done ctx { stack = val2 : rest }
else return $ Done ctx { stack = val1 : rest } else return $ Done ctx { stack = val1 : rest }
@@ -799,12 +897,12 @@ eval budget store FunctionInstance { funcType, moduleInstance, code = Function {
makeStoreInstr @Word16 ctx { stack = rest } offset 2 $ fromIntegral v makeStoreInstr @Word16 ctx { stack = rest } offset 2 $ fromIntegral v
step ctx@EvalCtx{ stack = (VI64 v:rest) } (I64Store32 MemArg { offset }) = step ctx@EvalCtx{ stack = (VI64 v:rest) } (I64Store32 MemArg { offset }) =
makeStoreInstr @Word32 ctx { stack = rest } offset 4 $ fromIntegral v makeStoreInstr @Word32 ctx { stack = rest } offset 4 $ fromIntegral v
step ctx@EvalCtx{ stack = st } CurrentMemory = do step ctx@EvalCtx{ stack = st } MemorySize = do
let MemoryInstance { memory = memoryRef } = memInstances store ! (memaddrs moduleInstance ! 0) let MemoryInstance { memory = memoryRef } = memInstances store ! (memaddrs moduleInstance ! 0)
memory <- readIORef memoryRef memory <- readIORef memoryRef
size <- ((`quot` pageSize) . fromIntegral) <$> ByteArray.getSizeofMutableByteArray memory size <- ((`quot` pageSize) . fromIntegral) <$> ByteArray.getSizeofMutableByteArray memory
return $ Done ctx { stack = VI32 (fromIntegral size) : st } return $ Done ctx { stack = VI32 (fromIntegral size) : st }
step ctx@EvalCtx{ stack = (VI32 n:rest) } GrowMemory = do step ctx@EvalCtx{ stack = (VI32 n:rest) } MemoryGrow = do
let MemoryInstance { lim = limit@(Limit _ maxLen), memory = memoryRef } = memInstances store ! (memaddrs moduleInstance ! 0) let MemoryInstance { lim = limit@(Limit _ maxLen), memory = memoryRef } = memInstances store ! (memaddrs moduleInstance ! 0)
memory <- readIORef memoryRef memory <- readIORef memoryRef
size <- (`quot` pageSize) <$> ByteArray.getSizeofMutableByteArray memory size <- (`quot` pageSize) <$> ByteArray.getSizeofMutableByteArray memory
@@ -822,6 +920,161 @@ eval budget store FunctionInstance { funcType, moduleInstance, code = Function {
else return $ -1 else return $ -1
) )
return $ Done ctx { stack = VI32 (asWord32 $ fromIntegral result) : rest } return $ Done ctx { stack = VI32 (asWord32 $ fromIntegral result) : rest }
step ctx@EvalCtx{ stack = (VI32 n:VI32 v:VI32 d:rest) } MemoryFill = do
let MemoryInstance { memory = memoryRef } = memInstances store ! (memaddrs moduleInstance ! 0)
memory <- readIORef memoryRef
size <- ByteArray.getSizeofMutableByteArray memory
let dest = fromIntegral d
let len = fromIntegral n
if dest + len > size
then return Trap
else do
ByteArray.setByteArray @Word8 memory dest len $ fromIntegral v
return $ Done ctx { stack = rest }
step ctx@EvalCtx{ stack = (VI32 n:VI32 s:VI32 d:rest) } MemoryCopy = do
let MemoryInstance { memory = memoryRef } = memInstances store ! (memaddrs moduleInstance ! 0)
memory <- readIORef memoryRef
size <- ByteArray.getSizeofMutableByteArray memory
let src = fromIntegral s
let dest = fromIntegral d
let len = fromIntegral n
if dest + len > size || src + len > size
then return Trap
else do
ByteArray.copyMutableByteArray memory dest memory src len
return $ Done ctx { stack = rest }
step ctx@EvalCtx{ stack = (VI32 n:VI32 s:VI32 d:rest) } (MemoryInit dataIdx) = do
let DataInstance {bytes, isDropped} = dataInstances store ! (dataaddrs moduleInstance ! fromIntegral dataIdx)
let MemoryInstance { memory = memoryRef } = memInstances store ! (memaddrs moduleInstance ! 0)
memory <- readIORef memoryRef
size <- fromIntegral <$> ByteArray.getSizeofMutableByteArray memory
let src = fromIntegral s
let dest = fromIntegral d
let len = fromIntegral n
dropped <- readIORef isDropped
if (dropped && len > 0) || src + len > LBS.length bytes || dest + len > size
then return Trap
else do
mapM_ (uncurry $ ByteArray.writeByteArray memory) $ zip [fromIntegral d..] $
LBS.unpack $ LBS.take len $ LBS.drop src bytes
return $ Done ctx { stack = rest }
step ctx (DataDrop dataIdx) = do
let DataInstance {isDropped} = dataInstances store ! (dataaddrs moduleInstance ! fromIntegral dataIdx)
writeIORef isDropped True
return $ Done ctx
step ctx@EvalCtx{ stack = (VI32 n:VI32 s:VI32 d:rest) } (TableInit tableIdx elemIdx) = do
let tableAddr = tableaddrs moduleInstance ! fromIntegral tableIdx
let TableInstance { items } = tableInstances store ! tableAddr
let elemAddr = elemaddrs moduleInstance ! fromIntegral elemIdx
let ElemInstance {
eiItems = refs,
eiMode = mode,
isDropped = dropFlag
} = elemInstances store ! elemAddr
let src = fromIntegral s
let dst = fromIntegral d
let len = fromIntegral n
els <- readIORef items
isDropped <- readIORef dropFlag
if src + len > Vector.length refs
|| dst + len > MVector.length els
|| (isDropped && len > 0)
|| isDeclarative mode
then return Trap
else do
Vector.iforM_ (Vector.slice src len refs) $ \idx (RF fn) -> do
MVector.unsafeWrite els (dst + idx) (fromIntegral <$> fn)
return $ Done ctx { stack = rest }
step ctx@EvalCtx{ stack = (VI32 n:VI32 s:VI32 d:rest) } (TableCopy toIdx fromIdx) = do
let fromAddr = tableaddrs moduleInstance ! fromIntegral fromIdx
let TableInstance { items = fromItems } = tableInstances store ! fromAddr
let toAddr = tableaddrs moduleInstance ! fromIntegral toIdx
let TableInstance { items = toItems } = tableInstances store ! toAddr
let src = fromIntegral s
let dst = fromIntegral d
let len = fromIntegral n
fromEls <- readIORef fromItems
toEls <- readIORef toItems
if src + len > MVector.length fromEls || dst + len > MVector.length toEls
then return Trap
else do
let range = if dst <= src then [0..len - 1] else reverse [0..len - 1]
flip mapM_ range $ \off -> do
el <- MVector.unsafeRead fromEls (src + off)
MVector.unsafeWrite toEls (dst + off) el
return $ Done ctx { stack = rest }
step ctx@EvalCtx{ stack = (VI32 n:ref:VI32 i:rest) } (TableFill tableIdx) = do
let tableAddr = tableaddrs moduleInstance ! fromIntegral tableIdx
let TableInstance { items, t } = tableInstances store ! tableAddr
let TableType (Limit _ max) _ = t
let inc = fromIntegral n
let from = fromIntegral i
let val = case ref of
RE extRef -> fromIntegral <$> extRef
RF fnRef -> (funcaddrs moduleInstance !) . fromIntegral <$> fnRef
v -> error "Impossible due to validation"
els <- readIORef items
if from + inc > MVector.length els
then return Trap
else do
Monad.forM_ [0..inc - 1] $ \off ->
MVector.unsafeWrite els (from + off) val
return $ Done ctx { stack = rest }
step ctx@EvalCtx{ stack } (TableSize tableIdx) = do
let tableAddr = tableaddrs moduleInstance ! fromIntegral tableIdx
let TableInstance { items } = tableInstances store ! tableAddr
len <- MVector.length <$> readIORef items
return $ Done ctx { stack = VI32 (fromIntegral len) : stack }
step ctx@EvalCtx{ stack = (VI32 growBy:ref:rest) } (TableGrow tableIdx) = do
let tableAddr = tableaddrs moduleInstance ! fromIntegral tableIdx
let TableInstance { items, t } = tableInstances store ! tableAddr
let TableType (Limit _ max) _ = t
let inc = fromIntegral growBy
let val = case ref of
RE extRef -> extRef
RF fnRef -> fnRef
v -> error "Impossible due to validation"
els <- readIORef items
let currLen = MVector.length els
let newLen = currLen + inc
if maybe False ((newLen >) . fromIntegral) max || newLen > 0xFFFFFFFF
then return $ Done ctx { stack = VI32 (asWord32 $ -1):rest }
else do
newEls <- MVector.grow els inc
writeIORef items newEls
Monad.forM_ [0..inc - 1] $ \off ->
MVector.unsafeWrite newEls (currLen + off) (fromIntegral <$> val)
return $ Done ctx { stack = VI32 (fromIntegral currLen):rest }
step ctx@EvalCtx{ stack = (ref:VI32 offset:rest) } (TableSet tableIdx) = do
let tableAddr = tableaddrs moduleInstance ! fromIntegral tableIdx
let TableInstance { items } = tableInstances store ! tableAddr
let dst = fromIntegral offset
let val = case ref of
RE extRef -> fromIntegral <$> extRef
RF fnRef -> (funcaddrs moduleInstance !) . fromIntegral <$> fnRef
v -> error "Impossible due to validation"
els <- readIORef items
if dst >= MVector.length els
then return Trap
else do
MVector.unsafeWrite els dst val
return $ Done ctx { stack = rest }
step ctx@EvalCtx{ stack = (VI32 offset:rest) } (TableGet tableIdx) = do
let tableAddr = tableaddrs moduleInstance ! fromIntegral tableIdx
let TableInstance { t = TableType _ et, items } = tableInstances store ! tableAddr
let dst = fromIntegral offset
els <- readIORef items
if dst >= MVector.length els
then return Trap
else do
v <- MVector.unsafeRead els dst
let val = (case et of {FuncRef -> RF; ExternRef -> RE}) (fromIntegral <$> v)
return $ Done ctx { stack = val : rest }
step ctx (ElemDrop elemIdx) = do
let elemAddr = elemaddrs moduleInstance ! fromIntegral elemIdx
let ElemInstance {isDropped} = elemInstances store ! elemAddr
writeIORef isDropped True
return $ Done ctx
step ctx (I32Const v) = return $ Done ctx { stack = VI32 v : stack ctx } step ctx (I32Const v) = return $ Done ctx { stack = VI32 v : stack ctx }
step ctx (I64Const v) = return $ Done ctx { stack = VI64 v : stack ctx } step ctx (I64Const v) = return $ Done ctx { stack = VI64 v : stack ctx }
step ctx (F32Const v) = return $ Done ctx { stack = VF32 v : stack ctx } step ctx (F32Const v) = return $ Done ctx { stack = VF32 v : stack ctx }
@@ -1207,3 +1460,8 @@ getGlobalValueByName store ModuleInstance { exports } name =
GIConst _ v -> return v GIConst _ v -> return v
GIMut _ ref -> readIORef ref GIMut _ ref -> readIORef ref
_ -> error $ "Function with name " ++ show name ++ " was not found in module's exports" _ -> error $ "Function with name " ++ show name ++ " was not found in module's exports"
-- | Retrieve mutable memory from the 'Store'
getMemory :: Store -> Address -> Maybe MemoryInstance
getMemory Store{memInstances} address = memInstances !? address
+4
View File
@@ -5,6 +5,8 @@ module Language.Wasm.Lexer (
Lexeme(..), Lexeme(..),
Token(..), Token(..),
AlexPosn(..), AlexPosn(..),
FloatRep(..),
NaN(..),
scanner, scanner,
asFloat, asFloat,
asDouble, asDouble,
@@ -22,6 +24,8 @@ import Data.List (isPrefixOf)
import Text.Read (readEither) import Text.Read (readEither)
import Data.Bits import Data.Bits
import Numeric (showHex) import Numeric (showHex)
import Control.DeepSeq (NFData)
import GHC.Generics (Generic)
} }
+357 -98
View File
@@ -93,6 +93,8 @@ import Language.Wasm.Lexer (
), ),
Lexeme(..), Lexeme(..),
AlexPosn(..), AlexPosn(..),
FloatRep(..),
NaN(..),
asFloat, asFloat,
asDouble, asDouble,
doubleFromInteger doubleFromInteger
@@ -119,6 +121,8 @@ import Language.Wasm.Lexer (
'f64' { Lexeme _ (TKeyword "f64") } 'f64' { Lexeme _ (TKeyword "f64") }
'mut' { Lexeme _ (TKeyword "mut") } 'mut' { Lexeme _ (TKeyword "mut") }
'funcref' { Lexeme _ (TKeyword "funcref") } 'funcref' { Lexeme _ (TKeyword "funcref") }
'externref' { Lexeme _ (TKeyword "externref") }
'extern' { Lexeme _ (TKeyword "extern") }
'type' { Lexeme _ (TKeyword "type") } 'type' { Lexeme _ (TKeyword "type") }
'unreachable' { Lexeme _ (TKeyword "unreachable") } 'unreachable' { Lexeme _ (TKeyword "unreachable") }
'nop' { Lexeme _ (TKeyword "nop") } 'nop' { Lexeme _ (TKeyword "nop") }
@@ -128,6 +132,10 @@ import Language.Wasm.Lexer (
'return' { Lexeme _ (TKeyword "return") } 'return' { Lexeme _ (TKeyword "return") }
'call' { Lexeme _ (TKeyword "call") } 'call' { Lexeme _ (TKeyword "call") }
'call_indirect' { Lexeme _ (TKeyword "call_indirect") } 'call_indirect' { Lexeme _ (TKeyword "call_indirect") }
'ref.null' { Lexeme _ (TKeyword "ref.null") }
'ref.is_null' { Lexeme _ (TKeyword "ref.is_null") }
'ref.func' { Lexeme _ (TKeyword "ref.func") }
'ref.extern' { Lexeme _ (TKeyword "ref.extern") }
'drop' { Lexeme _ (TKeyword "drop") } 'drop' { Lexeme _ (TKeyword "drop") }
'select' { Lexeme _ (TKeyword "select") } 'select' { Lexeme _ (TKeyword "select") }
'get_local' { Lexeme _ (TKeyword "local.get") } 'get_local' { Lexeme _ (TKeyword "local.get") }
@@ -160,6 +168,18 @@ import Language.Wasm.Lexer (
'i64.store32' { Lexeme _ (TKeyword "i64.store32") } 'i64.store32' { Lexeme _ (TKeyword "i64.store32") }
'memory.size' { Lexeme _ (TKeyword "memory.size") } 'memory.size' { Lexeme _ (TKeyword "memory.size") }
'memory.grow' { Lexeme _ (TKeyword "memory.grow") } 'memory.grow' { Lexeme _ (TKeyword "memory.grow") }
'memory.fill' { Lexeme _ (TKeyword "memory.fill") }
'memory.copy' { Lexeme _ (TKeyword "memory.copy") }
'memory.init' { Lexeme _ (TKeyword "memory.init") }
'data.drop' { Lexeme _ (TKeyword "data.drop") }
'table.init' { Lexeme _ (TKeyword "table.init") }
'table.copy' { Lexeme _ (TKeyword "table.copy") }
'table.fill' { Lexeme _ (TKeyword "table.fill") }
'table.size' { Lexeme _ (TKeyword "table.size") }
'table.grow' { Lexeme _ (TKeyword "table.grow") }
'table.get' { Lexeme _ (TKeyword "table.get") }
'table.set' { Lexeme _ (TKeyword "table.set") }
'elem.drop' { Lexeme _ (TKeyword "elem.drop") }
'i32.const' { Lexeme _ (TKeyword "i32.const") } 'i32.const' { Lexeme _ (TKeyword "i32.const") }
'i64.const' { Lexeme _ (TKeyword "i64.const") } 'i64.const' { Lexeme _ (TKeyword "i64.const") }
'f32.const' { Lexeme _ (TKeyword "f32.const") } 'f32.const' { Lexeme _ (TKeyword "f32.const") }
@@ -313,6 +333,8 @@ import Language.Wasm.Lexer (
'export' { Lexeme _ (TKeyword "export") } 'export' { Lexeme _ (TKeyword "export") }
'local' { Lexeme _ (TKeyword "local") } 'local' { Lexeme _ (TKeyword "local") }
'elem' { Lexeme _ (TKeyword "elem") } 'elem' { Lexeme _ (TKeyword "elem") }
'item' { Lexeme _ (TKeyword "item") }
'declare' { Lexeme _ (TKeyword "declare") }
'data' { Lexeme _ (TKeyword "data") } 'data' { Lexeme _ (TKeyword "data") }
'offset' { Lexeme _ (TKeyword "offset") } 'offset' { Lexeme _ (TKeyword "offset") }
'start' { Lexeme _ (TKeyword "start") } 'start' { Lexeme _ (TKeyword "start") }
@@ -363,6 +385,8 @@ valtype :: { ValueType }
| 'i64' { I64 } | 'i64' { I64 }
| 'f32' { F32 } | 'f32' { F32 }
| 'f64' { F64 } | 'f64' { F64 }
| 'funcref' { Func }
| 'externref' { Extern }
index :: { Index } index :: { Index }
: u32 { Index $1 } : u32 { Index $1 }
@@ -389,23 +413,23 @@ int64 :: { Integer }
else Left ("Int literal value is out of signed int64 boundaries: " ++ show $1) else Left ("Int literal value is out of signed int64 boundaries: " ++ show $1)
} }
float32 :: { Float } float32 :: { FloatRep }
: int {% : int {%
let maxInt = 340282356779733623858607532500980858880 in let maxInt = 340282356779733623858607532500980858880 in
if $1 <= maxInt && $1 >= -maxInt if $1 <= maxInt && $1 >= -maxInt
then return $ fromIntegral $1 then return $ BinRep $ fromIntegral $1
else Left "constant out of range" else Left "constant out of range"
} }
| f64 {% asFloat $1 } | f64 { $1 }
float64 :: { Double } float64 :: { FloatRep }
: int {% : int {%
let maxInt = round (maxFinite :: Double) in let maxInt = round (maxFinite :: Double) in
if $1 <= maxInt && $1 >= -maxInt if $1 <= maxInt && $1 >= -maxInt
then doubleFromInteger $1 then fmap BinRep $ doubleFromInteger $1
else Left "constant out of range" else Left "constant out of range"
} }
| f64 {% asDouble $1 } | f64 { $1 }
plaininstr :: { PlainInstr } plaininstr :: { PlainInstr }
-- control instructions -- control instructions
@@ -417,7 +441,11 @@ plaininstr :: { PlainInstr }
| 'return' { Return } | 'return' { Return }
| 'call' index { Call $2 } | 'call' index { Call $2 }
| 'drop' { Drop } | 'drop' { Drop }
| 'select' { Select } -- reference instructions
| 'ref.null' heaptype { RefNull $2 }
| 'ref.is_null' { RefIsNull }
| 'ref.func' index { RefFunc $2 }
| 'ref.extern' u32 { RefExtern $2 }
-- variable instructions -- variable instructions
| 'get_local' index { GetLocal $2 } | 'get_local' index { GetLocal $2 }
| 'set_local' index { SetLocal $2 } | 'set_local' index { SetLocal $2 }
@@ -448,8 +476,25 @@ plaininstr :: { PlainInstr }
| 'i64.store8' memarg1 { I64Store8 $2 } | 'i64.store8' memarg1 { I64Store8 $2 }
| 'i64.store16' memarg2 { I64Store16 $2 } | 'i64.store16' memarg2 { I64Store16 $2 }
| 'i64.store32' memarg4 { I64Store32 $2 } | 'i64.store32' memarg4 { I64Store32 $2 }
| 'memory.size' { CurrentMemory } | 'memory.size' { MemorySize }
| 'memory.grow' { GrowMemory } | 'memory.grow' { MemoryGrow }
| 'memory.fill' { MemoryFill }
| 'memory.copy' { MemoryCopy }
| 'memory.init' index { MemoryInit $2 }
| 'data.drop' index { DataDrop $2 }
-- table instructions
| 'table.init' index opt(index) {
case $3 of
Nothing -> TableInit (Index 0) $2
Just elemIdx -> TableInit $2 elemIdx
}
| 'table.get' opt(index) { TableGet (fromMaybe (Index 0) $2) }
| 'table.set' opt(index) { TableSet (fromMaybe (Index 0) $2) }
| 'table.copy' opt(index) opt(index) { TableCopy (fromMaybe (Index 0) $2) (fromMaybe (Index 0) $3) }
| 'table.fill' opt(index) { TableFill (fromMaybe (Index 0) $2) }
| 'table.size' opt(index) { TableSize (fromMaybe (Index 0) $2) }
| 'table.grow' opt(index) { TableGrow (fromMaybe (Index 0) $2) }
| 'elem.drop' index { ElemDrop $2 }
-- numeric instructions -- numeric instructions
| 'i32.const' int32 { I32Const $2 } | 'i32.const' int32 { I32Const $2 }
| 'i64.const' int64 { I64Const $2 } | 'i64.const' int64 { I64Const $2 }
@@ -657,12 +702,30 @@ memarg4 :: { MemArg }
memarg8 :: { MemArg } memarg8 :: { MemArg }
: opt(offset) opt(align) {% parseMemArg 8 $1 $2 } : opt(offset) opt(align) {% parseMemArg 8 $1 $2 }
select_type_or_instructions(terminator)
: terminator { ($1, Nothing, []) }
| '(' select_type_or_instructions1(terminator) { $2 }
select_type_or_instructions1(terminator)
: 'result' list(valtype) ')' mixed_instruction_list(terminator) {
let (end, instr) = $4 in
(end, Just $2, instr)
}
| folded_instr_list(terminator) {
let (end, instr) = $1 in
(end, Nothing, instr)
}
instruction_list(terminator) instruction_list(terminator)
: terminator { ($1, []) } : terminator { ($1, []) }
| plaininstr mixed_instruction_list(terminator) { ([PlainInstr $1] ++) `fmap` $2 } | plaininstr mixed_instruction_list(terminator) { ([PlainInstr $1] ++) `fmap` $2 }
| 'call_indirect' typeuse(terminator) {% | 'call_indirect' opt(index) typeuse(terminator) {%
let (tu, instr, end) = $2 in let tableIdx = fromMaybe (Index 0) $2 in
onlyAnonimParams tu >> (return (end, [PlainInstr $ CallIndirect tu] ++ instr)) let (tu, instr, end) = $3 in
onlyAnonimParams tu >> (return (end, [PlainInstr $ CallIndirect tableIdx tu] ++ instr))
}
| 'select' select_type_or_instructions(terminator) {
let (end, t, instr) = $2 in (end, [PlainInstr $ Select t] ++ instr)
} }
| 'block' opt(ident) typeuse('end') opt(ident) mixed_instruction_list(terminator) {% do | 'block' opt(ident) typeuse('end') opt(ident) mixed_instruction_list(terminator) {% do
let (tu, instr, _) = $3 let (tu, instr, _) = $3
@@ -700,9 +763,13 @@ folded_instr :: { [Instruction] }
folded_instr1 :: { [Instruction] } folded_instr1 :: { [Instruction] }
: plaininstr mixed_instruction_list(')') { snd $2 ++ [PlainInstr $1] } : plaininstr mixed_instruction_list(')') { snd $2 ++ [PlainInstr $1] }
| 'call_indirect' typeuse(')') {% | 'call_indirect' opt(index) typeuse(')') {%
let (tu, instr, _) = $2 in let tableIdx = fromMaybe (Index 0) $2 in
onlyAnonimParams tu >> (return $ instr ++ [PlainInstr $ CallIndirect tu]) let (tu, instr, _) = $3 in
onlyAnonimParams tu >> (return $ instr ++ [PlainInstr $ CallIndirect tableIdx tu])
}
| 'select' select_type_or_instructions(')') {
let (_, t, instr) = $2 in instr ++ [PlainInstr $ Select t]
} }
| 'block' opt(ident) typeuse(')') {% | 'block' opt(ident) typeuse(')') {%
let (typeUse, instr, _) = $3 in let (typeUse, instr, _) = $3 in
@@ -839,9 +906,12 @@ memory_limits_export_import1 :: { Maybe Ident -> [ModuleField] }
| 'data' datastring ')' ')' { | 'data' datastring ')' ')' {
\ident -> \ident ->
let m = fromIntegral $ LBS.length $2 in let m = fromIntegral $ LBS.length $2 in
let lim = if m `mod` 0x10000 == 0 then m `div` 0x10000 else m `div` 0x10000 + 1 in
-- TODO: unhardcode memory index
let memIdx = fromMaybe (Index 0) $ Named `fmap` ident in
[ [
MFMem $ Memory [] ident $ Limit m $ Just m, MFMem $ Memory [] ident $ Limit lim $ Just lim,
MFData $ DataSegment (fromMaybe (Index 0) $ Named `fmap` ident) [PlainInstr $ I32Const 0] $2 MFData $ DataSegment Nothing (ActiveData memIdx [PlainInstr $ I32Const 0]) $2
] ]
} }
@@ -856,6 +926,11 @@ limits :: { Limit }
elemtype :: { ElemType } elemtype :: { ElemType }
: 'funcref' { FuncRef } : 'funcref' { FuncRef }
| 'externref' { ExternRef }
heaptype :: { ElemType }
: 'func' { FuncRef }
| 'extern' { ExternRef }
tabletype :: { TableType } tabletype :: { TableType }
: limits elemtype { TableType $1 $2 } : limits elemtype { TableType $1 $2 }
@@ -865,15 +940,24 @@ table :: { [ModuleField] }
limits_elemtype_elem :: { Maybe Ident -> [ModuleField] } limits_elemtype_elem :: { Maybe Ident -> [ModuleField] }
: tabletype ')' { \ident -> [MFTable $ Table [] ident $1] } : tabletype ')' { \ident -> [MFTable $ Table [] ident $1] }
| elemtype '(' 'elem' list(index) ')' ')' { | elemtype '(' 'elem' indexes_or_ref_exprs ')' ')' {
\ident -> \ident ->
let funcsLen = fromIntegral $ length $4 in [ let funcsLen = fromIntegral $ length $4 in [
MFTable $ Table [] ident $ TableType (Limit funcsLen (Just funcsLen)) $1, MFTable $ Table [] ident $ TableType (Limit funcsLen (Just funcsLen)) $1,
MFElem $ ElemSegment (fromMaybe (Index 0) $ Named `fmap` ident) [PlainInstr $ I32Const 0] $4 -- TODO: unhardcode table index
let tableIndex = (fromMaybe (Index 0) $ Named `fmap` ident) in
let offset = [PlainInstr $ I32Const 0] in
let elements = $4 in
MFElem $ ElemSegment Nothing FuncRef (Active tableIndex offset) elements
] ]
} }
| '(' import_export_table { $2 } | '(' import_export_table { $2 }
indexes_or_ref_exprs :: {[[Instruction]]}
: index list(index) { funcIndexToExpr ($1:$2) }
| elemexpr list(elemexpr) { $1 : $2 }
| {- empty -} { [] }
import_export_table :: { Maybe Ident -> [ModuleField] } import_export_table :: { Maybe Ident -> [ModuleField] }
: 'import' name name ')' tabletype ')' { : 'import' name name ')' tabletype ')' {
\ident -> [MFImport $ Import [] $2 $3 $ ImportTable ident $5] \ident -> [MFImport $ Import [] $2 $3 $ ImportTable ident $5]
@@ -900,25 +984,58 @@ export :: { Export }
start :: { StartFunction } start :: { StartFunction }
: 'start' index ')' { StartFunction $2 } : 'start' index ')' { StartFunction $2 }
-- TODO: Spec from 09 Jan 2018 declares 'offset' keyword as mandatory, elem :: { ElemSegment }
-- but collection of testcases omits 'offset' in this position : 'elem' opt(ident) elem1 { $3{ ident = $2 } }
-- I am going to support both options for now, but maybe it has to be updated in future.
offsetexpr :: { [Instruction] } elem1 :: { ElemSegment }
: elemlist ')' { let (t, els) = $1 in ElemSegment Nothing t Passive els }
| 'declare' elemlist ')' { let (t, els) = $2 in ElemSegment Nothing t Declarative els }
| '(' elem1_active ')' { $2 }
elem1_active :: { ElemSegment }
: 'table' index ')' '(' elem1_active_offset {
let (offset, t, els) = $5 in ElemSegment Nothing t (Active $2 offset) els
}
| elem1_active_offset {
let (offset, t, els) = $1 in ElemSegment Nothing t (Active (Index 0) offset) els
}
elem1_active_offset :: { ([Instruction], ElemType, [[Instruction]]) }
: 'offset' mixed_instruction_list(')') elemlist { (snd $2, fst $3, snd $3) }
| folded_instr1 elemlist { ($1, fst $2, snd $2) }
elemlist :: { (ElemType, [[Instruction]]) }
: 'func' list(index) { (FuncRef, funcIndexToExpr $2) }
| 'funcref' list(elemexpr) { (FuncRef, $2) }
| 'externref' { (ExternRef, []) }
| list(index) { (FuncRef, funcIndexToExpr $1) }
elemexpr :: { [Instruction] }
: plaininstr { [PlainInstr $1] }
| '(' 'item' mixed_instruction_list(')') { snd $3 }
| '(' folded_instr1 { $2 }
offsetexpr1 :: { [Instruction] }
: 'offset' mixed_instruction_list(')') { snd $2 } : 'offset' mixed_instruction_list(')') { snd $2 }
| folded_instr1 { $1 } | folded_instr1 { $1 }
elemsegment :: { ElemSegment } memory_offsetexpr1 :: { (MemoryIndex, [Instruction]) }
: 'elem' opt(index) '(' offsetexpr list(index) ')' { ElemSegment (fromMaybe (Index 0) $2) $4 $5 } : offsetexpr1 { (Index 0, $1)}
| 'memory' index ')' '(' offsetexpr1 { ($2, $5) }
memory_mode :: { DataMode }
: '(' memory_offsetexpr1 { uncurry ActiveData $2 }
| {- empty -} { PassiveData }
datasegment :: { DataSegment } datasegment :: { DataSegment }
: 'data' opt(index) '(' offsetexpr datastring ')' { DataSegment (fromMaybe (Index 0) $2) $4 $5 } : 'data' opt(ident) memory_mode datastring ')' { DataSegment $2 $3 $4 }
modulefield1_single :: { ModuleField } modulefield1_single :: { ModuleField }
: typedef { MFType $1 } : typedef { MFType $1 }
| import { MFImport $1 } | import { MFImport $1 }
| export { MFExport $1 } | export { MFExport $1 }
| start { MFStart $1 } | start { MFStart $1 }
| elemsegment { MFElem $1 } | elem { MFElem $1 }
| datasegment { MFData $1 } | datasegment { MFData $1 }
| function { $1 } | function { $1 }
| global { $1 } | global { $1 }
@@ -964,11 +1081,15 @@ module1 :: { ModuleDef }
| 'module' opt(ident) list(modulefield) ')' {% RawModDef $2 `fmap` (desugarize $ concat $3) } | 'module' opt(ident) list(modulefield) ')' {% RawModDef $2 `fmap` (desugarize $ concat $3) }
action1 :: { Action } action1 :: { Action }
: 'invoke' opt(ident) string list(folded_instr) ')' { Invoke $2 $3 (map (map constInstructionToValue) $4) } : 'invoke' opt(ident) string list(folded_instr) ')' {%
fmap (Invoke $2 $3) $ (mapM (mapM constInstructionToValue) $4)
}
| 'get' opt(ident) string ')' { Get $2 $3 } | 'get' opt(ident) string ')' { Get $2 $3 }
assertion1 :: { (Maybe AlexPosn, Assertion) } assertion1 :: { (Maybe AlexPosn, Assertion) }
: 'assert_return' '(' action1 list(folded_instr) ')' { ($1, AssertReturn $3 (map (map constInstructionToValue) $4)) } : 'assert_return' '(' action1 list(folded_instr) ')' {%
fmap ((\a -> ($1, a)) . AssertReturn $3) $ (mapM (mapM constInstructionToValue) $4)
}
| 'assert_return_canonical_nan' '(' action1 ')' { ($1, AssertReturnCanonicalNaN $3) } | 'assert_return_canonical_nan' '(' action1 ')' { ($1, AssertReturnCanonicalNaN $3) }
| 'assert_return_arithmetic_nan' '(' action1 ')' { ($1, AssertReturnArithmeticNaN $3) } | 'assert_return_arithmetic_nan' '(' action1 ')' { ($1, AssertReturnArithmeticNaN $3) }
| 'assert_trap' '(' assertion_trap string ')' { ($1, AssertTrap $3 $4) } | 'assert_trap' '(' assertion_trap string ')' { ($1, AssertTrap $3 $4) }
@@ -1084,7 +1205,7 @@ integerToWord64 i
| i < 0 && i >= -(2 ^ 63) = 0xFFFFFFFFFFFFFFFF - (fromIntegral (abs i)) + 1 | i < 0 && i >= -(2 ^ 63) = 0xFFFFFFFFFFFFFFFF - (fromIntegral (abs i)) + 1
| otherwise = error "I64 is out of bounds." | otherwise = error "I64 is out of bounds."
data FuncType = FuncType { params :: [ParamType], results :: [ValueType] } deriving (Show, Eq, Generic, NFData) data FuncType = FuncType { params :: [ParamType], results :: [ValueType] } deriving (Show, Eq)
emptyFuncType :: FuncType emptyFuncType :: FuncType
emptyFuncType = FuncType [] [] emptyFuncType = FuncType [] []
@@ -1092,11 +1213,11 @@ emptyFuncType = FuncType [] []
data ParamType = ParamType { data ParamType = ParamType {
ident :: Maybe Ident, ident :: Maybe Ident,
paramType :: ValueType paramType :: ValueType
} deriving (Show, Eq, Generic, NFData) } deriving (Show, Eq)
newtype Ident = Ident TL.Text deriving (Show, Eq, Generic, NFData) newtype Ident = Ident TL.Text deriving (Show, Eq)
data Index = Named Ident | Index Natural deriving (Show, Eq, Generic, NFData) data Index = Named Ident | Index Natural deriving (Show, Eq)
type LabelIndex = Index type LabelIndex = Index
type FuncIndex = Index type FuncIndex = Index
@@ -1105,6 +1226,8 @@ type LocalIndex = Index
type GlobalIndex = Index type GlobalIndex = Index
type TableIndex = Index type TableIndex = Index
type MemoryIndex = Index type MemoryIndex = Index
type ElemIndex = Index
type DataIndex = Index
data PlainInstr = data PlainInstr =
-- Control instructions -- Control instructions
@@ -1115,10 +1238,15 @@ data PlainInstr =
| BrTable [LabelIndex] LabelIndex | BrTable [LabelIndex] LabelIndex
| Return | Return
| Call FuncIndex | Call FuncIndex
| CallIndirect TypeUse | CallIndirect TableIndex TypeUse
-- Reference instructions
| RefNull ElemType
| RefIsNull
| RefFunc FuncIndex
| RefExtern Natural
-- Parametric instructions -- Parametric instructions
| Drop | Drop
| Select | Select (Maybe [ValueType])
-- Variable instructions -- Variable instructions
| GetLocal LocalIndex | GetLocal LocalIndex
| SetLocal LocalIndex | SetLocal LocalIndex
@@ -1149,13 +1277,26 @@ data PlainInstr =
| I64Store8 MemArg | I64Store8 MemArg
| I64Store16 MemArg | I64Store16 MemArg
| I64Store32 MemArg | I64Store32 MemArg
| CurrentMemory | MemorySize
| GrowMemory | MemoryGrow
| MemoryFill
| MemoryCopy
| MemoryInit DataIndex
| DataDrop DataIndex
-- Table instructions
| TableInit TableIndex ElemIndex
| TableGrow TableIndex
| TableSize TableIndex
| TableFill TableIndex
| TableGet TableIndex
| TableSet TableIndex
| TableCopy TableIndex TableIndex
| ElemDrop ElemIndex
-- Numeric instructions -- Numeric instructions
| I32Const Integer | I32Const Integer
| I64Const Integer | I64Const Integer
| F32Const Float | F32Const FloatRep
| F64Const Double | F64Const FloatRep
| IUnOp BitSize IUnOp | IUnOp BitSize IUnOp
| IBinOp BitSize IBinOp | IBinOp BitSize IBinOp
| I32Eqz | I32Eqz
@@ -1177,14 +1318,14 @@ data PlainInstr =
| F64PromoteF32 | F64PromoteF32
| IReinterpretF BitSize | IReinterpretF BitSize
| FReinterpretI BitSize | FReinterpretI BitSize
deriving (Show, Eq, Generic, NFData) deriving (Show, Eq)
data TypeDef = TypeDef (Maybe Ident) FuncType deriving (Show, Eq, Generic, NFData) data TypeDef = TypeDef (Maybe Ident) FuncType deriving (Show, Eq)
data TypeUse = data TypeUse =
IndexedTypeUse TypeIndex (Maybe FuncType) IndexedTypeUse TypeIndex (Maybe FuncType)
| AnonimousTypeUse FuncType | AnonimousTypeUse FuncType
deriving (Show, Eq, Generic, NFData) deriving (Show, Eq)
emptyTypeUse = AnonimousTypeUse emptyFuncType emptyTypeUse = AnonimousTypeUse emptyFuncType
@@ -1206,26 +1347,26 @@ data Instruction =
trueBranch :: [Instruction], trueBranch :: [Instruction],
falseBranch :: [Instruction] falseBranch :: [Instruction]
} }
deriving (Show, Eq, Generic, NFData) deriving (Show, Eq)
data Import = Import { data Import = Import {
reExportAs :: [TL.Text], reExportAs :: [TL.Text],
sourceModule :: TL.Text, sourceModule :: TL.Text,
name :: TL.Text, name :: TL.Text,
desc :: ImportDesc desc :: ImportDesc
} deriving (Show, Eq, Generic, NFData) } deriving (Show, Eq)
data ImportDesc = data ImportDesc =
ImportFunc (Maybe Ident) TypeUse ImportFunc (Maybe Ident) TypeUse
| ImportTable (Maybe Ident) TableType | ImportTable (Maybe Ident) TableType
| ImportMemory (Maybe Ident) Limit | ImportMemory (Maybe Ident) Limit
| ImportGlobal (Maybe Ident) GlobalType | ImportGlobal (Maybe Ident) GlobalType
deriving (Show, Eq, Generic, NFData) deriving (Show, Eq)
data LocalType = LocalType { data LocalType = LocalType {
ident :: Maybe Ident, ident :: Maybe Ident,
localType :: ValueType localType :: ValueType
} deriving (Show, Eq, Generic, NFData) } deriving (Show, Eq)
data Function = Function { data Function = Function {
exportFuncAs :: [TL.Text], exportFuncAs :: [TL.Text],
@@ -1234,7 +1375,7 @@ data Function = Function {
locals :: [LocalType], locals :: [LocalType],
body :: [Instruction] body :: [Instruction]
} }
deriving (Show, Eq, Generic, NFData) deriving (Show, Eq)
emptyFunction :: Function emptyFunction :: Function
emptyFunction = emptyFunction =
@@ -1252,40 +1393,52 @@ data Global = Global {
globalType :: GlobalType, globalType :: GlobalType,
initializer :: [Instruction] initializer :: [Instruction]
} }
deriving (Show, Eq, Generic, NFData) deriving (Show, Eq)
data Memory = Memory [TL.Text] (Maybe Ident) Limit deriving (Show, Eq, Generic, NFData) data Memory = Memory [TL.Text] (Maybe Ident) Limit deriving (Show, Eq)
data Table = Table [TL.Text] (Maybe Ident) TableType deriving (Show, Eq, Generic, NFData) data Table = Table [TL.Text] (Maybe Ident) TableType deriving (Show, Eq)
data ExportDesc = data ExportDesc =
ExportFunc FuncIndex ExportFunc FuncIndex
| ExportTable TableIndex | ExportTable TableIndex
| ExportMemory MemoryIndex | ExportMemory MemoryIndex
| ExportGlobal GlobalIndex | ExportGlobal GlobalIndex
deriving (Show, Eq, Generic, NFData) deriving (Show, Eq)
data Export = Export { data Export = Export {
name :: TL.Text, name :: TL.Text,
desc :: ExportDesc desc :: ExportDesc
} }
deriving (Show, Eq, Generic, NFData) deriving (Show, Eq)
data StartFunction = StartFunction FuncIndex deriving (Show, Eq, Generic, NFData) data StartFunction = StartFunction FuncIndex deriving (Show, Eq)
data ElemMode
= Passive
| Active TableIndex [Instruction]
| Declarative
deriving (Show, Eq)
data ElemSegment = ElemSegment { data ElemSegment = ElemSegment {
tableIndex :: TableIndex, ident :: Maybe Ident,
offset :: [Instruction], elemType :: ElemType,
funcIndexes :: [FuncIndex] mode :: ElemMode,
elements :: [[Instruction]]
} }
deriving (Show, Eq, Generic, NFData) deriving (Show, Eq)
data DataMode =
PassiveData
| ActiveData MemoryIndex [Instruction]
deriving (Show, Eq)
data DataSegment = DataSegment { data DataSegment = DataSegment {
memIndex :: MemoryIndex, ident :: Maybe Ident,
offset :: [Instruction], dataMode :: DataMode,
datastring :: LBS.ByteString datastring :: LBS.ByteString
} }
deriving (Show, Eq, Generic, NFData) deriving (Show, Eq)
data ModuleField = data ModuleField =
MFType TypeDef MFType TypeDef
@@ -1298,7 +1451,7 @@ data ModuleField =
| MFStart StartFunction | MFStart StartFunction
| MFElem ElemSegment | MFElem ElemSegment
| MFData DataSegment | MFData DataSegment
deriving(Show, Eq, Generic, NFData) deriving(Show, Eq)
happyError (Lexeme _ EOF : []) = Left $ "Error occuried during parsing phase at the end of file" happyError (Lexeme _ EOF : []) = Left $ "Error occuried during parsing phase at the end of file"
happyError (Lexeme Nothing tok : tokens) = Left $ "Error occuried during parsing phase at the end of file" happyError (Lexeme Nothing tok : tokens) = Left $ "Error occuried during parsing phase at the end of file"
@@ -1371,12 +1524,17 @@ data FunCtx = FunCtx {
ctxParams :: [ParamType] ctxParams :: [ParamType]
} deriving (Eq, Show) } deriving (Eq, Show)
constInstructionToValue :: Instruction -> S.Instruction Natural constInstructionToValue :: Instruction -> Either String (S.Instruction Natural)
constInstructionToValue (PlainInstr (I32Const v)) = S.I32Const $ integerToWord32 v constInstructionToValue (PlainInstr (I32Const v)) = return $ S.I32Const $ integerToWord32 v
constInstructionToValue (PlainInstr (F32Const v)) = S.F32Const v constInstructionToValue (PlainInstr (F32Const v)) = S.F32Const <$> asFloat v
constInstructionToValue (PlainInstr (I64Const v)) = S.I64Const $ integerToWord64 v constInstructionToValue (PlainInstr (I64Const v)) = return $ S.I64Const $ integerToWord64 v
constInstructionToValue (PlainInstr (F64Const v)) = S.F64Const v constInstructionToValue (PlainInstr (F64Const v)) = S.F64Const <$> asDouble v
constInstructionToValue _ = error "Only const instructions supported as arguments for actions" constInstructionToValue (PlainInstr (RefNull et)) = return $ S.RefNull et
constInstructionToValue (PlainInstr (RefExtern n)) = return $ S.RefExtern n
constInstructionToValue _ = Left "Only const instructions supported as arguments for actions"
funcIndexToExpr :: [FuncIndex] -> [[Instruction]]
funcIndexToExpr = map $ (:[]) . PlainInstr . RefFunc
desugarize :: [ModuleField] -> Either String S.Module desugarize :: [ModuleField] -> Either String S.Module
desugarize fields = do desugarize fields = do
@@ -1463,17 +1621,13 @@ desugarize fields = do
extractTypeDefFromInstructions (matchTypeUse defs funcType) body extractTypeDefFromInstructions (matchTypeUse defs funcType) body
extractTypeDef defs (MFGlobal Global { initializer }) = extractTypeDef defs (MFGlobal Global { initializer }) =
extractTypeDefFromInstructions defs initializer extractTypeDefFromInstructions defs initializer
extractTypeDef defs (MFElem ElemSegment { offset }) =
extractTypeDefFromInstructions defs offset
extractTypeDef defs (MFData DataSegment { offset }) =
extractTypeDefFromInstructions defs offset
extractTypeDef defs _ = defs extractTypeDef defs _ = defs
extractTypeDefFromInstructions :: [TypeDef] -> [Instruction] -> [TypeDef] extractTypeDefFromInstructions :: [TypeDef] -> [Instruction] -> [TypeDef]
extractTypeDefFromInstructions = foldl' extractTypeDefFromInstruction extractTypeDefFromInstructions = foldl' extractTypeDefFromInstruction
extractTypeDefFromInstruction :: [TypeDef] -> Instruction -> [TypeDef] extractTypeDefFromInstruction :: [TypeDef] -> Instruction -> [TypeDef]
extractTypeDefFromInstruction defs (PlainInstr (CallIndirect typeUse)) = extractTypeDefFromInstruction defs (PlainInstr (CallIndirect _ typeUse)) =
matchTypeUse defs typeUse matchTypeUse defs typeUse
extractTypeDefFromInstruction defs (BlockInstr { body, blockType }) = extractTypeDefFromInstruction defs (BlockInstr { body, blockType }) =
extractTypeDefFromInstructions (matchTypeUse defs blockType) body extractTypeDefFromInstructions (matchTypeUse defs blockType) body
@@ -1556,12 +1710,23 @@ desugarize fields = do
case getFuncIndex ctxMod funIdx of case getFuncIndex ctxMod funIdx of
Just idx -> return $ S.Call idx Just idx -> return $ S.Call idx
Nothing -> Left "unknown function" Nothing -> Left "unknown function"
synInstrToStruct FunCtx { ctxMod = Module { types } } (PlainInstr (CallIndirect typeUse)) = synInstrToStruct FunCtx { ctxMod } (PlainInstr (CallIndirect tableIdx typeUse)) =
case getTypeIndex types typeUse of case getTableIndex ctxMod tableIdx of
Just idx -> return $ S.CallIndirect idx Just tableIdx ->
Nothing -> Left "unknown type" case getTypeIndex (types ctxMod) typeUse of
Just idx -> return $ S.CallIndirect tableIdx idx
Nothing -> Left "unknown type"
Nothing -> Left "unknown table"
synInstrToStruct _ (PlainInstr Drop) = return $ S.Drop synInstrToStruct _ (PlainInstr Drop) = return $ S.Drop
synInstrToStruct _ (PlainInstr Select) = return $ S.Select synInstrToStruct _ (PlainInstr (Select vt)) = return $ S.Select vt
synInstrToStruct _ (PlainInstr (RefNull elType)) = return $ S.RefNull elType
synInstrToStruct _ (PlainInstr RefIsNull) = return $ S.RefIsNull
synInstrToStruct FunCtx { ctxMod } (PlainInstr (RefFunc funIdx)) =
case getFuncIndex ctxMod funIdx of
Just idx -> return $ S.RefFunc idx
Nothing -> Left "unknown function"
synInstrToStruct FunCtx { ctxMod } (PlainInstr (RefExtern idx)) =
return $ S.RefExtern idx
synInstrToStruct ctx (PlainInstr (GetLocal localIdx)) = synInstrToStruct ctx (PlainInstr (GetLocal localIdx)) =
case getLocalIndex ctx localIdx of case getLocalIndex ctx localIdx of
Just idx -> return $ S.GetLocal idx Just idx -> return $ S.GetLocal idx
@@ -1605,12 +1770,68 @@ desugarize fields = do
synInstrToStruct _ (PlainInstr (I64Store8 memArg)) = return $ S.I64Store8 memArg synInstrToStruct _ (PlainInstr (I64Store8 memArg)) = return $ S.I64Store8 memArg
synInstrToStruct _ (PlainInstr (I64Store16 memArg)) = return $ S.I64Store16 memArg synInstrToStruct _ (PlainInstr (I64Store16 memArg)) = return $ S.I64Store16 memArg
synInstrToStruct _ (PlainInstr (I64Store32 memArg)) = return $ S.I64Store32 memArg synInstrToStruct _ (PlainInstr (I64Store32 memArg)) = return $ S.I64Store32 memArg
synInstrToStruct _ (PlainInstr CurrentMemory) = return $ S.CurrentMemory synInstrToStruct _ (PlainInstr MemorySize) = return $ S.MemorySize
synInstrToStruct _ (PlainInstr GrowMemory) = return $ S.GrowMemory synInstrToStruct _ (PlainInstr MemoryGrow) = return $ S.MemoryGrow
synInstrToStruct _ (PlainInstr MemoryFill) = return $ S.MemoryFill
synInstrToStruct _ (PlainInstr MemoryCopy) = return $ S.MemoryCopy
synInstrToStruct FunCtx { ctxMod } (PlainInstr (MemoryInit dataIdx)) =
case getDataIndex ctxMod dataIdx of
Just dataIdx -> return $ S.MemoryInit dataIdx
Nothing -> Left "unknown data"
synInstrToStruct FunCtx { ctxMod } (PlainInstr (DataDrop dataIdx)) =
case getDataIndex ctxMod dataIdx of
Just dataIdx -> return $ S.DataDrop dataIdx
Nothing -> Left "unknown data"
synInstrToStruct FunCtx { ctxMod } (PlainInstr (TableInit tableIdx elemIdx)) =
case getTableIndex ctxMod tableIdx of
Just tableIdx ->
case getElemIndex ctxMod elemIdx of
Just elemIdx -> return $ S.TableInit tableIdx elemIdx
Nothing -> Left "unknown elem"
Nothing -> Left "unknown table"
synInstrToStruct FunCtx { ctxMod } (PlainInstr (TableCopy toIdx fromIdx)) =
case getTableIndex ctxMod fromIdx of
Just fromIdx ->
case getTableIndex ctxMod toIdx of
Just toIdx -> return $ S.TableCopy toIdx fromIdx
Nothing -> Left "unknown table"
Nothing -> Left "unknown table"
synInstrToStruct FunCtx { ctxMod } (PlainInstr (TableSize tableIdx)) =
case getTableIndex ctxMod tableIdx of
Just tableIdx -> return $ S.TableSize tableIdx
Nothing -> Left "unknown table"
synInstrToStruct FunCtx { ctxMod } (PlainInstr (TableFill tableIdx)) =
case getTableIndex ctxMod tableIdx of
Just tableIdx -> return $ S.TableFill tableIdx
Nothing -> Left "unknown table"
synInstrToStruct FunCtx { ctxMod } (PlainInstr (TableGrow tableIdx)) =
case getTableIndex ctxMod tableIdx of
Just tableIdx -> return $ S.TableGrow tableIdx
Nothing -> Left "unknown table"
synInstrToStruct FunCtx { ctxMod } (PlainInstr (TableSet tableIdx)) =
case getTableIndex ctxMod tableIdx of
Just tableIdx -> return $ S.TableSet tableIdx
Nothing -> Left "unknown table"
synInstrToStruct FunCtx { ctxMod } (PlainInstr (TableGet tableIdx)) =
case getTableIndex ctxMod tableIdx of
Just tableIdx -> return $ S.TableGet tableIdx
Nothing -> Left "unknown table"
synInstrToStruct FunCtx { ctxMod } (PlainInstr (ElemDrop elemIdx)) =
case getElemIndex ctxMod elemIdx of
Just elemIdx -> return $ S.ElemDrop elemIdx
Nothing -> Left "unknown elem"
synInstrToStruct _ (PlainInstr (I32Const val)) = return $ S.I32Const $ integerToWord32 val synInstrToStruct _ (PlainInstr (I32Const val)) = return $ S.I32Const $ integerToWord32 val
synInstrToStruct _ (PlainInstr (I64Const val)) = return $ S.I64Const $ integerToWord64 val synInstrToStruct _ (PlainInstr (I64Const val)) = return $ S.I64Const $ integerToWord64 val
synInstrToStruct _ (PlainInstr (F32Const val)) = return $ S.F32Const val synInstrToStruct _ (PlainInstr (F32Const (NanRep Arithmetic))) =
synInstrToStruct _ (PlainInstr (F64Const val)) = return $ S.F64Const val Left "arithmetic nan constant allowed only in script"
synInstrToStruct _ (PlainInstr (F32Const (NanRep Canonical))) =
Left "canonical nan constant allowed only in script"
synInstrToStruct _ (PlainInstr (F32Const rep)) = S.F32Const <$> asFloat rep
synInstrToStruct _ (PlainInstr (F64Const (NanRep Arithmetic))) =
Left "arithmetic nan constant allowed only in script"
synInstrToStruct _ (PlainInstr (F64Const (NanRep Canonical))) =
Left "canonical nan constant allowed only in script"
synInstrToStruct _ (PlainInstr (F64Const rep)) = S.F64Const <$> asDouble rep
synInstrToStruct _ (PlainInstr (IUnOp sz op)) = return $ S.IUnOp sz op synInstrToStruct _ (PlainInstr (IUnOp sz op)) = return $ S.IUnOp sz op
synInstrToStruct _ (PlainInstr (IBinOp sz op)) = return $ S.IBinOp sz op synInstrToStruct _ (PlainInstr (IBinOp sz op)) = return $ S.IBinOp sz op
synInstrToStruct _ (PlainInstr I32Eqz) = return $ S.I32Eqz synInstrToStruct _ (PlainInstr I32Eqz) = return $ S.I32Eqz
@@ -1878,29 +2099,59 @@ desugarize fields = do
-- elem segment -- elem segment
synElemToStruct :: Module -> ElemSegment -> Either String S.ElemSegment synElemToStruct :: Module -> ElemSegment -> Either String S.ElemSegment
synElemToStruct mod ElemSegment { tableIndex, offset, funcIndexes } = synElemToStruct mod ElemSegment { ident, elemType, mode, elements } = do
let ctx = FunCtx mod [] [] [] in let ctx = FunCtx mod [] [] []
let offsetInstrs = mapM (synInstrToStruct ctx) offset in m <- case mode of {
let idx = fromJust $ getTableIndex mod tableIndex in Active tableIndex offset ->
let indexes = map (fromJust . getFuncIndex mod) funcIndexes in let offsetInstrs = mapM (synInstrToStruct ctx) offset in
S.ElemSegment idx <$> offsetInstrs <*> return indexes let idx = fromJust $ getTableIndex mod tableIndex in
S.Active idx <$> offsetInstrs;
Passive -> return S.Passive;
Declarative -> return S.Declarative
}
let elemExprs = mapM (mapM (synInstrToStruct ctx)) elements
S.ElemSegment elemType m <$> elemExprs
extractElemSegment :: [ElemSegment] -> ModuleField -> [ElemSegment] extractElemSegment :: [ElemSegment] -> ModuleField -> [ElemSegment]
extractElemSegment elems (MFElem elem) = elem : elems extractElemSegment elems (MFElem elem) = elem : elems
extractElemSegment elems _ = elems extractElemSegment elems _ = elems
getElemIndex :: Module -> GlobalIndex -> Maybe Natural
getElemIndex mod@Module { elems } (Named id) =
let isIdent (_, ElemSegment { ident }) = ident == Just id in
let elemIndexes = map fst $ filter isIdent $ zip [0..] elems in
case elemIndexes of
[idx] -> return idx
_ -> Nothing
getElemIndex _ (Index idx) = Just idx
-- data segment -- data segment
synDataToStruct :: Module -> DataSegment -> Either String S.DataSegment synDataToStruct :: Module -> DataSegment -> Either String S.DataSegment
synDataToStruct mod DataSegment { memIndex, offset, datastring } = synDataToStruct mod DataSegment { dataMode, datastring } = do
let ctx = FunCtx mod [] [] [] in m <- case dataMode of
let offsetInstrs = mapM (synInstrToStruct ctx) offset in PassiveData -> return S.PassiveData
let idx = fromJust $ getMemIndex mod memIndex in ActiveData memIndex offset -> do
S.DataSegment idx <$> offsetInstrs <*> return datastring let ctx = FunCtx mod [] [] []
offsetInstrs <- mapM (synInstrToStruct ctx) offset
idx <- case getMemIndex mod memIndex of
Just idx -> return idx
Nothing -> throwError "unknown memory"
return $ S.ActiveData idx offsetInstrs
return $ S.DataSegment m datastring
extractDataSegment :: [DataSegment] -> ModuleField -> [DataSegment] extractDataSegment :: [DataSegment] -> ModuleField -> [DataSegment]
extractDataSegment datas (MFData dataSegment) = dataSegment : datas extractDataSegment datas (MFData dataSegment) = dataSegment : datas
extractDataSegment datas _ = datas extractDataSegment datas _ = datas
getDataIndex :: Module -> GlobalIndex -> Maybe Natural
getDataIndex mod@Module { datas } (Named id) =
let isIdent (_, DataSegment { ident }) = ident == Just id in
let dataIndexes = map fst $ filter isIdent $ zip [0..] datas in
case dataIndexes of
[idx] -> return idx
_ -> Nothing
getDataIndex _ (Index idx) = Just idx
-- start -- start
synStartToStruct :: Module -> StartFunction -> S.StartFunction synStartToStruct :: Module -> StartFunction -> S.StartFunction
synStartToStruct mod (StartFunction funIdx) = synStartToStruct mod (StartFunction funIdx) =
@@ -1916,14 +2167,22 @@ desugarize fields = do
-- exports -- exports
extractExports :: Module -> [ModuleField] -> [ModuleField] extractExports :: Module -> [ModuleField] -> [ModuleField]
extractExports mod mf = extractExports mod mf =
let initial = (funcImportLength, globImportLength, memImportLength, tableImportLength, []) in let fromImports = foldl' reexport (0, 0, 0, 0, []) $ imports mod in
let (_, _, _, _, result) = foldl' extractExport initial mf in let (_, _, _, _, result) = foldl' extractExport fromImports mf in
reverse result reverse result
where where
funcImportLength = fromIntegral $ length $ filter isFuncImport $ imports mod reexport (fidx, gidx, midx, tidx, mf) (Import {reExportAs, desc = ImportFunc _ _}) =
globImportLength = fromIntegral $ length $ filter isGlobalImport $ imports mod let exports = map (\name -> MFExport $ Export name $ ExportFunc $ Index fidx) reExportAs in
memImportLength = fromIntegral $ length $ filter isMemImport $ imports mod (fidx + 1, gidx, midx, tidx, exports ++ mf)
tableImportLength = fromIntegral $ length $ filter isTableImport $ imports mod reexport (fidx, gidx, midx, tidx, mf) (Import {reExportAs, desc = ImportGlobal _ _}) =
let exports = map (\name -> MFExport $ Export name $ ExportGlobal $ Index gidx) reExportAs in
(fidx, gidx + 1, midx, tidx, exports ++ mf)
reexport (fidx, gidx, midx, tidx, mf) (Import {reExportAs, desc = ImportMemory _ _}) =
let exports = map (\name -> MFExport $ Export name $ ExportMemory $ Index midx) reExportAs in
(fidx, gidx, midx + 1, tidx, exports ++ mf)
reexport (fidx, gidx, midx, tidx, mf) (Import {reExportAs, desc = ImportTable _ _}) =
let exports = map (\name -> MFExport $ Export name $ ExportTable $ Index tidx) reExportAs in
(fidx, gidx, midx, tidx + 1, exports ++ mf)
extractExport (fidx, gidx, midx, tidx, mf) (MFFunc fun@Function{ exportFuncAs }) = extractExport (fidx, gidx, midx, tidx, mf) (MFFunc fun@Function{ exportFuncAs }) =
let exports = map (\name -> MFExport $ Export name $ ExportFunc $ Index fidx) exportFuncAs in let exports = map (\name -> MFExport $ Export name $ ExportFunc $ Index fidx) exportFuncAs in
+18 -7
View File
@@ -13,6 +13,7 @@ import Control.Monad.IO.Class (liftIO)
import Numeric.IEEE (identicalIEEE) import Numeric.IEEE (identicalIEEE)
import qualified Control.DeepSeq as DeepSeq import qualified Control.DeepSeq as DeepSeq
import Data.Maybe (fromJust, isNothing) import Data.Maybe (fromJust, isNothing)
import Debug.Trace (trace)
import Language.Wasm.Parser ( import Language.Wasm.Parser (
Ident(..), Ident(..),
@@ -122,7 +123,10 @@ runScript onAssertFail script = do
asArg [Struct.F32Const v] = Interpreter.VF32 v asArg [Struct.F32Const v] = Interpreter.VF32 v
asArg [Struct.I64Const v] = Interpreter.VI64 v asArg [Struct.I64Const v] = Interpreter.VI64 v
asArg [Struct.F64Const v] = Interpreter.VF64 v asArg [Struct.F64Const v] = Interpreter.VF64 v
asArg _ = error "Only const instructions supported as arguments for actions" asArg [Struct.RefNull Struct.FuncRef] = Interpreter.RF Nothing
asArg [Struct.RefNull Struct.ExternRef] = Interpreter.RE Nothing
asArg [Struct.RefExtern v] = Interpreter.RE (Just v)
asArg expr = error $ "Only const instructions supported as arguments for actions: " ++ show expr
runAction :: ScriptState -> Action -> IO (Maybe [Interpreter.Value]) runAction :: ScriptState -> Action -> IO (Maybe [Interpreter.Value])
runAction st (Invoke ident name args) = do runAction st (Invoke ident name args) = do
@@ -139,6 +143,8 @@ runScript onAssertFail script = do
isValueEqual (Interpreter.VI64 v1) (Interpreter.VI64 v2) = v1 == v2 isValueEqual (Interpreter.VI64 v1) (Interpreter.VI64 v2) = v1 == v2
isValueEqual (Interpreter.VF32 v1) (Interpreter.VF32 v2) = (isNaN v1 && isNaN v2) || identicalIEEE v1 v2 isValueEqual (Interpreter.VF32 v1) (Interpreter.VF32 v2) = (isNaN v1 && isNaN v2) || identicalIEEE v1 v2
isValueEqual (Interpreter.VF64 v1) (Interpreter.VF64 v2) = (isNaN v1 && isNaN v2) || identicalIEEE v1 v2 isValueEqual (Interpreter.VF64 v1) (Interpreter.VF64 v2) = (isNaN v1 && isNaN v2) || identicalIEEE v1 v2
isValueEqual (Interpreter.RF f1) (Interpreter.RF f2) = f1 == f2
isValueEqual (Interpreter.RE e1) (Interpreter.RE e2) = e1 == e2
isValueEqual _ _ = False isValueEqual _ _ = False
isNaNReturned :: Action -> Assertion -> AssertM () isNaNReturned :: Action -> Assertion -> AssertM ()
@@ -161,7 +167,7 @@ runScript onAssertFail script = do
let Right m = Lexer.scanner (TLEncoding.encodeUtf8 textRep) >>= Parser.parseModule in let Right m = Lexer.scanner (TLEncoding.encodeUtf8 textRep) >>= Parser.parseModule in
(ident, m) (ident, m)
buildModule (BinaryModDef ident binaryRep) = buildModule (BinaryModDef ident binaryRep) =
let Right m = Binary.decodeModuleLazy binaryRep in let Right m = Binary.decodeModuleLazy binaryRep in
(ident, m) (ident, m)
checkModuleInvalid :: Struct.Module -> IO () checkModuleInvalid :: Struct.Module -> IO ()
@@ -169,13 +175,13 @@ runScript onAssertFail script = do
getFailureString :: Validate.ValidationError -> [TL.Text] getFailureString :: Validate.ValidationError -> [TL.Text]
getFailureString (Validate.TypeMismatch _ _) = ["type mismatch"] getFailureString (Validate.TypeMismatch _ _) = ["type mismatch"]
getFailureString (Validate.RefTypeMismatch _ _) = ["type mismatch"]
getFailureString Validate.ResultTypeDoesntMatch = ["type mismatch"] getFailureString Validate.ResultTypeDoesntMatch = ["type mismatch"]
getFailureString Validate.MoreThanOneMemory = ["multiple memories"] getFailureString Validate.MoreThanOneMemory = ["multiple memories"]
getFailureString Validate.MoreThanOneTable = ["multiple tables"]
getFailureString (Validate.LocalIndexOutOfRange idx) = ["unknown local", "unknown local " <> TL.pack (show idx)] getFailureString (Validate.LocalIndexOutOfRange idx) = ["unknown local", "unknown local " <> TL.pack (show idx)]
getFailureString (Validate.MemoryIndexOutOfRange idx) = ["unknown memory", "unknown memory " <> TL.pack (show idx)] getFailureString (Validate.MemoryIndexOutOfRange idx) = ["unknown memory", "unknown memory " <> TL.pack (show idx)]
getFailureString (Validate.TableIndexOutOfRange idx) = ["unknown table", "unknown table " <> TL.pack (show idx)] getFailureString (Validate.TableIndexOutOfRange idx) = ["unknown table", "unknown table " <> TL.pack (show idx)]
getFailureString Validate.FunctionIndexOutOfRange = ["unknown function", "unknown function 0"] getFailureString (Validate.FunctionIndexOutOfRange idx) = ["unknown function", "unknown function " <> TL.pack (show idx)]
getFailureString (Validate.GlobalIndexOutOfRange idx) = ["unknown global", "unknown global " <> TL.pack (show idx)] getFailureString (Validate.GlobalIndexOutOfRange idx) = ["unknown global", "unknown global " <> TL.pack (show idx)]
getFailureString Validate.LabelIndexOutOfRange = ["unknown label"] getFailureString Validate.LabelIndexOutOfRange = ["unknown label"]
getFailureString Validate.TypeIndexOutOfRange = ["unknown type"] getFailureString Validate.TypeIndexOutOfRange = ["unknown type"]
@@ -188,7 +194,10 @@ runScript onAssertFail script = do
getFailureString Validate.GlobalIsImmutable = ["global is immutable"] getFailureString Validate.GlobalIsImmutable = ["global is immutable"]
getFailureString Validate.InvalidStartFunctionType = ["start function"] getFailureString Validate.InvalidStartFunctionType = ["start function"]
getFailureString Validate.InvalidTableType = ["size minimum must not be greater than maximum"] getFailureString Validate.InvalidTableType = ["size minimum must not be greater than maximum"]
getFailureString r = [TL.concat ["not implemented ", (TL.pack $ show r)]] getFailureString (Validate.ElemIndexOutOfRange idx) = ["unknown elem segment " <> TL.pack (show idx)]
getFailureString (Validate.DataIndexOutOfRange idx) = ["unknown data segment", "unknown data segment " <> TL.pack (show idx)]
getFailureString (Validate.UndeclaredFunctionRef _) = ["undeclared function reference"]
getFailureString r = [TL.concat ["not implemented ", TL.pack $ show r]]
printFailedAssert :: String -> Assertion -> AssertM () printFailedAssert :: String -> Assertion -> AssertM ()
printFailedAssert msg assert = do printFailedAssert msg assert = do
@@ -253,12 +262,14 @@ runScript onAssertFail script = do
let (_, m) = buildModule moduleDef in let (_, m) = buildModule moduleDef in
case Validate.validate m of case Validate.validate m of
Right m -> do Right m -> do
st <- fst <$> State.get (st, pos) <- State.get
(res, store') <- liftIO $ Interpreter.instantiate (store st) (buildImports st) m (res, store') <- liftIO $ Interpreter.instantiate (store st) (buildImports st) m
State.put (st { store = store' }, pos)
case res of case res of
Left err | err == TL.unpack failureString -> return ()
Left "Start function terminated with trap" -> Left "Start function terminated with trap" ->
State.modify $ \(st, pos) -> (st { store = store' }, pos) State.modify $ \(st, pos) -> (st { store = store' }, pos)
_ -> printFailedAssert ("Module linking should fail with trap during execution of a start function") assert r -> printFailedAssert "Module linking should fail with trap during execution of a start function" assert
Left reason -> error $ "Module linking failed due to invalid module with reason: " ++ show reason Left reason -> error $ "Module linking failed due to invalid module with reason: " ++ show reason
runAssert assert@(AssertExhaustion action failureString) = do runAssert assert@(AssertExhaustion action failureString) = do
result <- runActionInAssert action result <- runActionInAssert action
+44 -10
View File
@@ -4,8 +4,10 @@
module Language.Wasm.Structure ( module Language.Wasm.Structure (
Module(..), Module(..),
DataMode(..),
DataSegment(..), DataSegment(..),
ElemSegment(..), ElemSegment(..),
ElemMode(..),
StartFunction(..), StartFunction(..),
Export(..), Export(..),
ExportDesc(..), ExportDesc(..),
@@ -102,12 +104,16 @@ type LocalIndex = Natural
type GlobalIndex = Natural type GlobalIndex = Natural
type MemoryIndex = Natural type MemoryIndex = Natural
type TableIndex = Natural type TableIndex = Natural
type DataIndex = Natural
type ElemIndex = Natural
data ValueType = data ValueType =
I32 I32
| I64 | I64
| F32 | F32
| F64 | F64
| Func
| Extern
deriving (Show, Eq, Generic, NFData) deriving (Show, Eq, Generic, NFData)
type ResultType = [ValueType] type ResultType = [ValueType]
@@ -133,10 +139,15 @@ data Instruction index =
| BrTable [index] index | BrTable [index] index
| Return | Return
| Call index | Call index
| CallIndirect index | CallIndirect index index
-- Reference instructions
| RefNull ElemType
| RefIsNull
| RefFunc index
| RefExtern Natural
-- Parametric instructions -- Parametric instructions
| Drop | Drop
| Select | Select (Maybe [ValueType])
-- Variable instructions -- Variable instructions
| GetLocal index | GetLocal index
| SetLocal index | SetLocal index
@@ -167,8 +178,21 @@ data Instruction index =
| I64Store8 MemArg | I64Store8 MemArg
| I64Store16 MemArg | I64Store16 MemArg
| I64Store32 MemArg | I64Store32 MemArg
| CurrentMemory | MemorySize
| GrowMemory | MemoryGrow
| MemoryFill
| MemoryCopy
| MemoryInit DataIndex
| DataDrop DataIndex
-- Table instructions
| TableInit TableIndex ElemIndex
| TableGrow TableIndex
| TableSize TableIndex
| TableFill TableIndex
| TableGet TableIndex
| TableSet TableIndex
| TableCopy TableIndex TableIndex
| ElemDrop ElemIndex
-- Numeric instructions -- Numeric instructions
| I32Const Word32 | I32Const Word32
| I64Const Word64 | I64Const Word64
@@ -207,7 +231,7 @@ data Function = Function {
data Limit = Limit Natural (Maybe Natural) deriving (Show, Eq, Generic, NFData) data Limit = Limit Natural (Maybe Natural) deriving (Show, Eq, Generic, NFData)
data ElemType = FuncRef deriving (Show, Eq, Generic, NFData) data ElemType = FuncRef | ExternRef deriving (Show, Eq, Generic, NFData)
data TableType = TableType Limit ElemType deriving (Show, Eq, Generic, NFData) data TableType = TableType Limit ElemType deriving (Show, Eq, Generic, NFData)
@@ -222,15 +246,25 @@ data Global = Global {
initializer :: Expression initializer :: Expression
} deriving (Show, Eq, Generic, NFData) } deriving (Show, Eq, Generic, NFData)
data ElemMode =
Passive
| Active TableIndex Expression
| Declarative
deriving (Show, Eq, Generic, NFData)
data ElemSegment = ElemSegment { data ElemSegment = ElemSegment {
tableIndex :: TableIndex, elemType :: ElemType,
offset :: Expression, mode :: ElemMode,
funcIndexes :: [FuncIndex] elements :: [Expression]
} deriving (Show, Eq, Generic, NFData) } deriving (Show, Eq, Generic, NFData)
data DataMode =
PassiveData
| ActiveData MemoryIndex Expression
deriving (Show, Eq, Generic, NFData)
data DataSegment = DataSegment { data DataSegment = DataSegment {
memIndex :: MemoryIndex, dataMode :: DataMode,
offset :: Expression,
chunk :: LBS.ByteString chunk :: LBS.ByteString
} deriving (Show, Eq, Generic, NFData) } deriving (Show, Eq, Generic, NFData)
+305 -151
View File
@@ -15,11 +15,11 @@ import Language.Wasm.Structure
import qualified Data.Set as Set import qualified Data.Set as Set
import Data.List (foldl') import Data.List (foldl')
import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy as TL
import Data.Maybe (fromMaybe, maybeToList, catMaybes) import Data.Maybe (fromMaybe, catMaybes)
import Numeric.Natural (Natural) import Numeric.Natural (Natural)
import Prelude hiding ((<>)) import Prelude hiding ((<>))
import Control.Monad (foldM) import Control.Monad (foldM, forM_, when, unless)
import Control.Monad.Reader (ReaderT, runReaderT, withReaderT, ask) import Control.Monad.Reader (ReaderT, runReaderT, withReaderT, ask)
import Control.Monad.Except (Except, runExcept, throwError) import Control.Monad.Except (Except, runExcept, throwError)
@@ -32,20 +32,23 @@ data ValidationError =
| MemoryLimitExceeded | MemoryLimitExceeded
| AlignmentOverflow | AlignmentOverflow
| MoreThanOneMemory | MoreThanOneMemory
| MoreThanOneTable | FunctionIndexOutOfRange Natural
| FunctionIndexOutOfRange
| TableIndexOutOfRange Natural | TableIndexOutOfRange Natural
| MemoryIndexOutOfRange Natural | MemoryIndexOutOfRange Natural
| LocalIndexOutOfRange Natural | LocalIndexOutOfRange Natural
| GlobalIndexOutOfRange Natural | GlobalIndexOutOfRange Natural
| ElemIndexOutOfRange Natural
| DataIndexOutOfRange Natural
| LabelIndexOutOfRange | LabelIndexOutOfRange
| TypeIndexOutOfRange | TypeIndexOutOfRange
| ResultTypeDoesntMatch | ResultTypeDoesntMatch
| TypeMismatch { actual :: Arrow, expected :: Arrow } | TypeMismatch { actual :: Arrow, expected :: Arrow }
| RefTypeMismatch ElemType ElemType
| InvalidResultArity | InvalidResultArity
| InvalidConstantExpr | InvalidConstantExpr
| InvalidStartFunctionType | InvalidStartFunctionType
| GlobalIsImmutable | GlobalIsImmutable
| UndeclaredFunctionRef Natural
deriving (Show, Eq) deriving (Show, Eq)
type ValidationResult = Either ValidationError () type ValidationResult = Either ValidationError ()
@@ -62,13 +65,14 @@ instance Monoid ValidationResult where
isValid :: ValidationResult -> Bool isValid :: ValidationResult -> Bool
isValid (Right ()) = True isValid (Right ()) = True
isValid (Left reason) = Debug.trace ("Module mismatched with reason " ++ show reason) $ False isValid (Left reason) = False
type Validator = Module -> ValidationResult type Validator = Module -> ValidationResult
data VType = data VType =
Val ValueType Val ValueType
| Var | Var
| NonRefVar
| Any | Any
deriving (Show, Eq) deriving (Show, Eq)
@@ -103,6 +107,11 @@ asArrow (FuncType params results) = Arrow (map Val params) (map Val $ reverse re
isArrowMatch :: Arrow -> Arrow -> Bool isArrowMatch :: Arrow -> Arrow -> Bool
isArrowMatch (f `Arrow` t) ( f' `Arrow` t') = isEndMatch f f' && isEndMatch t t' isArrowMatch (f `Arrow` t) ( f' `Arrow` t') = isEndMatch f f' && isEndMatch t t'
where where
isRef :: VType -> Bool
isRef (Val Func) = True
isRef (Val Extern) = True
isRef _ = False
isEndMatch :: End -> End -> Bool isEndMatch :: End -> End -> Bool
isEndMatch (Any:l) (Any:r) = isEndMatch (Any:l) (Any:r) =
let (leftTail, rightTail) = unzip $ zip (takeWhile (/= Any) $ reverse l) (takeWhile (/= Any) $ reverse r) in let (leftTail, rightTail) = unzip $ zip (takeWhile (/= Any) $ reverse l) (takeWhile (/= Any) $ reverse r) in
@@ -119,6 +128,12 @@ isArrowMatch (f `Arrow` t) ( f' `Arrow` t') = isEndMatch f f' && isEndMatch t t'
isEndMatch (x:l) (Var:r) = isEndMatch (x:l) (Var:r) =
let subst = replace Var x in let subst = replace Var x in
isEndMatch (subst l) (subst r) isEndMatch (subst l) (subst r)
isEndMatch (NonRefVar:l) (x:r) =
let subst = replace NonRefVar x in
isEndMatch (subst l) (subst r)
isEndMatch (x:l) (NonRefVar:r) =
let subst = replace NonRefVar x in
isEndMatch (subst l) (subst r)
isEndMatch (Val v:l) (Val v':r) = v == v' && isEndMatch l r isEndMatch (Val v:l) (Val v':r) = v == v' && isEndMatch l r
isEndMatch [] [] = True isEndMatch [] [] = True
isEndMatch _ _ = False isEndMatch _ _ = False
@@ -127,12 +142,15 @@ data Ctx = Ctx {
types :: [FuncType], types :: [FuncType],
funcs :: [FuncType], funcs :: [FuncType],
tables :: [TableType], tables :: [TableType],
elems :: [ElemType],
datas :: [DataMode],
mems :: [Limit], mems :: [Limit],
globals :: [GlobalType], globals :: [GlobalType],
locals :: [ValueType], locals :: [ValueType],
labels :: [[ValueType]], labels :: [[ValueType]],
returns :: [ValueType], returns :: [ValueType],
importedGlobals :: Natural importedGlobals :: Natural,
refs :: Set.Set Natural
} deriving (Show, Eq) } deriving (Show, Eq)
type Checker = ReaderT Ctx (Except ValidationError) type Checker = ReaderT Ctx (Except ValidationError)
@@ -197,24 +215,28 @@ getResultType (TypeIndex typeIdx) = do
Ctx { types } <- ask Ctx { types } <- ask
maybeToEither TypeIndexOutOfRange $ results <$> types !? typeIdx maybeToEither TypeIndexOutOfRange $ results <$> types !? typeIdx
getInstrType :: Instruction Natural -> Checker Arrow elemTypeToRefType :: ElemType -> ValueType
getInstrType Unreachable = return $ Any ==> Any elemTypeToRefType FuncRef = Func
getInstrType Nop = return $ empty ==> empty elemTypeToRefType ExternRef = Extern
getInstrType Block { blockType, body } = do
getInstrType :: [VType] -> Instruction Natural -> Checker Arrow
getInstrType _ Unreachable = return $ Any ==> Any
getInstrType _ Nop = return $ empty ==> empty
getInstrType _ Block { blockType, body } = do
bt@(Arrow from _) <- getBlockType blockType bt@(Arrow from _) <- getBlockType blockType
resultType <- getResultType blockType resultType <- getResultType blockType
t <- withLabel resultType $ getExpressionTypeWithInput from body t <- withLabel resultType $ getExpressionTypeWithInput from body
if isArrowMatch t bt if isArrowMatch t bt
then return bt then return bt
else throwError $ TypeMismatch t bt else throwError $ TypeMismatch t bt
getInstrType Loop { blockType, body } = do getInstrType _ Loop { blockType, body } = do
bt@(Arrow from _) <- getBlockType blockType bt@(Arrow from _) <- getBlockType blockType
resultType <- getResultType blockType resultType <- getResultType blockType
t <- withLabel (map (\(Val v) -> v) from) $ getExpressionTypeWithInput from body t <- withLabel (map (\(Val v) -> v) from) $ getExpressionTypeWithInput from body
if isArrowMatch t bt if isArrowMatch t bt
then return bt then return bt
else throwError $ TypeMismatch t bt else throwError $ TypeMismatch t bt
getInstrType If { blockType, true, false } = do getInstrType _ If { blockType, true, false } = do
bt@(Arrow from _) <- getBlockType blockType bt@(Arrow from _) <- getBlockType blockType
resultType <- getResultType blockType resultType <- getResultType blockType
l <- withLabel resultType $ getExpressionTypeWithInput from true l <- withLabel resultType $ getExpressionTypeWithInput from true
@@ -227,184 +249,271 @@ getInstrType If { blockType, true, false } = do
else (throwError $ TypeMismatch r bt) else (throwError $ TypeMismatch r bt)
) )
else throwError $ TypeMismatch l bt else throwError $ TypeMismatch l bt
getInstrType (Br lbl) = do getInstrType _ (Br lbl) = do
r <- map Val <$> getLabel lbl r <- map Val <$> getLabel lbl
return $ (Any : r) ==> Any return $ (Any : r) ==> Any
getInstrType (BrIf lbl) = do getInstrType _ (BrIf lbl) = do
r <- map Val <$> getLabel lbl r <- map Val <$> getLabel lbl
return $ (r ++ [Val I32]) ==> r return $ (r ++ [Val I32]) ==> r
getInstrType (BrTable lbls lbl) = do getInstrType stack (BrTable lbls lbl) = do
r <- getLabel lbl r <- getLabel lbl
rs <- mapM getLabel lbls let returns lbl = do
if all (== r) rs args <- map Val <$> getLabel lbl
res <- matchStack stack (Val I32 : reverse args) []
return (args, res)
alternatives <- mapM returns lbls
(_, def) <- returns lbl
if all (\(args, res) -> res == def && length args == length r) alternatives
then return $ ([Any] ++ (map Val r) ++ [Val I32]) ==> Any then return $ ([Any] ++ (map Val r) ++ [Val I32]) ==> Any
else throwError ResultTypeDoesntMatch else throwError ResultTypeDoesntMatch
getInstrType Return = do getInstrType _ Return = do
Ctx { returns } <- ask Ctx { returns } <- ask
return $ (Any : (map Val returns)) ==> Any return $ (Any : (map Val returns)) ==> Any
getInstrType (Call fun) = do getInstrType _ (Call fun) = do
Ctx { funcs } <- ask Ctx { funcs } <- ask
maybeToEither FunctionIndexOutOfRange $ asArrow <$> funcs !? fun maybeToEither (FunctionIndexOutOfRange fun) $ asArrow <$> funcs !? fun
getInstrType (CallIndirect sign) = do getInstrType _ (CallIndirect tableIdx sign) = do
Ctx { types, tables } <- ask Ctx { types, tables } <- ask
if length tables < 1 if length tables <= fromIntegral tableIdx
then throwError (TableIndexOutOfRange 0) then throwError (TableIndexOutOfRange tableIdx)
else do else do
Arrow from to <- maybeToEither TypeIndexOutOfRange $ asArrow <$> types !? sign Arrow from to <- maybeToEither TypeIndexOutOfRange $ asArrow <$> types !? sign
return $ (from ++ [Val I32]) ==> to return $ (from ++ [Val I32]) ==> to
getInstrType Drop = do getInstrType _ Drop = do
var <- freshVar var <- freshVar
return $ var ==> empty return $ var ==> empty
getInstrType Select = do getInstrType _ (Select Nothing) = do
var <- freshVar var <- return NonRefVar
return $ [var, var, Val I32] ==> var return $ [var, var, Val I32] ==> var
getInstrType (GetLocal local) = do getInstrType _ (Select (Just vt)) =
case vt of
[t] -> return $ [t, t, I32] ==> t
_ -> throwError InvalidResultArity
getInstrType _ (RefNull elType) = do
let t = case elType of { FuncRef -> Func; ExternRef -> Extern }
return $ empty ==> Val t
getInstrType _ RefIsNull = do
var <- freshVar
return $ var ==> Val I32
getInstrType _ (RefFunc funIdx) = do
Ctx { funcs, refs } <- ask
if fromIntegral funIdx < length funcs
then do
unless (Set.member funIdx refs) $
throwError $ UndeclaredFunctionRef $ fromIntegral funIdx
return $ empty ==> Val Func
else throwError $ FunctionIndexOutOfRange $ fromIntegral funIdx
getInstrType _ (GetLocal local) = do
Ctx { locals } <- ask Ctx { locals } <- ask
t <- maybeToEither (LocalIndexOutOfRange local) $ locals !? local t <- maybeToEither (LocalIndexOutOfRange local) $ locals !? local
return $ empty ==> Val t return $ empty ==> Val t
getInstrType (SetLocal local) = do getInstrType _ (SetLocal local) = do
Ctx { locals } <- ask Ctx { locals } <- ask
t <- maybeToEither (LocalIndexOutOfRange local) $ locals !? local t <- maybeToEither (LocalIndexOutOfRange local) $ locals !? local
return $ Val t ==> empty return $ Val t ==> empty
getInstrType (TeeLocal local) = do getInstrType _ (TeeLocal local) = do
Ctx { locals } <- ask Ctx { locals } <- ask
t <- maybeToEither (LocalIndexOutOfRange local) $ locals !? local t <- maybeToEither (LocalIndexOutOfRange local) $ locals !? local
return $ Val t ==> Val t return $ Val t ==> Val t
getInstrType (GetGlobal global) = do getInstrType _ (GetGlobal global) = do
Ctx { globals } <- ask Ctx { globals } <- ask
t <- maybeToEither (GlobalIndexOutOfRange global) $ asType <$> globals !? global t <- maybeToEither (GlobalIndexOutOfRange global) $ asType <$> globals !? global
return $ empty ==> t return $ empty ==> t
getInstrType (SetGlobal global) = do getInstrType _ (SetGlobal global) = do
Ctx { globals } <- ask Ctx { globals } <- ask
t <- maybeToEither (GlobalIndexOutOfRange global) $ asType <$> globals !? global t <- maybeToEither (GlobalIndexOutOfRange global) $ asType <$> globals !? global
shouldBeMut $ globals !! fromIntegral global shouldBeMut $ globals !! fromIntegral global
return $ t ==> empty return $ t ==> empty
getInstrType (I32Load memarg) = do getInstrType _ (I32Load memarg) = do
checkMemoryInstr 4 memarg checkMemoryInstr 4 memarg
return $ I32 ==> I32 return $ I32 ==> I32
getInstrType (I64Load memarg) = do getInstrType _ (I64Load memarg) = do
checkMemoryInstr 8 memarg checkMemoryInstr 8 memarg
return $ I32 ==> I64 return $ I32 ==> I64
getInstrType (F32Load memarg) = do getInstrType _ (F32Load memarg) = do
checkMemoryInstr 4 memarg checkMemoryInstr 4 memarg
return $ I32 ==> F32 return $ I32 ==> F32
getInstrType (F64Load memarg) = do getInstrType _ (F64Load memarg) = do
checkMemoryInstr 8 memarg checkMemoryInstr 8 memarg
return $ I32 ==> F64 return $ I32 ==> F64
getInstrType (I32Load8S memarg) = do getInstrType _ (I32Load8S memarg) = do
checkMemoryInstr 1 memarg checkMemoryInstr 1 memarg
return $ I32 ==> I32 return $ I32 ==> I32
getInstrType (I32Load8U memarg) = do getInstrType _ (I32Load8U memarg) = do
checkMemoryInstr 1 memarg checkMemoryInstr 1 memarg
return $ I32 ==> I32 return $ I32 ==> I32
getInstrType (I32Load16S memarg) = do getInstrType _ (I32Load16S memarg) = do
checkMemoryInstr 2 memarg checkMemoryInstr 2 memarg
return $ I32 ==> I32 return $ I32 ==> I32
getInstrType (I32Load16U memarg) = do getInstrType _ (I32Load16U memarg) = do
checkMemoryInstr 2 memarg checkMemoryInstr 2 memarg
return $ I32 ==> I32 return $ I32 ==> I32
getInstrType (I64Load8S memarg) = do getInstrType _ (I64Load8S memarg) = do
checkMemoryInstr 1 memarg checkMemoryInstr 1 memarg
return $ I32 ==> I64 return $ I32 ==> I64
getInstrType (I64Load8U memarg) = do getInstrType _ (I64Load8U memarg) = do
checkMemoryInstr 1 memarg checkMemoryInstr 1 memarg
return $ I32 ==> I64 return $ I32 ==> I64
getInstrType (I64Load16S memarg) = do getInstrType _ (I64Load16S memarg) = do
checkMemoryInstr 2 memarg checkMemoryInstr 2 memarg
return $ I32 ==> I64 return $ I32 ==> I64
getInstrType (I64Load16U memarg) = do getInstrType _ (I64Load16U memarg) = do
checkMemoryInstr 2 memarg checkMemoryInstr 2 memarg
return $ I32 ==> I64 return $ I32 ==> I64
getInstrType (I64Load32S memarg) = do getInstrType _ (I64Load32S memarg) = do
checkMemoryInstr 4 memarg checkMemoryInstr 4 memarg
return $ I32 ==> I64 return $ I32 ==> I64
getInstrType (I64Load32U memarg) = do getInstrType _ (I64Load32U memarg) = do
checkMemoryInstr 4 memarg checkMemoryInstr 4 memarg
return $ I32 ==> I64 return $ I32 ==> I64
getInstrType (I32Store memarg) = do getInstrType _ (I32Store memarg) = do
checkMemoryInstr 4 memarg checkMemoryInstr 4 memarg
return $ [I32, I32] ==> empty return $ [I32, I32] ==> empty
getInstrType (I64Store memarg) = do getInstrType _ (I64Store memarg) = do
checkMemoryInstr 8 memarg checkMemoryInstr 8 memarg
return $ [I32, I64] ==> empty return $ [I32, I64] ==> empty
getInstrType (F32Store memarg) = do getInstrType _ (F32Store memarg) = do
checkMemoryInstr 4 memarg checkMemoryInstr 4 memarg
return $ [I32, F32] ==> empty return $ [I32, F32] ==> empty
getInstrType (F64Store memarg) = do getInstrType _ (F64Store memarg) = do
checkMemoryInstr 8 memarg checkMemoryInstr 8 memarg
return $ [I32, F64] ==> empty return $ [I32, F64] ==> empty
getInstrType (I32Store8 memarg) = do getInstrType _ (I32Store8 memarg) = do
checkMemoryInstr 1 memarg checkMemoryInstr 1 memarg
return $ [I32, I32] ==> empty return $ [I32, I32] ==> empty
getInstrType (I32Store16 memarg) = do getInstrType _ (I32Store16 memarg) = do
checkMemoryInstr 2 memarg checkMemoryInstr 2 memarg
return $ [I32, I32] ==> empty return $ [I32, I32] ==> empty
getInstrType (I64Store8 memarg) = do getInstrType _ (I64Store8 memarg) = do
checkMemoryInstr 1 memarg checkMemoryInstr 1 memarg
return $ [I32, I64] ==> empty return $ [I32, I64] ==> empty
getInstrType (I64Store16 memarg) = do getInstrType _ (I64Store16 memarg) = do
checkMemoryInstr 2 memarg checkMemoryInstr 2 memarg
return $ [I32, I64] ==> empty return $ [I32, I64] ==> empty
getInstrType (I64Store32 memarg) = do getInstrType _ (I64Store32 memarg) = do
checkMemoryInstr 4 memarg checkMemoryInstr 4 memarg
return $ [I32, I64] ==> empty return $ [I32, I64] ==> empty
getInstrType CurrentMemory = do getInstrType _ MemorySize = do
Ctx { mems } <- ask Ctx { mems } <- ask
if length mems < 1 then throwError (MemoryIndexOutOfRange 0) else return $ empty ==> I32 when (length mems < 1) $ throwError (MemoryIndexOutOfRange 0)
getInstrType GrowMemory = do return $ empty ==> I32
getInstrType _ MemoryGrow = do
Ctx { mems } <- ask Ctx { mems } <- ask
if length mems < 1 then throwError (MemoryIndexOutOfRange 0) else return $ I32 ==> I32 when (length mems < 1) $ throwError (MemoryIndexOutOfRange 0)
getInstrType (I32Const _) = return $ empty ==> I32 return $ I32 ==> I32
getInstrType (I64Const _) = return $ empty ==> I64 getInstrType _ MemoryFill = do
getInstrType (F32Const _) = return $ empty ==> F32 Ctx { mems } <- ask
getInstrType (F64Const _) = return $ empty ==> F64 when (length mems < 1) $ throwError (MemoryIndexOutOfRange 0)
getInstrType (IUnOp BS32 _) = return $ I32 ==> I32 return $ [I32, I32, I32] ==> empty
getInstrType (IUnOp BS64 _) = return $ I64 ==> I64 getInstrType _ MemoryCopy = do
getInstrType (IBinOp BS32 _) = return $ [I32, I32] ==> I32 Ctx { mems } <- ask
getInstrType (IBinOp BS64 _) = return $ [I64, I64] ==> I64 when (length mems < 1) $ throwError (MemoryIndexOutOfRange 0)
getInstrType I32Eqz = return $ I32 ==> I32 return $ [I32, I32, I32] ==> empty
getInstrType I64Eqz = return $ I64 ==> I32 getInstrType _ (MemoryInit dataIdx) = do
getInstrType (IRelOp BS32 _) = return $ [I32, I32] ==> I32 Ctx { mems, datas } <- ask
getInstrType (IRelOp BS64 _) = return $ [I64, I64] ==> I32 when (length mems < 1) $ throwError (MemoryIndexOutOfRange 0)
getInstrType (FUnOp BS32 _) = return $ F32 ==> F32 when (length datas <= fromIntegral dataIdx) $ throwError (DataIndexOutOfRange dataIdx)
getInstrType (FUnOp BS64 _) = return $ F64 ==> F64 return $ [I32, I32, I32] ==> empty
getInstrType (FBinOp BS32 _) = return $ [F32, F32] ==> F32 getInstrType _ (DataDrop dataIdx) = do
getInstrType (FBinOp BS64 _) = return $ [F64, F64] ==> F64 Ctx { datas } <- ask
getInstrType (FRelOp BS32 _) = return $ [F32, F32] ==> I32 when (length datas <= fromIntegral dataIdx) $ throwError (DataIndexOutOfRange dataIdx)
getInstrType (FRelOp BS64 _) = return $ [F64, F64] ==> I32 return $ empty ==> empty
getInstrType I32WrapI64 = return $ I64 ==> I32 getInstrType _ (TableInit tableIdx elemIdx) = do
getInstrType (ITruncFU BS32 BS32) = return $ F32 ==> I32 Ctx { tables, elems } <- ask
getInstrType (ITruncFU BS32 BS64) = return $ F64 ==> I32 when (length tables <= fromIntegral tableIdx) $ throwError (TableIndexOutOfRange tableIdx)
getInstrType (ITruncFU BS64 BS32) = return $ F32 ==> I64 when (length elems <= fromIntegral elemIdx) $ throwError (ElemIndexOutOfRange elemIdx)
getInstrType (ITruncFU BS64 BS64) = return $ F64 ==> I64 let TableType _ tableType = tables !! fromIntegral tableIdx
getInstrType (ITruncFS BS32 BS32) = return $ F32 ==> I32 let elemType = elems !! fromIntegral elemIdx
getInstrType (ITruncFS BS32 BS64) = return $ F64 ==> I32 when (elemType /= tableType) $ throwError (RefTypeMismatch tableType elemType)
getInstrType (ITruncFS BS64 BS32) = return $ F32 ==> I64 return $ [I32, I32, I32] ==> empty
getInstrType (ITruncFS BS64 BS64) = return $ F64 ==> I64 getInstrType _ (TableCopy toIdx fromIdx) = do
getInstrType (ITruncSatFU BS32 BS32) = return $ F32 ==> I32 Ctx { tables } <- ask
getInstrType (ITruncSatFU BS32 BS64) = return $ F64 ==> I32 let (from, to) = (fromIntegral fromIdx, fromIntegral toIdx)
getInstrType (ITruncSatFU BS64 BS32) = return $ F32 ==> I64 when (length tables <= from) $ throwError (TableIndexOutOfRange fromIdx)
getInstrType (ITruncSatFU BS64 BS64) = return $ F64 ==> I64 when (length tables <= to) $ throwError (TableIndexOutOfRange toIdx)
getInstrType (ITruncSatFS BS32 BS32) = return $ F32 ==> I32 let TableType _ fromType = tables !! from
getInstrType (ITruncSatFS BS32 BS64) = return $ F64 ==> I32 let TableType _ toType = tables !! to
getInstrType (ITruncSatFS BS64 BS32) = return $ F32 ==> I64 when (fromType /= toType) $ throwError (RefTypeMismatch fromType toType)
getInstrType (ITruncSatFS BS64 BS64) = return $ F64 ==> I64 return $ [I32, I32, I32] ==> empty
getInstrType I64ExtendSI32 = return $ I32 ==> I64 getInstrType _ (TableFill tableIdx) = do
getInstrType I64ExtendUI32 = return $ I32 ==> I64 Ctx { tables } <- ask
getInstrType (FConvertIU BS32 BS32) = return $ I32 ==> F32 when (length tables <= fromIntegral tableIdx) $ throwError (TableIndexOutOfRange tableIdx)
getInstrType (FConvertIU BS32 BS64) = return $ I64 ==> F32 let TableType _ tableType = tables !! fromIntegral tableIdx
getInstrType (FConvertIU BS64 BS32) = return $ I32 ==> F64 return $ [I32, elemTypeToRefType tableType, I32] ==> empty
getInstrType (FConvertIU BS64 BS64) = return $ I64 ==> F64 getInstrType _ (TableSize tableIdx) = do
getInstrType (FConvertIS BS32 BS32) = return $ I32 ==> F32 Ctx { tables } <- ask
getInstrType (FConvertIS BS32 BS64) = return $ I64 ==> F32 when (length tables <= fromIntegral tableIdx) $ throwError (TableIndexOutOfRange tableIdx)
getInstrType (FConvertIS BS64 BS32) = return $ I32 ==> F64 return $ empty ==> I32
getInstrType (FConvertIS BS64 BS64) = return $ I64 ==> F64 getInstrType _ (TableGrow tableIdx) = do
getInstrType F32DemoteF64 = return $ F64 ==> F32 Ctx { tables } <- ask
getInstrType F64PromoteF32 = return $ F32 ==> F64 when (length tables <= fromIntegral tableIdx) $ throwError (TableIndexOutOfRange tableIdx)
getInstrType (IReinterpretF BS32) = return $ F32 ==> I32 let TableType _ tableType = tables !! fromIntegral tableIdx
getInstrType (IReinterpretF BS64) = return $ F64 ==> I64 return $ [elemTypeToRefType tableType, I32] ==> I32
getInstrType (FReinterpretI BS32) = return $ I32 ==> F32 getInstrType _ (TableGet tableIdx) = do
getInstrType (FReinterpretI BS64) = return $ I64 ==> F64 Ctx { tables } <- ask
when (length tables <= fromIntegral tableIdx) $ throwError (TableIndexOutOfRange tableIdx)
let TableType _ tableType = tables !! fromIntegral tableIdx
return $ I32 ==> (elemTypeToRefType tableType)
getInstrType _ (TableSet tableIdx) = do
Ctx { tables } <- ask
when (length tables <= fromIntegral tableIdx) $ throwError (TableIndexOutOfRange tableIdx)
let TableType _ tableType = tables !! fromIntegral tableIdx
return $ [I32, elemTypeToRefType tableType] ==> empty
getInstrType _ (ElemDrop elemIdx) = do
Ctx { elems } <- ask
when (length elems <= fromIntegral elemIdx) $ throwError (ElemIndexOutOfRange elemIdx)
return $ empty ==> empty
getInstrType _ (I32Const _) = return $ empty ==> I32
getInstrType _ (I64Const _) = return $ empty ==> I64
getInstrType _ (F32Const _) = return $ empty ==> F32
getInstrType _ (F64Const _) = return $ empty ==> F64
getInstrType _ (IUnOp BS32 _) = return $ I32 ==> I32
getInstrType _ (IUnOp BS64 _) = return $ I64 ==> I64
getInstrType _ (IBinOp BS32 _) = return $ [I32, I32] ==> I32
getInstrType _ (IBinOp BS64 _) = return $ [I64, I64] ==> I64
getInstrType _ I32Eqz = return $ I32 ==> I32
getInstrType _ I64Eqz = return $ I64 ==> I32
getInstrType _ (IRelOp BS32 _) = return $ [I32, I32] ==> I32
getInstrType _ (IRelOp BS64 _) = return $ [I64, I64] ==> I32
getInstrType _ (FUnOp BS32 _) = return $ F32 ==> F32
getInstrType _ (FUnOp BS64 _) = return $ F64 ==> F64
getInstrType _ (FBinOp BS32 _) = return $ [F32, F32] ==> F32
getInstrType _ (FBinOp BS64 _) = return $ [F64, F64] ==> F64
getInstrType _ (FRelOp BS32 _) = return $ [F32, F32] ==> I32
getInstrType _ (FRelOp BS64 _) = return $ [F64, F64] ==> I32
getInstrType _ I32WrapI64 = return $ I64 ==> I32
getInstrType _ (ITruncFU BS32 BS32) = return $ F32 ==> I32
getInstrType _ (ITruncFU BS32 BS64) = return $ F64 ==> I32
getInstrType _ (ITruncFU BS64 BS32) = return $ F32 ==> I64
getInstrType _ (ITruncFU BS64 BS64) = return $ F64 ==> I64
getInstrType _ (ITruncFS BS32 BS32) = return $ F32 ==> I32
getInstrType _ (ITruncFS BS32 BS64) = return $ F64 ==> I32
getInstrType _ (ITruncFS BS64 BS32) = return $ F32 ==> I64
getInstrType _ (ITruncFS BS64 BS64) = return $ F64 ==> I64
getInstrType _ (ITruncSatFU BS32 BS32) = return $ F32 ==> I32
getInstrType _ (ITruncSatFU BS32 BS64) = return $ F64 ==> I32
getInstrType _ (ITruncSatFU BS64 BS32) = return $ F32 ==> I64
getInstrType _ (ITruncSatFU BS64 BS64) = return $ F64 ==> I64
getInstrType _ (ITruncSatFS BS32 BS32) = return $ F32 ==> I32
getInstrType _ (ITruncSatFS BS32 BS64) = return $ F64 ==> I32
getInstrType _ (ITruncSatFS BS64 BS32) = return $ F32 ==> I64
getInstrType _ (ITruncSatFS BS64 BS64) = return $ F64 ==> I64
getInstrType _ I64ExtendSI32 = return $ I32 ==> I64
getInstrType _ I64ExtendUI32 = return $ I32 ==> I64
getInstrType _ (FConvertIU BS32 BS32) = return $ I32 ==> F32
getInstrType _ (FConvertIU BS32 BS64) = return $ I64 ==> F32
getInstrType _ (FConvertIU BS64 BS32) = return $ I32 ==> F64
getInstrType _ (FConvertIU BS64 BS64) = return $ I64 ==> F64
getInstrType _ (FConvertIS BS32 BS32) = return $ I32 ==> F32
getInstrType _ (FConvertIS BS32 BS64) = return $ I64 ==> F32
getInstrType _ (FConvertIS BS64 BS32) = return $ I32 ==> F64
getInstrType _ (FConvertIS BS64 BS64) = return $ I64 ==> F64
getInstrType _ F32DemoteF64 = return $ F64 ==> F32
getInstrType _ F64PromoteF32 = return $ F32 ==> F64
getInstrType _ (IReinterpretF BS32) = return $ F32 ==> I32
getInstrType _ (IReinterpretF BS64) = return $ F64 ==> I64
getInstrType _ (FReinterpretI BS32) = return $ I32 ==> F32
getInstrType _ (FReinterpretI BS64) = return $ I64 ==> F64
replace :: (Eq a) => a -> a -> [a] -> [a] replace :: (Eq a) => a -> a -> [a] -> [a]
@@ -416,25 +525,46 @@ getExpressionTypeWithInput inp = fmap (inp `Arrow`) . foldM go inp
where where
go :: [VType] -> Instruction Natural -> Checker [VType] go :: [VType] -> Instruction Natural -> Checker [VType]
go stack instr = do go stack instr = do
(f `Arrow` t) <- getInstrType instr (f `Arrow` t) <- getInstrType stack instr
matchStack stack (reverse f) t matchStack stack (reverse f) t
matchStack :: [VType] -> [VType] -> [VType] -> Checker [VType] isRef :: ValueType -> Bool
matchStack stack@(Any:_) _arg res = return $ res ++ stack isRef (Func) = True
matchStack (Val v:stack) (Val v':args) res = isRef (Extern) = True
if v == v' isRef _ = False
then matchStack stack args res
else throwError $ TypeMismatch ((reverse $ Val v':args) `Arrow` res) ([] `Arrow` (Val v:stack)) matchStack :: [VType] -> [VType] -> [VType] -> Checker [VType]
matchStack _ (Any:_) res = return $ res matchStack stack@(Any:_) _arg res = return $ res ++ stack
matchStack (Val v:stack) (Var:args) res = matchStack (Val v:stack) (Val v':args) res =
let subst = replace Var (Val v) in if v == v'
matchStack stack (subst args) (subst res) then matchStack stack args res
matchStack (Var:stack) (Val v:args) res = else throwError $ TypeMismatch ((reverse $ Val v':args) `Arrow` res) ([] `Arrow` (Val v:stack))
let subst = replace Var (Val v) in matchStack _ (Any:_) res = return $ res
matchStack stack (subst args) (subst res) matchStack (Val v:stack) (Var:args) res =
matchStack stack [] res = return $ res ++ stack let subst = replace Var (Val v) in
matchStack [] args res = throwError $ TypeMismatch ((reverse args) `Arrow` res) ([] `Arrow` []) matchStack stack (subst args) (subst res)
matchStack _ _ _ = error "inconsistent checker state" matchStack (Var:stack) (Val v:args) res =
let subst = replace Var (Val v) in
matchStack stack (subst args) (subst res)
matchStack (Val v:stack) (NonRefVar:args) res =
let subst = replace NonRefVar (Val v) in
if isRef v
then throwError $ TypeMismatch (empty ==> empty) (empty ==> empty)
else matchStack stack (subst args) (subst res)
matchStack (NonRefVar:stack) (Val v:args) res =
let subst = replace NonRefVar (Val v) in
if isRef v
then throwError $ TypeMismatch (empty ==> empty) (empty ==> empty)
else matchStack stack (subst args) (subst res)
matchStack (Var:stack) (NonRefVar:args) res =
let subst = replace NonRefVar NonRefVar in
matchStack stack (subst args) (subst res)
matchStack (NonRefVar:stack) (Var:args) res =
let subst = replace Var NonRefVar in
matchStack stack (subst args) (subst res)
matchStack stack [] res = return $ res ++ stack
matchStack [] args res = throwError $ TypeMismatch ((reverse args) `Arrow` res) ([] `Arrow` [])
matchStack st args res = error $ "inconsistent checker state: " ++ show (st, args, res)
getExpressionType :: Expression -> Checker Arrow getExpressionType :: Expression -> Checker Arrow
getExpressionType = getExpressionTypeWithInput [] getExpressionType = getExpressionTypeWithInput []
@@ -445,6 +575,8 @@ isConstExpression ((I32Const _):rest) = isConstExpression rest
isConstExpression ((I64Const _):rest) = isConstExpression rest isConstExpression ((I64Const _):rest) = isConstExpression rest
isConstExpression ((F32Const _):rest) = isConstExpression rest isConstExpression ((F32Const _):rest) = isConstExpression rest
isConstExpression ((F64Const _):rest) = isConstExpression rest isConstExpression ((F64Const _):rest) = isConstExpression rest
isConstExpression ((RefNull _):rest) = isConstExpression rest
isConstExpression ((RefFunc _):rest) = isConstExpression rest
isConstExpression ((GetGlobal idx):rest) = do isConstExpression ((GetGlobal idx):rest) = do
Ctx {globals, importedGlobals} <- ask Ctx {globals, importedGlobals} <- ask
if importedGlobals <= idx if importedGlobals <= idx
@@ -464,7 +596,8 @@ getFuncTypes Module {types, functions, imports} =
getFuncType _ = Nothing getFuncType _ = Nothing
ctxFromModule :: [ValueType] -> [[ValueType]] -> [ValueType] -> Module -> Ctx ctxFromModule :: [ValueType] -> [[ValueType]] -> [ValueType] -> Module -> Ctx
ctxFromModule locals labels returns m@Module {types, tables, mems, globals, imports} = ctxFromModule locals labels returns m =
let Module {types, tables, mems, globals, imports, elems, exports, datas} = m in
let tableImports = catMaybes $ map getTableType imports in let tableImports = catMaybes $ map getTableType imports in
let memsImports = catMaybes $ map getMemType imports in let memsImports = catMaybes $ map getMemType imports in
let globalImports = catMaybes $ map getGlobalType imports in let globalImports = catMaybes $ map getGlobalType imports in
@@ -472,12 +605,17 @@ ctxFromModule locals labels returns m@Module {types, tables, mems, globals, impo
types, types,
funcs = getFuncTypes m, funcs = getFuncTypes m,
tables = tableImports ++ map (\(Table t) -> t) tables, tables = tableImports ++ map (\(Table t) -> t) tables,
elems = map elemType elems,
datas = map dataMode datas,
mems = memsImports ++ map (\(Memory l) -> l) mems, mems = memsImports ++ map (\(Memory l) -> l) mems,
globals = globalImports ++ map (\(Global g _) -> g) globals, globals = globalImports ++ map (\(Global g _) -> g) globals,
locals, locals,
labels, labels,
returns, returns,
importedGlobals = fromIntegral $ length globalImports importedGlobals = fromIntegral $ length globalImports,
refs = Set.unions $ map getElemRefs elems
++ map getGlobalRefs globals
++ map getExportRefs exports
} }
where where
getTableType (Import _ _ (ImportTable tableType)) = Just tableType getTableType (Import _ _ (ImportTable tableType)) = Just tableType
@@ -489,6 +627,19 @@ ctxFromModule locals labels returns m@Module {types, tables, mems, globals, impo
getGlobalType (Import _ _ (ImportGlobal gl)) = Just gl getGlobalType (Import _ _ (ImportGlobal gl)) = Just gl
getGlobalType _ = Nothing getGlobalType _ = Nothing
getElemRefs ElemSegment{ elemType = FuncRef, elements} =
foldl extractRef Set.empty elements
where
extractRef refs [RefFunc idx] = Set.insert idx refs
extractRef refs _ = refs
getElemRefs _ = Set.empty
getGlobalRefs Global {initializer = [RefFunc idx]} = Set.singleton idx
getGlobalRefs _ = Set.empty
getExportRefs Export {desc = ExportFunc idx} = Set.singleton idx
getExportRefs _ = Set.empty
isFunctionValid :: Function -> Validator isFunctionValid :: Function -> Validator
isFunctionValid Function {funcType, localTypes = locals, body} mod@Module {types} = isFunctionValid Function {funcType, localTypes = locals, body} mod@Module {types} =
if fromIntegral funcType < length types if fromIntegral funcType < length types
@@ -509,10 +660,7 @@ tablesShouldBeValid :: Validator
tablesShouldBeValid Module { imports, tables } = tablesShouldBeValid Module { imports, tables } =
let tableImports = filter isTableImport imports in let tableImports = filter isTableImport imports in
let res = foldMap (\Import { desc = ImportTable t } -> isValidTableType t) tableImports in let res = foldMap (\Import { desc = ImportTable t } -> isValidTableType t) tableImports in
let res' = foldl' (\r (Table t) -> r <> isValidTableType t) res tables in foldl' (\r (Table t) -> r <> isValidTableType t) res tables
if length tableImports + length tables <= 1
then res'
else Left MoreThanOneTable
where where
isValidTableType :: TableType -> ValidationResult isValidTableType :: TableType -> ValidationResult
isValidTableType (TableType (Limit min max) _) = isValidTableType (TableType (Limit min max) _) =
@@ -557,24 +705,29 @@ elemsShouldBeValid m@Module { elems, functions, tables, imports } =
foldMap (isElemValid ctx) elems foldMap (isElemValid ctx) elems
where where
isElemValid :: Ctx -> ElemSegment -> ValidationResult isElemValid :: Ctx -> ElemSegment -> ValidationResult
isElemValid ctx (ElemSegment tableIdx offset funs) = isElemValid ctx (ElemSegment elemType mode elements) = do
let check = runChecker ctx $ do unless (elemType == FuncRef)
$ throwError $ RefTypeMismatch FuncRef elemType
forM_ elements $ \elem -> runChecker ctx $ do
arr <- getExpressionType elem
isConstExpression elem
unless (isValidRef elemType arr)
$ throwError $ RefTypeMismatch elemType elemType
case mode of
Active tableIdx offset -> runChecker ctx $ do
isConstExpression offset isConstExpression offset
t <- getExpressionType offset t <- getExpressionType offset
if isArrowMatch (empty ==> I32) t unless (isArrowMatch (empty ==> I32) t) $ do
then return () throwError $ TypeMismatch t (empty ==> I32)
else throwError $ TypeMismatch t (empty ==> I32) let tableImports = filter isTableImport imports
in when (tableIdx >= fromIntegral (length tableImports + length tables)) $ do
let tableImports = filter isTableImport imports in throwError $ TableIndexOutOfRange tableIdx
let isTableIndexValid = _ -> return ()
if tableIdx < (fromIntegral $ length tableImports + length tables)
then return () isValidRef :: ElemType -> Arrow -> Bool
else Left (TableIndexOutOfRange tableIdx) isValidRef FuncRef arr | arr == (empty ==> Func) = True
in isValidRef ExternRef arr | arr == (empty ==> Extern) = True
let funImports = filter isFuncImport imports in isValidRef _ _ = False
let funsLength = fromIntegral $ length functions + length funImports in
let isFunsValid = foldMap (\i -> if i < funsLength then return () else Left FunctionIndexOutOfRange) funs in
check <> isFunsValid <> isTableIndexValid
datasShouldBeValid :: Validator datasShouldBeValid :: Validator
datasShouldBeValid m@Module { datas, mems, imports } = datasShouldBeValid m@Module { datas, mems, imports } =
@@ -582,7 +735,7 @@ datasShouldBeValid m@Module { datas, mems, imports } =
foldMap (isDataValid ctx) datas foldMap (isDataValid ctx) datas
where where
isDataValid :: Ctx -> DataSegment -> ValidationResult isDataValid :: Ctx -> DataSegment -> ValidationResult
isDataValid ctx (DataSegment memIdx offset _) = isDataValid ctx (DataSegment (ActiveData memIdx offset) _) =
let check = runChecker ctx $ do let check = runChecker ctx $ do
isConstExpression offset isConstExpression offset
t <- getExpressionType offset t <- getExpressionType offset
@@ -594,6 +747,7 @@ datasShouldBeValid m@Module { datas, mems, imports } =
if memIdx < (fromIntegral $ length memImports + length mems) if memIdx < (fromIntegral $ length memImports + length mems)
then check then check
else Left (MemoryIndexOutOfRange memIdx) else Left (MemoryIndexOutOfRange memIdx)
isDataValid ctx (DataSegment PassiveData _) = return ()
startShouldBeValid :: Validator startShouldBeValid :: Validator
startShouldBeValid Module { start = Nothing } = return () startShouldBeValid Module { start = Nothing } = return ()
@@ -602,7 +756,7 @@ startShouldBeValid m@Module { start = Just (StartFunction idx) } =
let i = fromIntegral idx in let i = fromIntegral idx in
if length types > i if length types > i
then if FuncType [] [] == types !! i then return () else Left InvalidStartFunctionType then if FuncType [] [] == types !! i then return () else Left InvalidStartFunctionType
else Left FunctionIndexOutOfRange else Left $ FunctionIndexOutOfRange $ fromIntegral i
exportsShouldBeValid :: Validator exportsShouldBeValid :: Validator
exportsShouldBeValid Module { exports, imports, functions, mems, tables, globals } = exportsShouldBeValid Module { exports, imports, functions, mems, tables, globals } =
@@ -615,7 +769,7 @@ exportsShouldBeValid Module { exports, imports, functions, mems, tables, globals
isExportValid :: Export -> ValidationResult isExportValid :: Export -> ValidationResult
isExportValid (Export _ (ExportFunc funIdx)) = isExportValid (Export _ (ExportFunc funIdx)) =
if fromIntegral funIdx < length funcImports + length functions then return () else Left FunctionIndexOutOfRange if fromIntegral funIdx < length funcImports + length functions then return () else Left (FunctionIndexOutOfRange funIdx)
isExportValid (Export _ (ExportTable tableIdx)) = isExportValid (Export _ (ExportTable tableIdx)) =
if fromIntegral tableIdx < length tableImports + length tables then return () else Left (TableIndexOutOfRange tableIdx) if fromIntegral tableIdx < length tableImports + length tables then return () else Left (TableIndexOutOfRange tableIdx)
isExportValid (Export _ (ExportMemory memIdx)) = isExportValid (Export _ (ExportMemory memIdx)) =
+1 -1
View File
@@ -1,4 +1,4 @@
resolver: lts-16.5 resolver: lts-20.23
packages: packages:
- '.' - '.'
extra-deps: [] extra-deps: []
+4 -4
View File
@@ -6,7 +6,7 @@
packages: [] packages: []
snapshots: snapshots:
- completed: - completed:
size: 531707 sha256: 4c972e067bae16b95961dbfdd12e07f1ee6c8fffabbfa05c3d65100b03f548b7
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/16/5.yaml size: 650253
sha256: 9751e25e0af5713a53ddcfcc79564b082c71b1b357fadef0d85672a5b5ba3703 url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/23.yaml
original: lts-16.5 original: lts-20.23
+4 -2
View File
@@ -16,8 +16,10 @@ import qualified Data.List as List
main :: IO () main :: IO ()
main = do main = do
files <- filter (List.isSuffixOf ".wast") <$> Directory.listDirectory "tests/spec" files <-
-- let files = ["const.wast"] filter (not . List.isPrefixOf "simd") . filter (List.isSuffixOf ".wast")
<$> Directory.listDirectory "tests/spec"
-- let files = ["bulk.wast"]
scriptTestCases <- (`mapM` files) $ \file -> do scriptTestCases <- (`mapM` files) $ \file -> do
test <- LBS.readFile ("tests/spec/" ++ file) test <- LBS.readFile ("tests/spec/" ++ file)
return $ testCase file $ do return $ testCase file $ do
+13 -13
View File
@@ -1,6 +1,6 @@
cabal-version: 2.2 cabal-version: 2.2
name: wasm name: wasm
version: 1.0.1.0 version: 1.1.2
synopsis: WebAssembly Language Toolkit and Interpreter synopsis: WebAssembly Language Toolkit and Interpreter
description: description:
Library for parsing and interpreting WebAssembly, including: Library for parsing and interpreting WebAssembly, including:
@@ -16,9 +16,9 @@ license: MIT
license-file: LICENSE license-file: LICENSE
build-type: Simple build-type: Simple
category: Language category: Language
homepage: https:github.com/SPY/haskell-wasm homepage: https://github.com/SPY/haskell-wasm
bug-reports: https:github.com/SPY/haskell-wasm/issues bug-reports: https://github.com/SPY/haskell-wasm/issues
tested-with: GHC==8.6.5, GHC==8.8.4, GHC==8.10.4 tested-with: GHC==8.6.5, GHC==8.8.4, GHC==8.10.4, GHC==9.2.7
extra-source-files: extra-source-files:
README.md README.md
src/Language/Wasm/Parser.y src/Language/Wasm/Parser.y
@@ -33,14 +33,14 @@ library
Language.Wasm.Script Language.Wasm.Script
Language.Wasm.Lexer Language.Wasm.Lexer
Language.Wasm.Structure Language.Wasm.Structure
Language.Wasm
other-modules:
Language.Wasm.Binary
Language.Wasm.Builder
Language.Wasm.FloatUtils
Language.Wasm.Interpreter Language.Wasm.Interpreter
Language.Wasm.Parser Language.Wasm.Parser
Language.Wasm.Validate Language.Wasm.Validate
Language.Wasm.Binary
Language.Wasm.Builder
Language.Wasm
other-modules:
Language.Wasm.FloatUtils
Paths_wasm Paths_wasm
autogen-modules: autogen-modules:
Paths_wasm Paths_wasm
@@ -62,12 +62,12 @@ library
, containers >=0.5 && < 0.7 , containers >=0.5 && < 0.7
, deepseq >=1.4 && < 1.5 , deepseq >=1.4 && < 1.5
, ieee754 >=0.8 && < 0.9 , ieee754 >=0.8 && < 0.9
, mtl >=2.2.1 && < 2.3 , mtl >=2.2.1 && < 2.4
, primitive >=0.7 && < 0.8 , primitive >=0.7 && < 0.8
, text >=1.1 && < 1.3 , text >=1.1 && < 3
, transformers >=0.4 && < 0.6 , transformers >=0.4 && < 0.7
, utf8-string >=1.0 && < 1.1 , utf8-string >=1.0 && < 1.1
, vector >=0.12 && < 0.13 , vector >=0.12.2 && < 0.14
default-language: Haskell2010 default-language: Haskell2010
test-suite test test-suite test