Annotation of src/external/gpl3/gcc/dist/gcc/config/aarch64/driver-aarch64.c, Revision 1.12
1.1 mrg 1: /* Native CPU detection for aarch64.
1.11 mrg 2: Copyright (C) 2015-2020 Free Software Foundation, Inc.
1.1 mrg 3:
4: This file is part of GCC.
5:
6: GCC is free software; you can redistribute it and/or modify
7: it under the terms of the GNU General Public License as published by
8: the Free Software Foundation; either version 3, or (at your option)
9: any later version.
10:
11: GCC is distributed in the hope that it will be useful,
12: but WITHOUT ANY WARRANTY; without even the implied warranty of
13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14: GNU General Public License for more details.
15:
16: You should have received a copy of the GNU General Public License
17: along with GCC; see the file COPYING3. If not see
18: <http://www.gnu.org/licenses/>. */
19:
1.5 mrg 20: #define IN_TARGET_CODE 1
21:
1.1 mrg 22: #include "config.h"
23: #define INCLUDE_STRING
1.11 mrg 24: #define INCLUDE_SET
1.1 mrg 25: #include "system.h"
26: #include "coretypes.h"
27: #include "tm.h"
1.2 mrg 28: #include "diagnostic-core.h"
1.1 mrg 29:
30: /* Defined in common/config/aarch64/aarch64-common.c. */
1.11 mrg 31: std::string aarch64_get_extension_string_for_isa_flags (uint64_t, uint64_t);
1.1 mrg 32:
33: struct aarch64_arch_extension
34: {
35: const char *ext;
1.11 mrg 36: uint64_t flag;
1.1 mrg 37: const char *feat_string;
38: };
39:
1.6 mrg 40: #define AARCH64_OPT_EXTENSION(EXT_NAME, FLAG_CANONICAL, FLAGS_ON, FLAGS_OFF, \
41: SYNTHETIC, FEATURE_STRING) \
1.1 mrg 42: { EXT_NAME, FLAG_CANONICAL, FEATURE_STRING },
43: static struct aarch64_arch_extension aarch64_extensions[] =
44: {
45: #include "aarch64-option-extensions.def"
46: };
47:
48:
49: struct aarch64_core_data
50: {
51: const char* name;
52: const char* arch;
1.3 mrg 53: unsigned char implementer_id; /* Exactly 8 bits */
54: unsigned int part_no; /* 12 bits + 12 bits */
55: unsigned variant;
1.11 mrg 56: const uint64_t flags;
1.1 mrg 57: };
58:
1.3 mrg 59: #define AARCH64_BIG_LITTLE(BIG, LITTLE) \
60: (((BIG)&0xFFFu) << 12 | ((LITTLE) & 0xFFFu))
61: #define INVALID_IMP ((unsigned char) -1)
62: #define INVALID_CORE ((unsigned)-1)
63: #define ALL_VARIANTS ((unsigned)-1)
64:
65: #define AARCH64_CORE(CORE_NAME, CORE_IDENT, SCHED, ARCH, FLAGS, COSTS, IMP, PART, VARIANT) \
66: { CORE_NAME, #ARCH, IMP, PART, VARIANT, FLAGS },
1.1 mrg 67:
68: static struct aarch64_core_data aarch64_cpu_data[] =
69: {
70: #include "aarch64-cores.def"
1.3 mrg 71: { NULL, NULL, INVALID_IMP, INVALID_CORE, ALL_VARIANTS, 0 }
1.1 mrg 72: };
73:
74:
75: struct aarch64_arch_driver_info
76: {
77: const char* id;
78: const char* name;
1.11 mrg 79: const uint64_t flags;
1.1 mrg 80: };
81:
82: #define AARCH64_ARCH(NAME, CORE, ARCH_IDENT, ARCH_REV, FLAGS) \
83: { #ARCH_IDENT, NAME, FLAGS },
84:
85: static struct aarch64_arch_driver_info aarch64_arches[] =
86: {
87: #include "aarch64-arches.def"
88: {NULL, NULL, 0}
89: };
90:
91:
92: /* Return an aarch64_arch_driver_info for the architecture described
93: by ID, or NULL if ID describes something we don't know about. */
94:
95: static struct aarch64_arch_driver_info*
96: get_arch_from_id (const char* id)
97: {
98: unsigned int i = 0;
99:
100: for (i = 0; aarch64_arches[i].id != NULL; i++)
101: {
102: if (strcmp (id, aarch64_arches[i].id) == 0)
103: return &aarch64_arches[i];
104: }
105:
106: return NULL;
107: }
108:
1.3 mrg 109: /* Check wether the CORE array is the same as the big.LITTLE BL_CORE.
110: For an example CORE={0xd08, 0xd03} and
111: BL_CORE=AARCH64_BIG_LITTLE (0xd08, 0xd03) will return true. */
1.1 mrg 112:
113: static bool
1.3 mrg 114: valid_bL_core_p (unsigned int *core, unsigned int bL_core)
1.1 mrg 115: {
1.3 mrg 116: return AARCH64_BIG_LITTLE (core[0], core[1]) == bL_core
117: || AARCH64_BIG_LITTLE (core[1], core[0]) == bL_core;
118: }
119:
120: /* Returns the hex integer that is after ':' for the FIELD.
121: Returns -1 is returned if there was problem parsing the integer. */
122: static unsigned
1.11 mrg 123: parse_field (const std::string &field)
1.3 mrg 124: {
1.11 mrg 125: const char *rest = strchr (field.c_str (), ':');
126:
127: /* The line must be in the format of <name>:<value>, if it's not
128: then we have a weird format. */
129: if (rest == NULL)
130: return -1;
131:
1.3 mrg 132: char *after;
133: unsigned fint = strtol (rest + 1, &after, 16);
134: if (after == rest + 1)
135: return -1;
136: return fint;
1.1 mrg 137: }
138:
1.11 mrg 139: /* Returns the index of the ':' inside the FIELD which must be found
140: after the value of KEY. Returns string::npos if line does not contain
141: a field. */
142:
143: static size_t
144: find_field (const std::string &field, const std::string &key)
145: {
146: size_t key_pos, sep_pos;
147: key_pos = field.find (key);
148: if (key_pos == std::string::npos)
149: return std::string::npos;
150:
151: sep_pos = field.find (":", key_pos + 1);
152: if (sep_pos == std::string::npos)
153: return std::string::npos;
154:
155: return sep_pos;
156: }
157:
158: /* Splits and returns a string based on whitespace and return it as
159: part of a set. Empty strings are ignored. */
160:
161: static void
162: split_words (const std::string &val, std::set<std::string> &result)
163: {
164: size_t cur, prev = 0;
165: std::string word;
166: while ((cur = val.find_first_of (" \n", prev)) != std::string::npos)
167: {
168: word = val.substr (prev, cur - prev);
169: /* Skip adding empty words. */
170: if (!word.empty ())
171: result.insert (word);
172: prev = cur + 1;
173: }
174:
175: if (prev != cur)
176: result.insert (val.substr (prev));
177: }
178:
179: /* Read an entire line from F until '\n' or EOF. */
180:
181: static std::string
182: readline (FILE *f)
183: {
184: char *buf = NULL;
185: int size = 0;
186: int last = 0;
187: const int buf_size = 128;
188:
189: if (feof (f))
190: return std::string ();
191:
192: do
193: {
194: size += buf_size;
195: buf = (char*) xrealloc (buf, size);
196: gcc_assert (buf);
197: /* If fgets fails it returns NULL, but if it reaches EOF
198: with 0 characters read it also returns EOF. However
199: the condition on the loop would have broken out of the
200: loop in that case, and if we are in the first iteration
201: then the empty string is the correct thing to return. */
202: if (!fgets (buf + last, buf_size, f))
203: return std::string ();
204: /* If we're not at the end of the line then override the
205: \0 added by fgets. */
206: last = strnlen (buf, size) - 1;
207: }
208: while (!feof (f) && buf[last] != '\n');
209:
210: std::string result (buf);
211: free (buf);
212: return result;
213: }
214:
1.3 mrg 215: /* Return true iff ARR contains CORE, in either of the two elements. */
1.1 mrg 216:
217: static bool
1.3 mrg 218: contains_core_p (unsigned *arr, unsigned core)
1.1 mrg 219: {
1.3 mrg 220: if (arr[0] != INVALID_CORE)
1.1 mrg 221: {
1.3 mrg 222: if (arr[0] == core)
223: return true;
1.1 mrg 224:
1.3 mrg 225: if (arr[1] != INVALID_CORE)
226: return arr[1] == core;
1.1 mrg 227: }
228:
229: return false;
230: }
231:
232: /* This will be called by the spec parser in gcc.c when it sees
233: a %:local_cpu_detect(args) construct. Currently it will be called
234: with either "arch", "cpu" or "tune" as argument depending on if
235: -march=native, -mcpu=native or -mtune=native is to be substituted.
236:
237: It returns a string containing new command line parameters to be
238: put at the place of the above two options, depending on what CPU
239: this is executed. E.g. "-march=armv8-a" on a Cortex-A57 for
240: -march=native. If the routine can't detect a known processor,
241: the -march or -mtune option is discarded.
242:
243: For -mtune and -mcpu arguments it attempts to detect the CPU or
244: a big.LITTLE system.
245: ARGC and ARGV are set depending on the actual arguments given
246: in the spec. */
247:
1.2 mrg 248: #ifdef __NetBSD__
249: /* The NetBSD/arm64 platform does not export linux-style cpuinfo,
250: but the data is available via a sysctl(3) interface. */
251: #include <sys/param.h>
252: #include <sys/sysctl.h>
253: #include <aarch64/armreg.h>
1.4 mrg 254: #endif
1.2 mrg 255:
256: const char *
257: host_detect_local_cpu (int argc, const char **argv)
258: {
259: const char *res = NULL;
260: static const int num_exts = ARRAY_SIZE (aarch64_extensions);
1.10 rin 261: FILE *f = NULL;
1.2 mrg 262: bool arch = false;
263: bool tune = false;
264: bool cpu = false;
1.1 mrg 265: unsigned int i = 0;
1.10 rin 266: unsigned char imp = INVALID_IMP;
1.3 mrg 267: unsigned int cores[2] = { INVALID_CORE, INVALID_CORE };
1.1 mrg 268: unsigned int n_cores = 0;
1.3 mrg 269: unsigned int variants[2] = { ALL_VARIANTS, ALL_VARIANTS };
270: unsigned int n_variants = 0;
1.1 mrg 271: bool processed_exts = false;
1.11 mrg 272: uint64_t extension_flags = 0;
273: uint64_t default_flags = 0;
274: std::string buf;
275: size_t sep_pos = -1;
276: char *fcpu_info;
1.4 mrg 277:
1.1 mrg 278: gcc_assert (argc);
279:
280: if (!argv[0])
281: goto not_found;
282:
283: /* Are we processing -march, mtune or mcpu? */
284: arch = strcmp (argv[0], "arch") == 0;
285: if (!arch)
286: tune = strcmp (argv[0], "tune") == 0;
287:
288: if (!arch && !tune)
289: cpu = strcmp (argv[0], "cpu") == 0;
290:
291: if (!arch && !tune && !cpu)
292: goto not_found;
293:
1.7 mrg 294: #ifndef __NetBSD__
1.11 mrg 295: fcpu_info = getenv ("GCC_CPUINFO");
296: if (fcpu_info)
297: f = fopen (fcpu_info, "r");
298: else
299: f = fopen ("/proc/cpuinfo", "r");
1.1 mrg 300:
301: if (f == NULL)
302: goto not_found;
303:
304: /* Look through /proc/cpuinfo to determine the implementer
305: and then the part number that identifies a particular core. */
1.11 mrg 306: while (!(buf = readline (f)).empty ())
1.1 mrg 307: {
1.11 mrg 308: if (find_field (buf, "implementer") != std::string::npos)
1.1 mrg 309: {
1.3 mrg 310: unsigned cimp = parse_field (buf);
311: if (cimp == INVALID_IMP)
312: goto not_found;
313:
314: if (imp == INVALID_IMP)
315: imp = cimp;
316: /* FIXME: BIG.little implementers are always equal. */
317: else if (imp != cimp)
318: goto not_found;
319: }
1.11 mrg 320: else if (find_field (buf, "variant") != std::string::npos)
1.3 mrg 321: {
322: unsigned cvariant = parse_field (buf);
323: if (!contains_core_p (variants, cvariant))
324: {
325: if (n_variants == 2)
326: goto not_found;
1.1 mrg 327:
1.3 mrg 328: variants[n_variants++] = cvariant;
329: }
330: continue;
331: }
1.11 mrg 332: else if (find_field (buf, "part") != std::string::npos)
1.1 mrg 333: {
1.3 mrg 334: unsigned ccore = parse_field (buf);
335: if (!contains_core_p (cores, ccore))
336: {
337: if (n_cores == 2)
338: goto not_found;
1.1 mrg 339:
1.3 mrg 340: cores[n_cores++] = ccore;
341: }
1.1 mrg 342: continue;
343: }
1.11 mrg 344: else if (!tune && !processed_exts
345: && (sep_pos = find_field (buf, "Features")) != std::string::npos)
1.1 mrg 346: {
1.11 mrg 347: /* First create the list of features in the buffer. */
348: std::set<std::string> features;
349: /* Drop everything till the :. */
350: buf = buf.substr (sep_pos + 1);
351: split_words (buf, features);
352:
1.1 mrg 353: for (i = 0; i < num_exts; i++)
354: {
1.11 mrg 355: const std::string val (aarch64_extensions[i].feat_string);
1.6 mrg 356:
357: /* If the feature contains no HWCAPS string then ignore it for the
358: auto detection. */
1.11 mrg 359: if (val.empty ())
1.6 mrg 360: continue;
361:
1.1 mrg 362: bool enabled = true;
363:
364: /* This may be a multi-token feature string. We need
1.6 mrg 365: to match all parts, which could be in any order. */
1.11 mrg 366: std::set<std::string> tokens;
367: split_words (val, tokens);
368: std::set<std::string>::iterator it;
369:
370: /* Iterate till the first feature isn't found or all of them
371: are found. */
372: for (it = tokens.begin (); enabled && it != tokens.end (); ++it)
373: enabled = enabled && features.count (*it);
1.1 mrg 374:
375: if (enabled)
376: extension_flags |= aarch64_extensions[i].flag;
377: else
378: extension_flags &= ~(aarch64_extensions[i].flag);
379: }
380:
381: processed_exts = true;
382: }
383: }
384:
385: fclose (f);
386: f = NULL;
1.4 mrg 387: #else
388: unsigned int curcpu;
389: size_t len;
390: char impl_buf[8];
391: int mib[2], ncpu;
392:
393: mib[0] = CTL_HW;
394: mib[1] = HW_NCPU;
395: len = sizeof(ncpu);
396: if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1)
397: goto not_found;
398:
399: for (curcpu = 0; curcpu < ncpu; curcpu++)
400: {
401: char path[128];
402: struct aarch64_sysctl_cpu_id id;
403:
404: len = sizeof id;
405: snprintf(path, sizeof path, "machdep.cpu%d.cpu_id", curcpu);
406: if (sysctlbyname(path, &id, &len, NULL, 0) != 0)
407: goto not_found;
408:
409: unsigned cimp = __SHIFTOUT(id.ac_midr, MIDR_EL1_IMPL);
410: if (cimp == INVALID_IMP)
411: goto not_found;
412:
413: if (imp == INVALID_IMP)
414: imp = cimp;
415: /* FIXME: BIG.little implementers are always equal. */
416: else if (imp != cimp)
417: goto not_found;
418:
419: unsigned cvariant = __SHIFTOUT(id.ac_midr, MIDR_EL1_VARIANT);
420: if (!contains_core_p (variants, cvariant))
421: {
422: if (n_variants == 2)
423: goto not_found;
424:
425: variants[n_variants++] = cvariant;
426: }
427:
428: unsigned ccore = __SHIFTOUT(id.ac_midr, MIDR_EL1_PARTNUM);
429: if (!contains_core_p (cores, ccore))
430: {
431: if (n_cores == 2)
432: goto not_found;
433:
434: cores[n_cores++] = ccore;
435: }
436:
437: if (!tune && !processed_exts)
438: {
1.7 mrg 439: std::string exts;
440:
441: /* These are all the extensions from aarch64-option-extensions.def. */
442: if (__SHIFTOUT(id.ac_aa64pfr0, ID_AA64PFR0_EL1_FP) == ID_AA64PFR0_EL1_FP_IMPL)
443: {
444: exts += "fp ";
445: }
446: if (__SHIFTOUT(id.ac_aa64pfr0, ID_AA64PFR0_EL1_ADVSIMD) == ID_AA64PFR0_EL1_ADV_SIMD_IMPL)
447: {
448: exts += "asimd ";
449: }
1.8 mrg 450: #ifdef ID_AA64ISAR0_EL1_RDM
1.7 mrg 451: if (__SHIFTOUT(id.ac_aa64isar0, ID_AA64ISAR0_EL1_RDM) == ID_AA64ISAR0_EL1_RDM_SQRDML)
452: {
453: exts += "asimdrdm ";
454: }
455: if (__SHIFTOUT(id.ac_aa64isar0, ID_AA64ISAR0_EL1_DP) == ID_AA64ISAR0_EL1_DP_UDOT)
456: {
457: exts += "asimddp ";
458: }
459: if (__SHIFTOUT(id.ac_aa64isar0, ID_AA64ISAR0_EL1_FHM) == ID_AA64ISAR0_EL1_FHM_FMLAL)
460: {
461: exts += "asimdfml ";
462: }
1.8 mrg 463: #endif
1.7 mrg 464: if (__SHIFTOUT(id.ac_aa64isar0, ID_AA64ISAR0_EL1_AES) == ID_AA64ISAR0_EL1_AES_AES)
465: {
466: exts += "aes ";
467: }
468: if (__SHIFTOUT(id.ac_aa64isar0, ID_AA64ISAR0_EL1_AES) == ID_AA64ISAR0_EL1_AES_PMUL)
469: {
470: exts += "aes pmull ";
471: }
472: if (__SHIFTOUT(id.ac_aa64isar0, ID_AA64ISAR0_EL1_CRC32) == ID_AA64ISAR0_EL1_CRC32_CRC32X)
473: {
474: exts += "crc32 ";
475: }
1.8 mrg 476: #ifdef ID_AA64ISAR0_EL1_ATOMIC
1.7 mrg 477: if (__SHIFTOUT(id.ac_aa64isar0, ID_AA64ISAR0_EL1_ATOMIC) == ID_AA64ISAR0_EL1_ATOMIC_SWP)
478: {
479: exts += "atomics ";
480: }
1.8 mrg 481: #endif
1.7 mrg 482: if ((__SHIFTOUT(id.ac_aa64isar0, ID_AA64ISAR0_EL1_SHA1) & ID_AA64ISAR0_EL1_SHA1_SHA1CPMHSU) != 0)
483: {
484: exts += "sha1 ";
485: }
486: if ((__SHIFTOUT(id.ac_aa64isar0, ID_AA64ISAR0_EL1_SHA2) & ID_AA64ISAR0_EL1_SHA2_SHA256HSU) != 0)
487: {
488: exts += "sha2 ";
489: }
1.8 mrg 490: #ifdef ID_AA64ISAR0_EL1_SHA2_SHA512HSU
1.7 mrg 491: if ((__SHIFTOUT(id.ac_aa64isar0, ID_AA64ISAR0_EL1_SHA2) & ID_AA64ISAR0_EL1_SHA2_SHA512HSU) != 0)
492: {
493: exts += "sha512 ";
494: }
495: if ((__SHIFTOUT(id.ac_aa64isar0, ID_AA64ISAR0_EL1_SHA3) & ID_AA64ISAR0_EL1_SHA3_EOR3) != 0)
496: {
497: exts += "sha3 ";
498: }
499: if (__SHIFTOUT(id.ac_aa64isar0, ID_AA64ISAR0_EL1_SM3) == ID_AA64ISAR0_EL1_SM3_SM3)
500: {
501: exts += "sm3 ";
502: }
503: if (__SHIFTOUT(id.ac_aa64isar0, ID_AA64ISAR0_EL1_SM4) == ID_AA64ISAR0_EL1_SM4_SM4)
504: {
505: exts += "sm4 ";
506: }
507: if (__SHIFTOUT(id.ac_aa64pfr0, ID_AA64PFR0_EL1_SVE) == ID_AA64PFR0_EL1_SVE_IMPL)
508: {
509: exts += "sve ";
510: }
511: if (__SHIFTOUT(id.ac_aa64isar1, ID_AA64ISAR1_EL1_LRCPC) == ID_AA64ISAR1_EL1_LRCPC_PR)
512: {
513: exts += "lrcpc ";
514: }
1.8 mrg 515: #endif
1.7 mrg 516:
1.4 mrg 517: for (i = 0; i < num_exts; i++)
518: {
1.7 mrg 519: const char *p = aarch64_extensions[i].feat_string;
520:
521: /* If the feature contains no HWCAPS string then ignore it for the
522: auto detection. */
523: if (*p == '\0')
524: continue;
1.4 mrg 525:
1.7 mrg 526: bool enabled = true;
527:
528: /* This may be a multi-token feature string. We need
529: to match all parts, which could be in any order. */
1.12 ! mrg 530: size_t len = strlen (exts.c_str());
1.7 mrg 531: do
1.4 mrg 532: {
1.7 mrg 533: const char *end = strchr (p, ' ');
534: if (end == NULL)
535: end = strchr (p, '\0');
1.12 ! mrg 536: if (memmem (exts.c_str(), len, p, end - p) == NULL)
1.7 mrg 537: {
538: /* Failed to match this token. Turn off the
539: features we'd otherwise enable. */
540: enabled = false;
541: break;
542: }
543: if (*end == '\0')
544: break;
545: p = end + 1;
1.4 mrg 546: }
1.7 mrg 547: while (1);
1.4 mrg 548:
549: if (enabled)
550: extension_flags |= aarch64_extensions[i].flag;
551: else
552: extension_flags &= ~(aarch64_extensions[i].flag);
553: }
554:
555: processed_exts = true;
556: }
557: }
558: /* End of NetBSD specific section. */
559: #endif
1.1 mrg 560:
561: /* Weird cpuinfo format that we don't know how to handle. */
1.3 mrg 562: if (n_cores == 0
563: || n_cores > 2
564: || (n_cores == 1 && n_variants != 1)
565: || imp == INVALID_IMP)
1.1 mrg 566: goto not_found;
567:
1.3 mrg 568: /* Simple case, one core type or just looking for the arch. */
569: if (n_cores == 1 || arch)
570: {
571: /* Search for one of the cores in the list. */
572: for (i = 0; aarch64_cpu_data[i].name != NULL; i++)
573: if (aarch64_cpu_data[i].implementer_id == imp
574: && cores[0] == aarch64_cpu_data[i].part_no
575: && (aarch64_cpu_data[i].variant == ALL_VARIANTS
576: || variants[0] == aarch64_cpu_data[i].variant))
577: break;
578: if (aarch64_cpu_data[i].name == NULL)
579: goto not_found;
1.1 mrg 580:
1.3 mrg 581: if (arch)
582: {
583: const char *arch_id = aarch64_cpu_data[i].arch;
584: aarch64_arch_driver_info* arch_info = get_arch_from_id (arch_id);
1.1 mrg 585:
1.3 mrg 586: /* We got some arch indentifier that's not in aarch64-arches.def? */
587: if (!arch_info)
588: goto not_found;
1.1 mrg 589:
1.3 mrg 590: res = concat ("-march=", arch_info->name, NULL);
591: default_flags = arch_info->flags;
592: }
593: else
594: {
595: default_flags = aarch64_cpu_data[i].flags;
596: res = concat ("-m",
597: cpu ? "cpu" : "tune", "=",
598: aarch64_cpu_data[i].name,
599: NULL);
600: }
1.1 mrg 601: }
602: /* We have big.LITTLE. */
1.3 mrg 603: else
1.1 mrg 604: {
605: for (i = 0; aarch64_cpu_data[i].name != NULL; i++)
606: {
1.3 mrg 607: if (aarch64_cpu_data[i].implementer_id == imp
608: && valid_bL_core_p (cores, aarch64_cpu_data[i].part_no))
1.1 mrg 609: {
610: res = concat ("-m",
611: cpu ? "cpu" : "tune", "=",
612: aarch64_cpu_data[i].name,
613: NULL);
614: default_flags = aarch64_cpu_data[i].flags;
615: break;
616: }
617: }
618: if (!res)
619: goto not_found;
620: }
621:
622: if (tune)
623: return res;
624:
1.6 mrg 625: {
626: std::string extension
627: = aarch64_get_extension_string_for_isa_flags (extension_flags,
628: default_flags);
629: res = concat (res, extension.c_str (), NULL);
630: }
1.1 mrg 631:
632: return res;
633:
634: not_found:
635: {
636: /* If detection fails we ignore the option.
1.6 mrg 637: Clean up and return NULL. */
1.1 mrg 638:
639: if (f)
640: fclose (f);
641:
1.6 mrg 642: return NULL;
1.1 mrg 643: }
644: }
1.4 mrg 645:
CVSweb <webmaster@jp.NetBSD.org>