#  vim: filetype=instructions

# Definitions of the Rubinius VM instruction set.
#

# [Description]
#   The classic no-op operator. It performs no actions and does not modify the
#   stack.
# [See Also]
#   pop

instruction noop() [ -- ]
end

section "Push primitive values"

# [Description]
#   The special object `nil` is pushed onto the stack.

instruction push_nil() [ -- nil ]
  stack_push(cNil);
end

# [Description]
#   The special value `true` is pushed onto the stack.

instruction push_true() [ -- true ]
  stack_push(cTrue);
end

# [Description]
#   The special object `false` is pushed onto the stack.

instruction push_false() [ -- false ]
  stack_push(cFalse);
end

# [Description]
#   Pushes the value of the integer literal onto the stack.
# [See Also]
#   meta_push_0
#   meta_push_1
#   meta_push_2
#   meta_push_neg_1
# [Notes]
#   Certain common cases (i.e. -1, 0, 1, and 2) are optimised to avoid the
#   decoding of the argument.

instruction push_int(number) [ -- number ]
  stack_push(Fixnum::from(number));
end

# [Description]
#   The current `self` object is pushed onto the stack.

instruction push_self() [ -- self ]
  stack_push(call_frame->self());
end

section "Manipulate literals"

# [Description]
#   Used to set the value of a literal. The stack top is set to the literal
#   indicated by the operand.
# [Notes]
#   Unlike other literals such as strings and numbers, creating a Regexp
#   literal (i.e. via the /regex/ syntax) is a two step process to create the
#   literal slot for the Regexp, create a literal for the string between the
#   '/' delimiters and create a new Regexp object passing it the string.  Only
#   then can the literal value be set, using the set_literal opcode.

instruction set_literal(literal) [ value -- value ]
  call_frame->compiled_code->literals()->put(state, literal, stack_top());
end

# [Description]
#   The value identified by the operand _literal_ in the current state
#   literals tuple is retrieved and placed onto the stack.
# [Notes]
#   The literals tuple is part of the machine state, and holds all literal
#   objects defined or used within a particular scope.

instruction push_literal(literal) [ -- literal ]
  stack_push(call_frame->compiled_code->literals()->at(state, literal));
end

section "Flow control"

# [Description]
#   Unconditionally moves the instruction pointer to the position specified by
#   _location_.
# [Notes]
#   All goto instructions use absolute addressing. This is absolute movement
#   rather than a relative one, so the operand must specify the ip starting
#   from 0 to move to.
# [See Also]
#   goto_if_true
#   goto_if_false

instruction goto(location) [ -- ] => branch
  store_ip(location);
end

# [Description]
#   Remove the top value on the stack, and if `nil` or `false`, set the
#   instruction pointer to the value specified by _location_.
# [See Also]
#   goto
#   goto_if_true

instruction goto_if_false(location) [ value -- ] => branch
  Object* t1 = stack_pop();
  if(!CBOOL(t1)) {
    store_ip(location);
  }
end

# [Description]
#   Remove the top value on the stack, and if not `nil` or `false`, set the
#   instruction pointer to the value specified by _location_.
# [See Also]
#   goto
#   goto_if_false

instruction goto_if_true(location) [ value -- ] => branch
  Object* t1 = stack_pop();
  if(CBOOL(t1)) {
    store_ip(location);
  }
end

# [Description]
#   Return a value to the direct caller
#
#   Pops the top value from the stack, and returns to the direct caller of the
#   current invocation.
# [Notes]
#   In a method, the `return` keyword uses this instruction. In a block
#   though, `return` uses the raise_return instruction and `next` uses this
#   instruction.
# [See Also]
#   raise_return
#   raise_exc

instruction ret() [ value -- value ] => return
  if(call_frame->scope->made_alias_p()) {
    call_frame->scope->flush_to_heap(state);
  }
  return stack_top();
end

section "Stack manipulations"

# [Description]
#   Swaps the top two values on the stack, so that the second value becomes
#   the first, and the first value becomes the second.

instruction swap_stack() [ s0 s1 -- s1 s0 ]
  Object* t1 = stack_pop();
  Object* t2 = stack_pop();
  stack_push(t1);
  stack_push(t2);
end

# [Description]
#   Read a value from the top of the stack and push it on the stack again
#   without removing the original value.

instruction dup_top() [ s0 -- s0 s0 ]
  stack_push(stack_top());
end

# [Description]
#   Duplicate multiple values on the stack
#
#   Read _count_ values from the stack and push them onto the stack again
#   in order without removing the original values.
# [Stack Before]
#   value1
#   value2
#   ...
# [Stack After]
#   value1
#   value2
#   value1
#   value2
#   ...

instruction dup_many(count) [ +count -- ++count ]
  Object** objs = stack_back_position(count);
  for(intptr_t i = 0; i < count; i++) {
    stack_push(objs[i]);
  }
end

# [Description]
#   Removes the top value from the stack, discarding it.
# [Notes]
#   Pop is typically used when the return value of another opcode is not
#   required.

instruction pop() [ value -- ]
  (void)stack_pop();
end

# [Description]
#   Removes _count_ values from the stack and discard them.
# [Stack Before]
#   value1
#   value2
#   ...
# [Stack After]
#   ...

instruction pop_many(count) [ +count -- ]
  stack_clear(count);
end

# [Description]
#   Reverses the order on the stack of the top _count_ values.
# [Stack Before]
#   obj1
#   obj2
#   obj3
#   ...
# [Stack After]
#   obj3
#   obj2
#   obj1
#   ...

instruction rotate(count) [ +count -- +count ]
  int diff = count >> 1;
  Object** start = STACK_PTR - (count - 1);
  Object** end = STACK_PTR;

  for(int i = 0; i < diff; i++) {
    Object* right = *end;
    Object* left =  *start;

    *start = right;
    *end = left;

    start++;
    end--;
  }
end

# [Description]
#   The top value on the stack is moved down by the specified number of
#   _positions_, with all values above that position shuffling up by one.
# [Stack Before]
#   obj1
#   obj2
#   ...
#   objn
# [Stack After]
#   obj2
#   ...
#   objn
#   obj1

instruction move_down(positions) [ +positions -- +positions ]
  Object* val = stack_top();
  for(int i = 0; i < positions; i++) {
    int target = -i;
    int current = target - 1;
    STACK_PTR[target] = STACK_PTR[current];
  }
  STACK_PTR[-positions] = val;
end

section "Manipulate local variables"

# [Description]
#   Read the top of the stack and set the local variable identified by operand
#   _local_ to it. The stack is not modified by this instruction.
# [See Also]
#   push_local

instruction set_local(local) [ value -- value ]
  call_frame->scope->set_local(local, stack_top());
end

# [Description]
#   Retrieves the current value of the local variable referenced by operand
#   _local_ and push it onto the stack.
# [See Also]
#   set_local

instruction push_local(local) [ -- value ]
  stack_push(call_frame->scope->get_local(local));
end

# [Description]
#   Pushes the value of a local from an enclosing scope onto the stack
#
#   Retrieves the value of a local variable. Operand _depth_ indicates how many
#   upward enclosing scopes to walk up and then operand _index_ indicates which
#   local in that context to read. The value is then pushed on the stack.
# [See Also]
#   set_local_depth
# [Example]
#     k = 0
#     foo.each do |i|
#       bar.each do |j|
#         # i is a local variable from enclosing scope at depth 1
#         # k is a local variable from enclosing scope at depth 2
#         i = i + j + k
#       end
#     end

instruction push_local_depth(depth index) [ -- value ]
  if(depth == 0) {
    Exception::internal_error(state, call_frame,
                              "illegal push_local_depth usage");
    RUN_EXCEPTION();
  } else {
    VariableScope* scope = call_frame->scope->parent();

    if(!scope || scope->nil_p()) {
      Exception::internal_error(state, call_frame,
                                "illegal push_local_depth usage, no parent");
      RUN_EXCEPTION();
    }

    for(int j = 1; j < depth; j++) {
      scope = scope->parent();
      if(!scope || scope->nil_p()) {
        Exception::internal_error(state, call_frame,
                                  "illegal push_local_depth usage, no parent");
        RUN_EXCEPTION();
      }
    }

    if(index >= scope->number_of_locals()) {
      Exception::internal_error(state, call_frame,
                                "illegal push_local_depth usage, bad index");
      RUN_EXCEPTION();
    }

    stack_push(scope->get_local(state, index));
  }
end

# [Description]
#   Updates the value of a local variable contained in an enclosing scope
#
#   Read a value from the top of the stack and use it to update a local
#   variable in an enclosing scope. The _depth_ and _index_ operands
#   identify the specific local the same as in `push_local_depth`.
# [See Also]
#   push_local_depth
# [Example]
#     foo.each do |i|
#       bar.each do |j|
#         i = i + j  # i is a local variable from enclosing scope at depth 1
#       end
#     end

instruction set_local_depth(depth index) [ value -- value ]
  if(depth == 0) {
    Exception::internal_error(state, call_frame,
                              "illegal set_local_depth usage");
    RUN_EXCEPTION();
  } else {
    VariableScope* scope = call_frame->scope->parent();

    if(!scope || scope->nil_p()) {
      Exception::internal_error(state, call_frame,
                                "illegal set_local_depth usage, no parent");
      RUN_EXCEPTION();
    }

    for(int j = 1; j < depth; j++) {
      scope = scope->parent();
      if(!scope || scope->nil_p()) {
        Exception::internal_error(state, call_frame,
                                  "illegal set_local_depth usage, no parent");
        RUN_EXCEPTION();
      }
    }

    if(index >= scope->number_of_locals()) {
      Exception::internal_error(state, call_frame,
                                "illegal set_local_depth usage, bad index");
      RUN_EXCEPTION();
    }
    Object* val = stack_pop();
    scope->set_local(state, index, val);
    stack_push(val);
  }
end

# [Description]
#   Checks if the argument specified by the operand _index_ was passed to
#   the current invocation. If so, push true, otherwise false.
# [Notes]
#   Arguments are specified via a zero-index, so the first argument is 0.

instruction passed_arg(index) [ -- boolean ]
  if(!call_frame->arguments) {
    Exception::internal_error(state, call_frame,
                              "no arguments object");
    RUN_EXCEPTION();
  }

  stack_push(RBOOL(index < (int)call_frame->arguments->total() - mcode->post_args));
end

section "Manipulate exceptions"

# [Description]
#   Pushes the current exception onto the stack, so that it can be used for
#   some purpose, such as checking the exception type, setting an exception
#   variable in a rescue clause, etc.
# [See Also]
#   raise_exc
# [Example]
#     begin
#       foo = BAR        # BAR is not defined
#     rescue NameError   # push_exception used to check type of exception (via ===)
#       puts "No BAR"
#     end

instruction push_current_exception() [ -- exception ]
  stack_push(state->vm()->thread_state()->current_exception());
end

# [Description]
#   Clears any exceptions from the current thread.

instruction clear_exception() [ -- ]
  state->vm()->thread_state()->clear_raise();
end

# [Description]
#   Package up the current exception state into an object and push it. This
#   is used to preserve the exception state around code that might mutate it.
#   For instance, when handling an ensure while an exception is being raised

instruction push_exception_state() [ -- exc_state ]
  stack_push(state->vm()->thread_state()->state_as_object(state));
end

# [Description]
#   Pops a value off the stack and set the threads exception state from it.
#   This instruction is only to be used with a value pushed on the stack
#   by the push_exception_state instruction.
# [See Also]
#   push_exception_state

instruction restore_exception_state() [ exception -- ]
    Object* top = stack_pop();
    if(top->nil_p()) {
      state->vm()->thread_state()->clear();
    } else {
      state->vm()->thread_state()->set_state(state, as<ThreadState>(top));
    }
end

# [Description]
#   Raises an exception
#
#   Pops a value off the stack and make it the current exception.
#   If the value is not an instance of Exception, a TypeError is raised.

instruction raise_exc() [ value -- ] => raise
  flush_ip();
  Object* t1 = stack_pop();
  Exception* exc = as<Exception>(t1);
  exc->locations(state, Location::from_call_stack(state, call_frame));
  state->raise_exception(exc);
  RUN_EXCEPTION();
end

# [Description]
#   Register an unwind handler
#
#   Registers what to happen when an exception wants to unwind through the
#   current invocation. Operand _ip_ specifies where to set the instruction
#   pointer if used. Operand _type_ is either 0 for if the value should be
#   used in rescue style (not run when unwinding because of a return caused by
#   `raise_return`) or 1 for ensure style (always used). The registrations are
#   nested within the current invocation and are automatically removed from
#   the registry when they are used. The `pop_unwind` instruction can be used
#   to remove an unused registration.
# [Notes]
#   The registration also contains the value of the stack depth when
#   created. If the registration is used, then the stack depth is
#   restored to the value contained in the registration
# [See Also]
#   pop_unwind

instruction setup_unwind(ip type) [ -- ] => handler
  unwinds.push(ip, stack_calculate_sp(), (UnwindType)type);
end

# [Description]
#   Remove the next unused unwind registration from the current invocation.
#   This instruction is paired with `setup_unwind` to remove registrations
#   when control exits a section of code that registered a handler but didn't
#   use it. For example, exiting a begin that had a rescue expression.
# [See Also]
#   setup_unwind

instruction pop_unwind() [ -- ]
  if(!unwinds.has_unwinds()) {
    Exception::internal_error(state, call_frame, "unbalanced pop_unwind");
    RUN_EXCEPTION();
  }
  unwinds.drop();
end

# [Description]
#   Cause the toplevel enclosing scope to return
#
#   Only used in a block, pop a value from the stack and raise a special
#   internal exception and begin unwinding the stack. The toplevel method
#   scope will rescue the exception and return the value.
# [See Also]
#   ret

instruction raise_return() [ value -- value ] => raise
  flush_ip();
  if(!(call_frame->flags & CallFrame::cIsLambda) &&
     !call_frame->scope_still_valid(call_frame->top_scope(state))) {
    Exception* exc = Exception::make_exception(state, G(jump_error), "unexpected return");
    exc->locations(state, Location::from_call_stack(state, call_frame));
    state->raise_exception(exc);
  } else {
    if(call_frame->flags & CallFrame::cIsLambda) {
      state->vm()->thread_state()->raise_return(stack_top(), call_frame->promote_scope(state));
    } else {
      state->vm()->thread_state()->raise_return(stack_top(), call_frame->top_scope(state));
    }
  }
  RUN_EXCEPTION();
end

# [Description]
#   Return from a scope but run ensures first
#
#   A one use instruction, used only in a method toplevel within a begin
#   that has an ensure. Use the same internal exception as `raise_return`
#   which will coax the ensure registration to run.
# [See Also]
#   ret
#   raise_return

instruction ensure_return() [ value -- value ] => raise
  flush_ip();
  state->vm()->thread_state()->raise_return(stack_top(), call_frame->promote_scope(state));
  RUN_EXCEPTION();
end

# [Description]
#   Cause the method that yielded the current block to return. Used to
#   implement the `break` keyword in a block.

instruction raise_break() [ value -- value ] => raise
  flush_ip();
  if(call_frame->flags & CallFrame::cIsLambda) {
    return stack_top();
  } else if(call_frame->scope_still_valid(call_frame->scope->parent())) {
    state->vm()->thread_state()->raise_break(stack_top(), call_frame->scope->parent());
  } else {
    Exception* exc = Exception::make_exception(state, G(jump_error), "attempted to break to exited method");
    exc->locations(state, Location::from_call_stack(state, call_frame));
    state->raise_exception(exc);
  }
  RUN_EXCEPTION();
end

# [Description]
#   Continue unwinding the stack with the current exception. Verify that there
#   is a current exception, then begin the unwinding process again.

instruction reraise() [ -- ] => raise
  interp_assert(state->vm()->thread_state()->raise_reason() != cNone);
  RUN_EXCEPTION();
end

section "Manipulate arrays"

# [Description]
#   Create an array and populate with values on the stack
#
#   Creates a new array, populating its contents by remove the number of
#   values specified by operand _count_ and putting them into the array in the
#   order they were on the stack. The resulting array is pushed onto the
#   stack.
# [Stack Before]
#   valueN
#   ...
#   value2
#   value1
#   ...
# [Stack After]
#   [value1, value2, ..., valueN]
#   ...

instruction make_array(count) [ +count -- array ]
  Object* t2;
  Array* ary = Array::create_dirty(state, count);
  Tuple* tup = ary->tuple();

#ifdef RBX_ALLOC_TRACKING
  if(unlikely(state->vm()->allocation_tracking())) {
    ary->setup_allocation_site(state, call_frame);
  }
#endif
  int j = count - 1;
  for(; j >= 0; j--) {
    t2 = stack_pop();
    tup->put(state, j, t2);
  }
  ary->total(state, Fixnum::from(count));
  stack_push(ary);
end

