Skip to content
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

Modular abstract interpretation #668

Merged
merged 92 commits into from
Apr 11, 2022
Merged

Modular abstract interpretation #668

merged 92 commits into from
Apr 11, 2022

Conversation

robrix
Copy link
Contributor

@robrix robrix commented Feb 25, 2022

This PR:

Base automatically changed from rhea-range to master March 1, 2022 17:34
@robrix robrix marked this pull request as ready for review April 4, 2022 19:09
@robrix robrix requested a review from a team as a code owner April 4, 2022 19:09
Copy link
Contributor Author

@robrix robrix left a comment

Choose a reason for hiding this comment

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

I intend to fix the export problem in a follow-up PR; this is, therefore, ready for review.

Comment on lines +35 to +37
-- 9.2+
:seti -Wno-missing-kind-signatures
:seti -Wno-missing-signatures
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 now support 9.2, but only for semantic-analysis (for now).

Comment on lines +9 to +28
(identifier) @this
{
node @id.node
node @this.node
attr (@this.node) type = "identifier"
var @this.text = (source-text @this)
attr (@this.node) text = (source-text @this)
}

(import_statement) @this
{
node @this.node
attr (@this.node) type = "import"
}

(import_statement name: (dotted_name (identifier) @id)) @this
{
edge @this.node -> @id.node
attr (@id.node) role = "module-name-fragment"
attr (@this.node -> @id.node) index = (named-child-index @id)
attr (@this.node -> @id.node) text = @id.text
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 lets us import imports 😊

=> (term -> m ExcSet)
-> (term -> m ExcSet) )
-> File term
-> m (File (Module ExcSet))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that this is the first of our analyses to return a Module (and thus to support modular analysis). Unfortunately, because we don't yet track exports, we can't do linking properly.

Comment on lines +34 to +35
runStatement :: ([Message] -> a -> m r) -> StatementC m a -> m r
runStatement k (StatementC m) = runState (k . reverse) [] 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.

Statement effects just track sequential, imperative directives, currently limited to imports. This collects them into Messages which are then returned via this handler.

Comment on lines +94 to +101
runFiles
:: (forall sig m . Has (State (MStore value)) sig m => File term -> m (File result))
-> [File term]
-> (MStore value, [File result])
runFiles runFile
= run
. runStoreState
. traverse runFile
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 convenience wraps up a pattern common among the (0-CFA) analyses into a convenient bundle. It's also a great example of exposing effects while hiding carriers, the essence of abstraction.

Comment on lines 1 to 27
module Analysis.Module
( Module(..)
, ModuleSet(..)
, link
) where

import Analysis.Name
import Data.Foldable (foldl')
import qualified Data.Map as Map
import qualified Data.Set as Set

data Module a = Module
{ body :: Map.Map Name a -> a
, imports :: Set.Set Name
, exports :: Map.Map Name a
, unknown :: Set.Set Name
}

newtype ModuleSet a = ModuleSet { getModuleSet :: Map.Map Name (Module a) }

instance Semigroup (ModuleSet a) where
m1 <> m2 = ModuleSet ((link m2 <$> getModuleSet m1) <> (link m1 <$> getModuleSet m2))

link :: ModuleSet a -> Module a -> Module a
link (ModuleSet ms) m = Module b' (imports m Set.\\ Map.keysSet ms) (exports m) u' where
(u', b') = foldl' (\ (u, b) -> resolve u b . exports) (unknown m, body m) (Map.restrictKeys ms (imports m))
resolve u b e = (u Set.\\ Map.keysSet e, b . mappend (Map.restrictKeys e u))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Brought in verbatim from membrane. I intend to tidy this up in a future PR, as it's somewhat unreadable as-is.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, it’s a bit dense. Would you mind taking a run at it now? Even pulling out and naming the fold function would help a bit.

Comment on lines +53 to +62
data Term
= Var Name
| Noop
| Iff Term Term Term
| Bool Bool
| String Text
| Throw Term
| Let Name Term Term
| Import (NonEmpty Text)
deriving (Eq, Ord, Show)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The tagless final class, and the Print and Interpret instantiations of it are gone, because defining an Ord instance for Interpret would have been agonizing. Instead, we parse into a first-order, initial encoding: plain old data.

Copy link
Contributor

Choose a reason for hiding this comment

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

Plain old data is (usually) my favorite kind of data!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's just, it was so nice! But too clever by half.

@@ -134,48 +106,66 @@ let' n v m = do

-- Parsing

parseFile :: Syntax rep => FilePath -> IO (Either (A.JSONPath, String) (Maybe rep))
parseFile :: (Has (Throw String) sig m, MonadIO m) => FilePath -> m (File Term)
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 also parse into a File, which is convenient since we use those for analysis (so that we can service file/line/etc directives).

pure (index, node, node <$ guard (ty == "module")))

