Skip to content

Commit ea45bf0

Browse files
committed
Implement resources as references
Following OTP, make resources appear as references to Erlang code. Also following OTP, make resources comparable and serializable, with the same properties. For a resource to be properly unserialized, it must still exist in the VM. Also fix a bug with reference ticks where the top 32 bits were incorrectly handled on 32 bits platforms, yielding collisions of `make_ref/0`. Signed-off-by: Paul Guyot <[email protected]>
1 parent b34c943 commit ea45bf0

File tree

17 files changed

+489
-96
lines changed

17 files changed

+489
-96
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8181
- `binary_to_integer/1` no longer accepts binaries such as `<<"0xFF">>` or `<<" 123">>`
8282
- `binary_to_integer` and `list_to_integer` do not raise anymore `overflow` error, they raise
8383
instead `badarg`.
84+
- Resources are now references instead of empty binaries.
8485

8586
### Fixed
8687

@@ -95,6 +96,7 @@ instead `badarg`.
9596
- Added missing support for supervisor `one_for_all` strategy.
9697
- Supervisor now honors period and intensity options.
9798
- Fix supervisor crash if a `one_for_one` child fails to restart.
99+
- Fix collision in references created with `make_ref/0` on 32 bits platforms.
98100

99101
## [0.6.7] - Unreleased
100102

doc/src/differences-with-beam.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,6 @@ locking the VM.
217217

218218
Ports are also executed by the schedulers and should return quickly.
219219

220-
### Resources
221-
222-
AtomVM supports resources but for historical reasons these appear as zero-length binaries as they
223-
used to before OTP20, and not as references as they currently do with recent versions of the BEAM.
224-
This has some consequences on matching and serialization.
225-
226220
### BEAM file compatibility
227221

228222
AtomVM can run BEAM files generated by `erlc` compiler from OTP21 to the latest master version,

src/libAtomVM/context.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ bool context_get_process_info(Context *ctx, term *out, size_t *term_size, term a
550550
if (monitor->monitor_type == CONTEXT_MONITOR_MONITORED_LOCAL) {
551551
ret_size += CONS_SIZE;
552552
} else if (monitor->monitor_type == CONTEXT_MONITOR_RESOURCE) {
553-
ret_size += CONS_SIZE + TERM_BOXED_RESOURCE_SIZE;
553+
ret_size += CONS_SIZE + TERM_BOXED_REFERENCE_RESOURCE_SIZE;
554554
}
555555
}
556556
break;

src/libAtomVM/dist_nifs.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ static term nif_erlang_setnode_3(Context *ctx, int argc, term argv[])
349349
}
350350
synclist_unlock(&ctx->global->dist_connections);
351351

352-
if (UNLIKELY(memory_ensure_free_opt(ctx, TERM_BOXED_RESOURCE_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
352+
if (UNLIKELY(memory_ensure_free_opt(ctx, TERM_BOXED_REFERENCE_RESOURCE_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
353353
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
354354
}
355355
term obj = term_from_resource(conn_obj, &ctx->heap);
@@ -705,7 +705,7 @@ static term dist_get_net_kernel_and_create_connection(struct DistConnection **co
705705

706706
static void dist_net_kernel_send_connect(term net_kernel_proc, struct DistConnection *new_conn_obj, int node_atom_index, Context *ctx)
707707
{
708-
BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(3) + TERM_BOXED_RESOURCE_SIZE, heap)
708+
BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(3) + TERM_BOXED_REFERENCE_RESOURCE_SIZE, heap)
709709
term autoconnect_message = term_alloc_tuple(3, &heap);
710710
term_put_tuple_element(autoconnect_message, 0, CONNECT_ATOM);
711711
term_put_tuple_element(autoconnect_message, 1, term_from_atom_index(node_atom_index));

src/libAtomVM/externalterm.c

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "defaultatoms.h"
3333
#include "memory.h"
3434
#include "module.h"
35+
#include "resources.h"
3536
#include "term.h"
3637
#include "unicode.h"
3738
#include "utils.h"
@@ -488,20 +489,38 @@ static int serialize_term(uint8_t *buf, term t, GlobalContext *glb)
488489
buf[0] = NEWER_REFERENCE_EXT;
489490
}
490491
size_t k = 1;
491-
uint32_t len = 2;
492+
uint32_t len;
493+
if (term_is_resource_reference(t)) {
494+
len = 4;
495+
} else {
496+
len = 2;
497+
}
492498
if (!IS_NULL_PTR(buf)) {
493499
WRITE_16_UNALIGNED(buf + k, len);
494500
}
495501
k += 2;
496502
term node_name = glb->node_name;
497503
uint32_t creation = node_name == NONODE_AT_NOHOST_ATOM ? 0 : glb->creation;
498504
k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, node_name, glb);
499-
if (!IS_NULL_PTR(buf)) {
500-
uint64_t ticks = term_to_ref_ticks(t);
501-
WRITE_32_UNALIGNED(buf + k, creation);
502-
WRITE_64_UNALIGNED(buf + k + 4, ticks);
505+
if (term_is_resource_reference(t)) {
506+
if (!IS_NULL_PTR(buf)) {
507+
WRITE_32_UNALIGNED(buf + k, creation);
508+
struct RefcBinary *refc_binary = term_resource_refc_binary_ptr(t);
509+
struct ResourceType *resource_type = refc_binary->resource_type;
510+
void *resource = refc_binary->data;
511+
uint64_t serialize_ref = resource_serialize(resource, resource_type);
512+
WRITE_64_UNALIGNED(buf + k + 4, ((uintptr_t) resource_type));
513+
WRITE_64_UNALIGNED(buf + k + 12, ((uintptr_t) serialize_ref));
514+
}
515+
return k + 20;
516+
} else {
517+
if (!IS_NULL_PTR(buf)) {
518+
uint64_t ticks = term_to_ref_ticks(t);
519+
WRITE_32_UNALIGNED(buf + k, creation);
520+
WRITE_64_UNALIGNED(buf + k + 4, ticks);
521+
}
522+
return k + 12;
503523
}
504-
return k + 12;
505524
} else if (term_is_external_reference(t)) {
506525
if (!IS_NULL_PTR(buf)) {
507526
buf[0] = NEWER_REFERENCE_EXT;
@@ -882,6 +901,11 @@ static term parse_external_terms(const uint8_t *external_term_buf, size_t *eterm
882901
if (len == 2 && node == this_node && creation == this_creation) {
883902
uint64_t ticks = ((uint64_t) data[0]) << 32 | data[1];
884903
return term_from_ref_ticks(ticks, heap);
904+
} else if (len == 4 && node == this_node && creation == this_creation) {
905+
// This is a resource
906+
uint64_t resource_type_ptr = ((uint64_t) data[0]) << 32 | data[1];
907+
uint64_t resource_serialize_ref = ((uint64_t) data[2]) << 32 | data[3];
908+
return term_from_resource_type_and_serialize_ref(resource_type_ptr, resource_serialize_ref, heap, glb);
885909
} else {
886910
return term_make_external_reference(node, len, data, creation, heap);
887911
}
@@ -1294,8 +1318,12 @@ static int calculate_heap_usage(const uint8_t *external_term_buf, size_t remaini
12941318
}
12951319
if (external_term_buf[3] == SMALL_ATOM_UTF8_EXT) {
12961320
// Check if it's non-distributed node, in which case it's always a local ref
1297-
if (len == 2 && external_term_buf[4] == strlen("nonode@nohost") && memcmp(external_term_buf + 5, "nonode@nohost", strlen("nonode@nohost")) == 0) {
1298-
heap_size = REF_SIZE;
1321+
if (external_term_buf[4] == strlen("nonode@nohost") && memcmp(external_term_buf + 5, "nonode@nohost", strlen("nonode@nohost")) == 0) {
1322+
if (len == 2) {
1323+
heap_size = REF_SIZE;
1324+
} else if (len == 4) {
1325+
heap_size = TERM_BOXED_REFERENCE_RESOURCE_SIZE;
1326+
}
12991327
}
13001328
// See above for pids
13011329
} else if (UNLIKELY(external_term_buf[3] != ATOM_EXT)) {

src/libAtomVM/memory.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,11 @@ static void memory_scan_and_copy(HeapFragment *old_fragment, term *mem_start, co
616616

617617
case TERM_BOXED_REF:
618618
TRACE("- Found ref.\n");
619+
term ref = ((term) ptr) | TERM_PRIMARY_BOXED;
620+
if (term_is_resource_reference(ref)) {
621+
*mso_list = term_list_init_prepend(ptr + REFERENCE_RESOURCE_CONS_OFFSET, ref, *mso_list);
622+
refc_binary_increment_refcount((struct RefcBinary *) term_resource_refc_binary_ptr(ref));
623+
}
619624
break;
620625

621626
case TERM_BOXED_EXTERNAL_PID:
@@ -953,6 +958,18 @@ void memory_sweep_mso_list(term mso_list, GlobalContext *global, bool from_task)
953958
refc_binary_decrement_refcount(term_refc_binary_ptr(h), global);
954959
#ifdef AVM_TASK_DRIVER_ENABLED
955960
}
961+
#endif
962+
}
963+
if (term_is_resource_reference(h)) {
964+
// unreferenced binary; decrement reference count
965+
#ifdef AVM_TASK_DRIVER_ENABLED
966+
if (from_task) {
967+
globalcontext_refc_decrement_refcount_from_task(global, term_resource_refc_binary_ptr(h));
968+
} else {
969+
#endif
970+
refc_binary_decrement_refcount(term_resource_refc_binary_ptr(h), global);
971+
#ifdef AVM_TASK_DRIVER_ENABLED
972+
}
956973
#endif
957974
}
958975
l = term_get_list_tail(l);

src/libAtomVM/otp_socket.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ static const AtomStringIntPair otp_socket_setopt_level_table[] = {
245245

246246
static ErlNifResourceType *socket_resource_type;
247247

248-
#define SOCKET_MAKE_SELECT_NOTIFICATION_SIZE (TUPLE_SIZE(4) + REF_SIZE + TUPLE_SIZE(2) + REF_SIZE + TERM_BOXED_RESOURCE_SIZE)
248+
#define SOCKET_MAKE_SELECT_NOTIFICATION_SIZE (TUPLE_SIZE(4) + REF_SIZE + TUPLE_SIZE(2) + REF_SIZE + TERM_BOXED_REFERENCE_RESOURCE_SIZE)
249249
static term socket_make_select_notification(struct SocketResource *rsrc_obj, Heap *heap);
250250

251251
//
@@ -632,7 +632,7 @@ static term nif_socket_open(Context *ctx, int argc, term argv[])
632632
#endif
633633
rsrc_obj->buf_size = DEFAULT_BUFFER_SIZE;
634634

635-
if (UNLIKELY(memory_ensure_free(ctx, TERM_BOXED_RESOURCE_SIZE) != MEMORY_GC_OK)) {
635+
if (UNLIKELY(memory_ensure_free(ctx, TERM_BOXED_REFERENCE_RESOURCE_SIZE) != MEMORY_GC_OK)) {
636636
AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__);
637637
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
638638
}
@@ -675,7 +675,7 @@ bool term_is_otp_socket(term socket_term)
675675
{
676676
bool ret = term_is_tuple(socket_term)
677677
&& term_get_tuple_arity(socket_term) == 2
678-
&& term_is_binary(term_get_tuple_element(socket_term, 0))
678+
&& term_is_resource_reference(term_get_tuple_element(socket_term, 0))
679679
&& term_is_local_reference(term_get_tuple_element(socket_term, 1));
680680

681681
TRACE("term is a socket: %i\n", ret);
@@ -1929,7 +1929,7 @@ static term nif_socket_accept(Context *ctx, int argc, term argv[])
19291929
SMP_RWLOCK_UNLOCK(rsrc_obj->socket_lock);
19301930
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
19311931
}
1932-
size_t requested_size = TERM_BOXED_RESOURCE_SIZE + TUPLE_SIZE(2) + TUPLE_SIZE(2) + REF_SIZE;
1932+
size_t requested_size = TERM_BOXED_REFERENCE_RESOURCE_SIZE + TUPLE_SIZE(2) + TUPLE_SIZE(2) + REF_SIZE;
19331933
if (UNLIKELY(memory_ensure_free_with_roots(ctx, requested_size, 1, argv, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
19341934
AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__);
19351935
LWIP_END();

src/libAtomVM/otp_ssl.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ static term nif_ssl_entropy_init(Context *ctx, int argc, term argv[])
226226
UNUSED(argc);
227227
UNUSED(argv);
228228

229-
if (UNLIKELY(memory_ensure_free(ctx, TERM_BOXED_RESOURCE_SIZE) != MEMORY_GC_OK)) {
229+
if (UNLIKELY(memory_ensure_free(ctx, TERM_BOXED_REFERENCE_RESOURCE_SIZE) != MEMORY_GC_OK)) {
230230
AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__);
231231
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
232232
}
@@ -249,7 +249,7 @@ static term nif_ssl_ctr_drbg_init(Context *ctx, int argc, term argv[])
249249
UNUSED(argc);
250250
UNUSED(argv);
251251

252-
if (UNLIKELY(memory_ensure_free(ctx, TERM_BOXED_RESOURCE_SIZE) != MEMORY_GC_OK)) {
252+
if (UNLIKELY(memory_ensure_free(ctx, TERM_BOXED_REFERENCE_RESOURCE_SIZE) != MEMORY_GC_OK)) {
253253
AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__);
254254
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
255255
}
@@ -301,7 +301,7 @@ static term nif_ssl_init(Context *ctx, int argc, term argv[])
301301
UNUSED(argc);
302302
UNUSED(argv);
303303

304-
if (UNLIKELY(memory_ensure_free(ctx, TERM_BOXED_RESOURCE_SIZE) != MEMORY_GC_OK)) {
304+
if (UNLIKELY(memory_ensure_free(ctx, TERM_BOXED_REFERENCE_RESOURCE_SIZE) != MEMORY_GC_OK)) {
305305
AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__);
306306
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
307307
}
@@ -354,7 +354,7 @@ static term nif_ssl_config_init(Context *ctx, int argc, term argv[])
354354
UNUSED(argc);
355355
UNUSED(argv);
356356

357-
if (UNLIKELY(memory_ensure_free(ctx, TERM_BOXED_RESOURCE_SIZE) != MEMORY_GC_OK)) {
357+
if (UNLIKELY(memory_ensure_free(ctx, TERM_BOXED_REFERENCE_RESOURCE_SIZE) != MEMORY_GC_OK)) {
358358
AVM_LOGW(TAG, "Failed to allocate memory: %s:%i.", __FILE__, __LINE__);
359359
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
360360
}

src/libAtomVM/posix_nifs.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ static term nif_atomvm_posix_open(Context *ctx, int argc, term argv[])
348348
term_put_tuple_element(result, 0, ERROR_ATOM);
349349
term_put_tuple_element(result, 1, posix_errno_to_term(errno, glb));
350350
} else {
351-
if (UNLIKELY(memory_ensure_free_opt(ctx, TUPLE_SIZE(2) + TERM_BOXED_RESOURCE_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
351+
if (UNLIKELY(memory_ensure_free_opt(ctx, TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_RESOURCE_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
352352
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
353353
}
354354
term obj = make_posix_fd_resource(ctx, fd);
@@ -672,7 +672,7 @@ static term nif_atomvm_subprocess(Context *ctx, int argc, term argv[])
672672
free_string_list(args);
673673
free_string_list(envp_array);
674674

675-
if (UNLIKELY(memory_ensure_free_opt(ctx, TUPLE_SIZE(3) + TERM_BOXED_RESOURCE_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
675+
if (UNLIKELY(memory_ensure_free_opt(ctx, TUPLE_SIZE(3) + TERM_BOXED_REFERENCE_RESOURCE_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
676676
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
677677
}
678678

@@ -846,7 +846,7 @@ static term nif_atomvm_posix_opendir(Context *ctx, int argc, term argv[])
846846
}
847847
dir_obj->dir = dir;
848848
if (UNLIKELY(memory_ensure_free_opt(
849-
ctx, TUPLE_SIZE(2) + TERM_BOXED_RESOURCE_SIZE, MEMORY_CAN_SHRINK)
849+
ctx, TUPLE_SIZE(2) + TERM_BOXED_REFERENCE_RESOURCE_SIZE, MEMORY_CAN_SHRINK)
850850
!= MEMORY_GC_OK)) {
851851
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
852852
}

src/libAtomVM/refc_binary.c

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,24 @@ void refc_binary_increment_refcount(struct RefcBinary *refc)
6666
bool refc_binary_decrement_refcount(struct RefcBinary *refc, struct GlobalContext *global)
6767
{
6868
if (--refc->ref_count == 0) {
69-
if (refc->resource_type && refc->resource_type->down) {
70-
// There may be monitors associated with this resource.
71-
destroy_resource_monitors(refc, global);
72-
// After this point, the resource can no longer be found by
73-
// resource_type_fire_monitor
74-
// However, resource_type_fire_monitor may have incremented ref_count
75-
// to call the monitor handler.
76-
// So we check ref_count again. We're not affected by the ABA problem
77-
// here as the resource cannot (should not) be monitoring while it is
78-
// being destroyed, i.e. no resource monitor will be created now
79-
if (UNLIKELY(refc->ref_count != 0)) {
80-
return false;
69+
if (refc->resource_type) {
70+
if (refc->resource_type->down) {
71+
// There may be monitors associated with this resource.
72+
destroy_resource_monitors(refc, global);
73+
// After this point, the resource can no longer be found by
74+
// resource_type_fire_monitor
75+
// However, resource_type_fire_monitor may have incremented ref_count
76+
// to call the monitor handler.
77+
// So we check ref_count again. We're not affected by the ABA problem
78+
// here as the resource cannot (should not) be monitoring while it is
79+
// being destroyed, i.e. no resource monitor will be created now
80+
if (UNLIKELY(refc->ref_count != 0)) {
81+
return false;
82+
}
8183
}
84+
// Remove the resource from the list of serialized resources
85+
// so it no longer can be unserialized.
86+
resource_unmark_serialized(refc->data, refc->resource_type);
8287
}
8388
synclist_remove(&global->refc_binaries, &refc->head);
8489
refc_binary_destroy(refc, global);

0 commit comments

Comments
 (0)