Repository navigation
IronRuby 4.0.3 comes with a Windows installer, and its irb works on Windows as CRuby's
does: it starts, completes with TypeCompletor, shows colours in a classic console window
and writes non-ASCII text as written. ironruby at a terminal starts irb. Calling .NET gains
using System::Linq, which brings CLR extension methods into one file as C#'s using
directive does, and DLLs required by path find the DLLs they reference. A refinement no
longer slows down the calls it cannot change, and error messages quote names 'like this',
as CRuby 4.0's do.
Download
Self-contained: unpack one and run ./ironruby, ./iirb.sh and ./igem.sh - on Windows
ironruby.cmd, iirb.cmd and igem.cmd. No .NET to install. Or, on Windows, run the
installer.
- Windows x64 installer:
ironruby-4.0.3-win-x64-setup.exe, the zip below, installed
intoC:\IronRuby40-x64and put on the PATH - Windows x64:
ironruby-4.0.3-win-x64.zip, with Windows Forms, WPF,ironrubyw.exe
andSamples\Notepad\notepad.cmd - Linux x64:
ironruby-4.0.3-linux-x64.tar.gz - macOS arm64:
ironruby-4.0.3-osx-arm64.tar.gz, built in CI, not tested there
SHA256SUMS lists the checksums. Each file and SHA256SUMS has a signed build
provenance attestation, which says which workflow built it, from which commit, in which run:
gh attestation verify ironruby-4.0.3-win-x64-setup.exe --repo Largo/ironruby
New
-
A Windows installer, in the style of RubyInstaller. It installs the win-x64 zip's
tree, unchanged, intoC:\IronRuby40-x64by default, or a directory you choose, for you
alone (no admin rights) or for all users, and puts that directory first on the PATH, so
ironruby,ir,iirbandigemwork from a new terminal, whileirbandgemstay
CRuby's.igem installwrites into
the install tree, which is why the default is a directory you can write to and has no
space in its path: Bundler'sRUBYOPT, the Rails templates and mspec break on one.
Running.rbfiles with IronRuby is an option, off by default. The uninstaller removes
the files, the gems installed since, and the PATH entry. It is built in CI with Inno
Setup 6.7.3 fromUtil/installer/ironruby.iss, and installed, run and uninstalled there
before it is published. -
irb completes as CRuby's does (#68). irb's default completor on Ruby 3.4 and later is
TypeCompletor, from the bundled gem repl_type_completor. IronRuby did not ship it, so
irb fell back toRegexpCompletorwithout a word and offered other candidates, in
another order. IronRuby now ships repl_type_completor 0.1.16 and rbs 4.0.0.dev.5, the
rbs release IronRuby's rbs extension is built from; both come from rubygems.org, each
pinned by SHA-256. CRuby 4.0.9 bundles repl_type_completor 0.1.12 and rbs 3.10.0, but
repl_type_completor before 0.1.14 does not accept rbs 4. The candidates forStri,
1.ti,"abc".upand[1,2].maare CRuby's. -
irb 1.18.0, with its startup banner (#70). irb 1.18 opens with a small Ruby logo, a
line naming irb and the Ruby, a tip and the current directory; CRuby shows it once irb
1.18 is installed over the 1.16.0 it bundles. On IronRuby the line names the interpreter,
asRUBY_DESCRIPTIONdoes:IRB v1.18.0 - IronRuby 4.0.3 (4.0.0). irb 1.18 finds open
nestings with Prism instead of Ripper; reline stays at 0.6.3, which irb 1.18 accepts. -
using System::Linq(#50, #60).using_clr_extensions System::Linqturns LINQ's
extension methods on for every call in the runtime, so a gem that calls it changes what
list.selectmeans in every other file.usingnow also takes a CLR namespace and brings
its extension methods into the rest of the file, class body or eval string - where a
refinement would be active - as C# brings them into the files that say
using System.Linq;.using System::Linq::Enumerablebrings in one static class's, as
C#'susing staticdoes. Inside that scope a call resolves as it does under
using_clr_extensions, so moving code from one form to the other means changing only
theusingline:using System::Linq list = System::Collections::Generic::List[Integer].new([3, 1, 2]) list.where { it > 1 }.order_by { -it }.to_a # => [3, 2]
The 101 LINQ samples run in CI and print their expected output.
-
More gems with C extensions (#47). As JRuby does, IronRuby leaves the installed gem's
Ruby code as it is and provides only the extension: markly and rbs with the gems' own C
libraries, built from pinned sources and giving CRuby's output byte for byte; strptime,
yajl-ruby, zstd-ruby and cool.io ported to C#.ptyis new in the standard library, and
sqlite3 hasSQLite3::Backup. With them, 34 of ruby-bench's 36 macro benchmarks run,
instead of 20; lobsters, shipit, fluentd and ruby-lsp run for the first time.
Changed
ironrubyat a terminal starts irb, aspythonstarts its interactive interpreter:
with no script and no-e, and stdin a terminal,ironrubyisiirb. From a pipe or a
file it still reads the program from stdin, asrubydoes, and so doironruby -,
-n,-p,-cand-x.- No more
irb.cmdandirb.sh; IronRuby's irb isiirb. On a PATH,irbis CRuby's;
ironruby -S irbstill runs IronRuby's. - IronRuby no longer uses a CRuby's gems. It used to offer the gem directory of a CRuby
4.0 found on the PATH for third-party gems. That loaded CRuby's code into IronRuby: with
CRuby on the PATH,igem installloaded CRuby's rdoc plugin over the rdoc IronRuby ships
("already initialized constant RDoc::VERSION") and then failed generating the gem's
documentation. IronRuby now reads only its own gem directories; a gem it needs is
installed for it, withigem installor Bundler.IRONRUBY_NO_HOST_GEMSis gone with it. igem installbuilds no documentation unless asked, as on TruffleRuby: generating ri
and rdoc takes longer than installing most gems.igem install --document(or
gem: --documentin~/.gemrc) builds it with the rdoc IronRuby ships, and uninstalling
the gem removes it again.- Error messages quote names 'like this' (#62), as CRuby has since 3.4:
undefined method 'foo' for an instance of Integer, where IronRuby still wrote
`foo'. IronRuby's own messages, about CLR interop or command-line options, use
straight quotes too. Some messages also got CRuby's wording:superwith no superclass
method names its receiver and setsNoMethodError#nameand#receiver, and anndigits
too large forroundand its kin gets CRuby's "too big to convert" text. 89 messages are
checked against CRuby 4.0.7's. Mutex,Queue,SizedQueueandConditionVariableareThread::Mutexand so on
(#44), as in CRuby. They were defined at the top level and aliased intoThread, so
Mutex.name,#inspectand the error messages naming the class saidMutex. The
top-level names are now the aliases, as in CRuby, and still name the same classes.using_clr_extensionswarns that it is deprecated (#50, #60), once per runtime, and
names theusingline to write instead. It still works as before.- The bundled gems are CRuby 4.0.9's, except repl_type_completor and rbs (above) and
irb 1.18.0, which is newer than the irb CRuby 4.0.9 bundles.
Fixes
- irb starts on Windows again. In 4.0.2 it stopped at once with
unknown symbol "FillConsoleOutputCharacter". reline asks Fiddle for Win32 functions by their bare
names, and Fiddle now also looks for theAexport, as CRuby's does. In a console, irb
then crashed: reline hands the console API its buffers as[str].pack("p"), and on
IronRuby that was an index, not an address. It is a real address now, of a block holding
the String's bytes, and what C writes there through Fiddle comes back into the String, as
with CRuby - so reline runs as Ruby ships it. - irb shows results on Windows. After the first result it stopped with
Errno::ENOTTYfromIO#winsize: IronRuby's io/console asked only termios for the
terminal size, which Windows does not have. It now asks the console, as CRuby does, and
raisesErrno::EBADFoff a console, as CRuby does on Windows. require 'ripper'givesRipper::Filter, as in CRuby. rdoc's Ruby parser subclasses
it, and generating a gem's documentation failed withuninitialized constant Ripper::Filter.- irb starts about a quarter of a second sooner.
tmpdir.rb, which irb loads, does
require 'etc.so'; IronRuby had no such file, so therequirefailed, after RubyGems had
searched every installed gem for it. A C extension required by its library name now loads
IronRuby's implementation of it (etc.rb), as JRuby loads its own;Dir.tmpdir's system
candidate is then the Windows temporary folder rather than/tmp, andEtc.systmpdir
answers as CRuby's does. Everyrequirealso skipped a regular expression that only an
assembly name can match.IRONRUBY_STARTUP_TRACE=1shows where a process's startup goes. - irb's colours show in a classic console window (#68). CRuby turns on
ENABLE_VIRTUAL_TERMINAL_PROCESSINGfor a console it writes to; IronRuby did not, so in a
classic conhost window irb's colours and reline's cursor movements came out as literal
escape sequences. IronRuby now sets the flag on stdout and stderr when they are a console. - Text outside ASCII shows in the Windows console as written (#70).
$stdoutand
$stderrhanded Ruby's UTF-8 bytes to the console, which read them in its output code
page, 850 or 437 on most machines: "ä" came out as two characters. As CRuby does,
IronRuby now writes to a console withWriteConsoleW, decoding the bytes as UTF-8; a
character split across two writes waits for its last byte. Redirected to a file or a
pipe, the bytes go out unchanged, as before. ConditionVariable#wait(#42). Awaitthat raisedThreadError, because the thread
did not hold the mutex, left the thread queued, so the nextsignalwent to it and the
real waiter slept through its timeout. Asignalthat arrived as a waiter's timeout ran
out was lost. A negative timeout returned at once, where CRuby raisesArgumentError;
waitnow converts its timeout asMutex#sleepdoes, as CRuby's does, with CRuby's
RangeErrorfor NaN and Infinity.Mutex#sleepandConditionVariable#waittake a Rational, a BigDecimal or a large
Integer (#55). They accepted only an Integer that fits 32 bits or a Float, and raised
TypeErrorform.sleep(Rational(1, 100))orm.sleep(2**40). They now convert the
timeout throughdivmod(1), as CRuby does.- CLR extension methods reach a method that was already called (#43). After
w.describe, ausing_clr_extensionsthat addedDescribeoverloads went unseen:
w.describe("x")kept calling the old method group, under the Ruby name, and raised
TypeError. The same happened to extension methods of interfaces and generic types:
after onelist.count,System::Linq'sCountoverloads could not be added. - An extension method named like a property accessor no longer breaks the property
(#61). With an extension methodget_Weight(this Widget)active,w.weight,w.weight =,
an indexer,respond_to?andsendof that name threw a CLRNullReferenceException, on
every class that had the extension method, even one with a realWeightproperty. As
in C#, such a method makes no property:w.weightis aNoMethodError,w.get_Weight
calls the method, and a real property or indexer works as before. - A DLL required by path finds the DLLs it references (#63). With extension methods
active,require '/x/Demo2.dll'raisedLoadErrorwhen Demo2 was built against a Demo
already required from/x/Demo.dll; without them, the first call that needed Demo
failed instead. IronRuby now answers such a reference with an assembly it has loaded, if
the name, culture and public key token match and the version is at least the one asked
for; then from$LOAD_PATH, as before; then from the directory of the DLL that asked. An
assembly already loaded is no longer loaded a second time from$LOAD_PATH, and
load_assembly 'Demo'afterrequire '/x/Demo.dll'returns that Demo. In a process with
several runtimes, each answers only the references of the DLLs it loaded. - A DLL whose extension methods could not be read can be required again (#64). When a
dependency was missing, a retriedrequirereturnedtruebut never read the DLL's
extension methods; and a DLL required before any extension methods were used made the
nextusingraise, whatever namespace it named, and lost the extension methods of every
DLL queued after it. A retriedrequirenow reads them, andusingwarns about the DLL
it cannot read and carries on with the rest. - Refinements (#49). A method added to, redefined in or removed from an active
refinement outside itsrefineblock, or a refined operator added after a method turned
hot, was ignored by call sites that had already run. A method defined before anything was
refined but first called after saw ausingthat came later in its file. All three now
behave as in CRuby. - Abandoned fibers are reclaimed (#65, #66). A suspended fiber, or an external
Enumerator, that a program dropped kept a parked CLR thread for the rest of the process.
Such a fiber is now collected, and its worker thread goes back to a pool, without running
the fiber's Rubyensureblocks. A secondFiber#initializeraises CRuby's
RuntimeError: cannot initialize twice. - Core constants have no source location (#45).
Object.const_source_location(:Data)
named a line in IronRuby's ownruby4.rb, and
Errno::EHWPOISON.const_source_location(:Errno)one ingem_prelude.rb; both return
[], as CRuby does for constants of its C core. - Deeply nested code no longer overflows the stack (#46). An array literal nested 1500
levels deep, on a 1 MB stack such as a Windows main thread's, overflowed it inside .NET's
JIT and ended the process with "Stack overflow.". Code nested that deep is now compiled
on a fresh stack, through a new patch to the DLR. - Found running ruby-bench's apps (#47): a
Gemfile.lockthat names another version of
a gem built into IronRuby now uses IronRuby's copy, unless the Gemfile excludes it;
Kernel#randand Psych no longer crash when used from several threads; a#comment in
an/xRegexp is a comment, so a(in it no longer counts as a group (this broke
ActiveRecord's SQLite order matcher);Date#<=>compares with Numerics and objects that
#coerce, such asActiveSupport::Duration;File.truncateworks above 4 GiB; and child
processes no longer inheritSIGPIPEignored.
Performance
- A refinement costs something only at the calls it can change (#49, #59). Defining a
refinement anywhere, even one nothingusing'd, turned off the method JIT for the rest of
the process and every fast path in call dispatch. Now only a call to a method that some
refinement of the receiver's class defines checks for refinements, and the JIT and OSR
keep compiling. A benchmark that defines one refinement and never activates it took
2.99-3.25 s and now takes 2.06-2.11 s, the same as without the refinement.
Thanks
@sholden for the ConditionVariable#wait fixes (#42) and the
timeouts Mutex#sleep takes (#55), for Thread::Mutex and its siblings (#44), for
refinements that cost nothing where they cannot change a call (#49, #59), for
using System::Linq and the 101 LINQ samples (#50, #60), for the CLR extension method and
DLL loading fixes (#43, #61, #63, #64), for quoting names in error messages as CRuby does
(#62), and for reclaiming abandoned fibers (#65, #66).
What's Changed
- irb in a Windows console: Fiddle finds A exports, pack("p") writes a real address by @Largo in #39
- A Windows installer, in the style of RubyInstaller by @Largo in #38
- irb on Windows, ironruby starts irb, no CRuby gems by @Largo in #40
- Faster startup: irb 0.23 s sooner, and a startup trace by @Largo in #41
- DeepNesting1: compile a deeply nested lambda on a fresh stack (DLR patch 0102) by @Largo in #46
- Core prelude constants: no source location with gems either; Errno loop, backtrace names by @Largo in #45
- ConditionVariable#wait: no stale waiter after a failed wait, no lost signal at a timeout, MRI's timeout errors by @sholden in #42
- CLR extension methods reach a method group used before using_clr_extensions by @sholden in #43
- Mutex, Queue, SizedQueue and ConditionVariable are Thread::Mutex and so on, as in MRI by @sholden in #44
- Refinements: only refined calls pay, and the JIT and OSR keep working by @sholden in #49
- Refinements: cheaper lexical guards and precompiled dispatchers (#56), plus a review fix by @Largo in #59
- Refinements: cheaper lexical guards and precompiled dispatchers by @sholden in #56
using System::Linq: scoped CLR extension methods (#50), plus a review fix by @Largo in #60using System::Linq: CLR extension methods scoped like C#'s using directive by @Largo in #50- Mutex#sleep and ConditionVariable#wait take a Rational, a BigDecimal or a long as their timeout by @Largo in #55
- CLR extension methods named like accessors don't break property lookups (rebased) by @Largo in #61
- Error messages quote names 'like this', as Ruby 3.4 and 4.0 do (rebased) by @Largo in #62
- DLLs required by path find the DLLs they reference, loaded or alongside (rebased) by @Largo in #63
- A DLL whose extension methods couldn't be read can be required again (rebased) by @Largo in #64
- run-tests.sh: runs in several trees at once by @Largo in #48
- Composite conversions: one binder per context by @Largo in #58
- Benchmark suite, C-extension gems for ruby-bench (lobsters, shipit, fluentd, ruby-lsp), and the core fixes they needed by @Largo in #47
- Fiber reclamation (#65) with review fixes by @Largo in #66
- Reclaim abandoned fiber workers without running Ruby cleanup by @sholden in #65
- Check a local DLR for every patch before a release is built from it by @Largo in #67
- irb: TypeCompletor as on CRuby, and ANSI colours in a classic conhost by @Largo in #68
- irb stays CRuby's: IronRuby's irb is iirb only by @Largo in #69
- irb 1.18.0 with its startup banner, naming IronRuby; UTF-8 in the Windows console by @Largo in #70
- Release 4.0.3 by @Largo in #71
New Contributors
Full Changelog: v4.0.2...v4.0.3