dlib/wasm/wasm.js

286 lines
7.4 KiB
JavaScript

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,
};
function assert(condition, message)
{
if(!condition)
{
console.assert(condition, message);
throw "Assert failure";
}
}
class WasmManager
{
constructor(memory, ptr_size = 4)
{
this.ptr_size = ptr_size;
this.sprintf_values = [];
this.stdout_string = "";
switch(this.ptr_size)
{
case 8: this.LoadSize = this.LoadU64; break;
case 4:
default: this.LoadSize = this.LoadU32; break;
}
}
SetMemory(memory)
{
this.memory = memory;
this.data_view = new DataView(this.memory.buffer);
}
LoadBool = (addr) => Boolean(this.data_view.getUint8(addr, true));
LoadF32 = (addr) => this.data_view.getFloat32(addr, true);
LoadF64 = (addr) => this.data_view.getFloat64(addr, true);
LoadU8 = (addr) => this.data_view.getUint8 (addr, true);
LoadI8 = (addr) => this.data_view.getInt8 (addr, true);
LoadU16 = (addr) => this.data_view.getUint16 (addr, true);
LoadI16 = (addr) => this.data_view.getInt16 (addr, true);
LoadU32 = (addr) => this.data_view.getUint32 (addr, true);
LoadI32 = (addr) => this.data_view.getInt32 (addr, true);
LoadU64(addr)
{
const lo = this.data_view.getUint32(addr+0, true);
const hi = this.data_view.getUint32(addr+4, true);
return lo + hi*4294967296;
}
LoadI64(addr)
{
const lo = this.data_view.getUint32(addr+0, true);
const hi = this.data_view.getInt32 (addr+4, true);
return lo + hi*4294967296;
}
LoadArray = (array_class, length, addr) => new array_class(this.memory.buffer, addr, length);
LoadString = (length, addr) => new TextDecoder().decode(this.LoadArray(Uint8Array, length, addr));
LoadCString(ptr)
{
if(!ptr) return null;
let length = 0;
for(; this.LoadU8(ptr+length); length += 1) {}
return this.LoadString(ptr, length);
}
StoreString(ptr, value)
{
const src = new TextEncoder().encode(value);
const dst = new Uint8Array(this.memory.buffer, ptr, src.length);
dst.set(src);
return src.length;
}
Imports()
{
const manager = this;
return {
env: {
Abort(string_length, string_ptr)
{
console.error(manager.LoadString(string_length, string_ptr));
},
SprintfLoadValue(ptr, type)
{
let value = null;
switch(type)
{
case TYPES.U8: value = manager.LoadU8(ptr); break;
case TYPES.I8: value = manager.LoadI8(ptr); break;
case TYPES.U16: value = manager.LoadU16(ptr); break;
case TYPES.I16: value = manager.LoadI16(ptr); break;
case TYPES.U32: value = manager.LoadU32(ptr); break;
case TYPES.I32: value = manager.LoadI32(ptr); break;
case TYPES.U64: value = manager.LoadU64(ptr); break;
case TYPES.I64: value = manager.LoadI64(ptr); break;
case TYPES.SizeT: value = manager.LoadSize(ptr); break;
case TYPES.F32: value = manager.LoadF32(ptr); break;
case TYPES.F64: value = manager.LoadF64(ptr); break;
case TYPES.Bool: value = manager.LoadBool(ptr); break;
case TYPES.Char: value = manager.LoadString(1, ptr); break;
default: break;
}
if(value)
{
if(type === TYPES.Bool)
{
manager.sprintf_values.push(value ? 'true' : 'false');
}
else
{
manager.sprintf_values.push(''+value);
}
}
},
SprintfLoadArray(length, ptr, type)
{
let value = null;
switch(type)
{
case TYPES.CharArray:
case TYPES.String: value = manager.LoadString(length, ptr); break;
case TYPES.U8Array: value = manager.LoadArray(Uint8Array, length, ptr); break;
case TYPES.I8Array: value = manager.LoadArray(Int8Array, length, ptr); break;
case TYPES.U16Array: value = manager.LoadArray(Uint16Array, length, ptr); break;
case TYPES.I16Array: value = manager.LoadArray(Int16Array, length, ptr); break;
case TYPES.U32Array: value = manager.LoadArray(Uint32Array, length, ptr); break;
case TYPES.I32Array: value = manager.LoadArray(Int32Array, length, ptr); break;
case TYPES.U64Array: value = manager.LoadArray(BigUint64Array, length, ptr); break;
case TYPES.I64Array: value = manager.LoadArray(BigInt64Array, length, ptr); break;
case TYPES.F32Array: value = manager.LoadArray(Float32Array, length, ptr); break;
case TYPES.F64Array: value = manager.LoadArray(Float64Array, length, ptr); break;
}
if(value)
{
if(type !== TYPES.String && type !== TYPES.CharArray)
{
value = `[${value.join(", ")}]`;
}
manager.sprintf_values.push(value);
}
},
SprintfEnd(buffer_length, buffer_ptr, string_length, string_ptr)
{
let format_string = manager.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 < 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 += manager.sprintf_values[value_index++];
format = format.substring(digit_index);
Next();
break;
}
}
else if((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z'))
{
result += manager.sprintf_values[value_index++];
format = format.substring(2);
Next();
}
else assert(false, "Invalid format string " + format_string);
}
result += format;
const bytes_written = manager.StoreString(buffer_ptr, result);
assert(bytes_written <= buffer_length, "Format string is longer than buffer length");
manager.sprintf_values = [];
return bytes_written;
},
Console(string_length, string_ptr, write_line)
{
manager.stdout_string += manager.LoadString(string_length, string_ptr);
if(write_line || manager.stdout_string.includes("\n"))
{
console.log(manager.stdout_string);
manager.stdout_string = "";
}
},
},
}
}
}
async function StartWasm(path)
{
const manager = new WasmManager();
const response = await fetch(path);
const data = await response.arrayBuffer();
const wasm = await WebAssembly.instantiate(data, manager.Imports());
const exports = wasm.instance.exports;
if(exports.memory)
{
manager.SetMemory(exports.memory);
}
exports?._start();
return;
}