Skip to content
This repository was archived by the owner on Apr 1, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
455990d
Change entrySymbol’s type to Text.
robrix Jul 24, 2019
8c59f9d
Only import the one symbol from Data.Text.
robrix Jul 25, 2019
5a40e01
Derive Eq, Ord, & Show instances for Entry.
robrix Jul 25, 2019
057a4f8
Copy in the import graph definition.
robrix Jul 25, 2019
f6cd84e
Merge branch 'sequence-values-in-the-abstract-domain' into scope-graphs
robrix Jul 25, 2019
53e08b3
:fire: the specialization of String in the scope graph.
robrix Jul 25, 2019
3654aac
:fire: the parent addr from the import & scope graph analyses.
robrix Jul 25, 2019
b8f7bb0
Don’t provide a current FrameId.
robrix Jul 25, 2019
cb5b0fb
Don’t allocate a pointless heap cell.
robrix Jul 25, 2019
09f2362
Abstract closures to their graphs.
robrix Jul 26, 2019
0c10686
Record where names were bound.
robrix Jul 26, 2019
16b1442
Simplify deref slightly.
robrix Jul 26, 2019
99a4f8e
Write an empty scope graph into the heap during abstraction.
robrix Jul 26, 2019
4296244
Merge branch 'generalize-analyses-over-the-term-type' into scope-graphs
robrix Jul 29, 2019
3bafa9a
Generalize runFile over the term type.
robrix Jul 29, 2019
c7bee13
Generalize scopeGraph over the term type.
robrix Jul 29, 2019
a04ccbb
Write record fields to the heap in the abstract analyses.
robrix Jul 29, 2019
34da6ad
Rename a couple of copy-pasta’d variables.
robrix Jul 29, 2019
3bf6cb6
Export ScopeGraph’s constructor & field.
robrix Jul 29, 2019
6cb9af2
Correct the Monoid instance for ScopeGraph.
robrix Jul 29, 2019
a4066b9
Construct scope graphs on deref.
robrix Jul 29, 2019
ed94104
Map entries to references.
robrix Jul 29, 2019
a73b267
Rename Entry to Decl.
robrix Jul 29, 2019
196f2bf
Add declarations to records.
robrix Jul 29, 2019
100d6a1
Extend the scope graph on assignment.
robrix Jul 30, 2019
e58dea9
Factor out how we extend bindings.
robrix Jul 30, 2019
255cddb
Merge branch 'generalize-analyses-over-the-term-type' into scope-graphs
robrix Jul 30, 2019
901014d
:fire: a redundant FIXME.
robrix Jul 30, 2019
c30e5ff
Merge branch 'master' into scope-graphs
robrix Aug 6, 2019
0515d7c
Let-bind the function extending each value in the cell.
robrix Aug 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion semantic-core/src/Analysis/FlowInsensitive.hs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ cacheTerm eval term = do
result <$ modify (Cache . Map.insertWith (<>) term (Set.singleton (result :: a)) . unCache)

runHeap :: StateC (Heap address a) m b -> m (Heap address a, b)
runHeap m = runState (Map.empty) m
runHeap m = runState Map.empty m

-- | Fold a collection by mapping each element onto an 'Alternative' action.
foldMapA :: (Alternative m, Foldable t) => (b -> m a) -> t b -> m a
Expand Down
10 changes: 7 additions & 3 deletions semantic-core/src/Analysis/ImportGraph.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Control.Effect.Reader
import Control.Effect.State
import Control.Monad ((>=>))
import Data.File
import Data.Foldable (fold)
import Data.Foldable (fold, for_)
import Data.Function (fix)
import Data.List.NonEmpty (nonEmpty)
import Data.Loc
Expand Down Expand Up @@ -103,7 +103,7 @@ importGraphAnalysis = Analysis{..}
bind _ _ m = m
lookupEnv = pure . Just
deref addr = gets (Map.lookup addr >=> nonEmpty . Set.toList) >>= maybe (pure Nothing) (foldMapA (pure . Just))
assign addr ty = modify (Map.insertWith (<>) addr (Set.singleton ty))
assign addr v = modify (Map.insertWith (<>) addr (Set.singleton v))
abstract _ name body = do
loc <- ask
pure (Value (Closure loc name body) mempty)
Expand All @@ -118,5 +118,9 @@ importGraphAnalysis = Analysis{..}
string s = pure (Value (String s) mempty)
asString (Value (String s) _) = pure s
asString _ = pure mempty
record fields = pure (Value Abstract (foldMap (valueGraph . snd) fields))
record fields = do
for_ fields $ \ (k, v) -> do
addr <- alloc k
assign addr v
pure (Value Abstract (foldMap (valueGraph . snd) fields))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We were leaving record fields uninitialized in the abstract analyses, which meant that later lookups would fail.

_ ... m = pure (Just m)
127 changes: 121 additions & 6 deletions semantic-core/src/Analysis/ScopeGraph.hs
Original file line number Diff line number Diff line change
@@ -1,15 +1,130 @@
{-# LANGUAGE FlexibleContexts, OverloadedStrings, RankNTypes, RecordWildCards, TypeApplications, TypeOperators #-}
module Analysis.ScopeGraph
( ScopeGraph
, Entry(..)
( ScopeGraph(..)
, Decl(..)
, scopeGraph
, scopeGraphAnalysis
) where

import Analysis.Eval
import Analysis.FlowInsensitive
import Control.Applicative (Alternative (..))
import Control.Effect.Carrier
import Control.Effect.Fail
import Control.Effect.Fresh
import Control.Effect.Reader
import Control.Effect.State
import Control.Monad ((>=>))
import Data.File
import Data.Foldable (fold)
import Data.Function (fix)
import Data.List.NonEmpty
import Data.Loc
import qualified Data.Map as Map
import Data.Name
import Data.Proxy
import qualified Data.Set as Set
import Data.Text (Text)
import Data.Traversable (for)
import Prelude hiding (fail)

data Entry = Entry
{ entrySymbol :: String -- FIXME: Text
, entryLoc :: Loc
data Decl = Decl
{ declSymbol :: Text
, declLoc :: Loc
}
deriving (Eq, Ord, Show)

type ScopeGraph = Map.Map Entry (Set.Set Entry)
newtype Ref = Ref Loc
deriving (Eq, Ord, Show)

newtype ScopeGraph = ScopeGraph { unScopeGraph :: Map.Map Decl (Set.Set Ref) }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This model of scope graphs has no recursive structure; references map directly to declarations, and not e.g. through other declarations in a path.

This makes this scope graph strictly less informative than those we’re computing in semantic proper, tho it’s hard to say for sure if that makes it strictly less useful for our purposes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike ImportGraph, our ScopeGraph abstract domain doesn’t require a semi-concrete representation of values alongside it, because we don’t need to treat any of the primitives specially: ImportGraph needs strings and closures modelled explicitly so that it can notice loads issued within methods like Ruby’s require, but ScopeGraph doesn’t require any special treatment of primitives (other than to construct a scope graph).

deriving (Eq, Ord, Show)

instance Semigroup ScopeGraph where
ScopeGraph a <> ScopeGraph b = ScopeGraph (Map.unionWith (<>) a b)

instance Monoid ScopeGraph where
mempty = ScopeGraph Map.empty

scopeGraph
:: Ord term
=> (forall sig m
. (Carrier sig m, Member (Reader Loc) sig, MonadFail m)
=> Analysis term User ScopeGraph m
-> (term -> m ScopeGraph)
-> (term -> m ScopeGraph)
)
-> [File term]
-> (Heap User ScopeGraph, [File (Either (Loc, String) ScopeGraph)])
scopeGraph eval
= run
. runFresh
. runHeap
. traverse (runFile eval)

runFile
:: ( Carrier sig m
, Effect sig
, Member Fresh sig
, Member (State (Heap User ScopeGraph)) sig
, Ord term
)
=> (forall sig m
. (Carrier sig m, Member (Reader Loc) sig, MonadFail m)
=> Analysis term User ScopeGraph m
-> (term -> m ScopeGraph)
-> (term -> m ScopeGraph)
)
-> File term
-> m (File (Either (Loc, String) ScopeGraph))
runFile eval file = traverse run file
where run = runReader (fileLoc file)
. runReader (Map.empty @User @Loc)
. runFailWithLoc
. fmap fold
. convergeTerm (Proxy @User) (fix (cacheTerm . eval scopeGraphAnalysis))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so readable 😍


scopeGraphAnalysis
:: ( Alternative m
, Carrier sig m
, Member (Reader Loc) sig
, Member (Reader (Map.Map User Loc)) sig
, Member (State (Heap User ScopeGraph)) sig
)
=> Analysis term User ScopeGraph m
scopeGraphAnalysis = Analysis{..}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m normally an anti-RecordWildCards partisan, but this is a 100% copacetic use thereof.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it’ll do until such time as we replace these with effects.

where alloc = pure
bind name _ m = do
loc <- ask @Loc
local (Map.insert name loc) m
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binding remembers (locally) the location for the binding site. Unfortunately, this will generally be the location (and especially the Span) of the Ann node surrounding the Lam or :>>=, making it a little imprecise. This could be improved by adding (optional) locations to these nodes for the bound variables, and since Core is curried, we’d do the right thing for multiple parameters automatically.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 File an issue about this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m actually just tackling it in a follow-up PR.

lookupEnv = pure . Just
deref addr = do
ref <- asks Ref
bindLoc <- asks (Map.lookup addr)
cell <- gets (Map.lookup addr >=> nonEmpty . Set.toList)
let extending = mappend (extendBinding addr ref bindLoc)
maybe (pure Nothing) (foldMapA (pure . Just . extending)) cell
assign addr v = do
ref <- asks Ref
bindLoc <- asks (Map.lookup addr)
modify (Map.insertWith (<>) addr (Set.singleton (extendBinding addr ref bindLoc <> v)))
abstract eval name body = do
addr <- alloc name
assign name (mempty @ScopeGraph)
bind name addr (eval body)
apply _ f a = pure (f <> a)
unit = pure mempty
bool _ = pure mempty
asBool _ = pure True <|> pure False
string _ = pure mempty
asString _ = pure mempty
record fields = do
fields' <- for fields $ \ (k, v) -> do
addr <- alloc k
loc <- ask @Loc
let v' = ScopeGraph (Map.singleton (Decl k loc) mempty) <> v
(k, v') <$ assign addr v'
pure (foldMap snd fields')
_ ... m = pure (Just m)

extendBinding addr ref bindLoc = ScopeGraph (maybe Map.empty (\ bindLoc -> Map.singleton (Decl addr bindLoc) (Set.singleton ref)) bindLoc)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bindLoc might be Nothing if e.g. we’re attempting to read a free variable, or if we’re trying to look a name up outside of its lexical binding scope—indirectly, using records. Thus, record field accesses don’t currently get added to the scope graph, which limits the utility of this approach.

8 changes: 7 additions & 1 deletion semantic-core/src/Analysis/Typecheck.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import Data.Scope
import Data.Semigroup (Last (..))
import qualified Data.Set as Set
import Data.Term
import Data.Traversable (for)
import Data.Void
import GHC.Generics (Generic1)
import Prelude hiding (fail)
Expand Down Expand Up @@ -175,7 +176,12 @@ typecheckingAnalysis = Analysis{..}
asBool b = unify (Term Bool) b >> pure True <|> pure False
string _ = pure (Term String)
asString s = unify (Term String) s $> mempty
record fields = pure (Term (Record (Map.fromList fields)))
record fields = do
fields' <- for fields $ \ (k, v) -> do
addr <- alloc k
(k, v) <$ assign addr v
-- FIXME: should records reference types by address instead?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this varies based on language? Not sure, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn’t for the types, but might for values.

pure (Term (Record (Map.fromList fields')))
_ ... m = pure (Just m)


Expand Down