Marc is likely in a better position to comment, and I hope I don't misunderstand your questions, but I'll give it a go.
On Oct 12, 2006, at 9:48 AM, Christian wrote:
I'm wondering: in which step of the compilation phase are inlinings of the kind that you are suggesting done? Is it in the code generation step? So will it inline the code *always*, even multiple times in the same compilation block, and not be subject to the inlining-limit declaration?
If (declare (runtime-bindings)) (which is the default), then each occurrence of the primitive will be inlined.
If, in addition, (declare (standard-bindings)) is in effect, then there will be no check that the current value of the global variable is indeed the primitive.
Finally, if (declare (not safe)) is in effect, then there will be no check that the arguments are suitable for the primitive.
And the behavior of some primitives changes if some numerical declarations are in effect; in particular, eqv? is changed to eq? if (declare (fixnum))
If so, wouldn't it be better to change that to actually create a function, but in the same block, and let the function inliner decide how to handle that?
I don't know that there's an absolute answer to that question.
Handling "optional inlining" is something I plan to do in chjmodule (I'm continuing to work on it currently), so that one can export functions from some module A as "inlinable", which means that they will be copied verbatim (but within the original namespace, by putting a ##namespace declaration around/into it) into caller modules, so e.g. module B importing module A will contain such a copy in it's toplevel. That doesn't necessarily mean that it is being inlined into every call site inside B: it's open to the normal Gambit inliner. At the very least the costs drop from ~200 to 30-40 cycles.
This seems similar to (declare (not core)), which makes a function available for inlining, but does not include a separate copy of the function in the file. It's used a lot in the files in the gsc directory, in the macro include-adt, which is defined in fixnum.scm.
If you want some information of what beta-20 inlines by default, take the test-code.scm I sent in my previous e-mail and run it through
euler-35% gsc -c -expansion test-code.scm > ! crap.scm
This expands macros and shows some of the default inlining, so that
(define (test-map-2 x0 x1) (map x0 x1))
gets expanded to
(define test-map-2 (lambda (x0 x1) (if (and ('#<procedure #2 ##eq?> map '#<procedure #185 map>) ('#<procedure #184 ##procedure?> x0)) (letrec ((loop2.248 (lambda (x0 lst2.249) (if ('#<procedure #7 ##pair?> lst2.249) (let ((x.250 (x0 ('#<procedure #11 ##car> lst2.249)))) ('#<procedure #9 ##cons> x.250 (loop2.248 x0 ('#<procedure #13 ##cdr> lst2.249)))) '())))) (loop2.248 x0 x1)) (map x0 x1))))
and
(define (test-s8vector-set!-3 x0 x1 x2) (s8vector-set! x0 x1 x2))
gets expanded to
(define test-s8vector-set!-3 (lambda (x0 x1 x2) (if (and ('#<procedure #2 ##eq?> s8vector-set! '#<procedure #287 s8vector-set!>) (and (and ('#<procedure #282 ##s8vector?> x0) ('#<procedure #171 ##subtyped-mutable?> x0)) (and (and ('#<procedure #60 ##fixnum?> x1) (and ('#<procedure #71 ##fx<=> 0 x1) ('#<procedure #65 ##fx<> x1 ('#<procedure #284 ##s8vector- length> x0)))) (and ('#<procedure #60 ##fixnum?> x2) (and ('#<procedure #71 ##fx<=> -128 x2) ('#<procedure #71 ##fx<=> x2 127)))))) ('#<procedure #288 ##s8vector-set!> x0 x1 x2) (s8vector-set! x0 x1 x2))))
with (declare (standard-bindings) (extended-bindings)) (I needed to comment out test-##return-dynamic-env-bind?-1 and test-##procedure- info-1), these are expanded to
(define test-map-2 (lambda (x0 x1) (if ('#<procedure #150 ##procedure?> x0) (letrec ((loop2.228 (lambda (x0 lst2.229) (if ('#<procedure #7 ##pair?> lst2.229) (let ((x.230 (x0 ('#<procedure #9 ##car> lst2.229)))) ('#<procedure #8 ##cons> x.230 (loop2.228 x0 ('#<procedure #10 ##cdr> lst2.229)))) '())))) (loop2.228 x0 x1)) (map x0 x1))))
and
(define test-s8vector-set!-3 (lambda (x0 x1 x2) (if (and (and ('#<procedure #225 ##s8vector?> x0) ('#<procedure #130 ##subtyped-mutable?> x0)) (and (and ('#<procedure #35 ##fixnum?> x1) (and ('#<procedure #43 ##fx<=> 0 x1) ('#<procedure #39 ##fx<> x1 ('#<procedure #228 ##s8vector-length> x0)))) (and ('#<procedure #35 ##fixnum?> x2) (and ('#<procedure #43 ##fx<=> -128 x2) ('#<procedure #43 ##fx<=> x2 127))))) ('#<procedure #230 ##s8vector-set!> x0 x1 x2) (s8vector-set! x0 x1 x2))))
and with (declare (standard-bindings)(extended-bindings)(not safe))
(define test-map-2 (lambda (x0 x1) (letrec ((loop2.94 (lambda (x0 lst2.95) (if ('#<procedure #7 ##pair?> lst2.95) (let ((x.96 (x0 ('#<procedure #9 ##car> lst2.95)))) ('#<procedure #8 ##cons> x.96 (loop2.94 x0 ('#<procedure #10 ##cdr> lst2.95)))) '())))) (loop2.94 x0 x1))))
and
(define test-s8vector-set!-3 (lambda (x0 x1 x2) ('#<procedure #260 ##s8vector-set!> x0 x1 x2)))
I think my suggested expansion of eqv? is at least comparable to these expansions.
Using such module techniques, instead of hard coding functions like |equal?| into Gambit, they could be defined in a standard module like "R5RS" with an inlinable export flag and be imported from there.
(BTW one could also split |equal?| like that:
(define (equal? a b) (or (eq? a b) (real-equal? a b))) (define (real-equal? a b) ..recursive definition))
and so make it possible that the first part, which doesn't generate much code bloat, can be inlined whereas the rest still isn't.
This definition of equal? doesn't help much if most often two things are not equal?, because there is not a quick test that (equal? a b) is false. Better might be
(define (equal? a b) (or (eq? a b) (and (subtyped? a) (subtyped? b) (real-equal? a b))))
Brad