1616
1717#include " tools/fuzzing.h"
1818#include " ir/gc-type-utils.h"
19+ #include " ir/iteration.h"
1920#include " ir/local-structural-dominance.h"
2021#include " ir/module-utils.h"
2122#include " ir/subtypes.h"
@@ -900,6 +901,26 @@ void TranslateToFuzzReader::recombine(Function* func) {
900901 };
901902 Modder modder (wasm, scanner, *this );
902903 modder.walk (func->body );
904+ // TODO: A specific form of recombination we should perhaps do more often is
905+ // to recombine among an expression's children, and in particular to
906+ // reorder them.
907+ }
908+
909+ // Given two expressions, try to replace one of the children of the first with
910+ // the second. For example, given i32.add and an i32.const, the i32.const could
911+ // be placed as either of the children of the i32.add. This tramples the
912+ // existing content there. Returns true if we found a place.
913+ static bool replaceChildWith (Expression* expr, Expression* with) {
914+ for (auto *& child : ChildIterator (expr)) {
915+ // To replace, we must have an appropriate type, and we cannot replace a
916+ // Pop under any circumstances.
917+ if (Type::isSubType (with->type , child->type ) &&
918+ FindAll<Pop>(child).list .empty ()) {
919+ child = with;
920+ return true ;
921+ }
922+ }
923+ return false ;
903924}
904925
905926void TranslateToFuzzReader::mutate (Function* func) {
@@ -930,16 +951,15 @@ void TranslateToFuzzReader::mutate(Function* func) {
930951 percentChance = std::max (percentChance, Index (3 ));
931952
932953 struct Modder : public PostWalker <Modder, UnifiedExpressionVisitor<Modder>> {
933- Module& wasm;
934954 TranslateToFuzzReader& parent;
935955 Index percentChance;
936956
937957 // Whether to replace with unreachable. This can lead to less code getting
938958 // executed, so we don't want to do it all the time even in a big function.
939959 bool allowUnreachable;
940960
941- Modder (Module& wasm, TranslateToFuzzReader& parent, Index percentChance)
942- : wasm(wasm), parent(parent), percentChance(percentChance) {
961+ Modder (TranslateToFuzzReader& parent, Index percentChance)
962+ : parent(parent), percentChance(percentChance) {
943963 // If the parent allows it then sometimes replace with an unreachable, and
944964 // sometimes not. Even if we allow it, only do it in certain functions
945965 // (half the time) and only do it rarely (see below).
@@ -949,29 +969,135 @@ void TranslateToFuzzReader::mutate(Function* func) {
949969 void visitExpression (Expression* curr) {
950970 if (parent.upTo (100 ) < percentChance &&
951971 parent.canBeArbitrarilyReplaced (curr)) {
952- if (allowUnreachable && parent.oneIn (20 )) {
972+ // We can replace in various modes, see below. Generate a random number
973+ // up to 100 to help us there.
974+ int mode = parent.upTo (100 );
975+
976+ if (allowUnreachable && mode < 5 ) {
953977 replaceCurrent (parent.make (Type::unreachable));
954978 return ;
955979 }
980+
956981 // For constants, perform only a small tweaking in some cases.
982+ // TODO: more minor tweaks to immediates, like making a load atomic or
983+ // not, changing an offset, etc.
957984 if (auto * c = curr->dynCast <Const>()) {
958- if (parent. oneIn ( 2 ) ) {
985+ if (mode < 50 ) {
959986 c->value = parent.tweak (c->value );
960- return ;
987+ } else {
988+ // Just replace the entire thing.
989+ replaceCurrent (parent.make (curr->type ));
990+ }
991+ return ;
992+ }
993+
994+ // Generate a replacement for the expression, and by default replace all
995+ // of |curr| (including children) with that replacement, but in some
996+ // cases we can do more subtle things.
997+ //
998+ // Note that such a replacement is not always valid due to nesting of
999+ // labels, but we'll fix that up later. Note also that make() picks a
1000+ // subtype, so this has a chance to replace us with anything that is
1001+ // valid to put here.
1002+ auto * rep = parent.make (curr->type );
1003+ if (mode < 33 && rep->type != Type::none) {
1004+ // This has a non-none type. Replace the output, keeping the
1005+ // expression and its children in a drop. This "interposes" between
1006+ // this expression and its parent, something like this:
1007+ //
1008+ // (D
1009+ // (A
1010+ // (B)
1011+ // (C)
1012+ // )
1013+ // )
1014+ // //
1015+ // => ;; keep A, replace it in the parent
1016+ //
1017+ // (D
1018+ // (block
1019+ // (drop
1020+ // (A
1021+ // (B)
1022+ // (C)
1023+ // )
1024+ // )
1025+ // (NEW)
1026+ // )
1027+ // )
1028+ //
1029+ // We also sometimes try to insert A as a child of NEW, so we actually
1030+ // interpose directly:
1031+ //
1032+ // (D
1033+ // (NEW
1034+ // (A
1035+ // (B)
1036+ // (C)
1037+ // )
1038+ // )
1039+ // )
1040+ //
1041+ // We do not do that all the time, as inserting a drop is actually an
1042+ // important situation to test: the drop makes the output of A unused,
1043+ // which may let optimizations remove it.
1044+ if ((mode & 1 ) && replaceChildWith (rep, curr)) {
1045+ // We managed to replace one of the children with curr, and have
1046+ // nothing more to do.
1047+ } else {
1048+ // Drop curr and append.
1049+ rep =
1050+ parent.builder .makeSequence (parent.builder .makeDrop (curr), rep);
1051+ }
1052+ } else if (mode >= 66 && !Properties::isControlFlowStructure (curr)) {
1053+ ChildIterator children (curr);
1054+ auto numChildren = children.getNumChildren ();
1055+ if (numChildren > 0 && numChildren < 5 ) {
1056+ // This is a normal (non-control-flow) expression with at least one
1057+ // child (and not an excessive amount of them; see the processing
1058+ // below). "Interpose" between the children and this expression by
1059+ // keeping them and replacing the parent |curr|. We do this by
1060+ // generating drops of the children, like this:
1061+ //
1062+ // (A
1063+ // (B)
1064+ // (C)
1065+ // )
1066+ //
1067+ // => ;; keep children, replace A
1068+ //
1069+ // (block
1070+ // (drop (B))
1071+ // (drop (C))
1072+ // (NEW)
1073+ // )
1074+ //
1075+ auto * block = parent.builder .makeBlock ();
1076+ for (auto * child : children) {
1077+ // Only drop the child if we can't replace it as one of NEW's
1078+ // children. This does a linear scan of |rep| which is the reason
1079+ // for the above limit on the number of children.
1080+ if (!replaceChildWith (rep, child)) {
1081+ block->list .push_back (parent.builder .makeDrop (child));
1082+ }
1083+ }
1084+
1085+ if (!block->list .empty ()) {
1086+ // We need the block, that is, we did not find a place for all the
1087+ // children.
1088+ block->list .push_back (rep);
1089+ block->finalize ();
1090+ rep = block;
1091+ }
9611092 }
9621093 }
963- // TODO: more minor tweaks to immediates, like making a load atomic or
964- // not, changing an offset, etc.
965- // Perform a general replacement. (This is not always valid due to
966- // nesting of labels, but we'll fix that up later.) Note that make()
967- // picks a subtype, so this has a chance to replace us with anything
968- // that is valid to put here.
969- replaceCurrent (parent.make (curr->type ));
1094+ replaceCurrent (rep);
9701095 }
9711096 }
9721097 };
973- Modder modder (wasm, *this , percentChance);
974- modder.walk (func->body );
1098+
1099+ Modder modder (*this , percentChance);
1100+ modder.walkFunctionInModule (func, &wasm);
9751101}
9761102
9771103void TranslateToFuzzReader::fixAfterChanges (Function* func) {
@@ -1078,6 +1204,10 @@ void TranslateToFuzzReader::modifyInitialFunctions() {
10781204 recombine (func);
10791205 mutate (func);
10801206 fixAfterChanges (func);
1207+ // TODO: This triad of functions appears in another place as well, and
1208+ // could be handled by a single function. That function could also
1209+ // decide to reorder recombine and mutate or even run more cycles of
1210+ // them.
10811211 }
10821212 }
10831213 // Remove a start function - the fuzzing harness expects code to run only
0 commit comments