# [Description]
#   Removes the object on the top of the stack, and:
#
#   1. If the input is a tuple, a new array object is created based on the
#      tuple data.
#   2. If the input is an array, it is unmodified.
#   3. If in 1.9 mode and the input is nil, an empty Array is returned
#
#   If the input is any other type, call `Rubinius::Type.coerce_to_array(value)`.
#   If the return value of the method call is an `Array`, make it the result.
#   Otherwise make the result an 1 element `Array` contain the original value.
#
#   The resulting array is then pushed back onto the stack.

instruction cast_array() [ value -- array ]
  // Use stack_top and not stack_pop because we may need
  // to preserve and reread the value from the stack below.
  Object* t1 = stack_top();
  if(t1->nil_p()) {
    t1 = Array::create_dirty(state, 0);
  } else if(Tuple* tup = try_as<Tuple>(t1)) {
    t1 = Array::from_tuple(state, tup);
  } else if(!kind_of<Array>(t1)) {
    Object* recv = G(type);
    Arguments args(G(sym_coerce_to_array), recv, 1, &t1);
    Dispatch dis(G(sym_coerce_to_array));

    Object* res = dis.send(state, call_frame, args);

    // If the send still doesn't produce an array, wrap
    // the value in one.
    if(res && !kind_of<Array>(res)) {
      Array* ary = Array::create_dirty(state, 1);
      // Don't read t1 here, it's not GC safe because we called
      // a method.
      ary->set(state, 0, stack_top());
      t1 = ary;
    } else {
      t1 = res;
    }
  }

  (void)stack_pop(); // Remove original value
  CHECK_AND_PUSH(t1);
end

# [Description]
#   Pops an array off the top of the stack. If the array is empty, it is
#   pushed back onto the stack, followed by `nil`.
#
#   Otherwise, the array is shifted, then pushed back onto the stack,
#   followed by the object that was shifted from the front of the array.
# [Stack Before]
#   [value1, value2, ..., valueN]
#   ...
# [Stack After]
#   value1
#   [value2, ..., valueN]
#   ...

instruction shift_array() [ array -- array value ]
  Array* array = as<Array>(stack_pop());
  native_int size = array->size();

  if(size == 0) {
    stack_push(array);
    stack_push(cNil);
  } else {
    native_int j = size - 1;
    Object* shifted_value = array->get(state, 0);
    Array* smaller_array = Array::create_dirty(state, j);

    for(native_int i = 0; i < j; i++) {
      smaller_array->set(state, i, array->get(state, i+1));
    }

    stack_push(smaller_array);
    stack_push(shifted_value);
  }
end

section "Manipulate instance variables"

# [Description]
#   Pops a value off the stack, and uses it to set the value of the instance
#   variable identifies by the literal specified by operand _literal_.  The
#   value popped off the stack is then pushed back on again.

instruction set_ivar(literal) [ value -- value ]
  if(CBOOL(call_frame->self()->frozen_p(state))) {
    Exception::frozen_error(state, call_frame, call_frame->self());
    RUN_EXCEPTION();
  }
  Symbol* sym = as<Symbol>(call_frame->compiled_code->literals()->at(state, literal));
  call_frame->self()->set_ivar(state, sym, stack_top());
end

# [Description]
#   Pushes the instance variable identified by _literal_ onto the stack.

instruction push_ivar(literal) [ -- value ]
  Symbol* sym = as<Symbol>(call_frame->compiled_code->literals()->at(state, literal));
  Object* ret = call_frame->self()->get_ivar(state, sym);

  CHECK_AND_PUSH(ret);
end

section "Manipulate constants"

# [Description]
#   Locates the constant indicated by the operand _literal_ from the current
#   context, and pushes it onto the stack. If the constant cannot be found in
#   the current context, nothing is pushed onto the stack, and a NameError
#   exception is raised.
# [Example]
#     engine = RUBY_ENGINE # RUBY_ENGINE is a constant defined by Rubinius

instruction push_const(literal) [ -- constant ]
  ConstantMissingReason reason;
  Symbol* sym = as<Symbol>(call_frame->compiled_code->literals()->at(state, literal));
  Object* res = Helpers::const_get(state, call_frame, sym, &reason);
  if(reason != vFound) {
    flush_ip();
    res = Helpers::const_missing(state, sym, call_frame);
  } else if(Autoload* autoload = try_as<Autoload>(res)) {
    flush_ip();
    res = autoload->resolve(state, gct, call_frame);
  }

  CHECK_AND_PUSH(res);
end

# [Description]
#   Pops an object off the stack, and uses value to set a constant named
#   by the _literal_. The value is pushed back onto the stack.

instruction set_const(literal) [ value -- value ]
  Symbol* sym = as<Symbol>(call_frame->compiled_code->literals()->at(state, literal));
  call_frame->constant_scope()->module()->set_const(state, sym, stack_top());
end

# [Description]
#   Pop a value from the literals table specified by the operand _literal_ and
#   use it as the value of a constant named inside a Module object popped from
#   the stack.  The _value_ is pushed back on the stack.
# [Stack Before]
#   value
#   module
#   ...
# [Stack After]
#   value
#   ...

instruction set_const_at(literal) [ value module -- value ]
  Symbol* sym = as<Symbol>(call_frame->compiled_code->literals()->at(state, literal));
  Object* val = stack_pop();
  Module* under = as<Module>(stack_pop());

  under->set_const(state, sym, val);
  stack_push(val);
end

# [Description]
#   Pops _module_ off the stack, and searches within its namespace for the
#   constant named by the literal specified by the operand _literal_. If found,
#   it is pushed onto the stack; otherwise, nothing is pushed onto the stack,
#   and a `NameError` exception is raised.
# [Example]
#     str = "abc"
#     enum = Enumerable::Enumerator(str, :each_byte)

instruction find_const(literal) [ module -- constant ]
  ConstantMissingReason reason;
  Module* under = as<Module>(stack_pop());
  Symbol* sym = as<Symbol>(call_frame->compiled_code->literals()->at(state, literal));
  Object* res = Helpers::const_get_under(state, under, sym, &reason);
  if(reason != vFound) {
    flush_ip();
    res = Helpers::const_missing_under(state, under, sym, call_frame);
  } else if(Autoload* autoload = try_as<Autoload>(res)) {
    flush_ip();
    res = autoload->resolve(state, gct, call_frame, under);
  }

  CHECK_AND_PUSH(res);
end

# [Description]
#   Pushes the top-level global object that represents the top-level namespace
#   for constants. Used to find constants relative to the toplevel. In Ruby,
#   this is the class `Object`.

instruction push_cpath_top() [ -- constant ]
  stack_push(G(object));
end

# [Description]
#   Pushes a constant onto the stack. Caches the lookup to provide faster
#   future lookup. This instruction is normally emitted only by the Generator.
# [See Also]
#   push_const
# [Example]
#     engine = RUBY_ENGINE # RUBY_ENGINE is a constant defined by Rubinius

instruction push_const_fast(literal) [ -- constant ]

  ConstantCache* cache = reinterpret_cast<ConstantCache*>(literal);

  Object* res = cache->retrieve(state, call_frame->constant_scope());

  if(!res) {
    ConstantMissingReason reason;
    flush_ip();

    Symbol* sym = cache->name();
    res = Helpers::const_get(state, call_frame, sym, &reason);

    if(reason == vFound) {
      OnStack<2> os(state, cache, res);
      if(Autoload* autoload = try_as<Autoload>(res)) {
        flush_ip();
        res = autoload->resolve(state, gct, call_frame);
      }

      if(res) {
        ConstantCache* update = ConstantCache::create(state, cache, res, call_frame->constant_scope());
        cache->update_constant_cache(state, update);
      }
    } else {
      res = Helpers::const_missing(state, sym, call_frame);
    }
  }

  CHECK_AND_PUSH(res);
end

# [Description]
#   Pushes a constant onto the stack scoped under the module on the top of
#   the stack. Caches the lookup to provide faster future lookup. This
#   instruction is normally emitted only by the Generator.
# [See Also]
#   find_const
# [Example]
# [Example]
#     str = "abc"
#     enum = Enumerable::Enumerator(str, :each_byte)

instruction find_const_fast(literal) [ module -- constant ]

  Module* under = as<Module>(stack_pop());

  ConstantCache* cache = reinterpret_cast<ConstantCache*>(literal);

  Object* res = cache->retrieve(state, under, call_frame->constant_scope());

  if(!res) {
    ConstantMissingReason reason;
    flush_ip();

    Symbol* sym = cache->name();
    res = Helpers::const_get_under(state, under, sym, &reason);

    if(reason == vFound) {
      OnStack<3> os(state, cache, under, res);
      if(Autoload* autoload = try_as<Autoload>(res)) {
        flush_ip();
        res = autoload->resolve(state, gct, call_frame, under);
      }

      if(res) {
        ConstantCache* update = ConstantCache::create(state, cache, res, under, call_frame->constant_scope());
        cache->update_constant_cache(state, update);
      }
    } else {
      res = Helpers::const_missing_under(state, under, sym, call_frame);
    }
  }

  CHECK_AND_PUSH(res);
end

section "Send messages"

# [Description]
#   The call flags on the current execution context are set to the opcode
#   argument _flags_.
# [Notes]
#   Currently this only has one use, which is that send_stack_with_splat
#   checks if flags is set to CALL_FLAG_CONCAT which indicates that
#   the splat represents arguments at the beginning rather than the end.

instruction set_call_flags(flags) [ -- ]
  SET_CALL_FLAGS(flags);
end

# [Description]
#   Indicate that the next send is allowed to see `private` methods.

instruction allow_private() [ -- ]
end

# [Description]
#   Pops a _receiver_ object off the top of the stack and sends it the
#   message specified by the operand _literal_ with zero arguments.
#
#   When the method returns, the return value is pushed on the stack.
# [See Also]
#   send_stack
# [Notes]
#   This form of send is for methods that take no arguments.

instruction send_method(literal) [ receiver -- value ] => send
  flush_ip();
  Object* recv = stack_top();
  CallSite* call_site = reinterpret_cast<CallSite*>(literal);

  Arguments args(call_site->name(), recv, cNil, 0, 0);
  Object* ret = call_site->execute(state, call_frame, args);

  (void)stack_pop();

  CHECK_AND_PUSH(ret);
end

# [Description]
#   Sends a message with arguments on the stack
#
#   Pops the _receiver_ of the message off the stack and sends the message
#   specified by the operand _literal_ with _count_ arguments. The arguments
#   are removed from the stack also.
#
#   When the method returns, the return value is pushed on the stack.
# [Stack Before]
#   argN
#   ...
#   arg2
#   arg1
#   receiver
# [Stack After]
#   value
#   ...
# [See Also]
#   send_stack_with_block
# [Notes]
#   This opcode does not pass a block to the receiver; see
#   `send_stack_with_block` for the equivalent op code used when a block is to
#   be passed.

instruction send_stack(literal count) [ receiver +count -- value ] => send
  flush_ip();
  Object* recv = stack_back(count);
  CallSite* call_site = reinterpret_cast<CallSite*>(literal);

  Arguments args(call_site->name(), recv, cNil, count,
                 stack_back_position(count));

  Object* ret = call_site->execute(state, call_frame, args);

  stack_clear(count + 1);

  CHECK_AND_PUSH(ret);
end

# [Description]
#   Sends a message with arguments and a block on the stack
#
#   Pops the _receiver_ of the message off the stack and sends the message
#   specified by the operand _literal_ with _count_ arguments. The arguments
#   are removed from the stack also. A value that represents the block to pass
#   on is popped off the stack after the normal arguments.
#
#   When the method returns, the return value will be on top of the stack.
# [Stack Before]
#   block
#   argN
#   ...
#   arg2
#   arg1
#   receiver
# [Stack After]
#   retval
#   ...
# [See Also]
#   send_stack
# [Notes]
#   This opcode passes a block to the receiver; see `send_stack` for the
#   equivalent op code used when no block is to be passed.

instruction send_stack_with_block(literal count) [ block receiver +count -- value ] => send
  flush_ip();
  Object* block = stack_pop();
  Object* recv = stack_back(count);
  CallSite* call_site = reinterpret_cast<CallSite*>(literal);

  Arguments args(call_site->name(), recv, block, count,
                 stack_back_position(count));

  Object* ret = call_site->execute(state, call_frame, args);

  stack_clear(count + 1);

  CHECK_AND_PUSH(ret);
end

# [Description]
#   Sends a message with static arguments, a block, and a splat.
#
#   Pops the _receiver_ of the message off the stack and sends the message
#   specified by the operand _literal_ with _count_ arguments. The arguments
#   are removed from the stack also. A value that represents additional
#   arguments packaged up as an Array is then popped from the stack. A value
#   that represents the block to pass is then popped off the stack.
#
#   When the method returns, the return value will be on top of the stack.
# [Stack Before]
#   block
#   splat
#   argN
#   ...
#   arg2
#   arg1
#   receiver
# [Stack After]
#   retval
#   ...
# [See Also]
#   send_stack_with_block

define CALL_FLAG_CONCAT 2

instruction send_stack_with_splat(literal count) [ block array receiver +count -- value ] => send
  flush_ip();
  Object* block = stack_pop();
  Object* ary   = stack_pop();
  Object* recv =  stack_back(count);
  CallSite* call_site = reinterpret_cast<CallSite*>(literal);

  Arguments args(call_site->name(), recv, block, count,
                 stack_back_position(count));

  if(!ary->nil_p()) {
    if(CALL_FLAGS() & CALL_FLAG_CONCAT) {
      args.prepend(state, as<Array>(ary));
    } else {
      args.append(state, as<Array>(ary));
    }
  }

  SET_CALL_FLAGS(0);

  Object* ret = call_site->execute(state, call_frame, args);

  stack_clear(count + 1);

  CHECK_AND_PUSH(ret);
end

# [Description]
#   Call a method on the superclass with a block
#
#   The same as `send_stack_with_block`, but receiver is the current self
#   instead of being read from the stack, and the method to call is looked up
#   starting with the receiver superclass.
# [Stack Before]
#   block
#   argN
#   ...
#   arg2
#   arg1
# [Stack After]
#   retval
#   ...

instruction send_super_stack_with_block(literal count) [ block +count -- value ] => send
  flush_ip();
  Object* block = stack_pop();
  CallSite* call_site = reinterpret_cast<CallSite*>(literal);
  Object* const recv = call_frame->self();

  Arguments new_args(call_site->name(), recv, block, count,
                     stack_back_position(count));

  Object* ret = call_site->execute(state, call_frame, new_args);

  stack_clear(count);

  CHECK_AND_PUSH(ret);
end

# [Description]
#   Call a method on the superclass, passing args plus a block.
#
#   The same as `send_stack_with_block`, but receiver is the current `self`
#   instead of being read from the stack, and the method to call is looked up
#   starting with the receiver superclass.
# [Stack Before]
#   block
#   argN
#   ...
#   arg2
#   arg1
# [Stack After]
#   retval
#   ...

instruction send_super_stack_with_splat(literal count) [ block array +count -- value ] => send
  flush_ip();
  Object* block = stack_pop();
  Object* ary   = stack_pop();
  Object* const recv = call_frame->self();
  CallSite* call_site = reinterpret_cast<CallSite*>(literal);

  Arguments new_args(call_site->name(), recv, block, count,
                     stack_back_position(count));

  if(!ary->nil_p()) {
    if(CALL_FLAGS() & CALL_FLAG_CONCAT) {
      new_args.prepend(state, as<Array>(ary));
    } else {
      new_args.append(state, as<Array>(ary));
    }
  }

  SET_CALL_FLAGS(0);

  Object* ret = call_site->execute(state, call_frame, new_args);

  stack_clear(count);

  CHECK_AND_PUSH(ret);
end

section "Manipulate blocks"

# [Description]
#   Pushes the current block onto the stack. The value is not wrapped in a
#   `Proc` if it is a `BlockEnvironment`.
# [See Also]
#   push_proc

instruction push_block() [ -- block ]
  stack_push(call_frame->scope->block());
end

# [Description]
#   Check if exactly _count_ arguments were passed to the current invocation.
# [Notes]
#   _This instruction is deprecated and no longer used._

instruction passed_blockarg(count) [ -- boolean ]
  if(!call_frame->arguments) {
    Exception::internal_error(state, call_frame,
                              "no arguments object");
    RUN_EXCEPTION();
  }

  stack_push(RBOOL(count == (int)call_frame->arguments->total()));
end

# [Description]
#   Read a CompiledCode specified by the operand +literal+ and create a
#   `BlockEnvironment`.  Push the new `BlockEnvironment` object on the stack.

instruction create_block(literal) [ -- block ]
  Object* _lit = call_frame->compiled_code->literals()->at(state, literal);
  CompiledCode* code = as<CompiledCode>(_lit);

  Object* be = BlockEnvironment::under_call_frame(state, gct, code, mcode, call_frame);

  CHECK_AND_PUSH(be);
