Skip to content

Commit 5dc2263

Browse files
committed
Merge pull request #1784 from pguyot/w31/serialize-funs
Serialize and unserialize functions 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
2 parents 4a7865e + 2e778a0 commit 5dc2263

File tree

6 files changed

+215
-12
lines changed

6 files changed

+215
-12
lines changed

src/libAtomVM/externalterm.c

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "bitstring.h"
3232
#include "defaultatoms.h"
3333
#include "memory.h"
34+
#include "module.h"
3435
#include "term.h"
3536
#include "unicode.h"
3637
#include "utils.h"
@@ -49,6 +50,7 @@
4950
#define LIST_EXT 108
5051
#define BINARY_EXT 109
5152
#define SMALL_BIG_EXT 110
53+
#define NEW_FUN_EXT 112
5254
#define EXPORT_EXT 113
5355
#define MAP_EXT 116
5456
#define ATOM_UTF8_EXT 118
@@ -362,7 +364,7 @@ static int serialize_term(uint8_t *buf, term t, GlobalContext *glb)
362364
k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, value, glb);
363365
}
364366
return k;
365-
} else if (term_is_function(t)) {
367+
} else if (term_is_external_fun(t)) {
366368
if (!IS_NULL_PTR(buf)) {
367369
buf[0] = EXPORT_EXT;
368370
}
@@ -373,6 +375,50 @@ static int serialize_term(uint8_t *buf, term t, GlobalContext *glb)
373375
k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, mfa, glb);
374376
}
375377
return k;
378+
} else if (term_is_function(t)) {
379+
const term *boxed_value = term_to_const_term_ptr(t);
380+
size_t num_free = (((uintptr_t) boxed_value[0]) >> 6) - 2;
381+
// TODO: old_uniq is marked deprecated in OTP source likely to be removed in OTP29
382+
term module, old_uniq, old_index;
383+
uint32_t arity;
384+
uint32_t index = term_to_int32(boxed_value[2]);
385+
size_t free_index;
386+
if (term_is_atom(boxed_value[1])) {
387+
module = boxed_value[1];
388+
arity = term_to_int32(boxed_value[3]);
389+
old_index = boxed_value[4];
390+
old_uniq = boxed_value[5];
391+
free_index = 6;
392+
num_free -= 3;
393+
} else {
394+
Module *mod = (Module *) boxed_value[1];
395+
module = module_get_name(mod);
396+
uint32_t f_old_index, f_old_uniq;
397+
module_get_fun_arity_old_index_uniq(mod, index, &arity, &f_old_index, &f_old_uniq);
398+
old_uniq = term_from_int(f_old_uniq);
399+
old_index = term_from_int(f_old_index);
400+
free_index = 3;
401+
}
402+
403+
if (!IS_NULL_PTR(buf)) {
404+
buf[0] = NEW_FUN_EXT;
405+
buf[5] = arity - num_free;
406+
bzero(buf + 6, 16);
407+
WRITE_32_UNALIGNED(buf + 22, index);
408+
WRITE_32_UNALIGNED(buf + 26, num_free);
409+
}
410+
size_t k = 1 + 4 + 1 + 16 + 4 + 4;
411+
k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, module, glb);
412+
k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, old_index, glb);
413+
k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, old_uniq, glb);
414+
k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, term_from_local_process_id(0), glb);
415+
for (size_t i = 0; i < num_free; i++) {
416+
k += serialize_term(IS_NULL_PTR(buf) ? NULL : buf + k, boxed_value[free_index + i], glb);
417+
}
418+
if (!IS_NULL_PTR(buf)) {
419+
WRITE_32_UNALIGNED(buf + 1, k - 1);
420+
}
421+
return k;
376422
} else if (term_is_local_pid(t)) {
377423
if (!IS_NULL_PTR(buf)) {
378424
buf[0] = NEW_PID_EXT;
@@ -814,6 +860,60 @@ static term parse_external_terms(const uint8_t *external_term_buf, size_t *eterm
814860
}
815861
}
816862

863+
case NEW_FUN_EXT: {
864+
uint32_t len = READ_32_UNALIGNED(external_term_buf + 1);
865+
uint8_t arity = external_term_buf[5];
866+
uint32_t index = READ_32_UNALIGNED(external_term_buf + 22);
867+
uint32_t num_free = READ_32_UNALIGNED(external_term_buf + 26);
868+
size_t term_size;
869+
size_t offset = 30;
870+
term module = parse_external_terms(external_term_buf + offset, &term_size, copy, heap, glb);
871+
offset += term_size;
872+
term old_index = parse_external_terms(external_term_buf + offset, &term_size, copy, heap, glb);
873+
offset += term_size;
874+
// TODO: old_uniq is marked deprecated in OTP source likely to be removed in OTP29
875+
term old_uniq = parse_external_terms(external_term_buf + offset, &term_size, copy, heap, glb);
876+
offset += term_size;
877+
// skip pid
878+
if (UNLIKELY(calculate_heap_usage(external_term_buf + offset, len - offset + 1, &term_size, copy) == INVALID_TERM_SIZE)) {
879+
return term_invalid_term();
880+
}
881+
offset += term_size;
882+
Module *mod = globalcontext_get_module(glb, term_to_atom_index(module));
883+
if (!IS_NULL_PTR(mod)) {
884+
uint32_t f_arity, f_old_index, f_old_uniq;
885+
module_get_fun_arity_old_index_uniq(mod, index, &f_arity, &f_old_index, &f_old_uniq);
886+
if (UNLIKELY(f_arity != (arity + num_free) || f_old_index != (uint32_t) term_to_int32(old_index) || f_old_uniq != (uint32_t) term_to_int32(old_uniq))) {
887+
mod = NULL;
888+
}
889+
}
890+
size_t size = BOXED_FUN_SIZE + num_free;
891+
if (IS_NULL_PTR(mod)) {
892+
size += 3;
893+
}
894+
term *boxed_func = memory_heap_alloc(heap, size);
895+
boxed_func[0] = ((size - 1) << 6) | TERM_BOXED_FUN;
896+
size_t free_index;
897+
if (IS_NULL_PTR(mod)) {
898+
boxed_func[1] = module;
899+
boxed_func[3] = term_from_int(arity);
900+
boxed_func[4] = old_index;
901+
boxed_func[5] = old_uniq;
902+
free_index = 6;
903+
} else {
904+
boxed_func[1] = (term) mod;
905+
free_index = 3;
906+
}
907+
908+
boxed_func[2] = term_from_int(index);
909+
for (uint32_t i = 0; i < num_free; i++) {
910+
boxed_func[i + free_index] = parse_external_terms(external_term_buf + offset, &term_size, copy, heap, glb);
911+
offset += term_size;
912+
}
913+
*eterm_size = len + 1;
914+
return ((term) boxed_func) | TERM_PRIMARY_BOXED;
915+
}
916+
817917
default:
818918
return term_invalid_term();
819919
}
@@ -1167,6 +1267,47 @@ static int calculate_heap_usage(const uint8_t *external_term_buf, size_t remaini
11671267
return heap_size + u;
11681268
}
11691269

1270+
case NEW_FUN_EXT: {
1271+
if (UNLIKELY(remaining < 30)) {
1272+
return INVALID_TERM_SIZE;
1273+
}
1274+
uint32_t len = READ_32_UNALIGNED(external_term_buf + 1);
1275+
remaining -= 1;
1276+
if (UNLIKELY(remaining < len)) {
1277+
return INVALID_TERM_SIZE;
1278+
}
1279+
uint32_t num_free = READ_32_UNALIGNED(external_term_buf + 26);
1280+
// If module doesn't match or exist, we'll need 3 more for arity, old_index and old_uniq
1281+
size_t heap_size = BOXED_FUN_SIZE + num_free + 3;
1282+
int u;
1283+
if (num_free > 0) {
1284+
remaining -= 29;
1285+
size_t offset = 30;
1286+
size_t term_size;
1287+
// skip module atom, old index, old uniq, pid
1288+
for (int i = 0; i < 4; i++) {
1289+
u = calculate_heap_usage(external_term_buf + offset, remaining, &term_size, copy);
1290+
if (UNLIKELY(u == INVALID_TERM_SIZE)) {
1291+
return INVALID_TERM_SIZE;
1292+
}
1293+
remaining -= term_size;
1294+
offset += term_size;
1295+
}
1296+
// add free values
1297+
for (size_t i = 0; i < num_free; i++) {
1298+
u = calculate_heap_usage(external_term_buf + offset, remaining, &term_size, copy);
1299+
if (UNLIKELY(u == INVALID_TERM_SIZE)) {
1300+
return INVALID_TERM_SIZE;
1301+
}
1302+
heap_size += u;
1303+
remaining -= term_size;
1304+
offset += term_size;
1305+
}
1306+
}
1307+
*eterm_size = 1 + len;
1308+
return heap_size;
1309+
}
1310+
11701311
default:
11711312
return INVALID_TERM_SIZE;
11721313
}

