Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

send-pop optimisation #2100

Open
wants to merge 20 commits into
base: master
from
Open
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

new attribute trace_equivalent

Some instructions are spacial cases for another ones.  These
instructions need not preserve their trace counterparts.  By reducing
those unnecessary trace instructions we can strip binary size of
vm_exec_core from 25,759 bytes to 24,924 bytes on my machine.

Yes, this changeset slows traces down a bit.  But is that a problem?
  • Loading branch information...
shyouhei committed Mar 6, 2019
commit 8adf4071be3d8c7a056fdf168878aeb90d9774e9
@@ -50,6 +50,10 @@
not introduce new stack frame on top of it.
If an instruction handles sp, that can never be a leaf.
* trace_equivalent: some instructions can be seen as special
cases of another one. Trace instructions can look at them
instead, if specified by this attribute.
- Attributes can access operands, but not stack (push/pop) variables.
- An instruction's body is a pure C block, copied verbatimly into
@@ -1062,6 +1066,7 @@ opt_plus
/* Array + anything can be handled inside of opt_plus, and that
* anything is converted into array using #to_ary. */
// attr bool leaf = false; /* has rb_to_array_type() */
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_plus(recv, obj);

@@ -1076,6 +1081,7 @@ opt_minus
(CALL_INFO ci, CALL_CACHE cc)
(VALUE recv, VALUE obj)
(VALUE val)
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_minus(recv, obj);

@@ -1090,6 +1096,7 @@ opt_mult
(CALL_INFO ci, CALL_CACHE cc)
(VALUE recv, VALUE obj)
(VALUE val)
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_mult(recv, obj);

@@ -1107,6 +1114,7 @@ opt_div
/* In case of division by zero, it raises. Thus
* ZeroDivisionError#initialize is called. */
// attr bool leaf = false;
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_div(recv, obj);

@@ -1123,6 +1131,7 @@ opt_mod
(VALUE val)
/* Same discussion as opt_mod. */
// attr bool leaf = false;
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_mod(recv, obj);

@@ -1141,6 +1150,7 @@ opt_eq
* (somewhat) coerces the non-string into a string, via a method
* call. */
// attr bool leaf = false; /* has rb_str_equal() */
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = opt_eq_func(recv, obj, ci, cc);

@@ -1171,6 +1181,7 @@ opt_lt
(CALL_INFO ci, CALL_CACHE cc)
(VALUE recv, VALUE obj)
(VALUE val)
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_lt(recv, obj);

@@ -1185,6 +1196,7 @@ opt_le
(CALL_INFO ci, CALL_CACHE cc)
(VALUE recv, VALUE obj)
(VALUE val)
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_le(recv, obj);

@@ -1199,6 +1211,7 @@ opt_gt
(CALL_INFO ci, CALL_CACHE cc)
(VALUE recv, VALUE obj)
(VALUE val)
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_gt(recv, obj);

@@ -1213,6 +1226,7 @@ opt_ge
(CALL_INFO ci, CALL_CACHE cc)
(VALUE recv, VALUE obj)
(VALUE val)
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_ge(recv, obj);

@@ -1231,6 +1245,7 @@ opt_ltlt
* string. Then what happens if that codepoint does not exist in the
* string's encoding? Of course an exception. That's not a leaf. */
// attr bool leaf = false; /* has "invalid codepoint" exception */
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_ltlt(recv, obj);

@@ -1245,6 +1260,7 @@ opt_and
(CALL_INFO ci, CALL_CACHE cc)
(VALUE recv, VALUE obj)
(VALUE val)
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_and(recv, obj);

@@ -1259,6 +1275,7 @@ opt_or
(CALL_INFO ci, CALL_CACHE cc)
(VALUE recv, VALUE obj)
(VALUE val)
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_or(recv, obj);

@@ -1278,6 +1295,7 @@ opt_aref
* default_proc. This is a method call. So opt_aref is
* (surprisingly) not leaf. */
// attr bool leaf = false; /* has rb_funcall() */ /* calls #yield */
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_aref(recv, obj);

@@ -1295,6 +1313,7 @@ opt_aset
/* This is another story than opt_aref. When vm_opt_aset() resorts
* to rb_hash_aset(), which should call #hash for `obj`. */
// attr bool leaf = false; /* has rb_funcall() */ /* calls #hash */
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_aset(recv, obj, set);

