Skip to content

Commit 1d97f47

Browse files
authored
GlobalStructInference: Fix trivial packed fields and un-nest them too (#8012)
#8005 did not handle packed fields, when finding trivial gets to optimize. Fix that and also implement the TODO for unnesting by sharing `getReadValue` (which happens to handle both things for us).
1 parent 98de5b4 commit 1d97f47

File tree

2 files changed

+134
-62
lines changed

2 files changed

+134
-62
lines changed

src/passes/GlobalStructInference.cpp

Lines changed: 65 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -351,13 +351,10 @@ struct GlobalStructInference : public Pass {
351351
if (!global->mutable_ && !global->imported()) {
352352
if (auto* structNew = global->init->dynCast<StructNew>()) {
353353
auto value = readFromStructNew(structNew, fieldIndex, field);
354-
// TODO: handle non-constant values using unnesting, as below.
355-
if (value.isConstant()) {
356-
replaceCurrent(value.getConstant().makeExpression(wasm));
357-
return;
358-
}
359-
// Otherwise, continue to try the main type-based optimization
360-
// below.
354+
// We know the exact global being read here.
355+
value.globals.push_back(global->name);
356+
replaceCurrent(getReadValue(value, fieldIndex, field, curr));
357+
return;
361358
}
362359
}
363360
}
@@ -433,52 +430,6 @@ struct GlobalStructInference : public Pass {
433430
}
434431
}
435432

436-
// Helper for optimization: Given a Value, returns what we should read
437-
// for it.
438-
auto getReadValue = [&](const Value& value) -> Expression* {
439-
Expression* ret;
440-
if (value.isConstant()) {
441-
// This is known to be a constant, so simply emit an expression for
442-
// that constant, and handle if the field is packed.
443-
ret = value.getConstant().makeExpression(wasm);
444-
if (field) {
445-
ret = Bits::makePackedFieldGet(
446-
ret, *field, curr->cast<StructGet>()->signed_, wasm);
447-
}
448-
} else {
449-
// Otherwise, this is non-constant, so we are in the situation where
450-
// we want to un-nest the value out of the struct.new it is in. Note
451-
// that for later work, as we cannot add a global in parallel.
452-
453-
// There can only be one global in a value that is not constant,
454-
// which is the global we want to read from.
455-
assert(value.globals.size() == 1);
456-
457-
// Create a global.get with temporary name, leaving only the
458-
// updating of the name to later work.
459-
auto* get = builder.makeGlobalGet(value.globals[0],
460-
value.getExpression()->type);
461-
462-
globalsToUnnest.emplace_back(
463-
GlobalToUnnest{value.globals[0], fieldIndex, get});
464-
465-
ret = get;
466-
}
467-
468-
// If the type is more refined, we must refinalize. For example, we
469-
// might have a struct.get that normally returns anyref, and know that
470-
// field contains null, so we return nullref.
471-
if (ret->type != curr->type) {
472-
refinalize = true;
473-
}
474-
475-
// This value replaces the struct.get, so it should have the same
476-
// source location.
477-
debuginfo::copyOriginalToReplacement(curr, ret, getFunction());
478-
479-
return ret;
480-
};
481-
482433
// We have some globals (at least 2), and so must have at least one
483434
// value. And we have already exited if we have more than 2 values (see
484435
// the early return above) so that only leaves 1 and 2.
@@ -489,7 +440,7 @@ struct GlobalStructInference : public Pass {
489440
// not need a fence.
490441
replaceCurrent(builder.makeSequence(
491442
builder.makeDrop(builder.makeRefAs(RefAsNonNull, ref)),
492-
getReadValue(values[0])));
443+
getReadValue(values[0], fieldIndex, field, curr)));
493444
return;
494445
}
495446
assert(values.size() == 2);
@@ -513,8 +464,8 @@ struct GlobalStructInference : public Pass {
513464
auto checkGlobal = values[0].globals[0];
514465
// Compute the left and right values before the next line, as the order
515466
// of their execution matters (they may note globals for un-nesting).
516-
auto* left = getReadValue(values[0]);
517-
auto* right = getReadValue(values[1]);
467+
auto* left = getReadValue(values[0], fieldIndex, field, curr);
468+
auto* right = getReadValue(values[1], fieldIndex, field, curr);
518469
// Note that we must trap on null, so add a ref.as_non_null here. As
519470
// before, the get cannot have synchronized with anything.
520471
Expression* getGlobal =
@@ -556,6 +507,64 @@ struct GlobalStructInference : public Pass {
556507
}
557508
return value;
558509
}
510+
511+
// Given a Value, returns what we should read for it.
512+
Expression* getReadValue(const Value& value,
513+
Index fieldIndex,
514+
std::optional<Field>& field,
515+
Expression* curr) {
516+
auto& wasm = *getModule();
517+
Builder builder(wasm);
518+
519+
Expression* ret;
520+
if (value.isConstant()) {
521+
// This is known to be a constant, so simply emit an expression for
522+
// that constant, and handle if the field is packed.
523+
ret = value.getConstant().makeExpression(wasm);
524+
if (field) {
525+
ret = Bits::makePackedFieldGet(
526+
ret, *field, curr->cast<StructGet>()->signed_, wasm);
527+
}
528+
} else {
529+
// Otherwise, this is non-constant, so we are in the situation where
530+
// we want to un-nest the value out of the struct.new it is in. Note
531+
// that for later work, as we cannot add a global in parallel.
532+
533+
// There can only be one global in a value that is not constant,
534+
// which is the global we want to read from.
535+
assert(value.globals.size() == 1);
536+
537+
// Create a global.get with temporary name, leaving only the
538+
// updating of the name to later work.
539+
auto* get = builder.makeGlobalGet(value.globals[0],
540+
value.getExpression()->type);
541+
542+
globalsToUnnest.emplace_back(
543+
GlobalToUnnest{value.globals[0], fieldIndex, get});
544+
545+
ret = get;
546+
}
547+
548+
// We must add a cast to non-null in some cases: A read of a null
549+
// descriptor returns a non-null value, so if there was a null in the
550+
// global, that would not validate by itself.
551+
if (ret->type.isNullable() && curr->type.isNonNullable()) {
552+
ret = builder.makeRefAs(RefAsNonNull, ret);
553+
}
554+
555+
// If the type is more refined, we must refinalize. For example, we
556+
// might have a struct.get that normally returns anyref, and know that
557+
// field contains null, so we return nullref.
558+
if (ret->type != curr->type) {
559+
refinalize = true;
560+
}
561+
562+
// This value replaces the struct.get, so it should have the same
563+
// source location.
564+
debuginfo::copyOriginalToReplacement(curr, ret, getFunction());
565+
566+
return ret;
567+
}
559568
};
560569

