Skip to content

Commit

Permalink
initial sourcemap loading
Browse files Browse the repository at this point in the history
not complete yet, needs a better comment parser
  • Loading branch information
paperdave committed May 10, 2024
1 parent abe4fd9 commit d1540ad
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 106 deletions.
22 changes: 18 additions & 4 deletions src/bun.js/bindings/ZigSourceProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,14 @@ JSC::SourceID sourceIDForSourceURL(const WTF::String& sourceURL)
}

extern "C" bool BunTest__shouldGenerateCodeCoverage(BunString sourceURL);

Ref<SourceProvider> SourceProvider::create(Zig::GlobalObject* globalObject, ResolvedSource& resolvedSource, JSC::SourceProviderSourceType sourceType, bool isBuiltin)
{

extern "C" void Bun__addSourceProviderSourceMap(void* bun_vm, void* opaque_source_provider, BunString* specifier);

Ref<SourceProvider> SourceProvider::create(
Zig::GlobalObject* globalObject,
ResolvedSource& resolvedSource,
JSC::SourceProviderSourceType sourceType,
bool isBuiltin
) {
auto string = resolvedSource.source_code.toWTFString(BunString::ZeroCopy);
auto sourceURLString = resolvedSource.source_url.toWTFString(BunString::ZeroCopy);

Expand Down Expand Up @@ -99,6 +103,10 @@ Ref<SourceProvider> SourceProvider::create(Zig::GlobalObject* globalObject, Reso
ByteRangeMapping__generate(Bun::toString(provider->sourceURL()), Bun::toString(provider->source().toStringWithoutCopying()), provider->asID());
}

if (resolvedSource.already_bundled) {
Bun__addSourceProviderSourceMap(globalObject->bunVM(), provider.ptr(), &resolvedSource.source_url);
}

return provider;
}

Expand Down Expand Up @@ -229,4 +237,10 @@ int SourceProvider::readCache(JSC::VM& vm, const JSC::SourceCode& sourceCode)
// return 0;
// }
}

extern "C" BunString ZigSourceProvider__getSourceSlice(SourceProvider* provider) {
// TODO(@paperdave): does this clone the data? i sure hope not.
return Bun::toStringRef(provider->source().toString());
}

}; // namespace Zig
15 changes: 8 additions & 7 deletions src/bun.js/bindings/ZigSourceProvider.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "headers.h"
#include "root.h"
#include <_types/_uint64_t.h>
#include <limits>

#pragma once

