diff --git a/rebar.config b/rebar.config index 77b30e8..1d7b511 100644 --- a/rebar.config +++ b/rebar.config @@ -2,7 +2,7 @@ {erl_opts, [debug_info]}. -{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"59af12b"}}} +{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"bf05e14"}}} , {getopt, "1.0.1"} , {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}} diff --git a/rebar.lock b/rebar.lock index c27ddea..4c2c833 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,7 +1,7 @@ {"1.1.0", [{<<"aebytecode">>, {git,"https://github.com/aeternity/aebytecode.git", - {ref,"59af12bf349edafcd470cd3ccefff09cacd2d010"}}, + {ref,"bf05e14661ae25905bd020bfc2dcbc074f8ad66b"}}, 0}, {<<"aeserialization">>, {git,"https://github.com/aeternity/aeserialization.git", diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index cd8d829..3d9a760 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -486,6 +486,12 @@ global_env() -> {"none", Bits}, {"all", Bits}]) }, + %% Bytes + BytesScope = #scope + { funs = MkDefs( + [{"to_int", Fun1(Bytes(any), Int)}, + {"to_str", Fun1(Bytes(any), String)}]) }, + %% Conversion IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) }, AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}, @@ -504,6 +510,7 @@ global_env() -> , ["Crypto"] => CryptoScope , ["String"] => StringScope , ["Bits"] => BitsScope + , ["Bytes"] => BytesScope , ["Int"] => IntScope , ["Address"] => AddressScope } }. @@ -916,8 +923,8 @@ lookup_name(Env, As, Id, Options) -> Freshen = proplists:get_value(freshen, Options, false), check_stateful(Env, Id, Ty), Ty1 = case Ty of - {type_sig, _, _, _, _} -> freshen_type(typesig_to_fun_t(Ty)); - _ when Freshen -> freshen_type(Ty); + {type_sig, _, _, _, _} -> freshen_type(As, typesig_to_fun_t(Ty)); + _ when Freshen -> freshen_type(As, Ty); _ -> Ty end, {set_qname(QId, Id), Ty1} @@ -1218,7 +1225,7 @@ infer_block(Env, _, [E], BlockType) -> [check_expr(Env, E, BlockType)]; infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) -> {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), - FunT = freshen_type(typesig_to_fun_t(TypeSig)), + FunT = freshen_type(Ann, typesig_to_fun_t(TypeSig)), NewE = bind_var({id, Ann, Name}, FunT, Env), [LetFun|infer_block(NewE, Attrs, Rest, BlockType)]; infer_block(Env, _, [{letval, Attrs, Pattern, Type, E}|Rest], BlockType) -> @@ -1358,14 +1365,17 @@ when_option(Opt, Do) -> create_constraints() -> create_named_argument_constraints(), + create_bytes_constraints(), create_field_constraints(). solve_constraints(Env) -> solve_named_argument_constraints(Env), + solve_bytes_constraints(Env), solve_field_constraints(Env). destroy_and_report_unsolved_constraints(Env) -> destroy_and_report_unsolved_field_constraints(Env), + destroy_and_report_unsolved_bytes_constraints(Env), destroy_and_report_unsolved_named_argument_constraints(Env). %% -- Named argument constraints -- @@ -1414,6 +1424,37 @@ destroy_and_report_unsolved_named_argument_constraints(Env) -> destroy_named_argument_constraints(), ok. +%% -- Bytes constraints -- + +-type byte_constraint() :: {is_bytes, utype()}. + +create_bytes_constraints() -> + ets_new(bytes_constraints, [bag]). + +get_bytes_constraints() -> + ets_tab2list(bytes_constraints). + +-spec add_bytes_constraint(byte_constraint()) -> true. +add_bytes_constraint(Constraint) -> + ets_insert(bytes_constraints, Constraint). + +solve_bytes_constraints(_Env) -> + ok. + +destroy_bytes_constraints() -> + ets_delete(bytes_constraints). + +destroy_and_report_unsolved_bytes_constraints(Env) -> + [ check_bytes_constraint(Env, C) || C <- get_bytes_constraints() ], + destroy_bytes_constraints(). + +check_bytes_constraint(Env, {is_bytes, Type}) -> + Type1 = unfold_types_in_type(Env, instantiate(Type)), + case Type1 of + {bytes_t, _, _} -> ok; + _ -> type_error({cannot_unify, Type1, {bytes_t, [], any}, {at, Type}}) + end. + %% -- Field constraints -- create_field_constraints() -> @@ -1840,26 +1881,31 @@ create_freshen_tvars() -> destroy_freshen_tvars() -> ets_delete(freshen_tvars). -freshen_type(Type) -> +freshen_type(Ann, Type) -> create_freshen_tvars(), - Type1 = freshen(Type), + Type1 = freshen(Ann, Type), destroy_freshen_tvars(), Type1. -freshen({tvar, As, Name}) -> +freshen(Type) -> + freshen(aeso_syntax:get_ann(Type), Type). + +freshen(Ann, {tvar, _, Name}) -> NewT = case ets_lookup(freshen_tvars, Name) of - [] -> - fresh_uvar(As); - [{Name, T}] -> - T + [] -> fresh_uvar(Ann); + [{Name, T}] -> T end, ets_insert(freshen_tvars, {Name, NewT}), NewT; -freshen(T) when is_tuple(T) -> - list_to_tuple(freshen(tuple_to_list(T))); -freshen([A|B]) -> - [freshen(A)|freshen(B)]; -freshen(X) -> +freshen(Ann, {bytes_t, _, any}) -> + X = fresh_uvar(Ann), + add_bytes_constraint({is_bytes, X}), + X; +freshen(Ann, T) when is_tuple(T) -> + list_to_tuple(freshen(Ann, tuple_to_list(T))); +freshen(Ann, [A | B]) -> + [freshen(Ann, A) | freshen(Ann, B)]; +freshen(_, X) -> X. %% Dereferences all uvars and replaces the uninstantiated ones with a @@ -2044,6 +2090,7 @@ pp_error(Err) -> io_lib:format("Unknown error: ~p\n", [Err]). pp_when({todo, What}) -> io_lib:format("[TODO] ~p\n", [What]); +pp_when({at, Ann}) -> io_lib:format("at ~s\n", [pp_loc(Ann)]); pp_when({check_typesig, Name, Inferred, Given}) -> io_lib:format("when checking the definition of ~s\n" " inferred type: ~s\n" @@ -2196,6 +2243,7 @@ pp({tvar, _, Name}) -> Name; pp({tuple_t, _, Cpts}) -> ["(", pp(Cpts), ")"]; +pp({bytes_t, _, any}) -> "bytes(_)"; pp({bytes_t, _, Len}) -> ["bytes(", integer_to_list(Len), ")"]; pp({app_t, _, T, []}) -> diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 7c760a4..3eedc24 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -190,6 +190,7 @@ builtins() -> {["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]}, {["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, + {["Bytes"], [{"to_int", 1}, {"to_str", 1}]}, {["Int"], [{"to_str", 1}]}, {["Address"], [{"to_str", 1}]} ], @@ -1323,6 +1324,7 @@ pp_call(Fun, Args) -> pp_ftype(T) when is_atom(T) -> pp_text(T); pp_ftype(any) -> pp_text("_"); pp_ftype({tvar, X}) -> pp_text(X); +pp_ftype({bytes, N}) -> pp_text("bytes(" ++ integer_to_list(N) ++ ")"); pp_ftype({tuple, Ts}) -> pp_parens(pp_par(pp_punctuate(pp_text(","), [pp_ftype(T) || T <- Ts]))); pp_ftype({list, T}) -> diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 24f9c05..5542354 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -421,6 +421,13 @@ ast_body(?qid_app(["Address", "is_contract"], [Addr], _, _), Icode) -> prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0}, [ast_body(Addr, Icode)], [word], word); +ast_body(?qid_app(["Bytes", "to_int"], [Bytes], _, _), Icode) -> + {typed, _, _, {bytes_t, _, N}} = Bytes, + builtin_call({bytes_to_int, N}, [ast_body(Bytes, Icode)]); +ast_body(?qid_app(["Bytes", "to_str"], [Bytes], _, _), Icode) -> + {typed, _, _, {bytes_t, _, N}} = Bytes, + builtin_call({bytes_to_str, N}, [ast_body(Bytes, Icode)]); + %% Other terms ast_body({id, _, Name}, _Icode) -> #var_ref{name = Name}; diff --git a/src/aeso_builtins.erl b/src/aeso_builtins.erl index bd4010c..4736e2f 100644 --- a/src/aeso_builtins.erl +++ b/src/aeso_builtins.erl @@ -44,6 +44,7 @@ builtin_deps1(addr_to_str) -> [{baseX_int, 58}]; builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}]; builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}]; builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}]; +builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker]; builtin_deps1(string_reverse) -> [string_reverse_]; builtin_deps1(require) -> [abort]; builtin_deps1(_) -> []. @@ -66,7 +67,7 @@ option_some(X) -> {tuple, [{integer, 1}, X]}. -define(V(X), v(X)). -define(A(Op), aeb_opcodes:mnemonic(Op)). -define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}). --define(DEREF(Var, Ptr, Body), {switch, v(Ptr), [{{tuple, [v(Var)]}, Body}]}). +-define(DEREF(Var, Ptr, Body), {switch, operand(Ptr), [{{tuple, [v(Var)]}, Body}]}). -define(NXT(Ptr), op('+', Ptr, 32)). -define(NEG(A), op('/', A, {unop, '-', {integer, 1}})). -define(BYTE(Ix, Word), op('byte', Ix, Word)). @@ -160,6 +161,9 @@ builtin_function(BF) -> {baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X)); {baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X)); {baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X)); + {bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N)); + {bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N)); + bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker()); string_reverse -> bfun(BF, builtin_string_reverse()); string_reverse_ -> bfun(BF, builtin_string_reverse_()) end. @@ -444,6 +448,10 @@ builtin_baseX_int_pad(X = 10) -> ?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]), ?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])}, word}; +builtin_baseX_int_pad(X = 16) -> + {[{"src", word}, {"ix", word}, {"dst", word}], + ?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]), + word}; builtin_baseX_int_pad(X = 58) -> {[{"src", word}, {"ix", word}, {"dst", word}], {ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0), @@ -478,6 +486,57 @@ builtin_baseX_digits(X) -> {ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}), word}. +builtin_bytes_to_int(32) -> + {[{"w", word}], ?V(w), word}; +builtin_bytes_to_int(N) when N < 32 -> + {[{"w", word}], ?BSR(w, 32 - N), word}; +builtin_bytes_to_int(N) when N > 32 -> + LastFullWord = N div 32 - 1, + Body = case N rem 32 of + 0 -> ?DEREF(n, ?ADD(b, LastFullWord * 32), ?V(n)); + R -> + ?DEREF(hi, ?ADD(b, LastFullWord * 32), + ?DEREF(lo, ?ADD(b, (LastFullWord + 1) * 32), + ?ADD(?BSR(lo, 32 - R), ?BSL(hi, R)))) + end, + {[{"b", pointer}], Body, word}. + +builtin_bytes_to_str_worker() -> + <> = <<"0123456789ABCDEF________________">>, + {[{"w", word}, {"offs", word}, {"acc", word}], + {seq, [{ifte, ?AND(?GT(offs, 0), ?EQ(0, ?MOD(offs, 16))), + {seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}]}, + {inline_asm, []}}, + {ifte, ?EQ(offs, 32), {inline_asm, [?A(?MSIZE)]}, + ?LET(b, ?BYTE(offs, w), + ?LET(lo, ?BYTE(?MOD(b, 16), Tab), + ?LET(hi, ?BYTE(op('bsr', 4 , b), Tab), + ?call(bytes_to_str_worker, + [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))])))) + } + ]}, + word}. + +builtin_bytes_to_str(N) when N =< 32 -> + {[{"w", word}], + ?LET(ret, {inline_asm, [?A(?MSIZE)]}, + {seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, + ?call(bytes_to_str_worker, [?V(w), ?I(0), ?I(0)]), + {inline_asm, [?A(?POP)]}, + ?V(ret)]}), + string}; +builtin_bytes_to_str(N) when N > 32 -> + Work = fun(I) -> + [?DEREF(w, ?ADD(p, 32 * I), ?call(bytes_to_str_worker, [?V(w), ?I(0), ?I(0)])), + {inline_asm, [?A(?POP)]}] + end, + {[{"p", pointer}], + ?LET(ret, {inline_asm, [?A(?MSIZE)]}, + {seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++ + lists:append([ Work(I) || I <- lists:seq(0, (N + 31) div 32 - 1) ]) ++ + [?V(ret)]}), + string}. + builtin_string_reverse() -> {[{"s", string}], ?DEREF(n, s, diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 679481c..c79459e 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -102,6 +102,8 @@ Op =:= 'ECVERIFY_SECP256K1' orelse Op =:= 'CONTRACT_TO_ADDRESS' orelse Op =:= 'AUTH_TX_HASH' orelse + Op =:= 'BYTES_TO_INT' orelse + Op =:= 'BYTES_TO_STR' orelse false)). -record(env, { contract, vars = [], locals = [], tailpos = true }). @@ -470,6 +472,10 @@ builtin_to_scode(_Env, bits_none, []) -> [aeb_fate_ops:bits_none(?a)]; builtin_to_scode(_Env, bits_all, []) -> [aeb_fate_ops:bits_all(?a)]; +builtin_to_scode(Env, bytes_to_int, [_] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytes_to_int(?a, ?a), Args); +builtin_to_scode(Env, bytes_to_str, [_] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytes_to_str(?a, ?a), Args); builtin_to_scode(Env, abort, [_] = Args) -> call_to_scode(Env, aeb_fate_ops:abort(?a), Args); builtin_to_scode(Env, chain_spend, [_, _] = Args) -> @@ -802,6 +808,8 @@ attributes(I) -> {'ECVERIFY_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]); {'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]); {'AUTH_TX_HASH', A} -> Pure(A, []); + {'BYTES_TO_INT', A, B} -> Pure(A, [B]); + {'BYTES_TO_STR', A, B} -> Pure(A, [B]); {'ADDRESS', A} -> Pure(A, []); {'BALANCE', A} -> Impure(A, []); {'BALANCE_OTHER', A, B} -> Impure(A, [B]); diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index fdcd06d..8762cf7 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -233,6 +233,7 @@ type({app_t, _, Type, Args}) -> beside(type(Type), tuple_type(Args)); type({tuple_t, _, Args}) -> tuple_type(Args); +type({bytes_t, _, any}) -> text("bytes(_)"); type({bytes_t, _, Len}) -> text(lists:concat(["bytes(", Len, ")"])); type({named_arg_t, _, Name, Type, _Default}) -> diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index 7f45aa8..5c9e81c 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -59,7 +59,7 @@ -type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()} | {app_t, ann(), type(), [type()]} | {tuple_t, ann(), [type()]} - | {bytes_t, ann(), integer()} + | {bytes_t, ann(), integer() | any} | id() | qid() | con() | qcon() %% contracts | tvar(). diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 8d95522..f2095df 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -115,7 +115,8 @@ compilable_contracts() -> "address_literals", "bytes_equality", "address_chain", - "namespace_bug" + "namespace_bug", + "bytes_to_x" ]. not_yet_compilable(fate) -> diff --git a/test/contracts/bytes_to_x.aes b/test/contracts/bytes_to_x.aes new file mode 100644 index 0000000..f6bfea2 --- /dev/null +++ b/test/contracts/bytes_to_x.aes @@ -0,0 +1,8 @@ + +contract BytesToX = + + function to_int(b : bytes(42)) : int = Bytes.to_int(b) + function to_str(b : bytes(12)) : string = + String.concat(Bytes.to_str(b), Bytes.to_str(#ffff)) + function to_str_big(b : bytes(65)) : string = + Bytes.to_str(b)