Releases: zilch-lang/nstar
Releases · zilch-lang/nstar
N* v2.2.0
✨ What's new?
- It is now possible to link against symbols that should be statically present at link-time using the
extern.code
section
Each binding is given a type that should correspond to the underlying FFI type, but this cannot be checked - Bang-types have made it into N*!
These are used to forget about some register binding
However, “forget” means different things depending on where the bang-type lies:- If the bang-type is in the caller's context, then this means that upon returning from this function, the register associated to the bang-type cannot be trusted anymore
This allows for some kind of reminder as to which register has to be callee-saved - If the bang-type is in the continuation, then this means that we basically don't care about the previous value in a register, and that we may even plan on overwriting it
- If the bang-type is in the caller's context, then this means that upon returning from this function, the register associated to the bang-type cannot be trusted anymore
- String literals are also a thing now, which makes it easier to write a basic "hello, world!" example
- A new
sref n, r
instruction was born, to be able to fetch a reference to a stack cell - And finally, packed structures with their literal equivalents for the
data
sections
🐛 Bug fixes
sst r, n
was compiled to the wrong opcodes when targeting amd64- When type-checking the
mv
instruction, when it moved the continuation, the register environmentχ
was not extended with the correct type (the∀().
part was erased)
N* v2.1.0
🎉 Additional features
include
sections allow to include code from other files found in the include path- Those cannot be cyclic, i.e. you cannot include X in Y if you already included Y in X
- It is impossible to include the same file multiple times. If you are familiar with C or C++, that's what header guards are for, but they are completely implicit here!
- Includes work using an include path, which is simply a set of path to search in. The include path always contains at least the current directory, but can be extended using the
-I <PATH>
(or-I=<PATH>
) flag - A simple example of including files can be found in the
examples
directory, namely thetrue.nst
file
🐛 Some bug fixes
- a bug in the ELF object generation library causing a crash has been fixed. This was triggered when the current file didn't include global symbols (for now, all functions in the
code
section are marked as global).
N* v2.0.0
This second version of N* is a major improvement over the first one. 🎉
🚫 Some of the problems of the first version
- in the first version, the branch-checker was something needed to ensure we did not break the control flow
- code addresses could not be manipulated as it would have made the branch-checker meaningless
- because of how context types were handled, writing
{}
was allowed but should not have been a correct context because it does not contain a stack (%sp
not bound) - the first version is heavily oriented towards the amd64 architecture
- instructions were not attached to a specific label, requiring the branch-checker to make the type-checking stage correct (it did not treat label fallthrough, as per #12)
- some bugs in the ELF file generation library were later discovered (e.g. the
sh_info
field of the.symtab
section was not assigned the correct value sometimes)
ℹ️ How those problems have been addressed in the second version
- the control flow is now simply controlled by the fact that every block ends with a jumping (“terminal”) instruction:
call
,jmp
orret
the branch-checker was therefore made useless, and has completely been removed - because instructions are now scoped, and thanks to the introduction of continuations (discussed below), we can “safely” manipulate code addresses within N*
- the new context type, written
∀(v).{ χ | σ → ε }
, now requires a stackσ
- instructions have been made more abstract, allowing to target any architecture more easily
- the syntax of label bindings has been a little bit reworked, leading to
label: type = block
where the instruction block is now scoped to the label - those bugs have been fixed
✨ Added features that were not in the first version
- continuations might be the biggest added feature there
basically, continuations allow for safe jumps where the return address (in case of acall
) is fully known at compile-time and cannot be changed at all
⚠️ continuations did exist in the first version, but under a different form where the compiler would try to infer the return context (this is not the case anymore in the second version because continuations are explicitly typed) - the new context types now embed the expected stack as well as the expected continuation
for example, writingf: forall(s: Ts, e: Tc).{ %r5: forall().{| s -> e } | s -> %r5 }
means that we can jump (or call) tof
only if the current environment contains a function pointer in%r5
, which is used as the continuation function when returning fromf
the continuation cannot be overwritten nor replaced (this is checked by the compiler) - the grammar has been reworked a little bit:
- the
unsafe
qualifier now applies only to specific instructions, not anymore to blocks of instructions (this is to greatly reduce the added complexity of parsing unsafe blocks within instruction blocks) - binders now accept a simple
=
sign between their type and their value
this is mainly done to simplify scoping rules (writingx: u64 0 1
was allowed and checked by the compiler ─ now this is forbidden by the grammar, requiring no more internal checks) - an instruction block is a list of instructions separated by
;
, and ended by a terminal instruction (one ofjmp
,call
orret
)
this restriction only makes sure that it is impossible to use one of the terminal instructions in the middle of an instruction block
- the
- the instruction set has completely been reworked to abstract away some architecture details (leaning towards amd64) and highly simplify inference rules and type-checking
all the new instructions are described in the specification document (it would be too long to describe them all here) - N* code is now compiled into a small abstract machine core (the NSAM) which is later compiled for the target architecture (may it be some machine code, or even bytecode for a virtual machine)
N* v1.0.0
Finally!
The first version of N* is finally finished.
The compiler incorporates the following features:
code
anddata
sections- the
code
section contains a sequence of labels and instructions - the
data
section contains a sequence of named values (where each label is associated with a value)
- the
- Instructions:
push expr
- ✅ Completely parses
- ✅ Completely type-checks
- Only compiles when
expr
is an immediate integer or a register
pop expr
- ✅ Completely parses
- Only type-checks when
expr
is a register (from%r0
to%r5
) - Compiles when
expr
is a register
nop
- ✅ Completely parses
- ✅ Completely type-checks
- ✅ Completely compiles
jmp label
- ✅ Completely parses
- ✅ Completely type-checks
- ✅ Completely compiles to a 32-bit
%ip
displacement
call label
- ✅ Completely parses
- ✅ Completely type-checks
- ✅ Completely compiles to a 32-bit
%ip
displacement
ret
- ✅ Completely parses
- ✅ Completely type-checks
- ✅ Completely compiles
mov src, dst
- ✅ Completely parses
- Only type-checks when
src
anddst
aren't pointer offsets - Only compiles when
src
anddst
aren't some sort of pointer offsets
- Compilation only targets ELF64 (ELF 64-bit relocatable file; needs to be compiled with the C runtime etc)
unsafe
blocks are used to enclose potentially unsafe instruction blocks
Expect more to come in the next months! Some expression types are not yet handled in the type-checker, or even the code generation step.