Stream: wasmtime

Topic: Why can compiled Pulley64 output be loaded on thei686Android


view this post on Zulip crowforkotlin (Jul 01 2026 at 10:05):

I would like to understand why the compiled Pulley64 output can be successfully loaded via the Pulley interpreter on the i686 Android platform. Previous troubleshooting with LLMs failed to identify the underlying reasons, and I am not highly proficient in Rust. Therefore, I am seeking your technical guidance.

In my project, I integrated Wasmtime via its C-API and encapsulated it using C++ JNI. I found that loading the compiled Pulley64 artifact using the Pulley interpreter succeeded and runs normally, and Pulley32 works as expected too. Below are the relevant logs and configurations.

./wasmtime compile ./kotlin.wasm -o kotlin.pwasm64 \
  --target pulley64 \
  -W gc=y \
  -W function-references=y \
  -W exceptions=y \
  -W simd=n \
  -W relaxed-simd=n \
  -O static-memory-guard-size=0 \
  -O dynamic-memory-guard-size=0 \
  -O signals-based-traps=n \
  -O opt-level=2 \
  -C cranelift-debug-verifier=no
10:29:41.416  I  [Wasmtime] Engine --> Configured Pulley target: pulley32

10:29:41.416  I  [Wasmtime] Engine --> Initialized successfully. engine_is_pulley=true

10:30:13.481  I  [Wasmtime] Module --> Deserializing precompiled artifact for /data/user/0/crow.wasmline/cache/plugin.pwasm

10:30:13.484  I  [Wasmtime] Module (Unsafe) --> Loaded and cached: /data/user/0/crow.wasmline/cache/plugin.pwasm
/**

     * Creates and configures the Wasm Config object.

     *

     * Critical Android Settings:

     * 1. Signals-based traps DISABLED: To prevent conflicts with Android ART signal handlers (SIGSEGV).

     * 2. Memory Guard Size = 0: To prevent VSS (Virtual Set Size) OOM on 32-bit or limited devices.

     * 3. GC / Exceptions: Enabled for Kotlin/Wasm support.

     */

    wasm_config_t *Engine::createConfig(bool usePulley) {

        wasm_config_t *conf = wasm_config_new();

        // Feature Flags for Kotlin/Wasm support

        wasmtime_config_wasm_gc_set(conf, true);

        wasmtime_config_gc_support_set(conf, true);

        wasmtime_config_wasm_reference_types_set(conf, true);

        wasmtime_config_wasm_function_references_set(conf, true);

        wasmtime_config_wasm_exceptions_set(conf, true);

        // Optimization: Disable SIMD if not strictly needed (improves compatibility)

        // Note: Kept as requested in requirements.

        wasmtime_config_wasm_simd_set(conf, false);

        wasmtime_config_wasm_relaxed_simd_set(conf, false);

        // [CRITICAL] Disable signal handlers to avoid crash conflicts with Android Runtime (ART)

        wasmtime_config_signals_based_traps_set(conf, false);

        // [CRITICAL] Set guard pages to 0 to minimize Virtual Memory usage (prevents OOM)

        wasmtime_config_memory_guard_size_set(conf, 0);

#ifdef WASMTIME_FEATURE_COMPONENT_MODEL_ASYNC

        // Enable concurrency support to match the compiler-side configuration.

        // The CLI compile step enables the component-model-async cargo feature,

        // which sets concurrency_support=true in the serialized .pwasm artifact.

        // The host must match this or deserialization will fail with:

        // "Module was compiled with concurrency support but it is not enabled for the host"

        wasmtime_config_concurrency_support_set(conf, true);

#endif

        // Set max stack size (512KB is usually sufficient for mobile logic)

        wasmtime_config_max_wasm_stack_set(conf, 512 * 1024);

        if (usePulley) {

            configurePulleyTarget(conf);

        }

#ifdef WASMTIME_FEATURE_COMPILER

        // Compiler Optimization Strategy: Optimize for Speed and Binary Size

        wasmtime_config_cranelift_opt_level_set(conf, WASMTIME_OPT_LEVEL_NONE);

        wasmtime_config_cranelift_debug_verifier_set(conf, false);

#endif

        return conf;

    }

    // Initialize the Engine

    void Engine::init(bool usePulley) {

        std::lock_guard<std::mutex> lock(engineMutex);

        if (!engine) {

            auto conf = createConfig(usePulley);

            // Create the engine with the configuration

            engine = wasm_engine_new_with_config(conf);

            if (engine) {

                LOGI(

                    "[Wasmtime] Engine --> Initialized successfully. engine_is_pulley=%s",

                    wasmtime_engine_is_pulley(engine) ? "true" : "false"

                );

            } else {

                LOGE("[Wasmtime] Engine --> Failed to initialize.");

            }

        }

    }
 /**

     * Shared Logic: File Read + raw wasm compilation or precompiled artifact deserialization.

     * No locks are held inside this function.

     */

    wasmtime_module_t *Module::compileInternal(const std::string &key, const std::string &filePath) {

        // 1. Engine Check

        wasm_engine_t *engine = Engine::getInstance().getEngine();

        if (!engine) {

            LOGE("[Wasmtime] Module --> Engine not initialized.");

            return nullptr;

        }

        const bool rawWasm = hasSuffixIgnoreCase(filePath, ".wasm");

        // 2. Compile raw wasm or deserialize precompiled artifacts

        wasmtime_module_t *module = nullptr;

        wasmtime_error_t *error = nullptr;

        if (rawWasm) {

#ifdef WASMTIME_FEATURE_COMPILER

            std::vector<uint8_t> data = Utils::readFile(filePath);

            if (data.empty()) {

                LOGE("[Wasmtime] Module --> Failed to read file: %s", filePath.c_str());

                return nullptr;

            }

            LOGI("[Wasmtime] Module --> Compiling raw wasm module for %s", filePath.c_str());

            error = wasmtime_module_new(

                engine,

                reinterpret_cast<const uint8_t*>(data.data()),

                data.size(),

                &module

            );

#else

            LOGE("[Wasmtime] Module --> Raw wasm compilation not available (no compiler). Use precompiled .pwasm artifacts. file=%s", filePath.c_str());

            return nullptr;

#endif

        } else {

            LOGI("[Wasmtime] Module --> Deserializing precompiled artifact for %s", filePath.c_str());

            error = wasmtime_module_deserialize_file(engine, filePath.c_str(), &module);

        }

        // 3. Error Handling

        if (error) {

            wasm_byte_vec_t msg;

            wasmtime_error_message(error, &msg);

            LOGE("[Wasmtime] Module --> Error loading module %s: %s", key.c_str(), msg.data);

            wasm_byte_vec_delete(&msg);

            wasmtime_error_delete(error);

            return nullptr;

        }

        return module;

    }

view this post on Zulip Alex Crichton (Jul 01 2026 at 14:45):

I'll note that we have a test for this specific behavior and that it returns an error, so my guess is that this is perhaps a configuration issue on your side. Your code snippet isn't complete, however, and configurePulleyTarget is missing for example, so I don't know what exactly the difference comes from

view this post on Zulip crowforkotlin (Jul 01 2026 at 15:09):

bool configurePulleyTarget(wasm_config_t *conf) {
            const auto targets = (sizeof(void*) == 8)
                ? std::array<const char*, 2>{"pulley64", "pulley64-unknown-unknown-elf"}
                : std::array<const char*, 2>{"pulley32", "pulley32-unknown-unknown-elf"};

            for (const char *target : targets) {
                wasmtime_error_t *error = wasmtime_config_target_set(conf, target);
                if (!error) {
                    LOGI("[Wasmtime] Engine --> Configured Pulley target: %s", target);
                    return true;
                }

                wasm_byte_vec_t msg;
                wasmtime_error_message(error, &msg);
                LOGE("[Wasmtime] Engine --> Failed to set Pulley target to %s: %s", target, msg.data);
                wasm_byte_vec_delete(&msg);
                wasmtime_error_delete(error);
            }

            return false;
 }
10:29:41.416  I  [Wasmtime] Engine --> Configured Pulley target: pulley32

It seems I accidentally left some code behind again. I've added it (sry); the logs show Pullley32, but Pullley64 can indeed be executed. I'll take some time to study some Pullley-related samples to find it.


Last updated: Jul 29 2026 at 05:03 UTC