Skip to content

Commit

Permalink
support compiler plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
luite committed Oct 16, 2016
1 parent 7dd37d4 commit a6b6beb
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 9 deletions.
1 change: 1 addition & 0 deletions ghcjs.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ Library
Compiler.GhcjsHooks,
Compiler.GhcjsPlatform,
Compiler.Info,
Compiler.Plugins,
Compiler.Program,
Compiler.GhcjsProgram,
Compiler.Settings,
Expand Down
1 change: 1 addition & 0 deletions lib/etc/boot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ packages:
- ./boot/deepseq
- ./boot/directory
- ./boot/filepath
- ./boot/ghc
- ./boot/ghc-boot
- ./boot/ghc-boot-th
- ./boot/ghci
Expand Down
1 change: 1 addition & 0 deletions src-bin/Boot.hs
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,7 @@ installRts = subTop' "ghcjs-boot" $ do
#else
cp (ghcLib </> exe "touchy") (ghcjsLib </> exe "touchy")
#endif
writefile (ghcjsLib </> "ghc_libdir") (toTextI ghcLib)
msg info "RTS prepared"

installPlatformIncludes :: FilePath -> FilePath -> B ()
Expand Down
11 changes: 2 additions & 9 deletions src/Compiler/GhcjsHooks.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import System.FilePath
import Compiler.Settings
import qualified Compiler.Utils as Utils
import Compiler.Variants
import qualified Compiler.Plugins as Plugins

import qualified Gen2.DynamicLinking as Gen2
import qualified Gen2.Foreign as Gen2
Expand All @@ -38,8 +39,6 @@ import qualified Gen2.TH as Gen2TH

import System.IO.Error

import TcRnTypes

#if __GLASGOW_HASKELL__ >= 711
import qualified GHC.LanguageExtensions as Ext
#endif
Expand All @@ -53,14 +52,8 @@ installGhcjsHooks env settings js_objs dflags =
where
addHooks h = h
{ linkHook = Just (Gen2.ghcjsLink env settings js_objs True)
#if __GLASGOW_HASKELL__ < 711
, getValueSafelyHook = Just (Gen2TH.ghcjsGetValueSafely settings)
#endif
#if __GLASGOW_HASKELL__ >= 709
, getValueSafelyHook = Just (Plugins.getValueSafely dflags env)
, runMetaHook = Just (Gen2TH.ghcjsRunMeta env settings)
#else
, hscCompileCoreExprHook = Just (Gen2TH.ghcjsCompileCoreExpr env settings)
#endif
}

installNativeHooks :: GhcjsEnv -> GhcjsSettings -> DynFlags -> DynFlags
Expand Down
233 changes: 233 additions & 0 deletions src/Compiler/Plugins.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
{-
Compiler plugins for GHCJS
Since GHCJS is a cross-compiler, it cannot execute the code that it
generates directly into its own process.
We can get around this for Template Haskell and GHCJSi by running the
code with an external interpreter. It is possible to do this relatively
efficiently because the code can only access a specific subset of GHC's
data through a small and well-defined API: The Quasi typeclass hides
the implementation.
Plugins on the other hand, can see much more, and the external
interpreter approach would require expensive serialization and
synchronisation. Fortunately, plugins are usually relatively self-contained,
so we use another approach:
When a plugin is needed, GHCJS finds the closest match of the
corresponding package in the GHC package database and loads that
instead. The package still needs to be installed in the GHCJS package
db. The plugin implementation would likely require some conditional
compilation because much of the GHC API doesn't exist on ghcjs_HOST_OS.
Since our tools don't yet know about compilers that use packages for
multiple architectures at the same time, matching the package is done
in a rather roundabout way, and it's likely to change in the future.
GHC Package Environment:
- The file `ghc_libdir` in the GHCJS library directory contains the full
path of a GHC library directory. The GHC version must be the same as
the one that GHCJS was compiled with. This location is used for the
global package db.
- If the user package db is visible, GHCJS will use GHC's user package
db for plugins.
- For custom package db locations, for example in a Cabal sandbox, the
GHCJS target triplet is replaced by the triplet for the underlying
GHC.
Package Matching:
When a plugin module is specified, GHCJS first finds the package with this
module in its own package environment. Once the package id is known,
GHCJS tries to find the closest match among the visible GHC packages,
trying in this order:
1. package-id (exact match with the same unit id)
2. package (exact version number match)
A package does not match if it doesn't have the same version number.
-}

