[Previous] [TOC] [Next]

Advanced Optimizations


Microsoft generally encourages you to play around with what they call the safe compiler options. Naturally, these are options that aren't situated beneath the Advanced Optimizations button. For those options Microsoft usually provides a disclaimer: "These might crash your program." Let's see what these Advanced Optimizations are about and why this warning is given. (See Table 7-5)

Table 7-5 Advanced Optimizations Options

Option Description
Allow Unrounded Floating Point Operations Allows the compiler to compare floating-point expressions without first rounding to the correct precision. Floating-point calculations are normally rounded off to the correct degree of precision (Single or Double) before comparisons are made. Selecting this option allows the compiler to do floating-point comparisons before rounding, when it can do so more efficiently. This improves the speed of some floating-point operations; however, this may result in calculations being maintained to a higher precision than expected, and two floating-point values not comparing equal when they might be expected to.
Assume No Aliasing Tells the compiler that your program does not use aliasing (that your program does not refer to the same memory location by more than one name, which occurs when using ByRef arguments that refer to the same variable in two ways). Checking this option allows the compiler to apply optimization such as storing variables in registers and performing loop optimizations.
Remove Array Bounds Checks Disables Visual Basic array bounds checking. By default, Visual Basic makes a check on every access to an array to determine if the index is within the range of the array. If the index is outside the bounds of the array, an error is returned. Selecting this option will turn off this error checking, which can speed up array manipulation significantly. However, if your program accesses an array with an index that is out of bounds, invalid memory locations might be accessed without warning. This can cause unexpected behavior or program crashes.
Remove Floating Point Error Checks Disables Visual Basic floating-point error checking and turns off error checking for valid floating-point operations and numeric values assigned to floating-point variables. By default in Visual Basic, a check is made on every calculation to a variable with floating-point data types (Single and Double) to be sure that the resulting value is within the range of that data type. If the value is of the wrong magnitude, an error will occur.

Error checking is also performed to determine if division by zero or other invalid operations are attempted. Selecting this option turns off this error checking, which can speed up floating-point calculations. If data type capacities are overflowed, however, no error will be returned and incorrect results might occur.

Remove Integer Overflow Checks Disables Visual Basic integer overflow checking. By default in Visual Basic, a check is made on every calculation to a variable with an integer data type (Byte, Integer, Long, and Currency) to be sure that the resulting value is within range of that data type. If the value is of the wrong magnitude, an error will occur. Selecting this option will turn off this error checking, which can speed up integer calculations. If data type capacities are overflowed, however, no error will be returned and incorrect results might occur.
Remove Safe Pentium FDIV Checks Disables checking for safe Pentium floating-point division and turns off the generation of special code for Pentium processors with the FDIV bug. The native code compiler automatically adds extra code for floating-point operations to make these operations safe when run on Pentium processors that have the FDIV bug. Selecting this option produces code that is smaller and faster, but which might in rare cases produce slightly incorrect results on Pentium processors with the FDIV bug.
By using the Visual C++ debugger (or any compatible debugger) with Visual Basic code that has been compiled to contain symbolic debugging information, it's possible to see more of what each option does to your code. By way of explanation, here are a few annotated examples (obviously you won't expect to see commented code like this from a debugger!):

Integer Overflow

  Dim n As Integer

  n = 100 * 200 * 300

Disassembly (without Integer Overflow check)

  ' Do the multiplication - ax = 300
  mov         ax,offset    Form::Proc+4Ch

  ' Signed integer multiplication. 300 * 20000
  ' The 20000 is stored in Form::Proc+51h and was
  ' created by the compiler from the constant exp.
  ' 100 * 200 and held as 'immediate data'
  imul        ax,ax,offset Form::Proc+51h

  n = Result

  mov         word ptr [n],ax

Disassembly (with Integer Overflow check)

  ' Do the multiplication - ax = 100
  mov         ax,offset    Form::Proc+4Ch
  imul        ax,ax,offset Form::Proc+51h

  ' Jump to error handler if the overflow flag set
  jo          ___vbaErrorOverflow

  Else, n = Result
  mov         word ptr [n],ax

