Line data Source code
1 : /**
2 : Copyright (c) 2024 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "SPWasm.h"
24 :
25 : #include "SPWasm-core.cc"
26 : #include "SPWasm-filesystem.cc"
27 : #include "SPWasm-data.cc"
28 :
29 : namespace stappler::wasm {
30 :
31 : static std::mutex s_loadMutex;
32 : static Runtime *s_instance = nullptr;
33 :
34 : struct Runtime::Data {
35 : bool enabled = false;
36 : };
37 :
38 112225 : static void *runtime_malloc(size_t size) {
39 112225 : return ::malloc(size);
40 : }
41 :
42 89275 : static void runtime_free(void *ptr) {
43 89275 : ::free(ptr);
44 89275 : }
45 :
46 0 : static void *runtime_realloc(void *ptr, size_t size) {
47 0 : return ::realloc(ptr, size);
48 : }
49 :
50 950 : static void StapplerWasmDebugPrint(wasm_exec_env_t exec_env, uint32_t ptr, uint32_t size) {
51 950 : auto mod = wasm_runtime_get_module_inst(exec_env);
52 950 : auto sptr = (const char *)wasm_runtime_addr_app_to_native(mod, ptr);
53 :
54 950 : log::debug("wasm::Runtime", StringView(sptr, size));
55 950 : }
56 :
57 : static NativeSymbol stapper_wasm_symbols[] = {
58 : NativeSymbol{"debug-print", (void *)&StapplerWasmDebugPrint, "(ii)", NULL},
59 : };
60 :
61 : static NativeModule s_wasmModule("stappler:wasm/wasm", stapper_wasm_symbols, sizeof(stapper_wasm_symbols) / sizeof(NativeSymbol));
62 :
63 25 : Runtime *Runtime::getInstance() {
64 25 : std::unique_lock lock(s_loadMutex);
65 25 : if (!s_instance) {
66 25 : s_instance = new Runtime;
67 : }
68 25 : return s_instance;
69 25 : }
70 :
71 0 : Runtime::~Runtime() {
72 0 : if (_data->enabled) {
73 0 : wasm_runtime_destroy();
74 0 : _data->enabled = false;
75 : }
76 0 : if (_data) {
77 0 : delete _data;
78 0 : _data = nullptr;
79 : }
80 0 : }
81 :
82 : struct RuntimeNativeStorage {
83 275 : static RuntimeNativeStorage *getInstance() {
84 275 : static RuntimeNativeStorage s_instance;
85 275 : return &s_instance;
86 : }
87 :
88 : Set<NativeModule *> s_nativeModules;
89 : };
90 :
91 :
92 25 : Runtime::Runtime() {
93 25 : _data = new Data;
94 :
95 : RuntimeInitArgs init_args;
96 25 : memset(&init_args, 0, sizeof(RuntimeInitArgs));
97 :
98 25 : init_args.mem_alloc_type = Alloc_With_Allocator;
99 25 : init_args.mem_alloc_option.allocator.malloc_func = (void *)&runtime_malloc;
100 25 : init_args.mem_alloc_option.allocator.realloc_func = (void *)&runtime_realloc;
101 25 : init_args.mem_alloc_option.allocator.free_func = (void *)&runtime_free;
102 : // init_args.mem_alloc_option.allocator.user_data = this;
103 :
104 25 : init_args.n_native_symbols = 0;
105 25 : init_args.native_module_name = "env";
106 25 : init_args.native_symbols = nullptr;
107 :
108 : #ifdef WASM_DEBUG
109 : strcpy(init_args.ip_addr, "127.0.0.1");
110 : init_args.instance_port = 0;
111 : #endif
112 :
113 : /* initialize runtime environment */
114 25 : if (wasm_runtime_full_init(&init_args)) {
115 25 : _data->enabled = true;
116 :
117 150 : for (auto &it : RuntimeNativeStorage::getInstance()->s_nativeModules) {
118 125 : wasm_runtime_register_natives(it->name.data(), it->symbols, it->symbolsCount);
119 : }
120 : }
121 25 : }
122 :
123 125 : NativeModule::NativeModule(StringView n, NativeSymbol *s, size_t count)
124 125 : : name(n.str<Interface>()), symbols(s), symbolsCount(count) {
125 125 : RuntimeNativeStorage::getInstance()->s_nativeModules.emplace(this);
126 125 : }
127 :
128 125 : NativeModule::~NativeModule() {
129 125 : RuntimeNativeStorage::getInstance()->s_nativeModules.erase(this);
130 125 : }
131 :
132 50 : Module::~Module() {
133 25 : if (_module) {
134 25 : wasm_runtime_unload(_module);
135 25 : _module = nullptr;
136 : }
137 50 : }
138 :
139 0 : bool Module::init(StringView name, BytesView data) {
140 0 : char errorBuf[128] = { 0 };
141 :
142 0 : _runtime = Runtime::getInstance();
143 :
144 0 : _data = data.bytes<Interface>();
145 :
146 0 : auto mod = wasm_runtime_load(const_cast<uint8_t *>(_data.data()), static_cast<uint32_t>(_data.size()), errorBuf, sizeof(errorBuf));
147 0 : if (!mod) {
148 0 : log::error("wasm::Module", "Fail to load module: ", errorBuf);
149 0 : return false;
150 : }
151 :
152 0 : _name = name.str<Interface>();
153 0 : _module = mod;
154 :
155 0 : if (!wasm_runtime_register_module(_name.data(), _module, errorBuf, sizeof(errorBuf))) {
156 0 : log::error("wasm::Module", "Fail to register module '", name, "': ", errorBuf);
157 0 : return false;
158 : }
159 :
160 0 : return true;
161 : }
162 :
163 0 : bool Module::init(StringView name, Bytes &&data) {
164 0 : char errorBuf[128] = { 0 };
165 :
166 0 : _runtime = Runtime::getInstance();
167 :
168 0 : _data = move(data);
169 :
170 0 : auto mod = wasm_runtime_load(const_cast<uint8_t *>(_data.data()), static_cast<uint32_t>(_data.size()), errorBuf, sizeof(errorBuf));
171 0 : if (!mod) {
172 0 : log::error("wasm::Module", "Fail to load module: ", errorBuf);
173 0 : return false;
174 : }
175 :
176 0 : _name = name.str<Interface>();
177 0 : _module = mod;
178 :
179 0 : if (!wasm_runtime_register_module(_name.data(), _module, errorBuf, sizeof(errorBuf))) {
180 0 : log::error("wasm::Module", "Fail to register module '", name, "': ", errorBuf);
181 0 : return false;
182 : }
183 :
184 0 : return true;
185 : }
186 :
187 25 : bool Module::init(StringView name, FilePath path) {
188 25 : char errorBuf[128] = { 0 };
189 25 : _data = filesystem::readIntoMemory<Interface>(path.get());
190 :
191 25 : if (_data.empty()) {
192 0 : log::error("wasm::Module", "Fail to open file: ", path.get());
193 0 : return false;
194 : }
195 :
196 25 : _runtime = Runtime::getInstance();
197 :
198 25 : auto mod = wasm_runtime_load(const_cast<uint8_t *>(_data.data()), static_cast<uint32_t>(_data.size()), errorBuf, sizeof(errorBuf));
199 25 : if (!mod) {
200 0 : log::error("wasm::Module", "Fail to load module '", path.get(), "': ", errorBuf);
201 0 : return false;
202 : }
203 :
204 25 : _name = name.str<Interface>();
205 25 : _module = mod;
206 :
207 25 : if (!wasm_runtime_register_module(_name.data(), _module, errorBuf, sizeof(errorBuf))) {
208 0 : log::error("wasm::Module", "Fail to register module '", path.get(), "': ", errorBuf);
209 0 : return false;
210 : }
211 :
212 25 : return true;
213 : }
214 :
215 50 : ModuleInstance::~ModuleInstance() {
216 25 : if (_inst) {
217 100 : for (auto &it : _handles) {
218 75 : if (it.destructor) {
219 25 : it.destructor();
220 : }
221 75 : it.destructor = nullptr;
222 : }
223 :
224 25 : _handles.clear();
225 25 : _objects.clear();
226 :
227 25 : auto senv = wasm_runtime_get_exec_env_singleton(_inst);
228 25 : if (senv && _finalize) {
229 25 : wasm_runtime_call_wasm(senv, _finalize, 0, nullptr);
230 25 : _finalize = nullptr;
231 : }
232 25 : wasm_runtime_deinstantiate(_inst);
233 25 : _inst = nullptr;
234 : }
235 50 : }
236 :
237 25 : bool ModuleInstance::init(Module *mod, uint32_t stackSize, uint32_t heapSize) {
238 25 : char errorBuf[128] = { 0 };
239 25 : auto inst = wasm_runtime_instantiate(mod->getModule(), stackSize, heapSize, errorBuf, sizeof(errorBuf));
240 : /* instantiate the module */
241 25 : if (!inst) {
242 0 : log::error("wasm::Module", "Fail to instantiate module '", mod->getName(), "': ", errorBuf);
243 0 : return false;
244 : }
245 :
246 25 : _inst = inst;
247 25 : _module = mod;
248 :
249 25 : auto senv = wasm_runtime_get_exec_env_singleton(_inst);
250 25 : auto env = Rc<ExecEnv>::create(this, senv);
251 :
252 25 : auto realloc = Rc<ExecFunction>::create(this, "realloc");
253 25 : if (realloc && realloc->getNumArgs() == 2 && realloc->getNumResults() == 1) {
254 25 : _realloc = realloc->getFunc();
255 : }
256 :
257 25 : _selfHandle = addHandle(this, [] { });
258 :
259 : // search for _start
260 25 : auto initialize = Rc<ExecFunction>::create(this, "_initialize");
261 : // search for _initialize
262 25 : if (initialize) {
263 25 : if (initialize->getName() != "_initialize") {
264 : // component-named _initialize, try stappler_initialize first
265 0 : auto stapplerInitialize = Rc<ExecFunction>::create(this, "initialize");
266 0 : if (stapplerInitialize) {
267 0 : stapplerInitialize->call(env);
268 : } else {
269 0 : initialize->call(env);
270 : }
271 0 : }
272 : } else {
273 0 : auto start = Rc<ExecFunction>::create(this, "_start");
274 0 : if (start) {
275 : // assume WASI command module
276 0 : return true;
277 : }
278 0 : }
279 :
280 25 : auto fin = Rc<ExecFunction>::create(this, "_finalize");
281 25 : if (fin && fin->getNumArgs() == 0 && fin->getNumResults() == 0) {
282 25 : _finalize = fin->getFunc();
283 : }
284 :
285 25 : _handles.reserve(16);
286 :
287 25 : return true;
288 25 : }
289 :
290 375 : void *ModuleInstance::appToNative(uint32_t offset) const {
291 375 : return wasm_runtime_addr_app_to_native(_inst, offset);
292 : }
293 :
294 0 : uint32_t ModuleInstance::nativeToApp(void *ptr) const {
295 0 : return wasm_runtime_addr_native_to_app(_inst, ptr);
296 : }
297 :
298 450 : uint32_t ModuleInstance::allocate(uint32_t size, void **ptr) {
299 450 : return wasm_runtime_module_malloc(_inst, size, ptr);
300 : }
301 :
302 0 : uint32_t ModuleInstance::reallocate(uint32_t offset, uint32_t size, void **ptr) {
303 0 : if (_realloc) {
304 : uint32_t args[] = {
305 : offset, size
306 0 : };
307 :
308 0 : auto senv = wasm_runtime_get_exec_env_singleton(_inst);
309 :
310 0 : if (wasm_runtime_call_wasm(senv, _realloc, 2, args)) {
311 0 : if (ptr) {
312 0 : *ptr = appToNative(args[0]);
313 : }
314 0 : return args[0];
315 : }
316 : }
317 :
318 0 : wasm_runtime_module_free(_inst, offset);
319 0 : return wasm_runtime_module_malloc(_inst, size, ptr);
320 : }
321 :
322 150 : void ModuleInstance::free(uint32_t ptr) {
323 150 : wasm_runtime_module_free(_inst, ptr);
324 150 : }
325 :
326 25 : Rc<ExecFunction> ModuleInstance::lookup(StringView name) {
327 50 : return Rc<ExecFunction>::create(this, name);
328 : }
329 :
330 0 : uint32_t ModuleInstance::getHandle(void *obj) const {
331 0 : auto it = _objects.find(obj);
332 0 : if (it != _objects.end()) {
333 0 : return it->second;
334 : }
335 0 : return InvalidHandle;
336 : }
337 :
338 75 : void ModuleInstance::removeHandle(uint32_t idx) {
339 75 : if (idx >= _handles.size()) {
340 0 : return;
341 : }
342 :
343 75 : auto &slot = _handles.at(idx);
344 75 : if (slot.object) {
345 75 : _objects.erase(slot.object);
346 75 : if (slot.destructor) {
347 50 : slot.destructor();
348 50 : slot.destructor = nullptr;
349 : }
350 75 : slot.nextIndex = _freeHandleSlot;
351 75 : slot.object = nullptr;
352 :
353 75 : _freeHandleSlot = slot.index;
354 : }
355 : }
356 :
357 25 : void ModuleInstance::removeObject(void *obj) {
358 25 : auto it = _objects.find(obj);
359 25 : if (it != _objects.end()) {
360 25 : removeHandle(it->second);
361 : }
362 25 : }
363 :
364 100 : uint32_t ModuleInstance::addHandleObject(void *obj, std::type_index &&idx, Function<void()> &&cb) {
365 100 : if (!obj) {
366 0 : return InvalidHandle;
367 : }
368 :
369 100 : HandleSlot *slot = nullptr;
370 100 : if (_freeHandleSlot != InvalidHandle) {
371 25 : slot = &_handles.at(_freeHandleSlot);
372 25 : _freeHandleSlot = slot->nextIndex;
373 25 : slot->nextIndex = InvalidHandle;
374 : } else {
375 75 : slot = &_handles.emplace_back(HandleSlot());
376 75 : slot->index = _handles.size() - 1;
377 : }
378 :
379 100 : slot->object = obj;
380 100 : slot->type = move(idx);
381 100 : slot->destructor = move(cb);
382 :
383 100 : _objects.emplace(obj, slot->index);
384 100 : return slot->index;
385 : }
386 :
387 0 : uint32_t ModuleInstance::getHandleObject(void *obj, const std::type_index &type) const {
388 0 : auto it = _objects.find(obj);
389 0 : if (it != _objects.end()) {
390 0 : uint32_t idx = it->second;
391 0 : if (_handles[idx].type == type) {
392 0 : return idx;
393 : }
394 : }
395 0 : return InvalidHandle;
396 : }
397 :
398 275 : void *ModuleInstance::getObjectHandle(uint32_t idx, const std::type_index &type) const {
399 275 : if (idx >= _handles.size()) {
400 0 : return nullptr;
401 : }
402 :
403 275 : auto &slot = _handles.at(idx);
404 275 : if (slot.type == type) {
405 275 : return slot.object;
406 : }
407 :
408 0 : return nullptr;
409 : }
410 :
411 525 : ExecEnv *ExecEnv::get(wasm_exec_env_t env) {
412 525 : return reinterpret_cast<ExecEnv *>(wasm_runtime_get_user_data(env));
413 : }
414 :
415 100 : ExecEnv::~ExecEnv() {
416 50 : if (_env) {
417 50 : if (_isSingleton) {
418 25 : wasm_runtime_set_user_data(_env, nullptr);
419 : } else {
420 25 : wasm_runtime_destroy_exec_env(_env);
421 : }
422 50 : _env = nullptr;
423 : }
424 100 : }
425 :
426 25 : bool ExecEnv::init(ModuleInstance *inst, uint32_t stackSize) {
427 25 : auto env = wasm_runtime_create_exec_env(inst->getInstance(), stackSize);
428 : /* create an execution env */
429 25 : if (!env) {
430 0 : log::error("wasm::Module", "Fail to create exec env for '", inst->getModule()->getName(), "' instance");
431 0 : return false;
432 : }
433 :
434 25 : _env = env;
435 25 : _instance = inst;
436 :
437 25 : wasm_runtime_set_user_data(_env, this);
438 :
439 : #ifdef WASM_DEBUG
440 : auto debugEnv = ::getenv("WASM_DEBUG");
441 : if (debugEnv) {
442 : auto port = wasm_runtime_start_debug_instance(env);
443 : log::info("wasm::Runtime", "start debug server with port ", port, "; Wait for debugger connection...");
444 : }
445 : #endif
446 :
447 25 : return true;
448 : }
449 :
450 25 : bool ExecEnv::init(ModuleInstance *inst, wasm_exec_env_t env) {
451 25 : if (wasm_runtime_get_user_data(env)) {
452 0 : log::warn("wasm::Module", "Userdata is not empty for '", inst->getModule()->getName(), "' instance env, it will be lost");
453 : }
454 :
455 25 : _env = env;
456 25 : _instance = inst;
457 25 : _isSingleton = true;
458 :
459 25 : wasm_runtime_set_user_data(_env, this);
460 :
461 25 : return true;
462 : }
463 :
464 0 : uint32_t ExecEnv::nativeToApp(void *ptr) const {
465 0 : return _instance->nativeToApp(ptr);
466 : }
467 :
468 0 : bool ExecEnv::callIndirect(uint32_t fn, uint32_t argc, uint32_t argv[]) {
469 0 : return wasm_runtime_call_indirect(_env, fn, argc, argv);
470 : }
471 :
472 100 : bool ExecFunction::init(ModuleInstance *inst, StringView name) {
473 : // search not prefixed version first
474 100 : _name = name.str<Interface>();
475 :
476 100 : auto fn = wasm_runtime_lookup_function(inst->getInstance(), _name.data(), NULL);
477 100 : if (!fn) {
478 25 : _name = toString(inst->getModule()->getName(), "#", name);
479 : // search module-prefixed
480 25 : fn = wasm_runtime_lookup_function(inst->getInstance(), _name.data(), NULL);
481 : }
482 :
483 100 : if (!fn) {
484 0 : log::warn("wasm::ExecFunction", "Fail to lookup function '", name, "' in module '", inst->getModule()->getName(), "'");
485 0 : return false;
486 : }
487 :
488 100 : _func = fn;
489 100 : _inst = inst;
490 100 : _nArgs = wasm_func_get_param_count(_func, _inst->getInstance());
491 100 : _nResults = wasm_func_get_result_count(_func, _inst->getInstance());
492 :
493 100 : if (_nArgs > StaticArgumentsLimit) {
494 0 : log::warn("wasm::ExecFunction", "Too many arguments for '", _name, "' in module '", inst->getModule()->getName(), "'");
495 : } else {
496 100 : wasm_func_get_param_types(_func, _inst->getInstance(), _argTypesStatic);
497 : }
498 :
499 100 : if (_nResults > StaticResultsLimit) {
500 0 : log::warn("wasm::ExecFunction", "Too many results for '", _name, "' in module '", inst->getModule()->getName(), "'");
501 : } else {
502 100 : wasm_func_get_result_types(_func, _inst->getInstance(), _resultTypesStatic);
503 : }
504 :
505 100 : return true;
506 : }
507 :
508 0 : Vector<wasm_valkind_t> ExecFunction::getArgsFull() const {
509 0 : Vector<wasm_valkind_t> ret; ret.resize(_nArgs);
510 0 : wasm_func_get_param_types(_func, _inst->getInstance(), ret.data());
511 0 : return ret;
512 0 : }
513 :
514 0 : Vector<wasm_valkind_t> ExecFunction::getResultsFull() const {
515 0 : Vector<wasm_valkind_t> ret; ret.resize(_nResults);
516 0 : wasm_func_get_result_types(_func, _inst->getInstance(), ret.data());
517 0 : return ret;
518 0 : }
519 :
520 0 : bool ExecFunction::call(ExecEnv *env, SpanView<wasm_val_t> args, VectorAdapter<wasm_val_t> *results) const {
521 0 : if (args.size() != _nArgs) {
522 0 : log::warn("wasm::ExecFunction", "Wrong number of arguments for '", _name, "' from module '", _inst->getModule()->getName(), "'");
523 : }
524 :
525 0 : bool ret = false;
526 0 : if (results) {
527 0 : results->resize(_nResults);
528 0 : ret = wasm_runtime_call_wasm_a(env->getEnv(), _func, _nResults, results->begin(), args.size(), const_cast<wasm_val_t *>(args.data()));
529 : } else {
530 0 : if (_nResults) {
531 0 : log::warn("wasm::ExecFunction", "Results buffer was not provided for call of '", _name, "' from module '", _inst->getModule()->getName(), "'");
532 : }
533 0 : ret = wasm_runtime_call_wasm_a(env->getEnv(), _func, 0, nullptr, args.size(), const_cast<wasm_val_t *>(args.data()));
534 : }
535 :
536 0 : if (!ret) {
537 0 : auto ex = wasm_runtime_get_exception(_inst->getInstance());
538 0 : if (ex) {
539 0 : log::error("wasm::ExecFunction", "Exception when call '", _name, "' from module '", _inst->getModule()->getName(), "': ", ex);
540 : }
541 : }
542 :
543 0 : return false;
544 : }
545 :
546 25 : wasm_val_t ExecFunction::call1(ExecEnv *env, SpanView<wasm_val_t> args) const {
547 25 : if (args.size() != _nArgs) {
548 0 : log::warn("wasm::ExecFunction", "Wrong number of arguments for '", _name, "' from module '", _inst->getModule()->getName(), "'");
549 : }
550 :
551 : wasm_val_t ret;
552 25 : ret.kind = WASM_ANYREF;
553 25 : ret.of.foreign = 0;
554 :
555 25 : if (_nResults != 1) {
556 0 : log::warn("wasm::ExecFunction", "Function '", _name, "' from module '", _inst->getModule()->getName(), "' called as single-argument function");
557 : }
558 25 : auto success = wasm_runtime_call_wasm_a(env->getEnv(), _func, 1, &ret, args.size(), const_cast<wasm_val_t *>(args.data()));
559 25 : if (!success) {
560 0 : auto ex = wasm_runtime_get_exception(_inst->getInstance());
561 0 : if (ex) {
562 0 : log::error("wasm::ExecFunction", "Exception when call '", _name, "' from module '", _inst->getModule()->getName(), "': ", ex);
563 : }
564 : }
565 25 : return ret;
566 : }
567 :
568 : }
|