Skip to content

Runtime Performance Optimization #207

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 20 commits into from
Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8d17f5c
Allocate function call argument buffer on stack
kateinoigakukun Aug 16, 2022
d684978
Revert "Allocate function call argument buffer on stack"
kateinoigakukun Aug 16, 2022
7f6e3d8
Reduce memory store for returned value kind
kateinoigakukun Aug 16, 2022
50fd47b
Add NODEJS_FLAGS to perform profiling by passing --prof
kateinoigakukun Aug 16, 2022
4f850a0
Revert "Revert "Allocate function call argument buffer on stack""
kateinoigakukun Aug 16, 2022
e66296f
Revert "Revert "Revert "Allocate function call argument buffer on sta…
kateinoigakukun Aug 16, 2022
0f53ca5
Reduce retain/release dance caused by Optional<this>
kateinoigakukun Aug 16, 2022
adfe1e8
Don't escape JSFunction self
kateinoigakukun Aug 16, 2022
78b442d
Add fast path for empty JSValue array
kateinoigakukun Aug 16, 2022
bffe009
Skip re-creating DataView in decodeArray
kateinoigakukun Aug 16, 2022
2a8146b
make regenerate_swiftpm_resources
kateinoigakukun Aug 16, 2022
43d9a2a
Apply the same techniques to call families
kateinoigakukun Aug 17, 2022
2b86b41
Reuse DataView as much as possible
kateinoigakukun Aug 17, 2022
5a3ecf2
npm run format
kateinoigakukun Aug 17, 2022
8184119
Optimize swjs_get_prop to reduce memory store
kateinoigakukun Aug 18, 2022
a1b690e
Optimize _get_subscript to reduce memory store
kateinoigakukun Aug 18, 2022
4110f7f
Rename writeV2 -> writeAndReturnKindBits
kateinoigakukun Aug 18, 2022
d4c45b4
Improve doc comment style
kateinoigakukun Aug 18, 2022
7532e6a
Use writeAndReturnKindBits in write
kateinoigakukun Aug 18, 2022
a9843ee
Add rationale comments for write
kateinoigakukun Aug 18, 2022
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
3 changes: 2 additions & 1 deletion IntegrationTests/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
CONFIGURATION ?= debug
SWIFT_BUILD_FLAGS ?=
NODEJS_FLAGS ?=

NODEJS = node --experimental-wasi-unstable-preview1
NODEJS = node --experimental-wasi-unstable-preview1 $(NODEJS_FLAGS)

