<commit_before>/*

Copyright (c) 2007, Arvid Norberg
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in
      the documentation and/or other materials provided with the distribution.
    * Neither the name of the author nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/

#include <boost/version.hpp>

#include <boost/bind.hpp>

#include "libtorrent/socket.hpp"
#include "libtorrent/enum_net.hpp"
#include "libtorrent/broadcast_socket.hpp"
#include "libtorrent/assert.hpp"

#ifndef NDEBUG
//#include "libtorrent/socket_io.hpp"
#endif

#if BOOST_VERSION < 103500
#include <asio/ip/host_name.hpp>
#include <asio/ip/multicast.hpp>
#else
#include <boost/asio/ip/host_name.hpp>
#include <boost/asio/ip/multicast.hpp>
#endif

namespace libtorrent
{
	bool is_local(address const& a)
	{
#if TORRENT_USE_IPV6
		if (a.is_v6()) return a.to_v6().is_link_local();
#endif
		address_v4 a4 = a.to_v4();
		unsigned long ip = a4.to_ulong();
		return ((ip & 0xff000000) == 0x0a000000 // 10.x.x.x
			|| (ip & 0xfff00000) == 0xac100000 // 172.16.x.x
			|| (ip & 0xffff0000) == 0xc0a80000 // 192.168.x.x
			|| (ip & 0xffff0000) == 0xa9fe0000); // 169.254.x.x
	}

	bool is_loopback(address const& addr)
	{
#if TORRENT_USE_IPV6
		if (addr.is_v4())
			return addr.to_v4() == address_v4::loopback();
		else
			return addr.to_v6() == address_v6::loopback();
#else
		return addr.to_v4() == address_v4::loopback();
#endif
	}

	bool is_multicast(address const& addr)
	{
#if TORRENT_USE_IPV6
		if (addr.is_v4())
			return addr.to_v4().is_multicast();
		else
			return addr.to_v6().is_multicast();
#else
		return addr.to_v4().is_multicast();
#endif
	}

	bool is_any(address const& addr)
	{
#if TORRENT_USE_IPV6
		if (addr.is_v4())
			return addr.to_v4() == address_v4::any();
		else if (addr.to_v6().is_v4_mapped())
			return (addr.to_v6().to_v4() == address_v4::any());
		else
			return addr.to_v6() == address_v6::any();
#else
		return addr.to_v4() == address_v4::any();
#endif
	}

	bool supports_ipv6()
	{
#if TORRENT_USE_IPV6
		error_code ec;
		address::from_string("::1", ec);
		return !ec;
#else
		return false;
#endif
	}

	address guess_local_address(io_service& ios)
	{
		// make a best guess of the interface we're using and its IP
		error_code ec;
		std::vector<ip_interface> const& interfaces = enum_net_interfaces(ios, ec);
		address ret = address_v4::any();
		for (std::vector<ip_interface>::const_iterator i = interfaces.begin()
			, end(interfaces.end()); i != end; ++i)
		{
			address const& a = i->interface_address;
			if (is_loopback(a)
				|| is_multicast(a)
				|| is_any(a)) continue;

			// prefer a v4 address, but return a v6 if
			// there are no v4
			if (a.is_v4()) return a;

			if (ret != address_v4::any())
				ret = a;
		}
		return ret;
	}

	// count the length of the common bit prefix
	int common_bits(unsigned char const* b1
		, unsigned char const* b2, int n)
	{
		for (int i = 0; i < n; ++i, ++b1, ++b2)
		{
			unsigned char a = *b1 ^ *b2;
			if (a == 0) continue;
			int ret = i * 8 + 8;
			for (; a > 0; a >>= 1) --ret;
			return ret;
		}
		return n * 8;
	}

	// returns the number of bits in that differ from the right
	// between the addresses.
	int cidr_distance(address const& a1, address const& a2)
	{
#if TORRENT_USE_IPV6
		if (a1.is_v4() && a2.is_v4())
		{
#endif
			// both are v4
			address_v4::bytes_type b1 = a1.to_v4().to_bytes();
			address_v4::bytes_type b2 = a2.to_v4().to_bytes();
			return address_v4::bytes_type::static_size * 8
				- common_bits(b1.c_array(), b2.c_array(), b1.size());
#if TORRENT_USE_IPV6
		}

		address_v6::bytes_type b1;
		address_v6::bytes_type b2;
		if (a1.is_v4()) b1 = address_v6::v4_mapped(a1.to_v4()).to_bytes();
		else b1 = a1.to_v6().to_bytes();
		if (a2.is_v4()) b2 = address_v6::v4_mapped(a2.to_v4()).to_bytes();
		else b2 = a2.to_v6().to_bytes();
		return address_v6::bytes_type::static_size * 8
			- common_bits(b1.c_array(), b2.c_array(), b1.size());
#endif
	}

	broadcast_socket::broadcast_socket(io_service& ios
		, udp::endpoint const& multicast_endpoint
		, receive_handler_t const& handler
		, bool loopback)
		: m_multicast_endpoint(multicast_endpoint)
		, m_on_receive(handler)
	{
		TORRENT_ASSERT(is_multicast(m_multicast_endpoint.address()));

		using namespace asio::ip::multicast;

		error_code ec;
		std::vector<ip_interface> interfaces = enum_net_interfaces(ios, ec);

		if (multicast_endpoint.address().is_v4())
			open_multicast_socket(ios, address_v4::any(), loopback, ec);
		else
			open_multicast_socket(ios, address_v6::any(), loopback, ec);

		for (std::vector<ip_interface>::const_iterator i = interfaces.begin()
			, end(interfaces.end()); i != end; ++i)
		{
			// only multicast on compatible networks
			if (i->interface_address.is_v4() != multicast_endpoint.address().is_v4()) continue;
			// ignore any loopback interface
			if (!loopback && is_loopback(i->interface_address)) continue;

			ec = error_code();
			open_multicast_socket(ios, i->interface_address, loopback, ec);
#ifndef NDEBUG
//			fprintf(stderr, "broadcast socket [ if: %s group: %s ] %s\n"
//				, i->interface_address.to_string().c_str()
//				, print_address(multicast_endpoint.address()).c_str()
//				, ec.message().c_str());
#endif
			open_unicast_socket(ios, i->interface_address);
		}
	}

	void broadcast_socket::open_multicast_socket(io_service& ios
		, address const& addr, bool loopback, error_code& ec)
	{
		using namespace asio::ip::multicast;

		boost::shared_ptr<datagram_socket> s(new datagram_socket(ios));
		s->open(addr.is_v4() ? udp::v4() : udp::v6(), ec);
		if (ec) return;
		s->set_option(datagram_socket::reuse_address(true), ec);
		if (ec) return;
		s->bind(udp::endpoint(addr, m_multicast_endpoint.port()), ec);
		if (ec) return;
		s->set_option(join_group(m_multicast_endpoint.address()), ec);
		if (ec) return;
		s->set_option(hops(255), ec);
		if (ec) return;
		s->set_option(enable_loopback(loopback), ec);
		if (ec) return;
		m_sockets.push_back(socket_entry(s));
		socket_entry& se = m_sockets.back();
		s->async_receive_from(asio::buffer(se.buffer, sizeof(se.buffer))
			, se.remote, bind(&broadcast_socket::on_receive, this, &se, _1, _2));
	}

	void broadcast_socket::open_unicast_socket(io_service& ios, address const& addr)
	{
		using namespace asio::ip::multicast;
		error_code ec;
		boost::shared_ptr<datagram_socket> s(new datagram_socket(ios));
		s->open(addr.is_v4() ? udp::v4() : udp::v6(), ec);
		if (ec) return;
		s->bind(udp::endpoint(addr, 0), ec);
		if (ec) return;
		m_unicast_sockets.push_back(socket_entry(s));
		socket_entry& se = m_unicast_sockets.back();
		s->async_receive_from(asio::buffer(se.buffer, sizeof(se.buffer))
			, se.remote, bind(&broadcast_socket::on_receive, this, &se, _1, _2));
	}

	void broadcast_socket::send(char const* buffer, int size, error_code& ec)
	{
		for (std::list<socket_entry>::iterator i = m_unicast_sockets.begin()
			, end(m_unicast_sockets.end()); i != end; ++i)
		{
			if (!i->socket) continue;
			error_code e;
			i->socket->send_to(asio::buffer(buffer, size), m_multicast_endpoint, 0, e);
#ifndef NDEBUG
//			fprintf(stderr, " sending on unicast %s to: %s\n", print_address(i->socket->local_endpoint().address()).c_str()
//				, print_endpoint(m_multicast_endpoint).c_str());
#endif
			if (e)
			{
#ifndef NDEBUG
//				fprintf(stderr, " ERROR: %s\n", e.message().c_str());
#endif
				i->socket->close(e);
				i->socket.reset();
			}
		}

		for (std::list<socket_entry>::iterator i = m_sockets.begin()
			, end(m_sockets.end()); i != end; ++i)
		{
			if (!i->socket) continue;
			error_code e;
			i->socket->send_to(asio::buffer(buffer, size), m_multicast_endpoint, 0, e);
#ifndef NDEBUG
//			extern std::string print_address(address const& addr);
//			extern std::string print_endpoint(udp::endpoint const& ep);
//			fprintf(stderr, " sending on multicast %s to: %s\n", print_address(i->socket->local_endpoint().address()).c_str()
//				, print_endpoint(m_multicast_endpoint).c_str());
#endif
			if (e)
			{
#ifndef NDEBUG
//				fprintf(stderr, " ERROR: %s\n", e.message().c_str());
#endif
				i->socket->close(e);
				i->socket.reset();
			}
		}
	}

	void broadcast_socket::on_receive(socket_entry* s, error_code const& ec
		, std::size_t bytes_transferred)
	{
		if (ec || bytes_transferred == 0 || !m_on_receive) return;
		m_on_receive(s->remote, s->buffer, bytes_transferred);
		if (!s->socket) return;
		s->socket->async_receive_from(asio::buffer(s->buffer, sizeof(s->buffer))
			, s->remote, bind(&broadcast_socket::on_receive, this, s, _1, _2));
	}

	void broadcast_socket::close()
	{
		std::for_each(m_sockets.begin(), m_sockets.end(), bind(&socket_entry::close, _1));
		std::for_each(m_unicast_sockets.begin(), m_unicast_sockets.end(), bind(&socket_entry::close, _1));

		m_on_receive.clear();
	}
}

<commit_msg>IPv6 build fix<commit_after>/*

Copyright (c) 2007, Arvid Norberg
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in
      the documentation and/or other materials provided with the distribution.
    * Neither the name of the author nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/

#include <boost/version.hpp>

#include <boost/bind.hpp>

#include "libtorrent/socket.hpp"
#include "libtorrent/enum_net.hpp"
#include "libtorrent/broadcast_socket.hpp"
#include "libtorrent/assert.hpp"

#ifndef NDEBUG
//#include "libtorrent/socket_io.hpp"
#endif

#if BOOST_VERSION < 103500
#include <asio/ip/host_name.hpp>
#include <asio/ip/multicast.hpp>
#else
#include <boost/asio/ip/host_name.hpp>
#include <boost/asio/ip/multicast.hpp>
#endif

namespace libtorrent
{
	bool is_local(address const& a)
	{
#if TORRENT_USE_IPV6
		if (a.is_v6()) return a.to_v6().is_link_local();
#endif
		address_v4 a4 = a.to_v4();
		unsigned long ip = a4.to_ulong();
		return ((ip & 0xff000000) == 0x0a000000 // 10.x.x.x
			|| (ip & 0xfff00000) == 0xac100000 // 172.16.x.x
			|| (ip & 0xffff0000) == 0xc0a80000 // 192.168.x.x
			|| (ip & 0xffff0000) == 0xa9fe0000); // 169.254.x.x
	}

	bool is_loopback(address const& addr)
	{
#if TORRENT_USE_IPV6
		if (addr.is_v4())
			return addr.to_v4() == address_v4::loopback();
		else
			return addr.to_v6() == address_v6::loopback();
#else
		return addr.to_v4() == address_v4::loopback();
#endif
	}

	bool is_multicast(address const& addr)
	{
#if TORRENT_USE_IPV6
		if (addr.is_v4())
			return addr.to_v4().is_multicast();
		else
			return addr.to_v6().is_multicast();
#else
		return addr.to_v4().is_multicast();
#endif
	}

	bool is_any(address const& addr)
	{
#if TORRENT_USE_IPV6
		if (addr.is_v4())
			return addr.to_v4() == address_v4::any();
		else if (addr.to_v6().is_v4_mapped())
			return (addr.to_v6().to_v4() == address_v4::any());
		else
			return addr.to_v6() == address_v6::any();
#else
		return addr.to_v4() == address_v4::any();
#endif
	}

	bool supports_ipv6()
	{
#if TORRENT_USE_IPV6
		error_code ec;
		address::from_string("::1", ec);
		return !ec;
#else
		return false;
#endif
	}

	address guess_local_address(io_service& ios)
	{
		// make a best guess of the interface we're using and its IP
		error_code ec;
		std::vector<ip_interface> const& interfaces = enum_net_interfaces(ios, ec);
		address ret = address_v4::any();
		for (std::vector<ip_interface>::const_iterator i = interfaces.begin()
			, end(interfaces.end()); i != end; ++i)
		{
			address const& a = i->interface_address;
			if (is_loopback(a)
				|| is_multicast(a)
				|| is_any(a)) continue;

			// prefer a v4 address, but return a v6 if
			// there are no v4
			if (a.is_v4()) return a;

			if (ret != address_v4::any())
				ret = a;
		}
		return ret;
	}

	// count the length of the common bit prefix
	int common_bits(unsigned char const* b1
		, unsigned char const* b2, int n)
	{
		for (int i = 0; i < n; ++i, ++b1, ++b2)
		{
			unsigned char a = *b1 ^ *b2;
			if (a == 0) continue;
			int ret = i * 8 + 8;
			for (; a > 0; a >>= 1) --ret;
			return ret;
		}
		return n * 8;
	}

	// returns the number of bits in that differ from the right
	// between the addresses.
	int cidr_distance(address const& a1, address const& a2)
	{
#if TORRENT_USE_IPV6
		if (a1.is_v4() && a2.is_v4())
		{
#endif
			// both are v4
			address_v4::bytes_type b1 = a1.to_v4().to_bytes();
			address_v4::bytes_type b2 = a2.to_v4().to_bytes();
			return address_v4::bytes_type::static_size * 8
				- common_bits(b1.c_array(), b2.c_array(), b1.size());
#if TORRENT_USE_IPV6
		}

		address_v6::bytes_type b1;
		address_v6::bytes_type b2;
		if (a1.is_v4()) b1 = address_v6::v4_mapped(a1.to_v4()).to_bytes();
		else b1 = a1.to_v6().to_bytes();
		if (a2.is_v4()) b2 = address_v6::v4_mapped(a2.to_v4()).to_bytes();
		else b2 = a2.to_v6().to_bytes();
		return address_v6::bytes_type::static_size * 8
			- common_bits(b1.c_array(), b2.c_array(), b1.size());
#endif
	}

	broadcast_socket::broadcast_socket(io_service& ios
		, udp::endpoint const& multicast_endpoint
		, receive_handler_t const& handler
		, bool loopback)
		: m_multicast_endpoint(multicast_endpoint)
		, m_on_receive(handler)
	{
		TORRENT_ASSERT(is_multicast(m_multicast_endpoint.address()));

		using namespace asio::ip::multicast;

		error_code ec;
		std::vector<ip_interface> interfaces = enum_net_interfaces(ios, ec);

#if TORRENT_USE_IPV6
		if (multicast_endpoint.address().is_v6())
			open_multicast_socket(ios, address_v6::any(), loopback, ec);
		else
#endif
			open_multicast_socket(ios, address_v4::any(), loopback, ec);

		for (std::vector<ip_interface>::const_iterator i = interfaces.begin()
			, end(interfaces.end()); i != end; ++i)
		{
			// only multicast on compatible networks
			if (i->interface_address.is_v4() != multicast_endpoint.address().is_v4()) continue;
			// ignore any loopback interface
			if (!loopback && is_loopback(i->interface_address)) continue;

			ec = error_code();
			open_multicast_socket(ios, i->interface_address, loopback, ec);
#ifndef NDEBUG
//			fprintf(stderr, "broadcast socket [ if: %s group: %s ] %s\n"
//				, i->interface_address.to_string().c_str()
//				, print_address(multicast_endpoint.address()).c_str()
//				, ec.message().c_str());
#endif
			open_unicast_socket(ios, i->interface_address);
		}
	}

	void broadcast_socket::open_multicast_socket(io_service& ios
		, address const& addr, bool loopback, error_code& ec)
	{
		using namespace asio::ip::multicast;

		boost::shared_ptr<datagram_socket> s(new datagram_socket(ios));
		s->open(addr.is_v4() ? udp::v4() : udp::v6(), ec);
		if (ec) return;
		s->set_option(datagram_socket::reuse_address(true), ec);
		if (ec) return;
		s->bind(udp::endpoint(addr, m_multicast_endpoint.port()), ec);
		if (ec) return;
		s->set_option(join_group(m_multicast_endpoint.address()), ec);
		if (ec) return;
		s->set_option(hops(255), ec);
		if (ec) return;
		s->set_option(enable_loopback(loopback), ec);
		if (ec) return;
		m_sockets.push_back(socket_entry(s));
		socket_entry& se = m_sockets.back();
		s->async_receive_from(asio::buffer(se.buffer, sizeof(se.buffer))
			, se.remote, bind(&broadcast_socket::on_receive, this, &se, _1, _2));
	}

	void broadcast_socket::open_unicast_socket(io_service& ios, address const& addr)
	{
		using namespace asio::ip::multicast;
		error_code ec;
		boost::shared_ptr<datagram_socket> s(new datagram_socket(ios));
		s->open(addr.is_v4() ? udp::v4() : udp::v6(), ec);
		if (ec) return;
		s->bind(udp::endpoint(addr, 0), ec);
		if (ec) return;
		m_unicast_sockets.push_back(socket_entry(s));
		socket_entry& se = m_unicast_sockets.back();
		s->async_receive_from(asio::buffer(se.buffer, sizeof(se.buffer))
			, se.remote, bind(&broadcast_socket::on_receive, this, &se, _1, _2));
	}

	void broadcast_socket::send(char const* buffer, int size, error_code& ec)
	{
		for (std::list<socket_entry>::iterator i = m_unicast_sockets.begin()
			, end(m_unicast_sockets.end()); i != end; ++i)
		{
			if (!i->socket) continue;
			error_code e;
			i->socket->send_to(asio::buffer(buffer, size), m_multicast_endpoint, 0, e);
#ifndef NDEBUG
//			fprintf(stderr, " sending on unicast %s to: %s\n", print_address(i->socket->local_endpoint().address()).c_str()
//				, print_endpoint(m_multicast_endpoint).c_str());
#endif
			if (e)
			{
#ifndef NDEBUG
//				fprintf(stderr, " ERROR: %s\n", e.message().c_str());
#endif
				i->socket->close(e);
				i->socket.reset();
			}
		}

		for (std::list<socket_entry>::iterator i = m_sockets.begin()
			, end(m_sockets.end()); i != end; ++i)
		{
			if (!i->socket) continue;
			error_code e;
			i->socket->send_to(asio::buffer(buffer, size), m_multicast_endpoint, 0, e);
#ifndef NDEBUG
//			extern std::string print_address(address const& addr);
//			extern std::string print_endpoint(udp::endpoint const& ep);
//			fprintf(stderr, " sending on multicast %s to: %s\n", print_address(i->socket->local_endpoint().address()).c_str()
//				, print_endpoint(m_multicast_endpoint).c_str());
#endif
			if (e)
			{
#ifndef NDEBUG
//				fprintf(stderr, " ERROR: %s\n", e.message().c_str());
#endif
				i->socket->close(e);
				i->socket.reset();
			}
		}
	}

	void broadcast_socket::on_receive(socket_entry* s, error_code const& ec
		, std::size_t bytes_transferred)
	{
		if (ec || bytes_transferred == 0 || !m_on_receive) return;
		m_on_receive(s->remote, s->buffer, bytes_transferred);
		if (!s->socket) return;
		s->socket->async_receive_from(asio::buffer(s->buffer, sizeof(s->buffer))
			, s->remote, bind(&broadcast_socket::on_receive, this, s, _1, _2));
	}

	void broadcast_socket::close()
	{
		std::for_each(m_sockets.begin(), m_sockets.end(), bind(&socket_entry::close, _1));
		std::for_each(m_unicast_sockets.begin(), m_unicast_sockets.end(), bind(&socket_entry::close, _1));

		m_on_receive.clear();
	}
}

<|endoftext|>


<commit_before>//
//  AmstradCPC.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 30/07/2017.
//  Copyright © 2017 Thomas Harte. All rights reserved.
//

#include "AmstradCPC.hpp"

#include "../../Processors/Z80/Z80.hpp"
#include "../../Components/AY38910/AY38910.hpp"
#include "../../Components/6845/CRTC6845.hpp"

using namespace AmstradCPC;

struct CRTCBusHandler {
	public:
		CRTCBusHandler() : cycles_(0), was_enabled_(false), was_sync_(false) {}

		inline void perform_bus_cycle(const Motorola::CRTC::BusState &state) {
			cycles_++;
			bool is_sync = state.hsync || state.vsync;
			if(state.display_enable != was_enabled_ || is_sync != was_sync_) {
				if(was_sync_) {
					crt_->output_sync((unsigned int)(cycles_ * 2) * 8);
				} else {
					uint8_t *colour_pointer = (uint8_t *)crt_->allocate_write_area(1);
					if(colour_pointer) *colour_pointer = was_enabled_ ? 0xff : 0x00;
					crt_->output_level((unsigned int)(cycles_ * 2) * 8);
				}

				cycles_ = 0;
				was_sync_ = is_sync;
				was_enabled_ = state.display_enable;
			}
		}

		void setup_output(float aspect_ratio) {
			crt_.reset(new Outputs::CRT::CRT(1024, 8, Outputs::CRT::DisplayType::PAL50, 1));
			crt_->set_rgb_sampling_function(
				"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
				"{"
					"return vec3(float(texture(texID, coordinate).r) / 255.0);"
				"}");
		}

		void close_output() {
			crt_.reset();
		}

		std::shared_ptr<Outputs::CRT::CRT> get_crt() {
			return crt_;
		}

	private:
		int cycles_;
		bool was_enabled_, was_sync_;
		std::shared_ptr<Outputs::CRT::CRT> crt_;
};

class ConcreteMachine:
	public CPU::Z80::Processor<ConcreteMachine>,
	public Machine {
	public:
		ConcreteMachine() :
			crtc_counter_(HalfCycles(4)),	// This starts the CRTC exactly out of phase with the memory accesses
			crtc_(crtc_bus_handler_) {
			// primary clock is 4Mhz
			set_clock_rate(4000000);
		}

		inline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
			// Amstrad CPC timing scheme: assert WAIT for three out of four cycles
			clock_offset_ = (clock_offset_ + cycle.length) & HalfCycles(7);
			set_wait_line(clock_offset_ >= HalfCycles(2));

			// Update the CRTC once every eight half cycles; aiming for half-cycle 4 as
			// per the initial seed to the crtc_counter_, but any time in the final four
			// will do as it's safe to conclude that nobody else has touched video RAM
			// during that whole window
			crtc_counter_ += cycle.length;
			int crtc_cycles = crtc_counter_.divide(HalfCycles(8)).as_int();
			if(crtc_cycles) crtc_.run_for(Cycles(1));

			// Stop now if no action is strictly required.
			if(!cycle.is_terminal()) return HalfCycles(0);

			uint16_t address = cycle.address ? *cycle.address : 0x0000;
			switch(cycle.operation) {
				case CPU::Z80::PartialMachineCycle::ReadOpcode:
				case CPU::Z80::PartialMachineCycle::Read:
					*cycle.value = read_pointers_[address >> 14][address & 16383];
				break;

				case CPU::Z80::PartialMachineCycle::Write:
					write_pointers_[address >> 14][address & 16383] = *cycle.value;
				break;

				case CPU::Z80::PartialMachineCycle::Output:
					// Check for a gate array access.
					if((address & 0xc000) == 0x4000) {
						switch(*cycle.value >> 6) {
							case 0: printf("Select pen %02x\n", *cycle.value & 0x1f);		break;
							case 1: printf("Select colour %02x\n", *cycle.value & 0x1f);	break;
							case 2:
								printf("Set mode %d, other flags %02x\n", *cycle.value & 3, (*cycle.value >> 2)&7);
								read_pointers_[0] = (*cycle.value & 4) ? &ram_[0] : os_.data();
								read_pointers_[3] = (*cycle.value & 8) ? &ram_[49152] : basic_.data();
							break;
							case 3: printf("RAM paging?\n"); break;
						}
					}

					// Check for a CRTC access
					if(!(address & 0x4000)) {
						switch((address >> 8) & 3) {
							case 0:	crtc_.select_register(*cycle.value);	break;
							case 1:	crtc_.set_register(*cycle.value);		break;
							case 2: case 3:	printf("Illegal CRTC write?\n");	break;
						}
					}

					// Check for a PIO access
					if(!(address & 0x800)) {
						switch((address >> 8) & 3) {
							case 0:	printf("PSG data: %d\n", *cycle.value);	break;
							case 1:	printf("Vsync, etc: %02x\n", *cycle.value);	break;
							case 2:	printf("Key row, etc: %02x\n", *cycle.value);	break;
							case 3:	printf("PIO control: %02x\n", *cycle.value);	break;
						}
					}
				break;
				case CPU::Z80::PartialMachineCycle::Input:
					// Check for a CRTC access
					if(!(address & 0x4000)) {
						switch((address >> 8) & 3) {
							case 0:	case 1: printf("Illegal CRTC read?\n");	break;
							case 2: *cycle.value = crtc_.get_status();		break;
							case 3:	*cycle.value = crtc_.get_register();	break;
						}
					}

					// Check for a PIO access
					if(!(address & 0x800)) {
						switch((address >> 8) & 3) {
							case 0:	printf("[In] PSG data\n");		break;
							case 1:	printf("[In] Vsync, etc\n");	break;
							case 2:	printf("[In] Key row, etc\n");	break;
							case 3:	printf("[In] PIO control\n");	break;
						}
					}

					*cycle.value = 0xff;
				break;

				case CPU::Z80::PartialMachineCycle::Interrupt:
					*cycle.value = 0xff;
				break;

				default: break;
			}

			return HalfCycles(0);
		}

		void flush() {}

		void setup_output(float aspect_ratio) {
			crtc_bus_handler_.setup_output(aspect_ratio);
		}

		void close_output() {
			crtc_bus_handler_.close_output();
		}

		std::shared_ptr<Outputs::CRT::CRT> get_crt() {
			return crtc_bus_handler_.get_crt();
		}

		std::shared_ptr<Outputs::Speaker> get_speaker() {
			return nullptr;
		}

		void run_for(const Cycles cycles) {
			CPU::Z80::Processor<ConcreteMachine>::run_for(cycles);
		}

		void configure_as_target(const StaticAnalyser::Target &target) {
			// Establish reset memory map as per machine model (or, for now, as a hard-wired 464)
			read_pointers_[0] = os_.data();
			read_pointers_[1] = &ram_[16384];
			read_pointers_[2] = &ram_[32768];
			read_pointers_[3] = basic_.data();

			write_pointers_[0] = &ram_[0];
			write_pointers_[1] = &ram_[16384];
			write_pointers_[2] = &ram_[32768];
			write_pointers_[3] = &ram_[49152];
		}

		void set_rom(ROMType type, std::vector<uint8_t> data) {
			// Keep only the two ROMs that are currently of interest.
			switch(type) {
				case ROMType::OS464:		os_ = data;		break;
				case ROMType::BASIC464:		basic_ = data;	break;
				default: break;
			}
		}

	private:
		CRTCBusHandler crtc_bus_handler_;
		Motorola::CRTC::CRTC6845<CRTCBusHandler> crtc_;

		HalfCycles clock_offset_;
		HalfCycles crtc_counter_;

		uint8_t ram_[65536];
		std::vector<uint8_t> os_, basic_;

		uint8_t *read_pointers_[4];
		uint8_t *write_pointers_[4];
};

Machine *Machine::AmstradCPC() {
	return new ConcreteMachine;
}
<commit_msg>Ensured an actual pixel stream is supplied for pixel regions. Though it's just a long stream of white pixels for now. So visual output is unchanged.<commit_after>//
//  AmstradCPC.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 30/07/2017.
//  Copyright © 2017 Thomas Harte. All rights reserved.
//

#include "AmstradCPC.hpp"

#include "../../Processors/Z80/Z80.hpp"
#include "../../Components/AY38910/AY38910.hpp"
#include "../../Components/6845/CRTC6845.hpp"

using namespace AmstradCPC;

struct CRTCBusHandler {
	public:
		CRTCBusHandler() : cycles_(0), was_enabled_(false), was_sync_(false), pixel_data_(nullptr), pixel_pointer_(nullptr) {}

		inline void perform_bus_cycle(const Motorola::CRTC::BusState &state) {
			cycles_++;

			bool is_sync = state.hsync || state.vsync;
			// collect some more pixels if output is ongoing
			if(!is_sync && state.display_enable) {
				if(!pixel_data_) {
					pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320);
				}
				if(pixel_pointer_) {
					*pixel_pointer_++ = 0xff;

					// flush the current buffer if full
					if(pixel_pointer_ == pixel_data_ + 320) {
						crt_->output_data(320, 16);
						pixel_pointer_ = pixel_data_ = nullptr;
					}
				}
			}

			// if a transition between sync/border/pixels just occurred, announce it
			if(state.display_enable != was_enabled_ || is_sync != was_sync_) {
				if(was_sync_) {
					crt_->output_sync((unsigned int)(cycles_ * 16));
				} else {
					if(was_enabled_) {
						crt_->output_data((unsigned int)(cycles_ * 16), 16);
						pixel_pointer_ = pixel_data_ = nullptr;
					} else {
						uint8_t *colour_pointer = (uint8_t *)crt_->allocate_write_area(1);
						if(colour_pointer) *colour_pointer = 0x00;
						crt_->output_level((unsigned int)(cycles_ * 16));
					}
				}

				cycles_ = 0;
				was_sync_ = is_sync;
				was_enabled_ = state.display_enable;
			}
		}

		void setup_output(float aspect_ratio) {
			crt_.reset(new Outputs::CRT::CRT(1024, 16, Outputs::CRT::DisplayType::PAL50, 1));
			crt_->set_rgb_sampling_function(
				"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
				"{"
					"return vec3(float(texture(texID, coordinate).r) / 255.0);"
				"}");
		}

		void close_output() {
			crt_.reset();
		}

		std::shared_ptr<Outputs::CRT::CRT> get_crt() {
			return crt_;
		}

	private:
		int cycles_;
		bool was_enabled_, was_sync_;
		std::shared_ptr<Outputs::CRT::CRT> crt_;
		uint8_t *pixel_data_, *pixel_pointer_;
};

class ConcreteMachine:
	public CPU::Z80::Processor<ConcreteMachine>,
	public Machine {
	public:
		ConcreteMachine() :
			crtc_counter_(HalfCycles(4)),	// This starts the CRTC exactly out of phase with the memory accesses
			crtc_(crtc_bus_handler_) {
			// primary clock is 4Mhz
			set_clock_rate(4000000);
		}

		inline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
			// Amstrad CPC timing scheme: assert WAIT for three out of four cycles
			clock_offset_ = (clock_offset_ + cycle.length) & HalfCycles(7);
			set_wait_line(clock_offset_ >= HalfCycles(2));

			// Update the CRTC once every eight half cycles; aiming for half-cycle 4 as
			// per the initial seed to the crtc_counter_, but any time in the final four
			// will do as it's safe to conclude that nobody else has touched video RAM
			// during that whole window
			crtc_counter_ += cycle.length;
			int crtc_cycles = crtc_counter_.divide(HalfCycles(8)).as_int();
			if(crtc_cycles) crtc_.run_for(Cycles(1));

			// Stop now if no action is strictly required.
			if(!cycle.is_terminal()) return HalfCycles(0);

			uint16_t address = cycle.address ? *cycle.address : 0x0000;
			switch(cycle.operation) {
				case CPU::Z80::PartialMachineCycle::ReadOpcode:
				case CPU::Z80::PartialMachineCycle::Read:
					*cycle.value = read_pointers_[address >> 14][address & 16383];
				break;

				case CPU::Z80::PartialMachineCycle::Write:
					write_pointers_[address >> 14][address & 16383] = *cycle.value;
				break;

				case CPU::Z80::PartialMachineCycle::Output:
					// Check for a gate array access.
					if((address & 0xc000) == 0x4000) {
						switch(*cycle.value >> 6) {
							case 0: printf("Select pen %02x\n", *cycle.value & 0x1f);		break;
							case 1: printf("Select colour %02x\n", *cycle.value & 0x1f);	break;
							case 2:
								printf("Set mode %d, other flags %02x\n", *cycle.value & 3, (*cycle.value >> 2)&7);
								read_pointers_[0] = (*cycle.value & 4) ? &ram_[0] : os_.data();
								read_pointers_[3] = (*cycle.value & 8) ? &ram_[49152] : basic_.data();
							break;
							case 3: printf("RAM paging?\n"); break;
						}
					}

					// Check for a CRTC access
					if(!(address & 0x4000)) {
						switch((address >> 8) & 3) {
							case 0:	crtc_.select_register(*cycle.value);	break;
							case 1:	crtc_.set_register(*cycle.value);		break;
							case 2: case 3:	printf("Illegal CRTC write?\n");	break;
						}
					}

					// Check for a PIO access
					if(!(address & 0x800)) {
						switch((address >> 8) & 3) {
							case 0:	printf("PSG data: %d\n", *cycle.value);	break;
							case 1:	printf("Vsync, etc: %02x\n", *cycle.value);	break;
							case 2:	printf("Key row, etc: %02x\n", *cycle.value);	break;
							case 3:	printf("PIO control: %02x\n", *cycle.value);	break;
						}
					}
				break;
				case CPU::Z80::PartialMachineCycle::Input:
					// Check for a CRTC access
					if(!(address & 0x4000)) {
						switch((address >> 8) & 3) {
							case 0:	case 1: printf("Illegal CRTC read?\n");	break;
							case 2: *cycle.value = crtc_.get_status();		break;
							case 3:	*cycle.value = crtc_.get_register();	break;
						}
					}

					// Check for a PIO access
					if(!(address & 0x800)) {
						switch((address >> 8) & 3) {
							case 0:	printf("[In] PSG data\n");		break;
							case 1:	printf("[In] Vsync, etc\n");	break;
							case 2:	printf("[In] Key row, etc\n");	break;
							case 3:	printf("[In] PIO control\n");	break;
						}
					}

					*cycle.value = 0xff;
				break;

				case CPU::Z80::PartialMachineCycle::Interrupt:
					*cycle.value = 0xff;
				break;

				default: break;
			}

			return HalfCycles(0);
		}

		void flush() {}

		void setup_output(float aspect_ratio) {
			crtc_bus_handler_.setup_output(aspect_ratio);
		}

		void close_output() {
			crtc_bus_handler_.close_output();
		}

		std::shared_ptr<Outputs::CRT::CRT> get_crt() {
			return crtc_bus_handler_.get_crt();
		}

		std::shared_ptr<Outputs::Speaker> get_speaker() {
			return nullptr;
		}

		void run_for(const Cycles cycles) {
			CPU::Z80::Processor<ConcreteMachine>::run_for(cycles);
		}

		void configure_as_target(const StaticAnalyser::Target &target) {
			// Establish reset memory map as per machine model (or, for now, as a hard-wired 464)
			read_pointers_[0] = os_.data();
			read_pointers_[1] = &ram_[16384];
			read_pointers_[2] = &ram_[32768];
			read_pointers_[3] = basic_.data();

			write_pointers_[0] = &ram_[0];
			write_pointers_[1] = &ram_[16384];
			write_pointers_[2] = &ram_[32768];
			write_pointers_[3] = &ram_[49152];
		}

		void set_rom(ROMType type, std::vector<uint8_t> data) {
			// Keep only the two ROMs that are currently of interest.
			switch(type) {
				case ROMType::OS464:		os_ = data;		break;
				case ROMType::BASIC464:		basic_ = data;	break;
				default: break;
			}
		}

	private:
		CRTCBusHandler crtc_bus_handler_;
		Motorola::CRTC::CRTC6845<CRTCBusHandler> crtc_;

		HalfCycles clock_offset_;
		HalfCycles crtc_counter_;

		uint8_t ram_[65536];
		std::vector<uint8_t> os_, basic_;

		uint8_t *read_pointers_[4];
		uint8_t *write_pointers_[4];
};

Machine *Machine::AmstradCPC() {
	return new ConcreteMachine;
}
<|endoftext|>


<commit_before>// Copyright 2010-2014 RethinkDB, all rights reserved.
#include "btree/operations.hpp"

#include "btree/internal_node.hpp"
#include "btree/leaf_node.hpp"
#include "btree/node.hpp"
#include "btree/slice.hpp"
#include "buffer_cache/alt/alt.hpp"
#include "concurrency/promise.hpp"
#include "rdb_protocol/profile.hpp"

// TODO: consider B#/B* trees to improve space efficiency

// TODO: perhaps allow memory reclamation due to oversplitting? We can
// be smart and only use a limited amount of ram for incomplete nodes
// (doing this efficiently very tricky for high insert
// workloads). Also, if the serializer is log-structured, we can write
// only a small part of each node.

// TODO: change rwi_write to rwi_intent followed by rwi_upgrade where
// relevant.

/* Passing in a pass_back_superblock parameter will cause this function to
 * return the superblock after it's no longer needed (rather than releasing
 * it). Notice the superblock is not guaranteed to be returned until the
 * keyvalue_location_t that's passed in (keyvalue_location_out) is destroyed.
 * This is because it may need to use the superblock for some of its methods.
 * */
// RSI: It seems like really we should pass the superblock_t via rvalue reference.
// Is that possible?  (promise_t makes it hard.)
template <class Value>
void find_keyvalue_location_for_write(
        superblock_t *superblock, const btree_key_t *key,
        keyvalue_location_t<Value> *keyvalue_location_out,
        btree_stats_t *stats,
        profile::trace_t *trace,
        promise_t<superblock_t *> *pass_back_superblock = NULL) {
    value_sizer_t<Value> sizer(superblock->expose_buf().cache()->max_block_size());

    keyvalue_location_out->superblock = superblock;
    keyvalue_location_out->pass_back_superblock = pass_back_superblock;

    ensure_stat_block(superblock);
    keyvalue_location_out->stat_block = keyvalue_location_out->superblock->get_stat_block_id();

    keyvalue_location_out->stats = stats;

    // RSI: Make sure we do the logic smart here -- don't needlessly hold both
    // buffers.  (This finds the keyvalue for _write_ so that probably won't really
    // happen.)
    buf_lock_t last_buf;
    buf_lock_t buf;
    {
        profile::starter_t starter("Acquiring block for write.\n", trace);
        get_root(&sizer, superblock, &buf);
    }

    // Walk down the tree to the leaf.
    for (;;) {
        {
            buf_read_t read(&buf);
            if (!node::is_internal(static_cast<const node_t *>(read.get_data_read()))) {
                break;
            }
        }
        // Check if the node is overfull and proactively split it if it is (since this is an internal node).
        {
            profile::starter_t starter("Perhaps split node.", trace);
            // RSI: Ugh, we're passing the superblock, buf, and last_buf.  Really?
            check_and_handle_split(&sizer, &buf, &last_buf, superblock, key, static_cast<Value *>(NULL));
        }

        // Check if the node is underfull, and merge/level if it is.
        {
            profile::starter_t starter("Perhaps merge nodes.", trace);
            // RSI: Ugh, we're passing the superblock, buf, last_buf here too.
            check_and_handle_underfull(&sizer, &buf, &last_buf, superblock, key);
        }

        // Release the superblock, if we've gone past the root (and haven't
        // already released it). If we're still at the root or at one of
        // its direct children, we might still want to replace the root, so
        // we can't release the superblock yet.
        if (!last_buf.empty() && keyvalue_location_out->superblock) {
            if (pass_back_superblock != NULL) {
                pass_back_superblock->pulse(superblock);
                keyvalue_location_out->superblock = NULL;
            } else {
                keyvalue_location_out->superblock->release();
                keyvalue_location_out->superblock = NULL;
            }
        }

        // Release the old previous node (unless we're at the root), and set
        // the next previous node (which is the current node).
        last_buf.reset_buf_lock();

        // Look up and acquire the next node.
        block_id_t node_id;
        {
            buf_read_t read(&buf);
            auto node = static_cast<const internal_node_t *>(read.get_data_read());
            node_id = internal_node::lookup(node, key);
        }
        rassert(node_id != NULL_BLOCK_ID && node_id != SUPERBLOCK_ID);

        {
            profile::starter_t starter("Acquiring block for write.\n", trace);
            buf_lock_t tmp(&buf, node_id, alt_access_t::write);
            last_buf = std::move(buf);
            buf = std::move(tmp);
        }
    }

    {
        scoped_malloc_t<Value> tmp(sizer.max_possible_size());

        // We've gone down the tree and gotten to a leaf. Now look up the key.
        buf_read_t read(&buf);
        auto node = static_cast<const leaf_node_t *>(read.get_data_read());
        bool key_found = leaf::lookup(&sizer, node, key, tmp.get());

        if (key_found) {
            keyvalue_location_out->there_originally_was_value = true;
            keyvalue_location_out->value = std::move(tmp);
        }
    }

    // RSI: keyvalue_location_out really saves last_buf?  Why??
    keyvalue_location_out->last_buf.swap(last_buf);
    keyvalue_location_out->buf.swap(buf);
}

