1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

[FunC] Auto-inline functions-wrappers T f(...args) { return anotherF(...args); }

This will allow to easily implement camelCase wrappers aside stdlib,
even without changing hashes of existing contracts.
Also, stdlib renamings could be easily performed in the same manner,
even with arguments reordered.
This commit is contained in:
Aleksandr Kirsanov 2024-04-26 14:27:16 +03:00
parent bac4e3df97
commit 18050a7591
No known key found for this signature in database
GPG key ID: B758BBAA01FFB3D3
12 changed files with 929 additions and 60 deletions

View file

@ -31,7 +31,8 @@
["whales-nominators/nominators.fc", 8941364499854379927692172316865293429893094891593442801401542636695127885153]
["tact-examples/treasure_Treasure.code.fc", 13962538639825790677138656603323869918938565499584297120566680287245364723897]
["tact-examples/jetton_SampleJetton.code.fc", 94076762218493729104783735200107713211245710256802265203823917715299139499110]
["tact-examples/jetton_JettonDefaultWallet.code.fc", 29421313492520031238091587108198906058157443241743283101866538036369069620563]
// (April 2024) tact hashes changed, because '__tact_address_eq()' is now inlined as a wrapper
["tact-examples/treasure_Treasure.code.fc", 74579212939836529446778705921340099196942507859825095056546203678047252921894]
["tact-examples/jetton_SampleJetton.code.fc", 90109697379313597998231209537203822165325184678797680193687781490465875320451]
["tact-examples/jetton_JettonDefaultWallet.code.fc", 40972091374757565863193840427121230466303309310521439431197914284004190565629]
["tact-examples/maps_MapTestContract.code.fc", 22556550222249123835909180266811414538971143565993192846012583552876721649744]

View file

