simd.jaytux.com/api/src/main/kotlin/data/Loader.kt

283 lines
12 KiB
Kotlin

package com.jaytux.simd.data
import com.fleeksoft.ksoup.Ksoup
import com.jaytux.simd.data.IntrinsicInstructions.xed
import kotlinx.coroutines.coroutineScope
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import org.jetbrains.exposed.sql.batchInsert
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.transactions.transaction
import java.io.File
import org.json.JSONObject
import org.junit.jupiter.api.DisplayNameGenerator.Simple
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Date
import java.util.prefs.Preferences
import java.time.Instant
object Loader {
private val cache = Preferences.userNodeForPackage(this::class.java)
private val intelDateTimeFormat = SimpleDateFormat("MM/dd/yyyy")
private val dateFormat = SimpleDateFormat("dd-MMMM-yyyy")
data class XmlIntrinsic(val name: String, val tech: String, val retType: String, val retVar: String?,
val args: List<Pair<String, String>>, val desc: String, val op: String?,
val insn: List<Triple<String, String, String?>>, val cpuid: String?, val category: String
)
data class Version(val major: Int, val minor: Int, val patch: Int) {
override fun toString(): String = "$major.$minor.$patch"
infix fun isNewerThan(other: Version): Boolean {
if(major > other.major) return true
if(major == other.major && minor > other.minor) return true
if(major == other.major && minor == other.minor && patch > other.patch) return true
return false
}
companion object {
fun fromString(version: String): Version {
val parts = version.split(".")
return Version(parts[0].toInt(), parts[1].toInt(), parts[2].toInt())
}
}
}
data class DatedVersion(val version: Version, val releaseDate: Date, val updateDate: Date) {
override fun toString(): String = "$version (remote released ${dateFormat.format(releaseDate)}; local updated ${dateFormat.format(updateDate)})"
companion object {
fun fromPrefs(): DatedVersion {
val v = cache.get("version", null)?.let { Version.fromString(it) } ?: Version(0, 0, 0)
val r = cache.get("release", null)?.let { dateFormat.parse(it) } ?: Date.from(Instant.ofEpochMilli(0))
val u = cache.get("update", null)?.let { dateFormat.parse(it) } ?: Date.from(Instant.ofEpochMilli(0))
return DatedVersion(v, r, u)
}
}
}
data class XmlData(
val version: DatedVersion,
val types: Set<String>, val techs: Set<String>, val cpuids: Set<String>, val categories: Set<String>,
val intrinsics: List<XmlIntrinsic>
)
data class Performance(val latency: Float?, val throughput: Float?)
data class JsonData(val platforms: Set<String>, val data: Map<String, Map<String, Performance>>)
private suspend fun updateVersion(version: DatedVersion) = coroutineScope {
cache.put("version", version.version.toString())
cache.put("release", dateFormat.format(version.releaseDate))
cache.put("update", dateFormat.format(version.updateDate))
}
suspend fun loadXml(xmlFile: String): XmlData = coroutineScope {
val xml = Ksoup.parseXml(File(xmlFile).readText(Charsets.UTF_8))
val cppTypes = mutableSetOf<String>()
val techs = mutableSetOf<String>()
val cpuids = mutableSetOf<String>()
val categories = mutableSetOf<String>()
val intrins = mutableListOf<XmlIntrinsic>()
val errors = mutableListOf<String>()
val (version, release) = xml.getElementsByTag("intrinsics_list").firstOrNull()?.let {
val version = it.attribute("version")?.value?.let { v -> Version.fromString(v) }
if(version == null) {
errors += "Missing version attribute in intrinsics_list element"
return@let null
}
val date = it.attribute("date")?.value?.let { d -> intelDateTimeFormat.parse(d) }
if(date == null) {
errors += "Missing release_date attribute in intrinsics_list element"
return@let null
}
version to date
} ?: Version(0, 0, 0) to Date()
if(errors.isNotEmpty()) {
errors.forEach { System.err.println(it) }
throw Exception("XML file is (partially) invalid")
}
val update = Date()
xml.getElementsByTag("intrinsic").forEachIndexed { i, it ->
val name = it.attribute("name")?.value
if(name == null) {
errors += "Missing name attribute in intrinsic element"
return@forEachIndexed
}
val tech = it.attribute("tech")?.value
if(tech == null) {
errors += "Missing tech attribute for intrinsic $name"
return@forEachIndexed
}
val ret = it.getElementsByTag("return").firstOrNull()
if(ret == null) {
errors += "Missing return element for intrinsic $name"
return@forEachIndexed
}
val retType = ret.attribute("type")?.value
if(retType == null) {
errors += "Missing type attribute for return element in intrinsic $name"
return@forEachIndexed
}
val retVar = ret.attribute("varname")?.value
val args = mutableListOf<Pair<String, String>>()
it.getElementsByTag("parameter").forEachIndexed { i, p ->
val argName = p.attribute("varname")?.value
val type = p.attribute("type")?.value
if(type != null && type == "void") return@forEachIndexed //ignore
if(argName == null) {
errors += "Missing varname attribute for parameter $i in intrinsic $name"
return@forEachIndexed
}
if(type == null) {
errors += "Missing type attribute for parameter $argName in intrinsic $name"
return@forEachIndexed
}
cppTypes += type
args += argName to type
}
val desc = it.getElementsByTag("description").firstOrNull()?.text()
if(desc == null) {
errors += "Missing description element for intrinsic $name"
return@forEachIndexed
}
val op = it.getElementsByTag("operation").firstOrNull()?.text()
val insn = mutableListOf<Triple<String, String, String?>>()
it.getElementsByTag("instruction").forEachIndexed { i, ins ->
val insnName = ins.attribute("xed")?.value ?: ins.attribute("name")?.value
if(insnName == null) {
errors += "Missing both xed and name attribute for instruction $i in intrinsic $name"
return@forEachIndexed
}
val insnMnemonic = ins.attribute("name")?.value
if(insnMnemonic == null) {
errors += "Missing name attribute for instruction $insnName in intrinsic $name"
return@forEachIndexed
}
val insnForm = ins.attribute("form")?.value
insn += Triple(insnName, insnMnemonic, insnForm)
}
val cpuid = it.getElementsByTag("cpuid").firstOrNull()?.text()
val category = it.getElementsByTag("category").firstOrNull()?.text()
if(category == null) {
errors += "Missing category element for intrinsic $name"
return@forEachIndexed
}
val intrinsic = XmlIntrinsic(name, tech, retType, retVar, args, desc, op, insn, cpuid, category)
intrins += intrinsic
techs += tech
cpuid?.let { c -> cpuids += c }
categories += category
cppTypes += retType
}
if(errors.isNotEmpty()) {
errors.forEach { System.err.println(it) }
throw Exception("XML file is (partially) invalid")
}
XmlData(version = DatedVersion(version, release, update),
types = cppTypes, techs = techs, cpuids = cpuids, categories = categories,
intrinsics = intrins
)
}
suspend fun loadJson(jsonFile: String): JsonData = coroutineScope {
val json = File(jsonFile).readText(Charsets.UTF_8)
val schema = JSONObject(json)
val pSet = mutableSetOf<String>()
val res = mutableMapOf<String, MutableMap<String, Performance>>()
schema.keys().forEach { opcode ->
val pMap = mutableMapOf<String, Performance>()
val platforms = schema.getJSONArray(opcode)
for (i in 0 until platforms.length()) {
val platform = platforms.getJSONObject(i)
platform.keys().forEach { k ->
pSet += k
val latency = platform.getJSONObject(k).getString("l").toFloatOrNull()
val throughput = platform.getJSONObject(k).getString("t").toFloatOrNull()
pMap += k to Performance(latency, throughput)
}
}
res += opcode to pMap
}
JsonData(pSet, res)
}
suspend fun importToDb(xml: XmlData, json: JsonData) = coroutineScope {
val db = Database.db
transaction {
val techMap = xml.techs.associateWith { tech -> Tech.new { name = tech } }
val typeMap = xml.types.associateWith { type -> CppType.new { name = type } }
val catMap = xml.categories.associateWith { cat -> Category.new { name = cat } }
val cpuidMap = xml.cpuids.associateWith { cpuid -> CPUID.new { name = cpuid } }
val platformMap = json.platforms.associateWith { platform -> Platform.new { name = platform } }
xml.intrinsics.forEach { intr ->
val dbIn = Intrinsic.new {
mnemonic = intr.name
returnType = typeMap[intr.retType] ?: throw Exception("Type ${intr.retType} not found")
returnVar = intr.retVar
description = intr.desc
operations = intr.op
category = catMap[intr.category] ?: throw Exception("Category ${intr.category} not found")
cpuid = intr.cpuid?.let { cpuidMap[it] ?: throw Exception("CPUID ${intr.cpuid} not found") }
tech = techMap[intr.tech] ?: throw Exception("Tech ${intr.tech} not found")
}
intr.args.forEachIndexed { i, arg ->
IntrinsicArgument.new {
intrinsic = dbIn
name = arg.first
type = typeMap[arg.second] ?: throw Exception("Type ${arg.second} not found")
index = i
}
}
intr.insn.forEach { insn ->
val dbInsn = IntrinsicInstruction.new {
intrinsic = dbIn
xed = insn.first
mnemonic = insn.second
insn.third?.let { form = it }
}
json.data[insn.first]?.forEach { (pl, perf) ->
val dbPl = platformMap[pl] ?: throw Exception("Platform $pl not found")
Performances.insert {
it[instruction] = dbInsn.id
it[platform] = dbPl.id
it[latency] = perf.latency
it[throughput] = perf.throughput
}
}
}
}
}
updateVersion(xml.version)
}
}