内联灰
当内联C太安全时.
你试过了吗内联-c
,但这还不够吗?你还需要吗?似乎什么都不满足?内联灰
去营救!
而且,由于内联程序集只是一个常见的Haskell值(即使在编译时操作),有很多很酷的事情可以做,比如显式的编译时循环展开。
示例
交换两个国际
s,并将其中一个值增加2:
defineAsmFun“swap2p1”[asmTy|(a:整数)(b:整数)|(_:整数)(_:Int)][阿斯姆|xchg{a},{b}添加$2,{b}|]
这提供了一个功能swap2p1::Int->Int->(Int,Int)
好吧,换两个国际
第条。注意,结果函数是纯函数{一}
,{b}
古董商。
获取的最后一个字符字节字符串
,或默认字符(如果为空):
defineAsmFun“lastChar”[asmTy|(bs:ByteString)(def:Word)|(w:Word)|][阿斯姆|测试{bs:len},{bs:len}jz为零移动zbq-1({bs:ptr},{bs:len}),{w}RET_任务为零(_0):移动{def},{w}|]
这提供了一个功能lastChar::ByteString->Word->Word
.注意这个特殊的{bs:ptr}
和{bs:len}
古董,以及RET_任务
命令提前返回。
字符串中的SIMD加速字符出现计数:
defineAsmFun“countCharSSE42”[asmTy|(ch:Word8)(ptr:ptr Word8)(len:Int)|(cnt:Int)|]$展开“i”[12..15][阿斯姆|推送%r{i}|]<>[asm|vmovd{ch},%xmm15vpxor%xmm0、%xmmO、%xmmvpshufb%xmm0、%xmm15、%xmm 15shr$7,{len}mov 16美元,%eax移动16美元,%edx异或{cnt},{cntneneneep{移动ptr rdi}循环:|]<>展开“i”[1..8][[阿斯姆|vmovdqa{(i-1)*0x10}({ptr}),%xmm{i}|],[asm|vpcmpestrm$10,%xmm15,%xmm{i}vmovdqa%xmm0,%xmm{i}|],[asm|vmovq%xmm{i},%r{i+7}|],[asm|popcnt%r{i+7},%r{i+7}|],[asm|添加%r{i+7},{cnt}|]] <>[阿斯姆|加128美元,{ptr}dec{len}jnz循环|]<>展开“i”[15,14..12][asm|弹出%r{i}|]
这提供了一个功能countCharSSE42::字8->Ptr字8->Int->Int
.请注意展开
/展开
用于编译时代码生成和循环展开的Haskell函数模板中包含算术表达式。
取决于某些外部状态的不准确计算,例如读取CPU的时间戳计数器:
定义AsmFunM“rdtsc”[asmTy||(输出:Word64)|][阿斯姆|rdtsc公司移动%rdx,{out}shl$32,{out}添加%rax,{out}|]
这提供了一个功能rdtsc::PrimMonad m=>m单词64
可用于装货单
或IO(输入输出)
上下文。请注意M(M)字母输入定义SmFunM
.
基本用法
入口点是定义SmFun
函数来自语言。装配。内联
以及asm公司
和asmTy(目标类型)
准引用自语言。装配。内联。QQ(QQ)
.
首先,启用Template Haskell和未下载的FFI编组资料所需的一些扩展:
{-#LANGUAGE模板Haskell,准引号#-}{-#LANGUAGE GHCForeignImportPrim,UnliftedFFITypes,UnboxedTuples#-}
然后就可以了
defineAsmFun“funName”[asmTy|(someInt:Int)(somePtr:Ptr字)(sometStr:BS.ByteString)|(len:Int,计数:Int)|][阿斯姆|; 您的asm代码如下|]
定义函数funName(功能名称)
类型的Int->Ptr字->BS.ByteString->(Int,Int)
.
反报价
好消息:没有必要记住GHC呼叫约定访问参数和输出值插槽!相反,可以使用{someInt}
引用参数或名为的返回槽的语法someInt公司
.
注:尽管如此,现在还是要小心不要意外覆盖输入参数通过选择错误的寄存器进行临时计算。我们可能会在未来的版本中引入一些语法来挑选未使用的寄存器,但现在必须小心。
字节字符串
支持参数,但作为复合对象,它们有点特殊:类型的输入参数字节字符串
实际上需要两个寄存器:一个表示字符串的地址,另一个表示其长度。
有时,将输入参数与另一个寄存器重新关联可能很方便。为此{移动argName newReg}
可以使用反引号(例如,{移动一些Int rdi}
).这将更新从参数名到寄存器名的映射以及发布程序集压敏电阻
命令。
如果你需要早点返回哈斯克兰,只需写信RET_任务
,它被实际的命令替换以返回Haskell。
显式循环展开
这个asm公司
准引用基本上产生一个字符串,因此,可以在编译时使用任意Haskell函数对其进行操作。特别是,字符串模板可以在编译时复制。
这个展开
函数展开单个asm公司
代码块,计算涉及unroll变量的算术表达式,因此
展开“i”[1..8][asm|vmovdqa{(i-1)*0x10}({ptr}),%xmm{i}|]
等于
vmovdqa 0x0({ptr}),%xmm1vmovdqa 0x10({ptr}),%xmm2vmovdqa 0x20({ptr}),%xmm3vmovdqa 0x30({ptr}),%xmm4vmovdqa 0x40({ptr}),%xmm5vmovdqa 0x50({ptr}),%xmm6vmovdqa 0x60({ptr}),%xmm7vmovdqa 0x70({ptr}),%xmm8
展开
工作原理类似,但需要一个列表asm公司
代码块(而不是单个块),展开每个结果,然后将结果连接起来。等式地,
展开var ints代码=foldMap(展开var int)代码
这个countCharSSE42个
上面的函数可能是一个很好的示例。
功能不完善
上述大多数功能实际上都是纯的:它们在给定相同参数的情况下返回相同的结果,并且没有副作用。也许不足为奇的是,情况并非总是如此。让我们考虑一下rdtsc公司
再举一个例子,假设我们已经写了
defineAsmFun“rdtsc”[asmTy|(_:单位)|(输出:Word64)|][阿斯姆|rdtsc公司移动%rdx,{out}shl$32,{out}添加%rax,{out}|]
(顺便说一句,我们需要一个假人单位
在此处输入因为否则生成的导入Assembly函数的类型将是64字#
,这不是一个函数,而是一个值,因此被GHC禁止)。
我们如何使用这个函数来测量一些东西?我们可能会写这样的东西
度量=do设r1=rdtsc单位运行长计算设r2=rdtsc单位打印$r2-r1
问题是编译器非常热衷于将其转换为
度量=do设r1=rdtsc单位运行长计算设r2=r1打印$r2-r1
因此,每个计算都会根据我们的测量结果立即执行,并且没有数量{-#NOINLINE#-}
喜欢的人会解决的。
唯一的修复方法是实际穿过州#
代币,这就是我们使用一元函数时发生的情况定义SmFunM
以下为:
定义AsmFunM“rdtsc”[asmTy||(输出:Word64)|][阿斯姆|rdtsc公司移动%rdx,{out}shl$32,{out}添加%rax,{out}|]
在这种情况下,生成的Assembly函数将与类型一起导入对于所有s.State#s->(#State#s,Word64##)
,以及发生了什么州#
对编译器来说是完全不透明的,因此它不能再“优化”它,以及预期的以下工作:
度量=dor1<-rdtsc运行长计算r2<-rdtsc打印$r2-r1
顺便说一句,现在有了state标记参数,我们不再需要伪标记单位
.
安全和注意事项
- 首先,所有这些都是完全不安全的。
- 虽然此软件包提供了一些快捷方式,理解调用约定(尤其是实际使用的寄存器)很重要。当然,最好的真相来源是GHC源代码.
- 每个函数都是用自己的函数编译的
.S系列
文件,这样就可以自由选择标签的任意命名等等,但是,另一方面,不能访问其他功能中的标签。这可以很容易地解决——如果确实需要,可以考虑抛出一个问题。
- 最后,所有这些都是完全不安全的。