Skip to content

Commit 52bcce6

Browse files
committed
[Tolk] Optimize storeUint: merge consecutive constant stores
Before: b.storeUint(0x18, 6) Now: b.storeUint(0,1).storeUint(1,1)... The compiler is able to join consecutive stores of const values. It works at the IR level, not at AST. That's why it works after constant folding: > x = 0; > b.storeUint(x, 10); > x = 1; > if (x==1) b.storeUint(x, 10); Merged to "store 1, 20." Same for skip bits, also merged.
1 parent 26761a1 commit 52bcce6

File tree

10 files changed

+387
-63
lines changed

10 files changed

+387
-63
lines changed

crypto/smartcont/tolk-stdlib/common.tolk

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ fun slice.loadBool(mutate self): bool
664664
/// Shifts a slice pointer to [len] bits forward, mutating the slice.
665665
@pure
666666
fun slice.skipBits(mutate self, len: int): self
667-
asm "SDSKIPFIRST";
667+
builtin;
668668

669669
/// Returns the first `0 ≤ len ≤ 1023` bits of a slice.
670670
@pure
@@ -758,13 +758,13 @@ fun builder.storeAddress(mutate self, addr: address): self
758758
/// Stores amount of Toncoins into a builder.
759759
@pure
760760
fun builder.storeCoins(mutate self, x: coins): self
761-
asm "STGRAMS";
761+
builtin;
762762

763763
/// Stores bool (-1 or 0) into a builder.
764764
/// Attention: true value is `-1`, not 1! If you pass `1` here, TVM will throw an exception.
765765
@pure
766766
fun builder.storeBool(mutate self, x: bool): self
767-
asm(x self) "1 STI";
767+
builtin;
768768

769769
/// Stores dictionary (represented by TVM `cell` or `null`) into a builder.
770770
/// In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise.

tolk-tester/tests/cells-slices.tolk