module Compiler.Plugins where

import DynFlags
import HscTypes
import Id
import Module
import Name
import Packages
import Type
import Outputable
import HscMain
import Panic
import GHCi
import FastString
import Linker
import DynamicLoading hiding (getValueSafely, getHValueSafely)
import GHCi.RemoteTypes
import qualified SysTools
import System.FilePath
import Control.Monad.IO.Class
import Compiler.Settings
import Data.Char (isSpace)
import Data.List
import Data.Maybe

import Control.Concurrent.MVar

import qualified Compiler.Info as Info

import NameEnv
import Data.IORef -- debug
import LoadIface
import Control.Monad
import RdrName
import SrcLoc
import TcRnMonad

getValueSafely :: DynFlags -> GhcjsEnv
-> HscEnv -> Name -> Type -> IO (Maybe a)
getValueSafely orig_dflags js_env hsc_env val_name expected_type = do
mb_hval <- getHValueSafely orig_dflags js_env hsc_env val_name expected_type
case mb_hval of
Nothing -> return Nothing
Just hval -> do
value <- lessUnsafeCoerce dflags "getValueSafely" hval
return (Just value)
where
dflags = hsc_dflags hsc_env

getHValueSafely :: DynFlags -> GhcjsEnv
-> HscEnv -> Name -> Type -> IO (Maybe HValue)
getHValueSafely orig_dflags js_env hsc_env orig_name expected_type = do
-- initialize the GHC package environment
plugins_env <- modifyMVar (pluginState js_env) (initPluginsEnv orig_dflags)

let dflags = hsc_dflags plugins_env
doc = text "contains a name used in an invocation of getHValueSafely"
val_name0 <- remapName hsc_env plugins_env orig_name

-- We now have an intermediate name that has the correct unit id for the GHC
-- package, but it still has the GHCJS unique. Here we load the interface
-- file and then find the the actual GHC name in the module exports.
let mod = nameModule val_name0
(_, Just val_iface) <- initTcInteractive plugins_env $ initIfaceTcRn $ loadPluginInterface doc mod
let mod_name = moduleName mod
rdr_name = mkRdrUnqual (nameOccName val_name0)
decl_spec = ImpDeclSpec { is_mod = mod_name, is_as = mod_name
, is_qual = False, is_dloc = noSrcSpan }
imp_spec = ImpSpec decl_spec ImpAll
env = mkGlobalRdrEnv (gresFromAvails (Just imp_spec) (mi_exports val_iface))
val_name = case lookupGRE_RdrName rdr_name env of
[gre] -> gre_name gre
_ -> panic "lookupRdrNameInModule"

-- Now look up the names for the value and type constructor in the type environment
mb_val_thing <- lookupTypeHscEnv plugins_env val_name
case mb_val_thing of
Nothing -> throwCmdLineErrorS dflags (missingTyThingErrorGHC val_name)
Just (AnId id) -> do
-- Check the value type in the interface against the type recovered from the type constructor
-- before finally casting the value to the type we assume corresponds to that constructor
if expected_type `eqType` idType id
then do
-- Link in the module that contains the value, if it has such a module
case nameModule_maybe val_name of
Just mod -> do linkModule plugins_env mod
return ()
Nothing -> return ()
-- Find the value that we just linked in and cast it given that we have proved its type
hval <- getHValue plugins_env val_name >>= wormhole dflags
return (Just hval)
else return Nothing
Just val_thing -> throwCmdLineErrorS dflags (wrongTyThingError val_name val_thing)

remapName :: HscEnv -> HscEnv -> Name -> IO Name
remapName src_env tgt_env val_name
| Just m <- nameModule_maybe val_name =
case remapUnit sdf tdf (moduleName m) (moduleUnitId m) of
Nothing ->
throwCmdLineErrorS (hsc_dflags tgt_env) $ missingTyThingErrorGHC val_name
Just tgt_unitid ->
let new_m = mkModule tgt_unitid (moduleName m)
in pure $ mkExternalName (nameUnique val_name) new_m
(nameOccName val_name) (nameSrcSpan val_name)
| otherwise = pure val_name
where
sdf = hsc_dflags src_env
tdf = hsc_dflags tgt_env

