Library calls ============= Overview -------- LitePAC runtime is nothing without application libraries. LitePAC virtual machine has a simple mechanism for implementing library calls. Library calls are implemented as the shared library objects (.so files). There is a special instruction *LIBCALL*. This instruction is like a gate to the real operating system. *LIBCALL* instruction works with one operand and this operand is the library call number. The operand is coded in the text section. Some details you can find in the paper devoted machine instructions. This paper describes the implementation of _strlen()_ library call. This call is a simple wrapper around the _strlen()_ C libary function. Machine facilities ------------------ As mentioned earlier the library calls are shared objects. The machine code loads libraries using _dlopen()_, finds the predefined symbol (library entry point), now it is _libcall_init_ symbol, and calls the function. This funcions has to register library calls. Each library call handler has the following prototype: ------------------------------------------- void vm_libcall_handler(struct vm *machine); ------------------------------------------- So the library call handler gets the machine context with all registers and memories. The handler code can do everything. There are several useful function to work with the machine stack. Usually we need to read something from the stack and write something back. The machine has useful API to do it. They are four main operations: - peek; - poke; - get; - put; The first and the second don't modify the *sp* register and just rewrite value in the stack, the last two operations grow and shrink the stack respectively. Using API user can work with four basic data types: - integer number; - float number; - string; - char symbol; The list of all these functions can be found in the __ file. Library call implementation --------------------------- In this chapter we'll write the simple library call. Our new call has the following prototype: ----------------------- int strlen(string str); ----------------------- The library call takes one argument -- the string and returns the string length in bytes. Below you see the implementation of this call: -------------------------------------------- static void vm_strlen(struct vm *machine) { char str[STRING_LEN]; size_t len; vm_stack_get_string(machine, str); len = strlen(str); vm_stack_poke_int(machine, len); } -------------------------------------------- [NOTE] In this implementation we follow our prototype. Our call has only one argument but if your library call has more arguments you extract them one by one, the first argument is on the top of the stack. After the get operation the *sp* register points to the second argument if you call get again *sp* points to the next argument and so on. You can see how first of all we get the string from the top of the stack. The next picture shows how the stack looks before we call a get function and after. high address high address +-------------+ +-------------+ | return value| | return value| | place | | place | | | | | +-------------+ +-------------+ <- %sp | string is | | | | here | | | +-------------+ <- %sp +-------------+ | | +-------------+ low address low address You see that we already have the place for return value. It means that the last call *must* be a *poke* operation with the right data type. When the *sp* register points exactly to the return value place. [NOTE] When you get all arguments with get operations the *sp* register points to the return value place. After calling the poke function our return value will be in the predefined place. Now we have the library call handler, we need to fill out the structure: --------------------------------------- static struct vm_libcall vm_strlen_lc = { .name = "strlen", .num = VMSTRLEN, .handler = vm_strlen }; --------------------------------------- and register our library call in the _libcall_init()_ function (remember it is a special symbol name): ----------------------------------------------------------------- void vm_libcall_init() { ret = vm_libcall_register(&vm_strlen_lc); if (ret != ret_ok) error(1, "can't register `strlen' library call"); } ----------------------------------------------------------------- Library call wrapper -------------------- So we already wrote the library call code. Now we need to make a wrapper. Then we can link the virtual machine object files against this wrapper and call our _strlen()_ library call. There is _libcalls.txt_ file in the root. Add the following strings. ---------- # string strlen ---------- Then run: ------------- $ make vmlib ------------- After that you will find _string.vo_ file in _vmlib_ folder. It is a library wrapper for our function. Now we get the wrapper and it is common virtual machine object file. We can link our object files against this wrapper. _vmobjdump_ gives a good picture what the file is. Then create a header file _string.h_ with the function prototype. Sample prorgram --------------- Now we can write a simple program and use our library call. ----------------------------------------------- #include #include void init() { string hello = "Hello world!"; int len; len = strlen(hello); printf("String length is %d\n", len); } ----------------------------------------------- Then we need to build our program. There is a _vmc_ program, it runs compiler, assembler and linker. ---------------------------------------------------------------- $ vmc -e init -o strlen.v strlen.vm vmlib/string.vo vmlib/io.vo ---------------------------------------------------------------- To test the built program run _vm-lib-test_. --------------------------------------------------------- $ vm-lib-test -e init -p vmlibcall/ strlen.v --------------------------------------------------------- Where *-e* is the symbol name of the entry point and *-p* is the path to the folder where library shared object files live.