Skip to content

Commit

Permalink
Merge pull request #599 from pguyot/w21/add-resource-objects
Browse files Browse the repository at this point in the history
Implement resource objects

Resources are garbage-collected memory-managed pointers that can have
destructors that are called when the pointer is garbage collected.
They are implemented as special refc binaries that look empty. This is similar
to BEAM's behavior before ERTS 9.0 (OTP-20.0).

Also create a header for enif-compatible API, to be used with resources. Add
`ERL_NIF_TERM` which is typedef'd to `term`, and update CodeQL rule accordingly.

Also reorder fields in Context structure so a Context can be passed as an enif
environment which could also be not linked to a context.

These changes are made under both the "Apache 2.0" and the "GNU Lesser General
Public License 2.1 or later" license terms (dual license).

SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
  • Loading branch information
bettio committed Jun 27, 2023
2 parents bba1b6f + e84347f commit 3d13d60
Show file tree
Hide file tree
Showing 20 changed files with 754 additions and 28 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/build-and-test-macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ jobs:
run: |
./tests/test-erlang
- name: "Test: test-enif"
working-directory: build
run: |
./tests/test-enif
- name: "Test: test-mailbox"
working-directory: build
run: |
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/build-and-test-other.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,13 @@ jobs:
cp ../build_tests/tests/libs/alisp/*.avm tests/libs/alisp/ &&
make AtomVM &&
make test-erlang &&
make test-enif &&
make test-mailbox &&
make test-structs &&
file ./tests/test-erlang &&
./tests/test-erlang -s prime_smp &&
file ./tests/test-enif &&
./tests/test-enif &&
file ./tests/test-mailbox &&
./tests/test-mailbox &&
file ./tests/test-structs &&
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ jobs:
./tests/test-erlang -s prime_smp
valgrind ./tests/test-erlang -s prime_smp
- name: "Test: test-enif"
working-directory: build
run: |
./tests/test-enif
valgrind ./tests/test-enif
- name: "Test: test-mailbox"
working-directory: build
run: |
Expand Down
12 changes: 10 additions & 2 deletions code-queries/non-term-to-term-func.ql
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,16 @@

import cpp

predicate isTermType(Type t) {
t.getName() = "term" or
(
t instanceof TypedefType
and isTermType(t.(TypedefType).getBaseType())
)
}

predicate isNotTermOrAtom(Expr expr) {
expr.getExplicitlyConverted().getType().getName() != "term" and
not isTermType(expr.getExplicitlyConverted().getType()) and
not (
expr.isInMacroExpansion() and
expr.isConstant() and
Expand All @@ -37,7 +45,7 @@ predicate isNotTermOrAtom(Expr expr) {
from FunctionCall functioncall, Type expected_type, Expr expr, int i
where
functioncall.getExpectedParameterType(i) = expected_type and
expected_type.getName() = "term" and
isTermType(expected_type) and
functioncall.getArgument(i) = expr and
isNotTermOrAtom(expr)
select expr, "Passing a non-term to a function expecting a term, without an explicit cast"
13 changes: 11 additions & 2 deletions code-queries/term-to-non-term-func.ql
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,20 @@
*/
import cpp


predicate isTermType(Type t) {
t.getName() = "term" or
(
t instanceof TypedefType
and isTermType(t.(TypedefType).getBaseType())
)
}

from FunctionCall functioncall, Type expected_type, Expr expr, int i
where
functioncall.getExpectedParameterType(i) = expected_type and
expected_type.getName() != "term" and
expected_type.getName() != "unknown" and // This includes variadic arguments
expr.getExplicitlyConverted().getType().getName() = "term" and
not isTermType(expected_type) and
isTermType(expr.getExplicitlyConverted().getType()) and
functioncall.getArgument(i) = expr
select functioncall, "Passing a term to a function expecting a non-term, without an explicit cast"
4 changes: 4 additions & 0 deletions src/libAtomVM/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ set(HEADER_FILES
debug.h
defaultatoms.h
dictionary.h
erl_nif.h
erl_nif_priv.h
exportedfunction.h
externalterm.h
globalcontext.h
Expand All @@ -48,6 +50,7 @@ set(HEADER_FILES
platform_nifs.h
port.h
refc_binary.h
resources.h
scheduler.h
smp.h
synclist.h
Expand Down Expand Up @@ -82,6 +85,7 @@ set(SOURCE_FILES
nifs.c
port.c
refc_binary.c
resources.c
scheduler.c
stacktrace.c
term.c
Expand Down
14 changes: 6 additions & 8 deletions src/libAtomVM/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ enum ContextFlags

struct Context
{
// First fields matches ErlNifEnv structure.
GlobalContext *global;
Heap heap;
term *e;
term x[MAX_REG];

struct ListHead processes_list_head;

struct ListHead processes_table_head;
Expand All @@ -82,14 +88,8 @@ struct Context

struct ListHead monitors_head;

term x[MAX_REG];

avm_float_t *fr;

Heap heap;

term *e;

size_t min_heap_size;
size_t max_heap_size;

Expand All @@ -105,8 +105,6 @@ struct Context

struct ListHead dictionary;

GlobalContext *global;

// Ports support
native_handler_f native_handler;

Expand Down
150 changes: 150 additions & 0 deletions src/libAtomVM/erl_nif.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* This file is part of AtomVM.
*
* Copyright 2023 Paul Guyot <pguyot@kallisys.net>
*
* 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
*
* http://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.
*
* SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
*/

/**
* @file erl_nif.h
* @brief Public API for nifs, compatible with Erlang/OTP API
*/

#ifndef _ERL_NIF_H_
#define _ERL_NIF_H_

#include "term_typedef.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Opaque environment, passed to nifs.
*/
typedef struct ErlNifEnv ErlNifEnv;

/**
* @brief A term.
*/
typedef term ERL_NIF_TERM;

/**
* @brief a pid
* @details We currently only handle local pids.
*/
typedef int32_t ErlNifPid;

/**
* @brief Opaque resource type
*/
typedef struct ResourceType ErlNifResourceType;

/**
* @brief Selectable event.
*/
typedef int ErlNifEvent;

/**
* @brief Destructor callback
*/
typedef void ErlNifResourceDtor(ErlNifEnv *caller_env, void *obj);

/**
* @brief Resource callbacks.
* @details Members should be set to 0, 1 depending on provided callbacks.
* Callbacks can also be NULL if not used.
*/
typedef struct
{
int members;
ErlNifResourceDtor *dtor;
} ErlNifResourceTypeInit;

/**
* @brief resource flags
*/
typedef enum
{
ERL_NIF_RT_CREATE = 1,
// ERL_NIF_RT_TAKEOVER is not supported yet
} ErlNifResourceFlags;

/**
* @brief Create or take over (code upgrade) a resource type.
* @param env the current environment
* @param init a structure describing the callbacks. Callbacks can be NULL if
* not used.
* @param name name of the resource (copied)
* @param flags `ERL_NIF_RT_CREATE` or `ERL_NIF_RT_TAKEOVER` to create or
* take over.
* @param tried on output, updated to `ERL_NIF_RT_CREATE` or
* `ERL_NIF_RT_TAKEOVER` depending on what has been done. On failure, updated
* to flags. Can be NULL.
* @return the resource type or `NULL` on failure.
*/
ErlNifResourceType *enif_init_resource_type(ErlNifEnv *env, const char *name, const ErlNifResourceTypeInit *init, ErlNifResourceFlags flags, ErlNifResourceFlags *tried);

/**
* @brief Allocate a resource for given type for `size` bytes.
* @param type a trype created by `enif_init_resource_type`.
* @param size the size in bytes.
* @return a pointer or `NULL` on failure.
*/
void *enif_alloc_resource(ErlNifResourceType *type, unsigned size);

/**
* @brief Get a pointer to a resource from a term representing it.
* @param env the current environment
* @param t the term
* @param type the resource type
* @param objp on output the pointer to the resource
* @return `true` on success, `false` on failure, if term is not a resource of
* type `type`
*/
int enif_get_resource(ErlNifEnv *env, ERL_NIF_TERM t, ErlNifResourceType *type, void **objp);

/**
* @brief Increment reference count of a resource
* @param resource the resource to keep
* @return `true`.
*/
int enif_keep_resource(void *resource);

/**
* @brief Decrement reference count of a resource
* @param resource the resource to release
* @return `true`.
*/
int enif_release_resource(void *resource);

/**
* @brief create a term from a resource
* @details the term can be later passed to `enif_get_resource`.
* The resource is typically released (by calling `enif_release_resource`)
* just after calling this function to "transfer ownership" to Erlang code so
* that it will be destroyed when garbage collected.
* @param env current environment
* @param obj resource
* @return a new term representing the resource
*/
ERL_NIF_TERM enif_make_resource(ErlNifEnv *env, void *obj);

#ifdef __cplusplus
}
#endif

#endif // _ERL_NIF_H_
74 changes: 74 additions & 0 deletions src/libAtomVM/erl_nif_priv.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* This file is part of AtomVM.
*
* Copyright 2023 Paul Guyot <pguyot@kallisys.net>
*
* 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
*
* http://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.
*
* SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
*/

#ifndef _ERL_NIF_PRIV_H_
#define _ERL_NIF_PRIV_H_

#ifdef __cplusplus
extern "C" {
#endif

#include "context.h"
#include "memory.h"

struct ErlNifEnv
{
GlobalContext *global;
Heap heap;
term *stack_pointer; // Context stack pointer, NULL for non-context envs
term x[2];
};

_Static_assert(offsetof(struct ErlNifEnv, global) == offsetof(struct Context, global) ? 1 : 0,
"ErlNifEnv.global doesn't match Context.global");
_Static_assert(offsetof(struct ErlNifEnv, heap) == offsetof(struct Context, heap) ? 1 : 0,
"ErlNifEnv.heap doesn't match Context.heap");
_Static_assert(offsetof(struct ErlNifEnv, stack_pointer) == offsetof(struct Context, e) ? 1 : 0,
"ErlNifEnv.stack_pointer doesn't match Context.e");
_Static_assert(offsetof(struct ErlNifEnv, x) == offsetof(struct Context, x) ? 1 : 0,
"ErlNifEnv.x doesn't match Context.x");

static inline ErlNifEnv *erl_nif_env_from_context(Context *ctx)
{
return (ErlNifEnv *) ctx;
}

static inline bool erl_nif_env_is_context(ErlNifEnv *env)
{
return env->stack_pointer != NULL;
}

static inline void erl_nif_env_partial_init_from_globalcontext(ErlNifEnv *env, GlobalContext *global)
{
env->global = global;
env->heap.root = NULL;
env->heap.heap_start = NULL;
env->heap.heap_ptr = NULL;
env->heap.heap_end = NULL;
env->stack_pointer = NULL;
env->x[0] = term_nil();
env->x[1] = term_nil();
}

#ifdef __cplusplus
}
#endif

#endif // _ERL_NIF_PRIV_H_
Loading

0 comments on commit 3d13d60

Please sign in to comment.