From 33e1adc0668c9449530e5839ea9ad3813c9264e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Tokodi?= Date: Mon, 9 Dec 2024 16:00:07 +0100 Subject: [PATCH] WASI: add args_get and args_sizes_get MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the test script to test passing the arguments as well Signed-off-by: Máté Tokodi mate.tokodi@szteszoftver.hu --- src/shell/Shell.cpp | 19 ++++++-- src/wasi/WASI.cpp | 37 ++++++++++++++++ src/wasi/WASI.h | 4 ++ test/wasi/args.wast | 106 ++++++++++++++++++++++++++++++++++++++++++++ tools/run-tests.py | 20 ++++++--- 5 files changed, 177 insertions(+), 9 deletions(-) create mode 100644 test/wasi/args.wast diff --git a/src/shell/Shell.cpp b/src/shell/Shell.cpp index f5ba4056d..5482bd091 100644 --- a/src/shell/Shell.cpp +++ b/src/shell/Shell.cpp @@ -43,6 +43,7 @@ struct ParseOptions { // WASI options std::vector wasi_envs; std::vector> wasi_dirs; + int argsIndex = -1; }; static uint32_t s_JITFlags = 0; @@ -1039,7 +1040,7 @@ static void runExports(Store* store, const std::string& filename, const std::vec &data); } -static void parseArguments(int argc, char* argv[], ParseOptions& options) +static void parseArguments(int argc, const char* argv[], ParseOptions& options) { for (int i = 1; i < argc; i++) { if (strlen(argv[i]) >= 2 && argv[i][0] == '-') { // parse command line option @@ -1083,6 +1084,15 @@ static void parseArguments(int argc, char* argv[], ParseOptions& options) options.wasi_dirs.push_back(std::make_pair(argv[i + 2], argv[i + 1])); i += 2; continue; + } else if (strcmp(argv[i], "--args") == 0) { + if (i + 1 == argc || argv[i + 1][0] == '-') { + fprintf(stderr, "error: --args requires one or more arguments\n"); + exit(1); + } + ++i; + options.fileNames.emplace_back(argv[i]); + options.argsIndex = i; + break; } else if (strcmp(argv[i], "--help") == 0) { fprintf(stdout, "Usage: walrus [OPTIONS] \n\n"); fprintf(stdout, "OPTIONS:\n"); @@ -1095,6 +1105,7 @@ static void parseArguments(int argc, char* argv[], ParseOptions& options) #endif fprintf(stdout, "\t--mapdirs \n\t\tMap real directories to virtual ones for WASI functions to use.\n\t\tExample: ./walrus test.wasm --mapdirs this/real/directory/ this/virtual/directory\n\n"); fprintf(stdout, "\t--env\n\t\tShare host environment to walrus WASI.\n\n"); + fprintf(stdout, "\t--args [ ... ]\n\t\tRun Webassembly module with arguments: must be followed by the name of the Webassembly module file, then optionally following arguments which are passed on to the module\n\t\tExample: ./walrus --args test.wasm 'hello' 'world' 42\n\n"); exit(0); } } @@ -1117,7 +1128,7 @@ static void parseArguments(int argc, char* argv[], ParseOptions& options) } } -int main(int argc, char* argv[]) +int main(int argc, const char* argv[]) { #ifndef NDEBUG setbuf(stdout, NULL); @@ -1164,8 +1175,8 @@ int main(int argc, char* argv[]) init_options.out = 1; init_options.err = 2; init_options.fd_table_size = 3; - init_options.argc = 0; - init_options.argv = nullptr; + init_options.argc = (options.argsIndex == -1 ? 0 : argc - options.argsIndex); + init_options.argv = (options.argsIndex == -1 ? nullptr : argv + options.argsIndex); init_options.envp = envp.data(); init_options.preopenc = dirs.size(); init_options.preopens = dirs.data(); diff --git a/src/wasi/WASI.cpp b/src/wasi/WASI.cpp index 4371b1e0e..7c04d543b 100644 --- a/src/wasi/WASI.cpp +++ b/src/wasi/WASI.cpp @@ -102,6 +102,43 @@ WASI::WasiFuncInfo* WASI::find(const std::string& funcName) return nullptr; } +void WASI::args_get(ExecutionState& state, Value* argv, Value* result, Instance* instance) +{ + uvwasi_size_t argc; + uvwasi_size_t bufSize; + uvwasi_args_sizes_get(WASI::g_uvwasi, &argc, &bufSize); + + uint32_t* uvArgv = reinterpret_cast(get_memory_pointer(instance, argv[0], argc * sizeof(uint32_t))); + char* uvArgBuf = reinterpret_cast(get_memory_pointer(instance, argv[1], bufSize)); + + if (uvArgv == nullptr || uvArgBuf == nullptr) { + result[0] = Value(WasiErrNo::inval); + return; + } + + TemporaryData pointers(argc); + + if (pointers.data() == nullptr) { + result[0] = Value(WasiErrNo::inval); + return; + } + + char** data = reinterpret_cast(pointers.data()); + result[0] = Value(static_cast(uvwasi_args_get(WASI::g_uvwasi, data, uvArgBuf))); + + for (uvwasi_size_t i = 0; i < argc; i++) { + uvArgv[i] = data[i] - uvArgBuf; + } +} + +void WASI::args_sizes_get(ExecutionState& state, Value* argv, Value* result, Instance* instance) +{ + uvwasi_size_t* uvArgc = reinterpret_cast(get_memory_pointer(instance, argv[0], sizeof(uint32_t))); + uvwasi_size_t* uvArgvBufSize = reinterpret_cast(get_memory_pointer(instance, argv[1], sizeof(uint32_t))); + + result[0] = Value(static_cast(uvwasi_args_sizes_get(WASI::g_uvwasi, uvArgc, uvArgvBufSize))); +} + void WASI::proc_exit(ExecutionState& state, Value* argv, Value* result, Instance* instance) { ASSERT(argv[0].type() == Value::I32); diff --git a/src/wasi/WASI.h b/src/wasi/WASI.h index 924b215f3..cca3980fa 100644 --- a/src/wasi/WASI.h +++ b/src/wasi/WASI.h @@ -36,6 +36,8 @@ class WASI { // https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md #define FOR_EACH_WASI_FUNC(F) \ + F(args_get, I32I32_RI32) \ + F(args_sizes_get, I32I32_RI32) \ F(proc_exit, I32R) \ F(proc_raise, I32_RI32) \ F(clock_res_get, I32I32_RI32) \ @@ -153,6 +155,8 @@ class WASI { private: // wasi functions + static void args_get(ExecutionState& state, Value* argv, Value* result, Instance* instance); + static void args_sizes_get(ExecutionState& state, Value* argv, Value* result, Instance* instance); static void proc_exit(ExecutionState& state, Value* argv, Value* result, Instance* instance); static void proc_raise(ExecutionState& state, Value* argv, Value* result, Instance* instance); static void clock_res_get(ExecutionState& state, Value* argv, Value* result, Instance* instance); diff --git a/test/wasi/args.wast b/test/wasi/args.wast new file mode 100644 index 000000000..2efd65ddc --- /dev/null +++ b/test/wasi/args.wast @@ -0,0 +1,106 @@ +(module + (import "wasi_snapshot_preview1" "args_get" (func $wasi_args_get (param i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "args_sizes_get" (func $wasi_args_sizes_get (param i32 i32) (result i32))) + + (memory 1 1) + (data (i32.const 32) "Hello\00World!\00Lorem ipsum dolor sit amet, consectetur adipiscing elit") + + (func (export "check_args")(result i32) + (local $i i32) + (local $mismatched_char_count i32) + (local $argc i32) + (local $argv i32) + (local $argv_buf i32) + (local $argv_buf_size i32) + (local $value_addr i32) + (local $expected_addr i32) + i32.const 0 + local.set $mismatched_char_count + + i32.const 32 + local.set $expected_addr + + ;; Memory[0] = args count + i32.const 0 + local.tee $argc + ;; Memory[4] = args size in characters + i32.const 4 + local.tee $argv_buf_size + call $wasi_args_sizes_get + i32.const 0 + i32.ne + (if + (then + i32.const -1 + return + ) + ) + ;; Memory[128] = argv[] (list of pointers to the strings) + i32.const 128 + local.tee $argv + ;; Memory[192] = argv_buf (the buffer the the strings themselves) + i32.const 192 + local.tee $argv_buf + call $wasi_args_get + i32.const 0 + i32.ne + (if + (then + i32.const -2 + return + ) + ) + + ;; get start of arg string (skip argv[0], which is the program path) + local.get $argv + i32.const 4 + i32.add + i32.load ;; &argv[1] + local.tee $i ;; start $i with the offset of the first char of argv[1] + local.get $argv_buf + i32.add ;; pointer to fist char in the buffer + local.set $value_addr + + (loop $for_each_char + ;; *($expected_addr) != *($value_addr) + local.get $expected_addr + i32.load8_u + local.get $value_addr + i32.load8_u + i32.ne + (if + (then + ;; $mismatched_char_count += 1 + local.get $mismatched_char_count + i32.const 1 + i32.add + local.set $mismatched_char_count + ) + ) + + local.get $value_addr + i32.const 1 + i32.add + local.set $value_addr + + local.get $expected_addr + i32.const 1 + i32.add + local.set $expected_addr + + local.get $i + i32.const 1 + i32.add + local.tee $i + + ;; $i < $argv_buf_size + local.get $argv_buf_size + i32.load + i32.lt_u + br_if $for_each_char + ) + local.get $mismatched_char_count + ) +) + +(assert_return (invoke "check_args") (i32.const 0)) diff --git a/tools/run-tests.py b/tools/run-tests.py index 892b04052..295b11184 100755 --- a/tools/run-tests.py +++ b/tools/run-tests.py @@ -61,7 +61,7 @@ def __call__(self, fn): DEFAULT_RUNNERS.append(self.suite) return fn -def _run_wast_tests(engine, files, is_fail): +def _run_wast_tests(engine, files, is_fail, args=None): fails = 0 for file in files: if jit: @@ -69,7 +69,13 @@ def _run_wast_tests(engine, files, is_fail): if filename in JIT_EXCLUDE_FILES: continue - proc = Popen([engine, "--mapdirs", "./test/wasi", "/var", file], stdout=PIPE) if not jit else Popen([engine, "--mapdirs", "./test/wasi", "/var", "--jit", file], stdout=PIPE) + subprocess_args = [engine, "--mapdirs", "./test/wasi", "/var"] + if jit: subprocess_args.append("--jit") + if args: subprocess_args.append("--args") + subprocess_args.append(file) + if args: subprocess_args.extend(args) + + proc = Popen(subprocess_args, stdout=PIPE) out, _ = proc.communicate() if is_fail and proc.returncode or not is_fail and not proc.returncode: @@ -77,7 +83,6 @@ def _run_wast_tests(engine, files, is_fail): else: print('%sFAIL(%d): %s%s' % (COLOR_RED, proc.returncode, file, COLOR_RESET)) print(out) - fails += 1 return fails @@ -125,9 +130,15 @@ def run_wasi_tests(engine): print('Running wasi tests:') xpass = glob(join(TEST_DIR, '*.wast')) + args_tests = glob(join(TEST_DIR, 'args.wast')) + for item in args_tests: + xpass.remove(item) + xpass_result = _run_wast_tests(engine, xpass, False) + xpass_result += _run_wast_tests(engine, args_tests, False, + args=["Hello", "World!", "Lorem ipsum dolor sit amet, consectetur adipiscing elit"]) - tests_total = len(xpass) + tests_total = len(xpass) + len(args_tests) fail_total = xpass_result print('TOTAL: %d' % (tests_total)) print('%sPASS : %d%s' % (COLOR_GREEN, tests_total - fail_total, COLOR_RESET)) @@ -136,7 +147,6 @@ def run_wasi_tests(engine): if fail_total > 0: raise Exception("basic wasi tests failed") - @runner('jit', default=True) def run_jit_tests(engine): TEST_DIR = join(PROJECT_SOURCE_DIR, 'test', 'jit')