1 | // Copyright 2006-2008 the V8 project authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | #include "src/flags.h" |
6 | |
7 | #include <cctype> |
8 | #include <cerrno> |
9 | #include <cstdlib> |
10 | #include <sstream> |
11 | |
12 | #include "src/allocation.h" |
13 | #include "src/base/functional.h" |
14 | #include "src/base/platform/platform.h" |
15 | #include "src/counters.h" |
16 | #include "src/cpu-features.h" |
17 | #include "src/memcopy.h" |
18 | #include "src/ostreams.h" |
19 | #include "src/utils.h" |
20 | #include "src/wasm/wasm-limits.h" |
21 | |
22 | namespace v8 { |
23 | namespace internal { |
24 | |
25 | // Define all of our flags. |
26 | #define FLAG_MODE_DEFINE |
27 | #include "src/flag-definitions.h" // NOLINT(build/include) |
28 | |
29 | // Define all of our flags default values. |
30 | #define FLAG_MODE_DEFINE_DEFAULTS |
31 | #include "src/flag-definitions.h" // NOLINT(build/include) |
32 | |
33 | namespace { |
34 | |
35 | // This structure represents a single entry in the flag system, with a pointer |
36 | // to the actual flag, default value, comment, etc. This is designed to be POD |
37 | // initialized as to avoid requiring static constructors. |
38 | struct Flag { |
39 | enum FlagType { |
40 | TYPE_BOOL, |
41 | TYPE_MAYBE_BOOL, |
42 | TYPE_INT, |
43 | TYPE_UINT, |
44 | TYPE_UINT64, |
45 | TYPE_FLOAT, |
46 | TYPE_SIZE_T, |
47 | TYPE_STRING, |
48 | }; |
49 | |
50 | FlagType type_; // What type of flag, bool, int, or string. |
51 | const char* name_; // Name of the flag, ex "my_flag". |
52 | void* valptr_; // Pointer to the global flag variable. |
53 | const void* defptr_; // Pointer to the default value. |
54 | const char* cmt_; // A comment about the flags purpose. |
55 | bool owns_ptr_; // Does the flag own its string value? |
56 | |
57 | FlagType type() const { return type_; } |
58 | |
59 | const char* name() const { return name_; } |
60 | |
61 | const char* () const { return cmt_; } |
62 | |
63 | bool* bool_variable() const { |
64 | DCHECK(type_ == TYPE_BOOL); |
65 | return reinterpret_cast<bool*>(valptr_); |
66 | } |
67 | |
68 | MaybeBoolFlag* maybe_bool_variable() const { |
69 | DCHECK(type_ == TYPE_MAYBE_BOOL); |
70 | return reinterpret_cast<MaybeBoolFlag*>(valptr_); |
71 | } |
72 | |
73 | int* int_variable() const { |
74 | DCHECK(type_ == TYPE_INT); |
75 | return reinterpret_cast<int*>(valptr_); |
76 | } |
77 | |
78 | unsigned int* uint_variable() const { |
79 | DCHECK(type_ == TYPE_UINT); |
80 | return reinterpret_cast<unsigned int*>(valptr_); |
81 | } |
82 | |
83 | uint64_t* uint64_variable() const { |
84 | DCHECK(type_ == TYPE_UINT64); |
85 | return reinterpret_cast<uint64_t*>(valptr_); |
86 | } |
87 | |
88 | double* float_variable() const { |
89 | DCHECK(type_ == TYPE_FLOAT); |
90 | return reinterpret_cast<double*>(valptr_); |
91 | } |
92 | |
93 | size_t* size_t_variable() const { |
94 | DCHECK(type_ == TYPE_SIZE_T); |
95 | return reinterpret_cast<size_t*>(valptr_); |
96 | } |
97 | |
98 | const char* string_value() const { |
99 | DCHECK(type_ == TYPE_STRING); |
100 | return *reinterpret_cast<const char**>(valptr_); |
101 | } |
102 | |
103 | void set_string_value(const char* value, bool owns_ptr) { |
104 | DCHECK(type_ == TYPE_STRING); |
105 | const char** ptr = reinterpret_cast<const char**>(valptr_); |
106 | if (owns_ptr_ && *ptr != nullptr) DeleteArray(*ptr); |
107 | *ptr = value; |
108 | owns_ptr_ = owns_ptr; |
109 | } |
110 | |
111 | bool bool_default() const { |
112 | DCHECK(type_ == TYPE_BOOL); |
113 | return *reinterpret_cast<const bool*>(defptr_); |
114 | } |
115 | |
116 | int int_default() const { |
117 | DCHECK(type_ == TYPE_INT); |
118 | return *reinterpret_cast<const int*>(defptr_); |
119 | } |
120 | |
121 | unsigned int uint_default() const { |
122 | DCHECK(type_ == TYPE_UINT); |
123 | return *reinterpret_cast<const unsigned int*>(defptr_); |
124 | } |
125 | |
126 | uint64_t uint64_default() const { |
127 | DCHECK(type_ == TYPE_UINT64); |
128 | return *reinterpret_cast<const uint64_t*>(defptr_); |
129 | } |
130 | |
131 | double float_default() const { |
132 | DCHECK(type_ == TYPE_FLOAT); |
133 | return *reinterpret_cast<const double*>(defptr_); |
134 | } |
135 | |
136 | size_t size_t_default() const { |
137 | DCHECK(type_ == TYPE_SIZE_T); |
138 | return *reinterpret_cast<const size_t*>(defptr_); |
139 | } |
140 | |
141 | const char* string_default() const { |
142 | DCHECK(type_ == TYPE_STRING); |
143 | return *reinterpret_cast<const char* const *>(defptr_); |
144 | } |
145 | |
146 | // Compare this flag's current value against the default. |
147 | bool IsDefault() const { |
148 | switch (type_) { |
149 | case TYPE_BOOL: |
150 | return *bool_variable() == bool_default(); |
151 | case TYPE_MAYBE_BOOL: |
152 | return maybe_bool_variable()->has_value == false; |
153 | case TYPE_INT: |
154 | return *int_variable() == int_default(); |
155 | case TYPE_UINT: |
156 | return *uint_variable() == uint_default(); |
157 | case TYPE_UINT64: |
158 | return *uint64_variable() == uint64_default(); |
159 | case TYPE_FLOAT: |
160 | return *float_variable() == float_default(); |
161 | case TYPE_SIZE_T: |
162 | return *size_t_variable() == size_t_default(); |
163 | case TYPE_STRING: { |
164 | const char* str1 = string_value(); |
165 | const char* str2 = string_default(); |
166 | if (str2 == nullptr) return str1 == nullptr; |
167 | if (str1 == nullptr) return str2 == nullptr; |
168 | return strcmp(str1, str2) == 0; |
169 | } |
170 | } |
171 | UNREACHABLE(); |
172 | } |
173 | |
174 | // Set a flag back to it's default value. |
175 | void Reset() { |
176 | switch (type_) { |
177 | case TYPE_BOOL: |
178 | *bool_variable() = bool_default(); |
179 | break; |
180 | case TYPE_MAYBE_BOOL: |
181 | *maybe_bool_variable() = MaybeBoolFlag::Create(false, false); |
182 | break; |
183 | case TYPE_INT: |
184 | *int_variable() = int_default(); |
185 | break; |
186 | case TYPE_UINT: |
187 | *uint_variable() = uint_default(); |
188 | break; |
189 | case TYPE_UINT64: |
190 | *uint64_variable() = uint64_default(); |
191 | break; |
192 | case TYPE_FLOAT: |
193 | *float_variable() = float_default(); |
194 | break; |
195 | case TYPE_SIZE_T: |
196 | *size_t_variable() = size_t_default(); |
197 | break; |
198 | case TYPE_STRING: |
199 | set_string_value(string_default(), false); |
200 | break; |
201 | } |
202 | } |
203 | }; |
204 | |
205 | Flag flags[] = { |
206 | #define FLAG_MODE_META |
207 | #include "src/flag-definitions.h" // NOLINT(build/include) |
208 | }; |
209 | |
210 | const size_t num_flags = sizeof(flags) / sizeof(*flags); |
211 | |
212 | } // namespace |
213 | |
214 | |
215 | static const char* Type2String(Flag::FlagType type) { |
216 | switch (type) { |
217 | case Flag::TYPE_BOOL: return "bool" ; |
218 | case Flag::TYPE_MAYBE_BOOL: return "maybe_bool" ; |
219 | case Flag::TYPE_INT: return "int" ; |
220 | case Flag::TYPE_UINT: |
221 | return "uint" ; |
222 | case Flag::TYPE_UINT64: |
223 | return "uint64" ; |
224 | case Flag::TYPE_FLOAT: return "float" ; |
225 | case Flag::TYPE_SIZE_T: |
226 | return "size_t" ; |
227 | case Flag::TYPE_STRING: return "string" ; |
228 | } |
229 | UNREACHABLE(); |
230 | } |
231 | |
232 | |
233 | std::ostream& operator<<(std::ostream& os, const Flag& flag) { // NOLINT |
234 | switch (flag.type()) { |
235 | case Flag::TYPE_BOOL: |
236 | os << (*flag.bool_variable() ? "true" : "false" ); |
237 | break; |
238 | case Flag::TYPE_MAYBE_BOOL: |
239 | os << (flag.maybe_bool_variable()->has_value |
240 | ? (flag.maybe_bool_variable()->value ? "true" : "false" ) |
241 | : "unset" ); |
242 | break; |
243 | case Flag::TYPE_INT: |
244 | os << *flag.int_variable(); |
245 | break; |
246 | case Flag::TYPE_UINT: |
247 | os << *flag.uint_variable(); |
248 | break; |
249 | case Flag::TYPE_UINT64: |
250 | os << *flag.uint64_variable(); |
251 | break; |
252 | case Flag::TYPE_FLOAT: |
253 | os << *flag.float_variable(); |
254 | break; |
255 | case Flag::TYPE_SIZE_T: |
256 | os << *flag.size_t_variable(); |
257 | break; |
258 | case Flag::TYPE_STRING: { |
259 | const char* str = flag.string_value(); |
260 | os << (str ? str : "nullptr" ); |
261 | break; |
262 | } |
263 | } |
264 | return os; |
265 | } |
266 | |
267 | |
268 | // static |
269 | std::vector<const char*>* FlagList::argv() { |
270 | std::vector<const char*>* args = new std::vector<const char*>(8); |
271 | for (size_t i = 0; i < num_flags; ++i) { |
272 | Flag* f = &flags[i]; |
273 | if (!f->IsDefault()) { |
274 | { |
275 | bool disabled = f->type() == Flag::TYPE_BOOL && !*f->bool_variable(); |
276 | std::ostringstream os; |
277 | os << (disabled ? "--no" : "--" ) << f->name(); |
278 | args->push_back(StrDup(os.str().c_str())); |
279 | } |
280 | if (f->type() != Flag::TYPE_BOOL) { |
281 | std::ostringstream os; |
282 | os << *f; |
283 | args->push_back(StrDup(os.str().c_str())); |
284 | } |
285 | } |
286 | } |
287 | return args; |
288 | } |
289 | |
290 | |
291 | inline char NormalizeChar(char ch) { |
292 | return ch == '_' ? '-' : ch; |
293 | } |
294 | |
295 | // Helper function to parse flags: Takes an argument arg and splits it into |
296 | // a flag name and flag value (or nullptr if they are missing). negated is set |
297 | // if the arg started with "-no" or "--no". The buffer may be used to NUL- |
298 | // terminate the name, it must be large enough to hold any possible name. |
299 | static void SplitArgument(const char* arg, char* buffer, int buffer_size, |
300 | const char** name, const char** value, |
301 | bool* negated) { |
302 | *name = nullptr; |
303 | *value = nullptr; |
304 | *negated = false; |
305 | |
306 | if (arg != nullptr && *arg == '-') { |
307 | // find the begin of the flag name |
308 | arg++; // remove 1st '-' |
309 | if (*arg == '-') { |
310 | arg++; // remove 2nd '-' |
311 | DCHECK_NE('\0', arg[0]); // '--' arguments are handled in the caller. |
312 | } |
313 | if (arg[0] == 'n' && arg[1] == 'o') { |
314 | arg += 2; // remove "no" |
315 | if (NormalizeChar(arg[0]) == '-') arg++; // remove dash after "no". |
316 | *negated = true; |
317 | } |
318 | *name = arg; |
319 | |
320 | // find the end of the flag name |
321 | while (*arg != '\0' && *arg != '=') |
322 | arg++; |
323 | |
324 | // get the value if any |
325 | if (*arg == '=') { |
326 | // make a copy so we can NUL-terminate flag name |
327 | size_t n = arg - *name; |
328 | CHECK(n < static_cast<size_t>(buffer_size)); // buffer is too small |
329 | MemCopy(buffer, *name, n); |
330 | buffer[n] = '\0'; |
331 | *name = buffer; |
332 | // get the value |
333 | *value = arg + 1; |
334 | } |
335 | } |
336 | } |
337 | |
338 | |
339 | static bool EqualNames(const char* a, const char* b) { |
340 | for (int i = 0; NormalizeChar(a[i]) == NormalizeChar(b[i]); i++) { |
341 | if (a[i] == '\0') { |
342 | return true; |
343 | } |
344 | } |
345 | return false; |
346 | } |
347 | |
348 | |
349 | static Flag* FindFlag(const char* name) { |
350 | for (size_t i = 0; i < num_flags; ++i) { |
351 | if (EqualNames(name, flags[i].name())) |
352 | return &flags[i]; |
353 | } |
354 | return nullptr; |
355 | } |
356 | |
357 | template <typename T> |
358 | bool TryParseUnsigned(Flag* flag, const char* arg, const char* value, |
359 | char** endp, T* out_val) { |
360 | // We do not use strtoul because it accepts negative numbers. |
361 | // Rejects values >= 2**63 when T is 64 bits wide but that |
362 | // seems like an acceptable trade-off. |
363 | uint64_t max = static_cast<uint64_t>(std::numeric_limits<T>::max()); |
364 | errno = 0; |
365 | int64_t val = static_cast<int64_t>(strtoll(value, endp, 10)); |
366 | if (val < 0 || static_cast<uint64_t>(val) > max || errno != 0) { |
367 | PrintF(stderr, |
368 | "Error: Value for flag %s of type %s is out of bounds " |
369 | "[0-%" PRIu64 "]\n" , |
370 | arg, Type2String(flag->type()), max); |
371 | return false; |
372 | } |
373 | *out_val = static_cast<T>(val); |
374 | return true; |
375 | } |
376 | |
377 | // static |
378 | int FlagList::SetFlagsFromCommandLine(int* argc, |
379 | char** argv, |
380 | bool remove_flags) { |
381 | int return_code = 0; |
382 | // parse arguments |
383 | for (int i = 1; i < *argc;) { |
384 | int j = i; // j > 0 |
385 | const char* arg = argv[i++]; |
386 | |
387 | // split arg into flag components |
388 | char buffer[1*KB]; |
389 | const char* name; |
390 | const char* value; |
391 | bool negated; |
392 | SplitArgument(arg, buffer, sizeof buffer, &name, &value, &negated); |
393 | |
394 | if (name != nullptr) { |
395 | // lookup the flag |
396 | Flag* flag = FindFlag(name); |
397 | if (flag == nullptr) { |
398 | if (remove_flags) { |
399 | // We don't recognize this flag but since we're removing |
400 | // the flags we recognize we assume that the remaining flags |
401 | // will be processed somewhere else so this flag might make |
402 | // sense there. |
403 | continue; |
404 | } else { |
405 | PrintF(stderr, "Error: unrecognized flag %s\n" , arg); |
406 | return_code = j; |
407 | break; |
408 | } |
409 | } |
410 | |
411 | // if we still need a flag value, use the next argument if available |
412 | if (flag->type() != Flag::TYPE_BOOL && |
413 | flag->type() != Flag::TYPE_MAYBE_BOOL && value == nullptr) { |
414 | if (i < *argc) { |
415 | value = argv[i++]; |
416 | } |
417 | if (!value) { |
418 | PrintF(stderr, "Error: missing value for flag %s of type %s\n" , arg, |
419 | Type2String(flag->type())); |
420 | return_code = j; |
421 | break; |
422 | } |
423 | } |
424 | |
425 | // set the flag |
426 | char* endp = const_cast<char*>("" ); // *endp is only read |
427 | switch (flag->type()) { |
428 | case Flag::TYPE_BOOL: |
429 | *flag->bool_variable() = !negated; |
430 | break; |
431 | case Flag::TYPE_MAYBE_BOOL: |
432 | *flag->maybe_bool_variable() = MaybeBoolFlag::Create(true, !negated); |
433 | break; |
434 | case Flag::TYPE_INT: |
435 | *flag->int_variable() = static_cast<int>(strtol(value, &endp, 10)); |
436 | break; |
437 | case Flag::TYPE_UINT: |
438 | if (!TryParseUnsigned(flag, arg, value, &endp, |
439 | flag->uint_variable())) { |
440 | return_code = j; |
441 | } |
442 | break; |
443 | case Flag::TYPE_UINT64: |
444 | if (!TryParseUnsigned(flag, arg, value, &endp, |
445 | flag->uint64_variable())) { |
446 | return_code = j; |
447 | } |
448 | break; |
449 | case Flag::TYPE_FLOAT: |
450 | *flag->float_variable() = strtod(value, &endp); |
451 | break; |
452 | case Flag::TYPE_SIZE_T: |
453 | if (!TryParseUnsigned(flag, arg, value, &endp, |
454 | flag->size_t_variable())) { |
455 | return_code = j; |
456 | } |
457 | break; |
458 | case Flag::TYPE_STRING: |
459 | flag->set_string_value(value ? StrDup(value) : nullptr, true); |
460 | break; |
461 | } |
462 | |
463 | // handle errors |
464 | bool is_bool_type = flag->type() == Flag::TYPE_BOOL || |
465 | flag->type() == Flag::TYPE_MAYBE_BOOL; |
466 | if ((is_bool_type && value != nullptr) || (!is_bool_type && negated) || |
467 | *endp != '\0') { |
468 | // TODO(neis): TryParseUnsigned may return with {*endp == '\0'} even in |
469 | // an error case. |
470 | PrintF(stderr, "Error: illegal value for flag %s of type %s\n" , arg, |
471 | Type2String(flag->type())); |
472 | if (is_bool_type) { |
473 | PrintF(stderr, |
474 | "To set or unset a boolean flag, use --flag or --no-flag.\n" ); |
475 | } |
476 | return_code = j; |
477 | break; |
478 | } |
479 | |
480 | // remove the flag & value from the command |
481 | if (remove_flags) { |
482 | while (j < i) { |
483 | argv[j++] = nullptr; |
484 | } |
485 | } |
486 | } |
487 | } |
488 | |
489 | if (FLAG_help) { |
490 | PrintHelp(); |
491 | exit(0); |
492 | } |
493 | |
494 | if (remove_flags) { |
495 | // shrink the argument list |
496 | int j = 1; |
497 | for (int i = 1; i < *argc; i++) { |
498 | if (argv[i] != nullptr) argv[j++] = argv[i]; |
499 | } |
500 | *argc = j; |
501 | } else if (return_code != 0) { |
502 | if (return_code + 1 < *argc) { |
503 | PrintF(stderr, "The remaining arguments were ignored:" ); |
504 | for (int i = return_code + 1; i < *argc; ++i) { |
505 | PrintF(stderr, " %s" , argv[i]); |
506 | } |
507 | PrintF(stderr, "\n" ); |
508 | } |
509 | } |
510 | if (return_code != 0) PrintF(stderr, "Try --help for options\n" ); |
511 | |
512 | return return_code; |
513 | } |
514 | |
515 | |
516 | static char* SkipWhiteSpace(char* p) { |
517 | while (*p != '\0' && isspace(*p) != 0) p++; |
518 | return p; |
519 | } |
520 | |
521 | |
522 | static char* SkipBlackSpace(char* p) { |
523 | while (*p != '\0' && isspace(*p) == 0) p++; |
524 | return p; |
525 | } |
526 | |
527 | |
528 | // static |
529 | int FlagList::SetFlagsFromString(const char* str, int len) { |
530 | // make a 0-terminated copy of str |
531 | ScopedVector<char> copy0(len + 1); |
532 | MemCopy(copy0.start(), str, len); |
533 | copy0[len] = '\0'; |
534 | |
535 | // strip leading white space |
536 | char* copy = SkipWhiteSpace(copy0.start()); |
537 | |
538 | // count the number of 'arguments' |
539 | int argc = 1; // be compatible with SetFlagsFromCommandLine() |
540 | for (char* p = copy; *p != '\0'; argc++) { |
541 | p = SkipBlackSpace(p); |
542 | p = SkipWhiteSpace(p); |
543 | } |
544 | |
545 | // allocate argument array |
546 | ScopedVector<char*> argv(argc); |
547 | |
548 | // split the flags string into arguments |
549 | argc = 1; // be compatible with SetFlagsFromCommandLine() |
550 | for (char* p = copy; *p != '\0'; argc++) { |
551 | argv[argc] = p; |
552 | p = SkipBlackSpace(p); |
553 | if (*p != '\0') *p++ = '\0'; // 0-terminate argument |
554 | p = SkipWhiteSpace(p); |
555 | } |
556 | |
557 | return SetFlagsFromCommandLine(&argc, argv.start(), false); |
558 | } |
559 | |
560 | |
561 | // static |
562 | void FlagList::ResetAllFlags() { |
563 | for (size_t i = 0; i < num_flags; ++i) { |
564 | flags[i].Reset(); |
565 | } |
566 | } |
567 | |
568 | |
569 | // static |
570 | void FlagList::PrintHelp() { |
571 | CpuFeatures::Probe(false); |
572 | CpuFeatures::PrintTarget(); |
573 | CpuFeatures::PrintFeatures(); |
574 | |
575 | StdoutStream os; |
576 | os << "Synopsis:\n" |
577 | " shell [options] [--shell] [<file>...]\n" |
578 | " d8 [options] [-e <string>] [--shell] [[--module] <file>...]\n\n" |
579 | " -e execute a string in V8\n" |
580 | " --shell run an interactive JavaScript shell\n" |
581 | " --module execute a file as a JavaScript module\n\n" |
582 | "Note: the --module option is implicitly enabled for *.mjs files.\n\n" |
583 | "The following syntax for options is accepted (both '-' and '--' are " |
584 | "ok):\n" |
585 | " --flag (bool flags only)\n" |
586 | " --no-flag (bool flags only)\n" |
587 | " --flag=value (non-bool flags only, no spaces around '=')\n" |
588 | " --flag value (non-bool flags only)\n" |
589 | " -- (captures all remaining args in JavaScript)\n\n" |
590 | "Options:\n" ; |
591 | |
592 | for (const Flag& f : flags) { |
593 | os << " --" ; |
594 | for (const char* c = f.name(); *c != '\0'; ++c) { |
595 | os << NormalizeChar(*c); |
596 | } |
597 | os << " (" << f.comment() << ")\n" |
598 | << " type: " << Type2String(f.type()) << " default: " << f |
599 | << "\n" ; |
600 | } |
601 | } |
602 | |
603 | |
604 | static uint32_t flag_hash = 0; |
605 | |
606 | |
607 | void ComputeFlagListHash() { |
608 | std::ostringstream modified_args_as_string; |
609 | #ifdef DEBUG |
610 | modified_args_as_string << "debug" ; |
611 | #endif // DEBUG |
612 | if (FLAG_embedded_builtins) { |
613 | modified_args_as_string << "embedded" ; |
614 | } |
615 | for (size_t i = 0; i < num_flags; ++i) { |
616 | Flag* current = &flags[i]; |
617 | if (current->type() == Flag::TYPE_BOOL && |
618 | current->bool_variable() == &FLAG_profile_deserialization) { |
619 | // We want to be able to flip --profile-deserialization without |
620 | // causing the code cache to get invalidated by this hash. |
621 | continue; |
622 | } |
623 | if (!current->IsDefault()) { |
624 | modified_args_as_string << i; |
625 | modified_args_as_string << *current; |
626 | } |
627 | } |
628 | std::string args(modified_args_as_string.str()); |
629 | flag_hash = static_cast<uint32_t>( |
630 | base::hash_range(args.c_str(), args.c_str() + args.length())); |
631 | } |
632 | |
633 | |
634 | // static |
635 | void FlagList::EnforceFlagImplications() { |
636 | #define FLAG_MODE_DEFINE_IMPLICATIONS |
637 | #include "src/flag-definitions.h" // NOLINT(build/include) |
638 | #undef FLAG_MODE_DEFINE_IMPLICATIONS |
639 | ComputeFlagListHash(); |
640 | } |
641 | |
642 | |
643 | uint32_t FlagList::Hash() { return flag_hash; } |
644 | } // namespace internal |
645 | } // namespace v8 |
646 | |