<commit_before>#include <stdint.h>
#include <iostream>
#include <stdexcept>

#include "allowedtypes.hpp"
#include "bin_file.hpp"
#include "renderer.hpp"
#include "statistics.hpp"
#include "string_util.hpp"

using allowed::AllowedTypes;
using std::cout;
using std::endl;
using std::runtime_error;
using std::size_t;
using std::string;
using std::stringstream;
using std::vector;

namespace render {

namespace { // anonymous namespace

static const string EOL("\n");

AllowedTypes getTypes()
{
  AllowedTypes types;
  types.append("char");
  types.append("uint8");
  types.append("uint16");
  types.append("uint32");
  types.append("uint64");
  types.append("int8");
  types.append("int16");
  types.append("int32");
  types.append("int64");
  types.append("float32");
  types.append("float64");

  return types;
}

} // anonymous namespace

Renderer::Renderer(): m_showLines(false), m_quiet(false), m_numItemsPerLine(1)
{
  this->m_dataDescr = new vector<string>();
  this->types = getTypes();
}

Renderer::~Renderer()
{
  if (this->m_dataDescr != NULL)
    delete this->m_dataDescr;
  this->m_dataDescr = NULL;
}

void Renderer::setDataDescr(const std::string & descr)
{
  if (! this->types.has(descr)) {
    stringstream msg;
    msg << "Encountered unknown type \"" << descr << "\". Allowed types are: "
	<< this->types;
    throw runtime_error(msg.str());
  }
  this->m_dataDescr->clear();
  this->m_dataDescr->push_back(descr);
}

void Renderer::showLines(const bool showLines)
{
  this->m_showLines = showLines;
}

bool Renderer::showLines() const
{
  return this->m_showLines;
}

void Renderer::numItemsPerLine(const std::size_t numItems)
{
  if (numItems > 0)
    m_numItemsPerLine = numItems;
  else
    throw std::runtime_error("Tried to set number of items per line to less than 1");
}

std::size_t Renderer::numItemsPerLine()
{
  return m_numItemsPerLine;
}

void Renderer::quiet(const bool value)
{
  m_quiet = value;
}

bool Renderer::quiet()
{
  return m_quiet;
}

template <typename NumT>
void Renderer::innerShowData(BinFile &file, size_t offset, size_t length)
{
  // this is used for printing line numbers
  size_t myOffset = 0;
  if ((offset % sizeof(NumT)) == 0)
    myOffset = offset / sizeof(NumT);

  Statistics<NumT> stats; // object for generating statistics
  vector<NumT> data;
  size_t totalItems = this->numItemsPerLine() - 1;
  size_t items = 0;
  file.read(data, length);
  if (!(data.empty())) {
    stats.parseData(data);
    if (!m_quiet)
    {
      for (size_t i = 0; i < data.size(); i++) {
        if (this->m_showLines)
          cout << (myOffset + i + 1) << " "; // start counting with one
        cout << toStr(data[i]);
        if (items < totalItems)
        {
          cout << "\t";
          items += 1;
        }
        else
        {
          cout << EOL;
          items = 0;
        }
      }
    }

  }
  cout << stats << endl;
}

/// Special version for strings
template <>
void Renderer::innerShowData<char>(BinFile &file, size_t offset, size_t length)
{
  StringStatistics stats;
  stringstream data;
  file.read(data, length);
  if (!m_quiet)
    cout << data.str();
  stats.parseData(data);
  cout << stats << endl;
}

void Renderer::showData(BinFile &file, size_t offset, size_t length)
{
  // TODO have debug mode for this print statement
  // cout << "Renderer.showData(file, " << offset << ", " << length << ")" << endl;
  file.seek(offset);

  if (this->m_dataDescr->size() != 1)
    throw runtime_error("Do not know how to deal with multi-type data");
  string descr = this->m_dataDescr->at(0);

  // TODO calculate information on the fly rather than reading in the whole file
  // TODO there was the ability to show integrated values
  // TODO ? there was the ability to filter out tof error events

  if (descr == "char")
    innerShowData<char>(file, offset, length);
  else if (descr == "uint8")
    innerShowData<uint8_t>(file, offset, length);
  else if (descr == "int8")
    innerShowData<int8_t>(file, offset, length);
  else if (descr == "uint16")
    innerShowData<uint16_t>(file, offset, length);
  else if (descr == "int16")
    innerShowData<int16_t>(file, offset, length);
  else if (descr == "uint32")
    innerShowData<uint32_t>(file, offset, length);
  else if (descr == "int32")
    innerShowData<int32_t>(file, offset, length);
  else if (descr == "uint64")
    innerShowData<uint64_t>(file, offset, length);
  else if (descr == "int64")
    innerShowData<int64_t>(file, offset, length);
  else if (descr == "float32" || descr == "float")
    innerShowData<float>(file, offset, length);
  else if (descr == "float64" || descr == "double")
    innerShowData<double>(file, offset, length);
  else
    throw runtime_error("The code should have never gotten to this place");
}

const std::string getKnownDataDescr()
{
  stringstream msg;
  msg << getTypes();
  return msg.str();
}

} // namespace render
<commit_msg>Refs #15. Pointing out unused variable.<commit_after>#include <stdint.h>
#include <iostream>
#include <stdexcept>

#include "allowedtypes.hpp"
#include "bin_file.hpp"
#include "renderer.hpp"
#include "statistics.hpp"
#include "string_util.hpp"

using allowed::AllowedTypes;
using std::cout;
using std::endl;
using std::runtime_error;
using std::size_t;
using std::string;
using std::stringstream;
using std::vector;

namespace render {

namespace { // anonymous namespace

static const string EOL("\n");

AllowedTypes getTypes()
{
  AllowedTypes types;
  types.append("char");
  types.append("uint8");
  types.append("uint16");
  types.append("uint32");
  types.append("uint64");
  types.append("int8");
  types.append("int16");
  types.append("int32");
  types.append("int64");
  types.append("float32");
  types.append("float64");

  return types;
}

} // anonymous namespace

Renderer::Renderer(): m_showLines(false), m_quiet(false), m_numItemsPerLine(1)
{
  this->m_dataDescr = new vector<string>();
  this->types = getTypes();
}

Renderer::~Renderer()
{
  if (this->m_dataDescr != NULL)
    delete this->m_dataDescr;
  this->m_dataDescr = NULL;
}

void Renderer::setDataDescr(const std::string & descr)
{
  if (! this->types.has(descr)) {
    stringstream msg;
    msg << "Encountered unknown type \"" << descr << "\". Allowed types are: "
	<< this->types;
    throw runtime_error(msg.str());
  }
  this->m_dataDescr->clear();
  this->m_dataDescr->push_back(descr);
}

void Renderer::showLines(const bool showLines)
{
  this->m_showLines = showLines;
}

bool Renderer::showLines() const
{
  return this->m_showLines;
}

void Renderer::numItemsPerLine(const std::size_t numItems)
{
  if (numItems > 0)
    m_numItemsPerLine = numItems;
  else
    throw std::runtime_error("Tried to set number of items per line to less than 1");
}

std::size_t Renderer::numItemsPerLine()
{
  return m_numItemsPerLine;
}

void Renderer::quiet(const bool value)
{
  m_quiet = value;
}

bool Renderer::quiet()
{
  return m_quiet;
}

template <typename NumT>
void Renderer::innerShowData(BinFile &file, size_t offset, size_t length)
{
  // this is used for printing line numbers
  size_t myOffset = 0;
  if ((offset % sizeof(NumT)) == 0)
    myOffset = offset / sizeof(NumT);

  Statistics<NumT> stats; // object for generating statistics
  vector<NumT> data;
  size_t totalItems = this->numItemsPerLine() - 1;
  size_t items = 0;
  file.read(data, length);
  if (!(data.empty())) {
    stats.parseData(data);
    if (!m_quiet)
    {
      for (size_t i = 0; i < data.size(); i++) {
        if (this->m_showLines)
          cout << (myOffset + i + 1) << " "; // start counting with one
        cout << toStr(data[i]);
        if (items < totalItems)
        {
          cout << "\t";
          items += 1;
        }
        else
        {
          cout << EOL;
          items = 0;
        }
      }
    }

  }
  cout << stats << endl;
}

/// Special version for strings
template <>
void Renderer::innerShowData<char>(BinFile &file, size_t offset, size_t length)
{
  //offset is required by interface but not needed
  (void)offset;

  StringStatistics stats;
  stringstream data;
  file.read(data, length);
  if (!m_quiet)
    cout << data.str();
  stats.parseData(data);
  cout << stats << endl;
}

void Renderer::showData(BinFile &file, size_t offset, size_t length)
{
  // TODO have debug mode for this print statement
  // cout << "Renderer.showData(file, " << offset << ", " << length << ")" << endl;
  file.seek(offset);

  if (this->m_dataDescr->size() != 1)
    throw runtime_error("Do not know how to deal with multi-type data");
  string descr = this->m_dataDescr->at(0);

  // TODO calculate information on the fly rather than reading in the whole file
  // TODO there was the ability to show integrated values
  // TODO ? there was the ability to filter out tof error events

  if (descr == "char")
    innerShowData<char>(file, offset, length);
  else if (descr == "uint8")
    innerShowData<uint8_t>(file, offset, length);
  else if (descr == "int8")
    innerShowData<int8_t>(file, offset, length);
  else if (descr == "uint16")
    innerShowData<uint16_t>(file, offset, length);
  else if (descr == "int16")
    innerShowData<int16_t>(file, offset, length);
  else if (descr == "uint32")
    innerShowData<uint32_t>(file, offset, length);
  else if (descr == "int32")
    innerShowData<int32_t>(file, offset, length);
  else if (descr == "uint64")
    innerShowData<uint64_t>(file, offset, length);
  else if (descr == "int64")
    innerShowData<int64_t>(file, offset, length);
  else if (descr == "float32" || descr == "float")
    innerShowData<float>(file, offset, length);
  else if (descr == "float64" || descr == "double")
    innerShowData<double>(file, offset, length);
  else
    throw runtime_error("The code should have never gotten to this place");
}

const std::string getKnownDataDescr()
{
  stringstream msg;
  msg << getTypes();
  return msg.str();
}

} // namespace render
<|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.
 */

/*
 * Modified by Cloudius Systems
 * Copyright 2015 Cloudius Systems
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla 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 Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <memory>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <boost/range/adaptor/map.hpp>

#include <core/future.hh>
#include <core/sharded.hh>

#include "commitlog.hh"
#include "commitlog_replayer.hh"
#include "database.hh"
#include "sstables/sstables.hh"
#include "db/system_keyspace.hh"
#include "db/serializer.hh"
#include "cql3/query_processor.hh"
#include "log.hh"
#include "converting_mutation_partition_applier.hh"
#include "schema_registry.hh"
#include "commitlog_entry.hh"

static logging::logger logger("commitlog_replayer");

class db::commitlog_replayer::impl {
    std::unordered_map<table_schema_version, column_mapping> _column_mappings;
public:
    impl(seastar::sharded<cql3::query_processor>& db);

    future<> init();

    struct stats {
        uint64_t invalid_mutations = 0;
        uint64_t skipped_mutations = 0;
        uint64_t applied_mutations = 0;
        uint64_t corrupt_bytes = 0;

        stats& operator+=(const stats& s) {
            invalid_mutations += s.invalid_mutations;
            skipped_mutations += s.skipped_mutations;
            applied_mutations += s.applied_mutations;
            corrupt_bytes += s.corrupt_bytes;
            return *this;
        }
        stats operator+(const stats& s) const {
            stats tmp = *this;
            tmp += s;
            return tmp;
        }
    };

    future<> process(stats*, temporary_buffer<char> buf, replay_position rp);
    future<stats> recover(sstring file);

    typedef std::unordered_map<utils::UUID, replay_position> rp_map;
    typedef std::unordered_map<unsigned, rp_map> shard_rpm_map;
    typedef std::unordered_map<unsigned, replay_position> shard_rp_map;

    seastar::sharded<cql3::query_processor>&
        _qp;
    shard_rpm_map
        _rpm;
    shard_rp_map
        _min_pos;
};

db::commitlog_replayer::impl::impl(seastar::sharded<cql3::query_processor>& qp)
    : _qp(qp)
{}

future<> db::commitlog_replayer::impl::init() {
    return _qp.map_reduce([this](shard_rpm_map map) {
        for (auto& p1 : map) {
            for (auto& p2 : p1.second) {
                auto& pp = _rpm[p1.first][p2.first];
                pp = std::max(pp, p2.second);

                auto& min = _min_pos[p1.first];
                min = (min == replay_position()) ? p2.second : std::min(p2.second, min);
            }
        }
    }, [this](cql3::query_processor& qp) {
        return do_with(shard_rpm_map{}, [this, &qp](shard_rpm_map& map) {
            return parallel_for_each(qp.db().local().get_column_families(), [&map, &qp](auto& cfp) {
                auto uuid = cfp.first;
                for (auto& sst : *cfp.second->get_sstables() | boost::adaptors::map_values) {
                    try {
                        auto p = sst->get_stats_metadata().position;
                        logger.trace("sstable {} -> rp {}", sst->get_filename(), p);
                        if (p != replay_position()) {
                            auto& pp = map[p.shard_id()][uuid];
                            pp = std::max(pp, p);
                        }
                    } catch (...) {
                        logger.warn("Could not read sstable metadata {}", std::current_exception());
                    }
                }
                // We do this on each cpu, for each CF, which technically is a little wasteful, but the values are
                // cached, this is only startup, and it makes the code easier.
                // Get all truncation records for the CF and initialize max rps if
                // present. Cannot do this on demand, as there may be no sstables to
                // mark the CF as "needed".
                return db::system_keyspace::get_truncated_position(uuid).then([&map, &uuid](std::vector<db::replay_position> tpps) {
                    for (auto& p : tpps) {
                        logger.trace("CF {} truncated at {}", uuid, p);
                        auto& pp = map[p.shard_id()][uuid];
                        pp = std::max(pp, p);
                    }
                });
            }).then([&map] {
                return make_ready_future<shard_rpm_map>(map);
            });
        });
    }).finally([this] {
        for (auto&p : _min_pos) {
            logger.debug("minimum position for shard {}: {}", p.first, p.second);
        }
        for (auto&p1 : _rpm) {
            for (auto& p2 : p1.second) {
                logger.debug("replay position for shard/uuid {}/{}: {}", p1.first, p2.first, p2.second);
            }
        }
    });
}

future<db::commitlog_replayer::impl::stats>
db::commitlog_replayer::impl::recover(sstring file) {
    replay_position rp{commitlog::descriptor(file)};
    auto gp = _min_pos[rp.shard_id()];

    if (rp.id < gp.id) {
        logger.debug("skipping replay of fully-flushed {}", file);
        return make_ready_future<stats>();
    }
    position_type p = 0;
    if (rp.id == gp.id) {
        p = gp.pos;
    }

    auto s = make_lw_shared<stats>();

    return db::commitlog::read_log_file(file,
            std::bind(&impl::process, this, s.get(), std::placeholders::_1,
                    std::placeholders::_2), p).then([](auto s) {
        auto f = s->done();
        return f.finally([s = std::move(s)] {});
    }).then_wrapped([s](future<> f) {
        try {
            f.get();
        } catch (commitlog::segment_data_corruption_error& e) {
            s->corrupt_bytes += e.bytes();
        } catch (...) {
            throw;
        }
        return make_ready_future<stats>(*s);
    });
}

future<> db::commitlog_replayer::impl::process(stats* s, temporary_buffer<char> buf, replay_position rp) {
    try {

        commitlog_entry_reader cer(buf);
        auto& fm = cer.mutation();

        auto cm_it = _column_mappings.find(fm.schema_version());
        if (cm_it == _column_mappings.end()) {
            if (!cer.get_column_mapping()) {
                throw std::runtime_error(sprint("unknown schema version {}", fm.schema_version()));
            }
            logger.debug("new schema version {} in entry {}", fm.schema_version(), rp);
            cm_it = _column_mappings.emplace(fm.schema_version(), *cer.get_column_mapping()).first;
        }

        auto shard_id = rp.shard_id();
        if (rp < _min_pos[shard_id]) {
            logger.trace("entry {} is less than global min position. skipping", rp);
            s->skipped_mutations++;
            return make_ready_future<>();
        }

        auto uuid = fm.column_family_id();
        auto& map = _rpm[shard_id];
        auto i = map.find(uuid);
        if (i != map.end() && rp <= i->second) {
            logger.trace("entry {} at {} is younger than recorded replay position {}. skipping", fm.column_family_id(), rp, i->second);
            s->skipped_mutations++;
            return make_ready_future<>();
        }

        auto shard = _qp.local().db().local().shard_of(fm);
        return _qp.local().db().invoke_on(shard, [this, cer = std::move(cer), cm_it, rp, shard, s] (database& db) -> future<> {
            auto& fm = cer.mutation();
            // TODO: might need better verification that the deserialized mutation
            // is schema compatible. My guess is that just applying the mutation
            // will not do this.
            auto& cf = db.find_column_family(fm.column_family_id());

            if (logger.is_enabled(logging::log_level::debug)) {
                logger.debug("replaying at {} v={} {}:{} at {}", fm.column_family_id(), fm.schema_version(),
                        cf.schema()->ks_name(), cf.schema()->cf_name(), rp);
            }
            // Removed forwarding "new" RP. Instead give none/empty.
            // This is what origin does, and it should be fine.
            // The end result should be that once sstables are flushed out
            // their "replay_position" attribute will be empty, which is
            // lower than anything the new session will produce.
            if (cf.schema()->version() != fm.schema_version()) {
                const column_mapping& cm = cm_it->second;
                mutation m(fm.decorated_key(*cf.schema()), cf.schema());
                converting_mutation_partition_applier v(cm, *cf.schema(), m.partition());
                fm.partition().accept(cm, v);
                cf.apply(std::move(m));
            } else {
                cf.apply(fm, cf.schema());
            }
            s->applied_mutations++;
            return make_ready_future<>();
        }).handle_exception([s](auto ep) {
            s->invalid_mutations++;
            // TODO: write mutation to file like origin.
            logger.warn("error replaying: {}", ep);
        });
    } catch (no_such_column_family&) {
        // No such CF now? Origin just ignores this.
    } catch (...) {
        s->invalid_mutations++;
        // TODO: write mutation to file like origin.
        logger.warn("error replaying: {}", std::current_exception());
    }

    return make_ready_future<>();
}

db::commitlog_replayer::commitlog_replayer(seastar::sharded<cql3::query_processor>& qp)
    : _impl(std::make_unique<impl>(qp))
{}

db::commitlog_replayer::commitlog_replayer(commitlog_replayer&& r) noexcept
    : _impl(std::move(r._impl))
{}

db::commitlog_replayer::~commitlog_replayer()
{}

future<db::commitlog_replayer> db::commitlog_replayer::create_replayer(seastar::sharded<cql3::query_processor>& qp) {
    return do_with(commitlog_replayer(qp), [](auto&& rp) {
        auto f = rp._impl->init();
        return f.then([rp = std::move(rp)]() mutable {
            return make_ready_future<commitlog_replayer>(std::move(rp));
        });
    });
}

future<> db::commitlog_replayer::recover(std::vector<sstring> files) {
    logger.info("Replaying {}", join(", ", files));
    return map_reduce(files, [this](auto f) {
        logger.debug("Replaying {}", f);
        return _impl->recover(f).then([f](impl::stats stats) {
            if (stats.corrupt_bytes != 0) {
                logger.warn("Corrupted file: {}. {} bytes skipped.", f, stats.corrupt_bytes);
            }
            logger.debug("Log replay of {} complete, {} replayed mutations ({} invalid, {} skipped)"
                            , f
                            , stats.applied_mutations
                            , stats.invalid_mutations
                            , stats.skipped_mutations
            );
            return make_ready_future<impl::stats>(stats);
        }).handle_exception([f](auto ep) -> future<impl::stats> {
            logger.error("Error recovering {}: {}", f, ep);
            try {
                std::rethrow_exception(ep);
            } catch (std::invalid_argument&) {
                logger.error("Scylla cannot process {}. Make sure to fully flush all Cassandra commit log files to sstable before migrating.");
                throw;
            } catch (...) {
                throw;
            }
        });
    }, impl::stats(), std::plus<impl::stats>()).then([](impl::stats totals) {
        logger.info("Log replay complete, {} replayed mutations ({} invalid, {} skipped)"
                        , totals.applied_mutations
                        , totals.invalid_mutations
                        , totals.skipped_mutations
        );
    });
}

future<> db::commitlog_replayer::recover(sstring f) {
    return recover(std::vector<sstring>{ f });
}

<commit_msg>db/commitlog: Fix debug log format string in commitlog_replayer::recover()<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.
 */

/*
 * Modified by Cloudius Systems
 * Copyright 2015 Cloudius Systems
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla 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 Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <memory>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <boost/range/adaptor/map.hpp>

#include <core/future.hh>
#include <core/sharded.hh>

#include "commitlog.hh"
#include "commitlog_replayer.hh"
#include "database.hh"
#include "sstables/sstables.hh"
#include "db/system_keyspace.hh"
#include "db/serializer.hh"
#include "cql3/query_processor.hh"
#include "log.hh"
#include "converting_mutation_partition_applier.hh"
#include "schema_registry.hh"
#include "commitlog_entry.hh"

static logging::logger logger("commitlog_replayer");

class db::commitlog_replayer::impl {
    std::unordered_map<table_schema_version, column_mapping> _column_mappings;
public:
    impl(seastar::sharded<cql3::query_processor>& db);

    future<> init();

    struct stats {
        uint64_t invalid_mutations = 0;
        uint64_t skipped_mutations = 0;
        uint64_t applied_mutations = 0;
        uint64_t corrupt_bytes = 0;

        stats& operator+=(const stats& s) {
            invalid_mutations += s.invalid_mutations;
            skipped_mutations += s.skipped_mutations;
            applied_mutations += s.applied_mutations;
            corrupt_bytes += s.corrupt_bytes;
            return *this;
        }
        stats operator+(const stats& s) const {
            stats tmp = *this;
            tmp += s;
            return tmp;
        }
    };

    future<> process(stats*, temporary_buffer<char> buf, replay_position rp);
    future<stats> recover(sstring file);

    typedef std::unordered_map<utils::UUID, replay_position> rp_map;
    typedef std::unordered_map<unsigned, rp_map> shard_rpm_map;
    typedef std::unordered_map<unsigned, replay_position> shard_rp_map;

    seastar::sharded<cql3::query_processor>&
        _qp;
    shard_rpm_map
        _rpm;
    shard_rp_map
        _min_pos;
};

db::commitlog_replayer::impl::impl(seastar::sharded<cql3::query_processor>& qp)
    : _qp(qp)
{}

future<> db::commitlog_replayer::impl::init() {
    return _qp.map_reduce([this](shard_rpm_map map) {
        for (auto& p1 : map) {
            for (auto& p2 : p1.second) {
                auto& pp = _rpm[p1.first][p2.first];
                pp = std::max(pp, p2.second);

                auto& min = _min_pos[p1.first];
                min = (min == replay_position()) ? p2.second : std::min(p2.second, min);
            }
        }
    }, [this](cql3::query_processor& qp) {
        return do_with(shard_rpm_map{}, [this, &qp](shard_rpm_map& map) {
            return parallel_for_each(qp.db().local().get_column_families(), [&map, &qp](auto& cfp) {
                auto uuid = cfp.first;
                for (auto& sst : *cfp.second->get_sstables() | boost::adaptors::map_values) {
                    try {
                        auto p = sst->get_stats_metadata().position;
                        logger.trace("sstable {} -> rp {}", sst->get_filename(), p);
                        if (p != replay_position()) {
                            auto& pp = map[p.shard_id()][uuid];
                            pp = std::max(pp, p);
                        }
                    } catch (...) {
                        logger.warn("Could not read sstable metadata {}", std::current_exception());
                    }
                }
                // We do this on each cpu, for each CF, which technically is a little wasteful, but the values are
                // cached, this is only startup, and it makes the code easier.
                // Get all truncation records for the CF and initialize max rps if
                // present. Cannot do this on demand, as there may be no sstables to
                // mark the CF as "needed".
                return db::system_keyspace::get_truncated_position(uuid).then([&map, &uuid](std::vector<db::replay_position> tpps) {
                    for (auto& p : tpps) {
                        logger.trace("CF {} truncated at {}", uuid, p);
                        auto& pp = map[p.shard_id()][uuid];
                        pp = std::max(pp, p);
                    }
                });
            }).then([&map] {
                return make_ready_future<shard_rpm_map>(map);
            });
        });
    }).finally([this] {
        for (auto&p : _min_pos) {
            logger.debug("minimum position for shard {}: {}", p.first, p.second);
        }
        for (auto&p1 : _rpm) {
            for (auto& p2 : p1.second) {
                logger.debug("replay position for shard/uuid {}/{}: {}", p1.first, p2.first, p2.second);
            }
        }
    });
}

future<db::commitlog_replayer::impl::stats>
db::commitlog_replayer::impl::recover(sstring file) {
    replay_position rp{commitlog::descriptor(file)};
    auto gp = _min_pos[rp.shard_id()];

    if (rp.id < gp.id) {
        logger.debug("skipping replay of fully-flushed {}", file);
        return make_ready_future<stats>();
    }
    position_type p = 0;
    if (rp.id == gp.id) {
        p = gp.pos;
    }

    auto s = make_lw_shared<stats>();

    return db::commitlog::read_log_file(file,
            std::bind(&impl::process, this, s.get(), std::placeholders::_1,
                    std::placeholders::_2), p).then([](auto s) {
        auto f = s->done();
        return f.finally([s = std::move(s)] {});
    }).then_wrapped([s](future<> f) {
        try {
            f.get();
        } catch (commitlog::segment_data_corruption_error& e) {
            s->corrupt_bytes += e.bytes();
        } catch (...) {
            throw;
        }
        return make_ready_future<stats>(*s);
    });
}

future<> db::commitlog_replayer::impl::process(stats* s, temporary_buffer<char> buf, replay_position rp) {
    try {

        commitlog_entry_reader cer(buf);
        auto& fm = cer.mutation();

        auto cm_it = _column_mappings.find(fm.schema_version());
        if (cm_it == _column_mappings.end()) {
            if (!cer.get_column_mapping()) {
                throw std::runtime_error(sprint("unknown schema version {}", fm.schema_version()));
            }
            logger.debug("new schema version {} in entry {}", fm.schema_version(), rp);
            cm_it = _column_mappings.emplace(fm.schema_version(), *cer.get_column_mapping()).first;
        }

        auto shard_id = rp.shard_id();
        if (rp < _min_pos[shard_id]) {
            logger.trace("entry {} is less than global min position. skipping", rp);
            s->skipped_mutations++;
            return make_ready_future<>();
        }

        auto uuid = fm.column_family_id();
        auto& map = _rpm[shard_id];
        auto i = map.find(uuid);
        if (i != map.end() && rp <= i->second) {
            logger.trace("entry {} at {} is younger than recorded replay position {}. skipping", fm.column_family_id(), rp, i->second);
            s->skipped_mutations++;
            return make_ready_future<>();
        }

        auto shard = _qp.local().db().local().shard_of(fm);
        return _qp.local().db().invoke_on(shard, [this, cer = std::move(cer), cm_it, rp, shard, s] (database& db) -> future<> {
            auto& fm = cer.mutation();
            // TODO: might need better verification that the deserialized mutation
            // is schema compatible. My guess is that just applying the mutation
            // will not do this.
            auto& cf = db.find_column_family(fm.column_family_id());

            if (logger.is_enabled(logging::log_level::debug)) {
                logger.debug("replaying at {} v={} {}:{} at {}", fm.column_family_id(), fm.schema_version(),
                        cf.schema()->ks_name(), cf.schema()->cf_name(), rp);
            }
            // Removed forwarding "new" RP. Instead give none/empty.
            // This is what origin does, and it should be fine.
            // The end result should be that once sstables are flushed out
            // their "replay_position" attribute will be empty, which is
            // lower than anything the new session will produce.
            if (cf.schema()->version() != fm.schema_version()) {
                const column_mapping& cm = cm_it->second;
                mutation m(fm.decorated_key(*cf.schema()), cf.schema());
                converting_mutation_partition_applier v(cm, *cf.schema(), m.partition());
                fm.partition().accept(cm, v);
                cf.apply(std::move(m));
            } else {
                cf.apply(fm, cf.schema());
            }
            s->applied_mutations++;
            return make_ready_future<>();
        }).handle_exception([s](auto ep) {
            s->invalid_mutations++;
            // TODO: write mutation to file like origin.
            logger.warn("error replaying: {}", ep);
        });
    } catch (no_such_column_family&) {
        // No such CF now? Origin just ignores this.
    } catch (...) {
        s->invalid_mutations++;
        // TODO: write mutation to file like origin.
        logger.warn("error replaying: {}", std::current_exception());
    }

    return make_ready_future<>();
}

db::commitlog_replayer::commitlog_replayer(seastar::sharded<cql3::query_processor>& qp)
    : _impl(std::make_unique<impl>(qp))
{}

db::commitlog_replayer::commitlog_replayer(commitlog_replayer&& r) noexcept
    : _impl(std::move(r._impl))
{}

db::commitlog_replayer::~commitlog_replayer()
{}

future<db::commitlog_replayer> db::commitlog_replayer::create_replayer(seastar::sharded<cql3::query_processor>& qp) {
    return do_with(commitlog_replayer(qp), [](auto&& rp) {
        auto f = rp._impl->init();
        return f.then([rp = std::move(rp)]() mutable {
            return make_ready_future<commitlog_replayer>(std::move(rp));
        });
    });
}

future<> db::commitlog_replayer::recover(std::vector<sstring> files) {
    logger.info("Replaying {}", join(", ", files));
    return map_reduce(files, [this](auto f) {
        logger.debug("Replaying {}", f);
        return _impl->recover(f).then([f](impl::stats stats) {
            if (stats.corrupt_bytes != 0) {
                logger.warn("Corrupted file: {}. {} bytes skipped.", f, stats.corrupt_bytes);
            }
            logger.debug("Log replay of {} complete, {} replayed mutations ({} invalid, {} skipped)"
                            , f
                            , stats.applied_mutations
                            , stats.invalid_mutations
                            , stats.skipped_mutations
            );
            return make_ready_future<impl::stats>(stats);
        }).handle_exception([f](auto ep) -> future<impl::stats> {
            logger.error("Error recovering {}: {}", f, ep);
            try {
                std::rethrow_exception(ep);
            } catch (std::invalid_argument&) {
                logger.error("Scylla cannot process {}. Make sure to fully flush all Cassandra commit log files to sstable before migrating.", f);
                throw;
            } catch (...) {
                throw;
            }
        });
    }, impl::stats(), std::plus<impl::stats>()).then([](impl::stats totals) {
        logger.info("Log replay complete, {} replayed mutations ({} invalid, {} skipped)"
                        , totals.applied_mutations
                        , totals.invalid_mutations
                        , totals.skipped_mutations
        );
    });
}

future<> db::commitlog_replayer::recover(sstring f) {
    return recover(std::vector<sstring>{ f });
}

<|endoftext|>


<commit_before>//===--- OwnershipModelEliminator.cpp - Eliminate SILOwnership Instr. -----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
///  \file
///
///  This file contains a small pass that lowers SIL ownership instructions to
///  their constituent operations. This will enable us to separate
///  implementation
///  of Semantic ARC in SIL and SILGen from ensuring that all of the optimizer
///  passes respect Semantic ARC. This is done by running this pass right after
///  SILGen and as the pass pipeline is updated, moving this pass further and
///  further back in the pipeline.
///
//===----------------------------------------------------------------------===//

#define DEBUG_TYPE "sil-ownership-model-eliminator"
#include "swift/SIL/Projection.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILVisitor.h"
#include "swift/SILOptimizer/Analysis/SimplifyInstruction.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/Local.h"
#include "llvm/Support/CommandLine.h"

using namespace swift;

// Utility command line argument to dump the module before we eliminate
// ownership from it.
static llvm::cl::opt<std::string>
DumpBefore("sil-dump-before-ome-to-path", llvm::cl::Hidden);

//===----------------------------------------------------------------------===//
//                               Implementation
//===----------------------------------------------------------------------===//

namespace {

struct OwnershipModelEliminatorVisitor
    : SILInstructionVisitor<OwnershipModelEliminatorVisitor, bool> {
  SILBuilder &B;
  SILOpenedArchetypesTracker OpenedArchetypesTracker;

  OwnershipModelEliminatorVisitor(SILBuilder &B)
      : B(B), OpenedArchetypesTracker(&B.getFunction()) {
    B.setOpenedArchetypesTracker(&OpenedArchetypesTracker);
  }

  void beforeVisit(SILInstruction *I) {
    B.setInsertionPoint(I);
    B.setCurrentDebugScope(I->getDebugScope());
  }

  bool visitSILInstruction(SILInstruction *I) { return false; }
  bool visitLoadInst(LoadInst *LI);
  bool visitStoreInst(StoreInst *SI);
  bool visitStoreBorrowInst(StoreBorrowInst *SI);
  bool visitCopyValueInst(CopyValueInst *CVI);
  bool visitDestroyValueInst(DestroyValueInst *DVI);
  bool visitLoadBorrowInst(LoadBorrowInst *LBI);
  bool visitBeginBorrowInst(BeginBorrowInst *BBI) {
    BBI->replaceAllUsesWith(BBI->getOperand());
    BBI->eraseFromParent();
    return true;
  }
  bool visitEndBorrowInst(EndBorrowInst *EBI) {
    EBI->eraseFromParent();
    return true;
  }
  bool visitEndLifetimeInst(EndLifetimeInst *ELI) {
    ELI->eraseFromParent();
    return true;
  }
  bool visitUncheckedOwnershipConversionInst(
      UncheckedOwnershipConversionInst *UOCI) {
    UOCI->replaceAllUsesWith(UOCI->getOperand());
    UOCI->eraseFromParent();
    return true;
  }
  bool visitUnmanagedRetainValueInst(UnmanagedRetainValueInst *URVI);
  bool visitUnmanagedReleaseValueInst(UnmanagedReleaseValueInst *URVI);
  bool visitUnmanagedAutoreleaseValueInst(UnmanagedAutoreleaseValueInst *UAVI);
  bool visitCheckedCastBranchInst(CheckedCastBranchInst *CBI);
  bool visitSwitchEnumInst(SwitchEnumInst *SWI);
  bool visitDestructureStructInst(DestructureStructInst *DSI);
  bool visitDestructureTupleInst(DestructureTupleInst *DTI);
};

} // end anonymous namespace

bool OwnershipModelEliminatorVisitor::visitLoadInst(LoadInst *LI) {
  auto Qualifier = LI->getOwnershipQualifier();

  // If the qualifier is unqualified, there is nothing further to do
  // here. Just return.
  if (Qualifier == LoadOwnershipQualifier::Unqualified)
    return false;

  SILValue Result = B.emitLoadValueOperation(LI->getLoc(), LI->getOperand(),
                                             LI->getOwnershipQualifier());

  // Then remove the qualified load and use the unqualified load as the def of
  // all of LI's uses.
  LI->replaceAllUsesWith(Result);
  LI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitStoreInst(StoreInst *SI) {
  auto Qualifier = SI->getOwnershipQualifier();

  // If the qualifier is unqualified, there is nothing further to do
  // here. Just return.
  if (Qualifier == StoreOwnershipQualifier::Unqualified)
    return false;

  B.emitStoreValueOperation(SI->getLoc(), SI->getSrc(), SI->getDest(),
                            SI->getOwnershipQualifier());

  // Then remove the qualified store.
  SI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitStoreBorrowInst(
    StoreBorrowInst *SI) {
  B.emitStoreValueOperation(SI->getLoc(), SI->getSrc(), SI->getDest(),
                            StoreOwnershipQualifier::Init);

  // Then remove the qualified store.
  SI->eraseFromParent();
  return true;
}

bool
OwnershipModelEliminatorVisitor::visitLoadBorrowInst(LoadBorrowInst *LBI) {
  // Break down the load borrow into an unqualified load.
  auto *UnqualifiedLoad = B.createLoad(LBI->getLoc(), LBI->getOperand(),
                                       LoadOwnershipQualifier::Unqualified);

  // Then remove the qualified load and use the unqualified load as the def of
  // all of LI's uses.
  LBI->replaceAllUsesWith(UnqualifiedLoad);
  LBI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitCopyValueInst(CopyValueInst *CVI) {
  // A copy_value of an address-only type cannot be replaced.
  if (CVI->getType().isAddressOnly(B.getFunction()))
    return false;

  // Now that we have set the unqualified ownership flag, destroy value
  // operation will delegate to the appropriate strong_release, etc.
  B.emitCopyValueOperation(CVI->getLoc(), CVI->getOperand());
  CVI->replaceAllUsesWith(CVI->getOperand());
  CVI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitUnmanagedRetainValueInst(
    UnmanagedRetainValueInst *URVI) {
  // Now that we have set the unqualified ownership flag, destroy value
  // operation will delegate to the appropriate strong_release, etc.
  B.emitCopyValueOperation(URVI->getLoc(), URVI->getOperand());
  URVI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitUnmanagedReleaseValueInst(
    UnmanagedReleaseValueInst *URVI) {
  // Now that we have set the unqualified ownership flag, destroy value
  // operation will delegate to the appropriate strong_release, etc.
  B.emitDestroyValueOperation(URVI->getLoc(), URVI->getOperand());
  URVI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitUnmanagedAutoreleaseValueInst(
    UnmanagedAutoreleaseValueInst *UAVI) {
  // Now that we have set the unqualified ownership flag, destroy value
  // operation will delegate to the appropriate strong_release, etc.
  B.createAutoreleaseValue(UAVI->getLoc(), UAVI->getOperand(),
                           UAVI->getAtomicity());
  UAVI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitDestroyValueInst(DestroyValueInst *DVI) {
  // A destroy_value of an address-only type cannot be replaced.
  if (DVI->getOperand()->getType().isAddressOnly(B.getFunction()))
    return false;

  // Now that we have set the unqualified ownership flag, destroy value
  // operation will delegate to the appropriate strong_release, etc.
  B.emitDestroyValueOperation(DVI->getLoc(), DVI->getOperand());
  DVI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitCheckedCastBranchInst(
    CheckedCastBranchInst *CBI) {
  // In ownership qualified SIL, checked_cast_br must pass its argument to the
  // fail case so we can clean it up. In non-ownership qualified SIL, we expect
  // no argument from the checked_cast_br in the default case. The way that we
  // handle this transformation is that:
  //
  // 1. We replace all uses of the argument to the false block with a use of the
  // checked cast branch's operand.
  // 2. We delete the argument from the false block.
  SILBasicBlock *FailureBlock = CBI->getFailureBB();
  if (FailureBlock->getNumArguments() == 0)
    return false;
  FailureBlock->getArgument(0)->replaceAllUsesWith(CBI->getOperand());
  FailureBlock->eraseArgument(0);
  return true;
}

bool OwnershipModelEliminatorVisitor::visitSwitchEnumInst(
    SwitchEnumInst *SWEI) {
  // In ownership qualified SIL, switch_enum must pass its argument to the fail
  // case so we can clean it up. In non-ownership qualified SIL, we expect no
  // argument from the switch_enum in the default case. The way that we handle
  // this transformation is that:
  //
  // 1. We replace all uses of the argument to the false block with a use of the
  // checked cast branch's operand.
  // 2. We delete the argument from the false block.
  if (!SWEI->hasDefault())
    return false;

  SILBasicBlock *DefaultBlock = SWEI->getDefaultBB();
  if (DefaultBlock->getNumArguments() == 0)
    return false;
  DefaultBlock->getArgument(0)->replaceAllUsesWith(SWEI->getOperand());
  DefaultBlock->eraseArgument(0);
  return true;
}

static void splitDestructure(SILBuilder &B, SILInstruction *I, SILValue Op) {
  assert((isa<DestructureStructInst>(I) || isa<DestructureTupleInst>(I)) &&
         "Only destructure operations can be passed to splitDestructure");

  // First before we destructure anything, see if we can simplify any of our
  // instruction operands.

  SILModule &M = I->getModule();
  SILLocation Loc = I->getLoc();
  SILType OpType = Op->getType();

  llvm::SmallVector<Projection, 8> Projections;
  Projection::getFirstLevelProjections(OpType, M, Projections);
  assert(Projections.size() == I->getNumResults());

  auto Results = I->getResults();
  for (unsigned Index : indices(Results)) {
    SILValue Result = Results[Index];

    // If our result doesnt have any uses, do not emit instructions, just skip
    // it.
    if (Result->use_empty())
      continue;

    // Otherwise, create a projection.
    const auto &Proj = Projections[Index];
    SingleValueInstruction *ProjInst =
        Proj.createObjectProjection(B, Loc, Op).get();

    // If we can simplify, do so.
    if (SILValue NewV = simplifyInstruction(ProjInst)) {
      Result->replaceAllUsesWith(NewV);
      ProjInst->eraseFromParent();
      continue;
    }

    Result->replaceAllUsesWith(ProjInst);
  }

  // We may have exposed trivially dead instructions due to
  // simplifyInstruction... delete I and any such instructions!
  recursivelyDeleteTriviallyDeadInstructions(I, true);
}

bool OwnershipModelEliminatorVisitor::visitDestructureStructInst(
    DestructureStructInst *DSI) {
  splitDestructure(B, DSI, DSI->getOperand());
  return true;
}

bool OwnershipModelEliminatorVisitor::visitDestructureTupleInst(
    DestructureTupleInst *DTI) {
  splitDestructure(B, DTI, DTI->getOperand());
  return true;
}

//===----------------------------------------------------------------------===//
//                           Top Level Entry Point
//===----------------------------------------------------------------------===//

static bool stripOwnership(SILFunction &F) {
  // If F is an external declaration, do not process it.
  if (F.isExternalDeclaration())
    return false;

  // Set F to have unqualified ownership.
  F.setOwnershipEliminated();

  bool MadeChange = false;
  SILBuilder B(F);
  OwnershipModelEliminatorVisitor Visitor(B);

  for (auto &BB : F) {
    // Change all arguments to have ValueOwnershipKind::Any.
    for (auto *Arg : BB.getArguments()) {
      Arg->setOwnershipKind(ValueOwnershipKind::Any);
    }

    for (auto II = BB.begin(), IE = BB.end(); II != IE;) {
      // Since we are going to be potentially removing instructions, we need
      // to make sure to increment our iterator before we perform any
      // visits.
      SILInstruction *I = &*II;
      ++II;

      MadeChange |= Visitor.visit(I);
    }
  }
  return MadeChange;
}

static void prepareNonTransparentSILFunctionForOptimization(ModuleDecl *,
                                                            SILFunction *F) {
  if (!F->hasOwnership() || F->isTransparent())
    return;

  LLVM_DEBUG(llvm::dbgs() << "After deserialization, stripping ownership in:"
                          << F->getName() << "\n");

  stripOwnership(*F);
}

static void prepareSILFunctionForOptimization(ModuleDecl *, SILFunction *F) {
  if (!F->hasOwnership())
    return;

  LLVM_DEBUG(llvm::dbgs() << "After deserialization, stripping ownership in:"
                          << F->getName() << "\n");

  stripOwnership(*F);
}

namespace {

struct OwnershipModelEliminator : SILModuleTransform {
  bool SkipTransparent;

  OwnershipModelEliminator(bool SkipTransparent)
      : SkipTransparent(SkipTransparent) {}

  void run() override {
    if (DumpBefore.size()) {
      getModule()->dump(DumpBefore.c_str());
    }

    auto &Mod = *getModule();
    for (auto &F : Mod) {
      // If F does not have ownership, skip it. We have no further work to do.
      if (!F.hasOwnership())
        continue;

      // If we were asked to not strip ownership from transparent functions in
      // /our/ module, continue.
      if (SkipTransparent && F.isTransparent())
        continue;

      // Verify here to make sure ownership is correct before we strip.
      F.verify();

      if (stripOwnership(F)) {
        auto InvalidKind =
            SILAnalysis::InvalidationKind::BranchesAndInstructions;
        invalidateAnalysis(&F, InvalidKind);
      }
    }

    // If we were asked to strip transparent, we are at the beginning of the
    // performance pipeline. In such a case, we register a handler so that all
    // future things we deserialize have ownership stripped.
    using NotificationHandlerTy =
        FunctionBodyDeserializationNotificationHandler;
    std::unique_ptr<DeserializationNotificationHandler> ptr;
    if (SkipTransparent) {
      ptr.reset(new NotificationHandlerTy(
          prepareNonTransparentSILFunctionForOptimization));
    } else {
      ptr.reset(new NotificationHandlerTy(prepareSILFunctionForOptimization));
    }
    Mod.registerDeserializationNotificationHandler(std::move(ptr));
  }
};

} // end anonymous namespace

SILTransform *swift::createOwnershipModelEliminator() {
  return new OwnershipModelEliminator(false /*skip transparent*/);
}

SILTransform *swift::createNonTransparentFunctionOwnershipModelEliminator() {
  return new OwnershipModelEliminator(true /*skip transparent*/);
}
<commit_msg>Revert "[ownership] Verify functions before we strip ownership."<commit_after>//===--- OwnershipModelEliminator.cpp - Eliminate SILOwnership Instr. -----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
///  \file
///
///  This file contains a small pass that lowers SIL ownership instructions to
///  their constituent operations. This will enable us to separate
///  implementation
///  of Semantic ARC in SIL and SILGen from ensuring that all of the optimizer
///  passes respect Semantic ARC. This is done by running this pass right after
///  SILGen and as the pass pipeline is updated, moving this pass further and
///  further back in the pipeline.
///
//===----------------------------------------------------------------------===//

#define DEBUG_TYPE "sil-ownership-model-eliminator"
#include "swift/SIL/Projection.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILVisitor.h"
#include "swift/SILOptimizer/Analysis/SimplifyInstruction.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/Local.h"
#include "llvm/Support/CommandLine.h"

using namespace swift;

// Utility command line argument to dump the module before we eliminate
// ownership from it.
static llvm::cl::opt<std::string>
DumpBefore("sil-dump-before-ome-to-path", llvm::cl::Hidden);

//===----------------------------------------------------------------------===//
//                               Implementation
//===----------------------------------------------------------------------===//

