This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fury.git
The following commit(s) were added to refs/heads/main by this push:
new 5ca0c49e feat(javascript): optimize string serializer (#2043)
5ca0c49e is described below
commit 5ca0c49e4fe438b01c5440372d6c5087dc1109d8
Author: weipeng <[email protected]>
AuthorDate: Fri Feb 7 12:11:42 2025 +0800
feat(javascript): optimize string serializer (#2043)
## What does this PR do?
1. Rewrting the string serializer by c++ to optimize the performance.
2. Downloading the special version of v8's header file through nodejs's
repo when installing the `Hps Module`. Now the `Hps Module` can support
all nodejs versions.
3. With V8's string info , we can now serialize strings by directly
copying whitout any encoding or checking. This feature was submitted to
the nodejs's repo and discussed for a long time but could not be merged.
4. Supporting utf16le
## Benchmark
### Before
| (index) | serialize | deserialize |
|----------|-----------|-------------|
| fury | 100 | 107 |
| protobuf | 23 | 29 |
| json | 17 | 23 |
### After
| (index) | serialize | deserialize |
|----------|-----------|-------------|
| fury | 141 | 107 |
| protobuf | 23 | 29 |
| json | 17 | 23 |
---
javascript/.gitignore | 1 +
javascript/benchmark/index.js | 2 +-
javascript/jest.config.js | 7 +-
javascript/packages/fury/lib/platformBuffer.ts | 86 +-
javascript/packages/fury/lib/reader/index.ts | 21 +-
javascript/packages/fury/lib/type.ts | 5 +-
javascript/packages/fury/lib/util.ts | 5 +-
javascript/packages/fury/lib/writer/index.ts | 18 +-
javascript/packages/hps/binding.gyp | 6 +-
javascript/packages/hps/index.ts | 27 +-
javascript/packages/hps/package.json | 10 +-
.../packages/hps/{index.ts => scripts/build.js} | 29 +-
javascript/packages/hps/scripts/preinstall.js | 56 ++
javascript/packages/hps/src/fastcall.cc | 146 +++-
javascript/packages/hps/src/v8-fast-api-calls.h | 957 ---------------------
javascript/test/hps.test.ts | 33 +-
javascript/test/platformBuffer.test.ts | 30 +-
17 files changed, 302 insertions(+), 1137 deletions(-)
diff --git a/javascript/.gitignore b/javascript/.gitignore
index 8216c380..40b7ff8f 100644
--- a/javascript/.gitignore
+++ b/javascript/.gitignore
@@ -1,2 +1,3 @@
package-lock.json
coverage
+packages/hps/includes
\ No newline at end of file
diff --git a/javascript/benchmark/index.js b/javascript/benchmark/index.js
index 52d0b267..9de0cb0a 100644
--- a/javascript/benchmark/index.js
+++ b/javascript/benchmark/index.js
@@ -19,7 +19,7 @@
const Fury = require("@furyjs/fury");
const utils = require("@furyjs/fury/dist/lib/util");
-const hps = require('@furyjs/hps');
+const hps = require('@furyjs/hps').default;
const fury = new Fury.default({ hps, refTracking: false, useSliceString: true
});
const Benchmark = require("benchmark");
const protobuf = require("protobufjs");
diff --git a/javascript/jest.config.js b/javascript/jest.config.js
index ce814269..363bdd2b 100644
--- a/javascript/jest.config.js
+++ b/javascript/jest.config.js
@@ -18,11 +18,9 @@
*/
/** @type {import('ts-jest').JestConfigWithTsJest} */
-const semver = require("semver");
-const hpsEnable = semver.gt(process.versions.node, '20.0.0') &&
process.platform !== 'win32';
module.exports = {
- collectCoverage: hpsEnable,
+ collectCoverage: true,
preset: 'ts-jest',
testEnvironment: 'node',
collectCoverageFrom: [
@@ -31,9 +29,6 @@ module.exports = {
"!**/build/**",
"!packages/fury/lib/murmurHash3.ts"
],
- "testPathIgnorePatterns" : [
- hpsEnable ? null : "(.*)/hps.test.ts$",
- ].filter(Boolean),
transform: {
'\\.ts$': ['ts-jest', {
tsconfig: {
diff --git a/javascript/packages/fury/lib/platformBuffer.ts
b/javascript/packages/fury/lib/platformBuffer.ts
index e3a0978d..2cecadac 100644
--- a/javascript/packages/fury/lib/platformBuffer.ts
+++ b/javascript/packages/fury/lib/platformBuffer.ts
@@ -19,10 +19,11 @@
import { hasBuffer } from "./util";
-let utf8Encoder: TextEncoder | null;
-let textDecoder: TextDecoder | null;
+const utf8Encoder = new TextEncoder();
+const utf8Decoder = new TextDecoder("utf-8");
+const utf16LEDecoder = new TextDecoder("utf-16le");
-export type SupportedEncodings = "latin1" | "utf8";
+export type SupportedEncodings = "latin1" | "utf8" | "utf16le";
export interface PlatformBuffer extends Uint8Array {
toString(encoding?: SupportedEncodings, start?: number, end?: number):
string;
@@ -32,59 +33,55 @@ export interface PlatformBuffer extends Uint8Array {
export class BrowserBuffer extends Uint8Array implements PlatformBuffer {
write(string: string, offset: number, encoding: SupportedEncodings =
"utf8"): void {
- if (encoding === "latin1") {
- return this.latin1Write(string, offset);
+ switch (encoding) {
+ case "utf8":
+ return this.utf8Write(string, offset);
+ case "utf16le":
+ return this.ucs2Write(string, offset);
+ case "latin1":
+ return this.latin1Write(string, offset);
+ default:
+ break;
+ }
+ }
+
+ private ucs2Write(string: string, offset: number): void {
+ for (let i = 0; i < string.length; i++) {
+ const codePoint = string.charCodeAt(i);
+ this[offset++] = codePoint & 0xFF;
+ this[offset++] = (codePoint >> 8) & 0xFF;
}
- return this.utf8Write(string, offset);
}
toString(encoding: SupportedEncodings = "utf8", start = 0, end =
this.length): string {
- if (encoding === "latin1") {
- return this.latin1Slice(start, end);
+ switch (encoding) {
+ case "utf8":
+ return this.utf8Slice(start, end);
+ case "utf16le":
+ return this.utf16LESlice(start, end);
+ case "latin1":
+ return this.latin1Slice(start, end);
+ default:
+ return "";
}
- return this.utf8Slice(start, end);
}
static alloc(size: number) {
return new BrowserBuffer(new Uint8Array(size));
}
- latin1Write(string: string, offset: number) {
+ private latin1Write(string: string, offset: number) {
let index = 0;
for (; index < string.length; index++) {
this[offset++] = string.charCodeAt(index);
}
}
- utf8Write(string: string, offset: number) {
- let c1: number;
- let c2: number;
- for (let i = 0; i < string.length; ++i) {
- c1 = string.charCodeAt(i);
- if (c1 < 128) {
- this[offset++] = c1;
- } else if (c1 < 2048) {
- this[offset++] = (c1 >> 6) | 192;
- this[offset++] = (c1 & 63) | 128;
- } else if (
- (c1 & 0xfc00) === 0xd800
- && ((c2 = string.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
- ) {
- c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff);
- ++i;
- this[offset++] = (c1 >> 18) | 240;
- this[offset++] = ((c1 >> 12) & 63) | 128;
- this[offset++] = ((c1 >> 6) & 63) | 128;
- this[offset++] = (c1 & 63) | 128;
- } else {
- this[offset++] = (c1 >> 12) | 224;
- this[offset++] = ((c1 >> 6) & 63) | 128;
- this[offset++] = (c1 & 63) | 128;
- }
- }
+ private utf8Write(string: string, offset: number) {
+ utf8Encoder.encodeInto(string, this.subarray(offset));
}
- latin1Slice(start: number, end: number) {
+ private latin1Slice(start: number, end: number) {
if (end - start < 1) {
return "";
}
@@ -95,16 +92,18 @@ export class BrowserBuffer extends Uint8Array implements
PlatformBuffer {
return str;
}
- utf8Slice(start: number, end: number) {
+ private utf16LESlice(start: number, end: number) {
if (end - start < 1) {
return "";
}
+ return utf16LEDecoder.decode(this.subarray(start, end));
+ }
- if (!textDecoder) {
- textDecoder = new TextDecoder("utf-8");
+ private utf8Slice(start: number, end: number) {
+ if (end - start < 1) {
+ return "";
}
-
- return textDecoder.decode(this.subarray(start, end));
+ return utf8Decoder.decode(this.subarray(start, end));
}
copy(target: Uint8Array, targetStart?: number, sourceStart?: number,
sourceEnd?: number) {
@@ -150,8 +149,5 @@ export const fromString
= hasBuffer
? (str: string) => Buffer.from(str) as unknown as PlatformBuffer
: (str: string) => {
- if (!utf8Encoder) {
- utf8Encoder = new TextEncoder();
- }
return new BrowserBuffer(utf8Encoder.encode(str));
};
diff --git a/javascript/packages/fury/lib/reader/index.ts
b/javascript/packages/fury/lib/reader/index.ts
index 5731127a..46fe1aa7 100644
--- a/javascript/packages/fury/lib/reader/index.ts
+++ b/javascript/packages/fury/lib/reader/index.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { Config, LATIN1 } from "../type";
+import { Config, LATIN1, UTF16, UTF8 } from "../type";
import { isNodeEnv } from "../util";
import { PlatformBuffer, alloc, fromUint8Array } from "../platformBuffer";
import { readLatin1String } from "./string";
@@ -124,10 +124,23 @@ export class BinaryReader {
return result;
}
+ stringUtf16LE(len: number) {
+ const result = this.platformBuffer.toString("utf16le", this.cursor,
this.cursor + len);
+ this.cursor += len;
+ return result;
+ }
+
stringOfVarUInt32() {
- const isLatin1 = this.uint8() === LATIN1;
- const len = this.varUInt32();
- return isLatin1 ? this.stringLatin1(len) : this.stringUtf8(len);
+ switch (this.uint8()) {
+ case LATIN1:
+ return this.stringLatin1(this.varUInt32());
+ case UTF8:
+ return this.stringUtf8(this.varUInt32());
+ case UTF16:
+ return this.stringUtf16LE(this.varUInt32());
+ default:
+ break;
+ }
}
stringLatin1(len: number) {
diff --git a/javascript/packages/fury/lib/type.ts
b/javascript/packages/fury/lib/type.ts
index 23956da8..6c76e9c0 100644
--- a/javascript/packages/fury/lib/type.ts
+++ b/javascript/packages/fury/lib/type.ts
@@ -94,10 +94,9 @@ export const HalfMinInt32 = MinInt32 / 2;
export const LATIN1 = 0;
export const UTF8 = 1;
-
+export const UTF16 = 2;
export interface Hps {
- isLatin1: (str: string) => boolean;
- stringCopy: (str: string, dist: Uint8Array, offset: number) => void;
+ serializeString: (str: string, dist: Uint8Array, offset: number) => number;
}
export enum Mode {
diff --git a/javascript/packages/fury/lib/util.ts
b/javascript/packages/fury/lib/util.ts
index 39042704..cba73927 100644
--- a/javascript/packages/fury/lib/util.ts
+++ b/javascript/packages/fury/lib/util.ts
@@ -17,8 +17,7 @@
* under the License.
*/
-import { ObjectTypeDescription, Type, TypeDescription } from "./description";
-import { InternalSerializerType } from "./type";
+import { Type, TypeDescription } from "./description";
export const isNodeEnv: boolean
= typeof process !== "undefined"
@@ -107,7 +106,7 @@ export const data2Description = (
.map(([key, value]) => {
return [key, data2Description(value, `${tag}.${key}`)];
})
- .filter(([_, v]) => Boolean(v)),
+ .filter(([, v]) => Boolean(v)),
),
);
}
diff --git a/javascript/packages/fury/lib/writer/index.ts
b/javascript/packages/fury/lib/writer/index.ts
index 69ef3090..d8984ac3 100644
--- a/javascript/packages/fury/lib/writer/index.ts
+++ b/javascript/packages/fury/lib/writer/index.ts
@@ -188,22 +188,8 @@ export class BinaryWriter {
}
stringOfVarUInt32Fast(v: string) {
- const { isLatin1: detectIsLatin1, stringCopy } = this.config.hps!;
- const isLatin1 = detectIsLatin1(v);
- const len = isLatin1 ? v.length : strByteLength(v);
- this.dataView.setUint8(this.cursor++, isLatin1 ? LATIN1 : UTF8);
- this.varUInt32(len);
- this.reserve(len);
- if (isLatin1) {
- stringCopy(v, this.platformBuffer, this.cursor);
- } else {
- if (len < 40) {
- this.fastWriteStringUtf8(v, this.platformBuffer, this.cursor);
- } else {
- this.platformBuffer.write(v, this.cursor, "utf8");
- }
- }
- this.cursor += len;
+ const { serializeString } = this.config.hps!;
+ this.cursor = serializeString(v, this.platformBuffer, this.cursor);
}
stringOfVarUInt32Slow(v: string) {
diff --git a/javascript/packages/hps/binding.gyp
b/javascript/packages/hps/binding.gyp
index 73b11b41..a9ba8c71 100644
--- a/javascript/packages/hps/binding.gyp
+++ b/javascript/packages/hps/binding.gyp
@@ -20,11 +20,11 @@
{
"target_name": "hps",
"sources": [
- "src/fastcall.cc",
- "src/v8-fast-api-calls.h"
+ "src/fastcall.cc"
],
"include_dirs" : [
- "<!(node -e \"require('nan')\")"
+ "<!(node -e \"require('nan')\")",
+ "includes"
],
"cflags": ["-g"]
}
diff --git a/javascript/packages/hps/index.ts b/javascript/packages/hps/index.ts
index f991eb4b..b67177bd 100644
--- a/javascript/packages/hps/index.ts
+++ b/javascript/packages/hps/index.ts
@@ -17,16 +17,27 @@
* under the License.
*/
-const hps: Hps = require("bindings")("hps.node");
-
interface Hps {
- isLatin1: (str: string) => boolean;
- stringCopy: (str: string, dist: Uint8Array, offset: number) => void;
+ serializeString: (dist: Uint8Array, str: string, offset: number, maxLength:
number) => number;
}
-const { isLatin1, stringCopy } = hps;
+const build = () => {
+ try {
+ const hps: Hps = require("bindings")("hps.node");
+ const { serializeString: _serializeString } = hps;
-export {
- isLatin1,
- stringCopy,
+ return {
+ serializeString: (v: string, dist: Uint8Array, offset: number) => {
+ if (typeof v !== "string") {
+ throw new Error(`isLatin1 requires string but got ${typeof v}`);
+ }
+ // todo boundary check
+ return _serializeString(dist, v, offset, 0);
+ },
+ };
+ } catch (error) {
+ return null;
+ }
};
+
+export default build();
diff --git a/javascript/packages/hps/package.json
b/javascript/packages/hps/package.json
index ffd529d4..7a726b90 100644
--- a/javascript/packages/hps/package.json
+++ b/javascript/packages/hps/package.json
@@ -6,19 +6,21 @@
"files": [
"dist",
"src",
+ "scripts/preinstall.js",
"binding.gyp"
],
"gypfile": false,
"scripts": {
- "postinstall": "node -e \"if (process.version.match(/v(\\d+)/)[1] >= 20 &&
process.platform !== 'win32') { require('child_process').execSync('npx node-gyp
rebuild') } \"",
- "build": "node -e \"if (process.version.match(/v(\\d+)/)[1] >= 20 &&
process.platform !== 'win32') { require('child_process').execSync('npx node-gyp
rebuild && tsc') } \"",
+ "preinstall": "node ./scripts/preinstall.js",
+ "build": "node ./scripts/build.js",
"prepublishOnly": "npm run build"
},
"license": "Apache-2.0",
"dependencies": {
"bindings": "~1.2.1",
- "nan": "^2.17.0",
- "node-gyp": "^9.4.0"
+ "fs-extra": "^11.3.0",
+ "nan": "^2.22.0",
+ "node-gyp": "^11.0.0"
},
"engines": {
"node": "^20.0.0"
diff --git a/javascript/packages/hps/index.ts
b/javascript/packages/hps/scripts/build.js
similarity index 56%
copy from javascript/packages/hps/index.ts
copy to javascript/packages/hps/scripts/build.js
index f991eb4b..09673636 100644
--- a/javascript/packages/hps/index.ts
+++ b/javascript/packages/hps/scripts/build.js
@@ -17,16 +17,25 @@
* under the License.
*/
-const hps: Hps = require("bindings")("hps.node");
+const { spawn } = require("node:child_process");
+const semver = require("semver");
+const { engines } = require("../package.json");
+const versionValid = semver.satisfies(process.version, engines.node);
-interface Hps {
- isLatin1: (str: string) => boolean;
- stringCopy: (str: string, dist: Uint8Array, offset: number) => void;
+function watchError(child) {
+ child.on("error", (error) => {
+ console.error(error);
+ process.exit(1);
+ });
+ child.on("exit", (code, signal) => {
+ if (code !== 0) {
+ process.exit(code);
+ }
+ });
}
-const { isLatin1, stringCopy } = hps;
-
-export {
- isLatin1,
- stringCopy,
-};
+if (versionValid) {
+ const gyp = spawn("npx", ["node-gyp", "rebuild"], { stdio: 'inherit', shell:
true });
+ watchError(gyp);
+}
+watchError(spawn("npx", ["tsc"], { stdio: 'inherit', shell: true }));
diff --git a/javascript/packages/hps/scripts/preinstall.js
b/javascript/packages/hps/scripts/preinstall.js
new file mode 100644
index 00000000..9a1ad291
--- /dev/null
+++ b/javascript/packages/hps/scripts/preinstall.js
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+const { spawn } = require("node:child_process");
+const path = require("node:path");
+const version = process.version;
+const fs = require('fs-extra');
+const semver = require("semver");
+const { engines } = require("../package.json");
+const versionValid = semver.satisfies(process.version, engines.node);
+
+async function downloadDeps(urls) {
+ await Promise.all(urls.map(({url, dist}) => {
+ new Promise((resolve, reject) => {
+ fs.ensureDirSync(path.dirname(dist));
+ const process = spawn('curl', [url, '-o', dist]);
+ process.on("close", () => {
+ console.log("finish")
+ resolve();
+ });
+ process.on("error", () => {
+ console.error(`download from ${url} failed`);
+ reject();
+ });
+ })
+ }))
+}
+
+async function main() {
+ await downloadDeps([
+ {
+ url:
`https://raw.githubusercontent.com/nodejs/node/refs/tags/${version}/deps/v8/include/v8-fast-api-calls.h`,
+ dist: path.join(__dirname, "..", "includes/v8-fast-api-calls.h")
+ }
+ ]);
+}
+
+if (versionValid) {
+ main();
+}
diff --git a/javascript/packages/hps/src/fastcall.cc
b/javascript/packages/hps/src/fastcall.cc
index 38f1cda6..2edd27ad 100644
--- a/javascript/packages/hps/src/fastcall.cc
+++ b/javascript/packages/hps/src/fastcall.cc
@@ -19,46 +19,124 @@
#include "v8-fast-api-calls.h"
#include <nan.h>
+using v8::Local;
+using v8::String;
+using v8::Value;
-void IsLatin1Slow(const v8::FunctionCallbackInfo<v8::Value> &info) {
- v8::Local<v8::String> input = info[0].As<v8::String>();
- info.GetReturnValue().Set(Nan::New(input->IsOneByte()));
+template <typename T> constexpr T RoundUp(T a, T b) {
+ return a % b != 0 ? a + b - (a % b) : a;
}
-bool IsLatin1Fast(v8::Local<v8::Value> receiver,
- const v8::FastOneByteString &source) {
- return true;
+template <typename T, typename U> constexpr T *AlignUp(T *ptr, U alignment) {
+ return reinterpret_cast<T *>(
+ RoundUp(reinterpret_cast<uintptr_t>(ptr), alignment));
}
-static v8::CFunction fast_is_latin1(v8::CFunction::Make(IsLatin1Fast));
+uint32_t writeVarUint32(uint8_t *dst, uint32_t offset, int32_t value) {
+ if (value >> 7 == 0) {
+ dst[offset] = (uint8_t)value;
+ return 1;
+ }
+ if (value >> 14 == 0) {
+ dst[offset++] = (uint8_t)((value & 0x7F) | 0x80);
+ dst[offset++] = (uint8_t)(value >> 7);
+ return 2;
+ }
+ if (value >> 21 == 0) {
+ dst[offset++] = (uint8_t)((value & 0x7F) | 0x80);
+ dst[offset++] = (uint8_t)(value >> 7 | 0x80);
+ dst[offset++] = (uint8_t)(value >> 14);
+ return 3;
+ }
+ if (value >> 28 == 0) {
+ dst[offset++] = (uint8_t)((value & 0x7F) | 0x80);
+ dst[offset++] = (uint8_t)(value >> 7 | 0x80);
+ dst[offset++] = (uint8_t)(value >> 14 | 0x80);
+ dst[offset++] = (uint8_t)(value >> 21);
+ return 4;
+ }
+ dst[offset++] = (uint8_t)((value & 0x7F) | 0x80);
+ dst[offset++] = (uint8_t)(value >> 7 | 0x80);
+ dst[offset++] = (uint8_t)(value >> 14 | 0x80);
+ dst[offset++] = (uint8_t)(value >> 21 | 0x80);
+ dst[offset++] = (uint8_t)(value >> 28);
+ return 5;
+}
-void StringCopySlow(const v8::FunctionCallbackInfo<v8::Value> &info) {
- v8::Isolate *isolate = info.GetIsolate();
- v8::Local<v8::String> source = info[0].As<v8::String>();
- char *type_array =
- (char
*)info[1].As<v8::Uint8Array>()->Buffer()->GetBackingStore()->Data();
- int32_t offset =
- info[2]->Int32Value(info.GetIsolate()->GetCurrentContext()).FromJust();
+enum Encoding { LATIN1, UTF8, UTF16 };
- v8::String::Utf8Value source_value(isolate, source);
+uint32_t writeUCS2(v8::Isolate *isolate, uint8_t *buf, Local<String> str,
+ int flags) {
+ uint16_t *const dst = reinterpret_cast<uint16_t *>(buf);
- for (size_t i = 0; i < source_value.length(); i++) {
- *(type_array + (offset++)) = *((*source_value) + i);
+ uint16_t *const aligned_dst = AlignUp(dst, sizeof(*dst));
+ size_t nchars;
+ if (aligned_dst == dst) {
+ nchars = str->Write(isolate, dst, 0, -1, flags);
+ return nchars * sizeof(*dst);
}
- info.GetReturnValue().Set(Nan::New(100));
+
+ nchars = str->Write(isolate, aligned_dst, 0, str->Length() - 1, flags);
+
+ memmove(dst, aligned_dst, nchars * sizeof(*dst));
+
+ uint16_t last;
+ str->Write(isolate, &last, nchars, 1, flags);
+ memcpy(buf + nchars * sizeof(*dst), &last, sizeof(last));
+ nchars++;
+
+ return nchars * sizeof(*dst);
+}
+
+static void serializeString(const v8::FunctionCallbackInfo<v8::Value> &args) {
+ auto isolate = args.GetIsolate();
+ auto context = isolate->GetCurrentContext();
+ v8::Local<v8::Uint8Array> dst = args[0].As<v8::Uint8Array>();
+ v8::Local<v8::String> str = args[1].As<v8::String>();
+ uint32_t offset = args[2].As<v8::Number>()->Uint32Value(context).ToChecked();
+
+ bool is_one_byte = str->IsOneByte();
+ uint8_t *dst_data =
+ reinterpret_cast<uint8_t *>(dst->Buffer()->GetBackingStore()->Data());
+
+ if (is_one_byte && str->IsExternalOneByte()) {
+ dst_data[offset++] = Encoding::LATIN1; // encoding
+ offset += writeVarUint32(dst_data, offset, str->Length()); // length
+ const auto src = str->GetExternalOneByteStringResource()->data();
+ memcpy(dst_data + offset, src, str->Length());
+ offset += str->Length();
+ } else {
+ v8::HandleScope scope(isolate);
+ int flags = String::HINT_MANY_WRITES_EXPECTED |
+ String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8;
+ if (is_one_byte) {
+ dst_data[offset++] = Encoding::LATIN1; // encoding
+ offset += writeVarUint32(dst_data, offset, str->Length()); // length
+ offset += str->WriteOneByte(isolate, dst_data + offset, 0, str->Length(),
+ flags);
+ } else {
+ dst_data[offset++] = Encoding::UTF16; //
encoding
+ offset += writeVarUint32(dst_data, offset, str->Length() * 2); // length
+ offset += writeUCS2(isolate, dst_data + offset, str, flags);
+ }
+ }
+
+ args.GetReturnValue().Set(offset);
}
-void StringCopyFast(v8::Local<v8::Value> receiver,
- const v8::FastOneByteString &source,
- const v8::FastApiTypedArray<u_int8_t> &ab,
- u_int32_t offset) {
- uint8_t *ptr;
- ab.getStorageIfAligned(&ptr);
- int32_t i = 0;
- memcpy(ptr + offset, source.data, source.length);
+static uint32_t serializeStringFast(Local<Value> receiver,
+ const v8::FastApiTypedArray<uint8_t> &dst,
+ const v8::FastOneByteString &src,
+ uint32_t offset, uint32_t max_length) {
+ uint8_t *dst_data;
+ dst.getStorageIfAligned(&dst_data);
+ dst_data[offset++] = Encoding::LATIN1; // encoding
+ offset += writeVarUint32(dst_data, src.length, offset); // length
+ memcpy(dst_data + offset, src.data, src.length);
+ return offset + src.length;
}
-static v8::CFunction string_copy_fast(v8::CFunction::Make(StringCopyFast));
+v8::CFunction fast_serialize_string(v8::CFunction::Make(serializeStringFast));
v8::Local<v8::FunctionTemplate> FastFunction(v8::Isolate *isolate,
v8::FunctionCallback callback,
@@ -72,14 +150,12 @@ v8::Local<v8::FunctionTemplate> FastFunction(v8::Isolate
*isolate,
void Init(v8::Local<v8::Object> exports) {
v8::Local<v8::Context> context = exports->GetCreationContextChecked();
v8::Isolate *isolate = context->GetIsolate();
- exports->Set(context, Nan::New("isLatin1").ToLocalChecked(),
- FastFunction(isolate, IsLatin1Slow, &fast_is_latin1)
- ->GetFunction(context)
- .ToLocalChecked());
- exports->Set(context, Nan::New("stringCopy").ToLocalChecked(),
- FastFunction(isolate, StringCopySlow, &string_copy_fast)
- ->GetFunction(context)
- .ToLocalChecked());
+ exports
+ ->Set(context, Nan::New("serializeString").ToLocalChecked(),
+ FastFunction(isolate, serializeString, &fast_serialize_string)
+ ->GetFunction(context)
+ .ToLocalChecked())
+ .Check();
}
NODE_MODULE(fury_util, Init)
\ No newline at end of file
diff --git a/javascript/packages/hps/src/v8-fast-api-calls.h
b/javascript/packages/hps/src/v8-fast-api-calls.h
deleted file mode 100644
index 0fe7cd24..00000000
--- a/javascript/packages/hps/src/v8-fast-api-calls.h
+++ /dev/null
@@ -1,957 +0,0 @@
-// Copyright 2020 the V8 project authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * This file provides additional API on top of the default one for making
- * API calls, which come from embedder C++ functions. The functions are being
- * called directly from optimized code, doing all the necessary typechecks
- * in the compiler itself, instead of on the embedder side. Hence the "fast"
- * in the name. Example usage might look like:
- *
- * \code
- * void FastMethod(int param, bool another_param);
- *
- * v8::FunctionTemplate::New(isolate, SlowCallback, data,
- * signature, length, constructor_behavior
- * side_effect_type,
- * &v8::CFunction::Make(FastMethod));
- * \endcode
- *
- * By design, fast calls are limited by the following requirements, which
- * the embedder should enforce themselves:
- * - they should not allocate on the JS heap;
- * - they should not trigger JS execution.
- * To enforce them, the embedder could use the existing
- * v8::Isolate::DisallowJavascriptExecutionScope and a utility similar to
- * Blink's NoAllocationScope:
- *
https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/heap/thread_state_scopes.h;l=16
- *
- * Due to these limitations, it's not directly possible to report errors by
- * throwing a JS exception or to otherwise do an allocation. There is an
- * alternative way of creating fast calls that supports falling back to the
- * slow call and then performing the necessary allocation. When one creates
- * the fast method by using CFunction::MakeWithFallbackSupport instead of
- * CFunction::Make, the fast callback gets as last parameter an output
variable,
- * through which it can request falling back to the slow call. So one might
- * declare their method like:
- *
- * \code
- * void FastMethodWithFallback(int param, FastApiCallbackOptions& options);
- * \endcode
- *
- * If the callback wants to signal an error condition or to perform an
- * allocation, it must set options.fallback to true and do an early return from
- * the fast method. Then V8 checks the value of options.fallback and if it's
- * true, falls back to executing the SlowCallback, which is capable of
reporting
- * the error (either by throwing a JS exception or logging to the console) or
- * doing the allocation. It's the embedder's responsibility to ensure that the
- * fast callback is idempotent up to the point where error and fallback
- * conditions are checked, because otherwise executing the slow callback might
- * produce visible side-effects twice.
- *
- * An example for custom embedder type support might employ a way to wrap/
- * unwrap various C++ types in JSObject instances, e.g:
- *
- * \code
- *
- * // Helper method with a check for field count.
- * template <typename T, int offset>
- * inline T* GetInternalField(v8::Local<v8::Object> wrapper) {
- * assert(offset < wrapper->InternalFieldCount());
- * return reinterpret_cast<T*>(
- * wrapper->GetAlignedPointerFromInternalField(offset));
- * }
- *
- * class CustomEmbedderType {
- * public:
- * // Returns the raw C object from a wrapper JS object.
- * static CustomEmbedderType* Unwrap(v8::Local<v8::Object> wrapper) {
- * return GetInternalField<CustomEmbedderType,
- * kV8EmbedderWrapperObjectIndex>(wrapper);
- * }
- * static void FastMethod(v8::Local<v8::Object> receiver_obj, int param) {
- * CustomEmbedderType* receiver = static_cast<CustomEmbedderType*>(
- * receiver_obj->GetAlignedPointerFromInternalField(
- * kV8EmbedderWrapperObjectIndex));
- *
- * // Type checks are already done by the optimized code.
- * // Then call some performance-critical method like:
- * // receiver->Method(param);
- * }
- *
- * static void SlowMethod(
- * const v8::FunctionCallbackInfo<v8::Value>& info) {
- * v8::Local<v8::Object> instance =
- * v8::Local<v8::Object>::Cast(info.Holder());
- * CustomEmbedderType* receiver = Unwrap(instance);
- * // TODO: Do type checks and extract {param}.
- * receiver->Method(param);
- * }
- * };
- *
- * // TODO(mslekova): Clean-up these constants
- * // The constants kV8EmbedderWrapperTypeIndex and
- * // kV8EmbedderWrapperObjectIndex describe the offsets for the type info
- * // struct and the native object, when expressed as internal field indices
- * // within a JSObject. The existance of this helper function assumes that
- * // all embedder objects have their JSObject-side type info at the same
- * // offset, but this is not a limitation of the API itself. For a detailed
- * // use case, see the third example.
- * static constexpr int kV8EmbedderWrapperTypeIndex = 0;
- * static constexpr int kV8EmbedderWrapperObjectIndex = 1;
- *
- * // The following setup function can be templatized based on
- * // the {embedder_object} argument.
- * void SetupCustomEmbedderObject(v8::Isolate* isolate,
- * v8::Local<v8::Context> context,
- * CustomEmbedderType* embedder_object) {
- * isolate->set_embedder_wrapper_type_index(
- * kV8EmbedderWrapperTypeIndex);
- * isolate->set_embedder_wrapper_object_index(
- * kV8EmbedderWrapperObjectIndex);
- *
- * v8::CFunction c_func =
- * MakeV8CFunction(CustomEmbedderType::FastMethod);
- *
- * Local<v8::FunctionTemplate> method_template =
- * v8::FunctionTemplate::New(
- * isolate, CustomEmbedderType::SlowMethod, v8::Local<v8::Value>(),
- * v8::Local<v8::Signature>(), 1, v8::ConstructorBehavior::kAllow,
- * v8::SideEffectType::kHasSideEffect, &c_func);
- *
- * v8::Local<v8::ObjectTemplate> object_template =
- * v8::ObjectTemplate::New(isolate);
- * object_template->SetInternalFieldCount(
- * kV8EmbedderWrapperObjectIndex + 1);
- * object_template->Set(isolate, "method", method_template);
- *
- * // Instantiate the wrapper JS object.
- * v8::Local<v8::Object> object =
- * object_template->NewInstance(context).ToLocalChecked();
- * object->SetAlignedPointerInInternalField(
- * kV8EmbedderWrapperObjectIndex,
- * reinterpret_cast<void*>(embedder_object));
- *
- * // TODO: Expose {object} where it's necessary.
- * }
- * \endcode
- *
- * For instance if {object} is exposed via a global "obj" variable,
- * one could write in JS:
- * function hot_func() {
- * obj.method(42);
- * }
- * and once {hot_func} gets optimized, CustomEmbedderType::FastMethod
- * will be called instead of the slow version, with the following arguments:
- * receiver := the {embedder_object} from above
- * param := 42
- *
- * Currently supported return types:
- * - void
- * - bool
- * - int32_t
- * - uint32_t
- * - float32_t
- * - float64_t
- * Currently supported argument types:
- * - pointer to an embedder type
- * - JavaScript array of primitive types
- * - bool
- * - int32_t
- * - uint32_t
- * - int64_t
- * - uint64_t
- * - float32_t
- * - float64_t
- *
- * The 64-bit integer types currently have the IDL (unsigned) long long
- * semantics: https://heycam.github.io/webidl/#abstract-opdef-converttoint
- * In the future we'll extend the API to also provide conversions from/to
- * BigInt to preserve full precision.
- * The floating point types currently have the IDL (unrestricted) semantics,
- * which is the only one used by WebGL. We plan to add support also for
- * restricted floats/doubles, similarly to the BigInt conversion policies.
- * We also differ from the specific NaN bit pattern that WebIDL prescribes
- * (https://heycam.github.io/webidl/#es-unrestricted-float) in that Blink
- * passes NaN values as-is, i.e. doesn't normalize them.
- *
- * To be supported types:
- * - TypedArrays and ArrayBuffers
- * - arrays of embedder types
- *
- *
- * The API offers a limited support for function overloads:
- *
- * \code
- * void FastMethod_2Args(int param, bool another_param);
- * void FastMethod_3Args(int param, bool another_param, int third_param);
- *
- * v8::CFunction fast_method_2args_c_func =
- * MakeV8CFunction(FastMethod_2Args);
- * v8::CFunction fast_method_3args_c_func =
- * MakeV8CFunction(FastMethod_3Args);
- * const v8::CFunction fast_method_overloads[] = {fast_method_2args_c_func,
- * fast_method_3args_c_func};
- * Local<v8::FunctionTemplate> method_template =
- * v8::FunctionTemplate::NewWithCFunctionOverloads(
- * isolate, SlowCallback, data, signature, length,
- * constructor_behavior, side_effect_type,
- * {fast_method_overloads, 2});
- * \endcode
- *
- * In this example a single FunctionTemplate is associated to multiple C++
- * functions. The overload resolution is currently only based on the number of
- * arguments passed in a call. For example, if this method_template is
- * registered with a wrapper JS object as described above, a call with two
- * arguments:
- * obj.method(42, true);
- * will result in a fast call to FastMethod_2Args, while a call with three or
- * more arguments:
- * obj.method(42, true, 11);
- * will result in a fast call to FastMethod_3Args. Instead a call with less
than
- * two arguments, like:
- * obj.method(42);
- * would not result in a fast call but would fall back to executing the
- * associated SlowCallback.
- */
-
-#ifndef INCLUDE_V8_FAST_API_CALLS_H_
-#define INCLUDE_V8_FAST_API_CALLS_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <tuple>
-#include <type_traits>
-
-#include "v8-internal.h" // NOLINT(build/include_directory)
-#include "v8-local-handle.h" // NOLINT(build/include_directory)
-#include "v8-typed-array.h" // NOLINT(build/include_directory)
-#include "v8-value.h" // NOLINT(build/include_directory)
-#include "v8config.h" // NOLINT(build/include_directory)
-
-namespace v8 {
-
-class Isolate;
-
-class CTypeInfo {
- public:
- enum class Type : uint8_t {
- kVoid,
- kBool,
- kUint8,
- kInt32,
- kUint32,
- kInt64,
- kUint64,
- kFloat32,
- kFloat64,
- kPointer,
- kV8Value,
- kSeqOneByteString,
- kApiObject, // This will be deprecated once all users have
- // migrated from v8::ApiObject to v8::Local<v8::Value>.
- kAny, // This is added to enable untyped representation of fast
- // call arguments for test purposes. It can represent any of
- // the other types stored in the same memory as a union (see
- // the AnyCType struct declared below). This allows for
- // uniform passing of arguments w.r.t. their location
- // (in a register or on the stack), independent of their
- // actual type. It's currently used by the arm64 simulator
- // and can be added to the other simulators as well when fast
- // calls having both GP and FP params need to be supported.
- };
-
- // kCallbackOptionsType is not part of the Type enum
- // because it is only used internally. Use value 255 that is larger
- // than any valid Type enum.
- static constexpr Type kCallbackOptionsType = Type(255);
-
- enum class SequenceType : uint8_t {
- kScalar,
- kIsSequence, // sequence<T>
- kIsTypedArray, // TypedArray of T or any ArrayBufferView if T
- // is void
- kIsArrayBuffer // ArrayBuffer
- };
-
- enum class Flags : uint8_t {
- kNone = 0,
- kAllowSharedBit = 1 << 0, // Must be an ArrayBuffer or TypedArray
- kEnforceRangeBit = 1 << 1, // T must be integral
- kClampBit = 1 << 2, // T must be integral
- kIsRestrictedBit = 1 << 3, // T must be float or double
- };
-
- explicit constexpr CTypeInfo(
- Type type, SequenceType sequence_type = SequenceType::kScalar,
- Flags flags = Flags::kNone)
- : type_(type), sequence_type_(sequence_type), flags_(flags) {}
-
- typedef uint32_t Identifier;
- explicit constexpr CTypeInfo(Identifier identifier)
- : CTypeInfo(static_cast<Type>(identifier >> 16),
- static_cast<SequenceType>((identifier >> 8) & 255),
- static_cast<Flags>(identifier & 255)) {}
- constexpr Identifier GetId() const {
- return static_cast<uint8_t>(type_) << 16 |
- static_cast<uint8_t>(sequence_type_) << 8 |
- static_cast<uint8_t>(flags_);
- }
-
- constexpr Type GetType() const { return type_; }
- constexpr SequenceType GetSequenceType() const { return sequence_type_; }
- constexpr Flags GetFlags() const { return flags_; }
-
- static constexpr bool IsIntegralType(Type type) {
- return type == Type::kUint8 || type == Type::kInt32 ||
- type == Type::kUint32 || type == Type::kInt64 ||
- type == Type::kUint64;
- }
-
- static constexpr bool IsFloatingPointType(Type type) {
- return type == Type::kFloat32 || type == Type::kFloat64;
- }
-
- static constexpr bool IsPrimitive(Type type) {
- return IsIntegralType(type) || IsFloatingPointType(type) ||
- type == Type::kBool;
- }
-
- private:
- Type type_;
- SequenceType sequence_type_;
- Flags flags_;
-};
-
-struct FastApiTypedArrayBase {
- public:
- // Returns the length in number of elements.
- size_t V8_EXPORT length() const { return length_; }
- // Checks whether the given index is within the bounds of the collection.
- void V8_EXPORT ValidateIndex(size_t index) const;
-
- protected:
- size_t length_ = 0;
-};
-
-template <typename T>
-struct FastApiTypedArray : public FastApiTypedArrayBase {
- public:
- V8_INLINE T get(size_t index) const {
-#ifdef DEBUG
- ValidateIndex(index);
-#endif // DEBUG
- T tmp;
- memcpy(&tmp, reinterpret_cast<T*>(data_) + index, sizeof(T));
- return tmp;
- }
-
- bool getStorageIfAligned(T** elements) const {
- if (reinterpret_cast<uintptr_t>(data_) % alignof(T) != 0) {
- return false;
- }
- *elements = reinterpret_cast<T*>(data_);
- return true;
- }
-
- private:
- // This pointer should include the typed array offset applied.
- // It's not guaranteed that it's aligned to sizeof(T), it's only
- // guaranteed that it's 4-byte aligned, so for 8-byte types we need to
- // provide a special implementation for reading from it, which hides
- // the possibly unaligned read in the `get` method.
- void* data_;
-};
-
-// Any TypedArray. It uses kTypedArrayBit with base type void
-// Overloaded args of ArrayBufferView and TypedArray are not supported
-// (for now) because the generic “any” ArrayBufferView doesn’t have its
-// own instance type. It could be supported if we specify that
-// TypedArray<T> always has precedence over the generic ArrayBufferView,
-// but this complicates overload resolution.
-struct FastApiArrayBufferView {
- void* data;
- size_t byte_length;
-};
-
-struct FastApiArrayBuffer {
- void* data;
- size_t byte_length;
-};
-
-struct FastOneByteString {
- const char* data;
- uint32_t length;
-};
-
-class V8_EXPORT CFunctionInfo {
- public:
- // Construct a struct to hold a CFunction's type information.
- // |return_info| describes the function's return type.
- // |arg_info| is an array of |arg_count| CTypeInfos describing the
- // arguments. Only the last argument may be of the special type
- // CTypeInfo::kCallbackOptionsType.
- CFunctionInfo(const CTypeInfo& return_info, unsigned int arg_count,
- const CTypeInfo* arg_info);
-
- const CTypeInfo& ReturnInfo() const { return return_info_; }
-
- // The argument count, not including the v8::FastApiCallbackOptions
- // if present.
- unsigned int ArgumentCount() const {
- return HasOptions() ? arg_count_ - 1 : arg_count_;
- }
-
- // |index| must be less than ArgumentCount().
- // Note: if the last argument passed on construction of CFunctionInfo
- // has type CTypeInfo::kCallbackOptionsType, it is not included in
- // ArgumentCount().
- const CTypeInfo& ArgumentInfo(unsigned int index) const;
-
- bool HasOptions() const {
- // The options arg is always the last one.
- return arg_count_ > 0 && arg_info_[arg_count_ - 1].GetType() ==
- CTypeInfo::kCallbackOptionsType;
- }
-
- private:
- const CTypeInfo return_info_;
- const unsigned int arg_count_;
- const CTypeInfo* arg_info_;
-};
-
-struct FastApiCallbackOptions;
-
-// Provided for testing.
-struct AnyCType {
- AnyCType() : int64_value(0) {}
-
- union {
- bool bool_value;
- int32_t int32_value;
- uint32_t uint32_value;
- int64_t int64_value;
- uint64_t uint64_value;
- float float_value;
- double double_value;
- void* pointer_value;
- Local<Object> object_value;
- Local<Array> sequence_value;
- const FastApiTypedArray<uint8_t>* uint8_ta_value;
- const FastApiTypedArray<int32_t>* int32_ta_value;
- const FastApiTypedArray<uint32_t>* uint32_ta_value;
- const FastApiTypedArray<int64_t>* int64_ta_value;
- const FastApiTypedArray<uint64_t>* uint64_ta_value;
- const FastApiTypedArray<float>* float_ta_value;
- const FastApiTypedArray<double>* double_ta_value;
- const FastOneByteString* string_value;
- FastApiCallbackOptions* options_value;
- };
-};
-
-static_assert(
- sizeof(AnyCType) == 8,
- "The AnyCType struct should have size == 64 bits, as this is assumed "
- "by EffectControlLinearizer.");
-
-class V8_EXPORT CFunction {
- public:
- constexpr CFunction() : address_(nullptr), type_info_(nullptr) {}
-
- const CTypeInfo& ReturnInfo() const { return type_info_->ReturnInfo(); }
-
- const CTypeInfo& ArgumentInfo(unsigned int index) const {
- return type_info_->ArgumentInfo(index);
- }
-
- unsigned int ArgumentCount() const { return type_info_->ArgumentCount(); }
-
- const void* GetAddress() const { return address_; }
- const CFunctionInfo* GetTypeInfo() const { return type_info_; }
-
- enum class OverloadResolution { kImpossible, kAtRuntime, kAtCompileTime };
-
- // Returns whether an overload between this and the given CFunction can
- // be resolved at runtime by the RTTI available for the arguments or at
- // compile time for functions with different number of arguments.
- OverloadResolution GetOverloadResolution(const CFunction* other) {
- // Runtime overload resolution can only deal with functions with the
- // same number of arguments. Functions with different arity are handled
- // by compile time overload resolution though.
- if (ArgumentCount() != other->ArgumentCount()) {
- return OverloadResolution::kAtCompileTime;
- }
-
- // The functions can only differ by a single argument position.
- int diff_index = -1;
- for (unsigned int i = 0; i < ArgumentCount(); ++i) {
- if (ArgumentInfo(i).GetSequenceType() !=
- other->ArgumentInfo(i).GetSequenceType()) {
- if (diff_index >= 0) {
- return OverloadResolution::kImpossible;
- }
- diff_index = i;
-
- // We only support overload resolution between sequence types.
- if (ArgumentInfo(i).GetSequenceType() ==
- CTypeInfo::SequenceType::kScalar ||
- other->ArgumentInfo(i).GetSequenceType() ==
- CTypeInfo::SequenceType::kScalar) {
- return OverloadResolution::kImpossible;
- }
- }
- }
-
- return OverloadResolution::kAtRuntime;
- }
-
- template <typename F>
- static CFunction Make(F* func) {
- return ArgUnwrap<F*>::Make(func);
- }
-
- // Provided for testing purposes.
- template <typename R, typename... Args, typename R_Patch,
- typename... Args_Patch>
- static CFunction Make(R (*func)(Args...),
- R_Patch (*patching_func)(Args_Patch...)) {
- CFunction c_func = ArgUnwrap<R (*)(Args...)>::Make(func);
- static_assert(
- sizeof...(Args_Patch) == sizeof...(Args),
- "The patching function must have the same number of arguments.");
- c_func.address_ = reinterpret_cast<void*>(patching_func);
- return c_func;
- }
-
- CFunction(const void* address, const CFunctionInfo* type_info);
-
- private:
- const void* address_;
- const CFunctionInfo* type_info_;
-
- template <typename F>
- class ArgUnwrap {
- static_assert(sizeof(F) != sizeof(F),
- "CFunction must be created from a function pointer.");
- };
-
- template <typename R, typename... Args>
- class ArgUnwrap<R (*)(Args...)> {
- public:
- static CFunction Make(R (*func)(Args...));
- };
-};
-
-/**
- * A struct which may be passed to a fast call callback, like so:
- * \code
- * void FastMethodWithOptions(int param, FastApiCallbackOptions& options);
- * \endcode
- */
-struct FastApiCallbackOptions {
- /**
- * Creates a new instance of FastApiCallbackOptions for testing purpose. The
- * returned instance may be filled with mock data.
- */
- static FastApiCallbackOptions CreateForTesting(Isolate* isolate) {
- return {false, {0}, nullptr};
- }
-
- /**
- * If the callback wants to signal an error condition or to perform an
- * allocation, it must set options.fallback to true and do an early return
- * from the fast method. Then V8 checks the value of options.fallback and if
- * it's true, falls back to executing the SlowCallback, which is capable of
- * reporting the error (either by throwing a JS exception or logging to the
- * console) or doing the allocation. It's the embedder's responsibility to
- * ensure that the fast callback is idempotent up to the point where error
and
- * fallback conditions are checked, because otherwise executing the slow
- * callback might produce visible side-effects twice.
- */
- bool fallback;
-
- /**
- * The `data` passed to the FunctionTemplate constructor, or `undefined`.
- * `data_ptr` allows for default constructing FastApiCallbackOptions.
- */
- union {
- uintptr_t data_ptr;
- v8::Local<v8::Value> data;
- };
-
- /**
- * When called from WebAssembly, a view of the calling module's memory.
- */
- FastApiTypedArray<uint8_t>* const wasm_memory;
-};
-
-namespace internal {
-
-// Helper to count the number of occurances of `T` in `List`
-template <typename T, typename... List>
-struct count : std::integral_constant<int, 0> {};
-template <typename T, typename... Args>
-struct count<T, T, Args...>
- : std::integral_constant<std::size_t, 1 + count<T, Args...>::value> {};
-template <typename T, typename U, typename... Args>
-struct count<T, U, Args...> : count<T, Args...> {};
-
-template <typename RetBuilder, typename... ArgBuilders>
-class CFunctionInfoImpl : public CFunctionInfo {
- static constexpr int kOptionsArgCount =
- count<FastApiCallbackOptions&, ArgBuilders...>();
- static constexpr int kReceiverCount = 1;
-
- static_assert(kOptionsArgCount == 0 || kOptionsArgCount == 1,
- "Only one options parameter is supported.");
-
- static_assert(sizeof...(ArgBuilders) >= kOptionsArgCount + kReceiverCount,
- "The receiver or the options argument is missing.");
-
- public:
- constexpr CFunctionInfoImpl()
- : CFunctionInfo(RetBuilder::Build(), sizeof...(ArgBuilders),
- arg_info_storage_),
- arg_info_storage_{ArgBuilders::Build()...} {
- constexpr CTypeInfo::Type kReturnType = RetBuilder::Build().GetType();
- static_assert(kReturnType == CTypeInfo::Type::kVoid ||
- kReturnType == CTypeInfo::Type::kBool ||
- kReturnType == CTypeInfo::Type::kInt32 ||
- kReturnType == CTypeInfo::Type::kUint32 ||
- kReturnType == CTypeInfo::Type::kFloat32 ||
- kReturnType == CTypeInfo::Type::kFloat64 ||
- kReturnType == CTypeInfo::Type::kPointer ||
- kReturnType == CTypeInfo::Type::kAny,
- "64-bit int, string and api object values are not currently "
- "supported return types.");
- }
-
- private:
- const CTypeInfo arg_info_storage_[sizeof...(ArgBuilders)];
-};
-
-template <typename T>
-struct TypeInfoHelper {
- static_assert(sizeof(T) != sizeof(T), "This type is not supported");
-};
-
-#define SPECIALIZE_GET_TYPE_INFO_HELPER_FOR(T, Enum) \
- template <> \
- struct TypeInfoHelper<T> { \
- static constexpr CTypeInfo::Flags Flags() { \
- return CTypeInfo::Flags::kNone; \
- } \
- \
- static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::Enum; } \
- static constexpr CTypeInfo::SequenceType SequenceType() { \
- return CTypeInfo::SequenceType::kScalar; \
- } \
- };
-
-template <CTypeInfo::Type type>
-struct CTypeInfoTraits {};
-
-#define DEFINE_TYPE_INFO_TRAITS(CType, Enum) \
- template <> \
- struct CTypeInfoTraits<CTypeInfo::Type::Enum> { \
- using ctype = CType; \
- };
-
-#define PRIMITIVE_C_TYPES(V) \
- V(bool, kBool) \
- V(uint8_t, kUint8) \
- V(int32_t, kInt32) \
- V(uint32_t, kUint32) \
- V(int64_t, kInt64) \
- V(uint64_t, kUint64) \
- V(float, kFloat32) \
- V(double, kFloat64) \
- V(void*, kPointer)
-
-// Same as above, but includes deprecated types for compatibility.
-#define ALL_C_TYPES(V) \
- PRIMITIVE_C_TYPES(V) \
- V(void, kVoid) \
- V(v8::Local<v8::Value>, kV8Value) \
- V(v8::Local<v8::Object>, kV8Value) \
- V(AnyCType, kAny)
-
-// ApiObject was a temporary solution to wrap the pointer to the v8::Value.
-// Please use v8::Local<v8::Value> in new code for the arguments and
-// v8::Local<v8::Object> for the receiver, as ApiObject will be deprecated.
-
-ALL_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR)
-PRIMITIVE_C_TYPES(DEFINE_TYPE_INFO_TRAITS)
-
-#undef PRIMITIVE_C_TYPES
-#undef ALL_C_TYPES
-
-#define SPECIALIZE_GET_TYPE_INFO_HELPER_FOR_TA(T, Enum) \
- template <> \
- struct TypeInfoHelper<const FastApiTypedArray<T>&> { \
- static constexpr CTypeInfo::Flags Flags() { \
- return CTypeInfo::Flags::kNone; \
- } \
- \
- static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::Enum; } \
- static constexpr CTypeInfo::SequenceType SequenceType() { \
- return CTypeInfo::SequenceType::kIsTypedArray; \
- } \
- };
-
-#define TYPED_ARRAY_C_TYPES(V) \
- V(uint8_t, kUint8) \
- V(int32_t, kInt32) \
- V(uint32_t, kUint32) \
- V(int64_t, kInt64) \
- V(uint64_t, kUint64) \
- V(float, kFloat32) \
- V(double, kFloat64)
-
-TYPED_ARRAY_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR_TA)
-
-#undef TYPED_ARRAY_C_TYPES
-
-template <>
-struct TypeInfoHelper<v8::Local<v8::Array>> {
- static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; }
-
- static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::kVoid; }
- static constexpr CTypeInfo::SequenceType SequenceType() {
- return CTypeInfo::SequenceType::kIsSequence;
- }
-};
-
-template <>
-struct TypeInfoHelper<v8::Local<v8::Uint32Array>> {
- static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; }
-
- static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::kUint32; }
- static constexpr CTypeInfo::SequenceType SequenceType() {
- return CTypeInfo::SequenceType::kIsTypedArray;
- }
-};
-
-template <>
-struct TypeInfoHelper<FastApiCallbackOptions&> {
- static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; }
-
- static constexpr CTypeInfo::Type Type() {
- return CTypeInfo::kCallbackOptionsType;
- }
- static constexpr CTypeInfo::SequenceType SequenceType() {
- return CTypeInfo::SequenceType::kScalar;
- }
-};
-
-template <>
-struct TypeInfoHelper<const FastOneByteString&> {
- static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; }
-
- static constexpr CTypeInfo::Type Type() {
- return CTypeInfo::Type::kSeqOneByteString;
- }
- static constexpr CTypeInfo::SequenceType SequenceType() {
- return CTypeInfo::SequenceType::kScalar;
- }
-};
-
-#define STATIC_ASSERT_IMPLIES(COND, ASSERTION, MSG) \
- static_assert(((COND) == 0) || (ASSERTION), MSG)
-
-} // namespace internal
-
-template <typename T, CTypeInfo::Flags... Flags>
-class V8_EXPORT CTypeInfoBuilder {
- public:
- using BaseType = T;
-
- static constexpr CTypeInfo Build() {
- constexpr CTypeInfo::Flags kFlags =
- MergeFlags(internal::TypeInfoHelper<T>::Flags(), Flags...);
- constexpr CTypeInfo::Type kType = internal::TypeInfoHelper<T>::Type();
- constexpr CTypeInfo::SequenceType kSequenceType =
- internal::TypeInfoHelper<T>::SequenceType();
-
- STATIC_ASSERT_IMPLIES(
- uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kAllowSharedBit),
- (kSequenceType == CTypeInfo::SequenceType::kIsTypedArray ||
- kSequenceType == CTypeInfo::SequenceType::kIsArrayBuffer),
- "kAllowSharedBit is only allowed for TypedArrays and ArrayBuffers.");
- STATIC_ASSERT_IMPLIES(
- uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kEnforceRangeBit),
- CTypeInfo::IsIntegralType(kType),
- "kEnforceRangeBit is only allowed for integral types.");
- STATIC_ASSERT_IMPLIES(
- uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kClampBit),
- CTypeInfo::IsIntegralType(kType),
- "kClampBit is only allowed for integral types.");
- STATIC_ASSERT_IMPLIES(
- uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kIsRestrictedBit),
- CTypeInfo::IsFloatingPointType(kType),
- "kIsRestrictedBit is only allowed for floating point types.");
- STATIC_ASSERT_IMPLIES(kSequenceType ==
CTypeInfo::SequenceType::kIsSequence,
- kType == CTypeInfo::Type::kVoid,
- "Sequences are only supported from void type.");
- STATIC_ASSERT_IMPLIES(
- kSequenceType == CTypeInfo::SequenceType::kIsTypedArray,
- CTypeInfo::IsPrimitive(kType) || kType == CTypeInfo::Type::kVoid,
- "TypedArrays are only supported from primitive types or void.");
-
- // Return the same type with the merged flags.
- return CTypeInfo(internal::TypeInfoHelper<T>::Type(),
- internal::TypeInfoHelper<T>::SequenceType(), kFlags);
- }
-
- private:
- template <typename... Rest>
- static constexpr CTypeInfo::Flags MergeFlags(CTypeInfo::Flags flags,
- Rest... rest) {
- return CTypeInfo::Flags(uint8_t(flags) | uint8_t(MergeFlags(rest...)));
- }
- static constexpr CTypeInfo::Flags MergeFlags() { return CTypeInfo::Flags(0);
}
-};
-
-namespace internal {
-template <typename RetBuilder, typename... ArgBuilders>
-class CFunctionBuilderWithFunction {
- public:
- explicit constexpr CFunctionBuilderWithFunction(const void* fn) : fn_(fn) {}
-
- template <CTypeInfo::Flags... Flags>
- constexpr auto Ret() {
- return CFunctionBuilderWithFunction<
- CTypeInfoBuilder<typename RetBuilder::BaseType, Flags...>,
- ArgBuilders...>(fn_);
- }
-
- template <unsigned int N, CTypeInfo::Flags... Flags>
- constexpr auto Arg() {
- // Return a copy of the builder with the Nth arg builder merged with
- // template parameter pack Flags.
- return ArgImpl<N, Flags...>(
- std::make_index_sequence<sizeof...(ArgBuilders)>());
- }
-
- // Provided for testing purposes.
- template <typename Ret, typename... Args>
- auto Patch(Ret (*patching_func)(Args...)) {
- static_assert(
- sizeof...(Args) == sizeof...(ArgBuilders),
- "The patching function must have the same number of arguments.");
- fn_ = reinterpret_cast<void*>(patching_func);
- return *this;
- }
-
- auto Build() {
- static CFunctionInfoImpl<RetBuilder, ArgBuilders...> instance;
- return CFunction(fn_, &instance);
- }
-
- private:
- template <bool Merge, unsigned int N, CTypeInfo::Flags... Flags>
- struct GetArgBuilder;
-
- // Returns the same ArgBuilder as the one at index N, including its flags.
- // Flags in the template parameter pack are ignored.
- template <unsigned int N, CTypeInfo::Flags... Flags>
- struct GetArgBuilder<false, N, Flags...> {
- using type =
- typename std::tuple_element<N, std::tuple<ArgBuilders...>>::type;
- };
-
- // Returns an ArgBuilder with the same base type as the one at index N,
- // but merges the flags with the flags in the template parameter pack.
- template <unsigned int N, CTypeInfo::Flags... Flags>
- struct GetArgBuilder<true, N, Flags...> {
- using type = CTypeInfoBuilder<
- typename std::tuple_element<N,
-
std::tuple<ArgBuilders...>>::type::BaseType,
- std::tuple_element<N, std::tuple<ArgBuilders...>>::type::Build()
- .GetFlags(),
- Flags...>;
- };
-
- // Return a copy of the CFunctionBuilder, but merges the Flags on
- // ArgBuilder index N with the new Flags passed in the template parameter
- // pack.
- template <unsigned int N, CTypeInfo::Flags... Flags, size_t... I>
- constexpr auto ArgImpl(std::index_sequence<I...>) {
- return CFunctionBuilderWithFunction<
- RetBuilder, typename GetArgBuilder<N == I, I, Flags...>::type...>(fn_);
- }
-
- const void* fn_;
-};
-
-class CFunctionBuilder {
- public:
- constexpr CFunctionBuilder() {}
-
- template <typename R, typename... Args>
- constexpr auto Fn(R (*fn)(Args...)) {
- return CFunctionBuilderWithFunction<CTypeInfoBuilder<R>,
- CTypeInfoBuilder<Args>...>(
- reinterpret_cast<const void*>(fn));
- }
-};
-
-} // namespace internal
-
-// static
-template <typename R, typename... Args>
-CFunction CFunction::ArgUnwrap<R (*)(Args...)>::Make(R (*func)(Args...)) {
- return internal::CFunctionBuilder().Fn(func).Build();
-}
-
-using CFunctionBuilder = internal::CFunctionBuilder;
-
-static constexpr CTypeInfo kTypeInfoInt32 = CTypeInfo(CTypeInfo::Type::kInt32);
-static constexpr CTypeInfo kTypeInfoFloat64 =
- CTypeInfo(CTypeInfo::Type::kFloat64);
-
-/**
- * Copies the contents of this JavaScript array to a C++ buffer with
- * a given max_length. A CTypeInfo is passed as an argument,
- * instructing different rules for conversion (e.g. restricted float/double).
- * The element type T of the destination array must match the C type
- * corresponding to the CTypeInfo (specified by CTypeInfoTraits).
- * If the array length is larger than max_length or the array is of
- * unsupported type, the operation will fail, returning false. Generally, an
- * array which contains objects, undefined, null or anything not convertible
- * to the requested destination type, is considered unsupported. The operation
- * returns true on success. `type_info` will be used for conversions.
- */
-template <CTypeInfo::Identifier type_info_id, typename T>
-bool V8_EXPORT V8_WARN_UNUSED_RESULT TryToCopyAndConvertArrayToCppBuffer(
- Local<Array> src, T* dst, uint32_t max_length);
-
-template <>
-bool V8_EXPORT V8_WARN_UNUSED_RESULT
-TryToCopyAndConvertArrayToCppBuffer<CTypeInfoBuilder<int32_t>::Build().GetId(),
- int32_t>(Local<Array> src, int32_t* dst,
- uint32_t max_length);
-
-template <>
-bool V8_EXPORT V8_WARN_UNUSED_RESULT
-TryToCopyAndConvertArrayToCppBuffer<CTypeInfoBuilder<uint32_t>::Build().GetId(),
- uint32_t>(Local<Array> src, uint32_t* dst,
- uint32_t max_length);
-
-template <>
-bool V8_EXPORT V8_WARN_UNUSED_RESULT
-TryToCopyAndConvertArrayToCppBuffer<CTypeInfoBuilder<float>::Build().GetId(),
- float>(Local<Array> src, float* dst,
- uint32_t max_length);
-
-template <>
-bool V8_EXPORT V8_WARN_UNUSED_RESULT
-TryToCopyAndConvertArrayToCppBuffer<CTypeInfoBuilder<double>::Build().GetId(),
- double>(Local<Array> src, double* dst,
- uint32_t max_length);
-
-} // namespace v8
-
-#endif // INCLUDE_V8_FAST_API_CALLS_H_
diff --git a/javascript/test/hps.test.ts b/javascript/test/hps.test.ts
index 8d8bc1bc..40be8d4e 100644
--- a/javascript/test/hps.test.ts
+++ b/javascript/test/hps.test.ts
@@ -17,28 +17,27 @@
* under the License.
*/
-import { isLatin1, stringCopy } from '../packages/hps/index';
+import { BinaryReader } from '@furyjs/fury/dist/lib/reader';
+import hps from '../packages/hps/index';
import { describe, expect, test } from '@jest/globals';
-describe('hps', () => {
- test('should isLatin1 work', () => {
- for (let index = 0; index < 10000; index++) {
- var is = isLatin1("hello");
- expect(is).toBe(true)
-
- var is = isLatin1("😁");
- expect(is).toBe(false)
- }
- });
+const skipableDescribe = (hps ? describe : describe.skip);
- test('should stringCopy work', () => {
+skipableDescribe('hps', () => {
+ test.only('should isLatin1 work', () => {
+ const { serializeString } = hps!;
for (let index = 0; index < 10000; index++) {
- const dist = new Uint8Array(5);
- stringCopy("hello", dist, 0);
- expect([...dist]).toEqual([104, 101, 108, 108, 111])
+ const bf = Buffer.alloc(100);
+ serializeString("hello", bf, 0);
+ var reader = new BinaryReader({});
+ reader.reset(bf);
+ expect(reader.stringOfVarUInt32()).toBe("hello")
+
+ serializeString("😁", bf, 0);
+ var reader = new BinaryReader({});
+ reader.reset(bf);
+ expect(reader.stringOfVarUInt32()).toBe("😁")
}
});
});
-
-
diff --git a/javascript/test/platformBuffer.test.ts
b/javascript/test/platformBuffer.test.ts
index cc633fc4..e0f6362e 100644
--- a/javascript/test/platformBuffer.test.ts
+++ b/javascript/test/platformBuffer.test.ts
@@ -40,37 +40,20 @@ describe('platformBuffer', () => {
test('should latin1Write work', () => {
const bb = BrowserBuffer.alloc(100);
- bb.latin1Write("hello, world", 0);
+ bb.write("hello, world", 0, "latin1");
- const str = bb.latin1Slice(0, 12);
+ const str = bb.toString("latin1", 0, 12);
expect(str).toBe("hello, world");
-
- const str2 = bb.toString('latin1', 0, 12);
- expect(str2).toBe("hello, world");
- });
-
- test('should write latin1 work', () => {
- const bb = BrowserBuffer.alloc(100);
- bb.write("hello, world", 0, 'latin1');
-
- const str = bb.latin1Slice(0, 12);
- expect(str).toBe("hello, world");
-
- const str2 = bb.toString('latin1', 0, 12);
- expect(str2).toBe("hello, world");
});
test('should utf8Write work', () => {
const rawStr = "我是Fury, 你好!😁א";
const bb = BrowserBuffer.alloc(100);
- bb.utf8Write(rawStr, 0);
+ bb.write(rawStr, 0);
- const str = bb.utf8Slice(0, 27);
+ const str = bb.toString("utf8", 0, 27);
expect(str).toBe(rawStr);
-
- const str2 = bb.toString('utf8', 0, 27);
- expect(str2).toBe(rawStr);
});
test('should utf8 work', () => {
@@ -78,9 +61,6 @@ describe('platformBuffer', () => {
const bb = BrowserBuffer.alloc(100);
bb.write(rawStr, 0, 'utf8');
- const str = bb.utf8Slice(0, 27);
- expect(str).toBe(rawStr);
-
const str2 = bb.toString('utf8', 0, 27);
expect(str2).toBe(rawStr);
});
@@ -93,7 +73,7 @@ describe('platformBuffer', () => {
test('should copy work', () => {
const bb = BrowserBuffer.alloc(100);
- bb.latin1Write("hello", 0);
+ bb.write("hello", 0, 'latin1');
const target = new Uint8Array(5);
bb.copy(target, 0, 0, 5);
expect([...target]).toEqual([ 104, 101, 108, 108, 111 ])
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]