├── .gitignore ├── LICENSE-2.0.txt ├── Makefile.old ├── NOTICE ├── README.md ├── apps ├── asm │ └── src │ │ ├── asm.app.src │ │ ├── asm_app.erl │ │ ├── asm_genop.erl │ │ ├── asm_irop.erl │ │ ├── asm_module.erl │ │ ├── asm_sup.erl │ │ └── asm_to_ir.erl ├── emuemu │ ├── rebar.config │ └── src │ │ ├── emu.hrl │ │ ├── emu_app.erl │ │ ├── emu_code_server.erl │ │ ├── emu_machine.erl │ │ ├── emu_proc.erl │ │ ├── emu_sup.erl │ │ └── emuemu.app.src └── prototype │ └── src │ ├── prototype.app.src │ ├── prototype_app.erl │ ├── prototype_emu.erl │ └── prototype_load.erl ├── codegen ├── .gitignore ├── Makefile ├── README.md ├── atoms.tab ├── bif.tab ├── copypaste_impl.py.txt ├── create_bif_tab_cpp.py ├── create_bif_tab_h.py ├── create_genop_cpp.py ├── create_genop_h.py ├── create_predef_atoms_cpp.py ├── create_predef_atoms_h.py ├── create_vm_loop.py ├── genop.py ├── genop.tab ├── implemented_ops.tab ├── jump_table.py.txt └── libgenop.py ├── emulator ├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── Makefile ├── TODO.md ├── cmake_linux.sh ├── format.sh ├── include │ ├── .gitignore │ ├── FEATURE.h │ ├── bif │ │ ├── bif_misc.h │ │ └── bif_proc.h │ ├── binary.h │ ├── code.h │ ├── code_index.h │ ├── code_server.h │ ├── defs.h │ ├── dist.h │ ├── error.h │ ├── ext_term.h │ ├── fun.h │ ├── functional.h │ ├── gsl │ │ ├── LICENSE │ │ ├── README.md │ │ ├── array_view.h │ │ ├── fail_fast.h │ │ ├── gsl.h │ │ └── string_view.h │ ├── heap.h │ ├── mailbox.h │ ├── module.h │ ├── process.h │ ├── process_fail.h │ ├── scheduler.h │ ├── struct │ │ ├── array.h │ │ ├── dict.h │ │ ├── list.h │ │ ├── set.h │ │ └── str.h │ ├── term.h │ ├── term_helpers.h │ └── vm.h ├── src │ ├── .gitignore │ ├── beam.h │ ├── beam_loader.cpp │ ├── beam_prepare.cpp │ ├── bif │ │ ├── bif_misc.cpp │ │ └── bif_proc.cpp │ ├── binary.cpp │ ├── code.cpp │ ├── code_index.cpp │ ├── code_server.cpp │ ├── dist.cpp │ ├── ext_term.cpp │ ├── fun.cpp │ ├── gleam_loader.bak │ ├── heap.cpp │ ├── mailbox.cpp │ ├── main.cpp │ ├── miniz │ │ ├── miniz.cpp │ │ └── tinfl.c │ ├── module.cpp │ ├── platf │ │ ├── README.md │ │ ├── gsys_file.cpp │ │ ├── gsys_file.h │ │ ├── gsys_mem.cpp │ │ ├── gsys_mem.h │ │ ├── gsys_stdlib.cpp │ │ └── gsys_stdlib.h │ ├── pointer.h │ ├── process.cpp │ ├── process_ctx.cpp │ ├── process_ctx.h │ ├── process_fail.cpp │ ├── reader.h │ ├── scheduler.cpp │ ├── stack.cpp │ ├── stack.h │ ├── term.cpp │ ├── term_helpers.cpp │ ├── term_layout.cpp │ ├── term_layout.h │ ├── term_tag.h │ ├── vm.cpp │ ├── vm_impl.h │ ├── vm_loop.cpp │ ├── vm_loop_ctx.cpp │ ├── vm_loop_ctx.h │ └── wrap.h └── test │ ├── fructose │ ├── AUTHORS.txt │ ├── COPYING │ ├── LICENSE.txt │ ├── double_compare.h │ ├── fructose.h │ ├── test_base.h │ └── test_root.h │ ├── test.cpp │ ├── test.h │ ├── test_code_index.cpp │ ├── test_process.cpp │ └── test_term.cpp ├── rebar.config └── test ├── .gitignore ├── Makefile ├── g_test1.erl ├── g_test2.erl ├── listfuns.erl.txt ├── lists.erl ├── mochijson.erl └── ring.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | erl_crash.dump 13 | .rebar 14 | _rel 15 | _deps 16 | _plugins 17 | _tdeps 18 | logs 19 | _build 20 | rebar.lock 21 | 22 | # IntelliJ 23 | *.iml 24 | .idea 25 | .idea/* 26 | *.user 27 | -------------------------------------------------------------------------------- /Makefile.old: -------------------------------------------------------------------------------- 1 | .PHONY: codegen test_erl 2 | #.PHONY: compile asm 3 | 4 | #compile: 5 | # rebar3 compile 6 | 7 | #asm: codegen test_erl compile 8 | #asm: test_erl 9 | # erl -pa _build/default/lib/*/ebin -s asm_app 10 | 11 | #emu: codegen compile 12 | # erl -pa _build/default/lib/*/ebin -s emu_app 13 | 14 | # Compile test scripts in test/ dir 15 | test_erl: 16 | cd test && ./test.sh && cd .. 17 | 18 | # Run codegen phase 19 | codegen: 20 | cd codegen && make && cd .. 21 | 22 | #prototype: compile 23 | # erl -pa _build/default/lib/*/ebin -s prototype_app 24 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Gluon Virtual Machine for BEAM 2 | Copyright 2015 Dmytro Lytovchenko 3 | 4 | This product includes LGPL licensed FRUCTOSE test harness developed by: 5 | Original author: Andrew Peter Marlow 6 | C++ test harness generator by Chris Main. 7 | Python test harness generator by Brian Neal. 8 | 9 | This product includes "unlicensed" MINIZ library 10 | miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP 11 | reading/writing/appending, PNG writing 12 | Rich Geldreich 13 | 14 | This product includes GSL (Guidelines Support Library) for C++ by Microsoft 15 | Copyright (c) 2015 Microsoft Corporation. All rights reserved. 16 | This code is licensed under the MIT License (MIT). 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## THE NEXT ~~BIG~~ small THING™ 2 | # Gluon Erlang Abstract Machine 3 | 4 | 5 | A configurable and small virtual machine which runs Erlang BEAM bytecode. Many 6 | simplifications have been made to keep code small at the cost of speed. 7 | The goal is to go smaller than several tens of kilobytes (oh well, under 8 | megabyte would be cool). 9 | 10 | Features used in code can be configured at compile time when you want to squeeze 11 | VM into a particularly small platform. 12 | 13 | # The emulator 14 | 15 | C++ implementation of minimalistic BEAM virtual machine. Located in `emulator/`. 16 | Has simple configurable feature settings in `include/g_FEATURES.h` (like 17 | distribution, float, bignum support etc). Note that (temporarily) some #define 18 | are duplicated as constants. 19 | 20 | ## Building 21 | 22 | Requires CMake, Clang (probably will work with GCC too?). 23 | 24 | Run `make` in `emulator/` directory. `CMakeLists.txt` will also work as a project 25 | with QtCreator IDE not to mention that CMake supports plenty of other IDEs (run 26 | `cmake` to see list of supported IDE, see `cmake_linux.sh` to get hint how to create 27 | a project for your IDE). 28 | 29 | # Features (Done) 30 | 31 | * Processes, heaps and stack (no GC yet) 32 | * Reductions and scheduling 33 | * Message passing and infinite receiving (no timers and timed receive yet) 34 | * Some BIFs (ever growing amount) 35 | * Many opcodes, BEAM file loading and code path search 36 | 37 | *TODO* 38 | 39 | * Exceptions 40 | * Process links, monitors 41 | * Simple GC 42 | * Binaries 43 | * Floats maybe? 44 | 45 | # License 46 | 47 | Apache v.2 48 | 49 | Contributions not welcome until the project reaches POC phase (a working prototype). -------------------------------------------------------------------------------- /apps/asm/src/asm.app.src: -------------------------------------------------------------------------------- 1 | {application, 'asm', 2 | [{description, "An OTP application"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {mod, {'asm_app', []}}, 6 | {applications, 7 | [kernel, 8 | stdlib 9 | ]}, 10 | {env,[]}, 11 | {modules, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /apps/asm/src/asm_app.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc asm public API 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module('asm_app'). 7 | 8 | -behaviour(application). 9 | 10 | %% Application callbacks 11 | -export([ start/2 12 | , stop/1 13 | , start/0 14 | ]). 15 | 16 | %%==================================================================== 17 | %% API 18 | %%==================================================================== 19 | 20 | start() -> 21 | %application:start(asm) 22 | asm_to_ir:process("test/g_test1.S"), 23 | asm_to_ir:process("test/lists.S"), 24 | init:stop(). 25 | 26 | start(_StartType, _StartArgs) -> 27 | 'asm_sup':start_link(). 28 | 29 | %%-------------------------------------------------------------------- 30 | stop(_State) -> 31 | ok. 32 | 33 | %%==================================================================== 34 | %% Internal functions 35 | %%==================================================================== 36 | -------------------------------------------------------------------------------- /apps/asm/src/asm_irop.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc Opcodes for gluon 3 | %%% @end 4 | %%%------------------------------------------------------------------- 5 | -module(asm_irop). 6 | 7 | %% API 8 | -export([tag_integer/1 9 | , uint_enc/1 10 | , encode_arg/2, get_test_type/1]). 11 | 12 | -include("../../emuemu/src/emu.hrl"). 13 | 14 | %% Byte tags to mark value sign or origin of value, tag for integer is optional 15 | %% when sign is known. 16 | -define(tag_integer_pos, 255). 17 | -define(tag_integer_neg, 254). 18 | -define(tag_atom, 253). 19 | -define(tag_label, 252). 20 | %-define(tag_mfarity, 251). 21 | -define(tag_register, 250). 22 | -define(tag_stack, 249). 23 | -define(tag_nil, 248). 24 | -define(tag_literal, 247). 25 | -define(tag_fp_register, 246). 26 | -define(tag_list, 245). 27 | 28 | get_test_type(is_lt) -> 0; 29 | get_test_type(is_ge) -> 1; 30 | get_test_type(is_eq) -> 2; 31 | get_test_type(is_ne) -> 3; 32 | get_test_type(is_eq_exact) -> 4; 33 | get_test_type(is_ne_exact) -> 5; 34 | get_test_type(is_integer) -> 6; 35 | get_test_type(is_float) -> 7; 36 | get_test_type(is_number) -> 8; 37 | get_test_type(is_atom) -> 9; 38 | get_test_type(is_pid) -> 10; 39 | get_test_type(is_reference) -> 11; 40 | get_test_type(is_port) -> 12; 41 | get_test_type(is_nil) -> 13; 42 | get_test_type(is_binary) -> 14; 43 | get_test_type(is_list) -> 15; 44 | get_test_type(is_nonempty_list)->16; 45 | get_test_type(is_tuple) -> 17; 46 | get_test_type(is_boolean) -> 18; 47 | get_test_type(is_function) -> 19. 48 | 49 | %% @doc Tagged varlength integer, tag specifies the sign 50 | tag_integer(N) when N < 0 -> <>; 51 | tag_integer(N) -> <>. 52 | 53 | %% @doc Varlength encoding with highest bit set to 1 for continuation and to 0 54 | %% for last segment of 7-bit sequence. Only positive integers. 55 | uint_enc(N) when N >= 0 -> 56 | Tail = integer_enc(N band 127, <<>>, 0), 57 | case N < 128 of 58 | true -> Tail; 59 | false -> 60 | Head = integer_enc(N div 128, <<>>, 1), 61 | <> 62 | end. 63 | 64 | integer_enc(N, Acc, FlagBit) when is_integer(N), N > 127 -> 65 | Part = N band 127, 66 | integer_enc(N div 128, <>, FlagBit); 67 | integer_enc(N, Acc, FlagBit) when is_integer(N) -> 68 | <>. 69 | 70 | atom_ref_enc(A, Mod0) -> asm_module:find_or_create_atom(A, Mod0). 71 | literal_ref_enc(L, Mod0) -> asm_module:find_or_create_literal(L, Mod0). 72 | 73 | encode_atom(A, Mod0) -> 74 | {Index, Mod} = atom_ref_enc(A, Mod0), 75 | {<>, Mod}. 76 | 77 | encode_arg({x, X}, Mod) when is_integer(X), X >= 0 -> 78 | %% TODO: Shorten this for low values of X to 1 byte 79 | {<>, Mod}; 80 | encode_arg({y, Y}, Mod) when is_integer(Y), Y >= 0 -> 81 | {<>, Mod}; 82 | encode_arg({atom, Atom}, Mod0) when is_atom(Atom) -> 83 | encode_atom(Atom, Mod0); 84 | encode_arg(nil, Mod) -> 85 | {<>, Mod}; 86 | encode_arg(Atom, Mod0) when is_atom(Atom) -> 87 | encode_atom(Atom, Mod0); 88 | encode_arg({integer, N}, Mod) -> 89 | {tag_integer(N), Mod}; 90 | encode_arg(N, Mod) when is_integer(N) -> 91 | {tag_integer(N), Mod}; 92 | encode_arg({f, L}, Mod) -> 93 | {<>, Mod}; 94 | %% encode_arg({'char', C}, Dict) -> 95 | %% {encode(?tag_h, C), Dict}; 96 | encode_arg({string, String}, Mod0) -> 97 | encode_arg({literal, String}, Mod0); 98 | encode_arg({extfunc, M, F, A}, Mod0) -> 99 | encode_arg({literal, {M,F,A}}, Mod0); 100 | %% encode_arg({list, List}, Dict0) -> 101 | %% {L, Dict} = encode_list(List, Dict0, []), 102 | %% {[encode(?tag_z, 1), encode(?tag_u, length(List))|L], Dict}; 103 | encode_arg({float, Float}, Dict) when is_float(Float) -> 104 | encode_arg({literal,Float}, Dict); 105 | encode_arg({fr,Fr}, Mod) -> 106 | {<>, Mod}; 107 | encode_arg({field_flags,Flags0}, Mod) -> 108 | Flags = lists:foldl(fun (F, S) -> S bor flag_to_bit(F) end, 0, Flags0), 109 | encode_arg({literal, Flags}, Mod); 110 | %% encode_arg({alloc,List}, Dict) -> 111 | %% encode_alloc_list(List, Dict); 112 | encode_arg({literal, Term}, Mod0) -> 113 | {Offset, Mod} = literal_ref_enc(Term, Mod0), 114 | {<>, Mod}; 115 | encode_arg([{location, _File, Line}], Mod0) -> 116 | %%{LitId, Mod1} = literal_ref_enc(File, Mod0), 117 | %%{<>, Mod1}; 118 | encode_arg({integer, Line}, Mod0); 119 | encode_arg({list, L}, Mod0) -> 120 | %% encode_arg({literal, list_to_tuple(L)}, Mod0); 121 | EncElement = fun(Elem, {Accum, M0}) -> 122 | {Encoded, M1} = encode_arg(Elem, M0), 123 | {<>, M1} 124 | end, 125 | {Enc, Mod1} = lists:foldr(EncElement, {<<>>, Mod0}, L), 126 | {<>, Mod1}; 127 | encode_arg([], Mod0) -> 128 | encode_arg(nil, Mod0). 129 | 130 | %%flag_to_bit(aligned) -> 16#01; %% No longer useful. 131 | flag_to_bit(little) -> 16#02; 132 | flag_to_bit(big) -> 16#00; 133 | flag_to_bit(signed) -> 16#04; 134 | flag_to_bit(unsigned)-> 16#00; 135 | %%flag_to_bit(exact) -> 16#08; 136 | flag_to_bit(native) -> 16#10; 137 | flag_to_bit({anno,_}) -> 0. 138 | -------------------------------------------------------------------------------- /apps/asm/src/asm_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc asm top level supervisor. 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module('asm_sup'). 7 | 8 | -behaviour(supervisor). 9 | 10 | %% API 11 | -export([start_link/0]). 12 | 13 | %% Supervisor callbacks 14 | -export([init/1]). 15 | 16 | -define(SERVER, ?MODULE). 17 | 18 | %%==================================================================== 19 | %% API functions 20 | %%==================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 24 | 25 | %%==================================================================== 26 | %% Supervisor callbacks 27 | %%==================================================================== 28 | 29 | %% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} 30 | init([]) -> 31 | {ok, { {one_for_all, 0, 1}, []} }. 32 | 33 | %%==================================================================== 34 | %% Internal functions 35 | %%==================================================================== 36 | -------------------------------------------------------------------------------- /apps/asm/src/asm_to_ir.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc Parses S file and builds IR file with Gluon instructions 3 | %%% @end 4 | %%%------------------------------------------------------------------- 5 | -module(asm_to_ir). 6 | 7 | %% API 8 | -export([process/1]). 9 | 10 | -include("../../emuemu/src/emu.hrl"). 11 | 12 | -record(compile_state, { ir = [] 13 | , bin = [] 14 | , mod 15 | }). 16 | 17 | %% @doc Loads beam S assembly and runs compile on it 18 | process(Fname) -> 19 | {ok, Asm} = file:consult(Fname), 20 | 21 | [{module, ModName} | _] = Asm, 22 | 23 | %% Cut away header tags 24 | Trimmed = lists:dropwhile(fun not_func_header/1, Asm), 25 | Funs = split_funs(Trimmed, []), 26 | io:format("Funs ~p~n", [Funs]), 27 | 28 | Mod0 = asm_module:new(ModName), 29 | Mod1 = asm_module:set_exports(proplists:get_value(exports, Asm), Mod0), 30 | 31 | CompileState = #compile_state{mod = Mod1}, 32 | #compile_state{ir=Code, mod=Mod2} 33 | = lists:foldr(fun compile_fun_gleam/2, CompileState, Funs), 34 | Code1 = lists:flatten(Code), 35 | Mod3 = asm_module:set_ir(Code1, Mod2), 36 | 37 | {Bin0, Mod4} = lists:foldr(fun compile_irop_dbg/2, {[], Mod3}, Code1), 38 | Bin = iolist_to_binary(Bin0), 39 | Mod = asm_module:set_bin(Bin, Mod4), 40 | 41 | io:format("Module ~p~n", [asm_module:to_proplist(Mod)]), 42 | %%ok = asm_module:write_ir(Fname ++ ".ir", Mod), 43 | ok = file:write_file(Fname ++ ".gleam", asm_module:to_binary(Mod)). 44 | 45 | %% @doc Predicate to separate fun headers 46 | not_func_header({function, _, _, _}) -> false; 47 | not_func_header(_) -> true. 48 | 49 | %% @doc Splits funs using {function,_,_,_} as first line in each block. 50 | %% Takes Asm with stripped headers; Returns [{fun_name, arity, [code]}] 51 | split_funs([], Accum) -> lists:reverse(Accum); 52 | split_funs([{function, FName, FArity, _} | Code], Accum) -> 53 | {Fun, Remaining} = lists:splitwith(fun not_func_header/1, Code), 54 | split_funs(Remaining, [{FName, FArity, Fun} | Accum]). 55 | 56 | compile_fun_gleam({F, Arity, Code}, CState) -> 57 | CState1 = lists:foldl(fun gleam_op/2, CState, Code), 58 | %% First opcode in function is always label, get it 59 | {label, FLabel} = hd(Code), 60 | M1 = CState1#compile_state.mod, 61 | M2 = asm_module:add_fun(F, Arity, FLabel, M1), 62 | CState1#compile_state{mod=M2}. 63 | 64 | %gleam_op({line, _}, #compile_state{}=CState) -> CState; 65 | %gleam_op({label, _}, #compile_state{}=CState) -> CState; 66 | gleam_op({kill, Dst}, #compile_state{}=CState) -> 67 | op({move, nil, Dst}, CState); 68 | gleam_op(X, #compile_state{}=CState) when is_atom(X) -> gleam_op({X}, CState); 69 | gleam_op({func_info, _M, _F, _A}, #compile_state{}=CState) -> CState; 70 | gleam_op({gc_bif, Lbl, Fail, Bif, Args, Result}, #compile_state{}=CState) -> 71 | Opcode = case length(Args) of 72 | 1 -> gc_bif1; 73 | 2 -> gc_bif2; 74 | 0 -> gc_bif0 75 | end, 76 | Op1 = list_to_tuple([Opcode, Lbl, Fail, Bif] ++ Args ++ [Result]), 77 | op(Op1, CState); 78 | gleam_op({bif, Lbl, Bif, Args, Result}, #compile_state{}=CState) -> 79 | Opcode = case length(Args) of 80 | 1 -> bif1; 81 | 2 -> bif2; 82 | 0 -> bif0 83 | end, 84 | Op1 = list_to_tuple([Opcode, Lbl, Bif] ++ Args ++ [Result]), 85 | op(Op1, CState); 86 | gleam_op({test, Test, Label, Args}, #compile_state{}=CState) -> 87 | Args1 = case is_list(Args) of true -> Args; _ -> [Args] end, 88 | Op1 = list_to_tuple([Test, Label] ++ Args1), 89 | op(Op1, CState); 90 | gleam_op(Src, #compile_state{}=CState) -> 91 | op(Src, CState). 92 | 93 | op(Src, #compile_state{}=CState) -> 94 | %%io:format("gleam_op src=~p~n", [Src]), 95 | Op = element(1, Src), 96 | %Opcode = asm_genop:opcode(Op), 97 | Arity = asm_genop:arity(Op), 98 | Args = [element(ArgIndex+1, Src) || ArgIndex <- lists:seq(1, Arity)], 99 | emit_gleam_op({Op, Args}, CState). 100 | 101 | emit_gleam_op(Op, CState) when not is_list(Op) -> emit_gleam_op([Op], CState); 102 | emit_gleam_op(Ops, CState = #compile_state{ir = Ir0}) -> 103 | %% TODO: ++ is O(N^2) 104 | CState#compile_state{ ir = Ir0 ++ Ops }. 105 | 106 | compile_irop_dbg(Op, {Accum0, State0}) -> 107 | {Accum1, State1} = compile_irop(Op, {Accum0, State0}), 108 | Diff = case Accum1 == Accum0 of true -> []; false -> hd(Accum1) end, 109 | io:format("~p -> ~p~n", [Op, Diff]), 110 | {Accum1, State1}. 111 | 112 | %% Catch label instruction and write its offset in bytes to separate map 113 | %% compile_irop({label, [N]}, {Accum, Mod0}) -> 114 | %% Offset = iolist_size(Accum), 115 | %% %io:format("label ~p pos ~p~n", [N, Offset]), 116 | %% {Accum, asm_module:register_label(N, Offset, Mod0)}; 117 | compile_irop({Op, OpArgs}, {Accum, Mod0}) -> 118 | CompileArg = fun(Arg, {Accum_, M}) -> 119 | {BinArg, M1} = asm_irop:encode_arg(Arg, M), 120 | {[BinArg | Accum_], M1} 121 | end, 122 | {Args, Mod} = lists:foldr(CompileArg, {[], Mod0}, OpArgs), 123 | Compiled = [asm_genop:opcode(Op) | Args], 124 | {[Compiled | Accum], Mod}. 125 | 126 | -------------------------------------------------------------------------------- /apps/emuemu/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, []}. -------------------------------------------------------------------------------- /apps/emuemu/src/emu.hrl: -------------------------------------------------------------------------------- 1 | -define(nil, '$NIL'). 2 | -------------------------------------------------------------------------------- /apps/emuemu/src/emu_app.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc emuemu public API 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(emu_app). 7 | 8 | -behaviour(application). 9 | 10 | %% Application callbacks 11 | -export([start/2 12 | , stop/1, start/0]). 13 | 14 | %%==================================================================== 15 | %% API 16 | %%==================================================================== 17 | 18 | start() -> 19 | simulate(). 20 | 21 | start(_StartType, _StartArgs) -> 22 | emu_sup:start_link(). 23 | 24 | %%-------------------------------------------------------------------- 25 | stop(_State) -> 26 | ok. 27 | 28 | %%==================================================================== 29 | %% Internal functions 30 | %%==================================================================== 31 | simulate() -> 32 | {ok, VM} = emu_machine:start_link(), 33 | emu_machine:load_module(VM, init, "test/g_test1.S.ir"), 34 | {ok, _MainThread} = emu_machine:spawn(VM, init, test1, []). 35 | -------------------------------------------------------------------------------- /apps/emuemu/src/emu_code_server.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc Owns ETS table with opcodes, loads, updates and fetches them 3 | %%% @end 4 | %%%------------------------------------------------------------------- 5 | -module(emu_code_server). 6 | -behaviour(gen_server). 7 | 8 | %% gen_server callbacks 9 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2 10 | , terminate/2, code_change/3]). 11 | 12 | %% API 13 | -export([start_link/0 14 | , add_code/2 15 | , find_mfa/2 16 | , fetch/2 17 | , step/2 18 | , label_to_offset/3 19 | , jump/2 20 | , get_module/1 21 | , find_atom/3 22 | , get_literal/3 23 | ]). 24 | 25 | -export_type([code_pointer/0]). 26 | 27 | -record(code_server, { modules = orddict:new() 28 | , code = ets:new(?MODULE, [ordered_set]) :: ets:tab() 29 | }). 30 | 31 | -record(code_pointer, { code_server :: pid() 32 | , offset :: non_neg_integer() 33 | , module :: atom() 34 | }). 35 | -type code_pointer() :: #code_pointer{}. 36 | 37 | -define(SERVER, ?MODULE). 38 | 39 | %%==================================================================== 40 | %% API 41 | %%==================================================================== 42 | 43 | %% @doc Starts the server 44 | -spec start_link() -> {ok, pid()} | ignore | {error, Error :: any()}. 45 | start_link() -> 46 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 47 | 48 | %% @doc Registers loaded asm_module in Gluon IR format (erlang tuples) 49 | add_code(CodeSrv, AsmM) when is_pid(CodeSrv) -> 50 | gen_server:call(CodeSrv, {add_code, AsmM}). 51 | 52 | %% @doc Returns opaque code pointer which points to beginning of function 53 | find_mfa(CodeSrv, {M, F, Arity}) when is_pid(CodeSrv) -> 54 | gen_server:call(CodeSrv, {find_mfa, {M, F, Arity}}). 55 | 56 | fetch(CodeSrv, IP) when is_pid(CodeSrv) -> 57 | gen_server:call(CodeSrv, {fetch, IP}). 58 | 59 | find_atom(CodeSrv, Module, AtomIndex) when is_pid(CodeSrv) -> 60 | gen_server:call(CodeSrv, {find_atom, Module, AtomIndex}). 61 | 62 | get_literal(CodeSrv, Module, LitIndex) when is_pid(CodeSrv) -> 63 | gen_server:call(CodeSrv, {get_literal, Module, LitIndex}). 64 | 65 | step(N, #code_pointer{offset=Offset}=IP) -> 66 | IP#code_pointer{offset=Offset+N}. 67 | 68 | jump(N, #code_pointer{}=IP) -> 69 | IP#code_pointer{offset=N}. 70 | 71 | get_module(#code_pointer{module=M}) -> M. 72 | 73 | label_to_offset(CodeSrv, Mod, Label) when is_pid(CodeSrv), is_atom(Mod) -> 74 | gen_server:call(CodeSrv, {label_to_offset, Mod, Label}). 75 | 76 | %%==================================================================== 77 | %% gen_server callbacks 78 | %%==================================================================== 79 | init([]) -> {ok, #code_server{}}. 80 | 81 | add_irop_fun(X, {_M, _Ets, _O}=State) 82 | when element(1,X) =:= '//' -> 83 | State; % Skip comment irops 84 | add_irop_fun(#{irop:=IROp}, {Modname, Ets, Offset}) -> 85 | %% Use {Modname, Offset} as key for opcodes. 86 | ets:insert(Ets, {{Modname, Offset}, IROp}), 87 | {Modname, Ets, Offset + 1}; 88 | add_irop_fun(#{irops:=Irops}, {_M, _Ets, _O} = State) -> 89 | lists:foldl(fun add_irop_fun/2, State, Irops). 90 | 91 | handle_call({add_code, AsmM}, _From 92 | , State=#code_server{ code=Ets 93 | , modules=Modules}) -> 94 | IR = asm_module:get_ir(AsmM), 95 | Modname = asm_module:get_name(AsmM), 96 | lists:foldl(fun add_irop_fun/2, {Modname, Ets, 0}, IR), 97 | Modules1 = orddict:store(Modname, AsmM, Modules), 98 | {reply, ok, State#code_server{modules = Modules1}}; 99 | handle_call({find_mfa, {M, F, Arity}}, _From, State) -> 100 | {reply, find_mfa_i({M, F, Arity}, State), State}; 101 | handle_call({fetch, IP}, _From, State=#code_server{code=Ets}) -> 102 | [{_Key, Instr}] = ets:lookup(Ets, {IP#code_pointer.module, IP#code_pointer.offset}), 103 | io:format("fetch -> ~p~n", [Instr]), 104 | {reply, {ok, Instr}, State}; 105 | handle_call({label_to_offset, Mod, Label}, _From, State) -> 106 | AsmMod = orddict:fetch(Mod, State#code_server.modules), 107 | {ok, Offset} = asm_module:label_to_offset(AsmMod, Label), 108 | {reply, {ok, Offset}, State}; 109 | handle_call({find_atom, Module, AtomIndex}, _From, State) -> 110 | {reply, find_atom_i(Module, AtomIndex, State), State}; 111 | handle_call({get_literal, Module, LIndex}, _From, State) -> 112 | {reply, get_literal_i(Module, LIndex, State), State}; 113 | handle_call(Request, _From, State) -> 114 | {reply, {?MODULE, bad_request, Request}, State}. 115 | 116 | handle_cast(_Msg, State) -> {noreply, State}. 117 | handle_info(_Info, State) -> {noreply, State}. 118 | terminate(_Reason, _State) -> ok. 119 | code_change(_OldVsn, State, _Extra) -> {ok, State}. 120 | 121 | %%-------------------------------------------------------------------- 122 | %%% Internal functions 123 | %%-------------------------------------------------------------------- 124 | 125 | find_atom_i(Module, AtomIndex, #code_server{modules=Modules}) -> 126 | AsmM = orddict:fetch(Module, Modules), 127 | asm_module:find_atom(AtomIndex, AsmM). 128 | 129 | get_literal_i(Module, LitIndex, #code_server{modules=Modules}) -> 130 | AsmM = orddict:fetch(Module, Modules), 131 | asm_module:find_literal(LitIndex, AsmM). 132 | 133 | find_mfa_i({M, F, Arity}, #code_server{modules=Modules}) -> 134 | AsmM = orddict:fetch(M, Modules), 135 | Ok_MLabel = asm_module:funarity_to_label(AsmM, {F, Arity}), 136 | Ok_Offset = asm_module:label_to_offset(AsmM, Ok_MLabel), 137 | make_ptr(M, Ok_Offset). 138 | 139 | make_ptr(Mod, {ok, Offs}) -> 140 | {ok, #code_pointer{module=Mod, code_server=self(), offset=Offs}}; 141 | make_ptr(_M, {error, _}=E) -> E. 142 | -------------------------------------------------------------------------------- /apps/emuemu/src/emu_machine.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc Defines properties of virtual machine (modules, atoms, processes and 3 | %%% other resources) 4 | %%% @end 5 | %%%------------------------------------------------------------------- 6 | -module(emu_machine). 7 | 8 | -behaviour(gen_server). 9 | 10 | %% gen_server callbacks 11 | -export([ init/1, handle_call/3, handle_cast/2, handle_info/2 12 | , terminate/2, code_change/3]). 13 | 14 | %% API 15 | -export([load_module/3 16 | , start_link/0 17 | , spawn/4 18 | ]). 19 | 20 | -define(SERVER, ?MODULE). 21 | 22 | -record(machine, { pid_counter = 0 23 | , processes = orddict:new() 24 | , code_server :: pid() 25 | }). 26 | 27 | %%==================================================================== 28 | %% API 29 | %%==================================================================== 30 | 31 | %% @doc Starts the server 32 | -spec start_link() -> {ok, pid()} | ignore | {error, Error :: any()}. 33 | start_link() -> 34 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 35 | 36 | load_module(VM, Name, Filename) -> 37 | gen_server:call(VM, {load_module, Name, Filename}). 38 | 39 | spawn(VM, M, F, Args) -> 40 | gen_server:call(VM, {spawn, M, F, Args}). 41 | 42 | %%==================================================================== 43 | %% gen_server callbacks 44 | %%==================================================================== 45 | init([]) -> 46 | {ok, CodeSrv} = emu_code_server:start_link(), 47 | {ok, #machine{code_server=CodeSrv}}. 48 | 49 | handle_call({load_module, _Name, Filename}, _From 50 | , State) -> 51 | Mod = asm_module:read_ir(Filename), 52 | emu_code_server:add_code(State#machine.code_server, Mod), 53 | {reply, ok, State}; 54 | handle_call({spawn, M, F, Args}, _From 55 | , State=#machine{ pid_counter=Counter 56 | , processes=Procs 57 | , code_server=CodeSrv}) -> 58 | Counter1 = Counter + 1, 59 | {ok, Proc} = emu_proc:start_link(self(), CodeSrv), 60 | emu_proc:call(Proc, M, F, Args), 61 | emu_proc:tick(Proc), % start the execution (ticking clock) 62 | Procs1 = orddict:store(Counter1, Proc, Procs), 63 | {reply, {ok, Counter1}, State#machine{ pid_counter=Counter1 64 | , processes=Procs1}}; 65 | handle_call(Request, _From, State) -> 66 | {reply, {?MODULE, bad_request, Request}, State}. 67 | 68 | handle_cast(_Msg, State) -> {noreply, State}. 69 | handle_info(_Info, State) -> {noreply, State}. 70 | terminate(_Reason, _State) -> ok. 71 | code_change(_OldVsn, State, _Extra) -> {ok, State}. 72 | 73 | %%-------------------------------------------------------------------- 74 | %%% Internal functions 75 | %%-------------------------------------------------------------------- 76 | -------------------------------------------------------------------------------- /apps/emuemu/src/emu_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc emuemu top level supervisor. 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(emu_sup). 7 | 8 | -behaviour(supervisor). 9 | 10 | %% API 11 | -export([start_link/0]). 12 | 13 | %% Supervisor callbacks 14 | -export([init/1]). 15 | 16 | -define(SERVER, ?MODULE). 17 | 18 | %%==================================================================== 19 | %% API functions 20 | %%==================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 24 | 25 | %%==================================================================== 26 | %% Supervisor callbacks 27 | %%==================================================================== 28 | 29 | %% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} 30 | init([]) -> 31 | {ok, { {one_for_all, 0, 1}, []} }. 32 | 33 | %%==================================================================== 34 | %% Internal functions 35 | %%==================================================================== 36 | -------------------------------------------------------------------------------- /apps/emuemu/src/emuemu.app.src: -------------------------------------------------------------------------------- 1 | {application, 'emuemu', 2 | [{description, "Erlang simulator for running GluonVM intermediate representation"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {mod, {'emu_app', []}}, 6 | {applications, 7 | [kernel, 8 | stdlib 9 | ]}, 10 | {env,[]}, 11 | {modules, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /apps/prototype/src/prototype.app.src: -------------------------------------------------------------------------------- 1 | {application, 'prototype', 2 | [{description, "A prototype BEAM/GluonVM runner"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {mod, {'prototype_app', []}}, 6 | {applications, 7 | [kernel, 8 | stdlib 9 | ]}, 10 | {env,[]}, 11 | {modules, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /apps/prototype/src/prototype_app.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc prototype beam runner public API 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(prototype_app). 7 | 8 | -behaviour(application). 9 | 10 | %% Application callbacks 11 | -export([ start/2 12 | , stop/1 13 | , start/0 14 | ]). 15 | 16 | %%==================================================================== 17 | %% API 18 | %%==================================================================== 19 | 20 | start() -> 21 | %% prototype_emu:run("test/g_test1.beam", test2, []). 22 | 23 | %% prototype_emu:run("test/g_test1.beam", f_test, []). 24 | 25 | J1 = {struct, [{hello, "world"}]}, 26 | prototype_emu:run("test/mochijson.beam", encode, [J1]). 27 | 28 | start(_StartType, _StartArgs) -> 29 | ok. 30 | %'asm_sup':start_link(). 31 | 32 | %%-------------------------------------------------------------------- 33 | stop(_State) -> 34 | ok. 35 | 36 | %%==================================================================== 37 | %% Internal functions 38 | %%==================================================================== 39 | -------------------------------------------------------------------------------- /codegen/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | vm_*.txt -------------------------------------------------------------------------------- /codegen/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: genop vm_loop vm_predef_atoms vm_bif_tab 3 | 4 | SRCDIR="../emulator/src" 5 | INCDIR="../emulator/include" 6 | 7 | .PHONY: genop 8 | genop: 9 | ./create_genop_cpp.py > ${SRCDIR}/genop.cpp && \ 10 | ./create_genop_h.py > ${INCDIR}/genop.h 11 | 12 | .PHONY: vm_loop 13 | vm_loop: 14 | ./create_vm_loop.py > ${SRCDIR}/vm_loop.inc.cpp 15 | 16 | #.PHONY: vm_copypaste_impl 17 | #vm_copypaste_impl: 18 | # ./vm_copypaste_impl.py > vm_copypaste_impl.txt 19 | 20 | .PHONY: vm_predef_atoms 21 | vm_predef_atoms: 22 | ./create_predef_atoms_h.py > ${INCDIR}/predef_atoms.h && \ 23 | ./create_predef_atoms_cpp.py > ${SRCDIR}/predef_atoms.cpp 24 | 25 | .PHONY: vm_bif_tab 26 | vm_bif_tab: 27 | ./create_bif_tab_h.py > ${SRCDIR}/bif_tab.h && \ 28 | ./create_bif_tab_cpp.py > ${SRCDIR}/bif_tab.cpp 29 | -------------------------------------------------------------------------------- /codegen/README.md: -------------------------------------------------------------------------------- 1 | # Adopted files 2 | 3 | ## genop.tab 4 | 5 | Belongs to erlang/otp by Ericsson. Not modified. 6 | -------------------------------------------------------------------------------- /codegen/atoms.tab: -------------------------------------------------------------------------------- 1 | #--- A 2 | apply 3 | 4 | #--- B 5 | badarg 6 | badarith 7 | badarity 8 | badfun 9 | badmatch 10 | 11 | #--- C 12 | case_clause 13 | 14 | #--- E 15 | erlang 16 | error 17 | exit 18 | 19 | #--- F 20 | false 21 | function_clause 22 | 23 | #--- H 24 | high 25 | 26 | #--- I 27 | if_clause 28 | init 29 | 30 | #--- K 31 | kill 32 | killed 33 | 34 | #--- L 35 | low 36 | 37 | #--- N 38 | nocatch 39 | normal 40 | 41 | #--- O 42 | ok 43 | 44 | #--- S 45 | system_limit 46 | 47 | #--- T 48 | throw 49 | trap_exit 50 | true 51 | 52 | #--- U 53 | undef 54 | undefined 55 | -------------------------------------------------------------------------------- /codegen/bif.tab: -------------------------------------------------------------------------------- 1 | # 2 | # Bif atom name Arity C++ function name 3 | # 4 | # Function name will be prefixed by "bif_" and suffixed by "_arity" 5 | # for example: - minus 6 | # will create mapping to bif '-'/2 named bif_minus_2(Process *, Term, Term) in C++ 7 | # 8 | - 2 minus 9 | + 2 plus 10 | ++ 2 plusplus 11 | == 2 equals 12 | =:= 2 equals_exact 13 | =< 2 less_equal 14 | >= 2 greater_equal 15 | * 2 multiply 16 | / 2 divide 17 | 18 | #--- A 19 | apply 2 20 | apply 3 21 | atom_to_list 1 22 | 23 | #--- E 24 | element 2 25 | exit 1 26 | exit 2 27 | 28 | #--- G 29 | group_leader 0 30 | group_leader 2 31 | 32 | #--- F 33 | function_exported 3 34 | 35 | #--- H 36 | hd 1 37 | 38 | #--- I 39 | integer_to_list 1 40 | integer_to_list 2 41 | is_process_alive 1 42 | 43 | #--- L 44 | length 1 45 | list_to_atom 1 46 | list_to_existing_atom 1 47 | 48 | #--- M 49 | make_fun 3 50 | 51 | #--- N 52 | nif_error 1 53 | 54 | #--- P 55 | process_flag 2 56 | 57 | #--- R 58 | register 2 59 | 60 | #--- S 61 | self 0 62 | spawn 3 63 | spawn_link 3 64 | # spawn_link 1 2 4 65 | 66 | #--- T 67 | tl 1 68 | -------------------------------------------------------------------------------- /codegen/copypaste_impl.py.txt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # takes: genop.tab from erlang/otp 3 | # produces: source of copypaste with all opcode implementations empty (copy manually) 4 | import libgenop 5 | 6 | libgenop.load() 7 | 8 | print("""#pragma once 9 | // Generated by codegen/vm_copypaste_impl.py 10 | 11 | #include "g_process.h" 12 | #include "bif/g_bif_misc.h" 13 | 14 | namespace gluon { 15 | namespace impl { 16 | 17 | // Set of stuff we take from Process struct to keep running, this will be saved 18 | // by loop runner on context switch or loop end 19 | typedef struct { 20 | word_t *ip; // code pointer 21 | } runtime_ctx_t; 22 | 23 | #define IMMED(var) if ((var).is_immed()) { proc->vm_resolve_immed(var); } 24 | """) 25 | 26 | # print headers 27 | #for opcode in range(libgenop.MIN_OPCODE, libgenop.MAX_OPCODE+1): 28 | # op = libgenop.ops_by_code[opcode] 29 | # print(""" inline void opcode_%s(Process *proc); // opcode: %d""" % (op['name'], opcode)) 30 | 31 | # print implementations 32 | for opcode in range(libgenop.MIN_OPCODE, libgenop.MAX_OPCODE+1): 33 | op = libgenop.ops_by_code[opcode] 34 | print(""" inline void opcode_%s(Process *proc, vm_runtime_ctx_t &ctx) { // opcode: %d 35 | }""" % (op['name'], opcode)) 36 | 37 | print 38 | print("""} // ns impl 39 | } // ns gluon 40 | """) 41 | print 42 | -------------------------------------------------------------------------------- /codegen/create_bif_tab_cpp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # takes: genop.tab from erlang/otp 3 | # produces: sorted bif search table 4 | import libgenop 5 | 6 | libgenop.load() 7 | 8 | print("""// Generated by codegen/create_bif_tab_*.py 9 | #include "bif_tab.h" 10 | #include "predef_atoms.h" 11 | 12 | #include "bif/bif_misc.h" 13 | #include "bif/bif_proc.h" 14 | 15 | namespace gluon { 16 | namespace bif { 17 | 18 | const BIFIndex g_bif_table[bif_table_size] = {""") 19 | 20 | # print atom constants 21 | for b in libgenop.bif_tab: 22 | arity = int(b['arity']) 23 | bif_atom_id = libgenop.atom_id_tab[b['atom']] 24 | bif_atom = libgenop.id_atom_tab[bif_atom_id] 25 | bifname = libgenop.atom_constname(bif_atom) 26 | print(' {atom::%s, %d, (void *)&bif_%s_%d}, // atom id=%d' \ 27 | % (bifname, arity, b['cname'], arity, bif_atom_id)) 28 | 29 | print("""}; 30 | 31 | } // ns bif 32 | } // ns gluon 33 | """) 34 | -------------------------------------------------------------------------------- /codegen/create_bif_tab_h.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # takes: genop.tab from erlang/otp 3 | # produces: sorted bif search table 4 | import libgenop 5 | 6 | libgenop.load() 7 | 8 | print("""// Generated by codegen/create_bif_tab_*.py 9 | #include "defs.h" 10 | #include "term.h" 11 | 12 | namespace gluon { 13 | namespace bif { 14 | 15 | typedef struct { 16 | Term fun; 17 | Word arity; 18 | void *bif_fn; 19 | } BIFIndex; 20 | 21 | """) 22 | 23 | print("const Word bif_table_size = %d;" % len(libgenop.bif_tab)); 24 | 25 | print("""extern const BIFIndex g_bif_table[bif_table_size]; 26 | 27 | } // ns bif 28 | } // ns gluon 29 | """) 30 | -------------------------------------------------------------------------------- /codegen/create_genop_cpp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # takes: genop.tab from erlang/otp 3 | # produces: g_genop.cpp with arrays that map erlang asm opcodes to numbers 4 | import libgenop 5 | 6 | libgenop.load() 7 | 8 | print("""// Generated by codegen/vm_genop_cpp.py 9 | 10 | #include "defs.h" 11 | #include "genop.h" 12 | 13 | namespace gluon { 14 | namespace genop { 15 | """) 16 | 17 | # print arity map 18 | print("const unsigned char arity_map[] = {0, // opcode 0 does not exist") 19 | for opcode in range(libgenop.MIN_OPCODE, libgenop.MAX_OPCODE+1): 20 | op = libgenop.ops_by_code[opcode] 21 | print(" %d, // opcode: %d (%s)" % (op['arity'], opcode, op['name'])) 22 | print("};") 23 | print 24 | 25 | # print opcode names map 26 | print("#if G_DEBUG") 27 | print("const char *opcode_name_map[] = {nullptr, // opcode 0 does not exist") 28 | for opcode in range(libgenop.MIN_OPCODE, libgenop.MAX_OPCODE+1): 29 | op = libgenop.ops_by_code[opcode] 30 | print(" \"%s\", // opcode: %d" % (op['name'], opcode)) 31 | print("};") 32 | print("#endif // G_DEBUG") 33 | print 34 | 35 | print("} // ns genop") 36 | print("} // ns gluon") 37 | print 38 | -------------------------------------------------------------------------------- /codegen/create_genop_h.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # takes: genop.tab from erlang/otp 3 | # produces: g_genop.h header with arrays that map erlang asm opcodes to numbers 4 | import libgenop 5 | 6 | libgenop.load() 7 | 8 | print("// Generated by codegen/vm_genop_h.py") 9 | print("#pragma once") 10 | 11 | print("namespace gluon {") 12 | print("namespace genop {") 13 | 14 | print 15 | 16 | print("const unsigned int min_opcode = %d;" % libgenop.MIN_OPCODE) 17 | print("const unsigned int max_opcode = %d;" % libgenop.MAX_OPCODE) 18 | print 19 | 20 | # print constants 21 | print("enum class Opcode {") 22 | for opcode in range(libgenop.MIN_OPCODE, libgenop.MAX_OPCODE+1): 23 | op = libgenop.ops_by_code[opcode] 24 | print(" %s = %d, // 0x%x" % (op['name'].capitalize(), opcode, opcode)); 25 | 26 | print("};") 27 | print 28 | 29 | # print arity map 30 | print("extern const unsigned char arity_map[];") 31 | print 32 | 33 | # print opcode names map 34 | print("#if G_DEBUG") 35 | print("extern const char *opcode_name_map[];") 36 | print("#endif // G_DEBUG") 37 | print 38 | 39 | print("} // ns genop") 40 | print("} // ns gluon") 41 | print 42 | -------------------------------------------------------------------------------- /codegen/create_predef_atoms_cpp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # takes: genop.tab from erlang/otp 3 | # produces: source of predef atoms on vm boot 4 | import libgenop 5 | 6 | libgenop.load() 7 | 8 | print("""// Generated by codegen/vm_predef_atoms_*.py 9 | 10 | namespace gluon { 11 | namespace atom { 12 | 13 | extern const char *g_predef_atoms; 14 | """) 15 | 16 | # print atom constants 17 | print("const char *g_predef_atoms =") 18 | for a in libgenop.atom_tab: 19 | print(' "\\x%02x" "%s"' % (len(a['atom']), a['atom'])) 20 | 21 | print(""" ; 22 | 23 | } // ns atom 24 | } // ns gluon 25 | """) 26 | -------------------------------------------------------------------------------- /codegen/create_predef_atoms_h.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # takes: genop.tab from erlang/otp 3 | # produces: predef atoms include file with constants 4 | import libgenop 5 | 6 | libgenop.load() 7 | 8 | print("""// Generated by codegen/vm_predef_atoms_*.py 9 | 10 | #include "term.h" 11 | 12 | namespace gluon { 13 | namespace atom { 14 | """) 15 | 16 | # print atom constants 17 | for a in libgenop.atom_tab: 18 | constname = libgenop.atom_constname(a) 19 | print(" const Term %s = Term::make_atom(%d);" % (constname, a['id'])) 20 | 21 | print(""" 22 | extern const char *g_predef_atoms; 23 | 24 | } // ns atom 25 | } // ns gluon 26 | """) 27 | -------------------------------------------------------------------------------- /codegen/create_vm_loop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # takes: genop.tab from erlang/otp 3 | # produces: g_vm_loop includable file with label copypaste template for vm 4 | # loop function 5 | import libgenop 6 | 7 | libgenop.load() 8 | 9 | print("""// Generated by codegen/vm_loop.py 10 | """) 11 | 12 | OPS_WHICH_MAY_YIELD = ['return', 'call', 'call_only', 'call_last', 13 | 'call_ext', 'call_fun', 'call_ext_only', 'call_ext_last', 14 | 'bif0', 'bif1', 'bif2', 'gc_bif1', 'gc_bif2', 'send', 15 | 'apply', 'apply_last', 16 | 'apply_mfargs_', 'normal_exit_', 'error_exit_'] 17 | OPS_WHICH_ALWAYS_YIELD = ['wait'] 18 | 19 | for opcode in range(libgenop.MIN_OPCODE, libgenop.MAX_OPCODE+1): 20 | op = libgenop.ops_by_code[opcode] 21 | # unconditional code end 22 | print("OP_%s: // opcode: %d" % (op['name'], opcode)) 23 | if op['name'] == "int_code_end": 24 | print(" goto vm_end;\n") 25 | continue 26 | 27 | # call handler or print TODO error 28 | if op['name'] in libgenop.implemented_ops: 29 | print(' if (debug_mode) { libc::fmt(tGreen("%s") "/%d args="); }' % (op['name'], op['arity'])) 30 | print(' ctx.print_args(%d);' % (op['arity'])) 31 | 32 | # unconditional scheduling 33 | if op['name'] in OPS_WHICH_ALWAYS_YIELD: 34 | print(" impl::opcode_%s(proc, ctx);" % (op['name'])) 35 | print(" goto schedule;") 36 | continue 37 | # conditional scheduling - false means we yield 38 | elif op['name'] in OPS_WHICH_MAY_YIELD: 39 | # special instruction which can interrupt loop 40 | print(" opcode_result = impl::opcode_%s(proc, ctx);" % (op['name'])) 41 | print(" if (G_UNLIKELY(opcode_result != impl::WantSchedule::NextProcess)) {") 42 | print(" goto schedule;") 43 | print(" }") 44 | else: 45 | print(" impl::opcode_%s(proc, ctx);" % (op['name'])) 46 | print(" goto next_instr;") 47 | else: 48 | print(" throw err::TODO(\"notimpl %s\");" % (op['name'])) 49 | print(" G_IF_NODEBUG(goto next_instr;)") 50 | print 51 | 52 | # 53 | # Init table with labels and export it to global scope 54 | # 55 | print("""vm_jump_table_init: { 56 | // Init table with labels and export it to global scope 57 | static const void *l_opcode_labels[] = { nullptr,""") 58 | 59 | for opcode in range(libgenop.MIN_OPCODE, libgenop.MAX_OPCODE+1): 60 | op = libgenop.ops_by_code[opcode] 61 | print(" &&OP_%s, // opcode %d" % (op['name'], opcode)) 62 | 63 | print(""" }; 64 | g_opcode_labels = l_opcode_labels; 65 | } // end init 66 | """) 67 | -------------------------------------------------------------------------------- /codegen/genop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # takes: genop.tab from erlang/otp 3 | # produces: genop.erl module which maps erlang asm opcodes to numbers 4 | import libgenop 5 | 6 | libgenop.load() 7 | 8 | print("%%% Generated by codegen/genop.py") 9 | print("-module(asm_genop).") 10 | print("-export([arity/1, name/1, opcode/1]).") 11 | print 12 | 13 | for op in libgenop.ops: 14 | print "arity('%s') -> %d;" % (op['name'], op['arity']) 15 | print("arity(X) -> erlang:error({bad_arity, X}).") 16 | print 17 | 18 | for op in libgenop.ops: 19 | print "name(%d) -> '%s';" % (op['opcode'], op['name']) 20 | print("name(X) -> erlang:error({bad_name, X}).") 21 | print 22 | 23 | for op in libgenop.ops: 24 | print "opcode('%s') -> %d;" % (op['name'], op['opcode']) 25 | print("opcode(X) -> erlang:error({bad_opcode, X}).") 26 | print 27 | -------------------------------------------------------------------------------- /codegen/implemented_ops.tab: -------------------------------------------------------------------------------- 1 | test_heap 2 | move 3 | return 4 | allocate 5 | allocate_zero 6 | allocate_heap 7 | allocate_heap_zero 8 | deallocate 9 | select_val 10 | select_tuple_arity 11 | trim 12 | init 13 | jump 14 | 15 | #--- send/recv --- 16 | send 17 | loop_rec 18 | loop_rec_end 19 | remove_message 20 | wait 21 | recv_mark 22 | recv_set 23 | 24 | #--- funs, hofs --- 25 | func_info 26 | call_only 27 | call 28 | call_last 29 | call_ext 30 | call_ext_last 31 | call_ext_only 32 | gc_bif1 33 | gc_bif2 34 | bif0 35 | bif1 36 | bif2 37 | make_fun2 38 | is_function2 39 | call_fun 40 | apply 41 | apply_last 42 | 43 | #--- list ops --- 44 | put_list 45 | get_list 46 | 47 | #--- errors --- 48 | badmatch 49 | case_end 50 | if_end 51 | catch 52 | 53 | #--- tuple 54 | is_tuple 55 | test_arity 56 | get_tuple_element 57 | put_tuple 58 | 59 | #--- tests --- 60 | is_lt 61 | is_eq 62 | is_eq_exact 63 | is_nonempty_list 64 | is_nil 65 | is_ge 66 | is_atom 67 | is_integer 68 | is_float 69 | is_list 70 | is_binary 71 | is_pid 72 | is_boolean 73 | is_function 74 | is_reference 75 | is_port 76 | 77 | #--- debug --- 78 | #line - processed on load stage and removed 79 | #func_info - processed on load stage and removed 80 | 81 | #--- Internal self-made opcodes 82 | normal_exit_ 83 | apply_mfargs_ 84 | error_exit_ 85 | -------------------------------------------------------------------------------- /codegen/jump_table.py.txt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # takes: genop.tab from erlang/otp 3 | # produces: g_jump_table includable file with label pointer table for vm ops 4 | import libgenop 5 | 6 | libgenop.load() 7 | 8 | print("// Generated by codegen/vm_jump_table.py") 9 | print 10 | 11 | # print arity map 12 | print("const void *VM::g_opcode_labels[] = { nullptr, // no 0 opcode") 13 | for opcode in range(libgenop.MIN_OPCODE, libgenop.MAX_OPCODE+1): 14 | op = libgenop.ops_by_code[opcode] 15 | print(" blockaddress(@VM::vm_loop, %%OP_%s), // opcode: %d" % (op['name'], opcode)) 16 | print("};") 17 | print 18 | -------------------------------------------------------------------------------- /codegen/libgenop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # takes: genop.tab from erlang/otp 3 | # returns list of dicts{name:str(), arity:int(), opcode:int()} 4 | 5 | import string 6 | 7 | MIN_OPCODE = 1 8 | MAX_OPCODE = 158 9 | 10 | def load_opcodes(): 11 | global ops 12 | ops = [] 13 | for ln in file("genop.tab"): 14 | ln = ln.strip() 15 | if not ln: continue 16 | if ln.startswith("#"): continue 17 | 18 | p1 = ln.split(" ") 19 | if len(p1) != 2: continue 20 | 21 | opcode = p1[0].strip(":") 22 | (opname, oparity) = p1[1].split("/") 23 | opname = opname.strip("-") 24 | ops.append({'name': opname, 'arity': int(oparity), 'opcode': int(opcode)}) 25 | 26 | global MAX_OPCODE 27 | extra_codes = 3 28 | ops.append({'name': 'normal_exit_', 'arity': 0, 'opcode': MAX_OPCODE+1}) 29 | ops.append({'name': 'apply_mfargs_', 'arity': 0, 'opcode': MAX_OPCODE+2}) 30 | ops.append({'name': 'error_exit_', 'arity': 0, 'opcode': MAX_OPCODE+3}) 31 | MAX_OPCODE += extra_codes 32 | 33 | # make op map by opcode 34 | global ops_by_code 35 | ops_by_code = {} 36 | for op in ops: 37 | ops_by_code[op['opcode']] = op 38 | 39 | def filter_comments(lst): 40 | # skip lines starting with # and empty lines 41 | return [i for i in lst 42 | if not i.strip().startswith("#") and len(i.strip()) > 0] 43 | 44 | implemented_ops = filter_comments(file("implemented_ops.tab").read().split("\n")) 45 | atom_tab = [] 46 | bif_tab = [] 47 | atom_id_tab = {} # string() -> int() - maps atom string to integer 48 | id_atom_tab = {} # int() -> dict({atom, id}) - maps atom id to atom record 49 | 50 | def is_printable(s): 51 | printable = string.ascii_letters + string.digits + "_" 52 | for c in s: 53 | if c not in printable: 54 | return False 55 | return True 56 | 57 | def bif_cname(b): 58 | if len(b) >= 3: return b[2] 59 | else: return b[0] 60 | 61 | def atom_constname(a): 62 | if 'cname' in a: 63 | return "Q_" + a['cname'].upper() 64 | else: 65 | return a['atom'].upper() 66 | 67 | atom_id = 1 68 | def atom_add(a): 69 | global atom_tab, atom_id, atom_id_tab, id_atom_tab 70 | if a['atom'] in atom_id_tab: #exists 71 | return 72 | adict = a 73 | adict['id'] = atom_id 74 | atom_tab.append(adict) 75 | atom_id_tab[a['atom']] = atom_id # name to id map 76 | id_atom_tab[atom_id] = a # id to atom map 77 | atom_id += 1 78 | 79 | def load_bifs(): 80 | global bif_tab, atom_tab 81 | atoms = filter_comments(file("atoms.tab").read().split("\n")) 82 | for a in atoms: 83 | atom_add({'atom': a}) 84 | 85 | bifs = filter_comments(file("bif.tab").read().split("\n")) 86 | bif_tab0 = [] 87 | for b in bifs: 88 | b = b.split() 89 | if len(b) >= 3: cname = b[2] 90 | else: cname = b[0] 91 | bif_tab0.append({'atom': b[0], 'arity': int(b[1]), 'cname': cname}) 92 | 93 | if is_printable(b[0]): atom_add({'atom': b[0]}) 94 | else: atom_add({'atom': b[0], 'cname': cname}) 95 | 96 | global atom_id_tab 97 | # sort by atom id plus arity if atom ids equal 98 | bif_tab = sorted(bif_tab0, key=lambda b: atom_id_tab[b['atom']] * 1000 + b['arity']) 99 | 100 | def load(): 101 | load_opcodes() 102 | load_bifs() 103 | 104 | ops = [] 105 | ops_by_code = {} 106 | -------------------------------------------------------------------------------- /emulator/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Chromium 4 | AccessModifierOffset: -1 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: true 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Inline 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakBeforeMultilineStrings: true 19 | AlwaysBreakTemplateDeclarations: true 20 | BinPackArguments: true 21 | BinPackParameters: false 22 | BraceWrapping: 23 | AfterClass: false 24 | AfterControlStatement: true 25 | AfterEnum: false 26 | AfterFunction: false 27 | AfterNamespace: false 28 | AfterObjCDeclaration: false 29 | AfterStruct: false 30 | AfterUnion: false 31 | BeforeCatch: false 32 | BeforeElse: false 33 | IndentBraces: false 34 | BreakBeforeBinaryOperators: None 35 | BreakBeforeBraces: Attach 36 | BreakBeforeTernaryOperators: true 37 | BreakConstructorInitializersBeforeComma: false 38 | ColumnLimit: 80 39 | CommentPragmas: '^ IWYU pragma:' 40 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 41 | ConstructorInitializerIndentWidth: 4 42 | ContinuationIndentWidth: 4 43 | Cpp11BracedListStyle: true 44 | DerivePointerAlignment: false 45 | DisableFormat: false 46 | ExperimentalAutoDetectBinPacking: false 47 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 48 | IncludeCategories: 49 | - Regex: '^<.*\.h>' 50 | Priority: 1 51 | - Regex: '^<.*' 52 | Priority: 2 53 | - Regex: '.*' 54 | Priority: 3 55 | IndentCaseLabels: true 56 | IndentWidth: 4 57 | IndentWrappedFunctionNames: false 58 | KeepEmptyLinesAtTheStartOfBlocks: false 59 | MacroBlockBegin: '' 60 | MacroBlockEnd: '' 61 | MaxEmptyLinesToKeep: 1 62 | NamespaceIndentation: None 63 | ObjCBlockIndentWidth: 2 64 | ObjCSpaceAfterProperty: false 65 | ObjCSpaceBeforeProtocolList: false 66 | PenaltyBreakBeforeFirstCallParameter: 1 67 | PenaltyBreakComment: 300 68 | PenaltyBreakFirstLessLess: 120 69 | PenaltyBreakString: 1000 70 | PenaltyExcessCharacter: 1000000 71 | PenaltyReturnTypeOnItsOwnLine: 200 72 | PointerAlignment: Left 73 | SpaceAfterCStyleCast: false 74 | SpaceBeforeAssignmentOperators: true 75 | SpaceBeforeParens: ControlStatements 76 | SpaceInEmptyParentheses: false 77 | SpacesBeforeTrailingComments: 2 78 | SpacesInAngles: false 79 | SpacesInContainerLiterals: true 80 | SpacesInCStyleCastParentheses: false 81 | SpacesInParentheses: false 82 | SpacesInSquareBrackets: false 83 | Standard: Auto 84 | TabWidth: 8 85 | UseTab: Never 86 | ... 87 | 88 | -------------------------------------------------------------------------------- /emulator/.gitignore: -------------------------------------------------------------------------------- 1 | # debug stdout dumps 2 | *.txt 3 | .gdb_history 4 | 5 | # erl asm files 6 | *.S 7 | -------------------------------------------------------------------------------- /emulator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(gluon CXX C) 2 | cmake_minimum_required(VERSION 2.8) 3 | 4 | #find_package (Threads) 5 | 6 | #SET (CMAKE_C_COMPILER "clang") 7 | #SET (CMAKE_CXX_COMPILER "clang++") 8 | 9 | include_directories( 10 | ${CMAKE_SOURCE_DIR}/include/ 11 | ${CMAKE_SOURCE_DIR}/src/ 12 | ${CMAKE_SOURCE_DIR}/test/ 13 | ) 14 | 15 | # Enables lot of Std::fmt debugging features as well as assertions everywhere in 16 | # the code. Makes things run slow. 17 | add_definitions(-DG_DEBUG=1) 18 | 19 | set(G_TEST 0) 20 | 21 | if(G_TEST) 22 | add_definitions(-DG_TEST=1) # to run tests 23 | else() 24 | add_definitions(-DG_TEST=0 -fno-rtti) # normal compilation no test 25 | endif() 26 | 27 | add_definitions(-O0 -g -ggdb) 28 | #add_definitions(-Os) 29 | # optimize for size AND generate asm, build will fail though 30 | #add_definitions(-S -g -Os) # generate asm in .o files (bad file extension) 31 | #add_definitions(-S -Os) # same but no debug 32 | add_definitions(#-m32 33 | -Weverything #-Werror 34 | -Wno-c++98-compat-pedantic -Wno-missing-noreturn 35 | -Wno-c++98-compat -Wno-padded -Wno-format-nonliteral 36 | -Wno-old-style-cast -Wno-exit-time-destructors -Wno-global-constructors 37 | -Wno-unused-parameter 38 | -Wno-gnu-label-as-value -Wno-unused-label 39 | ) 40 | #-fno-exceptions -fno-rtti 41 | set(CMAKE_CXX_FLAGS "-std=c++14 ${CMAKE_CXX_FLAGS}") 42 | set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} 43 | -std=c99 44 | ) 45 | set(SRC_LIST 46 | include/FEATURE.h # edit this to configure stuff 47 | include/defs.h 48 | include/error.h 49 | include/functional.h 50 | 51 | include/struct/array.h 52 | include/struct/dict.h 53 | include/struct/list.h 54 | include/struct/str.h 55 | include/struct/set.h 56 | 57 | src/beam_loader.cpp src/beam.h 58 | src/beam_prepare.cpp 59 | 60 | src/bif_tab.cpp src/bif_tab.h 61 | src/binary.cpp include/binary.h 62 | src/code.cpp include/code.h 63 | src/code_index.cpp include/code_index.h 64 | src/code_server.cpp include/code_server.h 65 | src/dist.cpp include/dist.h 66 | src/ext_term.cpp include/ext_term.h 67 | src/genop.cpp include/genop.h # generated via codegen/ script 68 | src/fun.cpp include/fun.h 69 | src/heap.cpp include/heap.h 70 | src/mailbox.cpp include/mailbox.h 71 | src/main.cpp 72 | src/module.cpp include/module.h 73 | src/pointer.h 74 | src/predef_atoms.cpp include/predef_atoms.h 75 | src/process.cpp include/process.h 76 | src/process_ctx.cpp src/process_ctx.h 77 | src/process_fail.cpp include/process_fail.h 78 | src/reader.h 79 | src/scheduler.cpp include/scheduler.h 80 | src/stack.cpp src/stack.h 81 | src/term.cpp include/term.h 82 | src/term_helpers.cpp include/term_helpers.h 83 | src/term_layout.cpp src/term_layout.h 84 | src/term_tag.h 85 | src/vm.cpp include/vm.h 86 | src/vm_impl.h 87 | src/vm_loop.cpp 88 | src/vm_loop_ctx.cpp src/vm_loop_ctx.h 89 | src/wrap.h 90 | 91 | src/bif/bif_misc.cpp include/bif/bif_misc.h 92 | src/bif/bif_proc.cpp include/bif/bif_proc.h 93 | 94 | # Zlib replacement 95 | src/miniz/miniz.cpp 96 | 97 | include/gsl/gsl.h include/gsl/array_view.h include/gsl/fail_fast.h 98 | 99 | src/platf/gsys_file.cpp src/platf/gsys_file.h 100 | src/platf/gsys_stdlib.cpp src/platf/gsys_stdlib.h 101 | src/platf/gsys_mem.cpp src/platf/gsys_mem.h 102 | ) 103 | 104 | if(G_TEST) 105 | set(SRC_LIST ${SRC_LIST} 106 | test/test.cpp test/test.h 107 | test/test_code_index.cpp 108 | test/test_process.cpp 109 | test/test_term.cpp 110 | 111 | # Header-only test framework 112 | test/fructose/fructose.h 113 | test/fructose/test_base.h 114 | test/fructose/test_root.h 115 | test/fructose/double_compare.h 116 | ) 117 | endif() 118 | 119 | set(SRC_LIBTOMMATH 120 | # deps/libtommath/tommath.h 121 | ) 122 | 123 | #SET_SOURCE_FILES_PROPERTIES(src/beam/bw_beam_load.cpp PROPERTIES COMPILE_FLAGS -emit-llvm) 124 | 125 | add_executable(${PROJECT_NAME} ${SRC_LIST} ${SRC_LIBTOMMATH}) 126 | ## Enable this to get 32-bit build 127 | ## Remember to install gcc-multilib and g++-multilib and libc6-{dev-}i386 128 | #set_target_properties(${PROJECT_NAME} PROPERTIES 129 | # COMPILE_FLAGS "-m32" LINK_FLAGS "-m32") 130 | 131 | set(G_LINK_LIBS m dl) 132 | target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT} 133 | ${G_LINK_LIBS} 134 | ) 135 | -------------------------------------------------------------------------------- /emulator/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: codegen debug testerlfiles compile vg 2 | 3 | debug: testerlfiles compile 4 | gdb _build/gluon 5 | 6 | testerlfiles: 7 | cd ../test/ && make && cd ../emulator 8 | 9 | compile: codegen 10 | cd _build && make -j7 && cd .. 11 | 12 | vg: compile 13 | valgrind --leak-check=yes --log-file="valgrind.txt" _build/gluon 14 | 15 | codegen: 16 | cd ../codegen && make && cd ../emulator 17 | 18 | format: 19 | ./format.sh 20 | -------------------------------------------------------------------------------- /emulator/TODO.md: -------------------------------------------------------------------------------- 1 | # Ideas and important things to consider 2 | 3 | * Functions and modules are first class objects. Functions can be monkey patched 4 | or deleted for user's convenience or to save memory. Fun objects created on 5 | heap can be refcounted. 6 | -------------------------------------------------------------------------------- /emulator/cmake_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rm -rf _build 3 | mkdir _build 4 | cd _build 5 | export CC=clang-3.8 6 | export CXX=clang++-3.8 7 | cmake -G "Unix Makefiles" .. 8 | -------------------------------------------------------------------------------- /emulator/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | BASE=`pwd` 4 | #for dir in src src/bif src/miniz src/platf include include/bif include/struct test; do 5 | for F in `find . -name \*.h -print -o -name \*.cpp -print -name \*.c -print`; do 6 | echo "===> $F" 7 | clang-format-3.8 -i $F 8 | done -------------------------------------------------------------------------------- /emulator/include/.gitignore: -------------------------------------------------------------------------------- 1 | predef_atoms.h 2 | genop.h 3 | 4 | -------------------------------------------------------------------------------- /emulator/include/FEATURE.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Allows loading big integer constants from BEAM files and using big 4 | // arithmetics 5 | // If this is disabled, only machine word size (32 or 64bit) integers are 6 | // allowed 7 | // TODO: bignums have to be implemented throughout loader and vm 8 | #define FEATURE_BIGNUM 0 9 | constexpr bool feature_bignum = false; 10 | 11 | // Allows loading float values from BEAM and enables float BIFs and arithmetics 12 | // 32-bit floats will be used as default (see Float in g_defs.h) 13 | // TODO: floats have to be implemented throughout loader and vm 14 | #define FEATURE_FLOAT 0 15 | constexpr bool feature_float = false; 16 | 17 | // Well, maps, map value support, map bifs, and map opcodes in VM loop 18 | // TODO: maps have to be implemented throughout loader and vm 19 | #define FEATURE_MAPS 0 20 | constexpr bool feature_maps = false; 21 | 22 | // Binary constants, binary opcodes opcodes in VM loop, binary construction and 23 | // matching features also BIFs that manipulate binaries 24 | // TODO: binaries have to be implemented throughout loader and vm 25 | #define FEATURE_BINARIES 0 26 | #define FEATURE_BIN_READ 1 /*support reading from ext term format*/ 27 | constexpr bool feature_binary = false; 28 | 29 | // Distribution features such as long pids 30 | // TODO: distribution features have to be implemented throughout loader and vm 31 | #define FEATURE_ERL_DIST 0 32 | constexpr bool feature_dist = false; 33 | 34 | // Loads line section from BEAM, enables 'line' opcodes in VM loop, resolves 35 | // line numbers for code locations. REQUIRES: FEATURE_CODE_RANGES=1 too 36 | #define FEATURE_LINE_NUMBERS 1 37 | constexpr bool feature_line_numbers = true; 38 | 39 | // Can be used separately without FEATURE_LINE_NUMBERS. Stores ranges of code 40 | // addresses to quickly identify module and function (and line number if feature 41 | // is enabled) by code location. 42 | #define FEATURE_CODE_RANGES 1 43 | constexpr bool feature_code_ranges = true; 44 | 45 | #if FEATURE_LINE_NUMBERS && !FEATURE_CODE_RANGES 46 | #error "LINE_NUMBERS feature requires also CODE_RANGES" 47 | #endif 48 | -------------------------------------------------------------------------------- /emulator/include/bif/bif_misc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "term.h" 5 | 6 | namespace gluon { 7 | 8 | class Process; 9 | 10 | namespace bif { 11 | 12 | bool is_term_smaller(const VM& vm, Term a, Term b); 13 | bool are_terms_equal(const VM& vm, Term a, Term b, bool exact); 14 | 15 | // Returns pair of {length, proper=true/improper=false} 16 | struct LengthResult { 17 | Word length : (word_bitsize - 1); 18 | bool is_proper : 1; 19 | }; 20 | LengthResult length(Term list); 21 | 22 | // A 23 | Term bif_apply_2(Process*, Term funobject, Term args); 24 | Term bif_apply_3(Process*, Term m, Term f, Term args); 25 | Term bif_atom_to_list_1(Process*, Term a); 26 | 27 | // D 28 | Term bif_divide_2(Process*, Term a, Term b); 29 | 30 | // E 31 | Term bif_element_2(Process*, Term n, Term tup); 32 | Term bif_equals_2(Process*, Term a, Term b); 33 | Term bif_equals_exact_2(Process*, Term a, Term b); 34 | 35 | // F 36 | Term bif_function_exported_3(Process* prc, Term m, Term f, Term arity); 37 | 38 | // G 39 | Term bif_greater_equal_2(Process*, Term a, Term b); 40 | 41 | // H 42 | Term bif_hd_1(Process*, Term a); 43 | 44 | // I 45 | Term bif_integer_to_list_1(Process*, Term a); 46 | Term bif_integer_to_list_2(Process*, Term a, Term base); 47 | 48 | // L 49 | Term bif_length_1(Process*, Term a); 50 | Term bif_less_equal_2(Process*, Term a, Term b); 51 | Term bif_list_to_atom_1(Process*, Term a); 52 | Term bif_list_to_existing_atom_1(Process*, Term a); 53 | 54 | // M 55 | Term bif_make_fun_3(Process*, Term m, Term f, Term arity); 56 | Term bif_minus_2(Process*, Term a, Term b); 57 | Term bif_multiply_2(Process*, Term a, Term b); 58 | 59 | // P 60 | Term bif_plus_2(Process*, Term a, Term b); 61 | Term bif_plusplus_2(Process* proc, Term a, Term b); 62 | 63 | // T 64 | Term bif_tl_1(Process*, Term a); 65 | 66 | } // ns bif 67 | } // ns gluon 68 | -------------------------------------------------------------------------------- /emulator/include/bif/bif_proc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "term.h" 5 | 6 | namespace gluon { 7 | 8 | class Process; 9 | 10 | namespace bif { 11 | 12 | // E 13 | Term bif_exit_1(Process* proc, Term what); 14 | Term bif_exit_2(Process* proc, Term pid, Term what); 15 | 16 | // G 17 | Term bif_group_leader_0(Process*); 18 | Term bif_group_leader_2(Process*, Term pid, Term gl); 19 | 20 | // I 21 | Term bif_is_process_alive_1(Process*, Term pid); 22 | 23 | // N 24 | Term bif_nif_error_1(Process*, Term what); 25 | 26 | // P 27 | Term bif_process_flag_2(Process* p, Term flag, Term value); 28 | 29 | // R 30 | Term bif_register_2(Process*, Term name, Term pid_port); 31 | 32 | // S 33 | Term bif_self_0(Process*); 34 | Term bif_spawn_3(Process*, Term m, Term f, Term args); 35 | Term bif_spawn_link_3(Process*, Term m, Term f, Term args); 36 | 37 | } // ns bif 38 | } // ns gluon 39 | -------------------------------------------------------------------------------- /emulator/include/binary.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "heap.h" 5 | #include "term.h" 6 | 7 | namespace gluon { 8 | namespace bin { 9 | 10 | static constexpr Word heapbin_limit = 64; 11 | 12 | } // ns bin 13 | } // ns gluon 14 | -------------------------------------------------------------------------------- /emulator/include/code.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | -------------------------------------------------------------------------------- /emulator/include/code_index.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "struct/dict.h" 5 | #include "wrap.h" 6 | 7 | #include 8 | 9 | namespace gluon { 10 | 11 | class Module; 12 | 13 | namespace code { 14 | 15 | #if FEATURE_CODE_RANGES 16 | class Range { 17 | public: 18 | CodePointer start; 19 | CodePointer end; // one word after code end 20 | 21 | Range() : start(nullptr), end(nullptr) {} 22 | Range(CodePointer s, CodePointer e) : start(s), end(e) {} 23 | 24 | bool contains(CodePointer p) const { 25 | // we only compare single pointer and store it in start, 'end' should be 26 | // null 27 | return p >= start && p < end; 28 | } 29 | bool operator<(const Range& other) const { 30 | // Assume ranges don't overlap so we can only compare starts 31 | return start < other.start; 32 | } 33 | }; 34 | 35 | template 36 | class Index { 37 | private: 38 | Dict ranges_; 39 | 40 | public: 41 | Index() {} 42 | 43 | // Register new loaded code as range [start,end) 44 | void add(const Range& r, T value) { ranges_.insert(r, value); } 45 | 46 | // Find code location in tree of ranges 47 | bool find(const CodePointer x, T& out) const { 48 | // I cannot into range search, something with lower_bound/upper_bound 49 | // which 50 | // compares ranges using operator < and that is too hard 51 | // TODO: fix this 52 | auto rng = ranges_.all(); 53 | while (rng.have()) { 54 | auto kv = rng.current(); 55 | if (kv->first.contains(x)) { 56 | out = kv->second; 57 | return true; 58 | } 59 | rng.advance(); 60 | } 61 | return false; 62 | } 63 | }; 64 | #endif 65 | 66 | #if G_TEST 67 | void range_test(int argc, const char* argv[]); 68 | #endif // TEST 69 | 70 | } // ns code 71 | } // ns gluon 72 | -------------------------------------------------------------------------------- /emulator/include/code_server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "code.h" 4 | #include "code_index.h" 5 | #include "defs.h" 6 | #include "error.h" 7 | #include "struct/array.h" 8 | #include "term.h" 9 | #include "wrap.h" // for CodePointer 10 | 11 | namespace gluon { 12 | 13 | // Code pointer, refers to module name, version and offset 14 | // typedef struct { 15 | // Term name; 16 | // Word version; 17 | // Word offset; 18 | //} code_ptr_t; 19 | 20 | class Module; 21 | class VM; 22 | 23 | namespace code { 24 | 25 | using ModuleMap = Dict; 26 | 27 | enum class FindModule { FindExisting, LoadIfNotFound }; 28 | 29 | class Server { 30 | private: 31 | VM& vm_; 32 | ModuleMap modules_; 33 | List search_path_; 34 | 35 | #if FEATURE_CODE_RANGES 36 | // Map code range to Module* 37 | code::Index mod_index_; 38 | #endif 39 | 40 | public: 41 | Server(VM& v) : vm_(v) {} 42 | // void init(); 43 | void load_module(Process* proc, Term name_atom); 44 | 45 | // Pass nil as name to take name automatically from the module 46 | void load_module(Process* proc, 47 | Term name_atom, 48 | ArrayView data); 49 | 50 | Module* find_module(Process* proc, Term m, FindModule opt); 51 | void path_append(const Str& p); 52 | void path_prepend(const Str& p); 53 | 54 | // Find module, function and arity for code location and print it. 55 | // Returns true if mfa was found and printed, else false 56 | bool print_mfa(const CodePointer ptr) const; 57 | bool find_mfa_from_code(const CodePointer ptr, MFArity& out) const; 58 | Export* find_mfa(const MFArity& mfa, Module** out_mod = nullptr) const; 59 | 60 | protected: 61 | Module* load_module_internal(proc::Heap* heap, 62 | Term expected_name_or_nil, 63 | ArrayView data); 64 | }; 65 | 66 | } // ns code 67 | } // ns gluon 68 | -------------------------------------------------------------------------------- /emulator/include/defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | //#include "gsl/array_view.h" 8 | #include 9 | 10 | // set features for use in code in this file 11 | #include "FEATURE.h" 12 | 13 | // Default value (set in CMakeLists.txt, do not edit here) 14 | #ifndef G_DEBUG 15 | #define G_DEBUG 1 16 | #endif 17 | 18 | #if __BYTE_ORDER == __LITTLE_ENDIAN 19 | #define G_BIGENDIAN 0 20 | #else 21 | #define G_BIGENDIAN 1 22 | #endif 23 | 24 | #if __GNUC__ 25 | #if __x86_64__ || __ppc64__ 26 | #define G_HARDWARE_BITS 64 27 | #else 28 | #define G_HARDWARE_BITS 32 29 | #endif 30 | #else 31 | #error "Define platform bit width detection code" 32 | #endif 33 | 34 | namespace gluon { 35 | 36 | #if G_DEBUG 37 | constexpr bool debug_mode = true; 38 | #else 39 | constexpr bool debug_mode = false; 40 | #endif 41 | 42 | #define FMT_HEX "%zx" 43 | #define FMT_0xHEX "0x" FMT_HEX 44 | #define FMT_SWORD "%zi" 45 | #define FMT_UWORD "%zu" 46 | 47 | #define DECL_EXCEPTION(NAME) \ 48 | class NAME : public std::runtime_error { \ 49 | public: \ 50 | NAME(const char* e) : std::runtime_error(e) {} \ 51 | virtual const char* what() const noexcept; \ 52 | }; 53 | #define IMPL_EXCEPTION(NAME) \ 54 | const char* NAME::what() const noexcept { \ 55 | return std::runtime_error::what(); \ 56 | } 57 | #define DECL_IMPL_EXCEPTION(NAME) DECL_EXCEPTION(NAME) IMPL_EXCEPTION(NAME) 58 | 59 | namespace err { 60 | DECL_EXCEPTION(FeatureMissing) 61 | DECL_EXCEPTION(TODO) 62 | DECL_EXCEPTION(BeamLoad) 63 | DECL_EXCEPTION(Scheduler) 64 | DECL_EXCEPTION(CodeServer) 65 | DECL_EXCEPTION(Process) 66 | } // ns err 67 | 68 | constexpr unsigned int word_bitsize = G_HARDWARE_BITS; 69 | 70 | // template 71 | // using array_view = gsl::array_view; 72 | 73 | // Self-deleting RAII-style pointer holder 74 | template 75 | using UniquePtr = std::unique_ptr; 76 | 77 | // Self-deleting refcounted copyable pointer 78 | template 79 | using RcPtr = std::shared_ptr; 80 | 81 | template 82 | using Pair = std::pair; 83 | 84 | template 85 | using List = std::list; 86 | 87 | template 88 | using SingleList = std::list; 89 | 90 | // Single-ended queue with push to end/pop front operations 91 | template 92 | using Queue = std::queue; 93 | 94 | template 95 | using Set = std::set; 96 | 97 | using Word = std::size_t; 98 | using SWord = std::ptrdiff_t; 99 | 100 | constexpr Word word_size(Word x) { 101 | return (x + sizeof(Word) - 1) / sizeof(Word); 102 | } 103 | 104 | using Uint8 = std::uint8_t; 105 | using Int8 = std::int8_t; 106 | using Uint16 = std::uint16_t; 107 | using Int16 = std::int16_t; 108 | using Uint32 = std::uint32_t; 109 | using Int32 = std::int32_t; 110 | using Uint64 = std::uint64_t; 111 | using Int64 = std::int64_t; 112 | 113 | using Float32 = float; 114 | using Float64 = double; 115 | using Float = Float32; 116 | 117 | // Used to shun debug printfs in release 118 | inline void dummy_printf(const char*, ...) {} 119 | 120 | namespace erts { 121 | // How many reds will a process be allowed to run before next proc wakes up 122 | // Adjust this for slow devices. 2000 is used for regular modern hardware. 123 | constexpr Word reductions_per_slice = 250; 124 | 125 | constexpr Word max_fun_arity = 16; 126 | constexpr Word max_regs = 64; // (max arity of fun + captured terms) 127 | constexpr Word max_stack = 128; // is not enforced anywhere yet 128 | constexpr Word max_fp_regs = 2; 129 | } // vm 130 | 131 | #if FEATURE_LINE_NUMBERS 132 | namespace line { 133 | constexpr bool is_valid_loc(Word File, Word Line) { 134 | return (File < 255 && Line < ((1 << 24) - 1)); 135 | } 136 | constexpr Word make_location(Word File, Word Line) { 137 | return (File << 24) | Line; 138 | } 139 | constexpr Word get_loc_file(Word Loc) { 140 | return Loc >> 24; 141 | } 142 | constexpr Word get_loc_line(Word Loc) { 143 | return Loc & ((1 << 24) - 1); 144 | } 145 | 146 | const static Word invalid_location = make_location(0, 0); 147 | } // ns line 148 | #endif 149 | 150 | namespace libc { 151 | // Used by G_ASSERT macro 152 | void assert_fail(const char* what, const char* file, int line); 153 | } // ns Std 154 | 155 | } // ns gluon 156 | 157 | // Branch prediction helper macros, use when something is going to happen much 158 | // more or much less often 159 | #define G_LIKELY(x) __builtin_expect((x), 1) 160 | #define G_UNLIKELY(x) __builtin_expect((x), 0) 161 | 162 | #define G_NORETURN __attribute__((noreturn)) 163 | 164 | //#define G_FAIL(MSG) ::fprintf(stderr, "FAIL: %s (%s:%d)\n", MSG, __FILE__, 165 | //__LINE__); ::abort(); 166 | 167 | // TODO: debug macro goes here 168 | #define G_ASSERT(X) \ 169 | if (debug_mode && !(X)) { \ 170 | libc::assert_fail(#X, __FILE__, __LINE__); \ 171 | } 172 | #define G_TODO(what) \ 173 | if (debug_mode) { \ 174 | ::fprintf(stderr, cYellow cBold "TODO:" cRst " %s (%s:%d)\n", what, \ 175 | __FILE__, __LINE__); \ 176 | throw gluon::err::TODO(what); \ 177 | } 178 | #if G_DEBUG 179 | // Famous io:format/2 skill on Linkedin! 180 | #define G_LOG gluon::libc::fmt 181 | #define G_IF_NODEBUG(X) 182 | 183 | #else // no G_DEBUG 184 | 185 | //#define G_ASSERT(X) 186 | //#define G_TODO(X) 187 | #define G_LOG dummy_printf 188 | #define G_IF_NODEBUG(X) X 189 | #endif 190 | 191 | // TODO: borrow hot/cold table or build my own 192 | #define G_ATTR_HOT __attribute((__hot__)) 193 | #define G_ATTR_COLD __attribute((__cold__)) 194 | 195 | // 196 | // Colors! 197 | // 198 | // FOREGROUND 199 | #define cRst "\x1B[0m" 200 | #define cBlack "\x1B[30m" 201 | #define cRed "\x1B[31m" 202 | #define cGreen "\x1B[32m" 203 | #define cYellow "\x1B[33m" 204 | #define cBlue "\x1B[34m" 205 | #define cMagenta "\x1B[35m" 206 | #define cCyan "\x1B[36m" 207 | #define cWhite "\x1B[37m" 208 | 209 | #define bBlack "\x1B[40m" 210 | #define bRed "\x1B[41m" 211 | #define bGreen "\x1B[42m" 212 | #define bYellow "\x1B[43m" 213 | #define bBlue "\x1B[44m" 214 | #define bMagenta "\x1B[45m" 215 | #define bCyan "\x1B[46m" 216 | #define bWhite "\x1B[47m" 217 | 218 | #define cBold "\x1B[1m" 219 | #define cUnderline "\x1B[4m" 220 | #define cItalic "\x1B[3m" 221 | #define cInverse "\x1B[7m" 222 | #define cStrike "\x1B[9m" 223 | #define noBold "\x1B[21m" 224 | #define noUnderline "\x1B[24m" 225 | #define noItalic "\x1B[23m" 226 | #define noInverse "\x1B[27m" 227 | #define noStrike "\x1B[29m" 228 | 229 | #define tRed(x) cRed x cRst 230 | #define tGreen(x) cGreen x cRst 231 | #define tYellow(x) cYellow x cRst 232 | #define tBlue(x) cBlue x cRst 233 | #define tMagenta(x) cMagenta x cRst 234 | #define tCyan(x) cCyan x cRst 235 | #define tWhite(x) cWhite x cRst 236 | #define tBold(x) cBold x cRst 237 | #define tUnderline(x) cUnderline x cRst 238 | -------------------------------------------------------------------------------- /emulator/include/dist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "term.h" 4 | 5 | namespace gluon { 6 | 7 | // Erl dist node implementation 8 | class Node { 9 | public: 10 | Term m_sysname = the_nil; 11 | dist::Creation m_creation = dist::internal_creation; 12 | }; 13 | 14 | } // ns gluon 15 | -------------------------------------------------------------------------------- /emulator/include/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "defs.h" 5 | #include "platf/gsys_stdlib.h" 6 | 7 | #define G_DEBUG_THROW_E \ 8 | if (e) { \ 9 | gluon::Std::fmt("debug throw: %s\n", e); \ 10 | gluon::Std::abort(); \ 11 | } 12 | 13 | namespace gluon { 14 | 15 | #if 0 16 | // 17 | // Wraps pair of error C string and result. Can either flag an error 18 | // (a C string) or carries result value of type T 19 | // 20 | template 21 | class Result { 22 | const char *m_what; 23 | T m_result; 24 | public: 25 | Result(const char *e, T result) 26 | : m_what(e), m_result(result) { 27 | G_DEBUG_THROW_E 28 | } 29 | bool is_error() const { return m_what != nullptr; } 30 | const char *get_error() const { return m_what; } 31 | bool is_success() const { return m_what == nullptr; } 32 | void clear() { 33 | m_what = nullptr; 34 | m_result = T(); 35 | } 36 | 37 | // Feeling lucky? call something().get_result() directly 38 | T get_result() const { 39 | G_ASSERT(is_success()); 40 | return m_result; 41 | } 42 | 43 | // Repacks error into another type of Result for easier returning 44 | template 45 | Result rewrap_error() { 46 | G_ASSERT(is_error()); 47 | return Result(m_what, U()); 48 | } 49 | }; 50 | template 51 | static Result error(const char *e) { 52 | return Result(e, T()); 53 | } 54 | template 55 | static Result success(T result) { 56 | return Result(nullptr, result); 57 | } 58 | 59 | // 60 | // Wraps error C string for a void function. Can only flag error but carries 61 | // no result value. Supports formatting of error message, for this it attempts 62 | // to allocate memory and print into it using standard library. 63 | // 64 | class MaybeError { 65 | private: 66 | const char *m_error; 67 | //bool m_owned_memory; 68 | public: 69 | MaybeError(): m_error(nullptr)/*, m_owned_memory(false)*/ {} 70 | MaybeError(const char *e): m_error(e) /*, m_owned_memory(false)*/ { 71 | G_DEBUG_THROW_E 72 | } 73 | // MaybeError(const char *e, bool own): m_error(e), m_owned_memory(own) { 74 | // G_DEBUG_THROW_E 75 | // } 76 | ~MaybeError() { 77 | // if (m_owned_memory) { 78 | // delete m_error; 79 | // } 80 | } 81 | 82 | void clear() { 83 | m_error = nullptr; 84 | // m_owned_memory = false; 85 | } 86 | // Move ctor and move assignment 87 | MaybeError(MaybeError &&other) { 88 | m_error = other.m_error; 89 | // m_owned_memory = other.m_owned_memory; 90 | 91 | other.m_error = nullptr; 92 | // other.m_owned_memory = false; 93 | } 94 | MaybeError &operator =(MaybeError &&other) { 95 | if (this != &other) { 96 | m_error = other.m_error; 97 | // m_owned_memory = other.m_owned_memory; 98 | 99 | other.m_error = nullptr; 100 | // other.m_owned_memory = false; 101 | } 102 | return *this; 103 | } 104 | 105 | bool is_error() const { return m_error != nullptr; } 106 | const char *get_error() const { return m_error; } 107 | bool is_success() const { return m_error == nullptr; } 108 | 109 | // Repacks error into MaybeResult for easier returning 110 | template 111 | Result rewrap_error() { 112 | G_ASSERT(is_success()); 113 | return Result(m_error, U()); 114 | } 115 | }; 116 | 117 | //template 118 | //static Err err_fmt(const char *_fmt, Args&&... args) { 119 | // char *err = new char[256]; // TODO: scale this or something 120 | // ::sprintf(err, _fmt, std::forward(args)...); 121 | // return Err(err, true); 122 | //} 123 | 124 | static MaybeError success() { return MaybeError(); } 125 | 126 | // 127 | // Errors are handled by two classes MaybeError and Result, both identically 128 | // can have error set as const char *. Result can carry value of T as result 129 | // (then error is guaranteed to be nullptr). The following macros allow to check 130 | // error state and forward it out of the current function. Rewrap macro allows 131 | // to convert MaybeError into Result. UNLIKELY and LIKELY macros help 132 | // branch prediction. 133 | // 134 | 135 | // Returns const char * reason of the error (auto converted to MaybeError) 136 | #define G_RETURN_IF_ERROR(res) \ 137 | if (res.is_error()) { \ 138 | return res.get_error(); \ 139 | } 140 | // Wrapped in unlikely, because we're happy to expect success 141 | #define G_RETURN_IF_ERROR_UNLIKELY(res) \ 142 | if (G_UNLIKELY(res.is_error())) { \ 143 | return res.get_error(); \ 144 | } 145 | // Same but error is likely to happen 146 | #define G_RETURN_IF_ERROR_LIKELY(res) \ 147 | if (G_LIKELY(res.is_error())) { \ 148 | return res.get_error(); \ 149 | } 150 | 151 | // Returns reason of MaybeResult rewrapped into another MaybeResult 152 | #define G_RETURN_REWRAP_IF_ERROR(res, T) \ 153 | if (res.is_error()) { \ 154 | return res.rewrap_error(); \ 155 | } 156 | // Wrapped in unlikely, because we're happy to expect success 157 | #define G_RETURN_REWRAP_IF_ERROR_UNLIKELY(res, T) \ 158 | if (G_UNLIKELY(res.is_error())) { \ 159 | return res.rewrap_error(); \ 160 | } 161 | // Same but error is likely to happen 162 | #define G_RETURN_REWRAP_IF_ERROR_LIKELY(res, T) \ 163 | if (G_LIKELY(res.is_error())) { \ 164 | return res.rewrap_error(); \ 165 | } 166 | 167 | #endif // 0 168 | 169 | } // ns gluon 170 | -------------------------------------------------------------------------------- /emulator/include/ext_term.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "error.h" 5 | #include "reader.h" 6 | #include "term.h" 7 | 8 | namespace gluon { 9 | namespace proc { 10 | class Heap; 11 | } 12 | 13 | namespace etf { 14 | 15 | constexpr Uint8 etf_marker = 131; 16 | enum class Tag : Uint8 { 17 | DistHeader = 68, // contains atom cache 18 | // 69 19 | IeeeFloatExt = 70, // 8-byte double 20 | // ... 21 | BitBinaryExt = 77, 22 | // ... 23 | Compressed = 80, 24 | // 81 25 | AtomCacheRef = 82, // used with dist header 26 | // ... 27 | SmallIntegerExt = 97, // 8bit integer 28 | IntegerExt = 98, // 32bit big endian integer 29 | OldFloatStringExt = 99, // superceded by ieee_float_ext 30 | AtomExt = 100, // atom as string 31 | ReferenceExt = 101, // encoded make_ref() 32 | PortExt = 102, // port, similar to ref() 33 | PidExt = 103, 34 | SmallTupleExt = 104, 35 | LargeTupleExt = 105, 36 | NilExt = 106, // empty list [] 37 | StringExt = 107, // 16bit size + bytes 38 | ListExt = 108, // 32bit length, elements, tail (or nil) 39 | BinaryExt = 109, 40 | SmallBigExt = 110, 41 | LargeBigExt = 111, 42 | // NEW_FUN_EXT = 112, 43 | // EXPORT_EXT = 113, 44 | // NEW_REFERENCE_EXT = 114, 45 | SmallAtomExt = 115, 46 | MapExt = 116, 47 | // FUN_EXT = 117, 48 | AtomUtf8Ext = 118, 49 | SmallAtomUtf8Ext = 119, 50 | }; 51 | 52 | // Term will be parsed and stored on heap (reads byte=131 first as an ETF tag) 53 | Term read_ext_term_with_marker(VM& vm, proc::Heap* heap, tool::Reader& r); 54 | // Term will be parsed and stored on heap (reads type tag first) 55 | Term read_ext_term(VM& vm, proc::Heap* heap, tool::Reader& r); 56 | 57 | } // ns etf 58 | } // ns gluon 59 | -------------------------------------------------------------------------------- /emulator/include/fun.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "term.h" 5 | #include "wrap.h" 6 | 7 | namespace gluon { 8 | 9 | class FunEntry { 10 | public: 11 | MFArity mfa; 12 | 13 | Word index = 0; 14 | Uint32 uniq[4] = {0, 0, 0, 0}; 15 | Word old_index = 0; 16 | Word old_uniq = 0; 17 | 18 | Word num_free = 0; // how many extra terms with frozen values 19 | CodePointer code; 20 | 21 | FunEntry() = default; 22 | }; 23 | 24 | #pragma clang diagnostic push 25 | #pragma clang diagnostic ignored "-Wzero-length-array" 26 | // TODO: pack this better in memory? 27 | class BoxedFun { 28 | public: 29 | // This struct will begin boxed memory at fun object location, followed by 30 | // 0 or several captured frozen terms (closure). First field includes 31 | // boxed subtag in 4 lower bits -- num_free:19,arity:8,subtag:4 32 | Word hdr; 33 | Term pid; 34 | Term module; 35 | Word index; 36 | Uint32 uniq[4]; 37 | Word old_index; 38 | Word old_uniq; 39 | FunEntry* fun_entry; 40 | Term frozen[0]; // captured terms (closure) 41 | 42 | Word get_arity() const { return (Uint8)(hdr >> 4); } 43 | Word get_num_free() const { return (hdr >> (4 + 8)) & 0x7ffff; } 44 | }; 45 | #pragma clang diagnostic pop 46 | 47 | // 48 | // Boxed callable functional object 49 | // 50 | class FunObject : public Term { 51 | public: 52 | FunObject(Word x) : Term(x) { G_ASSERT(is_boxed_fun()); } 53 | FunObject(Term& other) : Term(other.value()) { G_ASSERT(is_boxed_fun()); } 54 | 55 | // 56 | // Boxed callable object (a fun) 57 | // 58 | static FunObject make(BoxedFun* p) { 59 | return FunObject(term_tag::BoxedFun::create_from_ptr(p)); 60 | } 61 | BoxedFun* get_object() const { return boxed_get_ptr(); } 62 | }; 63 | 64 | namespace fun { 65 | 66 | // Fills words of memory mem with some fields from fun_entry_t and frozen terms. 67 | // Returns boxable pointer. Mem should have enough words for BoxedFun and 68 | // some extra for captured terms (closure). 69 | // @args: fe - lambda entry from beam file, mem - will host BoxedFun and extra 70 | // captured values, pid - oh well, its a pid; frozen - memory where we copy 71 | // captured values from (pointer to registers basically) 72 | Term box_fun(proc::Heap* heap, FunEntry* fe, Term pid, Term* frozen); 73 | BoxedFun* box_fun(FunEntry* fe, Word* mem, Term pid, Term* frozen); 74 | 75 | } // ns fun 76 | 77 | } // ns gluon 78 | -------------------------------------------------------------------------------- /emulator/include/functional.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace gluon { 6 | 7 | // Strongly typed pair of 2 any types. Only one can be active at any time 8 | template 9 | class Either { 10 | private: 11 | bool m_is_left; 12 | union { 13 | L m_left; 14 | R m_right; 15 | }; 16 | Either() {} 17 | 18 | // Either must be declared with different types for L and R 19 | static_assert(std::is_same::value == false, "L must != R"); 20 | 21 | public: 22 | Either(const L& l) : m_is_left(true), m_left(l) {} 23 | Either(const R& r) : m_is_left(false), m_right(r) {} 24 | bool is_left() const { return m_is_left; } 25 | L& left() { 26 | G_ASSERT(is_left()); 27 | return m_left; 28 | } 29 | const L& left() const { 30 | G_ASSERT(is_left()); 31 | return m_left; 32 | } 33 | R& right() { 34 | G_ASSERT(!is_left()); 35 | return m_right; 36 | } 37 | const R& right() const { 38 | G_ASSERT(!is_left()); 39 | return m_right; 40 | } 41 | }; 42 | 43 | template 44 | void for_each(Mapping& m, Callable fn) { 45 | while (m.have()) { 46 | fn(m.current()); 47 | m.advance(); 48 | } 49 | } 50 | } // ns gluon 51 | -------------------------------------------------------------------------------- /emulator/include/gsl/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Microsoft Corporation. All rights reserved. 2 | 3 | This code is licensed under the MIT License (MIT). 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /emulator/include/gsl/README.md: -------------------------------------------------------------------------------- 1 | # GSL: Guidelines Support Library [![Build Status](https://travis-ci.org/Microsoft/GSL.svg?branch=master)](https://travis-ci.org/Microsoft/GSL) 2 | 3 | The Guidelines Support Library (GSL) contains functions and types that are suggested for use by the 4 | [C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines) maintained by the [Standard C++ Foundation](https://isocpp.org). 5 | This repo contains Microsoft's implementation of GSL. 6 | 7 | The library includes types like `array_view<>`, `string_view<>`, `owner<>` and others. 8 | 9 | The entire implementation is provided inline in the headers under the [include](./include) directory. 10 | 11 | While some types have been broken out into their own headers (e.g. [include/array_view.h](./include/array_view.h)), 12 | it is simplest to just include [gsl.h](./include/gsl.h) and gain access to the entire library. 13 | 14 | > NOTE: We encourage contributions that improve or refine any of the types in this library as well as ports to 15 | other platforms. Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for more information about contributing. 16 | 17 | # Quick Start 18 | ## Supported Platforms 19 | The test suite that exercises GSL has been built and passes successfully on the following platforms: 20 | 21 | * Windows using Visual Studio 2013 22 | * Windows using Visual Studio 2015 23 | * Windows using Clang/LLVM 3.6 24 | * Windows using GCC 5.1 25 | * GNU/Linux using Clang/LLVM 3.6 26 | * GNU/Linux using GCC 5.1 27 | * OS X Yosemite using Xcode with AppleClang 7.0.0.7000072 28 | * OS X Yosemite using GCC-5.2.0 29 | * FreeBSD 10.x with Clang/LLVM 3.6 30 | 31 | > If you successfully port GSL to another platform, we would love to hear from you. Please submit an issue to let us know. Also please consider 32 | contributing any changes that were necessary back to this project to benefit the wider community. 33 | 34 | ## Building the tests 35 | To build the tests, you will require the following: 36 | 37 | * [CMake](http://cmake.org), version 2.8.7 or later to be installed and in your PATH. 38 | * [UnitTest-cpp](https://github.com/Microsoft/unittest-cpp), to be cloned under the [tests/unittest-cpp](./tests/unittest-cpp) directory 39 | of your GSL source. 40 | 41 | These steps assume the source code of this repository has been cloned into a directory named `c:\GSL`. 42 | 43 | 1. Create a directory to contain the build outputs for a particular architecture (we name it c:\GSL\vs14-x86 in this example). 44 | 45 | cd GSL 46 | md build-x86 47 | cd build-x86 48 | 49 | 2. Configure CMake to use the compiler of your choice (you can see a list by running `cmake --help`). 50 | 51 | cmake -G "Visual Studio 14 2015" c:\GSL 52 | 53 | 3. Build the test suite (in this case, in the Debug configuration, Release is another good choice). 54 | 55 | cmake --build . --config Debug 56 | 57 | 4. Run the test suite. 58 | 59 | ctest -C Debug 60 | 61 | All tests should pass - indicating your platform is fully supported and you are ready to use the GSL types! 62 | 63 | ## Using the libraries 64 | As the types are entirely implemented inline in headers, there are no linking requirements. 65 | 66 | Just place the contents of the [include](./include) directory within your source tree so it is available 67 | to your compiler, then include the appropriate headers in your program, and away you go! 68 | -------------------------------------------------------------------------------- /emulator/include/gsl/fail_fast.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright (c) 2015 Microsoft Corporation. All rights reserved. 4 | // 5 | // This code is licensed under the MIT License (MIT). 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 13 | // THE SOFTWARE. 14 | // 15 | /////////////////////////////////////////////////////////////////////////////// 16 | 17 | #pragma once 18 | 19 | #ifndef GSL_FAIL_FAST_H 20 | #define GSL_FAIL_FAST_H 21 | 22 | #include 23 | 24 | #if defined(GSL_THROWS_FOR_TESTING) 25 | #include 26 | #endif 27 | 28 | namespace gsl { 29 | 30 | // 31 | // Having "fail fast" result in an exception makes unit testing 32 | // the GSL classes that rely upon it much simpler. 33 | // 34 | #if defined(GSL_THROWS_FOR_TESTING) 35 | 36 | struct fail_fast : public std::runtime_error { 37 | fail_fast() : std::runtime_error("") {} 38 | explicit fail_fast(char const* const message) 39 | : std::runtime_error(message) {} 40 | }; 41 | 42 | void fail_fast_assert(bool cond) { 43 | if (!cond) 44 | throw fail_fast(); 45 | } 46 | void fail_fast_assert(bool cond, const char* const message) { 47 | if (!cond) 48 | throw fail_fast(message); 49 | } 50 | 51 | #else 52 | 53 | void fail_fast_assert(bool cond) { 54 | if (!cond) 55 | std::terminate(); 56 | } 57 | void fail_fast_assert(bool cond, const char* const) { 58 | if (!cond) 59 | std::terminate(); 60 | } 61 | 62 | #endif // GSL_THROWS_FOR_TESTING 63 | } 64 | 65 | #endif // GSL_FAIL_FAST_H 66 | -------------------------------------------------------------------------------- /emulator/include/gsl/string_view.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright (c) 2015 Microsoft Corporation. All rights reserved. 4 | // 5 | // This code is licensed under the MIT License (MIT). 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 13 | // THE SOFTWARE. 14 | // 15 | /////////////////////////////////////////////////////////////////////////////// 16 | 17 | #pragma once 18 | 19 | #ifndef GSL_STRING_VIEW_H 20 | #define GSL_STRING_VIEW_H 21 | 22 | #include 23 | #include "array_view.h" 24 | 25 | namespace gsl { 26 | // 27 | // czstring and wzstring 28 | // 29 | // These are "tag" typedef's for C-style strings (i.e. null-terminated character 30 | // arrays) 31 | // that allow static analysis to help find bugs. 32 | // 33 | // There are no additional features/semantics that we can find a way to add 34 | // inside the 35 | // type system for these types that will not either incur significant runtime 36 | // costs or 37 | // (sometimes needlessly) break existing programs when introduced. 38 | // 39 | template 40 | using czstring = const char*; 41 | 42 | template 43 | using cwzstring = const wchar_t*; 44 | 45 | template 46 | using zstring = char*; 47 | 48 | template 49 | using wzstring = wchar_t*; 50 | 51 | // 52 | // string_view and relatives 53 | // 54 | // Note that Extent is always single-dimension only 55 | // Note that SizeType is defaulted to be smaller than size_t which is the 56 | // array_view default 57 | // 58 | // TODO (neilmac) once array_view regains configurable size_type, update these 59 | // typedef's 60 | // 61 | template 62 | using basic_string_view = array_view; 63 | 64 | template 65 | using string_view = basic_string_view; 66 | 67 | template 68 | using cstring_view = basic_string_view; 69 | 70 | template 71 | using wstring_view = basic_string_view; 72 | 73 | template 74 | using cwstring_view = basic_string_view; 75 | 76 | // 77 | // ensure_sentinel() 78 | // 79 | // Provides a way to obtain an array_view from a contiguous sequence 80 | // that ends with a (non-inclusive) sentinel value. 81 | // 82 | // Will fail-fast if sentinel cannot be found before max elements are examined. 83 | // 84 | template 85 | array_view ensure_sentinel( 86 | const T* seq, 87 | SizeType max = std::numeric_limits::max()) { 88 | auto cur = seq; 89 | while ((cur - seq) < max && *cur != Sentinel) 90 | ++cur; 91 | fail_fast_assert(*cur == Sentinel); 92 | return {seq, cur - seq}; 93 | } 94 | 95 | // 96 | // ensure_z - creates a string_view for a czstring or cwzstring. 97 | // Will fail fast if a null-terminator cannot be found before 98 | // the limit of size_type. 99 | // 100 | template 101 | inline basic_string_view ensure_z( 102 | T* const& sz, 103 | size_t max = std::numeric_limits::max()) { 104 | return ensure_sentinel<0>(sz, max); 105 | } 106 | 107 | // TODO (neilmac) there is probably a better template-magic way to get the const 108 | // and non-const overloads to share an implementation 109 | inline basic_string_view ensure_z(char* const& sz, 110 | size_t max) { 111 | auto len = strnlen(sz, max); 112 | fail_fast_assert(sz[len] == 0); 113 | return {sz, len}; 114 | } 115 | 116 | inline basic_string_view ensure_z( 117 | const char* const& sz, 118 | size_t max) { 119 | auto len = strnlen(sz, max); 120 | fail_fast_assert(sz[len] == 0); 121 | return {sz, len}; 122 | } 123 | 124 | inline basic_string_view ensure_z(wchar_t* const& sz, 125 | size_t max) { 126 | auto len = wcsnlen(sz, max); 127 | fail_fast_assert(sz[len] == 0); 128 | return {sz, len}; 129 | } 130 | 131 | inline basic_string_view ensure_z( 132 | const wchar_t* const& sz, 133 | size_t max) { 134 | auto len = wcsnlen(sz, max); 135 | fail_fast_assert(sz[len] == 0); 136 | return {sz, len}; 137 | } 138 | 139 | template 140 | basic_string_view ensure_z(T (&sz)[N]) { 141 | return ensure_z(&sz[0], N); 142 | } 143 | 144 | template 145 | basic_string_view::type, 146 | dynamic_range> 147 | ensure_z(Cont& cont) { 148 | return ensure_z(cont.data(), cont.length()); 149 | } 150 | 151 | // 152 | // to_string() allow (explicit) conversions from string_view to string 153 | // 154 | template 155 | std::basic_string::type> to_string( 156 | basic_string_view view) { 157 | return {view.data(), view.length()}; 158 | } 159 | 160 | template 161 | class basic_zstring_builder { 162 | public: 163 | using string_view_type = basic_string_view; 164 | using value_type = CharT; 165 | using pointer = CharT*; 166 | using size_type = typename string_view_type::size_type; 167 | using iterator = typename string_view_type::iterator; 168 | 169 | basic_zstring_builder(CharT* data, size_type length) : sv_(data, length) {} 170 | 171 | template 172 | basic_zstring_builder(CharT (&arr)[Size]) : sv_(arr) {} 173 | 174 | pointer data() const { return sv_.data(); } 175 | string_view_type view() const { return sv_; } 176 | 177 | size_type length() const { return sv_.length(); } 178 | 179 | pointer assume0() const { return data(); } 180 | string_view_type ensure_z() const { return gsl::ensure_z(sv_); } 181 | 182 | iterator begin() const { return sv_.begin(); } 183 | iterator end() const { return sv_.end(); } 184 | 185 | private: 186 | string_view_type sv_; 187 | }; 188 | 189 | template 190 | using zstring_builder = basic_zstring_builder; 191 | 192 | template 193 | using wzstring_builder = basic_zstring_builder; 194 | } 195 | 196 | #endif // GSL_STRING_VIEW_H 197 | -------------------------------------------------------------------------------- /emulator/include/heap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "error.h" 5 | #include "platf/gsys_mem.h" 6 | #include "stack.h" 7 | 8 | //#include 9 | //#include 10 | 11 | namespace gluon { 12 | 13 | class VM; 14 | class Term; 15 | 16 | template 17 | static constexpr Word calculate_storage_size() { 18 | return ((sizeof(T) + sizeof(Word) - 1) / sizeof(Word)) * sizeof(Word); 19 | } 20 | static constexpr Word calculate_word_size(Word bytes) { 21 | return ((bytes + sizeof(Word) - 1) / sizeof(Word)) * sizeof(Word); 22 | } 23 | 24 | namespace mem { 25 | 26 | // 27 | // Takes memory blocks directly from system_memory (whatever that is) without 28 | // any additional tricks, segmenting, grouping by size, free-lists and so on 29 | // 30 | class SystemMemoryAllocator { 31 | public: 32 | template 33 | T* allocate() { 34 | return SysMemory::allocate(); 35 | } 36 | 37 | template 38 | T* allocate(size_t n) { 39 | mem::Blk blk = SysMemory::allocate(n); 40 | return blk.mem(); 41 | } 42 | 43 | template 44 | T* alloc_object(Args&&... args) { // NOTE: calls ctor 45 | Word* bytes = allocate(calculate_storage_size()); 46 | return new (bytes) T(std::forward(args)...); 47 | } 48 | 49 | template 50 | void deallocate_one(T* mem) { 51 | mem::Blk mem1(mem, 1); 52 | SysMemory::deallocate(mem1); 53 | } 54 | 55 | template 56 | void deallocate_many(T* mem, size_t sz) { 57 | mem::Blk mem1(mem, sz); 58 | SysMemory::deallocate(mem1); 59 | } 60 | }; 61 | 62 | } // ns mem 63 | 64 | namespace erts { 65 | 66 | // VM heap is abstract interface which gets memory from underlying system. 67 | class Heap : public mem::SystemMemoryAllocator {}; 68 | 69 | } // ns vm 70 | 71 | namespace proc { 72 | 73 | static constexpr Word DEFAULT_PROC_HEAP_WORDS = 100000; 74 | static constexpr Word DEFAULT_PROC_STACK_WORDS = 5000; 75 | // Allocated heap segments sequentially grow from DEFAULT_PROC_HEAP_WORDS (100) 76 | // up to 2^HEAP_SEGMENT_GROWTH_MAX (100*2^8=100kb on 32bit or 200kb on 64bit) 77 | // All new blocks after 8th will be capped at this size. 78 | static constexpr Word HEAP_SEGMENT_GROWTH_MAX = 8; 79 | 80 | class Heap; 81 | 82 | // Heap node for process heap. We begin with small enough heap and grow 83 | // by allocating double that every time when we run out of memory in last 84 | // node. Fields of Node take first words of the heap then follows free memory. 85 | #pragma clang diagnostic push 86 | #pragma clang diagnostic ignored "-Wzero-length-array" 87 | class Node { 88 | public: 89 | static constexpr Word FIELDS_WORD_SIZE = 90 | 3; // how many words this class takes 91 | static_assert(DEFAULT_PROC_STACK_WORDS < 92 | DEFAULT_PROC_HEAP_WORDS - FIELDS_WORD_SIZE, 93 | "default stack does not fit default heap size"); 94 | 95 | Node* next = nullptr; 96 | Word* start; // this points at first free space and grows 97 | Word* limit; // marks end of node 98 | Word heap_start[0]; // marks end of headers and beginning of heap 99 | 100 | static Node* create(Word sz_words); 101 | 102 | Word get_avail() const { 103 | G_ASSERT(limit >= start); 104 | return (Word)(limit - start); 105 | } 106 | // Allocated memory is not tagged in any way except regular term bitfields 107 | Word* allocate_words(Word n) { 108 | auto result = start; 109 | start += n; 110 | return result; 111 | } 112 | }; 113 | #pragma clang diagnostic pop 114 | 115 | // 116 | // Segmented heap which grows by allocating double the last segment size 117 | // and never shrinks (until process dies). Also contains stack which may migrate 118 | // out of its home node. 119 | // 120 | /* 121 | class SegmentedHeap { 122 | // 123 | // Heap 124 | // 125 | // track count of nodes allocated, each new is double of previous 126 | Word m_node_count = 1; 127 | Node *m_current; 128 | Node *m_root; 129 | 130 | public: 131 | Stack m_stack; 132 | 133 | public: 134 | // Allocates DEFAULT_PROC_HEAP_WORDS and writes Node header there, uses it as 135 | // initial heap node 136 | SegmentedHeap(); 137 | 138 | // Grows htop and gives requested memory 139 | Word *h_alloc(Word); 140 | 141 | // TODO: Mark memory so that GC will know its size 142 | Word *h_alloc_bytes(Word bytes) { 143 | return h_alloc(calculate_word_size(bytes)); 144 | } 145 | 146 | // TODO: Mark memory so that GC will know its size 147 | template 148 | T *h_alloc_object(Args&&... args) { // NOTE: calls ctor 149 | Word *bytes = h_alloc(calculate_storage_size()); 150 | return new(bytes)T(std::forward(args)...); 151 | } 152 | }; // class SegmentedHeap 153 | */ 154 | 155 | template 156 | class Heap_ : public A { 157 | // constexpr static Word STK_SZ = 1024; 158 | // Word stack_data_[STK_SZ]; 159 | 160 | public: 161 | Stack stack_; 162 | 163 | // Heap_() : A(), stack_(&stack_data_[0], &stack_data_[STK_SZ]) {} 164 | Heap_() : A(), stack_() {} 165 | }; 166 | 167 | class Heap : public Heap_ {}; 168 | 169 | // Takes all terms between 'start' and 'end', and copies them to 'dstheap', new 170 | // resulting terms are placed in array 'dst' which should be large enough. 171 | bool copy_terms(VM& vm, 172 | Heap* dstheap, 173 | const Term* start, 174 | const Term* end, 175 | Term* dst); 176 | // Copies one term 't' to 'dstheap' returns new clone term located in new heap 177 | Term copy_one_term(VM& vm, Heap* dstheap, Term t); 178 | 179 | } // ns proc 180 | } // ns gluon 181 | -------------------------------------------------------------------------------- /emulator/include/mailbox.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "term.h" 5 | 6 | namespace gluon { 7 | namespace proc { 8 | 9 | // 10 | // Mailbox 11 | // 12 | // Stuff arrives here, TODO: make this on process heap 13 | // Requirements to data structure: 14 | // * step forward 15 | // * ability to address any cell via iterator or a pointer 16 | // * ability to remove arbitrary item pointed at with iterator 17 | // * ability to go 1 step beyond the end (zero mark) 18 | class Mailbox { 19 | private: 20 | using MailboxStorage = List; 21 | using ConstIterator = MailboxStorage::const_iterator; 22 | 23 | MailboxStorage messages_; 24 | ConstIterator current_ = messages_.end(); 25 | 26 | // Set by recv_mark opcode and read by recv_set opcode 27 | Word saved_mark_label_; 28 | ConstIterator saved_mark_; 29 | 30 | public: 31 | Mailbox(); 32 | 33 | void on_incoming(Term value); 34 | Term get_current(); 35 | void remove_current(); 36 | void step_next(); 37 | void mark_position(Word label); 38 | void set_to_marked(Word label); 39 | }; 40 | 41 | } // ns proc 42 | } // ns mailbox 43 | -------------------------------------------------------------------------------- /emulator/include/module.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "code_index.h" 4 | #include "defs.h" 5 | #include "error.h" 6 | #include "fun.h" 7 | #include "reader.h" 8 | #include "struct/array.h" 9 | #include "term.h" 10 | #include "wrap.h" 11 | 12 | namespace gluon { 13 | 14 | class VM; 15 | class Heap; 16 | 17 | class Export { 18 | // first field stores bits used to tag boxed value and boolean bif flag 19 | // (shifted left by BOXED_SUBTAG_BITS) 20 | Word hdr_; 21 | // either biffn or code must be not-null 22 | union { 23 | CodePointer code_; 24 | void* bif_fn_; // use VM::apply_bif with this and mfa.arity 25 | }; 26 | 27 | public: 28 | MFArity mfa; 29 | 30 | Export() {} 31 | Export(void* biffn) 32 | : hdr_(term_tag::BoxedExport::create_subtag((Word)(biffn != nullptr))), 33 | bif_fn_(biffn) { 34 | G_ASSERT(biffn != nullptr); // either biffn or code must be not-null 35 | } 36 | Export(CodePointer co, const MFArity& _mfa) 37 | : hdr_(term_tag::BoxedExport::create_subtag((Word) false)), 38 | code_(co), 39 | mfa(_mfa) { 40 | G_ASSERT(co.is_not_null()); // either biffn or code must be not-null 41 | } 42 | 43 | static const Word BIF_BIT = (1U << term_tag::boxed_subtag_bits); 44 | bool is_bif() const { return (hdr_ & BIF_BIT) == BIF_BIT; } 45 | CodePointer code() const { 46 | G_ASSERT(is_bif() == false); 47 | return code_; 48 | } 49 | void* bif_fn() const { 50 | G_ASSERT(is_bif() == true); 51 | return bif_fn_; 52 | } 53 | }; 54 | 55 | // 56 | // Class Module represents a single Erlang module with code. When multiple 57 | // versions of same module are loaded, you'll find one Module for each version 58 | // 59 | class Module { 60 | public: 61 | using Labels = Dict; 62 | using Exports = Dict; 63 | using Imports = Vector; 64 | using Lambdas = Vector; 65 | 66 | using LineRefs = Vector; // 24bit line + 8bit file index 67 | using FileNames = Vector; // atoms with file names 68 | 69 | private: 70 | VM* vm_; 71 | Term name_; 72 | Labels labels_; 73 | Exports exports_; // just list of {f/arity} 74 | Imports imports_; 75 | Lambdas lambdas_; 76 | 77 | #if FEATURE_CODE_RANGES 78 | // Map code range to fun/arity pair 79 | code::Index fun_index_; 80 | #endif 81 | #if FEATURE_LINE_NUMBERS 82 | LineRefs line_refs_; 83 | FileNames filenames_; 84 | #endif 85 | 86 | // Instruction layout in code: { void *label; Term args[arity] } 87 | Vector code_; 88 | 89 | public: 90 | Module(Term name, Imports& imp) : name_(name) { imports_ = std::move(imp); } 91 | 92 | Module(const Module& src) = delete; 93 | Module(Module&& src) = default; 94 | 95 | Term get_name() const { 96 | G_ASSERT(name_.is_atom()); 97 | return name_; 98 | } 99 | 100 | Word read_word(Word ptr) const { 101 | G_ASSERT(ptr < code_.size()); 102 | return code_[ptr]; 103 | } 104 | Export* find_export(const FunArity& fa) { return exports_.find_ptr(fa); } 105 | 106 | // Resolves label to a code pointer 107 | CodePointer resolve_label(LabelIndex label); 108 | 109 | void set_code(Vector& code) { 110 | code_ = std::move(code); // take ownership 111 | } 112 | void set_labels(Labels& labels) { labels_ = std::move(labels); } 113 | void set_exports(Exports& e); 114 | void set_lambdas(Lambdas& la) { lambdas_ = std::move(la); } 115 | #if FEATURE_CODE_RANGES 116 | code::Range get_code_range(); 117 | void set_fun_ranges(code::Index& ci) { 118 | fun_index_ = std::move(ci); 119 | } 120 | bool find_fun_arity(CodePointer ptr, FunArity& out) const; 121 | #endif 122 | 123 | void set_line_numbers(LineRefs& lr, FileNames& fn) { 124 | if (feature_line_numbers) { 125 | line_refs_ = std::move(lr); 126 | filenames_ = std::move(fn); 127 | } 128 | } 129 | 130 | MFArity* get_import_entry(Word i) { 131 | G_ASSERT(i < imports_.size()); 132 | return &(imports_[i]); 133 | } 134 | FunEntry* get_lambda_entry(Word i) { 135 | G_ASSERT(i < lambdas_.size()); 136 | return &(lambdas_[i]); 137 | } 138 | }; 139 | 140 | } // ns gluon 141 | -------------------------------------------------------------------------------- /emulator/include/process_fail.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "term.h" 4 | 5 | namespace gluon { 6 | namespace proc { 7 | 8 | enum class FailType : Word { 9 | None, 10 | Exit, // Process termination - not an error 11 | Error, // Error (adds stacktrace; will be logged) 12 | Throw // Nonlocal return (turns into a 'nocatch' error if not caught) 13 | }; 14 | 15 | // 16 | // Holds process error state and provides accessors to it 17 | // Does not have knowledge about owning process 18 | // 19 | class Fail { 20 | private: 21 | bool panic_ : 1; // ignore catches 22 | bool thrown_ : 1; // nonlocal return 23 | bool log_ : 1; // write to logger on termination 24 | // bool native: 1; // occurred in native code (not impl) 25 | bool save_trace_ : 1; // save stack trace in internal form (not impl) 26 | bool arg_list_ : 1; // has arglist for top of trace 27 | FailType type_ : 2; 28 | Term value_ = the_non_value; 29 | Term last_trace_ = the_nil; // latest stack dump 30 | 31 | public: 32 | Fail() 33 | : panic_(false), 34 | thrown_(false), 35 | log_(false), 36 | arg_list_(false), 37 | type_(FailType::None) {} 38 | 39 | FailType type() const { return type_; } 40 | void type(FailType ft) { type_ = ft; } 41 | Term type_as_atom() const; 42 | 43 | Term last_trace() const { return last_trace_; } 44 | 45 | Term value() const { return value_; } 46 | void value(Term v) { value_ = v; } 47 | 48 | bool is_failed() const { return type_ != FailType::None; } 49 | bool is_not_failed() const { return type_ == FailType::None; } 50 | 51 | bool is_panic() const { return panic_; } 52 | bool is_arg_list_set() const { return arg_list_; } 53 | bool is_save_trace_set() const { return save_trace_; } 54 | 55 | void clear() { 56 | type_ = FailType::None; 57 | value_ = the_non_value; 58 | panic_ = thrown_ = log_ = arg_list_ = false; 59 | } 60 | 61 | // Sets error state (no special logic is hidden there) 62 | // type is atom error|throw|exit, unknown will become error 63 | void set(FailType ft, Term reason); 64 | static FailType to_fail_type(Term type); 65 | 66 | // Fills fields for some common error reasons 67 | void set_normal_exit(); // exit 68 | void set_internal_error(Term v); // error+panic 69 | void set_badarg(proc::Heap* heap, Term v); 70 | void set_badarg(); 71 | void set_badarith(); 72 | void set_badmatch(); 73 | void set_fun_clause(); 74 | void set_case_clause(); 75 | void set_if_clause(); 76 | void set_undef(); 77 | void set_badfun(); 78 | void set_badarity(); 79 | void set_timeout_value(); 80 | void set_noproc(); 81 | void set_notalive(); 82 | void set_system_limit(); 83 | void set_try_clause(); 84 | void set_not_supported(); 85 | void set_badmap(); 86 | void set_badkey(); 87 | }; 88 | 89 | } // ns proc 90 | } // ns gluon 91 | -------------------------------------------------------------------------------- /emulator/include/scheduler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "error.h" 5 | #include "struct/dict.h" 6 | #include "term.h" 7 | 8 | #include 9 | 10 | namespace gluon { 11 | 12 | class Process; 13 | 14 | // A single core process scheduler with timers, queues and stuff 15 | class Scheduler { 16 | private: 17 | using Queue = List; 18 | using WaitRoom = Set; 19 | 20 | VM& vm_; 21 | Queue low_queue_; // lowest priority (background) 22 | Queue normal_queue_; // normal priority (8x low) 23 | Queue high_queue_; // highest (realtime) priority 24 | WaitRoom inf_wait_; // on infinite receive 25 | WaitRoom timed_wait_; // on timed receive 26 | 27 | Word pid_counter_ = 0; 28 | Process* current_ = nullptr; 29 | 30 | Dict pid_to_proc_; 31 | 32 | // 33 | // Scheduling algorithm 34 | // 35 | 36 | // how often low prio gets to run despite all the business 37 | static constexpr Word normal_advantage = 8; 38 | 39 | Word normal_count_ = 0; 40 | 41 | public: 42 | Scheduler(VM& vm) : vm_(vm) {} 43 | 44 | // Register process in one of queues according to its priority. New pid is 45 | // set 46 | void add_new_runnable(Process* p); 47 | 48 | Process* next(); 49 | void queue_by_priority(Process* p); 50 | Process* find(Term pid) const; 51 | void exit_process(Process* p, Term reason); 52 | // Wake up if process was waiting or timed-waiting 53 | void on_new_message(Process* p); 54 | 55 | protected: 56 | static bool contains(Queue& q, Process* p) { 57 | return std::find(q.begin(), q.end(), p) != q.end(); 58 | } 59 | static bool contains(WaitRoom& q, Process* p) { 60 | return q.find(p) != q.end(); 61 | } 62 | }; 63 | 64 | } // ns gluon 65 | -------------------------------------------------------------------------------- /emulator/include/struct/array.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "defs.h" 6 | 7 | namespace gluon { 8 | 9 | namespace containers { 10 | 11 | // Wraps STL-compatible vector or fixed size array. Reimplement as appropriate. 12 | template 13 | class STLArray { 14 | private: 15 | using Self = STLArray; 16 | ContainerType data_; 17 | 18 | public: 19 | using Iterator = typename ContainerType::iterator; 20 | using ConstIterator = typename ContainerType::const_iterator; 21 | using RevIterator = typename ContainerType::reverse_iterator; 22 | using ConstRevIterator = typename ContainerType::const_reverse_iterator; 23 | using Value = typename ContainerType::value_type; 24 | 25 | size_t size() const { return data_.size(); } 26 | 27 | Value& operator[](size_t i) { 28 | G_ASSERT(i < size()); 29 | return data_[i]; 30 | } 31 | const Value& operator[](size_t i) const { 32 | G_ASSERT(i < size()); 33 | return data_[i]; 34 | } 35 | 36 | // Storage access 37 | Value* data() { return data_.data(); } 38 | const Value* data() const { return data_.data(); } 39 | 40 | Value& front() { return data_.front(); } 41 | const Value& front() const { return data_.front(); } 42 | 43 | Value& back() { return data_.back(); } 44 | const Value& back() const { return data_.back(); } 45 | 46 | Iterator begin() { return data_.begin(); } 47 | Iterator end() { return data_.end(); } 48 | ConstIterator begin() const { return data_.begin(); } 49 | ConstIterator end() const { return data_.end(); } 50 | 51 | RevIterator rbegin() { return data_.rbegin(); } 52 | RevIterator rend() { return data_.rend(); } 53 | ConstRevIterator rbegin() const { return data_.rbegin(); } 54 | ConstRevIterator rend() const { return data_.rend(); } 55 | 56 | void resize(size_t sz) { data_.resize(sz); } 57 | void reserve(size_t sz) { data_.reserve(sz); } 58 | 59 | void push_back(const Value& x) { data_.push_back(x); } 60 | }; 61 | 62 | } // ns containers 63 | 64 | // A fixed array 65 | template 66 | using Array = containers::STLArray>; 67 | 68 | // A growable vector 69 | template 70 | using Vector = containers::STLArray>; 71 | 72 | // Non-owning pointer to a window of memory 73 | template 74 | class ArrayView { 75 | private: 76 | T* start_ = nullptr; 77 | T* end_ = nullptr; 78 | 79 | public: 80 | ArrayView() {} 81 | ArrayView(T* start, size_t sz) : start_(start), end_(start + sz) {} 82 | 83 | T* data() const { return start_; } 84 | T* limit() const { return end_; } 85 | size_t size() const { 86 | G_ASSERT(end_ >= start_); 87 | return (size_t)(end_ - start_); 88 | } 89 | }; 90 | 91 | } // ns gluon 92 | -------------------------------------------------------------------------------- /emulator/include/struct/dict.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "defs.h" 5 | 6 | namespace gluon { 7 | 8 | namespace containers { 9 | 10 | template 11 | class DictRangeMapping { 12 | public: 13 | using Storage = std::map; 14 | using Iterator = typename Storage::iterator; 15 | 16 | Iterator pos; 17 | Iterator end; 18 | 19 | DictRangeMapping(const Iterator& b, const Iterator& e) : pos(b), end(e) {} 20 | 21 | bool have() const { return pos != end; } 22 | Iterator current() { return pos; } 23 | void advance() { pos++; } 24 | }; 25 | 26 | // TODO: std::enable_if magic with const/nonconst members? 27 | template 28 | class ConstDictRangeMapping { 29 | public: 30 | using Storage = std::map; 31 | using Iterator = typename Storage::const_iterator; 32 | 33 | Iterator pos; 34 | Iterator end; 35 | ConstDictRangeMapping(const Iterator& b, const Iterator& e) 36 | : pos(b), end(e) {} 37 | 38 | bool have() const { return pos != end; } 39 | Iterator current() { return pos; } 40 | void advance() { pos++; } 41 | }; 42 | 43 | // A map wrapper with mapping helper classes (to iterate) 44 | // TODO: Replace this with self-made structure 45 | template 46 | class STLDict { 47 | private: 48 | using Self = STLDict; 49 | using Storage = std::map; 50 | Storage map_; 51 | 52 | public: 53 | using Iterator = typename Storage::iterator; 54 | 55 | STLDict() = default; 56 | STLDict(const STLDict&) = delete; 57 | STLDict(STLDict&&) = default; 58 | STLDict& operator=(STLDict&&) = default; 59 | 60 | // Removed operator[] to separate insert and find semantic 61 | // const Value& operator[](const Key& k) const { return map_[k]; } 62 | // Value& operator[](const Key& k) { return map_[k]; } 63 | void insert(const Key& k, const Value& v) { map_[k] = v; } 64 | 65 | bool contains(const Key& k) const { 66 | auto i = map_.find(k); 67 | return i != map_.end(); 68 | } 69 | 70 | // Returns pointer to result or nullptr 71 | Value* find_ptr(const Key& k) { 72 | auto i = map_.find(k); 73 | if (i == map_.end()) { 74 | return nullptr; 75 | } 76 | return &i->second; 77 | } 78 | // Returns const pointer to result or nullptr 79 | const Value* find_ptr(const Key& k) const { 80 | auto i = map_.find(k); 81 | if (i == map_.end()) { 82 | return nullptr; 83 | } 84 | return &i->second; 85 | } 86 | /* 87 | // Returns reference to result or a default value you provide 88 | Value &find_ref(const Key &k, Value &defa) { 89 | auto i = map_.find(k); 90 | if (i == map_.end()) { return defa; } 91 | return i->second; 92 | } 93 | // Returns const reference to result or a default value you provide 94 | const Value &find_ref(const Key &k, const Value &defa) const { 95 | auto i = map_.find(k); 96 | if (i == map_.end()) { return defa; } 97 | return i->second; 98 | } 99 | */ 100 | 101 | void erase(const Key& k) { map_.erase(k); } 102 | size_t size() const { return map_.size(); } 103 | 104 | using Mapping = DictRangeMapping; 105 | using ConstMapping = ConstDictRangeMapping; 106 | 107 | Mapping all() { return Mapping(map_.begin(), map_.end()); } 108 | ConstMapping all() const { return ConstMapping(map_.begin(), map_.end()); } 109 | }; 110 | 111 | } // ns containers 112 | 113 | // A dictionary 114 | // Keys should have 'operator <' 115 | template 116 | using Dict = containers::STLDict; 117 | 118 | } // ns gluon 119 | -------------------------------------------------------------------------------- /emulator/include/struct/list.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvakvs/gluonvm1/7ad9b55c1f701e990ac35206e3119c4071c4ee1e/emulator/include/struct/list.h -------------------------------------------------------------------------------- /emulator/include/struct/set.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvakvs/gluonvm1/7ad9b55c1f701e990ac35206e3119c4071c4ee1e/emulator/include/struct/set.h -------------------------------------------------------------------------------- /emulator/include/struct/str.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //#include "defs.h" 4 | #include 5 | 6 | namespace gluon { 7 | 8 | namespace containers { 9 | 10 | // TODO: Replace this with C string or something later 11 | class STLString { 12 | private: 13 | std::string value_; 14 | 15 | public: 16 | using ConstIterator = const char*; 17 | 18 | STLString() = default; 19 | STLString(const char* x) : value_(x) {} 20 | STLString(const char* x, size_t sz) : value_(x, sz) {} 21 | // stl_string(const stl_string &x): value_(x.value_) {} 22 | 23 | const char* c_str() const { return value_.c_str(); } 24 | 25 | void reserve(size_t sz) { value_.reserve(sz); } 26 | 27 | template 28 | STLString& operator+=(Append c) { 29 | value_ += c; 30 | return *this; 31 | } 32 | STLString& operator+=(const STLString& c) { 33 | value_ += c.value_; 34 | return *this; 35 | } 36 | 37 | bool operator!=(const char* s) const { return value_ != s; } 38 | bool operator!=(const STLString& s) const { return value_ != s.value_; } 39 | bool operator==(const char* s) const { return value_ == s; } 40 | bool operator==(const STLString& s) const { return value_ == s.value_; } 41 | bool operator<(const char* s) const { return value_ < s; } 42 | bool operator<(const STLString& s) const { return value_ < s.value_; } 43 | 44 | char operator[](size_t i) const { return value_[i]; } 45 | 46 | ConstIterator begin() const { return value_.data(); } 47 | ConstIterator end() const { return value_.data() + value_.length(); } 48 | }; 49 | 50 | } // ns containers 51 | 52 | inline bool is_uppcase_latin(char c) { 53 | return c >= 'A' && c <= 'Z'; 54 | } 55 | inline bool is_lowcase_latin(char c) { 56 | return c >= 'a' && c <= 'z'; 57 | } 58 | inline bool is_latin(char c) { 59 | return is_uppcase_latin(c) || is_lowcase_latin(c); 60 | } 61 | 62 | // A string container 63 | using Str = containers::STLString; 64 | 65 | } // ns gluon 66 | -------------------------------------------------------------------------------- /emulator/include/term_helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "heap.h" 5 | #include "term.h" 6 | 7 | #if G_DEBUG 8 | #include "bif/bif_misc.h" 9 | #endif 10 | 11 | namespace gluon { 12 | namespace term { 13 | 14 | class TupleBuilder { 15 | Word m_arity; 16 | Word m_index; 17 | Term* m_elements; 18 | 19 | public: 20 | TupleBuilder(proc::Heap* heap, Word arity) : m_arity(arity), m_index(0) { 21 | m_elements = 22 | (Term*)heap->allocate(layout::Tuple::box_size(arity)); 23 | } 24 | 25 | void add(Term x) { 26 | G_ASSERT(m_index < m_arity); 27 | layout::Tuple::element(m_elements, m_index) = x; 28 | m_index++; 29 | } 30 | Term make_tuple() { 31 | G_ASSERT(m_index == m_arity) 32 | return Term::make_tuple(m_elements, m_arity); 33 | } 34 | }; 35 | 36 | Term make_tuple(proc::Heap* heap, const std::initializer_list& values); 37 | 38 | template 39 | Term make_term(const T&); 40 | 41 | template <> 42 | inline Term make_term(const char& x) { 43 | return Term::make_small_u((Word)x); 44 | } 45 | template <> 46 | inline Term make_term(const Word& x) { 47 | return Term::make_small_u(x); 48 | } 49 | template <> 50 | inline Term make_term(const SWord& x) { 51 | return Term::make_small(x); 52 | } 53 | template <> 54 | inline Term make_term(const Term& x) { 55 | return x; 56 | } 57 | 58 | template 59 | Word length(Iter iter, Iter to) { 60 | Word result = 0; 61 | for (; iter != to; iter++, result++) { 62 | } 63 | return result; 64 | } 65 | template 66 | inline Word length_p(T* iter, T* to) { 67 | return (Word)(to - iter + 1); 68 | } 69 | 70 | template // TODO: const Iter args? 71 | Term build_list(proc::Heap* heap, Iter iter, Iter end) { 72 | if (iter == end) { 73 | return ::gluon::the_nil; 74 | } 75 | 76 | Word len = length_p(iter, end); 77 | Term* h = (Term*)heap->allocate(layout::Cons::box_word_size * len); 78 | 79 | Term result = Term::make_cons(h); 80 | for (; iter < end; iter++) { 81 | layout::Cons::head(h) = make_term(*iter); 82 | layout::Cons::tail(h) = 83 | iter + 1 == end ? ::gluon::the_nil 84 | : Term::make_cons(h + layout::Cons::box_word_size); 85 | h += layout::Cons::box_word_size; 86 | } 87 | 88 | #if G_DEBUG 89 | auto lresult = bif::length(result); 90 | G_ASSERT(lresult.is_proper == true); // must be proper 91 | #endif 92 | return result; 93 | } 94 | 95 | Term build_string(proc::Heap* h, const char* cstr); 96 | 97 | // Builds string as list of integers on heap 98 | Term build_string(proc::Heap* heap, const Str& s); 99 | 100 | } // ns term 101 | } // ns gluon 102 | -------------------------------------------------------------------------------- /emulator/include/vm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "dist.h" 5 | #include "error.h" 6 | #include "platf/gsys_mem.h" 7 | #include "scheduler.h" 8 | #include "term.h" 9 | 10 | namespace gluon { 11 | 12 | using StrAtomMap = Dict; 13 | using AtomStrMap = Dict; 14 | 15 | namespace erts { 16 | class Heap; 17 | } 18 | class Process; 19 | namespace code { 20 | class Server; 21 | } // ns code 22 | 23 | // TODO: also ports somewhere here 24 | using AtomProcMap = Dict; 25 | 26 | enum class RegisterResult { Ok, RegistrationExists, ProcessNotFound }; 27 | 28 | enum class PremadeIndex : Word { 29 | Apply_mfargs_, 30 | Normal_exit_, 31 | Error_exit_, 32 | Total_count 33 | }; 34 | 35 | // Contains VM code to execute one specific instruction. 'Apply' is 36 | // used as process entry address and 'Exit' as process exit address 37 | class PremadeBeaminstr { 38 | public: 39 | mem::Blk instr_; 40 | 41 | PremadeBeaminstr() : instr_(nullptr, 0) {} 42 | void init(const VM& vm); 43 | bool contains(const Word* p) const { 44 | return p >= instr_.mem() && 45 | p < instr_.mem() + (Word)PremadeIndex::Total_count; 46 | } 47 | }; 48 | 49 | // Note: singleton, do not instantiate even 50 | class VM { 51 | private: 52 | // TODO: Optimize atom tab for insert-only, like OTP does 53 | StrAtomMap atoms_; 54 | AtomStrMap reverse_atoms_; 55 | Word atom_id_counter_; 56 | 57 | Node* this_node_ = nullptr; 58 | // used as "" constant when atom is not found 59 | Str const_empty_str_; 60 | Scheduler sched_; 61 | code::Server* codeserver_ = nullptr; 62 | 63 | // Registered names 64 | AtomProcMap reg_names_; 65 | Process* root_process_ = nullptr; 66 | 67 | PremadeBeaminstr premade_; 68 | 69 | public: 70 | VM(); 71 | 72 | CodePointer premade_instr(PremadeIndex i) const { 73 | return CodePointer(premade_.instr_.mem() + (Word)i); 74 | } 75 | code::Server& codeserver() { return *codeserver_; } 76 | const code::Server& codeserver() const { return *codeserver_; } 77 | Process* root_process() const { return root_process_; } 78 | 79 | Scheduler& scheduler() { return sched_; } 80 | const Scheduler& scheduler() const { return sched_; } 81 | 82 | // 83 | // Pid/port registration 84 | // 85 | RegisterResult register_name(Term name, Term pid_port); 86 | 87 | // 88 | // Atom table 89 | // 90 | 91 | // Creates atom or returns existing 92 | Term to_atom(const Str& s); 93 | // Returns existing or nil 94 | Term to_existing_atom(const Str& s) const { 95 | auto presult = atoms_.find_ptr(s); 96 | if (!presult) { 97 | return the_non_value; 98 | } 99 | return *presult; 100 | } 101 | const Str& find_atom(Term a) const; 102 | 103 | // 104 | // Distribution 105 | // 106 | Node* dist_this_node(); 107 | 108 | // 109 | // Heap management 110 | // 111 | enum class HeapType { 112 | Internal, // vm needs this for stuff 113 | Code, // modules code goes here 114 | LoaderTmp, // loader uses this, discard after loading 115 | LargeBinary 116 | }; 117 | erts::Heap* get_heap(HeapType); 118 | 119 | // 120 | // VM loop and loop labels 121 | // 122 | // this is initialized in vm_loop(nullptr) call 123 | const void** g_opcode_labels; 124 | 125 | // Takes next process from scheduler and runs for a while, eventually 126 | // switching 127 | // if the selected process yields or goes into receive/io wait. 128 | void vm_loop(bool init); 129 | 130 | // 131 | // Bif management 132 | // 133 | Term apply_bif(Process* proc, MFArity& mfa, Term* args); 134 | void* find_bif(const MFArity& mfa) const; 135 | Term apply_bif(Process* proc, Word arity, void* fn, Term* args); 136 | 137 | // Checks that opcode label is in allowed range and makes sense. Do not 138 | // confuse 139 | // this with actual code address which is Word* 140 | void assert_valid_vmloop_label(const void* p) const; 141 | 142 | private: 143 | // Does not check if atom existed before. Will break old values on overwrite 144 | Term new_atom(const Str& s); 145 | void init_predef_atoms(); 146 | }; 147 | 148 | } // ns gluon 149 | -------------------------------------------------------------------------------- /emulator/src/.gitignore: -------------------------------------------------------------------------------- 1 | genop.cpp 2 | predef_atoms.cpp 3 | bif_tab.cpp 4 | bif_tab.h 5 | vm_loop.inc.cpp -------------------------------------------------------------------------------- /emulator/src/bif/bif_proc.cpp: -------------------------------------------------------------------------------- 1 | #include "bif/bif_proc.h" 2 | #include "bif/bif_misc.h" 3 | #include "predef_atoms.h" 4 | #include "process.h" 5 | #include "term_helpers.h" 6 | #include "vm.h" 7 | 8 | namespace gluon { 9 | namespace bif { 10 | 11 | Term bif_self_0(Process* proc) { 12 | return proc->get_pid(); 13 | } 14 | 15 | static Term spawn_mfargs(Process* proc, Term m, Term f, Term args, bool link) { 16 | if (!m.is_atom()) { 17 | return proc->bif_error_badarg(m); 18 | } 19 | if (!f.is_atom()) { 20 | return proc->bif_error_badarg(f); 21 | } 22 | if (!args.is_list()) { 23 | return proc->bif_error_badarg(args); 24 | } 25 | 26 | // TODO: on process control blocks' heap 27 | Process* new_proc = new Process(proc->vm(), proc->get_group_leader()); 28 | MFArity mfa(m, f, bif::length(args).length); 29 | 30 | // A process (proc) spawning another process, and gives args from its heap 31 | // We should clone args to new process' registers 32 | Term old_heap_args[erts::max_fun_arity]; 33 | Term new_heap_args[erts::max_fun_arity]; // clone of array_args in new heap 34 | 35 | args.cons_to_array(old_heap_args, sizeof(old_heap_args)); 36 | 37 | proc::copy_terms(proc->vm(), new_proc->get_heap(), old_heap_args, 38 | old_heap_args + mfa.arity, new_heap_args); 39 | 40 | try { 41 | mfa.println(proc->vm()); 42 | new_proc->spawn(mfa, new_heap_args); 43 | } catch (std::runtime_error& e) { 44 | return proc->bif_error(atom::ERROR, e.what()); 45 | } 46 | 47 | if (link) { 48 | // TODO: Establish link in both directions 49 | proc->link(new_proc); 50 | new_proc->link(proc); 51 | // TODO: on error - destroy result 52 | } 53 | 54 | return new_proc->get_pid(); 55 | } 56 | 57 | Term bif_spawn_3(Process* proc, Term m, Term f, Term args) { 58 | return spawn_mfargs(proc, m, f, args, false); 59 | } 60 | 61 | Term bif_spawn_link_3(Process* proc, Term m, Term f, Term args) { 62 | return spawn_mfargs(proc, m, f, args, true); 63 | } 64 | 65 | Term bif_group_leader_0(Process* proc) { 66 | return proc->get_group_leader(); 67 | } 68 | 69 | Term bif_group_leader_2(Process* proc, Term pid, Term gl) { 70 | Process* other = proc->vm().scheduler().find(pid); 71 | other->set_group_leader(pid); 72 | return atom::OK; 73 | } 74 | 75 | Term bif_is_process_alive_1(Process* proc, Term pid) { 76 | if (!pid.is_short_pid()) { 77 | return proc->bif_error_badarg(pid); 78 | } 79 | if (proc->vm().scheduler().find(pid) == nullptr) { 80 | return atom::FALSE; 81 | } 82 | return atom::TRUE; 83 | } 84 | 85 | Term bif_nif_error_1(Process* p, Term what) { 86 | return p->bif_error(atom::ERROR, what); 87 | } 88 | 89 | Term bif_register_2(Process* p, Term name, Term pid_port) { 90 | if (!name.is_atom() || name == atom::UNDEFINED) { 91 | return p->bif_error_badarg(name); 92 | } 93 | if (!pid_port.is_pid() && !pid_port.is_port()) { 94 | return p->bif_error_badarg(pid_port); 95 | } 96 | switch (p->vm().register_name(name, pid_port)) { 97 | case RegisterResult::Ok: 98 | return atom::TRUE; 99 | case RegisterResult::RegistrationExists: // fall through 100 | case RegisterResult::ProcessNotFound: 101 | return p->bif_error_badarg(pid_port); 102 | } 103 | } 104 | 105 | Term bif_process_flag_2(Process* p, Term flag, Term value) { 106 | if (flag == atom::TRAP_EXIT) { 107 | if (!G_IS_BOOLEAN(value)) { 108 | return p->bif_error_badarg(value); 109 | } 110 | p->set_trap_exit(value == atom::TRUE); 111 | return atom::OK; 112 | } 113 | return p->bif_error_badarg(flag); 114 | } 115 | 116 | Term bif_exit_1(Process* p, Term what) { 117 | return p->bif_fail(proc::FailType::Exit, what); 118 | } 119 | 120 | Term bif_exit_2(Process* proc, Term pid, Term what) { 121 | // If the first argument is not a pid, or a local port it is an error. 122 | 123 | if (pid.is_short_port()) { 124 | // TODO: check erts_port_synchronous_ops and lookup the port 125 | // TODO: call port exit with reason 126 | // TODO: check if exited, or if op was scheduled on port 127 | throw err::TODO("exit/2 for local port"); 128 | } 129 | // not sure why? 130 | // TODO: if (pid.is_remote_port() && belongs to current node) return TRUE 131 | 132 | // If it is a remote pid, send a signal to the remote node. 133 | 134 | if (pid.is_remote_pid()) { 135 | // TODO: lookup the remote pid 136 | // TODO: if remote pid refers to current node: return TRUE 137 | // TODO: prepare distributed signal and send it if possible 138 | throw err::TODO("exit/2 for remote pid"); 139 | } else if (!pid.is_short_pid()) { 140 | return proc->bif_error_badarg(); 141 | } else { 142 | // The pid is internal. Verify that it refers to an existing process. 143 | // TODO: Find process, honour locks (for SMP) 144 | Process* other_p = proc->vm().scheduler().find(pid); 145 | 146 | // TODO: Send an exit signal 147 | if (other_p) { 148 | // return other_p->bif_fail(proc::FailType::Exit, what); 149 | throw err::TODO("notimpl exit signals"); 150 | } 151 | 152 | // TODO: if sending to self - remove ERTS_PROC_LOCK_MAIN (whatever that 153 | // is) 154 | // TODO: (smp) unlock 155 | 156 | // We may have exited ourselves and may have to take action. 157 | 158 | // TODO: if we killed self, do a nice exit: ERTS_BIF_CHK_EXITED(BIF_P) 159 | return atom::TRUE; 160 | } 161 | } 162 | 163 | } // ns bif 164 | } // ns gluonl 165 | -------------------------------------------------------------------------------- /emulator/src/binary.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvakvs/gluonvm1/7ad9b55c1f701e990ac35206e3119c4071c4ee1e/emulator/src/binary.cpp -------------------------------------------------------------------------------- /emulator/src/code.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvakvs/gluonvm1/7ad9b55c1f701e990ac35206e3119c4071c4ee1e/emulator/src/code.cpp -------------------------------------------------------------------------------- /emulator/src/code_index.cpp: -------------------------------------------------------------------------------- 1 | #include "code_index.h" 2 | 3 | namespace gluon { 4 | namespace code { 5 | 6 | #if FEATURE_CODE_RANGES 7 | 8 | #endif 9 | } // ns code 10 | } // ns gluon 11 | -------------------------------------------------------------------------------- /emulator/src/code_server.cpp: -------------------------------------------------------------------------------- 1 | #include "code_server.h" 2 | #include "heap.h" 3 | #include "module.h" 4 | #include "platf/gsys_file.h" 5 | #include "process.h" 6 | #include "vm.h" 7 | 8 | namespace gluon { 9 | namespace code { 10 | 11 | // mod_map_t Server::g_modules; 12 | // List Server::g_search_path; 13 | 14 | // void Server::init() { 15 | //} 16 | 17 | void Server::load_module(Process* proc, 18 | Term name_atom, 19 | ArrayView data) { 20 | // TODO: module versions for hot code loading 21 | // TODO: free if module already existed, check module usage by processes 22 | Module* m = load_module_internal(proc->get_heap(), name_atom, data); 23 | modules_.insert(m->get_name(), m); 24 | 25 | // assume that mod already registered own functions in own fun index 26 | // code to fun/arity mapping should be updated on loading stage 27 | if (feature_code_ranges) { 28 | auto range = m->get_code_range(); 29 | mod_index_.add(range, m); 30 | } 31 | } 32 | 33 | void Server::load_module(Process* proc, Term name) { 34 | // Scan for locations where module file can be found 35 | Str mod_filename(name.atom_str(vm_)); 36 | mod_filename += ".beam"; 37 | 38 | for (const Str& dir : search_path_) { 39 | Str path(dir); 40 | path += "/"; 41 | path += mod_filename; 42 | 43 | if (fs::exists(path)) { 44 | fs::File f; 45 | f.open(path); 46 | 47 | Word size = f.size(); 48 | erts::Heap* heap = vm_.get_heap(VM::HeapType::Code); 49 | Uint8* tmp_buffer = heap->allocate(size); 50 | f.seek(0); 51 | f.read(tmp_buffer, size); 52 | 53 | libc::fmt("Loading BEAM %s\n", path.c_str()); 54 | load_module(proc, name, ArrayView(tmp_buffer, size)); 55 | heap->deallocate_many(tmp_buffer, size); 56 | return; 57 | } 58 | } 59 | throw err::CodeServer("module not found"); 60 | } 61 | 62 | Module* Server::find_module(Process* proc, Term m, FindModule load) { 63 | auto presult = modules_.find_ptr(m); 64 | if (!presult) { 65 | if (load == code::FindModule::FindExisting) { 66 | throw err::CodeServer("function not found"); 67 | } else { 68 | load_module(proc, m); 69 | auto mptr = modules_.find_ptr(m); 70 | return mptr ? *mptr : nullptr; 71 | } 72 | } 73 | return *presult; 74 | } 75 | 76 | void Server::path_append(const Str& p) { 77 | search_path_.push_back(p); 78 | } 79 | 80 | void Server::path_prepend(const Str& p) { 81 | search_path_.push_front(p); 82 | } 83 | 84 | bool Server::print_mfa(const CodePointer ptr) const { 85 | MFArity mfa; 86 | if (!find_mfa_from_code(ptr, /*out*/ mfa)) { 87 | libc::fmt("%p", ptr.value()); 88 | return false; 89 | } 90 | if (!mfa.mod.is_atom() || !mfa.fun.is_atom()) { 91 | throw err::CodeServer("mfa is not atom:atom"); 92 | } 93 | mfa.mod.print(vm_); 94 | libc::fmt(":"); 95 | mfa.fun.print(vm_); 96 | libc::fmt("/" FMT_UWORD, mfa.arity); 97 | 98 | return true; 99 | } 100 | 101 | bool Server::find_mfa_from_code(const CodePointer ptr, MFArity& out) const { 102 | Module* m = nullptr; 103 | if (!mod_index_.find(ptr, /*out*/ m) || !m) { 104 | return false; 105 | } 106 | FunArity fa; 107 | if (!m->find_fun_arity(ptr, /*out*/ fa)) { 108 | return false; 109 | } 110 | out = MFArity(m->get_name(), fa); 111 | return true; 112 | } 113 | 114 | Export* Server::find_mfa(const MFArity& mfa, Module** out_mod) const { 115 | auto presult = modules_.find_ptr(mfa.mod); 116 | if (!presult) { 117 | return nullptr; 118 | } 119 | if (out_mod) { 120 | *out_mod = *presult; 121 | } 122 | return (*presult)->find_export(mfa.as_funarity()); 123 | } 124 | 125 | } // ns code 126 | } // ns gluon 127 | -------------------------------------------------------------------------------- /emulator/src/dist.cpp: -------------------------------------------------------------------------------- 1 | #include "dist.h" 2 | -------------------------------------------------------------------------------- /emulator/src/fun.cpp: -------------------------------------------------------------------------------- 1 | #include "fun.h" 2 | #include 3 | 4 | #include "heap.h" 5 | 6 | namespace gluon { 7 | namespace fun { 8 | 9 | BoxedFun* box_fun(FunEntry* fe, Word* mem, Term pid, Term* frozen) { 10 | BoxedFun* bf = (BoxedFun*)mem; 11 | // pack nfree and arity, then create_subtag() will shift it and tag as 12 | // boxedfun 13 | bf->hdr = 14 | term_tag::BoxedFun::create_subtag((fe->num_free << 8) | fe->mfa.arity); 15 | 16 | G_ASSERT(pid.is_pid()); 17 | bf->pid = pid; 18 | bf->module = fe->mfa.mod; 19 | bf->index = fe->index; 20 | std::copy(fe->uniq, fe->uniq + 4, bf->uniq); 21 | //::memcpy(&bf->uniq, fe->uniq, sizeof(u32_t) * 4); 22 | bf->old_index = fe->old_index; 23 | bf->old_uniq = fe->old_uniq; 24 | bf->fun_entry = fe; 25 | std::copy(frozen, frozen + fe->num_free, bf->frozen); 26 | //::memcpy(bf->frozen, frozen, fe->num_free * sizeof(Term)); 27 | 28 | return bf; 29 | } 30 | 31 | Term box_fun(proc::Heap* heap, FunEntry* fe, Term pid, Term* frozen) { 32 | Word* p8 = heap->allocate( 33 | calculate_word_size(sizeof(BoxedFun) + fe->num_free)); 34 | 35 | BoxedFun* p = fun::box_fun(fe, p8, pid, frozen); 36 | 37 | return FunObject::make(p); 38 | } 39 | 40 | } // ns fun 41 | } // ns gluon 42 | -------------------------------------------------------------------------------- /emulator/src/heap.cpp: -------------------------------------------------------------------------------- 1 | #include "heap.h" 2 | #include "fun.h" 3 | #include "term.h" 4 | 5 | namespace gluon { 6 | namespace proc { 7 | /* 8 | Heap::Heap() { 9 | m_root = m_current = Node::create(DEFAULT_PROC_HEAP_WORDS); 10 | m_stack.put_stack(m_current, DEFAULT_PROC_STACK_WORDS); 11 | } 12 | 13 | Word *Heap::h_alloc(Word n) 14 | { 15 | if (m_current->get_avail() <= n) { 16 | auto node_size_power = std::min(m_node_count, HEAP_SEGMENT_GROWTH_MAX); 17 | // TODO: replace max here with proper calculation of nearest higher size 18 | auto node_size = std::max(DEFAULT_PROC_HEAP_WORDS << node_size_power, 19 | n + Node::FIELDS_WORD_SIZE); 20 | 21 | // See constant comments 22 | auto new_node = Node::create(node_size); 23 | m_node_count++; 24 | m_current->next = new_node; 25 | m_current = new_node; 26 | } 27 | 28 | return m_current->allocate_words(n); 29 | } 30 | 31 | Node *Node::create(Word sz_words) { 32 | Word *mem = new Word[sz_words]; 33 | auto n = new (mem) Node; 34 | n->start = n->heap_start; 35 | n->limit = n->start + sz_words - sizeof(Node); 36 | return n; 37 | } 38 | */ 39 | 40 | /* 41 | void Stack::put_stack(Node *stk_node, Word size) { 42 | // Assume current node in heap has memory for stack 43 | G_ASSERT(stk_node->get_avail() >= size); 44 | m_node = stk_node; 45 | // Shrink node by stack size. Set 'top' to end of node's memory and 'bottom' 46 | // to new end (where stack will overflow). 47 | end_ = top_ = stk_node->limit; 48 | m_node->limit -= size; 49 | bottom_ = m_node->limit; 50 | } 51 | */ 52 | 53 | // Takes all terms between 'start' and 'end', and copies them to 'dstheap', new 54 | // resulting terms are placed in array 'dst' which should be large enough. 55 | bool copy_terms(VM& vm, 56 | Heap* dstheap, 57 | const Term* start, 58 | const Term* end, 59 | Term* dst) { 60 | while (start < end) { 61 | *dst = copy_one_term(vm, dstheap, *start); 62 | start++; 63 | dst++; 64 | } 65 | return true; 66 | } 67 | 68 | // Copies one term 't' to 'dstheap' returns new clone term located in new heap 69 | Term copy_one_term(VM& vm, Heap* dstheap, Term t) { 70 | // Immediate values go immediately out 71 | if (t.is_nonvalue() || t.is_nil() || t.is_small() || t.is_atom() || 72 | t.is_short_pid() || t.is_short_port()) { 73 | return t; 74 | } 75 | if (t.is_tuple()) { 76 | Word arity = t.tuple_get_arity(); 77 | Term* new_t = 78 | (Term*)dstheap->allocate(layout::Tuple::box_size(arity)); 79 | Term* this_t = t.boxed_get_ptr(); 80 | layout::Tuple::arity(new_t) = layout::Tuple::arity(this_t); 81 | // Deep clone 82 | for (Word i = 0; i < arity; ++i) { 83 | layout::Tuple::element(new_t, i) = 84 | copy_one_term(vm, dstheap, layout::Tuple::element(this_t, i)); 85 | } 86 | return Term::make_tuple_prepared(new_t); 87 | } 88 | if (t.is_boxed()) { 89 | if (t.is_boxed_fun()) { 90 | BoxedFun* bf = t.boxed_get_ptr(); 91 | return fun::box_fun(dstheap, bf->fun_entry, bf->pid, bf->frozen); 92 | } 93 | } 94 | t.println(vm); 95 | G_TODO("notimpl copy_one_term for some type of term"); 96 | } 97 | 98 | } // ns proc 99 | } // ns gluon 100 | -------------------------------------------------------------------------------- /emulator/src/mailbox.cpp: -------------------------------------------------------------------------------- 1 | #include "mailbox.h" 2 | 3 | namespace gluon { 4 | namespace proc { 5 | 6 | Mailbox::Mailbox() { 7 | messages_.push_back(the_non_value); 8 | } 9 | 10 | void Mailbox::on_incoming(Term value) { 11 | // Ensure there is always trailing nonvalue in msg list 12 | G_ASSERT(messages_.back() == the_non_value); 13 | messages_.insert(--messages_.end(), value); 14 | G_ASSERT(messages_.back() == the_non_value); 15 | 16 | // Reset save (current) position to beginning 17 | current_ = messages_.begin(); 18 | } 19 | 20 | // Returns current message 21 | // Returns NONVALUE if current points at NONVALUE terminator (after last) 22 | // Position of pointer at mailbox end means we start over 23 | Term Mailbox::get_current() { 24 | G_ASSERT(messages_.back() == the_non_value); 25 | 26 | if (current_ == messages_.end()) { 27 | current_ = messages_.begin(); 28 | } 29 | if (*current_ != the_non_value) { 30 | return *current_; 31 | } else { 32 | return the_non_value; 33 | } 34 | } 35 | 36 | // Removes current message at pointer 37 | void Mailbox::remove_current() { 38 | G_ASSERT(*current_ != the_non_value); 39 | 40 | if (current_ == messages_.end() || *current_ == the_non_value) { 41 | throw err::Process("removing from empty msgbox cell"); 42 | // m_mbox_ptr = m_mbox.begin(); 43 | } else { 44 | messages_.erase(current_); 45 | } 46 | 47 | // Reset current position to begin 48 | current_ = messages_.begin(); 49 | 50 | // TODO: Cancel timer 51 | // TODO: For off-heap message queue - free the message memory 52 | } 53 | 54 | // If pointer is not at terminator, step forward. Else set at mailbox end 55 | void Mailbox::step_next() { 56 | if (*current_ != the_non_value) { 57 | current_++; 58 | } else { 59 | current_ = messages_.end(); 60 | } 61 | } 62 | 63 | void Mailbox::mark_position(Word label) { 64 | saved_mark_label_ = label; 65 | saved_mark_ = --messages_.end(); 66 | } 67 | 68 | void Mailbox::set_to_marked(Word label) { 69 | if (saved_mark_label_ == label) { 70 | current_ = saved_mark_; 71 | } 72 | } 73 | 74 | } // ns proc 75 | } // ns mailbox 76 | -------------------------------------------------------------------------------- /emulator/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "code_index.h" 2 | #include "code_server.h" 3 | #include "error.h" 4 | #include "predef_atoms.h" 5 | #include "process.h" 6 | #include "vm.h" 7 | 8 | #include 9 | 10 | #if G_TEST 11 | #include "../test/test.h" 12 | #endif 13 | 14 | using namespace gluon; 15 | 16 | int main(int argc, const char* argv[]) { 17 | VM vm; 18 | 19 | #if G_TEST 20 | // test runner 21 | gluontest::run_tests(argc, argv); 22 | #else 23 | 24 | // normal start 25 | vm.codeserver().path_append("../test"); 26 | 27 | auto rootp = vm.root_process(); 28 | vm.codeserver().load_module(rootp, vm.to_atom("g_test2")); 29 | // vm.codeserver().load_module(rootp, atom::ERLANG); 30 | 31 | // create root process and set it to some entry function 32 | Term start_args[1] = { 33 | the_nil, 34 | }; 35 | // MFArity mfa(vm.to_atom("otp_ring0"), vm.to_atom("start"), 2); 36 | // MFArity mfa(vm.to_atom("init"), vm.to_atom("boot"), 1); 37 | MFArity mfa(vm.to_atom("g_test2"), vm.to_atom("test"), 0); 38 | 39 | rootp->spawn(mfa, start_args); 40 | 41 | // Run some code 42 | rootp->set_group_leader(rootp->get_pid()); 43 | vm.vm_loop(false); 44 | 45 | // Print x0 as result 46 | libc::fmt("Result X[0]="); 47 | rootp->get_runtime_ctx().regs_[0].println(vm); 48 | 49 | return 0; 50 | #endif 51 | } 52 | -------------------------------------------------------------------------------- /emulator/src/module.cpp: -------------------------------------------------------------------------------- 1 | #include "module.h" 2 | #include "functional.h" 3 | #include "reader.h" 4 | #include "vm.h" 5 | 6 | namespace gluon { 7 | 8 | CodePointer Module::resolve_label(LabelIndex label) { 9 | if (label.value() >= labels_.size()) { 10 | throw err::BeamLoad("label index too big"); 11 | } 12 | auto lptr = labels_.find_ptr(label); 13 | return lptr ? *lptr : CodePointer(); 14 | } 15 | 16 | void Module::set_exports(Module::Exports& e) { 17 | exports_ = std::move(e); 18 | 19 | // Replace known BIFs in exports with their BIF pointer and flag them as 20 | // such 21 | auto exps = exports_.all(); 22 | for_each(exps, [this](auto fa_exp) { 23 | void* bif_ptr = vm_->find_bif(MFArity(name_, fa_exp->first)); 24 | if (bif_ptr) { 25 | exports_.insert(fa_exp->first, Export(bif_ptr)); 26 | } 27 | }); 28 | } 29 | 30 | #if FEATURE_CODE_RANGES 31 | bool Module::find_fun_arity(CodePointer ptr, FunArity& out) const { 32 | return fun_index_.find(ptr, out); 33 | } 34 | #endif 35 | 36 | #if FEATURE_CODE_RANGES 37 | code::Range Module::get_code_range() { 38 | return code::Range(CodePointer(code_.data()), 39 | CodePointer(&code_.back() + 1)); 40 | } 41 | #endif 42 | 43 | } // ns gluon 44 | -------------------------------------------------------------------------------- /emulator/src/platf/README.md: -------------------------------------------------------------------------------- 1 | # Gluon System Library 2 | 3 | System abstraction layer for GluonVM, covers all OS interfacing needs of a VM, 4 | such as files, memory, network etc. 5 | 6 | Implement same API as seen in g_sys.h to support a new system. 7 | -------------------------------------------------------------------------------- /emulator/src/platf/gsys_file.cpp: -------------------------------------------------------------------------------- 1 | #include "gsys_file.h" 2 | //#include "error.h" 3 | #include "struct/str.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace gluon { 9 | 10 | namespace err { 11 | DECL_IMPL_EXCEPTION(file_error) 12 | } // ns err 13 | 14 | namespace fs { 15 | 16 | bool exists(const Str& name) { 17 | struct stat buffer; 18 | return (::stat(name.c_str(), &buffer) == 0); 19 | } 20 | 21 | static void* to_internal(FILE* f) { 22 | return reinterpret_cast(f); 23 | } 24 | 25 | static FILE* to_file(void* f) { 26 | return reinterpret_cast(f); 27 | } 28 | 29 | File::File(void* f) : m_handle(f) {} 30 | File::File() : m_handle(nullptr) {} 31 | 32 | void fs::File::open(const Str& name) { 33 | auto handle = ::fopen(name.c_str(), "rb"); 34 | if (!handle) { 35 | throw err::file_error("open error"); 36 | } 37 | m_handle = to_internal(handle); 38 | return; 39 | } 40 | 41 | File::~File() { 42 | if (m_handle) { 43 | ::fclose(to_file(m_handle)); 44 | } 45 | } 46 | 47 | Word File::size() { 48 | auto f = to_file(m_handle); 49 | // auto old_pos = ::ftell(f); 50 | 51 | ::fseek(f, 0, SEEK_END); 52 | return static_cast(::ftell(f)); 53 | } 54 | 55 | void File::seek(Word offset) { 56 | ::fseek(to_file(m_handle), (ssize_t)offset, SEEK_SET); 57 | } 58 | 59 | Word File::read(Uint8* dst, Word bytes) { 60 | auto result = ::fread(dst, 1, bytes, to_file(m_handle)); 61 | return result; 62 | } 63 | 64 | } // ns fs 65 | } // ns gluon 66 | -------------------------------------------------------------------------------- /emulator/src/platf/gsys_file.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | //#include "error.h" 5 | #include "struct/str.h" 6 | 7 | namespace gluon { 8 | 9 | // Filesystem 10 | namespace fs { 11 | 12 | class File { 13 | public: 14 | File(); 15 | File(void*); 16 | ~File(); 17 | 18 | void open(const Str& name); 19 | // May leave read position at file end, re-seek after you used size 20 | Word size(); 21 | void seek(Word offset); 22 | Word read(Uint8* dst, Word bytes); 23 | bool is_good() { return !(is_error() || is_eof()); } 24 | bool is_error(); 25 | bool is_eof(); 26 | 27 | private: 28 | void* m_handle; 29 | }; 30 | 31 | bool exists(const Str&); 32 | 33 | } // ns fs 34 | 35 | } // ns gluon 36 | -------------------------------------------------------------------------------- /emulator/src/platf/gsys_mem.cpp: -------------------------------------------------------------------------------- 1 | #include "gsys_mem.h" 2 | //#include "error.h" 3 | 4 | namespace gluon { 5 | namespace mem {} // ns mem 6 | } // ns gluon 7 | -------------------------------------------------------------------------------- /emulator/src/platf/gsys_mem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //#include 4 | #include 5 | 6 | namespace gluon { 7 | 8 | namespace mem { 9 | 10 | template 11 | class Blk { 12 | private: 13 | Type* mem_; 14 | size_t size_; 15 | 16 | public: 17 | Blk(Type* m, size_t size) : mem_(m), size_(size) {} 18 | Type* mem() const { return mem_; } 19 | size_t size() const { return size_; } 20 | //~Blk() { G_ASSERT(mem == nullptr); } 21 | }; 22 | 23 | // Uses new and delete to allocate system memory blocks 24 | // TODO: get rid of typed allocate and free here, use bytes 25 | class CppStdlibMemory { 26 | public: 27 | template 28 | static Blk allocate() { 29 | return Blk(new Type, sizeof(Type)); 30 | } 31 | 32 | template 33 | static Blk allocate(size_t count) { 34 | return Blk(new Type[count], count * sizeof(Type)); 35 | } 36 | 37 | template 38 | static void deallocate(Blk& p) { 39 | if (p.size() == 1) { 40 | delete p.mem(); 41 | } else { 42 | delete[] p.mem(); 43 | } 44 | } 45 | }; 46 | 47 | } // ns mem 48 | 49 | using SysMemory = mem::CppStdlibMemory; 50 | 51 | } // ns gluon 52 | -------------------------------------------------------------------------------- /emulator/src/platf/gsys_stdlib.cpp: -------------------------------------------------------------------------------- 1 | #include "gsys_stdlib.h" 2 | #include "defs.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace gluon { 8 | namespace libc { 9 | 10 | void abort() { 11 | ::abort(); 12 | } 13 | 14 | void sleep(size_t micro_sec) { 15 | ::usleep((unsigned int)micro_sec); 16 | } 17 | 18 | void exit(int x) { 19 | ::exit(x); 20 | } 21 | 22 | void fmt(const char* s) { 23 | while (*s) 24 | std::putchar(*s++); 25 | } 26 | 27 | void puts() { 28 | std::puts(""); 29 | } 30 | 31 | void assert_fail(const char* what, const char* file, int line) { 32 | libc::fmt(cRed cBold "FAIL: %s" cRst " (%s:%d)\n", what, file, line); 33 | libc::abort(); 34 | } 35 | 36 | } // ns Std 37 | } // ns gluon 38 | -------------------------------------------------------------------------------- /emulator/src/platf/gsys_stdlib.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //#include "defs.h" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace gluon { 9 | 10 | namespace libc { 11 | // 12 | // Console output 13 | // 14 | void fmt(const char* s); 15 | 16 | template 17 | void fmt(const char* format, Args&&... args) { 18 | std::printf(format, std::forward(args)...); 19 | } 20 | void puts(); 21 | 22 | // 23 | // Self management 24 | // 25 | void abort(); // blow up stuff 26 | void exit(int); // blow up stuff with a return value 27 | 28 | // 29 | // Time and stuff 30 | // 31 | void sleep(size_t micro_sec); 32 | 33 | void assert_fail(const char* what, const char* file, int line); 34 | 35 | } // ns stdlib 36 | 37 | } // ns gluon 38 | -------------------------------------------------------------------------------- /emulator/src/pointer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "defs.h" 3 | 4 | #include "platf/gsys_stdlib.h" // for debug print 5 | 6 | namespace gluon { 7 | 8 | enum class PointerHTag { None = 0, Continuation = 1 }; 9 | enum class PointerLTag { 10 | None = 0, 11 | Boxed = 2 // same as in term_tag.h for primary tag to look like a boxed 12 | }; 13 | 14 | // 15 | // Pointer knowledge for the current OS/CPU 16 | // Such as: high and low bits which are safe to use for tags 17 | // Tagging and untagging 18 | // Address ranges (global ranges and check heaps in VM) 19 | // 20 | // Interface: 21 | // is_userspace_pointer(p) checks if pointer looks like a legal address in 22 | // program's memory 23 | // untag, set_high/low_tag, high/low_tag - clears, sets, reads tags added 24 | // to free bits of pointer. Low tags match Term tags to have a tagged 25 | // pointer that looks like a boxed value or something else. High tags mark 26 | // type of pointer. 27 | 28 | #ifdef __linux__ 29 | template 30 | class LinuxPointerKnowledge { 31 | public: 32 | static_assert(total_bits == sizeof(void*) * 8, 33 | "bad total_bits in PointerKnowledge"); 34 | 35 | // how many bits are safe to cut and throw away for user-space pointers 36 | constexpr static Word high_pos = total_bits - high_bits; 37 | constexpr static Word high_mask = (Word)(~0ULL) << high_pos; 38 | // how many bits will always be zero due to pointer alignment 39 | constexpr static Word low_bits = (total_bits == 64 ? 3 : 2); 40 | constexpr static Word low_mask = (Word)(~0ULL) >> (total_bits - low_bits); 41 | constexpr static Word mask = high_mask | low_mask; 42 | 43 | LinuxPointerKnowledge() = delete; 44 | 45 | static void assert() { 46 | if (debug_mode) { 47 | G_ASSERT(is_userspace_pointer((void*)0x7fff'f7f8'd070ULL)); 48 | } 49 | } 50 | 51 | template 52 | static bool has_no_tags(T* p) { 53 | return ((Word)p & mask) == 0; 54 | } 55 | 56 | template 57 | static bool is_userspace_pointer(T* p) { 58 | // Must be in range for userspace for this platform 59 | // Must be aligned (no extra 1 in low bits) 60 | return ((Word)p <= 0x7fff'ffff'ffffULL) && has_no_tags(p); 61 | } 62 | 63 | template 64 | static T untag(Word p) { 65 | return (T)(p & ~mask); 66 | } 67 | 68 | template 69 | static Word set_tags(T* p, PointerHTag htag, PointerLTag ltag) { 70 | Word stripped = (Word)p & ~mask; 71 | Word res = stripped | ((Word)htag << high_pos) | (Word)ltag; 72 | // Std::fmt("cont set_tags 0x%zx\n", res); 73 | return res; 74 | } 75 | 76 | static PointerHTag high_tag(Word p) { 77 | return (PointerHTag)((Word)p >> (total_bits - high_bits)); 78 | } 79 | 80 | template 81 | static Word set_high_tag(T* p, PointerHTag tag) { 82 | return ((Word)p & high_mask) | ((Word)tag << high_bits); 83 | } 84 | 85 | static PointerLTag low_tag(Word p) { 86 | return (PointerLTag)((Word)p & low_mask); 87 | } 88 | 89 | template 90 | static Word set_low_tag(T* p, PointerLTag tag) { 91 | return ((Word)p & low_mask) | (Word)tag; 92 | } 93 | }; 94 | #endif // linux 95 | 96 | #ifdef __linux__ 97 | // Currently 48 bits of address are used on 64-bit linux which allows to 98 | // address something like 256Tb of RAM. Change this when they switch to using 99 | // 56 or 64bit. 100 | using PointerKnowledge = LinuxPointerKnowledge<64, 16>; 101 | #endif 102 | 103 | } // ns gluon 104 | -------------------------------------------------------------------------------- /emulator/src/process_ctx.cpp: -------------------------------------------------------------------------------- 1 | #include "process_ctx.h" 2 | 3 | namespace gluon { 4 | namespace erts {} // ns erts 5 | } // ns gluon 6 | -------------------------------------------------------------------------------- /emulator/src/process_ctx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "term.h" 4 | 5 | namespace gluon { 6 | namespace erts { 7 | 8 | // Set of stuff we take from Process struct to keep running, this will be saved 9 | // by loop runner on context switch or loop end 10 | class RuntimeContextFields { 11 | private: 12 | CodePointer ip_; 13 | // continuation, works like return address for a single call. If more nested 14 | // calls are done, cp is saved to stack 15 | CodePointer cp_; 16 | 17 | public: 18 | void set_ip(CodePointer ip) { 19 | G_ASSERT(PointerKnowledge::is_userspace_pointer(ip.value())); 20 | G_ASSERT(ip.is_not_null()); 21 | ip_ = ip; 22 | } 23 | CodePointer ip() const { return ip_; } 24 | Word ip(Word index) const { return ip_[index]; } 25 | void step_ip(SWord offset) { ip_ += offset; } 26 | void inc_ip() { ip_++; } 27 | 28 | void set_cp(CodePointer cp) { 29 | // some sane minimum for a pointer or nullptr 30 | G_ASSERT(PointerKnowledge::is_userspace_pointer(cp.value())); 31 | cp_ = cp; 32 | } 33 | CodePointer cp() const { return cp_; } 34 | 35 | Word live = 0; // saved registers count 36 | 37 | // TODO: maybe cache r0 in a local variable in vm loop? 38 | Term regs_[erts::max_regs]; 39 | 40 | #if FEATURE_FLOAT 41 | private: 42 | Float fp_regs_[vm::MAX_FP_REGS]; 43 | 44 | public: 45 | Float fp(Word i) const { return fp_regs[i]; } 46 | void set_fp(Word i, Float v) { fp_regs[i] = v; } 47 | #else 48 | Float fp(Word) const { throw err::FeatureMissing("FLOAT"); } 49 | void set_fp(Word, Float) { throw err::FeatureMissing("FLOAT"); } 50 | #endif 51 | 52 | void print_regs(const VM& vm) const { 53 | if (debug_mode) { 54 | for (Word r = 0; r < live; ++r) { 55 | libc::fmt("x[%zu]=", r); 56 | regs_[r].println(vm); 57 | } 58 | } 59 | } 60 | }; 61 | 62 | class RuntimeContext : public RuntimeContextFields { 63 | #if G_DEBUG 64 | private: 65 | enum class ContextBelongsTo{ 66 | VmLoop, // belongs to VM loop, do not modify ctx fields now 67 | ProcessPartial, // lightly swapped out (only ip/cp) 68 | Process, // fully swapped out with registers etc 69 | }; 70 | 71 | // Extra debug-time check to see if ctx belongs to VM or is swapped out 72 | ContextBelongsTo belongs_ = ContextBelongsTo::Process; 73 | 74 | public: 75 | void assert_swapped_out() { 76 | G_ASSERT(belongs_ == ContextBelongsTo::Process); 77 | } 78 | void assert_swapped_out_partial() { 79 | G_ASSERT(belongs_ == ContextBelongsTo::ProcessPartial || 80 | belongs_ == ContextBelongsTo::Process); 81 | } 82 | void assert_swapped_in() { G_ASSERT(belongs_ == ContextBelongsTo::VmLoop); } 83 | void swapped_out() { 84 | assert_swapped_in(); 85 | belongs_ = ContextBelongsTo::Process; 86 | } 87 | void swapped_out_partial() { 88 | assert_swapped_in(); 89 | belongs_ = ContextBelongsTo::ProcessPartial; 90 | } 91 | void swapped_in() { 92 | assert_swapped_out_partial(); 93 | belongs_ = ContextBelongsTo::VmLoop; 94 | } 95 | #else 96 | public: 97 | void assert_swapped_out() {} 98 | void assert_swapped_out_partial() {} 99 | void assert_swapped_in() {} 100 | void swapped_out() {} 101 | void swapped_out_partial() {} 102 | void swapped_in() {} 103 | #endif 104 | public: 105 | // Entry arguments for apply 106 | constexpr static Word num_arg_regs = 6; 107 | Term arg_regs_[num_arg_regs]; 108 | }; 109 | 110 | } // ns erts 111 | } // ns gluon 112 | -------------------------------------------------------------------------------- /emulator/src/process_fail.cpp: -------------------------------------------------------------------------------- 1 | #include "process_fail.h" 2 | #include "predef_atoms.h" 3 | #include "term_helpers.h" 4 | 5 | namespace gluon { 6 | namespace proc { 7 | 8 | Term Fail::type_as_atom() const { 9 | switch (type_) { 10 | case FailType::Error: 11 | return atom::ERROR; 12 | case FailType::Exit: 13 | return atom::EXIT; 14 | case FailType::Throw: 15 | return atom::THROW; 16 | case FailType::None:; 17 | } 18 | return the_non_value; 19 | } 20 | 21 | void Fail::set(FailType ft, Term reason) { 22 | type_ = ft; 23 | value_ = reason; 24 | } 25 | 26 | FailType Fail::to_fail_type(Term type) { 27 | return (type == atom::THROW) 28 | ? FailType::Throw 29 | : ((type == atom::EXIT) ? FailType::Exit : FailType::Error); 30 | } 31 | 32 | void Fail::set_normal_exit() { 33 | clear(); 34 | type(proc::FailType::Exit); 35 | value(atom::NORMAL); 36 | } 37 | 38 | void Fail::set_internal_error(Term v) { 39 | clear(); 40 | panic_ = true; 41 | type(proc::FailType::Error); 42 | value(v); 43 | } 44 | 45 | void Fail::set_badarg(proc::Heap* heap, Term v) { 46 | clear(); 47 | set(FailType::Error, term::make_tuple(heap, {atom::BADARG, v})); 48 | } 49 | 50 | void Fail::set_badarg() { 51 | clear(); 52 | set(FailType::Error, atom::BADARG); 53 | } 54 | 55 | } // ns proc 56 | } // ns gluon 57 | -------------------------------------------------------------------------------- /emulator/src/reader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "struct/array.h" 5 | #include "struct/str.h" 6 | 7 | namespace gluon { 8 | namespace tool { 9 | 10 | class Reader { 11 | const Uint8* ptr_; 12 | const ArrayView view_; 13 | 14 | public: 15 | // Reader(const u8_t *ptr, Word size): m_ptr(ptr), m_limit(ptr+size) { 16 | // } 17 | Reader(ArrayView vw) : ptr_(vw.data()), view_(vw) {} 18 | // Clones reader with m_ptr value, sets new size, to read smaller limited 19 | // parts of input data 20 | Reader clone(Word new_size) { 21 | G_ASSERT(ptr_ + new_size <= view_.limit()); 22 | Reader new_r(ArrayView(ptr_, new_size)); 23 | return new_r; 24 | } 25 | 26 | Uint8 peek_byte() { return *ptr_; } 27 | Uint8 read_byte() { 28 | // TODO: make this runtime error, not assertion 29 | G_ASSERT(ptr_ < view_.limit()); 30 | // FIXME: am i really not having 1 byte overlap here? 31 | return *ptr_++; 32 | } 33 | 34 | // Advance by 1 byte, assert its value equal to 'value' 35 | void assert_byte(Uint8 value) { G_ASSERT(value == read_byte()); } 36 | void assert_remaining_at_least(Word n) const { 37 | // TODO: make this runtime error, not assertion 38 | G_ASSERT((view_.limit() - ptr_) >= (SWord)n); 39 | } 40 | Word get_remaining_count() const { 41 | G_ASSERT(view_.limit() >= ptr_); 42 | return (Word)(view_.limit() - ptr_); 43 | } 44 | bool is_end() const { return view_.limit() <= ptr_; } 45 | const Uint8* get_ptr() const { return ptr_; } 46 | 47 | Str read_string(Word size) { 48 | assert_remaining_at_least(size); 49 | Str result; 50 | result.reserve(size); 51 | for (Word i = 0; i < size; ++i) { 52 | result += (char)read_byte(); 53 | } 54 | return result; 55 | } 56 | 57 | void read_bytes(Uint8* dst, Word sz) { 58 | std::copy(ptr_, ptr_ + sz, dst); 59 | ptr_ += sz; 60 | } 61 | 62 | Word read_big_u16() { 63 | Word result = ((Word)ptr_[0] << 8) | (Word)ptr_[1]; 64 | ptr_ += 2; 65 | return result; 66 | } 67 | Word read_big_u32() { 68 | Word result = ((Word)ptr_[0] << 24) | ((Word)ptr_[1] << 16) | 69 | ((Word)ptr_[2] << 8) | (Word)ptr_[3]; 70 | ptr_ += 4; 71 | return result; 72 | } 73 | SWord read_big_s(Word bytes) { 74 | SWord result = read_byte(); 75 | if (result & 128) { 76 | // set all bytes above first to 0xFF 77 | result = (SWord)((~0xFFul) | (Word)result); 78 | } 79 | for (Word i = 1; i < bytes; i++) { 80 | result <<= 8; 81 | result += read_byte(); 82 | } 83 | return result; 84 | } 85 | void advance(Word x) { 86 | assert_remaining_at_least(x); 87 | ptr_ += x; 88 | } 89 | template 90 | void advance_align(Word x) { 91 | assert_remaining_at_least(x); 92 | ptr_ += ALIGN * ((x + ALIGN - 1) / ALIGN); 93 | } 94 | }; 95 | 96 | } // ns tool 97 | } // ns gluon 98 | -------------------------------------------------------------------------------- /emulator/src/scheduler.cpp: -------------------------------------------------------------------------------- 1 | #include "scheduler.h" 2 | #include "platf/gsys_stdlib.h" 3 | #include "predef_atoms.h" 4 | #include "process.h" 5 | 6 | namespace gluon { 7 | 8 | void Scheduler::add_new_runnable(Process* p) { 9 | G_ASSERT(p->current_queue_ == proc::Queue::None); 10 | G_ASSERT(p->get_pid().is_pid() == false); 11 | 12 | auto new_pid = Term::make_short_pid(pid_counter_++); 13 | p->set_pid(new_pid); 14 | 15 | pid_to_proc_.insert(new_pid, p); 16 | return queue_by_priority(p); 17 | } 18 | 19 | void Scheduler::queue_by_priority(Process* p) { 20 | // G_ASSERT(!contains(m_normal_q, p)); 21 | // G_ASSERT(!contains(m_low_q, p)); 22 | // G_ASSERT(!contains(m_high_q, p)); 23 | G_ASSERT(p->current_queue_ == proc::Queue::None); 24 | 25 | auto prio = p->get_priority(); 26 | if (prio == atom::NORMAL) { 27 | normal_queue_.push_back(p); 28 | p->current_queue_ = proc::Queue::Normal; 29 | } else if (prio == atom::LOW) { 30 | low_queue_.push_back(p); 31 | p->current_queue_ = proc::Queue::Low; 32 | } else if (prio == atom::HIGH) { 33 | high_queue_.push_back(p); 34 | p->current_queue_ = proc::Queue::High; 35 | } else { 36 | throw err::Scheduler("bad prio"); 37 | } 38 | } 39 | 40 | Process* Scheduler::find(Term pid) const { 41 | if (pid.is_pid() == false) { 42 | return nullptr; 43 | } 44 | auto presult = pid_to_proc_.find_ptr(pid); 45 | return presult ? *presult : nullptr; 46 | } 47 | 48 | void Scheduler::exit_process(Process* p, Term reason) { 49 | // assert that process is not in any queue 50 | G_ASSERT(p->current_queue_ == proc::Queue::None); 51 | // root process exits with halt() 52 | // G_ASSERT(p->get_registered_name() != atom::INIT); 53 | 54 | // TODO: ets tables 55 | // TODO: notify monitors 56 | // TODO: cancel known timers who target this process 57 | // TODO: notify links 58 | // TODO: unregister name if registered 59 | // TODO: if pending timers - become zombie and sit in pending timers queue 60 | libc::fmt("Scheduler::exit_process "); 61 | p->get_pid().print(vm_); 62 | libc::fmt("; reason="); 63 | reason.print(vm_); 64 | libc::fmt("; result X[0]="); 65 | p->get_runtime_ctx().regs_[0].println(vm_); 66 | 67 | // m_inf_wait.erase(p); 68 | // m_timed_wait.erase(p); 69 | G_ASSERT(!contains(normal_queue_, p)); 70 | G_ASSERT(!contains(low_queue_, p)); 71 | G_ASSERT(!contains(high_queue_, p)); 72 | pid_to_proc_.erase(p->get_pid()); 73 | delete p; 74 | } 75 | 76 | void Scheduler::on_new_message(Process* p) { 77 | // Std::fmt("sched: new message to "); 78 | // p->get_pid().println(); 79 | auto current_q = p->current_queue_; 80 | 81 | switch (current_q) { 82 | case proc::Queue::Normal: 83 | case proc::Queue::High: 84 | case proc::Queue::Low: 85 | return; 86 | 87 | case proc::Queue::InfiniteWait: 88 | inf_wait_.erase(p); 89 | break; 90 | 91 | case proc::Queue::TimedWait: 92 | throw err::TODO("timed wait new message"); 93 | 94 | case proc::Queue::None: 95 | // Message arrived to a currently running process (for example send 96 | // to 97 | // self) 98 | return; 99 | 100 | case proc::Queue::PendingTimers: 101 | throw err::TODO("q_pending_timers"); 102 | } // switch 103 | 104 | p->current_queue_ = proc::Queue::None; 105 | queue_by_priority(p); 106 | } 107 | 108 | Process* Scheduler::next() { 109 | if (current_) { 110 | // G_ASSERT(!contains(m_normal_q, m_current)); 111 | // G_ASSERT(!contains(m_low_q, m_current)); 112 | // G_ASSERT(!contains(m_high_q, m_current)); 113 | G_ASSERT(current_->current_queue_ == proc::Queue::None); 114 | 115 | switch (current_->get_slice_result()) { 116 | case proc::SliceResult::Yield: 117 | case proc::SliceResult::None: { 118 | queue_by_priority(current_); 119 | current_ = nullptr; 120 | } break; 121 | 122 | case proc::SliceResult::Finished: { // normal exit 123 | exit_process(current_, atom::NORMAL); 124 | current_ = nullptr; 125 | } break; 126 | 127 | // TODO: WAIT put into infinite or timed wait queue 128 | // TODO: PURGE_PROCS running on old code 129 | case proc::SliceResult::Exception: { 130 | // Assuming here we already tried to find catch and failed 131 | G_ASSERT(current_->is_failed()); 132 | exit_process(current_, current_->fail_value()); 133 | current_ = nullptr; 134 | } break; 135 | 136 | case proc::SliceResult::Wait: { 137 | if (current_->slice_result_wait_ == proc::wait_infinite) { 138 | inf_wait_.insert(current_); 139 | current_->current_queue_ = proc::Queue::InfiniteWait; 140 | current_ = nullptr; 141 | } 142 | } break; 143 | // default: 144 | // G_FAIL("unknown slice result"); 145 | } // switch slice result 146 | } 147 | 148 | while (current_ == nullptr) { 149 | // TODO: monotonic clock 150 | // TODO: wait lists 151 | // TODO: network checks 152 | 153 | Process* next_proc = nullptr; 154 | 155 | // See if any are waiting in realtime (high) priority queue 156 | if (!high_queue_.empty()) { 157 | next_proc = high_queue_.front(); 158 | high_queue_.pop_front(); 159 | } else if (normal_count_ < normal_advantage) { 160 | if (!normal_queue_.empty()) { 161 | next_proc = normal_queue_.front(); 162 | normal_queue_.pop_front(); 163 | } else if (!low_queue_.empty()) { 164 | next_proc = low_queue_.front(); 165 | low_queue_.pop_front(); 166 | } 167 | normal_count_++; 168 | } else { 169 | if (!low_queue_.empty()) { 170 | next_proc = low_queue_.front(); 171 | low_queue_.pop_front(); 172 | } else if (!normal_queue_.empty()) { 173 | next_proc = normal_queue_.front(); 174 | normal_queue_.pop_front(); 175 | } 176 | normal_count_ = 0; 177 | } // select proc from q 178 | 179 | if (next_proc) { 180 | libc::fmt(cGreen cBold "---Scheduler::next() -> " cRst); 181 | libc::fmt("(Queue %d) ", (int)next_proc->current_queue_); 182 | next_proc->get_pid().println(vm_); 183 | 184 | next_proc->current_queue_ = proc::Queue::None; 185 | next_proc->new_slice(); 186 | return current_ = next_proc; 187 | } 188 | 189 | // if no runnable, do some polling 190 | // TODO: gc waiting processes 191 | // TODO: check wait lists and timeouts 192 | 193 | // Let CPU core free if we have nothing to do 194 | libc::sleep(1); 195 | } 196 | 197 | throw err::TODO("should not be here"); 198 | // return nullptr; 199 | } 200 | 201 | } // ns gluon 202 | -------------------------------------------------------------------------------- /emulator/src/stack.cpp: -------------------------------------------------------------------------------- 1 | #include "stack.h" 2 | #include "term.h" 3 | 4 | namespace gluon { 5 | namespace proc { 6 | 7 | // void OverlayStack::push_n_nils(Word n) { 8 | // G_ASSERT(get_avail() >= n); 9 | // top_ -= n; 10 | // std::fill_n(top_, n, term::nil_as_word); 11 | //} 12 | 13 | void SelfContainingStack::push_n_nils(Word n) { 14 | data_.reserve(data_.size() + n); 15 | for (Word i = 0; i < n; ++i) { 16 | push(term::nil_as_word); 17 | } 18 | } 19 | 20 | } // ns proc 21 | } // ns gluon 22 | -------------------------------------------------------------------------------- /emulator/src/term.cpp: -------------------------------------------------------------------------------- 1 | #include "term.h" 2 | #include "binary.h" 3 | #include "code_server.h" 4 | #include "fun.h" 5 | #include "heap.h" 6 | #include "heap.h" 7 | #include "module.h" // for Export class 8 | #include "predef_atoms.h" 9 | #include "term_helpers.h" 10 | #include "vm.h" 11 | 12 | #include 13 | 14 | namespace gluon { 15 | 16 | Word term::g_zero_sized_tuple = 0; 17 | 18 | #if FEATURE_MAPS 19 | Word term::g_zero_sized_map = term_tag::BoxedMap::create_subtag(0); 20 | #endif 21 | #define cSpecialTermColor cYellow cUnderline 22 | 23 | template <> 24 | Term term::ConsAspect::allocate_cons(proc::Heap* heap, 25 | Term head, 26 | Term tail) { 27 | Term* d = (Term*)heap->allocate(layout::Cons::box_word_size); 28 | layout::Cons::head(d) = head; 29 | layout::Cons::tail(d) = tail; 30 | return make_cons(d); 31 | } 32 | 33 | template <> 34 | bool term::ConsAspect::is_cons_printable() const { 35 | Term item = *self(); 36 | while (item.is_cons()) { 37 | Term tmp; 38 | item.cons_head_tail(tmp, item); 39 | if (!is_cons_printable_element(tmp)) { 40 | return false; 41 | } 42 | } 43 | return item.is_nil(); 44 | } 45 | 46 | template <> 47 | Str term::AtomAspect::atom_str(const VM& vm) const { 48 | return vm.find_atom(*self()); 49 | } 50 | 51 | #if G_DEBUG 52 | void Term::print(const VM& vm) const { 53 | if (value() == 0) { 54 | libc::fmt("NOT_A_TERM"); 55 | return; 56 | } 57 | if (is_cons()) { 58 | if (is_cons_printable()) { 59 | // list is printable - print quotes and every character except tail 60 | libc::fmt("\""); 61 | Word c = (Uint8)cons_head().small_word(); 62 | if (does_char_require_quoting(c)) { 63 | libc::fmt("\\"); 64 | } 65 | libc::fmt("%c", (Uint8)c); 66 | Term item = cons_tail(); 67 | while (item.is_cons()) { 68 | c = item.cons_head().small_word(); 69 | if (does_char_require_quoting(c)) { 70 | libc::fmt("\\"); 71 | } 72 | libc::fmt("%c", (Uint8)c); 73 | item = item.cons_tail(); 74 | } 75 | libc::fmt("\""); 76 | } else { 77 | // not printable - dump terms and tail 78 | libc::fmt("["); 79 | cons_head().print(vm); 80 | Term item = cons_tail(); 81 | while (item.is_cons()) { 82 | libc::fmt(","); 83 | item.cons_head().print(vm); 84 | item = item.cons_tail(); 85 | } 86 | if (!item.is_nil()) { 87 | libc::fmt("|"); 88 | item.print(vm); 89 | } 90 | libc::fmt("]"); 91 | } 92 | } else if (is_tuple()) { 93 | auto arity = tuple_get_arity(); 94 | libc::fmt("{"); 95 | for (Word n = 0; n < arity; ++n) { 96 | tuple_get_element(n).print(vm); 97 | if (n < arity - 1) { 98 | libc::fmt(","); 99 | } 100 | } 101 | libc::fmt("}"); 102 | } else if (is_boxed()) { 103 | // 104 | // ------ BOXED ------ 105 | // 106 | ContinuationPointer maybe_cp(value()); 107 | if (maybe_cp.check()) { 108 | libc::fmt(cSpecialTermColor "#CP<"); 109 | vm.codeserver().print_mfa(maybe_cp.untag()); 110 | libc::fmt(">" cRst); 111 | return; 112 | } 113 | auto p = boxed_get_ptr(); 114 | if (is_boxed_fun()) { 115 | libc::fmt(cSpecialTermColor "#Fun<"); 116 | auto bf = boxed_get_ptr(); 117 | vm.codeserver().print_mfa(bf->fun_entry->code); 118 | libc::fmt(">" cRst); 119 | return; 120 | } 121 | if (is_boxed_export()) { 122 | libc::fmt(cSpecialTermColor "#ExportedFun<"); 123 | auto ex = boxed_get_ptr(); 124 | ex->mfa.print(vm); 125 | libc::fmt(";"); 126 | if (ex->is_bif()) { 127 | libc::fmt("bif"); 128 | } else { 129 | vm.codeserver().print_mfa(ex->code()); 130 | } 131 | libc::fmt(">" cRst); 132 | return; 133 | } 134 | if (PointerKnowledge::is_userspace_pointer(p)) { 135 | libc::fmt(cSpecialTermColor "#Box" cRst); 139 | } else { 140 | libc::fmt(cSpecialTermColor "#Box<%p>" cRst, p); 141 | } 142 | // 143 | // 144 | // ------ end boxed ------ 145 | } else if (is_nil()) { 146 | libc::fmt("[]"); 147 | } else if (is_nonvalue()) { 148 | libc::fmt(cSpecialTermColor "NON_VALUE" cRst); 149 | } else if (is_atom()) { 150 | libc::fmt("'%s'", atom_str(vm).c_str()); 151 | } else if (is_small()) { 152 | libc::fmt(FMT_SWORD, small_sword()); 153 | } else if (is_catch()) { 154 | libc::fmt(cSpecialTermColor "#Catch(" FMT_0xHEX ")" cRst, catch_val()); 155 | } else if (is_short_pid()) { 156 | libc::fmt("#Pid<" FMT_0xHEX ">", short_pid_get_value()); 157 | } else if (is_regx()) { 158 | libc::fmt("X[" FMT_UWORD "]", regx_get_value()); 159 | } 160 | #if FEATURE_FLOAT 161 | else if (is_regfp()) { 162 | Std::fmt("FP[" FMT_UWORD "]", regfp_get_value()); 163 | } 164 | #endif 165 | else if (is_regy()) { 166 | libc::fmt("Y[" FMT_UWORD "]", regy_get_value()); 167 | } else { 168 | libc::fmt("UNKNOWN(" FMT_0xHEX ")", value()); 169 | } 170 | } 171 | 172 | void Term::println(const VM& vm) const { 173 | print(vm); 174 | libc::puts(); 175 | } 176 | 177 | template <> 178 | Term term::BinaryAspect::make_binary(VM& vm, proc::Heap* h, Word bytes) { 179 | // This many bytes fits boxed subtag value. Going larger means storing size 180 | // elsewhere or losing significant bit from the size 181 | G_ASSERT(bytes < term_tag::boxed_max_subtag_val); 182 | 183 | if (bytes <= bin::heapbin_limit) { 184 | Word* box = h->allocate(layout::ProcBin::box_size(bytes)); 185 | layout::ProcBin::set_byte_size(box, bytes); 186 | return Term(term_tag::BoxedProcBin::create_from_ptr(box)); 187 | } else { 188 | // Large bin, with boxed refcount and pointer 189 | erts::Heap* binheap = vm.get_heap(VM::HeapType::LargeBinary); 190 | layout::HeapbinBox* box = binheap->allocate( 191 | layout::HeapBin::box_size(bytes)); 192 | box->set_byte_size(bytes); 193 | box->set_refcount(1); 194 | return Term(term_tag::BoxedHeapBin::create_from_ptr(box)); 195 | } 196 | } 197 | 198 | void MFArity::println(const VM& vm) { 199 | print(vm); 200 | libc::puts(); 201 | } 202 | 203 | void MFArity::print(const VM& vm) { 204 | mod.print(vm); 205 | libc::fmt(":"); 206 | fun.print(vm); 207 | libc::fmt("/" FMT_UWORD, arity); 208 | } 209 | 210 | #endif // DEBUG 211 | 212 | } // ns gluon 213 | -------------------------------------------------------------------------------- /emulator/src/term_helpers.cpp: -------------------------------------------------------------------------------- 1 | #include "term_helpers.h" 2 | 3 | namespace gluon { 4 | namespace term { 5 | 6 | Term build_string(proc::Heap* heap, const Str& s) { 7 | return build_list(heap, s.begin(), s.end()); 8 | } 9 | 10 | Term build_string(proc::Heap* h, const char* cstr) { 11 | const char* cstr_end; 12 | for (cstr_end = cstr; *cstr_end; ++cstr_end) { 13 | } 14 | return build_list(h, cstr, cstr_end); 15 | } 16 | 17 | Term make_tuple(proc::Heap* heap, const std::initializer_list& values) { 18 | TupleBuilder tb(heap, values.size()); 19 | for (auto v : values) { 20 | tb.add(v); 21 | } 22 | return tb.make_tuple(); 23 | } 24 | 25 | } // ns term 26 | } // ns gluon 27 | -------------------------------------------------------------------------------- /emulator/src/term_layout.cpp: -------------------------------------------------------------------------------- 1 | #include "term.h" 2 | #include "heap.h" 3 | 4 | namespace gluon { 5 | namespace layout { 6 | 7 | Word ProcBin::box_size(Word bytes) { 8 | return calculate_word_size(bytes) + box_extra_words; 9 | } 10 | 11 | Word HeapBin::box_size(Word bytes) { 12 | return calculate_word_size(bytes) + farheap_extra_words; 13 | } 14 | 15 | } // ns layout 16 | } // ns gluon 17 | -------------------------------------------------------------------------------- /emulator/src/term_layout.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // TO BE INCLUDED FROM TERM.H ONLY 3 | 4 | // 5 | // Define layouts of boxes for boxed terms, tuple and cons 6 | // 7 | namespace layout { 8 | // Cons layout has no place for bit tag 9 | struct Cons { 10 | static const Word box_word_size = 2; 11 | 12 | template 13 | static Cell& head(Cell* box) { 14 | static_assert(sizeof(Cell) == sizeof(Word), "bad cell size"); 15 | return box[0]; 16 | } 17 | 18 | template 19 | static Cell& tail(Cell* box) { 20 | static_assert(sizeof(Cell) == sizeof(Word), "bad cell size"); 21 | return box[1]; 22 | } 23 | 24 | template 25 | static Cell& element(Cell* box, Word i) { 26 | static_assert(sizeof(Cell) == sizeof(Word), "bad cell size"); 27 | return box[i]; 28 | } 29 | }; 30 | 31 | // Tuple layout has no place for bit tag. 32 | // First goes arity, then elements 33 | struct Tuple { 34 | static const Word box_extra_words = 1; 35 | 36 | static Word box_size(Word Arity) { return Arity + box_extra_words; } 37 | 38 | template 39 | static Cell& arity(Cell* box) { 40 | static_assert(sizeof(Cell) == sizeof(Word), "bad cell size"); 41 | return box[0]; 42 | } 43 | 44 | template 45 | static Cell& element(Cell* box, Word i) { 46 | static_assert(sizeof(Cell) == sizeof(Word), "bad cell size"); 47 | return box[i + box_extra_words]; 48 | } 49 | }; 50 | 51 | // Process-heap (small) and heap (large) binary layout 52 | struct Binary { 53 | // both types of binary have size in first (subtag) word 54 | static Word get_byte_size(Word* p) { 55 | return term_tag::get_subtag_value(p[0]); 56 | } 57 | }; 58 | 59 | // Box structure 60 | // Word { size, tag_bits: 4 }; u8_t data[size] 61 | struct ProcBin { 62 | static const Word box_extra_words = 1; 63 | 64 | static Word box_size(Word bytes); 65 | 66 | static void set_byte_size(Word* box, Word bytes) { 67 | box[0] = term_tag::BoxedProcBin::create_subtag(bytes); 68 | } 69 | 70 | template 71 | static Cell* data(Cell* box) { 72 | static_assert(sizeof(Cell) == sizeof(Word), "bad cell size"); 73 | return box + 1; 74 | } 75 | }; 76 | #pragma clang diagnostic push 77 | #pragma clang diagnostic ignored "-Wzero-length-array" 78 | // Heapbin is stored on far heap with refcount field 79 | // Box only contains size and pointer to far heap with heapbin 80 | class HeapbinBox { 81 | private: 82 | Word m_size; // contains both size and boxed tag 83 | Word m_refcount; 84 | Word m_data[0]; 85 | 86 | public: 87 | void set_byte_size(Word bytes) { 88 | m_size = term_tag::BoxedHeapBin::create_subtag(bytes); 89 | } 90 | 91 | template 92 | Cell* data() { 93 | // static_assert(sizeof(Cell) == sizeof(Word), "bad cell size"); 94 | return (Cell*)&m_data; 95 | } 96 | 97 | Word refcount() const { return m_refcount; } 98 | void set_refcount(Word r) { m_refcount = r; } 99 | }; 100 | #pragma clang diagnostic pop 101 | 102 | struct HeapBin { 103 | static const Word farheap_extra_words = sizeof(HeapbinBox) / sizeof(Word); 104 | 105 | static Word box_size(Word bytes); 106 | }; 107 | 108 | } // ns layout 109 | -------------------------------------------------------------------------------- /emulator/src/vm.cpp: -------------------------------------------------------------------------------- 1 | #include "vm.h" 2 | #include "code_server.h" 3 | #include "dist.h" 4 | #include "heap.h" 5 | #include "platf/gsys_file.h" 6 | #include "platf/gsys_mem.h" 7 | #include "predef_atoms.h" 8 | #include "process.h" 9 | 10 | #include "bif/bif_misc.h" 11 | #include "bif_tab.h" 12 | #include "genop.h" 13 | 14 | #include 15 | 16 | namespace gluon { 17 | 18 | namespace err { 19 | IMPL_EXCEPTION(FeatureMissing) 20 | IMPL_EXCEPTION(TODO) 21 | IMPL_EXCEPTION(BeamLoad) 22 | IMPL_EXCEPTION(Scheduler) 23 | IMPL_EXCEPTION(CodeServer) 24 | IMPL_EXCEPTION(Process) 25 | } // ns err 26 | 27 | VM::VM() : sched_(*this) { 28 | PointerKnowledge::assert(); 29 | 30 | this_node_ = new Node; 31 | codeserver_ = new code::Server(*this); 32 | 33 | vm_loop(true); // initialize labels 34 | 35 | init_predef_atoms(); 36 | premade_.init(*this); 37 | 38 | // codeserver_->path_append("."); 39 | 40 | codeserver_->path_append("/usr/lib/erlang/lib/stdlib-2.4/ebin"); 41 | codeserver_->path_append("/usr/lib/erlang/lib/stdlib-2.5/ebin"); 42 | 43 | codeserver_->path_append("/usr/lib/erlang/lib/erts-6.4.1/ebin"); 44 | codeserver_->path_append("/usr/lib/erlang/lib/erts-7.0/ebin"); 45 | 46 | codeserver_->path_append("/usr/lib/erlang/lib/xmerl-1.3.7/ebin"); 47 | codeserver_->path_append("/usr/lib/erlang/lib/xmerl-1.3.8/ebin"); 48 | 49 | // create root process and set it to some entry function 50 | root_process_ = new Process(*this, the_non_value); 51 | codeserver_->load_module(root_process_, atom::INIT); 52 | codeserver_->load_module(root_process_, atom::ERLANG); 53 | } 54 | 55 | RegisterResult VM::register_name(Term name, Term pid_port) { 56 | if (reg_names_.contains(name)) { 57 | return RegisterResult::RegistrationExists; 58 | } 59 | 60 | Process* p = scheduler().find(pid_port); 61 | if (!p) { 62 | return RegisterResult::ProcessNotFound; 63 | } 64 | reg_names_.insert(name, p); 65 | p->registered_as(name); 66 | return RegisterResult::Ok; 67 | } 68 | 69 | Term VM::to_atom(const Str& s) { 70 | Term a = to_existing_atom(s); 71 | return a.is_nonvalue() ? new_atom(s) : a; 72 | } 73 | 74 | Term VM::new_atom(const Str& s) { 75 | Term new_a = Term::make_atom(atom_id_counter_); 76 | atoms_.insert(s, new_a); 77 | reverse_atoms_.insert(new_a, s); 78 | atom_id_counter_++; 79 | return new_a; 80 | } 81 | 82 | void VM::init_predef_atoms() { 83 | const char* p = atom::g_predef_atoms; 84 | atom_id_counter_ = 1; 85 | 86 | while (*p) { 87 | Word len = (Word)(p[0]); 88 | new_atom(Str(p + 1, len)); 89 | p += len + 1; 90 | } 91 | // TODO: get rid of 92 | } 93 | 94 | const Str& VM::find_atom(Term a) const { 95 | G_ASSERT(a.is_atom()); 96 | auto presult = reverse_atoms_.find_ptr(a); 97 | return presult ? *presult : const_empty_str_; 98 | } 99 | 100 | Node* VM::dist_this_node() { 101 | #if FEATURE_ERL_DIST 102 | G_TODO("implement Node and this node variable") 103 | #endif 104 | return this_node_; 105 | } 106 | 107 | // For now all heaps are located in normal C++ heap 108 | erts::Heap* VM::get_heap(VM::HeapType) { 109 | return nullptr; 110 | } 111 | 112 | static bool find_bif_compare_fun(const bif::BIFIndex& a, 113 | const bif::BIFIndex& b) { 114 | return a.fun < b.fun || (a.fun == b.fun && a.arity < b.arity); 115 | } 116 | 117 | void* VM::find_bif(const MFArity& mfa) const { 118 | if (mfa.mod != atom::ERLANG) { 119 | return nullptr; 120 | } 121 | 122 | bif::BIFIndex sample; 123 | sample.fun = mfa.fun; 124 | sample.arity = mfa.arity; 125 | auto i = std::lower_bound(&bif::g_bif_table[0], 126 | &bif::g_bif_table[bif::bif_table_size], sample, 127 | find_bif_compare_fun); 128 | if (i->fun == mfa.fun && i->arity == mfa.arity) { 129 | return i->bif_fn; 130 | } 131 | return nullptr; 132 | } 133 | 134 | Term VM::apply_bif(Process* proc, MFArity& mfa, Term* args) { 135 | void* b = find_bif(mfa); 136 | if (b) { 137 | switch (mfa.arity) { 138 | case 0: 139 | return ((bif0_fn)b)(proc); 140 | case 1: 141 | return ((bif1_fn)b)(proc, args[0]); 142 | case 2: 143 | return ((bif2_fn)b)(proc, args[0], args[1]); 144 | case 3: 145 | return ((bif3_fn)b)(proc, args[0], args[1], args[2]); 146 | } // switch 147 | } // if b 148 | return proc->bif_error(atom::UNDEF); 149 | } 150 | 151 | Term VM::apply_bif(Process* proc, Word arity, void* fn, Term* args) { 152 | if (!fn) { 153 | return proc->bif_error(atom::BADFUN); 154 | } 155 | switch (arity) { 156 | case 0: 157 | return ((bif0_fn)fn)(proc); 158 | case 1: 159 | return ((bif1_fn)fn)(proc, args[0]); 160 | case 2: 161 | return ((bif2_fn)fn)(proc, args[0], args[1]); 162 | case 3: 163 | return ((bif3_fn)fn)(proc, args[0], args[1], args[2]); 164 | } 165 | return proc->bif_error(atom::UNDEF); 166 | } 167 | 168 | void VM::assert_valid_vmloop_label(const void* p) const { 169 | G_ASSERT(p >= g_opcode_labels[1] && 170 | p <= g_opcode_labels[genop::max_opcode]); 171 | } 172 | 173 | void PremadeBeaminstr::init(const VM& vm) { 174 | instr_ = SysMemory::allocate((Word)PremadeIndex::Total_count); 175 | auto p = instr_.mem(); 176 | p[(Word)PremadeIndex::Apply_mfargs_] = 177 | (Word)vm.g_opcode_labels[(Word)genop::Opcode::Apply_mfargs_]; 178 | 179 | p[(Word)PremadeIndex::Normal_exit_] = 180 | (Word)vm.g_opcode_labels[(Word)genop::Opcode::Normal_exit_]; 181 | 182 | p[(Word)PremadeIndex::Error_exit_] = 183 | (Word)vm.g_opcode_labels[(Word)genop::Opcode::Error_exit_]; 184 | } 185 | 186 | } // ns gluon 187 | -------------------------------------------------------------------------------- /emulator/src/vm_loop.cpp: -------------------------------------------------------------------------------- 1 | #include "vm.h" 2 | #include "process.h" 3 | 4 | // has own gluon namespace 5 | #include "vm_impl.h" 6 | 7 | namespace gluon { 8 | 9 | void VM::vm_loop(bool init) { 10 | impl::VMRuntimeContext ctx(*this); 11 | void* jmp_to; 12 | Process* proc = nullptr; 13 | Scheduler& sched = VM::scheduler(); 14 | impl::WantSchedule opcode_result = impl::WantSchedule::KeepGoing; 15 | 16 | if (init) { 17 | goto vm_jump_table_init; 18 | } 19 | 20 | schedule: 21 | if (proc && opcode_result != impl::WantSchedule::Error) { 22 | // On error process will already be swapped out 23 | ctx.swap_out(proc); 24 | } 25 | proc = sched.next(); 26 | if (!proc) { 27 | return; 28 | } // program finished? 29 | ctx.swap_in(proc); // get copies of quick access data from environment 30 | 31 | next_instr: 32 | jmp_to = (void*)(ctx.ip(0)); 33 | 34 | // Print current Pid, MFA (if possible) 35 | ctx.println(); 36 | libc::fmt(cBlue "["); 37 | // Std::fmt("[0x" FMT_0xHEX, (Word)ctx.ip); 38 | proc->get_pid().print(*this); 39 | libc::fmt(";"); 40 | if (feature_code_ranges) { 41 | codeserver().print_mfa(ctx.ip()); // prints mfarity or pointer 42 | } 43 | libc::fmt("]: " cRst); 44 | 45 | ctx.inc_ip(); 46 | ctx.vm_.assert_valid_vmloop_label(jmp_to); 47 | goto* jmp_to; 48 | 49 | #include "vm_loop.inc.cpp" 50 | 51 | vm_end:; 52 | } 53 | } // ns gluon 54 | -------------------------------------------------------------------------------- /emulator/src/vm_loop_ctx.cpp: -------------------------------------------------------------------------------- 1 | #include "vm_loop_ctx.h" 2 | 3 | namespace gluon { 4 | namespace impl {} // ns impl 5 | } // ns gluon 6 | -------------------------------------------------------------------------------- /emulator/src/wrap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pointer.h" 3 | 4 | namespace gluon { 5 | 6 | // Wraps a type into a class to make it separate type 7 | template 8 | class Wrap { 9 | private: 10 | T value_; 11 | 12 | public: 13 | Wrap() : value_() {} 14 | explicit Wrap(T x) : value_(x) {} 15 | T value() const { return value_; } 16 | void set_value(T newvalue) { value_ = newvalue; } 17 | }; 18 | 19 | // Index in label table, wrapped to create a distinct compile-time type 20 | struct LabelIndex : Wrap { 21 | LabelIndex() = default; 22 | explicit LabelIndex(Word x) : Wrap(x) {} 23 | explicit LabelIndex(const LabelIndex& other) = default; 24 | explicit LabelIndex(LabelIndex&& other) = default; 25 | LabelIndex& operator=(const LabelIndex&) = default; 26 | LabelIndex& operator=(LabelIndex&&) = default; 27 | bool operator<(const LabelIndex& other) const { 28 | return value() < other.value(); 29 | } 30 | }; 31 | 32 | // Pointer to code, wrapped to have a distinct type 33 | struct CodePointer : Wrap { 34 | CodePointer() : Wrap(nullptr) {} 35 | explicit CodePointer(Word* x) : Wrap(x) {} 36 | 37 | CodePointer(const CodePointer& other) = default; 38 | CodePointer(CodePointer&& other) = default; 39 | CodePointer& operator=(const CodePointer&) = default; 40 | CodePointer& operator=(CodePointer&&) = default; 41 | 42 | bool operator<(const CodePointer& other) const { 43 | return value() < other.value(); 44 | } 45 | bool operator<=(const CodePointer& other) const { 46 | return value() <= other.value(); 47 | } 48 | bool operator>(const CodePointer& other) const { 49 | return value() > other.value(); 50 | } 51 | bool operator>=(const CodePointer& other) const { 52 | return value() >= other.value(); 53 | } 54 | operator bool() const { return value() != nullptr; } 55 | 56 | template 57 | Word& operator[](Index i) const { 58 | return value()[i]; 59 | } 60 | 61 | template 62 | void operator+=(Offset t) { 63 | set_value(value() + t); 64 | } 65 | 66 | void operator++(int) { set_value(value() + 1); } 67 | 68 | template 69 | CodePointer operator+(Offset t) { 70 | return CodePointer(value() + t); 71 | } 72 | 73 | bool is_null() const { return value() == nullptr; } 74 | bool is_not_null() const { return value() != nullptr; } 75 | }; 76 | 77 | // 78 | // Continuation Pointer (CP) is a term-looking value tagged as Boxed in its 79 | // low bits, and having PointerHTag::Continuation in its high bits. 80 | // It is used as return address and also marks stack frame 81 | // 82 | class ContinuationPointer { 83 | Word value_; 84 | 85 | public: 86 | explicit ContinuationPointer(Word x) : value_(x) {} 87 | ContinuationPointer() : value_(0) {} 88 | 89 | void set_word(Word x) { value_ = x; } 90 | Word value() const { return value_; } 91 | 92 | // Is a valid continuation 93 | bool check() const { return check(value_); } 94 | 95 | static bool check(Word x) { 96 | return (x != 0) && 97 | PointerKnowledge::high_tag(x) == PointerHTag::Continuation 98 | #ifdef G_DEBUG 99 | && PointerKnowledge::low_tag(x) == PointerLTag::Boxed 100 | #endif 101 | ; 102 | } 103 | 104 | // Set highest bit to mark CP pushed on stack 105 | static ContinuationPointer make_cp(CodePointer x) { 106 | // just in case, hope word x is not already tagged 107 | G_ASSERT(false == ContinuationPointer((Word)x.value()).check()); 108 | G_ASSERT(PointerKnowledge::is_userspace_pointer(x.value())); 109 | return ContinuationPointer(PointerKnowledge::set_tags( 110 | x.value(), PointerHTag::Continuation, PointerLTag::Boxed)); 111 | } 112 | 113 | // Check and clear highest bit to mark CP pushed on stack 114 | CodePointer untag() const { 115 | G_ASSERT(check() == true); 116 | auto result = PointerKnowledge::untag(value_); 117 | G_ASSERT(PointerKnowledge::is_userspace_pointer(result)); 118 | return CodePointer(result); 119 | } 120 | }; 121 | 122 | } // ns gluon 123 | -------------------------------------------------------------------------------- /emulator/test/fructose/AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Original author: Andrew Peter Marlow 2 | C++ test harness generator by Chris Main. 3 | Python test harness generator by Brian Neal. 4 | 5 | This package evolved after many discussions and approaches to unit testing. 6 | CppUnit remains the grand unit test framework, this is a very much cutdown 7 | and simplified one. It has been inspired by CppUnit. Several of the ideas 8 | connected with combining test harness development with command line 9 | handling were as a result of joint work between myself and Andreas Stirneman. 10 | FRUCTOSE has also taken some inspiration from CUTE by Peter Sommerlad. 11 | The ideas about one class/one test harness and about loop 12 | asserts are thanks to some discussions about testing I had with John Lakos. 13 | I also owe thanks to Salvatore Lovene, who made me aware of the TCLAP library 14 | for command line argument handling (even though it is no longer used). 15 | -------------------------------------------------------------------------------- /emulator/test/fructose/COPYING: -------------------------------------------------------------------------------- 1 | FRUCTOSE is released under the LGPL. 2 | This means it can be used in proprietary programs. 3 | See LICENSE.txt for details. 4 | 5 | -------------------------------------------------------------------------------- /emulator/test/fructose/fructose.h: -------------------------------------------------------------------------------- 1 | /* FRUCTOSE C++ unit test library. 2 | Copyright (c) 2012 Andrew Peter Marlow. All rights reserved. 3 | http://www.andrewpetermarlow.co.uk. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | 20 | #ifndef INCLUDED_FRUCTOSE_FRUCTOSE 21 | #define INCLUDED_FRUCTOSE_FRUCTOSE 22 | 23 | // As you can see, this is just a convenience header 24 | // that saves you giving the two includes below. 25 | 26 | #include 27 | #include 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /emulator/test/test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include "g_sys_stdlib.h" 3 | 4 | using namespace gluon; 5 | namespace gluontest { 6 | 7 | void run_tests(int argc, const char* argv[]) { 8 | gluon_test_terms(argc, argv); 9 | gluon_test_processes(argc, argv); 10 | gluon_test_ranges(argc, argv); 11 | 12 | // Std::exit(0); 13 | } 14 | 15 | } // ns gluontest 16 | -------------------------------------------------------------------------------- /emulator/test/test.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if G_TEST 4 | namespace gluontest { 5 | 6 | void run_tests(int argc, const char* argv[]); 7 | 8 | void gluon_test_terms(int argc, const char* argv[]); 9 | void gluon_test_processes(int argc, const char* argv[]); 10 | void gluon_test_ranges(int argc, const char* argv[]); 11 | 12 | } // ns gluontest 13 | #endif 14 | -------------------------------------------------------------------------------- /emulator/test/test_code_index.cpp: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include 3 | #include "g_code_index.h" 4 | 5 | #pragma clang diagnostic ignored "-Wweak-vtables" 6 | using namespace gluon; 7 | 8 | namespace gluontest { 9 | struct range_test_t : public fructose::test_base { 10 | void test_range_basics(const std::string& test_name) { 11 | code::Index i; 12 | i.add(code::Range((word_t*)100, (word_t*)200), 1); 13 | fructose_assert(i.find((word_t*)150) == 1); 14 | fructose_assert(i.find((word_t*)100) == 1); 15 | fructose_assert(i.find((word_t*)199) == 1); 16 | fructose_assert(i.find((word_t*)200) == 0); // upper not inclusive 17 | 18 | i.add(code::Range((word_t*)250, (word_t*)300), 3); 19 | i.add(code::Range((word_t*)1000, (word_t*)2000), 2); 20 | 21 | fructose_assert(i.find((word_t*)1000) == 2); 22 | fructose_assert(i.find((word_t*)1500) == 2); 23 | fructose_assert(i.find((word_t*)1999) == 2); 24 | fructose_assert(i.find((word_t*)2000) == 0); // upper not inclusive 25 | 26 | fructose_assert(i.find((word_t*)250) == 3); 27 | fructose_assert(i.find((word_t*)275) == 3); 28 | fructose_assert(i.find((word_t*)299) == 3); 29 | fructose_assert(i.find((word_t*)300) == 0); // upper not inclusive 30 | } 31 | 32 | }; // struct 33 | 34 | void gluon_test_ranges(int argc, const char* argv[]) { 35 | range_test_t tests; 36 | tests.add_test("codeindex.ranges", &range_test_t::test_range_basics); 37 | tests.run(argc, const_cast(argv)); 38 | } 39 | 40 | } // ns gluontest 41 | -------------------------------------------------------------------------------- /emulator/test/test_process.cpp: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | #include "g_process.h" 4 | /* 5 | #include "g_code.h" 6 | #include "g_code_server.h" 7 | #include "g_error.h" 8 | #include "g_module.h" 9 | #include "g_predef_atoms.h" 10 | #include "g_term_helpers.h" 11 | #include "g_vm.h" 12 | */ 13 | #include 14 | #include "bif/g_bif_misc.h" 15 | #pragma clang diagnostic ignored "-Wweak-vtables" 16 | 17 | using namespace gluon; 18 | namespace gluontest { 19 | 20 | struct process_test_t : public fructose::test_base { 21 | /*void test_process_stack(const std::string &tn) { 22 | ProcessStack s; 23 | s.push(Term::make_small_u(1)); // y[3] 24 | s.push_n_nils(3); // y[0 1 2] 25 | s.push(Term::make_small_u(2)); // y[-1] 26 | s.set_y(1, NONVALUE); 27 | fructose_assert(s.get_y(0).is_nil()); 28 | fructose_assert(s.get_y(1).is_non_value()); 29 | fructose_assert(s.get_y(2).is_nil()); 30 | fructose_assert(s.get_y(3) == Term::make_small_u(1)); 31 | }*/ 32 | 33 | }; // struct 34 | 35 | void gluon_test_processes(int argc, const char* argv[]) { 36 | // process_test_t tests; 37 | // tests.add_test("process.stack", &process_test_t::test_process_stack); 38 | // tests.run(argc, const_cast(argv)); 39 | } 40 | 41 | } // ns gluontest 42 | -------------------------------------------------------------------------------- /emulator/test/test_term.cpp: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include "g_heap.h" 3 | #include "g_term.h" 4 | #include "g_vm.h" 5 | 6 | /* 7 | #include "g_binary.h" 8 | #include "g_code_server.h" 9 | #include "g_fun.h" 10 | #include "g_heap.h" 11 | #include "g_module.h" // for export_t class 12 | #include "g_term_helpers.h" 13 | */ 14 | 15 | #include "../test/fructose/fructose.h" 16 | #include "bif/g_bif_misc.h" 17 | 18 | #pragma clang diagnostic ignored "-Wweak-vtables" 19 | 20 | using namespace gluon; 21 | namespace gluontest { 22 | 23 | struct term_test_t : public fructose::test_base { 24 | void basics(const std::string& test_name) { 25 | Term t_tuple_el[10]; 26 | Term t_tuple = Term::make_tuple(t_tuple_el, 10); 27 | fructose_assert(t_tuple.is_tuple()); 28 | } 29 | 30 | void comparison(const std::string& tn) { 31 | proc::Heap heap; 32 | Term l1 = Term::allocate_cons(&heap, Term::make_small(3), NIL); 33 | Term l2 = Term::allocate_cons(&heap, Term::make_small(2), l1); 34 | Term l3 = Term::allocate_cons(&heap, Term::make_small(1), l2); 35 | 36 | Term m1 = Term::allocate_cons(&heap, Term::make_small(1), NIL); 37 | Term m2 = Term::allocate_cons(&heap, Term::make_small(2), m1); 38 | Term m3 = Term::allocate_cons(&heap, Term::make_small(3), m2); 39 | 40 | fructose_assert(bif::are_terms_equal(l3, m3, false) == false); 41 | } 42 | 43 | void small(const std::string& test_name) { 44 | Term s1 = Term::make_small(-1); 45 | Term s2 = Term::make_small(0); 46 | Term s3 = Term::make_small(1); 47 | fructose_assert(s1.small_get_signed() == -1); 48 | fructose_assert(s2.small_get_signed() == 0); 49 | fructose_assert(s3.small_get_signed() == 1); 50 | } 51 | 52 | void proc_bin(const std::string& tn) { 53 | proc::Heap heap; 54 | Term pb1 = Term::make_binary(&heap, 1024); 55 | u8_t* ptr1 = pb1.binary_get(); 56 | // There was a moment when ptr returned was a very small integer. 57 | // TODO: Fix this test to make more sense 58 | fructose_assert((word_t)ptr1 > 0x10000); 59 | } 60 | }; // struct 61 | 62 | void gluon_test_terms(int argc, const char* argv[]) { 63 | term_test_t tests; 64 | tests.add_test("term.basics", &term_test_t::basics); 65 | tests.add_test("term.comparison", &term_test_t::comparison); 66 | tests.add_test("term.small", &term_test_t::small); 67 | tests.add_test("term.procbin", &term_test_t::proc_bin); 68 | tests.run(argc, const_cast(argv)); 69 | } 70 | 71 | } // ns gluontest 72 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, [ 3 | {beam, ".*", {git, "https://github.com/tonyrog/beam.git"}} 4 | ]}. 5 | 6 | {relx, [{release, {'asm', "0.1.0"}, 7 | ['asm', 8 | sasl]}, 9 | 10 | {sys_config, "./config/sys.config"}, 11 | {vm_args, "./config/vm.args"}, 12 | 13 | {dev_mode, true}, 14 | {include_erts, false}, 15 | 16 | {extended_start_script, true}] 17 | }. 18 | 19 | {profiles, [{prod, [{relx, [{dev_mode, false}, 20 | {include_erts, true}]}] 21 | }] 22 | }. 23 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | *.S 2 | *.beam 3 | *.ir 4 | *.gleam 5 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | # Should be equivalent to your list of C files, if you don't build selectively 2 | SRC=$(wildcard *.erl) 3 | BEAM = $(SRC:.erl=.beam) 4 | 5 | .PHONY: all 6 | all: $(BEAM) 7 | 8 | %.beam: %.erl 9 | erlc +debug_info -S $< && erlc $< 10 | -------------------------------------------------------------------------------- /test/g_test1.erl: -------------------------------------------------------------------------------- 1 | -module(g_test1). 2 | -export([test/0 3 | ]). 4 | 5 | test() -> 6 | [ 7 | %[5,4,3,2,1] = rev([1,2,3,4,5]), 8 | %0 = recurse(10), 9 | %false = test_eq(), 10 | %test_list_ops(), 11 | %test_extcalls(), 12 | %test_case(), 13 | %test_hof(), 14 | %test_hof_fold(), 15 | %test_hof_nested(), 16 | %test_lc(), 17 | %test_send_receive(), 18 | %test_ring(), 19 | %test_try_catch(), 20 | %test_try_of_catch(), 21 | %test_mochijson(), 22 | test_apply(lists, erlang), 23 | done]. 24 | 25 | %%----------------------------------------------- 26 | test_apply(X, Y) -> 27 | apply(X, any, [test1, test2]), 28 | Y:is_boolean(derp). 29 | 30 | test_try_catch() -> 31 | try erlang:error(hello) 32 | catch error:E -> E = hello 33 | end. 34 | 35 | test_try_of_catch() -> 36 | try self() of 37 | X when is_pid(X) -> erlang:error(hello); 38 | Y -> not_ok 39 | catch error:E -> E = hello 40 | end. 41 | 42 | test_send_receive() -> 43 | self() ! test, 44 | receive 45 | test -> ok; 46 | X -> {fail, X} 47 | end. 48 | 49 | test_lc() -> 50 | NumNodes = 5, 51 | [ID || ID <- lists:seq(1, NumNodes)]. 52 | 53 | test_ring() -> 54 | ring:create(5). 55 | 56 | test_mochijson() -> 57 | mochijson:encode({struct, [ {test_neg, -10000}, {test, 10000} ]}). 58 | % we need to go deeper to debuf 59 | %mochijson:json_encode_proplist([{hello, "world"}], {encoder, unicode, null}). 60 | 61 | test_hof_nested() -> 62 | C = fun(X, _) -> X end, 63 | %[{b} | Z] = lists:foldl(C, [], [a,b]), 64 | Z = lists:foldl(C, [], [a]), 65 | lists:reverse(Z). 66 | 67 | test_hof() -> 68 | F = fun(A,B) -> A =< B end, 69 | [1,2,3,4] = lists:sort(F, [3,2,4,1]). 70 | 71 | test_hof_fold() -> 72 | % test fold 73 | M = 2, 74 | G = fun(X, A) -> (X + A) * M end, 75 | 15 = my_foldl(G, 0, [1,2,3,4,5]). % sum fold 76 | 77 | my_foldl(F, Accu, [Hd|Tail]) -> 78 | my_foldl(F, F(Hd, Accu), Tail); 79 | my_foldl(F, Accu, []) when is_function(F, 2) -> Accu. 80 | 81 | test_case() -> 82 | [1,2,3,4] = lists:sort([3,2,4,1]). 83 | 84 | test_extcalls() -> 85 | [1,2,3] = lists:reverse([3,2,1]). 86 | 87 | test_eq() -> 88 | [1,2,3,4] == rev([1,2,3,4]). 89 | 90 | test_list_ops() -> 91 | X = [1,2,3,4,5], 92 | 5 = my_last2(X), 93 | 4 = my_but_last2(X), 94 | 2 = element_at(2, X), 95 | 5 = len(X), 96 | [5,4,3,2,1] = rev(X), 97 | false = is_palindrome([]), 98 | false = is_palindrome(X), 99 | true = is_palindrome([1,2,3,2,1]). 100 | 101 | %f_test() -> 102 | % F = fun(X) -> X * 2 end, 103 | % F(2). 104 | 105 | recurse(X) when X > 0 -> recurse(X-1); 106 | recurse(X) -> X. 107 | 108 | %% From 99 problems: P01 Find the last box of a list. 109 | %% Variant without using list reverse 110 | my_last2([]) -> false; 111 | my_last2([H|[]]) -> H; 112 | my_last2([_H|T]) when length(T) == 1 -> 113 | [H1|[]] = T, 114 | H1; 115 | my_last2([_H|T]) -> 116 | my_last2(T). 117 | 118 | %% From 99 problems: P02 Find the last but one box of a list. 119 | %% Variant without using list reverse 120 | my_but_last2([])-> false; 121 | my_but_last2([_H|[]]) -> false; 122 | my_but_last2([H|T]) when length(T) == 1 -> H; 123 | my_but_last2([_H|T]) -> my_but_last2(T). 124 | 125 | %% From 99 problems: P03 Find the K'th element of a list. 126 | %% Find the K'th element of a list (1-based) 127 | element_at(K,L) when length(L) < K -> false; 128 | element_at(K,L)-> element_at(K,L,1). 129 | element_at(K,[H|_T],C) when C == K-> H; 130 | element_at(K,[_H|T],C) -> element_at(K,T,C+1). 131 | 132 | %% From 99 problems: P04 Find the number of elements of a list. 133 | len([])-> 0; 134 | len(L) -> len(L,0). 135 | 136 | len([],Count) -> Count; 137 | len([_H|T],Count)-> len(T,Count+1). 138 | 139 | %% From 99 problems: P05 Reverse a list. 140 | rev([])-> []; 141 | rev(L) -> rev(L,[]). 142 | rev([],R)-> R; 143 | rev([H|T],R)-> rev(T,[H|R]). 144 | 145 | %% P06 Find out whether a list is a palindrome. 146 | is_palindrome([])-> false; 147 | is_palindrome(L) when length(L) == 1 -> false; 148 | is_palindrome(L) -> case L == rev(L) of true -> true; false -> false end. 149 | -------------------------------------------------------------------------------- /test/g_test2.erl: -------------------------------------------------------------------------------- 1 | %% Exceptions test 2 | -module(g_test2). 3 | 4 | -export([test/0]). 5 | 6 | test() -> 7 | test_inline_catch(), 8 | %test_try_catch(), 9 | ok. 10 | 11 | test_inline_catch() -> 12 | F = fun() -> erlang:throw(test_exception) end, 13 | (catch F()). 14 | -------------------------------------------------------------------------------- /test/ring.erl: -------------------------------------------------------------------------------- 1 | %%% Token Ring 2 | %%% The following example has been taken from 3 | %%% http://trigonakis.com/blog/2011/05/26/introduction-to-erlang-message-passing/ 4 | %%% 5 | %%% This application creates NumNodes processes and arranges them in a ring 6 | %%% (every process has one next process). 7 | %%% Then the coordinator “inserts” a token in the first node of the ring. 8 | %%% Every node (process) receiving the token increases its value by 1 and 9 | %%% sends it to the next node. The application stops when the token has value 10 | %%% greater than the MAXVAL. 11 | -module(ring). 12 | -export([create/1, node/2, connect/1]). 13 | 14 | -define(MAXVAL, 10). 15 | 16 | %% creates the ring's nodes, connects them in a ring, sends the token in the 17 | %% ring, and collects the exit messages from the nodes 18 | create(NumNodes) when is_integer(NumNodes), NumNodes > 1 -> 19 | Nodes = [spawn(?MODULE, node, [ID, self()]) || ID <- lists:seq(1, NumNodes)], 20 | 21 | %% notice that the above expression denotes a list using 22 | %% the mechanism of list comprehension, similar to Haskell list comprehension 23 | %% Remember that the function spawn returns a process identifier, 24 | %% so to the Nodes variable we associate the list of the created process nodes. 25 | ring:connect(Nodes), 26 | hd(Nodes) ! {token, 0}, 27 | getexits(Nodes). 28 | 29 | %% collects the exit messages from the nodes 30 | getexits([]) -> 31 | %io:format("[Coord] Done.~n"), 32 | ok; 33 | getexits(Nodes) -> 34 | receive 35 | {Node, exit} -> 36 | case lists:member(Node, Nodes) of 37 | true -> 38 | getexits(lists:delete(Node, Nodes)); 39 | _ -> 40 | getexits(Nodes) 41 | end 42 | end. 43 | 44 | %% little trick in order to connect the last with the first node 45 | %% handle the [nd0, nd1, ..., ndN] list as [nd0, nd1, ..., ndN, nd0] 46 | %% 47 | %% Notice the use of a particular sort of pattern matching, enabling 48 | %% to associate the whole input to the variable N and the head of the 49 | %% input list to the variable H. 50 | %% This particular sort of pattern matching is present also in Haskell (as-pattern) 51 | %% and PICT (layered pattern). 52 | %% In Haskell and PICT the following notation is used: x@p (where x is a variable and p a pattern). 53 | connect(N = [H | _]) -> 54 | connect_(N ++ [H]). 55 | 56 | %% connects the nodes to form a ring 57 | connect_([]) -> 58 | connected; 59 | connect_([_]) -> 60 | connected; 61 | connect_([N1, N2 | Nodes]) -> 62 | N1 ! {self(), connect, N2}, 63 | connect_([N2 | Nodes]). 64 | 65 | %% The computation in each process node consists in the evaluation of 66 | %% the node function below. 67 | %% The node function initially waits for the next node's pid and then proceed 68 | %% by evaluating the other node function (that can be recognized as different from 69 | %% the first one, since it has three arguments instead of two. 70 | node(ID, CrdId) -> 71 | receive 72 | {CrdId, connect, NxtNdId} -> 73 | %io:format("[~p:~p] got my next ~p~n", [ID, self(), NxtNdId]), 74 | node(ID, CrdId, NxtNdId) 75 | end. 76 | 77 | %% the main functionality of a node; receive the token, increase its value and 78 | %% send it to the next node on the ring 79 | node(ID, CrdId, NxtNdId) -> 80 | receive 81 | {token, Val} -> 82 | if 83 | Val < ?MAXVAL -> 84 | NxtNdId ! {token, Val + 1}, 85 | node(ID, CrdId, NxtNdId); 86 | true -> 87 | %io:format("[~p:~p] token value ~p~n", [ID, self(), Val]), 88 | case erlang:is_process_alive(NxtNdId) of 89 | true -> 90 | NxtNdId ! {token, Val + 1}; 91 | _ -> 92 | ok 93 | end, 94 | CrdId ! {self(), exit}, 95 | done 96 | end 97 | end. 98 | --------------------------------------------------------------------------------