Skip to content

Releases

v0.7.0

Choose a tag to compare

@rawframe-release rawframe-release released this 05 Nov 15:54
22879ef

0.7.0 (2026-11-05)

Changed

  • Breaking: A program reads and writes one of a fixed run at an index worked out while running where the run is inside one of another run at an index written down, inside an element of an array (boxes[0].ss[1].xs[j]). The verifier refused it under every build and switch as a fault in the compiler, with K0411 ("load.at at 102 reads layout 0 at 24 bytes into what an address is at, which has no such field there"): with struct S { tag: i32; xs: [i32; 4] }, struct Box { ss: [S; 2] }, boxes holding one Box of S(1, [1, 2, 3, 4]) and S(2, [5, 6, 7, 8]) and j worked out while running as 2, a program printing boxes[0].ss[1].xs[j], writing 9 there and printing boxes[0].ss[1].xs[2] now prints 7 and 9. A run of runs with its first index written down (bs[0].grid[1][j]) is the same case. Nothing the verifier accepted before changes, and no instruction the compiler writes changes. (#254)
  • Breaking: A program writes one of a run of runs at indexes worked out while running, in a local and inside an element of an array, and writes one of a run in a local at such an index with an operator or into a field of one. Each was refused under every build and switch as a fault in the compiler, with K0505 ("this assigns to something that is not a place, which the checker allowed"): with struct P { x: i32; y: i32 }, grid: [[i32; 3]; 2] holding [[1, 2, 3], [4, 5, 6]], xs: [i32; 3] holding [1, 2, 3], ps: [P; 2] holding [P(1, 2), P(3, 4)], and i and j worked out while running as 1 and 2, grid[i][j] = 9, xs[i] += 5 and ps[i].y = 9 were each refused, and printing {grid[1][2]} {xs[1]} {ps[1].y} after them now prints 9 7 9. Where more than one index is worked out while running, each is held to its own count, so an index past its run is refused with K0604 naming that index and that run's count ("index 3 is outside 3 of them"), in every engine and in a body compiled to C. kest emit writes one new instruction, in.run, and load.slots and store.slots carry how many slots of one of the run they read or write. A program that compiled before compiles to the same instructions, with load.slots and store.slots each two bytes wider. (#256)
  • Breaking: A program writes one of a run that is a field of one of another run, each at an index worked out while running (ss[i].xs[j] = 9), in a local and inside an element of an array, with = or an operator. Each was refused under every build and switch as a fault in the compiler, with K0505 ("this assigns to something that is not a place, which the checker allowed"): with struct S { tag: i32; xs: [i32; 4] }, ss: [S; 2] holding S(1, [1, 2, 3, 4]) and S(2, [5, 6, 7, 8]), and i and j worked out while running as 1 and 2, ss[i].xs[j] = 9 followed by printing ss[1].xs[2] now prints 9. An index past its run is refused with K0604 and that run's count. Inside an element, a write whose right-hand side writes the same element, or grows the array, keeps what the right-hand side wrote. A program that compiled before compiles to the same instructions. (#257)

v0.6.0

Choose a tag to compare

@rawframe-release rawframe-release released this 05 Nov 09:33
bbb1ca5

0.6.0 (2026-11-04)

Changed

  • Breaking: A program reads one of a fixed run inside an element of an array at an index worked out while running, wherever the run sits in the element. When the run was not the element's first field, the program was refused under every build and switch as a fault in the compiler, with K0505 ("this leaves the stack 1 deep and a statement leaves it as it found it"): with struct Row { tag: i32; count: [i32; 3] }, a function returning rows[k].count[at] did not compile, and now, with rows holding Row(7, [10, 20, 30]), it answers 30 for k 0 and at 2. The same held for a run of bool and for a run inside a struct inside the element. A program that compiled before compiles to the same instructions. (#246)
  • Breaking: A program writes one of a fixed run inside an element of an array at an index worked out while running, with =, +=, -=, *= or /=. Every such write was refused as a fault in the compiler, with K0505 ("this assigns to something that is not a place, which the checker allowed"), whichever field the run was; only a run held in a local could be written at such an index. With struct Row { count: [i32; 3] }, rows holding Row([10, 20, 30]) and at worked out while running as 1, rows[0].count[at] = 99 followed by rows[0].count[at - 1] += 5 now leaves the run 15 99 30. An index past the run is refused with K0604 ("index 3 is outside 3 of them"), the sentence a run in a local gets, in every engine and in a body compiled to C. A write whose right-hand side grows the array is written into the array as it is after it grew. kest emit writes these as two new instructions, load.elem.run and store.elem.run. A program that compiled before compiles to the same instructions. A run of runs written at two indexes worked out while running (grid[i][j] = 9) is still refused, in a local as well as in an element (#249). (#250)
  • A bool, or a set of flags, read through an address worked out before the read (load.at) is looked at only when the address is in memory a host lent, as a read that knows its array already was. Every such read was looked at, of the machine's own memory too. Nothing a program or a host sees changes: every read refused before is refused with the same line. A program reading rows[k].cells[i] 800,000 times from a [Row] of 1000 struct Row { cells: [bool; 4] } it filled itself runs in 131,499,448 instructions where it took 162,700,128, under perf stat -e instructions:u with the program and commands the pull request gives. (#247)

v0.5.0

Choose a tag to compare

@rawframe-release rawframe-release released this 04 Nov 08:00
8ff9e62

0.5.0 (2026-11-04)

Changed

  • Breaking: kest lsp and kest dap read a \u escape that is half of a UTF-16 pair with no other half as U+FFFD (EF BF BD). They wrote the three bytes UTF-8 would use for that number, \ud800 as ED A0 80, which spell no character: an editor holds its buffer as UTF-16 and can send a lone half this way, and the language server then published K0107 ("the byte 0xed starts no character") once for each of the three bytes, while the file the same editor saves, with U+FFFD in the half's place, checks with nothing to say under kest check. A buffer with a lone half now checks, and under kest dap a lone half in a path or an expression reads as U+FFFD. A whole pair, and every other \u escape, is read as before. (#233)
  • Breaking: A set of flags is held to the bits it has names for. ~ on a set flips only those bits, where it flipped every bit of its width, and S(bits) keeps only those bits, where it kept every bit it was given; either made a set that wrote itself as a set it did not equal. With flags S: u8 { A B }, u8(S(u8(255))) and u8(~S()) answer 3 where they answered 255, S(u8(12)) is S() where it held 12, and ~S() == S.A | S.B is true where it was false, in every engine and compiled to C. ~ on a whole number, and a set with a name for every bit of its width, answer as before. A deterministic program observes this, so the profile moves from kest-det 3 to kest-det 4. (#238)
  • Breaking: A program that reads a bool that is not nought or one, or a set of flags with a bit none of its names cover, out of memory a host lent is refused with K0668 where it reads it, as a tag with no case is refused with K0651. It went on with a value that was neither: with flags Mood: u8 { Hungry Tired } and a lent [Sense] of struct Sense { up: bool; mood: Mood }, a host writing 2 into up had if senses[0].up take its branch while senses[0].up == true was false, and writing 255 into mood had u8(senses[0].mood) answer 255, a Mood that wrote itself as Mood.Hungry | Mood.Tired and was not equal to it. Each read now stops with "bool here holds 2, which is neither true nor false" or "Mood here holds 255, which has bits it has no names for". A body compiled to C refuses with the same code and the same sentence. A lend whose bytes are values of their types, and a program reading its own memory, answer as before. (#239)
  • Breaking: kest hostile hands a door that takes a set of flags one of four, chosen by the seed: the empty set, every flag the set names, one flag, or some of them. It wrote the empty set every time, so a door that went wrong on a flag was never handed one: for a door Engine.felt(m: Mood) with flags Mood: u8 { Hungry Tired }, seeds 1 to 8 wrote 64 calls of it, and every one was Engine.felt(Mood()). The same file and seed now write a different program wherever a door takes a set; a file with no such door is written as before, byte for byte. (#243)

v0.4.0

Choose a tag to compare

@rawframe-release rawframe-release released this 04 Nov 03:01
740d642

0.4.0 (2026-11-04)

Changed

  • Breaking: kest fmt keeps the brackets around a block that gives a value (|x| value) wherever it is an operand: before a call, a field or an index, after a unary operator, and inside a binary operator. It dropped them and wrote a different expression while answering 0: (|| 41 + 1)() came out as || 41 + 1(), a block giving a call, and (|| c + 1) * 2 as || c + 1 * 2; where that form was not stable, fmt refused the file as a fault in the formatter. None of these blocks compile (K0367), so no program that runs changes, but fmt now writes different bytes for a file holding one. A block in braces, and a block handed to a function as an argument, are written as before. (#227)

Fixed

  • kest_held_begin and kest_held_call refuse an args of NULL with a count above nought, answering false and writing K0636 to errors with the door as the host named it ("step was handed 2 values and no address to find them at"). They copied count values from NULL and crashed the host. Values handed with an address are copied exactly as before. (#225)

v0.3.0

Choose a tag to compare

@rawframe-release rawframe-release released this 03 Nov 22:45
f4a5c0b

0.3.0 (2026-11-03)

Changed

  • Breaking: kest_gave_text holds the text it writes out to the text it is in, as kest_call does for text handed in. It copied as many bytes as the frame's length slot said, from wherever its address slot pointed, asking only that the address was not NULL: a host that wrote 64 into the length of two bytes of text had 64 copied, and the checked build stopped on a read of poisoned memory; with -1 it read 4095 bytes. A frame whose text has an address that is not the machine's, or a length past the text there, now answers -1 with K0632 ("slot N of the frame holds text with L in its length slot, which the text there cannot be, so it is not what f gave back"), and no room to list the program's own text answers -1 with K0605. What a call left in the frame is written out exactly as before. (#215)
  • Breaking: kest_text refuses a length past 2,147,483,647 bytes, the most len counts, with K0630 and the number, before a byte is read. A host that handed 2,147,483,648 bytes over had them taken, and the program's len answered 2147483648, which no i32 holds; at 4,294,967,295 bytes the room asked for the copy wrapped to nought and the refusal said K0605 "out of memory". Text of 2,147,483,647 bytes or fewer is taken exactly as before. (#216)

Fixed

  • kest_takes_text refuses a NULL among its words with K0635 naming the word, before any word is read and with nothing written into the frame. A list with a hole in it crashed the host: in the reader for a number, in strlen for text. (#219)
  • kest_build_from refuses a file with no path, the first or any after it, with K0701 ("file N of the M a build was handed has no path", counted from nought) and answers NULL. A missing path after the first crashed the host at the first import, and one on the first file was said as a build handed no files. (#221)
  • Seven doors answer a NULL name or path instead of crashing the host. kest_entry and kest_entry_of answer -1. kest_borrow with no element answers a lend with nothing in it and K0610. kest_build_layout answers nought. kest_build, kest_build_within and kest_held_new with no path answer NULL with K0701 ("a build handed no path has no program to read") written to errors. (#222)

v0.2.0

Choose a tag to compare

@rawframe-release rawframe-release released this 03 Nov 20:22
985a83c

0.2.0 (2026-11-03)

Changed

  • Breaking: Text a host hands to kest_call, or a door answers with, is held to the text its address is in: what the heap place was asked for, or the program's own text it was cut from. Its length slot was never asked about, and the program read as many bytes as it said. fn echo(t: text) -> text { return "{t}!" }, called with two bytes of text said to be 200, copied 200 bytes, and the checked build stopped on a read of poisoned heap memory; four bytes said to be 12 read what a value before them left in the place, and the program's own text said to be longer read on into whatever the compiler wrote after it. A length past the text, or below nought, is now refused with K0636 going in and K0652 coming back ("takes text in slot N with L in its length slot, which the text there cannot be"); a negative length was taken before and the program stopped with K0630. Text cut short is read as before, and text whose length fits is taken exactly as before. (#205)
  • Breaking: When the machine has no room to list the program's own text, the first time some crosses back, the call is refused with K0605 (no room) and kest_runtime_ran_out counts it. It was refused with K0636, or K0652 for a door's answer, as though the host's length ran past the text. (#212)
  • Breaking: os.read, under the command line and in a release binary, answers a file's text when the file holds a zero byte, where it answered none, which is what a missing file answers: a program could not read back what it had just written. A zero is a character of text and Io.read already handed it over. A path holding a zero still names no file. (#210)

v0.1.7

Choose a tag to compare

@rawframe-release rawframe-release released this 03 Nov 16:09
8676c2a

0.1.7 (2026-11-03)

Fixed

  • A handle a host hands to kest_array_length, kest_lend_ends or kest_call, or answers with from a door, has to be exactly what kest_call or kest_borrow gave: the front of an array or store header. Anything else in the machine's heap was read as a header. Text made with kest_text and beginning with the four bytes of an array's tag ("RRAKAAAABBBBCC\001CDDDDDDDDEEEEEEEE") was an array 1094795585 long with room for 1111638594 to kest_array_length, a lend kest_lend_ends answered true for, and, handed to kest_call for a [u8] or answered from a door, stopped the host with a segmentation fault; a handle moved by one byte was read from the middle of a header. A call in or a door's answer with one is now refused with K0636 or K0652 ("an address of this machine's where no handle begins"), kest_array_length answers 0, and kest_lend_ends answers false with K0637. A handle the machine gave is answered exactly as before. (#202)

v0.1.6

Choose a tag to compare

@rawframe-release rawframe-release released this 03 Nov 13:37
614dd80

0.1.6 (2026-11-03)

Fixed

  • A function is not carried into its caller when the two frames, or the two stacks, would come to more than 65535 slots together; it is called instead, which is the same answer. The caller's frame was given the sum less 65536, and the verifier refused the body as a fault in the compiler: with C a constant of type [[[bool; 25]; 25]; 40] (25000 slots), a fn copied that copies its argument into a local and returns it, and main holding C and copied(C), the program was refused under kest and kest-debug, by kest emit and by kest build --release with K0408 ("store.n at 15 names slot 50000 of the 34464 there are"), and ran only under KEST_PLAIN=1. A program that never comes near 65535 slots compiles exactly as before. (#200)
  • The verifier ends its walk of a body whose operand stack reaches all 65535 slots it may be given. A place reached at that depth was taken for a place not reached yet and walked again: a chunk of load.n 0, 65535 and a loop onto itself, with room for 65535 slots, was still being walked by kest_module_prove after a minute, where the same chunk one slot shallower is refused at once with K0411. It is now refused with K0411 too. A body whose stack stays under 65535 slots is walked exactly as before. (#197)

v0.1.5

Choose a tag to compare

@rawframe-release rawframe-release released this 03 Nov 11:27
87783eb

0.1.5 (2026-11-03)

Fixed

  • A shape holding a struct with no fields is moved, read and handed over with that struct as the one slot it is laid out as. It was walked as no slots, so everything after it was found one slot early. A struct After { e: Nothing; n: i64 } pushed into an array and read back had n as 0 where it was 42, in both engines and compiled to C, and nothing was said; the same held behind a [Nothing; 3] or a Nothing?, and an enum behind one was read back as a tag with no case (K0651), though the program wrote it. A host asking kest_case_of about the tag behind one was told it had no cases, and a host handing over a value with text behind one, filled correctly, was refused with K0636. A value with no such struct in it is moved exactly as before. (#194)

v0.1.4

Choose a tag to compare

@rawframe-release rawframe-release released this 03 Nov 02:22
ec822bc

0.1.4 (2026-11-02)

Fixed

  • A function whose constant values come to more than 65536, counting every element of a table it reads, is refused with K0502 and the number. Past that, an instruction naming one of them wrapped to the start of the function's values and read another, and nothing was said: a function reading two different tables of type [[[bool; 32]; 32]; 32] and then returning 7 answered 0, and a loop the compiler unrolls, two turns over one such table, counted 3 where the answer is 2. A function is not carried into one whose values it would take past the limit; it is called instead. A function within the limit compiles exactly as before. (#190)
  • A [T; N] copied through a generic function's signature, when a call settles what T is, is held to the 65535 bytes and slots a value may be, and one too big is refused with K0327 at the call. Its size wrapped to nought: with D of 32768 bytes, pushing two(D, D) of fn two<T>(a: T, b: T) -> [T; 2] twice into a [[T; 2]] wrote past the end of the array's element, and a release kest aborted with double free or corruption. A literal laid out as a [T; N] too big is refused at the literal. A program whose [T; N]s fit is laid out exactly as before. (#186)
  • A function whose names hold more than 65535 slots at once, or which works on more than 65535 slots of values at once, is refused with K0502 and the number, at the name or value that takes it past. It was refused with K0505, as a fault in the compiler, or with K0408, with a number that is not what it holds: let both = [C, C], with C of 32768 slots, was said to leave -32768 slots of stack. A function within the limit compiles exactly as before. (#187)
  • A function refused for one of the compiler's limits (K0502) is told that and nothing else. What came after was said as the compiler's own fault (K0505), and the body was handed to the backend, which refused it too and stopped the build: a piece of text with 65537 holes was said as K0502, K0505 and K0639, as though the build had run out of memory. A function within every limit compiles exactly as before. (#192)