Running Assembly Code directly within Godot

Hi Everyone,

Just for a bit of fun, and because I’ve written assembly for most of my working life I thought it’d be fun to try and get assembly code running directly within Godot.

So to that end I’ve written a module with a simple, error checking, tokenising compiler that allows editing and running assembly directly within Godot. At the moment this is merely a proof of concept and I have many more op codes to implement, but I’m far enough along to get a ‘Hello World’ assembly programming running.

Is this going to replace anything, probably not, and you’ll still need Godot signals to handle communication, but it’s a learning exercise and always fun to do something completely nuts and not the norm… :slight_smile:

The first assembly test and screenshot below.

Kindly

Ryn

Assembly Code

;Hello World - Example
.reset

.code

_start:
	lda 0
	sta b
	
mainLoop:
	lda msg, b
	jz end
	out
	lda b
	inc
	sta b
	jp mainLoop

end:
	brk

.data
	.db msg, "Hello, Godot World!"

Godot Interface

extends Control

func _ready() -> void:
	AssemblyEngine.asm_script_value.connect(asm_script_value)

func asm_script_value(value: float) -> void:
	$TextEdit.text += char(int(value))

func _on_button_pressed() -> void:
	$TextEdit.text = ""
	AssemblyEngine.execute_asmscript("helloworld.asm")

func _on_kill_pressed() -> void:
	AssemblyEngine.runable = false


17 Likes

Oh wow!!! This brings back memories! After my time with the Commodore 64, I slowly drifted away from assembly—it just got too wild for me, too fast. But seeing this running inside Godot is something else entirely! There’s something fascinating about bringing such a low-level approach into a modern engine.

I absolutely love this kind of experimentation, even if it’s just for the fun of it. Can’t wait to see where you take it next—keep up the amazing work! :rocket:

3 Likes

Thanks so much.

My first approach was to build a complete programmable retro computer in Godot (see my GitHub), but now I’ve done that I wanted assembly directly in Godot which I could use in any project. :slight_smile:

Kindly

Ryn

2 Likes

I used to think C++ would provide the fastest results.
If this was a supported language for godot… we’d have the quickest engine ever.

4 Likes

Thanks,

At the moment I’m just concentrating on getting it all working, so speed is not my primary focus. I’ve already got some ideas to improve that.

Throughout my career though I’ve learn’t that it’s just as easy to write crappy assembly code as easily as writing crappy code in any language.

Kindly

Ryn

1 Like

Nice work and this is the good nuts. :laughing:

2 Likes

Hi

I spent a bit of time today separating the compiler and runtime. I also finalised the messaging that would be emitted from the runtime. Mostly you’ll just output a value, how ever you use that value elsewhere is up to you. This is mostly because all assembly really has access to is the raw values in the registers.

signal assemblyscript_value(value: float)
signal asmemblyscript_message(message: String)
signal assemblyscript_programstarted
signal assemblyscript_programstopped

The program started and stopped signals are obvious enough, the message signal is mostly used by the compiler to output errors and warning. I’m also using it to output some debug info.

extends Control

var mode: int = -1

func _ready() -> void:
	AssemblyScript.assemblyscript_value.connect(assemblyscript_value)
	AssemblyScript.asmemblyscript_message.connect(asmemblyscript_message)


func assemblyscript_value(value: float) -> void:
	match mode:
		0:
			$Output.text += char(int(value))
		1:
			$Output.text += str(int(value)) + "\n"

	$Output.scroll_vertical = $Output.get_line_count()


func asmemblyscript_message(message: String) -> void:
	$Messages.text += message + "\n"
	$Messages.scroll_vertical = $Messages.get_line_count()

I’ve also spent some time getting conditional looping working, so now it just a process of linking in the other opcodes, optimisation and testing. Lots to go.

The lower display in the below screen shots show the program counter final position, snapshot of tokenised code, final registers state and variables as they were when the program ended.

Sorry I’m using decimal for the opcodes not hex, I know I should be taken out and shot… :wink:

Kindly

Ryn

3 Likes

Hi Everyone,

In this update I’ve made further improvements to the compiler. I’ve changed the opcode storage to be packed Int 32’s, removing the need to constantly convert from string to int’s or floats to ints. I’ve also dereferenced the variables and deduplicated the constants into their own storage so that the opcode / byte code and parameters can all be stored as Int’s in the instruction memory. I’d like to change from int 32’s to Packed Bytes but given I’m currently storing the jump location for jump instruction as a MNEMONIC or parameter with the OP code this would limit me to a program with no more that 255 lines in length. I could dereference the labels as well and this would increase that limit to 255 labels for jump locations but unlimited lines…

Though given that modern CPU’s can be slower at moving smaller bit sizes than larger, due to memory padding and such, I’m not sure it’s really worth the effort. I’m might experiment and see.

Would love your ideas though. Now that I’ve got most of the compiler and structure in place I’m going to start adding more instructions, PUSH and POP, comparison’s and conditional branching are next on the list. I’ve also got basic keyboard input / polling working for unicode keys.

I’ve also added a .perf compiler directive which controls how many assembly instructions are executed between yield cycles, higher numbers offer greater performance at the cost of higher CPU. I’ve capped it at a maximum of 30000, for smaller programs you won’t notice any difference from the default of 3000.

Kindly

Ryn

2 Likes

What are your plans for this project?

1 Like

Hi,

No idea really, I’m a core developer by nature, I don’t have any aspirations to write a game. I suspect I’ll use it as a teaching aid in some of the lecturing I do. I’ll also make it all freely available on my GitHub for anyone to use.

I’m very much a tinkerer and enjoy building things others wouldn’t consider or say isn’t possible.

Kindly

Ryn

2 Likes

That is what I was wondering without asking, thanks for the update. :grin:

I have done a lot with compilers and transpilers over the years, AS to JS years ago and some other stuff so I am always interested in these “projects”, fun to see this type of stuff. :nerd_face:

1 Like

Can certainly be a lot of fun… :slight_smile:

Ryn

1 Like

So, wait… Is this actually compiling this to machine code and running this directly on the hardware, or is it just interpreting the assembly code? Or maybe transpiling to GDScript or something?

1 Like

HI there

A bit of everything really. To make it platform agnostic so anyone can use it on any platform or any processor, I’ve basically recreated mostly 6502 assembly instructions which are compiled into bit code and run within an internal Godot Virtual machine, which uses GDScript signalling for communication.

I’ve designed a pattern matching compiler which syntax checks and creates the bit code thus bypassing the need for any reinterpretation of code at runtime. I could write a library that would build dylibs for each hardware type but then I would also need every hardware type to test on which sadly I don’t have. That would also be an enormous undertaking. Also everyone would probably need GCC or GASM or XCODE or what ever on their platform to build the code.

I also wanted to make this as small and as easy to use as possible. So all you need to do is add the assembly script file as a global script and you’re good to go.

I doubt this is ever going to replace anything but if you wanted to play with assembly in a safe way or include some sort of user script hacking type theme into a game then this could suit.

Hope that helps…

Kindly

Ryn

1 Like

Hi Everyone,

In this update I touch on the new signals emitted from the engine which you can use in your Godot program to interact with the assembly language. I’ve added a lot more signals to give you granular control and notification of certain events. Some of these signals can also be enabled or disabled using (DOT) compiler directives. I won’t go into much detail about each as their names are pretty self explanatory.

Signals

signal assemblyscript_value(value: float) # Used to output any value to the Godot
signal assemblyscript_sysvalue(value: float) # Used to control user defined action in Godot program
signal assemblyscript_message(message: String) # Used to output compiler or debug messages
signal assemblyscript_programstarted
signal assemblyscript_programstopped
signal assemblyscript_yield # Used to inform Godot program when a yield phase starts
signal assemblyscript_programpaused
signal assemblyscript_programresumed
signal assemblyscript_processormodechanged

Signal Directives
By default all the below directives default to False if not set.

.proc true # Enables processor performance mode switching when assembly program is paused or resumed
.yield true # Controls if the enter yield phase signal is sent
.debug true # Controls outputting of debug information on program completion

Instructions
I’ve completed the implementation of the follow instructions including some comparison operators and conditional branching:

nop
brk
lda,constant
lda,register
lda,variable
lda,variable,constant
lda,variable,register
lda,variable,variable
sta,register
sta,variable
sta,variable,constant
sta,variable,register
sta,variable,variable
inc
dec
push,register
pop,register
cmp,constant
cmp,register
cmp,variable
stc
out
in
lp,label
jp,label
jz,label
je,label
jnz,label
jne,label
sys
yld
hlt

Compiler
As usual I’ve spent some more time tidying up the compiler whilst also adding the new directives as well as a constants library for things like true / false / pi etc.

Extras
There is now a memory block datatype .mb and also the byte datatype .db can now accept a list of predefined bytes values rather than just a quoted string.

Kindly

Ryn

Screen Shots


You can also look at my virtual retro machine as well, if you’re interested:

Viro 1.1 Released

1 Like