Expand Down Expand Up @@ -36,7 +38,11 @@ class SourceProvider final : public JSC::SourceProvider {
using SourceOrigin = JSC::SourceOrigin;

public:
static Ref<SourceProvider> create(Zig::GlobalObject*, ResolvedSource& resolvedSource, JSC::SourceProviderSourceType sourceType = JSC::SourceProviderSourceType::Module, bool isBuiltIn = false);
static Ref<SourceProvider> create(
Zig::GlobalObject*,
ResolvedSource& resolvedSource,
JSC::SourceProviderSourceType sourceType = JSC::SourceProviderSourceType::Module,
bool isBuiltIn = false);
~SourceProvider();
unsigned hash() const override;
StringView source() const override { return StringView(m_source.get()); }
Expand Down Expand Up @@ -67,17 +73,12 @@ class SourceProvider final : public JSC::SourceProvider {
: Base(sourceOrigin, WTFMove(sourceURL), String(), taintedness, startPosition, sourceType)
, m_source(sourceImpl)
{

m_resolvedSource = resolvedSource;
}

RefPtr<JSC::CachedBytecode> m_cachedBytecode;
Ref<WTF::StringImpl> m_source;
bool did_free_source_code = false;
Zig::GlobalObject* m_globalObjectForSourceProviderMap;
unsigned m_hash = 0;

// JSC::SourceCodeKey key;
};

} // namespace Zig
} // namespace Zig
1 change: 1 addition & 0 deletions src/bun.js/bindings/exports.zig
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ pub const ResolvedSource = extern struct {

/// This is for source_code
source_code_needs_deref: bool = true,
already_bundled: bool = false,

pub const Tag = @import("ResolvedSourceTag").ResolvedSourceTag;
};
Expand Down
1 change: 1 addition & 0 deletions src/bun.js/bindings/headers-handwritten.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ typedef struct ResolvedSource {
JSC::EncodedJSValue jsvalue_for_export;
uint32_t tag;
bool needsDeref;
bool already_bundled;
} ResolvedSource;
static const uint32_t ResolvedSourceTagPackageJSONTypeModule = 1;
typedef union ErrorableResolvedSourceResult {
Expand Down
117 changes: 102 additions & 15 deletions src/bun.js/javascript.zig
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,13 @@ const MappingList = SourceMap.Mapping.List;
const uv = bun.windows.libuv;

pub const SavedSourceMap = struct {
/// This is a pointer to the map located on the VirtualMachine struct
map: *HashTable,
mutex: bun.Lock = bun.Lock.init(),

pub const vlq_offset = 24;

// For bun.js, we store the number of mappings and how many bytes the final list is at the beginning of the array
// For the runtime, we store the number of mappings and how many bytes the final list is at the beginning of the array
// The first 8 bytes are the length of the array
// The second 8 bytes are the number of mappings
pub const SavedMappings = struct {
Expand Down Expand Up @@ -172,16 +176,70 @@ pub const SavedSourceMap = struct {
}
};

/// ParsedSourceMap is the canonical form for sourcemaps,
///
/// but `SavedMappings` and `SourceProviderMap` are much cheaper to construct.
/// In `fn get`, this value gets converted to ParsedSourceMap always
pub const Value = TaggedPointerUnion(.{
ParsedSourceMap,
SavedMappings,
SourceProviderMap,
});
pub const HashTable = std.HashMap(u64, *anyopaque, IdentityContext(u64), 80);

/// This is a pointer to the map located on the VirtualMachine struct
map: *HashTable,
/// This is a pointer to a ZigSourceProvider that may or may not have a `//# sourceMappingURL` comment
/// when we want to lookup this data, we will then resolve it to a ParsedSourceMap if it does.
///
/// This is used for files that were pre-bundled with `bun build --target=bun --sourcemap`
pub const SourceProviderMap = opaque {
extern fn ZigSourceProvider__getSourceSlice(*SourceProviderMap) bun.String;

pub fn toParsedSourceMap(provider: *SourceProviderMap) ?*ParsedSourceMap {
const bun_str = ZigSourceProvider__getSourceSlice(provider);
defer bun_str.deref();
bun.assert(bun_str.tag == .WTFStringImpl);
const wtf = bun_str.value.WTFStringImpl;

// TODO: do not use toUTF8()
const utf8 = wtf.toUTF8(bun.default_allocator);
defer utf8.deinit();
const bytes = utf8.slice();

// TODO: use smarter technique
if (std.mem.indexOf(u8, bytes, "//# sourceMappingURL=")) |index| {
const end = std.mem.indexOfAnyPos(u8, bytes, index + "//# sourceMappingURL=".len, " \n\r") orelse bytes.len;
const url = bytes[index + "//# sourceMappingURL=".len .. end];

var sfb = std.heap.stackFallback(32768, bun.default_allocator);
var arena = bun.ArenaAllocator.init(sfb.get());
defer arena.deinit();

switch (bun.sourcemap.parseUrl(bun.default_allocator, arena.allocator(), url)) {
.fail => {
return null;
},
.success => |parsed| {
const ptr = bun.default_allocator.create(ParsedSourceMap) catch bun.outOfMemory();
ptr.* = parsed;
return ptr;
},
}
}

mutex: bun.Lock = bun.Lock.init(),
return null;
}

pub fn deinit(provider: *SourceProviderMap) void {
_ = provider;
@panic("TODO");
}
};

pub fn putZigSourceProvider(this: *SavedSourceMap, opaque_source_provider: *anyopaque, path: []const u8) void {
const source_provider: *SourceProviderMap = @ptrCast(opaque_source_provider);
this.putValue(path, Value.init(source_provider)) catch bun.outOfMemory();
}

pub const HashTable = std.HashMap(u64, *anyopaque, IdentityContext(u64), 80);

pub fn onSourceMapChunk(this: *SavedSourceMap, chunk: SourceMap.Chunk, source: logger.Source) anyerror!void {
try this.putMappings(source, chunk.buffer);
Expand Down Expand Up @@ -211,26 +269,31 @@ pub const SavedSourceMap = struct {
}

pub fn putMappings(this: *SavedSourceMap, source: logger.Source, mappings: MutableString) !void {
try this.putValue(source.path.text, Value.init(bun.cast(*SavedMappings, mappings.list.items.ptr)));
}

fn putValue(this: *SavedSourceMap, path: []const u8, value: Value) !void {
this.mutex.lock();
defer this.mutex.unlock();
const entry = try this.map.getOrPut(bun.hash(source.path.text));
const entry = try this.map.getOrPut(bun.hash(path));
if (entry.found_existing) {
var value = Value.from(entry.value_ptr.*);
if (value.get(ParsedSourceMap)) |source_map_| {
var source_map: *ParsedSourceMap = source_map_;
var old_value = Value.from(entry.value_ptr.*);
if (old_value.get(ParsedSourceMap)) |parsed_source_map| {
var source_map: *ParsedSourceMap = parsed_source_map;
source_map.deinit(default_allocator);
} else if (value.get(SavedMappings)) |saved_mappings| {
} else if (old_value.get(SavedMappings)) |saved_mappings| {
var saved = SavedMappings{ .data = @as([*]u8, @ptrCast(saved_mappings)) };

saved.deinit();
} else if (old_value.get(SourceProviderMap)) |provider| {
provider.deinit();

This comment has been minimized.

Copy link
@Jarred-Sumner

Jarred-Sumner May 10, 2024

Collaborator
  • are we calling ref on this in the Zig code? I don't think we should, sourcemappings shouldn't keep it alive, right?
  • this can be called from any thread and we do not know if it is safe to do this on another thread
}
}

entry.value_ptr.* = Value.init(bun.cast(*SavedMappings, mappings.list.items.ptr)).ptr();
entry.value_ptr.* = value.ptr();
}

pub fn get(this: *SavedSourceMap, path: string) ?ParsedSourceMap {
const mapping = this.map.getEntry(bun.hash(path)) orelse return null;
const hash = bun.hash(path);
const mapping = this.map.getEntry(hash) orelse return null;
switch (Value.from(mapping.value_ptr.*).tag()) {
Value.Tag.ParsedSourceMap => {
return Value.from(mapping.value_ptr.*).as(ParsedSourceMap).*;
Expand All @@ -246,7 +309,23 @@ pub const SavedSourceMap = struct {
mapping.value_ptr.* = Value.init(result).ptr();
return result.*;
},
else => return null,
Value.Tag.SourceProviderMap => {
var ptr = Value.from(mapping.value_ptr.*).as(SourceProviderMap);
if (ptr.toParsedSourceMap()) |result| {
mapping.value_ptr.* = Value.init(result).ptr();
return result.*;
} else {
// does not have a valid source map
_ = this.map.remove(hash);
return null;
}
},
else => {
if (Environment.allow_assert) {
@panic("Corrupt pointer tag");
}
return null;
},
}
}

Expand Down Expand Up @@ -3825,4 +3904,12 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime
};
}

export fn Bun__addSourceProviderSourceMap(bun_vm: *anyopaque, opaque_source_provider: *anyopaque, specifier: *bun.String) void {
const vm: *VirtualMachine = @alignCast(@ptrCast(bun_vm));
var sfb = std.heap.stackFallback(4096, bun.default_allocator);
const slice = specifier.toUTF8(sfb.get());
defer slice.deinit();
vm.source_mappings.putZigSourceProvider(opaque_source_provider, slice.slice());
}

pub export var isBunTest: bool = false;
3 changes: 2 additions & 1 deletion src/bun.js/module_loader.zig
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,7 @@ pub const RuntimeTranspilerStore = struct {
.source_code = bun.String.createLatin1(parse_result.source.contents),
.specifier = duped,
.source_url = duped.createIfDifferent(path.text),
.already_bundled = true,
.hash = 0,
};
this.resolved_source.source_code.ensureHash();
Expand Down Expand Up @@ -1796,7 +1797,7 @@ pub const ModuleLoader = struct {
.source_code = bun.String.createLatin1(parse_result.source.contents),
.specifier = input_specifier,
.source_url = input_specifier.createIfDifferent(path.text),

.already_bundled = true,
.hash = 0,
};
}
Expand Down
2 changes: 1 addition & 1 deletion src/js_printer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5865,7 +5865,7 @@ pub fn getSourceMapBuilder(
return .{
.source_map = SourceMap.Chunk.Builder.SourceMapper.init(
opts.allocator,
is_bun_platform,
is_bun_platform and generate_source_map == .lazy,
),
.cover_lines_without_mappings = true,
.approximate_input_line_count = tree.approximate_newline_count,
Expand Down

0 comments on commit d1540ad

Please sign in to comment.