561570
// Find the optimization opportunitites in parallel.

test/lit/passes/gsi-nontype.wast

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@
103103
)
104104
)
105105

106-
;; As above, but the value in the struct.new is not constant, so we cannot
107-
;; optimize.
106+
;; As above, but the value in the struct.new is not constant. We must un-nest
107+
;; it into another global.
108108
(module
109109
;; CHECK: (type $table (struct (field anyref)))
110110
(type $table (struct anyref))
@@ -114,8 +114,10 @@
114114
;; CHECK: (import "a" "b" (global $imported funcref))
115115
(import "a" "b" (global $imported funcref))
116116

117+
;; CHECK: (global $table.unnested.0 (ref (exact $table)) (struct.new_default $table))
118+
117119
;; CHECK: (global $table (ref $table) (struct.new $table
118-
;; CHECK-NEXT: (struct.new_default $table)
120+
;; CHECK-NEXT: (global.get $table.unnested.0)
119121
;; CHECK-NEXT: ))
120122
(global $table (ref $table)
121123
(struct.new $table
@@ -125,9 +127,7 @@
125127

126128
;; CHECK: (func $test (type $1)
127129
;; CHECK-NEXT: (drop
128-
;; CHECK-NEXT: (struct.get $table 0
129-
;; CHECK-NEXT: (global.get $table)
130-
;; CHECK-NEXT: )
130+
;; CHECK-NEXT: (global.get $table.unnested.0)
131131
;; CHECK-NEXT: )
132132
;; CHECK-NEXT: )
133133
(func $test
@@ -204,3 +204,66 @@
204204
)
205205
)
206206

207+
;; A packed field.
208+
(module
209+
;; CHECK: (type $A (struct (field i8)))
210+
(type $A (struct (field i8)))
211+
212+
;; CHECK: (type $1 (func))
213+
214+
;; CHECK: (global $global (ref $A) (struct.new $A
215+
;; CHECK-NEXT: (i32.const -1)
216+
;; CHECK-NEXT: ))
217+
(global $global (ref $A) (struct.new $A
218+
(i32.const -1)
219+
))
220+
221+
;; CHECK: (func $test (type $1)
222+
;; CHECK-NEXT: (drop
223+
;; CHECK-NEXT: (i32.and
224+
;; CHECK-NEXT: (i32.const -1)
225+
;; CHECK-NEXT: (i32.const 255)
226+
;; CHECK-NEXT: )
227+
;; CHECK-NEXT: )
228+
;; CHECK-NEXT: )
229+
(func $test
230+
;; This should be 255, not -1.
231+
(drop
232+
(struct.get_u $A 0
233+
(global.get $global)
234+
)
235+
)
236+
)
237+
)
238+
239+
;; When reading a null descriptor, we must cast to non-null.
240+
(module
241+
(rec
242+
;; CHECK: (rec
243+
;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct))))
244+
(type $A (sub (descriptor $A.desc (struct))))
245+
;; CHECK: (type $A.desc (sub (describes $A (struct))))
246+
(type $A.desc (sub (describes $A (struct))))
247+
)
248+
249+
;; CHECK: (type $2 (func (result (ref $A.desc))))
250+
251+
;; CHECK: (global $global (ref $A) (struct.new_default $A
252+
;; CHECK-NEXT: (ref.null none)
253+
;; CHECK-NEXT: ))
254+
(global $global (ref $A) (struct.new_default $A
255+
(ref.null none)
256+
))
257+
258+
;; CHECK: (func $test (type $2) (result (ref $A.desc))
259+
;; CHECK-NEXT: (ref.as_non_null
260+
;; CHECK-NEXT: (ref.null none)
261+
;; CHECK-NEXT: )
262+
;; CHECK-NEXT: )
263+
(func $test (result (ref $A.desc))
264+
(ref.get_desc $A
265+
(global.get $global)
266+
)
267+
)
268+
)
269+

0 commit comments

Comments
 (0)