{"id":15638774,"url":"https://github.com/mratsim/synthesis","last_synced_at":"2025-07-11T23:34:17.735Z","repository":{"id":85263764,"uuid":"227946732","full_name":"mratsim/Synthesis","owner":"mratsim","description":"Synthesis is a compiletime, procedure-based, low-overhead, no-allocation, state-machine generator optimized for communicating processes and threads","archived":false,"fork":false,"pushed_at":"2020-04-21T21:39:36.000Z","size":517,"stargazers_count":93,"open_issues_count":1,"forks_count":5,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-07-01T07:50:38.961Z","etag":null,"topics":["embedded","event-driven","finite-state-machine","fsm","reactive-programming","state-machine"],"latest_commit_sha":null,"homepage":"","language":"Nim","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/mratsim.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHEv2","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-12-14T00:57:45.000Z","updated_at":"2025-05-01T09:53:55.000Z","dependencies_parsed_at":"2023-05-25T02:00:35.197Z","dependency_job_id":null,"html_url":"https://github.com/mratsim/Synthesis","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/mratsim/Synthesis","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mratsim%2FSynthesis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mratsim%2FSynthesis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mratsim%2FSynthesis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mratsim%2FSynthesis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mratsim","download_url":"https://codeload.github.com/mratsim/Synthesis/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mratsim%2FSynthesis/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264913743,"owners_count":23682699,"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":["embedded","event-driven","finite-state-machine","fsm","reactive-programming","state-machine"],"created_at":"2024-10-03T11:23:08.050Z","updated_at":"2025-07-11T23:34:17.435Z","avatar_url":"https://github.com/mratsim.png","language":"Nim","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Synthesis\n[![Build Status: Travis](https://img.shields.io/travis/com/mratsim/Synthesis?label=Travis%20%28Linux%2FMac%20-%20x86_64%2FARM64%29)](https://travis-ci.com/mratsim/Synthesis)\n[![Build Status: Azure](https://img.shields.io/azure-devops/build/numforge/e96b84dd-e587-47d1-aea3-64cb49b50ca2/1?label=Azure%20%28C%2FC%2B%2B%2C%20Linux%2064-bit%2C%20Windows%2032-bit%2F64-bit%2C%20MacOS%2064-bit%29)](https://dev.azure.com/numforge/Synthesis/_build/latest?definitionId=1\u0026branchName=master)\n\n[![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg)\n\n## Overview\n\nThis package exports a set of macros to synthesize static procedure-based automata from\na declarative description of states, triggers and transitions\nwith all states, triggers and transitions known at compile-time.\n\nIt is fast, composable, threadsafe, generates compact code and does not allocate on the heap.\n\nWithin each states you also have the full power\nof the Nim language instead of being restricted to only operations\nsupported by a custom domain-specific language.\n\nThe generated state machine is a procedure, with parameters of your choosing that access in your states.\nYou can easily call other procedures to build nested state machines or\nintroduce a stack of past states to create a pushdown automata (for parsing Brainfuck or JSON for example).\n\nA detailed usage tutorial is available at [examples/water_phase_transitions.nim](). It is executable.\n\nThis is a support library for the [Weave](https://github.com/mratsim/weave) multithreading runtime.\nRequirements for a multithreading runtime makes Synthesis also an excellent fit\nto generate state machines for embedded devices, protocols\nand managing complex event-driven workloads in general.\n\n## Appetizers\n\nHere are 2 simple examples of the usage of Synthesis in production code to implement components of the Weave multithreading runtime.\n\n### Worker state machine\n\n[Source](https://github.com/mratsim/weave/blob/4493b493/weave/work_fsm.nim)\n\nThis is the description of the transitions of a worker thread that ran out of tasks in its own task queue\nand checks if it managed to steal tasks from other threads. (The theft is handled in another state machine.)\n\n```Nim\ntype\n  RecvTaskState = enum\n    RT_CheckChannel\n    RT_FoundTask\n\n  RT_Event = enum\n    RTE_CheckedAllChannels\n    RTE_FoundTask\n    RTE_isWaiting\n```\n\n![worker thread FSA](media/work_fsm.png)\n\n### sync (await) a task that may be spawned on another thread\n\n[Source](https://github.com/mratsim/weave/blob/4493b493/weave/await_fsm.nim)\n\nThis is the description of the transitions of any thread that syncs (awaits) a future that may be handled in another thread.\n\nIn summary, while the awaited task has child tasks still pending in this worker thread, those are processed in priority,\notherwise, it steals tasks from other threads to help them on their workload.\nAs soon as the future is ready, it exits.\n\n```Nim\ntype AwaitState = enum\n  AW_CheckTask\n  AW_OutOfChildTasks\n  AW_Steal\n  AW_SuccessfulTheft\n\ntype AwaitEvent = enum\n  AWE_FutureReady\n  AWE_HasChildTask\n  AWE_ReceivedTask\n```\n\n![sync/await FSA](media/await_fsm.png)\n\n## Table of Contents\n\n- [Synthesis](#synthesis)\n  - [Overview](#overview)\n  - [Appetizers](#appetizers)\n    - [Worker state machine](#worker-state-machine)\n    - [sync (await) a task that may be spawned on another thread](#sync-await-a-task-that-may-be-spawned-on-another-thread)\n  - [Table of Contents](#table-of-contents)\n  - [Commented example: Water phases](#commented-example-water-phases)\n  - [Displaying the state machine](#displaying-the-state-machine)\n  - [Technical constraints](#technical-constraints)\n  - [References](#references)\n\n## Commented example: Water phases\n\nThe example below gives you a short overview of how to build your state machine.\n\nRecipe:\n- A state enum (called `Phase` in the example)\n- An event/trigger/condition enum (called `Event`)\n- Declaring a state machine\n- Declaring prologue, epilogue, initial state, terminal state. SOme are optional\n- Implement your events. Those are boolean tests.\n  Events have visibility on variables declared\n  - in the prologue\n  - in `onEntry`\n  - and the synthesized function parameters (here `tempFeed`).\n    ```Nim\n    synthesize(waterMachine):\n      proc observeWater(tempFeed: var seq[float])\n    ```\n- Implement common setup and teardown on state entry and exit if needed\n- Describe behaviours (i.e. state to state transition):\n  - Transition without condition\n  - Conditional transition due to an event\n  - \"Interrupt\" which is a conditional transition that shortcuts regular control flow\n    and allow handling exceptional cases, for example reaching the end of the `tempFeed` sequence.\n- Synthesize the state machine\n- Run it\n- ...\n- Profit!\n\n```Nim\ntype Phase = enum\n  ## States of your automaton.\n  ## The terminal state does not need to be defined\n  Solid\n  Liquid\n  Gas\n  # Plasma is unused. On the graph display, it will not be reachable from the InitialState.\n  # The graph will also show that transitions out of the Plasma state are undefined via an `unreachable` transition.\n  Plasma\n\ntype Event = enum\n  ## Named events. They will be associated with a boolean expression.\n  Over100\n  Between0and100\n  Below0\n  OutOfWater\n\n# Common configuration\n# -------------------------------------------\n\n# Create a \"waterMachine\" entry.\ndeclareAutomaton(waterMachine, Phase, Event)\n\n# Optionally setup the \"prologue\". Extra state goes there, the variables are visible by all.\nsetPrologue(waterMachine):\n  echo \"Welcome to the Steamy machine version 2000!\\n\"\n  var temp: float64\n\n# Mandatory initial state. This must be one of the valid state of the state enum (\"Phase\" in our case)\nsetInitialState(waterMachine, Liquid)\n\n# Terminal state is mandatory. It's a pseudo state and does not have to be part of the state enum.\nsetTerminalState(waterMachine, Exit)\n\n# Optionally setup the \"epilogue\". Cleaning up what was setup in the prologue goes there.\nsetEpilogue(waterMachine):\n  echo \"Now I need some coffee.\"\n\n# Events\n# -------------------------------------------\n\nimplEvent(waterMachine, OutOfWater):\n  tempFeed.len == 0\n\nimplEvent(waterMachine, Between0and100):\n  0 \u003c temp and temp \u003c 100\n\nimplEvent(waterMachine, Below0):\n  temp \u003c 0\n\nimplEvent(waterMachine, Over100):\n  100 \u003c temp\n\n# `onEntry` and `onExit` hooks\n# -------------------------------------------\n#\n# Those are applied on each state entry, before conditions are checked\n# and on each state exits. The only exceptions are \"interrupt\" behaviours.\n\nonEntry(waterMachine, [Solid, Liquid, Gas]):\n  let oldTemp = temp\n  temp = tempFeed.pop()\n  echo \"Temperature: \", temp\n\n# `behaviors`\n# -------------------------------------------\n#\n# Interrupts are special triggers which ignores onEntry/onExit\n#\n# They allow the normal operations to make assumptions like\n# a container not being empty or a value being available.\n#\n# They are also suitable to handle termination signals.\n\nbehavior(waterMachine):\n  ini: [Solid, Liquid, Gas, Plasma]\n  fin: Exit\n  interrupt: OutOfWater\n  transition:\n    echo \"Running out of steam ...\"\n\n# Conditional state change, depending on temperature.\nbehavior(waterMachine):\n  ini: Solid\n  fin: Liquid\n  event: Between0and100\n  transition:\n    assert 0 \u003c= temp and temp \u003c= 100\n    echo \"Ice is melting into Water.\\n\"\n\nbehavior(waterMachine):\n  ini: Liquid\n  fin: Gas\n  event: Over100\n  transition:\n    assert temp \u003e= 100\n    echo \"Water is vaporizing into Vapor.\\n\"\n\n#...\n\n# Steady state, if no phase change was triggered, we stay in our current phase\nbehavior(waterMachine):\n  steady: [Solid, Liquid, Gas]\n  transition:\n    # Note how we use the oldTemp that was declared in `onEntry`\n    echo \"Changing temperature from \", oldTemp, \" to \", temp, \" didn't change phase. How exciting!\\n\"\n\n# `Synthesize`\n# -------------------------------------------\n# Synthesizing the automaton will transform the previous specification\n# into a concrete procedure with a name, type and inputs of your choosing.\n#\n# Assertions are inserted to ensure the automaton\n# stops if a state+event combination was not handled.\n#\n# You can pass \"-d:debugSynthesis\" to view the state machine generated\n# at compile-time.\n#\n# The generated code can also be copy-pasted for debugging or for further refining.\nsynthesize(waterMachine):\n  proc observeWater(tempFeed: var seq[float])\n\n# Running the machine\n# -------------------------------------------\nimport random, sequtils\n\necho \"\\n\"\n# Create 20 random temperature observations.\nvar obs = newSeqWith(20, rand(-50.0..150.0))\necho obs\necho \"\\n\"\nobserveWater(obs)\n\n# Output\n# -------------------------------------------\n# @[-3.460770047808822, 114.5693402308219, 16.66758940395412, 147.8992369379481, 38.74529893378966, -34.83679531473696, 68.73127270016445, -10.89306136942781, 55.17781700115015, 114.8825749296374, 86.88038583504948, 47.98729291960338, -40.94605405014646, 141.4807806383724, -19.78255259056119, -1.654260475969281, 37.0554825533913, 80.74588296425821, -7.707680239048244, 37.63170603752019]\n\n# Welcome to the Steamy machine version 2000!\n#\n# Temperature: 37.63170603752019\n# Changing temperature from 0.0 to 37.63170603752019 didn't change phase. How exciting!\n#\n# Temperature: -7.707680239048244\n# Water is freezing into Ice.\n#\n# Temperature: 80.74588296425821\n# Ice is melting into Water.\n#\n# Temperature: 37.0554825533913\n# Changing temperature from 80.74588296425821 to 37.0554825533913 didn't change phase. How exciting!\n#\n# Temperature: -1.654260475969281\n# Water is freezing into Ice.\n#\n# Temperature: -19.78255259056119\n# Changing temperature from -1.654260475969281 to -19.78255259056119 didn't change phase. How exciting!\n#\n# Temperature: 141.4807806383724\n# Ice is sublimating into Vapor.\n# ...\n```\n\n## Displaying the state machine\n\nIt is possible to display the finite state machine using Graphviz.\nSynthesis representation can be converted to Graphviz \".dot\" (or \".gv\") with `toGraphviz`\n\nUsing the previous `waterMachine`\n```Nim\nconst dotRepr = toGraphviz(waterMachine)\nwriteFile(\"water_phase_transitions.dot\", dotRepr)\n```\n\nNote: The conversion is done at compile-time and stored in a string.\n\nTo convert the graph described in the graphviz file to a `.png` use the following command (assuming a shell and graphviz package being installed)\n```sh\ndot -Tpng water_phase_transitions.dot -o water_phase_transitions.png\n```\nFor SVG\n```sh\ndot -Tsvg water_phase_transitions.dot -o water_phase_transitions.svg\n```\n\nA default style is used to differentiate between states, interrupts (exceptional events) and regular events/triggers.\n\n\nOutput of the waterMachine\n\n![water state transitions](examples/water_phase_transitions.png)\n\nAlternatives to Graphviz can be used on the `.dot` files if the output is unsatisfactory. Remember that graph node placement is usually an NP-complete task and requires heuristics to be solved that may not be optimal for your specific graph.\nIn that case consider splitting your finite state machine hence building hierarchical state machines.\n\n## Technical constraints\n\nThe state machine is used as the core of a multithreading runtime:\n- Threading friendly:\n  - No GC or memory management on the heap\n\n- Visual debugging via printing the graph to ensure that all events are handled\n  and no state leads to an unreachable code path.\n\n- Easy to map to model checking and formal verification via clearly labeled: states, events, transitions.\n\n  Multithreading is complex and the more we can prove properties (like the absence of deadlocks or livelocks)\n  the more confidence we can have in the runtime.\n\n- Extremely fast and very low CPU-overhead, as overhead in the state machine means more latency in scheduling work. Furthermore, slowness effects are \"scaled\" by the number of cores.\n\n- Extensibility: as runtime requirements grow (distributed clusters, heterogeneous computing, fine-grained barriers, IO, continuations, cancellations,...),\n\n  Synthesis should help minimizing the potential for entangled control-flow\n  and missing edge-cases.\n\nSynthesis generates a procedure-based automaton using gotos for transitions:\n  - This avoids function calls/returns and associated pushing/poping stack overhead\n  - and switch dispatch branch prediction miss due to having a single point of dispatch..\n\nIn addition to the multithreading runtime requirements this architecture\nallow Synthesis to produce code with a very low footprint which makes it suitable for embedded.\nFurthermore, setPrologue/setEpilogue/onEntry/onExit and the transitions are very flexible, they can\nuse inline statements, declare new variables or defer computation to one or more procs.\n\n## References\n- [Mealy State Machine](https://en.wikipedia.org/wiki/Mealy_machine)\n- [Pushdown Automaton](https://en.wikipedia.org/wiki/Pushdown_automaton)\n- [Communicating Finite State Machines](https://en.wikipedia.org/wiki/Communicating_finite-state_machine)\n- [Petri Nets](https://en.wikipedia.org/wiki/Petri_net)\n- [Kahn Process Networks](https://en.wikipedia.org/wiki/Kahn_process_networks)\n\nExtras\n- [Overview/Slides from Marquette Embedded SystemS Laboratory](https://www.dejazzer.com/ece777/ECE777_3_system_modeling.pptx)\n- [Berkeley's Ptolemy project](https://ptolemy.berkeley.edu/index.htm)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmratsim%2Fsynthesis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmratsim%2Fsynthesis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmratsim%2Fsynthesis/lists"}