Skip to content

Commit 845e070

Browse files
authored
MemoryPacking: Handle non-empty trapping segments (#6261)
Followup to #6243 which handled empty ones.
1 parent 5526027 commit 845e070

File tree

2 files changed

+153
-9
lines changed

2 files changed

+153
-9
lines changed

src/passes/MemoryPacking.cpp

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "ir/utils.h"
3535
#include "pass.h"
3636
#include "support/space.h"
37+
#include "support/stdckdint.h"
3738
#include "wasm-binary.h"
3839
#include "wasm-builder.h"
3940
#include "wasm.h"
@@ -46,6 +47,7 @@ namespace {
4647
// will be used instead of memory.init for this range.
4748
struct Range {
4849
bool isZero;
50+
// The range [start, end) - that is, start is included while end is not.
4951
size_t start;
5052
size_t end;
5153
};
@@ -109,7 +111,8 @@ struct MemoryPacking : public Pass {
109111
ReferrersMap& referrers);
110112
bool canSplit(const std::unique_ptr<DataSegment>& segment,
111113
const Referrers& referrers);
112-
void calculateRanges(const std::unique_ptr<DataSegment>& segment,
114+
void calculateRanges(Module* module,
115+
const std::unique_ptr<DataSegment>& segment,
113116
const Referrers& referrers,
114117
std::vector<Range>& ranges);
115118
void createSplitSegments(Builder& builder,
@@ -162,7 +165,7 @@ void MemoryPacking::run(Module* module) {
162165
std::vector<Range> ranges;
163166

164167
if (canSplit(segment, currReferrers)) {
165-
calculateRanges(segment, currReferrers, ranges);
168+
calculateRanges(module, segment, currReferrers, ranges);
166169
} else {
167170
// A single range covers the entire segment. Set isZero to false so the
168171
// original memory.init will be used even if segment is all zeroes.
@@ -295,14 +298,33 @@ bool MemoryPacking::canSplit(const std::unique_ptr<DataSegment>& segment,
295298
return segment->isPassive || segment->offset->is<Const>();
296299
}
297300

298-
void MemoryPacking::calculateRanges(const std::unique_ptr<DataSegment>& segment,
301+
void MemoryPacking::calculateRanges(Module* module,
302+
const std::unique_ptr<DataSegment>& segment,
299303
const Referrers& referrers,
300304
std::vector<Range>& ranges) {
301305
auto& data = segment->data;
302306
if (data.size() == 0) {
303307
return;
304308
}
305309

310+
// A segment that might trap during startup must be handled carefully, as we
311+
// do not want to remove that trap (unless we are allowed to by TNH).
312+
auto preserveTrap = !getPassOptions().trapsNeverHappen && segment->offset;
313+
if (preserveTrap) {
314+
// Check if we can rule out a trap by it being in bounds.
315+
if (auto* c = segment->offset->dynCast<Const>()) {
316+
auto* memory = module->getMemory(segment->memory);
317+
auto memorySize = memory->initial * Memory::kPageSize;
318+
Index start = c->value.getUnsigned();
319+
Index size = segment->data.size();
320+
Index end;
321+
if (!std::ckd_add(&end, start, size) && end <= memorySize) {
322+
// This is in bounds.
323+
preserveTrap = false;
324+
}
325+
}
326+
}
327+
306328
// Calculate initial zero and nonzero ranges
307329
size_t start = 0;
308330
while (start < data.size()) {
@@ -346,7 +368,10 @@ void MemoryPacking::calculateRanges(const std::unique_ptr<DataSegment>& segment,
346368
}
347369
}
348370

349-
// Merge edge zeroes if they are not worth splitting
371+
// Merge edge zeroes if they are not worth splitting. Note that we must not
372+
// have a trap we must preserve here (if we did, we'd need to handle that in
373+
// the code below).
374+
assert(!preserveTrap);
350375
if (ranges.size() >= 2) {
351376
auto last = ranges.end() - 1;
352377
auto penultimate = ranges.end() - 2;
@@ -364,12 +389,12 @@ void MemoryPacking::calculateRanges(const std::unique_ptr<DataSegment>& segment,
364389
}
365390
}
366391
} else {
367-
// Legacy ballpark overhead of active segment with offset
392+
// Legacy ballpark overhead of active segment with offset.
368393
// TODO: Tune this
369394
threshold = 8;
370395
}
371396

372-
// Merge ranges across small spans of zeroes
397+
// Merge ranges across small spans of zeroes.
373398
std::vector<Range> mergedRanges = {ranges.front()};
374399
size_t i;
375400
for (i = 1; i < ranges.size() - 1; ++i) {
@@ -383,10 +408,28 @@ void MemoryPacking::calculateRanges(const std::unique_ptr<DataSegment>& segment,
383408
mergedRanges.push_back(*curr);
384409
}
385410
}
386-
// Add the final range if it hasn't already been merged in
411+
// Add the final range if it hasn't already been merged in.
387412
if (i < ranges.size()) {
388413
mergedRanges.push_back(ranges.back());
389414
}
415+
// If we need to preserve a trap then we must keep the topmost byte of the
416+
// segment, which is enough to cause a trap if we do in fact trap.
417+
if (preserveTrap) {
418+
// Check if the last byte is in a zero range. Such a range will be dropped
419+
// later, so add a non-zero range with that byte. (It is slightly odd to
420+
// add a range with a zero marked as non-zero, but that is how we ensure it
421+
// is preserved later in the output.)
422+
auto& back = mergedRanges.back();
423+
if (back.isZero) {
424+
// Remove the last byte from |back|. Decrementing this prevents it from
425+
// overlapping with the new segment we are about to add. Note that this
426+
// might make |back| have size 0, but that is not a problem as it will be
427+
// dropped later anyhow, since it contains zeroes.
428+
back.end--;
429+
auto lastByte = data.size() - 1;
430+
mergedRanges.push_back({false, lastByte, lastByte + 1});
431+
}
432+
}
390433
std::swap(ranges, mergedRanges);
391434
}
392435

@@ -551,6 +594,20 @@ void MemoryPacking::dropUnusedSegments(
551594
module->updateDataSegmentsMap();
552595
}
553596

597+
// Given the start of a segment and an offset into it, compute the sum of the
598+
// two to get the absolute address the data should be at. This takes into
599+
// account overflows in that we saturate to UINT_MAX: we do not want an overflow
600+
// to take us down into a small address; in the invalid case of an overflow we
601+
// stay at the largest possible unsigned value, which will keep us trapping.
602+
template<typename T>
603+
Expression* addStartAndOffset(T start, T offset, Builder& builder) {
604+
T total;
605+
if (std::ckd_add(&total, start, offset)) {
606+
total = std::numeric_limits<T>::max();
607+
}
608+
return builder.makeConst(T(total));
609+
}
610+
554611
void MemoryPacking::createSplitSegments(
555612
Builder& builder,
556613
const DataSegment* segment,
@@ -568,10 +625,12 @@ void MemoryPacking::createSplitSegments(
568625
if (!segment->isPassive) {
569626
if (auto* c = segment->offset->dynCast<Const>()) {
570627
if (c->value.type == Type::i32) {
571-
offset = builder.makeConst(int32_t(c->value.geti32() + range.start));
628+
offset = addStartAndOffset<uint32_t>(
629+
range.start, c->value.geti32(), builder);
572630
} else {
573631
assert(c->value.type == Type::i64);
574-
offset = builder.makeConst(int64_t(c->value.geti64() + range.start));
632+
offset = addStartAndOffset<uint64_t>(
633+
range.start, c->value.geti64(), builder);
575634
}
576635
} else {
577636
assert(ranges.size() == 1);
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
3+
;; RUN: foreach %s %t wasm-opt --memory-packing -all --zero-filled-memory -S -o - | filecheck %s
4+
;; RUN: foreach %s %t wasm-opt --memory-packing -all --zero-filled-memory -tnh -S -o - | filecheck %s --check-prefix=TNH__
5+
6+
(module
7+
;; We should not optimize out a segment that will trap, as that is an effect
8+
;; we need to preserve (unless TrapsNeverHappen).
9+
;; CHECK: (memory $memory 1 2)
10+
;; TNH__: (memory $memory 1 2)
11+
(memory $memory 1 2)
12+
;; CHECK: (data $data (i32.const -1) "\00")
13+
(data $data (i32.const -1) "\00")
14+
)
15+
16+
(module
17+
;; We should handle the possible overflow in adding the offset and size, and
18+
;; see this might trap. To keep the segment trapping, we will emit a segment
19+
;; with offset -1 of size 1 (which is the minimal thing we need for a trap).
20+
;; CHECK: (memory $memory 1 2)
21+
;; TNH__: (memory $memory 1 2)
22+
(memory $memory 1 2)
23+
;; CHECK: (data $data (i32.const -1) "\00")
24+
(data $data (i32.const -2) "\00\00\00")
25+
)
26+
27+
(module
28+
;; This segment will almost trap, but not.
29+
;; CHECK: (memory $memory 1 2)
30+
;; TNH__: (memory $memory 1 2)
31+
(memory $memory 1 2)
32+
(data $data (i32.const 65535) "\00")
33+
)
34+
35+
(module
36+
;; This one is slightly larger, and will trap. We can at least shorten the
37+
;; segment to only contain one byte, at the highest address the segment would
38+
;; write to.
39+
;; CHECK: (memory $memory 1 2)
40+
;; TNH__: (memory $memory 1 2)
41+
(memory $memory 1 2)
42+
;; CHECK: (data $data (i32.const 65536) "\00")
43+
(data $data (i32.const 65535) "\00\00")
44+
)
45+
46+
(module
47+
;; This one is slightly larger, but the offset is lower so it will not trap.
48+
;; CHECK: (memory $memory 1 2)
49+
;; TNH__: (memory $memory 1 2)
50+
(memory $memory 1 2)
51+
(data $data (i32.const 65534) "\00\00")
52+
)
53+
54+
(module
55+
;; This one's offset is just large enough to trap.
56+
;; CHECK: (memory $memory 1 2)
57+
;; TNH__: (memory $memory 1 2)
58+
(memory $memory 1 2)
59+
;; CHECK: (data $data (i32.const 65536) "\00")
60+
(data $data (i32.const 65536) "\00")
61+
)
62+
63+
(module
64+
;; This offset is unknown, so assume the worst.
65+
;; TODO: We could remove it in TNH mode
66+
67+
;; CHECK: (import "a" "b" (global $g i32))
68+
;; TNH__: (import "a" "b" (global $g i32))
69+
(import "a" "b" (global $g i32))
70+
;; CHECK: (memory $memory 1 2)
71+
;; TNH__: (memory $memory 1 2)
72+
(memory $memory 1 2)
73+
;; CHECK: (data $data (global.get $g) "\00")
74+
;; TNH__: (data $data (global.get $g) "\00")
75+
(data $data (global.get $g) "\00")
76+
)
77+
78+
(module
79+
;; Passive segments cannot trap during startup and are removable if they have
80+
;; no uses, like here.
81+
;; CHECK: (memory $memory 1 2)
82+
;; TNH__: (memory $memory 1 2)
83+
(memory $memory 1 2)
84+
(data $data "\00\00\00")
85+
)

0 commit comments

Comments
 (0)