3 Commits
Author SHA1 Message Date
msyds 80164acb96 doc
build / build (push) Failing after 1m25s
2026-07-22 23:48:40 -06:00
msyds 1c7322614c fart 2026-07-22 23:48:02 -06:00
msyds 81a136fcf2 inspect-wasm 2026-07-22 12:22:41 -06:00
7 changed files with 153 additions and 32 deletions
+53
View File
@@ -0,0 +1,53 @@
#+title: Gyehoek Scheme
#+begin_center
(this document is written in present tense as if the project is complete, but Gyehoek is a work-in-progress.)
#+end_center
Gyehoek is an R⁷RS-compliant Scheme compiler targeting WebAssembly 3.0, relying principally on the recently standardised garbage collector and tail call proposals. the Gyehoek compiler is implemented in Haskell, and the Gyehoek runtime is a Rust program providing primitive routines and WebAssembly execution via the Wasmtime library.
primitives are implemented as native Rust functions made available to the guest by Wasmtime. in the future, it would be ideal to provide the primitives as a WASI interface to help decouple ourselves from a specific Wasm runtime, but it is not a priority.
Gyehoek allows separate compilation, ~eval~, first-class continuations, and so on.
* pipeline
a Scheme program's journey through Gyehoek is as follows:
1. read (source code → Scheme data)
2. parse (Scheme data → AST)
3. expand(?) (AST → AST)
4. contify (AST → CPS)
5. close (CPS → CPS)
6. lower (CPS → Wasm)
** read
in the read phase, Gyehoek's reader serialises textual source code into a sequence of tokens, which are then parsed into S-expressions. this phase is completely agnostic towards any interpretation of the data — it's just data, not code (yet). this distinction between reading and parsing is made so that the reader can easily be shared amongst many parsers, allowing convenient definition of human-readable representations for all sorts of compiler internals. Gyehoek's intermediate languages and WebAssembly text format are of particular interest.
the reader may be configured to extend R⁷RS's syntax with a special "antiquotation" notation, used internally in the compiler to elegantly interpolate and splice S-expression literals via Haskell's quasiquotation.
#+begin_src haskell
let meta = 123 :: Int
in [sx|(a b c #{meta} d)|] -- ⇒ (a b c 123 d)
let metas = ["c","d"] :: List Text
in [sx|(a b ##{metas} e f)|] -- ⇒ (a b "c" "d" e f)
#+end_src
Gyehoek's lexer and parser are generated by Alex and Happy, respectively.
unless otherwise noted, the term "parse" will be used in reference to the phase taking S-expressions to ASTs, while "read" refers to the combined Alex/Happy process. if the tokenisation process (Alex) must be distinguished from the "parse" process (Happy), the former is called "lexical analysis" and the latter "syntactic analysis."
** parse
- use invertible-grammar library
** expand
** contify
- procedures are distinguished from continuations, and procedure applications are distinguished from continuation jumps.
- all continuations and lambda will be named i think. the exception is continuations for primitive calls.
** close
** lower
+2 -1
View File
@@ -66,7 +66,7 @@ library
, base ^>=4.21.2.0 , base ^>=4.21.2.0
, binary , binary
, containers , containers
, cradle , typed-process
, effectful , effectful
, effectful-core , effectful-core
, effectful-plugin , effectful-plugin
@@ -89,6 +89,7 @@ library
, text-short , text-short
, unordered-containers , unordered-containers
, vector , vector
, bytestring
hs-source-dirs: src hs-source-dirs: src
default-language: GHC2024 default-language: GHC2024
+4 -4
View File
@@ -183,10 +183,10 @@ lower' g e@(ExpApply f xs ktail) = do
(ref.cast (ref $closure)) (ref.cast (ref $closure))
(struct.get $closure $code) (struct.get $closure $code)
(return_call_ref $cont-type) (return_call_ref $cont-type)
;; (@gyehoek todo (@gyehoek todo
;; (f' ##{f'}) (f' ##{f'})
;; (ktail #{l})) (ktail #{l}))
|] |]
lower' g e@(ExpContinue k xs) = do lower' g e@(ExpContinue k xs) = do
let nargs = length xs let nargs = length xs
+53 -24
View File
@@ -3,6 +3,7 @@
{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FunctionalDependencies #-}
module Gyehoek.CPS.Syntax module Gyehoek.CPS.Syntax
( Val(..) ( Val(..)
, Kappa(..) , Kappa(..)
@@ -19,6 +20,11 @@ module Gyehoek.CPS.Syntax
, _ExpPrim , _ExpPrim
, _ExpLetRec , _ExpLetRec
, _ExpApply , _ExpApply
, binders
, body
, op
, args
, cont
, cps , cps
, pattern AbsLambda' , pattern AbsLambda'
, pattern AbsKappa' , pattern AbsKappa'
@@ -48,6 +54,7 @@ import Data.HashSet (HashSet)
import qualified Data.HashSet as HS import qualified Data.HashSet as HS
import Data.Hashable (Hashable) import Data.Hashable (Hashable)
import Data.Monoid (Endo) import Data.Monoid (Endo)
import Data.Containers.ListUtils (nubOrd)
-- Data types -- Data types
@@ -56,10 +63,10 @@ data Val
| ValLit Lit | ValLit Lit
deriving (Show, Generic, Data, Eq) deriving (Show, Generic, Data, Eq)
data Kappa = MkKappa (List Name) Exp data Kappa = MkKappa { binders :: List Name, body :: Exp }
deriving (Show, Generic, Data, Eq) deriving (Show, Generic, Data, Eq)
data Lambda = MkLambda (List Name) Name Exp data Lambda = MkLambda { binders :: List Name, ktail :: Name, body :: Exp }
deriving (Show, Generic, Data, Eq) deriving (Show, Generic, Data, Eq)
data Abs data Abs
@@ -72,7 +79,7 @@ pattern AbsLambda' xs e ktail = AbsLambda (MkLambda xs e ktail)
data Exp data Exp
= ExpPrim (Prim Val) Kappa = ExpPrim (Prim Val) Kappa
| ExpLetRec (NonEmpty (Name, Abs)) Exp | ExpLetRec { binders :: NonEmpty (Name, Abs), body :: Exp }
| ExpContinue Name (List Val) | ExpContinue Name (List Val)
| ExpIf Val Exp Exp | ExpIf Val Exp Exp
| ExpApply | ExpApply
@@ -97,7 +104,24 @@ data Program = MkProgram
deriving (Show, Generic, Data) deriving (Show, Generic, Data)
makePrisms ''Kappa makePrisms ''Kappa
-- makeLenses ''Kappa
makePrisms ''Exp makePrisms ''Exp
-- makeLenses ''Exp
-- makeFieldsNoPrefix ''Exp
-- makeFieldsNoPrefix ''Kappa
-- makeLensesWith abbreviatedFields ''Exp
-- makeLensesFor [("binders", "_binders"), ("body", "_body")] ''Exp
makeFieldsId ''Exp
makeFieldsId ''Kappa
makeFieldsId ''Lambda
instance HasBinders Abs (List Name) where
binders k (AbsKappa kap) = AbsKappa <$> binders k kap
binders k (AbsLambda lam) = AbsLambda <$> binders k lam
instance HasBody Abs Exp where
body k (AbsKappa kap) = AbsKappa <$> body k kap
body k (AbsLambda lam) = AbsLambda <$> body k lam
-- SexpIso instances -- SexpIso instances
@@ -230,24 +254,29 @@ free = go where
-- | Free variables given in the order of their appearance. -- | Free variables given in the order of their appearance.
free' :: Exp -> List Name free' :: Exp -> List Name
free' = go HS.empty where free' = nubOrd . goFree HS.empty where
gokap bound (MkKappa xs m) = go (bound & insertFrom xs) m
golam bound (MkLambda xs k m) = go (bound & insertFrom (k:xs)) m goFreeKap bound (MkKappa xs m) = goFree (bound & insertFrom xs) m
goabs bound = \case goFreeLam bound (MkLambda xs k m) = goFree (bound & insertFrom (k:xs)) m
AbsKappa kap -> gokap bound kap goFreeAbs bound = \case
AbsLambda lam -> golam bound lam AbsKappa kap -> goFreeKap bound kap
go :: HashSet Name -> Exp -> List Name AbsLambda lam -> goFreeLam bound lam
go bound = \case
ExpPrim p k -> goFree :: HashSet Name -> Exp -> List Name
p & toListOf (folded . #ValVar . filtered (`notElem` bound)) goFree bound = \case
& (<> gokap bound k) ExpPrim p k ->
ExpLetRec bs m -> p & toListOf (folded . #ValVar . filtered (`notElem` bound))
foldMapOf (each . _2) (goabs bound') bs <> go bound' m & (<> goFreeKap bound k)
where bound' = bound & insertFrom (bs ^.. each . _1) ExpLetRec bs m ->
ExpContinue k xs -> filter (`notElem` bound) (k : xs ^.. each . #ValVar) foldMapOf (each . _2) (goFreeAbs bound') bs <> goFree bound' m
ExpIf c t f -> where bound' = bound & insertFrom (bs ^.. each . _1)
(c ^.. #ValVar . filtered (`notElem` bound)) ExpContinue k xs -> filter (`notElem` bound) (k : xs ^.. each . #ValVar)
<> go bound t <> go bound f ExpIf c t f ->
ExpApply f xs k -> (c ^.. #ValVar . filtered (`notElem` bound))
(f:xs) ^.. (each . #ValVar . filtered (`notElem` bound)) <> goFree bound t <> goFree bound f
<> (k ^.. filtered (`notElem` bound)) ExpApply f xs k ->
(f:xs) ^.. (each . #ValVar . filtered (`notElem` bound))
<> (k ^.. filtered (`notElem` bound))
freeLambda :: Lambda -> List Name
freeLambda (MkLambda {binders,ktail,body}) = _
+37 -2
View File
@@ -16,11 +16,18 @@ import qualified Gyehoek.Sexp as Sexp
import qualified Gyehoek.Scheme.Syntax as Scm import qualified Gyehoek.Scheme.Syntax as Scm
import qualified Data.Text.Encoding as T import qualified Data.Text.Encoding as T
import System.IO (Handle) import System.IO (Handle)
import System.IO qualified as IO
import Gyehoek.CPS.Convert import Gyehoek.CPS.Convert
import Gyehoek.CPS.Lower import Gyehoek.CPS.Lower
import Gyehoek.CPS.Syntax qualified as Cps import Gyehoek.CPS.Syntax qualified as Cps
import Control.Monad import Control.Monad
import Text.Pretty.Simple (pShowNoColor) import Text.Pretty.Simple (pShowNoColor)
import System.Process.Typed
import Data.Text.Encoding (encodeUtf8)
import System.Environment.Blank (getEnvDefault)
import GHC.Conc (atomically)
import qualified Data.Text.IO as TIO
import qualified Data.ByteString.Lazy as BS
main :: IO () main :: IO ()
@@ -59,6 +66,31 @@ readScm f =
Sexp.parseSexps @Scm.CommandOrDef (fileName f) <$> hGetContents h Sexp.parseSexps @Scm.CommandOrDef (fileName f) <$> hGetContents h
>>= either error (pure . Scm.MkProgram) >>= either error (pure . Scm.MkProgram)
inspectWasm :: IOE :> es => Text -> Eff es ()
inspectWasm wat = do
pager_cmd <- liftIO $ getEnvDefault "PAGER" "less"
let wasmtools_cfg
= proc "wasm-tools" ["print", "-pf", "--print-operand-stack"
,"--color", "always", "-"]
-- & setStdin (byteStringInput . view lazy . encodeUtf8 $ wat)
-- & setStdout byteStringOutput
& setStdin createPipe
& setStdout createPipe
& setStderr inherit
let pager_cfg = proc pager_cmd []
& setStdin createPipe
& setStdout inherit
& setStderr inherit
liftIO $ withProcessWait_ wasmtools_cfg \wasmtools -> do
TIO.hPutStrLn (getStdin wasmtools) wat
IO.hFlush (getStdin wasmtools)
IO.hClose (getStdin wasmtools)
withProcessWait_ pager_cfg \pager -> do
t <- BS.hGetContents (getStdout wasmtools)
BS.hPut (getStdin pager) t
IO.hFlush (getStdin pager)
IO.hClose (getStdin pager)
driver driver
:: (GenSym :> es, FileSystem :> es, IOE :> es) :: (GenSym :> es, FileSystem :> es, IOE :> es)
=> Options -> Eff es () => Options -> Eff es ()
@@ -70,8 +102,11 @@ driver opts = do
when opts.dumpCPS do when opts.dumpCPS do
hPutStrLn FS.stdout $ Sexp.encodePretty cps ^?! _Right hPutStrLn FS.stdout $ Sexp.encodePretty cps ^?! _Right
wat <- lowerProgram cps wat <- lowerProgram cps
withFile opts.output FS.WriteMode \h -> if not opts.inspectWasm then
hPutStrLn h wat withFile opts.output FS.WriteMode \h ->
hPutStrLn h wat
else
inspectWasm wat
parse_e2e :: FilePath -> IO Scm.Program parse_e2e :: FilePath -> IO Scm.Program
parse_e2e = runEff . runFileSystem . readScm parse_e2e = runEff . runFileSystem . readScm
+3
View File
@@ -19,6 +19,7 @@ data Options = MkOptions
-- , dumpQBE :: Maybe FilePath -- , dumpQBE :: Maybe FilePath
dumpCPS :: Bool dumpCPS :: Bool
, dumpParsed :: Bool , dumpParsed :: Bool
, inspectWasm :: Bool
, output :: FilePath , output :: FilePath
, sourceFile :: FilePath , sourceFile :: FilePath
} }
@@ -49,10 +50,12 @@ parseOutput = strOption
parseDumpCPS = switch (long "dump-cps") parseDumpCPS = switch (long "dump-cps")
parseDumpParsed = switch (long "dump-parsed") parseDumpParsed = switch (long "dump-parsed")
parseInspectWasm = switch $ long "inspect-wasm" <> short 'p'
parser :: Parser Options parser :: Parser Options
parser = MkOptions parser = MkOptions
<$> parseDumpCPS <$> parseDumpCPS
<*> parseDumpParsed <*> parseDumpParsed
<*> parseInspectWasm
<*> parseOutput <*> parseOutput
<*> argument str (metavar "FILE") <*> argument str (metavar "FILE")
+1 -1
View File
@@ -52,7 +52,7 @@ import Language.Haskell.TH.Quote (QuasiQuoter)
newtype Name = MkName { inner :: Text } newtype Name = MkName { inner :: Text }
deriving newtype (Show, Eq, IsString, Gen, Hashable) deriving newtype (Show, Eq, Ord, IsString, Gen, Hashable)
deriving stock (Generic, Data) deriving stock (Generic, Data)
getName :: Name -> Text getName :: Name -> Text