Skip to content

Commit 4f2b7e8

Browse files
Replace Facade Proxy with handwritten proxy. (rhashimoto#285)
* Replace Proxy with handwritten proxy for jRead/jWrite buffers. * Replace Proxy with handwritten proxy for VFS return data. --------- Co-authored-by: Roy Hashimoto <[email protected]>
1 parent 8623ef6 commit 4f2b7e8

File tree

2 files changed

+220
-47
lines changed

2 files changed

+220
-47
lines changed

src/FacadeVFS.js

Lines changed: 219 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -405,64 +405,30 @@ export class FacadeVFS extends VFS.Base {
405405

406406
/**
407407
* Wrapped DataView for pointer arguments.
408-
* Pointers to a single value are passed using DataView. A Proxy
409-
* wrapper prevents use of incorrect type or endianness.
408+
* Pointers to a single value are passed using a DataView-like class.
409+
* This wrapper class prevents use of incorrect type or endianness, and
410+
* reacquires the underlying buffer when the WebAssembly memory is resized.
410411
* @param {'Int32'|'BigInt64'} type
411412
* @param {number} byteOffset
412413
* @returns {DataView}
413414
*/
414415
#makeTypedDataView(type, byteOffset) {
415-
const byteLength = type === 'Int32' ? 4 : 8;
416-
const getter = `get${type}`;
417-
const setter = `set${type}`;
418-
const makeDataView = () => new DataView(
419-
this._module.HEAPU8.buffer,
420-
this._module.HEAPU8.byteOffset + byteOffset,
421-
byteLength);
422-
let dataView = makeDataView();
423-
return new Proxy(dataView, {
424-
get(_, prop) {
425-
if (dataView.buffer.byteLength === 0) {
426-
// WebAssembly memory resize detached the buffer.
427-
dataView = makeDataView();
428-
}
429-
if (prop === getter) {
430-
return function(byteOffset, littleEndian) {
431-
if (!littleEndian) throw new Error('must be little endian');
432-
return dataView[prop](byteOffset, littleEndian);
433-
}
434-
}
435-
if (prop === setter) {
436-
return function(byteOffset, value, littleEndian) {
437-
if (!littleEndian) throw new Error('must be little endian');
438-
return dataView[prop](byteOffset, value, littleEndian);
439-
}
440-
}
441-
if (typeof prop === 'string' && (prop.match(/^(get)|(set)/))) {
442-
throw new Error('invalid type');
443-
}
444-
const result = dataView[prop];
445-
return typeof result === 'function' ? result.bind(dataView) : result;
446-
}
447-
});
416+
// @ts-ignore
417+
return new DataViewProxy(this._module, byteOffset, type);
448418
}
449419

450420
/**
421+
* Wrapped Uint8Array for buffer arguments.
422+
* Memory blocks are passed as a Uint8Array-like class. This wrapper
423+
* class reacquires the underlying buffer when the WebAssembly memory
424+
* is resized.
451425
* @param {number} byteOffset
452426
* @param {number} byteLength
427+
* @returns {Uint8Array}
453428
*/
454429
#makeDataArray(byteOffset, byteLength) {
455-
let target = this._module.HEAPU8.subarray(byteOffset, byteOffset + byteLength);
456-
return new Proxy(target, {
457-
get: (_, prop, receiver) => {
458-
if (target.buffer.byteLength === 0) {
459-
// WebAssembly memory resize detached the buffer.
460-
target = this._module.HEAPU8.subarray(byteOffset, byteOffset + byteLength);
461-
}
462-
const result = target[prop];
463-
return typeof result === 'function' ? result.bind(target) : result;
464-
}
465-
});
430+
// @ts-ignore
431+
return new Uint8ArrayProxy(this._module, byteOffset, byteLength);
466432
}
467433