Array Bounds

    Dim n1        As Integer
    Dim n(100)    As Integer

    n1 = n(101)

Disassembly (without Array Bounds check)

  ' Sizeof(Integer) = 2, put in eax
  push        2
  pop         eax

  ' Integer multiplication.  2 * 101 (&H65) = result in eax.
  imul        eax,eax,65h

  ' Get array base address in to ecx.
  mov         ecx,dword ptr [ebp-20h]

  n(101) (base plus offset) is in ax
  mov         ax,word ptr [ecx+eax]

  n1 = n(101)
  mov         word ptr [n1],ax

Disassembly (with Array Bounds check)

  ' Address pointed to by v1 = 101, the offset we want
  mov         dword ptr [unnamed_var1],65h

  ' Compare value thus assigned with the known size of array + 1
  cmp         dword ptr [unnamed_var1],65h

  ' Jump above or equal to 'Call ___vbaGenerateBoundsError'
  jae         Form1::Proc+6Dh

  ' Zero the flags and a memory location.
  and         dword ptr [ebp-48h],0

  ' Jump to 'mov eax,dword ptr [unnamed_var1]'
  jmp         Form1::Proc+75h

  ' Raise the VB error here
  call        ___vbaGenerateBoundsError

  ' Store element number we want to access
  mov         dword ptr [ebp-48h],eax

  ' Get the element we wanted to access into eax
  mov         eax,dword ptr [unnamed_var1]

  ' Get array base address in to ecx.
  mov         ecx,dword ptr [ebp-20h]

  ' n(101) is in ax (* 2 because sizeof(Integer) = 2
  mov         ax,word ptr [ecx+eax*2]

  ' n1 = n(101)
  mov         word ptr [n1],ax
  Floating Point Error
  Dim s As Single

  s = s * s

Disassembly (without Floating Point Error check)

  ' Pushes the specified operand onto the FP stack
  fld         dword ptr [s]

  ' Multiplies the source by the destination and returns
  ' the product in the destination
  fmul        dword ptr [s]

  ' Stores the value in the floating point store (ST?)
  ' to the specified memory location
  fstp        dword ptr [s]

Disassembly (with Floating Point Error check)

  fld         dword ptr [s]
  fmul        dword ptr [s]
  fstp        dword ptr [s]

  ' Store the floating point flags in AX (no wait)
  fnstsw      ax

  ' Test for floating point error flag set
  test        al,0Dh

  ' Jump if zero flag not set
  jne         ___vbaFPException

You should now have more of a feel for why these options are left partially obscured like they are, and for the warning given by Microsoft. Without a native code debugger it's really hard to see just how your code's being affected. Even with a debugger like the one that comes with Visual Studio it's not a straightforward task to read through the assembly-language dumps and state that your code is cool and that the optimizations you've chosen are safe!

Library and object Files

From Table 7-3, you'll notice that VBAEXE6.LIB is linked in with our own OBJ file (created from our files and modules). The library contains just one component (library files contain object files), NATSUPP.OBJ. (NATSUPP might stand for "native support.") You can find this object by using DUMPBIN /ARCHIVEMEMBERS VBAEXE6.LIB. (DUMPBIN.EXE is the Microsoft Common Object File Format [COFF] Binary File Dumper.) NATSUPP.OBJ can be extracted for further examination using the Microsoft Library Manager, LIB.EXE:

  lib /
  extract:c:\vbadev\r6w32nd\presplit\vbarun\obj\natsupp.obj vbaexe6.lib