parseType :: A.Object -> [A.Value] -> String -> A.Parser (IntMap.IntMap Term -> Term)
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 pulled this and the rest of parseNode's where clause to the top level, mostly to make it tidier and give them type signatures. The only real change otherwise is using Term constructors in parseType here instead of Syntax methods.

Comment on lines +1 to +4
# 0.1.0.2

- Support ghc 9.2.

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 bumped semantic-source, which turned out to be rather confusing, but eh, it's out there now.


-- Messages

data Message
Copy link
Contributor

Choose a reason for hiding this comment

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

newtype here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Expectation is that there'll be other message types, but in the meantime, yeah, good call.

import Data.List.NonEmpty (NonEmpty)
import Data.Text

-- Statement effect
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you expand a little on this? Where is this intended to be used, etc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍🏻

Comment on lines 1 to 27
module Analysis.Module
( Module(..)
, ModuleSet(..)
, link
) where

import Analysis.Name
import Data.Foldable (foldl')
import qualified Data.Map as Map
import qualified Data.Set as Set

data Module a = Module
{ body :: Map.Map Name a -> a
, imports :: Set.Set Name
, exports :: Map.Map Name a
, unknown :: Set.Set Name
}

newtype ModuleSet a = ModuleSet { getModuleSet :: Map.Map Name (Module a) }

instance Semigroup (ModuleSet a) where
m1 <> m2 = ModuleSet ((link m2 <$> getModuleSet m1) <> (link m1 <$> getModuleSet m2))

link :: ModuleSet a -> Module a -> Module a
link (ModuleSet ms) m = Module b' (imports m Set.\\ Map.keysSet ms) (exports m) u' where
(u', b') = foldl' (\ (u, b) -> resolve u b . exports) (unknown m, body m) (Map.restrictKeys ms (imports m))
resolve u b e = (u Set.\\ Map.keysSet e, b . mappend (Map.restrictKeys e u))
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, it’s a bit dense. Would you mind taking a run at it now? Even pulling out and naming the fold function would help a bit.

Comment on lines +53 to +62
data Term
= Var Name
| Noop
| Iff Term Term Term
| Bool Bool
| String Text
| Throw Term
| Let Name Term Term
| Import (NonEmpty Text)
deriving (Eq, Ord, Show)
Copy link
Contributor

Choose a reason for hiding this comment

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

Plain old data is (usually) my favorite kind of data!

let' n v' (eval (b (Interpret (pure (pure v'))))))
evalModule :: (Has (Env addr) sig m, HasLabelled Store (Store addr val) sig m, Has (Dom val) sig m) => (Term -> S.StatementC m val) -> (Term -> m (Module val))
evalModule f i = S.runStatement mk (eval f i) where
mk msgs b = pure (Module (const b) (Set.fromList (map (\ (S.Import cs) -> name (Text.intercalate (pack ".") (toList cs))) msgs)) mempty mempty)
Copy link
Contributor

Choose a reason for hiding this comment

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

Some let-bindings here would go a long way.

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 think I'm going to 🔥 them instead, as there's no way to provide the body, unknowns, and exports independent of the domain type anyway.

Comment on lines +115 to +116
-- FIXME: this should get the path to the source file, not the path to the JSON.
-- FIXME: this should use the span of the source file, not an empty span.
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you want to fix these now or later?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Later, definitely.

@robrix robrix merged commit 4cfe7cc into master Apr 11, 2022
@robrix robrix deleted the modular-mechanics branch April 11, 2022 15:20
@robrix
Copy link
Contributor Author

robrix commented Apr 11, 2022

not today! I’ve got LEGS, motherfucker!

by Allie Brosh, Hyperbole and a Half

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants