Skip to content

Releases

d6 1.0.0-rc5

Choose a tag to compare

@github-actions github-actions released this 14 Nov 02:54
e4864b4

The fifth release candidate fills in everyday collection and String argument
shapes, and makes JIT-compiled code substantially faster on calls,
comparisons and block loops. The cookies cut (0.2.4) was published with these
changes on 2026-11-12; no other bundled cut changed since rc4.

Changed

  • More collection and String conveniences: a Hash each_with_object block may take (key, value, memo); Hash values_at, fetch_values and first/first(n); zip over up to six arrays; Array slice_when and intersect?; Int#step(limit, by); and String insert(index, other) and prepend(other).
  • Everyday collection and String methods accept the argument shapes that used to raise an arity error: any?/all?/none?/one? without a block (on Array; any?/all?/none? on Hash), each_slice(n)/each_cons(n) with a block, sum(start) (sum(0.0), sum("")), min(n)/max(n), uniq with a key block, flatten(depth), Hash#merge with a conflict block, String#chomp(suffix), squeeze(chars), index(needle, start), and Float#floor(digits)/ceil(digits). New: Array#take_while/drop_while and Hash#delete_if/keep_if.
  • With the JIT on (DIAMOND_JIT=1), Array select, each_with_index and reduce and Int times run their blocks as native loops, as each and map already did: about half the instructions per element for each_with_index, reduce and times, and a quarter fewer for select.
  • With the JIT on, compiled code runs much faster on calls and comparisons: fib takes half the cycles, mandelbrot a third less, poly a quarter less, and sieve, trees, nbody and objects 15-20% less. Values are now written as whole 8-byte words and copied in 8-byte halves, so the loads that read them back no longer wait for the stores to retire, and a comparison followed by a branch on its result branches directly.
  • With the JIT on, Array map and select push into their result array inline instead of calling C for every element: about 30% fewer instructions per element (map 156 to 113, select keeping everything 177 to 123, with an empty block).
  • Session cookies are marked Secure under DIAMOND_ENV=production, matching active_auth: the cookies cut's CookieSession (0.2.4, with a secure: override, and now Path=/) and the project_board example. Apps behind a TLS proxy such as nginx keep speaking plain HTTP. The guestbook, project_board, chat (wss:) and library examples were checked behind nginx over HTTPS; bookmarks, job_service and task_board are loopback-only single-user tools.
  • The brand is written D6 everywhere it names the language: guides, READMEs, the docs site, registry pages, the VS Code extension, CLI help text and GitHub release titles. Commands, file names and code stay lowercase (d6 build, .d6, d6-lang).
  • A thread that fails and is never joined now prints its uncaught exception and backtrace on stderr (d6: runtime error in unjoined thread: ...) instead of losing it silently. Exit status is unchanged.
  • Bundled cut maintenance avoids redundant timestamp formatting in text logging and simplifies database-config and static-file reads, including file cleanup on read errors. Network-safety comments now describe its string-only checks without outdated claims about runtime capabilities.

Diamond 1.0.0-rc4

Choose a tag to compare

@github-actions github-actions released this 12 Nov 05:39

The fourth release candidate extends rc3's compact disk representation to
runtime metadata. No bundled cut versions changed since rc3.

Changed

  • Compiled programs keep string constants and type-set members at their used sizes in memory, avoiding the expansion of compact caches and standalone binaries into mostly unused 4 KB string buffers and eight-member type records. Compiler, template and thread copies retain independent metadata ownership. Existing bytecode caches rebuild once for format 22.
  • A reproducible standalone-application soak exercises populated Pheint GraphQL APIs and authenticated Skindicate reads and writes with the JIT at one and four workers. The RC4 candidate used 50–67% less loaded RSS than rc3 in these runs, with successful read-backs and database integrity checks. See the measurement report for results and limits.

Diamond 1.0.0-rc3

Choose a tag to compare

@github-actions github-actions released this 11 Nov 23:46

The third release candidate reduces cache and executable sizes, fixes native
collection dispatch in JIT-compiled blocks, and expands general runtime
verification. No bundled cut versions changed since rc2.

