• v0.2.0 383830a3ff

    v0.2.0
    All checks were successful
    test / test (push) Successful in 46m39s
    Stable

    sicher released this 2026-10-08 20:51:57 +02:00 | 283 commits to main since this release

    • AOT: values travel in registers. A value is passed to and returned
      from the runtime's out-of-line paths in registers instead of through
      memory; a tight arithmetic loop no longer keeps its variables in memory
      for a slow path it never takes. sum 4.3 -> 1.3 s (now faster than the
      interpreter); over all 55 benchmarks 4% faster (geomean).
    • AOT: equal? is the interpreter's (shared code now): iterative, with
      an interleaved memo, so cycles and shared structures stay cheap; the
      equal benchmark 0.52 -> 0.31 s. (equal? 0.0 -0.0) is now #f, as R7RS
      says (equal? on numbers is eqv?); it answered #t.
    • AOT: bignums 2.3-3.5x faster. Division and integer square root are the
      VM's algorithms (shared code now), and a bignum is one allocation instead
      of two; pi 0.56 -> 0.18 s, chudnovsky 0.30 -> 0.14 s, level with the
      interpreter.
    • AOT: read from a file stops asking the file once it is exhausted.
      Each call after the end made a system call that returned nothing; sum1
      (100,000 numbers read 25 times) 1.46 -> 0.20 s.
    • AOT: a program's top level runs in order. Every top-level define
      ran before any top-level expression, so a define after a set! or a
      string-set! saw the old value; compiled programs now run their forms as
      written, as the interpreter does.
    • AOT: strings are one byte a character while every character is below
      U+0100, four after -- the VM's representation, and its code. string
      0.45 -> 0.11 s (faster than the VM's 0.15 s), tail -58%, bv2string
      -25%, and text takes the same memory under both engines. Invalid UTF-8
      read into a string decodes as the VM's does: a byte that starts no valid
      sequence is the Latin-1 character it would be (it used to make the whole
      string bytes-as-characters).
    • VM: string-fill! and string-copy! accept any character into a
      string that held only Latin-1 ones; they refused with "a wide character
      into a narrow string" (string-set! already widened).
    • AOT: large objects reuse their memory. Strings, vectors and
      bytevectors over 2 KB were a fresh OS mapping each, zero-filled by the
      kernel and unmapped when collected; freed blocks are now kept by size class
      and reused, bounded by the heap's own size. string 2.4x faster, array1
      -19%, peak memory unchanged.
    • AOT: tail calls between compiled functions are jumps, and every
      compiled function and closure takes its arguments in registers. A tail
      call no longer grows the machine stack until it bounces: continuation-
      passing code runs at the VM's speed (cpstak 16.8 -> 7.2 s), and closure-
      heavy code gains broadly: conform -22%, lattice -16%, scheme -24%,
      graphs -21% over the three steps (closure entries in registers, known
      functions' arguments in registers, jumps); the last two steps alone are
      about -3% geomean over all 55 runnable benchmarks.
    • Embedding: lamp.native, host functions as plain Zig functions with
      typed parameters, in a library: native.Library(.{ .name = "(my lib)", .Context = Engine, .natives = .{ .{ "name", zigFn }, ... } }) and
      Session.addLibrary(lib, &ctx). lamp reads each argument into its
      parameter's type (integers including bignums that fit, floats, booleans,
      strings, symbols, bytevectors, optional and rest arguments) and boxes the
      result (lists from slices); refusals name the native and the argument.
      A library's names are hidden until a unit imports it, and a session not
      given it cannot (docs/EMBEDDING.md). Every native is in a library: the
      old Session.defineNative, defineType and embed.* native helpers are
      gone.
    • Native value types in lamp.native: native.Type(Vec3, .{ .name, .format, .equal, .eqv, .add/.sub/.mul/.div }) in a library's .types;
      natives take and return Vec3; a native type is a value, so *Vec3 is a
      compile error. Operator overloads are typed functions, the first whose
      parameters the operands fit (vec3 * number, number * vec3).
    • lamp.native in AOT binaries: the same library declaration compiles
      ahead of time. addSchemeExecutable's .native_types (a hand-written
      .zon and pack_T/unpack_T helpers) is replaced by .native_library, a
      module whose root declares pub const libraries; the build generates the
      descriptor and the bridge from it. Imports of a host library are enforced
      as in the VM; + - * / on native values the compiler could not prove
      dispatch at run time; an unproven argument of the wrong type raises the
      VM's error instead of undefined behaviour. A native type's .format,
      .equal and .eqv work in AOT binaries too (they printed
      #<native-type-0>, and equal? was identity). AOT native values hold at
      most 24 bytes; a larger type is a compile error there.
    • lamp.native results: an array, or a struct or tuple, is a list of its
      elements or fields -- [2]usize is (3 14), a slice of struct { block, start, end } a list of (block start end).
    • A host native called with the wrong number of arguments now says so --
      "f: expected 1 argument, got 0" -- instead of "unbound variable: f".
    • Errors are never printed by the VM. An unhandled error or raise
      used to write error: <message> to standard output and fault with "a
      raise with no handler"; the message is now the fault's own text --
      Session.errorMessage returns it, and lamp prints it on stderr as
      before. Standard output stays the program's, which a host may be using.
    Downloads