wasm.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. /*
  2. * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
  3. * Copyright (c) 2022, the SerenityOS developers.
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibCore/ArgsParser.h>
  8. #include <LibCore/File.h>
  9. #include <LibCore/FileStream.h>
  10. #include <LibLine/Editor.h>
  11. #include <LibMain/Main.h>
  12. #include <LibWasm/AbstractMachine/AbstractMachine.h>
  13. #include <LibWasm/AbstractMachine/BytecodeInterpreter.h>
  14. #include <LibWasm/Printer/Printer.h>
  15. #include <LibWasm/Types.h>
  16. #include <signal.h>
  17. #include <unistd.h>
  18. RefPtr<Line::Editor> g_line_editor;
  19. static auto g_stdout = Core::OutputFileStream::standard_error();
  20. static Wasm::Printer g_printer { g_stdout };
  21. static bool g_continue { false };
  22. static void (*old_signal)(int);
  23. static Wasm::DebuggerBytecodeInterpreter g_interpreter;
  24. static void sigint_handler(int)
  25. {
  26. if (!g_continue) {
  27. signal(SIGINT, old_signal);
  28. kill(getpid(), SIGINT);
  29. }
  30. g_continue = false;
  31. }
  32. static bool post_interpret_hook(Wasm::Configuration&, Wasm::InstructionPointer& ip, Wasm::Instruction const& instr, Wasm::Interpreter const& interpreter)
  33. {
  34. if (interpreter.did_trap()) {
  35. g_continue = false;
  36. warnln("Trapped when executing ip={}", ip);
  37. g_printer.print(instr);
  38. warnln("Trap reason: {}", interpreter.trap_reason());
  39. const_cast<Wasm::Interpreter&>(interpreter).clear_trap();
  40. }
  41. return true;
  42. }
  43. static bool pre_interpret_hook(Wasm::Configuration& config, Wasm::InstructionPointer& ip, Wasm::Instruction const& instr)
  44. {
  45. static bool always_print_stack = false;
  46. static bool always_print_instruction = false;
  47. if (always_print_stack)
  48. config.dump_stack();
  49. if (always_print_instruction) {
  50. g_stdout.write(String::formatted("{:0>4} ", ip.value()).bytes());
  51. g_printer.print(instr);
  52. }
  53. if (g_continue)
  54. return true;
  55. g_stdout.write(String::formatted("{:0>4} ", ip.value()).bytes());
  56. g_printer.print(instr);
  57. String last_command = "";
  58. for (;;) {
  59. auto result = g_line_editor->get_line("> ");
  60. if (result.is_error()) {
  61. return false;
  62. }
  63. auto str = result.release_value();
  64. g_line_editor->add_to_history(str);
  65. if (str.is_empty())
  66. str = last_command;
  67. else
  68. last_command = str;
  69. auto args = str.split_view(' ');
  70. if (args.is_empty())
  71. continue;
  72. auto& cmd = args[0];
  73. if (cmd.is_one_of("h", "help")) {
  74. warnln("Wasm shell commands");
  75. warnln("Toplevel:");
  76. warnln("- [s]tep Run one instruction");
  77. warnln("- next Alias for step");
  78. warnln("- [c]ontinue Execute until a trap or the program exit point");
  79. warnln("- [p]rint <args...> Print various things (see section on print)");
  80. warnln("- call <fn> <args...> Call the function <fn> with the given arguments");
  81. warnln("- set <args...> Set shell option (see section on settings)");
  82. warnln("- unset <args...> Unset shell option (see section on settings)");
  83. warnln("- [h]elp Print this help");
  84. warnln();
  85. warnln("Print:");
  86. warnln("- print [s]tack Print the contents of the stack, including frames and labels");
  87. warnln("- print [[m]em]ory <index> Print the contents of the memory identified by <index>");
  88. warnln("- print [[i]nstr]uction Print the current instruction");
  89. warnln("- print [[f]unc]tion <index> Print the function identified by <index>");
  90. warnln();
  91. warnln("Settings:");
  92. warnln("- set print stack Make the shell print the stack on every instruction executed");
  93. warnln("- set print [instr]uction Make the shell print the instruction that will be executed next");
  94. warnln();
  95. continue;
  96. }
  97. if (cmd.is_one_of("s", "step", "next")) {
  98. return true;
  99. }
  100. if (cmd.is_one_of("p", "print")) {
  101. if (args.size() < 2) {
  102. warnln("Print what?");
  103. continue;
  104. }
  105. auto& what = args[1];
  106. if (what.is_one_of("s", "stack")) {
  107. config.dump_stack();
  108. continue;
  109. }
  110. if (what.is_one_of("m", "mem", "memory")) {
  111. if (args.size() < 3) {
  112. warnln("print what memory?");
  113. continue;
  114. }
  115. auto value = args[2].to_uint<u64>();
  116. if (!value.has_value()) {
  117. warnln("invalid memory index {}", args[2]);
  118. continue;
  119. }
  120. auto mem = config.store().get(Wasm::MemoryAddress(value.value()));
  121. if (!mem) {
  122. warnln("invalid memory index {} (not found)", args[2]);
  123. continue;
  124. }
  125. warnln("{:>32hex-dump}", mem->data().bytes());
  126. continue;
  127. }
  128. if (what.is_one_of("i", "instr", "instruction")) {
  129. g_printer.print(instr);
  130. continue;
  131. }
  132. if (what.is_one_of("f", "func", "function")) {
  133. if (args.size() < 3) {
  134. warnln("print what function?");
  135. continue;
  136. }
  137. auto value = args[2].to_uint<u64>();
  138. if (!value.has_value()) {
  139. warnln("invalid function index {}", args[2]);
  140. continue;
  141. }
  142. auto fn = config.store().get(Wasm::FunctionAddress(value.value()));
  143. if (!fn) {
  144. warnln("invalid function index {} (not found)", args[2]);
  145. continue;
  146. }
  147. if (auto* fn_value = fn->get_pointer<Wasm::HostFunction>()) {
  148. warnln("Host function at {:p}", &fn_value->function());
  149. continue;
  150. }
  151. if (auto* fn_value = fn->get_pointer<Wasm::WasmFunction>()) {
  152. g_printer.print(fn_value->code());
  153. continue;
  154. }
  155. }
  156. }
  157. if (cmd == "call"sv) {
  158. if (args.size() < 2) {
  159. warnln("call what?");
  160. continue;
  161. }
  162. Optional<Wasm::FunctionAddress> address;
  163. auto index = args[1].to_uint<u64>();
  164. if (index.has_value()) {
  165. address = config.frame().module().functions()[index.value()];
  166. } else {
  167. auto& name = args[1];
  168. for (auto& export_ : config.frame().module().exports()) {
  169. if (export_.name() == name) {
  170. if (auto addr = export_.value().get_pointer<Wasm::FunctionAddress>()) {
  171. address = *addr;
  172. break;
  173. }
  174. }
  175. }
  176. }
  177. if (!address.has_value()) {
  178. failed_to_find:;
  179. warnln("Could not find a function {}", args[1]);
  180. continue;
  181. }
  182. auto fn = config.store().get(*address);
  183. if (!fn)
  184. goto failed_to_find;
  185. auto type = fn->visit([&](auto& value) { return value.type(); });
  186. if (type.parameters().size() + 2 != args.size()) {
  187. warnln("Expected {} arguments for call, but found only {}", type.parameters().size(), args.size() - 2);
  188. continue;
  189. }
  190. Vector<u64> values_to_push;
  191. Vector<Wasm::Value> values;
  192. for (size_t index = 2; index < args.size(); ++index)
  193. values_to_push.append(args[index].to_uint().value_or(0));
  194. for (auto& param : type.parameters())
  195. values.append(Wasm::Value { param, values_to_push.take_last() });
  196. Wasm::Result result { Wasm::Trap {} };
  197. {
  198. Wasm::BytecodeInterpreter::CallFrameHandle handle { g_interpreter, config };
  199. result = config.call(g_interpreter, *address, move(values));
  200. }
  201. if (result.is_trap())
  202. warnln("Execution trapped: {}", result.trap().reason);
  203. if (!result.values().is_empty())
  204. warnln("Returned:");
  205. for (auto& value : result.values()) {
  206. g_stdout.write(" -> "sv.bytes());
  207. g_printer.print(value);
  208. }
  209. continue;
  210. }
  211. if (cmd.is_one_of("set", "unset")) {
  212. auto value = !cmd.starts_with('u');
  213. if (args.size() < 3) {
  214. warnln("(un)set what (to what)?");
  215. continue;
  216. }
  217. if (args[1] == "print"sv) {
  218. if (args[2] == "stack"sv)
  219. always_print_stack = value;
  220. else if (args[2].is_one_of("instr", "instruction"))
  221. always_print_instruction = value;
  222. else
  223. warnln("Unknown print category '{}'", args[2]);
  224. continue;
  225. }
  226. warnln("Unknown set category '{}'", args[1]);
  227. continue;
  228. }
  229. if (cmd.is_one_of("c", "continue")) {
  230. g_continue = true;
  231. return true;
  232. }
  233. warnln("Command not understood: {}", cmd);
  234. }
  235. }
  236. static Optional<Wasm::Module> parse(StringView filename)
  237. {
  238. auto result = Core::File::open(filename, Core::OpenMode::ReadOnly);
  239. if (result.is_error()) {
  240. warnln("Failed to open {}: {}", filename, result.error());
  241. return {};
  242. }
  243. auto stream = Core::InputFileStream(result.release_value());
  244. auto parse_result = Wasm::Module::parse(stream);
  245. if (parse_result.is_error()) {
  246. warnln("Something went wrong, either the file is invalid, or there's a bug with LibWasm!");
  247. warnln("The parse error was {}", Wasm::parse_error_to_string(parse_result.error()));
  248. return {};
  249. }
  250. return parse_result.release_value();
  251. }
  252. static void print_link_error(Wasm::LinkError const& error)
  253. {
  254. for (auto const& missing : error.missing_imports)
  255. warnln("Missing import '{}'", missing);
  256. }
  257. ErrorOr<int> serenity_main(Main::Arguments arguments)
  258. {
  259. StringView filename;
  260. bool print = false;
  261. bool attempt_instantiate = false;
  262. bool debug = false;
  263. bool export_all_imports = false;
  264. bool shell_mode = false;
  265. String exported_function_to_execute;
  266. Vector<u64> values_to_push;
  267. Vector<String> modules_to_link_in;
  268. Core::ArgsParser parser;
  269. parser.add_positional_argument(filename, "File name to parse", "file");
  270. parser.add_option(debug, "Open a debugger", "debug", 'd');
  271. parser.add_option(print, "Print the parsed module", "print", 'p');
  272. parser.add_option(attempt_instantiate, "Attempt to instantiate the module", "instantiate", 'i');
  273. parser.add_option(exported_function_to_execute, "Attempt to execute the named exported function from the module (implies -i)", "execute", 'e', "name");
  274. parser.add_option(export_all_imports, "Export noop functions corresponding to imports", "export-noop", 0);
  275. parser.add_option(shell_mode, "Launch a REPL in the module's context (implies -i)", "shell", 's');
  276. parser.add_option(Core::ArgsParser::Option {
  277. .requires_argument = true,
  278. .help_string = "Extra modules to link with, use to resolve imports",
  279. .long_name = "link",
  280. .short_name = 'l',
  281. .value_name = "file",
  282. .accept_value = [&](char const* str) {
  283. if (auto v = StringView { str }; !v.is_empty()) {
  284. modules_to_link_in.append(v);
  285. return true;
  286. }
  287. return false;
  288. },
  289. });
  290. parser.add_option(Core::ArgsParser::Option {
  291. .requires_argument = true,
  292. .help_string = "Supply arguments to the function (default=0) (expects u64, casts to required type)",
  293. .long_name = "arg",
  294. .short_name = 0,
  295. .value_name = "u64",
  296. .accept_value = [&](char const* str) -> bool {
  297. if (auto v = StringView { str }.to_uint<u64>(); v.has_value()) {
  298. values_to_push.append(v.value());
  299. return true;
  300. }
  301. return false;
  302. },
  303. });
  304. parser.parse(arguments);
  305. if (shell_mode) {
  306. debug = true;
  307. attempt_instantiate = true;
  308. }
  309. if (!shell_mode && debug && exported_function_to_execute.is_empty()) {
  310. warnln("Debug what? (pass -e fn)");
  311. return 1;
  312. }
  313. if (debug || shell_mode) {
  314. old_signal = signal(SIGINT, sigint_handler);
  315. }
  316. if (!exported_function_to_execute.is_empty())
  317. attempt_instantiate = true;
  318. auto parse_result = parse(filename);
  319. if (!parse_result.has_value())
  320. return 1;
  321. if (print && !attempt_instantiate) {
  322. auto out_stream = Core::OutputFileStream::standard_output();
  323. Wasm::Printer printer(out_stream);
  324. printer.print(parse_result.value());
  325. }
  326. if (attempt_instantiate) {
  327. Wasm::AbstractMachine machine;
  328. Core::EventLoop main_loop;
  329. if (debug) {
  330. g_line_editor = Line::Editor::construct();
  331. g_interpreter.pre_interpret_hook = pre_interpret_hook;
  332. g_interpreter.post_interpret_hook = post_interpret_hook;
  333. }
  334. // First, resolve the linked modules
  335. NonnullOwnPtrVector<Wasm::ModuleInstance> linked_instances;
  336. Vector<Wasm::Module> linked_modules;
  337. for (auto& name : modules_to_link_in) {
  338. auto parse_result = parse(name);
  339. if (!parse_result.has_value()) {
  340. warnln("Failed to parse linked module '{}'", name);
  341. return 1;
  342. }
  343. linked_modules.append(parse_result.release_value());
  344. Wasm::Linker linker { linked_modules.last() };
  345. for (auto& instance : linked_instances)
  346. linker.link(instance);
  347. auto link_result = linker.finish();
  348. if (link_result.is_error()) {
  349. warnln("Linking imported module '{}' failed", name);
  350. print_link_error(link_result.error());
  351. return 1;
  352. }
  353. auto instantiation_result = machine.instantiate(linked_modules.last(), link_result.release_value());
  354. if (instantiation_result.is_error()) {
  355. warnln("Instantiation of imported module '{}' failed: {}", name, instantiation_result.error().error);
  356. return 1;
  357. }
  358. linked_instances.append(instantiation_result.release_value());
  359. }
  360. Wasm::Linker linker { parse_result.value() };
  361. for (auto& instance : linked_instances)
  362. linker.link(instance);
  363. if (export_all_imports) {
  364. HashMap<Wasm::Linker::Name, Wasm::ExternValue> exports;
  365. for (auto& entry : linker.unresolved_imports()) {
  366. if (!entry.type.has<Wasm::TypeIndex>())
  367. continue;
  368. auto type = parse_result.value().type(entry.type.get<Wasm::TypeIndex>());
  369. auto address = machine.store().allocate(Wasm::HostFunction(
  370. [name = entry.name, type = type](auto&, auto& arguments) -> Wasm::Result {
  371. StringBuilder argument_builder;
  372. bool first = true;
  373. for (auto& argument : arguments) {
  374. DuplexMemoryStream stream;
  375. Wasm::Printer { stream }.print(argument);
  376. if (first)
  377. first = false;
  378. else
  379. argument_builder.append(", "sv);
  380. ByteBuffer buffer = stream.copy_into_contiguous_buffer();
  381. argument_builder.append(StringView(buffer).trim_whitespace());
  382. }
  383. dbgln("[wasm runtime] Stub function {} was called with the following arguments: {}", name, argument_builder.to_string());
  384. Vector<Wasm::Value> result;
  385. result.ensure_capacity(type.results().size());
  386. for (auto& result_type : type.results())
  387. result.append(Wasm::Value { result_type, 0ull });
  388. return Wasm::Result { move(result) };
  389. },
  390. type));
  391. exports.set(entry, *address);
  392. }
  393. linker.link(exports);
  394. }
  395. auto link_result = linker.finish();
  396. if (link_result.is_error()) {
  397. warnln("Linking main module failed");
  398. print_link_error(link_result.error());
  399. return 1;
  400. }
  401. auto result = machine.instantiate(parse_result.value(), link_result.release_value());
  402. if (result.is_error()) {
  403. warnln("Module instantiation failed: {}", result.error().error);
  404. return 1;
  405. }
  406. auto module_instance = result.release_value();
  407. auto launch_repl = [&] {
  408. Wasm::Configuration config { machine.store() };
  409. Wasm::Expression expression { {} };
  410. config.set_frame(Wasm::Frame {
  411. *module_instance,
  412. Vector<Wasm::Value> {},
  413. expression,
  414. 0,
  415. });
  416. Wasm::Instruction instr { Wasm::Instructions::nop };
  417. Wasm::InstructionPointer ip { 0 };
  418. g_continue = false;
  419. pre_interpret_hook(config, ip, instr);
  420. };
  421. auto stream = Core::OutputFileStream::standard_output();
  422. auto print_func = [&](auto const& address) {
  423. Wasm::FunctionInstance* fn = machine.store().get(address);
  424. stream.write(String::formatted("- Function with address {}, ptr = {}\n", address.value(), fn).bytes());
  425. if (fn) {
  426. stream.write(String::formatted(" wasm function? {}\n", fn->has<Wasm::WasmFunction>()).bytes());
  427. fn->visit(
  428. [&](Wasm::WasmFunction const& func) {
  429. Wasm::Printer printer { stream, 3 };
  430. stream.write(" type:\n"sv.bytes());
  431. printer.print(func.type());
  432. stream.write(" code:\n"sv.bytes());
  433. printer.print(func.code());
  434. },
  435. [](Wasm::HostFunction const&) {});
  436. }
  437. };
  438. if (print) {
  439. // Now, let's dump the functions!
  440. for (auto& address : module_instance->functions()) {
  441. print_func(address);
  442. }
  443. }
  444. if (shell_mode) {
  445. launch_repl();
  446. return 0;
  447. }
  448. if (!exported_function_to_execute.is_empty()) {
  449. Optional<Wasm::FunctionAddress> run_address;
  450. Vector<Wasm::Value> values;
  451. for (auto& entry : module_instance->exports()) {
  452. if (entry.name() == exported_function_to_execute) {
  453. if (auto addr = entry.value().get_pointer<Wasm::FunctionAddress>())
  454. run_address = *addr;
  455. }
  456. }
  457. if (!run_address.has_value()) {
  458. warnln("No such exported function, sorry :(");
  459. return 1;
  460. }
  461. auto instance = machine.store().get(*run_address);
  462. VERIFY(instance);
  463. if (instance->has<Wasm::HostFunction>()) {
  464. warnln("Exported function is a host function, cannot run that yet");
  465. return 1;
  466. }
  467. for (auto& param : instance->get<Wasm::WasmFunction>().type().parameters()) {
  468. if (values_to_push.is_empty())
  469. values.append(Wasm::Value { param, 0ull });
  470. else
  471. values.append(Wasm::Value { param, values_to_push.take_last() });
  472. }
  473. if (print) {
  474. outln("Executing ");
  475. print_func(*run_address);
  476. outln();
  477. }
  478. auto result = machine.invoke(g_interpreter, run_address.value(), move(values));
  479. if (debug)
  480. launch_repl();
  481. if (result.is_trap()) {
  482. warnln("Execution trapped: {}", result.trap().reason);
  483. } else {
  484. if (!result.values().is_empty())
  485. warnln("Returned:");
  486. for (auto& value : result.values()) {
  487. Wasm::Printer printer { stream };
  488. g_stdout.write(" -> "sv.bytes());
  489. g_printer.print(value);
  490. }
  491. }
  492. }
  493. }
  494. return 0;
  495. }