实现x86 PDEP/PEXT指令在SMTlib中的高效方式

huangapple go评论57阅读模式
英文:

Implementing the x86 PDEP/PEXT instructions efficiently in SMTlib

问题

在SMTlib位向量语法中,是否有一种有效的方式来指定PDEP/PEXT指令?

对于PEXT,我的最佳尝试大致如下:“如果mask中的第N位设置了,那么结果中的第count_bits(mask[0..N])位等于输入值中的第N位。” 但这需要一种计数位的方法,这在QF_BV中是不可用的。

类似地,对于PDEP,我得到了类似的结果:“如果mask中的第N位设置了,那么结果中的第N位等于输入值中的第count_bits(mask[0..N])位。” 但同样需要计数位。

我可以编写一个简单的位计数函数,但这可能需要逐位评估位向量中的所有位。整个规范最终会变成O(N^2),这应该是不必要的。

英特尔提供了PDEP的以下实现:

TEMP := SRC1;
MASK := SRC2;
DEST := 0 ;
m := 0, k := 0;
DO WHILE m < OperandSize
    IF MASK[ m] = 1 THEN
        DEST[ m] := TEMP[ k];
        k := k+ 1;
    FI
    m := m+ 1;
OD

...但我在将while循环翻译为SMTlib语法时遇到了困难。

英文:

Is there a way to specify the PDEP/PEXT instructions efficiently in SMTlib bitvector syntax?

My best attempt for PEXT ends up with something to the tune of: "Iff bit N in mask is set, then bit count_bits(mask[0..N]) in the result is equal to bit N in the input value.". But this requires a way to count bits, which isn't available in QF_BV.

Similarly for PDEP I end up with something like: "Iff bit N in mask is set, then bit N in the result is equal to bit count_bits(mask[0..N]) in the input value." Which again, requires counting bits.

I could write a naive bit counting function, but that would probably require evaluating all bits in the bitvector one-by-one. The entire specification would end up being O(N^2), which shouldn't be necessary.

Intel gives this implementation for PDEP:

TEMP := SRC1;
MASK := SRC2;
DEST := 0 ;
m := 0, k := 0;
DO WHILE m < OperandSize
    IF MASK[ m] = 1 THEN
        DEST[ m] := TEMP[ k];
        k := k+ 1;
    FI
    m := m+ 1;
OD

...but I am having trouble translating the while loop to SMTlib syntax.

答案1

得分: 1

当以符号方式编程时,你无法真正避免这种复杂性。请注意,你的实现必须对输入和掩码的所有符号值都正确工作:如果没有这些的具体值,优化就没有意义:求解器将不得不考虑所有可能的路径,因此优化技巧实际上不起作用。(你可以将符号模拟视为“运行所有可能的输入”,因此没有快捷方式可用。)

因此,不要关注循环的复杂性,只需在最终值上编写必要的约束条件。不幸的是,SMTLib 对于这种类型的编程来说并不是一个很好的选择,因为它不允许许多通常的编程习惯。但这正是为什么我们在求解器之上构建了可编程的 API 的原因。Z3 可以从许多语言(包括 C/C++/Python/Haskell/Java 等)进行脚本化,因此我建议使用你最熟悉的语言并学习相应的绑定。

基于这个,我将展示如何使用流行的 Python API 和 Haskell 版本编写 PEXT 的代码。最后,我还将展示如何在 SMTLib 中生成代码,这可能会很有用。请注意,我还没有进行详尽的测试,因此在用于任何重要用途之前,请务必仔细检查。

Python

from z3 import *

# 实现 bv[idx] = b,其中 idx 是具体的。假定 1 <= size
def updateBit(size, bv, idx, b):
    if idx == 0:
        return Concat(Extract(size-1, idx+1, bv), b)
    elif idx == size-1:
        return Concat(                            b, Extract(idx-1, 0, bv))
    else:
        return Concat(Extract(size-1, idx+1, bv), b, Extract(idx-1, 0, bv))

# 实现:bv[idx] = b,其中 idx 可能是符号的。假定 1 <= size <= 2^8
def Update(size, bv, idx, b):
    for i in range(size):
        bv = If(BitVecVal(i, 8) == idx, updateBit(size, bv, i, b), bv)
    return bv

# 实现 PEXT,通过返回结果位向量。假定 1 <= size <= 2^8
def PEXT(size, src, mask):
    dest = BitVec('dest', size)
    idx  = BitVecVal(0, 8)

    for m in [Extract(i, i, mask) for i in range(size)]:
        srcBit = Extract(0, 0, src)
        src    = LShR(src, 1)
        dest   = If(m == BitVecVal(1, 1), Update(size, dest, idx, srcBit), dest)
        idx    = If(m == BitVecVal(1, 1), idx+1, idx)

    return dest

# 简单示例
x, m, r = BitVecs('x m r', 8)
s = Solver()
s.add(x == BitVecVal(0xAA, 8))
s.add(m == BitVecVal(0xAA, 8))
s.add(r == PEXT(8, x, m))
print(s.check())

mdl = s.model()
def grab(v):
    return hex(mdl[v].as_long()

print("PEXT(8, " + grab(x) + ", " + grab(m) + ") = " + grab(r))

当我运行这段代码时,得到:

sat
PEXT(8, 0xaa, 0xaa) = 0xf

当然,这只是一个示例;因此,请通过详细研究实现来自行验证。

Haskell

对于 Haskell,我将使用 SBV 库(https://hackage.haskell.org/package/sbv)。与 Python 相比,它更加表达丰富且更容易使用;如果你熟悉 Haskell:

{-# LANGUAGE DataKinds #-}

import Data.SBV
import GHC.TypeLits (KnownNat)

pext :: forall n. (KnownNat n, BVIsNonZero n) => SWord n -> SWord n -> SWord n
pext src mask = walk 0 src 0 (blastLE mask)
  where walk dest _ _   []     = dest
        walk dest x idx (m:ms) = walk (ite m (sSetBitTo dest idx (lsb x)) dest)
                                      (x `shiftR` 1)
                                      (ite m (idx + 1) idx)
                                      ms

可以通过以下方式进行测试:

test :: IO SatResult
test = satWith z3{printBase = 16} $
             do x :: SWord 8 <- sWord "x"
                m :: SWord 8 <- sWord "m"
                r :: SWord 8 <- sWord "r"

                constrain $ x .== 0xAA
                constrain $ m .== 0xAA
                constrain $ r .== pext @8 x m

输出如下:

*Main> test
Satisfiable. Model:
  x = 0xaa :: Word8
  m = 0xaa :: Word8
  r = 0x0f :: Word8

为了完整性,这里还有 pdep 的实现:

pdep :: forall n. (KnownNat n, BVIsNonZero n) => SWord n -> SWord n -> SWord n
pdep src mask = walk 0 src 0 (blastLE mask)
  where walk dest _ _   []     = dest
        walk dest x idx (m:ms) = walk (ite m (sSetBitTo dest idx (lsb x)) dest)
                                      (ite m (x `shiftR` 1) x)
                                      (idx + 1)
                                      ms

希望你能采纳这些思路,并以类似的方式在你选择的编程语言中实现它们。再次强调,在用于任何重要用途之前,请务必进行进一步的测试,我只对上面显示的一个值进行了测试。

SMTLib

如我所述,SMTLib 实际上不适用于这

英文:

When programming symbolically, you can't really avoid this sort of complexity. Note that your implementation must work correctly for all symbolic values of the input and the mask: Without having a concrete value for these, it's pointless to optimize: The solver will have to consider all possible paths, and hence optimization tricks don't really work. (You can think of symbolic simulation as "running for all possible inputs," so no shortcuts apply.)

So, instead of focussing on the complexity of the loop, simply write the necessary constraints on the final value. Unfortunately SMTLib isn't terribly a good choice for this sort of programming, as it doesn't allow for many usual programming idioms. But that's precisely why we have programmable API's built on top of solvers. Z3 can be scripted from many languages, including C/C++/Python/Haskell/Java.. etc.; so I'd recommend using a language you're most familiar with and learning the bindings there.

Based on this, I'll show how to code PEXT using both the popular Python API, and also the Haskell version. At the end, I'll also show how to do this in SMTLib by generating the code, which might come in handy. Note that I haven't extensively tested these, so while I trust the basic implementation is correct, you should double check before using it for anything serious.

Python

from z3 import *

# Implements bv[idx] = b, where idx is concrete. Assumes 1 &lt;= size
def updateBit(size, bv, idx, b):
    if idx == 0:
        return Concat(Extract(size-1, idx+1, bv), b)
    elif idx == size-1:
        return Concat(                            b, Extract(idx-1, 0, bv))
    else:
        return Concat(Extract(size-1, idx+1, bv), b, Extract(idx-1, 0, bv))

# Implements: bv[idx] = b, where idx can be symbolic. Assumes 1 &lt;= size &lt;= 2^8
def Update(size, bv, idx, b):
    for i in range(size):
        bv = If(BitVecVal(i, 8) == idx, updateBit(size, bv, i, b), bv)
    return bv

# Implements PEXT, by returning the resulting bit-vector. Assumes 1 &lt;= size &lt;= 2^8
def PEXT(size, src, mask):
    dest = BitVec(&#39;dest&#39;, size)
    idx  = BitVecVal(0, 8)

    for m in [Extract(i, i, mask) for i in range(size)]:
        srcBit = Extract(0, 0, src)
        src    = LShR(src, 1)
        dest   = If(m == BitVecVal(1, 1), Update(size, dest, idx, srcBit), dest)
        idx    = If(m == BitVecVal(1, 1), idx+1, idx)

    return dest

# Simple example
x, m, r = BitVecs(&#39;x m r&#39;, 8)
s = Solver()
s.add(x == BitVecVal(0xAA, 8))
s.add(m == BitVecVal(0xAA, 8))
s.add(r == PEXT(8, x, m))
print(s.check())

mdl = s.model()
def grab(v):
    return hex(mdl[v].as_long())

print(&quot;PEXT(8, &quot; + grab(x) + &quot;, &quot; + grab(m) + &quot;) = &quot; + grab(r))

When I run this, I get:

sat
PEXT(8, 0xaa, 0xaa) = 0xf

Of course, this is just one example; so please convince yourself by studying the implementation in detail.

Haskell

For Haskell, I'll use the SBV library (https://hackage.haskell.org/package/sbv). Compared to Python, it's much more expressive and a lot easier to use; if you're familiar with Haskell:

{-# LANGUAGE DataKinds #-}

import Data.SBV
import GHC.TypeLits (KnownNat)

pext :: forall n. (KnownNat n, BVIsNonZero n) =&gt; SWord n -&gt; SWord n -&gt; SWord n
pext src mask = walk 0 src 0 (blastLE mask)
  where walk dest _ _   []     = dest
        walk dest x idx (m:ms) = walk (ite m (sSetBitTo dest idx (lsb x)) dest)
                                      (x `shiftR` 1)
                                      (ite m (idx + 1) idx)
                                      ms

Which can be tested by:

test :: IO SatResult
test = satWith z3{printBase = 16} $
             do x :: SWord 8 &lt;- sWord &quot;x&quot;
                m :: SWord 8 &lt;- sWord &quot;m&quot;
                r :: SWord 8 &lt;- sWord &quot;r&quot;

                constrain $ x .== 0xAA
                constrain $ m .== 0xAA
                constrain $ r .== pext @8 x m

which prints:

*Main&gt; test
Satisfiable. Model:
  x = 0xaa :: Word8
  m = 0xaa :: Word8
  r = 0x0f :: Word8

And here's pdep for completeness:

pdep :: forall n. (KnownNat n, BVIsNonZero n) =&gt; SWord n -&gt; SWord n -&gt; SWord n
pdep src mask = walk 0 src 0 (blastLE mask)
  where walk dest _ _   []     = dest
        walk dest x idx (m:ms) = walk (ite m (sSetBitTo dest idx (lsb x)) dest)
                                      (ite m (x `shiftR` 1) x)
                                      (idx + 1)
                                      ms

Hopefully you can adopt these ideas, and implement them in your language of choice in a similar fashion. Again, please test further before using it for anything serious though, I only tested these for just the one value I shown above.

SMTLib

As I mentioned, SMTLib isn't really suitable for this sort of programming, since it lacks usual programming language idioms. But if you have to have this coded in SMTLib, then you can use the sbv library in Haskell to generate the function for you. Note that SMTLib is a "monomorphic" language, that is, you can't have a definition of PEXT that works for arbitrary bit-sizes. You'll have to generate one for each size you need. The generated code will not be pretty to look at, as it will be a full unfolding, but I trust it should perform well. Here's the output for the 2-bit case. (As you can imagine, longer bit sizes will yield bigger functions).

*Main&gt; sbv2smt (smtFunction &quot;pext_2&quot; (pext @2)) &gt;&gt;= putStrLn
; Automatically generated by SBV. Do not modify!
; pext_2 :: SWord 2 -&gt; SWord 2 -&gt; SWord 2
(define-fun pext_2 ((l1_s0 (_ BitVec 2)) (l1_s1 (_ BitVec 2))) (_ BitVec 2)
  (let ((l1_s3 #b0))
  (let ((l1_s7 #b01))
  (let ((l1_s8 #b00))
  (let ((l1_s20 #b10))
  (let ((l1_s2 ((_ extract 1 1) l1_s1)))
  (let ((l1_s4 (distinct l1_s2 l1_s3)))
  (let ((l1_s5 ((_ extract 0 0) l1_s1)))
  (let ((l1_s6 (distinct l1_s3 l1_s5)))
  (let ((l1_s9 (ite l1_s6 l1_s7 l1_s8)))
  (let ((l1_s10 (= l1_s7 l1_s9)))
  (let ((l1_s11 (bvlshr l1_s0 l1_s7)))
  (let ((l1_s12 ((_ extract 0 0) l1_s11)))
  (let ((l1_s13 (distinct l1_s3 l1_s12)))
  (let ((l1_s14 (= l1_s8 l1_s9)))
  (let ((l1_s15 ((_ extract 0 0) l1_s0)))
  (let ((l1_s16 (distinct l1_s3 l1_s15)))
  (let ((l1_s17 (ite l1_s16 l1_s7 l1_s8)))
  (let ((l1_s18 (ite l1_s6 l1_s17 l1_s8)))
  (let ((l1_s19 (bvor l1_s7 l1_s18)))
  (let ((l1_s21 (bvand l1_s18 l1_s20)))
  (let ((l1_s22 (ite l1_s13 l1_s19 l1_s21)))
  (let ((l1_s23 (ite l1_s14 l1_s22 l1_s18)))
  (let ((l1_s24 (bvor l1_s20 l1_s23)))
  (let ((l1_s25 (bvand l1_s7 l1_s23)))
  (let ((l1_s26 (ite l1_s13 l1_s24 l1_s25)))
  (let ((l1_s27 (ite l1_s10 l1_s26 l1_s23)))
  (let ((l1_s28 (ite l1_s4 l1_s27 l1_s18)))
  l1_s28))))))))))))))))))))))))))))

You can drop the above (or at whatever size you generate it at) in your SMTLib program and use as is, if you really must stick to SMTLib.

huangapple
  • 本文由 发表于 2023年6月26日 22:37:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/76557703.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定