Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5ca4076
WIP: decode some `Memory*`
ggreif Dec 17, 2021
59e7ffc
administrative impl
ggreif Dec 17, 2021
474460c
encoding and DSL
ggreif Dec 17, 2021
306b0c6
first shot at `MemoryFill` semantics
ggreif Dec 17, 2021
5c7c12f
implement semantics for MemoryCopy
ggreif Dec 17, 2021
ca0574c
touchups
ggreif Dec 17, 2021
993f4c3
implement memory presence test for `MemoryFill`
ggreif Dec 17, 2021
b814f13
check addresses for 0-size `MemoryCopy`
ggreif Dec 17, 2021
53203cb
implement directionality for `MemoryCopy`
ggreif Dec 17, 2021
66c7510
add tests for `memory.fill` and `memory.copy`
ggreif Dec 18, 2021
8af6347
pull in more tests (also bulk memory ones)
ggreif Dec 18, 2021
3d25da0
typo
ggreif Dec 18, 2021
c1fbd74
moderenise
ggreif Dec 18, 2021
48618fb
excude `bulk.wast` for now
ggreif Dec 18, 2021
6df7ecb
accept bulk-memory instrs
ggreif Dec 18, 2021
8d9b484
ooops
ggreif Dec 18, 2021
eae8e8a
add to exclude list
ggreif Dec 18, 2021
6ef420d
fix list
ggreif Dec 18, 2021
979e0df
exclude more
ggreif Dec 18, 2021
e67949d
run MVP suite too
ggreif Dec 18, 2021
144370e
create two apec groups
ggreif Dec 18, 2021
e19b912
this seems to fail originally
ggreif Dec 18, 2021
106003c
allow zero-sized write past memory end
ggreif Dec 18, 2021
66d09e7
export `pageSize`
ggreif Dec 18, 2021
76757f7
trap when wrapping around
ggreif Dec 18, 2021
4e19a9b
perform early range-end checks
ggreif Dec 19, 2021
82254f1
simplify
ggreif Dec 19, 2021
b890cf0
loosen zero len with dest offset directly beyond
ggreif Dec 19, 2021
846bbf2
validate src too
ggreif Dec 19, 2021
d99970c
simplify
ggreif Dec 19, 2021
1cf9f32
simplify zero-range `MemoryCopy` too
ggreif Dec 19, 2021
07673a9
remove test/wasm and test/wast
ggreif Dec 19, 2021
a9b38a4
elim warning
ggreif Dec 19, 2021
355c6fc
Update src/Wasm/Binary/Decode.hs
ggreif Dec 19, 2021
96e5a29
Update src/Wasm/Binary/Decode.hs
ggreif Dec 19, 2021
338cdec
Update default.nix
ggreif Dec 20, 2021
5373bf2
simplify
ggreif Dec 20, 2021
04057a0
Merge branch 'master' of https://github.com/dfinity-side-projects/winter
ggreif Dec 20, 2021
4817a69
repair the problem which happened with `elem.wast` (from MVP) when ad…
ggreif Dec 20, 2021
220b7ee
bump version
ggreif Dec 20, 2021
618f679
update URLs
ggreif Dec 21, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,18 @@

let

spec-tests-mvp = pkgs.fetchFromGitHub {
owner = "WebAssembly";
repo = "testsuite";
rev = "35c50bf6fbb002cfdc1227b0af731bdcaf877714";
sha256 = "0difcpya5i7fc4xdrysx49186x9vh5yhm88dqpmfppj7ddj39l9i";
};

spec-tests = pkgs.fetchFromGitHub {
owner = "WebAssembly";
repo = "testsuite";
rev = "35c50bf6fbb002cfdc1227b0af731bdcaf877714";
sha256 = "0difcpya5i7fc4xdrysx49186x9vh5yhm88dqpmfppj7ddj39l9i";
rev = "6aacfd8929504d8e02a5144a14d184196ede6790";
sha256 = "sha256-HrnTpIEVN3H9P4fuSBUkaMpNdMxa2pbfp1iPElJLlUM";
};

