init WRT
This commit is contained in:
commit
a6578ea92c
17
.clang-format
Normal file
17
.clang-format
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
AlignConsecutiveMacros: AcrossEmptyLinesAndComments
|
||||||
|
AlignConsecutiveAssignments: Consecutive
|
||||||
|
AlignConsecutiveBitFields: AcrossEmptyLinesAndComments
|
||||||
|
AlignConsecutiveDeclarations: Consecutive
|
||||||
|
AlignEscapedNewlines: Left
|
||||||
|
AllowShortCaseLabelsOnASingleLine: true
|
||||||
|
AllowShortIfStatementsOnASingleLine: true
|
||||||
|
AllowShortLoopsOnASingleLine: true
|
||||||
|
ColumnLimit: 100
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentPPDirectives: BeforeHash
|
||||||
|
IndentWidth: 4
|
||||||
|
UseTab: Never
|
||||||
|
SeparateDefinitionBlocks: Leave
|
||||||
|
SortIncludes: Never
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.so
|
||||||
|
cmake-build-*
|
46
CMakeLists.txt
Normal file
46
CMakeLists.txt
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.24)
|
||||||
|
project(wrt)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fmacro-prefix-map=${CMAKE_SOURCE_DIR}=.")
|
||||||
|
add_definitions(-DWRT_DEBUG=4)
|
||||||
|
|
||||||
|
# math
|
||||||
|
find_library(M_LIBRARY m)
|
||||||
|
|
||||||
|
# libffi
|
||||||
|
include(FindPkgConfig)
|
||||||
|
pkg_check_modules(FFI REQUIRED IMPORTED_TARGET libffi)
|
||||||
|
|
||||||
|
# google test
|
||||||
|
# tests
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
googletest
|
||||||
|
URL https://github.com/google/googletest/archive/refs/tags/v1.13.0.zip
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(googletest)
|
||||||
|
|
||||||
|
# wamr
|
||||||
|
include(wasm.conf)
|
||||||
|
include(${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake)
|
||||||
|
add_library(vmlib ${WAMR_RUNTIME_LIB_SOURCE})
|
||||||
|
|
||||||
|
# wrt
|
||||||
|
add_library(wrt wrt/wrt.c wrt/dl.c)
|
||||||
|
target_include_directories(wrt PRIVATE .)
|
||||||
|
target_link_libraries(wrt vmlib PkgConfig::FFI ${M_LIBRARY})
|
||||||
|
|
||||||
|
# main
|
||||||
|
add_executable(main main.c)
|
||||||
|
target_include_directories(main PRIVATE .)
|
||||||
|
target_link_libraries(main wrt)
|
||||||
|
|
||||||
|
# test
|
||||||
|
enable_testing()
|
||||||
|
add_executable(test_all tests/dl.cpp)
|
||||||
|
target_include_directories(test_all PRIVATE .)
|
||||||
|
target_link_libraries(test_all GTest::gtest_main wrt)
|
||||||
|
include(GoogleTest)
|
||||||
|
gtest_discover_tests(test_all)
|
22
main.c
Normal file
22
main.c
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "utils/log.h"
|
||||||
|
#include "wrt/dl.h"
|
||||||
|
#include "wrt/wrt.h"
|
||||||
|
|
||||||
|
void test_wrt() {
|
||||||
|
WRTContext context;
|
||||||
|
wrt_platform_init(&context, malloc, free);
|
||||||
|
wrt_mem_init(&context, 512 * 1024, 128);
|
||||||
|
wrt_program_init(&context, NULL, 0, NULL, 0);
|
||||||
|
wrt_wamr_init(&context, "entry", 8092, 8092);
|
||||||
|
wrt_run(&context);
|
||||||
|
wrt_free(&context);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
printf("Hello, World!\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
128
tests/dl.cpp
Normal file
128
tests/dl.cpp
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "utils/defs.h"
|
||||||
|
#include "wrt/dl.h"
|
||||||
|
|
||||||
|
extern DLContext dl_context;
|
||||||
|
extern bool signature_check(const char *signature, int *arg_count, bool *has_ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define check_symbol_consistency(sym, postfix) \
|
||||||
|
do { \
|
||||||
|
ASSERT_GT((sym), 0); \
|
||||||
|
ASSERT_LT((sym), DL_MAX_SYMBOLS); \
|
||||||
|
ASSERT_NE(dl_context.sym[(sym)-1].symbol, nullptr); \
|
||||||
|
ASSERT_NE(dl_context.sym[(sym)-1].cif, nullptr); \
|
||||||
|
ASSERT_GT(dl_context.sym[(sym)-1].backref, 0); \
|
||||||
|
unsigned ref##postfix = dl_context.sym[(sym)-1].backref; \
|
||||||
|
ASSERT_GT(ref##postfix, 0); \
|
||||||
|
ASSERT_LE(ref##postfix, DL_MAX_HANDLES); \
|
||||||
|
ASSERT_NE(dl_context.hnd[ref##postfix - 1].handle, nullptr); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define check_empty(postfix) \
|
||||||
|
do { \
|
||||||
|
for (auto &hnd##postfix : dl_context.hnd) { \
|
||||||
|
ASSERT_EQ(hnd##postfix.handle, nullptr); \
|
||||||
|
} \
|
||||||
|
for (auto &sym##postfix : dl_context.sym) { \
|
||||||
|
ASSERT_EQ(sym##postfix.symbol, nullptr); \
|
||||||
|
ASSERT_EQ(sym##postfix.cif, nullptr); \
|
||||||
|
ASSERT_EQ(sym##postfix.backref, 0); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
TEST(DLTest, OpenAndClose) {
|
||||||
|
dl_init();
|
||||||
|
|
||||||
|
int handle = dl_open(nullptr, "./SimpleLib.so", RTLD_LAZY);
|
||||||
|
ASSERT_GT(handle, 0);
|
||||||
|
int result = dl_close(nullptr, handle);
|
||||||
|
ASSERT_EQ(result, WRT_OK);
|
||||||
|
check_empty(0);
|
||||||
|
|
||||||
|
handle = dl_open(nullptr, "not_exists", RTLD_LAZY);
|
||||||
|
ASSERT_EQ(handle, WRT_ERROR);
|
||||||
|
check_empty(1);
|
||||||
|
|
||||||
|
handle = dl_open(nullptr, "", RTLD_LAZY);
|
||||||
|
ASSERT_EQ(handle, WRT_ERROR);
|
||||||
|
check_empty(2);
|
||||||
|
|
||||||
|
handle = dl_open(nullptr, nullptr, RTLD_LAZY);
|
||||||
|
ASSERT_EQ(handle, WRT_ERROR);
|
||||||
|
check_empty(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DLTest, ManyOpen) {
|
||||||
|
dl_init();
|
||||||
|
|
||||||
|
int handles[DL_MAX_HANDLES];
|
||||||
|
for (int &handle : handles) {
|
||||||
|
handle = dl_open(nullptr, "./SimpleLib.so", RTLD_LAZY);
|
||||||
|
ASSERT_GT(handle, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
int handle = dl_open(nullptr, "./SimpleLib.so", RTLD_LAZY);
|
||||||
|
ASSERT_EQ(handle, WRT_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int handle : handles) {
|
||||||
|
int result = dl_close(nullptr, handle);
|
||||||
|
ASSERT_EQ(result, WRT_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
check_empty(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DLTest, SignatureCheck) {
|
||||||
|
typedef struct {
|
||||||
|
const char *test;
|
||||||
|
bool result;
|
||||||
|
int arg_count;
|
||||||
|
bool has_ret;
|
||||||
|
} Case;
|
||||||
|
|
||||||
|
const static Case cases[] = {
|
||||||
|
{"", false, 0, false}, {"()", true, 0, false}, {"(i)", true, 1, false},
|
||||||
|
{"()i", true, 0, true}, {"(x)", false, 0, false}, {"()x", false, 0, false},
|
||||||
|
{"(i)f", true, 1, true}, {"(i)x", false, 0, false}, {"(x)f", false, 0, false},
|
||||||
|
{"i", false, 0, false}, {"i()", false, 0, false}, {"i(i)", false, 0, false},
|
||||||
|
{"(i)()", false, 0, false}, {"(ii", false, 0, false}, {"(iiffdd)l", true, 6, true},
|
||||||
|
{"(ffddii)ll", false, 0, false}, {"()ii", false, 0, false}, {"(i", false, 0, false},
|
||||||
|
{"(i(i))i", false, 0, false}, {"i)i", false, 0, false}, {"((i)i", false, 0, false},
|
||||||
|
{"(i))i", false, 0, false}, {"())", false, 0, false}};
|
||||||
|
|
||||||
|
for (const auto &cur_case : cases) {
|
||||||
|
int arg_count = 0;
|
||||||
|
bool has_ret = false;
|
||||||
|
bool ok = signature_check(cur_case.test, &arg_count, &has_ret);
|
||||||
|
|
||||||
|
EXPECT_EQ(ok, cur_case.result);
|
||||||
|
if (ok) {
|
||||||
|
EXPECT_EQ(arg_count, cur_case.arg_count);
|
||||||
|
EXPECT_EQ(has_ret, cur_case.has_ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DLTest, GetSym) {
|
||||||
|
dl_init();
|
||||||
|
|
||||||
|
int hnd = dl_open(nullptr, "./SimpleLib.so", RTLD_LAZY);
|
||||||
|
{
|
||||||
|
int sym = dl_sym(nullptr, hnd, "fib_fast", "(i)i");
|
||||||
|
check_symbol_consistency(sym, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
int sym = dl_sym(nullptr, hnd, "fib_fast", "(v)i");
|
||||||
|
ASSERT_EQ(sym, WRT_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
dl_close(nullptr, hnd);
|
||||||
|
check_empty(0);
|
||||||
|
}
|
49
tests/lib/SimpleLib.cpp
Normal file
49
tests/lib/SimpleLib.cpp
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#define HIDE __attribute__((visibility("hidden")))
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
int fib_recurse(int n);
|
||||||
|
int fib_iterate(int n);
|
||||||
|
int fib_tail_call(int n);
|
||||||
|
int fib_fast(int n);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fib_recurse(int n) {
|
||||||
|
if (n <= 1) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
return fib_recurse(n - 1) + fib_recurse(n - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fib_iterate(int n) {
|
||||||
|
int a = 0, b = 1, c;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
c = a + b;
|
||||||
|
a = b;
|
||||||
|
b = c;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
HIDE inline int fib_tail(int n, int a, int b) {
|
||||||
|
if (n == 0) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
return fib_tail(n - 1, b, a + b);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fib_tail_call(int n) { return fib_tail(n, 0, 1); }
|
||||||
|
|
||||||
|
HIDE inline std::pair<int, int> fib_fast_internal(int n) {
|
||||||
|
if (n == 0) return {0, 1};
|
||||||
|
auto p = fib_fast_internal(n >> 1);
|
||||||
|
int c = p.first * (2 * p.second - p.first);
|
||||||
|
int d = p.first * p.first + p.second * p.second;
|
||||||
|
if (n & 1)
|
||||||
|
return {d, c + d};
|
||||||
|
else
|
||||||
|
return {c, d};
|
||||||
|
}
|
||||||
|
|
||||||
|
int fib_fast(int n) { return fib_fast_internal(n).first; }
|
15
utils/defs.h
Normal file
15
utils/defs.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#ifndef WRT_DEFS_H
|
||||||
|
#define WRT_DEFS_H
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
WRT_OK = 0,
|
||||||
|
WRT_ERROR = -1,
|
||||||
|
} Status;
|
||||||
|
|
||||||
|
typedef void *(*Allocator)(unsigned long);
|
||||||
|
typedef void (*DeAllocator)(void *);
|
||||||
|
|
||||||
|
#define DL_MAX_HANDLES 8
|
||||||
|
#define DL_MAX_SYMBOLS 16
|
||||||
|
|
||||||
|
#endif // WRT_DEFS_H
|
32
utils/log.h
Normal file
32
utils/log.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef WRT_LOG_H
|
||||||
|
#define WRT_LOG_H
|
||||||
|
|
||||||
|
#if WRT_DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define __COLOR_RED "\x1B[1;31m"
|
||||||
|
#define __COLOR_YELLOW "\x1B[1;33m"
|
||||||
|
#define __COLOR_GREEN "\x1B[1;32m"
|
||||||
|
#define __COLOR_CYAN "\x1B[1;36m"
|
||||||
|
#define __COLOR_RESET "\x1B[0m"
|
||||||
|
|
||||||
|
#define _LOG(color, level, fmt, ...) \
|
||||||
|
do { \
|
||||||
|
fprintf(stdout, color "[" level "]\t(%s:%d):\t" fmt __COLOR_RESET "\n", __FILE__, \
|
||||||
|
__LINE__, ##__VA_ARGS__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define LOG_ERR(fmt, ...) _LOG(__COLOR_RED, "E", fmt, ##__VA_ARGS__)
|
||||||
|
#define LOG_WARN(fmt, ...) _LOG(__COLOR_YELLOW, "W", fmt, ##__VA_ARGS__)
|
||||||
|
#define LOG_INFO(fmt, ...) _LOG(__COLOR_GREEN, "I", fmt, ##__VA_ARGS__)
|
||||||
|
#define LOG_DBG(fmt, ...) _LOG(__COLOR_CYAN, "D", fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define LOG_ERR(fmt, ...)
|
||||||
|
#define LOG_WARN(fmt, ...)
|
||||||
|
#define LOG_INFO(fmt, ...)
|
||||||
|
#define LOG_DBG(fmt, ...)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // WRT_LOG_H
|
9
wasm.conf
Normal file
9
wasm.conf
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
set(WAMR_BUILD_PLATFORM "linux")
|
||||||
|
set(WAMR_BUILD_TARGET "X86_64")
|
||||||
|
set(WAMR_BUILD_INTERP 1)
|
||||||
|
set(WAMR_BUILD_FAST_INTERP 1)
|
||||||
|
set(WAMR_BUILD_AOT 1)
|
||||||
|
set(WAMR_BUILD_LIBC_BUILTIN 1)
|
||||||
|
set(WAMR_BUILD_LIBC_WASI 1)
|
||||||
|
set(WAMR_BUILD_SIMD 1)
|
||||||
|
set(WAMR_ROOT_DIR $ENV{WAMR_ROOT_DIR})
|
238
wrt/dl.c
Normal file
238
wrt/dl.c
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
#include "utils/log.h"
|
||||||
|
#include "wrt/dl.h"
|
||||||
|
|
||||||
|
DLContext dl_context = {0};
|
||||||
|
Allocator dl_malloc = malloc;
|
||||||
|
DeAllocator dl_free = free;
|
||||||
|
|
||||||
|
#warning "TODO: Permission Check"
|
||||||
|
|
||||||
|
Status dl_init() {
|
||||||
|
memset(&dl_context, 0, sizeof(DLContext));
|
||||||
|
|
||||||
|
return WRT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dl_open(wasm_exec_env_t exec_env, const char *filename, int flags) {
|
||||||
|
if (filename == NULL) return WRT_ERROR;
|
||||||
|
if (filename[0] == '\0') return WRT_ERROR;
|
||||||
|
|
||||||
|
void *handle = dlopen(filename, flags);
|
||||||
|
if (handle == NULL) {
|
||||||
|
LOG_ERR("dlopen failed: %s", dlerror());
|
||||||
|
return WRT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < DL_MAX_HANDLES; i++) {
|
||||||
|
if (dl_context.hnd[i].handle == NULL) {
|
||||||
|
dl_context.hnd[i].handle = handle;
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dlclose(handle);
|
||||||
|
LOG_ERR("dl_open: too many handles");
|
||||||
|
return WRT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool signature_check(const char *signature, int *arg_count, bool *has_ret) {
|
||||||
|
const static char allowed_chars[] = {'i', 'l', 'f', 'd', 'p'};
|
||||||
|
const static int allowed_chars_len = sizeof(allowed_chars) / sizeof(char);
|
||||||
|
|
||||||
|
// tmpl: '(' ch* ')' ch?
|
||||||
|
|
||||||
|
const char *ch = signature;
|
||||||
|
|
||||||
|
// 1. first char must be '('
|
||||||
|
if (*ch != '(') return false;
|
||||||
|
|
||||||
|
// 2. check inside paren
|
||||||
|
*arg_count = 0;
|
||||||
|
ch++;
|
||||||
|
bool right_paren = false, illegal = false;
|
||||||
|
for (; *ch != '\0'; ch++) {
|
||||||
|
if (*ch == ')') {
|
||||||
|
right_paren = true;
|
||||||
|
ch++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
for (int i = 0; i < allowed_chars_len; i++) {
|
||||||
|
if (*ch == allowed_chars[i]) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
illegal = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*arg_count)++;
|
||||||
|
}
|
||||||
|
if (!right_paren || illegal) return false;
|
||||||
|
|
||||||
|
// 3. check after paren
|
||||||
|
*has_ret = false;
|
||||||
|
if (*ch == '\0') return true;
|
||||||
|
|
||||||
|
*has_ret = true;
|
||||||
|
bool found = false;
|
||||||
|
for (int i = 0; i < allowed_chars_len; i++) {
|
||||||
|
if (*ch == allowed_chars[i]) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) return false;
|
||||||
|
if (*(ch + 1) != '\0') return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ffi_cif *create_ffi_cif(const char *signature) {
|
||||||
|
int arg_count;
|
||||||
|
bool has_ret;
|
||||||
|
|
||||||
|
if (!signature_check(signature, &arg_count, &has_ret)) return NULL;
|
||||||
|
|
||||||
|
ffi_cif *cif = dl_malloc(sizeof(ffi_cif));
|
||||||
|
ffi_type **arg_types = dl_malloc(sizeof(ffi_type *) * arg_count);
|
||||||
|
ffi_type *ret_type;
|
||||||
|
|
||||||
|
const char *ch = signature + 1;
|
||||||
|
{ // extract args
|
||||||
|
int i = 0;
|
||||||
|
while (*ch != ')') {
|
||||||
|
switch (*ch) {
|
||||||
|
case 'i': arg_types[i++] = &ffi_type_sint; break;
|
||||||
|
case 'l': arg_types[i++] = &ffi_type_slong; break;
|
||||||
|
case 'f': arg_types[i++] = &ffi_type_float; break;
|
||||||
|
case 'd': arg_types[i++] = &ffi_type_double; break;
|
||||||
|
case 'p': arg_types[i++] = &ffi_type_pointer; break; // TODO: pointer to pointer
|
||||||
|
default: {
|
||||||
|
LOG_ERR("Invalid signature: %s", signature);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ch++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ch = signature + 2 + arg_count;
|
||||||
|
ch++;
|
||||||
|
{ // extract ret
|
||||||
|
switch (*ch) {
|
||||||
|
case 'i': ret_type = &ffi_type_sint; break;
|
||||||
|
case 'l': ret_type = &ffi_type_slong; break;
|
||||||
|
case 'f': ret_type = &ffi_type_float; break;
|
||||||
|
case 'd': ret_type = &ffi_type_double; break;
|
||||||
|
case 'p': ret_type = &ffi_type_pointer; break; // TODO: pointer to pointer
|
||||||
|
case '\0': ret_type = &ffi_type_void; break;
|
||||||
|
default: {
|
||||||
|
LOG_ERR("Invalid signature: %s", signature);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ffi_status status = ffi_prep_cif(cif, FFI_DEFAULT_ABI, arg_count, ret_type, arg_types);
|
||||||
|
if (status != FFI_OK) {
|
||||||
|
LOG_ERR("ffi_prep_cif failed: %d", status);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cif;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
dl_free(arg_types);
|
||||||
|
dl_free(cif);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dl_sym(wasm_exec_env_t exec_env, int handle, const char *symbol, const char *signature) {
|
||||||
|
if (handle < 1 || handle > DL_MAX_HANDLES) return WRT_ERROR;
|
||||||
|
if (symbol == NULL || signature == NULL) return WRT_ERROR;
|
||||||
|
|
||||||
|
void *ptr = dl_context.hnd[handle - 1].handle;
|
||||||
|
if (ptr == NULL) return WRT_ERROR;
|
||||||
|
|
||||||
|
void *sym = dlsym(ptr, symbol);
|
||||||
|
if (sym == NULL) {
|
||||||
|
LOG_ERR("dlsym failed: %s", dlerror());
|
||||||
|
return WRT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ffi_cif *cif = create_ffi_cif(signature);
|
||||||
|
if (cif == NULL) return WRT_ERROR;
|
||||||
|
|
||||||
|
for (int i = 0; i < DL_MAX_SYMBOLS; i++) {
|
||||||
|
if (dl_context.sym[i].symbol == NULL) {
|
||||||
|
dl_context.sym[i].backref = handle;
|
||||||
|
dl_context.sym[i].symbol = sym;
|
||||||
|
dl_context.sym[i].cif = cif;
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERR("dl_sym: too many symbols");
|
||||||
|
return WRT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dl_call(wasm_exec_env_t exec_env) {
|
||||||
|
// int sym = (int)wrt_pop();
|
||||||
|
// if (sym < 1 || sym > DL_MAX_SYMBOLS) {
|
||||||
|
// LOG_ERR("dl_call: invalid symbol");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (dl_context.sym[sym - 1].symbol == NULL) {
|
||||||
|
// LOG_ERR("dl_call: symbol not found");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ffi_cif *cif = dl_context.sym[sym - 1].cif;
|
||||||
|
//
|
||||||
|
// int arg_count = cif->nargs;
|
||||||
|
// void *args[arg_count];
|
||||||
|
// for (int i = arg_count - 1; i >= 0; i--) {
|
||||||
|
// args[i] = (void *)wrt_pop();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void *ret = NULL;
|
||||||
|
// if (cif->rtype != &ffi_type_void) {
|
||||||
|
// ret = dl_malloc(cif->rtype->size);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ffi_call(cif, FFI_FN(dl_context.sym[sym - 1].symbol), ret, args);
|
||||||
|
//
|
||||||
|
// if (ret != NULL) {
|
||||||
|
// wrt_push((int)ret);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
Status dl_close(wasm_exec_env_t exec_env, int handle) {
|
||||||
|
if (handle < 1 || handle > DL_MAX_HANDLES) return WRT_ERROR;
|
||||||
|
|
||||||
|
void *ptr = dl_context.hnd[handle - 1].handle;
|
||||||
|
if (ptr == NULL) return WRT_ERROR;
|
||||||
|
|
||||||
|
dl_context.hnd[handle - 1].handle = NULL;
|
||||||
|
for (int i = 0; i < DL_MAX_SYMBOLS; i++) {
|
||||||
|
if (dl_context.sym[i].backref == handle) {
|
||||||
|
dl_context.sym[i].backref = 0;
|
||||||
|
dl_context.sym[i].symbol = NULL;
|
||||||
|
|
||||||
|
dl_free(dl_context.sym[i].cif->arg_types);
|
||||||
|
dl_free(dl_context.sym[i].cif);
|
||||||
|
dl_context.sym[i].cif = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dlclose(ptr) == 0 ? WRT_OK : WRT_ERROR;
|
||||||
|
}
|
27
wrt/dl.h
Normal file
27
wrt/dl.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef WRT_DL_H
|
||||||
|
#define WRT_DL_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <ffi.h>
|
||||||
|
#include <wasm_export.h>
|
||||||
|
|
||||||
|
#include "utils/defs.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct {
|
||||||
|
void *handle;
|
||||||
|
} hnd[DL_MAX_HANDLES];
|
||||||
|
struct {
|
||||||
|
unsigned backref;
|
||||||
|
void *symbol;
|
||||||
|
ffi_cif *cif;
|
||||||
|
} sym[DL_MAX_SYMBOLS];
|
||||||
|
} DLContext;
|
||||||
|
|
||||||
|
Status dl_init();
|
||||||
|
int dl_open(wasm_exec_env_t exec_env, const char *filename, int flags);
|
||||||
|
int dl_sym(wasm_exec_env_t exec_env, int handle, const char *symbol, const char *signature);
|
||||||
|
void dl_call(wasm_exec_env_t exec_env); // TODO
|
||||||
|
Status dl_close(wasm_exec_env_t exec_env, int handle);
|
||||||
|
|
||||||
|
#endif // WRT_DL_H
|
130
wrt/tinymap.c
Normal file
130
wrt/tinymap.c
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "utils/log.h"
|
||||||
|
#include "wrt/tinymap.h"
|
||||||
|
|
||||||
|
#define FOR(i) for (unsigned i = map->head[hash]; i; i = map->node[i].next)
|
||||||
|
|
||||||
|
static inline unsigned map_hash(int key) { return (key % BUK_SIZE + BUK_SIZE) % BUK_SIZE; }
|
||||||
|
|
||||||
|
void map_init(Map *map) {
|
||||||
|
map->count = 0;
|
||||||
|
memset(map->head, 0, sizeof(map->head));
|
||||||
|
memset(map->node, 0, sizeof(map->node));
|
||||||
|
}
|
||||||
|
|
||||||
|
void *map_get(Map *map, int key) {
|
||||||
|
if (key == 0) return NULL;
|
||||||
|
|
||||||
|
unsigned hash = map_hash(key);
|
||||||
|
FOR(i) {
|
||||||
|
if (map->node[i].key == key) return map->node[i].value;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool map_set(Map *map, int key, void *value) {
|
||||||
|
if (key == 0) return false;
|
||||||
|
|
||||||
|
unsigned hash = map_hash(key);
|
||||||
|
FOR(i) {
|
||||||
|
if (map->node[i].key == key) {
|
||||||
|
LOG_DBG("map_set: upd key=%d, val=0x%llx, pos=%d", key, (unsigned long long)value, i);
|
||||||
|
map->node[i].value = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map->count >= NOD_SIZE) {
|
||||||
|
LOG_DBG("map_set: nod full, compact");
|
||||||
|
map_compact(map);
|
||||||
|
if (map->count == NOD_SIZE) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
map->node[++map->count] = (MapNode){key, value, map->head[hash]};
|
||||||
|
map->head[hash] = map->count;
|
||||||
|
LOG_DBG("map_set: ins key=%d, val=0x%llx, pos=%d", key, (unsigned long long)value, map->count);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_delete(Map *map, int key) {
|
||||||
|
if (key == 0) return;
|
||||||
|
|
||||||
|
unsigned hash = map_hash(key);
|
||||||
|
FOR(i) {
|
||||||
|
if (map->node[i].key == key) {
|
||||||
|
LOG_DBG("map_delete: del key=%d, pos=%d", key, i);
|
||||||
|
map->node[i].key = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_compact(Map *map) {
|
||||||
|
LOG_DBG("map_compact: before count=%d", map->count);
|
||||||
|
|
||||||
|
{
|
||||||
|
// compact map->node
|
||||||
|
// i = last empty node, j = current scanning node
|
||||||
|
// node[0] is undefined
|
||||||
|
unsigned i = 1, j = 2;
|
||||||
|
for (; j <= NOD_SIZE;) {
|
||||||
|
if (map->node[i].key) {
|
||||||
|
i++, j++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map->node[j].key) {
|
||||||
|
LOG_DBG("map_compact: move node[%d] to node[%d]", j, i);
|
||||||
|
|
||||||
|
map->node[i] = map->node[j];
|
||||||
|
map->node[j].key = 0;
|
||||||
|
|
||||||
|
i++, j++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
|
||||||
|
map->count = i - 1;
|
||||||
|
|
||||||
|
LOG_DBG("map_compact: after count=%d", map->count);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// rebuild map->head
|
||||||
|
for (unsigned i = 0; i <= NOD_SIZE; i++) map->node[i].next = 0;
|
||||||
|
memset(map->head, 0, sizeof(map->head));
|
||||||
|
for (unsigned i = 1; i <= NOD_SIZE; i++) {
|
||||||
|
if (!map->node[i].key) break;
|
||||||
|
|
||||||
|
unsigned hash = map_hash(map->node[i].key);
|
||||||
|
|
||||||
|
if (map->head[hash] == 0) {
|
||||||
|
map->head[hash] = i;
|
||||||
|
} else {
|
||||||
|
unsigned j = map->head[hash];
|
||||||
|
map->head[hash] = i;
|
||||||
|
map->node[i].next = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_dbg(Map *map) {
|
||||||
|
for (int hash = 0; hash < BUK_SIZE; hash++) {
|
||||||
|
printf("[+] BUK[%2d]:\n", hash);
|
||||||
|
int spaces = 5;
|
||||||
|
FOR(i) {
|
||||||
|
for (int j = 0; j < spaces; j++) putchar(' ');
|
||||||
|
printf("└─ NOD[" __COLOR_GREEN "%2d" __COLOR_RESET "]: key=" __COLOR_YELLOW
|
||||||
|
"%d" __COLOR_RESET ", val=" __COLOR_CYAN "0x%llx" __COLOR_RESET
|
||||||
|
", nxt=" __COLOR_RED "%d" __COLOR_RESET "\n",
|
||||||
|
i, map->node[i].key, (unsigned long long)map->node[i].value, map->node[i].next);
|
||||||
|
spaces += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
wrt/tinymap.h
Normal file
32
wrt/tinymap.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef WRT_TINYMAP_H
|
||||||
|
#define WRT_TINYMAP_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifndef BUK_SIZE
|
||||||
|
#define BUK_SIZE 8
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NOD_SIZE
|
||||||
|
#define NOD_SIZE 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int key;
|
||||||
|
void *value;
|
||||||
|
unsigned next;
|
||||||
|
} MapNode;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
MapNode node[NOD_SIZE + 1];
|
||||||
|
unsigned head[BUK_SIZE];
|
||||||
|
unsigned count;
|
||||||
|
} Map;
|
||||||
|
|
||||||
|
void map_init(Map *map);
|
||||||
|
void *map_get(Map *map, int key);
|
||||||
|
bool map_set(Map *map, int key, void *value);
|
||||||
|
void map_delete(Map *map, int key);
|
||||||
|
void map_compact(Map *map);
|
||||||
|
|
||||||
|
#endif // WRT_TINYMAP_H
|
167
wrt/wrt.c
Normal file
167
wrt/wrt.c
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "utils/log.h"
|
||||||
|
#include "wrt.h"
|
||||||
|
|
||||||
|
Status wrt_platform_init(WRTContext *context, Allocator allocator, DeAllocator deAllocator) {
|
||||||
|
context->platform.malloc = allocator;
|
||||||
|
context->platform.free = deAllocator;
|
||||||
|
|
||||||
|
return WRT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status wrt_mem_init(WRTContext *context, uint32_t heap_buf_size, uint32_t error_buf_size) {
|
||||||
|
context->mem.heap_buf = NULL;
|
||||||
|
context->mem.error_buf = NULL;
|
||||||
|
|
||||||
|
context->mem.heap_buf = context->platform.malloc(sizeof(char) * heap_buf_size);
|
||||||
|
context->mem.heap_buf_size = heap_buf_size;
|
||||||
|
if (context->mem.heap_buf == NULL) {
|
||||||
|
LOG_ERR("malloc heap_buf failed");
|
||||||
|
goto fail2;
|
||||||
|
}
|
||||||
|
|
||||||
|
context->mem.error_buf = context->platform.malloc(sizeof(char) * error_buf_size);
|
||||||
|
context->mem.error_buf_size = error_buf_size;
|
||||||
|
if (context->mem.error_buf == NULL) {
|
||||||
|
LOG_ERR("malloc error_buf failed");
|
||||||
|
goto fail1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return WRT_OK;
|
||||||
|
|
||||||
|
fail1:
|
||||||
|
context->mem.error_buf = NULL;
|
||||||
|
context->mem.error_buf_size = 0;
|
||||||
|
context->platform.free(context->mem.heap_buf);
|
||||||
|
fail2:
|
||||||
|
context->mem.heap_buf = NULL;
|
||||||
|
context->mem.heap_buf_size = 0;
|
||||||
|
|
||||||
|
return WRT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status wrt_program_init(WRTContext *context, uint8_t *wasm_buffer, uint32_t wasm_buffer_size,
|
||||||
|
NativeSymbol *native_symbols, uint32_t native_symbols_size) {
|
||||||
|
context->program.wasm_buffer = wasm_buffer;
|
||||||
|
context->program.wasm_buffer_size = wasm_buffer_size;
|
||||||
|
context->program.native_symbols = native_symbols;
|
||||||
|
context->program.native_symbols_size = native_symbols_size;
|
||||||
|
|
||||||
|
return WRT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status wrt_wamr_init(WRTContext *context, char *entry_func, uint32_t stack_size,
|
||||||
|
uint32_t heap_size) {
|
||||||
|
RuntimeInitArgs init_args;
|
||||||
|
memset(&init_args, 0, sizeof(RuntimeInitArgs));
|
||||||
|
|
||||||
|
// runtime memory
|
||||||
|
init_args.mem_alloc_type = Alloc_With_Pool;
|
||||||
|
init_args.mem_alloc_option.pool.heap_buf = context->mem.heap_buf;
|
||||||
|
init_args.mem_alloc_option.pool.heap_size = context->mem.heap_buf_size;
|
||||||
|
|
||||||
|
// runtime functions
|
||||||
|
init_args.n_native_symbols = context->program.native_symbols_size;
|
||||||
|
init_args.native_module_name = "env";
|
||||||
|
init_args.native_symbols = context->program.native_symbols;
|
||||||
|
|
||||||
|
// init environment
|
||||||
|
if (!wasm_runtime_full_init(&init_args)) {
|
||||||
|
LOG_ERR("Init runtime environment failed.");
|
||||||
|
return WRT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// init dl
|
||||||
|
dl_init();
|
||||||
|
|
||||||
|
// register dl functions
|
||||||
|
static NativeSymbol native_symbols[] = {
|
||||||
|
EXPORT_WASM_API_WITH_SIG(dl_open, "(si)i"),
|
||||||
|
EXPORT_WASM_API_WITH_SIG(dl_sym, "(iss)i"),
|
||||||
|
EXPORT_WASM_API_WITH_SIG(dl_call, "(i)i"), // TODO
|
||||||
|
EXPORT_WASM_API_WITH_SIG(dl_close, "(i)i"),
|
||||||
|
};
|
||||||
|
wasm_runtime_register_natives("env", native_symbols,
|
||||||
|
sizeof(native_symbols) / sizeof(NativeSymbol));
|
||||||
|
|
||||||
|
// load module
|
||||||
|
context->wamr.module =
|
||||||
|
wasm_runtime_load(context->program.wasm_buffer, context->program.wasm_buffer_size,
|
||||||
|
context->mem.error_buf, context->mem.error_buf_size);
|
||||||
|
if (!context->wamr.module) {
|
||||||
|
LOG_ERR("Load wasm module failed. error: %s", context->mem.error_buf);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// instantiate module
|
||||||
|
context->wamr.module_inst =
|
||||||
|
wasm_runtime_instantiate(context->wamr.module, stack_size, heap_size,
|
||||||
|
context->mem.error_buf, sizeof(context->mem.error_buf));
|
||||||
|
if (!context->wamr.module_inst) {
|
||||||
|
LOG_ERR("Instantiate wasm module failed. error: %s", context->mem.error_buf);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create env
|
||||||
|
context->wamr.exec_env = wasm_runtime_create_exec_env(context->wamr.module_inst, stack_size);
|
||||||
|
if (!context->wamr.exec_env) {
|
||||||
|
LOG_ERR("Create wasm execution environment failed.");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find entry function
|
||||||
|
if (!(context->wamr.entry_func =
|
||||||
|
wasm_runtime_lookup_function(context->wamr.module_inst, entry_func, NULL))) {
|
||||||
|
LOG_ERR("The wasm function %s wasm function is not found.", entry_func);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return WRT_OK;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
if (context->wamr.exec_env) wasm_runtime_destroy_exec_env(context->wamr.exec_env);
|
||||||
|
if (context->wamr.module_inst) {
|
||||||
|
wasm_runtime_deinstantiate(context->wamr.module_inst);
|
||||||
|
}
|
||||||
|
if (context->wamr.module) wasm_runtime_unload(context->wamr.module);
|
||||||
|
wasm_runtime_destroy();
|
||||||
|
|
||||||
|
return WRT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status wrt_free(WRTContext *context) {
|
||||||
|
if (context->wamr.exec_env) {
|
||||||
|
wasm_runtime_destroy_exec_env(context->wamr.exec_env);
|
||||||
|
context->wamr.exec_env = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->wamr.module_inst) {
|
||||||
|
wasm_runtime_deinstantiate(context->wamr.module_inst);
|
||||||
|
context->wamr.module_inst = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context->wamr.module) {
|
||||||
|
wasm_runtime_unload(context->wamr.module);
|
||||||
|
context->wamr.module = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
wasm_runtime_destroy();
|
||||||
|
|
||||||
|
context->platform.free(context->mem.error_buf);
|
||||||
|
context->mem.error_buf_size = 0;
|
||||||
|
context->platform.free(context->mem.heap_buf);
|
||||||
|
context->mem.heap_buf_size = 0;
|
||||||
|
|
||||||
|
return WRT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status wrt_run(WRTContext *context) {
|
||||||
|
// run wasm
|
||||||
|
if (!wasm_runtime_call_wasm(context->wamr.exec_env, context->wamr.entry_func, 0, NULL)) {
|
||||||
|
LOG_ERR("Call wasm function failed. error: %s", context->mem.error_buf);
|
||||||
|
return WRT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return WRT_OK;
|
||||||
|
}
|
48
wrt/wrt.h
Normal file
48
wrt/wrt.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#ifndef WRT_WRT_H
|
||||||
|
#define WRT_WRT_H
|
||||||
|
|
||||||
|
#include <wasm_export.h>
|
||||||
|
|
||||||
|
#include "utils/defs.h"
|
||||||
|
#include "wrt/dl.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct {
|
||||||
|
Allocator malloc;
|
||||||
|
DeAllocator free;
|
||||||
|
} platform;
|
||||||
|
struct {
|
||||||
|
wasm_module_t module;
|
||||||
|
wasm_module_inst_t module_inst;
|
||||||
|
wasm_exec_env_t exec_env;
|
||||||
|
wasm_function_inst_t entry_func;
|
||||||
|
} wamr;
|
||||||
|
struct {
|
||||||
|
uint8_t *heap_buf;
|
||||||
|
uint32_t heap_buf_size;
|
||||||
|
char *error_buf;
|
||||||
|
uint32_t error_buf_size;
|
||||||
|
} mem;
|
||||||
|
struct {
|
||||||
|
uint8_t *wasm_buffer;
|
||||||
|
uint32_t wasm_buffer_size;
|
||||||
|
NativeSymbol *native_symbols;
|
||||||
|
uint32_t native_symbols_size;
|
||||||
|
} program;
|
||||||
|
} WRTContext;
|
||||||
|
|
||||||
|
Status wrt_platform_init(WRTContext *context, Allocator allocator, DeAllocator deAllocator);
|
||||||
|
|
||||||
|
Status wrt_mem_init(WRTContext *context, uint32_t heap_buf_size, uint32_t error_buf_size);
|
||||||
|
|
||||||
|
Status wrt_program_init(WRTContext *context, uint8_t *wasm_buffer, uint32_t wasm_buffer_size,
|
||||||
|
NativeSymbol *native_symbols, uint32_t native_symbols_size);
|
||||||
|
|
||||||
|
Status wrt_wamr_init(WRTContext *context, char *entry_func, uint32_t stack_size,
|
||||||
|
uint32_t heap_size);
|
||||||
|
|
||||||
|
Status wrt_free(WRTContext *context);
|
||||||
|
|
||||||
|
Status wrt_run(WRTContext *context);
|
||||||
|
|
||||||
|
#endif // WRT_WRT_H
|
Reference in New Issue
Block a user