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 routine7.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 0Every 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/StartsTasks, 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.
Getover a map of owning pointers, andGetorGetOrover a map of records whose fields are owning pointers, are refused at the call, and the refusal says to give the type argument as aborrowed pointer.- A
disposeof memory a C routine answered, a pageMmapanswered or a blockMallocanswered 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
selectarm racing a scheduled generator for the ring it pulls, aselectarm over a stream no task feeds, and a pull or a bind a deferred statement reaches after its routine closed its streams. - A
deferinside aselectarm or a failure section, which ran whether or not its branch ran, and anemitin a generator’s deferred statement are refused.
The packages and the commands that install them are on the install page.