Skip to content

modernize loader #1279

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 13, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 27 additions & 26 deletions lib/loader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ const CHUNKSIZE = 1024;
function getStringImpl(buffer, ptr) {
const U32 = new Uint32Array(buffer);
const U16 = new Uint16Array(buffer);
let length = U32[(ptr + SIZE_OFFSET) >>> 2] >>> 1;
let length = U32[ptr + SIZE_OFFSET >>> 2] >>> 1;
let offset = ptr >>> 1;
if (length <= CHUNKSIZE) return String.fromCharCode.apply(String, U16.subarray(offset, offset + length));
const parts = [];
let parts = '';
do {
const last = U16[offset + CHUNKSIZE - 1];
const size = last >= 0xD800 && last < 0xDC00 ? CHUNKSIZE - 1 : CHUNKSIZE;
parts.push(String.fromCharCode.apply(String, U16.subarray(offset, offset += size)));
parts += String.fromCharCode.apply(String, U16.subarray(offset, offset += size));
Copy link
Member

@dcodeIO dcodeIO May 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure that this is always faster? Would imagine that it is for smaller strings, but once parts grows bigger, it might slow down substantially.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Chrome it should be definitely faster. See this tests:
https://twitter.com/MaxGraey/status/1232045301374111744
https://esbench.com/bench/5e540d53328a52009e8b1b22
It relate to string reversing but it also use same technique as string decoding

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to do String.fromCharCode.apply(String) could simply do something like

const fromCharCode = String.fromCharCode;

fromCharCode.apply(null, theArray);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this affect performance?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so

Copy link
Member Author

@MaxGraey MaxGraey May 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dcodeIO I made micro benchmark which more relate to this case:
https://gist.github.com/MaxGraey/6db5c6c6d7e4a0f45154b73a5e130976

and it seems string concat approach faster in 3 times

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While 4K+ is already a reasonably long string, an interesting measurement there would be how long a string has to be for both approaches to execute in an equal time span, if at all. That would help to inform if it is good enough in any case, or if we'd need another code path to deal with strings longer than X.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the test string is exclusively ASCII currently, so it might be that engines optimize this differently than strings that have at least one non-LATIN1 character. Both cases would be interesting.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my benchmarks inside Node, concat is faster than +:
https://github.com/aminya/typescript-optimization#string--vs-concat

length -= size;
} while (length > CHUNKSIZE);
return parts.join("") + String.fromCharCode.apply(String, U16.subarray(offset, offset + length));
return parts + String.fromCharCode.apply(String, U16.subarray(offset, offset + length));
}

/** Prepares the base module prior to instantiation. */
Expand All @@ -70,15 +70,13 @@ function preInstantiate(imports) {
const env = (imports.env = imports.env || {});
env.abort = env.abort || function abort(msg, file, line, colm) {
const memory = extendedExports.memory || env.memory; // prefer exported, otherwise try imported
throw Error("abort: " + getString(memory, msg) + " at " + getString(memory, file) + ":" + line + ":" + colm);
throw Error(`abort: ${getString(memory, msg)} at ${getString(memory, file)}:${line}:${colm}`);
};
env.trace = env.trace || function trace(msg, n) {
env.trace = env.trace || function trace(msg, n, ...args) {
const memory = extendedExports.memory || env.memory;
console.log("trace: " + getString(memory, msg) + (n ? " " : "") + Array.prototype.slice.call(arguments, 2, 2 + n).join(", "));
};
env.seed = env.seed || function seed() {
return Date.now();
console.log(`trace: ${getString(memory, msg)}${n ? " " : ""}${args.slice(0, n).join(", ")}`);
};
env.seed = env.seed || Date.now;
imports.Math = imports.Math || Math;
imports.Date = imports.Date || Date;

Expand All @@ -98,15 +96,22 @@ function postInstantiate(extendedExports, instance) {
function getInfo(id) {
const U32 = new Uint32Array(memory.buffer);
const count = U32[rttiBase >>> 2];
if ((id >>>= 0) >= count) throw Error("invalid id: " + id);
if ((id >>>= 0) >= count) throw Error(`invalid id: ${id}`);
return U32[(rttiBase + 4 >>> 2) + id * 2];
}

/** Gets and validate runtime type info for the given id for array like objects */
function getArrayInfo(id) {
const info = getInfo(id);
if (!(info & (ARRAYBUFFERVIEW | ARRAY | STATICARRAY))) throw Error(`not an array: ${id}, flags=${info}`);
return info;
}

/** Gets the runtime base id for the given id. */
function getBase(id) {
const U32 = new Uint32Array(memory.buffer);
const count = U32[rttiBase >>> 2];
if ((id >>>= 0) >= count) throw Error("invalid id: " + id);
if ((id >>>= 0) >= count) throw Error(`invalid id: ${id}`);
return U32[(rttiBase + 4 >>> 2) + id * 2 + 1];
}

Expand Down Expand Up @@ -135,7 +140,7 @@ function postInstantiate(extendedExports, instance) {
function __getString(ptr) {
const buffer = memory.buffer;
const id = new Uint32Array(buffer)[ptr + ID_OFFSET >>> 2];
if (id !== STRING_ID) throw Error("not a string: " + ptr);
if (id !== STRING_ID) throw Error(`not a string: ${ptr}`);
return getStringImpl(buffer, ptr);
}

Expand All @@ -157,13 +162,12 @@ function postInstantiate(extendedExports, instance) {
case 3: return new (signed ? BigInt64Array : BigUint64Array)(buffer);
}
}
throw Error("unsupported align: " + alignLog2);
throw Error(`unsupported align: ${alignLog2}`);
}

/** Allocates a new array in the module's memory and returns its retained pointer. */
function __allocArray(id, values) {
const info = getInfo(id);
if (!(info & (ARRAYBUFFERVIEW | ARRAY | STATICARRAY))) throw Error("not an array: " + id + ", flags= " + info);
const info = getArrayInfo(id);
const align = getValueAlign(info);
const length = values.length;
const buf = alloc(length << align, info & STATICARRAY ? id : ARRAYBUFFER_ID);
Expand Down Expand Up @@ -194,8 +198,7 @@ function postInstantiate(extendedExports, instance) {
function __getArrayView(arr) {
const U32 = new Uint32Array(memory.buffer);
const id = U32[arr + ID_OFFSET >>> 2];
const info = getInfo(id);
if (!(info & (ARRAYBUFFERVIEW | ARRAY | STATICARRAY))) throw Error("not an array: " + id + ", flags=" + info);
const info = getArrayInfo(id);
const align = getValueAlign(info);
let buf = info & STATICARRAY
? arr
Expand Down Expand Up @@ -244,8 +247,8 @@ function postInstantiate(extendedExports, instance) {

/** Attach a set of get TypedArray and View functions to the exports. */
function attachTypedArrayFunctions(ctor, name, align) {
extendedExports["__get" + name] = getTypedArray.bind(null, ctor, align);
extendedExports["__get" + name + "View"] = getTypedArrayView.bind(null, ctor, align);
extendedExports[`__get${name}`] = getTypedArray.bind(null, ctor, align);
extendedExports[`__get${name}View`] = getTypedArrayView.bind(null, ctor, align);
}

[
Expand All @@ -271,7 +274,7 @@ function postInstantiate(extendedExports, instance) {
/** Tests whether an object is an instance of the class represented by the specified base id. */
function __instanceof(ptr, baseId) {
const U32 = new Uint32Array(memory.buffer);
let id = U32[(ptr + ID_OFFSET) >>> 2];
let id = U32[ptr + ID_OFFSET >>> 2];
if (id <= U32[rttiBase >>> 2]) {
do if (id == baseId) return true;
while (id = getBase(id));
Expand Down Expand Up @@ -364,9 +367,7 @@ function demangle(exports, extendedExports = {}) {
return ctor.wrap(ctor.prototype.constructor(0, ...args));
};
ctor.prototype = {
valueOf: function valueOf() {
return this[THIS];
}
valueOf() { return this[THIS]; }
};
ctor.wrap = function(thisValue) {
return Object.create(ctor.prototype, { [THIS]: { value: thisValue, writable: false } });
Expand All @@ -383,8 +384,8 @@ function demangle(exports, extendedExports = {}) {
let getter = exports[internalName.replace("set:", "get:")];
let setter = exports[internalName.replace("get:", "set:")];
Object.defineProperty(curr, name, {
get: function() { return getter(this[THIS]); },
set: function(value) { setter(this[THIS], value); },
get() { return getter(this[THIS]); },
set(value) { setter(this[THIS], value); },
enumerable: true
});
}
Expand Down