UEFI News and Commentary

Tuesday, February 16, 2010

WHY: Why Do I Get Unresolved Externals For __allmul?

While I was working on my project this week, I kept running into the following error:

CLibApp.lib(String.obj) : error LNK2019: unresolved external symbol __allmul referenced in function _wcssize


CLibApp.lib(Cwd.obj) : error LNK2001: unresolved external symbol __allmul
 
This function appears nowhere in my code, nor does it appear in any of the EDK's code? So what's going on? It turns out that this function is one of several compiler support functions that are invoked explicitly by the Microsoft C/C++ compiler. In this case, this function is called whenever the 32-bit compiler needs to multiply two 64-bit integers together. The EDK does not link with Microsoft's libraries and does not provide this function.
 
So why don't all the other drivers and applications in the EDK generated unresolved externals, since they obviously do 64-bit math? The EDK authors skirted this problem by creating 64-bit math routines of their own, such as MultU64x64 and MultU64x32 and using these instead of the built-in C/C++ multiply (*) operator.
 
Are there other functions like this one? Sure, several more for 64-bit division, remainder and shifting.
 
Interestingly enough, the EDK does contain some Microsoft C/C++ compiler support. See CompilerStub.c where both memcpy and memset (from the C standard library) are defined. Why? It turns out that later versions of the compiler optimize certain code sequences by calling the library routines. Want a little stranger bit of trivia? The EfiCommonLib tries to optimize the SetMem function by special-casing a set to zero (i.e. SetMem (dest, count, 0)). But it turns out that the C compile convers both branches into a call to memset.
 
Anyway, here is my implementation of the multiplication routine. The Microsoft version is available, but has their license. The other versions available on the web look an awful lot like the Microsoft version, down to the comments, so I reverse engineered the calling convention (old-style STDCALL) and wrote it from scratch in MASM 9.0.
 
Tim
 
; allmul - 64-bit signed multiplication support function.



.586
.MODEL FLAT, C
.CODE

;
; FUNCTION NAME.
; _allmul
;
; FUNCTIONAL DESCRIPTION.
; This function is called by the Microsoft Visual C/C++ compiler for 32-
; bit executables to multiply two 64-bit integers and returning a 64-bit
; result. The X86 processors have only a 32-bit multiply instruction,
; thus the necessity for a library support function.
;
; The operands are divided into two 32-bit quantities. You can imagine
; that this works like simple 2-digit x 2-digit multiplication, except
; that each digit is 32-bits wide.
;
;   AB
; x CD
; ----
;   DB
;  DA0
;  CB0
; CA00
; ----
; RRRR
;
; You notice that the 3rd and 4th columns never will be used because the
; are the part of the result that is > 64-bits.
;
; R[0:31] = DB[0:31]
; R[32:63] = DB[32:63] + DA[0:31] + CB[0:31]
;
; There is a short cut, if both A and C are 0, then we can use the simple
; 32-bit instruction.
;
;
; ENTRY PARAMETERS.
;    multiplicand - Right-hand operator (CD)
;    multiplier - Left-hand operator (AB)
;
; EXIT PARAMETERS.
;    EDX:EAX - Result.
;


_allmul PROC NEAR USES ESI, multiplicand:QWORD, multiplier:QWORD

 MA EQU DWORD PTR multiplier [4]
 MB EQU DWORD PTR multiplier
 MC EQU DWORD PTR multiplicand [4]
 MD EQU DWORD PTR multiplicand

 mov eax, MA
 mov ecx, MC
 or  ecx, eax    ; both zero?
 mov ecx, MD
 .if zero?      ; yes, use shortcut.
   mov eax, MB
   mul ecx      ; EDX:EAX = DB[0:63].
 .else
   mov eax, MA
   mul ecx      ; EDX:EAX = DA[0:63].
   mov esi, eax ; ESI = DA[0:31].

   mov eax, MB
   mul MC       ; EDX:EAX = CB[0:63]
   add esi, eax ; ESI = DA[0:31] + CB[0:31]


   mov eax, MB
   mul ecx      ; EDX:EAX = BD[0:63]
   add edx, esi ; EDX = DA[0:31] + CB[0:31] + DB[31:63]
                ; EAX = DB[0:31]
 .endif


 ret 16 ; callee clears the stack.
_allmul ENDP


 END

2 comments:

Unknown said...

Hi,

you will simply use this for mul/div UINT64 types

UINT64 d1,d2;

d1=100;d2=200;

//d1=d1*d2; _aullmul link error

d1=MultU64x32(d1,(UINTN)d2);

Same goes for div ops.

Greetings

Tim Lewis said...

My goal was to allow me to do this:

UINT64 a;
UINT64 b;
UINT64 c;

c = b * a;

like in any normal environment using a C compiler. Why should UEFI not have full C compiler support? So I wrote my own...