Skip to content

Commit 1f01a77

Browse files
authored
[Outlining] Remove overlapping sequences (#7146)
While determining whether repeat sequences of instructions are candidates for outlining, remove sequences that overlap, giving weight to sequences that are longer and appear more frequently.
1 parent aee292b commit 1f01a77

File tree

9 files changed

+332
-0
lines changed

9 files changed

+332
-0
lines changed

src/passes/Outlining.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ struct Outlining : public Pass {
280280
DBG(printHashString(stringify.hashString, stringify.exprs));
281281
// Remove substrings that are substrings of longer repeat substrings.
282282
substrings = StringifyProcessor::dedupe(substrings);
283+
// Remove substrings with overlapping indices.
284+
substrings = StringifyProcessor::filterOverlaps(substrings);
283285
// Remove substrings with branch and return instructions until an analysis
284286
// is performed to see if the intended destination of the branch is included
285287
// in the substring to be outlined.

src/passes/hash-stringify-walker.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
#include "stringify-walker.h"
18+
#include "support/intervals.h"
1819

1920
namespace wasm {
2021

@@ -147,6 +148,47 @@ std::vector<SuffixTree::RepeatedSubstring> StringifyProcessor::dedupe(
147148
return result;
148149
}
149150

151+
std::vector<SuffixTree::RepeatedSubstring> StringifyProcessor::filterOverlaps(
152+
const std::vector<SuffixTree::RepeatedSubstring>& substrings) {
153+
// A substring represents a contiguous set of instructions that appear more
154+
// than once in a Wasm binary. For each appearance of the substring, an
155+
// Interval is created that lacks a connection back to its originating
156+
// substring. To fix, upon Interval creation, a second vector is populated
157+
// with the index of the corresponding substring.
158+
std::vector<Interval> intervals;
159+
std::vector<int> substringIdxs;
160+
161+
// Construct intervals
162+
for (Index i = 0; i < substrings.size(); i++) {
163+
auto& substring = substrings[i];
164+
for (auto startIdx : substring.StartIndices) {
165+
intervals.emplace_back(
166+
startIdx, startIdx + substring.Length, substring.Length);
167+
substringIdxs.push_back(i);
168+
}
169+
}
170+
171+
// Get the overlapping intervals
172+
std::vector<SuffixTree::RepeatedSubstring> result;
173+
std::vector<std::vector<Index>> startIndices(substrings.size());
174+
std::vector<int> indices = IntervalProcessor::filterOverlaps(intervals);
175+
for (auto i : indices) {
176+
// i is the idx of the Interval in the intervals vector
177+
// i in substringIdxs returns the idx of the substring that needs to be
178+
// included in result
179+
auto substringIdx = substringIdxs[i];
180+
startIndices[substringIdx].push_back(intervals[i].start);
181+
}
182+
for (Index i = 0; i < startIndices.size(); i++) {
183+
if (startIndices[i].size() > 1) {
184+
result.emplace_back(SuffixTree::RepeatedSubstring(
185+
{substrings[i].Length, std::move(startIndices[i])}));
186+
}
187+
}
188+
189+
return result;
190+
}
191+
150192
std::vector<SuffixTree::RepeatedSubstring> StringifyProcessor::filter(
151193
const std::vector<SuffixTree::RepeatedSubstring>& substrings,
152194
const std::vector<Expression*>& exprs,

src/passes/stringify-walker.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "ir/module-utils.h"
2222
#include "ir/stack-utils.h"
2323
#include "ir/utils.h"
24+
#include "support/intervals.h"
2425
#include "support/suffix_tree.h"
2526
#include "wasm-ir-builder.h"
2627
#include "wasm-traversal.h"
@@ -264,6 +265,7 @@ using Substrings = std::vector<SuffixTree::RepeatedSubstring>;
264265
struct StringifyProcessor {
265266
static Substrings repeatSubstrings(std::vector<uint32_t>& hashString);
266267
static Substrings dedupe(const Substrings& substrings);
268+
static Substrings filterOverlaps(const Substrings& substrings);
267269
// Filter is the general purpose function backing subsequent filter functions.
268270
// It can be used directly, but generally prefer a wrapper function
269271
// to encapsulate your condition and make it available for tests.

src/support/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ set(support_SOURCES
77
debug.cpp
88
dfa_minimization.cpp
99
file.cpp
10+
intervals.cpp
1011
istring.cpp
1112
json.cpp
1213
name.cpp

src/support/intervals.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2024 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <assert.h>
18+
19+
#include "intervals.h"
20+
#include "support/index.h"
21+
#include <algorithm>
22+
23+
using namespace wasm;
24+
25+
std::vector<int>
26+
IntervalProcessor::filterOverlaps(std::vector<Interval>& intervals) {
27+
if (intervals.size() == 0) {
28+
return std::vector<int>();
29+
}
30+
31+
std::vector<std::pair<Interval, int>> intIntervals;
32+
for (Index i = 0; i < intervals.size(); i++) {
33+
auto& interval = intervals[i];
34+
intIntervals.push_back({interval, i});
35+
}
36+
37+
std::sort(intIntervals.begin(), intIntervals.end());
38+
39+
std::vector<std::pair<Interval, int>> kept;
40+
kept.push_back(intIntervals[0]);
41+
for (auto& candidate : intIntervals) {
42+
auto& former = kept.back();
43+
if (former.first.end <= candidate.first.start) {
44+
kept.push_back(candidate);
45+
continue;
46+
}
47+
48+
// When two intervals overlap with the same weight, prefer to keep the
49+
// interval that ends sooner under the presumption that it will overlap with
50+
// fewer subsequent intervals.
51+
if (former.first.weight == candidate.first.weight &&
52+
former.first.end > candidate.first.end) {
53+
former = candidate;
54+
} else if (former.first.weight < candidate.first.weight) {
55+
former = candidate;
56+
}
57+
}
58+
59+
std::vector<int> result;
60+
for (auto& intPair : kept) {
61+
result.push_back(intPair.second);
62+
}
63+
64+
return result;
65+
}

src/support/intervals.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2024 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// Helpers for handling a generic range of values
18+
19+
#ifndef wasm_support_intervals_h
20+
#define wasm_support_intervals_h
21+
22+
#include <set>
23+
#include <vector>
24+
25+
namespace wasm {
26+
27+
struct Interval {
28+
unsigned start;
29+
unsigned end;
30+
// The weight is used to determine which interval to keep when two overlap,
31+
// higher is better
32+
unsigned weight;
33+
Interval(unsigned start, unsigned end, unsigned weight)
34+
: start(start), end(end), weight(weight) {}
35+
36+
bool operator<(const Interval& other) const {
37+
return start != other.start ? start < other.start
38+
: weight != other.weight ? weight < other.weight
39+
: end < other.end;
40+
}
41+
42+
bool operator==(const Interval& other) const {
43+
return start == other.start && end == other.end && weight == other.weight;
44+
}
45+
};
46+
47+
struct IntervalProcessor {
48+
// Given a vector of Interval, returns a vector of the indices that, mapping
49+
// back to the original input vector, do not overlap with each other, i.e. the
50+
// interval indexes with overlapping interval indexes already removed.
51+
static std::vector<int> filterOverlaps(std::vector<Interval>&);
52+
};
53+
54+
} // namespace wasm
55+
56+
#endif // wasm_suport_intervals

test/gtest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ set(unittest_SOURCES
1111
dfa_minimization.cpp
1212
disjoint_sets.cpp
1313
interpreter.cpp
14+
intervals.cpp
1415
json.cpp
1516
lattices.cpp
1617
local-graph.cpp

test/gtest/intervals.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#include "support/intervals.h"
2+
#include "ir/utils.h"
3+
#include "gtest/gtest.h"
4+
5+
using namespace wasm;
6+
7+
TEST(IntervalsTest, TestEmpty) {
8+
std::vector<Interval> intervals;
9+
std::vector<int> expected;
10+
ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected);
11+
}
12+
13+
TEST(IntervalsTest, TestOne) {
14+
std::vector<Interval> intervals;
15+
intervals.emplace_back(0, 2, 5);
16+
std::vector<int> expected{0};
17+
ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected);
18+
}
19+
20+
TEST(IntervalsTest, TestNoOverlapFound) {
21+
std::vector<Interval> intervals;
22+
intervals.emplace_back(0, 4, 2);
23+
intervals.emplace_back(4, 8, 2);
24+
std::vector<int> expected{0, 1};
25+
ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected);
26+
}
27+
28+
TEST(IntervalsTest, TestOverlapFound) {
29+
std::vector<Interval> intervals;
30+
intervals.emplace_back(0, 2, 5);
31+
intervals.emplace_back(1, 4, 10);
32+
intervals.emplace_back(4, 5, 15);
33+
std::vector<int> expected{1, 2};
34+
ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected);
35+
}
36+
37+
TEST(IntervalsTest, TestOverlapAscendingSequence) {
38+
std::vector<Interval> intervals;
39+
intervals.emplace_back(0, 3, 6);
40+
intervals.emplace_back(2, 6, 2);
41+
intervals.emplace_back(3, 15, 5);
42+
intervals.emplace_back(4, 11, 1);
43+
intervals.emplace_back(6, 20, 15);
44+
intervals.emplace_back(12, 18, 3);
45+
intervals.emplace_back(14, 21, 5);
46+
intervals.emplace_back(23, 28, 5);
47+
std::vector<int> expected{0, 4, 7};
48+
ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected);
49+
}
50+
51+
TEST(IntervalsTest, TestOverlapDescendingSequence) {
52+
std::vector<Interval> intervals;
53+
intervals.emplace_back(9, 15, 5);
54+
intervals.emplace_back(8, 11, 1);
55+
intervals.emplace_back(3, 15, 5);
56+
intervals.emplace_back(3, 6, 2);
57+
intervals.emplace_back(2, 10, 3);
58+
intervals.emplace_back(0, 3, 6);
59+
std::vector<int> expected{5, 2};
60+
ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected);
61+
}
62+
63+
TEST(IntervalsTest, TestOverlapRandomSequence) {
64+
std::vector<Interval> intervals;
65+
intervals.emplace_back(21, 24, 1);
66+
intervals.emplace_back(0, 5, 1);
67+
intervals.emplace_back(11, 18, 1);
68+
intervals.emplace_back(28, 35, 1);
69+
intervals.emplace_back(5, 11, 1);
70+
intervals.emplace_back(18, 21, 1);
71+
intervals.emplace_back(35, 40, 1);
72+
intervals.emplace_back(24, 28, 1);
73+
std::vector<int> expected{1, 4, 2, 5, 0, 7, 3, 6};
74+
ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected);
75+
}
76+
77+
TEST(IntervalsTest, TestOverlapInnerNested) {
78+
std::vector<Interval> intervals;
79+
intervals.emplace_back(0, 12, 3);
80+
intervals.emplace_back(2, 4, 2);
81+
intervals.emplace_back(3, 6, 5);
82+
intervals.emplace_back(12, 15, 4);
83+
std::vector<int> expected{2, 3};
84+
ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected);
85+
}
86+
87+
TEST(IntervalsTest, TestOverlapOuterNested) {
88+
std::vector<Interval> intervals;
89+
intervals.emplace_back(0, 3, 6);
90+
intervals.emplace_back(4, 11, 1);
91+
intervals.emplace_back(12, 18, 3);
92+
intervals.emplace_back(2, 15, 6);
93+
std::vector<int> expected{0, 1, 2};
94+
ASSERT_EQ(IntervalProcessor::filterOverlaps(intervals), expected);
95+
}

