fix(evi): build and run the scripting player on ios and android devices (#14425) 961a2accfa
* fix(evi): build and run the scripting player on iOS and Android devices

* fix(evi): resize the iOS drawable when the device rotates

* fix(evi): align the Android player for 16 KB page devices

* fix(evi): build and run the player on the iOS simulator

* fix(evi): move the pointer to a touch before pressing it

* fix(evi): pick the universal wamr shim before assuming arm64, and keep luau out of wasm only builds

Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head
index e0b2166..b699c18 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-f848d2174f3418b750a40c9cb0d14d672ad04ac0
+961a2accfaed7de1c9b486518baf81c36748462d
diff --git a/build/rive_build_config.lua b/build/rive_build_config.lua
index 7e04f4e..1777a85 100644
--- a/build/rive_build_config.lua
+++ b/build/rive_build_config.lua
@@ -243,6 +243,18 @@
     end
 end
 
+if _OPTIONS['arch'] == 'universal' then
+    -- ar updates an archive in place, which it refuses once the archive is
+    -- fat, so an incremental universal build writes each one afresh.
+    local base_gettoolname = premake.tools.clang.gettoolname
+    function premake.tools.clang.gettoolname(cfg, tool)
+        if tool == 'ar' then
+            return 'rm -f $out && ar'
+        end
+        return base_gettoolname(cfg, tool)
+    end
+end
+
 filter({ 'options:config=release', 'options:not no-lto', 'system:macosx or ios' })
 do
     -- The 'linktimeoptimization' command attempts to use llvm-ar, which doesn't always exist on macos.
diff --git a/scripting/premake5_wamr.lua b/scripting/premake5_wamr.lua
index 775ed36..6f0441c 100644
--- a/scripting/premake5_wamr.lua
+++ b/scripting/premake5_wamr.lua
@@ -99,21 +99,27 @@
     -- The tail-dup knobs need llvm 19; older clangs build correct but
     -- slower dispatch, still caught by the dispatch-site guard where it runs.
     -- The probe runs host clang; cross toolchains (the NDK's) may reject
-    -- the knobs the host accepts, so cross builds keep default dispatch.
+    -- the knobs the host accepts, and iOS embeds bitcode, which refuses
+    -- -mllvm, so those builds keep default dispatch.
     local devNull = os.ishost('windows') and 'NUL' or '/dev/null'
     local _, tailDupProbe = os.outputof(
         'clang -fsyntax-only -x c ' .. devNull ..
             ' -mllvm -tail-dup-pred-size=5000' ..
             ' -mllvm -tail-dup-succ-size=5000 2>&1'
     )
-    if tailDupProbe == 0 and _OPTIONS['for_android'] == nil and not isNx then
+    if
+        tailDupProbe == 0
+        and _OPTIONS['for_android'] == nil
+        and _OPTIONS['os'] ~= 'ios'
+        and not isNx
+    then
         buildoptions({
             '-mllvm -tail-dup-pred-size=5000',
             '-mllvm -tail-dup-succ-size=5000',
         })
     end
     defines(wamrConfigDefines)
-    filter({ 'system:macosx' })
+    filter({ 'system:macosx or system:ios' })
     defines({ 'BH_PLATFORM_DARWIN' })
     filter({ 'system:linux' })
     defines({ 'BH_PLATFORM_LINUX' })
@@ -131,11 +137,23 @@
     local archOption = _OPTIONS['arch']
     local machine = os.outputof('uname -m')
     local isArm64
+    local isUniversal = false
     -- 'host' is the option's default, not an explicit lane.
     if isNx then
         -- Windows hosted cross build, so neither the host arch nor --arch
         -- describes the target.
         isArm64 = true
+    elseif archOption == 'universal' then
+        -- One compile for every slice, as the iOS simulator build does.
+        isUniversal = true
+    elseif
+        _OPTIONS['os'] == 'ios'
+        and _OPTIONS['variant'] ~= 'emulator'
+        and _OPTIONS['variant'] ~= 'xrsimulator'
+        and _OPTIONS['variant'] ~= 'appletvsimulator'
+    then
+        -- Every Apple device is arm64.
+        isArm64 = true
     elseif archOption ~= nil and archOption ~= '' and archOption ~= 'host' then
         isArm64 = archOption == 'arm64' or archOption == 'aarch64'
     else
@@ -154,6 +172,8 @@
             '-DBUILD_TARGET=\\"THUMBV7\\"',
             '-Wno-int-conversion',
         })
+    elseif isUniversal then
+        -- WAMR's config.h names the target per slice when nothing here does.
     elseif isArm64 then
         defines({ 'BUILD_TARGET_AARCH64' })
         buildoptions({ '-DBUILD_TARGET=\\"AARCH64\\"' })
