Many DEFPROC, 1 ENDPROC?

Discuss all aspects of programming here. From 8-bit through to modern architectures.
Post Reply
User avatar
sydney
Posts: 2228
Joined: Wed May 18, 2005 9:09 am
Location: Newcastle upon Tyne
Contact:

Many DEFPROC, 1 ENDPROC?

Post by sydney » Mon Oct 02, 2017 1:58 pm

Is it OK to have a procedure defined within a procedure so you have two DEFPROCs but only one ENDPROC. It seems to work OK in my example below.

Code: Select all

   10 PROCThat
   20 PROCThisAndThat
   30 END
   40 DEFPROCThisAndThat
   50 PRINT "This And ";
   60 DEFPROCThat    
   70 PRINT "That"
   80 ENDPROC

crj
Posts: 834
Joined: Thu May 02, 2013 4:58 pm
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by crj » Mon Oct 02, 2017 2:49 pm

Yes, that's fine. At least as far as the interpreter is concerned, even if not to human aesthetics. You might at least have put a comment there alerting people to what was happening!

You could even (ugh!) GOTO an ENDPROC somewhere completely different in the program.

alex_farlie
Posts: 99
Joined: Sun Jul 07, 2013 9:46 pm
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by alex_farlie » Mon Oct 02, 2017 3:46 pm

crj wrote:Yes, that's fine. At least as far as the interpreter is concerned, even if not to human aesthetics. You might at least have put a comment there alerting people to what was happening!

You could even (ugh!) GOTO an ENDPROC somewhere completely different in the program.
It's even possible in some versions of BBC Basic to do "overloading"

Code: Select all

DEFFNmy_func(a%):func%=0
DEFNmy_func(a%,func%):CASE func OF 
WHEN 0: ....
....
ENDCASE
=0
But this may only be true of versions on PC.... Not sure if you could do tricks like that in BASIC V on RISC OS.

User avatar
jgharston
Posts: 3211
Joined: Thu Sep 24, 2009 11:22 am
Location: Whitby/Sheffield
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by jgharston » Mon Oct 02, 2017 3:53 pm

Yep, perfectly normal, such as this (DEFFNs in this example):

Code: Select all

      DEFFNNet_TxCount(Stn%,Ctrl%,Port%,Addr%,Len%,RAddr%,Try%,Delay%):LOCAL TxErr%
      DEFFNNet_Tx(Stn%,Ctrl%,Port%,Addr%,Len%,RAddr%):LOCAL TxErr%,Try%,Delay%:Try%=10:Delay%=50
      X%?1=Port%:X%!2=Stn%:X%!4=Addr%:X%!8=Addr%+Len%:X%!12=RAddr%
similarly, you can have multiple ENDPROCs (= in this example):

Code: Select all

      REPEAT:REPEAT:X%?0=Ctrl%:A%=&10:CALL &FFF1:UNTILX%?0 :REM Loop until Tx starts
      IFStn%=&FFFF:UNTIL TRUE:=0                           :REM Broadcast
        REPEAT:TxErr%=FNbyte(&32,0,0):UNTILTxErr%<&80      :REM Loop until complete
        IFTxErr%=&41 OR TxErr%=&42:IFTry%:A%=TIME+Delay%:REPEATUNTILTIME>A%:Try%=Try%-1
      UNTILNOT(TxErr%=&41 OR TxErr%=&42) OR Try%<1:=TxErr%

Code: Select all

$ bbcbasic
PDP11 BBC BASIC IV Version 0.25
(C) Copyright J.G.Harston 1989,2005-2015
>_

crj
Posts: 834
Joined: Thu May 02, 2013 4:58 pm
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by crj » Tue Oct 03, 2017 4:32 am

jgharston wrote:Yep, perfectly normal
Note that alex_farlie's example showed multiple definitions of the same function name with different numbers of parameters. I'm pretty sure that didn't work on the Beeb, and expect I'd have noticed it the Arc had added it.

alex_farlie
Posts: 99
Joined: Sun Jul 07, 2013 9:46 pm
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by alex_farlie » Tue Oct 03, 2017 9:19 am

alex_farlie wrote:
crj wrote:Yes, that's fine. At least as far as the interpreter is concerned, even if not to human aesthetics. You might at least have put a comment there alerting people to what was happening!

You could even (ugh!) GOTO an ENDPROC somewhere completely different in the program.
It's even possible in some versions of BBC Basic to do "overloading"

Code: Select all

DEFFNmy_func(a%):func%=0
DEFNmy_func(a%,func%):CASE func OF 
WHEN 0: ....
....
ENDCASE
=0
But this may only be true of versions on PC.... Not sure if you could do tricks like that in BASIC V on RISC OS.


Just tested and the above as


10 :
20 PRINT FNa(1,0)
30 PRINT FNa(1,2,3)
40
50 END
60
70
80
90
100 DEFFNa(a%,b%):c%=0
110 DEFFNa(A%,b%,c%)
120

example isn't possible on BB4W... I get an Incorrect Arguments error. So I was wrong.

