{"id":13467121,"url":"https://github.com/quchen/stgi","last_synced_at":"2025-04-12T16:37:40.154Z","repository":{"id":41553707,"uuid":"55171754","full_name":"quchen/stgi","owner":"quchen","description":"A user-centric visual STG implementation to help understand GHC/Haskell's execution model.","archived":false,"fork":false,"pushed_at":"2021-06-08T08:59:12.000Z","size":984,"stargazers_count":526,"open_issues_count":6,"forks_count":26,"subscribers_count":24,"default_branch":"master","last_synced_at":"2024-10-13T23:48:33.524Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/quchen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-03-31T17:57:55.000Z","updated_at":"2024-09-03T11:45:14.000Z","dependencies_parsed_at":"2022-09-21T12:50:48.526Z","dependency_job_id":null,"html_url":"https://github.com/quchen/stgi","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quchen%2Fstgi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quchen%2Fstgi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quchen%2Fstgi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quchen%2Fstgi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/quchen","download_url":"https://codeload.github.com/quchen/stgi/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248597652,"owners_count":21130916,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T15:00:53.299Z","updated_at":"2025-04-12T16:37:40.129Z","avatar_url":"https://github.com/quchen.png","language":"Haskell","funding_links":[],"categories":["Haskell"],"sub_categories":[],"readme":"STGi - STG interpreter\n======================\n\nSTGi is a visual STG implementation to help understand Haskell's execution\nmodel.\n\nIt does this by guiding through the running of a program, showing stack and\nheap, and giving explanations of the applied transition rules. Here is what an\nintermediate state looks like:\n\n![](screenshot.png)\n\n[![Release](https://img.shields.io/github/release/quchen/stgi.svg?style=flat-square\u0026label=Latest%20release)](https://github.com/quchen/stgi/releases)\n[![Hackage](https://img.shields.io/hackage/v/stgi.svg?style=flat-square)](http://hackage.haskell.org/packages/search?terms=stgi)\n[![BSD3](https://img.shields.io/badge/License-BSD-blue.svg?style=flat-square)](https://en.wikipedia.org/wiki/BSD_License)\n\n\u003c!--\n[![Master build](https://img.shields.io/travis/quchen/stgi/master.svg?style=flat-square\u0026label=Master%20build)](https://travis-ci.org/quchen/stgi)\n--\u003e\n\n\n\nTable of contents\n-----------------\n\n- [Quickstart guide](#quickstart-guide)\n- [About the machine](#about-the-machine)\n- [Useful applications](#useful-applications)\n- [Language introduction](#language-introduction)\n    - [Top-level](#top-level)\n    - [The `main` value, termination](#the-main-value-termination)\n    - [Expressions](#expressions)\n    - [Updates](#updates)\n    - [Pitfalls](#pitfalls)\n    - [Code example](#code-example)\n    - [Marshalling values](#marshalling-values)\n- [Runtime behaviour](#runtime-behaviour)\n    - [Code segment](#code-segment)\n    - [Stack](#stack)\n    - [Heap](#heap)\n    - [Black holes](#black-holes)\n    - [Garbage collection](#garbage-collection)\n    - [Unhelpful error message?](#unhelpful-error-message)\n- [Differences from the 1992 paper](#differences-from-the-1992-paper)\n    - [Grammar](#grammar)\n    - [Evaluation](#evaluation)\n- [GHC's current STG](#ghcs-current-stg)\n\n\nQuickstart guide\n----------------\n\nIf you want to have a quick look at the STG, here is what you need to get going.\nThe program should build with both [`stack`][stack] and [`cabal`][cabal].\n\nThe `app/Main.hs` file is written so you can easily switch out the `prog` value\nfor other `Program`s that contain a `main` definition. The `Stg.ExamplePrograms`\nmodule provides a number of examples that might be worth having a look, and are\na good starting point for modifications or adding your own programs. It's\nprobably easier to read in Haddock format, so go ahead and run\n\n```bash\nstack haddock --open stgi\n```\n\nand have a look at the example programs.\n\nWhen you're happy with your `app/Main.hs`, run\n\n```bash\nstack build --exec \"stgi-exe --colour=true\" | less -R\n```\n\nto get coloured output in `less`. Type `/====` to search for `====`, which\nfinds the top of every new step; use `n` (next step) or `N` (previous step) to\nnavigate through the execution.\n\n\nAbout the machine\n-----------------\n\nThe spineless tagless graph reduction machine, STG for short, is an automaton\nused to map non-strict functional languages onto stock hardware. It was\ndeveloped for, and is heavily used in, [the Haskell compiler GHC][ghc].\n\nThis project implements an interpreter for the STG as it is [described in the\n1992 paper on the subject][stg1992], with the main focus on being nice to a\nhuman user. Things that might be important for an actual compiler backend, such\nas performance or static analysis, are not considered in general, only if it\nhelps the understanding of the STG.\n\nThe idea behind the machine is to represent the program in its abstract syntax\ntree form. However, due to references to other parts of the syntax tree, a\nprogram is a graph, not a tree. By evaluating this graph using a small set of\nrules, it can be systematically reduced to a final value, which will be the\nresult of the program.\n\nThe STG is\n  - **spineless** because the graph is not represented as a single data\n    structure in memory, but as a set of small, individual parts of the graph\n    that reference each other. An important part of the evaluation mechanism is\n    how to follow these references.\n  - **tagless** because all heap values - unevaluated values, functions, already\n    evaluated values - are represented alike on the heap, in form of closures.\n    Tag*ful* would mean these closures have to be annotated with things like\n    type information, or whether they were previously evaluated already.\n  - **graph reducing** because heap objects can be overwritten by simpler values\n    the machine has found out to be equivalent. For example, the computation\n    `1+1` on the heap might be overwritten by a constant `2` once that result\n    has been obtained somewhere.\n\n\nUseful applications\n-------------------\n\nSTGi was started to teach myself about the STG. Not long into the project, I\ndecided to extend it to save others the many detours I had to take to implement\nit. In that sense, it can be a useful tool if you're interested in the\nlower-level properties of a Haskell implementation. I did my best to keep the\ncode readable, and added some decent Haddock/comment coverage. Speaking of\nHaddock: it's an excellent tool to start looking around the project before\ndigging into the source!\n\nThe other benefit is for teaching others: instead (or in addition to!) of\nexplaining certain common Haskell issues on a whiteboard with boxes and arrows,\nyou can share an interactive view of common programs with others. The example\nprograms feature some interesting cases.\n\n1. Does this leak memory? On the stack or the heap?\n2. I heard GHC doesn't have a call stack?!\n3. Why is this value not garbage collected?\n4. Why are lists sometimes not very performant?\n5. How many steps does this small, innocent function take to produce a result?\n\n\nLanguage introduction\n---------------------\n\nThe STG language can be seen as a mostly simplified version of Haskell with a\ncouple of lower level additions. The largest difference is probably that STG is\nan untyped language.\n\nThe syntax will be discussed below. For now, as an appetizer, the familiar\nHaskell code\n\n```haskell\nfoldl' _ acc [] = acc\nfoldl' f acc (y:ys) = case f acc y of\n    !acc' -\u003e foldl' f acc' ys\n\nsum = foldl' add 0\n```\n\ncould be translated to\n\n```haskell\nfoldl' = \\f acc xs -\u003e case xs of\n    Nil -\u003e acc;\n    Cons y ys -\u003e case f acc y of\n        acc' -\u003e foldl' f acc' ys;\n    badList -\u003e Error_foldl' badList;\n\nsum = \\ -\u003e foldl' add zero;\n\nzero = \\ -\u003e Int# 0#\n```\n\n\n### Top-level\n\nAn STG program consists of a set of bindings, each of the form\n\n```haskell\nname = \\(\u003cfree vars\u003e) \u003cbound vars\u003e -\u003e \u003cexpression body\u003e\n```\n\nThe right-hand side is called a *lambda form*, and is closely related to the\nusual lambda from Haskell.\n\n  - Bound variables are the lambda parameters just like in Haskell.\n  - Free variables are the variables used in the `body` that are not bound or\n    global. This means that variables from the parent scope are not\n    automatically in scope, but you can get them into scope by adding them to\n    the free variables list.\n\n### The `main` value, termination\n\nIn the default configuration, program execution starts by moving the definitions\ngiven in the source code onto the heap, and then evaluating the `main` value. It\nwill continue to run until there is no rule applicable to the current state. Due\nto the lazy IO implementation, you can load indefinitely running programs in\nyour pager application and step as long forward as you want.\n\n### Expressions\n\nExpressions can, in general, be one of a couple of alternatives.\n\n  - **Letrec**\n\n    ```haskell\n    letrec \u003c...bindings...\u003e in \u003cexpression\u003e\n    ```\n\n    Introduce local definitions, just like Haskell's `let`.\n\n  - **Let**\n\n    ```haskell\n    let \u003c...bindings...\u003e in \u003cexpression\u003e\n    ```\n\n    Like `letrec`, but the bindings cannot refer to each other (or themselves).\n    In other words, `let` is non-recursive.\n\n  - **Case**\n\n    ```haskell\n    case \u003cexpression\u003e of \u003calts\u003e\n    ```\n\n    Evaluate the `\u003cexpression\u003e` (called scrutinee) to WHNF and continue\n    evaluating the matching alternative. Note that the WHNF part makes case\n    strict, and indeed it is the *only* construct that does evaluation.\n\n    The `\u003calts\u003e` are semicolon-separated list of alternatives of the form\n\n    ```haskell\n    Constructor \u003cargs\u003e -\u003e \u003cexpression\u003e -- algebraic\n    1# -\u003e \u003cexpression\u003e                 -- primitive\n    ```\n\n    and can be either all algebraic or all primitive. In case of algebraic\n    alternatives, the constructor's arguments are in scope in the following\n    expression, just like in Haskell's pattern matching.\n\n    Each list of alts must include a default alternative at the end, which can\n    optinally bind a variable.\n\n    ```haskell\n    v -\u003e \u003cexpression\u003e       -- bound default; v is in scope in the expression\n    default -\u003e \u003cexpression\u003e -- unbound default\n    ```\n\n  - **Function application**\n\n    ```haskell\n    function \u003cargs\u003e\n    ```\n\n    Like Haskell's function application. The `\u003cargs\u003e` are primitive values or\n    variables.\n\n  - **Primitive application**\n\n    ```haskell\n    primop# \u003carg1\u003e \u003carg2\u003e\n    ```\n\n    Primitive operation on unboxed integers.\n\n    The following operations are supported:\n\n      - Arithmetic\n        - `+#`: addition\n        - `-#`: subtraction\n        - `*#`: multiplication\n        - `/#`: integer division (truncated towards -∞)\n        - `%#`: modulo (truncated towards -∞)\n      - Boolean, returning `1#` for truth and `0#` for falsehood:\n        `\u003c#`, `\u003c=#`, `==#`, `/=#`, `\u003e=#`, `\u003e#`\n\n  - **Constructor application**\n\n    ```haskell\n    Constructor \u003cargs\u003e\n    ```\n\n    An algebraic data constructor applied to a number of arguments, just like\n    function application. Note that constructors always have to be saturated\n    (not partially applied); to get a partially applied constructor, wrap it in\n    a lambda form that fills in the missing arguments with parameters.\n\n  - **Primitive literal**\n\n    An integer postfixed with `#`, like `123#`.\n\nFor example, Haskell's `maybe` function could be implemented in STG like this:\n\n```haskell\nmaybe = \\nothing just x -\u003e case x of\n    Just j   -\u003e just j;\n    Nothing  -\u003e nothing;\n    badMaybe -\u003e Error_badMaybe badMaybe\n```\n\nSome lambda expressions can only contain certain sub-elements; these special\ncases are detailed in the sections below. To foreshadow these issues:\n\n- Lambda forms always have lifted (not primitive) type\n- Lambda forms with non-empty argument lists and standard constructors are never\n  updatable\n\n\n### Updates\n\nA lambda form can optionally use a double arrow `=\u003e`, instead of a normal arrow `-\u003e`.\nThis tells the machine to update the lambda form's value in memory once it has\nbeen calculated, so the computation does not have to be repeated should the\nvalue be required again. This is the mechanism that is key to the lazy\nevaluation model the STG implements. For example, evaluating `main` in\n\n```haskell\nadd2 = … -- \u003cadd two boxed ints\u003e\none = \\ -\u003e Int# 1#;\ntwo = \\ -\u003e Int# 2#;\nmain = \\ =\u003e add2 one two\n```\n\nwould, once the computation returns, overwrite `main` (modulo technical\ndetails) with\n\n```haskell\nmain = \\ -\u003e Int# 3#\n```\n\nA couple of things to keep in mind:\n\n- Closures with non-empty argument lists and constructors are already in WHNF,\n  so they are never updatable.\n- When a value is only entered once, updating it is unnecessary work. Deciding\n  whether a potentially updatable closure should actually be updatable is what\n  the *update analysis* would do in a compiler when translating into the STG.\n\n\n\n### Pitfalls\n\n- Semicolons are an annoyance that allows the grammar to be simpler. This\n  tradeoff was chosen to keep the project's code simpler, but this may change\n  in the future.\n\n  For now, the semicolon rule is that **bindings and alternatives are\n  semicolon-separated**.\n\n- Lambda forms stand for deferred computations, and as such cannot have\n  primitive type, which are always in normal form. To handle primitive types,\n  you'll have to box them like in\n\n  ```haskell\n  three = \\ -\u003e Int# 3#\n  ```\n\n  Writing\n\n  ```haskell\n  three' = \\ -\u003e 3#\n  ```\n\n  is invalid, and the machine would halt in an error state. You'll notice that\n  the unboxing-boxing business is quite laborious, and this is precisely the\n  reason unboxed values alone are so fast in GHC.\n\n- Function application cannot be nested, since function arguments are primitives\n  or variables. Haskell's `map f (map g xs)` would be written\n\n  ```haskell\n  let map_g_xs = \\ -\u003e map g xs\n  in map f map_g_xs\n  ```\n\n  assuming all variables are in global scope. This means that nesting functions\n  in Haskell results in a heap allocation via `let`.\n\n- Free variable values have to be explicitly given to closures. Function\n  composition could be implemented like\n\n  ```haskell\n  compose = \\f g x -\u003e let gx = \\(g x) -\u003e g x\n                      in f gx\n  ```\n\n  Forgetting to hand `g` and `x` to the `gx` lambda form would mean that in the\n  `g x` call neither of them was in scope, and the machine would halt with\n  a \"variable not in scope\" error.\n\n  This applies even for recursive functions, which have to be given to\n  their own list of free variables, like in `rep` in the following example:\n\n  ```haskell\n  replicate = \\x -\u003e let rep = \\(rep x) -\u003e Cons x rep\n                    in rep\n  ```\n\n\n### Code example\n\nThe 1992 paper gives two implementations of the `map` function in section 4.1.\nThe first one is the STG version of\n\n```haskell\nmap f [] = []\nmap f (y:ys) = f y : map f ys\n```\n\nwhich, in this STG implementation, would be written\n\n```haskell\nmap = \\f xs -\u003e case xs of\n    Nil -\u003e Nil;\n    Cons y ys -\u003e let fy = \\(f y) =\u003e f y;\n                     mfy = \\(f ys) =\u003e map f ys\n                 in Cons fy mfy;\n    badList -\u003e Error_map badList\n```\n\nFor comparison, the paper's version is\n\n```haskell\nmap = {} \\n {f,xs} -\u003e case xs of\n    Nil {} -\u003e Nil {}\n    Cons {y,ys} -\u003e let fy = {f,y} \\u {} -\u003e f {y}\n                       mfy = {f,ys} \\u {} -\u003e map {f,ys}\n                   in Cons {fy,mfy}\n    badList -\u003e Error_map {badList}\n```\n\nYou can find lots of further examples of standard Haskell functions implemented\nby hand in STG in the `Prelude` modules. Combined with the above explanations,\nthis is all you should need to get started.\n\n\n\n### Marshalling values\n\nThe `Stg.Marshal` module provides functions to inject Haskell values into the\nSTG (`toStg`), and extract them from a machine state again (`fromStg`). These\nfunctions are tremendously useful in practice, make use of them! After chasing a\nlist value on the heap manually you'll know the value of `fromStg`, and in order\nto get data structures into the STG you have to write a lot of code, and be\ncareful doing it at that. Keep in mind that `fromStg` requires the value to be\nin normal form, or extraction will fail.\n\n\n\nRuntime behaviour\n-----------------\n\nThe following steps are an overview of the evaluation rules. Running the STG in\nverbose mode (`-v2`) will provide a more detailed description of what happened\neach particular step.\n\n### Code segment\n\nThe code segment is the current instruction the machine evaluates.\n\n- **Eval** evaluates expressions.\n    - **Function application** pushes the function's arguments on the stack\n      and **Enter**s the address of the function.\n    - **Constructor applications** simply transition into the\n      **ReturnCon** state when evaluated.\n    - Similarly, **primitive ints** transition into the **ReturnInt** state.\n    - **Case** pushes a return frame, and proceeds evaluating the scrutinee.\n    - **Let(rec)** allocates heap closures, and extends the local environment\n      with the new bindings.\n- **Enter** evaluates memory addresses by looking up the value at a memory\n  address on the heap, and evaluating its body.\n    - If the closure entered is updatable, push an update frame so it can later\n      be overwritten with the value it evaluates to.\n    - If the closure takes any arguments, supply it with values taken from\n      argument frames.\n- **ReturnCon** instructs the machine to branch depending on which constructor\n  is present, by popping a return frame.\n- **ReturnInt** does the same, but for primitive values.\n\n### Stack\n\nThe stack has three different kinds of frames.\n\n- **Argument** frames store pending function arguments. They are pushed when\n  evaluating a function applied to arguments, and popped when entering a closure\n  that has a non-empty argument list.\n- **Return** frames are pushed when evaluating a `case` expression, in order to\n  know where to continue once the scrutinee has been evaluated. They are popped\n  when evaluating constructors or primitive values.\n- **Update** frames block access to argument and return frames. If an evaluation\n  step needs to pop one of them but there is an update frame in the way, it can\n  get rid the update frame by overriding the memory address pointed to by it\n  with the current value being evaluated, and retrying the evaluation now that\n  the update frame is gone. This mechanism is what enables lazy evaluation in\n  the STG.\n\n### Heap\n\nThe heap is a mapping from memory addresses to heap objects, which can be\nclosures or black holes (see below). Heap entries are allocated by `let(rec)`,\nand deallocated by garbage collection.\n\nAs a visual guide to the user, closures are annotated with `Fun` (takes\narguments), `Con` (data constructors), and `Thunk` (suspended computations).\n\n\n### Black holes\n\nThe heap does not only contain closures, but also black holes. Black holes are\nannotated with the step in which they were created; this annotation is purely\nfor display purposes, and not used by the machine.\n\nAt runtime, when an updatable closure is entered (evaluated), it is overwritten\nby a black hole. Black holes do not only provide better overview over what\nthunk is currently evaluated, but have two useful technical benefits:\n\n1. Memory mentioned only in the closure is now ready to be collected,\n   avoiding certain space leaks. [The 1992 paper][stg1992] gives the following\n   example in section 9.3.3:\n\n   ```haskell\n   list = \\(x) =\u003e \u003clong list\u003e\n   l = \\(list) =\u003e last list\n   ```\n\n   When entering `l` without black holes, the entire `list` is kept in memory\n   until `last` is done. On the other hand, overwriting `l` with a black hole\n   upon entering deletes the `last` pointer from it, and `last` can run, and be\n   garbage collected, incrementally.\n\n2. Entering a black hole means a thunk depends on itself, allowing the\n interpreter to catch some non-terminating computations with a useful error\n\n\n### Garbage collection\n\nCurrently, two garbage collection algorithms are implemented:\n\n- **Tri-state tracing**: free all unused memory addresses, and does not touch\n  the others. This makes following specific closures on the heap easy.\n- **Two-space copying**: move all used memory addresses to the beginning of the\n  heap, and discard all those that weren't moved. This has the advantage of\n  reordering the heap roughly in the order the closures will be accessed by the\n  program again, but the disadvantage of making things harder to track, since\n  for example the `main` value might appear in several different locations\n  throughout the run of a program.\n\n\n### Unhelpful error message?\n\nThe goal of this project is being useful to human readers. If you find an error\nmessage that is unhelpful or even misleading, please open an issue with a\nminimal example on how to reproduce it!\n\n\n\nDifferences from the 1992 paper\n-------------------------------\n\n### Grammar\n\n- Function application uses no parentheses or commas like in Haskell `f x y z`,\n  not with curly parentheses and commas like in the paper `f {x,y,z}`.\n- Comment syntax like in Haskell\n- Constructors can end with a `#` to allow labelling primitive boxes\n  e.g. with `Int#`.\n- A lambda's head is written `\\(free) bound -\u003e body`, where `free` and\n  `bound` are space-separated variable lists, instead of the paper's\n  `{free} \\n {bound} -\u003e body`, which uses comma-separated lists. The\n  update flag `\\u` is signified using a double arrow `=\u003e` instead of the\n  normal arrow `-\u003e`.\n\n### Evaluation\n\n- The three stacks from the operational semantics given in the paper - argument,\n  return, and update - are unified into a single one, since they run\n  synchronously anyway. This makes the current location in the evaluation much\n  clearer, since the stack is always popped from the top. For example, having a\n  return frame at the top means the program is close to a `case` expression.\n- Although heap closures are all represented alike, they are classified for the\n  user in the visual output:\n    - Constructors are closures with a constructor application body, and\n      only free variables.\n    - Other closures with only free variables are thunks.\n    - Closures with non-empty argument lists are functions.\n\n\n\nGHC's current STG\n-----------------\n\nThe implementation here uses the *push/enter* evaluation model of the STG, which\nis fairly elegant, and was initially thought to also be top in terms of\nperformance. As it turned out, the latter is not the case, and another\nevaluation model called *eval/apply*, which treats (only) function application a\nbit different, is faster in practice.\n\nThis notable revision is documented in [the 2004 paper *How to make a fast\ncurry*][fastcurry]. I don't have plans to support this evaluation model right\nnow, but it's on my list of long-term goals (alongside the current push/enter).\n\n\n\n[cabal]: https://www.haskell.org/cabal/\n[fastcurry]: http://research.microsoft.com/en-us/um/people/simonpj/papers/eval-apply/\n[ghc]: https://www.haskell.org/ghc/\n[stack]: http://haskellstack.org/\n[stg1992]: http://research.microsoft.com/apps/pubs/default.aspx?id=67083\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquchen%2Fstgi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fquchen%2Fstgi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquchen%2Fstgi/lists"}