11 Commits
Author SHA1 Message Date
msyds 9b6da5cbe0 enable some working tests
build / build (push) Failing after 1m19s
2026-08-19 00:11:39 -06:00
msyds ca1b53f3d1 fix catching of exceptions in tests 2026-08-18 23:56:22 -06:00
msyds eb51f4fff7 call/cc 2026-08-18 23:13:32 -06:00
msyds 8bdbfafb9c fix some warnings 2026-08-18 21:14:50 -06:00
msyds 6949ff7fdf convert (limited) letrec
build / build (push) Successful in 20s
2026-08-18 20:38:17 -06:00
msyds a73b3ed89b correctly handle push-calls!!!!!!!!
build / build (push) Successful in 1m24s
2026-08-18 19:25:18 -06:00
msyds 1c13de4153 more stackification
build / build (push) Successful in 1m15s
2026-08-18 16:13:44 -06:00
msyds d91e059a84 wip: stackify
build / build (push) Successful in 1m30s
2026-08-18 02:27:02 -06:00
msyds c4bcf38374 factorial
build / build (push) Successful in 1m9s
2026-08-17 23:44:35 -06:00
msyds 745277ed1a wip: abstract stack/continuation machine
build / build (push) Successful in 1m12s
2026-08-17 22:31:45 -06:00
msyds c3c4866fa8 idk
build / build (push) Failing after 51s
2026-08-06 21:07:33 -06:00
38 changed files with 1639 additions and 404 deletions
+218
View File
@@ -0,0 +1,218 @@
#+title: ABI
largely based on the Guile Hoot's [[https://codeberg.org/spritely/hoot/src/branch/main/design/ABI.md][ABI]].
* calling convention
** non-tail calls
- set the global variable ~$current-closure~ to the callee's closure.
- load arguments into globals ~$arg0~, ~$arg1~, ~$arg2~, …
- push return continuation onto ~$cont-stack~
* scratchpad
#+begin_src scheme
;; Scheme source
(define (silly f g h x)
(f (h x) (g x)))
;; continuation-passing style
(define (silly f g h x ktail)
(h x (κ (x0)
(g x (κ (x1)
(f x0 x1 ktail))))))
;; with explicit stacks
(define (silly)
(define f (pop!))
(define g (pop!))
(define h (pop!))
(define x (pop!))
(define ktail (pop-cont!))
(push-cont! (κ (x0)
(define x* (pop!))
(define g* (pop!))
(push-cont! (κ (x1)
(define f* (pop!))
(define x0* (pop!))
(push-cont! ktail)
(push! x0*)
(push! x1)
(call! f)))
(push! x*)
(call! g)))
(push! x)
(call! h))
#+end_src
** fac
*** Scheme source
#+begin_src scheme
(define fac
(λ (n)
(if (zero? n)
1
(* n (fac (- n 1))))))
(fac 3)
#+end_src
*** CPS
#+begin_src scheme
(define fac
(λ (n ktail)
(zero? n (κ (x0)
(if x0
1
(- n 1
(κ (x1)
(fac x1
(κ (x2)
(* n x2 ktail))))))))))
(fac 3 halt)
#+end_src
*** tailified
#+begin_src scheme
(define (fac-k1)
(define n (pop!))
(define x2 (pop!))
(define x3 (* n x2))
(define ktail (pop-cont!))
(push! x3)
(call! ktail))
(define (fac-k0)
(define x0 (pop!))
(define n (pop!))
(if x0
(begin (define ktail (pop-cont!))
(push! 1)
(call! ktail))
(begin (define x1 (- n 1))
(push! x1)
(push-cont! fac-k1)
(call! fac))))
(define (fac)
(define n (pop!))
(push! n)
(push-cont! fac-k0)
(push! n)
(call! zero?))
(push! 3)
(push-cont! halt)
(call! fac)
#+end_src
evaluation of ~(fac 0)~:
#+begin_src scheme
(push! 0) ; [] []
(push-cont! halt) ; [0] []
(call! fac) ; [0] [halt]
(define n (pop!)) ; [0] [halt]
(push! n) ; [] [halt]
(push-cont! fac-k0) ; [0] [halt]
(push! n) ; [0] [halt fac-k0]
(call! zero?) ; [0 0] [halt fac-k0]
#<internals of zero?> ; [0 0] [halt fac-k0]
(define x0 (pop!)) ; [0 #t] [halt]
(define n (pop!)) ; [0] [halt]
(define ktail (pop-cont!)) ; [] [halt]
(push! 1) ; [] []
(call! ktail) ; [1] []
#+end_src
evaluation of ~(fac 3)~
#+begin_src scheme
(push! 3) ; [] []
(push-cont! halt) ; [3] []
(call! fac) ; [3] [halt]
(define n (pop!)) ; [3] [halt]
(push! n) ; [] [halt]
(push-cont! fac-k0) ; [3] [halt]
(push! n) ; [3] [halt fac-k0]
(call! zero?) ; [3 3] [halt fac-k0]
#<internals of zero?> ; [3 3] [halt fac-k0]
(define x0 (pop!)) ; [3 #f] [halt]
(define n (pop!)) ; [3] [halt]
(define x1 (- n 1)) ; [] [halt]
(push! n) ; [] [halt]
(push! x1) ; [3] [halt]
(push-cont! fac-k1) ; [3 2] [halt]
(call! fac) ; [3 2] [halt fac-k1]
(define n (pop!)) ; [3 2] [halt fac-k1]
(push! n) ; [3 ] [halt fac-k1]
(push-cont! fac-k0) ; [3 2] [halt fac-k1]
(push! n) ; [3 2] [halt fac-k1 fac-k0]
(call! zero?) ; [3 2 2] [halt fac-k1 fac-k0]
#<internals of zero?> ; [3 2 2] [halt fac-k1 fac-k0]
(define x0 (pop!)) ; [3 2 #f] [halt fac-k1]
(define n (pop!)) ; [3 2] [halt fac-k1]
(define x1 (- n 1)) ; [3] [halt fac-k1]
(push! n) ; [3] [halt fac-k1]
(push! x1) ; [3 2] [halt fac-k1]
(push-cont! fac-k1) ; [3 2 1] [halt fac-k1]
(call! fac) ; [3 2 1] [halt fac-k1 fac-k1]
(define n (pop!)) ; [3 2 1] [halt fac-k1 fac-k1]
(push! n) ; [3 2] [halt fac-k1 fac-k1]
(push-cont! fac-k0) ; [3 2 1] [halt fac-k1 fac-k1]
(push! n) ; [3 2 1] [halt fac-k1 fac-k1 fac-k0]
(call! zero?) ; [3 2 1 1] [halt fac-k1 fac-k1 fac-k0]
#<internals of zero?> ; [3 2 1 1] [halt fac-k1 fac-k1 fac-k0]
(define x0 (pop!)) ; [3 2 1 #f] [halt fac-k1 fac-k1]
(define n (pop!)) ; [3 2 1] [halt fac-k1 fac-k1]
(define x1 (- n 1)) ; [3 2] [halt fac-k1 fac-k1]
(push! n) ; [3 2] [halt fac-k1]
(push! x1) ; [3 2 1] [halt fac-k1 fac-k1]
(push-cont! fac-k1) ; [3 2 1 0] [halt fac-k1 fac-k1]
(call! fac) ; [3 2 1 0] [halt fac-k1 fac-k1 fac-k1]
(define n (pop!)) ; [3 2 1 0] [halt fac-k1 fac-k1 fac-k1]
(push! n) ; [3 2 1] [halt fac-k1 fac-k1 fac-k1]
(push-cont! fac-k0) ; [3 2 1 0] [halt fac-k1 fac-k1 fac-k1]
(push! n) ; [3 2 1 0] [halt fac-k1 fac-k1 fac-k1 fac-k0]
(call! zero?) ; [3 2 1 0 0] [halt fac-k1 fac-k1 fac-k1 fac-k0]
#<internals of zero?> ; [3 2 1 0 0] [halt fac-k1 fac-k1 fac-k1 fac-k0]
(define x0 (pop!)) ; [3 2 1 0 #t] [halt fac-k1 fac-k1 fac-k1]
(define n (pop!)) ; [3 2 1 0] [halt fac-k1 fac-k1 fac-k1]
(define ktail (pop-cont!)) ; [3 2 1] [halt fac-k1 fac-k1 fac-k1]
(push! 1) ; [3 2 1 1] [halt fac-k1 fac-k1]
(call! ktail) ; [3 2 1 1] [halt fac-k1 fac-k1]
(define n (pop!)) ; [3 2 1 1] [halt fac-k1 fac-k1]
(define x2 (pop!)) ; [3 2 1] [halt fac-k1 fac-k1]
(define x3 (* n x2)) ; [3 2] [halt fac-k1 fac-k1]
(define ktail (pop-cont!)) ; [3 2] [halt fac-k1 fac-k1]
(push! x3) ; [3 2] [halt fac-k1]
(call! ktail) ; [3 2 1] [halt fac-k1]
(define n (pop!)) ; [3 2 1] [halt fac-k1]
(define x2 (pop!)) ; [3 2] [halt fac-k1]
(define x3 (* n x2)) ; [3] [halt fac-k1]
(define ktail (pop-cont!)) ; [3] [halt fac-k1]
(push! x3) ; [3] [halt]
(call! ktail) ; [3 2] [halt]
(define n (pop!)) ; [3 2] [halt]
(define x2 (pop!)) ; [3] [halt]
(define x3 (* n x2)) ; [] [halt]
(define ktail (pop-cont!)) ; [] [halt]
(push! x3) ; [] []
(call! ktail) ; [6] []
;; => (halt 6)
#+end_src
+83
View File
@@ -0,0 +1,83 @@
#+title: closure-conversion
the closure-conversion phase makes closed-over variables explicit by addition of the primitive ~make-closure~, taking a code pointer (in the CPS language, bare lambda) and the environment.
* scratchpad
#+begin_src scheme
(letrec ((make-adder
(lambda (n)
(lambda (x)
(+ n x)))))
((make-adder 3) 2))
#+end_src
#+begin_src scheme
(define add-code
(lambda (n env)
(+ n (env-ref env 'x))))
(define make-adder-code
(lambda (n)
(make-closure add-code ('x n))))
(define make-adder (make-closure make-adder-code))
(apply-closure (apply-closure make-addder 3) 2)
#+end_src
#+begin_src wat
(module
(type $heap-object (sub (struct (field $hash (mut i32)))))
(type $closure (sub $heap-object
(struct (field $hash (mut i32))
(field $code (ref $cont-type)))))
(type $closure1 (sub $closure
(struct (field $hash (mut i32))
(field $code (ref $cont-type))
(field $env0 (ref eq)))))
(global $arg0 (mut (ref null eq)) (ref.null eq))
(global $arg1 (mut (ref null eq)) (ref.null eq))
(global $arg2 (mut (ref null eq)) (ref.null eq))
(global $arg3 (mut (ref null eq)) (ref.null eq))
(global $arg4 (mut (ref null eq)) (ref.null eq))
(global $arg5 (mut (ref null eq)) (ref.null eq))
;; ⋮
;; (global $argn (mut (ref null eq)) (ref.null eq))
(global $current-closure (mut (ref null $closure)) (ref.null $closure))
(func $add-code (param $nargs i32)
(local $n (ref eq))
(local $x (ref eq))
(local.set $n (global.get $arg0))
(local.set $x (struct.get $closure1
(global.get $current-closure)
$env0))
(return (i32.add $n $x)))
(func $make-adder-code (param $nargs i32)
(local $n (ref eq))
(local.set $n (global.get $arg0))
(return (struct.new $closure1
0
$add-code)))
(func $main
(local.set $make-adder
(struct.new $closure
0
$make-adder-code))
(global.set $current-closure $make-adder)
(global.set $arg0 (i32.const 3))
(local.set $f (call (struct.get $closure
$make-adder
$code)
1))
(global.set $current-closure $f)
(global.set $arg0 (i32.const 2))
(return (call (struct.get $closure
$f
$code)
1))))
#+end_src
+53
View File
@@ -0,0 +1,53 @@
#+title: assorted notes on compilation
* letrec
consider:
#+begin_src scheme
(letrec ((even? (lambda (n)
(if (zero? n)
#t
(odd? (- n 1)))))
(odd? (lambda (n)
(if (zero? n)
#f
(even? (- n 1))))))
(even? 12))
#+end_src
#+RESULTS:
: #t
since ~letrec~ is a primitive construct in the CPS language, the translation of mutually recursive functions is straightforward:
#+begin_src scheme
(define (-& x y k) (k (- x y)))
(define (zero?& x k) (k (zero? x)))
(define (halt x) x)
(letrec ((even? (lambda (n ktail)
(zero?& n
(lambda (x1)
(if x1
#t
(-& n 1
(lambda (x2)
(odd? x2 ktail))))))))
(odd? (lambda (n ktail)
(zero?& n
(lambda (x1)
(if x1
#f
(-& n 1
(lambda (x2)
(even? x2 ktail)))))))))
(even? 12 halt))
#+end_src
#+RESULTS:
: #t
however, Scheme permits ~letrec~-expressions with non-lambda right-hand sides, while the CPS language permits only kappa and lambda forms. thus, the handling of these forms is less trivial.
for now we'll just reject any ~letrec~ forms with non-lambda right-hand sides, lol. they aren't very important.
+4
View File
@@ -0,0 +1,4 @@
(let ((make-adder (lambda (x)
(lambda (y)
(+ x y)))))
((make-adder 4) 5))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 17
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 10
+5
View File
@@ -0,0 +1,5 @@
((λ (f g x)
(f (g x)))
(λ (x) (+ x 4))
(λ (x) (* x 2))
3)
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 123
+1
View File
@@ -0,0 +1 @@
(call/cc (λ (cc) (cc 123)))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 1234
+1
View File
@@ -0,0 +1 @@
(call/cc (λ (_) 1234))
+5
View File
@@ -0,0 +1,5 @@
(call/cc
(λ (k1)
(call/cc
(λ (k2)
(k1 456)))))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 456
+5
View File
@@ -0,0 +1,5 @@
(call/cc
(λ (k1)
(call/cc
(λ (k2)
(k2 456)))))
+2
View File
@@ -0,0 +1,2 @@
ret > ExitSuccess
out > 720
+5
View File
@@ -0,0 +1,5 @@
(letrec ((fac (λ (n)
(if (zero? n)
1
(* n (fac (- n 1)))))))
(fac 6))
+3 -1
View File
@@ -1 +1,3 @@
(((λ (f) f) (λ (x) (* x 4))) 32)
(((λ (f) f)
(λ (x) (* x 4)))
32)
+2
View File
@@ -0,0 +1,2 @@
(let ((square (λ (x) (* x x))))
(square 4))
+15 -2
View File
@@ -52,21 +52,26 @@ library
-- cabal-fmt: expand src
exposed-modules:
Gyehoek.CPS.Close
Gyehoek.CPS.Convert
Gyehoek.CPS.Lower
Gyehoek.CPS.Stackify
Gyehoek.CPS.Syntax
Gyehoek.Driver
Gyehoek.GenSym
Gyehoek.Options
Gyehoek.Scheme.Syntax
Gyehoek.Sexp
Gyehoek.Stack.Syntax
Gyehoek.Stack.VM
Gyehoek.Wasm
build-depends:
, base ^>=4.21.2.0
, binary
, bytestring
, containers
, typed-process
, deepseq
, effectful
, effectful-core
, effectful-plugin
@@ -87,9 +92,9 @@ library
, template-haskell
, text
, text-short
, typed-process
, unordered-containers
, vector
, bytestring
hs-source-dirs: src
default-language: GHC2024
@@ -100,19 +105,27 @@ test-suite test
hs-source-dirs: test
main-is: Main.hs
other-modules:
Gyehoek.Test.CPS.Stackify
Gyehoek.Test.CPS.Syntax
Gyehoek.Test.Golden
Gyehoek.Test.Sexp
Gyehoek.Test.Stack.VM
build-depends:
, base
, deepseq
, directory
, effectful
, filepath
, generic-lens
, gyehoek
, lens
, process-extras
, sexp-grammar
, tasty
, tasty-expected-failure
, tasty-hunit
, tasty-silver
, text
default-language: GHC2024
+29
View File
@@ -0,0 +1,29 @@
module Gyehoek.CPS.Close
( closeProgram
) where
import Gyehoek.CPS.Syntax
import Effectful
import Data.Functor.Foldable
import Control.Monad ((>=>))
import Control.Lens
import Data.List.NonEmpty (NonEmpty)
import qualified Data.HashSet as HS
cataM
:: (Monad m, Traversable (Base t), Recursive t)
=> (Base t a -> m a) -> t -> m a
cataM f = cata (sequenceA >=> f)
close :: Exp -> Exp
close = cata \case
ExpLetRecF {bindersF,bodyF} -> ExpLetRec binders bodyF
where
binders = bindersF & (each . _2 . _AbsLambda' . _3) %~ \e -> _
e -> embed e
-- let frees = freeWithBound' (HS.fromList $ ktail : bs) e'
closeProgram :: Program -> Eff es Program
closeProgram (MkProgram e) = pure . MkProgram . close $ e
+58 -4
View File
@@ -2,6 +2,7 @@
{- HLINT ignore "Use camelCase" -}
module Gyehoek.CPS.Convert
( convertProgram
, convertExp
) where
import Gyehoek.CPS.Syntax
@@ -13,6 +14,10 @@ import Control.Monad.Cont qualified as Cont
import Control.Lens
import qualified Data.List.NonEmpty as NE
import qualified Gyehoek.Sexp
import Data.String.Interpolate (i)
import Data.Functor (unzip)
import Data.List (List)
import Prelude hiding (unzip)
-- 뻘짓이어라
@@ -37,6 +42,22 @@ convert
convert (Scm.ExpVar x) k = k $ ValVar x
convert (Scm.ExpLit l) k = k $ ValLit l
-- special case: call/cc is desugared during cps-conversion...
convert (Scm.ExpPrim (PrimCallCC withcc)) k = do
convert withcc \withcc' -> do
cc <- gensym' @Name "cc"
r <- gensym' "r"
m <- k $ ValVar r
ccish <- gensym' @Name "cc-ish"
x <- gensym' @Name "x"
pure [cps|
(letrec ((#{cc} (κ (#{r}) #{m})))
(letrec ((#{ccish} (λ (#{x} _) (continue #{cc} #{x}))))
(#{withcc'} #{ccish} #{cc})))
|]
-- ...while all other prims are left as-is for later stages to
-- handle..
convert (Scm.ExpPrim p) k =
telescope (convert @es) p \p' -> do
r <- gensym' "r"
@@ -44,11 +65,10 @@ convert (Scm.ExpPrim p) k =
convert (Scm.ExpLambda xs e) k = do
f <- gensym' "λ-body"
ktail <- gensym' "λ-tail"
m <- convert e $ \e' -> pure $ ExpContinue ktail [e']
lam <- convertLambda xs e
ke <- k $ ValVar f
pure [cps|
(letrec ((#{f} (λ (##{xs} #{ktail}) #{m})))
(letrec ((#{f} #{lam}))
#{ke})
|]
@@ -65,7 +85,38 @@ convert (Scm.ExpIf c t f) k =
convert c \c' ->
ExpIf c' <$> convert t k <*> convert f k
convert _ k = _
-- let-bindings are desugared into continuation calls whose parameters
-- are the left-hand sides and whose arguments are the right-hand
-- sides.
convert (Scm.ExpLet bs e) k =
let rhss = bs ^.. each . _2
in telescope (convert @es) rhss \rhss' -> do
e' <- convert e k
kbody <- gensym' @Name "letrec-body"
let bs' = bs ^.. each . _1
pure [cps|
(letrec ((#{kbody} (κ #{bs'} #{e'})))
(continue #{kbody} ##{rhss'}))
|]
convert (Scm.ExpLetRec bs m) k = do
let bs' = bs & each . _2 %~ (^?! #ExpLambda)
let conv = traverseOf _2 (uncurry $ convertLambda @es)
bs'' <- traverse conv bs'
m' <- convert m k
pure [cps|
(letrec #{bs''} #{m'})
|]
-- convert e k = error [i|unimplemented expr: #{e}|]
convertLambda
:: GenSym :> es
=> List Name -> Scm.Exp -> Eff es Lambda
convertLambda bs m = do
ktail <- gensym' "lambda-tail"
m' <- convert m $ pure . ExpContinue ktail . (:[])
pure [cps|(λ (##{bs} #{ktail}) #{m'})|]
convertProgram :: forall es. (GenSym :> es) => Scm.Program -> Eff es Program
convertProgram p =
@@ -73,3 +124,6 @@ convertProgram p =
pure . Halt1 $ case NE.nonEmpty exps of
Nothing -> ValLit Void
Just es -> NE.last es
convertExp :: forall es. (GenSym :> es) => Scm.Exp -> Eff es Exp
convertExp e = convert e (pure . Halt1)
+44 -44
View File
@@ -26,9 +26,12 @@ import Language.Sexp.Located qualified as SL
import Control.Monad.Fix
import qualified Gyehoek.Sexp
import Data.Text qualified as T
import Data.List qualified
import Data.Foldable (fold)
import Gyehoek.Sexp (encodeOrShow, toSexp)
import Debug.Pretty.Simple
import GHC.Stack (HasCallStack)
import Data.String.Interpolate
data Env = MkEnv
@@ -58,31 +61,34 @@ makeSmallFixnum = [expr|
ref.i31
|]
getArgRegister :: Natural -> SL.Sexp
getArgRegister n = SL.Symbol [i|$arg#{n}|]
-- | Given an expression @e@ leaving a @ref eq@ atop the stack,
-- @pushArg rt n e@ sets the nth slot of the arg-passing array to the
-- result of @e@.
pushArg :: Natural -> Wasm.Expr -> Wasm.Expr
pushArg n e = [expr|
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const #{n})
(@gyehoek begin pushArg)
##{e}
(array.set $arg-array-type)
(global.set #{reg})
(@gyehoek end pushArg)
|]
where reg = getArgRegister n
-- | Pop the nth arg from the arg-passing array onto the stack.
popArg :: Int -> Wasm.Expr
popArg :: Natural -> Wasm.Expr
popArg n = [expr|
(@gyehoek "pop argument")
(global.get $arg-array)
(i32.const #{n})
(array.get $arg-array-type)
(@gyehoek begin popArg)
(global.get #{reg})
ref.as_non_null
(@gyehoek end popArg)
|]
where reg = getArgRegister n
lowerVal :: GenMod :> es => Env -> Val -> Eff es Wasm.Expr
lowerVal :: (HasCallStack, GenMod :> es) => Env -> Val -> Eff es Wasm.Expr
lowerVal g (ValLit l) =
pure $ case l of
@@ -97,17 +103,10 @@ lowerVal g (ValLit l) =
where b' :: Int = if b then 0b11 else 0b01
_ -> _
lowerVal g (ValVar x) = pure $ [expr|(local.get #{l})|]
lowerVal g (ValVar x) = do
pure [expr|(global.get #{l})|]
where
l = succ $ V.elemIndex x g.vars ^?! _Just
-- lowerVal g (ValLambda lam) = do
-- idx <- lowerLambda g lam
-- pure [expr|
-- (i32.const 0)
-- (ref.func #{idx})
-- (struct.new $closure)
-- |]
l = getArgRegister . fromIntegral . succ $ V.elemIndex x g.vars ^?! _Just
lower' :: (GenMod :> es) => Env -> Exp -> Eff es Wasm.Expr
@@ -159,11 +158,12 @@ lower' g (ExpLetRec [(r,AbsLambda lam)] e) = do
let g' = g & #vars <>~ [r]
let n = succ $ length g.vars
e' <- lower' g' e
let reg = getArgRegister . fromIntegral $ n
pure [expr|
(i32.const 0)
(ref.func #{idx})
(struct.new $closure)
(local.set #{n})
(global.set #{reg})
##{e'}
|]
@@ -219,20 +219,14 @@ lower' g e = error $ case Gyehoek.Sexp.encode e of
lowerKappa :: GenMod :> es => Env -> Kappa -> Eff es Idx
lowerKappa g e@(MkKappa xs m) = do
let g' = g & #vars .~ V.fromList xs
let g' = g & #vars <>~ V.fromList xs
m' <- lower' g' m
let body = mconcat
[ xs & ifoldMap \n _ ->
let n' = succ n
in popArg n <> [expr|(local.set #{n'})|]
, m'
]
let origin = encodeOrShow @_ @Text e
idx <- Wasm.defineFunction [wat|
(func (param i32)
(@gyehoek :origin #{origin})
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
##{body})
##{m'})
|]
Wasm.emit [wats|(elem declare funcref (ref.func #{idx}))|]
pure idx
@@ -242,18 +236,12 @@ lowerLambda g e@(MkLambda xs ktail m) = do
let g' = g & #vars .~ V.fromList xs
& #kvars <>~ [ktail]
m' <- lower' g' m
let body = mconcat
[ xs & ifoldMap \n _ ->
let n' = succ n
in popArg n <> [expr|(local.set #{n'})|]
, m'
]
let origin = encodeOrShow @_ @Text e
idx <- Wasm.defineFunction [wat|
(func (param i32)
(@gyehoek :origin #{origin})
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
##{body})
##{m'})
|]
Wasm.emit [wats|(elem declare funcref (ref.func #{idx}))|]
pure idx
@@ -265,6 +253,7 @@ lowerBinOp op g x y (MkKappa [r] e) = do
let op' = SL.Symbol op
let g' = g & #vars <>~ [r]
let n = succ $ length (g ^. #vars)
let reg = getArgRegister . fromIntegral $ n
x' <- lowerVal g x
y' <- lowerVal g y
e' <- lower' g' e
@@ -279,7 +268,7 @@ lowerBinOp op g x y (MkKappa [r] e) = do
i32.shr_u
#{op'}
##{makeSmallFixnum}
(local.set #{n})
(global.set #{reg})
##{e'}
|]
@@ -306,13 +295,24 @@ emitRuntime = mfix \runtime -> do
(global $cont-stack (ref $cont-stack-type)
(array.new_default $cont-stack-type (i32.const 128)))
|]
-- arg array
Wasm.defineType [wat|
(type $arg-array-type (array (mut (ref null eq))))
|]
Wasm.defineGlobal [wat|
(global $arg-array (ref $arg-array-type)
(array.new_default $arg-array-type (i32.const 32)))
-- arg registers
Wasm.defineGlobals [wats|
(global $arg0 (mut (ref null eq)) (ref.null eq))
(global $arg1 (mut (ref null eq)) (ref.null eq))
(global $arg2 (mut (ref null eq)) (ref.null eq))
(global $arg3 (mut (ref null eq)) (ref.null eq))
(global $arg4 (mut (ref null eq)) (ref.null eq))
(global $arg5 (mut (ref null eq)) (ref.null eq))
(global $arg6 (mut (ref null eq)) (ref.null eq))
(global $arg7 (mut (ref null eq)) (ref.null eq))
(global $arg8 (mut (ref null eq)) (ref.null eq))
(global $arg9 (mut (ref null eq)) (ref.null eq))
(global $arg10 (mut (ref null eq)) (ref.null eq))
(global $arg11 (mut (ref null eq)) (ref.null eq))
(global $arg12 (mut (ref null eq)) (ref.null eq))
(global $arg13 (mut (ref null eq)) (ref.null eq))
(global $arg14 (mut (ref null eq)) (ref.null eq))
(global $arg15 (mut (ref null eq)) (ref.null eq))
|]
-- other things 😼
Wasm.defineGlobal [wat|
+148
View File
@@ -0,0 +1,148 @@
{-# LANGUAGE OverloadedLists #-}
module Gyehoek.CPS.Stackify
( stackifyExp
, stackifyProgram
, module Gyehoek.CPS.Syntax
) where
import Gyehoek.CPS.Syntax
import Gyehoek.Stack.Syntax qualified as Stk
import Data.Sequence (Seq)
import Effectful
import Gyehoek.GenSym
import Effectful.Writer.Static.Shared
import Control.Lens
import Data.String.Interpolate
import Gyehoek.Stack.Syntax (Imm(..))
import GHC.Generics (Generic)
import Data.Foldable
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as H
import Data.List (List)
import GHC.Exts (IsList(fromList))
type Stackify = Writer Stk.Program
runStackify :: Eff (Stackify : es) a -> Eff es (a, Stk.Program)
runStackify = runWriter
live :: Free a => Env -> a -> List Name
live g e = free' e & filter \x ->
x `H.member` g.bound
&& not (x `elem` g.contStack)
stackify
:: (GenSym :> es, Stackify :> es)
=> Env -> Exp -> Eff es (Seq Stk.Instr)
stackify g (ExpLetRec [(f, kap@(AbsKappa' xs m))] e) = do
let vs = (f, Stk.ValLabel f) : (bindReg <$> xs)
let ls = live g kap
m' <- stackify (g & #bound .~ H.fromList (vs ++ (bindReg <$> ls))) m
tell [Stk.MkBlock f xs $
[Stk.Pop x | x <- ls] <> toList m']
let g' = g & #bound . at f ?~ Stk.ValLabel f
& #liveness . at f ?~ ls
stackify g' e
stackify g (ExpLetRec [(f, AbsLambda' xs k m)] e) = do
let vs = (k:xs) <&> \x -> (x, Stk.ValReg x)
lam_body <- gensym' "lambda-body"
m' <- stackify (g & #bound .~ H.fromList vs
& #bound . at f ?~ Stk.ValLabel lam_body
& #contStack %~ (k:)) m
tell [Stk.MkBlock lam_body xs . toList $ m']
stackify (g & #bound . at f ?~ Stk.ValLabel lam_body) e
stackify g (ExpIf c t f) = do
t' <- stackify g t
f' <- stackify g f
pure [ Stk.If (stackifyVal g c) (toList t') (toList f') ]
stackify g (ExpApply f xs ktail) = do
pure $
[ Stk.PushCont k ]
<> fromList [ Stk.Push (Stk.ValReg l) | l <- ls ]
<> [ Stk.Call (stackifyVal g f) (stackifyVal g <$> xs) ]
where
k = var g ktail
ls = fold $ (k ^? #ValImm . #ImmLabel)
>>= \klbl -> g ^. #liveness . at klbl
-- this probably won't work for call/cc, for cps-converted code it'll
-- be fine i think. notice how, instead of calling `var g k`, we just
-- assume it's the return continuation on top of the stack.
stackify g (ExpContinue k xs) = do
ktail <- gensym' $ k ^. _Wrapped'
pure $
fromList [ Stk.PopCont "_" | _ <- takeWhile (/= k) g.contStack ]
<> [ Stk.PopCont ktail
, Stk.Call (Stk.ValReg ktail) (stackifyVal g <$> xs)
]
stackify g (ExpPrim p (MkKappa [x] e)) = do
e' <- stackify (g & #bound . at x ?~ Stk.ValReg x) e
pure $ [ Stk.Prim x (stackifyVal g <$> p) ] <> e'
stackify _ e = error [i|unimplemented exp: #{e}|]
stackifyVal :: Env -> Val -> Stk.Val
stackifyVal g = \case
ValLit (LitInt n) -> Stk.ValImm (ImmInt n)
ValLit (LitBool b) -> Stk.ValImm (ImmBool b)
ValVar v -> var g v
v -> error [i|unimplemented val: #{v}|]
var :: Env -> Name -> Stk.Val
var g v = case g ^. #bound . at v of
Just x -> x
Nothing -> Stk.ValLabel v
bindReg :: Name -> (Name, Stk.Val)
bindReg x = (x, Stk.ValReg x)
data Env = MkEnv
{ bound :: HashMap Name Stk.Val
-- | for each locally-bound continuation @k@, @liveness@ has an
-- entry @(k,ls)@ where @ls@ is the sequence of registers @k@
-- expects to find saved on the stack.
, liveness :: HashMap Name (List Name)
, contStack :: List Name
}
deriving (Show, Generic)
emptyEnv :: Env
emptyEnv = MkEnv mempty mempty ["halt"]
stackifyExp :: GenSym :> es => Name -> Exp -> Eff es Stk.Program
stackifyExp lbl e = do
(code,p) <- runStackify $ stackify emptyEnv e
pure $ p <> Stk.MkProgram [ Stk.MkBlock lbl [] (toList code) ]
stackifyProgram :: GenSym :> es => Program -> Eff es Stk.Program
stackifyProgram (MkProgram e) = stackifyExp "main" e
fac :: Program
fac = [cps|
(letrec ((fac (λ (n ktail)
(prim (zero? n)
(κ (x0)
(if x0
(continue ktail 1)
(prim (- n 1)
(κ (x1)
(letrec ((fac-k0
(κ (x2)
(prim (* n x2)
(κ (x3)
(continue ktail x3))))))
(fac x1 fac-k0))))))))))
(fac 6 halt))
|]
+126 -42
View File
@@ -9,6 +9,7 @@ module Gyehoek.CPS.Syntax
, Kappa(..)
, Lambda(..)
, Exp(..)
, ExpF(..)
, Name(..)
, Prim(..)
, Program(..)
@@ -20,6 +21,7 @@ module Gyehoek.CPS.Syntax
, _ExpPrim
, _ExpLetRec
, _ExpApply
, _AbsLambda'
, binders
, body
, op
@@ -29,8 +31,9 @@ module Gyehoek.CPS.Syntax
, pattern AbsLambda'
, pattern AbsKappa'
, Abs(..)
, free
, free'
, Free(..)
, Vars(..)
, Subst(..)
)
where
@@ -39,6 +42,7 @@ import Gyehoek.Sexp qualified
import Gyehoek.Scheme.Syntax (Name (..), Prim(..), primSexpIso, Lit(..), pattern Void, getName)
import Data.List (List)
import GHC.Generics (Generic)
import Data.Generics.Labels ()
import Language.SexpGrammar.Generic
import Control.Category
import Control.Lens hiding (op)
@@ -55,6 +59,8 @@ import qualified Data.HashSet as HS
import Data.Hashable (Hashable)
import Data.Monoid (Endo)
import Data.Containers.ListUtils (nubOrd)
import Data.Functor.Foldable.TH
import Data.Functor.Foldable (Recursive(..), Corecursive (..))
-- Data types
@@ -74,7 +80,10 @@ data Abs
| AbsLambda Lambda
deriving (Show, Generic, Data, Eq)
pattern AbsKappa' :: [Name] -> Exp -> Abs
pattern AbsKappa' xs e = AbsKappa (MkKappa xs e)
pattern AbsLambda' :: [Name] -> Name -> Exp -> Abs
pattern AbsLambda' xs e ktail = AbsLambda (MkLambda xs e ktail)
data Exp
@@ -114,6 +123,7 @@ makePrisms ''Exp
makeFieldsId ''Exp
makeFieldsId ''Kappa
makeFieldsId ''Lambda
makeBaseFunctor ''Exp
instance HasBinders Abs (List Name) where
binders k (AbsKappa kap) = AbsKappa <$> binders k kap
@@ -123,6 +133,12 @@ instance HasBody Abs Exp where
body k (AbsKappa kap) = AbsKappa <$> body k kap
body k (AbsLambda lam) = AbsLambda <$> body k lam
_AbsLambda' :: Prism' Abs (List Name, Name, Exp)
_AbsLambda' = prism'
(\(bs,ktail,e) -> AbsLambda' bs ktail e)
(\case AbsLambda' bs ktail e -> Just (bs,ktail,e)
_ -> Nothing)
-- SexpIso instances
@@ -219,6 +235,7 @@ instance CPS Val where toCPS = Gyehoek.Sexp.fromSexp
instance CPS Kappa where toCPS = Gyehoek.Sexp.fromSexp
instance CPS Lambda where toCPS = Gyehoek.Sexp.fromSexp
instance CPS Abs where toCPS = Gyehoek.Sexp.fromSexp
instance CPS Program where toCPS = Gyehoek.Sexp.fromSexp
cps :: QuasiQuoter
cps = Gyehoek.Sexp.makeSx' [| toCPS |]
@@ -234,49 +251,116 @@ insertFrom = flip $ foldr HS.insert
toHashSetOf :: Hashable a => Getting (Endo (HashSet a)) s a -> s -> HashSet a
toHashSetOf l = foldrOf l HS.insert mempty
free :: Exp -> HashSet Name
free = go where
gokap (MkKappa xs m) = go m & deleteFrom xs
golam (MkLambda xs k m) = go m & deleteFrom xs & sans k
goabs = \case
AbsKappa kap -> gokap kap
AbsLambda lam -> golam lam
go = \case
class Free a where
free :: a -> HashSet Name
free = freeWithBound mempty
freeWithBound :: HashSet Name -> a -> HashSet Name
freeWithBound bound = HS.fromList . freeWithBound' bound
-- | Free variables given in the order of their appearance.
free' :: a -> List Name
free' = freeWithBound' mempty
freeWithBound' :: HashSet Name -> a -> List Name
instance Free Abs where
freeWithBound' bound (AbsKappa kap) = freeWithBound' bound kap
freeWithBound' bound (AbsLambda lam) = freeWithBound' bound lam
instance Free Exp where
freeWithBound' bound = \case
ExpPrim p k ->
p & toHashSetOf (folded . #ValVar)
& HS.union (gokap k)
p & toListOf (folded . #ValVar . filtered (`notElem` bound))
& (<> freeWithBound' bound k)
ExpLetRec bs m ->
foldMapOf (each . _2) goabs bs <> go m
& deleteFrom (bs ^.. each . _1)
ExpContinue k xs -> HS.fromList $ k : xs ^.. each . #ValVar
ExpIf c t f -> toHashSetOf #ValVar c <> go t <> go f
ExpApply f xs k -> toHashSetOf (each . #ValVar) (f:xs) <> HS.singleton k
foldMapOf (each . _2) (freeWithBound' bound') bs
<> freeWithBound' bound' m
where bound' = bound & insertFrom (bs ^.. each . _1)
ExpContinue k xs -> filter (`notElem` bound) (k : xs ^.. each . #ValVar)
ExpIf c t f ->
(c ^.. #ValVar . filtered (`notElem` bound))
<> freeWithBound' bound t <> freeWithBound' bound f
ExpApply f xs k ->
(f:xs) ^.. (each . #ValVar . filtered (`notElem` bound))
<> (k ^.. filtered (`notElem` bound))
-- | Free variables given in the order of their appearance.
free' :: Exp -> List Name
free' = nubOrd . goFree HS.empty where
instance Free Kappa where
freeWithBound' bound (MkKappa xs m) =
freeWithBound' (bound & insertFrom xs) m
goFreeKap bound (MkKappa xs m) = goFree (bound & insertFrom xs) m
goFreeLam bound (MkLambda xs k m) = goFree (bound & insertFrom (k:xs)) m
goFreeAbs bound = \case
AbsKappa kap -> goFreeKap bound kap
AbsLambda lam -> goFreeLam bound lam
instance Free Lambda where
freeWithBound' bound (MkLambda xs k m) =
freeWithBound' (bound & insertFrom (k:xs)) m
goFree :: HashSet Name -> Exp -> List Name
goFree bound = \case
ExpPrim p k ->
p & toListOf (folded . #ValVar . filtered (`notElem` bound))
& (<> goFreeKap bound k)
ExpLetRec bs m ->
foldMapOf (each . _2) (goFreeAbs bound') bs <> goFree bound' m
where bound' = bound & insertFrom (bs ^.. each . _1)
ExpContinue k xs -> filter (`notElem` bound) (k : xs ^.. each . #ValVar)
ExpIf c t f ->
(c ^.. #ValVar . filtered (`notElem` bound))
<> goFree bound t <> goFree bound f
ExpApply f xs k ->
(f:xs) ^.. (each . #ValVar . filtered (`notElem` bound))
<> (k ^.. filtered (`notElem` bound))
freeLambda :: Lambda -> List Name
freeLambda (MkLambda {binders,ktail,body}) = _
class Vars a where
-- | Traverse the immediate variables of an expression.
vars :: Traversal' a Name
instance Vars Val where
vars k (ValVar x) = ValVar <$> k x
vars _ x = pure x
instance Vars a => Vars (Prim a) where
vars k p = traverseOf (each . vars) k p
instance Vars Exp where
vars k (ExpPrim p kap) = ExpPrim <$> vars k p <*> pure kap
vars k (ExpContinue kname xs) = ExpContinue <$> k kname <*> pure xs
vars _ e = pure e
data Scope
= Bind (List Name) (List Scope)
| Use (List Name) (List Scope)
deriving (Show, Eq)
makeBaseFunctor ''Scope
class Scoped a where
scope :: a -> Scope
instance Scoped Kappa where
scope (MkKappa bs e) =
Bind bs [scope e]
instance Scoped Lambda where
scope (MkLambda bs k e) = Bind (bs ++ [k]) [scope e]
instance Scoped Abs where
scope = \case
AbsKappa k -> scope k
AbsLambda l -> scope l
instance Scoped Val where
scope = \case
ValVar x -> Use [x] []
_ -> Use [] []
instance Scoped Exp where
scope = \case
ExpApply f xs k ->
Use (((f:xs) ^.. each . _ValVar) ++ [k]) []
ExpLetRec bs e ->
Bind (bs ^.. each . _1) $
(bs ^.. each . _2 . to scope)
++ [scope e]
ExpPrim p k ->
Use (p ^.. each . _ValVar) [scope k]
ExpContinue k xs ->
Use (k : (xs ^.. each . _ValVar)) []
ExpIf c t f ->
Use (c ^.. _ValVar) [ scope t, scope f ]
class Subst a where
substWith :: (Name -> Maybe Val) -> a -> a
instance Subst Exp where
substWith f = go HS.empty where
go bound e = case scope e of
Use xs ss -> _
+24 -5
View File
@@ -1,5 +1,5 @@
module Gyehoek.Driver
(main, lower_e2e, convert_e2e, parse_e2e)
(main, lower_e2e, convert_e2e, parse_e2e, readScm, eval_e2e)
where
import Gyehoek.Options
@@ -28,6 +28,13 @@ import System.Environment.Blank (getEnvDefault)
import GHC.Conc (atomically)
import qualified Data.Text.IO as TIO
import qualified Data.ByteString.Lazy as BS
import Gyehoek.CPS.Stackify (stackifyProgram)
import Text.Pretty.Simple (pShow)
import Gyehoek.Stack.VM (eval, writeObj, Obj)
import qualified Data.Text as T
import Data.List (List)
import Gyehoek.Stack.Syntax (encodeProgram)
import Effectful.Exception
main :: IO ()
@@ -101,12 +108,19 @@ driver opts = do
cps <- convertProgram scm
when opts.dumpCPS do
hPutStrLn FS.stdout $ Sexp.encodePretty cps ^?! _Right
wat <- lowerProgram cps
if not opts.inspectWasm then
stk <- stackifyProgram cps
if opts.dumpStackified then do
hPutStrLn FS.stdout . encodeProgram $ stk
else if opts.stackify then do
eval stk & fmap writeObj
& T.unwords
& hPutStrLn FS.stdout
else do
wat <- lowerProgram cps
withFile opts.output FS.WriteMode \h ->
hPutStrLn h wat
else
inspectWasm wat
when opts.inspectWasm do
inspectWasm wat
parse_e2e :: FilePath -> IO Scm.Program
parse_e2e = runEff . runFileSystem . readScm
@@ -118,3 +132,8 @@ lower_e2e :: FilePath -> IO Text
lower_e2e =
runEff . runFileSystem . runGenSym
. (lowerProgram <=< convertProgram <=< readScm)
eval_e2e :: FilePath -> IO (List Obj)
eval_e2e fp = runEff . runFileSystem . runGenSym $ do
stk <- stackifyProgram <=< convertProgram <=< readScm $ fp
pure . eval $ stk
-1
View File
@@ -8,7 +8,6 @@ import Effectful.Dispatch.Dynamic
import Effectful
import Data.String (IsString(fromString))
import Data.Text (Text)
import qualified Data.Text.Short as ST
class Gen a where
+7 -3
View File
@@ -15,10 +15,10 @@ import GHC.Generics (Generic)
data Options = MkOptions
{ -- dumpANF :: Maybe FilePath
-- , dumpQBE :: Maybe FilePath
dumpCPS :: Bool
{ dumpCPS :: Bool
, dumpParsed :: Bool
, dumpStackified :: Bool
, stackify :: Bool
, inspectWasm :: Bool
, output :: FilePath
, sourceFile :: FilePath
@@ -49,6 +49,8 @@ parseOutput = strOption
)
parseDumpCPS = switch (long "dump-cps")
parseDumpStackified = switch (long "dump-stackified")
parseStackify = switch (long "stackify")
parseDumpParsed = switch (long "dump-parsed")
parseInspectWasm = switch $ long "inspect-wasm" <> short 'p'
@@ -56,6 +58,8 @@ parser :: Parser Options
parser = MkOptions
<$> parseDumpCPS
<*> parseDumpParsed
<*> parseDumpStackified
<*> parseStackify
<*> parseInspectWasm
<*> parseOutput
<*> argument str (metavar "FILE")
+77 -39
View File
@@ -8,6 +8,7 @@
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OrPatterns #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE DeriveAnyClass #-}
module Gyehoek.Scheme.Syntax
( Name(..)
, Prim(..)
@@ -23,6 +24,8 @@ module Gyehoek.Scheme.Syntax
, subst
, getName
, scm
, readExp
, readProgram
)
where
@@ -31,15 +34,16 @@ import Data.List (List)
import Language.SexpGrammar
( SexpIso(..), list, el, rest, sym, symbol )
import Language.SexpGrammar qualified as Sexp
import Language.Sexp.Located qualified as S
import Language.SexpGrammar.Generic
import GHC.Generics
import Effectful
import GHC.Generics (Generic)
import Prelude hiding ((.), id)
import Control.Category
import Data.List.NonEmpty (NonEmpty)
import Gyehoek.Sexp qualified
import Gyehoek.Sexp qualified as GS
import Gyehoek.GenSym (Gen)
import Control.Lens
import Data.Generics.Labels ()
import Data.String (IsString)
import Data.Hashable (Hashable)
import Data.Data (Data)
@@ -49,11 +53,20 @@ import Data.HashSet (HashSet)
import qualified Data.HashSet as HS
import Data.Foldable (fold)
import Language.Haskell.TH.Quote (QuasiQuoter)
import Effectful.FileSystem (runFileSystem)
import qualified Effectful.FileSystem.IO as FS
import qualified Data.Text.Encoding as T
import qualified Effectful.FileSystem.IO.ByteString as FB
import Control.DeepSeq (NFData)
newtype Name = MkName { inner :: Text }
deriving newtype (Show, Eq, Ord, IsString, Gen, Hashable)
deriving stock (Generic, Data)
deriving anyclass (Wrapped, NFData)
instance Prefixed Name where
prefixed (MkName s) = _Wrapped' . prefixed @Text s . from _Wrapped'
getName :: Name -> Text
getName (MkName x) = x
@@ -72,7 +85,11 @@ data Prim e
| PrimWrite e
| PrimZeroP e
| PrimNewline
deriving (Show, Generic, Functor, Foldable, Traversable, Data, Eq)
| PrimMakeClosure { code :: e, env :: List e }
| PrimEnvRef e Int
| PrimCallCC e
deriving stock (Show, Generic, Functor, Foldable, Traversable, Data, Eq)
deriving anyclass (NFData)
instance Each (Prim e) (Prim e') e e'
@@ -82,7 +99,8 @@ data Lit
| LitBool Bool
| LitString Text
| LitQuote Sexp
deriving (Show, Generic, Data, Eq)
deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData)
pattern Void :: Lit
pattern Void = LitNil
@@ -90,10 +108,12 @@ pattern Void = LitNil
data Def
= DefConstant Name Exp
| DefProcedure Name (List Name) (List Exp)
deriving (Show, Generic, Data)
deriving stock (Show, Generic, Data)
deriving anyclass (NFData)
data Exp
= ExpLet (NonEmpty (Name, Exp)) Exp
| ExpLetRec (NonEmpty (Name, Exp)) Exp
| ExpPrim (Prim Exp)
| ExpBegin (List Exp)
| ExpIf Exp Exp Exp
@@ -101,24 +121,28 @@ data Exp
| ExpLambda (List Name) Exp
| ExpVar Name
| ExpApply Exp (List Exp)
deriving (Show, Generic, Data)
deriving stock (Show, Generic, Data)
deriving anyclass (NFData)
data Sexp
= SexpCons Sexp Sexp
| SexpSymbol Text
| SexpLit Lit
deriving (Show, Generic, Data, Eq)
deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData)
data CommandOrDef
= Command Exp
| Definition Def
| Begin (List CommandOrDef)
deriving (Show, Generic, Data)
deriving stock (Show, Generic, Data)
deriving anyclass (NFData)
data Program = MkProgram
{ commandsAndDefs :: List CommandOrDef
}
deriving (Show, Generic, Data)
deriving stock (Show, Generic, Data)
deriving anyclass (NFData)
instance Each Program Program (Either Exp Def) (Either Exp Def) where
each = #commandsAndDefs . each . go
@@ -141,25 +165,29 @@ instance SexpIso Name where
primSexpIso :: (Text -> Text) -> Sexp.SexpGrammar a -> Sexp.SexpGrammar (Prim a)
primSexpIso namefn a = match
$ With (. binop "+")
$ With (. binop "-")
$ With (. binop "*")
$ With (. binop "/")
$ With (. binop "cons")
$ With (. unop "car")
$ With (. unop "cdr")
$ With (. unop "immediate?")
$ With (. unop "cons?")
$ With (. unop "integer?")
$ With (. unop "write")
$ With (. unop "zero?")
$ With (. ht2 "+")
$ With (. ht2 "-")
$ With (. ht2 "*")
$ With (. ht2 "/")
$ With (. ht2 "cons")
$ With (. ht1 "car")
$ With (. ht1 "cdr")
$ With (. ht1 "immediate?")
$ With (. ht1 "cons?")
$ With (. ht1 "integer?")
$ With (. ht1 "write")
$ With (. ht1 "zero?")
$ With (. nullop "newline")
$ With (. ht1' "make-closure")
$ With (. GS.headTagged2 (namefn "env-ref") a Sexp.int)
$ With (. ht1 "call/cc")
$ End
where
idn s = el (sym (namefn s))
nullop s = list $ idn s
unop s = list $ idn s >>> el a
binop s = list $ idn s >>> el a >>> el a
ht1 s = GS.headTagged1 (namefn s) a
ht2 s = GS.headTagged2 (namefn s) a a
ht1' s = GS.headTagged1' (namefn s) a a
instance SexpIso a => SexpIso (Prim a) where
-- sexpIso = primSexpIso ("prim:"<>) sexpIso
@@ -169,23 +197,14 @@ instance SexpIso Lit where
sexpIso = match
$ With (. sexpIso)
$ With (. sym "nil")
$ With (. bool)
$ With (. GS.schemeBool)
$ With (. sexpIso)
$ With (. Gyehoek.Sexp.prefixSugar "quote" Sexp.Quote sexpIso)
$ With (. GS.prefixSugar "quote" Sexp.Quote sexpIso)
$ End
where
bool :: Sexp.SexpGrammar Bool
bool = Sexp.hashed $ Sexp.partialOsi f g
where
f (S.Symbol ("t";"true")) = Right True
f (S.Symbol ("f";"false")) = Right False
f _ = Left $ Sexp.expected "bool"
g True = S.Symbol "true"
g False = S.Symbol "false"
instance SexpIso Sexp where
sexpIso = match
$ With (\conss -> conss . Gyehoek.Sexp.todo)
$ With (\conss -> conss . GS.todo)
$ With (\s -> s . symbol)
$ With (\lit -> lit . sexpIso)
$ End
@@ -202,7 +221,8 @@ instance SexpIso Def where
instance SexpIso Exp where
sexpIso = match
$ With (. Gyehoek.Sexp.let_ "let" sexpIso sexpIso sexpIso)
$ With (. GS.let_ "let" sexpIso sexpIso sexpIso)
$ With (. GS.let_ "letrec" sexpIso sexpIso sexpIso)
$ With (. sexpIso)
$ With (\bgn -> bgn . list (el (sym "begin") >>> rest sexpIso))
$ With (. if_)
@@ -214,7 +234,7 @@ instance SexpIso Exp where
where
if_ = list $ el (sym "if") >>> el sexpIso >>> el sexpIso >>> el sexpIso
lam = list
( el Gyehoek.Sexp.lambdaKeyword
( el GS.lambdaKeyword
>>> el (sexpIso @(List Name))
>>> el sexpIso )
@@ -231,7 +251,7 @@ instance SexpIso CommandOrDef where
-- utilities
scm :: QuasiQuoter
scm = Gyehoek.Sexp.makeSx [|| Gyehoek.Sexp.fromSexp @Exp ||]
scm = GS.makeSx [|| GS.fromSexp @Exp ||]
free :: Exp -> HashSet Name
free = cata \case
@@ -254,3 +274,21 @@ subst f = \e -> cata go e mempty where
go (ExpLetF _ _) _ = error "todo lol"
go (ExpLambdaF bs e) bound = e $ insertFrom bs bound
go e bound = embed $ fmap ($ bound) e
fileName :: FilePath -> FilePath
fileName "-" = "<interactive>"
fileName e = e
hGetContents :: FS.FileSystem :> es => FS.Handle -> Eff es Text
hGetContents h = T.decodeUtf8 <$> FB.hGetContents h
readProgram :: IOE :> es => FilePath -> Eff es Program
readProgram fp = runFileSystem $
FS.withFile fp FS.ReadMode $ \h ->
GS.parseSexps @CommandOrDef (fileName fp) <$> hGetContents h
>>= either error (pure . MkProgram)
readExp :: IOE :> es => FilePath -> Eff es Exp
readExp fp = readProgram fp <&> (^?! #commandsAndDefs . _head . #Command)
+69 -51
View File
@@ -25,8 +25,6 @@ module Gyehoek.Sexp
, lambdaKeyword
, encodePrettyWith
, encodePretty
, UglySexpIso(..)
, AsSexpIso(..)
, SpliceSexp(..)
, parseSexpsWithPos
, parseSexpWithPos
@@ -38,10 +36,17 @@ module Gyehoek.Sexp
, makeSx'
, toSexp
, fromSexp
, fromSexp'
, stripLocation
, format
, equivalent
, encodeOrShow
, readSxs
, prismIso
, schemeBool
, headTagged1'
, headTagged1
, headTagged2
)
where
@@ -49,44 +54,33 @@ import Data.Text (Text)
import Language.SexpGrammar as Sexp hiding (toSexp, List, encode, decode, encodeWith, decodeWith, iso, encodePrettyWith, encodePretty, fromSexp)
import Language.SexpGrammar qualified as Sexp
import Language.Sexp qualified as S
import Language.SexpGrammar.Generic
import Data.InvertibleGrammar.Base qualified as IGB
import Data.InvertibleGrammar qualified as IG
import Data.InvertibleGrammar.Base ((:-)((:-)))
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.List.NonEmpty qualified as NE
import Data.List (List, groupBy)
import Data.Text.Encoding
import Data.Either (either)
import GHC.Generics (Generic)
import Control.Lens hiding (para)
import Data.Generics.Labels
import System.Process
import GHC.IO.Unsafe (unsafePerformIO)
import qualified Data.Text.IO as TIO
import Control.Monad (join)
import qualified Language.Sexp.Located as SL
import Data.Void (absurd, Void)
import Data.Coerce (coerce)
import qualified Data.Map
import Data.Void (absurd)
import Language.Haskell.TH.Quote
import Language.Haskell.TH (Quote, location, Loc (..), ExpQ, varE, mkName, listE, Exp, appE, conE, Q, Code, unTypeCode)
import Language.Haskell.TH (Quote, location, Loc (..), ExpQ, varE, mkName, listE, Exp, appE, Q, Code, unTypeCode)
import qualified Data.Text as T
import qualified Control.Category
import Data.Data (Data (..), Typeable, cast)
import Language.Haskell.TH.Syntax (lift, Lift, liftData)
import GHC.IsList (fromList)
import Data.Functor.Foldable (cata, para, embed)
import Data.Functor.Classes (Show1(..))
import Data.Functor.Foldable (cata)
import Data.Vector (Vector)
import Numeric.Natural (Natural)
import Data.Maybe (fromMaybe)
import Control.Applicative (Alternative((<|>)))
import Debug.Pretty.Simple
import qualified Data.Vector as V
import qualified Data.Vector.Strict
import Data.Function (on)
import Data.String (IsString (fromString))
import Effectful
import qualified Effectful.FileSystem.IO as FS
import qualified Effectful.FileSystem.IO.ByteString as FB
import qualified Data.Text.Encoding as T
sexp :: SexpIso a => Iso' a Text
@@ -120,6 +114,10 @@ parseSexps :: SexpIso a => FilePath -> Text -> Either String (List a)
parseSexps f = marshal . SL.parseSexps f . view lazy . encodeUtf8
where marshal = join . traverseOf (_Right . each) (Sexp.fromSexp sexpIso)
parseSexpsWith :: SexpGrammar a -> FilePath -> Text -> Either String (List a)
parseSexpsWith g f = marshal . SL.parseSexps f . view lazy . encodeUtf8
where marshal = join . traverseOf (_Right . each) (Sexp.fromSexp g)
parseSexp :: SexpIso a => FilePath -> Text -> Either String a
parseSexp f = marshal . SL.parseSexp f . view lazy . encodeUtf8
where marshal = join . traverseOf _Right (Sexp.fromSexp sexpIso)
@@ -140,6 +138,24 @@ parseSexpWithPos g pos =
marshal . SL.parseSexpWithPos pos . view lazy . encodeUtf8
where marshal = join . traverseOf _Right (Sexp.fromSexp g)
fileName :: FilePath -> FilePath
fileName "-" = "<interactive>"
fileName e = e
hGetContents :: FS.FileSystem :> es => FS.Handle -> Eff es Text
hGetContents h = T.decodeUtf8 <$> FB.hGetContents h
readSxs
:: IOE :> es
=> SexpGrammar a
-> FilePath -> Eff es (List a)
readSxs g fp = FS.runFileSystem $
FS.withFile fp FS.ReadMode $ \h ->
parseSexpsWith g (fileName fp) <$> hGetContents h
>>= either error pure
nonEmptyGrammar :: Grammar p (NonEmpty x :- t) (List x :- x :- t)
nonEmptyGrammar = IGB.Iso
(\((x:|xs) :- t) -> reverse xs :- x :- t)
@@ -166,11 +182,6 @@ let_ kw name rhs e = list (el (sym kw) >>> el bindings >>> el e)
data DotList a = MkDotList (NonEmpty a) a
deriving (Show, Generic)
dotlist :: (forall t. Grammar Position (Sexp :- t) (a :- t)) -> _
dotlist x = list $ rest $ coproduct
[ x >>> _
]
-- | Define a sexp representation as either (⟨name⟩ ⟨e⟩) or '⟨e⟩.
prefixSugar
:: Text -> Prefix
@@ -184,7 +195,7 @@ prefixSugar name prefix e = coproduct
]
todo :: Grammar p (Sexp :- t) t'
todo = (IGB.Flip $ IGB.PartialIso absurd f) >>> IGB.PartialIso absurd g
todo = IGB.Flip (IGB.PartialIso absurd f) >>> IGB.PartialIso absurd g
where
f _ = Left $ unexpected "todo"
g _ = Left $ unexpected "todo"
@@ -210,39 +221,43 @@ lambda name e = list $
isoIso :: Iso' s a -> Grammar p (s :- t) (a :- t)
isoIso l = Sexp.iso (view l) (review l)
prismIso :: Mismatch -> Prism' s a -> Grammar p (s :- t) (a :- t)
prismIso mm p = Sexp.partialOsi
(maybe (Left mm) Right . preview p)
(review p)
kappaKeyword :: Grammar Position (Sexp :- t) t
kappaKeyword = coproduct [ sym "κ", sym "kappa" ]
lambdaKeyword :: Grammar Position (Sexp :- t) t
lambdaKeyword = coproduct [ sym "λ", sym "lambda" ]
schemeBool :: SexpGrammar Bool
schemeBool = Sexp.hashed $ Sexp.partialOsi f g
where
f (SL.Symbol ("t";"true")) = Right True
f (SL.Symbol ("f";"false")) = Right False
f _ = Left $ Sexp.expected "bool"
g True = SL.Symbol "true"
g False = SL.Symbol "false"
headTagged1 :: Text -> SexpGrammar a -> Grammar Position (Sexp :- t) (a :- t)
headTagged1 s g1 = list $ el (sym s) >>> el g1
headTagged1'
:: Text
-> SexpGrammar a -> SexpGrammar b
-> Grammar Position (Sexp :- t) (List b :- a :- t)
headTagged1' s g1 gt = list $ el (sym s) >>> el g1 >>> rest gt
headTagged2
:: Text
-> SexpGrammar a -> SexpGrammar b
-> Grammar Position (Sexp :- t) (b :- a :- t)
headTagged2 s g1 g2 = list $ el (sym s) >>> el g1 >>> el g2
class UglySexpIso a where
uglySexpIso :: SexpGrammar a
newtype AsSexpIso a = AsSexpIso a
newtype AsUglySexpIso a = AsUglySexpIso a
asSexpIso :: Grammar p (a :- t) (AsSexpIso a :- t)
asSexpIso = Sexp.iso AsSexpIso (\(AsSexpIso x) -> x)
instance UglySexpIso a => SexpIso (AsUglySexpIso a) where
sexpIso = uglySexpIso @a >>> Sexp.iso coerce coerce
instance SexpIso a => UglySexpIso (AsSexpIso a) where
uglySexpIso = sexpIso >>> Sexp.iso (\x -> AsSexpIso x) (\(AsSexpIso x) -> x)
-- why not work
-- deriving via AsSexpIso Text instance UglySexpIso Text
instance UglySexpIso Text where uglySexpIso = sexpIso
instance UglySexpIso Integer where uglySexpIso = sexpIso
instance UglySexpIso Int where uglySexpIso = sexpIso
instance UglySexpIso Bool where uglySexpIso = sexpIso
instance UglySexpIso Double where uglySexpIso = sexpIso
instance UglySexpIso () where uglySexpIso = sexpIso
instance SexpIso Sexp where
sexpIso = Control.Category.id
@@ -272,8 +287,11 @@ toSexp = either error id . Sexp.toSexp sexpIso
toSexps :: (Foldable f, SexpIso a) => f a -> List Sexp
toSexps = foldMap \x -> [toSexp x]
pattern Unquote :: Text -> Sexp
pattern Unquote x =
SL.Modified Hash (SL.BraceList [SL.Symbol x])
pattern UnquoteSplicing :: Text -> Sexp
pattern UnquoteSplicing x =
SL.Modified Hash (SL.Modified Hash (SL.BraceList [SL.Symbol x]))
+150
View File
@@ -0,0 +1,150 @@
{-# LANGUAGE TemplateHaskellQuotes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveAnyClass #-}
module Gyehoek.Stack.Syntax
( Program(..)
, Block(..)
, Instr(..)
, Val(..)
, Lit(..)
, Obj(..)
, Imm(..)
, Prim(..)
, Name
, pattern ValLabel
, encodeProgram
) where
import Control.Lens
import Data.List (List)
import GHC.Generics (Generic)
import Data.HashMap.Strict (HashMap)
import Language.SexpGrammar (SexpIso, (>>>), (:-))
import Language.SexpGrammar qualified as S
import Language.SexpGrammar.Generic
import Data.Coerce (coerce)
import Data.Text (Text)
import qualified Gyehoek.Sexp
import Language.Haskell.TH.Quote (QuasiQuoter)
import Data.Data (Data)
import qualified Data.HashMap.Strict as H
import Effectful
import Gyehoek.Scheme.Syntax (Name(..), Lit(..), Prim(..))
import GHC.Exts (IsList(..))
import Data.List (intersperse)
import Control.DeepSeq (NFData)
newtype Program = MkProgram
{ blocks :: List Block
}
deriving stock (Show, Generic, Data)
deriving newtype (Semigroup, Monoid)
deriving anyclass (NFData)
instance IsList Program where
type Item Program = Block
fromList = MkProgram
toList = view #blocks
data Block = MkBlock
{ label :: Name
, params :: List Name
, code :: List Instr
}
deriving stock (Show, Generic, Data)
deriving anyclass (NFData)
instance Each Block Block Instr Instr where
each = #code . each
data Instr
= Pop Name
| Push Val
| PopCont Name
| PushCont Val
| Prim Name (Prim Val)
| Call Val (List Val)
| If Val (List Instr) (List Instr)
deriving stock (Show, Generic, Data)
deriving anyclass (NFData)
data Val
= ValReg Name
| ValImm Imm
deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData)
pattern ValLabel :: Name -> Val
pattern ValLabel x = ValImm (ImmLabel x)
data Imm
= ImmInt Int
| ImmBool Bool
| ImmLabel Name
deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData)
data Obj
= ObjImm Imm
deriving stock (Show, Generic, Data, Eq)
deriving anyclass (NFData)
--- sexp work
pure []
instance SexpIso Instr where
sexpIso = match
$ With (Gyehoek.Sexp.headTagged1 "pop!" regName >>>)
$ With (Gyehoek.Sexp.headTagged1 "push!" S.sexpIso >>>)
$ With (Gyehoek.Sexp.headTagged1 "pop-cont!" regName >>>)
$ With (Gyehoek.Sexp.headTagged1 "push-cont!" S.sexpIso >>>)
$ With (Gyehoek.Sexp.headTagged2 "prim" regName S.sexpIso >>>)
$ With (Gyehoek.Sexp.headTagged1' "call" S.sexpIso S.sexpIso >>>)
$ With (if_ >>>)
$ End
where
if_ = S.list $ S.el (S.sym "if")
>>> S.el (S.sexpIso @Val)
>>> S.el (S.list $ S.el (S.sym "then") >>> S.rest (S.sexpIso @Instr))
>>> S.el (S.list $ S.el (S.sym "else") >>> S.rest (S.sexpIso @Instr))
instance SexpIso Val where
sexpIso = match
$ With (regName >>>)
$ With (S.sexpIso >>>)
$ End
instance SexpIso Imm where
sexpIso = match
$ With (S.sexpIso @Int >>>)
$ With (Gyehoek.Sexp.schemeBool >>>)
$ With (labelName >>>)
$ End
instance SexpIso Block where
sexpIso = with (block >>>)
where
block = S.list $
S.el (S.sym "define")
>>> S.el (S.list $ S.el labelName >>> S.rest regName)
>>> S.rest (S.sexpIso @Instr)
encodeProgram :: Program -> Text
encodeProgram p = p.blocks
& fmap ((^?! _Right) . Gyehoek.Sexp.encodePretty)
& intersperse "\n\n"
& mconcat
regName :: S.SexpGrammar Name
regName = S.sexpIso @Name >>> Gyehoek.Sexp.prismIso
(S.expected "register")
(prefixed @Name "%")
labelName :: S.SexpGrammar Name
labelName = S.sexpIso @Name >>> Gyehoek.Sexp.prismIso
(S.expected "label")
(prefixed @Name "$")
+143
View File
@@ -0,0 +1,143 @@
{-# LANGUAGE ViewPatterns #-}
module Gyehoek.Stack.VM
( VM(..)
, Env(..)
, eval
, trace
, module Gyehoek.Stack.Syntax
, writeObj
) where
import Gyehoek.Stack.Syntax
import Data.List (List)
import GHC.Generics (Generic)
import Control.Lens
import Data.HashMap.Strict (HashMap)
import Data.Text (Text)
import qualified Data.HashMap.Strict as H
import Data.String.Interpolate (i)
import Gyehoek.Scheme.Syntax (Sexp(..))
import Debug.Pretty.Simple (pTraceShowIdForceColor)
import qualified Data.List.NonEmpty as NE
import Data.Functor (($>))
import Data.List (unfoldr)
data VM = MkVM
{ stack :: List Obj
, kstack :: List Name
, code :: List Instr
, registers :: HashMap Name Obj
, stdout :: Text
, result :: Maybe (List Obj)
}
deriving (Show, Generic)
data Env = MkEnv
{ blocks :: HashMap Name Block
}
deriving (Show, Generic)
step :: Env -> VM -> VM
step e vm = case vm ^. #code of
c:cs -> stepI e (vm & #code .~ cs) c
_ -> error "halt never called"
stepI :: Env -> VM -> Instr -> VM
stepI e vm (Push v) = vm & #stack %~ (evalVal e vm v :)
stepI e vm (PushCont k) = vm & #kstack %~ (evalToLabel e vm k :)
stepI e vm (Prim r p) = case evalVal e vm <$> p of
PrimZeroP x -> case x of
ObjImm (ImmInt n) -> ret . ObjImm . ImmBool $ n == 0
_ -> error [i|bad arg to zero?: #{x}|]
PrimAdd x y -> arith_binop (+) x y
PrimMul x y -> arith_binop (*) x y
PrimSub x y -> arith_binop (-) x y
PrimDiv x y -> arith_binop div x y
x -> error [i|unimplemented prim: #{p}|]
where
ret v = vm & #registers . at r ?~ v
arith_binop op (ObjImm (ImmInt x)) (ObjImm (ImmInt y)) =
ret $ ObjImm (ImmInt (op x y))
arith_binop _ x y = error [i|bad arith: #{x}, #{y}|]
stepI e vm (Pop r) = case vm ^. #stack of
[] -> error "empty stack"
(x:xs) -> vm & #registers . at r ?~ x
& #stack .~ xs
stepI e vm (PopCont r) = case vm ^. #kstack of
[] -> error "empty stack"
(x:xs) -> vm & #registers . at r ?~ ObjImm (ImmLabel x)
& #kstack .~ xs
stepI e vm (Call v xs) =
case evalToLabel e vm v of
"halt" -> vm & #result ?~ fmap (evalVal e vm) xs
l -> vm & #code .~ b.code
& #registers .~ fmap (evalVal e vm) (H.fromList $ b.params `zip` xs)
where
b = case e ^. #blocks . at l of
Just x -> x
Nothing -> error [i|undefined label: #{l}|]
stepI e vm (If c t f) =
case evalVal e vm c of
ObjImm (ImmBool False) -> vm & #code .~ f
_ -> vm & #code .~ t
stepI e vm ins = error [i|unimplemented instruction: #{ins}|]
evalToLabel e vm v =
case evalVal e vm v of
ObjImm (ImmLabel x) -> x
x -> error [i|not a label: #{x}|]
evalVal :: Env -> VM -> Val -> Obj
evalVal e vm = \case
ValImm imm -> ObjImm imm
ValReg r -> case vm ^. #registers . at r of
Just x -> x
Nothing -> error [i|undefined register: #{r}|]
initialVM :: VM
initialVM = MkVM
{ stack = []
, kstack = ["halt"]
, code = [Call (ValImm $ ImmLabel "main") []]
, registers = mempty
, stdout = ""
, result = Nothing
}
initialEnv :: Program -> Env
initialEnv (MkProgram bs) = MkEnv
{ blocks = bs & foldMap \b -> H.singleton b.label b
}
loop :: (a -> Either b a) -> a -> b
loop f a = case f a of
Right a' -> loop f a'
Left b -> b
eval :: Program -> List Obj
eval p = initialVM & loop \vm -> case vm ^. #result of
Nothing -> Right $ step (initialEnv p) vm
Just rs -> Left rs
trace :: Program -> List VM
trace p = initialVM & unfoldr \vm ->
case vm.result of
Just _ -> Nothing
Nothing -> Just (vm, step e vm)
where e = initialEnv p
writeObj :: Obj -> Text
writeObj (ObjImm im) = case im of
ImmInt n -> [i|#{n}|]
ImmBool True -> "#t"
ImmBool False -> "#f"
ImmLabel l -> "#<procedure>"
+21 -1
View File
@@ -1 +1,21 @@
(values 1 2)
(define (-& x y k) (k (- x y)))
(define (zero?& x k) (k (zero? x)))
(define (halt x) x)
(letrec ((even? (lambda (n ktail)
(zero?& n
(lambda (x1)
(if x1
#t
(-& n 1
(lambda (x2)
(odd? x2 ktail))))))))
(odd? (lambda (n ktail)
(zero?& n
(lambda (x1)
(if x1
#f
(-& n 1
(lambda (x2)
(even? x2 ktail)))))))))
(even? 12 halt))
+68 -191
View File
@@ -22,38 +22,60 @@
$cont-stack
(ref $cont-stack-type)
(array.new_default $cont-stack-type (i32.const 128)))
(type $arg-array-type (array (mut (ref null eq))))
(global
$arg-array
(ref $arg-array-type)
(array.new_default $arg-array-type (i32.const 32)))
(global $arg0 (mut (ref null eq)) (ref.null eq))
(global $arg1 (mut (ref null eq)) (ref.null eq))
(global $arg2 (mut (ref null eq)) (ref.null eq))
(global $arg3 (mut (ref null eq)) (ref.null eq))
(global $arg4 (mut (ref null eq)) (ref.null eq))
(global $arg5 (mut (ref null eq)) (ref.null eq))
(global $arg6 (mut (ref null eq)) (ref.null eq))
(global $arg7 (mut (ref null eq)) (ref.null eq))
(global $arg8 (mut (ref null eq)) (ref.null eq))
(global $arg9 (mut (ref null eq)) (ref.null eq))
(global $arg10 (mut (ref null eq)) (ref.null eq))
(global $arg11 (mut (ref null eq)) (ref.null eq))
(global $arg12 (mut (ref null eq)) (ref.null eq))
(global $arg13 (mut (ref null eq)) (ref.null eq))
(global $arg14 (mut (ref null eq)) (ref.null eq))
(global $arg15 (mut (ref null eq)) (ref.null eq))
(global $result (mut (ref null eq)) (ref.null eq))
(func
$halt
(param i32)
(@gyehoek "pop argument")
(global.get $arg-array)
(i32.const 0)
(array.get $arg-array-type)
(@gyehoek begin popArg)
(global.get $arg0)
ref.as_non_null
(@gyehoek end popArg)
(global.set $result))
(func
(param i32)
(@gyehoek :origin "(κ (x5) (continue λ-tail1 x5))")
(@gyehoek
:origin
"(λ (x λ-tail1) (prim (* x x) (κ (r2) (continue λ-tail1 r2))))")
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(@gyehoek "pop argument")
(global.get $arg-array)
(i32.const 0)
(array.get $arg-array-type)
ref.as_non_null
(local.set 1)
(@gyehoek :origin "(continue λ-tail1 x5)")
(@gyehoek
:origin
"(prim (* x x) (κ (r2) (continue λ-tail1 r2)))")
(global.get $arg1)
(i31.get_s (ref.cast (ref i31)))
(i32.const 1)
i32.shr_u
(global.get $arg1)
(i31.get_s (ref.cast (ref i31)))
(i32.const 1)
i32.shr_u
i32.mul
(@gyehoek "construct small fixnum")
(i32.const 1)
i32.shl
ref.i31
(global.set $arg2)
(@gyehoek :origin "(continue λ-tail1 r2)")
(@gyehoek "push args")
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 0)
(local.get 4)
(array.set $arg-array-type)
(@gyehoek begin pushArg)
(global.get $arg2)
(global.set $arg0)
(@gyehoek end pushArg)
(@gyehoek "nargs")
(i32.const 1)
(@gyehoek "pop cont stack")
@@ -69,58 +91,26 @@
(elem declare funcref (ref.func 3))
(func
(param i32)
(@gyehoek
:origin
"(κ (x3) (letrec ((r4 (κ (x5) (continue λ-tail1 x5)))) (f x3 r4)))")
(@gyehoek :origin "(κ (x4) (continue halt x4))")
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(@gyehoek "pop argument")
(global.get $arg-array)
(i32.const 0)
(array.get $arg-array-type)
ref.as_non_null
(local.set 1)
(@gyehoek :origin "(f x3 r4)")
(@gyehoek "push cont" :idx 3)
(array.set
$cont-stack-type
(global.get $cont-stack)
(global.get $cont-stack-top)
(ref.func 3))
(global.set
$cont-stack-top
(i32.add (global.get $cont-stack-top) (i32.const 1)))
(@gyehoek :origin "(f x3 r4)")
(@gyehoek "load args")
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 0)
(local.get 3)
(array.set $arg-array-type)
(i32.const 1)
(local.get 1)
(ref.cast (ref $closure))
(struct.get $closure $code)
(return_call_ref $cont-type))
(@gyehoek begin pushArg)
(global.get $arg2)
(global.set $arg0)
(@gyehoek end pushArg)
(return_call $halt (i32.const 1)))
(elem declare funcref (ref.func 4))
(func
$scm-entry
(param i32)
(@gyehoek
:origin
"(λ (f x λ-tail1) (letrec ((r2 (κ (x3) (letrec ((r4 (κ (x5) (continue λ-tail1 x5)))) (f x3 r4))))) (f x r2)))")
"(letrec ((λ-body0 (λ (x λ-tail1) (prim (* x x) (κ (r2) (continue λ-tail1 r2)))))) (letrec ((r3 (κ (x4) (continue halt x4)))) (λ-body0 5 r3)))")
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(@gyehoek "pop argument")
(global.get $arg-array)
(i32.const 0)
(array.get $arg-array-type)
ref.as_non_null
(local.set 1)
(@gyehoek "pop argument")
(global.get $arg-array)
(i32.const 1)
(array.get $arg-array-type)
ref.as_non_null
(local.set 2)
(@gyehoek :origin "(f x r2)")
(ref.func 3)
(struct.new $closure)
(global.set $arg1)
(@gyehoek :origin "(λ-body0 5 r3)")
(@gyehoek "push cont" :idx 4)
(array.set
$cont-stack-type
@@ -130,135 +120,22 @@
(global.set
$cont-stack-top
(i32.add (global.get $cont-stack-top) (i32.const 1)))
(@gyehoek :origin "(f x r2)")
(@gyehoek :origin "(λ-body0 5 r3)")
(@gyehoek "load args")
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 0)
(local.get 2)
(array.set $arg-array-type)
(@gyehoek begin pushArg)
(i32.const 5)
(@gyehoek "construct small fixnum")
(i32.const 1)
(local.get 1)
i32.shl
ref.i31
(global.set $arg0)
(@gyehoek end pushArg)
(i32.const 1)
(global.get $arg1)
(ref.cast (ref $closure))
(struct.get $closure $code)
(return_call_ref $cont-type))
(elem declare funcref (ref.func 5))
(func
(param i32)
(@gyehoek
:origin
"(λ (x λ-tail7) (prim (+ x 4) (κ (r8) (continue λ-tail7 r8))))")
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(@gyehoek "pop argument")
(global.get $arg-array)
(i32.const 0)
(array.get $arg-array-type)
ref.as_non_null
(local.set 1)
(@gyehoek
:origin
"(prim (+ x 4) (κ (r8) (continue λ-tail7 r8)))")
(local.get 1)
(i31.get_s (ref.cast (ref i31)))
(i32.const 1)
i32.shr_u
(i32.const 4)
(@gyehoek "construct small fixnum")
(i32.const 1)
i32.shl
ref.i31
(i31.get_s (ref.cast (ref i31)))
(i32.const 1)
i32.shr_u
i32.add
(@gyehoek "construct small fixnum")
(i32.const 1)
i32.shl
ref.i31
(local.set 2)
(@gyehoek :origin "(continue λ-tail7 r8)")
(@gyehoek "push args")
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 0)
(local.get 2)
(array.set $arg-array-type)
(@gyehoek "nargs")
(i32.const 1)
(@gyehoek "pop cont stack")
(global.get $cont-stack-top)
(i32.const 1)
i32.sub
(global.set $cont-stack-top)
(global.get $cont-stack)
(global.get $cont-stack-top)
(array.get $cont-stack-type)
ref.as_non_null
(return_call_ref $cont-type))
(elem declare funcref (ref.func 6))
(func
(param i32)
(@gyehoek :origin "(κ (x10) (continue halt x10))")
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(@gyehoek "pop argument")
(global.get $arg-array)
(i32.const 0)
(array.get $arg-array-type)
ref.as_non_null
(local.set 1)
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 0)
(local.get 3)
(array.set $arg-array-type)
(return_call $halt (i32.const 1)))
(elem declare funcref (ref.func 7))
(func
$scm-entry
(param i32)
(@gyehoek
:origin
"(letrec ((λ-body0 (λ (f x λ-tail1) (letrec ((r2 (κ (x3) (letrec ((r4 (κ (x5) (continue λ-tail1 x5)))) (f x3 r4))))) (f x r2))))) (letrec ((λ-body6 (λ (x λ-tail7) (prim (+ x 4) (κ (r8) (continue λ-tail7 r8)))))) (letrec ((r9 (κ (x10) (continue halt x10)))) (λ-body0 λ-body6 9 r9))))")
(local (ref eq) (ref eq) (ref eq) (ref eq) (ref eq))
(i32.const 0)
(ref.func 5)
(struct.new $closure)
(local.set 1)
(i32.const 0)
(ref.func 6)
(struct.new $closure)
(local.set 2)
(@gyehoek :origin "(λ-body0 λ-body6 9 r9)")
(@gyehoek "push cont" :idx 7)
(array.set
$cont-stack-type
(global.get $cont-stack)
(global.get $cont-stack-top)
(ref.func 7))
(global.set
$cont-stack-top
(i32.add (global.get $cont-stack-top) (i32.const 1)))
(@gyehoek :origin "(λ-body0 λ-body6 9 r9)")
(@gyehoek "load args")
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 0)
(local.get 2)
(array.set $arg-array-type)
(@gyehoek "push argument")
(global.get $arg-array)
(i32.const 1)
(i32.const 9)
(@gyehoek "construct small fixnum")
(i32.const 1)
i32.shl
ref.i31
(array.set $arg-array-type)
(i32.const 1)
(local.get 1)
(ref.cast (ref $closure))
(struct.get $closure $code)
(return_call_ref $cont-type))
(return_call_ref $cont-type)
(@gyehoek todo (f' (global.get $arg1)) (ktail 1)))
(func
(export "main")
(call $scm-entry (i32.const 0))
+87
View File
@@ -0,0 +1,87 @@
module Gyehoek.Test.CPS.Stackify (root) where
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit
import qualified Gyehoek.CPS.Stackify as Sut
import Gyehoek.Stack.VM as Stk
import Data.List (List)
import Gyehoek.CPS.Syntax (cps)
import Gyehoek.GenSym (runGenSym)
import Effectful
root :: IO TestTree
root = pure . testGroup "stackify" $
[ trivialReturn
, tailCall
, prim
, condition
, procedure
]
evalsTo :: List Obj -> Sut.Exp -> Assertion
evalsTo rs e =
Stk.eval e' @?= rs
where e' = runPureEff . runGenSym $ Sut.stackifyExp "main" e
trivialReturn = testGroup "trivial return"
[ testCase "return int" do
evalsTo [ObjImm (ImmInt 4)]
[cps|(continue halt 4)|]
, testCase "return bool" do
evalsTo [ObjImm (ImmBool True)]
[cps|(continue halt #t)|]
evalsTo [ObjImm (ImmBool False)]
[cps|(continue halt #f)|]
]
tailCall = testGroup "tail call"
[ testCase "square" do
evalsTo [ObjImm (ImmInt 16)]
[cps|(letrec ((square (λ (x ktail)
(prim (* x x)
(κ (x0) (continue ktail x0))))))
(square 4 halt))|]
]
prim = testGroup "prim"
[ testCase "multiply" do
evalsTo [ObjImm (ImmInt 20)]
[cps|(prim (* 4 5)
(κ (x) (continue halt x)))|]
, testCase "add" do
evalsTo [ObjImm (ImmInt 9)]
[cps|(prim (+ 4 5)
(κ (x) (continue halt x)))|]
-- , testGroup "call/cc"
-- [ testCase "trivial" do
-- evalsTo [ObjImm (ImmInt 123)]
-- [cps|(letrec ((f (λ (cc ktail) (continue cc 123))))
-- (prim (call/cc f)))|]
-- ]
]
condition = testCase "if" do
evalsTo [ObjImm (ImmInt 123)]
[cps|(if #t (continue halt 123) (continue halt 456))|]
evalsTo [ObjImm (ImmInt 456)]
[cps|(if #f (continue halt 123) (continue halt 456))|]
procedure = testGroup "procedure"
[ testCase "factorial" do
evalsTo [ObjImm (ImmInt 720)]
[cps|(letrec ((fac (λ (n ktail)
(prim (zero? n)
(κ (x0)
(if x0
(continue ktail 1)
(prim (- n 1)
(κ (x1)
(letrec ((fac-k0
(κ (x2)
(prim (* n x2)
(κ (x3)
(continue ktail x3))))))
(fac x1 fac-k0))))))))))
(fac 6 halt))|]
]
+11 -12
View File
@@ -3,12 +3,9 @@ module Gyehoek.Test.CPS.Syntax (root) where
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit
import Language.Sexp.Located qualified as SL
import Language.SexpGrammar ()
import Gyehoek.CPS.Syntax (cps)
import Gyehoek.CPS.Syntax qualified as Sut
import Data.Function (on)
import Gyehoek.Test.Sexp (equivto)
root :: IO TestTree
@@ -18,15 +15,17 @@ root = pure . testGroup "cps syntax" $
]
freeTree :: TestTree
freeTree = testCase "free" do
Sut.free [cps|
(letrec ((x (lambda (r k1) (continue k1 y)))
(y (lambda (r k2) (continue k2 x))))
(continue x y k3))|] @=? ["k3"]
Sut.free' [cps|
(letrec ((x (lambda (r k1) (continue k1 y)))
(y (lambda (r k2) (continue k2 x))))
(continue x y k3))|] @=? ["k3"]
freeTree = testGroup "free"
[ testCase "lambda" do
Sut.free' @Sut.Lambda [cps|
(lambda (x y z k1) (continue k1 x a b c y))
|] @=? ["a","b","c"]
, testCase "exp" do
Sut.free' @Sut.Exp [cps|
(letrec ((x (lambda (r k1) (continue k1 y)))
(y (lambda (r k2) (continue k2 x))))
(continue x y k3))|] @=? ["k3"]
]
qqTree :: TestTree
qqTree = testGroup "parser"
+44 -8
View File
@@ -10,35 +10,71 @@ import System.Directory
import Data.Function
import System.Environment.Blank (getEnvDefault)
import qualified System.Process.Text as PT
import Control.Exception (catch, Exception (displayException))
import Gyehoek.Stack.VM (writeObj)
import Data.Text qualified as T
import System.Exit (ExitCode(..))
import Test.Tasty.ExpectedFailure (expectFail, ignoreTestBecause)
import Control.DeepSeq (($!!))
disabled :: List String
disabled =
brokenWasmTests :: List String
brokenWasmTests =
[
]
brokenStackifyTests :: List String
brokenStackifyTests =
[ "adder"
, "let-fn"
, "callcc-nested1" -- requires closure-conversion
]
root :: IO TestTree
root = do
all_cases <- listDirectory "golden"
let tests = all_cases
& filter (`notElem` disabled)
& fmap ("golden"</>)
testGroup "golden" <$> sequenceA
[ executionTests tests
[ ignoreTestBecause "wasm codegen is on the backburner"
<$> wasmTests tests
, stackifyTests tests
]
executionTests :: List FilePath -> IO TestTree
executionTests files = do
maybeBroken name broken = applyWhen (name `elem` broken) expectFail
wasmTests :: List FilePath -> IO TestTree
wasmTests files = do
cmd <- getEnvDefault "GYEHOEK_RUNTIME"
"runtime/target/debug/gyehoek-runtime"
pure $ testGroup "execution" $ files <&> \test ->
pure $ testGroup "wasm execution" $ files <&> \test ->
let testname = takeFileName test
scmfile = test </> "source.scm"
resultfile = test </> "exec"
action = do
t <- Driver.lower_e2e scmfile
PT.readProcessWithExitCode cmd ["-"] t
in goldenVsAction
in maybeBroken testname brokenWasmTests $
goldenVsAction
testname
resultfile
action
printProcResult
stackifyTests :: List FilePath -> IO TestTree
stackifyTests files = do
pure $ testGroup "stackified execution" $ files <&> \test ->
let testname = takeFileName test
scmfile = test </> "source.scm"
resultfile = test </> "exec"
action =
catch @SomeException
(do rs <- Driver.eval_e2e scmfile
pure $!! ( ExitSuccess
, T.unwords . fmap writeObj $ rs
, "" ))
\e -> pure (ExitFailure 1, "", T.pack $ displayException e)
in maybeBroken testname brokenStackifyTests $
goldenVsAction
testname
resultfile
action
+117
View File
@@ -0,0 +1,117 @@
module Gyehoek.Test.Stack.VM (root) where
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit
import Gyehoek.Stack.Syntax
import Gyehoek.Stack.VM qualified as Sut
import Data.List (List)
root :: IO TestTree
root = pure . testGroup "stack machine" $
[ lit_int
, procedure
, prims
]
evalsTo :: List Obj -> List Block -> Assertion
evalsTo rs bs = Sut.eval (MkProgram bs) @?= rs
lit_int = testCase "lit int" do
evalsTo [ObjImm (ImmInt 3)]
[ MkBlock "main" []
[ PopCont "ktail"
, Call (ValReg "ktail") [ValImm (ImmInt 3)]
]
]
procedure = testGroup "procedure"
[ testCase "return constant" do
evalsTo [ObjImm (ImmInt 123)]
[ MkBlock "main" []
[ Call (ValLabel "silly") []
]
, MkBlock "silly" []
[ PopCont "ktail"
, Call (ValReg "ktail") [ValImm (ImmInt 123)]
]
]
, testCase "identity function" do
evalsTo [ObjImm (ImmInt 45)]
[ MkBlock "main" []
[ Call (ValLabel "id") [ValImm (ImmInt 45)]
]
, MkBlock "id" ["x"]
[ PopCont "ktail"
, Call (ValReg "ktail") [ValReg "x"]
]
]
, testCase "square" do
evalsTo [ObjImm (ImmInt 16)]
[ MkBlock "main" []
[ Call (ValLabel "square") [ValImm (ImmInt 4)]
]
, MkBlock "square" ["x"]
[ PopCont "ktail"
, Prim "x2" $ PrimMul (ValReg "x") (ValReg "x")
, Call (ValReg "ktail") [ValReg "x2"]
]
]
, testCase "factorial" do
let fac n =
[ MkBlock "fac" ["n"]
[ Prim "x0" $ PrimZeroP (ValReg "n")
, If (ValReg "x0")
[ PopCont "ktail"
, Call (ValReg "ktail") [ValImm (ImmInt 1)]
]
[ Push (ValReg "n")
, Prim "x1" $ PrimSub (ValReg "n") (ValImm (ImmInt 1))
, PushCont (ValLabel "fac-k0")
, Call (ValLabel "fac") [ValReg "x1"]
]
]
, MkBlock "fac-k0" ["x2"]
[ Pop "n"
, Prim "x3" $ PrimMul (ValReg "x2") (ValReg "n")
, PopCont "ktail"
, Call (ValReg "ktail") [ValReg "x3"]
]
, MkBlock "main" []
[ Call (ValLabel "fac") [ValImm (ImmInt n)]
]
]
evalsTo [ObjImm (ImmInt 1)] $ fac 0
evalsTo [ObjImm (ImmInt 720)] $ fac 6
]
prims = testGroup "prims"
[ arith
, testCase "zero?" do
trivialPrimTest [ObjImm (ImmBool True)] $
PrimZeroP $ ValImm $ ImmInt 0
trivialPrimTest [ObjImm (ImmBool False)] $
PrimZeroP $ ValImm $ ImmInt 12
]
trivialPrimTest rs p =
evalsTo rs
[ MkBlock "main" []
[ PopCont "ktail"
, Prim "x1" p
, Call (ValReg "ktail") [ValReg "x1"]
]
]
arith = testGroup "arith"
[ testCase "multipy" do
trivialPrimTest [ObjImm (ImmInt 12)]
(PrimMul (ValImm $ ImmInt 3) (ValImm $ ImmInt 4))
, testCase "subtract" do
trivialPrimTest [ObjImm (ImmInt 14)]
(PrimSub (ValImm $ ImmInt 20) (ValImm $ ImmInt 6))
]
+4
View File
@@ -5,6 +5,8 @@ import Test.Tasty.Silver.Interactive (defaultMain)
import qualified Gyehoek.Test.Golden
import qualified Gyehoek.Test.Sexp
import qualified Gyehoek.Test.CPS.Syntax
import qualified Gyehoek.Test.Stack.VM
import qualified Gyehoek.Test.CPS.Stackify
main :: IO ()
@@ -15,5 +17,7 @@ root = testGroup "test" <$> sequenceA
[ Gyehoek.Test.Golden.root
, Gyehoek.Test.Sexp.root
, Gyehoek.Test.CPS.Syntax.root
, Gyehoek.Test.Stack.VM.root
, Gyehoek.Test.CPS.Stackify.root
]