From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../etc/wasm/generate-spectests/wast2js/Cargo.toml | 11 + .../etc/wasm/generate-spectests/wast2js/README.md | 3 + .../wasm/generate-spectests/wast2js/src/convert.rs | 672 +++++++++++++++++++++ .../wasm/generate-spectests/wast2js/src/harness.js | 360 +++++++++++ .../etc/wasm/generate-spectests/wast2js/src/lib.rs | 18 + .../wasm/generate-spectests/wast2js/src/license.js | 14 + .../wasm/generate-spectests/wast2js/src/main.rs | 40 ++ .../etc/wasm/generate-spectests/wast2js/src/out.rs | 180 ++++++ 8 files changed, 1298 insertions(+) create mode 100644 js/src/jit-test/etc/wasm/generate-spectests/wast2js/Cargo.toml create mode 100644 js/src/jit-test/etc/wasm/generate-spectests/wast2js/README.md create mode 100644 js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/convert.rs create mode 100644 js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/harness.js create mode 100644 js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/lib.rs create mode 100644 js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/license.js create mode 100644 js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/main.rs create mode 100644 js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/out.rs (limited to 'js/src/jit-test/etc/wasm/generate-spectests/wast2js') diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/Cargo.toml b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/Cargo.toml new file mode 100644 index 0000000000..4329cc10fb --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "wast2js" +version = "0.1.0" +authors = ["Ryan Hunt "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.19" +wast = "55.0.0" diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/README.md b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/README.md new file mode 100644 index 0000000000..c06210bb04 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/README.md @@ -0,0 +1,3 @@ +# wast2js + +Mozilla specific converter from `.wast` to `.js` for SpiderMonkey jit-tests. diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/convert.rs b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/convert.rs new file mode 100644 index 0000000000..c6516cb0d0 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/convert.rs @@ -0,0 +1,672 @@ +/* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use anyhow::{bail, Context as _, Result}; +use std::fmt::Write; +use std::path::Path; +use std::str; + +use super::out::*; + +const HARNESS: &'static str = include_str!("./harness.js"); +const LICENSE: &'static str = include_str!("./license.js"); + +pub fn harness() -> String { + HARNESS.to_string() +} + +pub fn convert>(path: P, wast: &str) -> Result { + let filename = path.as_ref(); + let adjust_wast = |mut err: wast::Error| { + err.set_path(filename); + err.set_text(wast); + err + }; + + let mut lexer = wast::lexer::Lexer::new(wast); + // The 'names.wast' spec test has confusable unicode -- disable detection. + lexer.allow_confusing_unicode(filename.ends_with("names.wast")); + let buf = wast::parser::ParseBuffer::new_with_lexer(lexer).map_err(adjust_wast)?; + let ast = wast::parser::parse::(&buf).map_err(adjust_wast)?; + + let mut out = String::new(); + + writeln!(&mut out, "{}", LICENSE)?; + writeln!(&mut out, "// {}", filename.display())?; + + let mut current_instance: Option = None; + + for directive in ast.directives { + let sp = directive.span(); + let (line, col) = sp.linecol_in(wast); + writeln!(&mut out, "")?; + convert_directive( + directive, + &mut current_instance, + filename, + line, + col, + wast, + &mut out, + ) + .with_context(|| { + format!( + "failed to convert directive on {}:{}:{}", + filename.display(), + line + 1, + col + ) + })?; + } + + Ok(out) +} + +fn convert_directive( + directive: wast::WastDirective, + current_instance: &mut Option, + filename: &Path, + line: usize, + col: usize, + wast: &str, + out: &mut String, +) -> Result<()> { + use wast::WastDirective::*; + + if col == 1 { + writeln!(out, "// {}:{}", filename.display(), line + 1)?; + } else { + writeln!(out, "// {}:{}:{}", filename.display(), line + 1, col)?; + } + match directive { + Wat(wast::QuoteWat::Wat(wast::Wat::Module(module))) => { + let next_instance = current_instance.map(|x| x + 1).unwrap_or(0); + let module_text = module_to_js_string(&module, wast)?; + + writeln!( + out, + "let ${} = instantiate(`{}`);", + next_instance, module_text + )?; + if let Some(id) = module.id { + writeln!( + out, + "register(${}, {});", + next_instance, + format!("`{}`", escape_template_string(id.name())) + )?; + } + + *current_instance = Some(next_instance); + } + Wat(_) => { + write!(out, "// unsupported quote wat")?; + } + Register { + span: _, + name, + module, + } => { + let instanceish = module + .map(|x| format!("`{}`", escape_template_string(x.name()))) + .unwrap_or_else(|| format!("${}", current_instance.unwrap())); + + writeln!( + out, + "register({}, `{}`);", + instanceish, + escape_template_string(name) + )?; + } + Invoke(i) => { + let invoke_node = invoke_to_js(current_instance, i)?; + writeln!(out, "{};", invoke_node.output(0))?; + } + AssertReturn { + span: _, + exec, + results, + } => { + let exec_node = execute_to_js(current_instance, exec, wast)?; + let expected_node = to_js_value_array(&results, assert_expression_to_js_value)?; + writeln!( + out, + "{};", + JSNode::Assert { + name: "assert_return".to_string(), + exec: exec_node, + expected: expected_node, + } + .output(0), + )?; + } + AssertTrap { + span: _, + exec, + message, + } => { + let exec_node = execute_to_js(current_instance, exec, wast)?; + let expected_node = Box::new(JSNode::Raw(format!( + "`{}`", + escape_template_string(message) + ))); + writeln!( + out, + "{};", + JSNode::Assert { + name: "assert_trap".to_string(), + exec: exec_node, + expected: expected_node, + } + .output(0), + )?; + } + AssertExhaustion { + span: _, + call, + message, + } => { + let exec_node = invoke_to_js(current_instance, call)?; + let expected_node = Box::new(JSNode::Raw(format!( + "`{}`", + escape_template_string(message) + ))); + writeln!( + out, + "{};", + JSNode::Assert { + name: "assert_exhaustion".to_string(), + exec: exec_node, + expected: expected_node, + } + .output(0), + )?; + } + AssertInvalid { + span: _, + module, + message, + } => { + let text = match module { + wast::QuoteWat::Wat(wast::Wat::Module(m)) => module_to_js_string(&m, wast)?, + wast::QuoteWat::QuoteModule(_, source) => quote_module_to_js_string(source)?, + other => bail!("unsupported {:?} in assert_invalid", other), + }; + let exec = Box::new(JSNode::Raw(format!("instantiate(`{}`)", text))); + let expected_node = Box::new(JSNode::Raw(format!( + "`{}`", + escape_template_string(message) + ))); + writeln!( + out, + "{};", + JSNode::Assert { + name: "assert_invalid".to_string(), + exec: exec, + expected: expected_node, + } + .output(0), + )?; + } + AssertMalformed { + module, + span: _, + message, + } => { + let text = match module { + wast::QuoteWat::Wat(wast::Wat::Module(m)) => module_to_js_string(&m, wast)?, + wast::QuoteWat::QuoteModule(_, source) => quote_module_to_js_string(source)?, + other => bail!("unsupported {:?} in assert_malformed", other), + }; + let exec = Box::new(JSNode::Raw(format!("instantiate(`{}`)", text))); + let expected_node = Box::new(JSNode::Raw(format!( + "`{}`", + escape_template_string(message) + ))); + writeln!( + out, + "{};", + JSNode::Assert { + name: "assert_malformed".to_string(), + exec: exec, + expected: expected_node, + } + .output(0), + )?; + } + AssertUnlinkable { + span: _, + module, + message, + } => { + let text = match module { + wast::Wat::Module(module) => module_to_js_string(&module, wast)?, + other => bail!("unsupported {:?} in assert_unlinkable", other), + }; + let exec = Box::new(JSNode::Raw(format!("instantiate(`{}`)", text))); + let expected_node = Box::new(JSNode::Raw(format!( + "`{}`", + escape_template_string(message) + ))); + writeln!( + out, + "{};", + JSNode::Assert { + name: "assert_unlinkable".to_string(), + exec: exec, + expected: expected_node, + } + .output(0), + )?; + } + AssertException { span: _, exec } => { + // This assert doesn't have a second parameter, so we don't bother + // formatting it using an Assert node. + let exec_node = execute_to_js(current_instance, exec, wast)?; + writeln!(out, "assert_exception(() => {});", exec_node.output(0))?; + } + } + + Ok(()) +} + +fn escape_template_string(text: &str) -> String { + let mut escaped = String::new(); + for c in text.chars() { + match c { + '$' => escaped.push_str("$$"), + '\\' => escaped.push_str("\\\\"), + '`' => escaped.push_str("\\`"), + c if c.is_ascii_control() && c != '\n' && c != '\t' => { + escaped.push_str(&format!("\\x{:02x}", c as u32)) + } + c if !c.is_ascii() => escaped.push_str(&c.escape_unicode().to_string()), + c => escaped.push(c), + } + } + escaped +} + +fn span_to_offset(span: wast::token::Span, text: &str) -> Result { + let (span_line, span_col) = span.linecol_in(text); + let mut cur = 0; + // Use split_terminator instead of lines so that if there is a `\r`, + // it is included in the offset calculation. The `+1` values below + // account for the `\n`. + for (i, line) in text.split_terminator('\n').enumerate() { + if span_line == i { + assert!(span_col < line.len()); + return Ok(cur + span_col); + } + cur += line.len() + 1; + } + bail!("invalid line/col"); +} + +fn closed_module(module: &str) -> Result<&str> { + enum State { + Module, + QStr, + EscapeQStr, + } + + let mut i = 0; + let mut level = 1; + let mut state = State::Module; + + let mut chars = module.chars(); + while level != 0 { + let next = match chars.next() { + Some(next) => next, + None => bail!("was unable to close module"), + }; + match state { + State::Module => match next { + '(' => level += 1, + ')' => level -= 1, + '"' => state = State::QStr, + _ => {} + }, + State::QStr => match next { + '"' => state = State::Module, + '\\' => state = State::EscapeQStr, + _ => {} + }, + State::EscapeQStr => match next { + _ => state = State::QStr, + }, + } + i += next.len_utf8(); + } + return Ok(&module[0..i]); +} + +fn module_to_js_string(module: &wast::core::Module, wast: &str) -> Result { + let offset = span_to_offset(module.span, wast)?; + let opened_module = &wast[offset..]; + if !opened_module.starts_with("module") { + return Ok(escape_template_string(opened_module)); + } + Ok(escape_template_string(&format!( + "({}", + closed_module(opened_module)? + ))) +} + +fn quote_module_to_js_string(quotes: Vec<(wast::token::Span, &[u8])>) -> Result { + let mut text = String::new(); + for (_, src) in quotes { + text.push_str(str::from_utf8(src)?); + text.push_str(" "); + } + let escaped = escape_template_string(&text); + Ok(escaped) +} + +fn invoke_to_js(current_instance: &Option, i: wast::WastInvoke) -> Result> { + let instanceish = i + .module + .map(|x| format!("`{}`", escape_template_string(x.name()))) + .unwrap_or_else(|| format!("${}", current_instance.unwrap())); + let body = to_js_value_array(&i.args, arg_to_js_value)?; + + Ok(Box::new(JSNode::Invoke { + instance: instanceish, + name: escape_template_string(i.name), + body: body, + })) +} + +fn execute_to_js( + current_instance: &Option, + exec: wast::WastExecute, + wast: &str, +) -> Result> { + match exec { + wast::WastExecute::Invoke(invoke) => invoke_to_js(current_instance, invoke), + wast::WastExecute::Wat(module) => { + let text = match module { + wast::Wat::Module(module) => module_to_js_string(&module, wast)?, + other => bail!("unsupported {:?} at execute_to_js", other), + }; + Ok(Box::new(JSNode::Raw(format!("instantiate(`{}`)", text)))) + } + wast::WastExecute::Get { module, global } => { + let instanceish = module + .map(|x| format!("`{}`", escape_template_string(x.name()))) + .unwrap_or_else(|| format!("${}", current_instance.unwrap())); + Ok(Box::new(JSNode::Raw(format!( + "get({}, `{}`)", + instanceish, global + )))) + } + } +} + +fn to_js_value_array Result>(vals: &[T], func: F) -> Result> { + let mut value_nodes: Vec> = vec![]; + for val in vals { + let converted_value = (func)(val)?; + value_nodes.push(Box::new(JSNode::Raw(converted_value))); + } + + Ok(Box::new(JSNode::Array(value_nodes))) +} + +fn to_js_value_array_string Result>(vals: &[T], func: F) -> Result { + let array = to_js_value_array(vals, func)?; + Ok(array.output(NOWRAP)) +} + +fn f32_needs_bits(a: f32) -> bool { + if a.is_infinite() { + return false; + } + return a.is_nan() + || ((a as f64) as f32).to_bits() != a.to_bits() + || (format!("{:.}", a).parse::().unwrap() as f32).to_bits() != a.to_bits(); +} + +fn f64_needs_bits(a: f64) -> bool { + return a.is_nan(); +} + +fn f32x4_needs_bits(a: &[wast::token::Float32; 4]) -> bool { + a.iter().any(|x| { + let as_f32 = f32::from_bits(x.bits); + f32_needs_bits(as_f32) + }) +} + +fn f64x2_needs_bits(a: &[wast::token::Float64; 2]) -> bool { + a.iter().any(|x| { + let as_f64 = f64::from_bits(x.bits); + f64_needs_bits(as_f64) + }) +} + +fn f32_to_js_value(val: f32) -> String { + if val == f32::INFINITY { + format!("Infinity") + } else if val == f32::NEG_INFINITY { + format!("-Infinity") + } else if val.is_sign_negative() && val == 0f32 { + format!("-0") + } else { + format!("{:.}", val) + } +} + +fn f64_to_js_value(val: f64) -> String { + if val == f64::INFINITY { + format!("Infinity") + } else if val == f64::NEG_INFINITY { + format!("-Infinity") + } else if val.is_sign_negative() && val == 0f64 { + format!("-0") + } else { + format!("{:.}", val) + } +} + +fn float32_to_js_value(val: &wast::token::Float32) -> String { + let as_f32 = f32::from_bits(val.bits); + if f32_needs_bits(as_f32) { + format!( + "bytes(\"f32\", {})", + to_js_value_array_string(&val.bits.to_le_bytes(), |x| Ok(format!("0x{:x}", x))) + .unwrap(), + ) + } else { + format!("value(\"f32\", {})", f32_to_js_value(as_f32)) + } +} + +fn float64_to_js_value(val: &wast::token::Float64) -> String { + let as_f64 = f64::from_bits(val.bits); + if f64_needs_bits(as_f64) { + format!( + "bytes(\"f64\", {})", + to_js_value_array_string(&val.bits.to_le_bytes(), |x| Ok(format!("0x{:x}", x))) + .unwrap(), + ) + } else { + format!("value(\"f64\", {})", f64_to_js_value(as_f64)) + } +} + +fn f32_pattern_to_js_value(pattern: &wast::core::NanPattern) -> String { + use wast::core::NanPattern::*; + match pattern { + CanonicalNan => format!("`canonical_nan`"), + ArithmeticNan => format!("`arithmetic_nan`"), + Value(x) => float32_to_js_value(x), + } +} + +fn f64_pattern_to_js_value(pattern: &wast::core::NanPattern) -> String { + use wast::core::NanPattern::*; + match pattern { + CanonicalNan => format!("`canonical_nan`"), + ArithmeticNan => format!("`arithmetic_nan`"), + Value(x) => float64_to_js_value(x), + } +} + +fn return_value_to_js_value(v: &wast::core::WastRetCore<'_>) -> Result { + use wast::core::WastRetCore::*; + Ok(match v { + I32(x) => format!("value(\"i32\", {})", x), + I64(x) => format!("value(\"i64\", {}n)", x), + F32(x) => f32_pattern_to_js_value(x), + F64(x) => f64_pattern_to_js_value(x), + RefNull(x) => match x { + Some(wast::core::HeapType::Func) => format!("value('anyfunc', null)"), + Some(wast::core::HeapType::Extern) => format!("value('externref', null)"), + other => bail!( + "couldn't convert ref.null {:?} to a js assertion value", + other + ), + }, + RefExtern(x) => format!("value('externref', externref({}))", x), + V128(x) => { + use wast::core::V128Pattern::*; + match x { + I8x16(elements) => format!( + "i8x16({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}", x)))?, + ), + I16x8(elements) => format!( + "i16x8({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}", x)))?, + ), + I32x4(elements) => format!( + "i32x4({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}", x)))?, + ), + I64x2(elements) => format!( + "i64x2({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}n", x)))?, + ), + F32x4(elements) => { + let elements: Vec = elements + .iter() + .map(|x| f32_pattern_to_js_value(x)) + .collect(); + let elements = strings_to_raws(elements); + JSNode::Call { + name: "new F32x4Pattern".to_string(), + args: elements, + } + .output(0) + } + F64x2(elements) => { + let elements: Vec = elements + .iter() + .map(|x| f64_pattern_to_js_value(x)) + .collect(); + let elements = strings_to_raws(elements); + JSNode::Call { + name: "new F64x2Pattern".to_string(), + args: elements, + } + .output(0) + } + } + } + Either(v) => { + let args = v + .iter() + .map(|v| return_value_to_js_value(v).unwrap()) + .collect(); + let args = strings_to_raws(args); + JSNode::Call { + name: "either".to_string(), + args: args, + } + .output(0) + } + other => bail!("couldn't convert Core({:?}) to a js assertion value", other), + }) +} + +fn assert_expression_to_js_value(v: &wast::WastRet<'_>) -> Result { + use wast::WastRet::*; + + Ok(match &v { + Core(x) => return_value_to_js_value(x)?, + other => bail!("couldn't convert {:?} to a js assertion value", other), + }) +} + +fn arg_to_js_value(v: &wast::WastArg<'_>) -> Result { + use wast::core::WastArgCore::*; + use wast::WastArg::Core; + + Ok(match &v { + Core(I32(x)) => format!("{}", *x), + Core(I64(x)) => format!("{}n", *x), + Core(F32(x)) => float32_to_js_value(x), + Core(F64(x)) => float64_to_js_value(x), + Core(V128(x)) => { + use wast::core::V128Const::*; + match x { + I8x16(elements) => format!( + "i8x16({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}", x)))?, + ), + I16x8(elements) => format!( + "i16x8({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}", x)))?, + ), + I32x4(elements) => format!( + "i32x4({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}", x)))?, + ), + I64x2(elements) => format!( + "i64x2({})", + to_js_value_array_string(elements, |x| Ok(format!("0x{:x}n", x)))?, + ), + F32x4(elements) => { + if f32x4_needs_bits(elements) { + let bytes = + to_js_value_array(&x.to_le_bytes(), |x| Ok(format!("0x{:x}", x)))?; + format!("bytes('v128', {})", bytes.output(0)) + } else { + let vals = to_js_value_array(elements, |x| { + Ok(f32_to_js_value(f32::from_bits(x.bits))) + })?; + format!("f32x4({})", vals.output(0)) + } + } + F64x2(elements) => { + if f64x2_needs_bits(elements) { + let bytes = + to_js_value_array(&x.to_le_bytes(), |x| Ok(format!("0x{:x}", x)))?; + format!("bytes('v128', {})", bytes.output(0)) + } else { + let vals = to_js_value_array(elements, |x| { + Ok(f64_to_js_value(f64::from_bits(x.bits))) + })?; + format!("f64x2({})", vals.output(0)) + } + } + } + } + Core(RefNull(_)) => format!("null"), + Core(RefExtern(x)) => format!("externref({})", x), + other => bail!("couldn't convert {:?} to a js value", other), + }) +} diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/harness.js b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/harness.js new file mode 100644 index 0000000000..ba615e05dd --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/harness.js @@ -0,0 +1,360 @@ +"use strict"; + +/* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +if (!wasmIsSupported()) { + quit(); +} + +function bytes(type, bytes) { + var typedBuffer = new Uint8Array(bytes); + return wasmGlobalFromArrayBuffer(type, typedBuffer.buffer); +} +function value(type, value) { + return new WebAssembly.Global({ + value: type, + mutable: false, + }, value); +} + +function i8x16(elements) { + let typedBuffer = new Uint8Array(elements); + return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); +} +function i16x8(elements) { + let typedBuffer = new Uint16Array(elements); + return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); +} +function i32x4(elements) { + let typedBuffer = new Uint32Array(elements); + return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); +} +function i64x2(elements) { + let typedBuffer = new BigUint64Array(elements); + return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); +} +function f32x4(elements) { + let typedBuffer = new Float32Array(elements); + return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); +} +function f64x2(elements) { + let typedBuffer = new Float64Array(elements); + return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer); +} + +function either(...arr) { + return new EitherVariants(arr); +} + +class F32x4Pattern { + constructor(x, y, z, w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } +} + +class F64x2Pattern { + constructor(x, y) { + this.x = x; + this.y = y; + } +} + +let externrefs = {}; +let externsym = Symbol("externref"); +function externref(s) { + if (!(s in externrefs)) externrefs[s] = { [externsym]: s }; + return externrefs[s]; +} +function is_externref(x) { + return (x !== null && externsym in x) ? 1 : 0; +} +function is_funcref(x) { + return typeof x === "function" ? 1 : 0; +} +function eq_externref(x, y) { + return x === y ? 1 : 0; +} +function eq_funcref(x, y) { + return x === y ? 1 : 0; +} + +let spectest = { + externref: externref, + is_externref: is_externref, + is_funcref: is_funcref, + eq_externref: eq_externref, + eq_funcref: eq_funcref, + print: console.log.bind(console), + print_i32: console.log.bind(console), + print_i32_f32: console.log.bind(console), + print_f64_f64: console.log.bind(console), + print_f32: console.log.bind(console), + print_f64: console.log.bind(console), + global_i32: 666, + global_i64: 666n, + global_f32: 666, + global_f64: 666, + table: new WebAssembly.Table({ + initial: 10, + maximum: 20, + element: "anyfunc", + }), + memory: new WebAssembly.Memory({ initial: 1, maximum: 2 }), +}; + +let linkage = { + spectest, +}; + +function getInstance(instanceish) { + if (typeof instanceish === "string") { + assertEq( + instanceish in linkage, + true, + `'${instanceish}'' must be registered`, + ); + return linkage[instanceish]; + } + return instanceish; +} + +function instantiate(source) { + let bytecode = wasmTextToBinary(source); + let module = new WebAssembly.Module(bytecode); + let instance = new WebAssembly.Instance(module, linkage); + return instance.exports; +} + +function register(instanceish, name) { + linkage[name] = getInstance(instanceish); +} + +function invoke(instanceish, field, params) { + let func = getInstance(instanceish)[field]; + assertEq(func instanceof Function, true, "expected a function"); + return wasmLosslessInvoke(func, ...params); +} + +function get(instanceish, field) { + let global = getInstance(instanceish)[field]; + assertEq( + global instanceof WebAssembly.Global, + true, + "expected a WebAssembly.Global", + ); + return global; +} + +function assert_trap(thunk, message) { + try { + thunk(); + assertEq("normal return", "trap"); + } catch (err) { + assertEq( + err instanceof WebAssembly.RuntimeError, + true, + "expected trap", + ); + } +} + +let StackOverflow; +try { + (function f() { + 1 + f(); + })(); +} catch (e) { + StackOverflow = e.constructor; +} +function assert_exhaustion(thunk, message) { + try { + thunk(); + assertEq("normal return", "exhaustion"); + } catch (err) { + assertEq( + err instanceof StackOverflow, + true, + "expected exhaustion", + ); + } +} + +function assert_invalid(thunk, message) { + try { + thunk(); + assertEq("valid module", "invalid module"); + } catch (err) { + assertEq( + err instanceof WebAssembly.LinkError || + err instanceof WebAssembly.CompileError, + true, + "expected an invalid module", + ); + } +} + +function assert_unlinkable(thunk, message) { + try { + thunk(); + assertEq(true, false, "expected an unlinkable module"); + } catch (err) { + assertEq( + err instanceof WebAssembly.LinkError || + err instanceof WebAssembly.CompileError, + true, + "expected an unlinkable module", + ); + } +} + +function assert_malformed(thunk, message) { + try { + thunk(); + assertEq("valid module", "malformed module"); + } catch (err) { + assertEq( + err instanceof TypeError || + err instanceof SyntaxError || + err instanceof WebAssembly.CompileError || + err instanceof WebAssembly.LinkError, + true, + `expected a malformed module`, + ); + } +} + +function assert_exception(thunk) { + let thrown = false; + try { + thunk(); + } catch (err) { + thrown = true; + } + assertEq(thrown, true, "expected an exception to be thrown"); +} + +function assert_return(thunk, expected) { + let results = thunk(); + + if (results === undefined) { + results = []; + } else if (!Array.isArray(results)) { + results = [results]; + } + if (!Array.isArray(expected)) { + expected = [expected]; + } + + if (!compareResults(results, expected)) { + let got = results.map((x) => formatResult(x)).join(", "); + let wanted = expected.map((x) => formatExpected(x)).join(", "); + assertEq( + `[${got}]`, + `[${wanted}]`, + ); + assertEq(true, false, `${got} !== ${wanted}`); + } +} + +function formatResult(result) { + if (typeof (result) === "object") { + return wasmGlobalToString(result); + } else { + return `${result}`; + } +} + +function formatExpected(expected) { + if ( + expected === `f32_canonical_nan` || + expected === `f32_arithmetic_nan` || + expected === `f64_canonical_nan` || + expected === `f64_arithmetic_nan` + ) { + return expected; + } else if (expected instanceof F32x4Pattern) { + return `f32x4(${formatExpected(expected.x)}, ${ + formatExpected(expected.y) + }, ${formatExpected(expected.z)}, ${formatExpected(expected.w)})`; + } else if (expected instanceof F64x2Pattern) { + return `f64x2(${formatExpected(expected.x)}, ${ + formatExpected(expected.y) + })`; + } else if (expected instanceof EitherVariants) { + return expected.formatExpected(); + } else if (typeof (expected) === "object") { + return wasmGlobalToString(expected); + } else { + throw new Error("unknown expected result"); + } +} + +class EitherVariants { + constructor(arr) { + this.arr = arr; + } + matches(v) { + return this.arr.some((e) => compareResult(v, e)); + } + formatExpected() { + return `either(${this.arr.map(formatExpected).join(", ")})`; + } +} + +function compareResults(results, expected) { + if (results.length !== expected.length) { + return false; + } + for (let i in results) { + if (expected[i] instanceof EitherVariants) { + return expected[i].matches(results[i]); + } + if (!compareResult(results[i], expected[i])) { + return false; + } + } + return true; +} + +function compareResult(result, expected) { + if ( + expected === `canonical_nan` || + expected === `arithmetic_nan` + ) { + return wasmGlobalIsNaN(result, expected); + } else if (expected instanceof F32x4Pattern) { + return compareResult( + wasmGlobalExtractLane(result, "f32x4", 0), + expected.x, + ) && + compareResult(wasmGlobalExtractLane(result, "f32x4", 1), expected.y) && + compareResult(wasmGlobalExtractLane(result, "f32x4", 2), expected.z) && + compareResult(wasmGlobalExtractLane(result, "f32x4", 3), expected.w); + } else if (expected instanceof F64x2Pattern) { + return compareResult( + wasmGlobalExtractLane(result, "f64x2", 0), + expected.x, + ) && + compareResult(wasmGlobalExtractLane(result, "f64x2", 1), expected.y); + } else if (typeof (expected) === "object") { + return wasmGlobalsEqual(result, expected); + } else { + throw new Error("unknown expected result"); + } +} diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/lib.rs b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/lib.rs new file mode 100644 index 0000000000..b62bcbfaea --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/lib.rs @@ -0,0 +1,18 @@ +/* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +mod convert; +mod out; +pub use convert::*; diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/license.js b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/license.js new file mode 100644 index 0000000000..12d51bd702 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/license.js @@ -0,0 +1,14 @@ +/* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/main.rs b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/main.rs new file mode 100644 index 0000000000..d5d1118e7e --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/main.rs @@ -0,0 +1,40 @@ +/* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use anyhow::{Context as _, Result}; +use std::env; +use std::path::Path; + +mod convert; +mod out; + +fn main() -> Result<()> { + let files = env::args().collect::>(); + for path in &files[1..] { + let source = + std::fs::read_to_string(path).with_context(|| format!("failed to read `{}`", path))?; + + let mut full_script = String::new(); + full_script.push_str(&convert::harness()); + full_script.push_str(&convert::convert(path, &source)?); + + std::fs::write( + Path::new(path).with_extension("js").file_name().unwrap(), + &full_script, + )?; + } + + Ok(()) +} diff --git a/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/out.rs b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/out.rs new file mode 100644 index 0000000000..1d47365f19 --- /dev/null +++ b/js/src/jit-test/etc/wasm/generate-spectests/wast2js/src/out.rs @@ -0,0 +1,180 @@ +/* Copyright 2022 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/// This module has a bare-bones "syntax tree" that helps us output more readable JS code in our +/// tests. This is a lot cheaper and faster than running a full JS formatter on the generated code. +/// The tree only has node types for things that matter to formatting; most things in the output +/// use the Raw type, which is just a plain old string. + +pub const LINE_WIDTH: usize = 80; // the default line width to try to meet (will not be met perfectly) +pub const NOWRAP: usize = 1000; // use for line_remaining when you don't want to wrap text + +pub enum JSNode { + Assert { + name: String, + exec: Box, + expected: Box, + }, + Invoke { + instance: String, + name: String, + body: Box, + }, + Call { + name: String, + args: Vec>, + }, + Array(Vec>), + Raw(String), +} + +impl JSNode { + /// Converts a node to JavaScript code. line_remaining is the number of characters remaining on + /// the line, used for line-wrapping purposes; if zero, the default LINE_WIDTH will be used. + pub fn output(&self, line_remaining: usize) -> String { + let line_remaining = if line_remaining == 0 { + LINE_WIDTH + } else { + line_remaining + }; + match self { + Self::Assert { + name, + exec, + expected, + } => { + if self.len() > line_remaining { + format!( + "{}(\n{}\n)", + name, + indent(format!( + "() => {},\n{},", + exec.output(line_remaining - 8), + expected.output(line_remaining - 2), + )), + ) + } else { + format!( + "{}(() => {}, {})", + name, + exec.output(NOWRAP), + expected.output(NOWRAP), + ) + } + } + Self::Invoke { + instance, + name, + body, + } => { + let body_string = + body.output(line_remaining - "invoke".len() - instance.len() - name.len()); + format!("invoke({}, `{}`, {})", instance, name, body_string) + } + Self::Call { name, args } => { + if self.len() > line_remaining { + let arg_strings: Vec = args + .iter() + .map(|arg| arg.output(line_remaining - 2)) + .collect(); + format!("{}(\n{},\n)", name, indent(arg_strings.join(",\n"))) + } else { + let arg_strings: Vec = + args.iter().map(|arg| arg.output(NOWRAP)).collect(); + format!("{}({})", name, arg_strings.join(", ")) + } + } + Self::Array(values) => { + if self.len() > line_remaining { + let value_strings = output_nodes(&values, 70); + format!("[\n{},\n]", indent(value_strings.join(",\n"))) + } else { + let value_strings = output_nodes(&values, 80); + format!("[{}]", value_strings.join(", ")) + } + } + Self::Raw(val) => val.to_string(), + } + } + + /// A rough estimate of the string length of the node. Used as a heuristic to know when we + /// should wrap text. + fn len(&self) -> usize { + match self { + Self::Assert { + name, + exec, + expected, + } => name.len() + exec.len() + expected.len(), + Self::Invoke { + instance, + name, + body, + } => instance.len() + name.len() + body.len(), + Self::Call { name, args } => { + let mut args_len: usize = 0; + for node in args { + args_len += node.len() + ", ".len(); + } + name.len() + args_len + } + Self::Array(nodes) => { + let mut content_len: usize = 0; + for node in nodes { + content_len += node.len() + ", ".len(); + } + content_len + } + Self::Raw(s) => s.len(), + } + } +} + +pub fn output_nodes(nodes: &Vec>, line_width_per_node: usize) -> Vec { + nodes + .iter() + .map(|node| node.output(line_width_per_node)) + .collect() +} + +pub fn strings_to_raws(strs: Vec) -> Vec> { + let mut res: Vec> = vec![]; + for s in strs { + res.push(Box::new(JSNode::Raw(s))); + } + res +} + +fn indent(s: String) -> String { + let mut result = String::new(); + let mut do_indent = true; + for (i, line) in s.lines().enumerate() { + if i > 0 { + result.push('\n'); + } + if line.chars().any(|c| !c.is_whitespace()) { + if do_indent { + result.push_str(" "); + } + result.push_str(line); + + // An odd number of backticks in the line means we are entering or exiting a raw string. + if line.matches("`").count() % 2 == 1 { + do_indent = !do_indent + } + } + } + result +} -- cgit v1.2.3