template <class Value>
void find_keyvalue_location_for_read(
        superblock_t *superblock, const btree_key_t *key,
        keyvalue_location_t<Value> *keyvalue_location_out,
        btree_stats_t *stats, profile::trace_t *trace) {
    stats->pm_keys_read.record();
    value_sizer_t<Value> sizer(superblock->expose_buf().cache()->max_block_size());

    const block_id_t root_id = superblock->get_root_block_id();
    rassert(root_id != SUPERBLOCK_ID);

    if (root_id == NULL_BLOCK_ID) {
        // There is no root, so the tree is empty.
        superblock->release();
        return;
    }

    buf_lock_t buf;
    {
        profile::starter_t starter("Acquire a block for read.", trace);
        buf_lock_t tmp(superblock->expose_buf(), root_id, alt_access_t::read);
        superblock->release();
        buf = std::move(tmp);
    }

#ifndef NDEBUG
    {
        buf_read_t read(&buf);
        node::validate(&sizer, static_cast<const node_t *>(read.get_data_read()));
    }
#endif  // NDEBUG

    for (;;) {
        {
            buf_read_t read(&buf);
            if (!node::is_internal(static_cast<const node_t *>(read.get_data_read()))) {
                break;
            }
        }
        block_id_t node_id;
        {
            buf_read_t read(&buf);
            const internal_node_t *node
                = static_cast<const internal_node_t *>(read.get_data_read());
            node_id = internal_node::lookup(node, key);
        }
        rassert(node_id != NULL_BLOCK_ID && node_id != SUPERBLOCK_ID);

        {
            profile::starter_t starter("Acquire a block for read.", trace);
            buf_lock_t tmp(&buf, node_id, alt_access_t::read);
            buf.reset_buf_lock();
            buf = std::move(tmp);
        }

#ifndef NDEBUG
        {
            buf_read_t read(&buf);
            node::validate(&sizer, static_cast<const node_t *>(read.get_data_read()));
        }
#endif  // NDEBUG
    }

    // Got down to the leaf, now probe it.
    scoped_malloc_t<Value> value(sizer.max_possible_size());
    bool value_found;
    {
        buf_read_t read(&buf);
        const leaf_node_t *leaf
            = static_cast<const leaf_node_t *>(read.get_data_read());
        value_found = leaf::lookup(&sizer, leaf, key, value.get());
    }
    if (value_found) {
        keyvalue_location_out->buf = std::move(buf);
        keyvalue_location_out->there_originally_was_value = true;
        keyvalue_location_out->value = std::move(value);
    }
}

