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.