|
150 | 150 | // This optimization focuses on such cases. |
151 | 151 | // |
152 | 152 |
|
| 153 | +#include "ir/abstract.h" |
153 | 154 | #include "ir/bits.h" |
154 | 155 | #include "ir/branch-utils.h" |
155 | 156 | #include "ir/eh-utils.h" |
@@ -414,6 +415,18 @@ struct EscapeAnalyzer { |
414 | 415 | escapes = false; |
415 | 416 | fullyConsumes = true; |
416 | 417 | } |
| 418 | + void visitStructRMW(StructRMW* curr) { |
| 419 | + if (curr->ref == child) { |
| 420 | + escapes = false; |
| 421 | + fullyConsumes = true; |
| 422 | + } |
| 423 | + } |
| 424 | + void visitStructCmpxchg(StructCmpxchg* curr) { |
| 425 | + if (curr->ref == child || curr->expected == child) { |
| 426 | + escapes = false; |
| 427 | + fullyConsumes = true; |
| 428 | + } |
| 429 | + } |
417 | 430 | void visitArraySet(ArraySet* curr) { |
418 | 431 | if (!curr->index->is<Const>()) { |
419 | 432 | // Array operations on nonconstant indexes do not escape in the normal |
@@ -891,6 +904,132 @@ struct Struct2Local : PostWalker<Struct2Local> { |
891 | 904 | } |
892 | 905 | replaceCurrent(builder.blockify(replacement, value)); |
893 | 906 | } |
| 907 | + |
| 908 | + void visitStructRMW(StructRMW* curr) { |
| 909 | + if (analyzer.getInteraction(curr) == ParentChildInteraction::None) { |
| 910 | + return; |
| 911 | + } |
| 912 | + |
| 913 | + auto& field = fields[curr->index]; |
| 914 | + auto type = curr->type; |
| 915 | + assert(type == field.type); |
| 916 | + assert(!field.isPacked()); |
| 917 | + |
| 918 | + // We need a scratch local to hold the old, unmodified field value while we |
| 919 | + // update the original local with the modified value. We also need another |
| 920 | + // scratch local to hold the evaluated modification value while we set the |
| 921 | + // first scratch local in case the evaluation of the modification value ends |
| 922 | + // up changing the field value. This is similar to the scratch locals used |
| 923 | + // for struct.new. |
| 924 | + auto oldScratch = builder.addVar(func, type); |
| 925 | + auto valScratch = builder.addVar(func, type); |
| 926 | + auto local = localIndexes[curr->index]; |
| 927 | + |
| 928 | + auto* block = |
| 929 | + builder.makeSequence(builder.makeDrop(curr->ref), |
| 930 | + builder.makeLocalSet(valScratch, curr->value)); |
| 931 | + |
| 932 | + // Stash the old value to return. |
| 933 | + block->list.push_back( |
| 934 | + builder.makeLocalSet(oldScratch, builder.makeLocalGet(local, type))); |
| 935 | + |
| 936 | + // Store the updated value. |
| 937 | + Expression* newVal = nullptr; |
| 938 | + if (curr->op == RMWXchg) { |
| 939 | + newVal = builder.makeLocalGet(valScratch, type); |
| 940 | + } else { |
| 941 | + Abstract::Op binop = Abstract::Add; |
| 942 | + switch (curr->op) { |
| 943 | + case RMWAdd: |
| 944 | + binop = Abstract::Add; |
| 945 | + break; |
| 946 | + case RMWSub: |
| 947 | + binop = Abstract::Sub; |
| 948 | + break; |
| 949 | + case RMWAnd: |
| 950 | + binop = Abstract::And; |
| 951 | + break; |
| 952 | + case RMWOr: |
| 953 | + binop = Abstract::Or; |
| 954 | + break; |
| 955 | + case RMWXor: |
| 956 | + binop = Abstract::Xor; |
| 957 | + break; |
| 958 | + case RMWXchg: |
| 959 | + WASM_UNREACHABLE("unexpected op"); |
| 960 | + } |
| 961 | + newVal = builder.makeBinary(Abstract::getBinary(type, binop), |
| 962 | + builder.makeLocalGet(local, type), |
| 963 | + builder.makeLocalGet(valScratch, type)); |
| 964 | + } |
| 965 | + block->list.push_back(builder.makeLocalSet(local, newVal)); |
| 966 | + |
| 967 | + // See the notes on seqcst struct.get and struct.set. |
| 968 | + if (curr->order == MemoryOrder::SeqCst) { |
| 969 | + block->list.push_back(builder.makeAtomicFence()); |
| 970 | + } |
| 971 | + |
| 972 | + // Unstash the old value. |
| 973 | + block->list.push_back(builder.makeLocalGet(oldScratch, type)); |
| 974 | + block->type = type; |
| 975 | + replaceCurrent(block); |
| 976 | + } |
| 977 | + |
| 978 | + void visitStructCmpxchg(StructCmpxchg* curr) { |
| 979 | + if (analyzer.getInteraction(curr->ref) != ParentChildInteraction::Flows) { |
| 980 | + // The allocation can't flow into `replacement` if we've made it this far, |
| 981 | + // but it might flow into `expected`, in which case we don't need to do |
| 982 | + // anything because we would still be performing the cmpxchg on a real |
| 983 | + // struct. We only need to replace the cmpxchg if the ref is being |
| 984 | + // replaced with locals. |
| 985 | + return; |
| 986 | + } |
| 987 | + |
| 988 | + auto& field = fields[curr->index]; |
| 989 | + auto type = curr->type; |
| 990 | + assert(type == field.type); |
| 991 | + assert(!field.isPacked()); |
| 992 | + |
| 993 | + // Hold everything in scratch locals, just like for other RMW ops and |
| 994 | + // struct.new. |
| 995 | + auto oldScratch = builder.addVar(func, type); |
| 996 | + auto expectedScratch = builder.addVar(func, type); |
| 997 | + auto replacementScratch = builder.addVar(func, type); |
| 998 | + auto local = localIndexes[curr->index]; |
| 999 | + |
| 1000 | + auto* block = builder.makeBlock( |
| 1001 | + {builder.makeDrop(curr->ref), |
| 1002 | + builder.makeLocalSet(expectedScratch, curr->expected), |
| 1003 | + builder.makeLocalSet(replacementScratch, curr->replacement), |
| 1004 | + builder.makeLocalSet(oldScratch, builder.makeLocalGet(local, type))}); |
| 1005 | + |
| 1006 | + // Create the check for whether we should do the exchange. |
| 1007 | + auto* lhs = builder.makeLocalGet(local, type); |
| 1008 | + auto* rhs = builder.makeLocalGet(expectedScratch, type); |
| 1009 | + Expression* pred; |
| 1010 | + if (type.isRef()) { |
| 1011 | + pred = builder.makeRefEq(lhs, rhs); |
| 1012 | + } else { |
| 1013 | + pred = |
| 1014 | + builder.makeBinary(Abstract::getBinary(type, Abstract::Eq), lhs, rhs); |
| 1015 | + } |
| 1016 | + |
| 1017 | + // The conditional exchange. |
| 1018 | + block->list.push_back( |
| 1019 | + builder.makeIf(pred, |
| 1020 | + builder.makeLocalSet( |
| 1021 | + local, builder.makeLocalGet(replacementScratch, type)))); |
| 1022 | + |
| 1023 | + // See the notes on seqcst struct.get and struct.set. |
| 1024 | + if (curr->order == MemoryOrder::SeqCst) { |
| 1025 | + block->list.push_back(builder.makeAtomicFence()); |
| 1026 | + } |
| 1027 | + |
| 1028 | + // Unstash the old value. |
| 1029 | + block->list.push_back(builder.makeLocalGet(oldScratch, type)); |
| 1030 | + block->type = type; |
| 1031 | + replaceCurrent(block); |
| 1032 | + } |
894 | 1033 | }; |
895 | 1034 |
|
896 | 1035 | // An optimizer that handles the rewriting to turn a nonescaping array |
|
0 commit comments