diff --git a/Cargo.toml b/Cargo.toml index b004b39b..5154106a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ wasm-bindgen-test-crate-b = { path = 'tests/crates/b', version = '0.1' } [workspace] members = [ + "benchmarks", "crates/cli", "crates/js-sys", "crates/macro/ui-tests", diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7dc20ca2..dbd4c449 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -144,15 +144,9 @@ jobs: steps: - template: ci/azure-install-rust.yml - template: ci/azure-install-sccache.yml + - template: ci/azure-install-wasm-pack.yml - script: mv _package.json package.json && npm install && rm package.json displayName: "run npm install" - - script: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f - displayName: "install wasm-pack" - - script: | - set -ex - cargo build -p wasm-bindgen-cli - ln -snf `pwd`/target/debug/wasm-bindgen $HOME/.cargo/bin/wasm-bindgen - displayName: "install wasm-bindgen for `wasm-pack` to use" - script: | for dir in `ls examples | grep -v README | grep -v asm.js | grep -v raytrace | grep -v without-a-bundler`; do (cd examples/$dir && @@ -190,6 +184,19 @@ jobs: artifactName: examples2 targetPath: '$(Build.ArtifactStagingDirectory)' + - job: build_benchmarks + displayName: "Build benchmarks" + steps: + - template: ci/azure-install-rust.yml + - template: ci/azure-install-sccache.yml + - template: ci/azure-install-wasm-pack.yml + - script: wasm-pack build --target web benchmarks + displayName: "build benchmarks" + - task: PublishPipelineArtifact@0 + inputs: + artifactName: benchmarks + targetPath: benchmarks + - job: dist_linux displayName: "Dist Linux binary" steps: @@ -292,6 +299,7 @@ jobs: - dist_windows - build_examples - build_raytrace + - build_benchmarks displayName: "Deploy everything" steps: - template: ci/azure-install-rust.yml @@ -315,6 +323,11 @@ jobs: inputs: artifactName: examples2 targetPath: gh-pages/exbuild/raytrace-parallel + - task: DownloadPipelineArtifact@0 + displayName: "Download benchmarks" + inputs: + artifactName: benchmarks + targetPath: gh-pages/benchmarks - task: DownloadPipelineArtifact@0 displayName: "Download dist - windows" inputs: diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml new file mode 100644 index 00000000..7ffef776 --- /dev/null +++ b/benchmarks/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "wasm-bindgen-benchmark" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] + +[dependencies] +wasm-bindgen = "0.2.43" +web-sys = { version = "0.3.20", features = ['Node'] } + +[lib] +crate-type = ['cdylib'] diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 00000000..381c7913 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,54 @@ +# Microbenchmarks for `wasm-bindgen` + +This folder houses a number of microbenchmarks for `wasm-bindgen`. These, like +all microbenchmarks, should be taken with a grain of salt. They are intended to +help developers understand changes over time, but they are not intended to be a +performance suite for WebAssembly for Rust. + +[View benchmarks for `master` branch online][online] + +[online]: https://rustwasm.github.io/wasm-bindgen/benchmarks/ + +## Building and Running + +First, copy the benchmarks to a temporary directory: + +``` +$ cp ./benchmarks /some/other/directory +``` + +Next, `cd` into that directory and execute: + +``` +$ wasm-pack build --target web +``` + +Next, use your favorite static file server to host the current directory. For +example using the [`https` crate](https://crates.io/crates/https): + +``` +$ http +``` + +Then open up a web browser and view http://localhost:8000, for example. + +You should be presented a page with lots of `(run)` links, where when you click +them it will execute the benchmark and then display the result. + +## Benchmark Architecture + +Currently benchmarks are pretty bare bones. They just use benchmark.js to +generate statistics which are then rendered to the screen. Benchmarks are listed +one-by-one in `index.html` where a `td` exists for each benchmark. In `index.js` +each of the `td`'s `id` properties are hooked up to an actual function to +benchmark, depending on what's being benchmarked. + +Relevant files are: + +* `index.html` - the page showing all benchmarks +* `index.js` - the driver JS for all benchmarks +* `globals.js` - global JS functions imported by all other benchmarks +* `js-bencharks.js` - the JS functions that we're benchmarking +* `src/lib.rs` - the Rust/`wasm-bindgen` functions we're benchmarking +* `raw.wast`/`raw.wasm` - a raw handwritten WebAssembly file used in some + benchmarks. A compiled version of this is checked into the repository. diff --git a/benchmarks/globals.js b/benchmarks/globals.js new file mode 100644 index 00000000..0412158e --- /dev/null +++ b/benchmarks/globals.js @@ -0,0 +1,5 @@ +export function jsthunk() {} +export function add(a, b) { return a + b; } +export class Foo { + bar() {} +} diff --git a/benchmarks/index.html b/benchmarks/index.html new file mode 100644 index 00000000..83a99337 --- /dev/null +++ b/benchmarks/index.html @@ -0,0 +1,295 @@ + + + + + + + + Source code + +

JS / wasm-bindgen comparison

+ +

+ These benchmarks are meant to compare WebAssembly costs using raw wasm + files and wasm-bindgen itself against the JS equivalents. These + microbenchmarks aren't really representative of WebAssembly performance, + but can be useful data points about how expensive it is to cross + boundaries for example. +

+

+ For all benchmarks higher numbers are better numbers. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Benchmarkwasm-bindgenJS*.wast
+ Call a thunk + + (?) + +

+ This benchmarks tests out how long it take JS to call a thunk in + the given language. For example JS will call a JS thunk or + JS will call a wasm function that does nothing. +

+
+ Call an adder + + (?) + +

+ This benchmarks tests out how long it take JS to call a function + in the target language which adds two numbers and returns the + result. This is likely to be similar to the previous benchmark + in terms of results, but it's thought that some extra computation + may show overheads differently. +

+
+ Call a JS thunk + + (?) + +

+ This benchmarks tests out how long it takes to call a JS function + 10,000 times. Each language/framework has a function which takes a + parameter of how many times to call an imported JS function. + Remember that JS itself benefits from inlining, where as JS cannot + be inlined into WebAssembly. In these cases the imported function + performs no work, it just returns immediately. +

+
+ Call a JS function which adds + + (?) + +

+ This benchmark is similar to the previous thunk benchmark except + that this time the imported function will add its two arguments + and returns the result. This helps measure the overhead of + sending data like integers back and forth. +

+
+ Calculate Fib(40) + + (?) + +

+ This benchmarks calculates the 40th fibonacci number. It in + theory should favor wasm since wasm is "better a compute", but + a good JIT will probably make the code roughly equivalent. +

+
+ Access Node.firstChild + + (?) + +

+ This benchmark attempts to see the cost of accessing a DOM + property from both JS and WebAssembly. We access the DOM property + as fast as possible in WebAssembly and otherwise just access it + as a normal property in JS. +

+
+ Access Node.nodeType + + (?) + +

+ This benchmark attempts to see the cost of accessing a DOM + property from both JS and WebAssembly. We access the DOM property + as fast as possible in WebAssembly and otherwise just access it + as a normal property in JS. +

+
+ Count types of nodes on a page + + (?) + +

+ This is intended to be a "flavorful DOM benchmark" which + exercises DOM functionality from WebAssembly, specifically + counting the number of types of each node on a page. +

+
+ +

wasm-bindgen benchmarks

+ +

+ These benchmarks don't compare against JS but are instead more intended + to be compared against each other, across browsers, or across versions of + wasm-bindgen. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BenchmarkResult
+ Access Node.nodeType with final + + (?) + +

+ This is similar to the Node.nodeType benchmark above + except that it uses the final attribute in + wasm-bindgen. +

+
+ Access Node.nodeType with structural + + (?) + +

+ This is similar to the Node.nodeType benchmark above + except that it uses the structural attribute in + wasm-bindgen. +

+
+ Call a custom JS class Foo.bar method with + final + + (?) + +

+ This is similar to the Node.nodeType benchmark above + except that it's not calling a DOM method but rather a custom JS + class's method. +

+
+ Call a custom JS class Foo.bar method with + structural + + (?) + +

+ This is similar to the Node.nodeType benchmark above + except that it's not calling a DOM method but rather a custom JS + class's method. +

+
+ + diff --git a/benchmarks/index.js b/benchmarks/index.js new file mode 100644 index 00000000..ac603b63 --- /dev/null +++ b/benchmarks/index.js @@ -0,0 +1,182 @@ +// Benchmarking framework that we're using +import 'https://unpkg.com/lodash@4.17.11/lodash.js'; +import 'https://unpkg.com/benchmark@2.1.4/benchmark.js'; + +// Import lots of functions from JS/wasm, and rename everything to have the +// namespace of where it's coming from. +import wbindgen_init, { + call_js_thunk_n_times as wbindgen_call_js_thunk_n_times, + call_js_add_n_times as wbindgen_call_js_add_n_times, + thunk as wbindgen_thunk, + add as wbindgen_add, + fibonacci as wbindgen_fibonacci, + call_node_first_child_n_times as wbindgen_call_node_first_child_n_times, + call_node_node_type_n_times as wbindgen_call_node_node_type_n_times, + count_node_types as wbindgen_count_node_types, + call_first_child_final_n_times as wbindgen_call_first_child_final_n_times, + call_first_child_structural_n_times as wbindgen_call_first_child_structural_n_times, + call_foo_bar_final_n_times as wbindgen_call_foo_bar_final_n_times, + call_foo_bar_structural_n_times as wbindgen_call_foo_bar_structural_n_times, + str_roundtrip as wbindgen_str_roundtrip, +} from './pkg/wasm_bindgen_benchmark.js'; +import { + call_js_thunk_n_times as js_call_js_thunk_n_times, + call_js_add_n_times as js_call_js_add_n_times, + thunk as js_thunk, + add as js_add, + fibonacci as js_fibonacci, + call_node_first_child_n_times as js_call_node_first_child_n_times, + call_node_node_type_n_times as js_call_node_node_type_n_times, + count_node_types as js_count_node_types, +} from './js-benchmarks.js'; +import * as globals from './globals.js'; +import { Lock } from './utils.js'; + +// These are set for `raw.wasm`, which we import and configure manually: +let raw_call_js_thunk_n_times = null; +let raw_call_js_add_n_times = null; +let raw_thunk = null; +let raw_add = null; + +// Create a `Map` of all benchmarks that we're going to execute, where the map +// is from a benchmark's name to a thunk to execute for the benchmark. +function makeBenchmarks() { + const benchmarks = new Map(); + + benchmarks.wbindgen_thunk = wbindgen_thunk; + benchmarks.raw_thunk = raw_thunk; + benchmarks.js_thunk = js_thunk; + + benchmarks.wbindgen_fib_40 = () => wbindgen_fibonacci(40); + benchmarks.js_fib_40 = () => js_fibonacci(40); + + benchmarks.wbindgen_add = () => wbindgen_add(2, 3); + benchmarks.raw_add = () => raw_add(2, 3); + benchmarks.js_add = () => js_add(2, 3); + + benchmarks.js_call_js_thunk_n_times = () => js_call_js_thunk_n_times(10000); + benchmarks.raw_call_js_thunk_n_times = () => raw_call_js_thunk_n_times(10000); + benchmarks.wbindgen_call_js_thunk_n_times = () => wbindgen_call_js_thunk_n_times(10000); + + benchmarks.js_call_js_add_n_times = () => js_call_js_add_n_times(10000, 2, 3); + benchmarks.raw_call_js_add_n_times = () => raw_call_js_add_n_times(10000, 2, 3); + benchmarks.wbindgen_call_js_add_n_times = () => wbindgen_call_js_add_n_times(10000, 2, 3); + + const list = []; + for (let i = 0; i < 10; i++) + list.push(document.body); + benchmarks.wbindgen_call_node_first_child_n_times = () => wbindgen_call_node_first_child_n_times(1000, list); + benchmarks.js_call_node_first_child_n_times = () => js_call_node_first_child_n_times(1000, list); + benchmarks.wbindgen_call_node_node_type_n_times = () => wbindgen_call_node_node_type_n_times(1000, list); + benchmarks.js_call_node_node_type_n_times = () => js_call_node_node_type_n_times(1000, list); + + const body = document.body; + benchmarks.wbindgen_count_node_types = () => wbindgen_count_node_types(body); + benchmarks.js_count_node_types = () => js_count_node_types(body); + + benchmarks.wbindgen_call_first_child_final_n_times = () => wbindgen_call_first_child_final_n_times(10000, body); + benchmarks.wbindgen_call_first_child_structural_n_times = () => wbindgen_call_first_child_structural_n_times(10000, body); + + const foo = new globals.Foo(); + benchmarks.wbindgen_call_foo_bar_final_n_times = () => wbindgen_call_foo_bar_final_n_times(10000, foo); + benchmarks.wbindgen_call_foo_bar_structural_n_times = () => wbindgen_call_foo_bar_structural_n_times(10000, foo); + + + const strings = { + ascii_small: 'ja', + ascii_medium: 'aym0566x', + ascii_number: '505874924095815681', + ascii_date: 'Sun Aug 31 00:29:15 +0000 2014', + ascii_url: 'https://pbs.twimg.com/profile_images/497760886795153410/LDjAwR_y_normal.jpeg', + ascii_link: 'Twitter for iPhone', + unicode: '@aym0566x \n\n名前:前田あゆみ\n第一印象:なんか怖っ!\n今の印象:とりあえずキモい。噛み合わない\n好きなところ:ぶすでキモいとこ😋✨✨\n思い出:んーーー、ありすぎ😊❤️\nLINE交換できる?:あぁ……ごめん✋\nトプ画をみて:照れますがな😘✨\n一言:お前は一生もんのダチ💖' + } + const template = document.querySelector('tr.str-benchmark'); + template.remove(); + const tbody = document.querySelector('tbody#wbindgen-body'); + for (const bm in strings) { + const s = strings[bm]; + const bm_name = `wbindgen_str_${bm}`; + benchmarks[bm_name] = () => wbindgen_str_roundtrip(s); + + const row = template.cloneNode(true); + row.querySelector('.str').textContent = bm; + row.querySelector('td.bm').id = bm_name; + row.removeAttribute('style'); + tbody.appendChild(row); + } + + return benchmarks; +} + +// Set up the page and initialize all event handlers and such. +function run() { + const benchmarks = makeBenchmarks(); + + const benchmarkLock = new Lock(); + for (const td of document.querySelectorAll('td.bm')) { + const bm = benchmarks[td.id]; + if (typeof bm !== 'function') + throw new Error(`no benchmark registered for ${td.id}`); + + const run = document.createElement('a'); + run.href = '#'; + run.innerText = '(run)'; + run.onclick = function() { + benchmarkLock.withLock(async () => { + await executeAndUpdate(td.id, bm, td); + }); + run.remove(); + td.innerText = 'executing ...'; + return false; + }; + td.appendChild(run); + } + + for (const a of document.querySelectorAll('.about-open')) { + a.onclick = function() { + a.nextElementSibling.style.display = 'block'; + a.remove(); + return false; + }; + } +} + +async function executeAndUpdate(name, bm, td) { + const result = await executeBenchmark(name, bm); + console.log(result.target); + const rme = Math.round(result.target.stats.rme * 100) / 100; + td.innerText = `${Math.round(result.target.hz).toLocaleString()}/s ±${rme}%`; +} + +function executeBenchmark(name, bm) { + return new Promise((resolve, reject) => { + const suite = new Benchmark.Suite(); + suite.add(name, bm); + suite.on('cycle', resolve); + suite.run({ async: true }); + }); +} + +// Load wasm files and when they're done (plus the DOM) then we initialize +// everything +const wasms = []; +wasms.push(wbindgen_init('./pkg/wasm_bindgen_benchmark_bg.wasm')); +wasms.push(fetch('./raw.wasm') + .then(r => r.arrayBuffer()) + .then(m => WebAssembly.instantiate(m, { './globals.js': globals })) + .then(m => { + raw_call_js_thunk_n_times = m.instance.exports.call_js_thunk_n_times; + raw_call_js_add_n_times = m.instance.exports.call_js_add_n_times; + raw_thunk = m.instance.exports.thunk; + raw_add = m.instance.exports.add; + })); + +Promise.all(wasms) + .then(() => { + if (document.readyState === 'loading') + document.addEventListener('DOMContentLoaded', run); + else + run(); + }) + .catch(console.error); diff --git a/benchmarks/js-benchmarks.js b/benchmarks/js-benchmarks.js new file mode 100644 index 00000000..d6b56614 --- /dev/null +++ b/benchmarks/js-benchmarks.js @@ -0,0 +1,76 @@ +import { jsthunk, add as jsadd } from './globals.js'; + +export function fibonacci(n) { + let a = 1; + let b = 1; + let tmp = 0; + + while (n > 1) { + tmp = b; + b += a; + a = tmp; + --n; + } + + return a; +} + +export function thunk() {} + +export function call_js_thunk_n_times(n) { + for (var i = 0; i < n; i++) { + jsthunk(); + } +} + +export function add(a, b) { + return a + b; +} + +export function call_js_add_n_times(n, a, b) { + for (var i = 0; i < n; i++) { + jsadd(a, b); + } +} + +export function call_node_first_child_n_times(n, array_of_elements) { + for (let i = 0; i < n; i++) { + for (const element of array_of_elements) + if (element.firstChild === null) + throw new Error("bad"); + } +} + +export function call_node_node_type_n_times(n, array_of_elements) { + for (let i = 0; i < n; i++) { + for (const element of array_of_elements) + if (element.nodeType === 100) + throw new Error("bad"); + } +} + +export function call_node_has_child_nodes_n_times(n, array_of_elements) { + for (let i = 0; i < n; i++) { + for (const element of array_of_elements) + if (!element.hasChildNodes()) + throw new Error("bad"); + } +} + +export function count_node_types(element) { + const types = []; + + function count(node, types) { + while(node) { + const type = node.nodeType; + while (types.length <= type) + types.push(0); + types[type] += 1; + count(node.firstChild, types); + node = node.nextSibling; + } + } + + count(element, types); + return types; +} diff --git a/benchmarks/raw.wasm b/benchmarks/raw.wasm new file mode 100644 index 00000000..00580e29 Binary files /dev/null and b/benchmarks/raw.wasm differ diff --git a/benchmarks/raw.wast b/benchmarks/raw.wast new file mode 100644 index 00000000..97298226 --- /dev/null +++ b/benchmarks/raw.wast @@ -0,0 +1,52 @@ +(module + (import "./globals.js" "jsthunk" (func $js_thunk)) + (import "./globals.js" "add" (func $js_add (param i32) (param i32) (result i32))) + + (export "call_js_thunk_n_times" (func $call_thunk)) + (export "call_js_add_n_times" (func $call_add)) + (export "thunk" (func $thunk)) + (export "add" (func $add)) + + (func $call_thunk (param i32) + block + get_local 0 + i32.eqz + br_if 0 + loop + call $js_thunk + get_local 0 + i32.const 1 + i32.sub + tee_local 0 + br_if 0 + end + end + ) + + (func $call_add (param i32) (param i32) (param i32) + block + get_local 0 + i32.eqz + br_if 0 + loop + get_local 2 + get_local 1 + call $js_add + drop + get_local 0 + i32.const 1 + i32.sub + tee_local 0 + br_if 0 + end + end + ) + + (func $thunk) + + (func $add (param i32) (param i32) (result i32) + get_local 0 + get_local 1 + i32.add + ) +) diff --git a/benchmarks/src/lib.rs b/benchmarks/src/lib.rs new file mode 100644 index 00000000..2b8e1736 --- /dev/null +++ b/benchmarks/src/lib.rs @@ -0,0 +1,179 @@ +extern crate wasm_bindgen; +extern crate web_sys; + +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use web_sys::Node; + +#[wasm_bindgen(raw_module = "../globals.js")] +extern { + #[wasm_bindgen(js_name = jsthunk)] + fn js_thunk(); + #[wasm_bindgen(js_name = add)] + fn js_add(a: i32, b: i32) -> i32; + + pub type Foo; + #[wasm_bindgen(method, final, js_name = bar)] + fn bar_final(this: &Foo); + #[wasm_bindgen(method, structural, js_name = bar)] + fn bar_structural(this: &Foo); + + #[wasm_bindgen(js_name = jsthunk)] + fn doesnt_throw(); + #[wasm_bindgen(catch, js_name = jsthunk)] + fn doesnt_throw_catch() -> Result<(), JsValue>; +} + +#[wasm_bindgen] +pub fn call_js_thunk_n_times(n: usize) { + for _ in 0..n { + js_thunk(); + } +} + +#[wasm_bindgen] +pub fn call_js_add_n_times(n: usize, a: i32, b: i32) { + for _ in 0..n { + js_add(a, b); + } +} + +#[wasm_bindgen] +pub fn thunk() {} + +#[wasm_bindgen] +pub fn add(a: i32, b: i32) -> i32 { + a + b +} + +static mut FIB_HIGH: i32 = 0; + +#[wasm_bindgen] +pub fn fibonacci(n: i32) -> i32 { + let mut a = 1u64; + let mut b = 1; + for _ in 0..n { + let tmp = b; + b += a; + a = tmp; + } + unsafe { FIB_HIGH = (a >> 32) as i32; } + return a as i32 +} + +#[wasm_bindgen] +pub fn fibonacci_high() -> i32 { + unsafe { FIB_HIGH } +} + +#[wasm_bindgen] +pub fn call_foo_bar_final_n_times(n: usize, foo: &Foo) { + for _ in 0..n { + foo.bar_final(); + } +} + +#[wasm_bindgen] +pub fn call_foo_bar_structural_n_times(n: usize, foo: &Foo) { + for _ in 0..n { + foo.bar_structural(); + } +} + +#[wasm_bindgen] +pub fn call_doesnt_throw_n_times(n: usize) { + for _ in 0..n { + doesnt_throw(); + } +} + +#[wasm_bindgen] +pub fn call_doesnt_throw_with_catch_n_times(n: usize) { + for _ in 0..n { + if let Err(e) = doesnt_throw_catch() { + wasm_bindgen::throw_val(e); + } + } +} + +#[wasm_bindgen] +extern { + pub type Element; + + #[wasm_bindgen(method, js_name = firstChild, final, getter)] + fn first_child_final(this: &Element) -> Element; + #[wasm_bindgen(method, js_name = firstChild, structural, getter)] + fn first_child_structural(this: &Element) -> Element; +} + +#[wasm_bindgen] +pub fn call_first_child_final_n_times(n: usize, element: &Element) { + for _ in 0..n { + drop(element.first_child_final()); + } +} + +#[wasm_bindgen] +pub fn call_first_child_structural_n_times(n: usize, element: &Element) { + for _ in 0..n { + drop(element.first_child_structural()); + } +} + +#[wasm_bindgen] +pub fn call_node_first_child_n_times(n: usize, elements: Vec) { + for _ in 0..n { + for element in elements.iter() { + let element = element.unchecked_ref::(); + assert!(element.first_child().is_some()); + } + } +} + +#[wasm_bindgen] +pub fn call_node_node_type_n_times(n: usize, elements: Vec) { + for _ in 0..n { + for element in elements.iter() { + let element = element.unchecked_ref::(); + assert!(element.node_type() != 100); + } + } +} + +#[wasm_bindgen] +pub fn call_node_has_child_nodes_n_times(n: usize, elements: Vec) { + for _ in 0..n { + for element in elements.iter() { + let element = element.unchecked_ref::(); + assert!(element.has_child_nodes()); + } + } +} + +#[wasm_bindgen] +pub fn count_node_types(element: Node) { + let mut count = Vec::new(); + count_node_types(element, &mut count); + + fn count_node_types(mut element: Node, count: &mut Vec) { + loop { + let t = element.node_type(); + if t as usize >= count.len() { + count.resize(t as usize + 1, 0); + } + count[t as usize] += 1; + if let Some(s) = element.first_child() { + count_node_types(s, count); + } + match element.next_sibling() { + Some(s) => element = s, + None => break, + } + } + } +} + +#[wasm_bindgen] +pub fn str_roundtrip(s: String) -> String { + s +} diff --git a/benchmarks/utils.js b/benchmarks/utils.js new file mode 100644 index 00000000..d2b4af24 --- /dev/null +++ b/benchmarks/utils.js @@ -0,0 +1,14 @@ +export class Lock { + constructor() { + this.lockHolder = null; + } + + async withLock(scope) { + while (this.lockHolder !== null) { + await this.lockHolder; + } + this.lockHolder = Promise.resolve(null).then(scope); + await this.lockHolder; + this.lockHolder = null; + } +} diff --git a/ci/azure-install-wasm-pack.yml b/ci/azure-install-wasm-pack.yml new file mode 100644 index 00000000..4759d82e --- /dev/null +++ b/ci/azure-install-wasm-pack.yml @@ -0,0 +1,8 @@ +steps: + - script: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f + displayName: "install wasm-pack" + - script: | + set -ex + cargo build -p wasm-bindgen-cli + ln -snf `pwd`/target/debug/wasm-bindgen $HOME/.cargo/bin/wasm-bindgen + displayName: "install wasm-bindgen for `wasm-pack` to use"