switch (m_controls->m_ampRangeModel.value())
	{
		case ARANGE_EXTENDED: return ARANGE_EXTENDED_END;
		case ARANGE_SILENT: return ARANGE_SILENT_END;
		case ARANGE_LOUD: return ARANGE_LOUD_END;
		default:
		case ARANGE_AUDIBLE: return ARANGE_AUDIBLE_END;
	}

/* SaProcessor.cpp - implementation of SaProcessor class.
 *
 *
 * Based partially on Eq plugin code,
 *
 * This file is part of LMMS - https://lmms.io
 *
 */

#include "SaProcessor.h"

#include <algorithm>
#ifdef SA_DEBUG
	#include <chrono>
#endif
#include <cmath>
#ifdef SA_DEBUG
	#include <iomanip>
	#include <iostream>
#endif
#include <QMutexLocker>

#include "lmms_math.h"
#include "LocklessRingBuffer.h"

SaProcessor::SaProcessor(const SaControls *controls) :
	m_controls(controls),
	m_terminate(false),
	m_inBlockSize(FFT_BLOCK_SIZES[0]),
	m_fftBlockSize(FFT_BLOCK_SIZES[0]),
	m_sampleRate(Engine::audioEngine()->processingSampleRate()),
	m_framesFilledUp(0),
	m_spectrumActive(false),
	m_waterfallActive(false),
	m_waterfallNotEmpty(0),
	m_reallocating(false)
{
	m_fftWindow.resize(m_inBlockSize, 1.0);
	precomputeWindow(m_fftWindow.data(), m_inBlockSize, BLACKMAN_HARRIS);

	m_bufferL.resize(m_inBlockSize, 0);
	m_bufferR.resize(m_inBlockSize, 0);
	m_filteredBufferL.resize(m_fftBlockSize, 0);
	m_filteredBufferR.resize(m_fftBlockSize, 0);
	m_spectrumL = (fftwf_complex *) fftwf_malloc(binCount() * sizeof (fftwf_complex));
	m_spectrumR = (fftwf_complex *) fftwf_malloc(binCount() * sizeof (fftwf_complex));
	m_fftPlanL = fftwf_plan_dft_r2c_1d(m_fftBlockSize, m_filteredBufferL.data(), m_spectrumL, FFTW_MEASURE);
	m_fftPlanR = fftwf_plan_dft_r2c_1d(m_fftBlockSize, m_filteredBufferR.data(), m_spectrumR, FFTW_MEASURE);

	m_absSpectrumL.resize(binCount(), 0);
	m_absSpectrumR.resize(binCount(), 0);
	m_normSpectrumL.resize(binCount(), 0);
	m_normSpectrumR.resize(binCount(), 0);

	m_waterfallHeight = 100;	// a small safe value
	m_history_work.resize(waterfallWidth() * m_waterfallHeight * sizeof qRgb(0,0,0), 0);
	m_history.resize(waterfallWidth() * m_waterfallHeight * sizeof qRgb(0,0,0), 0);
}

SaProcessor::~SaProcessor()
{
	if (m_fftPlanL != nullptr) {fftwf_destroy_plan(m_fftPlanL);}
	if (m_fftPlanR != nullptr) {fftwf_destroy_plan(m_fftPlanR);}
	if (m_spectrumL != nullptr) {fftwf_free(m_spectrumL);}
	if (m_spectrumR != nullptr) {fftwf_free(m_spectrumR);}

	m_fftPlanL = nullptr;
	m_fftPlanR = nullptr;
	m_spectrumL = nullptr;
	m_spectrumR = nullptr;
}

// Load data from audio thread ringbuffer and run FFT analysis if buffer is full enough.
void SaProcessor::analyze(LocklessRingBuffer<sampleFrame> &ring_buffer)
{
	LocklessRingBufferReader<sampleFrame> reader(ring_buffer);

	// Processing thread loop
	while (!m_terminate)
	{
		// If there is nothing to read, wait for notification from the writing side.
		if (reader.empty()) {reader.waitForData();}

		// skip waterfall render if processing can't keep up with input
		bool overload = ring_buffer.free() < ring_buffer.capacity() / 2;

		auto in_buffer = reader.read_max(ring_buffer.capacity() / 4);
		std::size_t frame_count = in_buffer.size();

		// Process received data only if any view is visible and not paused.
		// Also, to prevent a momentary GUI freeze under high load (due to lock
		// starvation), skip analysis when buffer reallocation is requested.
		if ((m_spectrumActive || m_waterfallActive) && !m_controls->m_pauseModel.value() && !m_reallocating)
		{
			const bool stereo = m_controls->m_stereoModel.value();
			fpp_t in_frame = 0;
			while (in_frame < frame_count)
			{
				// Lock data access to prevent reallocation from changing
				// buffers and control variables.
				QMutexLocker data_lock(&m_dataAccess);

				// Fill sample buffers and check for zero input.
				bool block_empty = true;
				for (; in_frame < frame_count && m_framesFilledUp < m_inBlockSize; in_frame++, m_framesFilledUp++)
				{
					if (stereo)
					{
						m_bufferL[m_framesFilledUp] = in_buffer[in_frame][0];
						m_bufferR[m_framesFilledUp] = in_buffer[in_frame][1];
					}
					else
					{
						m_bufferL[m_framesFilledUp] =
						m_bufferR[m_framesFilledUp] = (in_buffer[in_frame][0] + in_buffer[in_frame][1]) * 0.5f;
					}
					if (in_buffer[in_frame][0] != 0.f || in_buffer[in_frame][1] != 0.f)
					{
						block_empty = false;
					}
				}

				// Run analysis only if buffers contain enough data.
				if (m_framesFilledUp < m_inBlockSize) {break;}

				// Print performance analysis once per 2 seconds if debug is enabled
				#ifdef SA_DEBUG
					unsigned int total_time = std::chrono::high_resolution_clock::now().time_since_epoch().count();
					if (total_time - m_last_dump_time > 2000000000)
					{
						std::cout << "FFT analysis: " << std::fixed << std::setprecision(2)
							<< m_sum_execution / m_dump_count << " ms avg / "
							<< m_max_execution << " ms peak, executing "
							<< m_dump_count << " times per second ("
							<< m_sum_execution / 20.0 << " % CPU usage)." << std::endl;
						m_last_dump_time = total_time;
						m_sum_execution = m_max_execution = m_dump_count = 0;
					}
				#endif

				// update sample rate
				m_sampleRate = Engine::audioEngine()->processingSampleRate();

				// apply FFT window
				for (unsigned int i = 0; i < m_inBlockSize; i++)
				{
					m_filteredBufferL[i] = m_bufferL[i] * m_fftWindow[i];
					m_filteredBufferR[i] = m_bufferR[i] * m_fftWindow[i];
				}

				// Run FFT on left channel, convert the result to absolute magnitude
				// spectrum and normalize it.
				fftwf_execute(m_fftPlanL);
				absspec(m_spectrumL, m_absSpectrumL.data(), binCount());
				normalize(m_absSpectrumL, m_normSpectrumL, m_inBlockSize);

				// repeat analysis for right channel if stereo processing is enabled
				if (stereo)
				{
					fftwf_execute(m_fftPlanR);
					absspec(m_spectrumR, m_absSpectrumR.data(), binCount());
					normalize(m_absSpectrumR, m_normSpectrumR, m_inBlockSize);
				}

				// count empty lines so that empty history does not have to update
				if (block_empty && m_waterfallNotEmpty)
				{
					m_waterfallNotEmpty -= 1;
				}
				else if (!block_empty)
				{
					m_waterfallNotEmpty = m_waterfallHeight + 2;
				}

				if (m_waterfallActive && m_waterfallNotEmpty)
				{
					// move waterfall history one line down and clear the top line
					QRgb *pixel = (QRgb *)m_history_work.data();
					std::copy(pixel,
							  pixel + waterfallWidth() * m_waterfallHeight - waterfallWidth(),
							  pixel + waterfallWidth());
					memset(pixel, 0, waterfallWidth() * sizeof (QRgb));

					// add newest result on top
					int target;		// pixel being constructed
					float accL = 0;	// accumulators for merging multiple bins
					float accR = 0;
					for (unsigned int i = 0; i < binCount(); i++)
					{
						// fill line with red color to indicate lost data if CPU cannot keep up
						if (overload && i < waterfallWidth())
						{
							pixel[i] = qRgb(42, 0, 0);
							continue;
						}

						// Every frequency bin spans a frequency range that must be
						// partially or fully mapped to a pixel. Any inconsistency
						// may be seen in the spectrogram as dark or white lines --
						// play white noise to confirm your change did not break it.
						float band_start = freqToXPixel(binToFreq(i) - binBandwidth() / 2.0, waterfallWidth());
						float band_end = freqToXPixel(binToFreq(i + 1) - binBandwidth() / 2.0, waterfallWidth());
						if (m_controls->m_logXModel.value())
						{
							// Logarithmic scale
							if (band_end - band_start > 1.0)
							{
								// band spans multiple pixels: draw all pixels it covers
								for (target = (int)band_start; target < (int)band_end; target++)
								{
									if (target >= 0 && target < waterfallWidth())
									{
										pixel[target] = makePixel(m_normSpectrumL[i], m_normSpectrumR[i]);
									}
								}
								// save remaining portion of the band for the following band / pixel
								// (in case the next band uses sub-pixel drawing)
								accL = (band_end - (int)band_end) * m_normSpectrumL[i];
								accR = (band_end - (int)band_end) * m_normSpectrumR[i];
							}
							else
							{
								// sub-pixel drawing; add contribution of current band
								target = (int)band_start;
								if ((int)band_start == (int)band_end)
								{
									// band ends within current target pixel, accumulate
									accL += (band_end - band_start) * m_normSpectrumL[i];
									accR += (band_end - band_start) * m_normSpectrumR[i];
								}
								else
								{
									// Band ends in the next pixel -- finalize the current pixel.
									// Make sure contribution is split correctly on pixel boundary.
									accL += ((int)band_end - band_start) * m_normSpectrumL[i];
									accR += ((int)band_end - band_start) * m_normSpectrumR[i];

									if (target >= 0 && target < waterfallWidth()) {pixel[target] = makePixel(accL, accR);}

									// save remaining portion of the band for the following band / pixel
									accL = (band_end - (int)band_end) * m_normSpectrumL[i];
									accR = (band_end - (int)band_end) * m_normSpectrumR[i];
								}
							}
						}
						else
						{
							// Linear: always draws one or more pixels per band
							for (target = (int)band_start; target < band_end; target++)
							{
								if (target >= 0 && target < waterfallWidth())
								{
									pixel[target] = makePixel(m_normSpectrumL[i], m_normSpectrumR[i]);
								}
							}
						}
					}

					// Copy work buffer to result buffer. Done only if requested, so
					// that time isn't wasted on updating faster than display FPS.
					// (The copy is about as expensive as the movement.)
					if (m_flipRequest)
					{
						m_history = m_history_work;
						m_flipRequest = false;
					}
				}
				// clean up before checking for more data from input buffer
				const unsigned int overlaps = m_controls->m_windowOverlapModel.value();
				if (overlaps == 1)	// Discard buffer, each sample used only once
				{
					m_framesFilledUp = 0;
				}
				else
				{
					// Drop only a part of the buffer from the beginning, so that new
					// data can be added to the end. This means the older samples will
					// be analyzed again, but in a different position in the window,
					// making short transient signals show up better in the waterfall.
					const unsigned int drop = m_inBlockSize / overlaps;
					std::move(m_bufferL.begin() + drop, m_bufferL.end(), m_bufferL.begin());
					std::move(m_bufferR.begin() + drop, m_bufferR.end(), m_bufferR.begin());
					m_framesFilledUp -= drop;
				}

				#ifdef SA_DEBUG
					// measure overall FFT processing speed
					total_time = std::chrono::high_resolution_clock::now().time_since_epoch().count() - total_time;
					m_dump_count++;
					m_sum_execution += total_time / 1000000.0;
					if (total_time / 1000000.0 > m_max_execution) {m_max_execution = total_time / 1000000.0;}
				#endif
			}	// frame filler and processing
		}	// process if active
	}	// thread loop end
}

// Produce a spectrogram pixel from normalized spectrum data.
// Values over 1.0 will cause the color components to overflow: this is left
// intentionally untreated as it clearly indicates which frequency is clipping.
// Gamma correction is applied to make small values more visible and to make
// a linear gradient actually appear roughly linear. The correction should be
// around 0.42 to 0.45 for sRGB displays (or lower for bigger visibility boost).
QRgb SaProcessor::makePixel(float left, float right) const
{
	const float gamma_correction = m_controls->m_waterfallGammaModel.value();
	if (m_controls->m_stereoModel.value())
	{
		float ampL = pow(left, gamma_correction);
		float ampR = pow(right, gamma_correction);
		return qRgb(m_controls->m_colorL.red() * ampL + m_controls->m_colorR.red() * ampR,
					m_controls->m_colorL.green() * ampL + m_controls->m_colorR.green() * ampR,
					m_controls->m_colorL.blue() * ampL + m_controls->m_colorR.blue() * ampR);
	}
	else
	{
		float ampL = pow(left, gamma_correction);
		// make mono color brighter to compensate for the fact it is not summed
		return qRgb(m_controls->m_colorMonoW.red() * ampL,
					m_controls->m_colorMonoW.green() * ampL,
					m_controls->m_colorMonoW.blue() * ampL);
	}
}

// Inform the processor whether any display widgets actually need it.
void SaProcessor::setSpectrumActive(bool active)
{
	m_spectrumActive = active;
}

void SaProcessor::setWaterfallActive(bool active)
{
	m_waterfallActive = active;
}

// Reallocate data buffers according to newly set block size.
void SaProcessor::reallocateBuffers()
{
	unsigned int new_size_index = m_controls->m_blockSizeModel.value();
	unsigned int new_in_size, new_fft_size;
	unsigned int new_bins;

	// get new block sizes and bin count based on selected index
	if (new_size_index < FFT_BLOCK_SIZES.size())
	{
		new_in_size = FFT_BLOCK_SIZES[new_size_index];
	}
	else
	{
		new_in_size = FFT_BLOCK_SIZES.back();
	}
	m_zeroPadFactor = m_controls->m_zeroPaddingModel.value();
	if (new_size_index + m_zeroPadFactor < FFT_BLOCK_SIZES.size())
	{
		new_fft_size = FFT_BLOCK_SIZES[new_size_index + m_zeroPadFactor];
	}
	else
	{
		new_fft_size = FFT_BLOCK_SIZES.back();
	}

	new_bins = new_fft_size / 2 +1;

	// Use m_reallocating to tell analyze() to avoid asking for the lock. This
	// is needed because under heavy load the FFT thread requests data lock so
	// often that this routine could end up waiting even for several seconds.
	m_reallocating = true;

	// Lock data shared with SaSpectrumView and SaWaterfallView.
	// Reallocation lock must be acquired first to avoid deadlock (a view class
	// may already have it and request the "stronger" data lock on top of that).
	QMutexLocker reloc_lock(&m_reallocationAccess);
	QMutexLocker data_lock(&m_dataAccess);

	// destroy old FFT plan and free the result buffer
	if (m_fftPlanL != nullptr) {fftwf_destroy_plan(m_fftPlanL);}
	if (m_fftPlanR != nullptr) {fftwf_destroy_plan(m_fftPlanR);}
	if (m_spectrumL != nullptr) {fftwf_free(m_spectrumL);}
	if (m_spectrumR != nullptr) {fftwf_free(m_spectrumR);}

	// allocate new space, create new plan and resize containers
	m_fftWindow.resize(new_in_size, 1.0);
	precomputeWindow(m_fftWindow.data(), new_in_size, (FFT_WINDOWS) m_controls->m_windowModel.value());
	m_bufferL.resize(new_in_size, 0);
	m_bufferR.resize(new_in_size, 0);
	m_filteredBufferL.resize(new_fft_size, 0);
	m_filteredBufferR.resize(new_fft_size, 0);
	m_spectrumL = (fftwf_complex *) fftwf_malloc(new_bins * sizeof (fftwf_complex));
	m_spectrumR = (fftwf_complex *) fftwf_malloc(new_bins * sizeof (fftwf_complex));
	m_fftPlanL = fftwf_plan_dft_r2c_1d(new_fft_size, m_filteredBufferL.data(), m_spectrumL, FFTW_MEASURE);
	m_fftPlanR = fftwf_plan_dft_r2c_1d(new_fft_size, m_filteredBufferR.data(), m_spectrumR, FFTW_MEASURE);

	if (m_fftPlanL == nullptr || m_fftPlanR == nullptr)
	{
		#ifdef SA_DEBUG
			std::cerr << "Analyzer: failed to create new FFT plan!" << std::endl;
		#endif
	}
	m_absSpectrumL.resize(new_bins, 0);
	m_absSpectrumR.resize(new_bins, 0);
	m_normSpectrumL.resize(new_bins, 0);
	m_normSpectrumR.resize(new_bins, 0);

	m_waterfallHeight = m_controls->m_waterfallHeightModel.value();
	m_history_work.resize((new_bins < m_waterfallMaxWidth ? new_bins : m_waterfallMaxWidth)
							* m_waterfallHeight
							* sizeof qRgb(0,0,0), 0);
	m_history.resize((new_bins < m_waterfallMaxWidth ? new_bins : m_waterfallMaxWidth)
						* m_waterfallHeight
						* sizeof qRgb(0,0,0), 0);

	// done; publish new sizes and clean up
	m_inBlockSize = new_in_size;
	m_fftBlockSize = new_fft_size;

	data_lock.unlock();
	reloc_lock.unlock();
	m_reallocating = false;

	clear();
}

// Precompute a new FFT window based on currently selected type.
void SaProcessor::rebuildWindow()
{
	// computation is done in fft_helpers
	QMutexLocker lock(&m_dataAccess);
	precomputeWindow(m_fftWindow.data(), m_inBlockSize, (FFT_WINDOWS) m_controls->m_windowModel.value());
}

// Clear all data buffers and replace contents with zeros.
// Note: may take a few milliseconds, do not call in a loop!
void SaProcessor::clear()
{
	const unsigned int overlaps = m_controls->m_windowOverlapModel.value();
	QMutexLocker lock(&m_dataAccess);
	// If there is any window overlap, leave space only for the new samples
	// and treat the rest at initialized with zeros. Prevents missing
	// transients at the start of the very first block.
	m_framesFilledUp = m_inBlockSize - m_inBlockSize / overlaps;
	std::fill(m_bufferL.begin(), m_bufferL.end(), 0);
	std::fill(m_bufferR.begin(), m_bufferR.end(), 0);
	std::fill(m_filteredBufferL.begin(), m_filteredBufferL.end(), 0);
	std::fill(m_filteredBufferR.begin(), m_filteredBufferR.end(), 0);
	std::fill(m_absSpectrumL.begin(), m_absSpectrumL.end(), 0);
	std::fill(m_absSpectrumR.begin(), m_absSpectrumR.end(), 0);
	std::fill(m_normSpectrumL.begin(), m_normSpectrumL.end(), 0);
	std::fill(m_normSpectrumR.begin(), m_normSpectrumR.end(), 0);
	std::fill(m_history_work.begin(), m_history_work.end(), 0);
	std::fill(m_history.begin(), m_history.end(), 0);
}

// Clear only history work buffer. Used to flush old data when waterfall
// is shown after a period of inactivity.
void SaProcessor::clearHistory()
{
	QMutexLocker lock(&m_dataAccess);
	std::fill(m_history_work.begin(), m_history_work.end(), 0);
}

// Check if result buffers contain any non-zero values
bool SaProcessor::spectrumNotEmpty()
{
	QMutexLocker lock(&m_reallocationAccess);
	return notEmpty(m_normSpectrumL) || notEmpty(m_normSpectrumR);
}

// Frequency conversion helpers

// Get sample rate value that is valid for currently stored results.
unsigned int SaProcessor::getSampleRate() const
{
	return m_sampleRate;
}

// Maximum frequency of a sampled signal is equal to half of its sample rate.
float SaProcessor::getNyquistFreq() const
{
	return getSampleRate() / 2.0f;
}

// FFTW automatically discards upper half of the symmetric FFT output, so
// the useful bin count is the transform size divided by 2, plus zero.
unsigned int SaProcessor::binCount() const
{
	return m_fftBlockSize / 2 + 1;
}

// Return the final width of waterfall display buffer.
// Normally the waterfall width equals the number of frequency bins, but the
// FFT transform can easily produce more bins than can be reasonably useful for
// currently used display resolutions. This function limits width of the final
// image to a given size, which is then used during waterfall render and display.
unsigned int SaProcessor::waterfallWidth() const
{
	return binCount() < m_waterfallMaxWidth ? binCount() : m_waterfallMaxWidth;
}

// Return the center frequency of given frequency bin.
float SaProcessor::binToFreq(unsigned int bin_index) const
{
	return getNyquistFreq() * bin_index / binCount();
}

// Return width of the frequency range that falls into one bin.
// The binCount is lowered by one since half of the first and last bin is
// actually outside the frequency range.
float SaProcessor::binBandwidth() const
{
	return getNyquistFreq() / (binCount() - 1);
}

float SaProcessor::getFreqRangeMin(bool linear) const
{
	switch (m_controls->m_freqRangeModel.value())
	{
		case FRANGE_AUDIBLE: return FRANGE_AUDIBLE_START;
		case FRANGE_BASS: return FRANGE_BASS_START;
		case FRANGE_MIDS: return FRANGE_MIDS_START;
		case FRANGE_HIGH: return FRANGE_HIGH_START;
		default:
		case FRANGE_FULL: return linear ? 0 : LOWEST_LOG_FREQ;
	}
}

float SaProcessor::getFreqRangeMax() const
{
	switch (m_controls->m_freqRangeModel.value())
	{
		case FRANGE_AUDIBLE: return FRANGE_AUDIBLE_END;
		case FRANGE_BASS: return FRANGE_BASS_END;
		case FRANGE_MIDS: return FRANGE_MIDS_END;
		case FRANGE_HIGH: return FRANGE_HIGH_END;
		default:
		case FRANGE_FULL: return getNyquistFreq();
	}
}

// Map frequency to pixel x position on a display of given width.
float SaProcessor::freqToXPixel(float freq, unsigned int width) const
{
	if (m_controls->m_logXModel.value())
	{
		if (freq <= 1) {return 0;}
		float min = log10(getFreqRangeMin());
		float range = log10(getFreqRangeMax()) - min;
		return (log10(freq) - min) / range * width;
	}
	else
	{
		float min = getFreqRangeMin();
		float range = getFreqRangeMax() - min;
		return (freq - min) / range * width;
	}
}

