You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Emscripten的wasm_simd128.h等价实现_mm_hadd_epi32?

问题分析与解决方案

核心问题

你在改写代码时犯了两个关键错误:

  1. 错误替换_mm_hadd_epi32:直接用wasm_i32x4_add做垂直加法,完全不符合原水平加法的逻辑
  2. 错误替换_mm_unpacklo_epi8/_mm_unpackhi_epi8:误用i16x8.shuffle,没有正确实现字节到16位的扩展补0操作

原Intel SIMD代码

static void arVideoLumaRGBAtoL_Intel_simd_asm(uint8_t *__restrict dest,
                                              uint8_t *__restrict src,
                                              int32_t numPixels) {
  __m128i *pin = (__m128i *)src;
  uint32_t *pout = (uint32_t *)dest;
  int numPixelsDiv8 = numPixels >> 3;
  __m128i RGBScale = _mm_set_epi16(
      0, B8_CCIR601, G8_CCIR601, R8_CCIR601, 0, B8_CCIR601, G8_CCIR601,
      R8_CCIR601); // RGBScale =
                   // 000000[B8_CCIR601]00[G8_CCIR601]00[R8_CCIR601]000000[B8_CCIR601]00[G8_CCIR601]00[R8_CCIR601].

  do {
    __m128i pixels0_3 = _mm_load_si128(
        pin++); // pixels0_3 =
                // [A3][B3][G3][R3][A2][B2][G2][R2][A1][B1][G1][R1][A0][B0][G0][R0].
    __m128i pixels4_7 = _mm_load_si128(
        pin++); // pixels4_7 =
                // [A7][B7][G7][R7][A6][B6][G6][R6][A5][B5][G5][R5][A4][B4][G4][R4].

    __m128i pixels0_3_l = _mm_unpacklo_epi8(
        pixels0_3,
        _mm_setzero_si128()); // pixels0_3_l =
                              // 00[A1]00[B1]00[G1]00[R1]00[A0]00[B0]00[G0]00[R0].
    __m128i pixels0_3_h = _mm_unpackhi_epi8(
        pixels0_3,
        _mm_setzero_si128()); // pixels0_3_h =
                              // 00[A3]00[B3]00[G3]00[R3]00[A2]00[B2]00[G2]00[R2].
    __m128i pixels4_7_l = _mm_unpacklo_epi8(
        pixels4_7,
        _mm_setzero_si128()); // pixels4_7_l =
                              // 00[A5]00[B5]00[G5]00[R5]00[A4]00[B4]00[G4]00[R4].
    __m128i pixels4_7_h = _mm_unpackhi_epi8(
        pixels4_7,
        _mm_setzero_si128()); // pixels4_7_h =
                              // 00[A7]00[B7]00[G7]00[R7]00[A6]00[B6]00[G6]00[R6].

    __m128i y0_3_l = _mm_madd_epi16(pixels0_3_l, RGBScale);
    __m128i y0_3_h = _mm_madd_epi16(pixels0_3_h, RGBScale);
    __m128i y4_7_l = _mm_madd_epi16(pixels4_7_l, RGBScale);
    __m128i y4_7_h = _mm_madd_epi16(pixels4_7_h, RGBScale);
    __m128i y0_3 = _mm_hadd_epi32(y0_3_l, y0_3_h);
    __m128i y4_7 = _mm_hadd_epi32(y4_7_l, y4_7_h);

    y0_3 = _mm_srli_epi32(y0_3, 8);
    y4_7 = _mm_srli_epi32(y4_7, 8);
    y0_3 = _mm_packs_epi32(y0_3, y0_3);
    y4_7 = _mm_packs_epi32(y4_7, y4_7);
    y0_3 = _mm_packus_epi16(y0_3, y0_3);
    y4_7 = _mm_packus_epi16(y4_7, y4_7);

    *pout++ = _mm_cvtsi128_si32(y0_3);
    *pout++ = _mm_cvtsi128_si32(y4_7);

    numPixelsDiv8--;
  } while (numPixelsDiv8);
}

修正后的Emscripten SIMD代码

