add dylib.dlopen dylib.dlsym dylib.dlclose dylib.dlcall

now you could add your own modules into nasal without changing the source code!
This commit is contained in:
ValKmjolnir 2021-12-03 19:31:03 +08:00
parent aa191a9feb
commit 9861ecd03e
8 changed files with 337 additions and 94 deletions

303
README.md
View File

@ -889,7 +889,7 @@ a.set(114514);
println(a.get());
```
### __Native Functions(This is for library developers)__
### __Native Functions__
You could add builtin functions of your own
(written in C/C++) to help you calculate things more quickly.
@ -1010,6 +1010,126 @@ nasal_ref builtin_keys(std::vector<nasal_ref>& local,nasal_gc& gc)
}
```
### __Modules(This is for library developers)__
If there is only one way to add your own functions into nasal,
that is really inconvenient.
Luckily, we have developed some useful native-functions to help you add modules that created by you.
After 2021/12/3, there are some new functions added to `lib.nas`:
```javascript
var dylib=
{
dlopen: func(libname){return __builtin_dlopen;},
dlsym: func(lib,sym){return __builtin_dlsym; },
dlclose: func(lib){return __builtin_dlclose; },
dlcall: func(funcptr,args...){return __builtin_dlcall}
};
```
Aha, as you could see, these functions are used to load dynamic libraries into the nasal runtime and execute.
Let's see how they work.
First, write a cpp file that you want to generate the dynamic lib, take the `fib.cpp` as the example(example codes are in `./module`):
```C++
// add header file nasal.h to get api
#include "nasal.h"
double fibonaci(double x){
if(x<=2)
return x;
return fibonaci(x-1)+fibonaci(x-2);
}
// remember to use extern "C",
// so you could search the symbol quickly
extern "C" nasal_ref fib(std::vector<nasal_ref>& args,nasal_gc& gc){
// the arguments are generated into a vm_vec: args
// get values from the vector that must be used here
nasal_ref num=args[0];
// if you want your function safer, try this
// builtin_err will print the error info on screen
// and return vm_null for runtime to interrupt
if(num.type!=vm_num)
return builtin_err("extern_fib","\"num\" must be number");
// ok, you must know that vm_num now is not managed by gc
// if want to return a gc object, use gc.alloc(type)
// usage of gc is the same as adding a native function
return {vm_num,fibonaci(num.to_number())};
}
```
Next, compile this `fib.cpp` into dynamic lib.
Linux(`.so`):
`clang++ -c -O3 fib.cpp -fPIC -o fib.o`
`clang++ -shared -o libfib.so fib.o`
Mac: same as Linux, but remember to generate `.dylib`
Windows(`.dll`):
`g++ -c -O3 fib.cpp -fPIC -o fib.o`
`g++ -shared -o libfib.dll fib.o`
Then we write a test nasal file to run this fib function, this example runs on Linux:
```javascript
import("lib.nas");
var dlhandle=dylib.dlopen("./module/libfib.so");
var fib=dylib.dlsym(dlhandle,"fib");
for(var i=1;i<30;i+=1)
println(dylib.dlcall(fib,i));
dylib.dlclose(dlhandle);
```
`dylib.dlopen` is used to load dynamic library.
`dylib.dlsym` is used to get the function address.
`dylib.dlcall` is used to call the function, the first argument is the function address, make sure this argument is vm_obj and type=obj_extern.
`dylib.dlclose` is used to unload the library, at the moment that you call the function, all the function addresses that gotten from it are invalid.
If get this, Congratulations!
```bash
./nasal a.nas
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040
```
## Difference Between Andy's Nasal Interpreter and This Interpreter
This interpreter uses more strict syntax to make sure it is easier for you to program and debug.
@ -1098,22 +1218,17 @@ Function 'die' is used to throw error and crash.
```javascript
hello
[vm] error: error occurred this line
[vm] error at 0x0000009b: native function error.
[vm] native function error.
trace back:
0x0000009b: callb 0x22 <__builtin_die> (lib.nas line 85)
0x00000182: callfv 0x1 (a.nas line 6)
0x00000186: callfv 0x0 (a.nas line 8)
vm stack(limit 10):
null |
func | <0x8b0f50> func{entry=0x9b}
func | <0x8b1db0> func{entry=0x17c}
num | 57.295780
num | 1852.000000
num | 1.943800
num | 0.000540
num | 39.370100
num | 3.280800
num | 0.453600
0x00000088: callb 0x22 <__builtin_die@0x417620> (<lib.nas> line 19)
0x000002af: callfv 0x1 (<a.nas> line 5)
0x000002b3: callfv 0x0 (<a.nas> line 7)
vm stack(limit 10, total 5):
| null |
| addr | 0x2af
| func | <0x6c62c0> func{entry=0x88}
| addr | 0x2b3
| func | <0x6c8910> func{entry=0x2a9}
```
Here is an example of stack overflow:
@ -1133,13 +1248,22 @@ And the trace back info:
```javascript
[vm] stack overflow
trace back:
0x0000000f: callfv 0x1 (a.nas line 5)
0x0000000f: 4090 same call(s) ...
0x00000007: callfv 0x1 (a.nas line 2)
0x00000013: callfv 0x1 (a.nas line 3)
vm stack(limit 10):
func | <0xc511e0> func{entry=0xd}
... | 9 same value(s)
0x0000000d: calll 0x1 (<a.nas> line 5)
0x0000000f: callfv 0x1 (<a.nas> line 5)
0x0000000f: 2044 same call(s)
0x00000007: callfv 0x1 (<a.nas> line 2)
0x00000013: callfv 0x1 (<a.nas> line 3)
vm stack(limit 10, total 4095):
| func | <0x24f1f10> func{entry=0xd}
| addr | 0xf
| func | <0x24f1f10> func{entry=0xd}
| addr | 0xf
| func | <0x24f1f10> func{entry=0xd}
| addr | 0xf
| func | <0x24f1f10> func{entry=0xd}
| addr | 0xf
| func | <0x24f1f10> func{entry=0xd}
| addr | 0xf
```
Error will be thrown if there's a fatal error when executing:
@ -1153,11 +1277,11 @@ func(){
And the trace back info:
```javascript
[vm] error at 0x00000008: callv: must call a vector/hash/string
[vm] callv: must call a vector/hash/string
trace back:
0x00000008: callv 0x0 (a.nas line 3)
vm stack(limit 10):
num | 0.000000
0x00000008: callv 0x0 (<a.nas> line 3)
vm stack(limit 10, total 1):
| num | 0.000000
```
Use command `-d` or `--detail` the trace back info will be this:
@ -1167,67 +1291,70 @@ hello world
[vm] error: exception test
[vm] native function error.
trace back:
0x0000008f: callb 0x22 <__builtin_die> (<lib.nas> line 20)
0x00000214: callfv 0x1 (<test/exception.nas> line 16)
0x00000248: callfv 0x0 (<test/exception.nas> line 39)
vm stack(limit 10):
null |
func | <0x23bc3f0> func{entry=0x8f}
func | <0x23bdc50> func{entry=0x20e}
mcall address: 0x24a4b88
0x00000088: callb 0x22 <__builtin_die@0x417620> (<lib.nas> line 19)
0x000002d2: callfv 0x1 (<test/exception.nas> line 16)
0x00000306: callfv 0x0 (<test/exception.nas> line 39)
vm stack(limit 10, total 5):
| null |
| addr | 0x2d2
| func | <0x827750> func{entry=0x88}
| addr | 0x306
| func | <0x829ee0> func{entry=0x2cc}
mcall address: 0x90e498
global:
[0] func | <0x23d3960> func{entry=0x5}
[1] func | <0x23bb8b0> func{entry=0xc}
[2] func | <0x23bb950> func{entry=0x14}
[3] func | <0x23bb9f0> func{entry=0x1c}
[4] func | <0x23bba90> func{entry=0x23}
[5] func | <0x23bbb30> func{entry=0x29}
[6] func | <0x23bbbd0> func{entry=0x30}
[7] func | <0x23bbc70> func{entry=0x38}
[8] func | <0x23bbd10> func{entry=0x40}
[9] func | <0x23bbdb0> func{entry=0x47}
[10] func | <0x23bbe50> func{entry=0x4e}
[11] func | <0x23bbef0> func{entry=0x55}
[12] func | <0x23bbf90> func{entry=0x5c}
[13] func | <0x23bc030> func{entry=0x63}
[14] func | <0x23bc0d0> func{entry=0x6a}
[15] func | <0x23bc170> func{entry=0x72}
[16] func | <0x23bc210> func{entry=0x7a}
[17] func | <0x23bc2b0> func{entry=0x81}
[18] func | <0x23bc350> func{entry=0x88}
[19] func | <0x23bc3f0> func{entry=0x8f}
[20] func | <0x23bc490> func{entry=0x96}
[21] func | <0x23bc530> func{entry=0x9f}
[22] func | <0x23bc5d0> func{entry=0xa7}
[23] func | <0x23bc670> func{entry=0xaf}
[24] func | <0x23bc710> func{entry=0xb7}
[25] func | <0x23bc7b0> func{entry=0xbf}
[26] func | <0x23bc850> func{entry=0xc6}
[27] func | <0x23bc8f0> func{entry=0xcd}
[28] hash | <0x248ae70> {14 member}
[29] hash | <0x248aed0> {9 member}
[30] hash | <0x248af30> {12 member}
[31] num | 0.017453
[32] num | 0.592500
[33] num | 0.304800
[34] num | 3.785400
[35] num | 0.025400
[36] num | 2.204600
[37] num | 1.687800
[38] num | 0.514400
[39] num | 0.264200
[40] num | 0.453600
[41] num | 3.280800
[42] num | 39.370100
[43] num | 0.000540
[44] num | 1.943800
[45] num | 1852.000000
[46] num | 57.295780
[47] hash | <0x248af90> {3 member}
[48] func | <0x23bdcf0> func{entry=0x21e}
[49] func | <0x23bdd90> func{entry=0x22d}
[50] func | <0x23bde30> func{entry=0x237}
[0x00000000] | func | <0x83da50> func{entry=0x5}
[0x00000001] | func | <0x826cb0> func{entry=0xc}
[0x00000002] | func | <0x826d50> func{entry=0x14}
[0x00000003] | func | <0x826df0> func{entry=0x1c}
[0x00000004] | func | <0x826e90> func{entry=0x23}
[0x00000005] | func | <0x826f30> func{entry=0x29}
[0x00000006] | func | <0x826fd0> func{entry=0x31}
[0x00000007] | func | <0x827070> func{entry=0x39}
[0x00000008] | func | <0x827110> func{entry=0x40}
[0x00000009] | func | <0x8271b0> func{entry=0x47}
[0x0000000a] | func | <0x827250> func{entry=0x4e}
[0x0000000b] | func | <0x8272f0> func{entry=0x55}
[0x0000000c] | func | <0x827390> func{entry=0x5c}
[0x0000000d] | func | <0x827430> func{entry=0x63}
[0x0000000e] | func | <0x8274d0> func{entry=0x6b}
[0x0000000f] | func | <0x827570> func{entry=0x73}
[0x00000010] | func | <0x827610> func{entry=0x7a}
[0x00000011] | func | <0x8276b0> func{entry=0x81}
[0x00000012] | func | <0x827750> func{entry=0x88}
[0x00000013] | func | <0x8277f0> func{entry=0x8f}
[0x00000014] | func | <0x827890> func{entry=0x98}
[0x00000015] | func | <0x827930> func{entry=0xa0}
[0x00000016] | func | <0x8279d0> func{entry=0xa8}
[0x00000017] | func | <0x827a70> func{entry=0xb0}
[0x00000018] | func | <0x827b10> func{entry=0xb8}
[0x00000019] | func | <0x827bb0> func{entry=0xbf}
[0x0000001a] | func | <0x827c50> func{entry=0xc6}
[0x0000001b] | hash | <0x8f4ce0> {14 member}
[0x0000001c] | hash | <0x8f4d40> {9 member}
[0x0000001d] | hash | <0x8f4da0> {13 member}
[0x0000001e] | num | 0.017453
[0x0000001f] | num | 0.592500
[0x00000020] | num | 0.304800
[0x00000021] | num | 3.785400
[0x00000022] | num | 0.025400
[0x00000023] | num | 2.204600
[0x00000024] | num | 1.687800
[0x00000025] | num | 0.514400
[0x00000026] | num | 0.264200
[0x00000027] | num | 0.453600
[0x00000028] | num | 3.280800
[0x00000029] | num | 39.370100
[0x0000002a] | num | 0.000540
[0x0000002b] | num | 1.943800
[0x0000002c] | num | 1852.000000
[0x0000002d] | num | 57.295780
[0x0000002e] | hash | <0x8f4e00> {16 member}
[0x0000002f] | hash | <0x8f4e60> {4 member}
[0x00000030] | hash | <0x8f4ec0> {3 member}
[0x00000031] | func | <0x829f80> func{entry=0x2dc}
[0x00000032] | func | <0x82a020> func{entry=0x2eb}
[0x00000033] | func | <0x82a0c0> func{entry=0x2f5}
local:
[0] nil |
[1] str | <0x249abd0> exception test
[0x00000000] | nil |
[0x00000001] | str | <0x9057f0> exception test
```

12
lib.nas
View File

@ -65,6 +65,8 @@ var math=
{
e: 2.7182818284590452354,
pi: 3.14159265358979323846264338327950288,
inf: 1/0,
nan: 0/0,
sin: func(x) {return __builtin_sin(x); },
cos: func(x) {return __builtin_cos(x); },
tan: func(x) {return __builtin_tan(x); },
@ -73,8 +75,6 @@ var math=
ln: func(x) {return __builtin_ln(x); },
sqrt: func(x) {return __builtin_sqrt(x); },
atan2: func(x,y){return __builtin_atan2(x,y);},
inf: 1/0,
nan: 0/0,
isnan: func(x) {return __builtin_isnan(x); }
};
var D2R=math.pi/180;
@ -112,4 +112,12 @@ var unix=
environ: func(){die("not supported yet");},
getcwd: func(){return __builtin_getcwd();},
getenv: func(envvar){return __builtin_getenv(envvar);}
};
var dylib=
{
dlopen: func(libname){return __builtin_dlopen;},
dlsym: func(lib,sym){return __builtin_dlsym; },
dlclose: func(lib){return __builtin_dlclose; },
dlcall: func(funcptr,args...){return __builtin_dlcall}
};

View File

@ -1,6 +1,6 @@
.PHONY=test
nasal:main.cpp nasal_ast.h nasal_builtin.h nasal_codegen.h nasal_gc.h nasal_import.h nasal_lexer.h nasal_parse.h nasal_vm.h nasal.h
clang++ -std=c++11 -O3 main.cpp -o nasal -fno-exceptions -Wshadow -Wall
clang++ -std=c++11 -O3 main.cpp -o nasal -fno-exceptions -ldl -Wshadow -Wall
test:nasal
./nasal test/ascii-art.nas
./nasal -c test/bf.nas

13
module/fib.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "../nasal.h"
double fibonaci(double x){
if(x<=2)
return x;
return fibonaci(x-1)+fibonaci(x-2);
}
extern "C" nasal_ref fib(std::vector<nasal_ref>& args,nasal_gc& gc){
nasal_ref num=args[0];
if(num.type!=vm_num)
return builtin_err("extern_fib","\"num\" must be number");
return {vm_num,fibonaci(num.to_number())};
}

6
module/makefile Normal file
View File

@ -0,0 +1,6 @@
.PHONY=clean
libfib.so: fib.cpp
clang++ -c -O3 fib.cpp -fPIC -o fib.o
clang++ -shared -o libfib.so fib.o
clean:
rm *.o *.so *.dll *.dylib

View File

@ -23,6 +23,12 @@
#include <fcntl.h>
#include <dirent.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
inline double hex_to_double(const char* str)
{
double ret=0;

View File

@ -11,6 +11,8 @@ enum obj_type
{
obj_file=1,
obj_dir,
obj_dylib,
obj_extern
};
// declaration of builtin functions
// to add new builtin function, declare it here and write the definition below
@ -77,11 +79,15 @@ nas_native(builtin_closedir);
nas_native(builtin_chdir);
nas_native(builtin_getcwd);
nas_native(builtin_getenv);
nas_native(builtin_dlopen);
nas_native(builtin_dlsym);
nas_native(builtin_dlclose);
nas_native(builtin_dlcall);
nasal_ref builtin_err(const char* func_name,std::string info)
{
std::cout<<"[vm] "<<func_name<<": "<<info<<".\n";
return {vm_none};
return {vm_none,nullptr};
}
// register builtin function's name and it's address here in this table below
@ -154,6 +160,10 @@ struct
{"__builtin_chdir", builtin_chdir },
{"__builtin_getcwd", builtin_getcwd },
{"__builtin_getenv", builtin_getenv },
{"__builtin_dlopen", builtin_dlopen },
{"__builtin_dlsym", builtin_dlsym },
{"__builtin_dlclose", builtin_dlclose },
{"__builtin_dlcall", builtin_dlcall },
{nullptr, nullptr }
};
@ -973,4 +983,69 @@ nasal_ref builtin_getenv(std::vector<nasal_ref>& local,nasal_gc& gc)
*str.str()=res;
return str;
}
nasal_ref builtin_dlopen(std::vector<nasal_ref>& local,nasal_gc& gc)
{
nasal_ref dlname=local[1];
if(dlname.type!=vm_str)
return builtin_err("dlopen","\"libname\" must be string");
#ifdef _WIN32
// wchar_t* str=new wchar_t[dlname.str()->size()+1];
// memset(str,0,sizeof(wchar_t)*dlname.str()->size()+1);
// mbstowcs(str,dlname.str()->c_str(),dlname.str()->size()+1);
// void* ptr=LoadLibrary(str);
// delete []str;
void* ptr=LoadLibrary(dlname.str()->c_str());
#else
void* ptr=dlopen(dlname.str()->c_str(),RTLD_LOCAL|RTLD_LAZY);
#endif
if(!ptr)
return builtin_err("dlopen","cannot open dynamic lib \""+*dlname.str()+"\"");
nasal_ref ret=gc.alloc(vm_obj);
ret.obj()->type=obj_dylib;
ret.obj()->ptr=ptr;
return ret;
}
nasal_ref builtin_dlsym(std::vector<nasal_ref>& local,nasal_gc& gc)
{
nasal_ref libptr=local[1];
nasal_ref sym=local[2];
if(libptr.type!=vm_obj || libptr.obj()->type!=obj_dylib)
return builtin_err("dlsym","\"lib\" is not a correct dynamic lib entry");
if(sym.type!=vm_str)
return builtin_err("dlsym","\"sym\" must be string");
void* func=nullptr;
#ifdef _WIN32
func=(void*)GetProcAddress((HMODULE)libptr.obj()->ptr,sym.str()->c_str());
#else
func=dlsym(libptr.obj()->ptr,sym.str()->c_str());
#endif
if(!func)
return builtin_err("dlsym","cannot find symbol \""+*sym.str()+"\"");
nasal_ref ret=gc.alloc(vm_obj);
ret.obj()->type=obj_extern;
ret.obj()->ptr=func;
return ret;
}
nasal_ref builtin_dlclose(std::vector<nasal_ref>& local,nasal_gc& gc)
{
nasal_ref libptr=local[1];
if(libptr.type!=vm_obj || libptr.obj()->type!=obj_dylib)
return builtin_err("dlclose","\"lib\" is not a correct dynamic lib entry");
#ifdef _WIN32
FreeLibrary((HMODULE)libptr.obj()->ptr);
#else
dlclose(libptr.obj()->ptr);
#endif
return gc.nil;
}
nasal_ref builtin_dlcall(std::vector<nasal_ref>& local,nasal_gc& gc)
{
nasal_ref funcptr=local[1];
nasal_ref args=local[2];
if(funcptr.type!=vm_obj || funcptr.obj()->type!=obj_extern)
return builtin_err("dlcall","\"funcptr\" is not a correct function pointer");
typedef nasal_ref (*extern_func)(std::vector<nasal_ref>&,nasal_gc&);
extern_func func=(extern_func)funcptr.obj()->ptr;
return func(args.vec()->elems,gc);
}
#endif

View File

@ -65,6 +65,8 @@ var math=
{
e: 2.7182818284590452354,
pi: 3.14159265358979323846264338327950288,
inf: 1/0,
nan: 0/0,
sin: func(x) {return __builtin_sin(x); },
cos: func(x) {return __builtin_cos(x); },
tan: func(x) {return __builtin_tan(x); },
@ -73,8 +75,6 @@ var math=
ln: func(x) {return __builtin_ln(x); },
sqrt: func(x) {return __builtin_sqrt(x); },
atan2: func(x,y){return __builtin_atan2(x,y);},
inf: 1/0,
nan: 0/0,
isnan: func(x) {return __builtin_isnan(x); }
};
var D2R=math.pi/180;
@ -112,4 +112,12 @@ var unix=
environ: func(){die("not supported yet");},
getcwd: func(){return __builtin_getcwd();},
getenv: func(envvar){return __builtin_getenv(envvar);}
};
var dylib=
{
dlopen: func(libname){return __builtin_dlopen;},
dlsym: func(lib,sym){return __builtin_dlsym; },
dlclose: func(lib){return __builtin_dlclose; },
dlcall: func(funcptr,args...){return __builtin_dlcall}
};