UNPKG

microvium

Version:

A compact, embeddable scripting engine for microcontrollers for executing small scripts written in a subset of JavaScript.

235 lines (198 loc) 7.18 kB
document.addEventListener("DOMContentLoaded", main, false); const snapshot = [0x07,0x1c,0x07,0x00,0x7c,0x00,0xaa,0xdb,0x03,0x00,0x00,0x00,0x1c,0x00,0x1e,0x00,0x22,0x00,0x22,0x00,0x28,0x00,0x2c,0x00,0x6a,0x00,0x72,0x00,0x01,0x00,0x01,0x00,0x5d,0x00,0x71,0x00,0x6d,0x00,0x01,0x00,0x39,0x00,0x31,0x00,0x00,0x00,0x05,0x40,0x70,0x75,0x73,0x68,0x00,0x00,0x0d,0x40,0x68,0x65,0x6c,0x6c,0x6f,0x2c,0x20,0x77,0x6f,0x72,0x6c,0x64,0x00,0x00,0x02,0x60,0x00,0x00,0x0d,0x50,0x04,0x31,0x30,0x30,0x88,0x1d,0x00,0x6b,0x12,0x6f,0x67,0x01,0x60,0x00,0x0d,0x50,0x03,0x89,0x00,0x00,0x01,0x88,0x39,0x00,0x78,0x02,0x67,0x01,0x60,0x00,0x49,0x00,0x02,0x00,0x19,0x00,0x01,0x00,0x08,0xc0,0x05,0x00,0x05,0x00,0x31,0x00,0x4d,0x00]; const noOpFunc = Object.freeze(() => {}); const notImplemented = () => { throw new Error('Not implemented') } const assert = x => { if (!x) throw new Error('Assertion failed') } const TextEncoder = typeof require !== 'undefined' ? require('util').TextEncoder // node.js : globalThis.TextEncoder; // browser const TextDecoder = typeof require !== 'undefined' ? require('util').TextDecoder // node.js : globalThis.TextDecoder; // browser const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); async function runAsync() { const memory = new WebAssembly.Memory({ initial: 4, maximum: 4 }); const memArray = new Uint8Array(memory.buffer); const mem16 = new Uint16Array(memory.buffer); const readWord = address => mem16[address >>> 1]; const writeWord = (address, value) => mem16[address >>> 1] = value; const vmImports = new Map([ [1, print] ]); const imports = { env: { memory: memory, mvm_fatalError: (vm, code) => { const msg = `Microvium fatal error: code ${code}`; console.error(msg); throw new Error(msg); }, fmod: (x, y) => x % y, pow: (x, y) => x ** y, invokeHost, importRequired: (id) => { if (!vmImports.has(id)) { throw new Error(`VM requires import ${id} but not provided`) } } } }; // ----------------------------------------------------------------- // NOTE! If running locally, you need to serve using the tool // `npm i -g serve` and then the command `serve` in this folder, // otherwise you will get a CORS error for fetch. // ----------------------------------------------------------------- const wasmPromise = fetch("microvium.wasm"); const { instance } = await WebAssembly.instantiateStreaming(wasmPromise, imports) const exports = instance.exports; const { allocator_init, reserve_ram, reserve_rom, mvm_restore, generalPurpose1, generalPurpose2, generalPurpose3, mvm_resolveExports, mvm_call, mvm_newNumber, } = exports; const gp2 = generalPurpose2.value; const gp3 = generalPurpose3.value; const ramStart = reserve_ram.value; const romStart = reserve_rom.value; // ROM is positioned in memory directly after RAM const ramSize = romStart - ramStart; // The RAM must be linked at address 0 and be exactly 64kB. The allocator is // hard-coded to use this range because it means that 16-bit Microvium // addresses directly correspond to memory offsets without any modification. assert(ramStart === 0); assert(ramSize === 0x10000); allocator_init(ramStart, ramSize); // Copy the snapshot into ROM assert(snapshot.length < 0x10000); memArray.set(snapshot, romStart); check(mvm_restore( generalPurpose1, // *result romStart, // snapshotBytecode snapshot.length, // bytecodeSize 0, // context 2, // resolveImport )); const vm = readWord(generalPurpose1); const main = resolveExport(1); check(mvm_call(vm, main, gp2, 0, 0)); debugger; return { memArray } function invokeHost(vm, hostFunctionID, out_vmpResult, vmpArgs, argCount) { const hostArgs = []; let vmpArg = vmpArgs; for (let i = 0; i < argCount; i++) { const vmArg = readWord(vmpArg); const hostArg = valueToHost(vmArg); hostArgs.push(hostArg); vmpArg += 2; } const resolveTo = vmImports.get(hostFunctionID); const result = resolveTo(...hostArgs); const vmResult = valueToVM(result); writeWord(out_vmpResult, vmResult); return 0; } function resolveExport(id) { writeWord(gp2, id); check(mvm_resolveExports( vm, gp2, /* *ids */ gp3, /* *results */ 1, /* count */ )); return readWord(gp3); } function print(s) { console.log(s); } function valueToVM(hostValue) { switch (typeof hostValue) { case 'undefined': return 0x01; case 'boolean': return hostValue ? 0x09 : 0x0D; case 'number': { // int14 if ((hostValue | 0) === hostValue && hostValue >= -02000 && hostValue <= 0x1FFF1) { return (hostValue << 2) | 3; } return mvm_newNumber(vm, hostValue); } case 'string': { // I'm thinking to directly inject the string into Microvium memory // rather than going through mvm_newString, because mvm_newString would // require 2 copies: one to get it into the WASM memory and one to copy // it into Microvium. notImplemented(); } } // TODO notImplemented(); } function valueToHost(vmValue) { debugger; // Int14 if ((vmValue & 3) === 3) { // Use the 16th bit as the sign bit return (vmValue << 16) >> 18; } let address; // Short pointer if ((vmValue & 1) === 0) { address = vmValue; } // Bytecode-mapped pointer else if ((vmValue & 3) === 1) { // Well known values if (vmValue <= 0x25) { switch (vmValue) { case 0x01: return undefined; case 0x05: return null; case 0x09: return true; case 0x0D: return false; case 0x11: return NaN; case 0x15: return -0; case 0x19: return undefined; case 0x1D: return 'length'; case 0x21: return '__proto__'; case 0x25: return noOpFunc; } } // TODO Indirection through handles // Plain bytecode pointer address = romStart + (vmValue & 0xFFFC); } const headerWord = readWord(address - 2); const typeCode = headerWord >>> 12; const size = headerWord & 0xFFF; switch (typeCode) { // Int32 case 0x1: return readWord(address) | readWord(address + 2); // Float64 case 0x2: { const temp = new Float64Array(memory.buffer, address, 1); return temp[0]; } // String case 0x3: case 0x4: { const temp = new Uint8Array(memory.buffer, address, size - 1); return textDecoder.decode(temp); } default: notImplemented(); } } } function check(errorCode) { if (errorCode !== 0) throw new Error(`Microvium Error: ${errorCode}`) } function main() { runAsync() .catch(console.error); }