WasmKit seems to solve the issue represented in OP by going to C and coming back. Tail calls are achieved in C using the swiftasync
calling convention. The C methods are thin wrappers around Swift functions which are inlined inside the C wrappers. The C wrappers simply call the Swift equivalent, and then perform a tail call.
This, once again, makes me wonder if the need for a C wrapper can be saved by making it an explicit language feature. e.g. By allowing non-async Swift functions to explicitly request the swiftasync
calling convention, e.g.
@swiftasync
func wasm_op_f64_min(frame_ip:UnsafeRawPointer, stack:WasmStack) -> Void {
let a:Double = stack.pop_f64()
let b:Double = stack.pop_f64()
stack.push_f64(f64_min(a,b))
let label_addr:(@swiftasync (UnsafeRawPointer, WasmStack) -> Void) = frame_ip.assumingMemoryBound(to:(@swiftasync (UnsafeRawPointer, WasmStack) -> Void).self)
let next_frame_ip:UnsafeRawPointer = UnsafeRawPointer(label_addr + 1)
label_addr(next_frame_ip, stack)
}