VirtualBox

source: vbox/trunk/include/iprt/bldprog-strtab-template.cpp.h@ 96550

Last change on this file since 96550 was 96550, checked in by vboxsync, 2 years ago

iprt/bldprog-strtab*.cpp.h: Increased the size dictionary to make use of character codes needed for string encoding as well as slot 0xff in ASCII mode (otherwise used for UTF-8 sequence escaping), however slot zero is not yet usable. Improved the compression a bit further by consindering one separator character following a word, thus using 'VERR_' instead of 'VERR'. The the iprt defines-only header claims a 53% compression rate, up from 38%. bugref:9726

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 41.7 KB
Line 
1/* $Id: bldprog-strtab-template.cpp.h 96550 2022-08-30 01:04:57Z vboxsync $ */
2/** @file
3 * IPRT - Build Program - String Table Generator.
4 */
5
6/*
7 * Copyright (C) 2006-2022 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*
39 * (Avoid include sanity checks as this is ring-3 only, C++ code.)
40 */
41#if defined(__cplusplus) && defined(IN_RING3)
42
43
44/*********************************************************************************************************************************
45* Defined Constants And Macros *
46*********************************************************************************************************************************/
47/** @def BLDPROG_STRTAB_MAX_STRLEN
48 * The max length of strings in the table. */
49#if !defined(BLDPROG_STRTAB_MAX_STRLEN) || defined(DOXYGEN_RUNNING)
50# define BLDPROG_STRTAB_MAX_STRLEN 256
51#endif
52
53/** @def BLDPROG_STRTAB_WITH_COMPRESSION
54 * Enables very simple string compression.
55 */
56#if defined(DOXYGEN_RUNNING)
57# define BLDPROG_STRTAB_WITH_COMPRESSION
58#endif
59
60/** @def BLDPROG_STRTAB_WITH_CAMEL_WORDS
61 * Modifies the string compression to look for camel case words.
62 */
63#if defined(DOXYGEN_RUNNING)
64# define BLDPROG_STRTAB_WITH_CAMEL_WORDS
65#endif
66
67/** @def BLDPROG_STRTAB_PURE_ASCII
68 * String compression assumes pure 7-bit ASCII and will fail on UTF-8 when this
69 * is defined. Otherwise, the compression code will require IPRT to link.
70 */
71#if defined(DOXYGEN_RUNNING)
72# define BLDPROG_STRTAB_PURE_ASCII
73#endif
74
75
76
77/*********************************************************************************************************************************
78* Header Files *
79*********************************************************************************************************************************/
80#include <iprt/stdarg.h>
81#include <iprt/ctype.h>
82#include <stdlib.h>
83#include <stdio.h>
84#include <string.h>
85
86#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
87# include <iprt/asm.h>
88# include <map>
89# include <iprt/sanitized/string>
90
91# define BLDPROG_STRTAB_WITH_WORD_SEP_ALTERNATIVE
92typedef struct BLDPROGWORDFREQSTATS
93{
94 uint32_t cWithoutSep; /**< Number of occurances without a separator. */
95# ifdef BLDPROG_STRTAB_WITH_WORD_SEP_ALTERNATIVE
96 uint32_t cWithSep; /**< Number of occurance with a separator. */
97 char chSep; /**< The separator. First come basis. */
98# endif
99} BLDPROGWORDFREQSTATS;
100
101typedef std::map<std::string, BLDPROGWORDFREQSTATS> BLDPROGWORDFREQMAP;
102
103#endif
104
105#include "../../src/VBox/Runtime/include/internal/strhash.h" /** @todo make this one public */
106
107
108/*********************************************************************************************************************************
109* Structures and Typedefs *
110*********************************************************************************************************************************/
111/**
112 * Build table string.
113 */
114typedef struct BLDPROGSTRING
115{
116 /** The string.
117 * @note This may be modified or replaced (allocated from heap) when
118 * compressing the string table. */
119 char *pszString;
120 /** The string hash value. */
121 uint32_t uHash;
122 /** The strint table offset. */
123 uint32_t offStrTab;
124 /** The string length. */
125 size_t cchString;
126 /** Pointer to the next string reference (same string table entry). */
127 struct BLDPROGSTRING *pNextRef;
128 /** Pointer to the next string with the same hash value (collision). */
129 struct BLDPROGSTRING *pNextCollision;
130
131} BLDPROGSTRING;
132/** Pointer to a string table string. */
133typedef BLDPROGSTRING *PBLDPROGSTRING;
134
135
136/** String table data. */
137typedef struct BLDPROGSTRTAB
138{
139 /** The size of g_papStrHash. */
140 size_t cStrHash;
141 /** String hash table. */
142 PBLDPROGSTRING *papStrHash;
143 /** Duplicate strings found by AddString. */
144 size_t cDuplicateStrings;
145 /** Total length of the unique strings (no terminators). */
146 size_t cchUniqueStrings;
147 /** Number of unique strings after AddString. */
148 size_t cUniqueStrings;
149 /** Number of collisions. */
150 size_t cCollisions;
151
152 /** Number of entries in apSortedStrings. */
153 size_t cSortedStrings;
154 /** The sorted string table. */
155 PBLDPROGSTRING *papSortedStrings;
156
157#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
158 /** The 256 words we've picked to be indexed by reference. */
159 BLDPROGSTRING aCompDict[256];
160 /** The frequency of the 256 dictionary entries. */
161 size_t auCompDictFreq[256];
162 /** Incoming strings pending compression. */
163 PBLDPROGSTRING *papPendingStrings;
164 /** Current number of entries in papStrPending. */
165 size_t cPendingStrings;
166 /** The allocated size of papPendingStrings. */
167 size_t cMaxPendingStrings;
168 /** Work frequency map.
169 * @todo rewrite in plain C. */
170 BLDPROGWORDFREQMAP Frequencies;
171 /** Map of characters used by input strings. */
172 uint64_t bmUsedChars[256/64];
173#endif
174
175 /** The string table. */
176 char *pachStrTab;
177 /** The actual string table size. */
178 size_t cchStrTab;
179} BLDPROGSTRTAB;
180typedef BLDPROGSTRTAB *PBLDPROGSTRTAB;
181
182#if RT_CLANG_PREREQ(4, 0) || RT_GNUC_PREREQ(4, 6)
183# pragma GCC diagnostic push
184# pragma GCC diagnostic ignored "-Wunused-function"
185#endif
186
187
188/**
189 * Initializes the strint table compiler.
190 *
191 * @returns success indicator (out of memory if false).
192 * @param pThis The strint table compiler instance.
193 * @param cMaxStrings The max number of strings we'll be adding.
194 */
195static bool BldProgStrTab_Init(PBLDPROGSTRTAB pThis, size_t cMaxStrings)
196{
197 pThis->cStrHash = 0;
198 pThis->papStrHash = NULL;
199 pThis->cDuplicateStrings = 0;
200 pThis->cchUniqueStrings = 0;
201 pThis->cUniqueStrings = 0;
202 pThis->cCollisions = 0;
203 pThis->cSortedStrings = 0;
204 pThis->papSortedStrings = NULL;
205 pThis->pachStrTab = NULL;
206 pThis->cchStrTab = 0;
207#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
208 memset(pThis->aCompDict, 0, sizeof(pThis->aCompDict));
209 pThis->papPendingStrings = NULL;
210 pThis->cPendingStrings = 0;
211 pThis->cMaxPendingStrings = cMaxStrings;
212 memset(pThis->bmUsedChars, 0, sizeof(pThis->bmUsedChars));
213 ASMBitSet(pThis->bmUsedChars, 0); /* Some parts of the code still thinks zero is a terminator, so don't use it for now. */
214# ifndef BLDPROG_STRTAB_PURE_ASCII
215 ASMBitSet(pThis->bmUsedChars, 0xff); /* Reserve escape byte for codepoints above 127. */
216# endif
217#endif
218
219 /*
220 * Allocate a hash table double the size of all strings (to avoid too
221 * many collisions). Add all strings to it, finding duplicates in the
222 * process.
223 */
224#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
225 cMaxStrings += RT_ELEMENTS(pThis->aCompDict);
226#endif
227 cMaxStrings *= 2;
228 pThis->papStrHash = (PBLDPROGSTRING *)calloc(sizeof(pThis->papStrHash[0]), cMaxStrings);
229 if (pThis->papStrHash)
230 {
231 pThis->cStrHash = cMaxStrings;
232#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
233 pThis->papPendingStrings = (PBLDPROGSTRING *)calloc(sizeof(pThis->papPendingStrings[0]), pThis->cMaxPendingStrings);
234 if (pThis->papPendingStrings)
235#endif
236 return true;
237
238#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
239 free(pThis->papStrHash);
240 pThis->papStrHash = NULL;
241#endif
242 }
243 return false;
244}
245
246
247#if 0 /* unused */
248static void BldProgStrTab_Delete(PBLDPROGSTRTAB pThis)
249{
250 free(pThis->papStrHash);
251 free(pThis->papSortedStrings);
252 free(pThis->pachStrTab);
253# ifdef BLDPROG_STRTAB_WITH_COMPRESSION
254 free(pThis->papPendingStrings);
255# endif
256 memset(pThis, 0, sizeof(*pThis));
257}
258#endif
259
260#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
261
262DECLINLINE(size_t) bldProgStrTab_compressorFindNextWord(const char *pszSrc, char ch, const char **ppszSrc)
263{
264 /*
265 * Skip leading word separators.
266 */
267# ifdef BLDPROG_STRTAB_WITH_CAMEL_WORDS
268 while ( ch == ' '
269 || ch == '-'
270 || ch == '+'
271 || ch == '_')
272# else
273 while (ch == ' ')
274# endif
275 ch = *++pszSrc;
276 if (ch)
277 {
278 /*
279 * Find end of word.
280 */
281 size_t cchWord = 1;
282# ifdef BLDPROG_STRTAB_WITH_CAMEL_WORDS
283 char chPrev = ch;
284 while ( (ch = pszSrc[cchWord]) != ' '
285 && ch != '\0'
286 && ch != '-'
287 && ch != '+'
288 && ch != '_'
289 && ( ch == chPrev
290 || !RT_C_IS_UPPER(ch)
291 || RT_C_IS_UPPER(chPrev)) )
292 {
293 chPrev = ch;
294 cchWord++;
295 }
296# else
297 while ((ch = pszSrc[cchWord]) != ' ' && ch != '\0')
298 cchWord++;
299# endif
300 *ppszSrc = pszSrc;
301 return cchWord;
302 }
303
304 *ppszSrc = pszSrc;
305 return 0;
306}
307
308
309/**
310 * Analyzes a string.
311 *
312 * @param pThis The strint table compiler instance.
313 * @param pStr The string to analyze.
314 */
315static void bldProgStrTab_compressorAnalyzeString(PBLDPROGSTRTAB pThis, PBLDPROGSTRING pStr)
316{
317 /*
318 * Mark all the string characters as used.
319 */
320 const char *psz = pStr->pszString;
321 char ch;
322 while ((ch = *psz++) != '\0')
323 ASMBitSet(pThis->bmUsedChars, (uint8_t)ch);
324
325 /*
326 * For now we just consider words.
327 */
328 psz = pStr->pszString;
329 while ((ch = *psz) != '\0')
330 {
331 size_t cchWord = bldProgStrTab_compressorFindNextWord(psz, ch, &psz);
332 if (cchWord > 1)
333 {
334 std::string strWord(psz, cchWord);
335 BLDPROGWORDFREQMAP::iterator it = pThis->Frequencies.find(strWord);
336 if (it != pThis->Frequencies.end())
337 {
338# ifdef BLDPROG_STRTAB_WITH_WORD_SEP_ALTERNATIVE
339 char const chSep = psz[cchWord];
340 if (chSep != '\0' && (it->second.chSep == chSep || it->second.chSep == '\0'))
341 {
342 it->second.chSep = chSep;
343 it->second.cWithSep++;
344 }
345 else
346# endif
347 it->second.cWithoutSep++;
348 }
349 else
350 {
351# ifdef BLDPROG_STRTAB_WITH_WORD_SEP_ALTERNATIVE
352 char const chSep = psz[cchWord];
353 if (chSep != '\0')
354 {
355 BLDPROGWORDFREQSTATS NewWord = { 0, 0, chSep };
356 pThis->Frequencies[strWord] = NewWord;
357 }
358 else
359# endif
360 {
361 static BLDPROGWORDFREQSTATS s_NewWord = { 0 };
362 pThis->Frequencies[strWord] = s_NewWord;
363 }
364 }
365
366# if 0 /** @todo need better accounting for overlapping alternatives before this can be enabled. */
367 /* Two words - immediate yields calc may lie when this enabled and we may pick the wrong words. */
368 if (ch == ' ')
369 {
370 ch = psz[++cchWord];
371 if (ch != ' ' && ch != '\0')
372 {
373 size_t const cchSaved = cchWord;
374
375 do
376 cchWord++;
377 while ((ch = psz[cchWord]) != ' ' && ch != '\0');
378
379 strWord = std::string(psz, cchWord);
380 BLDPROGWORDFREQMAP::iterator it = pThis->Frequencies.find(strWord);
381 if (it != pThis->Frequencies.end())
382 it->second += cchWord - 1;
383 else
384 pThis->Frequencies[strWord] = 0;
385
386 cchWord = cchSaved;
387 }
388 }
389# endif
390 }
391 else if (!cchWord)
392 break;
393
394 /* Advance. */
395 psz += cchWord;
396 }
397 pStr->cchString = psz - pStr->pszString;
398 if (pStr->cchString > BLDPROG_STRTAB_MAX_STRLEN)
399 {
400 fprintf(stderr, "error: String to long (%u)\n", (unsigned)pStr->cchString);
401 abort();
402 }
403}
404
405#endif /* BLDPROG_STRTAB_WITH_COMPRESSION */
406
407/**
408 * Adds a string to the hash table.
409 * @param pThis The strint table compiler instance.
410 * @param pStr The string.
411 */
412static void bldProgStrTab_AddStringToHashTab(PBLDPROGSTRTAB pThis, PBLDPROGSTRING pStr)
413{
414 pStr->pNextRef = NULL;
415 pStr->pNextCollision = NULL;
416 pStr->offStrTab = 0;
417 pStr->uHash = sdbm(pStr->pszString, &pStr->cchString);
418 if (pStr->cchString > BLDPROG_STRTAB_MAX_STRLEN)
419 {
420 fprintf(stderr, "error: String to long (%u)\n", (unsigned)pStr->cchString);
421 exit(RTEXITCODE_FAILURE);
422 }
423
424 size_t idxHash = pStr->uHash % pThis->cStrHash;
425 PBLDPROGSTRING pCur = pThis->papStrHash[idxHash];
426 if (!pCur)
427 pThis->papStrHash[idxHash] = pStr;
428 else
429 {
430 /* Look for matching string. */
431 do
432 {
433 if ( pCur->uHash == pStr->uHash
434 && pCur->cchString == pStr->cchString
435 && memcmp(pCur->pszString, pStr->pszString, pStr->cchString) == 0)
436 {
437 pStr->pNextRef = pCur->pNextRef;
438 pCur->pNextRef = pStr;
439 pThis->cDuplicateStrings++;
440 return;
441 }
442 pCur = pCur->pNextCollision;
443 } while (pCur != NULL);
444
445 /* No matching string, insert. */
446 pThis->cCollisions++;
447 pStr->pNextCollision = pThis->papStrHash[idxHash];
448 pThis->papStrHash[idxHash] = pStr;
449 }
450
451 pThis->cUniqueStrings++;
452 pThis->cchUniqueStrings += pStr->cchString;
453}
454
455
456/**
457 * Adds a string to the string table.
458 *
459 * @param pThis The strint table compiler instance.
460 * @param pStr The string.
461 */
462static void BldProgStrTab_AddString(PBLDPROGSTRTAB pThis, PBLDPROGSTRING pStr)
463{
464#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
465 bldProgStrTab_compressorAnalyzeString(pThis, pStr);
466 if (pThis->cPendingStrings < pThis->cMaxPendingStrings)
467 pThis->papPendingStrings[pThis->cPendingStrings++] = pStr;
468 else
469 abort();
470#else
471 bldProgStrTab_AddStringToHashTab(pThis, pStr);
472#endif
473}
474
475
476/**
477 * Adds a string to the string table.
478 *
479 * @param pThis The strint table compiler instance.
480 * @param pStr The string entry (uninitialized).
481 * @param psz The string, will be duplicated if compression is enabled.
482 */
483DECLINLINE(void) BldProgStrTab_AddStringDup(PBLDPROGSTRTAB pThis, PBLDPROGSTRING pStr, const char *psz)
484{
485#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
486 pStr->pszString = strdup(psz);
487 if (!pStr->pszString)
488 abort();
489#else
490 pStr->pszString = (char *)psz;
491#endif
492 BldProgStrTab_AddString(pThis, pStr);
493}
494
495#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
496
497/**
498 * Copies @a cchSrc chars from @a pchSrc to @a pszDst, escaping special
499 * sequences.
500 *
501 * @returns New @a pszDst position, NULL if invalid source encoding.
502 * @param pszDst The destination buffer.
503 * @param pszSrc The source buffer.
504 * @param cchSrc How much to copy.
505 */
506static char *bldProgStrTab_compressorCopyAndEscape(char *pszDst, const char *pszSrc, size_t cchSrc)
507{
508 while (cchSrc-- > 0)
509 {
510 char ch = *pszSrc;
511 if (!((unsigned char)ch & 0x80))
512 {
513 *pszDst++ = ch;
514 pszSrc++;
515 }
516 else
517 {
518# ifdef BLDPROG_STRTAB_PURE_ASCII
519 fprintf(stderr, "error: unexpected char value %#x\n", ch);
520 return NULL;
521# else
522 RTUNICP uc;
523 int rc = RTStrGetCpEx(&pszSrc, &uc);
524 if (RT_SUCCESS(rc))
525 {
526 *pszDst++ = (unsigned char)0xff; /* escape single code point. */
527 pszDst = RTStrPutCp(pszDst, uc);
528 }
529 else
530 {
531 fprintf(stderr, "Error: RTStrGetCpEx failed with rc=%d\n", rc);
532 return NULL;
533 }
534# endif
535 }
536 }
537 return pszDst;
538}
539
540
541/**
542 * Replaces the dictionary words and escapes non-ascii chars in a string.
543 *
544 * @param pThis The strint table compiler instance.
545 * @param pString The string to fixup.
546 */
547static bool bldProgStrTab_compressorFixupString(PBLDPROGSTRTAB pThis, PBLDPROGSTRING pStr)
548{
549 char szNew[BLDPROG_STRTAB_MAX_STRLEN * 2];
550 char *pszDst = szNew;
551 const char *pszSrc = pStr->pszString;
552 const char *pszSrcEnd = pszSrc + pStr->cchString;
553
554 char ch;
555 while ((ch = *pszSrc) != '\0')
556 {
557 const char * const pszSrcUncompressed = pszSrc;
558 size_t cchWord = bldProgStrTab_compressorFindNextWord(pszSrc, ch, &pszSrc);
559 size_t cchSrcUncompressed = pszSrc - pszSrcUncompressed;
560 if (cchSrcUncompressed > 0)
561 {
562 pszDst = bldProgStrTab_compressorCopyAndEscape(pszDst, pszSrcUncompressed, cchSrcUncompressed);
563 if (!pszDst)
564 return false;
565 }
566 if (!cchWord)
567 break;
568
569 /* Check for g_aWord matches. */
570 if (cchWord > 1)
571 {
572 size_t cchMax = pszSrcEnd - pszSrc;
573 for (unsigned i = 0; i < RT_ELEMENTS(pThis->aCompDict); i++)
574 {
575 size_t cchLen = pThis->aCompDict[i].cchString;
576 if ( cchLen >= cchWord
577 && cchLen <= cchMax
578 && memcmp(pThis->aCompDict[i].pszString, pszSrc, cchLen) == 0)
579 {
580 *pszDst++ = (unsigned char)i;
581 pszSrc += cchLen;
582 cchWord = 0;
583 break;
584 }
585 }
586 }
587
588 if (cchWord > 0)
589 {
590 /* Copy the current word. */
591 pszDst = bldProgStrTab_compressorCopyAndEscape(pszDst, pszSrc, cchWord);
592 if (!pszDst)
593 return false;
594 pszSrc += cchWord;
595 }
596 }
597
598 /* Just terminate it now. */
599 *pszDst = '\0';
600
601 /*
602 * Update the string.
603 */
604 size_t cchNew = pszDst - &szNew[0];
605 if (cchNew > pStr->cchString)
606 {
607 pStr->pszString = (char *)malloc(cchNew + 1);
608 if (!pStr->pszString)
609 {
610 fprintf(stderr, "Out of memory!\n");
611 return false;
612 }
613 }
614 memcpy(pStr->pszString, szNew, cchNew + 1);
615 pStr->cchString = cchNew;
616
617 return true;
618}
619
620
621/**
622 * Entry in SortedDictionary.
623 *
624 * Uses variable length string member, so not class and allocated via malloc.
625 */
626struct SortedDictionaryEntry
627{
628 size_t m_cchGain;
629 size_t m_cchString;
630 char m_szString[RT_FLEXIBLE_ARRAY];
631
632 /** Allocates and initializes a new entry. */
633 static SortedDictionaryEntry *allocate(const char *a_pch, size_t a_cch, size_t a_cchGain, char a_chSep)
634 {
635 size_t cbString = a_cch + !!a_chSep + 1;
636 SortedDictionaryEntry *pNew = (SortedDictionaryEntry *)malloc(RT_UOFFSETOF(SortedDictionaryEntry, m_szString) + cbString);
637 if (pNew)
638 {
639 pNew->m_cchGain = a_cchGain;
640 memcpy(pNew->m_szString, a_pch, a_cch);
641 if (a_chSep)
642 pNew->m_szString[a_cch++] = a_chSep;
643 pNew->m_szString[a_cch] = '\0';
644 pNew->m_cchString = a_cch;
645 }
646 return pNew;
647 }
648
649
650 /** Compares this dictionary entry with an incoming one.
651 * @retval -1 if this entry is of less worth than the new one.
652 * @retval 0 if this entry is of equal worth to the new one.
653 * @retval +1 if this entry is of more worth than the new one.
654 */
655 int compare(size_t a_cchGain, size_t a_cchString)
656 {
657 /* Higher gain is preferred of course: */
658 if (m_cchGain < a_cchGain)
659 return -1;
660 if (m_cchGain > a_cchGain)
661 return 1;
662
663 /* Gain is the same. Prefer the shorter string, as it will result in a shorter string table: */
664 if (m_cchString > a_cchString)
665 return -1;
666 if (m_cchString < a_cchString)
667 return 1;
668 return 0;
669 }
670};
671
672
673/**
674 * Insertion sort dictionary that keeps the 256 best words.
675 *
676 * Used by bldProgStrTab_compressorDoStringCompression to pick the dictionary
677 * words.
678 */
679class SortedDictionary
680{
681public:
682 size_t m_cEntries;
683 SortedDictionaryEntry *m_apEntries[256];
684
685 SortedDictionary()
686 : m_cEntries(0)
687 {
688 for (size_t i = 0; i < RT_ELEMENTS(m_apEntries); i++)
689 m_apEntries[i] = NULL;
690 }
691
692 ~SortedDictionary()
693 {
694 while (m_cEntries > 0)
695 {
696 free(m_apEntries[--m_cEntries]);
697 m_apEntries[m_cEntries] = NULL;
698 }
699 }
700
701
702 /**
703 * Inserts a new entry, if it's worth it.
704 * @returns true on succes, false if out of memory.
705 */
706 bool insert(const char *a_pchString, size_t a_cchStringBase, size_t a_cchGain, char a_chSep = 0)
707 {
708 size_t const cchString = a_cchStringBase + (a_chSep + 1);
709
710 /*
711 * Drop the insert if the symbol table is full and the insert is less worth the last entry:
712 */
713 if ( m_cEntries >= RT_ELEMENTS(m_apEntries)
714 && m_apEntries[RT_ELEMENTS(m_apEntries) - 1]->compare(a_cchGain, cchString) >= 0)
715 return true;
716
717 /*
718 * Create a new entry to insert.
719 */
720 SortedDictionaryEntry *pNewEntry = SortedDictionaryEntry::allocate(a_pchString, a_cchStringBase, a_cchGain, a_chSep);
721 if (!pNewEntry)
722 return false;
723
724 /*
725 * Find the insert point.
726 */
727 if (m_cEntries == 0)
728 {
729 m_apEntries[0] = pNewEntry;
730 m_cEntries = 1;
731 }
732 else
733 {
734 /* If table is full, drop the last entry before we start (already made
735 sure the incoming entry is preferable to the one were dropping): */
736 if (m_cEntries >= RT_ELEMENTS(m_apEntries))
737 {
738 free(m_apEntries[RT_ELEMENTS(m_apEntries) - 1]);
739 m_apEntries[RT_ELEMENTS(m_apEntries) - 1] = NULL;
740 m_cEntries = RT_ELEMENTS(m_apEntries) - 1;
741 }
742
743 /* Find where to insert the new entry: */
744 /** @todo use binary search. */
745 size_t i = m_cEntries;
746 while (i > 0 && m_apEntries[i - 1]->compare(a_cchGain, cchString) < 0)
747 i--;
748
749 /* Shift entries to make room and insert the new entry. */
750 if (i < m_cEntries)
751 memmove(&m_apEntries[i + 1], &m_apEntries[i], (m_cEntries - i) * sizeof(m_apEntries[0]));
752 m_apEntries[i] = pNewEntry;
753 m_cEntries++;
754 }
755 return true;
756 }
757};
758
759
760/**
761 * Compresses the vendor and product strings.
762 *
763 * This is very very simple (a lot less work than the string table for
764 * instance).
765 */
766static bool bldProgStrTab_compressorDoStringCompression(PBLDPROGSTRTAB pThis, bool fVerbose)
767{
768 /*
769 * Sort the frequency analyzis result and pick the top entries for any
770 * available dictionary slots.
771 */
772 SortedDictionary SortedDict;
773 for (BLDPROGWORDFREQMAP::iterator it = pThis->Frequencies.begin(); it != pThis->Frequencies.end(); ++it)
774 {
775 bool fInsert;
776 size_t const cchString = it->first.length();
777# ifndef BLDPROG_STRTAB_WITH_WORD_SEP_ALTERNATIVE
778 size_t const cchGainWithout = it->second.cWithoutSep * cchString;
779# else
780 size_t const cchGainWithout = (it->second.cWithoutSep + it->second.cWithSep) * cchString;
781 size_t const cchGainWith = it->second.cWithSep * (cchString + 1);
782 if (cchGainWith > cchGainWithout)
783 fInsert = SortedDict.insert(it->first.c_str(), cchString, cchGainWith, it->second.chSep);
784 else
785# endif
786 fInsert = SortedDict.insert(it->first.c_str(), cchString, cchGainWithout);
787 if (!fInsert)
788 return false;
789 }
790
791 size_t cb = 0;
792 size_t cWords = 0;
793 size_t iDict = 0;
794 for (size_t i = 0; i < RT_ELEMENTS(pThis->aCompDict); i++)
795 {
796 char szTmp[2] = { (char)i, '\0' };
797 const char *psz = szTmp;
798 if ( ASMBitTest(pThis->bmUsedChars, (int32_t)i)
799 || iDict >= SortedDict.m_cEntries)
800 {
801 /* character entry */
802 pThis->auCompDictFreq[i] = 0;
803 pThis->aCompDict[i].cchString = 1;
804 }
805 else
806 {
807 /* word entry */
808 cb += SortedDict.m_apEntries[iDict]->m_cchGain;
809 pThis->auCompDictFreq[i] = SortedDict.m_apEntries[iDict]->m_cchGain;
810 pThis->aCompDict[i].cchString = SortedDict.m_apEntries[iDict]->m_cchString;
811 psz = SortedDict.m_apEntries[iDict]->m_szString;
812 cWords++;
813 iDict++;
814 }
815 pThis->aCompDict[i].pszString = (char *)malloc(pThis->aCompDict[i].cchString + 1);
816 if (pThis->aCompDict[i].pszString)
817 memcpy(pThis->aCompDict[i].pszString, psz, pThis->aCompDict[i].cchString + 1);
818 else
819 return false;
820 }
821
822 if (fVerbose)
823 printf("debug: Estimated string compression saving: %u bytes\n"
824 "debug: %u words, %u characters\n"
825 , (unsigned)cb, (unsigned)cWords, (unsigned)(RT_ELEMENTS(pThis->aCompDict) - cWords));
826
827 /*
828 * Rework the strings.
829 */
830 size_t cchOld = 0;
831 size_t cchOldMax = 0;
832 size_t cchOldMin = BLDPROG_STRTAB_MAX_STRLEN;
833 size_t cchNew = 0;
834 size_t cchNewMax = 0;
835 size_t cchNewMin = BLDPROG_STRTAB_MAX_STRLEN;
836 size_t i = pThis->cPendingStrings;
837 while (i-- > 0)
838 {
839 PBLDPROGSTRING pCurStr = pThis->papPendingStrings[i];
840 cchOld += pCurStr->cchString;
841 if (pCurStr->cchString > cchOldMax)
842 cchOldMax = pCurStr->cchString;
843 if (pCurStr->cchString < cchOldMin)
844 cchOldMin = pCurStr->cchString;
845
846 if (!bldProgStrTab_compressorFixupString(pThis, pCurStr))
847 return false;
848
849 cchNew += pCurStr->cchString;
850 if (pCurStr->cchString > cchNewMax)
851 cchNewMax = pCurStr->cchString;
852 if (pCurStr->cchString < cchNewMin)
853 cchNewMin = pCurStr->cchString;
854
855 bldProgStrTab_AddStringToHashTab(pThis, pCurStr);
856 }
857
858 /*
859 * Do debug stats.
860 */
861 if (fVerbose)
862 {
863 for (i = 0; i < RT_ELEMENTS(pThis->aCompDict); i++)
864 cchNew += pThis->aCompDict[i].cchString + 1;
865
866 printf("debug: Strings: original: %u bytes; compressed: %u bytes;", (unsigned)cchOld, (unsigned)cchNew);
867 if (cchNew < cchOld)
868 printf(" saving %u bytes (%u%%)\n", (unsigned)(cchOld - cchNew), (unsigned)((cchOld - cchNew) * 100 / cchOld));
869 else
870 printf(" wasting %u bytes!\n", (unsigned)(cchOld - cchNew));
871 printf("debug: Original string lengths: average %u; min %u; max %u\n",
872 (unsigned)(cchOld / pThis->cPendingStrings), (unsigned)cchOldMin, (unsigned)cchOldMax);
873 printf("debug: Compressed string lengths: average %u; min %u; max %u\n",
874 (unsigned)(cchNew / pThis->cPendingStrings), (unsigned)cchNewMin, (unsigned)cchNewMax);
875 }
876
877 return true;
878}
879
880#endif /* BLDPROG_STRTAB_WITH_COMPRESSION */
881
882/**
883 * Inserts a string into g_apUniqueStrings.
884 * @param pThis The strint table compiler instance.
885 * @param pStr The string.
886 */
887static void bldProgStrTab_InsertUniqueString(PBLDPROGSTRTAB pThis, PBLDPROGSTRING pStr)
888{
889 size_t iIdx;
890 size_t iEnd = pThis->cSortedStrings;
891 if (iEnd)
892 {
893 size_t iStart = 0;
894 for (;;)
895 {
896 iIdx = iStart + (iEnd - iStart) / 2;
897 if (pThis->papSortedStrings[iIdx]->cchString < pStr->cchString)
898 {
899 if (iIdx <= iStart)
900 break;
901 iEnd = iIdx;
902 }
903 else if (pThis->papSortedStrings[iIdx]->cchString > pStr->cchString)
904 {
905 if (++iIdx >= iEnd)
906 break;
907 iStart = iIdx;
908 }
909 else
910 break;
911 }
912
913 if (iIdx != pThis->cSortedStrings)
914 memmove(&pThis->papSortedStrings[iIdx + 1], &pThis->papSortedStrings[iIdx],
915 (pThis->cSortedStrings - iIdx) * sizeof(pThis->papSortedStrings[iIdx]));
916 }
917 else
918 iIdx = 0;
919
920 pThis->papSortedStrings[iIdx] = pStr;
921 pThis->cSortedStrings++;
922}
923
924
925/**
926 * Compiles the string table after the string has been added.
927 *
928 * This will save space by dropping string terminators, eliminating duplicates
929 * and try find strings that are sub-strings of others.
930 *
931 * Will initialize the StrRef of all StrTabString instances.
932 *
933 * @returns success indicator (error flagged).
934 * @param pThis The strint table compiler instance.
935 */
936static bool BldProgStrTab_CompileIt(PBLDPROGSTRTAB pThis, bool fVerbose)
937{
938#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
939 /*
940 * Do the compression and add all the compressed strings to the table.
941 */
942 if (!bldProgStrTab_compressorDoStringCompression(pThis, fVerbose))
943 return false;
944
945 /*
946 * Add the dictionary strings.
947 */
948 for (unsigned i = 0; i < RT_ELEMENTS(pThis->aCompDict); i++)
949 if (pThis->aCompDict[i].cchString > 1)
950 bldProgStrTab_AddStringToHashTab(pThis, &pThis->aCompDict[i]);
951# ifdef RT_STRICT
952 else if (pThis->aCompDict[i].cchString != 1)
953 abort();
954# endif
955#endif
956 if (fVerbose)
957 printf("debug: %u unique strings (%u bytes), %u duplicates, %u collisions\n",
958 (unsigned)pThis->cUniqueStrings, (unsigned)pThis->cchUniqueStrings,
959 (unsigned)pThis->cDuplicateStrings, (unsigned)pThis->cCollisions);
960
961 /*
962 * Create papSortedStrings from the hash table. The table is sorted by
963 * string length, with the longer strings first, so that we increase our
964 * chances of locating duplicate substrings.
965 */
966 pThis->papSortedStrings = (PBLDPROGSTRING *)malloc(sizeof(pThis->papSortedStrings[0]) * pThis->cUniqueStrings);
967 if (!pThis->papSortedStrings)
968 return false;
969 pThis->cSortedStrings = 0;
970 size_t idxHash = pThis->cStrHash;
971 while (idxHash-- > 0)
972 {
973 PBLDPROGSTRING pCur = pThis->papStrHash[idxHash];
974 if (pCur)
975 {
976 do
977 {
978 bldProgStrTab_InsertUniqueString(pThis, pCur);
979 pCur = pCur->pNextCollision;
980 } while (pCur);
981 }
982 }
983
984 /*
985 * Create the actual string table.
986 */
987 pThis->pachStrTab = (char *)malloc(pThis->cchUniqueStrings + 1);
988 if (!pThis->pachStrTab)
989 return false;
990 pThis->cchStrTab = 0;
991 for (size_t i = 0; i < pThis->cSortedStrings; i++)
992 {
993 PBLDPROGSTRING pCur = pThis->papSortedStrings[i];
994 const char * const pszCur = pCur->pszString;
995 size_t const cchCur = pCur->cchString;
996 size_t offStrTab = pThis->cchStrTab;
997
998 /*
999 * See if the string is a substring already found in the string table.
1000 * Excluding the zero terminator increases the chances for this.
1001 */
1002 size_t cchLeft = pThis->cchStrTab >= cchCur ? pThis->cchStrTab - cchCur : 0;
1003 const char *pchLeft = pThis->pachStrTab;
1004 char const chFirst = *pszCur;
1005 while (cchLeft > 0)
1006 {
1007 const char *pchCandidate = (const char *)memchr(pchLeft, chFirst, cchLeft);
1008 if (!pchCandidate)
1009 break;
1010 if (memcmp(pchCandidate, pszCur, cchCur) == 0)
1011 {
1012 offStrTab = pchCandidate - pThis->pachStrTab;
1013 break;
1014 }
1015
1016 cchLeft -= pchCandidate + 1 - pchLeft;
1017 pchLeft = pchCandidate + 1;
1018 }
1019
1020 if (offStrTab == pThis->cchStrTab)
1021 {
1022 /*
1023 * See if the start of the string overlaps the end of the string table.
1024 */
1025 if (pThis->cchStrTab && cchCur > 1)
1026 {
1027 cchLeft = RT_MIN(pThis->cchStrTab, cchCur - 1);
1028 pchLeft = &pThis->pachStrTab[pThis->cchStrTab - cchLeft];
1029 while (cchLeft > 0)
1030 {
1031 const char *pchCandidate = (const char *)memchr(pchLeft, chFirst, cchLeft);
1032 if (!pchCandidate)
1033 break;
1034 cchLeft -= pchCandidate - pchLeft;
1035 pchLeft = pchCandidate;
1036 if (memcmp(pchLeft, pszCur, cchLeft) == 0)
1037 {
1038 size_t cchToCopy = cchCur - cchLeft;
1039 memcpy(&pThis->pachStrTab[offStrTab], &pszCur[cchLeft], cchToCopy);
1040 pThis->cchStrTab += cchToCopy;
1041 offStrTab = pchCandidate - pThis->pachStrTab;
1042 break;
1043 }
1044 cchLeft--;
1045 pchLeft++;
1046 }
1047 }
1048
1049 /*
1050 * If we didn't have any luck above, just append the string.
1051 */
1052 if (offStrTab == pThis->cchStrTab)
1053 {
1054 memcpy(&pThis->pachStrTab[offStrTab], pszCur, cchCur);
1055 pThis->cchStrTab += cchCur;
1056 }
1057 }
1058
1059 /*
1060 * Set the string table offset for all the references to this string.
1061 */
1062 do
1063 {
1064 pCur->offStrTab = (uint32_t)offStrTab;
1065 pCur = pCur->pNextRef;
1066 } while (pCur != NULL);
1067 }
1068
1069 if (fVerbose)
1070 printf("debug: String table: %u bytes\n", (unsigned)pThis->cchStrTab);
1071 return true;
1072}
1073
1074
1075#ifdef RT_STRICT
1076/**
1077 * Sanity checks a string table string.
1078 * @param pThis The strint table compiler instance.
1079 * @param pStr The string to check.
1080 */
1081static void BldProgStrTab_CheckStrTabString(PBLDPROGSTRTAB pThis, PBLDPROGSTRING pStr)
1082{
1083 if (pStr->cchString != strlen(pStr->pszString))
1084 abort();
1085 if (pStr->offStrTab >= pThis->cchStrTab)
1086 abort();
1087 if (pStr->offStrTab + pStr->cchString > pThis->cchStrTab)
1088 abort();
1089 if (memcmp(pStr->pszString, &pThis->pachStrTab[pStr->offStrTab], pStr->cchString) != 0)
1090 abort();
1091}
1092#endif
1093
1094
1095/**
1096 * Output the string table string in C string litteral fashion.
1097 *
1098 * @param pThis The strint table instance.
1099 * @param pString The string to print.
1100 * @param pOut The output stream.
1101 */
1102static void BldProgStrTab_PrintCStringLitteral(PBLDPROGSTRTAB pThis, PBLDPROGSTRING pString, FILE *pOut)
1103{
1104 const unsigned char *psz = (const unsigned char *)pString->pszString;
1105 unsigned char uch;
1106 while ((uch = *psz++) != '\0')
1107 {
1108#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
1109 if (pThis->aCompDict[uch].cchString == 1)
1110#else
1111 if (!(uch & 0x80))
1112#endif
1113 {
1114 if (uch != '\'' && uch != '\\')
1115 fputc((char)uch, pOut);
1116 else
1117 {
1118 fputc('\\', pOut);
1119 fputc((char)uch, pOut);
1120 }
1121 }
1122#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
1123# ifndef BLDPROG_STRTAB_PURE_ASCII
1124 else if (uch == 0xff)
1125 {
1126 RTUNICP uc = RTStrGetCp((const char *)psz);
1127 psz += RTStrCpSize(uc);
1128 fprintf(pOut, "\\u%04x", uc);
1129 }
1130# else
1131 else
1132 fputs(pThis->aCompDict[uch].pszString, pOut);
1133# endif
1134#else
1135 else
1136 fprintf(pOut, "\\x%02x", (unsigned)uch);
1137 NOREF(pThis);
1138#endif
1139 }
1140}
1141
1142
1143/**
1144 * Writes the string table code to the output stream.
1145 *
1146 * @param pThis The strint table compiler instance.
1147 * @param pOut The output stream.
1148 * @param pszScope The scoping ("static " or empty string),
1149 * including trailing space.
1150 * @param pszPrefix The variable prefix. Typically "g_" for C programs,
1151 * whereas C++ classes normally use "class::s_g".
1152 * @param pszBaseName The base name for the variables. The user
1153 * accessible variable will end up as just the
1154 * prefix and basename concatenated.
1155 */
1156static void BldProgStrTab_WriteStringTable(PBLDPROGSTRTAB pThis, FILE *pOut,
1157 const char *pszScope, const char *pszPrefix, const char *pszBaseName)
1158{
1159#ifdef RT_STRICT
1160 /*
1161 * Do some quick sanity checks while we're here.
1162 */
1163# ifdef BLDPROG_STRTAB_WITH_COMPRESSION
1164 for (unsigned i = 0; i < RT_ELEMENTS(pThis->aCompDict); i++)
1165 {
1166 if (ASMBitTest(pThis->bmUsedChars, (int32_t)i)
1167 ? pThis->aCompDict[i].cchString != 1 : pThis->aCompDict[i].cchString < 1)
1168 abort();
1169 if (pThis->aCompDict[i].cchString > 1)
1170 BldProgStrTab_CheckStrTabString(pThis, &pThis->aCompDict[i]);
1171 }
1172# endif
1173#endif
1174
1175 /*
1176 * Create a table for speeding up the character categorization.
1177 */
1178 uint8_t abCharCat[256];
1179 memset(abCharCat, 0, sizeof(abCharCat));
1180 abCharCat[(unsigned char)'\\'] = 1;
1181 abCharCat[(unsigned char)'\''] = 1;
1182 for (unsigned i = 0; i < 0x20; i++)
1183 abCharCat[i] = 2;
1184 for (unsigned i = 0x7f; i < 0x100; i++)
1185 abCharCat[i] = 2;
1186#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
1187 for (unsigned i = 0; i < 0x100; i++)
1188 if (!ASMBitTest(pThis->bmUsedChars, (int32_t)i)) /* Encode table references using '\xYY'. */
1189 abCharCat[i] = 2;
1190#endif
1191
1192 /*
1193 * We follow the sorted string table, one string per line.
1194 */
1195 fprintf(pOut,
1196 "#include <iprt/bldprog-strtab.h>\n"
1197 "\n"
1198 "static const char g_achStrTab%s[] =\n"
1199 "{\n",
1200 pszBaseName);
1201
1202 uint32_t off = 0;
1203 for (uint32_t i = 0; i < pThis->cSortedStrings; i++)
1204 {
1205 PBLDPROGSTRING pCur = pThis->papSortedStrings[i];
1206 uint32_t offEnd = pCur->offStrTab + (uint32_t)pCur->cchString;
1207 if (offEnd > off)
1208 {
1209 /* Comment with an uncompressed and more readable version of the string. */
1210 if (off == pCur->offStrTab)
1211 fprintf(pOut, "/* 0x%05x = \"", off);
1212 else
1213 fprintf(pOut, "/* 0X%05x = \"", off);
1214 BldProgStrTab_PrintCStringLitteral(pThis, pCur, pOut);
1215 fputs("\" */\n", pOut);
1216
1217 /* Must use char by char here or we may trigger the max string
1218 length limit in the compiler, */
1219 fputs(" ", pOut);
1220 for (; off < offEnd; off++)
1221 {
1222 unsigned char uch = pThis->pachStrTab[off];
1223 fputc('\'', pOut);
1224 if (abCharCat[uch] == 0)
1225 fputc(uch, pOut);
1226 else if (abCharCat[uch] != 1)
1227 fprintf(pOut, "\\x%02x", (unsigned)uch);
1228 else
1229 {
1230 fputc('\\', pOut);
1231 fputc(uch, pOut);
1232 }
1233 fputc('\'', pOut);
1234 fputc(',', pOut);
1235 }
1236 fputc('\n', pOut);
1237 }
1238 }
1239
1240 fprintf(pOut,
1241 "};\n"
1242 "AssertCompile(sizeof(g_achStrTab%s) == %#x);\n\n",
1243 pszBaseName, (unsigned)pThis->cchStrTab);
1244
1245#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
1246 /*
1247 * Write the compression dictionary.
1248 */
1249 fprintf(pOut,
1250 "static const RTBLDPROGSTRREF g_aCompDict%s[%u] = \n"
1251 "{\n",
1252 pszBaseName, (unsigned)RT_ELEMENTS(pThis->aCompDict));
1253 for (unsigned i = 0; i < RT_ELEMENTS(pThis->aCompDict); i++)
1254 if (pThis->aCompDict[i].cchString > 1)
1255 fprintf(pOut, " /*[%3u]=*/ { %#08x, %#04x }, // %6lu - %s\n", i,
1256 pThis->aCompDict[i].offStrTab, (unsigned)pThis->aCompDict[i].cchString,
1257 (unsigned long)pThis->auCompDictFreq[i], pThis->aCompDict[i].pszString);
1258# ifndef BLDPROG_STRTAB_PURE_ASCII
1259 else if (i == 0xff)
1260 fprintf(pOut, " /*[%3u]=*/ { 0x000000, 0x00 }, // UTF-8 escape\n", i);
1261# endif
1262 else if (i == 0)
1263 fprintf(pOut, " /*[%3u]=*/ { 0x000000, 0x00 }, // unused, because zero terminator\n", i);
1264 else if (i < 0x20)
1265 fprintf(pOut, " /*[%3u]=*/ { 0x000000, 0x00 }, // %02x\n", i, i);
1266 else
1267 fprintf(pOut, " /*[%3u]=*/ { 0x000000, 0x00 }, // '%c'\n", i, (char)i);
1268 fprintf(pOut, "};\n\n");
1269#endif
1270
1271
1272 /*
1273 * Write the string table data structure.
1274 */
1275 fprintf(pOut,
1276 "%sconst RTBLDPROGSTRTAB %s%s = \n"
1277 "{\n"
1278 " /*.pchStrTab = */ &g_achStrTab%s[0],\n"
1279 " /*.cchStrTab = */ sizeof(g_achStrTab%s),\n"
1280 ,
1281 pszScope, pszPrefix, pszBaseName, pszBaseName, pszBaseName);
1282#ifdef BLDPROG_STRTAB_WITH_COMPRESSION
1283 fprintf(pOut,
1284 " /*.cCompDict = */ %u,\n"
1285 " /*.paCompDict = */ &g_aCompDict%s[0]\n"
1286 "};\n"
1287# ifndef BLDPROG_STRTAB_PURE_ASCII /* 255 or 256 entries is how the decoder knows */
1288 , (unsigned)RT_ELEMENTS(pThis->aCompDict) - 1,
1289# else
1290 , (unsigned)RT_ELEMENTS(pThis->aCompDict),
1291# endif
1292 pszBaseName);
1293#else
1294 fprintf(pOut,
1295 " /*.cCompDict = */ 0,\n"
1296 " /*.paCompDict = */ NULL\n"
1297 "};\n");
1298#endif
1299}
1300
1301#if RT_CLANG_PREREQ(4, 0) || RT_GNUC_PREREQ(4, 6)
1302# pragma GCC diagnostic pop
1303#endif
1304
1305#endif /* __cplusplus && IN_RING3 */
1306
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette