Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Continuation-Passing Style for Nim 🔗

License

NotificationsYou must be signed in to change notification settings

nim-works/cps

Repository files navigation

Test MatrixGitHub release (latest by date)Minimum supported Nim versionRecommended Nim versionMaximum supported Nim versionLicenseMatrixIRC

TL;DR

It's acps pragma you annotate a procedure declaration with in order toautomagically rewrite it as a type which holds 1) all the procedure's locals,and 2) a pointer to run the continuation.

You instantiate continuations by calling your function with arguments, or viatyped continuation callbacks. You may extend the Continuation type to addcustom data and functionality.

The macro provides comfort for automatic chaining, stack tracing, drying upfunction color, and handling exceptions. Hooks enable precisely-scoped controlof much of the machinery, including memory management.

What Is CPS?

Click here for an awesome infographic explaining what CPS is and how our transform works.The Nim CPS Transform

Acps proc macro enables enjoyment of the CPS abstraction for free, hiding allthe verbosity and spaghetti code paths of hand-written continuation passing. Ittakes your standard procedure and rewrites it at compile-time into a continuationform.

The macro performs only the control-flow rewrite. You may define your owncontinuation types. To manage them, you can choose from available dispatchersor customize your own.

As CPS is essentially a safe transformation of Nim to equivalent Nim, you canuse it anywhere you can use Nim,or more accurately, C, C++, or JavaScript.

Why Do I Want It?

The continuations produced by this macro aretiny -- as little as 32 byteseach -- and run at the speed of any native function call; they…

  • compose efficient and idiomatic asynchronous code
  • are over a thousand times lighter than threads
  • are leak-free under Nim's modern memory management
  • may be extended with custom inheritance-based types
  • may be managed using custom dispatchers (executors)
  • may be moved between threads to parallelize execution
  • have no backend or runtime library requirements
  • exploit no unsafe features of the language (cast,ptr,addr,emit)

Why Haven't I Heard of It?

The project isn't dead; it simply hasn't needed much development. We have a fewsmall improvements we want to make before a 1.0 major release, but the generalconsensus is that the current implementation accomplishes our original goalsquite well.

Major future development will revolve around implementing CPS directly intheNimskull compiler.

How Does It Work?

A substantial effort to demystify this style of programming, and what it mayenable, livesin the docs/ subdirectory.

We also havea tutorial to help new users on their way to get starting withCPS.

For a description of the origins of our approach, seethe includedpapers andnim-lang/RFCs#295, where we write in more depth aboutwhy the implementation exists, goals for future development, etc.

Architecture

The implementation is comprised of two moving parts:

  1. acontinuation is a bespoke type made to carry all locals in a procedure-- theenvironment -- plus a function pointer with which the continuationis poised to continue, orresume:
typeContinuation=refobject# all continuations inherit from this    next:proc(c:Continuation):Continuation
  1. adispatcher is a procedure which resumes a continuation by invokingthe continuation's function pointer with the continuation itself as input.It follows that tosuspend, the function simply returns control to thedispatcher.
c= c.next(c)# we often replace the continuation with its result

Atrampoline is common form of dispatcher which resumes a continuation in aloop; it bounces control up to the continuation, which returns back down to thetrampoline when it's ready to suspend.

whiletrue:if c.isNil:# 'dismissed': a nil continuation resultbreakifnot c.next.isNil:# 'finished': a nil continuation functionbreak  c= c.next(c)# resuming a 'running' (suspended) continuation

Application

We use a procedure definition to define the continuation. The type we want tobase the continuation upon is supplied as the only argument to ourcps pragmamacro. You can simply use theContinuation type itself, if you prefer.

proczoom() {.cps:Continuation.}=## a very fast continuationdiscard

Calling the procedure (with arguments, if required) runs the continuation tocompletion.

zoom()echo"that was fast!"

You can extend theContinuation type to carry state during the execution ofyour continuation.

typeCount=refobjectofContinuation    labels:Table[string,ContinuationFn]

Here we've introduced a table that maps strings to a continuation "leg", orslice of control-flow that is executed as a unit.

Now we'll introduce two magical procedures for manipulating the table.

proclabel(c:Count; name:string):Count {.cpsMagic.}=## Record a label by `name` which we can later goto().  c.labels[name]= c.fnreturn c# control resumes in the continuationprocgoto(c:Count; name:string):Count {.cpsMagic.}=## Resume execution at the `name` label in the code.  c.fn= c.labels[name]return c# control resumes in the continuation

These pragma'd procedures act as continuation legs and we can use them in ourcontinuations without supplying the initialCount continuation argument.

Some people call this "colorless" syntax, since calls look the same whethermade inside or outside of a continuation.

proccount(upto:int):int {.cps:Count.}=## deploy the Count to make counting fun again;## this continuation returns the number of trips through the gotovar number=0  label:"again!"inc numberecho number,"!"echo number," loops, ah ah ah!"if number< upto:goto"again!"echo"whew!"return numberconst many=1_000_000_000assert many+1==count(many)# (this might take awhile to finish)

Sometimes you don't want to do a lot of counting right away, but, y'know, maybea bit later, after your nap. In that case, you can usewhelp to instantiateyour continuation with arguments, without actually invoking it.

When you're ready, thetrampoline will run your continuation to completionand bounce it back to you.

var later=whelpcount(1_000_000)sleep30*60*1000echo"it's later!  time to count!"later=trampoline later

Continuations have a simplestate(c: Continuation): State enum that is helpedintorunning(),finished(), anddismissed() boolean predicates.

