From e7d61806a6f252c758f3a2fb06961f4252cbb405 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 13 Oct 2015 23:25:50 -0700 Subject: [PATCH 1/4] Rename `resize_memory` to `grow_memory`, following the design. The previous code interpreted `resize_memory` as an absolute size; this patch also changes it to be a delta as described in the design. Also, refactor resizing.wast to be independent of the host page size. --- ml-proto/README.md | 2 +- ml-proto/TestingTodo.md | 2 +- ml-proto/host/lexer.mll | 2 +- ml-proto/host/parser.mly | 4 +- ml-proto/spec/ast.ml | 2 +- ml-proto/spec/check.ml | 2 +- ml-proto/spec/eval.ml | 6 +-- ml-proto/spec/memory.ml | 11 ++++-- ml-proto/spec/memory.mli | 2 +- ml-proto/test/memory_trap.wast | 6 +++ ml-proto/test/resizing.wast | 67 ++++++++++++++++++++++------------ 11 files changed, 68 insertions(+), 38 deletions(-) diff --git a/ml-proto/README.md b/ml-proto/README.md index 3e83b397fb..984fd08475 100644 --- a/ml-proto/README.md +++ b/ml-proto/README.md @@ -154,7 +154,7 @@ expr: ( ./ ) ( page_size ) ( memory_size ) - ( resize_memory ) + ( grow_memory ) case: ( case * fallthrough? ) ;; = (case (block *) fallthrough?) diff --git a/ml-proto/TestingTodo.md b/ml-proto/TestingTodo.md index 063fbc64b3..a5f3126b55 100644 --- a/ml-proto/TestingTodo.md +++ b/ml-proto/TestingTodo.md @@ -55,7 +55,7 @@ Linear memory semantics: - test loading "uninitialized" things from aliased stack frames return what's there - test that loadwithoffset traps in overflow cases - test that newly allocated memory is zeroed - - test that resize_memory does a full 32-bit unsigned check for page_size divisibility + - test that grow_memory does a full 32-bit unsigned check for page_size divisibility - test that load/store addreses are full int32 (or int64), and not OCaml int - test that when allocating 4GiB, accessing index -1 fails diff --git a/ml-proto/host/lexer.mll b/ml-proto/host/lexer.mll index 7f3b325ce9..7dc6b40b4a 100644 --- a/ml-proto/host/lexer.mll +++ b/ml-proto/host/lexer.mll @@ -239,7 +239,7 @@ rule token = parse | "page_size" { PAGE_SIZE } | "memory_size" { MEMORY_SIZE } - | "resize_memory" { RESIZE_MEMORY } + | "grow_memory" { GROW_MEMORY } | "func" { FUNC } | "param" { PARAM } diff --git a/ml-proto/host/parser.mly b/ml-proto/host/parser.mly index 5723306dda..5e90197d74 100644 --- a/ml-proto/host/parser.mly +++ b/ml-proto/host/parser.mly @@ -99,7 +99,7 @@ let anon_label c = {c with labels = VarMap.map ((+) 1) c.labels} %token GET_LOCAL SET_LOCAL LOAD STORE %token CONST UNARY BINARY COMPARE CONVERT %token FUNC PARAM RESULT LOCAL MODULE MEMORY SEGMENT IMPORT EXPORT TABLE -%token PAGE_SIZE MEMORY_SIZE RESIZE_MEMORY +%token PAGE_SIZE MEMORY_SIZE GROW_MEMORY %token ASSERT_INVALID ASSERT_RETURN ASSERT_RETURN_NAN ASSERT_TRAP INVOKE %token EOF @@ -202,7 +202,7 @@ expr1 : | CONVERT expr { fun c -> convert ($1, $2 c) } | PAGE_SIZE { fun c -> host (PageSize, []) } | MEMORY_SIZE { fun c -> host (MemorySize, []) } - | RESIZE_MEMORY expr { fun c -> host (ResizeMemory, [$2 c]) } + | GROW_MEMORY expr { fun c -> host (GrowMemory, [$2 c]) } ; expr_opt : | /* empty */ { fun c -> None } diff --git a/ml-proto/spec/ast.ml b/ml-proto/spec/ast.ml index 425feff10e..8c7a4d3782 100644 --- a/ml-proto/spec/ast.ml +++ b/ml-proto/spec/ast.ml @@ -69,7 +69,7 @@ type wrapop = {memop : memop; sz : Memory.mem_size} type hostop = | PageSize (* inquire host-defined page size *) | MemorySize (* inquire current size of linear memory *) - | ResizeMemory (* resize linear memory *) + | GrowMemory (* grow linear memory *) (* Expressions *) diff --git a/ml-proto/spec/check.ml b/ml-proto/spec/check.ml index 00ebc651ad..f0f83f4bb3 100644 --- a/ml-proto/spec/check.ml +++ b/ml-proto/spec/check.ml @@ -92,7 +92,7 @@ let type_cvt at = function let type_hostop = function | PageSize -> {ins = []; out = Some Int32Type} | MemorySize -> {ins = []; out = Some Int32Type} - | ResizeMemory -> {ins = [Int32Type]; out = None} + | GrowMemory -> {ins = [Int32Type]; out = None} let type_func f = diff --git a/ml-proto/spec/eval.ml b/ml-proto/spec/eval.ml index 7d833a439a..8d3a91c4ac 100644 --- a/ml-proto/spec/eval.ml +++ b/ml-proto/spec/eval.ml @@ -275,11 +275,11 @@ and eval_hostop host mem hostop vs at = assert (Memory.size mem < Int64.of_int32 Int32.max_int); Some (Int32 (Int64.to_int32 (Memory.size mem))) - | ResizeMemory, [v] -> + | GrowMemory, [v] -> let sz = mem_size v at in if Int64.rem sz host.page_size <> 0L then - error at "runtime: resize_memory operand not multiple of page_size"; - Memory.resize mem sz; + error at "runtime: grow_memory operand not multiple of page_size"; + Memory.grow mem sz; None | _, _ -> diff --git a/ml-proto/spec/memory.ml b/ml-proto/spec/memory.ml index 9abd21b835..97169b2456 100644 --- a/ml-proto/spec/memory.ml +++ b/ml-proto/spec/memory.ml @@ -70,10 +70,13 @@ let init mem segs = let size mem = int64_of_host_size (Array1.dim !mem) -let resize mem n = - let after = create' n in - let min = host_index_of_int64 (min (size mem) n) 0 in - Array1.blit (Array1.sub !mem 0 min) (Array1.sub after 0 min); +let grow mem n = + let old_size = size mem in + let new_size = Int64.add old_size n in + if old_size > new_size then raise Out_of_memory else + let after = create' new_size in + let host_old_size = host_size_of_int64 old_size in + Array1.blit (Array1.sub !mem 0 host_old_size) (Array1.sub after 0 host_old_size); mem := after let rec loadn mem n a = diff --git a/ml-proto/spec/memory.mli b/ml-proto/spec/memory.mli index e830307fcc..707430dcb5 100644 --- a/ml-proto/spec/memory.mli +++ b/ml-proto/spec/memory.mli @@ -19,7 +19,7 @@ exception Address val create : size -> memory val init : memory -> segment list -> unit val size : memory -> size -val resize : memory -> size -> unit +val grow : memory -> size -> unit val load : memory -> address -> value_type -> value val store : memory -> address -> value -> unit val load_extend : diff --git a/ml-proto/test/memory_trap.wast b/ml-proto/test/memory_trap.wast index 64e6848178..c7551a5e88 100644 --- a/ml-proto/test/memory_trap.wast +++ b/ml-proto/test/memory_trap.wast @@ -6,6 +6,9 @@ (export "load" $load) (func $load (param $i i32) (result i32) (i32.load (i32.add (memory_size) (get_local $i)))) + + (export "grow_memory" $grow_memory) + (func $grow_memory (param $i i32) (grow_memory (get_local $i))) ) (assert_return (invoke "store" (i32.const -4) (i32.const 42)) (i32.const 42)) @@ -18,3 +21,6 @@ (assert_trap (invoke "load" (i32.const -1)) "runtime: out of bounds memory access") (assert_trap (invoke "store" (i32.const 0) (i32.const 13)) "runtime: out of bounds memory access") (assert_trap (invoke "load" (i32.const 0)) "runtime: out of bounds memory access") +(assert_trap (invoke "store" (i32.const 0x80000000) (i32.const 13)) "runtime: out of bounds memory access") +(assert_trap (invoke "load" (i32.const 0x80000000)) "runtime: out of bounds memory access") +(assert_trap (invoke "grow_memory" (i32.const 3)) "runtime: grow_memory operand not multiple of page_size") diff --git a/ml-proto/test/resizing.wast b/ml-proto/test/resizing.wast index eff4887f9f..14e33935e5 100644 --- a/ml-proto/test/resizing.wast +++ b/ml-proto/test/resizing.wast @@ -1,32 +1,53 @@ (module - (memory 4096) + (memory 0) - (export "load" $load) - (func $load (param $i i32) (result i32) (i32.load (get_local $i))) + (export "round_up_to_page" $round_up_to_page) + (func $round_up_to_page (param i32) (result i32) + (i32.and (i32.add (get_local 0) (i32.sub (page_size) (i32.const 1))) + (i32.sub (i32.const 0) (page_size))) + ) - (export "store" $store) - (func $store (param $i i32) (param $v i32) (result i32) (i32.store (get_local $i) (get_local $v))) + (export "load_at_zero" $load_at_zero) + (func $load_at_zero (result i32) (i32.load (i32.const 0))) - (export "resize" $resize) - (func $resize (param $sz i32) (resize_memory (get_local $sz))) + (export "store_at_zero" $store_at_zero) + (func $store_at_zero (result i32) (i32.store (i32.const 0) (i32.const 2))) + + (export "load_at_page_size" $load_at_page_size) + (func $load_at_page_size (result i32) (i32.load (page_size))) + + (export "store_at_page_size" $store_at_page_size) + (func $store_at_page_size (result i32) (i32.store (page_size) (i32.const 3))) + + (export "grow" $grow) + (func $grow (param $sz i32) + (grow_memory (call $round_up_to_page (get_local $sz))) + ) + + (export "size_at_least" $size_at_least) + (func $size_at_least (param i32) (result i32) (i32.ge_u (memory_size) (get_local 0))) (export "size" $size) (func $size (result i32) (memory_size)) ) -(assert_return (invoke "size") (i32.const 4096)) -(assert_return (invoke "store" (i32.const 0) (i32.const 42)) (i32.const 42)) -(assert_return (invoke "load" (i32.const 0)) (i32.const 42)) -(assert_trap (invoke "store" (i32.const 4096) (i32.const 42)) "runtime: out of bounds memory access") -(assert_trap (invoke "load" (i32.const 4096)) "runtime: out of bounds memory access") -(invoke "resize" (i32.const 8192)) -(assert_return (invoke "size") (i32.const 8192)) -(assert_return (invoke "load" (i32.const 0)) (i32.const 42)) -(assert_return (invoke "load" (i32.const 4096)) (i32.const 0)) -(assert_return (invoke "store" (i32.const 4096) (i32.const 43)) (i32.const 43)) -(assert_return (invoke "load" (i32.const 4096)) (i32.const 43)) -(invoke "resize" (i32.const 4096)) -(assert_return (invoke "size") (i32.const 4096)) -(assert_return (invoke "load" (i32.const 0)) (i32.const 42)) -(assert_trap (invoke "store" (i32.const 4096) (i32.const 42)) "runtime: out of bounds memory access") -(assert_trap (invoke "load" (i32.const 4096)) "runtime: out of bounds memory access") +(assert_return (invoke "size") (i32.const 0)) +(assert_return (invoke "size_at_least" (i32.const 0)) (i32.const 1)) +(assert_trap (invoke "store_at_zero") "runtime: out of bounds memory access") +(assert_trap (invoke "load_at_zero") "runtime: out of bounds memory access") +(assert_trap (invoke "store_at_page_size") "runtime: out of bounds memory access") +(assert_trap (invoke "load_at_page_size") "runtime: out of bounds memory access") +(invoke "grow" (i32.const 4)) +(assert_return (invoke "size_at_least" (i32.const 4)) (i32.const 1)) +(assert_return (invoke "load_at_zero") (i32.const 0)) +(assert_return (invoke "store_at_zero") (i32.const 2)) +(assert_return (invoke "load_at_zero") (i32.const 2)) +(assert_trap (invoke "store_at_page_size") "runtime: out of bounds memory access") +(assert_trap (invoke "load_at_page_size") "runtime: out of bounds memory access") +(invoke "grow" (i32.const 4)) +(assert_return (invoke "size_at_least" (i32.const 8)) (i32.const 1)) +(assert_return (invoke "load_at_zero") (i32.const 2)) +(assert_return (invoke "store_at_zero") (i32.const 2)) +(assert_return (invoke "load_at_page_size") (i32.const 0)) +(assert_return (invoke "store_at_page_size") (i32.const 3)) +(assert_return (invoke "load_at_page_size") (i32.const 3)) From bf2588936519f291dbf0d5c6c6f595205d8087da Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 14 Oct 2015 06:52:45 -0700 Subject: [PATCH 2/4] Implement proper overflow checking in `grow_memory`. --- ml-proto/spec/eval.ml | 23 ++++++++++++++++++----- ml-proto/spec/memory.ml | 3 ++- ml-proto/spec/memory.mli | 1 + ml-proto/test/memory_trap.wast | 6 ++++++ 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/ml-proto/spec/eval.ml b/ml-proto/spec/eval.ml index 8d3a91c4ac..2d872cfa20 100644 --- a/ml-proto/spec/eval.ml +++ b/ml-proto/spec/eval.ml @@ -67,6 +67,7 @@ end let memory_error at = function | Memory.Bounds -> error at "runtime: out of bounds memory access" + | Memory.AddressOverflow -> error at "runtime: memory address overflow" | Memory.Address -> error at "runtime: illegal address value" | exn -> raise exn @@ -104,6 +105,13 @@ let mem_size v at = let i64 = Int64.of_int32 i32 in Int64.shift_right_logical (Int64.shift_left i64 32) 32 +(* + * Test whether x has a value which is an overflow in the memory type. Since + * we currently only support i32, just test that. + *) +let mem_overflow x = + I64.gt_u x (Int64.of_int32 Int32.max_int) + let callstack_exhaustion at = error at ("runtime: callstack exhausted") @@ -269,18 +277,23 @@ and coerce et vo = and eval_hostop host mem hostop vs at = match hostop, vs with | PageSize, [] -> + assert (I64.lt_u host.page_size (Int64.of_int32 Int32.max_int)); Some (Int32 (Int64.to_int32 host.page_size)) | MemorySize, [] -> - assert (Memory.size mem < Int64.of_int32 Int32.max_int); + assert (I64.lt_u (Memory.size mem) (Int64.of_int32 Int32.max_int)); Some (Int32 (Int64.to_int32 (Memory.size mem))) | GrowMemory, [v] -> - let sz = mem_size v at in - if Int64.rem sz host.page_size <> 0L then + let delta = mem_size v at in + if I64.rem_u delta host.page_size <> 0L then error at "runtime: grow_memory operand not multiple of page_size"; - Memory.grow mem sz; - None + if I64.lt_u (Int64.add (Memory.size mem) delta) (Memory.size mem) then + error at "runtime: grow_memory overflow"; + if mem_overflow (Int64.add (Memory.size mem) delta) then + error at "runtime: grow_memory overflow"; + Memory.grow mem delta; + None; | _, _ -> error at "runtime: invalid invocation of host operator" diff --git a/ml-proto/spec/memory.ml b/ml-proto/spec/memory.ml index 97169b2456..57aca26179 100644 --- a/ml-proto/spec/memory.ml +++ b/ml-proto/spec/memory.ml @@ -21,6 +21,7 @@ type t = memory exception Type exception Bounds exception Address +exception AddressOverflow (* @@ -73,7 +74,7 @@ let size mem = let grow mem n = let old_size = size mem in let new_size = Int64.add old_size n in - if old_size > new_size then raise Out_of_memory else + if I64.gt_u old_size new_size then raise AddressOverflow else let after = create' new_size in let host_old_size = host_size_of_int64 old_size in Array1.blit (Array1.sub !mem 0 host_old_size) (Array1.sub after 0 host_old_size); diff --git a/ml-proto/spec/memory.mli b/ml-proto/spec/memory.mli index 707430dcb5..e42103e3b7 100644 --- a/ml-proto/spec/memory.mli +++ b/ml-proto/spec/memory.mli @@ -15,6 +15,7 @@ type value = Values.value exception Type exception Bounds exception Address +exception AddressOverflow val create : size -> memory val init : memory -> segment list -> unit diff --git a/ml-proto/test/memory_trap.wast b/ml-proto/test/memory_trap.wast index c7551a5e88..1cd5d57808 100644 --- a/ml-proto/test/memory_trap.wast +++ b/ml-proto/test/memory_trap.wast @@ -9,6 +9,11 @@ (export "grow_memory" $grow_memory) (func $grow_memory (param $i i32) (grow_memory (get_local $i))) + + (export "overflow_memory_size" $overflow_memory_size) + (func $overflow_memory_size + (grow_memory (i32.xor (i32.const -1) (i32.sub (page_size) (i32.const 1)))) + ) ) (assert_return (invoke "store" (i32.const -4) (i32.const 42)) (i32.const 42)) @@ -24,3 +29,4 @@ (assert_trap (invoke "store" (i32.const 0x80000000) (i32.const 13)) "runtime: out of bounds memory access") (assert_trap (invoke "load" (i32.const 0x80000000)) "runtime: out of bounds memory access") (assert_trap (invoke "grow_memory" (i32.const 3)) "runtime: grow_memory operand not multiple of page_size") +(assert_trap (invoke "overflow_memory_size") "runtime: grow_memory overflow") From c30a1b11c50fb5191d92abc928d778b0d7e20391 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 14 Oct 2015 06:55:08 -0700 Subject: [PATCH 3/4] Remove the Address exception, which is no longer used. --- ml-proto/spec/eval.ml | 1 - ml-proto/spec/memory.ml | 1 - ml-proto/spec/memory.mli | 1 - 3 files changed, 3 deletions(-) diff --git a/ml-proto/spec/eval.ml b/ml-proto/spec/eval.ml index 2d872cfa20..3234cae137 100644 --- a/ml-proto/spec/eval.ml +++ b/ml-proto/spec/eval.ml @@ -68,7 +68,6 @@ end let memory_error at = function | Memory.Bounds -> error at "runtime: out of bounds memory access" | Memory.AddressOverflow -> error at "runtime: memory address overflow" - | Memory.Address -> error at "runtime: illegal address value" | exn -> raise exn let type_error at v t = diff --git a/ml-proto/spec/memory.ml b/ml-proto/spec/memory.ml index 57aca26179..83b90d7f14 100644 --- a/ml-proto/spec/memory.ml +++ b/ml-proto/spec/memory.ml @@ -20,7 +20,6 @@ type t = memory exception Type exception Bounds -exception Address exception AddressOverflow diff --git a/ml-proto/spec/memory.mli b/ml-proto/spec/memory.mli index e42103e3b7..e2f8267f24 100644 --- a/ml-proto/spec/memory.mli +++ b/ml-proto/spec/memory.mli @@ -14,7 +14,6 @@ type value = Values.value exception Type exception Bounds -exception Address exception AddressOverflow val create : size -> memory From 427b8412961123a53e5008f2dc085db970b79dfd Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 14 Oct 2015 09:11:10 -0700 Subject: [PATCH 4/4] Rename AddressOverflow to SizeOverflow. --- ml-proto/spec/eval.ml | 2 +- ml-proto/spec/memory.ml | 4 ++-- ml-proto/spec/memory.mli | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ml-proto/spec/eval.ml b/ml-proto/spec/eval.ml index 3234cae137..ff9693d821 100644 --- a/ml-proto/spec/eval.ml +++ b/ml-proto/spec/eval.ml @@ -67,7 +67,7 @@ end let memory_error at = function | Memory.Bounds -> error at "runtime: out of bounds memory access" - | Memory.AddressOverflow -> error at "runtime: memory address overflow" + | Memory.SizeOverflow -> error at "runtime: memory size overflow" | exn -> raise exn let type_error at v t = diff --git a/ml-proto/spec/memory.ml b/ml-proto/spec/memory.ml index 83b90d7f14..1a508aec03 100644 --- a/ml-proto/spec/memory.ml +++ b/ml-proto/spec/memory.ml @@ -20,7 +20,7 @@ type t = memory exception Type exception Bounds -exception AddressOverflow +exception SizeOverflow (* @@ -73,7 +73,7 @@ let size mem = let grow mem n = let old_size = size mem in let new_size = Int64.add old_size n in - if I64.gt_u old_size new_size then raise AddressOverflow else + if I64.gt_u old_size new_size then raise SizeOverflow else let after = create' new_size in let host_old_size = host_size_of_int64 old_size in Array1.blit (Array1.sub !mem 0 host_old_size) (Array1.sub after 0 host_old_size); diff --git a/ml-proto/spec/memory.mli b/ml-proto/spec/memory.mli index e2f8267f24..25e804eec2 100644 --- a/ml-proto/spec/memory.mli +++ b/ml-proto/spec/memory.mli @@ -14,7 +14,7 @@ type value = Values.value exception Type exception Bounds -exception AddressOverflow +exception SizeOverflow val create : size -> memory val init : memory -> segment list -> unit