Repository navigation
Releases
Releases · Rawframe-Project/kest
v0.7.0
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, withK0411("load.atat 102 reads layout 0 at 24 bytes into what an address is at, which has no such field there"): withstruct S { tag: i32; xs: [i32; 4] },struct Box { ss: [S; 2] },boxesholding oneBoxofS(1, [1, 2, 3, 4])andS(2, [5, 6, 7, 8])andjworked out while running as 2, a program printingboxes[0].ss[1].xs[j], writing 9 there and printingboxes[0].ss[1].xs[2]now prints7and9. 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"): withstruct 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)], andiandjworked out while running as 1 and 2,grid[i][j] = 9,xs[i] += 5andps[i].y = 9were each refused, and printing{grid[1][2]} {xs[1]} {ps[1].y}after them now prints9 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 withK0604naming 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 emitwrites one new instruction,in.run, andload.slotsandstore.slotscarry how many slots of one of the run they read or write. A program that compiled before compiles to the same instructions, withload.slotsandstore.slotseach 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, withK0505("this assigns to something that is not a place, which the checker allowed"): withstruct S { tag: i32; xs: [i32; 4] },ss: [S; 2]holdingS(1, [1, 2, 3, 4])andS(2, [5, 6, 7, 8]), andiandjworked out while running as 1 and 2,ss[i].xs[j] = 9followed by printingss[1].xs[2]now prints9. An index past its run is refused withK0604and 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
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"): withstruct Row { tag: i32; count: [i32; 3] }, a function returningrows[k].count[at]did not compile, and now, withrowsholdingRow(7, [10, 20, 30]), it answers 30 fork0 andat2. The same held for a run ofbooland 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, withK0505("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. Withstruct Row { count: [i32; 3] },rowsholdingRow([10, 20, 30])andatworked out while running as 1,rows[0].count[at] = 99followed byrows[0].count[at - 1] += 5now leaves the run15 99 30. An index past the run is refused withK0604("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 emitwrites these as two new instructions,load.elem.runandstore.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 readingrows[k].cells[i]800,000 times from a[Row]of 1000struct Row { cells: [bool; 4] }it filled itself runs in 131,499,448 instructions where it took 162,700,128, underperf stat -e instructions:uwith the program and commands the pull request gives. (#247)
v0.5.0
0.5.0 (2026-11-04)
Changed
- Breaking:
kest lspandkest dapread a\uescape 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,\ud800asED 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 publishedK0107("the byte0xedstarts 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 underkest check. A buffer with a lone half now checks, and underkest dapa lone half in a path or an expression reads as U+FFFD. A whole pair, and every other\uescape, 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, andS(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. Withflags S: u8 { A B },u8(S(u8(255)))andu8(~S())answer 3 where they answered 255,S(u8(12))isS()where it held 12, and~S() == S.A | S.Bis 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. Adeterministicprogram observes this, so the profile moves fromkest-det 3tokest-det 4. (#238) - Breaking: A program that reads a
boolthat 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 withK0668where it reads it, as a tag with no case is refused withK0651. It went on with a value that was neither: withflags Mood: u8 { Hungry Tired }and a lent[Sense]ofstruct Sense { up: bool; mood: Mood }, a host writing 2 intouphadif senses[0].uptake its branch whilesenses[0].up == truewas false, and writing 255 intomoodhadu8(senses[0].mood)answer 255, aMoodthat wrote itself asMood.Hungry | Mood.Tiredand was not equal to it. Each read now stops with "boolhere holds 2, which is neither true nor false" or "Moodhere 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 hostilehands 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 doorEngine.felt(m: Mood)withflags Mood: u8 { Hungry Tired }, seeds 1 to 8 wrote 64 calls of it, and every one wasEngine.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
0.4.0 (2026-11-04)
Changed
- Breaking:
kest fmtkeeps 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) * 2as|| c + 1 * 2; where that form was not stable,fmtrefused the file as a fault in the formatter. None of these blocks compile (K0367), so no program that runs changes, butfmtnow 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_beginandkest_held_callrefuse anargsof NULL with a count above nought, answering false and writingK0636toerrorswith the door as the host named it ("stepwas handed 2 values and no address to find them at"). They copiedcountvalues from NULL and crashed the host. Values handed with an address are copied exactly as before. (#225)
v0.3.0
0.3.0 (2026-11-03)
Changed
- Breaking:
kest_gave_textholds the text it writes out to the text it is in, askest_calldoes 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 withK0632("slot N of the frame holds text with L in its length slot, which the text there cannot be, so it is not whatfgave back"), and no room to list the program's own text answers -1 withK0605. What a call left in the frame is written out exactly as before. (#215) - Breaking:
kest_textrefuses a length past 2,147,483,647 bytes, the mostlencounts, withK0630and the number, before a byte is read. A host that handed 2,147,483,648 bytes over had them taken, and the program'slenanswered 2147483648, which noi32holds; at 4,294,967,295 bytes the room asked for the copy wrapped to nought and the refusal saidK0605"out of memory". Text of 2,147,483,647 bytes or fewer is taken exactly as before. (#216)
Fixed
kest_takes_textrefuses a NULL among its words withK0635naming 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, instrlenfor text. (#219)kest_build_fromrefuses a file with no path, the first or any after it, withK0701("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_entryandkest_entry_ofanswer -1.kest_borrowwith noelementanswers a lend with nothing in it andK0610.kest_build_layoutanswers nought.kest_build,kest_build_withinandkest_held_newwith nopathanswer NULL withK0701("a build handed no path has no program to read") written toerrors. (#222)
v0.2.0
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 withK0636going in andK0652coming 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 withK0630. 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) andkest_runtime_ran_outcounts it. It was refused withK0636, orK0652for 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 answerednone, 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 andIo.readalready handed it over. A path holding a zero still names no file. (#210)
v0.1.7
0.1.7 (2026-11-03)
Fixed
- A handle a host hands to
kest_array_length,kest_lend_endsorkest_call, or answers with from a door, has to be exactly whatkest_callorkest_borrowgave: the front of an array or store header. Anything else in the machine's heap was read as a header. Text made withkest_textand beginning with the four bytes of an array's tag ("RRAKAAAABBBBCC\001CDDDDDDDDEEEEEEEE") was an array 1094795585 long with room for 1111638594 tokest_array_length, a lendkest_lend_endsanswered true for, and, handed tokest_callfor 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 withK0636orK0652("an address of this machine's where no handle begins"),kest_array_lengthanswers 0, andkest_lend_endsanswers false withK0637. A handle the machine gave is answered exactly as before. (#202)
v0.1.6
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
Ca constant of type[[[bool; 25]; 25]; 40](25000 slots), afn copiedthat copies its argument into a local and returns it, andmainholdingCandcopied(C), the program was refused underkestandkest-debug, bykest emitand bykest build --releasewithK0408("store.nat 15 names slot 50000 of the 34464 there are"), and ran only underKEST_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, 65535and alooponto itself, with room for 65535 slots, was still being walked bykest_module_proveafter a minute, where the same chunk one slot shallower is refused at once withK0411. It is now refused withK0411too. A body whose stack stays under 65535 slots is walked exactly as before. (#197)
v0.1.5
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 hadnas 0 where it was 42, in both engines and compiled to C, and nothing was said; the same held behind a[Nothing; 3]or aNothing?, and an enum behind one was read back as a tag with no case (K0651), though the program wrote it. A host askingkest_case_ofabout 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 withK0636. A value with no such struct in it is moved exactly as before. (#194)
v0.1.4
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
K0502and 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 returning7answered 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 whatTis, is held to the 65535 bytes and slots a value may be, and one too big is refused withK0327at the call. Its size wrapped to nought: withDof 32768 bytes, pushingtwo(D, D)offn two<T>(a: T, b: T) -> [T; 2]twice into a[[T; 2]]wrote past the end of the array's element, and a releasekestaborted withdouble 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
K0502and the number, at the name or value that takes it past. It was refused withK0505, as a fault in the compiler, or withK0408, with a number that is not what it holds:let both = [C, C], withCof 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 asK0502,K0505andK0639, as though the build had run out of memory. A function within every limit compiles exactly as before. (#192)