Skip to content

Commit

Permalink
Implement the reference types proposal (#155)
Browse files Browse the repository at this point in the history
* Implement the reference types proposal

This commit enables the `reference-types` and `bulk-memory-operations`
proposals from the upstream wasm spec test suite. This involved fully
implementing the reference types proposal, notably passive element
segments as well as a few table-related instructions. A summary of the
changes made here are:

* `TableCopy`, `TableInit`, and `ElemDrop` are now all implemented instructions.
* All element segments now live as an `Element` type rather than being
  embedded directly into a `Table`. This is because they all need an
  index to refer to in `ElemDrop` instructions and such.
* Type checking has been updated to handle subtyping
* The `Funcref` and `Nullref` value types have been added
* Tables now list their element type as a struct field
* Elements now participate in GC passes
* Global initializers are expanded with `ref.func` and `ref.null`

* Run rustfmt

* Checkout submodules on testing

* Update test assertions
  • Loading branch information
alexcrichton committed Feb 5, 2020
1 parent bcec920 commit 121340d
Show file tree
Hide file tree
Showing 23 changed files with 433 additions and 409 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/main.yml
Expand Up @@ -9,7 +9,9 @@ jobs:
matrix:
rust: [stable, beta, nightly]
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v1
with:
submodules: true
- name: Install Rust
run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }}
- name: Install wabt
Expand Down
12 changes: 12 additions & 0 deletions crates/macro/src/lib.rs
Expand Up @@ -561,6 +561,12 @@ fn create_visit(variants: &[WalrusVariant]) -> impl quote::ToTokens {
// ...
}

/// Visit `ElementId`
#[inline]
fn visit_element_id(&mut self, elem: &crate::ElementId) {
// ...
}

/// Visit `Value`.
#[inline]
fn visit_value(&mut self, value: &crate::ir::Value) {
Expand Down Expand Up @@ -645,6 +651,12 @@ fn create_visit(variants: &[WalrusVariant]) -> impl quote::ToTokens {
// ...
}

/// Visit `ElementId`
#[inline]
fn visit_element_id_mut(&mut self, elem: &mut crate::ElementId) {
// ...
}

/// Visit `Value`.
#[inline]
fn visit_value_mut(&mut self, value: &mut crate::ir::Value) {
Expand Down
9 changes: 8 additions & 1 deletion crates/tests/tests/round_trip/elem-segments-1.wat
Expand Up @@ -5,4 +5,11 @@
(export "foo" (table 0))
)

;; CHECK: (elem (;0;) (i32.const 1) 0)
(; CHECK-ALL:
(module
(type (;0;) (func))
(func (;0;) (type 0))
(table (;0;) 1 funcref)
(export "foo" (table 0))
(elem (;0;) (i32.const 1) func 0))
;)
10 changes: 9 additions & 1 deletion crates/tests/tests/round_trip/elem-segments-2.wat
Expand Up @@ -6,4 +6,12 @@
(export "foo" (table 0))
)

;; CHECK: (elem (;0;) (i32.const 1) 0 0)
(; CHECK-ALL:
(module
(type (;0;) (func))
(func (;0;) (type 0))
(table (;0;) 1 funcref)
(export "foo" (table 0))
(elem (;0;) (i32.const 1) func 0)
(elem (;1;) (i32.const 2) func 0))
;)
11 changes: 9 additions & 2 deletions crates/tests/tests/round_trip/elem-segments-3.wat
Expand Up @@ -6,5 +6,12 @@
(export "foo" (table 0))
)

;; CHECK: (elem (;0;) (i32.const 1) 0)
;; NEXT: (elem (;1;) (i32.const 3) 0)
(; CHECK-ALL:
(module
(type (;0;) (func))
(func (;0;) (type 0))
(table (;0;) 1 funcref)
(export "foo" (table 0))
(elem (;0;) (i32.const 1) func 0)
(elem (;1;) (i32.const 3) func 0))
;)
21 changes: 11 additions & 10 deletions crates/tests/tests/round_trip/keep-elem-segments.wat
Expand Up @@ -13,13 +13,14 @@
(export "foo" (func 0))
)

;; CHECK: (module
;; NEXT: (type (;0;) (func))
;; NEXT: (func (;0;) (type 0)
;; NEXT: i32.const 0
;; NEXT: call_indirect (type 0))
;; NEXT: (func (;1;) (type 0))
;; NEXT: (table (;0;) 1 1 funcref)
;; NEXT: (export "foo" (func 0))
;; NEXT: (elem (;0;) (i32.const 0) 1))

(; CHECK-ALL:
(module
(type (;0;) (func))
(func (;0;) (type 0)
i32.const 0
call_indirect (type 0))
(func (;1;) (type 0))
(table (;0;) 1 1 funcref)
(export "foo" (func 0))
(elem (;0;) (i32.const 0) func 1))
;)
30 changes: 1 addition & 29 deletions crates/tests/tests/round_trip/simd.wat
Expand Up @@ -502,25 +502,13 @@
(func $i32x4_trunc_u_f32x4_sat (export "i32x4_trunc_u_f32x4_sat") (param v128) (result v128)
local.get 0
i32x4.trunc_sat_f32x4_u)
(func $i64x2_trunc_s_f64x2_sat (export "i64x2_trunc_s_f64x2_sat") (param v128) (result v128)
local.get 0
i64x2.trunc_sat_f64x2_s)
(func $i64x2_trunc_u_f64x2_sat (export "i64x2_trunc_u_f64x2_sat") (param v128) (result v128)
local.get 0
i64x2.trunc_sat_f64x2_u)

(func $f32x4.convert_i32x4_s (export "f32x4.convert_i32x4_s") (param v128) (result v128)
local.get 0
f32x4.convert_i32x4_s)
(func $f32x4.convert_i32x4_u (export "f32x4.convert_i32x4_u") (param v128) (result v128)
local.get 0
f32x4.convert_i32x4_u)
(func $f64x2.convert_i64x2_s (export "f64x2.convert_i64x2_s") (param v128) (result v128)
local.get 0
f64x2.convert_i64x2_s)
(func $f64x2.convert_i64x2_u (export "f64x2.convert_i64x2_u") (param v128) (result v128)
local.get 0
f64x2.convert_i64x2_u)
)

(; CHECK-ALL:
Expand Down Expand Up @@ -1015,24 +1003,12 @@
(func $i32x4_trunc_u_f32x4_sat (type 10) (param v128) (result v128)
local.get 0
i32x4.trunc_sat_f32x4_u)
(func $i64x2_trunc_s_f64x2_sat (type 10) (param v128) (result v128)
local.get 0
i64x2.trunc_sat_f64x2_s)
(func $i64x2_trunc_u_f64x2_sat (type 10) (param v128) (result v128)
local.get 0
i64x2.trunc_sat_f64x2_u)
(func $f32x4.convert_i32x4_s (type 10) (param v128) (result v128)
local.get 0
f32x4.convert_i32x4_s)
(func $f32x4.convert_i32x4_u (type 10) (param v128) (result v128)
local.get 0
f32x4.convert_i32x4_u)
(func $f64x2.convert_i64x2_s (type 10) (param v128) (result v128)
local.get 0
f64x2.convert_i64x2_s)
(func $f64x2.convert_i64x2_u (type 10) (param v128) (result v128)
local.get 0
f64x2.convert_i64x2_u)
(func $v128.const (type 0) (result v128)
v128.const i32x4 0x00000001 0x00000002 0x00000003 0x00000004)
(memory (;0;) 0)
Expand Down Expand Up @@ -1164,10 +1140,6 @@
(export "f64x2.max" (func $f64x2.max))
(export "i32x4_trunc_s_f32x4_sat" (func $i32x4_trunc_s_f32x4_sat))
(export "i32x4_trunc_u_f32x4_sat" (func $i32x4_trunc_u_f32x4_sat))
(export "i64x2_trunc_s_f64x2_sat" (func $i64x2_trunc_s_f64x2_sat))
(export "i64x2_trunc_u_f64x2_sat" (func $i64x2_trunc_u_f64x2_sat))
(export "f32x4.convert_i32x4_s" (func $f32x4.convert_i32x4_s))
(export "f32x4.convert_i32x4_u" (func $f32x4.convert_i32x4_u))
(export "f64x2.convert_i64x2_s" (func $f64x2.convert_i64x2_s))
(export "f64x2.convert_i64x2_u" (func $f64x2.convert_i64x2_u)))
(export "f32x4.convert_i32x4_u" (func $f32x4.convert_i32x4_u)))
;)
16 changes: 9 additions & 7 deletions crates/tests/tests/round_trip/table-init.wast
Expand Up @@ -5,10 +5,12 @@
(elem (global.get 0) 0)
(export "x" (table 0)))

