17e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh// Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 27e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh// Use of this source code is governed by a BSD-style license that can be 37e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh// found in the LICENSE file. 47e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 57e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh#include "address_mapper.h" 67e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 77e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh#include "base/logging.h" 87e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 97e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntoshnamespace quipper { 107e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 117e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntoshAddressMapper::AddressMapper(const AddressMapper& source) { 127e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh mappings_ = source.mappings_; 137e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh} 147e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 157e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntoshbool AddressMapper::Map(const uint64_t real_addr, 167e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh const uint64_t size, 177e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh const bool remove_existing_mappings) { 187e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return MapWithID(real_addr, size, kuint64max, 0, remove_existing_mappings); 197e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh} 207e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 217e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntoshbool AddressMapper::MapWithID(const uint64_t real_addr, 227e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh const uint64_t size, 237e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh const uint64_t id, 247e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh const uint64_t offset_base, 257e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh bool remove_existing_mappings) { 267e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh MappedRange range; 277e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh range.real_addr = real_addr; 287e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh range.size = size; 297e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh range.id = id; 307e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh range.offset_base = offset_base; 317e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 327e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (size == 0) { 337e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh LOG(ERROR) << "Must allocate a nonzero-length address range."; 347e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return false; 357e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 367e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 377e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // Check that this mapping does not overflow the address space. 387e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (real_addr + size - 1 != kuint64max && 397e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh !(real_addr + size > real_addr)) { 407e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh DumpToLog(); 417e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh LOG(ERROR) << "Address mapping at " << std::hex << real_addr 427e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh << " with size " << std::hex << size << " overflows."; 437e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return false; 447e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 457e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 467e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // Check for collision with an existing mapping. This must be an overlap that 477e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // does not result in one range being completely covered by another 487e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh MappingList::iterator iter; 497e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh MappingList mappings_to_delete; 507e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh bool old_range_found = false; 517e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh MappedRange old_range; 527e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh for (iter = mappings_.begin(); iter != mappings_.end(); ++iter) { 537e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (!iter->Intersects(range)) 547e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh continue; 557e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // Quit if existing ranges that collide aren't supposed to be removed. 567e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (!remove_existing_mappings) 577e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return false; 587e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (!old_range_found && iter->Covers(range) && iter->size > range.size) { 597e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh old_range_found = true; 607e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh old_range = *iter; 617e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh continue; 627e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 637e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh mappings_to_delete.push_back(*iter); 647e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 657e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 667e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh while (!mappings_to_delete.empty()) { 677e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh const MappedRange& range = mappings_to_delete.front(); 687e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh CHECK(Unmap(range)); 697e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh mappings_to_delete.pop_front(); 707e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 717e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 727e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // Otherwise check for this range being covered by another range. If that 737e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // happens, split or reduce the existing range to make room. 747e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (old_range_found) { 757e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh CHECK(Unmap(old_range)); 767e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 777e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh uint64_t gap_before = range.real_addr - old_range.real_addr; 787e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh uint64_t gap_after = (old_range.real_addr + old_range.size) - 797e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh (range.real_addr + range.size); 807e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 817e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (gap_before) { 827e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh CHECK(MapWithID(old_range.real_addr, 837e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh gap_before, 847e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh old_range.id, 857e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh old_range.offset_base, 867e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh false)); 877e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 887e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 897e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh CHECK(MapWithID(range.real_addr, range.size, id, offset_base, false)); 907e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 917e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (gap_after) { 927e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh CHECK(MapWithID(range.real_addr + range.size, 937e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh gap_after, 947e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh old_range.id, 957e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh old_range.offset_base + gap_before + range.size, 967e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh false)); 977e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 987e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 997e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return true; 1007e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 1017e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1027e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // Now search for a location for the new range. It should be in the first 1037e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // free block in quipper space. 1047e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1057e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // If there is no existing mapping, add it to the beginning of quipper space. 1067e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (mappings_.empty()) { 1077e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh range.mapped_addr = 0; 1087e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh range.unmapped_space_after = kuint64max - range.size; 1097e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh mappings_.push_back(range); 1107e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return true; 1117e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 1127e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1137e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // If there is space before the first mapped range in quipper space, use it. 1147e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (mappings_.begin()->mapped_addr >= range.size) { 1157e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh range.mapped_addr = 0; 1167e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh range.unmapped_space_after = mappings_.begin()->mapped_addr - range.size; 1177e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh mappings_.push_front(range); 1187e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return true; 1197e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 1207e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1217e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // Otherwise, search through the existing mappings for a free block after one 1227e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // of them. 1237e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh for (iter = mappings_.begin(); iter != mappings_.end(); ++iter) { 1247e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (iter->unmapped_space_after < range.size) 1257e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh continue; 1267e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1277e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh range.mapped_addr = iter->mapped_addr + iter->size; 1287e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh range.unmapped_space_after = iter->unmapped_space_after - range.size; 1297e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh iter->unmapped_space_after = 0; 1307e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1317e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh mappings_.insert(++iter, range); 1327e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return true; 1337e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 1347e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1357e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // If it still hasn't succeeded in mapping, it means there is no free space in 1367e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // quipper space large enough for a mapping of this size. 1377e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh DumpToLog(); 1387e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh LOG(ERROR) << "Could not find space to map addr=" << std::hex << real_addr 1397e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh << " with size " << std::hex << size; 1407e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return false; 1417e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh} 1427e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1437e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntoshvoid AddressMapper::DumpToLog() const { 1447e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh MappingList::const_iterator it; 1457e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh for (it = mappings_.begin(); it != mappings_.end(); ++it) { 1467e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh LOG(INFO) << " real_addr: " << std::hex << it->real_addr 1477e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh << " mapped: " << std::hex << it->mapped_addr 1487e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh << " id: " << std::hex << it->id 1497e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh << " size: " << std::hex << it->size; 1507e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 1517e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh} 1527e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1537e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntoshbool AddressMapper::GetMappedAddress(const uint64_t real_addr, 1547e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh uint64_t* mapped_addr) const { 1557e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh CHECK(mapped_addr); 1567e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh MappingList::const_iterator iter; 1577e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh for (iter = mappings_.begin(); iter != mappings_.end(); ++iter) { 1587e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (!iter->ContainsAddress(real_addr)) 1597e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh continue; 1607e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh *mapped_addr = iter->mapped_addr + real_addr - iter->real_addr; 1617e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return true; 1627e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 1637e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return false; 1647e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh} 1657e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1667e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntoshbool AddressMapper::GetMappedIDAndOffset(const uint64_t real_addr, 1677e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh uint64_t* id, 1687e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh uint64_t* offset) const { 1697e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh CHECK(id); 1707e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh CHECK(offset); 1717e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh MappingList::const_iterator iter; 1727e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh for (iter = mappings_.begin(); iter != mappings_.end(); ++iter) { 1737e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (!iter->ContainsAddress(real_addr)) 1747e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh continue; 1757e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh *id = iter->id; 1767e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh *offset = real_addr - iter->real_addr + iter->offset_base; 1777e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return true; 1787e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 1797e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return false; 1807e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh} 1817e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1827e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntoshuint64_t AddressMapper::GetMaxMappedLength() const { 1837e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (IsEmpty()) 1847e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return 0; 1857e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1867e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh uint64_t min = mappings_.begin()->mapped_addr; 1877e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1887e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh MappingList::const_iterator iter = mappings_.end(); 1897e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh --iter; 1907e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh uint64_t max = iter->mapped_addr + iter->size; 1917e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1927e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return max - min; 1937e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh} 1947e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 1957e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntoshbool AddressMapper::Unmap(const MappedRange& range) { 1967e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh MappingList::iterator iter; 1977e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // TODO(sque): this is highly inefficient since Unmap() is called from a 1987e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // function that has already iterated to the right place within |mappings_|. 1997e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // For a first revision, I am sacrificing efficiency for of clarity, due to 2007e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // the trickiness of removing elements using iterators. 2017e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh for (iter = mappings_.begin(); iter != mappings_.end(); ++iter) { 2027e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (range.real_addr == iter->real_addr && range.size == iter->size) { 2037e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // Add the freed up space to the free space counter of the previous 2047e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh // mapped region, if it exists. 2057e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh if (iter != mappings_.begin()) { 2067e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh --iter; 2077e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh iter->unmapped_space_after += range.size + range.unmapped_space_after; 2087e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh ++iter; 2097e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 2107e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh mappings_.erase(iter); 2117e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return true; 2127e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 2137e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh } 2147e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh return false; 2157e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh} 2167e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh 2177e2f4e9d384d501cf86118ebac4b8de2b86eac53Than McIntosh} // namespace quipper 218