mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 05:42:43 +00:00
290 lines
11 KiB
Zig
290 lines
11 KiB
Zig
/// Call Frame for JavaScript -> Native function calls. In Bun, it is
|
|
/// preferred to use the bindings generator instead of directly decoding
|
|
/// arguments. See `docs/project/bindgen.md`
|
|
pub const CallFrame = opaque {
|
|
/// A slice of all passed arguments to this function call.
|
|
pub fn arguments(self: *const CallFrame) []const JSValue {
|
|
return self.asUnsafeJSValueArray()[offset_first_argument..][0..self.argumentsCount()];
|
|
}
|
|
|
|
/// Usage: `const arg1, const arg2 = call_frame.argumentsAsArray(2);`
|
|
pub fn argumentsAsArray(call_frame: *const CallFrame, comptime count: usize) [count]JSValue {
|
|
const slice = call_frame.arguments();
|
|
var value: [count]JSValue = @splat(.js_undefined);
|
|
const n = @min(call_frame.argumentsCount(), count);
|
|
@memcpy(value[0..n], slice[0..n]);
|
|
return value;
|
|
}
|
|
|
|
/// This function protects out-of-bounds access by returning undefined
|
|
pub fn argument(self: *const CallFrame, i: usize) jsc.JSValue {
|
|
return if (self.argumentsCount() > i) self.arguments()[i] else .js_undefined;
|
|
}
|
|
|
|
pub fn argumentsCount(self: *const CallFrame) u32 {
|
|
return self.argumentCountIncludingThis() - 1;
|
|
}
|
|
|
|
/// When this CallFrame belongs to a constructor, this value is not the `this`
|
|
/// value, but instead the value of `new.target`.
|
|
pub fn this(self: *const CallFrame) jsc.JSValue {
|
|
return self.asUnsafeJSValueArray()[offset_this_argument];
|
|
}
|
|
|
|
/// `JSValue` for the current function being called.
|
|
pub fn callee(self: *const CallFrame) jsc.JSValue {
|
|
return self.asUnsafeJSValueArray()[offset_callee];
|
|
}
|
|
|
|
/// From JavaScriptCore/interpreter/CallFrame.h
|
|
///
|
|
/// | ...... | |
|
|
/// +----------------------------+ |
|
|
/// | argN | v lower address
|
|
/// +----------------------------+
|
|
/// | arg1 |
|
|
/// +----------------------------+
|
|
/// | arg0 |
|
|
/// +----------------------------+
|
|
/// | this |
|
|
/// +----------------------------+
|
|
/// | argumentCountIncludingThis |
|
|
/// +----------------------------+
|
|
/// | callee |
|
|
/// +----------------------------+
|
|
/// | codeBlock |
|
|
/// +----------------------------+
|
|
/// | return-address |
|
|
/// +----------------------------+
|
|
/// | callerFrame |
|
|
/// +----------------------------+ <- callee's cfr is pointing this address
|
|
/// | local0 |
|
|
/// +----------------------------+
|
|
/// | local1 |
|
|
/// +----------------------------+
|
|
/// | localN |
|
|
/// +----------------------------+
|
|
/// | ...... |
|
|
///
|
|
/// The proper return type of this should be []Register, but
|
|
inline fn asUnsafeJSValueArray(self: *const CallFrame) [*]const jsc.JSValue {
|
|
return @ptrCast(@alignCast(self));
|
|
}
|
|
|
|
// These constants are from JSC::CallFrameSlot in JavaScriptCore/interpreter/CallFrame.h
|
|
const offset_code_block = 2;
|
|
const offset_callee = offset_code_block + 1;
|
|
const offset_argument_count_including_this = offset_callee + 1;
|
|
const offset_this_argument = offset_argument_count_including_this + 1;
|
|
const offset_first_argument = offset_this_argument + 1;
|
|
|
|
/// This function is manually ported from JSC's equivalent function in C++
|
|
/// See JavaScriptCore/interpreter/CallFrame.h
|
|
fn argumentCountIncludingThis(self: *const CallFrame) u32 {
|
|
// Register defined in JavaScriptCore/interpreter/Register.h
|
|
const Register = extern union {
|
|
value: JSValue, // EncodedJSValue
|
|
call_frame: *CallFrame,
|
|
code_block: *anyopaque, // CodeBlock*
|
|
/// EncodedValueDescriptor defined in JavaScriptCore/runtime/JSCJSValue.h
|
|
encoded_value: extern union {
|
|
ptr: JSValue, // JSCell*
|
|
as_bits: extern struct {
|
|
payload: i32,
|
|
tag: i32,
|
|
},
|
|
},
|
|
number: f64, // double
|
|
integer: i64, // integer
|
|
};
|
|
const registers: [*]const Register = @alignCast(@ptrCast(self));
|
|
// argumentCountIncludingThis takes the register at the defined offset, then
|
|
// calls 'ALWAYS_INLINE int32_t Register::unboxedInt32() const',
|
|
// which in turn calls 'ALWAYS_INLINE int32_t Register::payload() const'
|
|
// which accesses `.encodedValue.asBits.payload`
|
|
// JSC stores and works with value as signed, but it is always 1 or more.
|
|
return @intCast(registers[offset_argument_count_including_this].encoded_value.as_bits.payload);
|
|
}
|
|
|
|
fn Arguments(comptime max: usize) type {
|
|
return struct {
|
|
ptr: [max]jsc.JSValue,
|
|
len: usize,
|
|
|
|
pub inline fn init(comptime i: usize, ptr: [*]const jsc.JSValue) @This() {
|
|
var args: [max]jsc.JSValue = std.mem.zeroes([max]jsc.JSValue);
|
|
args[0..i].* = ptr[0..i].*;
|
|
|
|
return @This(){
|
|
.ptr = args,
|
|
.len = i,
|
|
};
|
|
}
|
|
|
|
pub inline fn initUndef(comptime i: usize, ptr: [*]const jsc.JSValue) @This() {
|
|
var args: [max]jsc.JSValue = @splat(.js_undefined);
|
|
args[0..i].* = ptr[0..i].*;
|
|
return @This(){ .ptr = args, .len = i };
|
|
}
|
|
|
|
pub inline fn slice(self: *const @This()) []const JSValue {
|
|
return self.ptr[0..self.len];
|
|
}
|
|
|
|
pub inline fn mut(self: *@This()) []JSValue {
|
|
return self.ptr[0..];
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Do not use this function. Migration path:
|
|
/// arguments(n).ptr[k] -> argumentsAsArray(n)[k]
|
|
/// arguments(n).slice() -> arguments()
|
|
/// arguments(n).mut() -> `var args = argumentsAsArray(n); &args`
|
|
pub fn arguments_old(self: *const CallFrame, comptime max: usize) Arguments(max) {
|
|
const slice = self.arguments();
|
|
comptime bun.assert(max <= 15);
|
|
return switch (@as(u4, @min(slice.len, max))) {
|
|
0 => .{ .ptr = undefined, .len = 0 },
|
|
inline 1...15 => |count| Arguments(max).init(comptime @min(count, max), slice.ptr),
|
|
};
|
|
}
|
|
|
|
/// Do not use this function. Migration path:
|
|
/// argumentsAsArray(n)
|
|
pub fn argumentsUndef(self: *const CallFrame, comptime max: usize) Arguments(max) {
|
|
const slice = self.arguments();
|
|
comptime bun.assert(max <= 9);
|
|
return switch (@as(u4, @min(slice.len, max))) {
|
|
0 => .{ .ptr = @splat(.js_undefined), .len = 0 },
|
|
inline 1...9 => |count| Arguments(max).initUndef(@min(count, max), slice.ptr),
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
extern fn Bun__CallFrame__isFromBunMain(*const CallFrame, *const VM) bool;
|
|
pub const isFromBunMain = Bun__CallFrame__isFromBunMain;
|
|
|
|
extern fn Bun__CallFrame__getCallerSrcLoc(*const CallFrame, *JSGlobalObject, *bun.String, *c_uint, *c_uint) void;
|
|
pub const CallerSrcLoc = struct {
|
|
str: bun.String,
|
|
line: c_uint,
|
|
column: c_uint,
|
|
};
|
|
pub fn getCallerSrcLoc(call_frame: *const CallFrame, globalThis: *JSGlobalObject) CallerSrcLoc {
|
|
var str: bun.String = undefined;
|
|
var line: c_uint = undefined;
|
|
var column: c_uint = undefined;
|
|
Bun__CallFrame__getCallerSrcLoc(call_frame, globalThis, &str, &line, &column);
|
|
return .{
|
|
.str = str,
|
|
.line = line,
|
|
.column = column,
|
|
};
|
|
}
|
|
|
|
extern fn Bun__CallFrame__describeFrame(*const CallFrame) [*:0]const u8;
|
|
pub fn describeFrame(self: *const CallFrame) [:0]const u8 {
|
|
return std.mem.span(Bun__CallFrame__describeFrame(self));
|
|
}
|
|
|
|
/// This is an advanced iterator struct which is used by various APIs. In
|
|
/// Node.fs, `will_be_async` is set to true which allows string/path APIs to
|
|
/// know if they have to do threadsafe clones.
|
|
///
|
|
/// Prefer `Iterator` for a simpler iterator.
|
|
pub const ArgumentsSlice = struct {
|
|
remaining: []const jsc.JSValue,
|
|
vm: *jsc.VirtualMachine,
|
|
arena: bun.ArenaAllocator = bun.ArenaAllocator.init(bun.default_allocator),
|
|
all: []const jsc.JSValue,
|
|
threw: bool = false,
|
|
protected: bun.bit_set.IntegerBitSet(32) = bun.bit_set.IntegerBitSet(32).initEmpty(),
|
|
will_be_async: bool = false,
|
|
|
|
pub fn unprotect(slice: *ArgumentsSlice) void {
|
|
var iter = slice.protected.iterator(.{});
|
|
while (iter.next()) |i| {
|
|
slice.all[i].unprotect();
|
|
}
|
|
slice.protected = bun.bit_set.IntegerBitSet(32).initEmpty();
|
|
}
|
|
|
|
pub fn deinit(slice: *ArgumentsSlice) void {
|
|
slice.unprotect();
|
|
slice.arena.deinit();
|
|
}
|
|
|
|
pub fn protectEat(slice: *ArgumentsSlice) void {
|
|
if (slice.remaining.len == 0) return;
|
|
const index = slice.all.len - slice.remaining.len;
|
|
slice.protected.set(index);
|
|
slice.all[index].protect();
|
|
slice.eat();
|
|
}
|
|
|
|
pub fn protectEatNext(slice: *ArgumentsSlice) ?jsc.JSValue {
|
|
if (slice.remaining.len == 0) return null;
|
|
return slice.nextEat();
|
|
}
|
|
|
|
pub fn from(vm: *jsc.VirtualMachine, slice: []const jsc.JSValueRef) ArgumentsSlice {
|
|
return init(vm, @as([*]const jsc.JSValue, @ptrCast(slice.ptr))[0..slice.len]);
|
|
}
|
|
pub fn init(vm: *jsc.VirtualMachine, slice: []const jsc.JSValue) ArgumentsSlice {
|
|
return ArgumentsSlice{
|
|
.remaining = slice,
|
|
.vm = vm,
|
|
.all = slice,
|
|
.arena = bun.ArenaAllocator.init(vm.allocator),
|
|
};
|
|
}
|
|
|
|
pub fn initAsync(vm: *jsc.VirtualMachine, slice: []const jsc.JSValue) ArgumentsSlice {
|
|
return ArgumentsSlice{
|
|
.remaining = bun.default_allocator.dupe(jsc.JSValue, slice),
|
|
.vm = vm,
|
|
.all = slice,
|
|
.arena = bun.ArenaAllocator.init(bun.default_allocator),
|
|
};
|
|
}
|
|
|
|
pub inline fn len(slice: *const ArgumentsSlice) u16 {
|
|
return @as(u16, @truncate(slice.remaining.len));
|
|
}
|
|
|
|
pub fn eat(slice: *ArgumentsSlice) void {
|
|
if (slice.remaining.len == 0) {
|
|
return;
|
|
}
|
|
|
|
slice.remaining = slice.remaining[1..];
|
|
}
|
|
|
|
/// Peek the next argument without eating it
|
|
pub fn next(slice: *ArgumentsSlice) ?jsc.JSValue {
|
|
if (slice.remaining.len == 0) {
|
|
return null;
|
|
}
|
|
|
|
return slice.remaining[0];
|
|
}
|
|
|
|
pub fn nextEat(slice: *ArgumentsSlice) ?jsc.JSValue {
|
|
if (slice.remaining.len == 0) {
|
|
return null;
|
|
}
|
|
defer slice.eat();
|
|
return slice.remaining[0];
|
|
}
|
|
};
|
|
};
|
|
|
|
const bun = @import("bun");
|
|
const std = @import("std");
|
|
const VM = @import("./VM.zig").VM;
|
|
|
|
const jsc = bun.jsc;
|
|
const JSGlobalObject = jsc.JSGlobalObject;
|
|
const JSValue = jsc.JSValue;
|