local buffers and tail-call optimization

This is a discussion on local buffers and tail-call optimization within the Forth forums in Programming Languages category; If a Forth system supports local buffers (or another locals extension that produces addresses of local memory, e.g., Gforth's variable-flavoured locals), then it has to define the lifetime of the memory the address points to. Typically the lifetime will be until the end of the definition that the buffer was defined in. This inhibits tail-call optimization; consider: : bar ( addr -- ) ... ; : foo { | [ 1 ] x } x bar ; in the syntax proposed by Stephen Pelc or : foo 1 lbuffer bar ; in a syntax suggested by others. The allocated buffer ...

Go Back   Application Development Forum > Programming Languages > Forth

Object Mix

Register FAQ Calendar Search Today's Posts Mark Forums Read
  #1  
Old 08-29-2008, 08:20 AM
Anton Ertl
Guest
 
Default local buffers and tail-call optimization

If a Forth system supports local buffers (or another locals extension
that produces addresses of local memory, e.g., Gforth's
variable-flavoured locals), then it has to define the lifetime of the
memory the address points to. Typically the lifetime will be until
the end of the definition that the buffer was defined in.

This inhibits tail-call optimization; consider:

: bar ( addr -- )
... ;

: foo { | [ 1 ] x } x bar ;

in the syntax proposed by Stephen Pelc or

: foo 1 lbuffer bar ;

in a syntax suggested by others. The allocated buffer must stay alive
across the call to BAR, because it is used in BAR, so the deallocation
of the buffer must happen after the call to BAR, and BAR is no longer
a tail-call and can therefore not be optimized.

Consider the contrast to ordinary (value-flavoured locals):

: flip { a } a bar ;

Here the local can be deallocated right after its last use, before the
call to BAR, and therefore that call is a tail-call. More generally,
as long as there are only value-flavoured locals in the definition,
the rules for tail-call optimization are the same as without locals.

So it's a good idea to define local buffers in a way that does not
make it too hard for the compiler to know whether a definition
contains a local buffer definition or not. Stephen Pelc's proposal
has this property. As for LBUFFER, if we want it, we should specify it
as a compile-only word.

- anton
--
M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
New standard: http://www.forth200x.org/forth200x.html
EuroForth 2008: http://www.euroforth.org/ef08.html
Reply With Quote
  #2  
Old 08-29-2008, 10:34 AM
Stephen Pelc
Guest
 
Default Re: local buffers and tail-call optimization

On Fri, 29 Aug 2008 12:20:25 GMT, anton@mips.complang.tuwien.ac.at
(Anton Ertl) wrote:

>If a Forth system supports local buffers (or another locals extension
>that produces addresses of local memory, e.g., Gforth's
>variable-flavoured locals), then it has to define the lifetime of the
>memory the address points to. Typically the lifetime will be until
>the end of the definition that the buffer was defined in.
>
>This inhibits tail-call optimization; consider:
>
>: bar ( addr -- )
> ... ;
>
>: foo { | [ 1 ] x } x bar ;
>
>in the syntax proposed by Stephen Pelc or
>
>: foo 1 lbuffer bar ;


In practice this not very important, as (especially with
buffers as opposed to values) bar will manipulate the buffer
from which a result will be extracted.

: foo { | [ /struct ] x }
x bar x manipulate x .bField @
;

is a more realistic set of operations, which would inhibit
tail call optimisation anyway, but does not inhibit VFX's
source inliner, iForth's tokeniser, or binary inliners.

Given the ingenuity of programmers, and widespread recognition
that return addresses are on the return stack (despite what
any standard may say), determining the routines that are safe
for tail optimisation is a non-trivial exercise, as any return
stack manipulation propagates in the call tree. Practical use
trumps any standard IMHO.

Stephen


--
Stephen Pelc, stephenXXX@mpeforth.com
MicroProcessor Engineering Ltd - More Real, Less Time
133 Hill Lane, Southampton SO15 5AF, England
tel: +44 (0)23 8063 1441, fax: +44 (0)23 8033 9691
web: http://www.mpeforth.com - free VFX Forth downloads
Reply With Quote
  #3  
Old 08-30-2008, 04:02 AM
Anton Ertl
Guest
 
Default Re: local buffers and tail-call optimization

stephenXXX@mpeforth.com (Stephen Pelc) writes:
>On Fri, 29 Aug 2008 12:20:25 GMT, anton@mips.complang.tuwien.ac.at
>(Anton Ertl) wrote:
>
>>If a Forth system supports local buffers (or another locals extension
>>that produces addresses of local memory, e.g., Gforth's
>>variable-flavoured locals), then it has to define the lifetime of the
>>memory the address points to. Typically the lifetime will be until
>>the end of the definition that the buffer was defined in.
>>
>>This inhibits tail-call optimization; consider:
>>
>>: bar ( addr -- )
>> ... ;
>>
>>: foo { | [ 1 ] x } x bar ;
>>
>>in the syntax proposed by Stephen Pelc or
>>
>>: foo 1 lbuffer bar ;

>
>In practice this not very important, as (especially with
>buffers as opposed to values) bar will manipulate the buffer
>from which a result will be extracted.
>
>: foo { | [ /struct ] x }
> x bar x manipulate x .bField @
>;


Another practical use would be to put something in the buffer that is
then read by the callee, e.g.:

: emit ( c -- )
1 lbuffer tuck c!
1 type ;

Here the call to TYPE looks like a tail-call, but it isn't.

>Given the ingenuity of programmers, and widespread recognition
>that return addresses are on the return stack (despite what
>any standard may say), determining the routines that are safe
>for tail optimisation is a non-trivial exercise, as any return
>stack manipulation propagates in the call tree.


It's non-trivial, but doable to analyse it if you want to go for that.
The other option is to make it the programmers' responsibility to turn
off tail-call optimization and inliners if they do return-address
manipulation stuff.

> Practical use
>trumps any standard IMHO.


Sure, but I don't think that return-address manipulation is used
widely enough to justify any more effort than to provide a switch for
turning off optimizations that don't agree with them.

- anton
--
M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
New standard: http://www.forth200x.org/forth200x.html
EuroForth 2008: http://www.euroforth.org/ef08.html
Reply With Quote
  #4  
Old 08-30-2008, 04:49 AM
Peter Fälth
Guest
 
Default Re: local buffers and tail-call optimization

On Aug 30, 10:02*am, an...@mips.complang.tuwien.ac.at (Anton Ertl)
wrote:
> stephen...@mpeforth.com (Stephen Pelc) writes:
> >On Fri, 29 Aug 2008 12:20:25 GMT, an...@mips.complang.tuwien.ac.at
> >(Anton Ertl) wrote:

>
> >>If a Forth system supports local buffers (or another locals extension
> >>that produces addresses of local memory, e.g., Gforth's
> >>variable-flavoured locals), then it has to define the lifetime of the
> >>memory the address points to. *Typically the lifetime will be until
> >>the end of the definition that the buffer was defined in.

>
> >>This inhibits tail-call optimization; consider:

>
> >>: bar ( addr -- )
> >> *... ;

>
> >>: foo { | [ 1 ] x } x bar ;

>
> >>in the syntax proposed by Stephen Pelc or

>
> >>: foo 1 lbuffer bar ;

>
> >In practice this not very important, as (especially with
> >buffers as opposed to values) bar will manipulate the buffer
> >from which a result will be extracted.

>
> >: foo { | [ /struct ] x }
> > *x bar *x manipulate *x .bField @
> >;

>
> Another practical use would be to put something in the buffer that is
> then read by the callee, e.g.:
>
> : emit ( c -- )
> *1 lbuffer tuck c!
> *1 type ;
>
> Here the call to TYPE looks like a tail-call, but it isn't.


In my syntax this becomes

: emit2 ( c -- )
1 lbuffer: tmp
tmp c!
tmp 1 type ;

The code generated is

see emit2
A490FC 40787F 33 C80000 5 normal EMIT2

40787F 8D6424FC lea esp , [esp-4h]
407883 8D0424 lea eax , [esp]
407886 8818 mov [eax] , bl
407888 8D1C24 lea ebx , [esp]
40788B 895DFC mov [ebp-4h] , ebx
40788E BB01000000 mov ebx , # 1h
407893 8D6DFC lea ebp , [ebp-4h]
407896 E89198FFFF call TYPE
40789B 8D642404 lea esp , [esp+4h]
40789F C3 ret near
ok

The local buffer prevents a tail jmp as the buffer needs to be
released

>
> >Given the ingenuity of programmers, and widespread recognition
> >that return addresses are on the return stack (despite what
> >any standard may say), determining the routines that are safe
> >for tail optimisation is a non-trivial exercise, as any return
> >stack manipulation propagates in the call tree.

>
> It's non-trivial, but doable to analyse it if you want to go for that.
> The other option is to make it the programmers' responsibility to turn
> off tail-call optimization and inliners if they do return-address
> manipulation stuff.


This is what I do with the word NO-TAIL-JMP

: t1 r> r@ 32 dump >r ; no-tail-jmp

: t2 t1 ;
see t2
A49128 4078D0 6 C80000 5 normal T2

4078D0 E8CBFFFFFF call T1
4078D5 C3 ret near
ok

but it don't propagate up the chain

: t3 t2 ; ok
see t3
A4913C 4078D6 5 C80000 5 normal T3

4078D6 E9F5FFFFFF jmp T2
ok

The problem with inlining is even simpler to solve. My system does not
do automatic inlining! You have to declare it manually with :m

:m t4 t3 ; ok
see t4
A49150 C885DA 0 A49164 5 macro T4
t3
ok
: t5 t4 ; ok
see t5
A4916C 4078DB 5 C80000 5 normal T5

4078DB E9F6FFFFFF jmp T3
ok

Peter

> > Practical use
> >trumps any standard IMHO.

>
> Sure, but I don't think that return-address manipulation is used
> widely enough to justify any more effort than to provide a switch for
> turning off optimizations that don't agree with them.
>
> - anton
> --
> M. Anton Ertl *http://www.complang.tuwien.ac.at/anton/home.html
> comp.lang.forth FAQs:http://www.complang.tuwien.ac.at/forth/faq/toc.html
> * * *New standard:http://www.forth200x.org/forth200x.html
> * *EuroForth 2008:http://www.euroforth.org/ef08.html


Reply With Quote
  #5  
Old 09-03-2008, 02:22 PM
Andrew Haley
Guest
 
Default Re: local buffers and tail-call optimization

Anton Ertl <anton@mips.complang.tuwien.ac.at> wrote:
> If a Forth system supports local buffers (or another locals extension
> that produces addresses of local memory, e.g., Gforth's
> variable-flavoured locals), then it has to define the lifetime of the
> memory the address points to. Typically the lifetime will be until
> the end of the definition that the buffer was defined in.


> This inhibits tail-call optimization; consider:


> : bar ( addr -- )
> ... ;


> : foo { | [ 1 ] x } x bar ;


> in the syntax proposed by Stephen Pelc or


> : foo 1 lbuffer bar ;


> in a syntax suggested by others. The allocated buffer must stay
> alive across the call to BAR, because it is used in BAR, so the
> deallocation of the buffer must happen after the call to BAR, and
> BAR is no longer a tail-call and can therefore not be optimized.


Sure it can. With one fairly obvious implementation of local buffers,
you push onto the return stack the address of a thunk that will
deallocate the buffer. At the end of the definition you can jump to
bar; when bar exits it returns to the thunk, which does the
deallocation.

Andrew.
Reply With Quote
  #6  
Old 09-04-2008, 02:07 PM
Albert van der Horst
Guest
 
Default Re: local buffers and tail-call optimization

In article <evWdnRDf97RBSCPVnZ2dnUVZ8qLinZ2d@supernews.com> ,
Andrew Haley <andrew29@littlepinkcloud.invalid> wrote:
>Anton Ertl <anton@mips.complang.tuwien.ac.at> wrote:
>> If a Forth system supports local buffers (or another locals extension
>> that produces addresses of local memory, e.g., Gforth's
>> variable-flavoured locals), then it has to define the lifetime of the
>> memory the address points to. Typically the lifetime will be until
>> the end of the definition that the buffer was defined in.

>
>> This inhibits tail-call optimization; consider:

>
>> : bar ( addr -- )
>> ... ;

>
>> : foo { | [ 1 ] x } x bar ;

>
>> in the syntax proposed by Stephen Pelc or

>
>> : foo 1 lbuffer bar ;

>
>> in a syntax suggested by others. The allocated buffer must stay
>> alive across the call to BAR, because it is used in BAR, so the
>> deallocation of the buffer must happen after the call to BAR, and
>> BAR is no longer a tail-call and can therefore not be optimized.

>
>Sure it can. With one fairly obvious implementation of local buffers,
>you push onto the return stack the address of a thunk that will
>deallocate the buffer. At the end of the definition you can jump to
>bar; when bar exits it returns to the thunk, which does the
>deallocation.


This is automatical with coroutine calls

: R-ALLOT R> SWAP (R-ALLOT) >R CO (R-DALLOT) ;

Where (R-ALLOT) is a code word that adds to the return stack pointer.

The buffer is to be found in the calling word at a cell offset
from the return stack pointer, typical I.
This is intended for systems with a true return stack.

With an S-stack ala tforth/iforth (or a fake return stack) it is even
easier:

: R-ALLOT (S-ALLOT) CO (S-DALLOT) ;

>
>Andrew.


P.S. I'm opposed to allocating space on a 64 k return stack if there
is a 2 Gbyte data stack, but that is an other story.

Groetjes Albert

--
--
Albert van der Horst, UTRECHT,THE NETHERLANDS
Economic growth -- like all pyramid schemes -- ultimately falters.
albert@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst

Reply With Quote
Reply


Thread Tools
Display Modes


All times are GMT -5. The time now is 02:37 AM.


Powered by vBulletin® Version 3.7.2
Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.2.0
vB Ad Management by =RedTyger=

In an effort to better serve ads to our visitors, cookies are used on objectmix.com. For more information, check out our Privacy Policy.