Page 1 of 2

Does PB compile unused procedures?

Posted: Mon Sep 21, 2009 9:42 am
by Justin
I thought it didn't but with this code both strings are in the executable, it is including both procedures or just the strings?

Code: Select all

Procedure proc1()
  Define.s st
  
  st = "proc111111111111111111111111111111"
EndProcedure

Procedure proc2()
  Define.s st
  
  st = "proc222222222222222222222222222222"
EndProcedure

proc1()

Re: Does PB compile unused procedures?

Posted: Mon Sep 21, 2009 10:39 am
by PB
Just the strings. This has been discussed before.

Re: Does PB compile unused procedures?

Posted: Mon Sep 21, 2009 10:43 am
by luis
I would say from the generated code: if a procedure is referenced its code is included.

In your case, proc1() is included and proc2() is not.

Both procs code is generated by the compiler, but in the end only the macro associated with the first proc (MP0) is called, so I would say only its code it expanded and inserted in the final exe.

The data segment instead contains both the strings, so it's a mixed bag...

See post below for more info.

Re: Does PB compile unused procedures?

Posted: Mon Sep 21, 2009 12:56 pm
by luis
If a function is defined but never referenced is not included in the exe.

If a function is defined and referenced because recursive or because referenced by a second function (even if the second is never referenced) it's included in the exe even if not needed.

Example: many of us are using a lot of includes in our programs as "libraries in source form".

This is a possible scenario, you have this include: LIB.PBI

Code: Select all

Procedure a() 
; *NOT NEEDED* and not included, OK
; never referenced  
EndProcedure

Procedure b()
; *NOT NEEDED* but included
; referenced only by itself (recursive)
 b()
EndProcedure

Procedure c()
; *NOT NEEDED* but included
; referenced by a function never referenced: d()
EndProcedure

Procedure d()
; *NOT NEEDED* and not included, OK
; never referenced
 c()
EndProcedure

Procedure e()
; NEEDED
; referenced and called
EndProcedure
You have 5 useful lib function here: a(), b(), c(), d(), e()

a() is a closed procedure, no dependencies
b() is a closed procedure, no dependencies, but it's recursive
c() is a support procedure, it's called from d() and can be called directly too
d() is a procedure depending on c()
e() is a closed procedure, no dependencies

Now, this is your program using the above include:

Code: Select all

IncludeFile "lib.pbi"

e()
In this case, you need only the code inside the e() procedure, but in reality PB include in the exe the code from b(), because recursive, even if never called... and the code from c() because referenced from d(), even if d() is never called and so both are not needed. This is something that can happen very often in your includes.

This is the generated ASM from the compiler:

Code: Select all

; 
; PureBasic 4.31 (Windows - x86) generated code
; 
; (c) 2009 Fantaisie Software
; 
; The header must remain intact for Re-Assembly
; 
; :System
; KERNEL32
; :Import
; 
format MS COFF
; 
extrn _ExitProcess@4
extrn _GetModuleHandleA@4
extrn _HeapCreate@12
extrn _HeapDestroy@4
; 
extrn _memset
extrn _PB_StringBase
extrn PB_StringBase
extrn _SYS_InitString@0
; 
extrn _PB_StringBasePosition
public _PB_Instance
public _PB_ExecutableType
public _PB_MemoryBase
public PB_Instance
public PB_MemoryBase
public _PB_EndFunctions

macro pb_public symbol
{
  public  _#symbol
  public symbol
_#symbol:
symbol:
}

macro    pb_align value { rb (value-1) - ($-_PB_DataSection + value-1) mod value }
macro pb_bssalign value { rb (value-1) - ($-_PB_BSSSection  + value-1) mod value }
extrn SYS_CpuID
public PureBasicStart
; 
section '.code' code readable executable
; 
; 
PureBasicStart:
; 
  PUSH   dword I_BSSEnd-I_BSSStart
  PUSH   dword 0
  PUSH   dword I_BSSStart
  CALL  _memset
  ADD    esp,12
  PUSH   dword 0
  CALL  _GetModuleHandleA@4
  MOV    [_PB_Instance],eax
  PUSH   dword 0
  PUSH   dword 4096
  PUSH   dword 0
  CALL  _HeapCreate@12
  MOV    [PB_MemoryBase],eax
  CALL   SYS_CpuID
; 
  CALL  _SYS_InitString@0
