diff --git a/src/runtime/SpecTest.h b/src/runtime/SpecTest.h new file mode 100644 index 000000000..65062014d --- /dev/null +++ b/src/runtime/SpecTest.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Walrus.h" + +namespace Walrus { + +class SpecTestFunctionTypes { + MAKE_STACK_ALLOCATED(); + +public: + enum Index : uint8_t { + // The R is meant to represent the results, after R are the result types. + NONE = 0, + I32R, + RI32, + I64R, + F32R, + F64R, + I32F32R, + F64F64R, + INVALID, + INDEX_NUM, + }; + + SpecTestFunctionTypes() + { + m_vector.reserve(INDEX_NUM); + size_t index = 0; + + ValueTypeVector* param; + ValueTypeVector* result; + + { + // NONE + param = new ValueTypeVector(); + result = new ValueTypeVector(); + m_vector[index++] = new FunctionType(param, result); + } + { + // I32R + param = new ValueTypeVector(); + result = new ValueTypeVector(); + param->push_back(Value::Type::I32); + m_vector[index++] = new FunctionType(param, result); + } + { + // RI32 + param = new ValueTypeVector(); + result = new ValueTypeVector(); + result->push_back(Value::Type::I32); + m_vector[index++] = new FunctionType(param, result); + } + { + // I64 + param = new ValueTypeVector(); + result = new ValueTypeVector(); + param->push_back(Value::Type::I64); + m_vector[index++] = new FunctionType(param, result); + } + { + // F32 + param = new ValueTypeVector(); + result = new ValueTypeVector(); + param->push_back(Value::Type::F32); + m_vector[index++] = new FunctionType(param, result); + } + { + // F64 + param = new ValueTypeVector(); + result = new ValueTypeVector(); + param->push_back(Value::Type::F64); + m_vector[index++] = new FunctionType(param, result); + } + { + // I32F32 + param = new ValueTypeVector(); + result = new ValueTypeVector(); + param->push_back(Value::Type::I32); + param->push_back(Value::Type::F32); + m_vector[index++] = new FunctionType(param, result); + } + { + // F64F64 + param = new ValueTypeVector(); + result = new ValueTypeVector(); + param->push_back(Value::Type::F64); + param->push_back(Value::Type::F64); + m_vector[index++] = new FunctionType(param, result); + } + { + // INVALID + param = new ValueTypeVector(); + result = new ValueTypeVector(); + param->push_back(Value::Type::Void); + m_vector[index++] = new FunctionType(param, result); + } + + ASSERT(index == INDEX_NUM); + } + + ~SpecTestFunctionTypes() + { + for (size_t i = 0; i < m_vector.size(); i++) { + delete m_vector[i]; + } + } + + FunctionType* operator[](const size_t idx) + { + ASSERT(idx < m_vector.size()); + return m_vector[idx]; + } + +private: + FunctionTypeVector m_vector; +}; + +} // namespace Walrus diff --git a/src/shell/Shell.cpp b/src/shell/Shell.cpp index 94d3d72d9..70935ab34 100644 --- a/src/shell/Shell.cpp +++ b/src/shell/Shell.cpp @@ -20,6 +20,8 @@ #include "runtime/Tag.h" #include "runtime/Trap.h" #include "parser/WASMParser.h" +#include "wasi/Wasi.h" + #include "wabt/wast-lexer.h" #include "wabt/wast-parser.h" @@ -105,109 +107,7 @@ static void printF64(double v) printf("%s : f64\n", formatDecmialString(ss.str()).c_str()); } -class SpecTestFunctionTypes { - MAKE_STACK_ALLOCATED(); - -public: - enum Index : uint8_t { - NONE = 0, - I32, - I64, - F32, - F64, - I32F32, - F64F64, - INVALID, - INDEX_NUM, - }; - - SpecTestFunctionTypes() - { - m_vector.reserve(INDEX_NUM); - size_t index = 0; - - ValueTypeVector* param; - ValueTypeVector* result; - - { - // NONE - param = new ValueTypeVector(); - result = new ValueTypeVector(); - m_vector[index++] = new FunctionType(param, result); - } - { - // I32 - param = new ValueTypeVector(); - result = new ValueTypeVector(); - param->push_back(Value::Type::I32); - m_vector[index++] = new FunctionType(param, result); - } - { - // I64 - param = new ValueTypeVector(); - result = new ValueTypeVector(); - param->push_back(Value::Type::I64); - m_vector[index++] = new FunctionType(param, result); - } - { - // F32 - param = new ValueTypeVector(); - result = new ValueTypeVector(); - param->push_back(Value::Type::F32); - m_vector[index++] = new FunctionType(param, result); - } - { - // F64 - param = new ValueTypeVector(); - result = new ValueTypeVector(); - param->push_back(Value::Type::F64); - m_vector[index++] = new FunctionType(param, result); - } - { - // I32F32 - param = new ValueTypeVector(); - result = new ValueTypeVector(); - param->push_back(Value::Type::I32); - param->push_back(Value::Type::F32); - m_vector[index++] = new FunctionType(param, result); - } - { - // F64F64 - param = new ValueTypeVector(); - result = new ValueTypeVector(); - param->push_back(Value::Type::F64); - param->push_back(Value::Type::F64); - m_vector[index++] = new FunctionType(param, result); - } - { - // INVALID - param = new ValueTypeVector(); - result = new ValueTypeVector(); - param->push_back(Value::Type::Void); - m_vector[index++] = new FunctionType(param, result); - } - - ASSERT(index == INDEX_NUM); - } - - ~SpecTestFunctionTypes() - { - for (size_t i = 0; i < m_vector.size(); i++) { - delete m_vector[i]; - } - } - - FunctionType* operator[](const size_t idx) - { - ASSERT(idx < m_vector.size()); - return m_vector[idx]; - } - -private: - FunctionTypeVector m_vector; -}; - -static Trap::TrapResult executeWASM(Store* store, const std::string& filename, const std::vector& src, SpecTestFunctionTypes& functionTypes, +static Trap::TrapResult executeWASM(Store* store, const std::string& filename, const std::vector& src, SpecTestFunctionTypes& functionTypes, WASI* wasi, std::map* registeredInstanceMap = nullptr) { auto parseResult = WASMParser::parseBinary(store, filename, src.data(), src.size()); @@ -241,7 +141,6 @@ static Trap::TrapResult executeWASM(Store* store, const std::string& filename, c (func (export "print_f64_f64") (param f64 f64)) ) */ - for (size_t i = 0; i < importTypes.size(); i++) { auto import = importTypes[i]; if (import->moduleName() == "spectest") { @@ -254,7 +153,7 @@ static Trap::TrapResult executeWASM(Store* store, const std::string& filename, c }, nullptr)); } else if (import->fieldName() == "print_i32") { - auto ft = functionTypes[SpecTestFunctionTypes::I32]; + auto ft = functionTypes[SpecTestFunctionTypes::I32R]; importValues.push_back(ImportedFunction::createImportedFunction( store, ft, @@ -263,7 +162,7 @@ static Trap::TrapResult executeWASM(Store* store, const std::string& filename, c }, nullptr)); } else if (import->fieldName() == "print_i64") { - auto ft = functionTypes[SpecTestFunctionTypes::I64]; + auto ft = functionTypes[SpecTestFunctionTypes::I64R]; importValues.push_back(ImportedFunction::createImportedFunction( store, ft, @@ -272,7 +171,7 @@ static Trap::TrapResult executeWASM(Store* store, const std::string& filename, c }, nullptr)); } else if (import->fieldName() == "print_f32") { - auto ft = functionTypes[SpecTestFunctionTypes::F32]; + auto ft = functionTypes[SpecTestFunctionTypes::F32R]; importValues.push_back(ImportedFunction::createImportedFunction( store, ft, @@ -281,7 +180,7 @@ static Trap::TrapResult executeWASM(Store* store, const std::string& filename, c }, nullptr)); } else if (import->fieldName() == "print_f64") { - auto ft = functionTypes[SpecTestFunctionTypes::F64]; + auto ft = functionTypes[SpecTestFunctionTypes::F64R]; importValues.push_back(ImportedFunction::createImportedFunction( store, ft, @@ -290,7 +189,7 @@ static Trap::TrapResult executeWASM(Store* store, const std::string& filename, c }, nullptr)); } else if (import->fieldName() == "print_i32_f32") { - auto ft = functionTypes[SpecTestFunctionTypes::I32F32]; + auto ft = functionTypes[SpecTestFunctionTypes::I32F32R]; importValues.push_back(ImportedFunction::createImportedFunction( store, ft, @@ -300,7 +199,7 @@ static Trap::TrapResult executeWASM(Store* store, const std::string& filename, c }, nullptr)); } else if (import->fieldName() == "print_f64_f64") { - auto ft = functionTypes[SpecTestFunctionTypes::F64F64]; + auto ft = functionTypes[SpecTestFunctionTypes::F64F64R]; importValues.push_back(ImportedFunction::createImportedFunction( store, ft, @@ -332,17 +231,16 @@ static Trap::TrapResult executeWASM(Store* store, const std::string& filename, c nullptr)); } } else if (import->moduleName() == "wasi_snapshot_preview1") { - // TODO wasi - if (import->fieldName() == "proc_exit") { - auto ft = functionTypes[SpecTestFunctionTypes::I32]; + Walrus::WASI::WasiFunc* wasiImportFunc = wasi->find(import->fieldName()); + if (wasiImportFunc == nullptr) { + break; + } + FunctionType* fn = functionTypes[wasiImportFunc->functionType]; + if (fn->equals(import->functionType())) { importValues.push_back(ImportedFunction::createImportedFunction( store, - ft, - [](ExecutionState& state, const uint32_t argc, Value* argv, Value* result, void* data) { - ASSERT(argc == 1 && argv[0].type() == Value::I32); - exit(argv[0].asI32()); - ASSERT_NOT_REACHED(); - }, + const_cast(import->functionType()), + wasiImportFunc->ptr, nullptr)); } } else if (registeredInstanceMap) { @@ -682,7 +580,7 @@ static Instance* fetchInstance(wabt::Var& moduleVar, std::map return registeredInstanceMap[moduleVar.name()]; } -static void executeWAST(Store* store, const std::string& filename, const std::vector& src, SpecTestFunctionTypes& functionTypes) +static void executeWAST(Store* store, const std::string& filename, const std::vector& src, SpecTestFunctionTypes& functionTypes, WASI* wasi) { auto lexer = wabt::WastLexer::CreateBufferLexer("test.wabt", src.data(), src.size()); if (!lexer) { @@ -710,7 +608,7 @@ static void executeWAST(Store* store, const std::string& filename, const std::ve case wabt::CommandType::ScriptModule: { auto* moduleCommand = static_cast(command.get()); auto buf = readModuleData(&moduleCommand->module); - auto trapResult = executeWASM(store, filename, buf->data, functionTypes, ®isteredInstanceMap); + auto trapResult = executeWASM(store, filename, buf->data, functionTypes, wasi, ®isteredInstanceMap); RELEASE_ASSERT(!trapResult.exception); instanceMap[commandCount] = store->getLastInstance(); if (moduleCommand->module.name.size()) { @@ -771,7 +669,7 @@ static void executeWAST(Store* store, const std::string& filename, const std::ve auto tsm = dynamic_cast(m); RELEASE_ASSERT(tsm); auto buf = readModuleData(&tsm->module); - auto trapResult = executeWASM(store, filename, buf->data, functionTypes, ®isteredInstanceMap); + auto trapResult = executeWASM(store, filename, buf->data, functionTypes, wasi, ®isteredInstanceMap); RELEASE_ASSERT(trapResult.exception); std::string& s = trapResult.exception->message(); RELEASE_ASSERT(s.find(assertModuleUninstantiable->text) == 0); @@ -808,7 +706,7 @@ static void executeWAST(Store* store, const std::string& filename, const std::ve } else { buf = dsm->data; } - auto trapResult = executeWASM(store, filename, buf, functionTypes); + auto trapResult = executeWASM(store, filename, buf, functionTypes, wasi); RELEASE_ASSERT(trapResult.exception); std::string& actual = trapResult.exception->message(); printf("assertModuleInvalid (expect compile error: '%s', actual '%s'(line: %d)) : OK\n", assertModuleInvalid->text.data(), actual.data(), assertModuleInvalid->module->location().line); @@ -831,7 +729,7 @@ static void executeWAST(Store* store, const std::string& filename, const std::ve } else { buf = dsm->data; } - auto trapResult = executeWASM(store, filename, buf, functionTypes); + auto trapResult = executeWASM(store, filename, buf, functionTypes, wasi); RELEASE_ASSERT(trapResult.exception); break; } @@ -980,6 +878,7 @@ int main(int argc, char* argv[]) Engine* engine = new Engine(); Store* store = new Store(engine); + WASI* wasi = new WASI(); SpecTestFunctionTypes functionTypes; ArgParser argParser; @@ -1001,14 +900,14 @@ int main(int argc, char* argv[]) if (!argParser.exportToRun.empty()) { runExports(store, filePath, buf, argParser.exportToRun); } else { - auto trapResult = executeWASM(store, filePath, buf, functionTypes); + auto trapResult = executeWASM(store, filePath, buf, functionTypes, wasi); if (trapResult.exception) { fprintf(stderr, "Uncaught Exception: %s\n", trapResult.exception->message().data()); return -1; } } } else if (endsWith(filePath, "wat") || endsWith(filePath, "wast")) { - executeWAST(store, filePath, buf, functionTypes); + executeWAST(store, filePath, buf, functionTypes, wasi); } } else { printf("Cannot open file %s\n", filePath.data()); @@ -1019,6 +918,7 @@ int main(int argc, char* argv[]) // finalize delete store; delete engine; + delete wasi; #if defined(WALRUS_GOOGLE_PERF) ProfilerStop(); diff --git a/src/wasi/Wasi.cpp b/src/wasi/Wasi.cpp new file mode 100644 index 000000000..34eae16ca --- /dev/null +++ b/src/wasi/Wasi.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wasi/Wasi.h" + +namespace Walrus { + +WASI::WasiFunc* WASI::find(std::string funcName) +{ + for (unsigned i = 0; i < WasiFuncName::FuncEnd; ++i) { + if (m_wasiFunctions[i].name == funcName) { + return &m_wasiFunctions[i]; + } + } + return nullptr; +} + +void WASI::test(ExecutionState& state, const uint32_t argc, Value* argv, Value* result, void* data) +{ + printf("No argument test succesful.\n"); +} + +void WASI::printI32(ExecutionState& state, const uint32_t argc, Value* argv, Value* result, void* data) +{ + printf("Recieved number: %d.\n", argv[0].asI32()); +} + +void WASI::writeI32(ExecutionState& state, const uint32_t argc, Value* argv, Value* result, void* data) +{ + printf("Writing 42 to stack.\n"); + result[0] = Value((int32_t)42); +} + +void WASI::proc_exit(ExecutionState& state, const uint32_t argc, Value* argv, Value* result, void* data) +{ + ASSERT(argc == 1 && argv[0].type() == Value::I32); + exit(argv[0].asI32()); + ASSERT_NOT_REACHED(); +} + +void WASI::fillWasiFuncTable() +{ +#define WASI_FUNC_TABLE(NAME, FUNCTYPE) \ + m_wasiFunctions[WASI::WasiFuncName::NAME##FUNC].name = #NAME; \ + m_wasiFunctions[WASI::WasiFuncName::NAME##FUNC].functionType = SpecTestFunctionTypes::FUNCTYPE; \ + m_wasiFunctions[WASI::WasiFuncName::NAME##FUNC].ptr = &WASI::NAME; + FOR_EACH_WASI_FUNC(WASI_FUNC_TABLE) +#undef WASI_FUNC_TABLE +} + +WASI::WASI() +{ + fillWasiFuncTable(); +} + +} // namespace Walrus diff --git a/src/wasi/Wasi.h b/src/wasi/Wasi.h new file mode 100644 index 000000000..b47da38a7 --- /dev/null +++ b/src/wasi/Wasi.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Walrus.h" +#include "runtime/Value.h" +#include "runtime/Function.h" +#include "runtime/ObjectType.h" +#include "runtime/SpecTest.h" + +namespace Walrus { + +class WASI { +public: + WASI(); + + ~WASI() + { + } + + struct WasiFunc { + std::string name; + SpecTestFunctionTypes::Index functionType; + ImportedFunction::ImportedFunctionCallback ptr; + }; + +#define FOR_EACH_WASI_FUNC(F) \ + F(test, NONE) \ + F(printI32, I32R) \ + F(writeI32, RI32) \ + F(proc_exit, I32R) + + enum WasiFuncName : size_t { +#define DECLARE_FUNCTION(NAME, FUNCTYPE) NAME##FUNC, + FOR_EACH_WASI_FUNC(DECLARE_FUNCTION) +#undef DECLARE_FUNCTION + FuncEnd, + }; + + void fillWasiFuncTable(); + WasiFunc* find(std::string funcName); + + static void test(ExecutionState& state, const uint32_t argc, Value* argv, Value* result, void* data); + static void printI32(ExecutionState& state, const uint32_t argc, Value* argv, Value* result, void* data); + static void writeI32(ExecutionState& state, const uint32_t argc, Value* argv, Value* result, void* data); + static void proc_exit(ExecutionState& state, const uint32_t argc, Value* argv, Value* result, void* data); + + WasiFunc m_wasiFunctions[FuncEnd]; +}; + +} // namespace Walrus diff --git a/test/wasi/example.wast b/test/wasi/example.wast new file mode 100644 index 000000000..3c8599390 --- /dev/null +++ b/test/wasi/example.wast @@ -0,0 +1,14 @@ +(module + (import "wasi_snapshot_preview1" "test" (func $test)) + (import "wasi_snapshot_preview1" "printI32" (func $print (param i32))) + (import "wasi_snapshot_preview1" "writeI32" (func $write (result i32))) + + (func (export "start") + call $test + call $write + call $print + ) +) + + +(assert_return (invoke "start")) diff --git a/test/wasi/proc_exit.wast b/test/wasi/proc_exit.wast new file mode 100644 index 000000000..0fbdc1081 --- /dev/null +++ b/test/wasi/proc_exit.wast @@ -0,0 +1,11 @@ +(module + (import "wasi_snapshot_preview1" "proc_exit" (func $exit (param i32))) + + + (func (export "start") + i32.const 0 + call $exit + ) +) + +(assert_return (invoke "start")) diff --git a/tools/run-tests.py b/tools/run-tests.py index 2ebd26728..1492f1f70 100755 --- a/tools/run-tests.py +++ b/tools/run-tests.py @@ -99,7 +99,7 @@ def run(args, cwd=None, env=None, stdout=None, checkresult=True, report=False): def readfile(filename): with open(filename, 'r') as f: return f.readlines() - + def _run_wast_tests(engine, files, is_fail): fails = 0 for file in files: @@ -151,6 +151,23 @@ def run_core_tests(engine): if fail_total > 0: raise Exception("wasm-test-core failed") +@runner('wasi', default=True) +def run_basic_tests(engine): + TEST_DIR = join(PROJECT_SOURCE_DIR, 'test', 'wasi') + + print('Running wasi tests:') + xpass = glob(join(TEST_DIR, '*.wast')) + xpass_result = _run_wast_tests(engine, xpass, False) + + tests_total = len(xpass) + fail_total = xpass_result + print('TOTAL: %d' % (tests_total)) + print('%sPASS : %d%s' % (COLOR_GREEN, tests_total, COLOR_RESET)) + print('%sFAIL : %d%s' % (COLOR_RED, fail_total, COLOR_RESET)) + + if fail_total > 0: + raise Exception("basic wasi tests failed") + def main(): parser = ArgumentParser(description='Walrus Test Suite Runner') parser.add_argument('--engine', metavar='PATH', default=DEFAULT_WALRUS,