3 Commits

Author SHA1 Message Date
c25a52b61b Prepare 0.1.1 release
add devkit mode to web runtime
add unpack and compile commands to uw8
2022-02-04 22:25:17 +01:00
619ea903ba add support for table/element section in pack command 2022-02-02 00:02:55 +01:00
9b900a49e4 add screenshot on F9, increase video rate + docs 2022-01-30 20:03:13 +01:00
12 changed files with 250 additions and 26 deletions

2
Cargo.lock generated
View File

@@ -1517,7 +1517,7 @@ dependencies = [
[[package]] [[package]]
name = "uw8" name = "uw8"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"curlywas", "curlywas",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "uw8" name = "uw8"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -53,6 +53,21 @@ Options:
-l LEVEL, --level LEVEL : Compression level (0-9). Higher compression levels are really slow. -l LEVEL, --level LEVEL : Compression level (0-9). Higher compression levels are really slow.
uw8 unpack <infile> <outfile>
Unpacks a MicroW8 module into a standard WebAssembly module.
uw8 compile [<options>] <infile> <outfile>
Compiles a CurlyWas source file to a standard WebAssembly module. Most useful together with
the --debug option to get a module that works well in the Chrome debugger.
Options:
-d, --debug : Generate a name section to help debugging
uw8 filter-exports <infile> <outfile> uw8 filter-exports <infile> <outfile>
Reads a binary WebAssembly module, removes all exports not used by the MicroW8 platform + everything that is unreachable without those exports and writes the resulting module to <outfile>. Reads a binary WebAssembly module, removes all exports not used by the MicroW8 platform + everything that is unreachable without those exports and writes the resulting module to <outfile>.

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
clang -O2 -Wno-incompatible-library-redeclaration --no-standard-libraries -ffast-math -Xclang -target-feature -Xclang +nontrapping-fptoint -Wl,--no-entry -Wl,--export-all -Wl,--import-memory -Wl,--initial-memory=262144 -Wl,-zstack-size=90000 -o cart.wasm cart.c --target=wasm32 && \ clang -O2 -Wno-incompatible-library-redeclaration --no-standard-libraries -ffast-math -Xclang -target-feature -Xclang +nontrapping-fptoint -Wl,--no-entry,--export-all,--import-memory,--initial-memory=262144,--global-base=81920,-zstack-size=4096 -o cart.wasm cart.c --target=wasm32 && \
uw8 filter-exports cart.wasm cart.wasm && \ uw8 filter-exports cart.wasm cart.wasm && \
wasm-opt -Oz --fast-math --strip-producers -o cart.wasm cart.wasm && \ wasm-opt -Oz --fast-math --strip-producers -o cart.wasm cart.wasm && \
uw8 pack -l 9 cart.wasm cart.uw8 uw8 pack -l 9 cart.wasm cart.uw8

View File

@@ -15,11 +15,14 @@ The initial motivation behind MicroW8 was to explore whether there was a way to
* Gamepad input (D-Pad + 4 Buttons) * Gamepad input (D-Pad + 4 Buttons)
## Examples ## Examples
* [Fireworks](v0.1pre5#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022. * [Fireworks](v0.1.1#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022.
* [Skip Ahead](v0.1pre5#AgyfpZ80wkW28kiUZ9VIK4v+RPnVxqjK1dz2BcDoNyQPsS2g4OgEzkTe6jyoAfFOmqKrS8SM2aRljBal9mjNn8i4fP9eBK+RehQKxxGtJa9FqftvqEnh3ez1YaYxqj7jgTdzJ/WAYVmKMovBT1myrX3FamqKSOgMsNedLhVTLAhQup3sNcYEjGNo8b0HZ5+AgMgCwYRGCe//XQOMAaAAzqDILgmpEZ/43RKHcQpHEQwbURfNQJpadJe2sz3q5FlQnTGXQ9oSMokidhlC+aR/IpNHieuBGLhFZ2GfnwVQ0geBbQpTPA==) (229 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21 * [Skip Ahead](v0.1.1#AgyfpZ80wkW28kiUZ9VIK4v+RPnVxqjK1dz2BcDoNyQPsS2g4OgEzkTe6jyoAfFOmqKrS8SM2aRljBal9mjNn8i4fP9eBK+RehQKxxGtJa9FqftvqEnh3ez1YaYxqj7jgTdzJ/WAYVmKMovBT1myrX3FamqKSOgMsNedLhVTLAhQup3sNcYEjGNo8b0HZ5+AgMgCwYRGCe//XQOMAaAAzqDILgmpEZ/43RKHcQpHEQwbURfNQJpadJe2sz3q5FlQnTGXQ9oSMokidhlC+aR/IpNHieuBGLhFZ2GfnwVQ0geBbQpTPA==) (229 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21
* [OhNoAnotherTunnel](v0.1pre4#Ag95rdCB5Ww5NofyQaKF4P1mrNRso4azgiem4hK99Gh8OMzSpFq3NsNDo7O7pqln10D11l9uXr/ritw7OEzKwbEfCdvaRnS2Z0Kz0iDEZt/gIqOdvFmxsL1MjPQ4XInPbUJpQUonhQq29oP2omFabnQxn0bzoK7mZjcwc5GetHG+hGajkJcRr8oOnjfCol8RD+ha33GYtPnut+GLe4ktzf5UxZwGs6oT9qqC61lRDakN) (177 bytes): A port of my [entry](http://tic80.com/play?cart=1871) in the Outline'21 bytebattle final * [OhNoAnotherTunnel](v0.1.1#AgPP1oEFvPzY/rBZwTumtYn37zeMFgpir1Bkn91jsNcp26VzoUpkAOOJTtnzVBfW+/dGnnIdbq/irBUJztY5wuua80DORTYZndgdwZHcSk15ajc4nyO0g1A6kGWyW56oZk0iPYJA9WtUmoj0Plvy1CGwIZrMe57X7QZcdqc3u6zjTA41Tpiqi9vnO3xbhi8o594Vx0XPXwVzpYq1ZCTYenfAGaXKkDmAFJqiVIsiCg==) (175 bytes): A port of my [entry](http://tic80.com/play?cart=1871) in the Outline'21 bytebattle final
* [Technotunnel](v0.1pre4#AqL8HeK1M9dn2nWNIF5vaq/Vh64pMt5nJIFoFKpBMPUsGtDtpqjo1JbT9LzPhAxCqJ7Yh4TA6oTGd4xhLowf+cWZMY73+7AZmfXJJsBi4cej/hH+4wlAgxFIrnOYnr/18IpnZbsHf0eGm1BhahX74+cVR0TRmNQmYC7GhCNS3mv/3MJn74lCj7t28aBJPjEZhP9fGXdG2u5Egh/Tjdg=) (158 bytes): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final * [Technotunnel](v0.1.1#AhPXpq894LaUhp5+HQf39f39/Jc8g5zUrBSc0uyKh36ivskczhY84h55zL8gWpkdvKuRQI+KIt80isKzh8jkM8nILcx0RUvyk8yjE8TgNsgkcORVI0RY5k3qE4ySjaycxa2DVZH61UWZuLsCouuwT7I80TbmmetQSbMywJ/avrrCZIAH0UzQfvOiCJNG48NI0FFY1vjB7a7dcp8Uqg==) (157 bytes): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final
* [Font & Palette](v0.1pre4#AgKaeeOuwg5gCKvFIeiitEwMpUI2rymEcu+DDB1vMu9uBoufvUxIr4Y5p4Jj2ukoNO4PE7QS5cN1ZyDMCRfSzYIGZxKlN2J6NKEWK7KVPk9wVUgn1Ip+hsMinWgEO8ETKfPuHoIa4kjI+ULFOMad7vd3rt/lh1Vy9w+R2MXG/7T61d3c7C6KY+eQNS0eW3ys4iU8R6SycuWZuuZ2Sg3Qxp826s+Kt+2qBojpzNOSoyFqyrVyYMTKEkSl0BZOj59Cs1hPm5bq0F1MmVhGAzMhW9V4YeAe): Just a simple viewer for the default font and palette. * [Font & Palette](v0.1.1#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ): Just a simple viewer for the default font and palette.
Examplers for older versions:
* [Technotunnel B/W](v0.1pre2#AQrDAQHAAQIBfwp9A0AgAUEAsiABQcACb7JDmhkgQ5MiBCAEIASUIAFBwAJtQfgAa7IiBSAFlJKRIgaVIgcgByAAskHQD7KVIgIQAEPNzEw/lCIDlCAHIAeUIAOUIAOUQQGykiADIAOUk5GSIgiUIAOTQQqylCACkiIJqCAFIAaVIAiUQQqylCACkiIKqHMgCEEyspQgBpUiCyACkkEUspSocUEFcbJBArIgC5OUQRaylJeoOgB4IAFBAWoiAUGA2ARIDQALCw==) (199 bytes uncompressed): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final (older MicroW8 version with monochrome palette) * [Technotunnel B/W](v0.1pre2#AQrDAQHAAQIBfwp9A0AgAUEAsiABQcACb7JDmhkgQ5MiBCAEIASUIAFBwAJtQfgAa7IiBSAFlJKRIgaVIgcgByAAskHQD7KVIgIQAEPNzEw/lCIDlCAHIAeUIAOUIAOUQQGykiADIAOUk5GSIgiUIAOTQQqylCACkiIJqCAFIAaVIAiUQQqylCACkiIKqHMgCEEyspQgBpUiCyACkkEUspSocUEFcbJBArIgC5OUQRaylJeoOgB4IAFBAWoiAUGA2ARIDQALCw==) (199 bytes uncompressed): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final (older MicroW8 version with monochrome palette)
* [XorScroll](v0.1pre2#AQovAS0BAX8DQCABIAFBwAJvIABBCm1qIAFBwAJtczoAeCABQQFqIgFBgNgESA0ACws=) (50 bytes uncompressed): A simple scrolling XOR pattern. Fun fact: This is the pre-loaded effect when entering a bytebattle. * [XorScroll](v0.1pre2#AQovAS0BAX8DQCABIAFBwAJvIABBCm1qIAFBwAJtczoAeCABQQFqIgFBgNgESA0ACws=) (50 bytes uncompressed): A simple scrolling XOR pattern. Fun fact: This is the pre-loaded effect when entering a bytebattle.
* [CircleWorm](v0.1pre2#AQp7AXkCAX8CfUEgEA0DQCABskEEspUiAkECspUgALJBiCeylSIDQQWylJIQAEEBspJBoAGylCACQQOylSADQQSylJIQAEEBspJB+ACylCADQRGylCACQQKylJIQAEECspJBELKUIAFBAmxBP2oQEiABQQFqIgFBP0gNAAsL) (126 bytes uncompressed): Just a test for the circle fill function. * [CircleWorm](v0.1pre2#AQp7AXkCAX8CfUEgEA0DQCABskEEspUiAkECspUgALJBiCeylSIDQQWylJIQAEEBspJBoAGylCACQQOylSADQQSylJIQAEEBspJB+ACylCADQRGylCACQQKylJIQAEECspJBELKUIAFBAmxBP2oQEiABQQFqIgFBP0gNAAsL) (126 bytes uncompressed): Just a test for the circle fill function.

View File

@@ -105,7 +105,7 @@ as a cheap random-access PRNG (aka noise function).
## Graphics ## Graphics
The default palette can be seen [here](../v0.1pre4#AgKaeeOuwg5gCKvFIeiitEwMpUI2rymEcu+DDB1vMu9uBoufvUxIr4Y5p4Jj2ukoNO4PE7QS5cN1ZyDMCRfSzYIGZxKlN2J6NKEWK7KVPk9wVUgn1Ip+hsMinWgEO8ETKfPuHoIa4kjI+ULFOMad7vd3rt/lh1Vy9w+R2MXG/7T61d3c7C6KY+eQNS0eW3ys4iU8R6SycuWZuuZ2Sg3Qxp826s+Kt+2qBojpzNOSoyFqyrVyYMTKEkSl0BZOj59Cs1hPm5bq0F1MmVhGAzMhW9V4YeAe). (Press Z on the keyboard to switch to palette.) The default palette can be seen [here](../v0.1.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ). (Press Z on the keyboard to switch to palette.)
The palette can be changed by writing 32bit rgba colors to addresses 0x13000-0x13400. The palette can be changed by writing 32bit rgba colors to addresses 0x13000-0x13400.
@@ -192,7 +192,7 @@ The integer time in milliseconds can also be read at address 0x40.
## Text output ## Text output
The default font can be seen [here](../v0.1pre4#AgKaeeOuwg5gCKvFIeiitEwMpUI2rymEcu+DDB1vMu9uBoufvUxIr4Y5p4Jj2ukoNO4PE7QS5cN1ZyDMCRfSzYIGZxKlN2J6NKEWK7KVPk9wVUgn1Ip+hsMinWgEO8ETKfPuHoIa4kjI+ULFOMad7vd3rt/lh1Vy9w+R2MXG/7T61d3c7C6KY+eQNS0eW3ys4iU8R6SycuWZuuZ2Sg3Qxp826s+Kt+2qBojpzNOSoyFqyrVyYMTKEkSl0BZOj59Cs1hPm5bq0F1MmVhGAzMhW9V4YeAe). The default font can be seen [here](../v0.1.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ).
The font can be changed by writing 1bpp 8x8 characters to addresses 0x13400-0x13c00. The font can be changed by writing 1bpp 8x8 characters to addresses 0x13400-0x13c00.
@@ -305,6 +305,27 @@ Options:
* `-u`, `--uncompressed`: Use the uncompressed `uw8` format for packing. * `-u`, `--uncompressed`: Use the uncompressed `uw8` format for packing.
* `-l LEVEL`, `--level LEVEL`: Compression level (0-9). Higher compression levels are really slow. * `-l LEVEL`, `--level LEVEL`: Compression level (0-9). Higher compression levels are really slow.
## `uw8 unpack`
Usage:
`uw8 unpack <infile> <outfile>`
Unpacks a MicroW8 module into a standard WebAssembly module.
## `uw8 compile`
Usage:
`uw8 compile [<options>] <infile> <outfile>`
Compiles a [CurlyWas](https://github.com/exoticorn/curlywas) source file to a standard WebAssembly module. Most useful together with
the `--debug` option to get a module that works well in the Chrome debugger.
Options:
* `-d`, `--debug`: Generate a name section to help debugging
## `uw8 filter-exports` ## `uw8 filter-exports`
Usage: Usage:
@@ -390,3 +411,33 @@ the first function in the file under the name `upd`.
Same as version `01` except everything after the first byte is compressed Same as version `01` except everything after the first byte is compressed
using a [custom LZ compression scheme](https://github.com/exoticorn/upkr). using a [custom LZ compression scheme](https://github.com/exoticorn/upkr).
# The web runtime
Load carts into the web runtime either by using the "Load cart..." button, or by dragging the file
onto the screen area.
## Input
For input, you can either use a standard gamepad or keyboard. On a keyboard use the arrow keys and the keys Z, X, A and S to emulate the A, B, X and Y buttons.
## Video recording
Press F10 to start recording, press again to stop. Then a download dialog will open for the video file.
The file might miss some metadata needed to load in some video editing tools, in that case you can run
it through ffmpeg like this `ffmpeg -i IN_NAME.webm -c copy -o OUT_NAME.webm to fix it up.
To convert it to 1280x720, for example for a lovebyte upload, you can use:
```
ffmpeg -i IN.webm -vf "scale=960:720:flags=neighbor,pad=1280:720:160:0" -r 60 OUT.mp4
```
## Screenshot
Pressing F9 opens a download dialog with a screenshot.
## Devkit mode
Append `#devkit` to the web runtime url in order to switch to devkit mode. In devkit mode, standard web assembly modules
are loaded bypassing the loader, removing all size restrictions. At the same time, the memory limit is increased to 1GB.

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
<section> <section>
<h1 class="text-center heading-text">A WebAssembly based fantasy console</h1> <h1 class="text-center heading-text">A WebAssembly based fantasy console</h1>
</section> </section>
<a href="v0.1.0"> <a href="v0.1.1">
<img class="demonstration-gif" style="width:640px;height:480px;image-rendering:pixelated" src="img/technotunnel.png"></img> <img class="demonstration-gif" style="width:640px;height:480px;image-rendering:pixelated" src="img/technotunnel.png"></img>
</a> </a>
</div> </div>

View File

@@ -20,6 +20,8 @@ fn main() -> Result<()> {
} }
Some("run") => run(args), Some("run") => run(args),
Some("pack") => pack(args), Some("pack") => pack(args),
Some("unpack") => unpack(args),
Some("compile") => compile(args),
Some("filter-exports") => filter_exports(args), Some("filter-exports") => filter_exports(args),
Some("help") | None => { Some("help") | None => {
println!("uw8 {}", env!("CARGO_PKG_VERSION")); println!("uw8 {}", env!("CARGO_PKG_VERSION"));
@@ -27,6 +29,8 @@ fn main() -> Result<()> {
println!("Usage:"); println!("Usage:");
println!(" uw8 run [-t/--timeout <frames>] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output <out-file>] <file>"); println!(" uw8 run [-t/--timeout <frames>] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output <out-file>] <file>");
println!(" uw8 pack [-u/--uncompressed] [-l/--level] <in-file> <out-file>"); println!(" uw8 pack [-u/--uncompressed] [-l/--level] <in-file> <out-file>");
println!(" uw8 unpack <in-file> <out-file>");
println!(" uw8 compile [-d/--debug] <in-file> <out-file>");
println!(" uw8 filter-exports <in-wasm> <out-wasm>"); println!(" uw8 filter-exports <in-wasm> <out-wasm>");
Ok(()) Ok(())
} }
@@ -166,6 +170,28 @@ fn pack(mut args: Arguments) -> Result<()> {
Ok(()) Ok(())
} }
fn unpack(mut args: Arguments) -> Result<()> {
let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
uw8_tool::unpack_file(&in_file, &out_file).into()
}
fn compile(mut args: Arguments) -> Result<()> {
let mut options = curlywas::Options::default();
if args.contains(["-d", "--debug"]) {
options = options.with_debug();
}
let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let module = curlywas::compile_file(in_file, options)?;
File::create(out_file)?.write_all(&module)?;
Ok(())
}
fn filter_exports(mut args: Arguments) -> Result<()> { fn filter_exports(mut args: Arguments) -> Result<()> {
let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?; let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?; let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;

View File

@@ -10,7 +10,7 @@ use std::{
use wasm_encoder as enc; use wasm_encoder as enc;
use wasmparser::{ use wasmparser::{
BinaryReader, ExportSectionReader, ExternalKind, FunctionBody, FunctionSectionReader, BinaryReader, ExportSectionReader, ExternalKind, FunctionBody, FunctionSectionReader,
ImportSectionEntryType, ImportSectionReader, TypeSectionReader, ImportSectionEntryType, ImportSectionReader, TableSectionReader, TypeSectionReader,
}; };
pub struct PackConfig { pub struct PackConfig {
@@ -31,7 +31,9 @@ impl PackConfig {
impl Default for PackConfig { impl Default for PackConfig {
fn default() -> PackConfig { fn default() -> PackConfig {
PackConfig { compression: Some(2) } PackConfig {
compression: Some(2),
}
} }
} }
@@ -156,6 +158,8 @@ struct ParsedModule<'a> {
start_section: Option<u32>, start_section: Option<u32>,
function_bodies: Vec<wasmparser::FunctionBody<'a>>, function_bodies: Vec<wasmparser::FunctionBody<'a>>,
data_section: Option<Section<()>>, data_section: Option<Section<()>>,
table_section: Option<Section<()>>,
element_section: Option<Vec<Element>>,
} }
impl<'a> ParsedModule<'a> { impl<'a> ParsedModule<'a> {
@@ -170,6 +174,8 @@ impl<'a> ParsedModule<'a> {
let mut start_section = None; let mut start_section = None;
let mut function_bodies = Vec::new(); let mut function_bodies = Vec::new();
let mut data_section = None; let mut data_section = None;
let mut table_section = None;
let mut element_section = None;
let mut offset = 0; let mut offset = 0;
@@ -209,6 +215,17 @@ impl<'a> ParsedModule<'a> {
Payload::DataSection(_) => { Payload::DataSection(_) => {
data_section = Some(Section::new(range, ())); data_section = Some(Section::new(range, ()));
} }
Payload::TableSection(reader) => {
validate_table_section(reader)?;
table_section = Some(Section::new(range, ()));
}
Payload::ElementSection(mut reader) => {
let mut elements = Vec::with_capacity(reader.get_count() as usize);
for _ in 0..reader.get_count() {
elements.push(Element::parse(reader.read()?)?);
}
element_section = Some(elements);
}
Payload::CodeSectionStart { .. } => (), Payload::CodeSectionStart { .. } => (),
Payload::CodeSectionEntry(body) => function_bodies.push(body), Payload::CodeSectionEntry(body) => function_bodies.push(body),
Payload::CustomSection { .. } => (), Payload::CustomSection { .. } => (),
@@ -229,6 +246,8 @@ impl<'a> ParsedModule<'a> {
start_section, start_section,
function_bodies, function_bodies,
data_section, data_section,
table_section,
element_section,
}) })
} }
@@ -405,6 +424,10 @@ impl<'a> ParsedModule<'a> {
module.section(&function_section); module.section(&function_section);
} }
if let Some(tables) = self.table_section {
copy_section(&mut module, &self.data[tables.range.clone()])?;
}
if let Some(ref globals) = self.globals { if let Some(ref globals) = self.globals {
copy_section(&mut module, &self.data[globals.range.clone()])?; copy_section(&mut module, &self.data[globals.range.clone()])?;
for i in 0..globals.data { for i in 0..globals.data {
@@ -446,6 +469,25 @@ impl<'a> ParsedModule<'a> {
}); });
} }
if let Some(elements) = self.element_section {
let mut element_section = wasm_encoder::ElementSection::new();
for element in elements {
let mut functions = Vec::with_capacity(element.functions.len());
for index in element.functions {
functions.push(*function_map.get(&index).ok_or_else(|| {
anyhow!("Function index {} not found in function map", index)
})?);
}
element_section.active(
None,
&wasm_encoder::Instruction::I32Const(element.start_index as i32),
ValType::FuncRef,
wasm_encoder::Elements::Functions(&functions),
);
}
module.section(&element_section);
}
{ {
let mut code_section = enc::CodeSection::new(); let mut code_section = enc::CodeSection::new();
@@ -502,6 +544,19 @@ fn read_type_section(reader: TypeSectionReader) -> Result<Vec<base_module::Funct
Ok(function_types) Ok(function_types)
} }
fn validate_table_section(mut reader: TableSectionReader) -> Result<()> {
if reader.get_count() != 1 {
bail!("Only up to one table supported");
}
let type_ = reader.read()?;
if type_.element_type != wasmparser::Type::FuncRef {
bail!("Only one funcref table is supported");
}
Ok(())
}
#[derive(Debug)] #[derive(Debug)]
struct Section<T> { struct Section<T> {
range: std::ops::Range<usize>, range: std::ops::Range<usize>,
@@ -579,6 +634,51 @@ impl ImportSection {
} }
} }
#[derive(Debug)]
struct Element {
start_index: u32,
functions: Vec<u32>,
}
impl Element {
fn parse(element: wasmparser::Element) -> Result<Element> {
if element.ty != wasmparser::Type::FuncRef {
bail!("Table element type is not FuncRef");
}
let start_index = if let wasmparser::ElementKind::Active {
init_expr,
table_index: 0,
} = element.kind
{
let mut init_reader = init_expr.get_operators_reader();
if let wasmparser::Operator::I32Const { value: start_index } = init_reader.read()? {
start_index as u32
} else {
bail!("Table element start index is not a integer constant");
}
} else {
bail!("Unsupported table element kind");
};
let mut items_reader = element.items.get_items_reader()?;
let mut functions = Vec::with_capacity(items_reader.get_count() as usize);
for _ in 0..items_reader.get_count() {
if let wasmparser::ElementItem::Func(index) = items_reader.read()? {
functions.push(index);
} else {
bail!("Table element item is not a function");
}
}
Ok(Element {
start_index,
functions,
})
}
}
#[derive(Debug)] #[derive(Debug)]
struct FunctionImport { struct FunctionImport {
module: String, module: String,
@@ -678,8 +778,13 @@ fn remap_function(
.get(&function_index) .get(&function_index)
.ok_or_else(|| anyhow!("Function index out of range: {}", function_index))?, .ok_or_else(|| anyhow!("Function index out of range: {}", function_index))?,
), ),
De::CallIndirect { .. } De::CallIndirect { index, table_index } => En::CallIndirect {
| De::ReturnCall { .. } ty: *type_map
.get(&index)
.ok_or_else(|| anyhow!("Unknown function type in call indirect"))?,
table: table_index,
},
De::ReturnCall { .. }
| De::ReturnCallIndirect { .. } | De::ReturnCallIndirect { .. }
| De::Delegate { .. } | De::Delegate { .. }
| De::CatchAll => todo!(), | De::CatchAll => todo!(),

View File

@@ -10,7 +10,7 @@
</head> </head>
<body> <body>
<div id="uw8"> <div id="uw8">
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.1.0 <a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.1.1
</div> </div>
<div id="centered"> <div id="centered">
<canvas id="screen" width="320" height="240"> <canvas id="screen" width="320" height="240">

View File

@@ -13,6 +13,8 @@ let screen = document.getElementById('screen');
let canvasCtx = screen.getContext('2d'); let canvasCtx = screen.getContext('2d');
let imageData = canvasCtx.createImageData(320, 240); let imageData = canvasCtx.createImageData(320, 240);
let devkitMode;
let cancelFunction; let cancelFunction;
let currentData; let currentData;
@@ -51,9 +53,17 @@ let keyHandler = (e) => {
break; break;
case 'KeyR': case 'KeyR':
if (isKeyDown) { if (isKeyDown) {
runModule(currentData); runModule(currentData, true);
} }
break; break;
case 'F9':
if(isKeyDown) {
screen.toBlob(blob => {
downloadBlob(blob, '.png');
});
}
e.preventDefault();
break;
case 'F10': case 'F10':
if(isKeyDown) { if(isKeyDown) {
recordVideo(); recordVideo();
@@ -102,7 +112,11 @@ async function runModule(data, keepUrl) {
screen.width = screen.width; screen.width = screen.width;
try { try {
let memory = new WebAssembly.Memory({ initial: 4, maximum: 4 }); let memSize = { initial: 4 };
if(!devkitMode) {
memSize.maximum = 4;
}
let memory = new WebAssembly.Memory({ initial: 4, maximum: devkitMode ? 16 : 4 });
let memU8 = U8(memory.buffer); let memU8 = U8(memory.buffer);
let importObject = { let importObject = {
@@ -114,7 +128,7 @@ async function runModule(data, keepUrl) {
let loader; let loader;
let loadModuleData = (data) => { let loadModuleData = (data) => {
if (U8(data)[0] != 0) { if (loader && (!devkitMode || U8(data)[0] != 0)) {
memU8.set(U8(data)); memU8.set(U8(data));
let length = loader.exports.load_uw8(data.byteLength); let length = loader.exports.load_uw8(data.byteLength);
data = new ArrayBuffer(length); data = new ArrayBuffer(length);
@@ -226,6 +240,14 @@ async function runModule(data, keepUrl) {
} }
} }
function downloadBlob(blob, ext) {
let a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'microw8_' + new Date().toISOString() + ext;
a.click();
URL.revokeObjectURL(a.href);
}
let videoRecorder; let videoRecorder;
let videoStartTime; let videoStartTime;
function recordVideo() { function recordVideo() {
@@ -237,7 +259,7 @@ function recordVideo() {
videoRecorder = new MediaRecorder(screen.captureStream(), { videoRecorder = new MediaRecorder(screen.captureStream(), {
mimeType: 'video/webm', mimeType: 'video/webm',
videoBitsPerSecond: 5000000 videoBitsPerSecond: 25000000
}); });
let chunks = []; let chunks = [];
@@ -251,11 +273,7 @@ function recordVideo() {
videoRecorder.onstop = () => { videoRecorder.onstop = () => {
timer.hidden = true; timer.hidden = true;
let a = document.createElement('a'); downloadBlob(new Blob(chunks, {type: 'video/webm'}), '.webm');
a.href = URL.createObjectURL(new Blob(chunks, {type: 'video/webm'}));
a.download = 'microw8_' + new Date().toISOString() + 'webm';
a.click();
URL.revokeObjectURL(a.href);
}; };
videoRecorder.start(); videoRecorder.start();
@@ -281,11 +299,16 @@ async function runModuleFromURL(url, keepUrl) {
if(type && type.includes('html')) { if(type && type.includes('html')) {
throw false; throw false;
} }
runModule(await response.arrayBuffer(), keepUrl); runModule(await response.arrayBuffer(), keepUrl || devkitMode);
} }
function runModuleFromHash() { function runModuleFromHash() {
let hash = window.location.hash.slice(1); let hash = window.location.hash.slice(1);
if(hash == 'devkit') {
devkitMode = true;
return;
}
devkitMode = false;
if (hash.length > 0) { if (hash.length > 0) {
if (hash.startsWith("url=")) { if (hash.startsWith("url=")) {
runModuleFromURL(hash.slice(4), true); runModuleFromURL(hash.slice(4), true);