跳至内容

Lush/编译

来自维基教科书,开放世界中的开放书籍

Lush 是一种解释型语言,但单个函数和类方法可以编译,要么是为了加快速度,要么是为了启用内联 C/C++ 代码。

编译纯 Lush 函数

[编辑 | 编辑源代码]

函数和类方法可以编译,只需进行一些修改。以下是一个我们想要编译的简单函数

 ;; Prints the size, sum, and mean of a vector
 (de print-mean (vec)
   (let* ((v-size (idx-dim vec 0))
          (v-sum (idx-sum vec)
          (v-mean (/ v-sum v-size))
     (printf "Sum = %f, size = %d, mean = %f\n" v-sum v-size v-mean)
     ())

在下面,我们添加了一些类型声明来使函数可编译,并在其后添加了编译命令

 ;; Prints the size, sum, and mean of a vector
 (de print-mean (vec)
   ((-idx1- (-double-)) vec)
   (let* ((v-size (idx-dim vec 0))
          (v-sum (idx-sum vec)
          (v-mean (/ v-sum v-size))
     ((-int-) v-size)
     (printf "Sum = %f, size = %d, mean = %f\n" v-sum v-size v-mean)
     ())
 
 ; compile "print-mean"
 (dhc-make () print-mean)

要使 Lush 函数可编译,必须

  • 声明任何类型不明确的对象的类型。
  • 仅使用可编译函数和表达式。
  • 在源文件末尾的编译语句中列出该函数。

声明类型

[编辑 | 编辑源代码]

在上面的函数中,紧随参数之后的几行声明了参数类型。请注意,该函数没有声明其他任何变量的类型。这是因为您只需要为函数参数和非双精度数值类型声明类型。 这是因为除非指定,否则函数参数类型在编译时并不明显,而 Lush 假定所有数字都是 double,除非另行说明。

所有类型声明都采用以下形式

((type_name) var_name)

例如

((-int-) my-int)

以下列出了可编译 Lush 类型的类型名称,以及它们相应的 C++ 等效类型

Lush 类型 C++ 类型 描述
-int- int 32 位整数
-float- float 32 位实数
-double- double 64 位实数
-short- short 16 位整数
-byte- char 8 位整数
-ubyte- unsigned char 8 位无符号整数
-gptr- void* 类型无关指针(大小取决于平台)。
-gptr- "std::string" std::string* 类型特定指针(大小与 -gptr-/void* 相同)
-str- ? Lush 字符串
-idx- (-double-) ? Lush 张量(-double- 可以是任何基本类型名称。)
-obj- (htable) ? 任何其他类(htable 是 Lush 哈希表的类名)。

* 张量不能包含类型特定指针。换句话说,以下情况不允许

;; NOT ALLOWED: a tensor of char* pointers
((-idx1- (-gptr- "char")) my-char-pointer-tensor)

但以下情况可以

;; A tensor of void* pointers
((-idx2- (-gptr-)) my-pointer-tensor)

仅使用可编译函数

[编辑 | 编辑源代码]

不幸的是,并非所有 Lush 函数或表达式都可编译。大多数不可编译的函数是那些涉及列表操作的函数。更一般地说,Lush 的任何“Lisp 式”特性(lambda 表达式、钩子、代码操作)通常都不可编译。要查看特定函数是否可编译,请使用 compilablep 函数

 ? (compilablep double-matrix)
 = t
 ? (compilablep range)
 = ()

请注意,compilablep 只能接受单个函数和宏名称,不能接受复杂的 Lush 表达式。

虽然列表操作通常不可编译,但包含列表的表达式不一定不可编译。例如,idx-transclone 函数必须接受列表作为其参数之一,但仍然可编译。

添加编译命令

[编辑 | 编辑源代码]

在使您的函数适合编译后,您必须在源文件末尾调用编译函数才能实际编译它们。以下是一个函数和两个类方法,后面是编译函数 dhc-make

 (de some-func (...)
   ... )
 
 (de my-class object
   ... )
 
 (defmethod my-class method-1 (...)
   ... )
 
 (defmethod my-class method-2 (...)
   ... )
 
 (dhc-make ()
           (some-func)
           (my-class method-1
                     method-2))

编译函数实际上执行 3 件事

  • 将函数/方法转录成等效的 C 代码。src-dir/src-file.lsh 文件的 C 源文件将位于 src-dir/C/src_file.c 中。
  • 使用 gcc 将此 C 源文件编译成目标文件。
  • 将生成的目標文件链接到内存中。

只有当 C 源文件比 Lush 源文件旧时,编译函数才会运行。

混合使用 C/C++ 代码

[编辑 | 编辑源代码]

您可以使用 #{ ... #} 结构将 C/C++ 代码插入 Lush 函数中。您可以非常密集地交织 Lush 和原生代码

从 C/C++ 代码访问 Lush 数据

[编辑 | 编辑源代码]

下表显示了如何从 C/C++ 代码访问不同的 Lush 对象

类型 Lush 名称 C/C++ 名称
基本类型 my-double $my_double
张量类型 my-float-tensor float* raw_data = IDX_PTR( $my_float_tensor, float )
字符串 my-str char* c_string = $my_str->data
对象槽 :my-obj:some-slot $my_obj->some_slot
对象方法 (==> my-obj some-method arg1) $my_obj->vtbl->M_some_method($arg1)
全局函数 (add-numbers num1 num2) C_add_numbers($num1, $num2)
全局常量 @MY_CONSTANT MY_CONSTANT

不同的编译函数

[编辑 | 编辑源代码]

dhc-make-with-libs

[编辑 | 编辑源代码]

dhc-make-with-c++

[编辑 | 编辑源代码]

编译函数中的 C 代码

[编辑 | 编辑源代码]

您可以将预处理器语句和其他 C 代码直接放入编译函数中

#include 和 #define

[编辑 | 编辑源代码]

调用 Lush 函数的 C 函数

[编辑 | 编辑源代码]

如果有很多这样的函数,您可以将它们放在 .h 和 .c 文件中,以提高可读性

 (dhc-make ()
           #{
           #include "c_funcs.h"
           #}
           
           lush-func-1
           lush-func-2
           ''...''
           
           #{
           #include "c_funcs.c"
           #})

链接到库

[编辑 | 编辑源代码]

libload 可能返回 null;请注意。

编译和链接 C/C++ 源文件

[编辑 | 编辑源代码]

lushmake。正确的 lushmake 语法?我使用的方式是,它不理会依赖关系。

隐藏参数的问题

[编辑 | 编辑源代码]

将lush函数编译成C代码的一个问题是,你在函数中实例化的任何lush对象都会成为C版本函数中的参数。这些只在lush函数的C版本中出现的参数被称为“隐藏参数”。lush函数的签名(例如它接受的参数数量和类型)可能会发生变化。这是因为

布尔值

[编辑 | 编辑源代码]

没有void 返回类型

[编辑 | 编辑源代码]

返回nil(也称为())的lush函数将在其C化身中返回一个char。当您确实需要您的函数不返回任何内容时,这可能是一个问题,例如当将其用作某些期望void返回函数的C API 的回调函数时

my-src.lsh 中返回nil 的函数

 (de my-callback ()
   ...
   ())

...在C/my_src.c 中变成了一个返回char 的函数

 char* C_my_callback(){
 ...
 }

在这种情况下,您最好的选择是在一个返回void的C函数中包装lush函数。那么问题是:如何编译这个包装函数?最简单的方法是在my-src.lsh 结尾的编译函数中直接放入它

 (dhc-make ()
           #{ void callback_wrapper(); #}
           my-callback
           ''... other lush functions & methods ...''
           ''...''
           ''...''
           #{ void callback_wrapper(){ C_my_callback(); } #} )

栈还是堆?:

没有cout

[编辑 | 编辑源代码]

包装的C++类的动态分配:类型转换错误

编译之前要考虑的问题

[编辑 | 编辑源代码]

在决定是否真的要编译lush代码之前,有几个问题需要考虑。

to be compiled can be more of a hassle than writing the same code in c/c++, for a number of reasons:
  • 可编译的Lush不如C++功能丰富或视觉上整洁
  • Lush编译器错误可能比C/C++编译器错误更加不透明,尤其是在涉及宏的情况下。
  • 调试能力的损失
华夏公益教科书