commit 5f91256c317aa4a4168ea60abb486f509858912c Author: jay-tux Date: Mon Apr 21 11:27:13 2025 +0200 Initial API diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 0000000..9c6bea1 --- /dev/null +++ b/api/.gitignore @@ -0,0 +1,45 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/* +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Kotlin ### +.kotlin + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store + +.env +intrinsics.sqlite diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..34aaed0 --- /dev/null +++ b/api/README.md @@ -0,0 +1,133 @@ +# Intrinsics API +*The backend API powering [https://simd.jaytux.com](https://simd.jaytux.com/api)* + +## Preparation +1. Set up the project's sources (recommended to just use Intellij IDEA, and have it figure out everything for you, which should work out of the box). Alternatively, the `./gradlew` wrapper can be used to build the project. +2. Create an environment file (`.env`) in the root directory of the project, with the following variables: + - `DATABASE_URL`: The JDBC URL to the database; for example: + - `jdbc:sqlite:./intrinsics.sqlite` for a local SQLite database + - `jdbc:mariadb://localhost:3306/intrinsics` for a MariaDB database (served on the local machine at port 3306), which should already be running + - `DATABASE_DRIVER`: The JDBC driver class name; for example: + - `org.sqlite.JDBC` for SQLite + - `org.mariadb.jdbc.Driver` for MariaDB + - `DATABASE_USER`: the username to connect to the database; not required for SQLite + - `DATABASE_PASSWORD`: the password to connect to the database; not required for SQLite +3. (Optional) change the default port in the `src/main/resources/application.yaml` configuration file. +4. Get the intrinsics data from the [Intel Intrinsics Guide](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html) and post-process the data (I am planning to automate this in the future, stand by). + 1. The downloaded data is a ZIP-file, with the relevant data files being `files/data.js` and `files/perf2.js`. + 2. Post-process `files/data.js` to be a valid XML file (when downloaded, it's an XML string in a JS variable): + 1. Remove the `var data = "` prefix at the beginning of the file + 2. Remove the `";` suffix at the end of the file + 3. Replace all `\n` with actual newlines, and all `\"` with `"`, and then remove all trailing `\` + 3. Post-process `files/perf2.js` to be a valid JSON file (when downloaded, it's a JSON string in a JS variable): + 1. Remove the `perf2_js = ` prefix at the beginning of the file + 2. Replace all `{l:` by `{"l":`, and all `,t:` by `,"t":` +5. At this point, you are ready to load the data into the database using the application. Run `./gradlew run -reload /path/to/post-processed/data.js /path/to/post-processed/perf2.js` +6. Finally, you can start the application using `./gradlew run` + +## Running +By default, the application runs on port `42024`. You can start it using `./gradlew run`. + +To reload the database (it drops all data, then re-parses everything), use `./gradlew run -reload /path/to/xml /path/to/json`. + +## API +Most of the API is paginated. The default page size is 100, and the default page number is 0. Each paginated response has the following format: +```json +{ + "page": , + "totalPages": , + "items": [] +} +``` + +Typically, you can use the "root" API endpoint (e.g. `/all`) to get the first page of results, and get any page by using the `/{page}` query parameter (e.g. `/all/3`). There is one notable exception (`/search`), where the page number is specified as a query parameter (`/search?page=3`). Finally, the details endpoint (`/details/{id}`) is not paginated, and returns a single object. + +### `GET /all` +Gets a (paginated) list of all intrinsics. For each intrinsic, the following fields are returned: +```json +{ + "id": "", + "name": "" +} +``` + +Other pages can be requested using the `GET /all/{page}` endpoint (`GET /all` is equivalent to `GET /all/0`). + +### `GET /cpuid` +Gets a (paginated) list of all CPUIDs in the database. Each CPUID can be used as a filter for the `/search` endpoint. The data is a simple list of strings. Examples include `PREFETCHI`, `SSE2`, `AVX`, etc. + +### `GET /tech` +Gets a (paginated) list of all technologies in the database. Each technology can be used as a filter for the `/search` endpoint. The data is a simple list of strings. The full list (at the moment of writing) is: +- `AMX` +- `AVX-512` +- `AVX_ALL` +- `MMX` +- `Other` +- `SSE_ALL` +- `SVML` + +### `GET /category` +Gets a (paginated) list of all categories in the database. Each category can be used as a filter for the `/search` endpoint. The data is a simple list of strings. Examples include `Logical`, `OS-Targeted`, `Swizzle`, etc. + +### `GET /types` +Gets a (paginated) list of all C/C++(-like) types used by the intrinsics. The types can be used as filter for the `/search` endpoint (but currently only based on return type). The data is a simple list of strings. Examples include `__int16`, `__m128 const *`, `string literal`, etc. + +### `GET /search` +Searches the database using the given filters. The filters are passed as query parameters, and can be combined. All filters are optional. The following filters are available: +- `name=[string]`: searches based on the name of the intrinsic; employs fuzzy-search (using `LIKE %it%`) +- `return=[string]`: searches based on the return type of the intrinsic; exact search only +- `cpuid=[string]`: searches based on the CPUID of the intrinsic; exact search only +- `tech=[string]`: searches based on the technology of the intrinsic; exact search only +- `category=[string]`: searches based on the category of the intrinsic; exact search only +- `desc=[string]`: searches based on the description of the intrinsic; employs fuzzy-search (using `LIKE %it%`) +- `page=[int]`: specifies the page number to return (default is 0) + +Passing no filters is equivalent to using `GET /all`, and data is returned in the same format: +```json +{ + "page": , + "totalPages": , + "items": [ + { + "id": "", + "name": "" + } + ] +} +``` + +### `GET /details/{id}` +Gets the details for a single, specific intrinsic. The following data is returned: +```json +{ + "id": "", + "name": "", + "returnType": "", + "returnVar": "", + "description": "", + "operations": "", + "category": "", + "cpuid": "", + "tech": "", + "params": [ + { + "name": "", + "type": "" + } + ], + "instructions": [ + { + "mnemonic": "", + "xed": "", + "form": "" + } + ], + "performance": [ + { + "platform": "", + "latency": , + "throughput": + } + ] +} +``` \ No newline at end of file diff --git a/api/build.gradle.kts b/api/build.gradle.kts new file mode 100644 index 0000000..a4b1b6d --- /dev/null +++ b/api/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + alias(libs.plugins.jvm) + alias(libs.plugins.ktor) + alias(libs.plugins.serialization) +} + +group = "com.jaytux.simd" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + implementation(libs.exposed.core) + implementation(libs.exposed.dao) + implementation(libs.exposed.jdbc) + + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.logging) + + implementation(libs.ktor.server.content.negotiation) + implementation(libs.ktor.server.core) + implementation(libs.ktor.server.openapi) + implementation(libs.ktor.server.auto.head.response) + implementation(libs.ktor.server.netty) + implementation(libs.ktor.server.config.yaml) + implementation(libs.ktor.server.test.host) + + implementation(libs.ktor.serialization.kotlinx.json) + + implementation(libs.dotenv) + implementation(libs.json) + implementation(libs.kotlinx.serialization.json) + implementation(libs.ksoup) + implementation(libs.logback.classic) + implementation(libs.mariadb) + implementation(libs.sqlite) +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(21) +} \ No newline at end of file diff --git a/api/gradle.properties b/api/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/api/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/api/gradle/libs.versions.toml b/api/gradle/libs.versions.toml new file mode 100644 index 0000000..ceb7578 --- /dev/null +++ b/api/gradle/libs.versions.toml @@ -0,0 +1,46 @@ +[versions] +kotlin = "2.1.10" +ktor = "3.1.2" +exposed = "0.58.0" +ksoup = "0.2.2" +logback = "1.5.6" +sqlite = "3.34.0" +mariadb = "3.5.3" +dotenv = "6.5.1" +serialization = "1.8.1" +json = "20231013" + +[libraries] +exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" } +exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" } +exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" } + +ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } +ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } +ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } + +ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } +ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } +ktor-server-openapi = { module = "io.ktor:ktor-server-openapi", version.ref = "ktor" } +ktor-server-auto-head-response = { module = "io.ktor:ktor-server-auto-head-response", version.ref = "ktor" } +ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } +ktor-server-config-yaml = { module = "io.ktor:ktor-server-config-yaml", version.ref = "ktor" } +ktor-server-test-host = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" } + +ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } + +dotenv = { module = "io.github.cdimascio:dotenv-kotlin", version.ref = "dotenv" } +json = { module = "org.json:json", version.ref = "json" } +kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } +ksoup = { module = "com.fleeksoft.ksoup:ksoup", version.ref = "ksoup" } +logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } +mariadb = { module = "org.mariadb.jdbc:mariadb-java-client", version.ref = "mariadb" } +sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" } + + + +[plugins] +jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +ktor = { id = "io.ktor.plugin", version.ref = "ktor" } +serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } \ No newline at end of file diff --git a/api/gradle/wrapper/gradle-wrapper.jar b/api/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/api/gradle/wrapper/gradle-wrapper.jar differ diff --git a/api/gradle/wrapper/gradle-wrapper.properties b/api/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2d7c225 --- /dev/null +++ b/api/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Apr 18 09:44:09 CEST 2025 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/api/gradlew b/api/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/api/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/api/gradlew.bat b/api/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/api/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/api/settings.gradle.kts b/api/settings.gradle.kts new file mode 100644 index 0000000..595dbf6 --- /dev/null +++ b/api/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} +rootProject.name = "api" + diff --git a/api/src/main/kotlin/DotEnv.kt b/api/src/main/kotlin/DotEnv.kt new file mode 100644 index 0000000..bfbafd0 --- /dev/null +++ b/api/src/main/kotlin/DotEnv.kt @@ -0,0 +1,15 @@ +package com.jaytux.simd + +import io.github.cdimascio.dotenv.dotenv + +object DotEnv { + val env = dotenv() + + operator fun get(name: String) = env[name] + + class DotEnvException(missing: String) : RuntimeException("Missing environment variable: $missing") { + constructor(missing: String, cause: Throwable) : this(missing) { + initCause(cause) + } + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/Main.kt b/api/src/main/kotlin/Main.kt new file mode 100644 index 0000000..b0960e2 --- /dev/null +++ b/api/src/main/kotlin/Main.kt @@ -0,0 +1,55 @@ +package com.jaytux.simd + +import com.jaytux.simd.data.Database +import com.jaytux.simd.data.Loader +import com.jaytux.simd.server.configureHTTP +import com.jaytux.simd.server.configureRouting +import com.jaytux.simd.server.configureSerialization +import io.ktor.server.application.* +import io.ktor.server.netty.* +import kotlinx.coroutines.runBlocking + +fun main(args: Array) { + if(args.size > 3 && args[1] == "-reload") { + val xmlFile = args[2] + val jsonFile = args[3] + dbSetup(xmlFile, jsonFile) + } + else if(args.size >= 1 && args[1] == "-h") { + println("Usage: ${args[0]} -reload (to reload the database)") + println(" ${args[0]} (to start the server)") + } + else { + EngineMain.main(args) + } +} + +fun dbSetup(xmlFile: String, jsonFile: String) { + runBlocking { + val xml = Loader.loadXml("/home/jay/intrinsics/data.xml") + val perf = Loader.loadJson("/home/jay/intrinsics/perf2.js") + Loader.importToDb(xml, perf) + } +} + +fun Application.module() { + Database.db + configureSerialization() + configureHTTP() + configureRouting() +} + +// API: (everything except /details/ is paginated per 100) +// - GET /all (list of SIMD intrinsics (name + ID)) +// - GET /cpuid (list of CPUID values) +// - GET /tech (list of techs) +// - GET /category (list of categories) +// - GET /types (list of types) +// - GET /search (search for SIMD intrinsics); query params: +// - name (string, optional, partial matching) +// - return (string, optional, full match) +// - cpuid (string, optional, full match) +// - tech (string, optional, full match) +// - category (string, optional, full match) +// - desc (string, optional, partial matching) +// - GET /details/ (details of a SIMD intrinsic) \ No newline at end of file diff --git a/api/src/main/kotlin/data/Database.kt b/api/src/main/kotlin/data/Database.kt new file mode 100644 index 0000000..391f8ae --- /dev/null +++ b/api/src/main/kotlin/data/Database.kt @@ -0,0 +1,32 @@ +package com.jaytux.simd.data + +import com.jaytux.simd.DotEnv +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction + +object Database { + val db by lazy { + val db = Database.connect( + url = DotEnv["DATABASE_URL"] ?: throw DotEnv.DotEnvException("DATABASE_URL"), + driver = DotEnv["DATABASE_DRIVER"] ?: throw DotEnv.DotEnvException("DATABASE_DRIVER"), + user = DotEnv["DATABASE_USER"] ?: "", + password = DotEnv["DATABASE_PASSWORD"] ?: "", + ) + transaction { + SchemaUtils.create(Techs, CppTypes, Categories, CPUIDs, Intrinsics, + IntrinsicArguments, IntrinsicInstructions, Platforms, Performances) + } + + db + } + + fun reset() { + transaction { + SchemaUtils.drop(Techs, CppTypes, Categories, CPUIDs, Intrinsics, + IntrinsicArguments, IntrinsicInstructions, Platforms, Performances) + SchemaUtils.create(Techs, CppTypes, Categories, CPUIDs, Intrinsics, + IntrinsicArguments, IntrinsicInstructions, Platforms, Performances) + } + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/data/Entities.kt b/api/src/main/kotlin/data/Entities.kt new file mode 100644 index 0000000..152a712 --- /dev/null +++ b/api/src/main/kotlin/data/Entities.kt @@ -0,0 +1,82 @@ +package com.jaytux.simd.data + +import com.jaytux.simd.data.CppType.Companion.optionalReferrersOn +import org.jetbrains.exposed.dao.* +import org.jetbrains.exposed.dao.id.CompositeID +import org.jetbrains.exposed.dao.id.EntityID +import java.util.* + +class Tech(id: EntityID) : UUIDEntity(id) { + companion object : UUIDEntityClass(Techs) + + var name by Techs.name +} + +class CppType(id: EntityID) : UUIDEntity(id) { + companion object : UUIDEntityClass(CppTypes) + + var name by CppTypes.name +} + +class Category(id: EntityID) : UUIDEntity(id) { + companion object : UUIDEntityClass(Categories) + + var name by Categories.name +} + +class CPUID(id: EntityID) : UUIDEntity(id) { + companion object : UUIDEntityClass(CPUIDs) + + var name by CPUIDs.name +} + +class Intrinsic(id: EntityID) : UUIDEntity(id) { + companion object : UUIDEntityClass(Intrinsics) + + var mnemonic by Intrinsics.mnemonic + var returnType by CppType referencedOn Intrinsics.returnType + var returnVar by Intrinsics.returnVar + var description by Intrinsics.description + var operations by Intrinsics.operations + var category by Category referencedOn Intrinsics.category + var cpuid by CPUID optionalReferencedOn Intrinsics.cpuid + var tech by Tech referencedOn Intrinsics.tech + + val arguments by IntrinsicArgument referrersOn IntrinsicArguments.intrinsic + val instructions by IntrinsicInstruction referrersOn IntrinsicInstructions.intrinsic +} + +class IntrinsicArgument(id: EntityID) : UUIDEntity(id) { + companion object : UUIDEntityClass(IntrinsicArguments) + + var intrinsic by Intrinsic referencedOn IntrinsicArguments.intrinsic + var name by IntrinsicArguments.name + var type by CppType referencedOn IntrinsicArguments.type + var index by IntrinsicArguments.index +} + +class IntrinsicInstruction(id: EntityID) : UUIDEntity(id) { + companion object : UUIDEntityClass(IntrinsicInstructions) + + var intrinsic by Intrinsic referencedOn IntrinsicInstructions.intrinsic + var mnemonic by IntrinsicInstructions.mnemonic + var xed by IntrinsicInstructions.xed + var form by IntrinsicInstructions.form + + val performance by Performance referrersOn Performances.instruction +} + +class Platform(id: EntityID) : UUIDEntity(id) { + companion object : UUIDEntityClass(Platforms) + + var name by Platforms.name +} + +class Performance(id: EntityID) : CompositeEntity(id) { + companion object : CompositeEntityClass(Performances) + + var instruction by IntrinsicInstruction referencedOn Performances.instruction + var platform by Platform referencedOn Performances.platform + var latency by Performances.latency + var throughput by Performances.throughput +} \ No newline at end of file diff --git a/api/src/main/kotlin/data/Loader.kt b/api/src/main/kotlin/data/Loader.kt new file mode 100644 index 0000000..d1c9945 --- /dev/null +++ b/api/src/main/kotlin/data/Loader.kt @@ -0,0 +1,205 @@ +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 + +object Loader { + data class XmlIntrinsic(val name: String, val tech: String, val retType: String, val retVar: String?, val args: List>, val desc: String, val op: String?, val insn: List>, val cpuid: String?, val category: String) + + data class XmlData( + val types: Set, val techs: Set, val cpuids: Set, val categories: Set, + val intrinsics: List + ) + + data class Performance(val latency: Float?, val throughput: Float?) + + data class JsonData(val platforms: Set, val data: Map>) + + suspend fun loadXml(xmlFile: String): XmlData = coroutineScope { + val xml = Ksoup.parseXml(File(xmlFile).readText(Charsets.UTF_8)) + + val cppTypes = mutableSetOf() + val techs = mutableSetOf() + val cpuids = mutableSetOf() + val categories = mutableSetOf() + val intrins = mutableListOf() + + val errors = mutableListOf() + + 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>() + 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>() + 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(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() + val res = mutableMapOf>() + + schema.keys().forEach { opcode -> + val pMap = mutableMapOf() + 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 + } + } + } + } + } + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/data/Tables.kt b/api/src/main/kotlin/data/Tables.kt new file mode 100644 index 0000000..e72882d --- /dev/null +++ b/api/src/main/kotlin/data/Tables.kt @@ -0,0 +1,63 @@ +package com.jaytux.simd.data + +import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.dao.id.CompositeIdTable + +object Techs : UUIDTable() { + val name = varchar("name", 255).uniqueIndex() +} + +object CppTypes : UUIDTable() { + val name = varchar("name", 255).uniqueIndex() +} + +object Categories : UUIDTable() { + val name = varchar("name", 255).uniqueIndex() +} + +object CPUIDs : UUIDTable() { + val name = varchar("name", 255).uniqueIndex() +} + +object Intrinsics : UUIDTable() { + val mnemonic = varchar("mnemonic", 255) + val returnType = reference("return_type", CppTypes) + val returnVar = varchar("return_var", 255).nullable() + val description = text("description") + val operations = text("operations").nullable() + val category = reference("category", Categories) + val cpuid = reference("cpuid", CPUIDs).nullable() + val tech = reference("tech", Techs) +} + +object IntrinsicArguments : UUIDTable() { + val intrinsic = reference("intrinsic", Intrinsics) + val name = varchar("name", 255) + val type = reference("type", CppTypes) + val index = integer("index") + + init { + uniqueIndex(intrinsic, name) + uniqueIndex(intrinsic, index) + } +} + +object IntrinsicInstructions : UUIDTable() { + val intrinsic = reference("intrinsic", Intrinsics) + val mnemonic = varchar("mnemonic", 255) + val xed = varchar("xed", 255) + val form = text("form").nullable() +} + +object Platforms : UUIDTable() { + val name = varchar("name", 255).uniqueIndex() +} + +object Performances : CompositeIdTable() { + val instruction = reference("instruction", IntrinsicInstructions) + val platform = reference("platform", Platforms) + val latency = float("latency").nullable() + val throughput = float("throughput").nullable() + + override val primaryKey: PrimaryKey = PrimaryKey(instruction, platform) +} \ No newline at end of file diff --git a/api/src/main/kotlin/server/Endpoints.kt b/api/src/main/kotlin/server/Endpoints.kt new file mode 100644 index 0000000..772da00 --- /dev/null +++ b/api/src/main/kotlin/server/Endpoints.kt @@ -0,0 +1,122 @@ +package com.jaytux.simd.server + +import com.jaytux.simd.data.* +import io.ktor.server.routing.* +import kotlinx.serialization.Serializable +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.SqlExpressionBuilder.like +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.* + +@Serializable +data class IntrinsicSummary(@Serializable(with = UUIDSerializer::class) val id: UUID, val name: String) + +@Serializable +data class Param(val name: String, val type: String) + +@Serializable +data class Instruction(val mnemonic: String, val xed: String, val form: String?) + +@Serializable +data class PlatformPerformance(val platform: String, val latency: Float?, val throughput: Float?) + +@Serializable +data class IntrinsicDetails( + @Serializable(with = UUIDSerializer::class) val id: UUID, + val name: String, + val returnType: String, + val returnVar: String?, + val description: String, + val operations: String?, + val category: String, + val cpuid: String?, + val tech: String, + val params: List, + val instructions: List?, + val performance: List? +) + +fun Routing.installGetAll() { + getPagedUrl("/all", { 100 }, { IntrinsicSummary(it.id.value, it.mnemonic) }) { + Intrinsic.all().orderAsc(Intrinsics.mnemonic) + } + + getPagedUrl("/cpuid", { 100 }, { it.name }) { CPUID.all().orderAsc(CPUIDs.name) } + getPagedUrl("/tech", { 100 }, { it.name }) { Tech.all().orderAsc(Techs.name) } + getPagedUrl("/category", { 100 }, { it.name }) { Category.all().orderAsc(Categories.name) } + getPagedUrl("/types", { 100 }, { it.name }) { CppType.all().orderAsc(CppTypes.name) } +} + +fun Routing.installSearch() { + getPagedRequest("/search", { 100 }, { IntrinsicSummary(it[Intrinsics.id].value, it[Intrinsics.mnemonic]) }) { + val name = call.request.queryParameters["name"] + val returnType = call.request.queryParameters["return"]?.let { + CppType.find { CppTypes.name eq it }.firstOrNull() + ?: throw HttpError("Unknown return type: $it") + } + val cpuid = call.request.queryParameters["cpuid"]?.let { + CPUID.find { CPUIDs.name eq it }.firstOrNull() + ?: throw HttpError("Unknown CPUID: $it") + } + val tech = call.request.queryParameters["tech"]?.let { + Tech.find { Techs.name eq it }.firstOrNull() + ?: throw HttpError("Unknown tech: $it") + } + val category = call.request.queryParameters["category"]?.let { + Category.find { Categories.name eq it }.firstOrNull() + ?: throw HttpError("Unknown category: $it") + } + val desc = call.request.queryParameters["desc"] + + var results = Intrinsics.selectAll() + name?.let { results = results.where { Intrinsics.mnemonic like "%$it%" } } + returnType?.let { results = results.where { Intrinsics.returnType eq it.id } } + cpuid?.let { results = results.where { Intrinsics.cpuid eq it.id } } + tech?.let { results = results.where { Intrinsics.tech eq it.id } } + category?.let { results = results.where { Intrinsics.category eq it.id } } + desc?.let { results = results.where { Intrinsics.description like "%$it%" } } + + results.orderAsc(Intrinsics.mnemonic) + } +} + +fun Routing.installDetails() { + get("/details/{id}") { + runCatching { + transaction { + val id = call.parameters["id"]?.let { UUID.fromString(it) } + ?: throw HttpError("Missing or invalid ID") + val intrinsic = Intrinsic.findById(id) + ?: throw HttpError("Unknown intrinsic: $id") + + IntrinsicDetails( + id = intrinsic.id.value, + name = intrinsic.mnemonic, + returnType = intrinsic.returnType.name, + returnVar = intrinsic.returnVar, + description = intrinsic.description, + operations = intrinsic.operations, + category = intrinsic.category.name, + cpuid = intrinsic.cpuid?.name, + tech = intrinsic.tech.name, + params = intrinsic.arguments.orderAsc(IntrinsicArguments.index) + .map { Param(it.name, it.type.name) }, + instructions = intrinsic.instructions.emptyToNull() + ?.map { Instruction(it.mnemonic, it.xed, it.form) }, + performance = intrinsic.instructions.firstOrNull()?.let { + (Performances innerJoin Platforms).selectAll().where { + Performances.instruction eq it.id + }.map { + PlatformPerformance( + platform = it[Platforms.name], + latency = it[Performances.latency], + throughput = it[Performances.throughput] + ) + } + } + ) + } + } + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/server/HTTP.kt b/api/src/main/kotlin/server/HTTP.kt new file mode 100644 index 0000000..7cd77d1 --- /dev/null +++ b/api/src/main/kotlin/server/HTTP.kt @@ -0,0 +1,10 @@ +package com.jaytux.simd.server + +import io.ktor.server.application.* +import io.ktor.server.routing.* + +fun Application.configureHTTP() { + routing { + // + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/server/Pagination.kt b/api/src/main/kotlin/server/Pagination.kt new file mode 100644 index 0000000..25cb858 --- /dev/null +++ b/api/src/main/kotlin/server/Pagination.kt @@ -0,0 +1,18 @@ +package com.jaytux.simd.server + +import kotlinx.serialization.Serializable +import org.jetbrains.exposed.sql.Expression +import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.SortOrder + +@Serializable data class Paginated(val page: Long, val totalPages: Long, val items: List) + +inline fun SizedIterable.paginated(page: Long, perPage: Int, crossinline mapper: (T) -> R): Paginated { + val total = this.count() + val subset = this.offset(page * perPage).limit(perPage).map { item -> mapper(item) } + return Paginated(page, total / perPage + if (total % perPage > 0) 1 else 0, subset) +} + +fun SizedIterable.orderAsc(expr: Expression<*>) = this.orderBy(expr to SortOrder.ASC) + +fun SizedIterable.emptyToNull() = if(empty()) null else this \ No newline at end of file diff --git a/api/src/main/kotlin/server/Routing.kt b/api/src/main/kotlin/server/Routing.kt new file mode 100644 index 0000000..b6af330 --- /dev/null +++ b/api/src/main/kotlin/server/Routing.kt @@ -0,0 +1,66 @@ +package com.jaytux.simd.server + +import com.jaytux.simd.data.* +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.plugins.autohead.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import kotlinx.serialization.Serializable +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.* + +@Serializable data class ErrorResponse(val error: String) +class HttpError(msg: String, val status: HttpStatusCode = HttpStatusCode.BadRequest) : Exception(msg) + +inline suspend fun RoutingContext.runCatching(crossinline block: suspend RoutingContext.() -> R) { + try { + call.respond(block()) + } + catch(err: HttpError) { + call.respond(err.status, ErrorResponse(err.message ?: "")) + } +} + +inline fun Route.getPagedUrl( + path: String, + crossinline perPage: RoutingContext.() -> Int, + crossinline mapper: (T) -> R, + crossinline getSet: RoutingContext.() -> SizedIterable, +) { + get(path) { + runCatching { + transaction { getSet().paginated(0, perPage(), mapper) } + } + } + get("$path/{page}") { + runCatching { + val page = call.parameters["page"]?.toLongOrNull() ?: 0 + transaction { getSet().paginated(page, perPage(), mapper) } + } + } +} + +inline fun Route.getPagedRequest( + path: String, + crossinline perPage: RoutingContext.() -> Int, + crossinline mapper: (T) -> R, + crossinline getSet: RoutingContext.() -> SizedIterable, +) { + get(path) { + runCatching { + val page = call.request.queryParameters["page"]?.toLongOrNull() ?: 0 + transaction { getSet().paginated(page, perPage(), mapper) } + } + } +} + +fun Application.configureRouting() { + install(AutoHeadResponse) + routing { + installGetAll() + installSearch() + installDetails() + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/server/Serialization.kt b/api/src/main/kotlin/server/Serialization.kt new file mode 100644 index 0000000..56e7987 --- /dev/null +++ b/api/src/main/kotlin/server/Serialization.kt @@ -0,0 +1,28 @@ +package com.jaytux.simd.server + +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.plugins.contentnegotiation.* +import io.ktor.server.routing.* +import io.ktor.util.* +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.Decoder +import java.util.* + +fun Application.configureSerialization() { + install(ContentNegotiation) { + json() + } +} + +object UUIDSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString()) + + override fun serialize(encoder: Encoder, value: UUID) = encoder.encodeString(value.toString()) +} \ No newline at end of file diff --git a/api/src/main/resources/application.yaml b/api/src/main/resources/application.yaml new file mode 100644 index 0000000..7304966 --- /dev/null +++ b/api/src/main/resources/application.yaml @@ -0,0 +1,5 @@ +ktor: + application: + modules: [ com.jaytux.simd.MainKt.module ] + deployment: + port: 42024 \ No newline at end of file diff --git a/api/src/main/resources/logback.xml b/api/src/main/resources/logback.xml new file mode 100644 index 0000000..1591528 --- /dev/null +++ b/api/src/main/resources/logback.xml @@ -0,0 +1,10 @@ + + + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + \ No newline at end of file