diff options
Diffstat (limited to 'src/bun.js')
m--------- | src/bun.js/WebKit | 0 | ||||
-rw-r--r-- | src/bun.js/bindings/CodeCoverage.cpp | 44 | ||||
-rw-r--r-- | src/bun.js/bindings/InternalModuleRegistry.cpp | 16 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigSourceProvider.cpp | 41 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigSourceProvider.h | 4 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 9 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 12 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.h | 1 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.zig | 1 | ||||
-rw-r--r-- | src/bun.js/javascript.zig | 28 | ||||
-rw-r--r-- | src/bun.js/modules/BunJSCModule.h | 60 | ||||
-rw-r--r-- | src/bun.js/test/jest.zig | 1 |
12 files changed, 199 insertions, 18 deletions
diff --git a/src/bun.js/WebKit b/src/bun.js/WebKit -Subproject 9d9172d3242b40f16fa980ead750dc9ab248065 +Subproject 74609640b2a7c5a1588b824f870d1b0ff91bfd8 diff --git a/src/bun.js/bindings/CodeCoverage.cpp b/src/bun.js/bindings/CodeCoverage.cpp new file mode 100644 index 000000000..1cb3b6ba2 --- /dev/null +++ b/src/bun.js/bindings/CodeCoverage.cpp @@ -0,0 +1,44 @@ +#include "root.h" +#include "ZigSourceProvider.h" +#include <JavaScriptCore/ControlFlowProfiler.h> + +using namespace JSC; + +extern "C" bool CodeCoverage__withBlocksAndFunctions( + JSC::VM* vmPtr, + JSC::SourceID sourceID, + void* ctx, + bool ignoreSourceMap, + void (*blockCallback)(void* ctx, JSC::BasicBlockRange* range, size_t len, size_t functionOffset, bool ignoreSourceMap)) +{ + + VM& vm = *vmPtr; + + auto basicBlocks = vm.controlFlowProfiler()->getBasicBlocksForSourceIDWithoutFunctionRange( + sourceID, vm); + + if (basicBlocks.isEmpty()) { + blockCallback(ctx, nullptr, 0, 0, ignoreSourceMap); + return true; + } + + size_t functionStartOffset = basicBlocks.size(); + + const Vector<std::tuple<bool, unsigned, unsigned>>& functionRanges = vm.functionHasExecutedCache()->getFunctionRanges(sourceID); + + basicBlocks.reserveCapacity(functionRanges.size() + basicBlocks.size()); + + for (const auto& functionRange : functionRanges) { + BasicBlockRange range; + range.m_hasExecuted = std::get<0>(functionRange); + range.m_startOffset = static_cast<int>(std::get<1>(functionRange)); + range.m_endOffset = static_cast<int>(std::get<2>(functionRange)); + range.m_executionCount = range.m_hasExecuted + ? 1 + : 0; // This is a hack. We don't actually count this. + basicBlocks.append(range); + } + + blockCallback(ctx, basicBlocks.data(), basicBlocks.size(), functionStartOffset, ignoreSourceMap); + return true; +} diff --git a/src/bun.js/bindings/InternalModuleRegistry.cpp b/src/bun.js/bindings/InternalModuleRegistry.cpp index e6b574d7b..841360502 100644 --- a/src/bun.js/bindings/InternalModuleRegistry.cpp +++ b/src/bun.js/bindings/InternalModuleRegistry.cpp @@ -12,6 +12,20 @@ namespace Bun { +extern "C" bool BunTest__shouldGenerateCodeCoverage(BunString sourceURL); +extern "C" void ByteRangeMapping__generate(BunString sourceURL, BunString code, int sourceID); + +static void maybeAddCodeCoverage(JSC::VM& vm, const JSC::SourceCode& code) +{ +#ifdef BUN_DEBUG + bool isCodeCoverageEnabled = !!vm.controlFlowProfiler(); + bool shouldGenerateCodeCoverage = isCodeCoverageEnabled && BunTest__shouldGenerateCodeCoverage(Bun::toString(code.provider()->sourceURL())); + if (shouldGenerateCodeCoverage) { + ByteRangeMapping__generate(Bun::toString(code.provider()->sourceURL()), Bun::toString(code.provider()->source().toStringWithoutCopying()), code.provider()->asID()); + } +#endif +} + // The `INTERNAL_MODULE_REGISTRY_GENERATE` macro handles inlining code to compile and run a // JS builtin that acts as a module. In debug mode, we use a different implementation that reads // from the developer's filesystem. This allows reloading code without recompiling bindings. @@ -20,7 +34,7 @@ namespace Bun { auto throwScope = DECLARE_THROW_SCOPE(vm); \ auto&& origin = SourceOrigin(WTF::URL(makeString("builtin://"_s, moduleName))); \ SourceCode source = JSC::makeSource(SOURCE, origin, moduleName); \ - \ + maybeAddCodeCoverage(vm, source); \ JSFunction* func \ = JSFunction::create( \ vm, \ diff --git a/src/bun.js/bindings/ZigSourceProvider.cpp b/src/bun.js/bindings/ZigSourceProvider.cpp index 2c448b5a6..d11c748da 100644 --- a/src/bun.js/bindings/ZigSourceProvider.cpp +++ b/src/bun.js/bindings/ZigSourceProvider.cpp @@ -58,11 +58,48 @@ static SourceOrigin toSourceOrigin(const String& sourceURL, bool isBuiltin) return SourceOrigin(WTF::URL::fileURLWithFileSystemPath(sourceURL)); } +void forEachSourceProvider(const WTF::Function<void(JSC::SourceID)>& func) +{ + // if (sourceProviderMap == nullptr) { + // return; + // } + + // for (auto& pair : *sourceProviderMap) { + // auto sourceProvider = pair.value; + // if (sourceProvider) { + // func(sourceProvider); + // } + // } +} +extern "C" int ByteRangeMapping__getSourceID(void* mappings, BunString sourceURL); +extern "C" void* ByteRangeMapping__find(BunString sourceURL); +void* sourceMappingForSourceURL(const WTF::String& sourceURL) +{ + return ByteRangeMapping__find(Bun::toString(sourceURL)); +} + +extern "C" void ByteRangeMapping__generate(BunString sourceURL, BunString code, int sourceID); + +JSC::SourceID sourceIDForSourceURL(const WTF::String& sourceURL) +{ + void* mappings = ByteRangeMapping__find(Bun::toString(sourceURL)); + if (!mappings) { + return 0; + } + + return ByteRangeMapping__getSourceID(mappings, Bun::toString(sourceURL)); +} + +extern "C" bool BunTest__shouldGenerateCodeCoverage(BunString sourceURL); + Ref<SourceProvider> SourceProvider::create(Zig::GlobalObject* globalObject, ResolvedSource resolvedSource, JSC::SourceProviderSourceType sourceType, bool isBuiltin) { auto stringImpl = Bun::toWTFString(resolvedSource.source_code); auto sourceURLString = toStringCopy(resolvedSource.source_url); + bool isCodeCoverageEnabled = !!globalObject->vm().controlFlowProfiler(); + + bool shouldGenerateCodeCoverage = isCodeCoverageEnabled && !isBuiltin && BunTest__shouldGenerateCodeCoverage(Bun::toString(sourceURLString)); auto provider = adoptRef(*new SourceProvider( globalObject->isThreadLocalDefaultGlobalObject ? globalObject : nullptr, @@ -71,6 +108,10 @@ Ref<SourceProvider> SourceProvider::create(Zig::GlobalObject* globalObject, Reso sourceURLString.impl(), TextPosition(), sourceType)); + if (shouldGenerateCodeCoverage) { + ByteRangeMapping__generate(Bun::toString(provider->sourceURL()), Bun::toString(provider->source().toStringWithoutCopying()), provider->asID()); + } + return provider; } diff --git a/src/bun.js/bindings/ZigSourceProvider.h b/src/bun.js/bindings/ZigSourceProvider.h index c189cc454..364e6ee23 100644 --- a/src/bun.js/bindings/ZigSourceProvider.h +++ b/src/bun.js/bindings/ZigSourceProvider.h @@ -22,6 +22,10 @@ namespace Zig { class GlobalObject; +void forEachSourceProvider(WTF::Function<void(JSC::SourceID)>); +JSC::SourceID sourceIDForSourceURL(const WTF::String& sourceURL); +void* sourceMappingForSourceURL(const WTF::String& sourceURL); + class SourceProvider final : public JSC::SourceProvider { WTF_MAKE_FAST_ALLOCATED; using Base = JSC::SourceProvider; diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index b0a291c2e..201fc0959 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -4552,3 +4552,12 @@ CPP_DECL void JSC__JSMap__set(JSC__JSMap* map, JSC__JSGlobalObject* arg1, JSC__J { map->set(arg1, JSC::JSValue::decode(JSValue2), JSC::JSValue::decode(JSValue3)); } + +CPP_DECL void JSC__VM__setControlFlowProfiler(JSC__VM* vm, bool isEnabled) +{ + if (isEnabled) { + vm->enableControlFlowProfiler(); + } else { + vm->disableControlFlowProfiler(); + } +}
\ No newline at end of file diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 4f533b9d9..3b6116a0d 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -102,8 +102,9 @@ pub const ZigString = extern struct { }; pub fn fromBytes(slice_: []const u8) ZigString { - if (!strings.isAllASCII(slice_)) - return fromUTF8(slice_); + if (!strings.isAllASCII(slice_)) { + return initUTF8(slice_); + } return init(slice_); } @@ -4978,6 +4979,7 @@ pub const VM = extern struct { SmallHeap = 0, LargeHeap = 1, }; + pub fn create(heap_type: HeapType) *VM { return cppFn("create", .{@intFromEnum(heap_type)}); } @@ -4986,6 +4988,10 @@ pub const VM = extern struct { return cppFn("deinit", .{ vm, global_object }); } + pub fn setControlFlowProfiler(vm: *VM, enabled: bool) void { + return cppFn("setControlFlowProfiler", .{ vm, enabled }); + } + pub fn isJITEnabled() bool { return cppFn("isJITEnabled", .{}); } @@ -5093,7 +5099,7 @@ pub const VM = extern struct { return cppFn("blockBytesAllocated", .{vm}); } - pub const Extern = [_][]const u8{ "collectAsync", "externalMemorySize", "blockBytesAllocated", "heapSize", "releaseWeakRefs", "throwError", "deferGC", "holdAPILock", "runGC", "generateHeapSnapshot", "isJITEnabled", "deleteAllCode", "create", "deinit", "setExecutionForbidden", "executionForbidden", "isEntered", "throwError", "drainMicrotasks", "whenIdle", "shrinkFootprint", "setExecutionTimeLimit", "clearExecutionTimeLimit" }; + pub const Extern = [_][]const u8{ "setControlFlowProfiler", "collectAsync", "externalMemorySize", "blockBytesAllocated", "heapSize", "releaseWeakRefs", "throwError", "deferGC", "holdAPILock", "runGC", "generateHeapSnapshot", "isJITEnabled", "deleteAllCode", "create", "deinit", "setExecutionForbidden", "executionForbidden", "isEntered", "throwError", "drainMicrotasks", "whenIdle", "shrinkFootprint", "setExecutionTimeLimit", "clearExecutionTimeLimit" }; }; pub const ThrowScope = extern struct { diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index 05c708a48..9a6d1b72a 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -428,6 +428,7 @@ CPP_DECL bool JSC__VM__isEntered(JSC__VM* arg0); CPP_DECL bool JSC__VM__isJITEnabled(); CPP_DECL void JSC__VM__releaseWeakRefs(JSC__VM* arg0); CPP_DECL JSC__JSValue JSC__VM__runGC(JSC__VM* arg0, bool arg1); +CPP_DECL void JSC__VM__setControlFlowProfiler(JSC__VM* arg0, bool arg1); CPP_DECL void JSC__VM__setExecutionForbidden(JSC__VM* arg0, bool arg1); CPP_DECL void JSC__VM__setExecutionTimeLimit(JSC__VM* arg0, double arg1); CPP_DECL void JSC__VM__shrinkFootprint(JSC__VM* arg0); diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig index fbca33a30..d39793c07 100644 --- a/src/bun.js/bindings/headers.zig +++ b/src/bun.js/bindings/headers.zig @@ -322,6 +322,7 @@ pub extern fn JSC__VM__isEntered(arg0: *bindings.VM) bool; pub extern fn JSC__VM__isJITEnabled(...) bool; pub extern fn JSC__VM__releaseWeakRefs(arg0: *bindings.VM) void; pub extern fn JSC__VM__runGC(arg0: *bindings.VM, arg1: bool) JSC__JSValue; +pub extern fn JSC__VM__setControlFlowProfiler(arg0: *bindings.VM, arg1: bool) void; pub extern fn JSC__VM__setExecutionForbidden(arg0: *bindings.VM, arg1: bool) void; pub extern fn JSC__VM__setExecutionTimeLimit(arg0: *bindings.VM, arg1: f64) void; pub extern fn JSC__VM__shrinkFootprint(arg0: *bindings.VM) void; diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 62b00cf42..1c8d91d52 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -128,6 +128,7 @@ pub fn OpaqueWrap(comptime Context: type, comptime Function: fn (this: *Context) pub const bun_file_import_path = "/node_modules.server.bun"; const SourceMap = @import("../sourcemap/sourcemap.zig"); +const ParsedSourceMap = SourceMap.Mapping.ParsedSourceMap; const MappingList = SourceMap.Mapping.List; pub const SavedSourceMap = struct { @@ -138,7 +139,7 @@ pub const SavedSourceMap = struct { data: [*]u8, pub fn vlq(this: SavedMappings) []u8 { - return this.data[16..this.len()]; + return this.data[24..this.len()]; } pub inline fn len(this: SavedMappings) usize { @@ -149,12 +150,13 @@ pub const SavedSourceMap = struct { default_allocator.free(this.data[0..this.len()]); } - pub fn toMapping(this: SavedMappings, allocator: Allocator, path: string) anyerror!MappingList { + pub fn toMapping(this: SavedMappings, allocator: Allocator, path: string) anyerror!ParsedSourceMap { const result = SourceMap.Mapping.parse( allocator, - this.data[16..this.len()], + this.data[24..this.len()], @as(usize, @bitCast(this.data[8..16].*)), 1, + @as(usize, @bitCast(this.data[16..24].*)), ); switch (result) { .fail => |fail| { @@ -183,7 +185,7 @@ pub const SavedSourceMap = struct { } }; - pub const Value = TaggedPointerUnion(.{ MappingList, SavedMappings }); + pub const Value = TaggedPointerUnion(.{ ParsedSourceMap, SavedMappings }); pub const HashTable = std.HashMap(u64, *anyopaque, IdentityContext(u64), 80); /// This is a pointer to the map located on the VirtualMachine struct @@ -203,8 +205,8 @@ pub const SavedSourceMap = struct { var entry = try this.map.getOrPut(bun.hash(source.path.text)); if (entry.found_existing) { var value = Value.from(entry.value_ptr.*); - if (value.get(MappingList)) |source_map_| { - var source_map: *MappingList = source_map_; + if (value.get(ParsedSourceMap)) |source_map_| { + var source_map: *ParsedSourceMap = source_map_; source_map.deinit(default_allocator); } else if (value.get(SavedMappings)) |saved_mappings| { var saved = SavedMappings{ .data = @as([*]u8, @ptrCast(saved_mappings)) }; @@ -216,16 +218,16 @@ pub const SavedSourceMap = struct { entry.value_ptr.* = Value.init(bun.cast(*SavedMappings, mappings.list.items.ptr)).ptr(); } - pub fn get(this: *SavedSourceMap, path: string) ?MappingList { + pub fn get(this: *SavedSourceMap, path: string) ?ParsedSourceMap { var mapping = this.map.getEntry(bun.hash(path)) orelse return null; switch (Value.from(mapping.value_ptr.*).tag()) { - (@field(Value.Tag, @typeName(MappingList))) => { - return Value.from(mapping.value_ptr.*).as(MappingList).*; + Value.Tag.ParsedSourceMap => { + return Value.from(mapping.value_ptr.*).as(ParsedSourceMap).*; }, Value.Tag.SavedMappings => { - var saved = SavedMappings{ .data = @as([*]u8, @ptrCast(Value.from(mapping.value_ptr.*).as(MappingList))) }; + var saved = SavedMappings{ .data = @as([*]u8, @ptrCast(Value.from(mapping.value_ptr.*).as(ParsedSourceMap))) }; defer saved.deinit(); - var result = default_allocator.create(MappingList) catch unreachable; + var result = default_allocator.create(ParsedSourceMap) catch unreachable; result.* = saved.toMapping(default_allocator, path) catch { _ = this.map.remove(mapping.key_ptr.*); return null; @@ -246,8 +248,8 @@ pub const SavedSourceMap = struct { this.mutex.lock(); defer this.mutex.unlock(); - var mappings = this.get(path) orelse return null; - return SourceMap.Mapping.find(mappings, line, column); + const parsed_mappings = this.get(path) orelse return null; + return SourceMap.Mapping.find(parsed_mappings.mappings, line, column); } }; const uws = @import("root").bun.uws; diff --git a/src/bun.js/modules/BunJSCModule.h b/src/bun.js/modules/BunJSCModule.h index d7548dcdf..73823e16e 100644 --- a/src/bun.js/modules/BunJSCModule.h +++ b/src/bun.js/modules/BunJSCModule.h @@ -28,14 +28,17 @@ #include "wtf/text/WTFString.h" #include "Process.h" - +#include <JavaScriptCore/SourceProviderCache.h> #if ENABLE(REMOTE_INSPECTOR) #include "JavaScriptCore/RemoteInspectorServer.h" #endif #include "JSDOMConvertBase.h" +#include "ZigSourceProvider.h" #include "mimalloc.h" +#include "JavaScriptCore/ControlFlowProfiler.h" + using namespace JSC; using namespace WTF; using namespace WebCore; @@ -650,6 +653,60 @@ JSC_DEFINE_HOST_FUNCTION(functionDeserialize, (JSGlobalObject * globalObject, RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +extern "C" EncodedJSValue ByteRangeMapping__findExecutedLines( + JSC::JSGlobalObject *, BunString sourceURL, BasicBlockRange *ranges, + size_t len, size_t functionOffset, bool ignoreSourceMap); + +JSC_DEFINE_HOST_FUNCTION(functionCodeCoverageForFile, + (JSGlobalObject * globalObject, + CallFrame *callFrame)) { + VM &vm = globalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + + String fileName = callFrame->argument(0).toWTFString(globalObject); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + bool ignoreSourceMap = callFrame->argument(1).toBoolean(globalObject); + + auto sourceID = Zig::sourceIDForSourceURL(fileName); + if (!sourceID) { + throwException(globalObject, throwScope, + createError(globalObject, "No source for file"_s)); + return JSValue::encode(jsUndefined()); + } + + auto basicBlocks = + vm.controlFlowProfiler()->getBasicBlocksForSourceIDWithoutFunctionRange( + sourceID, vm); + + if (basicBlocks.isEmpty()) { + return JSC::JSValue::encode( + JSC::constructEmptyArray(globalObject, nullptr, 0)); + } + + size_t functionStartOffset = basicBlocks.size(); + + const Vector<std::tuple<bool, unsigned, unsigned>> &functionRanges = + vm.functionHasExecutedCache()->getFunctionRanges(sourceID); + + basicBlocks.reserveCapacity(functionRanges.size() + basicBlocks.size()); + + for (const auto &functionRange : functionRanges) { + BasicBlockRange range; + range.m_hasExecuted = std::get<0>(functionRange); + range.m_startOffset = static_cast<int>(std::get<1>(functionRange)); + range.m_endOffset = static_cast<int>(std::get<2>(functionRange)); + range.m_executionCount = + range.m_hasExecuted + ? 1 + : 0; // This is a hack. We don't actually count this. + basicBlocks.append(range); + } + + return ByteRangeMapping__findExecutedLines( + globalObject, Bun::toString(fileName), basicBlocks.data(), + basicBlocks.size(), functionStartOffset, ignoreSourceMap); +} + // clang-format off /* Source for BunJSCModuleTable.lut.h @begin BunJSCModuleTable @@ -718,6 +775,7 @@ DEFINE_NATIVE_MODULE(BunJSC) putNativeFn(Identifier::fromString(vm, "getProtectedObjects"_s), functionGetProtectedObjects); putNativeFn(Identifier::fromString(vm, "generateHeapSnapshotForDebugging"_s), functionGenerateHeapSnapshotForDebugging); putNativeFn(Identifier::fromString(vm, "profile"_s), functionRunProfiler); + putNativeFn(Identifier::fromString(vm, "codeCoverageForFile"_s), functionCodeCoverageForFile); putNativeFn(Identifier::fromString(vm, "setTimeZone"_s), functionSetTimeZone); putNativeFn(Identifier::fromString(vm, "serialize"_s), functionSerialize); putNativeFn(Identifier::fromString(vm, "deserialize"_s), functionDeserialize); diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index aacf671ce..8691e5a2d 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -89,6 +89,7 @@ pub const TestRunner = struct { test_timeout_timer: ?*bun.uws.Timer = null, last_test_timeout_timer_duration: u32 = 0, active_test_for_timeout: ?TestRunner.Test.ID = null, + test_options: *const bun.CLI.Command.TestOptions = undefined, global_callbacks: struct { beforeAll: std.ArrayListUnmanaged(JSC.JSValue) = .{}, |