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;
}
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
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