diff --git a/applications/Makefile b/applications/Makefile index a461aa2..8cf3187 100644 --- a/applications/Makefile +++ b/applications/Makefile @@ -88,3 +88,9 @@ stack_overflow.install: ../runtime/bin/stack_overflow.wasm.so .PHONY: html.install html.install: ../runtime/bin/html.wasm.so + +.PHONY: scratch_storage_get.install +scratch_storage_get.install: ../runtime/bin/scratch_storage_get.wasm.so + +.PHONY: scratch_storage_set.install +scratch_storage_set.install: ../runtime/bin/scratch_storage_set.wasm.so diff --git a/applications/wasm_apps b/applications/wasm_apps index 456edf5..54de4d2 160000 --- a/applications/wasm_apps +++ b/applications/wasm_apps @@ -1 +1 @@ -Subproject commit 456edf5cecd7edc36314ba87e2d76ebf8cf1b5db +Subproject commit 54de4d27f9a6e1f20b65b7110612a658479e6133 diff --git a/libsledge/Makefile b/libsledge/Makefile index f0dd19c..37679f1 100644 --- a/libsledge/Makefile +++ b/libsledge/Makefile @@ -26,7 +26,7 @@ dist: dist/%.o: src/%.c dist clang ${CFLAGS} -c ${INCLUDES} -o $@ $< -dist/libsledge.a: dist/control_instructions.o dist/memory_instructions.o dist/numeric_instructions.o dist/table_instructions.o dist/variable_instructions.o dist/instantiation.o dist/wasi_snapshot_preview1.o +dist/libsledge.a: dist/control_instructions.o dist/memory_instructions.o dist/numeric_instructions.o dist/table_instructions.o dist/variable_instructions.o dist/instantiation.o dist/wasi_snapshot_preview1.o dist/sledge_extensions.o ar rcs dist/libsledge.a $^ clean: diff --git a/libsledge/include/sledge_abi.h b/libsledge/include/sledge_abi.h index a26e959..2b47f86 100644 --- a/libsledge/include/sledge_abi.h +++ b/libsledge/include/sledge_abi.h @@ -249,3 +249,10 @@ uint32_t sledge_abi__wasi_snapshot_preview1_sock_send(__wasi_fd_t fd, __wasi_siz __wasi_size_t nbytes_retoffset); uint32_t sledge_abi__wasi_snapshot_preview1_sock_shutdown(__wasi_fd_t fd, uint32_t how); + +uint32_t sledge_abi__scratch_storage_get_size(uint32_t key_offset, uint32_t key_len); +int sledge_abi__scratch_storage_get(uint32_t key_offset, uint32_t key_size, uint32_t buf_offset, uint32_t buf_len); +int sledge_abi__scratch_storage_set(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len); +int sledge_abi__scratch_storage_delete(uint32_t key_offset, uint32_t key_len); +void +sledge_abi__scratch_storage_upsert(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len); diff --git a/libsledge/src/sledge_extensions.c b/libsledge/src/sledge_extensions.c new file mode 100644 index 0000000..3687a4f --- /dev/null +++ b/libsledge/src/sledge_extensions.c @@ -0,0 +1,65 @@ +#include +#include "sledge_abi.h" + +#define INLINE __attribute__((always_inline)) + +/** + * @param key + * @param key_len + * @returns value_size at key or 0 if key not present + */ +INLINE uint32_t +scratch_storage_get_size(uint32_t key_offset, uint32_t key_len) +{ + return sledge_abi__scratch_storage_get_size(key_offset, key_len); +} + +/** + * @param key_offset + * @param key_len + * @param buf_offset linear memory offset to buffer where value should be copied. + * @param buf_len Size of buffer. Assumed to be size returned by sledge_kv_get_value_size. + * @returns 0 on success, 1 if key missing, 2 if buffer too small + */ +INLINE int +scratch_storage_get(uint32_t key_offset, uint32_t key_len, uint32_t buf_offset, uint32_t buf_len) +{ + return sledge_abi__scratch_storage_get(key_offset, key_len, buf_offset, buf_len); +}; + +/** + * @param key_offset + * @param key_len + * @param value_offset + * @param value_len + * @returns 0 on success, 1 if already present, + */ +INLINE int +scratch_storage_set(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len) +{ + return sledge_abi__scratch_storage_set(key_offset, key_len, value_offset, value_len); +} + +/** + * @param key_offset + * @param key_len + * @returns 0 on success, 1 if not present + */ +INLINE int +scratch_storage_delete(uint32_t key_offset, uint32_t key_len) +{ + return sledge_abi__scratch_storage_delete(key_offset, key_len); +} + +/** + * @param key_offset + * @param key_len + * @param value_offset + * @param value_len + * @returns 0 on success, 1 if already present, + */ +INLINE void +scratch_storage_upsert(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len) +{ + sledge_abi__scratch_storage_upsert(key_offset, key_len, value_offset, value_len); +} diff --git a/runtime/include/lock.h b/runtime/include/lock.h index 5f15d1b..81a7681 100644 --- a/runtime/include/lock.h +++ b/runtime/include/lock.h @@ -5,6 +5,7 @@ #include "arch/getcycles.h" #include "runtime.h" +#include "generic_thread.h" typedef ck_spinlock_mcs_t lock_t; diff --git a/runtime/include/map.h b/runtime/include/map.h new file mode 100644 index 0000000..1a64ddd --- /dev/null +++ b/runtime/include/map.h @@ -0,0 +1,183 @@ +#pragma once + +#include +#include +#include +#include + +#include "lock.h" + +/* Simple K-V store based on The Practice of Programming by Kernighan and Pike */ + +/* Bucket count is sized to be a prime that is approximately 20% larger than the desired capacity (6k keys) */ +#define MAP_BUCKET_COUNT 7907 +#define MAP_LOCK_STRIPES 17 + +#define MAP_HASH jenkins_hash + +struct map_node { + uint32_t hash; + uint8_t *key; + uint32_t key_len; + uint8_t *value; + uint32_t value_len; + struct map_node *next; +}; + +struct map { + struct map_node *buckets[MAP_BUCKET_COUNT]; + lock_t locks[MAP_LOCK_STRIPES]; +}; + +static inline void +map_init(struct map *restrict map) +{ + for (int i = 0; i < MAP_BUCKET_COUNT; i++) { map->buckets[i] = NULL; } + for (int i = 0; i < MAP_LOCK_STRIPES; i++) { LOCK_INIT(&map->locks[i]); } +}; + +/* See https://en.wikipedia.org/wiki/Jenkins_hash_function */ +static inline uint32_t +jenkins_hash(uint8_t *key, uint32_t key_len) +{ + uint32_t i = 0; + uint32_t hash = 0; + while (i != key_len) { + hash += key[i++]; + hash += hash << 10; + hash ^= hash >> 6; + } + hash += hash << 3; + hash ^= hash >> 11; + hash += hash << 15; + return hash; +} + +static inline uint8_t * +map_get(struct map *map, uint8_t *key, uint32_t key_len, uint32_t *ret_value_len) +{ + uint8_t *value = NULL; + + uint32_t hash = MAP_HASH(key, key_len); + LOCK_LOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + for (struct map_node *node = map->buckets[hash % MAP_BUCKET_COUNT]; node != NULL; node = node->next) { + if (node->hash == hash) { + value = node->value; + *ret_value_len = node->value_len; + goto DONE; + } + } + + if (value == NULL) *ret_value_len = 0; + +DONE: + LOCK_UNLOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + return value; +} + +static inline bool +map_set(struct map *map, uint8_t *key, uint32_t key_len, uint8_t *value, uint32_t value_len) +{ + bool did_set = false; + + uint32_t hash = MAP_HASH(key, key_len); + LOCK_LOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + for (struct map_node *node = map->buckets[hash % MAP_BUCKET_COUNT]; node != NULL; node = node->next) { + if (node->hash == hash) goto DONE; + } + + struct map_node *new_node = (struct map_node *)malloc(sizeof(struct map_node)); + + *(new_node) = (struct map_node){ .hash = hash, + .key = malloc(key_len), + .key_len = key_len, + .value = malloc(value_len), + .value_len = value_len, + .next = map->buckets[hash % MAP_BUCKET_COUNT] }; + + assert(new_node->key); + assert(new_node->value); + + // Copy Key and Value + memcpy(new_node->key, key, key_len); + memcpy(new_node->value, value, value_len); + + map->buckets[hash % MAP_BUCKET_COUNT] = new_node; + did_set = true; + +DONE: + LOCK_UNLOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + return did_set; +} + +/** + * @returns boolean if node was deleted or not + */ +static inline bool +map_delete(struct map *map, uint8_t *key, uint32_t key_len) +{ + bool did_delete = false; + + uint32_t hash = MAP_HASH(key, key_len); + LOCK_LOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + + struct map_node *prev = map->buckets[hash % MAP_BUCKET_COUNT]; + if (prev->hash == hash) { + map->buckets[hash % MAP_BUCKET_COUNT] = prev->next; + free(prev->key); + free(prev->value); + free(prev); + did_delete = true; + goto DONE; + } + + for (struct map_node *node = prev->next; node != NULL; prev = node, node = node->next) { + prev->next = node->next; + free(node->key); + free(node->value); + free(node); + did_delete = true; + goto DONE; + } + +DONE: + LOCK_UNLOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + return did_delete; +} + +static inline void +map_upsert(struct map *map, uint8_t *key, uint32_t key_len, uint8_t *value, uint32_t value_len) +{ + uint32_t hash = MAP_HASH(key, key_len); + LOCK_LOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + for (struct map_node *node = map->buckets[hash % MAP_BUCKET_COUNT]; node != NULL; node = node->next) { + if (node->hash == hash) { + node->value_len = value_len; + node->value = realloc(&node->value, value_len); + assert(node->value); + memcpy(&node->value, value, value_len); + } + goto DONE; + } + + struct map_node *new_node = (struct map_node *)malloc(sizeof(struct map_node)); + + *(new_node) = (struct map_node){ .hash = hash, + .key = malloc(key_len), + .key_len = key_len, + .value = malloc(value_len), + .value_len = value_len, + .next = map->buckets[hash % MAP_BUCKET_COUNT] }; + + assert(new_node->key); + assert(new_node->value); + + // Copy Key and Value + memcpy(&new_node->key, key, key_len); + memcpy(&new_node->value, value, value_len); + + map->buckets[hash % MAP_BUCKET_COUNT] = new_node; + +DONE: + LOCK_UNLOCK(&map->locks[hash % MAP_LOCK_STRIPES]); +} diff --git a/runtime/include/route_config.h b/runtime/include/route_config.h index 3a2dc6b..aeb214e 100644 --- a/runtime/include/route_config.h +++ b/runtime/include/route_config.h @@ -73,7 +73,6 @@ route_config_validate(struct route_config *config, bool *did_set) if (did_set[route_config_member_http_resp_content_type] == false) { debuglog("http_resp_content_type not set, defaulting to text/plain\n"); config->http_resp_content_type = "text/plain"; - return -1; } if (scheduler == SCHEDULER_EDF) { diff --git a/runtime/include/tenant.h b/runtime/include/tenant.h index 01977fc..06fa730 100644 --- a/runtime/include/tenant.h +++ b/runtime/include/tenant.h @@ -1,6 +1,7 @@ #pragma once #include "http_router.h" +#include "map.h" #include "module_database.h" #include "tcp_server.h" @@ -9,4 +10,5 @@ struct tenant { struct tcp_server tcp_server; http_router_t router; struct module_database module_db; + struct map scratch_storage; }; diff --git a/runtime/include/tenant_functions.h b/runtime/include/tenant_functions.h index cdf446c..89bb6f8 100644 --- a/runtime/include/tenant_functions.h +++ b/runtime/include/tenant_functions.h @@ -37,6 +37,7 @@ tenant_alloc(struct tenant_config *config) tcp_server_init(&tenant->tcp_server, config->port); http_router_init(&tenant->router, config->routes_len); module_database_init(&tenant->module_db); + map_init(&tenant->scratch_storage); for (int i = 0; i < config->routes_len; i++) { struct module *module = module_database_find_by_path(&tenant->module_db, config->routes[i].path); diff --git a/runtime/src/sledge_abi.c b/runtime/src/sledge_abi.c index 5cdf918..b1c2146 100644 --- a/runtime/src/sledge_abi.c +++ b/runtime/src/sledge_abi.c @@ -1,4 +1,5 @@ #include "current_sandbox.h" +#include "map.h" #include "sandbox_set_as_running_sys.h" #include "sandbox_set_as_running_user.h" #include "sledge_abi.h" @@ -1039,3 +1040,127 @@ sledge_abi__wasi_snapshot_preview1_sock_shutdown(__wasi_fd_t fd, uint32_t how) { return wasi_unsupported_syscall(__func__); } + +/** + * @param key + * @param key_len + * @returns value_len at key or 0 if key not present + */ +EXPORT uint32_t +sledge_abi__scratch_storage_get_size(uint32_t key_offset, uint32_t key_len) +{ + struct sandbox *sandbox = current_sandbox_get(); + + sandbox_syscall(sandbox); + + uint8_t *key = (uint8_t *)get_memory_ptr_for_runtime(key_offset, key_len); + + uint32_t value_len; + map_get(&sandbox->tenant->scratch_storage, key, key_len, &value_len); + + sandbox_return(sandbox); + + return value_len; +} + +EXPORT int +sledge_abi__scratch_storage_get(uint32_t key_offset, uint32_t key_len, uint32_t buf_offset, uint32_t buf_len) +{ + int rc = 0; + + struct sandbox *sandbox = current_sandbox_get(); + + sandbox_syscall(sandbox); + + uint8_t *key = (uint8_t *)get_memory_ptr_for_runtime(key_offset, key_len); + uint8_t *buf = (uint8_t *)get_memory_ptr_for_runtime(buf_offset, buf_len); + + uint32_t value_len; + uint8_t *value = map_get(&sandbox->tenant->scratch_storage, key, key_len, &value_len); + + if (value == NULL) { + rc = 1; + goto DONE; + } else if (value_len > buf_len) { + rc = 2; + goto DONE; + } else { + memcpy(buf, value, value_len); + rc = 0; + } + +DONE: + sandbox_return(sandbox); + return rc; +} + +/** + * @param key_offset + * @param key_len + * @param value_offset + * @param value_len + * @returns 0 on success, 1 if already present, + */ +EXPORT int +sledge_abi__scratch_storage_set(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len) +{ + int rc = 0; + + struct sandbox *sandbox = current_sandbox_get(); + + sandbox_syscall(sandbox); + + uint8_t *key = (uint8_t *)get_memory_ptr_for_runtime(key_offset, key_len); + uint8_t *value = (uint8_t *)get_memory_ptr_for_runtime(value_offset, value_len); + + bool did_set = map_set(&sandbox->tenant->scratch_storage, key, key_len, value, value_len); + +DONE: + sandbox_return(sandbox); + return did_set ? 0 : 1; +} + +/** + * @param key_offset + * @param key_len + * @returns 0 on success, 1 if not present + */ +EXPORT int +sledge_abi__scratch_storage_delete(uint32_t key_offset, uint32_t key_len) +{ + int rc = 0; + + struct sandbox *sandbox = current_sandbox_get(); + + sandbox_syscall(sandbox); + + uint8_t *key = (uint8_t *)get_memory_ptr_for_runtime(key_offset, key_len); + + bool did_delete = map_delete(&sandbox->tenant->scratch_storage, key, key_len); + +DONE: + sandbox_return(sandbox); + return did_delete ? 0 : 1; +} + +/** + * @param key_offset + * @param key_len + * @param value_offset + * @param value_len + * @returns 0 on success, 1 if already present, + */ +EXPORT void +sledge_abi__scratch_storage_upsert(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len) +{ + struct sandbox *sandbox = current_sandbox_get(); + + sandbox_syscall(sandbox); + + uint8_t *key = (uint8_t *)get_memory_ptr_for_runtime(key_offset, key_len); + uint8_t *value = (uint8_t *)get_memory_ptr_for_runtime(value_offset, value_len); + + map_upsert(&sandbox->tenant->scratch_storage, key, key_len, value, value_len); + + sandbox_return(sandbox); +} diff --git a/tests/scratch_storage/.gitignore b/tests/scratch_storage/.gitignore new file mode 100644 index 0000000..26cdcd9 --- /dev/null +++ b/tests/scratch_storage/.gitignore @@ -0,0 +1,3 @@ +res +perf.data +perf.data.old diff --git a/tests/scratch_storage/Makefile b/tests/scratch_storage/Makefile new file mode 100644 index 0000000..7239f8a --- /dev/null +++ b/tests/scratch_storage/Makefile @@ -0,0 +1,40 @@ +RUNTIME_DIR=../../runtime/ +SLEDGE_BINARY_DIR=${RUNTIME_DIR}/bin +SLEDGE_TESTS_DIR=${RUNTIME_DIR}/tests +HOSTNAME=localhost + +all: run + +clean: + make -C ${RUNTIME_DIR} clean + make -C ${SLEDGE_TESTS_DIR} clean + rm -f ${SLEDGE_BINARY_DIR}/html.wasm.so + +${SLEDGE_BINARY_DIR}/sledgert: + make -C ${RUNTIME_DIR} runtime + +.PHONY: sledgert +sledgert: ${SLEDGE_BINARY_DIR}/sledgert + +${SLEDGE_BINARY_DIR}/scratch_storage.wasm.so: + make -C ../../applications scratch_storage.install + +.PHONY: scratch_storage_get +scratch_storage_get: ${SLEDGE_BINARY_DIR}/scratch_storage_get.wasm.so + +.PHONY: scratch_storage_set +scratch_storage_set: ${SLEDGE_BINARY_DIR}/scratch_storage_set.wasm.so + +run: sledgert scratch_storage_get scratch_storage_set + LD_LIBRARY_PATH=${SLEDGE_BINARY_DIR} ${SLEDGE_BINARY_DIR}/sledgert spec.json + +debug: sledgert scratch_storage_get scratch_storage_set + SLEDGE_DISABLE_PREEMPTION=true SLEDGE_NWORKERS=1 \ + LD_LIBRARY_PATH=${SLEDGE_BINARY_DIR} gdb ${SLEDGE_BINARY_DIR}/sledgert \ + --eval-command="handle SIGUSR1 noprint nostop" \ + --eval-command="handle SIGPIPE noprint nostop" \ + --eval-command="set pagination off" \ + --eval-command="run spec.json" + +client: + http :1337/scratch_storage diff --git a/tests/scratch_storage/edf_nopreemption.env b/tests/scratch_storage/edf_nopreemption.env new file mode 100644 index 0000000..eeba531 --- /dev/null +++ b/tests/scratch_storage/edf_nopreemption.env @@ -0,0 +1,2 @@ +SLEDGE_SCHEDULER=EDF +SLEDGE_DISABLE_PREEMPTION=true diff --git a/tests/scratch_storage/edf_preemption.env b/tests/scratch_storage/edf_preemption.env new file mode 100644 index 0000000..302a324 --- /dev/null +++ b/tests/scratch_storage/edf_preemption.env @@ -0,0 +1,3 @@ +SLEDGE_SCHEDULER=EDF +SLEDGE_DISABLE_PREEMPTION=false +SLEDGE_SIGALRM_HANDLER=TRIAGED diff --git a/tests/scratch_storage/fifo_nopreemption.env b/tests/scratch_storage/fifo_nopreemption.env new file mode 100644 index 0000000..a572a70 --- /dev/null +++ b/tests/scratch_storage/fifo_nopreemption.env @@ -0,0 +1,2 @@ +SLEDGE_SCHEDULER=FIFO +SLEDGE_DISABLE_PREEMPTION=true diff --git a/tests/scratch_storage/fifo_preemption.env b/tests/scratch_storage/fifo_preemption.env new file mode 100644 index 0000000..eb1298f --- /dev/null +++ b/tests/scratch_storage/fifo_preemption.env @@ -0,0 +1,2 @@ +SLEDGE_SCHEDULER=FIFO +SLEDGE_DISABLE_PREEMPTION=false diff --git a/tests/scratch_storage/install.sh b/tests/scratch_storage/install.sh new file mode 100755 index 0000000..0cbcfe8 --- /dev/null +++ b/tests/scratch_storage/install.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +if ! command -v hey > /dev/null; then + HEY_URL=https://hey-release.s3.us-east-2.amazonaws.com/hey_linux_amd64 + wget $HEY_URL -O hey + chmod +x hey + + if [[ $(whoami) == "root" ]]; then + mv hey /usr/bin/hey + else + sudo mv hey /usr/bin/hey + fi +fi diff --git a/tests/scratch_storage/spec.json b/tests/scratch_storage/spec.json new file mode 100644 index 0000000..0047ea7 --- /dev/null +++ b/tests/scratch_storage/spec.json @@ -0,0 +1,26 @@ +[ + { + "name": "gwu", + "port": 1337, + "routes": [ + { + "route": "/set", + "path": "scratch_storage_set.wasm.so", + "expected-execution-us": 10000000, + "admissions-percentile": 70, + "relative-deadline-us": 20000000, + "http-resp-content-type": "text/plain", + "http-resp-size": 1024 + }, + { + "route": "/get", + "path": "scratch_storage_get.wasm.so", + "expected-execution-us": 10000000, + "admissions-percentile": 70, + "relative-deadline-us": 20000000, + "http-resp-content-type": "text/plain", + "http-resp-size": 1024 + } + ] + } +]