Shared Libraries

Shared libraries are useful because you don't have to link all code in your project over and over whenever the code changes. It also allows you to keep file sizes smaller. The same shared library can be used by multiple programs at the same time, and only one copy of it has to exist.

In KerboScript

As you probably know, in KerboScript when you want to load the code from another file, all you have to do us "run" that file. If we wanted the code to only be loaded once (because it is just a bunch of functions) we could do something like:

RUNONCEPATH("library.ksm").

And that would load our functions that we can later call. In order to understand how to do something similar in KASM, we must first look at the disassembly of calling RUNONCEPATH("library.ksm").

This can be found by first compiling the file, in this case the file with the above code was called main.ks and then running KDump (kdump --disassembly main.ksm):

push  @
push  @
push  true
pushv  "library.ksm"
eval
call  "@LR00",#
pop

This may be confusing at first, but mostly all that is happening here is that there is a place in the code called "@LR00" which is loaded into kOS every time you run a program from the terminal. It exists so that each and every piece of user code doesn't need to have all the code in it required to run a program every time the user wants to.

This code is able to be changed by the kOS devs at any time because it is not meant to be written directly by users, and is always accessed by the "run" family of functions. In order to understand what we are calling however, as of kOS version 1.3.0.0, the code is:

bscp -999, 0     ; This instruction has the label "@LR00"
stol "$runonce"
stol "$filename"
push @
push "$filename"
eval
push true
push #
call #, "load()"
bfa run_program
push "$runonce"
bfa run_program
pop_loop:
    pop
    targ
    btr after_pop
    jmp pop_loop
after_pop:
    pop
    push 0
    ret 1
run_program:
    stol "$entrypoint"
    call "$entrypoint", #
    pop
    push 0
    ret 1

This is a whole lot to digest, but basically there is a built-in function called load() that kOS calls when it wants to load a file. It returns various things, in particular the program's "entry point" which is called like a function, and that is just the first instruction in the program you are trying to run.

The arguments we are more interested in though, and on lines 2 and 3, there are the stol instructions which store if the program should only be run once, and the other one stores the filename. So these two things are the parameters to the kOS loader.

Loading

In order to do the same thing in our program, we can simply make a piece of code that calls "@LR00" the same way the KerboScript code does.

This is not too difficult to do. Here we create a macro to do it for us just in case we want to load multiple:

.global _start

.macro LOAD 1
    push @
    push @
    push true
    pushv &1 ; &1 gets replaced with the argument we give
    eval
    call "@LR00", #
    pop
.endmacro

.func
_start:
    LOAD("library.ksm)
    eop

This program will "load" (aka run) our library.ksm file.

Delegates

In KerboScript, delegates are functions that you can effectively store in a variable. As of the current version of KerboScript, it turns out that all functions are actually used in the same way that delegates are. This allows you to assume that a function exists and generate call instructions for it just by using a variable name. The way that you would call a function like this would be:

call #, "$add_two*"

Because it is a function delegate and not just a normal variable, the convention is to put a * after the variable name. In this case we call the function add_two because that is the name of it in our code, but you can mix and match the names although that might be confusing.

This would replace the normal:

call add_two, #

That we would do if calling the function statically.

Library Side

Now that we know how to call the function that we have loaded, we need to know how to set that up from the library's view.

This is where the special _init function label comes into play in KASM. It is used to initialize things inside of a shared library, which usually takes the form of setting up function delegates.

An example of the shared library code for our add_two function would be:

.global _init

.func
_init:
    pdrl add_two, true
    sto "$add_two*"

    push @
    push "Loaded!"
    call #, "print()"
    pop

    ret 0 ; Note this, see below


.func
add_two:
    add
    ret 0

As you can see, our code for add_two stays exactly the same, but we have the new _init function.

Here we use the special pdrl instruction. This stands for "push delegate relocate later", and basically allows us to store a function onto the stack, and then store it in a variable. The first argument is the function that we want to push, and the second argument is a boolean of whether or not we would like to capture a closure or not.

Then the next instruction we do is just storing our delegate into "$add_two*" like above.

Then just to verify that it is working, for fun we print out "Loaded!".

Note: Notice at the bottom of _init we ret 0. This may be confusing, but remember in the kOS file loader, it simply calls the file as a function, so in order to tell kOS that our _init function is over, we need to ret, and in this case we haven't created any variable scopes.

The main program can be linked normally (kld -o main.ksm main.ko) because it doesn't do anything special. But KLinker needs slightly more information in order to tread the shared library as it would need to be treated.

So in order to link a shared library, all you have to change is to add the --shared flag:

kld --shared -o library.ksm library.ko

Then you will have two files named main.ksm and library.ksm that when you put in the same place, you can run main.ksm and it will call functions inside of library.ksm.

Final code

Here is the final code in main.kasm:

.global _start

.macro LOAD 1
	push @
	push @
	push true
	pushv &1
	eval
	call "@LR00", #
	pop
.endmacro

.func
_start:
	LOAD("library.ksm")

	push @
	push @
	push 2
	push 3
	call #, "$add_two*"
	call #, "print()"
	pop
	eop

And library.kasm:

.global _init

.func
_init:
	pdrl add_two, true
	sto "$add_two*"

	push @
	push "Loaded!"
	call #, "print()"
	pop
	ret 0

.func
add_two:
	add
	ret 0

Calling KerboScript Functions

Because you have previously seen how KerboScript function calling actually works (using pdrl), it is worth noting that this is both how you can call KerboScript functions from your KASM code, and how you can make KASM functions callable from KerboScript code.

Final Notes

This is the last offical "tutorial" section of this book. The following section only lists the various kOS built-in functions. You now have enough information to do whatever you desire to do in KASM.

If after reading the list of all built-in functions, you are still confused on how to do something in KASM, I encourage you to write equivalent code in KerboScript, and then run KDump in order to see how KerboScript does it.

If there are requests to create a new tutorial section for something that is more specific, then I will definitely consider adding it.