Npt documentation.
Reference: ANSI Common Lisp npt
Prev: 3. Escape Function
Next: 5. LISP ABORT
This chapter describes how to implement Common Lisp functions in C alone.
Usually, functions are created by lambda
or defun
, but in this chapter, we will create functions only in C. However, the function type is different: it is not a FUNCTION
type, but a COMPILED-FUNCTION
type like standard functions like car
.
The type of the function is checked as follows.
* (lambda ())
#<FUNCTION LAMBDA #x801256a00>
* #'car
#<COMPILED-FUNCTION CAR>
*
To use a C language function, it is necessary to register a function pointer with a number. The number of function pointers that can be registered is up to 32.
If it’s not enough, define LISP_POINTER_EXTEND
at compile time. As an example, here’s how to run a compilation with 128 extensions
$ cc -DLISP_POINTER_EXTEND=128 src/*.c -lm
The instruction to register the function pointer is shown below.
void lisp_compiled_rest(int index, lisp_calltype_rest call);
void lisp_compiled_empty(int index, lisp_calltype_empty call);
void lisp_compiled_var1(int index, lisp_calltype_var1 call);
void lisp_compiled_var2(int index, lisp_calltype_var2 call);
void lisp_compiled_var3(int index, lisp_calltype_var3 call);
The argument index
is a number to register, usually from 0 to 31.
The argument call
is the pointer to the function of the escape function.
The function to be used depends on the argument.
The generic one is lisp_compiled_rest
, and lisp_calltype_rest
is of type int (*)(addr)
.
The type rest
means (&rest list)
of lambda list.
The type empty
means no argument, var1
means one, var2
means two, and var3
means three arguments.
It is an escape function to register.
The instructions for creating a function object are as follows
int lisp_compiled_function_(addr x, int index, addr symbol);
int lisp_compiled_function8_(addr x, int index, const void *str);
int lisp_compiled_function16_(addr x, int index, const void *str);
int lisp_compiled_function32_(addr x, int index, const void *str);
Because these functions are executed on Common Lisp, they are different from function pointer registration functions and cannot be executed until control is passed to the main_lisp
function.
As an example, consider the following escape function in Common Lisp.
int function_test_(addr list)
{
return lisp_format8_(NULL, "TEST = ~A~%", list, NULL);
}
At first, the function pointer of function_test_
is registered in function number 0.
(0, function_test_); lisp_compiled_rest
Now that a function can be created, the steps to create a function object are as follows
(x, 0, NULL); lisp_compiled_function_
Create the function test_output_
to run and check it.
int test_output_(void)
{
, x;
addr control
(&control);
lisp_push_control= Lisp_hold();
x (x, 0, NULL);
lisp_compiled_function_return lisp_pop_control_(control);
}
The function object is now stored in the x
hold variable.
Further, the function object is now executed with funcall
.
int test_output_(void)
{
, x, y, z;
addr control
(&control);
lisp_push_control= Lisp_hold();
x = Lisp_hold();
y = Lisp_hold();
z (x, 0, NULL);
lisp_compiled_function_(y, 10);
lisp_fixnum(z, 20);
lisp_fixnum(NULL, x, y, z, NULL);
lisp_funcall_return lisp_pop_control_(control);
}
Escape is ignored.
The function test_output_
is expressed in Common Lisp as follows.
funcall #<COMPILED-FUNCTION NIL> 10 20) (
The results are as follows.
TEST = (10 20)
The registered function works.
The following is an example of the behavior of defun
.
Register a function object in the symbol-function
.
The functions used for registration are as follows
int lisp_compiled_defun_(int index, addr symbol);
int lisp_compiled_defun8_(int index, const void *str);
int lisp_compiled_defun16_(int index, const void *str);
int lisp_compiled_defun32_(int index, const void *str);
As an example, register the function fact_
, which is a factorial function created previously, under the name FACT
.
The function fact_
, completed in the previous chapter, is as follows.
static int fact_(addr x, addr value)
{
, y;
addr control
if (! lisp_plus_p(value)) {
(x, 1);
lisp_fixnumreturn 0;
}
(&control);
lisp_push_control= Lisp_hold();
y if (lisp_funcall8_(y, "1-", value, NULL))
goto escape;
if (fact_(y, y))
goto escape;
if (lisp_funcall8_(x, "*", value, y, NULL))
goto escape;
:
escapereturn lisp_pop_control_(control);
}
Although the function fact_
is completed as an escape function, it cannot be registered as it is because it is not made for registration.
So, here is the function function_fact_
for registration.
The function calls the function fact_
and sets the return value.
int function_fact_(addr var)
{
, x;
addr control
(&control);
lisp_push_control= Lisp_hold();
x if (fact_(x, var))
goto escape;
(x);
lisp_set_result_control:
escapereturn lisp_pop_control_(control);
}
The function lisp_set_result_control
is used to set the return value of the function.
The next step is the registration process.
int main_lisp(void *ignore)
{
(1, function_fact_);
lisp_compiled_var1(1, "FACT");
lisp_compiled_defun8_
return lisp_eval_loop_();
}
The registration is done using lisp_compiled_var1
so that it takes only one argument.
The function number is 1.
Next, it runs lisp_compiled_defun8_
and registers the function under the name FACT
.
Finally, run lisp_eval_loop_
and call eval-loop for confirmation.
When prompted, run the fact
function.
$ ./a.out
* (fact 123)
1214630436702532967576624324188129585545421708848338231532891816182923
5892362167668831156960612640202170735835221294047782591091570411651472
186029519906261646730733907419814952960000000000000000000000000000
* (/ (fact 123) (fact 121))
15006
* ^D
$
Since the function fact
works as a function of Lisp, it can be incorporated into the expression.
The closure here is the ability to store values in a function object.
A function object can have a single value.
To set the value, use the following instructions
void lisp_compiled_setvalue(addr pos, addr value);
Here is an example of storing the value 10 in a closure after creating a function object
(x, 0, NULL);
lisp_compiled_function_(value, 10);
lisp_fixnum(x, value); lisp_compiled_setvalue
The value can be obtained by using the following instructions when the function that registered the function pointer is called.
void lisp_compiled_getvalue(addr *ret);
This function has a few things to be careful about.
The value must be fetched before doing lisp_push_control
.
Since this function does not allow to specify a hold variable, it receives the object directly.
The following is an example of storing a value in the hold variable
int function_test_(addr list)
{
, x;
addr control
(&x);
lisp_compiled_getvalue(&control);
lisp_push_control(&x, x);
lisp_hold...
return lisp_pop_control_(control);
}
We explained that when the function pointer is registered, it is assigned to a number from 0 to 31.
For example, do the following.
(0, function_test_); lisp_compiled_rest
So why do we assign them to numbers?
The reason is to read and write the core file.
Initially, the core file was supposed to contain the function pointer values as they are.
However, due to the recent security situation, a feature called ASLR (Address Space Layout Randomization) was implemented in the operating system, which allows the function pointer to change randomly every time a process is started.
If a function pointer value is written to the core file, it will not work properly when the next process is started because it will point to a completely different address.
Numbering is also used inside npt, where the lisp_init
function sets the numbering of all functions, including the Common Lisp functions.
You can register function pointers at any time before the function is executed, but it is better to register them as soon as possible.
On the other hand, the following function
(0, "TEST"); lisp_compiled_defun8_
This is a feature of Common Lisp to create a compiled-function
object.
Because it is not initialization, it can only be executed after the main_lisp
function.