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