Skip to content

Commit

Permalink
[WIP] Implement Class#new in Ruby
Browse files Browse the repository at this point in the history
This commit adds a new primitive function `Primitive.send_delegate!`
which emits a send instruction based on the first two parameters. It
forces the send instruction to be an FCALL, thereby passing visibility
checks.

This allows us to write `Class#new` in Ruby and have it work for both
BasicObject and regular objects.

Here are the instructions for `Class#new`:

```
$ ./miniruby -e'puts RubyVM::InstructionSequence.of(Class.method(:new)).disasm'
== disasm: #<ISeq:new@<internal:class>:2 (2,2)-(6,5)>
local table (size: 4, argc: 0 [opts: 0, rest: 0, post: 0, block: 1, kw: -1@-1, kwrest: -1])
[ 4] "*"@0<Rest>[ 3] "&"@1<Block>[ 2] "..."@2    [ 1] obj@3
0000 opt_invokebuiltin_delegate             <builtin!rb_class_alloc2/0>, 0(   3)[LiCa]
0003 setlocal_WC_0                          obj@3
0005 getlocal_WC_0                          obj@3                     (   4)[Li]
0007 getlocal_WC_0                          "*"@0
0009 splatarray                             false
0011 getblockparamproxy                     "&"@1, 0
0014 send                                   <calldata!mid:initialize, argc:1, ARGS_SPLAT|ARGS_BLOCKARG|FCALL>, nil
0017 pop
0018 getlocal_WC_0                          obj@3                     (   5)[Li]
0020 leave                                                            (   6)[Re]
```

Co-authored-by: John Hawthorn <jhawthorn@github.com>
  • Loading branch information
tenderlove and jhawthorn committed Dec 19, 2023
1 parent f35fec7 commit 63434c3
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 2 deletions.
7 changes: 7 additions & 0 deletions class.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Class
def new(...)
obj = Primitive.rb_class_alloc2
Primitive.send_delegate!(obj, :initialize, ...)
obj
end
end
3 changes: 3 additions & 0 deletions common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,7 @@ BUILTIN_RB_SRCS = \
$(srcdir)/timev.rb \
$(srcdir)/thread_sync.rb \
$(srcdir)/nilclass.rb \
$(srcdir)/class.rb \
$(srcdir)/prelude.rb \
$(srcdir)/gem_prelude.rb \
$(srcdir)/yjit.rb \
Expand Down Expand Up @@ -10250,6 +10251,7 @@ miniinit.$(OBJEXT): {$(VPATH)}missing.h
miniinit.$(OBJEXT): {$(VPATH)}nilclass.rb
miniinit.$(OBJEXT): {$(VPATH)}node.h
miniinit.$(OBJEXT): {$(VPATH)}numeric.rb
miniinit.$(OBJEXT): {$(VPATH)}class.rb
miniinit.$(OBJEXT): {$(VPATH)}onigmo.h
miniinit.$(OBJEXT): {$(VPATH)}oniguruma.h
miniinit.$(OBJEXT): {$(VPATH)}pack.rb
Expand Down Expand Up @@ -11090,6 +11092,7 @@ object.$(OBJEXT): {$(VPATH)}method.h
object.$(OBJEXT): {$(VPATH)}missing.h
object.$(OBJEXT): {$(VPATH)}nilclass.rbinc
object.$(OBJEXT): {$(VPATH)}node.h
object.$(OBJEXT): {$(VPATH)}class.rbinc
object.$(OBJEXT): {$(VPATH)}object.c
object.$(OBJEXT): {$(VPATH)}onigmo.h
object.$(OBJEXT): {$(VPATH)}oniguruma.h
Expand Down
52 changes: 52 additions & 0 deletions compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -8492,6 +8492,54 @@ compile_builtin_attr(rb_iseq_t *iseq, const NODE *node)
UNKNOWN_NODE("attr!", node, COMPILE_NG);
}

