Skip to content

Instantly share code, notes, and snippets.

@dalehamel
Last active July 24, 2019 02:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dalehamel/4aafc5436105937c0e0ad4316e583f70 to your computer and use it in GitHub Desktop.
Save dalehamel/4aafc5436105937c0e0ad4316e583f70 to your computer and use it in GitHub Desktop.
# First install https://github.com/postmodern/ruby-install
# Patches are below just download them
# After you have installed with ruby-install, you can chruby to use it
ruby-install ruby 2.6.3 -p 0001-RubyVM-InstructionSequence-eval_with.patch -p 0002-Update-iseq.eval-to-accept-optional-binding-Feature-.patch
From 5d0c8c83d8fdea16c403abbc80c160ddddb92ef8 Mon Sep 17 00:00:00 2001
From: Nobuyoshi Nakada <nobu@ruby-lang.org>
Date: Mon, 22 Feb 2016 16:54:57 +0900
Subject: [PATCH 1/2] RubyVM::InstructionSequence#eval_with
* iseq.c (iseq_eval_with): RubyVM::InstructionSequence#eval_with.
[Feature #12093]
* vm.c (rb_iseq_eval_in_scope): evaluate an iseq in the given
scope.
---
iseq.c | 18 ++++++++++++++++++
vm.c | 19 +++++++++++++++++++
vm_core.h | 1 +
3 files changed, 38 insertions(+)
diff --git a/iseq.c b/iseq.c
index fa4e72d2cd..62cc562a13 100644
--- a/iseq.c
+++ b/iseq.c
@@ -1345,6 +1345,23 @@ iseqw_eval(VALUE self)
return rb_iseq_eval(iseqw_check(self));
}
+/*
+ * call-seq:
+ * iseq.eval_with(binding) -> obj
+ *
+ * Evaluates the instruction sequence and returns the result.
+ *
+ * obj = Struct.new(:a, :b).new(1, 2)
+ * bind = obj.instance_eval {binding}
+ * RubyVM::InstructionSequence.compile("a + b").eval_with(bind) #=> 3
+ */
+static VALUE
+iseq_eval_with(VALUE self, VALUE scope)
+{
+ rb_secure(1);
+ return rb_iseq_eval_in_scope(iseqw_check(self), scope);
+}
+
/*
* Returns a human-readable string representation of this instruction
* sequence, including the #label and #path.
@@ -3483,6 +3500,7 @@ Init_ISeq(void)
rb_define_method(rb_cISeq, "disassemble", iseqw_disasm, 0);
rb_define_method(rb_cISeq, "to_a", iseqw_to_a, 0);
rb_define_method(rb_cISeq, "eval", iseqw_eval, 0);
+ rb_define_method(rb_cISeq, "eval_with", iseq_eval_with, 1);
rb_define_method(rb_cISeq, "to_binary", iseqw_to_binary, -1);
rb_define_singleton_method(rb_cISeq, "load_from_binary", iseqw_s_load_from_binary, 1);
diff --git a/vm.c b/vm.c
index 86b0e6075f..83a5d70beb 100644
--- a/vm.c
+++ b/vm.c
@@ -2138,6 +2138,25 @@ rb_iseq_eval(const rb_iseq_t *iseq)
return val;
}
+VALUE
+rb_iseq_eval_in_scope(const rb_iseq_t *iseq, VALUE scope)
+{
+ rb_thread_t *th = GET_THREAD();
+ rb_binding_t *bind = rb_check_typeddata(scope, &ruby_binding_data_type);
+ struct rb_block *base_block = &bind->block;
+
+ if (iseq->body->local_table_size > 0) {
+ vm_bind_update_env(bind, vm_make_env_object(th, th->cfp));
+ }
+#if 0
+ iseq_set_parent_block(iseq, base_block);
+ iseq_set_local_table(iseq, rb_vm_cref()->nd_tbl);
+#endif
+ vm_set_eval_stack(th, iseq, 0, base_block);
+
+ return vm_exec(th);
+}
+
VALUE
rb_iseq_eval_main(const rb_iseq_t *iseq)
{
diff --git a/vm_core.h b/vm_core.h
index 0120f21831..443aee895c 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -1616,6 +1616,7 @@ NORETURN(void rb_bug_context(const void *, const char *fmt, ...));
/* functions about thread/vm execution */
RUBY_SYMBOL_EXPORT_BEGIN
VALUE rb_iseq_eval(const rb_iseq_t *iseq);
+VALUE rb_iseq_eval_in_scope(const rb_iseq_t *iseq, VALUE scope);
VALUE rb_iseq_eval_main(const rb_iseq_t *iseq);
VALUE rb_iseq_path(const rb_iseq_t *iseq);
VALUE rb_iseq_realpath(const rb_iseq_t *iseq);
--
2.21.0
From d35112bf47119fdd920b527c8c5667add6b4cf86 Mon Sep 17 00:00:00 2001
From: Dale Hamel <dale.hamel@srvthe.net>
Date: Mon, 22 Jul 2019 23:05:46 -0400
Subject: [PATCH 2/2] Update iseq.eval to accept optional binding [Feature
#12093]
This implements a solution to [Feature #12093], and builts upon Nobu's
patch from 2016 that started this support.
This makes the RubyVM::InstructionSequence eval API and Kernel.eval
be more consistent:
* Using Kernel.eval, you can specify source code and a binding
to evaluate it within
* Using RubyVM::InstructionSequence, you can compile source code to an
instruction sequence
* You can execute source code within a binding, but you cannot execute
compiled instruction sequences against a binding, so the features of
Kernel.eval differ from iseq.eval
To fix this, the signature for iseq.eval is changed to allow for 0-1
arguments to be passed. I see this as keeping the same signature as
Kernel.eval, but just dropping the first parameter as the target to
evaluate is implicit as self. This allows preserving the original
functionality and performance characteristics of evaluating an instruction
sequence, while also matching the functionality of Kernel.eval for evaluating
compiled ruby code in the context of a specified binding.
RubyVM::InstructionSequence#eval
* iseq.c (iseqw_eval): RubyVM::InstructionSequence#eval.
[Feature #12093], updated from iseq_eval_with to be
consistent with Kernel.eval
* vm.c (rb_iseq_eval_in_scope): evaluate an iseq in the given
scope, updated for Ruby 2.7 rebase
* test/ruby/test_iseq.rb: Added tests for evaluating an iseq
within the context of a binding
---
iseq.c | 36 +++++++++++++++++-------------------
test/ruby/test_iseq.rb | 7 +++++++
vm.c | 17 +++++++----------
3 files changed, 31 insertions(+), 29 deletions(-)
diff --git a/iseq.c b/iseq.c
index 62cc562a13..40fb09e888 100644
--- a/iseq.c
+++ b/iseq.c
@@ -1332,34 +1332,33 @@ rb_iseqw_to_iseq(VALUE iseqw)
/*
* call-seq:
- * iseq.eval -> obj
+ * iseq.eval([binding]) -> obj
*
* Evaluates the instruction sequence and returns the result.
*
* RubyVM::InstructionSequence.compile("1 + 2").eval #=> 3
- */
-static VALUE
-iseqw_eval(VALUE self)
-{
- rb_secure(1);
- return rb_iseq_eval(iseqw_check(self));
-}
-
-/*
- * call-seq:
- * iseq.eval_with(binding) -> obj
*
- * Evaluates the instruction sequence and returns the result.
+ * If <em>binding</em> is given, which must be a Binding object, the
+ * evaluation is performed in its context.
*
* obj = Struct.new(:a, :b).new(1, 2)
* bind = obj.instance_eval {binding}
- * RubyVM::InstructionSequence.compile("a + b").eval_with(bind) #=> 3
+ * RubyVM::InstructionSequence.compile("a + b").eval(bind) #=> 3
*/
static VALUE
-iseq_eval_with(VALUE self, VALUE scope)
+iseqw_eval(int argc, const VALUE *argv, VALUE self)
{
- rb_secure(1);
- return rb_iseq_eval_in_scope(iseqw_check(self), scope);
+ VALUE scope;
+
+ if (argc == 0) {
+ rb_secure(1);
+ return rb_iseq_eval(iseqw_check(self));
+ }
+ else {
+ rb_scan_args(argc, argv, "01", &scope);
+ rb_secure(1);
+ return rb_iseq_eval_in_scope(iseqw_check(self), scope);
+ }
}
/*
@@ -3499,8 +3498,7 @@ Init_ISeq(void)
rb_define_method(rb_cISeq, "disasm", iseqw_disasm, 0);
rb_define_method(rb_cISeq, "disassemble", iseqw_disasm, 0);
rb_define_method(rb_cISeq, "to_a", iseqw_to_a, 0);
- rb_define_method(rb_cISeq, "eval", iseqw_eval, 0);
- rb_define_method(rb_cISeq, "eval_with", iseq_eval_with, 1);
+ rb_define_method(rb_cISeq, "eval", iseqw_eval, -1);
rb_define_method(rb_cISeq, "to_binary", iseqw_to_binary, -1);
rb_define_singleton_method(rb_cISeq, "load_from_binary", iseqw_s_load_from_binary, 1);
diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb
index b4d44e5f28..aedf935e84 100644
--- a/test/ruby/test_iseq.rb
+++ b/test/ruby/test_iseq.rb
@@ -281,6 +281,13 @@ def translate
end;
end
+ def test_eval_with_binding
+ obj = Struct.new(:a, :b).new(1, 2)
+ bind = obj.instance_eval {binding}
+ val = RubyVM::InstructionSequence.compile("a + b").eval(bind)
+ assert_equal(3, val)
+ end
+
def test_inspect
%W[foo \u{30d1 30b9}].each do |name|
assert_match(/@#{name}/, ISeq.compile("", name).inspect, name)
diff --git a/vm.c b/vm.c
index 83a5d70beb..e96a272dcc 100644
--- a/vm.c
+++ b/vm.c
@@ -2141,20 +2141,17 @@ rb_iseq_eval(const rb_iseq_t *iseq)
VALUE
rb_iseq_eval_in_scope(const rb_iseq_t *iseq, VALUE scope)
{
- rb_thread_t *th = GET_THREAD();
- rb_binding_t *bind = rb_check_typeddata(scope, &ruby_binding_data_type);
- struct rb_block *base_block = &bind->block;
+ rb_execution_context_t *ec = GET_EC();
+ rb_binding_t *bind = Check_TypedStruct(scope, &ruby_binding_data_type);
+
+ vm_set_eval_stack(ec, iseq, NULL, &bind->block);
+ /* save new env */
if (iseq->body->local_table_size > 0) {
- vm_bind_update_env(bind, vm_make_env_object(th, th->cfp));
+ vm_bind_update_env(scope, bind, vm_make_env_object(ec, ec->cfp));
}
-#if 0
- iseq_set_parent_block(iseq, base_block);
- iseq_set_local_table(iseq, rb_vm_cref()->nd_tbl);
-#endif
- vm_set_eval_stack(th, iseq, 0, base_block);
- return vm_exec(th);
+ return vm_exec(ec, TRUE);
}
VALUE
--
2.21.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment