// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "platform/globals.h" #if defined(DART_HOST_OS_FUCHSIA) #include "bin/eventhandler.h" #include "bin/eventhandler_fuchsia.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bin/fdutils.h" #include "bin/lockers.h" #include "bin/socket.h" #include "bin/thread.h" #include "bin/utils.h" #include "platform/hashmap.h" #include "platform/syslog.h" #include "platform/utils.h" // The EventHandler for Fuchsia uses its "ports v2" API: // https://fuchsia.googlesource.com/fuchsia/+/HEAD/zircon/docs/syscalls/port_create.md // This API does not have epoll()-like edge triggering (EPOLLET). Since clients // of the EventHandler expect edge-triggered notifications, we must simulate it. // When a packet from zx_port_wait() indicates that a signal is asserted for a // handle, we unsubscribe from that signal until the event that asserted the // signal can be processed. For example: // // 1. We get ZX_SOCKET_WRITABLE from zx_port_wait() for a handle. // 2. We send kOutEvent to the Dart thread. // 3. We unsubscribe from further ZX_SOCKET_WRITABLE signals for the handle. // 4. Some time later the Dart thread actually does a write(). // 5. After writing, the Dart thread resubscribes to write events. // // We use the same procedure for ZX_SOCKET_READABLE, and read()/accept(). // define EVENTHANDLER_LOG_ERROR to get log messages only for errors. // define EVENTHANDLER_LOG_INFO to get log messages for both information and // errors. // #define EVENTHANDLER_LOG_INFO 1 #define EVENTHANDLER_LOG_ERROR 1 #if defined(EVENTHANDLER_LOG_INFO) || defined(EVENTHANDLER_LOG_ERROR) #define LOG_ERR(msg, ...) \ { \ int err = errno; \ Syslog::PrintErr("Dart EventHandler ERROR: %s:%d: " msg, __FILE__, \ __LINE__, ##__VA_ARGS__); \ errno = err; \ } #if defined(EVENTHANDLER_LOG_INFO) #define LOG_INFO(msg, ...) \ Syslog::Print("Dart EventHandler INFO: %s:%d: " msg, __FILE__, __LINE__, \ ##__VA_ARGS__) #else #define LOG_INFO(msg, ...) #endif // defined(EVENTHANDLER_LOG_INFO) #else #define LOG_ERR(msg, ...) #define LOG_INFO(msg, ...) #endif // defined(EVENTHANDLER_LOG_INFO) || defined(EVENTHANDLER_LOG_ERROR) namespace dart { namespace bin { intptr_t IOHandle::Read(void* buffer, intptr_t num_bytes) { MutexLocker ml(&mutex_); const ssize_t read_bytes = NO_RETRY_EXPECTED(read(fd_, buffer, num_bytes)); const int err = errno; LOG_INFO("IOHandle::Read: fd = %ld. read %ld bytes\n", fd_, read_bytes); // Track the number of bytes available to read. if (read_bytes > 0) { available_bytes_ -= (available_bytes_ >= read_bytes) ? read_bytes : available_bytes_; } // If we have read all available bytes, or if there was an error, then // re-enable read events. We re-enable read events even if read() returns // an error. The error might be, e.g. EWOULDBLOCK, in which case // resubscription is necessary. Logic in the caller decides which errors // are real, and which are ignore-and-continue. if ((available_bytes_ == 0) || (read_bytes < 0)) { // Resubscribe to read events. read_events_enabled_ = true; if (wait_key_ == 0) { LOG_ERR("IOHandle::Read calling AsyncWaitLocked with wait_key_ == 0"); } if (!AsyncWaitLocked(ZX_HANDLE_INVALID, POLLIN, wait_key_)) { LOG_ERR("IOHandle::AsyncWait failed for fd = %ld\n", fd_); } } errno = err; return read_bytes; } intptr_t IOHandle::Write(const void* buffer, intptr_t num_bytes) { MutexLocker ml(&mutex_); const ssize_t written_bytes = NO_RETRY_EXPECTED(write(fd_, buffer, num_bytes)); const int err = errno; LOG_INFO("IOHandle::Write: fd = %ld. wrote %ld bytes\n", fd_, written_bytes); // Resubscribe to write events. write_events_enabled_ = true; if (wait_key_ == 0) { LOG_ERR("IOHandle::Write calling AsyncWaitLocked with wait_key_ == 0"); } if (!AsyncWaitLocked(ZX_HANDLE_INVALID, POLLOUT, wait_key_)) { LOG_ERR("IOHandle::AsyncWait failed for fd = %ld\n", fd_); } errno = err; return written_bytes; } intptr_t IOHandle::Accept(RawAddr* addr) { MutexLocker ml(&mutex_); const intptr_t socket = NO_RETRY_EXPECTED(accept(fd_, &addr->addr, &addr->size)); const int err = errno; LOG_INFO("IOHandle::Accept: fd = %ld. socket = %ld\n", fd_, socket); // Re-subscribe to read events. read_events_enabled_ = true; if (wait_key_ == 0) { LOG_ERR("IOHandle::Accept calling AsyncWaitLocked with wait_key_ == 0"); } if (!AsyncWaitLocked(ZX_HANDLE_INVALID, POLLIN, wait_key_)) { LOG_ERR("IOHandle::AsyncWait failed for fd = %ld\n", fd_); } errno = err; return socket; } intptr_t IOHandle::AvailableBytes() { MutexLocker ml(&mutex_); ASSERT(fd_ >= 0); intptr_t available = FDUtils::AvailableBytes(fd_); LOG_INFO("IOHandle::AvailableBytes(): fd = %ld, bytes = %ld\n", fd_, available); if (available < 0) { // If there is an error, we set available to 1 to trigger a read event that // then propagates the error. available = 1; } available_bytes_ = available; return available; } void IOHandle::Close() { MutexLocker ml(&mutex_); VOID_NO_RETRY_EXPECTED(close(fd_)); } uint32_t IOHandle::MaskToEpollEvents(intptr_t mask) { MutexLocker ml(&mutex_); // Do not ask for POLLERR and POLLHUP explicitly as they are // triggered anyway. uint32_t events = 0; // Do not subscribe to read closed events when kCloseEvent has already been // sent to the Dart thread. if (close_events_enabled_) { events |= POLLRDHUP; } if (read_events_enabled_ && ((mask & (1 << kInEvent)) != 0)) { events |= POLLIN; } if (write_events_enabled_ && ((mask & (1 << kOutEvent)) != 0)) { events |= POLLOUT; } return events; } intptr_t IOHandle::EpollEventsToMask(intptr_t events) { if ((events & POLLERR) != 0) { // Return error only if POLLIN is present. return ((events & POLLIN) != 0) ? (1 << kErrorEvent) : 0; } intptr_t event_mask = 0; if ((events & POLLIN) != 0) { event_mask |= (1 << kInEvent); } if ((events & POLLOUT) != 0) { event_mask |= (1 << kOutEvent); } if ((events & (POLLHUP | POLLRDHUP)) != 0) { event_mask |= (1 << kCloseEvent); } return event_mask; } bool IOHandle::AsyncWaitLocked(zx_handle_t port, uint32_t events, uint64_t key) { LOG_INFO("IOHandle::AsyncWaitLocked: fd = %ld\n", fd_); if (key == 0) { LOG_ERR("IOHandle::AsyncWaitLocked called with key == 0"); } // The call to fdio_unsafe_fd_to_io() in the DescriptorInfo constructor may // have returned nullptr. If it did, propagate the problem up to Dart. if (fdio_ == nullptr) { LOG_ERR("fdio_unsafe_fd_to_io(%ld) returned nullptr\n", fd_); return false; } zx_handle_t handle; zx_signals_t signals; fdio_unsafe_wait_begin(fdio_, events, &handle, &signals); if (handle == ZX_HANDLE_INVALID) { LOG_ERR("fd = %ld fdio_unsafe_wait_begin returned an invalid handle\n", fd_); return false; } // Remember the port. Use the remembered port if the argument "port" is // ZX_HANDLE_INVALID. ASSERT((port != ZX_HANDLE_INVALID) || (port_ != ZX_HANDLE_INVALID)); if ((port_ == ZX_HANDLE_INVALID) || (port != ZX_HANDLE_INVALID)) { port_ = port; } wait_key_ = key; LOG_INFO("zx_object_wait_async(fd = %ld, signals = %x)\n", fd_, signals); zx_status_t status = zx_object_wait_async(handle, port_, key, signals, ZX_WAIT_ASYNC_ONCE); if (status != ZX_OK) { LOG_ERR("zx_object_wait_async failed: %s\n", zx_status_get_string(status)); return false; } return true; } bool IOHandle::AsyncWait(zx_handle_t port, uint32_t events, uint64_t key) { MutexLocker ml(&mutex_); return AsyncWaitLocked(port, events, key); } void IOHandle::CancelWait(zx_handle_t port, uint64_t key) { MutexLocker ml(&mutex_); LOG_INFO("IOHandle::CancelWait: fd = %ld\n", fd_); ASSERT(port != ZX_HANDLE_INVALID); if (key == 0) { LOG_ERR("IOHandle::CancelWait calling zx_port_cancel_key with key == 0"); } zx_status_t status = zx_port_cancel_key(port, 0u, key); if ((status != ZX_OK) && (status != ZX_ERR_NOT_FOUND)) { LOG_ERR("zx_port_cancel_key failed: %s\n", zx_status_get_string(status)); } } uint32_t IOHandle::WaitEnd(zx_signals_t observed) { MutexLocker ml(&mutex_); uint32_t events = 0; fdio_unsafe_wait_end(fdio_, observed, &events); LOG_INFO("IOHandle::WaitEnd: fd = %ld, events = %x\n", fd_, events); return events; } // This function controls the simulation of edge-triggering. It is responsible // for removing events from the event mask when they should be suppressed, and // for suppressing future events. Events are unsuppressed by their respective // operations by the Dart thread on the socket---that is, where the // *_events_enabled_ flags are set to true. intptr_t IOHandle::ToggleEvents(intptr_t event_mask) { MutexLocker ml(&mutex_); // If write events are disabled, then remove the kOutEvent bit from the // event mask. if (!write_events_enabled_) { LOG_INFO( "IOHandle::ToggleEvents: fd = %ld " "de-asserting kOutEvent\n", fd_); event_mask = event_mask & ~(1 << kOutEvent); } // If the kOutEvent bit is set, then suppress future write events until the // Dart thread writes. if ((event_mask & (1 << kOutEvent)) != 0) { LOG_INFO( "IOHandle::ToggleEvents: fd = %ld " "asserting kOutEvent and disabling\n", fd_); write_events_enabled_ = false; } // If read events are disabled, then remove the kInEvent bit from the event // mask. if (!read_events_enabled_) { LOG_INFO( "IOHandle::ToggleEvents: fd = %ld " "de-asserting kInEvent\n", fd_); event_mask = event_mask & ~(1 << kInEvent); } // We may get In events without available bytes, so we must make sure there // are actually bytes, or we will never resubscribe (due to a short-circuit // on the Dart side). // // This happens due to how packets get enqueued on the port with all signals // asserted at that time. Sometimes we enqueue a packet due to // zx_object_wait_async e.g. for POLLOUT (writability) while the socket is // readable and while we have a Read queued up on the Dart side. This packet // will also have POLLIN (readable) asserted. We may then perform the Read // and drain the socket before our zx_port_wait is serviced, at which point // when we process the packet for POLLOUT with its stale POLLIN (readable) // signal, the socket is no longer actually readable. // // As a detail, negative available bytes (errors) are handled specially; see // IOHandle::AvailableBytes for more information. if ((event_mask & (1 << kInEvent)) != 0) { if (FDUtils::AvailableBytes(fd_) != 0) { LOG_INFO( "IOHandle::ToggleEvents: fd = %ld " "asserting kInEvent and disabling with bytes available\n", fd_); read_events_enabled_ = false; } // Also suppress future read events if we get a kCloseEvent. This is to // account for POLLIN being set by Fuchsia when the socket is read-closed. if ((event_mask & (1 << kCloseEvent)) != 0) { LOG_INFO( "IOHandle::ToggleEvents: fd = %ld " "asserting kInEvent and disabling due to a close event\n", fd_); read_events_enabled_ = false; } } // If the close events are disabled, then remove the kCloseEvent bit from the // event mask. if (!close_events_enabled_) { LOG_INFO( "IOHandle::ToggleEvents: fd = %ld " "de-asserting kCloseEvent\n", fd_); event_mask = event_mask & ~(1 << kCloseEvent); } // If the kCloseEvent bit is set, then suppress future close events, they will // be ignored by the Dart thread. See _NativeSocket.multiplex in // socket_patch.dart. if ((event_mask & (1 << kCloseEvent)) != 0) { LOG_INFO( "IOHandle::ToggleEvents: fd = %ld " "asserting kCloseEvent and disabling\n", fd_); close_events_enabled_ = false; } return event_mask; } void EventHandlerImplementation::AddToPort(zx_handle_t port_handle, DescriptorInfo* di) { const uint32_t events = di->io_handle()->MaskToEpollEvents(di->Mask()); const uint64_t key = reinterpret_cast(di); if (key == 0) { LOG_ERR( "EventHandlerImplementation::AddToPort calling AsyncWait with key == " "0"); } if (!di->io_handle()->AsyncWait(port_handle, events, key)) { di->NotifyAllDartPorts(1 << kCloseEvent); } } void EventHandlerImplementation::RemoveFromPort(zx_handle_t port_handle, DescriptorInfo* di) { const uint64_t key = reinterpret_cast(di); if (key == 0) { LOG_ERR( "EventHandlerImplementation::RemoveFromPort calling CancelWait with " "key == 0"); } di->io_handle()->CancelWait(port_handle, key); } EventHandlerImplementation::EventHandlerImplementation() : socket_map_(&SimpleHashMap::SamePointerValue, 16) { shutdown_ = false; // Create the port. port_handle_ = ZX_HANDLE_INVALID; zx_status_t status = zx_port_create(0, &port_handle_); if (status != ZX_OK) { // This is a FATAL because the VM won't work at all if we can't create this // port. FATAL("zx_port_create failed: %s\n", zx_status_get_string(status)); } ASSERT(port_handle_ != ZX_HANDLE_INVALID); } static void DeleteDescriptorInfo(void* info) { DescriptorInfo* di = reinterpret_cast(info); LOG_INFO("Closed %ld\n", di->io_handle()->fd()); di->Close(); delete di; } EventHandlerImplementation::~EventHandlerImplementation() { socket_map_.Clear(DeleteDescriptorInfo); zx_handle_close(port_handle_); port_handle_ = ZX_HANDLE_INVALID; } void EventHandlerImplementation::UpdatePort(intptr_t old_mask, DescriptorInfo* di) { const intptr_t new_mask = di->Mask(); if ((old_mask != 0) && (new_mask == 0)) { RemoveFromPort(port_handle_, di); } else if ((old_mask == 0) && (new_mask != 0)) { AddToPort(port_handle_, di); } else if ((old_mask != 0) && (new_mask != 0)) { ASSERT((old_mask == new_mask) || !di->IsListeningSocket()); RemoveFromPort(port_handle_, di); AddToPort(port_handle_, di); } } DescriptorInfo* EventHandlerImplementation::GetDescriptorInfo( intptr_t fd, bool is_listening) { IOHandle* handle = reinterpret_cast(fd); ASSERT(handle->fd() >= 0); SimpleHashMap::Entry* entry = socket_map_.Lookup(GetHashmapKeyFromFd(handle->fd()), GetHashmapHashFromFd(handle->fd()), true); ASSERT(entry != nullptr); DescriptorInfo* di = reinterpret_cast(entry->value); if (di == nullptr) { // If there is no data in the hash map for this file descriptor a // new DescriptorInfo for the file descriptor is inserted. if (is_listening) { di = new DescriptorInfoMultiple(fd); } else { di = new DescriptorInfoSingle(fd); } entry->value = di; } ASSERT(fd == di->fd()); return di; } void EventHandlerImplementation::WakeupHandler(intptr_t id, Dart_Port dart_port, int64_t data) { COMPILE_ASSERT(sizeof(InterruptMessage) <= sizeof(zx_packet_user_t)); zx_port_packet_t pkt; InterruptMessage* msg = reinterpret_cast(&pkt.user); pkt.key = kInterruptPacketKey; msg->id = id; msg->dart_port = dart_port; msg->data = data; zx_status_t status = zx_port_queue(port_handle_, &pkt); if (status != ZX_OK) { // This is a FATAL because the VM won't work at all if we can't send any // messages to the EventHandler thread. FATAL("zx_port_queue failed: %s\n", zx_status_get_string(status)); } } void EventHandlerImplementation::HandleInterrupt(InterruptMessage* msg) { if (msg->id == kTimerId) { LOG_INFO("HandleInterrupt read timer update\n"); timeout_queue_.UpdateTimeout(msg->dart_port, msg->data); return; } else if (msg->id == kShutdownId) { LOG_INFO("HandleInterrupt read shutdown\n"); shutdown_ = true; return; } ASSERT((msg->data & COMMAND_MASK) != 0); LOG_INFO("HandleInterrupt command:\n"); Socket* socket = reinterpret_cast(msg->id); RefCntReleaseScope rs(socket); if (socket->fd() == -1) { return; } IOHandle* io_handle = reinterpret_cast(socket->fd()); const intptr_t fd = io_handle->fd(); DescriptorInfo* di = GetDescriptorInfo(socket->fd(), IS_LISTENING_SOCKET(msg->data)); ASSERT(io_handle == di->io_handle()); if (IS_COMMAND(msg->data, kShutdownReadCommand)) { ASSERT(!di->IsListeningSocket()); // Close the socket for reading. LOG_INFO("\tSHUT_RD: %ld\n", fd); VOID_NO_RETRY_EXPECTED(shutdown(fd, SHUT_RD)); } else if (IS_COMMAND(msg->data, kShutdownWriteCommand)) { ASSERT(!di->IsListeningSocket()); // Close the socket for writing. LOG_INFO("\tSHUT_WR: %ld\n", fd); VOID_NO_RETRY_EXPECTED(shutdown(fd, SHUT_WR)); } else if (IS_COMMAND(msg->data, kCloseCommand)) { // Close the socket and free system resources and move on to next // message. const intptr_t old_mask = di->Mask(); Dart_Port port = msg->dart_port; if (port != ILLEGAL_PORT) { di->RemovePort(port); } const intptr_t new_mask = di->Mask(); UpdatePort(old_mask, di); LOG_INFO("\tCLOSE: %ld: %lx -> %lx\n", fd, old_mask, new_mask); if (di->IsListeningSocket()) { // We only close the socket file descriptor from the operating // system if there are no other dart socket objects which // are listening on the same (address, port) combination. ListeningSocketRegistry* registry = ListeningSocketRegistry::Instance(); MutexLocker locker(registry->mutex()); if (registry->CloseSafe(socket)) { ASSERT(new_mask == 0); socket_map_.Remove(GetHashmapKeyFromFd(fd), GetHashmapHashFromFd(fd)); di->Close(); delete di; socket->CloseFd(); } socket->SetClosedFd(); } else { ASSERT(new_mask == 0); socket_map_.Remove(GetHashmapKeyFromFd(fd), GetHashmapHashFromFd(fd)); di->Close(); delete di; socket->CloseFd(); } if (port != 0) { const bool success = DartUtils::PostInt32(port, 1 << kDestroyedEvent); if (!success) { LOG_INFO("Failed to post destroy event to port %ld\n", port); } } } else if (IS_COMMAND(msg->data, kReturnTokenCommand)) { const int count = TOKEN_COUNT(msg->data); const intptr_t old_mask = di->Mask(); LOG_INFO("\t Return Token: %ld: %lx\n", fd, old_mask); di->ReturnTokens(msg->dart_port, count); UpdatePort(old_mask, di); } else if (IS_COMMAND(msg->data, kSetEventMaskCommand)) { // `events` can only have kInEvent/kOutEvent flags set. const intptr_t events = msg->data & EVENT_MASK; ASSERT(0 == (events & ~(1 << kInEvent | 1 << kOutEvent))); const intptr_t old_mask = di->Mask(); LOG_INFO("\t Set Event Mask: %ld: %lx %lx\n", fd, old_mask, msg->data & EVENT_MASK); di->SetPortAndMask(msg->dart_port, msg->data & EVENT_MASK); UpdatePort(old_mask, di); } else { UNREACHABLE(); } } void EventHandlerImplementation::HandlePacket(zx_port_packet_t* pkt) { if (pkt->key == 0) { LOG_ERR("HandlePacket called with pkt->key==0"); return; } LOG_INFO("HandlePacket: Got event packet: key=%lx\n", pkt->key); LOG_INFO("HandlePacket: Got event packet: type=%x\n", pkt->type); LOG_INFO("HandlePacket: Got event packet: status=%d\n", pkt->status); if (pkt->type == ZX_PKT_TYPE_USER) { ASSERT(pkt->key == kInterruptPacketKey); InterruptMessage* msg = reinterpret_cast(&pkt->user); HandleInterrupt(msg); return; } if (pkt->type != ZX_PKT_TYPE_SIGNAL_ONE) { LOG_ERR("HandlePacket: Got unexpected packet type: key=%x\n", pkt->type); return; } // Handle pkt->type == ZX_PKT_TYPE_SIGNAL_ONE LOG_INFO("HandlePacket: Got event packet: observed = %x\n", pkt->signal.observed); LOG_INFO("HandlePacket: Got event packet: count = %ld\n", pkt->signal.count); DescriptorInfo* di = reinterpret_cast(pkt->key); zx_signals_t observed = pkt->signal.observed; const intptr_t old_mask = di->Mask(); const uint32_t epoll_event = di->io_handle()->WaitEnd(observed); intptr_t event_mask = IOHandle::EpollEventsToMask(epoll_event); if ((event_mask & (1 << kErrorEvent)) != 0) { di->NotifyAllDartPorts(event_mask); } else if (event_mask != 0) { event_mask = di->io_handle()->ToggleEvents(event_mask); if (event_mask != 0) { Dart_Port port = di->NextNotifyDartPort(event_mask); ASSERT(port != 0); bool success = DartUtils::PostInt32(port, event_mask); if (!success) { // This can happen if e.g. the isolate that owns the port has died // for some reason. LOG_INFO("Failed to post event to port %ld\n", port); } } } UpdatePort(old_mask, di); } int64_t EventHandlerImplementation::GetTimeout() const { if (!timeout_queue_.HasTimeout()) { return kInfinityTimeout; } int64_t millis = timeout_queue_.CurrentTimeout() - TimerUtils::GetCurrentMonotonicMillis(); return (millis < 0) ? 0 : millis; } void EventHandlerImplementation::HandleTimeout() { if (timeout_queue_.HasTimeout()) { int64_t millis = timeout_queue_.CurrentTimeout() - TimerUtils::GetCurrentMonotonicMillis(); if (millis <= 0) { DartUtils::PostNull(timeout_queue_.CurrentPort()); timeout_queue_.RemoveCurrent(); } } } void EventHandlerImplementation::Poll(uword args) { EventHandler* handler = reinterpret_cast(args); EventHandlerImplementation* handler_impl = &handler->delegate_; ASSERT(handler_impl != nullptr); zx_port_packet_t pkt; while (!handler_impl->shutdown_) { int64_t millis = handler_impl->GetTimeout(); ASSERT((millis == kInfinityTimeout) || (millis >= 0)); LOG_INFO("zx_port_wait(millis = %ld)\n", millis); zx_status_t status = zx_port_wait(handler_impl->port_handle_, millis == kInfinityTimeout ? ZX_TIME_INFINITE : zx_deadline_after(ZX_MSEC(millis)), &pkt); if (status == ZX_ERR_TIMED_OUT) { handler_impl->HandleTimeout(); } else if (status != ZX_OK) { FATAL("zx_port_wait failed: %s\n", zx_status_get_string(status)); } else { handler_impl->HandleTimeout(); handler_impl->HandlePacket(&pkt); } } DEBUG_ASSERT(ReferenceCounted::instances() == 0); handler->NotifyShutdownDone(); } void EventHandlerImplementation::Start(EventHandler* handler) { Thread::Start("dart:io EventHandler", &EventHandlerImplementation::Poll, reinterpret_cast(handler)); } void EventHandlerImplementation::Shutdown() { SendData(kShutdownId, 0, 0); } void EventHandlerImplementation::SendData(intptr_t id, Dart_Port dart_port, int64_t data) { WakeupHandler(id, dart_port, data); } void* EventHandlerImplementation::GetHashmapKeyFromFd(intptr_t fd) { // The hashmap does not support keys with value 0. return reinterpret_cast(fd + 1); } uint32_t EventHandlerImplementation::GetHashmapHashFromFd(intptr_t fd) { // The hashmap does not support keys with value 0. return dart::Utils::WordHash(fd + 1); } } // namespace bin } // namespace dart #endif // defined(DART_HOST_OS_FUCHSIA)