@@ -87,10 +87,7 @@ MaybeResult<IRBuilder::HoistedVal> IRBuilder::hoistLastValue(bool greedy) {
8787 }
8888 auto *& expr = stack[valIndex];
8989 if (expr->type == Type::unreachable) {
90- // Make sure the top of the stack also has an unreachable expression.
91- if (stack.back ()->type != Type::unreachable) {
92- pushSynthetic (builder.makeUnreachable ());
93- }
90+ // No need for a scratch local to hoist an unreachable.
9491 return HoistedVal{Index (hoistIndex), nullptr };
9592 }
9693 // Hoist with a scratch local. Normally the scratch local is the same type as
@@ -129,6 +126,11 @@ Result<> IRBuilder::packageHoistedValue(const HoistedVal& hoisted,
129126 };
130127
131128 auto type = scope.exprStack .back ()->type ;
129+ if (type == Type::none) {
130+ // If we did not have a value on top of the stack and did not add a scratch
131+ // local, then there must have been an unreachable.
132+ type = Type::unreachable;
133+ }
132134
133135 if (type.size () == sizeHint || type.size () <= 1 ) {
134136 if (hoisted.hoistIndex < scope.exprStack .size () - 1 ) {
@@ -280,6 +282,8 @@ void IRBuilder::dump() {
280282 if (tryy->name ) {
281283 std::cerr << " " << tryy->name ;
282284 }
285+ } else if (scope.getTryTable ()) {
286+ std::cerr << " try_table" ;
283287 } else {
284288 WASM_UNREACHABLE (" unexpected scope" );
285289 }
@@ -303,7 +307,8 @@ void IRBuilder::dump() {
303307 std::cerr << " :\n " ;
304308
305309 for (auto * expr : scope.exprStack ) {
306- std::cerr << " " << ShallowExpression{expr} << " \n " ;
310+ std::cerr << " " << ShallowExpression{expr} << " (; "
311+ << expr->type .toString () << " ;)\n " ;
307312 }
308313 }
309314#endif // IR_BUILDER_DEBUG
@@ -356,32 +361,26 @@ struct IRBuilder::ChildPopper
356361 Result<> popConstrainedChildren (std::vector<Child>& children) {
357362 auto & scope = builder.getScope ();
358363
359- // The index of the shallowest unreachable instruction on the stack, found
360- // by checkNeedsUnreachableFallback.
361- std::optional<size_t > unreachableIndex;
362-
363- // Whether popping the children past the unreachable would produce a type
364- // mismatch or try to pop from an empty stack.
365- bool needUnreachableFallback = false ;
364+ // The stack size at which we are about to pop an unreachable instruction
365+ // that we must not pop past because the expressions underneath it do not
366+ // have the types we need.
367+ std::optional<size_t > unreachableFallbackSize;
366368
367369 // We only need to check requirements if there is an unreachable.
368370 // Otherwise the validator will catch any problems.
369371 if (scope.unreachable ) {
370- needUnreachableFallback =
371- checkNeedsUnreachableFallback (children, unreachableIndex);
372+ unreachableFallbackSize = checkNeedsUnreachableFallback (children);
372373 }
373374
374375 // We have checked all the constraints, so we are ready to pop children.
375376 for (int i = children.size () - 1 ; i >= 0 ; --i) {
376- if (needUnreachableFallback &&
377- scope.exprStack .size () == *unreachableIndex + 1 && i > 0 ) {
377+ if (unreachableFallbackSize &&
378+ scope.exprStack .size () == *unreachableFallbackSize && i > 0 ) {
378379 // The next item on the stack is the unreachable instruction we must
379- // not pop past. We cannot insert unreachables in front of it because
380- // it might be a branch we actually have to execute, so this next item
381- // must be child 0. But we are not ready to pop child 0 yet, so
382- // synthesize an unreachable instead of popping. The deeper
383- // instructions that would otherwise have been popped will remain on
384- // the stack to become prior children of future expressions or to be
380+ // not pop past. We could pop it as child 0, but we are not ready to pop
381+ // child 0 yet, so synthesize an unreachable instead of popping. The
382+ // deeper instructions that would otherwise have been popped will remain
383+ // on the stack to become prior children of future expressions or to be
385384 // implicitly dropped at the end of the scope.
386385 *children[i].childp = builder.builder .makeUnreachable ();
387386 continue ;
@@ -397,9 +396,10 @@ struct IRBuilder::ChildPopper
397396 return Ok{};
398397 }
399398
400- bool checkNeedsUnreachableFallback ( const std::vector<Child>& children,
401- std::optional< size_t >& unreachableIndex ) {
399+ std::optional< size_t >
400+ checkNeedsUnreachableFallback ( const std::vector<Child >& children ) {
402401 auto & scope = builder.getScope ();
402+ std::optional<size_t > unreachableFallbackSize;
403403
404404 // Two-part indices into the stack of available expressions and the vector
405405 // of requirements, allowing them to move independently with the granularity
@@ -409,6 +409,9 @@ struct IRBuilder::ChildPopper
409409 size_t childIndex = children.size ();
410410 size_t childTupleIndex = 0 ;
411411
412+ // Whether we are deeper than some concrete expression.
413+ bool seenConcrete = false ;
414+
412415 // Check whether the values on the stack will be able to meet the given
413416 // requirements.
414417 while (true ) {
@@ -435,7 +438,7 @@ struct IRBuilder::ChildPopper
435438 // the input unreachable instruction is executed first. If we are
436439 // not reaching past an unreachable, the error will be caught when
437440 // we pop.
438- return true ;
441+ return unreachableFallbackSize ;
439442 }
440443 --stackIndex;
441444 stackTupleIndex = scope.exprStack [stackIndex]->type .size () - 1 ;
@@ -450,22 +453,35 @@ struct IRBuilder::ChildPopper
450453 }
451454
452455 // We have an available type and a constraint. Only check constraints if
453- // we are past an unreachable, since otherwise we can leave problems to be
454- // caught by the validator later.
456+ // we are deeper than an unreachable, since otherwise we can leave
457+ // problems to be caught by the validator later.
455458 auto type = scope.exprStack [stackIndex]->type [stackTupleIndex];
456- if (unreachableIndex ) {
459+ if (unreachableFallbackSize ) {
457460 auto constraint = children[childIndex].constraint [childTupleIndex];
458461 if (!PrincipalType::matches (type, constraint)) {
459- return true ;
462+ return unreachableFallbackSize ;
460463 }
461464 }
462465
463- // No problems for children after this unreachable.
466+ // We may need an unreachable fallback if we find violated constraints in
467+ // expressions deeper than this unreachable. Calculate the stack size at
468+ // which this unreachable will be the next thing popped. This size is
469+ // usually one more than the current index, but if there are no shallower
470+ // concrete expressions, then everything down to this unreachable will be
471+ // popped at once immediately at the current stack size.
464472 if (type == Type::unreachable) {
465- unreachableIndex = stackIndex;
473+ unreachableFallbackSize =
474+ seenConcrete ? stackIndex + 1 : scope.exprStack .size ();
475+ } else {
476+ // We skipped none-typed expressions and this isn't unreachable, so it
477+ // must be concrete.
478+ assert (type.isConcrete ());
479+ seenConcrete = true ;
466480 }
467481 }
468- return false ;
482+
483+ // No unreachable fallback necessary.
484+ return std::nullopt ;
469485 }
470486
471487 // If `greedy`, then we will pop additional none-typed expressions that come
@@ -848,6 +864,13 @@ Result<Expression*> IRBuilder::finishScope(Block* block) {
848864 return Err{" popping from empty stack" };
849865 }
850866
867+ if (scope.exprStack .back ()->type == Type::none) {
868+ // Nothing was hoisted, which means there must have been an unreachable
869+ // buried under none-type expressions. It is not valid to end a concretely
870+ // typed block with none-typed expressions, so add an extra unreachable.
871+ pushSynthetic (builder.makeUnreachable ());
872+ }
873+
851874 if (type.isTuple ()) {
852875 auto hoistedType = scope.exprStack .back ()->type ;
853876 if (hoistedType != Type::unreachable &&
0 commit comments