User avatar
Richard Russell
Posts: 503
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by Richard Russell » Tue Oct 03, 2017 11:33 am

alex_farlie wrote:It's even possible in some versions of BBC Basic to do "overloading"
As far as I'm aware the only 'overloading' you can do is of a function or procedure with no parameters and one of the same name with one or more parameters. This works - at least in my versions - because the opening parenthesis is treated as part of the function/procedure name, exactly as it is in the case of an array (indeed the same code is used). So just as name and name() are independent, so are PROCname and PROCname().

But I frequently use the multiple DEFPROC one ENDPROC technique in this sort of way:

Code: Select all

      DEF PROC1 : LOCAL flag : flag = 1
      DEF PROC2 : LOCAL flag : flag = 2
      DEF PROC3 : LOCAL flag : flag = 3
      ... shared code, which tests 'flag'
      ENDPROC
This is a really useful feature, if only in avoiding the use of the dreaded (and slow) GOTO.

Richard.

User avatar
Rich Talbot-Watkins
Posts: 1352
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by Rich Talbot-Watkins » Tue Oct 03, 2017 12:12 pm

I've been writing C and C++ for so long now that this just looks like a monstrosity compared to:

Code: Select all

DEF PROC1:PROCa(1):ENDPROC
DEF PROC2:PROCa(2):ENDPROC
DEF PROC3:PROCa(3):ENDPROC

DEF PROCa(flag)
REM ... do something with flag
ENDPROC
The deeper call stack probably means it runs a bit slower, but rather that than passing parameters via globals, creating multiple entry points, etc etc :)

crj
Posts: 834
Joined: Thu May 02, 2013 4:58 pm
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by crj » Tue Oct 03, 2017 1:04 pm

Rich Talbot-Watkins wrote:I've been writing C and C++ for so long now that [...]
Tell me: have you ever encountered Duff's Device? (-8<

User avatar
Rich Talbot-Watkins
Posts: 1352
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by Rich Talbot-Watkins » Tue Oct 03, 2017 1:50 pm

crj wrote:
Rich Talbot-Watkins wrote:I've been writing C and C++ for so long now that [...]
Tell me: have you ever encountered Duff's Device? (-8<
Of course! But haven't had to do such low-level C hacking for many years now. Also, these days, with such huge advances in compilers and optimization techniques, I doubt if Duff's Device would even be worthwhile any more. The rule these days tends to be: don't micro-optimize - the compiler almost certainly knows better than you!

User avatar
Richard Russell
Posts: 503
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by Richard Russell » Tue Oct 03, 2017 4:24 pm

Rich Talbot-Watkins wrote:The deeper call stack probably means it runs a bit slower
It could be substantially slower, especially if there are multiple parameters (and even worse if some of them are RETURNed parameters). Interpreted BBC BASIC has a lot of work to do in making the formal parameters (especially strings) 'local', and adding another layer of call hierarchy means this has to be done twice. If it's not speed critical then, fair enough, your suggested alternative is better structured but when speed matters I don't feel that the 'many DEFPROC one ENDPROC' technique is particularly ugly. It's in a completely different league from passing parameters or results in globals!

User avatar
Rich Talbot-Watkins
Posts: 1352
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by Rich Talbot-Watkins » Tue Oct 03, 2017 5:40 pm

In your example though, doesn't the "LOCAL flag" get processed in exactly the same way as the procedure parameter? I mean, I'd assume that procedure parameters break down internally as "LOCAL whatever: whatever = <value of passed parameter>". That's how I'd've implemented it anyway.

User avatar
Richard Russell
Posts: 503
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by Richard Russell » Tue Oct 03, 2017 8:00 pm

Rich Talbot-Watkins wrote:In your example though, doesn't the "LOCAL flag" get processed in exactly the same way as the procedure parameter?
Well, not "exactly" because LOCALs don't have to be copied from the 'actual' parameters after saving. But that's not really the point, it's the 'real' parameters of the function/procedure that cause the speed issue with your approach:

Code: Select all

      DEF PROC1(par1,par2$,par3(),RETURN par4):PROCshared(1,par1,par2$,par3(),par4):ENDPROC
      DEF PROC2(par1,par2$,par3(),RETURN par4):PROCshared(2,par1,par2$,par3(),par4):ENDPROC
      DEF PROC3(par1,par2$,par3(),RETURN par4):PROCshared(3,par1,par2$,par3(),par4):ENDPROC
      
      DEF PROCshared(flag,par1,par2$,par3(),RETURN par4)
      ... shared code that tests 'flag'
      ENDPROC
All the (substantial) processing of the four parameters now has to be done twice, both on entry and on exit, compared with only once for the 'many DEFPROC' method.

Richard.

Coeus
Posts: 1025
Joined: Mon Jul 25, 2016 11:05 am
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by Coeus » Sun Jan 14, 2018 4:17 pm

Thinking of Richard's comment about getting used to C and C++ it is interesting to back to BBC BASIC and fine the language that was praised at the time for enabling structured programming allows such as this and lacks features like modern exception handling despite the general approach of the underlying OS to being one of preferring exceptions to special return values.

