Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 24 additions & 74 deletions src/Ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -618,32 +618,6 @@ pub const Slice = struct {
return ctx.result orelse &.{};
}

const UsesFiberContext = struct {
result: bool = false,

pub fn processNode(self: *UsesFiberContext, _: std.mem.Allocator, ast: Self.Slice, node: Self.Node.Index) (std.mem.Allocator.Error || std.fmt.BufPrintError)!bool {
switch (ast.nodes.items(.tag)[node]) {
.AsyncCall,
.Resolve,
.Resume,
.Yield,
=> {
self.result = true;
return true;
},
else => return false,
}
}
};

pub fn usesFiber(self: Self.Slice, allocator: std.mem.Allocator, node: Node.Index) !bool {
var ctx = UsesFiberContext{};

try self.walk(allocator, &ctx, node, .breadthFirst);

return ctx.result;
}

const IsConstantContext = struct {
result: ?bool = null,

Expand Down Expand Up @@ -827,56 +801,26 @@ pub const Slice = struct {
return ctx.result orelse false;
}

/// Mirrors Chunk.score (even though Chunk.score and Node.score won't be comparable)
/// Is used to compute complexity of a hotspot node (which doesn't have a Chunk available to evaluate)
const ComplexityContext = struct {
/// JIT complexity metadata stored only on function and hotspot candidate components.
pub const JitComplexity = struct {
/// Complexity score computed during codegen to help evaluate if the node is worth JIT compiling.
score: usize = 0,

pub fn processNode(
ctx: *ComplexityContext,
_: std.mem.Allocator,
ast: Self.Slice,
node: Self.Node.Index,
) (std.mem.Allocator.Error || std.fmt.BufPrintError)!bool {
if (ast.nodes.items(.complexity_score)[node]) |sc| {
ctx.score += sc;
return true; // Don't go deeper we already computed this node score
}

ctx.score += switch (ast.nodes.items(.tag)[node]) {
.AsyncCall,
.Resolve,
.Resume,
=> { // Blacklist because of fiber use
ctx.score = 0;
return true;
},
.Call,
.DoUntil,
.For,
.ForEach,
.Throw,
.Try,
.While,
=> @as(usize, @intCast(1)),
else => @as(usize, @intCast(0)),
} + 1; // At least 1 per node

return false;
}
/// Parent in the codegen-time JIT complexity tree, used to update ancestor scores after a hotspot is compiled.
parent: ?Node.Index = null,
};

pub fn score(self: Self.Slice, allocator: std.mem.Allocator, node: Node.Index) !usize {
const complexity_score = &self.nodes.items(.complexity_score)[node];
if (complexity_score.* == null) {
var ctx = ComplexityContext{};

try self.walk(allocator, &ctx, node, .breadthFirst);

complexity_score.* = ctx.score;
}
/// Returns mutable JIT complexity metadata for nodes that can be compiled directly by the JIT.
pub fn jitComplexity(self: Self.Slice, node: Node.Index) ?*JitComplexity {
const tags = self.nodes.items(.tag);
const components = self.nodes.items(.components);

return complexity_score.* orelse 0;
return switch (tags[node]) {
.Function => &components[node].Function.jit,
.For => &components[node].For.jit,
.ForEach => &components[node].ForEach.jit,
.While => &components[node].While.jit,
else => null,
};
}

const NamespaceContext = struct {
Expand Down Expand Up @@ -1944,8 +1888,6 @@ pub const Node = struct {

/// How many time it was visited at runtime (used to decide wether its a hotspot that needs to be compiled)
count: usize = 0,
/// Complexity score computed once to help evaluate if the node is worth JIT compiling
complexity_score: ?usize = null,
/// Node status: blacklisted, queued/generated/compiled by the JIT, compilable
jit_status: JitStatus = .compilable,
/// Once compiled
Expand Down Expand Up @@ -2363,6 +2305,8 @@ pub const For = struct {
post_loop: []const Node.Index,
body: Node.Index,
label: ?TokenIndex,
/// JIT complexity metadata for this hotspot candidate.
jit: Slice.JitComplexity = .{},
};

pub const ForEach = struct {
Expand All @@ -2372,6 +2316,8 @@ pub const ForEach = struct {
body: Node.Index,
key_omitted: bool,
label: ?TokenIndex,
/// JIT complexity metadata for this hotspot candidate.
jit: Slice.JitComplexity = .{},
};

pub const Function = struct {
Expand All @@ -2391,6 +2337,8 @@ pub const Function = struct {
// Should be .FunctionType
// Only function without a function_signature is a script
function_signature: ?Node.Index,
/// JIT complexity metadata for this function candidate.
jit: Slice.JitComplexity = .{},

upvalue_binding: std.AutoArrayHashMapUnmanaged(u8, bool),

Expand Down Expand Up @@ -2628,6 +2576,8 @@ pub const WhileDoUntil = struct {
condition: Node.Index,
body: Node.Index,
label: ?TokenIndex,
/// JIT complexity metadata used when this component represents a hotspot candidate.
jit: Slice.JitComplexity = .{},
};

pub const Zdef = struct {
Expand Down
98 changes: 0 additions & 98 deletions src/Chunk.zig
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ code: std.ArrayList(u32) = .empty,
locations: std.ArrayList(Ast.TokenIndex) = .empty,
/// List of constants defined in this chunk
constants: std.ArrayList(Value) = .empty,
/// Ranges of bytecode skipped by compiled hotspots
compiled_hotspot_ranges: std.ArrayList(InstructionRange) = .empty,
/// Complexity score computed once to help evaluate if the chunk is worth JIT compiling
complexity_score: ?u32 = null,

pub fn init(allocator: std.mem.Allocator, ast: Ast.Slice) Self {
return Self{
Expand All @@ -29,7 +25,6 @@ pub fn deinit(self: *Self) void {
self.code.deinit(self.allocator);
self.constants.deinit(self.allocator);
self.locations.deinit(self.allocator);
self.compiled_hotspot_ranges.deinit(self.allocator);
}

pub fn write(self: *Self, code: u32, where: Ast.TokenIndex) !void {
Expand All @@ -45,94 +40,6 @@ pub fn addConstant(self: *Self, vm: ?*VM, value: Value) !u24 {
return @intCast(self.constants.items.len - 1);
}

/// Compute a basic complexity score based on size and presence "costly" opcodes
pub fn score(self: *Self) u32 {
if (self.complexity_score) |sc| return sc;

var complexity_score: u32 = 0;

for (self.code.items, 0..) |op, index| {
if (self.isInCompiledHotspotRange(index)) {
continue;
}

complexity_score += 1;

switch (VM.getCode(op)) {
.OP_HOTSPOT, // Those cover any loop
.OP_CALL,
.OP_TAIL_CALL,
.OP_CALL_INSTANCE_PROPERTY,
.OP_TAIL_CALL_INSTANCE_PROPERTY,
.OP_INSTANCE_INVOKE,
.OP_INSTANCE_TAIL_INVOKE,
.OP_PROTOCOL_INVOKE,
.OP_PROTOCOL_TAIL_INVOKE,
.OP_TRY,
.OP_TRY_END,
.OP_THROW,
=> complexity_score += 1,
.OP_FIBER_FOREACH,
.OP_RESUME,
.OP_RESOLVE,
=> return 0, // A chunk with fiber op codes will not be compiled so the score is 0
else => {},
}
}

self.complexity_score = complexity_score;

return complexity_score;
}

pub fn addCompiledHotspotRange(self: *Self, start: usize, end: usize) !void {
if (start >= end) {
return;
}

var merged = InstructionRange{
.start = start,
.end = end,
};

var index: usize = 0;
while (index < self.compiled_hotspot_ranges.items.len) {
const range = self.compiled_hotspot_ranges.items[index];

if (merged.end < range.start) {
try self.compiled_hotspot_ranges.insert(self.allocator, index, merged);
self.complexity_score = null;
return;
}

if (merged.start > range.end) {
index += 1;
continue;
}

merged.start = @min(merged.start, range.start);
merged.end = @max(merged.end, range.end);
_ = self.compiled_hotspot_ranges.orderedRemove(index);
}

try self.compiled_hotspot_ranges.append(self.allocator, merged);
self.complexity_score = null;
}

fn isInCompiledHotspotRange(self: *const Self, index: usize) bool {
for (self.compiled_hotspot_ranges.items) |range| {
if (index < range.start) {
return false;
}

if (index >= range.start and index < range.end) {
return true;
}
}

return false;
}

pub const OpCode = enum(u8) {
OP_CONSTANT,
OP_NULL,
Expand Down Expand Up @@ -278,11 +185,6 @@ const Self = @This();

pub const max_constants = std.math.maxInt(u24);

const InstructionRange = struct {
start: usize,
end: usize,
};

const RegistryContext = struct {
pub fn hash(_: RegistryContext, key: Self) u64 {
return std.hash.Wyhash.hash(
Expand Down
Loading
Loading