Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

2295 lines (1927 sloc) 66.517 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->cm->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->cm->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(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() - vmm->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
interp_assert(current_unwind < kMaxUnwindInfos);
UnwindInfo& info = unwinds[current_unwind++];
info.target_ip = ip;
info.stack_depth = stack_calculate_sp();
info.type = (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(current_unwind <= 0) {
Exception::internal_error(state, call_frame, "unbalanced pop_unwind");
RUN_EXCEPTION();
}
--current_unwind;
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->cm->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->cm->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->cm->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->cm->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->cm->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->cm->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->cm->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->cm->literals()->put(state, association, cache);
}
if(!res) {
bool found = false;
flush_ip();
Symbol* sym = as<Symbol>(call_frame->cm->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->cm->literals()->at(state, literal);
CompiledCode* cm = as<CompiledCode>(_lit);
// TODO: We do not need to be doing this everytime.
cm->scope(state, call_frame->constant_scope());
Object* be = BlockEnvironment::under_call_frame(state, gct, cm, vmm, 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));
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->cm->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(vmm->call_count >= 0) vmm->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);
VMMethod* v = scope->method()->backend_method();
Object* splat_obj = 0;
Array* splat = 0;
size_t arg_count = v->total_args;
if(v->splat_position >= 0) {
splat_obj = scope->get_local(state, v->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 < v->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 + v->total_args, splat->get(state, i));
}
} else if(splat_obj) {
tup->put(state, v->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
instruction push_type() [ -- constant ]
stack_push(G(type));
end
Jump to Line
Something went wrong with that request. Please try again.