The thing about PROC and ENDPROC is that these take effect at run time in BASIC and are based on a stack and are therefore not lexical constructs whereas in a compiled language they would be lexical.

Coeus
Posts: 1025
Joined: Mon Jul 25, 2016 11:05 am
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by Coeus » Mon Jan 15, 2018 1:30 pm

Rich Talbot-Watkins wrote:Of course! But haven't had to do such low-level C hacking for many years now. Also, these days, with such huge advances in compilers and optimization techniques, I doubt if Duff's Device would even be worthwhile any more. The rule these days tends to be: don't micro-optimize - the compiler almost certainly knows better than you!
Certainly modern compilers do loop unrolling. I had a case recently where some code gave four warnings about an index out of bounds for the same line which happened to be a loop that executed eight times. The compiler had unrolled the loop and then worked out that for half of the iterations the index was out of bounds.

If you're interested in the compiler generating fast code it is probably more worthwhile to think about what the compiler does or does not know about your code rather than attempt the optimisation yourself. I am reminded of a slow-running program (a2b or something like that) from the Kermit distribution back in the second half of the 1980s. When I looked at the source I found something like this:

Code: Select all

for (i = 0; i < strlen(s); i++) {
     x = s[i];
      ...
 }
 
and I was able to speed it up considerably by changing it to:

Code: Select all

len = strlen(s);
for (i = 0; i < len; i++) {
     x = s[i];
      ...
 }
 
On the face of it this is just loop invariance, i.e. an optimisation the compiler ought to do but the issue is that the compiler didn't treat strlen as anything special and thus did not know that it didn't have side-effects so it had to assume it could return a different result each time and therefore needed to be called repeatedly. Failure to optimise that changes the performance from being proportional to the length of the string to be closer to proportional to the square of the length of the string, depending on how complex the contents of the loop are.

User avatar
Rich Talbot-Watkins
Posts: 1352
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by Rich Talbot-Watkins » Mon Jan 15, 2018 2:21 pm

Of course, in this example, you'd be even better just writing:

Code: Select all

for (i = 0; s[i] != 0; i++) {
    x = s[i];
     ...
}
to save iterating through the string twice (once in strlen, once in the loop).

Edit: I previously stated that the compiler could be clever enough to hoist the strlen out of the loop if it meets certain requisites, but after some playing around I can't make it do anything clever with a custom strlen (although the stdlib strlen seems to get special treatment). Probably missing something as I feel certain that modern compilers are able to deduce far more through static analysis than my experiments currently suggest!

crj
Posts: 834
Joined: Thu May 02, 2013 4:58 pm
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by crj » Mon Jan 15, 2018 4:17 pm

If you want the compiler to be able to work out that it's safe to lift a strlen call out of a loop, you need it to know two things:
  • strlen() is a pure function (it has absolutely no side effects; it's impossible to tell if you called it once or twice)
  • The string its argument points to does not alias any other values used in the loop
If your strlen implementation is an inline function, the compiler can probably work out by itself that it's pure. Otherwise, you'd need to adorn the declaration in some way, such as GCC's "__attribute__ ((pure))" .

Aliasing is somewhat gnarlier. In the example:

Code: Select all

for (i = 0; i < strlen(s); i++) {
     x = s[i];
      ...
 }
...if x is an automatic variable, or is a variable of a type incompatible with s[n] and you have strict aliasing enabled (i.e. the default, but often defeated because reasons) then the compiler will work out that x = s cannot affect the contents of s. Otherwise, it can't easily know that by itself. And who knows what mysteries that enigmatic "..." might hide. (-8

If you're sure you know what you're doing, on a modern C compiler you can give s the restrict qualifier so it knows.

User avatar
Rich Talbot-Watkins
Posts: 1352
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: Many DEFPROC, 1 ENDPROC?

Post by Rich Talbot-Watkins » Mon Jan 15, 2018 5:00 pm

crj wrote:If you want the compiler to be able to work out that it's safe to lift a strlen call out of a loop, you need it to know two things:
  • strlen() is a pure function (it has absolutely no side effects; it's impossible to tell if you called it once or twice)
  • The string its argument points to does not alias any other values used in the loop
If your strlen implementation is an inline function, the compiler can probably work out by itself that it's pure. Otherwise, you'd need to adorn the declaration in some way, such as GCC's "__attribute__ ((pure))" .
This is exactly what I originally wrote in my post, so confident was I in the compiler, but I removed it all when I tried to put together an example and GCC refused to hoist out my strlen:
https://godbolt.org/g/MaLQ93
(I even tried giving it enormous big hints with __attribute__((pure)) to no avail.)

Turns out that GCC 7.2 will only do the 'right thing' with this if you mark various things as constexpr (C++17) - thanks to Matt Godbolt for pointing it out (and also being equally disappointed in the compiler!). Which means either it's
a) a bug; or
b) lazy :lol:

So, never has the maxim been truer: when performance matters, don't assume anything - look at what the compiler's emitting, and measure!

Post Reply