| /* |
| * Copyright (C) 2016 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| import * as assert from 'assert.js'; |
| import * as WASM from 'WASM.js'; |
| |
| const _initialAllocationSize = 1024; |
| const _growAllocationSize = allocated => allocated * 2; |
| |
| export const varuint32Min = 0; |
| export const varint7Min = -0b1000000; |
| export const varint7Max = 0b111111; |
| export const varuint7Max = 0b1111111; |
| export const varuint32Max = ((((1 << 31) >>> 0) - 1) * 2) + 1; |
| export const varint32Min = -((1 << 31) >>> 0); |
| export const varint32Max = ((1 << 31) - 1) >>> 0; |
| export const varBitsMax = 5; |
| |
| const _getterRangeCheck = (llb, at, size) => { |
| if (0 > at || at + size > llb._used) |
| throw new RangeError(`[${at}, ${at + size}) is out of buffer range [0, ${llb._used})`); |
| }; |
| |
| const _hexdump = (buf, size) => { |
| let s = ""; |
| const width = 16; |
| const base = 16; |
| for (let row = 0; row * width < size; ++row) { |
| const address = (row * width).toString(base); |
| s += "0".repeat(8 - address.length) + address; |
| let chars = ""; |
| for (let col = 0; col !== width; ++col) { |
| const idx = row * width + col; |
| if (idx < size) { |
| const byte = buf[idx]; |
| const bytestr = byte.toString(base); |
| s += " " + (bytestr.length === 1 ? "0" + bytestr : bytestr); |
| chars += 0x20 <= byte && byte < 0x7F ? String.fromCharCode(byte) : "ยท"; |
| } else { |
| s += " "; |
| chars += " "; |
| } |
| } |
| s+= " |" + chars + "|\n"; |
| } |
| return s; |
| }; |
| |
| export default class LowLevelBinary { |
| constructor() { |
| this._buf = new Uint8Array(_initialAllocationSize); |
| this._used = 0; |
| } |
| |
| newPatchable(type) { return new PatchableLowLevelBinary(type, this); } |
| |
| // Utilities. |
| get() { return this._buf; } |
| trim() { this._buf = this._buf.slice(0, this._used); } |
| |
| hexdump() { return _hexdump(this._buf, this._used); } |
| _maybeGrow(bytes) { |
| const allocated = this._buf.length; |
| if (allocated - this._used < bytes) { |
| let buf = new Uint8Array(_growAllocationSize(allocated)); |
| buf.set(this._buf); |
| this._buf = buf; |
| } |
| } |
| _push8(v) { this._buf[this._used] = v & 0xFF; this._used += 1; } |
| |
| // Data types. |
| uint8(v) { |
| if ((v & 0xFF) >>> 0 !== v) |
| throw new RangeError(`Invalid uint8 ${v}`); |
| this._maybeGrow(1); |
| this._push8(v); |
| } |
| uint16(v) { |
| if ((v & 0xFFFF) >>> 0 !== v) |
| throw new RangeError(`Invalid uint16 ${v}`); |
| this._maybeGrow(2); |
| this._push8(v); |
| this._push8(v >>> 8); |
| } |
| uint24(v) { |
| if ((v & 0xFFFFFF) >>> 0 !== v) |
| throw new RangeError(`Invalid uint24 ${v}`); |
| this._maybeGrow(3); |
| this._push8(v); |
| this._push8(v >>> 8); |
| this._push8(v >>> 16); |
| } |
| uint32(v) { |
| if ((v & 0xFFFFFFFF) >>> 0 !== v) |
| throw new RangeError(`Invalid uint32 ${v}`); |
| this._maybeGrow(4); |
| this._push8(v); |
| this._push8(v >>> 8); |
| this._push8(v >>> 16); |
| this._push8(v >>> 24); |
| } |
| float(v) { |
| if (isNaN(v)) |
| throw new RangeError("unimplemented, NaNs"); |
| // Unfortunately, we cannot just view the actual buffer as a Float32Array since it needs to be 4 byte aligned |
| let buffer = new ArrayBuffer(4); |
| let floatView = new Float32Array(buffer); |
| let int8View = new Uint8Array(buffer); |
| floatView[0] = v; |
| for (let byte of int8View) |
| this._push8(byte); |
| } |
| |
| double(v) { |
| if (isNaN(v)) |
| throw new RangeError("unimplemented, NaNs"); |
| // Unfortunately, we cannot just view the actual buffer as a Float64Array since it needs to be 4 byte aligned |
| let buffer = new ArrayBuffer(8); |
| let floatView = new Float64Array(buffer); |
| let int8View = new Uint8Array(buffer); |
| floatView[0] = v; |
| for (let byte of int8View) |
| this._push8(byte); |
| } |
| |
| varuint32(v) { |
| assert.isNumber(v); |
| if (v < varuint32Min || varuint32Max < v) |
| throw new RangeError(`Invalid varuint32 ${v} range is [${varuint32Min}, ${varuint32Max}]`); |
| while (v >= 0x80) { |
| this.uint8(0x80 | (v & 0x7F)); |
| v >>>= 7; |
| } |
| this.uint8(v); |
| } |
| varint32(v) { |
| assert.isNumber(v); |
| if (v < varint32Min || varint32Max < v) |
| throw new RangeError(`Invalid varint32 ${v} range is [${varint32Min}, ${varint32Max}]`); |
| do { |
| const b = v & 0x7F; |
| v >>= 7; |
| if ((v === 0 && ((b & 0x40) === 0)) || (v === -1 && ((b & 0x40) === 0x40))) { |
| this.uint8(b & 0x7F); |
| break; |
| } |
| this.uint8(0x80 | b); |
| } while (true); |
| } |
| varuint64(v) { |
| assert.isNumber(v); |
| if (v < varuint32Min || varuint32Max < v) |
| throw new RangeError(`unimplemented: varuint64 larger than 32-bit`); |
| this.varuint32(v); // FIXME implement 64-bit var{u}int https://bugs.webkit.org/show_bug.cgi?id=163420 |
| } |
| varint64(v) { |
| assert.isNumber(v); |
| if (v < varint32Min || varint32Max < v) |
| throw new RangeError(`unimplemented: varint64 larger than 32-bit`); |
| this.varint32(v); // FIXME implement 64-bit var{u}int https://bugs.webkit.org/show_bug.cgi?id=163420 |
| } |
| varuint1(v) { |
| if (v !== 0 && v !== 1) |
| throw new RangeError(`Invalid varuint1 ${v} range is [0, 1]`); |
| this.varuint32(v); |
| } |
| varint7(v) { |
| if (v < varint7Min || varint7Max < v) |
| throw new RangeError(`Invalid varint7 ${v} range is [${varint7Min}, ${varint7Max}]`); |
| this.varint32(v); |
| } |
| varuint7(v) { |
| if (v < varuint32Min || varuint7Max < v) |
| throw new RangeError(`Invalid varuint7 ${v} range is [${varuint32Min}, ${varuint7Max}]`); |
| this.varuint32(v); |
| } |
| block_type(v) { |
| if (!WASM.isValidBlockType(v)) |
| throw new Error(`Invalid block type ${v}`); |
| this.varint7(WASM.typeValue[v]); |
| } |
| string(str) { |
| let patch = this.newPatchable("varuint32"); |
| for (const char of str) { |
| // Encode UTF-8 2003 code points. |
| const code = char.codePointAt(); |
| if (code <= 0x007F) { |
| const utf8 = code; |
| patch.uint8(utf8); |
| } else if (code <= 0x07FF) { |
| const utf8 = 0x80C0 | ((code & 0x7C0) >> 6) | ((code & 0x3F) << 8); |
| patch.uint16(utf8); |
| } else if (code <= 0xFFFF) { |
| const utf8 = 0x8080E0 | ((code & 0xF000) >> 12) | ((code & 0xFC0) << 2) | ((code & 0x3F) << 16); |
| patch.uint24(utf8); |
| } else if (code <= 0x10FFFF) { |
| const utf8 = (0x808080F0 | ((code & 0x1C0000) >> 18) | ((code & 0x3F000) >> 4) | ((code & 0xFC0) << 10) | ((code & 0x3F) << 24)) >>> 0; |
| patch.uint32(utf8); |
| } else |
| throw new Error(`Unexpectedly large UTF-8 character code point '${char}' 0x${code.toString(16)}`); |
| } |
| patch.apply(); |
| } |
| |
| // Getters. |
| getSize() { return this._used; } |
| getUint8(at) { |
| _getterRangeCheck(this, at, 1); |
| return this._buf[at]; |
| } |
| getUint16(at) { |
| _getterRangeCheck(this, at, 2); |
| return this._buf[at] | (this._buf[at + 1] << 8); |
| } |
| getUint24(at) { |
| _getterRangeCheck(this, at, 3); |
| return this._buf[at] | (this._buf[at + 1] << 8) | (this._buf[at + 2] << 16); |
| } |
| getUint32(at) { |
| _getterRangeCheck(this, at, 4); |
| return (this._buf[at] | (this._buf[at + 1] << 8) | (this._buf[at + 2] << 16) | (this._buf[at + 3] << 24)) >>> 0; |
| } |
| getVaruint32(at) { |
| let v = 0; |
| let shift = 0; |
| let byte = 0; |
| do { |
| byte = this.getUint8(at++); |
| ++read; |
| v = (v | ((byte & 0x7F) << shift) >>> 0) >>> 0; |
| shift += 7; |
| } while ((byte & 0x80) !== 0); |
| if (shift - 7 > 32) throw new RangeError(`Shifting too much at ${at}`); |
| if ((shift == 35) && ((byte & 0xF0) != 0)) throw new Error(`Unexpected non-significant varuint32 bits in last byte 0x${byte.toString(16)}`); |
| return { value: v, next: at }; |
| } |
| getVarint32(at) { |
| let v = 0; |
| let shift = 0; |
| let byte = 0; |
| do { |
| byte = this.getUint8(at++); |
| v = (v | ((byte & 0x7F) << shift) >>> 0) >>> 0; |
| shift += 7; |
| } while ((byte & 0x80) !== 0); |
| if (shift - 7 > 32) throw new RangeError(`Shifting too much at ${at}`); |
| if ((shift == 35) && (((byte << 26) >> 30) != ((byte << 25) >> 31))) throw new Error(`Unexpected non-significant varint32 bits in last byte 0x${byte.toString(16)}`); |
| if ((byte & 0x40) === 0x40) { |
| const sext = shift < 32 ? 32 - shift : 0; |
| v = (v << sext) >> sext; |
| } |
| return { value: v, next: at }; |
| } |
| getVaruint1(at) { |
| const res = this.getVaruint32(at); |
| if (res.value !== 0 && res.value !== 1) throw new Error(`Expected a varuint1, got value ${res.value}`); |
| return res; |
| } |
| getVaruint7(at) { |
| const res = this.getVaruint32(at); |
| if (res.value > varuint7Max) throw new Error(`Expected a varuint7, got value ${res.value}`); |
| return res; |
| } |
| getString(at) { |
| const size = this.getVaruint32(at); |
| const last = size.next + size.value; |
| let i = size.next; |
| let str = ""; |
| while (i < last) { |
| // Decode UTF-8 2003 code points. |
| const peek = this.getUint8(i); |
| let code; |
| if ((peek & 0x80) === 0x0) { |
| const utf8 = this.getUint8(i); |
| assert.eq(utf8 & 0x80, 0x00); |
| i += 1; |
| code = utf8; |
| } else if ((peek & 0xE0) === 0xC0) { |
| const utf8 = this.getUint16(i); |
| assert.eq(utf8 & 0xC0E0, 0x80C0); |
| i += 2; |
| code = ((utf8 & 0x1F) << 6) | ((utf8 & 0x3F00) >> 8); |
| } else if ((peek & 0xF0) === 0xE0) { |
| const utf8 = this.getUint24(i); |
| assert.eq(utf8 & 0xC0C0F0, 0x8080E0); |
| i += 3; |
| code = ((utf8 & 0xF) << 12) | ((utf8 & 0x3F00) >> 2) | ((utf8 & 0x3F0000) >> 16); |
| } else if ((peek & 0xF8) === 0xF0) { |
| const utf8 = this.getUint32(i); |
| assert.eq((utf8 & 0xC0C0C0F8) | 0, 0x808080F0 | 0); |
| i += 4; |
| code = ((utf8 & 0x7) << 18) | ((utf8 & 0x3F00) << 4) | ((utf8 & 0x3F0000) >> 10) | ((utf8 & 0x3F000000) >> 24); |
| } else |
| throw new Error(`Unexpectedly large UTF-8 initial byte 0x${peek.toString(16)}`); |
| str += String.fromCodePoint(code); |
| } |
| if (i !== last) |
| throw new Error(`String decoding read up to ${i}, expected ${last}, UTF-8 decoding was too greedy`); |
| return str; |
| } |
| }; |
| |
| class PatchableLowLevelBinary extends LowLevelBinary { |
| constructor(type, lowLevelBinary) { |
| super(); |
| this.type = type; |
| this.target = lowLevelBinary; |
| this._buffered_bytes = 0; |
| } |
| _push8(v) { ++this._buffered_bytes; super._push8(v); } |
| apply() { |
| this.target[this.type](this._buffered_bytes); |
| for (let i = 0; i < this._buffered_bytes; ++i) |
| this.target.uint8(this._buf[i]); |
| } |
| }; |