Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2012 the V8 project authors. All rights reserved. | 1 // Copyright 2012 the V8 project authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 | 5 |
| 6 // Defined when linking against shared lib on Windows. | 6 // Defined when linking against shared lib on Windows. |
| 7 #if defined(USING_V8_SHARED) && !defined(V8_SHARED) | 7 #if defined(USING_V8_SHARED) && !defined(V8_SHARED) |
| 8 #define V8_SHARED | 8 #define V8_SHARED |
| 9 #endif | 9 #endif |
| 10 | 10 |
| (...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 138 | 138 |
| 139 private: | 139 private: |
| 140 friend class Shell; | 140 friend class Shell; |
| 141 friend class RealmScope; | 141 friend class RealmScope; |
| 142 Isolate* isolate_; | 142 Isolate* isolate_; |
| 143 int realm_count_; | 143 int realm_count_; |
| 144 int realm_current_; | 144 int realm_current_; |
| 145 int realm_switch_; | 145 int realm_switch_; |
| 146 Persistent<Context>* realms_; | 146 Persistent<Context>* realms_; |
| 147 Persistent<Value> realm_shared_; | 147 Persistent<Value> realm_shared_; |
| 148 ArrayBuffer::Allocator* array_buffer_allocator_; | |
| 148 | 149 |
| 149 int RealmIndexOrThrow(const v8::FunctionCallbackInfo<v8::Value>& args, | 150 int RealmIndexOrThrow(const v8::FunctionCallbackInfo<v8::Value>& args, |
| 150 int arg_offset); | 151 int arg_offset); |
| 151 int RealmFind(Handle<Context> context); | 152 int RealmFind(Handle<Context> context); |
| 152 }; | 153 }; |
| 153 | 154 |
| 154 | 155 |
| 155 LineEditor *LineEditor::current_ = NULL; | 156 LineEditor *LineEditor::current_ = NULL; |
| 156 | 157 |
| 157 | 158 |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 184 | 185 |
| 185 #ifndef V8_SHARED | 186 #ifndef V8_SHARED |
| 186 CounterMap* Shell::counter_map_; | 187 CounterMap* Shell::counter_map_; |
| 187 base::OS::MemoryMappedFile* Shell::counters_file_ = NULL; | 188 base::OS::MemoryMappedFile* Shell::counters_file_ = NULL; |
| 188 CounterCollection Shell::local_counters_; | 189 CounterCollection Shell::local_counters_; |
| 189 CounterCollection* Shell::counters_ = &local_counters_; | 190 CounterCollection* Shell::counters_ = &local_counters_; |
| 190 base::Mutex Shell::context_mutex_; | 191 base::Mutex Shell::context_mutex_; |
| 191 const base::TimeTicks Shell::kInitialTicks = | 192 const base::TimeTicks Shell::kInitialTicks = |
| 192 base::TimeTicks::HighResolutionNow(); | 193 base::TimeTicks::HighResolutionNow(); |
| 193 Persistent<Context> Shell::utility_context_; | 194 Persistent<Context> Shell::utility_context_; |
| 195 Worker Shell::worker_; | |
| 196 i::List<v8::SharedArrayBuffer::Contents> Shell::externalized_shared_contents_; | |
| 194 #endif // !V8_SHARED | 197 #endif // !V8_SHARED |
| 195 | 198 |
| 196 Persistent<Context> Shell::evaluation_context_; | 199 Persistent<Context> Shell::evaluation_context_; |
| 197 ShellOptions Shell::options; | 200 ShellOptions Shell::options; |
| 198 const char* Shell::kPrompt = "d8> "; | 201 const char* Shell::kPrompt = "d8> "; |
| 199 | 202 |
| 200 #ifndef V8_SHARED | 203 #ifndef V8_SHARED |
| 201 bool CounterMap::Match(void* key1, void* key2) { | 204 bool CounterMap::Match(void* key1, void* key2) { |
| 202 const char* name1 = reinterpret_cast<const char*>(key1); | 205 const char* name1 = reinterpret_cast<const char*>(key1); |
| 203 const char* name2 = reinterpret_cast<const char*>(key2); | 206 const char* name2 = reinterpret_cast<const char*>(key2); |
| (...skipping 450 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 654 String::NewFromUtf8(args.GetIsolate(), *file), | 657 String::NewFromUtf8(args.GetIsolate(), *file), |
| 655 false, | 658 false, |
| 656 true)) { | 659 true)) { |
| 657 Throw(args.GetIsolate(), "Error executing file"); | 660 Throw(args.GetIsolate(), "Error executing file"); |
| 658 return; | 661 return; |
| 659 } | 662 } |
| 660 } | 663 } |
| 661 } | 664 } |
| 662 | 665 |
| 663 | 666 |
| 667 #ifndef V8_SHARED | |
| 668 void Shell::WorkerNew(const v8::FunctionCallbackInfo<v8::Value>& args) { | |
| 669 Isolate* isolate = args.GetIsolate(); | |
| 670 HandleScope handle_scope(isolate); | |
| 671 if (args.Length() < 1 || !args[0]->IsFunction()) { | |
| 672 Throw(args.GetIsolate(), "1st argument must be function"); | |
| 673 return; | |
| 674 } | |
| 675 if (args.Length() >= 2 && !args[1]->IsSharedArrayBuffer()) { | |
| 676 Throw(args.GetIsolate(), "2nd argument must be SharedArrayBuffer"); | |
| 677 return; | |
| 678 } | |
| 679 | |
| 680 String::Utf8Value function_string(args[0]->ToString()); | |
| 681 v8::SharedArrayBuffer::Contents contents; | |
| 682 if (args.Length() >= 2) { | |
| 683 Handle<SharedArrayBuffer> sab = Handle<SharedArrayBuffer>::Cast(args[1]); | |
| 684 contents = sab->Externalize(); | |
| 685 externalized_shared_contents_.Add(contents); | |
| 686 } | |
| 687 worker_.StartExecuteInThread(isolate, *function_string, contents); | |
| 688 } | |
| 689 | |
| 690 | |
| 691 void Shell::WorkerJoin(const v8::FunctionCallbackInfo<v8::Value>& args) { | |
| 692 Isolate* isolate = args.GetIsolate(); | |
| 693 HandleScope handle_scope(isolate); | |
| 694 Handle<Value> result = worker_.WaitForThread(isolate); | |
| 695 args.GetReturnValue().Set(result); | |
| 696 } | |
| 697 #endif // !V8_SHARED | |
| 698 | |
| 699 | |
| 664 void Shell::Quit(const v8::FunctionCallbackInfo<v8::Value>& args) { | 700 void Shell::Quit(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| 665 int exit_code = args[0]->Int32Value(); | 701 int exit_code = args[0]->Int32Value(); |
| 666 OnExit(args.GetIsolate()); | 702 OnExit(args.GetIsolate()); |
| 667 exit(exit_code); | 703 exit(exit_code); |
| 668 } | 704 } |
| 669 | 705 |
| 670 | 706 |
| 671 void Shell::Version(const v8::FunctionCallbackInfo<v8::Value>& args) { | 707 void Shell::Version(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| 672 args.GetReturnValue().Set( | 708 args.GetReturnValue().Set( |
| 673 String::NewFromUtf8(args.GetIsolate(), V8::GetVersion())); | 709 String::NewFromUtf8(args.GetIsolate(), V8::GetVersion())); |
| (...skipping 311 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 985 realm_template->SetAccessor(String::NewFromUtf8(isolate, "shared"), | 1021 realm_template->SetAccessor(String::NewFromUtf8(isolate, "shared"), |
| 986 RealmSharedGet, RealmSharedSet); | 1022 RealmSharedGet, RealmSharedSet); |
| 987 global_template->Set(String::NewFromUtf8(isolate, "Realm"), realm_template); | 1023 global_template->Set(String::NewFromUtf8(isolate, "Realm"), realm_template); |
| 988 | 1024 |
| 989 #ifndef V8_SHARED | 1025 #ifndef V8_SHARED |
| 990 Handle<ObjectTemplate> performance_template = ObjectTemplate::New(isolate); | 1026 Handle<ObjectTemplate> performance_template = ObjectTemplate::New(isolate); |
| 991 performance_template->Set(String::NewFromUtf8(isolate, "now"), | 1027 performance_template->Set(String::NewFromUtf8(isolate, "now"), |
| 992 FunctionTemplate::New(isolate, PerformanceNow)); | 1028 FunctionTemplate::New(isolate, PerformanceNow)); |
| 993 global_template->Set(String::NewFromUtf8(isolate, "performance"), | 1029 global_template->Set(String::NewFromUtf8(isolate, "performance"), |
| 994 performance_template); | 1030 performance_template); |
| 1031 | |
| 1032 Handle<ObjectTemplate> worker_template = ObjectTemplate::New(isolate); | |
| 1033 worker_template->Set(String::NewFromUtf8(isolate, "new"), | |
| 1034 FunctionTemplate::New(isolate, WorkerNew)); | |
| 1035 worker_template->Set(String::NewFromUtf8(isolate, "join"), | |
| 1036 FunctionTemplate::New(isolate, WorkerJoin)); | |
| 1037 global_template->Set(String::NewFromUtf8(isolate, "Worker"), worker_template); | |
| 995 #endif // !V8_SHARED | 1038 #endif // !V8_SHARED |
| 996 | 1039 |
| 997 Handle<ObjectTemplate> os_templ = ObjectTemplate::New(isolate); | 1040 Handle<ObjectTemplate> os_templ = ObjectTemplate::New(isolate); |
| 998 AddOSMethods(isolate, os_templ); | 1041 AddOSMethods(isolate, os_templ); |
| 999 global_template->Set(String::NewFromUtf8(isolate, "os"), os_templ); | 1042 global_template->Set(String::NewFromUtf8(isolate, "os"), os_templ); |
| 1000 | 1043 |
| 1001 return global_template; | 1044 return global_template; |
| 1002 } | 1045 } |
| 1003 | 1046 |
| 1004 | 1047 |
| (...skipping 357 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1362 | 1405 |
| 1363 | 1406 |
| 1364 void SourceGroup::WaitForThread() { | 1407 void SourceGroup::WaitForThread() { |
| 1365 if (thread_ == NULL) return; | 1408 if (thread_ == NULL) return; |
| 1366 if (Shell::options.last_run) { | 1409 if (Shell::options.last_run) { |
| 1367 thread_->Join(); | 1410 thread_->Join(); |
| 1368 } else { | 1411 } else { |
| 1369 done_semaphore_.Wait(); | 1412 done_semaphore_.Wait(); |
| 1370 } | 1413 } |
| 1371 } | 1414 } |
| 1415 | |
| 1416 | |
| 1417 Worker::Worker() : thread_(NULL), script_(NULL), result_json_(NULL) {} | |
| 1418 | |
| 1419 | |
| 1420 Worker::~Worker() { Cleanup(); } | |
| 1421 | |
| 1422 | |
| 1423 void Worker::StartExecuteInThread( | |
| 1424 Isolate* isolate, const char* function_string, | |
| 1425 const v8::SharedArrayBuffer::Contents& contents) { | |
| 1426 if (thread_) { | |
| 1427 Throw(isolate, "Only one worker allowed"); | |
| 1428 return; | |
| 1429 } | |
| 1430 | |
| 1431 static const char format[] = "JSON.stringify((%s).call(this, sab));"; | |
|
binji
2015/06/15 19:11:48
Better way to do this?
| |
| 1432 size_t len = strlen(function_string) + sizeof(format); | |
| 1433 | |
| 1434 script_ = new char[len + 1]; | |
| 1435 snprintf(script_, len, format, function_string); | |
| 1436 script_[len] = 0; | |
| 1437 | |
| 1438 contents_ = contents; | |
| 1439 | |
| 1440 thread_ = new WorkerThread(this); | |
| 1441 thread_->Start(); | |
| 1442 } | |
| 1443 | |
| 1444 | |
| 1445 Handle<Value> Worker::WaitForThread(Isolate* isolate) { | |
| 1446 EscapableHandleScope handle_scope(isolate); | |
| 1447 Handle<Value> result; | |
| 1448 | |
| 1449 if (thread_ == NULL) return result; | |
| 1450 thread_->Join(); | |
| 1451 | |
| 1452 if (result_json_) { | |
| 1453 Local<String> result_json = String::NewFromUtf8(isolate, result_json_); | |
| 1454 MaybeLocal<Value> maybe_result = JSON::Parse(isolate, result_json); | |
| 1455 if (!maybe_result.IsEmpty()) { | |
| 1456 result = maybe_result.ToLocalChecked(); | |
| 1457 } | |
| 1458 } | |
| 1459 | |
| 1460 Cleanup(); | |
| 1461 return handle_scope.Escape(Local<Value>::Cast(result)); | |
| 1462 } | |
| 1463 | |
| 1464 | |
| 1465 void Worker::ExecuteInThread() { | |
|
binji
2015/06/15 19:11:48
A lot of this function is copy/paste from other pl
| |
| 1466 ShellArrayBufferAllocator allocator; | |
| 1467 Isolate::CreateParams create_params; | |
| 1468 create_params.array_buffer_allocator = &allocator; | |
| 1469 Isolate* isolate = Isolate::New(create_params); | |
| 1470 { | |
| 1471 Isolate::Scope iscope(isolate); | |
| 1472 { | |
| 1473 HandleScope scope(isolate); | |
| 1474 PerIsolateData data(isolate); | |
| 1475 Local<Context> context = Shell::CreateEvaluationContext(isolate); | |
| 1476 { | |
| 1477 Context::Scope cscope(context); | |
| 1478 PerIsolateData::RealmScope realm_scope(PerIsolateData::Get(isolate)); | |
| 1479 | |
| 1480 Handle<String> sab_name = String::NewFromUtf8(isolate, "sab"); | |
| 1481 if (contents_.Data()) { | |
| 1482 Handle<v8::SharedArrayBuffer> sab = v8::SharedArrayBuffer::New( | |
| 1483 isolate, contents_.Data(), contents_.ByteLength()); | |
| 1484 context->Global()->Set(sab_name, sab); | |
| 1485 } else { | |
| 1486 context->Global()->Set(sab_name, v8::Undefined(isolate)); | |
| 1487 } | |
| 1488 | |
| 1489 Handle<String> file_name = String::NewFromUtf8(isolate, "unnamed"); | |
| 1490 Handle<String> source = String::NewFromUtf8(isolate, script_); | |
| 1491 | |
| 1492 TryCatch try_catch(isolate); | |
| 1493 Handle<Script> script = | |
| 1494 Shell::CompileString(isolate, source, file_name, | |
| 1495 Shell::options.compile_options, Shell::SCRIPT); | |
| 1496 result_json_ = NULL; | |
| 1497 if (script.IsEmpty()) { | |
| 1498 // Print errors that happened during compilation. | |
| 1499 // TODO(binji): support debugger? | |
| 1500 Shell::ReportException(isolate, &try_catch); | |
| 1501 } else { | |
| 1502 Handle<Value> result = script->Run(); | |
| 1503 if (result->IsString()) { | |
| 1504 Handle<String> str_result = Handle<String>::Cast(result); | |
| 1505 v8::String::Utf8Value str(str_result); | |
| 1506 result_json_ = new char[str.length() + 1]; | |
| 1507 memcpy(result_json_, *str, str.length() + 1); | |
| 1508 } | |
| 1509 } | |
| 1510 } | |
| 1511 } | |
| 1512 } | |
| 1513 Shell::CollectGarbage(isolate); | |
| 1514 isolate->Dispose(); | |
| 1515 } | |
| 1516 | |
| 1517 | |
| 1518 void Worker::Cleanup() { | |
| 1519 delete thread_; | |
| 1520 thread_ = NULL; | |
| 1521 delete[] script_; | |
| 1522 script_ = NULL; | |
| 1523 delete[] result_json_; | |
| 1524 result_json_ = NULL; | |
| 1525 } | |
| 1372 #endif // !V8_SHARED | 1526 #endif // !V8_SHARED |
| 1373 | 1527 |
| 1374 | 1528 |
| 1375 void SetFlagsFromString(const char* flags) { | 1529 void SetFlagsFromString(const char* flags) { |
| 1376 v8::V8::SetFlagsFromString(flags, static_cast<int>(strlen(flags))); | 1530 v8::V8::SetFlagsFromString(flags, static_cast<int>(strlen(flags))); |
| 1377 } | 1531 } |
| 1378 | 1532 |
| 1379 | 1533 |
| 1380 bool Shell::SetOptions(int argc, char* argv[]) { | 1534 bool Shell::SetOptions(int argc, char* argv[]) { |
| 1381 bool logfile_per_isolate = false; | 1535 bool logfile_per_isolate = false; |
| (...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1536 Context::Scope cscope(context); | 1690 Context::Scope cscope(context); |
| 1537 PerIsolateData::RealmScope realm_scope(PerIsolateData::Get(isolate)); | 1691 PerIsolateData::RealmScope realm_scope(PerIsolateData::Get(isolate)); |
| 1538 options.isolate_sources[0].Execute(isolate); | 1692 options.isolate_sources[0].Execute(isolate); |
| 1539 } | 1693 } |
| 1540 } | 1694 } |
| 1541 CollectGarbage(isolate); | 1695 CollectGarbage(isolate); |
| 1542 #ifndef V8_SHARED | 1696 #ifndef V8_SHARED |
| 1543 for (int i = 1; i < options.num_isolates; ++i) { | 1697 for (int i = 1; i < options.num_isolates; ++i) { |
| 1544 options.isolate_sources[i].WaitForThread(); | 1698 options.isolate_sources[i].WaitForThread(); |
| 1545 } | 1699 } |
| 1700 CleanupWorker(isolate); | |
| 1546 #endif // !V8_SHARED | 1701 #endif // !V8_SHARED |
| 1547 return 0; | 1702 return 0; |
| 1548 } | 1703 } |
| 1549 | 1704 |
| 1550 | 1705 |
| 1551 void Shell::CollectGarbage(Isolate* isolate) { | 1706 void Shell::CollectGarbage(Isolate* isolate) { |
| 1552 if (options.send_idle_notification) { | 1707 if (options.send_idle_notification) { |
| 1553 const double kLongIdlePauseInSeconds = 1.0; | 1708 const double kLongIdlePauseInSeconds = 1.0; |
| 1554 isolate->ContextDisposedNotification(); | 1709 isolate->ContextDisposedNotification(); |
| 1555 isolate->IdleNotificationDeadline( | 1710 isolate->IdleNotificationDeadline( |
| 1556 g_platform->MonotonicallyIncreasingTime() + kLongIdlePauseInSeconds); | 1711 g_platform->MonotonicallyIncreasingTime() + kLongIdlePauseInSeconds); |
| 1557 } | 1712 } |
| 1558 if (options.invoke_weak_callbacks) { | 1713 if (options.invoke_weak_callbacks) { |
| 1559 // By sending a low memory notifications, we will try hard to collect all | 1714 // By sending a low memory notifications, we will try hard to collect all |
| 1560 // garbage and will therefore also invoke all weak callbacks of actually | 1715 // garbage and will therefore also invoke all weak callbacks of actually |
| 1561 // unreachable persistent handles. | 1716 // unreachable persistent handles. |
| 1562 isolate->LowMemoryNotification(); | 1717 isolate->LowMemoryNotification(); |
| 1563 } | 1718 } |
| 1564 } | 1719 } |
| 1565 | 1720 |
| 1566 | 1721 |
| 1567 #ifndef V8_SHARED | 1722 #ifndef V8_SHARED |
| 1723 void Shell::CleanupWorker(Isolate* isolate) { | |
| 1724 { | |
| 1725 HandleScope scope(isolate); | |
| 1726 worker_.WaitForThread(isolate); | |
| 1727 } | |
| 1728 PerIsolateData* data = PerIsolateData::Get(isolate); | |
| 1729 | |
| 1730 for (int i = 0; i < externalized_shared_contents_.length(); ++i) { | |
| 1731 const v8::SharedArrayBuffer::Contents& contents = | |
| 1732 externalized_shared_contents_[i]; | |
| 1733 data->array_buffer_allocator_->Free(contents.Data(), contents.ByteLength()); | |
| 1734 } | |
| 1735 externalized_shared_contents_.Clear(); | |
| 1736 } | |
| 1737 | |
| 1738 | |
| 1568 static void DumpHeapConstants(i::Isolate* isolate) { | 1739 static void DumpHeapConstants(i::Isolate* isolate) { |
| 1569 i::Heap* heap = isolate->heap(); | 1740 i::Heap* heap = isolate->heap(); |
| 1570 | 1741 |
| 1571 // Dump the INSTANCE_TYPES table to the console. | 1742 // Dump the INSTANCE_TYPES table to the console. |
| 1572 printf("# List of known V8 instance types.\n"); | 1743 printf("# List of known V8 instance types.\n"); |
| 1573 #define DUMP_TYPE(T) printf(" %d: \"%s\",\n", i::T, #T); | 1744 #define DUMP_TYPE(T) printf(" %d: \"%s\",\n", i::T, #T); |
| 1574 printf("INSTANCE_TYPES = {\n"); | 1745 printf("INSTANCE_TYPES = {\n"); |
| 1575 INSTANCE_TYPE_LIST(DUMP_TYPE) | 1746 INSTANCE_TYPE_LIST(DUMP_TYPE) |
| 1576 printf("}\n"); | 1747 printf("}\n"); |
| 1577 #undef DUMP_TYPE | 1748 #undef DUMP_TYPE |
| (...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1677 create_params.create_histogram_callback = CreateHistogram; | 1848 create_params.create_histogram_callback = CreateHistogram; |
| 1678 create_params.add_histogram_sample_callback = AddHistogramSample; | 1849 create_params.add_histogram_sample_callback = AddHistogramSample; |
| 1679 } | 1850 } |
| 1680 #endif | 1851 #endif |
| 1681 Isolate* isolate = Isolate::New(create_params); | 1852 Isolate* isolate = Isolate::New(create_params); |
| 1682 DumbLineEditor dumb_line_editor(isolate); | 1853 DumbLineEditor dumb_line_editor(isolate); |
| 1683 { | 1854 { |
| 1684 Isolate::Scope scope(isolate); | 1855 Isolate::Scope scope(isolate); |
| 1685 Initialize(isolate); | 1856 Initialize(isolate); |
| 1686 PerIsolateData data(isolate); | 1857 PerIsolateData data(isolate); |
| 1858 data.array_buffer_allocator_ = create_params.array_buffer_allocator; | |
| 1687 InitializeDebugger(isolate); | 1859 InitializeDebugger(isolate); |
| 1688 | 1860 |
| 1689 #ifndef V8_SHARED | 1861 #ifndef V8_SHARED |
| 1690 if (options.dump_heap_constants) { | 1862 if (options.dump_heap_constants) { |
| 1691 DumpHeapConstants(reinterpret_cast<i::Isolate*>(isolate)); | 1863 DumpHeapConstants(reinterpret_cast<i::Isolate*>(isolate)); |
| 1692 return 0; | 1864 return 0; |
| 1693 } | 1865 } |
| 1694 #endif | 1866 #endif |
| 1695 | 1867 |
| 1696 if (options.stress_opt || options.stress_deopt) { | 1868 if (options.stress_opt || options.stress_deopt) { |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1755 } | 1927 } |
| 1756 | 1928 |
| 1757 } // namespace v8 | 1929 } // namespace v8 |
| 1758 | 1930 |
| 1759 | 1931 |
| 1760 #ifndef GOOGLE3 | 1932 #ifndef GOOGLE3 |
| 1761 int main(int argc, char* argv[]) { | 1933 int main(int argc, char* argv[]) { |
| 1762 return v8::Shell::Main(argc, argv); | 1934 return v8::Shell::Main(argc, argv); |
| 1763 } | 1935 } |
| 1764 #endif | 1936 #endif |
| OLD | NEW |