Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 11 additions & 8 deletions interpreter/runtime/memory.ml
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,33 @@ let valid_limits {min; max} =
| None -> true
| Some m -> I64.le_u min m

let valid_size at i =
match at with
| I32AT -> I64.le_u i 0xffffL
| I64AT -> true
let valid_size at pt i =
match pt with
| PageT 0 -> true
| PageT ps ->
match at with
| I32AT -> I64.le_u i (Int64.shift_right 0xffffffffL ps)
| I64AT -> true
Comment on lines +25 to +31
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
let valid_size at pt i =
match pt with
| PageT 0 -> true
| PageT ps ->
match at with
| I32AT -> I64.le_u i (Int64.shift_right 0xffffffffL ps)
| I64AT -> true
let valid_size at pt i =
match at, pt with
| I32AT, PageT ps -> I64.le_u i (Int64.shift_right 0xffff_ffffL ps)
| _, _ -> true


let create n (PageT ps) =
try
let size = Int64.(shift_left n ps) in
let size = Int64.shift_left n ps in
let mem = Array1_64.create Int8_unsigned C_layout size in
Array1.fill mem 0;
mem
with Out_of_memory -> raise OutOfMemory

let alloc (MemoryT (at, lim, pt) as ty) =
assert Free.((memorytype ty).types = Set.empty);
if not (valid_size at lim.min) then raise SizeOverflow;
if not (valid_size at pt lim.min) then raise SizeOverflow;
if not (valid_limits lim) then raise Type;
{ty; content = create lim.min pt}

let bound mem =
Array1_64.dim mem.content

let pagesize mem =
let MemoryT (_, _, PageT x) = mem.ty in (Int64.shift_left 1L x)
let MemoryT (_, _, PageT x) = mem.ty in Int64.shift_left 1L x

let size mem =
Int64.(div (bound mem) (pagesize mem))
Expand All @@ -63,7 +66,7 @@ let grow mem delta =
let new_size = Int64.add old_size delta in
if I64.gt_u old_size new_size then raise SizeOverflow else
let lim' = {lim with min = new_size} in
if not (valid_size at new_size) then raise SizeOverflow else
if not (valid_size at pt new_size) then raise SizeOverflow else
if not (valid_limits lim') then raise SizeLimit else
let after = create new_size pt in
let dim = Array1_64.dim mem.content in
Expand Down
1 change: 1 addition & 0 deletions interpreter/text/lexer.mll
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ rule token = parse
| "start" -> START
| "import" -> IMPORT
| "export" -> EXPORT
| "pagesize" -> PAGESIZE

| "module" -> MODULE
| "binary" -> BIN
Expand Down
22 changes: 16 additions & 6 deletions interpreter/text/parser.mly
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ let parse_annots (m : module_) : Custom.section list =
%token VEC_SHUFFLE
%token<Ast.laneidx -> Ast.instr'> VEC_EXTRACT VEC_REPLACE
%token FUNC START TYPE PARAM RESULT LOCAL GLOBAL
%token PAGESIZE
%token TABLE ELEM MEMORY TAG DATA DECLARE OFFSET ITEM IMPORT EXPORT
%token MODULE BIN QUOTE DEFINITION INSTANCE
%token SCRIPT REGISTER INVOKE GET
Expand Down Expand Up @@ -465,8 +466,16 @@ subtype :
tabletype :
| addrtype limits reftype { fun c -> TableT ($1, $2, $3 c) }

pagetype :
| LPAR PAGESIZE NAT RPAR
{ let n = (nat32 $3 $loc($3)) in
if not (Lib.Int32.is_power_of_two_unsigned n) then
error (at $sloc) "invalid custom page size: must be power of two";
PageT (Int32.to_int (Lib.Int32.log2_unsigned n)) }

memorytype :
| addrtype limits { fun c -> MemoryT ($1, $2, PageT 16) }
| addrtype limits pagetype { fun c -> MemoryT ($1, $2, $3) }
Comment on lines +469 to +478
Copy link
Member

Choose a reason for hiding this comment

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

Hint: if you declare pagetype inline, then you can factor the empty case into it without having to duplicate rules at use sites:

Suggested change
pagetype :
| LPAR PAGESIZE NAT RPAR
{ let n = (nat32 $3 $loc($3)) in
if not (Lib.Int32.is_power_of_two_unsigned n) then
error (at $sloc) "invalid custom page size: must be power of two";
PageT (Int32.to_int (Lib.Int32.log2_unsigned n)) }
memorytype :
| addrtype limits { fun c -> MemoryT ($1, $2, PageT 16) }
| addrtype limits pagetype { fun c -> MemoryT ($1, $2, $3) }
%inline pagetype :
| LPAR PAGESIZE NAT RPAR
{ let n = (nat32 $3 $loc($3)) in
if not (Lib.Int32.is_power_of_two_unsigned n) then
error (at $sloc) "invalid custom page size: must be power of two";
PageT (Int32.to_int (Lib.Int32.log2_unsigned n)) }
| /* empty */ { PageT 0x1_0000l } /* Sugar */
memorytype :
| addrtype limits pagetype { fun c -> MemoryT ($1, $2, $3) }


limits :
| NAT { {min = nat64 $1 $loc($1); max = None} }
Expand Down Expand Up @@ -1125,16 +1134,17 @@ memory_fields :
[Import (fst $1, snd $1, ExternMemoryT ($2 c)) @@ loc], [] }
| inline_export memory_fields /* Sugar */
{ fun c x loc -> let mems, data, ims, exs = $2 c x loc in
mems, data, ims, $1 (MemoryX x) c :: exs }
| addrtype LPAR DATA string_list RPAR /* Sugar */
mems, data, ims, $1 (MemoryX x) c :: exs }
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
mems, data, ims, $1 (MemoryX x) c :: exs }
mems, data, ims, $1 (MemoryX x) c :: exs }

| addrtype pagetype LPAR DATA string_list RPAR /* Sugar */
Copy link
Member

@rossberg rossberg Jan 12, 2026

Choose a reason for hiding this comment

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

The pagetype needs to be optional (independently of the addrtype) in the presence of inline data, otherwise this would be a breaking change. You'll probably need two productions to handle this without s/r conflict.

{ fun c x loc ->
let size = Int64.(div (add (of_int (String.length $4)) 65535L) 65536L) in
let PageT ps = $2 in
let page_size = Int64.shift_left 1L ps in
let size = Int64.(div (add (of_int (String.length $5)) (sub page_size 1L)) page_size) in
let offset = [at_const $1 (0L @@ loc) @@ loc] @@ loc in
[Memory (MemoryT ($1, {min = size; max = Some size}, PageT 16)) @@ loc],
[Data ($4, Active (x, offset) @@ loc) @@ loc],
[Memory (MemoryT ($1, {min = size; max = Some size}, $2)) @@ loc],
[Data ($5, Active (x, offset) @@ loc) @@ loc],
[], [] }


elemkind :
| FUNC { (NoNull, FuncHT) }

Expand Down
8 changes: 8 additions & 0 deletions interpreter/util/lib.ml
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,17 @@ struct
if n = 1l then acc else loop (Int32.add acc 1l) (Int32.shift_right_logical n 1) in
loop 0l n

let log2_unsigned n =
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Redefine log2 in terms of log2_unsigned, as in Int64.

let rec loop acc n =
if n = 1l then acc else loop (Int32.add acc 1l) (Int32.shift_right_logical n 1) in
loop 0l n

let is_power_of_two n =
if n < 0l then failwith "is_power_of_two";
n <> 0l && Int32.(logand n (sub n 1l)) = 0l

let is_power_of_two_unsigned n =
n <> 0l && Int32.(logand n (sub n 1l)) = 0l
end

module Int64 =
Expand Down
2 changes: 2 additions & 0 deletions interpreter/util/lib.mli
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ end
module Int32 :
sig
val log2 : int32 -> int32
val log2_unsigned : int32 -> int32
val is_power_of_two : int32 -> bool
val is_power_of_two_unsigned : int32 -> bool
end

module Int64 :
Expand Down
18 changes: 12 additions & 6 deletions interpreter/valid/valid.ml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ let check_limits {min; max} range at msg =
"size minimum must not be greater than maximum"

let check_pagetype (PageT ps) at =
require (ps = 16 || ps = 0) at "page size must be 1 or 64KiB"
require (ps = 16 || ps = 0) at "invalid custom page size"

let check_numtype (c : context) (t : numtype) at =
()
Expand Down Expand Up @@ -200,13 +200,19 @@ let check_globaltype (c : context) (gt : globaltype) at =

