markdown-wasm/src/wlib.js
Rasmus Andersson 46892d1110 initial commit
2019-12-08 20:19:07 -08:00

382 lines
9.7 KiB
JavaScript

// WError represents an error from a wasm module
//
export class WError extends Error {
constructor(code, message, file, line) {
super(message, file || "wasm", line || 0)
this.name = "WError"
this.code = code
}
}
// Get & clear last WErr. Returns null if there was no error.
// Uses a descriptive name so to help in stack traces.
export function error_from_wasm() { // :WError|null
let code = _WErrGetCode()
if (code != 0) {
let msgptr = _WErrGetMsg()
let message = msgptr != 0 ? UTF8ArrayToString(HEAPU8, msgptr) : ""
_WErrClear()
return new WError(code, message)
}
}
export function werrCheck() {
let err = error_from_wasm()
if (err) {
throw err
}
}
// bytebuf takes an ArrayBuffer or Iterable<byte> and returns a Uint8Array
//
// bytebuf(buf :ArrayBuffer|Iterable<byte>|byte[]) : Uint8Array
//
export function bytebuf(buf) {
if (buf instanceof Uint8Array) {
return buf
}
return new Uint8Array(buf)
}
// mallocbuf allocates memory in the WASM heap and copies length bytes
// from byteArray into the allocated location.
// Returns the address to the allocated memory.
//
export function mallocbuf(byteArray, length) {
const offs = _wrealloc(0, length)
HEAPU8.set(byteArray, offs)
return offs
}
// malloc32 allocates at least size bytes on 32-bit boundary.
// Returns two values: original_address and aligned_address.
// You should call free() with original_address.
//
export function malloc32(size) {
let ptr_orig = _wrealloc(0, size + 3)
return [ptr_orig, ptr_orig + (4 - (ptr_orig % 4))]
}
// malloc16 allocates at least size bytes on 16-bit boundary.
// Returns two values: original_address and aligned_address.
// You should call free() with original_address.
//
export function malloc16(size) {
let ptr_orig = _wrealloc(0, size + 1)
return [ptr_orig, ptr_orig + (ptr_orig % 2)]
}
// free wasm heap memory
export function free(ptr) {
_wfree(ptr)
}
// writeUTF16Str writes str as UTF16 to address ptr.
// ptr must be aligned on a 16-bit boundary.
//
export function writeUTF16Str(str, ptr) {
for (let i = 0; i < str.length; ++i) {
HEAP16[ptr >> 1] = str.charCodeAt(i)
ptr += 2
}
}
// withTmpBytePtr takes an ArrayBuffer or Uint8Array and:
// 1. copies it into the WASM module memory
// 2. calls fn(pointer, size)
// 3. calls free(pointer)
//
export function withTmpBytePtr(buf, fn) {
const u8buf = bytebuf(buf)
const size = u8buf.length
const ptr = mallocbuf(u8buf, size)
const r = fn(ptr, size)
free(ptr)
return r
}
// withUTF16Str takes a JavaScript string and:
// 1. copies it into the WASM module memory as UTF16, 16-bit aligned
// 2. calls fn(aligned_pointer, bytesize)
// 3. calls free(original_pointer)
//
export function withUTF16Str(str, fn) {
let bytesize = str.length * 2
let ptr = _wrealloc(0, bytesize + 1) // +1 for alignment
let aligned_ptr = (ptr % 2 != 0) ? ptr + 1 : ptr
writeUTF16Str(str, aligned_ptr, bytesize)
let r = fn(aligned_ptr, bytesize)
free(ptr)
return r
}
export function cstrlen(ptr) {
let end = ptr >> 0
while (HEAP8[end]) { end++ }
return end - ptr
}
// asciicstr interprets memory in buf at offset as an ASCII-encoded string,
// and returns a JavaScript string.
//
export function asciicstr(buf, offset) {
let str = ''
while (true) {
let b = buf[offset++ >> 0]
if (b == 0) { break }
str += String.fromCharCode(b)
}
return str
}
// cstrStack allocates a UTF-8 encoded version of str as a nul-terminated
// "C string" on the stack.
//
export function cstrStack(str) {
var ret = 0
if (str !== null && str !== undefined && str !== 0) {
var len = (str.length << 2) + 1
ret = stackAlloc(len)
stringToUTF8Array(str, HEAPU8, ret, len)
}
return ret
}
// used by strFromUTF8Ptr as a temporary address-sized integer
let tmpPtr = 0
Module.postRun.push(() => {
tmpPtr = _wrealloc(0, 4)
})
// strFromUTF8Ptr provides a pointer-sized integer that can be written
// to by fn. fn is expected to return the number of bytes written to the
// address pointed to by p. The address is dereferenced and the written
// number of bytes are interpreted as UTF8, returning a JS string.
//
// This is useful for efficiently converting UTF8 strings that are
// already allocated inside the library to JavaScript strings.
//
// Example:
// strFromUTF8Ptr((p)=> _FooGetName(ptr, p))
// ...
// u32 EXPORT FooGetName(Foo* f, const char** p) {
// *p = f->name_ptr;
// return f->name_len;
// }
//
// Synopsis:
// strFromUTF8Ptr( fn :(p:int)=>int )
//
export function strFromUTF8Ptr(fn) {
let z = fn(tmpPtr)
let offs = HEAP32[tmpPtr >> 2]
return z == 0 ? "" : utf8.decode(HEAPU8.subarray(offs, offs + z))
}
// withOutPtr facilitates the following:
//
// 1. calls fn with an address to memory that fits a pointer.
// fn(outptr) is expected to:
// a. Write some data into heap memory
// b. Write the address of that data at outptr (i.e. *outptr = heapaddr)
// c. Return the length of data written
//
// 2. withOutPtr reads the address from outptr
// a. If the address is 0 (NULL), returns null
// b. Else a slice of the heap memory is created, starting at *outptr
// and ending at ((*outptr) + length_returned_by_fn).
// A free() function is added to the buffer and it is returned.
//
// It is important to free() the memory of the returned buffer when the caller is done.
// This is implementation specific, so this function can not help you with that.
//
// The return type is as follows:
// interface HeapData extends Uint8Array {
// readonly heapAddr :number // address in heap == *outptr
// }
//
// Example:
//
// // WASM module, in C:
// typedef struct Color_ { char r, g, b; } Color;
// size_t newColor(const Color** outp) {
// Color* c = (Color*)malloc(sizeof(Color));
// c->r = 0xFF;
// c->g = 0xCA;
// c->b = 0x0;
// *outp = c;
// return sizeof(Color);
// }
// void freeColor(const Color* p) {
// free(p);
// }
//
// // JavaScript
// let color = withOutPtr(_newColor)
// console.log("RGB:", color[0], color[1], color[2])
// _freeColor(color.heapAddr)
//
export function withOutPtr(fn) {
let len = fn(tmpPtr)
let addr = HEAP32[tmpPtr >> 2]
if (addr == 0) {
return null
}
let buf = HEAPU8.subarray(addr, addr + len)
buf.heapAddr = addr
return buf
}
// withStackFrame saves the stack and calls fn; code in fn can then
// allocate stack memory. When fn returns or throws, the stack is restored
// to the point before this function was called.
// Returns the return value of fn.
//
export function withStackFrame(fn) {
let stack = stackSave()
try {
return fn()
} finally {
stackRestore(stack)
}
}
// ureadU16 reads a (little endian) unsigned 16-bit integer from buf at addr
//
export function ureadU16(buf, addr) {
return ((buf[addr] | (buf[addr + 1] << 8))) >>> 0
}
// ureadI16 reads a (little endian) signed 16-bit integer from buf at addr
//
export function ureadI16(buf, addr) {
let n = ((buf[addr]) | (buf[addr + 1] << 8))
return n >= 0x8000 ? n - 0x10000 : n
}
// ureadU32 reads a (little endian) unsigned 32-bit integer from buf at addr
//
export function ureadU32(buf, addr) {
return (
(buf[addr + 3] << 24) |
(buf[addr + 2] << 16) |
(buf[addr + 1] << 8) |
(buf[addr] >>> 0)
) >>> 0
}
// ureadU32 reads a (little endian) signed 32-bit integer from buf at addr
//
export function ureadI32(buf, addr) {
return (
(buf[addr + 3] << 24) |
(buf[addr + 2] << 16) |
(buf[addr + 1] << 8) |
(buf[addr] >>> 0)
)
}
// export function ureadI16be(buf, addr) {
// let n = ((buf[addr] << 8) | (buf[addr + 1]))
// return n >= 0x8000 ? n - 0x10000 : n
// }
// export function ureadU16be(buf, addr) {
// return ((buf[addr] << 8) | (buf[addr + 1])) >>> 0
// }
// export function ureadU32be(buf, addr) {
// return (
// (buf[addr] << 24) |
// (buf[addr + 1] << 16) |
// (buf[addr + 2] << 8) |
// (buf[addr + 3])
// ) >>> 0
// }
// export function ureadI32be(buf, addr) {
// return (
// (buf[addr] << 24) |
// (buf[addr + 1] << 16) |
// (buf[addr + 2] << 8) |
// (buf[addr + 3])
// )
// }
// asciiStrToU32 converts a <=4 character string to a u32.
// For example, string -> hb_tag_t
//
export function asciiStrToU32(s) {
// Note: Should match #define HB_TAG(c1,c2,c3,c4) in hb-common.h
return (
((s.charCodeAt(0) >>> 0) << 24) >>> 0 | // "">>> 0" u32 please
((s.charCodeAt(1) >>> 0) << 16) |
((s.charCodeAt(2) >>> 0) << 8) |
(s.charCodeAt(3) >>> 0)
)
}
export const hbtag = asciiStrToU32
// u32ToAsciiStr converts a u32 to a ASCII string
// For example, hb_tag_t -> string
//
export function u32ToAsciiStr(u) {
return String.fromCharCode(
((u >> 24) & 0xff),
((u >> 16) & 0xff),
((u >> 8) & 0xff),
((u >> 0) & 0xff)
)
}
// interface utf8 {
// encode(s :string) :Uint8Array
// decode(b :Uint8Array) :string
// }
export const utf8 = typeof TextEncoder != 'undefined' ? (() => {
// Modern browsers
const enc = new TextEncoder("utf-8")
const dec = new TextDecoder("utf-8")
return {
encode: s => enc.encode(s),
decode: b => dec.decode(b),
};
})() : typeof Buffer != 'undefined' ? {
// Nodejs
encode: s => new Uint8Array(Buffer.from(s, 'utf-8')),
decode: b =>
Buffer.from(b.buffer, b.byteOffset, b.byteLength).toString('utf8'),
} : {
// Some other pesky JS environment
encode: s => {
let asciiBytes = [];
for (let i = 0, L = s.length; i != L; ++i) {
asciiBytes[i] = 0xff & s.charCodeAt(i);
}
return new Uint8Array(asciiBytes);
},
decode: b => String(b),
}
// Converts between 16.16 fixed-point number and 64-bit floating-point numbers
export function fixedToFloat(i) {
return i / 65536.0
}
export function floatToFixed(f) {
return (f * 65536.0) >> 0
}