drv = pkgs.haskellPackages.developPackage {
Expand All @@ -47,4 +54,7 @@ drv = pkgs.haskellPackages.developPackage {
inherit returnShellEnv;
};

in drv.overrideAttrs(old: { WASM_SPEC_TESTS = "${spec-tests}"; })
in drv.overrideAttrs(old:
{ WASM_SPEC_TESTS_MVP = spec-tests-mvp;
WASM_SPEC_TESTS = spec-tests;
})
4 changes: 4 additions & 0 deletions src/Wasm/Binary/Decode.hs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ getMathPrefix = do
0x05 -> return $ Convert $ I64ConvertOp Int.TruncUSatF32
0x06 -> return $ Convert $ I64ConvertOp Int.TruncSSatF64
0x07 -> return $ Convert $ I64ConvertOp Int.TruncUSatF64
0x0A -> do (0, 0) <- (,) <$> getWord8 <*> getWord8
return MemoryCopy
0x0B -> do 0 <- getWord8
return MemoryFill
_ -> fail (printf "getMathPrefix: illegal op %d" byte)


Expand Down
4 changes: 4 additions & 0 deletions src/Wasm/Binary/Encode.hs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,10 @@ putInstr = flip (.) unFix $ \case
MemoryGrow -> do
putWord8 0x40
putWord8 0x00
MemoryFill -> do
mapM_ putWord8 [0xFC, 0x0B, 0x00]
MemoryCopy -> do
mapM_ putWord8 [0xFC, 0x0A, 0x00, 0x00]

-- Constants.
Const (value -> I32 val) -> do
Expand Down
42 changes: 42 additions & 0 deletions src/Wasm/Exec/Eval.hs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,48 @@ step(Code cs cfg vs (e:es)) = (`runReaderT` cfg) $ do
Right () -> oldSize
k (I32 result : vs') es

(MemoryFill, I32 0 : _ : I32 dst : vs') -> {-# SCC step_MemoryFill #-} do
inst <- getFrameInst
mem <- lift $ memory inst (0 @@ at)
-- Zero len with offset out-of-bounds at the end of memory is allowed
sz <- lift $ lift $ Memory.size mem
if Memory.pageSize * sz >= dst
then k vs' es
else k vs' (Trapping (memoryErrorString Memory.MemoryBoundsError) @@ at : es)

(MemoryFill, I32 cnt : v : I32 dst : vs') -> {-# SCC step_MemoryFill #-} do
inst <- getFrameInst
mem <- lift $ memory inst (0 @@ at)
let [addr, count] = fromIntegral . i64_extend_u_i32 . fromIntegral <$> [dst, cnt]
if addr + count > 2^32
then k vs' (Trapping (memoryErrorString Memory.MemoryBoundsError) @@ at : es)
else do
eres <- lift $ lift $ runExceptT $ mapM_ (\off -> Memory.storePacked Pack8 mem addr off v) [0 .. pred cnt]
case eres of
Right () -> k vs' es
Left exn -> k vs' (Trapping (memoryErrorString exn) @@ at : es)
Comment on lines +482 to +501
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks quite different from the reference code in
https://github.com/WebAssembly/spec/blob/ffb5e3b40db7a45237d4e2fc972c1552ac4abef5/interpreter/exec/eval.ml#L413-L428
(and likewise below)

The design of winter is to be a pretty much direct port, so that chances are good to get the semantics right, and also to be able to follow and apply subsequent changes easily.

We have occasionally deviated, for performance reasons (e.g. https://www.joachim-breitner.de/blog/765-Faster_Winter_7__The_Zipper). Is this the reason here? Maybe worth adding a comment here… hmm, but I see you aren’t still implementing this to byte-wise operations here.

So why not just follow the logic of the reference code directly?

Copy link
Collaborator Author

@ggreif ggreif Dec 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I didn't even look at the other reference :-)

Will do.

Copy link
Collaborator Author

@ggreif ggreif Feb 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at that reference implementation makes me think it is written with proof assistants in mind. It lowers the complex instructions to simple ones and stucturally "smaller" identical ones. There is a case of MemoryCopy that isn't even tail recursive. So to make this remotely performant we'll have to rewrite these anyway. Otherwise even the Rust-style "do prefix and suffix bytewise and the inner part u64-wise" will be executing faster.


(MemoryCopy, I32 0 : I32 src : I32 dst : vs') -> {-# SCC step_MemoryCopy #-} do
inst <- getFrameInst
mem <- lift $ memory inst (0 @@ at)
-- Zero len with src/dest offset out-of-bounds at the end of memory is allowed
sz <- lift $ lift $ Memory.size mem
if Memory.pageSize * sz >= dst && Memory.pageSize * sz >= src
then k vs' es
else k vs' (Trapping (memoryErrorString Memory.MemoryBoundsError) @@ at : es)

(MemoryCopy, I32 cnt : I32 src : I32 dst : vs') -> {-# SCC step_MemoryCopy #-} do
inst <- getFrameInst
mem <- lift $ memory inst (0 @@ at)
let [addr_dst, addr_src, count] = fromIntegral . i64_extend_u_i32 . fromIntegral <$> [dst, src, cnt]
let range = if dst < src then [0 .. pred cnt] else tail $ enumFromThenTo cnt (pred cnt) 0
eres <- lift $ lift $ runExceptT $
mapM_ (\addr -> Memory.loadPacked Pack8 ZX mem (addr + pred count) 0 I32Type) [addr_dst, addr_src]
>> mapM_ (\off -> Memory.loadPacked Pack8 ZX mem addr_src off I32Type >>= Memory.storePacked Pack8 mem addr_dst off) range
case eres of
Right () -> k vs' es
Left exn -> k vs' (Trapping (memoryErrorString exn) @@ at : es)

(Const v, vs) -> {-# SCC step_Const #-}
k (value v : vs) es

Expand Down
1 change: 1 addition & 0 deletions src/Wasm/Runtime/Memory.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module Wasm.Runtime.Memory
, loadValue
, exportMemory
, importMemory
, pageSize
) where

import Control.Exception
Expand Down
22 changes: 16 additions & 6 deletions src/Wasm/Syntax/AST.hs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ data InstrF (phrase :: * -> *) fix
| Store StoreOp
| MemorySize
| MemoryGrow
| MemoryFill
| MemoryCopy
| Const (Literal phrase)
| Test TestOp
| Compare CompareOp
Expand Down Expand Up @@ -135,6 +137,8 @@ instance (NFData1 phrase) => NFData1 (InstrF phrase) where
Store op -> rnf op
MemorySize -> ()
MemoryGrow -> ()
MemoryFill -> ()
MemoryCopy -> ()
Const lit -> rnfLift lit
Test op -> rnf op
Compare op -> rnf op
Expand Down Expand Up @@ -219,6 +223,10 @@ instance (Show1 phrase) => Show1 (InstrF phrase) where
showString "MemorySize"
MemoryGrow ->
showString "MemoryGrow"
MemoryFill ->
showString "MemoryFill"
MemoryCopy ->
showString "MemoryCopy"
Const lit ->
showString "Const " .
showLiftPrec 11 lit
Expand Down Expand Up @@ -320,12 +328,14 @@ showWasm d ((value -> Fix instr):instrs) = go instr . showString "\n" . showWasm
Store (MemoryOp I64Type x y (Just Pack8)) -> showString "i64.store8 " . showsPrec d x . showString " " . showsPrec d y
Store (MemoryOp I64Type x y (Just Pack16)) -> showString "i64.store16 " . showsPrec d x . showString " " . showsPrec d y
Store (MemoryOp I64Type x y (Just Pack32)) -> showString "i64.store32 " . showsPrec d x . showString " " . showsPrec d y
MemorySize -> showString "memory_size"
MemoryGrow -> showString "memory_grow"
Const (value -> I32 x) -> showString "i32.const " . showsPrec d x
Const (value -> I64 x) -> showString "i64.const " . showsPrec d x
Const (value -> F32 x) -> showString "f32.const " . showsPrec d x
Const (value -> F64 x) -> showString "f64.const " . showsPrec d x
MemorySize -> showString "memory.size"
MemoryGrow -> showString "memory.grow"
MemoryFill -> showString "memory.fill"
MemoryCopy -> showString "memory.copy"
Const (value -> I32 x) -> showString "i32.const " . showsPrec d x
Const (value -> I64 x) -> showString "i64.const " . showsPrec d x
Const (value -> F32 x) -> showString "f32.const " . showsPrec d x
Const (value -> F64 x) -> showString "f64.const " . showsPrec d x
Unary (I32UnaryOp Clz) -> showString "i32.clz"
Unary (I32UnaryOp Ctz) -> showString "i32.ctz"
Unary (I32UnaryOp Popcnt) -> showString "i32.popcnt"
Expand Down
4 changes: 4 additions & 0 deletions src/Wasm/Syntax/DSL.hs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class Monad (WasmM t) => Wasm t where
-- Memory operators.
memory_size :: WasmM t ()
memory_grow :: WasmM t ()
memory_fill :: WasmM t ()
memory_copy :: WasmM t ()

-- Constants.
i32_const :: Int32 -> WasmM t ()
Expand Down Expand Up @@ -306,6 +308,8 @@ instance Applicative f => Wasm [Instr f] where
-- Memory operators.
memory_size = WasmM $ tell [Fix $ MemorySize]
memory_grow = WasmM $ tell [Fix $ MemoryGrow]
memory_fill = WasmM $ tell [Fix $ MemoryFill]
memory_copy = WasmM $ tell [Fix $ MemoryCopy]

-- Constants.
i32_const x = WasmM $ tell [Fix $ Const (pure (I32 x))]
Expand Down
60 changes: 53 additions & 7 deletions test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,69 @@ import Spec as Spec (tests)

main :: IO ()
main = do
mwasmPathMVP <- lookupEnv "WASM_SPEC_TESTS_MVP"
testDirMVP <- case mwasmPathMVP of
Nothing -> error "Please define WASM_SPEC_TESTS_MVP to point to .../WebAssembly/spec/test/core"
Just path -> pure path
mwasmPath <- lookupEnv "WASM_SPEC_TESTS"
testDir <- case mwasmPath of
Nothing -> error "Please define WASM_SPEC_TESTS to point to .../WebAssebly/spec/test/core"
Just path -> return path
Nothing -> error "Please define WASM_SPEC_TESTS to point to .../WebAssembly/spec/test/core"
Just path -> pure path
putStrLn $ "Using wasm MVP spec test directory: " ++ testDirMVP
putStrLn $ "Using wasm spec test directory: " ++ testDir
Comment on lines +22 to 23
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need two versions of the test suite? Can we not simply run the latest, possibly excluding new tests that we don't support?

Copy link
Collaborator Author

@ggreif ggreif Dec 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I had to disable too many tests that won't run in the new suite, and became concerned about regressing on those.


filesMVP <- listDirectory testDirMVP
let wastFilesMVP = flip concatMap filesMVP $ \file ->
[ testDirMVP ++ "/" ++ file
| ".wast" `isSuffixOf` file
&& file `notElem`
[ "inline-module.wast"
-- We aren't going to bother fully supporting
-- Unicode function names in the reference interpreter yet.
, "names.wast"
]
]

files <- listDirectory testDir
let wastFiles = flip concatMap files $ \file ->
[ testDir ++ "/" ++ file
| ".wast" `isSuffixOf` file
&& "inline-module.wast" /= file
-- We aren't going to bother fully supporting
-- Unicode function names in the reference interpreter yet.
&& "names.wast" /= file
&& file `notElem`
[ "inline-module.wast"
-- We aren't going to bother fully supporting
-- Unicode function names in the reference interpreter yet.
, "names.wast"
-- These contain features that `winter` won't accept yet
, "bulk.wast"
, "memory_init.wast"
, "ref_null.wast"
, "table_get.wast"
, "table_grow.wast"
, "table.wast"
, "ref_is_null.wast"
, "ref_func.wast"
, "unreached-valid.wast"
, "exports.wast"
, "imports.wast"
, "br_table.wast"
, "table_size.wast"
, "table_fill.wast"
, "table_init.wast"
, "table_set.wast"
, "table_copy.wast"
, "global.wast"
, "linking.wast"
, "binary-leb128.wast"
, "elem.wast"
, "call_indirect.wast"
, "binary.wast"
, "select.wast"
]
]

defaultMain $ testGroup "main"
[ Property.tests
, Unit.tests
, Spec.tests wastFiles
, Spec.tests [] "spec MVP" wastFilesMVP
, Spec.tests ["--enable-bulk-memory"] "spec" wastFiles
]
6 changes: 3 additions & 3 deletions test/Spec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ import Wasm.Util.Float (floatToBits, doubleToBits)
import SpecTest (spectest)
import Wat2Wasm (wat2Wasm)

tests :: [FilePath] -> TestTree
tests files = testGroup "spec" $ map prep files
tests :: [String] -> String -> [FilePath] -> TestTree
tests languageFlags name files = testGroup name $ map prep files
where
prep file = testCaseSteps file $ \step -> do
input <- Prelude.readFile file
inst <- spectest
parseWastFile @(Winter Phrase) @IO file input
(M.singleton "spectest" 1)
(IM.singleton 1 inst)
wat2Wasm step valListEq assertFailure
(wat2Wasm languageFlags) step valListEq assertFailure

valListEq :: [Value] -> [Value] -> Bool
valListEq vs1 vs2 =
Expand Down
10 changes: 5 additions & 5 deletions test/Wat2Wasm.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import System.Exit
import System.IO.Temp
import System.Process

wat2Wasm :: String -> IO ByteString
wat2Wasm contents = do
wat2Wasm :: [String] -> String -> IO ByteString
wat2Wasm languageFlags contents = do
wat <- emptyTempFile "." "test.wat"
wasm <- emptyTempFile "." "test.wasm"
writeFile wat contents
(exit, _out, err) <-
readProcessWithExitCode "wat2wasm" [wat, "-o", wasm] ""
readProcessWithExitCode "wat2wasm" (wat : languageFlags <> ["-o", wasm]) ""
case exit of
ExitSuccess -> do
ExitSuccess -> do
res <- BL.readFile wasm
removeFile wat
removeFile wasm
return res
pure res
ExitFailure _ ->
fail err
Binary file removed test/wasm/binary-module.wasm
Binary file not shown.
Binary file removed test/wasm/data-offset.wasm
Binary file not shown.
Binary file removed test/wasm/elem-offset.wasm
Binary file not shown.
Binary file removed test/wasm/export-func-multi.wasm
Binary file not shown.
Binary file removed test/wasm/export-func-named.wasm
Binary file not shown.
Binary file removed test/wasm/export-func.wasm
Binary file not shown.
Binary file removed test/wasm/export-global.wasm
Binary file not shown.
Binary file removed test/wasm/export-memory-multi.wasm
Binary file not shown.
Binary file removed test/wasm/export-memory.wasm
Binary file not shown.
Binary file removed test/wasm/export-table.wasm
Binary file not shown.
Binary file removed test/wasm/global.wasm
Binary file not shown.
Binary file removed test/wasm/import-func-no-param.wasm
Binary file not shown.
Binary file removed test/wasm/import-func-type.wasm
Binary file not shown.
Binary file removed test/wasm/import-func.wasm
Binary file not shown.
Binary file removed test/wasm/import-global-getglobal.wasm
Binary file not shown.
Binary file removed test/wasm/import-global.wasm
Binary file not shown.
Binary file removed test/wasm/import-memory.wasm
Binary file not shown.
Binary file removed test/wasm/import-table.wasm
Binary file not shown.
Binary file removed test/wasm/memory-init-max-size.wasm
Binary file not shown.
Binary file removed test/wasm/memory-init-size.wasm
Binary file not shown.
Binary file removed test/wasm/memory-segment-1.wasm
Binary file not shown.
Binary file removed test/wasm/memory-segment-long.wasm
Binary file not shown.
Binary file removed test/wasm/memory-segment-many.wasm
Binary file not shown.
Binary file removed test/wasm/memory-segment-multi-string.wasm
Binary file not shown.
Binary file removed test/wasm/module-empty.wasm
Binary file not shown.
Binary file removed test/wasm/module-name.wasm
Binary file not shown.
Binary file removed test/wasm/start-named.wasm
Binary file not shown.
Binary file removed test/wasm/start.wasm
Binary file not shown.
Binary file removed test/wasm/table-named.wasm
Binary file not shown.
Binary file removed test/wasm/table.wasm
Binary file not shown.
Binary file removed test/wasm/type-empty-param.wasm
Binary file not shown.
Binary file removed test/wasm/type-empty.wasm
Binary file not shown.
Binary file removed test/wasm/type-multi-param.wasm
Binary file not shown.
Binary file removed test/wasm/type-no-param.wasm
Binary file not shown.
Binary file removed test/wasm/type.wasm
Binary file not shown.
17 changes: 0 additions & 17 deletions test/wast/binary-module.wast

This file was deleted.

5 changes: 0 additions & 5 deletions test/wast/data-offset.wast

This file was deleted.

6 changes: 0 additions & 6 deletions test/wast/elem-offset.wast

This file was deleted.

4 changes: 0 additions & 4 deletions test/wast/export-func-multi.wast

This file was deleted.

3 changes: 0 additions & 3 deletions test/wast/export-func-named.wast

This file was deleted.

3 changes: 0 additions & 3 deletions test/wast/export-func.wast

This file was deleted.

4 changes: 0 additions & 4 deletions test/wast/export-global.wast

This file was deleted.

4 changes: 0 additions & 4 deletions test/wast/export-memory-multi.wast

This file was deleted.

3 changes: 0 additions & 3 deletions test/wast/export-memory.wast

This file was deleted.

3 changes: 0 additions & 3 deletions test/wast/export-table.wast

This file was deleted.

15 changes: 0 additions & 15 deletions test/wast/global.wast

This file was deleted.

1 change: 0 additions & 1 deletion test/wast/import-func-no-param.wast

This file was deleted.

3 changes: 0 additions & 3 deletions test/wast/import-func-type.wast

This file was deleted.

10 changes: 0 additions & 10 deletions test/wast/import-func.wast

This file was deleted.

3 changes: 0 additions & 3 deletions test/wast/import-global-getglobal.wast

This file was deleted.

Loading