@ -0,0 +1,239 @@
;; Here we test "functions that just wrap other functions" (camelCase in particular):
;; > builder beginCell() { return begin_cell(); }
;; Such functions, when called, are explicitly inlined during code generation (even without `inline` modifier).
;; It means, that `beginCell()` is replaced to `begin_cell()` (and effectively to `NEWC`).
;; Moreover, body of `beginCell` is NOT codegenerated at all.
;; Hence, we can write camelCase wrappers (as well as more intelligible namings around stdlib functions)
;; without affecting performance and even bytecode hashes.
;; This works with ~functions also. And even works with wrappers of wrappers.
;; Moreover, such wrappers can reorder input parameters, see a separate test camel2.fc.
builder begin_cell() asm "NEWC";
cell end_cell(builder b) asm "ENDC";
builder store_ref(builder b, cell c) asm(c b) "STREF";
slice begin_parse(cell c) asm "CTOS";
slice skip_bits(slice s, int len) asm "SDSKIPFIRST";
(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST";
builder beginCell() { return begin_cell(); }
cell endCell(builder b) { return end_cell(b); }
builder storeRef(builder b, cell c) { return store_ref(b, c); }
builder storeUint(builder b, int i, int bw) { return store_uint(b, i, bw); }
;; 'inline' is not needed actually, but if it exists, it's just ignored
slice beginParse(cell c) inline { return begin_parse(c); }
slice skipBits(slice s, int len) inline { return skip_bits(s, len); }
(slice, ()) ~skipBits(slice s, int len) inline { return ~skip_bits(s, len); }
(slice, int) ~loadUint(slice s, int len) inline { return load_uint(s, len); }
(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE";
(int, int, int) computeDataSize(cell c, int maxCells) impure { return compute_data_size(c, maxCells); }
cell new_dict() asm "NEWDICT";
cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET";
(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET";
(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT";
(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2";
cell dict::new() { return new_dict(); }
cell dict::iset(cell dict, int keyLen, int index, slice value) { return idict_set(dict, keyLen, index, value); }
(cell, ()) ~dict::iset(cell dict, int keyLen, int index, slice value) { return ~idict_set(dict, keyLen, index, value); }
(slice, int) dict::tryIGet(cell dict, int keyLen, int index) { return idict_get?(dict, keyLen, index); }
(int, slice, int) dict::tryIGetMin(cell dict, int keyLen) { return idict_get_min?(dict, keyLen); }
tuple empty_tuple() asm "NIL";
forall X -> tuple tpush(tuple t, X value) asm "TPUSH";
forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH";
forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND";
forall X -> X null() asm "PUSHNULL";
tuple emptyTuple() { return empty_tuple(); }
tuple emptyTuple1() { return emptyTuple(); }
tuple emptyTuple11() { return emptyTuple1(); }
forall X -> tuple tuplePush(tuple t, X value) { return tpush(t, value); }
forall X -> (tuple, ()) ~tuplePush(tuple t, X value) { return ~tpush(t, value); }
forall X -> X tupleAt(tuple t, int index) { return at(t, index); }
forall X1, Y2, Z3 -> Y2 tripleSecond([X1, Y2, Z3] p) { return triple_second(p); }
forall X -> X nullValue() asm "PUSHNULL";
() throwIf(int excNo, int condition) impure { return throw_if(excNo, condition); }
tuple initial1(tuple x) { return x; }
_ initial2(x) { return initial1(x); }
int add(int x, int y) { return x + y; } ;; this is also a wrapper, as its body is _+_(x,y)
() fake1(int a, int b, int c) impure asm(a b c) "DROP DROP DROP";
() fake2(int a, int b, int c) impure asm(b c a) "DROP DROP DROP";
() fake3(int a, int b, int c) impure asm(c a b) "DROP DROP DROP";
() fake4(int a, int b, int c) impure asm(c b a) "DROP DROP DROP";
() fake1Wrapper(int a, int b, int c) { return fake1(a, b, c); }
() fake2Wrapper(int a, int b, int c) { return fake2(a, b, c); }
() fake3Wrapper(int a, int b, int c) { return fake3(a, b, c); }
() fake4Wrapper(int a, int b, int c) { return fake4(a, b, c); }
[int, int, int] test1() method_id(101) {
int x = 1;
int y = 1;
cell to_be_ref = beginCell().endCell();
builder in_c = beginCell().storeUint(123, 8);
in_c = storeRef(in_c, to_be_ref);
var (a, b, c) = computeDataSize(in_c.endCell(), 10);
throwIf(101, b != 8);
throwIf(101, c != 1);
return [a, add(b, x), add(c, y)];
}
[[int, int, int], int, int, int] test2() method_id(102) {
cell dict = dict::new();
dict = dict::iset(dict, 32, 456, beginCell().storeUint(4560, 32).endCell().beginParse());
dict.dict::iset(32, 789, beginCell().storeUint(7890, 32).endCell().beginParse());
dict~dict::iset(32, 123, beginCell().storeUint(0, 64).storeUint(1230, 32).storeUint(1231, 32).storeUint(1232, 32).endCell().beginParse());
var (mink, minv, _) = dict::tryIGetMin(dict, 32);
;; skip 64 bits
minv~skipBits(16);
minv = minv.skipBits(16);
minv.skipBits(16000); ;; does nothing
(minv, _) = ~skipBits(minv, 16);
skipBits(minv, 16000); ;; does nothing
minv~skipBits(16);
;; load 3*32
var minv1 = minv~loadUint(32);
var minv2 = minv~loadUint(32);
var minv3 = minv~loadUint(32);
var (_, found123) = dict::tryIGet(dict, 32, 123);
var (_, found456) = dict::tryIGet(dict, 32, 456);
var (_, found789) = dict::tryIGet(dict, 32, 789);
return [[minv1, minv2, minv3], found123, found456, found789];
}
tuple test3() method_id(103) {
tuple with34 = initial2(emptyTuple1());
with34~tuplePush(34);
tuple t = emptyTuple11();
t = tuplePush(t, 12);
tuplePush(t, emptyTuple11()); ;; does nothing
t~tuplePush(emptyTuple1());
t~tuplePush(with34.tupleAt(0));
t.tuplePush("123"s); ;; does nothing
[cell, int, cell] tri = [nullValue(), 90 + 1, null()];
int f = tripleSecond(tri);
(t, _) = ~tuplePush(t, f);
return t;
}
(int) test4(int a, int b, int c) method_id(104) {
fake1Wrapper(a, b, c);
fake2Wrapper(a, b, c);
fake3Wrapper(a, b, c);
fake4Wrapper(a, b, c);
return 10;
}
int main() {
return 0;
}
{-
method_id | in | out
TESTCASE | 101 | | [ 2 9 2 ]
TESTCASE | 102 | | [ [ 1230 1231 1232 ] -1 -1 0 ]
TESTCASE | 103 | | [ 12 [] 34 91 ]
@fif_codegen
"""
test1 PROC:<{
//
NEWC // _5
ENDC // to_be_ref
123 PUSHINT // to_be_ref _8=123
NEWC // to_be_ref _8=123 _9
8 STU // to_be_ref in_c
STREF // in_c
ENDC // _16
10 PUSHINT // _16 _17=10
CDATASIZE // a b c
OVER // a b c b
8 NEQINT // a b c _21
101 THROWIF
DUP // a b c c
1 NEQINT // a b c _25
101 THROWIF
SWAP // a c b
INC // a c _28
SWAP // a _28 c
INC // a _28 _29
TRIPLE // _27
}>
"""
@fif_codegen
"""
test2 PROC:<{
...
16 PUSHINT // dict minv _45=16
SDSKIPFIRST // dict minv
16 PUSHINT // dict minv _47=16
SDSKIPFIRST // dict minv
16 PUSHINT // dict minv _52=16
SDSKIPFIRST // dict minv
16 PUSHINT // dict minv _57=16
SDSKIPFIRST // dict minv
...
32 PUSHINT // dict minv1 minv2 minv3 found123 _78=456 dict _79=32
DICTIGET
NULLSWAPIFNOT // dict minv1 minv2 minv3 found123 _99 _100
789 PUSHINT
s2 POP
s0 s6 XCHG
32 PUSHINT // found456 minv1 minv2 minv3 found123 _83=789 dict _84=32
...
4 TUPLE // _86
}>
"""
@fif_codegen
"""
test3 PROC:<{
//
NIL // _1
initial1 CALLDICT // with34
...
TRIPLE // t tri
SECOND // t f
TPUSH // t
}>
"""
@fif_codegen
"""
test4 PROC:<{
// a b c
s2 s1 s0 PUSH3 // a b c a b c
DROP DROP DROP
s1 s0 s2 PUSH3 // a b c b c a
DROP DROP DROP
s0 s2 s1 PUSH3 // a b c c a b
DROP DROP DROP
s0 s2 XCHG // c b a
DROP DROP DROP
10 PUSHINT // _7=10
}>
"""
@fif_codegen_avoid DECLPROC beginCell
@fif_codegen_avoid DECLPROC storeUint
@fif_codegen_avoid DECLPROC storeRef
@fif_codegen_avoid DECLPROC computeDataSize
@fif_codegen_avoid DECLPROC tryIdictGet
@fif_codegen_avoid DECLPROC emptyTuple
@fif_codegen_avoid DECLPROC storeUint1
@fif_codegen_avoid DECLPROC initial2
@fif_codegen_avoid DECLPROC add
-}

View file

@ -0,0 +1,208 @@
;; Here we also test "functions that just wrap other functions" like in camel1.fc,
;; but when they reorder arguments, e.g.
;; > T f(x,y) { return anotherF(y,x); }
;; This also works, even for wrappers of wrappers, even if anotherF is asm(with reorder).
;; But swapping arguments may sometimes lead to bytecode changes (see test2),
;; both with compute-asm-ltr and without it.
#pragma compute-asm-ltr;
builder begin_cell() asm "NEWC";
cell end_cell(builder b) asm "ENDC";
slice begin_parse(cell c) asm "CTOS";
builder store_ref(builder b, cell c) asm(c b) "STREF";
builder beginCell() { return begin_cell(); }
cell endCell(builder b) { return end_cell(b); }
builder storeRef1(builder b, cell c) { return store_ref(b, c); }
builder storeRef2(cell c, builder b) { return store_ref(b, c); }
builder storeUint1(builder b, int x, int bw) { return store_uint(b, x, bw); }
builder storeUint2(builder b, int bw, int x) { return store_uint(b, x, bw); }
(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE";
(int, int, int) computeDataSize1(cell c, int maxCells) impure { return compute_data_size(c, maxCells); }
(int, int, int) computeDataSize2(int maxCells, cell c) impure { return compute_data_size(c, maxCells); }
() throwIf(int condition, int excNo) impure { return throw_if(excNo, condition); }
() fake(int a, int b, int c) impure asm "DROP DROP DROP";
() fake2(int b, int c, int a) { return fake(a,b,c); }
() fake3(int c, int a, int b) { return fake(a,b,c); }
() fake4(int c, int b, int a) { return fake(a,b,c); }
(int, int, int) test1() method_id(101) {
int x = 1;
int y = 1;
cell to_be_ref = beginCell().endCell();
builder in_c = beginCell().storeUint1(123, 8);
in_c = storeRef1(in_c, to_be_ref);
var (a, b, c) = computeDataSize1(in_c.endCell(), 10);
throwIf(0, 101);
return (a, b + x, c + y);
}
(int, int, int) test2() method_id(102) {
int x = 1;
int y = 1;
cell to_be_ref = beginCell().endCell();
builder in_c = beginCell().storeUint2(8, 123);
in_c = storeRef2(to_be_ref, in_c);
var (a, b, c) = computeDataSize2(10, in_c.endCell());
return (a, b + x, c + y);
}
(int, int, int) test3() method_id(103) {
int x = 1;
int y = 1;
cell to_be_ref = begin_cell().end_cell();
builder in_c = begin_cell().store_uint(123, 8);
in_c = store_ref(in_c, to_be_ref);
var (a, b, c) = compute_data_size(in_c.end_cell(), 10);
return (a, b + x, c + y);
}
builder beginCell1() { return begin_cell(); }
builder beginCell11() { return beginCell1(); }
builder beginCell111() { return beginCell11(); }
cell endCell1(builder b) { return end_cell(b); }
cell endCell11(builder b) { return endCell1(b); }
slice beginParse1(cell c) { return begin_parse(c); }
slice beginParse11(cell c) { return beginParse1(c); }
builder storeInt1(builder b, int bw, int x) { return store_int(b, x, bw); }
builder storeInt11(int bw, int x, builder b) { return storeInt1(b, bw, x); }
builder storeInt111(builder b, int x, int bw) { return storeInt11(bw, x, b); }
slice test4() method_id(104) {
builder b = beginCell111();
b = storeInt11(32, 1, b);
b = storeInt111(b, 2, 32).storeInt111(3, 32);
return b.endCell11().beginParse11();
}
(int) test5(int a, int b, int c) method_id(105) {
fake(a, b, c);
fake2(b, c, a);
fake3(c, a, b);
fake4(c, b, a);
return a;
}
() main() {
throw(0);
}
{-
method_id | in | out
TESTCASE | 101 | | 2 9 2
TESTCASE | 102 | | 2 9 2
TESTCASE | 103 | | 2 9 2
TESTCASE | 104 | | CS{Cell{0018000000010000000200000003} bits: 0..96; refs: 0..0}
test1 and test3 fif code is absolutely identical, test2 (due to reorder) is a bit different:
@fif_codegen
"""
test1 PROC:<{
//
NEWC // _5
ENDC // to_be_ref
NEWC // to_be_ref _8
123 PUSHINT // to_be_ref _8 _9=123
SWAP // to_be_ref _9=123 _8
8 STU // to_be_ref in_c
STREF // in_c
ENDC // _16
10 PUSHINT // _16 _17=10
CDATASIZE // a b c
SWAP // a c b
INC // a c _22
SWAP // a _22 c
INC // a _22 _23
}>
"""
@fif_codegen
"""
test2 PROC:<{
//
NEWC // _5
ENDC // to_be_ref
NEWC // to_be_ref _8
123 PUSHINT // to_be_ref _8 _10=123
SWAP // to_be_ref _10=123 _8
8 STU // to_be_ref in_c
STREF // in_c
10 PUSHINT
SWAP
ENDC
SWAP
CDATASIZE // a b c
SWAP // a c b
INC // a c _19
SWAP // a _19 c
INC // a _19 _20
}>
"""
@fif_codegen
"""
test3 PROC:<{
//
NEWC // _5
ENDC // to_be_ref
NEWC // to_be_ref _8
123 PUSHINT // to_be_ref _8 _9=123
SWAP // to_be_ref _9=123 _8
8 STU // to_be_ref in_c
STREF // in_c
ENDC // _16
10 PUSHINT // _16 _17=10
CDATASIZE // a b c
SWAP // a c b
INC // a c _19
SWAP // a _19 c
INC // a _19 _20
}>
"""
@fif_codegen
"""
test4 PROC:<{
//
NEWC // b
1 PUSHINT // b _3=1
SWAP // _3=1 b
32 STI // b
2 PUSHINT
SWAP // _5=2 b
32 STI
3 PUSHINT
SWAP
32 STI // b
ENDC // _11
CTOS // _12
}>
"""
@fif_codegen
"""
test5 PROC:<{
// a b c
s2 s1 s0 PUSH3 // a b c a b c
DROP DROP DROP
s2 s1 s0 PUSH3 // a b c a b c
DROP DROP DROP
s2 s1 s0 PUSH3 // a b c a b c
DROP DROP DROP
s2 PUSH
-ROT // a a b c
DROP DROP DROP
}>
"""
@fif_codegen_avoid storeUint1
@fif_codegen_avoid storeUint2
-}

View file

@ -0,0 +1,104 @@
;; Here we test that if you declare a wrapper like
;; > builder beginCell() { return begin_cell(); }
;; but use it NOT only as a direct call, BUT as a 1-st class function
;; (save to a variable, return from a function, etc.)
;; it also works, since a function becomes codegenerated (though direct calls are expectedly inlined).
builder begin_cell() asm "NEWC";
cell end_cell(builder b) asm "ENDC";
builder store_ref(builder b, cell c) asm(c b) "STREF";
builder beginCell() { return begin_cell(); }
cell endCell(builder b) { return end_cell(b); }
builder storeRef(builder b, cell c) { return store_ref(b, c); }
builder storeUint3(int i, int bw, builder b) { return store_uint(b, i, bw); }
(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE";
(int, int, int) computeDataSize2(int maxCells, cell c) impure { return compute_data_size(c, maxCells); }
tuple empty_tuple() asm "NIL";
forall X -> tuple tpush(tuple t, X value) asm "TPUSH";
forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH";
forall X -> X first(tuple t) asm "FIRST";
tuple emptyTuple() { return empty_tuple(); }
forall X -> tuple tuplePush(tuple t, X value) { return tpush(t, value); }
forall X -> (tuple, ()) ~tuplePush(tuple t, X value) { return ~tpush(t, value); }
forall X -> X tupleGetFirst(tuple t) { return first(t); }
(var, var) getBeginEnd() inline {
return (beginCell, endCell);
}
(builder) begAndStore(beg, store, int x) {
return store(x, 8, beg());
}
(int, int, int) test1() {
var (_, computer) = (0, computeDataSize2);
var (beg, end) = getBeginEnd();
tuple t = emptyTuple();
t~tuplePush(storeRef);
var refStorer = tupleGetFirst(t);
int x = 1;
int y = 1;
cell to_be_ref = beginCell().endCell();
builder in_c = begAndStore(beg, storeUint3, 123);
in_c = refStorer(in_c, to_be_ref);
var (a, b, c) = computer(10, end(in_c));
return (a, b + x, c + y);
}
(int, int, int) main() {
return test1();
}
{-
method_id | in | out
TESTCASE | 0 | | 2 9 2
@fif_codegen DECLPROC beginCell
@fif_codegen DECLPROC computeDataSize2
@fif_codegen
"""
storeUint3 PROC:<{
// i bw b
SWAP // i b bw
STUX // _3
}>
"""
@fif_codegen
"""
storeRef PROC:<{
// b c
SWAP // c b
STREF // _2
}>
"""
@fif_codegen
"""
CONT:<{
computeDataSize2 CALLDICT
}> // computer
getBeginEnd INLINECALLDICT // computer beg end
NIL // computer beg end t
...
NEWC // computer beg end refStorer _19
ENDC // computer beg end refStorer to_be_ref
...
CONT:<{
storeUint3 CALLDICT
}>
...
begAndStore CALLDICT // computer to_be_ref end refStorer in_c
"""
@fif_codegen_avoid emptyTuple
@fif_codegen_avoid tuplePush
-}

View file

@ -0,0 +1,130 @@
;; Here we test that a just-return function is not a valid wrapper, it will not be inlined.
;; (doesn't use all arguments, has different pureness, has method_id, etc.)
builder begin_cell() asm "NEWC";
cell end_cell(builder b) asm "ENDC";
slice begin_parse(cell c) asm "CTOS";
() set_data(cell c) impure asm "c4 POP";
cell get_data() asm "c4 PUSH";
tuple empty_tuple() asm "NIL";
forall X -> (tuple, ()) tpush(tuple t, X x) asm "TPUSH";
builder storeUint(builder b, int x, int unused) { return store_uint(b, x, x); }
() throwIf(int excNo, int cond) impure { throw_if(excNo, cond); }
() throwIf2(int excNo, int cond) { return throw_if(excNo, cond); }
_ initial1(x) { return x; }
_ initial2(x) { return initial1(x); }
tuple asm_func_4(int a, (int, (int, int)) b, int c) asm (b a c -> 0) "5 TUPLE";
_ asmFunc4(int a, (int, (int, int)) b, int c) { return asm_func_4(a, b, c); }
int postpone_elections() {
return false;
}
(int) setAndGetData(int ret) {
cell c = begin_cell().store_uint(ret, 8).end_cell();
set_data(c);
slice s = get_data().begin_parse();
throwIf(101, 0);
var unused = throwIf2(101, 0);
return s~load_uint(8);
}
int setAndGetDataWrapper(int ret) {
return setAndGetData(ret);
}
int test1() method_id(101) {
cell c = begin_cell().storeUint(32, 10000000).end_cell();
slice s = c.begin_parse();
return s~load_uint(32);
}
int test2(int ret) method_id {
return setAndGetDataWrapper(ret);
}
int test3() method_id(103) {
return initial2(10);
}
global tuple t;
int foo(int x) {
t~tpush(x);
return x * 10;
}
(tuple, tuple) test4() method_id(104) {
t = empty_tuple();
tuple t2 = asmFunc4(foo(11), (foo(22), (foo(33), foo(44))), foo(55));
return (t, t2);
}
int test5() method_id(105) {
if (1) {
return postpone_elections();
}
return 123;
}
int main(int ret) {
return setAndGetDataWrapper(ret);
}
int recv_external(int ret) {
return setAndGetData(ret);
}
{-
method_id | in | out
TESTCASE | 101 | | 32
TESTCASE | 103 | | 10
TESTCASE | 104 | | [ 11 22 33 44 55 ] [ 220 330 440 110 550 ]
TESTCASE | 105 | | 0
TESTCASE | 74435 | 99 | 99
TESTCASE | 0 | 98 | 98
TESTCASE | -1 | 97 | 97
@fif_codegen DECLPROC storeUint
@fif_codegen DECLPROC throwIf
@fif_codegen DECLPROC throwIf2
@fif_codegen DECLPROC postpone_elections
@fif_codegen 74435 DECLMETHOD test2
@fif_codegen
"""
test3 PROC:<{
//
10 PUSHINT // _0=10
initial2 CALLDICT // _1
}>
"""
@fif_codegen
"""
test2 PROC:<{
// ret
setAndGetData CALLDICT // _1
}>
"""
@fif_codegen
"""
11 PUSHINT
foo CALLDICT
22 PUSHINT
foo CALLDICT
33 PUSHINT
foo CALLDICT
44 PUSHINT
foo CALLDICT
55 PUSHINT
foo CALLDICT
asmFunc4 CALLDICT // t2
"""
@fif_codegen_avoid setAndGetDataWrapper
-}