Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions auto_update_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@

for asm in sorted(os.listdir('test')):
if asm.endswith('.asm.js'):
for precise in [1, 0]:
for precise in [0, 1, 2]:
for opts in [1, 0]:
cmd = [os.path.join('bin', 'asm2wasm'), os.path.join('test', asm)]
wasm = asm.replace('.asm.js', '.fromasm')
if not precise:
cmd += ['--imprecise', '--ignore-implicit-traps']
cmd += ['--emit-potential-traps', '--ignore-implicit-traps']
wasm += '.imprecise'
elif precise == 2:
cmd += ['--emit-clamped-potential-traps']
wasm += '.clamp'
if not opts:
wasm += '.no-opts'
if precise:
Expand All @@ -31,8 +34,7 @@
cmd += ['--mem-base=1024']
if 'i64' in asm or 'wasm-only' in asm:
cmd += ['--wasm-only']
print '..', asm, wasm
print ' ', ' '.join(cmd)
print ' '.join(cmd)
actual = run_command(cmd)
with open(os.path.join('test', wasm), 'w') as o: o.write(actual)

Expand Down
7 changes: 5 additions & 2 deletions check.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,16 @@

for asm in tests:
if asm.endswith('.asm.js'):
for precise in [1, 0]:
for precise in [0, 1, 2]:
for opts in [1, 0]:
cmd = ASM2WASM + [os.path.join(options.binaryen_test, asm)]
wasm = asm.replace('.asm.js', '.fromasm')
if not precise:
cmd += ['--imprecise', '--ignore-implicit-traps']
cmd += ['--emit-potential-traps', '--ignore-implicit-traps']
wasm += '.imprecise'
elif precise == 2:
cmd += ['--emit-clamped-potential-traps']
wasm += '.clamp'
if not opts:
wasm += '.no-opts'
if precise:
Expand Down
166 changes: 115 additions & 51 deletions src/asm2wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,13 @@ struct Asm2WasmPreProcessor {
//

class Asm2WasmBuilder {
public:
enum class TrapMode {
Allow,
Clamp,
JS
};

Module& wasm;

MixedArena &allocator;
Expand All @@ -332,7 +339,8 @@ class Asm2WasmBuilder {

Asm2WasmPreProcessor& preprocessor;
bool debug;
bool imprecise;

TrapMode trapMode;
PassOptions passOptions;
bool runOptimizationPasses;
bool wasmOnly;
Expand Down Expand Up @@ -437,13 +445,13 @@ class Asm2WasmBuilder {
}

public:
Asm2WasmBuilder(Module& wasm, Asm2WasmPreProcessor& preprocessor, bool debug, bool imprecise, PassOptions passOptions, bool runOptimizationPasses, bool wasmOnly)
Asm2WasmBuilder(Module& wasm, Asm2WasmPreProcessor& preprocessor, bool debug, TrapMode trapMode, PassOptions passOptions, bool runOptimizationPasses, bool wasmOnly)
: wasm(wasm),
allocator(wasm.allocator),
builder(wasm),
preprocessor(preprocessor),
debug(debug),
imprecise(imprecise),
trapMode(trapMode),
passOptions(passOptions),
runOptimizationPasses(runOptimizationPasses),
wasmOnly(wasmOnly) {}
Expand Down Expand Up @@ -614,9 +622,10 @@ class Asm2WasmBuilder {
return ret;
}

Expression* makePotentiallyTrappingI32Binary(BinaryOp op, Expression* left, Expression* right) {
if (imprecise) return builder.makeBinary(op, left, right);
// we are precise, and the wasm operation might trap if done over 0, so generate a safe call
// Some binary opts might trap, so emit them safely if necessary
Expression* makeTrappingI32Binary(BinaryOp op, Expression* left, Expression* right) {
if (trapMode == TrapMode::Allow) return builder.makeBinary(op, left, right);
// the wasm operation might trap if done over 0, so generate a safe call
auto *call = allocator.alloc<Call>();
switch (op) {
case BinaryOp::RemSInt32: call->target = I32S_REM; break;
Expand Down Expand Up @@ -669,10 +678,10 @@ class Asm2WasmBuilder {
return call;
}

// Some binary opts might trap, so emit them safely if we are precise
Expression* makePotentiallyTrappingI64Binary(BinaryOp op, Expression* left, Expression* right) {
if (imprecise) return builder.makeBinary(op, left, right);
// we are precise, and the wasm operation might trap if done over 0, so generate a safe call
// Some binary opts might trap, so emit them safely if necessary
Expression* makeTrappingI64Binary(BinaryOp op, Expression* left, Expression* right) {
if (trapMode == TrapMode::Allow) return builder.makeBinary(op, left, right);
// wasm operation might trap if done over 0, so generate a safe call
auto *call = allocator.alloc<Call>();
switch (op) {
case BinaryOp::RemSInt64: call->target = I64S_REM; break;
Expand Down Expand Up @@ -725,6 +734,93 @@ class Asm2WasmBuilder {
return call;
}

// Some conversions might trap, so emit them safely if necessary
Expression* makeTrappingFloatToInt(Expression* value) {
if (trapMode == TrapMode::Allow) {
auto ret = allocator.alloc<Unary>();
ret->value = value;
ret->op = ret->value->type == f64 ? TruncSFloat64ToInt32 : TruncSFloat32ToInt32;
ret->type = WasmType::i32;
return ret;
}
// WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must do something
// First, normalize input to f64
auto input = value;
if (input->type == f32) {
auto conv = allocator.alloc<Unary>();
conv->op = PromoteFloat32;
conv->value = input;
conv->type = WasmType::f64;
input = conv;
}
// We can handle this in one of two ways: clamping, which is fast, or JS, which
// is precisely like JS but in order to do that we do a slow ffi
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior is fine for now; I'd be very interested in seeing how wasm build of __fixsfsi or __fixdfdi compares with FFI+JS version.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, worth trying that.

if (trapMode == TrapMode::JS) {
// WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must emulate that
CallImport *ret = allocator.alloc<CallImport>();
ret->target = F64_TO_INT;
ret->operands.push_back(input);
ret->type = i32;
static bool addedImport = false;
if (!addedImport) {
addedImport = true;
auto import = new Import; // f64-to-int = asm2wasm.f64-to-int;
import->name = F64_TO_INT;
import->module = ASM2WASM;
import->base = F64_TO_INT;
import->functionType = ensureFunctionType("id", &wasm)->name;
import->kind = ExternalKind::Function;
wasm.addImport(import);
}
return ret;
}
assert(trapMode == TrapMode::Clamp);
Call *ret = allocator.alloc<Call>();
ret->target = F64_TO_INT;
ret->operands.push_back(input);
ret->type = i32;
static bool added = false;
if (!added) {
added = true;
auto func = new Function;
func->name = ret->target;
func->params.push_back(f64);
func->result = i32;
func->body = builder.makeUnary(TruncSFloat64ToInt32,
builder.makeGetLocal(0, f64)
);
// too small XXX this is different than asm.js, which does frem. here we clamp, which is much simpler/faster, and similar to native builds
func->body = builder.makeIf(
builder.makeBinary(LeFloat64,
builder.makeGetLocal(0, f64),
builder.makeConst(Literal(double(std::numeric_limits<int32_t>::min()) - 1))
),
builder.makeConst(Literal(int32_t(std::numeric_limits<int32_t>::min()))),
func->body
);
// too big XXX see above
func->body = builder.makeIf(
builder.makeBinary(GeFloat64,
builder.makeGetLocal(0, f64),
builder.makeConst(Literal(double(std::numeric_limits<int32_t>::max()) + 1))
),
builder.makeConst(Literal(int32_t(std::numeric_limits<int32_t>::min()))), // NB: min here as well. anything out of range => to the min
func->body
);
// nan
func->body = builder.makeIf(
builder.makeBinary(NeFloat64,
builder.makeGetLocal(0, f64),
builder.makeGetLocal(0, f64)
),
builder.makeConst(Literal(int32_t(std::numeric_limits<int32_t>::min()))), // NB: min here as well. anything invalid => to the min
func->body
);
wasm.addFunction(func);
}
return ret;
}

Expression* truncateToInt32(Expression* value) {
if (value->type == i64) return builder.makeUnary(UnaryOp::WrapInt64, value);
// either i32, or a call_import whose type we don't know yet (but would be legalized to i32 anyhow)
Expand Down Expand Up @@ -1579,10 +1675,10 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
wasm.addImport(import);
}
return call;
} else if (!imprecise && (ret->op == BinaryOp::RemSInt32 || ret->op == BinaryOp::RemUInt32 ||
ret->op == BinaryOp::DivSInt32 || ret->op == BinaryOp::DivUInt32)) {
// we are precise, and the wasm operation might trap if done over 0, so generate a safe call
return makePotentiallyTrappingI32Binary(ret->op, ret->left, ret->right);
} else if (trapMode != TrapMode::Allow &&
(ret->op == BinaryOp::RemSInt32 || ret->op == BinaryOp::RemUInt32 ||
ret->op == BinaryOp::DivSInt32 || ret->op == BinaryOp::DivUInt32)) {
return makeTrappingI32Binary(ret->op, ret->left, ret->right);
}
return ret;
} else if (what == SUB) {
Expand Down Expand Up @@ -1654,39 +1750,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
} else if (ast[1] == B_NOT) {
// ~, might be ~~ as a coercion or just a not
if (ast[2]->isArray(UNARY_PREFIX) && ast[2][1] == B_NOT) {
if (imprecise) {
auto ret = allocator.alloc<Unary>();
ret->value = process(ast[2][2]);
ret->op = ret->value->type == f64 ? TruncSFloat64ToInt32 : TruncSFloat32ToInt32; // imprecise, because this wasm thing might trap, while asm.js never would
ret->type = WasmType::i32;
return ret;
} else {
// WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must emulate that
CallImport *ret = allocator.alloc<CallImport>();
ret->target = F64_TO_INT;
auto input = process(ast[2][2]);
if (input->type == f32) {
auto conv = allocator.alloc<Unary>();
conv->op = PromoteFloat32;
conv->value = input;
conv->type = WasmType::f64;
input = conv;
}
ret->operands.push_back(input);
ret->type = i32;
static bool addedImport = false;
if (!addedImport) {
addedImport = true;
auto import = new Import; // f64-to-int = asm2wasm.f64-to-int;
import->name = F64_TO_INT;
import->module = ASM2WASM;
import->base = F64_TO_INT;
import->functionType = ensureFunctionType("id", &wasm)->name;
import->kind = ExternalKind::Function;
wasm.addImport(import);
}
return ret;
}
return makeTrappingFloatToInt(process(ast[2][2]));
}
// no bitwise unary not, so do xor with -1
auto ret = allocator.alloc<Binary>();
Expand Down Expand Up @@ -1905,10 +1969,10 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
if (name == I64_ADD) return builder.makeBinary(BinaryOp::AddInt64, left, right);
if (name == I64_SUB) return builder.makeBinary(BinaryOp::SubInt64, left, right);
if (name == I64_MUL) return builder.makeBinary(BinaryOp::MulInt64, left, right);
if (name == I64_UDIV) return makePotentiallyTrappingI64Binary(BinaryOp::DivUInt64, left, right);
if (name == I64_SDIV) return makePotentiallyTrappingI64Binary(BinaryOp::DivSInt64, left, right);
if (name == I64_UREM) return makePotentiallyTrappingI64Binary(BinaryOp::RemUInt64, left, right);
if (name == I64_SREM) return makePotentiallyTrappingI64Binary(BinaryOp::RemSInt64, left, right);
if (name == I64_UDIV) return makeTrappingI64Binary(BinaryOp::DivUInt64, left, right);
if (name == I64_SDIV) return makeTrappingI64Binary(BinaryOp::DivSInt64, left, right);
if (name == I64_UREM) return makeTrappingI64Binary(BinaryOp::RemUInt64, left, right);
if (name == I64_SREM) return makeTrappingI64Binary(BinaryOp::RemSInt64, left, right);
if (name == I64_AND) return builder.makeBinary(BinaryOp::AndInt64, left, right);
if (name == I64_OR) return builder.makeBinary(BinaryOp::OrInt64, left, right);
if (name == I64_XOR) return builder.makeBinary(BinaryOp::XorInt64, left, right);
Expand Down
22 changes: 17 additions & 5 deletions src/tools/asm2wasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ using namespace wasm;
int main(int argc, const char *argv[]) {
PassOptions passOptions;
bool runOptimizationPasses = false;
bool imprecise = false;
Asm2WasmBuilder::TrapMode trapMode = Asm2WasmBuilder::TrapMode::JS;
bool wasmOnly = false;
bool debugInfo = false;
std::string symbolMap;
Expand Down Expand Up @@ -76,9 +76,21 @@ int main(int argc, const char *argv[]) {
[](Options *o, const std::string &) {
std::cerr << "--no-opts is deprecated (use -O0, etc.)\n";
})
.add("--imprecise", "-i", "Imprecise optimizations", Options::Arguments::Zero,
[&imprecise](Options *o, const std::string &) {
imprecise = true;
.add("--emit-potential-traps", "-i", "Emit instructions that might trap, like div/rem of 0", Options::Arguments::Zero,
[&trapMode](Options *o, const std::string &) {
trapMode = Asm2WasmBuilder::TrapMode::Allow;
})
.add("--emit-clamped-potential-traps", "-i", "Clamp instructions that might trap, like float => int", Options::Arguments::Zero,
[&trapMode](Options *o, const std::string &) {
trapMode = Asm2WasmBuilder::TrapMode::Clamp;
})
.add("--emit-jsified-potential-traps", "-i", "Avoid instructions that might trap, handling them exactly like JS would", Options::Arguments::Zero,
[&trapMode](Options *o, const std::string &) {
trapMode = Asm2WasmBuilder::TrapMode::JS;
})
.add("--imprecise", "-i", "Imprecise optimizations (old name for --emit-potential-traps)", Options::Arguments::Zero,
[&trapMode](Options *o, const std::string &) {
trapMode = Asm2WasmBuilder::TrapMode::Allow;
})
.add("--wasm-only", "-w", "Input is in WebAssembly-only format, and not actually valid asm.js", Options::Arguments::Zero,
[&wasmOnly](Options *o, const std::string &) {
Expand Down Expand Up @@ -128,7 +140,7 @@ int main(int argc, const char *argv[]) {
if (options.debug) std::cerr << "wasming..." << std::endl;
Module wasm;
wasm.memory.initial = wasm.memory.max = totalMemory / Memory::kPageSize;
Asm2WasmBuilder asm2wasm(wasm, pre, options.debug, imprecise, passOptions, runOptimizationPasses, wasmOnly);
Asm2WasmBuilder asm2wasm(wasm, pre, options.debug, trapMode, passOptions, runOptimizationPasses, wasmOnly);
asm2wasm.processAsm(asmjs);

// import mem init file, if provided
Expand Down
61 changes: 61 additions & 0 deletions test/debugInfo.fromasm.clamp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
(module
(type $FUNCSIG$vii (func (param i32 i32)))
(import "env" "memory" (memory $0 256 256))
(import "env" "table" (table 0 0 anyfunc))
(import "env" "memoryBase" (global $memoryBase i32))
(import "env" "tableBase" (global $tableBase i32))
(data (get_global $memoryBase) "debugInfo.asm.js")
(export "add" (func $add))
(export "ret" (func $ret))
(export "opts" (func $opts))
(func $add (param $0 i32) (param $1 i32) (result i32)
(i32.add
(get_local $1)
(get_local $1)
)
)
(func $ret (param $0 i32) (result i32)
(i32.add
(i32.shl
(get_local $0)
(i32.const 1)
)
(i32.const 1)
)
)
(func $i32s-rem (param $0 i32) (param $1 i32) (result i32)
(if i32
(get_local $1)
(i32.rem_s
(get_local $0)
(get_local $1)
)
(i32.const 0)
)
)
(func $opts (param $0 i32) (param $1 i32) (result i32)
;; even-opted.cpp:2
(set_local $1
(i32.shr_s
(get_local $1)
(tee_local $0
(i32.add
(get_local $0)
(get_local $1)
)
)
)
)
;; even-opted.cpp:3
(set_local $0
(call $i32s-rem
(get_local $0)
(get_local $1)
)
)
(i32.add
(get_local $0)
(get_local $1)
)
)
)
Loading