move all imports to module "env", some tweaks to rust example

This commit is contained in:
2021-11-06 19:46:16 +01:00
parent 32d2519d2d
commit d86f91789b
8 changed files with 126 additions and 86 deletions

View File

@@ -5,8 +5,8 @@ A nightly rust compiler is needed for the unstable sqrtf32
intrinsic. intrinsic.
Simply compiling with rustc as shown in build.sh results in a Simply compiling with rustc as shown in build.sh results in a
339 byte tunnel.wasm. Using wasm-opt this can be reduced to 342 byte tunnel.wasm. Using wasm-opt this can be reduced to
244 bytes. 243 bytes.
When you disassemble this wasm file using wasm2wat you can see When you disassemble this wasm file using wasm2wat you can see
these globals and exports: these globals and exports:
@@ -21,5 +21,5 @@ values that are not simple scalars (i32, f32, etc.). Since our
code doesn't actually use any of that, we can just delete them code doesn't actually use any of that, we can just delete them
in a text editor and assemble the code again with wat2wasm. in a text editor and assemble the code again with wat2wasm.
This gives us a 200 byte wasm file. Running this through This gives us a 199 byte wasm file. Running this through
uw8-tool pack brings us to the final size of 137 bytes. uw8-tool pack brings us to the final size of 137 bytes.

View File

@@ -1,25 +1,43 @@
#![no_std] #![no_std]
#![feature(core_intrinsics)] #![feature(core_intrinsics)]
#[link(wasm_import_module = "math")] mod env {
extern "C" { // "env" is the default module for imports, but it is still needed here
fn atan2(x: f32, y: f32) -> f32; // since there is a compiler builtin of the same name which is used
// if we don't make it clear that this is a module import.
#[link(wasm_import_module = "env")]
extern "C" {
pub fn atan2(x: f32, y: f32) -> f32;
}
}
fn atan2(x: f32, y: f32) -> f32 {
unsafe { env::atan2(x, y) }
} }
fn sqrt(v: f32) -> f32 { fn sqrt(v: f32) -> f32 {
unsafe { core::intrinsics::sqrtf32(v) } unsafe { core::intrinsics::sqrtf32(v) }
} }
fn ftoi(v: f32) -> i32 {
// The compiler is allowed to do bad things to our code if this
// ever results in a value that doesn't fit in an i32.
// (the joy of undefined behavior)
// But that would trap in wasm anyway, so we don't really
// care.
unsafe { v.to_int_unchecked() }
}
#[no_mangle] #[no_mangle]
pub fn tic(time: i32) { pub fn tic(time: i32) {
unsafe { for i in 0..320 * 256 {
for i in 0..320 * 256 { let t = time as f32 / 10 as f32;
let t = time as f32 / 10 as f32; let x = (i % 320 - 160) as f32;
let x = (i % 320 - 160) as f32; let y = (i / 320 - 128) as f32;
let y = (i / 320 - 128) as f32; let d = 20000 as f32 / sqrt(x * x + y * y + 1 as f32);
let d = 20000 as f32 / sqrt(x * x + y * y + 1 as f32); let u = atan2(x, y) * 512f32 / 3.141;
let u = atan2(x, y) * 512f32 / 3.141; let c = (ftoi(d + t) ^ ftoi(u + t)) as u8;
let c = ((d + t).to_int_unchecked::<i32>() ^ (u + t).to_int_unchecked::<i32>()) as u8; unsafe {
*((120 + i) as *mut u8) = c; *((120 + i) as *mut u8) = c;
} }
} }

Binary file not shown.

1
site/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/public/

View File

@@ -14,19 +14,18 @@ at offset 120 in memory.
The memory has to be imported as `"env" "memory"` and has a maximum size of 256kb (4 pages). The memory has to be imported as `"env" "memory"` and has a maximum size of 256kb (4 pages).
Other imports provided by the platform: Other imports provided by the platform, also all in module `env`:
* in module `math`: * `fn acos(f32) -> f32`
* * `fn acos(f32) -> f32` * `fn asin(f32) -> f32`
* * `fn asin(f32) -> f32` * `fn atan(f32) -> f32`
* * `fn atan(f32) -> f32` * `fn atan2(f32, f32) -> f32`
* * `fn atan2(f32, f32) -> f32` * `fn cos(f32) -> f32`
* * `fn cos(f32) -> f32` * `fn exp(f32, f32) -> f32`
* * `fn exp(f32, f32) -> f32` * `fn log(f32) -> f32`
* * `fn log(f32) -> f32` * `fn sin(f32) -> f32`
* * `fn sin(f32) -> f32` * `fn tan(f32) -> f32`
* * `fn tan(f32) -> f32` * `fn pow(f32) -> f32`
* * `fn pow(f32) -> f32`
## `.uw8` format ## `.uw8` format

File diff suppressed because one or more lines are too long

View File

@@ -1,25 +1,25 @@
use std::{collections::HashMap, fs::File, path::Path}; use std::{collections::HashMap, fs::File, path::Path};
use anyhow::{bail, Result};
use std::io::prelude::*;
use wasm_encoder::{ use wasm_encoder::{
CodeSection, EntityType, Export, ExportSection, Function, FunctionSection, CodeSection, EntityType, Export, ExportSection, Function, FunctionSection, ImportSection,
ImportSection, Instruction, MemoryType, Module, TypeSection, ValType, Instruction, MemoryType, Module, TypeSection, ValType,
}; };
use ValType::*; use ValType::*;
use anyhow::{Result, bail};
use std::io::prelude::*;
pub struct BaseModule { pub struct BaseModule {
pub types: Vec<FunctionType>, pub types: Vec<FunctionType>,
pub function_imports: Vec<(&'static str, String, u32)>, pub function_imports: Vec<(&'static str, String, u32)>,
pub functions: Vec<u32>, pub functions: Vec<u32>,
pub exports: Vec<(&'static str, u32)>, pub exports: Vec<(&'static str, u32)>,
pub memory: u32 pub memory: u32,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FunctionType { pub struct FunctionType {
pub params: Vec<ValType>, pub params: Vec<ValType>,
pub result: Option<ValType> pub result: Option<ValType>,
} }
impl BaseModule { impl BaseModule {
@@ -48,18 +48,24 @@ impl BaseModule {
} }
let mut functions = vec![]; let mut functions = vec![];
add_function(&mut functions, &type_map, "math","sin", &[F32], Some(F32)); add_function(&mut functions, &type_map, "sin", &[F32], Some(F32));
add_function(&mut functions, &type_map, "math", "cos", &[F32], Some(F32)); add_function(&mut functions, &type_map, "cos", &[F32], Some(F32));
add_function(&mut functions, &type_map, "math", "tan", &[F32], Some(F32)); add_function(&mut functions, &type_map, "tan", &[F32], Some(F32));
add_function(&mut functions, &type_map, "math", "asin", &[F32], Some(F32)); add_function(&mut functions, &type_map, "asin", &[F32], Some(F32));
add_function(&mut functions, &type_map, "math", "acos", &[F32], Some(F32)); add_function(&mut functions, &type_map, "acos", &[F32], Some(F32));
add_function(&mut functions, &type_map, "math", "atan", &[F32], Some(F32)); add_function(&mut functions, &type_map, "atan", &[F32], Some(F32));
add_function(&mut functions, &type_map, "math", "atan2", &[F32, F32], Some(F32)); add_function(&mut functions, &type_map, "atan2", &[F32, F32], Some(F32));
add_function(&mut functions, &type_map, "math", "pow", &[F32, F32], Some(F32)); add_function(&mut functions, &type_map, "pow", &[F32, F32], Some(F32));
add_function(&mut functions, &type_map, "math", "log", &[F32], Some(F32)); add_function(&mut functions, &type_map, "log", &[F32], Some(F32));
for i in functions.len()..64 { for i in functions.len()..64 {
add_function(&mut functions, &type_map, "uw8", &format!("reserved{}", i), &[], None); add_function(
&mut functions,
&type_map,
&format!("reserved{}", i),
&[],
None,
);
} }
let first_function = functions.len() as u32; let first_function = functions.len() as u32;
@@ -69,7 +75,7 @@ impl BaseModule {
function_imports: functions, function_imports: functions,
functions: vec![lookup_type(&type_map, &[I32], None)], functions: vec![lookup_type(&type_map, &[I32], None)],
exports: vec![("tic", first_function)], exports: vec![("tic", first_function)],
memory: 4 memory: 4,
}) })
} }
@@ -92,11 +98,15 @@ impl BaseModule {
imports.import(*module, Some(name.as_str()), EntityType::Function(*type_)); imports.import(*module, Some(name.as_str()), EntityType::Function(*type_));
} }
imports.import("env", Some("memory"), MemoryType { imports.import(
minimum: m.memory as u64, "env",
maximum: None, Some("memory"),
memory64: false MemoryType {
}); minimum: m.memory as u64,
maximum: None,
memory64: false,
},
);
module.section(&imports); module.section(&imports);
} }
@@ -143,14 +153,28 @@ impl BaseModule {
} }
} }
fn add_function(functions: &mut Vec<(&'static str, String, u32)>, type_map: &HashMap<FunctionType, u32>, module: &'static str, name: &str, params: &[ValType], result: Option<ValType>) { fn add_function(
functions.push((module, name.to_string(), lookup_type(type_map, params, result))); functions: &mut Vec<(&'static str, String, u32)>,
type_map: &HashMap<FunctionType, u32>,
name: &str,
params: &[ValType],
result: Option<ValType>,
) {
functions.push((
"env".into(),
name.to_string(),
lookup_type(type_map, params, result),
));
} }
fn lookup_type(type_map: &HashMap<FunctionType, u32>, params: &[ValType], result: Option<ValType>) -> u32 { fn lookup_type(
type_map: &HashMap<FunctionType, u32>,
params: &[ValType],
result: Option<ValType>,
) -> u32 {
let key = FunctionType { let key = FunctionType {
params: params.to_vec(), params: params.to_vec(),
result result,
}; };
*type_map.get(&key).unwrap() *type_map.get(&key).unwrap()
} }

View File

@@ -10,7 +10,7 @@ async function loadWasm(url, imports) {
function setMessage(size, error) { function setMessage(size, error) {
let html = size ? `${size} bytes` : 'Insert cart'; let html = size ? `${size} bytes` : 'Insert cart';
if(error) { if (error) {
html += ` - <span class="error">${error.replaceAll('<', '&lt;')}</span>` html += ` - <span class="error">${error.replaceAll('<', '&lt;')}</span>`
} }
document.getElementById('message').innerHTML = html; document.getElementById('message').innerHTML = html;
@@ -27,7 +27,7 @@ let canvasCtx = screen.getContext('2d');
let cancelFunction; let cancelFunction;
async function runModule(data) { async function runModule(data) {
if(cancelFunction) { if (cancelFunction) {
cancelFunction(); cancelFunction();
cancelFunction = null; cancelFunction = null;
} }
@@ -35,21 +35,21 @@ async function runModule(data) {
let cartridgeSize = data.byteLength; let cartridgeSize = data.byteLength;
setMessage(cartridgeSize); setMessage(cartridgeSize);
if(cartridgeSize == 0) { if (cartridgeSize == 0) {
return; return;
} }
let dataU8Array = new Uint8Array(data); let dataU8Array = new Uint8Array(data);
let newURL = window.location.pathname; let newURL = window.location.pathname;
if(cartridgeSize <= 1024) { if (cartridgeSize <= 1024) {
let dataString = ''; let dataString = '';
for(let byte of dataU8Array) { for (let byte of dataU8Array) {
dataString += String.fromCharCode(byte); dataString += String.fromCharCode(byte);
} }
newURL += '#' + btoa(dataString); newURL += '#' + btoa(dataString);
} }
if(newURL != window.location.pathname + window.location.hash) { if (newURL != window.location.pathname + window.location.hash) {
history.pushState(null, null, newURL); history.pushState(null, null, newURL);
} }
@@ -67,7 +67,7 @@ async function runModule(data) {
let baseModule = await (await fetch(baseUrl)).arrayBuffer(); let baseModule = await (await fetch(baseUrl)).arrayBuffer();
if(dataU8Array[0] != 0) { if (dataU8Array[0] != 0) {
new Uint8Array(loadMem).set(dataU8Array); new Uint8Array(loadMem).set(dataU8Array);
new Uint8Array(loadMem).set(new Uint8Array(baseModule), data.byteLength); new Uint8Array(loadMem).set(new Uint8Array(baseModule), data.byteLength);
@@ -82,16 +82,14 @@ async function runModule(data) {
env: { env: {
memory: new WebAssembly.Memory({ initial: 4, maximum: 4 }), memory: new WebAssembly.Memory({ initial: 4, maximum: 4 }),
}, },
math: {},
uw8: {}
}; };
for(let n of ['acos','asin','atan','atan2','cos','exp','log','sin','tan','pow']) { for (let n of ['acos', 'asin', 'atan', 'atan2', 'cos', 'exp', 'log', 'sin', 'tan', 'pow']) {
importObject.math[n] = Math[n]; importObject.env[n] = Math[n];
} }
for(let i = 9; i < 64; ++i) { for (let i = 9; i < 64; ++i) {
importObject.uw8['reserved' + i] = () => {}; importObject.env['reserved' + i] = () => { };
} }
let instance = new WebAssembly.Instance(await WebAssembly.compile(data), importObject); let instance = new WebAssembly.Instance(await WebAssembly.compile(data), importObject);
@@ -104,15 +102,15 @@ async function runModule(data) {
cancelFunction = () => keepRunning = false; cancelFunction = () => keepRunning = false;
function mainloop() { function mainloop() {
if(!keepRunning) { if (!keepRunning) {
return; return;
} }
try { try {
instance.exports.tic(Date.now() - startTime); instance.exports.tic(Date.now() - startTime);
let framebuffer = new Uint8Array(importObject.env.memory.buffer.slice(120, 120 + 320*256)); let framebuffer = new Uint8Array(importObject.env.memory.buffer.slice(120, 120 + 320 * 256));
for(let i = 0; i < 320*256; ++i) { for (let i = 0; i < 320 * 256; ++i) {
buffer[i * 4] = framebuffer[i]; buffer[i * 4] = framebuffer[i];
buffer[i * 4 + 1] = framebuffer[i]; buffer[i * 4 + 1] = framebuffer[i];
buffer[i * 4 + 2] = framebuffer[i]; buffer[i * 4 + 2] = framebuffer[i];
@@ -123,13 +121,13 @@ async function runModule(data) {
canvasCtx.drawImage(framebufferCanvas, 0, 0, 640, 512); canvasCtx.drawImage(framebufferCanvas, 0, 0, 640, 512);
window.requestAnimationFrame(mainloop); window.requestAnimationFrame(mainloop);
} catch(err) { } catch (err) {
setMessage(cartridgeSize, err.toString()); setMessage(cartridgeSize, err.toString());
} }
} }
mainloop(); mainloop();
} catch(err) { } catch (err) {
setMessage(cartridgeSize, err.toString()); setMessage(cartridgeSize, err.toString());
} }
} }
@@ -140,7 +138,7 @@ async function runModuleFromURL(url) {
function runModuleFromHash() { function runModuleFromHash() {
let base64Data = window.location.hash.slice(1); let base64Data = window.location.hash.slice(1);
if(base64Data.length > 0) { if (base64Data.length > 0) {
runModuleFromURL('data:;base64,' + base64Data); runModuleFromURL('data:;base64,' + base64Data);
} else { } else {
runModule(new ArrayBuffer(0)); runModule(new ArrayBuffer(0));
@@ -155,7 +153,7 @@ document.getElementById('cartButton').onclick = () => {
fileInput.type = 'file'; fileInput.type = 'file';
fileInput.accept = '.wasm,.uw8,application/wasm'; fileInput.accept = '.wasm,.uw8,application/wasm';
fileInput.onchange = () => { fileInput.onchange = () => {
if(fileInput.files.length > 0) { if (fileInput.files.length > 0) {
runModuleFromURL(URL.createObjectURL(fileInput.files[0])); runModuleFromURL(URL.createObjectURL(fileInput.files[0]));
} }
}; };