#pragma once #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Endian.h" #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" #include #include "src/Accelerators/PIM/Common/Support/CheckedArithmetic.hpp" namespace onnx_mlir::pim_binary { inline constexpr char kMagic[4] = {'P', 'I', 'M', 'B'}; inline constexpr uint32_t kVersion = 1; inline constexpr uint64_t kCountOffset = 8; inline constexpr size_t kHeaderSize = 12; inline constexpr size_t kRecordSize = 20; enum class Opcode : uint32_t { nop = 0, sldi = 1, sld = 2, sadd = 3, ssub = 4, smul = 5, saddi = 6, smuli = 7, setbw = 8, mvmul = 9, vvadd = 10, vvsub = 11, vvmul = 12, vvdmul = 13, vvmax = 14, vvsll = 15, vvsra = 16, vavg = 17, vrelu = 18, vtanh = 19, vsigm = 20, vsoftmax = 21, vmv = 22, vrsu = 23, vrsl = 24, ld = 25, st = 26, lldi = 27, lmv = 28, send = 29, recv = 30, wait = 31, sync = 32, }; inline constexpr size_t kOpcodeCount = static_cast(Opcode::sync) + 1; inline constexpr std::array kOpcodeNames = { "nop", "sldi", "sld", "sadd", "ssub", "smul", "saddi", "smuli", "setbw", "mvmul", "vvadd", "vvsub", "vvmul", "vvdmul", "vvmax", "vvsll", "vvsra", "vavg", "vrelu", "vtanh", "vsigm", "vsoftmax", "vmv", "vrsu", "vrsl", "ld", "st", "lldi", "lmv", "send", "recv", "wait", "sync", }; static_assert(kOpcodeNames.size() == kOpcodeCount); struct InstructionRecord { Opcode opcode = Opcode::nop; uint8_t rd = 0; uint8_t r1 = 0; int32_t r2OrImm = 0; int32_t generic1 = 0; int32_t generic2 = 0; int32_t generic3 = 0; uint8_t flags = 0; }; using EncodedInstruction = std::array; static_assert(kRecordSize == 20); static_assert(sizeof(EncodedInstruction) == kRecordSize); inline EncodedInstruction encodeInstructionRecord(const InstructionRecord& record) { EncodedInstruction encoded = {}; encoded[0] = static_cast(static_cast(record.opcode)); encoded[1] = static_cast(record.rd); encoded[2] = static_cast(record.r1); encoded[3] = static_cast(record.flags); llvm::support::endian::write32le(encoded.data() + 4, static_cast(record.r2OrImm)); llvm::support::endian::write32le(encoded.data() + 8, static_cast(record.generic1)); llvm::support::endian::write32le(encoded.data() + 12, static_cast(record.generic2)); llvm::support::endian::write32le(encoded.data() + 16, static_cast(record.generic3)); return encoded; } inline void writeUint32LE(llvm::raw_ostream& os, uint32_t value) { std::array bytes; llvm::support::endian::write32le(bytes.data(), value); os.write(bytes.data(), bytes.size()); } inline void writeHeader(llvm::raw_ostream& os) { os.write(kMagic, sizeof(kMagic)); writeUint32LE(os, kVersion); writeUint32LE(os, 0); } inline void patchInstructionCount(llvm::raw_pwrite_stream& os, uint32_t instructionCount) { std::array bytes; llvm::support::endian::write32le(bytes.data(), instructionCount); os.pwrite(bytes.data(), bytes.size(), kCountOffset); } inline int32_t toI32(int64_t value) { return onnx_mlir::pim::checkedI32OrCrash(value, "binary field"); } inline uint8_t toU8(int64_t value) { return onnx_mlir::pim::checkedU8OrCrash(static_cast(value), "binary field"); } inline int32_t getOptionalInt(const llvm::json::Object& object, llvm::StringRef key, int32_t defaultValue = 0) { if (std::optional value = object.getInteger(key)) return toI32(*value); return defaultValue; } struct InstructionJsonFormat { bool rd; bool r1; bool offset; llvm::StringLiteral r2; llvm::StringLiteral generic1; llvm::StringLiteral generic2; llvm::StringLiteral generic3; }; inline constexpr std::array kInstructionJsonFormats = {{ {false, false, false, "", "", "", "" }, // nop {true, false, false, "imm", "", "", "" }, // sldi {true, true, true, "", "", "", "" }, // sld {true, true, false, "rs2", "", "", "" }, // sadd {true, true, false, "rs2", "", "", "" }, // ssub {true, true, false, "rs2", "", "", "" }, // smul {true, true, false, "imm", "", "", "" }, // saddi {true, true, false, "imm", "", "", "" }, // smuli {false, false, false, "", "ibiw", "obiw", "" }, // setbw {true, true, false, "mbiw", "relu", "group", "" }, // mvmul {true, true, true, "rs2", "", "", "len" }, // vvadd {true, true, true, "rs2", "", "", "len" }, // vvsub {true, true, true, "rs2", "", "", "len" }, // vvmul {true, true, true, "rs2", "", "", "len" }, // vvdmul {true, true, true, "rs2", "", "", "len" }, // vvmax {true, true, true, "rs2", "", "", "len" }, // vvsll {true, true, true, "rs2", "", "", "len" }, // vvsra {true, true, true, "rs2", "", "", "len" }, // vavg {true, true, true, "", "", "", "len" }, // vrelu {true, true, true, "", "", "", "len" }, // vtanh {true, true, true, "", "", "", "len" }, // vsigm {true, true, true, "", "", "", "len" }, // vsoftmax {true, true, true, "rs2", "", "", "len" }, // vmv {true, true, true, "rs2", "", "", "len" }, // vrsu {true, true, true, "rs2", "", "", "len" }, // vrsl {true, true, true, "", "", "", "size"}, // ld {true, true, true, "", "", "", "size"}, // st {true, false, true, "imm", "", "", "len" }, // lldi {true, true, true, "", "", "", "len" }, // lmv {true, false, true, "core", "", "", "size"}, // send {true, false, true, "core", "", "", "size"}, // recv {false, false, false, "", "event_register", "wait_value", ""}, // wait {false, false, false, "core", "event_register", "", ""}, // sync }}; static_assert(kInstructionJsonFormats.size() == kOpcodeCount); inline Opcode opcodeFromString(llvm::StringRef opName) { for (auto [index, name] : llvm::enumerate(kOpcodeNames)) if (opName == name) return static_cast(index); llvm_unreachable("Unsupported PIM binary opcode"); } inline llvm::StringRef opcodeToString(Opcode opcode) { size_t index = static_cast(opcode); assert(index < kOpcodeNames.size() && "Unsupported PIM binary opcode"); return kOpcodeNames[index]; } inline InstructionRecord makeInstructionRecord(const llvm::json::Object& instruction) { InstructionRecord record; std::optional opName = instruction.getString("op"); assert(opName && "Missing op field in PIM instruction"); record.opcode = opcodeFromString(*opName); const auto& format = kInstructionJsonFormats[static_cast(record.opcode)]; if (format.rd) record.rd = toU8(getOptionalInt(instruction, "rd")); if (format.r1) record.r1 = toU8(getOptionalInt(instruction, "rs1")); if (!format.r2.empty()) record.r2OrImm = getOptionalInt(instruction, format.r2); if (!format.generic1.empty()) record.generic1 = getOptionalInt(instruction, format.generic1); if (!format.generic2.empty()) record.generic2 = getOptionalInt(instruction, format.generic2); if (format.offset) { if (auto* offsetValue = instruction.getObject("offset")) { record.generic1 = getOptionalInt(*offsetValue, "offset_select"); record.generic2 = getOptionalInt(*offsetValue, "offset_value"); } } if (!format.generic3.empty()) record.generic3 = getOptionalInt(instruction, format.generic3); return record; } inline llvm::json::Object makeInstructionJson(const InstructionRecord& record) { llvm::json::Object instruction; instruction["op"] = opcodeToString(record.opcode).str(); auto addOffset = [&](int32_t offsetSelect, int32_t offsetValue) { llvm::json::Object offset; offset["offset_select"] = offsetSelect; offset["offset_value"] = offsetValue; instruction["offset"] = std::move(offset); }; const auto& format = kInstructionJsonFormats[static_cast(record.opcode)]; if (format.rd) instruction["rd"] = static_cast(record.rd); if (format.r1) instruction["rs1"] = static_cast(record.r1); if (!format.r2.empty()) instruction[format.r2] = record.r2OrImm; if (!format.generic1.empty()) instruction[format.generic1] = record.generic1; if (!format.generic2.empty()) instruction[format.generic2] = record.generic2; if (format.offset) addOffset(record.generic1, record.generic2); if (!format.generic3.empty()) instruction[format.generic3] = record.generic3; return instruction; } } // namespace onnx_mlir::pim_binary