Permalink
Browse files

Safe navigation operator

* 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 a356fe1c3550892902103f66928426ac8279e072
Showing with 164 additions and 41 deletions.
  1. +14 −0 ChangeLog
  2. +11 −0 NEWS
  3. +50 −3 compile.c
  4. +4 −0 doc/syntax/calling_methods.rdoc
  5. +17 −0 insns.def
  6. +4 −1 node.c
  7. +4 −2 node.h
  8. +47 −34 parse.y
  9. +1 −1 template/id.h.tmpl
  10. +12 −0 test/ruby/test_call.rb
View
@@ -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.
View
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:

This comment has been minimized.

Show comment
Hide comment

This comment has been minimized.

Show comment
Hide comment
@marnen

marnen Oct 23, 2015

And it's try.

@marnen

marnen Oct 23, 2015

And it's try.

This comment has been minimized.

Show comment
Hide comment
@nobu

nobu Oct 23, 2015

Member

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

@nobu

nobu Oct 23, 2015

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)
* ARGF
View
@@ -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,21 +4417,26 @@ 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);
}
}
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.
View
@@ -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 */
View
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;
View
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)
Oops, something went wrong.

0 comments on commit a356fe1

Please sign in to comment.