File size: 5,037 Bytes
28a1a01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#include "compile_plan.hpp"

#include <cstdint>
#include <sstream>
#include <stdexcept>

namespace cism {

const char* plan_code_version() {
    return "cism-plan-v1";
}

namespace {

// Standard FNV-1a 64-bit parameters.
constexpr std::uint64_t kFnvOffsetBasis = 14695981039346656037ULL;
constexpr std::uint64_t kFnvPrime = 1099511628211ULL;

std::uint64_t fnv1a64(const std::string& data) {
    std::uint64_t hash = kFnvOffsetBasis;
    for (unsigned char octet : data) {
        hash ^= static_cast<std::uint64_t>(octet);
        hash *= kFnvPrime;
    }
    return hash;
}

std::string to_hex16(std::uint64_t value) {
    static const char* kDigits = "0123456789abcdef";
    std::string out(16, '0');
    for (int i = 15; i >= 0; --i) {
        out[static_cast<std::size_t>(i)] = kDigits[value & 0xF];
        value >>= 4;
    }
    return out;
}

// Minimal JSON string escaper (no third-party deps). Passes UTF-8 bytes
// >= 0x20 through, escapes '"', '\\', and C0 controls per RFC 8259.
std::string json_escape(const std::string& value) {
    std::string out;
    out.reserve(value.size() + 2);
    for (unsigned char c : value) {
        switch (c) {
            case '"': out += "\\\""; break;
            case '\\': out += "\\\\"; break;
            case '\b': out += "\\b"; break;
            case '\f': out += "\\f"; break;
            case '\n': out += "\\n"; break;
            case '\r': out += "\\r"; break;
            case '\t': out += "\\t"; break;
            default:
                if (c < 0x20) {
                    char buf[7];
                    const char* digits = "0123456789abcdef";
                    buf[0] = '\\';
                    buf[1] = 'u';
                    buf[2] = '0';
                    buf[3] = '0';
                    buf[4] = digits[(c >> 4) & 0xF];
                    buf[5] = digits[c & 0xF];
                    buf[6] = '\0';
                    out += buf;
                } else {
                    out += static_cast<char>(c);
                }
                break;
        }
    }
    return out;
}

}  // namespace

std::string compute_compile_key(const std::string& canonical_config,
                                const std::string& precision,
                                const std::string& act_precision,
                                std::size_t threads,
                                const std::string& cpu_features,
                                const std::string& code_version) {
    if (canonical_config.empty()) throw std::invalid_argument("canonical_config must be nonempty");
    if (precision.empty()) throw std::invalid_argument("precision must be nonempty");
    if (act_precision.empty()) throw std::invalid_argument("act_precision must be nonempty");
    if (threads == 0) throw std::invalid_argument("threads must be in [1, 64]");
    if (cpu_features.empty()) throw std::invalid_argument("cpu_features must be nonempty");
    if (code_version.empty()) throw std::invalid_argument("code_version must be nonempty");
    // Labeled join keeps every field load-bearing: flipping any single
    // input changes the hashed string.
    const std::string canonical = "canonical=" + canonical_config +
                                  ";precision=" + precision +
                                  ";act=" + act_precision +
                                  ";threads=" + std::to_string(threads) +
                                  ";cpu=" + cpu_features +
                                  ";code=" + code_version;
    return to_hex16(fnv1a64(canonical));
}

std::string build_manifest(const std::string& key,
                           const std::string& precision,
                           const std::string& act_precision,
                           std::size_t threads,
                           const std::string& cpu_features,
                           const std::vector<std::pair<std::size_t, std::size_t>>& shapes) {
    if (key.empty()) throw std::invalid_argument("key must be nonempty");
    if (precision.empty()) throw std::invalid_argument("precision must be nonempty");
    if (act_precision.empty()) throw std::invalid_argument("act_precision must be nonempty");
    if (threads == 0) throw std::invalid_argument("threads must be in [1, 64]");
    if (cpu_features.empty()) throw std::invalid_argument("cpu_features must be nonempty");
    std::ostringstream out;
    out << "{\"magic\":\"cism-plan\""
        << ",\"version\":\"" << json_escape(plan_code_version()) << "\""
        << ",\"key\":\"" << json_escape(key) << "\""
        << ",\"precision\":\"" << json_escape(precision) << "\""
        << ",\"act_precision\":\"" << json_escape(act_precision) << "\""
        << ",\"threads\":" << threads
        << ",\"cpu_features\":\"" << json_escape(cpu_features) << "\""
        << ",\"shapes\":[";
    for (std::size_t i = 0; i < shapes.size(); ++i) {
        if (i) out << ",";
        out << "[" << shapes[i].first << "," << shapes[i].second << "]";
    }
    out << "]}";
    return out.str();
}

}  // namespace cism