Sucha's Blog ~ Archive for July, 2019

19年7月13日 周六 23:34

gmp_ffi.lua

上周考虑的 GMP LuaJIT FFI 绑定,最后将 GMP 的绝大部分函数接口导了出来变成了 gmp_ffi,包括整数、浮点、有理数、随机数,以及格式化输出,除了那些 C 直接输出文件包含 FILE * 的部分,后续也可以考虑将标准 C 库的文件操作函数也一并导出来。

另外,还将相关的初始化函数,通过 ffi.gc() 跟 finalizer 关联了起来,就不需要自己做相关的清理工作了。

还有,GMP 的 C 接口,其内部实现,都是带 __gmp 这样前缀的,即便通过 FFI 使用,也很不方便,所以 gmp_ffi 库,将这些接口都重新做了绑定,重命名为类似 mpz_mul() 这样的函数,跟 gmp.h 上通过 C 宏定义出来的接口一样使用。

有了上面的铺垫,一些好玩的东西可以搞起来,比如粗糙的计算素数的程序 test_prime.lua,比如温习一下 RSA 加解密 test_rsa.lua,当前的实现是到处抄的,遥望大学时虽有做这个的大习题,但早就忘了,-.-

贴一下 gmp_ffi.lua 使用的例子吧:

local gmp = require("gmp_ffi")

function gmpffi_test()
   local a = gmp.mpz(11111111111)
   local b = gmp.mpz("999999999999999999999999999999999")
   local c = gmp.mpz("99999")
   gmp.mpz_mul(a, a, b)
   --gmp.mpz_set_ui(c, 1)
   gmp.printf("mpz: a:%Zd, c sign:", a)
   print(string.format("%d, odd:%s", gmp.mpz_sgn(c), gmp.mpz_odd(c)))
   a = gmp.mpz("ff", 16)
   gmp.printf("mpz: value base %Zx\n", a)

   a = gmp.mpf(111111111.111111111)
   b = gmp.mpf("999999999999999999999999999.99999999999999999999999999999999999")
   c = gmp.mpf("9999")
   gmp.mpf_mul(a, a, b)
   gmp.printf("mpf: %Ff, c sign:", a)
   print(gmp.mpf_sgn(c))

   a = gmp.mpq(111111111, 111111111)
   b = gmp.mpq("9999999999999999999999999999999/99999999999999999999999999999999999999")
   c = gmp.mpq(0)
   gmp.mpq_div(a, a, b)
   gmp.printf("mpq: %Qx, c sign:", a)
   print(gmp.mpq_sgn(c))

   local rt = gmp.randinit()
   local cs = gmp.cstring(64)
   gmp.sprintf(cs, "random: %u", gmp.urandomb_ui(rt, 9999999))
   print(gmp.tostring(cs))
end

gmpffi_test()

19年7月6日 周六 23:55

LuaJIT FFI Library

学习使用大数库,比如 GMP,只是使用起来很糟心,比如大整数运算,先要 mpz_init() 最后回收时是 mpz_clear(),没错就是跟 malloc() 和 free() 一样的。就想着能不能稍微改善下易用性,用 C++ 是可以达到目的的,只是牛刀杀鸡,要走编译才能跑的还是太重了,而且 C++ 我也不熟。

就想着不如用 Lua 将其封装起来,之前的经验,是得自己写一个跟 Lua 虚拟机交互数据的接口,一个先进后出塞参数的栈,如果是要凑 table,还要复杂些。但如果使用 LuaJIT 的 FFI 库,就方便多了,具体在 BSD 这边是通过 dlsym 打开共享库,在 LuaJIT 脚本声明 C 接口后,通过 FFI 库直接调用。

能够支持各种 C 的函数接口,比如可变参数,以及各种数据类型,包括可变长度 struct 数据,甚至还有支持 Lua 侧的 gc 对于创建的 cdata 进行 finalizer,让 Lua 的 gc 只管理虚拟机这一侧 C 指针生命周期,API 文档是 http://luajit.org/ext_ffi_api.html

基本使用

具体点的例子,比如声明并调用函数,创建一个 struct 结构,以及申请、释放内存的官方例子:

local ffi = require("ffi")

ffi.cdef[[
int printf(const char *fmt, ...); //声明并调用标准 C 库函数
typedef struct { uint8_t red, green, blue, alpha; } rgba_pixel; //struct 结构声明
]]

ffi.C.printf("Hello %s!", "world")

-- 栈上 struct 结构
local img = ffi.new("rgbg_pixel[?]", 3)
img[0].green = 1

-- 创建、并释放外部内存对象
local p = ffi.gc(ffi.C.malloc(n), ffi.C.free)
...
p = nil -- Last reference to p is gone.
-- GC will eventually run finalizer: ffi.C.free(p)

调用自定义库

如果是自定义库,库路径需加入 LuaJIT 的 package.cpath 中,使用前 ffi.load('myffi') 一下,比如下面的例子看下 ffi.gc 是不是真的有用:

//#include "stdio.h"
//#include "stdlib.h"
void* test_malloc(int n) {
   void *p = malloc(n);
   printf("test_malloc %p:%d\n", p, n);
   return p;
}

void test_free(void *p) {
   printf("test_free %p\n", p);
   free(p);
}

先在本目录生成一个共享库:

$ clang -g -o libmyffi.dylib -fpic -shared myffi.c

在 LuaJIT 脚本侧这样使用:

local ffi = require("ffi")
local myffi = ffi.load("myffi")
ffi.cdef[[
void* test_malloc(int n);
void test_free(void *p);
]]
local p = ffi.gc(myffi.test_malloc(10), myffi.test_free)
print( p )
p = nil

会这样输出:

test_malloc 0x7f9962c027c0:10
cdata<void *>: 0x7f9962c027c0
test_free 0x7f9962c027c0

使用 GMP 的例子

先试一下 GMP 提供的大整数接口:

local ffi = require("ffi")
local gmp = ffi.load("gmp")

ffi.cdef[[
int printf(const char *fmt, ...);

typedef unsigned long long int	mp_limb_t;

typedef struct
{
  int _mp_alloc;		/* Number of *limbs* allocated and pointed
				   to by the _mp_d field.  */
  int _mp_size;			/* abs(_mp_size) is the number of limbs the
				   last field points to.  If _mp_size is
				   negative this is a negative number.  */
  mp_limb_t *_mp_d;		/* Pointer to the limbs.  */
} __mpz_struct;

typedef const __mpz_struct *mpz_srcptr;
typedef __mpz_struct *mpz_ptr;
typedef __mpz_struct mpz_t[1];

void __gmpz_init(mpz_ptr);
int __gmpz_set_str(mpz_ptr, const char *, int);
void __gmpz_mul(mpz_ptr, mpz_srcptr, mpz_srcptr);

void __gmpz_clear(mpz_ptr);
int __gmp_printf (const char *, ...);
]]

ffi.C.printf("input n1 x %s:\n", "n2")

local arg1, arg2 = ...
if not arg or not arg2 then
   os.exit(0)
end

local n1 = ffi.new("mpz_t")
local n2 = ffi.new("mpz_t")
local n3 = ffi.new("mpz_t")

gmp.__gmpz_init(n1)
gmp.__gmpz_init(n2)
gmp.__gmpz_init(n3)

gmp.__gmpz_set_str(n1, arg1, 10)
gmp.__gmpz_set_str(n2, arg2, 10)

gmp.__gmpz_mul(n3, n1, n2)

gmp.__gmp_printf("n1 = %Zd\n", n1)
gmp.__gmp_printf("n2 = %Zd\n", n2)
gmp.__gmp_printf("n1 * n2 = %Zd\n", n3)

gmp.__gmpz_clear(n1)
gmp.__gmpz_clear(n2)
gmp.__gmpz_clear(n3)

输出是:

$ luajit test_ffi.lua 11111111111111111111111 99999999999999999999999999999999
input n1 x n2:
n1 = 11111111111111111111111
n2 = 99999999999999999999999999999999
n1 * n2 = 1111111111111111111111099999999988888888888888888888889

后面再看下怎么封装 GMP 的接口,毕竟这么使用,只是少了一次编译的时间,接口函数还是太复杂。