@@ -365,6 +365,10 @@ zipWithFlatTermF f = go
365
365
366
366
-- Term Functor ----------------------------------------------------------------
367
367
368
+ -- | A \"knot-tying\" structure for representing terms and term-like things.
369
+ -- Often, this appears in context as the type \"'TermF' 'Term'\", in which case
370
+ -- it represents a full 'Term' AST. The \"F\" stands for 'Functor', or
371
+ -- occasionally for \"Former\".
368
372
data TermF e
369
373
= FTermF ! (FlatTermF e )
370
374
-- ^ The atomic, or builtin, term constructs
@@ -381,24 +385,77 @@ data TermF e
381
385
-- The body and type should be closed terms.
382
386
deriving (Eq , Ord , Show , Functor , Foldable , Traversable , Generic )
383
387
388
+ -- See the commentary on 'Hashable Term' for a note on uniqueness.
384
389
instance Hashable e => Hashable (TermF e ) -- automatically derived.
390
+ -- NB: we may someday wish to customize this instance, for a couple reasons.
391
+ --
392
+ -- 1. Hash 'Constant's based on their definition, if it exists, rather than
393
+ -- always using both their type and definition (as the automatically derived
394
+ -- instance does). Their type, represented as an 'ExtCns', contains unavoidable
395
+ -- freshness derived from a global counter (via 'scFreshGlobalVar' as
396
+ -- initialized in 'Verifier.SAW.SharedTerm.mkSharedContext'), but their
397
+ -- definition does not necessarily contain the same freshness.
398
+ --
399
+ -- 2. Improve the default, XOR-based hashing scheme to improve collision
400
+ -- resistance. A polynomial-based approach may be fruitful. For a constructor
401
+ -- with fields numbered 1..n, evaluate a polynomial along the lines of:
402
+ -- coeff(0) * salt ^ 0 + coeff(1) + salt ^ 1 + ... + coeff(n) * salt ^ n
403
+ -- where
404
+ -- coeff(0) = salt `hashWithSalt` <custom per-constructor salt>
405
+ -- coeff(i) = salt `hashWithSalt` <field i>
385
406
386
407
387
408
-- Term Datatype ---------------------------------------------------------------
388
409
389
410
type TermIndex = Int -- Word64
390
411
412
+ -- | For more information on the semantics of 'Term's, see the
413
+ -- [manual](https://saw.galois.com/manual.html). 'Term' and 'TermF' are split
414
+ -- into two structures to facilitate mutual structural recursion (sometimes
415
+ -- referred to as the ["knot-tying"](https://wiki.haskell.org/Tying_the_Knot)
416
+ -- pattern, sometimes referred to in terms of ["recursion
417
+ -- schemes"](https://blog.sumtypeofway.com/posts/introduction-to-recursion-schemes.html))
418
+ -- and term object reuse via hash-consing.
391
419
data Term
392
420
= STApp
421
+ -- ^ This constructor \"wraps\" a 'TermF' 'Term', assigning it a
422
+ -- guaranteed-unique integer identifier and caching its likely-unique hash.
423
+ -- Most 'Term's are constructed via 'STApp'. When a fresh 'TermF' is evinced
424
+ -- in the course of a SAW invocation and needs to be lifted into a 'Term',
425
+ -- we can see if we've already created a 'Term' wrapper for an identical
426
+ -- 'TermF', and reuse it if so. The implementation of hash-consed 'Term'
427
+ -- construction exists in 'Verifier.SAW.SharedTerm', in particular in the
428
+ -- 'Verifier.SAW.SharedTerm.scTermF' field of the
429
+ -- t'Verifier.SAW.SharedTerm.SharedContext' object.
393
430
{ stAppIndex :: {-# UNPACK #-} ! TermIndex
394
- , stAppFreeVars :: ! BitSet -- Free variables
431
+ -- ^ The UID associated with a 'Term'. It is guaranteed unique across a
432
+ -- universe of properly-constructed 'Term's within a single SAW
433
+ -- invocation.
434
+ , stAppHash :: {-# UNPACK #-} ! Int
435
+ -- ^ The hash, according to 'hash', of the 'stAppTermF' field associated
436
+ -- with this 'Term'. This should be as unique as a hash can be, but is
437
+ -- not guaranteed unique as 'stAppIndex' is.
438
+ , stAppFreeVars :: ! BitSet
439
+ -- ^ The free variables associated with the 'stAppTermF' field.
395
440
, stAppTermF :: ! (TermF Term )
441
+ -- ^ The underlying 'TermF' that this 'Term' wraps. This field "ties the
442
+ -- knot" of the 'Term'/'TermF' recursion scheme.
396
443
}
397
444
| Unshared ! (TermF Term )
445
+ -- ^ Used for constructing 'Term's that don't need to be shared/reused.
398
446
deriving (Show , Typeable )
399
447
400
448
instance Hashable Term where
401
- hashWithSalt salt STApp { stAppIndex = i } = salt `combine` 0x00000000 `hashWithSalt` hash i
449
+ -- Why have 'Hashable' depend on the not-necessarily-unique hash instead of
450
+ -- the necessarily-unique index? Per #1830 (PR) and #1831 (issue), we want to
451
+ -- be able to derive a reference to terms based solely on their shape. Indices
452
+ -- have nothing to do with a term's shape - they're assigned sequentially when
453
+ -- building terms, according to the (arbitrary) order in which a term is
454
+ -- built. As for uniqueness, though hashing a term based on its subterms'
455
+ -- hashes introduces less randomness/freshness, it maintains plenty, and
456
+ -- provides benefits as described above. No code should ever rely on total
457
+ -- uniqueness of hashes, and terms are no exception.
458
+ hashWithSalt salt STApp { stAppHash = h } = salt `combine` 0x00000000 `hashWithSalt` h
402
459
hashWithSalt salt (Unshared t) = salt `combine` 0x55555555 `hashWithSalt` hash t
403
460
404
461
@@ -408,6 +465,15 @@ combine :: Int -> Int -> Int
408
465
combine h1 h2 = (h1 * 0x01000193 ) `xor` h2
409
466
410
467
instance Eq Term where
468
+ -- Note: we take some minor liberties with the contract of 'hashWithSalt' in
469
+ -- this implementation of 'Eq'. The contract states that if two values are
470
+ -- equal according to '==', then they must have the same hash. For terms
471
+ -- constructed by/within SAW, this will hold, because SAW's handling of index
472
+ -- generation and assignment ensures that equality of indices implies equality
473
+ -- of terms and term hashes (see 'Verifier.SAW.SharedTerm.getTerm'). However,
474
+ -- if terms are constructed outside this standard procedure or in a way that
475
+ -- does not respect index uniqueness rules, 'hashWithSalt''s contract could be
476
+ -- violated.
411
477
(==) = alphaEquiv
412
478
413
479
alphaEquiv :: Term -> Term -> Bool
0 commit comments