简体   繁体   中英

Bit rotations in WebAssembly

I have a hot bit of code in TypeScript which does a bunch of arithmetic--notably, several 32-bit rotations. Eg,

  let a = t << 16 | t >>> 16; // rotate left by 16 bits
  let b = t << 12 | t >>> 20; // rotate left by 12 bits
  let c = t << 8 | t >>> 24; // rotate left by 8 bits
  let d = t << 7 | t >>> 25; // rotate left by 7 bits

I am trying to port the function containing these operations over to WebAssembly to get better performance, since Lots of Math is exactly what WebAssembly should be good at. Unfortunately, the WebAssembly version ends up giving incorrect results, and I have managed to narrow the error down to precisely these bit rotation operations.

Now, WebAssembly has a convenient i32.rotl opcode which should replace the double shifts and bitwise or all on its own, but in the case of, for example, t = 1634760805 (0x61707865), the TypeScript code gives a result of 2019910000 (0x78656170) when rotating by 16 bits, which simple inspection of the hex digits shows to be correct. Moving just that one operation over to WebAssembly, though, gives a result of 512.

So, I thought maybe there's something I don't understand about the rotl operation, and I tried just directly translating the double-shift-and-or into WebAssembly... and it still produces a result of 512.

My test code that uses the rotl opcode looks like this:

  (func $rot (param $a i32) (param $r i32) (result i32)
    (local $xa i32)

    ;; x[a] = x[a] rotl r;
    (local.set $xa (i32.rotl (local.get $r) (i32.load (local.get $a))))
    (i32.store (local.get $a) (local.get $xa))
    (local.get $xa)
  )

It just reads a 32-bit value out of memory (pointed to by $a), rotates it by $r, stores the result back in linear memory and returns it.

The version that simulates rotl with shifts and bitwise or looks like this:

  (func $rot (param $a i32) (param $r i32) (result i32)
    (local $xa i32)

    ;; x[a] = x[a] rotl r;
    (local.set $xa (i32.load (local.get $a)))
    (local.set $xa (i32.or
      (i32.shl   (local.get $r) (local.get $xa))
      (i32.shr_u (i32.sub (i32.const 32) (local.get $r)) (local.get $xa))))
    (i32.store (local.get $a) (local.get $xa))
    (local.get $xa)
  )

So... any ideas on what's going wrong?

(I have already verified that yes, the correct starting values are in fact where I expect them to be in linear memory, both by directly inspecting the memory buffer on the TypeScript/JavaScript side and by using a really basic WASM debugging function: (func $read (param $a i32) (result i32) (i32.load (local.get $a))) )

Well, I figured it out. It all comes down to this expression:

(i32.rotl (local.get $r) (i32.load (local.get $a)))

Being backwards. Turns out the arguments go in the other order: Changing it to this:

(i32.rotl (i32.load (local.get $a)) (local.get $r))

makes everything work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM