You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
13 KiB
325 lines
13 KiB
const std = @import("std"); |
|
const assert = std.debug.assert; |
|
const panic = std.debug.panic; |
|
const c_alloc = std.heap.c_allocator; |
|
|
|
const Store = @import("./store.zig").Store; |
|
const Extern = @import("./extern.zig").Extern; |
|
const Memory = @import("./memory.zig").Memory; |
|
const Diagnostics = @import("./diagnostics.zig").Diagnostics; |
|
const Error = @import("./error.zig").Error; |
|
const Trap = @import("./trap.zig").Trap; |
|
|
|
const _val = @import("./val.zig"); |
|
const Val = _val.Val; |
|
const ValType = _val.ValType; |
|
const ValTypeVec = _val.ValTypeVec; |
|
|
|
/// Structure used to learn about the caller of a host-defined function. |
|
/// |
|
/// This structure is an argument to `Func.Callback`. Its purpose is to |
|
/// acquire a `Store.Context` pointer to interact with objects, but it can |
|
/// also be used to inspect the state of the caller (such as getting memories |
|
/// and functions) with `getExport`. |
|
pub const Caller = opaque { |
|
/// Returns the store context of the caller object. |
|
pub fn getContext(self: *Caller) *Store.Context { |
|
return wasmtime_caller_context(self); |
|
} |
|
|
|
/// Gets an exported `Extern` by name from the caller's context, converted |
|
/// to the specified type. If `T` is `Extern` no conversion is done. |
|
/// |
|
/// Returns `error.ExportNotFound` if export with that name is not found. |
|
/// Returns `error.IncorrectType` if the export isn't the right type. |
|
pub fn get( |
|
self: *Caller, |
|
name: []const u8, |
|
comptime T: type, |
|
) !T { |
|
var result: Extern = undefined; |
|
return if (wasmtime_caller_export_get(self, name.ptr, name.len, &result)) result.as(T) else error.ExportNotFound; |
|
} |
|
|
|
/// Get an exported `Memory`'s data slice from the caller's context. |
|
/// |
|
/// Returns `error.ExportNotFound` if export with that name is not found. |
|
/// Returns `error.IncorrectType` if the export isn't the a `Memory`. |
|
pub fn getData( |
|
self: *Caller, |
|
name: []const u8, |
|
) ![]u8 { |
|
return (try self.get(name, Memory)).getData(self.getContext()); |
|
} |
|
|
|
extern "c" fn wasmtime_caller_context(*Caller) *Store.Context; |
|
extern "c" fn wasmtime_caller_export_get(*Caller, [*]const u8, usize, *Extern) bool; |
|
}; |
|
|
|
/// Representation of a function in Wasmtime. |
|
pub const Func = extern struct { |
|
/// Internal identifier of what store this belongs to, never zero. |
|
store_id: u64, |
|
/// Internal index within the store. |
|
index: usize, |
|
|
|
/// Inserts a host-defined function into the `store` provided, which can |
|
/// be used to then instantiate a module with or define within a `Linker`. |
|
/// The returned function can only be used with the specified `store`. |
|
pub fn init( |
|
context: *Store.Context, |
|
func_type: *FuncType, |
|
callback: *const Callback, |
|
env: ?*anyopaque, |
|
env_finalizer: ?*const Finalizer, |
|
) Func { |
|
var result: Func = undefined; |
|
wasmtime_func_new(context, func_type, callback, env, env_finalizer, &result); |
|
return result; |
|
} |
|
|
|
pub fn initFromFn(context: *Store.Context, func: anytype) Func { |
|
const func_type = FuncType.fromFn(func); |
|
const wrapped = Wrapped.init(func); |
|
return init(context, func_type, wrapped.callback, wrapped.env, wrapped.env_finalizer); |
|
} |
|
|
|
pub const Wrapped = struct { |
|
env: ?*anyopaque, |
|
env_finalizer: ?*const Finalizer, |
|
callback: *const Callback, |
|
|
|
pub fn init(func: anytype) Wrapped { |
|
return .{ |
|
// Use the function's pointer itself as the "env" variable |
|
// that'll be passed to the callback. This way we can call |
|
// the original function from our custom callback. |
|
.env = @ptrCast(@constCast(&func)), |
|
.env_finalizer = null, // No finalizer necessary. |
|
.callback = &(struct { |
|
const meta = @typeInfo(@TypeOf(func)).Fn; |
|
const ArgsType = std.meta.ArgsTuple(@TypeOf(func)); |
|
const ReturnType = meta.return_type orelse void; |
|
|
|
fn callback( |
|
env: ?*anyopaque, |
|
caller: *Caller, |
|
args: [*]const Val, |
|
num_args: usize, |
|
results: [*]Val, |
|
num_results: usize, |
|
) ?*Trap { |
|
var func_args: ArgsType = undefined; |
|
|
|
comptime var offset = 0; |
|
// If the first function parameter accepts a *Caller, pass that in! |
|
if (meta.params.len > 0 and meta.params[0].type.? == *Caller) { |
|
func_args[0] = caller; |
|
offset = 1; |
|
} |
|
|
|
assert(num_args == meta.params.len - offset); |
|
assert(num_results == getNumResultsFromReturnType(ReturnType)); |
|
|
|
// Turn the "env" parameter back into a function pointer. |
|
const func_ptr: *const @TypeOf(func) = @ptrCast(env); |
|
|
|
// Build argument tuple from the arguments WASM has given us. |
|
inline for (meta.params[offset..], args, offset..) |p, arg, i| |
|
func_args[i] = arg.toValue(p.type.?) catch |
|
panic("Cannot convert {} to {s}", .{ arg, @typeName(p.type.?) }); |
|
|
|
// Call the function with the arguments, get the results. |
|
const func_results_maybe_error = @call(.auto, func_ptr, func_args); |
|
|
|
// If function returns an ErrorUnion, convert it to |
|
// its payload, or return a trap if an error occurred. |
|
const func_results = if (@typeInfo(@TypeOf(func_results_maybe_error)) == .ErrorUnion) |
|
func_results_maybe_error catch |err| { |
|
const fmt = "Error '{s}' occurred in host code"; |
|
const msg = std.fmt.allocPrintZ(c_alloc, fmt, .{@errorName(err)}); |
|
return Trap.init(msg catch "Error occurred in host code"); |
|
} |
|
else |
|
func_results_maybe_error; |
|
|
|
// Pass the result(s) back to WASM. |
|
if (comptime std.meta.trait.isTuple(@TypeOf(func_results))) { |
|
inline for (func_results, 0..) |res, i| |
|
results[i] = Val.fromValue(@TypeOf(res), res); |
|
} else if (@TypeOf(func_results) != void) |
|
results[0] = Val.fromValue(@TypeOf(func_results), func_results); |
|
|
|
return null; |
|
} |
|
}.callback), |
|
}; |
|
} |
|
}; |
|
|
|
pub fn getType(self: *const Func, context: *Store.Context) *FuncType { |
|
return wasmtime_func_type(context, self); |
|
} |
|
|
|
pub fn call( |
|
self: *const Func, |
|
context: *const Store.Context, |
|
args: anytype, |
|
comptime TResult: type, |
|
diag: ?*Diagnostics, |
|
) !TResult { |
|
if (!comptime isTupleOrEmpty(@TypeOf(args))) |
|
@compileError("Expected tuple, but found '" ++ @typeName(@TypeOf(args)) ++ "'"); |
|
|
|
// Set up function arguments. |
|
var args_array: [args.len]Val = undefined; |
|
inline for (&args_array, args) |*val, arg| |
|
val.* = Val.fromValue(@TypeOf(arg), arg); |
|
|
|
// Set up function results. |
|
const num_results = comptime getNumResultsFromReturnType(TResult); |
|
var results_array: [num_results]Val = undefined; |
|
|
|
var trap: ?*Trap = null; |
|
const err = wasmtime_func_call(context, self, &args_array, args_array.len, &results_array, results_array.len, &trap); |
|
try Diagnostics.handleErrorOrTrap(err, error.FuncCall, trap, {}, diag); |
|
|
|
if (comptime std.meta.trait.isTuple(TResult)) { |
|
var result: TResult = undefined; |
|
inline for (TResult, results_array, result) |T, val, *res| { |
|
defer val.deinit(); |
|
res.* = val.toValue(T) catch |
|
panic("Cannot convert {} to {s}", .{ val, @typeName(T) }); |
|
} |
|
return result; |
|
} else if (TResult != void) { |
|
defer results_array[0].deinit(); |
|
return results_array[0].toValue(TResult) catch |
|
panic("Cannot convert {} to {s}", .{ results_array[0], @typeName(TResult) }); |
|
} else return {}; |
|
} |
|
|
|
pub fn toExtern(self: Func) Extern { |
|
return Extern.from(self, Func); |
|
} |
|
|
|
fn isTupleOrEmpty(comptime T: type) bool { |
|
return switch (@typeInfo(T)) { |
|
.Struct => |s| s.is_tuple or s.fields.len == 0, |
|
else => false, |
|
}; |
|
} |
|
|
|
fn getNumResultsFromReturnType(comptime T: type) usize { |
|
const meta = @typeInfo(T); |
|
return comptime if (meta == .ErrorUnion) |
|
getNumResultsFromReturnType(meta.ErrorUnion.payload) |
|
else if (std.meta.trait.isTuple(T)) |
|
meta.Struct.fields.len |
|
else if (T != void) |
|
1 |
|
else |
|
0; |
|
} |
|
|
|
pub const Callback = fn ( |
|
env: ?*anyopaque, |
|
caller: *Caller, |
|
args: [*]const Val, |
|
num_args: usize, |
|
results: [*]Val, |
|
num_results: usize, |
|
) ?*Trap; |
|
|
|
pub const Finalizer = fn (env: *anyopaque) void; |
|
|
|
extern "c" fn wasmtime_func_new( |
|
context: *Store.Context, |
|
func_type: *const FuncType, |
|
callback: *const Callback, |
|
env: ?*anyopaque, |
|
env_finalizer: ?*const Finalizer, |
|
result: *Func, |
|
) void; |
|
|
|
extern "c" fn wasmtime_func_type( |
|
context: *const Store.Context, |
|
func: *const Func, |
|
) *FuncType; |
|
|
|
extern "c" fn wasmtime_func_call( |
|
context: *const Store.Context, |
|
func: *const Func, |
|
args: [*]const Val, |
|
num_args: usize, |
|
results: [*]Val, |
|
num_results: usize, |
|
trap: *?*Trap, |
|
) ?*Error; |
|
}; |
|
|
|
/// An opaque object representing the type of a function. |
|
pub const FuncType = opaque { |
|
pub fn init(params: *ValTypeVec, results: *ValTypeVec) *FuncType { |
|
return wasm_functype_new(params, results); |
|
} |
|
|
|
pub fn fromFn(func: anytype) *FuncType { |
|
const meta = @typeInfo(@TypeOf(func)).Fn; |
|
|
|
comptime var offset = 0; |
|
// If the first function parameter accepts a *Caller, skip it! |
|
if (meta.params.len > 0 and meta.params[0].type.? == *Caller) offset = 1; |
|
|
|
var params = ValTypeVec.initUninitialized(meta.params.len - offset); |
|
inline for (meta.params[offset..], 0..) |p, i| |
|
params.data[i] = ValType.fromType(p.type.?); |
|
|
|
const ResultType = switch (@typeInfo(meta.return_type orelse void)) { |
|
.ErrorUnion => |u| u.payload, |
|
else => |t| t, |
|
}; |
|
|
|
var results = if (comptime std.meta.trait.isTuple(ResultType)) blk: { |
|
var res = ValTypeVec.initUninitialized(ResultType.len); |
|
inline for (ResultType, res.toSlice()) |T, *r| |
|
r.* = ValType.fromType(T); |
|
break :blk res; |
|
} else if (ResultType != void) blk: { |
|
var res = ValTypeVec.initUninitialized(1); |
|
res.data[0] = ValType.fromType(ResultType); |
|
break :blk res; |
|
} else ValTypeVec.initEmpty(0); |
|
|
|
return init(¶ms, &results); |
|
} |
|
|
|
pub fn copy(self: *const FuncType) *FuncType { |
|
return wasm_functype_copy(self); |
|
} |
|
|
|
pub fn deinit(self: *FuncType) void { |
|
wasm_functype_delete(self); |
|
} |
|
|
|
pub fn getParams(self: *const FuncType) *const ValTypeVec { |
|
return wasm_functype_params(self); |
|
} |
|
|
|
pub fn getResults(self: *const FuncType) *const ValTypeVec { |
|
return wasm_functype_results(self); |
|
} |
|
|
|
extern "c" fn wasm_functype_new(*ValTypeVec, *ValTypeVec) *FuncType; |
|
extern "c" fn wasm_functype_copy(*const FuncType) *FuncType; |
|
extern "c" fn wasm_functype_delete(*FuncType) void; |
|
extern "c" fn wasm_functype_params(*const FuncType) *const ValTypeVec; |
|
extern "c" fn wasm_functype_results(*const FuncType) *const ValTypeVec; |
|
}; |
|
|
|
// pub const FuncTypeVec = Vec(*FuncType, wasm_functype_vec_new_empty, wasm_functype_vec_new_uninitialized, wasm_functype_vec_new, wasm_functype_vec_copy, wasm_functype_vec_delete); |
|
// extern "c" fn wasm_functype_vec_new_empty(*anyopaque, usize) void; |
|
// extern "c" fn wasm_functype_vec_new_uninitialized(*anyopaque, usize) void; |
|
// extern "c" fn wasm_functype_vec_new(*anyopaque, usize, [*]const ?*FuncType) void; |
|
// extern "c" fn wasm_functype_vec_copy(*anyopaque, *const anyopaque) void; |
|
// extern "c" fn wasm_functype_vec_delete(*anyopaque) void;
|
|
|