C++ Coder

HCP高性能计算架构,实现,编译器指令优化,算法优化, LLVM CLANG OpenCL CUDA OpenACC C++AMP OpenMP MPI

C++博客 首页 新随笔 联系 聚合 管理
  98 Posts :: 0 Stories :: 0 Comments :: 0 Trackbacks

http://blog.csdn.net/banyao2006/article/details/7045216

摘要

 

该文档是LLVM汇编语言的参考指南。LLVM是基于表示的静态单赋值(SSA),该表示提供类型安全、低层级操作,灵活性,及简洁表示所有高层级语言的能力。这是贯穿各方面LLVM编译策略的通用代码表示。

 

         简介

 

LLVM代码表示用于三个不同形式:作为在内存(in-memory)编译器IR、磁盘比特码表示(适合即时编译器的快速加载),以及人类可读的汇编语言表示。这让LLVM为高效编译器转换和分析提供强大的中间表示的同时,提供调试和转换可视化的自然手段。LLVM的三种不同形式是等价的。该文档描述了人类可读的表示和注解。

LLVM表示旨在具有易于表达、类型化和扩展性的同时保持轻量级和低层级。它通过处于可清晰映射高层想法的足够低的层级(类似于微处理器允许许多源语言映射其上而成为通用IR),使其成为通用IR。通过提供类型信息,LLVM可用于优化的目的:例如,通过指针分析,可证明C自动变量永远不会访问当前函数以外的部分...允许其使用简单的SSA以代替内存位置。

 

规范化

记住,该文档描述的是规范化的LLVM汇编语言,这很重要。语法分析器的接受范围与规范化之间有所差异。例如,下列指令句法通顺,但不够规范化:

                                           %x = add i32 1, %x

因为%x的定义不能控制自身的所有使用。LLVM基础架构提供验证LLVM模块是否规范化的验证pass。在语法分析输入的汇编后由语法分析器和在其输出比特码前由优化器自动运行该pass。验证pass指出的违反行为指名了转换pass或语法分析器输入的bug

标识符

LLVM标识符具有两种基本类型:全局和局部。全局标识符(函数,全局变量)以'@'起始。局部标识符(寄存器名字,类型)以'%'起始。另外,标识符针对不同目的有三种不同格式:

1 命名值表示为字符串及其前缀。例如,%foo, @DivisionByZero, %a.really.long.identifier。实际的正则表达式是'[%@][a-zA-Z$._][a-zA-Z$._0-9]*'。在其名字里需要其它字符的标识符会被引用环绕。特殊字符可使用"\xx"表示,其中xx是字符ASCII码的16进制表示。这样,所有字符可用于命名值,甚至引用自身。
2
未命名值利用无符号数值及其前缀表示。例如,%12, @2, %44
3
常量,在下面的章节常量中描述。

LLVM需要以前缀起始取值有两个原因:编译器无需担心保留字的命名冲突,而且保留字的集合可在未来无惩罚地扩展。另外,未命名标识符允许编译器无需刻意避免符号表冲突而快速使用临时变量。

LLVM保留字与其它语言非常相似。关键字包括不同操作码(add bitcastret等),基本类型名(voidi32等)和其它。这些保留字不会与变量名冲突,因为前者都不以前缀字符('%''@')起始。

这是整数变量'%x'乘以8LLVM代码示例。

简单方式:

%result = mul i32 %x, 8

简化之后:

%result = shl i32 %x, i8 3

复杂方式:

%0 = add i32 %X, %X           ; 生成 {i32}:%0

%1 = add i32 %0, %0           ; 生成 {i32}:%1

%result = add i32 %1, %1

最后一种实现%x乘以8的方式表明LLVM许多重要词法特点:

1.注释以';'界定,直到行尾。

2.当计算结果未赋值给命名变量时,创建未命名临时变量。

3.未命名临时变量顺序编号。

还表明该文档中遵循的习惯。当展示指令时,指令之后是定义生成值类型和命名的注释。注释以italic文本呈现。

高层结构

模块结构

LLVM程序是由"模块"组成的,每个模块是输入程序的一个转换单元。每个模块包括函数、全局变量和符号表入口。模块可由LLVM链接器组合在一起,这将合并函数(全局变量)的定义,解析之后的声明,并合并符号表入口。这是"hello world"模块的示例:

; 声明string常量作为全局常量

@.LC0 = internal constant [13 x i8] c"hello world\0A\00"          ; [13 x i8]*

; puts函数的外部声明

declare i32 @puts(i8 *)                                           ; i32(i8 *)*

; main函数的定义

define i32 @main() {                                              ; i32()*

        ; Convert [13 x i8]* to i8  *...

        %cast210 = getelementptr [13 x i8]* @.LC0, i64 0, i64 0   ; i8 *

        ; Call puts function to write out the string to stdout...

        call i32 @puts(i8 * %cast210)                             ; i32

        ret i32 0

}

本例由全局变量".LC0"puts函数的外部声明和main的函数定义组成。

通常地,模块由一系列的全局值组成,其中函数和全局变量都是全局值。全局值以指向内存位置的指针(因此,指向字符数组的指针,指向函数的指针)表示,并具有下列链接类型的一种。

链接类型

所有全局变量和函数具有下列链接类型之一:

private 具有私有链接的全局值只能被当前模块的对象直接访问。特别地,具有私有全局值模块的链接代码可能在必要时导致对私有部分重命名,以避免冲突。由于该符号对模块是私有的,所有引用会被更新。这并不会在目标文件的任何符号表有所显示。

linker_private 类似私有链接,但该符号通过汇编器并由链接器在求值后移除。

internal 类似私有链接,但其值在目标文件中显示为局部符号(ELF文件格式的STB_LOCAL)。这对应C语言的static关键字。

available_externally 具有"有效外部"链接的全局值不会引入到LLVM模块对应的目标文件。它们的存在是为进行内联和其它优化时告知位于模块外的全局值定义具体位置。具有有效外部链接的全局值可随意丢弃,否则与linkconce_odr链接类型一样。该链接类型只能定义不能声明。

linkonce 具有"仅链接一次"链接的全局值在链接发生时,与同名的其它全局值合并。这典型地用实现内联函数,模板或在每个使用该链接的转换单元中生成的其它代码。未引用的linkonce全局值可丢弃。

weak ""链接与linkonce链接具有相同的合并语法,除未引用的弱链接全局值不丢弃外。这用于C源码中声明为"weak"的全局值。

common "通用"链与""链接极其相似,但它们用于C的定义,例如全局作用域的"int X;""通用"链接的符号与weak符号一样,以同样的方式合,而且若未引用,该链接可能也不会删除。通用符号可没有显式段,但必须零初始化,且可能未标记为'constant'。函数和别名可能没有通用链接。

appending "附加"链接只用于指向数组类型的全局变量。两个具有appending链接的全局变量链接在一起时,两个全局数组附加在一起。这就是在链接.o文件时LLVM系统链接器将相同名称的附加在一起的类型安全和等性。

extern_weak 该链接的语法含义遵循ELF目标文件模型:该符号直到进行链接是才是weak类型,如果未链接,该符号会用null替代未定义的引用。

linkonce_odr

weak_odr 一些语言允许不同的全局值进行合并,例如不同语义的两个函数。其它语言,例如C++,保证只有等价的全局值才能合并("一次定义规则"-"ODR")。这样的语言可使用linkonce_odrweak_odr链接类型以表明只有等价的全局值才能合并。这些链接类型在其它方面与非odr版本具有相同属性。

externally 若以上标识符都没有使用,全局值则是外部可见的,这意味全局值参与链接并可用于解析外部符号引用。

以下两种链接类型仅针对微软Windows平台。它们为支持从DLLDynamic Link Library)引入符号(或导出符号至DLL)而设计。

dllimport "dllimport"链接导致编译器通过由DLL导出符号建立的指向指针的全局指针,引用函数或变量。

dllexport  "dllexport"链接导致编译器提供DLL中指向指针的全局指针,以使该全局值可利用dllimport属性引用。在微软Windows目标平台,指针通过联合_imp_和函数或变量名的形式命名。

例如,".LC0"变量定义为interal,如果其它模块定义了".LC0"变量且该变量被链接,两者之一将被重命名以避免冲突。既然"main""puts"external类型(比如缺少链接声明),它们可在当前模块外被访问。

除了"外部可见"dllimportextern_weak外,函数声明的其它链接类型都是不合法的。

别名仅有external, internal, weakweak_odr链接。

调用约定

LLVM的函数,callinvoke都可为指定的调用设定可选的调用约定。动态调用者/被调用者配对的调用约定必须匹配,否则程序行为未定义。LLVM支持下列调用约定,且可能在将来有所增加。

"ccc" - C调用约定

该调用约定(如果没有指定其它调用约定,默认使用该调用约定)匹配目标平台的C调用约定。它支持可变参数(varargs)函数调用,并容忍函数原型声明和声明实现的一些误配(就像正常C行为)。

"fastcc" - 快速调用约定

该调用约定试图使调用尽可能快(例如通过寄存器传值)。它允许目标平台使用任何小技巧以产生该平台的快速代码,而无需与外部指定的ABIApplication Binary Interface)保持一致。其实现支持任意的推后调用优化(tail call optimization)。它不支持可变参数,且要求所有被调用者的原型与函数定义的原型完全匹配。

