Add erlang binding (#54)

* add erlang support

* add erlang support for windows

* add erlang ci for linux and windows
This commit is contained in:
Cocoa 2024-02-29 21:42:14 +08:00 committed by GitHub
parent 683210d02f
commit a362da640a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 434 additions and 0 deletions

View File

@ -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

View File

@ -183,6 +183,45 @@ end
</td></tr>
</table>
### ruapu with Erlang
<table>
<tr><td>
Compile ruapu library
```shell
# from source code
rebar3 compile
```
</td>
<td>
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
```
</td></tr>
</table>
<details>
<summary>Github-hosted runner result (Linux)</summary>

22
erlang/.gitignore vendored Normal file
View File

@ -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/

84
erlang/CMakeLists.txt Normal file
View File

@ -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()

21
erlang/Makefile Normal file
View File

@ -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)

20
erlang/Makefile.win Normal file
View File

@ -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

135
erlang/c_src/ruapu.c Normal file
View File

@ -0,0 +1,135 @@
#include <erl_nif.h>
#include <stdbool.h>
#include <stdint.h>
#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);

18
erlang/rebar.config Normal file
View File

@ -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"}
]}.

1
erlang/rebar.lock Normal file
View File

@ -0,0 +1 @@
[].

14
erlang/src/ruapu.app.src Normal file
View File

@ -0,0 +1,14 @@
{application, ruapu,
[{description, "ruapu erlang/otp binding"},
{vsn, "0.1.0"},
{registered, []},
{applications,
[kernel,
stdlib
]},
{env,[]},
{modules, []},
{licenses, ["MIT"]},
{links, []}
]}.

32
erlang/src/ruapu.erl Normal file
View File

@ -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.

30
erlang/src/ruapu_nif.erl Normal file
View File

@ -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).