@@ -1351,6 +1370,7 @@ opt_length
(CALL_INFO ci, CALL_CACHE cc)
(VALUE recv)
(VALUE val)
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_length(recv, BOP_LENGTH);

@@ -1365,6 +1385,7 @@ opt_size
(CALL_INFO ci, CALL_CACHE cc)
(VALUE recv)
(VALUE val)
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_length(recv, BOP_SIZE);

@@ -1379,6 +1400,7 @@ opt_empty_p
(CALL_INFO ci, CALL_CACHE cc)
(VALUE recv)
(VALUE val)
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_empty_p(recv);

@@ -1393,6 +1415,7 @@ opt_succ
(CALL_INFO ci, CALL_CACHE cc)
(VALUE recv)
(VALUE val)
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_succ(recv);

@@ -1407,6 +1430,7 @@ opt_not
(CALL_INFO ci, CALL_CACHE cc)
(VALUE recv)
(VALUE val)
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_not(ci, cc, recv);

@@ -1433,6 +1457,7 @@ opt_regexpmatch2
(VALUE obj2, VALUE obj1)
(VALUE val)
// attr bool leaf = false; /* match_at() has rb_thread_check_ints() */
// attr enum ruby_vminsn_type trace_equivalent = BIN(trace_opt_send_without_block);
{
val = vm_opt_regexpmatch2(obj2, obj1);

65 iseq.c
@@ -2484,7 +2484,7 @@ iseq_data_to_ary(const rb_iseq_t *iseq)
VALUE exception = rb_ary_new(); /* [[....]] */
VALUE misc = rb_hash_new();

static VALUE insn_syms[VM_INSTRUCTION_SIZE/2]; /* w/o-trace only */
static VALUE insn_syms[VM_INSTRUCTION_SIZE];
struct st_table *labels_table = st_init_numtable();

DECL_SYMBOL(top);
@@ -2931,12 +2931,12 @@ rb_iseq_defined_string(enum defined_type type)

static st_table *encoded_insn_data;
typedef struct insn_data_struct {
int insn;
enum ruby_vminsn_type insn;
int insn_len;
void *notrace_encoded_insn;
void *trace_encoded_insn;
} insn_data_t;
static insn_data_t insn_data[VM_INSTRUCTION_SIZE/2];
static insn_data_t insn_data[VM_INSTRUCTION_SIZE];

void
rb_vm_encoded_insn_data_table_init(void)
@@ -2947,20 +2947,32 @@ rb_vm_encoded_insn_data_table_init(void)
#else
#define INSN_CODE(insn) (insn)
#endif
st_data_t insn;
encoded_insn_data = st_init_numtable_with_size(VM_INSTRUCTION_SIZE / 2);

for (insn = 0; insn < VM_INSTRUCTION_SIZE/2; insn++) {
st_data_t key1 = (st_data_t)INSN_CODE(insn);
st_data_t key2 = (st_data_t)INSN_CODE(insn + VM_INSTRUCTION_SIZE/2);
for (enum ruby_vminsn_type insn = 0; insn < VM_INSTRUCTION_SIZE; insn++) {
enum ruby_vminsn_type tracer = insn_trace_equivalent(insn);
RUBY_ASSERT(tracer >= 0);
RUBY_ASSERT(tracer <= VM_INSTRUCTION_SIZE);

insn_data[insn].insn = (int)insn;
insn_data[insn].insn_len = insn_len(insn);
insn_data[insn].notrace_encoded_insn = (void *) key1;
insn_data[insn].trace_encoded_insn = (void *) key2;

st_add_direct(encoded_insn_data, key1, (st_data_t)&insn_data[insn]);
st_add_direct(encoded_insn_data, key2, (st_data_t)&insn_data[insn]);
if (tracer == VM_INSTRUCTION_SIZE) {
return; /* end of non-trace insn */
}
else {
st_data_t key1 = (st_data_t)INSN_CODE(insn);
st_data_t key2 = (st_data_t)INSN_CODE(tracer);
insn_data_t *ptr = &insn_data[insn];
*ptr = (insn_data_t) {
.insn = insn,
.insn_len = insn_len(insn),
.notrace_encoded_insn = (void *)key1,
.trace_encoded_insn = (void *)key2,
};
st_add_direct(encoded_insn_data, key1, (st_data_t)ptr);

if (!(st_lookup(encoded_insn_data, key2, NULL))) {
st_add_direct(encoded_insn_data, key2, (st_data_t)ptr);
}
}
}
}

@@ -2974,6 +2986,19 @@ rb_vm_insn_addr2insn(const void *addr)
insn_data_t *e = (insn_data_t *)val;
return (int)e->insn;
}
else { /* try hard inspecting what this is */
#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE
const void *const *table = rb_vm_get_insns_address_table();
#endif

for (enum ruby_vminsn_type i = 0; i < VM_INSTRUCTION_SIZE; i++) {
const void *p = (const void *)INSN_CODE(i);

if (p == addr) {
rb_bug("rb_vm_insn_addr2insn: insn not registered: %s", insn_name(i));
}
}
}

