[bun:ffi] it works

This commit is contained in:
Jarred Sumner
2022-04-29 23:21:14 -07:00
parent d49ba50289
commit 516b54578d
15 changed files with 1278 additions and 222 deletions

View File

@@ -18,6 +18,7 @@
/* 7.18.1.1 Exact-width integer types */
typedef signed char int8_t;
typedef unsigned char uint8_t;
typedef char int8_t;
typedef short int16_t;
typedef unsigned short uint16_t;
typedef int int32_t;

View File

@@ -77,6 +77,7 @@ const VirtualMachine = @import("../javascript.zig").VirtualMachine;
const IOTask = JSC.IOTask;
const is_bindgen = JSC.is_bindgen;
const max_addressible_memory = std.math.maxInt(u56);
threadlocal var css_imports_list_strings: [512]ZigString = undefined;
threadlocal var css_imports_list: [512]Api.StringPointer = undefined;
@@ -1121,12 +1122,6 @@ pub const Class = NewClass(
.sha = .{
.rfn = JSC.wrapWithHasContainer(Crypto.SHA512_256, "hash", false, false),
},
.dlprint = .{
.rfn = JSC.wrapWithHasContainer(JSC.FFI, "print", false, false),
},
.dlopen = .{
.rfn = JSC.wrapWithHasContainer(JSC.FFI, "open", false, false),
},
},
.{
.main = .{
@@ -1208,6 +1203,9 @@ pub const Class = NewClass(
.SHA512_256 = .{
.get = Crypto.SHA512_256.getter,
},
.FFI = .{
.get = FFI.getter,
},
},
);
@@ -1862,112 +1860,10 @@ pub const Unsafe = struct {
.arrayBufferToString = .{
.rfn = arrayBufferToString,
},
.arrayBufferToPtr = .{
.rfn = JSC.wrapWithHasContainer(Unsafe, "arrayBufferToPtr", false, false),
},
.arrayBufferFromPtr = .{
.rfn = JSC.wrapWithHasContainer(Unsafe, "arrayBufferFromPtr", false, false),
},
.bufferFromPtr = .{
.rfn = JSC.wrapWithHasContainer(Unsafe, "bufferFromPtr", false, false),
},
},
.{},
);
const ValueOrError = union(enum) {
err: JSValue,
slice: []u8,
};
pub fn arrayBufferToPtr(globalThis: *JSGlobalObject, value: JSValue) JSValue {
if (value.isEmpty()) {
return JSC.JSValue.jsNull();
}
const array_buffer = value.asArrayBuffer(globalThis) orelse {
return JSC.toInvalidArguments("Expected ArrayBufferView", .{}, globalThis.ref());
};
if (array_buffer.len == 0) {
return JSC.toInvalidArguments("ArrayBufferView must have a length > 0. A pointer to empty memory doesn't work", .{}, globalThis.ref());
}
return JSC.JSValue.jsNumber(@bitCast(f64, @ptrToInt(array_buffer.ptr)));
}
fn getPtrSlice(globalThis: *JSGlobalObject, value: JSValue, valueLength: JSValue) ValueOrError {
if (!value.isNumber()) {
return .{ .err = JSC.toInvalidArguments("ptr must be a number.", .{}, globalThis.ref()) };
}
const num = value.asNumber();
if (num == 0) {
return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis.ref()) };
}
if (!std.math.isFinite(num)) {
return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis.ref()) };
}
const addr = @bitCast(usize, num);
if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
return .{ .err = JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis.ref()) };
}
if (!valueLength.isNumber()) {
return .{ .err = JSC.toInvalidArguments("length must be a number.", .{}, globalThis.ref()) };
}
if (valueLength.asNumber() == 0.0) {
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
}
const length_i = valueLength.toInt64();
if (length_i < 0) {
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
}
if (length_i > std.math.maxInt(u48)) {
return .{ .err = JSC.toInvalidArguments("length exceeds max addressable memory. This usually means a bug in your code.", .{}, globalThis.ref()) };
}
const length = @intCast(usize, length_i);
return .{ .slice = @intToPtr([*]u8, addr)[0..length] };
}
pub fn arrayBufferFromPtr(
globalThis: *JSGlobalObject,
value: JSValue,
valueLength: JSValue,
) JSC.JSValue {
switch (getPtrSlice(globalThis, value, valueLength)) {
.err => |erro| {
return erro;
},
.slice => |slice| {
return JSC.ArrayBuffer.fromBytes(slice, JSC.JSValue.JSType.ArrayBuffer).toJSWithContext(globalThis.ref(), null, null, null);
},
}
}
pub fn bufferFromPtr(
globalThis: *JSGlobalObject,
value: JSValue,
valueLength: JSValue,
) JSC.JSValue {
switch (getPtrSlice(globalThis, value, valueLength)) {
.err => |erro| {
return erro;
},
.slice => |slice| {
return JSC.JSValue.createBuffer(globalThis, slice, null);
},
}
}
// For testing the segfault handler
pub fn __debug__doSegfault(
_: void,
@@ -2353,6 +2249,265 @@ pub const Timer = struct {
}
};
pub const FFI = struct {
pub const Class = NewClass(
void,
.{
.name = "FFI",
},
.{
.viewSource = .{
.rfn = JSC.wrapWithHasContainer(JSC.FFI, "print", false, false),
},
.dlopen = .{
.rfn = JSC.wrapWithHasContainer(JSC.FFI, "open", false, false),
},
.ptr = .{
.rfn = JSC.wrapWithHasContainer(@This(), "ptr", false, false),
},
.toBuffer = .{
.rfn = JSC.wrapWithHasContainer(@This(), "toBuffer", false, false),
},
.toArrayBuffer = .{
.rfn = JSC.wrapWithHasContainer(@This(), "toArrayBuffer", false, false),
},
},
.{
.CString = .{
.get = UnsafeCString.getter,
},
},
);
pub fn ptr(
globalThis: *JSGlobalObject,
value: JSValue,
byteOffset: ?JSValue,
) JSValue {
if (value.isEmpty()) {
return JSC.JSValue.jsNull();
}
const array_buffer = value.asArrayBuffer(globalThis) orelse {
return JSC.toInvalidArguments("Expected ArrayBufferView", .{}, globalThis.ref());
};
if (array_buffer.len == 0) {
return JSC.toInvalidArguments("ArrayBufferView must have a length > 0. A pointer to empty memory doesn't work", .{}, globalThis.ref());
}
var addr: usize = @ptrToInt(array_buffer.ptr);
if (byteOffset) |off| {
if (!off.isEmptyOrUndefinedOrNull()) {
if (!off.isNumber()) {
return JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis.ref());
}
}
const bytei64 = off.toInt64();
if (bytei64 < 0) {
addr -|= @intCast(usize, bytei64 * -1);
} else {
addr += @intCast(usize, bytei64);
}
if (addr > @ptrToInt(array_buffer.ptr) + @as(usize, array_buffer.byte_len)) {
return JSC.toInvalidArguments("byteOffset out of bounds", .{}, globalThis.ref());
}
}
if (addr > max_addressible_memory) {
return JSC.toInvalidArguments("Pointer is outside max addressible memory, which usually means a bug in your program.", .{}, globalThis.ref());
}
if (addr == 0) {
return JSC.toInvalidArguments("Pointer must not be 0", .{}, globalThis.ref());
}
if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
return JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis.ref());
}
// truncate to 56 bits to clear any pointer tags
return JSC.JSValue.jsNumber(@bitCast(f64, @as(usize, @truncate(u56, addr))));
}
const ValueOrError = union(enum) {
err: JSValue,
slice: []u8,
};
pub fn getPtrSlice(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, byteLength: ?JSValue) ValueOrError {
if (!value.isNumber()) {
return .{ .err = JSC.toInvalidArguments("ptr must be a number.", .{}, globalThis.ref()) };
}
const num = value.asNumber();
if (num == 0) {
return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis.ref()) };
}
if (!std.math.isFinite(num)) {
return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis.ref()) };
}
var addr = @bitCast(usize, num);
if (byteOffset) |byte_off| {
if (byte_off.isNumber()) {
const off = byte_off.toInt64();
if (off < 0) {
addr -|= @intCast(usize, off * -1);
} else {
addr +|= @intCast(usize, off);
}
if (addr == 0) {
return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis.ref()) };
}
if (!std.math.isFinite(byte_off.asNumber())) {
return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis.ref()) };
}
} else if (!byte_off.isEmptyOrUndefinedOrNull()) {
// do nothing
} else {
return .{ .err = JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis.ref()) };
}
}
if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
return .{ .err = JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis.ref()) };
}
if (byteLength) |valueLength| {
if (!valueLength.isEmptyOrUndefinedOrNull()) {
if (!valueLength.isNumber()) {
return .{ .err = JSC.toInvalidArguments("length must be a number.", .{}, globalThis.ref()) };
}
if (valueLength.asNumber() == 0.0) {
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
}
const length_i = valueLength.toInt64();
if (length_i < 0) {
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
}
if (length_i > max_addressible_memory) {
return .{ .err = JSC.toInvalidArguments("length exceeds max addressable memory. This usually means a bug in your code.", .{}, globalThis.ref()) };
}
const length = @intCast(usize, length_i);
return .{ .slice = @intToPtr([*]u8, addr)[0..length] };
}
}
return .{ .slice = std.mem.sliceTo(@intToPtr([*:0]u8, addr), 0) };
}
pub fn toArrayBuffer(
globalThis: *JSGlobalObject,
value: JSValue,
byteOffset: ?JSValue,
valueLength: ?JSValue,
) JSC.JSValue {
switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
.err => |erro| {
return erro;
},
.slice => |slice| {
return JSC.ArrayBuffer.fromBytes(slice, JSC.JSValue.JSType.ArrayBuffer).toJSWithContext(globalThis.ref(), null, null, null);
},
}
}
pub fn toBuffer(
globalThis: *JSGlobalObject,
value: JSValue,
byteOffset: ?JSValue,
valueLength: ?JSValue,
) JSC.JSValue {
switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
.err => |erro| {
return erro;
},
.slice => |slice| {
return JSC.JSValue.createBuffer(globalThis, slice, null);
},
}
}
pub fn getter(
_: void,
ctx: js.JSContextRef,
_: js.JSValueRef,
_: js.JSStringRef,
_: js.ExceptionRef,
) js.JSValueRef {
var existing = ctx.ptr().getCachedObject(&ZigString.init("FFI"));
if (existing.isEmpty()) {
var prototype = JSC.C.JSObjectMake(ctx, FFI.Class.get().?[0], null);
var base = JSC.C.JSObjectMake(ctx, null, null);
JSC.C.JSObjectSetPrototype(ctx, base, prototype);
return ctx.ptr().putCachedObject(
&ZigString.init("FFI"),
JSValue.fromRef(base),
).asObjectRef();
}
return existing.asObjectRef();
}
};
pub const UnsafeCString = struct {
pub fn constructor(
ctx: js.JSContextRef,
_: js.JSObjectRef,
len: usize,
args: [*c]const js.JSValueRef,
exception: js.ExceptionRef,
) callconv(.C) js.JSObjectRef {
if (len == 0) {
JSC.throwInvalidArguments("Expected a ptr", .{}, ctx, exception);
return null;
}
return newCString(ctx.ptr(), JSC.JSValue.fromRef(args[0]), if (len > 1) JSC.JSValue.fromRef(args[1]) else null, if (len > 2) JSC.JSValue.fromRef(args[2]) else null).asObjectRef();
}
pub fn newCString(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, lengthValue: ?JSValue) JSC.JSValue {
switch (FFI.getPtrSlice(globalThis, value, byteOffset, lengthValue)) {
.err => |err| {
return err;
},
.slice => |slice| {
return WebCore.Encoder.toString(slice.ptr, slice.len, globalThis, .utf8);
},
}
}
pub fn getter(
_: void,
ctx: js.JSContextRef,
_: js.JSValueRef,
_: js.JSStringRef,
_: js.ExceptionRef,
) js.JSValueRef {
var existing = ctx.ptr().getCachedObject(&ZigString.init("UnsafeCString"));
if (existing.isEmpty()) {
return ctx.ptr().putCachedObject(
&ZigString.init("UnsafeCString"),
JSValue.fromRef(JSC.C.JSObjectMakeConstructor(ctx, null, constructor)),
).asObjectRef();
}
return existing.asObjectRef();
}
};
/// EnvironmentVariables is runtime defined.
/// Also, you can't iterate over process.env normally since it only exists at build-time otherwise
// This is aliased to Bun.env

