Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

2303 lines (1933 sloc) 66.846 kb
# 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
call_frame->set_ip(location);
cache_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)) {
call_frame->set_ip(location);
cache_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)) {
call_frame->set_ip(location);
cache_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, 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(state, count);
#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();
ary->set(state, j, t2);
}
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(!LANGUAGE_18_ENABLED(state) && t1->nil_p()) {
t1 = Array::create(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(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());
size_t size = (size_t)array->size();
if(size == 0) {
stack_push(array);
stack_push(cNil);
} else {
size_t j = size - 1;
Object* shifted_value = array->get(state, 0);
Array* smaller_array = Array::create(state, j);
for(size_t 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 _index_. The
# value popped off the stack is then pushed back on again.
instruction set_ivar(index) [ value -- value ]
if(CBOOL(call_frame->self()->frozen_p(state))) {
Exception::frozen_error(state, call_frame);
RUN_EXCEPTION();
}
Symbol* sym = as<Symbol>(call_frame->compiled_code->literals()->at(state, index));
call_frame->self()->set_ivar(state, sym, stack_top());
end
# [Description]
# Pushes the instance variable identified by _index_ onto the stack.
instruction push_ivar(index) [ -- value ]
Symbol* sym = as<Symbol>(call_frame->compiled_code->literals()->at(state, index));
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 ]
bool found;
Symbol* sym = as<Symbol>(call_frame->compiled_code->literals()->at(state, literal));
Object* res = Helpers::const_get(state, call_frame, sym, &found);
if(!found) {
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 _index_. The value is pushed back onto the stack.
instruction set_const(index) [ value -- value ]
Symbol* sym = as<Symbol>(call_frame->compiled_code->literals()->at(state, index));
call_frame->constant_scope()->module()->set_const(state, sym, stack_top());
end
# [Description]
# Pop a value from the literals table specified by the operand _index_ 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(index) [ value module -- value ]
Symbol* sym = as<Symbol>(call_frame->compiled_code->literals()->at(state, index));
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 _index_. 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(index) [ module -- constant ]
bool found;
Module* under = as<Module>(stack_pop());
Symbol* sym = as<Symbol>(call_frame->compiled_code->literals()->at(state, index));
Object* res = Helpers::const_get_under(state, under, sym, &found);
if(!found) {
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 association) [ -- constant ]
Object* res = 0;
Object* val = call_frame->compiled_code->literals()->at(state, association);
// See if the cache is present, if so, validate it and use the value
GlobalCacheEntry* cache;
if((cache = try_as<GlobalCacheEntry>(val)) != NULL) {
if(cache->valid_p(state, call_frame->constant_scope())) {
res = cache->value();
}
} else {
cache = GlobalCacheEntry::empty(state);
call_frame->compiled_code->literals()->put(state, association, cache);
}
if(!res) {
bool found = false;
flush_ip();
Symbol* sym = as<Symbol>(call_frame->compiled_code->literals()->at(state, literal));
res = Helpers::const_get(state, call_frame, sym, &found);
if(found) {
OnStack<2> os(state, cache, res);
if(Autoload* autoload = try_as<Autoload>(res)) {
flush_ip();
res = autoload->resolve(state, gct, call_frame);
}
if(res) {
cache->update(state, res, call_frame->constant_scope());
}
} else {
res = Helpers::const_missing(state, 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();
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Arguments args(cache->name, recv, cNil, 0, 0);
Object* ret = cache->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);
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Arguments args(cache->name, recv, cNil, count,
stack_back_position(count));
Object* ret = cache->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);
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Arguments args(cache->name, recv, block, count,
stack_back_position(count));
Object* ret = cache->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);
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Arguments args(cache->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 = cache->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();
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Object* const recv = call_frame->self();
Arguments new_args(cache->name, recv, block, count,
stack_back_position(count));
Object* ret = cache->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();
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Arguments new_args(cache->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);
Symbol* current_name = call_frame->original_name();
if(cache->name != current_name) {
cache->name = current_name;
}
Object* ret = InlineCache::empty_cache_super(state, cache, 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);
// TODO: We do not need to be doing this everytime.
code->scope(state, call_frame->constant_scope());
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(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, state->symbol("to_ary"), cFalse))) {
obj = obj->send(state, call_frame, state->symbol("to_ary"));
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(state, 1);
ary->set(state, 0, obj);
stack_push(ary);
}
} else {
Array* ary = Array::create(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, state->symbol("to_ary"), cFalse))) {
OnStack<1> os(state, obj);
Object* ignored = obj->send(state, call_frame, state->symbol("to_ary"));
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(state, 1);
ary->set(state, 0, obj);
stack_push(ary);
} else {
Array* ary = Array::create(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;
}
if(!LANGUAGE_18_ENABLED(state)) {
/* The String::encoding() accessor (without state) returns the raw
* Encoding attribute. If it is only ever cNil or all values are the
* same, we don't need to check Encoding compatibility later.
*
* TODO: Consider the case when -K is set (not implemented yet).
*/
if(!check_encoding) {
Encoding* str_enc = str->encoding();
if(!str_enc->nil_p()) {
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(!LANGUAGE_18_ENABLED(state)) {
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;
}
if(!LANGUAGE_18_ENABLED(state)) {
/* 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.
#
# This also sets the scope of the current `CompiledCode` to the new
# `ConstantScope`.
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->compiled_code->scope(state, 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->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() == cNil));
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();
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
MethodCacheEntry* mce =
cache->update_and_validate(state, call_frame, recv);
stack_push(RBOOL(mce && mce->method()->serial()->to_native() == 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();
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
MethodCacheEntry* mce =
cache->update_and_validate_private(state, call_frame, recv);
stack_push(RBOOL(mce && mce->method()->serial()->to_native() == 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();
(void)stack_pop();
Object* res = ((Fixnum*)(left))->add(state, (Fixnum*)(right));
stack_push(res);
} else {
flush_ip();
Arguments out_args(G(sym_plus), left, 1, stack_back_position(1));
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Object* ret = cache->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(((Fixnum*)(left))->sub(state, (Fixnum*)(right)));
} else {
flush_ip();
Arguments out_args(G(sym_minus), left, 1, stack_back_position(1));
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Object* ret = cache->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
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
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));
Object* ret = cache->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();
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Arguments out_args(cache->name, t1, 1, stack_back_position(1));
Object* ret = cache->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();
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Arguments out_args(cache->name, t1, 1, stack_back_position(1));
Object* ret = cache->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();
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Arguments out_args(cache->name, t1, 1, stack_back_position(1));
Object* ret = cache->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 {
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
ret = cache->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(state, arg_count);
for(int i = 0; i < mc->total_args; i++) {
tup->put(state, i, scope->get_local(state, i));
}
if(splat) {
for(size_t 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);
}
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Arguments new_args(cache->name, recv, block, arg_count, 0);
new_args.use_tuple(tup, arg_count);
Object* ret;
Symbol* current_name = call_frame->original_name();
if(cache->name != current_name) {
cache->name = current_name;
}
ret = InlineCache::empty_cache_super(state, cache, 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;
if(BlockEnvironment *env = try_as<BlockEnvironment>(obj)) {
prc = Proc::create(state, G(proc));
prc->block(state, env);
} else if(Proc* p = try_as<Proc>(obj)) {
prc = p;
} else {
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);
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);
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Arguments args(cache->name, recv, cNil, count,
stack_back_position(count));
Object* ret = cache->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();
InlineCache* cache = reinterpret_cast<InlineCache*>(literal);
Arguments args(cache->name, stack_top(), cNil, 0, 0);
Object* ret = cache->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
Jump to Line
Something went wrong with that request. Please try again.