Skip to content
This repository was archived by the owner on Apr 25, 2025. It is now read-only.
This repository was archived by the owner on Apr 25, 2025. It is now read-only.

Does the combo of subtyping and unreachable code require actual type inference? #516

Closed
@fitzgen

Description

@fitzgen

Consider the following example. I've annotated it with the operand stack in between each opcode twice:

  1. Once with the appendix's algorithm.
  2. A second time with wasm-smith's "actual" stack.

The difference is that the appendix algorithm truncates the stack after unreachable code, letting subsequent pops materialize their expected types, while wasm-smith maintains the "actual" stack going into the unreachable code.

AFAICT, the formal typing rules don't actually mandate the appendix's operand stack truncation. There has never been divergence between the two approaches until now, with the introduction of subtyping.

In the following example, the br 0 makes the subsequent code unreachable. The appendix will additionally truncate the stack. Next, the br_on_null is typed [anyref anyref] -> [anyref (ref any)] due to the type of its label. With wasm-smith's actual stack, this type checks fine because structref <: anyref combined with the rules for subtyping between instructions. For the appendix algorithm, we have an empty stack in unreachable code, so when the br_on_null wants to pop the anyref that the label demands, it does, and then when it pushes it back onto the stack, it pushes an anyref. Not a structref. This is the source of the divergence between the two approaches. Finally, the br_on_cast_fail observes the "actual" structref, triggering a type mismatch: expected structref, found anyref error in the appendix algorithm.

(module
  (func (param anyref) (result anyref)
    ;; 0x1b: appendix: []
    ;;         actual: []
    ref.null struct
    ;; 0x1d: appendix: [structref]
    ;;         actual: [structref]
    local.get 0
    ;; 0x1f: appendix: [structref anyref]
    ;;         actual: [structref anyref]
    br 0
    ;; 0x21: appendix: []
    ;;         actual: [structref]
    local.get 0
    ;; 0x23: appendix: [anyref]
    ;;         actual: [structref anyref]
    br_on_null 0
    ;; 0x25: appendix: [anyref (ref any)]
    ;;         actual: [structref (ref any)]
    drop
    ;; 0x26: appendix: [anyref]
    ;;         actual: [structref]
    br_on_cast_fail 0  structref structref
  )
)

The reference interpreter, and wasmparser, report the type mismatch error and consider the example invalid. But despite that, it isn't clear to me that it really is invalid.

Firefox and wasm-smith consider the example valid.

If this example is valid, then it seems like popping from an empty operand stack in unreachable code would have to, in this case, produce a value that is constrained to be some subtype of anyref but not necessarily exactly anyref and then the subsequent br_on_cast_fail would add a second constraint that the value is additionally a subtype of structref, and then implementations would need to solve these constraints to determine validity. But that kind of constraint solving and "real" type inference is something that we intentionally avoid requiring in Wasm validation. So if this example is currently valid, it seems like we want to do something to avoid the need for this kind of constraint solving.

Concrete Questions

  1. the actual formal typing rules don't mandate the appendix's operand stack truncation

    Is this statement true or false?

  2. Is the example valid or invalid?

Thanks for your time!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions