第一部分:问题复盘与解决方案 (From Zero to Hero)

我们按时间线梳理了遇到的 3 个核心问题:

1. Libc SIGILL (Fatal signal 4) 崩溃

  • 现象:App 刚运行到函数调用就闪退,报错 ILL_ILLOPC (非法指令)。
  • 原因
    • 信号冲突:Wasmtime 默认使用系统信号 (SIGBUS/SIGSEGV) 来处理内存越界,这与 Android 的 ART 虚拟机信号处理冲突。
    • JIT 激进优化:默认生成的 ARM64 机器码可能包含你手机 CPU 不支持的指令(如 SIMD 或 BTI)。
  • 解决方案
    • signals_based_traps = false关键修复,强制使用显式的 if 判断做边界检查,不依赖系统信号。
    • opt_level = NONE:关闭优化,生成最保守的代码。
    • simd = false:关闭向量指令,保证兼容性。

2. Wasm Trap: null reference

  • 现象:不闪退了,但报错 Trap,提示空指针。
  • 原因Kotlin 运行时未初始化。Wasm 模块只是加载了,但 Kotlin 的 GC 和内存分配器还没启动。CLI 会自动运行 _start,但嵌入式 API 需要手动调用。
  • 解决方案
    • 在调用 add 之前,手动查找并调用 _initialize (Reactor模式) 或 _start (Command模式)。

3. 看不到 println 日志

  • 现象:运行成功返回 3,但 Logcat 空空如也。
  • 原因:WASI 的标准输出 (stdout) 默认指向 /dev/null
  • 解决方案
    • 使用 wasi_config_set_stdout_custom 注册回调函数,将 stdout 数据流“偷”出来,通过 __android_log_print 转发给 Logcat。

第二部分:配置影响分析 (Trade-offs)

为了修复 SIGILL 和崩溃,我们使用了一些“保守”的配置。以下是这些修改带来的具体影响:

1. 关闭信号陷阱 (signals_based_traps = false)

  • 影响性能略微下降,但稳定性极大提升。
  • 解释
    • 默认模式:Wasmtime 假定内存访问是安全的,不做检查。如果越界,依靠操作系统抛出信号 (Hardware Trap) 来捕获。这很快,但在 Android 这种对信号管控严格的环境极易崩溃。
    • 当前模式:Wasmtime 会在每一条涉及内存读写的指令前,插入一段汇编代码(类似 if (addr > max) throw error)。这增加了 CPU 指令数量,但确保了绝对安全,不会导致 App 闪退,只会返回可捕获的 Trap 错误。

2. 关闭优化 (opt_level = NONE)

  • 影响JIT 编译速度变快,但 Wasm 执行速度变慢。
  • 解释:编译器不再花时间做循环展开、寄存器分配优化等高级操作。对于 add 这种简单函数没感觉,但如果是复杂的算法(如图像处理),性能可能会比开启优化慢 2-5 倍。
  • 未来优化:等环境稳定后,可以尝试改回 WASMTIME_OPT_LEVEL_SPEED,只要保留 signals_based_traps = false,或许能跑通的。

3. 关闭 SIMD (simd = false)

  • 影响无法利用硬件加速进行并行计算。
  • 解释:如果你的 Wasm 模块(比如图像滤镜、加密库)大量使用了向量指令,关闭此选项会导致模块加载失败或回退到标量计算(变慢)。但对于业务逻辑(如 Kotlin UI、数据处理),几乎没有影响。

总结

当前的配置是 “兼容性优先 (Stability First)” 的方案。它牺牲了一点点运行时性能,换取了在各种 Android 机型(无论新旧 CPU、无论系统信号机制如何)上都能不闪退、稳定运行的保障。对于初期集成和调试来说,这是最完美的起步点。

第三部分:优化后的完整 C++ 代码

我对代码进行了重构,特点如下:

  1. 增加配置开关:JNI 函数增加 jboolean enableLogs 参数,由 Kotlin 控制是否开启日志转发。
  2. 结构清晰:将配置、初始化、调用分块注释。
  3. 内存安全:确保所有 JNI 资源和 Wasm 对象都被正确释放。

1. 修改 Kotlin 定义 (MainActivity.kt)

// 增加一个 boolean 参数控制日志
private external fun runWasmAdd(wasmBytes: ByteArray, enableLogs: Boolean): Int

// 调用时
runWasmAdd(wasmBytes, true) // 开启日志

2. C++ 实现 (wasm_host.cpp)

#include <jni.h>
#include <android/log.h>
#include <vector>
#include <string>

// Wasmtime Headers
#include "wasm.h"
#include "wasi.h"
#include "wasmtime.h"

#define TAG "KotlinWasm"
// 简化的日志宏
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)

// ============================================================================
// 工具函数:错误打印
// ============================================================================
static void log_and_exit_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap) {
    wasm_byte_vec_t error_message;
    if (error != nullptr) {
        wasmtime_error_message(error, &error_message);
        wasmtime_error_delete(error);
        LOGE("ERROR: %s -> %.*s", message, (int)error_message.size, error_message.data);
    } else if (trap != nullptr) {
        wasm_trap_message(trap, &error_message);
        wasm_trap_delete(trap);
        LOGE("TRAP: %s -> %.*s", message, (int)error_message.size, error_message.data);
    } else {
        LOGE("UNKNOWN ERROR: %s", message);
    }
    if (error || trap) wasm_byte_vec_delete(&error_message);
}

// ============================================================================
// 回调函数:WASI stdout -> Android Logcat
// ============================================================================
ptrdiff_t android_log_callback(void* data, const unsigned char* buffer, size_t size) {
    if (size == 0) return 0;
    // 构造字符串并移除末尾换行符(Logcat 自带换行)
    std::string msg((const char*)buffer, size);
    while (!msg.empty() && (msg.back() == '\n' || msg.back() == '\r')) {
        msg.pop_back();
    }
    if (!msg.empty()) {
        __android_log_print(ANDROID_LOG_INFO, TAG, "[WasmOut] %s", msg.c_str());
    }
    return size;
}

