Skip to content
Snippets Groups Projects
Commit c9db1927 authored by Ben Gamari's avatar Ben Gamari :turtle:
Browse files

Merge branch 'factor-out-default-interpreter' into 'master'

Factor out default interpreter

Closes #3

See merge request !7
parents 37861958 37b907a4
No related branches found
No related tags found
2 merge requests!7Factor out default interpreter,!6CCall: Extend to test signed arguments and results
Pipeline #59588 failed
...@@ -19,6 +19,12 @@ There is a convenient script, `run.sh`, which handles this for you; just set ...@@ -19,6 +19,12 @@ There is a convenient script, `run.sh`, which handles this for you; just set
the `BOOT_GHC` environment variable to the bootstrap compiler and `TEST_GHC` to the `BOOT_GHC` environment variable to the bootstrap compiler and `TEST_GHC` to
the compiler-under-test. the compiler-under-test.
## Usage with cross-compilers
`test-primops` can test cross-compilers by running `run.sh` with `TEST_GHC` set
to the path to the cross-compiler and `EMULATOR` to the path to an emulator
which can run a target executable (e.g. `qemu-user` or `wasmtime`).
## REPL usage ## REPL usage
It can often be useful to load the `test-primops` executable into GHCi. It can often be useful to load the `test-primops` executable into GHCi.
......
...@@ -5,6 +5,7 @@ set -e -o pipefail ...@@ -5,6 +5,7 @@ set -e -o pipefail
BOOT_GHC="${BOOT_GHC:-ghc}" BOOT_GHC="${BOOT_GHC:-ghc}"
TEST_GHC="${TEST_GHC:-ghc}" TEST_GHC="${TEST_GHC:-ghc}"
CABAL="${CABAL:-cabal}" CABAL="${CABAL:-cabal}"
EMULATOR="${EMULATOR:-}"
build_runit() { build_runit() {
ALLOW_NEWER="--allow-newer=base" ALLOW_NEWER="--allow-newer=base"
...@@ -15,10 +16,14 @@ build_runit() { ...@@ -15,10 +16,14 @@ build_runit() {
run() { run() {
build_runit build_runit
"$CABAL" run -w "$BOOT_GHC" test-primops -- \ args=(
--ghc-path="$TEST_GHC" \ "--ghc-path=$TEST_GHC"
--run-it-path="$RUNIT" \ "--run-it-path=$RUNIT"
"$@" )
if [[ -n "$EMULATOR" ]]; then
args+=( "--emulator=$EMULATOR" )
fi
"$CABAL" run -w "$BOOT_GHC" test-primops -- "${args[@]}" "$@"
} }
repl() { repl() {
......
...@@ -2,11 +2,9 @@ ...@@ -2,11 +2,9 @@
module CCall module CCall
( CCallDesc(..) ( CCallDesc(..)
, testCCall , testCCall
, evalCCall
) where ) where
import Numeric.Natural
import System.FilePath
import System.IO.Temp
import Test.QuickCheck import Test.QuickCheck
import Expr import Expr
...@@ -14,10 +12,12 @@ import Width ...@@ -14,10 +12,12 @@ import Width
import ToCmm import ToCmm
import RunGhc import RunGhc
import Number import Number
import Compiler
data CCallDesc data CCallDesc
= CCallDesc { callRet :: SomeNumber = CCallDesc { callRet :: SomeNumber
, callArgs :: [SomeNumber] , callRetSignedness :: Signedness
, callArgs :: [(Signedness, SomeNumber)]
} }
deriving (Show) deriving (Show)
...@@ -30,29 +30,36 @@ mAX_ARGS = 32 ...@@ -30,29 +30,36 @@ mAX_ARGS = 32
instance Arbitrary CCallDesc where instance Arbitrary CCallDesc where
arbitrary = do arbitrary = do
ret <- arbitrary ret <- arbitrary
ret_signedness <- arbitrary
n <- chooseInt (0, mAX_ARGS) n <- chooseInt (0, mAX_ARGS)
args <- vectorOf n arbitrary args <- vectorOf n arbitrary
return $ CCallDesc ret args return $ CCallDesc ret ret_signedness args
shrink (CCallDesc ret ret_s args) =
CCallDesc <$> shrink ret <*> pure ret_s <*> shrinkList shrink args
evalCCall
:: EvalMethod
-> CCallDesc
-> IO [Integer]
evalCCall em c = do
cProg <- compileC (compiler em) (cStub c)
cmmProg <- compileCmm (compiler em) (cCallCmm c)
out <- runTestProgram em (cProg <> cmmProg)
let saw :: [Integer]
saw = map read (lines out)
return saw
testCCall testCCall
:: Compiler :: EvalMethod
-> CCallDesc -> CCallDesc
-> Property -> Property
testCCall comp c = testCCall em c = ioProperty $ do
ioProperty $ withTempDirectory "." "tmp" $ \tmpDir -> do saw <- evalCCall em c
writeFile (tmpDir </> "test_c.c") (cStub c) let expected :: [Integer]
writeFile (tmpDir </> "test.cmm") (cCallCmm c) expected = map (\(s, SomeNumber e) -> asInteger s e) (callArgs c) ++ [ret]
compile comp tmpDir ["test_c.c", "test.cmm"] soName ["-shared", "-dynamic"] -- The wrapper zero extends the result so interpret it as unsigned.
out <- runIt comp (tmpDir </> soName) ret = case callRet c of SomeNumber n -> asInteger Unsigned n
let saw :: [Natural] return $ saw === expected
saw = map read (lines out)
expected :: [Natural]
expected = map (\(SomeNumber e) -> toUnsigned e) (callArgs c) ++ [ret]
ret = case callRet c of SomeNumber n -> toUnsigned n
return $ saw === expected
where
soName = "test.so"
cStub :: CCallDesc -> String cStub :: CCallDesc -> String
cStub c cStub c
...@@ -65,11 +72,14 @@ cStub c ...@@ -65,11 +72,14 @@ cStub c
] ]
where where
argBndrs = [ "arg"++show i | (i,_) <- zip [0::Int ..] (callArgs c) ] argBndrs = [ "arg"++show i | (i,_) <- zip [0::Int ..] (callArgs c) ]
argWidths = [ knownWidth @w | SomeNumber (_ :: Number w) <- callArgs c ] argTypes =
[ (signedness, knownWidth @w)
| (signedness, SomeNumber (_ :: Number w)) <- callArgs c
]
funcDef = unlines $ funcDef = unlines $
[ cType (retWidth c) <> " test_c(" <> argList <> ") {" ] ++ [ cType Unsigned (retWidth c) <> " test_c(" <> argList <> ") {" ] ++
zipWith printArg argWidths argBndrs ++ zipWith printArg argTypes argBndrs ++
[ " fflush(stdout);" [ " fflush(stdout);"
, " return " ++ show (someNumberToUnsigned $ callRet c) ++ "ULL;" , " return " ++ show (someNumberToUnsigned $ callRet c) ++ "ULL;"
, "}" , "}"
...@@ -77,39 +87,50 @@ cStub c ...@@ -77,39 +87,50 @@ cStub c
argList = argList =
commaList commaList
[ unwords [cType w, bndr] [ unwords [ty, bndr]
| (w, bndr) <- zip argWidths argBndrs | (ty, bndr) <- zip (map (uncurry cType) argTypes) argBndrs
] ]
printArg w bndr = printArg ty bndr =
" printf(" ++ quoted (formatStr w ++ "\\n") ++ ", " ++ bndr ++ ");" " printf(" ++ quoted (formatStr ty ++ "\\n") ++ ", " ++ bndr ++ ");"
quoted :: String -> String quoted :: String -> String
quoted s = "\"" ++ s ++ "\"" quoted s = "\"" ++ s ++ "\""
formatStr :: Width -> String formatStr :: (Signedness, Width) -> String
formatStr w = formatStr (signedness, w) =
"0x%" ++ quoted ("PRIx"++show n) "%" ++ quoted ("PRI" ++ fmt ++ show n)
where where
fmt = case signedness of
Signed -> "d"
Unsigned -> "u"
n = widthBits w n = widthBits w
cType :: Width -> String cType :: Signedness -> Width -> String
cType W8 = "uint8_t" cType signedness width = prefix ++ "int" ++ show n ++ "_t"
cType W16 = "uint16_t" where
cType W32 = "uint32_t" n = widthBits width
cType W64 = "uint64_t" prefix = case signedness of
Signed -> ""
Unsigned -> "u"
cCallCmm :: CCallDesc -> String cCallCmm :: CCallDesc -> String
cCallCmm c = unlines cCallCmm c = unlines
[ "test("++cmmWordType ++" buffer) {" [ "test("++cmmWordType ++" buffer) {"
, " "++cmmType (retWidth c)++" ret;" , " "++cmmType (retWidth c)++" ret;"
, " (ret) = foreign \"C\" test_c(" ++ argList ++ ");" , " (" ++ retHint ++ "ret) = foreign \"C\" test_c(" ++ argList ++ ");"
, " return ("++widenOp++"(ret));" , " return ("++widenOp++"(ret));"
, "}" , "}"
] ]
where where
retHint = case callRetSignedness c of
Signed -> "\"signed\" "
Unsigned -> ""
widenOp = "%zx" ++ show (widthBits wordSize) widenOp = "%zx" ++ show (widthBits wordSize)
argList = argList =
commaList commaList
[ exprToCmm $ ELit e [ exprToCmm (ELit e) ++ hint
| SomeNumber e <- callArgs c | (signedness, SomeNumber e) <- callArgs c
, let hint = case signedness of
Signed -> " \"signed\""
Unsigned -> ""
] ]
...@@ -24,8 +24,8 @@ import ToCmm ...@@ -24,8 +24,8 @@ import ToCmm
import Number import Number
import Expr import Expr
prop_callish_ops_correct :: Compiler -> TestTree prop_callish_ops_correct :: EvalMethod -> TestTree
prop_callish_ops_correct comp = testGroup "callish ops" prop_callish_ops_correct em = testGroup "callish ops"
[ testCallishOp "popcnt" (\(_ :: Proxy w) -> toProp $ popcnt @w) [ testCallishOp "popcnt" (\(_ :: Proxy w) -> toProp $ popcnt @w)
, testCallishOp "pdep" (\(_ :: Proxy w) -> toProp $ pdep @w) , testCallishOp "pdep" (\(_ :: Proxy w) -> toProp $ pdep @w)
, testCallishOp "pext" (\(_ :: Proxy w) -> toProp $ pext @w) , testCallishOp "pext" (\(_ :: Proxy w) -> toProp $ pext @w)
...@@ -34,7 +34,7 @@ prop_callish_ops_correct comp = testGroup "callish ops" ...@@ -34,7 +34,7 @@ prop_callish_ops_correct comp = testGroup "callish ops"
toProp :: forall args. (CmmArgs args, Arbitrary args, Show args) toProp :: forall args. (CmmArgs args, Arbitrary args, Show args)
=> CallishOp args WordSize => CallishOp args WordSize
-> Property -> Property
toProp op = property $ prop_callish_correct comp op toProp op = property $ prop_callish_correct em op
testCallishOp testCallishOp
:: String :: String
...@@ -92,12 +92,12 @@ fromBits bits = foldl' (.|.) 0 [ bit i | (i, True) <- zip [0..] bits ] ...@@ -92,12 +92,12 @@ fromBits bits = foldl' (.|.) 0 [ bit i | (i, True) <- zip [0..] bits ]
prop_callish_correct prop_callish_correct
:: forall args. (CmmArgs args) :: forall args. (CmmArgs args)
=> Compiler => EvalMethod
-> CallishOp args WordSize -> CallishOp args WordSize
-> args -> args
-> Property -> Property
prop_callish_correct comp op args = counterexample (evalCallishOpCmm op args) $ ioProperty $ do prop_callish_correct em op args = counterexample (evalCallishOpCmm op args) $ ioProperty $ do
r <- evalCallishOp comp op args r <- evalCallishOp em op args
return $ refImpl op args === r return $ refImpl op args === r
data CallishOp args result data CallishOp args result
...@@ -116,12 +116,12 @@ instance (KnownWidth w) => CmmArgs (Expr w) where ...@@ -116,12 +116,12 @@ instance (KnownWidth w) => CmmArgs (Expr w) where
evalCallishOp evalCallishOp
:: forall args. (CmmArgs args) :: forall args. (CmmArgs args)
=> Compiler => EvalMethod
-> CallishOp args WordSize -> CallishOp args WordSize
-> args -> args
-> IO (Number WordSize) -> IO (Number WordSize)
evalCallishOp comp op args = evalCallishOp em op args =
fromUnsigned <$> evalCmm comp (evalCallishOpCmm op args) fromUnsigned <$> evalCmm em (evalCallishOpCmm op args)
evalCallishOpCmm evalCallishOpCmm
:: forall args. (CmmArgs args) :: forall args. (CmmArgs args)
......
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Compiler
( Compiler(..)
, addArgs
, compile
-- * Compiling objects
, compileC
, compileCmm
, compileHs
-- * Test programs
, TestProgram
, writeObjectsIn
) where
import qualified Data.ByteString as BS
import System.Exit
import System.Process
import System.IO.Temp
import System.FilePath
-- | The location of GHC and arguments to pass it.
data Compiler = Compiler { compPath :: FilePath
, compArgs :: [String]
}
deriving (Show)
addArgs :: Compiler -> [String] -> Compiler
addArgs c args = c { compArgs = compArgs c ++ args }
-- | Compile a set of compilation units.
compile :: Compiler
-> FilePath -- ^ working directory
-> [FilePath] -- ^ sources
-> FilePath -- ^ output path
-> [String] -- ^ other arguments
-> IO ()
compile comp workDir srcs out args = do
runProcess' $ inTmp (proc (compPath comp) allArgs)
where
allArgs = compArgs comp ++ srcs ++ args ++ ["-o", out]
inTmp c = c { cwd = Just workDir }
runProcess' p = do
(_, _, _, hdl) <- createProcess p
ExitSuccess <- waitForProcess hdl
return ()
compileHs :: Compiler -> String -> IO TestProgram
compileHs = compileOne "hs"
compileCmm :: Compiler -> String -> IO TestProgram
compileCmm = compileOne "cmm"
compileC :: Compiler -> String -> IO TestProgram
compileC = compileOne "c"
compileOne :: String -> Compiler -> String -> IO TestProgram
compileOne ext comp contents = withTempDirectory "." "tmp" $ \tmpDir -> do
writeFile (tmpDir </> srcName) contents
compile comp tmpDir [srcName] objName ["-c"]
obj <- BS.readFile (tmpDir </> objName)
return (TestProgram [obj])
where
srcName = "test" <.> ext
objName = "out.o"
-- | A set of object files which comprise a test program.
newtype TestProgram = TestProgram { _objects :: [BS.ByteString] }
deriving (Semigroup)
writeObjectsIn :: FilePath -> TestProgram -> IO [FilePath]
writeObjectsIn dir (TestProgram objs) = do
let writeObj :: Integer -> BS.ByteString -> IO FilePath
writeObj i obj = do
let fname = "obj"++show i++".o"
BS.writeFile (dir </> fname) obj
return fname
sequence $ zipWith writeObj [0..] objs
module Interpreter module Interpreter
( Interpreter ( Interpreter
, refInterpreter , refInterpreter
, ghcStaticInterpreter , ghcInterpreter
, ghcDynInterpreter'
-- * Properties of interpreters -- * Properties of interpreters
, agree , agree
, converges , converges
...@@ -23,13 +22,10 @@ type Interpreter w = ...@@ -23,13 +22,10 @@ type Interpreter w =
refInterpreter :: Interpreter w refInterpreter :: Interpreter w
refInterpreter = pure . interpret refInterpreter = pure . interpret
ghcStaticInterpreter :: Compiler -> Interpreter WordSize -- | An 'Interpreter' which compiles and evaluates the given expression.
ghcStaticInterpreter comp e = ghcInterpreter :: EvalMethod -> Interpreter WordSize
fromUnsigned <$> evalGhcStatic comp e ghcInterpreter em e =
fromUnsigned <$> evalExpr em e
ghcDynInterpreter' :: Compiler -> Interpreter WordSize
ghcDynInterpreter' comp e =
fromUnsigned <$> evalGhcDyn comp e
-- | Do two 'Interpreter's agree in their evaluation of the given expression? -- | Do two 'Interpreter's agree in their evaluation of the given expression?
agree agree
......
...@@ -5,47 +5,79 @@ module Main ...@@ -5,47 +5,79 @@ module Main
) where ) where
import Data.Proxy import Data.Proxy
import Data.Tagged
import Test.QuickCheck import Test.QuickCheck
import Test.Tasty import Test.Tasty
import Test.Tasty.Options import Test.Tasty.Options
import Test.Tasty.QuickCheck import Test.Tasty.QuickCheck
import Options.Applicative
import Prelude hiding (truncate) import Prelude hiding (truncate)
import Width
import Number
import Expr
import ToCmm
import Interpreter
import CallishOp
import CCall import CCall
import RunGhc import CallishOp
import Compiler
import Expr
import Expr.Parse import Expr.Parse
import Interpreter
import MulMayOverflow import MulMayOverflow
import Number
import RunGhc
import ToCmm
import Width
newtype UsedEvalMethod = UsedEvalMethod EvalMethod
newtype GhcPath = GhcPath FilePath
instance IsOption GhcPath where
defaultValue = GhcPath "ghc"
parseValue = pure . GhcPath
optionName = return "ghc-path"
optionHelp = return "Path to compiler"
optionCLParser =
GhcPath <$> option str (long "ghc-path" <> help "Path to compiler")
newtype GhcArgs = GhcArgs [String]
instance IsOption GhcArgs where
defaultValue = GhcArgs []
parseValue = pure . GhcArgs . words
optionName = return "ghc-args"
optionHelp = return "Arguments to pass to GHC"
optionCLParser =
fmap GhcArgs $ some $ option str (long "ghc-args" <> help "Arguments to pass to GHC")
newtype RunItPath = RunItPath FilePath
instance IsOption RunItPath where
defaultValue = RunItPath "run-it"
parseValue = pure . RunItPath
optionName = return "run-it-path"
optionHelp = return "Path to run-it executable built with tested compiler"
optionCLParser =
RunItPath <$> option str (long "run-it-path" <> help "Path to run-it")
newtype Emulator = Emulator (Maybe FilePath)
instance IsOption Emulator where
defaultValue = Emulator Nothing
parseValue = pure . Emulator . Just
optionName = return "emulator"
optionHelp = return "Path to emulator to use to run target executables"
optionCLParser =
Emulator . Just <$> option str (long "emulator" <> help "Path to emulator executable")
basicCompiler :: FilePath -> Compiler basicCompiler :: FilePath -> Compiler
basicCompiler ghcPath = basicCompiler ghcPath =
Compiler { compPath = ghcPath Compiler { compPath = ghcPath
, compArgs = ["-dcmm-lint", "-dasm-lint", "-O0"] , compArgs = ["-dcmm-lint", "-dasm-lint", "-O0"]
, compRunIt = "run-it"
} }
compilerConfigs :: FilePath -> [(String, Compiler)]
compilerConfigs ghcPath =
[ ("o0-ncg", c0 `addArgs` ["-O0"])
, ("o1-ncg", c0 `addArgs` ["-O1"])
, ("o0-llvm", c0 `addArgs` ["-O0", "-fllvm"])
, ("o1-llvm", c0 `addArgs` ["-O1", "-fllvm"])
]
where
c0 = basicCompiler ghcPath
-- * Properties -- * Properties
expr_prop :: Compiler -> Expr WordSize -> Property expr_prop :: EvalMethod -> Expr WordSize -> Property
expr_prop comp e = conjoin expr_prop em e = conjoin
[ converges refInterpreter e [ converges refInterpreter e
, agree refInterpreter (ghcDynInterpreter' comp) e , agree refInterpreter (ghcInterpreter em) e
] ]
quotRemProp quotRemProp
...@@ -63,45 +95,39 @@ quotRemProp interp s a (NonZero b) = ioProperty $ do ...@@ -63,45 +95,39 @@ quotRemProp interp s a (NonZero b) = ioProperty $ do
y = ELit b y = ELit b
rhs = ((EQuot s x y) * y) + ERem s x y rhs = ((EQuot s x y) * y) + ERem s x y
compilerTests :: String -> Compiler -> TestTree compilerTests :: EvalMethod -> [TestTree]
compilerTests name comp = testGroup name compilerTests em =
[ testProperty "expression correctness" (expr_prop comp) [ testProperty "expression correctness" (expr_prop em)
, prop_callish_ops_correct comp , prop_callish_ops_correct em
, testProperty "C-Call correctness" (testCCall comp) , testProperty "C-Call correctness" (testCCall em)
, testGroup "Quot-Rem invariant" , testGroup "Quot-Rem invariant"
[ testProperty (show (knownWidth @w)) [ testProperty (show (knownWidth @w))
$ quotRemProp @w (ghcDynInterpreter' comp) $ quotRemProp @w (ghcInterpreter em)
| SomeWidth (_ :: Proxy w) <- allWidths | SomeWidth (_ :: Proxy w) <- allWidths
] ]
, prop_mul_may_oflo_correct comp , prop_mul_may_oflo_correct em
] ]
newtype RunItPath = RunItPath FilePath
instance IsOption RunItPath where
defaultValue = RunItPath "run-it"
parseValue = Just . RunItPath
optionName = Tagged "run-it-path"
optionHelp = Tagged "Path to the run-it executable compiled with the compiler-under-test"
newtype GhcPath = GhcPath FilePath
instance IsOption GhcPath where
defaultValue = GhcPath "ghc"
parseValue = Just . GhcPath
optionName = Tagged "ghc-path"
optionHelp = Tagged "Path to compiler to test"
runCompilerTests :: Compiler -> IO () runCompilerTests :: Compiler -> IO ()
runCompilerTests = defaultMain . compilerTests "compiler" runCompilerTests =
defaultMain . testGroup "compiler" . compilerTests . staticEvalMethod
main :: IO () main :: IO ()
main = do main = do
createBufferFile createBufferFile
let ing = defaultIngredients ++ [includingOptions [Option (Proxy @GhcPath), Option (Proxy @RunItPath)]] let opts = [ Option (Proxy @GhcPath)
, Option (Proxy @GhcArgs)
, Option (Proxy @RunItPath)
, Option (Proxy @Emulator)
]
ing = defaultIngredients ++ [includingOptions opts]
defaultMainWithIngredients ing defaultMainWithIngredients ing
$ askOption $ \(GhcPath ghcPath) -> $ askOption $ \(GhcPath ghcPath) ->
askOption $ \(GhcArgs ghcArgs) ->
askOption $ \(RunItPath runItPath) -> askOption $ \(RunItPath runItPath) ->
testGroup "primops" askOption $ \(Emulator mbEmulator) ->
[ compilerTests name comp' let comp = Compiler ghcPath ghcArgs
| (name, comp) <- compilerConfigs ghcPath em = case mbEmulator of
, let comp' = comp { compRunIt = runItPath } Just emulator -> emulatedStaticEvalMethod comp emulator
] Nothing -> DynamicEval comp runItPath
in testGroup "test-primops" $ compilerTests em
...@@ -20,19 +20,19 @@ import Expr ...@@ -20,19 +20,19 @@ import Expr
-- - May return zero otherwise -- - May return zero otherwise
-- --
-- We cannot test this like other MachOps since its result is not well-defined. -- We cannot test this like other MachOps since its result is not well-defined.
prop_mul_may_oflo_correct :: Compiler -> TestTree prop_mul_may_oflo_correct :: EvalMethod -> TestTree
prop_mul_may_oflo_correct comp = testGroup "MulMayOflo" prop_mul_may_oflo_correct em = testGroup "MulMayOflo"
[ testProperty (show (knownWidth @w)) (prop @w comp Proxy) [ testProperty (show (knownWidth @w)) (prop @w em Proxy)
| SomeWidth (_ :: Proxy w) <- allWidths | SomeWidth (_ :: Proxy w) <- allWidths
] ]
prop :: forall w. (KnownWidth w) prop :: forall w. (KnownWidth w)
=> Compiler => EvalMethod
-> Proxy w -> Proxy w
-> Expr w -> Expr w -> Expr w -> Expr w
-> Property -> Property
prop comp Proxy x y = ioProperty $ do prop em Proxy x y = ioProperty $ do
r <- evalMulMayOflo comp x y r <- evalMulMayOflo em x y
let does_oflo = r /= 0 let does_oflo = r /= 0
return $ counterexample (show prod) (does_overflow ==> does_oflo) return $ counterexample (show prod) (does_overflow ==> does_oflo)
where where
...@@ -42,12 +42,12 @@ prop comp Proxy x y = ioProperty $ do ...@@ -42,12 +42,12 @@ prop comp Proxy x y = ioProperty $ do
evalMulMayOflo evalMulMayOflo
:: forall w. (KnownWidth w) :: forall w. (KnownWidth w)
=> Compiler => EvalMethod
-> Expr w -> Expr w
-> Expr w -> Expr w
-> IO (Number WordSize) -> IO (Number WordSize)
evalMulMayOflo comp x y = evalMulMayOflo em x y =
fromUnsigned <$> evalCmm comp cmm fromUnsigned <$> evalCmm em cmm
where where
cmm = unlines cmm = unlines
[ "test ( " <> cmmWordType <> " buffer ) {" [ "test ( " <> cmmWordType <> " buffer ) {"
......
...@@ -6,6 +6,7 @@ module Number ...@@ -6,6 +6,7 @@ module Number
-- * Fixed-width bit patterns -- * Fixed-width bit patterns
, Number , Number
, numberWidth , numberWidth
, asInteger
, toSigned , toSigned
, toUnsigned , toUnsigned
, fromSigned , fromSigned
...@@ -75,6 +76,13 @@ instance Arbitrary SomeNumber where ...@@ -75,6 +76,13 @@ instance Arbitrary SomeNumber where
arbitrary = arbitrary =
oneof $ forAllWidths $ \(_ :: Proxy w) -> SomeNumber @w <$> arbitrary oneof $ forAllWidths $ \(_ :: Proxy w) -> SomeNumber @w <$> arbitrary
-- | Intepret a bit pattern as either a signed or unsigned integer.
asInteger
:: forall width. (KnownWidth width)
=> Signedness -> Number width -> Integer
asInteger Signed = toSigned
asInteger Unsigned = fromIntegral . toUnsigned
-- | Interpret a bit pattern as an unsigned number. -- | Interpret a bit pattern as an unsigned number.
toUnsigned :: Number width -> Natural toUnsigned :: Number width -> Natural
toUnsigned (Number n) = n toUnsigned (Number n) = n
......
-- | Utilities for running GHC and evaluating Cmm via @run-it@. -- | Utilities for running GHC and evaluating Cmm via @run-it@.
module RunGhc module RunGhc
( Compiler(..) ( EvalMethod(..)
, addArgs , staticEvalMethod
, evalGhcStatic , emulatedStaticEvalMethod
, evalGhcDyn , runTestProgram
-- * Evaluating expressions
, evalExpr
-- * Evaluating Cmm programs
, evalCmm , evalCmm
, compile -- * Utilities
, runIt
, dumpCmmAsm , dumpCmmAsm
, dumpExprAsm , dumpExprAsm
) where ) where
import Numeric.Natural import Numeric.Natural
import System.Exit
import System.Process import System.Process
import System.IO.Temp import System.IO.Temp
import System.FilePath import System.FilePath
import Compiler
import Expr import Expr
import Width import Width
import ToCmm import ToCmm
-- | The location of GHC and arguments to pass it. data EvalMethod
data Compiler = Compiler { compPath :: FilePath = StaticEval { compiler :: Compiler
, compArgs :: [String] , runExe :: FilePath -> IO String
, compRunIt :: FilePath }
-- ^ Path of the `run-it` executable built with this compiler. | DynamicEval { compiler :: Compiler
} , runItPath :: FilePath
deriving (Show) }
addArgs :: Compiler -> [String] -> Compiler staticEvalMethod :: Compiler -> EvalMethod
addArgs c args = c { compArgs = compArgs c ++ args } staticEvalMethod comp =
StaticEval comp runExe
-- | Compile a set of compilation units. where
compile :: Compiler runExe exe = readProcess exe [] ""
-> FilePath -- ^ working directory
-> [FilePath] -- ^ sources -- | An 'EvalMethod' using an emulator to run target executables.
-> FilePath -- ^ output path emulatedStaticEvalMethod :: Compiler -> FilePath -> EvalMethod
-> [String] -- ^ other arguments emulatedStaticEvalMethod comp emulator =
-> IO () StaticEval comp runExe
compile comp workDir srcs out args = do
runProcess' $ inTmp (proc (compPath comp) allArgs)
where where
allArgs = compArgs comp ++ srcs ++ args ++ ["-o", out] runExe exe = readProcess emulator [exe] ""
inTmp c = c { cwd = Just workDir }
runProcess' p = do runTestProgram :: EvalMethod -> TestProgram -> IO String
(_, _, _, hdl) <- createProcess p runTestProgram (StaticEval comp runExe) = runTestProgramStatic comp runExe
ExitSuccess <- waitForProcess hdl runTestProgram (DynamicEval comp runIt) = runTestProgramDyn comp runIt
return ()
runTestProgramDyn :: Compiler -> FilePath -> TestProgram -> IO String
-- | Evaluate an 'Expr' without relying on @run-it@. This is a bit slower than runTestProgramDyn comp runItPath tp =
-- 'evalGhcDyn'. withTempDirectory "." "tmp" $ \tmpDir -> do
evalGhcStatic objs <- writeObjectsIn tmpDir tp
:: forall width. (KnownWidth width) compile comp tmpDir objs soName args
=> Compiler -> Expr width -> IO Natural readProcess runItPath [tmpDir </> soName] ""
evalGhcStatic comp e = withTempDirectory "." "tmp" $ \tmpDir -> do where
writeFile (tmpDir </> hsSrc) $ unlines args = ["-dynamic", "-package-env", "-", "-shared"]
soName = "Test.so"
runTestProgramStatic :: Compiler -> (FilePath -> IO String) -> TestProgram -> IO String
runTestProgramStatic comp runExe tp =
withTempDirectory "." "tmp" $ \tmpDir -> do
wrapper <- mkStaticWrapper comp (knownWidth @WordSize)
objs <- writeObjectsIn tmpDir (tp <> wrapper)
compile comp tmpDir objs exeName []
runExe (tmpDir </> exeName)
where
exeName = "Test"
mkStaticWrapper
:: Compiler
-> Width
-> IO TestProgram
mkStaticWrapper comp width = do
compileHs comp src
where
src = unlines
[ "{-# LANGUAGE GHCForeignImportPrim #-}" [ "{-# LANGUAGE GHCForeignImportPrim #-}"
, "{-# LANGUAGE UnliftedFFITypes #-}" , "{-# LANGUAGE UnliftedFFITypes #-}"
, "{-# LANGUAGE MagicHash #-}" , "{-# LANGUAGE MagicHash #-}"
...@@ -64,21 +84,12 @@ evalGhcStatic comp e = withTempDirectory "." "tmp" $ \tmpDir -> do ...@@ -64,21 +84,12 @@ evalGhcStatic comp e = withTempDirectory "." "tmp" $ \tmpDir -> do
, "import GHC.Exts" , "import GHC.Exts"
, "import GHC.Ptr (Ptr(Ptr))" , "import GHC.Ptr (Ptr(Ptr))"
, "import System.IO.MMap" , "import System.IO.MMap"
, "foreign import prim \"test\" test :: Addr# -> " <> hsType w , "foreign import prim \"test\" test :: Addr# -> " <> hsType width
, "main :: IO ()" , "main :: IO ()"
, "main = do" , "main = do"
, " (Ptr p, _, _, _) <- mmapFilePtr \"test\" ReadOnly Nothing" , " (Ptr p, _, _, _) <- mmapFilePtr \"test\" ReadOnly Nothing"
, " print $ " <> toHsWord w "test p" , " print $ " <> toHsWord width "test p"
] ]
writeFile (tmpDir </> cmmSrc) $ toCmmDecl "test" e
compile comp tmpDir [cmmSrc, hsSrc] exeName []
out <- readProcess (tmpDir </> exeName) [] ""
return $ read out
where
w = knownWidth @width
exeName = "Test"
cmmSrc = "test-cmm.cmm"
hsSrc = "test-hs.hs"
hsType :: Width -> String hsType :: Width -> String
hsType W8 = "Word8#" hsType W8 = "Word8#"
...@@ -93,30 +104,18 @@ toHsWord w x = "W# " <> parens (extendFn <> " " <> parens x) ...@@ -93,30 +104,18 @@ toHsWord w x = "W# " <> parens (extendFn <> " " <> parens x)
| w == W64 = "" | w == W64 = ""
| otherwise = "extendWord" <> show (widthBits w) <> "#" | otherwise = "extendWord" <> show (widthBits w) <> "#"
evalCmm :: EvalMethod -> Cmm -> IO Natural
evalCmm em cmm = do
tp <- compileCmm (compiler em) cmm
out <- runTestProgram em tp
return $ read out
-- | Evaluate an 'Expr'. -- | Evaluate an 'Expr'.
evalGhcDyn :: Compiler -> Expr WordSize -> IO Natural evalExpr :: EvalMethod -> Expr WordSize -> IO Natural
evalGhcDyn comp e = evalCmm comp $ toCmmDecl "test" e evalExpr em = evalCmm em . toCmmDecl "test"
type Cmm = String type Cmm = String
-- | Invoke @run-it@ on the given shared object.
runIt :: Compiler -> FilePath -> IO String
runIt comp soName =
readProcess (compRunIt comp) [soName] ""
-- | Evaluate a Cmm function using @run-it@. The function must be named @test@
-- and must return a @bits64@.
evalCmm :: Compiler -> Cmm -> IO Natural
evalCmm comp cmm = withTempDirectory "." "tmp" $ \tmpDir -> do
writeFile (tmpDir </> cmmSrc) cmm
let args = ["-dynamic", "-package-env", "-", "-shared"]
compile comp tmpDir [cmmSrc] soName args
out <- runIt comp (tmpDir </> soName)
return $ read out
where
soName = "Test.so"
cmmSrc = "test-cmm.cmm"
-- | Compile the given Cmm procedure and dump its disassembly. -- | Compile the given Cmm procedure and dump its disassembly.
dumpCmmAsm :: Compiler -> Cmm -> IO String dumpCmmAsm :: Compiler -> Cmm -> IO String
dumpCmmAsm comp cmm = withTempDirectory "." "tmp" $ \tmpDir -> do dumpCmmAsm comp cmm = withTempDirectory "." "tmp" $ \tmpDir -> do
......
...@@ -15,6 +15,7 @@ executable test-primops ...@@ -15,6 +15,7 @@ executable test-primops
Number, Number,
Expr, Expr,
Expr.Parse, Expr.Parse,
Compiler,
Interpreter, Interpreter,
ToCmm, ToCmm,
CallishOp, CallishOp,
...@@ -28,6 +29,7 @@ executable test-primops ...@@ -28,6 +29,7 @@ executable test-primops
directory, directory,
filepath, filepath,
megaparsec, megaparsec,
optparse-applicative,
parser-combinators, parser-combinators,
process, process,
tagged, tagged,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment