1
1
mirror of https://github.com/anoma/juvix.git synced 2024-12-13 19:49:20 +03:00
juvix/app/Commands/Compile.hs
Paul Cadman 433e882189
Export all functions with alpha numeric names from entrypoint module (#1425)
* Export all functions with alphanum names in entrypoint

Set the export_name attribute on every function signature that has a
fully alpha numeric name.

* Adds a non-WASI target to compile command

WASM libraries we want to run in the browser and in Anoma VM do not have
access to the WASI runtime. For this usecase we need a target runtime
that does not use the WASI runtime.

A `wasi-` prefix is added to existing compile targets and introduce a new
standalone target. This target does not have IO functions like
`putStrLn` and `exit` calls `__builtin_trap` which corresponds to the
`unreachable` WASM instruction.

In the non-exhaustive case error a debug message is emitted to tell the
user which function had the error. This is now abstracted to a `debug`
function in the runtime which calls `putStrLn` in the case of WASI and
is no-op in the case of non-WASI.

Tests are added which check that exported names can be called with the
correct name and result.

* Moves walloc to a common directory
2022-08-01 12:53:19 +02:00

215 lines
6.6 KiB
Haskell

module Commands.Compile where
import Data.ByteString qualified as BS
import Data.FileEmbed qualified as FE
import Data.Text.IO qualified as TIO
import Juvix.Prelude hiding (Doc)
import Options.Applicative
import System.Environment
import System.Process qualified as P
juvixBuildDir :: FilePath
juvixBuildDir = ".juvix-build"
data CompileTarget = TargetC | TargetWasm
deriving stock (Show)
data CompileRuntime
= RuntimeWasiStandalone
| RuntimeWasiLibC
| RuntimeStandalone
deriving stock (Show)
data CompileOptions = CompileOptions
{ _compileTarget :: CompileTarget,
_compileRuntime :: CompileRuntime,
_compileOutputFile :: Maybe FilePath
}
makeLenses ''CompileOptions
parseCompile :: Parser CompileOptions
parseCompile = do
_compileTarget <-
option
(eitherReader parseTarget)
( long "target"
<> short 't'
<> metavar "TARGET"
<> value TargetWasm
<> showDefaultWith targetShow
<> help "select a target: wasm, c"
)
_compileRuntime <-
option
(eitherReader parseRuntime)
( long "runtime"
<> short 'r'
<> metavar "RUNTIME"
<> value RuntimeWasiStandalone
<> showDefaultWith runtimeShow
<> help "select a runtime: wasi-standalone, wasi-libc, standalone"
)
_compileOutputFile <-
optional $
option
str
( long "output"
<> short 'o'
<> metavar "OUTPUT_FILE"
<> help "Path to output file"
<> action "file"
)
pure CompileOptions {..}
where
parseTarget :: String -> Either String CompileTarget
parseTarget = \case
"wasm" -> Right TargetWasm
"c" -> Right TargetC
s -> Left $ "unrecognised target: " <> s
targetShow :: CompileTarget -> String
targetShow = \case
TargetC -> "c"
TargetWasm -> "wasm"
parseRuntime :: String -> Either String CompileRuntime
parseRuntime = \case
"wasi-standalone" -> Right RuntimeWasiStandalone
"wasi-libc" -> Right RuntimeWasiLibC
"standalone" -> Right RuntimeStandalone
s -> Left $ "unrecognised runtime: " <> s
runtimeShow :: CompileRuntime -> String
runtimeShow = \case
RuntimeWasiStandalone -> "wasi-standalone"
RuntimeWasiLibC -> "wasi-libc"
RuntimeStandalone -> "standalone"
inputCFile :: FilePath -> FilePath -> FilePath
inputCFile projRoot compileInputFile =
projRoot </> juvixBuildDir </> outputMiniCFile
where
outputMiniCFile :: FilePath
outputMiniCFile = takeBaseName compileInputFile <> ".c"
runCompile :: FilePath -> FilePath -> CompileOptions -> Text -> IO (Either Text ())
runCompile projRoot compileInputFile o minic = do
createDirectoryIfMissing True (projRoot </> juvixBuildDir)
TIO.writeFile (inputCFile projRoot compileInputFile) minic
prepareRuntime projRoot o
case o ^. compileTarget of
TargetWasm -> runM (runError (clangCompile projRoot compileInputFile o))
TargetC -> return (Right ())
prepareRuntime :: FilePath -> CompileOptions -> IO ()
prepareRuntime projRoot o = do
mapM_ writeRuntime runtimeProjectDir
where
wasiStandaloneRuntimeDir :: [(FilePath, BS.ByteString)]
wasiStandaloneRuntimeDir = $(FE.makeRelativeToProject "minic-runtime/wasi-standalone" >>= FE.embedDir)
standaloneRuntimeDir :: [(FilePath, BS.ByteString)]
standaloneRuntimeDir = $(FE.makeRelativeToProject "minic-runtime/standalone" >>= FE.embedDir)
wasiLibCRuntimeDir :: [(FilePath, BS.ByteString)]
wasiLibCRuntimeDir = $(FE.makeRelativeToProject "minic-runtime/wasi-libc" >>= FE.embedDir)
builtinCRuntimeDir :: [(FilePath, BS.ByteString)]
builtinCRuntimeDir = $(FE.makeRelativeToProject "minic-runtime/builtins" >>= FE.embedDir)
wallocDir :: [(FilePath, BS.ByteString)]
wallocDir = $(FE.makeRelativeToProject "minic-runtime/walloc" >>= FE.embedDir)
runtimeProjectDir :: [(FilePath, BS.ByteString)]
runtimeProjectDir = case o ^. compileRuntime of
RuntimeWasiStandalone -> wasiStandaloneRuntimeDir <> builtinCRuntimeDir <> wallocDir
RuntimeWasiLibC -> wasiLibCRuntimeDir <> builtinCRuntimeDir
RuntimeStandalone -> standaloneRuntimeDir <> builtinCRuntimeDir <> wallocDir
writeRuntime :: (FilePath, BS.ByteString) -> IO ()
writeRuntime (filePath, contents) =
BS.writeFile (projRoot </> juvixBuildDir </> takeFileName filePath) contents
clangCompile ::
forall r.
Members '[Embed IO, Error Text] r =>
FilePath ->
FilePath ->
CompileOptions ->
Sem r ()
clangCompile projRoot compileInputFile o = clangArgs >>= runClang
where
clangArgs :: Sem r [String]
clangArgs = case o ^. compileRuntime of
RuntimeStandalone ->
return (standaloneLibArgs projRoot outputFile inputFile)
RuntimeWasiStandalone -> wasiStandaloneArgs projRoot outputFile inputFile <$> sysrootEnvVar
RuntimeWasiLibC -> wasiLibcArgs outputFile inputFile <$> sysrootEnvVar
outputFile :: FilePath
outputFile = fromMaybe (takeBaseName compileInputFile <> ".wasm") (o ^. compileOutputFile)
inputFile :: FilePath
inputFile = inputCFile projRoot compileInputFile
sysrootEnvVar :: Sem r String
sysrootEnvVar =
fromMaybeM (throw msg) (embed (lookupEnv "WASI_SYSROOT_PATH"))
where
msg :: Text
msg = "Missing environment variable WASI_SYSROOT_PATH"
commonArgs :: FilePath -> [String]
commonArgs wasmOutputFile =
[ "-nodefaultlibs",
"-std=c99",
"-Oz",
"-I",
juvixBuildDir,
"-o",
wasmOutputFile
]
standaloneLibArgs :: FilePath -> FilePath -> FilePath -> [String]
standaloneLibArgs projRoot wasmOutputFile inputFile =
commonArgs wasmOutputFile
<> [ "--target=wasm32",
"-nostartfiles",
"-Wl,--no-entry",
projRoot </> juvixBuildDir </> "walloc.c",
inputFile
]
wasiStandaloneArgs :: FilePath -> FilePath -> FilePath -> FilePath -> [String]
wasiStandaloneArgs projRoot wasmOutputFile inputFile sysrootPath =
wasiCommonArgs sysrootPath wasmOutputFile
<> [ projRoot </> juvixBuildDir </> "walloc.c",
inputFile
]
wasiLibcArgs :: FilePath -> FilePath -> FilePath -> [String]
wasiLibcArgs wasmOutputFile inputFile sysrootPath =
wasiCommonArgs sysrootPath wasmOutputFile
<> ["-lc", inputFile]
wasiCommonArgs :: FilePath -> FilePath -> [String]
wasiCommonArgs sysrootPath wasmOutputFile =
commonArgs wasmOutputFile
<> [ "--target=wasm32-wasi",
"--sysroot",
sysrootPath
]
runClang ::
Members '[Embed IO, Error Text] r =>
[String] ->
Sem r ()
runClang args = do
(exitCode, _, err) <- embed (P.readProcessWithExitCode "clang" args "")
case exitCode of
ExitSuccess -> return ()
_ -> throw (pack err)