Skip to content

Commit d4f5162

Browse files
authored
Finish bulk memory support (#2030)
Implement interpretation of remaining bulk memory ops, add bulk memory spec tests with light modifications, fix bugs preventing the fuzzer from running correctly with bulk memory, and fix bugs found by the fuzzer.
1 parent 711a22c commit d4f5162

8 files changed

Lines changed: 286 additions & 32 deletions

File tree

scripts/fuzz_opt.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,12 @@ def run_vm(cmd):
138138

139139

140140
def run_bynterp(wasm):
141-
return fix_output(run_vm([in_bin('wasm-opt'), wasm, '--fuzz-exec-before']))
141+
return fix_output(run_vm([in_bin('wasm-opt'), wasm, '--fuzz-exec-before'] + FUZZ_OPTS))
142142

143143

144144
def run_wasm2js(wasm):
145-
wrapper = run([in_bin('wasm-opt'), wasm, '--emit-js-wrapper=/dev/stdout'])
146-
main = run([in_bin('wasm2js'), wasm, '--emscripten'])
145+
wrapper = run([in_bin('wasm-opt'), wasm, '--emit-js-wrapper=/dev/stdout'] + FUZZ_OPTS)
146+
main = run([in_bin('wasm2js'), wasm, '--emscripten'] + FUZZ_OPTS)
147147
with open(os.path.join(options.binaryen_root, 'scripts', 'wasm2js.js')) as f:
148148
glue = f.read()
149149
with open('js.js', 'w') as f:
@@ -193,7 +193,7 @@ def test_one(infile, opts):
193193
before = run_vms('a.')
194194
print('----------------')
195195
# gather VM outputs on processed file
196-
run([in_bin('wasm-opt'), 'a.wasm', '-o', 'b.wasm'] + opts)
196+
run([in_bin('wasm-opt'), 'a.wasm', '-o', 'b.wasm'] + opts + FUZZ_OPTS)
197197
wasm_size = os.stat('b.wasm').st_size
198198
bytes += wasm_size
199199
print('post js size:', os.stat('a.js').st_size, ' wasm size:', wasm_size)
@@ -205,10 +205,10 @@ def test_one(infile, opts):
205205
if NANS:
206206
break
207207
# fuzz binaryen interpreter itself. separate invocation so result is easily fuzzable
208-
run([in_bin('wasm-opt'), 'a.wasm', '--fuzz-exec', '--fuzz-binary'] + opts)
208+
run([in_bin('wasm-opt'), 'a.wasm', '--fuzz-exec', '--fuzz-binary'] + opts + FUZZ_OPTS)
209209
# check for determinism
210-
run([in_bin('wasm-opt'), 'a.wasm', '-o', 'b.wasm'] + opts)
211-
run([in_bin('wasm-opt'), 'a.wasm', '-o', 'c.wasm'] + opts)
210+
run([in_bin('wasm-opt'), 'a.wasm', '-o', 'b.wasm'] + opts + FUZZ_OPTS)
211+
run([in_bin('wasm-opt'), 'a.wasm', '-o', 'c.wasm'] + opts + FUZZ_OPTS)
212212
assert open('b.wasm').read() == open('c.wasm').read(), 'output must be deterministic'
213213

214214
return bytes

src/passes/RemoveUnusedModuleElements.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ enum class ModuleElementKind {
3939
typedef std::pair<ModuleElementKind, Name> ModuleElement;
4040

4141
// Finds reachabilities
42+
// TODO: use Effects to determine if a memory is used
4243

4344
struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
4445
Module* module;
@@ -119,6 +120,18 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
119120
void visitAtomicNotify(AtomicNotify* curr) {
120121
usesMemory = true;
121122
}
123+
void visitMemoryInit(MemoryInit* curr) {
124+
usesMemory = true;
125+
}
126+
void visitDataDrop(DataDrop* curr) {
127+
usesMemory = true;
128+
}
129+
void visitMemoryCopy(MemoryCopy* curr) {
130+
usesMemory = true;
131+
}
132+
void visitMemoryFill(MemoryFill* curr) {
133+
usesMemory = true;
134+
}
122135
void visitHost(Host* curr) {
123136
if (curr->op == CurrentMemory || curr->op == GrowMemory) {
124137
usesMemory = true;

src/tools/wasm-opt.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ int main(int argc, const char* argv[]) {
240240
auto input = buffer.getAsChars();
241241
WasmBinaryBuilder parser(other, input, false);
242242
parser.read();
243+
options.applyFeatures(other);
243244
if (options.passOptions.validate) {
244245
bool valid = WasmValidator().validate(other);
245246
if (!valid) {

src/wasm-interpreter.h

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -928,10 +928,6 @@ class ModuleInstanceBase {
928928
}
929929

930930
void initializeMemoryContents() {
931-
// no way to create a Block without an ArenaAllocator, so use a builder
932-
// instead of creating it locally.
933-
Builder builder(wasm);
934-
935931
Const offset;
936932
offset.value = Literal(uint32_t(0));
937933
offset.finalize();
@@ -955,15 +951,16 @@ class ModuleInstanceBase {
955951
init.finalize();
956952

957953
DataDrop drop;
958-
drop.segment = segment.index;
954+
drop.segment = i;
959955
drop.finalize();
960956

961-
Function initializer;
962-
initializer.body = builder.blockify(&init, &drop);
963-
964-
FunctionScope scope(&initializer, {});
965-
966-
RuntimeExpressionRunner(*this, scope).visit(&init);
957+
// we don't actually have a function, but we need one in order to visit
958+
// the memory.init and data.drop instructions.
959+
Function dummyFunc;
960+
FunctionScope dummyScope(&dummyFunc, {});
961+
RuntimeExpressionRunner runner(*this, dummyScope);
962+
runner.visit(&init);
963+
runner.visit(&drop);
967964
}
968965
}
969966

@@ -1228,14 +1225,20 @@ class ModuleInstanceBase {
12281225
trap("memory.init of dropped segment");
12291226
}
12301227

1231-
size_t destVal(dest.value.geti32());
1232-
size_t offsetVal(offset.value.geti32());
1233-
size_t sizeVal(size.value.geti32());
1228+
Address destVal(uint32_t(dest.value.geti32()));
1229+
Address offsetVal(uint32_t(offset.value.geti32()));
1230+
Address sizeVal(uint32_t(size.value.geti32()));
1231+
1232+
instance.checkLoadAddress(destVal, 0);
1233+
if (offsetVal > segment.data.size()) {
1234+
trap("segment offset out of bounds");
1235+
}
1236+
12341237
for (size_t i = 0; i < sizeVal; ++i) {
12351238
if (offsetVal + i >= segment.data.size()) {
12361239
trap("out of bounds segment access in memory.init");
12371240
}
1238-
Literal addr = Literal(uint32_t(destVal + i));
1241+
Literal addr(uint32_t(destVal + i));
12391242
instance.externalInterface->store8(
12401243
instance.getFinalAddress(addr, 1),
12411244
segment.data[offsetVal + i]
@@ -1253,12 +1256,64 @@ class ModuleInstanceBase {
12531256
}
12541257
Flow visitMemoryCopy(MemoryCopy *curr) {
12551258
NOTE_ENTER("MemoryCopy");
1256-
// TODO(tlively): implement me
1259+
Flow dest = this->visit(curr->dest);
1260+
if (dest.breaking()) return dest;
1261+
Flow source = this->visit(curr->source);
1262+
if (source.breaking()) return source;
1263+
Flow size = this->visit(curr->size);
1264+
if (size.breaking()) return size;
1265+
NOTE_EVAL1(dest);
1266+
NOTE_EVAL1(source);
1267+
NOTE_EVAL1(size);
1268+
Address destVal(uint32_t(dest.value.geti32()));
1269+
Address sourceVal(uint32_t(source.value.geti32()));
1270+
Address sizeVal(uint32_t(size.value.geti32()));
1271+
1272+
instance.checkLoadAddress(destVal, 0);
1273+
instance.checkLoadAddress(sourceVal, 0);
1274+
1275+
size_t start = 0;
1276+
size_t end = sizeVal;
1277+
int step = 1;
1278+
// Reverse direction if source is below dest and they overlap
1279+
if (sourceVal < destVal &&
1280+
(sourceVal + sizeVal > destVal || sourceVal + sizeVal < sourceVal)) {
1281+
start = sizeVal - 1;
1282+
end = -1;
1283+
step = -1;
1284+
}
1285+
for (size_t i = start; i != end; i += step) {
1286+
if (i + destVal >= std::numeric_limits<uint32_t>::max()) {
1287+
trap("Out of bounds memory access");
1288+
}
1289+
instance.externalInterface->store8(
1290+
instance.getFinalAddress(Literal(uint32_t(destVal + i)), 1),
1291+
instance.externalInterface->load8s(
1292+
instance.getFinalAddress(Literal(uint32_t(sourceVal + i)), 1)));
1293+
}
12571294
return {};
12581295
}
12591296
Flow visitMemoryFill(MemoryFill *curr) {
12601297
NOTE_ENTER("MemoryFill");
1261-
// TODO(tlively): implement me
1298+
Flow dest = this->visit(curr->dest);
1299+
if (dest.breaking()) return dest;
1300+
Flow value = this->visit(curr->value);
1301+
if (value.breaking()) return value;
1302+
Flow size = this->visit(curr->size);
1303+
if (size.breaking()) return size;
1304+
NOTE_EVAL1(dest);
1305+
NOTE_EVAL1(value);
1306+
NOTE_EVAL1(size);
1307+
Address destVal(uint32_t(dest.value.geti32()));
1308+
Address sizeVal(uint32_t(size.value.geti32()));
1309+
1310+
instance.checkLoadAddress(destVal, 0);
1311+
1312+
uint8_t val(value.value.geti32());
1313+
for (size_t i = 0; i < sizeVal; ++i) {
1314+
instance.externalInterface->store8(
1315+
instance.getFinalAddress(Literal(uint32_t(destVal + i)), 1), val);
1316+
}
12621317
return {};
12631318
}
12641319

src/wasm-traversal.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -657,9 +657,9 @@ struct PostWalker : public Walker<SubType, VisitorType> {
657657
}
658658
case Expression::Id::MemoryInitId: {
659659
self->pushTask(SubType::doVisitMemoryInit, currp);
660-
self->pushTask(SubType::scan, &curr->cast<MemoryInit>()->dest);
661-
self->pushTask(SubType::scan, &curr->cast<MemoryInit>()->offset);
662660
self->pushTask(SubType::scan, &curr->cast<MemoryInit>()->size);
661+
self->pushTask(SubType::scan, &curr->cast<MemoryInit>()->offset);
662+
self->pushTask(SubType::scan, &curr->cast<MemoryInit>()->dest);
663663
break;
664664
}
665665
case Expression::Id::DataDropId: {
@@ -668,16 +668,16 @@ struct PostWalker : public Walker<SubType, VisitorType> {
668668
}
669669
case Expression::Id::MemoryCopyId: {
670670
self->pushTask(SubType::doVisitMemoryCopy, currp);
671-
self->pushTask(SubType::scan, &curr->cast<MemoryCopy>()->dest);
672-
self->pushTask(SubType::scan, &curr->cast<MemoryCopy>()->source);
673671
self->pushTask(SubType::scan, &curr->cast<MemoryCopy>()->size);
672+
self->pushTask(SubType::scan, &curr->cast<MemoryCopy>()->source);
673+
self->pushTask(SubType::scan, &curr->cast<MemoryCopy>()->dest);
674674
break;
675675
}
676676
case Expression::Id::MemoryFillId: {
677677
self->pushTask(SubType::doVisitMemoryFill, currp);
678-
self->pushTask(SubType::scan, &curr->cast<MemoryFill>()->dest);
679-
self->pushTask(SubType::scan, &curr->cast<MemoryFill>()->value);
680678
self->pushTask(SubType::scan, &curr->cast<MemoryFill>()->size);
679+
self->pushTask(SubType::scan, &curr->cast<MemoryFill>()->value);
680+
self->pushTask(SubType::scan, &curr->cast<MemoryFill>()->dest);
681681
break;
682682
}
683683
case Expression::Id::ConstId: {

src/wasm.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,6 @@ class Memory : public Importable {
859859

860860
struct Segment {
861861
bool isPassive = false;
862-
Index index = 0;
863862
Expression* offset = nullptr;
864863
std::vector<char> data; // TODO: optimize
865864
Segment() = default;

src/wasm/wasm-binary.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1540,7 +1540,10 @@ void WasmBinaryBuilder::readDataSegments() {
15401540
}
15411541
curr.isPassive = flags & BinaryConsts::IsPassive;
15421542
if (flags & BinaryConsts::HasMemIndex) {
1543-
curr.index = getU32LEB();
1543+
auto memIndex = getU32LEB();
1544+
if (memIndex != 0) {
1545+
throwError("nonzero memory index");
1546+
}
15441547
}
15451548
if (!curr.isPassive) {
15461549
curr.offset = readExpression();

0 commit comments

Comments
 (0)