Files
ldc/backend/cgreg.c
Alexey Prokhin caad8cde58 Squashed 'dmd2/' content from commit 10017d5
git-subtree-dir: dmd2
git-subtree-split: 10017d50eaaff4ecdc37a0153b6c37ea0b004c81
2012-04-05 11:10:48 +04:00

1014 lines
28 KiB
C

// Copyright (C) 1985-1998 by Symantec
// Copyright (C) 2000-2012 by Digital Mars
// All Rights Reserved
// http://www.digitalmars.com
// Written by Walter Bright
/*
* This source file is made available for personal use
* only. The license is in /dmd/src/dmd/backendlicense.txt
* or /dm/src/dmd/backendlicense.txt
* For any other uses, please contact Digital Mars.
*/
#if !SPP
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "cc.h"
#include "el.h"
#include "oper.h"
#include "code.h"
#include "global.h"
#include "type.h"
static char __file__[] = __FILE__; /* for tassert.h */
#include "tassert.h"
STATIC void el_weights(int bi,elem *e,unsigned weight);
#ifndef __DMC__
#undef __cdecl
#define __cdecl
#endif
static int __cdecl weight_compare(const void *e1,const void *e2);
static int nretblocks;
static vec_t regrange[REGMAX];
static int *weights;
#define WEIGHTS(bi,si) weights[bi * globsym.top + si]
/******************************************
*/
void cgreg_init()
{
if (!(config.flags4 & CFG4optimized))
return;
// Use calloc() instead because sometimes the alloc is too large
//printf("1weights: dfotop = %d, globsym.top = %d\n", dfotop, globsym.top);
weights = (int *) calloc(1,dfotop * globsym.top * sizeof(weights[0]));
assert(weights);
nretblocks = 0;
for (int bi = 0; bi < dfotop; bi++)
{ block *b = dfo[bi];
if (b->BC == BCret || b->BC == BCretexp)
nretblocks++;
if (b->Belem)
{
//printf("b->Bweight = x%x\n",b->Bweight);
el_weights(bi,b->Belem,b->Bweight);
}
}
memset(regrange,0,sizeof(regrange));
// Make adjustments to symbols we might stick in registers
for (size_t i = 0; i < globsym.top; i++)
{ unsigned sz;
symbol *s = globsym.tab[i];
//printf("candidate '%s' for register\n",s->Sident);
if (s->Srange)
s->Srange = vec_realloc(s->Srange,dfotop);
// Determine symbols that are not candidates
if (!(s->Sflags & GTregcand) ||
!s->Srange ||
(sz = type_size(s->Stype)) == 0 ||
(tysize(s->ty()) == -1) ||
(I16 && sz > REGSIZE) ||
(tyfloating(s->ty()) && !(config.fpxmmregs && tyxmmreg(s->ty())))
)
{
#ifdef DEBUG
if (debugr)
printf("not considering variable '%s' for register\n",s->Sident);
#endif
s->Sflags &= ~GTregcand;
continue;
}
switch (s->Sclass)
{ case SCparameter:
case SCfastpar:
// Do not put parameters in registers if they are not used
// more than twice (otherwise we have a net loss).
if (s->Sweight <= 2 && !tyxmmreg(s->ty()))
{
#ifdef DEBUG
if (debugr)
printf("parameter '%s' weight %d is not enough\n",s->Sident,s->Sweight);
#endif
s->Sflags &= ~GTregcand;
continue;
}
break;
}
if (sz == 1)
s->Sflags |= GTbyte;
if (!s->Slvreg)
s->Slvreg = vec_calloc(dfotop);
//printf("dfotop = %d, numbits = %d\n",dfotop,vec_numbits(s->Srange));
assert(vec_numbits(s->Srange) == dfotop);
}
}
/******************************************
*/
void cgreg_term()
{
if (config.flags4 & CFG4optimized)
{
for (size_t i = 0; i < globsym.top; i++)
{
Symbol *s = globsym.tab[i];
vec_free(s->Srange);
vec_free(s->Slvreg);
s->Srange = NULL;
s->Slvreg = NULL;
}
for (size_t i = 0; i < arraysize(regrange); i++)
{
if (regrange[i])
{ vec_free(regrange[i]);
regrange[i] = NULL;
}
}
free(weights);
weights = NULL;
}
}
/*********************************
*/
void cgreg_reset()
{
for (size_t j = 0; j < arraysize(regrange); j++)
if (!regrange[j])
regrange[j] = vec_calloc(dfotop);
else
vec_clear(regrange[j]);
}
/*******************************
* Registers used in block bi.
*/
void cgreg_used(unsigned bi,regm_t used)
{
for (size_t j = 0; used; j++)
{ if (used & 1) // if register j is used
vec_setbit(bi,regrange[j]);
used >>= 1;
}
}
/*************************
* Run through a tree calculating symbol weights.
*/
STATIC void el_weights(int bi,elem *e,unsigned weight)
{
while (1)
{ elem_debug(e);
int op = e->Eoper;
if (!OTleaf(op))
{
// This prevents variable references within common subexpressions
// from adding to the variable's usage count.
if (e->Ecount)
{
if (e->Ecomsub)
weight = 0;
else
e->Ecomsub = 1;
}
if (OTbinary(op))
{ el_weights(bi,e->E2,weight);
if ((OTopeq(op) || OTpost(op)) && e->E1->Eoper == OPvar)
{
if (weight >= 10)
weight += 10;
else
weight++;
}
}
e = e->E1;
}
else
{
switch (op)
{
case OPvar:
Symbol *s = e->EV.sp.Vsym;
if (s->Ssymnum != -1 && s->Sflags & GTregcand)
{
s->Sweight += weight;
//printf("adding %d weight to '%s' (block %d, Ssymnum %d), giving Sweight %d\n",weight,s->Sident,bi,s->Ssymnum,s->Sweight);
if (weights)
WEIGHTS(bi,s->Ssymnum) += weight;
}
break;
}
return;
}
}
}
/*****************************************
* Determine 'benefit' of assigning symbol s to register reg.
* Benefit is roughly the number of clocks saved.
* A negative value means that s cannot or should not be assigned to reg.
*/
int cgreg_benefit(Symbol *s,int reg, Symbol *retsym)
{
int benefit;
int benefit2;
block *b;
int bi;
int gotoepilog;
int retsym_cnt;
//printf("cgreg_benefit(s = '%s', reg = %d)\n", s->Sident, reg);
vec_sub(s->Slvreg,s->Srange,regrange[reg]);
int si = s->Ssymnum;
Lagain:
//printf("again\n");
benefit = 0;
retsym_cnt = 0;
// Make sure we have enough uses to justify
// using a register we must save
if (fregsaved & mask[reg] & mfuncreg)
benefit -= 1 + nretblocks;
foreach (bi,dfotop,s->Srange)
{ int inoutp;
int inout;
b = dfo[bi];
switch (b->BC)
{
case BCjcatch:
case BCcatch:
case BC_except:
case BC_finally:
case BC_ret:
s->Sflags &= ~GTregcand;
goto Lcant; // can't assign to register
}
if (vec_testbit(bi,s->Slvreg))
{ benefit += WEIGHTS(bi,si);
//printf("WEIGHTS(%d,%d) = %d, benefit = %d\n",bi,si,WEIGHTS(bi,si),benefit);
inout = 1;
if (s == retsym && (reg == AX || reg == XMM0) && b->BC == BCretexp)
{ benefit += 1;
retsym_cnt++;
//printf("retsym, benefit = %d\n",benefit);
if (s->Sfl == FLreg && !vec_disjoint(s->Srange,regrange[reg]))
goto Lcant; // don't spill if already in register
}
}
else
inout = -1;
// Look at predecessors to see if we need to load in/out of register
gotoepilog = 0;
L2:
inoutp = 0;
benefit2 = 0;
for (list_t bl = b->Bpred; bl; bl = list_next(bl))
{
block *bp = list_block(bl);
int bpi = bp->Bdfoidx;
if (!vec_testbit(bpi,s->Srange))
continue;
if (gotoepilog && bp->BC == BCgoto)
{
if (vec_testbit(bpi,s->Slvreg))
{
if (inout == -1)
benefit2 -= bp->Bweight; // need to mov into mem
}
else
{
if (inout == 1)
benefit2 -= bp->Bweight; // need to mov into reg
}
}
else if (vec_testbit(bpi,s->Slvreg))
{
switch (inoutp)
{
case 0:
inoutp = 1;
if (inout != 1)
{ if (gotoepilog)
{ vec_clearbit(bpi,s->Slvreg);
goto Lagain;
}
benefit2 -= b->Bweight; // need to mov into mem
}
break;
case 1:
break;
case -1:
if (gotoepilog == 0)
{ gotoepilog = 1;
goto L2;
}
vec_clearbit(bpi,s->Slvreg);
goto Lagain;
}
}
else
{
switch (inoutp)
{
case 0:
inoutp = -1;
if (inout != -1)
{ if (gotoepilog)
{ vec_clearbit(bi,s->Slvreg);
goto Lagain;
}
benefit2 -= b->Bweight; // need to mov into reg
}
break;
case 1:
if (gotoepilog == 0)
{ gotoepilog = 1;
goto L2;
}
if (inout == 1)
{ vec_clearbit(bi,s->Slvreg);
goto Lagain;
}
goto Lcant;
case -1:
break;
}
}
}
//printf("benefit2 = %d\n", benefit2);
benefit += benefit2;
}
#ifdef DEBUG
//printf("2weights: dfotop = %d, globsym.top = %d\n", dfotop, globsym.top);
if (benefit > s->Sweight + retsym_cnt)
printf("s = '%s', benefit = %d, Sweight = %d, retsym_cnt = x%x\n",s->Sident,benefit,s->Sweight, retsym_cnt);
#endif
assert(benefit <= s->Sweight + retsym_cnt);
return benefit;
Lcant:
return -1; // can't assign to reg
}
/*********************************************
* Determine if block gets symbol loaded by predecessor epilog (1),
* or by prolog (0).
*/
int cgreg_gotoepilog(block *b,Symbol *s)
{
int bi = b->Bdfoidx;
int inout;
if (vec_testbit(bi,s->Slvreg))
inout = 1;
else
inout = -1;
// Look at predecessors to see if we need to load in/out of register
int gotoepilog = 0;
int inoutp = 0;
for (list_t bl = b->Bpred; bl; bl = list_next(bl))
{
block *bp = list_block(bl);
int bpi = bp->Bdfoidx;
if (!vec_testbit(bpi,s->Srange))
continue;
if (vec_testbit(bpi,s->Slvreg))
{
switch (inoutp)
{
case 0:
inoutp = 1;
if (inout != 1)
{ if (gotoepilog)
goto Lcant;
}
break;
case 1:
break;
case -1:
if (gotoepilog == 0)
{ gotoepilog = 1;
goto Lret;
}
goto Lcant;
}
}
else
{
switch (inoutp)
{
case 0:
inoutp = -1;
if (inout != -1)
{ if (gotoepilog)
goto Lcant;
}
break;
case 1:
if (gotoepilog == 0)
{ gotoepilog = 1;
goto Lret;
}
goto Lcant;
case -1:
break;
}
}
}
Lret:
return gotoepilog;
Lcant:
assert(0);
return -1; // can't assign to reg
}
/**********************************
* Determine block prolog code - it's either
* assignments to register, or storing register back in memory.
*/
void cgreg_spillreg_prolog(block *b,Symbol *s,code **pcstore,code **pcload)
{
list_t bl;
code *cstore = *pcstore;
code *cload = *pcload;
int bi = b->Bdfoidx;
//printf("cgreg_spillreg_prolog(block %d, s = '%s')\n",bi,s->Sident);
int inoutp;
if (vec_testbit(bi,s->Slvreg))
{ inoutp = 1;
// If it's startblock, and it's a spilled parameter, we
// need to load it
if (s->Sflags & SFLspill && bi == 0 &&
(s->Sclass == SCparameter || s->Sclass == SCfastpar))
{
goto Lload;
}
}
else
inoutp = -1;
if (cgreg_gotoepilog(b,s))
return;
// Look at predecessors to see if we need to load in/out of register
for (bl = b->Bpred; bl; bl = list_next(bl))
{
{
block *bp = list_block(bl);
int bpi = bp->Bdfoidx;
if (!vec_testbit(bpi,s->Srange))
continue;
if (vec_testbit(bpi,s->Slvreg))
{
if (inoutp != -1)
continue;
}
else
{
if (inoutp != 1)
continue;
}
}
Lload:
#ifdef DEBUG
if (debugr)
{
int sz = type_size(s->Stype);
if (inoutp == -1)
printf("B%d: prolog moving %s into '%s'\n",bi,regstring[s->Sreglsw],s->Sident);
else
printf("B%d: prolog moving '%s' into %s:%s\n",
bi, s->Sident, regstring[s->Sregmsw], sz > REGSIZE ? regstring[s->Sreglsw] : "");
}
#endif
code* c = gen_spill_reg(s, inoutp == 1);
if (inoutp == -1)
cstore = cat(cstore,c);
else
cload = cat(cload,c);
break;
}
// Store old register values before loading in new ones
*pcstore = cstore;
*pcload = cload;
}
/**********************************
* Determine block epilog code - it's either
* assignments to register, or storing register back in memory.
*/
void cgreg_spillreg_epilog(block *b,Symbol *s,code **pcstore,code **pcload)
{
code *cstore = *pcstore;
code *cload = *pcload;
int bi = b->Bdfoidx;
//printf("cgreg_spillreg_epilog(block %d, s = '%s')\n",bi,s->Sident);
//assert(b->BC == BCgoto);
if (!cgreg_gotoepilog(list_block(b->Bsucc),s))
return;
int inoutp;
if (vec_testbit(bi,s->Slvreg))
inoutp = 1;
else
inoutp = -1;
// Look at successors to see if we need to load in/out of register
for (list_t bl = b->Bsucc; bl; bl = list_next(bl))
{
block *bp = list_block(bl);
int bpi = bp->Bdfoidx;
if (!vec_testbit(bpi,s->Srange))
continue;
if (vec_testbit(bpi,s->Slvreg))
{
if (inoutp != -1)
continue;
}
else
{
if (inoutp != 1)
continue;
}
#ifdef DEBUG
if (debugr)
{
if (inoutp == 1)
printf("B%d: epilog moving %s into '%s'\n",bi,regstring[s->Sreglsw],s->Sident);
else
printf("B%d: epilog moving '%s' into %s\n",bi,s->Sident,regstring[s->Sreglsw]);
}
#endif
code* c = gen_spill_reg(s, inoutp == -1);
if (inoutp == 1)
cstore = cat(cstore,c);
else
cload = cat(cload,c);
break;
}
// Store old register values before loading in new ones
*pcstore = cstore;
*pcload = cload;
}
/***************************
* Map symbol s into registers [NOREG,reglsw] or [regmsw, reglsw].
*/
void cgreg_map(Symbol *s, unsigned regmsw, unsigned reglsw)
{
//assert(I64 || reglsw < 8);
if (vec_disjoint(s->Srange,regrange[reglsw]) &&
(regmsw == NOREG || vec_disjoint(s->Srange,regrange[regmsw]))
)
{
s->Sfl = FLreg;
vec_copy(s->Slvreg,s->Srange);
}
else
{
s->Sflags |= SFLspill;
// Already computed by cgreg_benefit()
//vec_sub(s->Slvreg,s->Srange,regrange[reglsw]);
if (s->Sfl == FLreg) // if reassigned
{
switch (s->Sclass)
{
case SCauto:
case SCregister:
case SCtmp:
case SCfastpar:
s->Sfl = FLauto;
break;
case SCbprel:
s->Sfl = FLbprel;
break;
case SCparameter:
s->Sfl = FLpara;
break;
#if PSEUDO_REGS
case SCpseudo:
s->Sfl = FLpseudo;
break;
#endif
case SCstack:
s->Sfl = FLstack;
break;
default:
#ifdef DEBUG
symbol_print(s);
#endif
assert(0);
}
}
}
s->Sreglsw = reglsw;
s->Sregm = mask[reglsw];
mfuncreg &= ~mask[reglsw];
if (regmsw != NOREG)
vec_subass(s->Slvreg,regrange[regmsw]);
vec_orass(regrange[reglsw],s->Slvreg);
if (regmsw == NOREG)
{
#if DEBUG
if (debugr)
{
printf("symbol '%s' %s in register %s\n ",
s->Sident,
(s->Sflags & SFLspill) ? "spilled" : "put",
regstring[reglsw]);
vec_println(s->Slvreg);
}
#endif
}
else
{
assert(regmsw < 8);
s->Sregmsw = regmsw;
s->Sregm |= mask[regmsw];
mfuncreg &= ~mask[regmsw];
vec_orass(regrange[regmsw],s->Slvreg);
#if DEBUG
if (debugr)
printf("symbol '%s' %s in register pair %s\n",
s->Sident,
(s->Sflags & SFLspill) ? "spilled" : "put",
regm_str(s->Sregm));
#endif
}
}
/********************************************
* The register variables in this mask can not be in registers.
* "Unregister" them.
*/
void cgreg_unregister(regm_t conflict)
{
if (pass == PASSfinal)
pass = PASSreg; // have to codegen at least one more time
for (int i = 0; i < globsym.top; i++)
{ symbol *s = globsym.tab[i];
if (s->Sfl == FLreg && s->Sregm & conflict)
{
s->Sflags |= GTunregister;
}
}
}
/******************************************
* Do register assignments.
* Returns:
* !=0 redo code generation
* 0 no more register assignments
*/
struct Reg // data for trial register assignment
{
Symbol *sym;
int reglsw;
int regmsw;
int benefit;
};
int cgreg_assign(Symbol *retsym)
{
int flag = FALSE; // assume no changes
/* First do any 'unregistering' which might have happened in the last
* code gen pass.
*/
for (size_t si = 0; si < globsym.top; si++)
{ symbol *s = globsym.tab[si];
if (s->Sflags & GTunregister)
{
#if DEBUG
if (debugr)
{
printf("symbol '%s' %s register %s\n ",
s->Sident,
(s->Sflags & SFLspill) ? "unspilled" : "unregistered",
regstring[s->Sreglsw]);
vec_println(s->Slvreg);
}
#endif
flag = TRUE;
s->Sflags &= ~(GTregcand | GTunregister | SFLspill);
if (s->Sfl == FLreg)
{
switch (s->Sclass)
{
case SCauto:
case SCregister:
case SCtmp:
case SCfastpar:
s->Sfl = FLauto;
break;
case SCbprel:
s->Sfl = FLbprel;
break;
case SCparameter:
s->Sfl = FLpara;
break;
#if PSEUDO_REGS
case SCpseudo:
s->Sfl = FLpseudo;
break;
#endif
case SCstack:
s->Sfl = FLstack;
break;
default:
#ifdef DEBUG
symbol_print(s);
#endif
assert(0);
}
}
}
}
vec_t v = vec_calloc(dfotop);
// Find symbol t, which is the most 'deserving' symbol that should be
// placed into a register.
Reg t;
t.sym = NULL;
t.benefit = 0;
for (size_t si = 0; si < globsym.top; si++)
{ symbol *s = globsym.tab[si];
Reg u;
u.sym = s;
if (!(s->Sflags & GTregcand) ||
s->Sflags & SFLspill ||
// Keep trying to reassign retsym into AX
(s->Sfl == FLreg && !(s == retsym && s->Sregm != mAX && s->Sregm != mXMM0))
)
{
#ifdef DEBUG
if (debugr)
if (s->Sfl == FLreg)
printf("symbol '%s' is in reg %s\n",s->Sident,regm_str(s->Sregm));
else if (s->Sflags & SFLspill)
printf("symbol '%s' spilled in reg %s\n",s->Sident,regm_str(s->Sregm));
else if (!(s->Sflags & GTregcand))
printf("symbol '%s' is not a reg candidate\n",s->Sident);
else
printf("symbol '%s' is not a candidate\n",s->Sident);
#endif
continue;
}
tym_t ty = s->ty();
unsigned sz = tysize(ty);
#ifdef DEBUG
if (debugr)
{ printf("symbol '%3s', ty x%x weight x%x sz %d\n ",
s->Sident,ty,s->Sweight,(int)sz);
vec_println(s->Srange);
}
#endif
// Select sequence of registers to try to map s onto
char *pseq; // sequence to try for LSW
char *pseqmsw = NULL; // sequence to try for MSW, NULL if none
if (tyxmmreg(ty))
{
static char sequence[] = {XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6,XMM7,NOREG};
pseq = sequence;
}
else if (I64)
{
if (sz == REGSIZE * 2)
{
static char seqmsw[] = {CX,DX,NOREG};
static char seqlsw[] = {AX,BX,SI,DI,NOREG};
pseq = seqlsw;
pseqmsw = seqmsw;
}
else
{ // R10 is reserved for the static link
static char sequence[] = {AX,CX,DX,SI,DI,R8,R9,R11,BX,R12,R13,R14,R15,BP,NOREG};
pseq = sequence;
}
}
else if (I32)
{
if (sz == REGSIZE * 2)
{
static char seqlsw[] = {AX,BX,SI,DI,NOREG};
static char seqmsw[] = {CX,DX,NOREG};
pseq = seqlsw;
pseqmsw = seqmsw;
}
else
{
static char sequence[] = {AX,CX,DX,BX,SI,DI,BP,NOREG};
pseq = sequence;
}
}
else
{ assert(I16);
if (typtr(ty))
{
// For pointer types, try to pick index register first
static char seqidx[] = {BX,SI,DI,AX,CX,DX,BP,NOREG};
pseq = seqidx;
}
else
{
// Otherwise, try to pick index registers last
static char sequence[] = {AX,CX,DX,BX,SI,DI,BP,NOREG};
pseq = sequence;
}
}
u.benefit = 0;
for (int i = 0; pseq[i] != NOREG; i++)
{
unsigned reg = pseq[i];
// Symbols used as return values should only be mapped into return value registers
if (s == retsym && !(mask[reg] & (mAX | mXMM0)))
continue;
// If BP isn't available, can't assign to it
if (reg == BP && !(allregs & mBP))
continue;
#if 0 && TARGET_LINUX
// Need EBX for static pointer
if (reg == BX && !(allregs & mBX))
continue;
#endif
if (s->Sflags & GTbyte &&
!(mask[reg] & BYTEREGS))
continue;
int benefit = cgreg_benefit(s,reg,retsym);
#ifdef DEBUG
if (debugr)
{ printf(" %s",regstring[reg]);
vec_print(regrange[reg]);
printf(" %d\n",benefit);
}
#endif
if (benefit > u.benefit)
{ // successful assigning of lsw
unsigned regmsw = NOREG;
// Now assign MSW
if (pseqmsw)
{
for (unsigned regj = 0; 1; regj++)
{
regmsw = pseqmsw[regj];
if (regmsw == NOREG)
goto Ltried; // tried and failed to assign MSW
if (regmsw == reg) // can't assign msw and lsw to same reg
continue;
#ifdef DEBUG
if (debugr)
{ printf(".%s",regstring[regmsw]);
vec_println(regrange[regmsw]);
}
#endif
if (vec_disjoint(s->Slvreg,regrange[regmsw]))
break;
}
}
vec_copy(v,s->Slvreg);
u.benefit = benefit;
u.reglsw = reg;
u.regmsw = regmsw;
}
Ltried: ;
}
if (u.benefit > t.benefit)
{ t = u;
vec_copy(t.sym->Slvreg,v);
}
}
if (t.sym && t.benefit > 0)
{
cgreg_map(t.sym,t.regmsw,t.reglsw);
flag = TRUE;
}
/* See if any scratch registers have become available that we can use.
* Scratch registers are cheaper, as they don't need save/restore.
* All floating point registers are scratch registers, so no need
* to do this for them.
*/
if ((I32 || I64) && // not worth the bother for 16 bit code
!flag && // if haven't already assigned registers in this pass
(mfuncreg & ~fregsaved) & ALLREGS && // if unused non-floating scratch registers
!(funcsym_p->Sflags & SFLexit)) // don't need save/restore if function never returns
{
for (size_t si = 0; si < globsym.top; si++)
{ symbol *s = globsym.tab[si];
if (s->Sfl == FLreg && // if assigned to register
mask[s->Sreglsw] & fregsaved && // and that register is not scratch
type_size(s->Stype) <= REGSIZE && // don't bother with register pairs
!tyfloating(s->ty())) // don't assign floating regs to non-floating regs
{
s->Sreglsw = findreg((mfuncreg & ~fregsaved) & ALLREGS);
s->Sregm = mask[s->Sreglsw];
flag = TRUE;
#ifdef DEBUG
if (debugr)
printf("re-assigned '%s' to %s\n",s->Sident,regstring[s->Sreglsw]);
#endif
break;
}
}
}
vec_free(v);
return flag;
}
//////////////////////////////////////
// Qsort() comparison routine for array of pointers to Symbol's.
static int __cdecl weight_compare(const void *e1,const void *e2)
{ Symbol **psp1;
Symbol **psp2;
psp1 = (Symbol **)e1;
psp2 = (Symbol **)e2;
return (*psp2)->Sweight - (*psp1)->Sweight;
}
#endif