Skip to content

Commit

Permalink
Merge pull request #22 from well-typed/edsko/circular
Browse files Browse the repository at this point in the history
Circular programs
  • Loading branch information
edsko authored Dec 20, 2023
2 parents ea3af17 + 191fefc commit 452233c
Show file tree
Hide file tree
Showing 31 changed files with 1,088 additions and 423 deletions.
111 changes: 25 additions & 86 deletions .github/workflows/haskell-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
#
# haskell-ci regenerate
#
# For more information, see https://github.com/andreasabel/haskell-ci
# For more information, see https://github.com/haskell-CI/haskell-ci
#
# version: 0.17.20230928
# version: 0.17.20231219
#
# REGENDATA ("0.17.20230928",["github","visualize-cbn.cabal"])
# REGENDATA ("0.17.20231219",["github","visualize-cbn.cabal"])
#
name: Haskell-CI
on:
Expand All @@ -23,24 +23,24 @@ jobs:
timeout-minutes:
60
container:
image: buildpack-deps:focal
image: buildpack-deps:bionic
continue-on-error: ${{ matrix.allow-failure }}
strategy:
matrix:
include:
- compiler: ghc-9.8.0.20230919
- compiler: ghc-9.8.1
compilerKind: ghc
compilerVersion: 9.8.0.20230919
compilerVersion: 9.8.1
setup-method: ghcup
allow-failure: true
allow-failure: false
- compiler: ghc-9.6.3
compilerKind: ghc
compilerVersion: 9.6.3
setup-method: ghcup
allow-failure: false
- compiler: ghc-9.4.7
- compiler: ghc-9.4.8
compilerKind: ghc
compilerVersion: 9.4.7
compilerVersion: 9.4.8
setup-method: ghcup
allow-failure: false
- compiler: ghc-9.2.8
Expand All @@ -58,54 +58,17 @@ jobs:
compilerVersion: 8.10.7
setup-method: ghcup
allow-failure: false
- compiler: ghc-8.8.4
compilerKind: ghc
compilerVersion: 8.8.4
setup-method: hvr-ppa
allow-failure: false
- compiler: ghc-8.6.5
compilerKind: ghc
compilerVersion: 8.6.5
setup-method: hvr-ppa
allow-failure: false
- compiler: ghc-8.4.4
compilerKind: ghc
compilerVersion: 8.4.4
setup-method: hvr-ppa
allow-failure: false
- compiler: ghc-8.2.2
compilerKind: ghc
compilerVersion: 8.2.2
setup-method: hvr-ppa
allow-failure: false
- compiler: ghc-8.0.2
compilerKind: ghc
compilerVersion: 8.0.2
setup-method: hvr-ppa
allow-failure: false
fail-fast: false
steps:
- name: apt
run: |
apt-get update
apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5
if [ "${{ matrix.setup-method }}" = ghcup ]; then
mkdir -p "$HOME/.ghcup/bin"
curl -sL https://downloads.haskell.org/ghcup/0.1.19.5/x86_64-linux-ghcup-0.1.19.5 > "$HOME/.ghcup/bin/ghcup"
chmod a+x "$HOME/.ghcup/bin/ghcup"
"$HOME/.ghcup/bin/ghcup" config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.7.yaml;
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false)
"$HOME/.ghcup/bin/ghcup" install cabal 3.10.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
else
apt-add-repository -y 'ppa:hvr/ghc'
apt-get update
apt-get install -y "$HCNAME"
mkdir -p "$HOME/.ghcup/bin"
curl -sL https://downloads.haskell.org/ghcup/0.1.19.5/x86_64-linux-ghcup-0.1.19.5 > "$HOME/.ghcup/bin/ghcup"
chmod a+x "$HOME/.ghcup/bin/ghcup"
"$HOME/.ghcup/bin/ghcup" config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.7.yaml;
"$HOME/.ghcup/bin/ghcup" install cabal 3.10.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
fi
mkdir -p "$HOME/.ghcup/bin"
curl -sL https://downloads.haskell.org/ghcup/0.1.20.0/x86_64-linux-ghcup-0.1.20.0 > "$HOME/.ghcup/bin/ghcup"
chmod a+x "$HOME/.ghcup/bin/ghcup"
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false)
"$HOME/.ghcup/bin/ghcup" install cabal 3.10.2.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
env:
HCKIND: ${{ matrix.compilerKind }}
HCNAME: ${{ matrix.compiler }}
Expand All @@ -117,27 +80,18 @@ jobs:
echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV"
echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV"
HCDIR=/opt/$HCKIND/$HCVER
if [ "${{ matrix.setup-method }}" = ghcup ]; then
HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER")
HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#')
HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#')
echo "HC=$HC" >> "$GITHUB_ENV"
echo "HCPKG=$HCPKG" >> "$GITHUB_ENV"
echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV"
echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.1.0 -vnormal+nowrap" >> "$GITHUB_ENV"
else
HC=$HCDIR/bin/$HCKIND
echo "HC=$HC" >> "$GITHUB_ENV"
echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV"
echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV"
echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.1.0 -vnormal+nowrap" >> "$GITHUB_ENV"
fi
HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER")
HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#')
HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#')
echo "HC=$HC" >> "$GITHUB_ENV"
echo "HCPKG=$HCPKG" >> "$GITHUB_ENV"
echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV"
echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV"
if [ $((HCNUMVER >= 90800)) -ne 0 ] ; then echo "HEADHACKAGE=true" >> "$GITHUB_ENV" ; else echo "HEADHACKAGE=false" >> "$GITHUB_ENV" ; fi
echo "HEADHACKAGE=false" >> "$GITHUB_ENV"
echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
echo "GHCJSARITH=0" >> "$GITHUB_ENV"
env:
Expand Down Expand Up @@ -166,18 +120,6 @@ jobs:
repository hackage.haskell.org
url: http://hackage.haskell.org/
EOF
if $HEADHACKAGE; then
cat >> $CABAL_CONFIG <<EOF
repository head.hackage.ghc.haskell.org
url: https://ghc.gitlab.haskell.org/head.hackage/
secure: True
root-keys: 7541f32a4ccca4f97aea3b22f5e593ba2c0267546016b992dfadcd2fe944e55d
26021a13b401500c8eb2761ca95c61f2d625bfef951b939a8124ed12ecf07329
f76d08be13e9a61a377a85e2fb63f4c5435d40f8feb3e12eb05905edb8cdea89
key-threshold: 3
active-repositories: hackage.haskell.org, head.hackage.ghc.haskell.org:override
EOF
fi
cat >> $CABAL_CONFIG <<EOF
program-default-options
ghc-options: $GHCJOBS +RTS -M3G -RTS
Expand All @@ -201,7 +143,7 @@ jobs:
chmod a+x $HOME/.cabal/bin/cabal-plan
cabal-plan --version
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
path: source
- name: initial cabal.project for sdist
Expand All @@ -225,13 +167,10 @@ jobs:
touch cabal.project
touch cabal.project.local
echo "packages: ${PKGDIR_visualize_cbn}" >> cabal.project
if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo "package visualize-cbn" >> cabal.project ; fi
if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo " ghc-options: -Werror=missing-methods" >> cabal.project ; fi
echo "package visualize-cbn" >> cabal.project
echo " ghc-options: -Werror=missing-methods" >> cabal.project
cat >> cabal.project <<EOF
EOF
if $HEADHACKAGE; then
echo "allow-newer: $($HCPKG list --simple-output | sed -E 's/([a-zA-Z-]+)-[0-9.]+/*:\1,/g')" >> cabal.project
fi
$HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: $_ installed\n" unless /^(visualize-cbn)$/; }' >> cabal.project.local
cat cabal.project
cat cabal.project.local
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ dist-newstyle/
.stack-work
.cabal.sandbox.config
.envrc
foo.js
14 changes: 13 additions & 1 deletion ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Revision history for visualize-cbn