remapUnit :: DynFlags -> DynFlags -> ModuleName -> UnitId -> Maybe UnitId
remapUnit src_dflags tgt_dflags module_name unit
-- first try package with same unit id if possible
| Just _ <- lookupPackage tgt_dflags unit = Just unit
-- otherwise look up package with same package id (e.g. foo-0.1)
| Just src_config <- lookupPackage src_dflags unit
, tgt_config:_ <- searchPackageId tgt_dflags (sourcePackageId src_config) =
Just (unitId tgt_config)
| otherwise = Nothing

initPluginsEnv :: DynFlags -> Maybe HscEnv -> IO (Maybe HscEnv, HscEnv)
initPluginsEnv _ (Just env) = pure (Just env, env)
initPluginsEnv orig_dflags _ = do
let trim = let f = reverse . dropWhile isSpace in f . f
ghcTopDir <- readFile (topDir orig_dflags </> "ghc_libdir")
ghcSettings <- SysTools.initSysTools (Just $ trim ghcTopDir)
let dflags0 = orig_dflags { settings = ghcSettings }
dflags1 = gopt_unset dflags0 Opt_HideAllPackages
dflags2 = updateWays $
dflags1 { packageFlags = filterPackageFlags (packageFlags dflags1)
, extraPkgConfs = filterPackageConfs . extraPkgConfs dflags1
, ways = filter (/= WayCustom "js") (ways dflags1)
}
(dflags, units) <- initPackages dflags2
env <- newHscEnv dflags
pure (Just env, env)

filterPackageFlags :: [PackageFlag] -> [PackageFlag]
filterPackageFlags = map fixPkg
where
fixPkg (ExposePackage xs (UnitIdArg pkg) mr) = ExposePackage xs (PackageArg (unitToPkg pkg)) mr
fixPkg x = x

unitToPkg :: String -> String
unitToPkg xs
| ('-':ys) <- dropWhile (/='-') (reverse xs) = reverse ys
| otherwise = xs

filterPackageConfs :: [PkgConfRef] -> [PkgConfRef]
filterPackageConfs = mapMaybe fixPkgConf
where
dtu '.' = '_'
dtu x = x
ghcjsConf = "-ghcjs-" ++ Info.getCompilerVersion ++
"-ghc" ++ map dtu Info.getGhcCompilerVersion ++
"-packages.conf.d"
ghcConf = "-ghc-" ++ Info.getGhcCompilerVersion ++
"-packages.conf.d"
fixPkgConf (PkgConfFile file)
| ghcjsConf `isSuffixOf` file = Just (PkgConfFile $
(reverse . drop (length ghcjsConf) . reverse $ file) ++ ghcConf)
| "package.conf.inplace" `isSuffixOf` file = Nothing
fixPkgConf x = Just x


wrongTyThingError :: Name -> TyThing -> SDoc
wrongTyThingError name got_thing = hsep [text "The name", ppr name, ptext (sLit "is not that of a value but rather a"), pprTyThingCategory got_thing]

missingTyThingError :: Name -> SDoc
missingTyThingError name = hsep [text "The name", ppr name, ptext (sLit "is not in the type environment: are you sure it exists?")]

missingTyThingErrorGHC :: Name -> SDoc
missingTyThingErrorGHC name = hsep [text "The name", ppr name, ptext (sLit "is not in the GHC type environment: are you sure it exists?")]

throwCmdLineErrorS :: DynFlags -> SDoc -> IO a
throwCmdLineErrorS dflags = throwCmdLineError . showSDoc dflags

throwCmdLineError :: String -> IO a
throwCmdLineError = throwGhcExceptionIO . CmdLineError
3 changes: 3 additions & 0 deletions src/Compiler/Settings.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import System.Process
import Module
import TcRnTypes
import ErrUtils
import HscTypes

{- | We can link incrementally against a base bundle, where we assume
that the symbols from the bundle and their dependencies have already
Expand Down Expand Up @@ -124,13 +125,15 @@ data GhcjsEnv = GhcjsEnv
(Object.Deps, DepsLocation), [(Object.Package, Text, Int)]
)
)
, pluginState :: MVar (Maybe HscEnv)
}

newGhcjsEnv :: IO GhcjsEnv
newGhcjsEnv = GhcjsEnv <$> newMVar M.empty
<*> newMVar M.empty
<*> newMVar 0
<*> newMVar M.empty
<*> newMVar Nothing

-- an object file that's either already in memory (with name) or on disk
data LinkedObj = ObjFile FilePath -- load from this file
Expand Down

0 comments on commit a6b6beb

Please sign in to comment.