src/libAtomVM/module.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,21 @@ static inline uint32_t module_get_fun_freeze(const Module *this_module, int fun_
348348
return n_freeze;
349349
}
350350

351+
static inline void module_get_fun_arity_old_index_uniq(const Module *this_module, int fun_index, uint32_t *arity, uint32_t *index, uint32_t *uniq)
352+
{
353+
const uint8_t *table_data = (const uint8_t *) this_module->fun_table;
354+
int funs_count = READ_32_UNALIGNED(table_data + 8);
355+
356+
assert(fun_index < funs_count);
357+
358+
// fun atom index
359+
*arity = READ_32_UNALIGNED(table_data + fun_index * 24 + 4 + 12);
360+
// label
361+
*index = READ_32_UNALIGNED(table_data + fun_index * 24 + 12 + 12);
362+
// n_freeze
363+
*uniq = READ_32_UNALIGNED(table_data + fun_index * 24 + 20 + 12);
364+
}
365+
351366
static inline void module_get_fun(const Module *this_module, int fun_index, uint32_t *label, uint32_t *arity, uint32_t *n_freeze)
352367
{
353368
const uint8_t *table_data = (const uint8_t *) this_module->fun_table;

src/libAtomVM/opcodesswitch.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,6 +1398,10 @@ static void destroy_extended_registers(Context *ctx, unsigned int live)
13981398
} \
13991399
} \
14001400
} else { \
1401+
if (term_is_atom(boxed_value[1])) { \
1402+
SET_ERROR(UNDEF_ATOM); \
1403+
HANDLE_ERROR(); \
1404+
} \
14011405
fun_module = (Module *) boxed_value[1]; \
14021406
uint32_t fun_index = term_to_int(index_or_function); \
14031407
uint32_t fun_arity_and_freeze; \

src/libAtomVM/term.c

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -259,13 +259,11 @@ int term_funprint(PrinterFun *fun, term t, const GlobalContext *global)
259259
atom_index_t module_atom_index = term_to_atom_index(module_name_atom);
260260
size_t module_name_len;
261261
const uint8_t *module_name = atom_table_get_atom_string(global->atom_table, module_atom_index, &module_name_len);
262-
263-
// this is not the same fun_index used on the BEAM, but it should be fine
264-
uint32_t fun_index = boxed_value[2];
265-
266-
// TODO: last component is a uniq, we are temporarly using the memory address
267-
int ret = fun->print(fun, "#Fun<%.*s.%" PRIu32 ".%" PRIuPTR ">", (int) module_name_len,
268-
module_name, fun_index, (uintptr_t) fun_module);
262+
uint32_t fun_index = term_to_int32(boxed_value[2]);
263+
uint32_t arity, old_index, old_uniq;
264+
module_get_fun_arity_old_index_uniq(fun_module, fun_index, &arity, &old_index, &old_uniq);
265+
int ret = fun->print(fun, "#Fun<%.*s.%" PRIu32 ".%" PRIu32 ">", (int) module_name_len,
266+
module_name, old_index, old_uniq);
269267
return ret;
270268
}
271269