// Map pixel x position on display of given width back to frequency.
float SaProcessor::xPixelToFreq(float x, unsigned int width) const
{
	if (m_controls->m_logXModel.value())
	{
		float min = log10(getFreqRangeMin());
		float max = log10(getFreqRangeMax());
		float range = max - min;
		return pow(10, min + x / width * range);
	}
	else
	{
		float min = getFreqRangeMin();
		float range = getFreqRangeMax() - min;
		return min + x / width * range;
	}
}

// Amplitude conversion helpers
float SaProcessor::getAmpRangeMin(bool linear) const
{
	// return very low limit to make sure zero gets included at linear grid
	if (linear) {return -900;}
	switch (m_controls->m_ampRangeModel.value())
	{
		case ARANGE_EXTENDED: return ARANGE_EXTENDED_START;
		case ARANGE_SILENT: return ARANGE_SILENT_START;
		case ARANGE_LOUD: return ARANGE_LOUD_START;
		default:
		case ARANGE_AUDIBLE: return ARANGE_AUDIBLE_START;
	}
}

float SaProcessor::getAmpRangeMax() const
{

}

// Map linear amplitude to pixel y position on a display of given height.
// Note that display coordinates are flipped: amplitude grows from [height] to zero.
float SaProcessor::ampToYPixel(float amplitude, unsigned int height) const
{
	if (m_controls->m_logYModel.value())
	{
		// logarithmic scale: convert linear amplitude to dB (relative to 1.0)
		float amplitude_dB = 10 * log10(amplitude);
		if (amplitude_dB < getAmpRangeMin())
		{
			return height;
		}
		else
		{
			float max = getAmpRangeMax();
			float range = getAmpRangeMin() - max;
			return (amplitude_dB - max) / range * height;
		}
	}
	else
	{
		// linear scale: convert returned ranges from dB to linear scale
		float max = pow(10, getAmpRangeMax() / 10);
		float range = pow(10, getAmpRangeMin() / 10) - max;
		return (amplitude - max) / range * height;
	}
}

// Map pixel y position on display of given height back to amplitude.
// Note that display coordinates are flipped: amplitude grows from [height] to zero.
// Also note that in logarithmic Y mode the returned amplitude is in dB, not linear.
float SaProcessor::yPixelToAmp(float y, unsigned int height) const
{
	if (m_controls->m_logYModel.value())
	{
		float max = getAmpRangeMax();
		float range = getAmpRangeMin() - max;
		return max + range * (y / height);
	}
	else
	{
		// linear scale: convert returned ranges from dB to linear scale
		float max = pow(10, getAmpRangeMax() / 10);
		float range = pow(10, getAmpRangeMin() / 10) - max;
		return max + range * (y / height);
	}
}


TEST_P(ParallelShaderCompileTest, Basic)
{
    ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable());

    GLint count = 0;
    glMaxShaderCompilerThreadsKHR(8);
    EXPECT_GL_NO_ERROR();
    glGetIntegerv(GL_MAX_SHADER_COMPILER_THREADS_KHR, &count);
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(8, count);
}

// ParallelShaderCompileTest.cpp : Tests of the GL_KHR_parallel_shader_compile extension.

#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"

#include "util/random_utils.h"
#include "util/test_utils.h"

using namespace angle;

namespace
{

namespace
{

constexpr int kTaskCount             = 32;
constexpr unsigned int kPollInterval = 100;

}  // anonymous namespace

class ParallelShaderCompileTest : public ANGLETest
{
  protected:
    ParallelShaderCompileTest()
    {
        setWindowWidth(128);
        setWindowHeight(128);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }

    bool ensureParallelShaderCompileExtensionAvailable()
    {
        if (IsGLExtensionRequestable("GL_KHR_parallel_shader_compile"))
        {
            glRequestExtensionANGLE("GL_KHR_parallel_shader_compile");
        }

        if (!IsGLExtensionEnabled("GL_KHR_parallel_shader_compile"))
        {
            return false;
        }
        return true;
    }

    class Task
    {
      public:
        Task(int id) : mID(id) {}
        virtual ~Task() {}

        virtual bool compile()                                     = 0;
        virtual bool isCompileCompleted()                          = 0;
        virtual bool link()                                        = 0;
        virtual void runAndVerify(ParallelShaderCompileTest *test) = 0;

        bool isLinkCompleted()
        {
            GLint status;
            glGetProgramiv(mProgram, GL_COMPLETION_STATUS_KHR, &status);
            return (status == GL_TRUE);
        }

      protected:
        std::string insertRandomString(const std::string &source)
        {
            RNG rng;
            std::ostringstream ostream;
            ostream << source << "\n// Random string to fool program cache: " << rng.randomInt()
                    << "\n";
            return ostream.str();
        }

        GLuint CompileShader(GLenum type, const std::string &source)
        {
            GLuint shader = glCreateShader(type);

            const char *sourceArray[1] = {source.c_str()};
            glShaderSource(shader, 1, sourceArray, nullptr);
            glCompileShader(shader);
            return shader;
        }

        bool checkShader(GLuint shader)
        {
            GLint compileResult;
            glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);

            if (compileResult == 0)
            {
                GLint infoLogLength;
                glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);

                // Info log length includes the null terminator, so 1 means that the info log is an
                // empty string.
                if (infoLogLength > 1)
                {
                    std::vector<GLchar> infoLog(infoLogLength);
                    glGetShaderInfoLog(shader, static_cast<GLsizei>(infoLog.size()), nullptr,
                                       &infoLog[0]);
                    std::cerr << "shader compilation failed: " << &infoLog[0];
                }
                else
                {
                    std::cerr << "shader compilation failed. <Empty log message>";
                }
                std::cerr << std::endl;
            }
            return (compileResult == GL_TRUE);
        }

        GLuint mProgram;
        int mID;
    };

    template <typename T>
    class TaskRunner
    {
      public:
        TaskRunner() {}
        ~TaskRunner() {}

        void run(ParallelShaderCompileTest *test)
        {

            std::vector<std::unique_ptr<T>> compileTasks;
            for (int i = 0; i < kTaskCount; ++i)
            {
                std::unique_ptr<T> task(new T(i));
                bool isCompiling = task->compile();
                ASSERT_TRUE(isCompiling);
                compileTasks.push_back(std::move(task));
            }

            std::vector<std::unique_ptr<T>> linkTasks;
            while (!compileTasks.empty())
            {
                for (unsigned int i = 0; i < compileTasks.size();)
                {
                    auto &task = compileTasks[i];

                    if (task->isCompileCompleted())
                    {
                        bool isLinking = task->link();
                        ASSERT_TRUE(isLinking);
                        linkTasks.push_back(std::move(task));
                        compileTasks.erase(compileTasks.begin() + i);
                        continue;
                    }
                    ++i;
                }
                angle::Sleep(kPollInterval);
            }

            while (!linkTasks.empty())
            {
                for (unsigned int i = 0; i < linkTasks.size();)
                {
                    auto &task = linkTasks[i];

                    if (task->isLinkCompleted())
                    {
                        task->runAndVerify(test);
                        linkTasks.erase(linkTasks.begin() + i);
                        continue;
                    }
                    ++i;
                }
                angle::Sleep(kPollInterval);
            }
        }
    };

    class ClearColorWithDraw : public Task
    {
      public:
        ClearColorWithDraw(int taskID) : Task(taskID)
        {
            auto color = static_cast<GLubyte>(taskID * 255 / kTaskCount);
            mColor     = {color, color, color, 255};
        }

        bool compile() override
        {
            mVertexShader =
                CompileShader(GL_VERTEX_SHADER, insertRandomString(essl1_shaders::vs::Simple()));
            mFragmentShader = CompileShader(GL_FRAGMENT_SHADER,
                                            insertRandomString(essl1_shaders::fs::UniformColor()));
            return (mVertexShader != 0 && mFragmentShader != 0);
        }

        bool isCompileCompleted() override
        {
            GLint status;
            glGetShaderiv(mVertexShader, GL_COMPLETION_STATUS_KHR, &status);
            if (status == GL_TRUE)
            {
                glGetShaderiv(mFragmentShader, GL_COMPLETION_STATUS_KHR, &status);
                return (status == GL_TRUE);
            }
            return false;
        }

        bool link() override
        {
            mProgram = 0;
            if (checkShader(mVertexShader) && checkShader(mFragmentShader))
            {
                mProgram = glCreateProgram();
                glAttachShader(mProgram, mVertexShader);
                glAttachShader(mProgram, mFragmentShader);
                glLinkProgram(mProgram);
            }
            glDeleteShader(mVertexShader);
            glDeleteShader(mFragmentShader);
            return (mProgram != 0);
        }

        void runAndVerify(ParallelShaderCompileTest *test) override
        {
            glClearColor(0, 0, 0, 0);
            glClear(GL_COLOR_BUFFER_BIT);
            glDisable(GL_DEPTH_TEST);
            glUseProgram(mProgram);
            ASSERT_GL_NO_ERROR();
            GLint colorUniformLocation =
                glGetUniformLocation(mProgram, essl1_shaders::ColorUniform());
            ASSERT_NE(colorUniformLocation, -1);
            auto normalizeColor = mColor.toNormalizedVector();
            glUniform4fv(colorUniformLocation, 1, normalizeColor.data());
            test->drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(test->getWindowWidth() / 2, test->getWindowHeight() / 2, mColor);
            glUseProgram(0);
            glDeleteProgram(mProgram);
            ASSERT_GL_NO_ERROR();
        }

      private:
        GLColor mColor;
        GLuint mVertexShader;
        GLuint mFragmentShader;
    };

    class ImageLoadStore : public Task
    {
      public:
        ImageLoadStore(int taskID) : Task(taskID) {}
        ~ImageLoadStore() {}

        bool compile() override
        {
            const char kCSSource[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
void main()
{
    uvec4 value = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
    imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value);
})";

            mShader = CompileShader(GL_COMPUTE_SHADER, insertRandomString(kCSSource));
            return mShader != 0;
        }

        bool isCompileCompleted() override
        {
            GLint status;
            glGetShaderiv(mShader, GL_COMPLETION_STATUS_KHR, &status);
            return status == GL_TRUE;
        }

        bool link() override
        {
            mProgram = 0;
            if (checkShader(mShader))
            {
                mProgram = glCreateProgram();
                glAttachShader(mProgram, mShader);
                glLinkProgram(mProgram);
            }
            glDeleteShader(mShader);
            return mProgram != 0;
        }

        void runAndVerify(ParallelShaderCompileTest *test) override
        {
            // Taken from ComputeShaderTest.StoreImageThenLoad.
            constexpr GLuint kInputValues[3][1] = {{300}, {200}, {100}};
            GLTexture texture[3];
            glBindTexture(GL_TEXTURE_2D, texture[0]);
            glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT,
                            kInputValues[0]);
            EXPECT_GL_NO_ERROR();

            glBindTexture(GL_TEXTURE_2D, texture[1]);
            glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT,
                            kInputValues[1]);
            EXPECT_GL_NO_ERROR();

            glBindTexture(GL_TEXTURE_2D, texture[2]);
            glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT,
                            kInputValues[2]);
            EXPECT_GL_NO_ERROR();

            glUseProgram(mProgram);

            glBindImageTexture(0, texture[0], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
            glBindImageTexture(1, texture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);

            glDispatchCompute(1, 1, 1);
            glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
            EXPECT_GL_NO_ERROR();

            glBindImageTexture(0, texture[1], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
            glBindImageTexture(1, texture[2], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);

            glDispatchCompute(1, 1, 1);
            glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
            EXPECT_GL_NO_ERROR();

            GLuint outputValue;
            GLFramebuffer framebuffer;
            glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
            glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                   texture[2], 0);
            glReadPixels(0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &outputValue);
            EXPECT_GL_NO_ERROR();

            EXPECT_EQ(300u, outputValue);

            glUseProgram(0);
            glDeleteProgram(mProgram);
            ASSERT_GL_NO_ERROR();
        }

      private:
        GLuint mShader;
    };
};

