diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml deleted file mode 100644 index d642dd0ff88..00000000000 --- a/.github/workflows/wasm.yml +++ /dev/null @@ -1,111 +0,0 @@ -name: Build Wasm Modules -on: [ push, pull_request ] - -env: - LADYBIRD_SOURCE_DIR: ${{ github.workspace }} - SERENITY_CCACHE_DIR: ${{ github.workspace }}/.ccache - -concurrency: wasm - -jobs: - build: - runs-on: ubuntu-22.04 - if: false - strategy: - fail-fast: false - steps: - - name: Checkout LadybirdBrowser/ladybird - uses: actions/checkout@v4 - - - name: Checkout SerenityOS/libjs-data libjs-wasm - uses: actions/checkout@v4 - with: - repository: SerenityOS/libjs-data - path: libjs-data - ref: libjs-wasm - - - name: "Set up environment" - uses: ./.github/actions/setup - with: - os: 'Linux' - arch: 'Lagom' - - - name: "Install emscripten" - uses: mymindstorm/setup-emsdk@v14 - with: - version: 3.1.25 - - - name: Restore Caches - uses: ./.github/actions/cache-restore - id: 'cache-restore' - with: - os: 'Linux' - arch: 'Lagom' - cache_key_extra: 'WASM' - serenity_ccache_path: ${{ env.SERENITY_CCACHE_DIR }} - download_cache_path: ${{ github.workspace }}/Build/caches - - - name: "Build host lagom tools" - run: | - cmake -GNinja \ - -B ${{ github.workspace }}/Build/lagom-tools \ - -S ${{ github.workspace }}/Meta/Lagom \ - -DLAGOM_TOOLS_ONLY=ON - -DSERENITY_CACHE_DIR=${{ github.workspace }}/Build/caches \ - -DCMAKE_C_COMPILER=gcc-13 \ - -DCMAKE_CXX_COMPILER=g++-13 \ - -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/Build/lagom-tools \ - -Dpackage=LagomTools - ninja -C ${{ github.workspace }}/Build/lagom-tools install - env: - CCACHE_DIR: ${{ env.SERENITY_CCACHE_DIR }} - - - name: "Create wasm build environment" - run: | - emcmake cmake -GNinja \ - -B ${{ github.workspace }}/Build/wasm \ - -S ${{ github.workspace }}/Meta/Lagom \ - -DLagomTools_DIR=${{ github.workspace }}/Build/lagom-tools/share/LagomTools \ - -DSERENITY_CACHE_DIR=${{ github.workspace }}/Build/caches \ - -DBUILD_SHARED_LIBS=OFF - env: - CCACHE_DIR: ${{ env.SERENITY_CCACHE_DIR }} - - - name: "Build libjs.{js,wasm}" - run: | - ninja -C ${{ github.workspace }}/Build/wasm libjs.js - env: - CCACHE_DIR: ${{ env.SERENITY_CCACHE_DIR }} - - - name: Save Caches - uses: ./.github/actions/cache-save - with: - arch: 'Lagom' - serenity_ccache_path: ${{ env.SERENITY_CCACHE_DIR }} - serenity_ccache_primary_key: ${{ steps.cache-restore.outputs.serenity_ccache_primary_key }} - - - name: "Prepare files" - run: | - cp ${{ github.workspace }}/Build/wasm/bin/libjs.js ${{ github.workspace }}/libjs-data/libjs.js - cp ${{ github.workspace }}/Build/wasm/bin/libjs.wasm ${{ github.workspace }}/libjs-data/libjs.wasm - echo 'Module.SERENITYOS_COMMIT = "${{ github.sha }}";' >> ${{ github.workspace }}/libjs-data/libjs.js - tar --exclude='.[^/]*' -czvf libjs-wasm.tar.gz -C ${{ github.workspace }}/libjs-data . - - - name: Deploy to GitHub - uses: JamesIves/github-pages-deploy-action@v4.6.1 - if: github.ref == 'refs/heads/master' - with: - git-config-name: BuggieBot - git-config-email: buggiebot@serenityos.org - branch: libjs-wasm - repository-name: SerenityOS/libjs-data - token: ${{ secrets.BUGGIEBOT_TOKEN }} - folder: ${{ github.workspace }}/libjs-data - - - name: Upload artifact package - if: github.ref == 'refs/heads/master' - uses: actions/upload-artifact@v4 - with: - name: serenity-js-wasm - path: libjs-wasm.tar.gz - retention-days: 7 diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index f6e29950495..c13def0bb17 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -460,15 +460,6 @@ lagom_utility(image SOURCES ../../Userland/Utilities/image.cpp LIBS LibGfx LibMa lagom_utility(ttfdisasm SOURCES ../../Userland/Utilities/ttfdisasm.cpp LIBS LibGfx LibMain) lagom_utility(js SOURCES ../../Userland/Utilities/js.cpp LIBS LibCrypto LibJS LibLine LibLocale LibMain LibTextCodec Threads::Threads) -if (EMSCRIPTEN) - lagom_utility(libjs SOURCES Wasm/js_repl.cpp LIBS LibJS LibLocale LibTimeZone LibUnicode) - target_link_options(libjs PRIVATE - -sEXPORTED_FUNCTIONS=_initialize_repl,_execute - -sEXPORTED_RUNTIME_METHODS=allocateUTF8 - -sERROR_ON_UNDEFINED_SYMBOLS=0 - -sENVIRONMENT=web) -endif() - lagom_utility(lzcat SOURCES ../../Userland/Utilities/lzcat.cpp LIBS LibCompress LibMain) lagom_utility(tar SOURCES ../../Userland/Utilities/tar.cpp LIBS LibArchive LibCompress LibFileSystem LibMain) diff --git a/Meta/Lagom/Wasm/js_repl.cpp b/Meta/Lagom/Wasm/js_repl.cpp deleted file mode 100644 index 77ad4735eb9..00000000000 --- a/Meta/Lagom/Wasm/js_repl.cpp +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (c) 2020-2021, Andreas Kling - * Copyright (c) 2020-2023, Linus Groh - * Copyright (c) 2020-2022, Ali Mohammad Pur - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef AK_OS_EMSCRIPTEN -# error "This program is for Emscripten only" -#endif - -#include - -class ReplConsoleClient; - -RefPtr g_vm; -OwnPtr g_execution_context; -OwnPtr g_console_client; -JS::Handle g_last_value = JS::make_handle(JS::js_undefined()); - -EM_JS(void, user_display, (char const* string, u32 length), { globalDisplayToUser(UTF8ToString(string, length)); }); - -template -void display(CheckedFormatString format_string, Args const&... args) -{ - auto string = ByteString::formatted(format_string.view(), args...); - user_display(string.characters(), string.length()); -} - -template -void displayln(CheckedFormatString format_string, Args const&... args) -{ - display(format_string.view(), args...); - user_display("\n", 1); -} - -void displayln() { user_display("\n", 1); } - -class UserDisplayStream final : public Stream { - virtual ErrorOr read_some(Bytes) override { return Error::from_string_view("Not readable"sv); } - virtual ErrorOr write_some(ReadonlyBytes bytes) override - { - user_display(bit_cast(bytes.data()), bytes.size()); - return bytes.size(); - } - virtual bool is_eof() const override { return true; } - virtual bool is_open() const override { return true; } - virtual void close() override { } -}; - -ErrorOr print(JS::Value value) -{ - UserDisplayStream stream; - JS::PrintContext print_context { - .vm = *g_vm, - .stream = stream, - .strip_ansi = true, - }; - return JS::print(value, print_context); -} - -class ReplObject final : public JS::GlobalObject { - JS_OBJECT(ReplObject, JS::GlobalObject); - -public: - ReplObject(JS::Realm& realm) - : GlobalObject(realm) - { - } - virtual void initialize(JS::Realm&) override; - virtual ~ReplObject() override = default; - -private: - JS_DECLARE_NATIVE_FUNCTION(last_value_getter); - JS_DECLARE_NATIVE_FUNCTION(print); -}; - -static bool s_dump_ast = false; -static bool s_as_module = false; -static bool s_print_last_result = false; - -static ErrorOr parse_and_run(JS::Realm& realm, StringView source, StringView source_name) -{ - auto& interpreter = g_vm->bytecode_interpreter(); - - enum class ReturnEarly { - No, - Yes, - }; - - JS::ThrowCompletionOr result { JS::js_undefined() }; - - auto run_script_or_module = [&](auto& script_or_module) -> ErrorOr { - if (s_dump_ast) - script_or_module->parse_node().dump(0); - - result = interpreter.run(*script_or_module); - - return ReturnEarly::No; - }; - - if (!s_as_module) { - auto script_or_error = JS::Script::parse(source, realm, source_name); - if (script_or_error.is_error()) { - auto error = script_or_error.error()[0]; - auto hint = error.source_location_hint(source); - if (!hint.is_empty()) - displayln("{}", hint); - - auto error_string = error.to_string(); - displayln("{}", error_string); - result = g_vm->throw_completion(move(error_string)); - } else { - auto return_early = TRY(run_script_or_module(script_or_error.value())); - if (return_early == ReturnEarly::Yes) - return true; - } - } else { - auto module_or_error = JS::SourceTextModule::parse(source, realm, source_name); - if (module_or_error.is_error()) { - auto error = module_or_error.error()[0]; - auto hint = error.source_location_hint(source); - if (!hint.is_empty()) - displayln("{}", hint); - - auto error_string = error.to_string(); - displayln("{}", error_string); - result = g_vm->throw_completion(move(error_string)); - } else { - auto return_early = TRY(run_script_or_module(module_or_error.value())); - if (return_early == ReturnEarly::Yes) - return true; - } - } - - auto handle_exception = [&](JS::Value thrown_value) { - display("Uncaught exception: "); - (void)print(thrown_value); - - if (!thrown_value.is_object() || !is(thrown_value.as_object())) - return; - displayln("{}", static_cast(thrown_value.as_object()).stack_string(JS::CompactTraceback::Yes)); - }; - - if (!result.is_error()) - g_last_value = JS::make_handle(result.value()); - - if (result.is_error()) { - VERIFY(result.throw_completion().value().has_value()); - handle_exception(*result.release_error().value()); - return false; - } - - if (s_print_last_result) { - (void)print(result.value()); - display("\n"); - } - - return true; -} - -void ReplObject::initialize(JS::Realm& realm) -{ - Base::initialize(realm); - - define_direct_property("global", this, JS::Attribute::Enumerable); - u8 attr = JS::Attribute::Configurable | JS::Attribute::Writable | JS::Attribute::Enumerable; - define_native_function(realm, "print", print, 1, attr); - - define_native_accessor( - realm, - "_", - [](JS::VM&) { - return g_last_value.value(); - }, - [](JS::VM& vm) -> JS::ThrowCompletionOr { - auto& global_object = vm.get_global_object(); - VERIFY(is(global_object)); - displayln("Disable writing last value to '_'"); - - // We must delete first otherwise this setter gets called recursively. - TRY(global_object.internal_delete(JS::PropertyKey { "_" })); - - auto value = vm.argument(0); - TRY(global_object.internal_set(JS::PropertyKey { "_" }, value, &global_object)); - return value; - }, - attr); -} - -JS_DEFINE_NATIVE_FUNCTION(ReplObject::print) -{ - auto result = ::print(vm.argument(0)); - if (result.is_error()) - return g_vm->throw_completion(TRY_OR_THROW_OOM(*g_vm, String::formatted("Failed to print value: {}", result.error()))); - - displayln(); - - return JS::js_undefined(); -} - -class ReplConsoleClient final : public JS::ConsoleClient { -public: - ReplConsoleClient(JS::Console& console) - : ConsoleClient(console) - { - } - - virtual void clear() override - { - display("FIXME: clear"); - m_group_stack_depth = 0; - } - - virtual void end_group() override - { - if (m_group_stack_depth > 0) - m_group_stack_depth--; - } - - // 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer - virtual JS::ThrowCompletionOr printer(JS::Console::LogLevel log_level, PrinterArguments arguments) override - { - ByteString indent = ByteString::repeated(" "sv, m_group_stack_depth); - - if (log_level == JS::Console::LogLevel::Trace) { - auto trace = arguments.get(); - StringBuilder builder; - if (!trace.label.is_empty()) - builder.appendff("{}{}\n", indent, trace.label); - - for (auto& function_name : trace.stack) - builder.appendff("{}-> {}\n", indent, function_name); - - displayln("{}", builder.string_view()); - return JS::js_undefined(); - } - - if (log_level == JS::Console::LogLevel::Group || log_level == JS::Console::LogLevel::GroupCollapsed) { - auto group = arguments.get(); - displayln("{}{}", indent, group.label); - m_group_stack_depth++; - return JS::js_undefined(); - } - - auto output = ByteString::join(' ', arguments.get>()); - switch (log_level) { - case JS::Console::LogLevel::Debug: - displayln("{}{}", indent, output); - break; - case JS::Console::LogLevel::Error: - case JS::Console::LogLevel::Assert: - displayln("{}{}", indent, output); - break; - case JS::Console::LogLevel::Info: - displayln("{}(i) {}", indent, output); - break; - case JS::Console::LogLevel::Log: - displayln("{}{}", indent, output); - break; - case JS::Console::LogLevel::Warn: - case JS::Console::LogLevel::CountReset: - displayln("{}{}", indent, output); - break; - default: - displayln("{}{}", indent, output); - break; - } - return JS::js_undefined(); - } - -private: - int m_group_stack_depth { 0 }; -}; - -extern "C" int initialize_repl(char const* time_zone) -{ - if (time_zone) - setenv("TZ", time_zone, 1); - - g_vm = MUST(JS::VM::create()); - g_vm->set_dynamic_imports_allowed(true); - - // NOTE: These will print out both warnings when using something like Promise.reject().catch(...) - - // which is, as far as I can tell, correct - a promise is created, rejected without handler, and a - // handler then attached to it. The Node.js REPL doesn't warn in this case, so it's something we - // might want to revisit at a later point and disable warnings for promises created this way. - g_vm->on_promise_unhandled_rejection = [](auto& promise) { - display("WARNING: A promise was rejected without any handlers"); - display(" (result: "); - (void)print(promise.result()); - displayln(")"); - }; - g_vm->on_promise_rejection_handled = [](auto& promise) { - display("WARNING: A handler was added to an already rejected promise"); - display(" (result: "); - (void)print(promise.result()); - displayln(")"); - }; - - s_print_last_result = true; - g_execution_context = JS::create_simple_execution_context(*g_vm); - auto& realm = *g_execution_context->realm; - auto console_object = realm.intrinsics().console_object(); - g_console_client = make(console_object->console()); - console_object->console().set_client(*g_console_client); - - return 0; -} - -extern "C" bool execute(char const* source) -{ - if (auto result = parse_and_run(*g_execution_context->realm, { source, strlen(source) }, "REPL"sv); result.is_error()) { - displayln("{}", result.error()); - return false; - } else { - return result.value(); - } -}