From 9861ecd03e8c3a4281f142004c32bc263e5cf2af Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Fri, 3 Dec 2021 19:31:03 +0800 Subject: [PATCH] add dylib.dlopen dylib.dlsym dylib.dlclose dylib.dlcall now you could add your own modules into nasal without changing the source code! --- README.md | 303 ++++++++++++++++++++++++++++++++++-------------- lib.nas | 12 +- makefile | 2 +- module/fib.cpp | 13 +++ module/makefile | 6 + nasal.h | 6 + nasal_builtin.h | 77 +++++++++++- stl/lib.nas | 12 +- 8 files changed, 337 insertions(+), 94 deletions(-) create mode 100644 module/fib.cpp create mode 100644 module/makefile diff --git a/README.md b/README.md index c41e3e4..dfdd4d9 100644 --- a/README.md +++ b/README.md @@ -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& 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& 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> ( line 19) + 0x000002af: callfv 0x1 ( line 5) + 0x000002b3: callfv 0x0 ( 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 ( line 5) + 0x0000000f: callfv 0x1 ( line 5) + 0x0000000f: 2044 same call(s) + 0x00000007: callfv 0x1 ( line 2) + 0x00000013: callfv 0x1 ( 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 ( 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> ( line 20) - 0x00000214: callfv 0x1 ( line 16) - 0x00000248: callfv 0x0 ( 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> ( line 19) + 0x000002d2: callfv 0x1 ( line 16) + 0x00000306: callfv 0x0 ( 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 ``` diff --git a/lib.nas b/lib.nas index 5e5d5ff..3aa7e8e 100644 --- a/lib.nas +++ b/lib.nas @@ -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} }; \ No newline at end of file diff --git a/makefile b/makefile index facafb8..873443d 100644 --- a/makefile +++ b/makefile @@ -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 diff --git a/module/fib.cpp b/module/fib.cpp new file mode 100644 index 0000000..83b71db --- /dev/null +++ b/module/fib.cpp @@ -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& 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())}; +} \ No newline at end of file diff --git a/module/makefile b/module/makefile new file mode 100644 index 0000000..7190e7f --- /dev/null +++ b/module/makefile @@ -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 \ No newline at end of file diff --git a/nasal.h b/nasal.h index 5e6b002..b6ff229 100644 --- a/nasal.h +++ b/nasal.h @@ -23,6 +23,12 @@ #include #include +#ifdef _WIN32 +#include +#else +#include +#endif + inline double hex_to_double(const char* str) { double ret=0; diff --git a/nasal_builtin.h b/nasal_builtin.h index 4e9862f..e4b855f 100644 --- a/nasal_builtin.h +++ b/nasal_builtin.h @@ -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] "<& local,nasal_gc& gc) *str.str()=res; return str; } +nasal_ref builtin_dlopen(std::vector& 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& 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& 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& 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_gc&); + extern_func func=(extern_func)funcptr.obj()->ptr; + return func(args.vec()->elems,gc); +} #endif \ No newline at end of file diff --git a/stl/lib.nas b/stl/lib.nas index 5e5d5ff..3aa7e8e 100644 --- a/stl/lib.nas +++ b/stl/lib.nas @@ -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} }; \ No newline at end of file