// Test basic functionality of GL_KHR_parallel_shader_compile

// Test to compile and link many programs in parallel.
TEST_P(ParallelShaderCompileTest, LinkAndDrawManyPrograms)
{
    ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable());

    TaskRunner<ClearColorWithDraw> runner;
    runner.run(this);
}

class ParallelShaderCompileTestES31 : public ParallelShaderCompileTest
{};

// Test to compile and link many computing programs in parallel.
TEST_P(ParallelShaderCompileTestES31, LinkAndDispatchManyPrograms)
{
    // Flaky on Win NVIDIA D3D11. http://example.com
    // Suspectable to the flakyness of http://example.com.
    ANGLE_SKIP_TEST_IF(IsWindows() && IsD3D11());

    ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable());

    TaskRunner<ImageLoadStore> runner;
    runner.run(this);
}

ANGLE_INSTANTIATE_TEST(ParallelShaderCompileTest,
                       ES2_D3D9(),
                       ES2_D3D11(),
                       ES2_OPENGL(),
                       ES2_OPENGLES(),
                       ES2_VULKAN());

ANGLE_INSTANTIATE_TEST(ParallelShaderCompileTestES31, ES31_OPENGL(), ES31_OPENGLES(), ES31_D3D11());

}  // namespace


cc::Tensor resnet_branch2(const cc::Tensor& input, int stride, int innum, int outnum, int stage, string part){

	auto right = input;
	right = resnet_conv_block(right, { 3, 3, innum }, stage, part, 2, "a", stride, true);
	right = resnet_conv_block(right, { 3, 3, outnum }, stage, part, 2, "b", 1, false);
	return right;
}

#include <cv.h>
#include <highgui.h>
#include "cc_nb.h"

using namespace cv;
using namespace cc;
using namespace std;

namespace L = cc::layers;

#define InputWidth		32
#define InputHeight		32
#define BatchSize		128

int randr(int low, int high){

	if (low > high) std::swap(low, high);
	return rand() % (high - low + 1) + low;
}

float randr(float low, float high){

	if (low > high) std::swap(low, high);
	return rand() / (float)RAND_MAX * (high - low) + low;
}

int loadCifarImageLabel(const string filepath, const vector<string> filename, vector<Mat>& images, vector<int>& labels) {
	int img_w = 32;
	int img_h = 32;
	int img_c = 3;
	string file;
	for (int i = 0; i < filename.size(); i++) {
		file = (filepath + filename[i]);
		FILE* f = fopen(file.c_str(), "rb");
		if (!f) return 0;
		int num = 10000;
		char lab = 0;
		Mat R(img_w, img_h, CV_8UC1);
		Mat G(img_w, img_h, CV_8UC1);
		Mat B(img_w, img_h, CV_8UC1);
		for (int i = 0; i < num; ++i) {
			fread(&lab, 1, 1, f);
			labels.push_back(lab);
			fread(R.data, 1, img_w * img_h, f);
			fread(G.data, 1, img_w * img_h, f);
			fread(B.data, 1, img_w * img_h, f);
			std::vector<cv::Mat> tmp{ B, G, R };
			cv::Mat bgr;
			cv::merge(tmp, bgr);
			images.emplace_back(bgr);
		}
		fclose(f);
	}
	return 0;
}

void mulMat(const Mat& a, int type, const Mat& b, Mat& d){

	static Mat ms[3];
	split(a, ms);
	for (int i = 0; i < 3; ++i){
		ms[i].convertTo(ms[i], CV_32F);
		ms[i] = ms[i].mul(b);
		ms[i].convertTo(ms[i], CV_MAT_DEPTH(type));
	}
	merge(ms, 3, d);
}

void multi_padding_and_random_crop(const vector<Mat>& ims, float scale_min, float scale_max, int padding, vector<Mat>& outs, int type, float mixup){

	int imbatch = ims.size();
	int imheight = ims[0].rows;
	int imwidth = ims[0].cols;

	bool usemixup = mixup != 1;
	bool usescale = !(scale_min == scale_max && scale_min == 1);
	Mat background = Mat::zeros(imheight + padding * 2, imwidth + padding * 2, type);
	Mat mixup_heatmap = Mat::zeros(imheight, imwidth, CV_32F);
	Mat used_image = Mat::zeros(imheight, imwidth, type);
	Mat mix_image = Mat::zeros(imheight, imwidth, type);

	for (int i = 0; i < imbatch; ++i){

		if (usemixup){
			cv::randu(mixup_heatmap, mixup, 1.0);
			mulMat(ims[i], type, mixup_heatmap, used_image);

			int mixind = rand() % imbatch;
			mulMat(ims[mixind], type, 1 - mixup_heatmap, mix_image);
			used_image += mix_image;

			used_image.copyTo(background(Rect(padding, padding, imwidth, imheight)));
		}
		else{
			ims[i].copyTo(background(Rect(padding, padding, imwidth, imheight)));
		}

		if (usescale){
			if (rand() % 5 != 0){
				float acc = rand() / (float)RAND_MAX;
				float scale = acc * (scale_max - scale_min) + scale_min;

				int uw = imwidth * scale;
				int uh = imheight * scale;
				int rx = background.cols == uw ? 0 : rand() % (background.cols - uw);
				int ry = background.rows == uh ? 0 : rand() % (background.rows - uh);
				Rect roi = Rect(rx, ry, uw, uh) & Rect(0, 0, background.cols, background.rows);
				if (roi.area() > 0){
					resize(background(roi), outs[i], Size(imwidth, imheight));
				}
				else{
					printf("error uw(%d), uh(%d), scale(%f), scale_min(%f), scale_max(%f)\n", uw, uh, scale, scale_min, scale_max);
					return;
				}
			}

			if (rand() % 5 != 0){
				float cutout_acc = rand() / (float)RAND_MAX;
				float cutout_scale = cutout_acc * (scale_max - scale_min) + scale_min;

				int cutout_uw = (imwidth * 0.5) * cutout_scale;
				int cutout_uh = (imheight * 0.5) * cutout_scale;
				int cutout_rx = imwidth == cutout_uw ? 0 : rand() % (imwidth - cutout_uw);
				int cutout_ry = imheight == cutout_uh ? 0 : rand() % (imheight - cutout_uh);
				Rect cutout_roi = Rect(cutout_rx, cutout_ry, cutout_uw, cutout_uh) & Rect(0, 0, imwidth, imheight);
				if (cutout_roi.area() > 0){
					outs[i](cutout_roi).setTo(0);
				}
				else{
					printf("error uw(%d), uh(%d), scale(%f), scale_min(%f), scale_max(%f)", cutout_uw, cutout_uh, cutout_scale, scale_min, scale_max);
					return;
				}
			}
		}
		else{
			int rx = padding == 0 ? 0 : rand() % (padding * 2);
			int ry = padding == 0 ? 0 : rand() % (padding * 2);
			background(Rect(rx, ry, imwidth, imheight)).copyTo(outs[i]);
		}

		if (rand() % 2 == 0)
			flip(outs[i], outs[i], (rand() % 3) - 1);
	}
}

class CifarDataLayer : public BaseLayer {
public:
	SETUP_LAYERFUNC(CifarDataLayer);

	void preperData(int phase) {

		vector<string> img_labelfile;

		if (phase == PhaseTest) {
			img_labelfile.push_back("test_batch.bin");
		}
		else {
			img_labelfile.push_back("data_batch_1.bin");
			img_labelfile.push_back("data_batch_2.bin");
			img_labelfile.push_back("data_batch_3.bin");
			img_labelfile.push_back("data_batch_4.bin");
			img_labelfile.push_back("data_batch_5.bin");
		}
		loadCifarImageLabel("CIFAR_data/", img_labelfile, images_, labels_);
		inds_.resize(images_.size());
		cursor_ = 0;

		for (int i = 0; i < inds_.size(); ++i)
			inds_[i] = i;

		std::random_shuffle(inds_.begin(), inds_.end());
	}

	virtual void forward(Blob** bottom, int numBottom, Blob** top, int numTop) {

		Blob* image = top[0];
		Blob* label = top[1];

		for (int i = 0; i < batch_size_; ++i) {
			int ind = inds_[cursor_++];
			batchLabs_[i] = labels_[ind];
			images_[ind].convertTo(batchImage_[i], CV_32F, 2 / 255.0, -1.0);// , 1 / 255.0, -0.5);

			if (cursor_ == inds_.size()) {
				std::random_shuffle(inds_.begin(), inds_.end());
				cursor_ = 0;
			}
		}

		if (this->phase_ == PhaseTrain){

			multi_padding_and_random_crop(batchImage_, 1, 1, 4, augmentResult_, CV_32FC3, 0.8);
			for (int i = 0; i < batch_size_; ++i) {
				image->setData(i, CVFMat(augmentResult_[i]));
				label->mutable_cpu_data()[i] = batchLabs_[i];
			}
		}
		else{
			for (int i = 0; i < batch_size_; ++i) {
				image->setData(i, CVFMat(batchImage_[i]));
				label->mutable_cpu_data()[i] = batchLabs_[i];
			}
		}
	}

	virtual void setup(const char* name, const char* type, const char* param_str, int phase, Blob** bottom, int numBottom, Blob** top, int numTop) {

		Blob* data = top[0];
		Blob* label = top[1];

		this->phase_ = phase;
		this->batch_size_ = BatchSize;

		data->reshape(batch_size_, 3, InputHeight, InputWidth);
		label->reshape(batch_size_, 1, 1, 1);
		batchImage_.resize(batch_size_);
		batchLabs_.resize(batch_size_);
		augmentResult_.resize(batch_size_);
		preperData(phase);
	}

private:
	vector<Mat> augmentResult_;
	vector<int> batchLabs_;
	vector<Mat> batchImage_;
	vector<int> labels_;
	vector<Mat> images_;
	vector<int> inds_;
	int cursor_;
	int batch_size_;
	int phase_;
};

//第一节单独卷积
cc::Tensor resnet_conv(const cc::Tensor& input, const vector<int>& kernel, const string& name, int stride = 1, bool has_relu = true, bool bias_term = false){

	auto x = L::conv2d(input, kernel, "same", { stride, stride }, { 1, 1 }, name);
	L::OConv2D* layer = (L::OConv2D*)x->owner.get();
	layer->bias_term = bias_term;
	layer->kernel_initializer.reset(new cc::Initializer());
	layer->kernel_initializer->type = "msra";
	layer->bias_initializer.reset();
	layer->bias_mult.reset();

	x = L::batch_norm_only(x, "bn_" + name);
	x = L::scale(x, true, "scale_" + name);
	if (has_relu)
		x = L::relu(x, name + "_relu");
	return x;
}