// 模拟Intel的_mm_hadd_epi32指令:对两个i32x4做水平加法,组合结果
static inline v128_t wasm_i32x4_hadd(v128_t a, v128_t b) {
    // 对a做相邻元素水平加法:[a0+a1, a0+a1, a2+a3, a2+a3]
    v128_t shuf_a = wasm_i32x4_shuffle(a, a, 1, 0, 3, 2);
    v128_t sum_a = wasm_i32x4_add(a, shuf_a);
    // 对b做同样操作
    v128_t shuf_b = wasm_i32x4_shuffle(b, b, 1, 0, 3, 2);
    v128_t sum_b = wasm_i32x4_add(b, shuf_b);
    // 提取有效结果:[a0+a1, a2+a3, b0+b1, b2+b3]
    return wasm_i32x4_shuffle(sum_a, sum_b, 0, 2, 4, 6);
}

static void arVideoLumaRGBAtoL_Emscripten_simd128(uint8_t *__restrict dest,
                                                  uint8_t *__restrict src,
                                                  int32_t numPixels) {
  v128_t RGBScale = wasm_i16x8_make(
      0, B8_CCIR601, G8_CCIR601, R8_CCIR601, 0, B8_CCIR601, G8_CCIR601,
      R8_CCIR601); // RGBScale = [0, B8_CCIR601, G8_CCIR601, R8_CCIR601]
  int numPixelsDiv8 = numPixels >> 3;

  for (int i = 0; i < numPixelsDiv8; i++) {
    v128_t pixels0_3 = wasm_v128_load(src); // Load 16 bytes (4 pixels) from src
    v128_t pixels4_7 =
        wasm_v128_load(src + 16); // Load next 16 bytes (4 pixels) from src

    // 正确替换_mm_unpacklo_epi8和_mm_unpackhi_epi8:将u8扩展为u16,高位补0
    v128_t pixels0_3_l = wasm_u16x8_extend_low_u8x16(pixels0_3);
    v128_t pixels0_3_h = wasm_u16x8_extend_high_u8x16(pixels0_3);
    v128_t pixels4_7_l = wasm_u16x8_extend_low_u8x16(pixels4_7);
    v128_t pixels4_7_h = wasm_u16x8_extend_high_u8x16(pixels4_7);

    // 计算RGB分量乘积和
    v128_t y0_3_l = wasm_i32x4_dot_i16x8(pixels0_3_l, RGBScale);
    v128_t y0_3_h = wasm_i32x4_dot_i16x8(pixels0_3_h, RGBScale);
    v128_t y4_7_l = wasm_i32x4_dot_i16x8(pixels4_7_l, RGBScale);
    v128_t y4_7_h = wasm_i32x4_dot_i16x8(pixels4_7_h, RGBScale);

    // 使用模拟的水平加法替换_mm_hadd_epi32
    v128_t y0_3 = wasm_i32x4_hadd(y0_3_l, y0_3_h);
    v128_t y4_7 = wasm_i32x4_hadd(y4_7_l, y4_7_h);

    // 右移8位等价于除以256
    y0_3 = wasm_i32x4_shr(y0_3, 8);
    y4_7 = wasm_i32x4_shr(y4_7, 8);

    // 将32位结果压缩为16位,再压缩为8位
    y0_3 = wasm_i16x8_narrow_i32x4(y0_3, y0_3);
    y4_7 = wasm_i16x8_narrow_i32x4(y4_7, y4_7);
    y0_3 = wasm_u8x16_narrow_i16x8(y0_3, y0_3);
    y4_7 = wasm_u8x16_narrow_i16x8(y4_7, y4_7);

    // 存储结果,和原代码逻辑一致:仅提取低4字节(4个灰度值)
    uint32_t* pout = (uint32_t*)dest;
    *pout++ = wasm_i32x4_extract_lane(y0_3, 0);
    *pout++ = wasm_i32x4_extract_lane(y4_7, 0);

    src += 32;
    dest += 8;
  }
}

关键修正点说明

  1. 字节扩展修正:用wasm_u16x8_extend_low_u8x16和wasm_u16x8_extend_high_u8x16准确替换_mm_unpacklo_epi8和_mm_unpackhi_epi8,确保每个字节被正确扩展为16位且高位补0,和原逻辑一致。
  2. 水平加法模拟:实现wasm_i32x4_hadd函数,通过两次洗牌+加法模拟_mm_hadd_epi32的水平加法逻辑,确保每个像素的RGB分量乘积和被正确计算。
  3. 结果存储修正:原代码通过_mm_cvtsi128_si32提取低4字节存储,修正为用wasm_i32x4_extract_lane提取对应结果,避免存储多余数据。

内容的提问来源于stack exchange,提问作者kalwalt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 16:27:03