text: // -*- c -*-
  //
  // %CopyrightBegin%
  //
  // Copyright Ericsson AB 2017-2018. All Rights Reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //     http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  //
  // %CopyrightEnd%
  //
  
  // /*
  //  * Skeleton for receive statement:
  //  *
  //  *             recv_mark L1                     Optional
  //  *             call make_ref/monitor            Optional
  //  *             ...
  //  *             recv_set L1                      Optional
  //  *      L1:          <-------------------+
  //  *                   <-----------+       |
  //  *                               |       |
  //  *             loop_rec L2 ------+---+   |
  //  *             ...               |   |   |
  //  *             remove_message    |   |   |
  //  *             jump L3           |   |   |
  //  *		...                 |   |   |
  //  *		loop_rec_end L1   --+   |   |
  //  *      L2:          <---------------+   |
  //  *	   	wait L1  -------------------+      or wait_timeout
  //  *		timeout
  //  *
  //  *	 L3:    Code after receive...
  //  *
  //  */
  
  i_recv_mark() {
      /*
       * Save the current end of message queue
       */
      ERTS_RECV_MARK_SAVE(c_p);
  }
  
  i_recv_set() {
      /*
       * If previously saved recv mark, set peek position to it
       */
      ERTS_RECV_MARK_SET(c_p);
      SET_I($NEXT_INSTRUCTION);
      goto loop_rec_top__;
      //| -no_next
  }
  
  i_loop_rec(Dest) {
      //| -no_prefetch
  
      /*
       * Pick up the next message and place it in x(0).
       * If no message, jump to a wait or wait_timeout instruction.
       */
  
      ErtsMessage* msgp;
  
      /* Entry point from recv_set */
   loop_rec_top__:
  
      /*
       * We need to disable GC while matching messages
       * in the queue. This since messages with data outside
       * the heap will be corrupted by a GC.
       */
      ASSERT(!(c_p->flags & F_DELAY_GC));
      c_p->flags |= F_DELAY_GC;
  
      /* Entry point from loop_rec_end (and locally) */
   loop_rec__:
  
      if (FCALLS <= 0 && FCALLS <= neg_o_reds) {
          $SET_CP_I_ABS(I);
          c_p->flags &= ~F_DELAY_GC;
          SWAPOUT;
          c_p->arity = 0;
          c_p->current = NULL;
          goto do_schedule;
      }
  
      ASSERT(!ERTS_PROC_IS_EXITING(c_p));
  
      PROCESS_MAIN_CHK_LOCKS(c_p);
  
      msgp = PEEK_MESSAGE(c_p);
  
      if (ERTS_UNLIKELY(msgp == NULL)) {
          int get_out;
          SWAPOUT;
          FCALLS -= erts_proc_sig_receive_helper(c_p, FCALLS, neg_o_reds,
                                                 &msgp, &get_out);
          SWAPIN;
          if (ERTS_UNLIKELY(msgp == NULL)) {
              if (get_out) {
                  if (get_out < 0) {
                      ASSERT(FCALLS <= 0 && FCALLS <= neg_o_reds);
                      goto loop_rec__; /* yield */
                  }
                  else {
                      ASSERT(ERTS_PROC_IS_EXITING(c_p));
                      goto do_schedule; /* exit */
                  }
              }
  
              /*
               * If there are no more messages in queue
               * (and we are not yielding or exiting)
               * erts_proc_sig_receive_helper()
               * returns with message queue lock locked...
               */
              c_p->flags &= ~F_DELAY_GC;
              $SET_I_REL($Dest);
              Goto(*I);		/* Jump to a wait or wait_timeout instruction */
          }
      }
  
      ASSERT(msgp == PEEK_MESSAGE(c_p));
      ASSERT(msgp && ERTS_SIG_IS_MSG(msgp));
  
      if (ERTS_UNLIKELY(ERTS_SIG_IS_EXTERNAL_MSG(msgp))) {
          FCALLS -= 10; /* FIXME: bump appropriate amount... */
          SWAPOUT; /* erts_decode_dist_message() may write to heap... */
          if (!erts_decode_dist_message(c_p, ERTS_PROC_LOCK_MAIN, msgp, 0)) {
              /*
               * A corrupt distribution message that we weren't able to decode;
               * remove it...
               */
              /* No swapin should be needed */
              ASSERT(HTOP == c_p->htop && E == c_p->stop);
              /* TODO: Add DTrace probe for this bad message situation? */
              UNLINK_MESSAGE(c_p, msgp);
              msgp->next = NULL;
              erts_cleanup_messages(msgp);
              goto loop_rec__;
          }
          SWAPIN;
      }
  
      ASSERT(msgp == PEEK_MESSAGE(c_p));
      ASSERT(ERTS_SIG_IS_INTERNAL_MSG(msgp));
  
      r(0) = ERL_MESSAGE_TERM(msgp);
  }
  
  remove_message() {
      //| -no_prefetch
  
      /*
       * Remove a (matched) message from the message queue.
       */
  
      ErtsMessage* msgp;
      PROCESS_MAIN_CHK_LOCKS(c_p);
  
      ERTS_CHK_MBUF_SZ(c_p);
  
      msgp = PEEK_MESSAGE(c_p);
  
      if (ERTS_PROC_GET_SAVED_CALLS_BUF(c_p)) {
          save_calls(c_p, &exp_receive);
      }
      if (ERL_MESSAGE_TOKEN(msgp) == NIL) {
  #ifdef USE_VM_PROBES
          if (DT_UTAG(c_p) != NIL) {
              if (DT_UTAG_FLAGS(c_p) & DT_UTAG_PERMANENT) {
                  SEQ_TRACE_TOKEN(c_p) = am_have_dt_utag;
              } else {
                  DT_UTAG(c_p) = NIL;
                  SEQ_TRACE_TOKEN(c_p) = NIL;
              }
          } else {
  #endif
              SEQ_TRACE_TOKEN(c_p) = NIL;
  #ifdef USE_VM_PROBES
          }
          DT_UTAG_FLAGS(c_p) &= ~DT_UTAG_SPREADING;
  #endif
      } else if (ERL_MESSAGE_TOKEN(msgp) != am_undefined) {
          Eterm msg;
          SEQ_TRACE_TOKEN(c_p) = ERL_MESSAGE_TOKEN(msgp);
  #ifdef USE_VM_PROBES
          if (ERL_MESSAGE_TOKEN(msgp) == am_have_dt_utag) {
              if (DT_UTAG(c_p) == NIL) {
                  DT_UTAG(c_p) = ERL_MESSAGE_DT_UTAG(msgp);
              }
              DT_UTAG_FLAGS(c_p) |= DT_UTAG_SPREADING;
          } else {
  #endif
              ASSERT(is_tuple(SEQ_TRACE_TOKEN(c_p)));
              ASSERT(SEQ_TRACE_TOKEN_ARITY(c_p) == 5);
              ASSERT(is_small(SEQ_TRACE_TOKEN_SERIAL(c_p)));
              ASSERT(is_small(SEQ_TRACE_TOKEN_LASTCNT(c_p)));
              ASSERT(is_small(SEQ_TRACE_TOKEN_FLAGS(c_p)));
              ASSERT(is_pid(SEQ_TRACE_TOKEN_SENDER(c_p)));
              c_p->seq_trace_lastcnt = unsigned_val(SEQ_TRACE_TOKEN_SERIAL(c_p));
              if (c_p->seq_trace_clock < unsigned_val(SEQ_TRACE_TOKEN_SERIAL(c_p))) {
                  c_p->seq_trace_clock = unsigned_val(SEQ_TRACE_TOKEN_SERIAL(c_p));
              }
              msg = ERL_MESSAGE_TERM(msgp);
              seq_trace_output(SEQ_TRACE_TOKEN(c_p), msg, SEQ_TRACE_RECEIVE, 
                               c_p->common.id, c_p);
  #ifdef USE_VM_PROBES
          }
  #endif
      }
  #ifdef USE_VM_PROBES
      if (DTRACE_ENABLED(message_receive)) {
          Eterm token2 = NIL;
          DTRACE_CHARBUF(receiver_name, DTRACE_TERM_BUF_SIZE);
          Sint tok_label = 0;
          Sint tok_lastcnt = 0;
          Sint tok_serial = 0;
  
          dtrace_proc_str(c_p, receiver_name);
          token2 = SEQ_TRACE_TOKEN(c_p);
          if (have_seqtrace(token2)) {
              tok_label = SEQ_TRACE_T_DTRACE_LABEL(token2);
              tok_lastcnt = signed_val(SEQ_TRACE_T_LASTCNT(token2));
              tok_serial = signed_val(SEQ_TRACE_T_SERIAL(token2));
          }
          DTRACE6(message_receive,
                  receiver_name, size_object(ERL_MESSAGE_TERM(msgp)),
                  c_p->sig_qs.len - 1, tok_label, tok_lastcnt, tok_serial);
      }
  #endif
      UNLINK_MESSAGE(c_p, msgp);
      JOIN_MESSAGE(c_p);
      CANCEL_TIMER(c_p);
  
      erts_save_message_in_proc(c_p, msgp);
      c_p->flags &= ~F_DELAY_GC;
  
      if (ERTS_IS_GC_DESIRED_INTERNAL(c_p, HTOP, E)) {
          /*
           * We want to GC soon but we leave a few
           * reductions giving the message some time
           * to turn into garbage.
           */
          ERTS_VBUMP_LEAVE_REDS_INTERNAL(c_p, 5, FCALLS);
      }
  
      ERTS_DBG_CHK_REDS(c_p, FCALLS);
      ERTS_CHK_MBUF_SZ(c_p);
  
      ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
      PROCESS_MAIN_CHK_LOCKS(c_p);
  }
  
  loop_rec_end(Dest) {
      //| -no_next
      /*
       * Advance the save pointer to the next message (the current
       * message didn't match), then jump to the loop_rec instruction.
       */
  
      ASSERT(c_p->flags & F_DELAY_GC);
  
      $SET_I_REL($Dest);
      SAVE_MESSAGE(c_p);
      FCALLS--;
      goto loop_rec__;
  }
  
  timeout_locked() {
      /*
       * A timeout has occurred.  Reset the save pointer so that the next
       * receive statement will examine the first message first.
       */
  
      erts_proc_unlock(c_p, ERTS_PROC_LOCKS_MSG_RECEIVE);
      $timeout();
  }
  
  timeout() {
      if (IS_TRACED_FL(c_p, F_TRACE_RECEIVE)) {
          trace_receive(c_p, am_clock_service, am_timeout, NULL);
      }
      if (ERTS_PROC_GET_SAVED_CALLS_BUF(c_p)) {
          save_calls(c_p, &exp_timeout);
      }
      c_p->flags &= ~F_TIMO;
      JOIN_MESSAGE(c_p);
  }
  
  TIMEOUT_VALUE() {
      c_p->freason = EXC_TIMEOUT_VALUE;
      goto find_func_info;
      //| -no_next
  }
  
  i_wait_error_locked() {
      erts_proc_unlock(c_p, ERTS_PROC_LOCKS_MSG_RECEIVE);
      $TIMEOUT_VALUE();
  }
  
  i_wait_error() {
      $TIMEOUT_VALUE();
  }
  
  wait_timeout_unlocked_int := wait.lock.int.execute;
  wait_timeout_locked_int := wait.int.execute;
  
  wait_timeout_unlocked := wait.lock.src.execute;
  wait_timeout_locked := wait.src.execute;
  
  wait_unlocked := wait.lock.execute;
  wait_locked := wait.unlocked.execute;
  
  wait.lock() {
      erts_proc_lock(c_p, ERTS_PROC_LOCKS_MSG_RECEIVE);
  }
  
  wait.unlocked() {
  }
  
  wait.int(Int) {
      /*
       * If we have already set the timer, we must NOT set it again.  Therefore,
       * we must test the F_INSLPQUEUE flag as well as the F_TIMO flag.
       */
      if ((c_p->flags & (F_INSLPQUEUE | F_TIMO)) == 0) {
          BeamInstr** pi = (BeamInstr **) c_p->def_arg_reg;
          *pi = $NEXT_INSTRUCTION;
          erts_set_proc_timer_uword(c_p, $Int);
      }
  }
  
  wait.src(Src) {
      /*
       * If we have already set the timer, we must NOT set it again.  Therefore,
       * we must test the F_INSLPQUEUE flag as well as the F_TIMO flag.
       */
      if ((c_p->flags & (F_INSLPQUEUE | F_TIMO)) == 0) {
          Eterm timeout_value = $Src;
          if (timeout_value == make_small(0)) {
              erts_proc_unlock(c_p, ERTS_PROC_LOCKS_MSG_RECEIVE);
              $NEXT0();
          } else if (timeout_value == am_infinity) {
              c_p->flags |= F_TIMO;
          } else {
              int tres = erts_set_proc_timer_term(c_p, timeout_value);
              if (tres == 0) {
                  /*
                   * The timer routiner will set c_p->i to the value in
                   * c_p->def_arg_reg[0].  Note that it is safe to use this
                   * location because there are no living x registers in
                   * a receive statement.
                   */
                  BeamInstr** pi = (BeamInstr**) c_p->def_arg_reg;
                  *pi = $NEXT_INSTRUCTION;
              } else { /* Wrong time */
                  erts_proc_unlock(c_p, ERTS_PROC_LOCKS_MSG_RECEIVE);
                  c_p->freason = EXC_TIMEOUT_VALUE;
                  goto find_func_info;
              }
          }
      }
  }
  
  //
  // Prepare to wait indefinitely for a new message to arrive
  // (or the time set above if falling through from above).
  // When a new message arrives, control will be transferred
  // the loop_rec instruction (at label L1).  In case of
  // of timeout, control will be transferred to the timeout
  // instruction following the wait_timeout instruction.
  //
  
  wait.execute(JumpTarget) {
      $SET_REL_I(c_p->i, $JumpTarget); /* L1 */
      SWAPOUT;
      c_p->arity = 0;
  
      if (!ERTS_PTMR_IS_TIMED_OUT(c_p)) {
          erts_atomic32_read_band_relb(&c_p->state,
                                           ~ERTS_PSFLG_ACTIVE);
      }
      ASSERT(!ERTS_PROC_IS_EXITING(c_p));
      erts_proc_unlock(c_p, ERTS_PROC_LOCKS_MSG_RECEIVE);
      c_p->current = NULL;
      goto do_schedule;
      //| -no_next
  }
  