## 0.2.0 -- 2023-12-20

* Support multiple (mutually recursive) bindings in `let`
* Fix pattern matching on heap-allocated objects (we were losing sharing)
* Support heap inlining
* Support for selectors (`fst`, `snd`)
* Support the selector thunk optimization
* Add `--disable-ansi` command line
* Improve trace summarization
* Add some new primitive functions (`min`, `max`, `succ`)
* Add option to hide the prelude only after a specified step

## 0.1.0.2 -- 2019-09-10

* Newer GHC compatibility
Expand All @@ -18,4 +30,4 @@
this avoids moving `e1` to the heap (provided that there aren't multiple
references to `x` from `e2`), clarifying the evaluation.
* Added graph output (contributed by Yiğit Özkavcı).
* Improved heap descriptions (contributed by Tim Rakowski).
* Improved heap descriptions (contributed by Tim Rakowski).
54 changes: 54 additions & 0 deletions examples/circular_hos.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
-- See "Using Circular Programs for Higher-Order Syntax"
-- by Emil Axelsson and Koen Claessen (ICFP 2013)
-- <https://emilaxelsson.github.io/documents/axelsson2013using.pdf>
--
-- See Unfolder episode 17 for more details.
--
-- Suggested execution:
--
-- > cabal run visualize-cbn -- \
-- > --show-trace \
-- > --hide-prelude=1 \
-- > --gc \
-- > --selector-thunk-opt \
-- > --inline-heap \
-- > --hide-inlining \
-- > --hide-gc \
-- > --hide-selector-thunk-opt \
-- > --javascript foo.js \
-- > -i examples/circular_hos.hs
--
-- Annotated execution (as of dc51993):
--
-- 2. As soon as we demand the value of @maxBV body_0@ to determine the
-- variable to be used for the outer-most lambda, this will force the
-- construction of the next term down. This happens recursively, so the
-- entire term is build in memory.
-- 10. This is an instructive subsequence: we will see the evaluation of
-- the simple term @lam (\y -> y)@.
-- 16. At this point this term is fully known: @Lam 1 (Var 1)@.
-- 17. The computation is driven by the computation of the variable to be used
-- for the outermost lambda; we can now continue this computation a little
-- bit, because we now know the @maxBV@ of the subterm @Lam 1 (Var 1)@.
-- 19. We repeat for the second simple term @lam (\z -> z)@.
-- 27. At this point we're almost done: we need to know the @max@BV@ of the
-- subterm @Var n_1@, but there aren't any, so that is just @0@.
-- 33. At this point all bound variables are known, and the new term has been
-- constructed.
maxBV = (\exp ->
case exp of {
Var x -> 0
; App f e -> max (@maxBV f) (@maxBV e)
; Lam n f -> n
}
)

