Skip to content

IronRuby 4.0.3

Latest

Choose a tag to compare

@github-actions github-actions released this 30 Oct 10:04
· 2 commits to master since this release
0695d9a

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
    into C:\IronRuby40-x64 and put on the PATH
  • Windows x64: ironruby-4.0.3-win-x64.zip, with Windows Forms, WPF, ironrubyw.exe
    and Samples\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, into C:\IronRuby40-x64 by 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, iirb and igem work from a new terminal, while irb and gem stay
    CRuby's. igem install writes into
    the install tree, which is why the default is a directory you can write to and has no
    space in its path: Bundler's RUBYOPT, the Rails templates and mspec break on one.
    Running .rb files 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 from Util/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 to RegexpCompletor without 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 for Stri,
    1.ti, "abc".up and [1,2].ma are 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,
    as RUBY_DESCRIPTION does: 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::Linq turns LINQ's
    extension methods on for every call in the runtime, so a gem that calls it changes what
    list.select means in every other file. using now 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::Enumerable brings in one static class's, as
    C#'s using static does. 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
    the using line:

    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#. pty is new in the standard library, and
    sqlite3 has SQLite3::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

  • ironruby at a terminal starts irb, as python starts its interactive interpreter:
    with no script and no -e, and stdin a terminal, ironruby is iirb. From a pipe or a
    file it still reads the program from stdin, as ruby does, and so do ironruby -,
    -n, -p, -c and -x.
  • No more irb.cmd and irb.sh; IronRuby's irb is iirb. On a PATH, irb is CRuby's;
    ironruby -S irb still 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 install loaded 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, with igem install or Bundler. IRONRUBY_NO_HOST_GEMS is gone with it.
  • igem install builds no documentation unless asked, as on TruffleRuby: generating ri
    and rdoc takes longer than installing most gems. igem install --document (or
    gem: --document in ~/.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: super with no superclass
    method names its receiver and sets NoMethodError#name and #receiver, and an ndigits
    too large for round and its kin gets CRuby's "too big to convert" text. 89 messages are
    checked against CRuby 4.0.7's.
  • Mutex, Queue, SizedQueue and ConditionVariable are Thread::Mutex and so on
    (#44), as in CRuby. They were defined at the top level and aliased into Thread, so
    Mutex.name, #inspect and the error messages naming the class said Mutex. The
    top-level names are now the aliases, as in CRuby, and still name the same classes.
  • using_clr_extensions warns that it is deprecated (#50, #60), once per runtime, and
    names the using line 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 the A export, 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::ENOTTY from IO#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
    raises Errno::EBADF off a console, as CRuby does on Windows.
  • require 'ripper' gives Ripper::Filter, as in CRuby. rdoc's Ruby parser subclasses
    it, and generating a gem's documentation failed with uninitialized 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 the require failed, 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, and Etc.systmpdir
    answers as CRuby's does. Every require also skipped a regular expression that only an
    assembly name can match. IRONRUBY_STARTUP_TRACE=1 shows where a process's startup goes.
  • irb's colours show in a classic console window (#68). CRuby turns on
    ENABLE_VIRTUAL_TERMINAL_PROCESSING for 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). $stdout and
    $stderr handed 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 with WriteConsoleW, 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). A wait that raised ThreadError, because the thread
    did not hold the mutex, left the thread queued, so the next signal went to it and the
    real waiter slept through its timeout. A signal that arrived as a waiter's timeout ran
    out was lost. A negative timeout returned at once, where CRuby raises ArgumentError;
    wait now converts its timeout as Mutex#sleep does, as CRuby's does, with CRuby's
    RangeError for NaN and Infinity.
  • Mutex#sleep and ConditionVariable#wait take a Rational, a BigDecimal or a large
    Integer
    (#55). They accepted only an Integer that fits 32 bits or a Float, and raised
    TypeError for m.sleep(Rational(1, 100)) or m.sleep(2**40). They now convert the
    timeout through divmod(1), as CRuby does.
  • CLR extension methods reach a method that was already called (#43). After
    w.describe, a using_clr_extensions that added Describe overloads 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 one list.count, System::Linq's Count overloads could not be added.
  • An extension method named like a property accessor no longer breaks the property
    (#61). With an extension method get_Weight(this Widget) active, w.weight, w.weight =,
    an indexer, respond_to? and send of that name threw a CLR NullReferenceException, on
    every class that had the extension method, even one with a real Weight property. As
    in C#, such a method makes no property: w.weight is a NoMethodError, 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' raised LoadError when 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' after require '/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 retried require returned true but never read the DLL's
    extension methods; and a DLL required before any extension methods were used made the
    next using raise, whatever namespace it named, and lost the extension methods of every
    DLL queued after it. A retried require now reads them, and using warns 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 its refine block, 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 a using that 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 Ruby ensure blocks. A second Fiber#initialize raises CRuby's
    RuntimeError: cannot initialize twice.
  • Core constants have no source location (#45). Object.const_source_location(:Data)
    named a line in IronRuby's own ruby4.rb, and
    Errno::EHWPOISON.const_source_location(:Errno) one in gem_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.lock that names another version of
    a gem built into IronRuby now uses IronRuby's copy, unless the Gemfile excludes it;
    Kernel#rand and Psych no longer crash when used from several threads; a # comment in
    an /x Regexp 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 as ActiveSupport::Duration; File.truncate works above 4 GiB; and child
    processes no longer inherit SIGPIPE ignored.

Performance

  • A refinement costs something only at the calls it can change (#49, #59). Defining a
    refinement anywhere, even one nothing using'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 #60
  • using 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