Skip to content

Commit 6a4bab2

Browse files
committed
Run embedded avm on Linux and macOS
Most platforms run embedded avm in the flash. Implement a similar mechanism on generic_unix platform, to enable an escriptize-like experience. Signed-off-by: Paul Guyot <[email protected]>
1 parent 82b4e7c commit 6a4bab2

File tree

9 files changed

+299
-79
lines changed

9 files changed

+299
-79
lines changed

doc/src/atomvm-tooling.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,34 @@ Return value: ok
7171

7272
For instructions about how to install AtomVM on the `generic_unix` platform, see the [Getting Started Guide](./getting-started-guide.md#getting-started-on-the-generic-unix-platform)
7373

74+
#### Building standalone binaries (macOS and Linux)
75+
76+
Using [`atomvm_rebar3_plugin`](https://atomvm.github.io/atomvm_rebar3_plugin), it is possible to generate a standalone binary where your application is bundled with AtomVM virtual machine. The binary can be copied to another host and would work without any specific installation. Please note that AtomVM on generic unix currently depends on zlib and mbedtls as shared libraries.
77+
78+
To proceed, you need to define the main module that should export a `main/1` function. This function is called with parameters like regular Erlang `escript` applications. This is done by adding these two lines to your `rebar.config` file:
79+
80+
```
81+
{plugins, [atomvm_rebar3_plugin]}.
82+
{atomvm_rebar3_plugin, [{packbeam, [{start, main_module}]}]}.
83+
```
84+
85+
where `main_module` is the name of the module that exports `main/1`. Alternatively, `main_module` can be defined with `escript_name` or `escript_main_app` options.
86+
87+
Then you can run:
88+
```shell
89+
$ rebar3 atomvm escriptize
90+
...
91+
===> Created packed AVM: ... main_module_packed.avm with start module main_module
92+
===> Created standalone executable: ... main_module/_build/default/bin/main_module
93+
```
94+
95+
It is possible to build this standalone binary with a specific build of AtomVM, for example including a particular `nif`.
96+
97+
```{seealso}
98+
See the [`atomvm_rebar3_plugin`](https://atomvm.github.io/atomvm_rebar3_plugin) page for more detailed instructions
99+
about how to use the `escriptize` target.
100+
```
101+
74102
### Flashing your application with `rebar3`
75103

76104
The [`atomvm_rebar3_plugin`](https://atomvm.github.io/atomvm_rebar3_plugin) supports flash targets for various device types. These targets are described in more detail below.

libs/estdlib/src/init.erl

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,36 @@
3939
%% @doc Entry point.
4040
%% @end
4141
%%-----------------------------------------------------------------------------
42-
-spec boot([binary() | atom()]) -> any().
42+
-spec boot([binary() | atom() | string()]) -> any().
43+
boot([<<"-s">>, escript, <<"--">>, _Filename | Args]) ->
44+
% Escript mode: check for start beam and main/1 function before starting kernel
45+
case atomvm:get_start_beam(escript) of
46+
{ok, ModuleNameBinary} ->
47+
% Remove .beam suffix if present (check last 5 bytes)
48+
Size = byte_size(ModuleNameBinary),
49+
ModuleName =
50+
if
51+
Size > 5 ->
52+
case binary:part(ModuleNameBinary, Size - 5, 5) of
53+
<<".beam">> -> binary:part(ModuleNameBinary, 0, Size - 5);
54+
_ -> ModuleNameBinary
55+
end;
56+
true ->
57+
ModuleNameBinary
58+
end,
59+
Module = binary_to_atom(ModuleName, utf8),
60+
case erlang:function_exported(Module, main, 1) of
61+
true ->
62+
{ok, _KernelPid} = kernel:start(boot, []),
63+
Module:main(Args);
64+
false ->
65+
io:format("Function ~s:main/1 is not exported~n", [Module]),
66+
error
67+
end;
68+
_ ->
69+
io:format("start_beam not found~n"),
70+
error
71+
end;
4372
boot([<<"-s">>, StartupModule]) when is_atom(StartupModule) ->
4473
% Until we have boot scripts, we just start kernel application.
4574
{ok, _KernelPid} = kernel:start(boot, []),

src/libAtomVM/globalcontext.c

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "context.h"
3030
#include "defaultatoms.h"
3131
#include "erl_nif_priv.h"
32+
#include "interop.h"
3233
#include "list.h"
3334
#include "mailbox.h"
3435
#include "posix_nifs.h"
@@ -720,21 +721,62 @@ Module *globalcontext_get_module_by_index(GlobalContext *global, int index)
720721
return result;
721722
}
722723

723-
run_result_t globalcontext_run(GlobalContext *glb, Module *startup_module, FILE *out_f)
724+
run_result_t globalcontext_run(GlobalContext *glb, Module *startup_module, FILE *out_f, int argc, char **argv)
724725
{
725726
Context *ctx = context_new(glb);
726727
ctx->leader = 1;
727728
Module *init_module = globalcontext_get_module(glb, INIT_ATOM_INDEX);
728729
if (IS_NULL_PTR(init_module)) {
730+
if (IS_NULL_PTR(startup_module)) {
731+
fprintf(stderr, "Unable to locate entrypoint.\n");
732+
return RUN_NO_ENTRY_POINT;
733+
}
729734
context_execute_loop(ctx, startup_module, "start", 0);
730735
} else {
731-
if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(2) + LIST_SIZE(2, 0)) != MEMORY_GC_OK)) {
732-
fprintf(stderr, "Unable to allocate arguments.\n");
733-
return RUN_MEMORY_FAILURE;
736+
// Build boot arguments based on whether we're in embedded mode
737+
if (argc > 0 && argv != NULL) {
738+
// Embedded mode: ["-s", escript, "--" | argv_strings]
739+
// Calculate heap size needed
740+
size_t heap_needed = term_binary_heap_size(2) + // "-s"
741+
term_binary_heap_size(2) + // "--"
742+
LIST_SIZE(3, 0); // list for ["-s", escript, "--"]
743+
744+
// Calculate space for argv strings
745+
for (int i = 0; i < argc; i++) {
746+
size_t arg_len = strlen(argv[i]);
747+
heap_needed += CONS_SIZE * arg_len + LIST_SIZE(1, 0);
748+
}
749+
750+
if (UNLIKELY(memory_ensure_free(ctx, heap_needed) != MEMORY_GC_OK)) {
751+
fprintf(stderr, "Unable to allocate arguments.\n");
752+
return RUN_MEMORY_FAILURE;
753+
}
754+
755+
// Build the argv list in reverse: [argvn, ..., argv0, "--", escript, "-s"]
756+
term args_list = term_nil();
757+
for (int i = argc - 1; i >= 0; i--) {
758+
term arg = interop_chars_to_list(argv[i], strlen(argv[i]), &ctx->heap);
759+
args_list = term_list_prepend(arg, args_list, &ctx->heap);
760+
}
761+
762+
term separator = term_from_literal_binary("--", strlen("--"), &ctx->heap, glb);
763+
args_list = term_list_prepend(separator, args_list, &ctx->heap);
764+
765+
term escript_atom = globalcontext_make_atom(glb, ATOM_STR("\x7", "escript"));
766+
args_list = term_list_prepend(escript_atom, args_list, &ctx->heap);
767+
768+
term s_opt = term_from_literal_binary("-s", strlen("-s"), &ctx->heap, glb);
769+
ctx->x[0] = term_list_prepend(s_opt, args_list, &ctx->heap);
770+
} else {
771+
// Non-embedded mode: ["-s", startup_module]
772+
if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(2) + LIST_SIZE(2, 0)) != MEMORY_GC_OK)) {
773+
fprintf(stderr, "Unable to allocate arguments.\n");
774+
return RUN_MEMORY_FAILURE;
775+
}
776+
term s_opt = term_from_literal_binary("-s", strlen("-s"), &ctx->heap, glb);
777+
term list = term_list_prepend(module_get_name(startup_module), term_nil(), &ctx->heap);
778+
ctx->x[0] = term_list_prepend(s_opt, list, &ctx->heap);
734779
}
735-
term s_opt = term_from_literal_binary("-s", strlen("-s"), &ctx->heap, glb);
736-
term list = term_list_prepend(module_get_name(startup_module), term_nil(), &ctx->heap);
737-
ctx->x[0] = term_list_prepend(s_opt, list, &ctx->heap);
738780

739781
context_execute_loop(ctx, init_module, "boot", 1);
740782
}

src/libAtomVM/globalcontext.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ typedef enum run_result_t
9595
RUN_SUCCESS = 0,
9696
RUN_MEMORY_FAILURE = 1,
9797
RUN_RESULT_NOT_OK = 2,
98+
RUN_NO_ENTRY_POINT = 3,
9899
} run_result_t;
99100

100101
struct GlobalContext
@@ -530,9 +531,11 @@ Module *globalcontext_load_module_from_avm(GlobalContext *global, const char *mo
530531
* @param global the global context
531532
* @param start_module the start module
532533
* @param out_f file to print the result to, or NULL
534+
* @param argc number of command-line arguments (0 for non-embedded mode)
535+
* @param argv command-line arguments (NULL for non-embedded mode)
533536
* @returns RUN_SUCCESS or an error code
534537
*/
535-
run_result_t globalcontext_run(GlobalContext *global, Module *start_module, FILE *out_f);
538+
run_result_t globalcontext_run(GlobalContext *global, Module *start_module, FILE *out_f, int argc, char **argv);
536539

537540
#ifndef __cplusplus
538541
static inline uint64_t globalcontext_get_ref_ticks(GlobalContext *global)

src/platforms/emscripten/src/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ static int start(void)
9696
return EXIT_FAILURE;
9797
}
9898

99-
run_result_t ret_value = globalcontext_run(global, main_module, stdout);
99+
run_result_t ret_value = globalcontext_run(global, main_module, stdout, 0, NULL);
100100

101101
int status;
102102
if (ret_value == RUN_SUCCESS) {

src/platforms/esp32/main/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ void app_main()
123123
ESP_LOGI(TAG, "Starting %s...", startup_module_name);
124124
fprintf(stdout, "---\n");
125125

126-
run_result_t result = globalcontext_run(glb, mod, stdout);
126+
run_result_t result = globalcontext_run(glb, mod, stdout, 0, NULL);
127127

128128
bool reboot_on_not_ok =
129129
#if defined(CONFIG_REBOOT_ON_NOT_OK)

0 commit comments

Comments
 (0)