Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fab53969a | |||
| a3de890895 | |||
| 9eadc50ee3 | |||
| 5b6b043049 | |||
| bb2a6cea7f | |||
| f23dc20d57 | |||
| 2b822a8d44 | |||
| 46f95dd11d | |||
| 2d4ad42549 | |||
| c044f3f556 | |||
| d0403ad554 | |||
| d321bf6a9c | |||
| ec83b12ccd | |||
| 9f17885106 | |||
| ea50c54900 | |||
| 4e9cb99e69 | |||
| b2f001aea8 | |||
| f445340568 | |||
| 2d15ddfa36 | |||
| 0b44ee13b8 | |||
| e9140ae70c | |||
| cbeb4bb61d | |||
| 8c97e2c328 | |||
| 6e9c4d969a | |||
| 179b7ce180 | |||
| fd72ee9d23 | |||
| b6c0ef4462 | |||
| 51cf7e753b | |||
| fe6c31984b | |||
| f2f5fcc56f | |||
| a5ffe38a1c | |||
| 724973b15e | |||
| a58bb32d98 | |||
| 0e0b1abbba | |||
| 418847226e | |||
| efba9c2b50 | |||
| f6d0783fd0 | |||
| 57c4184c18 | |||
| 3be70781b8 | |||
| 5256d5063f | |||
| 2bf6e88072 | |||
| 32392dcc29 | |||
| c743e11ffd | |||
| 32357d68fe | |||
| 83fea36c02 | |||
| 43cbfe653f | |||
| 71d332e3dc | |||
| 4e9105717b | |||
| df15d3c4d1 | |||
| b1da37ac03 | |||
| 82defff076 | |||
| 6eb3acde17 | |||
| e388e21370 | |||
| 95fdcc2f80 | |||
| 5bcd863671 | |||
| 4753ebceb4 | |||
| de40134caf | |||
| 286ee40489 | |||
| c08e81fa40 | |||
| 8af7b45681 | |||
| 960acac955 | |||
| 99532adb63 | |||
| c8f1bc9186 | |||
| 66458e11f3 |
@@ -8,3 +8,5 @@ dist-newstyle/
|
||||
doc/
|
||||
setup-config
|
||||
wasm-*-docs.tar.gz
|
||||
cache
|
||||
packagedb
|
||||
@@ -20,9 +20,10 @@
|
||||
* [ ] Text Representation pretty-printer
|
||||
* [ ] Command line tool for calling interpreter/compiler/validator
|
||||
* [ ] Codegen interface for type enforced generating valid WASM code
|
||||
* [ ] Support for building if, loop, block
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
+151
-20
@@ -244,7 +244,13 @@ instance Serialize FuncType where
|
||||
|
||||
instance Serialize ElemType where
|
||||
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
|
||||
put (Limit min Nothing) = putWord8 0x00 >> putULEB128 min
|
||||
@@ -320,6 +326,12 @@ instance Serialize Index where
|
||||
put (Index idx) = putULEB128 idx
|
||||
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
|
||||
put MemArg { align, offset } = putULEB128 align >> putULEB128 offset
|
||||
get = do
|
||||
@@ -353,16 +365,50 @@ instance Serialize (Instruction Natural) where
|
||||
put (BrTable labels label) = putWord8 0x0E >> putVec (map Index labels) >> putULEB128 label
|
||||
put Return = putWord8 0x0F
|
||||
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
|
||||
put Drop = putWord8 0x1A
|
||||
put Select = putWord8 0x1B
|
||||
put (Select Nothing) = putWord8 0x1B
|
||||
put (Select (Just types)) = putWord8 0x1C >> putVec types
|
||||
-- Variable instructions
|
||||
put (GetLocal idx) = putWord8 0x20 >> putULEB128 idx
|
||||
put (SetLocal idx) = putWord8 0x21 >> putULEB128 idx
|
||||
put (TeeLocal idx) = putWord8 0x22 >> putULEB128 idx
|
||||
put (GetGlobal idx) = putWord8 0x23 >> 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
|
||||
put (I32Load memArg) = putWord8 0x28 >> 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 (I64Store16 memArg) = putWord8 0x3D >> put memArg
|
||||
put (I64Store32 memArg) = putWord8 0x3E >> put memArg
|
||||
put CurrentMemory = putWord8 0x3F >> putWord8 0x00
|
||||
put GrowMemory = putWord8 0x40 >> putWord8 0x00
|
||||
put MemorySize = putWord8 0x3F >> 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
|
||||
put (I32Const val) = putWord8 0x41 >> putSLEB128 (asInt32 val)
|
||||
put (I64Const val) = putWord8 0x42 >> putSLEB128 (asInt64 val)
|
||||
@@ -553,11 +617,15 @@ instance Serialize (Instruction Natural) where
|
||||
0x10 -> Call <$> getULEB128 32
|
||||
0x11 -> do
|
||||
typeIdx <- getULEB128 32
|
||||
byteGuard 0x00
|
||||
return $ CallIndirect typeIdx
|
||||
tableIdx <- getULEB128 32
|
||||
return $ CallIndirect tableIdx typeIdx
|
||||
-- Reference instructions
|
||||
0xD0 -> RefNull <$> get
|
||||
0xD1 -> return RefIsNull
|
||||
0xD2 -> RefFunc <$> getULEB128 32
|
||||
-- Parametric instructions
|
||||
0x1A -> return $ Drop
|
||||
0x1B -> return $ Select
|
||||
0x1B -> return $ Select Nothing
|
||||
-- Variable instructions
|
||||
0x20 -> GetLocal <$> getULEB128 32
|
||||
0x21 -> SetLocal <$> getULEB128 32
|
||||
@@ -588,8 +656,8 @@ instance Serialize (Instruction Natural) where
|
||||
0x3C -> I64Store8 <$> get
|
||||
0x3D -> I64Store16 <$> get
|
||||
0x3E -> I64Store32 <$> get
|
||||
0x3F -> byteGuard 0x00 >> (return $ CurrentMemory)
|
||||
0x40 -> byteGuard 0x00 >> (return $ GrowMemory)
|
||||
0x3F -> byteGuard 0x00 >> (return $ MemorySize)
|
||||
0x40 -> byteGuard 0x00 >> (return $ MemoryGrow)
|
||||
-- Numeric instructions
|
||||
0x41 -> I32Const <$> getSLEB128 32
|
||||
0x42 -> I64Const <$> getSLEB128 64
|
||||
@@ -735,7 +803,7 @@ instance Serialize (Instruction Natural) where
|
||||
0x06 -> return $ ITruncSatFS BS64 BS64
|
||||
0x07 -> return $ ITruncSatFU BS64 BS64
|
||||
_ -> 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 expr = do
|
||||
@@ -793,11 +861,56 @@ instance Serialize Export where
|
||||
get = Export <$> getName <*> get
|
||||
|
||||
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
|
||||
putExpression offset
|
||||
putVec $ map Index funcIndexes
|
||||
get = ElemSegment <$> getULEB128 32 <*> getExpression <*> (map unIndex <$> getVec)
|
||||
put elemType
|
||||
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)
|
||||
|
||||
@@ -824,17 +937,35 @@ instance Serialize Function where
|
||||
return $ Function 0 locals body
|
||||
|
||||
instance Serialize DataSegment where
|
||||
put (DataSegment memIdx offset init) = do
|
||||
put (DataSegment (ActiveData memIdx offset) init) = do
|
||||
putWord8 0x02
|
||||
putULEB128 memIdx
|
||||
putExpression offset
|
||||
putULEB128 $ LBS.length init
|
||||
putLazyByteString init
|
||||
put (DataSegment PassiveData init) = do
|
||||
putWord8 0x01
|
||||
putULEB128 $ LBS.length init
|
||||
putLazyByteString init
|
||||
get = do
|
||||
memIdx <- getULEB128 32
|
||||
offset <- getExpression
|
||||
len <- getULEB128 32
|
||||
init <- getLazyByteString len
|
||||
return $ DataSegment memIdx offset init
|
||||
op <- getULEB128 32
|
||||
case (op :: Word8) of
|
||||
0x00 -> do
|
||||
offset <- getExpression
|
||||
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
|
||||
put mod = do
|
||||
|
||||
@@ -197,7 +197,7 @@ select pred a b = select' (produce pred) (produce a) (produce b)
|
||||
a
|
||||
res <- b
|
||||
pred
|
||||
appendExpr [Select]
|
||||
appendExpr [Select Nothing]
|
||||
return res
|
||||
|
||||
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)]
|
||||
|
||||
memorySize :: GenFun (Proxy I32)
|
||||
memorySize = appendExpr [CurrentMemory] >> return Proxy
|
||||
memorySize = appendExpr [MemorySize] >> return Proxy
|
||||
|
||||
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 (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
|
||||
sequence_ args
|
||||
produce index
|
||||
appendExpr [CallIndirect idx]
|
||||
appendExpr [CallIndirect 0 idx]
|
||||
return returnableValue
|
||||
|
||||
br :: Label t -> GenFun ()
|
||||
@@ -975,7 +975,7 @@ table min max = do
|
||||
dataSegment :: (Producer offset, OutType offset ~ Proxy I32) => offset -> LBS.ByteString -> GenMod ()
|
||||
dataSegment offset bytes =
|
||||
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
|
||||
|
||||
@@ -10,8 +10,11 @@ module Language.Wasm.Interpreter (
|
||||
ExternalValue(..),
|
||||
ExportInstance(..),
|
||||
GlobalInstance(..),
|
||||
MemoryInstance(..),
|
||||
MemoryStore,
|
||||
Imports,
|
||||
HostItem(..),
|
||||
HostItem(..),
|
||||
Address,
|
||||
instantiate,
|
||||
invoke,
|
||||
invokeExport,
|
||||
@@ -20,7 +23,8 @@ module Language.Wasm.Interpreter (
|
||||
emptyImports,
|
||||
makeHostModule,
|
||||
makeMutGlobal,
|
||||
makeConstGlobal
|
||||
makeConstGlobal,
|
||||
getMemory
|
||||
) where
|
||||
|
||||
import qualified Data.Map as Map
|
||||
@@ -30,6 +34,8 @@ import Data.Maybe (fromMaybe, isNothing)
|
||||
|
||||
import Data.Vector (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.Types as Primitive
|
||||
import qualified Control.Monad.Primitive as Primitive
|
||||
@@ -65,11 +71,15 @@ import Language.Wasm.FloatUtils (
|
||||
doubleToWord
|
||||
)
|
||||
|
||||
import Debug.Trace as Debug
|
||||
|
||||
data Value =
|
||||
VI32 Word32
|
||||
| VI64 Word64
|
||||
| VF32 Float
|
||||
| VF64 Double
|
||||
| RF (Maybe Natural)
|
||||
| RE (Maybe Natural)
|
||||
deriving (Eq, Show)
|
||||
|
||||
asInt32 :: Word32 -> Int32
|
||||
@@ -162,9 +172,11 @@ data Label = Label ResultType deriving (Show, Eq)
|
||||
|
||||
type Address = Int
|
||||
|
||||
type TableStore = IORef (IOVector (Maybe Address))
|
||||
|
||||
data TableInstance = TableInstance {
|
||||
lim :: Limit,
|
||||
elements :: Vector (Maybe Address)
|
||||
t :: TableType,
|
||||
items :: TableStore
|
||||
}
|
||||
|
||||
type MemoryStore = ByteArray.MutableByteArray (Primitive.PrimState IO)
|
||||
@@ -187,6 +199,8 @@ getValueType (VI32 _) = I32
|
||||
getValueType (VI64 _) = I64
|
||||
getValueType (VF32 _) = F32
|
||||
getValueType (VF64 _) = F64
|
||||
getValueType (RF _) = Func
|
||||
genValueType (RE _) = Extern
|
||||
|
||||
data ExportInstance = ExportInstance TL.Text ExternalValue deriving (Eq, Show)
|
||||
|
||||
@@ -208,11 +222,30 @@ data FunctionInstance =
|
||||
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 {
|
||||
funcInstances :: Vector FunctionInstance,
|
||||
tableInstances :: Vector TableInstance,
|
||||
memInstances :: Vector MemoryInstance,
|
||||
globalInstances :: Vector GlobalInstance
|
||||
globalInstances :: Vector GlobalInstance,
|
||||
elemInstances :: Vector ElemInstance,
|
||||
dataInstances :: Vector DataInstance
|
||||
}
|
||||
|
||||
emptyStore :: Store
|
||||
@@ -220,7 +253,9 @@ emptyStore = Store {
|
||||
funcInstances = Vector.empty,
|
||||
tableInstances = Vector.empty,
|
||||
memInstances = Vector.empty,
|
||||
globalInstances = Vector.empty
|
||||
globalInstances = Vector.empty,
|
||||
elemInstances = Vector.empty,
|
||||
dataInstances = Vector.empty
|
||||
}
|
||||
|
||||
type HostFunction = [Value] -> IO [Value]
|
||||
@@ -284,8 +319,8 @@ makeHostModule st items = do
|
||||
makeHostTables :: (Store, ModuleInstance) -> IO (Store, ModuleInstance)
|
||||
makeHostTables (st, inst) = do
|
||||
let tableLen = Vector.length $ tableInstances st
|
||||
let (names, tables) = unzip [(name, Table (TableType lim FuncRef)) | (name, (HostTable lim)) <- items]
|
||||
let instances = allocTables tables
|
||||
let (names, tables) = unzip [(name, Table (TableType lim FuncRef)) | (name, HostTable lim) <- items]
|
||||
instances <- allocTables tables
|
||||
let exps = Vector.fromList $ zipWith (\name i -> ExportInstance name (ExternTable i)) names [tableLen..]
|
||||
let inst' = inst {
|
||||
tableaddrs = Vector.fromList [tableLen..tableLen + length instances - 1],
|
||||
@@ -300,6 +335,8 @@ data ModuleInstance = ModuleInstance {
|
||||
tableaddrs :: Vector Address,
|
||||
memaddrs :: Vector Address,
|
||||
globaladdrs :: Vector Address,
|
||||
elemaddrs :: Vector Address,
|
||||
dataaddrs :: Vector Address,
|
||||
exports :: Vector ExportInstance
|
||||
} deriving (Eq, Show)
|
||||
|
||||
@@ -310,15 +347,20 @@ emptyModInstance = ModuleInstance {
|
||||
tableaddrs = Vector.empty,
|
||||
memaddrs = Vector.empty,
|
||||
globaladdrs = Vector.empty,
|
||||
elemaddrs = Vector.empty,
|
||||
dataaddrs = Vector.empty,
|
||||
exports = Vector.empty
|
||||
}
|
||||
|
||||
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 tableLen = length ts
|
||||
let memLen = length ms
|
||||
let globalLen = length gs
|
||||
let elemLen = length es
|
||||
let dataLen = length ds
|
||||
funImps <- mapM checkImportType $ filter isFuncImport imports
|
||||
tableImps <- mapM checkImportType $ filter isTableImport 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,
|
||||
memaddrs = memories,
|
||||
globaladdrs = globs,
|
||||
elemaddrs = Vector.fromList [elemLen..elemLen + length elems - 1],
|
||||
dataaddrs = Vector.fromList [dataLen..dataLen + length datas - 1],
|
||||
exports = Vector.fromList $ map refExport exports
|
||||
}
|
||||
where
|
||||
@@ -357,7 +401,7 @@ calcInstance (Store fs ts ms gs) imps Module {functions, types, tables, mems, gl
|
||||
funcAddr <- case idx of
|
||||
ExternFunction funcAddr -> return funcAddr
|
||||
other -> throwError "incompatible import type"
|
||||
let expectedType = types !! fromIntegral typeIdx
|
||||
let expectedType = types mod !! fromIntegral typeIdx
|
||||
let actualType = Language.Wasm.Interpreter.funcType $ fs ! funcAddr
|
||||
if expectedType == actualType
|
||||
then return idx
|
||||
@@ -379,17 +423,18 @@ calcInstance (Store fs ts ms gs) imps Module {functions, types, tables, mems, gl
|
||||
memAddr <- case idx of
|
||||
ExternMemory memAddr -> return memAddr
|
||||
_ -> throwError "incompatible import type"
|
||||
let MemoryInstance { lim } = ms ! memAddr
|
||||
if limitMatch lim limit
|
||||
let MemoryInstance { lim = Limit _ limMax, memory = mem } = ms ! memAddr
|
||||
size <- liftIO $ (`quot` pageSize) <$> (readIORef mem >>= ByteArray.getSizeofMutableByteArray)
|
||||
if limitMatch (Limit (fromIntegral size) limMax) limit
|
||||
then return idx
|
||||
else throwError "incompatible import type"
|
||||
checkImportType imp@(Import _ _ (ImportTable (TableType limit _))) = do
|
||||
checkImportType imp@(Import _ _ (ImportTable (TableType limit et))) = do
|
||||
idx <- getImpIdx imp
|
||||
tableAddr <- case idx of
|
||||
ExternTable tableAddr -> return tableAddr
|
||||
_ -> throwError "incompatible import type"
|
||||
let TableInstance { lim } = ts ! tableAddr
|
||||
if limitMatch lim limit
|
||||
let TableInstance { t = TableType lim et' } = ts ! tableAddr
|
||||
if limitMatch lim limit && et == et'
|
||||
then return idx
|
||||
else throwError "incompatible import type"
|
||||
|
||||
@@ -422,6 +467,9 @@ evalConstExpr _ _ [I32Const v] = return $ VI32 v
|
||||
evalConstExpr _ _ [I64Const v] = return $ VI64 v
|
||||
evalConstExpr _ _ [F32Const v] = return $ VF32 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 _ _ 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
|
||||
GIMut vt <$> newIORef val
|
||||
|
||||
allocTables :: [Table] -> Vector TableInstance
|
||||
allocTables tables = Vector.fromList $ map allocTable tables
|
||||
allocTables :: [Table] -> IO (Vector TableInstance)
|
||||
allocTables = fmap Vector.fromList . mapM allocTable
|
||||
where
|
||||
allocTable :: Table -> TableInstance
|
||||
allocTable (Table (TableType lim@(Limit from to) _)) =
|
||||
TableInstance {
|
||||
lim,
|
||||
elements = Vector.fromList $ replicate (fromIntegral from) Nothing
|
||||
}
|
||||
allocTable :: Table -> IO TableInstance
|
||||
allocTable (Table t@(TableType lim@(Limit from to) _)) =
|
||||
let elements = MVector.replicate (fromIntegral from) Nothing in
|
||||
TableInstance t <$> (elements >>= newIORef)
|
||||
|
||||
defaultBudget :: Natural
|
||||
defaultBudget = 300
|
||||
@@ -469,12 +515,30 @@ allocMems mems = Vector.fromList <$> mapM allocMem mems
|
||||
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)
|
||||
|
||||
initialize :: ModuleInstance -> Module -> Initialize ()
|
||||
initialize inst Module {elems, datas, start} = do
|
||||
checkedMems <- mapM checkData datas
|
||||
checkedTables <- mapM checkElem elems
|
||||
checkedTables <- mapM checkElem $ filter isActiveElem $ zip [0..] elems
|
||||
mapM_ initData checkedMems
|
||||
mapM_ initElem checkedTables
|
||||
st <- State.get
|
||||
@@ -487,55 +551,72 @@ initialize inst Module {elems, datas, start} = do
|
||||
_ -> throwError "Start function terminated with trap"
|
||||
Nothing -> return ()
|
||||
where
|
||||
checkElem :: ElemSegment -> Initialize (Address, Int, [Address])
|
||||
checkElem ElemSegment {tableIndex, offset, funcIndexes} = do
|
||||
isActiveElem :: (Int, ElemSegment) -> Bool
|
||||
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
|
||||
VI32 val <- liftIO $ evalConstExpr inst st offset
|
||||
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 last = from + length 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)
|
||||
return (idx, elemaddrs inst ! elemN, from, funcs)
|
||||
|
||||
initElem :: (Address, Int, [Address]) -> Initialize ()
|
||||
initElem (idx, from, funcs) = State.modify $ \st ->
|
||||
let TableInstance lim elems = tableInstances st ! idx in
|
||||
let table = TableInstance lim (elems // zip [from..] (map Just funcs)) in
|
||||
st { tableInstances = tableInstances st Vector.// [(idx, table)] }
|
||||
initElem :: (Address, Address, Int, [Maybe Address]) -> Initialize ()
|
||||
initElem (tableIdx, elemIdx, from, funcs) = do
|
||||
Store {tableInstances, elemInstances} <- State.get
|
||||
elems <- liftIO $ readIORef $ items $ tableInstances ! tableIdx
|
||||
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 {memIndex, offset, chunk} = do
|
||||
checkData :: DataSegment -> Initialize (Maybe (Int, MemoryStore, LBS.ByteString))
|
||||
checkData DataSegment {dataMode = ActiveData memIndex offset, chunk} = do
|
||||
st <- State.get
|
||||
VI32 val <- liftIO $ evalConstExpr inst st offset
|
||||
let from = fromIntegral val
|
||||
let idx = memaddrs inst ! fromIntegral memIndex
|
||||
let last = from + (fromIntegral $ LBS.length chunk)
|
||||
let MemoryInstance _ memory = memInstances st ! idx
|
||||
mem <- liftIO $ readIORef memory
|
||||
len <- ByteArray.getSizeofMutableByteArray mem
|
||||
Monad.when (last > len) $ throwError "data segment does not fit"
|
||||
return (from, mem, chunk)
|
||||
return $ Just (from, mem, chunk)
|
||||
checkData DataSegment {dataMode = PassiveData, chunk} =
|
||||
return Nothing
|
||||
|
||||
initData :: (Int, MemoryStore, LBS.ByteString) -> Initialize ()
|
||||
initData (from, mem, chunk) =
|
||||
initData :: Maybe (Int, MemoryStore, LBS.ByteString) -> Initialize ()
|
||||
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
|
||||
|
||||
instantiate :: Store -> Imports -> Valid.ValidModule -> IO (Either String ModuleInstance, Store)
|
||||
instantiate st imps mod = flip State.runStateT st $ runExceptT $ do
|
||||
let m = Valid.getModule mod
|
||||
inst <- calcInstance st imps m
|
||||
let functions = funcInstances st <> (allocFunctions inst $ Struct.functions m)
|
||||
globals <- liftIO $ (globalInstances st <>) <$> (allocAndInitGlobals inst st $ Struct.globals m)
|
||||
let tables = tableInstances st <> (allocTables $ Struct.tables m)
|
||||
mems <- liftIO $ (memInstances st <>) <$> (allocMems $ Struct.mems m)
|
||||
let functions = funcInstances st <> allocFunctions inst (Struct.functions m)
|
||||
globals <- liftIO $ (globalInstances st <>) <$> allocAndInitGlobals inst st (Struct.globals m)
|
||||
tables <- (tableInstances st <>) <$> liftIO (allocTables (Struct.tables 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 {
|
||||
funcInstances = functions,
|
||||
tableInstances = tables,
|
||||
memInstances = mems,
|
||||
globalInstances = globals
|
||||
globalInstances = globals,
|
||||
elemInstances = elems,
|
||||
dataInstances = datas
|
||||
}
|
||||
initialize inst m
|
||||
return inst
|
||||
@@ -579,6 +660,8 @@ eval budget store FunctionInstance { funcType, moduleInstance, code = Function {
|
||||
checkValType I64 (VI64 v) = Just $ VI64 v
|
||||
checkValType F32 (VF32 v) = Just $ VF32 v
|
||||
checkValType F64 (VF64 v) = Just $ VF64 v
|
||||
checkValType Func (RF v) = Just $ RF v
|
||||
checkValType Extern (RE v) = Just $ RE v
|
||||
checkValType _ _ = Nothing
|
||||
|
||||
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) }
|
||||
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 TableInstance { elements } = tableInstances store ! (tableaddrs moduleInstance ! 0)
|
||||
let checks = do
|
||||
addr <- Monad.join $ elements !? fromIntegral v
|
||||
let funcInst = funcInstances store ! addr
|
||||
let targetType = Language.Wasm.Interpreter.funcType funcInst
|
||||
Monad.guard $ targetType == funcType
|
||||
let args = params targetType
|
||||
Monad.guard $ length args <= length rest
|
||||
params <- sequence $ zipWith checkValType args $ reverse $ take (length args) rest
|
||||
return (funcInst, params)
|
||||
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
|
||||
let TableInstance { items } = tableInstances store ! (tableaddrs moduleInstance ! fromIntegral tableIdx)
|
||||
let pos = fromIntegral v
|
||||
funcs <- readIORef items
|
||||
if pos >= MVector.length funcs
|
||||
then return Trap
|
||||
else do
|
||||
maybeAddr <- MVector.unsafeRead funcs pos
|
||||
let checks = do
|
||||
addr <- maybeAddr
|
||||
let funcInst = funcInstances store ! addr
|
||||
let targetType = Language.Wasm.Interpreter.funcType funcInst
|
||||
Monad.guard $ targetType == funcType
|
||||
let args = params targetType
|
||||
Monad.guard $ length args <= length rest
|
||||
params <- sequence $ zipWith checkValType args $ reverse $ take (length args) rest
|
||||
return (funcInst, params)
|
||||
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 = (VI32 test:val2:val1:rest) } Select =
|
||||
step ctx@EvalCtx{ stack = (VI32 test:val2:val1:rest) } (Select _) =
|
||||
if test == 0
|
||||
then return $ Done ctx { stack = val2 : 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
|
||||
step ctx@EvalCtx{ stack = (VI64 v:rest) } (I64Store32 MemArg { offset }) =
|
||||
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)
|
||||
memory <- readIORef memoryRef
|
||||
size <- ((`quot` pageSize) . fromIntegral) <$> ByteArray.getSizeofMutableByteArray memory
|
||||
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)
|
||||
memory <- readIORef memoryRef
|
||||
size <- (`quot` pageSize) <$> ByteArray.getSizeofMutableByteArray memory
|
||||
@@ -822,6 +920,161 @@ eval budget store FunctionInstance { funcType, moduleInstance, code = Function {
|
||||
else return $ -1
|
||||
)
|
||||
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 (I64Const v) = return $ Done ctx { stack = VI64 v : stack ctx }
|
||||
step ctx (F32Const v) = return $ Done ctx { stack = VF32 v : stack ctx }
|
||||
@@ -1206,4 +1459,9 @@ getGlobalValueByName store ModuleInstance { exports } name =
|
||||
case globalInst of
|
||||
GIConst _ v -> return v
|
||||
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
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ module Language.Wasm.Lexer (
|
||||
Lexeme(..),
|
||||
Token(..),
|
||||
AlexPosn(..),
|
||||
FloatRep(..),
|
||||
NaN(..),
|
||||
scanner,
|
||||
asFloat,
|
||||
asDouble,
|
||||
@@ -22,6 +24,8 @@ import Data.List (isPrefixOf)
|
||||
import Text.Read (readEither)
|
||||
import Data.Bits
|
||||
import Numeric (showHex)
|
||||
import Control.DeepSeq (NFData)
|
||||
import GHC.Generics (Generic)
|
||||
|
||||
}
|
||||
|
||||
|
||||
+357
-98
@@ -93,6 +93,8 @@ import Language.Wasm.Lexer (
|
||||
),
|
||||
Lexeme(..),
|
||||
AlexPosn(..),
|
||||
FloatRep(..),
|
||||
NaN(..),
|
||||
asFloat,
|
||||
asDouble,
|
||||
doubleFromInteger
|
||||
@@ -119,6 +121,8 @@ import Language.Wasm.Lexer (
|
||||
'f64' { Lexeme _ (TKeyword "f64") }
|
||||
'mut' { Lexeme _ (TKeyword "mut") }
|
||||
'funcref' { Lexeme _ (TKeyword "funcref") }
|
||||
'externref' { Lexeme _ (TKeyword "externref") }
|
||||
'extern' { Lexeme _ (TKeyword "extern") }
|
||||
'type' { Lexeme _ (TKeyword "type") }
|
||||
'unreachable' { Lexeme _ (TKeyword "unreachable") }
|
||||
'nop' { Lexeme _ (TKeyword "nop") }
|
||||
@@ -128,6 +132,10 @@ import Language.Wasm.Lexer (
|
||||
'return' { Lexeme _ (TKeyword "return") }
|
||||
'call' { Lexeme _ (TKeyword "call") }
|
||||
'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") }
|
||||
'select' { Lexeme _ (TKeyword "select") }
|
||||
'get_local' { Lexeme _ (TKeyword "local.get") }
|
||||
@@ -160,6 +168,18 @@ import Language.Wasm.Lexer (
|
||||
'i64.store32' { Lexeme _ (TKeyword "i64.store32") }
|
||||
'memory.size' { Lexeme _ (TKeyword "memory.size") }
|
||||
'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") }
|
||||
'i64.const' { Lexeme _ (TKeyword "i64.const") }
|
||||
'f32.const' { Lexeme _ (TKeyword "f32.const") }
|
||||
@@ -313,6 +333,8 @@ import Language.Wasm.Lexer (
|
||||
'export' { Lexeme _ (TKeyword "export") }
|
||||
'local' { Lexeme _ (TKeyword "local") }
|
||||
'elem' { Lexeme _ (TKeyword "elem") }
|
||||
'item' { Lexeme _ (TKeyword "item") }
|
||||
'declare' { Lexeme _ (TKeyword "declare") }
|
||||
'data' { Lexeme _ (TKeyword "data") }
|
||||
'offset' { Lexeme _ (TKeyword "offset") }
|
||||
'start' { Lexeme _ (TKeyword "start") }
|
||||
@@ -363,6 +385,8 @@ valtype :: { ValueType }
|
||||
| 'i64' { I64 }
|
||||
| 'f32' { F32 }
|
||||
| 'f64' { F64 }
|
||||
| 'funcref' { Func }
|
||||
| 'externref' { Extern }
|
||||
|
||||
index :: { Index }
|
||||
: u32 { Index $1 }
|
||||
@@ -389,23 +413,23 @@ int64 :: { Integer }
|
||||
else Left ("Int literal value is out of signed int64 boundaries: " ++ show $1)
|
||||
}
|
||||
|
||||
float32 :: { Float }
|
||||
float32 :: { FloatRep }
|
||||
: int {%
|
||||
let maxInt = 340282356779733623858607532500980858880 in
|
||||
if $1 <= maxInt && $1 >= -maxInt
|
||||
then return $ fromIntegral $1
|
||||
then return $ BinRep $ fromIntegral $1
|
||||
else Left "constant out of range"
|
||||
}
|
||||
| f64 {% asFloat $1 }
|
||||
| f64 { $1 }
|
||||
|
||||
float64 :: { Double }
|
||||
float64 :: { FloatRep }
|
||||
: int {%
|
||||
let maxInt = round (maxFinite :: Double) in
|
||||
if $1 <= maxInt && $1 >= -maxInt
|
||||
then doubleFromInteger $1
|
||||
then fmap BinRep $ doubleFromInteger $1
|
||||
else Left "constant out of range"
|
||||
}
|
||||
| f64 {% asDouble $1 }
|
||||
| f64 { $1 }
|
||||
|
||||
plaininstr :: { PlainInstr }
|
||||
-- control instructions
|
||||
@@ -417,7 +441,11 @@ plaininstr :: { PlainInstr }
|
||||
| 'return' { Return }
|
||||
| 'call' index { Call $2 }
|
||||
| '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
|
||||
| 'get_local' index { GetLocal $2 }
|
||||
| 'set_local' index { SetLocal $2 }
|
||||
@@ -448,8 +476,25 @@ plaininstr :: { PlainInstr }
|
||||
| 'i64.store8' memarg1 { I64Store8 $2 }
|
||||
| 'i64.store16' memarg2 { I64Store16 $2 }
|
||||
| 'i64.store32' memarg4 { I64Store32 $2 }
|
||||
| 'memory.size' { CurrentMemory }
|
||||
| 'memory.grow' { GrowMemory }
|
||||
| 'memory.size' { MemorySize }
|
||||
| '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
|
||||
| 'i32.const' int32 { I32Const $2 }
|
||||
| 'i64.const' int64 { I64Const $2 }
|
||||
@@ -657,12 +702,30 @@ memarg4 :: { MemArg }
|
||||
memarg8 :: { MemArg }
|
||||
: 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)
|
||||
: terminator { ($1, []) }
|
||||
| plaininstr mixed_instruction_list(terminator) { ([PlainInstr $1] ++) `fmap` $2 }
|
||||
| 'call_indirect' typeuse(terminator) {%
|
||||
let (tu, instr, end) = $2 in
|
||||
onlyAnonimParams tu >> (return (end, [PlainInstr $ CallIndirect tu] ++ instr))
|
||||
| 'call_indirect' opt(index) typeuse(terminator) {%
|
||||
let tableIdx = fromMaybe (Index 0) $2 in
|
||||
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
|
||||
let (tu, instr, _) = $3
|
||||
@@ -700,9 +763,13 @@ folded_instr :: { [Instruction] }
|
||||
|
||||
folded_instr1 :: { [Instruction] }
|
||||
: plaininstr mixed_instruction_list(')') { snd $2 ++ [PlainInstr $1] }
|
||||
| 'call_indirect' typeuse(')') {%
|
||||
let (tu, instr, _) = $2 in
|
||||
onlyAnonimParams tu >> (return $ instr ++ [PlainInstr $ CallIndirect tu])
|
||||
| 'call_indirect' opt(index) typeuse(')') {%
|
||||
let tableIdx = fromMaybe (Index 0) $2 in
|
||||
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(')') {%
|
||||
let (typeUse, instr, _) = $3 in
|
||||
@@ -839,9 +906,12 @@ memory_limits_export_import1 :: { Maybe Ident -> [ModuleField] }
|
||||
| 'data' datastring ')' ')' {
|
||||
\ident ->
|
||||
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,
|
||||
MFData $ DataSegment (fromMaybe (Index 0) $ Named `fmap` ident) [PlainInstr $ I32Const 0] $2
|
||||
MFMem $ Memory [] ident $ Limit lim $ Just lim,
|
||||
MFData $ DataSegment Nothing (ActiveData memIdx [PlainInstr $ I32Const 0]) $2
|
||||
]
|
||||
}
|
||||
|
||||
@@ -856,6 +926,11 @@ limits :: { Limit }
|
||||
|
||||
elemtype :: { ElemType }
|
||||
: 'funcref' { FuncRef }
|
||||
| 'externref' { ExternRef }
|
||||
|
||||
heaptype :: { ElemType }
|
||||
: 'func' { FuncRef }
|
||||
| 'extern' { ExternRef }
|
||||
|
||||
tabletype :: { TableType }
|
||||
: limits elemtype { TableType $1 $2 }
|
||||
@@ -865,15 +940,24 @@ table :: { [ModuleField] }
|
||||
|
||||
limits_elemtype_elem :: { Maybe Ident -> [ModuleField] }
|
||||
: tabletype ')' { \ident -> [MFTable $ Table [] ident $1] }
|
||||
| elemtype '(' 'elem' list(index) ')' ')' {
|
||||
| elemtype '(' 'elem' indexes_or_ref_exprs ')' ')' {
|
||||
\ident ->
|
||||
let funcsLen = fromIntegral $ length $4 in [
|
||||
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 }
|
||||
|
||||
indexes_or_ref_exprs :: {[[Instruction]]}
|
||||
: index list(index) { funcIndexToExpr ($1:$2) }
|
||||
| elemexpr list(elemexpr) { $1 : $2 }
|
||||
| {- empty -} { [] }
|
||||
|
||||
import_export_table :: { Maybe Ident -> [ModuleField] }
|
||||
: 'import' name name ')' tabletype ')' {
|
||||
\ident -> [MFImport $ Import [] $2 $3 $ ImportTable ident $5]
|
||||
@@ -900,25 +984,58 @@ export :: { Export }
|
||||
start :: { StartFunction }
|
||||
: 'start' index ')' { StartFunction $2 }
|
||||
|
||||
-- TODO: Spec from 09 Jan 2018 declares 'offset' keyword as mandatory,
|
||||
-- but collection of testcases omits 'offset' in this position
|
||||
-- I am going to support both options for now, but maybe it has to be updated in future.
|
||||
offsetexpr :: { [Instruction] }
|
||||
elem :: { ElemSegment }
|
||||
: 'elem' opt(ident) elem1 { $3{ ident = $2 } }
|
||||
|
||||
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 }
|
||||
| folded_instr1 { $1 }
|
||||
|
||||
elemsegment :: { ElemSegment }
|
||||
: 'elem' opt(index) '(' offsetexpr list(index) ')' { ElemSegment (fromMaybe (Index 0) $2) $4 $5 }
|
||||
memory_offsetexpr1 :: { (MemoryIndex, [Instruction]) }
|
||||
: offsetexpr1 { (Index 0, $1)}
|
||||
| 'memory' index ')' '(' offsetexpr1 { ($2, $5) }
|
||||
|
||||
memory_mode :: { DataMode }
|
||||
: '(' memory_offsetexpr1 { uncurry ActiveData $2 }
|
||||
| {- empty -} { PassiveData }
|
||||
|
||||
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 }
|
||||
: typedef { MFType $1 }
|
||||
| import { MFImport $1 }
|
||||
| export { MFExport $1 }
|
||||
| start { MFStart $1 }
|
||||
| elemsegment { MFElem $1 }
|
||||
| elem { MFElem $1 }
|
||||
| datasegment { MFData $1 }
|
||||
| function { $1 }
|
||||
| global { $1 }
|
||||
@@ -964,11 +1081,15 @@ module1 :: { ModuleDef }
|
||||
| 'module' opt(ident) list(modulefield) ')' {% RawModDef $2 `fmap` (desugarize $ concat $3) }
|
||||
|
||||
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 }
|
||||
|
||||
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_arithmetic_nan' '(' action1 ')' { ($1, AssertReturnArithmeticNaN $3) }
|
||||
| '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
|
||||
| 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 [] []
|
||||
@@ -1092,11 +1213,11 @@ emptyFuncType = FuncType [] []
|
||||
data ParamType = ParamType {
|
||||
ident :: Maybe Ident,
|
||||
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 FuncIndex = Index
|
||||
@@ -1105,6 +1226,8 @@ type LocalIndex = Index
|
||||
type GlobalIndex = Index
|
||||
type TableIndex = Index
|
||||
type MemoryIndex = Index
|
||||
type ElemIndex = Index
|
||||
type DataIndex = Index
|
||||
|
||||
data PlainInstr =
|
||||
-- Control instructions
|
||||
@@ -1115,10 +1238,15 @@ data PlainInstr =
|
||||
| BrTable [LabelIndex] LabelIndex
|
||||
| Return
|
||||
| Call FuncIndex
|
||||
| CallIndirect TypeUse
|
||||
| CallIndirect TableIndex TypeUse
|
||||
-- Reference instructions
|
||||
| RefNull ElemType
|
||||
| RefIsNull
|
||||
| RefFunc FuncIndex
|
||||
| RefExtern Natural
|
||||
-- Parametric instructions
|
||||
| Drop
|
||||
| Select
|
||||
| Select (Maybe [ValueType])
|
||||
-- Variable instructions
|
||||
| GetLocal LocalIndex
|
||||
| SetLocal LocalIndex
|
||||
@@ -1149,13 +1277,26 @@ data PlainInstr =
|
||||
| I64Store8 MemArg
|
||||
| I64Store16 MemArg
|
||||
| I64Store32 MemArg
|
||||
| CurrentMemory
|
||||
| GrowMemory
|
||||
| MemorySize
|
||||
| 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
|
||||
| I32Const Integer
|
||||
| I64Const Integer
|
||||
| F32Const Float
|
||||
| F64Const Double
|
||||
| F32Const FloatRep
|
||||
| F64Const FloatRep
|
||||
| IUnOp BitSize IUnOp
|
||||
| IBinOp BitSize IBinOp
|
||||
| I32Eqz
|
||||
@@ -1177,14 +1318,14 @@ data PlainInstr =
|
||||
| F64PromoteF32
|
||||
| IReinterpretF 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 =
|
||||
IndexedTypeUse TypeIndex (Maybe FuncType)
|
||||
| AnonimousTypeUse FuncType
|
||||
deriving (Show, Eq, Generic, NFData)
|
||||
deriving (Show, Eq)
|
||||
|
||||
emptyTypeUse = AnonimousTypeUse emptyFuncType
|
||||
|
||||
@@ -1206,26 +1347,26 @@ data Instruction =
|
||||
trueBranch :: [Instruction],
|
||||
falseBranch :: [Instruction]
|
||||
}
|
||||
deriving (Show, Eq, Generic, NFData)
|
||||
deriving (Show, Eq)
|
||||
|
||||
data Import = Import {
|
||||
reExportAs :: [TL.Text],
|
||||
sourceModule :: TL.Text,
|
||||
name :: TL.Text,
|
||||
desc :: ImportDesc
|
||||
} deriving (Show, Eq, Generic, NFData)
|
||||
} deriving (Show, Eq)
|
||||
|
||||
data ImportDesc =
|
||||
ImportFunc (Maybe Ident) TypeUse
|
||||
| ImportTable (Maybe Ident) TableType
|
||||
| ImportMemory (Maybe Ident) Limit
|
||||
| ImportGlobal (Maybe Ident) GlobalType
|
||||
deriving (Show, Eq, Generic, NFData)
|
||||
deriving (Show, Eq)
|
||||
|
||||
data LocalType = LocalType {
|
||||
ident :: Maybe Ident,
|
||||
localType :: ValueType
|
||||
} deriving (Show, Eq, Generic, NFData)
|
||||
} deriving (Show, Eq)
|
||||
|
||||
data Function = Function {
|
||||
exportFuncAs :: [TL.Text],
|
||||
@@ -1234,7 +1375,7 @@ data Function = Function {
|
||||
locals :: [LocalType],
|
||||
body :: [Instruction]
|
||||
}
|
||||
deriving (Show, Eq, Generic, NFData)
|
||||
deriving (Show, Eq)
|
||||
|
||||
emptyFunction :: Function
|
||||
emptyFunction =
|
||||
@@ -1252,40 +1393,52 @@ data Global = Global {
|
||||
globalType :: GlobalType,
|
||||
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 =
|
||||
ExportFunc FuncIndex
|
||||
| ExportTable TableIndex
|
||||
| ExportMemory MemoryIndex
|
||||
| ExportGlobal GlobalIndex
|
||||
deriving (Show, Eq, Generic, NFData)
|
||||
deriving (Show, Eq)
|
||||
|
||||
data Export = Export {
|
||||
name :: TL.Text,
|
||||
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 {
|
||||
tableIndex :: TableIndex,
|
||||
offset :: [Instruction],
|
||||
funcIndexes :: [FuncIndex]
|
||||
ident :: Maybe Ident,
|
||||
elemType :: ElemType,
|
||||
mode :: ElemMode,
|
||||
elements :: [[Instruction]]
|
||||
}
|
||||
deriving (Show, Eq, Generic, NFData)
|
||||
deriving (Show, Eq)
|
||||
|
||||
data DataMode =
|
||||
PassiveData
|
||||
| ActiveData MemoryIndex [Instruction]
|
||||
deriving (Show, Eq)
|
||||
|
||||
data DataSegment = DataSegment {
|
||||
memIndex :: MemoryIndex,
|
||||
offset :: [Instruction],
|
||||
ident :: Maybe Ident,
|
||||
dataMode :: DataMode,
|
||||
datastring :: LBS.ByteString
|
||||
}
|
||||
deriving (Show, Eq, Generic, NFData)
|
||||
deriving (Show, Eq)
|
||||
|
||||
data ModuleField =
|
||||
MFType TypeDef
|
||||
@@ -1298,7 +1451,7 @@ data ModuleField =
|
||||
| MFStart StartFunction
|
||||
| MFElem ElemSegment
|
||||
| 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 Nothing tok : tokens) = Left $ "Error occuried during parsing phase at the end of file"
|
||||
@@ -1371,12 +1524,17 @@ data FunCtx = FunCtx {
|
||||
ctxParams :: [ParamType]
|
||||
} deriving (Eq, Show)
|
||||
|
||||
constInstructionToValue :: Instruction -> S.Instruction Natural
|
||||
constInstructionToValue (PlainInstr (I32Const v)) = S.I32Const $ integerToWord32 v
|
||||
constInstructionToValue (PlainInstr (F32Const v)) = S.F32Const v
|
||||
constInstructionToValue (PlainInstr (I64Const v)) = S.I64Const $ integerToWord64 v
|
||||
constInstructionToValue (PlainInstr (F64Const v)) = S.F64Const v
|
||||
constInstructionToValue _ = error "Only const instructions supported as arguments for actions"
|
||||
constInstructionToValue :: Instruction -> Either String (S.Instruction Natural)
|
||||
constInstructionToValue (PlainInstr (I32Const v)) = return $ S.I32Const $ integerToWord32 v
|
||||
constInstructionToValue (PlainInstr (F32Const v)) = S.F32Const <$> asFloat v
|
||||
constInstructionToValue (PlainInstr (I64Const v)) = return $ S.I64Const $ integerToWord64 v
|
||||
constInstructionToValue (PlainInstr (F64Const v)) = S.F64Const <$> asDouble v
|
||||
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 fields = do
|
||||
@@ -1463,17 +1621,13 @@ desugarize fields = do
|
||||
extractTypeDefFromInstructions (matchTypeUse defs funcType) body
|
||||
extractTypeDef defs (MFGlobal Global { initializer }) =
|
||||
extractTypeDefFromInstructions defs initializer
|
||||
extractTypeDef defs (MFElem ElemSegment { offset }) =
|
||||
extractTypeDefFromInstructions defs offset
|
||||
extractTypeDef defs (MFData DataSegment { offset }) =
|
||||
extractTypeDefFromInstructions defs offset
|
||||
extractTypeDef defs _ = defs
|
||||
|
||||
extractTypeDefFromInstructions :: [TypeDef] -> [Instruction] -> [TypeDef]
|
||||
extractTypeDefFromInstructions = foldl' extractTypeDefFromInstruction
|
||||
|
||||
extractTypeDefFromInstruction :: [TypeDef] -> Instruction -> [TypeDef]
|
||||
extractTypeDefFromInstruction defs (PlainInstr (CallIndirect typeUse)) =
|
||||
extractTypeDefFromInstruction defs (PlainInstr (CallIndirect _ typeUse)) =
|
||||
matchTypeUse defs typeUse
|
||||
extractTypeDefFromInstruction defs (BlockInstr { body, blockType }) =
|
||||
extractTypeDefFromInstructions (matchTypeUse defs blockType) body
|
||||
@@ -1556,12 +1710,23 @@ desugarize fields = do
|
||||
case getFuncIndex ctxMod funIdx of
|
||||
Just idx -> return $ S.Call idx
|
||||
Nothing -> Left "unknown function"
|
||||
synInstrToStruct FunCtx { ctxMod = Module { types } } (PlainInstr (CallIndirect typeUse)) =
|
||||
case getTypeIndex types typeUse of
|
||||
Just idx -> return $ S.CallIndirect idx
|
||||
Nothing -> Left "unknown type"
|
||||
synInstrToStruct FunCtx { ctxMod } (PlainInstr (CallIndirect tableIdx typeUse)) =
|
||||
case getTableIndex ctxMod tableIdx of
|
||||
Just tableIdx ->
|
||||
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 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)) =
|
||||
case getLocalIndex ctx localIdx of
|
||||
Just idx -> return $ S.GetLocal idx
|
||||
@@ -1605,12 +1770,68 @@ desugarize fields = do
|
||||
synInstrToStruct _ (PlainInstr (I64Store8 memArg)) = return $ S.I64Store8 memArg
|
||||
synInstrToStruct _ (PlainInstr (I64Store16 memArg)) = return $ S.I64Store16 memArg
|
||||
synInstrToStruct _ (PlainInstr (I64Store32 memArg)) = return $ S.I64Store32 memArg
|
||||
synInstrToStruct _ (PlainInstr CurrentMemory) = return $ S.CurrentMemory
|
||||
synInstrToStruct _ (PlainInstr GrowMemory) = return $ S.GrowMemory
|
||||
synInstrToStruct _ (PlainInstr MemorySize) = return $ S.MemorySize
|
||||
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 (I64Const val)) = return $ S.I64Const $ integerToWord64 val
|
||||
synInstrToStruct _ (PlainInstr (F32Const val)) = return $ S.F32Const val
|
||||
synInstrToStruct _ (PlainInstr (F64Const val)) = return $ S.F64Const val
|
||||
synInstrToStruct _ (PlainInstr (F32Const (NanRep Arithmetic))) =
|
||||
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 (IBinOp sz op)) = return $ S.IBinOp sz op
|
||||
synInstrToStruct _ (PlainInstr I32Eqz) = return $ S.I32Eqz
|
||||
@@ -1878,29 +2099,59 @@ desugarize fields = do
|
||||
|
||||
-- elem segment
|
||||
synElemToStruct :: Module -> ElemSegment -> Either String S.ElemSegment
|
||||
synElemToStruct mod ElemSegment { tableIndex, offset, funcIndexes } =
|
||||
let ctx = FunCtx mod [] [] [] in
|
||||
let offsetInstrs = mapM (synInstrToStruct ctx) offset in
|
||||
let idx = fromJust $ getTableIndex mod tableIndex in
|
||||
let indexes = map (fromJust . getFuncIndex mod) funcIndexes in
|
||||
S.ElemSegment idx <$> offsetInstrs <*> return indexes
|
||||
synElemToStruct mod ElemSegment { ident, elemType, mode, elements } = do
|
||||
let ctx = FunCtx mod [] [] []
|
||||
m <- case mode of {
|
||||
Active tableIndex offset ->
|
||||
let offsetInstrs = mapM (synInstrToStruct ctx) offset in
|
||||
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 elems (MFElem elem) = elem : 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
|
||||
synDataToStruct :: Module -> DataSegment -> Either String S.DataSegment
|
||||
synDataToStruct mod DataSegment { memIndex, offset, datastring } =
|
||||
let ctx = FunCtx mod [] [] [] in
|
||||
let offsetInstrs = mapM (synInstrToStruct ctx) offset in
|
||||
let idx = fromJust $ getMemIndex mod memIndex in
|
||||
S.DataSegment idx <$> offsetInstrs <*> return datastring
|
||||
synDataToStruct mod DataSegment { dataMode, datastring } = do
|
||||
m <- case dataMode of
|
||||
PassiveData -> return S.PassiveData
|
||||
ActiveData memIndex offset -> do
|
||||
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 datas (MFData dataSegment) = dataSegment : 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
|
||||
synStartToStruct :: Module -> StartFunction -> S.StartFunction
|
||||
synStartToStruct mod (StartFunction funIdx) =
|
||||
@@ -1916,14 +2167,22 @@ desugarize fields = do
|
||||
-- exports
|
||||
extractExports :: Module -> [ModuleField] -> [ModuleField]
|
||||
extractExports mod mf =
|
||||
let initial = (funcImportLength, globImportLength, memImportLength, tableImportLength, []) in
|
||||
let (_, _, _, _, result) = foldl' extractExport initial mf in
|
||||
let fromImports = foldl' reexport (0, 0, 0, 0, []) $ imports mod in
|
||||
let (_, _, _, _, result) = foldl' extractExport fromImports mf in
|
||||
reverse result
|
||||
where
|
||||
funcImportLength = fromIntegral $ length $ filter isFuncImport $ imports mod
|
||||
globImportLength = fromIntegral $ length $ filter isGlobalImport $ imports mod
|
||||
memImportLength = fromIntegral $ length $ filter isMemImport $ imports mod
|
||||
tableImportLength = fromIntegral $ length $ filter isTableImport $ imports mod
|
||||
reexport (fidx, gidx, midx, tidx, mf) (Import {reExportAs, desc = ImportFunc _ _}) =
|
||||
let exports = map (\name -> MFExport $ Export name $ ExportFunc $ Index fidx) reExportAs in
|
||||
(fidx + 1, gidx, midx, tidx, exports ++ mf)
|
||||
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 }) =
|
||||
let exports = map (\name -> MFExport $ Export name $ ExportFunc $ Index fidx) exportFuncAs in
|
||||
|
||||
@@ -13,6 +13,7 @@ import Control.Monad.IO.Class (liftIO)
|
||||
import Numeric.IEEE (identicalIEEE)
|
||||
import qualified Control.DeepSeq as DeepSeq
|
||||
import Data.Maybe (fromJust, isNothing)
|
||||
import Debug.Trace (trace)
|
||||
|
||||
import Language.Wasm.Parser (
|
||||
Ident(..),
|
||||
@@ -122,7 +123,10 @@ runScript onAssertFail script = do
|
||||
asArg [Struct.F32Const v] = Interpreter.VF32 v
|
||||
asArg [Struct.I64Const v] = Interpreter.VI64 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 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.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.RF f1) (Interpreter.RF f2) = f1 == f2
|
||||
isValueEqual (Interpreter.RE e1) (Interpreter.RE e2) = e1 == e2
|
||||
isValueEqual _ _ = False
|
||||
|
||||
isNaNReturned :: Action -> Assertion -> AssertM ()
|
||||
@@ -161,7 +167,7 @@ runScript onAssertFail script = do
|
||||
let Right m = Lexer.scanner (TLEncoding.encodeUtf8 textRep) >>= Parser.parseModule in
|
||||
(ident, m)
|
||||
buildModule (BinaryModDef ident binaryRep) =
|
||||
let Right m = Binary.decodeModuleLazy binaryRep in
|
||||
let Right m = Binary.decodeModuleLazy binaryRep in
|
||||
(ident, m)
|
||||
|
||||
checkModuleInvalid :: Struct.Module -> IO ()
|
||||
@@ -169,13 +175,13 @@ runScript onAssertFail script = do
|
||||
|
||||
getFailureString :: Validate.ValidationError -> [TL.Text]
|
||||
getFailureString (Validate.TypeMismatch _ _) = ["type mismatch"]
|
||||
getFailureString (Validate.RefTypeMismatch _ _) = ["type mismatch"]
|
||||
getFailureString Validate.ResultTypeDoesntMatch = ["type mismatch"]
|
||||
getFailureString Validate.MoreThanOneMemory = ["multiple memories"]
|
||||
getFailureString Validate.MoreThanOneTable = ["multiple tables"]
|
||||
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.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.LabelIndexOutOfRange = ["unknown label"]
|
||||
getFailureString Validate.TypeIndexOutOfRange = ["unknown type"]
|
||||
@@ -188,7 +194,10 @@ runScript onAssertFail script = do
|
||||
getFailureString Validate.GlobalIsImmutable = ["global is immutable"]
|
||||
getFailureString Validate.InvalidStartFunctionType = ["start function"]
|
||||
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 msg assert = do
|
||||
@@ -253,12 +262,14 @@ runScript onAssertFail script = do
|
||||
let (_, m) = buildModule moduleDef in
|
||||
case Validate.validate m of
|
||||
Right m -> do
|
||||
st <- fst <$> State.get
|
||||
(st, pos) <- State.get
|
||||
(res, store') <- liftIO $ Interpreter.instantiate (store st) (buildImports st) m
|
||||
State.put (st { store = store' }, pos)
|
||||
case res of
|
||||
Left err | err == TL.unpack failureString -> return ()
|
||||
Left "Start function terminated with trap" ->
|
||||
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
|
||||
runAssert assert@(AssertExhaustion action failureString) = do
|
||||
result <- runActionInAssert action
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
module Language.Wasm.Structure (
|
||||
Module(..),
|
||||
DataMode(..),
|
||||
DataSegment(..),
|
||||
ElemSegment(..),
|
||||
ElemMode(..),
|
||||
StartFunction(..),
|
||||
Export(..),
|
||||
ExportDesc(..),
|
||||
@@ -102,12 +104,16 @@ type LocalIndex = Natural
|
||||
type GlobalIndex = Natural
|
||||
type MemoryIndex = Natural
|
||||
type TableIndex = Natural
|
||||
type DataIndex = Natural
|
||||
type ElemIndex = Natural
|
||||
|
||||
data ValueType =
|
||||
I32
|
||||
| I64
|
||||
| F32
|
||||
| F64
|
||||
| Func
|
||||
| Extern
|
||||
deriving (Show, Eq, Generic, NFData)
|
||||
|
||||
type ResultType = [ValueType]
|
||||
@@ -133,10 +139,15 @@ data Instruction index =
|
||||
| BrTable [index] index
|
||||
| Return
|
||||
| Call index
|
||||
| CallIndirect index
|
||||
| CallIndirect index index
|
||||
-- Reference instructions
|
||||
| RefNull ElemType
|
||||
| RefIsNull
|
||||
| RefFunc index
|
||||
| RefExtern Natural
|
||||
-- Parametric instructions
|
||||
| Drop
|
||||
| Select
|
||||
| Select (Maybe [ValueType])
|
||||
-- Variable instructions
|
||||
| GetLocal index
|
||||
| SetLocal index
|
||||
@@ -167,8 +178,21 @@ data Instruction index =
|
||||
| I64Store8 MemArg
|
||||
| I64Store16 MemArg
|
||||
| I64Store32 MemArg
|
||||
| CurrentMemory
|
||||
| GrowMemory
|
||||
| MemorySize
|
||||
| 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
|
||||
| I32Const Word32
|
||||
| I64Const Word64
|
||||
@@ -207,7 +231,7 @@ data Function = Function {
|
||||
|
||||
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)
|
||||
|
||||
@@ -222,15 +246,25 @@ data Global = Global {
|
||||
initializer :: Expression
|
||||
} deriving (Show, Eq, Generic, NFData)
|
||||
|
||||
data ElemMode =
|
||||
Passive
|
||||
| Active TableIndex Expression
|
||||
| Declarative
|
||||
deriving (Show, Eq, Generic, NFData)
|
||||
|
||||
data ElemSegment = ElemSegment {
|
||||
tableIndex :: TableIndex,
|
||||
offset :: Expression,
|
||||
funcIndexes :: [FuncIndex]
|
||||
elemType :: ElemType,
|
||||
mode :: ElemMode,
|
||||
elements :: [Expression]
|
||||
} deriving (Show, Eq, Generic, NFData)
|
||||
|
||||
data DataMode =
|
||||
PassiveData
|
||||
| ActiveData MemoryIndex Expression
|
||||
deriving (Show, Eq, Generic, NFData)
|
||||
|
||||
data DataSegment = DataSegment {
|
||||
memIndex :: MemoryIndex,
|
||||
offset :: Expression,
|
||||
dataMode :: DataMode,
|
||||
chunk :: LBS.ByteString
|
||||
} deriving (Show, Eq, Generic, NFData)
|
||||
|
||||
|
||||
+307
-153
@@ -15,11 +15,11 @@ import Language.Wasm.Structure
|
||||
import qualified Data.Set as Set
|
||||
import Data.List (foldl')
|
||||
import qualified Data.Text.Lazy as TL
|
||||
import Data.Maybe (fromMaybe, maybeToList, catMaybes)
|
||||
import Data.Maybe (fromMaybe, catMaybes)
|
||||
import Numeric.Natural (Natural)
|
||||
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.Except (Except, runExcept, throwError)
|
||||
|
||||
@@ -32,20 +32,23 @@ data ValidationError =
|
||||
| MemoryLimitExceeded
|
||||
| AlignmentOverflow
|
||||
| MoreThanOneMemory
|
||||
| MoreThanOneTable
|
||||
| FunctionIndexOutOfRange
|
||||
| FunctionIndexOutOfRange Natural
|
||||
| TableIndexOutOfRange Natural
|
||||
| MemoryIndexOutOfRange Natural
|
||||
| LocalIndexOutOfRange Natural
|
||||
| GlobalIndexOutOfRange Natural
|
||||
| ElemIndexOutOfRange Natural
|
||||
| DataIndexOutOfRange Natural
|
||||
| LabelIndexOutOfRange
|
||||
| TypeIndexOutOfRange
|
||||
| ResultTypeDoesntMatch
|
||||
| TypeMismatch { actual :: Arrow, expected :: Arrow }
|
||||
| RefTypeMismatch ElemType ElemType
|
||||
| InvalidResultArity
|
||||
| InvalidConstantExpr
|
||||
| InvalidStartFunctionType
|
||||
| GlobalIsImmutable
|
||||
| UndeclaredFunctionRef Natural
|
||||
deriving (Show, Eq)
|
||||
|
||||
type ValidationResult = Either ValidationError ()
|
||||
@@ -62,13 +65,14 @@ instance Monoid ValidationResult where
|
||||
|
||||
isValid :: ValidationResult -> Bool
|
||||
isValid (Right ()) = True
|
||||
isValid (Left reason) = Debug.trace ("Module mismatched with reason " ++ show reason) $ False
|
||||
isValid (Left reason) = False
|
||||
|
||||
type Validator = Module -> ValidationResult
|
||||
|
||||
data VType =
|
||||
Val ValueType
|
||||
| Var
|
||||
| NonRefVar
|
||||
| Any
|
||||
deriving (Show, Eq)
|
||||
|
||||
@@ -103,6 +107,11 @@ asArrow (FuncType params results) = Arrow (map Val params) (map Val $ reverse re
|
||||
isArrowMatch :: Arrow -> Arrow -> Bool
|
||||
isArrowMatch (f `Arrow` t) ( f' `Arrow` t') = isEndMatch f f' && isEndMatch t t'
|
||||
where
|
||||
isRef :: VType -> Bool
|
||||
isRef (Val Func) = True
|
||||
isRef (Val Extern) = True
|
||||
isRef _ = False
|
||||
|
||||
isEndMatch :: End -> End -> Bool
|
||||
isEndMatch (Any:l) (Any:r) =
|
||||
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) =
|
||||
let subst = replace Var x in
|
||||
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 [] [] = True
|
||||
isEndMatch _ _ = False
|
||||
@@ -127,12 +142,15 @@ data Ctx = Ctx {
|
||||
types :: [FuncType],
|
||||
funcs :: [FuncType],
|
||||
tables :: [TableType],
|
||||
elems :: [ElemType],
|
||||
datas :: [DataMode],
|
||||
mems :: [Limit],
|
||||
globals :: [GlobalType],
|
||||
locals :: [ValueType],
|
||||
labels :: [[ValueType]],
|
||||
returns :: [ValueType],
|
||||
importedGlobals :: Natural
|
||||
importedGlobals :: Natural,
|
||||
refs :: Set.Set Natural
|
||||
} deriving (Show, Eq)
|
||||
|
||||
type Checker = ReaderT Ctx (Except ValidationError)
|
||||
@@ -197,24 +215,28 @@ getResultType (TypeIndex typeIdx) = do
|
||||
Ctx { types } <- ask
|
||||
maybeToEither TypeIndexOutOfRange $ results <$> types !? typeIdx
|
||||
|
||||
getInstrType :: Instruction Natural -> Checker Arrow
|
||||
getInstrType Unreachable = return $ Any ==> Any
|
||||
getInstrType Nop = return $ empty ==> empty
|
||||
getInstrType Block { blockType, body } = do
|
||||
elemTypeToRefType :: ElemType -> ValueType
|
||||
elemTypeToRefType FuncRef = Func
|
||||
elemTypeToRefType ExternRef = Extern
|
||||
|
||||
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
|
||||
resultType <- getResultType blockType
|
||||
t <- withLabel resultType $ getExpressionTypeWithInput from body
|
||||
if isArrowMatch t bt
|
||||
then return bt
|
||||
else throwError $ TypeMismatch t bt
|
||||
getInstrType Loop { blockType, body } = do
|
||||
getInstrType _ Loop { blockType, body } = do
|
||||
bt@(Arrow from _) <- getBlockType blockType
|
||||
resultType <- getResultType blockType
|
||||
t <- withLabel (map (\(Val v) -> v) from) $ getExpressionTypeWithInput from body
|
||||
if isArrowMatch t bt
|
||||
then return bt
|
||||
else throwError $ TypeMismatch t bt
|
||||
getInstrType If { blockType, true, false } = do
|
||||
getInstrType _ If { blockType, true, false } = do
|
||||
bt@(Arrow from _) <- getBlockType blockType
|
||||
resultType <- getResultType blockType
|
||||
l <- withLabel resultType $ getExpressionTypeWithInput from true
|
||||
@@ -227,184 +249,271 @@ getInstrType If { blockType, true, false } = do
|
||||
else (throwError $ TypeMismatch r bt)
|
||||
)
|
||||
else throwError $ TypeMismatch l bt
|
||||
getInstrType (Br lbl) = do
|
||||
getInstrType _ (Br lbl) = do
|
||||
r <- map Val <$> getLabel lbl
|
||||
return $ (Any : r) ==> Any
|
||||
getInstrType (BrIf lbl) = do
|
||||
getInstrType _ (BrIf lbl) = do
|
||||
r <- map Val <$> getLabel lbl
|
||||
return $ (r ++ [Val I32]) ==> r
|
||||
getInstrType (BrTable lbls lbl) = do
|
||||
getInstrType stack (BrTable lbls lbl) = do
|
||||
r <- getLabel lbl
|
||||
rs <- mapM getLabel lbls
|
||||
if all (== r) rs
|
||||
let returns lbl = do
|
||||
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
|
||||
else throwError ResultTypeDoesntMatch
|
||||
getInstrType Return = do
|
||||
getInstrType _ Return = do
|
||||
Ctx { returns } <- ask
|
||||
return $ (Any : (map Val returns)) ==> Any
|
||||
getInstrType (Call fun) = do
|
||||
getInstrType _ (Call fun) = do
|
||||
Ctx { funcs } <- ask
|
||||
maybeToEither FunctionIndexOutOfRange $ asArrow <$> funcs !? fun
|
||||
getInstrType (CallIndirect sign) = do
|
||||
maybeToEither (FunctionIndexOutOfRange fun) $ asArrow <$> funcs !? fun
|
||||
getInstrType _ (CallIndirect tableIdx sign) = do
|
||||
Ctx { types, tables } <- ask
|
||||
if length tables < 1
|
||||
then throwError (TableIndexOutOfRange 0)
|
||||
if length tables <= fromIntegral tableIdx
|
||||
then throwError (TableIndexOutOfRange tableIdx)
|
||||
else do
|
||||
Arrow from to <- maybeToEither TypeIndexOutOfRange $ asArrow <$> types !? sign
|
||||
return $ (from ++ [Val I32]) ==> to
|
||||
getInstrType Drop = do
|
||||
getInstrType _ Drop = do
|
||||
var <- freshVar
|
||||
return $ var ==> empty
|
||||
getInstrType Select = do
|
||||
var <- freshVar
|
||||
getInstrType _ (Select Nothing) = do
|
||||
var <- return NonRefVar
|
||||
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
|
||||
t <- maybeToEither (LocalIndexOutOfRange local) $ locals !? local
|
||||
return $ empty ==> Val t
|
||||
getInstrType (SetLocal local) = do
|
||||
getInstrType _ (SetLocal local) = do
|
||||
Ctx { locals } <- ask
|
||||
t <- maybeToEither (LocalIndexOutOfRange local) $ locals !? local
|
||||
return $ Val t ==> empty
|
||||
getInstrType (TeeLocal local) = do
|
||||
getInstrType _ (TeeLocal local) = do
|
||||
Ctx { locals } <- ask
|
||||
t <- maybeToEither (LocalIndexOutOfRange local) $ locals !? local
|
||||
return $ Val t ==> Val t
|
||||
getInstrType (GetGlobal global) = do
|
||||
getInstrType _ (GetGlobal global) = do
|
||||
Ctx { globals } <- ask
|
||||
t <- maybeToEither (GlobalIndexOutOfRange global) $ asType <$> globals !? global
|
||||
return $ empty ==> t
|
||||
getInstrType (SetGlobal global) = do
|
||||
getInstrType _ (SetGlobal global) = do
|
||||
Ctx { globals } <- ask
|
||||
t <- maybeToEither (GlobalIndexOutOfRange global) $ asType <$> globals !? global
|
||||
shouldBeMut $ globals !! fromIntegral global
|
||||
return $ t ==> empty
|
||||
getInstrType (I32Load memarg) = do
|
||||
getInstrType _ (I32Load memarg) = do
|
||||
checkMemoryInstr 4 memarg
|
||||
return $ I32 ==> I32
|
||||
getInstrType (I64Load memarg) = do
|
||||
getInstrType _ (I64Load memarg) = do
|
||||
checkMemoryInstr 8 memarg
|
||||
return $ I32 ==> I64
|
||||
getInstrType (F32Load memarg) = do
|
||||
getInstrType _ (F32Load memarg) = do
|
||||
checkMemoryInstr 4 memarg
|
||||
return $ I32 ==> F32
|
||||
getInstrType (F64Load memarg) = do
|
||||
getInstrType _ (F64Load memarg) = do
|
||||
checkMemoryInstr 8 memarg
|
||||
return $ I32 ==> F64
|
||||
getInstrType (I32Load8S memarg) = do
|
||||
getInstrType _ (I32Load8S memarg) = do
|
||||
checkMemoryInstr 1 memarg
|
||||
return $ I32 ==> I32
|
||||
getInstrType (I32Load8U memarg) = do
|
||||
getInstrType _ (I32Load8U memarg) = do
|
||||
checkMemoryInstr 1 memarg
|
||||
return $ I32 ==> I32
|
||||
getInstrType (I32Load16S memarg) = do
|
||||
getInstrType _ (I32Load16S memarg) = do
|
||||
checkMemoryInstr 2 memarg
|
||||
return $ I32 ==> I32
|
||||
getInstrType (I32Load16U memarg) = do
|
||||
getInstrType _ (I32Load16U memarg) = do
|
||||
checkMemoryInstr 2 memarg
|
||||
return $ I32 ==> I32
|
||||
getInstrType (I64Load8S memarg) = do
|
||||
getInstrType _ (I64Load8S memarg) = do
|
||||
checkMemoryInstr 1 memarg
|
||||
return $ I32 ==> I64
|
||||
getInstrType (I64Load8U memarg) = do
|
||||
getInstrType _ (I64Load8U memarg) = do
|
||||
checkMemoryInstr 1 memarg
|
||||
return $ I32 ==> I64
|
||||
getInstrType (I64Load16S memarg) = do
|
||||
getInstrType _ (I64Load16S memarg) = do
|
||||
checkMemoryInstr 2 memarg
|
||||
return $ I32 ==> I64
|
||||
getInstrType (I64Load16U memarg) = do
|
||||
getInstrType _ (I64Load16U memarg) = do
|
||||
checkMemoryInstr 2 memarg
|
||||
return $ I32 ==> I64
|
||||
getInstrType (I64Load32S memarg) = do
|
||||
getInstrType _ (I64Load32S memarg) = do
|
||||
checkMemoryInstr 4 memarg
|
||||
return $ I32 ==> I64
|
||||
getInstrType (I64Load32U memarg) = do
|
||||
getInstrType _ (I64Load32U memarg) = do
|
||||
checkMemoryInstr 4 memarg
|
||||
return $ I32 ==> I64
|
||||
getInstrType (I32Store memarg) = do
|
||||
getInstrType _ (I32Store memarg) = do
|
||||
checkMemoryInstr 4 memarg
|
||||
return $ [I32, I32] ==> empty
|
||||
getInstrType (I64Store memarg) = do
|
||||
getInstrType _ (I64Store memarg) = do
|
||||
checkMemoryInstr 8 memarg
|
||||
return $ [I32, I64] ==> empty
|
||||
getInstrType (F32Store memarg) = do
|
||||
getInstrType _ (F32Store memarg) = do
|
||||
checkMemoryInstr 4 memarg
|
||||
return $ [I32, F32] ==> empty
|
||||
getInstrType (F64Store memarg) = do
|
||||
getInstrType _ (F64Store memarg) = do
|
||||
checkMemoryInstr 8 memarg
|
||||
return $ [I32, F64] ==> empty
|
||||
getInstrType (I32Store8 memarg) = do
|
||||
getInstrType _ (I32Store8 memarg) = do
|
||||
checkMemoryInstr 1 memarg
|
||||
return $ [I32, I32] ==> empty
|
||||
getInstrType (I32Store16 memarg) = do
|
||||
getInstrType _ (I32Store16 memarg) = do
|
||||
checkMemoryInstr 2 memarg
|
||||
return $ [I32, I32] ==> empty
|
||||
getInstrType (I64Store8 memarg) = do
|
||||
getInstrType _ (I64Store8 memarg) = do
|
||||
checkMemoryInstr 1 memarg
|
||||
return $ [I32, I64] ==> empty
|
||||
getInstrType (I64Store16 memarg) = do
|
||||
getInstrType _ (I64Store16 memarg) = do
|
||||
checkMemoryInstr 2 memarg
|
||||
return $ [I32, I64] ==> empty
|
||||
getInstrType (I64Store32 memarg) = do
|
||||
getInstrType _ (I64Store32 memarg) = do
|
||||
checkMemoryInstr 4 memarg
|
||||
return $ [I32, I64] ==> empty
|
||||
getInstrType CurrentMemory = do
|
||||
getInstrType _ MemorySize = do
|
||||
Ctx { mems } <- ask
|
||||
if length mems < 1 then throwError (MemoryIndexOutOfRange 0) else return $ empty ==> I32
|
||||
getInstrType GrowMemory = do
|
||||
Ctx { mems } <- ask
|
||||
if length mems < 1 then throwError (MemoryIndexOutOfRange 0) else return $ I32 ==> I32
|
||||
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
|
||||
when (length mems < 1) $ throwError (MemoryIndexOutOfRange 0)
|
||||
return $ empty ==> I32
|
||||
getInstrType _ MemoryGrow = do
|
||||
Ctx { mems } <- ask
|
||||
when (length mems < 1) $ throwError (MemoryIndexOutOfRange 0)
|
||||
return $ I32 ==> I32
|
||||
getInstrType _ MemoryFill = do
|
||||
Ctx { mems } <- ask
|
||||
when (length mems < 1) $ throwError (MemoryIndexOutOfRange 0)
|
||||
return $ [I32, I32, I32] ==> empty
|
||||
getInstrType _ MemoryCopy = do
|
||||
Ctx { mems } <- ask
|
||||
when (length mems < 1) $ throwError (MemoryIndexOutOfRange 0)
|
||||
return $ [I32, I32, I32] ==> empty
|
||||
getInstrType _ (MemoryInit dataIdx) = do
|
||||
Ctx { mems, datas } <- ask
|
||||
when (length mems < 1) $ throwError (MemoryIndexOutOfRange 0)
|
||||
when (length datas <= fromIntegral dataIdx) $ throwError (DataIndexOutOfRange dataIdx)
|
||||
return $ [I32, I32, I32] ==> empty
|
||||
getInstrType _ (DataDrop dataIdx) = do
|
||||
Ctx { datas } <- ask
|
||||
when (length datas <= fromIntegral dataIdx) $ throwError (DataIndexOutOfRange dataIdx)
|
||||
return $ empty ==> empty
|
||||
getInstrType _ (TableInit tableIdx elemIdx) = do
|
||||
Ctx { tables, elems } <- ask
|
||||
when (length tables <= fromIntegral tableIdx) $ throwError (TableIndexOutOfRange tableIdx)
|
||||
when (length elems <= fromIntegral elemIdx) $ throwError (ElemIndexOutOfRange elemIdx)
|
||||
let TableType _ tableType = tables !! fromIntegral tableIdx
|
||||
let elemType = elems !! fromIntegral elemIdx
|
||||
when (elemType /= tableType) $ throwError (RefTypeMismatch tableType elemType)
|
||||
return $ [I32, I32, I32] ==> empty
|
||||
getInstrType _ (TableCopy toIdx fromIdx) = do
|
||||
Ctx { tables } <- ask
|
||||
let (from, to) = (fromIntegral fromIdx, fromIntegral toIdx)
|
||||
when (length tables <= from) $ throwError (TableIndexOutOfRange fromIdx)
|
||||
when (length tables <= to) $ throwError (TableIndexOutOfRange toIdx)
|
||||
let TableType _ fromType = tables !! from
|
||||
let TableType _ toType = tables !! to
|
||||
when (fromType /= toType) $ throwError (RefTypeMismatch fromType toType)
|
||||
return $ [I32, I32, I32] ==> empty
|
||||
getInstrType _ (TableFill tableIdx) = do
|
||||
Ctx { tables } <- ask
|
||||
when (length tables <= fromIntegral tableIdx) $ throwError (TableIndexOutOfRange tableIdx)
|
||||
let TableType _ tableType = tables !! fromIntegral tableIdx
|
||||
return $ [I32, elemTypeToRefType tableType, I32] ==> empty
|
||||
getInstrType _ (TableSize tableIdx) = do
|
||||
Ctx { tables } <- ask
|
||||
when (length tables <= fromIntegral tableIdx) $ throwError (TableIndexOutOfRange tableIdx)
|
||||
return $ empty ==> I32
|
||||
getInstrType _ (TableGrow tableIdx) = do
|
||||
Ctx { tables } <- ask
|
||||
when (length tables <= fromIntegral tableIdx) $ throwError (TableIndexOutOfRange tableIdx)
|
||||
let TableType _ tableType = tables !! fromIntegral tableIdx
|
||||
return $ [elemTypeToRefType tableType, I32] ==> I32
|
||||
getInstrType _ (TableGet tableIdx) = do
|
||||
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]
|
||||
@@ -416,25 +525,46 @@ getExpressionTypeWithInput inp = fmap (inp `Arrow`) . foldM go inp
|
||||
where
|
||||
go :: [VType] -> Instruction Natural -> Checker [VType]
|
||||
go stack instr = do
|
||||
(f `Arrow` t) <- getInstrType instr
|
||||
(f `Arrow` t) <- getInstrType stack instr
|
||||
matchStack stack (reverse f) t
|
||||
|
||||
matchStack :: [VType] -> [VType] -> [VType] -> Checker [VType]
|
||||
matchStack stack@(Any:_) _arg res = return $ res ++ stack
|
||||
matchStack (Val v:stack) (Val v':args) res =
|
||||
if v == v'
|
||||
then matchStack stack args res
|
||||
else throwError $ TypeMismatch ((reverse $ Val v':args) `Arrow` res) ([] `Arrow` (Val v:stack))
|
||||
matchStack _ (Any:_) res = return $ res
|
||||
matchStack (Val v:stack) (Var:args) res =
|
||||
let subst = replace Var (Val v) in
|
||||
matchStack stack (subst args) (subst res)
|
||||
matchStack (Var:stack) (Val v:args) res =
|
||||
let subst = replace Var (Val v) in
|
||||
matchStack stack (subst args) (subst res)
|
||||
matchStack stack [] res = return $ res ++ stack
|
||||
matchStack [] args res = throwError $ TypeMismatch ((reverse args) `Arrow` res) ([] `Arrow` [])
|
||||
matchStack _ _ _ = error "inconsistent checker state"
|
||||
|
||||
isRef :: ValueType -> Bool
|
||||
isRef (Func) = True
|
||||
isRef (Extern) = True
|
||||
isRef _ = False
|
||||
|
||||
matchStack :: [VType] -> [VType] -> [VType] -> Checker [VType]
|
||||
matchStack stack@(Any:_) _arg res = return $ res ++ stack
|
||||
matchStack (Val v:stack) (Val v':args) res =
|
||||
if v == v'
|
||||
then matchStack stack args res
|
||||
else throwError $ TypeMismatch ((reverse $ Val v':args) `Arrow` res) ([] `Arrow` (Val v:stack))
|
||||
matchStack _ (Any:_) res = return $ res
|
||||
matchStack (Val v:stack) (Var:args) res =
|
||||
let subst = replace Var (Val v) in
|
||||
matchStack stack (subst args) (subst res)
|
||||
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 = getExpressionTypeWithInput []
|
||||
@@ -445,6 +575,8 @@ isConstExpression ((I32Const _):rest) = isConstExpression rest
|
||||
isConstExpression ((I64Const _):rest) = isConstExpression rest
|
||||
isConstExpression ((F32Const _):rest) = isConstExpression rest
|
||||
isConstExpression ((F64Const _):rest) = isConstExpression rest
|
||||
isConstExpression ((RefNull _):rest) = isConstExpression rest
|
||||
isConstExpression ((RefFunc _):rest) = isConstExpression rest
|
||||
isConstExpression ((GetGlobal idx):rest) = do
|
||||
Ctx {globals, importedGlobals} <- ask
|
||||
if importedGlobals <= idx
|
||||
@@ -464,7 +596,8 @@ getFuncTypes Module {types, functions, imports} =
|
||||
getFuncType _ = Nothing
|
||||
|
||||
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 memsImports = catMaybes $ map getMemType 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,
|
||||
funcs = getFuncTypes m,
|
||||
tables = tableImports ++ map (\(Table t) -> t) tables,
|
||||
elems = map elemType elems,
|
||||
datas = map dataMode datas,
|
||||
mems = memsImports ++ map (\(Memory l) -> l) mems,
|
||||
globals = globalImports ++ map (\(Global g _) -> g) globals,
|
||||
locals,
|
||||
labels,
|
||||
returns,
|
||||
importedGlobals = fromIntegral $ length globalImports
|
||||
importedGlobals = fromIntegral $ length globalImports,
|
||||
refs = Set.unions $ map getElemRefs elems
|
||||
++ map getGlobalRefs globals
|
||||
++ map getExportRefs exports
|
||||
}
|
||||
where
|
||||
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 _ = 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 {funcType, localTypes = locals, body} mod@Module {types} =
|
||||
if fromIntegral funcType < length types
|
||||
@@ -509,10 +660,7 @@ tablesShouldBeValid :: Validator
|
||||
tablesShouldBeValid Module { imports, tables } =
|
||||
let tableImports = filter isTableImport imports in
|
||||
let res = foldMap (\Import { desc = ImportTable t } -> isValidTableType t) tableImports in
|
||||
let res' = foldl' (\r (Table t) -> r <> isValidTableType t) res tables in
|
||||
if length tableImports + length tables <= 1
|
||||
then res'
|
||||
else Left MoreThanOneTable
|
||||
foldl' (\r (Table t) -> r <> isValidTableType t) res tables
|
||||
where
|
||||
isValidTableType :: TableType -> ValidationResult
|
||||
isValidTableType (TableType (Limit min max) _) =
|
||||
@@ -557,24 +705,29 @@ elemsShouldBeValid m@Module { elems, functions, tables, imports } =
|
||||
foldMap (isElemValid ctx) elems
|
||||
where
|
||||
isElemValid :: Ctx -> ElemSegment -> ValidationResult
|
||||
isElemValid ctx (ElemSegment tableIdx offset funs) =
|
||||
let check = runChecker ctx $ do
|
||||
isElemValid ctx (ElemSegment elemType mode elements) = 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
|
||||
t <- getExpressionType offset
|
||||
if isArrowMatch (empty ==> I32) t
|
||||
then return ()
|
||||
else throwError $ TypeMismatch t (empty ==> I32)
|
||||
in
|
||||
let tableImports = filter isTableImport imports in
|
||||
let isTableIndexValid =
|
||||
if tableIdx < (fromIntegral $ length tableImports + length tables)
|
||||
then return ()
|
||||
else Left (TableIndexOutOfRange tableIdx)
|
||||
in
|
||||
let funImports = filter isFuncImport imports in
|
||||
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
|
||||
unless (isArrowMatch (empty ==> I32) t) $ do
|
||||
throwError $ TypeMismatch t (empty ==> I32)
|
||||
let tableImports = filter isTableImport imports
|
||||
when (tableIdx >= fromIntegral (length tableImports + length tables)) $ do
|
||||
throwError $ TableIndexOutOfRange tableIdx
|
||||
_ -> return ()
|
||||
|
||||
isValidRef :: ElemType -> Arrow -> Bool
|
||||
isValidRef FuncRef arr | arr == (empty ==> Func) = True
|
||||
isValidRef ExternRef arr | arr == (empty ==> Extern) = True
|
||||
isValidRef _ _ = False
|
||||
|
||||
datasShouldBeValid :: Validator
|
||||
datasShouldBeValid m@Module { datas, mems, imports } =
|
||||
@@ -582,7 +735,7 @@ datasShouldBeValid m@Module { datas, mems, imports } =
|
||||
foldMap (isDataValid ctx) datas
|
||||
where
|
||||
isDataValid :: Ctx -> DataSegment -> ValidationResult
|
||||
isDataValid ctx (DataSegment memIdx offset _) =
|
||||
isDataValid ctx (DataSegment (ActiveData memIdx offset) _) =
|
||||
let check = runChecker ctx $ do
|
||||
isConstExpression offset
|
||||
t <- getExpressionType offset
|
||||
@@ -594,6 +747,7 @@ datasShouldBeValid m@Module { datas, mems, imports } =
|
||||
if memIdx < (fromIntegral $ length memImports + length mems)
|
||||
then check
|
||||
else Left (MemoryIndexOutOfRange memIdx)
|
||||
isDataValid ctx (DataSegment PassiveData _) = return ()
|
||||
|
||||
startShouldBeValid :: Validator
|
||||
startShouldBeValid Module { start = Nothing } = return ()
|
||||
@@ -602,7 +756,7 @@ startShouldBeValid m@Module { start = Just (StartFunction idx) } =
|
||||
let i = fromIntegral idx in
|
||||
if length types > i
|
||||
then if FuncType [] [] == types !! i then return () else Left InvalidStartFunctionType
|
||||
else Left FunctionIndexOutOfRange
|
||||
else Left $ FunctionIndexOutOfRange $ fromIntegral i
|
||||
|
||||
exportsShouldBeValid :: Validator
|
||||
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 _ (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)) =
|
||||
if fromIntegral tableIdx < length tableImports + length tables then return () else Left (TableIndexOutOfRange tableIdx)
|
||||
isExportValid (Export _ (ExportMemory memIdx)) =
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
resolver: lts-16.5
|
||||
resolver: lts-20.23
|
||||
packages:
|
||||
- '.'
|
||||
extra-deps: []
|
||||
flags: {}
|
||||
extra-package-dbs: []
|
||||
extra-package-dbs: []
|
||||
+4
-4
@@ -6,7 +6,7 @@
|
||||
packages: []
|
||||
snapshots:
|
||||
- completed:
|
||||
size: 531707
|
||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/16/5.yaml
|
||||
sha256: 9751e25e0af5713a53ddcfcc79564b082c71b1b357fadef0d85672a5b5ba3703
|
||||
original: lts-16.5
|
||||
sha256: 4c972e067bae16b95961dbfdd12e07f1ee6c8fffabbfa05c3d65100b03f548b7
|
||||
size: 650253
|
||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/23.yaml
|
||||
original: lts-20.23
|
||||
|
||||
+4
-2
@@ -16,8 +16,10 @@ import qualified Data.List as List
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
files <- filter (List.isSuffixOf ".wast") <$> Directory.listDirectory "tests/spec"
|
||||
-- let files = ["const.wast"]
|
||||
files <-
|
||||
filter (not . List.isPrefixOf "simd") . filter (List.isSuffixOf ".wast")
|
||||
<$> Directory.listDirectory "tests/spec"
|
||||
-- let files = ["bulk.wast"]
|
||||
scriptTestCases <- (`mapM` files) $ \file -> do
|
||||
test <- LBS.readFile ("tests/spec/" ++ file)
|
||||
return $ testCase file $ do
|
||||
|
||||
+1
-1
Submodule tests/spec updated: 9994915e0c...6241ce9e15
+14
-14
@@ -1,6 +1,6 @@
|
||||
cabal-version: 2.2
|
||||
name: wasm
|
||||
version: 1.0.1.0
|
||||
version: 1.1.2
|
||||
synopsis: WebAssembly Language Toolkit and Interpreter
|
||||
description:
|
||||
Library for parsing and interpreting WebAssembly, including:
|
||||
@@ -16,9 +16,9 @@ license: MIT
|
||||
license-file: LICENSE
|
||||
build-type: Simple
|
||||
category: Language
|
||||
homepage: https:github.com/SPY/haskell-wasm
|
||||
bug-reports: https:github.com/SPY/haskell-wasm/issues
|
||||
tested-with: GHC==8.6.5, GHC==8.8.4, GHC==8.10.4
|
||||
homepage: https://github.com/SPY/haskell-wasm
|
||||
bug-reports: https://github.com/SPY/haskell-wasm/issues
|
||||
tested-with: GHC==8.6.5, GHC==8.8.4, GHC==8.10.4, GHC==9.2.7
|
||||
extra-source-files:
|
||||
README.md
|
||||
src/Language/Wasm/Parser.y
|
||||
@@ -27,20 +27,20 @@ extra-source-files:
|
||||
source-repository head
|
||||
type: git
|
||||
location: https://github.com/SPY/haskell-wasm
|
||||
|
||||
|
||||
library
|
||||
exposed-modules:
|
||||
Language.Wasm.Script
|
||||
Language.Wasm.Lexer
|
||||
Language.Wasm.Structure
|
||||
Language.Wasm
|
||||
other-modules:
|
||||
Language.Wasm.Binary
|
||||
Language.Wasm.Builder
|
||||
Language.Wasm.FloatUtils
|
||||
Language.Wasm.Interpreter
|
||||
Language.Wasm.Parser
|
||||
Language.Wasm.Validate
|
||||
Language.Wasm.Binary
|
||||
Language.Wasm.Builder
|
||||
Language.Wasm
|
||||
other-modules:
|
||||
Language.Wasm.FloatUtils
|
||||
Paths_wasm
|
||||
autogen-modules:
|
||||
Paths_wasm
|
||||
@@ -62,12 +62,12 @@ library
|
||||
, containers >=0.5 && < 0.7
|
||||
, deepseq >=1.4 && < 1.5
|
||||
, ieee754 >=0.8 && < 0.9
|
||||
, mtl >=2.2.1 && < 2.3
|
||||
, mtl >=2.2.1 && < 2.4
|
||||
, primitive >=0.7 && < 0.8
|
||||
, text >=1.1 && < 1.3
|
||||
, transformers >=0.4 && < 0.6
|
||||
, text >=1.1 && < 3
|
||||
, transformers >=0.4 && < 0.7
|
||||
, utf8-string >=1.0 && < 1.1
|
||||
, vector >=0.12 && < 0.13
|
||||
, vector >=0.12.2 && < 0.14
|
||||
default-language: Haskell2010
|
||||
|
||||
test-suite test
|
||||
|
||||
Reference in New Issue
Block a user