//残差块卷积模块.构造一个分支卷积模块，左右通用
cc::Tensor resnet_conv_block(const cc::Tensor& input, const vector<int>& kernel, int innum1, string part1, int innum2, string part2, int stride = 1, bool has_relu = true, bool bias_term = false){

	auto x = L::conv2d(input, kernel, "same", { stride, stride }, { 1, 1 }, cc::f("res%d", innum1) + part1 + cc::f("_branch%d%s", innum2, part2.c_str()));
	L::OConv2D* layer = (L::OConv2D*)x->owner.get();
	layer->bias_term = bias_term;
	layer->kernel_initializer.reset(new cc::Initializer());
	layer->kernel_initializer->type = "msra";
	layer->bias_initializer.reset();
	layer->bias_mult.reset();

	x = L::batch_norm_only(x, cc::f("bn%d", innum1) + part1 + cc::f("_branch%d%s", innum2, part2.c_str()));
	x = L::scale(x, true, cc::f("scale%d", innum1) + part1 + cc::f("_branch%d%s", innum2, part2.c_str()));
	if (has_relu)
		x = L::relu(x, cc::f("res%d", innum1) + part1 + cc::f("_branch%d%s_relu", innum2, part2.c_str()));

	return x;
}

//右侧分支模块，由三个卷积模块组成

cc::Tensor resnet_block(const cc::Tensor& input, int n, int stride, int innum, int outnum, int numinner){

	auto x = input;
	{
		auto branch1 = resnet_conv_block(x, { 1, 1, outnum }, n, "a", 1, "", stride, false, false); //构建左侧分支
		auto branch2 = resnet_branch2(x, stride, innum, outnum, n, "a");
		auto out = L::add(branch1, branch2, cc::f("res%da", n));
		x = L::relu(out, cc::f("res%da_relu", n));
	};

	string buff[5] = { "b", "c", "d", "e", "f" };
	for (int i = 0; i < numinner; ++i){
		auto branch1 = x;
		auto branch2 = resnet_branch2(x, 1, innum, outnum, n, buff[i]);
		auto out = L::add(branch1, branch2, cc::f("res%d%s", n, buff[i].c_str()));
		x = L::relu(out, cc::f("res%d%s_relu", n, buff[i].c_str()));
	};
	return x;
}

cc::Tensor resnet50(const cc::Tensor& input, int numunit){

	auto x = input;
	{
		x = resnet_conv(x, { 7, 7, 64 }, "conv1", 2, true, true);
		x = L::max_pooling2d(x, { 3, 3 }, { 2, 2 }, { 0, 0 }, false, "pool1");
	};

	x = resnet_block(x, 2, 1, 64, 256, 2);
	x = resnet_block(x, 3, 2, 128, 512, 3);
	x = resnet_block(x, 4, 2, 256, 1024, 5);
	x = resnet_block(x, 5, 2, 512, 2048, 2);

	x = L::avg_pooling2d(x, { 1, 1 }, { 1, 1 }, { 0, 0 }, true, "pool5");
	x = L::dense(x, numunit, "fc", true);
	return x;
}

cc::Tensor resnet18(const cc::Tensor& input, int numunit){
	auto x = input;
	{
		x = resnet_conv(x, { 3, 3, 16 }, "conv1", 1, true, true);
	};

	x = resnet_block(x, 2, 1, 16, 32, 2);
	x = resnet_block(x, 3, 2, 32, 64, 2);
	x = resnet_block(x, 4, 2, 64, 64, 2);

	x = L::avg_pooling2d(x, { 1, 1 }, { 1, 1 }, { 0, 0 }, true, "pool5");
	x = L::dense(x, numunit, "fc", true);
	L::ODense* denseLayer = (L::ODense*)x->owner.get();
	denseLayer->weight_initializer.reset(new cc::Initializer("msra"));
	denseLayer->bias_initializer.reset(new cc::Initializer("constant", 0));
	return x;
}

bool saveToFile(const string& path, const string& data){

	FILE* f = fopen(path.c_str(), "wb");
	if (!f) return false;

	fwrite(data.data(), 1, data.size(), f);
	fclose(f);
	return true;
}

int main(){

	cc::installRegister();
	INSTALL_LAYER(CifarDataLayer);

	auto data = L::data("CifarDataLayer", {"image", "label"}, "data");
	auto image = data[0];
	auto label = data[1];
	auto x = resnet18(image, 10);
	auto test = cc::metric::classifyAccuracy(x, label, "accuracy");
	auto loss = cc::loss::softmax_cross_entropy(x, label, "loss");

	int trainDatasetSize = 50000;
	int valDatasetSize = 10000;
	int trainEpochs = 150;
	int epochIters = trainDatasetSize / BatchSize;

	cc::engine::caffe::buildGraphToFile(resnet18(L::input({ 1, 3, InputHeight, InputWidth }, "image"), 10), "deploy.prototxt");
	auto op = cc::optimizer::momentumStochasticGradientDescent(cc::learningrate::step(0.1, 0.1, 20*epochIters), 0.9);
	op->test_initialization = false;
	op->test_interval = epochIters;
	op->test_iter = valDatasetSize / BatchSize;
	op->weight_decay = 0.0002f;
	op->average_loss = 1;
	op->max_iter = trainEpochs * epochIters;
	op->display = 10;
	op->device_ids = { 0 };
	op->minimize({ loss, test });

	saveToFile("solver.prototxt", op->seril());
	cc::engine::caffe::buildGraphToFile({ loss, test }, "train.prototxt");

	//setup test classifier callback
	registerOnTestClassificationFunction([](Solver* solver, float testloss, int index, const char* itemname, float accuracy){
		if (strcmp(itemname, "accuracy") == 0){

			static string lastsavedname;
			static float bestAccuracy = 0, bestLoss = 0;
			static int bestIter = 0;
			static string datarootdir = ".";
			if (accuracy >= bestAccuracy){
				if (!lastsavedname.empty())
					remove(lastsavedname.c_str());

				int iter = solver->iter();
				bestIter = iter;
				bestAccuracy = accuracy;
				bestLoss = testloss;

				string savedname = format("%s/saved_iter[%d]_loss[%f]_accuracy[%f].caffemodel", datarootdir.c_str(), iter, testloss, accuracy);
				solver->net()->saveToCaffemodel(savedname.c_str());
				lastsavedname = savedname;

				if (accuracy >= 1){
					printf("accuracy 提前满足要求，退出.\n");
					solver->postEarlyStopSignal();
				}
			}
		}
	});

	cc::train::caffe::run(op, [](OThreadContextSession* session, int step, float smoothed_loss){
	});
	return 0;
}


void AssertConsistent() const {
    if (IsValid()) {
      assert(BB);
      assert(CmpI);
      assert(BranchI);
    }
  }

//===- MergeICmps.cpp - Optimize chains of integer comparisons ------------===//
// This pass turns chains of integer comparisons into memcmp (the memcmp is
// later typically inlined as a chain of efficient hardware comparisons). This
// typically benefits c++ member or nonmember operator==().
// The basic idea is to replace a longer chain of integer comparisons loaded
// from contiguous memory locations into a shorter chain of larger integer
// comparisons. Benefits are double:
//  - There are less jumps, and therefore less opportunities for mispredictions
//    and I-cache misses.
//  - Code size is smaller, both because jumps are removed and because the
//    encoding of a 2*n byte compare is smaller than that of two n-byte
//  struct S {
//    uint16_t d;
//  Is optimized as :
//  Which will later be expanded (ExpandMemCmp) as a single 8-bytes icmp.

#include "llvm/Analysis/Loads.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/Pass.h"
#include "llvm/Transforms/Scalar.h"
#include "llvm/Transforms/Utils/BuildLibCalls.h"
#include <algorithm>
#include <numeric>
#include <utility>
#include <vector>

using namespace llvm;

namespace {

#define DEBUG_TYPE "mergeicmps"

// Returns true if the instruction is a simple load or a simple store
static bool isSimpleLoadOrStore(const Instruction *I) {
  if (const LoadInst *LI = dyn_cast<LoadInst>(I))
    return LI->isSimple();
  if (const StoreInst *SI = dyn_cast<StoreInst>(I))
    return SI->isSimple();
  return false;
}

// A BCE atom "Binary Compare Expression Atom" represents an integer load
// that is a constant offset from a base value, e.g. `a` or `o.c` in the example
// at the top.
struct BCEAtom {
  BCEAtom() = default;
  BCEAtom(GetElementPtrInst *GEP, LoadInst *LoadI, int BaseId, APInt Offset)
      : GEP(GEP), LoadI(LoadI), BaseId(BaseId), Offset(Offset) {}

  // We want to order BCEAtoms by (Base, Offset). However we cannot use
  // the pointer values for Base because these are non-deterministic.
  // To make sure that the sort order is stable, we first assign to each atom
  // base value an index based on its order of appearance in the chain of
  // comparisons. We call this index `BaseOrdering`. For example, for:
  // b gets assigned index 0 and a index 1, because b appears as LHS in block 1,
  // which is before block 2.
  // We then sort by (BaseOrdering[LHS.Base()], LHS.Offset), which is stable.
  bool operator<(const BCEAtom &O) const {
    return BaseId != O.BaseId ? BaseId < O.BaseId : Offset.slt(O.Offset);
  }

  GetElementPtrInst *GEP = nullptr;
  LoadInst *LoadI = nullptr;
  unsigned BaseId = 0;
  APInt Offset;
};

// A class that assigns increasing ids to values in the order in which they are
// seen. See comment in `BCEAtom::operator<()``.
class BaseIdentifier {
public:
  // Returns the id for value `Base`, after assigning one if `Base` has not been
  // seen before.
  int getBaseId(const Value *Base) {
    assert(Base && "invalid base");
    const auto Insertion = BaseToIndex.try_emplace(Base, Order);
    if (Insertion.second)
      ++Order;
    return Insertion.first->second;
  }

private:
  unsigned Order = 1;
  DenseMap<const Value*, int> BaseToIndex;
};

// If this value is a load from a constant offset w.r.t. a base address, and
// there are no other users of the load or address, returns the base address and
// the offset.
BCEAtom visitICmpLoadOperand(Value *const Val, BaseIdentifier &BaseId) {
  auto *const LoadI = dyn_cast<LoadInst>(Val);
  if (!LoadI)
    return {};
  LLVM_DEBUG(dbgs() << "load\n");
  if (LoadI->isUsedOutsideOfBlock(LoadI->getParent())) {
    LLVM_DEBUG(dbgs() << "used outside of block\n");
    return {};
  }
  // Do not optimize atomic loads to non-atomic memcmp
  if (!LoadI->isSimple()) {
    LLVM_DEBUG(dbgs() << "volatile or atomic\n");
    return {};
  }
  Value *const Addr = LoadI->getOperand(0);
  auto *const GEP = dyn_cast<GetElementPtrInst>(Addr);
  if (!GEP)
    return {};
  LLVM_DEBUG(dbgs() << "GEP\n");
  if (GEP->isUsedOutsideOfBlock(LoadI->getParent())) {
    LLVM_DEBUG(dbgs() << "used outside of block\n");
    return {};
  }
  const auto &DL = GEP->getModule()->getDataLayout();
  if (!isDereferenceablePointer(GEP, DL)) {
    LLVM_DEBUG(dbgs() << "not dereferenceable\n");
    // We need to make sure that we can do comparison in any order, so we
    // require memory to be unconditionnally dereferencable.
    return {};
  }
  APInt Offset = APInt(DL.getPointerTypeSizeInBits(GEP->getType()), 0);
  if (!GEP->accumulateConstantOffset(DL, Offset))
    return {};
  return BCEAtom(GEP, LoadI, BaseId.getBaseId(GEP->getPointerOperand()),
                 Offset);
}

// A basic block with a comparison between two BCE atoms, e.g. `a == o.a` in the
// example at the top.
// The block might do extra work besides the atom comparison, in which case
// doesOtherWork() returns true. Under some conditions, the block can be
// split into the atom comparison part and the "other work" part
// Note: the terminology is misleading: the comparison is symmetric, so there
// is no real {l/r}hs. What we want though is to have the same base on the
// left (resp. right), so that we can detect consecutive loads. To ensure this
// we put the smallest atom on the left.
class BCECmpBlock {
 public:
  BCECmpBlock() {}

