aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-04-22 07:18:54 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-04-22 07:18:54 -0700
commit817491faad2857a7b8514fb58f6fc9f89e19f47a (patch)
tree55106a8faee7b61492bd94c763b410d087a6bdf1
parent900847fc721070c382c9e5581640a7f33db02bf3 (diff)
downloadbun-jarred/new-bund.tar.gz
bun-jarred/new-bund.tar.zst
bun-jarred/new-bund.zip
:palm_tree: :deciduous_tree: :tropical_drink:jarred/new-bund
-rw-r--r--src/bundler/bundle_v2.zig99
-rw-r--r--src/import_record.zig6
2 files changed, 99 insertions, 6 deletions
diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig
index 39cff2b74..7a458c390 100644
--- a/src/bundler/bundle_v2.zig
+++ b/src/bundler/bundle_v2.zig
@@ -1262,6 +1262,7 @@ const AstSourceIDMapping = struct {
const LinkerGraph = struct {
files: File.List = .{},
+ files_live: std.DynamicBitSetUnmanaged = undefined,
entry_points: EntryPoint.List = .{},
symbols: js_ast.Symbol.Map = .{},
@@ -1393,6 +1394,10 @@ const LinkerGraph = struct {
this.file_entry_bits = try Bitmap.init(sources.len, entry_points.len, this.allocator());
try this.files.ensureTotalCapacity(this.allocator(), sources.len);
+ this.files_live = std.DynamicBitSetUnmanaged.initEmpty(
+ this.allocator,
+ sources.len,
+ );
this.files.len = sources.len;
var files = this.files.slice();
@@ -1435,8 +1440,6 @@ const LinkerGraph = struct {
files.items(.distance_from_entry_point),
comptime file.distance_from_entry_point,
);
- var is_live = std.mem.sliceAsBytes(files.items(.is_live));
- @memset(is_live.ptr, 0, is_live.len);
}
this.symbols = js_ast.Symbol.Map.initList(js_ast.Symbol.NestedList.init(this.parse_graph.ast.items(.symbols)));
@@ -1459,10 +1462,6 @@ const LinkerGraph = struct {
/// may be "entryPointUserSpecified" instead of "entryPointDynamicImport".
entry_point_kind: EntryPoint.Kind = .none,
- /// This is true if this file has been marked as live by the tree shaking
- /// algorithm.
- is_live: bool = false,
-
pub fn isEntryPoint(this: *const File) bool {
return this.entry_point_kind.isEntryPoint();
}
@@ -1500,6 +1499,7 @@ const LinkerContext = struct {
pub const LinkerOptions = struct {
output_format: options.OutputFormat = .esm,
+ ignore_dce_annotations: bool = false,
};
fn load(this: *LinkerContext, bundle: *BundleV2, entry_points: []Index.Int, reachable: []Index.Int) !void {
@@ -2584,6 +2584,93 @@ const LinkerContext = struct {
pub fn source_(c: *LinkerContext, index: anytype) *const Logger.Source {
return &c.parse_graph.input_files.items(.source)[index];
}
+
+ pub fn treeShakingAndCodeSplitting(c: *LinkerContext) !void {
+ // Tree shaking: Each entry point marks all files reachable from itself
+ for (c.graph.entry_points.items(.source_index)) |entry_point| {
+ c.markFileLiveForTreeShaking(entry_point);
+ }
+ }
+
+ pub fn markFileLiveForTreeShaking(c: *LinkerContext, source_index: Index.Int) void {
+ if (c.graph.files_live.isSet(source_index))
+ return;
+
+ c.graph.files_live.set(source_index);
+
+ // TODO: CSS source index
+
+ const id = c.graph.files.items(.asts)[source_index];
+ if (@as(usize, id) >= c.graph.asts.len) return;
+ for (c.graph.ast.items(.parts)[id]) |part, part_index| {
+ var can_be_removed_if_unused = part.can_be_removed_if_unused;
+
+ // Also include any statement-level imports
+ for (part.import_record_indices) |import_record_Index| {
+ var record: *ImportRecord = &c.graph.ast.items(.import_records)[import_record_Index];
+
+ if (record.kind != .stmt)
+ continue;
+
+ if (record.source_index.isValid()) {
+ const other_source_index = record.source_index.get();
+
+ // Don't include this module for its side effects if it can be
+ // considered to have no side effects
+ if (c.parse_graph.input_files.items(.side_effects)[other_source_index] != .has_side_effects and !c.options.ignore_dce_annoations) {
+ continue;
+ }
+
+ // Otherwise, include this module for its side effects
+ c.markFileLiveForTreeShaking(other_source_index);
+ } else if (record.is_external_without_side_effects()) {
+ // This can be removed if it's unused
+ continue;
+ }
+
+ // If we get here then the import was included for its side effects, so
+ // we must also keep this part
+ can_be_removed_if_unused = false;
+ }
+
+ // Include all parts in this file with side effects, or just include
+ // everything if tree-shaking is disabled. Note that we still want to
+ // perform tree-shaking on the runtime even if tree-shaking is disabled.
+ if (!can_be_removed_if_unused or
+ (!part.force_tree_shaking and
+ !c.options.tree_shaking and
+ c.graph.entry_points.items(.entry_point_kinds)[id].isEntryPoint()))
+ {
+ c.markPartLiveForTreeShaking(
+ part_index,
+ id,
+ );
+ }
+ }
+ }
+
+ pub fn markPartLiveForTreeShaking(c: *LinkerContext, part_index: u32, id: u32) bool {
+ var part: js_ast.Part = &c.graph.ast.items(.parts)[id][part_index];
+ // only once
+ if (part.is_live) {
+ return false;
+ }
+
+ part.is_live = true;
+
+ for (part.dependencies.slice()) |dependency| {
+ const _id = c.parse_graph.input_files.items(.ast)[dependency.source_index];
+ if (c.markPartLiveForTreeShaking(
+ dependency.part_index,
+ _id,
+ )) {
+ c.markFileLiveForTreeShaking(dependency.source_index);
+ }
+ }
+
+ return true;
+ }
+
pub fn matchImportWithExport(
c: *LinkerContext,
tracker_: ImportTracker,
diff --git a/src/import_record.zig b/src/import_record.zig
index b160befe0..89c439604 100644
--- a/src/import_record.zig
+++ b/src/import_record.zig
@@ -170,6 +170,10 @@ pub const ImportRecord = struct {
return this.flags.contains(.was_originally_require);
}
+ pub inline fn is_external_without_side_effects(this: *const ImportRecord) bool {
+ return @enumToInt(this.tag) >= @enumToInt(Tag.bun) or this.flags.contains(.is_external_without_side_effects);
+ }
+
pub const Flags = enum {
/// True for the following cases:
///
@@ -219,6 +223,8 @@ pub const ImportRecord = struct {
was_originally_require,
+ is_external_without_side_effects,
+
pub const None = Set{};
pub const Fields = std.enums.EnumFieldStruct(Flags, bool, false);
pub const Set = std.enums.EnumSet(Flags);