The reason for including the path to the OBJ file is that the library manager expects us to specify exactly the name of the module-including its path. (This is embedded into the library file when the object file is first put into it and is discovered using DUMPBIN /ARCHIVEMEMBERS.) In other words, the object file probably "lived" at this location on someone's machine in Redmond! Similarly, we can tell that the source code for this object file was named NATSUPP.ASM and was in the directory C:\VBADEV\RT\WIN32. It was assembled using Microsoft's Macro Assembler, Version 6.13. (6.11 is the latest version available to the public, I believe.) Interestingly, it doesn't contain any code-just data-although what looks like a jump table (a mechanism often used to facilitate calls to external routines) appears to be included. To call a routine, you look up its address in the table and then jump to it, as shown in Table 7-6.

Table 7-6 Contents of NATSUPP.OBJ

Name Size Content
.text 0 Readable code
.data 4 Initialized readable writable data
.debug$S 140 Initialized discardable readable data
.debug$T 4 Initialized discardable readable data
The sections are as follows:
  • .text is where all the general-purpose code created by the compiler is output. (It's 0 bytes big, which probably means no code!)
  • .data is where initialized data is stored.
  • .debug$S and .debug$T contain, respectively, CodeView Version 4 (CV4) symbolic information (a stream of CV4 symbol records) and CV4 type information (a stream of CV4 type records), as described in the CV4 specification.

As well as statically linking with this library file, other object files reference exported functions in yet another library file, MSVBVM60.DLL This is a rather large DLL installed by the Visual Basic 6 Setup program in the WINDOWS\SYSTEM directory. (The file describes itself as Visual Basic Virtual Machine and at the time of writing was at version 6.0.81.76-or 6.00.8176 if you look a the version string.) Using DUMPBIN /EXPORTS MSVBVM60.DLL on this DLL yields some interesting symbolic information. For example, we can see that it exports a number of routines, 635 in fact! Some interesting-looking things, possibly routines for invoking methods and procedures, are in here as well: MethCallEngine and ProcCallEngine. Additionally, there are what look like stubs, prefixed with rtc ("run-time call," perhaps?), one for apparently all the VBA routines: rtcIsArray, rtcIsDate, rtcIsEmpty, … rtcMIRR , … rtcMsgBox, … rtcQBColor, and so on. And as with most DLLs, some cryptic, yet interesting exports, such as Zombie_Release, are included.

In addition to this symbolic information, the DLL contains a whole bunch of resources, which we can extract and examine using tools such as Visual C++ 6. Of all the resources the DLL contains, the one that really begs examination is the type library resource. If we disassemble this using OLEVIEW.EXE, we can see its entire type library in source form.

The type library contains all sorts of stuff as well as the interface definitions of methods and properties, such as the hidden VarPtr, ObjPtr, and StrPtr routines.

It turns out that this MSVBVM60.DLL is probably the run-time support DLL for any Visual Basic 6 native and p-code executable; that is, it acts like MFC42.DLL does for an MFC application. (MFC stands for Microsoft Foundation Classes, Microsoft's C++/Windows class libraries.) We can confirm this by dumping a built native code executable. Sure enough, we find that the executable imports routines from the DLL. (By the way, the Package And Deployment Wizard also lists this component as the Visual Basic Runtime.)

By dumping other separate object files, we can gather information about what is defined and where it is exported. For example, we can use DUMPBIN /SYMBOLS MODULE1.OBJ to discover that a function named Beep will be compiled using Microsoft's C++ name decoration (name mangling) regime and thus end up being named ?Beep@Module1@@AAGXXZ. Presumably, this function is compiled as a kind of C++ anyway; that is, in C++ it is defined as (private: void __stdcall Module1::Beep(void)). Or better yet, we can use DUMPBIN /DISASM ????????.OBJ to disassemble a module.

The same routine-Beep-defined in a class, Class1 for example, looks like this:

  ?Beep@Class1@@AAGXXZ (private: void __stdcall Class1::Beep(void)).

Maybe now we can see why, since Visual Basic 4, we've had to name modules even though they're not multiply instantiable. Each seems to become a kind of C++ class. According to the name decorations used, Beep is a member of the C++ Classes Class1 and Module1.

[Previous] [TOC] [Next]