3333 {atomvmlib , $l , " atomvmlib" , string , " Path to atomvmlib.avm (default: auto discovered)" },
3434 {output , $o , " output" , string , " Output executable name (default: app name)" },
3535 {objcopy , $c , " objcopy" , string , " Path to objcopy tool (auto-detected if not specified)" },
36- {start , $s , " start" , atom , " Start module (default: app name)" }
36+ {start , $s , " start" , atom , " Start module (default: app name)" },
37+ {aot , $a , " aot" , string ,
38+ " AOT-compile beams to native code for the given JIT target (e.g. aarch64)" },
39+ {jit_beams , $j , " jit_beams" , string ,
40+ " Path to the AtomVM JIT compiler beams (required with --aot)" },
41+ {embed , $e , " embed" , string ,
42+ " AVM embedding mechanism: trailer (default) or objcopy" }
3743]).
3844
3945-define (DEFAULT_OPTS , #{
4046 atomvm_binary => undefined ,
4147 atomvmlib => undefined ,
4248 output => undefined ,
4349 objcopy => undefined ,
44- start => undefined
50+ start => undefined ,
51+ aot => undefined ,
52+ jit_beams => undefined ,
53+ embed => " trailer"
4554}).
4655
56+ -define (ATOMVM_TRAILER_MAGIC , <<" ATOMVMv1" >>).
57+
4758% %
4859% % provider implementation
4960% %
@@ -88,7 +99,6 @@ do(State) ->
8899 TargetAVM = filename :join (DirName , Name ++ " .avm" ),
89100 AtomVMLib = get_atomvmlib_path (Opts ),
90101 AtomVMBinary = get_atomvm_binary (Opts ),
91- ObjCopyTool = get_objcopy_tool (Opts ),
92102 OutputExe = get_output_path (Opts , DirName , Name ),
93103
94104 % Get start module (default to app name)
@@ -99,13 +109,24 @@ do(State) ->
99109 end ,
100110
101111 % Create packed AVM with atomvmlib
102- PackedAVM = create_packed_avm (TargetAVM , AtomVMLib , DirName , Name , StartModule ),
112+ PackedAVM0 = create_packed_avm (TargetAVM , AtomVMLib , DirName , Name , StartModule ),
113+
114+ % Optionally AOT-compile the packed beams to native code
115+ PackedAVM = maybe_aot_precompile (PackedAVM0 , Opts , DirName , Name , StartModule ),
103116
104117 % Copy AtomVM binary
105118 ok = copy_atomvm_binary (AtomVMBinary , OutputExe ),
106119
107120 % Embed AVM into executable
108- ok = embed_avm (ObjCopyTool , OutputExe , PackedAVM ),
121+ case maps :get (embed , Opts ) of
122+ " objcopy" ->
123+ ObjCopyTool = get_objcopy_tool (Opts ),
124+ ok = embed_avm (ObjCopyTool , OutputExe , PackedAVM );
125+ " trailer" ->
126+ ok = embed_avm_trailer (OutputExe , PackedAVM );
127+ OtherEmbed ->
128+ throw ({unknown_embed_mechanism , OtherEmbed })
129+ end ,
109130
110131 % Make executable
111132 ok = make_executable (OutputExe ),
@@ -335,7 +356,7 @@ create_packed_avm(TargetAVM, AtomVMLib, DirName, Name, StartModule) ->
335356 % Read both AVM files
336357 case {filelib :is_file (AtomVMLib ), filelib :is_file (TargetAVM )} of
337358 {true , true } ->
338- packbeam_api :create (PackedAVM , [AtomVMLib , TargetAVM ], #{start => StartModule }),
359+ packbeam_api :create (PackedAVM , [AtomVMLib , TargetAVM ], #{start_module => StartModule }),
339360 rebar_api :info (" Created packed AVM: ~s with start module ~p " , [PackedAVM , StartModule ]),
340361 PackedAVM ;
341362 {false , _ } ->
@@ -344,6 +365,92 @@ create_packed_avm(TargetAVM, AtomVMLib, DirName, Name, StartModule) ->
344365 throw ({file_not_found , TargetAVM })
345366 end .
346367
368+ % % @private
369+ % % AOT-compile every beam in the packed AVM to native code for the given
370+ % % JIT target, using the jit_precompile compiler from the AtomVM sources.
371+ maybe_aot_precompile (PackedAVM , Opts , DirName , Name , StartModule ) ->
372+ case maps :get (aot , Opts ) of
373+ undefined ->
374+ PackedAVM ;
375+ Target ->
376+ JitBeams =
377+ case maps :get (jit_beams , Opts ) of
378+ undefined -> throw (jit_beams_required_with_aot );
379+ Path -> Path
380+ end ,
381+ case filelib :is_file (filename :join (JitBeams , " jit_precompile.beam" )) of
382+ true -> ok ;
383+ false -> throw ({jit_precompile_not_found , JitBeams })
384+ end ,
385+ true = code :add_patha (JitBeams ),
386+ aot_precompile_avm (PackedAVM , Target , DirName , Name , StartModule )
387+ end .
388+
389+ % % @private
390+ aot_precompile_avm (PackedAVM , Target , DirName , Name , StartModule ) ->
391+ Elements = packbeam_api :list (PackedAVM ),
392+ BeamNames = [
393+ packbeam_api :get_element_name (E )
394+ || E <- Elements , packbeam_api :is_beam (E )
395+ ],
396+ WorkDir = filename :join (DirName , Name ++ " _aot" ),
397+ InDir = filename :join (WorkDir , " in" ),
398+ OutDir = filename :join (WorkDir , " out" ),
399+ ok = filelib :ensure_dir (filename :join (InDir , " dummy" )),
400+ ok = filelib :ensure_dir (filename :join (OutDir , " dummy" )),
401+ rebar_api :info (" AOT-compiling ~p beams for ~s " , [length (BeamNames ), Target ]),
402+ lists :foreach (
403+ fun (E ) ->
404+ case packbeam_api :is_beam (E ) of
405+ true ->
406+ ElementName = packbeam_api :get_element_name (E ),
407+ Data = packbeam_api :get_element_data (E ),
408+ case has_native_code (Data ) of
409+ true ->
410+ % already AOT-compiled (e.g. a precompiled
411+ % atomvmlib): keep as is
412+ ok = file :write_file (filename :join (OutDir , ElementName ), Data );
413+ false ->
414+ BeamPath = filename :join (InDir , ElementName ),
415+ ok = file :write_file (BeamPath , Data ),
416+ ok = jit_precompile :compile (Target , OutDir , false , BeamPath )
417+ end ;
418+ false ->
419+ ok
420+ end
421+ end ,
422+ Elements
423+ ),
424+ % Non-beam elements (e.g. priv files) keep their names by going through
425+ % an AVM stripped of its beams
426+ NonBeamsAVM = filename :join (WorkDir , " non_beams.avm" ),
427+ packbeam_api :delete (NonBeamsAVM , PackedAVM , BeamNames ),
428+ AotAVM = filename :join (DirName , Name ++ " _aot.avm" ),
429+ AotBeams = [filename :join (OutDir , BeamName ) || BeamName <- BeamNames ],
430+ packbeam_api :create (AotAVM , AotBeams ++ [NonBeamsAVM ], #{start_module => StartModule }),
431+ rebar_api :info (" Created AOT packed AVM: ~s " , [AotAVM ]),
432+ AotAVM .
433+
434+ % % @private
435+ has_native_code (BeamData ) ->
436+ case beam_lib :chunks (BeamData , [" avmN" ], [allow_missing_chunks ]) of
437+ {ok , {_Module , [{" avmN" , Chunk }]}} -> Chunk =/= missing_chunk ;
438+ _ -> false
439+ end .
440+
441+ % % @private
442+ % % Append the AVM and a 16-byte trailer (8-byte little-endian size +
443+ % % "ATOMVMv1" magic) to the executable. Unlike objcopy, this requires no
444+ % % external tooling and is tolerated by the macOS code signature checks.
445+ embed_avm_trailer (Executable , AVMFile ) ->
446+ {ok , AVMData } = file :read_file (AVMFile ),
447+ Size = byte_size (AVMData ),
448+ Trailer = <<Size :64 /little , (? ATOMVM_TRAILER_MAGIC )/binary >>,
449+ rebar_api :debug (" Appending ~p byte AVM with trailer to ~s " , [Size , Executable ]),
450+ {ok , Fd } = file :open (Executable , [append , binary , raw ]),
451+ ok = file :write (Fd , [AVMData , Trailer ]),
452+ ok = file :close (Fd ).
453+
347454% % @private
348455copy_atomvm_binary (Source , Dest ) ->
349456 rebar_api :debug (" Copying AtomVM binary from ~s to ~s " , [Source , Dest ]),
0 commit comments