【问题标题】:How to use Data.SBV to help derive correct stack machine implementation?如何使用 Data.SBV 来帮助导出正确的堆栈机器实现?
【发布时间】:2020-01-07 13:49:19
【问题描述】:

Graham Hutton,在 Haskell 编程 的第二版中,最后 2 章讨论了基于 堆栈机 的 AST 实现这一主题。 最后,他展示了如何从 AST 的语义模型派生该机器的正确实现。

我正在尝试在该推导中寻求 Data.SBV 的帮助,但失败了。

我希望有人可以帮助我了解我是否:

  1. 要求Data.SBV做不到的事情,或者
  2. Data.SBV 询问它可以做的事情,但询问不正确。
-- test/sbv-stack.lhs - Data.SBV assisted stack machine implementation derivation.

{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE ScopedTypeVariables #-}

import Data.SBV
import qualified Data.SBV.List as L
import           Data.SBV.List ((.:), (.++))  -- Since they don't collide w/ any existing list functions.

-- AST Definition
data Exp = Val SWord8
         | Sum Exp Exp

-- Our "Meaning" Function
eval :: Exp -> SWord8
eval (Val x)   = x
eval (Sum x y) = eval x + eval y

type Stack  = SList Word8

-- Our "Operational" Definition.
--
-- This function attempts to implement the *specification* provided by our
-- "meaning" function, above, in a way that is more conducive to
-- implementation in our available (and, perhaps, quite primitive)
-- computational machinery.
--
-- Note that we've (temporarily) assumed that this machinery will consist
-- of some form of *stack-based computation engine* (because we're
-- following Hutton's example).
--
-- Note that we give the *specification* of the function in the first
-- (commented out) line of the definition. The derivation of the actual
-- correct definition from this specification is detailed in Ch. 17 of
-- Hutton's book.
eval' :: Exp -> Stack -> Stack
-- eval' e s = eval e : s         -- our "specification"
eval' (Val n) s = push n s        -- We're defining this one manually.
 where
  push :: SWord8 -> Stack -> Stack
  push n s = n .: s
eval' (Sum x y) s = add (eval' y (eval' x s))
 where
  add :: Stack -> Stack
  add = uninterpret "add" s       -- This is the function we're asking to be derived.

-- Now, let's just ask SBV to "solve" our specification of `eval'`:
spec :: Goal
spec = do x :: SWord8 <- forall "x"
          y :: SWord8 <- forall "y"
          -- Our spec., from above, specialized to the `Sum` case:
          constrain $ eval' (Sum (Val x) (Val y)) L.nil .== eval (Sum (Val x) (Val y)) .: L.nil

我们得到:

λ> :l test/sbv-stack.lhs
[1 of 1] Compiling Main             ( test/sbv-stack.lhs, interpreted )
Ok, one module loaded.
Collecting type info for 1 module(s) ... 
λ> sat spec
Unknown.
  Reason: smt tactic failed to show goal to be sat/unsat (incomplete quantifiers)

发生了什么?!
好吧,也许,要求 SBV 解决 谓词 以外的任何问题(即 - a -&gt; Bool)都行不通?

【问题讨论】:

    标签: haskell smt stack-machine sbv


    【解决方案1】:

    这里的根本问题是您混合了 SMTLib 的序列逻辑和量词。这个问题对于 SMT 求解器来说太难处理了。如果您将自己局限于基本逻辑,那么这种功能的综合确实是可能的。 (位向量、整数、实数。)但是将序列添加到混合中会将其放入不可判定的片段中。

    这并不意味着 z3 无法合成您的 add 函数。也许未来的版本可能能够处理它。但在这一点上,你受到启发式的摆布。要了解原因,请注意您要求求解器综合以下定义:

            add :: Stack -> Stack
            add s = v .: s''
              where (a, s')  = L.uncons s
                    (b, s'') = L.uncons s'
                    v        = a + b
    

    虽然这看起来相当单纯和简单,但它需要的能力超出了 z3 当前的能力。通常,z3 目前可以合成仅对具体元素做出有限选择的函数。但是,如果输出依赖于每个输入选择的输入,它就无法做到这一点。 (把它想象成一个案例分析生成引擎:它可以变出一个将某些输入映射到其他输入的函数,但无法确定是否应该增加某些东西或必须添加两个东西。这是从有限模型中的工作得出的寻找理论,远远超出了这个答案的范围!详情请看这里:https://arxiv.org/abs/1706.00096)

    SBV 和 SMT 解决此类问题的更好用例是实际告诉它add 函数是什么,然后证明使用 Hutton 的策略正确“编译”了某个给定程序。请注意,我明确地说是“给定”程序:对于任意程序也很难建模和证明这一点,但是对于给定的固定程序,您可以很容易地做到这一点。如果你有兴趣证明任意程序的对应关系,你真的应该看看 Isabelle、Coq、ACL2 等定理证明器;它可以处理归纳,这是一种你无疑需要解决这类问题的证明技术。请注意,SMT 求解器通常无法执行归纳。 (您可以使用电子匹配来模拟一些类似证明的归纳,但它充其量只是一个杂物,而且通常无法维护。)

    这是您的示例,编码以证明 \x -&gt; \y -&gt; x + y 程序根据引用语义“正确”编译和执行:

    {-# LANGUAGE ScopedTypeVariables #-}
    
    import Data.SBV
    import qualified Data.SBV.List as L
    import           Data.SBV.List ((.:))
    
    -- AST Definition
    data Exp = Val SWord8
             | Sum Exp Exp
    
    -- Our "Meaning" Function
    eval :: Exp -> SWord8
    eval (Val x)   = x
    eval (Sum x y) = eval x + eval y
    
    -- Evaluation by "execution"
    type Stack  = SList Word8
    
    run :: Exp -> SWord8
    run e = L.head (eval' e L.nil)
      where eval' :: Exp -> Stack -> Stack
            eval' (Val n)   s = n .: s
            eval' (Sum x y) s = add (eval' y (eval' x s))
    
            add :: Stack -> Stack
            add s = v .: s''
              where (a, s')  = L.uncons s
                    (b, s'') = L.uncons s'
                    v        = a + b
    
    correct :: IO ThmResult
    correct = prove $ do x :: SWord8 <- forall "x"
                         y :: SWord8 <- forall "y"
    
    
                         let pgm = Sum (Val x) (Val y)
    
                             spec    = eval pgm
                             machine = run  pgm
    
                         return $ spec .== machine
    

    当我运行它时,我得到:

    *Main> correct
    Q.E.D.
    

    而且证明几乎不需要时间。您可以通过添加其他运算符、绑定表单、函数调用来轻松扩展它,如果您愿意,整个工作都可以。只要你坚持一个固定的“程序”进行验证,它应该可以正常工作的。

    如果你犯了错误,假设通过减法定义add(将其最后一行修改为准备好的v = a - b),你会得到:

    *Main> correct
    Falsifiable. Counter-example:
      x = 32 :: Word8
      y =  0 :: Word8
    

    我希望这能让您了解 SMT 求解器的当前功能是什么,以及如何通过 SBV 在 Haskell 中使用它们。

    程序合成是一个活跃的研究领域,拥有许多自定义技术和工具。开箱即用的 SMT 求解器不会让您到达那里。但是,如果您确实在 Haskell 中构建了这样一个自定义系统,您可以使用 SBV 访问底层 SMT 求解器,以解决您在此过程中必须处理的许多约束。

    (旁白: SBV 包附带了一个扩展示例,其精神相似但目标不同:https://hackage.haskell.org/package/sbv-8.5/docs/Documentation-SBV-Examples-Strings-SQLInjection.html。该程序展示了如何使用 SBV 和 SMT 求解器来查找 SQL 注入理想化 SQL 实现中的漏洞。这可能会引起一些兴趣,并且更符合 SMT 求解器在实践中的典型使用方式。)

    【讨论】:

      猜你喜欢
      • 2013-06-10
      • 1970-01-01
      • 2021-07-19
      • 2017-10-20
      • 2012-09-02
      • 2020-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多