enum class expired_t { NO, YES };

template <class Value>
void apply_keyvalue_change(keyvalue_location_t<Value> *kv_loc,
        const btree_key_t *key, repli_timestamp_t tstamp, expired_t expired,
        key_modification_callback_t<Value> *km_callback) {

    value_sizer_t<Value> sizer(kv_loc->buf.cache()->get_block_size());

    key_modification_proof_t km_proof
        = km_callback->value_modification(kv_loc, key);

    /* how much this keyvalue change affects the total population of the btree
     * (should be -1, 0 or 1) */
    int population_change;

    if (kv_loc->value.has()) {
        // We have a value to insert.

        // Split the node if necessary, to make sure that we have room
        // for the value.  Not necessary when deleting, because the
        // node won't grow.

        check_and_handle_split(&sizer, &kv_loc->buf, &kv_loc->last_buf,
                               kv_loc->superblock, key, kv_loc->value.get());

        {
#ifndef NDEBUG
            buf_read_t read(&kv_loc->buf);
            auto leaf_node = static_cast<const leaf_node_t *>(read.get_data_read());
            rassert(!leaf::is_full(&sizer, leaf_node, key, kv_loc->value.get()));
#endif
        }

        if (kv_loc->there_originally_was_value) {
            population_change = 0;
        } else {
            population_change = 1;
        }

        {
            buf_write_t write(&kv_loc->buf);
            auto leaf_node = static_cast<leaf_node_t *>(write.get_data_write());
            leaf::insert(&sizer,
                         leaf_node,
                         key,
                         kv_loc->value.get(),
                         tstamp,
                         km_proof);
        }

        kv_loc->stats->pm_keys_set.record();
    } else {
        // Delete the value if it's there.
        if (kv_loc->there_originally_was_value) {
            if (expired == expired_t::NO) {
                rassert(tstamp != repli_timestamp_t::invalid, "Deletes need a valid timestamp now.");
                {
                    buf_write_t write(&kv_loc->buf);
                    auto leaf_node = static_cast<leaf_node_t *>(write.get_data_write());
                    leaf::remove(&sizer,
                                 leaf_node,
                                 key,
                                 tstamp,
                                 km_proof);
                }
                population_change = -1;
                kv_loc->stats->pm_keys_set.record();
            } else {
                // TODO: Oh god oh god get rid of "expired".
                // Expirations do an erase, not a delete.
                {
                    buf_write_t write(&kv_loc->buf);
                    auto leaf_node = static_cast<leaf_node_t *>(write.get_data_write());
                    leaf::erase_presence(&sizer,
                                         leaf_node,
                                         key,
                                         km_proof);
                }
                population_change = 0;
                kv_loc->stats->pm_keys_expired.record();
            }
        } else {
            population_change = 0;
        }
    }

    // Check to see if the leaf is underfull (following a change in
    // size or a deletion, and merge/level if it is.
    check_and_handle_underfull(&sizer, &kv_loc->buf, &kv_loc->last_buf,
                               kv_loc->superblock, key);

    //Modify the stats block
    // RSI: Should we _actually_ pass kv_loc->buf as the parent?
    // RSI: This was opened with some buffer_cache_order_mode_ignore flag.
    // RSI: See parallel_traversal.cc for another use of the stat block.
    buf_lock_t stat_block(&kv_loc->buf, kv_loc->stat_block, alt_access_t::write);
    buf_write_t stat_block_write(&stat_block);
    auto stat_block_buf
        = static_cast<btree_statblock_t *>(stat_block_write.get_data_write());
    stat_block_buf->population += population_change;
}
<commit_msg>Removed yet another RSI comment -- only 167 to go!<commit_after>// Copyright 2010-2014 RethinkDB, all rights reserved.
#include "btree/operations.hpp"

