aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/base.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/base.zig')
-rw-r--r--src/bun.js/base.zig374
1 files changed, 372 insertions, 2 deletions
diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig
index 41454da3c..dd4f2ffc5 100644
--- a/src/bun.js/base.zig
+++ b/src/bun.js/base.zig
@@ -843,6 +843,7 @@ pub const ClassOptions = struct {
no_inheritance: bool = false,
singleton: bool = false,
ts: d.ts.decl = d.ts.decl{ .empty = 0 },
+ has_dom_calls: bool = false,
};
pub fn NewConstructor(
@@ -891,6 +892,7 @@ pub fn NewClassWithInstanceType(
pub const Zig = ZigType;
const ClassDefinitionCreator = @This();
const function_names = std.meta.fieldNames(@TypeOf(staticFunctions));
+ pub const functionDefinitions = staticFunctions;
const function_name_literals = function_names;
var function_name_refs: [function_names.len]js.JSStringRef = undefined;
var function_name_refs_set = false;
@@ -994,6 +996,16 @@ pub fn NewClassWithInstanceType(
return result;
}
+
+ pub fn putDOMCalls(globalThis: *JSC.JSGlobalObject, value: JSValue) void {
+ inline for (function_name_literals) |functionName| {
+ const Function = comptime @field(staticFunctions, functionName);
+ if (@TypeOf(Function) == type and @hasDecl(Function, "is_dom_call")) {
+ Function.put(globalThis, value);
+ }
+ }
+ }
+
pub fn GetClass(comptime ReceiverType: type) type {
const ClassGetter = struct {
get: fn (
@@ -1993,6 +2005,8 @@ pub fn NewClassWithInstanceType(
_ = i;
switch (@typeInfo(@TypeOf(@field(staticFunctions, function_name_literal)))) {
.Struct => {
+ const CtxField = @field(staticFunctions, function_name_literals[i]);
+
if (strings.eqlComptime(function_name_literal, "constructor")) {
def.callAsConstructor = To.JS.Constructor(staticFunctions.constructor.rfn).rfn;
} else if (strings.eqlComptime(function_name_literal, "finalize")) {
@@ -2021,8 +2035,7 @@ pub fn NewClassWithInstanceType(
def.getPropertyNames = @field(staticFunctions, "getPropertyNames").rfn;
} else if (strings.eqlComptime(function_name_literal, "convertToType")) {
def.convertToType = @field(staticFunctions, "convertToType").rfn;
- } else {
- const CtxField = @field(staticFunctions, function_name_literals[i]);
+ } else if (!@hasField(@TypeOf(CtxField), "is_dom_call")) {
if (!@hasField(@TypeOf(CtxField), "rfn")) {
@compileError("Expected " ++ options.name ++ "." ++ function_name_literal ++ " to have .rfn");
}
@@ -2789,6 +2802,363 @@ pub fn wrap(
return wrapWithHasContainer(Container, name, maybe_async, true, true);
}
+pub const DOMEffect = struct {
+ reads: [4]ID = std.mem.zeroes([4]ID),
+ writes: [4]ID = std.mem.zeroes([4]ID),
+
+ pub const top = DOMEffect{
+ .reads = .{ ID.Heap, ID.Heap, ID.Heap, ID.Heap },
+ .writes = .{ ID.Heap, ID.Heap, ID.Heap, ID.Heap },
+ };
+
+ pub fn forRead(read: ID) DOMEffect {
+ return DOMEffect{
+ .reads = .{ read, ID.Heap, ID.Heap, ID.Heap },
+ .writes = .{ ID.Heap, ID.Heap, ID.Heap, ID.Heap },
+ };
+ }
+
+ pub fn forWrite(read: ID) DOMEffect {
+ return DOMEffect{
+ .writes = .{ read, ID.Heap, ID.Heap, ID.Heap },
+ .reads = .{ ID.Heap, ID.Heap, ID.Heap, ID.Heap },
+ };
+ }
+
+ pub const pure = DOMEffect{};
+
+ pub fn isPure(this: DOMEffect) bool {
+ return this.reads[0] == ID.InvalidAbstractHeap and this.writes[0] == ID.InvalidAbstractHeap;
+ }
+
+ pub const ID = enum(u8) {
+ InvalidAbstractHeap = 0,
+ World,
+ Stack,
+ Heap,
+ Butterfly_publicLength,
+ Butterfly_vectorLength,
+ GetterSetter_getter,
+ GetterSetter_setter,
+ JSCell_cellState,
+ JSCell_indexingType,
+ JSCell_structureID,
+ JSCell_typeInfoFlags,
+ JSObject_butterfly,
+ JSPropertyNameEnumerator_cachedPropertyNames,
+ RegExpObject_lastIndex,
+ NamedProperties,
+ IndexedInt32Properties,
+ IndexedDoubleProperties,
+ IndexedContiguousProperties,
+ IndexedArrayStorageProperties,
+ DirectArgumentsProperties,
+ ScopeProperties,
+ TypedArrayProperties,
+ /// Used to reflect the fact that some allocations reveal object identity */
+ HeapObjectCount,
+ RegExpState,
+ MathDotRandomState,
+ JSDateFields,
+ JSMapFields,
+ JSSetFields,
+ JSWeakMapFields,
+ JSWeakSetFields,
+ JSInternalFields,
+ InternalState,
+ CatchLocals,
+ Absolute,
+ /// DOMJIT tells the heap range with the pair of integers. */
+ DOMState,
+ /// Use this for writes only, to indicate that this may fire watchpoints. Usually this is never directly written but instead we test to see if a node clobbers this; it just so happens that you have to write world to clobber it. */
+ Watchpoint_fire,
+ /// Use these for reads only, just to indicate that if the world got clobbered, then this operation will not work. */
+ MiscFields,
+ /// Use this for writes only, just to indicate that hoisting the node is invalid. This works because we don't hoist anything that has any side effects at all. */
+ SideState,
+ };
+};
+
+fn DOMCallArgumentType(comptime Type: type) []const u8 {
+ const ChildType = if (@typeInfo(Type) == .Pointer) std.meta.Child(Type) else Type;
+ return switch (ChildType) {
+ i32 => "JSC::SpecInt32Only",
+ bool => "JSC::SpecBoolean",
+ JSC.JSString => "JSC::SpecString",
+ JSC.JSUint8Array => "JSC::SpecUint8Array",
+ else => @compileError("Unknown DOM type: " ++ @typeName(Type)),
+ };
+}
+
+fn DOMCallArgumentTypeWrapper(comptime Type: type) []const u8 {
+ const ChildType = if (@typeInfo(Type) == .Pointer) std.meta.Child(Type) else Type;
+ return switch (ChildType) {
+ i32 => "int32_t",
+ bool => "bool",
+ JSC.JSString => "JSC::JSString*",
+ JSC.JSUint8Array => "JSC::JSUint8Array*",
+ else => @compileError("Unknown DOM type: " ++ @typeName(Type)),
+ };
+}
+
+fn DOMCallResultType(comptime Type: type) []const u8 {
+ const ChildType = if (@typeInfo(Type) == .Pointer) std.meta.Child(Type) else Type;
+ return switch (ChildType) {
+ i32 => "JSC::SpecInt32Only",
+ bool => "JSC::SpecBoolean",
+ JSC.JSString => "JSC::SpecString",
+ JSC.JSUint8Array => "JSC::SpecUint8Array",
+ JSC.JSCell => "JSC::SpecCell",
+ f64 => "JSC::SpecNonIntAsDouble",
+ else => "JSC::SpecHeapTop",
+ };
+}
+
+pub fn DOMCall(
+ comptime class_name: string,
+ comptime Container: type,
+ comptime functionName: string,
+ comptime ResultType: type,
+ comptime dom_effect: DOMEffect,
+) type {
+ return extern struct {
+ const className = class_name;
+ pub const is_dom_call = true;
+ const Slowpath = @field(Container, functionName);
+ const SlowpathType = @TypeOf(@field(Container, functionName));
+ pub const shim = JSC.Shimmer(className, functionName, @This());
+ pub const name = class_name ++ "__" ++ functionName;
+
+ // Zig doesn't support @frameAddress(1)
+ // so we have to add a small wrapper fujnction
+ pub fn slowpath(
+ globalObject: *JSC.JSGlobalObject,
+ thisValue: JSC.JSValue,
+ arguments_ptr: [*]const JSC.JSValue,
+ arguments_len: usize,
+ ) callconv(.C) JSValue {
+ return @call(.{}, @field(Container, functionName), .{
+ globalObject,
+ thisValue,
+ arguments_ptr[0..arguments_len],
+ });
+ }
+
+ pub const fastpath = @field(Container, functionName ++ "WithoutTypeChecks");
+ pub const Fastpath = @TypeOf(fastpath);
+ pub const Arguments = std.meta.ArgsTuple(Fastpath);
+
+ pub const Export = shim.exportFunctions(.{
+ .@"slowpath" = slowpath,
+ .@"fastpath" = fastpath,
+ });
+
+ pub fn put(globalObject: *JSC.JSGlobalObject, value: JSValue) void {
+ shim.cppFn("put", .{ globalObject, value });
+ }
+
+ pub const effect = dom_effect;
+
+ pub fn printGenerateDOMJITSignature(comptime Writer: type, writer: Writer) !void {
+ const signatureName = "DOMJIT_" ++ shim.name ++ "_signature";
+ const slowPathName = Export[0].symbol_name;
+ const fastPathName = Export[1].symbol_name;
+ const Fields: []const std.builtin.Type.StructField = std.meta.fields(Arguments);
+
+ const options = .{
+ .name = functionName,
+ .exportName = name ++ "__put",
+ .signatureName = signatureName,
+ .IDLResultName = DOMCallResultType(ResultType),
+ .fastPathName = fastPathName,
+ .slowPathName = slowPathName,
+ .argumentsCount = Fields.len - 2,
+ };
+ {
+ const fmt =
+ \\extern "C" JSC_DECLARE_HOST_FUNCTION({[slowPathName]s}Wrapper);
+ \\extern "C" JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL({[fastPathName]s}Wrapper, EncodedJSValue, (JSC::JSGlobalObject* lexicalGlobalObject, void* thisValue
+ ;
+ try writer.print(fmt, .{ .fastPathName = options.fastPathName, .slowPathName = options.slowPathName });
+ }
+ {
+ switch (Fields.len - 2) {
+ 0 => @compileError("Must be > 0 arguments"),
+ 1 => {
+ try writer.writeAll(", ");
+ try writer.writeAll(DOMCallArgumentTypeWrapper(Fields[2].field_type));
+ try writer.writeAll("));\n");
+ },
+ 2 => {
+ try writer.writeAll(", ");
+ try writer.writeAll(DOMCallArgumentTypeWrapper(Fields[2].field_type));
+ try writer.writeAll(", ");
+ try writer.writeAll(DOMCallArgumentTypeWrapper(Fields[3].field_type));
+ try writer.writeAll("));\n");
+ },
+ else => @compileError("Must be <= 3 arguments"),
+ }
+ }
+
+ {
+ const fmt =
+ \\
+ \\JSC_DEFINE_JIT_OPERATION({[fastPathName]s}Wrapper, EncodedJSValue, (JSC::JSGlobalObject* lexicalGlobalObject, void* thisValue
+ ;
+ try writer.print(fmt, .{ .fastPathName = options.fastPathName });
+ }
+ {
+ switch (Fields.len - 2) {
+ 0 => @compileError("Must be > 0 arguments"),
+ 1 => {
+ try writer.writeAll(", ");
+ try writer.writeAll(DOMCallArgumentTypeWrapper(Fields[2].field_type));
+ try writer.writeAll(" arg1)) {\n");
+ },
+ 2 => {
+ try writer.writeAll(", ");
+ try writer.writeAll(DOMCallArgumentTypeWrapper(Fields[2].field_type));
+ try writer.writeAll("arg1, ");
+ try writer.writeAll(DOMCallArgumentTypeWrapper(Fields[3].field_type));
+ try writer.writeAll(" arg2)) {\n");
+ },
+ else => @compileError("Must be <= 3 arguments"),
+ }
+ {
+ const fmt =
+ \\VM& vm = JSC::getVM(lexicalGlobalObject);
+ \\IGNORE_WARNINGS_BEGIN("frame-address")
+ \\CallFrame* callFrame = DECLARE_CALL_FRAME(vm);
+ \\IGNORE_WARNINGS_END
+ \\JSC::JITOperationPrologueCallFrameTracer tracer(vm, callFrame);
+ \\return {[fastPathName]s}(lexicalGlobalObject, thisValue
+ ;
+ try writer.print(fmt, .{ .fastPathName = options.fastPathName });
+ }
+ {
+ switch (Fields.len - 2) {
+ 0 => @compileError("Must be > 0 arguments"),
+ 1 => {
+ try writer.writeAll(", arg1);\n}\n");
+ },
+ 2 => {
+ try writer.writeAll(", arg1, arg2);\n}\n");
+ },
+ else => @compileError("Must be <= 3 arguments"),
+ }
+ }
+ }
+
+ {
+ const fmt =
+ \\JSC_DEFINE_HOST_FUNCTION({[slowPathName]s}Wrapper, (JSC::JSGlobalObject *globalObject, JSC::CallFrame* frame)) {{
+ \\ return {[slowPathName]s}(globalObject, JSValue::encode(frame->thisValue()), reinterpret_cast<JSC::EncodedJSValue*>(frame->addressOfArgumentsStart()), frame->argumentCount());
+ \\}}
+ \\
+ \\extern "C" void {[exportName]s}(JSC::JSGlobalObject *globalObject, JSC::EncodedJSValue value) {{
+ \\ JSC::JSObject *thisObject = JSC::jsCast<JSC::JSObject *>(JSC::JSValue::decode(value));
+ \\ static const JSC::DOMJIT::Signature {[signatureName]s}(
+ \\ {[fastPathName]s}Wrapper,
+ \\ thisObject->classInfo(),
+ \\
+ ;
+
+ try writer.print(fmt, .{
+ .slowPathName = options.slowPathName,
+ .exportName = options.exportName,
+ .fastPathName = options.fastPathName,
+ .signatureName = options.signatureName,
+ });
+ }
+ if (effect.isPure()) {
+ try writer.writeAll("JSC::DOMJIT::Effect::forPure(),\n ");
+ } else if (effect.writes[0] == DOMEffect.pure.writes[0]) {
+ try writer.print(
+ "JSC::DOMJIT::Effect::forReadKinds(JSC::DFG::AbstractHeapKind::{s}, JSC::DFG::AbstractHeapKind::{s}, JSC::DFG::AbstractHeapKind::{s}, JSC::DFG::AbstractHeapKind::{s}),\n ",
+ .{
+ @tagName(effect.reads[0]),
+ @tagName(effect.reads[1]),
+ @tagName(effect.reads[2]),
+ @tagName(effect.reads[3]),
+ },
+ );
+ } else if (effect.reads[0] == DOMEffect.pure.reads[0]) {
+ try writer.print(
+ "JSC::DOMJIT::Effect::forWriteKinds(JSC::DFG::AbstractHeapKind::{s}, JSC::DFG::AbstractHeapKind::{s}, JSC::DFG::AbstractHeapKind::{s}, JSC::DFG::AbstractHeapKind::{s}),\n ",
+ .{
+ @tagName(effect.writes[0]),
+ @tagName(effect.writes[1]),
+ @tagName(effect.writes[2]),
+ @tagName(effect.writes[3]),
+ },
+ );
+ } else {
+ try writer.writeAll("JSC::DOMJIT::Effect::forReadWrite(JSC::DOMJIT::HeapRange::top(), JSC::DOMJIT::HeapRange::top()),\n ");
+ }
+
+ {
+ try writer.writeAll(DOMCallResultType(ResultType));
+ try writer.writeAll(",\n ");
+ }
+
+ switch (Fields.len - 2) {
+ 0 => @compileError("Must be > 0 arguments"),
+ 1 => {
+ try writer.writeAll(DOMCallArgumentType(Fields[2].field_type));
+ try writer.writeAll("\n ");
+ },
+ 2 => {
+ try writer.writeAll(DOMCallArgumentType(Fields[2].field_type));
+ try writer.writeAll(",\n ");
+ try writer.writeAll(DOMCallArgumentType(Fields[3].field_type));
+ try writer.writeAll("\n ");
+ },
+ else => @compileError("Must be <= 3 arguments"),
+ }
+
+ try writer.writeAll(");\n ");
+
+ {
+ const fmt =
+ \\ JSFunction* function = JSFunction::create(
+ \\ globalObject->vm(),
+ \\ globalObject,
+ \\ {[argumentsCount]d},
+ \\ String("{[name]s}"_s),
+ \\ {[slowPathName]s}Wrapper, ImplementationVisibility::Public, NoIntrinsic, {[slowPathName]s}Wrapper,
+ \\ &{[signatureName]s}
+ \\ );
+ \\ thisObject->putDirect(
+ \\ globalObject->vm(),
+ \\ Identifier::fromString(globalObject->vm(), "{[name]s}"_s),
+ \\ function,
+ \\ JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DOMJITFunction | 0
+ \\ );
+ \\}}
+ ;
+ try writer.print(fmt, .{
+ .argumentsCount = options.argumentsCount,
+ .name = options.name,
+ .slowPathName = options.slowPathName,
+ .signatureName = options.signatureName,
+ });
+ }
+ }
+
+ pub const Extern = [_][]const u8{"put"};
+
+ comptime {
+ if (!JSC.is_bindgen) {
+ @export(slowpath, .{ .name = Export[0].symbol_name });
+ @export(fastpath, .{ .name = Export[1].symbol_name });
+ } else {
+ _ = slowpath;
+ _ = fastpath;
+ }
+ }
+ };
+}
+
pub fn wrapWithHasContainer(
comptime Container: type,
comptime name: string,