static int
compile_builtin_send(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE * call_node, const NODE *node, const NODE *line_node, int popped)
{
if (!nd_type_p(node, NODE_BLOCK_PASS)) goto bad_arg;

node = RNODE_BLOCK_PASS(node)->nd_head;

if (!nd_type_p(node, NODE_ARGSCAT)) goto bad_arg;

NODE *argscat = (NODE *)node;

node = RNODE_ARGSCAT(node)->nd_head;

// Should be a 2 element array
if (!nd_type_p(node, NODE_LIST)) goto bad_arg;

const NODE *list = node;
NODE *recv = RNODE_LIST(list)->nd_head;

list = RNODE_LIST(list)->nd_next;
NODE *mid = RNODE_LIST(list)->nd_head;
list = RNODE_LIST(list)->nd_next;
if (!nd_type_p(recv, NODE_LVAR)) goto bad_arg;
if (!nd_type_p(mid, NODE_LIT)) goto bad_arg;

RNODE_CALL(call_node)->nd_mid = SYM2ID(RNODE_LIT(mid)->nd_lit);
RNODE_CALL(call_node)->nd_recv = recv;

//RNODE_CALL(call_node)->nd_args = list;
// Save off the splat node
NODE * splat = RNODE_ARGSCAT(argscat)->nd_body;
// Turn the args cat in to a SPLAT
nd_set_type(argscat, NODE_SPLAT);
RNODE_SPLAT(argscat)->nd_head = splat;

CHECK(COMPILE(ret, "recv", recv));
return compile_call(iseq, ret, call_node, NODE_FCALL, line_node, popped, true);

/*
non_symbol_arg:
COMPILE_ERROR(ERROR_ARGS "non symbol argument to arg!: %s",
rb_builtin_class_name(name));
return COMPILE_NG;
*/
bad_arg:
UNKNOWN_NODE("send!", node, COMPILE_NG);
}

static int
compile_builtin_arg(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, const NODE *line_node, int popped)
{
Expand Down Expand Up @@ -8622,6 +8670,10 @@ compile_builtin_function_call(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NOD
else if (strcmp("arg!", builtin_func) == 0) {
return compile_builtin_arg(iseq, ret, args_node, line_node, popped);
}
else if (strcmp("send_delegate!", builtin_func) == 0) {
// pop off first two params
return compile_builtin_send(iseq, ret, node, args_node, line_node, popped);
}
else if (strcmp("mandatory_only?", builtin_func) == 0) {
if (popped) {
rb_bug("mandatory_only? should be in if condition");
Expand Down
1 change: 1 addition & 0 deletions inits.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ rb_call_builtin_inits(void)
BUILTIN(warning);
BUILTIN(array);
BUILTIN(kernel);
BUILTIN(class);
BUILTIN(symbol);
BUILTIN(timev);
BUILTIN(thread_sync);
Expand Down
15 changes: 14 additions & 1 deletion object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2070,6 +2070,20 @@ rb_class_alloc(VALUE klass)
return class_call_alloc_func(allocator, klass);
}

static VALUE
rb_class_alloc2(rb_execution_context_t *ec, VALUE klass)
{
return rb_class_alloc(klass);
}

static VALUE
rb_obj_initialize2(rb_execution_context_t *ec, VALUE recv, VALUE obj)
{
return rb_obj_initialize(obj);
}

#include "class.rbinc"

static rb_alloc_func_t
class_get_alloc_func(VALUE klass)
{
Expand Down Expand Up @@ -4474,7 +4488,6 @@ InitVM_Object(void)

rb_define_method(rb_singleton_class(rb_cClass), "allocate", rb_class_alloc_m, 0);
rb_define_method(rb_cClass, "allocate", rb_class_alloc_m, 0);
rb_define_method(rb_cClass, "new", rb_class_new_instance_pass_kw, -1);
rb_define_method(rb_cClass, "initialize", rb_class_initialize, -1);
rb_define_method(rb_cClass, "superclass", rb_class_superclass, 0);
rb_define_method(rb_cClass, "subclasses", rb_class_subclasses, 0); /* in class.c */
Expand Down
5 changes: 4 additions & 1 deletion test/objspace/test_objspace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ def test_trace_object_allocations
assert_equal(line1, ObjectSpace.allocation_sourceline(o1))
assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(o1))
assert_equal(c1, ObjectSpace.allocation_generation(o1))
assert_equal(Class.name, ObjectSpace.allocation_class_path(o1))
# When the iseqs for class.rb got loaded, it happened to allocate
# and cache the class name. class.rb gets loaded before we can enable
# allocation tracing
#assert_equal(Class.name, ObjectSpace.allocation_class_path(o1))
assert_equal(:new, ObjectSpace.allocation_method_id(o1))

assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(o2))
Expand Down
4 changes: 4 additions & 0 deletions tool/mk_builtin_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ def collect_builtin base, tree, name, bs, inlines, locals = nil
argc -= 1
when 'mandatory_only'
func_name = nil
when 'send_delegate'
argc == 3 or raise "unexpected argument number #{argc}"
(arg = args[1])[0] == :symbol_literal or raise "symbol literal expected #{args}"
func_name = nil
when 'arg'
argc == 1 or raise "unexpected argument number #{argc}"
(arg = args.first)[0] == :symbol_literal or raise "symbol literal expected #{args}"
Expand Down

0 comments on commit 63434c3

Please sign in to comment.