#include "btree/internal_node.hpp"
#include "btree/leaf_node.hpp"
#include "btree/node.hpp"
#include "btree/slice.hpp"
#include "buffer_cache/alt/alt.hpp"
#include "concurrency/promise.hpp"
#include "rdb_protocol/profile.hpp"

// TODO: consider B#/B* trees to improve space efficiency

// TODO: perhaps allow memory reclamation due to oversplitting? We can
// be smart and only use a limited amount of ram for incomplete nodes
// (doing this efficiently very tricky for high insert
// workloads). Also, if the serializer is log-structured, we can write
// only a small part of each node.

// TODO: change rwi_write to rwi_intent followed by rwi_upgrade where
// relevant.

/* Passing in a pass_back_superblock parameter will cause this function to
 * return the superblock after it's no longer needed (rather than releasing
 * it). Notice the superblock is not guaranteed to be returned until the
 * keyvalue_location_t that's passed in (keyvalue_location_out) is destroyed.
 * This is because it may need to use the superblock for some of its methods.
 * */
// RSI: It seems like really we should pass the superblock_t via rvalue reference.
// Is that possible?  (promise_t makes it hard.)
template <class Value>
void find_keyvalue_location_for_write(
        superblock_t *superblock, const btree_key_t *key,
        keyvalue_location_t<Value> *keyvalue_location_out,
        btree_stats_t *stats,
        profile::trace_t *trace,
        promise_t<superblock_t *> *pass_back_superblock = NULL) {
    value_sizer_t<Value> sizer(superblock->expose_buf().cache()->max_block_size());

    keyvalue_location_out->superblock = superblock;
    keyvalue_location_out->pass_back_superblock = pass_back_superblock;

    ensure_stat_block(superblock);
    keyvalue_location_out->stat_block = keyvalue_location_out->superblock->get_stat_block_id();

    keyvalue_location_out->stats = stats;

    // RSI: Make sure we do the logic smart here -- don't needlessly hold both
    // buffers.  (This finds the keyvalue for _write_ so that probably won't really
    // happen.)
    buf_lock_t last_buf;
    buf_lock_t buf;
    {
        profile::starter_t starter("Acquiring block for write.\n", trace);
        get_root(&sizer, superblock, &buf);
    }

    // Walk down the tree to the leaf.
    for (;;) {
        {
            buf_read_t read(&buf);
            if (!node::is_internal(static_cast<const node_t *>(read.get_data_read()))) {
                break;
            }
        }
        // Check if the node is overfull and proactively split it if it is (since this is an internal node).
        {
            profile::starter_t starter("Perhaps split node.", trace);
            // RSI: Ugh, we're passing the superblock, buf, and last_buf.  Really?
            check_and_handle_split(&sizer, &buf, &last_buf, superblock, key, static_cast<Value *>(NULL));
        }

        // Check if the node is underfull, and merge/level if it is.
        {
            profile::starter_t starter("Perhaps merge nodes.", trace);
            // RSI: Ugh, we're passing the superblock, buf, last_buf here too.
            check_and_handle_underfull(&sizer, &buf, &last_buf, superblock, key);
        }

        // Release the superblock, if we've gone past the root (and haven't
        // already released it). If we're still at the root or at one of
        // its direct children, we might still want to replace the root, so
        // we can't release the superblock yet.
        if (!last_buf.empty() && keyvalue_location_out->superblock) {
            if (pass_back_superblock != NULL) {
                pass_back_superblock->pulse(superblock);
                keyvalue_location_out->superblock = NULL;
            } else {
                keyvalue_location_out->superblock->release();
                keyvalue_location_out->superblock = NULL;
            }
        }

        // Release the old previous node (unless we're at the root), and set
        // the next previous node (which is the current node).
        last_buf.reset_buf_lock();

        // Look up and acquire the next node.
        block_id_t node_id;
        {
            buf_read_t read(&buf);
            auto node = static_cast<const internal_node_t *>(read.get_data_read());
            node_id = internal_node::lookup(node, key);
        }
        rassert(node_id != NULL_BLOCK_ID && node_id != SUPERBLOCK_ID);

        {
            profile::starter_t starter("Acquiring block for write.\n", trace);
            buf_lock_t tmp(&buf, node_id, alt_access_t::write);
            last_buf = std::move(buf);
            buf = std::move(tmp);
        }
    }

    {
        scoped_malloc_t<Value> tmp(sizer.max_possible_size());

        // We've gone down the tree and gotten to a leaf. Now look up the key.
        buf_read_t read(&buf);
        auto node = static_cast<const leaf_node_t *>(read.get_data_read());
        bool key_found = leaf::lookup(&sizer, node, key, tmp.get());

        if (key_found) {
            keyvalue_location_out->there_originally_was_value = true;
            keyvalue_location_out->value = std::move(tmp);
        }
    }

    // RSI: keyvalue_location_out really saves last_buf?  Why??
    keyvalue_location_out->last_buf.swap(last_buf);
    keyvalue_location_out->buf.swap(buf);
}

