Initial API
This commit is contained in:
commit
5f91256c31
|
@ -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
|
|
@ -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": <page number>,
|
||||
"totalPages": <total number of pages>,
|
||||
"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": "<UUID string>",
|
||||
"name": "<function name of the intrinsic>"
|
||||
}
|
||||
```
|
||||
|
||||
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": <page number>,
|
||||
"totalPages": <total number of pages>,
|
||||
"items": [
|
||||
{
|
||||
"id": "<UUID string>",
|
||||
"name": "<function name of the intrinsic>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /details/{id}`
|
||||
Gets the details for a single, specific intrinsic. The following data is returned:
|
||||
```json
|
||||
{
|
||||
"id": "<UUID string>",
|
||||
"name": "<function name of the intrinsic>",
|
||||
"returnType": "<return type of the intrinsic>",
|
||||
"returnVar": "<variable name for the return value, as used in the description; can be null>",
|
||||
"description": "<description of the intrinsic>",
|
||||
"operations": "<operation of the intrinsic in pseudocode>",
|
||||
"category": "<category>",
|
||||
"cpuid": "<CPUID>",
|
||||
"tech": "<technology>",
|
||||
"params": [
|
||||
{
|
||||
"name": "<parameter name>",
|
||||
"type": "<parameter type>"
|
||||
}
|
||||
],
|
||||
"instructions": [
|
||||
{
|
||||
"mnemonic": "<instruction mnemonic>",
|
||||
"xed": "<Intel XED code>",
|
||||
"form": "<instruction argument form>"
|
||||
}
|
||||
],
|
||||
"performance": [
|
||||
{
|
||||
"platform": "<platform name>",
|
||||
"latency": <latency in cycles; can be null>,
|
||||
"throughput": <throughput in CPI; can be null>
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
kotlin.code.style=official
|
|
@ -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" }
|
Binary file not shown.
|
@ -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
|
|
@ -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" "$@"
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
|
||||
}
|
||||
rootProject.name = "api"
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String>) {
|
||||
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 <xmlFile> <jsonFile> (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/<id> (details of a SIMD intrinsic)
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<Tech>(Techs)
|
||||
|
||||
var name by Techs.name
|
||||
}
|
||||
|
||||
class CppType(id: EntityID<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<CppType>(CppTypes)
|
||||
|
||||
var name by CppTypes.name
|
||||
}
|
||||
|
||||
class Category(id: EntityID<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<Category>(Categories)
|
||||
|
||||
var name by Categories.name
|
||||
}
|
||||
|
||||
class CPUID(id: EntityID<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<CPUID>(CPUIDs)
|
||||
|
||||
var name by CPUIDs.name
|
||||
}
|
||||
|
||||
class Intrinsic(id: EntityID<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<Intrinsic>(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<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<IntrinsicArgument>(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<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<IntrinsicInstruction>(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<UUID>) : UUIDEntity(id) {
|
||||
companion object : UUIDEntityClass<Platform>(Platforms)
|
||||
|
||||
var name by Platforms.name
|
||||
}
|
||||
|
||||
class Performance(id: EntityID<CompositeID>) : CompositeEntity(id) {
|
||||
companion object : CompositeEntityClass<Performance>(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
|
||||
}
|
|
@ -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<Pair<String, String>>, val desc: String, val op: String?, val insn: List<Triple<String, String, String?>>, val cpuid: String?, val category: String)
|
||||
|
||||
data class XmlData(
|
||||
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>>)
|
||||
|
||||
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>()
|
||||
|
||||
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(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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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<Param>,
|
||||
val instructions: List<Instruction>?,
|
||||
val performance: List<PlatformPerformance>?
|
||||
)
|
||||
|
||||
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]
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.jaytux.simd.server
|
||||
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun Application.configureHTTP() {
|
||||
routing {
|
||||
//
|
||||
}
|
||||
}
|
|
@ -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<T>(val page: Long, val totalPages: Long, val items: List<T>)
|
||||
|
||||
inline fun <reified T, reified R> SizedIterable<T>.paginated(page: Long, perPage: Int, crossinline mapper: (T) -> R): Paginated<R> {
|
||||
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 <T> SizedIterable<T>.orderAsc(expr: Expression<*>) = this.orderBy(expr to SortOrder.ASC)
|
||||
|
||||
fun <T> SizedIterable<T>.emptyToNull() = if(empty()) null else this
|
|
@ -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 <reified R: Any> RoutingContext.runCatching(crossinline block: suspend RoutingContext.() -> R) {
|
||||
try {
|
||||
call.respond(block())
|
||||
}
|
||||
catch(err: HttpError) {
|
||||
call.respond(err.status, ErrorResponse(err.message ?: "<no error message given>"))
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T, reified R> Route.getPagedUrl(
|
||||
path: String,
|
||||
crossinline perPage: RoutingContext.() -> Int,
|
||||
crossinline mapper: (T) -> R,
|
||||
crossinline getSet: RoutingContext.() -> SizedIterable<T>,
|
||||
) {
|
||||
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 <reified T, reified R> Route.getPagedRequest(
|
||||
path: String,
|
||||
crossinline perPage: RoutingContext.() -> Int,
|
||||
crossinline mapper: (T) -> R,
|
||||
crossinline getSet: RoutingContext.() -> SizedIterable<T>,
|
||||
) {
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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<UUID> {
|
||||
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())
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
ktor:
|
||||
application:
|
||||
modules: [ com.jaytux.simd.MainKt.module ]
|
||||
deployment:
|
||||
port: 42024
|
|
@ -0,0 +1,10 @@
|
|||
<configuration>
|
||||
<appender name="APPENDER" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="trace">
|
||||
<appender-ref ref="APPENDER"/>
|
||||
</root>
|
||||
</configuration>
|
Loading…
Reference in New Issue