diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index c4a12f035b..dd5d2b6a40 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -47,6 +47,7 @@ #include "JavaScriptCore/JSCInlines.h" #include "JavaScriptCore/CodeCache.h" #include "JavaScriptCore/BytecodeCacheError.h" +#include "JavaScriptCore/FunctionCodeBlock.h" #include "wtf/FileHandle.h" #include "../vm/SigintWatcher.h" @@ -177,8 +178,9 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c FunctionMetadataNode* metadata = static_cast(expression)->metadata(); ASSERT(metadata); - if (!metadata) + if (!metadata) { return nullptr; + } // metadata->setStartOffset(startOffset); @@ -263,7 +265,7 @@ String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& a RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); JSC::CodeCache* cache = vm.codeCache(); JSC::ParserError parserError; JSC::UnlinkedProgramCodeBlock* unlinked = cache->getUnlinkedProgramCodeBlock(vm, executable, source, {}, parserError); @@ -276,9 +278,26 @@ RefPtr getBytecode(JSGlobalObject* globalObject, JSC::Progr return JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, fileHandle, bytecodeCacheError, {}); } +std::optional> getBytecode(JSGlobalObject* globalObject, JSC::JSFunction* function, CodeBlock*& codeBlock) +{ + VM& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + FunctionExecutable* executable = function->jsExecutable(); + executable->prepareForExecution(vm, function, function->scope(), CodeSpecializationKind::CodeForCall, codeBlock); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + if (!codeBlock) { + return std::nullopt; + } + + const JSInstructionStream& instructionStream = codeBlock->instructions(); + return std::span { reinterpret_cast(instructionStream.rawPointer()), instructionStream.sizeInBytes() }; +} + JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSC::ProgramExecutable* executable = JSC::ProgramExecutable::create(globalObject, source); @@ -292,7 +311,7 @@ JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::So } std::span bytes = bytecode->span(); - auto* buffer = WebCore::createBuffer(globalObject, bytes); + JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, bytes); RETURN_IF_EXCEPTION(scope, {}); ASSERT(buffer); @@ -523,7 +542,7 @@ bool NodeVMGlobalObject::put(JSCell* cell, JSGlobalObject* globalObject, Propert auto* sandbox = thisObject->m_sandbox.get(); - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); JSValue thisValue = slot.thisValue(); bool isContextualStore = thisValue != JSValue(globalObject); if (auto* proxy = jsDynamicCast(thisValue); proxy && proxy->target() == globalObject) { @@ -662,7 +681,7 @@ bool NodeVMGlobalObject::defineOwnProperty(JSObject* cell, JSGlobalObject* globa } auto* contextifiedObject = thisObject->m_sandbox.get(); - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); PropertySlot slot(globalObject, PropertySlot::InternalMethodType::GetOwnProperty, nullptr); @@ -772,7 +791,7 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject JSC_DEFINE_HOST_FUNCTION(vmModuleRunInThisContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto sourceStringValue = callFrame->argument(0); auto throwScope = DECLARE_THROW_SCOPE(vm); @@ -867,7 +886,7 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject SourceOrigin sourceOrigin = JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)); // Process contextExtensions if they exist - JSScope* functionScope = !!options.parsingContext ? options.parsingContext : globalObject; + JSScope* functionScope = options.parsingContext ? options.parsingContext : globalObject; if (!options.contextExtensions.isUndefinedOrNull() && !options.contextExtensions.isEmpty() && options.contextExtensions.isObject() && isArray(globalObject, options.contextExtensions)) { auto* contextExtensionsArray = jsCast(options.contextExtensions); @@ -896,10 +915,23 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject // Create the function using constructAnonymousFunction with the appropriate scope chain JSFunction* function = constructAnonymousFunction(globalObject, ArgList(constructFunctionArgs), sourceOrigin, options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset), functionScope); + if (options.produceCachedData) { + CodeBlock* codeBlock = nullptr; + std::optional> bytecode = getBytecode(globalObject, function, codeBlock); + if (bytecode) { + JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, *bytecode); + function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedData"_s), buffer); + function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(true)); + } else { + function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(false)); + } + } + RETURN_IF_EXCEPTION(scope, {}); - if (!function) + if (!function) { return throwVMError(globalObject, scope, "Failed to compile function"_s); + } return JSValue::encode(function); } @@ -1016,7 +1048,7 @@ bool NodeVMGlobalObject::deleteProperty(JSCell* cell, JSGlobalObject* globalObje return Base::deleteProperty(cell, globalObject, propertyName, slot); } - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto* sandbox = thisObject->m_sandbox.get(); diff --git a/src/bun.js/bindings/NodeVM.h b/src/bun.js/bindings/NodeVM.h index 869ba031b6..2a764c802d 100644 --- a/src/bun.js/bindings/NodeVM.h +++ b/src/bun.js/bindings/NodeVM.h @@ -20,6 +20,8 @@ class NodeVMContextOptions; namespace NodeVM { RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source); +// The lifetime of the return value is tied to the lifetime of the pointer in `codeBlock`. That's why the caller has to pass it by reference. +std::optional> getBytecode(JSGlobalObject* globalObject, JSC::JSFunction* function, CodeBlock*& codeBlock); bool extractCachedData(JSValue cachedDataValue, WTF::Vector& outCachedData); String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& args, ThrowScope& scope, int* outOffset); JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source); @@ -30,7 +32,7 @@ NodeVMGlobalObject* getGlobalObjectFromContext(JSGlobalObject* globalObject, JSV JSC::EncodedJSValue INVALID_ARG_VALUE_VM_VARIATION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value); /// For vm.compileFunction we need to return an anonymous function expression /// -/// This code is adapted/inspired from JSC::constructFunction, which is used for function declarations. +/// This code is adapted from/inspired by JSC::constructFunction, which is used for function declarations. JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, const String& fileName = String(), JSC::SourceTaintedOrigin sourceTaintOrigin = JSC::SourceTaintedOrigin::Untainted, TextPosition position = TextPosition(), JSC::JSScope* scope = nullptr); } // namespace NodeVM