Mica 7.7.4 holds a program to releasing every cell it makes exactly once. The checks that prove it were reviewed rule by rule, and every hole the review and the program generator found is closed: a program that released a cell twice, read one after its release or lost one used to compile, and now it is refused at the statement that does it, in words that name the repair. Correct programs those checks refused compile.

Beside them, a task that waits uses no processor time, --sanitize links a program under a C sanitizer so the C beneath it runs checked, and the language reference states the value every variable starts at. Narrow integers, wide records and arguments behind the argument registers get the right code on AArch64 and x86-64, every value crosses the C boundary where the platform’s C convention puts it, stream shapes that lost values are refused where they are written, and a name may be written in Devanagari, Bengali, Thai or any other script.

Every cell released exactly once

{
    Twice - makes a leaf on the heap, releases it in a nested routine and then again in the routine that made it.

    The nested routine 'Drop' releases the leaf on every path, so the 'dispose' after its call would release the same
    cell a second time, and the compiler refuses that second release where it is written.
}
program Twice;

type
    { A leaf holds one tag. }
    Leaf = record
        tag : int64;
    end;

{ The routine makes a leaf and lets a routine nested in it release the leaf. }
procedure Plant();
var
    leaf : pointer Leaf;

    { The nested routine releases the leaf of the routine around it. }
    procedure Drop();
    begin
        dispose leaf;
    end;

begin
    leaf := new Leaf;
    Drop();
    dispose leaf;
end;

begin
    Plant();
end.

7.7.4 refuses it at the second release:

heap cell is disposed again after it was already released on every path reaching here — the call to 'Drop' at line 28 runs a routine nested in this one that releases the cell allocated at line 27; remove this dispose, or the release in that routine

7.7.3 compiled it, and the program stopped as it ran, with free(): double free detected in tcache 2.

A routine nested in the owner of a cell acts for the owner, at any depth: the compiler follows the cell into the call and reads the release, the lend or the store the routine makes there as the owner’s own. A routine the owner calls, or a signal handler, that keeps the cell in a global is read the same way. The same care closes the other holes the review found: a cell released through a copy of its pointer, wherever the copy goes, a cell read out of a container a routine received, a cell a function hands back out of a record it took by value, an entry Get reads out of a map that owns it, a borrow parked in a field of a record, an owning parameter the routine never names, and an Assert condition that makes, hands over or releases a cell. Each such program compiled and then read freed memory, released a cell twice or leaked it.

Each refusal stands at every tier on both processors. The program generator, which writes valid Mica programs and judges each one at every tier on both processors, now writes programs of these shapes every night as well.

Every variable starts at its start value

{
    Starts - reads a count, a name, a month and a colour before the program assigns any of them.

    Every variable starts at its type's start value, so each read below is defined: a number starts at 0, a string
    empty, an enumeration at its first member, and a subrange that leaves 0 out at its bound nearest 0, so a month of
    '1..12' starts at 1.
}
program Starts;

imp
    WriteLn, Length, Ord : std;

const
    CountLine = "count %lld";
    NameLine = "name of %lld characters";
    MonthLine = "month %lld";
    ColourLine = "colour number %lld";

type
    { A month is numbered from 1 to 12. }
    Month = 1..12;

    { The palette holds three colours, the first of them red. }
    Colour = (Red, Green, Blue);

var
    count : int64;
    name : string;
    month : Month;
    colour : Colour;

begin
    WriteLn(CountLine, count);
    WriteLn(NameLine, Length(name));
    WriteLn(MonthLine, month as int64);
    WriteLn(ColourLine, Ord(colour));
end.

It prints, at every level on both processors:

count 0
name of 0 characters
month 1
colour number 0

Every variable starts at its type’s start value, so a program may read a variable before it assigns one, and the language reference now states each start value: a number at 0, a character at the one with code 0, a bool at False, a pointer at nil, a string, a set, a dynamic array, a map and a span empty, an enumeration at its first member, and a subrange that leaves 0 out at the bound nearest 0, so 1..12 starts at 1. A program’s variables take their start values before its body runs, and a routine’s at every call.

Three places keep the rule now where they broke it. A span no assignment bound is the empty view, where it read as one element at address zero and a walk over it stopped with a segmentation fault. The elements Resize adds start inside their type, where they read 0. A variable placed on a device register with absolute reads what the device holds, where the program wrote a start value over it.

Writing a start value out costs nothing: a debug build emits no store for count := 0 as a routine’s first statement, and a breakpoint on its line still stops there.

A waiting task uses no processor time

A task parks when it waits at the end of its concurrent block, for room in a full stream, for a value of an empty one, or in a select with nothing ready, as it parks for a sleep and a join. A carrier with nothing to run waits in the kernel until a value, a free slot, a producer’s end or a timeout wakes it.

A program whose tasks wait for each other, a consumer for a slow producer or a producer for room in a full stream, uses no processor time while they wait, on one carrier and on many. Such a program kept a processor busy for the whole wait. The editor’s runtime view names a waiting task parked.

C sanitizers under a Mica program

--sanitize address links a program under the GNU C compiler’s address sanitizer, which catches out-of-bounds and freed-memory accesses, and --sanitize undefined under its undefined-behavior sanitizer. The program’s own code is built as without the flag. What changes is what it links: the sanitizer’s runtime, and the Dragon runtime built under the same sanitizer, so the C beneath the program runs checked, the runtime’s own and that of any C library of yours compiled with the matching -fsanitize= option. The first report names the C file and line and ends the program.

mica --compile --link --sanitize address --source Starts.mica --build build
./build/Starts

Tasks, generators and streams run under the address sanitizer as cleanly as without it, because the sanitized runtime tells the sanitizer of every switch between stacks. A sanitized program names its sanitizer in its manifest, so mica --inspect build/Starts prints "sanitizer":"address".

The sanitized runtimes come in a new optional package, mica-dragon-rt-sanitizers, for both architectures. It depends on gcc, whose packages bring the sanitizer runtimes, and the default installation is unchanged. The sanitized builds reference lists the builds the compiler refuses, each with its one line.

Upgrading

Things a 7.7.3 build meets differently in 7.7.4:

  • A program that releases a cell twice, reads one after its release or loses one, in the shapes above, is refused at the statement that does it, and the message names the call, the allocation or the slot behind the refusal.
  • Get over a map of owning pointers, and Get or GetOr over a map of records whose fields are owning pointers, are refused at the call, and the refusal says to give the type argument as a borrowed pointer.
  • A dispose of memory a C routine answered, a page Mmap answered or a block Malloc answered is refused, through a copy as well, and the refusal names the routine the C library releases it with.
  • Stream shapes that lost values, raced or stopped a program are refused where they are written: a producer joined to a ring after the ring was handed to tasks, a select arm racing a scheduled generator for the ring it pulls, a select arm over a stream no task feeds, and a pull or a bind a deferred statement reaches after its routine closed its streams.
  • A defer inside a select arm or a failure section, which ran whether or not its branch ran, and an emit in a generator’s deferred statement are refused.

The packages and the commands that install them are on the install page.