lam = (\f ->
let {
body = f (Var n)
; n = succ (@maxBV body)
}
in seq n (Lam n body)
)

main = @lam (\x -> App (App (@lam (\y -> y)) (@lam (\z -> z))) x)
7 changes: 7 additions & 0 deletions examples/multiple-beta.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
f = (\x -> @g x)
g = (\x -> @h x)
h = (\x -> succ x)

main = @f 1


7 changes: 7 additions & 0 deletions examples/mutual_rec.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- Simple example of two mutually recursive functions
-- f x will return 0 if x is even and 1 if x is odd.
main =
let {
f = (\x -> if eq x 0 then 0 else g (sub x 1))
; g = (\x -> if eq x 0 then 1 else f (sub x 1))
} in f 2
51 changes: 51 additions & 0 deletions examples/repmin.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
-- The classic repMin circular program due to Richard Bird.
-- See Unfolder episode 17 for more details.
--
-- Suggested execution:
--
-- > cabal run visualize-cbn -- \
-- > --show-trace \
-- > --hide-prelude=1 \
-- > --gc \
-- > --selector-thunk-opt \
-- > --inline-heap \
-- > --hide-inlining \
-- > --hide-gc \
-- > --hide-selector-thunk-opt \
-- > --javascript foo.js \
-- > -i examples/repmin.hs
--
-- Annotated execution (as of dc51993):
--
-- 1. One way to think about this circular program is to consider that it
-- first creates a pointer to an int (the new value in the leaves), and
-- then starts building up a tree with all leaves pointing to this int;
-- as it builds the tree, it is also computing the value of this int.
-- 6. We're starting to see the tree take shape here; the top-level structure
-- of the tree is now known.
-- 10. Similarly, we now see the shape of the left subtree.
-- 13. Here we see the first @Leaf@, ponting to @m_1@; part of the computation
-- of @m_1@ is now also known (@mb_7@).
-- 16. The second @Leaf@ is known.
-- 18. The minimum value of the left subtree is known (@mb_4@).
-- 28. At this point the structure of the tree is mostly done. We can
-- finish the value computation.
worker = (\m -> \t ->
case t of {
Leaf x -> Pair x (Leaf m)
; Branch l r ->
let {
resultLeft = @worker m l
; resultRight = @worker m r
; mb = min (fst resultLeft) (fst resultRight)
}
in seq mb (Pair mb (Branch (snd resultLeft) (snd resultRight)))
}
)

repMin = (\t ->
let result = @worker (fst result) t
in snd result
)

main = @repMin (Branch (Branch (Leaf 1) (Leaf 2)) (Branch (Leaf 3) (Leaf 4)))
28 changes: 28 additions & 0 deletions examples/selthunkopt.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-- Demonstration of the need for the selector thunk optimization
-- This is the example from "Fixing some space leaks with a garbage collector".

break = (\xs ->
case xs of {
Nil -> Pair Nil Nil
; Cons x xs' ->
if eq x 0
then Pair Nil xs'
else let b = @break xs'
in Pair (Cons x (fst b)) (snd b)
}
)

-- strict version of concat (makes the example more clear)
concat = (\xs -> \ys ->
case xs of {
Nil -> ys
; Cons x xs' -> let r = @concat xs' ys in seq r (Cons x r)
}
)

surprise = (\xs ->
let b = @break xs
in @concat (fst b) (@concat (Cons 4 (Cons 5 (Cons 6 Nil))) (snd b))
)

main = @surprise (Cons 1 (Cons 2 (Cons 3 (Cons 0 (Cons 7 (Cons 8 (Cons 9 Nil)))))))
Loading

0 comments on commit 452233c

Please sign in to comment.