The Rubinius Language Platform
MPL-2.0 License
Bot releases are hidden (Show)
Published by brixen over 8 years ago
Version 3.28 (2016-04-24)
In a 'statically typed' (or early bound) language, the executable at the call
site is known 'statically', via textual analysis of the program text. In a
'dynamically-typed' (or late bound) language, the particular executable is not
known until the program is executed. Consequently, in a late-bound language, a
call site must permit dynamic reconfiguration based on the program's
execution.
This dynamic reconfiguration requires prediction: when this kind of object is
seen, we predict we will see it again. The cost of finding the executable in
the first place can then be reduced by caching the previous result of looking
up the executable. When we see this kind of object, we can retrieve the
executable from the cache at less cost than looking it up.
If our predictions are correct, and the mechanism of creating and retrieving
items from the cache is actually less than searching for the item, we can
execute the program more efficiently. However, predictions can be wrong. If
they are wrong, the cache mechanism can become pure overhead, work done that
produces no value. In this case, the caching mechanism will make the program
less efficient rather than more efficient.
The number of cached items is typically limited because as the number grows,
the cost of retrieving items typically grows. Ideally, the first cached item
would always be the desired one. When that is not true, some (hopefully less
costly than lookup) search mechanism is required. Contiguous structures that
permit linear probing and are CPU cache-efficient usually perform very well.
If the number of entries grows, the algorithm needs to change from on O(n)
complexity to something closer to O(1), or the extra work of the caching
mechanism will be a cost increase instead of a cost reduction.
To account for our predictions being wrong, we need to be able to update,
replace, or even discard previous predictions. In same cases, we may need to
disable the caching mechanism completely. This latter case is true when the
types of objects seen at a call site vary widely.
This change also adds some new metrics for understanding how a program is
performing from the perspective of the call sites and inline caches. The next
mechanism to add is diagnostics to see the operation of individual caches
instead of the aggregate values that the metrics provide.
Another improvement that is needed is to cache #method_missing
,
#respond_to?
and #send
call sites.
.git
directory in the projectThis compiles Rubinius in debug mode even if .git
is a file, so that
we compile in debug mode when rubinius is in a git submodule.
This can be set directly through the Travis interface.
Apparently, nope.
Published by brixen over 8 years ago
Version 3.27 (2016-04-15)
Published by brixen over 8 years ago
Version 3.26 (2016-04-11)
Until we replace this mechanism, we're going to lock operations.
Published by brixen over 8 years ago
Version 3.25 (2016-04-06)
Published by brixen over 8 years ago
Version 3.24 (2016-04-05)
Published by brixen over 8 years ago
Version 3.23 (2016-04-04)
The instruction set is being improved to, among other things, decouple method
or function resolution, caching, and invocation. This will provide arbitrary
semantics for finding the code to run, caching it, and invoking it.
Published by brixen over 8 years ago
Version 3.22 (2016-03-31)
=== 2.6.2 / 2016-03-12
Bug fixes:
=== 2.6.1 / 2016-02-28
Bug fixes:
default_path
and home
are set for paths. Pull request #1513Gem.paths=
. Pull=== 2.6.0 / 2016-02-26
Minor enhancements:
gem push
to the gem's "allowed_push_host"Bug fixes:
gem push
credentials under the host you signed-in for.coding
location to first line. Pull request #1471 by SHIBATAGem::Version@segments
instance variable. Pull request #1487 by=== 2.5.2 / 2016-01-31
Bug fixes:
gemspec
method, fixing #1204 and #1033. Pull requestMinor enhancements:
--no-rc
flag, which skips loading .gemrc
. Pull request #1329 by Luisallowed_push_host
. By Josh Lane.gem list --exact
, which finds gems by string match instead of regex. Pull--source
. Pull request--[no-]post-install-message
to install
and update
. Pull request #1162--host
option to yank
, providing symmetry with pull
. Pull requestbuild
without '.gemspec'. Pull request #1454 by Stephensource
option on gems in Gemfile. Pull request #1355 by=== 2.5.1 / 2015-12-10
Bug fixes:
When a value is stored into a managed object, we run a write barrier. This bit
of code serves two functions. First, it "remembers" the object being stored in
case the object it is being stored into is a mature object and the object
being stored is a young object. When the young collector runs, those
"remembered" objects are part of the roots for the young region being
collected. Second, if the object being stored into hasn't been traced yet, we
trace it, and we add the object being stored to the stack of objects to trace.
If the Tuple fields aren't initialized, we'll hit a garbage value when we try
to trace it. If we don't run the write barrier when we assign values, we have
the chance of losing one of those references. An alternative to "doubly
initializing" the Tuple instance is to run two loops. One that assigns values
and one that runs the write barrier on the entries. It should be apparent that
we haven't really saved anything by using this purported "optimization" of
creating the Tuple uninitialized (dirty).
This is a good lesson in optimizing. The best optimization is not running any
code at all, rather than making running code faster, if that is an option. It
usually will give greater return to rework code in a way that significant code
can be eliminated vs "optimized". In this case, we should get rid of Array and
Tuple in the middle of stuff like calling methods.
Ultimately, CallFrame needs to be split into separate frame types for managed
code, FFI, JIT, and native methods (ie C-API), and constructors should be
defined for every type.
Published by brixen over 8 years ago
Version 3.21 (2016-03-29)
However, not running the GC synchronously when a memory threshold is reached
(eg when the Immix region has exhausted available chunks) means that heap
growth or spill over to another region (eg the Large Object Space) is
essential.
The growth is undesirable (because right now there is no compaction and growth
will continue unbounded) and the spill is especially undesirable because the
LOS collector is much less efficient than the Immix collector.
Since the marker thread is essentially racing the mutator threads, under high
allocation rates, the marker would be perpetually behind and a lot of growth
or spill would occur.
Eventually, we'll implement compaction, which will mitigate heap growth. But
that doesn't solve all the problems. To address these issues, we adhere to the
following constraints:
During the finalizer finish process, no new finalizers may be created.
Previously, we treated these to types quite differently. We used the '// slot'
notation when declaring the first type so that we could automatically generate
code to process the objects. The second type was usually somewhat consistent
but also ad hoc in many places.
Now we mostly use two macros (attr_accessor for the first type, and attr_field
for the second type) that both define the variables and define setters and
getters for them. There are still some places where this needs to be cleaned
up.
One reason that we need to be very careful about the setters for the first
type of variable (the managed object references) is that when the concurrent
GC is running, we need to know when an object reference is stored into an
object that may have already been processed by the marker. If we don't see
this, that object may be considered unreachable by the GC even though it's
being referenced. This results in the equivalent to a use-after-free bug in a
language with manually managed memory.
Consistently using the managed object reference accessors means that we can
layer other behavior on the access functions. For example, if we tag objects
with an identifier for the thread that created them, we can log or prevent
mutation access from a different thread.
Published by brixen over 8 years ago
Version 3.20 (2016-03-25)
The idea was to have the compiler help check where in call paths a
garbage-collection cycle could run. Unfortnately, adding this in as an
after-thought resulted in all the places where GCTokens are created from thin
air deep in some call path. It didn't change the fact that GC could happen
pretty much anywhere.
In a managed runtime, either GC can happen everywhere or it should only happen
at a very small number of extremely well-defined points. The middle ground of
"it can happen at all these places" is an invitation for a low budget horror
movie, dismembered objects strewn throughout the code.
Along with the rework of the stop-the-world mechanism, the removal of GCToken
and restricting the invocation of a GC cycle to a single well-defined method
call in a few well-defined locations, and finally, making all allocation paths
GC-safe (ie GC will NOT run when allocating an object), Rubinius will have much
better defined GC behavior.
The GC safe allocation path is important for cases like the string_dup
instruction, where a young GC cycle could run when allocating the dup and the
original String (eg a literal String in a method) is in the young generation
and moved. Since the original String is on the C stack and not in a GC root
object, the dup fails when copying the contents of the original String. It's
better to make allocation GC-safe than to accept the performance cost of the GC
root in these sorts of cases. Also, that case is only one well-defined instance
of the issue. There are more complicated ones.
What that means is that when requesting a new object be created, the request
will be fulfilled unless the system (or process limits prevent it) without
GC running. In other words, there are two possible results of allocating an
object: 1) a new object, or 2) an exception because no more memory is
available to the process.
In either case, from the point the object is requested until that request
returns (or the return is bypassed by the exception unwind), the GC will not
run.
There is a trade-off here between running the GC at the instant that some
threshold is breached (eg the eden space is exhausted) and loosening some
requirements that must be maintained for a generational, moving garbage
collector (ie every object reference must be known to the GC at the time the
GC runs). Since we run GC on method entry and loop back branches, there is no
reasonable scenario in which deferring GC until allocation has completed will
result in unwanted object graph thresholds being breached pathologically (eg
an execution path where allocation can grow unbounded).
The initialization routine is T::initialize(State* state, T* obj)
, where T is
the type of object being allocated. The method is a static method of the class
of the object. This breaks with the protocol that Ruby uses where new
is a
module method and initialize
is an instance method. The primary reason for
choosing a static (ie C++ class) is to avoid an instance method operating on
an incompletely initialized object.
One purpose of this initialization protocol is to eliminate or reduce the
double initialization that we were doing (ie setting all fields to nil and
then initializing them to other default values). The main initialization
method shown above may be an empty body, in which case the compiler will elide
it anyway and there's no overhead to the protocol. In that case, another
initialization method should be called on the newly created object. Since the
allocation method is templated and if the initialization method is visible (ie
in the header file), the compiler should be able to elide remaining double
initialization in most contexts.
Thread.new
, the OS thread will never run because aThread.new
raises an exception orUnfortunately, this is more complex than it appears on its face. While
executing a process exit, we are essentially racing any sleeping threads that
may wake up and attempt to access resources that are being destroyed (something
like one of those scenes from Inception with the buildings crumbling around the
participants). We cannot waking thread proceed once process exit has started,
so we permanently lock a mutex that every waking thread must acquire before
progressing.
Ultimately, these lock resources will need to be in the program's data segment,
so they are static memory, not dynamically allocated, as they are now.
The vm()->thread_state()->raise_reason() == cThreadKill except when
vm_thread_state primitive is called from the Thread#run ensure clause.
For some reason, these updates appear to be causing the failure of the
spec quarantined in b903c1f1989233f95d1d329eef53cd30a266ad91.
See cfd672a5373e2e16a97b43f01692fe28c8a9043a
The goal right now is to stabilize heap growth so that concurrent marking
can be re-introduced, then the young gen can be re-added.
CallFrame invariants are:
Assume a vector of references to CallFrame instances exists
(VM::call_frames_), where n=VM::call_frames_index_ is the total number of
CallFrame instances and VM::call_frame_ refers to VM::call_frames_[n-1], and
VM::call_frame->previous refers to VM::call_frames_[n-2], etc.
So, we avoid operating on really dead Threads, where a Thread's lifetime
looks something like this:
Time.now
has aPublished by brixen over 8 years ago
Version 3.19 (2016-02-27)
Published by brixen over 8 years ago
Version 3.18 (2016-02-27)
Published by brixen over 8 years ago
Version 3.17 (2016-02-26)
Published by brixen over 8 years ago
Version 3.16 (2016-02-25)
Resolves #3622.
This is an explanation of the issue:
We already have A::B been autoloaded by the main thread. When c.rb (see
any of the previous links) is being autoloaded in another thread, class
A::B is being opened using Rubinius.open_class_under (through
Rubinius.open_class).
Since module A has an Autoload entry for B in its constant_table,
open_class_under tries to call #call on this Autload object, which returns nil
because #resolve returns false (CodeLoader.require returns false
if a feature is already been loaded).
With nil returned, open_class_under decided to create a new Class object
for B, which means the autoload entry it already had for :C is lost,
resulting in constant A::B::C not being found.
This reverts commit 01a7c2f9b91a6edf5637c6dbf0d79f4e59b560b3.
Published by brixen over 8 years ago
Version 3.15 (2016-02-19)
Fixes #2992
Closes #3372
meth.ruby_method.executable
is a instance ofThis is used for example in pg gem:
https://github.com/ged/ruby-pg/blob/bb4693e811f9348f202835e701e6509d15685b0a/lib/pg/connection.rb#L187
Fixes #3607
Related to #3531
Published by brixen over 8 years ago
Version 3.14 (2016-01-28)
Published by brixen over 8 years ago
Version 3.13 (2016-01-25)
Published by brixen over 8 years ago
Version 3.12 (2016-01-22)
Published by brixen over 8 years ago
Version 3.11 (2016-01-21)
Published by brixen almost 9 years ago
Version 3.10 (2016-01-17)
Published by brixen almost 9 years ago
Version 3.9 (2016-01-16)
This fixes #3570