Skip to content
This repository was archived by the owner on Apr 25, 2025. It is now read-only.
Merged
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
4 changes: 0 additions & 4 deletions interpreter/syntax/types.ml
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,6 @@ let defaultable = function

(* Projections *)

let inv_null = function
| Null -> NoNull
| NoNull -> Null

let unpacked_storage_type = function
| ValStorageT t -> t
| PackStorageT _ -> NumT I32T
Expand Down
20 changes: 10 additions & 10 deletions interpreter/valid/valid.ml
Original file line number Diff line number Diff line change
Expand Up @@ -430,20 +430,20 @@ let rec check_instr (c : context) (e : instr) (s : infer_result_type) : infer_in
(label c x @ [RefT (Null, ht)]) --> (label c x @ [RefT (NoNull, ht)]), []

| BrOnNonNull x ->
let (nul, ht) = peek_ref 0 s e.at in
let (_nul, ht) = peek_ref 0 s e.at in
let t' = RefT (NoNull, ht) in
require (label c x <> []) e.at
("type mismatch: instruction requires type " ^ string_of_val_type t' ^
" but label has " ^ string_of_result_type (label c x));
let ts0, t1 = Lib.List.split_last (label c x) in
require (match_val_type c.types t' t1) e.at
("type mismatch: instruction requires type " ^ string_of_val_type t' ^
("type mismatch: instruction requires type " ^ string_of_val_type t1 ^
" but label has " ^ string_of_result_type (label c x));
(ts0 @ [RefT (nul, ht)]) --> ts0, []
(ts0 @ [RefT (Null, ht)]) --> ts0, []

| BrOnCast (x, rt) ->
let (nul, ht) = rt in
let (nul', ht') as rt' = peek_ref 0 s e.at in
let (_nul, ht) = rt in
let rt' = peek_ref 0 s e.at in
let tht = top_of_heap_type c.types ht in
require
(match_ref_type c.types rt' (Null, tht)) e.at
Expand All @@ -457,11 +457,11 @@ let rec check_instr (c : context) (e : instr) (s : infer_result_type) : infer_in
require (match_val_type c.types (RefT rt) t1) e.at
("type mismatch: instruction requires type " ^ string_of_ref_type rt ^
" but label has " ^ string_of_result_type (label c x));
(ts0 @ [RefT rt']) --> (ts0 @ [RefT (inv_null nul, ht')]), []
(ts0 @ [RefT rt']) --> (ts0 @ [RefT rt']), []

| BrOnCastFail (x, rt) ->
let (nul, ht) = rt in
let (nul', ht') as rt' = peek_ref 0 s e.at in
let (_nul, ht) = rt in
let rt' = peek_ref 0 s e.at in
let tht = top_of_heap_type c.types ht in
require
(match_ref_type c.types rt' (Null, tht)) e.at
Expand All @@ -472,8 +472,8 @@ let rec check_instr (c : context) (e : instr) (s : infer_result_type) : infer_in
("type mismatch: instruction requires type " ^ string_of_ref_type rt' ^
" but label has " ^ string_of_result_type (label c x));
let ts0, t1 = Lib.List.split_last (label c x) in
require (match_val_type c.types (RefT (inv_null nul, ht')) t1) e.at
("type mismatch: instruction requires type " ^ string_of_ref_type (inv_null nul, ht') ^
require (match_val_type c.types (RefT rt') t1) e.at
("type mismatch: instruction requires type " ^ string_of_ref_type rt' ^
" but label has " ^ string_of_result_type (label c x));
(ts0 @ [RefT rt']) --> (ts0 @ [RefT rt]), []

Expand Down
43 changes: 20 additions & 23 deletions proposals/gc/MVP.md
Original file line number Diff line number Diff line change
Expand Up @@ -597,36 +597,33 @@ In particular, `ref.null` is typed as before, despite the introduction of `none`

Casts work for both abstract and concrete types. In the latter case, they test if the operand's RTT is a sub-RTT of the target type.

* `ref.test null? <heaptype>` checks whether a reference has a given heap type
- `ref.test null? ht : [(ref null ht')] -> [i32]`
- iff `ht <: tht` and `ht' <: tht` where `tht` is a common super type
- if `null?` is present, returns 1 for null, otherwise 0

* `ref.cast null? <heaptype>` tries to convert to a given heap type
- `ref.cast null? ht : [(ref null ht')] -> [(ref null2? ht)]`
- iff `ht <: tht` and `ht' <: tht` where `tht` is a common super type
- and `null? = null2?`
* `ref.test <reftype>` tests whether a reference has a given type
- `ref.test rt : [rt'] -> [i32]`
- iff `rt <: trt` and `rt' <: trt` for some `trt`
- if `rt` contains `null`, returns 1 for null, otherwise 0
Copy link
Member

Choose a reason for hiding this comment

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

What does "rt contains null" mean? Is rt a reference type? Then it would be different in kind from rt'.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, I was using the wrong factorisation. Fixed.


* `ref.cast <reftype>` tries to convert a reference to a given type
- `ref.cast rt : [rt'] -> [rt]`
- iff `rt <: trt` and `rt' <: trt` for some `trt`
- traps if reference is not of requested type
- if `null?` is present, a null operand is passed through, otherwise traps on null
- equivalent to `(block $l (param anyref) (result (ref null? ht)) (br_on_cast null? ht $l) (unreachable))`
- if `rt` contains `null`, a null operand is passed through, otherwise traps on null
- equivalent to `(block $l (param anyref) (result rt) (br_on_cast $l rt) (unreachable))`
Copy link

Choose a reason for hiding this comment

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

I think, this isn't true in all cases. For externref types or funcref types anyref would not be compatible.
is there a way to make it look nice while also applying to funcref types? Otherwise we could add a (for input values of the anyref type hierarchy) or a similar restriction to this line.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, indeed, this ceased to hold when we split the type hierarchy.

Copy link
Member Author

Choose a reason for hiding this comment

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

I pushed a quick fix that simply replaces anyref with trt, as occurring in the typing rule.


* `br_on_cast <labelidx> null? <heaptype>` branches if a reference has a given heap type
- `br_on_cast $l null? ht : [t0* (ref null ht')] -> [t0* (ref null2? ht')]`
* `br_on_cast <labelidx> <reftype>` branches if a reference has a given type
- `br_on_cast $l rt : [t0* rt'] -> [t0* rt']`
- iff `$l : [t0* t']`
- and `(ref null3? ht) <: t'`
- and `ht <: tht` and `ht' <: tht` where `tht` is a common super type
- and `null? = null3? =/= null2?`
- and `rt <: t'`
- and `rt <: trt` and `rt' <: trt` for some `trt`
- passes operand along with branch under target type, plus possible extra args
- if `null?` is present, branches on null, otherwise does not
- if `rt` contains `null`, branches on null, otherwise does not

* `br_on_cast_fail <labelidx> null? <heaptype>` branches if a reference does not have a given heap type
- `br_on_cast_fail $l null? ht : [t0* (ref null ht')] -> [t0* (ref null2? ht)]`
* `br_on_cast_fail <labelidx> <reftype>` branches if a reference does not have a given type
- `br_on_cast_fail $l rt : [t0* rt'] -> [t0* rt]`
- iff `$l : [t0* t']`
- and `(ref null3? ht') <: t'`
- and `ht <: tht` and `ht' <: tht` where `tht` is a common super type
- and `null? = null2? =/= null3?`
- and `rt' <: t'`
- and `rt <: trt` and `rt' <: trt` for some `trt`
- passes operand along with branch, plus possible extra args
- if `null?` is present, does not branch on null, otherwise does
- if `rt` contains `null`, does not branch on null, otherwise does

Note: Cast instructions do _not_ require the operand's source type to be a supertype of the target type. It can also be a "sibling" in the same hierarchy, i.e., they only need to have a common supertype (in practice, it is sufficient to test that both types share the same top heap type.). Allowing so is necessary to maintain subtype substitutability, i.e., the ability to maintain well-typedness when operands are replaced by subtypes.

Expand Down
39 changes: 39 additions & 0 deletions test/core/gc/br_on_cast.wast
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,42 @@

(invoke "test-sub")
(invoke "test-canon")


;; Cases of nullability

(module
(type $t (struct))

(func (param (ref any)) (result (ref $t))
(block (result (ref any)) (br_on_cast 1 $t (local.get 0))) (unreachable)
)
(func (param (ref null any)) (result (ref $t))
(block (result (ref null any)) (br_on_cast 1 $t (local.get 0))) (unreachable)
)
(func (param (ref any)) (result (ref null $t))
(block (result (ref any)) (br_on_cast 1 null $t (local.get 0))) (unreachable)
)
(func (param (ref null any)) (result (ref null $t))
(block (result (ref null any)) (br_on_cast 1 null $t (local.get 0))) (unreachable)
)
)

(assert_invalid
(module
(type $t (struct))
(func (param (ref any)) (result (ref $t))
(block (result (ref any)) (br_on_cast 1 null $t (local.get 0))) (unreachable)
)
)
"type mismatch"
)
(assert_invalid
(module
(type $t (struct))
(func (param (ref null any)) (result (ref $t))
(block (result (ref any)) (br_on_cast 1 $t (local.get 0))) (unreachable)
)
)
"type mismatch"
)
39 changes: 39 additions & 0 deletions test/core/gc/br_on_cast_fail.wast
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,42 @@

(invoke "test-sub")
(invoke "test-canon")


;; Cases of nullability

(module
(type $t (struct))

(func (param (ref any)) (result (ref any))
(block (result (ref $t)) (br_on_cast_fail 1 $t (local.get 0)))
)
(func (param (ref null any)) (result (ref null any))
(block (result (ref $t)) (br_on_cast_fail 1 $t (local.get 0)))
)
(func (param (ref any)) (result (ref any))
(block (result (ref null $t)) (br_on_cast_fail 1 null $t (local.get 0))) (ref.as_non_null)
)
(func (param (ref null any)) (result (ref null any))
(block (result (ref null $t)) (br_on_cast_fail 1 null $t (local.get 0)))
)
)

(assert_invalid
(module
(type $t (struct))
(func (param (ref any)) (result (ref any))
(block (result (ref $t)) (br_on_cast_fail 1 null $t (local.get 0)))
)
)
"type mismatch"
)
(assert_invalid
(module
(type $t (struct))
(func (param (ref null any)) (result (ref any))
(block (result (ref $t)) (br_on_cast_fail 1 $t (local.get 0)))
)
)
"type mismatch"
)