Skip to content

Commit cbe546f

Browse files
committed
Improve descriptions and fix examples in Explainer.md, addressing feedback in #276
1 parent 4bafd01 commit cbe546f

File tree

4 files changed

+91
-74
lines changed

4 files changed

+91
-74
lines changed

design/mvp/Binary.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ Notes:
4848
WebAssembly 1.0 spec.)
4949
* The `layer` field is meant to distinguish modules from components early in
5050
the binary format. (Core WebAssembly modules already implicitly have a
51-
`layer` field of `0x0` in their 4 byte [`core:version`] field.)
51+
`layer` field of `0x0` if the existing 4-byte [`core:version`] field is
52+
reinterpreted as two 2-byte fields. This implies that the Core WebAssembly
53+
spec needs to make a backwards-compatible spec change to split `core:version`
54+
and fix `layer` to forever be `0x0`.)
5255

5356

5457
## Instance Definitions

design/mvp/CanonicalABI.md

+44-34
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ of modules in Core WebAssembly.
77
* [Supporting definitions](#supporting-definitions)
88
* [Despecialization](#despecialization)
99
* [Alignment](#alignment)
10-
* [Size](#size)
10+
* [Element Size](#element-size)
1111
* [Runtime State](#runtime-state)
1212
* [Loading](#loading)
1313
* [Storing](#storing)
@@ -156,13 +156,17 @@ Handle types are passed as `i32` indices into the `HandleTable` introduced
156156
below.
157157

158158

159-
### Size
159+
### Element Size
160160

161-
Each value type is also assigned a `size`, measured in bytes, which corresponds
162-
the `sizeof` operator in C. Empty types, such as records with no fields, are
163-
not permitted, to avoid complications in source languages.
161+
Each value type is also assigned an `elem_size` which is the number of bytes
162+
used when values of the type are stored as elements of a `list`. Having this
163+
byte size be a static property of the type instead of attempting to use a
164+
variable-length element-encoding scheme both simplifies the implementation and
165+
maps well to languages which represent `list`s as random-access arrays. Empty
166+
types, such as records with no fields, are not permitted, to avoid
167+
complications in source languages.
164168
```python
165-
def size(t):
169+
def elem_size(t):
166170
match despecialize(t):
167171
case Bool() : return 1
168172
case S8() | U8() : return 1
@@ -173,33 +177,33 @@ def size(t):
173177
case F64() : return 8
174178
case Char() : return 4
175179
case String() | List(_) : return 8
176-
case Record(fields) : return size_record(fields)
177-
case Variant(cases) : return size_variant(cases)
178-
case Flags(labels) : return size_flags(labels)
180+
case Record(fields) : return elem_size_record(fields)
181+
case Variant(cases) : return elem_size_variant(cases)
182+
case Flags(labels) : return elem_size_flags(labels)
179183
case Own(_) | Borrow(_) : return 4
180184

181-
def size_record(fields):
185+
def elem_size_record(fields):
182186
s = 0
183187
for f in fields:
184188
s = align_to(s, alignment(f.t))
185-
s += size(f.t)
189+
s += elem_size(f.t)
186190
assert(s > 0)
187191
return align_to(s, alignment_record(fields))
188192

189193
def align_to(ptr, alignment):
190194
return math.ceil(ptr / alignment) * alignment
191195

192-
def size_variant(cases):
193-
s = size(discriminant_type(cases))
196+
def elem_size_variant(cases):
197+
s = elem_size(discriminant_type(cases))
194198
s = align_to(s, max_case_alignment(cases))
195199
cs = 0
196200
for c in cases:
197201
if c.t is not None:
198-
cs = max(cs, size(c.t))
202+
cs = max(cs, elem_size(c.t))
199203
s += cs
200204
return align_to(s, alignment_variant(cases))
201205

202-
def size_flags(labels):
206+
def elem_size_flags(labels):
203207
n = len(labels)
204208
assert(n > 0)
205209
if n <= 8: return 1
@@ -430,7 +434,7 @@ the top-level case analysis:
430434
```python
431435
def load(cx, ptr, t):
432436
assert(ptr == align_to(ptr, alignment(t)))
433-
assert(ptr + size(t) <= len(cx.opts.memory))
437+
assert(ptr + elem_size(t) <= len(cx.opts.memory))
434438
match despecialize(t):
435439
case Bool() : return convert_int_to_bool(load_int(cx, ptr, 1))
436440
case U8() : return load_int(cx, ptr, 1)
@@ -511,6 +515,7 @@ testing that its unsigned integral value is in the valid [Unicode Code Point]
511515
range and not a [Surrogate]:
512516
```python
513517
def convert_i32_to_char(cx, i):
518+
assert(i >= 0)
514519
trap_if(i >= 0x110000)
515520
trap_if(0xD800 <= i <= 0xDFFF)
516521
return chr(i)
@@ -571,18 +576,18 @@ def load_list(cx, ptr, elem_type):
571576

572577
def load_list_from_range(cx, ptr, length, elem_type):
573578
trap_if(ptr != align_to(ptr, alignment(elem_type)))
574-
trap_if(ptr + length * size(elem_type) > len(cx.opts.memory))
579+
trap_if(ptr + length * elem_size(elem_type) > len(cx.opts.memory))
575580
a = []
576581
for i in range(length):
577-
a.append(load(cx, ptr + i * size(elem_type), elem_type))
582+
a.append(load(cx, ptr + i * elem_size(elem_type), elem_type))
578583
return a
579584

580585
def load_record(cx, ptr, fields):
581586
record = {}
582587
for field in fields:
583588
ptr = align_to(ptr, alignment(field.t))
584589
record[field.label] = load(cx, ptr, field.t)
585-
ptr += size(field.t)
590+
ptr += elem_size(field.t)
586591
return record
587592
```
588593
As a technical detail: the `align_to` in the loop in `load_record` is
@@ -599,7 +604,7 @@ implementation can build the appropriate index tables at compile-time so that
599604
variant-passing is always O(1) and not involving string operations.
600605
```python
601606
def load_variant(cx, ptr, cases):
602-
disc_size = size(discriminant_type(cases))
607+
disc_size = elem_size(discriminant_type(cases))
603608
case_index = load_int(cx, ptr, disc_size)
604609
ptr += disc_size
605610
trap_if(case_index >= len(cases))
@@ -630,7 +635,7 @@ derived from the ordered labels of the `flags` type. The code here takes
630635
advantage of Python's support for integers of arbitrary width.
631636
```python
632637
def load_flags(cx, ptr, labels):
633-
i = load_int(cx, ptr, size_flags(labels))
638+
i = load_int(cx, ptr, elem_size_flags(labels))
634639
return unpack_flags_from_int(i, labels)
635640

636641
def unpack_flags_from_int(i, labels):
@@ -670,8 +675,13 @@ def lift_borrow(cx, i, t):
670675
cx.track_owning_lend(h)
671676
return h.rep
672677
```
673-
The `track_owning_lend` call to `CallContext` participates in the enforcement of
674-
the dynamic borrow rules.
678+
The `track_owning_lend` call to `CallContext` participates in the enforcement
679+
of the dynamic borrow rules, which keep the source `own` handle alive until the
680+
end of the call (as an intentionally-conservative upper bound on how long the
681+
`borrow` handle can be held). This tracking is only required when `h` is an
682+
`own` handle because, when `h` is a `borrow` handle, this tracking has already
683+
happened (when the originating `own` handle was lifted) for a strictly longer
684+
call scope than the current call.
675685

676686

677687
### Storing
@@ -682,7 +692,7 @@ The `store` function defines how to write a value `v` of a given value type
682692
```python
683693
def store(cx, v, t, ptr):
684694
assert(ptr == align_to(ptr, alignment(t)))
685-
assert(ptr + size(t) <= len(cx.opts.memory))
695+
assert(ptr + elem_size(t) <= len(cx.opts.memory))
686696
match despecialize(t):
687697
case Bool() : store_int(cx, int(bool(v)), ptr, 1)
688698
case U8() : store_int(cx, v, ptr, 1)
@@ -990,20 +1000,20 @@ def store_list(cx, v, ptr, elem_type):
9901000
store_int(cx, length, ptr + 4, 4)
9911001

9921002
def store_list_into_range(cx, v, elem_type):
993-
byte_length = len(v) * size(elem_type)
1003+
byte_length = len(v) * elem_size(elem_type)
9941004
trap_if(byte_length >= (1 << 32))
9951005
ptr = cx.opts.realloc(0, 0, alignment(elem_type), byte_length)
9961006
trap_if(ptr != align_to(ptr, alignment(elem_type)))
9971007
trap_if(ptr + byte_length > len(cx.opts.memory))
9981008
for i,e in enumerate(v):
999-
store(cx, e, elem_type, ptr + i * size(elem_type))
1009+
store(cx, e, elem_type, ptr + i * elem_size(elem_type))
10001010
return (ptr, len(v))
10011011

10021012
def store_record(cx, v, ptr, fields):
10031013
for f in fields:
10041014
ptr = align_to(ptr, alignment(f.t))
10051015
store(cx, v[f.label], f.t, ptr)
1006-
ptr += size(f.t)
1016+
ptr += elem_size(f.t)
10071017
```
10081018

10091019
Variants are stored using the `|`-separated list of `refines` cases built
@@ -1015,7 +1025,7 @@ case indices to the consumer's case indices.
10151025
```python
10161026
def store_variant(cx, v, ptr, cases):
10171027
case_index, case_value = match_case(v, cases)
1018-
disc_size = size(discriminant_type(cases))
1028+
disc_size = elem_size(discriminant_type(cases))
10191029
store_int(cx, case_index, ptr, disc_size)
10201030
ptr += disc_size
10211031
ptr = align_to(ptr, max_case_alignment(cases))
@@ -1042,7 +1052,7 @@ to variants.
10421052
```python
10431053
def store_flags(cx, v, ptr, labels):
10441054
i = pack_flags_into_int(v, labels)
1045-
store_int(cx, i, ptr, size_flags(labels))
1055+
store_int(cx, i, ptr, elem_size_flags(labels))
10461056

10471057
def pack_flags_into_int(v, labels):
10481058
i = 0
@@ -1292,7 +1302,7 @@ def lift_flat_variant(cx, vi, cases):
12921302
case ('i64', 'i32') : return wrap_i64_to_i32(x)
12931303
case ('i64', 'f32') : return decode_i32_as_float(wrap_i64_to_i32(x))
12941304
case ('i64', 'f64') : return decode_i64_as_float(x)
1295-
case _ : return x
1305+
case _ : assert(have == want); return x
12961306
c = cases[case_index]
12971307
if c.t is None:
12981308
v = None
@@ -1403,7 +1413,7 @@ def lower_flat_variant(cx, v, cases):
14031413
case ('i32', 'i64') : payload[i] = Value('i64', have.v)
14041414
case ('f32', 'i64') : payload[i] = Value('i64', encode_float_as_i32(have.v))
14051415
case ('f64', 'i64') : payload[i] = Value('i64', encode_float_as_i64(have.v))
1406-
case _ : pass
1416+
case _ : assert(have.t == want)
14071417
for want in flat_types:
14081418
payload.append(Value(want, 0))
14091419
return [Value('i32', case_index)] + payload
@@ -1433,7 +1443,7 @@ def lift_values(cx, max_flat, vi, ts):
14331443
ptr = vi.next('i32')
14341444
tuple_type = Tuple(ts)
14351445
trap_if(ptr != align_to(ptr, alignment(tuple_type)))
1436-
trap_if(ptr + size(tuple_type) > len(cx.opts.memory))
1446+
trap_if(ptr + elem_size(tuple_type) > len(cx.opts.memory))
14371447
return list(load(cx, ptr, tuple_type).values())
14381448
else:
14391449
return [ lift_flat(cx, vi, t) for t in ts ]
@@ -1451,11 +1461,11 @@ def lower_values(cx, max_flat, vs, ts, out_param = None):
14511461
tuple_type = Tuple(ts)
14521462
tuple_value = {str(i): v for i,v in enumerate(vs)}
14531463
if out_param is None:
1454-
ptr = cx.opts.realloc(0, 0, alignment(tuple_type), size(tuple_type))
1464+
ptr = cx.opts.realloc(0, 0, alignment(tuple_type), elem_size(tuple_type))
14551465
else:
14561466
ptr = out_param.next('i32')
14571467
trap_if(ptr != align_to(ptr, alignment(tuple_type)))
1458-
trap_if(ptr + size(tuple_type) > len(cx.opts.memory))
1468+
trap_if(ptr + elem_size(tuple_type) > len(cx.opts.memory))
14591469
store(cx, tuple_value, tuple_type, ptr)
14601470
return [ Value('i32', ptr) ]
14611471
else:

design/mvp/Explainer.md

+14-11
Original file line numberDiff line numberDiff line change
@@ -433,10 +433,10 @@ type and function definitions which are introduced in the next two sections.
433433
The syntax for defining core types extends the existing core type definition
434434
syntax, adding a `module` type constructor:
435435
```ebnf
436-
core:type ::= (type <id>? <core:deftype>) (GC proposal)
437-
core:deftype ::= <core:functype> (WebAssembly 1.0)
438-
| <core:structtype> (GC proposal)
439-
| <core:arraytype> (GC proposal)
436+
core:rectype ::= ... from the Core WebAssembly spec
437+
core:typedef ::= ... from the Core WebAssembly spec
438+
core:subtype ::= ... from the Core WebAssembly spec
439+
core:comptype ::= ... from the Core WebAssembly spec
440440
| <core:moduletype>
441441
core:moduletype ::= (module <core:moduledecl>*)
442442
core:moduledecl ::= <core:importdecl>
@@ -452,8 +452,8 @@ core:exportdesc ::= strip-id(<core:importdesc>)
452452
where strip-id(X) parses '(' sort Y ')' when X parses '(' sort <id>? Y ')'
453453
```
454454

455-
Here, `core:deftype` (short for "defined type") is inherited from the [gc]
456-
proposal and extended with a `module` type constructor. If [module-linking] is
455+
Here, `core:comptype` (short for "composite type") as defined in the [gc]
456+
proposal is extended with a `module` type constructor. If [module-linking] is
457457
added to Core WebAssembly, an `instance` type constructor would be added as
458458
well but, for now, it's left out since it's unnecessary. Also, in the MVP,
459459
validation will reject `core:moduletype` defining or aliasing other
@@ -571,6 +571,8 @@ typebound ::= (eq <typeidx>)
571571
572572
where bind-id(X) parses '(' sort <id>? Y ')' when X parses '(' sort Y ')'
573573
```
574+
Because there is nothing in this type grammar analogous to the [gc] proposal's
575+
[`rectype`], none of these types are recursive.
574576

575577
#### Fundamental value types
576578

@@ -788,8 +790,8 @@ definitions:
788790
(export "h" (func (result $U)))
789791
(import "T" (type $T (sub resource)))
790792
(import "i" (func (param "x" (list (own $T)))))
791-
(export $T' "T2" (type (eq $T)))
792-
(export $U' "U" (type (sub resource)))
793+
(export "T2" (type $T' (eq $T)))
794+
(export "U" (type $U' (sub resource)))
793795
(export "j" (func (param "x" (borrow $T')) (result (own $U'))))
794796
))
795797
)
@@ -960,7 +962,7 @@ as well. For example, in this component:
960962
(component
961963
(import "C" (component $C
962964
(export "T1" (type (sub resource)))
963-
(export $T2 "T2" (type (sub resource)))
965+
(export "T2" (type $T2 (sub resource)))
964966
(export "T3" (type (eq $T2)))
965967
))
966968
(instance $c (instantiate $C))
@@ -1028,7 +1030,7 @@ following component:
10281030
is assigned the following `componenttype`:
10291031
```wasm
10301032
(component
1031-
(export $r1 "r1" (type (sub resource)))
1033+
(export "r1" (type $r1 (sub resource)))
10321034
(export "r2" (type (eq $r1)))
10331035
)
10341036
```
@@ -1039,7 +1041,7 @@ If a component wants to hide this fact and force clients to assume `r1` and
10391041
`r2` are distinct types (thereby allowing the implementation to actually use
10401042
separate types in the future without breaking clients), an explicit type can be
10411043
ascribed to the export that replaces the `eq` bound with a less-precise `sub`
1042-
bound.
1044+
bound (using syntax introduced [below](#import-and-export-definitions)).
10431045
```wasm
10441046
(component
10451047
(type $r (resource (rep i32)))
@@ -1991,6 +1993,7 @@ and will be added over the coming months to complete the MVP proposal:
19911993
[stack-switching]: https://github.com/WebAssembly/stack-switching/blob/main/proposals/stack-switching/Overview.md
19921994
[esm-integration]: https://github.com/WebAssembly/esm-integration/tree/main/proposals/esm-integration
19931995
[gc]: https://github.com/WebAssembly/gc/blob/main/proposals/gc/MVP.md
1996+
[`rectype`]: https://webassembly.github.io/gc/core/text/types.html#text-rectype
19941997
[shared-everything-threads]: https://github.com/WebAssembly/shared-everything-threads
19951998
[WASI Preview 2]: https://github.com/WebAssembly/WASI/tree/main/preview2
19961999

0 commit comments

Comments
 (0)