Hello Jorg,
On Dec 17, 2019, at 8:30 AM, Jörg F. Wittenberger Joerg.Wittenberger@softeyes.net wrote:
Hi all,
once I wrote a piece of code implementing "software transactional memory". The sugar part of it is in the tests[1]: handler can be attached to the transaction. The latter feature was used to implement a small data flow language embedded in Scheme. I miss it under gambit.
If there was a STM implementation for gambit, which I could start from, please tell me. It could be quite a short cut.
Otherwise I need help to understand three things:
- Atomicity in GVM
To start with I could always just have a global lock instead of lock-free updates. Optimizations might be done later. (Likely crazy, but let's see.)
But I wonder: how are signal handlers actually handled? Can I declare a `for-each` statement in such a way that the GVM sees no signal handler running during execution time? Also, there appears to come some multi threaded GVM our way: will this be the case in the future too?
Background: the heart of the implementation is a list of in-transaction-value records, which need to be written back to the global scope atomically.
There are two thread schedulers implemented by Gambit, the default scheduler multiplexes the Scheme threads on one “processor” and the SMP thread scheduler which uses multiple processors. To get the SMP scheduler you need to: ./configure;make;make bootstrap;make bootclean;./configure --enable-multiple-threaded-vms --enable-smp;make
The approach that will work with both schedulers is to use mutexes to implement critical sections. That way you can guarantee that a thread’s operations in the critical section will not be interleaved with operations from other threads that aquire the same mutex.
With the default thread scheduler, or the SMP thread scheduler when there is a single processor (runtime option -:p1) you can disable the scheduler’s thread preemption interrupt by using the (declare (not interrupts-enabled)) declaration. When that declaration is in effect, the compiler will no longer add interrupt checking code in the generated code. Note however that if there is a call to a procedure that wasn’t compiled with this declaration then the thread scheduler may interrupt its execution. Moreover disabling interrupt checking is tricky because stack overflows are detected using the same mechanism, so you have to make sure the code does not add more than a few stack frames.
- Defining yet another record type. Much like SRFI-9 just two actual
slots per record field, the value and a version tag. Likely doable, but the tricky part: how do I hide the version tag accessors from user code?
Gambit’s define-type can assign various properties to the fields, including whether the field is printed or not, whether the field is read-only, whether its content is checked by the equal? procedure, what the initial value of the field is, etc
A plain definition of a 2d point record can be done like this:
(define-type point x y)
It implicitly defines the procedures make-point, point-copy, point?, point-x, point-x-set!, point-x-set, point-y, point-y-set!, point-y-set. Both fields will be shown when a point is printed:
(make-point 1 2)
#<point #2 x: 1 y: 2>
If you want two other hidden fields for the version tags of x and y, you could define the record like this:
(define-type point x y (x-version unprintable: equality-skip: no-functional-setter: init: #f) (y-version unprintable: equality-skip: no-functional-setter: init: #f))
Then the version fields will not be printed:
(make-point 1 2)
#<point #3 x: 1 y: 2>
However, this implicitly defines the procedures point-x-version, point-x-version-set!, point-y-version, point-y-version-set!.
There are two ways to hide these procedures from users. The first is to use the module system to explicitly indicate the names that should be exported from the module that contains the record definition.
The other way is to use the macros: option to define-type which defines all operations as macros, and the prefix: option that allows specifying a prefix for the implicitly generated names.
(define-type point macros: prefix: macro- x y (x-version unprintable: equality-skip: no-functional-setter: init: #f) (y-version unprintable: equality-skip: no-functional-setter: init: #f))
(define (make-point x y) (macro-make-point x y)) (define (point? x) (macro-point? x)) (define (point-x p) (macro-point-x p)) (define (point-x-set! p x) (macro-point-x-set! p x)) (define (point-y p) (macro-point-y p)) (define (point-y-set! p y) (macro-point-y-set! p y))
Then your module can still use macro-point-x-version, etc to access these fields. Because the macros are not exported from the module these fields cannot be accessed by user code.
- For *each slot* of a record X of type Y defined using the definition
from step 2, I need to be able to create another record ITV[2]. The `for-each` from #1 traverses a ist of ITV checks the version tag from ITV against X's tag and if all goes well, copies the value field from ITV to X.
In the original code, ITV holds a pointer to X and an integer offset into X representation. (A low level chicken feature `##sys#slot`.) Is there something alike in gambit?
Gambit’s define-type expands into definitions that call a few primitive procedures to access records. Here are the unsafe primitives that don’t check the type of the first parameter:
(##unchecked-structure-ref obj i type proc) ;; fetch a field of obj (##unchecked-structure-set! obj val i type proc) ;; store in a field of obj (##unchecked-structure-cas! obj val oldval i type proc) ;; compare-and-swap a field of obj
These primitive procedures ignore the type and proc parameters. Note that the compare-and-swap operation may be useful for your purposes as it is an atomic operation in both thread schedulers.
Here’s a sample use:
(define-type point x y) (define p (make-point 11 22)) (##unchecked-structure-ref p 1 #f #f) ;; field x is at index 1
11
(##unchecked-structure-set! p 77 1 #f #f) ;; set field x
#<point #2 x: 77 y: 22>
p
#<point #2 x: 77 y: 22>
(##unchecked-structure-cas! p 88 77 1 #f #f) ;; will change field x because it contains 77
77
p
#<point #2 x: 88 y: 22>
(##unchecked-structure-cas! p 99 77 1 #f #f) ;; won’t change field x because it no longer contains 77
88
p
#<point #2 x: 88 y: 22>
(##structure-type p) ;; get type descriptor of record p
#<type #3 point>
(##type-fields (##structure-type p)) ;; get vector with field attributes (3 per field)
#(x 0 #f y 0 #f)
Marc