Parcourir la source

LibWasm+Meta: Add test-wasm and optionally test the conformance tests

This only tests "can it be parsed", but the goal of this commit is to
provide a test framework that can be built upon :)
The conformance tests are downloaded, compiled* and installed only if
the INCLUDE_WASM_SPEC_TESTS cmake option is enabled.
(*) Since we do not yet have a wast parser, the compilation is delegated
to an external tool from binaryen, `wasm-as`, which is required for the
test suite download/install to succeed.
This *does* run the tests in CI, but it currently does not include the
spec conformance tests.
Ali Mohammad Pur il y a 4 ans
Parent
commit
b3c13c3e8a

+ 2 - 0
.gitignore

@@ -25,3 +25,5 @@ compile_commands.json
 cmake-build-debug/
 sync-local.sh
 .vim/
+
+Userland/Libraries/LibWasm/Tests/Fixtures/SpecTests

+ 31 - 0
CMakeLists.txt

@@ -27,6 +27,7 @@ option(ENABLE_COMPILETIME_FORMAT_CHECK "Enable compiletime format string checks"
 option(ENABLE_PCI_IDS_DOWNLOAD "Enable download of the pci.ids database at build time" ON)
 option(BUILD_LAGOM "Build parts of the system targeting the host OS for fuzzing/testing" OFF)
 option(ENABLE_KERNEL_LTO "Build the kernel with link-time optimization" OFF)
+option(INCLUDE_WASM_SPEC_TESTS "Download and include the WebAssembly spec testsuite" OFF)
 
 add_custom_target(run
     COMMAND ${CMAKE_SOURCE_DIR}/Meta/run.sh
@@ -245,3 +246,33 @@ if(EXISTS ${PCI_IDS_GZ_PATH} AND NOT EXISTS ${PCI_IDS_INSTALL_PATH})
     file(MAKE_DIRECTORY ${CMAKE_INSTALL_DATAROOTDIR})
     file(RENAME ${PCI_IDS_PATH} ${PCI_IDS_INSTALL_PATH})
 endif()
+
+if(INCLUDE_WASM_SPEC_TESTS)
+    set(WASM_SPEC_TEST_GZ_URL https://github.com/WebAssembly/testsuite/archive/refs/heads/master.tar.gz)
+    set(WASM_SPEC_TEST_GZ_PATH ${CMAKE_BINARY_DIR}/wasm-spec-testsuite.tar.gz)
+    set(WASM_SPEC_TEST_TAR_PATH ${CMAKE_BINARY_DIR}/wasm-spec-testsuite.tar)
+    set(WASM_SPEC_TEST_PATH ${CMAKE_SOURCE_DIR}/Userland/Libraries/LibWasm/Tests/Fixtures/SpecTests)
+
+    if(NOT EXISTS ${WASM_SPEC_TEST_GZ_PATH})
+        message(STATUS "Downloading the WebAssembly testsuite from ${WASM_SPEC_TEST_GZ_URL}...")
+        file(DOWNLOAD ${WASM_SPEC_TEST_GZ_URL} ${WASM_SPEC_TEST_GZ_PATH} INACTIVITY_TIMEOUT 10)
+    endif()
+
+    if(EXISTS ${WASM_SPEC_TEST_GZ_PATH} AND NOT EXISTS ${WASM_SPEC_TEST_PATH})
+        message(STATUS "Extracting the WebAssembly testsuite from ${WASM_SPEC_TEST_GZ_PATH}...")
+        file(MAKE_DIRECTORY ${WASM_SPEC_TEST_PATH})
+        execute_process(COMMAND gzip -k -d ${WASM_SPEC_TEST_GZ_PATH})
+        execute_process(COMMAND tar -xf ${WASM_SPEC_TEST_TAR_PATH})
+        execute_process(COMMAND rm ${WASM_SPEC_TEST_TAR_PATH})
+        file(GLOB WASM_TESTS "${CMAKE_BINARY_DIR}/testsuite-master/*.wast")
+        foreach(PATH ${WASM_TESTS})
+            get_filename_component(NAME ${PATH} NAME_WLE)
+            message(STATUS "Compiling WebAssembly test ${NAME}...")
+            execute_process(
+                COMMAND wasm-as -n ${PATH} -o "${WASM_SPEC_TEST_PATH}/${NAME}.wasm"
+                OUTPUT_QUIET
+                ERROR_QUIET)
+        endforeach()
+        file(REMOVE testsuite-master)
+    endif()
+endif()

+ 1 - 0
Documentation/BuildInstructions.md

@@ -252,6 +252,7 @@ There are some optional features that can be enabled during compilation that are
 - `BUILD_LAGOM`: builds [Lagom](../Meta/Lagom/ReadMe.md), which makes various SerenityOS libraries and programs available on the host system.
 - `PRECOMPILE_COMMON_HEADERS`: precompiles some common headers to speedup compilation.
 - `ENABLE_KERNEL_LTO`: builds the kernel with link-time optimization.
+- `INCLUDE_WASM_SPEC_TESTS`: downloads and includes the WebAssembly spec testsuite tests
 
 Many parts of the SerenityOS codebase have debug functionality, mostly consisting of additional messages printed to the debug console. This is done via the `<component_name>_DEBUG` macros, which can be enabled individually at build time. They are listed in [this file](../Meta/CMake/all_the_debug_macros.cmake).
 

+ 15 - 0
Meta/Lagom/CMakeLists.txt

@@ -155,6 +155,21 @@ if (BUILD_LAGOM)
             WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
         )
 
+        add_executable(test-wasm_lagom
+            ../../Tests/LibWasm/test-wasm.cpp
+            ../../Userland/Libraries/LibTest/JavaScriptTestRunnerMain.cpp)
+        set_target_properties(test-wasm_lagom PROPERTIES OUTPUT_NAME test-wasm)
+        target_link_libraries(test-wasm_lagom Lagom)
+        target_link_libraries(test-wasm_lagom stdc++)
+        target_link_libraries(test-wasm_lagom pthread)
+        add_test(
+            NAME WasmParser
+            COMMAND test-wasm_lagom --show-progress=false
+            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+        )
+        set_tests_properties(WasmParser PROPERTIES
+            ENVIRONMENT SERENITY_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/../..)
+
         add_executable(disasm_lagom ../../Userland/Utilities/disasm.cpp)
         set_target_properties(disasm_lagom PROPERTIES OUTPUT_NAME disasm)
         target_link_libraries(disasm_lagom Lagom)

+ 3 - 1
Meta/build-root-filesystem.sh

@@ -104,13 +104,15 @@ mkdir -p mnt/home/anon
 mkdir -p mnt/home/anon/Desktop
 mkdir -p mnt/home/anon/Downloads
 mkdir -p mnt/home/nona
-rm -fr mnt/home/anon/js-tests mnt/home/anon/web-tests mnt/home/anon/cpp-tests
+rm -fr mnt/home/anon/js-tests mnt/home/anon/web-tests mnt/home/anon/cpp-tests mnt/home/anon/wasm-tests
 mkdir -p mnt/home/anon/cpp-tests/
 cp "$SERENITY_SOURCE_DIR"/README.md mnt/home/anon/
 cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibJS/Tests mnt/home/anon/js-tests
 cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibWeb/Tests mnt/home/anon/web-tests
 cp -r "$SERENITY_SOURCE_DIR"/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests mnt/home/anon/cpp-tests/comprehension
 cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibCpp/Tests mnt/home/anon/cpp-tests/parser
+cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibWasm/Tests mnt/home/anon/wasm-tests
+cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibJS/Tests/test-common.js mnt/home/anon/wasm-tests
 chmod 700 mnt/root
 chmod 700 mnt/home/anon
 chmod 700 mnt/home/nona

+ 1 - 0
Tests/CMakeLists.txt

@@ -11,5 +11,6 @@ add_subdirectory(LibM)
 add_subdirectory(LibPthread)
 add_subdirectory(LibRegex)
 add_subdirectory(LibSQL)
+add_subdirectory(LibWasm)
 add_subdirectory(LibWeb)
 add_subdirectory(UserspaceEmulator)

+ 2 - 0
Tests/LibWasm/CMakeLists.txt

@@ -0,0 +1,2 @@
+serenity_testjs_test(test-wasm.cpp test-wasm LIBS LibWasm)
+install(TARGETS test-wasm RUNTIME DESTINATION bin)

+ 70 - 0
Tests/LibWasm/test-wasm.cpp

@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibCore/File.h>
+#include <LibTest/JavaScriptTestRunner.h>
+#include <LibWasm/AbstractMachine/Interpreter.h>
+#include <LibWasm/Types.h>
+
+TEST_ROOT("Userland/Libraries/LibWasm/Tests");
+
+TESTJS_GLOBAL_FUNCTION(read_binary_wasm_file, readBinaryWasmFile)
+{
+    auto filename = vm.argument(0).to_string(global_object);
+    if (vm.exception())
+        return {};
+    auto file = Core::File::open(filename, Core::OpenMode::ReadOnly);
+    if (file.is_error()) {
+        vm.throw_exception<JS::TypeError>(global_object, file.error());
+        return {};
+    }
+    auto contents = file.value()->read_all();
+    auto array = JS::Uint8Array::create(global_object, contents.size());
+    contents.span().copy_to(array->data());
+    return array;
+}
+
+TESTJS_GLOBAL_FUNCTION(parse_webassembly_module, parseWebAssemblyModule)
+{
+    auto object = vm.argument(0).to_object(global_object);
+    if (vm.exception())
+        return {};
+    if (!is<JS::Uint8Array>(object)) {
+        vm.throw_exception<JS::TypeError>(global_object, "Expected a Uint8Array argument to parse_webassembly_module");
+        return {};
+    }
+    auto& array = static_cast<JS::Uint8Array&>(*object);
+    InputMemoryStream stream { array.data() };
+    auto result = Wasm::Module::parse(stream);
+    if (result.is_error()) {
+        vm.throw_exception<JS::SyntaxError>(global_object, Wasm::parse_error_to_string(result.error()));
+        return {};
+    }
+    if (stream.handle_any_error())
+        return JS::js_undefined();
+    return JS::js_null();
+}
+
+TESTJS_GLOBAL_FUNCTION(compare_typed_arrays, compareTypedArrays)
+{
+    auto lhs = vm.argument(0).to_object(global_object);
+    if (vm.exception())
+        return {};
+    if (!is<JS::TypedArrayBase>(lhs)) {
+        vm.throw_exception<JS::TypeError>(global_object, "Expected a TypedArray");
+        return {};
+    }
+    auto& lhs_array = static_cast<JS::TypedArrayBase&>(*lhs);
+    auto rhs = vm.argument(1).to_object(global_object);
+    if (vm.exception())
+        return {};
+    if (!is<JS::TypedArrayBase>(rhs)) {
+        vm.throw_exception<JS::TypeError>(global_object, "Expected a TypedArray");
+        return {};
+    }
+    auto& rhs_array = static_cast<JS::TypedArrayBase&>(*rhs);
+    return JS::Value(lhs_array.viewed_array_buffer()->buffer() == rhs_array.viewed_array_buffer()->buffer());
+}

BIN
Userland/Libraries/LibWasm/Tests/Fixtures/Modules/empty-module.wasm


+ 23 - 0
Userland/Libraries/LibWasm/Tests/Parser/spec-testsuite.js

@@ -0,0 +1,23 @@
+let haveSpecTestSuite = false;
+try {
+    readBinaryWasmFile("Fixtures/SpecTests/address.wasm");
+    haveSpecTestSuite = true;
+} catch {}
+
+let testFunction = haveSpecTestSuite ? test : test.skip;
+
+// prettier-ignore
+const tests = [
+    "address", "align", "binary", "binary-leb128", "br_table", "comments", "endianness", "exports",
+    "f32", "f32_bitwise", "f32_cmp", "f64", "f64_bitwise", "f64_cmp", "float_exprs", "float_literals",
+    "float_memory", "float_misc", "forward", "func_ptrs", "int_exprs", "int_literals", "labels",
+    "left-to-right", "linking", "load", "local_get", "memory", "memory_grow", "memory_redundancy",
+    "memory_size", "memory_trap", "names", "return", "switch", "table", "traps", "type"
+];
+
+for (let testName of tests) {
+    testFunction(`parse ${testName}`, () => {
+        const contents = readBinaryWasmFile(`Fixtures/SpecTests/${testName}.wasm`);
+        parseWebAssemblyModule(contents);
+    });
+}

+ 59 - 0
Userland/Libraries/LibWasm/Tests/Parser/test-basic-load.js

@@ -0,0 +1,59 @@
+test("test harness test", () => {
+    expect(parseWebAssemblyModule).not.toBeUndefined();
+    expect(readBinaryWasmFile).not.toBeUndefined();
+    expect(compareTypedArrays).not.toBeUndefined();
+});
+
+test("parsing can pass", () => {
+    // prettier-ignore
+    let binary = new Uint8Array([
+        0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x86, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60,
+        0x01, 0x7f, 0x01, 0x7f, 0x02, 0xba, 0x80, 0x80, 0x80, 0x00, 0x02, 0x03, 0x65, 0x6e, 0x76, 0x0f,
+        0x5f, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02,
+        0x00, 0x00, 0x03, 0x65, 0x6e, 0x76, 0x19, 0x5f, 0x5f, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63,
+        0x74, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65,
+        0x01, 0x70, 0x00, 0x00, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x0a, 0xcd, 0x80, 0x80,
+        0x80, 0x00, 0x01, 0x4b, 0x01, 0x03, 0x7f, 0x41, 0x00, 0x21, 0x01, 0x02, 0x40, 0x02, 0x40, 0x20,
+        0x00, 0x41, 0x02, 0x4e, 0x0d, 0x00, 0x20, 0x00, 0x21, 0x02, 0x0c, 0x01, 0x0b, 0x41, 0x00, 0x21,
+        0x01, 0x03, 0x40, 0x20, 0x00, 0x41, 0x7f, 0x6a, 0x10, 0x80, 0x80, 0x80, 0x80, 0x00, 0x20, 0x01,
+        0x6a, 0x21, 0x01, 0x20, 0x00, 0x41, 0x03, 0x4a, 0x21, 0x03, 0x20, 0x00, 0x41, 0x7e, 0x6a, 0x22,
+        0x02, 0x21, 0x00, 0x20, 0x03, 0x0d, 0x00, 0x0b, 0x0b, 0x20, 0x02, 0x20, 0x01, 0x6a, 0x0b, 0x00,
+        0x97, 0x80, 0x80, 0x80, 0x00, 0x07, 0x6c, 0x69, 0x6e, 0x6b, 0x69, 0x6e, 0x67, 0x02, 0x08, 0x88,
+        0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x66, 0x69, 0x62, 0x00, 0x90, 0x80, 0x80,
+        0x80, 0x00, 0x0a, 0x72, 0x65, 0x6c, 0x6f, 0x63, 0x2e, 0x43, 0x4f, 0x44, 0x45, 0x03, 0x01, 0x00,
+        0x27, 0x00, 0x00, 0xa6, 0x80, 0x80, 0x80, 0x00, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65,
+        0x72, 0x73, 0x01, 0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x2d, 0x62, 0x79,
+        0x01, 0x05, 0x63, 0x6c, 0x61, 0x6e, 0x67, 0x06, 0x31, 0x31, 0x2e, 0x31, 0x2e, 0x30,
+    ]);
+    // This just checks that the function actually works
+    parseWebAssemblyModule(binary);
+});
+
+test("parsing can fail", () => {
+    let binary = new Uint8Array([0, 0x32, 0x73, 0x6d]);
+    expect(() => parseWebAssemblyModule(binary)).toThrow(
+        SyntaxError,
+        "Incorrect module magic (did not match \\0asm)"
+    );
+});
+
+test("file reading can pass", () => {
+    // prettier-ignore
+    let referenceContents = new Uint8Array([
+        0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x05, 0x03, 0x01, 0x00,
+        0x00, 0x07, 0x0a, 0x01, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02,
+        0x00, 0x00, 0x17, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x61,
+        0x70, 0x70, 0x69, 0x6e, 0x67, 0x55, 0x52, 0x4c, 0x05, 0x66, 0x61, 0x6c,
+        0x73, 0x65
+    ]);
+
+    let contents = readBinaryWasmFile("Fixtures/Modules/empty-module.wasm");
+    expect(compareTypedArrays(contents, referenceContents)).toBe(true);
+});
+
+test("file reading can fail", () => {
+    expect(() => readBinaryWasmFile("Fixtures/this-file-must-not-exist.wasm")).toThrow(
+        TypeError,
+        "No such file or directory"
+    );
+});