Introduction
NexFUSE is a bytecode format designed to be efficient and a drop-in low memory replacement for OPENLUD binaries. NexFUSE works by keeping an instruction pointer to an instruction address and incrementing it, parsing each opcode and running its respective functionality. NexFUSE would then be replaced by the brand-new "MercuryPIC" format, that supported 8-bit to 32-bit architectures and cleaned up a lot of the procedural mess NexFUSE left behind. The NexFUSE novelty is to be usable, and to get to that point with as little steps and commands as possible. That is why NexFUSE is manually managed, meaning that each free and create call is explicity stated in the code instead of hidden behind different allocations and hidden frees.
Architecture
The NexFUSE instruction set contains around 20 instructions, each one performing either a read or write on data, analysis, or clocking. NexFUSE, unlike OpenLUD, is more expansive, and, albeit not Turing-complete, strives to provide as many abstractions and gates for logic as possible with the knowledge at the time it was created. NexFUSE roughly has 65-70% of ABI compatibility with OpenLUD, meaning that a large portion of programs can be ran with NexFUSE and OpenLUD, however, more comprehensive and non-standard programs need to be ran with their respective interfaces.
VASM Interface
VASM compiles LR Assembly directly into NexFUSE with two context options:
-
Folded
-
Non-Folded
With folded programs, performance can be higher, however, a lot of the logical expansions of NexFUSE are limited due
to the compiler having no awareness of the program’s state or processes. Non-Folded programs create procedure headers
for each instruction set with dead code elimination optimizations still in place. Those must forcefully be disabled
via flags and options that can be found in frontend.zig
and compiler_main.zig
.
Big Registers
NexFUSE has a concept of big registers, which is data that is stored separately from the unsigned bytes and stored as 32-bit integers. (platform-dependent) Instructions like LAR
are designed to deal with big registers. LAR
prints out each number in a big register, ADD
can add up all integers in a register and put them into a big register (not a regular sized one) as it would potentially not fit the result of the sum of the data inside of the register.
The reason for big registers is so larger numbers are able to be stored with little to no conflict with the byte-sized information that is continually passed between existing registers, however, they are still different and data can not be safely passed between fast-access registers and big registers. To pass data between big registers and regular ones without truncating, you must keep a copy of the original data and only add the data into a big register when the representation gets larger than 255 (the standard size for a character/unsigned byte)
; Big registers are a NexFUSE-specific feature ONLY.
; they allow for certain things like assignment to the same register twice
; as the register exists in two different "worlds"
_start:
; clear all registers
zeroall
mov R1,5 ; add 5 to R1 (small stack space)
mov R1,10 ; add 10 to R1 (small stack space)
mov R1,15 ; add 15 to R1 (small stack space)
add R1, R2 ; R2 now has 30
add R1, R1 ; R1 now has 30. However, R1 in the small stack space remains unchanged
Specs
NexFUSE is an 8-bit binary format.
Fun Fact: In the NexFUSE MANUAL file, the word manual is spelt horribly wrong.
nexfuse menaul
note: this does not go over openLUD OBI, the manual is in the openLUD repository
* SUB - starts a subroutine (SUB ... ENDSUB)
* ENDSUB - ends a subroutine (SUB ... ENDSUB)
* GOSUB - jumps to a subroutine (GOSUB [address])
Procedure Endings
Warning
|
In NexFUSE, the subroutine body is simply a view into another program from within a parent program. |
In NexFUSE, a "sub-routine" or "procedure" is a separated environment of binary, delimited by two separate end bytes. One is to signal the end of the procedure, and the other is to signal the end of the bytecode. The EOB (End of Bytecode) byte is not used by the interpreter when creating procedures; it is simply used to denote where the method ends.
SUB a
DO WORK | the entirety of the subroutine.
END | This is run with the same function that the root binary is run with, that's why END is needed here as well
END SUB
END <--- ends the program
There are two different bytes used in the example above, and one of them is to signal the end of the procedure. The other one is the end of the procedure. Once it is run, the interpreter can pass through the same function that is used to run the file itself.