Skip to content

Instantly share code, notes, and snippets.

@nasser
Created April 7, 2020 18:40
Show Gist options
  • Save nasser/bd3de4582ed96ab496971d4967f15626 to your computer and use it in GitHub Desktop.
Save nasser/bd3de4582ed96ab496971d4967f15626 to your computer and use it in GitHub Desktop.
MAGE Generators/Coroutines
using System;
using System.Reflection;
[assembly: AssemblyVersion("0.0.0.0")]
public class ManualCoro
{
private int <state>;
public void CoroutineMethod()
{
switch (this.<state>)
{
case 0:
this.<state> = -1;
Console.WriteLine("A");
this.<state> = 1;
return;
case 1:
this.<state> = -1;
Console.WriteLine("B");
this.<state> = 2;
return;
case 2:
this.<state> = -1;
if (!DateTime.IsLeapYear(2005))
{
Console.WriteLine("C1");
this.<state> = 3;
return;
}
break;
case 3:
this.<state> = -1;
Console.WriteLine("C2");
break;
case 4:
this.<state> = -1;
break;
case 5:
this.<state> = -1;
this.<state> = 6;
return;
case 6:
this.<state> = -1;
Console.WriteLine("F");
return;
default:
return;
}
if (DateTime.IsLeapYear(2009))
{
Console.WriteLine("D");
this.<state> = 4;
return;
}
Console.WriteLine("E");
this.<state> = 5;
}
}
(defn yield [field state]
[(il/ldarg-0)
(il/ldc-i4 (int state))
(il/stfld field)
(il/ret)
(il/label (str "state" state))
(il/ldarg-0)
(il/ldc-i4-m1)
(il/stfld field)])
(defn yield-header [field count]
[(il/ldarg-0)
(il/ldfld field)
(il/switch (mapv #(il/label (str "state" %)) (range count)))
(il/ret)
(il/label "state0")
(il/ldarg-0)
(il/ldc-i4-m1)
(il/stfld field)])
(il/emit!
(il/assembly+module
"out"
(il/type
"ManualCoro"
(let [state-field (il/field Int32 "<state>" System.Reflection.FieldAttributes/Private)
cond-label (il/label)
loop-start-label (il/label)
loop-end-label (il/label)]
(il/method
"CoroutineMethod" System.Void []
[(yield-header state-field 7)
;; (print "A")
(il/ldstr "A")
(il/call (interop/method Console "WriteLine" String))
;; (yield)
(yield state-field 1)
;; (print "B")
(il/ldstr "B")
(il/call (interop/method Console "WriteLine" String))
;; (yield)
(yield state-field 2)
;; (when (DateTime/IsLeapYear 2005)
(il/ldc-i4 (int 2005))
(il/call (interop/method DateTime "IsLeapYear" Int32))
(il/brtrue cond-label)
;; (print "C1")
(il/ldstr "C1")
(il/call (interop/method Console "WriteLine" String))
;; (yield)
(yield state-field 3)
;; (print "C2")
(il/ldstr "C2")
(il/call (interop/method Console "WriteLine" String))
;; )
cond-label
;; (while (DateTime/IsLeapYear 2009)
loop-start-label
(il/ldc-i4 (int 2009))
(il/call (interop/method DateTime "IsLeapYear" Int32))
(il/brfalse loop-end-label)
;; (print "D")
(il/ldstr "D")
(il/call (interop/method Console "WriteLine" String))
;; (yield)
(yield state-field 4)
(il/br loop-start-label)
loop-end-label
;; )
;; (print "E")
(il/ldstr "E")
(il/call (interop/method Console "WriteLine" String))
;; (yield)
(yield state-field 5)
;; (yield)
(yield state-field 6)
;; (print "F")
(il/ldstr "F")
(il/call (interop/method Console "WriteLine" String))
(il/ret)]
)))))
(print "A")
(yield)
(print "B")
(yield)
(when (DateTime/IsLeapYear 2005)
(print "C1")
(yield)
(print "C2"))
(while (DateTime/IsLeapYear 2009)
(print "D")
(yield))
(print "E")
(yield)
(yield)
(print "F")
@nasser
Copy link
Author

nasser commented Apr 7, 2020

Playing around with emitting coroutines using MAGE, trying to mimic the transformation that C# does. The idea is to turn a function with yield expressions into a stateful generator object that implements a state machine. You want yield to work inside of loops and conditionals. It seems to be pretty simple when operating against bytecode for a stack machine?

  1. Every yield expression represents a state in the finite state machine
  2. The current state is encoded as an integer instance field, the state field
  3. A preprocessor or analyzer assigns each yield expression a sequential state number, as captured by the yield function in mage-code.clj.
  4. Each yield expands into the following instructions:
    1. Assign the state number to the state field
    2. Return
    3. Mark this point in the instruction stream with a label corresponding to the state number
    4. Assign -1 to the state field
  5. The start of method needs the following instructions (captured by yield-header in mage-source.clj):
    1. Switch on the state field, the jump table is an array of labels corresponding to state numbers
    2. Mark this point in the instruction stream with a label corresponding to state number 0
    3. Assign -1 to the state field

I am not sure how important assigning -1 to the state field is. I do it here to mimic Roslyn's output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment