#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/sysctl.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <unistd.h>
#include <assert.h>
#include <iostream>
#include <sstream>
#include <string>
#include <map>
#include <set>
#include <vector>
#include <algorithm>
#include "Options.h"
#include "ld.hpp"
#include "InputFiles.h"
#include "SymbolTable.h"
namespace ld {
namespace tool {
static ld::IndirectBindingTable* _s_indirectBindingTable = NULL;
SymbolTable::SymbolTable(const Options& opts, std::vector<const ld::Atom*>& ibt)
: _options(opts), _cstringTable(6151), _indirectBindingTable(ibt), _hasExternalTentativeDefinitions(false)
{
_s_indirectBindingTable = this;
}
size_t SymbolTable::ContentFuncs::operator()(const ld::Atom* atom) const
{
return atom->contentHash(*_s_indirectBindingTable);
}
bool SymbolTable::ContentFuncs::operator()(const ld::Atom* left, const ld::Atom* right) const
{
return (memcmp(left->rawContentPointer(), right->rawContentPointer(), left->size()) == 0);
}
size_t SymbolTable::CStringHashFuncs::operator()(const ld::Atom* atom) const
{
return atom->contentHash(*_s_indirectBindingTable);
}
bool SymbolTable::CStringHashFuncs::operator()(const ld::Atom* left, const ld::Atom* right) const
{
return (strcmp((char*)left->rawContentPointer(), (char*)right->rawContentPointer()) == 0);
}
size_t SymbolTable::UTF16StringHashFuncs::operator()(const ld::Atom* atom) const
{
return atom->contentHash(*_s_indirectBindingTable);
}
bool SymbolTable::UTF16StringHashFuncs::operator()(const ld::Atom* left, const ld::Atom* right) const
{
if ( left == right )
return true;
const void* leftContent = left->rawContentPointer();
const void* rightContent = right->rawContentPointer();
unsigned int amount = left->size()-2;
bool result = (memcmp(leftContent, rightContent, amount) == 0);
return result;
}
size_t SymbolTable::ReferencesHashFuncs::operator()(const ld::Atom* atom) const
{
return atom->contentHash(*_s_indirectBindingTable);
}
bool SymbolTable::ReferencesHashFuncs::operator()(const ld::Atom* left, const ld::Atom* right) const
{
return left->canCoalesceWith(*right, *_s_indirectBindingTable);
}
void SymbolTable::addDuplicateSymbol(const char *name, const ld::Atom *atom)
{
DuplicateSymbols::iterator symbolsIterator = _duplicateSymbols.find(name);
DuplicatedSymbolAtomList *atoms = NULL;
if (symbolsIterator != _duplicateSymbols.end()) {
atoms = symbolsIterator->second;
} else {
atoms = new std::vector<const ld::Atom *>;
_duplicateSymbols.insert(std::pair<const char *, DuplicatedSymbolAtomList *>(name, atoms));
}
bool found = false;
for (DuplicatedSymbolAtomList::iterator it = atoms->begin(); !found && it != atoms->end(); it++)
if (strcmp((*it)->file()->path(), atom->file()->path()) == 0)
found = true;
if (!found)
atoms->push_back(atom);
}
void SymbolTable::checkDuplicateSymbols() const
{
bool foundDuplicate = false;
for (DuplicateSymbols::const_iterator symbolIt = _duplicateSymbols.begin(); symbolIt != _duplicateSymbols.end(); symbolIt++) {
DuplicatedSymbolAtomList *atoms = symbolIt->second;
bool reportDuplicate;
if (_options.deadCodeStrip()) {
reportDuplicate = false;
for (DuplicatedSymbolAtomList::iterator it = atoms->begin(); !reportDuplicate && it != atoms->end(); it++) {
if ((*it)->live())
reportDuplicate = true;
}
} else {
reportDuplicate = true;
}
if (reportDuplicate) {
foundDuplicate = true;
fprintf(stderr, "duplicate symbol %s in:\n", symbolIt->first);
for (DuplicatedSymbolAtomList::iterator atomIt = atoms->begin(); atomIt != atoms->end(); atomIt++) {
fprintf(stderr, " %s\n", (*atomIt)->file()->path());
}
}
}
if (foundDuplicate)
throwf("%d duplicate symbol%s", (int)_duplicateSymbols.size(), _duplicateSymbols.size()==1?"":"s");
}
class NameCollisionResolution {
public:
NameCollisionResolution(const ld::Atom& a, const ld::Atom& b, bool ignoreDuplicates, const Options& options) : _atomA(a), _atomB(b), _options(options), _reportDuplicate(false), _ignoreDuplicates(ignoreDuplicates) {
pickAtom();
}
const ld::Atom& chosen() { return *_chosen; }
bool choseAtom(const ld::Atom& atom) { return _chosen == &atom; }
bool reportDuplicate() { return _reportDuplicate; }
private:
const ld::Atom& _atomA;
const ld::Atom& _atomB;
const Options& _options;
const ld::Atom* _chosen;
bool _reportDuplicate;
bool _ignoreDuplicates;
void pickAtom(const ld::Atom& atom) { _chosen = &atom; } void pickAtomA() { pickAtom(_atomA); } void pickAtomB() { pickAtom(_atomB); }
void pickAOrB(bool pickA) { if (pickA) pickAtomA(); else pickAtomB(); }
void pickHigherOrdinal() {
pickAOrB(_atomA.file()->ordinal() < _atomB.file()->ordinal());
}
void pickLowerOrdinal() {
pickAOrB(_atomA.file()->ordinal() > _atomB.file()->ordinal());
}
void pickLargerSize() {
if (_atomA.size() == _atomB.size())
pickLowerOrdinal();
else
pickAOrB(_atomA.size() > _atomB.size());
}
void pickGreaterAlignment() {
pickAOrB(_atomA.alignment().trailingZeros() > _atomB.alignment().trailingZeros());
}
void pickBetweenRegularAtoms() {
if ( _atomA.combine() == ld::Atom::combineByName ) {
if ( _atomB.combine() == ld::Atom::combineByName ) {
const bool aIsLTO = (_atomA.contentType() == ld::Atom::typeLTOtemporary);
const bool bIsLTO = (_atomB.contentType() == ld::Atom::typeLTOtemporary);
if ( aIsLTO != bIsLTO ) {
pickAOrB(!aIsLTO);
}
else {
if ( _atomA.autoHide() != _atomB.autoHide() ) {
pickAOrB(!_atomA.autoHide());
}
else if ( _atomA.autoHide() && _atomB.autoHide() ) {
pickGreaterAlignment();
}
else {
if ( _atomA.scope() != _atomB.scope() ) {
pickAOrB(_atomA.scope() == ld::Atom::scopeGlobal);
}
else {
pickGreaterAlignment();
}
}
}
}
else {
pickAtomB();
}
}
else {
if ( _atomB.combine() == ld::Atom::combineByName ) {
pickAtomA();
}
else {
if ( _atomA.section().type() == ld::Section::typeMachHeader ) {
pickAtomA();
}
else if ( _atomB.section().type() == ld::Section::typeMachHeader ) {
pickAtomB();
}
else {
if ( _ignoreDuplicates ) {
pickLowerOrdinal();
}
else {
_reportDuplicate = true;
}
}
}
}
}
void pickCommonsMode(const ld::Atom& dylib, const ld::Atom& proxy) {
assert(dylib.definition() == ld::Atom::definitionTentative);
assert(proxy.definition() == ld::Atom::definitionProxy);
switch ( _options.commonsMode() ) {
case Options::kCommonsIgnoreDylibs:
if ( _options.warnCommons() )
warning("using common symbol %s from %s and ignoring defintion from dylib %s",
proxy.name(), proxy.file()->path(), dylib.file()->path());
pickAtom(dylib);
break;
case Options::kCommonsOverriddenByDylibs:
if ( _options.warnCommons() )
warning("replacing common symbol %s from %s with true definition from dylib %s",
proxy.name(), proxy.file()->path(), dylib.file()->path());
pickAtom(proxy);
break;
case Options::kCommonsConflictsDylibsError:
throwf("common symbol %s from %s conflicts with defintion from dylib %s",
proxy.name(), proxy.file()->path(), dylib.file()->path());
}
}
void pickProxyAtom() {
if ( _atomA.combine() == ld::Atom::combineByName ) {
pickAtomB();
} else if ( _atomB.combine() == ld::Atom::combineByName ) {
pickAtomA();
} else {
throwf("symbol %s exported from both %s and %s\n", _atomA.name(), _atomA.file()->path(), _atomB.file()->path());
}
}
void pickAtom() {
switch (_atomA.definition()) {
case ld::Atom::definitionRegular:
switch (_atomB.definition()) {
case ld::Atom::definitionRegular:
pickBetweenRegularAtoms();
break;
case ld::Atom::definitionTentative:
if ( _atomB.size() > _atomA.size() ) {
const char* atomApath = (_atomA.file() != NULL) ? _atomA.file()->path() : "<internal>";
const char* atomBpath = (_atomB.file() != NULL) ? _atomB.file()->path() : "<internal>";
warning("tentative definition of '%s' with size %llu from '%s' is being replaced by real definition of smaller size %llu from '%s'",
_atomA.name(), _atomB.size(), atomBpath, _atomA.size(), atomApath);
}
pickAtomA();
break;
case ld::Atom::definitionAbsolute:
_reportDuplicate = true;
pickHigherOrdinal();
break;
case ld::Atom::definitionProxy:
pickAtomA();
break;
}
break;
case ld::Atom::definitionTentative:
switch (_atomB.definition()) {
case ld::Atom::definitionRegular:
if ( _atomA.size() > _atomB.size() ) {
const char* atomApath = (_atomA.file() != NULL) ? _atomA.file()->path() : "<internal>";
const char* atomBpath = (_atomB.file() != NULL) ? _atomB.file()->path() : "<internal>";
warning("tentative definition of '%s' with size %llu from '%s' is being replaced by real definition of smaller size %llu from '%s'",
_atomA.name(), _atomA.size(),atomApath, _atomB.size(), atomBpath);
}
pickAtomB();
break;
case ld::Atom::definitionTentative:
pickLargerSize();
break;
case ld::Atom::definitionAbsolute:
pickHigherOrdinal();
break;
case ld::Atom::definitionProxy:
pickCommonsMode(_atomA, _atomB);
break;
}
break;
case ld::Atom::definitionAbsolute:
switch (_atomB.definition()) {
case ld::Atom::definitionRegular:
_reportDuplicate = true;
pickHigherOrdinal();
break;
case ld::Atom::definitionTentative:
pickAtomA();
break;
case ld::Atom::definitionAbsolute:
_reportDuplicate = true;
pickHigherOrdinal();
break;
case ld::Atom::definitionProxy:
pickAtomA();
break;
}
break;
case ld::Atom::definitionProxy:
switch (_atomB.definition()) {
case ld::Atom::definitionRegular:
pickAtomB();
break;
case ld::Atom::definitionTentative:
pickCommonsMode(_atomB, _atomA);
break;
case ld::Atom::definitionAbsolute:
pickAtomB();
break;
case ld::Atom::definitionProxy:
pickProxyAtom();
break;
}
break;
}
}
};
bool SymbolTable::addByName(const ld::Atom& newAtom, bool ignoreDuplicates)
{
bool useNew = true;
assert(newAtom.name() != NULL);
const char* name = newAtom.name();
IndirectBindingSlot slot = this->findSlotForName(name);
const ld::Atom* existingAtom = _indirectBindingTable[slot];
if ( existingAtom != NULL ) {
assert(&newAtom != existingAtom);
NameCollisionResolution picker(newAtom, *existingAtom, ignoreDuplicates, _options);
if (picker.reportDuplicate()) {
addDuplicateSymbol(name, existingAtom);
addDuplicateSymbol(name, &newAtom);
}
useNew = picker.choseAtom(newAtom);
}
if ( useNew ) {
_indirectBindingTable[slot] = &newAtom;
if ( existingAtom != NULL ) {
markCoalescedAway(existingAtom);
}
if ( newAtom.scope() == ld::Atom::scopeGlobal ) {
if ( newAtom.definition() == ld::Atom::definitionTentative ) {
_hasExternalTentativeDefinitions = true;
}
}
}
else {
markCoalescedAway(&newAtom);
}
return useNew && (existingAtom != NULL);
}
bool SymbolTable::addByContent(const ld::Atom& newAtom)
{
bool useNew = true;
const ld::Atom* existingAtom;
IndirectBindingSlot slot = this->findSlotForContent(&newAtom, &existingAtom);
if ( existingAtom != NULL ) {
useNew = ( newAtom.alignment().trailingZeros() > existingAtom->alignment().trailingZeros() );
}
if ( useNew ) {
_indirectBindingTable[slot] = &newAtom;
if ( existingAtom != NULL )
markCoalescedAway(existingAtom);
}
else {
_indirectBindingTable[slot] = existingAtom;
if ( existingAtom != &newAtom )
markCoalescedAway(&newAtom);
}
return useNew && (existingAtom != NULL);
}
bool SymbolTable::addByReferences(const ld::Atom& newAtom)
{
bool useNew = true;
const ld::Atom* existingAtom;
IndirectBindingSlot slot = this->findSlotForReferences(&newAtom, &existingAtom);
if ( existingAtom != NULL ) {
useNew = ( newAtom.alignment().trailingZeros() > existingAtom->alignment().trailingZeros() );
}
if ( useNew ) {
_indirectBindingTable[slot] = &newAtom;
if ( existingAtom != NULL )
markCoalescedAway(existingAtom);
}
else {
if ( existingAtom != &newAtom )
markCoalescedAway(&newAtom);
}
return useNew && (existingAtom != NULL);
}
bool SymbolTable::add(const ld::Atom& atom, bool ignoreDuplicates)
{
assert(atom.scope() != ld::Atom::scopeTranslationUnit);
switch ( atom.combine() ) {
case ld::Atom::combineNever:
case ld::Atom::combineByName:
return this->addByName(atom, ignoreDuplicates);
break;
case ld::Atom::combineByNameAndContent:
return this->addByContent(atom);
break;
case ld::Atom::combineByNameAndReferences:
return this->addByReferences(atom);
break;
}
return false;
}
void SymbolTable::markCoalescedAway(const ld::Atom* atom)
{
(const_cast<ld::Atom*>(atom))->setCoalescedAway();
for (ld::Fixup::iterator fit=atom->fixupsBegin(), fend=atom->fixupsEnd(); fit != fend; ++fit) {
switch ( fit->kind ) {
case ld::Fixup::kindNoneGroupSubordinate:
case ld::Fixup::kindNoneGroupSubordinateFDE:
case ld::Fixup::kindNoneGroupSubordinateLSDA:
assert(fit->binding == ld::Fixup::bindingDirectlyBound);
this->markCoalescedAway(fit->u.target);
break;
default:
break;
}
}
}
struct StrcmpSorter {
bool operator() (const char* i,const char* j) {
if (i==NULL)
return true;
if (j==NULL)
return false;
return strcmp(i, j)<0;}
};
void SymbolTable::undefines(std::vector<const char*>& undefs)
{
for (NameToSlot::iterator it=_byNameTable.begin(); it != _byNameTable.end(); ++it) {
if ( _indirectBindingTable[it->second] == NULL )
undefs.push_back(it->first);
}
struct StrcmpSorter strcmpSorter;
std::sort(undefs.begin(), undefs.end(), strcmpSorter);
}
void SymbolTable::tentativeDefs(std::vector<const char*>& tents)
{
for (NameToSlot::iterator it=_byNameTable.begin(); it != _byNameTable.end(); ++it) {
const char* name = it->first;
const ld::Atom* atom = _indirectBindingTable[it->second];
if ( (atom != NULL) && (atom->definition() == ld::Atom::definitionTentative) )
tents.push_back(name);
}
std::sort(tents.begin(), tents.end());
}
bool SymbolTable::hasName(const char* name)
{
NameToSlot::iterator pos = _byNameTable.find(name);
if ( pos == _byNameTable.end() )
return false;
return (_indirectBindingTable[pos->second] != NULL);
}
SymbolTable::IndirectBindingSlot SymbolTable::findSlotForName(const char* name)
{
NameToSlot::iterator pos = _byNameTable.find(name);
if ( pos != _byNameTable.end() )
return pos->second;
SymbolTable::IndirectBindingSlot slot = _indirectBindingTable.size();
_indirectBindingTable.push_back(NULL);
_byNameTable[name] = slot;
_byNameReverseTable[slot] = name;
return slot;
}
void SymbolTable::removeDeadAtoms()
{
std::vector<const char*> namesToRemove;
for (NameToSlot::iterator it=_byNameTable.begin(); it != _byNameTable.end(); ++it) {
IndirectBindingSlot slot = it->second;
const ld::Atom* atom = _indirectBindingTable[slot];
if ( atom != NULL ) {
if ( !atom->live() && !atom->dontDeadStrip() ) {
_indirectBindingTable[slot] = NULL;
_byNameReverseTable.erase(slot);
namesToRemove.push_back(it->first);
}
}
}
for (std::vector<const char*>::iterator it = namesToRemove.begin(); it != namesToRemove.end(); ++it) {
_byNameTable.erase(*it);
}
for (ReferencesToSlot::iterator it=_nonLazyPointerTable.begin(); it != _nonLazyPointerTable.end(); ) {
const ld::Atom* atom = it->first;
assert(atom != NULL);
if ( !atom->live() && !atom->dontDeadStrip() )
it = _nonLazyPointerTable.erase(it);
else
++it;
}
for (CStringToSlot::iterator it=_cstringTable.begin(); it != _cstringTable.end(); ) {
const ld::Atom* atom = it->first;
assert(atom != NULL);
if ( !atom->live() && !atom->dontDeadStrip() )
it = _cstringTable.erase(it);
else
++it;
}
for (UTF16StringToSlot::iterator it=_utf16Table.begin(); it != _utf16Table.end(); ) {
const ld::Atom* atom = it->first;
assert(atom != NULL);
if ( !atom->live() && !atom->dontDeadStrip() )
it = _utf16Table.erase(it);
else
++it;
}
for (ReferencesToSlot::iterator it=_cfStringTable.begin(); it != _cfStringTable.end(); ) {
const ld::Atom* atom = it->first;
assert(atom != NULL);
if ( !atom->live() && !atom->dontDeadStrip() )
it = _cfStringTable.erase(it);
else
++it;
}
for (ContentToSlot::iterator it=_literal4Table.begin(); it != _literal4Table.end(); ) {
const ld::Atom* atom = it->first;
assert(atom != NULL);
if ( !atom->live() && !atom->dontDeadStrip() )
it = _literal4Table.erase(it);
else
++it;
}
for (ContentToSlot::iterator it=_literal8Table.begin(); it != _literal8Table.end(); ) {
const ld::Atom* atom = it->first;
assert(atom != NULL);
if ( !atom->live() && !atom->dontDeadStrip() )
it = _literal8Table.erase(it);
else
++it;
}
for (ContentToSlot::iterator it=_literal16Table.begin(); it != _literal16Table.end(); ) {
const ld::Atom* atom = it->first;
assert(atom != NULL);
if ( !atom->live() && !atom->dontDeadStrip() )
it = _literal16Table.erase(it);
else
++it;
}
}
SymbolTable::IndirectBindingSlot SymbolTable::findSlotForContent(const ld::Atom* atom, const ld::Atom** existingAtom)
{
SymbolTable::IndirectBindingSlot slot = 0;
UTF16StringToSlot::iterator upos;
CStringToSlot::iterator cspos;
ContentToSlot::iterator pos;
switch ( atom->section().type() ) {
case ld::Section::typeCString:
cspos = _cstringTable.find(atom);
if ( cspos != _cstringTable.end() ) {
*existingAtom = _indirectBindingTable[cspos->second];
return cspos->second;
}
slot = _indirectBindingTable.size();
_cstringTable[atom] = slot;
break;
case ld::Section::typeNonStdCString:
{
char segsect[64];
sprintf(segsect, "%s/%s", atom->section().segmentName(), atom->section().sectionName());
NameToMap::iterator mpos = _nonStdCStringSectionToMap.find(segsect);
CStringToSlot* map = NULL;
if ( mpos == _nonStdCStringSectionToMap.end() ) {
map = new CStringToSlot();
_nonStdCStringSectionToMap[strdup(segsect)] = map;
}
else {
map = mpos->second;
}
cspos = map->find(atom);
if ( cspos != map->end() ) {
*existingAtom = _indirectBindingTable[cspos->second];
return cspos->second;
}
slot = _indirectBindingTable.size();
map->operator[](atom) = slot;
}
break;
case ld::Section::typeUTF16Strings:
upos = _utf16Table.find(atom);
if ( upos != _utf16Table.end() ) {
*existingAtom = _indirectBindingTable[upos->second];
return upos->second;
}
slot = _indirectBindingTable.size();
_utf16Table[atom] = slot;
break;
case ld::Section::typeLiteral4:
pos = _literal4Table.find(atom);
if ( pos != _literal4Table.end() ) {
*existingAtom = _indirectBindingTable[pos->second];
return pos->second;
}
slot = _indirectBindingTable.size();
_literal4Table[atom] = slot;
break;
case ld::Section::typeLiteral8:
pos = _literal8Table.find(atom);
if ( pos != _literal8Table.end() ) {
*existingAtom = _indirectBindingTable[pos->second];
return pos->second;
}
slot = _indirectBindingTable.size();
_literal8Table[atom] = slot;
break;
case ld::Section::typeLiteral16:
pos = _literal16Table.find(atom);
if ( pos != _literal16Table.end() ) {
*existingAtom = _indirectBindingTable[pos->second];
return pos->second;
}
slot = _indirectBindingTable.size();
_literal16Table[atom] = slot;
break;
default:
assert(0 && "section type does not support coalescing by content");
}
_indirectBindingTable.push_back(atom);
*existingAtom = NULL;
return slot;
}
SymbolTable::IndirectBindingSlot SymbolTable::findSlotForReferences(const ld::Atom* atom, const ld::Atom** existingAtom)
{
SymbolTable::IndirectBindingSlot slot = 0;
ReferencesToSlot::iterator pos;
switch ( atom->section().type() ) {
case ld::Section::typeNonLazyPointer:
pos = _nonLazyPointerTable.find(atom);
if ( pos != _nonLazyPointerTable.end() ) {
*existingAtom = _indirectBindingTable[pos->second];
return pos->second;
}
slot = _indirectBindingTable.size();
_nonLazyPointerTable[atom] = slot;
break;
case ld::Section::typeCFString:
pos = _cfStringTable.find(atom);
if ( pos != _cfStringTable.end() ) {
*existingAtom = _indirectBindingTable[pos->second];
return pos->second;
}
slot = _indirectBindingTable.size();
_cfStringTable[atom] = slot;
break;
case ld::Section::typeObjCClassRefs:
pos = _objc2ClassRefTable.find(atom);
if ( pos != _objc2ClassRefTable.end() ) {
*existingAtom = _indirectBindingTable[pos->second];
return pos->second;
}
slot = _indirectBindingTable.size();
_objc2ClassRefTable[atom] = slot;
break;
case ld::Section::typeCStringPointer:
pos = _pointerToCStringTable.find(atom);
if ( pos != _pointerToCStringTable.end() ) {
*existingAtom = _indirectBindingTable[pos->second];
return pos->second;
}
slot = _indirectBindingTable.size();
_pointerToCStringTable[atom] = slot;
break;
case ld::Section::typeTLVPointers:
pos = _threadPointerTable.find(atom);
if ( pos != _threadPointerTable.end() ) {
*existingAtom = _indirectBindingTable[pos->second];
return pos->second;
}
slot = _indirectBindingTable.size();
_threadPointerTable[atom] = slot;
break;
default:
assert(0 && "section type does not support coalescing by references");
}
_indirectBindingTable.push_back(atom);
*existingAtom = NULL;
return slot;
}
const char* SymbolTable::indirectName(IndirectBindingSlot slot) const
{
assert(slot < _indirectBindingTable.size());
const ld::Atom* target = _indirectBindingTable[slot];
if ( target != NULL ) {
return target->name();
}
SlotToName::const_iterator pos = _byNameReverseTable.find(slot);
if ( pos != _byNameReverseTable.end() )
return pos->second;
assert(0);
return NULL;
}
const ld::Atom* SymbolTable::indirectAtom(IndirectBindingSlot slot) const
{
assert(slot < _indirectBindingTable.size());
return _indirectBindingTable[slot];
}
void SymbolTable::printStatistics()
{
int count[11];
for(unsigned int b=0; b < 11; ++b) {
count[b] = 0;
}
for(unsigned int i=0; i < _cstringTable.bucket_count(); ++i) {
unsigned int n = _cstringTable.bucket_size(i);
if ( n < 10 )
count[n] += 1;
else
count[10] += 1;
}
fprintf(stderr, "cstring table distribution\n");
for(unsigned int b=0; b < 11; ++b) {
fprintf(stderr, "%u buckets have %u elements\n", count[b], b);
}
fprintf(stderr, "indirect table size: %lu\n", _indirectBindingTable.size());
fprintf(stderr, "by-name table size: %lu\n", _byNameTable.size());
}
} }