Previous: Delay Slots, Up: Insn Attributes


16.19.8 处理器流水线描述

为了获得更好的性能,大多数现代处理器(超流水线,超标量RISC, 以及VLIW处理器)都具有许多功能单元(functional units), 可以在其上同时执行多条指令。一条指令当它的发射条件(issue conditions) 被满足时才开始执行。如果不满足,则指令会被阻塞(stalled),直到它的条件被满足。 这样的互锁(流水线)延迟(interlock (pipeline) delay) 导致对后续指令读取的中断(或者需要nop指令,例如一些MIPS处理器)。

现代处理器中有两种主要的互锁延迟。第一种为数据依赖延迟,用来确定指令延迟时间 (instruction latency time)。直到所有源数据都被先前指令求得, 该指令才会开始执行(有更加复杂的情况是, 当指令开始执行时数据还不可用,但是将会在指令开始执行后的给定时间准备好)。 考虑数据依赖延迟是简单的。 两个指令间的数据依赖(真依赖,输出依赖,反依赖)延迟被给定为一个常量。 大多数情况下该方法都适合。第二种互锁延迟为保留延迟(reservation delay)。 保留延迟意味着要执行的两条指令将会需要共享的处理器资源,即总线,内部寄存器, 以及/或者功能单元,而这些将被保留一段时间。考虑这种延迟是复杂的, 特别是对于现代RISC处理器。

探索更多的处理器并行的任务是由指令调度器来解决的。为了能够更好的解决该问题, 指令调度器必须具有一个处理器并行的适当描述(或者说流水线描述)。 GCC机器描述使用正规表达式来描述处理器并行和对指令组的功能单元保留。

GCC指令调度器使用流水线冒险识别器通过给定的处理器时钟周期模拟来找出可能的指令问题。 流水线冒险识别器通过处理器流水线描述自动生成。 由机器描述生成的流水线冒险识别器是基于有限确定状态机(DFA): 如果存在从一个自动机状态到另一状态的转换,则可以进行指令发射。 该算法非常快,而且它的速度不依赖于处理器的复杂度 1

该章节的剩余部分描述了构造一个基于自动机的处理器流水线描述的命令(directive)。 这些结构在机器描述文件中的顺序并不重要。

下面的可选结构描述了生成的自动机的名字,并用于流水线冒险识别。 有时供流水线冒险识别器使用的生成的有限状态机会非常大。 如果我们使用多个自动机并且将功能单元绑定到自动机上, 则自动机的总的大小通常会小于单个自动机的情况。 如果没有这样一个结构,则会只生成一个有限状态机。

     (define_automaton automata-names)

automata-names为一个字符串,给出了自动机的名字。 名字由逗号分隔。所有自动机应该具有唯一的名字。 自动机名用于结构define_cpu_unitdefine_query_cpu_unit

用于指令保留描述的每个处理器功能单元应该使用下列结构来描述。

     (define_cpu_unit unit-names [automaton-name])

unit-names为一个字符串,给出了由逗号分隔的功能单元的名字。 不要使用名字‘nothing’,它被保留用于其它目的。

automaton-name为一个字符串,给出了功能单元绑定的自动机名。 自动机应该在结构define_automaton中有描述。 如果有一个定义的自动机,则你应该给出automaton-name

为功能单元赋予自动机,受到insn保留中对功能单元使用的限制。 最重要的constraint为:如果一个功能单元保留。 其余的constraint将在后续的结构描述中提到。

下面的结构描述了CPU功能单元,类似于define_cpu_unit。 对于这样的功能单元的保留,可以被询问自动机状态。 对于给定的自动机状态,指令调度器从来不询问功能单元的保留。 所以按照规则,你不需要该结构。 该结构可以被用于将来的代码生成目的(例如,生成VLIW insn模板)。

     (define_query_cpu_unit unit-names [automaton-name])

unit-names为一个字符串,给出了由逗号分隔的功能单元名字。

automaton-name为一个字符串,给出了功能单元所绑定的自动机。

下面的结构为描述一条指令的流水线特征的主要结构。

     (define_insn_reservation insn-name default_latency
                              condition regexp)

