From 986a7e6734982ce8d2b33d0de77cb57358b9fa83 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Wed, 7 Aug 2019 13:47:51 +0200 Subject: [PATCH 01/10] Add a store map fate value. A store map is a (contract local) unique id pointing to a map saved in the contract store, plus a cache of updates. --- include/aeb_fate_data.hrl | 9 +++++++++ src/aeb_fate_data.erl | 6 ++++++ src/aeb_fate_encoding.erl | 7 +++++++ 3 files changed, 22 insertions(+) diff --git a/include/aeb_fate_data.hrl b/include/aeb_fate_data.hrl index 3160b4d..8c07896 100644 --- a/include/aeb_fate_data.hrl +++ b/include/aeb_fate_data.hrl @@ -5,6 +5,7 @@ -define(FATE_LIST_T, list()). -define(FATE_UNIT_T, {tuple, {}}). -define(FATE_MAP_T, #{ fate_type() => fate_type() }). +-define(FATE_STORE_MAP_T, {store_map, #{ fate_type() => fate_type() | ?FATE_MAP_TOMBSTONE }, integer()}). -define(FATE_STRING_T, binary()). -define(FATE_ADDRESS_T, {address, <<_:256>>}). -define(FATE_BYTES_T(N), {bytes, binary()}). @@ -20,6 +21,10 @@ -define(IS_FATE_INTEGER(X), (is_integer(X))). -define(IS_FATE_LIST(X), (is_list(X))). -define(IS_FATE_STRING(X), (is_binary(X))). +-define(IS_FATE_STORE_MAP(X), (is_tuple(X) andalso tuple_size(X) == 3 + andalso store_map == element(1, X) + andalso is_map(element(2, X)) + andalso is_integer(element(3, X)))). -define(IS_FATE_MAP(X), (is_map(X))). -define(IS_FATE_TUPLE(X), (is_tuple(X) andalso (tuple == element(1, X) andalso is_tuple(element(2, X))))). -define(IS_FATE_ADDRESS(X), (is_tuple(X) andalso (address == element(1, X) andalso is_binary(element(2, X))))). @@ -50,6 +55,8 @@ -define(FATE_CHANNEL(X), {channel, X}). -define(FATE_BITS(B), {bits, B}). -define(FATE_TYPEREP(T), {typerep, T}). +-define(FATE_STORE_MAP(Cache, Id), {store_map, Cache, Id}). +-define(FATE_MAP_TOMBSTONE, '__DELETED__'). -define(FATE_INTEGER_VALUE(X), (X)). -define(FATE_BOOLEAN_VALUE(X), (X)). @@ -63,6 +70,8 @@ -define(FATE_CHANNEL_VALUE(X), (element(2, X))). -define(FATE_BITS_VALUE(X), (element(2, X))). -define(FATE_MAP_VALUE(X), (X)). +-define(FATE_STORE_MAP_CACHE(X), (element(2, X))). +-define(FATE_STORE_MAP_ID(X), (element(3, X))). -define(FATE_MAP_SIZE(X), (map_size(X))). -define(FATE_STRING_SIZE(X), (byte_size(X))). -define(FATE_TRUE, true). diff --git a/src/aeb_fate_data.erl b/src/aeb_fate_data.erl index 2660386..eb7b54b 100644 --- a/src/aeb_fate_data.erl +++ b/src/aeb_fate_data.erl @@ -10,6 +10,7 @@ -type fate_list() :: ?FATE_LIST_T. -type fate_unit() :: ?FATE_UNIT_T. -type fate_map() :: ?FATE_MAP_T. +-type fate_store_map() :: ?FATE_STORE_MAP_T. -type fate_string() :: ?FATE_STRING_T. -type fate_address() :: ?FATE_ADDRESS_T. -type fate_hash() :: ?FATE_BYTES_T(32). @@ -71,6 +72,7 @@ , fate_channel/0 , fate_variant/0 , fate_map/0 + , fate_store_map/0 , fate_bits/0 , fate_type_type/0 ]). @@ -82,6 +84,8 @@ , make_tuple/1 , make_string/1 , make_map/1 + , make_store_map/1 + , make_store_map/2 , make_address/1 , make_bytes/1 , make_hash/1 @@ -108,6 +112,8 @@ make_list(L) -> ?MAKE_FATE_LIST(L). make_unit() -> ?FATE_UNIT. make_tuple(T) -> ?FATE_TUPLE(T). make_map(M) -> ?MAKE_FATE_MAP(M). +make_store_map(Id) -> make_store_map(#{}, Id). +make_store_map(Cache, Id) -> ?FATE_STORE_MAP(Cache, Id). make_address(X) -> ?FATE_ADDRESS(X). make_bytes(X) -> ?FATE_BYTES(X). make_hash(X) -> make_bytes(X). diff --git a/src/aeb_fate_encoding.erl b/src/aeb_fate_encoding.erl index 2475c1d..3706322 100644 --- a/src/aeb_fate_encoding.erl +++ b/src/aeb_fate_encoding.erl @@ -93,6 +93,7 @@ %% %% 1000 1111 - FREE (Possibly for bytecode in the future.) -define(OBJECT , 2#10011111). %% 1001 1111 | ObjectType | RLP encoded Array -define(VARIANT , 2#10101111). %% 1010 1111 | [encoded arities] | encoded tag | [encoded values] +-define(MAP_ID , 2#10111111). %% 1011 1111 | RLP encoded integer (store map id) -define(NEG_BITS , 2#11001111). %% 1100 1111 | RLP encoded integer (infinite 1:s bitfield) -define(EMPTY_MAP , 2#11011111). %% 1101 1111 -define(NEG_BIG_INT , 2#11101111). %% 1110 1111 | RLP encoded (integer - 64) @@ -193,6 +194,9 @@ serialize(Map) when ?IS_FATE_MAP(Map) -> <>; +serialize(?FATE_STORE_MAP(Cache, Id)) when Cache =:= #{} -> + %% We should never get to serialization without having flushed the caches. + <>; serialize(?FATE_VARIANT(Arities, Tag, Values)) -> Arities = [A || A <- Arities, is_integer(A), A < 256], Size = length(Arities), @@ -426,6 +430,9 @@ deserialize2(<>) -> false -> error({unknown_map_serialization_format, KVList}) end; +deserialize2(<>) -> + {Id, Rest1} = rlp_decode_int(Rest), + {?FATE_STORE_MAP(#{}, Id), Rest1}; deserialize2(<>) -> {AritiesBin, <>} = aeser_rlp:decode_one(Rest), Arities = binary_to_list(AritiesBin), From e184028261a46473153a4afa3fdc5db50035255d Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Thu, 8 Aug 2019 12:42:03 +0200 Subject: [PATCH 02/10] Code for allocating store maps --- src/aeb_fate_maps.erl | 96 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/aeb_fate_maps.erl diff --git a/src/aeb_fate_maps.erl b/src/aeb_fate_maps.erl new file mode 100644 index 0000000..1f36681 --- /dev/null +++ b/src/aeb_fate_maps.erl @@ -0,0 +1,96 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2019, Aeternity Anstalt +%%% @doc +%%% Functions for manipulating FATE maps. In particular for mediating +%%% between plain map values (represented by Erlang maps) and maps that are +%%% fully or partially saved in the contract store. +%%% @end +%%% ------------------------------------------------------------------- +-module(aeb_fate_maps). + +-include("aeb_fate_data.hrl"). + +-export([allocate_store_maps/2, no_used_ids/0]). + +-export_type([used_ids/0, maps/0]). + +%% Size in bytes of serialization of a map for which we turn it into a store +%% map. It's not worth turning small maps into store maps. +%% Under consensus! +-define(STORE_MAP_THRESHOLD, 500). + +-type fate_value() :: aeb_fate_data:fate_type(). +-type id() :: integer(). +-type used_ids() :: list(id()). %% TODO: more clever representation +-type maps() :: #{ id() => aeb_fate_data:fate_map() | aeb_fate_data:fate_store_map() }. + +-spec allocate_store_maps(used_ids(), [fate_value()]) -> + {[fate_value()], maps()}. +allocate_store_maps(Used, Vals) -> + allocate_store_maps_l(Used, Vals, #{}). + +allocate_store_maps(Used, ?FATE_MAP_TOMBSTONE = Val, Maps) -> {Used, Val, Maps}; +allocate_store_maps(Used, ?FATE_TRUE = Val, Maps) -> {Used, Val, Maps}; +allocate_store_maps(Used, ?FATE_FALSE = Val, Maps) -> {Used, Val, Maps}; +allocate_store_maps(Used, ?FATE_UNIT = Val, Maps) -> {Used, Val, Maps}; +allocate_store_maps(Used, ?FATE_BITS(_) = Val, Maps) -> {Used, Val, Maps}; +allocate_store_maps(Used, ?FATE_BYTES(_) = Val, Maps) -> {Used, Val, Maps}; +allocate_store_maps(Used, ?FATE_ADDRESS(_) = Val, Maps) -> {Used, Val, Maps}; +allocate_store_maps(Used, ?FATE_CONTRACT(_) = Val, Maps) -> {Used, Val, Maps}; +allocate_store_maps(Used, ?FATE_ORACLE(_) = Val, Maps) -> {Used, Val, Maps}; +allocate_store_maps(Used, ?FATE_ORACLE_Q(_) = Val, Maps) -> {Used, Val, Maps}; +allocate_store_maps(Used, ?FATE_CHANNEL(_) = Val, Maps) -> {Used, Val, Maps}; +allocate_store_maps(Used, ?FATE_TYPEREP(_) = Val, Maps) -> {Used, Val, Maps}; +allocate_store_maps(Used, Val, Maps) when ?IS_FATE_INTEGER(Val) -> {Used, Val, Maps}; +allocate_store_maps(Used, Val, Maps) when ?IS_FATE_STRING(Val) -> {Used, Val, Maps}; +allocate_store_maps(Used, ?FATE_TUPLE(Val), Maps) -> + {Used1, Vals, Maps1} = allocate_store_maps_l(Used, tuple_to_list(Val), Maps), + {Used1, ?FATE_TUPLE(list_to_tuple(Vals)), Maps1}; +allocate_store_maps(Used, Val, Maps) when ?IS_FATE_LIST(Val) -> + {Used1, Vals, Maps1} = allocate_store_maps_l(Used, ?FATE_LIST_VALUE(Val), Maps), + {Used1, ?MAKE_FATE_LIST(Vals), Maps1}; +allocate_store_maps(Used, ?FATE_VARIANT(Arities, Tag, Vals), Maps) -> + {Used1, Vals1, Maps1} = allocate_store_maps_l(Used, tuple_to_list(Vals), Maps), + {Used1, ?FATE_VARIANT(Arities, Tag, list_to_tuple(Vals1)), Maps1}; +allocate_store_maps(Used, Val, Maps) when ?IS_FATE_MAP(Val) -> + {Used1, KVs, Maps1} = allocate_store_maps_m(Used, ?FATE_MAP_VALUE(Val), Maps), + Val1 = ?MAKE_FATE_MAP(KVs), + case byte_size(aeb_fate_encoding:serialize(Val1)) < ?STORE_MAP_THRESHOLD of + true -> {Used1, Val1, Maps1}; + false -> + {Id, Used2} = next_id(Used1), + {Used2, ?FATE_STORE_MAP(#{}, Id), Maps1#{Id => Val1}} + end; +allocate_store_maps(Used, ?FATE_STORE_MAP(Cache, _Id) = Val, Maps) when Cache =:= #{} -> + {Used, Val, Maps}; +allocate_store_maps(Used, ?FATE_STORE_MAP(Cache, Id), Maps) -> + {NewId, Used1} = next_id(Used), + {Used1, Cache1, Maps1} = allocate_store_maps_m(Used1, Cache, Maps), + {Used1, ?FATE_STORE_MAP(#{}, NewId), Maps1#{NewId => ?FATE_STORE_MAP(Cache1, Id)}}. + +allocate_store_maps_l(Used, [], Maps) -> {Used, [], Maps}; +allocate_store_maps_l(Used, [H | T], Maps) -> + {Used1, H1, Maps1} = allocate_store_maps(Used, H, Maps), + {Used2, T1, Maps2} = allocate_store_maps(Used1, T, Maps1), + {Used2, [H1 | T1], Maps2}. + +allocate_store_maps_m(Used, Val, Maps) -> + KVs = [ ?FATE_TUPLE(KV) || KV <- maps:to_list(Val) ], + {Used1, KVs1, Maps1} = allocate_store_maps_l(Used, KVs, Maps), + {Used1, maps:from_list([ KV || ?FATE_TUPLE(KV) <- KVs1 ]), Maps1}. + +%% -- Map id allocation ------------------------------------------------------ + +-spec no_used_ids() -> used_ids(). +no_used_ids() -> []. + +-spec next_id(used_ids()) -> {id(), used_ids()}. +next_id(UsedIds) -> + next_id(UsedIds, 0, []). + +next_id(Used, J, Acc) when Used == []; J < hd(Used) -> + {J, lists:reverse(Acc) ++ [J | Used]}; +next_id([I | Used], I, Acc) -> + next_id(Used, I + 1, [I | Acc]); +next_id([I | Used], J, Acc) when J > I -> + next_id(Used, J, [I | Acc]). From c30bfd7b1c9fc3fef759f0edc5a9b9b120a2d482 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 9 Aug 2019 12:52:31 +0200 Subject: [PATCH 03/10] Unfolding store maps --- src/aeb_fate_maps.erl | 47 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/aeb_fate_maps.erl b/src/aeb_fate_maps.erl index 1f36681..d48d070 100644 --- a/src/aeb_fate_maps.erl +++ b/src/aeb_fate_maps.erl @@ -10,7 +10,7 @@ -include("aeb_fate_data.hrl"). --export([allocate_store_maps/2, no_used_ids/0]). +-export([allocate_store_maps/2, unfold_store_maps/2, no_used_ids/0]). -export_type([used_ids/0, maps/0]). @@ -24,6 +24,8 @@ -type used_ids() :: list(id()). %% TODO: more clever representation -type maps() :: #{ id() => aeb_fate_data:fate_map() | aeb_fate_data:fate_store_map() }. +%% -- Allocating store maps -------------------------------------------------- + -spec allocate_store_maps(used_ids(), [fate_value()]) -> {[fate_value()], maps()}. allocate_store_maps(Used, Vals) -> @@ -79,6 +81,49 @@ allocate_store_maps_m(Used, Val, Maps) -> {Used1, KVs1, Maps1} = allocate_store_maps_l(Used, KVs, Maps), {Used1, maps:from_list([ KV || ?FATE_TUPLE(KV) <- KVs1 ]), Maps1}. +%% -- Unfolding store maps --------------------------------------------------- + +-type unfold_fun() :: fun((id()) -> aeb_fate_data:fate_map()). + +-spec unfold_store_maps(unfold_fun(), fate_value()) -> fate_value(). +unfold_store_maps(_Unfold, ?FATE_TRUE = Val) -> Val; +unfold_store_maps(_Unfold, ?FATE_FALSE = Val) -> Val; +unfold_store_maps(_Unfold, ?FATE_UNIT = Val) -> Val; +unfold_store_maps(_Unfold, ?FATE_BITS(_) = Val) -> Val; +unfold_store_maps(_Unfold, ?FATE_BYTES(_) = Val) -> Val; +unfold_store_maps(_Unfold, ?FATE_ADDRESS(_) = Val) -> Val; +unfold_store_maps(_Unfold, ?FATE_CONTRACT(_) = Val) -> Val; +unfold_store_maps(_Unfold, ?FATE_ORACLE(_) = Val) -> Val; +unfold_store_maps(_Unfold, ?FATE_ORACLE_Q(_) = Val) -> Val; +unfold_store_maps(_Unfold, ?FATE_CHANNEL(_) = Val) -> Val; +unfold_store_maps(_Unfold, ?FATE_TYPEREP(_) = Val) -> Val; +unfold_store_maps(_Unfold, Val) when ?IS_FATE_INTEGER(Val) -> Val; +unfold_store_maps(_Unfold, Val) when ?IS_FATE_STRING(Val) -> Val; +unfold_store_maps(Unfold, ?FATE_TUPLE(Val)) -> + Vals = unfold_store_maps_l(Unfold, tuple_to_list(Val)), + ?FATE_TUPLE(list_to_tuple(Vals)); +unfold_store_maps(Unfold, Val) when ?IS_FATE_LIST(Val) -> + ?MAKE_FATE_LIST(unfold_store_maps_l(Unfold, ?FATE_LIST_VALUE(Val))); +unfold_store_maps(Unfold, ?FATE_VARIANT(Arities, Tag, Vals)) -> + Vals1 = unfold_store_maps_l(Unfold, tuple_to_list(Vals)), + ?FATE_VARIANT(Arities, Tag, list_to_tuple(Vals1)); +unfold_store_maps(Unfold, Val) when ?IS_FATE_MAP(Val) -> + ?MAKE_FATE_MAP(unfold_store_maps_m(Unfold, ?FATE_MAP_VALUE(Val))); +unfold_store_maps(Unfold, ?FATE_STORE_MAP(Cache, Id)) -> + StoreMap = Unfold(Id), + maps:fold(fun write_cache/3, unfold_store_maps(Unfold, StoreMap), Cache). + +unfold_store_maps_l(Unfold, Vals) -> + [ unfold_store_maps(Unfold, Val) || Val <- Vals ]. + +unfold_store_maps_m(Unfold, Val) -> + maps:map(fun(_, V) -> unfold_store_maps(Unfold, V) end, Val). + +write_cache(Key, ?FATE_MAP_TOMBSTONE, Map) -> + maps:remove(Key, Map); +write_cache(Key, Val, Map) -> + Map#{ Key => Val }. + %% -- Map id allocation ------------------------------------------------------ -spec no_used_ids() -> used_ids(). From 54dcf364e55f0ffc23d78d07907684714ded4ebd Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 12 Aug 2019 10:59:30 +0200 Subject: [PATCH 04/10] Store map reference counting --- src/aeb_fate_maps.erl | 64 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/aeb_fate_maps.erl b/src/aeb_fate_maps.erl index d48d070..8a01e06 100644 --- a/src/aeb_fate_maps.erl +++ b/src/aeb_fate_maps.erl @@ -10,7 +10,14 @@ -include("aeb_fate_data.hrl"). --export([allocate_store_maps/2, unfold_store_maps/2, no_used_ids/0]). +-export([ allocate_store_maps/2 + , unfold_store_maps/2 + , refcount/1 + , refcount_zero/0 + , refcount_diff/2 + , refcount_union/1 + , refcount_union/2 + , no_used_ids/0 ]). -export_type([used_ids/0, maps/0]). @@ -124,6 +131,61 @@ write_cache(Key, ?FATE_MAP_TOMBSTONE, Map) -> write_cache(Key, Val, Map) -> Map#{ Key => Val }. +%% -- Reference counting ----------------------------------------------------- + +-type refcount() :: #{id() => pos_integer()}. + +-spec refcount_zero() -> refcount(). +refcount_zero() -> #{}. + +-spec refcount_diff(refcount(), refcount()) -> refcount(). +refcount_diff(New, Old) -> + maps:fold(fun(K, N, C) -> maps:update_with(K, fun(M) -> M - N end, -N, C) end, + New, Old). + +-spec refcount_union([refcount()]) -> refcount(). +refcount_union(Counts) -> lists:foldl(fun refcount_union/2, #{}, Counts). + +-spec refcount_union(refcount(), refcount()) -> refcount(). +refcount_union(A, B) -> + maps:fold(fun(K, N, C) -> maps:update_with(K, fun(M) -> M + N end, N, C) end, + B, A). + +-spec refcount(fate_value()) -> refcount(). +refcount(Val) -> refcount(Val, #{}). + +-spec refcount(refcount(), fate_value()) -> refcount(). +refcount(?FATE_TRUE, Count) -> Count; +refcount(?FATE_FALSE, Count) -> Count; +refcount(?FATE_UNIT, Count) -> Count; +refcount(?FATE_BITS(_), Count) -> Count; +refcount(?FATE_BYTES(_), Count) -> Count; +refcount(?FATE_ADDRESS(_), Count) -> Count; +refcount(?FATE_CONTRACT(_), Count) -> Count; +refcount(?FATE_ORACLE(_), Count) -> Count; +refcount(?FATE_ORACLE_Q(_), Count) -> Count; +refcount(?FATE_CHANNEL(_), Count) -> Count; +refcount(?FATE_TYPEREP(_), Count) -> Count; +refcount(Val, Count) when ?IS_FATE_INTEGER(Val) -> Count; +refcount(Val, Count) when ?IS_FATE_STRING(Val) -> Count; +refcount(?FATE_TUPLE(Val), Count) -> + refcount_l(tuple_to_list(Val), Count); +refcount(Val, Count) when ?IS_FATE_LIST(Val) -> + refcount_l(?FATE_LIST_VALUE(Val), Count); +refcount(?FATE_VARIANT(_Arities, _Tag, Vals), Count) -> + refcount_l(tuple_to_list(Vals), Count); +refcount(Val, Count) when ?IS_FATE_MAP(Val) -> + refcount_m(?FATE_MAP_VALUE(Val), Count); +refcount(?FATE_STORE_MAP(Cache, Id), Count) -> + refcount_m(Cache, maps:update_with(Id, fun(N) -> N + 1 end, 1, Count)). + +refcount_l(Vals, Count) -> + lists:foldl(fun refcount/2, Count, Vals). + +refcount_m(Val, Count) -> + %% No maps in map keys + maps:fold(fun(_, V, C) -> refcount(V, C) end, Count, Val). + %% -- Map id allocation ------------------------------------------------------ -spec no_used_ids() -> used_ids(). From 5aee70b8ffcb2dbaa25bcd79d9c1254959a060b0 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 12 Aug 2019 14:37:42 +0200 Subject: [PATCH 05/10] Add arity to CALL_R and CALL_GR and deprecate CALL_TR and CALL_GTR --- src/aeb_fate_generate_ops.erl | 22 ++++++++++++---------- test/asm_code/test.fate | 6 +----- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/aeb_fate_generate_ops.erl b/src/aeb_fate_generate_ops.erl index 55396d4..a588f4c 100644 --- a/src/aeb_fate_generate_ops.erl +++ b/src/aeb_fate_generate_ops.erl @@ -46,11 +46,11 @@ ops_defs() -> [ { 'RETURN', 16#00, true, true, true, 2, [], return, {}, any, "Return from function call, top of stack is return value . The type of the retun value has to match the return type of the function."} , { 'RETURNR', 16#01, true, true, true, 2, [a], returnr, {any}, any, "Push Arg0 and return from function. The type of the retun value has to match the return type of the function."} , { 'CALL', 16#02, true, true, true, 4, [a], call, {string}, any, "Call the function Arg0 with args on stack. The types of the arguments has to match the argument typs of the function."} - , { 'CALL_R', 16#03, true, false, true, 8, [a,is,a], call_r, {contract, string, integer}, any, "Remote call to contract Arg0 and function Arg1 with value Arg2. The types of the arguments has to match the argument typs of the function."} + , { 'CALL_R', 16#03, true, false, true, 8, [a,is,ii,a], call_r, {contract, string, integer, integer}, any, "Remote call to contract Arg0 and Arg2-ary function Arg1 with value Arg3. The types of the arguments has to match the argument typs of the function."} , { 'CALL_T', 16#04, true, true, true, 4, [a], call_t, {string}, any, "Tail call to function Arg0. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."} - , { 'CALL_TR', 16#05, true, false, true, 8, [a,is,a], call_tr, {contract, string, integer}, any, "Remote tail call to contract Arg0 and function Arg1 with value Arg2. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."} - , { 'CALL_GR', 16#06, true, false, true, 8, [a,is,a,a], call_gr, {contract, string, integer, integer}, any, "Remote call with gas cap in Arg3. Otherwise as CALL_R."} - , { 'CALL_GTR', 16#07, true, false, true, 8, [a,is,a,a], call_gtr, {contract, string, integer, integer}, any, "Remote tail call with gas cap in Arg3. Otherwise as CALL_TR."} + , { 'CALL_TR', 16#05, true, false, true, 8, [a,is,a], call_tr, {contract, string, integer}, any, "DEPRECATED: Remote tail call to contract Arg0 and function Arg1 with value Arg2. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."} + , { 'CALL_GR', 16#06, true, false, true, 8, [a,is,ii,a,a], call_gr, {contract, string, integer, integer, integer}, any, "Remote call with gas cap in Arg3. Otherwise as CALL_R."} + , { 'CALL_GTR', 16#07, true, false, true, 8, [a,is,a,a], call_gtr, {contract, string, integer, integer}, any, "DEPRECATED: Remote tail call with gas cap in Arg3. Otherwise as CALL_TR."} , { 'JUMP', 16#08, true, true, true, 3, [ii], jump, {integer}, none, "Jump to a basic block. The basic block has to exist in the current function."} , { 'JUMPIF', 16#09, true, true, true, 4, [a,ii], jumpif, {boolean, integer}, none, "Conditional jump to a basic block. If Arg0 then jump to Arg1."} , { 'SWITCH_V2', 16#0a, true, true, true, 4, [a,ii,ii], switch, {variant, integer, ingeger}, none, "Conditional jump to a basic block on variant tag."} @@ -488,23 +488,25 @@ gen_asm_pp(Module, Path, Ops) -> io:format(File, "format_op(Op, _Symbols) -> io_lib:format(\";; Bad Op: ~~w\\n\", [Op]).\n", []), file:close(File). -gen_format(#{opname := Name}) when (Name =:= 'CALL_R') or (Name =:= 'CALL_TR') -> - io_lib:format("format_op({~w, {immediate, Contract}, {immediate, Function}, Value}, Symbols) ->\n" +gen_format(#{opname := Name}) when (Name =:= 'CALL_R') -> + io_lib:format("format_op({~w, {immediate, Contract}, {immediate, Function}, Arity, Value}, Symbols) ->\n" " [\"~s \", lookup(Contract, Symbols), \".\", " "lookup(Function, Symbols), \" \", " + "format_arg(a, Arity), \" \", " "format_arg(a, Value)];\n" - "format_op({~w, Contract, {immediate, Function}, Value}, Symbols) ->\n" + "format_op({~w, Contract, {immediate, Function}, Arity, Value}, Symbols) ->\n" "[\"~s \", format_arg(a, Contract), \".\", " "lookup(Function, Symbols), \" \", " + "format_arg(a, Arity), \" \", " "format_arg(a, Value)];\n", [Name, atom_to_list(Name), Name, atom_to_list(Name)]); -gen_format(#{opname := Name}) when (Name =:= 'CALL_GR') or (Name =:= 'CALL_GTR') -> - io_lib:format("format_op({~w, {immediate, Contract}, {immediate, Function}, Value, Gas}, Symbols) ->\n" +gen_format(#{opname := Name}) when (Name =:= 'CALL_GR') -> + io_lib:format("format_op({~w, {immediate, Contract}, {immediate, Function}, {immediate, Arity}, Value, Gas}, Symbols) ->\n" " [\"~s \", lookup(Contract, Symbols), \".\", " "lookup(Function, Symbols), \" \", " "format_arg(a, Value), \" \", " "format_arg(a, Gas)];\n" - "format_op({~w, Contract, {immediate, Function}, Value, Gas}, Symbols) ->\n" + "format_op({~w, Contract, {immediate, Function}, {immediate, Arity}, Value, Gas}, Symbols) ->\n" "[\"~s \", format_arg(a, Contract), \".\", " "lookup(Function, Symbols), \" \", " "format_arg(a, Value), \" \", " diff --git a/test/asm_code/test.fate b/test/asm_code/test.fate index 7e3b43b..b80af70 100644 --- a/test/asm_code/test.fate +++ b/test/asm_code/test.fate @@ -30,14 +30,10 @@ FUNCTION tailcall(integer) -> integer FUNCTION remote_call(integer) : integer PUSH arg0 - CALL_R remote.add_five 0 + CALL_R remote.add_five 1 0 INCA RETURN -FUNCTION remote_tailcall(integer) : integer - PUSH arg0 - CALL_TR remote add_five 0 - ;; Test the code from the shell ;; _build/default/rel/aessembler/bin/aessembler console From 15095a74ab68904da1283bee01da68dc3e36bd9f Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 12 Aug 2019 14:40:34 +0200 Subject: [PATCH 06/10] Add has_store_maps function --- src/aeb_fate_maps.erl | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/aeb_fate_maps.erl b/src/aeb_fate_maps.erl index 8a01e06..dcec997 100644 --- a/src/aeb_fate_maps.erl +++ b/src/aeb_fate_maps.erl @@ -11,6 +11,7 @@ -include("aeb_fate_data.hrl"). -export([ allocate_store_maps/2 + , has_store_maps/1 , unfold_store_maps/2 , refcount/1 , refcount_zero/0 @@ -151,21 +152,26 @@ refcount_union(A, B) -> maps:fold(fun(K, N, C) -> maps:update_with(K, fun(M) -> M + N end, N, C) end, B, A). +-spec has_store_maps(fate_value()) -> boolean(). +has_store_maps(Val) -> + refcount_zero() /= refcount(Val). + -spec refcount(fate_value()) -> refcount(). refcount(Val) -> refcount(Val, #{}). -spec refcount(refcount(), fate_value()) -> refcount(). -refcount(?FATE_TRUE, Count) -> Count; -refcount(?FATE_FALSE, Count) -> Count; -refcount(?FATE_UNIT, Count) -> Count; -refcount(?FATE_BITS(_), Count) -> Count; -refcount(?FATE_BYTES(_), Count) -> Count; -refcount(?FATE_ADDRESS(_), Count) -> Count; -refcount(?FATE_CONTRACT(_), Count) -> Count; -refcount(?FATE_ORACLE(_), Count) -> Count; -refcount(?FATE_ORACLE_Q(_), Count) -> Count; -refcount(?FATE_CHANNEL(_), Count) -> Count; -refcount(?FATE_TYPEREP(_), Count) -> Count; +refcount(?FATE_MAP_TOMBSTONE, Count) -> Count; +refcount(?FATE_TRUE, Count) -> Count; +refcount(?FATE_FALSE, Count) -> Count; +refcount(?FATE_UNIT, Count) -> Count; +refcount(?FATE_BITS(_), Count) -> Count; +refcount(?FATE_BYTES(_), Count) -> Count; +refcount(?FATE_ADDRESS(_), Count) -> Count; +refcount(?FATE_CONTRACT(_), Count) -> Count; +refcount(?FATE_ORACLE(_), Count) -> Count; +refcount(?FATE_ORACLE_Q(_), Count) -> Count; +refcount(?FATE_CHANNEL(_), Count) -> Count; +refcount(?FATE_TYPEREP(_), Count) -> Count; refcount(Val, Count) when ?IS_FATE_INTEGER(Val) -> Count; refcount(Val, Count) when ?IS_FATE_STRING(Val) -> Count; refcount(?FATE_TUPLE(Val), Count) -> @@ -184,7 +190,8 @@ refcount_l(Vals, Count) -> refcount_m(Val, Count) -> %% No maps in map keys - maps:fold(fun(_, V, C) -> refcount(V, C) end, Count, Val). + maps:fold(fun(_, ?FATE_MAP_TOMBSTONE, C) -> C; + (_, V, C) -> refcount(V, C) end, Count, Val). %% -- Map id allocation ------------------------------------------------------ From f5a9be67d9fcfc88ddb2aec64dc42115e65985b7 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 13 Aug 2019 09:17:56 +0200 Subject: [PATCH 07/10] Remove CALL_TR and CALL_GTR --- src/aeb_fate_generate_ops.erl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/aeb_fate_generate_ops.erl b/src/aeb_fate_generate_ops.erl index a588f4c..1c34692 100644 --- a/src/aeb_fate_generate_ops.erl +++ b/src/aeb_fate_generate_ops.erl @@ -48,9 +48,9 @@ ops_defs() -> , { 'CALL', 16#02, true, true, true, 4, [a], call, {string}, any, "Call the function Arg0 with args on stack. The types of the arguments has to match the argument typs of the function."} , { 'CALL_R', 16#03, true, false, true, 8, [a,is,ii,a], call_r, {contract, string, integer, integer}, any, "Remote call to contract Arg0 and Arg2-ary function Arg1 with value Arg3. The types of the arguments has to match the argument typs of the function."} , { 'CALL_T', 16#04, true, true, true, 4, [a], call_t, {string}, any, "Tail call to function Arg0. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."} - , { 'CALL_TR', 16#05, true, false, true, 8, [a,is,a], call_tr, {contract, string, integer}, any, "DEPRECATED: Remote tail call to contract Arg0 and function Arg1 with value Arg2. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."} + , { 'UNUSED_1', 16#05, false, false, true, 8, [], unused_1, {}, none, "Was CALL_TR."} , { 'CALL_GR', 16#06, true, false, true, 8, [a,is,ii,a,a], call_gr, {contract, string, integer, integer, integer}, any, "Remote call with gas cap in Arg3. Otherwise as CALL_R."} - , { 'CALL_GTR', 16#07, true, false, true, 8, [a,is,a,a], call_gtr, {contract, string, integer, integer}, any, "DEPRECATED: Remote tail call with gas cap in Arg3. Otherwise as CALL_TR."} + , { 'UNUSED_2', 16#07, false, false, true, 8, [], unused_2, {}, none, "Was CALL_GTR."} , { 'JUMP', 16#08, true, true, true, 3, [ii], jump, {integer}, none, "Jump to a basic block. The basic block has to exist in the current function."} , { 'JUMPIF', 16#09, true, true, true, 4, [a,ii], jumpif, {boolean, integer}, none, "Conditional jump to a basic block. If Arg0 then jump to Arg1."} , { 'SWITCH_V2', 16#0a, true, true, true, 4, [a,ii,ii], switch, {variant, integer, ingeger}, none, "Conditional jump to a basic block on variant tag."} @@ -501,14 +501,16 @@ gen_format(#{opname := Name}) when (Name =:= 'CALL_R') -> "format_arg(a, Value)];\n", [Name, atom_to_list(Name), Name, atom_to_list(Name)]); gen_format(#{opname := Name}) when (Name =:= 'CALL_GR') -> - io_lib:format("format_op({~w, {immediate, Contract}, {immediate, Function}, {immediate, Arity}, Value, Gas}, Symbols) ->\n" + io_lib:format("format_op({~w, {immediate, Contract}, {immediate, Function}, Arity, Value, Gas}, Symbols) ->\n" " [\"~s \", lookup(Contract, Symbols), \".\", " "lookup(Function, Symbols), \" \", " + "format_arg(a, Arity), \" \", " "format_arg(a, Value), \" \", " "format_arg(a, Gas)];\n" - "format_op({~w, Contract, {immediate, Function}, {immediate, Arity}, Value, Gas}, Symbols) ->\n" + "format_op({~w, Contract, {immediate, Function}, Arity, Value, Gas}, Symbols) ->\n" "[\"~s \", format_arg(a, Contract), \".\", " "lookup(Function, Symbols), \" \", " + "format_arg(a, Arity), \" \", " "format_arg(a, Value), \" \", " "format_arg(a, Gas)];\n", [Name, atom_to_list(Name), Name, atom_to_list(Name)]); From 414c45fbf78dbcc3705268fee94093119e1bdd68 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 13 Aug 2019 11:25:54 +0200 Subject: [PATCH 08/10] Export refcount type --- src/aeb_fate_maps.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeb_fate_maps.erl b/src/aeb_fate_maps.erl index dcec997..1d7c189 100644 --- a/src/aeb_fate_maps.erl +++ b/src/aeb_fate_maps.erl @@ -20,7 +20,7 @@ , refcount_union/2 , no_used_ids/0 ]). --export_type([used_ids/0, maps/0]). +-export_type([used_ids/0, maps/0, refcount/0]). %% Size in bytes of serialization of a map for which we turn it into a store %% map. It's not worth turning small maps into store maps. From c49140fd5d367c11eaa800463fee3de81318cf2a Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 13 Aug 2019 11:33:21 +0200 Subject: [PATCH 09/10] Fix type and some code cleanup --- src/aeb_fate_maps.erl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/aeb_fate_maps.erl b/src/aeb_fate_maps.erl index 1d7c189..8aceaae 100644 --- a/src/aeb_fate_maps.erl +++ b/src/aeb_fate_maps.erl @@ -29,17 +29,16 @@ -type fate_value() :: aeb_fate_data:fate_type(). -type id() :: integer(). --type used_ids() :: list(id()). %% TODO: more clever representation +-type used_ids() :: list(id()). -type maps() :: #{ id() => aeb_fate_data:fate_map() | aeb_fate_data:fate_store_map() }. %% -- Allocating store maps -------------------------------------------------- --spec allocate_store_maps(used_ids(), [fate_value()]) -> - {[fate_value()], maps()}. +-spec allocate_store_maps(used_ids(), [fate_value()]) -> {[fate_value()], maps()}. allocate_store_maps(Used, Vals) -> - allocate_store_maps_l(Used, Vals, #{}). + {_Used, Vals1, Maps} = allocate_store_maps_l(Used, Vals, #{}), + {Vals1, Maps}. -allocate_store_maps(Used, ?FATE_MAP_TOMBSTONE = Val, Maps) -> {Used, Val, Maps}; allocate_store_maps(Used, ?FATE_TRUE = Val, Maps) -> {Used, Val, Maps}; allocate_store_maps(Used, ?FATE_FALSE = Val, Maps) -> {Used, Val, Maps}; allocate_store_maps(Used, ?FATE_UNIT = Val, Maps) -> {Used, Val, Maps}; @@ -85,9 +84,10 @@ allocate_store_maps_l(Used, [H | T], Maps) -> {Used2, [H1 | T1], Maps2}. allocate_store_maps_m(Used, Val, Maps) -> - KVs = [ ?FATE_TUPLE(KV) || KV <- maps:to_list(Val) ], - {Used1, KVs1, Maps1} = allocate_store_maps_l(Used, KVs, Maps), - {Used1, maps:from_list([ KV || ?FATE_TUPLE(KV) <- KVs1 ]), Maps1}. + maps:fold(fun(K, V, {Us, M, Ms}) -> + {Us1, V1, Ms1} = allocate_store_maps(Us, V, Ms), + {Us1, M#{ K => V1 }, Ms1} + end, {Used, #{}, Maps}, Val). %% -- Unfolding store maps --------------------------------------------------- From 087ec3169894a3acac547106f05744f07f7e91d8 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 13 Aug 2019 13:17:41 +0200 Subject: [PATCH 10/10] Fix bad type spec --- src/aeb_fate_maps.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeb_fate_maps.erl b/src/aeb_fate_maps.erl index 8aceaae..ed9e86a 100644 --- a/src/aeb_fate_maps.erl +++ b/src/aeb_fate_maps.erl @@ -159,7 +159,7 @@ has_store_maps(Val) -> -spec refcount(fate_value()) -> refcount(). refcount(Val) -> refcount(Val, #{}). --spec refcount(refcount(), fate_value()) -> refcount(). +-spec refcount(fate_value(), refcount()) -> refcount(). refcount(?FATE_MAP_TOMBSTONE, Count) -> Count; refcount(?FATE_TRUE, Count) -> Count; refcount(?FATE_FALSE, Count) -> Count;