Previous: machinery tail calls, Up: machinery [Index]
We assume the validity of machinery simplifications to focus on some aspect of the runtime behaviour, Simplification assumptions.
Local variables are put on the Scheme stack in such a way that the stack frame can be left immutable if it is freezed in a Scheme continuation object. Immutable local variables and mutable local variables, before the first mutation, are just put in machine words on the stack frame; mutable local variables after the first mutation are put in the single slot of a Scheme vector whose reference is put on the stack.
Let’s consider the following program in which the function one
has only immutable local variables:
(import (rnrs)) (define (one A B) (let ((C 3) (D 4)) (list A B C D))) (one 1 2)
right after the call to one
the arguments are on the stack below
the return address, machinery
locals.
high memory | | |----------------------| -- . . . . caller . . stack frame |----------------------| . | return address | <- FPR . |----------------------| -- | argument A == 1 | . |----------------------| . stack frame | argument B == 2 | . of ONE |----------------------| . | | low memory
Then the local values are put on the stack, right below the arguments, machinery locals.
high memory | | |----------------------| -- . . . . caller . . stack frame |----------------------| . | return address | <- FPR . |----------------------| -- | argument A == 1 | <- FPR - 1*wordsize . |----------------------| . | argument B == 2 | <- FPR - 2*wordsize . |----------------------| . stack frame | local C == 3 | <- FPR - 3*wordsize . of ONE |----------------------| . | local D == 4 | <- FPR - 4*wordsize . |----------------------| . | | low memory
We can imagine the function one
compiled to pseudo–code as
follows:
(define (one stack-slot-1 stack-slot-2) (let-on-stack ((stack-slot-3 3) (stack-slot-4 4)) (list stack-slot-1 stack-slot-2 stack-slot-3 stack-slot-4)))
if the Assembly code needs to copy the value of the local variable
stack-slot-3
into the CPU register AAR, it just does it as
stack memory access:
(movl (disp (* -3 wordsize) FPR) AAR)
If a continuation object is created with this scenario on the stack: everything is ready, because the stack frame never needs to be mutated.
Let’s consider the following program in which the function one
has both an immutable local variable and a mutable one:
(import (rnrs)) (define (one A B) (let ((C 3) (D 4)) (display (list A B C D)) (set! D 41) (display (list A B C D)) (set! D 42) (display (list A B C D)))) (one 1 2)
after the call to one
the arguments and the local variables are
put on the stack below the return address, compiler machinery locals.
high memory | | |----------------------| -- . . . . caller . . stack frame |----------------------| . | return address | <- FPR . |----------------------| -- | argument A == 1 | <- FPR - 1*wordsize . |----------------------| . | argument B == 2 | <- FPR - 2*wordsize . |----------------------| . stack frame | local C == 3 | <- FPR - 3*wordsize . of ONE |----------------------| . | local D == 4 | <- FPR - 4*wordsize . |----------------------| . | | low memory
Before the first local variable mutation (the set!
form): if the
Assembly code needs to copy the value of the local variable
stack-slot-4
into the CPU register AAR, it just does it as
stack memory access:
(movl (disp (* -3 wordsize) FPR) AAR)
because the value is just there. If a continuation object is created with this scenario on the stack: everything is ready, because the stack frame never needs to be mutated.
When the mutable local variable is assigned: something has to change.
We can imagine the function one
compiled to pseudo–code as
follows:
(define (one stack-slot-1 stack-slot-2) (let-on-stack ((stack-slot-3 3) (stack-slot-4 4)) (display (list stack-slot-1 stack-slot-2 stack-slot-3 stack-slot-4)) (set! stack-slot-4 (vector 41)) (display (list stack-slot-1 stack-slot-2 stack-slot-3 ($vector-ref stack-slot-4 0))) ($vector-set! stack-slot-4 0 42) (display (list stack-slot-1 stack-slot-2 stack-slot-3 ($vector-ref stack-slot-4 0)))))
whenever the local variable assignment happens: a Scheme vector is
allocated on the heap and filled with the new variable’s value; then a
reference to such object is stored in the stack slot reserved to the
local variable. From this point onwards: access to the local variable
happens through the primitive operations $vector-ref
and
$vector-set!
.
If a continuation object is created after the local variable first mutation: everything is ready, the stack frame does not need to be mutated because the mutable location is in the Scheme vector.
Mutable argument bindings are handled in the same way of mutable local
variables. Let’s consider the following program in which the function
one
has a mutable argument:
(import (rnrs)) (define (one A) (display A) (set! A 2) (display A) (set! A 3) (display A)) (one 1)
We can imagine the function one
compiled to pseudo–code as
follows:
(define (one stack-slot-1) (display stack-slot-1) (set! stack-slot-1 (vector 2)) (display ($vector-ref stack-slot-1 0)) ($vector-set! stack-slot-1 0 3) (display ($vector-ref stack-slot-1 0))
Previous: machinery tail calls, Up: machinery [Index]