template <class Value>
void find_keyvalue_location_for_read(
        superblock_t *superblock, const btree_key_t *key,
        keyvalue_location_t<Value> *keyvalue_location_out,
        btree_stats_t *stats, profile::trace_t *trace) {
    stats->pm_keys_read.record();
    value_sizer_t<Value> sizer(superblock->expose_buf().cache()->max_block_size());

    const block_id_t root_id = superblock->get_root_block_id();
    rassert(root_id != SUPERBLOCK_ID);

    if (root_id == NULL_BLOCK_ID) {
        // There is no root, so the tree is empty.
        superblock->release();
        return;
    }

    buf_lock_t buf;
    {
        profile::starter_t starter("Acquire a block for read.", trace);
        buf_lock_t tmp(superblock->expose_buf(), root_id, alt_access_t::read);
        superblock->release();
        buf = std::move(tmp);
    }

#ifndef NDEBUG
    {
        buf_read_t read(&buf);
        node::validate(&sizer, static_cast<const node_t *>(read.get_data_read()));
    }
#endif  // NDEBUG

    for (;;) {
        {
            buf_read_t read(&buf);
            if (!node::is_internal(static_cast<const node_t *>(read.get_data_read()))) {
                break;
            }
        }
        block_id_t node_id;
        {
            buf_read_t read(&buf);
            const internal_node_t *node
                = static_cast<const internal_node_t *>(read.get_data_read());
            node_id = internal_node::lookup(node, key);
        }
        rassert(node_id != NULL_BLOCK_ID && node_id != SUPERBLOCK_ID);

        {
            profile::starter_t starter("Acquire a block for read.", trace);
            buf_lock_t tmp(&buf, node_id, alt_access_t::read);
            buf.reset_buf_lock();
            buf = std::move(tmp);
        }

#ifndef NDEBUG
        {
            buf_read_t read(&buf);
            node::validate(&sizer, static_cast<const node_t *>(read.get_data_read()));
        }
#endif  // NDEBUG
    }

    // Got down to the leaf, now probe it.
    scoped_malloc_t<Value> value(sizer.max_possible_size());
    bool value_found;
    {
        buf_read_t read(&buf);
        const leaf_node_t *leaf
            = static_cast<const leaf_node_t *>(read.get_data_read());
        value_found = leaf::lookup(&sizer, leaf, key, value.get());
    }
    if (value_found) {
        keyvalue_location_out->buf = std::move(buf);
        keyvalue_location_out->there_originally_was_value = true;
        keyvalue_location_out->value = std::move(value);
    }
}

enum class expired_t { NO, YES };

template <class Value>
void apply_keyvalue_change(keyvalue_location_t<Value> *kv_loc,
        const btree_key_t *key, repli_timestamp_t tstamp, expired_t expired,
        key_modification_callback_t<Value> *km_callback) {

    value_sizer_t<Value> sizer(kv_loc->buf.cache()->get_block_size());

    key_modification_proof_t km_proof
        = km_callback->value_modification(kv_loc, key);

    /* how much this keyvalue change affects the total population of the btree
     * (should be -1, 0 or 1) */
    int population_change;

    if (kv_loc->value.has()) {
        // We have a value to insert.

        // Split the node if necessary, to make sure that we have room
        // for the value.  Not necessary when deleting, because the
        // node won't grow.

        check_and_handle_split(&sizer, &kv_loc->buf, &kv_loc->last_buf,
                               kv_loc->superblock, key, kv_loc->value.get());

        {
#ifndef NDEBUG
            buf_read_t read(&kv_loc->buf);
            auto leaf_node = static_cast<const leaf_node_t *>(read.get_data_read());
            rassert(!leaf::is_full(&sizer, leaf_node, key, kv_loc->value.get()));
#endif
        }

        if (kv_loc->there_originally_was_value) {
            population_change = 0;
        } else {
            population_change = 1;
        }

        {
            buf_write_t write(&kv_loc->buf);
            auto leaf_node = static_cast<leaf_node_t *>(write.get_data_write());
            leaf::insert(&sizer,
                         leaf_node,
                         key,
                         kv_loc->value.get(),
                         tstamp,
                         km_proof);
        }

        kv_loc->stats->pm_keys_set.record();
    } else {
        // Delete the value if it's there.
        if (kv_loc->there_originally_was_value) {
            if (expired == expired_t::NO) {
                rassert(tstamp != repli_timestamp_t::invalid, "Deletes need a valid timestamp now.");
                {
                    buf_write_t write(&kv_loc->buf);
                    auto leaf_node = static_cast<leaf_node_t *>(write.get_data_write());
                    leaf::remove(&sizer,
                                 leaf_node,
                                 key,
                                 tstamp,
                                 km_proof);
                }
                population_change = -1;
                kv_loc->stats->pm_keys_set.record();
            } else {
                // TODO: Oh god oh god get rid of "expired".
                // Expirations do an erase, not a delete.
                {
                    buf_write_t write(&kv_loc->buf);
                    auto leaf_node = static_cast<leaf_node_t *>(write.get_data_write());
                    leaf::erase_presence(&sizer,
                                         leaf_node,
                                         key,
                                         km_proof);
                }
                population_change = 0;
                kv_loc->stats->pm_keys_expired.record();
            }
        } else {
            population_change = 0;
        }
    }

    // Check to see if the leaf is underfull (following a change in
    // size or a deletion, and merge/level if it is.
    check_and_handle_underfull(&sizer, &kv_loc->buf, &kv_loc->last_buf,
                               kv_loc->superblock, key);

    // Modify the stats block.
    // RSI: Should we _actually_ pass kv_loc->buf as the parent?
    // RSI: See parallel_traversal.cc for another use of the stat block -- we do the
    // same thing there.
    buf_lock_t stat_block(&kv_loc->buf, kv_loc->stat_block, alt_access_t::write);
    buf_write_t stat_block_write(&stat_block);
    auto stat_block_buf
        = static_cast<btree_statblock_t *>(stat_block_write.get_data_write());
    stat_block_buf->population += population_change;
}
<|endoftext|>


<commit_before>#include "buffer_cache/mock.hpp"

#include "arch/arch.hpp"
#include "arch/random_delay.hpp"
#include "serializer/serializer.hpp"

/* Internal buf object */

class internal_buf_t {
public:
    mock_cache_t *cache;
    block_id_t block_id;
    repli_timestamp_t subtree_recency;
    void *data;
    rwi_lock_t lock;

    internal_buf_t(mock_cache_t *_cache, block_id_t _block_id, repli_timestamp_t _subtree_recency)
        : cache(_cache), block_id(_block_id), subtree_recency(_subtree_recency),
          data(cache->serializer->malloc()) {
        rassert(data);
        bzero(data, cache->block_size.value());
    }

    ~internal_buf_t() {
        cache->serializer->free(data);
    }

    void destroy() {
        rassert(!lock.locked());

        rassert(cache->bufs[block_id] == this);
        cache->bufs[block_id] = NULL;

        delete this;
    }
};

/* Buf */

block_id_t mock_buf_lock_t::get_block_id() const {
    return internal_buf->block_id;
}

const void *mock_buf_lock_t::get_data_read() const {
    return internal_buf->data;
}

void *mock_buf_lock_t::get_data_major_write() {
    rassert(access == rwi_write);
    dirty = true;
    return internal_buf->data;
}

void mock_buf_lock_t::apply_patch(buf_patch_t *patch) {
    rassert(access == rwi_write);

    patch->apply_to_buf(reinterpret_cast<char *>(internal_buf->data), internal_buf->cache->block_size);
    dirty = true;

    delete patch;
}

patch_counter_t mock_buf_lock_t::get_next_patch_counter() {
    return 0;
}

void mock_buf_lock_t::set_data(void *dest, const void *src, const size_t n) {
    size_t offset = reinterpret_cast<const char *>(dest) - reinterpret_cast<const char *>(internal_buf->data);
    apply_patch(new memcpy_patch_t(internal_buf->block_id, get_next_patch_counter(), offset, reinterpret_cast<const char *>(src), n));
}

void mock_buf_lock_t::move_data(void *dest, const void *src, const size_t n) {
    size_t dest_offset = reinterpret_cast<const char *>(dest) - reinterpret_cast<const char *>(internal_buf->data);
    size_t src_offset = reinterpret_cast<const char *>(src) - reinterpret_cast<const char *>(internal_buf->data);
    apply_patch(new memmove_patch_t(internal_buf->block_id, get_next_patch_counter(), dest_offset, src_offset, n));
}

void mock_buf_lock_t::mark_deleted() {
    rassert(access == rwi_write);
    deleted = true;
}

void mock_buf_lock_t::touch_recency(repli_timestamp_t timestamp) {
    rassert(access == rwi_write);
    internal_buf->subtree_recency = timestamp;
}

void mock_buf_lock_t::release() {
    internal_buf->lock.unlock();
    if (deleted) internal_buf->destroy();
    acquired = false;
    delete this;
}

// TODO: Add notiont of recency_dirty
bool mock_buf_lock_t::is_deleted() const {
    return deleted;
}

/* Transaction */
mock_buf_lock_t::mock_buf_lock_t() :
    internal_buf(NULL),
    access(rwi_read),
    dirty(false),
    deleted(false),
    acquired(false)
{ }

mock_buf_lock_t::mock_buf_lock_t(mock_transaction_t *txn, block_id_t block_id, access_t mode, boost::function<void()> call_when_in_line) :
    internal_buf(txn->cache->bufs[block_id]),
    access(mode),
    dirty(false),
    deleted(false),
    acquired(true)
{
    assert_thread();
    rassert(mode != rwi_write || txn->access == rwi_write);
    rassert(block_id < txn->cache->bufs.get_size());
    rassert(internal_buf);

    internal_buf->lock.co_lock(mode == rwi_read_outdated_ok ? rwi_read : mode, call_when_in_line);

    if (!(mode == rwi_read || mode == rwi_read_outdated_ok || mode == rwi_read_sync)) {
        internal_buf->subtree_recency = txn->recency_timestamp;
    }

    nap(5);   // TODO: We should nap for a random time like `maybe_random_delay()` does
}

mock_buf_lock_t::~mock_buf_lock_t() {
}

mock_buf_lock_t::mock_buf_lock_t(mock_transaction_t *txn) :
    internal_buf(NULL),
    access(txn->access),
    dirty(false),
    deleted(false)
{
    rassert(access == rwi_write);

    block_id_t block_id = txn->cache->bufs.get_size();
    txn->cache->bufs.set_size(block_id + 1);
    internal_buf = new internal_buf_t(txn->cache, block_id, txn->recency_timestamp);
    txn->cache->bufs[block_id] = internal_buf;
    bool locked __attribute__((unused)) = internal_buf->lock.lock(rwi_write, NULL);
    rassert(locked);
}

