477 lines
13 KiB
C++
477 lines
13 KiB
C++
// Copyright (c) 2024 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 "io.h"
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
|
|
#if defined(SPIRV_WINDOWS)
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
|
|
#define SET_STDIN_TO_BINARY_MODE() _setmode(_fileno(stdin), O_BINARY);
|
|
#define SET_STDIN_TO_TEXT_MODE() _setmode(_fileno(stdin), O_TEXT);
|
|
#define SET_STDOUT_TO_BINARY_MODE() _setmode(_fileno(stdout), O_BINARY);
|
|
#define SET_STDOUT_TO_TEXT_MODE() _setmode(_fileno(stdout), O_TEXT);
|
|
#define SET_STDOUT_MODE(mode) _setmode(_fileno(stdout), mode);
|
|
#else
|
|
#define SET_STDIN_TO_BINARY_MODE()
|
|
#define SET_STDIN_TO_TEXT_MODE()
|
|
#define SET_STDOUT_TO_BINARY_MODE() 0
|
|
#define SET_STDOUT_TO_TEXT_MODE() 0
|
|
#define SET_STDOUT_MODE(mode)
|
|
#endif
|
|
|
|
namespace {
|
|
// Appends the contents of the |file| to |data|, assuming each element in the
|
|
// file is of type |T|.
|
|
template <typename T>
|
|
void ReadFile(FILE* file, std::vector<T>* data) {
|
|
if (file == nullptr) return;
|
|
|
|
const int buf_size = 4096 / sizeof(T);
|
|
T buf[buf_size];
|
|
while (size_t len = fread(buf, sizeof(T), buf_size, file)) {
|
|
data->insert(data->end(), buf, buf + len);
|
|
}
|
|
}
|
|
|
|
// Returns true if |file| has encountered an error opening the file or reading
|
|
// from it. If there was an error, writes an error message to standard error.
|
|
bool WasFileCorrectlyRead(FILE* file, const char* filename) {
|
|
if (file == nullptr) {
|
|
fprintf(stderr, "error: file does not exist '%s'\n", filename);
|
|
return false;
|
|
}
|
|
|
|
if (ftell(file) == -1L) {
|
|
if (ferror(file)) {
|
|
fprintf(stderr, "error: error reading file '%s'\n", filename);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Ensure the file contained an exact number of elements, whose size is given in
|
|
// |alignment|.
|
|
bool WasFileSizeAligned(const char* filename, size_t read_size,
|
|
size_t alignment) {
|
|
assert(alignment != 1);
|
|
if ((read_size % alignment) != 0) {
|
|
fprintf(stderr,
|
|
"error: file size should be a multiple of %zd; file '%s' corrupt\n",
|
|
alignment, filename);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Different formats the hex is expected to be in.
|
|
enum class HexMode {
|
|
// 0x07230203, ...
|
|
Words,
|
|
// 0x07, 0x23, 0x02, 0x03, ...
|
|
BytesBigEndian,
|
|
// 0x03, 0x02, 0x23, 0x07, ...
|
|
BytesLittleEndian,
|
|
// 07 23 02 03 ...
|
|
StreamBigEndian,
|
|
// 03 02 23 07 ...
|
|
StreamLittleEndian,
|
|
};
|
|
|
|
// Whether a character should be skipped as whitespace / separator /
|
|
// end-of-file.
|
|
bool IsSpace(char c) { return isspace(c) || c == ',' || c == '\0'; }
|
|
|
|
bool IsHexStream(const std::vector<char>& stream) {
|
|
for (char c : stream) {
|
|
if (IsSpace(c)) {
|
|
continue;
|
|
}
|
|
|
|
// Every possible case of a SPIR-V hex stream starts with either '0' or 'x'
|
|
// (see |HexMode| values). Make a decision upon inspecting the first
|
|
// non-space character.
|
|
return c == '0' || c == 'x' || c == 'X';
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MatchIgnoreCase(const char* token, const char* expect, size_t len) {
|
|
for (size_t i = 0; i < len; ++i) {
|
|
if (tolower(token[i]) != tolower(expect[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Helper class to tokenize a hex stream
|
|
class HexTokenizer {
|
|
public:
|
|
HexTokenizer(const char* filename, const std::vector<char>& stream,
|
|
std::vector<uint32_t>* data)
|
|
: filename_(filename), stream_(stream), data_(data) {
|
|
DetermineMode();
|
|
}
|
|
|
|
bool Parse() {
|
|
while (current_ < stream_.size() && !encountered_error_) {
|
|
data_->push_back(GetNextWord());
|
|
|
|
// Make sure trailing space does not lead to parse error by skipping it
|
|
// and exiting the loop.
|
|
SkipSpace();
|
|
}
|
|
|
|
return !encountered_error_;
|
|
}
|
|
|
|
private:
|
|
void ParseError(const char* reason) {
|
|
if (!encountered_error_) {
|
|
fprintf(stderr,
|
|
"error: hex stream parse error at character %zu: %s in '%s'\n",
|
|
current_, reason, filename_);
|
|
encountered_error_ = true;
|
|
}
|
|
}
|
|
|
|
// Skip whitespace until the next non-whitespace non-comma character.
|
|
void SkipSpace() {
|
|
while (current_ < stream_.size()) {
|
|
char c = stream_[current_];
|
|
if (!IsSpace(c)) {
|
|
return;
|
|
}
|
|
|
|
++current_;
|
|
}
|
|
}
|
|
|
|
// Skip the 0x or x at the beginning of a hex value.
|
|
void Skip0x() {
|
|
// The first character must be 0 or x.
|
|
const char first = Next();
|
|
if (first != '0' && first != 'x' && first != 'X') {
|
|
ParseError("expected 0x or x");
|
|
} else if (first == '0') {
|
|
const char second = Next();
|
|
if (second != 'x' && second != 'X') {
|
|
ParseError("expected 0x");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Consume the next character.
|
|
char Next() { return current_ < stream_.size() ? stream_[current_++] : '\0'; }
|
|
|
|
// Determine how to read the hex stream based on the first token.
|
|
void DetermineMode() {
|
|
SkipSpace();
|
|
|
|
// Read 11 bytes, that is the size of the biggest token (10) + one more.
|
|
char first_token[11];
|
|
for (uint32_t i = 0; i < 11; ++i) {
|
|
first_token[i] = Next();
|
|
}
|
|
|
|
// Table of how to match the first token with a mode.
|
|
struct {
|
|
const char* expect;
|
|
bool must_have_delimiter;
|
|
HexMode mode;
|
|
} parse_info[] = {
|
|
{"0x07230203", true, HexMode::Words},
|
|
{"0x7230203", true, HexMode::Words},
|
|
{"x07230203", true, HexMode::Words},
|
|
{"x7230203", true, HexMode::Words},
|
|
|
|
{"0x07", true, HexMode::BytesBigEndian},
|
|
{"0x7", true, HexMode::BytesBigEndian},
|
|
{"x07", true, HexMode::BytesBigEndian},
|
|
{"x7", true, HexMode::BytesBigEndian},
|
|
|
|
{"0x03", true, HexMode::BytesLittleEndian},
|
|
{"0x3", true, HexMode::BytesLittleEndian},
|
|
{"x03", true, HexMode::BytesLittleEndian},
|
|
{"x3", true, HexMode::BytesLittleEndian},
|
|
|
|
{"07", false, HexMode::StreamBigEndian},
|
|
{"03", false, HexMode::StreamLittleEndian},
|
|
};
|
|
|
|
// Check to see if any of the possible first tokens are matched. If not,
|
|
// this is not a recognized hex stream.
|
|
encountered_error_ = true;
|
|
for (const auto& info : parse_info) {
|
|
const size_t expect_len = strlen(info.expect);
|
|
const bool matches_expect =
|
|
MatchIgnoreCase(first_token, info.expect, expect_len);
|
|
const bool satisfies_delimeter =
|
|
!info.must_have_delimiter || IsSpace(first_token[expect_len]);
|
|
if (matches_expect && satisfies_delimeter) {
|
|
mode_ = info.mode;
|
|
encountered_error_ = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (encountered_error_) {
|
|
fprintf(stderr,
|
|
"error: hex format detected, but pattern '%.11s' is not "
|
|
"recognized '%s'\n",
|
|
first_token, filename_);
|
|
}
|
|
|
|
// Reset the position to restart parsing with the determined mode.
|
|
current_ = 0;
|
|
}
|
|
|
|
// Consume up to |max_len| characters and put them in |token_chars|. A
|
|
// delimiter is expected. The resulting string is NUL-terminated.
|
|
void NextN(char token_chars[9], size_t max_len) {
|
|
assert(max_len < 9);
|
|
|
|
for (size_t i = 0; i <= max_len; ++i) {
|
|
char c = Next();
|
|
if (IsSpace(c)) {
|
|
token_chars[i] = '\0';
|
|
return;
|
|
}
|
|
|
|
token_chars[i] = c;
|
|
if (!isxdigit(c)) {
|
|
ParseError("encountered non-hex character");
|
|
}
|
|
}
|
|
|
|
// If space is not reached before the maximum number of characters where
|
|
// consumed, that's an error.
|
|
ParseError("expected delimiter (space or comma)");
|
|
token_chars[max_len] = '\0';
|
|
}
|
|
|
|
// Consume one hex digit.
|
|
char NextHexDigit() {
|
|
char c = Next();
|
|
if (!isxdigit(c)) {
|
|
ParseError("encountered non-hex character");
|
|
}
|
|
return c;
|
|
}
|
|
|
|
// Extract a token out of the stream. It could be either a word or a byte,
|
|
// based on |mode_|.
|
|
uint32_t GetNextToken() {
|
|
SkipSpace();
|
|
|
|
// The longest token can be 8 chars (for |HexMode::Words|), add one for
|
|
// '\0'.
|
|
char token_chars[9];
|
|
|
|
switch (mode_) {
|
|
case HexMode::Words:
|
|
case HexMode::BytesBigEndian:
|
|
case HexMode::BytesLittleEndian:
|
|
// Start with 0x, followed by up to 8 (for Word) or 2 (for Byte*)
|
|
// digits.
|
|
Skip0x();
|
|
NextN(token_chars, mode_ == HexMode::Words ? 8 : 2);
|
|
break;
|
|
case HexMode::StreamBigEndian:
|
|
case HexMode::StreamLittleEndian:
|
|
// Always expected to see two consecutive hex digits.
|
|
token_chars[0] = NextHexDigit();
|
|
token_chars[1] = NextHexDigit();
|
|
token_chars[2] = '\0';
|
|
break;
|
|
}
|
|
|
|
if (encountered_error_) {
|
|
return 0;
|
|
}
|
|
|
|
// Parse the hex value that was just read.
|
|
return static_cast<uint32_t>(strtol(token_chars, nullptr, 16));
|
|
}
|
|
|
|
// Construct a word out of tokens
|
|
uint32_t GetNextWord() {
|
|
if (mode_ == HexMode::Words) {
|
|
return GetNextToken();
|
|
}
|
|
|
|
uint32_t tokens[4] = {
|
|
GetNextToken(),
|
|
GetNextToken(),
|
|
GetNextToken(),
|
|
GetNextToken(),
|
|
};
|
|
|
|
switch (mode_) {
|
|
case HexMode::BytesBigEndian:
|
|
case HexMode::StreamBigEndian:
|
|
return tokens[0] << 24 | tokens[1] << 16 | tokens[2] << 8 | tokens[3];
|
|
case HexMode::BytesLittleEndian:
|
|
case HexMode::StreamLittleEndian:
|
|
return tokens[3] << 24 | tokens[2] << 16 | tokens[1] << 8 | tokens[0];
|
|
default:
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
const char* filename_;
|
|
const std::vector<char>& stream_;
|
|
std::vector<uint32_t>* data_;
|
|
|
|
HexMode mode_ = HexMode::Words;
|
|
size_t current_ = 0;
|
|
bool encountered_error_ = false;
|
|
};
|
|
} // namespace
|
|
|
|
bool ReadBinaryFile(const char* filename, std::vector<uint32_t>* data) {
|
|
assert(data->empty());
|
|
|
|
const bool use_file = filename && strcmp("-", filename);
|
|
FILE* fp = nullptr;
|
|
if (use_file) {
|
|
fp = fopen(filename, "rb");
|
|
} else {
|
|
SET_STDIN_TO_BINARY_MODE();
|
|
fp = stdin;
|
|
}
|
|
|
|
// Read into a char vector first. If this is a hex stream, it needs to be
|
|
// processed as such.
|
|
std::vector<char> data_raw;
|
|
ReadFile(fp, &data_raw);
|
|
bool succeeded = WasFileCorrectlyRead(fp, filename);
|
|
if (use_file && fp) fclose(fp);
|
|
|
|
if (!succeeded) {
|
|
return false;
|
|
}
|
|
|
|
if (IsHexStream(data_raw)) {
|
|
// If a hex stream, parse it and fill |data|.
|
|
HexTokenizer tokenizer(filename, data_raw, data);
|
|
succeeded = tokenizer.Parse();
|
|
} else {
|
|
// If not a hex stream, convert it to uint32_t via memcpy.
|
|
succeeded = WasFileSizeAligned(filename, data_raw.size(), sizeof(uint32_t));
|
|
if (succeeded) {
|
|
data->resize(data_raw.size() / sizeof(uint32_t), 0);
|
|
memcpy(data->data(), data_raw.data(), data_raw.size());
|
|
}
|
|
}
|
|
|
|
return succeeded;
|
|
}
|
|
|
|
bool ConvertHexToBinary(const std::vector<char>& stream,
|
|
std::vector<uint32_t>* data) {
|
|
HexTokenizer tokenizer("<input string>", stream, data);
|
|
return tokenizer.Parse();
|
|
}
|
|
|
|
bool ReadTextFile(const char* filename, std::vector<char>* data) {
|
|
assert(data->empty());
|
|
|
|
const bool use_file = filename && strcmp("-", filename);
|
|
FILE* fp = nullptr;
|
|
if (use_file) {
|
|
fp = fopen(filename, "r");
|
|
} else {
|
|
SET_STDIN_TO_TEXT_MODE();
|
|
fp = stdin;
|
|
}
|
|
|
|
ReadFile(fp, data);
|
|
bool succeeded = WasFileCorrectlyRead(fp, filename);
|
|
if (use_file && fp) fclose(fp);
|
|
return succeeded;
|
|
}
|
|
|
|
namespace {
|
|
// A class to create and manage a file for outputting data.
|
|
class OutputFile {
|
|
public:
|
|
// Opens |filename| in the given mode. If |filename| is nullptr, the empty
|
|
// string or "-", stdout will be set to the given mode.
|
|
OutputFile(const char* filename, const char* mode) : old_mode_(0) {
|
|
const bool use_stdout =
|
|
!filename || (filename[0] == '-' && filename[1] == '\0');
|
|
if (use_stdout) {
|
|
if (strchr(mode, 'b')) {
|
|
old_mode_ = SET_STDOUT_TO_BINARY_MODE();
|
|
} else {
|
|
old_mode_ = SET_STDOUT_TO_TEXT_MODE();
|
|
}
|
|
fp_ = stdout;
|
|
} else {
|
|
fp_ = fopen(filename, mode);
|
|
}
|
|
}
|
|
|
|
~OutputFile() {
|
|
if (fp_ == stdout) {
|
|
fflush(stdout);
|
|
SET_STDOUT_MODE(old_mode_);
|
|
} else if (fp_ != nullptr) {
|
|
fclose(fp_);
|
|
}
|
|
}
|
|
|
|
// Returns a file handle to the file.
|
|
FILE* GetFileHandle() const { return fp_; }
|
|
|
|
private:
|
|
FILE* fp_;
|
|
int old_mode_;
|
|
};
|
|
} // namespace
|
|
|
|
template <typename T>
|
|
bool WriteFile(const char* filename, const char* mode, const T* data,
|
|
size_t count) {
|
|
OutputFile file(filename, mode);
|
|
FILE* fp = file.GetFileHandle();
|
|
if (fp == nullptr) {
|
|
fprintf(stderr, "error: could not open file '%s'\n", filename);
|
|
return false;
|
|
}
|
|
|
|
size_t written = fwrite(data, sizeof(T), count, fp);
|
|
if (count != written) {
|
|
fprintf(stderr, "error: could not write to file '%s'\n", filename);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template bool WriteFile<uint32_t>(const char* filename, const char* mode,
|
|
const uint32_t* data, size_t count);
|
|
template bool WriteFile<char>(const char* filename, const char* mode,
|
|
const char* data, size_t count);
|