View File

@@ -98,12 +98,14 @@ pub const FFI = struct {
this.closed = true;
this.dylib.close();
for (this.functions.values()) |*val| {
VirtualMachine.vm.allocator.free(bun.constStrToU8(std.mem.span(val.base_name)));
const allocator = VirtualMachine.vm.allocator;
val.arg_types.deinit(VirtualMachine.vm.allocator);
for (this.functions.values()) |*val| {
allocator.free(bun.constStrToU8(std.mem.span(val.base_name)));
val.arg_types.deinit(allocator);
}
this.functions.deinit(VirtualMachine.vm.allocator);
this.functions.deinit(allocator);
return JSC.JSValue.jsUndefined();
}
@@ -312,9 +314,28 @@ pub const FFI = struct {
try abi_types.ensureTotalCapacityPrecise(allocator, array.len);
while (array.next()) |val| {
if (val.isEmptyOrUndefinedOrNull() or !val.jsType().isStringLike()) {
if (val.isEmptyOrUndefinedOrNull()) {
abi_types.clearAndFree(allocator);
return ZigString.init("param must be a string (type name)").toErrorInstance(global);
return ZigString.init("param must be a string (type name) or number").toErrorInstance(global);
}
if (val.isAnyInt()) {
const int = val.toInt32();
switch (int) {
0...13 => {
abi_types.appendAssumeCapacity(@intToEnum(ABIType, int));
continue;
},
else => {
abi_types.clearAndFree(allocator);
return ZigString.init("invalid ABI type").toErrorInstance(global);
},
}
}
if (!val.jsType().isStringLike()) {
abi_types.clearAndFree(allocator);
return ZigString.init("param must be a string (type name) or number").toErrorInstance(global);
}
var type_name = val.toSlice(global, allocator);
@@ -581,39 +602,78 @@ pub const FFI = struct {
@"void" = 13,
pub const label = ComptimeStringMap(ABIType, .{
.{ "bool", .bool },
.{ "c_int", .int32_t },
.{ "c_uint", .uint32_t },
.{ "char", .char },
.{ "char*", .ptr },
.{ "double", .double },
.{ "f32", .float },
.{ "f64", .double },
.{ "float", .float },
.{ "i16", .int16_t },
.{ "i32", .int32_t },
.{ "i64", .int64_t },
.{ "i8", .int8_t },
.{ "int", .int32_t },
.{ "int16_t", .int16_t },
.{ "int32_t", .int32_t },
.{ "int64_t", .int64_t },
.{ "int8_t", .int8_t },
.{ "isize", .int64_t },
.{ "u16", .uint16_t },
.{ "u32", .uint32_t },
.{ "u64", .uint64_t },
.{ "u8", .uint8_t },
.{ "uint16_t", .uint16_t },
.{ "uint32_t", .uint32_t },
.{ "uint64_t", .uint64_t },
.{ "uint8_t", .uint8_t },
.{ "usize", .uint64_t },
.{ "void*", .ptr },
.{ "ptr", .ptr },
.{ "pointer", .ptr },
});
const map = .{
.{ "bool", ABIType.bool },
.{ "c_int", ABIType.int32_t },
.{ "c_uint", ABIType.uint32_t },
.{ "char", ABIType.char },
.{ "char*", ABIType.ptr },
.{ "double", ABIType.double },
.{ "f32", ABIType.float },
.{ "f64", ABIType.double },
.{ "float", ABIType.float },
.{ "i16", ABIType.int16_t },
.{ "i32", ABIType.int32_t },
.{ "i64", ABIType.int64_t },
.{ "i8", ABIType.int8_t },
.{ "int", ABIType.int32_t },
.{ "int16_t", ABIType.int16_t },
.{ "int32_t", ABIType.int32_t },
.{ "int64_t", ABIType.int64_t },
.{ "int8_t", ABIType.int8_t },
.{ "isize", ABIType.int64_t },
.{ "u16", ABIType.uint16_t },
.{ "u32", ABIType.uint32_t },
.{ "u64", ABIType.uint64_t },
.{ "u8", ABIType.uint8_t },
.{ "uint16_t", ABIType.uint16_t },
.{ "uint32_t", ABIType.uint32_t },
.{ "uint64_t", ABIType.uint64_t },
.{ "uint8_t", ABIType.uint8_t },
.{ "usize", ABIType.uint64_t },
.{ "void*", ABIType.ptr },
.{ "ptr", ABIType.ptr },
.{ "pointer", ABIType.ptr },
};
pub const label = ComptimeStringMap(ABIType, map);
const EnumMapFormatter = struct {
name: []const u8,
entry: ABIType,
pub fn format(self: EnumMapFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try writer.writeAll("['");
// these are not all valid identifiers
try writer.writeAll(self.name);
try writer.writeAll("']:");
try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer);
try writer.writeAll(",'");
try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer);
try writer.writeAll("':");
try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer);
}
};
pub const map_to_js_object = brk: {
var count: usize = 2;
for (map) |item, i| {
var fmt = EnumMapFormatter{ .name = item.@"0", .entry = item.@"1" };
count += std.fmt.count("{}", .{fmt});
count += @boolToInt(i > 0);
}
var buf: [count]u8 = undefined;
buf[0] = '{';
buf[buf.len - 1] = '}';
var end: usize = 1;
for (map) |item, i| {
var fmt = EnumMapFormatter{ .name = item.@"0", .entry = item.@"1" };
if (i > 0) {
buf[end] = ',';
end += 1;
}
end += (std.fmt.bufPrint(buf[end..], "{}", .{fmt}) catch unreachable).len;
}
break :brk buf;
};
pub fn isFloatingPoint(this: ABIType) bool {
return switch (this) {

View File

@@ -2882,6 +2882,9 @@ pub fn wrapWithHasContainer(
};
args[i] = val;
},
?JSValue => {
args[i] = iter.protectEatNext();
},
else => @compileError("Unexpected Type " ++ @typeName(ArgType)),
}
}
@@ -2921,3 +2924,51 @@ pub fn wrapWithHasContainer(
}
}.callback;
}
pub fn cachedBoundFunction(comptime name: [:0]const u8, comptime callback: anytype) (fn (
_: void,
ctx: js.JSContextRef,
_: js.JSValueRef,
_: js.JSStringRef,
_: js.ExceptionRef,
) js.JSValueRef) {
return struct {
const name_ = name;
pub fn call(
arg2: js.JSContextRef,
arg3: js.JSObjectRef,
arg4: js.JSObjectRef,
arg5: usize,
arg6: [*c]const js.JSValueRef,
arg7: js.ExceptionRef,
) callconv(.C) js.JSObjectRef {
return callback(
{},
arg2,
arg3,
arg4,
arg6[0..arg5],
arg7,
);
}
pub fn getter(
_: void,
ctx: js.JSContextRef,
_: js.JSValueRef,
_: js.JSStringRef,
_: js.ExceptionRef,
) js.JSValueRef {
const name_slice = std.mem.span(name_);
var existing = ctx.ptr().getCachedObject(&ZigString.init(name_slice));
if (existing.isEmpty()) {
return ctx.ptr().putCachedObject(
&ZigString.init(name_slice),
JSValue.fromRef(JSC.C.JSObjectMakeFunctionWithCallback(ctx, JSC.C.JSStringCreateStatic(name_slice.ptr, name_slice.len), call)),
).asObjectRef();
}
return existing.asObjectRef();
}
}.getter;
}

