Discussion:
[gambit-list] Marc, (not interrupts-enabled) ramifications and safe practices? Also (not safe)
Adam
2018-11-08 02:14:43 UTC
Permalink
Hi Marc!

Can you please explain the ramifications of (declare (not
interrupts-enabled))?

The effect of (not interrupts-enabled) that I have been aware of, is that
it disables thread switches in the green thread scheduling.

With respect to green thread switching, the same effect as removing the
interrupt sugar, can be achieved by setting the current green thread's
quantum to infinity.

This can be done by (thread-quantum-set! (current-thread) +inf.0) or even
better (##enable-interrupts), and is discussed at
http://gambitscheme.org/wiki/index.php/Using_Gambit_with_External_Libraries#Ensuring_singlethreaded_behaviour
and also further below in this email.

An example of when you want to disable green thread switches, would be when
you have Scheme code that calls C that then calls Scheme, meaning you have
a C stack frame in the stack - Gambit requires the user to rewind C stack
frames in exact reverse order or the program will get into an undefined
state right, and so creating a clean-room environment for guaranteeing that
by disabling thread switching, makes all sense in the world.


Reading the
https://github.com/gambit/gambit/commit/674ddff913cdfb7b3e73c6f78f3f034521982dc3
commit, where you fixed a stack overflow bug by changing a

(define (p) (declare (not interrupts-enabled)) ... (let () ... result))


to

(define (p) (declare (not interrupts-enabled)) ... (let () ... (let ()
(declare (interrupts-enabled) result)))


, I'm realizing that there may be more ramifications to (not
interrupts-enabled) namely that as a performance measure Gambit has
delegated some Scheme stack frame (aka msection) management logics to the
interrupt handler.

Is this so?

If so, what are safe coding practices relating to (not interrupts-enabled)?

Can a user ever have a reason to use it?

Is it actually a dangerous option that shouldn't be used by normal users??

The question "how much memory is available in an msection so that I can
write code that won't overflow it" is mostly irrelevant as the user *not* can
manage msection location anyhow e.g. there's no
##flush-msection-give-me-a-new-fresh-one! , right?

Can you direct me to where in the code the interrupt code sugar is located
(e.g. in standard library or in the compiler code)?

Brad mentioned that none of the code in _num that has (not
interrupts-enabled) "not does any allocations whatsoever".


Apart from the interrupts-enabled declare, two procedures are available
called (##disable-interrupts) and (##enable-interrupts) /
___EXT(___disable_interrupts)() and ___EXT(___enable_interrupts)().

While their names overlap with the declare, these are actually functionally
separate altogether from the declare, and have the effect of
(thread-quantum-set! (current-thread) +inf.0) and resetting the green
thread quantum back to its previous value, right?


Meanwhile, (not safe) only has the effect of skipping production of type
checks on variable accesses and it has no other effects than that, right?

Thanks,
Adam
Marc Feeley
2018-11-08 02:51:47 UTC
Permalink
With the declaration

(declare (interrupts-enabled))

, which is the default, the Gambit compiler will insert “polling instructions” throughout the generated code and guarantees that no more than a fixed small constant stack space will be allocated between the execution of successive polling instructions.

Polling instructions check for a multitude of events:

- stack close to overflowing (causing a new stack section to be allocated or the GC triggered)
- interprocessor request to start a GC (or other operation requiring all processors to synchronize)
- heartbeat timer interrupt (to notify the green thread scheduler it is time to switch threads)
- ctrl-C interrupt (causing a REPL to be started if enabled)

With the declaration

(declare (not interrupts-enabled))

the Gambit compiler does not generate the polling instructions. This makes the code slightly faster (no interrupt checks are done) and the code in the scope of that declaration can’t be interrupted. This can be useful to implement a cheap critical section (no green thread context switch possible, or ctrl-C). However, the code in the scope of that declaration should not do a large number of non-tail calls otherwise the current stack section might overflow and corrupt memory. So “normal users” should not be using this declaration.

The bug in thread-receive that was fixed by commit https://github.com/gambit/gambit/commit/674ddff913cdfb7b3e73c6f78f3f034521982dc3 was that interrupts were disabled at a point where stack space was allocated, causing a stack overflow in some cases. A polling instruction was added to make sure this doesn’t happen.

Marc
Post by Adam
Hi Marc!
Can you please explain the ramifications of (declare (not interrupts-enabled))?
The effect of (not interrupts-enabled) that I have been aware of, is that it disables thread switches in the green thread scheduling.
With respect to green thread switching, the same effect as removing the interrupt sugar, can be achieved by setting the current green thread's quantum to infinity.
This can be done by (thread-quantum-set! (current-thread) +inf.0) or even better (##enable-interrupts), and is discussed at http://gambitscheme.org/wiki/index.php/Using_Gambit_with_External_Libraries#Ensuring_singlethreaded_behaviour and also further below in this email.
An example of when you want to disable green thread switches, would be when you have Scheme code that calls C that then calls Scheme, meaning you have a C stack frame in the stack - Gambit requires the user to rewind C stack frames in exact reverse order or the program will get into an undefined state right, and so creating a clean-room environment for guaranteeing that by disabling thread switching, makes all sense in the world.
Reading the https://github.com/gambit/gambit/commit/674ddff913cdfb7b3e73c6f78f3f034521982dc3 commit, where you fixed a stack overflow bug by changing a
(define (p) (declare (not interrupts-enabled)) ... (let () ... result))
to
(define (p) (declare (not interrupts-enabled)) ... (let () ... (let () (declare (interrupts-enabled) result)))
, I'm realizing that there may be more ramifications to (not interrupts-enabled) namely that as a performance measure Gambit has delegated some Scheme stack frame (aka msection) management logics to the interrupt handler.
Is this so?
If so, what are safe coding practices relating to (not interrupts-enabled)?
Can a user ever have a reason to use it?
Is it actually a dangerous option that shouldn't be used by normal users??
The question "how much memory is available in an msection so that I can write code that won't overflow it" is mostly irrelevant as the user not can manage msection location anyhow e.g. there's no ##flush-msection-give-me-a-new-fresh-one! , right?
Can you direct me to where in the code the interrupt code sugar is located (e.g. in standard library or in the compiler code)?
Brad mentioned that none of the code in _num that has (not interrupts-enabled) "not does any allocations whatsoever".
Apart from the interrupts-enabled declare, two procedures are available called (##disable-interrupts) and (##enable-interrupts) / ___EXT(___disable_interrupts)() and ___EXT(___enable_interrupts)().
While their names overlap with the declare, these are actually functionally separate altogether from the declare, and have the effect of (thread-quantum-set! (current-thread) +inf.0) and resetting the green thread quantum back to its previous value, right?
Meanwhile, (not safe) only has the effect of skipping production of type checks on variable accesses and it has no other effects than that, right?
Thanks,
Adam
Adam
2018-11-08 15:05:15 UTC
Permalink
Hi Marc,

Thank you very much for explaining.

Where in the runtime or compiler code is the polling instruction logic?

You mention that a "cheap critical section" by (not interrupts-enabled)
cannot do a "large number of non-tail calls", what is a large number here?

(For context, all heap-allocated objects in a stack are actually stored on
the heap which is allocated separately, so what occupies msection space is
variable slots and other stack frame data only right?)

Does the switch from (declare (not interrupts-enabled)) to (declare
(interrupts-enabled)) force generation of a polling instruction, or could a
stack overflow happen at a stack allocation taking place just right after a
switch from (declare (not interrupts-enabled)) to (declare
(interrupts-enabled)) e.g.

(let ()
(declare (not interrupts-enabled))
[consume stack space just up to right under the mstack's end]
(declare (interrupts-enabled))
normal stack value allocation here e.g. a non-tail call ; <-- here?
returnvalue)



Last, in contrast with interrupts-enabled, the (not safe) declare is about
typechecking only right?

Thanks again,
Adam
Post by Marc Feeley
With the declaration
(declare (interrupts-enabled))
, which is the default, the Gambit compiler will insert “polling
instructions” throughout the generated code and guarantees that no more
than a fixed small constant stack space will be allocated between the
execution of successive polling instructions.
- stack close to overflowing (causing a new stack section to be allocated
or the GC triggered)
- interprocessor request to start a GC (or other operation requiring all
processors to synchronize)
- heartbeat timer interrupt (to notify the green thread scheduler it is
time to switch threads)
- ctrl-C interrupt (causing a REPL to be started if enabled)
With the declaration
(declare (not interrupts-enabled))
the Gambit compiler does not generate the polling instructions. This
makes the code slightly faster (no interrupt checks are done) and the code
in the scope of that declaration can’t be interrupted. This can be useful
to implement a cheap critical section (no green thread context switch
possible, or ctrl-C). However, the code in the scope of that declaration
should not do a large number of non-tail calls otherwise the current stack
section might overflow and corrupt memory. So “normal users” should not be
using this declaration.
The bug in thread-receive that was fixed by commit
https://github.com/gambit/gambit/commit/674ddff913cdfb7b3e73c6f78f3f034521982dc3
was that interrupts were disabled at a point where stack space was
allocated, causing a stack overflow in some cases. A polling instruction
was added to make sure this doesn’t happen.
Marc
Post by Adam
Hi Marc!
Can you please explain the ramifications of (declare (not
interrupts-enabled))?
Post by Adam
The effect of (not interrupts-enabled) that I have been aware of, is
that it disables thread switches in the green thread scheduling.
Post by Adam
With respect to green thread switching, the same effect as removing the
interrupt sugar, can be achieved by setting the current green thread's
quantum to infinity.
Post by Adam
This can be done by (thread-quantum-set! (current-thread) +inf.0) or
even better (##enable-interrupts), and is discussed at
http://gambitscheme.org/wiki/index.php/Using_Gambit_with_External_Libraries#Ensuring_singlethreaded_behaviour
and also further below in this email.
Post by Adam
An example of when you want to disable green thread switches, would be
when you have Scheme code that calls C that then calls Scheme, meaning you
have a C stack frame in the stack - Gambit requires the user to rewind C
stack frames in exact reverse order or the program will get into an
undefined state right, and so creating a clean-room environment for
guaranteeing that by disabling thread switching, makes all sense in the
world.
Post by Adam
Reading the
https://github.com/gambit/gambit/commit/674ddff913cdfb7b3e73c6f78f3f034521982dc3
commit, where you fixed a stack overflow bug by changing a
Post by Adam
(define (p) (declare (not interrupts-enabled)) ... (let () ... result))
to
(define (p) (declare (not interrupts-enabled)) ... (let () ... (let ()
(declare (interrupts-enabled) result)))
Post by Adam
, I'm realizing that there may be more ramifications to (not
interrupts-enabled) namely that as a performance measure Gambit has
delegated some Scheme stack frame (aka msection) management logics to the
interrupt handler.
Post by Adam
Is this so?
If so, what are safe coding practices relating to (not
interrupts-enabled)?
Post by Adam
Can a user ever have a reason to use it?
Is it actually a dangerous option that shouldn't be used by normal
users??
Post by Adam
The question "how much memory is available in an msection so that I can
write code that won't overflow it" is mostly irrelevant as the user not can
manage msection location anyhow e.g. there's no
##flush-msection-give-me-a-new-fresh-one! , right?
Post by Adam
Can you direct me to where in the code the interrupt code sugar is
located (e.g. in standard library or in the compiler code)?
Post by Adam
Brad mentioned that none of the code in _num that has (not
interrupts-enabled) "not does any allocations whatsoever".
Post by Adam
Apart from the interrupts-enabled declare, two procedures are available
called (##disable-interrupts) and (##enable-interrupts) /
___EXT(___disable_interrupts)() and ___EXT(___enable_interrupts)().
Post by Adam
While their names overlap with the declare, these are actually
functionally separate altogether from the declare, and have the effect of
(thread-quantum-set! (current-thread) +inf.0) and resetting the green
thread quantum back to its previous value, right?
Post by Adam
Meanwhile, (not safe) only has the effect of skipping production of type
checks on variable accesses and it has no other effects than that, right?
Post by Adam
Thanks,
Adam
Loading...