default_latency为一个数,给出了指令的延迟时间。 在旧描述和基于自动机的流水线描述中,有一个重要的不同之处。 当我们使用旧描述时,延迟时间是用于所有的依赖。 在基于自动机的流水线描述中,给定的延迟时间只用于真依赖。 反依赖的代价总为0,并且输出依赖的代价是生产者insn和消费者insn的延迟时间之差 (如果差为负数,则代价被认为为0)。 你可以通过使用目标机钩子TARGET_SCHED_ADJUST_COST(参见Scheduling), 来改变任何描述的缺省代价。

insn-name为一个字符串,给出了insn的内部名字。 内部名字被用于结构define_bypass和为了调试所生成的自动机描述文件。 内部名字与define_insn中的名字没有任何关系。 使用在处理器手册中描述的insn类别,是一个很好的做法。

condition定义了什么样的RTL insns由该结构描述。 你应该记住如果对于一个insn, 两个或更多不同define_insn_reservation结构的condition都为真, 则会出问题。这种情况,该insn将使用什么保留,是未定义的。 这种情况在流水线冒险识别器生成时,是不被检查的, 因为识别两个条件具有相同值是十分困难的 (特别是如果条件中包含symbol_ref)。 这在流水线识别器工作时,也不被检查,因为它将使识别器变得相当慢。

regexp为一个字符串,描述了指令对cpu的功能单元的保留。 保留通过正规表达式来描述,语法如下:

            regexp = regexp "," oneof
                   | oneof
     
            oneof = oneof "|" allof
                  | allof
     
            allof = allof "+" repeat
                  | repeat
     
            repeat = element "*" number
                   | element
     
            element = cpu_function_unit_name
                    | reservation_name
                    | result_name
                    | "nothing"
                    | "(" regexp ")"

有时,对于不同insn,具有共同部分的单元保留。 这样情况,你可以通过使用下面的结构来描述共同部分,以简化流水线描述。

     (define_reservation reservation-name regexp)

reservation-name为一个字符串,给出了regexp的名字。 功能单元名和保留名属于同一命名空间。所以,保留名应该与功能单元名不同, 并且不能为预留名‘nothing’。

下面的结构被用于描述对于给定的指令对,在延迟时间上的例外。也称之为bypass。

     (define_bypass number out_insn_names in_insn_names
                    [guard])

number定义了给定字符串out_insn_names的指令所产生的结果, 什么时候可以由给定字符串in_insn_names的指令使用。 字符串中的指令由逗号分隔。

guard为一个可选的字符串,给出了C函数名,其定义了bypass的额外的保护条件。 该函数将两个insn作为参数。如果函数返回0,则对于该情况bypass将被忽略。 额外的guard在识别复杂的bypass时,很有必要。 例如当消费者只是一个insn ‘store’的地址(而不是被存储的值)。

下面五个结构通常用于描述VLIW处理器,或者更精确的说, 来描述放入VLIW指令槽中的小指令的位置。 它们也可以用于RISC处理器。

     (exclusion_set unit-names unit-names)
     (presence_set unit-names patterns)
     (final_presence_set unit-names patterns)
     (absence_set unit-names patterns)
     (final_absence_set unit-names patterns)

unit-names为一个字符串,给出了由逗号分隔的功能单元的名字。

patterns为一个字符串,给出了由逗号分隔的功能单元的模式。 目前的模式,为一个单元或者由空格分隔的单元。

第一个结构(‘exclusion_set’) 意味着第一个字符串中的每个功能单元不能与 第二个字符串中的功能单元同时被保留,反之亦然。例如,结构可以用于描述处理器 (例如,一些SPARC处理器)具有全流水浮点功能单元, 其只可以同时执行单浮点insn或者双浮点insn。

第二个结构(‘presence_set’) 意味着第一个字符串中的每个功能单元不能被保留, 除非至少一种模式的功能单元其名字在第二个字符串中且被保留。这是一个不对称关系。 例如,可以用于描述VLIWslot1’在‘slot0’保留之后被保留。 我们可以使用下列结构来描述

或者‘slot1’只在‘slot0’和功能单元‘b0’保留之后被保留。 这种情况下,我们可以写成

     (presence_set "slot1" "slot0 b0")