@@ -167,13 +187,26 @@
     -- no simd invoke variant; its marshaling never widens. Windows takes
     -- the mingw shim: same Win64 ABI as MSVC targets, and GAS syntax that
     -- clang's integrated assembler handles without ml64.
-    local invokeNative = isArm32 and 'invokeNative_thumb_vfp.s'
-        or isArm64 and 'invokeNative_aarch64_simd.s'
-        or os.target() == 'windows' and 'invokeNative_mingw_x64_simd.s'
-        or 'invokeNative_em64_simd.s'
-    local aotReloc = isArm32 and 'aot_reloc_thumb.c'
-        or isArm64 and 'aot_reloc_aarch64.c'
-        or 'aot_reloc_x86_64.c'
+    local archDir = wamr .. '/core/iwasm/common/arch/'
+    local relocDir = wamr .. '/core/iwasm/aot/arch/'
+    local invokeNative = isUniversal and 'wamr_universal_invoke.S'
+        or archDir
+            .. (
+                isArm32 and 'invokeNative_thumb_vfp.s'
+                or isArm64 and 'invokeNative_aarch64_simd.s'
+                or os.target() == 'windows' and 'invokeNative_mingw_x64_simd.s'
+                or 'invokeNative_em64_simd.s'
+            )
+    local aotReloc = isUniversal and 'wamr_universal_reloc.c'
+        or relocDir
+            .. (
+                isArm32 and 'aot_reloc_thumb.c'
+                or isArm64 and 'aot_reloc_aarch64.c'
+                or 'aot_reloc_x86_64.c'
+            )
+    if isUniversal then
+        includedirs({ archDir, relocDir })
+    end
     includedirs({
         wamr .. '/core/iwasm/include',
         wamr .. '/core/iwasm/common',
@@ -195,12 +228,12 @@
     })
     files({
         wamr .. '/core/iwasm/common/*.c',
-        wamr .. '/core/iwasm/common/arch/' .. invokeNative,
+        invokeNative,
         wamr .. '/core/iwasm/interpreter/wasm_loader.c',
         wamr .. '/core/iwasm/interpreter/wasm_runtime.c',
         wamr .. '/core/iwasm/interpreter/wasm_interp_fast.c',
         wamr .. '/core/iwasm/aot/*.c',
-        wamr .. '/core/iwasm/aot/arch/' .. aotReloc,
+        aotReloc,
         wamr .. '/core/iwasm/libraries/libc-builtin/*.c',
         wamr .. '/core/iwasm/libraries/libc-wasi/**.c',
         platformPath .. '/*.c',
diff --git a/scripting/wamr_universal_invoke.S b/scripting/wamr_universal_invoke.S
new file mode 100644
index 0000000..72e7d1c
--- /dev/null
+++ b/scripting/wamr_universal_invoke.S
@@ -0,0 +1,7 @@
+// A universal build assembles once for every arch slice, so each slice picks
+// its own invoke shim.
+#if defined(__aarch64__)
+#include "invokeNative_aarch64_simd.s"
+#else
+#include "invokeNative_em64_simd.s"
+#endif
diff --git a/scripting/wamr_universal_reloc.c b/scripting/wamr_universal_reloc.c
new file mode 100644
index 0000000..3b1a7a7
--- /dev/null
+++ b/scripting/wamr_universal_reloc.c
@@ -0,0 +1,9 @@
+// A universal build compiles once for every arch slice, so each slice picks
+// its own relocations and the target name its AOT artifacts must carry.
+#if defined(__aarch64__)
+#define BUILD_TARGET "AARCH64"
+#include "aot_reloc_aarch64.c"
+#else
+#define BUILD_TARGET "X86_64"
+#include "aot_reloc_x86_64.c"
+#endif
diff --git a/src/wasm/module_tier_ladder.cpp b/src/wasm/module_tier_ladder.cpp
index 096926f..608d3b3 100644
--- a/src/wasm/module_tier_ladder.cpp
+++ b/src/wasm/module_tier_ladder.cpp
@@ -148,7 +148,8 @@
         // rejects; say so once rather than fail each compile quietly.
         std::string probe =
             m_wamrcPath + " --rive-interrupt --version >/dev/null 2>&1";
-        m_wamrcUsable = system(probe.c_str()) == 0;
+        FILE* probePipe = popen(probe.c_str(), "r");
+        m_wamrcUsable = probePipe != nullptr && pclose(probePipe) == 0;
         if (!m_wamrcUsable)
         {
             fprintf(stderr,