Skip to content

Commit

Permalink
Safe navigation operator
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nobu committed Oct 22, 2015
1 parent 5a599dd commit a356fe1
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 41 deletions.
14 changes: 14 additions & 0 deletions ChangeLog
@@ -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> Thu Oct 22 13:16:19 2015 Guilherme Reis Campos <guilhermekbsa@gmail.com>


* dir.c (ruby_brace_expand): glob brace expansion edge case fix. * dir.c (ruby_brace_expand): glob brace expansion edge case fix.
Expand Down
11 changes: 11 additions & 0 deletions NEWS
Expand Up @@ -18,6 +18,17 @@ with all sufficient information, see the ChangeLog file.
* besides, --enable/--disable=frozen-string-literal options also have * besides, --enable/--disable=frozen-string-literal options also have
been introduced. 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:

This comment has been minimized.

Copy link
@fxn

fxn Oct 22, 2015

Contributor

This comment has been minimized.

Copy link
@marnen

marnen Oct 23, 2015

And it's try.

This comment has been minimized.

Copy link
@nobu

nobu Oct 23, 2015

Author Member

try checks by respond_to?.
.? checks if nil, same as try!.

* method name is syntactically required
obj.try! {} # valid
obj.? {} # syntax error
* attribute assignment is valid
obj.?attr += 1

=== Core classes updates (outstanding ones only) === Core classes updates (outstanding ones only)


* ARGF * ARGF
Expand Down
53 changes: 50 additions & 3 deletions compile.c
Expand Up @@ -1942,6 +1942,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
} }