assert later.finished,"counting incomplete!"

Continuations can themselves be called in order to retrieve their result.

echo"i counted",later()," trips through the goto"

Such a call will run the continuation on your behalf if it has not already runto completion.

var later=whelpcount(1_000_000)echo"we counted",later()," trips through the goto"

Installation

$ nimble install https://github.com/nim-works/cps

Documentation

Ready For More?

examples/coroutine.nim(walkthrough): A simple coroutine implementation ofcoroutines on top of CPS communicating with each other.

Complete API Documentation

Seethe documentation for the cps module as generated directly from the source.

Extensive Test Suite

At last count, there are no less than211 unique tests of the cpslibrary which confirm successful handling of myriad semantic formsand complex continuation composition. The tests are arguably the most valuablepiece of code in the project and, as per usual, serve as the most completedocumentation of the specification.

Dispatchers

An example dispatcher with support for yields, I/O, and timers was included inthe past, but demonstrating dispatch conflated the roles ofcontinuation anddispatcher, confusing newcomers.

For a robust dispatcher implementation targeting broad OS support and modernasync features, take a look athttps://github.com/alaviss/nim-sys.

Examples

A small collection of examples provides good demonstration of multiple patternsof CPS composition. Each example runs independently, with no other requirements,yet demonstrates different exploits ofcps.

ExampleDescription
ChannelsA channel connects sender and receiver continuations
GotoImplementation oflabel andgoto statements using CPS
IteratorA simple demonstration of a CPS-based iterator
CoroutinesA pair of continuations communicate as coroutines.Walkthrough.
LazyLazy streams are composed by continuations in a functional style
PipesCoroutines compose streams which connect arbitrarily
TryCatchException handling is reimplemented using only CPS
CpsCpsContinuations can efficiently call other continuations
WorkImplementation of a simple continuation scheduler
LuaCoroutinesCoroutines implemented in the style of Lua
ThreadPool1,000,000 continuations run across all your CPU cores

Demonstration Projects

Here are a few projects which demonstrate CPS library integration.

Smaller

ProjectDescription
WebServerZevv's "Real World Test" WebServer And More
BackgroundRun any function on a background thread
PassengerCompose graph visualizations of CPS control-flow
HttpLeastA simple web-server for benchmarking CPS
solo5_dispatcherA simple CPS scheduler for Solo5 unikernels

Larger

ProjectDescription
BallsA threaded test runner based upon CPS
SysNext-generation operating system service abstractions
ActorsZevv's experimental project to create a threaded, share-nothing actor based framework on top of CPS
InsideOutAnother experimental concurrency library which supports CPS over threads
TapsA portable, asynchronous, network abstraction library
SyndicateThe Syndicated Actor Model for Nim

Debugging

Expectations

Seethis list of open Nim issues surfaced by CPSdevelopment; some repercussions include the following:

  • Exceptions are evaluated differently underpanics:on andpanics:off, soyou may need to usepanics:on in order to produce reliably correct code.

  • Expressions are evaluated differently undergc:[ao]rc, so you may need touse those memory managers in order to produce reliably correct code.

  • Thecpp backend often doesn't work, particularly due to faulty codegen butalso, perhaps, due toexceptions:goto assumptions that we rely upon.

  • var/openArray/varargs parameters to procedures with thecps pragmaare not supported.

  • Nim'sfor loops work, but you cannot perform any CPS control-flow inside ofthem; if in doubt, use awhile loop instead.

Call Syntax

Use--define:cpsNoCallOperator to explicitly disable the() operator.

Performance

If you are not running withdefine:danger andgc:arc andpanics:on thenperformance considerations really aren't your primary consideration, right?

Tracing

There are two tracing systems that are enabled by default via Nim's built-instackTrace:on compiler option, which defaults toon outside ofreleaseanddanger builds.

Stack Frames

Stack semantics are implemented by a singleTraceFrame object stored on eachcontinuation and these serve to capture the progress of control-flow throughmultiple levels of CPS calls just as they do so for normal stack-based code.

TherenderStackFrames() andwriteStackFrames() procedures return a sequenceof strings or write them to the standard message stream, respectively. You canrun these procedures without arguments in any continuation.

You can extend theStack hook to alter its behavior.

Force-enable the stack frame support with--define:cpsStackFrames=on.

Trace Deque

The trace deque system stores the lastN hooks executed by the continuation,whereN defaults to4096 (see below). A single continuation has multiplehooks executed during its lifecycle, so these traces can be very detailed.

TherenderTraceDeque() andwriteTraceDeque() procedures return a sequenceof strings or write them to the standard message stream, respectively. You canrun these procedures without arguments in any continuation.

You can extend theTrace hook to alter its behavior.

Force-enable the trace deque support with--define:cpsTraceDeque=on andalter the size of the deque with e.g.--define:traceDequeSize=100.

UsingcpsDebug

Add--define:cpsDebug=SomePass whereSomePass matches one of the CPStransformation passes; this will output Nim codegen corresponding to therewrite phase. Interesting places to start include the following:

  • cpsTransform
  • cpsResolver
  • cpsJump
  • cpsContinuationJump
  • cpsManageException
  • cpsTryFinally
  • etc.

Note that only one--define:cpsDebug=... can be enabled at a time - multiple defines will use only the final one.

Usingtrace

Implementtrace and it will be called at each continuation leg;see the documentation for details.

Use--define:cpsNoTrace to disable thetrace code generation.

License

MIT


[8]ページ先頭

©2009-2025 Movatter.jp