let check_memorytype (c : context) (mt : memorytype) at =
let MemoryT (at_, lim, pt) = mt in
check_pagetype pt at;
let sz, s =
match at_ with
| I32AT -> 0x1_0000L, "2^16 pages (4 GiB) for i32"
| I64AT -> 0x1_0000_0000_0000L, "2^48 pages (256 TiB) for i64"
match pt with
| PageT 16 ->
(match at_ with
| I32AT -> 0x1_0000L, "2^16 pages (4 GiB) for i32"
| I64AT -> 0x1_0000_0000_0000L, "2^48 pages (256 TiB) for i64")
| _ -> (* TODO: divide by page size, what about error msg? *)
(match at_ with
| I32AT -> 0xFFFF_FFFFL, "2^32 - 1 bytes for i32"
| I64AT -> 0xFFFF_FFFF_FFFF_FFFFL, "2^64 - 1 bytes for i64")
Comment on lines +205 to +213
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
match pt with
| PageT 16 ->
(match at_ with
| I32AT -> 0x1_0000L, "2^16 pages (4 GiB) for i32"
| I64AT -> 0x1_0000_0000_0000L, "2^48 pages (256 TiB) for i64")
| _ -> (* TODO: divide by page size, what about error msg? *)
(match at_ with
| I32AT -> 0xFFFF_FFFFL, "2^32 - 1 bytes for i32"
| I64AT -> 0xFFFF_FFFF_FFFF_FFFFL, "2^64 - 1 bytes for i64")
match at_, pt with
| I32AT, PageT 16 -> 0x1_0000L, "2^16 pages (4 GiB) for i32"
| I64AT, PageT 16 -> 0x1_0000_0000_0000L, "2^48 pages (256 TiB) for i64")
(* TODO: divide by page size, what about error msg? *)
| I32AT, _ -> 0xFFFF_FFFFL, "2^32 - 1 bytes for i32"
| I64AT, _ -> 0xFFFF_FFFF_FFFF_FFFFL, "2^64 - 1 bytes for i64"

in
check_limits lim sz at ("memory size must be at most " ^ s);
check_pagetype pt at
check_limits lim sz at ("memory size must be at most " ^ s)

let check_tabletype (c : context) (tt : tabletype) at =
let TableT (at_, lim, t) = tt in
Expand Down
2 changes: 1 addition & 1 deletion test/core/custom-page-sizes/custom-page-sizes.wast
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
;; Inline data segments

;; pagesize 0
(assert_malformed (module quote "(memory (pagesize 0) (data))") "invalid custom page size")
(assert_malformed (module quote "(module (memory (pagesize 0) (data)))") "invalid custom page size")

;; pagesize 1
(module
Expand Down
16 changes: 14 additions & 2 deletions test/core/custom-page-sizes/memory_max.wast
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,26 @@
(module
(import "test" "unknown" (func))
(memory 0xFFFF_FFFF (pagesize 1)))
"unknown import")
"incompatible import type")

;; i32 (pagesize 1)
(assert_unlinkable
(module
(import "test" "unknown" (memory 0xFFFF_FFFF (pagesize 1))))
"incompatible import type")

;; i32 (default pagesize)
(assert_unlinkable
(module
(import "test" "unknown" (func))
(memory 65536 (pagesize 65536)))
"unknown import")
"incompatible import type")

;; i32 (default pagesize)
(assert_unlinkable
(module
(import "test" "unknown" (memory 65536 (pagesize 65536))))
"incompatible import type")

;; Memory size just over the maximum.

Expand Down
20 changes: 16 additions & 4 deletions test/core/custom-page-sizes/memory_max_i64.wast
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,28 @@
;; i64 (pagesize 1)
(assert_unlinkable
(module
(import "test" "import" (func))
(import "test" "unknown" (func))
(memory i64 0xFFFF_FFFF_FFFF_FFFF (pagesize 1)))
"unknown import")
"incompatible import type")

;; i64 (pagesize 1)
(assert_unlinkable
(module
(import "test" "unknown" (memory i64 0xFFFF_FFFF_FFFF_FFFF (pagesize 1))))
"incompatible import type")

;; i64 (default pagesize)
(assert_unlinkable
(module
(import "test" "unknown" (func))
(memory i64 0x1_0000_0000_0000 (pagesize 65536)))
"unknown import")
"incompatible import type")

;; i64 (default pagesize)
(assert_unlinkable
(module
(import "test" "unknown" (memory i64 0x1_0000_0000_0000 (pagesize 65536))))
"incompatible import type")

;; Memory size just over the maximum.
;;
Expand All @@ -36,7 +48,7 @@
;; i64 (pagesize 1)
(assert_malformed
(module quote "(memory i64 0x1_0000_0000_0000_0000 (pagesize 1))")
"constant out of range")
"i64 constant out of range")

;; i64 (default pagesize)
(assert_invalid
Expand Down