if (iobj->insn_id == BIN(branchif) || if (iobj->insn_id == BIN(branchif) ||
iobj->insn_id == BIN(branchnil) ||
iobj->insn_id == BIN(branchunless)) { iobj->insn_id == BIN(branchunless)) {
/* /*
* if L1 * if L1
Expand All @@ -1955,6 +1956,31 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
if (nobj->insn_id == BIN(jump)) { if (nobj->insn_id == BIN(jump)) {
OPERAND_AT(iobj, 0) = OPERAND_AT(nobj, 0); 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)) { if (do_tailcallopt && iobj->insn_id == BIN(leave)) {
Expand Down Expand Up @@ -4319,6 +4345,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
VALUE asgnflag; VALUE asgnflag;
LABEL *lfin = NEW_LABEL(line); LABEL *lfin = NEW_LABEL(line);
LABEL *lcfin = NEW_LABEL(line); LABEL *lcfin = NEW_LABEL(line);
LABEL *lskip = 0;
/* /*
class C; attr_accessor :c; end class C; attr_accessor :c; end
r = C.new r = C.new
Expand Down Expand Up @@ -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); 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_INSN(ret, line, dup);
ADD_SEND(ret, line, vid, INT2FIX(0)); ADD_SEND(ret, line, vid, INT2FIX(0));


Expand All @@ -4385,21 +4417,26 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)


ADD_LABEL(ret, lfin); ADD_LABEL(ret, lfin);
ADD_INSN(ret, line, pop); ADD_INSN(ret, line, pop);
if (lskip) {
ADD_LABEL(ret, lskip);
}
if (poped) { if (poped) {
/* we can apply more optimize */ /* we can apply more optimize */
ADD_INSN(ret, line, pop); ADD_INSN(ret, line, pop);
} }
} }
else { else {
COMPILE(ret, "NODE_OP_ASGN2 val", node->nd_value); COMPILE(ret, "NODE_OP_ASGN2 val", node->nd_value);
ADD_SEND(ret, line, node->nd_next->nd_mid, ADD_SEND(ret, line, atype, INT2FIX(1));
INT2FIX(1));
if (!poped) { if (!poped) {
ADD_INSN(ret, line, swap); ADD_INSN(ret, line, swap);
ADD_INSN1(ret, line, topn, INT2FIX(1)); ADD_INSN1(ret, line, topn, INT2FIX(1));
} }
ADD_SEND_WITH_FLAG(ret, line, aid, INT2FIX(1), INT2FIX(asgnflag)); ADD_SEND_WITH_FLAG(ret, line, aid, INT2FIX(1), INT2FIX(asgnflag));
ADD_INSN(ret, line, pop); ADD_INSN(ret, line, pop);
if (lskip) {
ADD_LABEL(ret, lskip);
}
} }
break; break;
} }
Expand Down Expand Up @@ -4548,6 +4585,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
} }
break; break;
} }
case NODE_QCALL:
case NODE_FCALL: case NODE_FCALL:
case NODE_VCALL:{ /* VCALL: variable or call */ case NODE_VCALL:{ /* VCALL: variable or call */
/* /*
Expand All @@ -4557,6 +4595,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
*/ */
DECL_ANCHOR(recv); DECL_ANCHOR(recv);
DECL_ANCHOR(args); DECL_ANCHOR(args);
LABEL *lskip = 0;
ID mid = node->nd_mid; ID mid = node->nd_mid;
VALUE argc; VALUE argc;
unsigned int flag = 0; unsigned int flag = 0;
Expand Down Expand Up @@ -4631,8 +4670,13 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
} }
#endif #endif
/* receiver */ /* receiver */
if (type == NODE_CALL) { if (type == NODE_CALL || type == NODE_QCALL) {
COMPILE(recv, "recv", node->nd_recv); 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) { else if (type == NODE_FCALL || type == NODE_VCALL) {
ADD_CALL_RECEIVER(recv, line); ADD_CALL_RECEIVER(recv, line);
Expand Down Expand Up @@ -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); ADD_SEND_R(ret, line, mid, argc, parent_block, INT2FIX(flag), keywords);


if (lskip) {
ADD_LABEL(ret, lskip);
}
if (poped) { if (poped) {
ADD_INSN(ret, line, pop); ADD_INSN(ret, line, pop);
} }
Expand Down
4 changes: 4 additions & 0 deletions doc/syntax/calling_methods.rdoc
Expand Up @@ -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 receiver but depending on the method's visibility sending a message may raise a
NoMethodError. 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 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. used due to the potential for confusion with <code>::</code> for namespaces.


Expand Down
17 changes: 17 additions & 0 deletions insns.def
Expand Up @@ -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 */ /* for optimize */
Expand Down
5 changes: 4 additions & 1 deletion node.c
Expand Up @@ -357,7 +357,10 @@ dump_node(VALUE buf, VALUE indent, int comment, NODE *node)
ANN(" where [attr]: [nd_next->nd_vid]"); ANN(" where [attr]: [nd_next->nd_vid]");
ANN("example: struct.field += foo"); ANN("example: struct.field += foo");
F_NODE(nd_recv, "receiver"); 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") { F_CUSTOM1(nd_next->nd_mid, "operator") {
switch (node->nd_next->nd_mid) { switch (node->nd_next->nd_mid) {
case 0: A("0 (||)"); break; case 0: A("0 (||)"); break;
Expand Down
6 changes: 4 additions & 2 deletions node.h
Expand Up @@ -96,6 +96,8 @@ enum node_type {
#define NODE_FCALL NODE_FCALL #define NODE_FCALL NODE_FCALL
NODE_VCALL, NODE_VCALL,
#define NODE_VCALL NODE_VCALL #define NODE_VCALL NODE_VCALL
NODE_QCALL,
#define NODE_QCALL NODE_QCALL
NODE_SUPER, NODE_SUPER,
#define NODE_SUPER NODE_SUPER #define NODE_SUPER NODE_SUPER
NODE_ZSUPER, NODE_ZSUPER,
Expand Down Expand Up @@ -394,8 +396,8 @@ typedef struct RNode {
#define NEW_CVASGN(v,val) NEW_NODE(NODE_CVASGN,v,val,0) #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_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_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_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) NEW_NODE(NODE_OP_ASGN2,i,o,0) #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_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_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) #define NEW_OP_CDECL(v,op,val) NEW_NODE(NODE_OP_CDECL,v,val,op)
Expand Down

0 comments on commit a356fe1

Please sign in to comment.