Skip to content

Conversation

Aidan63
Copy link
Contributor

@Aidan63 Aidan63 commented Apr 12, 2025

Third times the charm, right?

This is an evolution of #11554, the core state machine transformation has remained pretty much untouched from that branch, but the surrounding scaffolding code has been changed to more closely align with kotlin's coroutine implementation. This should hopefully address the issues surrounding continuation, scheduling, and a few others which were discovered in the other PR.

Changes

Coroutine functions are now transformed to accept a Continuation<Any> argument, return Any, and contain the state machine transformation. The continuation argument either holds the state of a paused execution of the coroutine function, or what will be invoked once the coroutine completes. The any return will either be the result of the coroutines execution, or a special token indicating that it's execution has been suspended.

@:coroutine function foo(i:Int):String {}

function foo(i:Int, _hx_continuation:ICoroutine<Any>):Any {}

In addition each coroutine function has a class generated for it. This class implements IContinuation<Any> and stores data related to the execution of a coroutine, this includes, the current state, the result or error, variables accessed across states, and a reference to the continuation which will be invoked once this coroutine completes, essentially creating a chain. The continuation interface contains a resume function which when called resumes execution of the coroutine and is responsible to scheduling the execution of the coroutine into an appropriate thread.

class HxCoro__Main_foo implements IContinuation<Any> {
	public var _hx_result : Any;

	public var _hx_error : Exception;

	public var _hx_state : Int;

	public var _hx_context : CoroutineContext;

	public var _hx_completion : IContinuation<Any>;

	public function resume(result:Any, error:Exception) {
		_hx_result = result;
		_hx_error  = error;

		_hx_context.scheduler.schedule(() -> {
			try {
				var result = Main.foo(0, this);
				if (result is Primitive) {
					return;
				} else {
					_hx_completion.resume(result, null);
				}
			} catch (exn:Exception) {
				_hx_completion.resume(null, exn);
			}
		});
	}
}

Coroutines can be started with the blocking function Coroutine.run, which blocks until a coroutine completes. Internally it has its own event loop which all coroutine continuations are scheduled onto.

A quick hello world example is provided below. It should work on all sys targets, but some targets might need their generators updated.

import haxe.coro.Coroutine;
import haxe.coro.Coroutine.delay;

@:coroutine function test() {
    fancyPrint('hello');

    delay(1000);

    fancyPrint('world');
}

@:coroutine function fancyPrint(text:String):Void {
    for (i in 0...text.length) {
        Sys.print(text.charAt(i));

        delay(100);
    }

    Sys.print('\n');
}

function main() {
    Coroutine.run(test);
}

Resources

I'm putting a few links below, specifically related to kotlin's coroutines, which helped the puzzle come together a bit for me.

https://kt.academy/article/cc-under-the-hood

That is a good overview of the underlying machinery of kotlin's coroutines. Jumping straight into decompiled kotlin is very daunting as there are many, many layers of abstraction due to optimisations, features, etc. That article cuts through all of it and gets right to the core of whats happening. I pretty much copied that for this coroutine attempt.

https://www.droidcon.com/2022/09/22/design-of-kotlin-coroutines/

This then goes one layer deper, it covers a lot of those layers you see in kotlin's implementation and explains what they're there for.

https://github.com/JetBrains/kotlin/blob/master/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutines-codegen.md

Finally, the mega document. Goes in depth into the jvm codegen of kotlin's coroutines.

@Simn
Copy link
Member

Simn commented Apr 14, 2025

I noticed that we were stuck on an outdated hxcpp branch. After updating this we now have green CI for cpp, which is a good baseline.

As the next step I'd like to get the JVM tests green. And afterwards we'll have to figure out how to make all this work on non-sys targets, because our coroutine execution currently relies on sys.thread.EventLoop.

@skial skial mentioned this pull request Apr 14, 2025
1 task
@Simn
Copy link
Member

Simn commented Apr 15, 2025

Update:

TestBasic
  testResumeWithError: FAILURE F
    line: 30, exception of type haxe.Exception not raised
  testSimple: FAILURE F
    line: 6, expected 42 but it is 330584

Maybe @yuxiaomao could take a look at that some time. We also don't seem to run these tests on CI yet because HL is green.

Simn and others added 30 commits June 14, 2025 06:33
* support element deletion in Page

* get cpp green even though this seems weird

* add some more tests

* fix isEmpty but find more problems

* fix isEmpty resetting

* don't blit with length 0

* use ArraySort for stability

* change API to something that might actually be sound

* test middle page deletion

* move tests to TestPagedDeque
* [tests] fix server tests builder

* WIP fix server tests with coro

* [CI] enable server tests..

* Use promise wrapper instead of blocking run

* point to my updated utest coro branch

* Fix server display tests

---------

Co-authored-by: Aidan Lee <[email protected]>
* Some initial work on variable spilling

* skip restoring with the first state

* bodge saving and restoring

* make arguments appear in the first state usage table

* Don't restore variables in their defined state

* Add loop iteration hoisting test

* another attempt

* sort out arguments

they're always a pain

* round and round we go...

Back to per state vars, was hoping the "tmp used without being initialised" would magically solve itself, but no. Might be related to TVar exprs

* give some vars default type expressions

* bodge it

* disable hanging test so we can look at actual failures

* hack it even more

* Add some comments so I don't forget what this all is again

* attempt at avoiding double wrapping

seems to work on some targets...

* mark them as captured as well

* don't duplicate half of capturedVars

* Fix dodgy merge

* Need to follow abstracts away before getting default values

Not sure why I need to do this now

---------

Co-authored-by: Simon Krajewski <[email protected]>
* track local read and writes for states

* Use Texpr.skip
* add tryRead

* progress so far

* work around variance problem

* apply variance to bounded reader as well

* Add reader wakeup tests

* add write tests

* adjust

* generic createUnbounded

* load of bounded reader tests

* Remove test which I think we don't need

* channel kind enum

* add reader tests to main

* work around weird paged dequeue issue

* channel tests updated and passing

* bring back all tests

* Add basic creation tests

* add writer closing tests

* remove some unused imports

* reader cancellation

* async success, but immediate scheduler in tests

* Fix missing var

* Move existing tests into the bounded test class

* Writer sets out closed object

* Add another tests which includes the wait for read and channel closing

* use PagedDeque.remove

* go back to the dequeue tryPop loop

* add concurrent circular buffer

* configurable write behaviour when buffer is full

* experiments with single reader and writer combo

* drop callbacks

* Quickly thrown together and untested ubbounded channel

* prompt cancellation waitForWrite

* throw channel closed when writing to a closed channel

* Add unbounded writer tests

* work around weird issue

* disable single producer consumer implementation

will revisit at a later time

* Work around weird python issue by breaking these out into variables

* unbounded reader tests

* Add tryPeek to channel reader

* Have bounded channel use a circular buffer instead of an array

---------

Co-authored-by: Aidan Lee <[email protected]>
* Add thread pool scheduler

* Use event loop now

* Make negative time exception message consistent

* try sys wrap

* fix thread dodge

---------

Co-authored-by: Simon Krajewski <[email protected]>
And add basic 3rd party tests (eval only for now)
Move continuations to be the leading argument
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants