Update the *.wast runner to use the wasmtime API (#690)

* Update the `*.wast` runner to use the `wasmtime` API

This commit migrates the `wasmtime-wast` crate, which executes `*.wast`
test suites, to use the `wasmtime` crate exclusively instead of the raw
support provided by the `wasmtime-*` family of crates.

The primary motivation for this change is to use `*.wast` test to test
the support for interface types, but interface types is only being added
in the `wasmtime` crate for now rather than all throughout the core
crates. This means that without this transition it's much more difficult
to write tests for wasm interface types!

A secondary motivation for this is that it's testing the support we
provide to users through the `wasmtime` crate, since that's the
expectation of what most users would use rather than the raw
`wasmtime-*` crates.

* Run rustfmt

* Fix the multi example

* Handle v128 values in the `wasmtime` crate

Ensure that we allocate 128-bit stack slots instead of 64-bit stack
slots.

* Update to master

* Add comment
This commit is contained in:
Alex Crichton
2019-12-17 13:30:50 -06:00
committed by GitHub
parent 4141daae68
commit d5a2eb397c
13 changed files with 368 additions and 478 deletions

View File

@@ -1,28 +1,25 @@
use crate::spectest::instantiate_spectest;
use anyhow::{bail, Context as _, Result};
use std::convert::TryInto;
use anyhow::{anyhow, bail, Context as _, Result};
use std::collections::HashMap;
use std::path::Path;
use std::str;
use wasmtime_jit::{
ActionError, ActionOutcome, Compiler, Context, Features, InstanceHandle, InstantiationError,
RuntimeValue, SetupError,
};
use wasmtime::*;
/// Translate from a `script::Value` to a `RuntimeValue`.
fn runtime_value(v: &wast::Expression<'_>) -> RuntimeValue {
fn runtime_value(v: &wast::Expression<'_>) -> Result<Val> {
use wast::Instruction::*;
if v.instrs.len() != 1 {
panic!("too many instructions in {:?}", v);
}
match &v.instrs[0] {
I32Const(x) => RuntimeValue::I32(*x),
I64Const(x) => RuntimeValue::I64(*x),
F32Const(x) => RuntimeValue::F32(x.bits),
F64Const(x) => RuntimeValue::F64(x.bits),
V128Const(x) => RuntimeValue::V128(x.to_le_bytes()),
other => panic!("couldn't convert {:?} to a runtime value", other),
bail!("too many instructions in {:?}", v);
}
Ok(match &v.instrs[0] {
I32Const(x) => Val::I32(*x),
I64Const(x) => Val::I64(*x),
F32Const(x) => Val::F32(x.bits),
F64Const(x) => Val::F64(x.bits),
V128Const(x) => Val::V128(u128::from_le_bytes(x.to_le_bytes())),
other => bail!("couldn't convert {:?} to a runtime value", other),
})
}
/// The wast test script language allows modules to be defined and actions
@@ -30,85 +27,131 @@ fn runtime_value(v: &wast::Expression<'_>) -> RuntimeValue {
pub struct WastContext {
/// Wast files have a concept of a "current" module, which is the most
/// recently defined.
current: Option<InstanceHandle>,
current: Option<HostRef<Instance>>,
context: Context,
instances: HashMap<String, HostRef<Instance>>,
store: HostRef<Store>,
spectest: Option<HashMap<&'static str, Extern>>,
}
enum Outcome<T = Vec<Val>> {
Ok(T),
Trap(HostRef<Trap>),
}
impl WastContext {
/// Construct a new instance of `WastContext`.
pub fn new(compiler: Box<Compiler>) -> Self {
pub fn new(store: HostRef<Store>) -> Self {
Self {
current: None,
context: Context::new(compiler),
store,
spectest: None,
instances: HashMap::new(),
}
}
/// Construct a new instance with the given features using the current `Context`
pub fn with_features(self, features: Features) -> Self {
Self {
context: self.context.with_features(features),
..self
fn get_instance(&self, instance_name: Option<&str>) -> Result<HostRef<Instance>> {
match instance_name {
Some(name) => self
.instances
.get(name)
.cloned()
.ok_or_else(|| anyhow!("failed to find instance named `{}`", name)),
None => self
.current
.clone()
.ok_or_else(|| anyhow!("no previous instance found")),
}
}
fn get_instance(&mut self, instance_name: Option<&str>) -> Result<&mut InstanceHandle> {
let instance = if let Some(instance_name) = instance_name {
self.context
.get_instance(instance_name)
.context("failed to fetch instance")?
} else {
self.current
.as_mut()
.ok_or_else(|| anyhow::format_err!("no current instance"))?
fn instantiate(&self, module: &[u8]) -> Result<Outcome<HostRef<Instance>>> {
let module = HostRef::new(Module::new(&self.store, module)?);
let mut imports = Vec::new();
for import in module.borrow().imports() {
if import.module() == "spectest" {
let spectest = self
.spectest
.as_ref()
.ok_or_else(|| anyhow!("spectest module isn't instantiated"))?;
let export = spectest
.get(import.name())
.ok_or_else(|| anyhow!("unknown import `spectest::{}`", import.name()))?;
imports.push(export.clone());
continue;
}
let instance = self
.instances
.get(import.module())
.ok_or_else(|| anyhow!("no module named `{}`", import.module()))?;
let export = instance
.borrow()
.find_export_by_name(import.name())
.ok_or_else(|| anyhow!("unknown import `{}::{}`", import.name(), import.module()))?
.clone();
imports.push(export);
}
let instance = match Instance::new(&self.store, &module, &imports) {
Ok(i) => i,
// FIXME(#683) shouldn't have to reach into runtime crate
Err(e) => {
use wasmtime_runtime::InstantiationError;
let err = e
.chain()
.filter_map(|e| e.downcast_ref::<InstantiationError>())
.next();
if let Some(InstantiationError::StartTrap(msg)) = err {
return Ok(Outcome::Trap(HostRef::new(Trap::new(msg.clone()))));
}
return Err(e);
}
};
Ok(instance)
Ok(Outcome::Ok(HostRef::new(instance)))
}
/// Register "spectest" which is used by the spec testsuite.
pub fn register_spectest(&mut self) -> Result<()> {
let instance = instantiate_spectest()?;
self.context.name_instance("spectest".to_owned(), instance);
self.spectest = Some(instantiate_spectest(&self.store));
Ok(())
}
/// Perform the action portion of a command.
fn perform_execute(&mut self, exec: wast::WastExecute<'_>) -> Result<ActionOutcome> {
fn perform_execute(&mut self, exec: wast::WastExecute<'_>) -> Result<Outcome> {
match exec {
wast::WastExecute::Invoke(invoke) => self.perform_invoke(invoke),
wast::WastExecute::Module(mut module) => {
let binary = module.encode()?;
let result = self.context.instantiate_module(None, &binary);
match result {
Ok(_) => Ok(ActionOutcome::Returned { values: Vec::new() }),
Err(ActionError::Setup(SetupError::Instantiate(
InstantiationError::StartTrap(message),
))) => Ok(ActionOutcome::Trapped { message }),
Err(e) => Err(e.into()),
}
let result = self.instantiate(&binary)?;
Ok(match result {
Outcome::Ok(_) => Outcome::Ok(Vec::new()),
Outcome::Trap(e) => Outcome::Trap(e),
})
}
wast::WastExecute::Get { module, global } => self.get(module.map(|s| s.name()), global),
}
}
fn perform_invoke(&mut self, exec: wast::WastInvoke<'_>) -> Result<ActionOutcome> {
fn perform_invoke(&mut self, exec: wast::WastInvoke<'_>) -> Result<Outcome> {
self.invoke(exec.module.map(|i| i.name()), exec.name, &exec.args)
}
/// Define a module and register it.
fn module(&mut self, instance_name: Option<&str>, module: &[u8]) -> Result<()> {
let index = self
.context
.instantiate_module(instance_name.map(|s| s.to_string()), module)?;
self.current = Some(index);
let instance = match self.instantiate(module)? {
Outcome::Ok(i) => i,
Outcome::Trap(e) => bail!("instantiation failed with: {}", e.borrow().message()),
};
if let Some(name) = instance_name {
self.instances.insert(name.to_string(), instance.clone());
}
self.current = Some(instance);
Ok(())
}
/// Register an instance to make it available for performing actions.
fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
let instance = self.get_instance(name)?.clone();
self.context.name_instance(as_name.to_string(), instance);
self.instances.insert(as_name.to_string(), instance);
Ok(())
}
@@ -118,26 +161,35 @@ impl WastContext {
instance_name: Option<&str>,
field: &str,
args: &[wast::Expression],
) -> Result<ActionOutcome> {
let value_args = args.iter().map(runtime_value).collect::<Vec<_>>();
let mut instance = self.get_instance(instance_name)?.clone();
let result = self
.context
.invoke(&mut instance, field, &value_args)
.with_context(|| format!("failed to invoke `{}`", field))?;
Ok(result)
) -> Result<Outcome> {
let values = args.iter().map(runtime_value).collect::<Result<Vec<_>>>()?;
let instance = self.get_instance(instance_name.as_ref().map(|x| &**x))?;
let instance = instance.borrow();
let export = instance
.find_export_by_name(field)
.ok_or_else(|| anyhow!("no global named `{}`", field))?;
let func = match export {
Extern::Func(f) => f.borrow(),
_ => bail!("export of `{}` wasn't a global", field),
};
Ok(match func.call(&values) {
Ok(result) => Outcome::Ok(result.into()),
Err(e) => Outcome::Trap(e),
})
}
/// Get the value of an exported global from an instance.
fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<ActionOutcome> {
let instance = self
.get_instance(instance_name.as_ref().map(|x| &**x))?
.clone();
let result = self
.context
.get(&instance, field)
.with_context(|| format!("failed to get field `{}`", field))?;
Ok(result)
fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
let instance = self.get_instance(instance_name.as_ref().map(|x| &**x))?;
let instance = instance.borrow();
let export = instance
.find_export_by_name(field)
.ok_or_else(|| anyhow!("no global named `{}`", field))?;
let global = match export {
Extern::Global(g) => g.borrow(),
_ => bail!("export of `{}` wasn't a global", field),
};
Ok(Outcome::Ok(vec![global.get()]))
}
/// Run a wast script from a byte buffer.
@@ -179,16 +231,18 @@ impl WastContext {
exec,
results,
} => match self.perform_execute(exec).with_context(|| context(span))? {
ActionOutcome::Returned { values } => {
Outcome::Ok(values) => {
for (v, e) in values.iter().zip(results.iter().map(runtime_value)) {
if *v == e {
let e = e?;
if values_equal(v, &e)? {
continue;
}
bail!("{}\nexpected {}, got {}", context(span), e, v)
bail!("{}\nexpected {:?}, got {:?}", context(span), e, v)
}
}
ActionOutcome::Trapped { message } => {
bail!("{}\nunexpected trap: {}", context(span), message)
Outcome::Trap(t) => {
let t = t.borrow();
bail!("{}\nunexpected trap: {}", context(span), t.message())
}
},
AssertTrap {
@@ -196,13 +250,12 @@ impl WastContext {
exec,
message,
} => match self.perform_execute(exec).with_context(|| context(span))? {
ActionOutcome::Returned { values } => {
Outcome::Ok(values) => {
bail!("{}\nexpected trap, got {:?}", context(span), values)
}
ActionOutcome::Trapped {
message: trap_message,
} => {
if trap_message.contains(message) {
Outcome::Trap(t) => {
let t = t.borrow();
if t.message().contains(message) {
continue;
}
if cfg!(feature = "lightbeam") {
@@ -217,7 +270,7 @@ impl WastContext {
"{}\nexpected {}, got {}",
context(span),
message,
trap_message
t.message(),
)
}
},
@@ -226,202 +279,169 @@ impl WastContext {
call,
message,
} => match self.perform_invoke(call).with_context(|| context(span))? {
ActionOutcome::Returned { values } => {
Outcome::Ok(values) => {
bail!("{}\nexpected trap, got {:?}", context(span), values)
}
ActionOutcome::Trapped {
message: trap_message,
} => {
if trap_message.contains(message) {
Outcome::Trap(t) => {
let t = t.borrow();
if t.message().contains(message) {
continue;
}
bail!(
"{}\nexpected exhaustion with {}, got {}",
context(span),
message,
trap_message
t.message(),
)
}
},
AssertReturnCanonicalNan { span, invoke } => {
match self.perform_invoke(invoke).with_context(|| context(span))? {
ActionOutcome::Returned { values } => {
Outcome::Ok(values) => {
for v in values.iter() {
match v {
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
bail!("{}\nunexpected integer in NaN test", context(span))
}
RuntimeValue::V128(_) => {
bail!("{}\nunexpected vector in NaN test", context(span))
}
RuntimeValue::F32(x) => {
Val::F32(x) => {
if !is_canonical_f32_nan(x) {
bail!("{}\nexpected canonical NaN", context(span))
}
}
RuntimeValue::F64(x) => {
Val::F64(x) => {
if !is_canonical_f64_nan(x) {
bail!("{}\nexpected canonical NaN", context(span))
}
}
other => bail!("expected float, got {:?}", other),
};
}
}
ActionOutcome::Trapped { message } => {
bail!("{}\nunexpected trap: {}", context(span), message)
Outcome::Trap(t) => {
let t = t.borrow();
bail!("{}\nunexpected trap: {}", context(span), t.message())
}
}
}
AssertReturnCanonicalNanF32x4 { span, invoke } => {
match self.perform_invoke(invoke).with_context(|| context(span))? {
ActionOutcome::Returned { values } => {
Outcome::Ok(values) => {
for v in values.iter() {
match v {
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
bail!("{}\nunexpected integer in NaN test", context(span))
}
RuntimeValue::F32(_) | RuntimeValue::F64(_) => bail!(
"{}\nunexpected scalar float in vector NaN test",
context(span)
),
RuntimeValue::V128(x) => {
for l in 0..4 {
if !is_canonical_f32_nan(&extract_lane_as_u32(x, l)?) {
bail!(
"{}\nexpected f32x4 canonical NaN in lane {}",
context(span),
l
)
}
}
}
let val = match v {
Val::V128(x) => x,
other => bail!("expected v128, got {:?}", other),
};
for l in 0..4 {
if !is_canonical_f32_nan(&extract_lane_as_u32(val, l)?) {
bail!(
"{}\nexpected f32x4 canonical NaN in lane {}",
context(span),
l
)
}
}
}
}
ActionOutcome::Trapped { message } => {
bail!("{}\nunexpected trap: {}", context(span), message)
Outcome::Trap(t) => {
let t = t.borrow();
bail!("{}\nunexpected trap: {}", context(span), t.message())
}
}
}
AssertReturnCanonicalNanF64x2 { span, invoke } => {
match self.perform_invoke(invoke).with_context(|| context(span))? {
ActionOutcome::Returned { values } => {
Outcome::Ok(values) => {
for v in values.iter() {
match v {
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
bail!("{}\nunexpected integer in NaN test", context(span))
}
RuntimeValue::F32(_) | RuntimeValue::F64(_) => bail!(
"{}\nunexpected scalar float in vector NaN test",
context(span)
),
RuntimeValue::V128(x) => {
for l in 0..2 {
if !is_canonical_f64_nan(&extract_lane_as_u64(x, l)?) {
bail!(
"{}\nexpected f64x2 canonical NaN in lane {}",
context(span),
l
)
}
}
}
let val = match v {
Val::V128(x) => x,
other => bail!("expected v128, got {:?}", other),
};
for l in 0..2 {
if !is_canonical_f64_nan(&extract_lane_as_u64(val, l)?) {
bail!(
"{}\nexpected f64x2 canonical NaN in lane {}",
context(span),
l
)
}
}
}
}
ActionOutcome::Trapped { message } => {
bail!("{}\nunexpected trap: {}", context(span), message)
Outcome::Trap(t) => {
let t = t.borrow();
bail!("{}\nunexpected trap: {}", context(span), t.message())
}
}
}
AssertReturnArithmeticNan { span, invoke } => {
match self.perform_invoke(invoke).with_context(|| context(span))? {
ActionOutcome::Returned { values } => {
Outcome::Ok(values) => {
for v in values.iter() {
match v {
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
bail!("{}\nunexpected integer in NaN test", context(span))
}
RuntimeValue::V128(_) => {
bail!("{}\nunexpected vector in NaN test", context(span))
}
RuntimeValue::F32(x) => {
Val::F32(x) => {
if !is_arithmetic_f32_nan(x) {
bail!("{}\nexpected arithmetic NaN", context(span))
}
}
RuntimeValue::F64(x) => {
Val::F64(x) => {
if !is_arithmetic_f64_nan(x) {
bail!("{}\nexpected arithmetic NaN", context(span))
}
}
other => bail!("expected float, got {:?}", other),
};
}
}
ActionOutcome::Trapped { message } => {
bail!("{}\nunexpected trap: {}", context(span), message)
Outcome::Trap(t) => {
let t = t.borrow();
bail!("{}\nunexpected trap: {}", context(span), t.message())
}
}
}
AssertReturnArithmeticNanF32x4 { span, invoke } => {
match self.perform_invoke(invoke).with_context(|| context(span))? {
ActionOutcome::Returned { values } => {
Outcome::Ok(values) => {
for v in values.iter() {
match v {
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
bail!("{}\nunexpected integer in NaN test", context(span))
}
RuntimeValue::F32(_) | RuntimeValue::F64(_) => bail!(
"{}\nunexpected scalar float in vector NaN test",
context(span)
),
RuntimeValue::V128(x) => {
for l in 0..4 {
if !is_arithmetic_f32_nan(&extract_lane_as_u32(x, l)?) {
bail!(
"{}\nexpected f32x4 arithmetic NaN in lane {}",
context(span),
l
)
}
}
}
let val = match v {
Val::V128(x) => x,
other => bail!("expected v128, got {:?}", other),
};
for l in 0..4 {
if !is_arithmetic_f32_nan(&extract_lane_as_u32(val, l)?) {
bail!(
"{}\nexpected f32x4 arithmetic NaN in lane {}",
context(span),
l
)
}
}
}
}
ActionOutcome::Trapped { message } => {
bail!("{}\nunexpected trap: {}", context(span), message)
Outcome::Trap(t) => {
let t = t.borrow();
bail!("{}\nunexpected trap: {}", context(span), t.message())
}
}
}
AssertReturnArithmeticNanF64x2 { span, invoke } => {
match self.perform_invoke(invoke).with_context(|| context(span))? {
ActionOutcome::Returned { values } => {
Outcome::Ok(values) => {
for v in values.iter() {
match v {
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
bail!("{}\nunexpected integer in NaN test", context(span))
}
RuntimeValue::F32(_) | RuntimeValue::F64(_) => bail!(
"{}\nunexpected scalar float in vector NaN test",
context(span)
),
RuntimeValue::V128(x) => {
for l in 0..2 {
if !is_arithmetic_f64_nan(&extract_lane_as_u64(x, l)?) {
bail!(
"{}\nexpected f64x2 arithmetic NaN in lane {}",
context(span),
l
)
}
}
}
let val = match v {
Val::V128(x) => x,
other => bail!("expected v128, got {:?}", other),
};
for l in 0..2 {
if !is_arithmetic_f64_nan(&extract_lane_as_u64(val, l)?) {
bail!(
"{}\nexpected f64x2 arithmetic NaN in lane {}",
context(span),
l
)
}
}
}
}
ActionOutcome::Trapped { message } => {
bail!("{}\nunexpected trap: {}", context(span), message)
Outcome::Trap(t) => {
let t = t.borrow();
bail!("{}\nunexpected trap: {}", context(span), t.message())
}
}
}
@@ -495,7 +515,7 @@ impl WastContext {
)
}
}
AssertReturnFunc { .. } => panic!("need to implement assert_return_func"),
AssertReturnFunc { .. } => bail!("need to implement assert_return_func"),
}
}
@@ -510,14 +530,12 @@ impl WastContext {
}
}
fn extract_lane_as_u32(bytes: &[u8; 16], lane: usize) -> Result<u32> {
let i = lane * 4;
Ok(u32::from_le_bytes(bytes[i..i + 4].try_into()?))
fn extract_lane_as_u32(bytes: &u128, lane: usize) -> Result<u32> {
Ok((*bytes >> (lane * 32)) as u32)
}
fn extract_lane_as_u64(bytes: &[u8; 16], lane: usize) -> Result<u64> {
let i = lane * 8;
Ok(u64::from_le_bytes(bytes[i..i + 8].try_into()?))
fn extract_lane_as_u64(bytes: &u128, lane: usize) -> Result<u64> {
Ok((*bytes >> (lane * 64)) as u64)
}
fn is_canonical_f32_nan(bits: &u32) -> bool {
@@ -535,3 +553,16 @@ fn is_arithmetic_f32_nan(bits: &u32) -> bool {
fn is_arithmetic_f64_nan(bits: &u64) -> bool {
return (bits & 0x0008000000000000) == 0x0008000000000000;
}
fn values_equal(v1: &Val, v2: &Val) -> Result<bool> {
Ok(match (v1, v2) {
(Val::I32(a), Val::I32(b)) => a == b,
(Val::I64(a), Val::I64(b)) => a == b,
// Note that these float comparisons are comparing bits, not float
// values, so we're testing for bit-for-bit equivalence
(Val::F32(a), Val::F32(b)) => a == b,
(Val::F64(a), Val::F64(b)) => a == b,
(Val::V128(a), Val::V128(b)) => a == b,
_ => bail!("don't know how to compare {:?} and {:?} yet", v1, v2),
})
}