* compile.c (iseq_peephole_optimize): peephole optimization for
  branchnil jumps.
* compile.c (iseq_compile_each): generate save navigation operator
  code.
* insns.def (branchnil): new opcode to pop the tos and branch if
  it is nil.
* parse.y (NEW_QCALL, call_op, parser_yylex): parse token '.?'.
  [Feature #11537]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@52214 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
nobu 2015-10-22 06:30:12 +00:00
Родитель 5a599dde0c
Коммит a356fe1c35
10 изменённых файлов: 164 добавлений и 41 удалений

Просмотреть файл

@ -1,3 +1,17 @@
Thu Oct 22 15:30:08 2015 Nobuyoshi Nakada <nobu@ruby-lang.org>
* compile.c (iseq_peephole_optimize): peephole optimization for
branchnil jumps.
* compile.c (iseq_compile_each): generate save navigation operator
code.
* insns.def (branchnil): new opcode to pop the tos and branch if
it is nil.
* parse.y (NEW_QCALL, call_op, parser_yylex): parse token '.?'.
[Feature #11537]
Thu Oct 22 13:16:19 2015 Guilherme Reis Campos <guilhermekbsa@gmail.com>
* dir.c (ruby_brace_expand): glob brace expansion edge case fix.

11
NEWS
Просмотреть файл

@ -18,6 +18,17 @@ with all sufficient information, see the ChangeLog file.
* besides, --enable/--disable=frozen-string-literal options also have
been introduced.
* safe navigation operator:
* new method call syntax, `object.?foo', method #foo is called on
`object' if it is not nil.
this is similar to `try!' in ActiveSupport, except for:
* method name is syntactically required
obj.try! {} # valid
obj.? {} # syntax error
* attribute assignment is valid
obj.?attr += 1
=== Core classes updates (outstanding ones only)
* ARGF

Просмотреть файл

@ -1942,6 +1942,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
}
if (iobj->insn_id == BIN(branchif) ||
iobj->insn_id == BIN(branchnil) ||
iobj->insn_id == BIN(branchunless)) {
/*
* if L1
@ -1955,6 +1956,31 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
if (nobj->insn_id == BIN(jump)) {
OPERAND_AT(iobj, 0) = OPERAND_AT(nobj, 0);
}
if (nobj->insn_id == BIN(dup)) {
/*
* dup
* if L1
* ...
* L1:
* dup
* if L2
* =>
* dup
* if L2
* ...
* L1:
* dup
* if L2
*/
INSN *pobj = (INSN *)iobj->link.prev;
nobj = (INSN *)nobj->link.next;
/* basic blocks, with no labels in the middle */
if ((pobj && pobj->insn_id == BIN(dup)) &&
(nobj && nobj->insn_id == iobj->insn_id)) {
OPERAND_AT(iobj, 0) = OPERAND_AT(nobj, 0);
}
}
}
if (do_tailcallopt && iobj->insn_id == BIN(leave)) {
@ -4319,6 +4345,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
VALUE asgnflag;
LABEL *lfin = NEW_LABEL(line);
LABEL *lcfin = NEW_LABEL(line);
LABEL *lskip = 0;
/*
class C; attr_accessor :c; end
r = C.new
@ -4362,6 +4389,11 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
*/
asgnflag = COMPILE_RECV(ret, "NODE_OP_ASGN2#recv", node);
if (node->nd_next->nd_aid) {
lskip = NEW_LABEL(line);
ADD_INSN(ret, line, dup);
ADD_INSNL(ret, line, branchnil, lskip);
}
ADD_INSN(ret, line, dup);
ADD_SEND(ret, line, vid, INT2FIX(0));
@ -4385,6 +4417,9 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
ADD_LABEL(ret, lfin);
ADD_INSN(ret, line, pop);
if (lskip) {
ADD_LABEL(ret, lskip);
}
if (poped) {
/* we can apply more optimize */
ADD_INSN(ret, line, pop);
@ -4392,14 +4427,16 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
}
else {
COMPILE(ret, "NODE_OP_ASGN2 val", node->nd_value);
ADD_SEND(ret, line, node->nd_next->nd_mid,
INT2FIX(1));
ADD_SEND(ret, line, atype, INT2FIX(1));
if (!poped) {
ADD_INSN(ret, line, swap);
ADD_INSN1(ret, line, topn, INT2FIX(1));
}
ADD_SEND_WITH_FLAG(ret, line, aid, INT2FIX(1), INT2FIX(asgnflag));
ADD_INSN(ret, line, pop);
if (lskip) {
ADD_LABEL(ret, lskip);
}
}
break;
}
@ -4548,6 +4585,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
}
break;
}
case NODE_QCALL:
case NODE_FCALL:
case NODE_VCALL:{ /* VCALL: variable or call */
/*
@ -4557,6 +4595,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
*/
DECL_ANCHOR(recv);
DECL_ANCHOR(args);
LABEL *lskip = 0;
ID mid = node->nd_mid;
VALUE argc;
unsigned int flag = 0;
@ -4631,8 +4670,13 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
}
#endif
/* receiver */
if (type == NODE_CALL) {
if (type == NODE_CALL || type == NODE_QCALL) {
COMPILE(recv, "recv", node->nd_recv);
if (type == NODE_QCALL) {
lskip = NEW_LABEL(line);
ADD_INSN(recv, line, dup);
ADD_INSNL(recv, line, branchnil, lskip);
}
}
else if (type == NODE_FCALL || type == NODE_VCALL) {
ADD_CALL_RECEIVER(recv, line);
@ -4662,6 +4706,9 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
ADD_SEND_R(ret, line, mid, argc, parent_block, INT2FIX(flag), keywords);
if (lskip) {
ADD_LABEL(ret, lskip);
}
if (poped) {
ADD_INSN(ret, line, pop);
}

Просмотреть файл

@ -27,6 +27,10 @@ This sends the +my_method+ message to +my_object+. Any object can be a
receiver but depending on the method's visibility sending a message may raise a
NoMethodError.
You may use <code>.?</code> to designate a receiver, then +my_method+ is not
invoked and the result is +nil+ when the receiver is +nil+. In that case, the
argument of +my_method+ are not evaluated.
You may also use <code>::</code> to designate a receiver, but this is rarely
used due to the potential for confusion with <code>::</code> for namespaces.

Просмотреть файл

@ -1132,6 +1132,23 @@ branchunless
}
}
/**
@c jump
@e if val is nil, set PC to (PC + dst).
@j val nil PC (PC + dst)
*/
DEFINE_INSN
branchnil
(OFFSET dst)
(VALUE val)
()
{
if (NIL_P(val)) {
RUBY_VM_CHECK_INTS(th);
JUMP(dst);
}
}
/**********************************************************/
/* for optimize */

5
node.c
Просмотреть файл

@ -357,7 +357,10 @@ dump_node(VALUE buf, VALUE indent, int comment, NODE *node)
ANN(" where [attr]: [nd_next->nd_vid]");
ANN("example: struct.field += foo");
F_NODE(nd_recv, "receiver");
F_ID(nd_next->nd_vid, "attr");
F_CUSTOM1(nd_next->nd_vid, "attr") {
if (node->nd_next->nd_aid) A("? ");
A_ID(node->nd_next->nd_vid);
}
F_CUSTOM1(nd_next->nd_mid, "operator") {
switch (node->nd_next->nd_mid) {
case 0: A("0 (||)"); break;

6
node.h
Просмотреть файл

@ -96,6 +96,8 @@ enum node_type {
#define NODE_FCALL NODE_FCALL
NODE_VCALL,
#define NODE_VCALL NODE_VCALL
NODE_QCALL,
#define NODE_QCALL NODE_QCALL
NODE_SUPER,
#define NODE_SUPER NODE_SUPER
NODE_ZSUPER,
@ -394,8 +396,8 @@ typedef struct RNode {
#define NEW_CVASGN(v,val) NEW_NODE(NODE_CVASGN,v,val,0)
#define NEW_CVDECL(v,val) NEW_NODE(NODE_CVDECL,v,val,0)
#define NEW_OP_ASGN1(p,id,a) NEW_NODE(NODE_OP_ASGN1,p,id,a)
#define NEW_OP_ASGN2(r,i,o,val) NEW_NODE(NODE_OP_ASGN2,r,val,NEW_OP_ASGN22(i,o))
#define NEW_OP_ASGN22(i,o) NEW_NODE(NODE_OP_ASGN2,i,o,0)
#define NEW_OP_ASGN2(r,t,i,o,val) NEW_NODE(NODE_OP_ASGN2,r,val,NEW_OP_ASGN22(i,o,t))
#define NEW_OP_ASGN22(i,o,t) NEW_NODE(NODE_OP_ASGN2,i,o,t)
#define NEW_OP_ASGN_OR(i,val) NEW_NODE(NODE_OP_ASGN_OR,i,val,0)
#define NEW_OP_ASGN_AND(i,val) NEW_NODE(NODE_OP_ASGN_AND,i,val,0)
#define NEW_OP_CDECL(v,op,val) NEW_NODE(NODE_OP_CDECL,v,val,op)

81
parse.y
Просмотреть файл

@ -371,6 +371,9 @@ static int parser_yyerror(struct parser_params*, const char*);
#define ruby_coverage (parser->coverage)
#endif
#define NODE_CALL_Q(q) (((q) == tDOTQ) ? NODE_QCALL : NODE_CALL)
#define NEW_QCALL(q,r,m,a) NEW_NODE(NODE_CALL_Q(q),r,m,a)
static int yylex(YYSTYPE*, struct parser_params*);
#ifndef RIPPER
@ -457,8 +460,8 @@ static NODE *node_assign_gen(struct parser_params*,NODE*,NODE*);
#define node_assign(node1, node2) node_assign_gen(parser, (node1), (node2))
static NODE *new_op_assign_gen(struct parser_params *parser, NODE *lhs, ID op, NODE *rhs);
static NODE *new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs, ID attr, ID op, NODE *rhs);
#define new_attr_op_assign(lhs, type, attr, op, rhs) new_attr_op_assign_gen(parser, (lhs), (attr), (op), (rhs))
static NODE *new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs, ID atype, ID attr, ID op, NODE *rhs);
#define new_attr_op_assign(lhs, type, attr, op, rhs) new_attr_op_assign_gen(parser, (lhs), (type), (attr), (op), (rhs))
static NODE *new_const_op_assign_gen(struct parser_params *parser, NODE *lhs, ID op, NODE *rhs);
#define new_const_op_assign(lhs, op, rhs) new_const_op_assign_gen(parser, (lhs), (op), (rhs))
@ -844,7 +847,7 @@ static void token_info_pop(struct parser_params*, const char *token, size_t len)
%type <node> mlhs mlhs_head mlhs_basic mlhs_item mlhs_node mlhs_post mlhs_inner
%type <id> fsym keyword_variable user_variable sym symbol operation operation2 operation3
%type <id> cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_bad_arg
%type <id> f_kwrest f_label f_arg_asgn
%type <id> f_kwrest f_label f_arg_asgn call_op
/*%%%*/
/*%
%type <val> program reswords then do dot_or_colon
@ -869,6 +872,7 @@ static void token_info_pop(struct parser_params*, const char *token, size_t len)
%token tASET RUBY_TOKEN(ASET) "[]="
%token tLSHFT RUBY_TOKEN(LSHFT) "<<"
%token tRSHFT RUBY_TOKEN(RSHFT) ">>"
%token tDOTQ RUBY_TOKEN(DOTQ) ".?"
%token tCOLON2 "::"
%token tCOLON3 ":: at EXPR_BEG"
%token <id> tOP_ASGN /* +=, -= etc. */
@ -1260,15 +1264,15 @@ stmt : keyword_alias fitem {lex_state = EXPR_FNAME;} fitem
$$ = dispatch3(opassign, $$, $5, $6);
%*/
}
| primary_value '.' tIDENTIFIER tOP_ASGN command_call
| primary_value call_op tIDENTIFIER tOP_ASGN command_call
{
value_expr($5);
$$ = new_attr_op_assign($1, '.', $3, $4, $5);
$$ = new_attr_op_assign($1, $2, $3, $4, $5);
}
| primary_value '.' tCONSTANT tOP_ASGN command_call
| primary_value call_op tCONSTANT tOP_ASGN command_call
{
value_expr($5);
$$ = new_attr_op_assign($1, '.', $3, $4, $5);
$$ = new_attr_op_assign($1, $2, $3, $4, $5);
}
| primary_value tCOLON2 tCONSTANT tOP_ASGN command_call
{
@ -1456,24 +1460,24 @@ command : fcall command_args %prec tLOWEST
$$ = method_add_block($$, $3);
%*/
}
| primary_value '.' operation2 command_args %prec tLOWEST
| primary_value call_op operation2 command_args %prec tLOWEST
{
/*%%%*/
$$ = NEW_CALL($1, $3, $4);
$$ = NEW_QCALL($2, $1, $3, $4);
fixpos($$, $1);
/*%
$$ = dispatch4(command_call, $1, ripper_id2sym('.'), $3, $4);
$$ = dispatch4(command_call, $1, ripper_id2sym($2), $3, $4);
%*/
}
| primary_value '.' operation2 command_args cmd_brace_block
| primary_value call_op operation2 command_args cmd_brace_block
{
/*%%%*/
block_dup_check($4,$5);
$5->nd_iter = NEW_CALL($1, $3, $4);
$5->nd_iter = NEW_QCALL($2, $1, $3, $4);
$$ = $5;
fixpos($$, $1);
/*%
$$ = dispatch4(command_call, $1, ripper_id2sym('.'), $3, $4);
$$ = dispatch4(command_call, $1, ripper_id2sym($2), $3, $4);
$$ = method_add_block($$, $5);
%*/
}
@ -1713,12 +1717,12 @@ mlhs_node : user_variable
$$ = dispatch2(aref_field, $1, escape_Qundef($3));
%*/
}
| primary_value '.' tIDENTIFIER
| primary_value call_op tIDENTIFIER
{
/*%%%*/
$$ = attrset($1, $3);
/*%
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3);
$$ = dispatch3(field, $1, ripper_id2sym($2), $3);
%*/
}
| primary_value tCOLON2 tIDENTIFIER
@ -1729,12 +1733,12 @@ mlhs_node : user_variable
$$ = dispatch2(const_path_field, $1, $3);
%*/
}
| primary_value '.' tCONSTANT
| primary_value call_op tCONSTANT
{
/*%%%*/
$$ = attrset($1, $3);
/*%
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3);
$$ = dispatch3(field, $1, ripper_id2sym($2), $3);
%*/
}
| primary_value tCOLON2 tCONSTANT
@ -1804,12 +1808,12 @@ lhs : user_variable
$$ = dispatch2(aref_field, $1, escape_Qundef($3));
%*/
}
| primary_value '.' tIDENTIFIER
| primary_value call_op tIDENTIFIER
{
/*%%%*/
$$ = attrset($1, $3);
/*%
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3);
$$ = dispatch3(field, $1, ripper_id2sym($2), $3);
%*/
}
| primary_value tCOLON2 tIDENTIFIER
@ -1820,12 +1824,12 @@ lhs : user_variable
$$ = dispatch3(field, $1, ID2SYM(idCOLON2), $3);
%*/
}
| primary_value '.' tCONSTANT
| primary_value call_op tCONSTANT
{
/*%%%*/
$$ = attrset($1, $3);
/*%
$$ = dispatch3(field, $1, ripper_id2sym('.'), $3);
$$ = dispatch3(field, $1, ripper_id2sym($2), $3);
%*/
}
| primary_value tCOLON2 tCONSTANT
@ -2064,15 +2068,15 @@ arg : lhs '=' arg
$$ = dispatch3(opassign, $1, $5, $6);
%*/
}
| primary_value '.' tIDENTIFIER tOP_ASGN arg
| primary_value call_op tIDENTIFIER tOP_ASGN arg
{
value_expr($5);
$$ = new_attr_op_assign($1, '.', $3, $4, $5);
$$ = new_attr_op_assign($1, $2, $3, $4, $5);
}
| primary_value '.' tCONSTANT tOP_ASGN arg
| primary_value call_op tCONSTANT tOP_ASGN arg
{
value_expr($5);
$$ = new_attr_op_assign($1, '.', $3, $4, $5);
$$ = new_attr_op_assign($1, $2, $3, $4, $5);
}
| primary_value tCOLON2 tIDENTIFIER tOP_ASGN arg
{
@ -3648,7 +3652,7 @@ method_call : fcall paren_args
$$ = method_arg(dispatch1(fcall, $1), $2);
%*/
}
| primary_value '.' operation2
| primary_value call_op operation2
{
/*%%%*/
$<num>$ = ruby_sourceline;
@ -3657,10 +3661,10 @@ method_call : fcall paren_args
opt_paren_args
{
/*%%%*/
$$ = NEW_CALL($1, $3, $5);
$$ = NEW_QCALL($2, $1, $3, $5);
nd_set_line($$, $<num>4);
/*%
$$ = dispatch3(call, $1, ripper_id2sym('.'), $3);
$$ = dispatch3(call, $1, ripper_id2sym($2), $3);
$$ = method_optarg($$, $5);
%*/
}
@ -3688,7 +3692,7 @@ method_call : fcall paren_args
$$ = dispatch3(call, $1, ID2SYM(idCOLON2), $3);
%*/
}
| primary_value '.'
| primary_value call_op
{
/*%%%*/
$<num>$ = ruby_sourceline;
@ -3697,10 +3701,10 @@ method_call : fcall paren_args
paren_args
{
/*%%%*/
$$ = NEW_CALL($1, idCall, $4);
$$ = NEW_QCALL($2, $1, idCall, $4);
nd_set_line($$, $<num>3);
/*%
$$ = dispatch3(call, $1, ripper_id2sym('.'),
$$ = dispatch3(call, $1, ripper_id2sym($2),
ID2SYM(idCall));
$$ = method_optarg($$, $4);
%*/
@ -5103,7 +5107,7 @@ operation3 : tIDENTIFIER
| op
;
dot_or_colon : '.'
dot_or_colon : call_op
/*%c%*/
/*%c
{ $$ = $<val>1; }
@ -5115,6 +5119,10 @@ dot_or_colon : '.'
%*/
;
call_op : '.' {$$ = '.';}
| tDOTQ {$$ = tDOTQ;}
;
opt_terms : /* none */
| terms
;
@ -8356,6 +8364,10 @@ parser_yylex(struct parser_params *parser)
pushback(c);
return tDOT2;
}
if (c == '?') {
lex_state = EXPR_DOT;
return tDOTQ;
}
pushback(c);
if (c != -1 && ISDIGIT(c)) {
yyerror("no .<digit> floating literal anymore; put 0 before dot");
@ -10036,7 +10048,8 @@ new_op_assign_gen(struct parser_params *parser, NODE *lhs, ID op, NODE *rhs)
}
static NODE *
new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs, ID attr, ID op, NODE *rhs)
new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs,
ID atype, ID attr, ID op, NODE *rhs)
{
NODE *asgn;
@ -10046,7 +10059,7 @@ new_attr_op_assign_gen(struct parser_params *parser, NODE *lhs, ID attr, ID op,
else if (op == tANDOP) {
op = 1;
}
asgn = NEW_OP_ASGN2(lhs, attr, op, rhs);
asgn = NEW_OP_ASGN2(lhs, (atype == tDOTQ), attr, op, rhs);
fixpos(asgn, lhs);
return asgn;
}

Просмотреть файл

@ -18,7 +18,7 @@ op_id_offset = 128
token_op_ids = %w[
tDOT2 tDOT3 tUPLUS tUMINUS tPOW tDSTAR tCMP tLSHFT tRSHFT
tLEQ tGEQ tEQ tEQQ tNEQ tMATCH tNMATCH tAREF tASET
tCOLON2 tCOLON3 tANDOP tOROP
tCOLON2 tCOLON3 tANDOP tOROP tDOTQ
]
defs = File.join(File.dirname(File.dirname(erb.filename)), "defs/id.def")

Просмотреть файл

@ -31,4 +31,16 @@ class TestCall < Test::Unit::TestCase
assert_nothing_raised(ArgumentError) {o.foo}
assert_raise_with_message(ArgumentError, e.message, bug9622) {o.foo(100)}
end
def test_safe_call
s = Struct.new(:x, :y)
o = s.new("x")
assert_equal("X", o.x.?upcase)
assert_nil(o.y.?upcase)
assert_equal("x", o.x)
o.?x = 6
assert_equal(6, o.x)
o.?x *= 7
assert_equal(42, o.x)
end
end