// ============================================================================
// JNI 主入口
// ============================================================================
extern "C" JNIEXPORT jint JNICALL
Java_crow_wasmedge_wasmline_MainActivity_runWasmAdd(JNIEnv *env, jobject thiz, jbyteArray wasmBytes, jboolean enableLogs) {
    
    // --- 1. 获取 Wasm 字节数据 ---
    jsize len = env->GetArrayLength(wasmBytes);
    jbyte *bytes = env->GetByteArrayElements(wasmBytes, nullptr);
    if (!bytes) return -1;

    // --- 2. 配置 Wasmtime (The Silver Bullet Config) ---
    wasm_config_t *config = wasm_config_new();
    
    // 开启 Kotlin 所需特性
    wasmtime_config_wasm_gc_set(config, true);
    wasmtime_config_wasm_function_references_set(config, true);
    wasmtime_config_wasm_exceptions_set(config, true);

    // 解决 Android SIGILL 崩溃的核心配置
    wasmtime_config_cranelift_opt_level_set(config, WASMTIME_OPT_LEVEL_NONE); // 关闭优化
    wasmtime_config_wasm_simd_set(config, false);         // 关闭 SIMD
    wasmtime_config_wasm_relaxed_simd_set(config, false); // 关闭 Relaxed SIMD
    wasmtime_config_signals_based_traps_set(config, false); // 关闭信号 Trap,改用显式检查

    // --- 3. 创建 Engine & Store ---
    wasm_engine_t *engine = wasm_engine_new_with_config(config);
    if (!engine) {
        env->ReleaseByteArrayElements(wasmBytes, bytes, JNI_ABORT);
        LOGE("Failed to create engine");
        return -1;
    }
    wasmtime_store_t *store = wasmtime_store_new(engine, nullptr, nullptr);
    wasmtime_context_t *context = wasmtime_store_context(store);

    // --- 4. 配置 WASI (含日志重定向) ---
    wasi_config_t *wasi_config = wasi_config_new();
    wasi_config_inherit_argv(wasi_config);
    wasi_config_inherit_env(wasi_config);

    if (enableLogs) {
        // 如果启用,挂载回调函数
        wasi_config_set_stdout_custom(wasi_config, android_log_callback, nullptr, nullptr);
        wasi_config_set_stderr_custom(wasi_config, android_log_callback, nullptr, nullptr);
    }

    wasmtime_error_t *error = wasmtime_context_set_wasi(context, wasi_config);
    if (error) {
        log_and_exit_error("WASI Config Error", error, nullptr);
        return -1;
    }

    // --- 5. 编译与实例化 ---
    wasmtime_linker_t *linker = wasmtime_linker_new(engine);
    wasmtime_linker_define_wasi(linker);

    wasmtime_module_t *module = nullptr;
    error = wasmtime_module_new(engine, (uint8_t *)bytes, len, &module);
    
    // 释放 Java 数组,节省内存
    env->ReleaseByteArrayElements(wasmBytes, bytes, JNI_ABORT);

    if (error) {
        log_and_exit_error("Compile Error", error, nullptr);
        return -1;
    }

    wasmtime_instance_t instance;
    wasm_trap_t *trap = nullptr;
    error = wasmtime_linker_instantiate(linker, context, module, &instance, &trap);
    if (error || trap) {
        log_and_exit_error("Instantiate Error", error, trap);
        return -1;
    }

    // --- 6. 核心步骤:初始化 Kotlin 运行时 (_initialize / _start) ---
    // 这是解决 null reference 的关键
    wasmtime_extern_t init_extern;
    wasmtime_func_t init_func;
    bool needs_init = false;

    if (wasmtime_instance_export_get(context, &instance, "_initialize", 11, &init_extern) &&
        init_extern.kind == WASM_EXTERN_FUNC) {
        init_func = init_extern.of.func;
        needs_init = true;
    } else if (wasmtime_instance_export_get(context, &instance, "_start", 6, &init_extern) &&
             init_extern.kind == WASM_EXTERN_FUNC) {
        init_func = init_extern.of.func;
        needs_init = true;
    }

    if (needs_init) {
        error = wasmtime_func_call(context, &init_func, nullptr, 0, nullptr, 0, &trap);
        if (error || trap) {
             // _start 可能会因为 exit(0) 而 trap,这里简单处理,如果是严重错误才退出
             // 实际生产中可能需要检查 trap message 是否包含 "exit"
            log_and_exit_error("Runtime Init Error (Ignorable if exit code 0)", error, trap);
        }
    }

    // --- 7. 调用目标函数 (add) ---
    int32_t result_val = -1;
    wasmtime_extern_t run_extern;
    if (wasmtime_instance_export_get(context, &instance, "add", 3, &run_extern) &&
        run_extern.kind == WASM_EXTERN_FUNC) {
        
        wasmtime_func_t func_obj = run_extern.of.func;
        
        // 参数:1, 2
        wasmtime_val_t args[2];
        args[0].kind = WASMTIME_I32; args[0].of.i32 = 1;
        args[1].kind = WASMTIME_I32; args[1].of.i32 = 2;
        
        wasmtime_val_t results[1];

        error = wasmtime_func_call(context, &func_obj, args, 2, results, 1, &trap);
        
        if (error || trap) {
            log_and_exit_error("Function Call Error", error, trap);
        } else {
            if (results[0].kind == WASMTIME_I32) {
                result_val = results[0].of.i32;
                if (enableLogs) LOGD("Success! Result: %d", result_val);
            }
        }
    } else {
        LOGE("Export 'add' not found");
    }

    // --- 8. 清理资源 ---
    wasmtime_module_delete(module);
    wasmtime_linker_delete(linker);
    wasmtime_store_delete(store);
    wasm_engine_delete(engine);

    return result_val;
}