Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions src/passes/GlobalStructInference.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ struct GlobalStructInference : public Pass {

bool refinalize = false;

// As we prepare to un-nest globals, we create global.gets of the global
// that we will un-nest the content to. That global does not yet exist,
// and we note such globals as we go so we ignore them (they are invalid
// IR until the global is created, later in this pass).
std::unordered_set<GlobalGet*> unnestingGlobalGets;

void visitStructGet(StructGet* curr) {
optimize(curr, curr->ref, curr->index);
}
Expand Down Expand Up @@ -347,14 +353,18 @@ struct GlobalStructInference : public Pass {
// This is a read of an immutable field. See if it is a trivial case, of
// a read from an immutable global.
if (auto* get = ref->dynCast<GlobalGet>()) {
auto* global = wasm.getGlobal(get->name);
if (!global->mutable_ && !global->imported()) {
if (auto* structNew = global->init->dynCast<StructNew>()) {
auto value = readFromStructNew(structNew, fieldIndex, field);
// We know the exact global being read here.
value.globals.push_back(global->name);
replaceCurrent(getReadValue(value, fieldIndex, field, curr));
return;
// The global.get must be valid, and not in the process of being
// rewritten to point to a new un-nested global.
if (!unnestingGlobalGets.count(get)) {
auto* global = wasm.getGlobal(get->name);
if (!global->mutable_ && !global->imported()) {
if (auto* structNew = global->init->dynCast<StructNew>()) {
auto value = readFromStructNew(structNew, fieldIndex, field);
// We know the exact global being read here.
value.globals.push_back(global->name);
replaceCurrent(getReadValue(value, fieldIndex, field, curr));
return;
}
}
}
}
Expand Down Expand Up @@ -541,6 +551,7 @@ struct GlobalStructInference : public Pass {

globalsToUnnest.emplace_back(
GlobalToUnnest{value.globals[0], fieldIndex, get});
unnestingGlobalGets.insert(get);

ret = get;
}
Expand Down
58 changes: 58 additions & 0 deletions test/lit/passes/gsi-nontype.wast
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,61 @@
)
)

;; Nested struct.gets that seem optimizable.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $func (func (result anyref)))
(type $func (func (result anyref)))
;; CHECK: (type $outer (sub (struct (field (ref $inner)))))
(type $outer (sub (struct (field (ref $inner)))))
;; CHECK: (type $inner (sub (struct (field (ref $func)))))
(type $inner (sub (struct (field (ref $func)))))
)

;; CHECK: (type $3 (func (result anyref)))

;; CHECK: (global $global.unnested.0 (ref (exact $inner)) (struct.new $inner
;; CHECK-NEXT: (ref.func $func)
;; CHECK-NEXT: ))

;; CHECK: (global $global (ref $outer) (struct.new $outer
;; CHECK-NEXT: (global.get $global.unnested.0)
;; CHECK-NEXT: ))
(global $global (ref $outer) (struct.new $outer
(struct.new $inner
(ref.func $func)
)
))

;; CHECK: (func $func (type $func) (result anyref)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $func (type $func) (result anyref)
(unreachable)
)

;; CHECK: (func $caller (type $3) (result anyref)
;; CHECK-NEXT: (call_ref $func
;; CHECK-NEXT: (struct.get $inner 0
;; CHECK-NEXT: (global.get $global.unnested.0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller (result anyref)
(call_ref $func
Comment thread
tlively marked this conversation as resolved.
;; TODO: If we did two passes, we could optimize this one too.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesn't this get optimized via un-nesting as well?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would just take another pass. Maybe worth it as the TODO says.

(We could do it in a single pass with more complexity, or if we traversed in the other direction, in theory...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, we can only do one level of un-nesting per pass because we're function-parallel and therefore cannot actually install the un-nested globals before we move on to the parent? That seems worth fixing! There's no reason why the globals wouldn't have very deep initialization trees, and we don't want to have to run this pass many times to fully optimize them.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it does seem worth fixing. We could perhaps just take a lock around global creation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maintain a per-function set of un-nested globals (with deterministic names) to be de-duplicated at the module level afterward.

(struct.get $inner 0
;; These two can be optimized, if we un-nest the global. When doing so we
;; turn these into a global.get, with a global name that does not exist yet
;; (we only create that global later in the pass). We must not think it is
;; a complete global.get and try to optimize with it when we reach the
;; parent struct.get.
(struct.get $outer 0
(global.get $global)
)
)
)
)
)

Loading