summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2018-05-13 16:14:36 -0500
committerAlon Zakai <alonzakai@gmail.com>2018-05-13 14:14:36 -0700
commit7ae28f8edba5b23223e4db4dc281b8bf77ce88ea (patch)
treed51fe9f94091b56975caba194022433f8246a834
parent4282c9478c7cb435401e0eed1a464c1d7a2db213 (diff)
downloadbinaryen-7ae28f8edba5b23223e4db4dc281b8bf77ce88ea.tar.gz
binaryen-7ae28f8edba5b23223e4db4dc281b8bf77ce88ea.tar.bz2
binaryen-7ae28f8edba5b23223e4db4dc281b8bf77ce88ea.zip
wasm2asm: Implement reinterpretation instructions (#1547)
As mentioned in #1458 a naive implementation of these instructions is to round trip the value through address 0 in linear memory. Also pointed out in #1458 this isn't necessarily valid for all languages. For now, though, languages like Rust, C, and C++ would likely be horribly broken if valid data could be stored at low addresses, so this commit goes ahead and adds an implementation of the reinterpretation instructions by traveling data through address 0. This will likely need an update if a language comes a long which can validly store data in the first 8 bytes of linear memory, but it seems like that won't happen in the near future. Closes #1458
-rw-r--r--src/passes/I64ToI32Lowering.cpp37
-rw-r--r--src/wasm2asm.h24
-rw-r--r--test/wasm2asm/reinterpret.2asm.js77
-rw-r--r--test/wasm2asm/reinterpret.wast24
4 files changed, 159 insertions, 3 deletions
diff --git a/src/passes/I64ToI32Lowering.cpp b/src/passes/I64ToI32Lowering.cpp
index 759e5e265..657df4896 100644
--- a/src/passes/I64ToI32Lowering.cpp
+++ b/src/passes/I64ToI32Lowering.cpp
@@ -587,6 +587,37 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
replaceCurrent(curr->value);
}
+ void lowerReinterpretFloat64(Unary* curr) {
+ // Assume that the wasm file assumes the address 0 is invalid and roundtrip
+ // our f64 through memory at address 0
+ Expression* zero = builder->makeConst(Literal(int32_t(0)));
+ TempVar highBits = getTemp();
+ Block *result = builder->blockify(
+ builder->makeStore(8, 0, 8, zero, curr->value, f64),
+ builder->makeSetLocal(
+ highBits,
+ builder->makeLoad(4, true, 4, 4, zero, i32)
+ ),
+ builder->makeLoad(4, true, 0, 4, zero, i32)
+ );
+ setOutParam(result, std::move(highBits));
+ replaceCurrent(result);
+ }
+
+ void lowerReinterpretInt64(Unary* curr) {
+ // Assume that the wasm file assumes the address 0 is invalid and roundtrip
+ // our i64 through memory at address 0
+ TempVar highBits = fetchOutParam(curr->value);
+ TempVar lowBits = getTemp();
+ Expression* zero = builder->makeConst(Literal(int32_t(0)));
+ Block *result = builder->blockify(
+ builder->makeStore(4, 0, 4, zero, curr->value, i32),
+ builder->makeStore(4, 4, 4, zero, builder->makeGetLocal(highBits, i32), i32),
+ builder->makeLoad(8, true, 0, 8, zero, f64)
+ );
+ replaceCurrent(result);
+ }
+
void lowerPopcnt64(Unary* curr) {
TempVar highBits = fetchOutParam(curr->value);
TempVar lowBits = getTemp();
@@ -701,7 +732,7 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
replaceCurrent(curr->value);
return;
}
- assert(hasOutParam(curr->value) || curr->type == i64);
+ assert(hasOutParam(curr->value) || curr->type == i64 || curr->type == f64);
switch (curr->op) {
case ClzInt64:
case CtzInt64: lowerCountZeros(curr); break;
@@ -710,16 +741,16 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
case ExtendSInt32: lowerExtendSInt32(curr); break;
case ExtendUInt32: lowerExtendUInt32(curr); break;
case WrapInt64: lowerWrapInt64(curr); break;
+ case ReinterpretFloat64: lowerReinterpretFloat64(curr); break;
+ case ReinterpretInt64: lowerReinterpretInt64(curr); break;
case TruncSFloat32ToInt64:
case TruncUFloat32ToInt64:
case TruncSFloat64ToInt64:
case TruncUFloat64ToInt64:
- case ReinterpretFloat64:
case ConvertSInt64ToFloat32:
case ConvertSInt64ToFloat64:
case ConvertUInt64ToFloat32:
case ConvertUInt64ToFloat64:
- case ReinterpretInt64:
default:
std::cerr << "Unhandled unary operator: " << curr->op << std::endl;
abort();
diff --git a/src/wasm2asm.h b/src/wasm2asm.h
index dce91dacb..ea8fd8142 100644
--- a/src/wasm2asm.h
+++ b/src/wasm2asm.h
@@ -1376,6 +1376,19 @@ Ref Wasm2AsmBuilder::processFunctionBody(Function* func, IString result) {
makeAsmCoercion(visit(curr->value,
EXPRESSION_RESULT), ASM_INT), EQ,
makeAsmCoercion(ValueBuilder::makeInt(0), ASM_INT));
+ case ReinterpretFloat32: {
+ // Naively assume that the address 0 and the next 4 bytes are
+ // permanently unused by the source program, which is definitely
+ // true for languages like C/C++/Rust
+ Ref zero = ValueBuilder::makeInt(0);
+ Ref ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32), zero);
+ Ref value = visit(curr->value, EXPRESSION_RESULT);
+ Ref store = ValueBuilder::makeBinary(ret, SET, value);
+ return ValueBuilder::makeSeq(
+ store,
+ ValueBuilder::makeSub(ValueBuilder::makeName(HEAP32), zero)
+ );
+ }
default: {
std::cerr << "Unhandled unary i32 operator: " << curr
<< std::endl;
@@ -1442,6 +1455,17 @@ Ref Wasm2AsmBuilder::processFunctionBody(Function* func, IString result) {
case DemoteFloat64:
return makeAsmCoercion(visit(curr->value, EXPRESSION_RESULT),
ASM_FLOAT);
+ case ReinterpretInt32: {
+ // Like above, assume address 0 is unused.
+ Ref zero = ValueBuilder::makeInt(0);
+ Ref ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP32), zero);
+ Ref value = visit(curr->value, EXPRESSION_RESULT);
+ Ref store = ValueBuilder::makeBinary(ret, SET, value);
+ return ValueBuilder::makeSeq(
+ store,
+ ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32), zero)
+ );
+ }
// TODO: more complex unary conversions
default:
std::cerr << "Unhandled unary float operator: " << curr
diff --git a/test/wasm2asm/reinterpret.2asm.js b/test/wasm2asm/reinterpret.2asm.js
new file mode 100644
index 000000000..3d770efe1
--- /dev/null
+++ b/test/wasm2asm/reinterpret.2asm.js
@@ -0,0 +1,77 @@
+function asmFunc(global, env, buffer) {
+ "use asm";
+ var HEAP8 = new global.Int8Array(buffer);
+ var HEAP16 = new global.Int16Array(buffer);
+ var HEAP32 = new global.Int32Array(buffer);
+ var HEAPU8 = new global.Uint8Array(buffer);
+ var HEAPU16 = new global.Uint16Array(buffer);
+ var HEAPU32 = new global.Uint32Array(buffer);
+ var HEAPF32 = new global.Float32Array(buffer);
+ var HEAPF64 = new global.Float64Array(buffer);
+ var Math_imul = global.Math.imul;
+ var Math_fround = global.Math.fround;
+ var Math_abs = global.Math.abs;
+ var Math_clz32 = global.Math.clz32;
+ var Math_min = global.Math.min;
+ var Math_max = global.Math.max;
+ var i64toi32_i32$HIGH_BITS = 0;
+ function dummy() {
+
+ }
+
+ function $1($0) {
+ $0 = $0 | 0;
+ return ((HEAPF32[0] = (HEAP32[0] = $0, HEAPF32[0]), HEAP32[0]) | 0) == ($0 | 0) | 0;
+ }
+
+ function $2($0, $0$hi) {
+ $0 = $0 | 0;
+ $0$hi = $0$hi | 0;
+ HEAP32[0 >> 2] = $0;
+ HEAP32[(0 + 4 | 0) >> 2] = $0$hi;
+ HEAPF64[0 >> 3] = +HEAPF64[0 >> 3];
+ return (HEAP32[0 >> 2] | 0 | 0) == ($0 | 0) & (HEAP32[(0 + 4 | 0) >> 2] | 0 | 0) == ($0$hi | 0) | 0 | 0;
+ }
+
+ function __wasm_ctz_i32(x) {
+ x = x | 0;
+ var $1 = 0;
+ if ((x | 0) == (0 | 0)) $1 = 32; else $1 = 31 - Math_clz32(x ^ (x - 1 | 0) | 0) | 0;
+ return $1 | 0;
+ }
+
+ function __wasm_popcnt_i32(x) {
+ x = x | 0;
+ var count = 0, $2 = 0;
+ count = 0;
+ b : {
+ l : do {
+ $2 = count;
+ if ((x | 0) == (0 | 0)) break b;
+ x = x & (x - 1 | 0) | 0;
+ count = count + 1 | 0;
+ continue l;
+ break l;
+ } while (1);
+ };
+ return $2 | 0;
+ }
+
+ function __wasm_rotl_i32(x, k) {
+ x = x | 0;
+ k = k | 0;
+ return ((4294967295 >>> (k & 31 | 0) | 0) & x | 0) << (k & 31 | 0) | 0 | (((4294967295 << (32 - (k & 31 | 0) | 0) | 0) & x | 0) >>> (32 - (k & 31 | 0) | 0) | 0) | 0 | 0;
+ }
+
+ function __wasm_rotr_i32(x, k) {
+ x = x | 0;
+ k = k | 0;
+ return ((4294967295 << (k & 31 | 0) | 0) & x | 0) >>> (k & 31 | 0) | 0 | (((4294967295 >>> (32 - (k & 31 | 0) | 0) | 0) & x | 0) << (32 - (k & 31 | 0) | 0) | 0) | 0 | 0;
+ }
+
+ return {
+ i32_roundtrip: $1,
+ i64_roundtrip: $2
+ };
+}
+
diff --git a/test/wasm2asm/reinterpret.wast b/test/wasm2asm/reinterpret.wast
new file mode 100644
index 000000000..816fb90a3
--- /dev/null
+++ b/test/wasm2asm/reinterpret.wast
@@ -0,0 +1,24 @@
+(module
+ (func $dummy)
+
+ (func (export "i32_roundtrip") (param $0 i32) (result i32)
+ (i32.eq (i32.reinterpret (f32.reinterpret (get_local $0))) (get_local $0)))
+ (func (export "i64_roundtrip") (param $0 i64) (result i32)
+ (i64.eq (i64.reinterpret (f64.reinterpret (get_local $0))) (get_local $0)))
+)
+
+(assert_return (invoke "i32_roundtrip" (i32.const 0))
+ (i32.const 1))
+(assert_return (invoke "i32_roundtrip" (i32.const 1))
+ (i32.const 1))
+(assert_return (invoke "i32_roundtrip" (i32.const 100))
+ (i32.const 1))
+(assert_return (invoke "i32_roundtrip" (i32.const 10000))
+ (i32.const 1))
+
+(assert_return (invoke "i64_roundtrip" (i32.const 0) (i32.const 0))
+ (i32.const 1))
+(assert_return (invoke "i64_roundtrip" (i32.const 1) (i32.const 0))
+ (i32.const 1))
+(assert_return (invoke "i64_roundtrip" (i32.const 0) (i32.const 1))
+ (i32.const 1))