<commit_before>/*
 * PrimeSieveGUI.cpp -- This file is part of primesieve
 *
 * Copyright (C) 2012 Kim Walisch, <kim.walisch@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */

#include "PrimeSieveGUI.h"
#include "ui_PrimeSieveGUI.h"
#include "PrimeSieveProcess.h"
#include "../soe/ParallelPrimeSieve.h"
#include "../expr/ExpressionParser.h"

#include <QtGlobal>
#include <QCoreApplication>
#include <QByteArray>
#include <QTextStream>
#include <QFile>
#include <QSize>
#include <QMessageBox>
#include <QTextCursor>
#include <stdexcept>

PrimeSieveGUI::PrimeSieveGUI(QWidget *parent) :
  QMainWindow(parent), ui(new Ui::PrimeSieveGUI), validator_(0),
  primeSieveProcess_(0), saveAct_(0), quitAct_(0), aboutAct_(0),
  alignmentGroup_(0) {
  ui->setupUi(this);
  primeText_.push_back("Prime numbers");
  primeText_.push_back("Twin primes");
  primeText_.push_back("Prime triplets");
  primeText_.push_back("Prime quadruplets");
  primeText_.push_back("Prime quintuplets");
  primeText_.push_back("Prime sextuplets");
  primeText_.push_back("Prime septuplets");
  this->initGUI();
  this->initConnections();
}

PrimeSieveGUI::~PrimeSieveGUI() {
  this->cleanUp();
  delete validator_;
  delete saveAct_;
  delete quitAct_;
  delete aboutAct_;
  delete alignmentGroup_;
  for (; !countAct_.isEmpty(); countAct_.pop_back())
    delete countAct_.back();
  for (; !printAct_.isEmpty(); printAct_.pop_back())
    delete printAct_.back();
  // Qt code
  delete ui;
}

void PrimeSieveGUI::changeEvent(QEvent *e) {
  QMainWindow::changeEvent(e);
  switch (e->type()) {
  case QEvent::LanguageChange:
    ui->retranslateUi(this);
    break;
  default:
    break;
  }
}

void PrimeSieveGUI::initGUI() {
  this->setWindowTitle(APPLICATION_NAME + " " + APPLICATION_VERSION);
  this->createMenu(primeText_);

  // fill the sieveSizeComboBox with power of 2 values <= "4096 KB"
  for (int i = MINIMUM_SIEVE_SIZE; i <= MAXIMUM_SIEVE_SIZE; i *= 2)
    ui->sieveSizeComboBox->addItem(QString::number(i) + " KB");
  this->setTo(ui->sieveSizeComboBox, DEFAULT_SIEVE_SIZE);

  // fill the threadsComboBox with power of 2 values <= maxThreads_
  maxThreads_ = ParallelPrimeSieve::getMaxThreads();
  for (int i = 1; i < maxThreads_; i *= 2)
    ui->threadsComboBox->addItem(QString::number(i));
  ui->threadsComboBox->addItem(QString::number(maxThreads_));
  this->setTo(ui->threadsComboBox, "1");

  // set an ideal ComboBox width
  int width = ui->sieveSizeComboBox->minimumSizeHint().width();
  ui->sieveSizeComboBox->setFixedWidth(width);
  ui->threadsComboBox->setFixedWidth(width);

  // set a nice GUI size
  QSize size = this->sizeHint();
  size.setWidth(this->minimumSizeHint().width());
#if defined(Q_OS_WIN)
  size.setHeight(size.height() - size.height() / 10);
#endif
  this->resize(size);

  // limit input for arithmetic expressions
  QRegExp rx("[0-9\\+\\-\\*\\/\\%\\^\\(\\)\\e\\E]*");
  validator_ = new QRegExpValidator(rx, this);
  ui->lowerBoundLineEdit->setValidator(validator_);
  ui->upperBoundLineEdit->setValidator(validator_);
}

void PrimeSieveGUI::initConnections() {
  connect(&progressBarTimer_,     SIGNAL(timeout()),                    this, SLOT(advanceProgressBar()));
  connect(ui->lowerBoundLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(autoSetThreads()));
  connect(ui->upperBoundLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(autoSetThreads()));
  connect(ui->autoSetCheckBox,    SIGNAL(toggled(bool)),                this, SLOT(autoSetThreads()));
  connect(saveAct_,               SIGNAL(triggered()),                  this, SLOT(saveToFile()));
  connect(quitAct_,               SIGNAL(triggered()),                  qApp, SLOT(closeAllWindows()));
  connect(alignmentGroup_,        SIGNAL(triggered(QAction*)),          this, SLOT(printMenuClicked(QAction*)));
  connect(aboutAct_,              SIGNAL(triggered()),                  this, SLOT(showAboutDialog()));
}

/**
 * Get the sieve size in kilobytes from the sieveSizeComboBox.
 * @post sieveSize >= 1 && sieveSize <= 4096.
 */
int PrimeSieveGUI::getSieveSize() {
  QString sieveSize(ui->sieveSizeComboBox->currentText());
  // remove " KB"
  sieveSize.chop(3);
  return sieveSize.toInt();
}

/**
 * Get the number of threads from the threadsComboBox.
 */
int PrimeSieveGUI::getThreads() {
  return ui->threadsComboBox->currentText().toInt();
}

quint64 PrimeSieveGUI::getNumber(const QString& str) {
  if (str.isEmpty())
    throw std::invalid_argument("Please enter a lower and upper bound for prime sieving.");

  ExpressionParser<quint64> parser;
  quint64 result = 0;
  try {
    result = parser.eval(str.toAscii().data());
  }
  catch (parser_error& e) {
    throw std::invalid_argument((("\"" + e.expression()) + "\"\n") + e.what());
  }

  int digits = str.count(QRegExp("[0-9]"));
  if (result >= UPPER_BOUND_LIMIT || (
      digits == str.size() && (
      digits >  UPPER_BOUND_STR.size() || (
      digits == UPPER_BOUND_STR.size() &&
      str    >= UPPER_BOUND_STR))))
    throw std::invalid_argument("Please use positive integers < 2^64 - 2^32*10.");

  return result;
}

void PrimeSieveGUI::setTo(QComboBox* comboBox, const QString& text) {
  comboBox->setCurrentIndex(comboBox->findText(text));
}

/**
 * If "Auto set" is enabled set an ideal number of threads for the
 * current lower bound, upper bound in the threadsComboBox.
 */
void PrimeSieveGUI::autoSetThreads() {
  if (ui->autoSetCheckBox->isEnabled() && ui->autoSetCheckBox->isChecked()) {
    try {
      quint64 lowerBound = this->getNumber(ui->lowerBoundLineEdit->text());
      quint64 upperBound = this->getNumber(ui->upperBoundLineEdit->text());
      ParallelPrimeSieve pps;
      pps.setStart(lowerBound);
      pps.setStop(upperBound);
      int idealNumThreads = pps.getNumThreads();
      if (idealNumThreads < maxThreads_) {
        // floor to the next power of 2 value
        int p = 1;
        for (; p <= idealNumThreads; p *= 2)
          ;
        idealNumThreads = p / 2;
      }
      this->setTo(ui->threadsComboBox, QString::number(idealNumThreads));
    } catch (...) {
      this->setTo(ui->threadsComboBox, "1");
    }
  }
}

/**
 * The user has chosen a custom number of threads, disable "Auto set".
 */
void PrimeSieveGUI::on_threadsComboBox_activated() {
  ui->autoSetCheckBox->setChecked(false);
}

/**
 * Start sieving primes.
 */
void PrimeSieveGUI::on_sieveButton_clicked() {
  // invert buttons, reset upon cleanUp()
  ui->sieveButton->setDisabled(true);
  ui->cancelButton->setEnabled(true);
  try {
    flags_ = this->getMenuSettings() | CALCULATE_STATUS;
    if ((flags_ & (COUNT_FLAGS | PRINT_FLAGS)) == 0)
      throw std::invalid_argument("Nothing to do, no count or print options selected.");

    quint64 lowerBound = this->getNumber(ui->lowerBoundLineEdit->text());
    quint64 upperBound = this->getNumber(ui->upperBoundLineEdit->text());
    if (lowerBound > upperBound)
      throw std::invalid_argument("The lower bound must not be greater than the upper bound.");

    // reset the GUI widgets
    ui->progressBar->setValue(ui->progressBar->minimum());
    ui->textEdit->clear();
    progressBarTimer_.start(25);

    // start a new process for sieving (avoids cancel
    // trouble with multiple threads)
    primeSieveProcess_ = new PrimeSieveProcess(this);
    if (flags_ & PRINT_FLAGS)
      connect(primeSieveProcess_, SIGNAL(readyReadStandardOutput()),
          this, SLOT(printProcessOutput()));
    connect(primeSieveProcess_, SIGNAL(finished(int, QProcess::ExitStatus)),
        this, SLOT(processFinished(int, QProcess::ExitStatus)));
    primeSieveProcess_->start(lowerBound, upperBound, this->getSieveSize(),
        flags_, this->getThreads());

  } catch (std::invalid_argument& ex) {
    this->cleanUp();
    QMessageBox::warning(this, APPLICATION_NAME, ex.what());
  } catch (std::exception& ex) {
    this->cleanUp();
    QMessageBox::critical(this, APPLICATION_NAME, ex.what());
  }
}

void PrimeSieveGUI::advanceProgressBar() {
  int permil = static_cast<int>(primeSieveProcess_->getStatus() * 10.0);
  ui->progressBar->setValue(permil);
}

/**
 * Redirects the standard output (prime numbers or prime k-tuplets) of
 * the primeSieveProcess_ to the TextEdit.
 */
void PrimeSieveGUI::printProcessOutput() {
  QByteArray buffer;
  buffer.reserve(PRINT_BUFFER_SIZE + 256);
  while (ui->cancelButton->isEnabled() && primeSieveProcess_->canReadLine()) {
    buffer.clear();
    while (primeSieveProcess_->canReadLine() && buffer.size() < PRINT_BUFFER_SIZE)
      buffer.append(primeSieveProcess_->readLine(256));
    // remove "\r\n" or '\n', '\r' at the back
    while (buffer.endsWith('\n') ||
           buffer.endsWith('\r'))
      buffer.chop(1);
    if (!buffer.isEmpty())
      ui->textEdit->appendPlainText(buffer);
/// Keep the GUI responsive.
/// @warning QApplication::processEvents() must not be used on
///          operating systems that use signal recursion (like Linux
///          X11) otherwise the stack will explode!
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
    QApplication::processEvents();
#else
    ui->textEdit->repaint();
#endif
  }
}

/**
 * Is executed when the primeSieveProcess_ finishes, checks for
 * process errors and calls this->printResults().
 */
void PrimeSieveGUI::processFinished(int exitCode,
    QProcess::ExitStatus exitStatus) {
  // the process did not exit normally, i.e. threw and exception
  if (exitCode != 0) {
    // Qt uses '/' internally, even for Windows
    QString path = QCoreApplication::applicationDirPath() + "/" + APPLICATION_NAME + "_error.txt";
    QFile error_log(path);
    if (error_log.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
      QTextStream out(&error_log);
      out << primeSieveProcess_->readAllStandardError();
      error_log.close();
    }
    this->cleanUp();
    QMessageBox::critical(this, APPLICATION_NAME,
        "The PrimeSieveProcess reported an error (see primesieve_error.txt), sieving has been aborted.");
  }
  // the PrimeSieveProcess has been interrupted by a signal (SIGTERM,
  // SIGKILL, ...) or a segmentation fault
  else if (exitStatus == QProcess::CrashExit) {
    this->cleanUp();
    QMessageBox::critical(this, APPLICATION_NAME, "The PrimeSieveProcess crashed, sieving has been aborted.");
  }
  // the PrimeSieveProcess has finished correctly
  else {
    ui->progressBar->setValue(ui->progressBar->maximum());
    // print results if not canceled lately
    if (ui->cancelButton->isEnabled())
      this->printResults();
    this->cleanUp();
  }
}

/**
 * Print the sieving results.
 */
