Reworked the examples to be more welcoming

This commit is contained in:
Johan B.W. de Vries 2025-06-05 19:45:54 +02:00
parent 3cb4860973
commit 8a1a6af3e7
14 changed files with 147 additions and 562 deletions

View File

@ -1,51 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Examples - Buffer</title>
</head>
<body>
<h1>Buffer</h1>
<a href="index.html">List</a> - <a href="buffer.py.html">Source</a> - <a href="buffer.wat.html">WebAssembly</a>
<div style="white-space: pre;" id="results"></div>
<script type="text/javascript" src="./include.js"></script>
<script type="text/javascript">
let importObject = {};
// Run a single test
function run_test(app, str)
{
let offset = alloc_bytes(app, str);
let js_chars = [];
let wasm_chars = [];
for(let idx = 0; idx < str.length; ++idx) {
js_chars.push(str.charCodeAt(idx));
wasm_chars.push(app.instance.exports.index(offset, idx));
}
let result = js_chars.every(function(value, index) { return value === wasm_chars[index]})
test_result(result, {
'summary': 'js_chars == wasm_chars, for "' + str + '"',
'attributes': {
'js_chars': js_chars,
'wasm_chars': wasm_chars,
}
});
}
WebAssembly.instantiateStreaming(fetch('buffer.wasm'), importObject)
.then(app => {
run_test(app, '');
run_test(app, 'a');
run_test(app, 'Hello');
run_test(app, 'The quick brown fox jumps over the lazy dog');
});
</script>
</body>
</html>

View File

@ -1,7 +0,0 @@
@exported
def index(inp: bytes, idx: u32) -> u8:
return inp[idx]
@exported
def length(inp: bytes) -> u32:
return len(inp)

View File