View File

@@ -0,0 +1,7 @@
export const ptr = globalThis.Bun.FFI.ptr;
export const toBuffer = globalThis.Bun.FFI.toBuffer;
export const toArrayBuffer = globalThis.Bun.FFI.toArrayBuffer;
export const CString = globalThis.Bun.FFI.CString;
export const dlopen = globalThis.Bun.FFI.dlopen;
export const viewSource = globalThis.Bun.FFI.viewSource;
// --- FFIType ---

View File

@@ -1078,6 +1078,14 @@ pub const VirtualMachine = struct {
.source_url = ZigString.init("node:path"),
.hash = 0,
};
} else if (strings.eqlComptime(_specifier, "bun:ffi")) {
return ResolvedSource{
.allocator = null,
.source_code = ZigString.init(@embedFile("ffi.exports.js") ++ "export const FFIType = " ++ JSC.FFI.ABIType.map_to_js_object ++ ";\n"),
.specifier = ZigString.init("bun:ffi"),
.source_url = ZigString.init("bun:ffi"),
.hash = 0,
};
}
const specifier = normalizeSpecifier(_specifier);
@@ -1303,11 +1311,14 @@ pub const VirtualMachine = struct {
ret.result = null;
ret.path = "node:fs";
return;
}
if (strings.eqlComptime(specifier, "node:path")) {
} else if (strings.eqlComptime(specifier, "node:path")) {
ret.result = null;
ret.path = "node:path";
return;
} else if (strings.eqlComptime(specifier, "bun:ffi")) {
ret.result = null;
ret.path = "bun:ffi";
return;
}
const is_special_source = strings.eqlComptime(source, main_file_name) or js_ast.Macro.isMacroPath(source);