@@ -894,43 +894,103 @@ struct Reducer
894894 }
895895 }
896896
897+ bool isEmptyBody (Expression* body) {
898+ if (body->is <Nop>() || body->is <Unreachable>()) {
899+ return true ;
900+ }
901+ if (auto * block = body->dynCast <Block>()) {
902+ return block->list .empty ();
903+ }
904+ return false ;
905+ }
906+
897907 void reduceFunctionBodies () {
898908 std::cerr << " | try to remove function bodies\n " ;
899909 // Use function indices to speed up finding the complement of the kept
900910 // partition.
901- std::vector<Index> funcs ;
902- funcs .reserve (module ->functions .size ());
911+ std::vector<Index> nontrivialFuncIndices ;
912+ nontrivialFuncIndices .reserve (module ->functions .size ());
903913 for (Index i = 0 ; i < module ->functions .size (); ++i) {
904- funcs.push_back (i);
905- }
906- deltaDebugging (
907- std::move (funcs),
908- [&](Index partitionIndex,
909- Index numPartitions,
910- const std::vector<Index>& partition) {
911- std::cerr << " | try partition " << partitionIndex + 1 << " / "
912- << numPartitions << " (size " << partition.size () << " )\n " ;
913- std::vector<Name> removed;
914- removed.reserve (module ->functions .size () - partition.size ());
915- Index i = 0 ;
916- for (Index j : partition) {
917- while (i < j) {
918- removed.push_back (module ->functions [i++]->name );
914+ auto & func = module ->functions [i];
915+ // Skip functions that already have trivial bodies.
916+ if (func->imported () || isEmptyBody (func->body )) {
917+ continue ;
918+ }
919+ nontrivialFuncIndices.push_back (i);
920+ }
921+ // TODO: Use something other than an exception to implement early return.
922+ struct EarlyReturn {};
923+ try {
924+ deltaDebugging (
925+ nontrivialFuncIndices,
926+ [&](Index partitionIndex,
927+ Index numPartitions,
928+ const std::vector<Index>& partition) {
929+ // Stop early if the partition size is less than the square root of
930+ // the remaining set. We don't want to waste time on very fine-grained
931+ // partitions when we could switch to another reduction strategy
932+ // instead.
933+ if (partition.size () > 0 &&
934+ partition.size () < std::sqrt (nontrivialFuncIndices.size ())) {
935+ throw EarlyReturn{};
919936 }
920- ++i;
921- }
922- while (i < module ->functions .size ()) {
923- removed.push_back (module ->functions [i++]->name );
924- }
925- if (tryToEmptyFunctions (removed)) {
926- // TODO: Consider doing this just once after the delta debugging since
927- // we never need to restore from the working copy while removing
928- // function bodies.
929- noteReduction (removed.size ());
937+
938+ std::cerr << " | try partition " << partitionIndex + 1 << " / "
939+ << numPartitions << " (size " << partition.size () << " )\n " ;
940+ Index removedSize = nontrivialFuncIndices.size () - partition.size ();
941+ std::vector<Expression*> oldBodies (removedSize);
942+
943+ // We first need to remove each non-kept function body, and later we
944+ // might need to restore the same function bodies. Abstract the logic
945+ // for iterating over these function bodies. `f` takes a Function* and
946+ // Expression*& for the stashed body.
947+ auto forEachRemovedFuncBody = [&](auto f) {
948+ Index bodyIndex = 0 ;
949+ Index nontrivialIndex = 0 ;
950+ Index partitionIndex = 0 ;
951+ while (nontrivialIndex < nontrivialFuncIndices.size ()) {
952+ if (partitionIndex < partition.size () &&
953+ nontrivialFuncIndices[nontrivialIndex] ==
954+ partition[partitionIndex]) {
955+ // Kept, skip it.
956+ nontrivialIndex++;
957+ partitionIndex++;
958+ } else {
959+ // Removed, process it
960+ Index funcIndex = nontrivialFuncIndices[nontrivialIndex++];
961+ f (module ->functions [funcIndex].get (), oldBodies[bodyIndex++]);
962+ }
963+ }
964+ assert (bodyIndex == removedSize);
965+ assert (partitionIndex == partition.size ());
966+ };
967+
968+ // Stash the bodies.
969+ forEachRemovedFuncBody ([&](Function* func, Expression*& oldBody) {
970+ oldBody = func->body ;
971+ Builder builder (*module );
972+ if (func->getResults () == Type::none) {
973+ func->body = builder.makeNop ();
974+ } else {
975+ func->body = builder.makeUnreachable ();
976+ }
977+ });
978+
979+ if (!writeAndTestReduction ()) {
980+ // Failure. Restore the bodies.
981+ forEachRemovedFuncBody ([](Function* func, Expression*& oldBody) {
982+ func->body = oldBody;
983+ });
984+ return false ;
985+ }
986+
987+ // Success!
988+ noteReduction (removedSize);
989+ nontrivialFuncIndices = partition;
930990 return true ;
931- }
932- return false ;
933- });
991+ });
992+ } catch (EarlyReturn) {
993+ }
934994 }
935995
936996 bool reduceFunctions () {
@@ -1085,41 +1145,6 @@ struct Reducer
10851145 }
10861146 }
10871147
1088- // Try to empty out the bodies of some functions.
1089- bool tryToEmptyFunctions (std::vector<Name> names) {
1090- std::vector<Expression*> oldBodies;
1091- size_t actuallyEmptied = 0 ;
1092- for (auto name : names) {
1093- auto * func = module ->getFunction (name);
1094- auto * oldBody = func->body ;
1095- oldBodies.push_back (oldBody);
1096- // Nothing to do for imported functions (body is nullptr) or for bodies
1097- // that have already been as reduced as we can make them.
1098- if (func->imported () || oldBody->is <Unreachable>() ||
1099- oldBody->is <Nop>()) {
1100- continue ;
1101- }
1102- actuallyEmptied++;
1103- bool useUnreachable = func->getResults () != Type::none;
1104- if (useUnreachable) {
1105- func->body = builder->makeUnreachable ();
1106- } else {
1107- func->body = builder->makeNop ();
1108- }
1109- }
1110- if (actuallyEmptied > 0 && writeAndTestReduction ()) {
1111- std::cerr << " | emptied " << actuallyEmptied << " / "
1112- << names.size () << " functions\n " ;
1113- return true ;
1114- } else {
1115- // Restore the bodies.
1116- for (size_t i = 0 ; i < names.size (); i++) {
1117- module ->getFunction (names[i])->body = oldBodies[i];
1118- }
1119- return false ;
1120- }
1121- }
1122-
11231148 // Try to actually remove functions. If they are somehow referred to, we will
11241149 // get a validation error and undo it.
11251150 bool tryToRemoveFunctions (std::vector<Name> names) {
@@ -1542,10 +1567,20 @@ More documentation can be found at
15421567
15431568 bool stopping = false ;
15441569
1570+ bool first = true ;
15451571 while (1 ) {
15461572 Reducer reducer (
15471573 command, test, working, binary, deNan, verbose, debugInfo, options);
15481574
1575+ // For extremely large modules with slow reproduction commands, reducing
1576+ // function bodies first can be more effective than running passes. TODO:
1577+ // clean this up and reconsider the order of reducers.
1578+ if (first) {
1579+ reducer.loadWorking ();
1580+ reducer.reduceFunctionBodies ();
1581+ first = false ;
1582+ }
1583+
15491584 // run binaryen optimization passes to reduce. passes are fast to run
15501585 // and can often reduce large amounts of code efficiently, as opposed
15511586 // to detructive reduction (i.e., that doesn't preserve correctness as
0 commit comments