mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-01 23:40:04 +00:00
76e0bde196
Currently the utils/ folder uses both spvutils:: and spvtools::utils. This CL changes the namespace to consistenly be spvtools::utils to match the rest of the codebase.
439 lines
14 KiB
C++
439 lines
14 KiB
C++
// Copyright (c) 2017 Google Inc.
|
|
//
|
|
// Licensed 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.
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <sstream>
|
|
#include <type_traits>
|
|
|
|
#include "util/bit_stream.h"
|
|
|
|
namespace spvtools {
|
|
namespace utils {
|
|
namespace {
|
|
|
|
// Returns if the system is little-endian. Unfortunately only works during
|
|
// runtime.
|
|
bool IsLittleEndian() {
|
|
// This constant value allows the detection of the host machine's endianness.
|
|
// Accessing it as an array of bytes is valid due to C++11 section 3.10
|
|
// paragraph 10.
|
|
static const uint16_t kFF00 = 0xff00;
|
|
return reinterpret_cast<const unsigned char*>(&kFF00)[0] == 0;
|
|
}
|
|
|
|
// Copies bytes from the given buffer to a uint64_t buffer.
|
|
// Motivation: casting uint64_t* to uint8_t* is ok. Casting in the other
|
|
// direction is only advisable if uint8_t* is aligned to 64-bit word boundary.
|
|
std::vector<uint64_t> ToBuffer64(const void* buffer, size_t num_bytes) {
|
|
std::vector<uint64_t> out;
|
|
out.resize((num_bytes + 7) / 8, 0);
|
|
memcpy(out.data(), buffer, num_bytes);
|
|
return out;
|
|
}
|
|
|
|
// Copies uint8_t buffer to a uint64_t buffer.
|
|
std::vector<uint64_t> ToBuffer64(const std::vector<uint8_t>& in) {
|
|
return ToBuffer64(in.data(), in.size());
|
|
}
|
|
|
|
// Returns uint64_t containing the same bits as |val|.
|
|
// Type size must be less than 8 bytes.
|
|
template <typename T>
|
|
uint64_t ToU64(T val) {
|
|
static_assert(sizeof(T) <= 8, "Type size too big");
|
|
uint64_t val64 = 0;
|
|
std::memcpy(&val64, &val, sizeof(T));
|
|
return val64;
|
|
}
|
|
|
|
// Returns value of type T containing the same bits as |val64|.
|
|
// Type size must be less than 8 bytes. Upper (unused) bits of |val64| must be
|
|
// zero (irrelevant, but is checked with assertion).
|
|
template <typename T>
|
|
T FromU64(uint64_t val64) {
|
|
assert(sizeof(T) == 8 || (val64 >> (sizeof(T) * 8)) == 0);
|
|
static_assert(sizeof(T) <= 8, "Type size too big");
|
|
T val = 0;
|
|
std::memcpy(&val, &val64, sizeof(T));
|
|
return val;
|
|
}
|
|
|
|
// Writes bits from |val| to |writer| in chunks of size |chunk_length|.
|
|
// Signal bit is used to signal if the reader should expect another chunk:
|
|
// 0 - no more chunks to follow
|
|
// 1 - more chunks to follow
|
|
// If number of written bits reaches |max_payload| last chunk is truncated.
|
|
void WriteVariableWidthInternal(BitWriterInterface* writer, uint64_t val,
|
|
size_t chunk_length, size_t max_payload) {
|
|
assert(chunk_length > 0);
|
|
assert(chunk_length < max_payload);
|
|
assert(max_payload == 64 || (val >> max_payload) == 0);
|
|
|
|
if (val == 0) {
|
|
// Split in two writes for more readable logging.
|
|
writer->WriteBits(0, chunk_length);
|
|
writer->WriteBits(0, 1);
|
|
return;
|
|
}
|
|
|
|
size_t payload_written = 0;
|
|
|
|
while (val) {
|
|
if (payload_written + chunk_length >= max_payload) {
|
|
// This has to be the last chunk.
|
|
// There is no need for the signal bit and the chunk can be truncated.
|
|
const size_t left_to_write = max_payload - payload_written;
|
|
assert((val >> left_to_write) == 0);
|
|
writer->WriteBits(val, left_to_write);
|
|
break;
|
|
}
|
|
|
|
writer->WriteBits(val, chunk_length);
|
|
payload_written += chunk_length;
|
|
val = val >> chunk_length;
|
|
|
|
// Write a single bit to signal if there is more to come.
|
|
writer->WriteBits(val ? 1 : 0, 1);
|
|
}
|
|
}
|
|
|
|
// Reads data written with WriteVariableWidthInternal. |chunk_length| and
|
|
// |max_payload| should be identical to those used to write the data.
|
|
// Returns false if the stream ends prematurely.
|
|
bool ReadVariableWidthInternal(BitReaderInterface* reader, uint64_t* val,
|
|
size_t chunk_length, size_t max_payload) {
|
|
assert(chunk_length > 0);
|
|
assert(chunk_length <= max_payload);
|
|
size_t payload_read = 0;
|
|
|
|
while (payload_read + chunk_length < max_payload) {
|
|
uint64_t bits = 0;
|
|
if (reader->ReadBits(&bits, chunk_length) != chunk_length) return false;
|
|
|
|
*val |= bits << payload_read;
|
|
payload_read += chunk_length;
|
|
|
|
uint64_t more_to_come = 0;
|
|
if (reader->ReadBits(&more_to_come, 1) != 1) return false;
|
|
|
|
if (!more_to_come) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Need to read the last chunk which may be truncated. No signal bit follows.
|
|
uint64_t bits = 0;
|
|
const size_t left_to_read = max_payload - payload_read;
|
|
if (reader->ReadBits(&bits, left_to_read) != left_to_read) return false;
|
|
|
|
*val |= bits << payload_read;
|
|
return true;
|
|
}
|
|
|
|
// Calls WriteVariableWidthInternal with the right max_payload argument.
|
|
template <typename T>
|
|
void WriteVariableWidthUnsigned(BitWriterInterface* writer, T val,
|
|
size_t chunk_length) {
|
|
static_assert(std::is_unsigned<T>::value, "Type must be unsigned");
|
|
static_assert(std::is_integral<T>::value, "Type must be integral");
|
|
WriteVariableWidthInternal(writer, val, chunk_length, sizeof(T) * 8);
|
|
}
|
|
|
|
// Calls ReadVariableWidthInternal with the right max_payload argument.
|
|
template <typename T>
|
|
bool ReadVariableWidthUnsigned(BitReaderInterface* reader, T* val,
|
|
size_t chunk_length) {
|
|
static_assert(std::is_unsigned<T>::value, "Type must be unsigned");
|
|
static_assert(std::is_integral<T>::value, "Type must be integral");
|
|
uint64_t val64 = 0;
|
|
if (!ReadVariableWidthInternal(reader, &val64, chunk_length, sizeof(T) * 8))
|
|
return false;
|
|
*val = static_cast<T>(val64);
|
|
assert(*val == val64);
|
|
return true;
|
|
}
|
|
|
|
// Encodes signed |val| to an unsigned value and calls
|
|
// WriteVariableWidthInternal with the right max_payload argument.
|
|
template <typename T>
|
|
void WriteVariableWidthSigned(BitWriterInterface* writer, T val,
|
|
size_t chunk_length, size_t zigzag_exponent) {
|
|
static_assert(std::is_signed<T>::value, "Type must be signed");
|
|
static_assert(std::is_integral<T>::value, "Type must be integral");
|
|
WriteVariableWidthInternal(writer, EncodeZigZag(val, zigzag_exponent),
|
|
chunk_length, sizeof(T) * 8);
|
|
}
|
|
|
|
// Calls ReadVariableWidthInternal with the right max_payload argument
|
|
// and decodes the value.
|
|
template <typename T>
|
|
bool ReadVariableWidthSigned(BitReaderInterface* reader, T* val,
|
|
size_t chunk_length, size_t zigzag_exponent) {
|
|
static_assert(std::is_signed<T>::value, "Type must be signed");
|
|
static_assert(std::is_integral<T>::value, "Type must be integral");
|
|
uint64_t encoded = 0;
|
|
if (!ReadVariableWidthInternal(reader, &encoded, chunk_length, sizeof(T) * 8))
|
|
return false;
|
|
|
|
const int64_t decoded = DecodeZigZag(encoded, zigzag_exponent);
|
|
|
|
*val = static_cast<T>(decoded);
|
|
assert(*val == decoded);
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
size_t Log2U64(uint64_t val) {
|
|
size_t res = 0;
|
|
|
|
if (val & 0xFFFFFFFF00000000) {
|
|
val >>= 32;
|
|
res |= 32;
|
|
}
|
|
|
|
if (val & 0xFFFF0000) {
|
|
val >>= 16;
|
|
res |= 16;
|
|
}
|
|
|
|
if (val & 0xFF00) {
|
|
val >>= 8;
|
|
res |= 8;
|
|
}
|
|
|
|
if (val & 0xF0) {
|
|
val >>= 4;
|
|
res |= 4;
|
|
}
|
|
|
|
if (val & 0xC) {
|
|
val >>= 2;
|
|
res |= 2;
|
|
}
|
|
|
|
if (val & 0x2) {
|
|
res |= 1;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void BitWriterInterface::WriteVariableWidthU64(uint64_t val,
|
|
size_t chunk_length) {
|
|
WriteVariableWidthUnsigned(this, val, chunk_length);
|
|
}
|
|
|
|
void BitWriterInterface::WriteVariableWidthU32(uint32_t val,
|
|
size_t chunk_length) {
|
|
WriteVariableWidthUnsigned(this, val, chunk_length);
|
|
}
|
|
|
|
void BitWriterInterface::WriteVariableWidthU16(uint16_t val,
|
|
size_t chunk_length) {
|
|
WriteVariableWidthUnsigned(this, val, chunk_length);
|
|
}
|
|
|
|
void BitWriterInterface::WriteVariableWidthU8(uint8_t val,
|
|
size_t chunk_length) {
|
|
WriteVariableWidthUnsigned(this, val, chunk_length);
|
|
}
|
|
|
|
void BitWriterInterface::WriteVariableWidthS64(int64_t val, size_t chunk_length,
|
|
size_t zigzag_exponent) {
|
|
WriteVariableWidthSigned(this, val, chunk_length, zigzag_exponent);
|
|
}
|
|
|
|
void BitWriterInterface::WriteVariableWidthS32(int32_t val, size_t chunk_length,
|
|
size_t zigzag_exponent) {
|
|
WriteVariableWidthSigned(this, val, chunk_length, zigzag_exponent);
|
|
}
|
|
|
|
void BitWriterInterface::WriteVariableWidthS16(int16_t val, size_t chunk_length,
|
|
size_t zigzag_exponent) {
|
|
WriteVariableWidthSigned(this, val, chunk_length, zigzag_exponent);
|
|
}
|
|
|
|
void BitWriterInterface::WriteVariableWidthS8(int8_t val, size_t chunk_length,
|
|
size_t zigzag_exponent) {
|
|
WriteVariableWidthSigned(this, val, chunk_length, zigzag_exponent);
|
|
}
|
|
|
|
void BitWriterInterface::WriteFixedWidth(uint64_t val, uint64_t max_val) {
|
|
if (val > max_val) {
|
|
assert(0 && "WriteFixedWidth: value too wide");
|
|
return;
|
|
}
|
|
|
|
const size_t num_bits = 1 + Log2U64(max_val);
|
|
WriteBits(val, num_bits);
|
|
}
|
|
|
|
BitWriterWord64::BitWriterWord64(size_t reserve_bits) : end_(0) {
|
|
buffer_.reserve(NumBitsToNumWords<64>(reserve_bits));
|
|
}
|
|
|
|
void BitWriterWord64::WriteBits(uint64_t bits, size_t num_bits) {
|
|
// Check that |bits| and |num_bits| are valid and consistent.
|
|
assert(num_bits <= 64);
|
|
const bool is_little_endian = IsLittleEndian();
|
|
assert(is_little_endian && "Big-endian architecture support not implemented");
|
|
if (!is_little_endian) return;
|
|
|
|
if (num_bits == 0) return;
|
|
|
|
bits = GetLowerBits(bits, num_bits);
|
|
|
|
EmitSequence(bits, num_bits);
|
|
|
|
// Offset from the start of the current word.
|
|
const size_t offset = end_ % 64;
|
|
|
|
if (offset == 0) {
|
|
// If no offset, simply add |bits| as a new word to the buffer_.
|
|
buffer_.push_back(bits);
|
|
} else {
|
|
// Shift bits and add them to the current word after offset.
|
|
const uint64_t first_word = bits << offset;
|
|
buffer_.back() |= first_word;
|
|
|
|
// If we don't overflow to the next word, there is nothing more to do.
|
|
|
|
if (offset + num_bits > 64) {
|
|
// We overflow to the next word.
|
|
const uint64_t second_word = bits >> (64 - offset);
|
|
// Add remaining bits as a new word to buffer_.
|
|
buffer_.push_back(second_word);
|
|
}
|
|
}
|
|
|
|
// Move end_ into position for next write.
|
|
end_ += num_bits;
|
|
assert(buffer_.size() * 64 >= end_);
|
|
}
|
|
|
|
bool BitReaderInterface::ReadVariableWidthU64(uint64_t* val,
|
|
size_t chunk_length) {
|
|
return ReadVariableWidthUnsigned(this, val, chunk_length);
|
|
}
|
|
|
|
bool BitReaderInterface::ReadVariableWidthU32(uint32_t* val,
|
|
size_t chunk_length) {
|
|
return ReadVariableWidthUnsigned(this, val, chunk_length);
|
|
}
|
|
|
|
bool BitReaderInterface::ReadVariableWidthU16(uint16_t* val,
|
|
size_t chunk_length) {
|
|
return ReadVariableWidthUnsigned(this, val, chunk_length);
|
|
}
|
|
|
|
bool BitReaderInterface::ReadVariableWidthU8(uint8_t* val,
|
|
size_t chunk_length) {
|
|
return ReadVariableWidthUnsigned(this, val, chunk_length);
|
|
}
|
|
|
|
bool BitReaderInterface::ReadVariableWidthS64(int64_t* val, size_t chunk_length,
|
|
size_t zigzag_exponent) {
|
|
return ReadVariableWidthSigned(this, val, chunk_length, zigzag_exponent);
|
|
}
|
|
|
|
bool BitReaderInterface::ReadVariableWidthS32(int32_t* val, size_t chunk_length,
|
|
size_t zigzag_exponent) {
|
|
return ReadVariableWidthSigned(this, val, chunk_length, zigzag_exponent);
|
|
}
|
|
|
|
bool BitReaderInterface::ReadVariableWidthS16(int16_t* val, size_t chunk_length,
|
|
size_t zigzag_exponent) {
|
|
return ReadVariableWidthSigned(this, val, chunk_length, zigzag_exponent);
|
|
}
|
|
|
|
bool BitReaderInterface::ReadVariableWidthS8(int8_t* val, size_t chunk_length,
|
|
size_t zigzag_exponent) {
|
|
return ReadVariableWidthSigned(this, val, chunk_length, zigzag_exponent);
|
|
}
|
|
|
|
bool BitReaderInterface::ReadFixedWidth(uint64_t* val, uint64_t max_val) {
|
|
const size_t num_bits = 1 + Log2U64(max_val);
|
|
return ReadBits(val, num_bits) == num_bits;
|
|
}
|
|
|
|
BitReaderWord64::BitReaderWord64(std::vector<uint64_t>&& buffer)
|
|
: buffer_(std::move(buffer)), pos_(0) {}
|
|
|
|
BitReaderWord64::BitReaderWord64(const std::vector<uint8_t>& buffer)
|
|
: buffer_(ToBuffer64(buffer)), pos_(0) {}
|
|
|
|
BitReaderWord64::BitReaderWord64(const void* buffer, size_t num_bytes)
|
|
: buffer_(ToBuffer64(buffer, num_bytes)), pos_(0) {}
|
|
|
|
size_t BitReaderWord64::ReadBits(uint64_t* bits, size_t num_bits) {
|
|
assert(num_bits <= 64);
|
|
const bool is_little_endian = IsLittleEndian();
|
|
assert(is_little_endian && "Big-endian architecture support not implemented");
|
|
if (!is_little_endian) return 0;
|
|
|
|
if (ReachedEnd()) return 0;
|
|
|
|
// Index of the current word.
|
|
const size_t index = pos_ / 64;
|
|
|
|
// Bit position in the current word where we start reading.
|
|
const size_t offset = pos_ % 64;
|
|
|
|
// Read all bits from the current word (it might be too much, but
|
|
// excessive bits will be removed later).
|
|
*bits = buffer_[index] >> offset;
|
|
|
|
const size_t num_read_from_first_word = std::min(64 - offset, num_bits);
|
|
pos_ += num_read_from_first_word;
|
|
|
|
if (pos_ >= buffer_.size() * 64) {
|
|
// Reached end of buffer_.
|
|
EmitSequence(*bits, num_read_from_first_word);
|
|
return num_read_from_first_word;
|
|
}
|
|
|
|
if (offset + num_bits > 64) {
|
|
// Requested |num_bits| overflows to next word.
|
|
// Write all bits from the beginning of next word to *bits after offset.
|
|
*bits |= buffer_[index + 1] << (64 - offset);
|
|
pos_ += offset + num_bits - 64;
|
|
}
|
|
|
|
// We likely have written more bits than requested. Clear excessive bits.
|
|
*bits = GetLowerBits(*bits, num_bits);
|
|
EmitSequence(*bits, num_bits);
|
|
return num_bits;
|
|
}
|
|
|
|
bool BitReaderWord64::ReachedEnd() const { return pos_ >= buffer_.size() * 64; }
|
|
|
|
bool BitReaderWord64::OnlyZeroesLeft() const {
|
|
if (ReachedEnd()) return true;
|
|
|
|
const size_t index = pos_ / 64;
|
|
if (index < buffer_.size() - 1) return false;
|
|
|
|
assert(index == buffer_.size() - 1);
|
|
|
|
const size_t offset = pos_ % 64;
|
|
const uint64_t remaining_bits = buffer_[index] >> offset;
|
|
return !remaining_bits;
|
|
}
|
|
|
|
} // namespace utils
|
|
} // namespace spvtools
|