;; CHECK: (module
;; NEXT: (type
;; NEXT: (import "x" "y" (global (;0;) i32))
;; NEXT: (func
;; NEXT: (table
;; NEXT: (export "x" (table 0))
;; NEXT: (elem (;0;) (global.get 0) 0))
(; CHECK-ALL:
(module
(type (;0;) (func))
(import "x" "y" (global (;0;) i32))
(func (;0;) (type 0))
(table (;0;) 1 funcref)
(export "x" (table 0))
(elem (;0;) (global.get 0) func 0))
;)
25 changes: 16 additions & 9 deletions crates/tests/tests/spec-tests.rs
Expand Up @@ -28,13 +28,8 @@ fn run(wast: &Path) -> Result<(), anyhow::Error> {
Some("sign-extension-ops") => &["--enable-sign-extension"],
Some("multi-value") => &["--enable-multi-value"],
Some("nontrapping-float-to-int-conversions") => &["--enable-saturating-float-to-int"],

// Currently wabt doesn't have support for `ref.host` which is used in
// these tests.
Some("reference-types") => return Ok(()),

// Currently wabt has broken support for `ref.func` initializers
Some("bulk-memory-operations") => return Ok(()),
Some("reference-types") => &["--enable-reference-types", "--enable-bulk-memory"],
Some("bulk-memory-operations") => &["--enable-bulk-memory"],

// TODO: should get threads working
Some("threads") => return Ok(()),
Expand Down Expand Up @@ -76,8 +71,12 @@ fn run(wast: &Path) -> Result<(), anyhow::Error> {
let path = tempdir.path().join(filename);
match command["type"].as_str().unwrap() {
"assert_invalid" | "assert_malformed" => {
if command["text"].as_str().unwrap() == "invalid result arity" {
// These tests are valid with multi-value!
// Skip tests that are actually valid with various in-flight proposals
let text = command["text"].as_str().unwrap();
if text == "invalid result arity"
|| text == "multiple memories"
|| text == "multiple tables"
{
continue;
}
let wasm = fs::read(&path)?;
Expand All @@ -89,6 +88,14 @@ fn run(wast: &Path) -> Result<(), anyhow::Error> {
if message.contains("invalid result arity") {
continue;
}

// MVP wasm considers this tests to fail, but
// reference-types-enhanced wasm considers this test to
// pass. We implement the reference-types semantics, so
// let's go forward with that.
if wast.ends_with("unreached-invalid.wast") && line == 539 {
continue;
}
panic!("wasm parsed when it shouldn't (line {})", line);
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/dot.rs
Expand Up @@ -281,7 +281,6 @@ impl DotNode for Table {
fields.add_field(&[&format!("<b>Table {:?}</b>", self.id())]);
fields.add_field(&["initial", &self.initial.to_string()]);
fields.add_field(&["maximum", &format!("{:?}", self.maximum)]);
fields.add_field(&["kind", &format!("{:?}", self.kind)]);
if self.import.is_some() {
fields.add_field_with_port("import", "import");
}
Expand Down Expand Up @@ -528,7 +527,9 @@ impl DotNode for Element {

fn edges(&self, edges: &mut impl EdgeAggregator) {
for m in self.members.iter() {
edges.add_edge(m);
if let Some(m) = m {
edges.add_edge(m);
}
}
}
}
13 changes: 12 additions & 1 deletion src/init_expr.rs
Expand Up @@ -3,7 +3,7 @@
use crate::emit::{Emit, EmitContext};
use crate::ir::Value;
use crate::parse::IndicesToIds;
use crate::{GlobalId, Result};
use crate::{FunctionId, GlobalId, Result};
use anyhow::bail;

/// A constant which is produced in WebAssembly, typically used in global
Expand All @@ -14,6 +14,10 @@ pub enum InitExpr {
Value(Value),
/// A constant value referenced by the global specified
Global(GlobalId),
/// A null reference
RefNull,
/// A function initializer
RefFunc(FunctionId),
}

impl InitExpr {
Expand All @@ -27,6 +31,8 @@ impl InitExpr {
F64Const { value } => InitExpr::Value(Value::F64(f64::from_bits(value.bits()))),
V128Const { value } => InitExpr::Value(Value::V128(v128_to_u128(&value))),
GlobalGet { global_index } => InitExpr::Global(ids.get_global(global_index)?),
RefNull => InitExpr::RefNull,
RefFunc { function_index } => InitExpr::RefFunc(ids.get_func(function_index)?),
_ => bail!("invalid constant expression"),
};
match reader.read()? {
Expand All @@ -47,6 +53,11 @@ impl Emit for InitExpr {
cx.encoder.byte(0x23); // global.get
cx.encoder.u32(idx);
}
InitExpr::RefNull => cx.encoder.byte(0xd0), // ref.null
InitExpr::RefFunc(id) => {
cx.encoder.byte(0xd2); // ref.func
cx.encoder.u32(cx.indices.get_func_index(id));
}
}
cx.encoder.byte(0x0b); // end
}
Expand Down
28 changes: 27 additions & 1 deletion src/ir/mod.rs
Expand Up @@ -9,7 +9,8 @@ pub use self::traversals::*;

use crate::encode::Encoder;
use crate::{
DataId, FunctionId, GlobalId, LocalFunction, MemoryId, ModuleTypes, TableId, TypeId, ValType,
DataId, ElementId, FunctionId, GlobalId, LocalFunction, MemoryId, ModuleTypes, TableId, TypeId,
ValType,
};
use id_arena::Id;
use std::fmt;
Expand Down Expand Up @@ -556,6 +557,28 @@ pub enum Instr {
#[walrus(skip_visit)]
arg: MemArg,
},

/// `table.init`
TableInit {
/// The table we're copying into.
table: TableId,
/// The element we're getting items from.
elem: ElementId,
},

/// `elem.drop`
ElemDrop {
/// The elem segment to drop
elem: ElementId,
},

/// `table.copy`
TableCopy {
/// The source table
src: TableId,
/// The destination table
dst: TableId,
},
}

/// Argument in `V128Shuffle` of lane indices to select
Expand Down Expand Up @@ -1177,6 +1200,9 @@ impl Instr {
| Instr::V128Shuffle(..)
| Instr::LoadSimd(..)
| Instr::AtomicFence(..)
| Instr::TableInit(..)
| Instr::TableCopy(..)
| Instr::ElemDrop(..)
| Instr::Drop(..) => false,
}
}
Expand Down

0 comments on commit 121340d

Please sign in to comment.