From a362da640a5d17c1efc210cc0fabaa4fea96f5ef Mon Sep 17 00:00:00 2001 From: Cocoa Date: Thu, 29 Feb 2024 21:42:14 +0800 Subject: [PATCH] Add erlang binding (#54) * add erlang support * add erlang support for windows * add erlang ci for linux and windows --- .github/workflows/ci.yml | 18 ++++++ README.md | 39 +++++++++++ erlang/.gitignore | 22 +++++++ erlang/CMakeLists.txt | 84 ++++++++++++++++++++++++ erlang/Makefile | 21 ++++++ erlang/Makefile.win | 20 ++++++ erlang/c_src/ruapu.c | 135 +++++++++++++++++++++++++++++++++++++++ erlang/rebar.config | 18 ++++++ erlang/rebar.lock | 1 + erlang/src/ruapu.app.src | 14 ++++ erlang/src/ruapu.erl | 32 ++++++++++ erlang/src/ruapu_nif.erl | 30 +++++++++ 12 files changed, 434 insertions(+) create mode 100644 erlang/.gitignore create mode 100644 erlang/CMakeLists.txt create mode 100644 erlang/Makefile create mode 100644 erlang/Makefile.win create mode 100644 erlang/c_src/ruapu.c create mode 100644 erlang/rebar.config create mode 100644 erlang/rebar.lock create mode 100644 erlang/src/ruapu.app.src create mode 100644 erlang/src/ruapu.erl create mode 100644 erlang/src/ruapu_nif.erl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fbb4fb..372bce8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,15 @@ jobs: cd rust cargo build --verbose cargo test --verbose + - uses: erlef/setup-beam@v1 + with: + otp-version: 26.2 + rebar3-version: "3.22.1" + - name: build-test-erlang + run: | + cd erlang + rebar3 compile + rebar3 eunit macos: runs-on: macos-latest @@ -124,6 +133,15 @@ jobs: cd rust cargo build --verbose cargo test --verbose + - uses: erlef/setup-beam@v1 + with: + otp-version: 26.2 + rebar3-version: "3.22.1" + - name: build-test-erlang + run: | + cd erlang + rebar3 compile + rebar3 eunit qemu: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 5af2854..aecd5ed 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,45 @@ end +### ruapu with Erlang + + + + + +
+ +Compile ruapu library + +```shell +# from source code +rebar3 compile +``` + + +Use ruapu in Erlang `rebar3 shell` + +```erlang +ruapu:rua(). +{ok,["neon","vfpv4","asimdrdm","asimdhp","asimddp", + "asimdfhm","bf16","i8mm","pmull","crc32","aes","sha1", + "sha2","sha3","sha512","amx"]} +> ruapu:supports("neon"). +true +> ruapu:supports(neon). +true +> ruapu:supports(<<"neon">>). +true +> ruapu:supports("avx2"). +false +> ruapu:supports(avx2). +false +> ruapu:supports(<<"avx2">>). +false +``` + +
+
Github-hosted runner result (Linux) diff --git a/erlang/.gitignore b/erlang/.gitignore new file mode 100644 index 0000000..3b5a579 --- /dev/null +++ b/erlang/.gitignore @@ -0,0 +1,22 @@ +.rebar3 +_* +.eunit +*.o +*.beam +*.plt +*.swp +*.swo +.erlang.cookie +ebin +log +erl_crash.dump +.rebar +logs +_build +.idea +*.iml +rebar3.crashdump +*~ +doc/ +priv/ +_build/ diff --git a/erlang/CMakeLists.txt b/erlang/CMakeLists.txt new file mode 100644 index 0000000..fb8f998 --- /dev/null +++ b/erlang/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 3.8 FATAL_ERROR) +project(ruapu_nif C CXX) + +if(NOT DEFINED PRIV_DIR) + if(DEFINED MIX_APP_PATH AND NOT "${MIX_APP_PATH}" STREQUAL "") + if(WIN32) + string(REPLACE "\\" "/" MIX_APP_PATH "${MIX_APP_PATH}") + endif() + set(PRIV_DIR "${MIX_APP_PATH}/priv") + else() + set(PRIV_DIR "${CMAKE_CURRENT_SOURCE_DIR}/priv") + endif() +endif() +message(STATUS "Using PRIV_DIR: ${PRIV_DIR}") + +if(DEFINED ERTS_INCLUDE_DIR AND NOT "${ERTS_INCLUDE_DIR}" STREQUAL "") + set(ERTS_INCLUDE_DIR "${ERTS_INCLUDE_DIR}") +else() + set(ERTS_INCLUDE_DIR_ONE_LINER "erl -noshell -eval \"io:format('~ts/erts-~ts/include/', [code:root_dir(), erlang:system_info(version)]), halt().\"") + if(WIN32) + execute_process(COMMAND powershell -command "${ERTS_INCLUDE_DIR_ONE_LINER}" OUTPUT_VARIABLE ERTS_INCLUDE_DIR) + else() + execute_process(COMMAND bash -c "${ERTS_INCLUDE_DIR_ONE_LINER}" OUTPUT_VARIABLE ERTS_INCLUDE_DIR) + endif() + set(ERTS_INCLUDE_DIR "${ERTS_INCLUDE_DIR}") +endif() +message(STATUS "Using ERTS_INCLUDE_DIR: ${ERTS_INCLUDE_DIR}") + +if(UNIX AND APPLE) + set(CMAKE_SHARED_LINKER_FLAGS "-flat_namespace -undefined dynamic_lookup") +endif() + +if(DEFINED ENV{TARGET_GCC_FLAGS}) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} $ENV{TARGET_GCC_FLAGS}") +endif() + +message(STATUS "CMAKE_TOOLCHAIN_FILE: ${CMAKE_TOOLCHAIN_FILE}") + +if(WIN32) + string(REPLACE "\\" "/" C_SRC "${C_SRC}") +endif() +set(SOURCE_FILES + "${C_SRC}/ruapu.c" +) +if(POLICY CMP0068) + cmake_policy(SET CMP0068 NEW) +endif() +include_directories("${ERTS_INCLUDE_DIR}") + +add_library(ruapu_nif SHARED + ${SOURCE_FILES} +) +install( + TARGETS ruapu_nif + RUNTIME DESTINATION "${PRIV_DIR}" +) + +set_target_properties(ruapu_nif PROPERTIES PREFIX "") +if(NOT WIN32) + set_target_properties(ruapu_nif PROPERTIES SUFFIX ".so") +endif() +set_target_properties(ruapu_nif PROPERTIES + INSTALL_RPATH_USE_LINK_PATH TRUE + BUILD_WITH_INSTALL_RPATH TRUE +) + +if(UNIX AND NOT APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unused-but-set-variable -Wno-reorder") +elseif(UNIX AND APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-reorder-ctor") + set(CMAKE_SHARED_LINKER_FLAGS "-flat_namespace -undefined dynamic_lookup") +endif() + +if(WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj /wd4996 /wd4267 /wd4068") +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") + endif() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-function -Wno-sign-compare -Wno-unused-parameter -Wno-missing-field-initializers -Wno-deprecated-declarations") +endif() diff --git a/erlang/Makefile b/erlang/Makefile new file mode 100644 index 0000000..01a15ce --- /dev/null +++ b/erlang/Makefile @@ -0,0 +1,21 @@ +ifndef MIX_APP_PATH + MIX_APP_PATH=$(shell pwd) +endif + +PRIV_DIR = $(MIX_APP_PATH)/priv +RUAPU_SO = $(PRIV_DIR)/ruapu_nif.so +C_SRC = $(shell pwd)/c_src + +.DEFAULT_GLOBAL := build + +build: $(RUAPU_SO) + @echo > /dev/null + +$(RUAPU_SO): $(C_SRC)/ruapu.c + @ cmake -S . -B "$(PRIV_DIR)/cmake_ruapu" \ + -DCMAKE_BUILD_TYPE=Release \ + -DC_SRC=$(C_SRC) \ + -DMIX_APP_PATH=$(MIX_APP_PATH) \ + -DCMAKE_INSTALL_PREFIX=$(PRIV_DIR) + @ cmake --build "$(PRIV_DIR)/cmake_ruapu" --config Release + @ cp "$(PRIV_DIR)/cmake_ruapu/ruapu_nif.so" $(RUAPU_SO) diff --git a/erlang/Makefile.win b/erlang/Makefile.win new file mode 100644 index 0000000..4903d38 --- /dev/null +++ b/erlang/Makefile.win @@ -0,0 +1,20 @@ +!IFNDEF MIX_APP_PATH +MIX_APP_PATH=$(MAKEDIR) +!ENDIF + +PRIV_DIR = $(MIX_APP_PATH)\priv +RUAPU_SO = $(PRIV_DIR)\ruapu_nif.dll +C_SRC = $(MAKEDIR)\c_src + +build: $(RUAPU_SO) + +$(RUAPU_SO): + @ if not exist "$(PRIV_DIR)" mkdir "$(PRIV_DIR)" + @ cmake -G "NMake Makefiles" -B"$(PRIV_DIR)\cmake_ruapu" \ + -DC_SRC=$(C_SRC) \ + -DMIX_APP_PATH=$(MIX_APP_PATH) \ + -DCMAKE_INSTALL_PREFIX=$(PRIV_DIR) + @ cmake --build "$(PRIV_DIR)\cmake_ruapu" --config Release + @ cmake --install "$(PRIV_DIR)\cmake_ruapu" --config Release + +.PHONY: build diff --git a/erlang/c_src/ruapu.c b/erlang/c_src/ruapu.c new file mode 100644 index 0000000..5f2951f --- /dev/null +++ b/erlang/c_src/ruapu.c @@ -0,0 +1,135 @@ +#include +#include +#include +#define RUAPU_IMPLEMENTATION +#include "../../ruapu.h" + +ERL_NIF_TERM atom(ErlNifEnv *env, const char *msg) { + ERL_NIF_TERM a; + if (enif_make_existing_atom(env, msg, &a, ERL_NIF_LATIN1)) { + return a; + } else { + return enif_make_atom(env, msg); + } +} + +ERL_NIF_TERM error(ErlNifEnv *env, const char *msg) { + ERL_NIF_TERM error_atom = atom(env, "error"); + ERL_NIF_TERM msg_term = enif_make_string(env, msg, ERL_NIF_LATIN1); + return enif_make_tuple2(env, error_atom, msg_term); +} + +ERL_NIF_TERM ok(ErlNifEnv *env) { return atom(env, "ok"); } + +ERL_NIF_TERM ok_tuple(ErlNifEnv *env, ERL_NIF_TERM term) { + return enif_make_tuple2(env, atom(env, "ok"), term); +} + +const char *get_string(ErlNifEnv *env, ERL_NIF_TERM term) { + char *result = NULL; + unsigned len; + int ret = enif_get_list_length(env, term, &len); + + if (ret) { + result = (char *)enif_alloc(sizeof(char) * (len + 1)); + ret = enif_get_string(env, term, result, len + 1, ERL_NIF_LATIN1); + if (ret > 0) { + return result; + } else { + enif_free((void *)result); + return NULL; + } + } + + ErlNifBinary bin; + ret = enif_inspect_binary(env, term, &bin); + if (ret) { + result = (char *)enif_alloc(sizeof(char) * (bin.size + 1)); + if (result == NULL) { + return NULL; + } + memcpy(result, bin.data, bin.size); + result[bin.size] = '\0'; + return result; + } + + ret = enif_get_atom_length(env, term, &len, ERL_NIF_LATIN1); + if (ret) { + result = (char *)enif_alloc(sizeof(char) * (len + 1)); + ret = enif_get_atom(env, term, result, len + 1, ERL_NIF_LATIN1); + if (ret) { + return result; + } else { + enif_free((void *)result); + return NULL; + } + } + + return NULL; +} + +static ERL_NIF_TERM nif_supports(ErlNifEnv *env, int argc, + const ERL_NIF_TERM argv[]) { + const char *isa = get_string(env, argv[0]); + if (isa == NULL) { + return enif_make_badarg(env); + } + int supports = ruapu_supports(isa); + enif_free((void *)isa); + if (supports) { + return atom(env, "true"); + } else { + return atom(env, "false"); + } +} + +static ERL_NIF_TERM nif_rua(ErlNifEnv *env, int argc, + const ERL_NIF_TERM argv[]) { + const char *const *supported = ruapu_rua(); + const char *const *supported_start = supported; + uint32_t count = 0; + while (*supported_start) { + supported_start++; + count++; + } + if (count == 0) { + ERL_NIF_TERM list = enif_make_list(env, 0, NULL); + return ok_tuple(env, list); + } + + ERL_NIF_TERM *terms = + (ERL_NIF_TERM *)enif_alloc(sizeof(ERL_NIF_TERM) * count); + if (terms == NULL) { + return error(env, "enif_alloc failed"); + } + supported_start = supported; + for (uint32_t i = 0; i < count; i++) { + terms[i] = enif_make_string(env, *supported_start, ERL_NIF_LATIN1); + supported_start++; + } + + ERL_NIF_TERM list = enif_make_list_from_array(env, terms, count); + enif_free(terms); + return ok_tuple(env, list); +} + +static int on_load(ErlNifEnv *env, void **_sth1, ERL_NIF_TERM _sth2) { + ruapu_init(); + return 0; +} + +static int on_reload(ErlNifEnv *_sth0, void **_sth1, ERL_NIF_TERM _sth2) { + return 0; +} + +static int on_upgrade(ErlNifEnv *_sth0, void **_sth1, void **_sth2, + ERL_NIF_TERM _sth3) { + return 0; +} + +static ErlNifFunc nif_functions[] = { + {"supports", 1, nif_supports, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"rua", 0, nif_rua, ERL_NIF_DIRTY_JOB_CPU_BOUND}, +}; + +ERL_NIF_INIT(ruapu_nif, nif_functions, on_load, on_reload, on_upgrade, NULL); diff --git a/erlang/rebar.config b/erlang/rebar.config new file mode 100644 index 0000000..25203a2 --- /dev/null +++ b/erlang/rebar.config @@ -0,0 +1,18 @@ +{erl_opts, [debug_info]}. +{deps, []}. + +{pre_hooks, + [{"(linux|darwin|solaris)", compile, "make"}, + {"(freebsd)", compile, "gmake"}, + {"win32", compile, "nmake /F Makefile.win"} +]}. + +{project_plugins, [rebar3_ex_doc]}. + +{hex, [ + {doc, #{provider => ex_doc}} +]}. + +{ex_doc, [ + {source_url, "https://github.com/nihui/ruapu"} +]}. diff --git a/erlang/rebar.lock b/erlang/rebar.lock new file mode 100644 index 0000000..57afcca --- /dev/null +++ b/erlang/rebar.lock @@ -0,0 +1 @@ +[]. diff --git a/erlang/src/ruapu.app.src b/erlang/src/ruapu.app.src new file mode 100644 index 0000000..73a11d4 --- /dev/null +++ b/erlang/src/ruapu.app.src @@ -0,0 +1,14 @@ +{application, ruapu, + [{description, "ruapu erlang/otp binding"}, + {vsn, "0.1.0"}, + {registered, []}, + {applications, + [kernel, + stdlib + ]}, + {env,[]}, + {modules, []}, + + {licenses, ["MIT"]}, + {links, []} + ]}. diff --git a/erlang/src/ruapu.erl b/erlang/src/ruapu.erl new file mode 100644 index 0000000..d873a13 --- /dev/null +++ b/erlang/src/ruapu.erl @@ -0,0 +1,32 @@ +-module(ruapu). +-export([rua/0, supports/1]). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +%% @doc +%% +%% Get a list of supported features. +-spec rua() -> {ok, [string()]} | {error, string()}. +rua() -> + ruapu_nif:rua(). + +%% @doc +%% +%% Check if a feature is supported. +-spec supports(atom() | string() | binary()) -> boolean(). +supports(ISA) -> + ruapu_nif:supports(ISA). + +-ifdef(EUNIT). +rua_test() -> + ?assertMatch({ok, _}, rua()). + +supports_test() -> + ?assert(supports(<<"binary_non_exists_feature">>) =:= false), + ?assert(supports("string_non_exists_feature") =:= false), + ?assert(supports(atom_non_exists_feature) =:= false), + {ok, Features} = rua(), + ?assert(lists:all(fun(Feature) -> supports(Feature) end, Features)). +-endif. diff --git a/erlang/src/ruapu_nif.erl b/erlang/src/ruapu_nif.erl new file mode 100644 index 0000000..0a30a19 --- /dev/null +++ b/erlang/src/ruapu_nif.erl @@ -0,0 +1,30 @@ +-module(ruapu_nif). +-export([supports/1, rua/0]). + +-on_load(init/0). + +-define(APPNAME, ruapu). +-define(LIBNAME, ruapu_nif). + +init() -> + SoName = case code:priv_dir(?APPNAME) of + {error, bad_name} -> + case filelib:is_dir(filename:join(["..", priv])) of + true -> + filename:join(["..", priv, ?LIBNAME]); + _ -> + filename:join([priv, ?LIBNAME]) + end; + Dir -> + filename:join(Dir, ?LIBNAME) + end, + erlang:load_nif(SoName, 0). + +not_loaded(Line) -> + erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}). + +supports(_ISA) -> + not_loaded(?LINE). + +rua() -> + not_loaded(?LINE).