namespace {

struct OwnershipModelEliminatorVisitor
    : SILInstructionVisitor<OwnershipModelEliminatorVisitor, bool> {
  SILBuilder &B;
  SILOpenedArchetypesTracker OpenedArchetypesTracker;

  OwnershipModelEliminatorVisitor(SILBuilder &B)
      : B(B), OpenedArchetypesTracker(&B.getFunction()) {
    B.setOpenedArchetypesTracker(&OpenedArchetypesTracker);
  }

  void beforeVisit(SILInstruction *I) {
    B.setInsertionPoint(I);
    B.setCurrentDebugScope(I->getDebugScope());
  }

  bool visitSILInstruction(SILInstruction *I) { return false; }
  bool visitLoadInst(LoadInst *LI);
  bool visitStoreInst(StoreInst *SI);
  bool visitStoreBorrowInst(StoreBorrowInst *SI);
  bool visitCopyValueInst(CopyValueInst *CVI);
  bool visitDestroyValueInst(DestroyValueInst *DVI);
  bool visitLoadBorrowInst(LoadBorrowInst *LBI);
  bool visitBeginBorrowInst(BeginBorrowInst *BBI) {
    BBI->replaceAllUsesWith(BBI->getOperand());
    BBI->eraseFromParent();
    return true;
  }
  bool visitEndBorrowInst(EndBorrowInst *EBI) {
    EBI->eraseFromParent();
    return true;
  }
  bool visitEndLifetimeInst(EndLifetimeInst *ELI) {
    ELI->eraseFromParent();
    return true;
  }
  bool visitUncheckedOwnershipConversionInst(
      UncheckedOwnershipConversionInst *UOCI) {
    UOCI->replaceAllUsesWith(UOCI->getOperand());
    UOCI->eraseFromParent();
    return true;
  }
  bool visitUnmanagedRetainValueInst(UnmanagedRetainValueInst *URVI);
  bool visitUnmanagedReleaseValueInst(UnmanagedReleaseValueInst *URVI);
  bool visitUnmanagedAutoreleaseValueInst(UnmanagedAutoreleaseValueInst *UAVI);
  bool visitCheckedCastBranchInst(CheckedCastBranchInst *CBI);
  bool visitSwitchEnumInst(SwitchEnumInst *SWI);
  bool visitDestructureStructInst(DestructureStructInst *DSI);
  bool visitDestructureTupleInst(DestructureTupleInst *DTI);
};

} // end anonymous namespace