; :
; IncludeFile "lib.pbi"
; Procedure a() 
macro MP0{
_Procedure0:
  PS0=4                                                                                                                                                                                                                                                     
; *NOT NEEDED* and not included, OK
; never referenced  
; EndProcedure
  XOR    eax,eax
_EndProcedure1:
  RET
}
; 
; Procedure b()
macro MP2{
_Procedure2:
  PS2=4                                                                                                                                                                                                                                                     
; *NOT NEEDED* but included
; referenced only by itself (recursive)
; INCLUDED by PB
; b()
  CALL  _Procedure2
; EndProcedure
  XOR    eax,eax
_EndProcedure3:
  RET
}
; 
; Procedure c()
macro MP4{
_Procedure4:
  PS4=4                                                                                                                                                                                                                                                     
; *NOT NEEDED* but included
; referenced by a function never referenced: d()
; EndProcedure
  XOR    eax,eax
_EndProcedure5:
  RET
}
; 
; Procedure d()
macro MP6{
_Procedure6:
  PS6=4                                                                                                                                                                                                                                                     
; *NOT NEEDED* and not included, OK
; never referenced
; c()
  CALL  _Procedure4
; EndProcedure
  XOR    eax,eax
_EndProcedure7:
  RET
}
; 
; Procedure e()
macro MP8{
_Procedure8:
  PS8=4                                                                                                                                                                                                                                                     
; NEEDED
; referenced and called
; EndProcedure
  XOR    eax,eax
_EndProcedure9:
  RET
}
; 
; e()
  CALL  _Procedure8
; 
; 
; 
_PB_EOP_NoValue:
  PUSH   dword 0
_PB_EOP:
  CALL  _PB_EndFunctions
  PUSH   dword [PB_MemoryBase]
  CALL  _HeapDestroy@4
  CALL  _ExitProcess@4
_PB_EndFunctions:
  RET
; 
MP2
MP4
MP8
; 
section '.data' data readable writeable
; 
_PB_DataSection:
pb_public PB_DEBUGGER_LineNumber
  dd     -1
pb_public PB_DEBUGGER_IncludedFiles
  dd     0
pb_public PB_DEBUGGER_FileName
  db     0
_PB_ExecutableType: dd 0
; 
; Dynamic functions jump table
; 
public _SYS_StaticStringStart
_SYS_StaticStringStart:
pb_public PB_NullString
  db     0
public _SYS_StaticStringEnd
_SYS_StaticStringEnd:
align 4
align 4
s_s:
  dd     0
  dd     -1
align 4
; 
section '.bss' readable writeable
_PB_BSSSection:
align 4
; 
I_BSSStart:
_PB_MemoryBase:
PB_MemoryBase: rd 1
_PB_Instance:
PB_Instance: rd 1
; 
align 4
PB_DataPointer rd 1
align 4
align 4
align 4
align 4
I_BSSEnd:
section '.data' data readable writeable
SYS_EndDataSection:

Re: Does PB compile unused procedures?

Posted: Mon Sep 21, 2009 1:13 pm
by luis
A practical worst case scenario with some numbers

Code: Select all

; this is the include

Procedure LoadTIFF()
 UseTIFFImageDecoder()
 ; code for load the tiff
EndProcedure

Procedure TIFF_DoSomethingToTheFile()
 LoadTIFF()
 ; do something to the tiff file
EndProcedure

Procedure LoadJPEG()
 UseJPEGImageDecoder()
 ; code for load the jpeg
 MessageRequester("TEST", "Hello, I'm working!")
EndProcedure


; this is my program, is using a jpg file

LoadJPEG()

; do something to the jpeg

End

; now ... don't think you are only using the JPEG part of your include
; because the TIFF decoder has been included too in your exe !
Compiled as it is, the exe is 357 KB

If you comment out LoadTIFF() and TIFF_DoSomethingToTheFile(), both not needed, the exe size is 42 KB.

This is a case hand-crafted using a big dependence I admit, but it show the point.

What do you think ? Wouldn't be nice if the compiler optimize all of the above ?
Many times you probably try to optimize your code for size but if you rely on includes (like myself) to help you to organize your work you could end up with a lot of unwanted code in your final exe. It's something you may want to pay attention to.

Re: Does PB compile unused procedures?

Posted: Tue Sep 22, 2009 7:28 pm
by Num3
Yes, it would be nice to 'forget' unused procedures at compile time.

Maybe the IDE can do a pre-parse before compiling and pop up a warning:

xx unused procedures
procedure xxx1
procedure xxx2
etc...

Re: Does PB compile unused procedures?

Posted: Tue Sep 22, 2009 8:19 pm
by luis
I don't think that would be a job for the IDE but for the compiler.

A source is not so easy to parse, if you consider macros expansion for example. Moreover the language can change and the way to reference a proc can change.

The compiler (or the preprocessor + compiler) should be responsible for that, I think, or you'll have to maintain a full parser + a simple one in the IDE just for this.

And keep them synchronized.

Re: Does PB compile unused procedures?

Posted: Thu Sep 24, 2009 3:00 am
by Blue
Most interesting.
Many thanks Luis for the thorough and eye-opening explanations and demo code.
I had no idea.

Thanks also to curious Justin. As they say, there are no stupid questions.

Re: Does PB compile unused procedures?

Posted: Mon Oct 05, 2009 9:11 pm
by Logman
Interesting question as to whether or not unused procedures are included in the final executable file. To find out, I put together this quick code snippet, compiled it to an executable program, and looked at it under a 32-bit disassembler to find out for myself.

Code: Select all

EnableASM
DisableDebugger

OpenConsole()

Global x.l = 0

Procedure proc1()
! nop
! nop
! nop
mov eax, 1
ProcedureReturn
EndProcedure

Procedure proc2()
! nop
! nop
! nop
mov eax, 2
ProcedureReturn
EndProcedure

x = proc2()

PrintN("x equals: " + Str(x))
Print("Press [ENTER] to exit... ")
Input()
CloseConsole()
End
As can be seen in the test code I only made a call to "proc2()" so only "proc2()" was actually used by the program. My initial guess would have been that PB would have included both procedures in the final executable file.

Results: to my surprise only "proc2()" showed up in the disassembled code. "proc1()" was not included anywhere in the executable file. I used "! nop" instructions to make it easier to look for both procedures in the code.

For me, seeing the results of actual tests on code is far better than guessing because I assumed both procedures would have been included. Of course, I wasn't making any references to external files and so forth.

To me, it looks like PB does some good "house keeping" by cleaning out unused procedures when compiled to an executable file.

Logman

Re: Does PB compile unused procedures?

Posted: Mon Oct 05, 2009 9:39 pm
by luis
What is the meaning of your post ? Am I missing something ?
Logman wrote: As you can see in the code I only make a call to "proc2()" so only "proc2()" is actually used.
Yes, but wasn't that clear already ?
Logman wrote: Doing actual tests on code is far better than guessing.
And this remark is direct to ... whom ?

Who is guessing ? Did you read the thread before making your post ?

By the way the things are not so simple as you pictured them, you really should read the thread.

Re: Does PB compile unused procedures?

Posted: Mon Oct 05, 2009 10:04 pm
by Justin
So i guess using @proc() also includes the procedure even is not used, right?

Code: Select all

procedure a()
x = @b()
endprocedure

procedure b()
endprocedure

procedure c()
endprocedure

c()

Re: Does PB compile unused procedures?

Posted: Mon Oct 05, 2009 10:14 pm
by luis
Yes, b() and c() go in the exe (you should use a declare or swap the a/b proc to compile the program but it's irrilevant).

Re: Does PB compile unused procedures?

Posted: Mon Oct 05, 2009 10:36 pm
by luis
Well, Logman you edited your post so now my initial reply seem a little off, a follow up maybe would have been better.

Just to be clear, I was contesting the fact you have moved back in time the thread to the "a function NOT USED is not included in the exe" concept. Why ?

When was just showed, in the very same thread, this is not the case.

Your post is still not showing the full picture.

You tested the simplest case, do the same test with the code from

http://www.purebasic.fr/english/viewtop ... 03#p300303

or

http://www.purebasic.fr/english/viewtop ... 04#p300304

and you should see something different.

OK, maybe PB will continue to do so in future versions, I don't know.
It's not something criminal, but it's quite important people know how things works, to organize their work the best way, and keeping all this in mind.

After all this is a question often asked from time to time.

Re: Does PB compile unused procedures?

Posted: Mon Oct 05, 2009 11:08 pm
by Logman
Luis:

I'm on my laptop traveling home right now. I'm curious about what will happen when I check out the example code you provided. I'll run it when I get on my desktop this evening and post the results.

You raise some very interesting questions about what PB includes and doesn't include in it's final compiled executable and I am just taking a look at it now that I have some capable tools.

Sorry about editing my initial post, but it seemed from your response that my writing style was ticking people off and I was not trying to do that. You posted some very poignant concerns and now that I have some useful tools, I'm able to look at how PB compiles its code. As I suspected, PB does a really good job of compiling code into executables, I just didn't realize how well a job it was doing until I actually started looking at some executable programs based on the examples discussed in this thread.

Let me put your latest code under the microscope and see what PB does with it.

Logman

Re: Does PB compile unused procedures?

Posted: Mon Oct 05, 2009 11:32 pm
by luis
Fair enough.

Bye and happy investigations ... :)