Lines changed: 185 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ fun test5(): [int,int] {
7777

7878
@method_id(106)
7979
fun test6() {
80-
return beginCell().storeUint(1, 32).storeUint(2, 32).storeUint(3, 32);
80+
var st = beginCell().storeUint(1, 32).storeUint(2, 32).storeUint(3, 32).endCell().beginParse();
81+
return st.loadUint(96) == ((1 << 64) + (2 << 32) + 3);
8182
}
8283

8384
@method_id(107)
@@ -229,6 +230,96 @@ fun test16() {
229230
}
230231

231232

233+
@method_id(117)
234+
fun test17() {
235+
var b = beginCell().storeUint(1, 4).storeCoins(0).storeInt(123, 8);
236+
var s = b.endCell().beginParse();
237+
return (s.loadUint(4), s.loadCoins(), s.loadUint(8));
238+
}
239+
240+
@method_id(118)
241+
fun test18() {
242+
var x = 0;
243+
var b = beginCell();
244+
b = b.storeUint(x, 14);
245+
x += 12;
246+
if (10 > 3) { x += x; }
247+
if (true) {
248+
b.storeInt(x + 2, 8).storeUint(x = match (x) { 24 => 5, else => 0 }, 4);
249+
}
250+
var s = b.endCell().beginParse();
251+
return (s.loadUint(14), s.loadInt(8), s.loadUint(4));
252+
}
253+
254+
fun test19() {
255+
// numbers with potential overflow for STU are not joined, check via codegen
256+
var b = beginCell();
257+
b.storeInt(123, 4).storeUint(0xFF, 8).storeUint(0xFF, 8).storeInt(-1, 8);
258+
return b;
259+
}
260+
261+
@method_id(120)
262+
fun test20() {
263+
var x = false;
264+
var n = 4;
265+
var b = true ? beginCell() : null;
266+
b.storeBool(true).storeBool(x);
267+
b = b.storeBool(true).storeUint(0, n *= 2).storeBool(!!true).storeCoins(0);
268+
var s = b.endCell().beginParse();
269+
return (s.loadBool(), s.loadBool(), s.loadBool(), s.loadUint(8), s.loadBool(), s.loadCoins());
270+
}
271+
272+
fun test21(s: slice) {
273+
// successive skipBits are also joined
274+
var x = 8;
275+
s.skipBits(x);
276+
x -= 4;
277+
s = s.skipBits(x).skipBits(2);
278+
x *= 0;
279+
s = s.skipBits(x);
280+
return s;
281+
}
282+
283+
@method_id(122)
284+
fun test22() {
285+
// different builders aren't mixed, store inside them are joined independently
286+
var (b1, b2) = (beginCell(), beginCell());
287+
b1.storeUint(8, 16).storeUint(8, 8);
288+
b2.storeUint(8, 32).storeUint(1<<88, 100);
289+
return (
290+
b1.endCell().beginParse().remainingBitsCount(),
291+
b2.endCell().beginParse().skipBits(32).loadUint(100),
292+
);
293+
}
294+
295+
@method_id(123)
296+
fun test23(uns: bool) {
297+
// corner values, signed/unsigned 255/256
298+
var b = beginCell();
299+
if (uns) {
300+
b.storeUint(1, 100).storeUint(2, 100).storeInt(3, 55).storeInt(0, 1);
301+
b.storeUint(8, 256);
302+
} else {
303+
b.storeInt(1, 10).storeUint(2, 190).storeInt(3, 54).storeUint(1, 1);
304+
}
305+
return b.bitsCount();
306+
}
307+
308+
@method_id(124)
309+
fun test24(uns: bool) {
310+
// doesn't fit into a single STI/STU instruction, is splitted
311+
var b = beginCell();
312+
if (uns) {
313+
b.storeUint(1, 100).storeUint(2, 100)
314+
.storeInt(3, 100).storeInt(8, 19);
315+
return b.endCell().beginParse().skipBits(200+100).loadInt(19);
316+
} else {
317+
b.storeInt(1, 20).storeUint(2, 200).storeInt(3, 35)
318+
.storeUint(1, 1).storeUint(5, 5).storeUint(10, 10);
319+
return b.endCell().beginParse().skipBits(255+6).loadUint(10);
320+
}
321+
}
322+
232323
fun main(): int {
233324
return 0;
234325
}
@@ -239,6 +330,7 @@ fun main(): int {
239330
@testcase | 103 | 103 | 103
240331
@testcase | 104 | | [ 1 3 ]
241332
@testcase | 105 | | [ 210 1 ]
333+
@testcase | 106 | | -1
242334
@testcase | 107 | | 72 40 72
243335
@testcase | 108 | | 0 40 32
244336
@testcase | 110 | | 64 3 0 0 -1 0 100 -1
@@ -249,20 +341,102 @@ fun main(): int {
249341
@testcase | 114 | 0 | 0 0 0
250342
@testcase | 115 | | 123 456 123 456
251343
@testcase | 116 | | BC{00140008000000ff00000008}
344+
@testcase | 117 | | 1 0 123
345+
@testcase | 118 | | 0 26 5
346+
@testcase | 120 | | -1 0 -1 0 -1 0
347+
@testcase | 122 | | 24 309485009821345068724781056
348+
@testcase | 123 | -1 | 512
349+
@testcase | 123 | 0 | 255
350+
@testcase | 124 | -1 | 8
351+
@testcase | 124 | 0 | 10
352+
353+
We test that consequtive storeInt/storeUint with constants are joined into a single number
252354

253-
Note, that since 'compute-asm-ltr' became on be default, chaining methods codegen is not quite optimal.
254355
@fif_codegen
255356
"""
256357
test6 PROC:<{
257-
1 PUSHINT // '0=1
258-
NEWC // '0=1 '1
259-
32 STU // '1
260-
2 PUSHINT // '1 '4=2
261-
SWAP // '4=2 '1
262-
32 STU // '1
263-
3 PUSHINT // '1 '7=3
264-
SWAP // '7=3 '1
265-
32 STU // '1
358+
18446744082299486211 PUSHINT
359+
NEWC
360+
96 STU // '2
361+
ENDC // '11
362+
"""
363+
364+
@fif_codegen
365+
"""
366+
test17 PROC:<{
367+
4219 PUSHINT
368+
NEWC
369+
16 STU
370+
"""
371+
372+
@fif_codegen
373+
"""
374+
test18 PROC:<{
375+
421 PUSHINT
376+
NEWC
377+
26 STU
378+
"""
379+
380+
@fif_codegen
381+
"""
382+
test19 PROC:<{
383+
NEWC
384+
123 PUSHINT
385+
SWAP
386+
4 STI
387+
16 PUSHPOW2DEC
388+
16 STUR
389+
-1 PUSHINT
390+
SWAP
391+
8 STI
266392
}>
267393
"""
394+
395+
@fif_codegen
396+
"""
397+
test20 PROC:<{
398+
40976 PUSHINT
399+
NEWC
400+
16 STU
401+
"""
402+
403+
@fif_codegen
404+
"""
405+
test21 PROC:<{
406+
14 PUSHINT
407+
SDSKIPFIRST
408+
}>
409+
"""
410+
411+
@fif_codegen
412+
"""
413+
test22 PROC:<{
414+
NEWC // '2
415+
NEWC // b1 b2
416+
SWAP // b2 b1
417+
2056 PUSHINT
418+
24 STUR // b2 b1
419+
SWAP // b1 b2
420+
10141514286835656557042350424064 PUSHINTX
421+
132 STUR // b1 b2
422+
"""
423+
424+
@fif_codegen
425+
"""
426+
test23 PROC:<{
427+
NEWC
428+
SWAP
429+
IF:<{
430+
91343852333181432387730302044911803916571639814 PUSHINT
431+
256 STUR
432+
8 PUSHINT
433+
256 STUR
434+
}>ELSE<{
435+
56539106072908298546665520023773392506479484700019806659963456035401760775 PUSHINT
436+
255 STIR
437+
}>
438+
BBITS
439+
}>
440+
"""
441+
268442
*/

tolk-tester/tests/pack-unpack-1.tolk

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,12 @@ fun main(c: cell) {
107107
@fif_codegen
108108
"""
109109
test3 PROC:<{
110-
20 PUSHINT // yy=20
111-
10 PUSHINT // p.y=20 p.x=10
112-
NEWC // p.y=20 p.x=10 b
113-
32 STI // p.y=20 b
114-
32 STI // b
110+
42949672980 PUSHINT
111+
NEWC
112+
64 STI // b
115113
ENDC // c
116114
CTOS // s
117-
32 PUSHINT // s '15=32
118-
SDSKIPFIRST // s
119-
32 PUSHINT // s '16=32
115+
64 PUSHINT
120116
SDSKIPFIRST // s
121117
SBITS // '17
122118
}>

tolk-tester/tests/strings-tests.tolk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,5 @@ fun test1() {
7878
@testcase | 0 | | 0
7979
@testcase | 101 | | [ 65 66 67 68 ]
8080

81-
@code_hash 58447050269190289721084012139099481162782788646785441106022886746601529758643
81+
@code_hash 61963905046482786665931036224265588524206004864815957521622605110240907698574
8282
*/

tolk/analyzer.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,16 @@ void Op::prepare_args(VarDescrList values) {
678678
}
679679
}
680680

681+
void Op::maybe_swap_builtin_args_to_compile() {
682+
// in builtins.cpp, where optimizing constants are done, implementations assume that args are passed ltr (as declared);
683+
// if a function has arg_order, called arguments might have been put on a stack not ltr, but in asm order;
684+
// here we swap them back before calling FunctionBodyBuiltin compile, and also swap after
685+
tolk_assert(arg_order_already_equals_asm());
686+
if (f_sym->method_name == "storeUint" || f_sym->method_name == "storeInt" || f_sym->method_name == "storeBool") {
687+
std::swap(args[0], args[1]);
688+
}
689+
}
690+
681691
VarDescrList Op::fwd_analyze(VarDescrList values) {
682692
var_info.import_values(values);
683693
switch (cl) {
@@ -705,7 +715,13 @@ VarDescrList Op::fwd_analyze(VarDescrList values) {
705715
}
706716
AsmOpList tmp;
707717
if (!f_sym->is_asm_function()) {
718+
if (arg_order_already_equals_asm()) {
719+
maybe_swap_builtin_args_to_compile();
720+
}
708721
std::get<FunctionBodyBuiltin*>(f_sym->body)->compile(tmp, res, args, loc);
722+
if (arg_order_already_equals_asm()) {
723+
maybe_swap_builtin_args_to_compile();
724+
}
709725
}
710726
int j = 0;
711727
for (var_idx_t i : left) {

0 commit comments

Comments
 (0)