Skip to content

Commit

Permalink
Merge pull request #2710 from aeternity/fate-map-fixes
Browse files Browse the repository at this point in the history
Fate map fixes
  • Loading branch information
UlfNorell committed Aug 27, 2019
2 parents 5a31ead + 7a80b55 commit b86d6e3
Show file tree
Hide file tree
Showing 5 changed files with 620 additions and 84 deletions.
35 changes: 26 additions & 9 deletions apps/aefate/src/aefa_stores.erl
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,15 @@ copy_map(MapId, ?FATE_STORE_MAP(Cache, OldId), {Meta, Store}) ->
%% First copy the old data, then update with the new
Store1 = write_bin_data(RawId, maps:to_list(OldMap) ++ NewData, Store),
Store2 = aect_contracts_store:put(map_data_key(RawId), <<0>>, Store1),
{Meta1, Store2}.

%% We also need to update the refcounts for nested maps. We already added
%% refcounts for maps in the Cache, now we have to add refcounts for
%% entries copied from the old map (unless they are overwritten by the
%% cache).
CopiedBin = maps:without([K || {K, _} <- NewData], OldMap),
RefCounts = aeb_fate_maps:refcount([ aeb_fate_encoding:deserialize(Val) || Val <- maps:values(CopiedBin) ]),
Meta2 = update_refcounts(RefCounts, Meta1),
{Meta2, Store2}.

%% In-place update of an existing store map. This happens, for instance, when
%% you update a map in the state throwing away the old copy of the it.
Expand All @@ -413,7 +421,16 @@ update_map(MapId, ?FATE_STORE_MAP(Cache, OldId), {Meta, Store}) ->
Store1 = write_bin_data(RawId, NewData, Store),
Meta1 = put_map_meta(MapId, ?METADATA(RawId, RefCount, Size),
remove_map_meta(OldId, Meta)),
{Meta1, Store1}.

%% We also need to update the refcounts for nested maps. We already added
%% refcounts for maps in the Cache, now we have to subtract refcounts for
%% entries overwritten by the cache.
RefCounts = lists:foldl(fun({Key, _}, Count) ->
aeb_fate_maps:refcount_union(refcount_delta(Key, false, Store), %% old store
Count)
end, aeb_fate_maps:refcount_zero(), NewData),
Meta2 = update_refcounts(RefCounts, Meta1),
{Meta2, Store1}.

