#include "compile_plan.hpp" #include #include #include 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(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(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(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>& 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