bool OwnershipModelEliminatorVisitor::visitLoadInst(LoadInst *LI) {
  auto Qualifier = LI->getOwnershipQualifier();

  // If the qualifier is unqualified, there is nothing further to do
  // here. Just return.
  if (Qualifier == LoadOwnershipQualifier::Unqualified)
    return false;

  SILValue Result = B.emitLoadValueOperation(LI->getLoc(), LI->getOperand(),
                                             LI->getOwnershipQualifier());

  // Then remove the qualified load and use the unqualified load as the def of
  // all of LI's uses.
  LI->replaceAllUsesWith(Result);
  LI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitStoreInst(StoreInst *SI) {
  auto Qualifier = SI->getOwnershipQualifier();

  // If the qualifier is unqualified, there is nothing further to do
  // here. Just return.
  if (Qualifier == StoreOwnershipQualifier::Unqualified)
    return false;

  B.emitStoreValueOperation(SI->getLoc(), SI->getSrc(), SI->getDest(),
                            SI->getOwnershipQualifier());

  // Then remove the qualified store.
  SI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitStoreBorrowInst(
    StoreBorrowInst *SI) {
  B.emitStoreValueOperation(SI->getLoc(), SI->getSrc(), SI->getDest(),
                            StoreOwnershipQualifier::Init);

  // Then remove the qualified store.
  SI->eraseFromParent();
  return true;
}

bool
OwnershipModelEliminatorVisitor::visitLoadBorrowInst(LoadBorrowInst *LBI) {
  // Break down the load borrow into an unqualified load.
  auto *UnqualifiedLoad = B.createLoad(LBI->getLoc(), LBI->getOperand(),
                                       LoadOwnershipQualifier::Unqualified);

  // Then remove the qualified load and use the unqualified load as the def of
  // all of LI's uses.
  LBI->replaceAllUsesWith(UnqualifiedLoad);
  LBI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitCopyValueInst(CopyValueInst *CVI) {
  // A copy_value of an address-only type cannot be replaced.
  if (CVI->getType().isAddressOnly(B.getFunction()))
    return false;

  // Now that we have set the unqualified ownership flag, destroy value
  // operation will delegate to the appropriate strong_release, etc.
  B.emitCopyValueOperation(CVI->getLoc(), CVI->getOperand());
  CVI->replaceAllUsesWith(CVI->getOperand());
  CVI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitUnmanagedRetainValueInst(
    UnmanagedRetainValueInst *URVI) {
  // Now that we have set the unqualified ownership flag, destroy value
  // operation will delegate to the appropriate strong_release, etc.
  B.emitCopyValueOperation(URVI->getLoc(), URVI->getOperand());
  URVI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitUnmanagedReleaseValueInst(
    UnmanagedReleaseValueInst *URVI) {
  // Now that we have set the unqualified ownership flag, destroy value
  // operation will delegate to the appropriate strong_release, etc.
  B.emitDestroyValueOperation(URVI->getLoc(), URVI->getOperand());
  URVI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitUnmanagedAutoreleaseValueInst(
    UnmanagedAutoreleaseValueInst *UAVI) {
  // Now that we have set the unqualified ownership flag, destroy value
  // operation will delegate to the appropriate strong_release, etc.
  B.createAutoreleaseValue(UAVI->getLoc(), UAVI->getOperand(),
                           UAVI->getAtomicity());
  UAVI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitDestroyValueInst(DestroyValueInst *DVI) {
  // A destroy_value of an address-only type cannot be replaced.
  if (DVI->getOperand()->getType().isAddressOnly(B.getFunction()))
    return false;

  // Now that we have set the unqualified ownership flag, destroy value
  // operation will delegate to the appropriate strong_release, etc.
  B.emitDestroyValueOperation(DVI->getLoc(), DVI->getOperand());
  DVI->eraseFromParent();
  return true;
}

bool OwnershipModelEliminatorVisitor::visitCheckedCastBranchInst(
    CheckedCastBranchInst *CBI) {
  // In ownership qualified SIL, checked_cast_br must pass its argument to the
  // fail case so we can clean it up. In non-ownership qualified SIL, we expect
  // no argument from the checked_cast_br in the default case. The way that we
  // handle this transformation is that:
  //
  // 1. We replace all uses of the argument to the false block with a use of the
  // checked cast branch's operand.
  // 2. We delete the argument from the false block.
  SILBasicBlock *FailureBlock = CBI->getFailureBB();
  if (FailureBlock->getNumArguments() == 0)
    return false;
  FailureBlock->getArgument(0)->replaceAllUsesWith(CBI->getOperand());
  FailureBlock->eraseArgument(0);
  return true;
}

bool OwnershipModelEliminatorVisitor::visitSwitchEnumInst(
    SwitchEnumInst *SWEI) {
  // In ownership qualified SIL, switch_enum must pass its argument to the fail
  // case so we can clean it up. In non-ownership qualified SIL, we expect no
  // argument from the switch_enum in the default case. The way that we handle
  // this transformation is that:
  //
  // 1. We replace all uses of the argument to the false block with a use of the
  // checked cast branch's operand.
  // 2. We delete the argument from the false block.
  if (!SWEI->hasDefault())
    return false;

  SILBasicBlock *DefaultBlock = SWEI->getDefaultBB();
  if (DefaultBlock->getNumArguments() == 0)
    return false;
  DefaultBlock->getArgument(0)->replaceAllUsesWith(SWEI->getOperand());
  DefaultBlock->eraseArgument(0);
  return true;
}

static void splitDestructure(SILBuilder &B, SILInstruction *I, SILValue Op) {
  assert((isa<DestructureStructInst>(I) || isa<DestructureTupleInst>(I)) &&
         "Only destructure operations can be passed to splitDestructure");

  // First before we destructure anything, see if we can simplify any of our
  // instruction operands.

  SILModule &M = I->getModule();
  SILLocation Loc = I->getLoc();
  SILType OpType = Op->getType();

  llvm::SmallVector<Projection, 8> Projections;
  Projection::getFirstLevelProjections(OpType, M, Projections);
  assert(Projections.size() == I->getNumResults());

  auto Results = I->getResults();
  for (unsigned Index : indices(Results)) {
    SILValue Result = Results[Index];

    // If our result doesnt have any uses, do not emit instructions, just skip
    // it.
    if (Result->use_empty())
      continue;

    // Otherwise, create a projection.
    const auto &Proj = Projections[Index];
    SingleValueInstruction *ProjInst =
        Proj.createObjectProjection(B, Loc, Op).get();

    // If we can simplify, do so.
    if (SILValue NewV = simplifyInstruction(ProjInst)) {
      Result->replaceAllUsesWith(NewV);
      ProjInst->eraseFromParent();
      continue;
    }

    Result->replaceAllUsesWith(ProjInst);
  }

  // We may have exposed trivially dead instructions due to
  // simplifyInstruction... delete I and any such instructions!
  recursivelyDeleteTriviallyDeadInstructions(I, true);
}

bool OwnershipModelEliminatorVisitor::visitDestructureStructInst(
    DestructureStructInst *DSI) {
  splitDestructure(B, DSI, DSI->getOperand());
  return true;
}

bool OwnershipModelEliminatorVisitor::visitDestructureTupleInst(
    DestructureTupleInst *DTI) {
  splitDestructure(B, DTI, DTI->getOperand());
  return true;
}

//===----------------------------------------------------------------------===//
//                           Top Level Entry Point
//===----------------------------------------------------------------------===//

static bool stripOwnership(SILFunction &F) {
  // If F is an external declaration, do not process it.
  if (F.isExternalDeclaration())
    return false;

  // Set F to have unqualified ownership.
  F.setOwnershipEliminated();

  bool MadeChange = false;
  SILBuilder B(F);
  OwnershipModelEliminatorVisitor Visitor(B);

  for (auto &BB : F) {
    // Change all arguments to have ValueOwnershipKind::Any.
    for (auto *Arg : BB.getArguments()) {
      Arg->setOwnershipKind(ValueOwnershipKind::Any);
    }

    for (auto II = BB.begin(), IE = BB.end(); II != IE;) {
      // Since we are going to be potentially removing instructions, we need
      // to make sure to increment our iterator before we perform any
      // visits.
      SILInstruction *I = &*II;
      ++II;

      MadeChange |= Visitor.visit(I);
    }
  }
  return MadeChange;
}

static void prepareNonTransparentSILFunctionForOptimization(ModuleDecl *,
                                                            SILFunction *F) {
  if (!F->hasOwnership() || F->isTransparent())
    return;

  LLVM_DEBUG(llvm::dbgs() << "After deserialization, stripping ownership in:"
                          << F->getName() << "\n");

  stripOwnership(*F);
}

static void prepareSILFunctionForOptimization(ModuleDecl *, SILFunction *F) {
  if (!F->hasOwnership())
    return;

  LLVM_DEBUG(llvm::dbgs() << "After deserialization, stripping ownership in:"
                          << F->getName() << "\n");

  stripOwnership(*F);
}

namespace {

struct OwnershipModelEliminator : SILModuleTransform {
  bool SkipTransparent;

  OwnershipModelEliminator(bool SkipTransparent)
      : SkipTransparent(SkipTransparent) {}

  void run() override {
    if (DumpBefore.size()) {
      getModule()->dump(DumpBefore.c_str());
    }

    auto &Mod = *getModule();
    for (auto &F : Mod) {
      // If F does not have ownership, skip it. We have no further work to do.
      if (!F.hasOwnership())
        continue;

      // If we were asked to not strip ownership from transparent functions in
      // /our/ module, continue.
      if (SkipTransparent && F.isTransparent())
        continue;

      if (stripOwnership(F)) {
        auto InvalidKind =
            SILAnalysis::InvalidationKind::BranchesAndInstructions;
        invalidateAnalysis(&F, InvalidKind);
      }
    }

    // If we were asked to strip transparent, we are at the beginning of the
    // performance pipeline. In such a case, we register a handler so that all
    // future things we deserialize have ownership stripped.
    using NotificationHandlerTy =
        FunctionBodyDeserializationNotificationHandler;
    std::unique_ptr<DeserializationNotificationHandler> ptr;
    if (SkipTransparent) {
      ptr.reset(new NotificationHandlerTy(
          prepareNonTransparentSILFunctionForOptimization));
    } else {
      ptr.reset(new NotificationHandlerTy(prepareSILFunctionForOptimization));
    }
    Mod.registerDeserializationNotificationHandler(std::move(ptr));
  }
};

} // end anonymous namespace

SILTransform *swift::createOwnershipModelEliminator() {
  return new OwnershipModelEliminator(false /*skip transparent*/);
}

SILTransform *swift::createNonTransparentFunctionOwnershipModelEliminator() {
  return new OwnershipModelEliminator(true /*skip transparent*/);
}
<|endoftext|>


<commit_before>/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2008 Scientific Computing and Imaging Institute,
   University of Utah.

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/

/**
  \file    TransferFunction1D.cpp
  \author  Jens Krueger
           SCI Institute
           University of Utah
  \version 1.0
  \date    September 2008
*/

#include <cstdlib>
#include <fstream>
#include <iterator>
#include <memory.h>
#include <sstream>
#include "TransferFunction1D.h"
#include "Controller/Controller.h"

using namespace std;

TransferFunction1D::TransferFunction1D(size_t iSize) :
  m_vValueBBox(0,0)
{
  Resize(iSize);
}

TransferFunction1D::TransferFunction1D(const std::string& filename) {
  Load(filename);
}

TransferFunction1D::~TransferFunction1D(void)
{
}

void TransferFunction1D::Resize(size_t iSize) {
  vColorData.resize(iSize);
}

float TransferFunction1D::Smoothstep(float x) const {
  return 3*x*x-2*x*x*x;
}

void TransferFunction1D::SetStdFunction(float fCenterPoint, float fInvGradient) {
  SetStdFunction(fCenterPoint, fInvGradient,0, false);
  SetStdFunction(fCenterPoint, fInvGradient,1, false);
  SetStdFunction(fCenterPoint, fInvGradient,2, false);
  SetStdFunction(fCenterPoint, fInvGradient,3, false);
}

void TransferFunction1D::SetStdFunction(float fCenterPoint, float fInvGradient, int iComponent, bool bInvertedStep) {
  size_t iCenterPoint = size_t((vColorData.size()-1) * float(std::min<float>(std::max<float>(0,fCenterPoint),1)));
  size_t iInvGradient = size_t((vColorData.size()-1) * float(std::min<float>(std::max<float>(0,fInvGradient),1)));

  size_t iRampStartPoint = (iInvGradient/2 > iCenterPoint)                      ? 0                 : iCenterPoint-(iInvGradient/2);
  size_t iRampEndPoint   = (iInvGradient/2 + iCenterPoint > vColorData.size())  ? vColorData.size() : iCenterPoint+(iInvGradient/2);

  if (bInvertedStep) {
    for (size_t i = 0;i<iRampStartPoint;i++)
      vColorData[i][iComponent] = 1;

    for (size_t i = iRampStartPoint;i<iRampEndPoint;i++) {
      float fValue = Smoothstep(float(i-iCenterPoint+(iInvGradient/2))/float(iInvGradient));
      vColorData[i][iComponent] = 1.0f-fValue;
    }

    for (size_t i = iRampEndPoint;i<vColorData.size();i++)
      vColorData[i][iComponent] = 0;
  } else {
    for (size_t i = 0;i<iRampStartPoint;i++)
      vColorData[i][iComponent] = 0;

    for (size_t i = iRampStartPoint;i<iRampEndPoint;i++) {
      float fValue = Smoothstep(float(i-iCenterPoint+(iInvGradient/2))/float(iInvGradient));
      vColorData[i][iComponent] = fValue;
    }

    for (size_t i = iRampEndPoint;i<vColorData.size();i++)
      vColorData[i][iComponent] = 1;
  }

  ComputeNonZeroLimits();
}

template<typename T>
static bool is_nan(T) { std::abort(); } // Rely on specialization.
template<>
bool is_nan(float v) {
  // This is only valid for ieee754.
  return (v != v);
}

template<typename T>
static bool is_infinite(T) { abort(); } // Rely on specialization.
template<>
bool is_infinite(float v) {
  return (v ==  std::numeric_limits<float>::infinity() ||
          v == -std::numeric_limits<float>::infinity());
}

template<typename in, typename out>
static inline out
lerp(in value, in imin, in imax, out omin, out omax)
{
  out ret = out(omin + (value-imin) * (static_cast<double>(omax-omin) /
                                                      (imax-imin)));
#if 0
  // Very useful while debugging.
  if(is_nan(ret) || is_infinite(ret)) { return 0; }
#endif
  return ret;
}

/// Finds the minimum and maximum per-channel of a 4-component packed vector.
template<typename ForwardIter, typename Out>
void minmax_component4(ForwardIter first, ForwardIter last,
                       Out c_min[4], Out c_max[4])
{
  c_min[0] = c_min[1] = c_min[2] = c_min[3] = *first;
  c_max[0] = c_max[1] = c_max[2] = c_max[3] = *first;
  if(first == last) { return; }
  while(first < last) {
    if(*(first+0) < c_min[0]) { c_min[0] = *(first+0); }
    if(*(first+1) < c_min[1]) { c_min[1] = *(first+1); }
    if(*(first+2) < c_min[2]) { c_min[2] = *(first+2); }
    if(*(first+3) < c_min[3]) { c_min[3] = *(first+3); }

    if(*(first+0) > c_max[0]) { c_max[0] = *(first+0); }
    if(*(first+1) > c_max[1]) { c_max[1] = *(first+1); }
    if(*(first+2) > c_max[2]) { c_max[2] = *(first+2); }
    if(*(first+3) > c_max[3]) { c_max[3] = *(first+3); }

    // Ugh.  Bail out if incrementing the iterator would go beyond the end.
    // We'd never actually deref the iterator in that case (because of the
    // while conditional), but we hit internal libstdc++ asserts anyway.
    if(static_cast<size_t>(std::distance(first, last)) < 4) {
      break;
    }
    std::advance(first, 4);
  }
}

/// Set the transfer function from an external source.  Assumes the vector
/// has 4-components per element, in RGBA order.
void TransferFunction1D::Set(const std::vector<unsigned char>& tf)
{
  assert(!tf.empty());
  vColorData.resize(tf.size()/4);

  unsigned char tfmin[4];
  unsigned char tfmax[4];
  // A bit tricky.  We need the min/max of our vector so that we know how to
  // interpolate, but it needs to be a per-channel min/max.
  minmax_component4(tf.begin(),tf.end(), tfmin,tfmax);

  // Similarly, we need the min/max of our output format.
  const float fmin = 0.0;
  const float fmax = 1.0;

  assert(tfmin[0] <= tfmax[0]);
  assert(tfmin[1] <= tfmax[1]);
  assert(tfmin[2] <= tfmax[2]);
  assert(tfmin[3] <= tfmax[3]);

  for(size_t i=0; i < vColorData.size(); ++i) {
    vColorData[i] = FLOATVECTOR4(
      lerp(tf[4*i+0], tfmin[0],tfmax[0], fmin,fmax),
      lerp(tf[4*i+1], tfmin[1],tfmax[1], fmin,fmax),
      lerp(tf[4*i+2], tfmin[2],tfmax[2], fmin,fmax),
      lerp(tf[4*i+3], static_cast<unsigned char>(0),
                      static_cast<unsigned char>(255), fmin,fmax)
    );
  }

  ComputeNonZeroLimits();
}

void TransferFunction1D::Clear() {
  for (size_t i = 0;i<vColorData.size();i++)
    vColorData[i] = FLOATVECTOR4(0,0,0,0);

  m_vValueBBox = UINT64VECTOR2(0,0);
}

void TransferFunction1D::Resample(size_t iTargetSize) {
  size_t iSourceSize = vColorData.size();

  if (iTargetSize == iSourceSize) return;

  vector< FLOATVECTOR4 > vTmpColorData(iTargetSize);

  if (iTargetSize < iSourceSize) {
    // downsample
    size_t iFrom = 0;
    for (size_t i = 0;i<vTmpColorData.size();i++) {

      size_t iTo = iFrom + iSourceSize/iTargetSize;

      vTmpColorData[i] = 0;
      for (size_t j = iFrom;j<iTo;j++) {
        vTmpColorData[i] += vColorData[j];
      }
      vTmpColorData[i] /= float(iTo-iFrom);

      iTargetSize -= 1;
      iSourceSize -= iTo-iFrom;

      iFrom = iTo;
    }
  } else {
    // upsample
    for (size_t i = 0;i<vTmpColorData.size();i++) {
      float fPos = float(i) * float(iSourceSize-1)/float(iTargetSize);
      size_t iFloor = size_t(floor(fPos));
      size_t iCeil  = std::min(iFloor+1, vColorData.size()-1);
      float fInterp = fPos - float(iFloor);

      vTmpColorData[i] = vColorData[iFloor] * (1-fInterp) + vColorData[iCeil] * fInterp;
    }

  }

  vColorData = vTmpColorData;
  ComputeNonZeroLimits();
}

bool TransferFunction1D::Load(const std::string& filename, size_t iTargetSize) {
  if (!Load(filename)) {
    return false;
  } else {
    Resample(iTargetSize);
    return true;
  }
}

bool TransferFunction1D::Load(const std::string& filename) {
  ifstream file(filename.c_str());
  if (!Load(file)) return false;
  file.close();
  ComputeNonZeroLimits();
  return true;
}

bool TransferFunction1D::Load(std::istream& tf, size_t iTargetSize) {
  if (!Load(tf)) {
    return false;
  } else {
    Resample(iTargetSize);
    return true;
  }
}

bool TransferFunction1D::Save(const std::string& filename) const {
  ofstream file(filename.c_str());
  if (!Save(file)) return false;
  file.close();
  return true;
}

bool TransferFunction1D::Load(std::istream& tf) {
  UINT32 iSize;
  tf >> iSize;
  vColorData.resize(iSize);

  for(size_t i=0;i<vColorData.size();++i){
    for(size_t j=0;j<4;++j){
      tf >> vColorData[i][j];
    }
  }

  return true;
}

bool TransferFunction1D::Save(std::ostream& file) const {
  file << vColorData.size() << endl;

  for(size_t i=0;i<vColorData.size();++i){
    for(size_t j=0;j<4;++j){
      file << vColorData[i][j] << " ";
    }
    file << endl;
  }

  return true;
}

void TransferFunction1D::GetByteArray(std::vector<unsigned char>& vData,
                                      unsigned char cUsedRange) const {
  // bail out immediately if we've got no data
  if(vColorData.empty()) { return; }

  vData.resize(vColorData.size() * 4);

  unsigned char *pcDataIterator = &vData.at(0);
  for (size_t i = 0;i<vColorData.size();i++) {
    *pcDataIterator++ = (unsigned char)(vColorData[i][0]*cUsedRange);
    *pcDataIterator++ = (unsigned char)(vColorData[i][1]*cUsedRange);
    *pcDataIterator++ = (unsigned char)(vColorData[i][2]*cUsedRange);
    *pcDataIterator++ = (unsigned char)(vColorData[i][3]*cUsedRange);
  }
}

void TransferFunction1D::GetShortArray(unsigned short** psData,
                                       unsigned short sUsedRange) const {
  // bail out immediately if we've got no data
  if(vColorData.empty()) { return; }

  if (*psData == NULL) *psData = new unsigned short[vColorData.size()*4];

  unsigned short *psDataIterator = *psData;
  for (size_t i = 0;i<vColorData.size();i++) {
    *psDataIterator++ = (unsigned short)(vColorData[i][0]*sUsedRange);
    *psDataIterator++ = (unsigned short)(vColorData[i][1]*sUsedRange);
    *psDataIterator++ = (unsigned short)(vColorData[i][2]*sUsedRange);
    *psDataIterator++ = (unsigned short)(vColorData[i][3]*sUsedRange);
  }
}

void TransferFunction1D::GetFloatArray(float** pfData) const {
  // bail out immediately if we've got no data
  if(vColorData.empty()) { return; }

  if (*pfData == NULL) *pfData = new float[4*vColorData.size()];
  memcpy(*pfData, &pfData[0], sizeof(float)*4*vColorData.size());
}

void TransferFunction1D::ComputeNonZeroLimits() {
  m_vValueBBox = UINT64VECTOR2(UINT64(vColorData.size()),0);

  for (size_t i = 0;i<vColorData.size();i++) {
    if (vColorData[i][3] != 0) {
      m_vValueBBox.x = MIN(m_vValueBBox.x, i);
      m_vValueBBox.y = i;
    }
  }
}
<commit_msg>Use the `lerp' from MathTools.<commit_after>/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2008 Scientific Computing and Imaging Institute,
   University of Utah.

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/

/**
  \file    TransferFunction1D.cpp
  \author  Jens Krueger
           SCI Institute
           University of Utah
  \version 1.0
  \date    September 2008
*/

#include <cstdlib>
#include <fstream>
#include <iterator>
#include <memory.h>
#include <sstream>
#include "Basics/MathTools.h"
#include "Controller/Controller.h"
#include "TransferFunction1D.h"

using namespace std;

TransferFunction1D::TransferFunction1D(size_t iSize) :
  m_vValueBBox(0,0)
{
  Resize(iSize);
}

TransferFunction1D::TransferFunction1D(const std::string& filename) {
  Load(filename);
}

TransferFunction1D::~TransferFunction1D(void)
{
}

void TransferFunction1D::Resize(size_t iSize) {
  vColorData.resize(iSize);
}

float TransferFunction1D::Smoothstep(float x) const {
  return 3*x*x-2*x*x*x;
}

void TransferFunction1D::SetStdFunction(float fCenterPoint, float fInvGradient) {
  SetStdFunction(fCenterPoint, fInvGradient,0, false);
  SetStdFunction(fCenterPoint, fInvGradient,1, false);
  SetStdFunction(fCenterPoint, fInvGradient,2, false);
  SetStdFunction(fCenterPoint, fInvGradient,3, false);
}

void TransferFunction1D::SetStdFunction(float fCenterPoint, float fInvGradient, int iComponent, bool bInvertedStep) {
  size_t iCenterPoint = size_t((vColorData.size()-1) * float(std::min<float>(std::max<float>(0,fCenterPoint),1)));
  size_t iInvGradient = size_t((vColorData.size()-1) * float(std::min<float>(std::max<float>(0,fInvGradient),1)));

  size_t iRampStartPoint = (iInvGradient/2 > iCenterPoint)                      ? 0                 : iCenterPoint-(iInvGradient/2);
  size_t iRampEndPoint   = (iInvGradient/2 + iCenterPoint > vColorData.size())  ? vColorData.size() : iCenterPoint+(iInvGradient/2);

  if (bInvertedStep) {
    for (size_t i = 0;i<iRampStartPoint;i++)
      vColorData[i][iComponent] = 1;

    for (size_t i = iRampStartPoint;i<iRampEndPoint;i++) {
      float fValue = Smoothstep(float(i-iCenterPoint+(iInvGradient/2))/float(iInvGradient));
      vColorData[i][iComponent] = 1.0f-fValue;
    }

    for (size_t i = iRampEndPoint;i<vColorData.size();i++)
      vColorData[i][iComponent] = 0;
  } else {
    for (size_t i = 0;i<iRampStartPoint;i++)
      vColorData[i][iComponent] = 0;

    for (size_t i = iRampStartPoint;i<iRampEndPoint;i++) {
      float fValue = Smoothstep(float(i-iCenterPoint+(iInvGradient/2))/float(iInvGradient));
      vColorData[i][iComponent] = fValue;
    }

    for (size_t i = iRampEndPoint;i<vColorData.size();i++)
      vColorData[i][iComponent] = 1;
  }

  ComputeNonZeroLimits();
}

/// Finds the minimum and maximum per-channel of a 4-component packed vector.
template<typename ForwardIter, typename Out>
void minmax_component4(ForwardIter first, ForwardIter last,
                       Out c_min[4], Out c_max[4])
{
  c_min[0] = c_min[1] = c_min[2] = c_min[3] = *first;
  c_max[0] = c_max[1] = c_max[2] = c_max[3] = *first;
  if(first == last) { return; }
  while(first < last) {
    if(*(first+0) < c_min[0]) { c_min[0] = *(first+0); }
    if(*(first+1) < c_min[1]) { c_min[1] = *(first+1); }
    if(*(first+2) < c_min[2]) { c_min[2] = *(first+2); }
    if(*(first+3) < c_min[3]) { c_min[3] = *(first+3); }

    if(*(first+0) > c_max[0]) { c_max[0] = *(first+0); }
    if(*(first+1) > c_max[1]) { c_max[1] = *(first+1); }
    if(*(first+2) > c_max[2]) { c_max[2] = *(first+2); }
    if(*(first+3) > c_max[3]) { c_max[3] = *(first+3); }

    // Ugh.  Bail out if incrementing the iterator would go beyond the end.
    // We'd never actually deref the iterator in that case (because of the
    // while conditional), but we hit internal libstdc++ asserts anyway.
    if(static_cast<size_t>(std::distance(first, last)) < 4) {
      break;
    }
    std::advance(first, 4);
  }
}

/// Set the transfer function from an external source.  Assumes the vector
/// has 4-components per element, in RGBA order.
void TransferFunction1D::Set(const std::vector<unsigned char>& tf)
{
  assert(!tf.empty());
  vColorData.resize(tf.size()/4);

  unsigned char tfmin[4];
  unsigned char tfmax[4];
  // A bit tricky.  We need the min/max of our vector so that we know how to
  // interpolate, but it needs to be a per-channel min/max.
  minmax_component4(tf.begin(),tf.end(), tfmin,tfmax);

  // Similarly, we need the min/max of our output format.
  const float fmin = 0.0;
  const float fmax = 1.0;

  assert(tfmin[0] <= tfmax[0]);
  assert(tfmin[1] <= tfmax[1]);
  assert(tfmin[2] <= tfmax[2]);
  assert(tfmin[3] <= tfmax[3]);

  for(size_t i=0; i < vColorData.size(); ++i) {
    vColorData[i] = FLOATVECTOR4(
      MathTools::lerp(tf[4*i+0], tfmin[0],tfmax[0], fmin,fmax),
      MathTools::lerp(tf[4*i+1], tfmin[1],tfmax[1], fmin,fmax),
      MathTools::lerp(tf[4*i+2], tfmin[2],tfmax[2], fmin,fmax),
      MathTools::lerp(tf[4*i+3], static_cast<unsigned char>(0),
                                 static_cast<unsigned char>(255), fmin,fmax)
    );
  }

  ComputeNonZeroLimits();
}

void TransferFunction1D::Clear() {
  for (size_t i = 0;i<vColorData.size();i++)
    vColorData[i] = FLOATVECTOR4(0,0,0,0);

  m_vValueBBox = UINT64VECTOR2(0,0);
}

void TransferFunction1D::Resample(size_t iTargetSize) {
  size_t iSourceSize = vColorData.size();

  if (iTargetSize == iSourceSize) return;

  vector< FLOATVECTOR4 > vTmpColorData(iTargetSize);

  if (iTargetSize < iSourceSize) {
    // downsample
    size_t iFrom = 0;
    for (size_t i = 0;i<vTmpColorData.size();i++) {

      size_t iTo = iFrom + iSourceSize/iTargetSize;

      vTmpColorData[i] = 0;
      for (size_t j = iFrom;j<iTo;j++) {
        vTmpColorData[i] += vColorData[j];
      }
      vTmpColorData[i] /= float(iTo-iFrom);

      iTargetSize -= 1;
      iSourceSize -= iTo-iFrom;

      iFrom = iTo;
    }
  } else {
    // upsample
    for (size_t i = 0;i<vTmpColorData.size();i++) {
      float fPos = float(i) * float(iSourceSize-1)/float(iTargetSize);
      size_t iFloor = size_t(floor(fPos));
      size_t iCeil  = std::min(iFloor+1, vColorData.size()-1);
      float fInterp = fPos - float(iFloor);

      vTmpColorData[i] = vColorData[iFloor] * (1-fInterp) + vColorData[iCeil] * fInterp;
    }

  }

  vColorData = vTmpColorData;
  ComputeNonZeroLimits();
}

bool TransferFunction1D::Load(const std::string& filename, size_t iTargetSize) {
  if (!Load(filename)) {
    return false;
  } else {
    Resample(iTargetSize);
    return true;
  }
}

bool TransferFunction1D::Load(const std::string& filename) {
  ifstream file(filename.c_str());
  if (!Load(file)) return false;
  file.close();
  ComputeNonZeroLimits();
  return true;
}

bool TransferFunction1D::Load(std::istream& tf, size_t iTargetSize) {
  if (!Load(tf)) {
    return false;
  } else {
    Resample(iTargetSize);
    return true;
  }
}

bool TransferFunction1D::Save(const std::string& filename) const {
  ofstream file(filename.c_str());
  if (!Save(file)) return false;
  file.close();
  return true;
}

bool TransferFunction1D::Load(std::istream& tf) {
  UINT32 iSize;
  tf >> iSize;
  vColorData.resize(iSize);

  for(size_t i=0;i<vColorData.size();++i){
    for(size_t j=0;j<4;++j){
      tf >> vColorData[i][j];
    }
  }

  return true;
}

bool TransferFunction1D::Save(std::ostream& file) const {
  file << vColorData.size() << endl;

  for(size_t i=0;i<vColorData.size();++i){
    for(size_t j=0;j<4;++j){
      file << vColorData[i][j] << " ";
    }
    file << endl;
  }

  return true;
}

void TransferFunction1D::GetByteArray(std::vector<unsigned char>& vData,
                                      unsigned char cUsedRange) const {
  // bail out immediately if we've got no data
  if(vColorData.empty()) { return; }

  vData.resize(vColorData.size() * 4);

  unsigned char *pcDataIterator = &vData.at(0);
  for (size_t i = 0;i<vColorData.size();i++) {
    *pcDataIterator++ = (unsigned char)(vColorData[i][0]*cUsedRange);
    *pcDataIterator++ = (unsigned char)(vColorData[i][1]*cUsedRange);
    *pcDataIterator++ = (unsigned char)(vColorData[i][2]*cUsedRange);
    *pcDataIterator++ = (unsigned char)(vColorData[i][3]*cUsedRange);
  }
}

void TransferFunction1D::GetShortArray(unsigned short** psData,
                                       unsigned short sUsedRange) const {
  // bail out immediately if we've got no data
  if(vColorData.empty()) { return; }

  if (*psData == NULL) *psData = new unsigned short[vColorData.size()*4];

  unsigned short *psDataIterator = *psData;
  for (size_t i = 0;i<vColorData.size();i++) {
    *psDataIterator++ = (unsigned short)(vColorData[i][0]*sUsedRange);
    *psDataIterator++ = (unsigned short)(vColorData[i][1]*sUsedRange);
    *psDataIterator++ = (unsigned short)(vColorData[i][2]*sUsedRange);
    *psDataIterator++ = (unsigned short)(vColorData[i][3]*sUsedRange);
  }
}

void TransferFunction1D::GetFloatArray(float** pfData) const {
  // bail out immediately if we've got no data
  if(vColorData.empty()) { return; }

  if (*pfData == NULL) *pfData = new float[4*vColorData.size()];
  memcpy(*pfData, &pfData[0], sizeof(float)*4*vColorData.size());
}

void TransferFunction1D::ComputeNonZeroLimits() {
  m_vValueBBox = UINT64VECTOR2(UINT64(vColorData.size()),0);

  for (size_t i = 0;i<vColorData.size();i++) {
    if (vColorData[i][3] != 0) {
      m_vValueBBox.x = MIN(m_vValueBBox.x, i);
      m_vValueBBox.y = i;
    }
  }
}
<|endoftext|>