FORCE:
TestSuites/.build/$(CONFIGURATION)/%.wasm: FORCE
Expand Down
87 changes: 23 additions & 64 deletions Runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,29 +204,25 @@ export class SwiftRuntime {
ref: ref,
argv: pointer,
argc: number,
kind_ptr: pointer,
payload1_ptr: pointer,
payload2_ptr: pointer
) => {
const func = this.memory.getObject(ref);
let result: any;
let result = undefined;
try {
const args = JSValue.decodeArray(argv, argc, this.memory);
result = func(...args);
} catch (error) {
JSValue.write(
return JSValue.writeV2(
error,
kind_ptr,
payload1_ptr,
payload2_ptr,
true,
this.memory
);
return;
}
JSValue.write(
return JSValue.writeV2(
result,
kind_ptr,
payload1_ptr,
payload2_ptr,
false,
Expand All @@ -237,44 +233,26 @@ export class SwiftRuntime {
ref: ref,
argv: pointer,
argc: number,
kind_ptr: pointer,
payload1_ptr: pointer,
payload2_ptr: pointer
) => {
const func = this.memory.getObject(ref);
let isException = true;
try {
const args = JSValue.decodeArray(argv, argc, this.memory);
const result = func(...args);
JSValue.write(
result,
kind_ptr,
payload1_ptr,
payload2_ptr,
false,
this.memory
);
isException = false;
} finally {
if (isException) {
JSValue.write(
undefined,
kind_ptr,
payload1_ptr,
payload2_ptr,
true,
this.memory
);
}
}
const args = JSValue.decodeArray(argv, argc, this.memory);
const result = func(...args);
return JSValue.writeV2(
result,
payload1_ptr,
payload2_ptr,
false,
this.memory
);
},

swjs_call_function_with_this: (
obj_ref: ref,
func_ref: ref,
argv: pointer,
argc: number,
kind_ptr: pointer,
payload1_ptr: pointer,
payload2_ptr: pointer
) => {
Expand All @@ -285,19 +263,16 @@ export class SwiftRuntime {
const args = JSValue.decodeArray(argv, argc, this.memory);
result = func.apply(obj, args);
} catch (error) {
JSValue.write(
return JSValue.writeV2(
error,
kind_ptr,
payload1_ptr,
payload2_ptr,
true,
this.memory
);
return;
}
JSValue.write(
return JSValue.writeV2(
result,
kind_ptr,
payload1_ptr,
payload2_ptr,
false,
Expand All @@ -309,37 +284,21 @@ export class SwiftRuntime {
func_ref: ref,
argv: pointer,
argc: number,
kind_ptr: pointer,
payload1_ptr: pointer,
payload2_ptr: pointer
) => {
const obj = this.memory.getObject(obj_ref);
const func = this.memory.getObject(func_ref);
let isException = true;
try {
let result = undefined;
const args = JSValue.decodeArray(argv, argc, this.memory);
const result = func.apply(obj, args);
JSValue.write(
result,
kind_ptr,
payload1_ptr,
payload2_ptr,
false,
this.memory
);
isException = false;
} finally {
if (isException) {
JSValue.write(
undefined,
kind_ptr,
payload1_ptr,
payload2_ptr,
true,
this.memory
);
}
}
result = func.apply(obj, args);
return JSValue.writeV2(
result,
payload1_ptr,
payload2_ptr,
false,
this.memory
);
},

swjs_call_new: (ref: ref, argv: pointer, argc: number) => {
Expand Down
66 changes: 62 additions & 4 deletions Runtime/src/js-value.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Memory } from "./memory.js";
import { assertNever, pointer } from "./types.js";
import { assertNever, JavaScriptValueKindAndFlags, pointer } from "./types.js";

export const enum Kind {
Boolean = 0,
Expand Down Expand Up @@ -51,12 +51,18 @@ export const decode = (
// Note:
// `decodeValues` assumes that the size of RawJSValue is 16.
export const decodeArray = (ptr: pointer, length: number, memory: Memory) => {
// fast path for empty array
if (length === 0) { return []; }

let result = [];
// It's safe to hold DataView here because WebAssembly.Memory.buffer won't
// change within this function.
const view = memory.dataView();
for (let index = 0; index < length; index++) {
const base = ptr + 16 * index;
const kind = memory.readUint32(base);
const payload1 = memory.readUint32(base + 4);
const payload2 = memory.readFloat64(base + 8);
const kind = view.getUint32(base, true);
const payload1 = view.getUint32(base + 4, true);
const payload2 = view.getFloat64(base + 8, true);
result.push(decode(kind, payload1, payload2, memory));
}
return result;
Expand Down Expand Up @@ -121,3 +127,55 @@ export const write = (
assertNever(type, `Type "${type}" is not supported yet`);
}
};


export const writeV2 = (
value: any,
payload1_ptr: pointer,
payload2_ptr: pointer,
is_exception: boolean,
memory: Memory
): JavaScriptValueKindAndFlags => {
const exceptionBit = (is_exception ? 1 : 0) << 31;
if (value === null) {
return exceptionBit | Kind.Null;
}

const writeRef = (kind: Kind) => {
memory.writeUint32(payload1_ptr, memory.retain(value));
return exceptionBit | kind
};

const type = typeof value;
switch (type) {
case "boolean": {
memory.writeUint32(payload1_ptr, value ? 1 : 0);
return exceptionBit | Kind.Boolean;
}
case "number": {
memory.writeFloat64(payload2_ptr, value);
return exceptionBit | Kind.Number;
}
case "string": {
return writeRef(Kind.String);
}
case "undefined": {
return exceptionBit | Kind.Undefined;
}
case "object": {
return writeRef(Kind.Object);
}
case "function": {
return writeRef(Kind.Function);
}
case "symbol": {
return writeRef(Kind.Symbol);
}
case "bigint": {
return writeRef(Kind.BigInt);
}
default:
assertNever(type, `Type "${type}" is not supported yet`);
}
throw new Error("Unreachable");
};
13 changes: 5 additions & 8 deletions Runtime/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as JSValue from "./js-value.js";
export type ref = number;
export type pointer = number;
export type bool = number;
export type JavaScriptValueKindAndFlags = number;

export interface ExportedFunctions {
swjs_library_version(): number;
Expand Down Expand Up @@ -55,36 +56,32 @@ export interface ImportedFunctions {
ref: number,
argv: pointer,
argc: number,
kind_ptr: pointer,
payload1_ptr: pointer,
payload2_ptr: pointer
): void;
): JavaScriptValueKindAndFlags;
swjs_call_function_no_catch(
ref: number,
argv: pointer,
argc: number,
kind_ptr: pointer,
payload1_ptr: pointer,
payload2_ptr: pointer
): void;
): JavaScriptValueKindAndFlags;
swjs_call_function_with_this(
obj_ref: ref,
func_ref: ref,
argv: pointer,
argc: number,
kind_ptr: pointer,
payload1_ptr: pointer,
payload2_ptr: pointer
): void;
): JavaScriptValueKindAndFlags;
swjs_call_function_with_this_no_catch(
obj_ref: ref,
func_ref: ref,
argv: pointer,
argc: number,
kind_ptr: pointer,
payload1_ptr: pointer,
payload2_ptr: pointer
): void;
): JavaScriptValueKindAndFlags;
swjs_call_new(ref: number, argv: pointer, argc: number): number;
swjs_call_throwing_new(
ref: number,
Expand Down
3 changes: 3 additions & 0 deletions Sources/JavaScriptKit/ConvertibleToJSValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,9 @@ extension JSValue {

extension Array where Element == ConvertibleToJSValue {
func withRawJSValues<T>(_ body: ([RawJSValue]) -> T) -> T {
// fast path for empty array
guard self.count != 0 else { return body([]) }

func _withRawJSValues<T>(
_ values: [ConvertibleToJSValue], _ index: Int,
_ results: inout [RawJSValue], _ body: ([RawJSValue]) -> T
Expand Down
Loading