| #ifndef _RIVE_MODULE_TIER_LADDER_HPP_ |
| #define _RIVE_MODULE_TIER_LADDER_HPP_ |
| |
| #ifdef WITH_RIVE_SCRIPTING_WASM |
| |
| // An editor/tools feature: the ladder spawns wamrc subprocesses. Runtime |
| // builds stub it out and stay on interp (prelinked artifacts load by |
| // registry instead); windows tools builds stay stubbed pending a |
| // CreateProcess port. |
| #if defined(WITH_RIVE_TOOLS) && !defined(_WIN32) |
| #define RIVE_WASM_TIER_LADDER 1 |
| #endif |
| |
| #include "rive/span.hpp" |
| #include <cstdint> |
| #include <functional> |
| #include <string> |
| |
| #ifdef RIVE_WASM_TIER_LADDER |
| #include <condition_variable> |
| #include <deque> |
| #include <mutex> |
| #include <sys/types.h> |
| #include <thread> |
| #include <unordered_map> |
| #include <vector> |
| #endif |
| |
| namespace rive |
| { |
| |
| // Compiled-artifact species, in ascending speed. Interp is the implicit |
| // tier 0 every module already runs on; the ladder only produces artifacts. |
| enum class TierSpecies : uint8_t |
| { |
| o0 = 0, // wamrc -O0: ~2x interp, sub-second for typical modules |
| o3 = 1, // wamrc top opt level with sw bounds, runs on every build |
| // No inline bounds checks; needs the guard-page trap handler, so only |
| // RIVE_WASM_HW_BOUNDS builds produce or load these. |
| hw = 2, |
| }; |
| |
| #ifdef RIVE_WASM_TIER_LADDER |
| |
| // Drives wamrc subprocesses that turn wasm modules into AOT artifacts and |
| // reports arrivals for frame-boundary hot swap. File-in/file-out, no IPC: |
| // compile crashes and LLVM's RSS stay in the child. Process-wide like the |
| // shared module cache; safe to call from any thread. |
| class ModuleTierLadder |
| { |
| public: |
| static ModuleTierLadder& instance(); |
| |
| // wamrc discovery: explicit path wins, RIVE_WAMRC env second. The |
| // ladder is inert (schedule() == no-op) until both a compiler and a |
| // cache directory are configured. |
| void configure(const std::string& wamrcPath, const std::string& cacheDir); |
| bool enabled(); |
| |
| struct Artifact |
| { |
| uint64_t moduleKey = 0; |
| TierSpecies species = TierSpecies::o0; |
| std::string path; |
| }; |
| using ArrivalCallback = std::function<void(const Artifact&)>; |
| // One process-wide sink; the editor fans out. Called from ladder worker |
| // threads — receivers defer the swap to a frame boundary. |
| void onArrival(ArrivalCallback callback); |
| |
| // Schedule compiles for a module. laneId groups requests for the same |
| // logical script: a newer schedule on the lane kills superseded |
| // in-flight compiles (newest-hash-wins). Small modules go straight to |
| // -O3; larger ones run -O0 and -O3 in parallel. Cache hits report |
| // arrival immediately without spawning. |
| void schedule(const std::string& laneId, |
| uint64_t moduleKey, |
| Span<const uint8_t> moduleBytes); |
| |
| // Write the module's wamrc input before wasm_runtime_load touches the |
| // buffer: the fast-interp loader rewrites it in place, so bytes staged |
| // at schedule time are no longer valid wasm. |
| void stagePristine(uint64_t moduleKey, Span<const uint8_t> moduleBytes); |
| |
| // Ready artifact path for a module at the given species, empty if none. |
| std::string artifactPath(uint64_t moduleKey, TierSpecies species); |
| |
| // Test hooks: block until the lane has no queued or running compiles. |
| void drain(); |
| |
| // Straight -O3 cutoff; modules at or under skip the -O0 rung entirely. |
| static constexpr size_t kStraightToO3Bytes = 50 * 1024; |
| |
| private: |
| ModuleTierLadder() = default; |
| ~ModuleTierLadder(); |
| |
| struct Job |
| { |
| std::string laneId; |
| uint64_t moduleKey = 0; |
| TierSpecies species = TierSpecies::o0; |
| std::string wasmPath; |
| uint64_t generation = 0; |
| pid_t pid = -1; |
| bool cancelled = false; |
| }; |
| |
| void ensureWorkers(); |
| void workerLoop(); |
| bool runWamrc(Job& job); |
| std::string artifactName(uint64_t moduleKey, TierSpecies species); |
| std::string keyedCacheDir(); |
| const std::string& wamrcVersion(); |
| |
| std::mutex m_mutex; |
| std::condition_variable m_workAvailable; |
| std::condition_variable m_idle; |
| std::string m_wamrcPath; |
| std::string m_cacheDir; |
| std::string m_wamrcVersion; |
| bool m_versionProbed = false; |
| ArrivalCallback m_arrival; |
| std::deque<Job> m_queue; |
| std::vector<Job*> m_running; |
| // Latest schedule generation per lane; older jobs are stale and are |
| // killed (running) or skipped (queued). |
| std::unordered_map<std::string, uint64_t> m_laneGeneration; |
| uint64_t m_nextGeneration = 1; |
| std::vector<std::thread> m_workers; |
| bool m_terminating = false; |
| |
| // Two concurrent compiles saturate the edit loop without starving the |
| // machine; the -O3 tail rides behind the -O0 rung it bridges. |
| static constexpr unsigned kWorkerCount = 2; |
| }; |
| |
| #else // RIVE_WASM_TIER_LADDER |
| |
| // Same surface, all inline: the ladder reports disabled, so the tier |
| // paths behind enabled() checks compile away. |
| class ModuleTierLadder |
| { |
| public: |
| static ModuleTierLadder& instance() |
| { |
| static ModuleTierLadder ladder; |
| return ladder; |
| } |
| |
| void configure(const std::string&, const std::string&) {} |
| bool enabled() { return false; } |
| |
| struct Artifact |
| { |
| uint64_t moduleKey = 0; |
| TierSpecies species = TierSpecies::o0; |
| std::string path; |
| }; |
| using ArrivalCallback = std::function<void(const Artifact&)>; |
| void onArrival(ArrivalCallback) {} |
| |
| void schedule(const std::string&, uint64_t, Span<const uint8_t>) {} |
| void stagePristine(uint64_t, Span<const uint8_t>) {} |
| std::string artifactPath(uint64_t, TierSpecies) { return std::string(); } |
| void drain() {} |
| |
| static constexpr size_t kStraightToO3Bytes = 50 * 1024; |
| |
| private: |
| // Singleton in both configurations, so code cannot compile against one |
| // and break against the other. |
| ModuleTierLadder() = default; |
| ~ModuleTierLadder() = default; |
| ModuleTierLadder(const ModuleTierLadder&) = delete; |
| ModuleTierLadder& operator=(const ModuleTierLadder&) = delete; |
| }; |
| |
| #endif // RIVE_WASM_TIER_LADDER |
| |
| } // namespace rive |
| |
| #endif // WITH_RIVE_SCRIPTING_WASM |
| #endif |