Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2017-04

Coroutines and VirtualAlloc

29 messages · 10 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Ryan Joseph

I was curious about possible ways coroutines could work in FPC and found this example that claims to implement just that. It appears to be designed for Windows though and makes use of a function called VirtualAlloc which I don’t understand. Is there a non platform specific version of this function? I’d like to test the code but I have no idea what that function does or how to replace it.

http://www.festra.com/wwwboard/messages/12899.html

Regards,
Ryan Joseph

Daniel Gaspary
to Ryan Joseph
Ryan Joseph wrote
I was curious about possible ways coroutines could work in FPC and found this example that claims to implement just that. It appears to be designed for Windows though and makes use of a function called VirtualAlloc which I don’t understand. Is there a non platform specific version of this function? I’d like to test the code but I have no idea what that function does or how to replace it.

Using SetJmp and LongJmp?

I believe some months ago it was a discussion on the list on why this was not really the way to implement coroutines.

Searching for longjmp/setjmp you can find the thread, I guess.

Ryan Joseph
to Daniel Gaspary
Show quoted text 8 lines
On Apr 19, 2017, at 2:34 AM, Daniel Gaspary wrote: Using SetJmp and LongJmp? I believe some months ago it was a discussion on the list on why this was not really the way to implement coroutines. Searching for longjmp/setjmp you can find the thread, I guess.

I never heard of those functions and I did find a thread about them but it seemed inconslusive.

Here’s a little test I made not knowing exactly how those functions work. I expected the stack to be restored and “i” incremented but that’s not what happens. The program jumps in and out of the loop but “i” remains the same each time. Is this not how to use those functions?

type
	TCoroutine = class (TObject)
		public
			procedure Start;
			procedure Yield;
			procedure Resume;
		private
			entry: jmp_buf;
			env: jmp_buf;
			passes: longint;
			yieldNext: boolean;
	end;
procedure TCoroutine.Start;
var
	i: integer = 0;
begin
	Setjmp(entry);
	if passes > 0 then
		exit;
	writeln('start');
	while i < 10 do
		begin
			writeln('loop ', i);
			i += 1;
			Setjmp(env);
			if not yieldNext then
				Yield;
			yieldNext := false;
		end;
end;
procedure TCoroutine.Yield;
begin
	writeln('yield');
	passes += 1;
	yieldNext := false;
	Longjmp(entry, 1);
end;
procedure TCoroutine.Resume;
begin
	writeln('resume');
	yieldNext := true;
	Longjmp(env, 1);
end;
procedure CoroutineTest;
var
	co: TCoroutine;
	i: integer;
begin
	co := TCoroutine.Create;
	co.Start;
	writeln('stepped out');
	for i := 0 to 4 do
		co.Resume;
	writeln('finished');
end;

========

start
loop 0
yield
stepped out
resume
loop 1
yield
resume
loop 1
yield
resume
loop 1
yield
resume
loop 1
yield
resume
loop 1
yield
finished

Regards,
Ryan Joseph

Mark Morgan Lloyd
to Ryan Joseph
Ryan Joseph wrote
> On Apr 19, 2017, at 2:34 AM, Daniel Gaspary wrote:> > Using SetJmp and LongJmp?> > I believe some months ago it was a discussion on the list on why this> was not really the way to implement coroutines.> > Searching for longjmp/setjmp you can find the thread, I guess. I never heard of those functions and I did find a thread about them but it seemed inconslusive. Here’s a little test I made not knowing exactly how those functions work. I expected the stack to be restored and “i” incremented but that’s not what happens. The program jumps in and out of the loop but “i” remains the same each time. Is this not how to use those functions?

I thought that Windows implemented "fibers" for this sort of thing?

It is possible to partially-simulate coroutines with setjmp/longjmp, but you need to store state outside the function. The key thing about coroutines, at least as implemented by Wirth in Modula-2, is that you can transfer arbitrarily between them i.e. it's not just a case of a scheduler selecting one specific task which runs and then returns.

Mark Morgan Lloyd markMLl .AT. telemetry.co .DOT. uk [Opinions above are the author's, not those of his employers or colleagues]
Ryan Joseph
to Mark Morgan Lloyd
On Apr 19, 2017, at 3:28 PM, Mark Morgan Lloyd wrote: It is possible to partially-simulate coroutines with setjmp/longjmp, but you need to store state outside the function. The key thing about coroutines, at least as implemented by Wirth in Modula-2, is that you can transfer arbitrarily between them i.e. it's not just a case of a scheduler selecting one specific task which runs and then returns.

I only used coroutines in Lua and they were basically a way for a control structure like a loop to return control the main program without using threading. From the descriptions it sounds like longjmp moves the current point of execution of the program (if that concept exists like I imagine it does) so you could step in and out of loops or functions. If not that then what do those functions actually do?

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph: I never heard of those functions and I did find a thread about them but it seemed…
"Ryan Joseph" wrote 12 lines
> On Apr 19, 2017, at 2:34 AM, Daniel Gaspary wrote: > > Using SetJmp and LongJmp? > > I believe some months ago it was a discussion on the list on why this > was not really the way to implement coroutines. > > Searching for longjmp/setjmp you can find the thread, I guess. I never heard of those functions and I did find a thread about them but

it seemed inconslusive.

Those functions simply store (setjmp) and restore (longjmp) register values (and setjmp also returns the value passed to longjmp if it had been reached by a longjmp). Nothing more, nothing less. So while the stack register while be changed, the contents on the stack will not.

Regards,
Sven

Ryan Joseph
to Sven Barth
On Apr 19, 2017, at 4:14 PM, Sven Barth via fpc-pascal wrote: Those functions simply store (setjmp) and restore (longjmp) register values (and setjmp also returns the value passed to longjmp if it had been reached by a longjmp). Nothing more, nothing less. So while the stack register while be changed, the contents on the stack will not.

Why doesn’t “i” in my example increment? The value keeps going back to 1 even after I used += 1 so its like the old copy of the stack before the jump got pushed back on top and it started over. I don’t understand how assembly works but I thought it would just start over and the state of the stack in that function would still be the same as before so I could keep adding 1 every pass.

Regards,
Ryan Joseph

Michael Van Canneyt
to Ryan Joseph
Ryan Joseph wrote 11 lines
> On Apr 19, 2017, at 4:14 PM, Sven Barth via fpc-pascal wrote: > > Those functions simply store (setjmp) and restore (longjmp) register values (and setjmp also returns the value passed to longjmp if it had been reached by a longjmp). Nothing more, nothing less. So while the stack register while be changed, the contents on the stack will not. Why doesn’t “i” in my example increment? The value keeps going back to 1 even after I used += 1 so its like the old copy of the stack before the jump got pushed back on top and it started over. I don’t understand how assembly works but I thought it would just start over and the state of the stack in that function would still be the same as before so I could keep adding 1 every pass.

The stack remains untouched by setjmp/longjmp.

Your reasoning contains a wrong assumption, namely that I is on the stack.

If I is a register variable, then it is not on the stack, and will be reset with each longjmp.

Michael.

Sven Barth
to Ryan Joseph: Why doesn’t “i” in my example increment? The value keeps going back to 1 even after…
"Ryan Joseph" wrote 6 lines
> On Apr 19, 2017, at 4:14 PM, Sven Barth via fpc-pascal < wrote: > > Those functions simply store (setjmp) and restore (longjmp) register

values (and setjmp also returns the value passed to longjmp if it had been reached by a longjmp). Nothing more, nothing less. So while the stack register while be changed, the contents on the stack will not.

Why doesn’t “i” in my example increment? The value keeps going back to 1

even after I used += 1 so its like the old copy of the stack before the jump got pushed back on top and it started over. I don’t understand how assembly works but I thought it would just start over and the state of the stack in that function would still be the same as before so I could keep adding 1 every pass.

Ah, I didn't catch that earlier. Probably "i" is kept in a register due to optimization. You should check the generated assembler code to be sure.

Regards,
Sven

Ryan Joseph
to Michael Van Canneyt: The stack remains untouched by setjmp/longjmp. Your reasoning contains a wrong…
Show quoted text 6 lines
On Apr 19, 2017, at 4:33 PM, Michael Van Canneyt wrote: Your reasoning contains a wrong assumption, namely that I is on the stack. If I is a register variable, then it is not on the stack, and will be reset with each longjmp.

I thought all variables declared inside a function (like below) were “on the stack” and I don’t even know what a register variable is honestly. Is there a way to make my example work and not have “i” reset each jump by declaring it differently or is that the choice of the compiler?

procedure TCoroutine.Start;
var
	i: integer = 0;
begin
end;

Regards,
Ryan Joseph

Michael Van Canneyt
to Sven Barth: values (and setjmp also returns the value passed to longjmp if it had been reached by a…
Sven Barth wrote 20 lines
Am 19.04.2017 11:26 schrieb "Ryan Joseph" : > > >> On Apr 19, 2017, at 4:14 PM, Sven Barth via fpc-pascal < > wrote: >> >> Those functions simply store (setjmp) and restore (longjmp) register values (and setjmp also returns the value passed to longjmp if it had been reached by a longjmp). Nothing more, nothing less. So while the stack register while be changed, the contents on the stack will not. > > Why doesn’t “i” in my example increment? The value keeps going back to 1 even after I used += 1 so its like the old copy of the stack before the jump got pushed back on top and it started over. I don’t understand how assembly works but I thought it would just start over and the state of the stack in that function would still be the same as before so I could keep adding 1 every pass. Ah, I didn't catch that earlier. Probably "i" is kept in a register due to optimization. You should check the generated assembler code to be sure.

Glad we responded the same thing :)

Michael.

Mark Morgan Lloyd
to Ryan Joseph: I only used coroutines in Lua and they were basically a way for a control structure like…
Ryan Joseph wrote
> On Apr 19, 2017, at 3:28 PM, Mark Morgan Lloyd wrote:> > It is possible to partially-simulate coroutines with setjmp/longjmp, but you need to store state outside the function. The key thing about coroutines, at least as implemented by Wirth in Modula-2, is that you can transfer arbitrarily between them i.e. it's not just a case of a scheduler selecting one specific task which runs and then returns. I only used coroutines in Lua and they were basically a way for a control structure like a loop to return control the main program without using threading. From the descriptions it sounds like longjmp moves the current point of execution of the program (if that concept exists like I imagine it does) so you could step in and out of loops or functions. If not that then what do those functions actually do?

SetJmp records the current state, LongJmp reverts to it. There's some common-sense limitations.

I've got an example program of about 100 lines that would demonstrate what I've hacked together, I could tack it onto a message if the ML managers consider that acceptable.

Mark Morgan Lloyd markMLl .AT. telemetry.co .DOT. uk [Opinions above are the author's, not those of his employers or colleagues]
Ryan Joseph
to Mark Morgan Lloyd
Show quoted text 5 lines
On Apr 19, 2017, at 4:57 PM, Mark Morgan Lloyd wrote: SetJmp records the current state, LongJmp reverts to it. There's some common-sense limitations. I've got an example program of about 100 lines that would demonstrate what I've hacked together, I could tack it onto a message if the ML managers consider that acceptable.

yes, I’d like to see that so I know why my example doesn’t work as I expected. Everything I’m hearing makes me think “i” should keep incrementing after I call SetJmp and then return with JongJmp but there’s something I’m missing obviously.

Regards,
Ryan Joseph

Michael Van Canneyt
to Ryan Joseph: I thought all variables declared inside a function (like below) were “on the stack”…
Ryan Joseph wrote 10 lines
> On Apr 19, 2017, at 4:33 PM, Michael Van Canneyt wrote: > > Your reasoning contains a wrong assumption, namely that I is on the stack. > > If I is a register variable, then it is not on the stack, and will be reset > with each longjmp. I thought all variables declared inside a function (like below) were “on the stack” and I don’t even know what a register variable is honestly.

It's a variable which the compiler does not put on the stack, it exists just in a register.

Is there a way to make my example work and not have “i” reset each jump by declaring it differently or is that the choice of the compiler?

The compiler chooses this. But you can disable it, I think, by disabling optimizations.

Michael.

Ryan Joseph
to Michael Van Canneyt
On Apr 19, 2017, at 5:17 PM, Michael Van Canneyt wrote: It's a variable which the compiler does not put on the stack, it exists just in a register.

That kind of defeats the purpose then if you can’t rely on function scoped variables to be restored. Maybe that’s what the code in the original link did? I was going to try it but I never figured out how to replace VirtualAlloc without Windows (I’m on a Mac).

Thanks.

Regards,
Ryan Joseph

Karoly Balogh (Charlie/SGR
to Ryan Joseph: yes, I’d like to see that so I know why my example doesn’t work as I expected.…

Hi,

Ryan Joseph wrote
yes, I?d like to see that so I know why my example doesn?t work as I expected. Everything I?m hearing makes me think ?i? should keep incrementing after I call SetJmp and then return with JongJmp but there?s something I?m missing obviously.

Your example is simply broken. A few points:

First, When you're LongJmp'ing to entry state in Resume, you're expecting the Start stack state (and with that, your "i" variable's state) to be intact. This is not the case. After the Start method returns (stepped out) it's stack contents are destroyed, and even get overwritten by the next 'writeln' method call, so the next time Resume is called, you get unpredictable behavior by jumping into the stack state of Start, which no longer exist and was overwritten since. For example, it simply crashes with Runtime Error 216 on Mac OS X 32 bit. For you, it just uses some "random" value on the stack (or register), which happens to be 1. I get this even without register variables, so I doubt it has anything to do with that... (Sorry Michael/Sven... ;)

Basically, you can only jump to a "higher" level on the stack, not equal (from a different function) and not to a "deeper" level.

Second, if you really want to use SetJmp/LongJmp states you can use SetJmp return value for that. If SetJmp returns 0, it wasn't returning from a LongJmp. If it returns 1, it was returning from a LongJmp. You don't need external state to track that.

Charlie

Ryan Joseph
to Karoly Balogh (Charlie/SGR
On Apr 19, 2017, at 5:37 PM, Karoly Balogh (Charlie/SGR) wrote: Your example is simply broken. A few points:

Thanks for the description. Yeah, I assumed the stack would be restored but that isn’t the case apparently.

I think the coroutine implementation in the link below tries to manage the stack frames and restore them but it requires Windows for the VirtualAlloc function and I’m on a Mac. No idea if that code works or not and it looks kind of risky honestly.

http://www.festra.com/wwwboard/messages/12899.html

Regards,
Ryan Joseph

Marco van de Voort
to Ryan Joseph

In our previous episode, Ryan Joseph said:

Show quoted text 7 lines
> Your example is simply broken. A few points: Thanks for the description. Yeah, I assumed the stack would be restored but that isn?t the case apparently. I think the coroutine implementation in the link below tries to manage the stack frames and restore them but it requires Windows for the VirtualAlloc function and I?m on a Mac. No idea if that code works or not and it looks kind of risky honestly. http://www.festra.com/wwwboard/messages/12899.html

Not just virtual alloc, nearly the whole of the asm is accessing SEH via fs and thus Windows dependent.

Daniel Gaspary
to Marco van de Voort

So..

Any chance of an Official implementation ?

Marco van de Voort wrote 14 lines
In our previous episode, Ryan Joseph said: > > Your example is simply broken. A few points: > > Thanks for the description. Yeah, I assumed the stack would be restored but that isn?t the case apparently. > > I think the coroutine implementation in the link below tries to manage the stack frames and restore them but it requires Windows for the VirtualAlloc function and I?m on a Mac. No idea if that code works or not and it looks kind of risky honestly. > > http://www.festra.com/wwwboard/messages/12899.html Not just virtual alloc, nearly the whole of the asm is accessing SEH via fs and thus Windows dependent. _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Marco van de Voort
to Daniel Gaspary

In our previous episode, Daniel Gaspary said:

So.. Any chance of an Official implementation ?

I don't know. Such effort should chiefly come from the people interested in it I guess.

Mark Morgan Lloyd
to Marco van de Voort
Marco van de Voort wrote
In our previous episode, Daniel Gaspary said:> So..> > Any chance of an Official implementation ? I don't know. Such effort should chiefly come from the people interested init I guess.

Turning it around a little: are there still FPC targets that don't have threads? Having coroutines would allow a native thread mechanism to be implemented, without relying on the underlying OS.

Mark Morgan Lloyd markMLl .AT. telemetry.co .DOT. uk [Opinions above are the author's, not those of his employers or colleagues]
Bernd Mueller
to Mark Morgan Lloyd
Mark Morgan Lloyd wrote
Turning it around a little: are there still FPC targets that don't have threads? Having coroutines would allow a native thread mechanism to be implemented, without relying on the underlying OS.

it would be really nice to have coroutines for the embedded targets like AVR and ARM.

Regards, Bernd.

Ryan Joseph
to Bernd Mueller
On Apr 20, 2017, at 3:01 PM, Bernd Mueller wrote: it would be really nice to have coroutines for the embedded targets like AVR and ARM.

What are people using this for btw? Personally I wanted them to solve some problems in game programming where I lots of nested loops that need to be synched with a main loop and can’t block.

Regards,
Ryan Joseph

Marco van de Voort
to Mark Morgan Lloyd: Turning it around a little: are there still FPC targets that don't have threads? Having…

In our previous episode, Mark Morgan Lloyd said:

Show quoted text 5 lines
> I don't know. Such effort should chiefly come from the people interested init I guess. Turning it around a little: are there still FPC targets that don't have threads? Having coroutines would allow a native thread mechanism to be implemented, without relying on the underlying OS.

No, since the code would be for preemptive systems, and the coroutine system is cooperative.

Mark Morgan Lloyd
to Bernd Mueller: it would be really nice to have coroutines for the embedded targets like AVR and ARM.…
Bernd Mueller wrote 6 lines
On 04/20/2017 09:40 AM, Mark Morgan Lloyd wrote:>> Turning it around a little: are there still FPC targets that don't have> threads? Having coroutines would allow a native thread mechanism to be> implemented, without relying on the underlying OS. it would be really nice to have coroutines for the embedded targets like AVR and ARM.

The Topspeed family of languages also demonstrated that a coroutine could be used as the target of an interrupt. However looking at the manual I see that when the interrupt arrived the association was removed, and I can't remember what it did with interrupt acknowledgement etc.

Mark Morgan Lloyd markMLl .AT. telemetry.co .DOT. uk [Opinions above are the author's, not those of his employers or colleagues]
denisgolovan
to Ryan Joseph: What are people using this for btw? Personally I wanted them to solve some problems in…

20.04.2017, 11:43, "Ryan Joseph" :

Show quoted text 5 lines
>  On Apr 20, 2017, at 3:01 PM, Bernd Mueller wrote: > >  it would be really nice to have coroutines for the embedded targets like AVR and ARM. What are people using this for btw? Personally I wanted them to solve some problems in game programming where I lots of nested loops that need to be synched with a main loop and can’t block.

It is especially favorable for game development.
All processes/objects which live across several rendering frames and have any state are greatly simplified by coroutines. e.g. living entities, animations, any game triggers, any finite state machines, etc. Proper implemented coroutines support thousands of instances. With proper (read "mostly functional"/non-shared data) approach coroutines might also be calculated within real threads in parallel.

Another example is network-related code (both client and server code). Break your huge finite state machines into pipeline stages via coroutines and it gets pretty modular, extensive and simple to reason about.

To say to generally, had we had coroutines in first place, no OOP crap would be necessary :) At least in game development.

Regards, Denis Golovan
Mark Morgan Lloyd
to Marco van de Voort: In our previous episode, Mark Morgan Lloyd said: No, since the code would be for…
Marco van de Voort wrote
In our previous episode, Mark Morgan Lloyd said:> > I don't know. Such effort should chiefly come from the people interested init I guess.> > Turning it around a little: are there still FPC targets that don't have > threads? Having coroutines would allow a native thread mechanism to be > implemented, without relying on the underlying OS. No, since the code would be for preemptive systems, and the coroutine systemis cooperative.

But once you have coroutines as a way of managing context it's not particularly difficult to define one which receives control on a timer tick or when nothing else is happening, and that schedules another on the basis of priority or simply round-robin.

Being able to switch context, which in practice means switching the stack frame, is a prerequisite to having threads, just as threads are a prerequisite of having processes.

I don't quite know where exception handling would fit into this.

Mark Morgan Lloyd markMLl .AT. telemetry.co .DOT. uk [Opinions above are the author's, not those of his employers or colleagues]
Ryan Joseph
to denisgolovan: 20.04.2017, 11:43, "Ryan Joseph" : It is especially favorable for game development. All…
On Apr 20, 2017, at 4:06 PM, denisgolovan wrote: Another example is network-related code (both client and server code). Break your huge finite state machines into pipeline stages via coroutines and it gets pretty modular, extensive and simple to reason about.

Nicely encapsulated in that statement. I’m building these big ugly state machines inside my classes and polluting the instance variables with muck. Coroutines would greatly clean this up and with only minimal overhead.

Regards,
Ryan Joseph