Files
wehub-resource-sync 26446540fa
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

315 lines
12 KiB
C++

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* \file tvmjs_support.cc
* \brief Support functions to be linked with wasm_runtime to provide
* ffi::Function callbacks in tvmjs.
* We do not need to link this file in standalone wasm.
*/
// configurations for tvm logging
#define TVM_LOG_DEBUG 0
#define TVM_LOG_CUSTOMIZE 1
#define TVM_FFI_ALWAYS_LOG_BEFORE_THROW 1
#include <tvm/ffi/extra/module.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/runtime/device_api.h>
#include <unordered_set>
#include "../../src/runtime/rpc/rpc_local_session.h"
extern "C" {
// --- Additional C API for the Wasm runtime ---
/*!
* \brief Allocate space aligned to 64 bit.
* \param size The size of the space.
* \return The allocated space.
*/
TVM_DLL void* TVMWasmAllocSpace(int size);
/*!
* \brief Free the space allocated by TVMWasmAllocSpace.
* \param data The data pointer.
*/
TVM_DLL void TVMWasmFreeSpace(void* data);
/*!
* \brief Create ffi::Function from a resource handle.
* \param resource_handle The handle to the resource.
* \param out The output ffi::Function.
* \sa TVMWasmPackedCFunc, TVMWasmPackedCFuncFinalizer
3A * \return 0 if success.
*/
TVM_DLL int TVMFFIWasmFunctionCreate(void* resource_handle, TVMFFIObjectHandle* out);
/*!
* \brief Get the last error message.
* \return The last error message.
*/
TVM_DLL const char* TVMFFIWasmGetLastError();
// --- APIs to be implemented by the frontend. ---
/*!
* \brief Wasm frontend new ffi call function caller.
*
* \param self The pointer to the ffi::Function.
* \param args The arguments
* \param num_args Number of arguments.
* \param result The return value handle.
* \return 0 if success, -1 if failure happens, set error via TVMAPISetLastError.
*/
extern int TVMFFIWasmSafeCall(void* self, const TVMFFIAny* args, int32_t num_args,
TVMFFIAny* result);
/*!
* \brief Delete ffi::Function.
* \param self The pointer to the ffi::Function.
*/
extern void TVMFFIWasmFunctionDeleter(void* self);
} // extern "C"
void* TVMWasmAllocSpace(int size) {
int num_count = (size + 7) / 8;
return new int64_t[num_count];
}
void TVMWasmFreeSpace(void* arr) { delete[] static_cast<int64_t*>(arr); }
int TVMFFIWasmFunctionCreate(void* self, TVMFFIObjectHandle* out) {
return TVMFFIFunctionCreate(self, TVMFFIWasmSafeCall, TVMFFIWasmFunctionDeleter, out);
}
const char* TVMFFIWasmGetLastError() {
static thread_local std::string last_error;
last_error = ::tvm::ffi::details::MoveFromSafeCallRaised().what();
return last_error.c_str();
}
namespace tvm {
namespace runtime {
// A special local session that can interact with async
// functions in the JS runtime.
class AsyncLocalSession : public LocalSession {
public:
AsyncLocalSession() {}
PackedFuncHandle GetFunction(const std::string& name) final {
if (name == "runtime.RPCTimeEvaluator") {
return get_time_eval_placeholder_.get();
} else if (auto fp = tvm::ffi::Function::GetGlobal(name)) {
TVMFFIAny val = tvm::ffi::details::AnyUnsafe::MoveAnyToTVMFFIAny(tvm::ffi::Any(*fp));
return val.v_obj;
} else if (auto fp = tvm::ffi::Function::GetGlobal("__async." + name)) {
TVMFFIAny val = tvm::ffi::details::AnyUnsafe::MoveAnyToTVMFFIAny(tvm::ffi::Any(*fp));
async_func_set_.insert(val.v_obj);
return val.v_obj;
} else {
return nullptr;
}
}
void FreeHandle(void* handle) final {
{
auto it = async_func_set_.find(handle);
if (it != async_func_set_.end()) {
async_func_set_.erase(it);
}
}
if (handle != get_time_eval_placeholder_.get()) {
LocalSession::FreeHandle(handle);
}
}
void AsyncCallFunc(PackedFuncHandle func, ffi::PackedArgs args, FAsyncCallback callback) final {
auto it = async_func_set_.find(func);
if (it != async_func_set_.end()) {
ffi::Function packed_callback([callback, this](ffi::PackedArgs args, ffi::Any*) {
int code = args[0].cast<int>();
ffi::Any rv;
rv = args[1];
if (code == static_cast<int>(RPCCode::kReturn)) {
this->EncodeReturn(std::move(rv), [&](ffi::PackedArgs encoded_args) {
callback(RPCCode::kReturn, encoded_args);
});
} else {
// for exception, we can pass through as since this is just normal encoding.
TVM_FFI_ICHECK_EQ(code, static_cast<int>(RPCCode::kException));
callback(RPCCode::kException, args);
}
});
std::vector<AnyView> packed_args(args.data(), args.data() + args.size());
// pass the callback as the last argument.
packed_args.emplace_back(AnyView(packed_callback));
auto* pf = static_cast<ffi::FunctionObj*>(func);
Any temp;
pf->CallPacked(packed_args.data(), packed_args.size(), &temp);
} else if (func == get_time_eval_placeholder_.get()) {
// special handle time evaluator.
try {
ffi::Function retfunc = this->GetTimeEvaluator(
args[0].cast<ffi::Optional<ffi::Module>>(), args[1].cast<std::string>(),
args[2].cast<int>(), args[3].cast<int>(), args[4].cast<int>(), args[5].cast<int>(),
args[6].cast<int>(), args[7].cast<int>(), args[8].cast<int>(), args[9].cast<int>());
ffi::Any rv;
rv = retfunc;
this->EncodeReturn(std::move(rv), [&](ffi::PackedArgs encoded_args) {
const void* pf = encoded_args[0].as<ffi::FunctionObj>();
TVM_FFI_ICHECK(pf != nullptr);
// mark as async.
async_func_set_.insert(const_cast<void*>(pf));
callback(RPCCode::kReturn, encoded_args);
});
} catch (const std::runtime_error& e) {
this->SendException(callback, e.what());
}
} else {
LocalSession::AsyncCallFunc(func, args, callback);
}
}
void AsyncCopyToRemote(void* local_from_bytes, DLTensor* remote_to, uint64_t nbytes,
FAsyncCallback on_complete) final {
try {
DLTensor local_from;
local_from.data = local_from_bytes;
local_from.device = Device{kDLCPU, 0};
local_from.ndim = remote_to->ndim;
local_from.shape = remote_to->shape;
local_from.dtype = remote_to->dtype;
local_from.strides = nullptr;
local_from.byte_offset = 0;
this->GetDeviceAPI(remote_to->device)->CopyDataFromTo(&local_from, remote_to, nullptr);
this->AsyncStreamWait(remote_to->device, nullptr, on_complete);
} catch (const std::runtime_error& e) {
this->SendException(on_complete, e.what());
}
}
void AsyncCopyFromRemote(DLTensor* remote_from, void* local_to_bytes, uint64_t nbytes,
FAsyncCallback on_complete) final {
try {
DLTensor local_to;
local_to.data = local_to_bytes;
local_to.device = Device{kDLCPU, 0};
local_to.ndim = remote_from->ndim;
local_to.shape = remote_from->shape;
local_to.dtype = remote_from->dtype;
local_to.strides = nullptr;
local_to.byte_offset = 0;
this->GetDeviceAPI(remote_from->device)->CopyDataFromTo(remote_from, &local_to, nullptr);
this->AsyncStreamWait(remote_from->device, nullptr, on_complete);
} catch (const std::runtime_error& e) {
this->SendException(on_complete, e.what());
}
}
void AsyncStreamWait(Device dev, TVMStreamHandle stream, FAsyncCallback on_complete) final {
if (dev.device_type == kDLCPU) {
AnyView packed_args[1];
packed_args[0] = nullptr;
on_complete(RPCCode::kReturn, ffi::PackedArgs(packed_args, 1));
} else {
TVM_FFI_ICHECK(dev.device_type == static_cast<DLDeviceType>(kDLWebGPU));
if (!async_wait_.has_value()) {
async_wait_ = tvm::ffi::Function::GetGlobal("__async.wasm.WebGPUWaitForTasks");
}
TVM_FFI_ICHECK(async_wait_.has_value());
ffi::Function packed_callback([on_complete](ffi::PackedArgs args, ffi::Any*) {
int code = args[0].cast<int>();
on_complete(static_cast<RPCCode>(code), args.Slice(1));
});
(*async_wait_)(packed_callback);
}
}
bool IsAsync() const final { return true; }
private:
std::unordered_set<void*> async_func_set_;
std::unique_ptr<ffi::Function> get_time_eval_placeholder_ = std::make_unique<ffi::Function>();
std::optional<ffi::Function> async_wait_;
// time evaluator
ffi::Function GetTimeEvaluator(ffi::Optional<ffi::Module> opt_mod, std::string name,
int device_type, int device_id, int number, int repeat,
int min_repeat_ms, int limit_zero_time_iterations,
int cooldown_interval_ms, int repeats_to_cooldown) {
Device dev;
dev.device_type = static_cast<DLDeviceType>(device_type);
dev.device_id = device_id;
if (opt_mod.has_value()) {
ffi::Module m = opt_mod.value();
std::string tkey = m->kind();
return WrapWasmTimeEvaluator(m->GetFunction(name, false).value(), dev, number, repeat,
min_repeat_ms, limit_zero_time_iterations, cooldown_interval_ms,
repeats_to_cooldown);
} else {
auto pf = tvm::ffi::Function::GetGlobal(name);
TVM_FFI_ICHECK(pf.has_value()) << "Cannot find " << name << " in the global function";
return WrapWasmTimeEvaluator(*pf, dev, number, repeat, min_repeat_ms,
limit_zero_time_iterations, cooldown_interval_ms,
repeats_to_cooldown);
}
}
// time evaluator
ffi::Function WrapWasmTimeEvaluator(ffi::Function pf, Device dev, int number, int repeat,
int min_repeat_ms, int limit_zero_time_iterations,
int cooldown_interval_ms, int repeats_to_cooldown) {
auto ftimer = [pf, dev, number, repeat, min_repeat_ms, limit_zero_time_iterations,
cooldown_interval_ms, repeats_to_cooldown](ffi::PackedArgs args, ffi::Any* rv) {
// the function is a async function.
ffi::Function on_complete = args[args.size() - 1].cast<ffi::Function>();
std::vector<AnyView> packed_args(args.data(), args.data() + args.size() - 1);
auto finvoke = [pf, packed_args](int n) {
ffi::Any temp;
ffi::PackedArgs invoke_args(packed_args.data(), packed_args.size());
for (int i = 0; i < n; ++i) {
pf.CallPacked(invoke_args, &temp);
}
};
auto time_exec = tvm::ffi::Function::GetGlobal("__async.wasm.TimeExecution");
TVM_FFI_ICHECK(time_exec.has_value()) << "Cannot find wasm.GetTimer in the global function";
(*time_exec)(ffi::TypedFunction<void(int)>(finvoke), dev, number, repeat, min_repeat_ms,
limit_zero_time_iterations, cooldown_interval_ms, repeats_to_cooldown,
/*cache_flush_bytes=*/0, on_complete);
};
return ffi::Function(ftimer);
}
};
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def("wasm.LocalSession", []() {
return CreateRPCSessionModule(std::make_shared<AsyncLocalSession>());
});
}
} // namespace runtime
} // namespace tvm