  BCECmpBlock(BCEAtom L, BCEAtom R, int SizeBits)
      : Lhs_(L), Rhs_(R), SizeBits_(SizeBits) {
    if (Rhs_ < Lhs_) std::swap(Rhs_, Lhs_);
  }

  bool IsValid() const { return Lhs_.BaseId != 0 && Rhs_.BaseId != 0; }

  // Assert the block is consistent: If valid, it should also have
  // non-null members besides Lhs_ and Rhs_.

  const BCEAtom &Lhs() const { return Lhs_; }
  const BCEAtom &Rhs() const { return Rhs_; }
  int SizeBits() const { return SizeBits_; }

  // Returns true if the block does other works besides comparison.
  bool doesOtherWork() const;

  // Returns true if the non-BCE-cmp instructions can be separated from BCE-cmp
  // instructions in the block.
  bool canSplit(AliasAnalysis *AA) const;

  // Return true if this all the relevant instructions in the BCE-cmp-block can
  // be sunk below this instruction. By doing this, we know we can separate the
  // BCE-cmp-block instructions from the non-BCE-cmp-block instructions in the
  bool canSinkBCECmpInst(const Instruction *, DenseSet<Instruction *> &,
                         AliasAnalysis *AA) const;

  // We can separate the BCE-cmp-block instructions and the non-BCE-cmp-block
  // instructions. Split the old block and move all non-BCE-cmp-insts into the
  void split(BasicBlock *NewParent, AliasAnalysis *AA) const;

  // The basic block where this comparison happens.
  BasicBlock *BB = nullptr;
  ICmpInst *CmpI = nullptr;
  // The terminating branch.
  BranchInst *BranchI = nullptr;
  // The block requires splitting.
  bool RequireSplit = false;

private:
  BCEAtom Lhs_;
  BCEAtom Rhs_;
  int SizeBits_ = 0;
};

bool BCECmpBlock::canSinkBCECmpInst(const Instruction *Inst,
                                    DenseSet<Instruction *> &BlockInsts,
                                    AliasAnalysis *AA) const {
  // If this instruction has side effects and its in middle of the BCE cmp block
  // instructions, then bail for now.
  if (Inst->mayHaveSideEffects()) {
    // Bail if this is not a simple load or store
    if (!isSimpleLoadOrStore(Inst))
      return false;
    // Disallow stores that might alias the BCE operands
    MemoryLocation LLoc = MemoryLocation::get(Lhs_.LoadI);
    MemoryLocation RLoc = MemoryLocation::get(Rhs_.LoadI);
    if (isModSet(AA->getModRefInfo(Inst, LLoc)) ||
        isModSet(AA->getModRefInfo(Inst, RLoc)))
        return false;
  }
  // Make sure this instruction does not use any of the BCE cmp block
  // instructions as operand.
  for (auto BI : BlockInsts) {
    if (is_contained(Inst->operands(), BI))
      return false;
  }
  return true;
}

void BCECmpBlock::split(BasicBlock *NewParent, AliasAnalysis *AA) const {
  DenseSet<Instruction *> BlockInsts(
      {Lhs_.GEP, Rhs_.GEP, Lhs_.LoadI, Rhs_.LoadI, CmpI, BranchI});
  llvm::SmallVector<Instruction *, 4> OtherInsts;
  for (Instruction &Inst : *BB) {
    if (BlockInsts.count(&Inst))
      continue;
      assert(canSinkBCECmpInst(&Inst, BlockInsts, AA) &&
             "Split unsplittable block");
    // This is a non-BCE-cmp-block instruction. And it can be separated
    // from the BCE-cmp-block instruction.
    OtherInsts.push_back(&Inst);
  }

  // Do the actual spliting.
  for (Instruction *Inst : reverse(OtherInsts)) {
    Inst->moveBefore(&*NewParent->begin());
  }
}

bool BCECmpBlock::canSplit(AliasAnalysis *AA) const {
  DenseSet<Instruction *> BlockInsts(
      {Lhs_.GEP, Rhs_.GEP, Lhs_.LoadI, Rhs_.LoadI, CmpI, BranchI});
  for (Instruction &Inst : *BB) {
    if (!BlockInsts.count(&Inst)) {
      if (!canSinkBCECmpInst(&Inst, BlockInsts, AA))
        return false;
    }
  }
  return true;
}

bool BCECmpBlock::doesOtherWork() const {
  AssertConsistent();
  // All the instructions we care about in the BCE cmp block.
  DenseSet<Instruction *> BlockInsts(
      {Lhs_.GEP, Rhs_.GEP, Lhs_.LoadI, Rhs_.LoadI, CmpI, BranchI});
  // TODO(courbet): Can we allow some other things ? This is very conservative.
  // We might be able to get away with anything does not have any side
  // effects outside of the basic block.
  // Note: The GEPs and/or loads are not necessarily in the same block.
  for (const Instruction &Inst : *BB) {
    if (!BlockInsts.count(&Inst))
      return true;
  }
  return false;
}

// Visit the given comparison. If this is a comparison between two valid
// BCE atoms, returns the comparison.
BCECmpBlock visitICmp(const ICmpInst *const CmpI,
                      const ICmpInst::Predicate ExpectedPredicate,
                      BaseIdentifier &BaseId) {
  // The comparison can only be used once:
  //  - For intermediate blocks, as a branch condition.
  //  - For the final block, as an incoming value for the Phi.
  // If there are any other uses of the comparison, we cannot merge it with
  // other comparisons as we would create an orphan use of the value.
  if (!CmpI->hasOneUse()) {
    LLVM_DEBUG(dbgs() << "cmp has several uses\n");
    return {};
  }
  if (CmpI->getPredicate() != ExpectedPredicate)
    return {};
  LLVM_DEBUG(dbgs() << "cmp "
                    << (ExpectedPredicate == ICmpInst::ICMP_EQ ? "eq" : "ne")
                    << "\n");
  auto Lhs = visitICmpLoadOperand(CmpI->getOperand(0), BaseId);
  if (!Lhs.BaseId)
    return {};
  auto Rhs = visitICmpLoadOperand(CmpI->getOperand(1), BaseId);
  if (!Rhs.BaseId)
    return {};
  const auto &DL = CmpI->getModule()->getDataLayout();
  return BCECmpBlock(std::move(Lhs), std::move(Rhs),
                     DL.getTypeSizeInBits(CmpI->getOperand(0)->getType()));
}

// Visit the given comparison block. If this is a comparison between two valid
// BCE atoms, returns the comparison.
BCECmpBlock visitCmpBlock(Value *const Val, BasicBlock *const Block,
                          const BasicBlock *const PhiBlock,
                          BaseIdentifier &BaseId) {
  if (Block->empty()) return {};
  auto *const BranchI = dyn_cast<BranchInst>(Block->getTerminator());
  if (!BranchI) return {};
  LLVM_DEBUG(dbgs() << "branch\n");
  if (BranchI->isUnconditional()) {
    // In this case, we expect an incoming value which is the result of the
    // comparison. This is the last link in the chain of comparisons (note
    // that this does not mean that this is the last incoming value, blocks
    // can be reordered).
    auto *const CmpI = dyn_cast<ICmpInst>(Val);
    if (!CmpI) return {};
    LLVM_DEBUG(dbgs() << "icmp\n");
    auto Result = visitICmp(CmpI, ICmpInst::ICMP_EQ, BaseId);
    Result.CmpI = CmpI;
    Result.BranchI = BranchI;
    return Result;
  } else {
    // In this case, we expect a constant incoming value (the comparison is
    const auto *const Const = dyn_cast<ConstantInt>(Val);
    LLVM_DEBUG(dbgs() << "const\n");
    if (!Const->isZero()) return {};
    LLVM_DEBUG(dbgs() << "false\n");
    auto *const CmpI = dyn_cast<ICmpInst>(BranchI->getCondition());
    if (!CmpI) return {};
    LLVM_DEBUG(dbgs() << "icmp\n");
    assert(BranchI->getNumSuccessors() == 2 && "expecting a cond branch");
    BasicBlock *const FalseBlock = BranchI->getSuccessor(1);
    auto Result = visitICmp(
        CmpI, FalseBlock == PhiBlock ? ICmpInst::ICMP_EQ : ICmpInst::ICMP_NE,
        BaseId);
    Result.CmpI = CmpI;
    Result.BranchI = BranchI;
    return Result;
  }
  return {};
}

static inline void enqueueBlock(std::vector<BCECmpBlock> &Comparisons,
                                BCECmpBlock &Comparison) {
  LLVM_DEBUG(dbgs() << "Block '" << Comparison.BB->getName()
                    << "': Found cmp of " << Comparison.SizeBits()
                    << " bits between " << Comparison.Lhs().BaseId << " + "
                    << Comparison.Lhs().Offset << " and "
                    << Comparison.Rhs().BaseId << " + "
                    << Comparison.Rhs().Offset << "\n");
  LLVM_DEBUG(dbgs() << "\n");
  Comparisons.push_back(Comparison);
}

// A chain of comparisons.
class BCECmpChain {
 public:
  BCECmpChain(const std::vector<BasicBlock *> &Blocks, PHINode &Phi,
              AliasAnalysis *AA);

  int size() const { return Comparisons_.size(); }

#ifdef MERGEICMPS_DOT_ON
  void dump() const;
#endif  // MERGEICMPS_DOT_ON

  bool simplify(const TargetLibraryInfo *const TLI, AliasAnalysis *AA);

 private:
  static bool IsContiguous(const BCECmpBlock &First,
                           const BCECmpBlock &Second) {
    return First.Lhs().BaseId == Second.Lhs().BaseId &&
           First.Rhs().BaseId == Second.Rhs().BaseId &&
           First.Lhs().Offset + First.SizeBits() / 8 == Second.Lhs().Offset &&
           First.Rhs().Offset + First.SizeBits() / 8 == Second.Rhs().Offset;
  }

  // Merges the given comparison blocks into one memcmp block and update
  // branches. Comparisons are assumed to be continguous. If NextBBInChain is
  // null, the merged block will link to the phi block.
  void mergeComparisons(ArrayRef<BCECmpBlock> Comparisons,
                        BasicBlock *const NextBBInChain, PHINode &Phi,
                        const TargetLibraryInfo *const TLI, AliasAnalysis *AA);

