aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-11-10 15:05:06 -0800
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-11-10 15:05:23 -0800
commitdddbce8a41aa266af106b19e583c152126900730 (patch)
tree0940f99ea3dca7b43764a47b5cc1e9849981da3c
parent7ce8328d28f415fd89cc025121b774d7a6e4661e (diff)
downloadbun-dddbce8a41aa266af106b19e583c152126900730.tar.gz
bun-dddbce8a41aa266af106b19e583c152126900730.tar.zst
bun-dddbce8a41aa266af106b19e583c152126900730.zip
Implement `napi_create_external` and `napi_get_value_external`
-rw-r--r--src/bun.js/bindings/napi.cpp47
-rw-r--r--src/bun.js/bindings/napi_external.cpp60
-rw-r--r--src/bun.js/bindings/napi_external.h93
-rw-r--r--src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h1
-rw-r--r--src/bun.js/bindings/webcore/DOMIsoSubspaces.h1
-rw-r--r--src/napi/napi.zig4
-rw-r--r--src/options.zig4
-rw-r--r--src/symbols.dyn2
-rw-r--r--src/symbols.txt2
-rw-r--r--test/bun.js/napi-test.c3
-rw-r--r--test/bun.js/napi.test.ts0
-rwxr-xr-xtest/bun.js/third-party/napi_create_external/bun.lockbbin0 -> 1584 bytes
-rw-r--r--test/bun.js/third-party/napi_create_external/napi-create-external.test.ts199
-rw-r--r--test/bun.js/third-party/napi_create_external/package.json12
14 files changed, 342 insertions, 86 deletions
diff --git a/src/bun.js/bindings/napi.cpp b/src/bun.js/bindings/napi.cpp
index 223808035..649b8bf50 100644
--- a/src/bun.js/bindings/napi.cpp
+++ b/src/bun.js/bindings/napi.cpp
@@ -50,6 +50,7 @@
#include "../modules/ObjectModule.h"
#include "JavaScriptCore/JSSourceCode.h"
+#include "napi_external.h"
// #include <iostream>
using namespace JSC;
@@ -1433,3 +1434,49 @@ extern "C" napi_status napi_get_property_names(napi_env env, napi_value object,
return napi_ok;
}
+
+extern "C" napi_status napi_create_external(napi_env env, void* data,
+ napi_finalize finalize_cb,
+ void* finalize_hint,
+ napi_value* result)
+{
+ if (UNLIKELY(result == nullptr)) {
+ return napi_invalid_arg;
+ }
+
+ Zig::GlobalObject* globalObject = toJS(env);
+ JSC::VM& vm = globalObject->vm();
+
+ auto* structure = Bun::NapiExternal::createStructure(vm, globalObject, globalObject->objectPrototype());
+ JSValue value = JSValue(Bun::NapiExternal::create(vm, structure, data, finalize_hint, finalize_cb));
+ JSC::EnsureStillAliveScope ensureStillAlive(value);
+ *result = toNapi(value);
+ return napi_ok;
+}
+
+extern "C" napi_status napi_get_value_external(napi_env env, napi_value value,
+ void** result)
+{
+ if (UNLIKELY(result == nullptr)) {
+ return napi_invalid_arg;
+ }
+
+ Zig::GlobalObject* globalObject = toJS(env);
+ JSC::VM& vm = globalObject->vm();
+
+ auto scope = DECLARE_CATCH_SCOPE(vm);
+ JSC::JSValue jsValue = JSC::JSValue::decode(reinterpret_cast<JSC::EncodedJSValue>(value));
+ JSC::EnsureStillAliveScope ensureStillAlive(jsValue);
+
+ if (!jsValue || !jsValue.isObject()) {
+ return napi_invalid_arg;
+ }
+
+ JSC::JSObject* object = jsValue.getObject();
+ if (!object->inherits<Bun::NapiExternal>()) {
+ return napi_invalid_arg;
+ }
+
+ *result = jsCast<Bun::NapiExternal*>(object)->value();
+ return napi_ok;
+}
diff --git a/src/bun.js/bindings/napi_external.cpp b/src/bun.js/bindings/napi_external.cpp
index 15e46aa97..eb5786b6f 100644
--- a/src/bun.js/bindings/napi_external.cpp
+++ b/src/bun.js/bindings/napi_external.cpp
@@ -1,50 +1,20 @@
+#include "napi_external.h"
+#include "napi.h"
+namespace Bun {
-// #pragma once
+NapiExternal::~NapiExternal()
+{
+ if (finalizer) {
+ finalizer(toNapi(globalObject()), m_value, m_finalizerHint);
+ }
+}
-// #include "root.h"
+void NapiExternal::destroy(JSC::JSCell* cell)
+{
+ jsCast<NapiExternal*>(cell)->~NapiExternal();
+}
-// #include "BunBuiltinNames.h"
-// #include "BunClientData.h"
+const ClassInfo NapiExternal::s_info = { "External"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NapiExternal) };
-// namespace Zig {
-
-// using namespace JSC;
-
-// class NapiExternal : public JSC::JSNonFinalObject {
-// using Base = JSC::JSNonFinalObject;
-
-// public:
-// NapiExternal(JSC::VM& vm, JSC::Structure* structure)
-// : Base(vm, structure)
-// {
-// }
-
-// DECLARE_INFO;
-
-// static constexpr unsigned StructureFlags = Base::StructureFlags;
-
-// template<typename CellType, SubspaceAccess> static GCClient::IsoSubspace* subspaceFor(VM& vm)
-// {
-// return &vm.plainObjectSpace();
-// }
-
-// static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject,
-// JSC::JSValue prototype)
-// {
-// return JSC::Structure::create(vm, globalObject, prototype,
-// JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
-// }
-
-// static NapiExternal* create(JSC::VM& vm, JSC::Structure* structure)
-// {
-// NapiExternal* accessor = new (NotNull, JSC::allocateCell<NapiExternal>(vm)) NapiExternal(vm, structure);
-// accessor->finishCreation(vm);
-// return accessor;
-// }
-
-// void finishCreation(JSC::VM& vm);
-// void* m_value;
-// };
-
-// } // namespace Zig \ No newline at end of file
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/napi_external.h b/src/bun.js/bindings/napi_external.h
index edac914f8..8090281fe 100644
--- a/src/bun.js/bindings/napi_external.h
+++ b/src/bun.js/bindings/napi_external.h
@@ -6,13 +6,15 @@
#include "BunBuiltinNames.h"
#include "BunClientData.h"
+#include "node_api.h"
-namespace Zig {
+namespace Bun {
using namespace JSC;
+using namespace WebCore;
-class NapiExternal : public JSC::JSNonFinalObject {
- using Base = JSC::JSNonFinalObject;
+class NapiExternal : public JSC::JSDestructibleObject {
+ using Base = JSC::JSDestructibleObject;
public:
NapiExternal(JSC::VM& vm, JSC::Structure* structure)
@@ -20,45 +22,54 @@ public:
{
}
- DECLARE_INFO;
+ DECLARE_EXPORT_INFO;
- ~NapiExternal()
+ static constexpr unsigned StructureFlags = Base::StructureFlags;
+
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+
+ return WebCore::subspaceForImpl<NapiExternal, UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForNapiExternal.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNapiExternal = WTFMove(space); },
+ [](auto& spaces) { return spaces.m_subspaceForNapiExternal.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForNapiExternal = WTFMove(space); });
+ }
+
+ ~NapiExternal();
+
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject,
+ JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype,
+ JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
+ }
+
+ static NapiExternal* create(JSC::VM& vm, JSC::Structure* structure, void* value, void* finalizer_hint, napi_finalize finalizer)
{
- if (m_value) {
- delete m_value;
- }
-
- static constexpr unsigned StructureFlags = Base::StructureFlags;
-
- template<typename, SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM & vm)
- {
- if constexpr (mode == JSC::SubspaceAccess::Concurrently)
- return nullptr;
- return WebCore::subspaceForImpl<JSNapiExternal, WebCore::UseCustomHeapCellType::No>(
- vm,
- [](auto& spaces) { return spaces.m_clientSubspaceForNapiExternal.get(); },
- [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNapiExternal = WTFMove(space); },
- [](auto& spaces) { return spaces.m_subspaceForNapiExternal.get(); },
- [](auto& spaces, auto&& space) { spaces.m_subspaceForNapiExternal = WTFMove(space); });
- }
-
- static JSC::Structure* createStructure(JSC::VM & vm, JSC::JSGlobalObject * globalObject,
- JSC::JSValue prototype)
- {
- return JSC::Structure::create(vm, globalObject, prototype,
- JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
- }
-
- static NapiExternal* create(JSC::VM & vm, JSC::Structure * structure)
- {
- NapiExternal* accessor = new (NotNull, JSC::allocateCell<NapiExternal>(vm)) NapiExternal(vm, structure);
- accessor->finishCreation(vm);
- return accessor;
- }
-
- void finishCreation(JSC::VM & vm);
- void* m_value;
- void* finalizer_context;
- };
+ NapiExternal* accessor = new (NotNull, JSC::allocateCell<NapiExternal>(vm)) NapiExternal(vm, structure);
+ accessor->finishCreation(vm, value, finalizer_hint, finalizer);
+ return accessor;
+ }
+
+ void finishCreation(JSC::VM& vm, void* value, void* finalizer_hint, napi_finalize finalizer)
+ {
+ Base::finishCreation(vm);
+ m_value = value;
+ m_finalizerHint = finalizer_hint;
+ this->finalizer = finalizer;
+ }
+
+ static void destroy(JSC::JSCell* cell);
+
+ void* value() const { return m_value; }
+
+ void* m_value;
+ void* m_finalizerHint;
+ napi_finalize finalizer;
+};
} // namespace Zig \ No newline at end of file
diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
index cd292d938..e7504c024 100644
--- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
@@ -31,6 +31,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForPendingVirtualModuleResult;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForOnigurumaRegExp;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCallSite;
+ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNapiExternal;
#include "ZigGeneratedClasses+DOMClientIsoSubspaces.h"
/* --- bun --- */
diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
index 568b4e978..cba631c61 100644
--- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
@@ -31,6 +31,7 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForPendingVirtualModuleResult;
std::unique_ptr<IsoSubspace> m_subspaceForOnigurumaRegExp;
std::unique_ptr<IsoSubspace> m_subspaceForCallSite;
+ std::unique_ptr<IsoSubspace> m_subspaceForNapiExternal;
#include "ZigGeneratedClasses+DOMIsoSubspaces.h"
/*-- BUN --*/
diff --git a/src/napi/napi.zig b/src/napi/napi.zig
index 46e10765e..3beaa03d7 100644
--- a/src/napi/napi.zig
+++ b/src/napi/napi.zig
@@ -1506,6 +1506,8 @@ pub fn fixDeadCodeElimination() void {
std.mem.doNotOptimizeAway(&napi_add_async_cleanup_hook);
std.mem.doNotOptimizeAway(&napi_remove_async_cleanup_hook);
std.mem.doNotOptimizeAway(&napi_add_finalizer);
+ std.mem.doNotOptimizeAway(&napi_create_external);
+ std.mem.doNotOptimizeAway(&napi_get_value_external);
std.mem.doNotOptimizeAway(&@import("../bun.js/node/buffer.zig").BufferVectorized.fill);
}
@@ -1600,5 +1602,7 @@ comptime {
_ = napi_remove_async_cleanup_hook;
_ = @import("../bun.js/node/buffer.zig").BufferVectorized.fill;
_ = napi_add_finalizer;
+ _ = napi_create_external;
+ _ = napi_get_value_external;
}
}
diff --git a/src/options.zig b/src/options.zig
index 825b8cb37..24c0cdc2a 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -1402,6 +1402,10 @@ pub const BundleOptions = struct {
// If we're not doing SSR, we want all the import paths to be absolute
opts.import_path_format = if (opts.import_path_format == .absolute_url) .absolute_url else .absolute_path;
opts.env.behavior = .load_all;
+ if (transform.extension_order.len == 0) {
+ // we must also support require'ing .node files
+ opts.extension_order = Defaults.ExtensionOrder ++ &[_][]const u8{".node"};
+ }
},
else => {},
}
diff --git a/src/symbols.dyn b/src/symbols.dyn
index d81166623..9eeed901e 100644
--- a/src/symbols.dyn
+++ b/src/symbols.dyn
@@ -126,4 +126,6 @@
_napi_unwrap;
_napi_wrap;
_napi_remove_wrap;
+ _napi_create_external;
+ _napi_get_value_external;
}; \ No newline at end of file
diff --git a/src/symbols.txt b/src/symbols.txt
index 7367f1549..e88483515 100644
--- a/src/symbols.txt
+++ b/src/symbols.txt
@@ -125,3 +125,5 @@ _napi_unref_threadsafe_function
_napi_unwrap
_napi_wrap
_napi_remove_wrap
+_napi_create_external
+_napi_get_value_external
diff --git a/test/bun.js/napi-test.c b/test/bun.js/napi-test.c
new file mode 100644
index 000000000..dac40c83d
--- /dev/null
+++ b/test/bun.js/napi-test.c
@@ -0,0 +1,3 @@
+#include "../../src/napi/node_api.h"
+
+void \ No newline at end of file
diff --git a/test/bun.js/napi.test.ts b/test/bun.js/napi.test.ts
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/test/bun.js/napi.test.ts
diff --git a/test/bun.js/third-party/napi_create_external/bun.lockb b/test/bun.js/third-party/napi_create_external/bun.lockb
new file mode 100755
index 000000000..f091c506b
--- /dev/null
+++ b/test/bun.js/third-party/napi_create_external/bun.lockb
Binary files differ
diff --git a/test/bun.js/third-party/napi_create_external/napi-create-external.test.ts b/test/bun.js/third-party/napi_create_external/napi-create-external.test.ts
new file mode 100644
index 000000000..055b488f9
--- /dev/null
+++ b/test/bun.js/third-party/napi_create_external/napi-create-external.test.ts
@@ -0,0 +1,199 @@
+import { test, it, describe, expect } from "bun:test";
+import * as _ from "lodash";
+
+function rebase(str, inBase, outBase) {
+ const mapBase = (b) => (b === 2 ? 32 : b === 16 ? 8 : null);
+ const stride = mapBase(inBase);
+ const pad = mapBase(outBase);
+ if (!stride) throw new Error(`Bad inBase ${inBase}`);
+ if (!pad) throw new Error(`Bad outBase ${outBase}`);
+ if (str.length % stride) throw new Error(`Bad string length ${str.length}`);
+ const out = [];
+ for (let i = 0; i < str.length; i += stride)
+ out.push(
+ parseInt(str.slice(i, i + stride), inBase)
+ .toString(outBase)
+ .padStart(pad, "0"),
+ );
+ return out.join("");
+}
+
+function expectDeepEqual(a, b) {
+ expect(JSON.stringify(a)).toBe(JSON.stringify(b));
+}
+class HashMaker {
+ constructor(length) {
+ this.length = length;
+ this._dist = {};
+ }
+ length: number;
+ _dist: any;
+
+ binToHex(binHash) {
+ if (binHash.length !== this.length)
+ throw new Error(
+ `Hash length mismatch ${this.length} != ${binHash.length}`,
+ );
+ return rebase(binHash, 2, 16);
+ }
+
+ makeBits() {
+ const bits = [];
+ for (let i = 0; i < this.length; i++) bits.push(i);
+ return _.shuffle(bits);
+ }
+
+ makeRandom() {
+ const bits = [];
+ for (let i = 0; i < this.length; i++)
+ bits.push(Math.random() < 0.5 ? 1 : 0);
+ return bits;
+ }
+
+ get keySet() {
+ return (this._set = this._set || new Set(this.data));
+ }
+
+ randomKey() {
+ while (true) {
+ const hash = this.binToHex(this.makeRandom().join(""));
+ if (!this.keySet.has(hash)) return hash;
+ }
+ }
+
+ get data() {
+ return (this._data =
+ this._data ||
+ (() => {
+ const bits = this.makeBits();
+ const base = this.makeRandom();
+ const data = [];
+ for (let stride = 0; bits.length; stride++) {
+ const flip = bits.splice(0, stride);
+ for (const bit of flip) base[bit] = 1 - base[bit];
+ data.push(this.binToHex(base.join("")));
+ }
+ return data;
+ })());
+ }
+
+ get random() {
+ const d = this.data;
+ return d[Math.floor(Math.random() * d.length)];
+ }
+
+ distance(a, b) {
+ const bitCount = (n) => {
+ n = n - ((n >> 1) & 0x55555555);
+ n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
+ return (((n + (n >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
+ };
+
+ if (a === b) return 0;
+ if (a > b) return this.distance(b, a);
+ const hash = a + "-" + b;
+ return (this._dist[hash] =
+ this._dist[hash] ||
+ (() => {
+ let dist = 0;
+ for (let i = 0; i < a.length; i += 8) {
+ const va = parseInt(a.slice(i, i + 8), 16);
+ const vb = parseInt(b.slice(i, i + 8), 16);
+ dist += bitCount(va ^ vb);
+ }
+ return dist;
+ })());
+ }
+
+ query(baseKey, maxDist) {
+ const out = [];
+ for (const key of this.data) {
+ const distance = this.distance(key, baseKey);
+ if (distance <= maxDist) out.push({ key, distance });
+ }
+ return out.sort((a, b) => a.distance - b.distance);
+ }
+}
+
+const treeClass = require("bktree-fast/native");
+
+for (let keyLen = 64; keyLen <= 512; keyLen += 64) {
+ const hm = new HashMaker(keyLen);
+ describe(`Key length: ${keyLen}`, () => {
+ it("should compute distance", () => {
+ const tree = new treeClass(keyLen);
+ for (const a of hm.data)
+ for (const b of hm.data)
+ expect(tree.distance(a, b)).toBe(hm.distance(a, b));
+ });
+
+ it("should know which keys it has", () => {
+ const tree = new treeClass(keyLen).add(hm.data);
+ expectDeepEqual(
+ hm.data.map((hash) => tree.has(hash)),
+ hm.data.map(() => true),
+ );
+ // Not interested in the hash
+ for (const hash of hm.data) expect(tree.has(hm.randomKey())).toBe(false);
+ });
+
+ it("should know the tree size", () => {
+ const tree = new treeClass(keyLen, { foo: 1 });
+ expect(tree.size).toBe(0);
+ tree.add(hm.data);
+ expect(tree.size).toBe(hm.data.length);
+ tree.add(hm.data);
+ expect(tree.size).toBe(hm.data.length);
+ });
+
+ it("should walk the tree", () => {
+ const tree = new treeClass(keyLen).add(hm.data);
+ const got = [];
+ tree.walk((hash, depth) => got.push(hash));
+ expectDeepEqual(got.sort(), hm.data.slice(0).sort());
+ });
+
+ it("should query", () => {
+ Bun.gc(true);
+ ((treeClass, expectDeepEqual) => {
+ const tree = new treeClass(keyLen).add(hm.data);
+
+ for (let dist = 0; dist <= hm.length; dist++) {
+ for (const baseKey of [hm.random, hm.data[0]]) {
+ const baseKey = hm.random;
+ const got = [];
+ tree.query(baseKey, dist, (key, distance) =>
+ got.push({ key, distance }),
+ );
+ const want = hm.query(baseKey, dist);
+ expectDeepEqual(
+ got.sort((a, b) => a.distance - b.distance),
+ want,
+ );
+ expectDeepEqual(tree.find(baseKey, dist), want);
+ }
+ }
+ })(treeClass, expectDeepEqual);
+ Bun.gc(true);
+ });
+ });
+}
+
+describe("Misc functions", () => {
+ it("should pad keys", () => {
+ const tree = new treeClass(64);
+ expect(tree.padKey("1")).toBe("0000000000000001");
+ tree.add(["1", "2", "3"]);
+
+ const got = [];
+ tree.query("2", 3, (hash, distance) => got.push({ hash, distance }));
+ const res = got.sort((a, b) => a.distance - b.distance);
+ const want = [
+ { hash: "0000000000000002", distance: 0 },
+ { hash: "0000000000000003", distance: 1 },
+ { hash: "0000000000000001", distance: 2 },
+ ];
+
+ expectDeepEqual(res, want);
+ });
+});
diff --git a/test/bun.js/third-party/napi_create_external/package.json b/test/bun.js/third-party/napi_create_external/package.json
new file mode 100644
index 000000000..82b0ef58d
--- /dev/null
+++ b/test/bun.js/third-party/napi_create_external/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "napi-create-external-test",
+ "version": "1.0.0",
+ "description": "Test for napi_create_external",
+ "dependencies": {
+ "bktree-fast": "0.0.7",
+ "lodash": "^4.17.21"
+ },
+ "scripts": {
+ "postinstall": "cd node_modules/bktree-fast && node-gyp configure"
+ }
+}