下面的汇编序列中的哪些指令会放在延迟槽中,
(A)
ADD R5 <- R4, R3
OR R3 <- R1, R2
SUB R7 <- R5, R6
J X
Delay Slots
LW R10 <- (R7)
ADD R6 <-R1 , R2
X:
此处延迟时隙为2.
但我没有得到什么是分支条件?是跳转到X ???
如果是,那么如何在延迟槽中放置2条指令?因为来自&#34;之前&#34;或者&#34;目标&#34;或&#34;经历&#34; ,我们需要将它们与Branch条件进行比较。如果它没有冲突,那么我们可以放置这些指令。没有得到J X的意思
Ans:ADD和OR指令。
(B)
ADD R5 <- R4, R3
OR R3 <- R1, R2
SUB R7 <- R5, R6
BEQ R5 <- R7, X
Delay Slots
LW R10 <- (R7)
ADD R6 <- R1, R2
BEQ R5&lt; - R7,X是否意味着如果X + R7等于R5则采用分支X?如果是,那么我们就不能使用SUB指令,因为R7的值会改变。 ADD也不能用作R5会改变。 (两者都取决于分支)。 OR,LW和ADD R6&lt; -R1,R2在分支上是独立的。那为什么LW和ADD呢? Ans说:OR和NOP。
请指导我解决这些问题。
答案 0 :(得分:2)
对于(A),J X
是无条件跳转到单个固定标签(可能在ISA提供的范围内),因此它对先前的指令没有任何数据依赖性。
由于ADD
的名称依赖于OR
的结果,如果要在OR
OR
的目的地之后移动R3
被更改(假设有一个可用的空闲寄存器,并且稍后的移动可以在调度方面是空闲的,或者通过更改稍后在OR
中使用该值的指令来使用作为目标的寄存器来消除。 SUB
)。这将是一个相对复杂的变化,可能被排除在考虑之外。
由于ADD
是依赖于OR R3 <- R1, R2; SUB R7 <- R5, R6
的数据,因此必须在程序顺序之后保留它。因此,两个延迟槽可以填充为SUB R7 <- R5, R6; OR R3 <- R1, R2
或ADD R5 <- R4, R3; SUB R7 <- R5, R6
。 (R3
因名称依赖于ADD R5 <- R4, R3; OR R3 <- R1, R2
而被禁止R5
;由于数据依赖于SUB
指令中的LW R10 <- (R7)
,因此不允许X
。)
跳跃的无条件性质也意味着没有直通路径;总是采取跳跃。延迟槽后的指令不会在到达跳转指令的路径中执行(无条件跳转总是跳过它们),所以即使在延迟槽中使用它们也不会引入异常或覆盖使用的寄存器在跳转到X之后,相对于在延迟槽中使用NOP,没有任何优势。
(示例代码序列的第一部分可能是if-then-else语句的then子句,由跳转到ADD R5 <- R4, R3; OR R3 <- R1, R2
的条件分支继续,这将是else子句的开头即,该代码可以访问,但只能在显示的代码之前采用分支。)
无条件性质进一步意味着可以从目标获取指令,尽管编译器需要确保标签SUB R7 <- R5, R6
的所有其他路径也包含从该目标移动/复制到该跳转的任何指令&# 39;延迟时隙。
提供的答案(ADD
)完全错误,特别是因为ADD
是依赖于BEQ R5 <- R7, X
指令的数据,因此在if R5 == R7 goto X; R5 = old_PC+length(BEQ R5 <- R7, X)
之前移动它会产生错误导致!
对于(B),SUB R7 <- R5, R6
是一个不寻常的指令,可能意味着SUB
,即分支链接指令。无论如何,写入R5的内容并不重要,只是分支覆盖R5会限制指令调度。
由于分支指令覆盖R5,BEQ R8 <- R5, R7, X
不能在分支后移动(即ADD R6 <- R1, R2
具有名称依赖性;如果LW R10 <- (R7)
是有效形式(其中R8是自由注册和后来使用的R5改为R8)然后可以避免这种名称依赖)。
由于分支是依赖于“ADD R5&lt; -R4,R3&#”的数据,因此该指令不能移入延迟槽。
如果R6中的当前值未被所采用的分支路径中的任何指令读取,那么SUB R7 <- R5, R6
可以从直通路径提升到第二个延迟槽(假设ADD不能产生例如溢出)。这只会在不采用分支时有助于提高性能,但是考虑使用NOP填充插槽会更好(这对两条路径都没有帮助)。
将R6
移入其中一个延迟槽更有问题,因为当R5 == R7时,负载可能会出现保护错误,而采用的分支将避免这种情况。 (从技术上讲,运行时系统可以通过查看可执行文件附带的附加调试信息来处理不适当的保护错误,以发现负载从直通路径中提升,确定将采取分支,零R10(避免权限违规),并在X处继续执行。但是,这比更改寄存器名称以避免名称依赖更为极端。)
使用来自条件分支的目标或直通路径的指令通常必须保留异常行为。即,如果实际采用了分支,则不允许来自直通路径的指令生成异常(因为如果没有重新排序将在重新排序时发生不会发生的异常),并且类似地如果分支实际上不是如果没有,则不允许来自目标路径的指令生成异常。
如果没有关于目标路径的信息,则无法确定R6
是否是R6
的最后一位读者(即R6
在该点之后死亡)。但是,如果在任何指令使用ADD
作为源之前使用ADD R6 <- R1, R2
在目标路径中找到指令,则OR R3 <- R1, R2; OR R3 <- R3, R3
将会死亡(假设OR
无法生成异常)NOP
可以在其中一个延迟槽中使用。
取决于ISA(以及使用的操作码,例如,MIPS对签名的加法指令有溢出异常但对于无符号无效 - 由于使用了二进制补码格式,结果是相同的)添加可能会产生溢出异常(但是我认为情况并非如此)。通过硬件实现某种形式的内存保护,内存读取可以生成保护异常。通常,编译器必须假设分支可能正在阻止这样的保护异常(例如超过数组的末尾或取消引用空指针),因此它通常不能使用来自目标的内存访问(如果是分支)没有采取)或跌倒(如果采取分支)。 (通常需要在虚拟内存系统中避免不必要的页面错误以提高性能,但对于正确性而言可能不是严格必要的。)
如上所述,一个稍微复杂的运行时系统可以纠正这样的异常,至少对于负载而言,但是这样的&#34;英雄&#34;措施可能被认为超出了此类练习的范围。
在某些情况下,编译器可以确定加载是安全的(例如,当前堆栈帧中的加载),即使它被条件分支保护,但是对于这样的练习,通常会假设编译器不能保证这种存储器访问的安全性。如果可能是带有加载的路径,则编译器可以在延迟槽中使用预取指令来减少高速缓存未命中延迟;然而,在该示例中,假设标量流水线,预取将仅将未命中延迟减少两个周期。对于缓存命中或保护违规,预取是不定的,因此相对于使用nop而言,这可能经常没有优势 - 并且可能通过在执行分支时引入缓存未命中而降低性能 - 但在异常行为方面是安全的。
因此延迟时隙可以是SUB R7 <- R5, R6
(即OR R3 <- R1, R2; ADD R6 <- R1, R2
和R6
),或者,如果R6在R6
之后死亡(并且溢出异常不是问题) ),{{1}}。给定的答案似乎假设编译器无法确定{{1}}是否已死,这是一个合理的假设,因为不知道所采用路径上的代码。 (实际上,编译器可以检查代码,而{{1}}可能 - 或者可能不会 - 已经死了。)