@ -2,245 +2,68 @@
<html> <html>
<head> <head>
<title>Examples - CRC32</title> <title>Examples - CRC32</title>
<link rel="stylesheet" type="text/css" href="main.css">
</head> </head>
<body> <body>
<h1>Buffer</h1> <h1>Examples - CRC32</h1>
<a href="index.html">List</a> - <a href="crc32.py.html">Source</a> - <a href="crc32.wat.html">WebAssembly</a><br /> <div class="menu">
<br /> <a href="index.html">List</a> - <a href="crc32.py.html">Source</a> - <a href="crc32.wat.html">WebAssembly</a><br />
Note: This tests performs some timing comparison, please wait a few seconds for the results.<br /> </div>
<div style="white-space: pre;" id="results"></div>
<h2>Measurement log</h2> <div class="description">
<h3>AMD Ryzen 7 3700X 8-Core, Ubuntu 20.04, Linux 5.4.0-124-generic</h3> <p>
<h4>After optimizing fold over bytes by inlineing __subscript_bytes__</h4> This example shows a fold implementation of a <a href="https://en.wikipedia.org/wiki/Cyclic_redundancy_check">cyclic redundancy check</a>.
<table> There are actually many variations of these CRCs - this one is specifically know as CRC-32/ISO-HDLC.
<tr> </p>
<td>Test</td> </div>
<td>Interpreter</td>
<td>Setup</td>
<td>WebAssembly</td>
<td>Javascript</td>
</tr>
<tr>
<td>Lynx * 65536</td>
<td>Chromium 104.0.5112.101</td>
<td>DevTools closed</td>
<td>5.70</td>
<td>12.45</td>
</tr>
<tr>
<td>Lynx * 65536</td>
<td>Firefox 103</td>
<td>DevTools closed</td>
<td>5.16</td>
<td>5.72</td>
</tr>
<tr>
<td>Lynx * 1048576</td>
<td>Chromium 104.0.5112.101</td>
<td>DevTools closed</td>
<td>95.65</td>
<td>203.60</td>
</tr>
<tr>
<td>Lynx * 1048576</td>
<td>Firefox 103</td>
<td>DevTools closed</td>
<td>83.34</td>
<td>92.38</td>
</tr>
</table>
<h4>Before optimizing fold over bytes by inlineing __subscript_bytes__</h4>
<table>
<tr>
<td>Test</td>
<td>Interpreter</td>
<td>Setup</td>
<td>WebAssembly</td>
<td>Javascript</td>
</tr>
<tr>
<td>Lynx * 65536</td>
<td>Chromium 104.0.5112.101</td>
<td>DevTools closed</td>
<td>9.35</td>
<td>12.56</td>
</tr>
<tr>
<td>Lynx * 65536</td>
<td>Chromium 104.0.5112.101</td>
<td>DevTools open</td>
<td>14.71</td>
<td>12.72</td>
</tr>
<tr>
<td>Lynx * 65536</td>
<td>Chromium 104.0.5112.101</td>
<td>Record page load</td>
<td>9.44</td>
<td>12.69</td>
</tr>
<tr>
<td>Lynx * 65536</td>
<td>Firefox 103</td>
<td>DevTools closed</td>
<td>9.02</td>
<td>5.86</td>
</tr>
<tr>
<td>Lynx * 65536</td>
<td>Firefox 103</td>
<td>DevTools open</td>
<td>9.01</td>
<td>5.83</td>
</tr>
<tr>
<td>Lynx * 65536</td>
<td>Firefox 103</td>
<td>Record page load</td>
<td>72.41</td>
<td>5.85</td>
</tr>
<tr> <h3>Try it out!</h3>
<td>Lynx * 1048576</td> <div class="example">
<td>Chromium 104.0.5112.101</td> <textarea id="example-data" style="width: 75%; height: 4em;">The quick brown fox jumps over the lazy dog</textarea><br />
<td>DevTools closed</td> <button type="click" id="example-click" disabled>Calculate</button>
<td>149.24</td> <input type="text" id="example-crc32" />
<td>202.36</td> </div>
</tr>
<tr>
<td>Lynx * 1048576</td>
<td>Firefox 103</td>
<td>DevTools closed</td>
<td>145.01</td>
<td>91.44</td>
</tr>
</table>
<h4>Notes</h4> <div class="example-list">
- Firefox seems faster than Chromium in my setup for Javascript, WebAssembly seems about the same.<br /> <ul>
- Having DevTools open in Chromium seems to slow down the WebAssembly by about 30%, but not when doing a recording of the page load.<br /> <li><a href="#" data-n="123456789">crc32("123456789")</a> = cbf43926</li>
- WebAssembly in Firefox seems to slow down when doing a recording of the page load, which makes sense, but the Javascript does not.<br /> <li><a href="#" data-n="Hello, world!">crc32("Hello, world!")</a> = ebe6c6e6</li>
<li><a href="#" data-n="The quick brown fox jumps over the lazy dog">crc32("The quick brown fox jumps over the lazy dog")</a> = 414fa339</li>
<li><a href="#" data-n="CRC-32/ISO-HDLC
Also referred to as ISO 3309, ITU-T V.42, CRC-32-IEEE, and many other names.
<script type="text/javascript" src="./include.js"></script> The CRC of ASCII &quot;123456789&quot; is 0xcbf43926.
<script type="text/javascript">
let importObject = {};
// Build up a JS version Examples of formats that use CRC-32/ISO-HDLC: ZIP, PNG, Gzip, ARJ.">crc32("CRC-32/ISO-HDLC...")</a> = 126afcf</li>
var makeCRCTable = function(){ </ul>
var c; </div>
var crcTable = [];
for(var n =0; n < 256; n++){ <!-- We'll need to use some interface glue - WebAssembly doesn't let us pass strings directly. -->
c = n; <script type="text/javascript" src="./include.js"></script>
for(var k =0; k < 8; k++){ <script>
c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); let importObject = {};
} let exampleN = document.querySelector('#example-data');
crcTable[n] = c; let exampleClick = document.querySelector('#example-click');
let exampleCrc32 = document.querySelector('#example-crc32');
WebAssembly.instantiateStreaming(fetch('crc32.wasm'), importObject)
.then(app => {
exampleClick.addEventListener('click', event => {
let in_put = exampleN.value;
let in_put_addr = alloc_bytes(app, in_put);
let result = app.instance.exports.crc32(in_put_addr);
exampleCrc32.value = i32_to_u32(result).toString(16);
});
exampleClick.removeAttribute('disabled');
});
for(let exmpl of document.querySelectorAll('a[data-n]') ) {
exmpl.addEventListener('click', event => {
exampleN.value = exmpl.getAttribute('data-n');
exampleClick.click();
});
} }
return crcTable; </script>
}
window.crcTable = makeCRCTable();
var crc32_js = function(i8arr) {
// console.log('crc32_js', i8arr.length);
var crcTable = window.crcTable;
var crc = 0 ^ (-1);
for (var i = 0; i < i8arr.length; i++ ) {
crc = (crc >>> 8) ^ crcTable[(crc ^ i8arr[i]) & 0xFF];
}
return (crc ^ (-1)) >>> 0;
};
// Run a single test
function run_test(app, str, str_repeat)
{
// Cast to Uint32 in Javascript
let crc32_wasm = function(offset) {
// console.log('crc32_wasm', str.length);
return app.instance.exports.crc32(offset) >>> 0;
};
let orig_str = str;
if( str_repeat ) {
str = str.repeat(str_repeat);
} else {
str_repeat = 1;
}
let data = Uint8Array.from(str.split('').map(x => x.charCodeAt()));
offset = alloc_bytes(app, data);
let tweak = () => {
data[0] = data[0] + 1;
let i8arr = new Uint8Array(app.instance.exports.memory.buffer, offset + 4, data.length);
i8arr[0] = i8arr[0] + 1;
};
let tweak_reset = () => {
data[0] = 'T'.charCodeAt(0);
let i8arr = new Uint8Array(app.instance.exports.memory.buffer, offset + 4, data.length);
i8arr[0] = 'T'.charCodeAt(0);
};
// Run once to get the result
// For some reason, the JS version takes 2ms on the first run
// let wasm_result = crc32_wasm(offset);
// let js_result = crc32_js(data);
let wasm_timing = run_times(100, () => crc32_wasm(offset));
let js_timing = run_times(100, () => crc32_js(data));
let wasm_time = wasm_timing.avg;
let js_time = js_timing.avg;
let check = wasm_timing.values.every(function(value, index) {
return value.result === js_timing.values[index].result;
});
// Don't test speedup for small strings, it varies a lot
let speedup = (wasm_timing.min == 0 || js_timing.min == 0)
? 1
: js_time / wasm_time;
test_result(check && 0.999 < speedup, { // At least as fast as Javascript
'summary': 'crc32(' + (str
? (str.length < 64 ? '"' + str + '"' : '"' + str.substring(0, 64) + '..." (' + str.length + ')')
: '""') + ')',
'attributes': {
'str': orig_str,
'str_repeat': str_repeat,
'wasm_timing': wasm_timing,
'js_timing': js_timing,
'check': check,
'speedup': speedup,
},
});
}
// Load WebAssembly, and run all tests
WebAssembly.instantiateStreaming(fetch('crc32.wasm'), importObject)
.then(app => {
app.instance.exports.memory.grow(640);
run_test(app, "");
run_test(app, "a");
run_test(app, "Z");
run_test(app, "ab");
run_test(app, "abcdefghijklmnopqrstuvwxyz");
run_test(app, "The quick brown fox jumps over the lazy dog");
run_test(app, "The quick brown fox jumps over the lazy dog", 1024);
run_test(app, "Lynx c.q. vos prikt bh: dag zwemjuf!", 65536);
});
</script>
</body> </body>
</html> </html>

