const TYPES = { U8: 1, I8: 2, U16: 3, I16: 4, U32: 5, I32: 6, U64: 7, I64: 8, SizeT: 9, F32: 10, F64: 11, Bool: 12, String: 13, U8Array: 14, I8Array: 15, U16Array: 16, I16Array: 17, U32Array: 18, I32Array: 19, U64Array: 20, I64Array: 21, F32Array: 22, F64Array: 23, Char: 24, CharArray: 25, }; let LoadSize = null; function assert(condition, message) { if(!condition) { console.assert(condition, message); throw "Assert failure"; } } const wasm_manager = { memory: null, data_view: null, stdout_string: "", sprintf_values: [], ptr_size: 4, }; function InitWasmManager(memory, ptr_size = 4) { wasm_manager.ptr_size = ptr_size; wasm_manager.sprintf_values = []; wasm_manager.stdout_string = ""; switch(wasm_manager.ptr_size) { case 8: LoadSize = LoadU64; break; case 4: default: LoadSize = LoadU32; break; } } function SetMemory(memory) { wasm_manager.memory = memory; wasm_manager.data_view = new DataView(wasm_manager.memory.buffer); } function Memory() { return new DataView(wasm_manager.memory.buffer); } function LoadBool(addr) { return Boolean(Memory().getUint8(addr, true)); } function LoadF32(addr) { return Memory().getFloat32(addr, true); } function LoadF64(addr) { return Memory().getFloat64(addr, true); } function LoadU8 (addr) { return Memory().getUint8 (addr, true); } function LoadI8 (addr) { return Memory().getInt8 (addr, true); } function LoadU16(addr) { return Memory().getUint16 (addr, true); } function LoadI16(addr) { return Memory().getInt16 (addr, true); } function LoadU32(addr) { return Memory().getUint32 (addr, true); } function LoadI32(addr) { return Memory().getInt32 (addr, true); } function LoadU64(addr) { const lo = Memory().getUint32(addr+0, true); const hi = Memory().getUint32(addr+4, true); return lo + hi*4294967296; } function LoadI64(addr) { const lo = Memory().getUint32(addr+0, true); const hi = Memory().getInt32 (addr+4, true); return lo + hi*4294967296; } function LoadArray(array_class, length, addr) { return new array_class(wasm_manager.memory.buffer, addr, length); } function LoadString(length, addr) { return new TextDecoder().decode(LoadArray(Uint8Array, length, addr)); } function LoadCString(ptr) { if(!ptr) return null; let length = 0; for(; LoadU8(ptr+length); length += 1) {} return LoadString(ptr, length); } function StoreString(ptr, value) { const src = new TextEncoder().encode(value); const dst = new Uint8Array(wasm_manager.memory.buffer, ptr, src.length); dst.set(src); return src.length; } const imports = { env: { Abort(string_length, string_ptr) { console.error(LoadString(string_length, string_ptr)); }, SprintfLoadValue(ptr, type) { let value = null; switch(type) { case TYPES.U8: value = LoadU8(ptr); break; case TYPES.I8: value = LoadI8(ptr); break; case TYPES.U16: value = LoadU16(ptr); break; case TYPES.I16: value = LoadI16(ptr); break; case TYPES.U32: value = LoadU32(ptr); break; case TYPES.I32: value = LoadI32(ptr); break; case TYPES.U64: value = LoadU64(ptr); break; case TYPES.I64: value = LoadI64(ptr); break; case TYPES.SizeT: value = LoadSize(ptr); break; case TYPES.F32: value = LoadF32(ptr); break; case TYPES.F64: value = LoadF64(ptr); break; case TYPES.Bool: value = LoadBool(ptr); break; case TYPES.Char: value = LoadString(1, ptr); break; default: break; } if(value != null) { if(type === TYPES.Bool) { wasm_manager.sprintf_values.push(value ? 'true' : 'false'); } else { wasm_manager.sprintf_values.push(''+value); } } }, SprintfLoadArray(length, ptr, type) { let value = null; switch(type) { case TYPES.CharArray: case TYPES.String: value = LoadString(length, ptr); break; case TYPES.U8Array: value = LoadArray(Uint8Array, length, ptr); break; case TYPES.I8Array: value = LoadArray(Int8Array, length, ptr); break; case TYPES.U16Array: value = LoadArray(Uint16Array, length, ptr); break; case TYPES.I16Array: value = LoadArray(Int16Array, length, ptr); break; case TYPES.U32Array: value = LoadArray(Uint32Array, length, ptr); break; case TYPES.I32Array: value = LoadArray(Int32Array, length, ptr); break; case TYPES.U64Array: value = LoadArray(BigUint64Array, length, ptr); break; case TYPES.I64Array: value = LoadArray(BigInt64Array, length, ptr); break; case TYPES.F32Array: value = LoadArray(Float32Array, length, ptr); break; case TYPES.F64Array: value = LoadArray(Float64Array, length, ptr); break; } if(value) { if(type !== TYPES.String && type !== TYPES.CharArray) { value = `[${value.join(", ")}]`; } wasm_manager.sprintf_values.push(value); } }, SprintfEnd(buffer_length, buffer_ptr, string_length, string_ptr) { let format_string = LoadString(string_length, string_ptr); let index = format_string.indexOf("%"); let result = format_string.substring(0, index); let format = format_string.substring(index); function Next() { index = format.indexOf("%"); result += format.substring(0, index); format = format.substring(index); } let value_index = 0; for(; value_index < wasm_manager.sprintf_values.length;) { assert(format.length); const char = format.charAt(1); assert(char !== " " && char.length, "Invalid format string " + format_string); if(char === "%") { result += "%"; format = format.substring(2); Next(); continue; } else if(char >= '0' && char <= '9') { let digit_index = 2; for(;;) { let next_char = format.charAt(digit_index++); assert(next_char !== " " && next_char.length, "Invalid format string " + format_string); if(next_char < '0' || next_char > '9') { continue; } result += wasm_manager.sprintf_values[value_index++]; format = format.substring(digit_index); Next(); break; } } else if((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z')) { result += wasm_manager.sprintf_values[value_index++]; format = format.substring(2); Next(); } else assert(false, "Invalid format string " + format_string); } result += format; const bytes_written = StoreString(buffer_ptr, result); assert(bytes_written <= buffer_length, "Format string is longer than buffer length"); wasm_manager.sprintf_values = []; return bytes_written; }, Console(string_length, string_ptr, write_line) { wasm_manager.stdout_string += LoadString(string_length, string_ptr); const arr = LoadArray(Uint8Array, string_length, string_ptr); if(write_line || wasm_manager.stdout_string.includes("\n")) { console.log(wasm_manager.stdout_string); wasm_manager.stdout_string = ""; } }, Console2(string_length, string_ptr, write_line) { wasm_manager.stdout_string += LoadString(string_length, string_ptr); const arr = LoadArray(Uint8Array, string_length, string_ptr); if(write_line || wasm_manager.stdout_string.includes("\n")) { console.log(wasm_manager.stdout_string); wasm_manager.stdout_string = ""; } }, pow: Math.pow, cos: Math.cos, acos: Math.acos, }, } async function StartWasm(path) { const response = await fetch(path); const data = await response.arrayBuffer(); const wasm = await WebAssembly.instantiate(data, imports); const exports = wasm.instance.exports; if(exports.memory) { SetMemory(exports.memory); } exports?._start(); exports?.RunTests(); return; }