Skip to content

Commit 5a07a93

Browse files
authored
Merge pull request #1116 from WebAssembly/fuzz
Fuzz fixes
2 parents 76751bf + 44154e3 commit 5a07a93

19 files changed

Lines changed: 546 additions & 52 deletions

auto_update_tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@
9393
print '..', t
9494
binary = '.wasm' in t
9595
passname = os.path.basename(t).replace('.wast', '').replace('.wasm', '')
96-
opts = ['-' + passname] if passname.startswith('O') else ['--' + p for p in passname.split('_')]
96+
opts = [('--' + p if not p.startswith('O') else '-' + p) for p in passname.split('_')]
9797
t = os.path.join('test', 'passes', t)
9898
actual = ''
9999
for module, asserts in split_wast(t):

check.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
print '..', t
7979
binary = '.wasm' in t
8080
passname = os.path.basename(t).replace('.wast', '').replace('.wasm', '')
81-
opts = ['-' + passname] if passname.startswith('O') else ['--' + p for p in passname.split('_')]
81+
opts = [('--' + p if not p.startswith('O') else '-' + p) for p in passname.split('_')]
8282
t = os.path.join(options.binaryen_test, 'passes', t)
8383
actual = ''
8484
for module, asserts in split_wast(t):

src/ast/bits.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,26 @@ struct Bits {
3939
// this is indeed a mask
4040
return 32 - CountLeadingZeroes(mask);
4141
}
42+
43+
// gets the number of effective shifts a shift operation does. In
44+
// wasm, only 5 bits matter for 32-bit shifts, and 6 for 64.
45+
static Index getEffectiveShifts(Index amount, WasmType type) {
46+
if (type == i32) {
47+
return amount & 31;
48+
} else if (type == i64) {
49+
return amount & 63;
50+
}
51+
WASM_UNREACHABLE();
52+
}
53+
54+
static Index getEffectiveShifts(Const* amount) {
55+
if (amount->type == i32) {
56+
return getEffectiveShifts(amount->value.geti32(), i32);
57+
} else if (amount->type == i64) {
58+
return getEffectiveShifts(amount->value.geti64(), i64);
59+
}
60+
WASM_UNREACHABLE();
61+
}
4262
};
4363

4464
} // namespace wasm

src/ast/literal-utils.h

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,31 @@ namespace wasm {
2323

2424
namespace LiteralUtils {
2525

26-
inline Expression* makeZero(WasmType type, Module& wasm) {
27-
Literal value;
26+
inline Literal makeLiteralFromInt32(int32_t x, WasmType type) {
2827
switch (type) {
29-
case i32: value = Literal(int32_t(0)); break;
30-
case i64: value = Literal(int64_t(0)); break;
31-
case f32: value = Literal(float(0)); break;
32-
case f64: value = Literal(double(0)); break;
28+
case i32: return Literal(int32_t(x)); break;
29+
case i64: return Literal(int64_t(x)); break;
30+
case f32: return Literal(float(x)); break;
31+
case f64: return Literal(double(x)); break;
3332
default: WASM_UNREACHABLE();
3433
}
34+
}
35+
36+
inline Literal makeLiteralZero(WasmType type) {
37+
return makeLiteralFromInt32(0, type);
38+
}
39+
40+
inline Expression* makeFromInt32(int32_t x, WasmType type, Module& wasm) {
3541
auto* ret = wasm.allocator.alloc<Const>();
36-
ret->value = value;
37-
ret->type = value.type;
42+
ret->value = makeLiteralFromInt32(x, type);
43+
ret->type = type;
3844
return ret;
3945
}
4046

47+
inline Expression* makeZero(WasmType type, Module& wasm) {
48+
return makeFromInt32(0, type, wasm);
49+
}
50+
4151
} // namespace LiteralUtils
4252

4353
} // namespace wasm

src/passes/MergeBlocks.cpp

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,23 @@ namespace wasm {
7373
// For example, if there is a switch targeting us, we can't do it - we can't remove the value from other targets
7474
struct ProblemFinder : public ControlFlowWalker<ProblemFinder> {
7575
Name origin;
76-
bool foundSwitch = false;
76+
bool foundProblem = false;
7777
// count br_ifs, and dropped br_ifs. if they don't match, then a br_if flow value is used, and we can't drop it
7878
Index brIfs = 0;
7979
Index droppedBrIfs = 0;
80+
PassOptions& passOptions;
81+
82+
ProblemFinder(PassOptions& passOptions) : passOptions(passOptions) {}
8083

8184
void visitBreak(Break* curr) {
82-
if (curr->name == origin && curr->condition) {
83-
brIfs++;
85+
if (curr->name == origin) {
86+
if (curr->condition) {
87+
brIfs++;
88+
}
89+
// if the value has side effects, we can't remove it
90+
if (EffectAnalyzer(passOptions, curr->value).hasSideEffects()) {
91+
foundProblem = true;
92+
}
8493
}
8594
}
8695

@@ -94,27 +103,30 @@ struct ProblemFinder : public ControlFlowWalker<ProblemFinder> {
94103

95104
void visitSwitch(Switch* curr) {
96105
if (curr->default_ == origin) {
97-
foundSwitch = true;
106+
foundProblem = true;
98107
return;
99108
}
100109
for (auto& target : curr->targets) {
101110
if (target == origin) {
102-
foundSwitch = true;
111+
foundProblem = true;
103112
return;
104113
}
105114
}
106115
}
107116

108117
bool found() {
109118
assert(brIfs >= droppedBrIfs);
110-
return foundSwitch || brIfs > droppedBrIfs;
119+
return foundProblem || brIfs > droppedBrIfs;
111120
}
112121
};
113122

114123
// Drops values from breaks to an origin.
115124
// While doing so it can create new blocks, so optimize blocks as well.
116125
struct BreakValueDropper : public ControlFlowWalker<BreakValueDropper> {
117126
Name origin;
127+
PassOptions& passOptions;
128+
129+
BreakValueDropper(PassOptions& passOptions) : passOptions(passOptions) {}
118130

119131
void visitBlock(Block* curr);
120132

@@ -143,7 +155,7 @@ struct BreakValueDropper : public ControlFlowWalker<BreakValueDropper> {
143155
};
144156

145157
// core block optimizer routine
146-
static void optimizeBlock(Block* curr, Module* module) {
158+
static void optimizeBlock(Block* curr, Module* module, PassOptions& passOptions) {
147159
bool more = true;
148160
bool changed = false;
149161
while (more) {
@@ -159,14 +171,14 @@ static void optimizeBlock(Block* curr, Module* module) {
159171
if (child->name.is()) {
160172
Expression* expression = child;
161173
// check if it's ok to remove the value from all breaks to us
162-
ProblemFinder finder;
174+
ProblemFinder finder(passOptions);
163175
finder.origin = child->name;
164176
finder.walk(expression);
165177
if (finder.found()) {
166178
child = nullptr;
167179
} else {
168180
// fix up breaks
169-
BreakValueDropper fixer;
181+
BreakValueDropper fixer(passOptions);
170182
fixer.origin = child->name;
171183
fixer.setModule(module);
172184
fixer.walk(expression);
@@ -217,7 +229,7 @@ static void optimizeBlock(Block* curr, Module* module) {
217229
}
218230

219231
void BreakValueDropper::visitBlock(Block* curr) {
220-
optimizeBlock(curr, getModule());
232+
optimizeBlock(curr, getModule(), passOptions);
221233
}
222234

223235
struct MergeBlocks : public WalkerPass<PostWalker<MergeBlocks>> {
@@ -226,7 +238,7 @@ struct MergeBlocks : public WalkerPass<PostWalker<MergeBlocks>> {
226238
Pass* create() override { return new MergeBlocks; }
227239

228240
void visitBlock(Block *curr) {
229-
optimizeBlock(curr, getModule());
241+
optimizeBlock(curr, getModule(), getPassOptions());
230242
}
231243

232244
Block* optimize(Expression* curr, Expression*& child, Block* outer = nullptr, Expression** dependency1 = nullptr, Expression** dependency2 = nullptr) {

src/passes/OptimizeInstructions.cpp

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <ast/effects.h>
3030
#include <ast/manipulation.h>
3131
#include <ast/properties.h>
32+
#include <ast/literal-utils.h>
3233

3334
namespace wasm {
3435

@@ -188,14 +189,14 @@ Index getMaxBits(Expression* curr, LocalInfoProvider* localInfoProvider) {
188189
case OrInt32: case XorInt32: return std::max(getMaxBits(binary->left, localInfoProvider), getMaxBits(binary->right, localInfoProvider));
189190
case ShlInt32: {
190191
if (auto* shifts = binary->right->dynCast<Const>()) {
191-
return std::min(Index(32), getMaxBits(binary->left, localInfoProvider) + shifts->value.geti32());
192+
return std::min(Index(32), getMaxBits(binary->left, localInfoProvider) + Bits::getEffectiveShifts(shifts));
192193
}
193194
return 32;
194195
}
195196
case ShrUInt32: {
196197
if (auto* shift = binary->right->dynCast<Const>()) {
197198
auto maxBits = getMaxBits(binary->left, localInfoProvider);
198-
auto shifts = std::min(Index(shift->value.geti32()), maxBits); // can ignore more shifts than zero us out
199+
auto shifts = std::min(Index(Bits::getEffectiveShifts(shift)), maxBits); // can ignore more shifts than zero us out
199200
return std::max(Index(0), maxBits - shifts);
200201
}
201202
return 32;
@@ -204,7 +205,7 @@ Index getMaxBits(Expression* curr, LocalInfoProvider* localInfoProvider) {
204205
if (auto* shift = binary->right->dynCast<Const>()) {
205206
auto maxBits = getMaxBits(binary->left, localInfoProvider);
206207
if (maxBits == 32) return 32;
207-
auto shifts = std::min(Index(shift->value.geti32()), maxBits); // can ignore more shifts than zero us out
208+
auto shifts = std::min(Index(Bits::getEffectiveShifts(shift)), maxBits); // can ignore more shifts than zero us out
208209
return std::max(Index(0), maxBits - shifts);
209210
}
210211
return 32;
@@ -533,9 +534,16 @@ struct OptimizeInstructions : public WalkerPass<PostWalker<OptimizeInstructions,
533534
} else if (left->op == OrInt32) {
534535
leftRight->value = leftRight->value.or_(right->value);
535536
return left;
536-
} else if (left->op == ShlInt32 || left->op == ShrUInt32 || left->op == ShrSInt32) {
537-
leftRight->value = leftRight->value.add(right->value);
538-
return left;
537+
} else if (left->op == ShlInt32 || left->op == ShrUInt32 || left->op == ShrSInt32 ||
538+
left->op == ShlInt64 || left->op == ShrUInt64 || left->op == ShrSInt64) {
539+
// shifts only use an effective amount from the constant, so adding must
540+
// be done carefully
541+
auto total = Bits::getEffectiveShifts(leftRight) + Bits::getEffectiveShifts(right);
542+
if (total == Bits::getEffectiveShifts(total, right->type)) {
543+
// no overflow, we can do this
544+
leftRight->value = LiteralUtils::makeLiteralFromInt32(total, right->type);
545+
return left;
546+
} // TODO: handle overflows
539547
}
540548
}
541549
}
@@ -874,14 +882,17 @@ struct OptimizeInstructions : public WalkerPass<PostWalker<OptimizeInstructions,
874882
auto* left = binary->left;
875883
auto* right = binary->right;
876884
if (!Properties::emitsBoolean(left) || !Properties::emitsBoolean(right)) return nullptr;
877-
auto leftEffects = EffectAnalyzer(getPassOptions(), left).hasSideEffects();
878-
auto rightEffects = EffectAnalyzer(getPassOptions(), right).hasSideEffects();
879-
if (leftEffects && rightEffects) return nullptr; // both must execute
880-
// canonicalize with side effects, if any, happening on the left
881-
if (rightEffects) {
885+
auto leftEffects = EffectAnalyzer(getPassOptions(), left);
886+
auto rightEffects = EffectAnalyzer(getPassOptions(), right);
887+
auto leftHasSideEffects = leftEffects.hasSideEffects();
888+
auto rightHasSideEffects = rightEffects.hasSideEffects();
889+
if (leftHasSideEffects && rightHasSideEffects) return nullptr; // both must execute
890+
// canonicalize with side effects, if any, happening on the left
891+
if (rightHasSideEffects) {
882892
if (CostAnalyzer(left).cost < MIN_COST) return nullptr; // avoidable code is too cheap
893+
if (leftEffects.invalidates(rightEffects)) return nullptr; // cannot reorder
883894
std::swap(left, right);
884-
} else if (leftEffects) {
895+
} else if (leftHasSideEffects) {
885896
if (CostAnalyzer(right).cost < MIN_COST) return nullptr; // avoidable code is too cheap
886897
} else {
887898
// no side effects, reorder based on cost estimation
@@ -908,8 +919,15 @@ struct OptimizeInstructions : public WalkerPass<PostWalker<OptimizeInstructions,
908919
// it's better to do the opposite for gzip purposes as well as for readability.
909920
auto* last = ptr->dynCast<Const>();
910921
if (last) {
911-
last->value = Literal(int32_t(last->value.geti32() + offset));
912-
offset = 0;
922+
// don't do this if it would wrap the pointer
923+
uint64_t value64 = last->value.geti32();
924+
uint64_t offset64 = offset;
925+
if (value64 <= std::numeric_limits<int32_t>::max() &&
926+
offset64 <= std::numeric_limits<int32_t>::max() &&
927+
value64 + offset64 <= std::numeric_limits<int32_t>::max()) {
928+
last->value = Literal(int32_t(value64 + offset64));
929+
offset = 0;
930+
}
913931
}
914932
}
915933

src/wasm/wasm-binary.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ void WasmBinaryWriter::recurse(Expression*& curr) {
545545
}
546546

547547
static bool brokenTo(Block* block) {
548-
return block->name.is() && BranchUtils::BranchSeeker::has(block, block->name);
548+
return block->name.is() && BranchUtils::BranchSeeker::hasNamed(block, block->name);
549549
}
550550

551551
void WasmBinaryWriter::visitBlock(Block *curr) {
@@ -636,7 +636,7 @@ int32_t WasmBinaryWriter::getBreakIndex(Name name) { // -1 if not found
636636
return breakStack.size() - 1 - i;
637637
}
638638
}
639-
std::cerr << "bad break: " << name << std::endl;
639+
std::cerr << "bad break: " << name << " in " << currFunction->name << std::endl;
640640
abort();
641641
}
642642

@@ -2102,7 +2102,14 @@ void WasmBinaryBuilder::visitBlock(Block *curr) {
21022102
}
21032103
for (size_t i = start; i < end; i++) {
21042104
if (debug) std::cerr << " " << size_t(expressionStack[i]) << "\n zz Block element " << curr->list.size() << std::endl;
2105-
curr->list.push_back(expressionStack[i]);
2105+
auto* item = expressionStack[i];
2106+
curr->list.push_back(item);
2107+
if (i < end - 1) {
2108+
// stacky&unreachable code may introduce elements that need to be dropped in non-final positions
2109+
if (isConcreteWasmType(item->type)) {
2110+
curr->list.back() = Builder(wasm).makeDrop(curr->list.back());
2111+
}
2112+
}
21062113
}
21072114
expressionStack.resize(start);
21082115
curr->finalize(curr->type);

test/passes/fuzz-exec_O.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[fuzz-exec] 2 results noted
2+
(module
3+
(type $0 (func (result i64)))
4+
(type $1 (func (result i32)))
5+
(memory $0 1 1)
6+
(export "func_0" (func $func_0))
7+
(export "func_1" (func $func_1))
8+
(func $func_0 (type $0) (result i64)
9+
(block $label$0 (result i64)
10+
(br_if $label$0
11+
(i64.const 1234)
12+
(i32.load16_s offset=22 align=1
13+
(i32.const -1)
14+
)
15+
)
16+
)
17+
)
18+
(func $func_1 (type $1) (result i32)
19+
(i32.load16_s offset=22 align=1
20+
(i32.const -1)
21+
)
22+
)
23+
)
24+
[fuzz-exec] 2 results noted
25+
[fuzz-exec] results match

test/passes/fuzz-exec_O.wast

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
(module
2+
(memory $0 1 1)
3+
(export "func_0" (func $func_0))
4+
(export "func_1" (func $func_1))
5+
(func $func_0 (result i64)
6+
(block $label$0 (result i64)
7+
(loop $label$1 (result i64)
8+
(br_if $label$0
9+
(i64.const 1234)
10+
(i32.load16_s offset=22 align=1
11+
(i32.const -1)
12+
)
13+
)
14+
)
15+
)
16+
)
17+
(func $func_1 (result i32)
18+
(i32.load16_s offset=22 align=1
19+
(i32.const -1)
20+
)
21+
)
22+
)
23+

0 commit comments

Comments
 (0)