  PHINode &Phi_;
  std::vector<BCECmpBlock> Comparisons_;
  // The original entry block (before sorting);
  BasicBlock *EntryBlock_;
};

BCECmpChain::BCECmpChain(const std::vector<BasicBlock *> &Blocks, PHINode &Phi,
                         AliasAnalysis *AA)
    : Phi_(Phi) {
  assert(!Blocks.empty() && "a chain should have at least one block");
  // Now look inside blocks to check for BCE comparisons.
  std::vector<BCECmpBlock> Comparisons;
  BaseIdentifier BaseId;
  for (size_t BlockIdx = 0; BlockIdx < Blocks.size(); ++BlockIdx) {
    BasicBlock *const Block = Blocks[BlockIdx];
    assert(Block && "invalid block");
    BCECmpBlock Comparison = visitCmpBlock(Phi.getIncomingValueForBlock(Block),
                                           Block, Phi.getParent(), BaseId);
    Comparison.BB = Block;
    if (!Comparison.IsValid()) {
      LLVM_DEBUG(dbgs() << "chain with invalid BCECmpBlock, no merge.\n");
      return;
    }
    if (Comparison.doesOtherWork()) {
      LLVM_DEBUG(dbgs() << "block '" << Comparison.BB->getName()
                        << "' does extra work besides compare\n");
      if (Comparisons.empty()) {
        // This is the initial block in the chain, in case this block does other
        // work, we can try to split the block and move the irrelevant
        // instructions to the predecessor.
        // If this is not the initial block in the chain, splitting it wont
        // As once split, there will still be instructions before the BCE cmp
        // instructions that do other work in program order, i.e. within the
        // chain before sorting. Unless we can abort the chain at this point
        // and start anew.
        // NOTE: we only handle block with single predecessor for now.
        if (Comparison.canSplit(AA)) {
          LLVM_DEBUG(dbgs()
                     << "Split initial block '" << Comparison.BB->getName()
                     << "' that does extra work besides compare\n");
          Comparison.RequireSplit = true;
          enqueueBlock(Comparisons, Comparison);
        } else {
          LLVM_DEBUG(dbgs()
                     << "ignoring initial block '" << Comparison.BB->getName()
                     << "' that does extra work besides compare\n");
        }
        continue;
      }
      // TODO(courbet): Right now we abort the whole chain. We could be
      // merging only the blocks that don't do other work and resume the
      // chain from there. For example:
      //        do a ton of stuff  //bb4
      // This is:
      //  \            \           \               \
      //   ne           ne          ne              \
      //    \            \           \               v
      // We can only merge the first two comparisons, because bb3* does
      // "other work" (setting some_value to 3).
      // We could still merge bb1 and bb2 though.
      return;
    }
    enqueueBlock(Comparisons, Comparison);
  }

  // It is possible we have no suitable comparison to merge.
  if (Comparisons.empty()) {
    LLVM_DEBUG(dbgs() << "chain with no BCE basic blocks, no merge\n");
    return;
  }
  EntryBlock_ = Comparisons[0].BB;
  Comparisons_ = std::move(Comparisons);
#ifdef MERGEICMPS_DOT_ON
  errs() << "BEFORE REORDERING:\n\n";
  dump();
#endif  // MERGEICMPS_DOT_ON
  // Reorder blocks by LHS. We can do that without changing the
  // semantics because we are only accessing dereferencable memory.
  llvm::sort(Comparisons_,
             [](const BCECmpBlock &LhsBlock, const BCECmpBlock &RhsBlock) {
               return LhsBlock.Lhs() < RhsBlock.Lhs();
             });
#ifdef MERGEICMPS_DOT_ON
  errs() << "AFTER REORDERING:\n\n";
  dump();
#endif  // MERGEICMPS_DOT_ON
}

#ifdef MERGEICMPS_DOT_ON
void BCECmpChain::dump() const {
  errs() << "digraph dag {\n";
  errs() << " graph [bgcolor=transparent];\n";
  errs() << " node [color=black,style=filled,fillcolor=lightyellow];\n";
  errs() << " edge [color=black];\n";
  for (size_t I = 0; I < Comparisons_.size(); ++I) {
    const auto &Comparison = Comparisons_[I];
    errs() << " \"" << I << "\" [label=\"%"
           << Comparison.Lhs().Base()->getName() << " + "
           << Comparison.Lhs().Offset << " == %"
           << Comparison.Rhs().Base()->getName() << " + "
           << Comparison.Rhs().Offset << " (" << (Comparison.SizeBits() / 8)
           << " bytes)\"];\n";
    const Value *const Val = Phi_.getIncomingValueForBlock(Comparison.BB);
    if (I > 0) errs() << " \"" << (I - 1) << "\" -> \"" << I << "\";\n";
    errs() << " \"" << I << "\" -> \"Phi\" [label=\"" << *Val << "\"];\n";
  }
  errs() << " \"Phi\" [label=\"Phi\"];\n";
  errs() << "}\n\n";
}
#endif  // MERGEICMPS_DOT_ON

bool BCECmpChain::simplify(const TargetLibraryInfo *const TLI,
                           AliasAnalysis *AA) {
  // First pass to check if there is at least one merge. If not, we don't do
  // anything and we keep analysis passes intact.
  {
    bool AtLeastOneMerged = false;
    for (size_t I = 1; I < Comparisons_.size(); ++I) {
      if (IsContiguous(Comparisons_[I - 1], Comparisons_[I])) {
        AtLeastOneMerged = true;
        break;
      }
    }
    if (!AtLeastOneMerged) return false;
  }

  // Remove phi references to comparison blocks, they will be rebuilt as we
  // merge the blocks.
  for (const auto &Comparison : Comparisons_) {
    Phi_.removeIncomingValue(Comparison.BB, false);
  }

  // If entry block is part of the chain, we need to make the first block
  // of the chain the new entry block of the function.
  BasicBlock *Entry = &Comparisons_[0].BB->getParent()->getEntryBlock();
  for (size_t I = 1; I < Comparisons_.size(); ++I) {
    if (Entry == Comparisons_[I].BB) {
      BasicBlock *NEntryBB = BasicBlock::Create(Entry->getContext(), "",
                                                Entry->getParent(), Entry);
      BranchInst::Create(Entry, NEntryBB);
      break;
    }
  }

  // Point the predecessors of the chain to the first comparison block (which is
  // the new entry point) and update the entry block of the chain.
  if (EntryBlock_ != Comparisons_[0].BB) {
    EntryBlock_->replaceAllUsesWith(Comparisons_[0].BB);
    EntryBlock_ = Comparisons_[0].BB;
  }

  // Effectively merge blocks.
  int NumMerged = 1;
  for (size_t I = 1; I < Comparisons_.size(); ++I) {
    if (IsContiguous(Comparisons_[I - 1], Comparisons_[I])) {
      ++NumMerged;
    } else {
      // Merge all previous comparisons and start a new merge block.
      mergeComparisons(
          makeArrayRef(Comparisons_).slice(I - NumMerged, NumMerged),
          Comparisons_[I].BB, Phi_, TLI, AA);
      NumMerged = 1;
    }
  }
  mergeComparisons(makeArrayRef(Comparisons_)
                       .slice(Comparisons_.size() - NumMerged, NumMerged),
                   nullptr, Phi_, TLI, AA);

  return true;
}

void BCECmpChain::mergeComparisons(ArrayRef<BCECmpBlock> Comparisons,
                                   BasicBlock *const NextBBInChain,
                                   PHINode &Phi,
                                   const TargetLibraryInfo *const TLI,
                                   AliasAnalysis *AA) {
  assert(!Comparisons.empty());
  const auto &FirstComparison = *Comparisons.begin();
  BasicBlock *const BB = FirstComparison.BB;
  LLVMContext &Context = BB->getContext();

  if (Comparisons.size() >= 2) {
    // If there is one block that requires splitting, we do it now, i.e.
    // just before we know we will collapse the chain. The instructions
    // can be executed before any of the instructions in the chain.
    auto C = std::find_if(Comparisons.begin(), Comparisons.end(),
                          [](const BCECmpBlock &B) { return B.RequireSplit; });
    if (C != Comparisons.end())
      C->split(EntryBlock_, AA);

    LLVM_DEBUG(dbgs() << "Merging " << Comparisons.size() << " comparisons\n");
    const auto TotalSize =
        std::accumulate(Comparisons.begin(), Comparisons.end(), 0,
                        [](int Size, const BCECmpBlock &C) {
                          return Size + C.SizeBits();
                        }) /
        8;

    // Incoming edges do not need to be updated, and both GEPs are already
    // computing the right address, we just need to:
    //   - replace the two loads and the icmp with the memcmp
    //   - update the branch
    //   - update the incoming values in the phi.
    FirstComparison.BranchI->eraseFromParent();
    FirstComparison.CmpI->eraseFromParent();
    FirstComparison.Lhs().LoadI->eraseFromParent();
    FirstComparison.Rhs().LoadI->eraseFromParent();

    IRBuilder<> Builder(BB);
    const auto &DL = Phi.getModule()->getDataLayout();
    Value *const MemCmpCall = emitMemCmp(
        FirstComparison.Lhs().GEP, FirstComparison.Rhs().GEP,
        ConstantInt::get(DL.getIntPtrType(Context), TotalSize),
        Builder, DL, TLI);
    Value *const MemCmpIsZero = Builder.CreateICmpEQ(
        MemCmpCall, ConstantInt::get(Type::getInt32Ty(Context), 0));

    // Add a branch to the next basic block in the chain.
    if (NextBBInChain) {
      Builder.CreateCondBr(MemCmpIsZero, NextBBInChain, Phi.getParent());
      Phi.addIncoming(ConstantInt::getFalse(Context), BB);
    } else {
      Builder.CreateBr(Phi.getParent());
      Phi.addIncoming(MemCmpIsZero, BB);
    }

    // Delete merged blocks.
    for (size_t I = 1; I < Comparisons.size(); ++I) {
      BasicBlock *CBB = Comparisons[I].BB;
      CBB->replaceAllUsesWith(BB);
      CBB->eraseFromParent();
    }
  } else {
    assert(Comparisons.size() == 1);
    // There are no blocks to merge, but we still need to update the branches.
    LLVM_DEBUG(dbgs() << "Only one comparison, updating branches\n");
    if (NextBBInChain) {
      if (FirstComparison.BranchI->isConditional()) {
        LLVM_DEBUG(dbgs() << "conditional -> conditional\n");
        // Just update the "true" target, the "false" target should already be
        // the phi block.
        assert(FirstComparison.BranchI->getSuccessor(1) == Phi.getParent());
        FirstComparison.BranchI->setSuccessor(0, NextBBInChain);
        Phi.addIncoming(ConstantInt::getFalse(Context), BB);
      } else {
        LLVM_DEBUG(dbgs() << "unconditional -> conditional\n");
        // Replace the unconditional branch by a conditional one.
        FirstComparison.BranchI->eraseFromParent();
        IRBuilder<> Builder(BB);
        Builder.CreateCondBr(FirstComparison.CmpI, NextBBInChain,
                             Phi.getParent());
        Phi.addIncoming(FirstComparison.CmpI, BB);
      }
    } else {
      if (FirstComparison.BranchI->isConditional()) {
        LLVM_DEBUG(dbgs() << "conditional -> unconditional\n");
        // Replace the conditional branch by an unconditional one.
        FirstComparison.BranchI->eraseFromParent();
        IRBuilder<> Builder(BB);
        Builder.CreateBr(Phi.getParent());
        Phi.addIncoming(FirstComparison.CmpI, BB);
      } else {
        LLVM_DEBUG(dbgs() << "unconditional -> unconditional\n");
        Phi.addIncoming(FirstComparison.CmpI, BB);
      }
    }
  }
}

std::vector<BasicBlock *> getOrderedBlocks(PHINode &Phi,
                                           BasicBlock *const LastBlock,
                                           int NumBlocks) {
  // Walk up from the last block to find other blocks.
  std::vector<BasicBlock *> Blocks(NumBlocks);
  assert(LastBlock && "invalid last block");
  BasicBlock *CurBlock = LastBlock;
  for (int BlockIndex = NumBlocks - 1; BlockIndex > 0; --BlockIndex) {
    if (CurBlock->hasAddressTaken()) {
      // Somebody is jumping to the block through an address, all bets are
      LLVM_DEBUG(dbgs() << "skip: block " << BlockIndex
                        << " has its address taken\n");
      return {};
    }
    Blocks[BlockIndex] = CurBlock;
    auto *SinglePredecessor = CurBlock->getSinglePredecessor();
    if (!SinglePredecessor) {
      // The block has two or more predecessors.
      LLVM_DEBUG(dbgs() << "skip: block " << BlockIndex
                        << " has two or more predecessors\n");
      return {};
    }
    if (Phi.getBasicBlockIndex(SinglePredecessor) < 0) {
      // The block does not link back to the phi.
      LLVM_DEBUG(dbgs() << "skip: block " << BlockIndex
                        << " does not link back to the phi\n");
      return {};
    }
    CurBlock = SinglePredecessor;
  }
  Blocks[0] = CurBlock;
  return Blocks;
}

bool processPhi(PHINode &Phi, const TargetLibraryInfo *const TLI,
                AliasAnalysis *AA) {
  LLVM_DEBUG(dbgs() << "processPhi()\n");
  if (Phi.getNumIncomingValues() <= 1) {
    LLVM_DEBUG(dbgs() << "skip: only one incoming value in phi\n");
    return false;
  }
  // We are looking for something that has the following structure:
  //     \            \           \               \
  //      ne           ne          ne              \
  //       \            \           \               v
  //  - The last basic block (bb4 here) must branch unconditionally to bb_phi.
  //    It's the only block that contributes a non-constant value to the Phi.
  //  - All other blocks (b1, b2, b3) must have exactly two successors, one of
  //    them being the phi block.
  //  - All intermediate blocks (bb2, bb3) must have only one predecessor.
  //  - Blocks cannot do other work besides the comparison, see doesOtherWork()

  // The blocks are not necessarily ordered in the phi, so we start from the
  // last block and reconstruct the order.
  BasicBlock *LastBlock = nullptr;
  for (unsigned I = 0; I < Phi.getNumIncomingValues(); ++I) {
    if (isa<ConstantInt>(Phi.getIncomingValue(I))) continue;
    if (LastBlock) {
      // There are several non-constant values.
      LLVM_DEBUG(dbgs() << "skip: several non-constant values\n");
      return false;
    }
    if (!isa<ICmpInst>(Phi.getIncomingValue(I)) ||
        cast<ICmpInst>(Phi.getIncomingValue(I))->getParent() !=
            Phi.getIncomingBlock(I)) {
      // Non-constant incoming value is not from a cmp instruction or not
      // produced by the last block. We could end up processing the value
      // producing block more than once.
      // This is an uncommon case, so we bail.
      LLVM_DEBUG(
          dbgs()
          << "skip: non-constant value not from cmp or not from last block.\n");
      return false;
    }
    LastBlock = Phi.getIncomingBlock(I);
  }
  if (!LastBlock) {
    // There is no non-constant block.
    LLVM_DEBUG(dbgs() << "skip: no non-constant block\n");
    return false;
  }
  if (LastBlock->getSingleSuccessor() != Phi.getParent()) {
    LLVM_DEBUG(dbgs() << "skip: last block non-phi successor\n");
    return false;
  }

  const auto Blocks =
      getOrderedBlocks(Phi, LastBlock, Phi.getNumIncomingValues());
  if (Blocks.empty()) return false;
  BCECmpChain CmpChain(Blocks, Phi, AA);

  if (CmpChain.size() < 2) {
    LLVM_DEBUG(dbgs() << "skip: only one compare block\n");
    return false;
  }

  return CmpChain.simplify(TLI, AA);
}

class MergeICmps : public FunctionPass {
 public:
  static char ID;