rb_bug("rb_vm_insn_addr2insn: invalid insn address: %p", addr);
}
@@ -2989,6 +3014,18 @@ encoded_iseq_trace_instrument(VALUE *iseq_encoded_insn, rb_event_flag_t turnon)
*iseq_encoded_insn = (VALUE) (turnon ? e->trace_encoded_insn : e->notrace_encoded_insn);
return e->insn_len;
}
else { /* try hard inspecting what this is */
#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE
const void *const *table = rb_vm_get_insns_address_table();
#endif

for (enum ruby_vminsn_type i = 0; i < VM_INSTRUCTION_SIZE; i++) {
const VALUE p = INSN_CODE(i);
if (p == *iseq_encoded_insn) {
rb_bug("trace_instrument: insn not registered: %s", insn_name(i));
}
}
}

rb_bug("trace_instrument: invalid insn address: %p", (void *)*iseq_encoded_insn);
}
@@ -48,6 +48,10 @@ def bin
return "BIN(#{name})"
end

def trace_bin
return "BIN(trace_#{name})"
end

def call_attribute name
return sprintf 'attr_%s_%s(%s)', name, @name, \
@opes.map {|i| i[:name] }.compact.join(', ')
@@ -162,6 +166,7 @@ def predefine_attributes
generate_attribute 'rb_snum_t', 'sp_inc', rets.size - pops.size
generate_attribute 'bool', 'handles_sp', default_definition_of_handles_sp
generate_attribute 'bool', 'leaf', default_definition_of_leaf
generate_attribute 'enum ruby_vminsn_type', 'trace_equivalent', trace_bin
end

def default_definition_of_handles_sp
@@ -61,7 +61,9 @@ def has_attribute? *;

private

@instances = RubyVM::Instructions.map {|i| new i }
@instances = RubyVM::Instructions \
. reject {|i| i.has_attribute? 'trace_equivalent' } \
. map {|i| new i }

def self.to_a
@instances
@@ -12,7 +12,9 @@
INSN_ENTRY(<%= insn.name %>)
{
/* ### Declare that we have just entered into an instruction. ### */
% unless insn.has_attribute?('trace_equivalent')
START_OF_ORIGINAL_INSN(<%= insn.name %>);
% end
DEBUG_ENTER_INSN(<%=cstr insn.name %>);

/* ### Declare and assign variables. ### */
@@ -0,0 +1,26 @@
%# -*- C -*-
%# Copyright (c) 2019 Urabe, Shyouhei. All rights reserved.
%#
%# This file is a part of the programming language Ruby. Permission is hereby
%# granted, to either redistribute and/or modify this file, provided that the
%# conditions mentioned in the file COPYING are met. Consult the file for
%# details.
%#
MAYBE_UNUSED(CONSTFUNC(static enum ruby_vminsn_type insn_trace_equivalent(enum ruby_vminsn_type insn)));

enum ruby_vminsn_type
insn_trace_equivalent(enum ruby_vminsn_type insn)
{
/* :TODO: This can be a table lookup -- but is it worth? */
switch(insn) {
default:
return VM_INSTRUCTION_SIZE;
% RubyVM::Instructions.each do |i|
% break if RubyVM::TraceInstructions === i
case <%= i.bin %>:
return attr_trace_equivalent_<%= i.name %>(<%=
i.opes.map { "0" }.join(", ") # operands never make sense for this.
%>);
% end
}
}
@@ -19,3 +19,4 @@
<%= render 'sp_inc_helpers' %>
<%= render 'attributes' %>
<%= render 'insn_stack_increase' %>
<%= render 'insn_trace_equivalent' %>
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.