Changed

  • The runtime test suite now checks cross-feature failure recovery and native-service workflows across interpreter, JIT, GC-stress, cached-bytecode and standalone-binary execution.
  • Registry deployments build the runtime at -O3 (was -O2), turn on the JIT through DIAMOND_JIT=1 in the env file, and tools/deploy_registry.sh --revision=REF deploys a release tag instead of HEAD.
  • Bytecode caches and d6 build binaries store type sets compactly (count, flag and used members only) instead of as fixed 354-byte records: Pheint's cache went from 71 MB to 7.3 MB and its binary from 76 MB to 11 MB. The cache format version is now 21, so existing caches are rebuilt once.

Fixed

  • JIT-compiled blocks dispatch methods on captured instance-variable arrays, hashes and strings correctly instead of treating them as user-defined instances. This fixes Pheint's benchmark worker-pool initialization.
  • The docs site rendered the "Compatibility links" lists on the syntax and I/O pages as raw Markdown. Its renderer now keeps inline anchors, <br>, <kbd>, <sup> and <sub> inside paragraphs and list items, and an anchor on a line of its own no longer swallows the list below it.

Diamond 1.0.0-rc2

Choose a tag to compare

@github-actions github-actions released this 11 Nov 04:24
0880b6a

The second release candidate fixes captured-local retries, JIT error handling
and supervised cancellation, and local type flow around safe calls. No cuts
changed since rc1.

Changed

  • Developer documentation reflects retry/capture semantics, JIT error and cancellation contracts, and current CI and test-running procedures.
  • Ubuntu 22.04 is no longer a supported deployment target and has been removed from portable CI validation.

Fixed

  • Retrying a rescued body preserves access to locals captured by blocks, including captures in rescue and ensure clauses.
  • Differential fuzzing actually executes the JIT, with a terminating timeout and a bounded interpreter preflight.
  • JIT failures after captured-local writes preserve those writes instead of replaying the closure.
  • Supervised workers stop promptly inside JIT Array block loops and native loops calling compiled blocks.
  • Safe calls preserve local type facts when nil skips their arguments, and join conditional assignments.
  • Release downloads select the exact requested asset when falling back to formatted GitHub API responses.

Diamond 1.0.0-rc1

Choose a tag to compare

@github-actions github-actions released this 09 Nov 23:17
48696d4

The first release candidate for d6 1.0. Since 0.29.0: safe navigation (&.), and a faster JIT for
array stores and Int ops with a constant operand (loop and sieve now beat YJIT).

Added

  • Safe navigation (&.). value&.name(args) calls name unless value is nil; then the
    arguments are skipped and the call is nil. Only nil short-circuits, each &. covers one call,
    writers (pet&.name = "Max") and block calls work, and a line starting with &. continues a
    chain. The result is the method's return type joined with Nil, and the receiver is treated as
    non-nil inside the call (core syntax). The language
    server resolves the receiver left of &. through its non-nil view, so pets["rex"]&.,
    pet&. on a Pet | Nil and owner&.pet()&. complete, hover and jump to definition; a plain
    . after a nullable value stays unresolved. The formatter and the VS Code grammar know &..

Changed

  • Portable tarballs withdrawn from releases. Releases no longer attach the static
    d6-VERSION-linux-*.tar.gz builds introduced in 0.29.0 while their limits (no JIT, baseline
    x86-64, no d6 build) are worked out. CI still builds and tests them, and
    tools/build_portable.sh still makes them.
  • Package file names keep the upstream version. A pre-release such as 1.0.0-rc1 is packaged
    as version 1.0.0~rc1 (so the final 1.0.0 upgrades it), but the files are named
    d6-lang_1.0.0-rc1-1_amd64.deb and d6-lang-1.0.0-rc1-1.fc44.x86_64.rpm.
  • Faster array[i] = value in compiled code. The JIT stores straight into a young, unfrozen,
    unconstrained Array when the index is an in-bounds Int, and only falls back to the full
    INDEX_SET path for promoted (write-barrier), frozen, typed or out-of-range cases.
    Array.new(n, value) and [value] * n fill without a per-element modulo. sieve: −50%
    instructions, −16% cycles.
  • Cheaper Int ops with a constant operand in compiled code. i + 1, x * 7, i < 1000
    and x == 0 check only the variable operand's kind and use the constant as an immediate;
    x % k for a positive constant is a mask (powers of two) or a multiply by the reciprocal
    instead of a hardware divide. With the JIT on, loop and sieve now beat YJIT in the local
    benchmark suite (loop −24% cycles, fib −5%).