第三个结构(‘final_presence_set’) 类似于‘presence_set’。 区别在于什么时候进行检查。当指令在给定自动机状态被发射时, 其将影响所有当前和计划中的单元保留,并且自动机状态被改变。 第一个状态为源状态,第二个为结果状态。 对于‘presence_set’的检查是在源状态保留时进行的, 对于‘final_presence_set’的检查是在结果状态下进行的。 该结构可以用于描述实际上是两个连续的保留的保留。例如,如果我们使用

     (presence_set "slot1" "slot0")

下列insn将永远不会被发射(因为‘slot1’需要‘slot0’, 而‘slot0’在源状态是空缺的)

     (define_reservation "insn_and_nop" "slot0 + slot1")

但是如果我们使用类似的‘final_presence_set’其就可以被发射。

第四个结构 (‘absence_set’) 意味着在第一个字符串中的每个功能单元, 只有在每个名字在第二个字符串中的功能单元没有被保留时才能被保留。 这是一个不对称关系(实际上‘exclusion_set’与其类似,但它是对成的)。 例如,可以用于VLIW描述,来表示‘slot0’不能在‘slot1’或 ‘slot2’保留后被保留。这可以描述为

     (absence_set "slot0" "slot1, slot2")

或者‘slot2’不能被保留,如果‘slot0’和单元‘b0’被保留, 或者‘slot1’和单元‘b1’被保留. 这种情况下,我们可以写成

     (absence_set "slot2" "slot0 b0, slot1 b1")

所有在集合(set)中提到的功能单元应属于相同的自动机。 最后一个结构(‘final_absence_set’)类似于‘absence_set’, 但是检查是在结果(状态)保留时进行。参见‘final_presence_set’的注解。

你可以使用下面的结构来控制流水线冒险识别器的生成。

     (automata_option options)

options为一个字符串,给出了影响生成代码的选项。目前有下列选项:

作为一个例子,考虑一个超标量RISC机器, 其可以在一个周期发射三条insn(两条整数insn和一条浮点insn), 但是只能完成两条insn。为了描述,我们定义下列功能单元。

     (define_cpu_unit "i0_pipeline, i1_pipeline, f_pipeline")
     (define_cpu_unit "port0, port1")

所有简单的整数insn可以在任何整数流水线中被执行,并且结果可以在两个周期获得。 简单的整数insn将被发射到第一个流水线中,除非它被保留, 否则它们将被发射到第二个流水线中。整数除和乘insn只能在第二个整数流水线中被执行, 并且它们的结果相应的在8和4个周期获得。 整数除为非流水线,即后续的整数除insn在当前的除法insn完成前不能被发射。 浮点insn为全流水的并且它们的结果在3个周期获得。 当浮点insn的结果被整数insn使用使,将会产生一个额外的周期延迟。 要描述所有这些,我们可以指定

     (define_cpu_unit "div")
     
     (define_insn_reservation "simple" 2 (eq_attr "type" "int")
                              "(i0_pipeline | i1_pipeline), (port0 | port1)")
     
     (define_insn_reservation "mult" 4 (eq_attr "type" "mult")
                              "i1_pipeline, nothing*2, (port0 | port1)")
     
     (define_insn_reservation "div" 8 (eq_attr "type" "div")
                              "i1_pipeline, div*7, div + (port0 | port1)")
     
     (define_insn_reservation "float" 3 (eq_attr "type" "float")
                              "f_pipeline, nothing, (port0 | port1))
     
     (define_bypass 4 "float" "simple,mult,div")

为了简化描述,我们可以描述下列保留

     (define_reservation "finish" "port0|port1")

并在所有define_insn_reservation中使用,比如下面的结构

     (define_insn_reservation "simple" 2 (eq_attr "type" "int")
                              "(i0_pipeline | i1_pipeline), finish")

Footnotes

[1] 然而,自动机的大小依赖于处理器的复杂度。为了限制这种影响, 机器描述可以将机器描述的正交部分拆分成多个自动机:但是, 由于每个这样的自动机都必须独立的执行每一步, 所以这确实会在算法性能上造成一点消减。