@@ -2413,23 +2413,35 @@ bool Flower::updateContents(LocationIndex locationIndex,
2413
2413
auto location = getLocation (locationIndex);
2414
2414
2415
2415
// 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.
2426
2420
//
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.
2432
2438
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.
2433
2445
filterDataContents (newContents, *dataLoc);
2434
2446
#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
2435
2447
std::cout << " pre-filtered data contents:\n " ;
@@ -2438,21 +2450,29 @@ bool Flower::updateContents(LocationIndex locationIndex,
2438
2450
#endif
2439
2451
} else if (auto * exprLoc = std::get_if<ExpressionLocation>(&location)) {
2440
2452
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.
2447
2456
filterPackedDataReads (newContents, *exprLoc);
2448
2457
#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
2449
2458
std::cout << " pre-filtered packed read contents:\n " ;
2450
2459
newContents.dump (std::cout, &wasm);
2451
2460
std::cout << ' \n ' ;
2452
2461
#endif
2453
2462
}
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);
2454
2473
}
2455
2474
2475
+ // After filtering newContents, combine it onto the existing contents.
2456
2476
contents.combine (newContents);
2457
2477
2458
2478
if (contents.isNone ()) {
0 commit comments