7

我打算创建一个优化的数据结构来保存汇编代码。这样我就可以完全负责将在这个结构上工作的优化算法。如果我可以在运行时编译。这将是一种动态执行。这可能吗?有没有人见过这样的东西?

我应该使用结构将结构链接到程序流中吗?对象更好吗?

struct asm_code {
   int type;
   int value;
   int optimized;
   asm_code *next_to_execute;
 } asm_imp;

更新:我认为它会变成一个链表。

更新:我知道那里还有其他编译器。但这是一个军事绝密项目。所以我们不能相信任何代码。我们必须自己做这一切。

更新:好的,我想我只会生成基本的 i386 机器代码。但是,当它完成后,我如何跳转到我的内存 blob 中呢?

4

5 回答 5

8

有可能的。在软件渲染和图形等某些领域,动态代码生成甚至是主流。你发现在各种脚本语言中都有很多用处,在机器码中字节码的动态编译(.NET、Java,据我所知 Perl。最近 JavaScript 也加入了俱乐部)。

您还发现它也用于数学非常繁重的应用程序中,例如,如果您打算进行数千次这样的乘法运算,则从矩阵乘法中删除所有为零的乘法会有所不同。

我强烈建议您阅读代码的 SSA 表示。这是一种表示,其中每个原语都转换为所谓的三操作数形式,并且每个变量只分配一次(因此是相同的静态单一赋值形式)。

您可以对此类代码进行高阶优化,并且可以直接将该代码转换为可执行代码。不过,您不会在周末编写代码生成后端...

要了解 SSA 的外观,您可以尝试 LLVM 编译器。在他们的网站上,他们有一个小的“试用”小部件可以玩。您将 C 代码粘贴到一个窗口中,您会得到一些接近 SSA 表单的内容。

小例子它的样子:

让我们在 C 中使用这个整数平方根算法。(任意示例,我只是举了一些简单但不平凡的东西):

unsigned int isqrt32 (unsigned int value) 
{
    unsigned int g = 0;
    unsigned int bshift = 15;
    unsigned int b = 1<<bshift;
    do {
        unsigned int temp = (g+g+b)<<bshift;
        if (value >= temp) {
            g += b;
            value -= temp;
        }
        b>>=1;
    } while (bshift--);
    return g;
}

LLVM 把它变成:

define i32 @isqrt32(i32 %value) nounwind  {
entry:
    br label %bb

bb:     ; preds = %bb, %entry
    %indvar = phi i32 [ 0, %entry ], [ %indvar.next, %bb ]      
    %b.0 = phi i32 [ 32768, %entry ], [ %tmp23, %bb ]
    %g.1 = phi i32 [ 0, %entry ], [ %g.0, %bb ]     
    %value_addr.1 = phi i32 [ %value, %entry ], [ %value_addr.0, %bb ]      
    %bshift.0 = sub i32 15, %indvar 
    %tmp5 = shl i32 %g.1, 1 
    %tmp7 = add i32 %tmp5, %b.0 
    %tmp9 = shl i32 %tmp7, %bshift.0    
    %tmp12 = icmp ult i32 %value_addr.1, %tmp9      
    %tmp17 = select i1 %tmp12, i32 0, i32 %b.0      
    %g.0 = add i32 %tmp17, %g.1     
    %tmp20 = select i1 %tmp12, i32 0, i32 %tmp9     
    %value_addr.0 = sub i32 %value_addr.1, %tmp20           
    %tmp23 = lshr i32 %b.0, 1       
    %indvar.next = add i32 %indvar, 1       
    %exitcond = icmp eq i32 %indvar.next, 16    
    br i1 %exitcond, label %bb30, label %bb

bb30:       ; preds = %bb
    ret i32 %g.0
}

我知道一开始看起来很可怕。它甚至不是纯粹的 SSA-Form。您对该表示的阅读越多,它就越有意义。而且您还会发现为什么这种表示法如今被如此广泛地使用。

