tco_method 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/tco_method/version.rb +1 -1
- data/lib/tco_method.rb +5 -3
- data/test/test_helper.rb +1 -3
- data/test/test_helpers/assertions.rb +28 -0
- data/test/unit/mixin_test.rb +0 -2
- data/test/unit/tco_method_test.rb +41 -41
- metadata +4 -12
- data/test/test_helpers/factorial_stack_buster_helper.rb +0 -31
- data/test/test_helpers/stack_busters/factorial_stack_buster.rb +0 -60
- data/test/test_helpers/stack_busters/vanilla_stack_buster.rb +0 -25
- data/test/test_helpers/vanilla_stack_buster_helper.rb +0 -16
- data/test/test_helpers/vm_stack_helper.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b38db19eea395f824f107cdf26128b24f4a0de7
|
4
|
+
data.tar.gz: 061f302f00571f8d3290b2c33deb15c8e3e12c85
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5582d4e9e64fc235526dc5c5504ad041e1736252f9017c45934990f0bbd8c005327dc12ecad192373a1cce42a00e76209b5abff4038248170105860b79222987
|
7
|
+
data.tar.gz: 8e38fa82f97b0cb9f60678ef5342f59f7c7042ed5cd4104f90b49a66cb82de4e9c6571cd36aaa9ea8695d2f3c4d885c718794372c19a8f15bd5f7a0e21d7503f
|
data/lib/tco_method/version.rb
CHANGED
data/lib/tco_method.rb
CHANGED
@@ -53,7 +53,9 @@ module TCOMethod
|
|
53
53
|
#{existing_method.source}
|
54
54
|
end
|
55
55
|
CODE
|
56
|
-
|
56
|
+
|
57
|
+
file, line = existing_method.source_location
|
58
|
+
tco_eval(code, file, File.dirname(file), line - 1)
|
57
59
|
method_name.to_sym
|
58
60
|
end
|
59
61
|
|
@@ -65,8 +67,8 @@ module TCOMethod
|
|
65
67
|
# @return [Object] Returns the value of the final expression of the provided
|
66
68
|
# code String.
|
67
69
|
# @raise [ArgumentError] if the provided code argument is not a String.
|
68
|
-
def self.tco_eval(code)
|
70
|
+
def self.tco_eval(code, file = nil, path = nil, line = nil)
|
69
71
|
raise ArgumentError, "Invalid code string!" unless code.is_a?(String)
|
70
|
-
RubyVM::InstructionSequence.new(code,
|
72
|
+
RubyVM::InstructionSequence.new(code, file, path, line, ISEQ_OPTIONS).eval
|
71
73
|
end
|
72
74
|
end
|
data/test/test_helper.rb
CHANGED
@@ -12,9 +12,7 @@ require "minitest/autorun"
|
|
12
12
|
require "mocha/setup"
|
13
13
|
require "tco_method"
|
14
14
|
|
15
|
-
|
16
|
-
require "test_helpers/factorial_stack_buster_helper"
|
17
|
-
require "test_helpers/vanilla_stack_buster_helper"
|
15
|
+
require_relative "test_helpers/assertions"
|
18
16
|
|
19
17
|
# Use alternate shoulda-style DSL for tests
|
20
18
|
class TCOMethod::TestCase < Minitest::Spec
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module TCOMethod
|
2
|
+
module TestHelpers
|
3
|
+
module Assertions
|
4
|
+
def assert_tail_call_optimized(method, *args)
|
5
|
+
is_tco = tail_call_optimized(method, *args)
|
6
|
+
msg = "Expected method #{method.name} to be tail call optimized"
|
7
|
+
assert is_tco, msg
|
8
|
+
end
|
9
|
+
|
10
|
+
def refute_tail_call_optimized(method, *args)
|
11
|
+
is_tco = tail_call_optimized(method, *args)
|
12
|
+
msg = "Expected method #{method.name} not to be tail call optimized"
|
13
|
+
refute is_tco, msg
|
14
|
+
end
|
15
|
+
|
16
|
+
def tail_call_optimized?(method, *args)
|
17
|
+
initial_length = nil
|
18
|
+
method.call(*args) do
|
19
|
+
if initial_length.nil?
|
20
|
+
initial_length = caller.length
|
21
|
+
else
|
22
|
+
break initial_length == caller.length
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/test/unit/mixin_test.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require "test_helper"
|
2
2
|
|
3
3
|
class TCOMethodTest < TCOMethod::TestCase
|
4
|
-
include TCOMethod::TestHelpers::
|
4
|
+
include TCOMethod::TestHelpers::Assertions
|
5
5
|
|
6
6
|
Subject = TCOMethod
|
7
7
|
|
@@ -14,26 +14,34 @@ class TCOMethodTest < TCOMethod::TestCase
|
|
14
14
|
|
15
15
|
# Equivalent to the below, but provides a target for verifying that
|
16
16
|
# tco_module_method works on Classes and tco_class_method works on Modules.
|
17
|
-
def self.
|
18
|
-
|
17
|
+
def self.module_fib_yielder(index, back_one = 1, back_two = 0, &block)
|
18
|
+
yield back_two if index > 0
|
19
|
+
index < 1 ? back_two : module_fib_yielder(index - 1, back_one + back_two, back_one, &block)
|
19
20
|
end
|
20
21
|
|
21
22
|
# Equivalent to the above, but provides a target for verifying that
|
22
23
|
# tco_module_method works on Classes and tco_class_method works on Modules.
|
23
|
-
def self.
|
24
|
-
|
24
|
+
def self.class_fib_yielder(index, back_one = 1, back_two = 0, &block)
|
25
|
+
yield back_two if index > 0
|
26
|
+
index < 1 ? back_two : class_fib_yielder(index - 1, back_one + back_two, back_one, &block)
|
25
27
|
end
|
26
28
|
|
27
29
|
define_method(:instance_block_method) { }
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
+
# Equivalent to the above, but provides a target for verifying that
|
32
|
+
# instance methods work for both Classes and Modules
|
33
|
+
def instance_fib_yielder(index, back_one = 1, back_two = 0, &block)
|
34
|
+
yield back_two if index > 0
|
35
|
+
index < 1 ? back_two : instance_fib_yielder(index - 1, back_one + back_two, back_one, &block)
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
34
39
|
TestModule = Module.new(&test_subject_builder)
|
35
40
|
TestClass = Class.new(&test_subject_builder)
|
36
41
|
|
42
|
+
# Grab source before it's recompiled for use later
|
43
|
+
InstanceFibYielderSource = TestClass.instance_method(:instance_fib_yielder).source
|
44
|
+
|
37
45
|
subject { Subject }
|
38
46
|
|
39
47
|
context Subject.name do
|
@@ -57,21 +65,15 @@ class TCOMethodTest < TCOMethod::TestCase
|
|
57
65
|
end
|
58
66
|
|
59
67
|
should "compile the given code with tail call optimization" do
|
60
|
-
|
68
|
+
EvalDummy = dummy_class = Class.new
|
61
69
|
subject.tco_eval(<<-CODE)
|
62
70
|
class #{dummy_class.name}
|
63
|
-
|
64
|
-
n <= 1 ? acc : factorial(n - 1, n * acc)
|
65
|
-
end
|
71
|
+
#{InstanceFibYielderSource}
|
66
72
|
end
|
67
73
|
CODE
|
68
74
|
|
69
|
-
|
70
|
-
|
71
|
-
assert_unoptimized_factorial_stack_overflow(factorial_seed)
|
72
|
-
|
73
|
-
expected_result = iterative_factorial(factorial_seed)
|
74
|
-
assert_equal expected_result, dummy_class.new.factorial(factorial_seed)
|
75
|
+
fib_yielder = dummy_class.new.method(:instance_fib_yielder)
|
76
|
+
assert tail_call_optimized?(fib_yielder, 5)
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
@@ -119,15 +121,14 @@ class TCOMethodTest < TCOMethod::TestCase
|
|
119
121
|
end
|
120
122
|
|
121
123
|
should "re-compile the given method with tail call optimization" do
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
124
|
+
fib_yielder = method_owner.method(:module_fib_yielder)
|
125
|
+
refute tail_call_optimized?(fib_yielder, 5)
|
126
|
+
|
127
|
+
subject.call(method_owner, :module_fib_yielder, :module)
|
128
|
+
tco_fib_yielder = method_owner.method(:module_fib_yielder)
|
129
|
+
assert tail_call_optimized?(tco_fib_yielder, 5)
|
127
130
|
|
128
|
-
|
129
|
-
expected_result = iterative_factorial(factorial_seed)
|
130
|
-
assert_equal expected_result, method_owner.module_factorial(factorial_seed)
|
131
|
+
assert_equal fib_yielder.source_location, tco_fib_yielder.source_location
|
131
132
|
end
|
132
133
|
end
|
133
134
|
|
@@ -139,15 +140,14 @@ class TCOMethodTest < TCOMethod::TestCase
|
|
139
140
|
end
|
140
141
|
|
141
142
|
should "re-compile the given method with tail call optimization" do
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
143
|
+
fib_yielder = method_owner.method(:class_fib_yielder)
|
144
|
+
refute tail_call_optimized?(fib_yielder, 5)
|
145
|
+
|
146
|
+
subject.call(method_owner, :class_fib_yielder, :module)
|
147
|
+
tco_fib_yielder = method_owner.method(:class_fib_yielder)
|
148
|
+
assert tail_call_optimized?(tco_fib_yielder, 5)
|
147
149
|
|
148
|
-
|
149
|
-
expected_result = iterative_factorial(factorial_seed)
|
150
|
-
assert_equal expected_result, method_owner.class_factorial(factorial_seed)
|
150
|
+
assert_equal fib_yielder.source_location, tco_fib_yielder.source_location
|
151
151
|
end
|
152
152
|
end
|
153
153
|
|
@@ -159,16 +159,16 @@ class TCOMethodTest < TCOMethod::TestCase
|
|
159
159
|
end
|
160
160
|
|
161
161
|
should "re-compile the given method with tail call optimization" do
|
162
|
-
# Exceed maximum available stack depth by 100 for good measure
|
163
|
-
factorial_seed = factorial_stack_buster_stack_depth_remaining + 100
|
164
162
|
instance_class = instance_class_for_receiver(method_owner)
|
165
|
-
assert_raises(SystemStackError) do
|
166
|
-
instance_class.new.instance_factorial(factorial_seed)
|
167
|
-
end
|
168
163
|
|
169
|
-
|
170
|
-
|
171
|
-
|
164
|
+
fib_yielder = instance_class.new.method(:instance_fib_yielder)
|
165
|
+
refute tail_call_optimized?(fib_yielder, 5)
|
166
|
+
|
167
|
+
subject.call(method_owner, :instance_fib_yielder, :instance)
|
168
|
+
tco_fib_yielder = instance_class.new.method(:instance_fib_yielder)
|
169
|
+
assert tail_call_optimized?(tco_fib_yielder, 5)
|
170
|
+
|
171
|
+
assert_equal fib_yielder.source_location, tco_fib_yielder.source_location
|
172
172
|
end
|
173
173
|
end
|
174
174
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tco_method
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Danny Guinther
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: method_source
|
@@ -72,11 +72,7 @@ files:
|
|
72
72
|
- lib/tco_method/version.rb
|
73
73
|
- tco_method.gemspec
|
74
74
|
- test/test_helper.rb
|
75
|
-
- test/test_helpers/
|
76
|
-
- test/test_helpers/stack_busters/factorial_stack_buster.rb
|
77
|
-
- test/test_helpers/stack_busters/vanilla_stack_buster.rb
|
78
|
-
- test/test_helpers/vanilla_stack_buster_helper.rb
|
79
|
-
- test/test_helpers/vm_stack_helper.rb
|
75
|
+
- test/test_helpers/assertions.rb
|
80
76
|
- test/unit/method_info_test.rb
|
81
77
|
- test/unit/mixin_test.rb
|
82
78
|
- test/unit/tco_method_test.rb
|
@@ -106,11 +102,7 @@ specification_version: 4
|
|
106
102
|
summary: Simplifies compiling code with tail call optimization in MRI Ruby.
|
107
103
|
test_files:
|
108
104
|
- test/test_helper.rb
|
109
|
-
- test/test_helpers/
|
110
|
-
- test/test_helpers/stack_busters/factorial_stack_buster.rb
|
111
|
-
- test/test_helpers/stack_busters/vanilla_stack_buster.rb
|
112
|
-
- test/test_helpers/vanilla_stack_buster_helper.rb
|
113
|
-
- test/test_helpers/vm_stack_helper.rb
|
105
|
+
- test/test_helpers/assertions.rb
|
114
106
|
- test/unit/method_info_test.rb
|
115
107
|
- test/unit/mixin_test.rb
|
116
108
|
- test/unit/tco_method_test.rb
|
@@ -1,31 +0,0 @@
|
|
1
|
-
require "test_helpers/stack_busters/factorial_stack_buster"
|
2
|
-
|
3
|
-
module TCOMethod
|
4
|
-
module TestHelpers
|
5
|
-
module FactorialStackBusterHelper
|
6
|
-
extend Forwardable
|
7
|
-
|
8
|
-
def_delegators "#{self.name}.stack_buster", :unoptimized_factorial
|
9
|
-
|
10
|
-
long_alias = :factorial_stack_buster_stack_depth_remaining
|
11
|
-
def_delegator "#{self.name}.stack_buster", :stack_depth_remaining, long_alias
|
12
|
-
|
13
|
-
def self.stack_buster
|
14
|
-
@@stack_buster ||= StackBusters::FactorialStackBuster.new
|
15
|
-
end
|
16
|
-
|
17
|
-
def assert_unoptimized_factorial_stack_overflow(depth)
|
18
|
-
# Subtract 1 to account for this methiod call since result should be
|
19
|
-
# relative to caller
|
20
|
-
unoptimized_factorial(depth - 1)
|
21
|
-
assert false, "Factorial for depth #{depth} did not overflow!"
|
22
|
-
rescue SystemStackError
|
23
|
-
assert true
|
24
|
-
end
|
25
|
-
|
26
|
-
def iterative_factorial(n)
|
27
|
-
(2..n).inject(1, :*)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,60 +0,0 @@
|
|
1
|
-
require "pry"
|
2
|
-
|
3
|
-
module TCOMethod
|
4
|
-
module TestHelpers
|
5
|
-
module StackBusters
|
6
|
-
class FactorialStackBuster
|
7
|
-
include VMStackHelper
|
8
|
-
|
9
|
-
def frame_size
|
10
|
-
@frame_size ||= vm_stack_size / stack_overflow_threshold
|
11
|
-
end
|
12
|
-
|
13
|
-
def unoptimized_factorial(n, acc = 1)
|
14
|
-
n <= 1 ? acc : unoptimized_factorial(n - 1, n * acc)
|
15
|
-
end
|
16
|
-
|
17
|
-
def stack_depth_remaining
|
18
|
-
stack_depth_remaining_for_frame_size(frame_size)
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
# Stack buster based on binary search for point of stack oveflow of
|
24
|
-
# unoptimized_factorial
|
25
|
-
def stack_overflow_threshold
|
26
|
-
# Use a frame size that's larger than the expected frame size to ensure
|
27
|
-
# that limit is less than the point of overflow
|
28
|
-
limit = last_good_limit = stack_depth_limit_for_frame_size(LARGEST_VM_STACK_SIZE * 2)
|
29
|
-
last_overflow_limit = nil
|
30
|
-
# Determine an upper-bound for binary search
|
31
|
-
loop do
|
32
|
-
begin
|
33
|
-
unoptimized_factorial(limit)
|
34
|
-
last_good_limit = limit
|
35
|
-
limit *= 2
|
36
|
-
rescue SystemStackError
|
37
|
-
last_overflow_limit = limit
|
38
|
-
break
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# Reset for binary search for point of stack overflow
|
43
|
-
limit = last_good_limit
|
44
|
-
loop do
|
45
|
-
return last_overflow_limit if last_overflow_limit == last_good_limit + 1
|
46
|
-
begin
|
47
|
-
half_the_distance_to_overflow = (last_overflow_limit - limit) / 2
|
48
|
-
limit += half_the_distance_to_overflow
|
49
|
-
unoptimized_factorial(limit)
|
50
|
-
last_good_limit = limit
|
51
|
-
rescue SystemStackError
|
52
|
-
last_overflow_limit = limit
|
53
|
-
limit = last_good_limit
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
module TCOMethod
|
2
|
-
module TestHelpers
|
3
|
-
module StackBusters
|
4
|
-
class VanillaStackBuster
|
5
|
-
include VMStackHelper
|
6
|
-
|
7
|
-
def frame_size
|
8
|
-
@frame_size ||= vm_stack_size / stack_overflow_threshold
|
9
|
-
end
|
10
|
-
|
11
|
-
def stack_depth_remaining
|
12
|
-
stack_depth_remaining_for_frame_size(frame_size)
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def stack_overflow_threshold(depth = 1)
|
18
|
-
stack_overflow_threshold(depth + 1)
|
19
|
-
rescue SystemStackError
|
20
|
-
depth
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
require "test_helpers/stack_busters/vanilla_stack_buster"
|
2
|
-
|
3
|
-
module TCOMethod
|
4
|
-
module TestHelpers
|
5
|
-
module VanillaStackBusterHelper
|
6
|
-
extend Forwardable
|
7
|
-
|
8
|
-
def_delegator "#{self.name}.stack_buster", :stack_depth_remaining, :vanilla_stack_depth_remaining
|
9
|
-
|
10
|
-
def self.stack_buster
|
11
|
-
@@stack_buster ||= StackBusters::VanillaStackBuster.new
|
12
|
-
end
|
13
|
-
private_class_method :stack_buster
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
module TCOMethod
|
2
|
-
module TestHelpers
|
3
|
-
module VMStackHelper
|
4
|
-
LARGEST_VM_STACK_SIZE = 128
|
5
|
-
|
6
|
-
# Calculate the maximum number of times a stack frame of a given size could
|
7
|
-
# be repeated before running out of room on the stack.
|
8
|
-
def stack_depth_limit_for_frame_size(frame_size)
|
9
|
-
vm_stack_size / frame_size
|
10
|
-
end
|
11
|
-
module_function :stack_depth_limit_for_frame_size
|
12
|
-
|
13
|
-
# Calculate how many more times a frame could be repeated before running out of
|
14
|
-
# room on the stack.
|
15
|
-
def stack_depth_remaining_for_frame_size(frame_size)
|
16
|
-
stack_depth_limit_for_frame_size(frame_size) - caller.length + 1
|
17
|
-
end
|
18
|
-
module_function :stack_depth_remaining_for_frame_size
|
19
|
-
|
20
|
-
# Determine maximum size of VM stack for a single thread based on
|
21
|
-
# environment or default value.
|
22
|
-
def vm_stack_size
|
23
|
-
ENV["RUBY_THREAD_VM_STACK_SIZE"] || RubyVM::DEFAULT_PARAMS[:thread_vm_stack_size]
|
24
|
-
end
|
25
|
-
module_function :vm_stack_size
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|