feat(runtime): gate the wasm tier ladder behind tools builds (#13882) 314202c0f2
* feat(runtime): gate the wasm tier ladder and transplant behind tools builds

* refactor(runtime): inline the disabled ladder so tier paths compile away

* fix(runtime): honor the tests workspace tools global for wamr internals

* fix(runtime): stub ladder keeps the singleton contract

* fix(scripting_workspace): handles import survives windows separators

Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head
index 358d82e..c68f2c2 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-9fe2b415a70bb2d6460a9df2c2e4a6caf8589c30
+314202c0f2271da5100892d0fd430114107cf8da
diff --git a/include/rive/wasm/module_tier_ladder.hpp b/include/rive/wasm/module_tier_ladder.hpp
index d353edb..161b0bc 100644
--- a/include/rive/wasm/module_tier_ladder.hpp
+++ b/include/rive/wasm/module_tier_ladder.hpp
@@ -3,16 +3,28 @@
 
 #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 <condition_variable>
 #include <cstdint>
-#include <deque>
 #include <functional>
-#include <mutex>
 #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
 {
@@ -28,6 +40,8 @@
     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
@@ -88,12 +102,7 @@
         TierSpecies species = TierSpecies::o0;
         std::string wasmPath;
         uint64_t generation = 0;
-#ifdef _WIN32
-        // The ladder compiles out on Windows; only the layout survives.
-        intptr_t pid = -1;
-#else
         pid_t pid = -1;
-#endif
         bool cancelled = false;
     };
 
@@ -126,6 +135,49 @@
     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
diff --git a/include/rive/wasm/wamr_state_transplant.hpp b/include/rive/wasm/wamr_state_transplant.hpp
index 7046371..76fc5fe 100644
--- a/include/rive/wasm/wamr_state_transplant.hpp
+++ b/include/rive/wasm/wamr_state_transplant.hpp
@@ -3,12 +3,18 @@
 
 #ifdef WITH_RIVE_SCRIPTING_WASM
 
+// The transplant is the tier ladder's frame-boundary half; it rides the
+// same gate.
+#include "rive/wasm/module_tier_ladder.hpp"
+
 #include "wasm_export.h"
 #include <string>
 
 namespace rive
 {
 
+#ifdef RIVE_WASM_TIER_LADDER
+
 // Copies live mutable state (memories, globals, table entries) from one
 // instance of a module onto a fresh instance of the same module content in
 // a different representation - the frame-boundary half of a tier swap.
@@ -20,6 +26,19 @@
                          wasm_module_inst_t destination,
                          std::string& error);
 
+#else // RIVE_WASM_TIER_LADDER
+
+// Only reachable through the disabled ladder's upgrade path.
+inline bool wamrTransplantState(wasm_module_inst_t,
+                                wasm_module_inst_t,
+                                std::string& error)
+{
+    error = "tier transplant not built";
+    return false;
+}
+
+#endif // RIVE_WASM_TIER_LADDER
+
 } // namespace rive
 
 #endif // WITH_RIVE_SCRIPTING_WASM
diff --git a/premake5_v2.lua b/premake5_v2.lua
index bdb1cf3..4c87f57 100644
--- a/premake5_v2.lua
+++ b/premake5_v2.lua
@@ -258,10 +258,20 @@
         filter({ 'options:with_rive_scripting' })
         do
             includedirs({ wamr .. '/core/iwasm/include' })
-            -- The tier transplant reads instance internals; layout hinges
-            -- on the same config defines the wamr lib builds with.
-            includedirs(wamrInternalIncludes)
             defines(wamrConfigDefines)
+            -- The tier transplant is the one TU reading instance internals
+            -- (layout hinges on the config defines above); runtime builds
+            -- compile its stub without them. The tests workspace forces
+            -- tools on without the option, so honor both signals.
+            if WITH_RIVE_TOOLS == true then
+                includedirs(wamrInternalIncludes)
+            else
+                filter({
+                    'options:with_rive_scripting',
+                    'options:with_rive_tools',
+                })
+                includedirs(wamrInternalIncludes)
+            end
             filter({ 'options:with_rive_scripting', 'system:macosx' })
             defines({ 'BH_PLATFORM_DARWIN' })
             filter({ 'options:with_rive_scripting', 'system:linux' })
diff --git a/src/wasm/module_tier_ladder.cpp b/src/wasm/module_tier_ladder.cpp
index 5318d0f..98c0c25 100644
--- a/src/wasm/module_tier_ladder.cpp
+++ b/src/wasm/module_tier_ladder.cpp
@@ -2,7 +2,7 @@
 
 #include "rive/wasm/module_tier_ladder.hpp"
 
-#ifndef _WIN32
+#ifdef RIVE_WASM_TIER_LADDER
 
 #include <algorithm>
 #include <cerrno>
@@ -472,32 +472,5 @@
 
 } // namespace rive
 
-#else // _WIN32
-
-namespace rive
-{
-// Windows editor execution is a deferred decision; the ladder reports
-// disabled and every module stays on interp.
-ModuleTierLadder& ModuleTierLadder::instance()
-{
-    static ModuleTierLadder* ladder = new ModuleTierLadder();
-    return *ladder;
-}
-ModuleTierLadder::~ModuleTierLadder() {}
-void ModuleTierLadder::configure(const std::string&, const std::string&) {}
-bool ModuleTierLadder::enabled() { return false; }
-void ModuleTierLadder::onArrival(ArrivalCallback) {}
-void ModuleTierLadder::schedule(const std::string&,
-                                uint64_t,
-                                Span<const uint8_t>)
-{}
-void ModuleTierLadder::stagePristine(uint64_t, Span<const uint8_t>) {}
-std::string ModuleTierLadder::artifactPath(uint64_t, TierSpecies)
-{
-    return std::string();
-}
-void ModuleTierLadder::drain() {}
-} // namespace rive
-
-#endif // _WIN32
+#endif // RIVE_WASM_TIER_LADDER
 #endif // WITH_RIVE_SCRIPTING_WASM
diff --git a/src/wasm/wamr_state_transplant.cpp b/src/wasm/wamr_state_transplant.cpp
index d5862de..c3691f6 100644
--- a/src/wasm/wamr_state_transplant.cpp
+++ b/src/wasm/wamr_state_transplant.cpp
@@ -2,6 +2,10 @@
 
 #include "rive/wasm/wamr_state_transplant.hpp"
 
+// The one TU reading wamr instance internals; runtime builds compile it
+// empty and use the header's inline stub.
+#ifdef RIVE_WASM_TIER_LADDER
+
 #include "aot_runtime.h"
 #include "wasm_runtime.h"
 
@@ -175,4 +179,5 @@
 
 } // namespace rive
 
+#endif // RIVE_WASM_TIER_LADDER
 #endif // WITH_RIVE_SCRIPTING_WASM