gc_map(RawId, {Meta, Store}) ->
%% Only the RawId here, we already removed the MapId from the metadata.
Expand Down Expand Up @@ -492,14 +509,14 @@ maps_refcounts(Metadata, Maps, Store) ->
map_refcounts(_Meta, Map, _Store) when ?IS_FATE_MAP(Map) ->
%% Fresh map, only adds new references.
aeb_fate_maps:refcount(Map);
map_refcounts(Metadata, ?FATE_STORE_MAP(Cache, Id), Store) ->
map_refcounts(_Meta, ?FATE_STORE_MAP(Cache, _Id), _Store) ->
%% Note that this does not count as a reference to Id
maps:fold(fun(Key, Val, Count) ->
%% We need to compute the refcount delta from the updates to
%% the store map.
?METADATA(RawId, _, _) = get_map_meta(Id, Metadata),
New = refcount_delta(map_data_key(RawId, Key), Val, Store),
aeb_fate_maps:refcount_union(New, Count)
maps:fold(fun(_Key, Val, Count) ->
%% We don't know if this map will be copied or updated in place,
%% so we shouldn't compute a refcount delta. Instead we conservatively
%% only look at the new value. Once the copy or update happens we'll take
%% the delta into account.
aeb_fate_maps:refcount_union(aeb_fate_maps:refcount(Val), Count)
end, #{}, Cache).

%% Compute the difference in reference counts caused by performing a store
Expand Down
186 changes: 113 additions & 73 deletions apps/aevm/src/aevm_eeevm_store.erl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
-define(SOPHIA_STATE_TYPE_KEY, <<1>>).
-define(SOPHIA_STATE_MAPS_KEY, <<2>>).

%% -define(DEBUG, true).
-ifdef(DEBUG).
-define(DEBUG_PRINT(Fmt, Args), io:format(Fmt, Args)).
-else.
-define(DEBUG_PRINT(Fmt, Args), ok).
-endif.

%%====================================================================
%% API
%%====================================================================
Expand Down Expand Up @@ -94,7 +101,9 @@ store(Address, Value, State) when is_integer(Value) ->
%% The argument should be a binary encoding a pair of a typerep and a value of that type.
-spec from_sophia_state(aect_contracts:version(), aeb_heap:binary_value()) ->
{ok, aect_contracts:store()} | {error, term()}.
from_sophia_state(Version, Data) ->
from_sophia_state(Version = #{vm := VM}, Data) when VM =< ?VM_AEVM_SOPHIA_3 ->
aevm_eeevm_store_vm3:from_sophia_state(Version, Data);
from_sophia_state(_Version, Data) ->
%% TODO: less encoding/decoding
case aeb_heap:from_binary({tuple, [typerep]}, Data) of
{ok, {Type}} ->
Expand All @@ -107,11 +116,11 @@ from_sophia_state(Version, Data) ->
Ptr = aeb_heap:heap_value_pointer(StateValue),
StateData = <<Ptr:256, Mem/binary>>,
Maps = aeb_heap:heap_value_maps(StateValue),
Store = store_maps(Version, Maps,
Store = store_maps(Maps,
store_put(?SOPHIA_STATE_KEY, StateData,
store_put(?SOPHIA_STATE_TYPE_KEY, TypeData,
store_empty()))),
%% io:format("Initial state:\n~s\n", [show_store(Store)]),
?DEBUG_PRINT("Initial state:\n~s\n", [show_store(Store)]),
{ok, Store};
E = {error, _} ->
E
Expand All @@ -127,11 +136,13 @@ second_component(<<Ptr:256, Heap/binary>> = Data) ->
<<Snd:256, Heap/binary>>.

-spec set_sophia_state(aect_contracts:version(), aeb_heap:heap_value(), aect_contracts:store()) -> aect_contracts:store().
set_sophia_state(Version, Value, Store) ->
set_sophia_state(Version = #{vm := VM}, Value, Store) when VM =< ?VM_AEVM_SOPHIA_3 ->
aevm_eeevm_store_vm3:set_sophia_state(Version, Value, Store);
set_sophia_state(_Version, Value, Store) ->
Ptr = aeb_heap:heap_value_pointer(Value),
Mem = aeb_heap:heap_value_heap(Value),
Maps = aeb_heap:heap_value_maps(Value),
store_maps(Version, Maps, store_put(?SOPHIA_STATE_KEY, <<Ptr:256, Mem/binary>>, Store)).
store_maps(Maps, store_put(?SOPHIA_STATE_KEY, <<Ptr:256, Mem/binary>>, Store)).

-spec get_sophia_state(aect_contracts:store()) -> aeb_heap:heap_value().
get_sophia_state(Store) ->
Expand Down Expand Up @@ -169,53 +180,61 @@ next_map_id(#{?SOPHIA_STATE_MAPS_KEY := MapKeys}) ->
1 + lists:max([-1 | [ Id || <<Id:256>> <= MapKeys ]]);
next_map_id(_) -> 0.

%% show_store(Store0) ->
%% Store = aect_contracts_store:subtree(<<>>, Store0),
%% Show = fun(?SOPHIA_STATE_KEY) -> "?SOPHIA_STATE_KEY";
%% (?SOPHIA_STATE_TYPE_KEY) -> "?SOPHIA_STATE_TYPE_KEY";
%% (?SOPHIA_STATE_MAPS_KEY) -> "?SOPHIA_STATE_MAPS_KEY";
%% (<<Id:256>>) -> integer_to_list(Id);
%% (<<Id:256, Key/binary>>) -> io_lib:format("~p:~p", [Id, aevm_test_utils:dump_words(Key)])
%% end,
%% io_lib:format("~s\n", [[io_lib:format(" ~s =>\n ~p\n",
%% [Show(Key), aevm_test_utils:dump_words(Val)]) || {Key, Val} <- maps:to_list(Store)]]).
-ifdef(DEBUG).
show_store(Store0) ->
Store = aect_contracts_store:subtree(<<>>, Store0),
Show = fun(?SOPHIA_STATE_KEY) -> "?SOPHIA_STATE_KEY";
(?SOPHIA_STATE_TYPE_KEY) -> "?SOPHIA_STATE_TYPE_KEY";
(?SOPHIA_STATE_MAPS_KEY) -> "?SOPHIA_STATE_MAPS_KEY";
(<<Id:256>>) -> integer_to_list(Id);
(<<Id:256, Key/binary>>) -> io_lib:format("~p:~w", [Id, aevm_test_utils:dump_words(Key)])
end,
ShowAt = fun(Type, Bin) ->
case aeb_heap:from_binary(Type, Bin) of
{ok, Val} -> io_lib:format("~p", [Val]);
Other -> io_lib:format("(~w : ~p => ~p)", [aevm_test_utils:dump_words(Bin), Type, Other])
end
end,
ShowVal =
fun(?SOPHIA_STATE_TYPE_KEY, Val) -> ShowAt(typerep, Val);
(<<_:256>>, ?MapInfo(RealId, RefCount, Size, Bin)) ->
io_lib:format("?MapInfo(~p, ~p, ~p, ~s)",
[RealId, RefCount, Size, ShowAt({tuple, [typerep, typerep]}, Bin)]);
(_, Val) -> io_lib:format("~p", [aevm_test_utils:dump_words(Val)])
end,
io_lib:format("~s\n", [[io_lib:format(" ~s =>\n ~s\n",
[Show(Key), ShowVal(Key, Val)]) || {Key, Val} <- maps:to_list(Store)]]).
-endif.

%% -- Updating Sophia maps --

store_maps(Version, Maps0, Store) ->
store_maps(Maps0, Store) ->
Maps = maps:to_list(Maps0#maps.maps),

RefCounts = get_ref_counts(Store),
OldMapKeys = maps:keys(RefCounts),
NewMapKeys = [ Id || {Id, _} <- Maps ],

NewRefCounts = update_ref_counts(OldMapKeys, NewMapKeys, Maps, RefCounts, Store),
DeltaRefCounts = update_ref_counts(OldMapKeys, NewMapKeys, Maps, RefCounts, Store),

Garbage = [ G || G <- OldMapKeys -- NewMapKeys, 0 == maps:get(G, NewRefCounts, 0) ],
Garbage = [ G || G <- OldMapKeys -- NewMapKeys, 0 == maps:get(G, RefCounts, 0) + maps:get(G, DeltaRefCounts, 0) ],
AllMapKeys = lists:usort(NewMapKeys ++ OldMapKeys) -- Garbage,

Updates = compute_map_updates(Garbage, Maps),
?DEBUG_PRINT("Updates: ~p\n", [Updates]),

Store1 = store_put(?SOPHIA_STATE_MAPS_KEY, << <<Id:256>> || Id <- AllMapKeys >>, Store),
NewRefCounts1 = maps:filter(fun(Id, _) -> lists:member(Id, AllMapKeys) end, NewRefCounts),
PerformUpdate = fun(Upd, S) -> perform_update(Version, Upd, S) end,
NewStore = set_ref_counts(NewRefCounts1, lists:foldl(PerformUpdate, Store1, Updates)),
%% io:format("NewStore:\n~s\n", [show_store(NewStore)]),
DeltaRefCounts1 = maps:filter(fun(Id, _) -> lists:member(Id, AllMapKeys) end, DeltaRefCounts),
PerformUpdate = fun(Upd, S) -> perform_update(Upd, S) end,
NewStore = add_ref_counts(DeltaRefCounts1, lists:foldl(PerformUpdate, Store1, Updates)),
?DEBUG_PRINT("NewStore:\n~s\n", [show_store(NewStore)]),
NewStore.

perform_update(#{ vm := Version }, {new_inplace, NewId, OldId, Size}, Store) ->
perform_update({new_inplace, NewId, OldId, Size}, Store) ->
OldKey = <<OldId:256>>,
NewKey = <<NewId:256>>,
Entry =
case Version >= ?VM_AEVM_SOPHIA_2 of
true ->
%% Don't forget to update the size
?MapInfo(RId, RefCount, _, Bin) = store_get(OldKey, Store),
?MapInfo(RId, RefCount, Size, Bin);
false ->
%% Buggy before AEVM_SOPHIA_2.
store_get(OldKey, Store)
end,
?MapInfo(RId, RefCount, _, Bin) = store_get(OldKey, Store),
Entry = ?MapInfo(RId, RefCount, Size, Bin),
%% Subtle: Don't remove the RealId entry because the mp trees requires
%% there to be a value at any node that we want to get a subtree for. We
%% need this for store_subtree.
Expand All @@ -224,21 +243,32 @@ perform_update(#{ vm := Version }, {new_inplace, NewId, OldId, Size}, Store) ->
true -> Store
end,
store_put(NewKey, Entry, Store1);
perform_update(_Version, {insert, Id, Key, Val}, Store) ->
RealId = real_id(Id, Store),
store_put(<<RealId:256, Key/binary>>, Val, Store);
perform_update(_Version, {delete, Id, Key}, Store) ->
RealId = real_id(Id, Store),
store_remove(<<RealId:256, Key/binary>>, Store);
perform_update(_Version, {new, Id, Map0}, Store) ->
Map = aevm_eeevm_maps:flatten_map(Store, Id, Map0),
perform_update({insert, Id, ValType, Key, Val}, Store) ->
RealId = real_id(Id, Store),
RealKey = <<RealId:256, Key/binary>>,
Store1 = store_put(RealKey, Val, Store),
%% We also need to subtract reference counts for the value that got
%% overwritten (broken pre-lima).
subtract_removed_ref_counts(store_get(RealKey, Store), ValType, Store1);
perform_update({delete, Id, ValType, Key}, Store) ->
RealId = real_id(Id, Store),
RealKey = <<RealId:256, Key/binary>>,
Store1 = store_remove(RealKey, Store),
%% We also need to subtract reference counts for the value that got
%% deleted (broken pre-lima).
subtract_removed_ref_counts(store_get(RealKey, Store), ValType, Store1);
perform_update({new, Id, Map0}, Store0) ->
%% Here we need to add reference counts for the copied entries (broken
%% pre-lima).
Map = aevm_eeevm_maps:flatten_map(Store0, Id, Map0),
Store = add_copied_ref_counts(Map0, Map, Store0),
RefCount = 0, %% Set later
Size = Map0#pmap.size,
Bin = aeb_heap:to_binary({Map#pmap.key_t, Map#pmap.val_t}),
Info = [{<<Id:256>>, ?MapInfo(Id, RefCount, Size, Bin)}],
Data = [ {<<Id:256, Key/binary>>, Val} || {Key, Val} <- maps:to_list(Map#pmap.data) ],
lists:foldl(fun({K, V}, S) -> store_put(K, V, S) end, Store, Info ++ Data);
perform_update(_Version, {gc, Id}, Store) ->
perform_update({gc, Id}, Store) ->
RealId = real_id(Id, Store),
%% Remove map info entry. Also remove RealId which we kept around for mp
%% tree reasons (see note at `new_inplace` case above), and all the data.
Expand All @@ -247,19 +277,36 @@ perform_update(_Version, {gc, Id}, Store) ->
lists:foldl(fun store_remove/2, Store, ToRemove).

update_ref_counts(OldMapKeys, NewMapKeys, Maps, RefCounts, Store) ->
RefCounts1 = update_ref_counts1(Maps, RefCounts, Store),
DeltaCounts = update_ref_counts1(Maps, #{}, Store),
PotentialGarbage = OldMapKeys -- NewMapKeys,
ref_count_garbage(PotentialGarbage, [], Maps, RefCounts1, Store).
ref_count_garbage(PotentialGarbage, [], Maps, RefCounts, DeltaCounts, Store).

subtract_removed_ref_counts(<<>>, _, Store) -> Store;
subtract_removed_ref_counts(Val, ValType, Store) ->
Used = aevm_data:used_maps(ValType, Val),
lists:foldl(fun(Id, S) -> add_ref_count(Id, -1, S) end, Store, Used).

add_copied_ref_counts(#pmap{data = Updates}, #pmap{val_t = ValType, data = Data}, Store) ->
%% Increase ref counts for all entries not overwritten by Updates
Overwritten =
case Updates of
stored -> [];
_ -> maps:keys(Updates)
end,
Used = [ Id || Val <- maps:values(maps:without(Overwritten, Data)),
Id <- aevm_data:used_maps(ValType, Val) ],
lists:foldl(fun(Id, S) -> add_ref_count(Id, 1, S) end,
Store, Used).

ref_count_garbage(PotentialGarbage, ActualGarbage, Maps, RefCounts, Store) ->
Garbage = [ G || G <- PotentialGarbage, 0 == maps:get(G, RefCounts, 0) ],
ref_count_garbage(PotentialGarbage, ActualGarbage, Maps, RefCounts, DeltaCounts, Store) ->
Garbage = [ G || G <- PotentialGarbage, 0 == maps:get(G, RefCounts, 0) + maps:get(G, DeltaCounts, 0) ],
{ActualGarbage1, _} = do_inplace_assignment(Garbage, Maps),
case ActualGarbage1 -- ActualGarbage of
[] -> RefCounts; %% No new garbage
[] -> DeltaCounts; %% No new garbage
NewGarbage ->
RefCounts1 = lists:foldl(fun(Id, RfC) -> gc_ref_count(Id, RfC, Store) end,
RefCounts, NewGarbage),
ref_count_garbage(PotentialGarbage, ActualGarbage1, Maps, RefCounts1, Store)
DeltaCounts1 = lists:foldl(fun(Id, RfC) -> gc_ref_count(Id, RfC, Store) end,
DeltaCounts, NewGarbage),
ref_count_garbage(PotentialGarbage, ActualGarbage1, Maps, RefCounts, DeltaCounts1, Store)
end.

gc_ref_count(Id, RefCounts, Store) ->
Expand All @@ -271,7 +318,7 @@ gc_ref_count(Id, RefCounts, Store) ->
lists:append([ aevm_data:used_maps(ValType, Val)
|| {_Key, Val} <- store_to_list(Id, Store) ]),
lists:foldl(fun(Used, RfC) ->
maps:update_with(Used, fun(N) -> N - 1 end, RfC)
maps:update_with(Used, fun(N) -> N - 1 end, -1, RfC)
end, RefCounts, UsedMaps)
end.

Expand All @@ -285,23 +332,16 @@ update_ref_counts1([{_Id, Map} | Maps], RefCounts, Store) ->
Data ->
ValType = Map#pmap.val_t,
DeltaCount =
fun({Key, Val}, Counts) ->
fun({_Key, Val}, Counts) ->
New =
case Val of
tombstone -> [];
_ -> aevm_data:used_maps(ValType, Val)
end,
Old =
case Map#pmap.parent of
none -> [];
PId ->
case get_value(PId, Key, Store) of
false -> [];
OldVal -> aevm_data:used_maps(ValType, OldVal)
end
end,
%% Subtract old from new
Updates = [ {I, 1} || I <- New ] ++ [ {I, -1} || I <- Old ],
%% We don't know if this map will be copied or
%% updated in-place so don't subtract the old
%% value yet (broken pre-lima).
Updates = [ {I, 1} || I <- New ],
lists:foldl(fun({I, Count}, RfC) ->
maps:update_with(I, fun(N) -> N + Count end, Count, RfC)
end, Counts, Updates)
Expand Down Expand Up @@ -343,10 +383,10 @@ compute_map_updates(Garbage, Maps0) ->
[ [{new, Id, Map} || {Id, Map} <- Copy]
, [ [{new_inplace, Id, Parent, Size},
[ case Val of
tombstone -> {delete, Id, Key};
_ -> {insert, Id, Key, Val}
tombstone -> {delete, Id, ValType, Key};
_ -> {insert, Id, ValType, Key, Val}
end || {Key, Val} <- maps:to_list(Data) ]]
|| {Id, #pmap{ parent = Parent, data = Data, size = Size }} <- Inplace ]
|| {Id, #pmap{ parent = Parent, val_t = ValType, data = Data, size = Size }} <- Inplace ]
, [{gc, Id} || Id <- ActualGarbage ]
]).

Expand All @@ -372,14 +412,14 @@ ref_count(Id, Store) ->
?MapInfo(_, RefCount, _, _) = store_get(<<Id:256>>, Store),
RefCount.

set_ref_count(Id, RefCount, Store) ->
?MapInfo(RealId, _, Size, Bin) = store_get(<<Id:256>>, Store),
store_put(<<Id:256>>, ?MapInfo(RealId, RefCount, Size, Bin), Store).
add_ref_count(Id, Delta, Store) ->
?MapInfo(RealId, RefCount, Size, Bin) = store_get(<<Id:256>>, Store),
store_put(<<Id:256>>, ?MapInfo(RealId, RefCount + Delta, Size, Bin), Store).

set_ref_counts(RefCounts, Store) ->
lists:foldl(fun({Id, RefCount}, St) ->
set_ref_count(Id, RefCount, St)
end, Store, maps:to_list(RefCounts)).
add_ref_counts(RefCounts, Store) ->
maps:fold(fun(Id, Delta, St) ->
add_ref_count(Id, Delta, St)
end, Store, RefCounts).

get_ref_counts(Store) ->
maps:from_list([ {Id, ref_count(Id, Store)} || Id <- all_map_ids(Store) ]).
Expand Down

0 comments on commit b86d6e3

Please sign in to comment.