end

# [Description]
#   Converts the value on the top of the stack into an argument for a block
#   taking one argument.
#
#   The value on the top of the stack is popped, and:
#
#   If it has no fields, the result is `nil`.
#
#   If the value contains a single field, the result is the value in the
#   first field.
#
#   Otherwise, package up all the arguments in an `Array` as the result.
#
#   The result is then pushed onto the stack.
# [Notes]
#   This is a single use instruction, only used to simplify how to handle a
#   block that accepts one argument.

instruction cast_for_single_block_arg() [ -- argument ]
  if(!call_frame->arguments) {
    Exception::internal_error(state, call_frame,
                              "no arguments object");
    RUN_EXCEPTION();
  }

  int k = call_frame->arguments->total();
  if(k == 0) {
    stack_push(cNil);
  } else if(k == 1) {
    stack_push(call_frame->arguments->get_argument(0));
  } else {
    Array* ary = Array::create_dirty(state, k);
    for(int i = 0; i < k; i++) {
      ary->set(state, i, call_frame->arguments->get_argument(i));
    }
    stack_push(ary);
  }
end

# [Description]
#   Converts a block argument single-valued tuple into multiple arguments if
#   the arg is an array.
#
#   If the Proc invoked from was in lambda mode, and one argument is passed:
#     * and it's an Array, push it.
#     * and it responds to `#to_ary`, try and convert it and push it.
#     * otherwise wrap it in a one element Array and push it.
#
#   Otherwise:
#     Package up the arguments into an `Array` and push it onto the stack.
# [Stack Before]
#   value1
#   value2
#   ...
# [Stack After]
#   array[value1,..] | value1
#   ...
# [Example]
#     [[1,2,3]].each do |i,j,k|
#       # do something
#     end
# [Notes]
#   This is a single use instruction, only used to simplify how to handle a
#   block that accepts 2 or more arguments. The semantics for this instruction
#   change depending on if the current block invocation is from a Proc with
#   lambda semantics or not.

instruction cast_for_multi_block_arg() [ -- array ]
  if(!call_frame->arguments) {
    Exception::internal_error(state, call_frame,
                              "no arguments object");
    RUN_EXCEPTION();
  }

  /* If there is only one argument and that thing is an array...
     AND the thing being invoked is not a lambda... */
  if(!(call_frame->flags & CallFrame::cIsLambda) &&
       call_frame->arguments->total() == 1) {
    Object* obj = call_frame->arguments->get_argument(0);
    if(kind_of<Array>(obj)) {
      stack_push(obj);
    } else if(CBOOL(obj->respond_to(state, G(sym_to_ary), cFalse))) {
      obj = obj->send(state, call_frame, G(sym_to_ary));
      if(!obj) RUN_EXCEPTION();
      if(kind_of<Array>(obj)) {
        stack_push(obj);
      } else {
        Exception::type_error(state, "to_ary must return an Array", call_frame);
        RUN_EXCEPTION();
      }
    } else {
      Array* ary = Array::create_dirty(state, 1);
      ary->set(state, 0, obj);
      stack_push(ary);
    }
  } else {
    Array* ary = Array::create_dirty(state, call_frame->arguments->total());
    for(size_t i = 0; i < call_frame->arguments->total(); i++) {
      ary->set(state, i, call_frame->arguments->get_argument(i));
    }
    stack_push(ary);
  }
end

# [Description]
#   Take all arguments passed to the current invocation and package
#   them into an `Array`, which is then pushed on the stack.

instruction cast_for_splat_block_arg() [ -- arguments ]
  if(!call_frame->arguments) {
    Exception::internal_error(state, call_frame,
                              "no arguments object");
    RUN_EXCEPTION();
  }

  if(call_frame->arguments->total() == 1) {
    Object* obj = call_frame->arguments->get_argument(0);
    if(!kind_of<Array>(obj)) {
      /* Yes, you are reading this code correctly: In Ruby 1.8, calling a
       * block with these forms { |*| } and { |*a| } with a single argument
       * that is not an Array and which responds to #to_ary will cause #to_ary
       * to be called and its return value ignored. Ultimately, the original
       * object itself is wrapped in an Array and passed to the block.
       */
      if(CBOOL(obj->respond_to(state, G(sym_to_ary), cFalse))) {
        OnStack<1> os(state, obj);
        Object* ignored = obj->send(state, call_frame, G(sym_to_ary));
        if(!ignored) RUN_EXCEPTION();
        if(!ignored->nil_p() && !kind_of<Array>(ignored)) {
          Exception::type_error(state, "to_ary must return an Array", call_frame);
          RUN_EXCEPTION();
        }
      }
    }
    Array* ary = Array::create_dirty(state, 1);
    ary->set(state, 0, obj);
    stack_push(ary);
  } else {
    Array* ary = Array::create_dirty(state, call_frame->arguments->total());
    for(size_t i = 0; i < call_frame->arguments->total(); i++) {
      ary->set(state, i, call_frame->arguments->get_argument(i));
    }
    stack_push(ary);
  }
end

# [Description]
#   Invoke the current block, passing _count_ arguments to it.
# [Stack Before]
#   argN
#   ...
#   arg2
#   arg1
#   ...
# [Stack After]
#   value
#   ...
# [See Also]
#   send_stack

instruction yield_stack(count) [ +count -- value ] => yield
  flush_ip();
  Object* t1 = call_frame->scope->block();
  Object* ret;
  Arguments args(G(sym_call), t1, count, stack_back_position(count));

  if(BlockEnvironment *env = try_as<BlockEnvironment>(t1)) {
    ret = env->call(state, call_frame, args);
  } else if(Proc* proc = try_as<Proc>(t1)) {
    ret = proc->yield(state, call_frame, args);
  } else if(t1->nil_p()) {
    state->raise_exception(Exception::make_lje(state, call_frame));
    ret = NULL;
  } else {
    Dispatch dis(G(sym_call));
    ret = dis.send(state, call_frame, args);
  }

  stack_clear(count);

  CHECK_AND_PUSH(ret);
end

# [Description]
#   Invoke the current block, passing _count_ arguments to it in
#   addition to the values in the `Array` _array_.
# [Stack Before]
#   array
#   argN
#   ...
#   arg2
#   arg1
#   ...
# [Stack After]
#   value
#   ...
# [See Also]
#   send_stack_with_splat

instruction yield_splat(count) [ array +count -- value ] => yield
  flush_ip();
  Object* ary = stack_pop();
  Object* t1 = call_frame->scope->block();

  Arguments args(G(sym_call), t1, count, stack_back_position(count));

  if(!ary->nil_p()) {
    args.append(state, as<Array>(ary));
  }

  Object* ret;
  if(BlockEnvironment *env = try_as<BlockEnvironment>(t1)) {
    ret = env->call(state, call_frame, args);
  } else if(Proc* proc = try_as<Proc>(t1)) {
    ret = proc->yield(state, call_frame, args);
  } else if(t1->nil_p()) {
    state->raise_exception(Exception::make_lje(state, call_frame));
    ret = NULL;
  } else {
    Dispatch dis(G(sym_call));
    ret = dis.send(state, call_frame, args);
  }

  stack_clear(count);

  CHECK_AND_PUSH(ret);
end

section "Manipulate strings"

# [Description]
#   Pops two strings off the stack, appends the second to the first, and
#   then pushes the combined string back onto the stack.
# [Notes]
#   The original string is modified by the append.

instruction string_append() [ prefix suffix -- string ]
  flush_ip();
  String* s1 = as<String>(stack_pop());
  String* s2 = as<String>(stack_pop());
  s1->append(state, s2);
  stack_push(s1);
end

# [Description]
#   Build a new string using many substrings
#
#   Remove _count_ elements from the stack and interpret each as a `String`.
#   Build a new string which is all the removed elements concatenated together in
#   the order they were on the stack.
#
#   Push the resulting string.
# [Stack Before]
#   stringN
#   ...
#   string2
#   string1
# [Stack After]
#   string1string2..stringN
#   ...

instruction string_build(count) [ +count -- string ]
  flush_ip();
  size_t size = 0;

  bool tainted = false;
  bool untrusted = false;

  bool check_encoding = false;
  Encoding* enc = nil<Encoding>();

  // Figure out the total size
  for(int i = 0; i < count; i++) {
    Object* obj = stack_back(i);

    if(obj->reference_p()) {
      tainted |= obj->is_tainted_p();
      untrusted |= obj->is_untrusted_p();
    }

    String* str = try_as<String>(obj);

    if(str) {
      native_int cur_size = str->byte_size();
      native_int data_size = as<ByteArray>(str->data())->size();
      if(unlikely(cur_size > data_size)) {
        cur_size = data_size;
      }
      size += cur_size;
    } else {
      // This isn't how MRI does this. If sub isn't a String, it converts
      // the original object via any_to_s, not the bad value returned from #to_s.
      // This quite a bit harder to implement in rubinius atm, so I'm opting for
      // this way instead.

      str = obj->to_s(state, false);

      tainted |= str->is_tainted_p();
      untrusted |= str->is_untrusted_p();
      native_int cur_size = str->byte_size();
      native_int data_size = as<ByteArray>(str->data())->size();
      if(unlikely(cur_size > data_size)) {
        cur_size = data_size;
      }
      size += cur_size;

      // TRICKY! Reuse the stack to store our new String value.
      stack_back(i) = str;
    }

    /*
     * TODO: Consider the case when -K is set (not implemented yet).
     */
    if(!check_encoding) {
      Encoding* str_enc = str->encoding(state);
      if(enc->nil_p()) {
        enc = str_enc;
      } else if(str_enc != enc) {
        check_encoding = true;
        enc = nil<Encoding>();
      }
    }
  }

  String* str = String::create(state, 0, size);
#ifdef RBX_ALLOC_TRACKING
  if(unlikely(state->vm()->allocation_tracking())) {
    str->setup_allocation_site(state, call_frame);
  }
#endif

  uint8_t* pos = str->byte_address();
  native_int str_size = 0;

  for(int i = count - 1; i >= 0; i--) {
    Object* obj = stack_back(i);

    // We can force here because we've typed check them above.
    String* sub = force_as<String>(obj);

    native_int sub_size = sub->byte_size();
    native_int data_size = as<ByteArray>(sub->data())->size();
    if(unlikely(sub_size > data_size)) {
      sub_size = data_size;
    }

    if(check_encoding) {
      if(i < count - 1) {
        str->num_bytes(state, Fixnum::from(str_size));

        Encoding* enc = Encoding::compatible_p(state, str, sub);

        if(enc->nil_p()) {
          Exception::encoding_compatibility_error(state, str, sub);
          RUN_EXCEPTION();
        } else {
          str->encoding(state, enc);
        }
      } else {
        str->encoding(state, sub->encoding());
      }
    }

    memcpy(pos + str_size, sub->byte_address(), sub_size);
    str_size += sub_size;
  }

  /* We had to set the size of the result String before every Encoding check
   * so we have to set it to the final size here.
   */
  if(check_encoding) {
    str->num_bytes(state, Fixnum::from(size));
    str->ascii_only(state, cNil);
  }
  if(!enc->nil_p()) str->encoding(state, enc);

  if(tainted) str->set_tainted();
  if(untrusted) str->set_untrusted();

  stack_clear(count);
  stack_push(str);
end

# [Description]
#   Consume the string on the stack, replacing it with a duplicate. Mutating
#   operations on the original string will not affect the duplicate, and
#   vice-versa.

instruction string_dup() [ string -- string ]
  flush_ip();
  String *s1 = as<String>(stack_pop());
  String *dup = s1->string_dup(state);
#ifdef RBX_ALLOC_TRACKING
  if(unlikely(state->vm()->allocation_tracking())) {
    dup->setup_allocation_site(state, call_frame);
  }
#endif
  stack_push(dup);
end

section "Manipulate scope"

# [Description]
#   Pushes the current `ConstantScope` object on the stack. Many operations are
#   defered to the current scope. This operation retrieves the current scope
#   so methods can be called on it.

instruction push_scope() [ -- scope ]
  stack_push(call_frame->constant_scope());
end

# [Description]
#   Create a new `ConstantScope` object for the given Module on the stack.
#   This scope is chained off the current scope of the method.

instruction add_scope() [ module -- ]
  Object* obj = stack_pop();
  Module* mod = as<Module>(obj);
  ConstantScope* scope = ConstantScope::create(state);
  scope->module(state, mod);
  scope->parent(state, call_frame->constant_scope());
  call_frame->constant_scope_ = scope;
end

# [Description]
#   Push the `VariableScope` for the current method/block invocation on the
#   stack.

instruction push_variables() [ -- scope ]
  stack_push(call_frame->promote_scope(state));
end

section "Miscellaneous. TODO: better categorize these"

# [Description]
#   Perform required occasional checks that must be done.  This instruction is
#   used by loops to allow them to be interrupted externally, and thus also
#   cause the current method to heat up.

instruction check_interrupts() [ -- ]
  flush_ip();

  // This is used in loops, and allows loops to heat a method up.
  if(mcode->call_count >= 0) {
    mcode->loop_count++;
    mcode->call_count++;
  }

  if(!state->check_async(call_frame)) RUN_EXCEPTION();

  state->checkpoint(gct, call_frame);
end

# [Description]
#   Pauses virtual machine execution and yields control to the debugger on the
#   debug channel. If no debugger is registered, an error is raised.
# [Notes]
#   _This instruction is deprecated and should not be used._

instruction yield_debugger() [ -- ]
  flush_ip();
  Helpers::yield_debugger(state, gct, call_frame, cNil);
end

# [Description]
#   Pop the _value_ from the stack, and push `true` or `false` depending on
#   whether the consumed value was the special value `nil`.

instruction is_nil() [ value -- boolean ]
  stack_push(RBOOL(stack_pop()->nil_p()));
end

# [Description]
#   Checks if the specified method serial number matches an expected value.
#
#   Pops the _receiver_ object from the stack and checks if it responds to the
#   message specified by the operand _literal_ and the target method has
#   serial number _serial_. If so, push `true`, else push `false`.
# [Notes]
#   This opcode is typically used to determine at runtime whether an
#   optimisation can be performed. At compile time, two code paths are
#   generated: a slow, but guaranteed correct path, and a fast path that uses
#   certain optimisations. The serial number check is then performed at
#   runtime to determine which code path is executed.
#
#   For example, a method such as `Fixnum#times` can be optimised at compile
#   time, but we can't know until runtime whether or not the `Fixnum#times`
#   method has been overridden. The serial number check is used to determine
#   each time the code is executed, whether or not the standard `Fixnum#times`
#   has been overridden. It leverages the serial number field on a
#   `CompiledCode`, is initialised to either 0 (for kernel land methods) or
#   1 (for user land methods).

instruction check_serial(literal serial) [ receiver -- boolean ]
  Object* recv = stack_pop();
  CallSite* call_site = reinterpret_cast<CallSite*>(literal);

  stack_push(RBOOL(call_site->update_and_validate(state,
                    call_frame, recv, G(sym_public), serial)));
end

# [Description]
#   Checks if the specified method's serial number matches an expected value.
#   Considers `private` methods too.
# [See Also]
#   check_serial

instruction check_serial_private(literal serial) [ receiver -- boolean ]
  Object* recv = stack_pop();
  CallSite* call_site = reinterpret_cast<CallSite*>(literal);

  stack_push(RBOOL(call_site->update_and_validate(state,
                    call_frame, recv, G(sym_private), serial)));
end

section "Access object fields"

# [Description]
#   Pushes the value of the specified field in the current object onto the
#   stack.
# [Notes]
#   Fields are similar to instance variables, but have dedicated storage
#   allocated. They are primarily used on core or bootstrap classes.
#   This instruction should not be used directly. The VM will specialize
#   push_ivar instructions into this.

instruction push_my_field(index) [ -- value ]
  stack_push(call_frame->self()->get_field(state, index));
end

# [Description]
#   Stores the value at the top of the stack into the field specified by
#   _index_ on `self`.
#
#   The stack is left unmodified.
# [Notes]
#   This instruction should not be used directly. The VM will specialize
#   push_ivar instructions into this.

instruction store_my_field(index) [ value -- value ]
  call_frame->self()->set_field(state, index, stack_top());
end

section "Type checks"

# [Description]
#   Evaluate if _object_ is an instance of _class_ or of an ancestor of
#   _class_. If so, push `true`, else push `false`.
#
#   The equivalent of `object.kind_of?(klass)` in Ruby.
# [See Also]
#   instance_of

instruction kind_of() [ object class -- boolean ]
  Object* t1 = stack_pop();
  Object* mod = stack_pop();
  stack_push(RBOOL(t1->kind_of_p(state, mod)));
end

