| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- // 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);
|