468434
#decodeFilename(zName, flags) {
@@ -506,3 +472,210 @@ export class FacadeVFS extends VFS.Base {
506472
function delegalize(lo32, hi32) {
507473
return (hi32 * 0x100000000) + lo32 + (lo32 < 0 ? 2**32 : 0);
508474
}
475+
476+
// This class provides a Uint8Array-like interface for a WebAssembly memory
477+
// buffer. It is used to access memory blocks passed as arguments to
478+
// xRead, xWrite, etc. The class reacquires the underlying buffer when the
479+
// WebAssembly memory is resized, which can happen when the memory is
480+
// detached and resized by the WebAssembly module.
481+
//
482+
// Note that although this class implements the same methods as Uint8Array,
483+
// it is not a real Uint8Array and passing it to functions that expect
484+
// a Uint8Array may not work. Use subarray() to get a real Uint8Array
485+
// if needed.
486+
class Uint8ArrayProxy {
487+
#module;
488+
489+
#_array = new Uint8Array()
490+
get #array() {
491+
if (this.#_array.buffer.byteLength === 0) {
492+
// WebAssembly memory resize detached the buffer so re-create the
493+
// array with the new buffer.
494+
this.#_array = this.#module.HEAPU8.subarray(
495+
this.byteOffset,
496+
this.byteOffset + this.byteLength);
497+
}
498+
return this.#_array;
499+
}
500+
501+
/**
502+
* @param {*} module
503+
* @param {number} byteOffset
504+
* @param {number} byteLength
505+
*/
506+
constructor(module, byteOffset, byteLength) {
507+
this.#module = module;
508+
this.byteOffset = byteOffset;
509+
this.length = this.byteLength = byteLength;
510+
}
511+
512+
get buffer() {
513+
return this.#array.buffer;
514+
}
515+
516+
at(index) {
517+
return this.#array.at(index);
518+
}
519+
copyWithin(target, start, end) {
520+
this.#array.copyWithin(target, start, end);
521+
}
522+
entries() {
523+
return this.#array.entries();
524+
}
525+
every(predicate) {
526+
return this.#array.every(predicate);
527+
}
528+
fill(value, start, end) {
529+
this.#array.fill(value, start, end);
530+
}
531+
filter(predicate) {
532+
return this.#array.filter(predicate);
533+
}
534+
find(predicate) {
535+
return this.#array.find(predicate);
536+
}
537+
findIndex(predicate) {
538+
return this.#array.findIndex(predicate);
539+
}
540+
findLast(predicate) {
541+
return this.#array.findLast(predicate);
542+
}
543+
findLastIndex(predicate) {
544+
return this.#array.findLastIndex(predicate);
545+
}
546+
forEach(callback) {
547+
this.#array.forEach(callback);
548+
}
549+
includes(value, start) {
550+
return this.#array.includes(value, start);
551+
}
552+
indexOf(value, start) {
553+
return this.#array.indexOf(value, start);
554+
}
555+
join(separator) {
556+
return this.#array.join(separator);
557+
}
558+
keys() {
559+
return this.#array.keys();
560+
}
561+
lastIndexOf(value, start) {
562+
return this.#array.lastIndexOf(value, start);
563+
}
564+
map(callback) {
565+
return this.#array.map(callback);
566+
}
567+
reduce(callback, initialValue) {
568+
return this.#array.reduce(callback, initialValue);
569+
}
570+
reduceRight(callback, initialValue) {
571+
return this.#array.reduceRight(callback, initialValue);
572+
}
573+
reverse() {
574+
this.#array.reverse();
575+
}
576+
set(array, offset) {
577+
this.#array.set(array, offset);
578+
}
579+
slice(start, end) {
580+
return this.#array.slice(start, end);
581+
}
582+
some(predicate) {
583+
return this.#array.some(predicate);
584+
}
585+
sort(compareFn) {
586+
this.#array.sort(compareFn);
587+
}
588+
subarray(begin, end) {
589+
return this.#array.subarray(begin, end);
590+
}
591+
toLocaleString(locales, options) {
592+
// @ts-ignore
593+
return this.#array.toLocaleString(locales, options);
594+
}
595+
toReversed() {
596+
return this.#array.toReversed();
597+
}
598+
toSorted(compareFn) {
599+
return this.#array.toSorted(compareFn);
600+
}
601+
toString() {
602+
return this.#array.toString();
603+
}
604+
values() {
605+
return this.#array.values();
606+
}
607+
with(index, value) {
608+
return this.#array.with(index, value);
609+
}
610+
[Symbol.iterator]() {
611+
return this.#array[Symbol.iterator]();
612+
}
613+
}
614+
615+
// This class provides a DataView-like interface for a WebAssembly memory
616+
// buffer, restricted to either Int32 or BigInt64 types. It also reacquires
617+
// the underlying buffer when the WebAssembly memory is resized, which can
618+
// happen when the memory is detached and resized by the WebAssembly module.
619+
class DataViewProxy {
620+
#module;
621+
#type;
622+
623+
#_view = new DataView(new ArrayBuffer(0));
624+
get #view() {
625+
if (this.#_view.buffer.byteLength === 0) {
626+
// WebAssembly memory resize detached the buffer so re-create the
627+
// view with the new buffer.
628+
this.#_view = new DataView(
629+
this.#module.HEAPU8.buffer,
630+
this.#module.HEAPU8.byteOffset + this.byteOffset);
631+
}
632+
return this.#_view;
633+
}
634+
635+
/**
636+
* @param {*} module
637+
* @param {number} byteOffset
638+
* @param {'Int32'|'BigInt64'} type
639+
*/
640+
constructor(module, byteOffset, type) {
641+
this.#module = module;
642+
this.byteOffset = byteOffset;
643+
this.#type = type;
644+
}
645+
646+
get buffer() {
647+
return this.#view.buffer;
648+
}
649+
get byteLength() {
650+
return this.#type === 'Int32' ? 4 : 8;
651+
}
652+
653+
getInt32(byteOffset, littleEndian) {
654+
if (this.#type !== 'Int32') {
655+
throw new Error('invalid type');
656+
}
657+
if (!littleEndian) throw new Error('must be little endian');
658+
return this.#view.getInt32(byteOffset, littleEndian);
659+
}
660+
setInt32(byteOffset, value, littleEndian) {
661+
if (this.#type !== 'Int32') {
662+
throw new Error('invalid type');
663+
}
664+
if (!littleEndian) throw new Error('must be little endian');
665+
this.#view.setInt32(byteOffset, value, littleEndian);
666+
}
667+
getBigInt64(byteOffset, littleEndian) {
668+
if (this.#type !== 'BigInt64') {
669+
throw new Error('invalid type');
670+
}
671+
if (!littleEndian) throw new Error('must be little endian');
672+
return this.#view.getBigInt64(byteOffset, littleEndian);
673+
}
674+
setBigInt64(byteOffset, value, littleEndian) {
675+
if (this.#type !== 'BigInt64') {
676+
throw new Error('invalid type');
677+
}
678+
if (!littleEndian) throw new Error('must be little endian');
679+
this.#view.setBigInt64(byteOffset, value, littleEndian);
680+
}
681+
}

src/examples/MemoryVFS.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export class MemoryVFS extends FacadeVFS {
116116
}
117117

118118
// Copy data.
119-
new Uint8Array(file.data, iOffset, pData.byteLength).set(pData);
119+
new Uint8Array(file.data, iOffset, pData.byteLength).set(pData.subarray());
120120
file.size = Math.max(file.size, iOffset + pData.byteLength);
121121
return VFS.SQLITE_OK;
122122
}

0 commit comments

Comments
 (0)