# [Description]
#   Evaluate if _object_ is an instance of _class_. If so, push `true`, else
#   push `false`.
#
#   The equivalent of `object.instance_of?(klass)` in Ruby.
# [See Also]
#   kind_of

instruction instance_of() [ object class -- boolean ]
  Object* t1 = stack_pop();
  Class* cls = as<Class>(stack_pop());
  stack_push(RBOOL(t1->class_object(state) == cls));
end

section "Optimizations"

# [Description]
#   Push `-1` (negative 1) onto the stack.
# [Notes]
#   This is an optimisation applied by the compiler.

instruction meta_push_neg_1() [ -- value ]
  stack_push(Fixnum::from(-1));
end

# [Description]
#   Push `0` (zero) onto the stack.
# [Notes]
#   This is an optimisation applied by the compiler.

instruction meta_push_0() [ -- value ]
  stack_push(Fixnum::from(0));
end

# [Description]
#   Push `1` (one) onto the stack.
# [Notes]
#   This is an optimisation applied by the compiler.

instruction meta_push_1() [ -- value ]
  stack_push(Fixnum::from(1));
end

# [Description]
#   Push `2` (two) onto the stack.
# [Notes]
#   This is an optimisation applied by the compiler.

instruction meta_push_2() [ -- value ]
  stack_push(Fixnum::from(2));
end

# [Description]
#   Implementation of `#+` optimised for `Fixnum`.
#
#   Pops _value1_ and _value2_ off the stack, and pushes the _sum_ (_value1_
#   `+` _value2_).  If both values are Fixnums, the addition is done directly
#   via the `fixnum_add` primitive. Otherwise, the `#+` method is called on
#   _value1_, passing _value2_ as the argument.

instruction meta_send_op_plus(literal) [ value1 value2 -- sum ] => send
  Object* left =  stack_back(1);
  Object* right = stack_back(0);

  if(both_fixnum_p(left, right)) {
    (void)stack_pop();
    stack_set_top(force_as<Fixnum>(left)->add(state, force_as<Fixnum>(right)));
  } else {
    flush_ip();
    Arguments out_args(G(sym_plus), left, 1, stack_back_position(1));
    CallSite* call_site = reinterpret_cast<CallSite*>(literal);
    Object* ret = call_site->execute(state, call_frame, out_args);
    stack_clear(2);

    CHECK_AND_PUSH(ret);
  }
end

# [Description]
#   Implementation of `#-` optimised for `Fixnum`.
#
#   Pops _value1_ and _value2_ off the stack, and pushes the _difference_ (_value1_
#   `-` _value2_).  If both values are Fixnums, the subtraction is done directly
#   via the `fixnum_sub` primitive. Otherwise, the `#-` method is called on
#   _value1_, passing _value2_ as the argument.

instruction meta_send_op_minus(literal) [ value1 value2 -- difference ] => send
  Object* left =  stack_back(1);
  Object* right = stack_back(0);

  if(both_fixnum_p(left, right)) {
    (void)stack_pop();
    stack_set_top(force_as<Fixnum>(left)->sub(state, force_as<Fixnum>(right)));
  } else {
    flush_ip();
    Arguments out_args(G(sym_minus), left, 1, stack_back_position(1));
    CallSite* call_site = reinterpret_cast<CallSite*>(literal);
    Object* ret = call_site->execute(state, call_frame, out_args);
    stack_clear(2);

    CHECK_AND_PUSH(ret);
  }
end

# [Description]
#   Implementation of `#==` optimised for `Fixnum` and `Symbol`.
#
#   Pops _value1_ and _value2_ off the stack and pushes the logical result
#   of (_value1_ `==` _value2_). If _value1_ and _value2_ are both Fixnums or
#   both Symbols, the comparison is done directly. Otherwise, the `#==` method
#   is called on _value1_, passing _value2_ as the argument.

instruction meta_send_op_equal(literal) [ value1 value2 -- boolean ] => send
  Object* t1 = stack_back(1);
  Object* t2 = stack_back(0);

  /* If both are not references, compare them directly. */
  if(!t1->reference_p() && !t2->reference_p()) {
    (void)stack_pop();
    stack_set_top(RBOOL(t1 == t2));
  } else {
    flush_ip();

    Arguments out_args(G(sym_equal), t1, 1, stack_back_position(1));
    CallSite* call_site = reinterpret_cast<CallSite*>(literal);
    Object* ret = call_site->execute(state, call_frame, out_args);
    stack_clear(2);

    CHECK_AND_PUSH(ret);
  }
end

# [Description]
#   Implementation of `#<` optimised for `Fixnum`.
#
#   Pops _value1_ and _value2_ off the stack, and pushes the logical result
#   of (_value1_ `<` _value2_). If _value1_ and _value2_ are both Fixnums, the
#   comparison is done directly. Otherwise, the `#<` method is called on
#   _value1_, passing _value2_ as the argument.

instruction meta_send_op_lt(literal) [ value1 value2 -- boolean ]
  Object* t1 = stack_back(1);
  Object* t2 = stack_back(0);
  if(both_fixnum_p(t1, t2)) {
    native_int j = as<Integer>(t1)->to_native();
    native_int k = as<Integer>(t2)->to_native();
    (void)stack_pop();
    stack_set_top(RBOOL(j < k));
  } else {
    flush_ip();
    CallSite* call_site = reinterpret_cast<CallSite*>(literal);
    Arguments out_args(call_site->name(), t1, 1, stack_back_position(1));
    Object* ret = call_site->execute(state, call_frame, out_args);
    stack_clear(2);

    CHECK_AND_PUSH(ret);
  }
end

# [Description]
#   Implementation of `#>` optimised for `Fixnum`.
#
#   Pops _value1_ and _value2_ off the stack, and pushes the logical result
#   of (_value1_ `>` _value2_). If _value1_ and _value2_ are both Fixnums, the
#   comparison is done directly. Otherwise, the `#>` method is called on
#   _value1_, passing _value2_ as the argument.

instruction meta_send_op_gt(literal) [ value1 value2 -- boolean ]
  Object* t1 = stack_back(1);
  Object* t2 = stack_back(0);
  if(both_fixnum_p(t1, t2)) {
    native_int j = as<Integer>(t1)->to_native();
    native_int k = as<Integer>(t2)->to_native();
    (void)stack_pop();
    stack_set_top(RBOOL(j > k));
  } else {
    flush_ip();
    CallSite* call_site = reinterpret_cast<CallSite*>(literal);
    Arguments out_args(call_site->name(), t1, 1, stack_back_position(1));
    Object* ret = call_site->execute(state, call_frame, out_args);
    stack_clear(2);

    CHECK_AND_PUSH(ret);
  }
end

# [Description]
#   Implementation of `#===` (triple equal) optimised for `Fixnum` and
#   `Symbol`.
#
#   Pops _value1_ and _value2_ off the stack, and pushes the logical result
#   of (_value1_ `===` _value2_). If _value1_ and _value2_ are both Fixnums or
#   both Symbols, the comparison is done directly. Otherwise, the `#===` method
#   is called on _value1_, passing _value2_ as the argument.
# [Notes]
#   Exactly like equal, except calls `#===` if it can't handle it directly.

instruction meta_send_op_tequal(literal) [ value1 value2 -- boolean ] => send
  Object* t1 = stack_back(1);
  Object* t2 = stack_back(0);
  /* If both are fixnums, or both are symbols, compare the ops directly. */
  if((t1->fixnum_p() && t2->fixnum_p()) || (t1->symbol_p() && t2->symbol_p())) {
    (void)stack_pop();
    stack_set_top(RBOOL(t1 == t2));
  } else {
    flush_ip();
    CallSite* call_site = reinterpret_cast<CallSite*>(literal);
    Arguments out_args(call_site->name(), t1, 1, stack_back_position(1));
    Object* ret = call_site->execute(state, call_frame, out_args);
    stack_clear(2);

    CHECK_AND_PUSH(ret);
  }
end

# [Description]
#   Simplified call instruction used for non-dynamic `yield` calls and for
#   simple calls with static arguments.
# [Stack Before]
#   argN
#   ...
#   arg1
#   receiver
# [Stack After]
#   retval

instruction meta_send_call(literal count) [ receiver +count -- value ] => send
  flush_ip();
  Object* t1 = stack_back(count);
  Object* ret;

  Arguments out_args(G(sym_call), t1, count, stack_back_position(count));

  if(BlockEnvironment *env = try_as<BlockEnvironment>(t1)) {
    ret = env->call(state, call_frame, out_args);
  } else if(Proc* proc = try_as<Proc>(t1)) {
    ret = proc->call(state, call_frame, out_args);
  } else {
    CallSite* call_site = reinterpret_cast<CallSite*>(literal);
    ret = call_site->execute(state, call_frame, out_args);
  }

  stack_clear(count + 1);

  CHECK_AND_PUSH(ret);
