diff --git a/CMakeLists.txt b/CMakeLists.txt index 171d9ae352..1020b770fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,9 @@ cmake_path(APPEND LOCAL_ZIG_CACHE_DIR "local") cmake_path(APPEND GLOBAL_ZIG_CACHE_DIR "global") # Used in process.version, process.versions.node, napi, and elsewhere -set(REPORTED_NODEJS_VERSION "22.3.0") +set(REPORTED_NODEJS_VERSION "22.6.0") +# Used in process.versions.modules and compared while loading V8 modules +set(REPORTED_NODEJS_ABI_VERSION "127") # WebKit uses -std=gnu++20 on non-macOS non-Windows # If we do not set this, it will crash at startup on the first memory allocation. @@ -664,6 +666,7 @@ file(GLOB BUN_CPP ${CONFIGURE_DEPENDS} "${BUN_SRC}/bun.js/bindings/sqlite/*.cpp" "${BUN_SRC}/bun.js/bindings/webcrypto/*.cpp" "${BUN_SRC}/bun.js/bindings/webcrypto/*/*.cpp" + "${BUN_SRC}/bun.js/bindings/v8/*.cpp" "${BUN_SRC}/deps/picohttpparser/picohttpparser.c" ) list(APPEND BUN_RAW_SOURCES ${BUN_CPP}) @@ -1040,6 +1043,7 @@ add_compile_definitions( "BUILDING_JSCONLY__" "BUN_DYNAMIC_JS_LOAD_PATH=\"${BUN_WORKDIR}/js\"" "REPORTED_NODEJS_VERSION=\"${REPORTED_NODEJS_VERSION}\"" + "REPORTED_NODEJS_ABI_VERSION=${REPORTED_NODEJS_ABI_VERSION}" ) if(NOT ASSERT_ENABLED) diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index d902c10f2d..6aa54597f4 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -196,8 +196,12 @@ static JSValue constructVersions(VM& vm, JSObject* processObject) object->putDirect(vm, JSC::Identifier::fromString(vm, "icu"_s), JSValue(JSC::jsString(vm, makeString(ASCIILiteral::fromLiteralUnsafe(U_ICU_VERSION)))), 0); object->putDirect(vm, JSC::Identifier::fromString(vm, "unicode"_s), JSValue(JSC::jsString(vm, makeString(ASCIILiteral::fromLiteralUnsafe(U_UNICODE_VERSION)))), 0); +#define STRINGIFY_IMPL(x) #x +#define STRINGIFY(x) STRINGIFY_IMPL(x) object->putDirect(vm, JSC::Identifier::fromString(vm, "modules"_s), - JSC::JSValue(JSC::jsString(vm, makeString("115"_s)))); + JSC::JSValue(JSC::jsString(vm, makeString(ASCIILiteral::fromLiteralUnsafe(STRINGIFY(REPORTED_NODEJS_ABI_VERSION)))))); +#undef STRINGIFY +#undef STRINGIFY_IMPL return object; } @@ -288,7 +292,9 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, return JSC::JSValue::encode(JSC::JSValue {}); } - globalObject->pendingNapiModule = exports; + globalObject->m_pendingNapiModuleAndExports[0].set(vm, globalObject, moduleObject); + globalObject->m_pendingNapiModuleAndExports[1].set(vm, globalObject, exports); + Strong strongExports; if (exports.isCell()) { @@ -352,9 +358,10 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, } if (callCountAtStart != globalObject->napiModuleRegisterCallCount) { - JSValue resultValue = globalObject->pendingNapiModule; - globalObject->pendingNapiModule = JSValue {}; + JSValue resultValue = globalObject->m_pendingNapiModuleAndExports[0].get(); globalObject->napiModuleRegisterCallCount = 0; + globalObject->m_pendingNapiModuleAndExports[0].clear(); + globalObject->m_pendingNapiModuleAndExports[1].clear(); RETURN_IF_EXCEPTION(scope, {}); @@ -374,6 +381,8 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, #define dlsym GetProcAddress #endif + // TODO(@190n) look for node_register_module_vXYZ according to BuildOptions.reported_nodejs_version + // (bun/src/env.zig:36) and the table at https://github.com/nodejs/node/blob/main/doc/abi_version_registry.json napi_register_module_v1 = reinterpret_cast( dlsym(handle, "napi_register_module_v1")); @@ -397,6 +406,9 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, RETURN_IF_EXCEPTION(scope, {}); + globalObject->m_pendingNapiModuleAndExports[0].clear(); + globalObject->m_pendingNapiModuleAndExports[1].clear(); + // https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/src/node_api.cc#L734-L742 // https://github.com/oven-sh/bun/issues/1288 if (!resultValue.isEmpty() && !scope.exception() && (!strongExports || resultValue != strongExports.get())) { @@ -1788,6 +1800,7 @@ static JSValue constructProcessConfigObject(VM& vm, JSObject* processObject) JSC::JSObject* variables = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 1); variables->putDirect(vm, JSC::Identifier::fromString(vm, "v8_enable_i8n_support"_s), JSC::jsNumber(1), 0); + variables->putDirect(vm, JSC::Identifier::fromString(vm, "enable_lto"_s), JSC::jsBoolean(false), 0); config->putDirect(vm, JSC::Identifier::fromString(vm, "target_defaults"_s), JSC::constructEmptyObject(globalObject), 0); config->putDirect(vm, JSC::Identifier::fromString(vm, "variables"_s), variables, 0); diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index ff684340fc..6ee84c7dfe 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -148,6 +148,7 @@ #include "Base64Helpers.h" #include "wtf/text/OrdinalNumber.h" #include "ErrorCode.h" +#include "v8/V8GlobalInternals.h" #if ENABLE(REMOTE_INSPECTOR) #include "JavaScriptCore/RemoteInspectorServer.h" @@ -2685,6 +2686,15 @@ void GlobalObject::finishCreation(VM& vm) init.set(WebCore::createJSSQLStatementStructure(init.owner)); }); + m_V8GlobalInternals.initLater( + [](const JSC::LazyProperty::Initializer& init) { + init.set( + v8::GlobalInternals::create( + init.vm, + v8::GlobalInternals::createStructure(init.vm, init.owner), + jsDynamicCast(init.owner))); + }); + m_memoryFootprintStructure.initLater( [](const JSC::LazyProperty::Initializer& init) { init.set( @@ -3530,6 +3540,9 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.append(thisObject->m_nextTickQueue); visitor.append(thisObject->m_errorConstructorPrepareStackTraceValue); + visitor.append(thisObject->m_pendingNapiModuleAndExports[0]); + visitor.append(thisObject->m_pendingNapiModuleAndExports[1]); + thisObject->m_asyncBoundFunctionStructure.visit(visitor); thisObject->m_bunObject.visit(visitor); thisObject->m_cachedNodeVMGlobalObjectStructure.visit(visitor); @@ -3557,6 +3570,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_JSHTTPSResponseSinkClassStructure.visit(visitor); thisObject->m_JSSocketAddressStructure.visit(visitor); thisObject->m_JSSQLStatementStructure.visit(visitor); + thisObject->m_V8GlobalInternals.visit(visitor); thisObject->m_JSStringDecoderClassStructure.visit(visitor); thisObject->m_lazyPreloadTestModuleObject.visit(visitor); thisObject->m_lazyReadableStreamPrototypeMap.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 440a071d7e..96eaddc954 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -31,6 +31,10 @@ namespace Bun { class InternalModuleRegistry; } // namespace Bun +namespace v8 { +class GlobalInternals; +} // namespace v8 + #include "root.h" #include "headers-handwritten.h" #include @@ -282,6 +286,8 @@ public: Structure* JSSQLStatementStructure() const { return m_JSSQLStatementStructure.getInitializedOnMainThread(this); } + v8::GlobalInternals* V8GlobalInternals() const { return m_V8GlobalInternals.getInitializedOnMainThread(this); } + bool hasProcessObject() const { return m_processObject.isInitialized(); } RefPtr performance(); @@ -396,6 +402,9 @@ public: // Error.prepareStackTrace mutable WriteBarrier m_errorConstructorPrepareStackTraceValue; + // When a napi module initializes on dlopen, we need to know what the value is + mutable JSC::WriteBarrier m_pendingNapiModuleAndExports[2]; + // The original, unmodified Error.prepareStackTrace. // // We set a default value for this to mimick Node.js behavior It is a @@ -447,9 +456,6 @@ public: JSC::Structure* pendingVirtualModuleResultStructure() { return m_pendingVirtualModuleResultStructure.get(this); } - // When a napi module initializes on dlopen, we need to know what the value is - // This value is not observed by GC. It should be extremely ephemeral. - JSValue pendingNapiModule = JSValue {}; // We need to know if the napi module registered itself or we registered it. // To do that, we count the number of times we register a module. int napiModuleRegisterCallCount = 0; @@ -559,6 +565,7 @@ public: LazyProperty m_NapiPrototypeStructure; LazyProperty m_NAPIFunctionStructure; LazyProperty m_JSSQLStatementStructure; + LazyProperty m_V8GlobalInternals; LazyProperty m_bunObject; LazyProperty m_cryptoObject; diff --git a/src/bun.js/bindings/napi.cpp b/src/bun.js/bindings/napi.cpp index 5e5b522283..2d122605e6 100644 --- a/src/bun.js/bindings/napi.cpp +++ b/src/bun.js/bindings/napi.cpp @@ -7,6 +7,7 @@ #include "JavaScriptCore/JSGlobalObject.h" #include "JavaScriptCore/SourceCode.h" #include "js_native_api_types.h" +#include "v8/V8HandleScope.h" #include "helpers.h" #include @@ -341,6 +342,7 @@ public: NAPICallFrame frame(JSC::ArgList(args), function->m_dataPtr); auto scope = DECLARE_THROW_SCOPE(vm); + v8::HandleScope handleScope(v8::Isolate::fromGlobalObject(static_cast(globalObject))); auto result = callback(env, NAPICallFrame::toNapiCallbackInfo(frame)); @@ -879,7 +881,7 @@ extern "C" void napi_module_register(napi_module* mod) JSC::VM& vm = globalObject->vm(); auto keyStr = WTF::String::fromUTF8(mod->nm_modname); globalObject->napiModuleRegisterCallCount++; - JSValue pendingNapiModule = globalObject->pendingNapiModule; + JSValue pendingNapiModule = globalObject->m_pendingNapiModuleAndExports[0].get(); JSObject* object = (pendingNapiModule && pendingNapiModule.isObject()) ? pendingNapiModule.getObject() : nullptr; @@ -893,7 +895,6 @@ extern "C" void napi_module_register(napi_module* mod) object = Bun::JSCommonJSModule::create(globalObject, keyStr, exportsObject, false, jsUndefined()); strongExportsObject = { vm, exportsObject }; } else { - globalObject->pendingNapiModule = JSC::JSValue(); JSValue exportsObject = object->getIfPropertyExists(globalObject, WebCore::builtinNames(vm).exportsPublicName()); RETURN_IF_EXCEPTION(scope, void()); @@ -910,17 +911,13 @@ extern "C" void napi_module_register(napi_module* mod) if (resultValue.isEmpty()) { JSValue errorInstance = createError(globalObject, makeString("Node-API module \""_s, keyStr, "\" returned an error"_s)); - globalObject->pendingNapiModule = errorInstance; - vm.writeBarrier(globalObject, errorInstance); - EnsureStillAliveScope ensureAlive(globalObject->pendingNapiModule); + globalObject->m_pendingNapiModuleAndExports[0].set(vm, globalObject, errorInstance); return; } if (!resultValue.isObject()) { JSValue errorInstance = createError(globalObject, makeString("Expected Node-API module \""_s, keyStr, "\" to return an exports object"_s)); - globalObject->pendingNapiModule = errorInstance; - vm.writeBarrier(globalObject, errorInstance); - EnsureStillAliveScope ensureAlive(globalObject->pendingNapiModule); + globalObject->m_pendingNapiModuleAndExports[0].set(vm, globalObject, errorInstance); return; } @@ -931,7 +928,7 @@ extern "C" void napi_module_register(napi_module* mod) strongObject->put(strongObject.get(), globalObject, WebCore::builtinNames(vm).exportsPublicName(), resultValue, slot); } - globalObject->pendingNapiModule = object; + globalObject->m_pendingNapiModuleAndExports[1].set(vm, globalObject, object); } extern "C" napi_status napi_wrap(napi_env env, diff --git a/src/bun.js/bindings/v8.cpp b/src/bun.js/bindings/v8.cpp deleted file mode 100644 index 719e8517dc..0000000000 --- a/src/bun.js/bindings/v8.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// This file implements the v8 and node C++ APIs -// -// If you have issues linking this file, you probably have to update -// the code in `napi.zig` at `const V8API` -#include "root.h" -#include "ZigGlobalObject.h" - -#if defined(WIN32) || defined(_WIN32) -#define BUN_EXPORT __declspec(dllexport) -#else -#define BUN_EXPORT JS_EXPORT -#endif - -extern "C" Zig::GlobalObject* Bun__getDefaultGlobalObject(); - -namespace v8 { - -using Context = JSC::JSGlobalObject; - -template -class Local final { -public: - T* ptr; -}; - -// This currently is just a pointer to a Zig::GlobalObject* -// We do that so that we can recover the context and the VM from the "Isolate" -class Isolate final { -public: - Isolate() = default; - - // Returns the isolate inside which the current thread is running or nullptr. - BUN_EXPORT static Isolate* TryGetCurrent(); - - // Returns the isolate inside which the current thread is running. - BUN_EXPORT static Isolate* GetCurrent(); - - BUN_EXPORT Local GetCurrentContext(); - - Zig::GlobalObject* globalObject() { return reinterpret_cast(this); } - JSC::VM& vm() { return globalObject()->vm(); } -}; - -// Returns the isolate inside which the current thread is running or nullptr. -Isolate* Isolate::TryGetCurrent() -{ - auto* global = Bun__getDefaultGlobalObject(); - - return global ? reinterpret_cast(global) : nullptr; -} - -// Returns the isolate inside which the current thread is running. -Isolate* Isolate::GetCurrent() -{ - auto* global = Bun__getDefaultGlobalObject(); - - return global ? reinterpret_cast(global) : nullptr; -} - -Local Isolate::GetCurrentContext() -{ - return Local { reinterpret_cast(this) }; -} - -} - -namespace node { - -BUN_EXPORT void AddEnvironmentCleanupHook(v8::Isolate* isolate, - void (*fun)(void* arg), - void* arg) -{ - // TODO -} - -BUN_EXPORT void RemoveEnvironmentCleanupHook(v8::Isolate* isolate, - void (*fun)(void* arg), - void* arg) -{ - // TODO -} - -} diff --git a/src/bun.js/bindings/v8/V8Array.cpp b/src/bun.js/bindings/v8/V8Array.cpp new file mode 100644 index 0000000000..fce7592e14 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Array.cpp @@ -0,0 +1,23 @@ +#include "V8Array.h" + +#include "V8HandleScope.h" + +using JSC::ArrayAllocationProfile; +using JSC::JSArray; +using JSC::JSValue; + +namespace v8 { + +Local Array::New(Isolate* isolate, Local* elements, size_t length) +{ + V8_UNIMPLEMENTED(); + // TODO fix for v8 layout + Zig::GlobalObject* globalObject = isolate->globalObject(); + JSArray* array = JSC::constructArray(globalObject, + static_cast(nullptr), + reinterpret_cast(elements), + (unsigned int)length); + return isolate->currentHandleScope()->createLocal(array); +} + +} diff --git a/src/bun.js/bindings/v8/V8Array.h b/src/bun.js/bindings/v8/V8Array.h new file mode 100644 index 0000000000..d88418894e --- /dev/null +++ b/src/bun.js/bindings/v8/V8Array.h @@ -0,0 +1,16 @@ +#pragma once + +#include "v8.h" +#include "V8Object.h" +#include "V8Local.h" +#include "V8Isolate.h" +#include "V8Value.h" + +namespace v8 { + +class Array : public Object { +public: + BUN_EXPORT static Local New(Isolate* isolate, Local* elements, size_t length); +}; + +} diff --git a/src/bun.js/bindings/v8/V8Boolean.cpp b/src/bun.js/bindings/v8/V8Boolean.cpp new file mode 100644 index 0000000000..f746584c4d --- /dev/null +++ b/src/bun.js/bindings/v8/V8Boolean.cpp @@ -0,0 +1,16 @@ +#include "V8Boolean.h" +#include "V8HandleScope.h" + +namespace v8 { + +bool Boolean::Value() const +{ + return localToJSValue(Isolate::GetCurrent()->globalInternals()).asBoolean(); +} + +Local Boolean::New(Isolate* isolate, bool value) +{ + return isolate->currentHandleScope()->createLocal(JSC::jsBoolean(value)); +} + +} diff --git a/src/bun.js/bindings/v8/V8Boolean.h b/src/bun.js/bindings/v8/V8Boolean.h new file mode 100644 index 0000000000..9dd76f124a --- /dev/null +++ b/src/bun.js/bindings/v8/V8Boolean.h @@ -0,0 +1,15 @@ +#pragma once + +#include "V8Primitive.h" +#include "V8Isolate.h" + +namespace v8 { + +class Boolean : public Primitive { +public: + BUN_EXPORT bool Value() const; + // usually inlined + BUN_EXPORT static Local New(Isolate* isolate, bool value); +}; + +} diff --git a/src/bun.js/bindings/v8/V8Context.h b/src/bun.js/bindings/v8/V8Context.h new file mode 100644 index 0000000000..a842b9dc8b --- /dev/null +++ b/src/bun.js/bindings/v8/V8Context.h @@ -0,0 +1,34 @@ +#pragma once + +#include "ZigGlobalObject.h" +#include "V8GlobalInternals.h" +#include "V8Data.h" + +namespace v8 { + +// Context is always a reinterpret pointer to V8::Roots, so that inlined V8 functions can find +// values they expect to find at fixed offsets +class Context : public Data { +public: + JSC::VM& vm() const + { + return globalObject()->vm(); + } + + const Zig::GlobalObject* globalObject() const + { + return reinterpret_cast(localToCell())->parent->globalObject; + } + + Zig::GlobalObject* globalObject() + { + return reinterpret_cast(localToCell())->parent->globalObject; + } + + HandleScope* currentHandleScope() const + { + return globalObject()->V8GlobalInternals()->currentHandleScope(); + }; +}; + +} diff --git a/src/bun.js/bindings/v8/V8Data.h b/src/bun.js/bindings/v8/V8Data.h new file mode 100644 index 0000000000..78b9aaf994 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Data.h @@ -0,0 +1,102 @@ +#pragma once + +#include "root.h" +#include "V8TaggedPointer.h" +#include "V8Handle.h" +#include "V8GlobalInternals.h" + +namespace v8 { + +class Data { +public: + // Functions beginning with "localTo" must only be used when "this" comes from a v8::Local (i.e. + // in public V8 functions), as they make assumptions about how V8 lays out local handles. They + // will segfault or worse otherwise. + + // Recover an opaque pointer out of a v8::Local which is not a number + void* localToPointer() + { + TaggedPointer tagged = localToTagged(); + RELEASE_ASSERT(tagged.type() != TaggedPointer::Type::Smi); + return tagged.getPtr(); + } + + // Recover a JSCell pointer out of a v8::Local + JSC::JSCell* localToCell() + { + return reinterpret_cast(localToPointer()); + } + + // Recover a pointer to a JSCell subclass out of a v8::Local + template + T* localToObjectPointer() + { + static_assert(std::is_base_of::value, "localToObjectPointer can only be used when T is a JSCell subclass"); + return JSC::jsDynamicCast(localToCell()); + } + + // Get this as a JSValue when this is a v8::Local + JSC::JSValue localToJSValue(GlobalInternals* globalInternals) const + { + TaggedPointer root = *reinterpret_cast(this); + if (root.type() == TaggedPointer::Type::Smi) { + return JSC::jsNumber(root.getSmiUnchecked()); + } else { + void* raw_ptr = root.getPtr(); + // check if this pointer is identical to the fixed locations where these primitive + // values are stored + if (raw_ptr == globalInternals->undefinedSlot()->getPtr()) { + return JSC::jsUndefined(); + } else if (raw_ptr == globalInternals->nullSlot()->getPtr()) { + return JSC::jsNull(); + } else if (raw_ptr == globalInternals->trueSlot()->getPtr()) { + return JSC::jsBoolean(true); + } else if (raw_ptr == globalInternals->falseSlot()->getPtr()) { + return JSC::jsBoolean(false); + } + + JSC::JSCell** v8_object = reinterpret_cast(raw_ptr); + return JSC::JSValue(v8_object[1]); + } + } + + // Recover an opaque pointer out of a v8::Local which is not a number + const void* localToPointer() const + { + TaggedPointer tagged = localToTagged(); + RELEASE_ASSERT(tagged.type() != TaggedPointer::Type::Smi); + return tagged.getPtr(); + } + + // Recover a JSCell pointer out of a v8::Local + const JSC::JSCell* localToCell() const + { + return reinterpret_cast(localToPointer()); + } + + // Recover a pointer to a JSCell subclass out of a v8::Local + template + const T* localToObjectPointer() const + { + static_assert(std::is_base_of::value, "localToObjectPointer can only be used when T is a JSCell subclass"); + return JSC::jsDynamicCast(localToCell()); + } + +private: + // Convert the local handle into either a smi or a pointer to some non-V8 type. + TaggedPointer localToTagged() const + { + TaggedPointer root = *reinterpret_cast(this); + if (root.type() == TaggedPointer::Type::Smi) { + return root; + } else { + // root points to the V8 object. The first field of the V8 object is the map, and the + // second is a pointer to some object we have stored. So we ignore the map and recover + // the object pointer. + JSC::JSCell** v8_object = reinterpret_cast(root.getPtr()); + return TaggedPointer(v8_object[1]); + } + } +}; + +} diff --git a/src/bun.js/bindings/v8/V8EscapableHandleScope.cpp b/src/bun.js/bindings/v8/V8EscapableHandleScope.cpp new file mode 100644 index 0000000000..c5e7bcb913 --- /dev/null +++ b/src/bun.js/bindings/v8/V8EscapableHandleScope.cpp @@ -0,0 +1,15 @@ +#include "V8EscapableHandleScope.h" + +namespace v8 { + +EscapableHandleScope::EscapableHandleScope(Isolate* isolate) + : EscapableHandleScopeBase(isolate) +{ +} + +EscapableHandleScope::~EscapableHandleScope() +{ + EscapableHandleScopeBase::~EscapableHandleScopeBase(); +} + +} diff --git a/src/bun.js/bindings/v8/V8EscapableHandleScope.h b/src/bun.js/bindings/v8/V8EscapableHandleScope.h new file mode 100644 index 0000000000..ad5f39ebd7 --- /dev/null +++ b/src/bun.js/bindings/v8/V8EscapableHandleScope.h @@ -0,0 +1,13 @@ +#pragma once + +#include "V8EscapableHandleScopeBase.h" + +namespace v8 { + +class EscapableHandleScope : public EscapableHandleScopeBase { +public: + BUN_EXPORT EscapableHandleScope(Isolate* isolate); + BUN_EXPORT ~EscapableHandleScope(); +}; + +} diff --git a/src/bun.js/bindings/v8/V8EscapableHandleScopeBase.cpp b/src/bun.js/bindings/v8/V8EscapableHandleScopeBase.cpp new file mode 100644 index 0000000000..7563b6c684 --- /dev/null +++ b/src/bun.js/bindings/v8/V8EscapableHandleScopeBase.cpp @@ -0,0 +1,24 @@ +#include "V8EscapableHandleScopeBase.h" + +namespace v8 { + +EscapableHandleScopeBase::EscapableHandleScopeBase(Isolate* isolate) + : HandleScope(isolate) +{ + // at this point isolate->currentHandleScope() would just be this, so instead we have to get the + // previous one + auto& handle = prev->buffer->createUninitializedHandle(); + memset(&handle, 0xaa, sizeof(handle)); + handle.to_v8_object = TaggedPointer(0); + escape_slot = &handle; +} + +// Store the handle escape_value in the escape slot that we have allocated from the parent +// HandleScope, and return the escape slot +uintptr_t* EscapableHandleScopeBase::EscapeSlot(uintptr_t* escape_value) +{ + *escape_slot = *reinterpret_cast(escape_value); + return reinterpret_cast(escape_slot); +} + +} diff --git a/src/bun.js/bindings/v8/V8EscapableHandleScopeBase.h b/src/bun.js/bindings/v8/V8EscapableHandleScopeBase.h new file mode 100644 index 0000000000..1b82748dc7 --- /dev/null +++ b/src/bun.js/bindings/v8/V8EscapableHandleScopeBase.h @@ -0,0 +1,20 @@ +#pragma once + +#include "v8.h" +#include "V8HandleScope.h" +#include "V8Isolate.h" + +namespace v8 { + +class EscapableHandleScopeBase : public HandleScope { +public: + BUN_EXPORT EscapableHandleScopeBase(Isolate* isolate); + +protected: + BUN_EXPORT uintptr_t* EscapeSlot(uintptr_t* escape_value); + +private: + Handle* escape_slot; +}; + +} diff --git a/src/bun.js/bindings/v8/V8External.cpp b/src/bun.js/bindings/v8/V8External.cpp new file mode 100644 index 0000000000..b46a0119f7 --- /dev/null +++ b/src/bun.js/bindings/v8/V8External.cpp @@ -0,0 +1,26 @@ +#include "V8External.h" +#include "V8HandleScope.h" + +#include "napi_external.h" + +namespace v8 { + +Local External::New(Isolate* isolate, void* value) +{ + auto globalObject = isolate->globalObject(); + auto& vm = globalObject->vm(); + auto structure = globalObject->NapiExternalStructure(); + Bun::NapiExternal* val = Bun::NapiExternal::create(vm, structure, value, nullptr, nullptr); + return isolate->currentHandleScope()->createLocal(val); +} + +void* External::Value() const +{ + auto* external = localToObjectPointer(); + if (!external) { + return nullptr; + } + return external->value(); +} + +} diff --git a/src/bun.js/bindings/v8/V8External.h b/src/bun.js/bindings/v8/V8External.h new file mode 100644 index 0000000000..8c01c3db66 --- /dev/null +++ b/src/bun.js/bindings/v8/V8External.h @@ -0,0 +1,16 @@ +#pragma once + +#include "v8.h" +#include "V8Value.h" +#include "V8MaybeLocal.h" +#include "V8Isolate.h" + +namespace v8 { + +class External : public Value { +public: + BUN_EXPORT static Local New(Isolate* isolate, void* value); + BUN_EXPORT void* Value() const; +}; + +} diff --git a/src/bun.js/bindings/v8/V8Function.cpp b/src/bun.js/bindings/v8/V8Function.cpp new file mode 100644 index 0000000000..3ff1dc4ae1 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Function.cpp @@ -0,0 +1,64 @@ +#include "V8Function.h" + +#include "V8FunctionTemplate.h" + +#include "JavaScriptCore/FunctionPrototype.h" + +using JSC::Structure; +using JSC::VM; + +namespace v8 { + +// for CREATE_METHOD_TABLE +namespace JSCastingHelpers = JSC::JSCastingHelpers; + +const JSC::ClassInfo Function::s_info = { + "Function"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(Function) +}; + +Structure* Function::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + return Structure::create( + vm, + globalObject, + globalObject->functionPrototype(), + JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), + info()); +} + +template +void Function::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + Function* fn = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(fn, info()); + Base::visitChildren(fn, visitor); + + visitor.append(fn->__internals.functionTemplate); +} + +DEFINE_VISIT_CHILDREN(Function); + +Function* Function::create(VM& vm, Structure* structure, FunctionTemplate* functionTemplate) +{ + auto* function = new (NotNull, JSC::allocateCell(vm)) Function(vm, structure); + function->finishCreation(vm, functionTemplate); + return function; +} + +void Function::finishCreation(VM& vm, FunctionTemplate* functionTemplate) +{ + Base::finishCreation(vm, 0, "Function"_s); + __internals.functionTemplate.set(vm, this, functionTemplate); +} + +void Function::SetName(Local name) +{ + auto* thisObj = localToObjectPointer(); + thisObj->m_originalName.set(Isolate::GetCurrent()->vm(), thisObj, name->localToJSString()); +} + +} diff --git a/src/bun.js/bindings/v8/V8Function.h b/src/bun.js/bindings/v8/V8Function.h new file mode 100644 index 0000000000..9b2d1f552e --- /dev/null +++ b/src/bun.js/bindings/v8/V8Function.h @@ -0,0 +1,76 @@ +#pragma once + +#include "V8Object.h" +#include "V8FunctionTemplate.h" +#include "V8Local.h" +#include "V8String.h" + +namespace v8 { + +// If this inherited Object like it does in V8, the layout would be wrong for JSC HeapCell. +// Inheritance shouldn't matter for the ABI. +class Function : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static Function* create(JSC::VM& vm, JSC::Structure* structure, FunctionTemplate* functionTemplate); + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject); + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForV8Function.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForV8Function = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForV8Function.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForV8Function = std::forward(space); }); + } + + BUN_EXPORT void SetName(Local name); + + FunctionTemplate* functionTemplate() const + { + return __internals.functionTemplate.get(); + } + +private: + class Internals { + private: + JSC::WriteBarrier functionTemplate; + friend class Function; + friend class FunctionTemplate; + }; + + Internals __internals; + + Function(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, FunctionTemplate::functionCall) + { + } + + Function* localToObjectPointer() + { + return reinterpret_cast(this)->localToObjectPointer(); + } + + const Function* localToObjectPointer() const + { + return reinterpret_cast(this)->localToObjectPointer(); + } + + Internals& internals() + { + return localToObjectPointer()->__internals; + } + + void finishCreation(JSC::VM& vm, FunctionTemplate* functionTemplate); +}; + +} diff --git a/src/bun.js/bindings/v8/V8FunctionTemplate.cpp b/src/bun.js/bindings/v8/V8FunctionTemplate.cpp new file mode 100644 index 0000000000..1cc65d1707 --- /dev/null +++ b/src/bun.js/bindings/v8/V8FunctionTemplate.cpp @@ -0,0 +1,136 @@ +#include "V8FunctionTemplate.h" +#include "V8Function.h" +#include "V8HandleScope.h" + +#include "JavaScriptCore/FunctionPrototype.h" + +using JSC::JSCell; +using JSC::JSValue; +using JSC::Structure; + +namespace v8 { + +// for CREATE_METHOD_TABLE +namespace JSCastingHelpers = JSC::JSCastingHelpers; + +const JSC::ClassInfo FunctionTemplate::s_info = { + "FunctionTemplate"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(FunctionTemplate) +}; + +Local FunctionTemplate::New( + Isolate* isolate, + FunctionCallback callback, + Local data, + Local signature, + int length, + ConstructorBehavior behavior, + SideEffectType side_effect_type, + const CFunction* c_function, + uint16_t instance_type, + uint16_t allowed_receiver_instance_type_range_start, + uint16_t allowed_receiver_instance_type_range_end) +{ + // only handling simpler cases for now + // (pass most of these into v8::Function / JSC::InternalFunction) + RELEASE_ASSERT(signature.IsEmpty()); + RELEASE_ASSERT(length == 0); + RELEASE_ASSERT(behavior == ConstructorBehavior::kAllow); + RELEASE_ASSERT(side_effect_type == SideEffectType::kHasSideEffect); + RELEASE_ASSERT(c_function == nullptr); + RELEASE_ASSERT(instance_type == 0); + RELEASE_ASSERT(allowed_receiver_instance_type_range_start == 0); + RELEASE_ASSERT(allowed_receiver_instance_type_range_end == 0); + + auto globalObject = isolate->globalObject(); + auto& vm = globalObject->vm(); + JSValue jsc_data = data.IsEmpty() ? JSC::jsUndefined() : data->localToJSValue(globalObject->V8GlobalInternals()); + + Structure* structure = globalObject->V8GlobalInternals()->functionTemplateStructure(globalObject); + auto* functionTemplate = new (NotNull, JSC::allocateCell(vm)) FunctionTemplate( + vm, structure, callback, jsc_data); + functionTemplate->finishCreation(vm); + + return isolate->currentHandleScope()->createLocal(functionTemplate); +} + +MaybeLocal FunctionTemplate::GetFunction(Local context) +{ + auto& vm = context->vm(); + auto* globalObject = context->globalObject(); + auto* f = Function::create(vm, globalObject->V8GlobalInternals()->v8FunctionStructure(globalObject), localToObjectPointer()); + + return context->currentHandleScope()->createLocal(f); +} + +Structure* FunctionTemplate::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + return Structure::create( + vm, + globalObject, + globalObject->functionPrototype(), + JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), + info()); +} + +template +void FunctionTemplate::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + FunctionTemplate* fn = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(fn, info()); + Base::visitChildren(fn, visitor); + + if (fn->__internals.data.isCell()) { + JSC::JSCell::visitChildren(fn->__internals.data.asCell(), visitor); + } +} + +DEFINE_VISIT_CHILDREN(FunctionTemplate); + +JSC::EncodedJSValue FunctionTemplate::functionCall(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) +{ + auto* callee = JSC::jsDynamicCast(callFrame->jsCallee()); + auto* functionTemplate = callee->functionTemplate(); + auto* isolate = Isolate::fromGlobalObject(JSC::jsDynamicCast(globalObject)); + + WTF::Vector args(callFrame->argumentCount() + 1); + + HandleScope hs(isolate); + Local thisValue = hs.createLocal(callFrame->thisValue()); + args[0] = thisValue.tagged(); + + for (size_t i = 0; i < callFrame->argumentCount(); i++) { + Local argValue = hs.createLocal(callFrame->argument(i)); + args[i + 1] = argValue.tagged(); + } + + Local data = hs.createLocal(functionTemplate->__internals.data); + + ImplicitArgs implicit_args = { + .holder = nullptr, + .isolate = isolate, + .context = reinterpret_cast(isolate), + .return_value = TaggedPointer(), + // data may be an object + // put it in the handle scope so that it has a map ptr + .target = data.tagged(), + .new_target = nullptr, + }; + + FunctionCallbackInfo info(&implicit_args, args.data() + 1, callFrame->argumentCount()); + + functionTemplate->__internals.callback(info); + + if (implicit_args.return_value.type() != TaggedPointer::Type::Smi && implicit_args.return_value.getPtr() == nullptr) { + // callback forgot to set a return value, so return undefined + return JSValue::encode(JSC::jsUndefined()); + } else { + Local local_ret(&implicit_args.return_value); + return JSValue::encode(local_ret->localToJSValue(isolate->globalInternals())); + } +} + +} diff --git a/src/bun.js/bindings/v8/V8FunctionTemplate.h b/src/bun.js/bindings/v8/V8FunctionTemplate.h new file mode 100644 index 0000000000..09dcfcea1d --- /dev/null +++ b/src/bun.js/bindings/v8/V8FunctionTemplate.h @@ -0,0 +1,155 @@ +#pragma once + +#include "v8.h" +#include "V8Context.h" +#include "V8Isolate.h" +#include "V8Local.h" +#include "V8MaybeLocal.h" +#include "V8Value.h" +#include "V8Signature.h" + +namespace v8 { + +class Function; + +struct ImplicitArgs { + // v8-function-callback.h:168 + void* holder; + Isolate* isolate; + Context* context; + // overwritten by the callback + TaggedPointer return_value; + // holds the value passed for data in FunctionTemplate::New + TaggedPointer target; + void* new_target; +}; + +// T = return value +template +class FunctionCallbackInfo { + // V8 treats this as an array of pointers + ImplicitArgs* implicit_args; + // index -1 is this + TaggedPointer* values; + int length; + +public: + FunctionCallbackInfo(ImplicitArgs* implicit_args_, TaggedPointer* values_, int length_) + : implicit_args(implicit_args_) + , values(values_) + , length(length_) + { + } +}; + +using FunctionCallback = void (*)(const FunctionCallbackInfo&); + +enum class ConstructorBehavior { + kThrow, + kAllow, +}; + +enum class SideEffectType { + kHasSideEffect, + kHasNoSideEffect, + kHasSideEffectToReceiver, +}; + +class CFunction { +private: + const void* address; + const void* type_info; +}; + +// If this inherited Template like it does in V8, the layout would be wrong for JSC HeapCell. +// Inheritance shouldn't matter for the ABI. +class FunctionTemplate : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + BUN_EXPORT static Local New( + Isolate* isolate, + FunctionCallback callback = nullptr, + Local data = Local(), + Local signature = Local(), + int length = 0, + ConstructorBehavior behavior = ConstructorBehavior::kAllow, + SideEffectType side_effect_type = SideEffectType::kHasSideEffect, + const CFunction* c_function = nullptr, + uint16_t instance_type = 0, + uint16_t allowed_receiver_instance_type_range_start = 0, + uint16_t allowed_receiver_instance_type_range_end = 0); + + BUN_EXPORT MaybeLocal GetFunction(Local context); + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject); + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForFunctionTemplate.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForFunctionTemplate = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForFunctionTemplate.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForFunctionTemplate = std::forward(space); }); + } + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + friend class Function; + + FunctionCallback callback() const + { + return __internals.callback; + } + +private: + class Internals { + private: + FunctionCallback callback; + JSC::JSValue data; + + Internals(FunctionCallback callback_, JSC::JSValue data_) + : callback(callback_) + , data(data_) + { + } + + friend class FunctionTemplate; + }; + + // only use from functions called directly on FunctionTemplate + Internals __internals; + + FunctionTemplate* localToObjectPointer() + { + return reinterpret_cast(this)->localToObjectPointer(); + } + + const FunctionTemplate* localToObjectPointer() const + { + return reinterpret_cast(this)->localToObjectPointer(); + } + + // only use from functions called on Local + Internals& internals() + { + return localToObjectPointer()->__internals; + } + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES functionCall(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame); + + FunctionTemplate(JSC::VM& vm, JSC::Structure* structure, FunctionCallback callback, JSC::JSValue data) + : __internals(callback, data) + , Base(vm, structure, functionCall, JSC::callHostFunctionAsConstructor) + { + } + + // some kind of static trampoline +}; + +} diff --git a/src/bun.js/bindings/v8/V8GlobalInternals.cpp b/src/bun.js/bindings/v8/V8GlobalInternals.cpp new file mode 100644 index 0000000000..a13bad38e4 --- /dev/null +++ b/src/bun.js/bindings/v8/V8GlobalInternals.cpp @@ -0,0 +1,64 @@ +#include "V8GlobalInternals.h" + +#include "V8ObjectTemplate.h" +#include "V8InternalFieldObject.h" +#include "V8HandleScopeBuffer.h" +#include "V8FunctionTemplate.h" +#include "V8Function.h" + +#include "JavaScriptCore/FunctionPrototype.h" +#include "JavaScriptCore/LazyClassStructureInlines.h" +#include "JavaScriptCore/VMTrapsInlines.h" + +using JSC::ClassInfo; +using JSC::LazyClassStructure; +using JSC::Structure; +using JSC::VM; + +namespace v8 { + +// for CREATE_METHOD_TABLE +namespace JSCastingHelpers = JSC::JSCastingHelpers; + +const ClassInfo GlobalInternals::s_info = { "GlobalInternals"_s, nullptr, nullptr, nullptr, CREATE_METHOD_TABLE(GlobalInternals) }; + +GlobalInternals* GlobalInternals::create(VM& vm, Structure* structure, Zig::GlobalObject* globalObject) +{ + GlobalInternals* internals = new (NotNull, JSC::allocateCell(vm)) GlobalInternals(vm, structure, globalObject); + internals->finishCreation(vm); + return internals; +} + +void GlobalInternals::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + m_ObjectTemplateStructure.initLater([](LazyClassStructure::Initializer& init) { + init.setStructure(ObjectTemplate::createStructure(init.vm, init.global, init.global->functionPrototype())); + }); + m_HandleScopeBufferStructure.initLater([](LazyClassStructure::Initializer& init) { + init.setStructure(HandleScopeBuffer::createStructure(init.vm, init.global)); + }); + m_FunctionTemplateStructure.initLater([](LazyClassStructure::Initializer& init) { + init.setStructure(FunctionTemplate::createStructure(init.vm, init.global)); + }); + m_V8FunctionStructure.initLater([](LazyClassStructure::Initializer& init) { + init.setStructure(Function::createStructure(init.vm, init.global)); + }); +} + +template +void GlobalInternals::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + GlobalInternals* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + + thisObject->m_ObjectTemplateStructure.visit(visitor); + thisObject->m_HandleScopeBufferStructure.visit(visitor); + thisObject->m_FunctionTemplateStructure.visit(visitor); + thisObject->m_V8FunctionStructure.visit(visitor); +} + +DEFINE_VISIT_CHILDREN_WITH_MODIFIER(JS_EXPORT_PRIVATE, GlobalInternals); + +} diff --git a/src/bun.js/bindings/v8/V8GlobalInternals.h b/src/bun.js/bindings/v8/V8GlobalInternals.h new file mode 100644 index 0000000000..ec9c20b0e9 --- /dev/null +++ b/src/bun.js/bindings/v8/V8GlobalInternals.h @@ -0,0 +1,103 @@ +#pragma once + +#include "BunClientData.h" + +#include "V8Roots.h" +#include "V8Oddball.h" + +namespace v8 { + +class HandleScope; + +class GlobalInternals : public JSC::JSCell { +public: + using Base = JSC::JSCell; + + static GlobalInternals* create(JSC::VM& vm, JSC::Structure* structure, Zig::GlobalObject* globalObject); + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) + { + return JSC::Structure::create(vm, globalObject, JSC::jsNull(), JSC::TypeInfo(JSC::ObjectType, StructureFlags), info(), 0, 0); + } + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForV8GlobalInternals.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForV8GlobalInternals = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForV8GlobalInternals.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForV8GlobalInternals = std::forward(space); }); + } + + JSC::Structure* objectTemplateStructure(JSC::JSGlobalObject* globalObject) const + { + return m_ObjectTemplateStructure.getInitializedOnMainThread(globalObject); + } + + JSC::Structure* handleScopeBufferStructure(JSC::JSGlobalObject* globalObject) const + { + return m_HandleScopeBufferStructure.getInitializedOnMainThread(globalObject); + } + + JSC::Structure* functionTemplateStructure(JSC::JSGlobalObject* globalObject) const + { + return m_FunctionTemplateStructure.getInitializedOnMainThread(globalObject); + } + + JSC::Structure* v8FunctionStructure(JSC::JSGlobalObject* globalObject) const + { + return m_V8FunctionStructure.getInitializedOnMainThread(globalObject); + } + + HandleScope* currentHandleScope() const { return m_CurrentHandleScope; } + + void setCurrentHandleScope(HandleScope* handleScope) { m_CurrentHandleScope = handleScope; } + + TaggedPointer* undefinedSlot() { return &roots.roots[Roots::kUndefinedValueRootIndex]; } + + TaggedPointer* nullSlot() { return &roots.roots[Roots::kNullValueRootIndex]; } + + TaggedPointer* trueSlot() { return &roots.roots[Roots::kTrueValueRootIndex]; } + + TaggedPointer* falseSlot() { return &roots.roots[Roots::kFalseValueRootIndex]; } + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN_WITH_MODIFIER(JS_EXPORT_PRIVATE); + + friend struct Roots; + friend class Isolate; + friend class Context; + +private: + Zig::GlobalObject* globalObject; + JSC::LazyClassStructure m_ObjectTemplateStructure; + JSC::LazyClassStructure m_HandleScopeBufferStructure; + JSC::LazyClassStructure m_FunctionTemplateStructure; + JSC::LazyClassStructure m_V8FunctionStructure; + HandleScope* m_CurrentHandleScope; + Oddball undefinedValue; + Oddball nullValue; + Oddball trueValue; + Oddball falseValue; + + Roots roots; + + void finishCreation(JSC::VM& vm); + GlobalInternals(JSC::VM& vm, JSC::Structure* structure, Zig::GlobalObject* globalObject_) + : Base(vm, structure) + , m_CurrentHandleScope(nullptr) + , undefinedValue(Oddball::Kind::kUndefined) + , nullValue(Oddball::Kind::kNull) + , trueValue(Oddball::Kind::kTrue, &Map::boolean_map) + , falseValue(Oddball::Kind::kFalse, &Map::boolean_map) + , roots(this) + , globalObject(globalObject_) + { + } +}; + +} diff --git a/src/bun.js/bindings/v8/V8Handle.h b/src/bun.js/bindings/v8/V8Handle.h new file mode 100644 index 0000000000..21c5563ab4 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Handle.h @@ -0,0 +1,76 @@ +#pragma once + +#include "V8Map.h" + +namespace v8 { + +// A handle stored in a HandleScope with layout suitable for V8's inlined functions: +// - The first field is a V8 tagged pointer. If it's a SMI (int32), it holds the numeric value +// directly and the other fields don't matter. +// - Otherwise, if the first field is a pointer value, V8 treats that as a pointer to an object with +// V8 layout. V8 objects have a tagged pointer to their map (which describes their structure) as +// the first field. Therefore, in the object case, the first field is a pointer to the second +// field. +// - V8 will inspect the instance type of the map to determine if it can take fast paths for some +// functions (notably, Value::IsUndefined()/IsNull() and Object::GetInternalField()). For objects, +// we use a map with an instance type that makes V8 think it must call SlowGetInternalField(), +// which we can control. That function (and all other functions that are called on Locals) uses +// the third field to get the actual object (either a JSCell* or a void*, depending on whether map +// points to Map::object_map or Map::raw_ptr_map). +struct Handle { + Handle(const Map* map_, void* ptr_) + : to_v8_object(&this->map) + , map(const_cast(map_)) + , ptr(ptr_) + { + } + + Handle(int32_t smi) + : to_v8_object(smi) + { + } + + Handle(const Handle& that) + { + *this = that; + } + + Handle& operator=(const Handle& that) + { + map = that.map; + ptr = that.ptr; + if (that.to_v8_object.type() == TaggedPointer::Type::Smi) { + to_v8_object = that.to_v8_object; + } else { + to_v8_object = &this->map; + } + return *this; + } + + Handle() {} + + bool isCell() const + { + if (to_v8_object.type() == TaggedPointer::Type::Smi) { + return false; + } + const Map* map_ptr = map.getPtr(); + if (map_ptr == &Map::object_map || map_ptr == &Map::string_map) { + return true; + } else if (map_ptr == &Map::map_map || map_ptr == &Map::raw_ptr_map + || map_ptr == &Map::oddball_map || map_ptr == &Map::boolean_map) { + return false; + } else { + RELEASE_ASSERT_NOT_REACHED("unknown Map at %p with instance type %" PRIx16, + map_ptr, map_ptr->instance_type); + } + } + + // if not SMI, holds &this->map so that V8 can see what kind of object this is + TaggedPointer to_v8_object; + // these two fields are laid out so that V8 can find the map + TaggedPointer map; + void* ptr; +}; + +} diff --git a/src/bun.js/bindings/v8/V8HandleScope.cpp b/src/bun.js/bindings/v8/V8HandleScope.cpp new file mode 100644 index 0000000000..35cd6876cc --- /dev/null +++ b/src/bun.js/bindings/v8/V8HandleScope.cpp @@ -0,0 +1,28 @@ +#include "V8HandleScope.h" + +#include "V8GlobalInternals.h" + +namespace v8 { + +HandleScope::HandleScope(Isolate* isolate_) + : isolate(isolate_) + , prev(isolate->globalInternals()->currentHandleScope()) + , buffer(HandleScopeBuffer::create(isolate_->vm(), isolate_->globalInternals()->handleScopeBufferStructure(isolate_->globalObject()))) +{ + isolate->globalInternals()->setCurrentHandleScope(this); +} + +HandleScope::~HandleScope() +{ + isolate->globalInternals()->setCurrentHandleScope(prev); +} + +uintptr_t* HandleScope::CreateHandle(internal::Isolate* isolate, uintptr_t value) +{ + // TODO figure out if this is actually used directly + V8_UNIMPLEMENTED(); + // return buffer->createHandle(value); + return nullptr; +} + +} diff --git a/src/bun.js/bindings/v8/V8HandleScope.h b/src/bun.js/bindings/v8/V8HandleScope.h new file mode 100644 index 0000000000..af0cca4218 --- /dev/null +++ b/src/bun.js/bindings/v8/V8HandleScope.h @@ -0,0 +1,58 @@ +#pragma once + +#include "v8.h" +#include "V8Isolate.h" +#include "v8_internal.h" +#include "V8HandleScopeBuffer.h" + +namespace v8 { + +class Number; + +class HandleScope { +public: + BUN_EXPORT HandleScope(Isolate* isolate); + BUN_EXPORT ~HandleScope(); + BUN_EXPORT uintptr_t* CreateHandle(internal::Isolate* isolate, uintptr_t value); + + template Local createLocal(JSC::JSValue value) + { + // TODO(@190n) handle more types + if (value.isString()) { + return Local(buffer->createHandle(value.asCell(), &Map::string_map)); + } else if (value.isCell()) { + return Local(buffer->createHandle(value.asCell(), &Map::object_map)); + } else if (value.isInt32()) { + return Local(buffer->createSmiHandle(value.asInt32())); + } else if (value.isUndefined()) { + return Local(isolate->globalInternals()->undefinedSlot()); + } else if (value.isNull()) { + return Local(isolate->globalInternals()->nullSlot()); + } else if (value.isTrue()) { + return Local(isolate->globalInternals()->trueSlot()); + } else if (value.isFalse()) { + return Local(isolate->globalInternals()->falseSlot()); + } else { + V8_UNIMPLEMENTED(); + return Local(); + } + } + + template Local createRawLocal(void* ptr) + { + TaggedPointer* handle = buffer->createHandle(ptr, &Map::raw_ptr_map); + return Local(handle); + } + + friend class EscapableHandleScopeBase; + +protected: + // must be 24 bytes to match V8 layout + Isolate* isolate; + HandleScope* prev; + HandleScopeBuffer* buffer; +}; + +static_assert(sizeof(HandleScope) == 24, "HandleScope has wrong layout"); + +} diff --git a/src/bun.js/bindings/v8/V8HandleScopeBuffer.cpp b/src/bun.js/bindings/v8/V8HandleScopeBuffer.cpp new file mode 100644 index 0000000000..2d019d2fd8 --- /dev/null +++ b/src/bun.js/bindings/v8/V8HandleScopeBuffer.cpp @@ -0,0 +1,63 @@ +#include "V8HandleScopeBuffer.h" + +namespace v8 { + +// for CREATE_METHOD_TABLE +namespace JSCastingHelpers = JSC::JSCastingHelpers; + +const JSC::ClassInfo HandleScopeBuffer::s_info = { + "HandleScopeBuffer"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(HandleScopeBuffer) +}; + +HandleScopeBuffer* HandleScopeBuffer::create(JSC::VM& vm, JSC::Structure* structure) +{ + HandleScopeBuffer* buffer = new (NotNull, JSC::allocateCell(vm)) HandleScopeBuffer(vm, structure); + buffer->finishCreation(vm); + return buffer; +} + +template +void HandleScopeBuffer::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + HandleScopeBuffer* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + + for (int i = 0; i < thisObject->size; i++) { + auto& handle = thisObject->storage[i]; + if (handle.isCell()) { + JSCell::visitChildren(reinterpret_cast(handle.ptr), visitor); + } + } +} + +DEFINE_VISIT_CHILDREN(HandleScopeBuffer); + +Handle& HandleScopeBuffer::createUninitializedHandle() +{ + RELEASE_ASSERT(size < capacity - 1); + int index = size; + size++; + return storage[index]; +} + +TaggedPointer* HandleScopeBuffer::createHandle(void* ptr, const Map* map) +{ + // TODO(@190n) specify the map more correctly + auto& handle = createUninitializedHandle(); + handle = Handle(map, ptr); + return &handle.to_v8_object; +} + +TaggedPointer* HandleScopeBuffer::createSmiHandle(int32_t smi) +{ + auto& handle = createUninitializedHandle(); + handle.to_v8_object = TaggedPointer(smi); + return &handle.to_v8_object; +} + +} diff --git a/src/bun.js/bindings/v8/V8HandleScopeBuffer.h b/src/bun.js/bindings/v8/V8HandleScopeBuffer.h new file mode 100644 index 0000000000..e52ca9bf75 --- /dev/null +++ b/src/bun.js/bindings/v8/V8HandleScopeBuffer.h @@ -0,0 +1,59 @@ +#pragma once + +#include "v8.h" +#include "V8TaggedPointer.h" +#include "V8Map.h" +#include "V8Handle.h" + +namespace v8 { + +// An array used by HandleScope to store the items. Must keep pointer stability when resized, since +// v8::Locals point inside this array. +class HandleScopeBuffer : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static HandleScopeBuffer* create(JSC::VM& vm, JSC::Structure* structure); + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) + { + return JSC::Structure::create(vm, globalObject, JSC::jsNull(), JSC::TypeInfo(JSC::ObjectType, StructureFlags), info(), 0, 0); + } + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForHandleScopeBuffer.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForHandleScopeBuffer = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForHandleScopeBuffer.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForHandleScopeBuffer = std::forward(space); }); + } + + TaggedPointer* createHandle(void* ptr, const Map* map); + TaggedPointer* createSmiHandle(int32_t smi); + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + friend class EscapableHandleScopeBase; + +private: + // TODO make resizable + static constexpr int capacity = 64; + + Handle storage[capacity]; + int size = 0; + + Handle& createUninitializedHandle(); + + HandleScopeBuffer(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } +}; + +} diff --git a/src/bun.js/bindings/v8/V8InternalFieldObject.cpp b/src/bun.js/bindings/v8/V8InternalFieldObject.cpp new file mode 100644 index 0000000000..0f7bf03571 --- /dev/null +++ b/src/bun.js/bindings/v8/V8InternalFieldObject.cpp @@ -0,0 +1,25 @@ +#include "V8InternalFieldObject.h" + +namespace v8 { + +// for CREATE_METHOD_TABLE +namespace JSCastingHelpers = JSC::JSCastingHelpers; + +const JSC::ClassInfo InternalFieldObject::s_info = { + "InternalFieldObject"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(InternalFieldObject) +}; + +InternalFieldObject* InternalFieldObject::create(JSC::VM& vm, JSC::Structure* structure, Local objectTemplate) +{ + // TODO figure out how this works with __internals + // maybe pass a Local + auto object = new (NotNull, JSC::allocateCell(vm)) InternalFieldObject(vm, structure, objectTemplate->InternalFieldCount()); + object->finishCreation(vm); + return object; +} + +} diff --git a/src/bun.js/bindings/v8/V8InternalFieldObject.h b/src/bun.js/bindings/v8/V8InternalFieldObject.h new file mode 100644 index 0000000000..4dcdd0b890 --- /dev/null +++ b/src/bun.js/bindings/v8/V8InternalFieldObject.h @@ -0,0 +1,69 @@ +#pragma once + +#include "V8ObjectTemplate.h" + +namespace v8 { + +class InternalFieldObject : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + + DECLARE_INFO; + + struct InternalField { + union { + JSC::JSValue js_value; + void* raw; + } data; + bool is_js_value; + + InternalField(JSC::JSValue js_value) + : data({ .js_value = js_value }) + , is_js_value(true) + { + } + + InternalField(void* raw) + : data({ .raw = raw }) + , is_js_value(false) + { + } + + InternalField() + : data({ .js_value = JSC::jsUndefined() }) + , is_js_value(true) + { + } + }; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForInternalFieldObject.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForInternalFieldObject = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForInternalFieldObject.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForInternalFieldObject = std::forward(space); }); + } + + using FieldContainer = WTF::Vector; + + FieldContainer* internalFields() { return &fields; } + static InternalFieldObject* create(JSC::VM& vm, JSC::Structure* structure, Local objectTemplate); + +protected: + InternalFieldObject(JSC::VM& vm, JSC::Structure* structure, int internalFieldCount) + : Base(vm, structure) + , fields(internalFieldCount) + { + } + +private: + // TODO(@190n) use template with fixed size array for small counts + FieldContainer fields; +}; + +} diff --git a/src/bun.js/bindings/v8/V8Isolate.cpp b/src/bun.js/bindings/v8/V8Isolate.cpp new file mode 100644 index 0000000000..80e173814a --- /dev/null +++ b/src/bun.js/bindings/v8/V8Isolate.cpp @@ -0,0 +1,27 @@ +#include "V8Isolate.h" +#include "V8HandleScope.h" + +namespace v8 { + +// Returns the isolate inside which the current thread is running or nullptr. +Isolate* Isolate::TryGetCurrent() +{ + auto* global = Bun__getDefaultGlobalObject(); + + return global ? reinterpret_cast(&global->V8GlobalInternals()->roots) : nullptr; +} + +// Returns the isolate inside which the current thread is running. +Isolate* Isolate::GetCurrent() +{ + auto* global = Bun__getDefaultGlobalObject(); + + return global ? reinterpret_cast(&global->V8GlobalInternals()->roots) : nullptr; +} + +Local Isolate::GetCurrentContext() +{ + return currentHandleScope()->createRawLocal(this); +} + +} diff --git a/src/bun.js/bindings/v8/V8Isolate.h b/src/bun.js/bindings/v8/V8Isolate.h new file mode 100644 index 0000000000..4616c22060 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Isolate.h @@ -0,0 +1,34 @@ +#pragma once + +#include "v8.h" +#include "V8Context.h" +#include "V8Local.h" +#include "V8GlobalInternals.h" + +namespace v8 { + +class HandleScope; + +// This currently is just a pointer to a v8::Roots +// We do that so that we can recover the context and the VM from the "Isolate," and so that inlined +// V8 functions can find values at certain fixed offsets from the Isolate +class Isolate final { +public: + Isolate() = default; + + // Returns the isolate inside which the current thread is running or nullptr. + BUN_EXPORT static Isolate* TryGetCurrent(); + + // Returns the isolate inside which the current thread is running. + BUN_EXPORT static Isolate* GetCurrent(); + + BUN_EXPORT Local GetCurrentContext(); + + static Isolate* fromGlobalObject(Zig::GlobalObject* globalObject) { return reinterpret_cast(&globalObject->V8GlobalInternals()->roots); } + Zig::GlobalObject* globalObject() { return reinterpret_cast(this)->parent->globalObject; } + JSC::VM& vm() { return globalObject()->vm(); } + GlobalInternals* globalInternals() { return globalObject()->V8GlobalInternals(); } + HandleScope* currentHandleScope() { return globalInternals()->currentHandleScope(); } +}; + +} diff --git a/src/bun.js/bindings/v8/V8Local.h b/src/bun.js/bindings/v8/V8Local.h new file mode 100644 index 0000000000..a2f23ec1b4 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Local.h @@ -0,0 +1,42 @@ +#pragma once + +#include "root.h" + +#include "V8TaggedPointer.h" + +namespace v8 { + +template +class Local final { +public: + Local() + : location(nullptr) + { + } + + Local(TaggedPointer* slot) + : location(slot) + { + } + + bool IsEmpty() const { return location == nullptr; } + + T* operator*() const { return reinterpret_cast(location); } + T* operator->() const { return reinterpret_cast(location); } + + template + Local reinterpret() const + { + return Local(location); + } + + TaggedPointer tagged() const + { + return *location; + } + +private: + TaggedPointer* location; +}; + +} diff --git a/src/bun.js/bindings/v8/V8Map.cpp b/src/bun.js/bindings/v8/V8Map.cpp new file mode 100644 index 0000000000..d2f985094e --- /dev/null +++ b/src/bun.js/bindings/v8/V8Map.cpp @@ -0,0 +1,13 @@ +#include "V8Map.h" + +namespace v8 { + +// TODO give these more appropriate instance types +const Map Map::map_map(InstanceType::Object); +const Map Map::object_map(InstanceType::Object); +const Map Map::raw_ptr_map(InstanceType::Object); +const Map Map::oddball_map(InstanceType::Oddball); +const Map Map::boolean_map(InstanceType::Oddball); +const Map Map::string_map(InstanceType::String); + +} diff --git a/src/bun.js/bindings/v8/V8Map.h b/src/bun.js/bindings/v8/V8Map.h new file mode 100644 index 0000000000..eff73c5dec --- /dev/null +++ b/src/bun.js/bindings/v8/V8Map.h @@ -0,0 +1,55 @@ +#pragma once + +#include "V8TaggedPointer.h" + +namespace v8 { + +enum class InstanceType : uint16_t { + // v8-internal.h:787, kFirstNonstringType is 0x80 + String = 0x7f, + // "Oddball" in V8 means undefined or null + // v8-internal.h:788 + Oddball = 0x83, + // v8-internal.h:1016 kFirstNonstringType + // this cannot be kJSObjectType (or anything in the range [kJSObjectType, kLastJSApiObjectType]) + // because then V8 will try to access internal fields directly instead of calling + // SlowGetInternalField + Object = 0x80, +}; + +// V8's description of the structure of an object +struct Map { + // the structure of the map itself (always points to map_map) + TaggedPointer meta_map; + // TBD whether we need to put anything here to please inlined V8 functions + uint32_t unused; + // describes which kind of object this is. we shouldn't actually need to create very many + // instance types -- only ones for primitives, and one to make sure V8 thinks it cannot take the + // fast path when accessing internal fields + // (v8::internal::Internals::CanHaveInternalField, in v8-internal.h) + InstanceType instance_type; + + // the map used by maps + static const Map map_map; + // the map used by objects inheriting JSCell + static const Map object_map; + // the map used by pointers to non-JSCell objects stored in handles + static const Map raw_ptr_map; + // the map used by oddballs (null, undefined) + static const Map oddball_map; + // the map used by booleans + static const Map boolean_map; + // the map used by strings + static const Map string_map; + + Map(InstanceType instance_type_) + : meta_map(const_cast(&map_map)) + , unused(0xaaaaaaaa) + , instance_type(instance_type_) + { + } +}; + +static_assert(sizeof(Map) == 16, "Map has wrong layout"); + +} diff --git a/src/bun.js/bindings/v8/V8Maybe.h b/src/bun.js/bindings/v8/V8Maybe.h new file mode 100644 index 0000000000..6ad41688ad --- /dev/null +++ b/src/bun.js/bindings/v8/V8Maybe.h @@ -0,0 +1,33 @@ +#pragma once + +namespace v8 { + +template +class Maybe { +public: + Maybe() + : has_value(false) + { + } + explicit Maybe(const T& t) + : has_value(true) + , value(t) + { + } + bool has_value; + T value; +}; + +template +inline Maybe Nothing() +{ + return Maybe(); +} + +template +inline Maybe Just(const T& t) +{ + return Maybe(t); +} + +} diff --git a/src/bun.js/bindings/v8/V8MaybeLocal.h b/src/bun.js/bindings/v8/V8MaybeLocal.h new file mode 100644 index 0000000000..f53a6feedb --- /dev/null +++ b/src/bun.js/bindings/v8/V8MaybeLocal.h @@ -0,0 +1,24 @@ +#pragma once + +#include "V8Local.h" + +namespace v8 { + +template +class MaybeLocal { +public: + MaybeLocal() + : local_(Local()) {}; + + template MaybeLocal(Local that) + : local_(that) + { + } + + bool IsEmpty() const { return local_.IsEmpty(); } + +private: + Local local_; +}; + +} diff --git a/src/bun.js/bindings/v8/V8Number.cpp b/src/bun.js/bindings/v8/V8Number.cpp new file mode 100644 index 0000000000..267d8de90f --- /dev/null +++ b/src/bun.js/bindings/v8/V8Number.cpp @@ -0,0 +1,22 @@ +#include "V8Number.h" +#include "V8HandleScope.h" + +#include +#include + +namespace v8 { + +Local Number::New(Isolate* isolate, double value) +{ + double int_part; + RELEASE_ASSERT_WITH_MESSAGE(std::modf(value, &int_part) == 0.0, "TODO handle doubles in Number::New"); + RELEASE_ASSERT_WITH_MESSAGE(int_part >= INT32_MIN && int_part <= INT32_MAX, "TODO handle doubles in Number::New"); + return isolate->currentHandleScope()->createLocal(JSC::jsNumber(value)); +} + +double Number::Value() const +{ + return localToJSValue(Isolate::GetCurrent()->globalInternals()).asNumber(); +} + +} diff --git a/src/bun.js/bindings/v8/V8Number.h b/src/bun.js/bindings/v8/V8Number.h new file mode 100644 index 0000000000..7ec8e6d54c --- /dev/null +++ b/src/bun.js/bindings/v8/V8Number.h @@ -0,0 +1,17 @@ +#pragma once + +#include "v8.h" +#include "V8Primitive.h" +#include "V8Local.h" +#include "V8Isolate.h" + +namespace v8 { + +class Number : public Primitive { +public: + BUN_EXPORT static Local New(Isolate* isolate, double value); + + BUN_EXPORT double Value() const; +}; + +} diff --git a/src/bun.js/bindings/v8/V8Object.cpp b/src/bun.js/bindings/v8/V8Object.cpp new file mode 100644 index 0000000000..9e35aedda9 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Object.cpp @@ -0,0 +1,93 @@ +#include "V8Object.h" +#include "V8InternalFieldObject.h" +#include "V8HandleScope.h" + +#include "JavaScriptCore/ConstructData.h" +#include "JavaScriptCore/ObjectConstructor.h" + +using JSC::Identifier; +using JSC::JSFinalObject; +using JSC::JSGlobalObject; +using JSC::JSObject; +using JSC::JSValue; +using JSC::PutPropertySlot; + +namespace v8 { + +using FieldContainer = InternalFieldObject::FieldContainer; + +static FieldContainer* getInternalFieldsContainer(Object* object) +{ + JSObject* js_object = object->localToObjectPointer(); + + // TODO(@190n): do we need to unwrap proxies like node-jsc did? + + if (auto ifo = JSC::jsDynamicCast(js_object)) { + return ifo->internalFields(); + } + + return nullptr; +} + +Local Object::New(Isolate* isolate) +{ + JSFinalObject* object = JSC::constructEmptyObject(isolate->globalObject()); + return isolate->currentHandleScope()->createLocal(object); +} + +Maybe Object::Set(Local context, Local key, Local value) +{ + Zig::GlobalObject* globalObject = context->globalObject(); + JSObject* object = localToObjectPointer(); + JSValue k = key->localToJSValue(globalObject->V8GlobalInternals()); + JSValue v = value->localToJSValue(globalObject->V8GlobalInternals()); + auto& vm = globalObject->vm(); + + auto scope = DECLARE_CATCH_SCOPE(vm); + PutPropertySlot slot(object, false); + + Identifier identifier = k.toPropertyKey(globalObject); + RETURN_IF_EXCEPTION(scope, Nothing()); + + if (!object->put(object, globalObject, identifier, v, slot)) { + scope.clearExceptionExceptTermination(); + return Nothing(); + } + if (scope.exception()) { + scope.clearException(); + return Nothing(); + } + return Just(true); +} + +void Object::SetInternalField(int index, Local data) +{ + auto fields = getInternalFieldsContainer(this); + if (fields && index >= 0 && index < fields->size()) { + fields->at(index) = InternalFieldObject::InternalField(data->localToJSValue(Isolate::GetCurrent()->globalInternals())); + } +} + +Local Object::GetInternalField(int index) +{ + return SlowGetInternalField(index); +} + +Local Object::SlowGetInternalField(int index) +{ + auto* fields = getInternalFieldsContainer(this); + JSObject* js_object = localToObjectPointer(); + HandleScope* handleScope = Isolate::fromGlobalObject(JSC::jsDynamicCast(js_object->globalObject()))->currentHandleScope(); + if (fields && index >= 0 && index < fields->size()) { + auto& field = fields->at(index); + if (field.is_js_value) { + return handleScope->createLocal(field.data.js_value); + } else { + V8_UNIMPLEMENTED(); + } + } + // TODO handle undefined/null the way v8 does + return Local(); +} + +} diff --git a/src/bun.js/bindings/v8/V8Object.h b/src/bun.js/bindings/v8/V8Object.h new file mode 100644 index 0000000000..6c74b0f035 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Object.h @@ -0,0 +1,25 @@ +#pragma once + +#include "v8.h" +#include "V8Value.h" +#include "V8Local.h" +#include "V8Isolate.h" +#include "V8Maybe.h" +#include "V8Context.h" +#include "V8Data.h" + +namespace v8 { + +class Object : public Value { +public: + BUN_EXPORT static Local New(Isolate* isolate); + BUN_EXPORT Maybe Set(Local context, Local key, Local value); + BUN_EXPORT void SetInternalField(int index, Local data); + // usually inlined + BUN_EXPORT Local GetInternalField(int index); + +private: + BUN_EXPORT Local SlowGetInternalField(int index); +}; + +} diff --git a/src/bun.js/bindings/v8/V8ObjectTemplate.cpp b/src/bun.js/bindings/v8/V8ObjectTemplate.cpp new file mode 100644 index 0000000000..1fcfc516cc --- /dev/null +++ b/src/bun.js/bindings/v8/V8ObjectTemplate.cpp @@ -0,0 +1,107 @@ +#include "V8ObjectTemplate.h" +#include "V8InternalFieldObject.h" +#include "V8GlobalInternals.h" +#include "V8HandleScope.h" + +#include "JavaScriptCore/FunctionPrototype.h" +#include "JavaScriptCore/LazyPropertyInlines.h" +#include "JavaScriptCore/VMTrapsInlines.h" + +using JSC::JSGlobalObject; +using JSC::JSValue; +using JSC::LazyProperty; +using JSC::Structure; + +namespace v8 { + +void ObjectTemplate::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + __internals.objectStructure.initLater([](const LazyProperty::Initializer& init) { + init.set(JSC::Structure::create( + init.vm, + init.owner->globalObject(), + init.owner->globalObject()->objectPrototype(), + JSC::TypeInfo(JSC::ObjectType, InternalFieldObject::StructureFlags), + InternalFieldObject::info())); + }); +} + +// for CREATE_METHOD_TABLE +namespace JSCastingHelpers = JSC::JSCastingHelpers; + +const JSC::ClassInfo ObjectTemplate::s_info = { + "ObjectTemplate"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(ObjectTemplate) +}; + +Local ObjectTemplate::New(Isolate* isolate, Local constructor) +{ + RELEASE_ASSERT(constructor.IsEmpty()); + auto globalObject = isolate->globalObject(); + auto& vm = globalObject->vm(); + Structure* structure = globalObject->V8GlobalInternals()->objectTemplateStructure(globalObject); + auto* objectTemplate = new (NotNull, JSC::allocateCell(vm)) ObjectTemplate(vm, structure); + // TODO pass constructor + objectTemplate->finishCreation(vm); + return isolate->currentHandleScope()->createLocal(objectTemplate); +} + +MaybeLocal ObjectTemplate::NewInstance(Local context) +{ + // TODO handle constructor + // TODO handle interceptors? + + auto& vm = context->vm(); + auto thisObj = localToObjectPointer(); + + // get a structure + // must take thisObj because JSC needs the native pointer + auto structure = internals().objectStructure.get(thisObj); + + // create object from it + // InternalFieldObject needs a Local, which we can create using the `this` + // pointer as we know this method itself was called through a Local + auto newInstance = InternalFieldObject::create(vm, structure, Local(reinterpret_cast(this))); + + // todo: apply properties + + return MaybeLocal(context->currentHandleScope()->createLocal(newInstance)); +} + +template +void ObjectTemplate::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + ObjectTemplate* fn = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(fn, info()); + Base::visitChildren(fn, visitor); + + fn->__internals.objectStructure.visit(visitor); +} + +DEFINE_VISIT_CHILDREN(ObjectTemplate); + +void ObjectTemplate::SetInternalFieldCount(int value) +{ + internals().internalFieldCount = value; +} + +int ObjectTemplate::InternalFieldCount() const +{ + return internals().internalFieldCount; +} + +Structure* ObjectTemplate::createStructure(JSC::VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create( + vm, + globalObject, + prototype, + JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), + info()); +} + +} diff --git a/src/bun.js/bindings/v8/V8ObjectTemplate.h b/src/bun.js/bindings/v8/V8ObjectTemplate.h new file mode 100644 index 0000000000..b8d92118f7 --- /dev/null +++ b/src/bun.js/bindings/v8/V8ObjectTemplate.h @@ -0,0 +1,83 @@ +#pragma once + +#include "JavaScriptCore/SubspaceAccess.h" +#include "v8.h" +#include "V8Context.h" +#include "V8Local.h" +#include "V8Isolate.h" +#include "V8FunctionTemplate.h" +#include "V8MaybeLocal.h" +#include "V8Object.h" +#include "V8Template.h" + +namespace v8 { + +// If this inherited Template like it does in V8, the layout would be wrong for JSC HeapCell. +// Inheritance shouldn't matter for the ABI. +class ObjectTemplate : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + DECLARE_INFO; + + BUN_EXPORT static Local New(Isolate* isolate, Local constructor = Local()); + BUN_EXPORT MaybeLocal NewInstance(Local context); + BUN_EXPORT void SetInternalFieldCount(int value); + BUN_EXPORT int InternalFieldCount() const; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype); + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForObjectTemplate.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForObjectTemplate = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForObjectTemplate.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForObjectTemplate = std::forward(space); }); + } + + DECLARE_VISIT_CHILDREN; + +private: + class Internals { + int internalFieldCount = 0; + JSC::LazyProperty objectStructure; + friend class ObjectTemplate; + }; + + // do not use directly inside exported V8 functions, use internals() + Internals __internals; + + ObjectTemplate* localToObjectPointer() + { + return reinterpret_cast(this)->localToObjectPointer(); + } + + const ObjectTemplate* localToObjectPointer() const + { + return reinterpret_cast(this)->localToObjectPointer(); + } + + Internals& internals() + { + return localToObjectPointer()->__internals; + } + + const Internals& internals() const + { + return localToObjectPointer()->__internals; + } + + ObjectTemplate(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, Template::DummyCallback, Template::DummyCallback) + { + } + + void finishCreation(JSC::VM& vm); +}; + +} diff --git a/src/bun.js/bindings/v8/V8Oddball.h b/src/bun.js/bindings/v8/V8Oddball.h new file mode 100644 index 0000000000..c58f40c149 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Oddball.h @@ -0,0 +1,28 @@ +#pragma once + +#include "V8TaggedPointer.h" +#include "V8Map.h" + +namespace v8 { + +struct Oddball { + enum class Kind : int { + kUndefined = 4, + kNull = 3, + kInvalid = 255, + kTrue = 1, + kFalse = 0, + }; + + TaggedPointer map; + uintptr_t unused[4]; + TaggedPointer kind; + + Oddball(Kind kind_, const Map* map_ = &Map::oddball_map) + : map(const_cast(map_)) + , kind(TaggedPointer(static_cast(kind_))) + { + } +}; + +} diff --git a/src/bun.js/bindings/v8/V8Primitive.h b/src/bun.js/bindings/v8/V8Primitive.h new file mode 100644 index 0000000000..afb9565fe6 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Primitive.h @@ -0,0 +1,9 @@ +#pragma once + +#include "V8Value.h" + +namespace v8 { + +class Primitive : public Value {}; + +} diff --git a/src/bun.js/bindings/v8/V8Roots.cpp b/src/bun.js/bindings/v8/V8Roots.cpp new file mode 100644 index 0000000000..025f68a714 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Roots.cpp @@ -0,0 +1,15 @@ +#include "V8Roots.h" +#include "V8GlobalInternals.h" + +namespace v8 { + +Roots::Roots(GlobalInternals* parent_) + : parent(parent_) +{ + roots[kUndefinedValueRootIndex] = TaggedPointer(&parent->undefinedValue); + roots[kNullValueRootIndex] = TaggedPointer(&parent->nullValue); + roots[kTrueValueRootIndex] = TaggedPointer(&parent->trueValue); + roots[kFalseValueRootIndex] = TaggedPointer(&parent->falseValue); +} + +} diff --git a/src/bun.js/bindings/v8/V8Roots.h b/src/bun.js/bindings/v8/V8Roots.h new file mode 100644 index 0000000000..1a538388e4 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Roots.h @@ -0,0 +1,32 @@ +#pragma once + +#include "V8TaggedPointer.h" + +namespace v8 { + +class GlobalInternals; + +// Container for some data that V8 expects to find at certain offsets. Isolate and Context pointers +// actually point to this object. It is a separate struct so that we can use offsetof() to make sure +// the layout is correct. +struct Roots { + // v8-internal.h:775 + static const int kUndefinedValueRootIndex = 4; + static const int kTheHoleValueRootIndex = 5; + static const int kNullValueRootIndex = 6; + static const int kTrueValueRootIndex = 7; + static const int kFalseValueRootIndex = 8; + + GlobalInternals* parent; + + uintptr_t padding[73]; + + TaggedPointer roots[9]; + + Roots(GlobalInternals* parent); +}; + +// kIsolateRootsOffset at v8-internal.h:744 +static_assert(offsetof(Roots, roots) == 592, "Roots does not match V8 layout"); + +} diff --git a/src/bun.js/bindings/v8/V8Signature.h b/src/bun.js/bindings/v8/V8Signature.h new file mode 100644 index 0000000000..9138775653 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Signature.h @@ -0,0 +1,9 @@ +#pragma once + +#include "V8Data.h" + +namespace v8 { + +class Signature : public Data {}; + +} diff --git a/src/bun.js/bindings/v8/V8String.cpp b/src/bun.js/bindings/v8/V8String.cpp new file mode 100644 index 0000000000..b9769e506e --- /dev/null +++ b/src/bun.js/bindings/v8/V8String.cpp @@ -0,0 +1,76 @@ +#include "V8String.h" + +#include "V8HandleScope.h" + +using JSC::JSString; +using JSC::JSValue; + +namespace v8 { + +MaybeLocal String::NewFromUtf8(Isolate* isolate, char const* data, NewStringType type, int signed_length) +{ + // TODO(@190n) maybe use JSC::AtomString instead of ignoring type + (void)type; + size_t length = 0; + if (signed_length < 0) { + length = strlen(data); + } else { + length = static_cast(signed_length); + } + + if (length > JSString::MaxLength) { + // empty + return MaybeLocal(); + } + + std::span span(reinterpret_cast(data), length); + // ReplacingInvalidSequences matches how v8 behaves here + auto string = WTF::String::fromUTF8ReplacingInvalidSequences(span); + RELEASE_ASSERT(!string.isNull()); + JSString* jsString = JSC::jsString(isolate->vm(), string); + return MaybeLocal(isolate->globalInternals()->currentHandleScope()->createLocal(jsString)); +} + +int String::WriteUtf8(Isolate* isolate, char* buffer, int length, int* nchars_ref, int options) const +{ + RELEASE_ASSERT(options == 0); + auto jsString = localToObjectPointer(); + WTF::String string = jsString->getString(isolate->globalObject()); + + // TODO(@190n) handle 16 bit strings + if (!string.is8Bit()) { + V8_UNIMPLEMENTED(); + } + auto span = string.span8(); + + int to_copy = length; + bool terminate = true; + if (to_copy < 0) { + to_copy = span.size(); + } else if (to_copy > span.size()) { + to_copy = span.size(); + } else if (length < span.size()) { + to_copy = length; + terminate = false; + } + // TODO(@190n) span.data() is latin1 not utf8, but this is okay as long as the only way to make + // a v8 string is NewFromUtf8. that's because NewFromUtf8 will use either all ASCII or all UTF-16. + memcpy(buffer, span.data(), to_copy); + if (terminate) { + buffer[to_copy] = 0; + } + if (nchars_ref) { + *nchars_ref = to_copy; + } + return terminate ? to_copy + 1 : to_copy; +} + +int String::Length() const +{ + auto jsString = localToObjectPointer(); + RELEASE_ASSERT(jsString->isString()); + WTF::String s = jsString->getString(Isolate::GetCurrent()->globalObject()); + return s.length(); +} + +} diff --git a/src/bun.js/bindings/v8/V8String.h b/src/bun.js/bindings/v8/V8String.h new file mode 100644 index 0000000000..eaf7e3364b --- /dev/null +++ b/src/bun.js/bindings/v8/V8String.h @@ -0,0 +1,35 @@ +#pragma once + +#include "v8.h" +#include "V8Primitive.h" +#include "V8MaybeLocal.h" +#include "V8Isolate.h" + +namespace v8 { + +enum class NewStringType { + kNormal, + kInternalized, +}; + +class String : Primitive { +public: + enum WriteOptions { + NO_OPTIONS = 0, + HINT_MANY_WRITES_EXPECTED = 1, + NO_NULL_TERMINATION = 2, + PRESERVE_ONE_BYTE_NULL = 4, + REPLACE_INVALID_UTF8 = 8, + }; + + BUN_EXPORT static MaybeLocal NewFromUtf8(Isolate* isolate, char const* data, NewStringType type, int length = -1); + BUN_EXPORT int WriteUtf8(Isolate* isolate, char* buffer, int length = -1, int* nchars_ref = nullptr, int options = NO_OPTIONS) const; + BUN_EXPORT int Length() const; + + JSC::JSString* localToJSString() + { + return localToObjectPointer(); + } +}; + +} diff --git a/src/bun.js/bindings/v8/V8TaggedPointer.h b/src/bun.js/bindings/v8/V8TaggedPointer.h new file mode 100644 index 0000000000..6cb18feb82 --- /dev/null +++ b/src/bun.js/bindings/v8/V8TaggedPointer.h @@ -0,0 +1,95 @@ +#pragma once + +#include "v8.h" + +namespace v8 { + +struct TaggedPointer { + uintptr_t value; + + enum class Type : uint8_t { + Smi, + StrongPointer, + WeakPointer, + }; + + TaggedPointer() + : TaggedPointer(nullptr) {}; + TaggedPointer(const TaggedPointer&) = default; + TaggedPointer& operator=(const TaggedPointer&) = default; + bool operator==(const TaggedPointer& other) const { return value == other.value; } + + TaggedPointer(void* ptr, bool weak) + : value(reinterpret_cast(ptr) | (weak ? 3 : 1)) + { + RELEASE_ASSERT((reinterpret_cast(ptr) & 3) == 0); + } + + TaggedPointer(void* ptr) + : TaggedPointer(ptr, false) + { + } + + TaggedPointer(int32_t smi) + : value(static_cast(smi) << 32) + { + } + + static TaggedPointer fromRaw(uintptr_t raw) + { + TaggedPointer tagged; + tagged.value = raw; + return tagged; + } + + Type type() const + { + switch (value & 3) { + case 0: + return Type::Smi; + case 1: + return Type::StrongPointer; + case 3: + return Type::WeakPointer; + default: + RELEASE_ASSERT_NOT_REACHED(); + } + } + + template T* getPtr() const + { + if (type() == Type::Smi) { + return nullptr; + } + return reinterpret_cast(value & ~3ull); + } + + bool getSmi(int32_t& smi) const + { + if (type() != Type::Smi) { + return false; + } + smi = static_cast(value >> 32); + return true; + } + + int32_t getSmiUnchecked() const + { + ASSERT(type() == Type::Smi); + return static_cast(value >> 32); + } + + JSC::JSValue getJSValue() const + { + // TODO handle heap doubles + int32_t smi; + if (getSmi(smi)) { + return JSC::jsNumber(smi); + } + // TODO(@190n) this needs to be a pointer to a specific class for v8 objects + // maybe not? + return getPtr(); + } +}; + +} diff --git a/src/bun.js/bindings/v8/V8Template.cpp b/src/bun.js/bindings/v8/V8Template.cpp new file mode 100644 index 0000000000..5993d74d41 --- /dev/null +++ b/src/bun.js/bindings/v8/V8Template.cpp @@ -0,0 +1,11 @@ +#include "V8Template.h" + +namespace v8 { + +JSC::EncodedJSValue Template::DummyCallback(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) +{ + ASSERT_NOT_REACHED(); + return JSC::JSValue::encode(JSC::jsUndefined()); +} + +} diff --git a/src/bun.js/bindings/v8/V8Template.h b/src/bun.js/bindings/v8/V8Template.h new file mode 100644 index 0000000000..d6cbdbc66f --- /dev/null +++ b/src/bun.js/bindings/v8/V8Template.h @@ -0,0 +1,13 @@ +#pragma once + +#include "V8Data.h" + +namespace v8 { + +// matches V8 class hierarchy +class Template : public Data { +public: + static JSC_HOST_CALL_ATTRIBUTES JSC::EncodedJSValue DummyCallback(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame); +}; + +} diff --git a/src/bun.js/bindings/v8/V8Value.cpp b/src/bun.js/bindings/v8/V8Value.cpp new file mode 100644 index 0000000000..f0dcdf0d7c --- /dev/null +++ b/src/bun.js/bindings/v8/V8Value.cpp @@ -0,0 +1,76 @@ +#include "V8Value.h" +#include "V8Isolate.h" + +namespace v8 { + +bool Value::IsBoolean() const +{ + return localToJSValue(Isolate::GetCurrent()->globalInternals()).isBoolean(); +} + +bool Value::IsObject() const +{ + return localToJSValue(Isolate::GetCurrent()->globalInternals()).isObject(); +} + +bool Value::IsNumber() const +{ + return localToJSValue(Isolate::GetCurrent()->globalInternals()).isNumber(); +} + +bool Value::IsUint32() const +{ + return localToJSValue(Isolate::GetCurrent()->globalInternals()).isUInt32(); +} + +bool Value::IsUndefined() const +{ + return localToJSValue(Isolate::GetCurrent()->globalInternals()).isUndefined(); +} + +bool Value::IsNull() const +{ + return localToJSValue(Isolate::GetCurrent()->globalInternals()).isNull(); +} + +bool Value::IsNullOrUndefined() const +{ + return localToJSValue(Isolate::GetCurrent()->globalInternals()).isUndefinedOrNull(); +} + +bool Value::IsTrue() const +{ + return FullIsTrue(); +} + +bool Value::IsFalse() const +{ + return FullIsFalse(); +} + +bool Value::IsString() const +{ + return localToJSValue(Isolate::GetCurrent()->globalInternals()).isString(); +} + +Maybe Value::Uint32Value(Local context) const +{ + auto js_value = localToJSValue(context->globalObject()->V8GlobalInternals()); + uint32_t value; + if (js_value.getUInt32(value)) { + return Just(value); + } + return Nothing(); +} + +bool Value::FullIsTrue() const +{ + return localToJSValue(Isolate::GetCurrent()->globalInternals()).isTrue(); +} + +bool Value::FullIsFalse() const +{ + return localToJSValue(Isolate::GetCurrent()->globalInternals()).isFalse(); +} + +} diff --git a/src/bun.js/bindings/v8/V8Value.h b/src/bun.js/bindings/v8/V8Value.h new file mode 100644 index 0000000000..fe3b7c9f7e --- /dev/null +++ b/src/bun.js/bindings/v8/V8Value.h @@ -0,0 +1,32 @@ +#pragma once + +#include "V8Data.h" +#include "V8Maybe.h" +#include "V8Local.h" +#include "V8Context.h" + +namespace v8 { + +class Value : public Data { +public: + BUN_EXPORT bool IsBoolean() const; + BUN_EXPORT bool IsObject() const; + BUN_EXPORT bool IsNumber() const; + BUN_EXPORT bool IsUint32() const; + BUN_EXPORT Maybe Uint32Value(Local context) const; + + // usually inlined: + BUN_EXPORT bool IsUndefined() const; + BUN_EXPORT bool IsNull() const; + BUN_EXPORT bool IsNullOrUndefined() const; + BUN_EXPORT bool IsTrue() const; + BUN_EXPORT bool IsFalse() const; + BUN_EXPORT bool IsString() const; + +private: + // non-inlined versions of these + BUN_EXPORT bool FullIsTrue() const; + BUN_EXPORT bool FullIsFalse() const; +}; + +} diff --git a/src/bun.js/bindings/v8/node.cpp b/src/bun.js/bindings/v8/node.cpp new file mode 100644 index 0000000000..fa12e1d62a --- /dev/null +++ b/src/bun.js/bindings/v8/node.cpp @@ -0,0 +1,110 @@ +#include "node.h" +#include "V8HandleScope.h" + +#include "JavaScriptCore/ObjectConstructor.h" +#include "CommonJSModuleRecord.h" +#include + +using v8::Context; +using v8::HandleScope; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::Value; + +using JSC::JSObject; +using JSC::jsUndefined; +using JSC::JSValue; + +namespace node { + +void AddEnvironmentCleanupHook(v8::Isolate* isolate, + void (*fun)(void* arg), + void* arg) +{ + // TODO +} + +void RemoveEnvironmentCleanupHook(v8::Isolate* isolate, + void (*fun)(void* arg), + void* arg) +{ + // TODO +} + +void node_module_register(void* opaque_mod) +{ + // TODO unify this with napi_module_register + auto* globalObject = Bun__getDefaultGlobalObject(); + auto& vm = globalObject->vm(); + auto* mod = reinterpret_cast(opaque_mod); + // Error: The module '/Users/ben/code/bun/test/v8/v8-module/build/Release/v8tests.node' + // was compiled against a different Node.js version using + // NODE_MODULE_VERSION 127. This version of Node.js requires + // NODE_MODULE_VERSION 108. Please try re-compiling or re-installing + // the module (for instance, using `npm rebuild` or `npm install`). + + if (mod->nm_version != REPORTED_NODEJS_ABI_VERSION) { + } + + auto keyStr = WTF::String::fromUTF8(mod->nm_modname); + globalObject->napiModuleRegisterCallCount++; + JSValue pendingNapiModule = globalObject->m_pendingNapiModuleAndExports[0].get(); + JSObject* object = (pendingNapiModule && pendingNapiModule.isObject()) ? pendingNapiModule.getObject() + : nullptr; + + auto scope = DECLARE_THROW_SCOPE(vm); + JSC::Strong strongExportsObject; + + if (mod->nm_version != REPORTED_NODEJS_ABI_VERSION) { + auto* error = JSC::createError(globalObject, + WTF::makeString("The module '"_s, + keyStr, + "' was compiled against a different Node.js ABI version using NODE_MODULE_VERSION "_s, + mod->nm_version, + ". This version of Bun requires NODE_MODULE_VERSION "_s, + REPORTED_NODEJS_ABI_VERSION, + ". Please try re-compiling or re-installing the module."_s)); + globalObject->m_pendingNapiModuleAndExports[0].set(vm, globalObject, error); + return; + } + + if (!object) { + auto* exportsObject = JSC::constructEmptyObject(globalObject); + RETURN_IF_EXCEPTION(scope, void()); + + object = Bun::JSCommonJSModule::create(globalObject, keyStr, exportsObject, false, jsUndefined()); + strongExportsObject = { vm, exportsObject }; + } else { + JSValue exportsObject = object->getIfPropertyExists(globalObject, WebCore::builtinNames(vm).exportsPublicName()); + RETURN_IF_EXCEPTION(scope, void()); + + if (exportsObject && exportsObject.isObject()) { + strongExportsObject = { vm, exportsObject.getObject() }; + } + } + + JSC::Strong strongObject = { vm, object }; + + HandleScope hs(Isolate::fromGlobalObject(globalObject)); + + // exports, module + Local exports = hs.createLocal(*strongExportsObject); + Local module = hs.createLocal(object); + Local context = hs.createLocal(globalObject); + if (mod->nm_context_register_func) { + mod->nm_context_register_func(exports, module, context, mod->nm_priv); + } else if (mod->nm_register_func) { + mod->nm_register_func(exports, module, mod->nm_priv); + } else { + auto* error = JSC::createError(globalObject, WTF::makeString("The module '"_s, keyStr, "' has no declared entry point."_s)); + globalObject->m_pendingNapiModuleAndExports[0].set(vm, globalObject, error); + return; + } + + RETURN_IF_EXCEPTION(scope, void()); + + globalObject->m_pendingNapiModuleAndExports[1].set(vm, globalObject, object); +} + +} diff --git a/src/bun.js/bindings/v8/node.h b/src/bun.js/bindings/v8/node.h new file mode 100644 index 0000000000..d1b791f7ea --- /dev/null +++ b/src/bun.js/bindings/v8/node.h @@ -0,0 +1,44 @@ +#pragma once + +#include "v8.h" +#include "V8Local.h" +#include "V8Isolate.h" +#include "V8Object.h" +#include "V8Value.h" + +namespace node { + +BUN_EXPORT void AddEnvironmentCleanupHook(v8::Isolate* isolate, + void (*fun)(void* arg), + void* arg); + +BUN_EXPORT void RemoveEnvironmentCleanupHook(v8::Isolate* isolate, + void (*fun)(void* arg), + void* arg); + +typedef void (*addon_register_func)( + v8::Local exports, + v8::Local module, + void* priv); + +typedef void (*addon_context_register_func)( + v8::Local exports, + v8::Local module, + v8::Local context, + void* priv); + +struct node_module { + int nm_version; + unsigned int nm_flags; + void* nm_dso_handle; + const char* nm_filename; + node::addon_register_func nm_register_func; + node::addon_context_register_func nm_context_register_func; + const char* nm_modname; + void* nm_priv; + struct node_module* nm_link; +}; + +extern "C" BUN_EXPORT void node_module_register(void* mod); + +} diff --git a/src/bun.js/bindings/v8/v8.h b/src/bun.js/bindings/v8/v8.h new file mode 100644 index 0000000000..461c653f51 --- /dev/null +++ b/src/bun.js/bindings/v8/v8.h @@ -0,0 +1,16 @@ +#pragma once + +#include "ZigGlobalObject.h" + +#if defined(WIN32) || defined(_WIN32) +#define BUN_EXPORT __declspec(dllexport) +#else +#define BUN_EXPORT JS_EXPORT +#endif + +#define V8_UNIMPLEMENTED() BUN_PANIC("You're using a module which calls a V8 function that Bun does not yet implement. Track progress at https://github.com/oven-sh/bun/issues/4290.") + +extern "C" Zig::GlobalObject* Bun__getDefaultGlobalObject(); + +namespace v8 { +} diff --git a/src/bun.js/bindings/v8/v8_api_internal.cpp b/src/bun.js/bindings/v8/v8_api_internal.cpp new file mode 100644 index 0000000000..800ac92ece --- /dev/null +++ b/src/bun.js/bindings/v8/v8_api_internal.cpp @@ -0,0 +1,13 @@ +#include "v8.h" + +namespace v8 { +namespace api_internal { + +BUN_EXPORT void ToLocalEmpty() +{ + // TODO(@190n) proper error handling + V8_UNIMPLEMENTED(); +} + +} +} diff --git a/src/bun.js/bindings/v8/v8_internal.cpp b/src/bun.js/bindings/v8/v8_internal.cpp new file mode 100644 index 0000000000..4f4ce03da4 --- /dev/null +++ b/src/bun.js/bindings/v8/v8_internal.cpp @@ -0,0 +1,13 @@ +#include "v8_internal.h" + +namespace v8 { +namespace internal { + +Isolate* IsolateFromNeverReadOnlySpaceObject(uintptr_t obj) +{ + V8_UNIMPLEMENTED(); + return nullptr; +} + +} +} diff --git a/src/bun.js/bindings/v8/v8_internal.h b/src/bun.js/bindings/v8/v8_internal.h new file mode 100644 index 0000000000..da9be2b59b --- /dev/null +++ b/src/bun.js/bindings/v8/v8_internal.h @@ -0,0 +1,13 @@ +#pragma once + +#include "v8.h" + +namespace v8 { +namespace internal { + +class Isolate {}; + +BUN_EXPORT Isolate* IsolateFromNeverReadOnlySpaceObject(uintptr_t obj); + +} +} diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index f4846abe1a..0a3bf4b400 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -906,5 +906,12 @@ public: std::unique_ptr m_clientSubspaceForEventListener; std::unique_ptr m_clientSubspaceForEventTarget; std::unique_ptr m_clientSubspaceForEventEmitter; + // todo(@190n) move these up or move these elsewhere + std::unique_ptr m_clientSubspaceForObjectTemplate; + std::unique_ptr m_clientSubspaceForInternalFieldObject; + std::unique_ptr m_clientSubspaceForV8GlobalInternals; + std::unique_ptr m_clientSubspaceForHandleScopeBuffer; + std::unique_ptr m_clientSubspaceForFunctionTemplate; + std::unique_ptr m_clientSubspaceForV8Function; }; } // namespace WebCore diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index e4889d4384..196c93e073 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -910,6 +910,13 @@ public: // std::unique_ptr m_subspaceForDOMFormData; // std::unique_ptr m_subspaceForDOMFormDataIterator; std::unique_ptr m_subspaceForDOMURL; + // todo(@190n) move up + std::unique_ptr m_subspaceForObjectTemplate; + std::unique_ptr m_subspaceForInternalFieldObject; + std::unique_ptr m_subspaceForV8GlobalInternals; + std::unique_ptr m_subspaceForHandleScopeBuffer; + std::unique_ptr m_subspaceForFunctionTemplate; + std::unique_ptr m_subspaceForV8Function; }; } // namespace WebCore diff --git a/src/linker.lds b/src/linker.lds index 9aee7b8227..8d92906c2a 100644 --- a/src/linker.lds +++ b/src/linker.lds @@ -2,6 +2,7 @@ BUN_1.1 { global: napi*; node_api_*; + node_module_register; extern "C++" { v8::*; node::*; diff --git a/src/napi/napi.zig b/src/napi/napi.zig index 30d97267a3..34af015e50 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -1697,18 +1697,109 @@ const V8API = if (!bun.Environment.isWindows) struct { pub extern fn _ZN2v87Isolate17GetCurrentContextEv() *anyopaque; pub extern fn _ZN4node25AddEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_() *anyopaque; pub extern fn _ZN4node28RemoveEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_() *anyopaque; + pub extern fn _ZN2v86Number3NewEPNS_7IsolateEd() *anyopaque; + pub extern fn _ZNK2v86Number5ValueEv() *anyopaque; + pub extern fn _ZN2v86String11NewFromUtf8EPNS_7IsolateEPKcNS_13NewStringTypeEi() *anyopaque; + pub extern fn _ZNK2v86String9WriteUtf8EPNS_7IsolateEPciPii() *anyopaque; + pub extern fn _ZN2v812api_internal12ToLocalEmptyEv() *anyopaque; + pub extern fn _ZNK2v86String6LengthEv() *anyopaque; + pub extern fn _ZN2v88External3NewEPNS_7IsolateEPv() *anyopaque; + pub extern fn _ZNK2v88External5ValueEv() *anyopaque; + pub extern fn _ZN2v86Object3NewEPNS_7IsolateE() *anyopaque; + pub extern fn _ZN2v86Object3SetENS_5LocalINS_7ContextEEENS1_INS_5ValueEEES5_() *anyopaque; + pub extern fn _ZN2v86Object16SetInternalFieldEiNS_5LocalINS_4DataEEE() *anyopaque; + pub extern fn _ZN2v86Object20SlowGetInternalFieldEi() *anyopaque; + pub extern fn _ZN2v811HandleScope12CreateHandleEPNS_8internal7IsolateEm() *anyopaque; + pub extern fn _ZN2v811HandleScopeC1EPNS_7IsolateE() *anyopaque; + pub extern fn _ZN2v811HandleScopeD1Ev() *anyopaque; + pub extern fn _ZN2v811HandleScopeD2Ev() *anyopaque; + pub extern fn _ZN2v816FunctionTemplate11GetFunctionENS_5LocalINS_7ContextEEE() *anyopaque; + pub extern fn _ZN2v816FunctionTemplate3NewEPNS_7IsolateEPFvRKNS_20FunctionCallbackInfoINS_5ValueEEEENS_5LocalIS4_EENSA_INS_9SignatureEEEiNS_19ConstructorBehaviorENS_14SideEffectTypeEPKNS_9CFunctionEttt() *anyopaque; + pub extern fn _ZN2v814ObjectTemplate11NewInstanceENS_5LocalINS_7ContextEEE() *anyopaque; + pub extern fn _ZN2v814ObjectTemplate21SetInternalFieldCountEi() *anyopaque; + pub extern fn _ZNK2v814ObjectTemplate18InternalFieldCountEv() *anyopaque; + pub extern fn _ZN2v814ObjectTemplate3NewEPNS_7IsolateENS_5LocalINS_16FunctionTemplateEEE() *anyopaque; + pub extern fn _ZN2v824EscapableHandleScopeBase10EscapeSlotEPm() *anyopaque; + pub extern fn _ZN2v824EscapableHandleScopeBaseC2EPNS_7IsolateE() *anyopaque; + pub extern fn _ZN2v88internal35IsolateFromNeverReadOnlySpaceObjectEm() *anyopaque; + pub extern fn _ZN2v85Array3NewEPNS_7IsolateEPNS_5LocalINS_5ValueEEEm() *anyopaque; + pub extern fn _ZN2v88Function7SetNameENS_5LocalINS_6StringEEE() *anyopaque; + pub extern fn _ZNK2v85Value9IsBooleanEv() *anyopaque; + pub extern fn _ZNK2v87Boolean5ValueEv() *anyopaque; + pub extern fn _ZNK2v85Value10FullIsTrueEv() *anyopaque; + pub extern fn _ZNK2v85Value11FullIsFalseEv() *anyopaque; + pub extern fn _ZN2v820EscapableHandleScopeC1EPNS_7IsolateE() *anyopaque; + pub extern fn _ZN2v820EscapableHandleScopeC2EPNS_7IsolateE() *anyopaque; + pub extern fn _ZN2v820EscapableHandleScopeD1Ev() *anyopaque; + pub extern fn _ZN2v820EscapableHandleScopeD2Ev() *anyopaque; + pub extern fn _ZNK2v85Value8IsObjectEv() *anyopaque; + pub extern fn _ZNK2v85Value8IsNumberEv() *anyopaque; + pub extern fn _ZNK2v85Value8IsUint32Ev() *anyopaque; + pub extern fn _ZNK2v85Value11Uint32ValueENS_5LocalINS_7ContextEEE() *anyopaque; + pub extern fn _ZNK2v85Value11IsUndefinedEv() *anyopaque; + pub extern fn _ZNK2v85Value6IsNullEv() *anyopaque; + pub extern fn _ZNK2v85Value17IsNullOrUndefinedEv() *anyopaque; + pub extern fn _ZNK2v85Value6IsTrueEv() *anyopaque; + pub extern fn _ZNK2v85Value7IsFalseEv() *anyopaque; + pub extern fn _ZNK2v85Value8IsStringEv() *anyopaque; + pub extern fn _ZN2v87Boolean3NewEPNS_7IsolateEb() *anyopaque; + pub extern fn _ZN2v86Object16GetInternalFieldEi() *anyopaque; } else struct { // MSVC name mangling is different than it is on unix. // To make this easier to deal with, I have provided a script to generate the list of functions. // - // dumpbin .\build\CMakeFiles\bun-debug.dir\src\bun.js\bindings\v8.cpp.obj /symbols | where-object { $_.Contains(' node::') -or $_.Contains(' v8::') } | foreach-object { (($_ -split "\|")[1] -split " ")[1] } | ForEach-Object { "extern fn @`"${_}`"() *anyopaque;" } + // dumpbin .\build\CMakeFiles\bun-debug.dir\src\bun.js\bindings\v8\*.cpp.obj /symbols | where-object { $_.Contains(' node::') -or $_.Contains(' v8::') } | foreach-object { (($_ -split "\|")[1] -split " ")[1] } | ForEach-Object { "extern fn @`"${_}`"() *anyopaque;" } // // Bug @paperdave if you get stuck here pub extern fn @"?TryGetCurrent@Isolate@v8@@SAPEAV12@XZ"() *anyopaque; pub extern fn @"?GetCurrent@Isolate@v8@@SAPEAV12@XZ"() *anyopaque; - pub extern fn @"?GetCurrentContext@Isolate@v8@@QEAA?AV?$Local@VJSGlobalObject@JSC@@@2@XZ"() *anyopaque; + pub extern fn @"?GetCurrentContext@Isolate@v8@@QEAA?AV?$Local@VContext@v8@@@2@XZ"() *anyopaque; pub extern fn @"?AddEnvironmentCleanupHook@node@@YAXPEAVIsolate@v8@@P6AXPEAX@Z1@Z"() *anyopaque; pub extern fn @"?RemoveEnvironmentCleanupHook@node@@YAXPEAVIsolate@v8@@P6AXPEAX@Z1@Z"() *anyopaque; + pub extern fn @"?New@Number@v8@@SA?AV?$Local@VNumber@v8@@@2@PEAVIsolate@2@N@Z"() *anyopaque; + pub extern fn @"?Value@Number@v8@@QEBANXZ"() *anyopaque; + pub extern fn @"?NewFromUtf8@String@v8@@SA?AV?$MaybeLocal@VString@v8@@@2@PEAVIsolate@2@PEBDW4NewStringType@2@H@Z"() *anyopaque; + pub extern fn @"?WriteUtf8@String@v8@@QEBAHPEAVIsolate@2@PEADHPEAHH@Z"() *anyopaque; + pub extern fn @"?ToLocalEmpty@api_internal@v8@@YAXXZ"() *anyopaque; + pub extern fn @"?Length@String@v8@@QEBAHXZ"() *anyopaque; + pub extern fn @"?New@External@v8@@SA?AV?$Local@VExternal@v8@@@2@PEAVIsolate@2@PEAX@Z"() *anyopaque; + pub extern fn @"?Value@External@v8@@QEBAPEAXXZ"() *anyopaque; + pub extern fn @"?New@Object@v8@@SA?AV?$Local@VObject@v8@@@2@PEAVIsolate@2@@Z"() *anyopaque; + pub extern fn @"?Set@Object@v8@@QEAA?AV?$Maybe@_N@2@V?$Local@VContext@v8@@@2@V?$Local@VValue@v8@@@2@1@Z"() *anyopaque; + pub extern fn @"?SetInternalField@Object@v8@@QEAAXHV?$Local@VData@v8@@@2@@Z"() *anyopaque; + pub extern fn @"?SlowGetInternalField@Object@v8@@AEAA?AV?$Local@VData@v8@@@2@H@Z"() *anyopaque; + pub extern fn @"?CreateHandle@HandleScope@v8@@QEAAPEA_KPEAVIsolate@internal@2@_K@Z"() *anyopaque; + pub extern fn @"??0HandleScope@v8@@QEAA@PEAVIsolate@1@@Z"() *anyopaque; + pub extern fn @"??1HandleScope@v8@@QEAA@XZ"() *anyopaque; + pub extern fn @"?GetFunction@FunctionTemplate@v8@@QEAA?AV?$MaybeLocal@VFunction@v8@@@2@V?$Local@VContext@v8@@@2@@Z"() *anyopaque; + pub extern fn @"?New@FunctionTemplate@v8@@SA?AV?$Local@VFunctionTemplate@v8@@@2@PEAVIsolate@2@P6AXAEBV?$FunctionCallbackInfo@VValue@v8@@@2@@ZV?$Local@VValue@v8@@@2@V?$Local@VSignature@v8@@@2@HW4ConstructorBehavior@2@W4SideEffectType@2@PEBVCFunction@2@GGG@Z"() *anyopaque; + pub extern fn @"?NewInstance@ObjectTemplate@v8@@QEAA?AV?$MaybeLocal@VObject@v8@@@2@V?$Local@VContext@v8@@@2@@Z"() *anyopaque; + pub extern fn @"?SetInternalFieldCount@ObjectTemplate@v8@@QEAAXH@Z"() *anyopaque; + pub extern fn @"?InternalFieldCount@ObjectTemplate@v8@@QEBAHXZ"() *anyopaque; + pub extern fn @"?New@ObjectTemplate@v8@@SA?AV?$Local@VObjectTemplate@v8@@@2@PEAVIsolate@2@V?$Local@VFunctionTemplate@v8@@@2@@Z"() *anyopaque; + pub extern fn @"?EscapeSlot@EscapableHandleScopeBase@v8@@IEAAPEA_KPEA_K@Z"() *anyopaque; + pub extern fn @"??0EscapableHandleScopeBase@v8@@QEAA@PEAVIsolate@1@@Z"() *anyopaque; + pub extern fn @"?IsolateFromNeverReadOnlySpaceObject@internal@v8@@YAPEAVIsolate@12@_K@Z"() *anyopaque; + pub extern fn @"?New@Array@v8@@SA?AV?$Local@VArray@v8@@@2@PEAVIsolate@2@PEAV?$Local@VValue@v8@@@2@_K@Z"() *anyopaque; + pub extern fn @"?SetName@Function@v8@@QEAAXV?$Local@VString@v8@@@2@@Z"() *anyopaque; + pub extern fn @"?IsBoolean@Value@v8@@QEBA_NXZ"() *anyopaque; + pub extern fn @"?Value@Boolean@v8@@QEBA_NXZ"() *anyopaque; + pub extern fn @"?FullIsTrue@Value@v8@@AEBA_NXZ"() *anyopaque; + pub extern fn @"?FullIsFalse@Value@v8@@AEBA_NXZ"() *anyopaque; + pub extern fn @"??1EscapableHandleScope@v8@@QEAA@XZ"() *anyopaque; + pub extern fn @"??0EscapableHandleScope@v8@@QEAA@PEAVIsolate@1@@Z"() *anyopaque; + pub extern fn @"?IsObject@Value@v8@@QEBA_NXZ"() *anyopaque; + pub extern fn @"?IsNumber@Value@v8@@QEBA_NXZ"() *anyopaque; + pub extern fn @"?IsUint32@Value@v8@@QEBA_NXZ"() *anyopaque; + pub extern fn @"?Uint32Value@Value@v8@@QEBA?AV?$Maybe@I@2@V?$Local@VContext@v8@@@2@@Z"() *anyopaque; + pub extern fn @"?IsUndefined@Value@v8@@QEBA_NXZ"() *anyopaque; + pub extern fn @"?IsNull@Value@v8@@QEBA_NXZ"() *anyopaque; + pub extern fn @"?IsNullOrUndefined@Value@v8@@QEBA_NXZ"() *anyopaque; + pub extern fn @"?IsTrue@Value@v8@@QEBA_NXZ"() *anyopaque; + pub extern fn @"?IsFalse@Value@v8@@QEBA_NXZ"() *anyopaque; + pub extern fn @"?IsString@Value@v8@@QEBA_NXZ"() *anyopaque; + pub extern fn @"?New@Boolean@v8@@SA?AV?$Local@VBoolean@v8@@@2@PEAVIsolate@2@_N@Z"() *anyopaque; + pub extern fn @"?GetInternalField@Object@v8@@QEAA?AV?$Local@VData@v8@@@2@H@Z"() *anyopaque; }; pub fn fixDeadCodeElimination() void { diff --git a/src/symbols.def b/src/symbols.def index 1aa19a0cbf..78ff9cd631 100644 --- a/src/symbols.def +++ b/src/symbols.def @@ -567,3 +567,52 @@ EXPORTS napi_is_detached_arraybuffer napi_create_external_buffer napi_fatal_exception + ?TryGetCurrent@Isolate@v8@@SAPEAV12@XZ + ?GetCurrent@Isolate@v8@@SAPEAV12@XZ + ?GetCurrentContext@Isolate@v8@@QEAA?AV?$Local@VContext@v8@@@2@XZ + ?AddEnvironmentCleanupHook@node@@YAXPEAVIsolate@v8@@P6AXPEAX@Z1@Z + ?RemoveEnvironmentCleanupHook@node@@YAXPEAVIsolate@v8@@P6AXPEAX@Z1@Z + ?New@Number@v8@@SA?AV?$Local@VNumber@v8@@@2@PEAVIsolate@2@N@Z + ?Value@Number@v8@@QEBANXZ + ?NewFromUtf8@String@v8@@SA?AV?$MaybeLocal@VString@v8@@@2@PEAVIsolate@2@PEBDW4NewStringType@2@H@Z + ?WriteUtf8@String@v8@@QEBAHPEAVIsolate@2@PEADHPEAHH@Z + ?ToLocalEmpty@api_internal@v8@@YAXXZ + ?Length@String@v8@@QEBAHXZ + ?New@External@v8@@SA?AV?$Local@VExternal@v8@@@2@PEAVIsolate@2@PEAX@Z + ?Value@External@v8@@QEBAPEAXXZ + ?New@Object@v8@@SA?AV?$Local@VObject@v8@@@2@PEAVIsolate@2@@Z + ?Set@Object@v8@@QEAA?AV?$Maybe@_N@2@V?$Local@VContext@v8@@@2@V?$Local@VValue@v8@@@2@1@Z + ?SetInternalField@Object@v8@@QEAAXHV?$Local@VData@v8@@@2@@Z + ?SlowGetInternalField@Object@v8@@AEAA?AV?$Local@VData@v8@@@2@H@Z + ?CreateHandle@HandleScope@v8@@QEAAPEA_KPEAVIsolate@internal@2@_K@Z + ??0HandleScope@v8@@QEAA@PEAVIsolate@1@@Z + ??1HandleScope@v8@@QEAA@XZ + ?GetFunction@FunctionTemplate@v8@@QEAA?AV?$MaybeLocal@VFunction@v8@@@2@V?$Local@VContext@v8@@@2@@Z + ?New@FunctionTemplate@v8@@SA?AV?$Local@VFunctionTemplate@v8@@@2@PEAVIsolate@2@P6AXAEBV?$FunctionCallbackInfo@VValue@v8@@@2@@ZV?$Local@VValue@v8@@@2@V?$Local@VSignature@v8@@@2@HW4ConstructorBehavior@2@W4SideEffectType@2@PEBVCFunction@2@GGG@Z + ?NewInstance@ObjectTemplate@v8@@QEAA?AV?$MaybeLocal@VObject@v8@@@2@V?$Local@VContext@v8@@@2@@Z + ?SetInternalFieldCount@ObjectTemplate@v8@@QEAAXH@Z + ?InternalFieldCount@ObjectTemplate@v8@@QEBAHXZ + ?New@ObjectTemplate@v8@@SA?AV?$Local@VObjectTemplate@v8@@@2@PEAVIsolate@2@V?$Local@VFunctionTemplate@v8@@@2@@Z + ?EscapeSlot@EscapableHandleScopeBase@v8@@IEAAPEA_KPEA_K@Z + ??0EscapableHandleScopeBase@v8@@QEAA@PEAVIsolate@1@@Z + ?IsolateFromNeverReadOnlySpaceObject@internal@v8@@YAPEAVIsolate@12@_K@Z + ?New@Array@v8@@SA?AV?$Local@VArray@v8@@@2@PEAVIsolate@2@PEAV?$Local@VValue@v8@@@2@_K@Z + ?SetName@Function@v8@@QEAAXV?$Local@VString@v8@@@2@@Z + ?IsBoolean@Value@v8@@QEBA_NXZ + ?Value@Boolean@v8@@QEBA_NXZ + ?FullIsTrue@Value@v8@@AEBA_NXZ + ?FullIsFalse@Value@v8@@AEBA_NXZ + ??1EscapableHandleScope@v8@@QEAA@XZ + ??0EscapableHandleScope@v8@@QEAA@PEAVIsolate@1@@Z + ?IsObject@Value@v8@@QEBA_NXZ + ?IsNumber@Value@v8@@QEBA_NXZ + ?IsUint32@Value@v8@@QEBA_NXZ + ?Uint32Value@Value@v8@@QEBA?AV?$Maybe@I@2@V?$Local@VContext@v8@@@2@@Z + ?IsUndefined@Value@v8@@QEBA_NXZ + ?IsNull@Value@v8@@QEBA_NXZ + ?IsNullOrUndefined@Value@v8@@QEBA_NXZ + ?IsTrue@Value@v8@@QEBA_NXZ + ?IsFalse@Value@v8@@QEBA_NXZ + ?IsString@Value@v8@@QEBA_NXZ + ?New@Boolean@v8@@SA?AV?$Local@VBoolean@v8@@@2@PEAVIsolate@2@_N@Z + ?GetInternalField@Object@v8@@QEAA?AV?$Local@VData@v8@@@2@H@Z diff --git a/src/symbols.dyn b/src/symbols.dyn index 2d16df145d..77f1f872f8 100644 --- a/src/symbols.dyn +++ b/src/symbols.dyn @@ -154,5 +154,53 @@ __ZN2v87Isolate17GetCurrentContextEv; __ZN4node25AddEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_; __ZN4node28RemoveEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_; + __ZN2v86Number3NewEPNS_7IsolateEd; + __ZNK2v86Number5ValueEv; + __ZN2v86String11NewFromUtf8EPNS_7IsolateEPKcNS_13NewStringTypeEi; + __ZNK2v86String9WriteUtf8EPNS_7IsolateEPciPii; + __ZN2v812api_internal12ToLocalEmptyEv; + __ZNK2v86String6LengthEv; + __ZN2v88External3NewEPNS_7IsolateEPv; + __ZNK2v88External5ValueEv; + __ZN2v86Object3NewEPNS_7IsolateE; + __ZN2v86Object3SetENS_5LocalINS_7ContextEEENS1_INS_5ValueEEES5_; + __ZN2v86Object16SetInternalFieldEiNS_5LocalINS_4DataEEE; + __ZN2v86Object20SlowGetInternalFieldEi; + __ZN2v811HandleScope12CreateHandleEPNS_8internal7IsolateEm; + __ZN2v811HandleScopeC1EPNS_7IsolateE; + __ZN2v811HandleScopeD1Ev; + __ZN2v811HandleScopeD2Ev; + __ZN2v816FunctionTemplate11GetFunctionENS_5LocalINS_7ContextEEE; + __ZN2v816FunctionTemplate3NewEPNS_7IsolateEPFvRKNS_20FunctionCallbackInfoINS_5ValueEEEENS_5LocalIS4_EENSA_INS_9SignatureEEEiNS_19ConstructorBehaviorENS_14SideEffectTypeEPKNS_9CFunctionEttt; + __ZN2v814ObjectTemplate11NewInstanceENS_5LocalINS_7ContextEEE; + __ZN2v814ObjectTemplate21SetInternalFieldCountEi; + __ZNK2v814ObjectTemplate18InternalFieldCountEv; + __ZN2v814ObjectTemplate3NewEPNS_7IsolateENS_5LocalINS_16FunctionTemplateEEE; + __ZN2v824EscapableHandleScopeBase10EscapeSlotEPm; + __ZN2v824EscapableHandleScopeBaseC2EPNS_7IsolateE; + __ZN2v88internal35IsolateFromNeverReadOnlySpaceObjectEm; + _node_module_register; __ZN3JSC9CallFrame13describeFrameEv; -}; \ No newline at end of file + __ZN2v85Array3NewEPNS_7IsolateEPNS_5LocalINS_5ValueEEEm; + __ZN2v88Function7SetNameENS_5LocalINS_6StringEEE; + __ZNK2v85Value9IsBooleanEv; + __ZNK2v87Boolean5ValueEv; + __ZNK2v85Value10FullIsTrueEv; + __ZNK2v85Value11FullIsFalseEv; + __ZN2v820EscapableHandleScopeC1EPNS_7IsolateE; + __ZN2v820EscapableHandleScopeC2EPNS_7IsolateE; + __ZN2v820EscapableHandleScopeD1Ev; + __ZN2v820EscapableHandleScopeD2Ev; + __ZNK2v85Value8IsObjectEv; + __ZNK2v85Value8IsNumberEv; + __ZNK2v85Value8IsUint32Ev; + __ZNK2v85Value11Uint32ValueENS_5LocalINS_7ContextEEE; + __ZNK2v85Value11IsUndefinedEv; + __ZNK2v85Value6IsNullEv; + __ZNK2v85Value17IsNullOrUndefinedEv; + __ZNK2v85Value6IsTrueEv; + __ZNK2v85Value7IsFalseEv; + __ZNK2v85Value8IsStringEv; + __ZN2v87Boolean3NewEPNS_7IsolateEb; + __ZN2v86Object16GetInternalFieldEi; +}; diff --git a/src/symbols.txt b/src/symbols.txt index 2e24afa1c4..54ef66c28c 100644 --- a/src/symbols.txt +++ b/src/symbols.txt @@ -153,4 +153,52 @@ __ZN2v87Isolate13TryGetCurrentEv __ZN2v87Isolate17GetCurrentContextEv __ZN4node25AddEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_ __ZN4node28RemoveEnvironmentCleanupHookEPN2v87IsolateEPFvPvES3_ -__ZN3JSC9CallFrame13describeFrameEv \ No newline at end of file +__ZN2v86Number3NewEPNS_7IsolateEd +__ZNK2v86Number5ValueEv +__ZN2v86String11NewFromUtf8EPNS_7IsolateEPKcNS_13NewStringTypeEi +__ZNK2v86String9WriteUtf8EPNS_7IsolateEPciPii +__ZN2v812api_internal12ToLocalEmptyEv +__ZNK2v86String6LengthEv +__ZN2v88External3NewEPNS_7IsolateEPv +__ZNK2v88External5ValueEv +__ZN2v86Object3NewEPNS_7IsolateE +__ZN2v86Object3SetENS_5LocalINS_7ContextEEENS1_INS_5ValueEEES5_ +__ZN2v86Object16SetInternalFieldEiNS_5LocalINS_4DataEEE +__ZN2v86Object20SlowGetInternalFieldEi +__ZN2v811HandleScope12CreateHandleEPNS_8internal7IsolateEm +__ZN2v811HandleScopeC1EPNS_7IsolateE +__ZN2v811HandleScopeD1Ev +__ZN2v811HandleScopeD2Ev +__ZN2v816FunctionTemplate11GetFunctionENS_5LocalINS_7ContextEEE +__ZN2v816FunctionTemplate3NewEPNS_7IsolateEPFvRKNS_20FunctionCallbackInfoINS_5ValueEEEENS_5LocalIS4_EENSA_INS_9SignatureEEEiNS_19ConstructorBehaviorENS_14SideEffectTypeEPKNS_9CFunctionEttt +__ZN2v814ObjectTemplate11NewInstanceENS_5LocalINS_7ContextEEE +__ZN2v814ObjectTemplate21SetInternalFieldCountEi +__ZNK2v814ObjectTemplate18InternalFieldCountEv +__ZN2v814ObjectTemplate3NewEPNS_7IsolateENS_5LocalINS_16FunctionTemplateEEE +__ZN2v824EscapableHandleScopeBase10EscapeSlotEPm +__ZN2v824EscapableHandleScopeBaseC2EPNS_7IsolateE +__ZN2v88internal35IsolateFromNeverReadOnlySpaceObjectEm +_node_module_register +__ZN3JSC9CallFrame13describeFrameEv +__ZN2v85Array3NewEPNS_7IsolateEPNS_5LocalINS_5ValueEEEm +__ZN2v88Function7SetNameENS_5LocalINS_6StringEEE +__ZNK2v85Value9IsBooleanEv +__ZNK2v87Boolean5ValueEv +__ZNK2v85Value10FullIsTrueEv +__ZNK2v85Value11FullIsFalseEv +__ZN2v820EscapableHandleScopeC1EPNS_7IsolateE +__ZN2v820EscapableHandleScopeC2EPNS_7IsolateE +__ZN2v820EscapableHandleScopeD1Ev +__ZN2v820EscapableHandleScopeD2Ev +__ZNK2v85Value8IsObjectEv +__ZNK2v85Value8IsNumberEv +__ZNK2v85Value8IsUint32Ev +__ZNK2v85Value11Uint32ValueENS_5LocalINS_7ContextEEE +__ZNK2v85Value11IsUndefinedEv +__ZNK2v85Value6IsNullEv +__ZNK2v85Value17IsNullOrUndefinedEv +__ZNK2v85Value6IsTrueEv +__ZNK2v85Value7IsFalseEv +__ZNK2v85Value8IsStringEv +__ZN2v87Boolean3NewEPNS_7IsolateEb +__ZN2v86Object16GetInternalFieldEi diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js index 5a1375dcef..57f173269a 100644 --- a/test/js/node/process/process.test.js +++ b/test/js/node/process/process.test.js @@ -277,6 +277,7 @@ const versions = existsSync(generated_versions_list); it("process.config", () => { expect(process.config).toEqual({ variables: { + enable_lto: false, v8_enable_i8n_support: 1, }, target_defaults: {}, diff --git a/test/napi/napi-app/main.cpp b/test/napi/napi-app/main.cpp index 61f7c564c7..0975f48868 100644 --- a/test/napi/napi-app/main.cpp +++ b/test/napi/napi-app/main.cpp @@ -1,7 +1,10 @@ #include +#include #include #include +#include +#include #include @@ -11,6 +14,15 @@ napi_value fail(napi_env env, const char *msg) { return result; } +napi_value fail_fmt(napi_env env, const char *fmt, ...) { + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + return fail(env, buf); +} + napi_value ok(napi_env env) { napi_value result; napi_get_undefined(env, &result); @@ -155,11 +167,9 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports1) { exports.Set("test_issue_7685", Napi::Function::New(env, test_issue_7685)); exports.Set("test_issue_11949", Napi::Function::New(env, test_issue_11949)); - exports.Set( "test_napi_get_value_string_utf8_with_buffer", Napi::Function::New(env, test_napi_get_value_string_utf8_with_buffer)); - exports.Set( "test_napi_threadsafe_function_does_not_hang_after_finalize", Napi::Function::New( diff --git a/test/napi/napi.test.ts b/test/napi/napi.test.ts index 90e800446b..18298c0d55 100644 --- a/test/napi/napi.test.ts +++ b/test/napi/napi.test.ts @@ -24,6 +24,7 @@ describe("napi", () => { checkSameOutput("test_issue_7685", args); }); }); + describe("issue_11949", () => { it("napi_call_threadsafe_function should accept null", () => { const result = checkSameOutput("test_issue_11949", []); diff --git a/test/v8/.gitignore b/test/v8/.gitignore new file mode 100644 index 0000000000..883c5ba3c1 --- /dev/null +++ b/test/v8/.gitignore @@ -0,0 +1,2 @@ +v8-module-node +v8-module-debug diff --git a/test/v8/bad-modules/.gitignore b/test/v8/bad-modules/.gitignore new file mode 100644 index 0000000000..b6d1c481cd --- /dev/null +++ b/test/v8/bad-modules/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +compile_commands.json diff --git a/test/v8/bad-modules/binding.gyp b/test/v8/bad-modules/binding.gyp new file mode 100644 index 0000000000..03fd1698ff --- /dev/null +++ b/test/v8/bad-modules/binding.gyp @@ -0,0 +1,12 @@ +{ + "targets": [ + { + "target_name": "mismatched_abi_version", + "sources": ["mismatched_abi_version.cpp"], + }, + { + "target_name": "no_entrypoint", + "sources": ["no_entrypoint.cpp"], + } + ] +} diff --git a/test/v8/bad-modules/bun.lockb b/test/v8/bad-modules/bun.lockb new file mode 100755 index 0000000000..8f21ad1141 Binary files /dev/null and b/test/v8/bad-modules/bun.lockb differ diff --git a/test/v8/bad-modules/mismatched_abi_version.cpp b/test/v8/bad-modules/mismatched_abi_version.cpp new file mode 100644 index 0000000000..46f1a5f124 --- /dev/null +++ b/test/v8/bad-modules/mismatched_abi_version.cpp @@ -0,0 +1,26 @@ +#include + +void init(v8::Local exports, v8::Local module, + void *priv) { + // this should not even get called + abort(); +} + +extern "C" { +static node::node_module _module = { + // bun expects 127 + 42, // nm_version + 0, // nm_flags + nullptr, // nm_dso_handle + "mismatched_abi_version.cpp", // nm_filename + init, // nm_register_func + nullptr, // nm_context_register_func + "mismatched_abi_version", // nm_modname + nullptr, // nm_priv + nullptr, // nm_link +}; + +NODE_C_CTOR(_register_mismatched_abi_version) { + node_module_register(&_module); +} +} diff --git a/test/v8/bad-modules/no_entrypoint.cpp b/test/v8/bad-modules/no_entrypoint.cpp new file mode 100644 index 0000000000..710ddf2ef4 --- /dev/null +++ b/test/v8/bad-modules/no_entrypoint.cpp @@ -0,0 +1,17 @@ +#include + +extern "C" { +static node::node_module _module = { + 127, // nm_version + 0, // nm_flags + nullptr, // nm_dso_handle + "no_entrypoint.cpp", // nm_filename + nullptr, // nm_register_func + nullptr, // nm_context_register_func + "no_entrypoint", // nm_modname + nullptr, // nm_priv + nullptr, // nm_link +}; + +NODE_C_CTOR(_register_no_entrypoint) { node_module_register(&_module); } +} diff --git a/test/v8/bad-modules/package.json b/test/v8/bad-modules/package.json new file mode 100644 index 0000000000..3854193b8a --- /dev/null +++ b/test/v8/bad-modules/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "node-gyp": "~10.1.0" + } +} diff --git a/test/v8/v8-module/.gitignore b/test/v8/v8-module/.gitignore new file mode 100644 index 0000000000..b6d1c481cd --- /dev/null +++ b/test/v8/v8-module/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +compile_commands.json diff --git a/test/v8/v8-module/binding.gyp b/test/v8/v8-module/binding.gyp new file mode 100644 index 0000000000..b191695ab9 --- /dev/null +++ b/test/v8/v8-module/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "v8tests", + "sources": ["main.cpp"] + } + ] +} diff --git a/test/v8/v8-module/bun.lockb b/test/v8/v8-module/bun.lockb new file mode 100755 index 0000000000..8f21ad1141 Binary files /dev/null and b/test/v8/v8-module/bun.lockb differ diff --git a/test/v8/v8-module/main.cpp b/test/v8/v8-module/main.cpp new file mode 100644 index 0000000000..99154ade89 --- /dev/null +++ b/test/v8/v8-module/main.cpp @@ -0,0 +1,458 @@ +#include + +#include +#include + +using namespace v8; + +namespace v8tests { + +enum class ValueKind : uint16_t { + Undefined = 1 << 0, + Null = 1 << 1, + NullOrUndefined = 1 << 2, + True = 1 << 3, + False = 1 << 4, + Boolean = 1 << 5, + String = 1 << 6, + Object = 1 << 7, + Number = 1 << 8, +}; + +static bool check_value_kind(Local value, ValueKind kind) { + uint16_t matched_kinds = 0; + if (value->IsUndefined()) { + matched_kinds |= static_cast(ValueKind::Undefined); + } + if (value->IsNull()) { + matched_kinds |= static_cast(ValueKind::Null); + } + if (value->IsNullOrUndefined()) { + matched_kinds |= static_cast(ValueKind::NullOrUndefined); + } + if (value->IsTrue()) { + matched_kinds |= static_cast(ValueKind::True); + } + if (value->IsFalse()) { + matched_kinds |= static_cast(ValueKind::False); + } + if (value->IsBoolean()) { + matched_kinds |= static_cast(ValueKind::Boolean); + } + if (value->IsString()) { + matched_kinds |= static_cast(ValueKind::String); + } + if (value->IsObject()) { + matched_kinds |= static_cast(ValueKind::Object); + } + if (value->IsNumber()) { + matched_kinds |= static_cast(ValueKind::Number); + } + + switch (kind) { + case ValueKind::Undefined: + return matched_kinds == (static_cast(ValueKind::Undefined) | + static_cast(ValueKind::NullOrUndefined)); + case ValueKind::Null: + return matched_kinds == (static_cast(ValueKind::Null) | + static_cast(ValueKind::NullOrUndefined)); + case ValueKind::True: + return matched_kinds == (static_cast(ValueKind::True) | + static_cast(ValueKind::Boolean)); + case ValueKind::False: + return matched_kinds == (static_cast(ValueKind::False) | + static_cast(ValueKind::Boolean)); + case ValueKind::String: + return matched_kinds == static_cast(ValueKind::String); + case ValueKind::Object: + return matched_kinds == static_cast(ValueKind::Object); + case ValueKind::Number: + return matched_kinds == static_cast(ValueKind::Number); + case ValueKind::NullOrUndefined: + return (matched_kinds == + (static_cast(ValueKind::Undefined) | + static_cast(ValueKind::NullOrUndefined))) || + ((static_cast(ValueKind::Null) | + static_cast(ValueKind::NullOrUndefined))); + case ValueKind::Boolean: + return (matched_kinds == (static_cast(ValueKind::True) | + static_cast(ValueKind::Boolean))) || + (matched_kinds == (static_cast(ValueKind::False) | + static_cast(ValueKind::Boolean))); + } + return false; +} + +void fail(const FunctionCallbackInfo &info, const char *fmt, ...) { + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + Local message = + String::NewFromUtf8(info.GetIsolate(), buf).ToLocalChecked(); + info.GetReturnValue().Set(message); +} + +void ok(const FunctionCallbackInfo &args) { + args.GetReturnValue().Set(Undefined(args.GetIsolate())); +} + +void test_v8_native_call(const FunctionCallbackInfo &info) { + Isolate *isolate = info.GetIsolate(); + Local undefined = Undefined(isolate); + info.GetReturnValue().Set(undefined); +} + +void test_v8_primitives(const FunctionCallbackInfo &info) { + Isolate *isolate = info.GetIsolate(); + + Local v8_undefined = Undefined(isolate); + if (!check_value_kind(v8_undefined, ValueKind::Undefined)) { + return fail(info, "undefined is not undefined"); + } + + Local v8_null = Null(isolate); + if (!check_value_kind(v8_null, ValueKind::Null)) { + return fail(info, "null is not null"); + } + + Local v8_true = Boolean::New(isolate, true); + if (!check_value_kind(v8_true, ValueKind::True)) { + return fail(info, "true is not true"); + } + + Local v8_false = Boolean::New(isolate, false); + if (!check_value_kind(v8_false, ValueKind::False)) { + return fail(info, "false is not false"); + } + + return ok(info); +} + +static void perform_number_test(const FunctionCallbackInfo &info, + double number) { + Isolate *isolate = info.GetIsolate(); + + Local v8_number = Number::New(isolate, number); + if (v8_number->Value() != number) { + return fail(info, "wrong v8 number value: expected %f got %f", number, + v8_number->Value()); + } + if (!check_value_kind(v8_number, ValueKind::Number)) { + return fail(info, "number is not a number"); + } + + return ok(info); +} + +void test_v8_number_int(const FunctionCallbackInfo &info) { + perform_number_test(info, 123.0); +} + +void test_v8_number_large_int(const FunctionCallbackInfo &info) { + // 2^33 + perform_number_test(info, 8589934592.0); +} + +void test_v8_number_fraction(const FunctionCallbackInfo &info) { + perform_number_test(info, 2.5); +} + +static bool perform_string_test(const FunctionCallbackInfo &info, + const char *c_string, int utf_16_code_units, + int encoded_utf_8_length, + const char *encoded_utf_8_data) { + Isolate *isolate = info.GetIsolate(); + char buf[256] = {0}; + int retval; + int nchars; + + Local v8_string = + String::NewFromUtf8(isolate, c_string).ToLocalChecked(); + + if (!check_value_kind(v8_string, ValueKind::String)) { + fail(info, "string is not a string"); + return false; + } + + if (v8_string->Length() != utf_16_code_units) { + fail(info, "String::Length return: expected %d got %d", utf_16_code_units, + v8_string->Length()); + return false; + } + + if ((retval = v8_string->WriteUtf8(isolate, buf, sizeof buf, &nchars)) != + encoded_utf_8_length + 1) { + fail(info, "String::WriteUtf8 return: expected %d got %d", + encoded_utf_8_length + 1, retval); + return false; + } + if (nchars != utf_16_code_units) { + fail(info, + "String::WriteUtf8 set nchars to wrong value: expected %d got %d", + utf_16_code_units, nchars); + return false; + } + // cmp including terminator + if (memcmp(buf, encoded_utf_8_data, encoded_utf_8_length + 1) != 0) { + fail(info, + "String::WriteUtf8 stored wrong data in buffer: expected %s got %s", + c_string, buf); + return false; + } + + // try with assuming the buffer is large enough + if ((retval = v8_string->WriteUtf8(isolate, buf, -1, &nchars)) != + encoded_utf_8_length + 1) { + fail(info, "String::WriteUtf8 return: expected %d got %d", + encoded_utf_8_length + 1, retval); + return false; + } + if (nchars != utf_16_code_units) { + fail(info, + "String::WriteUtf8 set nchars to wrong value: expected %d got %d", + utf_16_code_units, nchars); + return false; + } + // cmp including terminator + if (memcmp(buf, encoded_utf_8_data, encoded_utf_8_length + 1) != 0) { + fail(info, + "String::WriteUtf8 stored wrong data in buffer: expected %s got %s", + c_string, buf); + return false; + } + + // try with ignoring nchars (it should not try to store anything in a nullptr) + if ((retval = v8_string->WriteUtf8(isolate, buf, sizeof buf, nullptr)) != + encoded_utf_8_length + 1) { + fail(info, "String::WriteUtf8 return: expected %d got %d", + encoded_utf_8_length + 1, retval); + return false; + } + // cmp including terminator + if (memcmp(buf, encoded_utf_8_data, encoded_utf_8_length + 1) != 0) { + fail(info, + "String::WriteUtf8 stored wrong data in buffer: expected %s got %s", + c_string, buf); + return false; + } + + ok(info); + return true; +} + +void test_v8_string_ascii(const FunctionCallbackInfo &info) { + if (!perform_string_test(info, "hello world", 11, 11, "hello world")) { + // if perform_string_test failed, don't replace the return value with + // success in the below truncated test + return; + } + + // try with a length shorter than the string + Isolate *isolate = info.GetIsolate(); + Local v8_string = + String::NewFromUtf8(info.GetIsolate(), "hello world").ToLocalChecked(); + char buf[256]; + memset(buf, 0xaa, sizeof buf); + int retval; + int nchars; + if ((retval = v8_string->WriteUtf8(isolate, buf, 5, &nchars)) != 5) { + return fail(info, "String::WriteUtf8 return: expected 5 got %d", retval); + } + if (nchars != 5) { + return fail( + info, "String::WriteUtf8 set nchars to wrong value: expected 5 got %d", + nchars); + } + // check it did not write a terminator + if (memcmp(buf, "hello\xaa", 6) != 0) { + return fail(info, + "String::WriteUtf8 stored wrong data in buffer: expected " + "hello\\xaa got %s", + buf); + } +} + +void test_v8_string_utf8(const FunctionCallbackInfo &info) { + const unsigned char trans_flag_unsigned[] = {240, 159, 143, 179, 239, 184, + 143, 226, 128, 141, 226, 154, + 167, 239, 184, 143, 0}; + const char *trans_flag = reinterpret_cast(trans_flag_unsigned); + perform_string_test(info, trans_flag, 6, 16, trans_flag); +} + +void test_v8_string_invalid_utf8(const FunctionCallbackInfo &info) { + const unsigned char mixed_sequence_unsigned[] = {'o', 'h', ' ', 0xc0, 'n', + 'o', 0xc2, '!', 0xf5, 0}; + const char *mixed_sequence = + reinterpret_cast(mixed_sequence_unsigned); + const unsigned char replaced_sequence_unsigned[] = { + 'o', 'h', ' ', 0xef, 0xbf, 0xbd, 'n', 'o', + 0xef, 0xbf, 0xbd, '!', 0xef, 0xbf, 0xbd, 0}; + const char *replaced_sequence = + reinterpret_cast(replaced_sequence_unsigned); + perform_string_test(info, mixed_sequence, 9, 15, replaced_sequence); +} + +void test_v8_external(const FunctionCallbackInfo &info) { + Isolate *isolate = info.GetIsolate(); + int x = 5; + Local external = External::New(isolate, &x); + if (external->Value() != &x) { + return fail(info, + "External::Value() returned wrong pointer: expected %p got %p", + &x, external->Value()); + } + return ok(info); +} + +void test_v8_object(const FunctionCallbackInfo &info) { + Isolate *isolate = info.GetIsolate(); + + Local obj = Object::New(isolate); + auto key = String::NewFromUtf8(isolate, "key").ToLocalChecked(); + auto val = Number::New(isolate, 5.0); + Maybe retval = Nothing(); + if ((retval = obj->Set(isolate->GetCurrentContext(), key, val)) != + Just(true)) { + return fail(info, "Object::Set wrong return: expected Just(true), got %s", + retval.IsNothing() ? "Nothing" : "Just(false)"); + } + + return ok(info); +} + +static std::string describe(Isolate *isolate, Local value) { + if (value->IsUndefined()) { + return "undefined"; + } else if (value->IsNull()) { + return "null"; + } else if (value->IsTrue()) { + return "true"; + } else if (value->IsFalse()) { + return "false"; + } else if (value->IsString()) { + char buf[1024] = {0}; + value.As()->WriteUtf8(isolate, buf, sizeof(buf) - 1); + std::string result = "\""; + result += buf; + result += "\""; + return result; + } else if (value->IsObject()) { + return "[object Object]"; + } else if (value->IsNumber()) { + return std::to_string(value.As()->Value()); + } else { + return "unknown"; + } +} + +void test_v8_array_new(const FunctionCallbackInfo &info) { + Isolate *isolate = info.GetIsolate(); + + Local vals[5] = { + Number::New(isolate, 50.0), + String::NewFromUtf8(isolate, "meow").ToLocalChecked(), + Number::New(isolate, 8.5), + Null(isolate), + Boolean::New(isolate, true), + }; + Local v8_array = + Array::New(isolate, vals, sizeof(vals) / sizeof(Local)); + + if (v8_array->Length() != 5) { + return fail(info, "Array::Length wrong return: expected 5, got %" PRIu32, + v8_array->Length()); + } + + for (uint32_t i = 0; i < 5; i++) { + Local array_value = + v8_array->Get(isolate->GetCurrentContext(), i).ToLocalChecked(); + if (!array_value->StrictEquals(vals[i])) { + return fail(info, "array has wrong value at index %" PRIu32 ": %s", i, + describe(isolate, array_value).c_str()); + } + } + + return ok(info); +} + +void test_v8_object_template(const FunctionCallbackInfo &info) { + Isolate *isolate = info.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + Local obj_template = ObjectTemplate::New(isolate); + obj_template->SetInternalFieldCount(2); + if (obj_template->InternalFieldCount() != 2) { + return fail(info, + "ObjectTemplate did not remember internal field count: " + "expected 2, got %d", + obj_template->InternalFieldCount()); + } + + Local obj1 = obj_template->NewInstance(context).ToLocalChecked(); + obj1->SetInternalField(0, Number::New(isolate, 3.0)); + obj1->SetInternalField(1, Number::New(isolate, 4.0)); + + Local obj2 = obj_template->NewInstance(context).ToLocalChecked(); + obj2->SetInternalField(0, Number::New(isolate, 5.0)); + obj2->SetInternalField(1, Number::New(isolate, 6.0)); + + double value = obj1->GetInternalField(0).As()->Value(); + if (value != 3.0) { + return fail(info, + "obj1 internal field 0 has wrong value: expected 3.0, got %f", + value); + } + value = obj1->GetInternalField(1).As()->Value(); + if (value != 4.0) { + return fail(info, + "obj1 internal field 1 has wrong value: expected 4.0, got %f", + value); + } + value = obj2->GetInternalField(0).As()->Value(); + if (value != 5.0) { + return fail(info, + "obj2 internal field 0 has wrong value: expected 5.0, got %f", + value); + } + value = obj2->GetInternalField(1).As()->Value(); + if (value != 6.0) { + return fail(info, + "obj2 internal field 1 has wrong value: expected 6.0, got %f", + value); + } +} + +void print_values_from_js(const FunctionCallbackInfo &info) { + Isolate *isolate = info.GetIsolate(); + printf("%d arguments\n", info.Length()); + printf("this = %s\n", describe(isolate, info.This()).c_str()); + for (int i = 0; i < info.Length(); i++) { + printf("argument %d = %s\n", i, describe(isolate, info[i]).c_str()); + } + return ok(info); +} + +void initialize(Local exports) { + NODE_SET_METHOD(exports, "test_v8_native_call", test_v8_native_call); + NODE_SET_METHOD(exports, "test_v8_primitives", test_v8_primitives); + NODE_SET_METHOD(exports, "test_v8_number_int", test_v8_number_int); + NODE_SET_METHOD(exports, "test_v8_number_large_int", + test_v8_number_large_int); + NODE_SET_METHOD(exports, "test_v8_number_fraction", test_v8_number_fraction); + NODE_SET_METHOD(exports, "test_v8_string_ascii", test_v8_string_ascii); + NODE_SET_METHOD(exports, "test_v8_string_utf8", test_v8_string_utf8); + NODE_SET_METHOD(exports, "test_v8_string_invalid_utf8", + test_v8_string_invalid_utf8); + NODE_SET_METHOD(exports, "test_v8_external", test_v8_external); + NODE_SET_METHOD(exports, "test_v8_object", test_v8_object); + NODE_SET_METHOD(exports, "test_v8_array_new", test_v8_array_new); + NODE_SET_METHOD(exports, "test_v8_object_template", test_v8_object_template); + NODE_SET_METHOD(exports, "print_values_from_js", print_values_from_js); +} + +NODE_MODULE(NODE_GYP_MODULE_NAME, initialize) + +} // namespace v8tests diff --git a/test/v8/v8-module/main.js b/test/v8/v8-module/main.js new file mode 100644 index 0000000000..d7a252d3ff --- /dev/null +++ b/test/v8/v8-module/main.js @@ -0,0 +1,18 @@ +// usage: bun/node main.js [JSON array of arguments] [JSON `this` value] [debug] + +const buildMode = process.argv[5]; + +const tests = require(`./build/${buildMode === "debug" ? "Debug" : "Release"}/v8tests`); + +const testName = process.argv[2]; +const args = JSON.parse(process.argv[3] ?? "[]"); +const thisValue = JSON.parse(process.argv[4] ?? "null"); + +const fn = tests[testName]; +if (typeof fn !== "function") { + throw new Error("Unknown test:", testName); +} +const result = fn.apply(thisValue, args); +if (result) { + throw new Error(result); +} diff --git a/test/v8/v8-module/package.json b/test/v8/v8-module/package.json new file mode 100644 index 0000000000..3854193b8a --- /dev/null +++ b/test/v8/v8-module/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "node-gyp": "~10.1.0" + } +} diff --git a/test/v8/v8.test.ts b/test/v8/v8.test.ts new file mode 100644 index 0000000000..b059e97762 --- /dev/null +++ b/test/v8/v8.test.ts @@ -0,0 +1,247 @@ +import { it, expect, test, beforeAll, describe, afterAll } from "bun:test"; +import { bunExe, bunEnv } from "harness"; +import { spawnSync } from "bun"; +import { join } from "path"; +import fs from "node:fs"; +import assert from "node:assert"; + +// clang-cl does not work on Windows with node-gyp 10.2.0, so we should not let that affect the +// test environment +delete bunEnv.CC; +delete bunEnv.CXX; +if (process.platform == "darwin") { + bunEnv.CXXFLAGS ??= ""; + bunEnv.CXXFLAGS += "-std=gnu++17"; +} +// https://github.com/isaacs/node-tar/blob/bef7b1e4ffab822681fea2a9b22187192ed14717/lib/get-write-flag.js +// prevent node-tar from using UV_FS_O_FILEMAP +if (process.platform == "win32") { + bunEnv.__FAKE_PLATFORM__ = "linux"; +} + +beforeAll(() => { + // set up a clean directory for the version built with node and the version built in debug mode + fs.rmSync(join(__dirname, "v8-module-node"), { recursive: true, force: true }); + fs.rmSync(join(__dirname, "v8-module-debug"), { recursive: true, force: true }); + fs.cpSync(join(__dirname, "v8-module"), join(__dirname, "v8-module-node"), { recursive: true }); + fs.cpSync(join(__dirname, "v8-module"), join(__dirname, "v8-module-debug"), { recursive: true }); + + // build code using bun + // we install/build with separate commands so that we can use --bun to run node-gyp + const bunInstall = spawnSync({ + cmd: [bunExe(), "install", "--ignore-scripts"], + cwd: join(__dirname, "v8-module"), + env: bunEnv, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + }); + if (!bunInstall.success) { + throw new Error("build failed"); + } + const bunBuild = spawnSync({ + cmd: [bunExe(), "x", "--bun", "node-gyp", "rebuild"], + cwd: join(__dirname, "v8-module"), + env: bunEnv, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + }); + if (!bunBuild.success) { + throw new Error("build failed"); + } + + // build code using bun, in debug mode + const bunDebugInstall = spawnSync({ + cmd: [bunExe(), "install", "--verbose", "--ignore-scripts"], + cwd: join(__dirname, "v8-module-debug"), + env: bunEnv, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + }); + if (!bunDebugInstall.success) { + throw new Error("build failed"); + } + const bunDebugBuild = spawnSync({ + cmd: [bunExe(), "x", "--bun", "node-gyp", "rebuild", "--debug"], + cwd: join(__dirname, "v8-module-debug"), + env: bunEnv, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + }); + if (!bunDebugBuild.success) { + throw new Error("build failed"); + } + + // build code using node + const nodeInstall = spawnSync({ + cmd: ["npm", "install", "--verbose", "--foreground-scripts"], + cwd: join(__dirname, "v8-module-node"), + env: bunEnv, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + }); + if (!nodeInstall.success) { + throw new Error("build failed"); + } + + // build invalid modules + const invalidModulesBuild = spawnSync({ + cmd: [bunExe(), "install"], + cwd: join(__dirname, "bad-modules"), + env: bunEnv, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + }); + if (!invalidModulesBuild.success) { + throw new Error("build failed"); + } +}); + +describe("module lifecycle", () => { + it("can call a basic native function", () => { + checkSameOutput("test_v8_native_call", []); + }); +}); + +describe("primitives", () => { + it("can create and distinguish between null, undefined, true, and false", () => { + checkSameOutput("test_v8_primitives", []); + }); +}); + +describe("Number", () => { + it("can create small integer", () => { + checkSameOutput("test_v8_number_int", []); + }); + // non-i32 v8::Number is not implemented yet + it.skip("can create large integer", () => { + checkSameOutput("test_v8_number_large_int", []); + }); + it.skip("can create fraction", () => { + checkSameOutput("test_v8_number_fraction", []); + }); +}); + +describe("String", () => { + it("can create and read back strings with only ASCII characters", () => { + checkSameOutput("test_v8_string_ascii", []); + }); + // non-ASCII strings are not implemented yet + it.skip("can create and read back strings with UTF-8 characters", () => { + checkSameOutput("test_v8_string_utf8", []); + }); + it.skip("handles replacement correctly in strings with invalid UTF-8 sequences", () => { + checkSameOutput("test_v8_string_invalid_utf8", []); + }); +}); + +describe("External", () => { + it("can create an external and read back the correct value", () => { + checkSameOutput("test_v8_external", []); + }); +}); + +describe("Object", () => { + it("can create an object and set properties", () => { + checkSameOutput("test_v8_object", []); + }); +}); +describe("Array", () => { + // v8::Array::New is broken as it still tries to reinterpret locals as JSValues + it.skip("can create an array from a C array of Locals", () => { + checkSameOutput("test_v8_array_new", []); + }); +}); + +describe("ObjectTemplate", () => { + it("creates objects with internal fields", () => { + checkSameOutput("test_v8_object_template", []); + }); +}); + +describe("Function", () => { + it("correctly receives all its arguments from JS", () => { + checkSameOutput("print_values_from_js", [5.0, true, null, false, "meow", {}], {}); + }); +}); + +describe("error handling", () => { + it("throws an error for modules built using the wrong ABI version", () => { + expect(() => require("./bad-modules/build/Release/mismatched_abi_version.node")).toThrow( + "The module 'mismatched_abi_version' was compiled against a different Node.js ABI version using NODE_MODULE_VERSION 42.", + ); + }); + + it("throws an error for modules with no entrypoint", () => { + expect(() => require("./bad-modules/build/Release/no_entrypoint.node")).toThrow( + "The module 'no_entrypoint' has no declared entry point.", + ); + }); +}); + +afterAll(() => { + fs.rmSync(join(__dirname, "v8-module-node"), { recursive: true, force: true }); + fs.rmSync(join(__dirname, "v8-module-debug"), { recursive: true, force: true }); +}); + +enum Runtime { + node, + bun, +} + +enum BuildMode { + debug, + release, +} + +function checkSameOutput(testName: string, args: any[], thisValue?: any) { + const nodeResult = runOn(Runtime.node, BuildMode.release, testName, args, thisValue).trim(); + let bunReleaseResult = runOn(Runtime.bun, BuildMode.release, testName, args, thisValue); + let bunDebugResult = runOn(Runtime.bun, BuildMode.debug, testName, args, thisValue); + + // remove all debug logs + bunReleaseResult = bunReleaseResult.replaceAll(/^\[\w+\].+$/gm, "").trim(); + bunDebugResult = bunDebugResult.replaceAll(/^\[\w+\].+$/gm, "").trim(); + + expect(bunReleaseResult, `test ${testName} printed different output under bun vs. under node`).toBe(nodeResult); + expect(bunDebugResult, `test ${testName} printed different output under bun in debug mode vs. under node`).toBe( + nodeResult, + ); + return nodeResult; +} + +function runOn(runtime: Runtime, buildMode: BuildMode, testName: string, jsArgs: any[], thisValue?: any) { + if (runtime == Runtime.node) { + assert(buildMode == BuildMode.release); + } + const baseDir = + runtime == Runtime.node ? "v8-module-node" : buildMode == BuildMode.debug ? "v8-module-debug" : "v8-module"; + const exe = runtime == Runtime.node ? "node" : bunExe(); + + const cmd = [ + exe, + join(__dirname, baseDir, "main.js"), + testName, + JSON.stringify(jsArgs), + JSON.stringify(thisValue ?? null), + ]; + if (buildMode == BuildMode.debug) { + cmd.push("debug"); + } + + const exec = spawnSync({ + cmd, + env: bunEnv, + }); + const errs = exec.stderr.toString(); + if (errs !== "") { + throw new Error(errs); + } + expect(exec.success, `test ${testName} crashed under ${Runtime[runtime]} in ${BuildMode[buildMode]} mode`).toBeTrue(); + return exec.stdout.toString(); +}