void PrimeSieveGUI::printResults() {
  if (!ui->textEdit->toPlainText().isEmpty())
    ui->textEdit->appendPlainText("");

  // hack to get the count results aligned using tabs
  QString maxSizeText;
  for (int i = 0; i < 7; i++) {
    if ((flags_ & (COUNT_PRIMES << i)) && maxSizeText.size() < primeText_[i].size())
      maxSizeText = primeText_[i];
  }
  ui->textEdit->insertPlainText(maxSizeText + ": ");
  int maxWidth = ui->textEdit->cursorRect().left();
  ui->textEdit->moveCursor(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
  ui->textEdit->textCursor().removeSelectedText();
  ui->textEdit->setTabStopWidth(maxWidth);

  // print prime counts & time elapsed
  for (int i = 0; i < 7; i++) {
    if (flags_ & (COUNT_PRIMES << i))
      ui->textEdit->appendPlainText(primeText_[i] + ":\t" + QString::number(primeSieveProcess_->getCounts(i)));
  }
  if (flags_ & COUNT_KTUPLETS)
    ui->textEdit->appendPlainText("");
  QString time("Elapsed time:\t" + QString::number(primeSieveProcess_->getSeconds(), 'f', 2) + " sec");
  ui->textEdit->appendPlainText(time);
}

/**
 * Cancel sieving.
 */
void PrimeSieveGUI::on_cancelButton_clicked() {
  ui->cancelButton->setDisabled(true);
  ui->progressBar->setValue(0);
  // too late to abort
  if ((flags_ & PRINT_FLAGS) && primeSieveProcess_->isFinished())
    return;
  this->cleanUp();
}

/**
 * Clean up after sieving is finished or canceled (abort the
 * PrimeSieveProcess if still running).
 */
void PrimeSieveGUI::cleanUp() {
  progressBarTimer_.stop();
  if (primeSieveProcess_ != 0)
    delete primeSieveProcess_;
  primeSieveProcess_ = 0;
  // invert buttons
  ui->cancelButton->setDisabled(true);
  ui->sieveButton->setEnabled(true);
  // force repainting widgets
  this->repaint();
}
<commit_msg>use 3.6 error message<commit_after>/*
 * PrimeSieveGUI.cpp -- This file is part of primesieve
 *
 * Copyright (C) 2012 Kim Walisch, <kim.walisch@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */

#include "PrimeSieveGUI.h"
#include "ui_PrimeSieveGUI.h"
#include "PrimeSieveProcess.h"
#include "../soe/ParallelPrimeSieve.h"
#include "../expr/ExpressionParser.h"

#include <QtGlobal>
#include <QCoreApplication>
#include <QByteArray>
#include <QTextStream>
#include <QFile>
#include <QSize>
#include <QMessageBox>
#include <QTextCursor>
#include <stdexcept>

PrimeSieveGUI::PrimeSieveGUI(QWidget *parent) :
  QMainWindow(parent), ui(new Ui::PrimeSieveGUI), validator_(0),
  primeSieveProcess_(0), saveAct_(0), quitAct_(0), aboutAct_(0),
  alignmentGroup_(0) {
  ui->setupUi(this);
  primeText_.push_back("Prime numbers");
  primeText_.push_back("Twin primes");
  primeText_.push_back("Prime triplets");
  primeText_.push_back("Prime quadruplets");
  primeText_.push_back("Prime quintuplets");
  primeText_.push_back("Prime sextuplets");
  primeText_.push_back("Prime septuplets");
  this->initGUI();
  this->initConnections();
}

PrimeSieveGUI::~PrimeSieveGUI() {
  this->cleanUp();
  delete validator_;
  delete saveAct_;
  delete quitAct_;
  delete aboutAct_;
  delete alignmentGroup_;
  for (; !countAct_.isEmpty(); countAct_.pop_back())
    delete countAct_.back();
  for (; !printAct_.isEmpty(); printAct_.pop_back())
    delete printAct_.back();
  // Qt code
  delete ui;
}

void PrimeSieveGUI::changeEvent(QEvent *e) {
  QMainWindow::changeEvent(e);
  switch (e->type()) {
  case QEvent::LanguageChange:
    ui->retranslateUi(this);
    break;
  default:
    break;
  }
}

void PrimeSieveGUI::initGUI() {
  this->setWindowTitle(APPLICATION_NAME + " " + APPLICATION_VERSION);
  this->createMenu(primeText_);

  // fill the sieveSizeComboBox with power of 2 values <= "4096 KB"
  for (int i = MINIMUM_SIEVE_SIZE; i <= MAXIMUM_SIEVE_SIZE; i *= 2)
    ui->sieveSizeComboBox->addItem(QString::number(i) + " KB");
  this->setTo(ui->sieveSizeComboBox, DEFAULT_SIEVE_SIZE);

  // fill the threadsComboBox with power of 2 values <= maxThreads_
  maxThreads_ = ParallelPrimeSieve::getMaxThreads();
  for (int i = 1; i < maxThreads_; i *= 2)
    ui->threadsComboBox->addItem(QString::number(i));
  ui->threadsComboBox->addItem(QString::number(maxThreads_));
  this->setTo(ui->threadsComboBox, "1");

  // set an ideal ComboBox width
  int width = ui->sieveSizeComboBox->minimumSizeHint().width();
  ui->sieveSizeComboBox->setFixedWidth(width);
  ui->threadsComboBox->setFixedWidth(width);

  // set a nice GUI size
  QSize size = this->sizeHint();
  size.setWidth(this->minimumSizeHint().width());
#if defined(Q_OS_WIN)
  size.setHeight(size.height() - size.height() / 10);
#endif
  this->resize(size);

  // limit input for arithmetic expressions
  QRegExp rx("[0-9\\+\\-\\*\\/\\%\\^\\(\\)\\e\\E]*");
  validator_ = new QRegExpValidator(rx, this);
  ui->lowerBoundLineEdit->setValidator(validator_);
  ui->upperBoundLineEdit->setValidator(validator_);
}

void PrimeSieveGUI::initConnections() {
  connect(&progressBarTimer_,     SIGNAL(timeout()),                    this, SLOT(advanceProgressBar()));
  connect(ui->lowerBoundLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(autoSetThreads()));
  connect(ui->upperBoundLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(autoSetThreads()));
  connect(ui->autoSetCheckBox,    SIGNAL(toggled(bool)),                this, SLOT(autoSetThreads()));
  connect(saveAct_,               SIGNAL(triggered()),                  this, SLOT(saveToFile()));
  connect(quitAct_,               SIGNAL(triggered()),                  qApp, SLOT(closeAllWindows()));
  connect(alignmentGroup_,        SIGNAL(triggered(QAction*)),          this, SLOT(printMenuClicked(QAction*)));
  connect(aboutAct_,              SIGNAL(triggered()),                  this, SLOT(showAboutDialog()));
}

/**
 * Get the sieve size in kilobytes from the sieveSizeComboBox.
 * @post sieveSize >= 1 && sieveSize <= 4096.
 */
int PrimeSieveGUI::getSieveSize() {
  QString sieveSize(ui->sieveSizeComboBox->currentText());
  // remove " KB"
  sieveSize.chop(3);
  return sieveSize.toInt();
}

/**
 * Get the number of threads from the threadsComboBox.
 */
int PrimeSieveGUI::getThreads() {
  return ui->threadsComboBox->currentText().toInt();
}

quint64 PrimeSieveGUI::getNumber(const QString& str) {
  if (str.isEmpty())
    throw std::invalid_argument("Please enter a lower and upper bound for prime sieving.");

  ExpressionParser<quint64> parser;
  quint64 result = 0;
  try {
    result = parser.eval(str.toAscii().data());
  }
  catch (parser_error& e) {
    throw std::invalid_argument(e.what());
  }

  int digits = str.count(QRegExp("[0-9]"));
  if (result >= UPPER_BOUND_LIMIT || (
      digits == str.size() && (
      digits >  UPPER_BOUND_STR.size() || (
      digits == UPPER_BOUND_STR.size() &&
      str    >= UPPER_BOUND_STR))))
    throw std::invalid_argument("Please use positive integers < 2^64 - 2^32*10.");

  return result;
}

void PrimeSieveGUI::setTo(QComboBox* comboBox, const QString& text) {
  comboBox->setCurrentIndex(comboBox->findText(text));
}

/**
 * If "Auto set" is enabled set an ideal number of threads for the
 * current lower bound, upper bound in the threadsComboBox.
 */
void PrimeSieveGUI::autoSetThreads() {
  if (ui->autoSetCheckBox->isEnabled() && ui->autoSetCheckBox->isChecked()) {
    try {
      quint64 lowerBound = this->getNumber(ui->lowerBoundLineEdit->text());
      quint64 upperBound = this->getNumber(ui->upperBoundLineEdit->text());
      ParallelPrimeSieve pps;
      pps.setStart(lowerBound);
      pps.setStop(upperBound);
      int idealNumThreads = pps.getNumThreads();
      if (idealNumThreads < maxThreads_) {
        // floor to the next power of 2 value
        int p = 1;
        for (; p <= idealNumThreads; p *= 2)
          ;
        idealNumThreads = p / 2;
      }
      this->setTo(ui->threadsComboBox, QString::number(idealNumThreads));
    } catch (...) {
      this->setTo(ui->threadsComboBox, "1");
    }
  }
}

/**
 * The user has chosen a custom number of threads, disable "Auto set".
 */
void PrimeSieveGUI::on_threadsComboBox_activated() {
  ui->autoSetCheckBox->setChecked(false);
}

/**
 * Start sieving primes.
 */
void PrimeSieveGUI::on_sieveButton_clicked() {
  // invert buttons, reset upon cleanUp()
  ui->sieveButton->setDisabled(true);
  ui->cancelButton->setEnabled(true);
  try {
    flags_ = this->getMenuSettings() | CALCULATE_STATUS;
    if ((flags_ & (COUNT_FLAGS | PRINT_FLAGS)) == 0)
      throw std::invalid_argument("Nothing to do, no count or print options selected.");

    quint64 lowerBound = this->getNumber(ui->lowerBoundLineEdit->text());
    quint64 upperBound = this->getNumber(ui->upperBoundLineEdit->text());
    if (lowerBound > upperBound)
      throw std::invalid_argument("The lower bound must not be greater than the upper bound.");

    // reset the GUI widgets
    ui->progressBar->setValue(ui->progressBar->minimum());
    ui->textEdit->clear();
    progressBarTimer_.start(25);

    // start a new process for sieving (avoids cancel
    // trouble with multiple threads)
    primeSieveProcess_ = new PrimeSieveProcess(this);
    if (flags_ & PRINT_FLAGS)
      connect(primeSieveProcess_, SIGNAL(readyReadStandardOutput()),
          this, SLOT(printProcessOutput()));
    connect(primeSieveProcess_, SIGNAL(finished(int, QProcess::ExitStatus)),
        this, SLOT(processFinished(int, QProcess::ExitStatus)));
    primeSieveProcess_->start(lowerBound, upperBound, this->getSieveSize(),
        flags_, this->getThreads());

  } catch (std::invalid_argument& ex) {
    this->cleanUp();
    QMessageBox::warning(this, APPLICATION_NAME, ex.what());
  } catch (std::exception& ex) {
    this->cleanUp();
    QMessageBox::critical(this, APPLICATION_NAME, ex.what());
  }
}

void PrimeSieveGUI::advanceProgressBar() {
  int permil = static_cast<int>(primeSieveProcess_->getStatus() * 10.0);
  ui->progressBar->setValue(permil);
}

/**
 * Redirects the standard output (prime numbers or prime k-tuplets) of
 * the primeSieveProcess_ to the TextEdit.
 */
void PrimeSieveGUI::printProcessOutput() {
  QByteArray buffer;
  buffer.reserve(PRINT_BUFFER_SIZE + 256);
  while (ui->cancelButton->isEnabled() && primeSieveProcess_->canReadLine()) {
    buffer.clear();
    while (primeSieveProcess_->canReadLine() && buffer.size() < PRINT_BUFFER_SIZE)
      buffer.append(primeSieveProcess_->readLine(256));
    // remove "\r\n" or '\n', '\r' at the back
    while (buffer.endsWith('\n') ||
           buffer.endsWith('\r'))
      buffer.chop(1);
    if (!buffer.isEmpty())
      ui->textEdit->appendPlainText(buffer);
/// Keep the GUI responsive.
/// @warning QApplication::processEvents() must not be used on
///          operating systems that use signal recursion (like Linux
///          X11) otherwise the stack will explode!
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
    QApplication::processEvents();
#else
    ui->textEdit->repaint();
#endif
  }
}

/**
 * Is executed when the primeSieveProcess_ finishes, checks for
 * process errors and calls this->printResults().
 */
void PrimeSieveGUI::processFinished(int exitCode,
    QProcess::ExitStatus exitStatus) {
  // the process did not exit normally, i.e. threw and exception
  if (exitCode != 0) {
    // Qt uses '/' internally, even for Windows
    QString path = QCoreApplication::applicationDirPath() + "/" + APPLICATION_NAME + "_error.txt";
    QFile error_log(path);
    if (error_log.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
      QTextStream out(&error_log);
      out << primeSieveProcess_->readAllStandardError();
      error_log.close();
    }
    this->cleanUp();
    QMessageBox::critical(this, APPLICATION_NAME,
        "The PrimeSieveProcess reported an error (see primesieve_error.txt), sieving has been aborted.");
  }
  // the PrimeSieveProcess has been interrupted by a signal (SIGTERM,
  // SIGKILL, ...) or a segmentation fault
  else if (exitStatus == QProcess::CrashExit) {
    this->cleanUp();
    QMessageBox::critical(this, APPLICATION_NAME, "The PrimeSieveProcess crashed, sieving has been aborted.");
  }
  // the PrimeSieveProcess has finished correctly
  else {
    ui->progressBar->setValue(ui->progressBar->maximum());
    // print results if not canceled lately
    if (ui->cancelButton->isEnabled())
      this->printResults();
    this->cleanUp();
  }
}

/**
 * Print the sieving results.
 */
void PrimeSieveGUI::printResults() {
  if (!ui->textEdit->toPlainText().isEmpty())
    ui->textEdit->appendPlainText("");

  // hack to get the count results aligned using tabs
  QString maxSizeText;
  for (int i = 0; i < 7; i++) {
    if ((flags_ & (COUNT_PRIMES << i)) && maxSizeText.size() < primeText_[i].size())
      maxSizeText = primeText_[i];
  }
  ui->textEdit->insertPlainText(maxSizeText + ": ");
  int maxWidth = ui->textEdit->cursorRect().left();
  ui->textEdit->moveCursor(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
  ui->textEdit->textCursor().removeSelectedText();
  ui->textEdit->setTabStopWidth(maxWidth);

  // print prime counts & time elapsed
  for (int i = 0; i < 7; i++) {
    if (flags_ & (COUNT_PRIMES << i))
      ui->textEdit->appendPlainText(primeText_[i] + ":\t" + QString::number(primeSieveProcess_->getCounts(i)));
  }
  if (flags_ & COUNT_KTUPLETS)
    ui->textEdit->appendPlainText("");
  QString time("Elapsed time:\t" + QString::number(primeSieveProcess_->getSeconds(), 'f', 2) + " sec");
  ui->textEdit->appendPlainText(time);
}

/**
 * Cancel sieving.
 */
void PrimeSieveGUI::on_cancelButton_clicked() {
  ui->cancelButton->setDisabled(true);
  ui->progressBar->setValue(0);
  // too late to abort
  if ((flags_ & PRINT_FLAGS) && primeSieveProcess_->isFinished())
    return;
  this->cleanUp();
}

/**
 * Clean up after sieving is finished or canceled (abort the
 * PrimeSieveProcess if still running).
 */
void PrimeSieveGUI::cleanUp() {
  progressBarTimer_.stop();
  if (primeSieveProcess_ != 0)
    delete primeSieveProcess_;
  primeSieveProcess_ = 0;
  // invert buttons
  ui->cancelButton->setDisabled(true);
  ui->sieveButton->setEnabled(true);
  // force repainting widgets
  this->repaint();
}
<|endoftext|>


<commit_before>#include "runtime_internal.h"
#include "device_interface.h"
#include "HalideRuntime.h"
#include "scoped_mutex_lock.h"

extern "C" {

extern void *malloc(size_t);
extern void free(void *);

}

namespace Halide { namespace Runtime { namespace Internal {

struct device_handle_wrapper {
    uint64_t device_handle;
    const halide_device_interface *interface;
};

// TODO: Coarser grained locking, also consider all things that need
// to be atomic with respect to each other. At present only
// halide_copy_to_host and halide_copy_to_device are atomic with
// respect to each other. halide_device_malloc and halide_device_free
// are also candidates, but to do so they likely need to be able to do
// a copy internaly as well.
WEAK halide_mutex device_copy_mutex;

WEAK int copy_to_host_already_locked(void *user_context, struct buffer_t *buf) {
    int result = 0;

    if (buf->dev_dirty) {
        debug(user_context) << "copy_to_host_already_locked " << buf << " dev_dirty is true\n";
        const halide_device_interface *interface = halide_get_device_interface(buf->dev);
        if (buf->host_dirty) {
            debug(user_context) << "copy_to_host_already_locked " << buf << " dev_dirty and host_dirty are true\n";
            result = halide_error_code_copy_to_host_failed;
        } else if (interface == NULL) {
            debug(user_context) << "copy_to_host_already_locked " << buf << " interface is NULL\n";
            result = halide_error_code_no_device_interface;
        } else {
            result = interface->copy_to_host(user_context, buf);
            if (result == 0) {
                buf->dev_dirty = false;
            } else {
                debug(user_context) << "copy_to_host_already_locked " << buf << " device copy_to_host returned an error\n";
                result = halide_error_code_copy_to_host_failed;
            }
        }
    }

    return result;
}

}}} // namespace Halide::Runtime::Internal

extern "C" {

WEAK uint64_t halide_new_device_wrapper(uint64_t handle, const struct halide_device_interface *interface) {
    // Using malloc instead of halide_malloc avoids alignment overhead.
    device_handle_wrapper *wrapper = (device_handle_wrapper *)malloc(sizeof(device_handle_wrapper));
    if (wrapper == NULL) {
        return 0;
    }
    wrapper->device_handle = handle;
    wrapper->interface = interface;

    interface->use_module();

    debug(NULL) << "Creating device wrapper for interface " << interface << " handle " << (void *)handle << " wrapper " << wrapper << "\n";
    return (uint64_t)wrapper;
}

WEAK void halide_delete_device_wrapper(uint64_t wrapper) {
    device_handle_wrapper *wrapper_ptr = (device_handle_wrapper *)wrapper;
    wrapper_ptr->interface->release_module();
    debug(NULL) << "Deleting device wrapper for interface " << wrapper_ptr->interface << " device_handle " << (void *)wrapper_ptr->device_handle << " at addr " << wrapper_ptr << "\n";
    free(wrapper_ptr);
}

WEAK uint64_t halide_get_device_handle(uint64_t dev_field) {
    const device_handle_wrapper *wrapper = (const device_handle_wrapper *)dev_field;
    if (wrapper == NULL) {
        debug(NULL) << "Getting device handle for NULL wrapper\n";
        return 0;
    }
    debug(NULL) << "Getting device handle for interface " << wrapper->interface
                << " device_handle " << (void *)wrapper->device_handle
                << " at addr " << wrapper << "\n";
    return wrapper->device_handle;
}

WEAK const halide_device_interface *halide_get_device_interface(uint64_t dev_field) {
    if (dev_field == 0) {
        return NULL;
    }
    return ((device_handle_wrapper *)dev_field)->interface;
}

/** Release all data associated with the current GPU backend, in particular
 * all resources (memory, texture, context handles) allocated by Halide. Must
 * be called explicitly when using AOT compilation. */
WEAK void halide_device_release(void *user_context, const halide_device_interface *interface) {
    interface->device_release(user_context);
}

/** Copy image data from device memory to host memory. This must be called
 * explicitly to copy back the results of a GPU-based filter. */
WEAK int halide_copy_to_host(void *user_context, struct buffer_t *buf) {
    ScopedMutexLock lock(&device_copy_mutex);

    debug(NULL) << "halide_copy_to_host " << buf << "\n";

    return copy_to_host_already_locked(user_context, buf);
}

/** Copy image data from host memory to device memory. This should not be
 * called directly; Halide handles copying to the device automatically. */
WEAK int halide_copy_to_device(void *user_context, struct buffer_t *buf, const halide_device_interface *interface) {
    int result = 0;

    ScopedMutexLock lock(&device_copy_mutex);

    debug(user_context) << "halide_copy_to_device " << buf << ", host: " << buf->host << ", dev: " << buf->dev << ", host_dirty: " << buf->host_dirty << ", dev_dirty:" << buf->dev_dirty << "\n";
    const halide_device_interface *buf_dev_interface = halide_get_device_interface(buf->dev);
    if (interface == NULL) {
        debug(user_context) << "halide_copy_to_device " << buf << " interface is NULL\n";
        if (buf_dev_interface == NULL) {
            debug(user_context) << "halide_copy_to_device " << buf << " no interface error\n";
            return halide_error_code_no_device_interface;
        }
        interface = buf_dev_interface;
    }

    if (buf->dev && buf_dev_interface != interface) {
        debug(user_context) << "halide_copy_to_device " << buf << " flipping buffer to new device\n";
        if (buf_dev_interface != NULL && buf->dev_dirty) {
            halide_assert(user_context, !buf->host_dirty);
            result = copy_to_host_already_locked(user_context, buf);
            if (result != 0) {
                debug(user_context) << "halide_copy_to_device " << buf << " flipping buffer halide_copy_to_host failed\n";
                return result;
            }
        }
        result = halide_device_free(user_context, buf);
        if (result != 0) {
            debug(user_context) << "halide_copy_to_device " << buf << " flipping buffer halide_device_free failed\n";
            return result;
        }
        buf->host_dirty = true; // force copy back to new device below.
    }

    if (buf->dev == 0) {
        result = halide_device_malloc(user_context, buf, interface);
        if (result != 0) {
            debug(user_context) << "halide_copy_to_device " << buf
                                << " halide_copy_to_device call to halide_device_malloc failed\n";
            return result;
        }
    }

    if (buf->host_dirty) {
        debug(user_context) << "halide_copy_to_device " << buf << " host is dirty\n";
        if (buf->dev_dirty) {
            debug(user_context) << "halide_copy_to_device " << buf << " dev_dirty is true error\n";
            result = halide_error_code_copy_to_device_failed;
        } else {
            result = interface->copy_to_device(user_context, buf);
            if (result == 0) {
                buf->host_dirty = 0;
            } else {
                debug(user_context) << "halide_copy_to_device "
                                    << buf << "device copy_to_device returned an error\n";
                return halide_error_code_copy_to_device_failed;
            }
        }
    }

    return 0;
}

/** Wait for current GPU operations to complete. Calling this explicitly
 * should rarely be necessary, except maybe for profiling. */
WEAK int halide_device_sync(void *user_context, struct buffer_t *buf) {
    const halide_device_interface *interface = NULL;
    if (buf) {
        interface = halide_get_device_interface(buf->dev);
    }
    if (interface == NULL) {
        return halide_error_code_no_device_interface;
    }
    int result = interface->device_sync(user_context, buf);
    if (result) {
        return halide_error_code_device_sync_failed;
    } else {
        return 0;
    }
}

/** Allocate device memory to back a buffer_t. */
WEAK int halide_device_malloc(void *user_context, struct buffer_t *buf, const halide_device_interface *interface) {
    const halide_device_interface *current_interface = halide_get_device_interface(buf->dev);
    debug(user_context) << "halide_device_malloc: " << buf
                        << " interface " << interface
                        << " host: " << buf->host
                        << ", dev: " << buf->dev
                        << ", host_dirty: " << buf->host_dirty
                        << ", dev_dirty:" << buf->dev_dirty
                        << " buf current interface: " << current_interface << "\n";

    // halide_device_malloc does not support switching interfaces.
    if (current_interface != NULL && current_interface != interface) {
        return halide_error_code_device_malloc_failed;
    }

    // Ensure code is not freed prematurely.
    // TODO: Exception safety...
    interface->use_module();
    int result = interface->device_malloc(user_context, buf);
    interface->release_module();

    if (result) {
        return halide_error_code_device_malloc_failed;
    } else {
        return 0;
    }
}

/** Free any device memory associated with a buffer_t. */
WEAK int halide_device_free(void *user_context, struct buffer_t *buf) {
    uint64_t dev_field = 0;
    if (buf) {
        dev_field = buf->dev;
    }
    debug(user_context) << "halide_device_free: " << buf
                        << " buf dev " << buf->dev
                        << " interface " << halide_get_device_interface(dev_field) << "\n";
    if (buf != NULL) {
        const halide_device_interface *interface = halide_get_device_interface(dev_field);
        if (interface != NULL) {
            // Ensure interface is not freed prematurely.
            // TODO: Exception safety...
            interface->use_module();
            int result = interface->device_free(user_context, buf);
            interface->release_module();
            halide_assert(user_context, buf->dev == 0);
            if (result) {
                return halide_error_code_device_free_failed;
            } else {
                return 0;
            }
        }
    }
    buf->dev_dirty = false;
    return 0;
}

/** Free any device memory associated with a buffer_t and ignore any
 * error. Used when freeing as a destructor. */
WEAK void halide_device_free_as_destructor(void *user_context, void *obj) {
    struct buffer_t *buf = (struct buffer_t *)obj;
    halide_device_free(user_context, buf);
}

} // extern "C" linkage
<commit_msg>Add some more debug statements in device_interface<commit_after>#include "runtime_internal.h"
#include "device_interface.h"
#include "HalideRuntime.h"
#include "scoped_mutex_lock.h"

extern "C" {

extern void *malloc(size_t);
extern void free(void *);

}

namespace Halide { namespace Runtime { namespace Internal {

struct device_handle_wrapper {
    uint64_t device_handle;
    const halide_device_interface *interface;
};

// TODO: Coarser grained locking, also consider all things that need
// to be atomic with respect to each other. At present only
// halide_copy_to_host and halide_copy_to_device are atomic with
// respect to each other. halide_device_malloc and halide_device_free
// are also candidates, but to do so they likely need to be able to do
// a copy internaly as well.
WEAK halide_mutex device_copy_mutex;

WEAK int copy_to_host_already_locked(void *user_context, struct buffer_t *buf) {
    int result = 0;

    if (buf->dev_dirty) {
        debug(user_context) << "copy_to_host_already_locked " << buf << " dev_dirty is true\n";
        const halide_device_interface *interface = halide_get_device_interface(buf->dev);
        if (buf->host_dirty) {
            debug(user_context) << "copy_to_host_already_locked " << buf << " dev_dirty and host_dirty are true\n";
            result = halide_error_code_copy_to_host_failed;
        } else if (interface == NULL) {
            debug(user_context) << "copy_to_host_already_locked " << buf << " interface is NULL\n";
            result = halide_error_code_no_device_interface;
        } else {
            result = interface->copy_to_host(user_context, buf);
            if (result == 0) {
                buf->dev_dirty = false;
            } else {
                debug(user_context) << "copy_to_host_already_locked " << buf << " device copy_to_host returned an error\n";
                result = halide_error_code_copy_to_host_failed;
            }
        }
    }

    return result;
}

}}} // namespace Halide::Runtime::Internal

extern "C" {

WEAK uint64_t halide_new_device_wrapper(uint64_t handle, const struct halide_device_interface *interface) {
    // Using malloc instead of halide_malloc avoids alignment overhead.
    device_handle_wrapper *wrapper = (device_handle_wrapper *)malloc(sizeof(device_handle_wrapper));
    if (wrapper == NULL) {
        return 0;
    }
    wrapper->device_handle = handle;
    wrapper->interface = interface;

    interface->use_module();

    debug(NULL) << "Creating device wrapper for interface " << interface << " handle " << (void *)handle << " wrapper " << wrapper << "\n";
    return (uint64_t)wrapper;
}

WEAK void halide_delete_device_wrapper(uint64_t wrapper) {
    device_handle_wrapper *wrapper_ptr = (device_handle_wrapper *)wrapper;
    wrapper_ptr->interface->release_module();
    debug(NULL) << "Deleting device wrapper for interface " << wrapper_ptr->interface << " device_handle " << (void *)wrapper_ptr->device_handle << " at addr " << wrapper_ptr << "\n";
    free(wrapper_ptr);
}

WEAK uint64_t halide_get_device_handle(uint64_t dev_field) {
    const device_handle_wrapper *wrapper = (const device_handle_wrapper *)dev_field;
    if (wrapper == NULL) {
        debug(NULL) << "Getting device handle for NULL wrapper\n";
        return 0;
    }
    debug(NULL) << "Getting device handle for interface " << wrapper->interface
                << " device_handle " << (void *)wrapper->device_handle
                << " at addr " << wrapper << "\n";
    return wrapper->device_handle;
}

WEAK const halide_device_interface *halide_get_device_interface(uint64_t dev_field) {
    if (dev_field == 0) {
        return NULL;
    }
    return ((device_handle_wrapper *)dev_field)->interface;
}

/** Release all data associated with the current GPU backend, in particular
 * all resources (memory, texture, context handles) allocated by Halide. Must
 * be called explicitly when using AOT compilation. */
WEAK void halide_device_release(void *user_context, const halide_device_interface *interface) {
    interface->device_release(user_context);
}

/** Copy image data from device memory to host memory. This must be called
 * explicitly to copy back the results of a GPU-based filter. */
WEAK int halide_copy_to_host(void *user_context, struct buffer_t *buf) {
    ScopedMutexLock lock(&device_copy_mutex);

    debug(NULL) << "halide_copy_to_host " << buf << "\n";

    return copy_to_host_already_locked(user_context, buf);
}

/** Copy image data from host memory to device memory. This should not be
 * called directly; Halide handles copying to the device automatically. */
WEAK int halide_copy_to_device(void *user_context, struct buffer_t *buf, const halide_device_interface *interface) {
    int result = 0;

    ScopedMutexLock lock(&device_copy_mutex);

    debug(user_context) << "halide_copy_to_device " << buf << ", host: " << buf->host << ", dev: " << buf->dev << ", host_dirty: " << buf->host_dirty << ", dev_dirty:" << buf->dev_dirty << "\n";
    const halide_device_interface *buf_dev_interface = halide_get_device_interface(buf->dev);
    if (interface == NULL) {
        debug(user_context) << "halide_copy_to_device " << buf << " interface is NULL\n";
        if (buf_dev_interface == NULL) {
            debug(user_context) << "halide_copy_to_device " << buf << " no interface error\n";
            return halide_error_code_no_device_interface;
        }
        interface = buf_dev_interface;
    }

    if (buf->dev && buf_dev_interface != interface) {
        debug(user_context) << "halide_copy_to_device " << buf << " flipping buffer to new device\n";
        if (buf_dev_interface != NULL && buf->dev_dirty) {
            halide_assert(user_context, !buf->host_dirty);
            result = copy_to_host_already_locked(user_context, buf);
            if (result != 0) {
                debug(user_context) << "halide_copy_to_device " << buf << " flipping buffer halide_copy_to_host failed\n";
                return result;
            }
        }
        result = halide_device_free(user_context, buf);
        if (result != 0) {
            debug(user_context) << "halide_copy_to_device " << buf << " flipping buffer halide_device_free failed\n";
            return result;
        }
        buf->host_dirty = true; // force copy back to new device below.
    }

    if (buf->dev == 0) {
        result = halide_device_malloc(user_context, buf, interface);
        if (result != 0) {
            debug(user_context) << "halide_copy_to_device " << buf
                                << " halide_copy_to_device call to halide_device_malloc failed\n";
            return result;
        }
    }

    if (buf->host_dirty) {
        debug(user_context) << "halide_copy_to_device " << buf << " host is dirty\n";
        if (buf->dev_dirty) {
            debug(user_context) << "halide_copy_to_device " << buf << " dev_dirty is true error\n";
            result = halide_error_code_copy_to_device_failed;
        } else {
            result = interface->copy_to_device(user_context, buf);
            if (result == 0) {
                buf->host_dirty = 0;
            } else {
                debug(user_context) << "halide_copy_to_device "
                                    << buf << "device copy_to_device returned an error\n";
                return halide_error_code_copy_to_device_failed;
            }
        }
    }

    return 0;
}

/** Wait for current GPU operations to complete. Calling this explicitly
 * should rarely be necessary, except maybe for profiling. */
WEAK int halide_device_sync(void *user_context, struct buffer_t *buf) {
    const halide_device_interface *interface = NULL;
    if (buf) {
        interface = halide_get_device_interface(buf->dev);
    }
    if (interface == NULL) {
        debug(user_context) << "halide_device_sync on buffer with no interface\n";
        return halide_error_code_no_device_interface;
    }
    int result = interface->device_sync(user_context, buf);
    if (result) {
        return halide_error_code_device_sync_failed;
    } else {
        return 0;
    }
}

/** Allocate device memory to back a buffer_t. */
WEAK int halide_device_malloc(void *user_context, struct buffer_t *buf, const halide_device_interface *interface) {
    const halide_device_interface *current_interface = halide_get_device_interface(buf->dev);
    debug(user_context) << "halide_device_malloc: " << buf
                        << " interface " << interface
                        << " host: " << buf->host
                        << ", dev: " << buf->dev
                        << ", host_dirty: " << buf->host_dirty
                        << ", dev_dirty:" << buf->dev_dirty
                        << " buf current interface: " << current_interface << "\n";

    // halide_device_malloc does not support switching interfaces.
    if (current_interface != NULL && current_interface != interface) {
        debug(user_context) << "halide_malloc doesn't support switching interfaces\n";
        return halide_error_code_device_malloc_failed;
    }

    // Ensure code is not freed prematurely.
    // TODO: Exception safety...
    interface->use_module();
    int result = interface->device_malloc(user_context, buf);
    interface->release_module();

    if (result) {
        return halide_error_code_device_malloc_failed;
    } else {
        return 0;
    }
}

/** Free any device memory associated with a buffer_t. */
WEAK int halide_device_free(void *user_context, struct buffer_t *buf) {
    uint64_t dev_field = 0;
    if (buf) {
        dev_field = buf->dev;
    }
    debug(user_context) << "halide_device_free: " << buf
                        << " buf dev " << buf->dev
                        << " interface " << halide_get_device_interface(dev_field) << "\n";
    if (buf != NULL) {
        const halide_device_interface *interface = halide_get_device_interface(dev_field);
        if (interface != NULL) {
            // Ensure interface is not freed prematurely.
            // TODO: Exception safety...
            interface->use_module();
            int result = interface->device_free(user_context, buf);
            interface->release_module();
            halide_assert(user_context, buf->dev == 0);
            if (result) {
                return halide_error_code_device_free_failed;
            } else {
                return 0;
            }
        }
    }
    buf->dev_dirty = false;
    return 0;
}

/** Free any device memory associated with a buffer_t and ignore any
 * error. Used when freeing as a destructor. */
WEAK void halide_device_free_as_destructor(void *user_context, void *obj) {
    struct buffer_t *buf = (struct buffer_t *)obj;
    halide_device_free(user_context, buf);
}

} // extern "C" linkage
<|endoftext|>


<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*!
 * \file tvm/runtime/vm/memory_manager.cc
 * \brief Allocate and manage memory for the runtime.
 */
#include <tvm/runtime/vm/memory_manager.h>

#include <memory>
#include <utility>

#include "naive_allocator.h"
#include "pooled_allocator.h"

namespace tvm {
namespace runtime {
namespace vm {

static void BufferDeleter(Object* obj) {
  auto* ptr = static_cast<NDArray::Container*>(obj);
  ICHECK(ptr->manager_ctx != nullptr);
  Buffer* buffer = reinterpret_cast<Buffer*>(ptr->manager_ctx);
  MemoryManager::GetAllocator(buffer->device)->Free(*(buffer));
  delete buffer;
  delete ptr;
}

void StorageObj::Deleter(Object* obj) {
  auto* ptr = static_cast<NDArray::Container*>(obj);
  // When invoking AllocNDArray we don't own the underlying allocation
  // and should not delete the buffer, but instead let it be reclaimed
  // by the storage object's destructor.
  //
  // We did bump the reference count by 1 to keep alive the StorageObj
  // allocation in case this NDArray is the sole owner.
  //
  // We decrement the object allowing for the buffer to release our
  // reference count from allocation.
  StorageObj* storage = reinterpret_cast<StorageObj*>(ptr->manager_ctx);
  storage->DecRef();
  delete ptr;
}

inline void VerifyDataType(DLDataType dtype) {
  ICHECK_GE(dtype.lanes, 1);
  if (dtype.code == kDLFloat) {
    ICHECK_EQ(dtype.bits % 8, 0);
  } else {
    // allow uint1 as a special flag for bool.
    if (dtype.bits == 1 && dtype.code == kDLUInt) return;
    ICHECK_EQ(dtype.bits % 8, 0);
  }
  ICHECK_EQ(dtype.bits & (dtype.bits - 1), 0);
}

inline size_t GetDataAlignment(const DLTensor& arr) {
  size_t align = (arr.dtype.bits / 8) * arr.dtype.lanes;
  if (align < kAllocAlignment) return kAllocAlignment;
  return align;
}

NDArray StorageObj::AllocNDArray(size_t offset, std::vector<int64_t> shape, DLDataType dtype) {
  VerifyDataType(dtype);

  // crtical zone: allocate header, cannot throw
  NDArray::Container* container =
      new NDArray::Container(nullptr, shape, dtype, this->buffer.device);

  container->SetDeleter(StorageObj::Deleter);
  size_t needed_size = GetDataSize(container->dl_tensor);
  this->IncRef();
  // The manager context pointer must continue to point to the storage object
  // which owns the backing memory, and keeps track of the reference count.
  //
  // When we free a container we extract the storage object, decrement its
  // reference count, then destroy the container, but leave the underlying
  // buffer intact.
  container->manager_ctx = reinterpret_cast<void*>(this);

  // is this UB?
  // The only change we make w.r.t offset is modifying the data pointer
  // of the backing tensor to point into the buffer instead of its start.
  auto offset_ptr = reinterpret_cast<uint8_t*>(this->buffer.data) + offset;
  container->dl_tensor.data = reinterpret_cast<void*>(offset_ptr);

  NDArray ret(GetObjectPtr<Object>(container));
  // RAII in effect, now run the check.

  ICHECK(offset + needed_size <= this->buffer.size)
      << "storage allocation failure, attempted to allocate " << needed_size << " at offset "
      << offset << " in region that is " << this->buffer.size << "bytes";

  return ret;
}

MemoryManager* MemoryManager::Global() {
  // NOTE: explicitly use new to avoid exit-time destruction of global state
  // Global state will be recycled by OS as the process exits.
  static auto* inst = new MemoryManager();
  return inst;
}

Allocator* MemoryManager::GetOrCreateAllocator(Device dev, AllocatorType type) {
  MemoryManager* m = MemoryManager::Global();
  std::lock_guard<std::mutex> lock(m->mu_);
  if (m->allocators_.find(dev) == m->allocators_.end()) {
    std::unique_ptr<Allocator> alloc;
    switch (type) {
      case kNaive: {
        DLOG(INFO) << "New naive allocator for " << DeviceName(dev.device_type) << "("
                   << dev.device_id << ")";
        alloc.reset(new NaiveAllocator(dev));
        break;
      }
      case kPooled: {
        DLOG(INFO) << "New pooled allocator for " << DeviceName(dev.device_type) << "("
                   << dev.device_id << ")";
        alloc.reset(new PooledAllocator(dev));
        break;
      }
      default:
        LOG(FATAL) << "Unknown allocator type: " << type;
    }
    auto ret = alloc.get();
    m->allocators_.emplace(dev, std::move(alloc));
    return ret;
  }
  auto alloc = m->allocators_.at(dev).get();
  if (alloc->type() != type) {
    LOG(WARNING) << "The type of existing allocator for " << DeviceName(dev.device_type) << "("
                 << dev.device_id << ") is different from the request type (" << alloc->type()
                 << " vs " << type << ")";
  }
  return alloc;
}

Allocator* MemoryManager::GetAllocator(Device dev) {
  MemoryManager* m = MemoryManager::Global();
  std::lock_guard<std::mutex> lock(m->mu_);
  auto it = m->allocators_.find(dev);
  if (it == m->allocators_.end()) {
    LOG(FATAL) << "Allocator for " << DeviceName(dev.device_type) << "(" << dev.device_id
               << ") has not been created yet.";
  }
  return it->second.get();
}

NDArray Allocator::Empty(std::vector<int64_t> shape, DLDataType dtype, DLDevice dev) {
  VerifyDataType(dtype);
  NDArray::Container* container = new NDArray::Container(nullptr, shape, dtype, dev);
  container->SetDeleter(BufferDeleter);
  size_t size = GetDataSize(container->dl_tensor);
  size_t alignment = GetDataAlignment(container->dl_tensor);
  Buffer* buffer = new Buffer;
  *buffer = this->Alloc(size, alignment, dtype);
  container->manager_ctx = reinterpret_cast<void*>(buffer);
  container->dl_tensor.data = buffer->data;
  return NDArray(GetObjectPtr<Object>(container));
}

}  // namespace vm
}  // namespace runtime
}  // namespace tvm
<commit_msg>Remove pointer arithmetic in StorageObj::AllocNDArray (#7890)<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*!
 * \file tvm/runtime/vm/memory_manager.cc
 * \brief Allocate and manage memory for the runtime.
 */
#include <tvm/runtime/vm/memory_manager.h>

#include <memory>
#include <utility>

#include "naive_allocator.h"
#include "pooled_allocator.h"

namespace tvm {
namespace runtime {
namespace vm {

static void BufferDeleter(Object* obj) {
  auto* ptr = static_cast<NDArray::Container*>(obj);
  ICHECK(ptr->manager_ctx != nullptr);
  Buffer* buffer = reinterpret_cast<Buffer*>(ptr->manager_ctx);
  MemoryManager::GetAllocator(buffer->device)->Free(*(buffer));
  delete buffer;
  delete ptr;
}

void StorageObj::Deleter(Object* obj) {
  auto* ptr = static_cast<NDArray::Container*>(obj);
  // When invoking AllocNDArray we don't own the underlying allocation
  // and should not delete the buffer, but instead let it be reclaimed
  // by the storage object's destructor.
  //
  // We did bump the reference count by 1 to keep alive the StorageObj
  // allocation in case this NDArray is the sole owner.
  //
  // We decrement the object allowing for the buffer to release our
  // reference count from allocation.
  StorageObj* storage = reinterpret_cast<StorageObj*>(ptr->manager_ctx);
  storage->DecRef();
  delete ptr;
}

inline void VerifyDataType(DLDataType dtype) {
  ICHECK_GE(dtype.lanes, 1);
  if (dtype.code == kDLFloat) {
    ICHECK_EQ(dtype.bits % 8, 0);
  } else {
    // allow uint1 as a special flag for bool.
    if (dtype.bits == 1 && dtype.code == kDLUInt) return;
    ICHECK_EQ(dtype.bits % 8, 0);
  }
  ICHECK_EQ(dtype.bits & (dtype.bits - 1), 0);
}

inline size_t GetDataAlignment(const DLTensor& arr) {
  size_t align = (arr.dtype.bits / 8) * arr.dtype.lanes;
  if (align < kAllocAlignment) return kAllocAlignment;
  return align;
}

NDArray StorageObj::AllocNDArray(size_t offset, std::vector<int64_t> shape, DLDataType dtype) {
  VerifyDataType(dtype);

  // crtical zone: allocate header, cannot throw
  NDArray::Container* container =
      new NDArray::Container(this->buffer.data, shape, dtype, this->buffer.device);
  container->dl_tensor.byte_offset = offset;

  container->SetDeleter(StorageObj::Deleter);
  size_t needed_size = GetDataSize(container->dl_tensor);
  this->IncRef();
  // The manager context pointer must continue to point to the storage object
  // which owns the backing memory, and keeps track of the reference count.
  //
  // When we free a container we extract the storage object, decrement its
  // reference count, then destroy the container, but leave the underlying
  // buffer intact.
  container->manager_ctx = reinterpret_cast<void*>(this);

  NDArray ret(GetObjectPtr<Object>(container));
  // RAII in effect, now run the check.

  ICHECK(offset + needed_size <= this->buffer.size)
      << "storage allocation failure, attempted to allocate " << needed_size << " at offset "
      << offset << " in region that is " << this->buffer.size << "bytes";

  return ret;
}

MemoryManager* MemoryManager::Global() {
  // NOTE: explicitly use new to avoid exit-time destruction of global state
  // Global state will be recycled by OS as the process exits.
  static auto* inst = new MemoryManager();
  return inst;
}

Allocator* MemoryManager::GetOrCreateAllocator(Device dev, AllocatorType type) {
  MemoryManager* m = MemoryManager::Global();
  std::lock_guard<std::mutex> lock(m->mu_);
  if (m->allocators_.find(dev) == m->allocators_.end()) {
    std::unique_ptr<Allocator> alloc;
    switch (type) {
      case kNaive: {
        DLOG(INFO) << "New naive allocator for " << DeviceName(dev.device_type) << "("
                   << dev.device_id << ")";
        alloc.reset(new NaiveAllocator(dev));
        break;
      }
      case kPooled: {
        DLOG(INFO) << "New pooled allocator for " << DeviceName(dev.device_type) << "("
                   << dev.device_id << ")";
        alloc.reset(new PooledAllocator(dev));
        break;
      }
      default:
        LOG(FATAL) << "Unknown allocator type: " << type;
    }
    auto ret = alloc.get();
    m->allocators_.emplace(dev, std::move(alloc));
    return ret;
  }
  auto alloc = m->allocators_.at(dev).get();
  if (alloc->type() != type) {
    LOG(WARNING) << "The type of existing allocator for " << DeviceName(dev.device_type) << "("
                 << dev.device_id << ") is different from the request type (" << alloc->type()
                 << " vs " << type << ")";
  }
  return alloc;
}

Allocator* MemoryManager::GetAllocator(Device dev) {
  MemoryManager* m = MemoryManager::Global();
  std::lock_guard<std::mutex> lock(m->mu_);
  auto it = m->allocators_.find(dev);
  if (it == m->allocators_.end()) {
    LOG(FATAL) << "Allocator for " << DeviceName(dev.device_type) << "(" << dev.device_id
               << ") has not been created yet.";
  }
  return it->second.get();
}

NDArray Allocator::Empty(std::vector<int64_t> shape, DLDataType dtype, DLDevice dev) {
  VerifyDataType(dtype);
  NDArray::Container* container = new NDArray::Container(nullptr, shape, dtype, dev);
  container->SetDeleter(BufferDeleter);
  size_t size = GetDataSize(container->dl_tensor);
  size_t alignment = GetDataAlignment(container->dl_tensor);
  Buffer* buffer = new Buffer;
  *buffer = this->Alloc(size, alignment, dtype);
  container->manager_ctx = reinterpret_cast<void*>(buffer);
  container->dl_tensor.data = buffer->data;
  return NDArray(GetObjectPtr<Object>(container));
}

}  // namespace vm
}  // namespace runtime
}  // namespace tvm
<|endoftext|>


<commit_before>/*************************************************************************
 *
 * TIGHTDB CONFIDENTIAL
 * __________________
 *
 *  [2011] - [2012] TightDB Inc
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of TightDB Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to TightDB Incorporated
 * and its suppliers and may be covered by U.S. and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from TightDB Incorporated.
 *
 **************************************************************************/
#include "file_mapper.hpp"

#include <cerrno>
#include <sys/mman.h>

#include <tightdb/util/errno.hpp>

#ifdef TIGHTDB_ENABLE_ENCRYPTION

#include "encrypted_file_mapping.hpp"

#include <memory>
#include <signal.h>
#include <sys/stat.h>
#include <unistd.h>

#include <tightdb/util/shared_ptr.hpp>
#include <tightdb/util/terminate.hpp>
#include <tightdb/util/thread.hpp>

using namespace tightdb;
using namespace tightdb::util;

namespace {
const size_t page_size = 4096;

class SpinLockGuard {
public:
    SpinLockGuard(Atomic<bool>& lock) : m_lock(lock)
    {
        while (m_lock.exchange_acquire(true)) ;
    }

    ~SpinLockGuard()
    {
        m_lock.store_release(false);
    }

private:
    Atomic<bool>& m_lock;
};

// A list of all of the active encrypted mappings for a single file
struct mappings_for_file {
    dev_t device;
    ino_t inode;
    SharedPtr<SharedFileInfo> info;
};

// Group the information we need to map a SIGSEGV address to an
// EncryptedFileMapping for the sake of cache-friendliness with 3+ active
// mappings (and no worse with only two
struct mapping_and_addr {
    SharedPtr<EncryptedFileMapping> mapping;
    void* addr;
    size_t size;
};

Atomic<bool> mapping_lock;
std::vector<mapping_and_addr> mappings_by_addr;
std::vector<mappings_for_file> mappings_by_file;

// The signal handlers which our handlers replaced, if any, for forwarding
// signals for segfaults outside of our encrypted pages
struct sigaction old_segv;
struct sigaction old_bus;

void signal_handler(int code, siginfo_t* info, void* ctx)
{
    SpinLockGuard lock(mapping_lock);
    for (size_t i = 0; i < mappings_by_addr.size(); ++i) {
        mapping_and_addr& m = mappings_by_addr[i];
        if (m.addr > info->si_addr || static_cast<char*>(m.addr) + m.size <= info->si_addr)
            continue;

        m.mapping->handle_access(info->si_addr);
        return;
    }

    // forward unhandled signals
    if (code == SIGSEGV) {
        if (old_segv.sa_sigaction)
            old_segv.sa_sigaction(code, info, ctx);
        else if (old_segv.sa_handler)
            old_segv.sa_handler(code);
        else
            TIGHTDB_TERMINATE("Segmentation fault");
    }
    else if (code == SIGBUS) {
        if (old_bus.sa_sigaction)
            old_bus.sa_sigaction(code, info, ctx);
        else if (old_bus.sa_handler)
            old_bus.sa_handler(code);
        else
            TIGHTDB_TERMINATE("Segmentation fault");
    }
    else
        TIGHTDB_TERMINATE("Segmentation fault");
}

mapping_and_addr* find_mapping_for_addr(void* addr, size_t size)
{
    for (size_t i = 0; i < mappings_by_addr.size(); ++i) {
        mapping_and_addr& m = mappings_by_addr[i];
        if (m.addr == addr && m.size == size)
            return &m;
    }

    return 0;
}

size_t round_up_to_page_size(size_t size)
{
    return (size + page_size - 1) & ~(page_size - 1);
}

void add_mapping(void* addr, size_t size, int fd, File::AccessMode access, const char* encryption_key)
{
    struct stat st;
    if (fstat(fd, &st))
        TIGHTDB_TERMINATE("fstat failed"); // FIXME: throw instead

    if (st.st_size > 0 && static_cast<size_t>(st.st_size) < page_size)
        throw DecryptionFailed();

    SpinLockGuard lock(mapping_lock);

    static bool has_installed_handler = false;
    if (!has_installed_handler) {
        has_installed_handler = true;

        struct sigaction action;
        action.sa_sigaction = signal_handler;
        action.sa_flags = SA_SIGINFO;

        if (sigaction(SIGSEGV, &action, &old_segv) != 0)
            TIGHTDB_TERMINATE("sigaction SEGV failed");
        if (sigaction(SIGBUS, &action, &old_bus) != 0)
            TIGHTDB_TERMINATE("sigaction SIGBUS");
    }

    std::vector<mappings_for_file>::iterator it;
    for (it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
        if (it->inode == st.st_ino && it->device == st.st_dev)
            break;
    }

    // Get the potential memory allocation out of the way so that mappings_by_addr.push_back can't throw
    mappings_by_addr.reserve(mappings_by_addr.size() + 1);

    if (it == mappings_by_file.end()) {
        mappings_for_file f;
        f.device = st.st_dev;
        f.inode = st.st_ino;
        f.info = new SharedFileInfo(reinterpret_cast<const uint8_t*>(encryption_key), dup(fd));
        mappings_by_file.push_back(f);
        it = mappings_by_file.end() - 1;
    }

    try {
        mapping_and_addr m;
        m.addr = addr;
        m.size = size;
        m.mapping = new EncryptedFileMapping(*it->info, addr, size, access);
        mappings_by_addr.push_back(m);
    }
    catch (...) {
        if (it->info->mappings.empty()) {
            ::close(it->info->fd);
            mappings_by_file.erase(it);
        }
        throw;
    }
}

void remove_mapping(void* addr, size_t size)
{
    size = round_up_to_page_size(size);
    SpinLockGuard lock(mapping_lock);
    mapping_and_addr* m = find_mapping_for_addr(addr, size);
    if (!m)
        return;

    mappings_by_addr.erase(mappings_by_addr.begin() + (m - &mappings_by_addr[0]));
    for (std::vector<mappings_for_file>::iterator it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
        if (it->info->mappings.empty()) {
            ::close(it->info->fd);
            mappings_by_file.erase(it);
            break;
        }
    }
}

void* mmap_anon(size_t size)
{
    void* addr = ::mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
    if (addr == MAP_FAILED) {
        int err = errno; // Eliminate any risk of clobbering
        throw std::runtime_error(get_errno_msg("mmap() failed: ", err));
    }
    return addr;
}

} // anonymous namespace
#endif

namespace tightdb {
namespace util {

void* mmap(int fd, size_t size, File::AccessMode access, const char* encryption_key)
{
#ifdef TIGHTDB_ENABLE_ENCRYPTION
    if (encryption_key) {
        size = round_up_to_page_size(size);
        void* addr = mmap_anon(size);
        add_mapping(addr, size, fd, access, encryption_key);
        return addr;
    }
    else
#else
    TIGHTDB_ASSERT(!encryption_key);
    static_cast<void>(encryption_key);
#endif
    {
        int prot = PROT_READ;
        switch (access) {
            case File::access_ReadWrite:
                prot |= PROT_WRITE;
                break;
            case File::access_ReadOnly:
                break;
        }

        void* addr = ::mmap(0, size, prot, MAP_SHARED, fd, 0);
        if (addr != MAP_FAILED)
            return addr;
    }

    int err = errno; // Eliminate any risk of clobbering
    throw std::runtime_error(get_errno_msg("mmap() failed: ", err));
}

void munmap(void* addr, size_t size) TIGHTDB_NOEXCEPT
{
#ifdef TIGHTDB_ENABLE_ENCRYPTION
    remove_mapping(addr, size);
#endif
    ::munmap(addr, size);
}

void* mremap(int fd, void* old_addr, size_t old_size, File::AccessMode a, size_t new_size)
{
#ifdef TIGHTDB_ENABLE_ENCRYPTION
    {
        SpinLockGuard lock(mapping_lock);
        size_t rounded_old_size = round_up_to_page_size(old_size);
        if (mapping_and_addr* m = find_mapping_for_addr(old_addr, rounded_old_size)) {
            size_t rounded_new_size = round_up_to_page_size(new_size);
            if (rounded_old_size == rounded_new_size)
                return old_addr;

            void* new_addr = mmap_anon(rounded_new_size);
            m->mapping->set(new_addr, rounded_new_size);
            ::munmap(old_addr, rounded_old_size);
            m->addr = new_addr;
            m->size = rounded_new_size;
            return new_addr;
        }
    }
#endif

#ifdef _GNU_SOURCE
    static_cast<void>(fd);
    static_cast<void>(a);
    void* new_addr = ::mremap(old_addr, old_size, new_size, MREMAP_MAYMOVE);
    if (new_addr != MAP_FAILED)
        return new_addr;
    int err = errno; // Eliminate any risk of clobbering
    throw std::runtime_error(get_errno_msg("mremap(): failed: ", err));
#else
    void* new_addr = mmap(fd, new_size, a, nullptr);
    ::munmap(old_addr, old_size);
    return new_addr;
#endif
}

void msync(void* addr, size_t size)
{
#ifdef TIGHTDB_ENABLE_ENCRYPTION
    { // first check the encrypted mappings
        SpinLockGuard lock(mapping_lock);
        if (mapping_and_addr* m = find_mapping_for_addr(addr, size)) {
            m->mapping->flush();
            m->mapping->sync();
            return;
        }
    }
#endif

    // not an encrypted mapping
    if (::msync(addr, size, MS_SYNC) != 0) {
        int err = errno; // Eliminate any risk of clobbering
        throw std::runtime_error(get_errno_msg("msync() failed: ", err));
    }
}

}
}
<commit_msg>Check for dup() failures<commit_after>/*************************************************************************
 *
 * TIGHTDB CONFIDENTIAL
 * __________________
 *
 *  [2011] - [2012] TightDB Inc
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of TightDB Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to TightDB Incorporated
 * and its suppliers and may be covered by U.S. and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from TightDB Incorporated.
 *
 **************************************************************************/
#include "file_mapper.hpp"

#include <cerrno>
#include <sys/mman.h>

#include <tightdb/util/errno.hpp>

#ifdef TIGHTDB_ENABLE_ENCRYPTION

#include "encrypted_file_mapping.hpp"

#include <memory>
#include <signal.h>
#include <sys/stat.h>
#include <unistd.h>

#include <tightdb/util/errno.hpp>
#include <tightdb/util/shared_ptr.hpp>
#include <tightdb/util/terminate.hpp>
#include <tightdb/util/thread.hpp>

using namespace tightdb;
using namespace tightdb::util;

namespace {
const size_t page_size = 4096;

class SpinLockGuard {
public:
    SpinLockGuard(Atomic<bool>& lock) : m_lock(lock)
    {
        while (m_lock.exchange_acquire(true)) ;
    }

    ~SpinLockGuard()
    {
        m_lock.store_release(false);
    }

private:
    Atomic<bool>& m_lock;
};

// A list of all of the active encrypted mappings for a single file
struct mappings_for_file {
    dev_t device;
    ino_t inode;
    SharedPtr<SharedFileInfo> info;
};

// Group the information we need to map a SIGSEGV address to an
// EncryptedFileMapping for the sake of cache-friendliness with 3+ active
// mappings (and no worse with only two
struct mapping_and_addr {
    SharedPtr<EncryptedFileMapping> mapping;
    void* addr;
    size_t size;
};

Atomic<bool> mapping_lock;
std::vector<mapping_and_addr> mappings_by_addr;
std::vector<mappings_for_file> mappings_by_file;

// The signal handlers which our handlers replaced, if any, for forwarding
// signals for segfaults outside of our encrypted pages
struct sigaction old_segv;
struct sigaction old_bus;

void signal_handler(int code, siginfo_t* info, void* ctx)
{
    SpinLockGuard lock(mapping_lock);
    for (size_t i = 0; i < mappings_by_addr.size(); ++i) {
        mapping_and_addr& m = mappings_by_addr[i];
        if (m.addr > info->si_addr || static_cast<char*>(m.addr) + m.size <= info->si_addr)
            continue;

        m.mapping->handle_access(info->si_addr);
        return;
    }

    // forward unhandled signals
    if (code == SIGSEGV) {
        if (old_segv.sa_sigaction)
            old_segv.sa_sigaction(code, info, ctx);
        else if (old_segv.sa_handler)
            old_segv.sa_handler(code);
        else
            TIGHTDB_TERMINATE("Segmentation fault");
    }
    else if (code == SIGBUS) {
        if (old_bus.sa_sigaction)
            old_bus.sa_sigaction(code, info, ctx);
        else if (old_bus.sa_handler)
            old_bus.sa_handler(code);
        else
            TIGHTDB_TERMINATE("Segmentation fault");
    }
    else
        TIGHTDB_TERMINATE("Segmentation fault");
}

mapping_and_addr* find_mapping_for_addr(void* addr, size_t size)
{
    for (size_t i = 0; i < mappings_by_addr.size(); ++i) {
        mapping_and_addr& m = mappings_by_addr[i];
        if (m.addr == addr && m.size == size)
            return &m;
    }

    return 0;
}

size_t round_up_to_page_size(size_t size)
{
    return (size + page_size - 1) & ~(page_size - 1);
}

void add_mapping(void* addr, size_t size, int fd, File::AccessMode access, const char* encryption_key)
{
    struct stat st;
    if (fstat(fd, &st)) {
        int err = errno; // Eliminate any risk of clobbering
        throw std::runtime_error(get_errno_msg("fstat() failed: ", err));
    }

    if (st.st_size > 0 && static_cast<size_t>(st.st_size) < page_size)
        throw DecryptionFailed();

    SpinLockGuard lock(mapping_lock);

    static bool has_installed_handler = false;
    if (!has_installed_handler) {
        has_installed_handler = true;

        struct sigaction action;
        action.sa_sigaction = signal_handler;
        action.sa_flags = SA_SIGINFO;

        if (sigaction(SIGSEGV, &action, &old_segv) != 0)
            TIGHTDB_TERMINATE("sigaction SEGV failed");
        if (sigaction(SIGBUS, &action, &old_bus) != 0)
            TIGHTDB_TERMINATE("sigaction SIGBUS");
    }

    std::vector<mappings_for_file>::iterator it;
    for (it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
        if (it->inode == st.st_ino && it->device == st.st_dev)
            break;
    }

    // Get the potential memory allocation out of the way so that mappings_by_addr.push_back can't throw
    mappings_by_addr.reserve(mappings_by_addr.size() + 1);

    if (it == mappings_by_file.end()) {
        fd = dup(fd);
        if (fd == -1) {
            int err = errno; // Eliminate any risk of clobbering
            throw std::runtime_error(get_errno_msg("dup() failed: ", err));
        }

        mappings_for_file f;
        f.device = st.st_dev;
        f.inode = st.st_ino;
        f.info = new SharedFileInfo(reinterpret_cast<const uint8_t*>(encryption_key), fd);
        mappings_by_file.push_back(f);
        it = mappings_by_file.end() - 1;
    }

    try {
        mapping_and_addr m;
        m.addr = addr;
        m.size = size;
        m.mapping = new EncryptedFileMapping(*it->info, addr, size, access);
        mappings_by_addr.push_back(m);
    }
    catch (...) {
        if (it->info->mappings.empty()) {
            ::close(it->info->fd);
            mappings_by_file.erase(it);
        }
        throw;
    }
}

void remove_mapping(void* addr, size_t size)
{
    size = round_up_to_page_size(size);
    SpinLockGuard lock(mapping_lock);
    mapping_and_addr* m = find_mapping_for_addr(addr, size);
    if (!m)
        return;

    mappings_by_addr.erase(mappings_by_addr.begin() + (m - &mappings_by_addr[0]));
    for (std::vector<mappings_for_file>::iterator it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
        if (it->info->mappings.empty()) {
            ::close(it->info->fd);
            mappings_by_file.erase(it);
            break;
        }
    }
}

void* mmap_anon(size_t size)
{
    void* addr = ::mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
    if (addr == MAP_FAILED) {
        int err = errno; // Eliminate any risk of clobbering
        throw std::runtime_error(get_errno_msg("mmap() failed: ", err));
    }
    return addr;
}

} // anonymous namespace
#endif

namespace tightdb {
namespace util {

void* mmap(int fd, size_t size, File::AccessMode access, const char* encryption_key)
{
#ifdef TIGHTDB_ENABLE_ENCRYPTION
    if (encryption_key) {
        size = round_up_to_page_size(size);
        void* addr = mmap_anon(size);
        add_mapping(addr, size, fd, access, encryption_key);
        return addr;
    }
    else
#else
    TIGHTDB_ASSERT(!encryption_key);
    static_cast<void>(encryption_key);
#endif
    {
        int prot = PROT_READ;
        switch (access) {
            case File::access_ReadWrite:
                prot |= PROT_WRITE;
                break;
            case File::access_ReadOnly:
                break;
        }

        void* addr = ::mmap(0, size, prot, MAP_SHARED, fd, 0);
        if (addr != MAP_FAILED)
            return addr;
    }

    int err = errno; // Eliminate any risk of clobbering
    throw std::runtime_error(get_errno_msg("mmap() failed: ", err));
}

void munmap(void* addr, size_t size) TIGHTDB_NOEXCEPT
{
#ifdef TIGHTDB_ENABLE_ENCRYPTION
    remove_mapping(addr, size);
#endif
    ::munmap(addr, size);
}

void* mremap(int fd, void* old_addr, size_t old_size, File::AccessMode a, size_t new_size)
{
#ifdef TIGHTDB_ENABLE_ENCRYPTION
    {
        SpinLockGuard lock(mapping_lock);
        size_t rounded_old_size = round_up_to_page_size(old_size);
        if (mapping_and_addr* m = find_mapping_for_addr(old_addr, rounded_old_size)) {
            size_t rounded_new_size = round_up_to_page_size(new_size);
            if (rounded_old_size == rounded_new_size)
                return old_addr;

            void* new_addr = mmap_anon(rounded_new_size);
            m->mapping->set(new_addr, rounded_new_size);
            ::munmap(old_addr, rounded_old_size);
            m->addr = new_addr;
            m->size = rounded_new_size;
            return new_addr;
        }
    }
#endif

#ifdef _GNU_SOURCE
    static_cast<void>(fd);
    static_cast<void>(a);
    void* new_addr = ::mremap(old_addr, old_size, new_size, MREMAP_MAYMOVE);
    if (new_addr != MAP_FAILED)
        return new_addr;
    int err = errno; // Eliminate any risk of clobbering
    throw std::runtime_error(get_errno_msg("mremap(): failed: ", err));
#else
    void* new_addr = mmap(fd, new_size, a, nullptr);
    ::munmap(old_addr, old_size);
    return new_addr;
#endif
}

void msync(void* addr, size_t size)
{
#ifdef TIGHTDB_ENABLE_ENCRYPTION
    { // first check the encrypted mappings
        SpinLockGuard lock(mapping_lock);
        if (mapping_and_addr* m = find_mapping_for_addr(addr, size)) {
            m->mapping->flush();
            m->mapping->sync();
            return;
        }
    }
#endif

    // not an encrypted mapping
    if (::msync(addr, size, MS_SYNC) != 0) {
        int err = errno; // Eliminate any risk of clobbering
        throw std::runtime_error(get_errno_msg("msync() failed: ", err));
    }
}

}
}
<|endoftext|>