@@ -58,11 +58,13 @@ struct FieldInfoScanner
5858
5959 void
6060 noteDefault (Type fieldType, HeapType type, Index index, FieldInfo& info) {
61- // Default values do not affect what the heap type of a field can be turned
62- // into. Note them, however, as they force us to keep the type nullable.
61+ // Default values must be noted, so that we know there is content there.
6362 if (fieldType.isRef ()) {
64- info.note (Type (fieldType.getHeapType ().getBottom (), Nullable));
63+ // All we need to note here is nullability (the field must remain
64+ // nullable), but not anything else about the type.
65+ fieldType = Type (fieldType.getHeapType ().getBottom (), Nullable);
6566 }
67+ info.note (fieldType);
6668 }
6769
6870 void noteCopy (HeapType type, Index index, FieldInfo& info) {
@@ -270,71 +272,49 @@ struct TypeRefining : public Pass {
270272 return ;
271273 }
272274
273- if (curr->ref ->type .isNull ()) {
274- // This get will trap. In theory we could leave this for later
275- // optimizations to do, but we must actually handle it here, because
276- // of the situation where this get's type is refined, and the input
277- // type is the result of a refining:
278- //
279- // (struct.get $A ;; should be refined to something
280- // (struct.get $B ;; just refined to nullref
281- //
282- // If the input has become a nullref then we can't just return out of
283- // this function, as we'd be leaving a struct.get of $A with the
284- // wrong type. But we can't find the right type since in Binaryen IR
285- // we use the ref's type to see what is being read, and that just
286- // turned into nullref. To avoid that corner case, just turn this code
287- // into unreachable code now, and the later refinalize will turn all
288- // the parents unreachable, avoiding any type-checking problems.
289- Builder builder (*getModule ());
290- replaceCurrent (builder.makeSequence (builder.makeDrop (curr->ref ),
291- builder.makeUnreachable ()));
292- return ;
275+ Type newFieldType;
276+ if (!curr->ref ->type .isNull ()) {
277+ auto oldType = curr->ref ->type .getHeapType ();
278+ newFieldType = parent.finalInfos [oldType][curr->index ].getLUB ();
293279 }
294280
295- auto oldType = curr->ref ->type .getHeapType ();
296- auto newFieldType = parent.finalInfos [oldType][curr->index ].getLUB ();
297- if (Type::isSubType (newFieldType, curr->type )) {
298- // This is the normal situation, where the new type is a refinement of
299- // the old type. Apply that type so that the type of the struct.get
300- // matches what is in the refined field. ReFinalize will later
301- // propagate this to parents.
302- //
303- // Note that ReFinalize will also apply the type of the field itself
304- // to a struct.get, so our doing it here in this pass is usually
305- // redundant. But ReFinalize also updates other types while doing so,
306- // which can cause a problem:
307- //
308- // (struct.get $A
309- // (block (result (ref null $A))
310- // (ref.null any)
311- // )
312- // )
313- //
314- // Here ReFinalize will turn the block's result into a bottom type,
315- // which means it won't know a type for the struct.get at that point.
316- // Doing it in this pass avoids that issue, as we have all the
317- // necessary information. (ReFinalize will still get into the
318- // situation where it doesn't know how to update the type of the
319- // struct.get, but it will just leave the existing type - it assumes
320- // no update is needed - which will be correct, since we've updated it
321- // ourselves here, before.)
322- curr->type = newFieldType;
323- } else {
324- // This instruction is invalid, so it must be the result of the
325- // situation described above: we ignored the read during our
326- // inference, and optimized accordingly, and so now we must remove it
327- // to keep the module validating. It doesn't matter what we emit here,
328- // since there are no struct.new or struct.sets for this type, so this
329- // code is logically unreachable.
330- //
331- // Note that we emit an unreachable here, which changes the type, and
332- // so we should refinalize. However, we will be refinalizing later
333- // anyhow in updateTypes, so there is no need.
281+ if (curr->ref ->type .isNull () || newFieldType == Type::unreachable ||
282+ !Type::isSubType (newFieldType, curr->type )) {
283+ // This get will trap, or cannot be reached: either the ref is null,
284+ // or the field is never written any contents, or the contents we see
285+ // are invalid (they passed through some fallthrough that will trap at
286+ // runtime). Emit unreachable code here.
334287 Builder builder (*getModule ());
335288 replaceCurrent (builder.makeSequence (builder.makeDrop (curr->ref ),
336289 builder.makeUnreachable ()));
290+ return ;
337291 }
292+
293+ // This is the normal situation, where the new type is a refinement of
294+ // the old type. Apply that type so that the type of the struct.get
295+ // matches what is in the refined field. ReFinalize will later
296+ // propagate this to parents.
297+ //
298+ // Note that ReFinalize will also apply the type of the field itself
299+ // to a struct.get, so our doing it here in this pass is usually
300+ // redundant. But ReFinalize also updates other types while doing so,
301+ // which can cause a problem:
302+ //
303+ // (struct.get $A
304+ // (block (result (ref null $A))
305+ // (ref.null any)
306+ // )
307+ // )
308+ //
309+ // Here ReFinalize will turn the block's result into a bottom type,
310+ // which means it won't know a type for the struct.get at that point.
311+ // Doing it in this pass avoids that issue, as we have all the
312+ // necessary information. (ReFinalize will still get into the
313+ // situation where it doesn't know how to update the type of the
314+ // struct.get, but it will just leave the existing type - it assumes
315+ // no update is needed - which will be correct, since we've updated it
316+ // ourselves here, before.)
317+ curr->type = newFieldType;
338318 }
339319 };
340320
0 commit comments