View File

@ -1,55 +1,62 @@
<!DOCTYPE html>
<html> <html>
<head> <head>
<title>Examples - Fibonacci</title> <title>Examples - Fibonacci</title>
<link rel="stylesheet" type="text/css" href="main.css">
</head> </head>
<body> <body>
<h1>Fibonacci</h1> <h1>Examples - Fibonacci</h1>
<a href="index.html">List</a> - <a href="fib.py.html">Source</a> - <a href="fib.wat.html">WebAssembly</a> <div class="menu">
<a href="index.html">List</a> - <a href="fib.py.html">Source</a> - <a href="fib.wat.html">WebAssembly</a>
</div>
<div style="white-space: pre;" id="results"></div> <div class="description">
<p>
This example shows a recursive implementation of the <a href="https://en.wikipedia.org/wiki/Fibonacci_sequence">Fibonacci sequence</a>.
It makes uses of WebAssembly's support for <a href="https://en.wikipedia.org/wiki/Tail_call">tail calls</a>.
</p>
</div>
<h3>Try it out!</h3>
<div class="example">
<input type="number" id="example-n" value="25" />
<button type="click" id="example-click" disabled>Calculate</button>
<input type="number" id="example-fib" />
</div>
<script type="text/javascript" src="./include.js"></script> <div class="example-list">
<script type="text/javascript"> <ul>
let importObject = {}; <li><a href="#" data-n="5">fib(5)</a> = 5</li>
<li><a href="#" data-n="10">fib(10)</a> = 55</li>
<li><a href="#" data-n="25">fib(25)</a> = 75025</li>
<li><a href="#" data-n="50">fib(50)</a> = 12586269025</li>
</ul>
</div>
function run_test(app, number, expected) <script>
{ let importObject = {};
let actual = app.instance.exports.fib(BigInt(number)); let exampleN = document.querySelector('#example-n');
console.log(actual); let exampleClick = document.querySelector('#example-click');
let exampleFib = document.querySelector('#example-fib');
test_result(BigInt(expected) == actual, { WebAssembly.instantiateStreaming(fetch('fib.wasm'), importObject)
'summary': 'fib(' + number + ')', .then(app => {
'attributes': { exampleClick.addEventListener('click', event => {
'expected': expected, let in_put = exampleN.value;
'actual': actual let result = app.instance.exports.fib(BigInt(in_put));
}, exampleFib.value = result;
}); });
} exampleClick.removeAttribute('disabled');
});
WebAssembly.instantiateStreaming(fetch('fib.wasm'), importObject) for(let exmpl of document.querySelectorAll('a[data-n]') ) {
.then(app => { exmpl.addEventListener('click', event => {
// 92: 7540113804746346429 exampleN.value = exmpl.getAttribute('data-n');
// i64: 9223372036854775807 exampleClick.click();
// 93: 12200160415121876738 });
}
run_test(app, 1, '1');
run_test(app, 2, '1');
run_test(app, 3, '2');
run_test(app, 4, '3');
run_test(app, 10, '55');
run_test(app, 20, '6765');
run_test(app, 30, '832040');
run_test(app, 40, '102334155');
run_test(app, 50, '12586269025');
run_test(app, 60, '1548008755920');
run_test(app, 70, '190392490709135');
run_test(app, 80, '23416728348467685');
run_test(app, 90, '2880067194370816120');
run_test(app, 92, '7540113804746346429');
});
</script>
</script>
</body> </body>
</html> </html>

View File

@ -13,7 +13,3 @@ def fib(n: u64) -> u64:
return 1 return 1
return helper(n - 1, 0, 1) return helper(n - 1, 0, 1)
@exported
def testEntry() -> u64:
return fib(40)

View File

@ -1,48 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Examples - Fold</title>
</head>
<body>
<h1>Fold</h1>
<a href="index.html">List</a> - <a href="fold.py.html">Source</a> - <a href="fold.wat.html">WebAssembly</a>
<div style="white-space: pre;" id="results"></div>
<script type="text/javascript" src="./include.js"></script>
<script type="text/javascript">
let importObject = {};
function run_test(app, data, expected)
{
let offset = alloc_bytes(app, data);
let actual = app.instance.exports.foldl_u8_or_1(offset);
test_result(expected == actual, {
'summary': 'foldl(or, 1, [' + data + ']) == ' + expected,
'attributes': {
'data': data,
'expected': expected,
'offset': offset,
'actual': actual,
},
});
}
WebAssembly.instantiateStreaming(fetch('fold.wasm'), importObject)
.then(app => {
run_test(app, [], 1);
run_test(app, [0x20], 0x21);
run_test(app, [0x20, 0x10], 0x31);
run_test(app, [0x20, 0x10, 0x08], 0x39);
run_test(app, [0x20, 0x10, 0x08, 0x04], 0x3D);
run_test(app, [0x20, 0x10, 0x08, 0x04, 0x02], 0x3F);
run_test(app, [0x20, 0x10, 0x08, 0x04, 0x02, 0x01], 0x3F);
});
</script>
</body>
</html>

View File

@ -1,6 +0,0 @@
def u8_or(l: u8, r: u8) -> u8:
return l | r
@exported
def foldl_u8_or_1(b: bytes) -> u8:
return foldl(u8_or, 1, b)

View File

@ -1,60 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Examples - Imported</title>
</head>
<body>
<h1>Imported</h1>
<a href="index.html">List</a> - <a href="imported.py.html">Source</a> - <a href="imported.wat.html">WebAssembly</a>
<div style="white-space: pre;" id="results"></div>
<script type="text/javascript" src="./include.js"></script>
<script type="text/javascript">
let importObject = {
'imports': {
'log': log,
}
};
let log_result = [];
function log(inp)
{
log_result.push(inp);
}
function run_test(app, a, b)
{
log_result = [];
let expected = a * b;
app.instance.exports.mult_and_log(a, b);
let actual = log_result[0];
test_result(expected == actual, {
'summary': 'mult_and_log(' + a + ', ' + b + ') == ' + expected,
'attributes': {
'a': a,
'b': b,
'expected': expected,
'actual': actual,
},
});
}
WebAssembly.instantiateStreaming(fetch('imported.wasm'), importObject)
.then(app => {
run_test(app, 1, 1);
run_test(app, 3, 5);
run_test(app, 8, 19);
run_test(app, 12, 127);
run_test(app, 79, 193);
});
</script>
</body>
</html>

View File

@ -1,7 +0,0 @@
@imported
def log(no: i32) -> None:
pass
@exported
def mult_and_log(a: i32, b: i32) -> None:
return log(a * b)

View File

@ -1,3 +1,6 @@
/***
* Allocates the given string in the given application's memory
*/
function alloc_bytes(app, data) function alloc_bytes(app, data)
{ {
let stdlib_types___alloc_bytes__ = app.instance.exports['stdlib.types.__alloc_bytes__'] let stdlib_types___alloc_bytes__ = app.instance.exports['stdlib.types.__alloc_bytes__']
@ -14,85 +17,12 @@ function alloc_bytes(app, data)
return offset; return offset;
} }
function run_times(times, callback, tweak) /**
* WebAssembly's interface only gets you signed integers
*
* Getting unsigned values out requires some work.
*/
function i32_to_u32(n)
{ {
let sum = 0; return n >>> 0;
let max = 0;
let min = 1000000000000000000;
let values = [];
for(let idx = 0; idx < times; idx += 1) {
if( tweak ) {
tweak();
}
const t0 = performance.now();
let result = callback();
const t1 = performance.now();
let time = t1 - t0;
sum += time;
values.push({'time': time, 'result': result});
max = max < time ? time : max;
min = min > time ? time : min;
}
return {
'min': min,
'avg': sum / times,
'max': max,
'sum': sum,
'values': values,
}
}
function test_result(is_pass, data)
{
data = data || {};
let result_details = document.createElement('details');
let result_summary = document.createElement('summary');
result_summary.textContent =
(is_pass ? 'Test passed: ' : 'Test failed: ')
+ (data.summary ?? '(no summary)')
;
result_summary.setAttribute('style', is_pass ? 'background: green' : 'background: red');
result_details.appendChild(result_summary);
if( data.attributes ) {
result_table(data, result_details);
}
let results = document.getElementById('results');
results.appendChild(result_details);
}
function result_table(attributes, parent)
{
let table = document.createElement('table');
Object.keys(attributes).forEach(idx => {
let td0 = document.createElement('td');
td0.setAttribute('style', 'vertical-align: top;');
td0.textContent = idx;
let td1 = document.createElement('td');
if( typeof(attributes[idx]) == 'object' ) {
let result_details = document.createElement('details');
let result_summary = document.createElement('summary');
result_summary.textContent = 'Show me';
result_details.appendChild(result_summary);
result_table(attributes[idx], result_details);
td1.appendChild(result_details);
} else {
td1.textContent = attributes[idx];
}
let tr = document.createElement('tr');
tr.appendChild(td0);
tr.appendChild(td1);
table.appendChild(tr);
});
parent.append(table);
} }

View File

@ -1,19 +1,16 @@
<!DOCTYPE html>
<html> <html>
<head> <head>
<title>Examples</title> <title>Examples</title>
<link rel="stylesheet" type="text/css" href="main.css">
</head> </head>
<body> <body>
<h1>Examples</h1> <h1>Examples</h1>
<h2>Standard</h2>
<h2>Functions</h2>
<ul> <ul>
<li><a href="crc32.html">CRC32</a></li> <li><a href="crc32.html">CRC32</a></li>
<li><a href="fib.html">Fibonacci</a></li> <li><a href="fib.html">Fibonacci</a></li>
</ul> </ul>
<h2>Technical</h2>
<ul>
<li><a href="buffer.html">Buffer</a></li>
<li><a href="fold.html">Folding</a></li>
<li><a href="imported.html">Imported</a></li>
</ul>
</body> </body>
</html> </html>

31
examples/main.css Normal file
View File

@ -0,0 +1,31 @@
:root {
/* CSS HEX */
--seasalt: #fcfafaff;
--silver: #c8d3d5ff;
--powder-blue: #a4b8c4ff;
--slate-gray: #6e8387ff;
--dark-pastel-green: #0cca4aff;
}
body {
background-color: var(--seasalt);
color: var(--slate-gray);
}
a {
color: var(--dark-pastel-green);
text-decoration: none;
font-weight: bold;
}
.menu {
border: 1px solid var(--powder-blue);
border-width: 1px 0px;
padding: 0.2em;
margin: 0.2em 0;
}
h3 {
border-top: 1px solid var(--powder-blue);
padding-top: 0.3em;
}

View File

@ -1,20 +0,0 @@
import pytest
from ..helpers import Suite
@pytest.mark.slow_integration_test
def test_index():
with open('examples/buffer.py', 'r', encoding='ASCII') as fil:
code_py = "\n" + fil.read()
result = Suite(code_py).run_code(b'Hello, world!', 5, func_name='index')
assert 44 == result.returned_value
@pytest.mark.slow_integration_test
def test_length():
with open('examples/buffer.py', 'r', encoding='ASCII') as fil:
code_py = "\n" + fil.read()
result = Suite(code_py).run_code(b'Hello, world!', func_name='length')
assert 13 == result.returned_value

View File

@ -8,6 +8,6 @@ def test_fib():
with open('./examples/fib.py', 'r', encoding='UTF-8') as fil: with open('./examples/fib.py', 'r', encoding='UTF-8') as fil:
code_py = "\n" + fil.read() code_py = "\n" + fil.read()
result = Suite(code_py).run_code() result = Suite(code_py).run_code(40, func_name='fib')
assert 102334155 == result.returned_value assert 102334155 == result.returned_value