end

section "More misc"

# [Description]
#   Pushes a value read directly from within the body of an object.
# [Notes]
#   This instruction must never be used directly. The VM will specialize
#   `push_my_field` instructions into this.
# [See Also]
#   push_my_field

instruction push_my_offset(index) [ -- offset ]
  Object* val = *reinterpret_cast<Object**>(
      reinterpret_cast<uintptr_t>(call_frame->self()) + index);
  stack_push(val);
end

# [Description]
#   Call a superclass method on the current, passing the arguments
#   passed to the current invocation.
# [Stack Before]
#   argN
#   ...
#   arg2
#   arg1
#   ...
# [Stack After]
#   value
#   ...
# [Notes]
#   This is a specialization of `send_super_with_stack` that is necessary for
#   Ruby semantics regarding how to read the original arguments.
# [See Also]
#   send_super_with_stack

instruction zsuper(literal) [ block -- value ]
  flush_ip();
  Object* block = stack_pop();
  Object* const recv = call_frame->self();

  VariableScope* scope = call_frame->method_scope(state);
  interp_assert(scope);

  MachineCode* mc = scope->method()->machine_code();
  Object* splat_obj = 0;
  Array* splat = 0;

  size_t arg_count = mc->total_args;

  if(mc->splat_position >= 0) {
    splat_obj = scope->get_local(state, mc->splat_position);
    splat = try_as<Array>(splat_obj);
    if(splat) {
      arg_count += splat->size();
    } else {
      arg_count++;
    }
  }

  Tuple* tup = Tuple::create_dirty(state, arg_count);
  for(int i = 0; i < mc->total_args; i++) {
    tup->put(state, i, scope->get_local(state, i));
  }

  if(splat) {
    for(native_int i = 0; i < splat->size(); i++) {
      tup->put(state, i + mc->total_args, splat->get(state, i));
    }
  } else if(splat_obj) {
    tup->put(state, mc->total_args, splat_obj);
  }

  CallSite* call_site = reinterpret_cast<CallSite*>(literal);

  Arguments new_args(call_site->name(), recv, block, arg_count, 0);
  new_args.use_tuple(tup, arg_count);

  Object* ret;

  Symbol* current_name = call_frame->original_name();
  if(call_site->name() != current_name) {
    call_site->name(state, current_name);
  }

  ret = call_site->execute(state, call_frame, new_args);

  CHECK_AND_PUSH(ret);
end

# [Description]
#   Push the block passed as an argument to the current invocation.
#   This differs from `push_block` in that in is not the block for the
#   current scope because of how the current block is seen within
#   an existing block.
# [See Also]
#   push_block

instruction push_block_arg() [ -- block ]
  if(!call_frame->arguments) {
    Exception::internal_error(state, call_frame,
                              "no arguments object");
    RUN_EXCEPTION();
  }

  stack_push(call_frame->arguments->block());
end

# [Description]
#   Push the special undefined value on the stack.

instruction push_undef() [ -- value ]
  stack_push(G(undefined));
end

# [Description]
#   Push the stack local identified by operand _which_.
# [Notes]
#   Stack locals differ from normal locals in that they are not viewable by
#   closures.

instruction push_stack_local(which) [ -- value ]
  stack_push(stack_local(which));
end

# [Description]
#   Set the stack local identified by operand _which_ using the value on the
#   top of the stack.
# [Notes]
#   Stack locals differ from normal locals in that they are not viewable by
#   closures.

instruction set_stack_local(which) [ value -- value ]
  stack_local(which) = stack_top();
end

# [Description]
#   Push `true` or `false` based on whether there is a current block.
# [Notes]
#   Used to implement `block_given?` without having to directly expose
#   the block object itself. This simplifies JIT inlining.
instruction push_has_block() [ -- value ]
  stack_push(RBOOL(CBOOL(call_frame->scope->block())));
end

# [Description]
#   Wrap the current block in a `Proc` and push it onto the stack.  If there
#   is no current block, push `nil`.
# [Notes]
#   Used to implement `&block` in a method signature.
# [See Also]
#   push_block

instruction push_proc() [ -- value ]
  if(!call_frame->arguments) {
    Exception::internal_error(state, call_frame,
                              "no arguments object");
    RUN_EXCEPTION();
  }

  Object* obj = call_frame->arguments->block();
  if(CBOOL(obj)) {
    Proc* prc = Proc::from_env(state, G(proc), obj);
    if(!prc) {
      Exception::internal_error(state, call_frame, "invalid block type");
      RUN_EXCEPTION();
    }
    stack_push(prc);
  } else {
    stack_push(cNil);
  }
end

# [Description]
#   Check if the value on the top of the stack is frozen.  If so, raise a
#   `TypeError` indicating so.
# [Notes]
#   An optimization to deal with check for frozen.

instruction check_frozen() [ value -- value ]
  Object* value = stack_top();

  if(value->reference_p() && value->is_frozen_p()) {
    Exception::frozen_error(state, call_frame, value);
    RUN_EXCEPTION();
  }
end

# [Description]
#   Convert a value into an Array
#
#   Pop _value_. If it is an `Array`, push it back on the stack.  Otherwise,
#   attempt to convert it to an `Array` using `#to_ary` and push the result.
#   If the value can not be converted to an array, it is wrapped in a one
#   element `Array`.

instruction cast_multi_value() [ value -- array ]
  Object* value = stack_top();

  if(!kind_of<Array>(value)) {
    Object* res = Array::to_ary(state, value, call_frame);
    if(!res) {
      RUN_EXCEPTION();
    } else {
      stack_set_top(res);
    }
  }
end

# [Description]
#   Directly invoke a primitive by name.
#
#   Pop _count_ values off the stack and pass them directly to the primitive
#   operation named by the operand _literal_.
# [Stack Before]
#   argN
#   ...
#   arg2
#   arg1
# [Stack After]
#   value
#   ...

instruction invoke_primitive(literal count) [ +count -- value ]
  flush_ip();
  InvokePrimitive ip = reinterpret_cast<InvokePrimitive>(literal);

  Object* ret = (*ip)(state, call_frame, stack_back_position(count), count);

  stack_clear(count);

  CHECK_AND_PUSH(ret);
end

# [Description]
#   Pushes the top-level global `Rubinius` constant onto the stack. Generally
#   this is done to call a utility method.

instruction push_rubinius() [ -- constant ]
  stack_push(G(rubinius));
end

# [Description]
#   Invoke a method via the call custom protocol.
#
#   Pop the _receiver_ and _count_ values off the stack and begin the call
#   custom invocation protocol with them as arguments.
# [Stack Before]
#   argN
#   ...
#   arg2
#   arg1
#   receiver
# [Stack After]
#   value
#   ...

instruction call_custom(literal count) [ receiver +count -- value ] => send
  flush_ip();
  Object* recv = stack_back(count);
  CallSite* call_site = reinterpret_cast<CallSite*>(literal);

  Arguments args(call_site->name(), recv, cNil, count,
                 stack_back_position(count));

  Object* ret = call_site->execute(state, call_frame, args);

  stack_clear(count + 1);

  CHECK_AND_PUSH(ret);
end

# [Description]
#   Pop a value off the stack and if it's not a `String`, call a method
#   indicated by _literal_ on it.  Push the resulting object back on the
#   stack.
# [Notes]
#   Normally literal is `:to_s`, but this instruction leaves it up to the user
#   to indicate for flexibility.

instruction meta_to_s(literal) [ object -- string ] => send
  if(!kind_of<String>(stack_top())) {
    flush_ip();
    CallSite* call_site = reinterpret_cast<CallSite*>(literal);

    Arguments args(call_site->name(), stack_top(), cNil, 0, 0);
    Object* ret = call_site->execute(state, call_frame, args);
    if(ret && !kind_of<String>(ret)) {
      ret = stack_top()->to_s(state, false);
    }

    (void)stack_pop();
    CHECK_AND_PUSH(ret);
  }
end

# [Description]
#   Pushes the distinguished module `Rubinius::Type` onto the stack.

instruction push_type() [ -- constant ]
  stack_push(G(type));
end

# [Description]
#   Pushes the distinguished class `Rubinius::Mirror` onto the stack.

instruction push_mirror() [ -- constant ]
  stack_push(G(mirror));
end