test/lit/passes/outlining.wast

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,3 +1006,71 @@
10061006
(loop (nop))
10071007
)
10081008
)
1009+
1010+
;; Test that no attempt is made to outline overlapping repeat substrings
1011+
;; In the below module, the Outlining optimization identifies two substrings
1012+
;; that each repeat twice. During filtering, one of the repeat substrings is
1013+
;; found to have an overlapping interval with itself. Because an interval is
1014+
;; dropped, only one of the substrings repeats enough times (minimum twice)
1015+
;; to warrant outlining.
1016+
(module
1017+
;; CHECK: (type $0 (func))
1018+
1019+
;; CHECK: (func $outline$ (type $0)
1020+
;; CHECK-NEXT: (drop
1021+
;; CHECK-NEXT: (i32.const 0)
1022+
;; CHECK-NEXT: )
1023+
;; CHECK-NEXT: (drop
1024+
;; CHECK-NEXT: (i32.const 1)
1025+
;; CHECK-NEXT: )
1026+
;; CHECK-NEXT: (drop
1027+
;; CHECK-NEXT: (i32.const 2)
1028+
;; CHECK-NEXT: )
1029+
;; CHECK-NEXT: (drop
1030+
;; CHECK-NEXT: (i32.const 3)
1031+
;; CHECK-NEXT: )
1032+
;; CHECK-NEXT: )
1033+
1034+
;; CHECK: (func $a (type $0)
1035+
;; CHECK-NEXT: (call $outline$)
1036+
;; CHECK-NEXT: (call $outline$)
1037+
;; CHECK-NEXT: (drop
1038+
;; CHECK-NEXT: (i32.const 1)
1039+
;; CHECK-NEXT: )
1040+
;; CHECK-NEXT: (drop
1041+
;; CHECK-NEXT: (i32.const 2)
1042+
;; CHECK-NEXT: )
1043+
;; CHECK-NEXT: (drop
1044+
;; CHECK-NEXT: (i32.const 1)
1045+
;; CHECK-NEXT: )
1046+
;; CHECK-NEXT: (drop
1047+
;; CHECK-NEXT: (i32.const 2)
1048+
;; CHECK-NEXT: )
1049+
;; CHECK-NEXT: )
1050+
(func $a
1051+
i32.const 0 ;; Begin substring 1
1052+
drop
1053+
i32.const 1
1054+
drop
1055+
i32.const 2
1056+
drop
1057+
i32.const 3
1058+
drop ;; End substring 1
1059+
i32.const 0 ;; Begin substring 1 repeat
1060+
drop
1061+
i32.const 1
1062+
drop
1063+
i32.const 2
1064+
drop
1065+
i32.const 3
1066+
drop ;; End substring 1 repeat && Begin substring 2
1067+
i32.const 1
1068+
drop
1069+
i32.const 2
1070+
drop ;; End substring 2 && Begin substring 2 repeat
1071+
i32.const 1
1072+
drop
1073+
i32.const 2
1074+
drop ;; End substring 2 repeat
1075+
)
1076+
)

0 commit comments

Comments
 (0)