diff --git a/.github/workflows/benchx_cli_publish.yml b/.github/workflows/benchx_cli_publish.yml new file mode 100644 index 0000000000..5be8b86ea6 --- /dev/null +++ b/.github/workflows/benchx_cli_publish.yml @@ -0,0 +1,167 @@ +name: Benchx CLI Publish + +on: + pull_request: + branches: + - develop + push: + tags: + - '*' + workflow_call: + inputs: + skip_release: + description: 'Skip the release job' + required: false + default: false + type: boolean + commit_ref: + description: 'Commit SHA or branch name to build' + required: false + type: string + workflow_dispatch: + +permissions: + contents: write + +jobs: + build: + name: Build ${{ matrix.os_name }} ${{ matrix.arch_name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + os_name: Linux + arch_name: x86_64 + - os: macos-latest + os_name: Darwin + arch_name: arm64 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.commit_ref || github.ref }} + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '24' + + - name: Setup sccache + uses: mozilla-actions/sccache-action@v0.0.8 + + - name: Setup uv + uses: astral-sh/setup-uv@v5 + + - name: Configure sccache + run: | + # Configure sccache directory before cache step + echo "SCCACHE_DIR=$GITHUB_WORKSPACE/.sccache" >> $GITHUB_ENV + mkdir -p .sccache + + - name: Cache sccache + uses: actions/cache@v4 + with: + path: .sccache + key: ${{ matrix.os }}-${{ matrix.arch_name }}-sccache-${{ github.sha }} + restore-keys: | + ${{ matrix.os }}-${{ matrix.arch_name }}-sccache- + + - name: Install Dependencies + run: | + # Show sccache stats before build + sccache -z + + uv venv + uv pip install pip + + # Ensure tools are executable + chmod +x tools/hab || true + + # Source envsetup to set paths for hab and other tools + set +u + source tools/envsetup.sh + + echo "Syncing dependencies..." + tools/hab sync . + + - name: Build + run: | + set +u + source tools/envsetup.sh + + echo "Generating build files for ${{ matrix.os_name }} ${{ matrix.arch_name }}..." + + # Base args + ARGS='enable_unittests=true enable_trace="perfetto" jsengine_type="quickjs" enable_frozen_mode=true use_sccache=true' + + + # Platform specific args + if [[ "${{ matrix.os_name }}" == "Darwin" ]]; then + ARGS="$ARGS use_flutter_cxx=false" + fi + + # Note: If cross-compilation is needed for arm64 on Linux, target_cpu arg would be needed + # But here we are running on native runners (macos-latest is arm64, others x64) + + echo "GN Args: $ARGS" + gn gen --args="$ARGS" out/Default + + echo "Building benchx_cli..." + ninja -C out/Default cli/benchx:benchx_cli + + # Show sccache stats after build + sccache -s + + - name: Prepare and Package Artifacts + run: | + mkdir -p dist/bin + BINARY_PATH="out/Default/benchx_cli" + + if [ ! -f "$BINARY_PATH" ]; then + echo "Error: Binary not found at $BINARY_PATH" + ls -R out/Default + exit 1 + fi + + echo "Found binary at $BINARY_PATH" + cp "$BINARY_PATH" dist/bin/benchx_cli + + # Create archive with specific naming format: benchx_cli_OS_Arch.tar.gz + # e.g., benchx_cli_Darwin_arm64.tar.gz + ARCHIVE_NAME="benchx_cli_${{ matrix.os_name }}_${{ matrix.arch_name }}.tar.gz" + + cd dist/bin + tar -czvf "../../$ARCHIVE_NAME" benchx_cli + cd ../.. + + echo "Created archive: $ARCHIVE_NAME" + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: build-artifact-${{ matrix.os_name }}-${{ matrix.arch_name }} + path: benchx_cli_*.tar.gz + + release: + name: Create Release + needs: build + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && !inputs.skip_release + runs-on: ubuntu-latest + steps: + - name: Download Artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true # Merge all artifacts into one directory + + - name: List Artifacts + run: | + ls -R artifacts + + - name: Publish Release + uses: softprops/action-gh-release@v2 + with: + files: artifacts/* diff --git a/.github/workflows/rebase_upstream.yml b/.github/workflows/rebase_upstream.yml new file mode 100644 index 0000000000..6eae0d8e56 --- /dev/null +++ b/.github/workflows/rebase_upstream.yml @@ -0,0 +1,86 @@ +name: Periodic Rebase + +on: + schedule: + - cron: '0 0 * * *' # Daily at 00:00 UTC + workflow_dispatch: + +permissions: + contents: write + +jobs: + prepare-rebase: + runs-on: ubuntu-latest + outputs: + temp_branch: ${{ steps.create_branch.outputs.branch_name }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: develop + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure Git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Rebase Upstream and Push to Temp Branch + id: create_branch + run: | + UPSTREAM_URL="https://github.com/lynx-family/lynx.git" + + echo "Setting upstream to $UPSTREAM_URL" + git remote add upstream "$UPSTREAM_URL" + git fetch upstream + + echo "Rebasing develop..." + git checkout develop + + if git rebase upstream/develop; then + echo "Rebase successful." + # Create a unique temporary branch name + TEMP_BRANCH="rebase-check-${{ github.run_id }}" + git checkout -b "$TEMP_BRANCH" + git push origin "$TEMP_BRANCH" --force + + echo "branch_name=$TEMP_BRANCH" >> "$GITHUB_OUTPUT" + else + echo "Rebase failed due to conflicts. Aborting." + git rebase --abort + exit 1 + fi + + check-build: + needs: prepare-rebase + uses: ./.github/workflows/benchx_cli_publish.yml + with: + skip_release: true + commit_ref: ${{ needs.prepare-rebase.outputs.temp_branch }} + + push-to-develop: + needs: [prepare-rebase, check-build] + runs-on: ubuntu-latest + if: success() + steps: + - name: Checkout Temp Branch + uses: actions/checkout@v4 + with: + ref: ${{ needs.prepare-rebase.outputs.temp_branch }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Push to Develop + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + echo "Pushing verified rebase to develop..." + git checkout develop + # Reset develop to the verified temp branch commit + git reset --hard origin/${{ needs.prepare-rebase.outputs.temp_branch }} + git push origin develop --force-with-lease + + # Cleanup + git push origin --delete ${{ needs.prepare-rebase.outputs.temp_branch }} diff --git a/.gitignore b/.gitignore index 5c363afe6f..8d22bbb2cc 100644 --- a/.gitignore +++ b/.gitignore @@ -144,3 +144,8 @@ js_libraries/lynx-core/src/common/feature.ts /tools/api/android /tools/api/ios /tools/api/docs/gen + +compile_commands.json +.cache/ + +.codspeed_*/ diff --git a/.habitat b/.habitat index 901e6721eb..36070436be 100644 --- a/.habitat +++ b/.habitat @@ -1,4 +1,4 @@ -habitat_version = "0.3.146-alpha.2" +habitat_version = "0.3.146-alpha.4" solutions = [ { 'name': '.', diff --git a/BUILD.gn b/BUILD.gn index 348bc39f16..fa54aed2cd 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -71,3 +71,8 @@ group("third_party_trace_group") { deps += [ "base/trace/native:trace_tests" ] } } + +group("cli") { + deps = ["cli/benchx:benchx_cli"] + testonly = true +} diff --git a/cli/benchx/BUILD.gn b/cli/benchx/BUILD.gn new file mode 100644 index 0000000000..cb4711eec5 --- /dev/null +++ b/cli/benchx/BUILD.gn @@ -0,0 +1,34 @@ +# Copyright 2020 The Lynx Authors. All rights reserved. +# Licensed under the Apache License Version 2.0 that can be found in the +# LICENSE file in the root directory of this source tree. + +import("//core/Lynx.gni") +import("//testing/test.gni") + +executable("benchx_cli") { + testonly = true + sources = [ + "benchx_cli.cc", + "painting_context_platform_impl.cc", + "performance_apis.cc", + "resource_loader.cc", + "tasm_platform_invoker_dummy.cc", + ] + deps = [ + "//base/src:base_group", + "//base/trace/native:trace", + "//core/event", + "//core/renderer:tasm", + "//core/renderer/dom:dom", + "//core/renderer/dom:renderer_dom", + "//core/renderer/dom/selector:element_selector", + "//core/resource", + "//core/services/event_report", + "//core/shared_data", + "//core/shell", + "//js_libraries/lynx-core", + "//third_party/argparse", + "//third_party/codspeed-instrument-hooks", + "//third_party/rapidjson", + ] +} diff --git a/cli/benchx/benchx_cli.cc b/cli/benchx/benchx_cli.cc new file mode 100644 index 0000000000..4053ce6ca7 --- /dev/null +++ b/cli/benchx/benchx_cli.cc @@ -0,0 +1,224 @@ +#include +#include +#include +#include +#include + +#include "argparse/argparse.hpp" +#include "base/include/log/logging.h" +#include "base/threading/task_runner_manufactor.h" +#include "base/trace/native/trace_controller.h" +#include "cli/benchx/painting_context_platform_impl.h" +#include "cli/benchx/resource_loader.h" +#include "cli/benchx/tasm_platform_invoker_dummy.h" +#include "core/shell/lynx_shell_builder.h" +#include "renderer/ui_wrapper/layout/empty/layout_context_empty_implementation.h" +#include "renderer/utils/lynx_env.h" +#include "shell/lynx_shell.h" +#include "shell/runtime/common/module_delegate_impl.h" +#include "shell/runtime/bts/lynx_bts_runtime_proxy_impl.h" + +static std::ofstream g_logfile; +static std::mutex g_logfile_mutex; + +int main(int argc, char** argv) { + argparse::ArgumentParser program("benchx_cli"); + program.add_argument("--logfile") + .action([](const std::string& value) { + g_logfile = std::ofstream(value, std::ios::app); + }) + .help("The path to file Lynx Native will log to"); + + lynx::base::logging::EnableLogOutputByPlatform(); + lynx::base::logging::InitLynxLogging( + nullptr, + [](lynx::base::logging::LogMessage* msg, const char* tag) { + if (msg->source() == lynx::base::logging::LOG_SOURCE_NATIVE) { + if (g_logfile) { + std::lock_guard lock(g_logfile_mutex); + g_logfile << msg->stream() << std::endl; + } + } else { + std::cout << msg->stream().str().substr(msg->messageStart()); + } + }, + false); + + std::shared_ptr g_session_id; + program.add_argument("-o", "--out-trace-file") + .action([&g_session_id](const std::string& out_trace_file) { + auto instance = lynx::trace::TraceController::Instance(); + auto trace_config = std::make_shared(); + trace_config->buffer_size = 65536; + trace_config->file_path = out_trace_file; + trace_config->included_categories = {"*"}; + trace_config->excluded_categories = {"*"}; + auto session_id = instance->StartTracing(trace_config); + g_session_id = + std::shared_ptr(new int(session_id), [=](int* session_id) { + instance->StopTracing(*session_id); + delete session_id; + // give perfetto some time to flush buffers + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + }); + }) + .help("The path to the output trace file"); + + argparse::ArgumentParser run_command("run"); + run_command.add_argument("bundle") + .help("The path to '.lynx.bundle' file to be benchmarked") + .required(); + run_command.add_argument("--repeat") + .help("The number of times to repeat the benchmark") + .default_value(1) + .scan<'i', int32_t>(); + run_command.add_argument("--wait-for-id") + .help("Wait for ui with specified id"); + + program.add_subparser(run_command); + try { + program.parse_args(argc, argv); + } catch (const std::exception& err) { + std::cerr << err.what() << std::endl; + std::cerr << program; + return 1; + } + + if (program.is_subcommand_used(run_command)) { + auto filename = run_command.get("bundle"); + std::ifstream file(filename, std::ios::binary); + if (!file) { + std::cerr << "Error: Could not open file " << filename << std::endl; + return 1; + } + + auto source = std::vector((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + + auto repeat = run_command.get("--repeat"); + + auto wait_for_id = run_command.present("--wait-for-id"); + + auto run = [&]() { + auto config = lynx::tasm::LynxEnvConfig(1080, 2060, 1.0f, 1.0f); + // Fake enviornment which is never used. + lynx::tasm::Config::InitializeVersion("Benchx"); + lynx::tasm::LynxEnv::GetInstance().SetBoolLocalEnv("enable_quickjs_cache", + false); + auto invoker = std::make_unique(); + auto* invoker_ptr = invoker.get(); + auto painting_context = std::make_unique(); + auto* painting_context_ptr = painting_context.get(); + + auto shell = + lynx::shell::LynxShellBuilder() + // .SetNativeFacade(std::move(native_facade)) + .SetUseInvokeUIMethodFunction(true) + .SetPaintingContextPlatformImpl(std::move(painting_context)) + .SetLynxEnvConfig(config) + .SetEnableElementManagerVsyncMonitor(true) + .SetEnableNewAnimator( + /* settings_.enable_new_animator */ true) + .SetEnableNativeList(/* settings_.enable_native_list */ true) + // .SetLazyBundleLoader(loader) + // .SetEnablePreUpdateData(enable_pre_update_data_) + .SetLayoutContextPlatformImpl( + std::make_unique()) + .SetStrategy(lynx::base::ThreadStrategyForRendering::ALL_ON_UI) + // .SetPropBundleCreator(ui_delegate_->CreatePropBundleCreator()) + // .SetEngineActor([](auto& actor) {}) + .SetShellOption(lynx::shell::ShellOption{ + .enable_js_ = true, + }) + .SetTasmPlatformInvoker(std::move(invoker)) + // .SetPerformanceControllerPlatform(std::move(perf_controller_ptr_)) + // .SetPerfControllerActor() + .build(); + invoker_ptr->SetUITaskRunner(shell->GetRunners()->GetUITaskRunner()); + invoker_ptr->SetTASMTaskRunner(shell->GetRunners()->GetTASMTaskRunner()); + invoker_ptr->SetTASM(shell->GetTasm()); + painting_context_ptr->SetShellPtr(shell); + + shell->UpdateViewport(config.ScreenWidth(), 1, config.ScreenHeight(), 1); + + std::shared_ptr runtime_proxy; + auto module_manager = + std::make_shared(); + module_manager->SetModuleFactory(nullptr); + auto on_runtime_actor_created = [&](auto& actor) { + auto module_delegate = + std::make_shared( + shell->GetRuntimeActor(), shell->GetFacadeActor()); + module_manager->initBindingPtr(module_manager, module_delegate); + runtime_proxy = + std::make_shared(actor); + module_manager->runtime_proxy = runtime_proxy; + }; + + auto runtime_flags = + lynx::shell::CalcRuntimeFlags(false, true, false, false); + shell->InitRuntime("1", std::make_shared(), + module_manager, std::move(on_runtime_actor_created), + std::vector{}, runtime_flags, + std::string{}); + + // if (settings.global_props) { + // shell->UpdateGlobalProps(*settings.global_props); + // } + + auto pipeline_options = std::make_shared(); + shell->LoadTemplate(filename, source, pipeline_options, nullptr); + + return shell; + }; + + lynx::base::UIThread::Init(); + auto runner = lynx::base::UIThread::GetRunner(); + + std::thread([&]() { + for (int i = 0; i < repeat; ++i) { + lynx::shell::LynxShell* shell; + runner->PostSyncTask([&]() { shell = run(); }); + + if (wait_for_id.has_value() && !wait_for_id->empty()) { + // wait for id selector + while (true) { + bool found = false; + runner->PostSyncTask([&]() { + auto root = shell->GetTasm() + ->page_proxy() + ->element_manager() + ->catalyzer() + ->get_root(); + + std::function + search_id_selector = [&](lynx::tasm::Element* node) { + if (node->data_model()->idSelector() == *wait_for_id) { + found = true; + } + for (auto child : node->GetChildren()) { + search_id_selector(child); + } + }; + + search_id_selector(root); + }); + if (found) { + break; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + } + + runner->PostSyncTask([&]() { delete shell; }); + } + + runner->PostSyncTask([&]() { runner->GetLoop()->Terminate(); }); + }).detach(); + + runner->GetLoop()->Run(); + } + + return 0; +} diff --git a/cli/benchx/painting_context_platform_impl.cc b/cli/benchx/painting_context_platform_impl.cc new file mode 100644 index 0000000000..fa87c9f1ff --- /dev/null +++ b/cli/benchx/painting_context_platform_impl.cc @@ -0,0 +1,72 @@ +#include + +#include + +#include "renderer/ui_wrapper/common/testing/prop_bundle_mock.h" + +void PaintingContextPlatformImpl::InvokeUIMethod( + int32_t view_id, const std::string& method, + fml::RefPtr prop_bundle, int32_t callback_id) { + enum LynxUIMethodErrorCode { + kUIMethodSuccess = 0, + kUIMethodUnknown, + kUIMethodNodeNotFound, + kUIMethodMethodNotFound, + kUIMethodParamInvalid, + kUIMethodSelectorNotSupported, + kUIMethodNoUiForNode, + kUIMethodInvalidStateError, + kUIMethodOperationError + }; + + auto& args = (static_cast(prop_bundle.get())) + ->GetPropsMap(); + + if (method == "scrollToPosition") { + if (shell_->GetListNode(view_id) != nullptr) { + int32_t position = 0; + float offset = 0; + int32_t align_to = 0; + bool smooth = false; + for (const auto& [key, value] : args) { + if (key == "position") { + position = static_cast(value.Number()); + } else if (key == "offset") { + offset = value.Double(); + } else if (key == "alignTo") { + auto s = value.StringView(); + if (s == "top") { + align_to = 0; + } else if (s == "middle") { + align_to = 1; + } else if (s == "bottom") { + align_to = 2; + } + } else if (key == "smooth") { + smooth = value.Bool(); + } + } + shell_->ScrollToPosition(view_id, position, offset, align_to, smooth); + shell_->GetTasm()->GetDelegate().CallJSApiCallbackWithValue( + callback_id, []() { + auto ret = lynx::lepus::Dictionary::Create(); + ret->SetValue("code", LynxUIMethodErrorCode::kUIMethodSuccess); + ret->SetValue("data", lynx::lepus::Dictionary::Create()); + return lynx::lepus::Value(ret); + }()); + return; + } + } + + shell_->GetTasm()->GetDelegate().CallJSApiCallbackWithValue( + callback_id, []() { + auto ret = lynx::lepus::Dictionary::Create(); + ret->SetValue("code", LynxUIMethodErrorCode::kUIMethodMethodNotFound); + ret->SetValue("data", lynx::lepus::Dictionary::Create()); + return lynx::lepus::Value(ret); + }()); +} + +void PaintingContextPlatformImpl::SetShellPtr(lynx::shell::LynxShell* shell) { + shell_ = shell; +} diff --git a/cli/benchx/painting_context_platform_impl.h b/cli/benchx/painting_context_platform_impl.h new file mode 100644 index 0000000000..3eca698329 --- /dev/null +++ b/cli/benchx/painting_context_platform_impl.h @@ -0,0 +1,15 @@ +#include "renderer/ui_wrapper/painting/empty/painting_context_implementation.h" +#include "shell/lynx_shell.h" + +class PaintingContextPlatformImpl + : public lynx::tasm::PaintingContextPlatformImpl { + public: + virtual void InvokeUIMethod(int32_t view_id, const std::string& method, + fml::RefPtr prop_bundle, + int32_t callback_id) override; + + void SetShellPtr(lynx::shell::LynxShell* shell); + + private: + lynx::shell::LynxShell* shell_; +}; diff --git a/cli/benchx/performance_apis.cc b/cli/benchx/performance_apis.cc new file mode 100644 index 0000000000..63f9fc7d1b --- /dev/null +++ b/cli/benchx/performance_apis.cc @@ -0,0 +1,114 @@ +#include "cli/benchx/performance_apis.h" + +#include + +#include "quickjs/include/trace-gc.h" + +extern "C" { +#include "callgrind.h" +#include "quickjs/include/quickjs.h" +#include "valgrind.h" +} + +// clang-format off +inline __attribute__((always_inline)) uint8_t running_on_valgrind() { return RUNNING_ON_VALGRIND > 0; } + +inline __attribute__((always_inline)) void callgrind_dump_stats() { CALLGRIND_DUMP_STATS; } + +inline __attribute__((always_inline)) void callgrind_dump_stats_at(uint8_t const* pos_str) { CALLGRIND_DUMP_STATS_AT(pos_str); } + +inline __attribute__((always_inline)) void callgrind_zero_stats() { CALLGRIND_ZERO_STATS; } + +inline __attribute__((always_inline)) void callgrind_start_instrumentation() { CALLGRIND_START_INSTRUMENTATION; } + +inline __attribute__((always_inline)) void callgrind_stop_instrumentation() { CALLGRIND_STOP_INSTRUMENTATION; } +// clang-format on + +static bool is_running_on_valgrind = []() { + if (running_on_valgrind()) { + callgrind_dump_stats_at((const uint8_t*)"Metadata: codspeed-cpp 1.2.0"); + return true; + } + return false; +}(); + +LEPUSValue perf_start_benchmark(LEPUSContext* ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst* argv) { + if (is_running_on_valgrind) { + callgrind_zero_stats(); + callgrind_start_instrumentation(); + } + return LEPUS_UNDEFINED; +} + +LEPUSValue perf_stop_benchmark(LEPUSContext* ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst* argv) { + if (is_running_on_valgrind) { + callgrind_stop_instrumentation(); + } + return LEPUS_UNDEFINED; +} + +LEPUSValue perf_set_executed_benchmark(LEPUSContext* ctx, + LEPUSValueConst this_val, int argc, + LEPUSValueConst* argv) { + const char* str; + size_t len; + + HandleScope scope(ctx); + str = LEPUS_ToCStringLen2(ctx, &len, argv[0], 0); + if (is_running_on_valgrind) { + callgrind_dump_stats_at((const uint8_t*)str); + } + if (!LEPUS_IsGCMode(ctx)) { + LEPUS_FreeCString(ctx, str); + } + return LEPUS_UNDEFINED; +} + +LEPUSValue perf_zero_stats(LEPUSContext* ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst* argv) { + if (is_running_on_valgrind) { + callgrind_zero_stats(); + } + return LEPUS_UNDEFINED; +} + +static auto time0 = std::chrono::steady_clock::now(); +LEPUSValue perf_now(LEPUSContext* ctx, LEPUSValueConst this_val, int argc, + LEPUSValueConst* argv) { + const auto time = std::chrono::steady_clock::now() - time0; + return LEPUS_NewFloat64( + ctx, std::chrono::duration_cast(time).count() / + 1000000.0); +} + +static const LEPUSCFunctionListEntry codspeed_funcs[] = { + LEPUS_CFUNC_DEF("startBenchmark", 0, perf_start_benchmark), + LEPUS_CFUNC_DEF("stopBenchmark", 0, perf_stop_benchmark), + LEPUS_CFUNC_DEF("setExecutedBenchmark", 1, perf_set_executed_benchmark), + LEPUS_CFUNC_DEF("zeroStats", 1, perf_zero_stats), +}; + +static const LEPUSCFunctionListEntry js_perf_funcs[] = { + LEPUS_CFUNC_DEF("now", 0, perf_now), +}; + +static const LEPUSCFunctionListEntry obj[] = { + LEPUS_OBJECT_DEF("Codspeed", codspeed_funcs, + sizeof(codspeed_funcs) / sizeof(codspeed_funcs[0]), + LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE), + LEPUS_OBJECT_DEF("performance", js_perf_funcs, + sizeof(js_perf_funcs) / sizeof(js_perf_funcs[0]), + LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE), +}; + +extern "C" { +void LEPUS_AddExtraIntrinsicObjects(LEPUSContext* ctx) { + LEPUSValue global = LEPUS_GetGlobalObject(ctx); + LEPUS_SetPropertyFunctionList(ctx, global, obj, sizeof(obj) / sizeof(obj[0])); + if (!LEPUS_IsGCMode(ctx)) { + LEPUS_FreeValue(ctx, global); + } +} +} diff --git a/cli/benchx/performance_apis.h b/cli/benchx/performance_apis.h new file mode 100644 index 0000000000..4fbdd4d3cb --- /dev/null +++ b/cli/benchx/performance_apis.h @@ -0,0 +1,4 @@ +#ifndef CLI_BENCHX_PERFORMANCE_APIS +#define CLI_BENCHX_PERFORMANCE_APIS + +#endif // CLI_BENCHX_PERFORMANCE_APIS diff --git a/cli/benchx/resource_loader.cc b/cli/benchx/resource_loader.cc new file mode 100644 index 0000000000..d23a5f3f3b --- /dev/null +++ b/cli/benchx/resource_loader.cc @@ -0,0 +1,25 @@ +#include "cli/benchx/resource_loader.h" + +#include +#include + +#include "js_libraries/lynx_core_dev.h" + +void ResourceLoaderDummy::LoadResourceInternal( + const lynx::pub::LynxResourceRequest& request, + lynx::base::MoveOnlyClosure + callback) { + if (request.type == lynx::pub::LynxResourceType::kAssets) { + if (request.url == "assets://lynx_core.js" || + request.url == "assets://lynx_core_dev.js") { + lynx::pub::LynxResourceResponse response{ + .data = std::vector( + lynx_core_dev_js, lynx_core_dev_js + lynx_core_dev_js_len)}; + callback(response); + return; + } + } + + lynx::pub::LynxResourceResponse response{}; + callback(response); +} diff --git a/cli/benchx/resource_loader.h b/cli/benchx/resource_loader.h new file mode 100644 index 0000000000..88019771eb --- /dev/null +++ b/cli/benchx/resource_loader.h @@ -0,0 +1,17 @@ +#ifndef CLI_BENCHX_RESOURCE_LOADER +#define CLI_BENCHX_RESOURCE_LOADER + +#include "public/lynx_resource_loader.h" + +class ResourceLoaderDummy : public lynx::pub::LynxResourceLoader { + public: + ResourceLoaderDummy() = default; + ~ResourceLoaderDummy() override = default; + + void LoadResourceInternal( + const lynx::pub::LynxResourceRequest& request, + lynx::base::MoveOnlyClosure + callback) override; +}; + +#endif // CLI_BENCHX_RESOURCE_LOADER diff --git a/cli/benchx/tasm_platform_invoker_dummy.cc b/cli/benchx/tasm_platform_invoker_dummy.cc new file mode 100644 index 0000000000..29f0797ccf --- /dev/null +++ b/cli/benchx/tasm_platform_invoker_dummy.cc @@ -0,0 +1,33 @@ +#include "cli/benchx/tasm_platform_invoker_dummy.h" + +#include + +#include "base/include/fml/task_runner.h" +#include "runtime/lepusng/quick_context.h" + +namespace lynx { +namespace shell { + +void TasmPlatformInvokerDummy::OnPageConfigDecoded( + const std::shared_ptr& config) {} + +void TasmPlatformInvokerDummy::OnRunPipelineFinished() {} + +std::string TasmPlatformInvokerDummy::TranslateResourceForTheme( + const std::string& res_id, const std::string& theme_key) { + return std::string(); +} + +lepus::Value TasmPlatformInvokerDummy::TriggerLepusMethod( + const std::string& js_method_name, const lepus::Value& args) { + return lepus::Value(); +} + +void TasmPlatformInvokerDummy::TriggerLepusMethodAsync( + const std::string& method_name, const lepus::Value& args) {} + +void TasmPlatformInvokerDummy::GetI18nResource( + const std::string& channel, const std::string& fallback_url) {} + +} // namespace shell +} // namespace lynx diff --git a/cli/benchx/tasm_platform_invoker_dummy.h b/cli/benchx/tasm_platform_invoker_dummy.h new file mode 100644 index 0000000000..e625dfe698 --- /dev/null +++ b/cli/benchx/tasm_platform_invoker_dummy.h @@ -0,0 +1,52 @@ +#ifndef CLI_BENCHX_TASM_PLATFORM_INVOKER_DUMMY_H +#define CLI_BENCHX_TASM_PLATFORM_INVOKER_DUMMY_H + +#include "base/include/fml/task_runner.h" +#include "renderer/template_assembler.h" +#include "shell/tasm_platform_invoker.h" + +namespace lynx { +namespace shell { + +class TasmPlatformInvokerDummy : public TasmPlatformInvoker { + public: + explicit TasmPlatformInvokerDummy() = default; + ~TasmPlatformInvokerDummy() override = default; + + void OnPageConfigDecoded( + const std::shared_ptr& config) override; + + void OnRunPipelineFinished() override; + + std::string TranslateResourceForTheme(const std::string& res_id, + const std::string& theme_key) override; + + lepus::Value TriggerLepusMethod(const std::string& method_name, + const lepus::Value& args) override; + + void TriggerLepusMethodAsync(const std::string& method_name, + const lepus::Value& args) override; + + void GetI18nResource(const std::string& channel, + const std::string& fallback_url) override; + + void SetUITaskRunner(const fml::RefPtr& task_runner) { + ui_task_runner_ = task_runner; + }; + + void SetTASMTaskRunner(const fml::RefPtr& task_runner) { + tasm_task_runner_ = task_runner; + }; + + void SetTASM(tasm::TemplateAssembler* tasm) { tasm_ = tasm; } + + private: + fml::RefPtr ui_task_runner_; + fml::RefPtr tasm_task_runner_; + tasm::TemplateAssembler* tasm_; +}; + +} // namespace shell +} // namespace lynx + +#endif // CLI_BENCHX_TASM_PLATFORM_INVOKER_DUMMY_H diff --git a/core/BUILD.gn b/core/BUILD.gn index 26175dd20d..311431cbb5 100644 --- a/core/BUILD.gn +++ b/core/BUILD.gn @@ -162,6 +162,10 @@ config("lynx_public_config") { ] } } + + if (enable_frozen_mode) { + defines += [ "LYNX_ENABLE_FROZEN_MODE=true" ] + } } config("config") { diff --git a/core/base/threading/task_runner_manufactor.cc b/core/base/threading/task_runner_manufactor.cc index ff46748f98..3434626969 100644 --- a/core/base/threading/task_runner_manufactor.cc +++ b/core/base/threading/task_runner_manufactor.cc @@ -407,7 +407,11 @@ fml::Thread TaskRunnerManufactor::CreateJSWorkerThread( void TaskRunnerManufactor::PostTaskToConcurrentLoop(base::closure task, ConcurrentTaskType type) { +#if LYNX_ENABLE_FROZEN_MODE + base::UIThread::GetRunner()->PostTask(std::move(task)); +#else GetConcurrentLoop(type).PostTask(std::move(task)); +#endif } bool TaskRunnerManufactor::IsOnConcurrentLoopWorker(ConcurrentTaskType type) { diff --git a/core/base/threading/vsync_monitor_default.cc b/core/base/threading/vsync_monitor_default.cc index d206ba2a7a..f7e275d8ba 100644 --- a/core/base/threading/vsync_monitor_default.cc +++ b/core/base/threading/vsync_monitor_default.cc @@ -9,8 +9,16 @@ namespace lynx { namespace base { +class VSyncMonitorDummy : public VSyncMonitor { + public: + VSyncMonitorDummy() = default; + ~VSyncMonitorDummy() override = default; + + void RequestVSync() override {}; +}; + std::shared_ptr VSyncMonitor::Create(bool is_on_ui_thread) { - return nullptr; + return std::make_shared(); } } // namespace base diff --git a/core/runtime/js/jsi/quickjs/quickjs_context_wrapper.h b/core/runtime/js/jsi/quickjs/quickjs_context_wrapper.h index 9b740468a1..e949aefb33 100644 --- a/core/runtime/js/jsi/quickjs/quickjs_context_wrapper.h +++ b/core/runtime/js/jsi/quickjs/quickjs_context_wrapper.h @@ -29,7 +29,6 @@ class LYNX_EXPORT_FOR_DEVTOOL QuickjsContextWrapper : public JSIContext { static RegisterWasmFuncType& RegisterWasmFunc(); static RegisterWasmFuncType register_wasm_func_; - private: LEPUSContext* ctx_; }; diff --git a/core/runtime/js/jsi/quickjs/quickjs_runtime_wrapper.cc b/core/runtime/js/jsi/quickjs/quickjs_runtime_wrapper.cc index 5aeb9cfb52..28b880a627 100644 --- a/core/runtime/js/jsi/quickjs/quickjs_runtime_wrapper.cc +++ b/core/runtime/js/jsi/quickjs/quickjs_runtime_wrapper.cc @@ -99,19 +99,6 @@ void QuickjsRuntimeInstance::InitQuickjsRuntime(bool is_sync, if (is_sync) { AddToIdContainer(); } -#if LYNX_ENABLE_FROZEN_MODE - // Due to the fact that QuickJS’s GC traverses all objects in a Stop The World - // fashion to try to free the circular objects, it causes extra time - // consumption once QuickJS triggers GC. Currently, the default GC threshold - // of QuickJS is 256 bytes, and even a slight change in the JS Framework may - // cause changes in the GC timing. The impact is that in performance - // degradation tests, some indicators may experience significant fluctuations - // due to changes in the GC timing. To avoid the GC from fluctuating the - // performance, when LYNX_ENABLE_FROZEN_MODE is enabled, set the GC of the JS - // QuickJSRuntime to INT_MAX. In the long run, a reasonable GC threshold needs - // to be set for QuickJS. - LEPUS_SetGCThreshold(rt_, INT_MAX); -#endif LOGI("lynx InitQuickjsRuntime success"); } diff --git a/core/services/event_report/event_tracker_platform_impl.h b/core/services/event_report/event_tracker_platform_impl.h index 97bb30b6bc..6a2941c7a0 100644 --- a/core/services/event_report/event_tracker_platform_impl.h +++ b/core/services/event_report/event_tracker_platform_impl.h @@ -12,6 +12,7 @@ #include #include "base/include/fml/thread.h" +#include "base/threading/task_runner_manufactor.h" #include "core/renderer/utils/lynx_env.h" #include "core/services/event_report/event_tracker.h" @@ -70,10 +71,14 @@ class EventTrackerPlatformImpl { static void ClearCache(int32_t instance_id); // Get Task Runner of report thread. static fml::RefPtr GetReportTaskRunner() { +#if LYNX_ENABLE_FROZEN_MODE + return base::UIThread::GetRunner(); +#else static base::NoDestructor event_report_thread_t_( fml::Thread::ThreadConfig( kLynxReportThread, fml::Thread::ThreadPriority::NORMAL, nullptr)); return event_report_thread_t_->GetTaskRunner(); +#endif } }; diff --git a/dependencies/DEPS b/dependencies/DEPS index caa9edfbf3..234644ff3d 100644 --- a/dependencies/DEPS +++ b/dependencies/DEPS @@ -114,7 +114,8 @@ deps = { "url": "https://github.com/lynx-family/buildroot.git", "commit": "917b38180c78da016b1023436d5b568ca5402bee", "ignore_in_git": True, - "condition": system in ['linux', 'darwin', 'windows'] + "condition": system in ['linux', 'darwin', 'windows'], + "patches": os.path.join(root_dir, 'patches', 'build', '*.patch'), }, "build/linux/debian_sid_amd64-sysroot": { "type": "http", @@ -202,6 +203,7 @@ deps = { "url": "https://github.com/lynx-family/primjs.git", "commit": "572f55190b38a3b329e123623d30118b798c8d66", "ignore_in_git": True, + "patches": os.path.join(root_dir, 'patches', 'quickjs', '*.patch'), }, "third_party/debug_router/src": { 'type': 'git', @@ -254,6 +256,18 @@ deps = { "require": ["third_party/webview2/webview2.nupkg"], "condition": system in ['windows'] }, + "third_party/codspeed-instrument-hooks/instrument-hooks": { + 'type': 'git', + 'url': 'https://github.com/CodSpeedHQ/instrument-hooks.git', + 'commit': 'a9ae7a4d897dbb694cb0355c9994141a7c4a1ab9', + "ignore_in_git": True, + }, + "third_party/argparse/src": { + 'type': 'git', + 'url': 'https://github.com/p-ranav/argparse', + 'commit': '3eda91b2e1ce7d569f84ba295507c4cd8fd96910', + "ignore_in_git": True, + }, 'buildtools/corepack/pnpm/7.33.6': { "type": "http", "url": "https://registry.npmjs.org/pnpm/-/pnpm-7.33.6.tgz", diff --git a/js_libraries/lynx-core/BUILD.gn b/js_libraries/lynx-core/BUILD.gn new file mode 100644 index 0000000000..ecce4e23c1 --- /dev/null +++ b/js_libraries/lynx-core/BUILD.gn @@ -0,0 +1,22 @@ +# Copyright 20205The Lynx Authors. All rights reserved. +# Licensed under the Apache License Version 2.0 that can be found in the +# LICENSE file in the root directory of this source tree. + +import("//core/Lynx.gni") + +config("lynx_core_public_config") { + include_dirs = [ + "./output", + ] +} + +source_set("lynx-core") { + exec_script("../../tools/js_tools/build.py", ["--platform", "android"]) + + public_configs = [ ":lynx_core_public_config" ] + + sources = [ + "output/js_libraries/lynx_core.h", + "output/js_libraries/lynx_core_dev.h", + ] +} diff --git a/js_libraries/lynx-core/src/app/app.ts b/js_libraries/lynx-core/src/app/app.ts index ba34ed6323..0219a25653 100644 --- a/js_libraries/lynx-core/src/app/app.ts +++ b/js_libraries/lynx-core/src/app/app.ts @@ -509,7 +509,7 @@ export abstract class BaseApp< ); } try { - this.lynx.performance.profileStart(TraceEventDef.EXECUTE_LOADED_SCRIPT, { + this.lynx.performance._profileStart(TraceEventDef.EXECUTE_LOADED_SCRIPT, { args: { path }, }); const ret = factory({ tt: this }); @@ -523,7 +523,7 @@ export abstract class BaseApp< return ret; } finally { - this.lynx.performance.profileEnd(); + this.lynx.performance._profileEnd(); } } diff --git a/patches/build/disable_strip.patch b/patches/build/disable_strip.patch new file mode 100644 index 0000000000..cd514e7e54 --- /dev/null +++ b/patches/build/disable_strip.patch @@ -0,0 +1,87 @@ +From 0842a13b8f3f1bb96722d94a3677260b2b465322 Mon Sep 17 00:00:00 2001 +From: Bot +Date: Wed, 11 Feb 2026 00:17:31 +0800 +Subject: [PATCH 1/2] Disable strip + +--- + toolchain/linux/BUILD.gn | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/toolchain/linux/BUILD.gn b/toolchain/linux/BUILD.gn +index 37e0869..31bc5fe 100644 +--- a/toolchain/linux/BUILD.gn ++++ b/toolchain/linux/BUILD.gn +@@ -48,7 +48,7 @@ gcc_toolchain("clang_x86") { + nm = "${prefix}/llvm-nm" + ar = "${prefix}/llvm-ar" + ld = cxx +- llvm_objcopy = "${prefix}/llvm-objcopy" ++ # llvm_objcopy = "${prefix}/llvm-objcopy" + + toolchain_cpu = "x86" + toolchain_os = "linux" +@@ -64,7 +64,7 @@ gcc_toolchain("x86") { + nm = "${prefix}nm" + ar = "${prefix}ar" + ld = cxx +- strip = "${prefix}strip" ++ # strip = "${prefix}strip" + + toolchain_cpu = "x86" + toolchain_os = "linux" +@@ -85,7 +85,7 @@ gcc_toolchain("clang_x64") { + nm = "${prefix}/llvm-nm" + ar = "${prefix}/llvm-ar" + ld = cxx +- llvm_objcopy = "${prefix}/llvm-objcopy" ++ # llvm_objcopy = "${prefix}/llvm-objcopy" + + toolchain_cpu = "x64" + toolchain_os = "linux" +@@ -106,7 +106,7 @@ gcc_toolchain("clang_arm64") { + nm = "${prefix}/llvm-nm" + ar = "${prefix}/llvm-ar" + ld = cxx +- llvm_objcopy = "${prefix}/llvm-objcopy" ++ # llvm_objcopy = "${prefix}/llvm-objcopy" + + toolchain_cpu = "arm64" + toolchain_os = "linux" +@@ -122,7 +122,7 @@ gcc_toolchain("x64") { + nm = "${prefix}nm" + ar = "${prefix}ar" + ld = cxx +- strip = "${prefix}strip" ++ # strip = "${prefix}strip" + + toolchain_cpu = "x64" + toolchain_os = "linux" +-- +2.39.5 + + +From 68f375eaacf9c31f9e6db166012fd9f1b0ac3a68 Mon Sep 17 00:00:00 2001 +From: Bot +Date: Wed, 11 Feb 2026 00:32:25 +0800 +Subject: [PATCH 2/2] Enable gc-sections for Linux + +--- + config/compiler/BUILD.gn | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/config/compiler/BUILD.gn b/config/compiler/BUILD.gn +index 855c10a..c38dceb 100644 +--- a/config/compiler/BUILD.gn ++++ b/config/compiler/BUILD.gn +@@ -416,6 +416,8 @@ config("compiler") { + # Linux/Android common flags setup. + # --------------------------------- + if (is_linux || is_android || is_harmony) { ++ cflags += ["-ffunction-sections", "-fdata-sections"] ++ ldflags += ["-Wl,--gc-sections"] + cflags += [ + "-fPIC", + "-pipe", # Use pipes for communicating between sub-processes. Faster. +-- +2.39.5 + diff --git a/patches/quickjs/0001-Introduce-LEPUS_AddExtraIntrinsicObjects.patch b/patches/quickjs/0001-Introduce-LEPUS_AddExtraIntrinsicObjects.patch new file mode 100644 index 0000000000..70433691fa --- /dev/null +++ b/patches/quickjs/0001-Introduce-LEPUS_AddExtraIntrinsicObjects.patch @@ -0,0 +1,36 @@ +From cbceced2576857f1896294e33f244aa3342d88bd Mon Sep 17 00:00:00 2001 +From: "hongzhiyuan.hzy" +Date: Wed, 11 Feb 2026 01:07:10 +0800 +Subject: [PATCH] Introduce LEPUS_AddExtraIntrinsicObjects + +--- + src/interpreter/quickjs/source/quickjs.cc | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/src/interpreter/quickjs/source/quickjs.cc b/src/interpreter/quickjs/source/quickjs.cc +index 260ad2d..b49638f 100644 +--- a/src/interpreter/quickjs/source/quickjs.cc ++++ b/src/interpreter/quickjs/source/quickjs.cc +@@ -1889,6 +1889,10 @@ QJS_STATIC void JS_AddIntrinsicWeakRef(LEPUSContext *ctx); + QJS_STATIC void JS_AddIntrinsicFinalizationRegistry(LEPUSContext *ctx); + QJS_STATIC void JS_AddIntrinsicBigInt(LEPUSContext *ctx); + ++extern "C" { ++void LEPUS_AddExtraIntrinsicObjects(LEPUSContext *ctx); ++} ++ + LEPUSContext *LEPUS_NewContext(LEPUSRuntime *rt) { + CallGCFunc(JS_NewContext_GC, rt); + LEPUSContext *ctx; +@@ -1911,6 +1915,8 @@ LEPUSContext *LEPUS_NewContext(LEPUSRuntime *rt) { + JS_AddIntrinsicWeakRef(ctx); + JS_AddIntrinsicFinalizationRegistry(ctx); + ++ LEPUS_AddExtraIntrinsicObjects(ctx); ++ + return ctx; + } + +-- +2.39.5 + diff --git a/third_party/argparse/BUILD.gn b/third_party/argparse/BUILD.gn new file mode 100644 index 0000000000..62b5de2782 --- /dev/null +++ b/third_party/argparse/BUILD.gn @@ -0,0 +1,14 @@ +import("../../config.gni") + +config("argparse_public_config") { + include_dirs = [ + "./src/include", + ] +} + +source_set("argparse") { + public_configs = [ ":argparse_public_config" ] + sources = [ + "./src/include/argparse/argparse.hpp" + ] +} diff --git a/third_party/codspeed-instrument-hooks/BUILD.gn b/third_party/codspeed-instrument-hooks/BUILD.gn new file mode 100644 index 0000000000..275e1cd82e --- /dev/null +++ b/third_party/codspeed-instrument-hooks/BUILD.gn @@ -0,0 +1,24 @@ +import("../../config.gni") + +config("codspeed_instrument_hooks_public_config") { + include_dirs = [ + "./", + "./instrument-hooks/includes", + ] +} + +config("codspeed_instrument_hooks_config") { + cflags = [ + "-Wno-unused-variable", + "-Wno-incompatible-pointer-types", + "-O0", + "-g2", + "-UNVALGRIND", + ] +} + +source_set("codspeed-instrument-hooks") { + configs += [ ":codspeed_instrument_hooks_config" ] + public_configs = [":codspeed_instrument_hooks_public_config"] + sources = [] +} diff --git a/third_party/quickjs/BUILD.gn b/third_party/quickjs/BUILD.gn index ad4c8ca8ed..0a8a7ca344 100644 --- a/third_party/quickjs/BUILD.gn +++ b/third_party/quickjs/BUILD.gn @@ -30,7 +30,7 @@ config("quickjs_private_config") { "-Wno-unknown-warning-option", "-Wno-newline-eof", "-Wno-sign-compare", - "-Oz" + "-O2", ] # FIXME(): definition controlled by the args diff --git a/tools/envsetup.sh b/tools/envsetup.sh index a2d7e04b05..07e0f6e2eb 100644 --- a/tools/envsetup.sh +++ b/tools/envsetup.sh @@ -34,7 +34,7 @@ function android_env_setup() { local SCRIPT_REAL_PATH=$(posix_absolute_path $1) local TOOLS_REAL_PATH=$(dirname $SCRIPT_REAL_PATH) echo $TOOLS_REAL_PATH - if [ "$ANDROID_HOME" ]; then + if [ "${ANDROID_HOME:-}" ]; then export ANDROID_NDK=$ANDROID_HOME/ndk/21.1.6352462 export ANDROID_NDK_21=$ANDROID_HOME/ndk/21.1.6352462 export ANDROID_SDK=$ANDROID_HOME diff --git a/tools/hab b/tools/hab index 34023ae9a0..fdc210aeed 100755 --- a/tools/hab +++ b/tools/hab @@ -11,7 +11,7 @@ set -e ## habitat start up script for UN*X ## ############################################################################## -__version__="0.3.146-alpha.2" +__version__="0.3.146-alpha.4" if [[ "$HABITAT_DEBUG" = "true" ]]; then set -x diff --git a/tools/hab.ps1 b/tools/hab.ps1 index d79f3ec7fd..4c8ad58042 100644 --- a/tools/hab.ps1 +++ b/tools/hab.ps1 @@ -1,6 +1,6 @@ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 -$__version__ = "0.3.146-alpha.2" +$__version__ = "0.3.146-alpha.4" if ($null -eq $env:HABITAT_VERSION) { $HABITAT_VERSION = $__version__ diff --git a/tools/js_tools/build.py b/tools/js_tools/build.py index a8db513059..5a549d6715 100644 --- a/tools/js_tools/build.py +++ b/tools/js_tools/build.py @@ -47,6 +47,18 @@ def build(platform, releaseOutput, devOutput, version): ['pnpm', '--filter', f'@lynx-js/lynx-core', f'build:{platform}'], os.getcwd(), env) + subprocess.run( + ['mkdir', '-p', 'js_libraries'], + cwd=outputPath) + subprocess.run( + ['xxd -i lynx_core.js > js_libraries/lynx_core.h'], + cwd=outputPath, + shell=True) + subprocess.run( + ['xxd -i lynx_core_dev.js > js_libraries/lynx_core_dev.h'], + cwd=outputPath, + shell=True) + # Copy lynx_core.js if releaseOutput is provided if releaseOutput: releaseDir = os.path.dirname(releaseOutput)