Created
July 17, 2018 23:16
-
-
Save calebsander/8052bdee96ec711000a95e73bd007f5b to your computer and use it in GitHub Desktop.
base-64 encode and decode in WASM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(module | |
;; Memory layout | |
;; 0 - 63: lookup | |
;; 64 - 186: revLookup | |
;; 187 - : input, followed by output | |
(global $INPUT (export "INPUT") i32 (i32.const 187)) | |
(memory (export "memory") 1) | |
(data (i32.const 0) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") | |
(func (export "init") | |
(local $i i32) | |
;; for (i = 0; i < 64; i++) | |
(loop $initRevLookup | |
(i32.store8 offset=64 ;; revLookup[lookup[i]] = i | |
(i32.load8_u (get_local $i)) | |
(get_local $i) | |
) | |
(br_if $initRevLookup ;; if (++i < 64) continue | |
(i32.lt_u | |
(tee_local $i (i32.add (get_local $i) (i32.const 1))) | |
(i32.const 64) | |
) | |
) | |
) | |
;; revLookup['-'] = 62 | |
(i32.store8 offset=64 (i32.const 0x2d) (i32.const 62)) | |
;; revLookup['_'] = 63 | |
(i32.store8 offset=64 (i32.const 0x5f) (i32.const 63)) | |
) | |
(func $fit (param $end i32) | |
(local $needed i32) | |
(set_local $needed ;; needed = (needed >> 16) + (needed % (1 << 16) ? 1 : 0) - memory.size | |
(i32.sub | |
(i32.add | |
(i32.shr_u (get_local $needed) (i32.const 16)) | |
(select | |
(i32.const 1) | |
(i32.const 0) | |
(i32.and (get_local $needed) (i32.const 0xffff)) | |
) | |
) | |
(current_memory) | |
) | |
) | |
(if (i32.gt_s (get_local $needed) (i32.const 0)) ;; if (needed > 0) memory.grow(needed) | |
(drop (grow_memory (get_local $needed))) | |
) | |
) | |
(func (export "fitInput") (param $len i32) | |
(call $fit (i32.add (get_global $INPUT) (get_local $len))) | |
) | |
(func $getPlaceholdersLen (param $len i32) (result i32) | |
;; input[len - 2] == '=' | |
;; ? 2 | |
;; : (input[len - 1] == '=' ? 1 : 0) | |
(select | |
(i32.const 2) | |
(select | |
(i32.const 1) | |
(i32.const 0) | |
(i32.eq | |
(i32.load8_u offset=186 (get_local $len)) | |
(i32.const 0x3d) | |
) | |
) | |
(i32.eq | |
(i32.load8_u offset=185 (get_local $len)) | |
(i32.const 0x3d) | |
) | |
) | |
) | |
(func (export "getByteArrayOutputLen") (param $len i32) (result i32) | |
;; return getByteArrayOutputLen(len, getPlaceholdersLen(len)) | |
(call $getByteArrayOutputLen | |
(get_local $len) | |
(call $getPlaceholdersLen (get_local $len)) | |
) | |
) | |
(func $getByteArrayOutputLen (param $len i32) (param $placeholdersLen i32) (result i32) | |
(i32.sub ;; return len * 3 / 4 - placeholdersLen | |
(i32.mul | |
(i32.shr_u (get_local $len) (i32.const 2)) | |
(i32.const 3) | |
) | |
(get_local $placeholdersLen) | |
) | |
) | |
(func (export "toByteArray") (param $len i32) (result i32) | |
(local $placeholdersLen i32) | |
(local $output i32) | |
(local $completeLen i32) | |
(local $i i32) | |
(local $curByte i32) | |
(local $tmp i32) | |
;; placeholdersLen = getPlaceholdersLen(len) | |
(set_local $placeholdersLen (call $getPlaceholdersLen (get_local $len))) | |
(set_local $output ;; output = curByte = INPUT + len | |
(tee_local $curByte (i32.add (get_global $INPUT) (get_local $len))) | |
) | |
(call $fit ;; fit(output + getByteArrayOutputLen(len, placeholdersLen)) | |
(i32.add | |
(get_local $output) | |
(call $getByteArrayOutputLen (get_local $len) (get_local $placeholdersLen)) | |
) | |
) | |
(set_local $completeLen ;; completeLen = validLen - (placeholdersLen ? 4 : 0) | |
(i32.sub | |
;; validLen (== len - placeholdersLen) | |
(i32.sub (get_local $len) (get_local $placeholdersLen)) | |
(select (i32.const 4) (i32.const 0) (get_local $placeholdersLen)) | |
) | |
) | |
;; for (i = 0; i < completeLen; i += 4) | |
(if (i32.gt_s (get_local $completeLen) (i32.const 0)) | |
(loop $setFullBlocks | |
;; tmp = revLookup[INPUT[i ]] << 18 | | |
;; revLookup[INPUT[i + 1]] << 12 | | |
;; revLookup[INPUT[i + 2]] << 6 | | |
;; revLookup[INPUT[i + 3]] | |
(set_local $tmp | |
(i32.or | |
(i32.or | |
(i32.shl | |
(i32.load8_u offset=64 | |
(i32.load8_u offset=187 (get_local $i)) | |
) | |
(i32.const 18) | |
) | |
(i32.shl | |
(i32.load8_u offset=64 | |
(i32.load8_u offset=188 (get_local $i)) | |
) | |
(i32.const 12) | |
) | |
) | |
(i32.or | |
(i32.shl | |
(i32.load8_u offset=64 | |
(i32.load8_u offset=189 (get_local $i)) | |
) | |
(i32.const 6) | |
) | |
(i32.load8_u offset=64 | |
(i32.load8_u offset=190 (get_local $i)) | |
) | |
) | |
) | |
) | |
;; output[curByte ] = tmp >>> 16 | |
;; output[curByte + 1] = tmp >>> 8 & 0xFF | |
(i32.store16 | |
(get_local $curByte) | |
(i32.or | |
(i32.shr_u (get_local $tmp) (i32.const 16)) | |
(i32.and (get_local $tmp) (i32.const 0xff00)) | |
) | |
) | |
;; output[curByte + 2] = tmp & 0xFF | |
(i32.store8 offset=2 (get_local $curByte) (get_local $tmp)) | |
;; curByte += 3 | |
(set_local $curByte (i32.add (get_local $curByte) (i32.const 3))) | |
(br_if $setFullBlocks ;; if ((i += 4) < completeLen) continue | |
(i32.lt_u | |
(tee_local $i (i32.add (get_local $i) (i32.const 4))) | |
(get_local $completeLen) | |
) | |
) | |
) | |
) | |
;; if (!placeholdersLen) return output | |
(if (i32.eqz (get_local $placeholdersLen)) (return (get_local $output))) | |
(if (i32.eq (get_local $placeholdersLen) (i32.const 2)) | |
;; if (placeholdersLen === 2) | |
(then | |
;; output[curByte] = revLookup[INPUT[i ]] << 2 | | |
;; revLookup[INPUT[i + 1]] >>> 4 | |
(i32.store8 | |
(get_local $curByte) | |
(i32.or | |
(i32.shl | |
(i32.load8_u offset=64 | |
(i32.load8_u offset=187 (get_local $i)) | |
) | |
(i32.const 2) | |
) | |
(i32.shr_u | |
(i32.load8_u offset=64 | |
(i32.load8_u offset=188 (get_local $i)) | |
) | |
(i32.const 4) | |
) | |
) | |
) | |
) | |
;; if (placeholdersLen === 1) | |
(else | |
;; tmp = revLookup[INPUT[i]] << 10 | | |
;; revLookup[INPUT[i + 1]] << 4 | | |
;; revLookup[INPUT[i + 2]] >>> 2 | |
(set_local $tmp | |
(i32.or | |
(i32.shl | |
(i32.load8_u offset=64 | |
(i32.load8_u offset=187 (get_local $i)) | |
) | |
(i32.const 10) | |
) | |
(i32.or | |
(i32.shl | |
(i32.load8_u offset=64 | |
(i32.load8_u offset=188 (get_local $i)) | |
) | |
(i32.const 4) | |
) | |
(i32.shr_u | |
(i32.load8_u offset=64 | |
(i32.load8_u offset=189 (get_local $i)) | |
) | |
(i32.const 2) | |
) | |
) | |
) | |
) | |
;; output[curByte ] = tmp >>> 8 | |
;; output[curByte + 1] = tmp & 0xFF | |
(i32.store16 | |
(get_local $curByte) | |
(i32.or | |
(i32.shr_u (get_local $tmp) (i32.const 8)) | |
(i32.shl (get_local $tmp) (i32.const 8)) | |
) | |
) | |
) | |
) | |
(get_local $output) ;; return output | |
) | |
(func $getExtraBytes (param $len i32) (result i32) | |
;; return len % 3 | |
(i32.rem_u (get_local $len) (i32.const 3)) | |
) | |
(func $getTripletLength (param $len i32) (param $extraBytes i32) (result i32) | |
;; return len - extraBytes | |
(i32.sub (get_local $len) (get_local $extraBytes)) | |
) | |
(func $getBase64OutputLen (param $len i32) (param $tripletLength i32) (result i32) | |
(i32.shl ;; return (tripletLength / 3 + (tripleLength < len)) * 4 | |
(i32.add | |
(i32.div_u (get_local $tripletLength) (i32.const 3)) | |
(i32.lt_u (get_local $tripletLength) (get_local $len)) | |
) | |
(i32.const 2) | |
) | |
) | |
(func (export "getBase64OutputLen") (param $len i32) (result i32) | |
;; return getBase64OutputLen(len, getTripletLength(len, getExtraBytes(len))) | |
(call $getBase64OutputLen | |
(get_local $len) | |
(call $getTripletLength | |
(get_local $len) | |
(call $getExtraBytes (get_local $len)) | |
) | |
) | |
) | |
(func (export "fromByteArray") (param $len i32) (result i32) | |
(local $extraBytes i32) | |
(local $tripletLength i32) | |
(local $output i32) | |
(local $i i32) | |
(local $curByte i32) | |
(local $tmp i32) | |
;; extraBytes = getExtraBytes(len) | |
(set_local $extraBytes (call $getExtraBytes (get_local $len))) | |
;; tripleLength = getTripletLength(len, extraBytes) | |
(set_local $tripletLength | |
(call $getTripletLength | |
(get_local $len) | |
(get_local $extraBytes) | |
) | |
) | |
;; output = INPUT + len | |
(set_local $output (i32.add (get_global $INPUT) (get_local $len))) | |
;; if (output % 4) output += 4 - (output % 4) | |
(set_local $output | |
(i32.add | |
(i32.and (get_local $output) (i32.const -4)) | |
(select | |
(i32.const 4) | |
(i32.const 0) | |
(i32.and (get_local $output) (i32.const 3)) | |
) | |
) | |
) | |
;; fit(output + getBase64OutputLen(len, tripletLength)) | |
(call $fit | |
(i32.add | |
(get_local $output) | |
(call $getBase64OutputLen (get_local $len) (get_local $tripletLength)) | |
) | |
) | |
;; curByte = output | |
(set_local $curByte (get_local $output)) | |
;; for (i = 0; i < tripletLength; i += 3) | |
(if (get_local $tripletLength) | |
(loop $setFullBlocks | |
;; tmp = INPUT[i] << 16 | INPUT[i + 1] << 8 | INPUT[i + 2] | |
(set_local $tmp | |
(i32.or | |
(i32.shl | |
(i32.load8_u offset=187 (get_local $i)) | |
(i32.const 16) | |
) | |
(i32.or | |
(i32.shl | |
(i32.load8_u offset=188 (get_local $i)) | |
(i32.const 8) | |
) | |
(i32.load8_u offset=189 (get_local $i)) | |
) | |
) | |
) | |
;; output[curByte ] = lookup[tmp >>> 18 & 0x3f] | |
;; output[curByte + 1] = lookup[tmp >>> 12 & 0x3f] | |
;; output[curByte + 2] = lookup[tmp >>> 6 & 0x3f] | |
;; output[curByte + 3] = lookup[tmp & 0x3f] | |
(i32.store align=2 | |
(get_local $curByte) | |
(i32.or | |
(i32.or | |
(i32.load8_u | |
(i32.and | |
(i32.shr_u (get_local $tmp) (i32.const 18)) | |
(i32.const 0x3f) | |
) | |
) | |
(i32.shl | |
(i32.load8_u | |
(i32.and | |
(i32.shr_u (get_local $tmp) (i32.const 12)) | |
(i32.const 0x3f) | |
) | |
) | |
(i32.const 8) | |
) | |
) | |
(i32.or | |
(i32.shl | |
(i32.load8_u | |
(i32.and | |
(i32.shr_u (get_local $tmp) (i32.const 6)) | |
(i32.const 0x3f) | |
) | |
) | |
(i32.const 16) | |
) | |
(i32.shl | |
(i32.load8_u | |
(i32.and (get_local $tmp) (i32.const 0x3f)) | |
) | |
(i32.const 24) | |
) | |
) | |
) | |
) | |
;; curByte += 4 | |
(set_local $curByte (i32.add (get_local $curByte) (i32.const 4))) | |
(br_if $setFullBlocks ;; if ((i += 3) < tripletLength) continue | |
(i32.lt_u | |
(tee_local $i (i32.add (get_local $i) (i32.const 3))) | |
(get_local $tripletLength) | |
) | |
) | |
) | |
) | |
;; if (!extraBytes) return output | |
(if (i32.eqz (get_local $extraBytes)) (return (get_local $output))) | |
(if (i32.eq (get_local $extraBytes) (i32.const 1)) | |
;; if (extraBytes === 1) | |
(then | |
;; tmp = INPUT[i] | |
(set_local $tmp (i32.load8_u offset=187 (get_local $i))) | |
;; output[curByte ] = lookup[tmp >>> 2 ] | |
;; output[curByte + 1] = lookup[tmp << 4 & 0x3f] | |
;; output[curByte + 2] = output[curByte + 3] = '=' | |
(i32.store align=2 | |
(get_local $curByte) | |
(i32.or | |
(i32.or | |
(i32.load8_u (i32.shr_u (get_local $tmp) (i32.const 2))) | |
(i32.shl | |
(i32.load8_u | |
(i32.and | |
(i32.shl (get_local $tmp) (i32.const 4)) | |
(i32.const 0x3f) | |
) | |
) | |
(i32.const 8) | |
) | |
) | |
(i32.const 0x3d3d0000) ;; '==' | |
) | |
) | |
) | |
;; if (extraBytes === 2) | |
(else | |
(set_local $tmp ;; tmp = INPUT[i] << 8 | INPUT[i + 1] | |
(i32.or | |
(i32.shl | |
(i32.load8_u offset=187 (get_local $i)) | |
(i32.const 8) | |
) | |
(i32.load8_u offset=188 (get_local $i)) | |
) | |
) | |
;; output[curByte ] = lookup[tmp >>> 10 ] | |
;; output[curByte + 1] = lookup[tmp >>> 4 & 0x3f] | |
;; output[curByte + 2] = lookup[tmp << 2 & 0x3f] | |
;; output[curByte + 3] = '=' | |
(i32.store align=2 | |
(get_local $curByte) | |
(i32.or | |
(i32.or | |
(i32.load8_u (i32.shr_u (get_local $tmp) (i32.const 10))) | |
(i32.shl | |
(i32.load8_u | |
(i32.and | |
(i32.shr_u (get_local $tmp) (i32.const 4)) | |
(i32.const 0x3f) | |
) | |
) | |
(i32.const 8) | |
) | |
) | |
(i32.or | |
(i32.shl | |
(i32.load8_u | |
(i32.and | |
(i32.shl (get_local $tmp) (i32.const 2)) | |
(i32.const 0x3f) | |
) | |
) | |
(i32.const 16) | |
) | |
(i32.const 0x3d000000) ;; '=' | |
) | |
) | |
) | |
) | |
) | |
(get_local $output) ;; return output | |
) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment