-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tagless representation for Term #15
Open
fizruk
wants to merge
3
commits into
master
Choose a base branch
from
tagless-term
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
{-# LANGUAGE BangPatterns #-} | ||
{-# LANGUAGE MagicHash #-} | ||
-- | "Tagless" sum types allow to skip explicit tag for at least one of the alternatives in a sum type, | ||
-- assuming that we can differentiate between the alternatives at runtime by other means. | ||
-- | ||
-- The benefit of this representation is pronounced for recursive data types: | ||
-- | ||
-- * In the fastest miniKanren implementations, this approach is used | ||
-- to avoid expensive recursive injection of a regular (non-logical) value into a miniKanren program. | ||
-- See Section 6 of [1] for more details about typed implementation of tagless approach in OCanren. | ||
-- Note that faster-minikanren implementation (in Scheme/Racket) is also using a similar technique in an untyped setting. | ||
-- | ||
-- * An efficient bignum representation can be done by "unboxing" the (small) integer constructor. | ||
-- See the details of this example in [2]. | ||
-- | ||
-- This "tagless" representation approach is a variation of | ||
-- * niche filling optimization [3] (which can be found in Rust, for instance) | ||
-- * unboxed constructors [2] | ||
-- | ||
-- See also "flat Maybe" implementation by Andras Kovacs: <https://github.com/AndrasKovacs/flat-maybe> | ||
-- | ||
-- Note that this approach is NOT the same as unboxed sum typed in Haskell (at least not in their current form). | ||
-- | ||
-- [1] Dmitrii Kosarev, Dmitry Boulytchev. Typed Embedding of a Relational Language in OCaml <https://arxiv.org/abs/1805.11006> | ||
-- [2] Nicolas Chataing, Stephen Dolan, Gabriel Scherer, and Jeremy Yallop. 2024. Unboxed Data Constructors: Or, How cpp Decides a Halting Problem. Proc. ACM Program. Lang. 8, POPL, Article 51 (January 2024), 31 pages. https://doi.org/10.1145/3632893 | ||
-- [3] Bartell-Mangel, N. L. (2022). Filling a Niche: Using Spare Bits to Optimize Data Representations. | ||
module Kanren.Tagless (Tagless, tagless, extra, inspectTagless, inspectAsTagless) where | ||
|
||
import Control.DeepSeq (NFData (rnf)) | ||
import qualified Foreign.Ptr as Foreign | ||
import qualified Foreign.StablePtr as Foreign | ||
import GHC.Exts (Int (I#), closureSize#) | ||
import System.IO.Unsafe (unsafePerformIO) | ||
import Unsafe.Coerce (unsafeCoerce) | ||
|
||
-- | A value of type 'Anchor' is used | ||
-- as a runtime tag for the extra data in the 'Tagless' sum type. | ||
type Anchor = Int | ||
|
||
-- | The anchor | ||
theAnchor :: Anchor | ||
{-# NOINLINE theAnchor #-} | ||
theAnchor = unsafePerformIO $ do | ||
sptr <- Foreign.newStablePtr theAnchor | ||
let ptr = Foreign.castStablePtrToPtr sptr | ||
Foreign.IntPtr n = Foreign.ptrToIntPtr ptr | ||
return $! (n - 2837641298) -- scrambled integer value of a stable pointer to the anchor itself | ||
-- the hope is that it is sufficiently unique and will not interfere with any user data | ||
|
||
-- | Extra data packed with an 'Anchor' (a runtime tag, specifying that these are indeed extra data). | ||
data Extra extra = Extra | ||
{ _extraAnchor :: {-# UNPACK #-} !Anchor | ||
, _getExtra :: !extra | ||
} | ||
|
||
-- | Make a sum type, but do not use any runtime tag for @a@. | ||
-- This effectively allows injecting (tagged) values of type @extra@ | ||
-- into the type of @a@, meaning that coercion from @a@ to @'Tagless' extra a@ is zero-cost. | ||
newtype Tagless extra a = Tagless a | ||
|
||
instance (Eq extra, Eq a) => Eq (Tagless extra a) where | ||
x == y = inspectTagless x == inspectTagless y | ||
|
||
instance (NFData extra, NFData a) => NFData (Tagless extra a) where | ||
rnf x = case inspectTagless x of | ||
Left l -> rnf l | ||
Right r -> rnf r | ||
|
||
-- | Returns the size of the given closure in machine words. | ||
-- | ||
-- A wrapper around 'closureSize#'. | ||
-- | ||
-- Note that in many situations it will return the size of a thunk. | ||
-- This might be especially surprising/inconsistent when dealing with polymorphic constants: | ||
-- | ||
-- >>> closureSize ([] :: [Int]) | ||
-- 4 | ||
-- >>> closureSize $! ([] :: [Int]) | ||
-- 2 | ||
-- >>> closureSize (Nothing :: Maybe Int) | ||
-- 2 | ||
-- >>> >>> closureSize $! (Nothing :: Maybe Int) | ||
-- 2 | ||
closureSize :: a -> Int | ||
closureSize x = I# (closureSize# x) | ||
|
||
-- | Inspect a value of type @a@ to see if it contains @extra@ data | ||
-- via 'Tagless' representation. | ||
inspectAsTagless :: a -> Either extra a | ||
{-# INLINE inspectAsTagless #-} | ||
inspectAsTagless = inspectTagless . tagless | ||
|
||
-- | Check if a given 'Tagless' sum type is @extra@ or not. | ||
-- | ||
-- >>> inspectTagless (extra 123 :: Tagless Int [Int]) | ||
-- Left 123 | ||
-- >>> inspectTagless (tagless [] :: Tagless Int [Int]) | ||
-- Right [] | ||
-- >>> inspectTagless (tagless [123] :: Tagless Int [Int]) | ||
-- Right [123] | ||
inspectTagless :: Tagless extra a -> Either extra a | ||
{-# NOINLINE inspectTagless #-} | ||
inspectTagless (Tagless !x) | ||
| closureSize x < 3 = Right x | ||
| otherwise = | ||
case unsafeCoerce x of | ||
Extra anchor extra_ | anchor == theAnchor -> | ||
Left extra_ | ||
_ -> Right x | ||
|
||
-- | Wrap a value into a 'Tagless' sum type. | ||
-- This operation is zero-cost and amounts to a simple coercion. | ||
tagless :: a -> Tagless extra a | ||
tagless = Tagless | ||
|
||
-- | Wrap @extra@ data into a 'Tagless' sum type. | ||
-- This operation adds a runtime tag which is inspectable via 'inspectTagless'. | ||
extra :: extra -> Tagless extra a | ||
extra x = unsafeCoerce (Extra theAnchor x) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should also have this unboxed and unpacked.