将您需要的所有信息封装到数据结构中很容易。最后,您必须决定是否要使用枚举或字符串作为操作码名称等。

顺便说一句 - 我知道我没有给你数据结构,而是更正式但实用的语言以及进一步研究的建议。

这是一个非常好的和有趣的研究领域。

编辑:在我忘记之前:不要忽视 .NET 和 Java 的内置功能。这些语言允许您从程序内部的字节码或源代码编译并执行结果。

干杯,尼尔斯


关于您的编辑:如何使用代码执行二进制 blob:

跳转到二进制 blob 取决于操作系统和平台。简而言之,您使指令缓存无效,也许您必须写回数据缓存,并且您可能必须在您已将代码写入的内存区域上启用执行权限。

在 win32 上它相对容易,因为如果将代码放在堆上,指令缓存刷新似乎就足够了。

您可以使用此存根开始:

typedef void (* voidfunc) (void);

void * generate_code (void)
{
    // reserve some space
    unsigned char * buffer = (unsigned char *) malloc (1024);


    // write a single RET-instruction
    buffer[0] = 0xc3; 

    return buffer;
}

int main (int argc, char **args)
{
    // generate some code:
    voidfunc func = (voidfunc) generate_code();

    // flush instruction cache:
    FlushInstructionCache(GetCurrentProcess(), func, 1024);

    // execute the code (it does nothing atm)
    func();

    // free memory and exit.
    free (func);
}
于 2008-09-20T15:31:55.837 回答
1

我假设你想要一个数据结构来保存某种指令模板,可能是从现有的机器代码中解析出来的,类似于:

add r1, r2, <int>

您将拥有一个这种结构的数组,您将对这个数组进行一些优化,可能会更改其大小或构建一个新数组,并生成相应的机器代码。

如果您的目标机器使用可变宽度指令(例如 x86),则您无法确定数组大小,而无需实际完成对构建数组的指令的解析。此外,在从优化数组实际生成所有指令之前,您无法准确确定需要多少缓冲区。不过,您可以做出一个很好的估计。

查看GNU 闪电。它可能对你有用。

于 2008-09-20T16:05:51.933 回答
0

在 99% 的情况下,性能差异可以忽略不计。类的主要优点是 OOP 生成的代码比过程代码更好、更容易理解。

我不确定您使用哪种语言进行编码 - 请注意,在 C# 中,类和结构之间的主要区别在于结构是值类型,而类是引用类型。在这种情况下,您可能希望从结构开始,但仍向它们添加行为(构造函数、方法)。

于 2008-09-20T15:34:25.987 回答
0

不讨论优化代码的技术价值,在 C++ 代码中,在 POD 结构或完整对象之间进行选择主要是一个封装点。

内联代码将使编译器优化(或不优化)使用的构造函数/访问器。不会有性能损失。

首先,设置一个构造函数

如果您使用的是 C++ 编译器,请至少创建一个构造函数:

struct asm_code {
   asm_code()
      : type(0), value(0), optimized(0) {}

   asm_code(int type_, int value_, int optimized_)
      : type(type_), value(value_), optimized(_optimized) {}

   int type;
   int value;
   int optimized;
 };

至少,您的代码中不会有未定义的结构。

每种数据组合都可能吗?

使用像您使用的结构意味着任何类型都是可能的,具有任何值和任何优化。例如,如果我设置 type = 25, value = 1205 并且优化 = -500,那么就可以了。

如果您不希望用户将随机值放入您的结构中,请添加内联访问器:

struct asm_code {

   int getType() { return type ; }
   void setType(int type_) { VERIFY_TYPE(type_) ; type = type_ ; }

   // Etc.

   private :
      int type;
      int value;
      int optimized;
 };

这将使您可以控制结构中设置的内容,并更轻松地调试代码(甚至对代码进行运行时验证)

于 2008-09-20T15:37:04.343 回答
0

经过一番阅读,我的结论是 common lisp 最适合这项任务。使用 lisp 宏,我拥有巨大的力量。

于 2011-02-16T20:31:22.960 回答