tests/erlang_tests/test_binary_to_term.erl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ start() ->
167167
97, 251, 97, 252, 97, 253, 97, 254, 97, 255, 98, 0, 0, 1, 0>>
168168
),
169169
ok = test_external_function(),
170+
ok = test_function(),
170171

171172
{32768, 6} = erlang:binary_to_term(<<131, 98, 0, 0, 128, 0, 127>>, [used]),
172173
test_catenate_and_split([foo, bar, 128, {foo, bar}, [a, b, c, {d}]]),
@@ -295,6 +296,50 @@ test_external_function() ->
295296
42 = Fun3(?MODULE, apply, [Fun2, [fun() -> 42 end, []]]),
296297
ok.
297298

299+
test_function() ->
300+
X = id(2),
301+
T = [fun(A) -> A * 2 end, fun(A) -> A * X end],
302+
303+
Bin = erlang:term_to_binary(T),
304+
305+
<<131, ModuleAtom/binary>> = term_to_binary(?MODULE:id(?MODULE)),
306+
ModuleAtomSize = byte_size(ModuleAtom),
307+
308+
<<131, 108, 2:32, Funs/binary>> = Bin,
309+
<<112, Size2:32, Rest1/binary>> = Funs,
310+
% need to use split_binary for OTP 21/22
311+
{Fun2Bin, <<112, Size3:32, Rest2/binary>>} = split_binary(Rest1, Size2 - 4),
312+
{Fun3Bin, <<106>>} = split_binary(Rest2, Size3 - 4),
313+
<<1, MD5:16/binary, Index2:32, 0:32, ModuleAtom:ModuleAtomSize/binary, 97, Index2, 98,
314+
OldUniq:32, Rest3/binary>> = Fun2Bin,
315+
<<1, MD5:16/binary, Index3:32, 1:32, ModuleAtom:ModuleAtomSize/binary, 97, Index3, 98,
316+
OldUniq:32, _Rest4/binary>> = Fun3Bin,
317+
318+
[Fun2, Fun3] = binary_to_term(Bin),
319+
true = is_function(Fun2),
320+
true = is_function(Fun3),
321+
42 = Fun2(21),
322+
42 = Fun3(21),
323+
324+
B1 =
325+
<<131, 112, Size2:32, 1, 0:(16 * 8), Index2:32, 0:32, ModuleAtom:ModuleAtomSize/binary, 97,
326+
(Index2 bxor 42), 98, (OldUniq bxor 42):32, Rest3/binary>>,
327+
Fun4 = binary_to_term(B1),
328+
ok =
329+
try
330+
42 = Fun4(21),
331+
unexpected
332+
catch
333+
error:{badfun, Fun4} ->
334+
% BEAM
335+
ok;
336+
error:undef ->
337+
% AtomVM
338+
ok
339+
end,
340+
B1 = term_to_binary(Fun4),
341+
ok.
342+
298343
get_binary(Id) ->
299344
case Id of
300345
% 'buondì'

tests/erlang_tests/test_fun_to_list.erl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ start() ->
2828

2929
[Id1, LastTok1] = binary:split(Rest1, <<".">>),
3030
[Uniq1, <<"">>] = binary:split(LastTok1, <<">">>),
31-
_ = erlang:binary_to_integer(Id1),
32-
_ = erlang:binary_to_integer(Uniq1),
31+
0 = erlang:binary_to_integer(Id1),
32+
Uniq = erlang:binary_to_integer(Uniq1),
3333

3434
[Id2, LastTok2] = binary:split(Rest2, <<".">>),
3535
[Uniq2, <<"">>] = binary:split(LastTok2, <<">">>),
3636

37-
_ = erlang:binary_to_integer(Id2),
38-
_ = erlang:binary_to_integer(Uniq2),
37+
1 = erlang:binary_to_integer(Id2),
38+
Uniq = erlang:binary_to_integer(Uniq2),
3939

4040
<<"fun erlang:integer_to_list/1">> = ?MODULE:fun_to_bin(
4141
erlang:binary_to_term(erlang:list_to_binary(?MODULE:get_fun_bytes()))

0 commit comments

Comments
 (0)