"coldcc" - 生冷的调用约定

该调用约定基于调用不常执行这一假定,试图使调用者的代码尽可能有效。因此,这些调用常保护所有寄存器,以致调用不会破坏调用者边界的任何数据区域。它不支持可变参数,且要求所有被调用者的原型与函数定义的原型完全匹配。

"cc <n>" - 编号约定

所有调用约定可通过编号指定,可用于目标平台相关的调用约定。目标平台相关的调用约定从64开始编号。

更多的调用约定可按需增加/定义,以支持Pascal约定或所有其它熟知的与目标平台无关的约定。

可见性样式

所有全局变量和函数具有下列可见性样式之一:

"default" - 默认样式

在使用ELF目标文件格式的目标平台上,默认可见性意味着声明对其它模块可见,对于共享库意味着声明实体可被覆盖(override)。对于Darwin平台,默认可见性意味着声明可对其它模块可见。默认可见性对应语言的"外部链接"

"hidden" - 隐藏样式

如果具有隐藏样式对象的两个声明位于同一共享目标文件时,它们引用同一目标。隐藏可见性通常表明,符号不会位于动态符号表,因此没有其它模块(可执行或共享库)能直接引用该符号。

"protected" - 保护样式

ELF的保护可见性表明,符号将会位于动态符号表,但在定义模块内的引用会绑定为局部符号。也就是说,该符号不会被其它模块覆盖。

命名类型

LLVM IR允许对确定类型指定别名。这可使IR更可读和更紧凑(condense)(特别是涉及递归类型时)。下面是命名规范的示例:

%mytype = type { %mytype*, i32 }

"void"外可以给定所有类型命名。类型名字别名可用于任何识别到"%mytype"语法的地方。

记住,针对指定结构的类型进行类型名字别名,而且你还能对同一类型指定不同名字。当输出.ll文件时,这常导致混淆的行为。既然LLVM IR使用结构的类型,名字就不是类型的一部分。当输出LLVM IR时,打印器(printer)将挑选一个名字以返回(render)特有形状的所有类型。这意味着,如果对以同一LLVM类型结尾的不同源类型进行编码,输出器(dumper)有时输出"错误"或意外的类型。这是重要的设计观点,且不会改变。

全局变量

全局变量定义了在编译期而非运行期的内存分配区域。它可被任意值初始化,可位于显式段(section),还具有可选的显式指定对齐。变量可定义为"thread_local",这意味它不会被线程共享(每个线程将具有它的独立拷贝)。变量也可定义为全局"常量",这表明它的内容永不会被修改(允许更好的优化,可将全局数据位于执行代码的只读段等)。记住,需要在运行时初始化的变量不能标记为"常量",是因为它有存储空间。

LLVM显式允许全局变量的声明标记为常量,甚至它的最终定义并非如此。这可用于使程序优化效果好一些,但需要语言定义以保证基于'constantness'的优化对并不包含定义的转换单元有效。

SSA赋值,全局变量定义程序中具有所有基本块作用域(例如指针指向的)的指针值。它总定义至其"内容"类型的指针,是因为后者描述了内存区域和可通过指针访问的所有LLVM内存目标。

全局变量可声明位于目标平台相关的计数地址空间。对于支持以上操作的目标平台,地址空间可能会影响优化器的执行和/或可访问该变量的目标平台指令。默认的地址空间是零。地址空间的限制必须处于其它属性之前。

LLVM允许指定针对全局值的显式段。如果目标平台支持的话,LLVM将发射全局值至指定段。

还可对全局值指定显式的对齐。如果没设置,或是对齐设为零,全局值的对齐将设置为目标平台方便的值。如果指定了显式对齐,全局值强制具有至少指定的对齐值。所有对齐必须为2的幂。

例如,下列定义了位于计数地址空间的全局值,具有初始器,段和对齐:

@G = addrspace(5) constant float 1.0, section "foo", align 4

函数

LLVM函数定义由"define"关键字、可选的链接类型、可选的可见样式、可选的调用惯例、返回类型、可选的返回类型参数属性、函数名称、(可能为空的)参数列表(每一个参数都有可选的参数属性)、可选的函数属性、可选的段、可选的对齐、可选的垃圾回收器、打开的花括号、基本块列表和封闭的花括号组成。

LLVM函数声明由"declare"关键字、可选的链接类型、可选的可见样式、可选的调用惯例、返回类型、可选的返回类型参数属性、函数名称、可能为空的参数列表、可选的对齐和可选的垃圾回收器组成。

函数定义包含基本块列表以形成该函数的CFG(控制流图)。每个基本块可选地以标号(给出基本块的符号表入口)起始,包含指令列表,并以终止指令(例如跳转或函数返回)结束。

