@@ -2413,23 +2413,35 @@ bool Flower::updateContents(LocationIndex locationIndex,
24132413 auto location = getLocation (locationIndex);
24142414
24152415 // Handle special cases: Some locations can only contain certain contents, so
2416- // filter accordingly. In principle we need to filter both before and after
2417- // combining with existing content; filtering afterwards is obviously
2418- // necessary as combining two things will create something larger than both,
2419- // and our representation has limitations (e.g. two different ref types will
2420- // result in a cone, potentially a very large one). Filtering beforehand is
2421- // necessary for the a more subtle reason: consider a location that contains
2422- // an i8 which is sent a 0 and then 0x100. If we filter only after, then we'd
2423- // combine 0 and 0x100 first and get "unknown integer"; only by filtering
2424- // 0x100 to 0 beforehand (since 0x100 & 0xff => 0) will we combine 0 and 0 and
2425- // not change anything, which is correct.
2416+ // filter accordingly. For example, if anyref arrives to a non-nullable
2417+ // location, we know it must be (ref any). As a result, each time we update
2418+ // the contents at a location we are both merging in the new contents, and
2419+ // filtering based on what we know of the location.
24262420 //
2427- // For efficiency reasons we aim to only filter once, depending on the type of
2428- // filtering. Most can be filtered a single time afterwards, while for data
2429- // locations, where the issue is packed integer fields, it's necessary to do
2430- // it before as we've mentioned, and also sufficient (see details in
2431- // filterDataContents).
2421+ // The operation of merging in new content and also filtering is *not*
2422+ // commutative. Set intersection and union of course is, but the shapes we
2423+ // work with here are limited, e.g. we have cones which include all children
2424+ // up to a fixed depth (and not specific children or each with a different
2425+ // depth). For example, if we start e.g. with a ref.func literal, and a
2426+ // ref.null arrives, then merging results in a cone that allows null, as that
2427+ // is the best shape we have that includes both. If the location is non-
2428+ // nullable then the cone becomes non-nullable, so we ended up with something
2429+ // worse than the original ref.func literal. In contrast, if we filtered the
2430+ // new contents first, the null would vanish (as no null is possible in the
2431+ // non-nullable location), so that order ends up better.
2432+ //
2433+ // For those reasons we filter the new contents arriving and also the merged
2434+ // contents afterwards, to try to get the best results. This also avoids some
2435+ // nondeterminism hazards with different orders. TODO: This does not avoid
2436+ // them all, in principle, due to lack of commutativity. Using a deterministic
2437+ // order (like abstract interpretation) would fix that.
24322438 if (auto * dataLoc = std::get_if<DataLocation>(&location)) {
2439+ // Filtering data contents is especially important to do before, and not
2440+ // necessary afterwards. For example, imagine a location that contains an
2441+ // i8 which is sent a 0 and then 0x100. If we filter only after, then we'd
2442+ // combine 0 and 0x100 first and get "unknown integer"; only by filtering
2443+ // 0x100 to 0 beforehand (since 0x100 & 0xff => 0) will we combine 0 and 0
2444+ // and not change anything, which is best.
24332445 filterDataContents (newContents, *dataLoc);
24342446#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
24352447 std::cout << " pre-filtered data contents:\n " ;
@@ -2438,21 +2450,29 @@ bool Flower::updateContents(LocationIndex locationIndex,
24382450#endif
24392451 } else if (auto * exprLoc = std::get_if<ExpressionLocation>(&location)) {
24402452 if (exprLoc->expr ->is <StructGet>() || exprLoc->expr ->is <ArrayGet>()) {
2441- // Packed data reads must be filtered before the combine() operation, as
2442- // we must only combine the filtered contents (e.g. if 0xff arrives which
2443- // as a signed read is truly 0xffffffff then we cannot first combine the
2444- // existing 0xffffffff with the new 0xff, as they are different, and the
2445- // result will no longer be a constant). There is no need to filter atomic
2446- // RMW operations here because they always do unsigned reads.
2453+ // As mentioned above, data locations can have packed reads, which require
2454+ // filtering before. Note that there is no need to filter atomic RMW
2455+ // operations here because they always do unsigned reads.
24472456 filterPackedDataReads (newContents, *exprLoc);
24482457#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
24492458 std::cout << " pre-filtered packed read contents:\n " ;
24502459 newContents.dump (std::cout, &wasm);
24512460 std::cout << ' \n ' ;
24522461#endif
24532462 }
2463+
2464+ // Generic filtering. We do this both before and after.
2465+ //
2466+ // The outcome of this filtering does not affect whether it is worth sending
2467+ // more later (we compute that at the end), so use a temp out var for that.
2468+ bool worthSendingMoreTemp = true ;
2469+ filterExpressionContents (newContents, *exprLoc, worthSendingMoreTemp);
2470+ } else if (auto * globalLoc = std::get_if<GlobalLocation>(&location)) {
2471+ // Generic filtering. We do this both before and after.
2472+ filterGlobalContents (newContents, *globalLoc);
24542473 }
24552474
2475+ // After filtering newContents, combine it onto the existing contents.
24562476 contents.combine (newContents);
24572477
24582478 if (contents.isNone ()) {
0 commit comments