Diamond 0.29.0

Choose a tag to compare

@github-actions github-actions released this 09 Nov 06:13
57f0132

Added

  • A portable d6 for any Linux. Each release now attaches d6-VERSION-linux-x86_64.tar.gz
    and d6-VERSION-linux-aarch64.tar.gz. They hold d6, facet, d6-lsp and d6-dap as
    fully static musl executables, with SQLite, PostgreSQL, MySQL/MariaDB (MySQL 8 and MariaDB
    authentication plugins included), TLS, zlib and BCrypt linked in. Before each release they
    are checked in clean Ubuntu 26.04 and 22.04, Debian 12, Fedora 44, Arch, Alpine and
    openSUSE Leap containers. They target baseline x86-64 rather than x86-64-v3, have no JIT
    and cannot run d6 build. tools/build_portable.sh builds them in an Alpine container
    (deployment).

Changed

  • Self-hosted compiler closer to native. selfhost/parser.d6 now compiles -> Self return
    types, attr_reader item: T in generic classes, generic type arguments on classes inside a
    module (Relation[T]), every File and Dir method, Fiber.yield,
    TCPServer.listen's host:/backlog: options with the non-blocking TCP forms, and do-block
    contracts on generic functions (generic_collect(21) do [] end binds T to Int). It also
    compiles every runtime builtin call: Time, Thread.new, Channel, Supervisor.new,
    Process, IO, SQLite3/MySQL/PostgreSQL, TLS and UDP sockets, DNS, Signal,
    Tensor, SemVer, and the crypto and encoding calls, so Active Record runs through it. Run
    through it, every one of the 1,587 tests/cases programs now compiles and runs (up from
    1,342 of 1,586), including the Callable contracts native infers: do blocks on constructors and
    on a union of callables, generic function references (identity) fixed to the type a parameter
    expects, and Box.new(41) known as a Box[Int].
  • ProgramBuilder type sets can say Callable[[], T]. A declare_type_set descriptor takes
    an optional seventh Bool marking a written parameter list, so a typed zero-parameter Callable
    differs from Callable[0, T] as it does in the native compiler.
  • Time and Tensor values cross threads. They can be Thread.new arguments and results,
    go through a Channel, and be the result of ProgramBuilder#run, copied as plain data.
  • The JIT compiles functions that call methods on values of unknown type. A method call
    whose receiver the JIT could not prove the type of (an untyped parameter, an Array element, a
    polymorphic call site) used to keep the whole function interpreted; it now compiles to a call
    into the interpreter's own dispatch. Functions that pass a block capturing their locals, and
    the Math functions, compile too. With DIAMOND_JIT=1, the counts benchmark runs about 37%
    fewer instructions, nbody and poly about 14% fewer, and sort 13% fewer.
  • Faster recursion under the JIT. A compiled function calling itself now calls its own
    machine code directly, with the callee's registers on the native stack, instead of going
    back through the VM. With DIAMOND_JIT=1, fib runs 53% fewer instructions (160 to 121 ms)
    and trees 14% fewer. The call-depth limit and its stack-overflow error are unchanged.
  • Blocks run as machine code under the JIT. The body of a do ... end block or closure now
    compiles, including the captured variables it reads and writes. Functions that read a
    top-level constant compile too. With DIAMOND_JIT=1, the blocks and iterate benchmarks run
    36% fewer instructions (91 to 76 ms and 152 to 116 ms), and regex 5% fewer. Captured
    variables are now read and written by inline machine code instead of a call per access, and
    a compiled block is entered directly from each/map/times, which takes blocks a
    further 20% (70 to 60 ms) and iterate 15% (112 to 95 ms).
  • Closure calls and string interpolation compile under the JIT. A function that calls a
    block or closure it holds, or builds a string with #{...}, no longer stays interpreted. With
    DIAMOND_JIT=1, the closures benchmark runs 5.6% fewer cycles and regex 6% fewer.
  • Functions with begin/rescue/ensure compile under the JIT. Exception handlers,
    retry, raise, and break/next/return through ensure no longer keep a function
    interpreted, with the same errors and backtraces. With DIAMOND_JIT=1, the exceptions
    benchmark runs 14% fewer cycles.
  • A memory budget keeps a VM on the interpreter under DIAMOND_JIT=1. DIAMOND_MAX_MEMORY_BYTES
    was not enforced in compiled code: a compiled loop that only grew one array ran past the
    budget until the system ran out of memory. Instruction and wall-clock budgets already did
    this.
  • Many more functions compile under the JIT. !, puts/print, class-method calls
    (Name.method(), self.method()), class variables, destructuring assignment, unary minus,
    symbols, .class(), and calls that pass keywords, a *spread or explicit type arguments no
    longer keep a function interpreted. These run the interpreter's own code for the
    instruction, so results and errors are unchanged. Across the test cases, benchmarks and
    examples, functions the JIT turned away fell from 3,041 to 1,333.
  • Generic functions and methods of generic classes compile under the JIT. Each compiled
    call binds its type variables on entry, from the arguments, explicit type arguments
    (f[Int](https://github.com/diamond-language/diamond/blob/v0.29.0/x), obj.method[Int](https://github.com/diamond-language/diamond/blob/v0.29.0/x)), the receiver or a block's creator, exactly as the
    interpreter does, so type checks and errors are unchanged. -> Self methods compile too.
    Functions the JIT turned away fell from 1,333 to 553. With DIAMOND_JIT=1, the
    typed_dispatch benchmark runs 7% fewer instructions and the new generic_calls 4.6% fewer.
  • The JIT compiles functions with more than 256 registers. A function that big (a long
    hash literal, two large methods the ORM packages share) used to run interpreted; it now
    compiles, with its registers on the heap as the interpreter keeps them. Functions the JIT
    turns away fell from 553 to 457, and native calls to smaller functions cost the same as before.
  • The JIT compiles variadic functions and methods. def f(head, *rest) and
    def f(*rest, &block) now compile; the rest Array is built in the compiled code, and their
    tail calls to themselves still run in constant stack. A call with more arguments than the
    function has registers runs in the interpreter, as does a function whose parameter setup
    writes where its rest arguments wait (a default before the rest parameter). Functions the
    JIT turns away fell from 457 to 416.
  • The JIT compiles blocks that break or return, and the functions they land in. A
    function whose block breaks out of the call it was passed to, or returns from the function,
    used to run interpreted, and so did the block. Both compile now, ensure clauses still run on
    the way out, and a break from a block whose call has finished is still a rescuable error.
    About 70 fewer functions are turned away.
  • The JIT compiles functions that use the builtins. Bitwise and shift operators, <=>,
    to_i/to_f/to_sym, case patterns, JSON, Regexp.new, encodings, digests, ciphers,
    Time, Fiber, sockets, processes, database opens, Thread.new, define_method and the
    rest of the builtin calls no longer send a function back to the interpreter. Across the test
    cases, benchmarks and examples the functions the JIT turns away fell from 363 to 17, and
    those 17 only stop under the debugger.
  • Compiled code calls other compiled functions and methods directly. With the JIT on, a
    call from compiled code to another compiled function, or to a method of an object whose class
    the call site has seen before (up to four classes per site), no longer goes through the
    interpreter's call machinery, and getters and setters called that way compile too. Instruction
    counts fell 49% on a polymorphic shapes[i].area() loop, 38% on small objects, 28% on an
    n-body simulation and 20% on structs; method dispatch benchmarks run 44 to 73% fewer.
    Redefining a method with redefine_method or define_method still takes effect at once.
  • A block boxes only the locals it reads. Creating a do ... end block used to move every
    local visible at that point into a heap cell, and every later read of those locals went
    through the cell. Now only the ones the block's body names are, so the rest stay plain
    registers: an n-body step that runs one bodies.each() do ... end per call does 11% fewer
    instructions in the interpreter.
  • Compiled code handles objects inline. With the JIT on, Class.new allocates and calls a
    compiled initialize directly, a type check against a single class, x == nil on an object
    whose class has no ==, instance variable reads and writes, and calls to plain getters and
    setters (attr_accessor, def x() = @x) no longer go through C. Together with the native
    calls above, instruction counts fell 63% on an n-body simulation, 61% on small objects, 54%
    on polymorphic calls, 49% on binary trees and 41% on structs.
  • Compiled code reads Array elements inline. With the JIT on, values[i] for a
    non-negative Int index inside the array is a direct load; everything else takes the usual
    path. The sieve benchmark runs 15% fewer instructions.
  • Closures and captured variables are cheaper to create. Swept closures (up to 16 captures)
    and capture cells are kept for reuse, like small objects, strings and arrays already were.
    A closure-heavy benchmark runs 31% fewer instructions in the interpreter and 47% fewer with
    the JIT.
  • Compiled code runs Array each and map loops itself. With the JIT on, a block passed
    to each or map on an Array is called natively from the loop instead of through C for
    every element. A map-heavy benchmark runs 8% fewer instructions and an each-heavy on...
Read more

Diamond 0.28.0

Choose a tag to compare

@github-actions github-actions released this 07 Nov 03:49
7cf5178

Changed

  • Cheaper getter and setter calls. A call site that keeps reaching the same attr_reader,
    attr_writer or attr_accessor method on the same class now remembers that it is one, and
    later calls read or write the field after a class check and a value check, skipping the method
    lookup's per-call checks. Typed and untyped receivers both benefit, and errors (a wrong-typed
    value, a frozen receiver) are the same as before. The n-body simulation runs about 13% fewer
    instructions (8% fewer cycles), building and walking trees 5% fewer cycles, and struct- and
    object-heavy code 2-5% fewer.
  • Cheaper class-typed getters and call results. A getter declared as one class, with or
    without Nil (attr_reader left: Node | Nil), now checks its value with one class comparison
    instead of walking its declared type each call; a subclass instance or a wrong-typed value
    still gets the full check and the same error. Method, function and == call results are
    copied back in a form the processor can forward straight from the callee's stores. The
    n-body simulation runs about 10% fewer cycles, object- and struct-heavy code 4-5% fewer, and
    building and walking trees about 3% fewer.
  • Cheaper new for simple constructors. An initialize that only checks its parameters'
    types and copies them into fields (def initialize(x: Int, y: Int) @x = x; @y = y end, and
    every struct's) no longer runs as a method call: new checks the arguments and fills the
    fields directly. Any argument it can't settle at once runs initialize as before, so errors
    and backtraces are unchanged. Struct-heavy code runs about 17% fewer cycles, object
    allocation 13% fewer and building binary trees 8% fewer. With DIAMOND_TRACE_JIT, such
    constructors no longer appear among the compiled functions, since they never run.
  • Cheaper short Strings and Arrays. The collector keeps swept Strings of up to about 100
    bytes, sorted by size, and swept Array headers for the next allocations instead of returning
    them to malloc, as it already did for small instances; together the pools stay within one
    nursery's worth of memory. Regexp#match also collects its group positions on the stack and
    builds its result Array at its final size. Text processing runs about 34% fewer cycles, regular
    expressions 28%, Hash-heavy code 23%, String building 20% and exception handling 17%.
  • Faster regular expressions on ASCII text. The matcher steps over an ASCII character and
    tests \w against it without asking the UTF-8 decoder, which it used to call for every
    character. Results are unchanged for all text; mostly-ASCII input matches about 15% faster.

Diamond 0.27.0

Choose a tag to compare

@github-actions github-actions released this 06 Nov 08:11
2727999

Changed

  • Cheaper calls. A call no longer copies its arguments into a fresh register block when they
    already sit in order, which a single argument always does. A typed function's parameter checks
    run as a few inline tests on entry instead of one dispatched instruction each when the types are
    simple (Int, Float, Bool, String, Symbol, an exact class, or one of these or Nil); anything else,
    such as a subclass instance, still goes through the full check with the same error. And a body
    ending in a ternary returns straight from each branch. Recursive fib runs about 11% fewer
    instructions and cycles, building and walking binary trees about 9% fewer instructions, and
    object-heavy code 2-3% fewer.
  • Cheaper objects. Reading or writing an instance variable checks the instance's shape directly
    instead of going through a per-site cache, a trivial getter or setter is recognized once per
    program instead of on every call, and the collector keeps swept small instances (up to eight
    fields, at most one nursery's worth) for the next allocations instead of returning them to
    malloc. Allocating a million small objects and calling a method on each runs about 16% fewer
    cycles; struct-heavy code 15% fewer, building and walking trees 10% and an n-body simulation 18%.
    DIAMOND_TRACE_FIELDS, which reported the field cache's hits and misses, is gone.
  • Cheaper call setup. Each function's call context is built once when a program starts instead
    of on every call into it, a warm method call site makes the whole call in one cache lookup, and
    a method call's arguments are copied without a library call. A method call costs about 150 fewer
    instructions. Closures and object-heavy code run about 8% fewer instructions, struct-heavy code
    4%, exceptions 6% and an n-body simulation 4%. The call-site caches are now 8-way, so a program
    whose hot call sites happen to share a set no longer evicts one on every pass. Reading an
    instance variable no longer makes a function call (polymorphic shape code 4% fewer
    instructions), and a body ending in a ternary returns a parameter or local from its true branch
    without copying it first (recursive fib 3% fewer).
  • Typed fields. The compiler now tracks Int and Float instance variables. When every write to
    a field stores an Int (or every write a Float), the class's own initialize sets the field
    before anything else runs, and nothing else can write it (no subclass, no module writing fields
    by name, no define_method or compile_method), reading the field is known to give that type.
    Arithmetic on such fields uses the Int fast paths, and a method or generated attr_reader
    that returns one skips its return check. A call to a method defined further down the class now
    knows that method's declared Int, Float, String, Bool or Symbol return, as it already did for a
    method defined above. Object-heavy code runs about 5% fewer instructions, polymorphic shape code
    6% and an n-body simulation 3%.

Fixed

  • A program with more than about 245 classes could fail to compile with an error like
    expected Target, got Int on a method returning an instance variable of class type: the
    field's class was stored as an 8-bit type id.

Diamond 0.26.0

Choose a tag to compare

@github-actions github-actions released this 05 Nov 19:31
e42eba0

Added

  • A documentation site. tools/docsite/build.d6 (written in d6) turns docs/ into the site
    served at getdiamond.codes/docs: a new beginner-friendly guide in docs/guide/ for people who
    already know another language, followed by the existing reference pages, with a sidebar,
    an on-this-page panel and d6 syntax highlighting. Guide examples have a Run button that runs
    them in the browser with the homepage's WebAssembly build, and every # => claim in the guide
    is checked by make test-docs. make test-docsite builds the site and checks every link.

  • Plain d6 build. With no source file, d6 build builds the project's entry file in
    the current directory (main.d6, as facet new generates, else app.d6 or a legacy app.di) and, unless -o says otherwise, names the binary after the
    directory: cd greeter && d6 build produces ./greeter. d6 build tool.d6 still produces
    tool.

Changed

  • Fewer regex allocations. Matches use call-local temporary capture storage
    for small patterns, and boolean matches skip unused result storage.

  • Faster regular expressions. The native regex library is now optimized in
    every build, reducing the work and instruction-cache pressure of matching.

  • Faster interpreter dispatch. Hot instructions reuse their operand bounds
    check to prepare the next opcode, removing a separate end-of-code check.

  • Int division rounds down. / on two Ints now floors (rounds toward negative infinity),
    like Ruby and Python and like % already did, instead of truncating toward zero: -7 / 2 is
    -4 (was -3) and 7 / -2 is -4. (a / b) * b + a % b == a now holds for every sign mix,
    arbitrary-precision Ints included. Results with both operands the same sign, or an exact
    quotient, are unchanged.

  • A leaner interpreter loop. With GCC on x86-64, the release build compiles the interpreter's
    dispatch function on its own with flags that free one more CPU register for it, and arithmetic
    on an Int constant no longer stalls on reading back the previous instruction's result. Across the
    benchmark suite, programs run about 3.5% fewer cycles on average. Calls and loops over
    collections gain the most: recursive fib and raising and rescuing exceptions each run about 8%
    fewer cycles, and block iteration (each, times, reduce) about 9% fewer. A bare integer
    counting loop runs about 7% more. With the JIT off, a call to a plain function also skips a
    forwarding layer and two no-op frees: recursive fib runs another 8% fewer instructions, and
    closures 5% fewer cycles. Method calls and new skip the same layer: allocating objects and
    calling their methods runs 5% fewer cycles. The interpreter now checks for pending signals and
    for a supervisor or thread stop on backward jumps and calls instead of before every
    instruction; resource limits and opcode counting still see every instruction. That cuts the
    counting loop's cost by 14% (more than winning back the 7% above), a sieve by 19%, Float-heavy
    mandelbrot by 14% and fib by 12%; across the suite, about 4% fewer cycles on average.

  • Profile-guided release builds. With GCC, make release (and so the .deb and .rpm packages)
    now builds an instrumented binary, runs a few small training programs from tools/pgo/, and
    rebuilds using the profile, which adds well under a minute. Hot code is packed together and cold
    code moved out of the way, so the processor's instruction cache stops thrashing: object-heavy code
    runs about 14% faster, text processing 15%, tree building 13% and JSON 10%, and the benchmark suite
    about 6.6% faster on average; tight integer loops are 1-2% slower. make release PGO=0 builds
    without it. clang builds are unchanged.

  • Faster setters and Float arithmetic. A setter whose whole body stores its argument
    (attr_accessor's x=, or a one-field initialize) is answered by writing the field,
    without a call, the way getters already were; and Float-with-Float + - * / no longer
    leaves the interpreter's arithmetic instruction. An n-body simulation runs about 19% fewer
    instructions and a Mandelbrot loop about 37% fewer. Both shortcuts also settle class-typed
    fields (-> Node | Nil), so a binary-tree walk runs about 21% fewer. A warm call site
    answers such a getter or setter before the method-call machinery is entered at all,
    another 19% off the n-body simulation and 5-6% off object-heavy loops.

  • Faster Array indexing and Array.new. Reading or writing an Array element at an
    in-bounds Int index is answered in the interpreter's own instruction, and Array * Int
    (which Array.new(n, fill) compiles to) fills the result natively instead of pushing one
    element at a time: a sieve of Eratosthenes to a million runs about 63% fewer instructions.

  • Native each, each_with_index, reduce and times. Like map and select before
    them, Array's each, each_with_index and reduce and Int's times call their block
    from a loop in C instead of the prelude's d6 loop, about 45% fewer instructions per block
    call. break, next, return, raises and errors behave as before.

  • Native Hash#fetch. A found key, or a missing key with a fallback, is answered with one
    native lookup instead of the prelude's include_key? plus []: counting into a Hash with
    counts[k] = counts.fetch(k, 0) + 1 runs about 49% fewer instructions. A missing key with
    no fallback raises the same IndexError as before.

  • sub/gsub with a String pattern skip the Regexp. When both the pattern and the
    replacement are Strings and the replacement has no \ escapes, the replace is a plain
    substring search instead of compiling a Regexp on every call: a small-string workload
    mixing gsub with upcase, include? and slice runs about 41% fewer instructions.
    Interpolating an Int ("#{n}", to_s, join) no longer goes through printf: another
    10% off that workload and 16% off a string-building one. Raising an exception builds its
    backtrace entries the same way, about 15% off a loop that raises in one call of five.

Diamond 0.25.0

Choose a tag to compare

@github-actions github-actions released this 03 Nov 15:06
afc8e94

Changed

  • sort is native for Int and String arrays. A blockless sort of an array whose elements are
    all Ints or all Strings now runs in C: building and sorting 50,000 Ints ten times went from 1.26 s
    to 81 ms. Anything else (a block, mixed kinds, Floats, big integers, nested arrays) sorts as before.

  • The JIT compiles % and Float arithmetic. A function using % or a Float literal used to
    stay in the interpreter; both now compile, and Float-with-Float + - * / and comparisons run
    as native SSE2 code. A tight Int loop with % runs about 6x faster under DIAMOND_JIT=1, and a
    Mandelbrot loop about 4x.

  • Faster startup and recursive calls. Loading a cached program no longer formats its whole
    bytecode while checking it, which halves the work a short script does at startup. A function
    calling itself reuses its call context instead of rebuilding it, and under DIAMOND_JIT=1 a
    compiled function's call to itself skips checks that can't change; recursive fib runs about
    19% faster with the JIT and 4% faster without.

  • Method calls and untyped arithmetic are cheaper. A method whose body only returns an
    instance variable (def x() -> Int = @x) is now answered by reading the field, without setting
    up a call; return-type checks, arity, privacy and overrides behave as before. Int -, * and /
    on values the compiler can't prove are Ints no longer take the big-integer path. An
    object-heavy benchmark went from 457 ms to 339 ms and a block-heavy one from 378 ms to 349 ms.

  • Array#map and Array#select run natively. Their loop now runs in C and calls the block
    directly, instead of through a d6 loop that paid for its own length, index and push calls on
    every element. break, next, return and exceptions inside the block behave as before, and
    so do the error messages for a wrong argument. == and != on two Ints that the compiler
    couldn't prove were Ints no longer take the general comparison path. The block-heavy benchmark
    went from 349 ms to 199 ms.

  • Cheaper block calls, push, length, sum and Range#to_a. map and select set up
    their block's call once rather than once per element, and a block reads or assigns a variable
    it captured without first re-checking that the variable is shared. Calls to push and
    length skip about ten other method-name tests that used to come first. A blockless sum of
    plain Ints adds them in C, and to_a on a bounded Int range fills the array in C. Anything
    else (a block, Floats, big integers, an endless range, overflow) works as before. The
    block-heavy benchmark went from 208 ms to 123 ms.

  • Blocks load only the outer variables they use. A block used to fetch every local variable
    of the method around it on every call, whether it used them or not. It now fetches only the
    ones its body names, unless it creates another block or def or calls debugger(). Debug
    sessions still see every variable. The block-heavy benchmark went from 127 ms to 109 ms.

  • Cheaper method calls. A warm call site that keeps seeing one class now goes straight to
    the method it cached, without first testing the name against tap, to_s, dup and the other
    methods every object answers. A class that defines its own tap or to_s still gets its own.
    A typed parameter is checked without a function call when its value is exactly the declared
    type or class. A plain method call (no type arguments, at most seven arguments) also goes
    through fewer nested calls before reaching the method body. A loop of a million one-argument
    method calls runs about 11% fewer instructions, and the object-heavy benchmark about 9%.

  • Faster interpreter loop. Each bytecode handler now jumps straight to the next one through
    a table instead of returning to one central switch. Arithmetic and comparisons with an Int
    literal operand, such as i < 3000000, n - 1 or i += 1, compile to a single instruction
    instead of a constant load plus the operation, and each Int operator has its own handler
    rather than sharing one that tested the opcode again. A typed parameter declared Int or
    Float is checked without a call. The counting loop benchmark went from 1330 ms to about
    720 ms, recursive fib from about 500 ms to 340 ms, and the object-heavy one from 376 ms to
    about 300 ms. JIT-compiled code is unchanged.

Fixed

  • Return annotations naming a later class. When a method's return annotation named a class
    declared further down the file, a later caller checking that method's result against the class
    failed to compile with "expected R, got Nil". It now compiles, and a real mismatch is still
    reported with the right class names.