函数的第一个基本块有两点特殊性:进入函数时直接执行,且不允许具有前趋基本块(例如,函数入口的块不能有任何分支)。因为该块没有前趋,它同样没有任何PHI节点。

LLVM允许为函数指定显式段。如果目标平台支持的话,LLVM将函数置于指定的段。

对函数可指定显式的对齐。如果未设置,或是对齐设置为零,函数的对齐会设置为目标平台方便的值。如果指定显式的对齐,函数强制至少具有指定的对齐。所有对齐必须是2的幂。

语法:

define [linkage] [visibility]

       [cconv] [ret attrs]

       <ResultType> @<FunctionName> ([argument list])

       [fn Attrs] [section "name"] [align N]

       [gc] { ... }

别名

别名作为被别名值(the aliasee value)(可为函数、全局变量、另一个别名或全局值的位转换)的"第二名称"。别名可具有可选的链接类型和可选的可见样式。

语法:

@<Name> = alias [Linkage] [Visibility] <AliaseeTy> @<Aliasee>

参数属性

返回类型和函数类型的每个参数可具有与其相关的参数属性集合。参数属性用于表达函数结果或参数的额外信息。参数属性被认为是函数而非函数类型的一部分,因此具有不同参数属性的函数可具有同一函数类型。

参数属性是遵循指定类型的简单关键字。如果需要多个函数属性,则以空格分隔。例如:

declare i32 @printf(i8* noalias nocapture, ...)

declare i32 @atoi(i8 zeroext)

declare signext i8 @returns_signed_char()

记住,函数结果(nounwind,只读)的任何属性直接位于参数列表后。

当前,只定义了下列参数属性:

zeroext

这表明,代码生成器中调用者或被调用者分别将参数或返回值零扩展为32位值。

signext

这表明,代码生成器中调用者或被调用者分别将参数或返回值符号扩展为32位值。

inreg

这表明,在函数调用或返回生成代码期间(通常,将变量置于与内存相对的寄存器中,但是目标平台使用这种方式以区别寄存器的两种不同类型),参数或返回值被视为处于特殊的目标平台无关方式。该属性的使用是目标平台相关的。

byval

这表明,指针参数应以传值方式传至函数。该属性指出,调用者和被调用者间将导致被指向单元(pointee)的隐形复制,因此被调用者不能修改调用者的值(原文有笔误:so the callee is unable to modify the value in the callee,应为in the caller)。该属性只对LLVM指针参数有效。这通常用于传值方式传递结构和数组,但对指向标量的指针同样有效。上述复制被认为是属于调用者而非被调用者(例如,只读函数不应写具有byval属性的参数)。这不是返回值的有效属性。byval属性也支持由align属性指定的对齐。这对代码生成器有与目标平台相关的影响,这通常表明生成的栈槽(stack slot)需要对齐。

sret

这表明,指针参数指定了源程序的函数返回值结构的地址。该指针必须由调用者保证有效:加载和存储该结构假定被调用者不会进入陷阱(trap)。这只可能应用与第一个参数。这不是返回值的有效属性。

noalias

这表明,该指针不能别名一切全局值或其它任何参数。调用者有责任保证符合上述情况。对于函数返回值,noalias还表明指针不能别名为其它任何对调用者可见的指针。更多的细节请参见别名分析中NoAlias回复的讨论。

nocapture

这表明,被调用者不产生比被调用者本身生命周期长的指针的任何复制。这不是返回值的有效属性。

nest

这表明,指针参数可通过使用trampoline intrinsics引用。这不是返回值的有效属性。

垃圾回收器名称

每个函数可指定垃圾回收器名称,该名称是简单的字符串:

define void @f() gc "name" { ... }

编译器声明name的支持值。指定将导致编译器改变其输出的回收器,是为了支持有名的垃圾回收算法。

函数属性

函数属性为增加函数的附加信息而设置。函数属性被视为函数而非函数类型的一部分,因此具有不同参数属性的函数可具有同一函数类型。

函数属性是遵循指定类型的简单关键字。如果需要多个函数属性,则以空格分隔。例如:

define void @f() noinline { ... }

define void @f() alwaysinline { ... }

define void @f() alwaysinline optsize { ... }

define void @f() optsize

alwaysinline

该属性表明,内联器试图尽可能将函数内联至调用者,而忽略该调用者的任何活跃的内联大小阈值。

noinline

该属性表明,内联器任何情况都不会将函数内联。该属性可能不能与alwaysinline属性一起使用。

optsize

该属性建议,优化pass和代码生成器pass在保持函数少代码量还是执行特定优化以减少代码大小间做决定。

noreturn

 

posted on 2012-10-22 12:04 jackdong 阅读(1874) 评论(0)  编辑 收藏 引用 所属分类: LLVM

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理