我打算创建一个优化的数据结构来保存汇编代码。这样我就可以完全负责将在这个结构上工作的优化算法。如果我可以在运行时编译。它将是一种动态执行。这可能吗?有人见过这样的东西吗?
我应该使用结构将结构链接到程序流程中。对象更好吗?
struct asm_code {
int type;
int value;
int optimized;
asm_code *next_to_execute;
} asm_imp;
更新:我认为它会像链接列表一样。
更新:我知道还有其他编译器。但这是一个军事绝密项目。所以我们不能相信任何代码。我们必须自己做这一切。
更新:好的,我想我只会生成基本的i386机器代码。但是如何在完成后跳入我的内存blob?
答案 0 :(得分:7)
有可能。动态代码生成甚至是软件渲染和图形等领域的主流。您可以在各种脚本语言中找到很多用法,在机器代码中动态编译字节码(.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表格。你对这种表现的阅读越多,它就越有意义。而且你也会发现为什么这种表现形式如今被广泛使用。
将所需的所有信息封装到数据结构中非常简单。最后,您必须决定是否要使用枚举或字符串作为操作码名称等。
Btw - 我知道我没有给你一个数据结构,但更多的是一个正式但实用的语言,并建议在哪里进一步观察。这是一个非常好的和有趣的研究领域。
编辑:在我忘记它之前:不要忽视.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);
}
答案 1 :(得分:1)
我假设你想要一个数据结构来保存某种指令模板,可能是从现有的机器代码中解析出来的,类似于:
add r1, r2, <int>
您将拥有此结构的数组,您将对此数组执行一些优化,可能会更改其大小或构建新数组,并生成相应的机器代码。
如果您的目标计算机使用可变宽度指令(例如x86),则无法在不实际解析构建阵列的指令的情况下确定数组大小。在实际生成优化数组的所有指令之前,您无法确切地确定需要多少缓冲区。你可以做一个很好的估计。
结帐GNU Lightning。它可能对你有用。
答案 2 :(得分:0)
在99%的案例中,表现差异可以忽略不计。类的主要优点是OOP生成的代码比过程代码更好,更容易理解。
我不确定你编码的语言是什么 - 请注意,在C#中,类和结构之间的主要区别在于结构是值类型而类是引用类型。在这种情况下,您可能希望从结构开始,但仍然向它们添加行为(构造函数,方法)。
答案 3 :(得分: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,那么它就是Ok。
如果您不希望用户在结构中放置随机值,请添加内联访问者:
struct asm_code {
int getType() { return type ; }
void setType(int type_) { VERIFY_TYPE(type_) ; type = type_ ; }
// Etc.
private :
int type;
int value;
int optimized;
};
这将允许您控制结构中设置的内容,并更轻松地调试代码(甚至可以对代码进行运行时验证)
答案 4 :(得分:0)
经过一番阅读,我的结论是普通的lisp最适合这项任务。 使用lisp宏我有巨大的力量。