void mock_transaction_t::get_subtree_recencies(block_id_t *block_ids, size_t num_block_ids, repli_timestamp_t *recencies_out, get_subtree_recencies_callback_t *cb) {
    for (size_t i = 0; i < num_block_ids; ++i) {
        rassert(block_ids[i] < cache->bufs.get_size());
        internal_buf_t *internal_buf = cache->bufs[block_ids[i]];
        rassert(internal_buf);
        recencies_out[i] = internal_buf->subtree_recency;
    }
    cb->got_subtree_recencies();
}

mock_transaction_t::mock_transaction_t(mock_cache_t *_cache, access_t _access, UNUSED int expected_change_count, repli_timestamp_t _recency_timestamp)
    : cache(_cache), order_token(order_token_t::ignore), access(_access), recency_timestamp(_recency_timestamp),
      keepalive(_cache->transaction_counter.get()) {
    coro_fifo_acq_t write_throttle_acq;
    if (is_write_mode(access)) {
        write_throttle_acq.enter(&cache->transaction_constructor_coro_fifo_);
        nap(5);   // TODO: Nap for a random amount of time.
    }
}

mock_transaction_t::~mock_transaction_t() {
    assert_thread();
    if (access == rwi_write) nap(5);   // TODO: Nap for a random amount of time.
}

/* Cache */

// TODO: Why do we take a static_config if we don't use it?
// (I.i.r.c. we have a similar situation in the mirrored cache.)

void mock_cache_t::create(serializer_t *serializer, UNUSED mirrored_cache_static_config_t *static_config) {
    on_thread_t switcher(serializer->home_thread());

    void *superblock = serializer->malloc();
    bzero(superblock, serializer->get_block_size().value());

    index_write_op_t op(SUPERBLOCK_ID);
    op.token = serializer->block_write(superblock, SUPERBLOCK_ID, DEFAULT_DISK_ACCOUNT);
    op.recency = repli_timestamp_t::invalid;
    serializer_index_write(serializer, op, DEFAULT_DISK_ACCOUNT);

    serializer->free(superblock);
}

// dynamic_config is unused because this is a mock cache and the
// configuration parameters don't apply.
mock_cache_t::mock_cache_t( serializer_t *_serializer, UNUSED mirrored_cache_config_t *dynamic_config)
    : serializer(_serializer), transaction_counter(new auto_drainer_t),
      block_size(_serializer->get_block_size()) {

    on_thread_t switcher(serializer->home_thread());

    struct read_callback_t : public iocallback_t, public cond_t {
        read_callback_t() : count(0) { }
        void on_io_complete() {
            count--;
            if (count == 0) pulse();
        }
        int count;
    } read_cb;

    block_id_t end_block_id = serializer->max_block_id();
    bufs.set_size(end_block_id, NULL);
    for (block_id_t i = 0; i < end_block_id; i++) {
        if (!serializer->get_delete_bit(i)) {
            internal_buf_t *internal_buf = bufs[i] = new internal_buf_t(this, i, serializer->get_recency(i));
            read_cb.count++;
            serializer->block_read(serializer->index_read(i), internal_buf->data, DEFAULT_DISK_ACCOUNT, &read_cb);
        }
    }

    /* Block until all readers are done */
    read_cb.wait();
}

struct mock_cb_t : public iocallback_t, public cond_t {
    void on_io_complete() { pulse(); }
};

mock_cache_t::~mock_cache_t() {
    /* Wait for all transactions to complete */
    transaction_counter.reset();

    {
        on_thread_t thread_switcher(serializer->home_thread());
        std::vector<serializer_write_t> writes;
        for (block_id_t i = 0; i < bufs.get_size(); i++)
            writes.push_back(
                bufs[i]
                ? serializer_write_t::make_update(i, bufs[i]->subtree_recency, bufs[i]->data)
                : serializer_write_t::make_delete(i));
        do_writes(serializer, writes, DEFAULT_DISK_ACCOUNT);
    }

    for (block_id_t i = 0; i < bufs.get_size(); i++) {
        if (bufs[i]) delete bufs[i];
    }
}

block_size_t mock_cache_t::get_block_size() {
    return block_size;
}

bool mock_cache_t::offer_read_ahead_buf(UNUSED block_id_t block_id, UNUSED void *buf, UNUSED const boost::intrusive_ptr<standard_block_token_t>& token, UNUSED repli_timestamp_t recency_timestamp) {
    // We never use read-ahead.
    return false;
}

bool mock_cache_t::contains_block(UNUSED block_id_t id) {
    return true;    // TODO (maybe) write a more sensible implementation
}

void mock_buf_lock_t::swap(mock_buf_lock_t &swapee) {
    assert_thread();
    swapee.assert_thread();
    std::swap(internal_buf, swapee.internal_buf);
    std::swap(access, swapee.access);
    std::swap(dirty, swapee.dirty);
    std::swap(deleted, swapee.deleted);
    std::swap(acquired, swapee.acquired);
#ifndef NDEBUG
    std::swap(real_home_thread, swapee.real_home_thread);
#endif
}

bool mock_buf_lock_t::is_acquired() const {
    return acquired;
}

repli_timestamp_t mock_buf_lock_t::get_recency() const {
    return internal_buf->subtree_recency;
}

<commit_msg>Made the mock cache build under valgrind.<commit_after>#include "buffer_cache/mock.hpp"

#include "arch/arch.hpp"
#include "arch/random_delay.hpp"
#include "serializer/serializer.hpp"

/* Internal buf object */

class internal_buf_t {
public:
    mock_cache_t *cache;
    block_id_t block_id;
    repli_timestamp_t subtree_recency;
    void *data;
    rwi_lock_t lock;

    internal_buf_t(mock_cache_t *_cache, block_id_t _block_id, repli_timestamp_t _subtree_recency)
        : cache(_cache), block_id(_block_id), subtree_recency(_subtree_recency),
          data(cache->serializer->malloc()) {
        rassert(data);
        bzero(data, cache->block_size.value());
    }

    ~internal_buf_t() {
        cache->serializer->free(data);
    }

    void destroy() {
        rassert(!lock.locked());

        rassert(cache->bufs[block_id] == this);
        cache->bufs[block_id] = NULL;

        delete this;
    }
};

/* Buf */

block_id_t mock_buf_lock_t::get_block_id() const {
    return internal_buf->block_id;
}

const void *mock_buf_lock_t::get_data_read() const {
    return internal_buf->data;
}

void *mock_buf_lock_t::get_data_major_write() {
    rassert(access == rwi_write);
    dirty = true;
    return internal_buf->data;
}

void mock_buf_lock_t::apply_patch(buf_patch_t *patch) {
    rassert(access == rwi_write);

    patch->apply_to_buf(reinterpret_cast<char *>(internal_buf->data), internal_buf->cache->block_size);
    dirty = true;

    delete patch;
}

patch_counter_t mock_buf_lock_t::get_next_patch_counter() {
    return 0;
}

void mock_buf_lock_t::set_data(void *dest, const void *src, const size_t n) {
    size_t offset = reinterpret_cast<const char *>(dest) - reinterpret_cast<const char *>(internal_buf->data);
    apply_patch(new memcpy_patch_t(internal_buf->block_id, get_next_patch_counter(), offset, reinterpret_cast<const char *>(src), n));
}

void mock_buf_lock_t::move_data(void *dest, const void *src, const size_t n) {
    size_t dest_offset = reinterpret_cast<const char *>(dest) - reinterpret_cast<const char *>(internal_buf->data);
    size_t src_offset = reinterpret_cast<const char *>(src) - reinterpret_cast<const char *>(internal_buf->data);
    apply_patch(new memmove_patch_t(internal_buf->block_id, get_next_patch_counter(), dest_offset, src_offset, n));
}

void mock_buf_lock_t::mark_deleted() {
    rassert(access == rwi_write);
    deleted = true;
}

void mock_buf_lock_t::touch_recency(repli_timestamp_t timestamp) {
    rassert(access == rwi_write);
    internal_buf->subtree_recency = timestamp;
}

void mock_buf_lock_t::release() {
    internal_buf->lock.unlock();
    if (deleted) internal_buf->destroy();
    acquired = false;
    delete this;
}

void mock_buf_lock_t::release_if_acquired() {
    if (is_acquired()) {
        release();
    }
}

// TODO: Add notiont of recency_dirty
bool mock_buf_lock_t::is_deleted() const {
    return deleted;
}

/* Transaction */
mock_buf_lock_t::mock_buf_lock_t() :
    internal_buf(NULL),
    access(rwi_read),
    dirty(false),
    deleted(false),
    acquired(false)
{ }

mock_buf_lock_t::mock_buf_lock_t(mock_transaction_t *txn, block_id_t block_id, access_t mode, boost::function<void()> call_when_in_line) :
    internal_buf(txn->cache->bufs[block_id]),
    access(mode),
    dirty(false),
    deleted(false),
    acquired(true)
{
    assert_thread();
    rassert(mode != rwi_write || txn->access == rwi_write);
    rassert(block_id < txn->cache->bufs.get_size());
    rassert(internal_buf);

    internal_buf->lock.co_lock(mode == rwi_read_outdated_ok ? rwi_read : mode, call_when_in_line);

    if (!(mode == rwi_read || mode == rwi_read_outdated_ok || mode == rwi_read_sync)) {
        internal_buf->subtree_recency = txn->recency_timestamp;
    }

    nap(5);   // TODO: We should nap for a random time like `maybe_random_delay()` does
}

mock_buf_lock_t::~mock_buf_lock_t() {
}

mock_buf_lock_t::mock_buf_lock_t(mock_transaction_t *txn) :
    internal_buf(NULL),
    access(txn->access),
    dirty(false),
    deleted(false)
{
    rassert(access == rwi_write);

    block_id_t block_id = txn->cache->bufs.get_size();
    txn->cache->bufs.set_size(block_id + 1);
    internal_buf = new internal_buf_t(txn->cache, block_id, txn->recency_timestamp);
    txn->cache->bufs[block_id] = internal_buf;
    bool locked __attribute__((unused)) = internal_buf->lock.lock(rwi_write, NULL);
    rassert(locked);
}

void mock_transaction_t::get_subtree_recencies(block_id_t *block_ids, size_t num_block_ids, repli_timestamp_t *recencies_out, get_subtree_recencies_callback_t *cb) {
    for (size_t i = 0; i < num_block_ids; ++i) {
        rassert(block_ids[i] < cache->bufs.get_size());
        internal_buf_t *internal_buf = cache->bufs[block_ids[i]];
        rassert(internal_buf);
        recencies_out[i] = internal_buf->subtree_recency;
    }
    cb->got_subtree_recencies();
}

