Skip to content

Cranelift: reconsider the heap_addr instruction #5283

@fitzgen

Description

@fitzgen

On error condition, does this instruction itself trap or return an address that will trap when accessed? Currently that depends on arbitrary bits of the heap configuration and memory being accessed, and is different for index + offset overflow vs out of bounds. I've been improving the situation a little by making it more consistent, but it is worth backing up a bit and gaining context.

The actual problem we are solving is that of wasm loads and stores, which must trap when out of bounds. At some point long ago, we decomposed wasm memory accesses into smaller parts, one of which is heap_addr. Whether we trap in heap_addr itself or in another clif instruction after heap_addr is irrelevant to the fact that the wasm memory access our instruction sequence is implementing must trap when out of bounds. So I think trying to pin down and clean up heap_addr's semantics in the face of error conditions is accidental complexity; a problem of our own making.

That is not an argument against being precise with heap_addr semantics, however. If we have heap_addr, we should have precise semantics for it. The same way that we should aspire to giving precise (formal!) semantics to all CLIF instructions.

But... what if we didn't have a heap_addr instruction at all? I see a few possibilities:

  1. CLIF already has everything needed to emit what heap_addr gets legalized to today, so cranelift-wasm could just emit those already-legalized instructions. And now there is no interface barrier between heap_addr and the other CLIF instructions surrounding it where we have to define semantics and decide which side of the interface is responsible for actually doing the trap when we encounter an error condition.

  2. We introduce bounds_checked_load and bounds_checked_store instructions that match wasm semantics. They would take a static heap immediate, a dynamic index operand, a static offset immediate, and have a static type parameter that is being loaded/stored. We would carry these instructions all the way through Cranelift to the backends, where they would get lowered into precise machine instruction sequences. Again, there is no artificial interface barrier where we have to decide which side of the interface is responsible for trapping; the bounds_checked_* instructions implement the whole of the Wasm semantics and will trap on OOB/overflow.

  3. We introduce bounds_checked_{load,store} instructions as above, but we legalize them early in Cranelift's pipeline to more primitive CLIF instructions (equivalent of what cranelift-wasm would otherwise produce directly in option 1).

The primary benefits of 1 and 3 are that by decomposing the memory accesses into smaller parts, we can GVN/LICM/etc those smaller parts.

The primary benefits of 2 are that we have fewer CLIF instructions to process and so compilation times should be a little better, and that we have very precise control over the machine instructions emitted. However, we can't easily GVN/LICM/etc subcomponents of a wasm load like loading the memory base and length out of the vmctx.

The trade off between 1 and 3 is whether we want complexity in the cranelift-wasm frontend or in cranelift-codegen legalization.


Personally, I would rank the options in the order of 3, 1, 2.

I think we can make up for 1/3's lack of precise machine instruction control with filetests.

The compilation time benefits of a macro instruction are tempting, but I think trading that away for code quality is the correct move in this scenario, since we stille have many other avenues for improving compilation times (as we have been doing a lot recently!) that don't sacrifice code quality at all.

As for why 3 over 1? Keeping cranelift-wasm relatively simple seems like the way to go for me, and legalization seems like the natural home for this kind of thing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    craneliftIssues related to the Cranelift code generatorcranelift:wasm

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions