5

我正在开发一种为其自己的虚拟机编译的脚本语言,这是一种简单的语言,它具有处理某种数据(如向量浮点数等)的指令。内存单元以这种方式表示:

struct memory_cell
{
    u32 id;
    u8 type;

    union
    {
        u8 b; /* boolean */
        double f; /* float */
        struct { double x, y, z; } v; /* vector */
        struct { double r, g, b; } c; /* color */
        struct { double r, g, b; } cw; /* color weight */
        struct { double x, y, z; } p; /* point variable */
        struct { u16 length; memory_cell **cells; } l; /* list variable */
    };  
};

指令是通用的,能够处理许多不同的操作数。例如

ADD dest, src1, src2

可以使用浮点数、向量、点、颜色根据操作数设置正确的目标类型。

主执行周期只是检查指令的操作码(这是一个包含联合来定义任何类型指令的结构)并执行它。我使用了一种简化的方法,其中没有寄存器,只有一大堆存储单元。

我想知道 JIT 是否可以帮助我获得最佳性能以及如何实现它。

正如我所说,到目前为止达到的最佳实现是这样的:

 void VirtualMachine::executeInstruction(instr i)
 {
     u8 opcode = (i.opcode[0] & (u8)0xFC) >> 2;

     if (opcode >= 1 && opcode <= 17) /* RTL instruction */
     {
        memory_cell *dest;
        memory_cell *src1;
        memory_cell *src2;

        /* fetching destination */
        switch (i.opcode[0] & 0x03)
        {
            /* skip fetching for optimization */
            case 0: { break; }
            case MEM_CELL: { dest = memory[stack_pointer+i.rtl.dest.cell]; break; }
            case ARRAY_VAL: { dest = memory[stack_pointer+i.rtl.dest.cell]->l.cells[i.rtl.dest.index]; break; }
            case ARRAY_CELL: { dest = memory[stack_pointer+i.rtl.dest.cell]->l.cells[(int)i.rtl.dest.value]; break; }
        }

     /* omitted code */

     switch (opcode)
     {
         case ADD:
         {
             if (src1->type == M_VECTOR && src2->type == M_VECTOR)
             {
                 dest->type = M_VECTOR;
                 dest->v.x = src1->v.x + src2->v.x;
                 dest->v.y = src1->v.y + src2->v.y;
                 dest->v.z = src1->v.z + src2->v.z;
              }

      /* omitted code */

尝试 jit 编译是否容易/方便?但我真的不知道从哪里开始,这就是为什么我要问一些建议。

除此之外,在开发它时我还应该考虑其他建议吗?

这个虚拟机应该足够快来计算光线追踪器的着色器,但我还没有做过任何基准测试。

4

3 回答 3

7

在编写 JIT(“Just-in-time”)编译器之前,您至少应该考虑如何编写“Way-ahead-of-time”编译器。

也就是说,给定一个包含 VM 指令的程序,您将如何生成一个包含 x86(或其他)指令的程序,它与原始程序的功能相同?您将如何优化不同指令集和同一架构的不同版本的输出?您给出的示例操作码具有相当复杂的实现,那么您将通过发出执行该工作的代码来实现哪些操作码“内联”,以及通过发出对某些共享代码的调用来实现哪些操作码?

JIT 必须能够做到这一点,并且它还必须在 VM 运行时就它执行哪些代码、何时执行以及它如何表示 VM 指令和本机指令的混合结果做出决定。

如果您还不是装配骑师,那么我不建议您编写 JIT。这并不是说“永远不要这样做”,而是在认真开始之前,您应该成为一名装配骑师。

正如 Jeff Foster 所说,另一种方法是编写一个非 JIT 编译器来将您的 VM 指令(或原始脚本语言)转换为 Java 字节码或 LLVM。然后让该字节码的工具链完成困难的、依赖于 CPU 的工作。

于 2009-11-28T14:05:29.360 回答
6

虚拟机是一项需要考虑的大任务。您是否考虑过将您的 VM 基于LLVM 之类的东西?

LLVM 将提供一个良好的起点,并且有大量示例项目可供您用于理解。

于 2009-11-28T12:20:10.713 回答
3

Steve Jessop 有一个观点:JIT 编译器比普通编译器更难。普通的编译器本身就很难。

但是,阅读问题的最后一部分,我想知道你是否真的想要一个 JIT 编译器。

如果你的问题是这样的:

我想创建一个光线追踪程序,允许用户使用我自己的领域特定语言提供他们的着色器程序等。一切顺利。我定义了我的语言,实现了解释器,它运行良好且正确。但它很慢:我怎样才能将它作为本机代码执行?

然后这是我以前做的类似情况:

  • 将您的用户提供的过程翻译成可以从您的程序中调用的 C 函数。

  • 使用正确的#includes 等将它们写到普通的 C 源文件中。

  • 使用普通的 C 编译器将它们编译为 .dll(或 *nix 中的 .so)。

  • 在程序中动态加载 .dll,找出函数指针并在光线追踪器中使用它们来代替解释版本。

一些注意事项:

  • 在某些环境中,这可能是不可能的:无法访问禁止您加载自己的 dll 的 C 编译器或系统策略。所以在你尝试之前检查一下。

  • 不要丢弃您的口译员。将其保留为您的语言的参考实现。

于 2009-11-28T14:36:41.713 回答