mock_transaction_t::mock_transaction_t(mock_cache_t *_cache, access_t _access, UNUSED int expected_change_count, repli_timestamp_t _recency_timestamp)
    : cache(_cache), order_token(order_token_t::ignore), access(_access), recency_timestamp(_recency_timestamp),
      keepalive(_cache->transaction_counter.get()) {
    coro_fifo_acq_t write_throttle_acq;
    if (is_write_mode(access)) {
        write_throttle_acq.enter(&cache->transaction_constructor_coro_fifo_);
        nap(5);   // TODO: Nap for a random amount of time.
    }
}

mock_transaction_t::~mock_transaction_t() {
    assert_thread();
    if (access == rwi_write) nap(5);   // TODO: Nap for a random amount of time.
}

/* Cache */

// TODO: Why do we take a static_config if we don't use it?
// (I.i.r.c. we have a similar situation in the mirrored cache.)

void mock_cache_t::create(serializer_t *serializer, UNUSED mirrored_cache_static_config_t *static_config) {
    on_thread_t switcher(serializer->home_thread());

    void *superblock = serializer->malloc();
    bzero(superblock, serializer->get_block_size().value());

    index_write_op_t op(SUPERBLOCK_ID);
    op.token = serializer->block_write(superblock, SUPERBLOCK_ID, DEFAULT_DISK_ACCOUNT);
    op.recency = repli_timestamp_t::invalid;
    serializer_index_write(serializer, op, DEFAULT_DISK_ACCOUNT);

    serializer->free(superblock);
}

// dynamic_config is unused because this is a mock cache and the
// configuration parameters don't apply.
mock_cache_t::mock_cache_t( serializer_t *_serializer, UNUSED mirrored_cache_config_t *dynamic_config)
    : serializer(_serializer), transaction_counter(new auto_drainer_t),
      block_size(_serializer->get_block_size()) {

    on_thread_t switcher(serializer->home_thread());

    struct read_callback_t : public iocallback_t, public cond_t {
        read_callback_t() : count(0) { }
        void on_io_complete() {
            count--;
            if (count == 0) pulse();
        }
        int count;
    } read_cb;

    block_id_t end_block_id = serializer->max_block_id();
    bufs.set_size(end_block_id, NULL);
    for (block_id_t i = 0; i < end_block_id; i++) {
        if (!serializer->get_delete_bit(i)) {
            internal_buf_t *internal_buf = bufs[i] = new internal_buf_t(this, i, serializer->get_recency(i));
            read_cb.count++;
            serializer->block_read(serializer->index_read(i), internal_buf->data, DEFAULT_DISK_ACCOUNT, &read_cb);
        }
    }

    /* Block until all readers are done */
    read_cb.wait();
}

struct mock_cb_t : public iocallback_t, public cond_t {
    void on_io_complete() { pulse(); }
};

mock_cache_t::~mock_cache_t() {
    /* Wait for all transactions to complete */
    transaction_counter.reset();

    {
        on_thread_t thread_switcher(serializer->home_thread());
        std::vector<serializer_write_t> writes;
        for (block_id_t i = 0; i < bufs.get_size(); i++)
            writes.push_back(
                bufs[i]
                ? serializer_write_t::make_update(i, bufs[i]->subtree_recency, bufs[i]->data)
                : serializer_write_t::make_delete(i));
        do_writes(serializer, writes, DEFAULT_DISK_ACCOUNT);
    }

    for (block_id_t i = 0; i < bufs.get_size(); i++) {
        if (bufs[i]) delete bufs[i];
    }
}

block_size_t mock_cache_t::get_block_size() {
    return block_size;
}

bool mock_cache_t::offer_read_ahead_buf(UNUSED block_id_t block_id, UNUSED void *buf, UNUSED const boost::intrusive_ptr<standard_block_token_t>& token, UNUSED repli_timestamp_t recency_timestamp) {
    // We never use read-ahead.
    return false;
}

bool mock_cache_t::contains_block(UNUSED block_id_t id) {
    return true;    // TODO (maybe) write a more sensible implementation
}

void mock_buf_lock_t::swap(mock_buf_lock_t &swapee) {
    assert_thread();
    swapee.assert_thread();
    std::swap(internal_buf, swapee.internal_buf);
    std::swap(access, swapee.access);
    std::swap(dirty, swapee.dirty);
    std::swap(deleted, swapee.deleted);
    std::swap(acquired, swapee.acquired);
#ifndef NDEBUG
    std::swap(real_home_thread, swapee.real_home_thread);
#endif
}

bool mock_buf_lock_t::is_acquired() const {
    return acquired;
}

repli_timestamp_t mock_buf_lock_t::get_recency() const {
    return internal_buf->subtree_recency;
}

<|endoftext|>


<commit_before>#pragma once

#include "math_helpers.hpp"

#include <array>
#include <cassert>
#include <tuple>

namespace cu
{

/// An N-dimensional point whose coordinate type is @c T.
///
/// The coordinates can be accessed via the @c operator[]().
/// The constructor takes the same arguments as an @c std::array<T,N>.
template <typename T, std::size_t N>
class Point
{
public:
  template <typename ...Us, typename = decltype(std::array<T,N>{std::declval<Us>()...})>
  Point( Us && ...args )
    : x_{ std::forward<Us>(args)... }
  {}

  T &operator[]( std::size_t idx )
  {
    return x_[idx];
  }

  const T & operator[]( std::size_t idx ) const
  {
    return x_[idx];
  }

private:
  std::array<T,N> x_;
};

/// An @c N-dimensional rectangle whose coordinate type is @c T.
///
/// The rectangle is determined by two points @c A and @c B, where no
/// coordinate of @c A may be larger than the corresponding coordinate
/// of @c B. This ensures, that the edges of the rectangle all have
/// non-negative lenghts.
template <typename T, std::size_t N>
class Rect
{
  using Point = Point<T,N>;
public:
  Rect()
  {}

  Rect( Point A, Point B )
    : A_(A)
    , B_(B)
  {
    checkInvariants();
  }

  constexpr Point A() const { return A_; }
  constexpr Point B() const { return B_; }

  void setA( const Point & P ) { A_ = P; checkInvariants(); }
  void setB( const Point & P ) { B_ = P; checkInvariants(); }

private:
  void checkInvariants() const
  {
    for ( std::size_t i = 0; i != N; ++i )
      assert( A_[i] <= B_[i] );
  }

  Point A_;
  Point B_;
};

namespace detail
{
  template <typename T>
  std::pair<T,T> intersect_interval( const T & a1, const T & b1,
                                     const T & a2, const T & b2 )
  {
    if ( b1 < a2 || a1 > b2 )
      return { {}, {} };
    return { std::max( a1, a2 ), std::min(b1, b2) };
  }

  template <typename T, std::size_t N, std::size_t ...indices>
  Rect<T,N> intersect_impl( const Rect<T,N> & lhs,
                            const Rect<T,N> & rhs,
                            std::index_sequence<indices...> )
  {
    Point<T,N> A;
    Point<T,N> B;
    for ( std::size_t i = 0; i != N; ++i )
    {
      std::tie( A[i], B[i] ) = intersect_interval(
            lhs.A()[i], lhs.B()[i],
            rhs.A()[i], rhs.B()[i] );
    }
    return { A, B };
  }
}

/// Calculates the intersection of two rectangles.
///
/// If the intersection is empty, then the resulting rectangle will have
/// volume @c 0.
template <typename T, std::size_t N>
Rect<T,N> operator &( const Rect<T,N> & lhs, const Rect<T,N> & rhs )
{
  return detail::intersect_impl( lhs, rhs, std::make_index_sequence<N>() );
}

} // namespace cu
<commit_msg>New: Added operator==() for Point<T,N> and Rect<T,N>.<commit_after>#pragma once

#include "math_helpers.hpp"

#include <array>
#include <cassert>
#include <tuple>

namespace cu
{

/// An N-dimensional point whose coordinate type is @c T.
///
/// The coordinates can be accessed via the @c operator[]().
/// The constructor takes the same arguments as an @c std::array<T,N>.
template <typename T, std::size_t N>
class Point
{
public:
  template <typename ...Us, typename = decltype(std::array<T,N>{std::declval<Us>()...})>
  Point( Us && ...args )
    : x_{ std::forward<Us>(args)... }
  {}

  T &operator[]( std::size_t idx )
  {
    return x_[idx];
  }

  const T & operator[]( std::size_t idx ) const
  {
    return x_[idx];
  }

  bool operator==( const Point & other )
  {
    return x_ == other.x_;
  }

private:
  std::array<T,N> x_;
};

/// An @c N-dimensional rectangle whose coordinate type is @c T.
///
/// The rectangle is determined by two points @c A and @c B, where no
/// coordinate of @c A may be larger than the corresponding coordinate
/// of @c B. This ensures, that the edges of the rectangle all have
/// non-negative lenghts.
template <typename T, std::size_t N>
class Rect
{
  using Point = Point<T,N>;
public:
  Rect()
  {}

  Rect( Point A, Point B )
    : A_(A)
    , B_(B)
  {
    checkInvariants();
  }

  constexpr Point A() const { return A_; }
  constexpr Point B() const { return B_; }

  void setA( const Point & P ) { A_ = P; checkInvariants(); }
  void setB( const Point & P ) { B_ = P; checkInvariants(); }

private:
  void checkInvariants() const
  {
    for ( std::size_t i = 0; i != N; ++i )
      assert( A_[i] <= B_[i] );
  }

  Point A_;
  Point B_;
};

namespace detail
{
  template <typename T>
  std::pair<T,T> intersect_interval( const T & a1, const T & b1,
                                     const T & a2, const T & b2 )
  {
    if ( b1 < a2 || a1 > b2 )
      return { {}, {} };
    return { std::max( a1, a2 ), std::min(b1, b2) };
  }

  template <typename T, std::size_t N, std::size_t ...indices>
  Rect<T,N> intersect_impl( const Rect<T,N> & lhs,
                            const Rect<T,N> & rhs,
                            std::index_sequence<indices...> )
  {
    Point<T,N> A;
    Point<T,N> B;
    for ( std::size_t i = 0; i != N; ++i )
    {
      std::tie( A[i], B[i] ) = intersect_interval(
            lhs.A()[i], lhs.B()[i],
            rhs.A()[i], rhs.B()[i] );
    }
    return { A, B };
  }
}

/// Calculates the intersection of two rectangles.
///
/// If the intersection is empty, then the resulting rectangle will have
/// volume @c 0.
template <typename T, std::size_t N>
Rect<T,N> operator &( const Rect<T,N> & lhs, const Rect<T,N> & rhs )
{
  return detail::intersect_impl( lhs, rhs, std::make_index_sequence<N>() );
}

template <typename T, std::size_t N>
bool operator==( const Rect<T,N> & lhs, const Rect<T,N> & rhs )
{
  return lhs.A() == rhs.A() && lhs.B() == rhs.B();
}

} // namespace cu
<|endoftext|>