8#include "wasm_export.h"
24 if (m) wasm_runtime_unload(m);
29 if (m) wasm_runtime_deinstantiate(m);
34 if (e) wasm_runtime_destroy_exec_env(e);
47constexpr int kInstrLimit = 500'000'000;
51[[nodiscard]]
bool load_bytecode_psram(
const std::string& path,
59 if (!fp)
return false;
60 std::fseek(fp.get(), 0, SEEK_END);
61 long n = std::ftell(fp.get());
62 if (n <= 0)
return false;
63 std::fseek(fp.get(), 0, SEEK_SET);
66 if (!buf)
return false;
68 if (std::fread(buf.get(), 1, n, fp.get()) !=
static_cast<std::size_t
>(n)) {
72 out_len =
static_cast<std::size_t
>(n);
84 plg_log_error(
"plugin: failed to read plugin binary into PSRAM");
88 char err_buf[128] = {0};
89 module_.reset(wasm_runtime_load(bytecode_.get(),
90 static_cast<uint32_t
>(bytecode_len_),
91 err_buf,
sizeof(err_buf)));
99 (void)manifest_.linear_memory_kb;
100 const uint32_t stack_bytes = 64 * 1024;
106 const uint32_t heap_bytes = 0;
108 module_inst_.reset(wasm_runtime_instantiate(module_.get(), stack_bytes,
118 exec_env_.reset(wasm_runtime_create_exec_env(module_inst_.get(), stack_bytes));
120 plg_log_error(
"plugin: wasm_runtime_create_exec_env failed");
131 return wasm_runtime_lookup_function(module_inst_.get(),
name) !=
nullptr;
138 module_inst_.reset();
146 std::initializer_list<int32_t> args,
153 constexpr std::size_t kMaxArgs = 8;
154 if (args.size() > kMaxArgs) {
158 uint32_t argv[kMaxArgs + 1] = {0};
160 for (int32_t a : args) argv[i++] =
static_cast<uint32_t
>(a);
161 const uint32_t argc =
static_cast<uint32_t
>(args.size());
165 wasm_runtime_set_instruction_count_limit(exec_env_.get(), kInstrLimit);
167 last_call_trapped_ =
false;
168 wasm_function_inst_t fn = wasm_runtime_lookup_function(module_inst_.get(),
name);
170 if (missingExports_.insert(
name).second) {
172 std::snprintf(buf,
sizeof(buf),
"plugin: missing export '%s'",
name);
176 }
else if (!wasm_runtime_call_wasm(exec_env_.get(), fn, argc, argv)) {
177 const char* exc = wasm_runtime_get_exception(module_inst_.get());
178 std::snprintf(last_trap_,
sizeof(last_trap_),
"%s", exc ? exc :
"unknown trap");
179 last_call_trapped_ =
true;
181 std::snprintf(buf,
sizeof(buf),
"plugin: call '%s' failed: %s",
name, last_trap_);
183 wasm_runtime_clear_exception(module_inst_.get());
185 }
else if (out_i32) {
186 *out_i32 =
static_cast<int32_t
>(argv[0]);
194 langOverlay_.clear();
195 langOverlayLang_.clear();
197 std::string default_path;
198 if (!path || !*path) {
200 path = default_path.c_str();
204 if (!fp)
return false;
206 std::fseek(fp.get(), 0, SEEK_END);
207 long size = std::ftell(fp.get());
208 std::fseek(fp.get(), 0, SEEK_SET);
209 if (size <= 0 || size > 256 * 1024) {
215 if (!buf)
return false;
216 if (std::fread(buf.get(), 1, size, fp.get()) !=
static_cast<size_t>(size)) {
219 buf.get()[size] =
'\0';
221 cJSON* root = cJSON_Parse(buf.get());
227 cJSON* translations = cJSON_GetObjectItemCaseSensitive(root,
"translations");
228 if (!translations || !cJSON_IsObject(translations)) {
234 if (active ==
"en") {
239 cJSON* lang_obj = cJSON_GetObjectItemCaseSensitive(translations, active.c_str());
240 if (!lang_obj || !cJSON_IsObject(lang_obj)) {
245 cJSON* entry =
nullptr;
246 cJSON_ArrayForEach(entry, lang_obj) {
247 if (!cJSON_IsString(entry) || !entry->string || !entry->valuestring)
continue;
248 langOverlay_.push_back({entry->string, entry->valuestring});
250 std::sort(langOverlay_.begin(), langOverlay_.end(),
251 [](
const OverlayEntry& a,
const OverlayEntry& b) {
252 return a.key < b.key;
254 langOverlayLang_ = active;
262 if (!key || langOverlay_.empty())
return nullptr;
263 auto it = std::lower_bound(
264 langOverlay_.begin(), langOverlay_.end(), key,
265 [](
const OverlayEntry& e,
const char* k) { return e.key < k; });
266 if (it != langOverlay_.end() && it->key == key)
return it->value.c_str();
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
Mounts the FAT-FS partition that holds plugin .wasm + .meta files.
void plg_log_warn(const char *msg)
void * plg_get_active_plugin(void)
void plg_set_active_plugin(void *plugin)
void plg_log_error(const char *msg)
void plg_log_info(const char *msg)
Owned WAMR module instance + per-plugin state.
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
Shared RAII wrappers for firmware resources.
static std::string langPath(const std::string &id)
Returns the full VFS path of <id>.lang (translation overlay).
static std::string binaryPath(const std::string &id)
Returns the path that should be loaded for <id>: <id>.aot if it exists on disk, otherwise <id>....
bool load(const std::string &id, const PluginManifest &manifest)
Load bytecode + instantiate the module. Does NOT run plugin_init yet - the manager handles ordering o...
std::vector< std::string > acquired_prereqs
bool hasExport(const char *name) const
const char * trKey(const char *key) const noexcept
Look up a plugin-local translation key in the loaded overlay.
bool isLoaded() const noexcept
bool callI(const char *name, std::initializer_list< int32_t > args={}, int32_t *out_i32=nullptr)
Call an exported i32(i32...)->i32 function by name.
void unload() noexcept
Destroy WAMR instance + free bytecode buffer. Idempotent.
bool loadLangOverlay(const char *path=nullptr)
Load the plugin's translation overlay from disk into PSRAM.
const std::string & id() const noexcept
const PluginManifest & manifest() const noexcept
const std::string & getLanguageCode() const
Current language code (lower-case ISO-639-1, e.g. "en", "de").
static I18n & instance()
Singleton accessor.
void * plg_get_active_plugin(void)
void plg_log_warn(const char *msg)
FilePtr openFile(const char *path, const char *mode) noexcept
Open a FILE* and wrap it in a FilePtr.
PsramUniquePtr< T > psramAlloc(std::size_t count) noexcept
Allocate count elements of T in PSRAM (8-bit capable region).
PsramUniquePtr< T > psramAlloc(std::size_t count) noexcept
::cdc::core::PsramUniquePtr< T > PsramUniquePtr
void operator()(WASMExecEnv *e) const noexcept
void operator()(WASMModuleInstanceCommon *m) const noexcept
void operator()(WASMModuleCommon *m) const noexcept