  MergeICmps() : FunctionPass(ID) {
    initializeMergeICmpsPass(*PassRegistry::getPassRegistry());
  }

  bool runOnFunction(Function &F) override {
    if (skipFunction(F)) return false;
    const auto &TLI = getAnalysis<TargetLibraryInfoWrapperPass>().getTLI();
    const auto &TTI = getAnalysis<TargetTransformInfoWrapperPass>().getTTI(F);
    AliasAnalysis *AA = &getAnalysis<AAResultsWrapperPass>().getAAResults();
    auto PA = runImpl(F, &TLI, &TTI, AA);
    return !PA.areAllPreserved();
  }

 private:
  void getAnalysisUsage(AnalysisUsage &AU) const override {
    AU.addRequired<TargetLibraryInfoWrapperPass>();
    AU.addRequired<TargetTransformInfoWrapperPass>();
    AU.addRequired<AAResultsWrapperPass>();
  }

  PreservedAnalyses runImpl(Function &F, const TargetLibraryInfo *TLI,
                            const TargetTransformInfo *TTI, AliasAnalysis *AA);
};

PreservedAnalyses MergeICmps::runImpl(Function &F, const TargetLibraryInfo *TLI,
                                      const TargetTransformInfo *TTI,
                                      AliasAnalysis *AA) {
  LLVM_DEBUG(dbgs() << "MergeICmpsPass: " << F.getName() << "\n");

  // We only try merging comparisons if the target wants to expand memcmp later.
  // The rationale is to avoid turning small chains into memcmp calls.
  if (!TTI->enableMemCmpExpansion(true)) return PreservedAnalyses::all();

  // If we don't have memcmp avaiable we can't emit calls to it.
  if (!TLI->has(LibFunc_memcmp))
    return PreservedAnalyses::all();

  bool MadeChange = false;

  for (auto BBIt = ++F.begin(); BBIt != F.end(); ++BBIt) {
    // A Phi operation is always first in a basic block.
    if (auto *const Phi = dyn_cast<PHINode>(&*BBIt->begin()))
      MadeChange |= processPhi(*Phi, TLI, AA);
  }

  if (MadeChange) return PreservedAnalyses::none();
  return PreservedAnalyses::all();
}

}  // namespace

char MergeICmps::ID = 0;
INITIALIZE_PASS_BEGIN(MergeICmps, "mergeicmps",
                      "Merge contiguous icmps into a memcmp", false, false)
INITIALIZE_PASS_DEPENDENCY(TargetLibraryInfoWrapperPass)
INITIALIZE_PASS_DEPENDENCY(TargetTransformInfoWrapperPass)
INITIALIZE_PASS_DEPENDENCY(AAResultsWrapperPass)
INITIALIZE_PASS_END(MergeICmps, "mergeicmps",
                    "Merge contiguous icmps into a memcmp", false, false)

Pass *llvm::createMergeICmpsPass() { return new MergeICmps(); }


try { env->CheckVersion(8); }
  catch (const AvisynthError&) { has_at_least_v8 = false; }

// Make a motion compensate temporal denoiser
// Author: Manao

// See legal notice in Copying.txt for more information

#include "MVSCDetection.h"
#include "CopyCode.h"
#include "avs/minmax.h"
#include <vector>

MVSCDetection::MVSCDetection(PClip _child, PClip vectors, float Ysc, sad_t nSCD1, int nSCD2, bool isse, IScriptEnvironment* env) :
GenericVideoFilter(_child),
mvClip(vectors, nSCD1, nSCD2, env, 1, 0),
MVFilter(vectors, "MSCDetection", env, 1, 0)
{
  has_at_least_v8 = true;

  if (pixelsize == 4) {
    if (Ysc < 0) // default if not provided: -9999
      sceneChangeValue_f = 1.0f;
    else
      sceneChangeValue_f = clamp(Ysc, 0.0f, 1.0f); // (Ysc < 0) ? 0 : ((Ysc > 255) ? 255 : Ysc);
  }
  else {
    if (Ysc < 0)
      sceneChangeValue = (1 << bits_per_pixel) - 1;
    else
      sceneChangeValue = clamp(int(Ysc), 0, (1 << bits_per_pixel) - 1);
  }
}

MVSCDetection::~MVSCDetection()
{
}

PVideoFrame __stdcall MVSCDetection::GetFrame(int n, IScriptEnvironment* env)
{
   PVideoFrame dst = env->NewVideoFrame(vi); // no frame props here

  PVideoFrame mvn = mvClip.GetFrame(n, env);
   mvClip.Update(mvn, env);

   if ( mvClip.IsUsable() )
  {
     if((vi.IsYUV() || vi.IsYUVA()) && !vi.IsYUY2())
     {
      MemZoneSet(dst->GetWritePtr(PLANAR_Y), 0, dst->GetRowSize(PLANAR_Y), dst->GetHeight(PLANAR_Y), 0, 0, dst->GetPitch(PLANAR_Y));
      MemZoneSet(dst->GetWritePtr(PLANAR_U), 0, dst->GetRowSize(PLANAR_U), dst->GetHeight(PLANAR_U), 0, 0, dst->GetPitch(PLANAR_U));
      MemZoneSet(dst->GetWritePtr(PLANAR_V), 0, dst->GetRowSize(PLANAR_V), dst->GetHeight(PLANAR_V), 0, 0, dst->GetPitch(PLANAR_V));
     }
     else
     {
      MemZoneSet(dst->GetWritePtr(), 0, dst->GetRowSize(), dst->GetHeight(), 0, 0, dst->GetPitch());
     }
  }
   else {
     if((vi.IsYUV() || vi.IsYUVA()) && !vi.IsYUY2())
     {
       if (pixelsize == 1) {
         MemZoneSet(dst->GetWritePtr(PLANAR_Y), sceneChangeValue, dst->GetRowSize(PLANAR_Y), dst->GetHeight(PLANAR_Y), 0, 0, dst->GetPitch(PLANAR_Y));
         MemZoneSet(dst->GetWritePtr(PLANAR_U), sceneChangeValue, dst->GetRowSize(PLANAR_U), dst->GetHeight(PLANAR_U), 0, 0, dst->GetPitch(PLANAR_U));
         MemZoneSet(dst->GetWritePtr(PLANAR_V), sceneChangeValue, dst->GetRowSize(PLANAR_V), dst->GetHeight(PLANAR_V), 0, 0, dst->GetPitch(PLANAR_V));
       }
       else if (pixelsize == 2) {
         fill_plane<uint16_t>(dst->GetWritePtr(PLANAR_Y), dst->GetHeight(PLANAR_Y), dst->GetPitch(PLANAR_Y), sceneChangeValue);
         fill_plane<uint16_t>(dst->GetWritePtr(PLANAR_U), dst->GetHeight(PLANAR_U), dst->GetPitch(PLANAR_U), sceneChangeValue);
         fill_plane<uint16_t>(dst->GetWritePtr(PLANAR_V), dst->GetHeight(PLANAR_V), dst->GetPitch(PLANAR_V), sceneChangeValue);
       }
       else if (pixelsize == 4) {
         fill_plane<float>(dst->GetWritePtr(PLANAR_Y), dst->GetHeight(PLANAR_Y), dst->GetPitch(PLANAR_Y), sceneChangeValue_f);
         fill_plane<float>(dst->GetWritePtr(PLANAR_U), dst->GetHeight(PLANAR_U), dst->GetPitch(PLANAR_U), sceneChangeValue_f);
         fill_plane<float>(dst->GetWritePtr(PLANAR_V), dst->GetHeight(PLANAR_V), dst->GetPitch(PLANAR_V), sceneChangeValue_f);
       }
     }
     else
     {
      MemZoneSet(dst->GetWritePtr(), sceneChangeValue, dst->GetRowSize(), dst->GetHeight(), 0, 0, dst->GetPitch());
     }
   }

  return dst;
}