mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 11:29:02 +00:00
[bun ffi] Support pointers
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
bool returns_true();
|
||||
bool returns_false();
|
||||
@@ -83,3 +84,13 @@ uint8_t add_uint8_t(uint8_t a, uint8_t b) { return a + b; }
|
||||
uint16_t add_uint16_t(uint16_t a, uint16_t b) { return a + b; }
|
||||
uint32_t add_uint32_t(uint32_t a, uint32_t b) { return a + b; }
|
||||
uint64_t add_uint64_t(uint64_t a, uint64_t b) { return a + b; }
|
||||
|
||||
void *ptr_should_point_to_42_as_int32_t();
|
||||
void *ptr_should_point_to_42_as_int32_t() {
|
||||
int32_t *ptr = malloc(sizeof(int32_t));
|
||||
*ptr = 42;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool does_pointer_equal_42_as_int32_t(int32_t *ptr);
|
||||
bool does_pointer_equal_42_as_int32_t(int32_t *ptr) { return *ptr == 42; }
|
||||
|
||||
@@ -153,6 +153,16 @@ it("ffi run", () => {
|
||||
return_type: "uint32_t",
|
||||
params: ["uint32_t", "uint32_t"],
|
||||
},
|
||||
|
||||
does_pointer_equal_42_as_int32_t: {
|
||||
return_type: "bool",
|
||||
params: ["ptr"],
|
||||
},
|
||||
|
||||
ptr_should_point_to_42_as_int32_t: {
|
||||
return_type: "ptr",
|
||||
params: [],
|
||||
},
|
||||
// add_uint64_t: {
|
||||
// return_type: "uint64_t",
|
||||
// params: ["uint64_t", "uint64_t"],
|
||||
@@ -197,6 +207,8 @@ it("ffi run", () => {
|
||||
add_uint16_t,
|
||||
add_uint32_t,
|
||||
add_uint64_t,
|
||||
does_pointer_equal_42_as_int32_t,
|
||||
ptr_should_point_to_42_as_int32_t,
|
||||
},
|
||||
close,
|
||||
} = Bun.dlopen("/tmp/bun-ffi-test.dylib", types);
|
||||
@@ -236,6 +248,11 @@ it("ffi run", () => {
|
||||
expect(add_uint8_t(1, 1)).toBe(2);
|
||||
expect(add_uint16_t(1, 1)).toBe(2);
|
||||
expect(add_uint32_t(1, 1)).toBe(2);
|
||||
|
||||
const ptr = ptr_should_point_to_42_as_int32_t();
|
||||
expect(ptr != 0).toBe(true);
|
||||
expect(typeof ptr === "number").toBe(true);
|
||||
expect(does_pointer_equal_42_as_int32_t(ptr)).toBe(true);
|
||||
// expect(add_uint64_t(1, 1)).toBe(2);
|
||||
close();
|
||||
});
|
||||
|
||||
@@ -80,12 +80,26 @@ static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inli
|
||||
static EncodedJSValue DOUBLE_TO_JSVALUE(double val) __attribute__((__always_inline__));
|
||||
static EncodedJSValue FLOAT_TO_JSVALUE(float val) __attribute__((__always_inline__));
|
||||
static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) __attribute__((__always_inline__));
|
||||
static EncodedJSValue PTR_TO_JSVALUE(void* ptr) __attribute__((__always_inline__));
|
||||
|
||||
static void* JSVALUE_TO_PTR(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static int32_t JSVALUE_TO_INT32(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static float JSVALUE_TO_FLOAT(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static double JSVALUE_TO_DOUBLE(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
|
||||
static void* JSVALUE_TO_PTR(EncodedJSValue val) {
|
||||
// must be a double
|
||||
return (void*)(val.asInt64 - DoubleEncodeOffset);
|
||||
}
|
||||
|
||||
static EncodedJSValue PTR_TO_JSVALUE(void* ptr) {
|
||||
EncodedJSValue val;
|
||||
val.asInt64 = (int64_t)ptr + DoubleEncodeOffset;
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int32_t JSVALUE_TO_INT32(EncodedJSValue val) {
|
||||
return val.asInt64;
|
||||
|
||||
@@ -326,7 +326,7 @@ pub const FFI = struct {
|
||||
}
|
||||
}
|
||||
// var function
|
||||
var return_type = ABIType{ .primitive = .@"void" };
|
||||
var return_type = ABIType.@"void";
|
||||
|
||||
if (value.get(global, "return_type")) |ret_value| {
|
||||
var ret_slice = ret_value.toSlice(global, allocator);
|
||||
@@ -471,14 +471,14 @@ pub const FFI = struct {
|
||||
writer: anytype,
|
||||
) !void {
|
||||
brk: {
|
||||
if (this.return_type == .primitive and this.return_type.primitive.isFloatingPoint()) {
|
||||
if (this.return_type.isFloatingPoint()) {
|
||||
try writer.writeAll("#define USES_FLOAT 1\n");
|
||||
break :brk;
|
||||
}
|
||||
|
||||
for (this.arg_types.items) |arg| {
|
||||
// conditionally include math.h
|
||||
if (arg == .primitive and arg.primitive.isFloatingPoint()) {
|
||||
if (arg.isFloatingPoint()) {
|
||||
try writer.writeAll("#define USES_FLOAT 1\n");
|
||||
break;
|
||||
}
|
||||
@@ -527,7 +527,7 @@ pub const FFI = struct {
|
||||
}
|
||||
|
||||
try writer.writeAll(" ");
|
||||
if (!(this.return_type == .primitive and this.return_type.primitive == .void)) {
|
||||
if (!(this.return_type == .void)) {
|
||||
try this.return_type.typename(writer);
|
||||
try writer.writeAll(" return_value = ");
|
||||
}
|
||||
@@ -547,7 +547,7 @@ pub const FFI = struct {
|
||||
|
||||
try writer.writeAll("return ");
|
||||
|
||||
if (!(this.return_type == .primitive and this.return_type.primitive == .void)) {
|
||||
if (!(this.return_type == .void)) {
|
||||
try writer.print("{}.asPtr", .{this.return_type.toJS("return_value")});
|
||||
} else {
|
||||
try writer.writeAll("ValueUndefined.asPtr");
|
||||
@@ -557,263 +557,162 @@ pub const FFI = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const ABIType = union(enum) {
|
||||
primitive: Primitive.Tag,
|
||||
pointer: Pointer,
|
||||
pub const ABIType = enum(i32) {
|
||||
char = 0,
|
||||
|
||||
pub const label = ComptimeStringMap(
|
||||
ABIType,
|
||||
.{
|
||||
.{ "char", ABIType{ .primitive = Primitive.Tag.char } },
|
||||
.{ "float", ABIType{ .primitive = Primitive.Tag.float } },
|
||||
.{ "double", ABIType{ .primitive = Primitive.Tag.double } },
|
||||
.{ "f32", ABIType{ .primitive = Primitive.Tag.float } },
|
||||
.{ "f64", ABIType{ .primitive = Primitive.Tag.double } },
|
||||
.{ "bool", ABIType{ .primitive = Primitive.Tag.@"bool" } },
|
||||
int8_t = 1,
|
||||
uint8_t = 2,
|
||||
|
||||
.{ "i8", ABIType{ .primitive = Primitive.Tag.int8_t } },
|
||||
.{ "u8", ABIType{ .primitive = Primitive.Tag.uint8_t } },
|
||||
.{ "i16", ABIType{ .primitive = Primitive.Tag.int16_t } },
|
||||
.{ "int", ABIType{ .primitive = Primitive.Tag.int32_t } },
|
||||
.{ "c_int", ABIType{ .primitive = Primitive.Tag.int32_t } },
|
||||
.{ "c_uint", ABIType{ .primitive = Primitive.Tag.uint32_t } },
|
||||
.{ "i32", ABIType{ .primitive = Primitive.Tag.int32_t } },
|
||||
.{ "i64", ABIType{ .primitive = Primitive.Tag.int64_t } },
|
||||
.{ "u16", ABIType{ .primitive = Primitive.Tag.uint16_t } },
|
||||
.{ "u32", ABIType{ .primitive = Primitive.Tag.uint32_t } },
|
||||
.{ "u64", ABIType{ .primitive = Primitive.Tag.uint64_t } },
|
||||
.{ "int8_t", ABIType{ .primitive = Primitive.Tag.int8_t } },
|
||||
.{ "isize", ABIType{ .primitive = Primitive.Tag.int64_t } },
|
||||
.{ "usize", ABIType{ .primitive = Primitive.Tag.uint64_t } },
|
||||
.{ "int16_t", ABIType{ .primitive = Primitive.Tag.int16_t } },
|
||||
.{ "int32_t", ABIType{ .primitive = Primitive.Tag.int32_t } },
|
||||
.{ "int64_t", ABIType{ .primitive = Primitive.Tag.int64_t } },
|
||||
.{ "uint8_t", ABIType{ .primitive = Primitive.Tag.uint8_t } },
|
||||
.{ "uint16_t", ABIType{ .primitive = Primitive.Tag.uint16_t } },
|
||||
.{ "uint32_t", ABIType{ .primitive = Primitive.Tag.uint32_t } },
|
||||
.{ "uint64_t", ABIType{ .primitive = Primitive.Tag.uint64_t } },
|
||||
int16_t = 3,
|
||||
uint16_t = 4,
|
||||
|
||||
.{ "char*", ABIType{ .pointer = .{ .primitive = Primitive.Tag.char } } },
|
||||
.{ "void*", ABIType{ .pointer = .{ .primitive = Primitive.Tag.@"void" } } },
|
||||
},
|
||||
);
|
||||
int32_t = 5,
|
||||
uint32_t = 6,
|
||||
|
||||
const ToJSFormatter = struct {
|
||||
symbol: []const u8,
|
||||
abi: ABIType,
|
||||
int64_t = 7,
|
||||
uint64_t = 8,
|
||||
|
||||
pub fn format(self: ToJSFormatter, comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
switch (self.abi) {
|
||||
.pointer => |ptr| {
|
||||
_ = ptr;
|
||||
double = 9,
|
||||
float = 10,
|
||||
|
||||
bool = 11,
|
||||
|
||||
ptr = 12,
|
||||
|
||||
@"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 },
|
||||
});
|
||||
|
||||
pub fn isFloatingPoint(this: ABIType) bool {
|
||||
return switch (this) {
|
||||
.double, .float => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
const ToCFormatter = struct {
|
||||
symbol: string,
|
||||
tag: ABIType,
|
||||
|
||||
pub fn format(self: ToCFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
switch (self.tag) {
|
||||
.void => {},
|
||||
.bool => {
|
||||
try writer.print("JSVALUE_TO_BOOL({s})", .{self.symbol});
|
||||
},
|
||||
.primitive => |prim| {
|
||||
try prim.toJS(self.symbol).format(comptime fmt, opts, writer);
|
||||
.char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => {
|
||||
try writer.print("JSVALUE_TO_INT32({s})", .{self.symbol});
|
||||
},
|
||||
.int64_t => {},
|
||||
.uint64_t => {},
|
||||
.ptr => {
|
||||
try writer.print("JSVALUE_TO_PTR({s})", .{self.symbol});
|
||||
},
|
||||
.double => {
|
||||
try writer.print("JSVALUE_TO_DOUBLE({s})", .{self.symbol});
|
||||
},
|
||||
.float => {
|
||||
try writer.print("JSVALUE_TO_FLOAT({s})", .{self.symbol});
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const ToCFormatter = struct {
|
||||
const ToJSFormatter = struct {
|
||||
symbol: []const u8,
|
||||
abi: ABIType,
|
||||
tag: ABIType,
|
||||
|
||||
pub fn format(self: ToCFormatter, comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
try self.abi.primitive.toC(self.symbol).format(
|
||||
comptime fmt,
|
||||
opts,
|
||||
writer,
|
||||
);
|
||||
pub fn format(self: ToJSFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
switch (self.tag) {
|
||||
.void => {},
|
||||
.bool => {
|
||||
try writer.print("BOOLEAN_TO_JSVALUE({s})", .{self.symbol});
|
||||
},
|
||||
.char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => {
|
||||
try writer.print("INT32_TO_JSVALUE({s})", .{self.symbol});
|
||||
},
|
||||
.int64_t => {},
|
||||
.uint64_t => {},
|
||||
.ptr => {
|
||||
try writer.print("PTR_TO_JSVALUE({s})", .{self.symbol});
|
||||
},
|
||||
.double => {
|
||||
try writer.print("DOUBLE_TO_JSVALUE({s})", .{self.symbol});
|
||||
},
|
||||
.float => {
|
||||
try writer.print("FLOAT_TO_JSVALUE({s})", .{self.symbol});
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn toC(this: ABIType, symbol: string) ToCFormatter {
|
||||
return ToCFormatter{ .tag = this, .symbol = symbol };
|
||||
}
|
||||
|
||||
pub fn toJS(
|
||||
this: ABIType,
|
||||
symbol: string,
|
||||
) ToJSFormatter {
|
||||
return ToJSFormatter{
|
||||
.tag = this,
|
||||
.symbol = symbol,
|
||||
.abi = this,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toC(this: ABIType, symbol: string) ToCFormatter {
|
||||
return ToCFormatter{
|
||||
.symbol = symbol,
|
||||
.abi = this,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn typename(this: ABIType, writer: anytype) !void {
|
||||
switch (this) {
|
||||
.primitive => |prim| {
|
||||
try writer.writeAll(prim.typename());
|
||||
},
|
||||
.pointer => |ptr| {
|
||||
try ptr.typename(writer);
|
||||
},
|
||||
}
|
||||
try writer.writeAll(this.typenameLabel());
|
||||
}
|
||||
};
|
||||
|
||||
pub const Pointer = struct {
|
||||
count: u8 = 1,
|
||||
primitive: Primitive.Tag,
|
||||
is_const: bool = false,
|
||||
|
||||
pub fn typename(this: Pointer, writer: anytype) !void {
|
||||
if (this.is_const) {
|
||||
try writer.writeAll("const ");
|
||||
}
|
||||
|
||||
var i: u8 = 0;
|
||||
while (i < this.count) {
|
||||
try writer.writeAll("*");
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
try writer.writeAll(" ");
|
||||
try writer.writeAll(this.primitive.typename());
|
||||
pub fn typenameLabel(this: ABIType) []const u8 {
|
||||
return switch (this) {
|
||||
.ptr => "void*",
|
||||
.bool => "bool",
|
||||
.int8_t => "int8_t",
|
||||
.uint8_t => "uint8_t",
|
||||
.int16_t => "int16_t",
|
||||
.uint16_t => "uint16_t",
|
||||
.int32_t => "int32_t",
|
||||
.uint32_t => "uint32_t",
|
||||
.int64_t => "int64_t",
|
||||
.uint64_t => "uint64_t",
|
||||
.double => "float",
|
||||
.float => "float",
|
||||
.char => "char",
|
||||
.void => "void",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Primitive = union(Tag) {
|
||||
char: i8,
|
||||
int8_t: i8,
|
||||
uint8_t: u8,
|
||||
int16_t: i16,
|
||||
uint16_t: u16,
|
||||
int32_t: c_int,
|
||||
uint32_t: c_uint,
|
||||
int64_t: i64,
|
||||
uint64_t: u64,
|
||||
double: f64,
|
||||
float: f32,
|
||||
|
||||
void: *anyopaque,
|
||||
|
||||
bool: bool,
|
||||
|
||||
dynamic: struct {
|
||||
size: u32,
|
||||
alignment: u21,
|
||||
name: []const u8,
|
||||
},
|
||||
|
||||
pub const Tag = enum(i32) {
|
||||
char = 0,
|
||||
|
||||
int8_t = 1,
|
||||
uint8_t = 2,
|
||||
|
||||
int16_t = 3,
|
||||
uint16_t = 4,
|
||||
|
||||
int32_t = 5,
|
||||
uint32_t = 6,
|
||||
|
||||
int64_t = 7,
|
||||
uint64_t = 8,
|
||||
|
||||
double = 9,
|
||||
float = 10,
|
||||
|
||||
void = 11,
|
||||
dynamic = 12,
|
||||
|
||||
bool = 13,
|
||||
|
||||
pub fn isFloatingPoint(this: Tag) bool {
|
||||
return switch (this) {
|
||||
.double, .float => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
const ToCFormatter = struct {
|
||||
symbol: string,
|
||||
tag: Tag,
|
||||
|
||||
pub fn format(self: ToCFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
switch (self.tag) {
|
||||
.void => {},
|
||||
.bool => {
|
||||
try writer.print("JSVALUE_TO_BOOL({s})", .{self.symbol});
|
||||
},
|
||||
.char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => {
|
||||
try writer.print("JSVALUE_TO_INT32({s})", .{self.symbol});
|
||||
},
|
||||
.int64_t => {},
|
||||
.uint64_t => {},
|
||||
.double => {
|
||||
try writer.print("JSVALUE_TO_DOUBLE({s})", .{self.symbol});
|
||||
},
|
||||
.float => {
|
||||
try writer.print("JSVALUE_TO_FLOAT({s})", .{self.symbol});
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const ToJSFormatter = struct {
|
||||
symbol: []const u8,
|
||||
tag: Tag,
|
||||
|
||||
pub fn format(self: ToJSFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
switch (self.tag) {
|
||||
.void => {},
|
||||
.bool => {
|
||||
try writer.print("BOOLEAN_TO_JSVALUE({s})", .{self.symbol});
|
||||
},
|
||||
.char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => {
|
||||
try writer.print("INT32_TO_JSVALUE({s})", .{self.symbol});
|
||||
},
|
||||
.int64_t => {},
|
||||
.uint64_t => {},
|
||||
.double => {
|
||||
try writer.print("DOUBLE_TO_JSVALUE({s})", .{self.symbol});
|
||||
},
|
||||
.float => {
|
||||
try writer.print("FLOAT_TO_JSVALUE({s})", .{self.symbol});
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn toC(this: Tag, symbol: string) ToCFormatter {
|
||||
return ToCFormatter{ .tag = this, .symbol = symbol };
|
||||
}
|
||||
|
||||
pub fn toJS(
|
||||
this: Tag,
|
||||
symbol: string,
|
||||
) ToJSFormatter {
|
||||
return ToJSFormatter{
|
||||
.tag = this,
|
||||
.symbol = symbol,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn typename(this: Tag) []const u8 {
|
||||
return switch (this) {
|
||||
.void => "void",
|
||||
.bool => "bool",
|
||||
.int8_t => "int8_t",
|
||||
.uint8_t => "uint8_t",
|
||||
.int16_t => "int16_t",
|
||||
.uint16_t => "uint16_t",
|
||||
.int32_t => "int32_t",
|
||||
.uint32_t => "uint32_t",
|
||||
.int64_t => "int64_t",
|
||||
.uint64_t => "uint64_t",
|
||||
.double => "float",
|
||||
.float => "float",
|
||||
.char => "char",
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user