Remove heaps from core Cranelift, push them into cranelift-wasm (#5386)
* cranelift-wasm: translate Wasm loads into lower-level CLIF operations
Rather than using `heap_{load,store,addr}`.
* cranelift: Remove the `heap_{addr,load,store}` instructions
These are now legalized in the `cranelift-wasm` frontend.
* cranelift: Remove the `ir::Heap` entity from CLIF
* Port basic memory operation tests to .wat filetests
* Remove test for verifying CLIF heaps
* Remove `heap_addr` from replace_branching_instructions_and_cfg_predecessors.clif test
* Remove `heap_addr` from readonly.clif test
* Remove `heap_addr` from `table_addr.clif` test
* Remove `heap_addr` from the simd-fvpromote_low.clif test
* Remove `heap_addr` from simd-fvdemote.clif test
* Remove `heap_addr` from the load-op-store.clif test
* Remove the CLIF heap runtest
* Remove `heap_addr` from the global_value.clif test
* Remove `heap_addr` from fpromote.clif runtests
* Remove `heap_addr` from fdemote.clif runtests
* Remove `heap_addr` from memory.clif parser test
* Remove `heap_addr` from reject_load_readonly.clif test
* Remove `heap_addr` from reject_load_notrap.clif test
* Remove `heap_addr` from load_readonly_notrap.clif test
* Remove `static-heap-without-guard-pages.clif` test
Will be subsumed when we port `make-heap-load-store-tests.sh` to generating
`.wat` tests.
* Remove `static-heap-with-guard-pages.clif` test
Will be subsumed when we port `make-heap-load-store-tests.sh` over to `.wat`
tests.
* Remove more heap tests
These will be subsumed by porting `make-heap-load-store-tests.sh` over to `.wat`
tests.
* Remove `heap_addr` from `simple-alias.clif` test
* Remove `heap_addr` from partial-redundancy.clif test
* Remove `heap_addr` from multiple-blocks.clif test
* Remove `heap_addr` from fence.clif test
* Remove `heap_addr` from extends.clif test
* Remove runtests that rely on heaps
Heaps are not a thing in CLIF or the interpreter anymore
* Add generated load/store `.wat` tests
* Enable memory-related wasm features in `.wat` tests
* Remove CLIF heap from fcmp-mem-bug.clif test
* Add a mode for compiling `.wat` all the way to assembly in filetests
* Also generate WAT to assembly tests in `make-load-store-tests.sh`
* cargo fmt
* Reinstate `f{de,pro}mote.clif` tests without the heap bits
* Remove undefined doc link
* Remove outdated SVG and dot file from docs
* Add docs about `None` returns for base address computation helpers
* Factor out `env.heap_access_spectre_mitigation()` to a local
* Expand docs for `FuncEnvironment::heaps` trait method
* Restore f{de,pro}mote+load clif runtests with stack memory
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
digraph {
|
||||
node [
|
||||
shape=record,
|
||||
fontsize=10,
|
||||
fontname="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans"
|
||||
]
|
||||
"static" [label="mapped\npages|unmapped\npages|offset_guard\npages"]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.42.3 (0)
|
||||
-->
|
||||
<!-- Title: %3 Pages: 1 -->
|
||||
<svg width="209pt" height="45pt"
|
||||
viewBox="0.00 0.00 209.00 45.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 41)">
|
||||
<title>%3</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-41 205,-41 205,4 -4,4"/>
|
||||
<!-- static -->
|
||||
<g id="node1" class="node">
|
||||
<title>static</title>
|
||||
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 201,-36.5 201,-0.5 0,-0.5"/>
|
||||
<text text-anchor="middle" x="28" y="-21.5" font-family="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans" font-size="10.00">mapped</text>
|
||||
<text text-anchor="middle" x="28" y="-10.5" font-family="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans" font-size="10.00">pages</text>
|
||||
<polyline fill="none" stroke="black" points="56,-0.5 56,-36.5 "/>
|
||||
<text text-anchor="middle" x="90" y="-21.5" font-family="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans" font-size="10.00">unmapped</text>
|
||||
<text text-anchor="middle" x="90" y="-10.5" font-family="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans" font-size="10.00">pages</text>
|
||||
<polyline fill="none" stroke="black" points="124,-0.5 124,-36.5 "/>
|
||||
<text text-anchor="middle" x="162.5" y="-21.5" font-family="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans" font-size="10.00">offset_guard</text>
|
||||
<text text-anchor="middle" x="162.5" y="-10.5" font-family="Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans" font-size="10.00">pages</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -559,148 +559,6 @@ GV = [colocated] symbol Name
|
||||
:arg Name: External name.
|
||||
:result GV: Global value.
|
||||
|
||||
### Heaps
|
||||
|
||||
Code compiled from WebAssembly or asm.js runs in a sandbox where it can't access
|
||||
all process memory. Instead, it is given a small set of memory areas to work
|
||||
in, and all accesses are bounds checked. Cranelift models this through the
|
||||
concept of *heaps*.
|
||||
|
||||
A heap is declared in the function preamble and can be accessed with the
|
||||
`heap_addr` instruction that [traps] on out-of-bounds accesses or
|
||||
returns a pointer that is guaranteed to trap. Heap addresses can be smaller than
|
||||
the native pointer size, for example unsigned `i32` offsets on a 64-bit
|
||||
architecture.
|
||||
|
||||

|
||||
|
||||
A heap appears as three consecutive ranges of address space:
|
||||
|
||||
1. The *mapped pages* are the [accessible] memory range in the heap. A
|
||||
heap may have a minimum guaranteed size which means that some mapped pages
|
||||
are always present.
|
||||
2. The *unmapped pages* is a possibly empty range of address space that may be
|
||||
mapped in the future when the heap is grown. They are [addressable] but
|
||||
not [accessible].
|
||||
3. The *offset-guard pages* is a range of address space that is guaranteed to
|
||||
always cause a trap when accessed. It is used to optimize bounds checking for
|
||||
heap accesses with a shared base pointer. They are [addressable] but
|
||||
not [accessible].
|
||||
|
||||
The *heap bound* is the total size of the mapped and unmapped pages. This is
|
||||
the bound that `heap_addr` checks against. Memory accesses inside the
|
||||
heap bounds can trap if they hit an unmapped page (which is not
|
||||
[accessible]).
|
||||
|
||||
Two styles of heaps are supported, *static* and *dynamic*. They behave
|
||||
differently when resized.
|
||||
|
||||
#### Static heaps
|
||||
|
||||
A *static heap* starts out with all the address space it will ever need, so it
|
||||
never moves to a different address. At the base address is a number of mapped
|
||||
pages corresponding to the heap's current size. Then follows a number of
|
||||
unmapped pages where the heap can grow up to its maximum size. After the
|
||||
unmapped pages follow the offset-guard pages which are also guaranteed to
|
||||
generate a trap when accessed.
|
||||
|
||||
H = static Base, min MinBytes, bound BoundBytes, offset_guard OffsetGuardBytes
|
||||
Declare a static heap in the preamble.
|
||||
|
||||
:arg Base: Global value holding the heap's base address.
|
||||
:arg MinBytes: Guaranteed minimum heap size in bytes. Accesses below this
|
||||
size will never trap.
|
||||
:arg BoundBytes: Fixed heap bound in bytes. This defines the amount of
|
||||
address space reserved for the heap, not including the offset-guard
|
||||
pages.
|
||||
:arg OffsetGuardBytes: Size of the offset-guard pages in bytes.
|
||||
|
||||
#### Dynamic heaps
|
||||
|
||||
A *dynamic heap* can be relocated to a different base address when it is
|
||||
resized, and its bound can move dynamically. The offset-guard pages move when
|
||||
the heap is resized. The bound of a dynamic heap is stored in a global value.
|
||||
|
||||
H = dynamic Base, min MinBytes, bound BoundGV, offset_guard OffsetGuardBytes
|
||||
Declare a dynamic heap in the preamble.
|
||||
|
||||
:arg Base: Global value holding the heap's base address.
|
||||
:arg MinBytes: Guaranteed minimum heap size in bytes. Accesses below this
|
||||
size will never trap.
|
||||
:arg BoundGV: Global value containing the current heap bound in bytes.
|
||||
:arg OffsetGuardBytes: Size of the offset-guard pages in bytes.
|
||||
|
||||
#### Heap examples
|
||||
|
||||
Some Wasm VMs prefer to use fixed heaps with a 4 GB bound and 2 GB of
|
||||
offset-guard pages when running WebAssembly code on 64-bit CPUs. The combination
|
||||
of a 4 GB fixed bound and 1-byte bounds checks means that no code needs to be
|
||||
generated for bounds checks at all:
|
||||
|
||||
```
|
||||
test verifier
|
||||
|
||||
function %add_members(i32, i64 vmctx) -> f32 {
|
||||
gv0 = vmctx
|
||||
gv1 = load.i64 notrap aligned gv0+64
|
||||
heap0 = static gv1, min 0x1000, bound 0x1_0000_0000, offset_guard 0x8000_0000
|
||||
|
||||
block0(v0: i32, v5: i64):
|
||||
v1 = heap_addr.i64 heap0, v0, 1
|
||||
v2 = load.f32 v1+16
|
||||
v3 = load.f32 v1+20
|
||||
v4 = fadd v2, v3
|
||||
return v4
|
||||
}
|
||||
```
|
||||
|
||||
A static heap can also be used for 32-bit code when the WebAssembly module
|
||||
declares a small upper bound on its memory. A 1 MB static bound with a single 4
|
||||
KB offset-guard page still has opportunities for sharing bounds checking code:
|
||||
|
||||
```
|
||||
test verifier
|
||||
|
||||
function %add_members(i32, i32 vmctx) -> f32 {
|
||||
gv0 = vmctx
|
||||
gv1 = load.i32 notrap aligned gv0+64
|
||||
heap0 = static gv1, min 0x1000, bound 0x10_0000, offset_guard 0x1000
|
||||
|
||||
block0(v0: i32, v5: i32):
|
||||
v1 = heap_addr.i32 heap0, v0, 1
|
||||
v2 = load.f32 v1+16
|
||||
v3 = load.f32 v1+20
|
||||
v4 = fadd v2, v3
|
||||
return v4
|
||||
}
|
||||
```
|
||||
|
||||
If the upper bound on the heap size is too large, a dynamic heap is required
|
||||
instead.
|
||||
|
||||
Finally, a runtime environment that simply allocates a heap with
|
||||
`malloc()` may not have any offset-guard pages at all. In that case,
|
||||
full bounds checking is required for each access:
|
||||
|
||||
```
|
||||
test verifier
|
||||
|
||||
function %add_members(i32, i64 vmctx) -> f32 {
|
||||
gv0 = vmctx
|
||||
gv1 = load.i64 notrap aligned gv0+64
|
||||
gv2 = load.i32 notrap aligned gv0+72
|
||||
heap0 = dynamic gv1, min 0x1000, bound gv2, offset_guard 0
|
||||
|
||||
block0(v0: i32, v6: i64):
|
||||
v1 = heap_addr.i64 heap0, v0, 20
|
||||
v2 = load.f32 v1+16
|
||||
v3 = heap_addr.i64 heap0, v0, 24
|
||||
v4 = load.f32 v3+20
|
||||
v5 = fadd v2, v4
|
||||
return v5
|
||||
}
|
||||
```
|
||||
|
||||
### Tables
|
||||
|
||||
Code compiled from WebAssembly often needs access to objects outside of its
|
||||
@@ -728,7 +586,7 @@ T = dynamic Base, min MinElements, bound BoundGV, element_size ElementSize
|
||||
|
||||
:arg Base: Global value holding the table's base address.
|
||||
:arg MinElements: Guaranteed minimum table size in elements.
|
||||
:arg BoundGV: Global value containing the current heap bound in elements.
|
||||
:arg BoundGV: Global value containing the current table bound in elements.
|
||||
:arg ElementSize: Size of each element.
|
||||
|
||||
### Constant materialization
|
||||
|
||||
@@ -374,79 +374,3 @@ pointers are always 8 bytes, and laid out sequentially in memory. Even for 32 bi
|
||||
|
||||
Currently, we only support requesting heaps, however this is a generic mechanism that should
|
||||
be able to introduce any sort of environment support that we may need later. (e.g. tables, global values, external functions)
|
||||
|
||||
##### `heap` directive
|
||||
|
||||
The `heap` directive allows a test to request a heap to be allocated and passed to the test via the environment struct.
|
||||
|
||||
|
||||
A sample heap annotation is the following:
|
||||
```
|
||||
; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
|
||||
```
|
||||
|
||||
This indicates the following:
|
||||
* `static`: We have requested a non-resizable and non-movable static heap.
|
||||
* `size=0x1000`: It has to have a size of 4096 bytes.
|
||||
* `ptr=vmctx+0`: The pointer to the address to the start of this heap is placed at offset 0 in the `vmctx` struct
|
||||
* `bound=vmctx+8`: The pointer to the address to the end of this heap is placed at offset 8 in the `vmctx` struct
|
||||
|
||||
The `ptr` and `bound` arguments make explicit the placement of the pointers to the start and end of the heap memory in
|
||||
the environment struct. `vmctx+0` means that at offset 0 of the environment struct there will be the pointer to the start
|
||||
similarly, at offset 8 the pointer to the end.
|
||||
|
||||
|
||||
You can combine multiple heap annotations, in which case, their pointers are laid out sequentially in memory in
|
||||
the order that the annotations appear in the source file.
|
||||
|
||||
```
|
||||
; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
|
||||
; heap: dynamic, size=0x1000, ptr=vmctx+16, bound=vmctx+24
|
||||
```
|
||||
|
||||
An invalid or unexpected offset will raise an error when the test is run.
|
||||
|
||||
See the diagram below, on how the `vmctx` struct ends up if with multiple heaps:
|
||||
|
||||
```
|
||||
┌─────────────────────┐ vmctx+0
|
||||
│heap0: start address │
|
||||
├─────────────────────┤ vmctx+8
|
||||
│heap0: end address │
|
||||
├─────────────────────┤ vmctx+16
|
||||
│heap1: start address │
|
||||
├─────────────────────┤ vmctx+24
|
||||
│heap1: end address │
|
||||
├─────────────────────┤ vmctx+32
|
||||
│etc... │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
With this setup, you can now use the global values to load heaps, and load / store to them.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
function %heap_load_store(i64 vmctx, i64, i32) -> i32 {
|
||||
gv0 = vmctx
|
||||
gv1 = load.i64 notrap aligned gv0+0
|
||||
gv2 = load.i64 notrap aligned gv0+8
|
||||
heap0 = dynamic gv1, bound gv2, offset_guard 0, index_type i64
|
||||
|
||||
block0(v0: i64, v1: i64, v2: i32):
|
||||
v3 = heap_addr.i64 heap0, v1, 4
|
||||
store.i32 v2, v3
|
||||
v4 = load.i32 v3
|
||||
return v4
|
||||
}
|
||||
; heap: static, size=0x1000, ptr=vmctx+0, bound=vmctx+8
|
||||
; run: %heap_load_store(0, 1) == 1
|
||||
```
|
||||
|
||||
|
||||
### `test interpret`
|
||||
|
||||
Test the CLIF interpreter
|
||||
|
||||
This test supports the same commands as `test run`, but runs the code in the cranelift
|
||||
interpreter instead of the host machine.
|
||||
|
||||
Reference in New Issue
Block a user