-
Notifications
You must be signed in to change notification settings - Fork 793
[Wasm GC] Support non-nullable locals in the "1a" form #4959
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 249 commits
dcd9b03
1db5e3f
168c15f
92d8ed2
bd3ba24
7376756
6a160c7
aa1e8ee
f05bf1e
164ff19
b96f71c
62de4b6
4951186
3693a88
050bf41
5aba92d
fd0b471
c558398
ba0c978
8033586
8702630
15c31de
84f6f5f
dd18b76
f28dd06
af559ea
2b92b7c
cbd24a3
e5d6cd0
cb44b96
73389f1
a5f35c4
0d21ef3
592f678
3a67edc
eec42fd
ee2b2ad
c2c51dc
6bbec5e
22e410e
a42e560
be644ea
e882b6f
f68ef7e
e0bd604
b978ca9
9478ea4
178bb8f
5257404
96f0383
7c8b38c
aaf5832
56c9e4f
6b1671d
efe3b2a
f187a4e
91e2a2d
630e826
5deafa6
29b0782
0cff57b
2048f09
4abe768
77e03b6
39a704b
271b045
1f66918
32682dd
e852b12
7d32494
902fba5
21cf733
9ca6fd9
b36ab47
1a8afc5
e5c2a1b
943f683
a1a6065
5123462
5325dc9
a4bd6f2
2554d94
4364b72
2b02845
8c15506
f65bb35
1ee8177
40da1bb
7f3aae3
5bb33c3
b3ded6e
9c5f28d
49148ef
b4e4134
993f03e
88ae913
230e3a0
d871cbc
103bed8
165c508
6ed0b9f
d3ad932
92bb071
5c64216
c5ab091
287b589
6207095
eb414ec
c931aa3
56085f0
f64abc1
231bcd1
9532a95
9be6041
fe14045
a7bf33f
0c64c7c
985e4fe
cac0849
e3a91cf
e7a2505
51c7321
95a7215
1432e9a
60d1aad
e1b8b86
3dfaa7d
e7f3ea2
e0550f5
8429578
d66806f
6cb2a1f
ef65d78
497c554
662afe2
511effb
c044edf
901bb84
7251b13
cd64156
5336750
c713810
4b703e1
1384b13
d44e67f
886e32a
8d4b4e6
6900cba
dd2a66f
47f2be0
a01f288
97e0db9
6c305be
3a6af79
b524862
9b8a13e
7ff905e
909f03e
e6ca657
ff56712
849d2d9
1d66418
70e225a
1531138
817c9de
84311cb
0b04f83
2740403
40803cc
67589ea
1bbcd7a
dff6053
e050686
c9fcb08
c17d288
acbbbdd
098cbad
574cb5c
4ca37f7
1710868
bb83a18
d9646fa
d8ab045
2d72b83
a7d9302
a6b5a5c
e95dcea
8c4af99
ee8982a
b3d8a42
128f69d
9c8342e
2c42fa6
9b111cb
ad128f8
9feed9a
636ee9f
0a03a48
eaa5ff9
e3c5126
5d40988
05350f5
1171241
64738b7
9eba40c
7aeb2e1
802a4a9
f5a1452
2ffe49d
547e853
8c51239
c05f92e
25122af
b327bb0
502251a
de68f71
38bbe46
764a859
1202a4c
51e8d5a
f0fbf02
5136819
8c8f12f
a5a29c1
7d345f6
bd6c351
7919a8c
963f524
1b9d49b
4e15a4c
e28b5de
6e160fd
d0637f5
a82b6a0
bc863a4
80ac4f8
93e9b62
d730464
e8bbf77
637b092
b138fd8
afa15c7
1e75064
48e97c2
2f9c484
3ac568a
f69e721
0b0a252
456d190
159d93e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
/* | ||
* Copyright 2022 WebAssembly Community Group participants | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
#include "ir/iteration.h" | ||
#include "ir/local-structural-dominance.h" | ||
#include "support/small_set.h" | ||
#include "support/small_vector.h" | ||
|
||
namespace wasm { | ||
|
||
LocalStructuralDominance::LocalStructuralDominance(Function* func, | ||
Module& wasm, | ||
Mode mode) { | ||
if (!wasm.features.hasReferenceTypes()) { | ||
// No references, so nothing to look at. | ||
return; | ||
} | ||
|
||
bool hasRefVar = false; | ||
for (auto var : func->vars) { | ||
if (var.isRef()) { | ||
hasRefVar = true; | ||
break; | ||
} | ||
} | ||
if (!hasRefVar) { | ||
return; | ||
} | ||
|
||
if (mode == NonNullableOnly) { | ||
bool hasNonNullableVar = false; | ||
for (auto var : func->vars) { | ||
// Check if we have any non-nullable vars at all. | ||
if (var.isNonNullable()) { | ||
hasNonNullableVar = true; | ||
break; | ||
} | ||
} | ||
if (!hasNonNullableVar) { | ||
return; | ||
} | ||
} | ||
|
||
struct Scanner : public PostWalker<Scanner> { | ||
std::set<Index>& nonDominatingIndices; | ||
|
||
// The locals that have been set, and so at the current time, they | ||
// structurally dominate. | ||
std::vector<bool> localsSet; | ||
|
||
Scanner(Function* func, Mode mode, std::set<Index>& nonDominatingIndices) | ||
: nonDominatingIndices(nonDominatingIndices) { | ||
localsSet.resize(func->getNumLocals()); | ||
|
||
// Parameters always dominate. | ||
for (Index i = 0; i < func->getNumParams(); i++) { | ||
localsSet[i] = true; | ||
} | ||
|
||
for (Index i = func->getNumParams(); i < func->getNumLocals(); i++) { | ||
auto type = func->getLocalType(i); | ||
// Mark locals we don't need to care about as "set". We never do any | ||
// work for such a local. | ||
if (!type.isRef() || (mode == NonNullableOnly && type.isNullable())) { | ||
localsSet[i] = true; | ||
} | ||
} | ||
|
||
// Note that we do not need to start a scope for the function body. | ||
// Logically there is a scope there, but there is no code after it, so | ||
// there is nothing to clean up when that scope exits, so we may as well | ||
// not even create a scope. Just start walking the body now. | ||
walk(func->body); | ||
} | ||
|
||
using Locals = SmallVector<Index, 5>; | ||
|
||
// When we exit a control flow scope, we must undo the locals that it set. | ||
std::vector<Locals> cleanupStack; | ||
|
||
static void doBeginScope(Scanner* self, Expression** currp) { | ||
self->cleanupStack.emplace_back(); | ||
} | ||
|
||
static void doEndScope(Scanner* self, Expression** currp) { | ||
for (auto index : self->cleanupStack.back()) { | ||
assert(self->localsSet[index]); | ||
self->localsSet[index] = false; | ||
} | ||
self->cleanupStack.pop_back(); | ||
} | ||
|
||
static void doLocalSet(Scanner* self, Expression** currp) { | ||
auto index = (*currp)->cast<LocalSet>()->index; | ||
if (!self->localsSet[index]) { | ||
// This local is now set until the end of this scope. | ||
self->localsSet[index] = true; | ||
// If we are not in the topmost scope, note this for later cleanup. | ||
if (!self->cleanupStack.empty()) { | ||
self->cleanupStack.back().push_back(index); | ||
} | ||
tlively marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
static void scan(Scanner* self, Expression** currp) { | ||
// Use a loop to avoid recursing on the last child - we can just go | ||
// straight into a loop iteration for it. | ||
while (1) { | ||
Expression* curr = *currp; | ||
|
||
switch (curr->_id) { | ||
case Expression::Id::InvalidId: | ||
WASM_UNREACHABLE("bad id"); | ||
|
||
// local.get can just be visited immediately, as it has no children. | ||
case Expression::Id::LocalGetId: { | ||
auto index = curr->cast<LocalGet>()->index; | ||
if (!self->localsSet[index]) { | ||
self->nonDominatingIndices.insert(index); | ||
} | ||
return; | ||
} | ||
case Expression::Id::LocalSetId: { | ||
auto* set = curr->cast<LocalSet>(); | ||
if (!self->localsSet[set->index]) { | ||
self->pushTask(doLocalSet, currp); | ||
} | ||
// Immediately continue in the loop. | ||
currp = &set->value; | ||
continue; | ||
} | ||
|
||
// Control flow structures. | ||
case Expression::Id::BlockId: { | ||
auto* block = curr->cast<Block>(); | ||
// Blocks with no name are never emitted in the binary format, so do | ||
// not create a scope for them. | ||
if (block->name.is()) { | ||
self->pushTask(Scanner::doEndScope, currp); | ||
} | ||
auto& list = block->list; | ||
for (int i = int(list.size()) - 1; i >= 0; i--) { | ||
self->pushTask(Scanner::scan, &list[i]); | ||
} | ||
if (block->name.is()) { | ||
// Just call the task immediately. | ||
doBeginScope(self, currp); | ||
} | ||
return; | ||
} | ||
case Expression::Id::IfId: { | ||
if (curr->cast<If>()->ifFalse) { | ||
self->pushTask(Scanner::doEndScope, currp); | ||
self->maybePushTask(Scanner::scan, &curr->cast<If>()->ifFalse); | ||
self->pushTask(Scanner::doBeginScope, currp); | ||
} | ||
self->pushTask(Scanner::doEndScope, currp); | ||
self->pushTask(Scanner::scan, &curr->cast<If>()->ifTrue); | ||
self->pushTask(Scanner::doBeginScope, currp); | ||
// Immediately continue in the loop. | ||
currp = &curr->cast<If>()->condition; | ||
continue; | ||
} | ||
case Expression::Id::LoopId: { | ||
self->pushTask(Scanner::doEndScope, currp); | ||
// Just call the task immediately. | ||
doBeginScope(self, currp); | ||
// Immediately continue in the loop. | ||
currp = &curr->cast<Loop>()->body; | ||
continue; | ||
} | ||
case Expression::Id::TryId: { | ||
auto& list = curr->cast<Try>()->catchBodies; | ||
for (int i = int(list.size()) - 1; i >= 0; i--) { | ||
self->pushTask(Scanner::doEndScope, currp); | ||
self->pushTask(Scanner::scan, &list[i]); | ||
self->pushTask(Scanner::doBeginScope, currp); | ||
} | ||
self->pushTask(Scanner::doEndScope, currp); | ||
// Just call the task immediately. | ||
doBeginScope(self, currp); | ||
// Immediately continue in the loop. | ||
currp = &curr->cast<Try>()->body; | ||
continue; | ||
} | ||
|
||
default: { | ||
// Control flow structures have been handled. This is an expression, | ||
// which we scan normally. | ||
assert(!Properties::isControlFlowStructure(curr)); | ||
PostWalker<Scanner>::scan(self, currp); | ||
return; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it would have performance benefits, we could special-case There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure what you mean? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, but if I understand correctly, we could trivial reimplement its scanning in terms of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Specifically, we could add this:
We could do this with any expression type that only has a single operand. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, yes, that could be faster. I think it would add a bunch of extra code though, and need to be updated when we add new instructions, etc. I am also unsure how much it would help. I did measure something related, btw - I checked whether we pushed a single child on the stack (by measuring the size before and after) and if so, continued in the loop on that. That does what you said, but it does write and read from the stack, so it just saves the call. I saw no significant benefit there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the loop doesn't help much, would be simpler to do without it entirely? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The loop does help in itself, in my measurements. Just adding those particular additional uses of the loop didn't help. One reason the loop helps in the current code is that not only do we avoid a call, but also we can avoid pushing a task and later popping it and doing an indirect call. (see e.g. how Loop just calls There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused about why avoiding that task push, pop, and indirect call doesn't help as much when applied to other expression types. The existing code lgtm, though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it's not totally obvious to me either. It could depend on the frequency of the instructions or their size (which affects caching) for example. Overall, the current code has some added optimization complexity but not much, and each piece seemed worthwhile when I measured it. |
||
} | ||
} | ||
} | ||
} | ||
|
||
// Only local.set needs to be visited. | ||
void pushTask(TaskFunc func, Expression** currp) { | ||
// Visits to anything but a set can be ignored, so only very specific | ||
// tasks need to actually be pushed here. In particular, we don't want to | ||
// push tasks to call doVisit* when those callbacks do nothing. | ||
if (func == scan || func == doLocalSet || func == doBeginScope || | ||
func == doEndScope) { | ||
PostWalker<Scanner>::pushTask(func, currp); | ||
} | ||
} | ||
void maybePushTask(TaskFunc func, Expression** currp) { | ||
if (*currp) { | ||
pushTask(func, currp); | ||
} | ||
} | ||
}; | ||
|
||
Scanner(func, mode, nonDominatingIndices); | ||
} | ||
|
||
} // namespace wasm |
Uh oh!
There was an error while loading. Please reload this page.