wasm.cpp 25 KB

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